typesync
Version:
Install missing TypeScript typings for your dependencies.
307 lines (297 loc) • 10.1 kB
JavaScript
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