diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..bcbf612 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,25 @@ +name: Publish Package to npmjs + +on: + push: + branches: ["main"] +env: + PNPM_VERSION: 7.2.0 + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@v3 + with: + node-version: "16.x" + registry-url: "https://registry.npmjs.org" + scope: "@cesarlai" + - run: npm i -g pnpm@${PNPM_VERSION} + - run: pnpm install + - run: pnpm build + - run: pnpm run publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index bef95c5..8e16f8a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# picgo-plugin-image-name-format +# picgo-plugin-filename-format -A picgo plugin to format filename by your setting. \ No newline at end of file +A picgo plugin to format filename by your setting. diff --git a/package.json b/package.json index 287d9ee..4ed3ab9 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "picgo-plugin-image-name-format", + "name": "picgo-plugin-filename-format", "version": "0.1.0", "author": "Cesar Lai", "description": "A picgo plugin to format filename by your setting.", @@ -18,7 +18,7 @@ "package.json", "LICENSE" ], - "repository": "git@github.com:CesarLai/picgo-plugin-image-name-format.git", + "repository": "git@github.com:CesarLai/picgo-plugin-filename-format.git", "publishConfig": { "access": "public" }, @@ -32,10 +32,12 @@ "build": "rimraf dist && tspc" }, "dependencies": { - "dayjs": "1.11.10" + "dayjs": "1.11.10", + "uuid": "9.0.1" }, "devDependencies": { "@types/node": "16.9.1", + "@types/uuid": "9.0.7", "picgo": "1.5.0-alpha.13", "rimraf": "5.0.5", "ts-patch": "3.1.1", @@ -43,4 +45,4 @@ "typescript": "5.0.2", "typescript-transform-paths": "3.4.6" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a2e875..ff975c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,18 +1,20 @@ lockfileVersion: '6.0' -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - dependencies: dayjs: specifier: 1.11.10 version: 1.11.10 + uuid: + specifier: ^9.0.1 + version: 9.0.1 devDependencies: '@types/node': specifier: 16.9.1 version: 16.9.1 + '@types/uuid': + specifier: ^9.0.7 + version: 9.0.7 picgo: specifier: 1.5.0-alpha.13 version: 1.5.0-alpha.13 @@ -146,6 +148,10 @@ packages: '@types/node': 16.9.1 dev: true + /@types/uuid@9.0.7: + resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==} + dev: true + /agentkeepalive@4.5.0: resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} @@ -2145,6 +2151,11 @@ packages: unescape: 1.0.1 dev: true + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -2212,3 +2223,7 @@ packages: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 dev: true + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false diff --git a/src/beforeTransformPlugins.ts b/src/beforeTransformPlugins.ts index 65ad528..32fc906 100644 --- a/src/beforeTransformPlugins.ts +++ b/src/beforeTransformPlugins.ts @@ -1,6 +1,6 @@ import type { IPicGo } from 'picgo' import fs from 'fs' -import { isUrl } from './util' +import { isUrl } from '@/utils' /** * beforeTransformPlugins handle function @@ -9,7 +9,7 @@ const handleBeforeTransformPlugins = (ctx: IPicGo) => { ctx.input = ctx.input.map((item) => { if (Buffer.isBuffer(item)) { return item - } else if (typeof item === 'string' && !isUrl(item)) { + } else if (typeof item === 'string' && fs.existsSync(item)) { return fs.readFileSync(item) } diff --git a/src/beforeUploadPlugins.ts b/src/beforeUploadPlugins.ts index 170cde9..03c5716 100644 --- a/src/beforeUploadPlugins.ts +++ b/src/beforeUploadPlugins.ts @@ -1,4 +1,8 @@ import type { IPicGo } from 'picgo' +import { PLUGIN_NAME } from '@/constants' +import { parsePluginSetting } from '@/utils' +import { formatterMap } from '@/formatters' +import { PluginSetting } from '@/types' /** * beforeUploadPlugins handle function @@ -6,9 +10,17 @@ import type { IPicGo } from 'picgo' const handleBeforeUploadPlugins = (ctx: IPicGo) => { ctx.output = ctx.output.map((item) => { if (typeof item === 'object' && item.fileName) { + const fixedFileName = item.fileName.replace(/}$/, '') + const config = ctx.getConfig(PLUGIN_NAME) as PluginSetting + const fullConfig = parsePluginSetting(config) + const formatedFileName = + typeof formatterMap[fullConfig.format] === 'function' + ? formatterMap[fullConfig.format](fixedFileName, fullConfig) + : fixedFileName + return { ...item, - fileName: item.fileName.replace(/}$/, '') + fileName: formatedFileName } } return item diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..ff658f1 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1 @@ +export const PLUGIN_NAME = 'picgo-plugin-filename-format' diff --git a/src/formatters/hash.ts b/src/formatters/hash.ts new file mode 100644 index 0000000..310777f --- /dev/null +++ b/src/formatters/hash.ts @@ -0,0 +1,47 @@ +import { createHash } from 'crypto' +import { getExt } from '@/utils' +import { Formatter, FormatterFunc, HashFormatOptions } from '@/types' + +const getRandomString = (length = 8) => { + if (length <= 0) { + return '' + } + + const charPool = 'abcdefghijklmnopqrstuvwxyz0123456789' + const randomChars: string[] = [] + let i = length + while (i > 0) { + const randomIndex = (Math.floor(Math.random() * 100) % charPool.length) - 1 + randomChars.push(charPool[randomIndex]) + i-- + } + + return randomChars.join('') +} + +const generateHashFileName = ( + originName: string, + type: string, + length: number +) => { + const ext = getExt(originName) + const randomString = getRandomString(8) + const timestamp = new Date().getTime() + const hash = createHash(type) + .update(`${timestamp}_${originName}_${randomString}`) + .digest() + .toString('hex') + .substring(0, length) + return `${hash}${ext}` +} + +export const hashFormatter: FormatterFunc = (originName, config) => { + const { type, length } = config.options as HashFormatOptions + const hashType = type || 'sha256' + const hashLength = typeof length === 'number' && length > 0 ? length : 32 + return generateHashFileName(originName, hashType, hashLength) +} + +Reflect.defineProperty(hashFormatter, 'formatterType', { value: 'hash' }) + +export default hashFormatter as Formatter diff --git a/src/formatters/index.ts b/src/formatters/index.ts new file mode 100644 index 0000000..e3f90a8 --- /dev/null +++ b/src/formatters/index.ts @@ -0,0 +1,18 @@ +import originFormatter from './origin' +import hashFormatter from './hash' +import uuidFormatter from './uuid' +import timeFormatter from './time' +import timestampFormatter from './timestamp' + +export const formatterMap = [ + originFormatter, + hashFormatter, + uuidFormatter, + timeFormatter, + timestampFormatter +].reduce((map, formatter) => { + return { + ...map, + [formatter.formatterType]: formatter + } +}, {}) diff --git a/src/formatters/origin.ts b/src/formatters/origin.ts new file mode 100644 index 0000000..2452b8e --- /dev/null +++ b/src/formatters/origin.ts @@ -0,0 +1,7 @@ +import { Formatter, FormatterFunc } from '@/types' + +const originFormatter: FormatterFunc = (originName) => originName + +Reflect.defineProperty(originFormatter, 'formatterType', { value: 'origin' }) + +export default originFormatter as Formatter diff --git a/src/formatters/time.ts b/src/formatters/time.ts new file mode 100644 index 0000000..bffdd9e --- /dev/null +++ b/src/formatters/time.ts @@ -0,0 +1,44 @@ +import dayjs from 'dayjs' +import { + Formatter, + FormatterFunc, + PluginSetting, + TimeFormatOptions +} from '@/types' +import originFormatter from './origin' +import hashFormatter from './hash' +import timestampFormatter from './timestamp' +import uuidFormatter from './uuid' + +const generalFileName = ( + format: TimeFormatOptions['nameFormat'], + originName: string, + config: PluginSetting +) => { + switch (format) { + case 'hash': + return hashFormatter(originName, config) + case 'uuid': + return uuidFormatter(originName, config) + case 'timestamp': + return timestampFormatter(originName, config) + default: + return originFormatter(originName, config) + } +} + +export const timeFormatter: FormatterFunc = (originName, config) => { + const { timeFormat, nameFormat, nameOptions } = (config.options || {}) as TimeFormatOptions + const parsedTimeFormat = timeFormat || 'YYYY-MM-DD' + const parsedNameFormat = nameFormat || 'origin' + const fileName = generalFileName(parsedNameFormat, originName, { + ...config, + format: parsedNameFormat, + options: nameOptions + }) + return `${dayjs().format(parsedTimeFormat)}/${fileName}` +} + +Reflect.defineProperty(timeFormatter, 'formatterType', { value: 'time' }) + +export default timeFormatter as Formatter diff --git a/src/formatters/timestamp.ts b/src/formatters/timestamp.ts new file mode 100644 index 0000000..0e61061 --- /dev/null +++ b/src/formatters/timestamp.ts @@ -0,0 +1,18 @@ +import { getExt } from '@/utils' +import { Formatter, FormatterFunc, TimestampFormatOptions } from '@/types' + +const timestampFormatter: FormatterFunc = (originName, config) => { + const ext = getExt(originName) + const { length } = (config.options || {}) as TimestampFormatOptions + let timestamp = new Date().getTime() + if (length === 10) { + timestamp = Math.ceil(timestamp / 1000) + } + return `${timestamp}${ext}` +} + +Reflect.defineProperty(timestampFormatter, 'formatterType', { + value: 'timestamp' +}) + +export default timestampFormatter as Formatter diff --git a/src/formatters/uuid.ts b/src/formatters/uuid.ts new file mode 100644 index 0000000..f8fe7e4 --- /dev/null +++ b/src/formatters/uuid.ts @@ -0,0 +1,12 @@ +import { v4 as uuidv4 } from 'uuid' +import { getExt } from '@/utils' +import { Formatter, FormatterFunc } from '@/types' + +export const uuidFormatter: FormatterFunc = (originName) => { + const ext = getExt(originName) + return `${uuidv4()}${ext}` +} + +Reflect.defineProperty(uuidFormatter, 'formatterType', { value: 'uuid' }) + +export default uuidFormatter as Formatter diff --git a/src/index.ts b/src/index.ts index d88ff00..d44103e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import type { IPicGo } from 'picgo' import handleBeforeTransformPlugins from './beforeTransformPlugins' import handleBeforeUploadPlugins from './beforeUploadPlugins' -const PLUGIN_NAME: Readonly = 'image-name-format' +const PLUGIN_NAME: Readonly = 'filename-format' /** * picgo image filename format plugin diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5dd08be --- /dev/null +++ b/src/types.ts @@ -0,0 +1,47 @@ +export type FormatType = 'timestamp' | 'time' | 'hash' | 'uuid' | 'origin' + +export interface TimestampFormatOptions { + length?: 10 | 13 +} + +export interface TimeFormatOptions { + timeFormat?: string + nameFormat?: Exclude + nameOptions?: any +} + +export interface HashFormatOptions { + type?: string + length?: number +} + +export interface BasePluginSetting { + format: T + options?: O +} + +export type TimestampPluginSetting = BasePluginSetting< + 'timestamp', + TimestampFormatOptions +> + +export type TimePluginSetting = BasePluginSetting<'time', TimeFormatOptions> + +export type HashPluginSetting = BasePluginSetting<'hash', HashFormatOptions> + +export type UuidPluginSetting = BasePluginSetting<'uuid', undefined> + +export type OriginPluginSetting = BasePluginSetting<'origin', undefined> + +export type PluginSetting = + | TimestampPluginSetting + | TimePluginSetting + | HashPluginSetting + | UuidPluginSetting + | OriginPluginSetting + +export type FormatterFunc = (originName: string, config: PluginSetting) => string +export type Formatter = FormatterFunc & { + // (originName: string, config: PluginSetting): string + formatterType: string +} diff --git a/src/utils/helper.ts b/src/utils/helper.ts new file mode 100644 index 0000000..663fc4e --- /dev/null +++ b/src/utils/helper.ts @@ -0,0 +1,15 @@ +import { PluginSetting } from '@/types' + +const DefaultPluginSetting: Readonly = { + format: 'origin' +} + +export const parsePluginSetting = ( + setting: PluginSetting | undefined +): PluginSetting => { + return setting?.format ? setting : DefaultPluginSetting +} + +export const getExt = (fileName: string) => { + return /(\.[a-zA-Z]+)?$/.exec(fileName)?.[1] || '' +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..95d3cf7 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './helper' +export * from './url' diff --git a/src/util.ts b/src/utils/url.ts similarity index 100% rename from src/util.ts rename to src/utils/url.ts