UNPKG

typesync

Version:

Install missing TypeScript typings for your dependencies.

307 lines (297 loc) 10.1 kB
import { createConfigService, createPackageSource, createTypeSyncer, uniq } from "./type-syncer-D40qMMFq.js"; import * as path$1 from "node:path"; import * as path from "node:path"; import { InjectionMode, asFunction, createContainer } from "awilix"; import { blue, bold, cyan, gray, green, magenta, red, white } from "ansis"; import { Spinner } from "picospinner"; import * as yaml from "yaml"; import { glob } from "tinyglobby"; import * as fsp from "node:fs/promises"; import { readFile } from "node:fs/promises"; import detectIndent from "detect-indent"; //#region rolldown:runtime var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name$1 in all) __defProp(target, name$1, { get: all[name$1], enumerable: true }); }; //#endregion //#region src/cli-util.ts function log(message) { console.log(`${white`»`} ${gray(message)}`); } function success(text) { console.log(`${green`✔`} ${white(text)}`); } function error(err) { const msg = err instanceof Error ? err.message : err; const stack = err instanceof Error ? `\nStack:\n${err.stack}` : ""; console.log(`${red`✖`} ${white.bgRed(msg)}${stack}`); } async function spinWhile(text, fn) { const spinner = new Spinner({ text: gray` ${text}`, symbolFormatter: blue }); spinner.start(); return await fn().finally(() => { spinner.stop(); }); } function parseArguments(argv) { const flags = {}; const args = []; for (const arg of argv) if (arg.startsWith("--")) if (arg.includes("=")) { const idx = arg.indexOf("="); const afterEq = arg.substring(idx + 1); flags[arg.substring(2, idx)] = afterEq; } else flags[arg.substring(2)] = true; else args.push(arg); return { flags, args }; } //#endregion //#region src/globber.ts function createGlobber() { return { async globDirs(root, patterns, ignore = []) { const source = await glob(patterns, { cwd: root, ignore: ["**/node_modules/**", ...ignore], onlyDirectories: true }); return uniq(source); } }; } //#endregion //#region src/fs-utils.ts var fs_utils_exports = {}; __export(fs_utils_exports, { readFileContents: () => readFileContents }); async function readFileContents(filePath) { try { return await readFile(filePath, "utf-8"); } catch (err) { if (err.code === "ENOENT") throw new Error(`${filePath} does not exist.`); throw err; } } //#endregion //#region src/package-json-file-service.ts function createPackageJSONFileService() { return { readPackageFile: async (filePath) => { const contents = await readFileContents(filePath); return JSON.parse(contents); }, writePackageFile: async (filePath, fileContent) => { const contents = await readFileContents(filePath); const { indent } = detectIndent(contents); const trailingNewline = contents.length ? contents.endsWith("\n") : false; const data = JSON.stringify(fileContent, null, indent || " "); await fsp.writeFile(filePath, data + (trailingNewline ? "\n" : "")); } }; } //#endregion //#region src/workspace-resolver.ts function createWorkspaceResolverService({ readFileContents: readFileContents$1 }) { return { getWorkspaces: async (packageJson, root, globber, ignored) => { const workspaces = await getWorkspaces(packageJson, root); return await globber.globDirs(root, ensureWorkspacesArray(workspaces), ignored); } }; async function getWorkspaces(packageJson, root) { const packageJsonWorkspaces = packageJson.workspaces; if (packageJsonWorkspaces !== void 0) return packageJsonWorkspaces; return await getPnpmWorkspaces(root); } async function getPnpmWorkspaces(root) { try { const filePath = path$1.join(root, "pnpm-workspace.yaml"); const contents = await readFileContents$1(filePath); const pnpmWorkspaces = yaml.parse(contents); return pnpmWorkspaces.packages; } catch { return void 0; } } } function ensureWorkspacesArray(data) { if (!data) return []; if (!Array.isArray(data)) return ensureWorkspacesArray(data.packages); if (!data.every((s) => typeof s === "string")) return []; return data; } //#endregion //#region package.json var name = "typesync"; var version = "0.14.3"; var type = "module"; var description = "Install missing TypeScript typings for your dependencies."; var engines = { "node": "^18.20.0 || ^20.10.0 || >=22.0.0" }; var files = ["dist", "bin"]; var bin = { "typesync": "./bin/typesync" }; var exports = { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } }; var scripts = { "test": "vitest run", "test:watch": "vitest --ui", "fix": "eslint --fix && npm run format", "lint": "eslint && tsc && npm run format:check", "format": "prettier --write 'src/**/*.ts'", "format:check": "prettier --check 'src/**/*.ts'", "lint:watch": "nodemon --exec npm run lint", "build": "tsdown", "build:watch": "tsdown --watch", "run-cli": "npm run build -- --silent && node bin/typesync", "run-cli:dry": "npm run run-cli -- --dry", "do:publish": "npm run lint && npm run test && npm run build && npm publish", "release:patch": "npm version patch && npm run do:publish && git push --follow-tags", "release:minor": "npm version minor && npm run do:publish && git push --follow-tags", "release:prerelease": "npm version prerelease && npm run do:publish && git push --follow-tags" }; var repository = { "type": "git", "url": "git+https://github.com/jeffijoe/typesync.git" }; var author = "Jeff Hansen <jeff@jeffijoe.com>"; var license = "MIT"; var bugs = { "url": "https://github.com/jeffijoe/typesync/issues" }; var homepage = "https://github.com/jeffijoe/typesync#readme"; var dependencies = { "ansis": "^3.17.0", "awilix": "^12.0.5", "detect-indent": "^7.0.1", "lilconfig": "^3.1.3", "npm-registry-fetch": "^18.0.2", "picospinner": "^3.0.0", "semver": "^7.7.0", "tinyglobby": "^0.2.12", "yaml": "^2.7.1" }; var devDependencies = { "@eslint/js": "^9.23.0", "@types/node": "^22.13.17", "@types/npm-registry-fetch": "^8.0.7 || ~18.0.0", "@types/semver": "~7.7.0", "@vitest/coverage-v8": "^3.1.1", "@vitest/ui": "^3.1.1", "eslint": "^9.23.0", "globals": "^16.0.0", "nodemon": "^3.1.9", "prettier": "^3.5.3", "publint": "^0.3.9", "tsdown": "^0.7.2", "typescript": "^5.8.2", "typescript-eslint": "^8.29.0", "unplugin-unused": "^0.4.4", "vitest": "^3.1.1" }; var packageManager = "npm@10.8.2"; var package_default = { name, version, type, description, engines, files, bin, exports, scripts, repository, author, license, bugs, homepage, dependencies, devDependencies, packageManager }; //#endregion //#region src/cli.ts async function startCli() { try { const container = createContainer({ injectionMode: InjectionMode.CLASSIC }).register({ packageJSONService: asFunction(createPackageJSONFileService).singleton(), workspaceResolverService: asFunction(() => createWorkspaceResolverService(fs_utils_exports)).singleton(), packageSource: asFunction(createPackageSource).singleton(), configService: asFunction(createConfigService).singleton(), globber: asFunction(createGlobber).singleton(), typeSyncer: asFunction(createTypeSyncer) }); await run(container.resolve("typeSyncer")); } catch (err) { error(err); process.exitCode = 1; } } /** * Actual CLI runner. Uses the `syncer` instance to sync. * @param syncer */ async function run(syncer) { const { args, flags } = parseArguments(process.argv.slice(2)); const [filePath = "package.json"] = args; if (flags.help) { printHelp(); return; } log(`TypeSync v${white(package_default.version)}`); if (flags.dry) log("—— DRY RUN — will not modify file ——"); const result = await spinWhile(`Syncing type definitions in ${cyan(filePath)}...`, async () => await syncer.sync(filePath, flags)); const syncedFilesOutput = result.syncedFiles.map(renderSyncedFile).join("\n\n"); const totals = result.syncedFiles.reduce((accum, f) => ({ newTypings: accum.newTypings + f.newTypings.length }), { newTypings: 0 }); const syncMessage = `\n\n${syncedFilesOutput}\n\n✨ Run ${green`typesync`} again without the ${gray`--dry`} flag to update your ${gray`package.json`}.`; if (flags.dry === "fail" && totals.newTypings > 0) { error("Typings changed; check failed."); log(syncMessage); process.exitCode = 1; return; } success(totals.newTypings === 0 ? `No new typings to add, looks like you're all synced up!` : flags.dry ? `${totals.newTypings.toString()} new typings can be added.${syncMessage}` : `${totals.newTypings.toString()} new typings added.\n\n${syncedFilesOutput}\n\n✨ Go ahead and run ${green`npm install`}, ${green`yarn`}, or ${green`pnpm i`} to install the packages that were added.`); } /** * Renders a type definition. * @param typeDef * @param isLast */ function renderTypeDef(typeDef, isLast) { const treeNode = isLast ? "└─" : "├─"; return `${treeNode} ${green.bold`+`} ${gray`@types/`}${bold.blue(typeDef.typingsName)}`; } /** * Renders a synced file. * * @param file */ function renderSyncedFile(file) { const badge = file.newTypings.length === 0 ? blue.bold`(no new typings added)` : green.bold`(${file.newTypings.length.toString()} new typings added)`; const dirName = path.basename(path.dirname(path.resolve(file.filePath))); const title = `📦 ${file.package.name ?? dirName} ${gray.italic`— ${file.filePath}`} ${badge}`; const nl = "\n"; const combined = [...file.newTypings.map((t) => ({ ...t, action: "add" }))]; const rendered = title + nl + combined.map((t) => renderTypeDef(t, combined[combined.length - 1] === t)).join(nl); return rendered; } /** * Prints the help text. */ function printHelp() { console.log(` ${blue.bold`typesync`} - adds missing TypeScript definitions to package.json Options ${magenta.bold`--dry`} dry run, won't save the package.json ${magenta.bold`--ignoredeps=<deps|dev|peer|optional>`} ignores dependencies in the specified sections (comma separate for multiple). Example: ${magenta`ignoredeps=dev,peer`} ${magenta.bold`--help`} shows this help menu `.trim()); } //#endregion export { startCli }; //# sourceMappingURL=cli.js.map