@bensandee/tooling 0.25.3 → 0.26.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.
Files changed (3) hide show
  1. package/README.md +25 -3
  2. package/dist/bin.mjs +33 -35
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -32,10 +32,32 @@ The tool auto-detects project structure, CI platform, project type, and Docker p
32
32
  | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
33
  | `tooling repo:sync [dir]` | Detect, generate, and sync project tooling (idempotent). First run prompts for release strategy, CI platform (if not detected), and formatter (if Prettier found). Subsequent runs are non-interactive. |
34
34
  | `tooling repo:sync --check [dir]` | Dry-run drift detection. Exits 1 if files would change. CI-friendly. |
35
- | `tooling checks:run` | Run project checks (build, typecheck, lint, knip, test). Flag: `--fail-fast`. |
35
+ | `tooling checks:run` | Run project checks (build, docker:build, typecheck, lint, test, format, knip, tooling:check, docker:check). Flags: `--skip`, `--add`, `--fail-fast`. |
36
36
 
37
37
  **Flags:** `--yes` (accept all defaults), `--no-ci`, `--no-prompt`, `--eslint-plugin`
38
38
 
39
+ #### `checks:run`
40
+
41
+ Runs checks in order: build, docker:build, typecheck, lint, test, format (--check), knip, tooling:check, docker:check. Checks without a matching script in `package.json` are silently skipped.
42
+
43
+ The `--skip` flag supports glob patterns via picomatch:
44
+
45
+ ```bash
46
+ # Skip all docker steps
47
+ tooling checks:run --skip 'docker:*'
48
+
49
+ # Skip specific checks
50
+ tooling checks:run --skip build,knip
51
+ ```
52
+
53
+ The `--add` flag appends extra checks (must be defined in `package.json`):
54
+
55
+ ```bash
56
+ tooling checks:run --add e2e
57
+ ```
58
+
59
+ The generated `ci:check` script defaults to `pnpm check --skip 'docker:*'` since CI environments typically lack Docker support.
60
+
39
61
  ### Release management
40
62
 
41
63
  | Command | Description |
@@ -99,7 +121,7 @@ To give individual packages a standalone `image:build` script for local testing:
99
121
  }
100
122
  ```
101
123
 
102
- **Flags:** `--package <dir>` (build a single package), `--verbose`
124
+ **Flags:** `--package <dir>` (build a single package)
103
125
 
104
126
  #### `docker:publish`
105
127
 
@@ -109,7 +131,7 @@ Tags generated per package: `latest`, `vX.Y.Z`, `vX.Y`, `vX`
109
131
 
110
132
  Each package is tagged independently using its own version, so packages in a monorepo can have different release cadences. Packages without a `version` field are rejected at publish time.
111
133
 
112
- **Flags:** `--dry-run` (build and tag only, skip login/push/logout), `--verbose`
134
+ **Flags:** `--dry-run` (build and tag only, skip login/push/logout)
113
135
 
114
136
  **Required environment variables:**
115
137
 
package/dist/bin.mjs CHANGED
@@ -10,6 +10,7 @@ import { z } from "zod";
10
10
  import { FatalError, TransientError, UnexpectedError } from "@bensandee/common";
11
11
  import { isMap, isScalar, isSeq, parse as parse$1, parseDocument, stringify } from "yaml";
12
12
  import { execSync } from "node:child_process";
13
+ import picomatch from "picomatch";
13
14
  import { tmpdir } from "node:os";
14
15
  //#region src/types.ts
15
16
  const LEGACY_TOOLS = [
@@ -904,7 +905,7 @@ const STANDARD_SCRIPTS_SINGLE = {
904
905
  lint: "oxlint",
905
906
  knip: "knip",
906
907
  check: "pnpm exec tooling checks:run",
907
- "ci:check": "pnpm check",
908
+ "ci:check": "pnpm check --skip 'docker:*'",
908
909
  "tooling:check": "pnpm exec tooling repo:sync --check",
909
910
  "tooling:sync": "pnpm exec tooling repo:sync"
910
911
  };
@@ -915,7 +916,7 @@ const STANDARD_SCRIPTS_MONOREPO = {
915
916
  lint: "oxlint",
916
917
  knip: "knip",
917
918
  check: "pnpm exec tooling checks:run",
918
- "ci:check": "pnpm check",
919
+ "ci:check": "pnpm check --skip 'docker:*'",
919
920
  "tooling:check": "pnpm exec tooling repo:sync --check",
920
921
  "tooling:sync": "pnpm exec tooling repo:sync"
921
922
  };
@@ -983,7 +984,7 @@ function getAddedDevDepNames(config) {
983
984
  const deps = { ...ROOT_DEV_DEPS };
984
985
  if (config.structure !== "monorepo") Object.assign(deps, PER_PACKAGE_DEV_DEPS);
985
986
  deps["@bensandee/config"] = "0.9.0";
986
- deps["@bensandee/tooling"] = "0.25.3";
987
+ deps["@bensandee/tooling"] = "0.26.0";
987
988
  if (config.formatter === "oxfmt") deps["oxfmt"] = "0.35.0";
988
989
  if (config.formatter === "prettier") deps["prettier"] = "3.8.1";
989
990
  addReleaseDeps(deps, config);
@@ -1008,7 +1009,7 @@ async function generatePackageJson(ctx) {
1008
1009
  const devDeps = { ...ROOT_DEV_DEPS };
1009
1010
  if (!isMonorepo) Object.assign(devDeps, PER_PACKAGE_DEV_DEPS);
1010
1011
  devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.9.0";
1011
- devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.25.3";
1012
+ devDeps["@bensandee/tooling"] = isWorkspacePackage(ctx, "@bensandee/tooling") ? "workspace:*" : "0.26.0";
1012
1013
  if (ctx.config.useEslintPlugin) devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.9.2";
1013
1014
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = "0.35.0";
1014
1015
  if (ctx.config.formatter === "prettier") devDeps["prettier"] = "3.8.1";
@@ -2779,9 +2780,6 @@ function imageRef(namespace, imageName, tag) {
2779
2780
  function log$1(message) {
2780
2781
  console.log(message);
2781
2782
  }
2782
- function debug$1(verbose, message) {
2783
- if (verbose) console.log(`[debug] ${message}`);
2784
- }
2785
2783
  /** Read the repo name from root package.json. */
2786
2784
  function readRepoName(executor, cwd) {
2787
2785
  const rootPkgRaw = executor.readFile(path.join(cwd, "package.json"));
@@ -2791,7 +2789,7 @@ function readRepoName(executor, cwd) {
2791
2789
  return repoName;
2792
2790
  }
2793
2791
  /** Build a single docker image from its config. Paths are resolved relative to cwd. */
2794
- function buildImage(executor, pkg, cwd, verbose, extraArgs) {
2792
+ function buildImage(executor, pkg, cwd, extraArgs) {
2795
2793
  const dockerfilePath = path.resolve(cwd, pkg.docker.dockerfile);
2796
2794
  const contextPath = path.resolve(cwd, pkg.docker.context);
2797
2795
  const command = [
@@ -2801,10 +2799,7 @@ function buildImage(executor, pkg, cwd, verbose, extraArgs) {
2801
2799
  ...extraArgs,
2802
2800
  contextPath
2803
2801
  ].join(" ");
2804
- debug$1(verbose, `Running: ${command}`);
2805
- const buildResult = executor.exec(command);
2806
- debug$1(verbose, `Build stdout: ${buildResult.stdout}`);
2807
- if (buildResult.exitCode !== 0) throw new FatalError(`docker build failed for ${pkg.dir} (exit ${buildResult.exitCode}): ${buildResult.stderr}`);
2802
+ executor.execInherit(command);
2808
2803
  }
2809
2804
  /**
2810
2805
  * Detect packages with docker config in .tooling.json and build each one.
@@ -2818,7 +2813,7 @@ function runDockerBuild(executor, config) {
2818
2813
  if (config.packageDir) {
2819
2814
  const pkg = readSinglePackageDocker(executor, config.cwd, config.packageDir, repoName);
2820
2815
  log$1(`Building image for ${pkg.dir} (${pkg.imageName}:latest)...`);
2821
- buildImage(executor, pkg, config.cwd, config.verbose, config.extraArgs);
2816
+ buildImage(executor, pkg, config.cwd, config.extraArgs);
2822
2817
  log$1(`Built ${pkg.imageName}:latest`);
2823
2818
  return { packages: [pkg] };
2824
2819
  }
@@ -2830,7 +2825,7 @@ function runDockerBuild(executor, config) {
2830
2825
  log$1(`Found ${packages.length} Docker package(s): ${packages.map((p) => p.dir).join(", ")}`);
2831
2826
  for (const pkg of packages) {
2832
2827
  log$1(`Building image for ${pkg.dir} (${pkg.imageName}:latest)...`);
2833
- buildImage(executor, pkg, config.cwd, config.verbose, config.extraArgs);
2828
+ buildImage(executor, pkg, config.cwd, config.extraArgs);
2834
2829
  }
2835
2830
  log$1(`Built ${packages.length} image(s)`);
2836
2831
  return { packages };
@@ -2847,7 +2842,6 @@ function runDockerPublish(executor, config) {
2847
2842
  const { packages } = runDockerBuild(executor, {
2848
2843
  cwd: config.cwd,
2849
2844
  packageDir: void 0,
2850
- verbose: config.verbose,
2851
2845
  extraArgs: []
2852
2846
  });
2853
2847
  if (packages.length === 0) return {
@@ -3311,6 +3305,16 @@ function createRealExecutor() {
3311
3305
  };
3312
3306
  }
3313
3307
  },
3308
+ execInherit(command, options) {
3309
+ execSync(command, {
3310
+ cwd: options?.cwd,
3311
+ env: options?.env ? {
3312
+ ...process.env,
3313
+ ...options.env
3314
+ } : void 0,
3315
+ stdio: "inherit"
3316
+ });
3317
+ },
3314
3318
  fetch: globalThis.fetch,
3315
3319
  listChangesetFiles(cwd) {
3316
3320
  const dir = path.join(cwd, ".changeset");
@@ -4162,6 +4166,7 @@ const releaseSimpleCommand = defineCommand({
4162
4166
  //#region src/commands/repo-run-checks.ts
4163
4167
  const CHECKS = [
4164
4168
  { name: "build" },
4169
+ { name: "docker:build" },
4165
4170
  { name: "typecheck" },
4166
4171
  { name: "lint" },
4167
4172
  { name: "test" },
@@ -4173,6 +4178,11 @@ const CHECKS = [
4173
4178
  { name: "tooling:check" },
4174
4179
  { name: "docker:check" }
4175
4180
  ];
4181
+ /** Check if a name matches any skip pattern. Supports glob syntax via picomatch. */
4182
+ function shouldSkip(name, patterns) {
4183
+ if (patterns.size === 0) return false;
4184
+ return picomatch.isMatch(name, [...patterns]);
4185
+ }
4176
4186
  function defaultGetScripts(targetDir) {
4177
4187
  try {
4178
4188
  const pkg = parsePackageJson(readFileSync(path.join(targetDir, "package.json"), "utf-8"));
@@ -4207,7 +4217,7 @@ function runRunChecks(targetDir, options = {}) {
4207
4217
  const failures = [];
4208
4218
  const notDefined = [];
4209
4219
  for (const check of allChecks) {
4210
- if (skip.has(check.name)) continue;
4220
+ if (shouldSkip(check.name, skip)) continue;
4211
4221
  if (!definedScripts.has(check.name)) {
4212
4222
  if (addedNames.has(check.name)) {
4213
4223
  p.log.error(`${check.name} not defined in package.json`);
@@ -4283,16 +4293,10 @@ const publishDockerCommand = defineCommand({
4283
4293
  name: "docker:publish",
4284
4294
  description: "Build, tag, and push Docker images for packages with an image:build script"
4285
4295
  },
4286
- args: {
4287
- "dry-run": {
4288
- type: "boolean",
4289
- description: "Build and tag images but skip login, push, and logout"
4290
- },
4291
- verbose: {
4292
- type: "boolean",
4293
- description: "Enable detailed debug logging"
4294
- }
4295
- },
4296
+ args: { "dry-run": {
4297
+ type: "boolean",
4298
+ description: "Build and tag images but skip login, push, and logout"
4299
+ } },
4296
4300
  async run({ args }) {
4297
4301
  const config = {
4298
4302
  cwd: process.cwd(),
@@ -4300,8 +4304,7 @@ const publishDockerCommand = defineCommand({
4300
4304
  registryNamespace: requireEnv("DOCKER_REGISTRY_NAMESPACE"),
4301
4305
  username: requireEnv("DOCKER_USERNAME"),
4302
4306
  password: requireEnv("DOCKER_PASSWORD"),
4303
- dryRun: args["dry-run"] === true,
4304
- verbose: args.verbose === true
4307
+ dryRun: args["dry-run"] === true
4305
4308
  };
4306
4309
  runDockerPublish(createRealExecutor(), config);
4307
4310
  }
@@ -4334,10 +4337,6 @@ const dockerBuildCommand = defineCommand({
4334
4337
  type: "string",
4335
4338
  description: "Build a single package by directory path (e.g. packages/server). Useful as an image:build script."
4336
4339
  },
4337
- verbose: {
4338
- type: "boolean",
4339
- description: "Enable detailed debug logging"
4340
- },
4341
4340
  _: {
4342
4341
  type: "positional",
4343
4342
  required: false,
@@ -4358,7 +4357,6 @@ const dockerBuildCommand = defineCommand({
4358
4357
  runDockerBuild(executor, {
4359
4358
  cwd,
4360
4359
  packageDir,
4361
- verbose: args.verbose === true,
4362
4360
  extraArgs: extraArgs.filter((a) => a.length > 0)
4363
4361
  });
4364
4362
  }
@@ -4659,7 +4657,7 @@ const dockerCheckCommand = defineCommand({
4659
4657
  const main = defineCommand({
4660
4658
  meta: {
4661
4659
  name: "tooling",
4662
- version: "0.25.3",
4660
+ version: "0.26.0",
4663
4661
  description: "Bootstrap and maintain standardized TypeScript project tooling"
4664
4662
  },
4665
4663
  subCommands: {
@@ -4675,7 +4673,7 @@ const main = defineCommand({
4675
4673
  "docker:check": dockerCheckCommand
4676
4674
  }
4677
4675
  });
4678
- console.log(`@bensandee/tooling v0.25.3`);
4676
+ console.log(`@bensandee/tooling v0.26.0`);
4679
4677
  async function run() {
4680
4678
  await runMain(main);
4681
4679
  process.exit(process.exitCode ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.25.3",
3
+ "version": "0.26.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
5
  "bin": {
6
6
  "tooling": "./dist/bin.mjs"
@@ -34,12 +34,14 @@
34
34
  "citty": "^0.2.1",
35
35
  "json5": "^2.2.3",
36
36
  "jsonc-parser": "^3.3.1",
37
+ "picomatch": "^4.0.3",
37
38
  "yaml": "^2.8.2",
38
39
  "zod": "^4.3.6",
39
40
  "@bensandee/common": "0.1.2"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/node": "24.12.0",
44
+ "@types/picomatch": "^4.0.2",
43
45
  "tsdown": "0.21.2",
44
46
  "typescript": "5.9.3",
45
47
  "vitest": "4.0.18",