@bensandee/tooling 0.64.2 → 0.66.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/README.md +22 -0
- package/dist/bin.mjs +3963 -901
- package/dist/docker-check/index.d.mts +23 -1
- package/dist/docker-check/index.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/{project-name-CHPvgMTP.mjs → project-name-CNaAmafB.mjs} +129 -60
- package/dist/{types-CMKT2loB.d.mts → types-C6cLSf_a.d.mts} +16 -0
- package/package.json +4 -4
- package/tooling.schema.json +84 -0
|
@@ -109,7 +109,7 @@ declare function createRealExecutor(): DockerCheckExecutor;
|
|
|
109
109
|
//#endregion
|
|
110
110
|
//#region src/docker-check/check.d.ts
|
|
111
111
|
/** Run the full Docker check lifecycle. */
|
|
112
|
-
declare function runDockerCheck(executor: DockerCheckExecutor,
|
|
112
|
+
declare function runDockerCheck(executor: DockerCheckExecutor, rawConfig: CheckConfig): Promise<CheckResult>;
|
|
113
113
|
//#endregion
|
|
114
114
|
//#region src/docker-check/compose.d.ts
|
|
115
115
|
/** Zod schema for a single container entry from `docker compose ps --format json`. */
|
|
@@ -242,6 +242,12 @@ declare const ToolingConfigSchema: z.ZodObject<{
|
|
|
242
242
|
historyBranch: z.ZodOptional<z.ZodString>;
|
|
243
243
|
tolerance: z.ZodOptional<z.ZodNumber>;
|
|
244
244
|
}, z.core.$strip>>;
|
|
245
|
+
docs: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodObject<{
|
|
246
|
+
enforced: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodArray<z.ZodString>]>>;
|
|
247
|
+
dirs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
248
|
+
staleDays: z.ZodOptional<z.ZodNumber>;
|
|
249
|
+
staleEnforced: z.ZodOptional<z.ZodBoolean>;
|
|
250
|
+
}, z.core.$strict>]>>;
|
|
245
251
|
deploy: z.ZodOptional<z.ZodObject<{
|
|
246
252
|
dispatchRepo: z.ZodString;
|
|
247
253
|
stack: z.ZodOptional<z.ZodString>;
|
|
@@ -250,6 +256,22 @@ declare const ToolingConfigSchema: z.ZodObject<{
|
|
|
250
256
|
}, z.core.$strict>>;
|
|
251
257
|
pnpmWorkspace: z.ZodOptional<z.ZodLiteral<false>>;
|
|
252
258
|
defaultBranch: z.ZodOptional<z.ZodString>;
|
|
259
|
+
unmanagedDevDependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
260
|
+
stack: z.ZodOptional<z.ZodEnum<{
|
|
261
|
+
none: "none";
|
|
262
|
+
typescript: "typescript";
|
|
263
|
+
javascript: "javascript";
|
|
264
|
+
}>>;
|
|
265
|
+
builder: z.ZodOptional<z.ZodEnum<{
|
|
266
|
+
none: "none";
|
|
267
|
+
custom: "custom";
|
|
268
|
+
tsdown: "tsdown";
|
|
269
|
+
vite: "vite";
|
|
270
|
+
}>>;
|
|
271
|
+
checks: z.ZodOptional<z.ZodObject<{
|
|
272
|
+
add: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
273
|
+
skip: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
274
|
+
}, z.core.$strict>>;
|
|
253
275
|
}, z.core.$strict>;
|
|
254
276
|
type ToolingConfig = z.infer<typeof ToolingConfigSchema>;
|
|
255
277
|
//#endregion
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as composeArgs, c as composePs,
|
|
1
|
+
import { a as composeArgs, c as composePs, d as lintComposeFiles, i as getContainerHealth, l as composeUp, n as runDockerCheck, o as composeDown, r as checkHttpHealth, s as composeLogs, t as deriveProjectName, u as createRealExecutor } from "../project-name-CNaAmafB.mjs";
|
|
2
2
|
export { checkHttpHealth, composeArgs, composeDown, composeLogs, composePs, composeUp, createRealExecutor, deriveProjectName, getContainerHealth, lintComposeFiles, runDockerCheck };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as LegacyConfig, i as GeneratorResult, n as Generator, o as ProjectConfig, r as GeneratorContext, t as DetectedProjectState } from "./types-
|
|
1
|
+
import { a as LegacyConfig, i as GeneratorResult, n as Generator, o as ProjectConfig, r as GeneratorContext, t as DetectedProjectState } from "./types-C6cLSf_a.mjs";
|
|
2
2
|
export type { DetectedProjectState, Generator, GeneratorContext, GeneratorResult, LegacyConfig, ProjectConfig };
|
|
@@ -1,27 +1,11 @@
|
|
|
1
|
-
import * as clack from "@clack/prompts";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
|
-
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
import { parse } from "yaml";
|
|
7
|
-
//#region src/utils/log.ts
|
|
8
|
-
const out = (msg) => console.log(msg);
|
|
9
|
-
const isCI = Boolean(process.env["CI"]);
|
|
10
|
-
const log = isCI ? {
|
|
11
|
-
info: out,
|
|
12
|
-
warn: (msg) => out(`[warn] ${msg}`),
|
|
13
|
-
error: (msg) => out(`[error] ${msg}`),
|
|
14
|
-
success: (msg) => out(`✓ ${msg}`)
|
|
15
|
-
} : clack.log;
|
|
16
|
-
function note(body, title) {
|
|
17
|
-
if (isCI) {
|
|
18
|
-
if (title) out(`--- ${title} ---`);
|
|
19
|
-
out(body);
|
|
20
|
-
} else clack.note(body, title);
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
6
|
//#region src/utils/debug.ts
|
|
24
7
|
const REDACT_PATTERNS = [/x-access-token:[^\s@]+/g, /Authorization:\s*(?:token|Bearer)\s+\S+/gi];
|
|
8
|
+
const debugOut = (msg) => console.error(msg);
|
|
25
9
|
/** Redact secrets (tokens, credentials) from text before logging. */
|
|
26
10
|
function redact(text) {
|
|
27
11
|
let result = text;
|
|
@@ -34,7 +18,7 @@ function isEnvVerbose() {
|
|
|
34
18
|
}
|
|
35
19
|
/** Log a debug message when verbose mode is enabled. */
|
|
36
20
|
function debug(config, message) {
|
|
37
|
-
if (config.verbose)
|
|
21
|
+
if (config.verbose) debugOut(`[debug] ${message}`);
|
|
38
22
|
}
|
|
39
23
|
/** Log the result of an exec call when verbose mode is enabled. */
|
|
40
24
|
function debugExec(config, label, result) {
|
|
@@ -42,7 +26,33 @@ function debugExec(config, label, result) {
|
|
|
42
26
|
const lines = [`[debug] ${redact(label)} (exit code ${String(result.exitCode)})`];
|
|
43
27
|
if (result.stdout.trim()) lines.push(` stdout: ${redact(result.stdout.trim())}`);
|
|
44
28
|
if (result.stderr.trim()) lines.push(` stderr: ${redact(result.stderr.trim())}`);
|
|
45
|
-
|
|
29
|
+
debugOut(lines.join("\n"));
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/docker-check/compose-yaml.ts
|
|
33
|
+
/**
|
|
34
|
+
* Parse a Docker Compose file as plain data.
|
|
35
|
+
*
|
|
36
|
+
* Compose defines its own merge tags — `!reset` and `!override` — which control how
|
|
37
|
+
* values combine across layered `-f` files, and they may tag a scalar, a map or a
|
|
38
|
+
* sequence (`ipam: !reset null`, `ports: !override [...]`). `yaml` doesn't know them,
|
|
39
|
+
* and for an unknown tag it does not throw: it calls `process.emitWarning()` with code
|
|
40
|
+
* `TAG_RESOLVE_FAILED` and then falls back to the default schema. That warning reached
|
|
41
|
+
* every `bst repo:sync --check` and `bst docker:check` run in any repo whose overlay
|
|
42
|
+
* used a merge tag — noise in every CI log, from a file bst went looking for itself.
|
|
43
|
+
*
|
|
44
|
+
* `logLevel: "silent"` suppresses those non-fatal warnings. It does not weaken parsing:
|
|
45
|
+
* genuine syntax errors still throw, and a tagged value still resolves to its underlying
|
|
46
|
+
* data. Callers here only read compose as data (service names, images, ports,
|
|
47
|
+
* healthchecks, the top-level `name:`) and never re-serialize it, so the tag semantics
|
|
48
|
+
* are irrelevant to them — which is why silencing beats registering the tags, whose
|
|
49
|
+
* scalar/map/seq forms would need a six-entry matrix to model correctly.
|
|
50
|
+
*
|
|
51
|
+
* Do not drop the option; `test/docker-check/detect.test.ts` and `compose-lint.test.ts`
|
|
52
|
+
* pin it by asserting no process warning is emitted.
|
|
53
|
+
*/
|
|
54
|
+
function parseComposeYaml(content) {
|
|
55
|
+
return parse(content, { logLevel: "silent" });
|
|
46
56
|
}
|
|
47
57
|
//#endregion
|
|
48
58
|
//#region src/docker-check/compose-lint.ts
|
|
@@ -54,7 +64,7 @@ const ComposeFileShape = z.looseObject({ name: z.unknown().optional() });
|
|
|
54
64
|
*/
|
|
55
65
|
const COMPOSE_DIR_CANDIDATES = [".", "docker"];
|
|
56
66
|
/** Base compose file names whose project namespace defaults to dir basename. */
|
|
57
|
-
const BASE_COMPOSE_FILES = new Set([
|
|
67
|
+
const BASE_COMPOSE_FILES = /* @__PURE__ */ new Set([
|
|
58
68
|
"docker-compose.yml",
|
|
59
69
|
"docker-compose.yaml",
|
|
60
70
|
"compose.yml",
|
|
@@ -176,7 +186,7 @@ function readTopLevelName(absPath) {
|
|
|
176
186
|
}
|
|
177
187
|
let yamlValue;
|
|
178
188
|
try {
|
|
179
|
-
yamlValue =
|
|
189
|
+
yamlValue = parseComposeYaml(raw);
|
|
180
190
|
} catch (_error) {
|
|
181
191
|
return;
|
|
182
192
|
}
|
|
@@ -187,47 +197,29 @@ function readTopLevelName(absPath) {
|
|
|
187
197
|
return value;
|
|
188
198
|
}
|
|
189
199
|
//#endregion
|
|
190
|
-
//#region src/utils/exec.ts
|
|
191
|
-
/** Type guard for `execSync` errors that carry stdout/stderr/status. */
|
|
192
|
-
function isExecSyncError(err) {
|
|
193
|
-
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";
|
|
194
|
-
}
|
|
195
|
-
//#endregion
|
|
196
200
|
//#region src/docker-check/executor.ts
|
|
197
201
|
/** Create an executor that runs real commands, fetches, and manages process signals. */
|
|
198
202
|
function createRealExecutor() {
|
|
199
203
|
return {
|
|
200
204
|
exec(file, args, options) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
} catch (err) {
|
|
220
|
-
if (isExecSyncError(err)) return {
|
|
221
|
-
stdout: err.stdout,
|
|
222
|
-
stderr: err.stderr,
|
|
223
|
-
exitCode: err.status
|
|
224
|
-
};
|
|
225
|
-
return {
|
|
226
|
-
stdout: "",
|
|
227
|
-
stderr: "",
|
|
228
|
-
exitCode: 1
|
|
229
|
-
};
|
|
230
|
-
}
|
|
205
|
+
const result = spawnSync(file, args, {
|
|
206
|
+
cwd: options?.cwd,
|
|
207
|
+
env: options?.env ? {
|
|
208
|
+
...process.env,
|
|
209
|
+
...options.env
|
|
210
|
+
} : void 0,
|
|
211
|
+
encoding: "utf-8",
|
|
212
|
+
stdio: [
|
|
213
|
+
"pipe",
|
|
214
|
+
"pipe",
|
|
215
|
+
"pipe"
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
stdout: result.stdout ?? "",
|
|
220
|
+
stderr: result.stderr ?? "",
|
|
221
|
+
exitCode: result.status ?? 1
|
|
222
|
+
};
|
|
231
223
|
},
|
|
232
224
|
execInherit(file, args, options) {
|
|
233
225
|
execFileSync(file, args, {
|
|
@@ -270,6 +262,55 @@ function composeArgs(config) {
|
|
|
270
262
|
if (config.envFile) args.push("--env-file", config.envFile);
|
|
271
263
|
return args;
|
|
272
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Compose's unset-interpolation warning. The variable name is quoted, and the
|
|
267
|
+
* quotes arrive backslash-escaped when stderr is a pipe (which it always is
|
|
268
|
+
* here) but bare when it is a TTY — so both forms are accepted.
|
|
269
|
+
*/
|
|
270
|
+
const UNSET_VARIABLE_WARNING = /The\s+\\?"([^"\\]+)\\?"\s+variable is not set/g;
|
|
271
|
+
/** Names of the interpolation variables compose warned were unset, in order, de-duplicated. */
|
|
272
|
+
function parseUnsetVariables(stderr) {
|
|
273
|
+
const names = /* @__PURE__ */ new Set();
|
|
274
|
+
for (const match of stderr.matchAll(UNSET_VARIABLE_WARNING)) {
|
|
275
|
+
const name = match[1];
|
|
276
|
+
if (name) names.add(name);
|
|
277
|
+
}
|
|
278
|
+
return [...names];
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Ask compose which interpolation variables are unset, and map each to an empty
|
|
282
|
+
* string so it can be injected into the child env of every later compose call.
|
|
283
|
+
*
|
|
284
|
+
* Compose warns `The "FOO" variable is not set` once per file per invocation —
|
|
285
|
+
* including on `down` — for a `${FOO}` with no default. Nothing expressible in
|
|
286
|
+
* the check overlay silences that: interpolation happens per-file, before the
|
|
287
|
+
* merge. Setting the variable does silence it, and setting it to the *empty
|
|
288
|
+
* string* is what compose already substitutes, so the value each container sees
|
|
289
|
+
* is unchanged. A non-empty placeholder would not be: a service that dials
|
|
290
|
+
* `${LYRION_HOST}` would start resolving a hostname where today it gets "".
|
|
291
|
+
*
|
|
292
|
+
* Detection is delegated to compose rather than re-implementing its grammar:
|
|
293
|
+
* `config` has already consulted `--env-file` and the process env, so it reports
|
|
294
|
+
* exactly the variables that still need filling, in every position (`image:`,
|
|
295
|
+
* `ports:`, `volumes:`, …) and every form, with `$$` escapes and `${FOO:-def}`
|
|
296
|
+
* defaults already accounted for. If the wording ever changes we fill nothing
|
|
297
|
+
* and the warnings simply come back — a `${FOO:?required}` error is never masked.
|
|
298
|
+
*
|
|
299
|
+
* Variables already present in `config.env` are left alone: the free-port logic
|
|
300
|
+
* owns PORT, and an empty string there would unbind the published port.
|
|
301
|
+
*/
|
|
302
|
+
function fillUnsetVariables(executor, config) {
|
|
303
|
+
const result = executor.exec("docker", [...composeArgs(config), "config"], {
|
|
304
|
+
cwd: config.cwd,
|
|
305
|
+
env: config.env
|
|
306
|
+
});
|
|
307
|
+
const filled = {};
|
|
308
|
+
for (const name of parseUnsetVariables(result.stderr)) {
|
|
309
|
+
if (config.env && name in config.env) continue;
|
|
310
|
+
filled[name] = "";
|
|
311
|
+
}
|
|
312
|
+
return filled;
|
|
313
|
+
}
|
|
273
314
|
/** Run the build command if configured. */
|
|
274
315
|
function buildImages(executor, config) {
|
|
275
316
|
if (!config.buildCommand) return;
|
|
@@ -372,6 +413,33 @@ async function checkHttpHealth(executor, check) {
|
|
|
372
413
|
//#region src/docker-check/check.ts
|
|
373
414
|
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
374
415
|
const DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
416
|
+
/**
|
|
417
|
+
* Fill any compose interpolation variable that is still unset with an empty
|
|
418
|
+
* string, so `${FOO}` with no default stops warning on every compose call —
|
|
419
|
+
* `up` and, after the check has already passed, teardown. See
|
|
420
|
+
* `fillUnsetVariables` for why empty (not a placeholder) and why compose itself
|
|
421
|
+
* decides which variables need it.
|
|
422
|
+
*
|
|
423
|
+
* This runs here rather than in the `docker:check` command so the env reaches
|
|
424
|
+
* every compose invocation *and* the smoketest through one `CheckConfig`, and so
|
|
425
|
+
* programmatic consumers of the `docker-check` export get it too.
|
|
426
|
+
*/
|
|
427
|
+
function withFilledVariables(executor, config) {
|
|
428
|
+
const filled = fillUnsetVariables(executor, config.compose);
|
|
429
|
+
const names = Object.keys(filled);
|
|
430
|
+
if (names.length === 0) return config;
|
|
431
|
+
executor.log(`Unset compose variables set to an empty string: ${names.join(", ")}. To supply real values, put them in ${config.compose.envFile ?? "docker-compose.check.env"} beside the compose file.`);
|
|
432
|
+
return {
|
|
433
|
+
...config,
|
|
434
|
+
compose: {
|
|
435
|
+
...config.compose,
|
|
436
|
+
env: {
|
|
437
|
+
...config.compose.env,
|
|
438
|
+
...filled
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
375
443
|
/** Probe any not-yet-healthy HTTP endpoints, flipping them to healthy in `healthStatus`. */
|
|
376
444
|
async function updateHttpHealth(executor, healthChecks, healthStatus) {
|
|
377
445
|
for (const check of healthChecks) {
|
|
@@ -409,7 +477,8 @@ function runSmoketestStep(executor, config, cleanup, startTime) {
|
|
|
409
477
|
}
|
|
410
478
|
}
|
|
411
479
|
/** Run the full Docker check lifecycle. */
|
|
412
|
-
async function runDockerCheck(executor,
|
|
480
|
+
async function runDockerCheck(executor, rawConfig) {
|
|
481
|
+
const config = withFilledVariables(executor, rawConfig);
|
|
413
482
|
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
414
483
|
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
415
484
|
const { compose } = config;
|
|
@@ -540,4 +609,4 @@ function sanitize(name) {
|
|
|
540
609
|
return name.replace(/^@[^/]+\//, "").toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "repo";
|
|
541
610
|
}
|
|
542
611
|
//#endregion
|
|
543
|
-
export {
|
|
612
|
+
export { composeArgs as a, composePs as c, lintComposeFiles as d, parseComposeYaml as f, redact as g, isEnvVerbose as h, getContainerHealth as i, composeUp as l, debugExec as m, runDockerCheck as n, composeDown as o, debug as p, checkHttpHealth as r, composeLogs as s, deriveProjectName as t, createRealExecutor as u };
|
|
@@ -9,6 +9,7 @@ declare const PackageJsonSchema: z.ZodObject<{
|
|
|
9
9
|
scripts: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
10
10
|
dependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
11
11
|
devDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
12
|
+
peerDependencies: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
12
13
|
bin: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
13
14
|
exports: z.ZodOptional<z.ZodUnknown>;
|
|
14
15
|
main: z.ZodOptional<z.ZodString>;
|
|
@@ -37,6 +38,10 @@ type PackageJson = z.infer<typeof PackageJsonSchema>;
|
|
|
37
38
|
type CiPlatform = "github" | "forgejo" | "none";
|
|
38
39
|
type ReleaseStrategy = "release-it" | "simple" | "changesets" | "none";
|
|
39
40
|
type Formatter = "oxfmt" | "prettier";
|
|
41
|
+
/** Whether the repo has TS/JS source at all. Never inferred from absence. */
|
|
42
|
+
type Stack = "typescript" | "javascript" | "none";
|
|
43
|
+
/** What compiles the project, if anything. */
|
|
44
|
+
type Builder = "tsdown" | "vite" | "custom" | "none";
|
|
40
45
|
/** User's answers from the interactive prompt or CLI flags. */
|
|
41
46
|
interface ProjectConfig {
|
|
42
47
|
/** Project name (from package.json name or user input) */
|
|
@@ -47,6 +52,10 @@ interface ProjectConfig {
|
|
|
47
52
|
useEslintPlugin: boolean;
|
|
48
53
|
/** Formatter choice */
|
|
49
54
|
formatter: Formatter;
|
|
55
|
+
/** Whether the repo has TS/JS source at all */
|
|
56
|
+
stack: Stack;
|
|
57
|
+
/** What compiles the project */
|
|
58
|
+
builder: Builder;
|
|
50
59
|
/** Set up vitest with a starter test */
|
|
51
60
|
setupVitest: boolean;
|
|
52
61
|
/** CI platform choice */
|
|
@@ -88,6 +97,13 @@ interface GeneratorContext {
|
|
|
88
97
|
write: (relativePath: string, content: string) => void;
|
|
89
98
|
/** Remove a file from the target directory (no-op if not found) */
|
|
90
99
|
remove: (relativePath: string) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Rewrite generated content the way the target repo's own formatter would — so a
|
|
102
|
+
* generated file is a fixed point of `pnpm format` and can't drift against
|
|
103
|
+
* `repo:sync --check`. Returns the content unchanged when the formatter isn't
|
|
104
|
+
* installed yet. See `utils/format.ts`.
|
|
105
|
+
*/
|
|
106
|
+
format: (relativePath: string, content: string) => string;
|
|
91
107
|
/** Prompt user for conflict resolution on non-mergeable files */
|
|
92
108
|
confirmOverwrite: (relativePath: string) => Promise<"overwrite" | "skip">;
|
|
93
109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"zod": "^4.4.3"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@bensandee/config": "0.
|
|
50
|
+
"@bensandee/config": "0.13.1",
|
|
51
51
|
"@changesets/cli": "2.31.0",
|
|
52
52
|
"@release-it/bumper": "7.0.6",
|
|
53
53
|
"@types/node": "24.13.2",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"commit-and-tag-version": "12.7.3",
|
|
56
56
|
"prettier": "3.8.4",
|
|
57
57
|
"release-it": "20.2.0",
|
|
58
|
-
"tsdown": "0.22.
|
|
59
|
-
"@bensandee/claude-hooks": "0.
|
|
58
|
+
"tsdown": "0.22.3",
|
|
59
|
+
"@bensandee/claude-hooks": "0.10.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build": "tsdown",
|
package/tooling.schema.json
CHANGED
|
@@ -314,6 +314,52 @@
|
|
|
314
314
|
},
|
|
315
315
|
"additionalProperties": false
|
|
316
316
|
},
|
|
317
|
+
"docs": {
|
|
318
|
+
"description": "Plan/spec lifecycle gate (`bst docs:check`). Overrides only — dirs are discovered by convention. Set to false to disable. Violations are advisory until a pair is named in `enforced`.",
|
|
319
|
+
"anyOf": [
|
|
320
|
+
{
|
|
321
|
+
"type": "boolean",
|
|
322
|
+
"const": false
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"type": "object",
|
|
326
|
+
"properties": {
|
|
327
|
+
"enforced": {
|
|
328
|
+
"description": "Which discovered pairs fail the build. true = all; an array names active dirs (e.g. [\"docs/plans\"]); default false = every violation is advisory.",
|
|
329
|
+
"anyOf": [
|
|
330
|
+
{
|
|
331
|
+
"type": "boolean"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"type": "array",
|
|
335
|
+
"items": {
|
|
336
|
+
"type": "string"
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
"dirs": {
|
|
342
|
+
"description": "Extra active doc dirs beyond convention (e.g. [\"docs/rfcs\"]). Each retires into its own completed/ subdir.",
|
|
343
|
+
"type": "array",
|
|
344
|
+
"items": {
|
|
345
|
+
"type": "string"
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
"staleDays": {
|
|
349
|
+
"description": "Days before an untouched active doc is flagged (default 30; 0 disables the rule).",
|
|
350
|
+
"type": "integer",
|
|
351
|
+
"minimum": 0,
|
|
352
|
+
"maximum": 9007199254740991
|
|
353
|
+
},
|
|
354
|
+
"staleEnforced": {
|
|
355
|
+
"description": "Make the staleness rule fatal inside an enforced pair (default false: staleness is always advisory).",
|
|
356
|
+
"type": "boolean"
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"additionalProperties": false
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
},
|
|
317
363
|
"deploy": {
|
|
318
364
|
"description": "Generate a deploy-dispatch step in the release workflow that triggers a workflow_dispatch event in another repo after a successful release. The resolved release token must have rights on `dispatchRepo`.",
|
|
319
365
|
"type": "object",
|
|
@@ -346,6 +392,44 @@
|
|
|
346
392
|
"defaultBranch": {
|
|
347
393
|
"description": "Repository default branch name. Used by `checks:run` to decide when to enable coverage automatically. Default: detected via git (origin/HEAD), falling back to 'main'.",
|
|
348
394
|
"type": "string"
|
|
395
|
+
},
|
|
396
|
+
"unmanagedDevDependencies": {
|
|
397
|
+
"description": "devDependency names repo:sync must never add or re-pin. Escape hatch for holding a tooling-managed dep (oxlint, vitest, knip, ...) at a deliberately off-canonical version; without it, repo:sync raises any managed devDep whose version has drifted below the canonical pin.",
|
|
398
|
+
"type": "array",
|
|
399
|
+
"items": {
|
|
400
|
+
"type": "string"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
"stack": {
|
|
404
|
+
"description": "Whether the repo has TS/JS source. Detected from source evidence; TypeScript is never inferred from absence.",
|
|
405
|
+
"type": "string",
|
|
406
|
+
"enum": ["typescript", "javascript", "none"]
|
|
407
|
+
},
|
|
408
|
+
"builder": {
|
|
409
|
+
"description": "What compiles the project. Detected from the package.json build script, then config-file presence.",
|
|
410
|
+
"type": "string",
|
|
411
|
+
"enum": ["tsdown", "vite", "custom", "none"]
|
|
412
|
+
},
|
|
413
|
+
"checks": {
|
|
414
|
+
"description": "Check-pipeline overrides: extra steps to add, and steps to skip (unioned with CI defaults).",
|
|
415
|
+
"type": "object",
|
|
416
|
+
"properties": {
|
|
417
|
+
"add": {
|
|
418
|
+
"description": "Extra package.json scripts to run as first-class check steps, appended to the built-in list (e.g. ['lint:php']). An added step with no matching script is a hard failure.",
|
|
419
|
+
"type": "array",
|
|
420
|
+
"items": {
|
|
421
|
+
"type": "string"
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
"skip": {
|
|
425
|
+
"description": "Check steps to skip, unioned with the built-in CI defaults (never replacing them). Supports glob patterns.",
|
|
426
|
+
"type": "array",
|
|
427
|
+
"items": {
|
|
428
|
+
"type": "string"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
"additionalProperties": false
|
|
349
433
|
}
|
|
350
434
|
},
|
|
351
435
|
"additionalProperties": false
|