@bensandee/tooling 0.54.0 → 0.54.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 +53 -20
- package/package.json +3 -3
- package/tooling.schema.json +4 -6
package/dist/bin.mjs
CHANGED
|
@@ -623,23 +623,44 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
623
623
|
]).optional().meta({ description: "Project type (determines tsconfig base)" }),
|
|
624
624
|
detectPackageTypes: z.boolean().optional().meta({ description: "Auto-detect project types for monorepo packages" }),
|
|
625
625
|
docker: z.record(z.string(), z.object({
|
|
626
|
-
dockerfile: z.string().meta({ description: "Path to Dockerfile relative to package" }),
|
|
627
|
-
context: z.string().
|
|
626
|
+
dockerfile: z.string().optional().meta({ description: "Path to Dockerfile relative to package (falls back to convention when omitted)" }),
|
|
627
|
+
context: z.string().optional().meta({ description: "Docker build context relative to package (falls back to convention, default '.')" }),
|
|
628
628
|
buildArgs: z.record(z.string(), z.string()).optional().meta({ description: "Build args passed to `docker build` for this package. Values support ${VAR} and ${VAR:-default} expansion against process.env." })
|
|
629
|
-
})).optional().meta({ description: "Docker package overrides (package name → config)" }),
|
|
629
|
+
})).optional().meta({ description: "Docker package overrides (package name → partial config; any field may be omitted to fall back to convention)" }),
|
|
630
630
|
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional().meta({ description: "Docker health check configuration or false to disable" }),
|
|
631
631
|
registryHost: z.string().optional().meta({ description: "Docker registry hostname (e.g. ghcr.io, code.example.com)" }),
|
|
632
632
|
registryNamespace: z.string().optional().meta({ description: "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)" }),
|
|
633
633
|
postReleaseHook: z.string().optional().meta({ description: "Override the post-release hook command (default: POST to POST_RELEASE_HOOK_URL with HMAC-SHA256 auth)" }),
|
|
634
634
|
postReleaseHookSecrets: z.array(z.string()).optional().meta({ description: "Additional secret names to inject as env vars in the post-release hook step (e.g. ['WEBHOOK_SECRET'])" })
|
|
635
635
|
});
|
|
636
|
+
/** Format a Zod issue as a human-readable bullet: location + plain-English explanation. */
|
|
637
|
+
function formatIssue(issue) {
|
|
638
|
+
const loc = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
|
|
639
|
+
if (issue.code === "invalid_type") {
|
|
640
|
+
if (issue.input === void 0) return ` ${loc}: missing (expected ${issue.expected})`;
|
|
641
|
+
return ` ${loc}: expected ${issue.expected}, got ${typeof issue.input}`;
|
|
642
|
+
}
|
|
643
|
+
if (issue.code === "unrecognized_keys") return ` ${loc}: ${issue.keys.length > 1 ? "unknown fields" : "unknown field"} ${issue.keys.map((k) => `"${k}"`).join(", ")}`;
|
|
644
|
+
return ` ${loc}: ${issue.message}`;
|
|
645
|
+
}
|
|
636
646
|
/** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
|
|
637
647
|
function loadToolingConfig(targetDir) {
|
|
638
648
|
const fullPath = path.join(targetDir, CONFIG_FILE);
|
|
639
649
|
if (!existsSync(fullPath)) return void 0;
|
|
640
650
|
const raw = readFileSync(fullPath, "utf-8");
|
|
641
|
-
const
|
|
642
|
-
|
|
651
|
+
const displayPath = path.relative(process.cwd(), fullPath) || fullPath;
|
|
652
|
+
let parsed;
|
|
653
|
+
try {
|
|
654
|
+
parsed = JSON.parse(raw);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
throw new FatalError(`Could not parse ${displayPath} as JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
657
|
+
}
|
|
658
|
+
const result = ToolingConfigSchema.safeParse(parsed);
|
|
659
|
+
if (!result.success) {
|
|
660
|
+
const issues = result.error.issues.map(formatIssue).join("\n");
|
|
661
|
+
const count = result.error.issues.length;
|
|
662
|
+
throw new FatalError(`Invalid .tooling.json (${displayPath}) — ${count === 1 ? "1 problem" : `${count} problems`}:\n${issues}`);
|
|
663
|
+
}
|
|
643
664
|
return result.data;
|
|
644
665
|
}
|
|
645
666
|
/** Config fields that can be overridden in .tooling.json. */
|
|
@@ -658,30 +679,42 @@ const OVERRIDE_KEYS = [
|
|
|
658
679
|
];
|
|
659
680
|
/** Keys that have no effect for monorepos (generators ignore them). */
|
|
660
681
|
const MONOREPO_IGNORED_KEYS = new Set(["setupVitest", "projectType"]);
|
|
682
|
+
const SCHEMA_REF = "node_modules/@bensandee/tooling/tooling.schema.json";
|
|
683
|
+
const RawJsonObjectSchema = z.record(z.string(), z.unknown());
|
|
684
|
+
/** Parse the raw .tooling.json contents into a plain object, or {} if invalid/missing. */
|
|
685
|
+
function parseExistingConfig(raw) {
|
|
686
|
+
if (raw === void 0) return {};
|
|
687
|
+
try {
|
|
688
|
+
const result = RawJsonObjectSchema.safeParse(JSON.parse(raw));
|
|
689
|
+
if (result.success) return result.data;
|
|
690
|
+
} catch (_error) {}
|
|
691
|
+
return {};
|
|
692
|
+
}
|
|
661
693
|
/**
|
|
662
694
|
* Save only the fields that differ from detected defaults to .tooling.json.
|
|
663
695
|
* A fully conventional project produces `{}` (or a minimal set of overrides).
|
|
664
696
|
* Keys that have no effect for the current structure are omitted.
|
|
697
|
+
*
|
|
698
|
+
* Non-override fields (e.g. `docker`, `dockerCheck`) are copied verbatim from the
|
|
699
|
+
* raw JSON — they are not re-parsed through Zod, so schema defaults like
|
|
700
|
+
* `docker.<pkg>.context: "."` are never injected into the user's file.
|
|
665
701
|
*/
|
|
666
702
|
function saveToolingConfig(ctx, config) {
|
|
667
703
|
const defaults = computeDefaults(config.targetDir);
|
|
668
704
|
const isMonorepo = config.structure === "monorepo";
|
|
669
705
|
const existing = ctx.exists(CONFIG_FILE) ? ctx.read(CONFIG_FILE) : void 0;
|
|
706
|
+
const existingJson = parseExistingConfig(existing);
|
|
670
707
|
const overrideKeySet = new Set(OVERRIDE_KEYS);
|
|
671
|
-
const
|
|
672
|
-
if (existing) {
|
|
673
|
-
const result = ToolingConfigSchema.safeParse(JSON.parse(existing));
|
|
674
|
-
if (result.success) {
|
|
675
|
-
for (const [key, value] of Object.entries(result.data)) if (key !== "$schema" && !overrideKeySet.has(key)) preserved[key] = value;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
const overrides = { $schema: "node_modules/@bensandee/tooling/tooling.schema.json" };
|
|
708
|
+
const result = { $schema: SCHEMA_REF };
|
|
679
709
|
for (const key of OVERRIDE_KEYS) {
|
|
680
710
|
if (isMonorepo && MONOREPO_IGNORED_KEYS.has(key)) continue;
|
|
681
|
-
if (config[key] !== defaults[key])
|
|
711
|
+
if (config[key] !== defaults[key]) result[key] = config[key];
|
|
712
|
+
}
|
|
713
|
+
for (const [key, value] of Object.entries(existingJson)) {
|
|
714
|
+
if (key === "$schema" || overrideKeySet.has(key)) continue;
|
|
715
|
+
result[key] = value;
|
|
682
716
|
}
|
|
683
|
-
|
|
684
|
-
const content = JSON.stringify(overrides, null, 2) + "\n";
|
|
717
|
+
const content = JSON.stringify(result, null, 2) + "\n";
|
|
685
718
|
if (existing !== void 0 && contentEqual(CONFIG_FILE, existing, content)) return {
|
|
686
719
|
filePath: CONFIG_FILE,
|
|
687
720
|
action: "skipped",
|
|
@@ -1700,7 +1733,7 @@ async function generatePackageJson(ctx) {
|
|
|
1700
1733
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1701
1734
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1702
1735
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.2";
|
|
1703
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.54.
|
|
1736
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.54.1";
|
|
1704
1737
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1705
1738
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1706
1739
|
"@changesets/cli": "2.30.0",
|
|
@@ -3476,7 +3509,7 @@ async function runGenerators(ctx) {
|
|
|
3476
3509
|
function generateMigratePrompt(results, config, detected) {
|
|
3477
3510
|
const sections = [];
|
|
3478
3511
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3479
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.54.
|
|
3512
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.54.1 repo:sync\` on ${timestamp}_\n`);
|
|
3480
3513
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3481
3514
|
if (changed.length > 0) {
|
|
3482
3515
|
const items = changed.map((r) => {
|
|
@@ -6779,7 +6812,7 @@ async function sendWebhookWithLogging(url, secret, payload) {
|
|
|
6779
6812
|
const main = defineCommand({
|
|
6780
6813
|
meta: {
|
|
6781
6814
|
name: "bst",
|
|
6782
|
-
version: "0.54.
|
|
6815
|
+
version: "0.54.1",
|
|
6783
6816
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
6784
6817
|
},
|
|
6785
6818
|
subCommands: {
|
|
@@ -6847,7 +6880,7 @@ const main = defineCommand({
|
|
|
6847
6880
|
})
|
|
6848
6881
|
}
|
|
6849
6882
|
});
|
|
6850
|
-
console.log(`@bensandee/tooling v0.54.
|
|
6883
|
+
console.log(`@bensandee/tooling v0.54.1`);
|
|
6851
6884
|
async function run() {
|
|
6852
6885
|
await runMain(main);
|
|
6853
6886
|
process.exit(process.exitCode ?? 0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.1",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"prettier": "3.8.3",
|
|
55
55
|
"release-it": "20.0.0",
|
|
56
56
|
"tsdown": "0.21.9",
|
|
57
|
-
"@bensandee/
|
|
58
|
-
"@bensandee/
|
|
57
|
+
"@bensandee/config": "0.10.2",
|
|
58
|
+
"@bensandee/claude-hooks": "0.7.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsdown",
|
package/tooling.schema.json
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"type": "boolean"
|
|
59
59
|
},
|
|
60
60
|
"docker": {
|
|
61
|
-
"description": "Docker package overrides (package name → config)",
|
|
61
|
+
"description": "Docker package overrides (package name → partial config; any field may be omitted to fall back to convention)",
|
|
62
62
|
"type": "object",
|
|
63
63
|
"propertyNames": {
|
|
64
64
|
"type": "string"
|
|
@@ -67,12 +67,11 @@
|
|
|
67
67
|
"type": "object",
|
|
68
68
|
"properties": {
|
|
69
69
|
"dockerfile": {
|
|
70
|
-
"
|
|
71
|
-
"
|
|
70
|
+
"description": "Path to Dockerfile relative to package (falls back to convention when omitted)",
|
|
71
|
+
"type": "string"
|
|
72
72
|
},
|
|
73
73
|
"context": {
|
|
74
|
-
"
|
|
75
|
-
"description": "Docker build context relative to package",
|
|
74
|
+
"description": "Docker build context relative to package (falls back to convention, default '.')",
|
|
76
75
|
"type": "string"
|
|
77
76
|
},
|
|
78
77
|
"buildArgs": {
|
|
@@ -86,7 +85,6 @@
|
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
},
|
|
89
|
-
"required": ["dockerfile", "context"],
|
|
90
88
|
"additionalProperties": false
|
|
91
89
|
}
|
|
92
90
|
},
|