@dbx-tools/projen 0.9.106 → 0.9.110

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
@@ -584,30 +584,37 @@ release [options]
584
584
 
585
585
  #### Options
586
586
 
587
- | Option | Description |
588
- | ------------------------------------- | --------------------------------------------------------------------------------------------- |
589
- | `--root <path>` | repository root |
590
- | `--branch <name>` | release branch (default: "main") |
591
- | `--prefix <prefix>` | release tag prefix (default: "v") |
592
- | `--remote <name>` | git remote (default: "origin") |
593
- | `--python-root <path>` | Python package root for local publish (default: "packages/py") |
594
- | `--validate <task>` | task to run before pushing (default: []) |
595
- | `--no-bump` | use an existing synchronized local version bump |
596
- | `--publish <target>` | publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto") |
597
- | `--install <mode>` | local workspace dependency installation (choices: "auto", "always", "never", default: "auto") |
598
- | `--no-npm` | skip npm build and publication, including local npm publication |
599
- | `--no-pypi` | skip Python build and publication, including local Python publication |
600
- | `--docs` | build and deploy docs for a selected scope |
601
- | `--no-docs` | skip documentation build and deployment |
602
- | `--no-validation` | skip optional release validation tasks; workspace version checks remain mandatory |
603
- | `--no-release-notes` | skip writing docs/releases notes (Genie and git-log fallback) |
604
- | `--release-notes-text <markdown>` | write supplied markdown without invoking Genie |
605
- | `--release-notes-file <path>` | copy supplied markdown without invoking Genie |
606
- | `--release-notes-instructions <text>` | append custom instructions to the Genie release-notes prompt |
607
- | `--demo-deploy` | after tagging, stage and deploy the AppKit demo app (off by default) |
608
- | `--profile <name>` | Databricks CLI profile for --demo-deploy |
609
- | `--no-local-publish` | skip publishing to configured local registries |
610
- | `--local-registry <auto\|false\|url>` | local npm registry selection (default: "auto") |
611
- | `--local-pypi <auto\|false\|url>` | local devpi registry selection (default: "auto") |
587
+ | Option | Description |
588
+ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
589
+ | `--root <value>` | Repository or workspace root (env: PROJEN_RELEASE_ROOT) |
590
+ | `--branch <value>` | Release branch (default: "main", env: PROJEN_RELEASE_BRANCH) |
591
+ | `--prefix <value>` | Release tag prefix (default: "v", env: PROJEN_RELEASE_PREFIX) |
592
+ | `--remote <value>` | Git remote (default: "origin", env: PROJEN_RELEASE_REMOTE) |
593
+ | `--python-root <value>` | Python package root for local publish (default: "packages/py", env: PROJEN_RELEASE_PYTHON_ROOT) |
594
+ | `--validate <value>` | Repeatable task to run before pushing (default: [], env: PROJEN_RELEASE_VALIDATE) |
595
+ | `--bump` | Create and synchronize a patch version bump (default: true, env: PROJEN_RELEASE_BUMP) |
596
+ | `--no-bump` | Disable create and synchronize a patch version bump |
597
+ | `--publish <value>` | Publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto", env: PROJEN_RELEASE_PUBLISH) |
598
+ | `-i, --install <value>` | Local workspace dependency installation (choices: "auto", "always", "never", default: "auto", env: PROJEN_RELEASE_INSTALL) |
599
+ | `-n, --npm` | Build and publish npm packages (default: true, env: PROJEN_RELEASE_NPM) |
600
+ | `--no-npm` | Disable build and publish npm packages |
601
+ | `--pypi` | Build and publish Python packages (default: true, env: PROJEN_RELEASE_PYPI) |
602
+ | `--no-pypi` | Disable build and publish python packages |
603
+ | `--docs` | Build and deploy documentation (env: PROJEN_RELEASE_DOCS) |
604
+ | `--no-docs` | Disable build and deploy documentation |
605
+ | `--validation` | Run optional release validation tasks before publishing (default: true, env: PROJEN_RELEASE_VALIDATION) |
606
+ | `--no-validation` | Disable run optional release validation tasks before publishing |
607
+ | `--release-notes` | Write release notes before tagging (default: true, env: PROJEN_RELEASE_RELEASE_NOTES) |
608
+ | `--no-release-notes` | Disable write release notes before tagging |
609
+ | `--release-notes-text <value>` | Write supplied release-note Markdown without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_TEXT) |
610
+ | `--release-notes-file <value>` | Copy release-note Markdown from this file without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_FILE) |
611
+ | `--release-notes-instructions <value>` | Append instructions to the Genie release-note prompt (env: PROJEN_RELEASE_RELEASE_NOTES_INSTRUCTIONS) |
612
+ | `--demo-deploy` | Deploy the AppKit demo after tagging and local publication (env: PROJEN_RELEASE_DEMO_DEPLOY) |
613
+ | `--no-demo-deploy` | Disable deploy the appkit demo after tagging and local publication |
614
+ | `--profile <value>` | Explicit Databricks CLI profile for demo deployment |
615
+ | `--local-publish` | Publish to configured local registries (default: true, env: PROJEN_RELEASE_LOCAL_PUBLISH) |
616
+ | `--no-local-publish` | Disable publish to configured local registries |
617
+ | `--local-registry <value>` | Local npm registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_REGISTRY) |
618
+ | `--local-pypi <value>` | Local devpi registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_PYPI) |
612
619
 
613
620
  <!-- cli-reference:end -->
package/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
6
- export const PACKAGE_VERSION = "0.9.106";
6
+ export const PACKAGE_VERSION = "0.9.110";
7
7
  export * as barrels from "./src/barrels.ts";
8
8
  export * as bunApp from "./src/bun-app.ts";
9
9
  export * as bunWorkflow from "./src/bun-workflow.ts";
package/package.json CHANGED
@@ -30,9 +30,10 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.7.0",
33
- "@dbx-tools/core": "^0.9.106",
34
- "@dbx-tools/path": "^0.9.106",
35
- "@dbx-tools/shared-core": "^0.9.106",
33
+ "@dbx-tools/cli-args": "^0.9.110",
34
+ "@dbx-tools/core": "^0.9.110",
35
+ "@dbx-tools/path": "^0.9.110",
36
+ "@dbx-tools/shared-core": "^0.9.110",
36
37
  "commander": "^15.0.0",
37
38
  "concurrently": "^10.0.3",
38
39
  "constructs": "^10.6.0",
@@ -44,7 +45,7 @@
44
45
  "ts-to-zod": "^5.1.0",
45
46
  "typescript": "^5.9.3",
46
47
  "yaml": "^2.9.0",
47
- "zod": "^4.3.6"
48
+ "zod": "4.3.6"
48
49
  },
49
50
  "devEngines": {
50
51
  "packageManager": {
@@ -54,7 +55,7 @@
54
55
  },
55
56
  "main": "index.ts",
56
57
  "license": "Apache-2.0",
57
- "version": "0.9.106",
58
+ "version": "0.9.110",
58
59
  "types": "index.ts",
59
60
  "type": "module",
60
61
  "exports": {
package/src/project-py.ts CHANGED
@@ -613,7 +613,9 @@ export class DBXToolsPythonWorkspace extends Component {
613
613
  run: [
614
614
  'bun node_modules/@dbx-tools/projen/tasks/publish-python.ts "$RELEASE_VERSION"',
615
615
  `--root ${JSON.stringify(this.repository.root)}`,
616
- `--package ${allPublications.map((publication) => JSON.stringify(publication.directory)).join(" ")}`,
616
+ allPublications
617
+ .map((publication) => `--package ${JSON.stringify(publication.directory)}`)
618
+ .join(" "),
617
619
  "--output .release/python --package-directories",
618
620
  ].join(" \\\n "),
619
621
  },
package/tasks/barrels.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /** Generate package barrels once or keep affected barrels current in watch mode. */
3
3
  import { sep } from "node:path";
4
- import { parseArgs } from "node:util";
5
4
  import { log, object, stringUtils } from "@dbx-tools/shared-core";
5
+ import { z } from "zod";
6
+ import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
7
+ import { TaskDirectoriesOptionSchema, TaskWatchOptionSchema } from "./options.ts";
6
8
  import { generateBarrels } from "../src/barrels.ts";
7
9
  import { recordedPackages } from "../src/packages.ts";
8
10
  import { watchLoop, watchRoots } from "../src/watch.ts";
@@ -35,15 +37,21 @@ function warnUnownedChanges(changed: readonly string[]): void {
35
37
  }
36
38
  }
37
39
 
38
- export function main(args: string[] = process.argv.slice(2)): void {
39
- const { values } = parseArgs({
40
+ export const BarrelsOptionsSchema = z.object({
41
+ watch: TaskWatchOptionSchema,
42
+ dir: TaskDirectoriesOptionSchema.describe("Repeatable package directory to rebuild"),
43
+ });
44
+
45
+ export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
46
+ const values = await taskOptions(
47
+ taskCommand(
48
+ import.meta.url,
49
+ "Generate package barrels once or keep them current",
50
+ BarrelsOptionsSchema,
51
+ ),
52
+ BarrelsOptionsSchema,
40
53
  args,
41
- options: {
42
- watch: { type: "boolean" },
43
- dir: { type: "string", multiple: true },
44
- },
45
- strict: false,
46
- });
54
+ );
47
55
 
48
56
  if (values.watch) {
49
57
  // Watch the package roots; a source edit inside a package rebuilds just that
@@ -70,9 +78,7 @@ export function main(args: string[] = process.argv.slice(2)): void {
70
78
  },
71
79
  );
72
80
  } else {
73
- const dirs = Array.isArray(values.dir)
74
- ? values.dir.filter((value): value is string => typeof value === "string")
75
- : [];
81
+ const dirs = values.dir;
76
82
  const n = generateBarrels(dirs.length ? { dirs } : undefined);
77
83
  logger.success(
78
84
  n === 0 ? "barrels already up to date" : `updated ${stringUtils.pluralize(n, "barrel")}`,
@@ -80,4 +86,4 @@ export function main(args: string[] = process.argv.slice(2)): void {
80
86
  }
81
87
  }
82
88
 
83
- if (import.meta.main) main();
89
+ await runTaskMain(import.meta, main);
package/tasks/bump.ts CHANGED
@@ -6,7 +6,9 @@
6
6
  */
7
7
  import * as projectUtils from "@dbx-tools/core/project-utils";
8
8
  import { log } from "@dbx-tools/shared-core";
9
- import { Command, Option } from "commander";
9
+ import { z } from "zod";
10
+ import { runTaskMain, taskCommand, taskOptions, taskRoot } from "./cli.ts";
11
+ import { TaskRootOptionSchema } from "./options.ts";
10
12
  import { runTaskCommand } from "../src/_task-command.ts";
11
13
  import {
12
14
  resolveNextVersion,
@@ -16,35 +18,38 @@ import {
16
18
 
17
19
  const logger = log.logger("projen:bump");
18
20
 
19
- export async function main(): Promise<void> {
20
- const program = new Command();
21
- program
22
- .description("Increment VERSION and synchronize generated workspace versions")
23
- .addOption(
24
- new Option("-l, --level <level>", "semver increment")
25
- .choices(["patch", "minor", "major"])
26
- .default("patch"),
27
- )
28
- .option("--no-synth", "write VERSION without synchronizing generated files")
29
- .action((opts: { level: VersionLevel; synth: boolean }) => {
30
- const root = projectUtils.root() ?? process.cwd();
31
- const next = resolveNextVersion(root, opts.level);
21
+ export const BumpOptionsSchema = z.object({
22
+ root: TaskRootOptionSchema,
23
+ level: z
24
+ .enum(["patch", "minor", "major"])
25
+ .default("patch")
26
+ .describe("Semantic version increment")
27
+ .meta({ short: "l" }),
28
+ synth: z.boolean().default(true).describe("Synchronize generated workspace versions"),
29
+ });
32
30
 
33
- logger.info(`bump ${next.base} -> ${next.version} (${opts.level})`);
34
- writeWorkspaceVersion(root, next.version);
31
+ export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
32
+ const options = await taskOptions(
33
+ taskCommand(
34
+ import.meta.url,
35
+ "Increment VERSION and synchronize generated workspace versions",
36
+ BumpOptionsSchema,
37
+ ),
38
+ BumpOptionsSchema,
39
+ args,
40
+ );
41
+ const root = taskRoot(options.root ?? projectUtils.root());
42
+ const level: VersionLevel = options.level;
43
+ const next = resolveNextVersion(root, level);
35
44
 
36
- if (opts.synth) {
37
- runTaskCommand(root, process.execPath, [".projenrc.ts"]);
38
- // Package manifests are generated during the first synthesis, while
39
- // cross-package derived artifacts can read those manifests earlier in
40
- // the same pass. A second pass makes the new version visible to every
41
- // generator and leaves release validation on a converged tree.
42
- runTaskCommand(root, process.execPath, [".projenrc.ts"]);
43
- }
44
- logger.success(`workspace version synchronized at ${next.version}`);
45
- });
45
+ logger.info(`bump ${next.base} -> ${next.version} (${level})`);
46
+ writeWorkspaceVersion(root, next.version);
46
47
 
47
- await program.parseAsync();
48
+ if (options.synth) {
49
+ runTaskCommand(root, process.execPath, [".projenrc.ts"]);
50
+ runTaskCommand(root, process.execPath, [".projenrc.ts"]);
51
+ }
52
+ logger.success(`workspace version synchronized at ${next.version}`);
48
53
  }
49
54
 
50
- if (import.meta.main) await main();
55
+ await runTaskMain(import.meta, main);
package/tasks/clean.ts CHANGED
@@ -2,12 +2,30 @@
2
2
  /** Interactively remove generated files and installed dependency directories. */
3
3
  import { relative } from "node:path";
4
4
  import { log, stringUtils } from "@dbx-tools/shared-core";
5
+ import { z } from "zod";
6
+ import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
5
7
  import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/clean.ts";
6
8
  import { repoRoot, toPosix } from "../src/packages.ts";
7
9
 
8
10
  const logger = log.logger("projen:clean");
11
+ export const CleanOptionsSchema = z.object({
12
+ yes: z
13
+ .boolean()
14
+ .default(false)
15
+ .describe("Remove every generated and dependency path without prompting")
16
+ .meta({ short: "y", env: [] }),
17
+ });
18
+
9
19
  export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
10
- const yes = args.includes("-y") || args.includes("--yes");
20
+ const { yes } = await taskOptions(
21
+ taskCommand(
22
+ import.meta.url,
23
+ "Remove generated files and dependency directories",
24
+ CleanOptionsSchema,
25
+ ),
26
+ CleanOptionsSchema,
27
+ args,
28
+ );
11
29
 
12
30
  const files = listGeneratedFiles();
13
31
  const nodeModules = listNodeModulesDirs();
@@ -69,4 +87,4 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
69
87
  clack.outro(`removed ${stringUtils.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
70
88
  }
71
89
 
72
- if (import.meta.main) await main();
90
+ await runTaskMain(import.meta, main);
package/tasks/cli.ts ADDED
@@ -0,0 +1,80 @@
1
+ /** Shared Commander and entrypoint integration for Projen task CLIs. */
2
+ import { basename, extname, resolve } from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { addArgs, parseArgs, type CliArgsOptions } from "@dbx-tools/cli-args";
5
+ import * as projectUtils from "@dbx-tools/core/project-utils";
6
+ import { stringUtils } from "@dbx-tools/shared-core";
7
+ import { Command } from "commander";
8
+ import { z } from "zod";
9
+
10
+ /** Stable task name derived from its source filename. */
11
+ export function taskName(moduleUrl: string): string {
12
+ const file = basename(fileURLToPath(moduleUrl));
13
+ return file.slice(0, -extname(file).length);
14
+ }
15
+
16
+ /** Config resolution shared by task schemas. */
17
+ export function taskCliArgsOptions(moduleUrl: string): CliArgsOptions {
18
+ const prefix = [...stringUtils.tokenize(taskName(moduleUrl))].join("_").toUpperCase();
19
+ return {
20
+ cwd: process.cwd(),
21
+ prefix: `PROJEN_${prefix}`,
22
+ sources: ["env", "dotenv"],
23
+ };
24
+ }
25
+
26
+ /** Create a task command and bind every Zod-owned option through cli-args. */
27
+ export function taskCommand<T extends z.ZodRawShape>(
28
+ moduleUrl: string,
29
+ description: string,
30
+ schema: z.ZodObject<T>,
31
+ ): Command {
32
+ return addArgs(
33
+ new Command().name(taskName(moduleUrl)).description(description),
34
+ schema,
35
+ taskCliArgsOptions(moduleUrl),
36
+ );
37
+ }
38
+
39
+ /** Parse one task's flags and layered environment values through its schema. */
40
+ export async function taskOptions<T extends z.ZodRawShape>(
41
+ command: Command,
42
+ schema: z.ZodObject<T>,
43
+ argv: readonly string[] = process.argv.slice(2),
44
+ ): Promise<z.output<z.ZodObject<T>>> {
45
+ await command.parseAsync([...argv], { from: "user" });
46
+ return parseArgs(command, schema);
47
+ }
48
+
49
+ /** Read one already-parsed task command through its owning Zod schema. */
50
+ export function parsedTaskOptions<T extends z.ZodRawShape>(
51
+ command: Command,
52
+ schema: z.ZodObject<T>,
53
+ ): z.output<z.ZodObject<T>> {
54
+ return parseArgs(command, schema);
55
+ }
56
+
57
+ /** Read positional arguments after Commander has parsed one task command. */
58
+ export function taskPositionals(command: Command): readonly unknown[] {
59
+ return command.processedArgs;
60
+ }
61
+
62
+ /** Resolve the explicit task root or discover the current project root. */
63
+ export function taskRoot(root?: string): string {
64
+ return resolve(root ?? projectUtils.root() ?? process.cwd());
65
+ }
66
+
67
+ /** True when an imported task module is the active Bun or Node entrypoint. */
68
+ export function isTaskMain(meta: ImportMeta): boolean {
69
+ if (meta.main) return true;
70
+ const entry = process.argv[1];
71
+ return Boolean(entry && pathToFileURL(resolve(entry)).href === meta.url);
72
+ }
73
+
74
+ /** Run a task only when its module is the active process entrypoint. */
75
+ export async function runTaskMain(
76
+ meta: ImportMeta,
77
+ main: () => void | Promise<void>,
78
+ ): Promise<void> {
79
+ if (isTaskMain(meta)) await main();
80
+ }
@@ -5,6 +5,7 @@ import { availableParallelism } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
6
  import * as exec from "@dbx-tools/core/exec";
7
7
  import { log } from "@dbx-tools/shared-core";
8
+ import { runTaskMain } from "./cli.ts";
8
9
 
9
10
  const logger = log.logger("projen:compile");
10
11
 
@@ -142,4 +143,4 @@ async function compileCustomPackage(pkg: {
142
143
  return result!;
143
144
  }
144
145
 
145
- if (import.meta.main) await main();
146
+ await runTaskMain(import.meta, main);
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /** Restart a development command after relevant workspace changes settle. */
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
- import { configUtils } from "@dbx-tools/core";
5
4
  import * as exec from "@dbx-tools/core/exec";
6
5
  import { watch as pathWatch } from "@dbx-tools/path";
7
6
  import { log } from "@dbx-tools/shared-core";
8
- import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
7
+ import { CommanderError } from "commander";
8
+ import { z } from "zod";
9
+ import { parsedTaskOptions, runTaskMain, taskCommand, taskPositionals } from "./cli.ts";
9
10
  import {
10
11
  DEV_RESTART_DEBOUNCE_MS,
11
12
  DEV_RESTART_KEY,
@@ -30,73 +31,57 @@ export interface DevWatchOptions {
30
31
  readonly serverWatchDisabled: boolean;
31
32
  }
32
33
 
34
+ export const DevWatchOptionsSchema = z.object({
35
+ debounceMs: z.coerce
36
+ .number()
37
+ .int()
38
+ .nonnegative("Debounce must be a non-negative number of milliseconds")
39
+ .default(DEV_RESTART_DEBOUNCE_MS)
40
+ .describe("Quiet period before restarting the command"),
41
+ restartKey: z
42
+ .string()
43
+ .refine((value) => Array.from(value).length === 1, "Restart key must be exactly one character")
44
+ .default(DEV_RESTART_KEY)
45
+ .describe("Interactive immediate-restart key"),
46
+ serverWatchDisabled: z
47
+ .boolean()
48
+ .default(false)
49
+ .describe("Run the command once without watching")
50
+ .meta({ env: SERVER_WATCH_DISABLED_ENV }),
51
+ });
52
+
33
53
  interface WorkspacePackage {
34
54
  readonly dir: string;
35
55
  readonly name?: string;
36
56
  }
37
57
 
38
- function debounceMilliseconds(value: string): number {
39
- const parsed = configUtils.toNumber(value);
40
- if (parsed === undefined || parsed < 0) {
41
- throw new InvalidArgumentError("must be a non-negative number of milliseconds");
42
- }
43
- return parsed;
44
- }
45
-
46
- function singleKey(value: string): string {
47
- if (Array.from(value).length !== 1) {
48
- throw new InvalidArgumentError("must be exactly one character");
49
- }
50
- return value;
51
- }
52
-
53
58
  /** Parse watcher flags while passing every token after the command through unchanged. */
54
- export function parseDevWatchOptions(args: string[]): DevWatchOptions {
55
- const program = new Command()
59
+ export function parseDevWatchOptions(args: readonly string[]): DevWatchOptions {
60
+ const program = taskCommand(
61
+ import.meta.url,
62
+ "Restart a development command after watched changes settle",
63
+ DevWatchOptionsSchema,
64
+ )
56
65
  .name(DEV_WATCH_TASK)
57
- .description("Restart a development command after watched changes settle")
58
66
  .exitOverride()
59
67
  .configureOutput({
60
68
  writeErr: (message) => {
61
- if (import.meta.main) process.stderr.write(message);
69
+ if (process.argv[1]) process.stderr.write(message);
62
70
  },
63
71
  })
64
72
  .enablePositionalOptions()
65
73
  .passThroughOptions()
66
- .addOption(
67
- new Option("--debounce-ms <milliseconds>", "quiet period before restart")
68
- .default(DEV_RESTART_DEBOUNCE_MS)
69
- .argParser(debounceMilliseconds),
70
- )
71
- .addOption(
72
- new Option("--restart-key <key>", "interactive immediate-restart key")
73
- .default(DEV_RESTART_KEY)
74
- .argParser(singleKey),
75
- )
76
- .addOption(
77
- new Option(
78
- "--server-watch-disabled",
79
- `run the command once without watching (${SERVER_WATCH_DISABLED_ENV})`,
80
- ),
81
- )
82
74
  .argument("<command>")
83
75
  .argument("[args...]")
84
- .parse(["bun", DEV_WATCH_TASK, ...args]);
85
- const options = program.opts<{
86
- debounceMs: number;
87
- restartKey: string;
88
- serverWatchDisabled?: boolean;
89
- }>();
90
- const [command, commandArgs = []] = program.processedArgs as [string, string[]?];
91
- const serverWatchDisabled =
92
- options.serverWatchDisabled === true ||
93
- configUtils.boolean(undefined, SERVER_WATCH_DISABLED_ENV, configUtils.ENV_ONLY) === true;
94
- if (serverWatchDisabled) process.env[SERVER_WATCH_DISABLED_ENV] = "1";
76
+ .parse([...args], { from: "user" });
77
+ const options = parsedTaskOptions(program, DevWatchOptionsSchema);
78
+ const [command, commandArgs = []] = taskPositionals(program) as [string, string[]?];
79
+ if (options.serverWatchDisabled) process.env[SERVER_WATCH_DISABLED_ENV] = "1";
95
80
  return {
96
81
  command: [command, ...commandArgs],
97
82
  debounceMs: options.debounceMs,
98
83
  restartKey: options.restartKey,
99
- serverWatchDisabled,
84
+ serverWatchDisabled: options.serverWatchDisabled,
100
85
  };
101
86
  }
102
87
 
@@ -336,4 +321,4 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
336
321
  await complete;
337
322
  }
338
323
 
339
- if (import.meta.main) await main();
324
+ await runTaskMain(import.meta, main);
@@ -0,0 +1,59 @@
1
+ /** Shared Zod option fields for Projen task CLIs. */
2
+ import { z } from "zod";
3
+
4
+ /** Optional repository or workspace root. */
5
+ export const TaskRootOptionSchema = z
6
+ .string()
7
+ .trim()
8
+ .min(1)
9
+ .optional()
10
+ .describe("Repository or workspace root");
11
+
12
+ /** Required project directory. */
13
+ export const TaskProjectOptionSchema = z.string().trim().min(1).describe("Project directory");
14
+
15
+ /** Check generated state without writing changes. */
16
+ export const TaskCheckOptionSchema = z
17
+ .boolean()
18
+ .default(false)
19
+ .describe("Check generated state without writing changes");
20
+
21
+ /** Force work even when fingerprints or generated state appear current. */
22
+ export const TaskForceOptionSchema = z
23
+ .boolean()
24
+ .default(false)
25
+ .describe("Force regeneration even when outputs appear current");
26
+
27
+ /** Keep a task running and react to changes. */
28
+ export const TaskWatchOptionSchema = z
29
+ .boolean()
30
+ .default(false)
31
+ .describe("Watch inputs and keep generated state current");
32
+
33
+ /** Validate publication artifacts without uploading them. */
34
+ export const TaskDryRunOptionSchema = z
35
+ .boolean()
36
+ .default(false)
37
+ .describe("Validate artifacts without publishing them");
38
+
39
+ /** Optional output directory. */
40
+ export const TaskOutputOptionSchema = z
41
+ .string()
42
+ .trim()
43
+ .min(1)
44
+ .optional()
45
+ .describe("Output directory");
46
+
47
+ /** Positive worker concurrency. */
48
+ export const TaskConcurrencyOptionSchema = z.coerce
49
+ .number()
50
+ .int()
51
+ .positive()
52
+ .optional()
53
+ .describe("Maximum concurrent workers");
54
+
55
+ /** Repeatable package or directory selection. */
56
+ export const TaskDirectoriesOptionSchema = z
57
+ .array(z.string().trim().min(1))
58
+ .default([])
59
+ .describe("Repeatable package or directory selection");
package/tasks/projenrc.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  /** Resynthesize when the root definition or configured generator inputs change. */
3
3
  import { resolve } from "node:path";
4
4
  import { log } from "@dbx-tools/shared-core";
5
+ import { runTaskMain } from "./cli.ts";
5
6
  import { repoRoot, syncResynthPaths } from "../src/packages.ts";
6
7
  import { runSynth } from "../src/scaffold.ts";
7
8
  import { watchLoop } from "../src/watch.ts";
@@ -38,4 +39,4 @@ export function main(): void {
38
39
  );
39
40
  }
40
41
 
41
- if (import.meta.main) main();
42
+ await runTaskMain(import.meta, main);
@@ -16,7 +16,9 @@ import { join, relative, resolve } from "node:path";
16
16
  import * as exec from "@dbx-tools/core/exec";
17
17
  import * as projectUtils from "@dbx-tools/core/project-utils";
18
18
  import { log } from "@dbx-tools/shared-core";
19
- import { Command } from "commander";
19
+ import { z } from "zod";
20
+ import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
21
+ import { TaskDryRunOptionSchema } from "./options.ts";
20
22
  import { runTaskCommand } from "../src/_task-command.ts";
21
23
 
22
24
  const DEFAULT_REGISTRY = "https://registry.npmjs.org";
@@ -451,18 +453,27 @@ export async function publishNpmArchives(options: {
451
453
  }
452
454
 
453
455
  /** Parse npm archive publication options and execute the publisher. */
454
- export async function main(): Promise<void> {
455
- const program = new Command();
456
- program
457
- .requiredOption("--directory <path>", "Directory containing npm archives")
458
- .option("--version <version>", "Expected npm release version")
459
- .option("--registry <url>", "npm registry URL")
460
- .option("--dry-run", "Validate archives without publishing")
461
- .action(
462
- (options: { directory: string; dryRun?: boolean; registry?: string; version?: string }) =>
463
- publishNpmArchives(options),
464
- );
465
- await program.parseAsync();
456
+ export const PublishNpmOptionsSchema = z.object({
457
+ directory: z.string().trim().min(1).describe("Directory containing npm archives"),
458
+ version: z.string().trim().min(1).optional().describe("Expected npm release version"),
459
+ registry: z
460
+ .string()
461
+ .trim()
462
+ .min(1)
463
+ .optional()
464
+ .describe("npm registry URL")
465
+ .meta({ env: "NPM_CONFIG_REGISTRY" }),
466
+ dryRun: TaskDryRunOptionSchema,
467
+ });
468
+
469
+ /** Parse task options and publish validated npm release archives. */
470
+ export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
471
+ const options = await taskOptions(
472
+ taskCommand(import.meta.url, "Publish validated npm release archives", PublishNpmOptionsSchema),
473
+ PublishNpmOptionsSchema,
474
+ args,
475
+ );
476
+ await publishNpmArchives(options);
466
477
  }
467
478
 
468
- if (import.meta.main) await main();
479
+ await runTaskMain(import.meta, main);