@bensandee/tooling 0.26.0 → 0.27.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 +276 -179
- package/dist/index.d.mts +4 -0
- package/package.json +9 -8
- package/tooling.schema.json +74 -55
package/dist/bin.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { l as createRealExecutor$1, t as runDockerCheck, u as isExecSyncError } from "./check-D41R218h.mjs";
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
|
-
import * as
|
|
4
|
+
import * as clack from "@clack/prompts";
|
|
5
|
+
import { isCancel, select } from "@clack/prompts";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
8
|
import JSON5 from "json5";
|
|
@@ -12,6 +13,22 @@ import { isMap, isScalar, isSeq, parse as parse$1, parseDocument, stringify } fr
|
|
|
12
13
|
import { execSync } from "node:child_process";
|
|
13
14
|
import picomatch from "picomatch";
|
|
14
15
|
import { tmpdir } from "node:os";
|
|
16
|
+
//#region src/utils/log.ts
|
|
17
|
+
const out = (msg) => console.log(msg);
|
|
18
|
+
const isCI = Boolean(process.env["CI"]);
|
|
19
|
+
const log$2 = isCI ? {
|
|
20
|
+
info: out,
|
|
21
|
+
warn: (msg) => out(`[warn] ${msg}`),
|
|
22
|
+
error: (msg) => out(`[error] ${msg}`),
|
|
23
|
+
success: (msg) => out(`✓ ${msg}`)
|
|
24
|
+
} : clack.log;
|
|
25
|
+
function note(body, title) {
|
|
26
|
+
if (isCI) {
|
|
27
|
+
if (title) out(`--- ${title} ---`);
|
|
28
|
+
out(body);
|
|
29
|
+
} else clack.note(body, title);
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
15
32
|
//#region src/types.ts
|
|
16
33
|
const LEGACY_TOOLS = [
|
|
17
34
|
"eslint",
|
|
@@ -205,6 +222,8 @@ function computeDefaults(targetDir) {
|
|
|
205
222
|
ci: detectCiPlatform(targetDir),
|
|
206
223
|
setupRenovate: true,
|
|
207
224
|
releaseStrategy: "none",
|
|
225
|
+
publishNpm: false,
|
|
226
|
+
publishDocker: false,
|
|
208
227
|
projectType: isMonorepo ? "default" : detectProjectType(targetDir),
|
|
209
228
|
detectPackageTypes: true
|
|
210
229
|
};
|
|
@@ -257,10 +276,10 @@ function getMonorepoPackages(targetDir) {
|
|
|
257
276
|
//#endregion
|
|
258
277
|
//#region src/prompts/init-prompts.ts
|
|
259
278
|
function isCancelled(value) {
|
|
260
|
-
return
|
|
279
|
+
return clack.isCancel(value);
|
|
261
280
|
}
|
|
262
281
|
async function runInitPrompts(targetDir, saved) {
|
|
263
|
-
|
|
282
|
+
clack.intro("@bensandee/tooling repo:sync");
|
|
264
283
|
const existingPkg = readPackageJson(targetDir);
|
|
265
284
|
const detected = detectProject(targetDir);
|
|
266
285
|
const defaults = computeDefaults(targetDir);
|
|
@@ -277,7 +296,7 @@ async function runInitPrompts(targetDir, saved) {
|
|
|
277
296
|
const projectType = saved?.projectType ?? defaults.projectType;
|
|
278
297
|
const detectPackageTypes = saved?.detectPackageTypes ?? defaults.detectPackageTypes;
|
|
279
298
|
if (detected.legacyConfigs.some((l) => l.tool === "prettier") && isFirstInit) {
|
|
280
|
-
const formatterAnswer = await
|
|
299
|
+
const formatterAnswer = await clack.select({
|
|
281
300
|
message: "Existing Prettier config found. Keep Prettier or migrate to oxfmt?",
|
|
282
301
|
initialValue: "prettier",
|
|
283
302
|
options: [{
|
|
@@ -290,14 +309,14 @@ async function runInitPrompts(targetDir, saved) {
|
|
|
290
309
|
}]
|
|
291
310
|
});
|
|
292
311
|
if (isCancelled(formatterAnswer)) {
|
|
293
|
-
|
|
312
|
+
clack.cancel("Cancelled.");
|
|
294
313
|
process.exit(0);
|
|
295
314
|
}
|
|
296
315
|
formatter = formatterAnswer;
|
|
297
316
|
}
|
|
298
317
|
const detectedCi = detectCiPlatform(targetDir);
|
|
299
318
|
if (isFirstInit && detectedCi === "none") {
|
|
300
|
-
const ciAnswer = await
|
|
319
|
+
const ciAnswer = await clack.select({
|
|
301
320
|
message: "CI workflow",
|
|
302
321
|
initialValue: "forgejo",
|
|
303
322
|
options: [
|
|
@@ -316,14 +335,14 @@ async function runInitPrompts(targetDir, saved) {
|
|
|
316
335
|
]
|
|
317
336
|
});
|
|
318
337
|
if (isCancelled(ciAnswer)) {
|
|
319
|
-
|
|
338
|
+
clack.cancel("Cancelled.");
|
|
320
339
|
process.exit(0);
|
|
321
340
|
}
|
|
322
341
|
ci = ciAnswer;
|
|
323
342
|
}
|
|
324
343
|
const hasExistingRelease = detected.hasReleaseItConfig || detected.hasSimpleReleaseConfig || detected.hasChangesetsConfig;
|
|
325
344
|
if (isFirstInit && !hasExistingRelease) {
|
|
326
|
-
const releaseAnswer = await
|
|
345
|
+
const releaseAnswer = await clack.select({
|
|
327
346
|
message: "Release management",
|
|
328
347
|
initialValue: defaults.releaseStrategy,
|
|
329
348
|
options: [
|
|
@@ -349,12 +368,40 @@ async function runInitPrompts(targetDir, saved) {
|
|
|
349
368
|
]
|
|
350
369
|
});
|
|
351
370
|
if (isCancelled(releaseAnswer)) {
|
|
352
|
-
|
|
371
|
+
clack.cancel("Cancelled.");
|
|
353
372
|
process.exit(0);
|
|
354
373
|
}
|
|
355
374
|
releaseStrategy = releaseAnswer;
|
|
356
375
|
}
|
|
357
|
-
|
|
376
|
+
let publishNpm = saved?.publishNpm ?? false;
|
|
377
|
+
if (isFirstInit && releaseStrategy !== "none") {
|
|
378
|
+
if (getPublishablePackages(targetDir, structure).length > 0) {
|
|
379
|
+
const answer = await clack.confirm({
|
|
380
|
+
message: "Publish packages to npm?",
|
|
381
|
+
initialValue: false
|
|
382
|
+
});
|
|
383
|
+
if (isCancelled(answer)) {
|
|
384
|
+
clack.cancel("Cancelled.");
|
|
385
|
+
process.exit(0);
|
|
386
|
+
}
|
|
387
|
+
publishNpm = answer;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
let publishDocker = saved?.publishDocker ?? false;
|
|
391
|
+
if (isFirstInit) {
|
|
392
|
+
if (existsSync(path.join(targetDir, "Dockerfile")) || existsSync(path.join(targetDir, "docker/Dockerfile"))) {
|
|
393
|
+
const answer = await clack.confirm({
|
|
394
|
+
message: "Publish Docker images to a registry?",
|
|
395
|
+
initialValue: false
|
|
396
|
+
});
|
|
397
|
+
if (isCancelled(answer)) {
|
|
398
|
+
clack.cancel("Cancelled.");
|
|
399
|
+
process.exit(0);
|
|
400
|
+
}
|
|
401
|
+
publishDocker = answer;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
clack.outro("Configuration complete!");
|
|
358
405
|
return {
|
|
359
406
|
name,
|
|
360
407
|
isNew: !isExisting,
|
|
@@ -365,6 +412,8 @@ async function runInitPrompts(targetDir, saved) {
|
|
|
365
412
|
ci,
|
|
366
413
|
setupRenovate,
|
|
367
414
|
releaseStrategy,
|
|
415
|
+
publishNpm,
|
|
416
|
+
publishDocker,
|
|
368
417
|
projectType,
|
|
369
418
|
detectPackageTypes,
|
|
370
419
|
targetDir
|
|
@@ -482,51 +531,53 @@ function createDryRunContext(config) {
|
|
|
482
531
|
//#region src/utils/tooling-config.ts
|
|
483
532
|
const CONFIG_FILE = ".tooling.json";
|
|
484
533
|
const DeclarativeHealthCheckSchema = z.object({
|
|
485
|
-
name: z.string(),
|
|
486
|
-
url: z.string(),
|
|
487
|
-
status: z.number().int().optional()
|
|
534
|
+
name: z.string().meta({ description: "Service name" }),
|
|
535
|
+
url: z.string().meta({ description: "Health check URL" }),
|
|
536
|
+
status: z.number().int().optional().meta({ description: "Expected HTTP status code" })
|
|
488
537
|
});
|
|
489
538
|
const DockerCheckConfigSchema = z.object({
|
|
490
|
-
composeFiles: z.array(z.string()).optional(),
|
|
491
|
-
envFile: z.string().optional(),
|
|
492
|
-
services: z.array(z.string()).optional(),
|
|
493
|
-
healthChecks: z.array(DeclarativeHealthCheckSchema).optional(),
|
|
494
|
-
buildCommand: z.string().optional(),
|
|
495
|
-
buildCwd: z.string().optional(),
|
|
496
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
497
|
-
pollIntervalMs: z.number().int().positive().optional()
|
|
539
|
+
composeFiles: z.array(z.string()).optional().meta({ description: "Compose files to use" }),
|
|
540
|
+
envFile: z.string().optional().meta({ description: "Environment file for compose" }),
|
|
541
|
+
services: z.array(z.string()).optional().meta({ description: "Services to check (default: all)" }),
|
|
542
|
+
healthChecks: z.array(DeclarativeHealthCheckSchema).optional().meta({ description: "Health check definitions" }),
|
|
543
|
+
buildCommand: z.string().optional().meta({ description: "Command to build images before checking" }),
|
|
544
|
+
buildCwd: z.string().optional().meta({ description: "Working directory for build command" }),
|
|
545
|
+
timeoutMs: z.number().int().positive().optional().meta({ description: "Overall timeout in milliseconds" }),
|
|
546
|
+
pollIntervalMs: z.number().int().positive().optional().meta({ description: "Poll interval in milliseconds" })
|
|
498
547
|
});
|
|
499
548
|
const ToolingConfigSchema = z.strictObject({
|
|
500
|
-
$schema: z.string().optional(),
|
|
501
|
-
structure: z.enum(["single", "monorepo"]).optional(),
|
|
502
|
-
useEslintPlugin: z.boolean().optional(),
|
|
503
|
-
formatter: z.enum(["oxfmt", "prettier"]).optional(),
|
|
504
|
-
setupVitest: z.boolean().optional(),
|
|
549
|
+
$schema: z.string().optional().meta({ description: "JSON Schema reference (ignored by tooling)" }),
|
|
550
|
+
structure: z.enum(["single", "monorepo"]).optional().meta({ description: "Project structure" }),
|
|
551
|
+
useEslintPlugin: z.boolean().optional().meta({ description: "Include @bensandee/eslint-plugin oxlint plugin" }),
|
|
552
|
+
formatter: z.enum(["oxfmt", "prettier"]).optional().meta({ description: "Formatter choice" }),
|
|
553
|
+
setupVitest: z.boolean().optional().meta({ description: "Generate vitest config and example test" }),
|
|
505
554
|
ci: z.enum([
|
|
506
555
|
"github",
|
|
507
556
|
"forgejo",
|
|
508
557
|
"none"
|
|
509
|
-
]).optional(),
|
|
510
|
-
setupRenovate: z.boolean().optional(),
|
|
558
|
+
]).optional().meta({ description: "CI platform" }),
|
|
559
|
+
setupRenovate: z.boolean().optional().meta({ description: "Generate Renovate config" }),
|
|
511
560
|
releaseStrategy: z.enum([
|
|
512
561
|
"release-it",
|
|
513
562
|
"simple",
|
|
514
563
|
"changesets",
|
|
515
564
|
"none"
|
|
516
|
-
]).optional(),
|
|
565
|
+
]).optional().meta({ description: "Release management strategy" }),
|
|
566
|
+
publishNpm: z.boolean().optional().meta({ description: "Publish packages to npm (opt-in)" }),
|
|
567
|
+
publishDocker: z.boolean().optional().meta({ description: "Publish Docker images to a registry (opt-in)" }),
|
|
517
568
|
projectType: z.enum([
|
|
518
569
|
"default",
|
|
519
570
|
"node",
|
|
520
571
|
"react",
|
|
521
572
|
"library"
|
|
522
|
-
]).optional(),
|
|
523
|
-
detectPackageTypes: z.boolean().optional(),
|
|
524
|
-
setupDocker: z.boolean().optional(),
|
|
573
|
+
]).optional().meta({ description: "Project type (determines tsconfig base)" }),
|
|
574
|
+
detectPackageTypes: z.boolean().optional().meta({ description: "Auto-detect project types for monorepo packages" }),
|
|
575
|
+
setupDocker: z.boolean().optional().meta({ description: "Generate Docker build/check scripts" }),
|
|
525
576
|
docker: z.record(z.string(), z.object({
|
|
526
|
-
dockerfile: z.string(),
|
|
527
|
-
context: z.string().default(".")
|
|
528
|
-
})).optional(),
|
|
529
|
-
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional()
|
|
577
|
+
dockerfile: z.string().meta({ description: "Path to Dockerfile relative to package" }),
|
|
578
|
+
context: z.string().default(".").meta({ description: "Docker build context relative to package" })
|
|
579
|
+
})).optional().meta({ description: "Docker package overrides (package name → config)" }),
|
|
580
|
+
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional().meta({ description: "Docker health check configuration or false to disable" })
|
|
530
581
|
});
|
|
531
582
|
/** Load saved tooling config from the target directory. Returns undefined if missing, throws on invalid. */
|
|
532
583
|
function loadToolingConfig(targetDir) {
|
|
@@ -546,6 +597,8 @@ const OVERRIDE_KEYS = [
|
|
|
546
597
|
"ci",
|
|
547
598
|
"setupRenovate",
|
|
548
599
|
"releaseStrategy",
|
|
600
|
+
"publishNpm",
|
|
601
|
+
"publishDocker",
|
|
549
602
|
"projectType",
|
|
550
603
|
"detectPackageTypes"
|
|
551
604
|
];
|
|
@@ -591,6 +644,8 @@ function mergeWithSavedConfig(detected, saved) {
|
|
|
591
644
|
ci: saved.ci ?? detected.ci,
|
|
592
645
|
setupRenovate: saved.setupRenovate ?? detected.setupRenovate,
|
|
593
646
|
releaseStrategy: saved.releaseStrategy ?? detected.releaseStrategy,
|
|
647
|
+
publishNpm: saved.publishNpm ?? detected.publishNpm,
|
|
648
|
+
publishDocker: saved.publishDocker ?? detected.publishDocker,
|
|
594
649
|
projectType: saved.projectType ?? detected.projectType,
|
|
595
650
|
detectPackageTypes: saved.detectPackageTypes ?? detected.detectPackageTypes
|
|
596
651
|
};
|
|
@@ -773,7 +828,7 @@ jobs:
|
|
|
773
828
|
DOCKER_REGISTRY_NAMESPACE: ${actionsExpr$2("vars.DOCKER_REGISTRY_NAMESPACE")}
|
|
774
829
|
DOCKER_USERNAME: ${actionsExpr$2("secrets.DOCKER_USERNAME")}
|
|
775
830
|
DOCKER_PASSWORD: ${actionsExpr$2("secrets.DOCKER_PASSWORD")}
|
|
776
|
-
run: pnpm exec
|
|
831
|
+
run: pnpm exec bst docker:publish
|
|
777
832
|
`;
|
|
778
833
|
}
|
|
779
834
|
function requiredDeploySteps() {
|
|
@@ -796,7 +851,7 @@ function requiredDeploySteps() {
|
|
|
796
851
|
},
|
|
797
852
|
{
|
|
798
853
|
match: { run: "docker:publish" },
|
|
799
|
-
step: { run: "pnpm exec
|
|
854
|
+
step: { run: "pnpm exec bst docker:publish" }
|
|
800
855
|
}
|
|
801
856
|
];
|
|
802
857
|
}
|
|
@@ -833,7 +888,7 @@ function hasDockerPackages(ctx) {
|
|
|
833
888
|
}
|
|
834
889
|
async function generateDeployCi(ctx) {
|
|
835
890
|
const filePath = "deploy-ci";
|
|
836
|
-
if (!
|
|
891
|
+
if (!ctx.config.publishDocker || ctx.config.ci === "none") return {
|
|
837
892
|
filePath,
|
|
838
893
|
action: "skipped",
|
|
839
894
|
description: "Deploy CI workflow not applicable"
|
|
@@ -852,21 +907,13 @@ async function generateDeployCi(ctx) {
|
|
|
852
907
|
};
|
|
853
908
|
const merged = mergeWorkflowSteps(existing, "deploy", requiredDeploySteps());
|
|
854
909
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
855
|
-
if (
|
|
856
|
-
|
|
857
|
-
return {
|
|
858
|
-
filePath: workflowPath,
|
|
859
|
-
action: "updated",
|
|
860
|
-
description: "Added missing steps to deploy workflow"
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
if (await ctx.confirmOverwrite(workflowPath) === "skip") {
|
|
864
|
-
if (merged.changed || withComment !== merged.content) {
|
|
910
|
+
if (!merged.changed) {
|
|
911
|
+
if (withComment !== existing) {
|
|
865
912
|
ctx.write(workflowPath, withComment);
|
|
866
913
|
return {
|
|
867
914
|
filePath: workflowPath,
|
|
868
915
|
action: "updated",
|
|
869
|
-
description: "Added
|
|
916
|
+
description: "Added schema comment to deploy workflow"
|
|
870
917
|
};
|
|
871
918
|
}
|
|
872
919
|
return {
|
|
@@ -875,11 +922,11 @@ async function generateDeployCi(ctx) {
|
|
|
875
922
|
description: "Existing deploy workflow preserved"
|
|
876
923
|
};
|
|
877
924
|
}
|
|
878
|
-
ctx.write(workflowPath,
|
|
925
|
+
ctx.write(workflowPath, withComment);
|
|
879
926
|
return {
|
|
880
927
|
filePath: workflowPath,
|
|
881
928
|
action: "updated",
|
|
882
|
-
description: "
|
|
929
|
+
description: "Added missing steps to deploy workflow"
|
|
883
930
|
};
|
|
884
931
|
}
|
|
885
932
|
return {
|
|
@@ -904,10 +951,10 @@ const STANDARD_SCRIPTS_SINGLE = {
|
|
|
904
951
|
test: "vitest run",
|
|
905
952
|
lint: "oxlint",
|
|
906
953
|
knip: "knip",
|
|
907
|
-
check: "
|
|
954
|
+
check: "bst checks:run",
|
|
908
955
|
"ci:check": "pnpm check --skip 'docker:*'",
|
|
909
|
-
"tooling:check": "
|
|
910
|
-
"tooling:sync": "
|
|
956
|
+
"tooling:check": "bst repo:sync --check",
|
|
957
|
+
"tooling:sync": "bst repo:sync"
|
|
911
958
|
};
|
|
912
959
|
const STANDARD_SCRIPTS_MONOREPO = {
|
|
913
960
|
build: "pnpm -r build",
|
|
@@ -915,10 +962,10 @@ const STANDARD_SCRIPTS_MONOREPO = {
|
|
|
915
962
|
typecheck: "pnpm -r --parallel run typecheck",
|
|
916
963
|
lint: "oxlint",
|
|
917
964
|
knip: "knip",
|
|
918
|
-
check: "
|
|
965
|
+
check: "bst checks:run",
|
|
919
966
|
"ci:check": "pnpm check --skip 'docker:*'",
|
|
920
|
-
"tooling:check": "
|
|
921
|
-
"tooling:sync": "
|
|
967
|
+
"tooling:check": "bst repo:sync --check",
|
|
968
|
+
"tooling:sync": "bst repo:sync"
|
|
922
969
|
};
|
|
923
970
|
/** Scripts that tooling owns — map from script name to keyword that must appear in the value. */
|
|
924
971
|
const MANAGED_SCRIPTS = {
|
|
@@ -983,8 +1030,8 @@ function addReleaseDeps(deps, config) {
|
|
|
983
1030
|
function getAddedDevDepNames(config) {
|
|
984
1031
|
const deps = { ...ROOT_DEV_DEPS };
|
|
985
1032
|
if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
|
|
986
|
-
deps["@bensandee/config"] = "0.9.
|
|
987
|
-
deps["@bensandee/tooling"] = "0.
|
|
1033
|
+
deps["@bensandee/config"] = "0.9.1";
|
|
1034
|
+
deps["@bensandee/tooling"] = "0.27.0";
|
|
988
1035
|
if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
|
|
989
1036
|
if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
|
|
990
1037
|
addReleaseDeps(deps, config);
|
|
@@ -1001,15 +1048,15 @@ async function generatePackageJson(ctx) {
|
|
|
1001
1048
|
format: formatScript
|
|
1002
1049
|
};
|
|
1003
1050
|
if (ctx.config.releaseStrategy === "changesets") allScripts["changeset"] = "changeset";
|
|
1004
|
-
if (ctx.config.releaseStrategy !== "none" && ctx.config.releaseStrategy !== "changesets") allScripts["trigger-release"] = "
|
|
1051
|
+
if (ctx.config.releaseStrategy !== "none" && ctx.config.releaseStrategy !== "changesets") allScripts["trigger-release"] = "bst release:trigger";
|
|
1005
1052
|
if (hasDockerPackages(ctx)) {
|
|
1006
|
-
allScripts["docker:build"] = "
|
|
1007
|
-
allScripts["docker:check"] = "
|
|
1053
|
+
allScripts["docker:build"] = "bst docker:build";
|
|
1054
|
+
allScripts["docker:check"] = "bst docker:check";
|
|
1008
1055
|
}
|
|
1009
1056
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1010
1057
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1011
|
-
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.
|
|
1012
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1058
|
+
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.1";
|
|
1059
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.27.0";
|
|
1013
1060
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
|
|
1014
1061
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
|
|
1015
1062
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
|
|
@@ -1770,8 +1817,14 @@ const ClaudeSettingsSchema = z.object({
|
|
|
1770
1817
|
});
|
|
1771
1818
|
function parseClaudeSettings(raw) {
|
|
1772
1819
|
try {
|
|
1773
|
-
const
|
|
1774
|
-
|
|
1820
|
+
const json = JSON.parse(raw);
|
|
1821
|
+
const rawResult = z.record(z.string(), z.unknown()).safeParse(json);
|
|
1822
|
+
const settingsResult = ClaudeSettingsSchema.safeParse(json);
|
|
1823
|
+
if (!rawResult.success || !settingsResult.success) return void 0;
|
|
1824
|
+
return {
|
|
1825
|
+
settings: settingsResult.data,
|
|
1826
|
+
rawJson: rawResult.data
|
|
1827
|
+
};
|
|
1775
1828
|
} catch {
|
|
1776
1829
|
return;
|
|
1777
1830
|
}
|
|
@@ -1830,7 +1883,6 @@ function buildSettings(ctx) {
|
|
|
1830
1883
|
"Bash(wc *)",
|
|
1831
1884
|
"Bash(test *)",
|
|
1832
1885
|
"Bash([ *)",
|
|
1833
|
-
"Bash(find *)",
|
|
1834
1886
|
"Bash(grep *)",
|
|
1835
1887
|
"Bash(which *)",
|
|
1836
1888
|
"Bash(node -e *)",
|
|
@@ -1897,7 +1949,7 @@ function buildSettings(ctx) {
|
|
|
1897
1949
|
instructions: [
|
|
1898
1950
|
"Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
|
|
1899
1951
|
"No typecasts (as/any). Use zod schemas, type guards, or narrowing instead.",
|
|
1900
|
-
"Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option.",
|
|
1952
|
+
"Fix lint violations instead of suppressing them. Only add disable comments when suppression is genuinely the best option. For no-empty-function: add a `{ /* no-op */ }` comment body instead of a disable comment.",
|
|
1901
1953
|
"Prefer extensionless imports; if an extension is required, use .ts over .js."
|
|
1902
1954
|
],
|
|
1903
1955
|
enabledPlugins,
|
|
@@ -1921,27 +1973,44 @@ function writeOrMergeSettings(ctx, filePath, generated) {
|
|
|
1921
1973
|
action: "skipped",
|
|
1922
1974
|
description: "Could not parse existing settings"
|
|
1923
1975
|
};
|
|
1924
|
-
const
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
const
|
|
1930
|
-
|
|
1976
|
+
const { settings, rawJson } = parsed;
|
|
1977
|
+
const missingAllow = generated.permissions.allow.filter((rule) => !settings.permissions.allow.includes(rule));
|
|
1978
|
+
const missingDeny = generated.permissions.deny.filter((rule) => !settings.permissions.deny.includes(rule));
|
|
1979
|
+
const mergedInstructions = [...settings.instructions];
|
|
1980
|
+
let instructionChanges = 0;
|
|
1981
|
+
for (const inst of generated.instructions) {
|
|
1982
|
+
if (mergedInstructions.includes(inst)) continue;
|
|
1983
|
+
const prefixIdx = mergedInstructions.findIndex((e) => inst.startsWith(e) || e.startsWith(inst));
|
|
1984
|
+
if (prefixIdx !== -1) mergedInstructions[prefixIdx] = inst;
|
|
1985
|
+
else mergedInstructions.push(inst);
|
|
1986
|
+
instructionChanges++;
|
|
1987
|
+
}
|
|
1988
|
+
const missingPlugins = Object.entries(generated.enabledPlugins).filter(([key]) => !(key in settings.enabledPlugins));
|
|
1989
|
+
const missingMarketplaces = Object.entries(generated.extraKnownMarketplaces).filter(([key]) => !(key in settings.extraKnownMarketplaces));
|
|
1990
|
+
const changed = missingAllow.length + missingDeny.length + instructionChanges + missingPlugins.length + missingMarketplaces.length;
|
|
1991
|
+
if (changed === 0) return {
|
|
1931
1992
|
filePath,
|
|
1932
1993
|
action: "skipped",
|
|
1933
1994
|
description: "Already has all rules and instructions"
|
|
1934
1995
|
};
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1996
|
+
rawJson["permissions"] = {
|
|
1997
|
+
allow: [...settings.permissions.allow, ...missingAllow],
|
|
1998
|
+
deny: [...settings.permissions.deny, ...missingDeny]
|
|
1999
|
+
};
|
|
2000
|
+
rawJson["instructions"] = mergedInstructions;
|
|
2001
|
+
const updatedPlugins = { ...settings.enabledPlugins };
|
|
2002
|
+
for (const [key, value] of missingPlugins) updatedPlugins[key] = value;
|
|
2003
|
+
const updatedMarketplaces = { ...settings.extraKnownMarketplaces };
|
|
2004
|
+
for (const [key, value] of missingMarketplaces) updatedMarketplaces[key] = value;
|
|
2005
|
+
if (Object.keys(updatedPlugins).length > 0) rawJson["enabledPlugins"] = updatedPlugins;
|
|
2006
|
+
else delete rawJson["enabledPlugins"];
|
|
2007
|
+
if (Object.keys(updatedMarketplaces).length > 0) rawJson["extraKnownMarketplaces"] = updatedMarketplaces;
|
|
2008
|
+
else delete rawJson["extraKnownMarketplaces"];
|
|
2009
|
+
ctx.write(filePath, JSON.stringify(rawJson, null, 2) + "\n");
|
|
1941
2010
|
return {
|
|
1942
2011
|
filePath,
|
|
1943
2012
|
action: "updated",
|
|
1944
|
-
description: `
|
|
2013
|
+
description: `Updated ${String(changed)} rules/instructions`
|
|
1945
2014
|
};
|
|
1946
2015
|
}
|
|
1947
2016
|
ctx.write(filePath, serializeSettings$1(generated));
|
|
@@ -2115,13 +2184,13 @@ permissions:
|
|
|
2115
2184
|
- name: Release
|
|
2116
2185
|
env:
|
|
2117
2186
|
GITHUB_TOKEN: \${{ github.token }}
|
|
2118
|
-
run: pnpm exec
|
|
2187
|
+
run: pnpm exec bst release:simple` : `
|
|
2119
2188
|
- name: Release
|
|
2120
2189
|
env:
|
|
2121
2190
|
FORGEJO_SERVER_URL: \${{ github.server_url }}
|
|
2122
2191
|
FORGEJO_REPOSITORY: \${{ github.repository }}
|
|
2123
2192
|
FORGEJO_TOKEN: \${{ secrets.FORGEJO_TOKEN }}
|
|
2124
|
-
run: pnpm exec
|
|
2193
|
+
run: pnpm exec bst release:simple`;
|
|
2125
2194
|
return `${workflowSchemaComment(ci)}name: Release
|
|
2126
2195
|
on:
|
|
2127
2196
|
workflow_dispatch:
|
|
@@ -2161,7 +2230,7 @@ function changesetsReleaseStep(ci, publishesNpm) {
|
|
|
2161
2230
|
FORGEJO_TOKEN: actionsExpr("secrets.FORGEJO_TOKEN"),
|
|
2162
2231
|
...publishesNpm && { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
|
|
2163
2232
|
},
|
|
2164
|
-
run: "pnpm exec
|
|
2233
|
+
run: "pnpm exec bst release:changesets"
|
|
2165
2234
|
}
|
|
2166
2235
|
};
|
|
2167
2236
|
}
|
|
@@ -2209,13 +2278,13 @@ function requiredReleaseSteps(strategy, nodeVersionYaml, publishesNpm) {
|
|
|
2209
2278
|
case "simple":
|
|
2210
2279
|
steps.push({
|
|
2211
2280
|
match: { run: "release:simple" },
|
|
2212
|
-
step: { run: "pnpm exec
|
|
2281
|
+
step: { run: "pnpm exec bst release:simple" }
|
|
2213
2282
|
});
|
|
2214
2283
|
break;
|
|
2215
2284
|
case "changesets":
|
|
2216
2285
|
steps.push({
|
|
2217
2286
|
match: { run: "changeset" },
|
|
2218
|
-
step: { run: "pnpm exec
|
|
2287
|
+
step: { run: "pnpm exec bst release:changesets" }
|
|
2219
2288
|
});
|
|
2220
2289
|
break;
|
|
2221
2290
|
}
|
|
@@ -2257,7 +2326,7 @@ async function generateReleaseCi(ctx) {
|
|
|
2257
2326
|
action: "skipped",
|
|
2258
2327
|
description: "Release CI workflow not applicable"
|
|
2259
2328
|
};
|
|
2260
|
-
const publishesNpm =
|
|
2329
|
+
const publishesNpm = ctx.config.publishNpm === true;
|
|
2261
2330
|
if (ctx.config.releaseStrategy === "changesets") return generateChangesetsReleaseCi(ctx, publishesNpm);
|
|
2262
2331
|
const isGitHub = ctx.config.ci === "github";
|
|
2263
2332
|
const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
|
|
@@ -2278,21 +2347,13 @@ async function generateReleaseCi(ctx) {
|
|
|
2278
2347
|
};
|
|
2279
2348
|
const merged = mergeWorkflowSteps(existing, "release", requiredReleaseSteps(ctx.config.releaseStrategy, nodeVersionYaml, publishesNpm));
|
|
2280
2349
|
const withComment = ensureSchemaComment(merged.content, ctx.config.ci);
|
|
2281
|
-
if (
|
|
2282
|
-
|
|
2283
|
-
return {
|
|
2284
|
-
filePath: workflowPath,
|
|
2285
|
-
action: "updated",
|
|
2286
|
-
description: "Added missing steps to release workflow"
|
|
2287
|
-
};
|
|
2288
|
-
}
|
|
2289
|
-
if (await ctx.confirmOverwrite(workflowPath) === "skip") {
|
|
2290
|
-
if (merged.changed || withComment !== merged.content) {
|
|
2350
|
+
if (!merged.changed) {
|
|
2351
|
+
if (withComment !== existing) {
|
|
2291
2352
|
ctx.write(workflowPath, withComment);
|
|
2292
2353
|
return {
|
|
2293
2354
|
filePath: workflowPath,
|
|
2294
2355
|
action: "updated",
|
|
2295
|
-
description: "Added
|
|
2356
|
+
description: "Added schema comment to release workflow"
|
|
2296
2357
|
};
|
|
2297
2358
|
}
|
|
2298
2359
|
return {
|
|
@@ -2301,11 +2362,11 @@ async function generateReleaseCi(ctx) {
|
|
|
2301
2362
|
description: "Existing release workflow preserved"
|
|
2302
2363
|
};
|
|
2303
2364
|
}
|
|
2304
|
-
ctx.write(workflowPath,
|
|
2365
|
+
ctx.write(workflowPath, withComment);
|
|
2305
2366
|
return {
|
|
2306
2367
|
filePath: workflowPath,
|
|
2307
2368
|
action: "updated",
|
|
2308
|
-
description: "
|
|
2369
|
+
description: "Added missing steps to release workflow"
|
|
2309
2370
|
};
|
|
2310
2371
|
}
|
|
2311
2372
|
return {
|
|
@@ -3085,15 +3146,19 @@ function contextAsDockerReader(ctx) {
|
|
|
3085
3146
|
}
|
|
3086
3147
|
/** Log what was detected so the user understands generator decisions. */
|
|
3087
3148
|
function logDetectionSummary(ctx) {
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3149
|
+
if (ctx.config.publishDocker) {
|
|
3150
|
+
const dockerPackages = detectDockerPackages(contextAsDockerReader(ctx), ctx.targetDir, ctx.config.name);
|
|
3151
|
+
if (dockerPackages.length > 0) log$2.info(`Docker images: ${dockerPackages.map((pkg) => pkg.imageName).join(", ")}`);
|
|
3152
|
+
}
|
|
3153
|
+
if (ctx.config.publishNpm) {
|
|
3154
|
+
const publishable = getPublishablePackages(ctx.targetDir, ctx.config.structure, ctx.packageJson);
|
|
3155
|
+
if (publishable.length > 0) log$2.info(`npm packages: ${publishable.map((pkg) => pkg.name).join(", ")}`);
|
|
3156
|
+
}
|
|
3092
3157
|
}
|
|
3093
3158
|
async function runInit(config, options = {}) {
|
|
3094
3159
|
const detected = detectProject(config.targetDir);
|
|
3095
3160
|
const { ctx, archivedFiles } = createContext(config, options.confirmOverwrite ?? (async (relativePath) => {
|
|
3096
|
-
const result = await
|
|
3161
|
+
const result = await select({
|
|
3097
3162
|
message: `${relativePath} already exists. What do you want to do?`,
|
|
3098
3163
|
options: [{
|
|
3099
3164
|
value: "overwrite",
|
|
@@ -3103,10 +3168,9 @@ async function runInit(config, options = {}) {
|
|
|
3103
3168
|
label: "Skip"
|
|
3104
3169
|
}]
|
|
3105
3170
|
});
|
|
3106
|
-
if (
|
|
3171
|
+
if (isCancel(result)) return "skip";
|
|
3107
3172
|
return result;
|
|
3108
3173
|
}));
|
|
3109
|
-
if (config.releaseStrategy !== "none" && !ctx.packageJson?.repository) p.log.warn(`package.json is missing a "repository" field — required for release strategy "${config.releaseStrategy}"`);
|
|
3110
3174
|
logDetectionSummary(ctx);
|
|
3111
3175
|
const results = await runGenerators(ctx);
|
|
3112
3176
|
const alreadyArchived = new Set(results.filter((r) => r.action === "archived").map((r) => r.filePath));
|
|
@@ -3117,8 +3181,12 @@ async function runInit(config, options = {}) {
|
|
|
3117
3181
|
});
|
|
3118
3182
|
const created = results.filter((r) => r.action === "created");
|
|
3119
3183
|
const updated = results.filter((r) => r.action === "updated");
|
|
3120
|
-
|
|
3121
|
-
|
|
3184
|
+
const hasChanges = created.length > 0 || updated.length > 0 || archivedFiles.length > 0;
|
|
3185
|
+
const prompt = generateMigratePrompt(results, config, detected);
|
|
3186
|
+
const promptPath = ".tooling-migrate.md";
|
|
3187
|
+
ctx.write(promptPath, prompt);
|
|
3188
|
+
if (!hasChanges && options.noPrompt) {
|
|
3189
|
+
log$2.success("Repository is up to date.");
|
|
3122
3190
|
return results;
|
|
3123
3191
|
}
|
|
3124
3192
|
if (results.some((r) => r.action === "archived" && r.filePath.startsWith(".husky/"))) try {
|
|
@@ -3131,18 +3199,15 @@ async function runInit(config, options = {}) {
|
|
|
3131
3199
|
const summaryLines = [];
|
|
3132
3200
|
if (created.length > 0) summaryLines.push(`Created: ${created.map((r) => r.filePath).join(", ")}`);
|
|
3133
3201
|
if (updated.length > 0) summaryLines.push(`Updated: ${updated.map((r) => r.filePath).join(", ")}`);
|
|
3134
|
-
|
|
3202
|
+
note(summaryLines.join("\n"), "Summary");
|
|
3135
3203
|
if (!options.noPrompt) {
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
ctx.write(promptPath, prompt);
|
|
3139
|
-
p.log.info(`Migration prompt written to ${promptPath}`);
|
|
3140
|
-
p.log.info("In Claude Code, run: \"Execute the steps in .tooling-migrate.md\"");
|
|
3204
|
+
log$2.info(`Migration prompt written to ${promptPath}`);
|
|
3205
|
+
log$2.info("In Claude Code, run: \"Execute the steps in .tooling-migrate.md\"");
|
|
3141
3206
|
}
|
|
3142
3207
|
const bensandeeDeps = getAddedDevDepNames(config).filter((name) => name.startsWith("@bensandee/"));
|
|
3143
3208
|
const hasLockfile = ctx.exists("pnpm-lock.yaml");
|
|
3144
3209
|
if (bensandeeDeps.length > 0 && hasLockfile) {
|
|
3145
|
-
|
|
3210
|
+
log$2.info("Updating @bensandee/* packages...");
|
|
3146
3211
|
try {
|
|
3147
3212
|
execSync(`pnpm update --latest ${bensandeeDeps.join(" ")}`, {
|
|
3148
3213
|
cwd: config.targetDir,
|
|
@@ -3150,10 +3215,17 @@ async function runInit(config, options = {}) {
|
|
|
3150
3215
|
timeout: 6e4
|
|
3151
3216
|
});
|
|
3152
3217
|
} catch (_error) {
|
|
3153
|
-
|
|
3218
|
+
log$2.warn("Could not update @bensandee/* packages — run pnpm install manually");
|
|
3154
3219
|
}
|
|
3155
3220
|
}
|
|
3156
|
-
|
|
3221
|
+
if (hasChanges && ctx.exists("package.json")) try {
|
|
3222
|
+
execSync("pnpm format", {
|
|
3223
|
+
cwd: config.targetDir,
|
|
3224
|
+
stdio: "ignore",
|
|
3225
|
+
timeout: 3e4
|
|
3226
|
+
});
|
|
3227
|
+
} catch (_error) {}
|
|
3228
|
+
note([
|
|
3157
3229
|
"1. Run: pnpm install",
|
|
3158
3230
|
"2. Run: pnpm check",
|
|
3159
3231
|
...options.noPrompt ? [] : ["3. In Claude Code, run: \"Execute the steps in .tooling-migrate.md\""]
|
|
@@ -3235,22 +3307,22 @@ async function runCheck(targetDir) {
|
|
|
3235
3307
|
return true;
|
|
3236
3308
|
});
|
|
3237
3309
|
if (actionable.length === 0) {
|
|
3238
|
-
|
|
3310
|
+
log$2.success("Repository is up to date.");
|
|
3239
3311
|
return 0;
|
|
3240
3312
|
}
|
|
3241
|
-
|
|
3313
|
+
log$2.warn(`${actionable.length} file(s) would be changed by repo:sync`);
|
|
3242
3314
|
for (const r of actionable) {
|
|
3243
|
-
|
|
3315
|
+
log$2.info(` ${r.action}: ${r.filePath} — ${r.description}`);
|
|
3244
3316
|
const newContent = pendingWrites.get(r.filePath);
|
|
3245
3317
|
if (!newContent) continue;
|
|
3246
3318
|
const existingPath = path.join(targetDir, r.filePath);
|
|
3247
3319
|
const existing = existsSync(existingPath) ? readFileSync(existingPath, "utf-8") : void 0;
|
|
3248
3320
|
if (!existing) {
|
|
3249
3321
|
const lineCount = newContent.split("\n").length - 1;
|
|
3250
|
-
|
|
3322
|
+
log$2.info(` + ${lineCount} new lines`);
|
|
3251
3323
|
} else {
|
|
3252
3324
|
const diff = lineDiff(existing, newContent);
|
|
3253
|
-
for (const line of diff)
|
|
3325
|
+
for (const line of diff) log$2.info(` ${line}`);
|
|
3254
3326
|
}
|
|
3255
3327
|
}
|
|
3256
3328
|
return 1;
|
|
@@ -3497,7 +3569,7 @@ async function createRelease(executor, conn, tag) {
|
|
|
3497
3569
|
//#region src/release/log.ts
|
|
3498
3570
|
/** Log a debug message when verbose mode is enabled. */
|
|
3499
3571
|
function debug(config, message) {
|
|
3500
|
-
if (config.verbose)
|
|
3572
|
+
if (config.verbose) log$2.info(`[debug] ${message}`);
|
|
3501
3573
|
}
|
|
3502
3574
|
/** Log the result of an exec call when verbose mode is enabled. */
|
|
3503
3575
|
function debugExec(config, label, result) {
|
|
@@ -3505,7 +3577,7 @@ function debugExec(config, label, result) {
|
|
|
3505
3577
|
const lines = [`[debug] ${label} (exit code ${String(result.exitCode)})`];
|
|
3506
3578
|
if (result.stdout.trim()) lines.push(` stdout: ${result.stdout.trim()}`);
|
|
3507
3579
|
if (result.stderr.trim()) lines.push(` stderr: ${result.stderr.trim()}`);
|
|
3508
|
-
|
|
3580
|
+
log$2.info(lines.join("\n"));
|
|
3509
3581
|
}
|
|
3510
3582
|
//#endregion
|
|
3511
3583
|
//#region src/release/version.ts
|
|
@@ -3577,7 +3649,7 @@ function buildPrContent(executor, cwd, packagesBefore) {
|
|
|
3577
3649
|
}
|
|
3578
3650
|
/** Mode 1: version packages and create/update a PR. */
|
|
3579
3651
|
async function runVersionMode(executor, config) {
|
|
3580
|
-
|
|
3652
|
+
log$2.info("Changesets detected — versioning packages");
|
|
3581
3653
|
const packagesBefore = executor.listWorkspacePackages(config.cwd);
|
|
3582
3654
|
debug(config, `Packages before versioning: ${packagesBefore.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "(none)"}`);
|
|
3583
3655
|
const changesetConfigPath = path.join(config.cwd, ".changeset", "config.json");
|
|
@@ -3603,19 +3675,19 @@ async function runVersionMode(executor, config) {
|
|
|
3603
3675
|
const addResult = executor.exec("git add -A", { cwd: config.cwd });
|
|
3604
3676
|
if (addResult.exitCode !== 0) throw new FatalError(`git add failed: ${addResult.stderr || addResult.stdout}`);
|
|
3605
3677
|
const remainingChangesets = executor.listChangesetFiles(config.cwd);
|
|
3606
|
-
if (remainingChangesets.length > 0)
|
|
3678
|
+
if (remainingChangesets.length > 0) log$2.warn(`Changeset files still present after versioning: ${remainingChangesets.join(", ")}`);
|
|
3607
3679
|
debug(config, `Changeset files after versioning: ${remainingChangesets.length > 0 ? remainingChangesets.join(", ") : "(none — all consumed)"}`);
|
|
3608
3680
|
const commitResult = executor.exec("git commit -m \"chore: version packages\"", { cwd: config.cwd });
|
|
3609
3681
|
debugExec(config, "git commit", commitResult);
|
|
3610
3682
|
if (commitResult.exitCode !== 0) {
|
|
3611
|
-
|
|
3683
|
+
log$2.info("Nothing to commit after versioning");
|
|
3612
3684
|
return {
|
|
3613
3685
|
mode: "version",
|
|
3614
3686
|
pr: "none"
|
|
3615
3687
|
};
|
|
3616
3688
|
}
|
|
3617
3689
|
if (config.dryRun) {
|
|
3618
|
-
|
|
3690
|
+
log$2.info("[dry-run] Would push and create/update PR");
|
|
3619
3691
|
return {
|
|
3620
3692
|
mode: "version",
|
|
3621
3693
|
pr: "none"
|
|
@@ -3638,7 +3710,7 @@ async function runVersionMode(executor, config) {
|
|
|
3638
3710
|
base: "main",
|
|
3639
3711
|
body
|
|
3640
3712
|
});
|
|
3641
|
-
|
|
3713
|
+
log$2.info("Created version PR");
|
|
3642
3714
|
return {
|
|
3643
3715
|
mode: "version",
|
|
3644
3716
|
pr: "created"
|
|
@@ -3648,7 +3720,7 @@ async function runVersionMode(executor, config) {
|
|
|
3648
3720
|
title,
|
|
3649
3721
|
body
|
|
3650
3722
|
});
|
|
3651
|
-
|
|
3723
|
+
log$2.info(`Updated version PR #${String(existingPr)}`);
|
|
3652
3724
|
return {
|
|
3653
3725
|
mode: "version",
|
|
3654
3726
|
pr: "updated"
|
|
@@ -3673,7 +3745,7 @@ async function retryAsync(fn) {
|
|
|
3673
3745
|
}
|
|
3674
3746
|
/** Mode 2: publish to npm, push tags, and create Forgejo releases. */
|
|
3675
3747
|
async function runPublishMode(executor, config) {
|
|
3676
|
-
|
|
3748
|
+
log$2.info("No changesets — publishing packages");
|
|
3677
3749
|
const publishResult = executor.exec("pnpm changeset publish", { cwd: config.cwd });
|
|
3678
3750
|
debugExec(config, "pnpm changeset publish", publishResult);
|
|
3679
3751
|
if (publishResult.exitCode !== 0) throw new FatalError(`pnpm changeset publish failed (exit code ${String(publishResult.exitCode)}):\n${publishResult.stderr}`);
|
|
@@ -3688,11 +3760,11 @@ async function runPublishMode(executor, config) {
|
|
|
3688
3760
|
debug(config, `Reconciled tags to push: ${tagsToPush.length > 0 ? tagsToPush.join(", ") : "(none)"}`);
|
|
3689
3761
|
if (config.dryRun) {
|
|
3690
3762
|
if (tagsToPush.length === 0) {
|
|
3691
|
-
|
|
3763
|
+
log$2.info("No packages were published");
|
|
3692
3764
|
return { mode: "none" };
|
|
3693
3765
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3766
|
+
log$2.info(`Tags to process: ${tagsToPush.join(", ")}`);
|
|
3767
|
+
log$2.info("[dry-run] Would push tags and create releases");
|
|
3696
3768
|
return {
|
|
3697
3769
|
mode: "publish",
|
|
3698
3770
|
tags: tagsToPush
|
|
@@ -3708,10 +3780,10 @@ async function runPublishMode(executor, config) {
|
|
|
3708
3780
|
for (const tag of remoteExpectedTags) if (!await findRelease(executor, conn, tag)) tagsWithMissingReleases.push(tag);
|
|
3709
3781
|
const allTags = [...tagsToPush, ...tagsWithMissingReleases];
|
|
3710
3782
|
if (allTags.length === 0) {
|
|
3711
|
-
|
|
3783
|
+
log$2.info("No packages were published");
|
|
3712
3784
|
return { mode: "none" };
|
|
3713
3785
|
}
|
|
3714
|
-
|
|
3786
|
+
log$2.info(`Tags to process: ${allTags.join(", ")}`);
|
|
3715
3787
|
const errors = [];
|
|
3716
3788
|
for (const tag of allTags) try {
|
|
3717
3789
|
if (!remoteSet.has(tag)) {
|
|
@@ -3722,7 +3794,7 @@ async function runPublishMode(executor, config) {
|
|
|
3722
3794
|
const pushTagResult = executor.exec(`git push origin refs/tags/${tag}`, { cwd: config.cwd });
|
|
3723
3795
|
if (pushTagResult.exitCode !== 0) throw new FatalError(`Failed to push tag ${tag}: ${pushTagResult.stderr || pushTagResult.stdout}`);
|
|
3724
3796
|
}
|
|
3725
|
-
if (await findRelease(executor, conn, tag))
|
|
3797
|
+
if (await findRelease(executor, conn, tag)) log$2.warn(`Release for ${tag} already exists — skipping`);
|
|
3726
3798
|
else {
|
|
3727
3799
|
await retryAsync(async () => {
|
|
3728
3800
|
try {
|
|
@@ -3732,14 +3804,14 @@ async function runPublishMode(executor, config) {
|
|
|
3732
3804
|
throw error;
|
|
3733
3805
|
}
|
|
3734
3806
|
});
|
|
3735
|
-
|
|
3807
|
+
log$2.info(`Created release for ${tag}`);
|
|
3736
3808
|
}
|
|
3737
3809
|
} catch (error) {
|
|
3738
3810
|
errors.push({
|
|
3739
3811
|
tag,
|
|
3740
3812
|
error
|
|
3741
3813
|
});
|
|
3742
|
-
|
|
3814
|
+
log$2.warn(`Failed to process ${tag}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3743
3815
|
}
|
|
3744
3816
|
if (errors.length > 0) throw new TransientError(`Failed to create releases for: ${errors.map((e) => e.tag).join(", ")}`);
|
|
3745
3817
|
return {
|
|
@@ -3909,12 +3981,12 @@ async function triggerForgejo(conn, ref) {
|
|
|
3909
3981
|
body: JSON.stringify({ ref })
|
|
3910
3982
|
});
|
|
3911
3983
|
if (!res.ok) throw new FatalError(`Failed to trigger Forgejo workflow: ${res.status} ${res.statusText}`);
|
|
3912
|
-
|
|
3984
|
+
log$2.info(`Triggered release workflow on Forgejo (ref: ${ref})`);
|
|
3913
3985
|
}
|
|
3914
3986
|
function triggerGitHub(ref) {
|
|
3915
3987
|
const result = createRealExecutor().exec(`gh workflow run release.yml --ref ${ref}`, { cwd: process.cwd() });
|
|
3916
3988
|
if (result.exitCode !== 0) throw new FatalError(`Failed to trigger GitHub workflow: ${result.stderr || result.stdout || "unknown error"}`);
|
|
3917
|
-
|
|
3989
|
+
log$2.info(`Triggered release workflow on GitHub (ref: ${ref})`);
|
|
3918
3990
|
}
|
|
3919
3991
|
//#endregion
|
|
3920
3992
|
//#region src/commands/forgejo-create-release.ts
|
|
@@ -3934,11 +4006,11 @@ const createForgejoReleaseCommand = defineCommand({
|
|
|
3934
4006
|
const executor = createRealExecutor();
|
|
3935
4007
|
const conn = resolved.conn;
|
|
3936
4008
|
if (await findRelease(executor, conn, args.tag)) {
|
|
3937
|
-
|
|
4009
|
+
log$2.info(`Release for ${args.tag} already exists — skipping`);
|
|
3938
4010
|
return;
|
|
3939
4011
|
}
|
|
3940
4012
|
await createRelease(executor, conn, args.tag);
|
|
3941
|
-
|
|
4013
|
+
log$2.info(`Created Forgejo release for ${args.tag}`);
|
|
3942
4014
|
}
|
|
3943
4015
|
});
|
|
3944
4016
|
//#endregion
|
|
@@ -3965,26 +4037,26 @@ async function mergeForgejo(conn, dryRun) {
|
|
|
3965
4037
|
const prNumber = await findOpenPr(executor, conn, HEAD_BRANCH);
|
|
3966
4038
|
if (prNumber === null) throw new FatalError(`No open PR found for branch ${HEAD_BRANCH}`);
|
|
3967
4039
|
if (dryRun) {
|
|
3968
|
-
|
|
4040
|
+
log$2.info(`[dry-run] Would merge PR #${String(prNumber)} and delete branch ${HEAD_BRANCH}`);
|
|
3969
4041
|
return;
|
|
3970
4042
|
}
|
|
3971
4043
|
await mergePr(executor, conn, prNumber, {
|
|
3972
4044
|
method: "merge",
|
|
3973
4045
|
deleteBranch: true
|
|
3974
4046
|
});
|
|
3975
|
-
|
|
4047
|
+
log$2.info(`Merged PR #${String(prNumber)} and deleted branch ${HEAD_BRANCH}`);
|
|
3976
4048
|
}
|
|
3977
4049
|
function mergeGitHub(dryRun) {
|
|
3978
4050
|
const executor = createRealExecutor();
|
|
3979
4051
|
if (dryRun) {
|
|
3980
4052
|
const prNum = executor.exec(`gh pr view ${HEAD_BRANCH} --json number --jq .number`, { cwd: process.cwd() }).stdout.trim();
|
|
3981
4053
|
if (!prNum) throw new FatalError(`No open PR found for branch ${HEAD_BRANCH}`);
|
|
3982
|
-
|
|
4054
|
+
log$2.info(`[dry-run] Would merge PR #${prNum} and delete branch ${HEAD_BRANCH}`);
|
|
3983
4055
|
return;
|
|
3984
4056
|
}
|
|
3985
4057
|
const result = executor.exec(`gh pr merge ${HEAD_BRANCH} --merge --delete-branch`, { cwd: process.cwd() });
|
|
3986
4058
|
if (result.exitCode !== 0) throw new FatalError(`Failed to merge PR: ${result.stderr || result.stdout || "unknown error"}`);
|
|
3987
|
-
|
|
4059
|
+
log$2.info(`Merged changesets PR and deleted branch ${HEAD_BRANCH}`);
|
|
3988
4060
|
}
|
|
3989
4061
|
//#endregion
|
|
3990
4062
|
//#region src/release/simple.ts
|
|
@@ -4017,7 +4089,7 @@ function readVersion(executor, cwd) {
|
|
|
4017
4089
|
/** Run the full commit-and-tag-version release flow. */
|
|
4018
4090
|
async function runSimpleRelease(executor, config) {
|
|
4019
4091
|
const command = buildCommand(config);
|
|
4020
|
-
|
|
4092
|
+
log$2.info(`Running: ${command}`);
|
|
4021
4093
|
const versionResult = executor.exec(command, { cwd: config.cwd });
|
|
4022
4094
|
debugExec(config, "commit-and-tag-version", versionResult);
|
|
4023
4095
|
if (versionResult.exitCode !== 0) throw new FatalError(`commit-and-tag-version failed (exit code ${String(versionResult.exitCode)}):\n${versionResult.stderr || versionResult.stdout}`);
|
|
@@ -4027,12 +4099,12 @@ async function runSimpleRelease(executor, config) {
|
|
|
4027
4099
|
debugExec(config, "git describe", tagResult);
|
|
4028
4100
|
const tag = tagResult.stdout.trim();
|
|
4029
4101
|
if (!tag) throw new FatalError("Could not determine the new tag from git describe");
|
|
4030
|
-
|
|
4102
|
+
log$2.info(`Version ${version} tagged as ${tag}`);
|
|
4031
4103
|
if (config.dryRun) {
|
|
4032
4104
|
const slidingTags = config.noSlidingTags ? [] : computeSlidingTags(version);
|
|
4033
|
-
|
|
4034
|
-
if (slidingTags.length > 0)
|
|
4035
|
-
if (!config.noRelease && config.platform)
|
|
4105
|
+
log$2.info(`[dry-run] Would push to origin with --follow-tags`);
|
|
4106
|
+
if (slidingTags.length > 0) log$2.info(`[dry-run] Would create sliding tags: ${slidingTags.join(", ")}`);
|
|
4107
|
+
if (!config.noRelease && config.platform) log$2.info(`[dry-run] Would create ${config.platform.type} release for ${tag}`);
|
|
4036
4108
|
return {
|
|
4037
4109
|
version,
|
|
4038
4110
|
tag,
|
|
@@ -4049,7 +4121,7 @@ async function runSimpleRelease(executor, config) {
|
|
|
4049
4121
|
debugExec(config, "git push", pushResult);
|
|
4050
4122
|
if (pushResult.exitCode !== 0) throw new FatalError(`git push failed (exit code ${String(pushResult.exitCode)}):\n${pushResult.stderr || pushResult.stdout}`);
|
|
4051
4123
|
pushed = true;
|
|
4052
|
-
|
|
4124
|
+
log$2.info("Pushed to origin");
|
|
4053
4125
|
}
|
|
4054
4126
|
let slidingTags = [];
|
|
4055
4127
|
if (!config.noSlidingTags && pushed) {
|
|
@@ -4060,8 +4132,8 @@ async function runSimpleRelease(executor, config) {
|
|
|
4060
4132
|
}
|
|
4061
4133
|
const forcePushResult = executor.exec(`git push origin ${slidingTags.join(" ")} --force`, { cwd: config.cwd });
|
|
4062
4134
|
debugExec(config, "force-push sliding tags", forcePushResult);
|
|
4063
|
-
if (forcePushResult.exitCode !== 0)
|
|
4064
|
-
else
|
|
4135
|
+
if (forcePushResult.exitCode !== 0) log$2.warn(`Warning: Failed to push sliding tags: ${forcePushResult.stderr || forcePushResult.stdout}`);
|
|
4136
|
+
else log$2.info(`Created sliding tags: ${slidingTags.join(", ")}`);
|
|
4065
4137
|
}
|
|
4066
4138
|
let releaseCreated = false;
|
|
4067
4139
|
if (!config.noRelease && config.platform) releaseCreated = await createPlatformRelease(executor, config, tag);
|
|
@@ -4081,16 +4153,16 @@ async function createPlatformRelease(executor, config, tag) {
|
|
|
4081
4153
|
return false;
|
|
4082
4154
|
}
|
|
4083
4155
|
await createRelease(executor, config.platform.conn, tag);
|
|
4084
|
-
|
|
4156
|
+
log$2.info(`Created Forgejo release for ${tag}`);
|
|
4085
4157
|
return true;
|
|
4086
4158
|
}
|
|
4087
4159
|
const ghResult = executor.exec(`gh release create ${tag} --generate-notes`, { cwd: config.cwd });
|
|
4088
4160
|
debugExec(config, "gh release create", ghResult);
|
|
4089
4161
|
if (ghResult.exitCode !== 0) {
|
|
4090
|
-
|
|
4162
|
+
log$2.warn(`Warning: Failed to create GitHub release: ${ghResult.stderr || ghResult.stdout}`);
|
|
4091
4163
|
return false;
|
|
4092
4164
|
}
|
|
4093
|
-
|
|
4165
|
+
log$2.info(`Created GitHub release for ${tag}`);
|
|
4094
4166
|
return true;
|
|
4095
4167
|
}
|
|
4096
4168
|
//#endregion
|
|
@@ -4166,17 +4238,17 @@ const releaseSimpleCommand = defineCommand({
|
|
|
4166
4238
|
//#region src/commands/repo-run-checks.ts
|
|
4167
4239
|
const CHECKS = [
|
|
4168
4240
|
{ name: "build" },
|
|
4169
|
-
{ name: "docker:build" },
|
|
4170
4241
|
{ name: "typecheck" },
|
|
4171
4242
|
{ name: "lint" },
|
|
4172
4243
|
{ name: "test" },
|
|
4244
|
+
{ name: "docker:build" },
|
|
4245
|
+
{ name: "tooling:check" },
|
|
4246
|
+
{ name: "docker:check" },
|
|
4173
4247
|
{
|
|
4174
4248
|
name: "format",
|
|
4175
4249
|
args: "--check"
|
|
4176
4250
|
},
|
|
4177
|
-
{ name: "knip" }
|
|
4178
|
-
{ name: "tooling:check" },
|
|
4179
|
-
{ name: "docker:check" }
|
|
4251
|
+
{ name: "knip" }
|
|
4180
4252
|
];
|
|
4181
4253
|
/** Check if a name matches any skip pattern. Supports glob syntax via picomatch. */
|
|
4182
4254
|
function shouldSkip(name, patterns) {
|
|
@@ -4203,7 +4275,30 @@ function defaultExecCommand(cmd, cwd) {
|
|
|
4203
4275
|
return 1;
|
|
4204
4276
|
}
|
|
4205
4277
|
}
|
|
4206
|
-
const
|
|
4278
|
+
const rawLog = (msg) => console.log(msg);
|
|
4279
|
+
const ciReporter = {
|
|
4280
|
+
groupStart: (name) => rawLog(`::group::${name}`),
|
|
4281
|
+
groupEnd: () => rawLog("::endgroup::"),
|
|
4282
|
+
passed: (name, elapsedS) => rawLog(`✓ ${name} (${elapsedS}s)`),
|
|
4283
|
+
failed: (name, elapsedS) => {
|
|
4284
|
+
rawLog(`✗ ${name} (${elapsedS}s)`);
|
|
4285
|
+
rawLog(`::error::${name} failed`);
|
|
4286
|
+
},
|
|
4287
|
+
undefinedCheck: (name) => rawLog(`::error::${name} not defined in package.json`),
|
|
4288
|
+
skippedNotDefined: (names) => rawLog(`Skipped (not defined): ${names.join(", ")}`),
|
|
4289
|
+
allPassed: () => rawLog("✓ All checks passed"),
|
|
4290
|
+
anyFailed: (names) => rawLog(`::error::Failed checks: ${names.join(", ")}`)
|
|
4291
|
+
};
|
|
4292
|
+
const localReporter = {
|
|
4293
|
+
groupStart: (_name) => {},
|
|
4294
|
+
groupEnd: () => {},
|
|
4295
|
+
passed: (name) => log$2.success(name),
|
|
4296
|
+
failed: (name) => log$2.error(`${name} failed`),
|
|
4297
|
+
undefinedCheck: (name) => log$2.error(`${name} not defined in package.json`),
|
|
4298
|
+
skippedNotDefined: (names) => log$2.info(`Skipped (not defined): ${names.join(", ")}`),
|
|
4299
|
+
allPassed: () => log$2.success("All checks passed"),
|
|
4300
|
+
anyFailed: (names) => log$2.error(`Failed checks: ${names.join(", ")}`)
|
|
4301
|
+
};
|
|
4207
4302
|
function runRunChecks(targetDir, options = {}) {
|
|
4208
4303
|
const exec = options.execCommand ?? defaultExecCommand;
|
|
4209
4304
|
const getScripts = options.getScripts ?? defaultGetScripts;
|
|
@@ -4211,6 +4306,7 @@ function runRunChecks(targetDir, options = {}) {
|
|
|
4211
4306
|
const add = options.add ?? [];
|
|
4212
4307
|
const isCI = Boolean(process.env["CI"]);
|
|
4213
4308
|
const failFast = options.failFast ?? !isCI;
|
|
4309
|
+
const reporter = isCI ? ciReporter : localReporter;
|
|
4214
4310
|
const definedScripts = getScripts(targetDir);
|
|
4215
4311
|
const addedNames = new Set(add);
|
|
4216
4312
|
const allChecks = [...CHECKS, ...add.map((name) => ({ name }))];
|
|
@@ -4220,29 +4316,30 @@ function runRunChecks(targetDir, options = {}) {
|
|
|
4220
4316
|
if (shouldSkip(check.name, skip)) continue;
|
|
4221
4317
|
if (!definedScripts.has(check.name)) {
|
|
4222
4318
|
if (addedNames.has(check.name)) {
|
|
4223
|
-
|
|
4319
|
+
reporter.undefinedCheck(check.name);
|
|
4224
4320
|
failures.push(check.name);
|
|
4225
4321
|
} else notDefined.push(check.name);
|
|
4226
4322
|
continue;
|
|
4227
4323
|
}
|
|
4228
4324
|
const cmd = check.args ? `pnpm run ${check.name} ${check.args}` : `pnpm run ${check.name}`;
|
|
4229
|
-
|
|
4325
|
+
reporter.groupStart(check.name);
|
|
4326
|
+
const start = Date.now();
|
|
4230
4327
|
const exitCode = exec(cmd, targetDir);
|
|
4231
|
-
|
|
4232
|
-
|
|
4328
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
4329
|
+
reporter.groupEnd();
|
|
4330
|
+
if (exitCode === 0) reporter.passed(check.name, elapsed);
|
|
4233
4331
|
else {
|
|
4234
|
-
|
|
4235
|
-
p.log.error(`${check.name} failed`);
|
|
4332
|
+
reporter.failed(check.name, elapsed);
|
|
4236
4333
|
failures.push(check.name);
|
|
4237
4334
|
if (failFast) return 1;
|
|
4238
4335
|
}
|
|
4239
4336
|
}
|
|
4240
|
-
if (notDefined.length > 0)
|
|
4337
|
+
if (notDefined.length > 0) reporter.skippedNotDefined(notDefined);
|
|
4241
4338
|
if (failures.length > 0) {
|
|
4242
|
-
|
|
4339
|
+
reporter.anyFailed(failures);
|
|
4243
4340
|
return 1;
|
|
4244
4341
|
}
|
|
4245
|
-
|
|
4342
|
+
reporter.allPassed();
|
|
4246
4343
|
return 0;
|
|
4247
4344
|
}
|
|
4248
4345
|
const runChecksCommand = defineCommand({
|
|
@@ -4276,7 +4373,7 @@ const runChecksCommand = defineCommand({
|
|
|
4276
4373
|
const exitCode = runRunChecks(path.resolve(args.dir ?? "."), {
|
|
4277
4374
|
skip: args.skip ? new Set(args.skip.split(",").map((s) => s.trim())) : void 0,
|
|
4278
4375
|
add: args.add ? args.add.split(",").map((s) => s.trim()) : void 0,
|
|
4279
|
-
failFast: args["fail-fast"]
|
|
4376
|
+
failFast: args["fail-fast"] ? true : void 0
|
|
4280
4377
|
});
|
|
4281
4378
|
process.exitCode = exitCode;
|
|
4282
4379
|
}
|
|
@@ -4656,8 +4753,8 @@ const dockerCheckCommand = defineCommand({
|
|
|
4656
4753
|
//#region src/bin.ts
|
|
4657
4754
|
const main = defineCommand({
|
|
4658
4755
|
meta: {
|
|
4659
|
-
name: "
|
|
4660
|
-
version: "0.
|
|
4756
|
+
name: "bst",
|
|
4757
|
+
version: "0.27.0",
|
|
4661
4758
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
4662
4759
|
},
|
|
4663
4760
|
subCommands: {
|
|
@@ -4673,7 +4770,7 @@ const main = defineCommand({
|
|
|
4673
4770
|
"docker:check": dockerCheckCommand
|
|
4674
4771
|
}
|
|
4675
4772
|
});
|
|
4676
|
-
console.log(`@bensandee/tooling v0.
|
|
4773
|
+
console.log(`@bensandee/tooling v0.27.0`);
|
|
4677
4774
|
async function run() {
|
|
4678
4775
|
await runMain(main);
|
|
4679
4776
|
process.exit(process.exitCode ?? 0);
|
package/dist/index.d.mts
CHANGED
|
@@ -44,6 +44,10 @@ interface ProjectConfig {
|
|
|
44
44
|
setupRenovate: boolean;
|
|
45
45
|
/** Release management strategy */
|
|
46
46
|
releaseStrategy: ReleaseStrategy;
|
|
47
|
+
/** Opt-in: publish packages to npm registry */
|
|
48
|
+
publishNpm: boolean;
|
|
49
|
+
/** Opt-in: publish Docker images to a registry */
|
|
50
|
+
publishDocker: boolean;
|
|
47
51
|
/** Project type determines tsconfig base configuration */
|
|
48
52
|
projectType: "default" | "node" | "react" | "library";
|
|
49
53
|
/** Auto-detect and configure tsconfig bases for monorepo packages */
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bensandee/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
|
|
5
5
|
"bin": {
|
|
6
|
-
"
|
|
6
|
+
"bst": "./dist/bin.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
9
|
"dist",
|
|
@@ -41,20 +41,21 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "24.12.0",
|
|
44
|
-
"@types/picomatch": "
|
|
44
|
+
"@types/picomatch": "4.0.2",
|
|
45
45
|
"tsdown": "0.21.2",
|
|
46
46
|
"typescript": "5.9.3",
|
|
47
|
-
"vitest": "4.0
|
|
48
|
-
"@bensandee/config": "0.9.
|
|
47
|
+
"vitest": "4.1.0",
|
|
48
|
+
"@bensandee/config": "0.9.1"
|
|
49
49
|
},
|
|
50
50
|
"optionalDependencies": {
|
|
51
|
-
"@changesets/cli": "^2.
|
|
52
|
-
"commit-and-tag-version": "^12.
|
|
51
|
+
"@changesets/cli": "^2.30.0",
|
|
52
|
+
"commit-and-tag-version": "^12.6.1"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsdown",
|
|
56
56
|
"dev": "tsdown --watch",
|
|
57
57
|
"typecheck": "tsc --noEmit",
|
|
58
|
-
"test": "vitest run"
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"generate:schema": "node scripts/generate-schema.ts"
|
|
59
60
|
}
|
|
60
61
|
}
|
package/tooling.schema.json
CHANGED
|
@@ -3,107 +3,120 @@
|
|
|
3
3
|
"title": "@bensandee/tooling configuration",
|
|
4
4
|
"description": "Override convention-detected defaults for repo:sync. Only fields that differ from detected defaults need to be specified.",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"additionalProperties": false,
|
|
7
6
|
"properties": {
|
|
8
7
|
"$schema": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
8
|
+
"description": "JSON Schema reference (ignored by tooling)",
|
|
9
|
+
"type": "string"
|
|
11
10
|
},
|
|
12
11
|
"structure": {
|
|
12
|
+
"description": "Project structure",
|
|
13
13
|
"type": "string",
|
|
14
|
-
"enum": ["single", "monorepo"]
|
|
15
|
-
"description": "Project structure"
|
|
14
|
+
"enum": ["single", "monorepo"]
|
|
16
15
|
},
|
|
17
16
|
"useEslintPlugin": {
|
|
18
|
-
"
|
|
19
|
-
"
|
|
17
|
+
"description": "Include @bensandee/eslint-plugin oxlint plugin",
|
|
18
|
+
"type": "boolean"
|
|
20
19
|
},
|
|
21
20
|
"formatter": {
|
|
21
|
+
"description": "Formatter choice",
|
|
22
22
|
"type": "string",
|
|
23
|
-
"enum": ["oxfmt", "prettier"]
|
|
24
|
-
"description": "Formatter choice"
|
|
23
|
+
"enum": ["oxfmt", "prettier"]
|
|
25
24
|
},
|
|
26
25
|
"setupVitest": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
26
|
+
"description": "Generate vitest config and example test",
|
|
27
|
+
"type": "boolean"
|
|
29
28
|
},
|
|
30
29
|
"ci": {
|
|
30
|
+
"description": "CI platform",
|
|
31
31
|
"type": "string",
|
|
32
|
-
"enum": ["github", "forgejo", "none"]
|
|
33
|
-
"description": "CI platform"
|
|
32
|
+
"enum": ["github", "forgejo", "none"]
|
|
34
33
|
},
|
|
35
34
|
"setupRenovate": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
35
|
+
"description": "Generate Renovate config",
|
|
36
|
+
"type": "boolean"
|
|
38
37
|
},
|
|
39
38
|
"releaseStrategy": {
|
|
39
|
+
"description": "Release management strategy",
|
|
40
40
|
"type": "string",
|
|
41
|
-
"enum": ["release-it", "simple", "changesets", "none"]
|
|
42
|
-
|
|
41
|
+
"enum": ["release-it", "simple", "changesets", "none"]
|
|
42
|
+
},
|
|
43
|
+
"publishNpm": {
|
|
44
|
+
"description": "Publish packages to npm (opt-in)",
|
|
45
|
+
"type": "boolean"
|
|
46
|
+
},
|
|
47
|
+
"publishDocker": {
|
|
48
|
+
"description": "Publish Docker images to a registry (opt-in)",
|
|
49
|
+
"type": "boolean"
|
|
43
50
|
},
|
|
44
51
|
"projectType": {
|
|
52
|
+
"description": "Project type (determines tsconfig base)",
|
|
45
53
|
"type": "string",
|
|
46
|
-
"enum": ["default", "node", "react", "library"]
|
|
47
|
-
"description": "Project type (determines tsconfig base)"
|
|
54
|
+
"enum": ["default", "node", "react", "library"]
|
|
48
55
|
},
|
|
49
56
|
"detectPackageTypes": {
|
|
50
|
-
"
|
|
51
|
-
"
|
|
57
|
+
"description": "Auto-detect project types for monorepo packages",
|
|
58
|
+
"type": "boolean"
|
|
52
59
|
},
|
|
53
60
|
"setupDocker": {
|
|
54
|
-
"
|
|
55
|
-
"
|
|
61
|
+
"description": "Generate Docker build/check scripts",
|
|
62
|
+
"type": "boolean"
|
|
56
63
|
},
|
|
57
64
|
"docker": {
|
|
58
|
-
"type": "object",
|
|
59
65
|
"description": "Docker package overrides (package name → config)",
|
|
66
|
+
"type": "object",
|
|
67
|
+
"propertyNames": {
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
60
70
|
"additionalProperties": {
|
|
61
71
|
"type": "object",
|
|
62
|
-
"required": ["dockerfile"],
|
|
63
72
|
"properties": {
|
|
64
73
|
"dockerfile": {
|
|
65
74
|
"type": "string",
|
|
66
75
|
"description": "Path to Dockerfile relative to package"
|
|
67
76
|
},
|
|
68
77
|
"context": {
|
|
69
|
-
"type": "string",
|
|
70
78
|
"default": ".",
|
|
71
|
-
"description": "Docker build context relative to package"
|
|
79
|
+
"description": "Docker build context relative to package",
|
|
80
|
+
"type": "string"
|
|
72
81
|
}
|
|
73
82
|
},
|
|
83
|
+
"required": ["dockerfile", "context"],
|
|
74
84
|
"additionalProperties": false
|
|
75
85
|
}
|
|
76
86
|
},
|
|
77
87
|
"dockerCheck": {
|
|
78
|
-
"
|
|
88
|
+
"description": "Docker health check configuration or false to disable",
|
|
89
|
+
"anyOf": [
|
|
79
90
|
{
|
|
80
|
-
"
|
|
81
|
-
"
|
|
91
|
+
"type": "boolean",
|
|
92
|
+
"const": false
|
|
82
93
|
},
|
|
83
94
|
{
|
|
84
95
|
"type": "object",
|
|
85
|
-
"additionalProperties": false,
|
|
86
96
|
"properties": {
|
|
87
97
|
"composeFiles": {
|
|
98
|
+
"description": "Compose files to use",
|
|
88
99
|
"type": "array",
|
|
89
|
-
"items": {
|
|
90
|
-
|
|
100
|
+
"items": {
|
|
101
|
+
"type": "string"
|
|
102
|
+
}
|
|
91
103
|
},
|
|
92
104
|
"envFile": {
|
|
93
|
-
"
|
|
94
|
-
"
|
|
105
|
+
"description": "Environment file for compose",
|
|
106
|
+
"type": "string"
|
|
95
107
|
},
|
|
96
108
|
"services": {
|
|
109
|
+
"description": "Services to check (default: all)",
|
|
97
110
|
"type": "array",
|
|
98
|
-
"items": {
|
|
99
|
-
|
|
111
|
+
"items": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
}
|
|
100
114
|
},
|
|
101
115
|
"healthChecks": {
|
|
116
|
+
"description": "Health check definitions",
|
|
102
117
|
"type": "array",
|
|
103
118
|
"items": {
|
|
104
119
|
"type": "object",
|
|
105
|
-
"required": ["name", "url"],
|
|
106
|
-
"additionalProperties": false,
|
|
107
120
|
"properties": {
|
|
108
121
|
"name": {
|
|
109
122
|
"type": "string",
|
|
@@ -114,35 +127,41 @@
|
|
|
114
127
|
"description": "Health check URL"
|
|
115
128
|
},
|
|
116
129
|
"status": {
|
|
130
|
+
"description": "Expected HTTP status code",
|
|
117
131
|
"type": "integer",
|
|
118
|
-
"
|
|
132
|
+
"minimum": -9007199254740991,
|
|
133
|
+
"maximum": 9007199254740991
|
|
119
134
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
},
|
|
136
|
+
"required": ["name", "url"],
|
|
137
|
+
"additionalProperties": false
|
|
138
|
+
}
|
|
123
139
|
},
|
|
124
140
|
"buildCommand": {
|
|
125
|
-
"
|
|
126
|
-
"
|
|
141
|
+
"description": "Command to build images before checking",
|
|
142
|
+
"type": "string"
|
|
127
143
|
},
|
|
128
144
|
"buildCwd": {
|
|
129
|
-
"
|
|
130
|
-
"
|
|
145
|
+
"description": "Working directory for build command",
|
|
146
|
+
"type": "string"
|
|
131
147
|
},
|
|
132
148
|
"timeoutMs": {
|
|
149
|
+
"description": "Overall timeout in milliseconds",
|
|
133
150
|
"type": "integer",
|
|
134
|
-
"
|
|
135
|
-
"
|
|
151
|
+
"exclusiveMinimum": 0,
|
|
152
|
+
"maximum": 9007199254740991
|
|
136
153
|
},
|
|
137
154
|
"pollIntervalMs": {
|
|
155
|
+
"description": "Poll interval in milliseconds",
|
|
138
156
|
"type": "integer",
|
|
139
|
-
"
|
|
140
|
-
"
|
|
157
|
+
"exclusiveMinimum": 0,
|
|
158
|
+
"maximum": 9007199254740991
|
|
141
159
|
}
|
|
142
|
-
}
|
|
160
|
+
},
|
|
161
|
+
"additionalProperties": false
|
|
143
162
|
}
|
|
144
|
-
]
|
|
145
|
-
"description": "Docker health check configuration or false to disable"
|
|
163
|
+
]
|
|
146
164
|
}
|
|
147
|
-
}
|
|
165
|
+
},
|
|
166
|
+
"additionalProperties": false
|
|
148
167
|
}
|