@bensandee/tooling 0.37.0 → 0.38.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.
@@ -1,5 +1,5 @@
1
1
  import * as clack from "@clack/prompts";
2
- import { execSync } from "node:child_process";
2
+ import { execFileSync } from "node:child_process";
3
3
  import { z } from "zod";
4
4
  //#region src/utils/log.ts
5
5
  const out = (msg) => console.log(msg);
@@ -18,6 +18,13 @@ function note(body, title) {
18
18
  }
19
19
  //#endregion
20
20
  //#region src/utils/debug.ts
21
+ const REDACT_PATTERNS = [/x-access-token:[^\s@]+/g, /Authorization:\s*(?:token|Bearer)\s+\S+/gi];
22
+ /** Redact secrets (tokens, credentials) from text before logging. */
23
+ function redact(text) {
24
+ let result = text;
25
+ for (const pattern of REDACT_PATTERNS) result = result.replace(pattern, "[REDACTED]");
26
+ return result;
27
+ }
21
28
  /** Check if verbose/debug mode is enabled via environment variables. */
22
29
  function isEnvVerbose() {
23
30
  return process.env["TOOLING_DEBUG"] === "true";
@@ -29,9 +36,9 @@ function debug(config, message) {
29
36
  /** Log the result of an exec call when verbose mode is enabled. */
30
37
  function debugExec(config, label, result) {
31
38
  if (!config.verbose) return;
32
- const lines = [`[debug] ${label} (exit code ${String(result.exitCode)})`];
33
- if (result.stdout.trim()) lines.push(` stdout: ${result.stdout.trim()}`);
34
- if (result.stderr.trim()) lines.push(` stderr: ${result.stderr.trim()}`);
39
+ const lines = [`[debug] ${redact(label)} (exit code ${String(result.exitCode)})`];
40
+ if (result.stdout.trim()) lines.push(` stdout: ${redact(result.stdout.trim())}`);
41
+ if (result.stderr.trim()) lines.push(` stderr: ${redact(result.stderr.trim())}`);
35
42
  log.info(lines.join("\n"));
36
43
  }
37
44
  //#endregion
@@ -45,10 +52,10 @@ function isExecSyncError(err) {
45
52
  /** Create an executor that runs real commands, fetches, and manages process signals. */
46
53
  function createRealExecutor() {
47
54
  return {
48
- exec(command, options) {
55
+ exec(file, args, options) {
49
56
  try {
50
57
  return {
51
- stdout: execSync(command, {
58
+ stdout: execFileSync(file, args, {
52
59
  cwd: options?.cwd,
53
60
  env: options?.env ? {
54
61
  ...process.env,
@@ -77,8 +84,8 @@ function createRealExecutor() {
77
84
  };
78
85
  }
79
86
  },
80
- execInherit(command, options) {
81
- execSync(command, {
87
+ execInherit(file, args, options) {
88
+ execFileSync(file, args, {
82
89
  cwd: options?.cwd,
83
90
  env: options?.env ? {
84
91
  ...process.env,
@@ -107,30 +114,45 @@ const ContainerInfoSchema = z.object({
107
114
  Service: z.string(),
108
115
  Health: z.string()
109
116
  });
110
- /** Build the `docker compose` base command string from config. */
111
- function composeCommand(config) {
112
- return `docker compose ${config.composeFiles.map((f) => `-f ${f}`).join(" ")}${config.envFile ? ` --env-file ${config.envFile}` : ""}`;
117
+ /** Build the `docker compose` base args from config. */
118
+ function composeArgs(config) {
119
+ const args = ["compose"];
120
+ for (const f of config.composeFiles) args.push("-f", f);
121
+ if (config.envFile) args.push("--env-file", config.envFile);
122
+ return args;
113
123
  }
114
124
  /** Run the build command if configured. */
115
125
  function buildImages(executor, config) {
116
126
  if (!config.buildCommand) return;
117
- executor.execInherit(config.buildCommand, { cwd: config.buildCwd ?? config.compose.cwd });
127
+ const [file, ...args] = config.buildCommand;
128
+ if (!file) return;
129
+ executor.execInherit(file, args, { cwd: config.buildCwd ?? config.compose.cwd });
118
130
  }
119
131
  /** Start the compose stack in detached mode. */
120
132
  function composeUp(executor, config) {
121
- executor.execInherit(`${composeCommand(config)} up -d`, { cwd: config.cwd });
133
+ executor.execInherit("docker", [
134
+ ...composeArgs(config),
135
+ "up",
136
+ "-d"
137
+ ], { cwd: config.cwd });
122
138
  }
123
139
  /** Tear down the compose stack, removing volumes and orphans. Swallows errors. */
124
140
  function composeDown(executor, config) {
125
141
  try {
126
- executor.execInherit(`${composeCommand(config)} down -v --remove-orphans`, { cwd: config.cwd });
142
+ executor.execInherit("docker", [
143
+ ...composeArgs(config),
144
+ "down",
145
+ "-v",
146
+ "--remove-orphans"
147
+ ], { cwd: config.cwd });
127
148
  } catch (_error) {}
128
149
  }
129
150
  /** Show logs for a specific service (or all services if not specified). Swallows errors. */
130
151
  function composeLogs(executor, config, service) {
131
152
  try {
132
- const suffix = service ? ` ${service}` : "";
133
- executor.execInherit(`${composeCommand(config)} logs${suffix}`, { cwd: config.cwd });
153
+ const args = [...composeArgs(config), "logs"];
154
+ if (service) args.push(service);
155
+ executor.execInherit("docker", args, { cwd: config.cwd });
134
156
  } catch (_error) {}
135
157
  }
136
158
  /**
@@ -138,7 +160,12 @@ function composeLogs(executor, config, service) {
138
160
  * Handles both JSON array and newline-delimited JSON (varies by docker compose version).
139
161
  */
140
162
  function composePs(executor, config) {
141
- const output = executor.exec(`${composeCommand(config)} ps --format json`, { cwd: config.cwd }).stdout.trim();
163
+ const output = executor.exec("docker", [
164
+ ...composeArgs(config),
165
+ "ps",
166
+ "--format",
167
+ "json"
168
+ ], { cwd: config.cwd }).stdout.trim();
142
169
  if (!output) return [];
143
170
  const ArraySchema = z.array(ContainerInfoSchema);
144
171
  try {
@@ -275,4 +302,4 @@ async function runDockerCheck(executor, config) {
275
302
  }
276
303
  }
277
304
  //#endregion
278
- export { composeDown as a, composeUp as c, debug as d, debugExec as f, note as h, composeCommand as i, createRealExecutor as l, log as m, checkHttpHealth as n, composeLogs as o, isEnvVerbose as p, getContainerHealth as r, composePs as s, runDockerCheck as t, isExecSyncError as u };
305
+ export { composeDown as a, composeUp as c, debug as d, debugExec as f, note as g, log as h, composeArgs as i, createRealExecutor as l, redact as m, checkHttpHealth as n, composeLogs as o, isEnvVerbose as p, getContainerHealth as r, composePs as s, runDockerCheck as t, isExecSyncError as u };
@@ -11,17 +11,22 @@ interface ExecResult {
11
11
  interface ExecOptions {
12
12
  cwd?: string;
13
13
  env?: Record<string, string>;
14
+ /** Data to pass to the command via stdin. */
15
+ input?: string;
16
+ }
17
+ /** Shared command execution and HTTP fetch interface. */
18
+ interface BaseExecutor {
19
+ /** Run a command with arguments and return the result (stdout/stderr captured). */
20
+ exec(file: string, args: string[], options?: ExecOptions): ExecResult;
21
+ /** Run a command with arguments, streaming stdout/stderr to the console. */
22
+ execInherit(file: string, args: string[], options?: ExecOptions): void;
23
+ /** Perform an HTTP request (mirrors the global fetch signature). */
24
+ fetch(url: string, init?: RequestInit): Promise<Response>;
14
25
  }
15
26
  //#endregion
16
27
  //#region src/docker-check/types.d.ts
17
28
  /** Abstraction over side effects for testability. */
18
- interface DockerCheckExecutor {
19
- /** Run a shell command and return the result (stdout/stderr captured). */
20
- exec(command: string, options?: ExecOptions): ExecResult;
21
- /** Run a shell command, streaming stdout/stderr to the console. */
22
- execInherit(command: string, options?: ExecOptions): void;
23
- /** Perform an HTTP request. */
24
- fetch(url: string, init?: RequestInit): Promise<Response>;
29
+ interface DockerCheckExecutor extends BaseExecutor {
25
30
  /** Get current time in ms (mockable clock). */
26
31
  now(): number;
27
32
  /** Sleep for the given duration (mockable delay). */
@@ -57,8 +62,8 @@ interface ComposeConfig {
57
62
  interface CheckConfig {
58
63
  /** Docker compose settings. */
59
64
  compose: ComposeConfig;
60
- /** Optional build command to run before starting compose (e.g. "pnpm image:build"). */
61
- buildCommand?: string;
65
+ /** Optional build command to run before starting compose (e.g. ["pnpm", "image:build"]). */
66
+ buildCommand?: string[];
62
67
  /** Working directory for the build command (defaults to compose.cwd). */
63
68
  buildCwd?: string;
64
69
  /** HTTP health checks to poll. All must pass for success. */
@@ -96,8 +101,8 @@ declare const ContainerInfoSchema: z.ZodObject<{
96
101
  Health: z.ZodString;
97
102
  }, z.core.$strip>;
98
103
  type ContainerInfo = z.infer<typeof ContainerInfoSchema>;
99
- /** Build the `docker compose` base command string from config. */
100
- declare function composeCommand(config: ComposeConfig): string;
104
+ /** Build the `docker compose` base args from config. */
105
+ declare function composeArgs(config: ComposeConfig): string[];
101
106
  /** Start the compose stack in detached mode. */
102
107
  declare function composeUp(executor: DockerCheckExecutor, config: ComposeConfig): void;
103
108
  /** Tear down the compose stack, removing volumes and orphans. Swallows errors. */
@@ -116,4 +121,4 @@ declare function getContainerHealth(containers: ContainerInfo[], serviceName: st
116
121
  /** Run a single HTTP health check, returning true if the validator passes. */
117
122
  declare function checkHttpHealth(executor: DockerCheckExecutor, check: HttpHealthCheck): Promise<boolean>;
118
123
  //#endregion
119
- export { type CheckConfig, type CheckResult, type ComposeConfig, type ContainerInfo, type DockerCheckExecutor, type HttpHealthCheck, checkHttpHealth, composeCommand, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
124
+ export { type CheckConfig, type CheckResult, type ComposeConfig, type ContainerInfo, type DockerCheckExecutor, type HttpHealthCheck, checkHttpHealth, composeArgs, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
@@ -1,2 +1,2 @@
1
- import { a as composeDown, c as composeUp, i as composeCommand, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-Ceom_OgJ.mjs";
2
- export { checkHttpHealth, composeCommand, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
1
+ import { a as composeDown, c as composeUp, i as composeArgs, l as createRealExecutor, n as checkHttpHealth, o as composeLogs, r as getContainerHealth, s as composePs, t as runDockerCheck } from "../check-hutCTIXe.mjs";
2
+ export { checkHttpHealth, composeArgs, composeDown, composeLogs, composePs, composeUp, createRealExecutor, getContainerHealth, runDockerCheck };
package/dist/index.d.mts CHANGED
@@ -86,6 +86,8 @@ interface DetectedProjectState {
86
86
  hasPackageJson: boolean;
87
87
  hasTsconfig: boolean;
88
88
  hasOxlintConfig: boolean;
89
+ /** Existing oxlint.config.ts already imports @bensandee/config/oxlint/recommended */
90
+ oxlintExtendsRecommended: boolean;
89
91
  /** Legacy .oxlintrc.json found (should be migrated to oxlint.config.ts) */
90
92
  hasLegacyOxlintJson: boolean;
91
93
  hasGitignore: boolean;
package/package.json CHANGED
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "name": "@bensandee/tooling",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "CLI tool to bootstrap and maintain standardized TypeScript project tooling",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://codeberg.org/bensandee/tooling.git",
9
+ "directory": "packages/tooling-cli"
10
+ },
5
11
  "bin": {
6
12
  "bst": "./dist/bin.mjs"
7
13
  },
@@ -44,8 +50,8 @@
44
50
  "@release-it/bumper": "7.0.5",
45
51
  "@types/node": "24.12.0",
46
52
  "@types/picomatch": "4.0.2",
47
- "commit-and-tag-version": "12.7.0",
48
- "knip": "5.87.0",
53
+ "commit-and-tag-version": "12.7.1",
54
+ "knip": "6.0.3",
49
55
  "lefthook": "2.1.4",
50
56
  "oxfmt": "0.41.0",
51
57
  "oxlint": "1.56.0",
@@ -54,7 +60,7 @@
54
60
  "tsdown": "0.21.4",
55
61
  "typescript": "5.9.3",
56
62
  "vitest": "4.1.0",
57
- "@bensandee/config": "0.9.1"
63
+ "@bensandee/config": "0.9.2"
58
64
  },
59
65
  "scripts": {
60
66
  "build": "tsdown",
@@ -134,8 +134,11 @@
134
134
  }
135
135
  },
136
136
  "buildCommand": {
137
- "description": "Command to build images before checking",
138
- "type": "string"
137
+ "description": "Command to build images before checking (e.g. [\"pnpm\", \"image:build\"])",
138
+ "type": "array",
139
+ "items": {
140
+ "type": "string"
141
+ }
139
142
  },
140
143
  "buildCwd": {
141
144
  "description": "Working directory for build command",
@@ -157,6 +160,14 @@
157
160
  "additionalProperties": false
158
161
  }
159
162
  ]
163
+ },
164
+ "registryHost": {
165
+ "description": "Docker registry hostname (e.g. ghcr.io, code.example.com)",
166
+ "type": "string"
167
+ },
168
+ "registryNamespace": {
169
+ "description": "Docker registry namespace (e.g. ghcr.io/myorg, code.example.com/myorg)",
170
+ "type": "string"
160
171
  }
161
172
  },
162
173
  "additionalProperties": false