Skip to content

Commit

Permalink
feat: add new commands (#1)
Browse files Browse the repository at this point in the history
* feat: add commands

* feat(config): resolve config

* chore: update

* chore: update

* chore: test
  • Loading branch information
zyyv authored Mar 11, 2023
1 parent 01858b3 commit b975a4f
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules
dist
*.log
untiny.config.ts
mock
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"dependencies": {
"@clack/prompts": "^0.6.3",
"cac": "^6.7.14",
"consola": "^2.15.3",
"picocolors": "^1.0.0",
"tinify": "^1.7.1"
Expand Down
3 changes: 2 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import consola from 'consola'
import { startCli } from './cli-start'
import { startCli } from './ui'

startCli().catch(consola.error)
77 changes: 77 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { dirname, resolve } from 'node:path'
import fs from 'node:fs'
import { createConfigLoader, loadConfig } from 'unconfig'
import { sourcePackageJsonFields } from 'unconfig/presets'
import type { Config } from './types'

const _sources = [
{
files: 'untiny.config',
extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs', 'json', ''],
},
sourcePackageJsonFields({
fields: 'untiny',
}),
]

const _defaults = {
apiKey: '',
}

export async function getConfig(cwd = process.cwd()) {
const { config } = await loadConfig<Config>({
sources: _sources,
cwd,
defaults: _defaults,
merge: true,
})

return config
}

export async function resolveConfig<U extends Config>(
cwd = process.cwd(),
configOrPath: string | U = cwd,
defaults: Config = _defaults,
) {
let inlineConfig = {} as U
if (typeof configOrPath !== 'string') {
inlineConfig = configOrPath
if (inlineConfig.configFile != null && inlineConfig.configFile === false) {
return {
config: inlineConfig as U,
sources: [],
}
}
else {
configOrPath = inlineConfig.configFile || process.cwd()
}
}

const resolved = resolve(configOrPath)

let isFile = false
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) {
isFile = true
cwd = dirname(resolved)
}

const loader = createConfigLoader<Config>({
sources: isFile
? [
{
files: resolved,
extensions: [],
},
]
: _sources,
cwd,
defaults: _defaults,
merge: true,
})

const result = await loader.load()
result.config = Object.assign(defaults, result.config || inlineConfig)

return result
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import consola from 'consola'
import { IMG_EXT } from './constant'
import type { CompressOption } from './types'
import { formatFileName, formatFileSize, isPathValid } from './utils'
import { getConfig } from './cli-start'
import { getConfig } from './config'

export class TinifyCompressor {
private tinifyInstance: typeof tinify
Expand Down
15 changes: 15 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,19 @@ export interface Config {
* @default ''
*/
apiKey: string

/**
* Custom your config file path
*/
configFile?: string | false
}

export interface CliOption {
path?: string | string[]
type: 'image' | 'images' | 'directory'
config?: Config
key?: string
out?: string
debug?: boolean
cwd: string
}
93 changes: 93 additions & 0 deletions src/ui/cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { resolve } from 'node:path'
import cac from 'cac'
import type { Command } from 'cac'
import pkg from '../../package.json'
import type { CliOption } from '../types'
import { resolveConfig } from '../config'
import { createUntiny } from '..'
import { promptUI } from './prompt'

export async function startCli(cwd = process.cwd()) {
const cli = cac('untiny')

const passCommonOptions = (command: Command) => {
return command
.option('-c, --config [file]', 'Config file')
.option('-k, --key <key>', 'Your access key')
.option('-o, --out <out>', 'Output file', { default: './' })
.option('-d, --debug', 'Open debug mode')
}

passCommonOptions(cli.command('ci <path>', 'compress a image'))
.example('untiny ci ./test.png')
.action(async (path, options) => {
compress({
cwd,
path,
type: 'image',
...options,
})
})

passCommonOptions(cli.command('cis <...paths>', 'compress array of images'))
.example('untiny cis ./test.png ./test2.png')
.action(async (path, options) => {
compress({
cwd,
path,
type: 'images',
...options,
})
})

passCommonOptions(cli.command('cd <dir>', 'compress a directory'))
.example('untiny cd ./assets/images')
.action(async (path, options) => {
compress({
cwd,
path,
type: 'directory',
...options,
})
})

cli
.command('ui', 'Untiny Prompt UI')
.action(() => {
promptUI(cwd)
})

cli.help()
cli.version(pkg.version)
cli.parse()
}

async function compress(options: CliOption) {
options.key = options.key || (await resolveConfig(options.cwd, options.config)).config.apiKey
const untinyIns = await createUntiny(options.key)

const handler = (_originPath: string, originImgName: string) => resolve(...[
options.out!.startsWith('/') ? '' : options.cwd,
options.out!,
originImgName,
].filter(Boolean))

if (options.type === 'image') {
await untinyIns.compressImage(options.path as string, {
handler,
debug: options.debug,
})
}
else if (options.type === 'images') {
await untinyIns.compressImages(options.path as string[], {
handler,
debug: options.debug,
})
}
else if (options.type === 'directory') {
await untinyIns.compressDir(options.path as string, {
handler,
debug: options.debug,
})
}
}
1 change: 1 addition & 0 deletions src/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './cmd'
107 changes: 39 additions & 68 deletions src/cli-start.ts → src/ui/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
import { setTimeout } from 'node:timers/promises'
import path from 'node:path'
import { loadConfig } from 'unconfig'
import { sourcePackageJsonFields } from 'unconfig/presets'
import * as p from '@clack/prompts'
import color from 'picocolors'
import type { Config } from './types'
import { formatFileSize, isDir, isFile, isPathValid } from './utils'
import { TinifyCompressor } from '.'
import { formatFileSize, isDir, isFile, isPathValid } from '../utils'
import { createUntiny } from '..'

export async function getConfig(cwd = process.cwd()) {
const { config } = await loadConfig<Config>({
sources: [
{
files: 'untiny.config',
extensions: ['ts', 'mts', 'cts', 'js', 'mjs', 'cjs', 'json', ''],
async function commandProcess() {
return await p.group(
{
input: () =>
p.text({
message: 'Compressed resource paths?',
placeholder: './src/assets or ./src/assets/test/img.png',
validate: (value) => {
if (!value)
return 'Please enter a path.'
if (!isPathValid(value))
return 'Please enter a relative path.'
},
}),
output: () =>
p.text({
message: 'Where would you like to output?',
placeholder: 'default current',
validate: (value) => {
if (value && !isPathValid(value))
return 'Please enter a relative path.'
},
}),
debug: () =>
p.confirm({
message: 'Would you like to open debug mode?',
initialValue: false,
})
,
},
{
onCancel: () => {
p.cancel('Operation cancelled.')
process.exit(1)
},
sourcePackageJsonFields({
fields: 'untiny',
}),
],
cwd,
defaults: {
apiKey: '',
},
merge: true,
})

return config
)
}

export async function startCli(cwd = process.cwd()) {
export async function promptUI(cwd = process.cwd()) {
// eslint-disable-next-line no-console
console.clear()
await setTimeout(1000)
Expand All @@ -44,7 +59,7 @@ export async function startCli(cwd = process.cwd()) {
else if (project.output.startsWith('.'))
project.output = path.resolve(cwd, project.output)

const tinifyIns = await getTinifyIns()
const tinifyIns = await createUntiny()

const s = p.spinner()
if (await isDir(project.input)) {
Expand Down Expand Up @@ -79,47 +94,3 @@ Diff : ${color.yellow(formatFileSize(tinifyIns.TotalBeforeSize - tinifyIns.Tota

p.outro(`Problems? ${color.underline(color.cyan('https://github.com/zyyv/untinyimg/issues'))}`)
}

async function commandProcess() {
return await p.group(
{
input: () =>
p.text({
message: 'Compressed resource paths?',
placeholder: './src/assets or ./src/assets/test/img.png',
validate: (value) => {
if (!value)
return 'Please enter a path.'
if (!isPathValid(value))
return 'Please enter a relative path.'
},
}),
output: () =>
p.text({
message: 'Where would you like to output?',
placeholder: 'default current',
validate: (value) => {
if (value && !isPathValid(value))
return 'Please enter a relative path.'
},
}),
debug: () =>
p.confirm({
message: 'Would you like to open debug mode?',
initialValue: false,
})
,
},
{
onCancel: () => {
p.cancel('Operation cancelled.')
process.exit(1)
},
},
)
}

async function getTinifyIns() {
const { apiKey } = await getConfig()
return new TinifyCompressor(apiKey)
}
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ export function formatFileName(name: string, length = 12, ellipsis = '...') {
const r = length - ellipsis.length - l
return `${nameWithoutExt.slice(0, l)}${ellipsis}${nameWithoutExt.slice(nameLength - r)}${ext}`
}

export function toArray<T>(val: T | T[]): T[] {
return Array.isArray(val) ? val : [val]
}
2 changes: 1 addition & 1 deletion tests/fixtures/configs/untiny.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default {
apiKey: 'RWDMNgkQJGldpgBVhn5MJ2944cHxN2CK',
apiKey: 'qweqweqwe',
}
Loading

0 comments on commit b975a4f

Please sign in to comment.