@bensandee/tooling 0.52.2 → 0.53.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 +20 -0
- package/dist/bin.mjs +55 -12
- package/package.json +1 -1
- package/tooling.schema.json +10 -0
package/README.md
CHANGED
|
@@ -153,6 +153,26 @@ To override defaults, add a `docker` entry to `.tooling.json`:
|
|
|
153
153
|
|
|
154
154
|
The `context` field defaults to `"."` (project root) when omitted. Versions for tagging are read from each package's own `package.json`.
|
|
155
155
|
|
|
156
|
+
#### Per-package build args
|
|
157
|
+
|
|
158
|
+
Each `docker` entry may declare `buildArgs` — a map of `ARG_NAME` to value that is passed to that package's `docker build` as `--build-arg ARG_NAME=<value>`. Args flow to the configured package only, so they don't trigger "unused build arg" warnings on sibling images. They apply to both `docker:build` and `docker:publish`, so CI-published images match local builds.
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"docker": {
|
|
163
|
+
"frontend": {
|
|
164
|
+
"dockerfile": "packages/frontend/docker/Dockerfile",
|
|
165
|
+
"context": ".",
|
|
166
|
+
"buildArgs": {
|
|
167
|
+
"VITE_BASE_PATH": "${BASE_PATH:-/}"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Values support `${VAR}` and `${VAR:-default}` expansion against `process.env`. Empty strings are treated as unset (POSIX `:-` semantics). Unset variables with no default expand to `""` and log a warning. When the CLI also receives `--build-arg` via `--` pass-through, the CLI value comes after the configured value and wins on duplicate `ARG_NAME`.
|
|
175
|
+
|
|
156
176
|
#### `docker:build`
|
|
157
177
|
|
|
158
178
|
Builds all discovered packages, or a single package with `--package`:
|
package/dist/bin.mjs
CHANGED
|
@@ -624,7 +624,8 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
624
624
|
detectPackageTypes: z.boolean().optional().meta({ description: "Auto-detect project types for monorepo packages" }),
|
|
625
625
|
docker: z.record(z.string(), z.object({
|
|
626
626
|
dockerfile: z.string().meta({ description: "Path to Dockerfile relative to package" }),
|
|
627
|
-
context: z.string().default(".").meta({ description: "Docker build context relative to package" })
|
|
627
|
+
context: z.string().default(".").meta({ description: "Docker build context relative to package" }),
|
|
628
|
+
buildArgs: z.record(z.string(), z.string()).optional().meta({ description: "Build args passed to `docker build` for this package. Values support ${VAR} and ${VAR:-default} expansion against process.env." })
|
|
628
629
|
})).optional().meta({ description: "Docker package overrides (package name → config)" }),
|
|
629
630
|
dockerCheck: z.union([z.literal(false), DockerCheckConfigSchema]).optional().meta({ description: "Docker health check configuration or false to disable" }),
|
|
630
631
|
registryHost: z.string().optional().meta({ description: "Docker registry hostname (e.g. ghcr.io, code.example.com)" }),
|
|
@@ -715,7 +716,8 @@ function mergeWithSavedConfig(detected, saved) {
|
|
|
715
716
|
//#region src/release/docker.ts
|
|
716
717
|
const ToolingDockerMapSchema = z.record(z.string(), z.object({
|
|
717
718
|
dockerfile: z.string(),
|
|
718
|
-
context: z.string().default(".")
|
|
719
|
+
context: z.string().default("."),
|
|
720
|
+
buildArgs: z.record(z.string(), z.string()).optional()
|
|
719
721
|
}));
|
|
720
722
|
const ToolingConfigDockerSchema = z.object({ docker: ToolingDockerMapSchema.optional() });
|
|
721
723
|
const PackageInfoSchema = z.object({
|
|
@@ -877,16 +879,53 @@ function readRepoName(executor, cwd) {
|
|
|
877
879
|
if (!repoName) throw new FatalError("Root package.json must have a name field");
|
|
878
880
|
return repoName;
|
|
879
881
|
}
|
|
882
|
+
const INTERPOLATE_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
|
|
883
|
+
/**
|
|
884
|
+
* Expand `${VAR}` and `${VAR:-default}` in `value` against `env`. Unset (or empty)
|
|
885
|
+
* variables with no default produce `""` and invoke `onMissing(name)` once per occurrence.
|
|
886
|
+
* Literal `$` not followed by `{…}` passes through unchanged. POSIX `:-` semantics:
|
|
887
|
+
* empty string is treated the same as unset.
|
|
888
|
+
*/
|
|
889
|
+
function interpolate(value, env, onMissing) {
|
|
890
|
+
return value.replace(INTERPOLATE_RE, (_, name, defaultValue) => {
|
|
891
|
+
const v = env[name];
|
|
892
|
+
if (v !== void 0 && v !== "") return v;
|
|
893
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
894
|
+
onMissing?.(name);
|
|
895
|
+
return "";
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Translate a package's configured buildArgs map into `--build-arg K=V` flags,
|
|
900
|
+
* expanding env-var references in values. Warns once per unset variable per build.
|
|
901
|
+
*/
|
|
902
|
+
function resolveBuildArgs(pkg, env) {
|
|
903
|
+
const entries = Object.entries(pkg.docker.buildArgs ?? {});
|
|
904
|
+
if (entries.length === 0) return [];
|
|
905
|
+
const warned = /* @__PURE__ */ new Set();
|
|
906
|
+
const flags = [];
|
|
907
|
+
for (const [k, rawValue] of entries) {
|
|
908
|
+
const resolved = interpolate(rawValue, env, (missing) => {
|
|
909
|
+
if (warned.has(missing)) return;
|
|
910
|
+
warned.add(missing);
|
|
911
|
+
log.warn(`docker build arg "${k}" for ${pkg.dir}: env var ${missing} is not set; using empty string`);
|
|
912
|
+
});
|
|
913
|
+
flags.push("--build-arg", `${k}=${resolved}`);
|
|
914
|
+
}
|
|
915
|
+
return flags;
|
|
916
|
+
}
|
|
880
917
|
/** Build a single docker image from its config. Paths are resolved relative to cwd. */
|
|
881
918
|
function buildImage(executor, pkg, cwd, extraArgs) {
|
|
882
919
|
const dockerfilePath = path.resolve(cwd, pkg.docker.dockerfile);
|
|
883
920
|
const contextPath = path.resolve(cwd, pkg.docker.context);
|
|
921
|
+
const configuredArgs = resolveBuildArgs(pkg, process.env);
|
|
884
922
|
executor.execInherit("docker", [
|
|
885
923
|
"build",
|
|
886
924
|
"-f",
|
|
887
925
|
dockerfilePath,
|
|
888
926
|
"-t",
|
|
889
927
|
`${pkg.imageName}:latest`,
|
|
928
|
+
...configuredArgs,
|
|
890
929
|
...extraArgs,
|
|
891
930
|
contextPath
|
|
892
931
|
]);
|
|
@@ -1596,7 +1635,7 @@ async function generatePackageJson(ctx) {
|
|
|
1596
1635
|
const devDeps = { ...ROOT_DEV_DEPS };
|
|
1597
1636
|
if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
|
|
1598
1637
|
devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.10.2";
|
|
1599
|
-
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.
|
|
1638
|
+
devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.53.0";
|
|
1600
1639
|
if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.12.0";
|
|
1601
1640
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
1602
1641
|
"@changesets/cli": "2.30.0",
|
|
@@ -2218,13 +2257,13 @@ const DEFAULT_POST_RELEASE_COMMAND = ["pnpm exec bst webhook:send", " \"$POST_RE
|
|
|
2218
2257
|
/** Build env vars for hook secrets to inject into a workflow step. */
|
|
2219
2258
|
function hookSecretEnv(secrets) {
|
|
2220
2259
|
const env = {
|
|
2221
|
-
POST_RELEASE_HOOK_URL: actionsExpr("secrets.POST_RELEASE_HOOK_URL"),
|
|
2260
|
+
POST_RELEASE_HOOK_URL: actionsExpr("vars.POST_RELEASE_HOOK_URL || secrets.POST_RELEASE_HOOK_URL"),
|
|
2222
2261
|
WEBHOOK_SECRET: actionsExpr("secrets.POST_RELEASE_HOOK_SECRET")
|
|
2223
2262
|
};
|
|
2224
2263
|
for (const name of secrets ?? []) env[name] = actionsExpr(`secrets.${name}`);
|
|
2225
2264
|
return env;
|
|
2226
2265
|
}
|
|
2227
|
-
/** Step that runs after release
|
|
2266
|
+
/** Step that runs after release with post-release webhook env wired up. */
|
|
2228
2267
|
function postReleaseHookStep(opts) {
|
|
2229
2268
|
const ifCondition = opts?.condition ?? (opts?.guardMain ? "github.ref == 'refs/heads/main'" : void 0);
|
|
2230
2269
|
return { step: {
|
|
@@ -3012,11 +3051,15 @@ async function generateChangesets(ctx) {
|
|
|
3012
3051
|
}
|
|
3013
3052
|
//#endregion
|
|
3014
3053
|
//#region src/generators/lefthook.ts
|
|
3054
|
+
const LINT_COMMAND = "pnpm exec oxlint --no-error-on-unmatched-pattern {staged_files}";
|
|
3055
|
+
function formatCommandFor(formatter) {
|
|
3056
|
+
return formatter === "prettier" ? "pnpm exec prettier --ignore-unknown --no-error-on-unmatched-pattern --write {staged_files}" : "pnpm exec oxfmt --no-error-on-unmatched-pattern {staged_files}";
|
|
3057
|
+
}
|
|
3015
3058
|
function requiredCommands(formatter) {
|
|
3016
3059
|
return {
|
|
3017
|
-
lint: { run:
|
|
3060
|
+
lint: { run: LINT_COMMAND },
|
|
3018
3061
|
format: {
|
|
3019
|
-
run: formatter
|
|
3062
|
+
run: formatCommandFor(formatter),
|
|
3020
3063
|
stage_fixed: true
|
|
3021
3064
|
}
|
|
3022
3065
|
};
|
|
@@ -3026,9 +3069,9 @@ function buildConfig$1(formatter) {
|
|
|
3026
3069
|
"pre-commit:",
|
|
3027
3070
|
" commands:",
|
|
3028
3071
|
" lint:",
|
|
3029
|
-
|
|
3072
|
+
` run: ${LINT_COMMAND}`,
|
|
3030
3073
|
" format:",
|
|
3031
|
-
` run: ${formatter
|
|
3074
|
+
` run: ${formatCommandFor(formatter)}`,
|
|
3032
3075
|
" stage_fixed: true",
|
|
3033
3076
|
""
|
|
3034
3077
|
].join("\n");
|
|
@@ -3368,7 +3411,7 @@ async function runGenerators(ctx) {
|
|
|
3368
3411
|
function generateMigratePrompt(results, config, detected) {
|
|
3369
3412
|
const sections = [];
|
|
3370
3413
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3371
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
3414
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.53.0 repo:sync\` on ${timestamp}_\n`);
|
|
3372
3415
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
3373
3416
|
if (changed.length > 0) {
|
|
3374
3417
|
const items = changed.map((r) => {
|
|
@@ -6671,7 +6714,7 @@ async function sendWebhookWithLogging(url, secret, payload) {
|
|
|
6671
6714
|
const main = defineCommand({
|
|
6672
6715
|
meta: {
|
|
6673
6716
|
name: "bst",
|
|
6674
|
-
version: "0.
|
|
6717
|
+
version: "0.53.0",
|
|
6675
6718
|
description: "Bootstrap and maintain standardized TypeScript project tooling"
|
|
6676
6719
|
},
|
|
6677
6720
|
subCommands: {
|
|
@@ -6739,7 +6782,7 @@ const main = defineCommand({
|
|
|
6739
6782
|
})
|
|
6740
6783
|
}
|
|
6741
6784
|
});
|
|
6742
|
-
console.log(`@bensandee/tooling v0.
|
|
6785
|
+
console.log(`@bensandee/tooling v0.53.0`);
|
|
6743
6786
|
async function run() {
|
|
6744
6787
|
await runMain(main);
|
|
6745
6788
|
process.exit(process.exitCode ?? 0);
|
package/package.json
CHANGED
package/tooling.schema.json
CHANGED
|
@@ -74,6 +74,16 @@
|
|
|
74
74
|
"default": ".",
|
|
75
75
|
"description": "Docker build context relative to package",
|
|
76
76
|
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
"buildArgs": {
|
|
79
|
+
"description": "Build args passed to `docker build` for this package. Values support ${VAR} and ${VAR:-default} expansion against process.env.",
|
|
80
|
+
"type": "object",
|
|
81
|
+
"propertyNames": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": {
|
|
85
|
+
"type": "string"
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
},
|
|
79
89
|
"required": ["dockerfile", "context"],
|