@bensandee/tooling 0.22.0 → 0.23.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 +40 -30
- package/package.json +5 -3
- package/tooling.schema.json +148 -0
package/dist/bin.mjs
CHANGED
|
@@ -7,9 +7,9 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, w
|
|
|
7
7
|
import JSON5 from "json5";
|
|
8
8
|
import { parse } from "jsonc-parser";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
|
|
10
11
|
import { isMap, isScalar, isSeq, parse as parse$1, parseDocument, stringify } from "yaml";
|
|
11
12
|
import { execSync } from "node:child_process";
|
|
12
|
-
import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
//#region src/types.ts
|
|
15
15
|
const LEGACY_TOOLS = [
|
|
@@ -201,7 +201,7 @@ function computeDefaults(targetDir) {
|
|
|
201
201
|
setupVitest: !isMonorepo && !detected.hasVitestConfig,
|
|
202
202
|
ci: detectCiPlatform(targetDir),
|
|
203
203
|
setupRenovate: true,
|
|
204
|
-
releaseStrategy:
|
|
204
|
+
releaseStrategy: "none",
|
|
205
205
|
projectType: isMonorepo ? "default" : detectProjectType(targetDir),
|
|
206
206
|
detectPackageTypes: true
|
|
207
207
|
};
|
|
@@ -493,7 +493,8 @@ const DockerCheckConfigSchema = z.object({
|
|
|
493
493
|
timeoutMs: z.number().int().positive().optional(),
|
|
494
494
|
pollIntervalMs: z.number().int().positive().optional()
|
|
495
495
|
});
|
|
496
|
-
const ToolingConfigSchema = z.
|
|
496
|
+
const ToolingConfigSchema = z.strictObject({
|
|
497
|
+
$schema: z.string().optional(),
|
|
497
498
|
structure: z.enum(["single", "monorepo"]).optional(),
|
|
498
499
|
useEslintPlugin: z.boolean().optional(),
|
|
499
500
|
formatter: z.enum(["oxfmt", "prettier"]).optional(),
|
|
@@ -524,17 +525,14 @@ const ToolingConfigSchema = z.object({
|
|
|
524
525
|
})).optional(),
|
|
525
526
|
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional()
|
|
526
527
|
});
|
|
527
|
-
/** Load saved tooling config from the target directory. Returns undefined if missing
|
|
528
|
+
/** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
|
|
528
529
|
function loadToolingConfig(targetDir) {
|
|
529
530
|
const fullPath = path.join(targetDir, CONFIG_FILE);
|
|
530
531
|
if (!existsSync(fullPath)) return void 0;
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
} catch {
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
532
|
+
const raw = readFileSync(fullPath, "utf-8");
|
|
533
|
+
const result = ToolingConfigSchema.safeParse(JSON.parse(raw));
|
|
534
|
+
if (!result.success) throw new FatalError(`Invalid .tooling.json:\n${result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`);
|
|
535
|
+
return result.data;
|
|
538
536
|
}
|
|
539
537
|
/** Config fields that can be overridden in .tooling.json. */
|
|
540
538
|
const OVERRIDE_KEYS = [
|
|
@@ -558,7 +556,7 @@ const MONOREPO_IGNORED_KEYS = new Set(["setupVitest", "projectType"]);
|
|
|
558
556
|
function saveToolingConfig(ctx, config) {
|
|
559
557
|
const defaults = computeDefaults(config.targetDir);
|
|
560
558
|
const isMonorepo = config.structure === "monorepo";
|
|
561
|
-
const overrides = {};
|
|
559
|
+
const overrides = { $schema: "node_modules/@bensandee/tooling/tooling.schema.json" };
|
|
562
560
|
for (const key of OVERRIDE_KEYS) {
|
|
563
561
|
if (isMonorepo && MONOREPO_IGNORED_KEYS.has(key)) continue;
|
|
564
562
|
if (config[key] !== defaults[key]) overrides[key] = config[key];
|
|
@@ -983,7 +981,7 @@ function getAddedDevDepNames(config) {
|
|
|
983
981
|
const deps = { ...ROOT_DEV_DEPS };
|
|
984
982
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
985
983
|
deps["@bensandee/config"] = "0.8.2";
|
|
986
|
-
deps["@bensandee/tooling"] = "0.
|
|
984
|
+
deps["@bensandee/tooling"] = "0.23.0";
|
|
987
985
|
if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
|
|
988
986
|
if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
|
|
989
987
|
addReleaseDeps(deps, config);
|
|
@@ -1008,7 +1006,7 @@ async function generatePackageJson(ctx) {
|
|
|
1008
1006
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1009
1007
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1010
1008
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.8.2";
|
|
1011
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1009
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.23.0";
|
|
1012
1010
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1013
1011
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
|
|
1014
1012
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
|
|
@@ -1494,25 +1492,26 @@ function actionsExpr$1(expr) {
|
|
|
1494
1492
|
}
|
|
1495
1493
|
const CI_CONCURRENCY = {
|
|
1496
1494
|
group: `ci-${actionsExpr$1("github.ref")}`,
|
|
1497
|
-
"cancel-in-progress":
|
|
1495
|
+
"cancel-in-progress": actionsExpr$1("github.ref != 'refs/heads/main'")
|
|
1498
1496
|
};
|
|
1499
1497
|
function hasEnginesNode$1(ctx) {
|
|
1500
1498
|
const raw = ctx.read("package.json");
|
|
1501
1499
|
if (!raw) return false;
|
|
1502
1500
|
return typeof parsePackageJson(raw)?.engines?.["node"] === "string";
|
|
1503
1501
|
}
|
|
1504
|
-
function ciWorkflow(nodeVersionYaml, isForgejo) {
|
|
1502
|
+
function ciWorkflow(nodeVersionYaml, isForgejo, isChangesets) {
|
|
1505
1503
|
const emailNotifications = isForgejo ? "\nenable-email-notifications: true\n" : "";
|
|
1504
|
+
const concurrencyBlock = isChangesets ? `
|
|
1505
|
+
concurrency:
|
|
1506
|
+
group: ci-${actionsExpr$1("github.ref")}
|
|
1507
|
+
cancel-in-progress: ${actionsExpr$1("github.ref != 'refs/heads/main'")}
|
|
1508
|
+
` : "";
|
|
1506
1509
|
return `${workflowSchemaComment(isForgejo ? "forgejo" : "github")}name: CI
|
|
1507
1510
|
${emailNotifications}on:
|
|
1508
1511
|
push:
|
|
1509
1512
|
branches: [main]
|
|
1510
1513
|
pull_request:
|
|
1511
|
-
|
|
1512
|
-
concurrency:
|
|
1513
|
-
group: ci-${actionsExpr$1("github.ref")}
|
|
1514
|
-
cancel-in-progress: true
|
|
1515
|
-
|
|
1514
|
+
${concurrencyBlock}
|
|
1516
1515
|
jobs:
|
|
1517
1516
|
check:
|
|
1518
1517
|
runs-on: ubuntu-latest
|
|
@@ -1561,6 +1560,10 @@ function requiredCheckSteps(nodeVersionYaml) {
|
|
|
1561
1560
|
}
|
|
1562
1561
|
];
|
|
1563
1562
|
}
|
|
1563
|
+
/** Resolve the CI workflow filename based on release strategy. */
|
|
1564
|
+
function ciWorkflowPath(ci, releaseStrategy) {
|
|
1565
|
+
return `${ci === "github" ? ".github/workflows" : ".forgejo/workflows"}/${releaseStrategy === "changesets" ? "ci.yml" : "check.yml"}`;
|
|
1566
|
+
}
|
|
1564
1567
|
async function generateCi(ctx) {
|
|
1565
1568
|
if (ctx.config.ci === "none") return {
|
|
1566
1569
|
filePath: "ci",
|
|
@@ -1568,16 +1571,23 @@ async function generateCi(ctx) {
|
|
|
1568
1571
|
description: "CI workflow not requested"
|
|
1569
1572
|
};
|
|
1570
1573
|
const isGitHub = ctx.config.ci === "github";
|
|
1574
|
+
const isChangesets = ctx.config.releaseStrategy === "changesets";
|
|
1571
1575
|
const nodeVersionYaml = hasEnginesNode$1(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
|
|
1572
|
-
const filePath =
|
|
1573
|
-
const content = ciWorkflow(nodeVersionYaml, !isGitHub);
|
|
1576
|
+
const filePath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
1577
|
+
const content = ciWorkflow(nodeVersionYaml, !isGitHub, isChangesets);
|
|
1574
1578
|
if (ctx.exists(filePath)) {
|
|
1575
1579
|
const existing = ctx.read(filePath);
|
|
1576
1580
|
if (existing) {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
+
let result = mergeWorkflowSteps(existing, "check", requiredCheckSteps(nodeVersionYaml));
|
|
1582
|
+
if (isChangesets) {
|
|
1583
|
+
const withConcurrency = ensureWorkflowConcurrency(result.content, CI_CONCURRENCY);
|
|
1584
|
+
result = {
|
|
1585
|
+
content: withConcurrency.content,
|
|
1586
|
+
changed: result.changed || withConcurrency.changed
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
const withComment = ensureSchemaComment(result.content, isGitHub ? "github" : "forgejo");
|
|
1590
|
+
if (result.changed || withComment !== result.content) {
|
|
1581
1591
|
ctx.write(filePath, withComment);
|
|
1582
1592
|
return {
|
|
1583
1593
|
filePath,
|
|
@@ -2215,7 +2225,7 @@ function buildWorkflow(strategy, ci, nodeVersionYaml, publishesNpm) {
|
|
|
2215
2225
|
}
|
|
2216
2226
|
}
|
|
2217
2227
|
function generateChangesetsReleaseCi(ctx, publishesNpm) {
|
|
2218
|
-
const ciPath = ctx.config.ci
|
|
2228
|
+
const ciPath = ciWorkflowPath(ctx.config.ci, ctx.config.releaseStrategy);
|
|
2219
2229
|
const existing = ctx.read(ciPath);
|
|
2220
2230
|
if (!existing) return {
|
|
2221
2231
|
filePath: ciPath,
|
|
@@ -4577,7 +4587,7 @@ const dockerCheckCommand = defineCommand({
|
|
|
4577
4587
|
const main = defineCommand({
|
|
4578
4588
|
meta: {
|
|
4579
4589
|
name: "tooling",
|
|
4580
|
-
version: "0.
|
|
4590
|
+
version: "0.23.0",
|
|
4581
4591
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
4582
4592
|
},
|
|
4583
4593
|
subCommands: {
|
|
@@ -4593,7 +4603,7 @@ const main = defineCommand({
|
|
|
4593
4603
|
"docker:check": dockerCheckCommand
|
|
4594
4604
|
}
|
|
4595
4605
|
});
|
|
4596
|
-
console.log(`@bensandee/tooling v0.
|
|
4606
|
+
console.log(`@bensandee/tooling v0.23.0`);
|
|
4597
4607
|
runMain(main);
|
|
4598
4608
|
//#endregion
|
|
4599
4609
|
export {};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tooling": "./dist/bin.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
9
|
+
"dist",
|
|
10
|
+
"tooling.schema.json"
|
|
10
11
|
],
|
|
11
12
|
"type": "module",
|
|
12
13
|
"imports": {
|
|
@@ -22,7 +23,8 @@
|
|
|
22
23
|
"types": "./dist/docker-check/index.d.mts",
|
|
23
24
|
"default": "./dist/docker-check/index.mjs"
|
|
24
25
|
},
|
|
25
|
-
"./package.json": "./package.json"
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./tooling.schema.json": "./tooling.schema.json"
|
|
26
28
|
},
|
|
27
29
|
"publishConfig": {
|
|
28
30
|
"access": "public"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "@bensandee/tooling configuration",
|
|
4
|
+
"description": "Override convention-detected defaults for repo:sync. Only fields that differ from detected defaults need to be specified.",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "JSON Schema reference (ignored by tooling)"
|
|
11
|
+
},
|
|
12
|
+
"structure": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": ["single", "monorepo"],
|
|
15
|
+
"description": "Project structure"
|
|
16
|
+
},
|
|
17
|
+
"useEslintPlugin": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"description": "Include @bensandee/eslint-plugin oxlint plugin"
|
|
20
|
+
},
|
|
21
|
+
"formatter": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["oxfmt", "prettier"],
|
|
24
|
+
"description": "Formatter choice"
|
|
25
|
+
},
|
|
26
|
+
"setupVitest": {
|
|
27
|
+
"type": "boolean",
|
|
28
|
+
"description": "Generate vitest config and example test"
|
|
29
|
+
},
|
|
30
|
+
"ci": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"enum": ["github", "forgejo", "none"],
|
|
33
|
+
"description": "CI platform"
|
|
34
|
+
},
|
|
35
|
+
"setupRenovate": {
|
|
36
|
+
"type": "boolean",
|
|
37
|
+
"description": "Generate Renovate config"
|
|
38
|
+
},
|
|
39
|
+
"releaseStrategy": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["release-it", "simple", "changesets", "none"],
|
|
42
|
+
"description": "Release management strategy"
|
|
43
|
+
},
|
|
44
|
+
"projectType": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": ["default", "node", "react", "library"],
|
|
47
|
+
"description": "Project type (determines tsconfig base)"
|
|
48
|
+
},
|
|
49
|
+
"detectPackageTypes": {
|
|
50
|
+
"type": "boolean",
|
|
51
|
+
"description": "Auto-detect project types for monorepo packages"
|
|
52
|
+
},
|
|
53
|
+
"setupDocker": {
|
|
54
|
+
"type": "boolean",
|
|
55
|
+
"description": "Generate Docker build/check scripts"
|
|
56
|
+
},
|
|
57
|
+
"docker": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"description": "Docker package overrides (package name → config)",
|
|
60
|
+
"additionalProperties": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": ["dockerfile"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"dockerfile": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "Path to Dockerfile relative to package"
|
|
67
|
+
},
|
|
68
|
+
"context": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"default": ".",
|
|
71
|
+
"description": "Docker build context relative to package"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"dockerCheck": {
|
|
78
|
+
"oneOf": [
|
|
79
|
+
{
|
|
80
|
+
"const": false,
|
|
81
|
+
"description": "Disable Docker health checks"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"type": "object",
|
|
85
|
+
"additionalProperties": false,
|
|
86
|
+
"properties": {
|
|
87
|
+
"composeFiles": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": { "type": "string" },
|
|
90
|
+
"description": "Compose files to use"
|
|
91
|
+
},
|
|
92
|
+
"envFile": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "Environment file for compose"
|
|
95
|
+
},
|
|
96
|
+
"services": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"items": { "type": "string" },
|
|
99
|
+
"description": "Services to check (default: all)"
|
|
100
|
+
},
|
|
101
|
+
"healthChecks": {
|
|
102
|
+
"type": "array",
|
|
103
|
+
"items": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"required": ["name", "url"],
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"properties": {
|
|
108
|
+
"name": {
|
|
109
|
+
"type": "string",
|
|
110
|
+
"description": "Service name"
|
|
111
|
+
},
|
|
112
|
+
"url": {
|
|
113
|
+
"type": "string",
|
|
114
|
+
"description": "Health check URL"
|
|
115
|
+
},
|
|
116
|
+
"status": {
|
|
117
|
+
"type": "integer",
|
|
118
|
+
"description": "Expected HTTP status code"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"description": "Health check definitions"
|
|
123
|
+
},
|
|
124
|
+
"buildCommand": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Command to build images before checking"
|
|
127
|
+
},
|
|
128
|
+
"buildCwd": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "Working directory for build command"
|
|
131
|
+
},
|
|
132
|
+
"timeoutMs": {
|
|
133
|
+
"type": "integer",
|
|
134
|
+
"minimum": 1,
|
|
135
|
+
"description": "Overall timeout in milliseconds"
|
|
136
|
+
},
|
|
137
|
+
"pollIntervalMs": {
|
|
138
|
+
"type": "integer",
|
|
139
|
+
"minimum": 1,
|
|
140
|
+
"description": "Poll interval in milliseconds"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
"description": "Docker health check configuration or false to disable"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|