UNPKG

@neodx/pkg-misc

Version:

Collection of utilities for common cases in package development - smart dependencies updates, simple formatting with auto-detection of prettier config, etc.

1 lines 13.9 kB
{"version":3,"file":"index.mjs","sources":["../../src/semver.ts","../../src/package-json.ts","../../src/prettier.ts"],"sourcesContent":["import { entries, hasOwn } from '@neodx/std';\nimport { coerce, gt } from 'semver';\n\nconst NON_SEMVER_PRIORITY = {\n '*': 2,\n next: 1,\n latest: 0,\n previous: -1,\n legacy: -2\n};\n\n/**\n * Check if incoming version is greater than existing version (including non-semver versions).\n * @param incoming Incoming version\n * @param existing Existing version\n * @return True if incoming version is greater than existing version\n */\nexport function isGreaterVersion(incoming: string, existing: string) {\n const incomingIsNotSemver = hasOwn(NON_SEMVER_PRIORITY, incoming);\n const existingIsNotSemver = hasOwn(NON_SEMVER_PRIORITY, existing);\n\n if (incomingIsNotSemver && existingIsNotSemver) {\n return NON_SEMVER_PRIORITY[incoming] > NON_SEMVER_PRIORITY[existing];\n }\n\n if (incomingIsNotSemver || existingIsNotSemver) {\n return true;\n }\n\n return gt(coerce(incoming)!, coerce(existing)!);\n}\n\n/**\n * Get dependencies that have been upgraded compared to current dependencies.\n * @param changes Object with dependencies to upgrade\n * @param current Object with current dependencies\n * @return Object with dependencies that have been upgraded or null if nothing has been upgraded\n * @example\n * getUpgradedDependenciesVersions(\n * { dependencies: { a: '^1.2.3', b: '^2.0.0' } },\n * { dependencies: { a: '^1.2.3', b: '^1.0.0' } } // b is outdated\n * );\n * // { dependencies: { b: '^2.0.0' } }\n * getUpgradedDependenciesVersions(\n * { dependencies: { a: '^1.2.3', b: '^2.0.0' } },\n * { dependencies: { a: '^1.2.3', b: '^2.0.0' } } // nothing is outdated\n * );\n * // null\n */\nexport function getUpgradedDependenciesVersions(\n changes: Record<string, string>,\n current: Record<string, string>\n) {\n const applied = entries(changes).filter(\n ([name, version]) => hasOwn(current, name) && isGreaterVersion(version, current[name]!)\n );\n\n return applied.length > 0 ? Object.fromEntries(applied) : null;\n}\n","import { entries, filterObject, hasOwn, isTruthy, keys, sortObjectByKeys } from '@neodx/std';\nimport { getUpgradedDependenciesVersions } from './semver';\n\n/**\n * Add any missing dependency to package.json content.\n * @param current Current package.json content\n * @param updates Updates to apply\n * @return Object with new content if changed and null otherwise\n * @example\n * addPackageJsonDependencies({}, { dependencies: { a: '^1.2.3' } });\n * // { dependencies: { a: '^1.2.3' } }\n * addPackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { dependencies: { a: '^1.2.0' } });\n * // null\n * addPackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { dependencies: { a: '^1.3.0', b: '^2.0.0' } });\n * // { dependencies: { a: '^1.3.0', b: '^2.0.0' } }\n * addPackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { devDependencies: { a: '^1.3.0', b: '^2.0.0' } });\n * // null\n */\nexport function addPackageJsonDependencies(\n current: PackageJsonDependencies,\n updates: PackageJsonDependencies\n) {\n const affected = entries(updates)\n .map(([type, dependencies]) => {\n const missed = lookupMissedDependencies(type, keys(dependencies), current);\n const outdated = lookupOutdatedDependencies(dependencies, current[type] ?? null);\n\n if (missed.length > 0 || outdated) {\n return [\n type,\n filterObject(\n dependencies,\n (_, name) => missed.includes(name) || Boolean(outdated?.[name])\n )\n ] as const;\n }\n return null;\n })\n .filter(isTruthy);\n\n if (affected.length > 0) {\n return sortPackageJson({\n ...current,\n ...Object.fromEntries(\n affected.map(([type, dependencies]) => [\n type,\n {\n ...current[type]!,\n ...dependencies\n }\n ])\n )\n });\n }\n return null;\n}\n\n/**\n * Removes provided dependencies from package.json.\n * @param current Current package.json content\n * @param updates Specific dependencies to remove\n * @return Object with new content if changed and null otherwise\n * @example\n * removePackageJsonDependencies({}, { dependencies: ['a'] });\n * // null\n * removePackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { dependencies: ['a'] });\n * // { dependencies: {} }\n * removePackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { dependencies: ['b'] });\n * // null\n * removePackageJsonDependencies({ dependencies: { a: '^1.2.3' } }, { dependencies: ['a', 'b'] });\n */\nexport function removePackageJsonDependencies(\n current: PackageJsonDependencies,\n updates: Partial<Record<DependencyTypeName, string[]>>\n) {\n const affected = entries(updates)\n .map(([type, names]) => {\n const removing = names.filter(name => hasOwn(current[type] ?? {}, name));\n\n return removing.length > 0 ? ([type, removing] as const) : null;\n })\n .filter(isTruthy);\n\n if (affected.length > 0) {\n return sortPackageJson({\n ...current,\n ...Object.fromEntries(\n affected.map(([type, removing]) => [\n type,\n filterObject(current[type]!, (_, name) => !removing.includes(name))\n ])\n )\n });\n }\n return null;\n}\n\nexport function sortPackageJson<T extends PackageJsonDependencies>(value: T) {\n const next = {\n ...value\n };\n\n for (const type of dependenciesTypes) {\n if (!hasOwn(next, type)) continue;\n if (keys(next[type]).length === 0) {\n delete next[type];\n } else {\n next[type] = sortObjectByKeys(next[type]!);\n }\n }\n\n return sortObjectByKeys(next);\n}\n\nfunction lookupOutdatedDependencies(\n incomingDependencies: Record<string, string>,\n existingDependencies: Record<string, string> | null\n) {\n return existingDependencies\n ? getUpgradedDependenciesVersions(incomingDependencies, existingDependencies)\n : null;\n}\n\nfunction lookupMissedDependencies(\n dependenciesType: DependencyTypeName,\n dependenciesNames: string[],\n currentPackageJsonDeps: PackageJsonDependencies\n) {\n const lookupPriority = dependenciesLookupPriority[dependenciesType];\n\n return dependenciesNames.filter(name =>\n lookupPriority.every(type => !hasOwn(currentPackageJsonDeps[type] ?? {}, name))\n );\n}\n\nconst dependenciesLookupPriority: Record<DependencyTypeName, DependencyTypeName[]> = {\n dependencies: ['dependencies', 'devDependencies'],\n devDependencies: ['devDependencies', 'dependencies'],\n peerDependencies: ['peerDependencies', 'dependencies'],\n optionalDependencies: ['optionalDependencies', 'dependencies', 'peerDependencies']\n};\nconst dependenciesTypes = keys(dependenciesLookupPriority);\n\nexport type PackageJsonDependencies = Partial<Record<DependencyTypeName, Record<string, string>>>;\nexport type DependencyTypeName =\n | 'dependencies'\n | 'devDependencies'\n | 'peerDependencies'\n | 'optionalDependencies';\n","import { isFile } from '@neodx/fs';\nimport { resolve } from 'pathe';\nimport type { Options } from 'prettier';\n\nexport interface TransformPrettierOptions {\n (path: string, options: Options): Partial<Options> | void;\n}\n\n/**\n * Tries to format a file with prettier and returns the formatted content or null if it fails.\n * @param path Path to the file\n * @param content Content of the file\n * @param transform Optional function to transform prettier options\n * @return Formatted content or null if it fails or the file is ignored\n * @example\n * tryFormatPrettier('package.json', JSON.stringify({ a: 1, b: 2 }, null, 2));\n * // {\n * // \"a\": 1,\n * // \"b\": 2\n * // }\n * tryFormatPrettier('src/index.ts', 'const a=11,b=22;');\n * // const a = 11;\n * // const b = 22;\n * tryFormatPrettier('ignored.ts', 'const a=11,b=22;');\n * // null\n */\nexport async function tryFormatPrettier(\n path: string,\n content: string,\n transform: TransformPrettierOptions = markSwcRcAsJson\n) {\n const prettier = await tryImportPrettier();\n\n if (!prettier) {\n return null;\n }\n const configPath = await prettier.resolveConfigFile(path);\n // TODO: Cache .prettierignore lookup\n const possibleIgnorePath = configPath && resolve(configPath, '../.prettierignore');\n const resolvedOptions = await prettier.resolveConfig(path, {\n editorconfig: true\n });\n\n if (!resolvedOptions) {\n return null;\n }\n const prettierOptions: Options = {\n filepath: path,\n ...resolvedOptions\n };\n\n Object.assign(prettierOptions, transform(path, prettierOptions));\n const support = await prettier.getFileInfo(path, {\n ignorePath:\n possibleIgnorePath && (await isFile(possibleIgnorePath)) ? possibleIgnorePath : undefined\n });\n\n if (support.ignored || !support.inferredParser) {\n return null;\n }\n try {\n // eslint-disable-next-line @typescript-eslint/return-await\n return prettier.format(content, prettierOptions);\n } catch (error) {\n if (!(error instanceof Error)) {\n throw error;\n }\n console.warn(`Could not format ${path} with prettier. Error: \"${error.message}\"`);\n return null;\n }\n}\n\nconst tryImportPrettier = () =>\n import('prettier').then(module => module.default ?? module).catch(() => null);\n\nconst markSwcRcAsJson = (path: string) => (path.endsWith('.swcrc') ? { parser: 'json' } : {});\n"],"names":["NON_SEMVER_PRIORITY","next","latest","previous","legacy","isGreaterVersion","incoming","existing","incomingIsNotSemver","hasOwn","existingIsNotSemver","gt","coerce","getUpgradedDependenciesVersions","changes","current","applied","entries","filter","name","version","length","Object","fromEntries","addPackageJsonDependencies","updates","affected","map","type","dependencies","existingDependencies","missed","lookupMissedDependencies","dependenciesType","dependenciesNames","currentPackageJsonDeps","lookupPriority","dependenciesLookupPriority","every","keys","outdated","filterObject","_","includes","Boolean","isTruthy","sortPackageJson","removePackageJsonDependencies","names","removing","value","dependenciesTypes","sortObjectByKeys","devDependencies","peerDependencies","optionalDependencies","tryFormatPrettier","path","content","transform","markSwcRcAsJson","prettier","tryImportPrettier","configPath","resolveConfigFile","possibleIgnorePath","resolve","resolvedOptions","resolveConfig","editorconfig","prettierOptions","filepath","assign","support","getFileInfo","ignorePath","isFile","undefined","ignored","inferredParser","format","error","Error","console","warn","message","then","module","default","catch","endsWith","parser"],"mappings":"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"}