@dbx-tools/projen 0.9.106 → 0.9.108

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/tasks/release.ts CHANGED
@@ -4,8 +4,11 @@ import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import * as projectUtils from "@dbx-tools/core/project-utils";
6
6
  import { log } from "@dbx-tools/shared-core";
7
- import { Command, Option } from "commander";
7
+ import type { Command } from "commander";
8
+ import { z } from "zod";
9
+ import { parsedTaskOptions, runTaskMain, taskCommand } from "./cli.ts";
8
10
  import { publishLocalRelease } from "./local-publish.ts";
11
+ import { TaskDirectoriesOptionSchema, TaskRootOptionSchema } from "./options.ts";
9
12
  import { writeReleaseNotes } from "./release-notes.ts";
10
13
  import { assertReleaseVersion } from "./release-version.ts";
11
14
  import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
@@ -368,101 +371,105 @@ export async function runRelease(
368
371
  return tag;
369
372
  }
370
373
 
371
- /** Build the release task's native parser without executing git or registry operations. */
374
+ export const ReleaseOptionsSchema = z.object({
375
+ root: TaskRootOptionSchema,
376
+ branch: z.string().trim().min(1).default("main").describe("Release branch"),
377
+ prefix: z.string().default("v").describe("Release tag prefix"),
378
+ remote: z.string().trim().min(1).default("origin").describe("Git remote"),
379
+ pythonRoot: z
380
+ .string()
381
+ .trim()
382
+ .min(1)
383
+ .default("packages/py")
384
+ .describe("Python package root for local publish"),
385
+ validate: TaskDirectoriesOptionSchema.describe("Repeatable task to run before pushing"),
386
+ bump: z.boolean().default(true).describe("Create and synchronize a patch version bump"),
387
+ publish: z.enum(RELEASE_PUBLISH_TARGETS).default("auto").describe("Publication scope"),
388
+ install: z
389
+ .enum(RELEASE_INSTALL_MODES)
390
+ .default("auto")
391
+ .describe("Local workspace dependency installation"),
392
+ npm: z.boolean().default(true).describe("Build and publish npm packages"),
393
+ pypi: z.boolean().default(true).describe("Build and publish Python packages"),
394
+ docs: z.boolean().optional().describe("Build and deploy documentation"),
395
+ validation: z
396
+ .boolean()
397
+ .default(true)
398
+ .describe("Run optional release validation tasks before publishing"),
399
+ releaseNotes: z.boolean().default(true).describe("Write release notes before tagging"),
400
+ releaseNotesText: z
401
+ .string()
402
+ .optional()
403
+ .describe("Write supplied release-note Markdown without invoking Genie"),
404
+ releaseNotesFile: z
405
+ .string()
406
+ .trim()
407
+ .min(1)
408
+ .optional()
409
+ .describe("Copy release-note Markdown from this file without invoking Genie"),
410
+ releaseNotesInstructions: z
411
+ .string()
412
+ .optional()
413
+ .describe("Append instructions to the Genie release-note prompt"),
414
+ demoDeploy: z
415
+ .boolean()
416
+ .optional()
417
+ .describe("Deploy the AppKit demo after tagging and local publication"),
418
+ profile: z
419
+ .string()
420
+ .trim()
421
+ .min(1)
422
+ .optional()
423
+ .describe("Explicit Databricks CLI profile for demo deployment")
424
+ .meta({ env: [], helpDefault: false }),
425
+ localPublish: z.boolean().default(true).describe("Publish to configured local registries"),
426
+ localRegistry: z
427
+ .string()
428
+ .trim()
429
+ .min(1)
430
+ .default("auto")
431
+ .describe("Local npm registry selection: auto, false, or URL"),
432
+ localPypi: z
433
+ .string()
434
+ .trim()
435
+ .min(1)
436
+ .default("auto")
437
+ .describe("Local devpi registry selection: auto, false, or URL"),
438
+ });
439
+
440
+ /** Build the release task's schema-driven parser without executing release operations. */
372
441
  export function createReleaseCommand(): Command {
373
- return new Command()
374
- .name("release")
375
- .description("Prepare an annotated release and select its build and publication steps")
376
- .option("--root <path>", "repository root")
377
- .option("--branch <name>", "release branch", "main")
378
- .option("--prefix <prefix>", "release tag prefix", "v")
379
- .option("--remote <name>", "git remote", "origin")
380
- .option("--python-root <path>", "Python package root for local publish", "packages/py")
381
- .option(
382
- "--validate <task>",
383
- "task to run before pushing",
384
- (task, tasks: string[]) => [...tasks, task],
385
- [],
386
- )
387
- .option("--no-bump", "use an existing synchronized local version bump")
388
- .addOption(
389
- new Option("--publish <target>", "publication scope")
390
- .choices([...RELEASE_PUBLISH_TARGETS])
391
- .default("auto"),
392
- )
393
- .addOption(
394
- new Option("--install <mode>", "local workspace dependency installation")
395
- .choices([...RELEASE_INSTALL_MODES])
396
- .default("auto"),
397
- )
398
- .option("--no-npm", "skip npm build and publication, including local npm publication")
399
- .option("--no-pypi", "skip Python build and publication, including local Python publication")
400
- .option("--docs", "build and deploy docs for a selected scope")
401
- .option("--no-docs", "skip documentation build and deployment")
402
- .option(
403
- "--no-validation",
404
- "skip optional release validation tasks; workspace version checks remain mandatory",
405
- )
406
- .option("--no-release-notes", "skip writing docs/releases notes (Genie and git-log fallback)")
407
- .option("--release-notes-text <markdown>", "write supplied markdown without invoking Genie")
408
- .option("--release-notes-file <path>", "copy supplied markdown without invoking Genie")
409
- .option(
410
- "--release-notes-instructions <text>",
411
- "append custom instructions to the Genie release-notes prompt",
412
- )
413
- .option("--demo-deploy", "after tagging, stage and deploy the AppKit demo app (off by default)")
414
- .option("--profile <name>", "Databricks CLI profile for --demo-deploy")
415
- .option("--no-local-publish", "skip publishing to configured local registries")
416
- .option("--local-registry <auto|false|url>", "local npm registry selection", "auto")
417
- .option("--local-pypi <auto|false|url>", "local devpi registry selection", "auto")
418
- .action(
419
- async (
420
- options: ReleaseSelectionOptions & {
421
- root?: string;
422
- branch: string;
423
- bump: boolean;
424
- prefix: string;
425
- remote: string;
426
- pythonRoot: string;
427
- localPublish: boolean;
428
- localRegistry: string;
429
- localPypi: string;
430
- install: ReleaseInstallMode;
431
- validate: string[];
432
- demoDeploy?: boolean;
433
- profile?: string;
434
- releaseNotes?: boolean;
435
- releaseNotesInstructions?: string;
436
- releaseNotesText?: string;
437
- releaseNotesFile?: string;
438
- },
439
- ) => {
440
- await runRelease({
441
- root: options.root ?? projectUtils.root() ?? process.cwd(),
442
- branch: options.branch,
443
- bump: options.bump,
444
- prefix: options.prefix,
445
- remote: options.remote,
446
- pythonRoot: options.pythonRoot,
447
- localPublish: options.localPublish,
448
- localRegistry: options.localRegistry,
449
- localPypi: options.localPypi,
450
- install: options.install,
451
- publish: options.publish,
452
- npm: options.npm,
453
- pypi: options.pypi,
454
- docs: options.docs,
455
- validation: options.validation,
456
- validationTasks: options.validate,
457
- demoDeploy: options.demoDeploy,
458
- demoProfile: options.profile,
459
- releaseNotes: options.releaseNotes,
460
- releaseNotesInstructions: options.releaseNotesInstructions,
461
- releaseNotesText: options.releaseNotesText,
462
- releaseNotesFile: options.releaseNotesFile,
463
- });
464
- },
465
- );
442
+ return taskCommand(
443
+ import.meta.url,
444
+ "Prepare an annotated release and select its build and publication steps",
445
+ ReleaseOptionsSchema,
446
+ ).action(async (_options: unknown, command: Command) => {
447
+ const options = parsedTaskOptions(command, ReleaseOptionsSchema);
448
+ await runRelease({
449
+ root: options.root ?? projectUtils.root() ?? process.cwd(),
450
+ branch: options.branch,
451
+ bump: options.bump,
452
+ prefix: options.prefix,
453
+ remote: options.remote,
454
+ pythonRoot: options.pythonRoot,
455
+ localPublish: options.localPublish,
456
+ localRegistry: options.localRegistry,
457
+ localPypi: options.localPypi,
458
+ install: options.install,
459
+ publish: options.publish,
460
+ npm: options.npm,
461
+ pypi: options.pypi,
462
+ docs: options.docs,
463
+ validation: options.validation,
464
+ validationTasks: options.validate,
465
+ demoDeploy: options.demoDeploy,
466
+ demoProfile: options.profile,
467
+ releaseNotes: options.releaseNotes,
468
+ releaseNotesInstructions: options.releaseNotesInstructions,
469
+ releaseNotesText: options.releaseNotesText,
470
+ releaseNotesFile: options.releaseNotesFile,
471
+ });
472
+ });
466
473
  }
467
474
 
468
475
  /** Execute the native release task parser. */
@@ -470,4 +477,4 @@ export async function main(): Promise<void> {
470
477
  await createReleaseCommand().parseAsync();
471
478
  }
472
479
 
473
- if (import.meta.main) await main();
480
+ await runTaskMain(import.meta, main);
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /** Print shared-core runtime-export usage by caller kind. */
3
+ import { runTaskMain } from "./cli.ts";
3
4
  import { formatSharedCoreUsage, sharedCoreExportUsage } from "../src/shared-core-usage.ts";
4
5
 
5
- process.stdout.write(formatSharedCoreUsage(sharedCoreExportUsage()));
6
+ export function main(): void {
7
+ process.stdout.write(formatSharedCoreUsage(sharedCoreExportUsage()));
8
+ }
9
+
10
+ await runTaskMain(import.meta, main);
package/tasks/sync.ts CHANGED
@@ -3,6 +3,9 @@
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { log } from "@dbx-tools/shared-core";
5
5
  import concurrently from "concurrently";
6
+ import { z } from "zod";
7
+ import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
8
+ import { TaskWatchOptionSchema } from "./options.ts";
6
9
  import { repoRoot, syncWatchTasks } from "../src/packages.ts";
7
10
  import { runSynth } from "../src/scaffold.ts";
8
11
  import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
@@ -45,8 +48,19 @@ function taskWatcher(task: string) {
45
48
  };
46
49
  }
47
50
 
51
+ export const SyncOptionsSchema = z.object({ watch: TaskWatchOptionSchema });
52
+
48
53
  export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
49
- if (!args.includes("--watch")) {
54
+ const { watch } = await taskOptions(
55
+ taskCommand(
56
+ import.meta.url,
57
+ "Synthesize once or supervise focused generators",
58
+ SyncOptionsSchema,
59
+ ),
60
+ SyncOptionsSchema,
61
+ args,
62
+ );
63
+ if (!watch) {
50
64
  // One-shot: full synth (+install + barrels via the post-synth component). This is
51
65
  // the scriptable path, so a failed synth stays a failed exit code.
52
66
  logger.start("synthesizing");
@@ -123,4 +137,4 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
123
137
  }
124
138
  }
125
139
 
126
- if (import.meta.main) await main();
140
+ await runTaskMain(import.meta, main);
@@ -2,6 +2,9 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { basename, join, relative, resolve, sep } from "node:path";
3
3
  import * as exec from "@dbx-tools/core/exec";
4
4
  import { log, object } from "@dbx-tools/shared-core";
5
+ import { z } from "zod";
6
+ import { runTaskMain, taskCommand, taskOptions, taskPositionals, taskRoot } from "./cli.ts";
7
+ import { TaskRootOptionSchema } from "./options.ts";
5
8
 
6
9
  const logger = log.logger("projen:test");
7
10
 
@@ -26,6 +29,12 @@ export interface WorkspaceGraph {
26
29
  }
27
30
 
28
31
  type TestMode = "focused" | "changed" | "unit" | "integration" | "all" | "graph";
32
+ const TEST_MODES = ["focused", "changed", "unit", "integration", "all", "graph"] as const;
33
+
34
+ export const TestWorkspaceOptionsSchema = z.object({
35
+ root: TaskRootOptionSchema,
36
+ base: z.string().trim().min(1).optional().describe("Git base used for changed-test selection"),
37
+ });
29
38
 
30
39
  const PROJEN_INTEGRATION_TEST =
31
40
  /(?:local-publish|npm-release|packed-consumer|project-py|publish|python-sync|release|root-install|sdk-boundary).*\.test\.ts$/;
@@ -155,7 +164,8 @@ function selectedTests(
155
164
  root: string,
156
165
  graph: WorkspaceGraph,
157
166
  mode: TestMode,
158
- args: readonly string[],
167
+ selections: readonly string[],
168
+ base?: string,
159
169
  ): string[] {
160
170
  const all = [
161
171
  ...graph.packages.flatMap((pkg) => testFiles(join(root, pkg.path, "test"))),
@@ -167,14 +177,13 @@ function selectedTests(
167
177
 
168
178
  let names: Set<string>;
169
179
  if (mode === "changed") {
170
- const baseIndex = args.indexOf("--base");
171
- const changed = changedFiles(root, baseIndex >= 0 ? args[baseIndex + 1] : undefined);
180
+ const changed = changedFiles(root, base);
172
181
  if (changed.length === 0) return [];
173
182
  names = affectedPackages(graph, changed);
174
183
  } else {
175
- if (args.length === 0) throw new Error("test:focused requires a package name or path");
184
+ if (selections.length === 0) throw new Error("test:focused requires a package name or path");
176
185
  names = new Set<string>();
177
- for (const value of args) {
186
+ for (const value of selections) {
178
187
  const direct = graph.packages.find((pkg) => pkg.name === value);
179
188
  const byPath = direct ?? packageForPath(graph, toPosix(relative(root, resolve(root, value))));
180
189
  if (!byPath) throw new Error(`Unknown workspace package or path: ${value}`);
@@ -239,15 +248,21 @@ async function runTests(root: string, files: readonly string[]): Promise<void> {
239
248
  }
240
249
 
241
250
  export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<void> {
242
- const root = process.cwd();
243
- const mode = (argv[0] ?? "all") as TestMode;
244
- if (!["focused", "changed", "unit", "integration", "all", "graph"].includes(mode)) {
245
- throw new Error(`Unknown test mode: ${mode}`);
246
- }
251
+ const command = taskCommand(
252
+ import.meta.url,
253
+ "Select and run workspace tests by dependency graph",
254
+ TestWorkspaceOptionsSchema,
255
+ )
256
+ .argument(`[mode]`, `Test mode: ${TEST_MODES.join(", ")}`, "all")
257
+ .argument("[selection...]", "Workspace package names or paths for focused mode");
258
+ const options = await taskOptions(command, TestWorkspaceOptionsSchema, argv);
259
+ const [modeValue = "all", selections = []] = taskPositionals(command) as [string?, string[]?];
260
+ const mode = z.enum(TEST_MODES).parse(modeValue);
261
+ const root = taskRoot(options.root);
247
262
  const graph = workspaceGraph(root);
248
263
  writeGraph(root, graph);
249
264
  if (mode === "graph") return;
250
- await runTests(root, selectedTests(root, graph, mode, argv.slice(1)));
265
+ await runTests(root, selectedTests(root, graph, mode, selections, options.base));
251
266
  }
252
267
 
253
- if (import.meta.main) await main();
268
+ await runTaskMain(import.meta, main);
@@ -1,4 +1,5 @@
1
1
  import { resolve } from "node:path";
2
+ import { runTaskMain } from "./cli.ts";
2
3
 
3
4
  const entries = {
4
5
  auth: "packages/js/ui/appkit/src/auth/react/index.ts",
@@ -6,17 +7,21 @@ const entries = {
6
7
  search: "packages/js/ui/appkit/src/search/react/index.ts",
7
8
  } as const;
8
9
 
9
- for (const [name, entry] of Object.entries(entries)) {
10
- const result = await Bun.build({
11
- entrypoints: [resolve(entry)],
12
- target: "browser",
13
- minify: true,
14
- sourcemap: "none",
15
- external: ["react", "react-dom", "@databricks/*", "@dbx-tools/shared-*", "lucide-react"],
16
- });
17
- if (!result.success) {
18
- throw new AggregateError(result.logs, `Could not build the ${name} UI subpath`);
10
+ export async function main(): Promise<void> {
11
+ for (const [name, entry] of Object.entries(entries)) {
12
+ const result = await Bun.build({
13
+ entrypoints: [resolve(entry)],
14
+ target: "browser",
15
+ minify: true,
16
+ sourcemap: "none",
17
+ external: ["react", "react-dom", "@databricks/*", "@dbx-tools/shared-*", "lucide-react"],
18
+ });
19
+ if (!result.success) {
20
+ throw new AggregateError(result.logs, `Could not build the ${name} UI subpath`);
21
+ }
22
+ const bytes = result.outputs.reduce((total, output) => total + output.size, 0);
23
+ console.log(`${name}: ${bytes} bytes`);
19
24
  }
20
- const bytes = result.outputs.reduce((total, output) => total + output.size, 0);
21
- console.log(`${name}: ${bytes} bytes`);
22
25
  }
26
+
27
+ await runTaskMain(import.meta, main);
@@ -6,6 +6,7 @@ import * as projectUtils from "@dbx-tools/core/project-utils";
6
6
  import { find } from "@dbx-tools/path";
7
7
  import { json, log } from "@dbx-tools/shared-core";
8
8
  import { parse } from "smol-toml";
9
+ import { runTaskMain } from "./cli.ts";
9
10
  import { recordedPackages } from "../src/packages.ts";
10
11
  import { readWorkspaceVersion } from "../src/workspace-version.ts";
11
12
 
@@ -45,4 +46,4 @@ export function main(): void {
45
46
  logger.success(`all workspace package versions match ${version}`);
46
47
  }
47
48
 
48
- if (import.meta.main) main();
49
+ await runTaskMain(import.meta, main);