@bensandee/tooling 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.mjs +151 -96
- package/package.json +2 -2
package/dist/bin.mjs
CHANGED
|
@@ -437,29 +437,31 @@ function writeFile(targetDir, relativePath, content) {
|
|
|
437
437
|
function createContext(config, confirmOverwrite) {
|
|
438
438
|
const archivedFiles = [];
|
|
439
439
|
const pkgRaw = readFile(config.targetDir, "package.json");
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
archivedFiles.push(rel);
|
|
453
|
-
}
|
|
440
|
+
const ctx = {
|
|
441
|
+
config,
|
|
442
|
+
targetDir: config.targetDir,
|
|
443
|
+
packageJson: pkgRaw ? parsePackageJson(pkgRaw) : void 0,
|
|
444
|
+
exists: (rel) => fileExists(config.targetDir, rel),
|
|
445
|
+
read: (rel) => readFile(config.targetDir, rel),
|
|
446
|
+
write: (rel, content) => {
|
|
447
|
+
if (!rel.startsWith(".tooling-archived/")) {
|
|
448
|
+
const existing = readFile(config.targetDir, rel);
|
|
449
|
+
if (existing !== void 0 && existing !== content) {
|
|
450
|
+
writeFile(config.targetDir, `.tooling-archived/${rel}`, existing);
|
|
451
|
+
archivedFiles.push(rel);
|
|
454
452
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
453
|
+
}
|
|
454
|
+
writeFile(config.targetDir, rel, content);
|
|
455
|
+
if (rel === "package.json") ctx.packageJson = parsePackageJson(content);
|
|
456
|
+
},
|
|
457
|
+
remove: (rel) => {
|
|
458
|
+
const fullPath = path.join(config.targetDir, rel);
|
|
459
|
+
if (existsSync(fullPath)) rmSync(fullPath);
|
|
462
460
|
},
|
|
461
|
+
confirmOverwrite
|
|
462
|
+
};
|
|
463
|
+
return {
|
|
464
|
+
ctx,
|
|
463
465
|
archivedFiles
|
|
464
466
|
};
|
|
465
467
|
}
|
|
@@ -472,20 +474,22 @@ function createDryRunContext(config) {
|
|
|
472
474
|
const pkgRaw = readFile(config.targetDir, "package.json");
|
|
473
475
|
const pendingWrites = /* @__PURE__ */ new Map();
|
|
474
476
|
const shadow = /* @__PURE__ */ new Map();
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
},
|
|
486
|
-
remove: () => {},
|
|
487
|
-
confirmOverwrite: async () => "overwrite"
|
|
477
|
+
const ctx = {
|
|
478
|
+
config,
|
|
479
|
+
targetDir: config.targetDir,
|
|
480
|
+
packageJson: pkgRaw ? parsePackageJson(pkgRaw) : void 0,
|
|
481
|
+
exists: (rel) => shadow.has(rel) || fileExists(config.targetDir, rel),
|
|
482
|
+
read: (rel) => shadow.get(rel) ?? readFile(config.targetDir, rel),
|
|
483
|
+
write: (rel, content) => {
|
|
484
|
+
pendingWrites.set(rel, content);
|
|
485
|
+
shadow.set(rel, content);
|
|
486
|
+
if (rel === "package.json") ctx.packageJson = parsePackageJson(content);
|
|
488
487
|
},
|
|
488
|
+
remove: () => {},
|
|
489
|
+
confirmOverwrite: async () => "overwrite"
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
ctx,
|
|
489
493
|
pendingWrites
|
|
490
494
|
};
|
|
491
495
|
}
|
|
@@ -562,8 +566,8 @@ function addReleaseDeps(deps, config) {
|
|
|
562
566
|
function getAddedDevDepNames(config) {
|
|
563
567
|
const deps = { ...ROOT_DEV_DEPS };
|
|
564
568
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
565
|
-
deps["@bensandee/config"] = "0.7.
|
|
566
|
-
deps["@bensandee/tooling"] = "0.
|
|
569
|
+
deps["@bensandee/config"] = "0.7.1";
|
|
570
|
+
deps["@bensandee/tooling"] = "0.9.0";
|
|
567
571
|
if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
|
|
568
572
|
if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
|
|
569
573
|
addReleaseDeps(deps, config);
|
|
@@ -583,8 +587,8 @@ async function generatePackageJson(ctx) {
|
|
|
583
587
|
if (ctx.config.releaseStrategy !== "none") allScripts["trigger-release"] = "pnpm exec tooling release:trigger";
|
|
584
588
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
585
589
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
586
|
-
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.7.
|
|
587
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
590
|
+
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.7.1";
|
|
591
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.9.0";
|
|
588
592
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.0";
|
|
589
593
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
|
|
590
594
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
|
|
@@ -2058,15 +2062,107 @@ async function generateLefthook(ctx) {
|
|
|
2058
2062
|
const SCHEMA_NPM_PATH = "@bensandee/config/schemas/forgejo-workflow.schema.json";
|
|
2059
2063
|
const SCHEMA_LOCAL_PATH = ".vscode/forgejo-workflow.schema.json";
|
|
2060
2064
|
const SETTINGS_PATH = ".vscode/settings.json";
|
|
2065
|
+
const SCHEMA_GLOB = ".forgejo/workflows/*.yml";
|
|
2061
2066
|
const VscodeSettingsSchema = z.object({ "yaml.schemas": z.record(z.string(), z.unknown()).default({}) }).passthrough();
|
|
2067
|
+
const FullWorkspaceFileSchema = z.object({ settings: z.record(z.string(), z.unknown()).default({}) }).passthrough();
|
|
2062
2068
|
function readSchemaFromNodeModules(targetDir) {
|
|
2063
2069
|
const candidate = path.join(targetDir, "node_modules", SCHEMA_NPM_PATH);
|
|
2064
2070
|
if (!existsSync(candidate)) return void 0;
|
|
2065
2071
|
return readFileSync(candidate, "utf-8");
|
|
2066
2072
|
}
|
|
2073
|
+
/** Find a *.code-workspace file in the target directory. */
|
|
2074
|
+
function findWorkspaceFile(targetDir) {
|
|
2075
|
+
try {
|
|
2076
|
+
return readdirSync(targetDir).find((e) => e.endsWith(".code-workspace"));
|
|
2077
|
+
} catch {
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2067
2081
|
function serializeSettings(settings) {
|
|
2068
2082
|
return JSON.stringify(settings, null, 2) + "\n";
|
|
2069
2083
|
}
|
|
2084
|
+
const YamlSchemasSchema = z.record(z.string(), z.unknown());
|
|
2085
|
+
/** Merge yaml.schemas into a settings object. Returns the result and whether anything changed. */
|
|
2086
|
+
function mergeYamlSchemas(settings) {
|
|
2087
|
+
const parsed = YamlSchemasSchema.safeParse(settings["yaml.schemas"]);
|
|
2088
|
+
const yamlSchemas = parsed.success ? { ...parsed.data } : {};
|
|
2089
|
+
if (SCHEMA_LOCAL_PATH in yamlSchemas) return {
|
|
2090
|
+
merged: settings,
|
|
2091
|
+
changed: false
|
|
2092
|
+
};
|
|
2093
|
+
yamlSchemas[SCHEMA_LOCAL_PATH] = SCHEMA_GLOB;
|
|
2094
|
+
return {
|
|
2095
|
+
merged: {
|
|
2096
|
+
...settings,
|
|
2097
|
+
"yaml.schemas": yamlSchemas
|
|
2098
|
+
},
|
|
2099
|
+
changed: true
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
function writeSchemaToWorkspaceFile(ctx, wsFileName) {
|
|
2103
|
+
const raw = ctx.read(wsFileName);
|
|
2104
|
+
if (!raw) return {
|
|
2105
|
+
filePath: wsFileName,
|
|
2106
|
+
action: "skipped",
|
|
2107
|
+
description: "Could not read workspace file"
|
|
2108
|
+
};
|
|
2109
|
+
const fullParsed = FullWorkspaceFileSchema.safeParse(parse(raw));
|
|
2110
|
+
if (!fullParsed.success) return {
|
|
2111
|
+
filePath: wsFileName,
|
|
2112
|
+
action: "skipped",
|
|
2113
|
+
description: "Could not parse workspace file"
|
|
2114
|
+
};
|
|
2115
|
+
const { merged, changed } = mergeYamlSchemas(fullParsed.data.settings);
|
|
2116
|
+
if (!changed) return {
|
|
2117
|
+
filePath: wsFileName,
|
|
2118
|
+
action: "skipped",
|
|
2119
|
+
description: "Already has Forgejo schema mapping"
|
|
2120
|
+
};
|
|
2121
|
+
const updated = {
|
|
2122
|
+
...fullParsed.data,
|
|
2123
|
+
settings: merged
|
|
2124
|
+
};
|
|
2125
|
+
ctx.write(wsFileName, JSON.stringify(updated, null, 2) + "\n");
|
|
2126
|
+
return {
|
|
2127
|
+
filePath: wsFileName,
|
|
2128
|
+
action: "updated",
|
|
2129
|
+
description: "Added Forgejo workflow schema mapping to workspace settings"
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
function writeSchemaToSettings(ctx) {
|
|
2133
|
+
if (ctx.exists(SETTINGS_PATH)) {
|
|
2134
|
+
const raw = ctx.read(SETTINGS_PATH);
|
|
2135
|
+
if (!raw) return {
|
|
2136
|
+
filePath: SETTINGS_PATH,
|
|
2137
|
+
action: "skipped",
|
|
2138
|
+
description: "Could not read existing settings"
|
|
2139
|
+
};
|
|
2140
|
+
const parsed = VscodeSettingsSchema.safeParse(parse(raw));
|
|
2141
|
+
if (!parsed.success) return {
|
|
2142
|
+
filePath: SETTINGS_PATH,
|
|
2143
|
+
action: "skipped",
|
|
2144
|
+
description: "Could not parse existing settings"
|
|
2145
|
+
};
|
|
2146
|
+
const { merged, changed } = mergeYamlSchemas(parsed.data);
|
|
2147
|
+
if (!changed) return {
|
|
2148
|
+
filePath: SETTINGS_PATH,
|
|
2149
|
+
action: "skipped",
|
|
2150
|
+
description: "Already has Forgejo schema mapping"
|
|
2151
|
+
};
|
|
2152
|
+
ctx.write(SETTINGS_PATH, serializeSettings(merged));
|
|
2153
|
+
return {
|
|
2154
|
+
filePath: SETTINGS_PATH,
|
|
2155
|
+
action: "updated",
|
|
2156
|
+
description: "Added Forgejo workflow schema mapping"
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
ctx.write(SETTINGS_PATH, serializeSettings({ "yaml.schemas": { [SCHEMA_LOCAL_PATH]: SCHEMA_GLOB } }));
|
|
2160
|
+
return {
|
|
2161
|
+
filePath: SETTINGS_PATH,
|
|
2162
|
+
action: "created",
|
|
2163
|
+
description: "Generated .vscode/settings.json with Forgejo workflow schema"
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2070
2166
|
async function generateVscodeSettings(ctx) {
|
|
2071
2167
|
const results = [];
|
|
2072
2168
|
if (ctx.config.ci !== "forgejo") {
|
|
@@ -2100,54 +2196,8 @@ async function generateVscodeSettings(ctx) {
|
|
|
2100
2196
|
description: "Copied Forgejo workflow schema from @bensandee/config"
|
|
2101
2197
|
});
|
|
2102
2198
|
}
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2105
|
-
const raw = ctx.read(SETTINGS_PATH);
|
|
2106
|
-
if (!raw) {
|
|
2107
|
-
results.push({
|
|
2108
|
-
filePath: SETTINGS_PATH,
|
|
2109
|
-
action: "skipped",
|
|
2110
|
-
description: "Could not read existing settings"
|
|
2111
|
-
});
|
|
2112
|
-
return results;
|
|
2113
|
-
}
|
|
2114
|
-
const parsed = VscodeSettingsSchema.safeParse(JSON.parse(raw));
|
|
2115
|
-
if (!parsed.success) {
|
|
2116
|
-
results.push({
|
|
2117
|
-
filePath: SETTINGS_PATH,
|
|
2118
|
-
action: "skipped",
|
|
2119
|
-
description: "Could not parse existing settings"
|
|
2120
|
-
});
|
|
2121
|
-
return results;
|
|
2122
|
-
}
|
|
2123
|
-
const existing = parsed.data;
|
|
2124
|
-
const yamlSchemas = existing["yaml.schemas"];
|
|
2125
|
-
if (SCHEMA_LOCAL_PATH in yamlSchemas) {
|
|
2126
|
-
results.push({
|
|
2127
|
-
filePath: SETTINGS_PATH,
|
|
2128
|
-
action: "skipped",
|
|
2129
|
-
description: "Already has Forgejo schema mapping"
|
|
2130
|
-
});
|
|
2131
|
-
return results;
|
|
2132
|
-
}
|
|
2133
|
-
yamlSchemas[SCHEMA_LOCAL_PATH] = schemaGlob;
|
|
2134
|
-
ctx.write(SETTINGS_PATH, serializeSettings({
|
|
2135
|
-
...existing,
|
|
2136
|
-
"yaml.schemas": yamlSchemas
|
|
2137
|
-
}));
|
|
2138
|
-
results.push({
|
|
2139
|
-
filePath: SETTINGS_PATH,
|
|
2140
|
-
action: "updated",
|
|
2141
|
-
description: "Added Forgejo workflow schema mapping"
|
|
2142
|
-
});
|
|
2143
|
-
} else {
|
|
2144
|
-
ctx.write(SETTINGS_PATH, serializeSettings({ "yaml.schemas": { [SCHEMA_LOCAL_PATH]: schemaGlob } }));
|
|
2145
|
-
results.push({
|
|
2146
|
-
filePath: SETTINGS_PATH,
|
|
2147
|
-
action: "created",
|
|
2148
|
-
description: "Generated .vscode/settings.json with Forgejo workflow schema"
|
|
2149
|
-
});
|
|
2150
|
-
}
|
|
2199
|
+
const wsFile = findWorkspaceFile(ctx.targetDir);
|
|
2200
|
+
results.push(wsFile ? writeSchemaToWorkspaceFile(ctx, wsFile) : writeSchemaToSettings(ctx));
|
|
2151
2201
|
return results;
|
|
2152
2202
|
}
|
|
2153
2203
|
//#endregion
|
|
@@ -2355,7 +2405,7 @@ async function runInit(config, options = {}) {
|
|
|
2355
2405
|
const promptPath = ".tooling-migrate.md";
|
|
2356
2406
|
ctx.write(promptPath, prompt);
|
|
2357
2407
|
p.log.info(`Migration prompt written to ${promptPath}`);
|
|
2358
|
-
p.log.info("
|
|
2408
|
+
p.log.info("In Claude Code, run: \"Execute the steps in .tooling-migrate.md\"");
|
|
2359
2409
|
}
|
|
2360
2410
|
const bensandeeDeps = getAddedDevDepNames(config).filter((name) => name.startsWith("@bensandee/"));
|
|
2361
2411
|
const hasLockfile = ctx.exists("pnpm-lock.yaml");
|
|
@@ -2376,7 +2426,7 @@ async function runInit(config, options = {}) {
|
|
|
2376
2426
|
"2. Run: pnpm typecheck",
|
|
2377
2427
|
"3. Run: pnpm build",
|
|
2378
2428
|
"4. Run: pnpm test",
|
|
2379
|
-
...options.noPrompt ? [] : ["5.
|
|
2429
|
+
...options.noPrompt ? [] : ["5. In Claude Code, run: \"Execute the steps in .tooling-migrate.md\""]
|
|
2380
2430
|
].join("\n"), "Next steps");
|
|
2381
2431
|
return results;
|
|
2382
2432
|
}
|
|
@@ -2398,9 +2448,12 @@ const updateCommand = defineCommand({
|
|
|
2398
2448
|
});
|
|
2399
2449
|
async function runUpdate(targetDir) {
|
|
2400
2450
|
const saved = loadToolingConfig(targetDir);
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2451
|
+
if (!saved) {
|
|
2452
|
+
p.log.error("No .tooling.json found. Run `tooling repo:init` first to initialize the project.");
|
|
2453
|
+
process.exitCode = 1;
|
|
2454
|
+
return [];
|
|
2455
|
+
}
|
|
2456
|
+
return runInit(mergeWithSavedConfig(buildDefaultConfig(targetDir, {}), saved), {
|
|
2404
2457
|
noPrompt: true,
|
|
2405
2458
|
confirmOverwrite: async () => "overwrite"
|
|
2406
2459
|
});
|
|
@@ -2424,9 +2477,11 @@ const checkCommand = defineCommand({
|
|
|
2424
2477
|
});
|
|
2425
2478
|
async function runCheck(targetDir) {
|
|
2426
2479
|
const saved = loadToolingConfig(targetDir);
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2480
|
+
if (!saved) {
|
|
2481
|
+
p.log.error("No .tooling.json found. Run `tooling repo:init` first to initialize the project.");
|
|
2482
|
+
return 1;
|
|
2483
|
+
}
|
|
2484
|
+
const { ctx, pendingWrites } = createDryRunContext(mergeWithSavedConfig(buildDefaultConfig(targetDir, {}), saved));
|
|
2430
2485
|
const actionable = (await runGenerators(ctx)).filter((r) => r.action === "created" || r.action === "updated");
|
|
2431
2486
|
if (actionable.length === 0) {
|
|
2432
2487
|
p.log.success("Repository is up to date.");
|
|
@@ -3152,7 +3207,7 @@ function mergeGitHub(dryRun) {
|
|
|
3152
3207
|
const main = defineCommand({
|
|
3153
3208
|
meta: {
|
|
3154
3209
|
name: "tooling",
|
|
3155
|
-
version: "0.
|
|
3210
|
+
version: "0.9.0",
|
|
3156
3211
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
3157
3212
|
},
|
|
3158
3213
|
subCommands: {
|
|
@@ -3165,7 +3220,7 @@ const main = defineCommand({
|
|
|
3165
3220
|
"release:merge": releaseMergeCommand
|
|
3166
3221
|
}
|
|
3167
3222
|
});
|
|
3168
|
-
console.log(`@bensandee/tooling v0.
|
|
3223
|
+
console.log(`@bensandee/tooling v0.9.0`);
|
|
3169
3224
|
runMain(main);
|
|
3170
3225
|
//#endregion
|
|
3171
3226
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tooling": "./dist/bin.mjs"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"tsdown": "0.21.0",
|
|
34
34
|
"typescript": "5.9.3",
|
|
35
35
|
"vitest": "4.0.18",
|
|
36
|
-
"@bensandee/config": "0.7.
|
|
36
|
+
"@bensandee/config": "0.7.1"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsdown",
|