@dbx-tools/projen 0.9.154 → 0.9.157

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 (49) hide show
  1. package/README.md +36 -47
  2. package/index.ts +42 -20
  3. package/package.json +8 -8
  4. package/src/_task-command.ts +7 -7
  5. package/src/appkit-workspace.ts +278 -104
  6. package/src/barrels.ts +147 -259
  7. package/src/bun-app.ts +54 -10
  8. package/src/clean.ts +6 -6
  9. package/src/engine-root.ts +4 -4
  10. package/src/mixin.ts +0 -2
  11. package/src/module-exports.ts +4 -5
  12. package/src/npm-packaging.ts +128 -0
  13. package/src/npm-release.ts +200 -0
  14. package/src/packages.ts +11 -11
  15. package/src/project-js.ts +209 -103
  16. package/src/project-predicate.ts +5 -12
  17. package/src/project-py.ts +6 -6
  18. package/src/project.ts +7 -14
  19. package/src/publish.ts +3 -3
  20. package/src/python-node-bindings.ts +16 -12
  21. package/src/python-packaging.ts +177 -0
  22. package/src/release-options.ts +2 -2
  23. package/src/release.ts +3 -3
  24. package/src/shared-core-usage.ts +17 -4
  25. package/src/tags.ts +10 -9
  26. package/src/watch.ts +7 -9
  27. package/src/workspace-lock.ts +2 -2
  28. package/src/workspace-manifest.ts +2 -2
  29. package/tasks/appkit-deploy.ts +35 -15
  30. package/tasks/barrels.ts +6 -9
  31. package/tasks/bump.ts +1 -1
  32. package/tasks/clean.ts +3 -3
  33. package/tasks/cli.ts +3 -3
  34. package/tasks/compile-workspace.ts +8 -8
  35. package/tasks/dev-watch.ts +11 -11
  36. package/tasks/local-publish.ts +6 -6
  37. package/tasks/publish-npm.ts +11 -319
  38. package/tasks/publish-python.ts +2 -180
  39. package/tasks/publish.ts +6 -11
  40. package/tasks/python-node-bindings.ts +2 -2
  41. package/tasks/python-registry.ts +6 -6
  42. package/tasks/python-sync.ts +2 -3
  43. package/tasks/release-version.ts +1 -1
  44. package/tasks/release.ts +11 -11
  45. package/tasks/sync.ts +2 -2
  46. package/tasks/test-workspace.ts +9 -9
  47. package/tasks/verify-publications.ts +9 -5
  48. package/tasks/version-check.ts +5 -5
  49. /package/{tasks → src}/python-release.ts +0 -0
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /** Deploy an AppKit workspace through npm-compatible temporary manifests. */
3
- import { existsSync, rmSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { profile } from "@dbx-tools/auth";
3
+ import { cpSync, existsSync, rmSync } from "node:fs";
4
+ import { isAbsolute, join, relative, resolve } from "node:path";
5
+ import { resolveProfile } from "@dbx-tools/auth";
6
6
  import { serializeArgs } from "@dbx-tools/cli-args";
7
- import * as exec from "@dbx-tools/core/exec";
8
- import { errorUtils, log } from "@dbx-tools/shared-core";
7
+ import { spawn, spawnSync } from "@dbx-tools/core";
8
+ import { log, toError } from "@dbx-tools/shared-core";
9
9
  import { z } from "zod";
10
10
  import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
11
11
  import { withStagedWorkspaceManifests } from "../src/workspace-manifest.ts";
@@ -33,6 +33,13 @@ export const AppKitDeployContextOptionsSchema = z.object({
33
33
  });
34
34
 
35
35
  export const AppKitDeployOptionsSchema = AppKitDeployContextOptionsSchema.extend({
36
+ directory: z
37
+ .string()
38
+ .trim()
39
+ .min(1)
40
+ .default(".")
41
+ .describe("Application directory relative to the workspace root")
42
+ .meta({ env: [] }),
36
43
  autoApprove: z
37
44
  .boolean()
38
45
  .default(false)
@@ -81,6 +88,17 @@ export async function deployApp(
81
88
  ): Promise<void> {
82
89
  const root = dependencies.root ?? process.cwd();
83
90
  const options = await resolveDeployOptions(args, dependencies);
91
+ const application = resolve(root, options.directory);
92
+ const applicationPath = relative(root, application);
93
+ if (!applicationPath || applicationPath.startsWith("..") || isAbsolute(applicationPath)) {
94
+ if (options.directory !== ".") {
95
+ throw new Error("Application directory must be inside the workspace root");
96
+ }
97
+ }
98
+ const clientDist = join(application, "client", "dist");
99
+ if (!existsSync(clientDist)) {
100
+ throw new Error(`Built client is missing: ${clientDist}`);
101
+ }
84
102
  const context = AppKitDeployContextOptionsSchema.parse({
85
103
  target: options.target,
86
104
  profile: options.profile,
@@ -99,7 +117,7 @@ export async function deployApp(
99
117
  (dependencies.run
100
118
  ? undefined
101
119
  : (validateArgs: readonly string[], cwd: string) =>
102
- exec.spawn(
120
+ spawn(
103
121
  "databricks",
104
122
  ["bundle", "validate", ...serializeArgs({ strict: true }), ...validateArgs],
105
123
  {
@@ -111,7 +129,7 @@ export async function deployApp(
111
129
  },
112
130
  ));
113
131
  if (validate) {
114
- const validation = await validate(contextArgs, root);
132
+ const validation = await validate(contextArgs, application);
115
133
  if (validation.exitCode !== 0) {
116
134
  throw new Error(`databricks bundle validate exited with code ${validation.exitCode}`);
117
135
  }
@@ -119,7 +137,7 @@ export async function deployApp(
119
137
  const run =
120
138
  dependencies.run ??
121
139
  ((deployArgs: readonly string[], cwd: string) =>
122
- exec.spawn("databricks", ["apps", "deploy", ...deployArgs], {
140
+ spawn("databricks", ["apps", "deploy", ...deployArgs], {
123
141
  cwd,
124
142
  env: dependencies.environment ?? process.env,
125
143
  stdin: "inherit",
@@ -127,10 +145,12 @@ export async function deployApp(
127
145
  stderr: "inherit",
128
146
  }));
129
147
  await withStagedWorkspaceManifests(root, async (stage) => {
130
- await reconcileStagedBundleState(stage, contextArgs, options.target, dependencies);
131
- const result = await run(deployArgs, stage);
148
+ const stagedApplication = resolve(stage, options.directory);
149
+ cpSync(clientDist, join(stagedApplication, "client", "dist"), { recursive: true });
150
+ await reconcileStagedBundleState(stagedApplication, contextArgs, options.target, dependencies);
151
+ const result = await run(deployArgs, stagedApplication);
132
152
  if (result.exitCode !== 0) {
133
- await printAppLogs(contextArgs, stage, dependencies);
153
+ await printAppLogs(contextArgs, stagedApplication, dependencies);
134
154
  throw new Error(`databricks apps deploy exited with code ${result.exitCode}`);
135
155
  }
136
156
  });
@@ -148,7 +168,7 @@ async function reconcileStagedBundleState(
148
168
  ? undefined
149
169
  : (args: readonly string[], cwd: string) =>
150
170
  Promise.resolve(
151
- exec.spawnSync(
171
+ spawnSync(
152
172
  "databricks",
153
173
  ["bundle", "plan", ...serializeArgs({ quiet: true }), ...args],
154
174
  {
@@ -186,7 +206,7 @@ async function printAppLogs(
186
206
  const showLogs =
187
207
  dependencies.logs ??
188
208
  ((args: readonly string[], logsCwd: string) =>
189
- exec.spawn("databricks", ["apps", "logs", ...serializeArgs({ tailLines: 200 }), ...args], {
209
+ spawn("databricks", ["apps", "logs", ...serializeArgs({ tailLines: 200 }), ...args], {
190
210
  cwd: logsCwd,
191
211
  env: dependencies.environment ?? process.env,
192
212
  stdin: "ignore",
@@ -200,7 +220,7 @@ async function printAppLogs(
200
220
  }
201
221
  } catch (error) {
202
222
  logger.warn("Could not read Databricks App logs", {
203
- message: errorUtils.toError(error).message,
223
+ error: toError(error),
204
224
  });
205
225
  }
206
226
  }
@@ -229,7 +249,7 @@ async function resolveDeployOptions(
229
249
  configuredProfile ??
230
250
  (hasEnvironmentAuth
231
251
  ? undefined
232
- : (dependencies.resolveProfile ?? (() => profile.resolveProfile()?.name))());
252
+ : (dependencies.resolveProfile ?? (() => resolveProfile()?.name))());
233
253
  if (!hasEnvironmentAuth && !resolvedProfile) {
234
254
  throw new Error(
235
255
  "Select a Databricks profile with --profile or configure Databricks environment credentials",
package/tasks/barrels.ts CHANGED
@@ -1,7 +1,7 @@
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 { log, object, stringUtils } from "@dbx-tools/shared-core";
4
+ import { log, type Sequence, sequence, pluralize } from "@dbx-tools/shared-core";
5
5
  import { z } from "zod";
6
6
  import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
7
7
  import { TaskDirectoriesOptionSchema, TaskWatchOptionSchema } from "./options.ts";
@@ -19,10 +19,9 @@ function ownerPackageDir(
19
19
  return packages.find(({ dir }) => abs === dir || abs.startsWith(dir + sep))?.dir;
20
20
  }
21
21
 
22
- function changedBarrelTargets(changed: readonly string[]): object.Sequence<string> {
22
+ function changedBarrelTargets(changed: readonly string[]): Sequence<string> {
23
23
  const packages = recordedPackages();
24
- return object
25
- .sequence(changed)
24
+ return sequence(changed)
26
25
  .map((path) => ownerPackageDir(path, packages))
27
26
  .nonNull()
28
27
  .distinct();
@@ -31,7 +30,7 @@ function changedBarrelTargets(changed: readonly string[]): object.Sequence<strin
31
30
  function warnUnownedChanges(changed: readonly string[]): void {
32
31
  if (changed.length) {
33
32
  logger.warn(
34
- `no recorded package owns ${stringUtils.pluralize(changed.length, "change")}; ` +
33
+ `no recorded package owns ${pluralize(changed.length, "change")}; ` +
35
34
  "run `bun run default` (or touch .projenrc.ts) to pick up a new package folder",
36
35
  );
37
36
  }
@@ -62,7 +61,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
62
61
  watchRoots(),
63
62
  (changed) => {
64
63
  const n = generateBarrels({ dirs: [...changedBarrelTargets(changed)] });
65
- if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
64
+ if (n) logger.success(`rebuilt ${pluralize(n, "barrel")}`);
66
65
  },
67
66
  {
68
67
  check: (changed) => {
@@ -80,9 +79,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
80
79
  } else {
81
80
  const dirs = values.dir;
82
81
  const n = generateBarrels(dirs.length ? { dirs } : undefined);
83
- logger.success(
84
- n === 0 ? "barrels already up to date" : `updated ${stringUtils.pluralize(n, "barrel")}`,
85
- );
82
+ logger.success(n === 0 ? "barrels already up to date" : `updated ${pluralize(n, "barrel")}`);
86
83
  }
87
84
  }
88
85
 
package/tasks/bump.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * generated version surface. This task has no git, registry, or publication
5
5
  * side effects; release preparation owns those operations.
6
6
  */
7
- import * as projectUtils from "@dbx-tools/core/project-utils";
7
+ import { projectUtils } from "@dbx-tools/core";
8
8
  import { log } from "@dbx-tools/shared-core";
9
9
  import { z } from "zod";
10
10
  import { runTaskMain, taskCommand, taskOptions, taskRoot } from "./cli.ts";
package/tasks/clean.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /** Interactively remove generated files and installed dependency directories. */
3
3
  import { relative } from "node:path";
4
- import { log, stringUtils } from "@dbx-tools/shared-core";
4
+ import { log, pluralize } from "@dbx-tools/shared-core";
5
5
  import { z } from "zod";
6
6
  import { runTaskMain, taskCommand, taskOptions } from "./cli.ts";
7
7
  import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/clean.ts";
@@ -43,7 +43,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
43
43
  if (yes) {
44
44
  const n = removePaths(targets);
45
45
  logger.success(
46
- `removed ${stringUtils.pluralize(n, "path")} (${files.length} generated + ${nodeModules.length} node_modules) - ${regenHint(nodeModules.length > 0)}`,
46
+ `removed ${pluralize(n, "path")} (${files.length} generated + ${nodeModules.length} node_modules) - ${regenHint(nodeModules.length > 0)}`,
47
47
  );
48
48
  process.exit(0);
49
49
  }
@@ -83,7 +83,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
83
83
 
84
84
  const removedNodeModules = picked.some((p) => nodeModules.includes(p));
85
85
  const n = removePaths(picked);
86
- clack.outro(`removed ${stringUtils.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
86
+ clack.outro(`removed ${pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
87
87
  }
88
88
 
89
89
  await runTaskMain(import.meta, main);
package/tasks/cli.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  import { basename, extname, resolve } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
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";
5
+ import { projectUtils } from "@dbx-tools/core";
6
+ import { tokenize } from "@dbx-tools/shared-core";
7
7
  import { Command } from "commander";
8
8
  import { z } from "zod";
9
9
 
@@ -15,7 +15,7 @@ export function taskName(moduleUrl: string): string {
15
15
 
16
16
  /** Config resolution shared by task schemas. */
17
17
  export function taskCliArgsOptions(moduleUrl: string): CliArgsOptions {
18
- const prefix = [...stringUtils.tokenize(taskName(moduleUrl))].join("_").toUpperCase();
18
+ const prefix = [...tokenize(taskName(moduleUrl))].join("_").toUpperCase();
19
19
  return {
20
20
  cwd: process.cwd(),
21
21
  prefix: `PROJEN_${prefix}`,
@@ -3,8 +3,8 @@
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { availableParallelism } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
- import * as exec from "@dbx-tools/core/exec";
7
- import { errorUtils, log } from "@dbx-tools/shared-core";
6
+ import { type ExecResult, spawn } from "@dbx-tools/core";
7
+ import { log, toError } from "@dbx-tools/shared-core";
8
8
  import type { TaskSpec, TasksManifest } from "projen";
9
9
  import { runTaskMain } from "./cli.ts";
10
10
 
@@ -80,7 +80,7 @@ const emoji = /[\p{Extended_Pictographic}\uFE0F\u200D]/gu;
80
80
  export async function main(): Promise<void> {
81
81
  const root = process.cwd();
82
82
  const plan = compilePlan(root);
83
- const jobs: Array<{ name: string; result: Promise<exec.ExecResult> }> = [];
83
+ const jobs: Array<{ name: string; result: Promise<ExecResult> }> = [];
84
84
  if (plan.typescriptConfigs.length > 0) {
85
85
  const batchCount = Math.min(4, availableParallelism(), plan.typescriptConfigs.length);
86
86
  const batches = Array.from({ length: batchCount }, () => [] as string[]);
@@ -91,7 +91,7 @@ export async function main(): Promise<void> {
91
91
  for (const [index, batch] of batches.entries()) {
92
92
  jobs.push({
93
93
  name: `TypeScript batch ${index + 1}`,
94
- result: exec.spawn("bunx", ["tsc", "--build", ...batch], {
94
+ result: spawn("bunx", ["tsc", "--build", ...batch], {
95
95
  cwd: root,
96
96
  stdin: "ignore",
97
97
  stdout: "inherit",
@@ -111,7 +111,7 @@ export async function main(): Promise<void> {
111
111
  for (const [index, result] of results.entries()) {
112
112
  if (result.status === "rejected") {
113
113
  logger.error(`${jobs[index]!.name} compile failed`, {
114
- error: errorUtils.toError(result.reason),
114
+ error: toError(result.reason),
115
115
  });
116
116
  failed = true;
117
117
  } else if (result.value.exitCode !== 0) {
@@ -125,10 +125,10 @@ export async function main(): Promise<void> {
125
125
  async function compileCustomPackage(pkg: {
126
126
  readonly name: string;
127
127
  readonly directory: string;
128
- }): Promise<exec.ExecResult> {
129
- let result: exec.ExecResult | undefined;
128
+ }): Promise<ExecResult> {
129
+ let result: ExecResult | undefined;
130
130
  for (const task of ["pre-compile", "compile", "post-compile"]) {
131
- result = await exec.spawn("bun", ["run", task], {
131
+ result = await spawn("bun", ["run", task], {
132
132
  cwd: pkg.directory,
133
133
  stdin: "ignore",
134
134
  stdout: (line) => logger.info(`${pkg.name}: ${line.replace(emoji, "")}`),
@@ -2,9 +2,9 @@
2
2
  /** Restart a development command after relevant workspace changes settle. */
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
4
  import { serializeArgs } from "@dbx-tools/cli-args";
5
- import * as exec from "@dbx-tools/core/exec";
6
- import { watch as pathWatch } from "@dbx-tools/path";
7
- import { asyncUtils, errorUtils, log } from "@dbx-tools/shared-core";
5
+ import { spawn, type ChildProcessResult } from "@dbx-tools/core";
6
+ import { watchFiles } from "@dbx-tools/path";
7
+ import { log, sleep, toError } from "@dbx-tools/shared-core";
8
8
  import { CommanderError } from "commander";
9
9
  import { z } from "zod";
10
10
  import { parsedTaskOptions, runTaskMain, taskCommand, taskPositionals } from "./cli.ts";
@@ -232,7 +232,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
232
232
  if (options.watchDisable) {
233
233
  logger.info(`watch disabled; running ${commandLabel}`);
234
234
  const [command, ...commandArgs] = options.command;
235
- const { exitCode } = await exec.spawn(command!, commandArgs, {
235
+ const { exitCode } = await spawn(command!, commandArgs, {
236
236
  cwd,
237
237
  stdin: "inherit",
238
238
  stdout: "inherit",
@@ -250,13 +250,13 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
250
250
  options.watchInclude,
251
251
  options.watchExclude,
252
252
  );
253
- let child: exec.ChildProcessResult | undefined;
253
+ let child: ChildProcessResult | undefined;
254
254
  let timer: ReturnType<typeof setTimeout> | undefined;
255
255
  let restartReason: string | undefined;
256
256
  let restarting = false;
257
257
  let stopping = false;
258
258
 
259
- const watcher = await pathWatch.watchFiles([...watchPaths], {
259
+ const watcher = await watchFiles([...watchPaths], {
260
260
  cwd: root,
261
261
  ignore: watchIgnore.length > 0 ? [...watchIgnore] : undefined,
262
262
  ignoreInitial: true,
@@ -276,7 +276,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
276
276
  });
277
277
  } catch (err) {
278
278
  logger.error("failed to stop command process tree", {
279
- error: errorUtils.toError(err),
279
+ error: toError(err),
280
280
  });
281
281
  }
282
282
  }
@@ -284,7 +284,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
284
284
  function startChild(reason: string): void {
285
285
  logger.info(`${reason}; starting ${commandLabel}`);
286
286
  const [command, ...commandArgs] = options.command;
287
- const current = exec.spawn(command!, commandArgs, {
287
+ const current = spawn(command!, commandArgs, {
288
288
  cwd,
289
289
  stdin: "ignore",
290
290
  stdout: "inherit",
@@ -304,7 +304,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
304
304
  .catch((err) => {
305
305
  if (child !== current || stopping) return;
306
306
  child = undefined;
307
- logger.error("command failed", { error: errorUtils.toError(err) });
307
+ logger.error("command failed", { error: toError(err) });
308
308
  });
309
309
  }
310
310
 
@@ -337,7 +337,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
337
337
  }
338
338
 
339
339
  watcher.on("all", scheduleRestart);
340
- watcher.on("error", (err) => logger.error("watcher error", { error: errorUtils.toError(err) }));
340
+ watcher.on("error", (err) => logger.error("watcher error", { error: toError(err) }));
341
341
  watcher.on("ready", () => {
342
342
  const count = watchPaths.length;
343
343
  const source = options.watchInclude.length > 0 ? "include glob" : "workspace directory";
@@ -380,7 +380,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
380
380
  restoreStdin();
381
381
  await Promise.race([
382
382
  Promise.allSettled([watcher.close(), stopChild()]),
383
- asyncUtils.sleep(SHUTDOWN_TIMEOUT_MS),
383
+ sleep(SHUTDOWN_TIMEOUT_MS),
384
384
  ]);
385
385
  process.exit(exitCode);
386
386
  }
@@ -1,9 +1,9 @@
1
1
  /** Build and publish the current workspace to configured loopback registries. */
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import type { ChildProcessResult } from "@dbx-tools/core/exec";
5
- import * as projectUtils from "@dbx-tools/core/project-utils";
6
- import { log, net } from "@dbx-tools/shared-core";
4
+ import type { ChildProcessResult } from "@dbx-tools/core";
5
+ import { npmRegistry } from "@dbx-tools/core";
6
+ import { log, isLoopbackHost } from "@dbx-tools/shared-core";
7
7
  import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
8
8
  import { spawnTaskCommand } from "../src/_task-command.ts";
9
9
 
@@ -22,8 +22,8 @@ export function resolveLocalRegistry(value: string): string | undefined {
22
22
  const trimmed = value.trim();
23
23
  if (!trimmed || trimmed.toLowerCase() === "false") return undefined;
24
24
  if (trimmed.toLowerCase() === "auto") {
25
- const registry = projectUtils.npmRegistry();
26
- return registry && net.isLoopbackHost(registry) ? registry.href : undefined;
25
+ const registry = npmRegistry();
26
+ return registry && isLoopbackHost(registry) ? registry.href : undefined;
27
27
  }
28
28
  return trimmed;
29
29
  }
@@ -69,7 +69,7 @@ export async function publishLocalRelease(
69
69
 
70
70
  if (
71
71
  options.localPypi.toLowerCase() === "auto" &&
72
- activeIndexes.some((index) => net.isLoopbackHost(index)) &&
72
+ activeIndexes.some((index) => isLoopbackHost(index)) &&
73
73
  !localPypi
74
74
  ) {
75
75
  logger.info(