@bensandee/tooling 0.8.0 → 0.8.1
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 +166 -78
- package/dist/index.mjs +1 -1
- package/package.json +5 -5
package/dist/bin.mjs
CHANGED
|
@@ -8,7 +8,6 @@ import JSON5 from "json5";
|
|
|
8
8
|
import { parse } from "jsonc-parser";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
|
|
11
|
-
|
|
12
11
|
//#region src/types.ts
|
|
13
12
|
/** Default CI platform when not explicitly chosen. */
|
|
14
13
|
const DEFAULT_CI = "forgejo";
|
|
@@ -19,7 +18,6 @@ const LEGACY_TOOLS = [
|
|
|
19
18
|
"webpack",
|
|
20
19
|
"rollup"
|
|
21
20
|
];
|
|
22
|
-
|
|
23
21
|
//#endregion
|
|
24
22
|
//#region src/utils/json.ts
|
|
25
23
|
const StringRecord = z.record(z.string(), z.string());
|
|
@@ -83,7 +81,6 @@ function parsePackageJson(raw) {
|
|
|
83
81
|
return;
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
|
-
|
|
87
84
|
//#endregion
|
|
88
85
|
//#region src/utils/detect.ts
|
|
89
86
|
const LEGACY_PATTERNS = {
|
|
@@ -205,7 +202,6 @@ function getMonorepoPackages(targetDir) {
|
|
|
205
202
|
} catch (_error) {}
|
|
206
203
|
return results;
|
|
207
204
|
}
|
|
208
|
-
|
|
209
205
|
//#endregion
|
|
210
206
|
//#region src/prompts/init-prompts.ts
|
|
211
207
|
function isCancelled(value) {
|
|
@@ -416,7 +412,6 @@ function buildDefaultConfig(targetDir, flags) {
|
|
|
416
412
|
targetDir
|
|
417
413
|
};
|
|
418
414
|
}
|
|
419
|
-
|
|
420
415
|
//#endregion
|
|
421
416
|
//#region src/utils/fs.ts
|
|
422
417
|
/** Check whether a file exists at the given path. */
|
|
@@ -494,7 +489,6 @@ function createDryRunContext(config) {
|
|
|
494
489
|
pendingWrites
|
|
495
490
|
};
|
|
496
491
|
}
|
|
497
|
-
|
|
498
492
|
//#endregion
|
|
499
493
|
//#region src/generators/package-json.ts
|
|
500
494
|
const STANDARD_SCRIPTS_SINGLE = {
|
|
@@ -568,8 +562,8 @@ function addReleaseDeps(deps, config) {
|
|
|
568
562
|
function getAddedDevDepNames(config) {
|
|
569
563
|
const deps = { ...ROOT_DEV_DEPS };
|
|
570
564
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
571
|
-
deps["@bensandee/config"] = "
|
|
572
|
-
deps["@bensandee/tooling"] = "
|
|
565
|
+
deps["@bensandee/config"] = "0.7.0";
|
|
566
|
+
deps["@bensandee/tooling"] = "0.8.1";
|
|
573
567
|
if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
|
|
574
568
|
if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
|
|
575
569
|
addReleaseDeps(deps, config);
|
|
@@ -589,9 +583,9 @@ async function generatePackageJson(ctx) {
|
|
|
589
583
|
if (ctx.config.releaseStrategy !== "none") allScripts["trigger-release"] = "pnpm exec tooling release:trigger";
|
|
590
584
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
591
585
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
592
|
-
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "
|
|
593
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "
|
|
594
|
-
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "
|
|
586
|
+
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.7.0";
|
|
587
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.8.1";
|
|
588
|
+
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.0";
|
|
595
589
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
|
|
596
590
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
|
|
597
591
|
addReleaseDeps(devDeps, ctx.config);
|
|
@@ -617,9 +611,9 @@ async function generatePackageJson(ctx) {
|
|
|
617
611
|
for (const [key, value] of Object.entries(devDeps)) if (!(key in existingDevDeps)) {
|
|
618
612
|
existingDevDeps[key] = value;
|
|
619
613
|
changes.push(`added devDependency: ${key}`);
|
|
620
|
-
} else if (key.startsWith("@bensandee/") &&
|
|
621
|
-
existingDevDeps[key] =
|
|
622
|
-
changes.push(`updated devDependency: ${key} to
|
|
614
|
+
} else if (key.startsWith("@bensandee/") && existingDevDeps[key] !== value && existingDevDeps[key] !== "workspace:*") {
|
|
615
|
+
existingDevDeps[key] = value;
|
|
616
|
+
changes.push(`updated devDependency: ${key} to ${value}`);
|
|
623
617
|
}
|
|
624
618
|
pkg.devDependencies = existingDevDeps;
|
|
625
619
|
if (!pkg["engines"]) {
|
|
@@ -655,7 +649,6 @@ async function generatePackageJson(ctx) {
|
|
|
655
649
|
description: "Generated package.json"
|
|
656
650
|
};
|
|
657
651
|
}
|
|
658
|
-
|
|
659
652
|
//#endregion
|
|
660
653
|
//#region src/generators/migrate-prompt.ts
|
|
661
654
|
/**
|
|
@@ -825,7 +818,6 @@ function generateMigratePrompt(results, config, detected) {
|
|
|
825
818
|
sections.push("");
|
|
826
819
|
return sections.join("\n");
|
|
827
820
|
}
|
|
828
|
-
|
|
829
821
|
//#endregion
|
|
830
822
|
//#region src/generators/tsconfig.ts
|
|
831
823
|
async function generateTsconfig(ctx) {
|
|
@@ -993,7 +985,6 @@ function generateMonorepoPackageTsconfigs(ctx) {
|
|
|
993
985
|
}
|
|
994
986
|
return results;
|
|
995
987
|
}
|
|
996
|
-
|
|
997
988
|
//#endregion
|
|
998
989
|
//#region src/generators/vitest.ts
|
|
999
990
|
const VITEST_CONFIG = `import { defineConfig } from "vitest/config";
|
|
@@ -1062,7 +1053,6 @@ async function generateVitest(ctx) {
|
|
|
1062
1053
|
}
|
|
1063
1054
|
return results;
|
|
1064
1055
|
}
|
|
1065
|
-
|
|
1066
1056
|
//#endregion
|
|
1067
1057
|
//#region src/generators/oxlint.ts
|
|
1068
1058
|
const CONFIG_WITH_LINT_RULES = `import recommended from "@bensandee/config/oxlint/recommended";
|
|
@@ -1102,7 +1092,6 @@ async function generateOxlint(ctx) {
|
|
|
1102
1092
|
description: "Generated oxlint.config.ts"
|
|
1103
1093
|
};
|
|
1104
1094
|
}
|
|
1105
|
-
|
|
1106
1095
|
//#endregion
|
|
1107
1096
|
//#region src/generators/formatter.ts
|
|
1108
1097
|
const OXFMT_CONFIG = `{}\n`;
|
|
@@ -1146,7 +1135,6 @@ async function generatePrettier(ctx) {
|
|
|
1146
1135
|
description: "Generated .prettierrc"
|
|
1147
1136
|
};
|
|
1148
1137
|
}
|
|
1149
|
-
|
|
1150
1138
|
//#endregion
|
|
1151
1139
|
//#region src/generators/tsdown.ts
|
|
1152
1140
|
const TSDOWN_CONFIG = `import { defineConfig } from "tsdown";
|
|
@@ -1182,7 +1170,6 @@ async function generateTsdown(ctx) {
|
|
|
1182
1170
|
description: "Generated tsdown.config.ts"
|
|
1183
1171
|
};
|
|
1184
1172
|
}
|
|
1185
|
-
|
|
1186
1173
|
//#endregion
|
|
1187
1174
|
//#region src/generators/gitignore.ts
|
|
1188
1175
|
/** Entries that every project should have — repo:check flags these as missing. */
|
|
@@ -1236,7 +1223,6 @@ async function generateGitignore(ctx) {
|
|
|
1236
1223
|
description: "Generated .gitignore"
|
|
1237
1224
|
};
|
|
1238
1225
|
}
|
|
1239
|
-
|
|
1240
1226
|
//#endregion
|
|
1241
1227
|
//#region src/generators/ci.ts
|
|
1242
1228
|
function hasEnginesNode$1(ctx) {
|
|
@@ -1244,9 +1230,9 @@ function hasEnginesNode$1(ctx) {
|
|
|
1244
1230
|
if (!raw) return false;
|
|
1245
1231
|
return typeof parsePackageJson(raw)?.engines?.["node"] === "string";
|
|
1246
1232
|
}
|
|
1247
|
-
function ciWorkflow(isMonorepo, nodeVersionYaml) {
|
|
1233
|
+
function ciWorkflow(isMonorepo, nodeVersionYaml, isForgejo) {
|
|
1248
1234
|
return `name: CI
|
|
1249
|
-
on:
|
|
1235
|
+
${isForgejo ? "\nenable-email-notifications: true\n" : ""}on:
|
|
1250
1236
|
push:
|
|
1251
1237
|
branches: [main]
|
|
1252
1238
|
pull_request:
|
|
@@ -1300,7 +1286,7 @@ async function generateCi(ctx) {
|
|
|
1300
1286
|
const isGitHub = ctx.config.ci === "github";
|
|
1301
1287
|
const nodeVersionYaml = hasEnginesNode$1(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
|
|
1302
1288
|
const filePath = isGitHub ? ".github/workflows/check.yml" : ".forgejo/workflows/check.yml";
|
|
1303
|
-
const content = ciWorkflow(isMonorepo, nodeVersionYaml);
|
|
1289
|
+
const content = ciWorkflow(isMonorepo, nodeVersionYaml, !isGitHub);
|
|
1304
1290
|
if (ctx.exists(filePath)) {
|
|
1305
1291
|
const existing = ctx.read(filePath);
|
|
1306
1292
|
if (existing && !existing.includes("repo:check")) {
|
|
@@ -1327,7 +1313,6 @@ async function generateCi(ctx) {
|
|
|
1327
1313
|
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions CI workflow`
|
|
1328
1314
|
};
|
|
1329
1315
|
}
|
|
1330
|
-
|
|
1331
1316
|
//#endregion
|
|
1332
1317
|
//#region src/generators/knip.ts
|
|
1333
1318
|
const KNIP_CONFIG_SINGLE = `import type { KnipConfig } from "knip";
|
|
@@ -1380,7 +1365,6 @@ async function generateKnip(ctx) {
|
|
|
1380
1365
|
description: "Generated knip.config.ts for dead code analysis"
|
|
1381
1366
|
};
|
|
1382
1367
|
}
|
|
1383
|
-
|
|
1384
1368
|
//#endregion
|
|
1385
1369
|
//#region src/generators/renovate.ts
|
|
1386
1370
|
const SHARED_PRESET = "local>bensandee/tooling";
|
|
@@ -1447,7 +1431,6 @@ async function generateRenovate(ctx) {
|
|
|
1447
1431
|
description: "Generated renovate.json extending shared config"
|
|
1448
1432
|
};
|
|
1449
1433
|
}
|
|
1450
|
-
|
|
1451
1434
|
//#endregion
|
|
1452
1435
|
//#region src/generators/pnpm-workspace.ts
|
|
1453
1436
|
const WORKSPACE_YAML = `packages:
|
|
@@ -1472,7 +1455,6 @@ async function generatePnpmWorkspace(ctx) {
|
|
|
1472
1455
|
description: "Generated pnpm-workspace.yaml"
|
|
1473
1456
|
};
|
|
1474
1457
|
}
|
|
1475
|
-
|
|
1476
1458
|
//#endregion
|
|
1477
1459
|
//#region src/generators/claude-settings.ts
|
|
1478
1460
|
const ClaudeSettingsSchema = z.object({
|
|
@@ -1557,12 +1539,8 @@ function buildSettings(ctx) {
|
|
|
1557
1539
|
if (ctx.config.structure === "monorepo") allow.push(`Bash(${pm} --filter *)`, `Bash(${pm} -r *)`);
|
|
1558
1540
|
const enabledPlugins = { "code-simplifier@claude-plugins-official": true };
|
|
1559
1541
|
const extraKnownMarketplaces = {};
|
|
1560
|
-
if (ctx.config.structure
|
|
1561
|
-
enabledPlugins["
|
|
1562
|
-
extraKnownMarketplaces["anthropic-agent-skills"] = { source: {
|
|
1563
|
-
source: "github",
|
|
1564
|
-
repo: "anthropics/skills"
|
|
1565
|
-
} };
|
|
1542
|
+
if (ctx.config.structure !== "monorepo") {
|
|
1543
|
+
if (ctx.packageJson ? packageHasWebUIDeps(ctx.packageJson) : false) enabledPlugins["frontend-design@claude-plugins-official"] = true;
|
|
1566
1544
|
}
|
|
1567
1545
|
return {
|
|
1568
1546
|
permissions: {
|
|
@@ -1626,17 +1604,15 @@ function buildSettings(ctx) {
|
|
|
1626
1604
|
};
|
|
1627
1605
|
}
|
|
1628
1606
|
/** Remove enabledPlugins/extraKnownMarketplaces when empty to keep the file clean. */
|
|
1629
|
-
function serializeSettings(settings) {
|
|
1607
|
+
function serializeSettings$1(settings) {
|
|
1630
1608
|
const { enabledPlugins, extraKnownMarketplaces, ...rest } = settings;
|
|
1631
1609
|
const out = { ...rest };
|
|
1632
1610
|
if (Object.keys(enabledPlugins).length > 0) out["enabledPlugins"] = enabledPlugins;
|
|
1633
1611
|
if (Object.keys(extraKnownMarketplaces).length > 0) out["extraKnownMarketplaces"] = extraKnownMarketplaces;
|
|
1634
1612
|
return JSON.stringify(out, null, 2) + "\n";
|
|
1635
1613
|
}
|
|
1636
|
-
|
|
1637
|
-
const filePath = ".claude/settings.json";
|
|
1614
|
+
function writeOrMergeSettings(ctx, filePath, generated) {
|
|
1638
1615
|
const existing = ctx.read(filePath);
|
|
1639
|
-
const generated = buildSettings(ctx);
|
|
1640
1616
|
if (existing) {
|
|
1641
1617
|
const parsed = parseClaudeSettings(existing);
|
|
1642
1618
|
if (!parsed) return {
|
|
@@ -1660,21 +1636,39 @@ async function generateClaudeSettings(ctx) {
|
|
|
1660
1636
|
parsed.instructions = [...parsed.instructions, ...missingInstructions];
|
|
1661
1637
|
for (const [key, value] of missingPlugins) parsed.enabledPlugins[key] = value;
|
|
1662
1638
|
for (const [key, value] of missingMarketplaces) parsed.extraKnownMarketplaces[key] = value;
|
|
1663
|
-
ctx.write(filePath, serializeSettings(parsed));
|
|
1639
|
+
ctx.write(filePath, serializeSettings$1(parsed));
|
|
1664
1640
|
return {
|
|
1665
1641
|
filePath,
|
|
1666
1642
|
action: "updated",
|
|
1667
1643
|
description: `Added ${String(added)} rules/instructions`
|
|
1668
1644
|
};
|
|
1669
1645
|
}
|
|
1670
|
-
ctx.write(filePath, serializeSettings(generated));
|
|
1646
|
+
ctx.write(filePath, serializeSettings$1(generated));
|
|
1671
1647
|
return {
|
|
1672
1648
|
filePath,
|
|
1673
1649
|
action: "created",
|
|
1674
|
-
description:
|
|
1650
|
+
description: `Generated ${filePath}`
|
|
1675
1651
|
};
|
|
1676
1652
|
}
|
|
1677
|
-
|
|
1653
|
+
async function generateClaudeSettings(ctx) {
|
|
1654
|
+
const results = [];
|
|
1655
|
+
results.push(writeOrMergeSettings(ctx, ".claude/settings.json", buildSettings(ctx)));
|
|
1656
|
+
if (ctx.config.structure === "monorepo") for (const pkg of getMonorepoPackages(ctx.targetDir)) {
|
|
1657
|
+
if (!hasWebUIDeps(pkg.dir)) continue;
|
|
1658
|
+
const pkgRelDir = path.relative(ctx.targetDir, pkg.dir);
|
|
1659
|
+
const pkgSettingsPath = path.join(pkgRelDir, ".claude/settings.json");
|
|
1660
|
+
results.push(writeOrMergeSettings(ctx, pkgSettingsPath, {
|
|
1661
|
+
permissions: {
|
|
1662
|
+
allow: [],
|
|
1663
|
+
deny: []
|
|
1664
|
+
},
|
|
1665
|
+
instructions: [],
|
|
1666
|
+
enabledPlugins: { "frontend-design@claude-plugins-official": true },
|
|
1667
|
+
extraKnownMarketplaces: {}
|
|
1668
|
+
}));
|
|
1669
|
+
}
|
|
1670
|
+
return results;
|
|
1671
|
+
}
|
|
1678
1672
|
//#endregion
|
|
1679
1673
|
//#region src/generators/release-it.ts
|
|
1680
1674
|
function buildConfig$2(ci, isMonorepo) {
|
|
@@ -1726,7 +1720,6 @@ async function generateReleaseIt(ctx) {
|
|
|
1726
1720
|
description: "Generated .release-it.json"
|
|
1727
1721
|
};
|
|
1728
1722
|
}
|
|
1729
|
-
|
|
1730
1723
|
//#endregion
|
|
1731
1724
|
//#region src/generators/changesets.ts
|
|
1732
1725
|
function buildConfig$1() {
|
|
@@ -1763,7 +1756,6 @@ async function generateChangesets(ctx) {
|
|
|
1763
1756
|
description: "Generated .changeset/config.json"
|
|
1764
1757
|
};
|
|
1765
1758
|
}
|
|
1766
|
-
|
|
1767
1759
|
//#endregion
|
|
1768
1760
|
//#region src/generators/release-ci.ts
|
|
1769
1761
|
function hasEnginesNode(ctx) {
|
|
@@ -1933,7 +1925,6 @@ async function generateReleaseCi(ctx) {
|
|
|
1933
1925
|
description: `Generated ${isGitHub ? "GitHub" : "Forgejo"} Actions release workflow`
|
|
1934
1926
|
};
|
|
1935
1927
|
}
|
|
1936
|
-
|
|
1937
1928
|
//#endregion
|
|
1938
1929
|
//#region src/generators/lefthook.ts
|
|
1939
1930
|
function buildConfig(formatter) {
|
|
@@ -2062,7 +2053,103 @@ async function generateLefthook(ctx) {
|
|
|
2062
2053
|
});
|
|
2063
2054
|
return results;
|
|
2064
2055
|
}
|
|
2065
|
-
|
|
2056
|
+
//#endregion
|
|
2057
|
+
//#region src/generators/vscode-settings.ts
|
|
2058
|
+
const SCHEMA_NPM_PATH = "@bensandee/config/schemas/forgejo-workflow.schema.json";
|
|
2059
|
+
const SCHEMA_LOCAL_PATH = ".vscode/forgejo-workflow.schema.json";
|
|
2060
|
+
const SETTINGS_PATH = ".vscode/settings.json";
|
|
2061
|
+
const VscodeSettingsSchema = z.object({ "yaml.schemas": z.record(z.string(), z.unknown()).default({}) }).passthrough();
|
|
2062
|
+
function readSchemaFromNodeModules(targetDir) {
|
|
2063
|
+
const candidate = path.join(targetDir, "node_modules", SCHEMA_NPM_PATH);
|
|
2064
|
+
if (!existsSync(candidate)) return void 0;
|
|
2065
|
+
return readFileSync(candidate, "utf-8");
|
|
2066
|
+
}
|
|
2067
|
+
function serializeSettings(settings) {
|
|
2068
|
+
return JSON.stringify(settings, null, 2) + "\n";
|
|
2069
|
+
}
|
|
2070
|
+
async function generateVscodeSettings(ctx) {
|
|
2071
|
+
const results = [];
|
|
2072
|
+
if (ctx.config.ci !== "forgejo") {
|
|
2073
|
+
results.push({
|
|
2074
|
+
filePath: SETTINGS_PATH,
|
|
2075
|
+
action: "skipped",
|
|
2076
|
+
description: "Not a Forgejo project"
|
|
2077
|
+
});
|
|
2078
|
+
return results;
|
|
2079
|
+
}
|
|
2080
|
+
const schemaContent = readSchemaFromNodeModules(ctx.targetDir);
|
|
2081
|
+
if (!schemaContent) {
|
|
2082
|
+
results.push({
|
|
2083
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
2084
|
+
action: "skipped",
|
|
2085
|
+
description: "Could not find @bensandee/config schema in node_modules"
|
|
2086
|
+
});
|
|
2087
|
+
return results;
|
|
2088
|
+
}
|
|
2089
|
+
const existingSchema = ctx.read(SCHEMA_LOCAL_PATH);
|
|
2090
|
+
if (existingSchema === schemaContent) results.push({
|
|
2091
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
2092
|
+
action: "skipped",
|
|
2093
|
+
description: "Schema already up to date"
|
|
2094
|
+
});
|
|
2095
|
+
else {
|
|
2096
|
+
ctx.write(SCHEMA_LOCAL_PATH, schemaContent);
|
|
2097
|
+
results.push({
|
|
2098
|
+
filePath: SCHEMA_LOCAL_PATH,
|
|
2099
|
+
action: existingSchema ? "updated" : "created",
|
|
2100
|
+
description: "Copied Forgejo workflow schema from @bensandee/config"
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
const schemaGlob = ".forgejo/workflows/*.yml";
|
|
2104
|
+
if (ctx.exists(SETTINGS_PATH)) {
|
|
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
|
+
}
|
|
2151
|
+
return results;
|
|
2152
|
+
}
|
|
2066
2153
|
//#endregion
|
|
2067
2154
|
//#region src/generators/pipeline.ts
|
|
2068
2155
|
/** Run all generators sequentially and return their results. */
|
|
@@ -2079,14 +2166,14 @@ async function runGenerators(ctx) {
|
|
|
2079
2166
|
results.push(await generateKnip(ctx));
|
|
2080
2167
|
results.push(await generateRenovate(ctx));
|
|
2081
2168
|
results.push(await generateCi(ctx));
|
|
2082
|
-
results.push(await generateClaudeSettings(ctx));
|
|
2169
|
+
results.push(...await generateClaudeSettings(ctx));
|
|
2083
2170
|
results.push(await generateReleaseIt(ctx));
|
|
2084
2171
|
results.push(await generateChangesets(ctx));
|
|
2085
2172
|
results.push(await generateReleaseCi(ctx));
|
|
2086
2173
|
results.push(...await generateVitest(ctx));
|
|
2174
|
+
results.push(...await generateVscodeSettings(ctx));
|
|
2087
2175
|
return results;
|
|
2088
2176
|
}
|
|
2089
|
-
|
|
2090
2177
|
//#endregion
|
|
2091
2178
|
//#region src/utils/tooling-config.ts
|
|
2092
2179
|
const CONFIG_FILE = ".tooling.json";
|
|
@@ -2141,11 +2228,16 @@ function saveToolingConfig(ctx, config) {
|
|
|
2141
2228
|
detectPackageTypes: config.detectPackageTypes
|
|
2142
2229
|
};
|
|
2143
2230
|
const content = JSON.stringify(saved, null, 2) + "\n";
|
|
2144
|
-
const
|
|
2231
|
+
const existing = ctx.exists(CONFIG_FILE) ? ctx.read(CONFIG_FILE) : void 0;
|
|
2232
|
+
if (existing === content) return {
|
|
2233
|
+
filePath: CONFIG_FILE,
|
|
2234
|
+
action: "skipped",
|
|
2235
|
+
description: "Already up to date"
|
|
2236
|
+
};
|
|
2145
2237
|
ctx.write(CONFIG_FILE, content);
|
|
2146
2238
|
return {
|
|
2147
2239
|
filePath: CONFIG_FILE,
|
|
2148
|
-
action:
|
|
2240
|
+
action: existing ? "updated" : "created",
|
|
2149
2241
|
description: "Saved tooling configuration"
|
|
2150
2242
|
};
|
|
2151
2243
|
}
|
|
@@ -2166,7 +2258,6 @@ function mergeWithSavedConfig(detected, saved) {
|
|
|
2166
2258
|
detectPackageTypes: saved.detectPackageTypes ?? detected.detectPackageTypes
|
|
2167
2259
|
};
|
|
2168
2260
|
}
|
|
2169
|
-
|
|
2170
2261
|
//#endregion
|
|
2171
2262
|
//#region src/commands/repo-init.ts
|
|
2172
2263
|
const initCommand = defineCommand({
|
|
@@ -2238,6 +2329,14 @@ async function runInit(config, options = {}) {
|
|
|
2238
2329
|
action: "archived",
|
|
2239
2330
|
description: `Original saved to .tooling-archived/${rel}`
|
|
2240
2331
|
});
|
|
2332
|
+
const created = results.filter((r) => r.action === "created");
|
|
2333
|
+
const updated = results.filter((r) => r.action === "updated");
|
|
2334
|
+
const skipped = results.filter((r) => r.action === "skipped");
|
|
2335
|
+
const archived = results.filter((r) => r.action === "archived");
|
|
2336
|
+
if (!(created.length > 0 || updated.length > 0 || archived.length > 0) && options.noPrompt) {
|
|
2337
|
+
s.stop("Repository is up to date.");
|
|
2338
|
+
return results;
|
|
2339
|
+
}
|
|
2241
2340
|
s.stop("Done!");
|
|
2242
2341
|
if (results.some((r) => r.action === "archived" && r.filePath.startsWith(".husky/"))) try {
|
|
2243
2342
|
execSync("git config --unset core.hooksPath", {
|
|
@@ -2245,10 +2344,6 @@ async function runInit(config, options = {}) {
|
|
|
2245
2344
|
stdio: "ignore"
|
|
2246
2345
|
});
|
|
2247
2346
|
} catch (_error) {}
|
|
2248
|
-
const created = results.filter((r) => r.action === "created");
|
|
2249
|
-
const updated = results.filter((r) => r.action === "updated");
|
|
2250
|
-
const skipped = results.filter((r) => r.action === "skipped");
|
|
2251
|
-
const archived = results.filter((r) => r.action === "archived");
|
|
2252
2347
|
const summaryLines = [];
|
|
2253
2348
|
if (created.length > 0) summaryLines.push(`Created: ${created.map((r) => r.filePath).join(", ")}`);
|
|
2254
2349
|
if (updated.length > 0) summaryLines.push(`Updated: ${updated.map((r) => r.filePath).join(", ")}`);
|
|
@@ -2285,7 +2380,6 @@ async function runInit(config, options = {}) {
|
|
|
2285
2380
|
].join("\n"), "Next steps");
|
|
2286
2381
|
return results;
|
|
2287
2382
|
}
|
|
2288
|
-
|
|
2289
2383
|
//#endregion
|
|
2290
2384
|
//#region src/commands/repo-update.ts
|
|
2291
2385
|
const updateCommand = defineCommand({
|
|
@@ -2311,7 +2405,6 @@ async function runUpdate(targetDir) {
|
|
|
2311
2405
|
confirmOverwrite: async () => "overwrite"
|
|
2312
2406
|
});
|
|
2313
2407
|
}
|
|
2314
|
-
|
|
2315
2408
|
//#endregion
|
|
2316
2409
|
//#region src/commands/repo-check.ts
|
|
2317
2410
|
const checkCommand = defineCommand({
|
|
@@ -2370,14 +2463,12 @@ function lineDiff(oldText, newText) {
|
|
|
2370
2463
|
for (const l of added) lines.push(`+ ${l.trim()}`);
|
|
2371
2464
|
return lines;
|
|
2372
2465
|
}
|
|
2373
|
-
|
|
2374
2466
|
//#endregion
|
|
2375
2467
|
//#region src/utils/exec.ts
|
|
2376
2468
|
/** Type guard for `execSync` errors that carry stdout/stderr/status. */
|
|
2377
2469
|
function isExecSyncError(err) {
|
|
2378
2470
|
return err instanceof Error && "stdout" in err && typeof err.stdout === "string" && "stderr" in err && typeof err.stderr === "string" && "status" in err && typeof err.status === "number";
|
|
2379
2471
|
}
|
|
2380
|
-
|
|
2381
2472
|
//#endregion
|
|
2382
2473
|
//#region src/release/executor.ts
|
|
2383
2474
|
/** Create an executor that runs real commands, fetches, and reads the filesystem. */
|
|
@@ -2488,18 +2579,25 @@ function reconcileTags(expectedTags, remoteTags, stdoutTags) {
|
|
|
2488
2579
|
for (const tag of stdoutTags) result.add(tag);
|
|
2489
2580
|
return [...result];
|
|
2490
2581
|
}
|
|
2491
|
-
|
|
2492
2582
|
//#endregion
|
|
2493
2583
|
//#region src/release/forgejo.ts
|
|
2494
|
-
const PullRequestSchema = z.array(z.object({
|
|
2495
|
-
|
|
2584
|
+
const PullRequestSchema = z.array(z.object({
|
|
2585
|
+
number: z.number(),
|
|
2586
|
+
head: z.object({ ref: z.string() })
|
|
2587
|
+
}));
|
|
2588
|
+
/**
|
|
2589
|
+
* Find an open PR with the given head branch. Returns the PR number or null.
|
|
2590
|
+
*
|
|
2591
|
+
* Fetches all open PRs and filters client-side by head.ref rather than relying
|
|
2592
|
+
* on Forgejo's query parameter filtering, which behaves inconsistently.
|
|
2593
|
+
*/
|
|
2496
2594
|
async function findOpenPr(executor, conn, head) {
|
|
2497
|
-
const url = `${conn.serverUrl}/api/v1/repos/${conn.repository}/pulls?state=open
|
|
2595
|
+
const url = `${conn.serverUrl}/api/v1/repos/${conn.repository}/pulls?state=open`;
|
|
2498
2596
|
const res = await executor.fetch(url, { headers: { Authorization: `token ${conn.token}` } });
|
|
2499
2597
|
if (!res.ok) throw new TransientError(`Failed to list PRs: ${res.status} ${res.statusText}`);
|
|
2500
2598
|
const parsed = PullRequestSchema.safeParse(await res.json());
|
|
2501
2599
|
if (!parsed.success) throw new UnexpectedError(`Unexpected PR list response: ${parsed.error.message}`);
|
|
2502
|
-
return parsed.data
|
|
2600
|
+
return parsed.data.find((pr) => pr.head.ref === head)?.number ?? null;
|
|
2503
2601
|
}
|
|
2504
2602
|
/** Create a new pull request. */
|
|
2505
2603
|
async function createPr(executor, conn, options) {
|
|
@@ -2578,7 +2676,6 @@ async function createRelease(executor, conn, tag) {
|
|
|
2578
2676
|
});
|
|
2579
2677
|
if (!res.ok) throw new TransientError(`Failed to create release for ${tag}: ${res.status} ${res.statusText}`);
|
|
2580
2678
|
}
|
|
2581
|
-
|
|
2582
2679
|
//#endregion
|
|
2583
2680
|
//#region src/release/log.ts
|
|
2584
2681
|
/** Log a debug message when verbose mode is enabled. */
|
|
@@ -2593,7 +2690,6 @@ function debugExec(config, label, result) {
|
|
|
2593
2690
|
if (result.stderr.trim()) lines.push(` stderr: ${result.stderr.trim()}`);
|
|
2594
2691
|
p.log.info(lines.join("\n"));
|
|
2595
2692
|
}
|
|
2596
|
-
|
|
2597
2693
|
//#endregion
|
|
2598
2694
|
//#region src/release/version.ts
|
|
2599
2695
|
const BRANCH = "changeset-release/main";
|
|
@@ -2738,7 +2834,6 @@ async function runVersionMode(executor, config) {
|
|
|
2738
2834
|
pr: "updated"
|
|
2739
2835
|
};
|
|
2740
2836
|
}
|
|
2741
|
-
|
|
2742
2837
|
//#endregion
|
|
2743
2838
|
//#region src/release/publish.ts
|
|
2744
2839
|
const RETRY_ATTEMPTS = 3;
|
|
@@ -2828,7 +2923,6 @@ async function runPublishMode(executor, config) {
|
|
|
2828
2923
|
tags: allTags
|
|
2829
2924
|
};
|
|
2830
2925
|
}
|
|
2831
|
-
|
|
2832
2926
|
//#endregion
|
|
2833
2927
|
//#region src/release/connection.ts
|
|
2834
2928
|
const RepositorySchema = z.union([z.string(), z.object({ url: z.string() })]);
|
|
@@ -2902,7 +2996,6 @@ function parseGitUrl(urlStr) {
|
|
|
2902
2996
|
return null;
|
|
2903
2997
|
}
|
|
2904
2998
|
}
|
|
2905
|
-
|
|
2906
2999
|
//#endregion
|
|
2907
3000
|
//#region src/commands/release-changesets.ts
|
|
2908
3001
|
const releaseForgejoCommand = defineCommand({
|
|
@@ -2949,7 +3042,6 @@ async function runRelease(config, executor) {
|
|
|
2949
3042
|
debug(config, "Entering publish mode");
|
|
2950
3043
|
return runPublishMode(executor, config);
|
|
2951
3044
|
}
|
|
2952
|
-
|
|
2953
3045
|
//#endregion
|
|
2954
3046
|
//#region src/commands/release-trigger.ts
|
|
2955
3047
|
const releaseTriggerCommand = defineCommand({
|
|
@@ -2986,7 +3078,6 @@ function triggerGitHub(ref) {
|
|
|
2986
3078
|
createRealExecutor().exec(`gh workflow run release.yml --ref ${ref}`, { cwd: process.cwd() });
|
|
2987
3079
|
p.log.info(`Triggered release workflow on GitHub (ref: ${ref})`);
|
|
2988
3080
|
}
|
|
2989
|
-
|
|
2990
3081
|
//#endregion
|
|
2991
3082
|
//#region src/commands/release-create-forgejo-release.ts
|
|
2992
3083
|
const createForgejoReleaseCommand = defineCommand({
|
|
@@ -3012,7 +3103,6 @@ const createForgejoReleaseCommand = defineCommand({
|
|
|
3012
3103
|
p.log.info(`Created Forgejo release for ${args.tag}`);
|
|
3013
3104
|
}
|
|
3014
3105
|
});
|
|
3015
|
-
|
|
3016
3106
|
//#endregion
|
|
3017
3107
|
//#region src/commands/release-merge.ts
|
|
3018
3108
|
const HEAD_BRANCH = "changeset-release/main";
|
|
@@ -3057,13 +3147,12 @@ function mergeGitHub(dryRun) {
|
|
|
3057
3147
|
executor.exec(`gh pr merge ${HEAD_BRANCH} --merge --delete-branch`, { cwd: process.cwd() });
|
|
3058
3148
|
p.log.info(`Merged changesets PR and deleted branch ${HEAD_BRANCH}`);
|
|
3059
3149
|
}
|
|
3060
|
-
|
|
3061
3150
|
//#endregion
|
|
3062
3151
|
//#region src/bin.ts
|
|
3063
3152
|
const main = defineCommand({
|
|
3064
3153
|
meta: {
|
|
3065
3154
|
name: "tooling",
|
|
3066
|
-
version: "0.8.
|
|
3155
|
+
version: "0.8.1",
|
|
3067
3156
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
3068
3157
|
},
|
|
3069
3158
|
subCommands: {
|
|
@@ -3076,8 +3165,7 @@ const main = defineCommand({
|
|
|
3076
3165
|
"release:merge": releaseMergeCommand
|
|
3077
3166
|
}
|
|
3078
3167
|
});
|
|
3079
|
-
console.log(`@bensandee/tooling v0.8.
|
|
3168
|
+
console.log(`@bensandee/tooling v0.8.1`);
|
|
3080
3169
|
runMain(main);
|
|
3081
|
-
|
|
3082
3170
|
//#endregion
|
|
3083
|
-
export {
|
|
3171
|
+
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tooling": "./dist/bin.mjs"
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@clack/prompts": "^1.0
|
|
24
|
+
"@clack/prompts": "^1.1.0",
|
|
25
25
|
"citty": "^0.2.1",
|
|
26
26
|
"json5": "^2.2.3",
|
|
27
27
|
"jsonc-parser": "^3.3.1",
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@bensandee/common": "0.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/node": "24.
|
|
33
|
-
"tsdown": "0.
|
|
32
|
+
"@types/node": "24.12.0",
|
|
33
|
+
"tsdown": "0.21.0",
|
|
34
34
|
"typescript": "5.9.3",
|
|
35
35
|
"vitest": "4.0.18",
|
|
36
|
-
"@bensandee/config": "0.
|
|
36
|
+
"@bensandee/config": "0.7.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "tsdown",
|