@bensandee/tooling 0.53.0 → 0.54.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 +88 -23
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -715,8 +715,8 @@ function mergeWithSavedConfig(detected, saved) {
|
|
|
715
715
|
//#endregion
|
|
716
716
|
//#region src/release/docker.ts
|
|
717
717
|
const ToolingDockerMapSchema = z.record(z.string(), z.object({
|
|
718
|
-
dockerfile: z.string(),
|
|
719
|
-
context: z.string().
|
|
718
|
+
dockerfile: z.string().optional(),
|
|
719
|
+
context: z.string().optional(),
|
|
720
720
|
buildArgs: z.record(z.string(), z.string()).optional()
|
|
721
721
|
}));
|
|
722
722
|
const ToolingConfigDockerSchema = z.object({ docker: ToolingDockerMapSchema.optional() });
|
|
@@ -737,6 +737,21 @@ function loadDockerMap(executor, cwd) {
|
|
|
737
737
|
return {};
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* Merge a convention-discovered config with a user override. Missing override fields
|
|
742
|
+
* fall back to convention. Returns undefined if no dockerfile can be resolved from
|
|
743
|
+
* either source.
|
|
744
|
+
*/
|
|
745
|
+
function mergeDockerConfig(convention, override) {
|
|
746
|
+
if (!override) return convention;
|
|
747
|
+
const dockerfile = override.dockerfile ?? convention?.dockerfile;
|
|
748
|
+
if (!dockerfile) return void 0;
|
|
749
|
+
return {
|
|
750
|
+
dockerfile,
|
|
751
|
+
context: override.context ?? convention?.context ?? ".",
|
|
752
|
+
buildArgs: override.buildArgs ?? convention?.buildArgs
|
|
753
|
+
};
|
|
754
|
+
}
|
|
740
755
|
/** Read name and version from a package's package.json. */
|
|
741
756
|
function readPackageInfo(executor, packageJsonPath) {
|
|
742
757
|
const raw = executor.readFile(packageJsonPath);
|
|
@@ -821,16 +836,23 @@ function detectDockerPackages(executor, cwd, repoName) {
|
|
|
821
836
|
if (packageDirs.length > 0) {
|
|
822
837
|
for (const dir of packageDirs) {
|
|
823
838
|
const convention = findConventionDockerfile(executor, cwd, dir);
|
|
824
|
-
const
|
|
839
|
+
const override = overrides[dir];
|
|
840
|
+
const docker = mergeDockerConfig(convention, override);
|
|
825
841
|
if (docker) {
|
|
826
842
|
packages.push(createDockerPackage(executor, cwd, dir, repoName, docker));
|
|
827
843
|
seen.add(dir);
|
|
828
844
|
}
|
|
829
845
|
}
|
|
830
|
-
for (const [dir,
|
|
846
|
+
for (const [dir, override] of Object.entries(overrides)) {
|
|
847
|
+
if (seen.has(dir)) continue;
|
|
848
|
+
const docker = mergeDockerConfig(void 0, override);
|
|
849
|
+
if (!docker) throw new FatalError(`Docker override for "${dir}" has no dockerfile and no convention Dockerfile was found`);
|
|
850
|
+
packages.push(createDockerPackage(executor, cwd, dir, repoName, docker));
|
|
851
|
+
}
|
|
831
852
|
} else {
|
|
832
853
|
const convention = findRootDockerfile(executor, cwd);
|
|
833
|
-
const
|
|
854
|
+
const override = overrides["."];
|
|
855
|
+
const docker = mergeDockerConfig(convention, override);
|
|
834
856
|
if (docker) packages.push(createDockerPackage(executor, cwd, ".", repoName, docker));
|
|
835
857
|
}
|
|
836
858
|
return packages;
|
|
@@ -842,7 +864,8 @@ function detectDockerPackages(executor, cwd, repoName) {
|
|
|
842
864
|
function readSinglePackageDocker(executor, cwd, packageDir, repoName) {
|
|
843
865
|
const dir = path.basename(path.resolve(cwd, packageDir));
|
|
844
866
|
const convention = findConventionDockerfile(executor, cwd, dir);
|
|
845
|
-
const
|
|
867
|
+
const override = loadDockerMap(executor, cwd)[dir];
|
|
868
|
+
const docker = mergeDockerConfig(convention, override);
|
|
846
869
|
if (!docker) throw new FatalError(`No Dockerfile found for package "${dir}" (checked convention paths and .tooling.json)`);
|
|
847
870
|
return createDockerPackage(executor, cwd, dir, repoName, docker);
|
|
848
871
|
}
|
|
@@ -896,36 +919,39 @@ function interpolate(value, env, onMissing) {
|
|
|
896
919
|
});
|
|
897
920
|
}
|
|
898
921
|
/**
|
|
899
|
-
*
|
|
900
|
-
*
|
|
922
|
+
* Resolve a package's configured buildArgs map into `K=V` pairs, expanding
|
|
923
|
+
* env-var references in values. Warns once per unset variable per build.
|
|
901
924
|
*/
|
|
902
925
|
function resolveBuildArgs(pkg, env) {
|
|
903
926
|
const entries = Object.entries(pkg.docker.buildArgs ?? {});
|
|
904
927
|
if (entries.length === 0) return [];
|
|
905
928
|
const warned = /* @__PURE__ */ new Set();
|
|
906
|
-
const
|
|
929
|
+
const pairs = [];
|
|
907
930
|
for (const [k, rawValue] of entries) {
|
|
908
931
|
const resolved = interpolate(rawValue, env, (missing) => {
|
|
909
932
|
if (warned.has(missing)) return;
|
|
910
933
|
warned.add(missing);
|
|
911
934
|
log.warn(`docker build arg "${k}" for ${pkg.dir}: env var ${missing} is not set; using empty string`);
|
|
912
935
|
});
|
|
913
|
-
|
|
936
|
+
pairs.push(`${k}=${resolved}`);
|
|
914
937
|
}
|
|
915
|
-
return
|
|
938
|
+
return pairs;
|
|
916
939
|
}
|
|
917
940
|
/** Build a single docker image from its config. Paths are resolved relative to cwd. */
|
|
918
941
|
function buildImage(executor, pkg, cwd, extraArgs) {
|
|
919
942
|
const dockerfilePath = path.resolve(cwd, pkg.docker.dockerfile);
|
|
920
943
|
const contextPath = path.resolve(cwd, pkg.docker.context);
|
|
921
|
-
const
|
|
944
|
+
const configuredPairs = resolveBuildArgs(pkg, process.env);
|
|
945
|
+
log.info(` build args: ${configuredPairs.length > 0 ? configuredPairs.join(" ") : "(none)"}`);
|
|
946
|
+
log.info(` extra args: ${extraArgs.length > 0 ? extraArgs.join(" ") : "(none)"}`);
|
|
947
|
+
const buildArgFlags = configuredPairs.flatMap((pair) => ["--build-arg", pair]);
|
|
922
948
|
executor.execInherit("docker", [
|
|
923
949
|
"build",
|
|
924
950
|
"-f",
|
|
925
951
|
dockerfilePath,
|
|
926
952
|
"-t",
|
|
927
953
|
`${pkg.imageName}:latest`,
|
|
928
|
-
...
|
|
954
|
+
...buildArgFlags,
|
|
929
955
|
...extraArgs,
|
|
930
956
|
contextPath
|
|
931
957
|
]);
|
|
@@ -1188,6 +1214,45 @@ function preserveDevBinaryPath(generated, existing) {
|
|
|
1188
1214
|
if (!match) return generated;
|
|
1189
1215
|
return generated.replaceAll("pnpm exec bst", match[0]).replaceAll("pnpm dlx @bensandee/tooling", match[0]);
|
|
1190
1216
|
}
|
|
1217
|
+
function escapeRegex(value) {
|
|
1218
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1219
|
+
}
|
|
1220
|
+
/** Match a `uses: <name>@<ref>` line, capturing indentation, action name, ref, and trailing comment. */
|
|
1221
|
+
const USES_LINE_PATTERN = /^(?<indent>\s*(?:-\s+)?uses:\s*)(?<name>[^\s@#]+)@(?<ref>\S+)(?<comment>[ \t]+#[^\n]*)?$/gm;
|
|
1222
|
+
/**
|
|
1223
|
+
* Extract a map of action-name → full verbatim tail (`<ref>` + optional inline comment)
|
|
1224
|
+
* from an existing workflow's `uses:` lines. Local actions (starting with `./` or
|
|
1225
|
+
* `docker://`) are skipped. First occurrence wins.
|
|
1226
|
+
*/
|
|
1227
|
+
function extractPinnedActions(existing) {
|
|
1228
|
+
const map = /* @__PURE__ */ new Map();
|
|
1229
|
+
for (const match of existing.matchAll(USES_LINE_PATTERN)) {
|
|
1230
|
+
const { name, ref, comment } = match.groups ?? {};
|
|
1231
|
+
if (!name || !ref) continue;
|
|
1232
|
+
if (name.startsWith("./") || name.startsWith("docker://")) continue;
|
|
1233
|
+
if (!map.has(name)) map.set(name, `${ref}${comment ?? ""}`);
|
|
1234
|
+
}
|
|
1235
|
+
return map;
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Preserve existing Renovate-managed action pins when rewriting a workflow.
|
|
1239
|
+
*
|
|
1240
|
+
* For each `uses: <name>@<ref>` in the existing file, if the generated file references the
|
|
1241
|
+
* same action name, substitute the existing ref (including any inline comment like `# v6`)
|
|
1242
|
+
* into the generated output. This keeps SHA pins and their version annotations stable when
|
|
1243
|
+
* unrelated workflow changes force a rewrite.
|
|
1244
|
+
*/
|
|
1245
|
+
function preservePinnedActions(generated, existing) {
|
|
1246
|
+
if (!existing) return generated;
|
|
1247
|
+
const pins = extractPinnedActions(existing);
|
|
1248
|
+
if (pins.size === 0) return generated;
|
|
1249
|
+
let result = generated;
|
|
1250
|
+
for (const [name, tail] of pins) {
|
|
1251
|
+
const pattern = new RegExp(String.raw`^(\s*(?:-\s+)?uses:\s*)${escapeRegex(name)}@\S+(?:[ \t]+#[^\n]*)?$`, "gm");
|
|
1252
|
+
result = result.replace(pattern, `$1${name}@${tail}`);
|
|
1253
|
+
}
|
|
1254
|
+
return result;
|
|
1255
|
+
}
|
|
1191
1256
|
/** Build a complete workflow YAML string from structured options. Single source of truth for workflow files. */
|
|
1192
1257
|
function buildWorkflowYaml(options) {
|
|
1193
1258
|
const doc = { name: options.name };
|
|
@@ -1350,8 +1415,8 @@ async function generateDeployCi(ctx) {
|
|
|
1350
1415
|
description: "Publish workflow already up to date"
|
|
1351
1416
|
};
|
|
1352
1417
|
}
|
|
1353
|
-
const
|
|
1354
|
-
const final = existing ? insertCustomBlocks(
|
|
1418
|
+
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
1419
|
+
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
1355
1420
|
ctx.write(workflowPath, final);
|
|
1356
1421
|
return {
|
|
1357
1422
|
filePath: workflowPath,
|
|
@@ -1635,7 +1700,7 @@ async function generatePackageJson(ctx) {
|
|
|
1635
1700
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1636
1701
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1637
1702
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.2";
|
|
1638
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1703
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.54.0";
|
|
1639
1704
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1640
1705
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1641
1706
|
"@changesets/cli": "2.30.0",
|
|
@@ -2427,8 +2492,8 @@ async function generateReleaseCi(ctx) {
|
|
|
2427
2492
|
description: "Release workflow already up to date"
|
|
2428
2493
|
};
|
|
2429
2494
|
}
|
|
2430
|
-
const
|
|
2431
|
-
const final = existing ? insertCustomBlocks(
|
|
2495
|
+
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
2496
|
+
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
2432
2497
|
ctx.write(workflowPath, final);
|
|
2433
2498
|
return {
|
|
2434
2499
|
filePath: workflowPath,
|
|
@@ -2557,8 +2622,8 @@ async function generateCi(ctx) {
|
|
|
2557
2622
|
description: "CI workflow already up to date"
|
|
2558
2623
|
};
|
|
2559
2624
|
}
|
|
2560
|
-
const
|
|
2561
|
-
const final = existing ? insertCustomBlocks(
|
|
2625
|
+
const withPinnedActions = preservePinnedActions(preserveDevBinaryPath(content, existing), existing);
|
|
2626
|
+
const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
|
|
2562
2627
|
ctx.write(filePath, final);
|
|
2563
2628
|
return {
|
|
2564
2629
|
filePath,
|
|
@@ -3411,7 +3476,7 @@ async function runGenerators(ctx) {
|
|
|
3411
3476
|
function generateMigratePrompt(results, config, detected) {
|
|
3412
3477
|
const sections = [];
|
|
3413
3478
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3414
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3479
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.54.0 repo:sync\` on ${timestamp}_\n`);
|
|
3415
3480
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3416
3481
|
if (changed.length > 0) {
|
|
3417
3482
|
const items = changed.map((r) => {
|
|
@@ -6714,7 +6779,7 @@ async function sendWebhookWithLogging(url, secret, payload) {
|
|
|
6714
6779
|
const main = defineCommand({
|
|
6715
6780
|
meta: {
|
|
6716
6781
|
name: "bst",
|
|
6717
|
-
version: "0.
|
|
6782
|
+
version: "0.54.0",
|
|
6718
6783
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
6719
6784
|
},
|
|
6720
6785
|
subCommands: {
|
|
@@ -6782,7 +6847,7 @@ const main = defineCommand({
|
|
|
6782
6847
|
})
|
|
6783
6848
|
}
|
|
6784
6849
|
});
|
|
6785
|
-
console.log(`@bensandee/tooling v0.
|
|
6850
|
+
console.log(`@bensandee/tooling v0.54.0`);
|
|
6786
6851
|
async function run() {
|
|
6787
6852
|
await runMain(main);
|
|
6788
6853
|
process.exit(process.exitCode ?? 0);
|