@bensandee/tooling 0.52.3 → 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 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.52.3";
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",
@@ -3372,7 +3411,7 @@ async function runGenerators(ctx) {
3372
3411
  function generateMigratePrompt(results, config, detected) {
3373
3412
  const sections = [];
3374
3413
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3375
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.52.3 repo:sync\` on ${timestamp}_\n`);
3414
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.53.0 repo:sync\` on ${timestamp}_\n`);
3376
3415
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
3377
3416
  if (changed.length > 0) {
3378
3417
  const items = changed.map((r) => {
@@ -6675,7 +6714,7 @@ async function sendWebhookWithLogging(url, secret, payload) {
6675
6714
  const main = defineCommand({
6676
6715
  meta: {
6677
6716
  name: "bst",
6678
- version: "0.52.3",
6717
+ version: "0.53.0",
6679
6718
  description: "Bootstrap and maintain standardized TypeScript project tooling"
6680
6719
  },
6681
6720
  subCommands: {
@@ -6743,7 +6782,7 @@ const main = defineCommand({
6743
6782
  })
6744
6783
  }
6745
6784
  });
6746
- console.log(`@bensandee/tooling v0.52.3`);
6785
+ console.log(`@bensandee/tooling v0.53.0`);
6747
6786
  async function run() {
6748
6787
  await runMain(main);
6749
6788
  process.exit(process.exitCode ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.52.3",
3
+ "version": "0.53.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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"],