@dbx-tools/projen 0.9.5 → 0.9.6

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 (47) hide show
  1. package/README.md +64 -16
  2. package/index.ts +7 -5
  3. package/package.json +9 -6
  4. package/src/_rust-project.ts +356 -0
  5. package/src/_rust-release-workflow.ts +901 -0
  6. package/src/_rust-release.ts +605 -0
  7. package/src/_task-command.ts +89 -0
  8. package/src/barrels.ts +9 -7
  9. package/src/engine-root.ts +5 -5
  10. package/src/openapi.ts +1 -1
  11. package/src/packages.ts +9 -9
  12. package/src/pnpm-workspace.ts +1 -1
  13. package/src/project-js.ts +19 -25
  14. package/src/project-py.ts +6 -6
  15. package/src/project-rs.ts +100 -1098
  16. package/src/projen-version.ts +9 -0
  17. package/src/publish.ts +3 -4
  18. package/src/release-catalog.ts +1 -1
  19. package/src/release.ts +11 -12
  20. package/src/scaffold.ts +1 -1
  21. package/src/uniffi.ts +7 -0
  22. package/src/watch.ts +2 -2
  23. package/src/workspace-version.ts +20 -15
  24. package/tasks/barrels.ts +4 -4
  25. package/tasks/build-rust-release.ts +68 -0
  26. package/tasks/bump.ts +3 -2
  27. package/tasks/clean.ts +3 -3
  28. package/tasks/local-publish.ts +21 -3
  29. package/tasks/openapi.ts +2 -2
  30. package/tasks/publish-npm.ts +12 -6
  31. package/tasks/publish-python.ts +2 -2
  32. package/tasks/publish-uniffi-local.ts +11 -15
  33. package/tasks/publish.ts +88 -88
  34. package/tasks/python-registry.ts +1 -1
  35. package/tasks/release-bootstrap.ts +2 -2
  36. package/tasks/release-plan.ts +1 -1
  37. package/tasks/release-please.ts +1 -1
  38. package/tasks/release-pr.ts +60 -115
  39. package/tasks/release-request.ts +8 -29
  40. package/tasks/release-summary-units.ts +2 -9
  41. package/tasks/release-summary.ts +35 -24
  42. package/tasks/release-version.ts +44 -0
  43. package/tasks/rust-release.d.ts +41 -0
  44. package/tasks/rust-release.mjs +1717 -0
  45. package/tasks/uniffi-python.js +1 -1
  46. package/tasks/uniffi.ts +7 -1
  47. package/tasks/version-check.ts +6 -5
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Co-tested Projen release used by the engine and every generated project.
3
+ *
4
+ * The engine exposes Projen as a peer dependency so consumers and generated
5
+ * projects share one runtime instance. Keep the peer range and generated
6
+ * development dependency on this single value; duplicate Projen instances can
7
+ * break its remaining `instanceof`-based file detection.
8
+ */
9
+ export const PROJEN_VERSION = "^0.103.27";
package/src/publish.ts CHANGED
@@ -148,10 +148,9 @@ export function applyCompiledPublish(pkg: javascript.NodeProject): void {
148
148
  const config = publishConfig(pkg);
149
149
  if (config) pkg.package.addField("publishConfig", config);
150
150
 
151
- // The release workflow publishes straight after `bun install`, with no build
152
- // in between, so the compiled output has to be produced by the pack itself
153
- // rather than assumed present. This also covers a bare `pnpm pack` and the
154
- // bump task's local-registry publish.
151
+ // The workspace publisher compiles every selected package once and disables
152
+ // lifecycle scripts while packing. Keep `prepack` for a standalone package
153
+ // publish that did not pass through that workspace-level build.
155
154
  if (!pkg.tasks.tryFind("prepack")) {
156
155
  pkg.addTask("prepack", { description: "Compile before packing the published tarball" });
157
156
  }
@@ -9,8 +9,8 @@
9
9
  * @module
10
10
  */
11
11
 
12
- import { createHash } from "node:crypto";
13
12
  import { execFileSync } from "node:child_process";
13
+ import { createHash } from "node:crypto";
14
14
  import { existsSync, readFileSync, readdirSync } from "node:fs";
15
15
  import { basename, join, relative, resolve } from "node:path";
16
16
  import { Component, JsonFile, type Project } from "projen";
package/src/release.ts CHANGED
@@ -188,6 +188,12 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
188
188
  "fetch-depth": 2,
189
189
  },
190
190
  },
191
+ {
192
+ name: "Setup Bun",
193
+ uses: "oven-sh/setup-bun@v2",
194
+ with: { "bun-version": BUN_VERSION },
195
+ },
196
+ { name: "Install release validation dependencies", run: "bun install --frozen-lockfile" },
191
197
  {
192
198
  name: "Verify release context",
193
199
  id: "release",
@@ -206,7 +212,7 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
206
212
  ' RELEASE_SHA="$GITHUB_SHA"',
207
213
  ' test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
208
214
  " RELEASE_VERSION=\"$(tr -d '\\r\\n' < VERSION)\"",
209
- ' [[ "$RELEASE_VERSION" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]',
215
+ ' bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"',
210
216
  ' PREVIOUS_VERSION="$(git show "$RELEASE_SHA^:VERSION" | tr -d \'\\r\\n\')"',
211
217
  ' test "$PREVIOUS_VERSION" != "$RELEASE_VERSION"',
212
218
  ` RELEASE_TAG="${tagPrefix}$RELEASE_VERSION"`,
@@ -216,16 +222,7 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
216
222
  ' test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$RELEASE_SHA"',
217
223
  " else",
218
224
  " git fetch --force --tags origin",
219
- ' LATEST_VERSION=""',
220
- ` for TAG in $(git tag --list '${tagPrefix}*' '*-${tagPrefix}*'); do`,
221
- ` if [[ "$TAG" == ${tagPrefix}* ]]; then CANDIDATE="\${TAG#${tagPrefix}}"; else CANDIDATE="\${TAG##*-${tagPrefix}}"; fi`,
222
- ' if [[ "$CANDIDATE" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then',
223
- ' if [ -z "$LATEST_VERSION" ] || node -e \'const a=process.argv[1].split(".").map(Number); const b=process.argv[2].split(".").map(Number); process.exit(b[0]>a[0] || b[0]===a[0] && (b[1]>a[1] || b[1]===a[1] && b[2]>a[2]) ? 0 : 1)\' "$LATEST_VERSION" "$CANDIDATE"; then LATEST_VERSION="$CANDIDATE"; fi',
224
- " fi",
225
- " done",
226
- ' if [ -n "$LATEST_VERSION" ]; then',
227
- ' node -e \'const a=process.argv[1].split(".").map(Number); const b=process.argv[2].split(".").map(Number); if (!(b[0]>a[0] || b[0]===a[0] && (b[1]>a[1] || b[1]===a[1] && b[2]>a[2]))) process.exit(1)\' "$LATEST_VERSION" "$RELEASE_VERSION"',
228
- " fi",
225
+ ` bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION" --prefix ${JSON.stringify(tagPrefix)} --assert-next`,
229
226
  ' git config user.name "github-actions[bot]"',
230
227
  ' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
231
228
  ' git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"',
@@ -233,6 +230,8 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
233
230
  " fi",
234
231
  "else",
235
232
  ` case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac`,
233
+ ` RELEASE_VERSION="\${RELEASE_TAG#${tagPrefix}}"`,
234
+ ' bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"',
236
235
  ' git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
237
236
  ' test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
238
237
  ' RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"',
@@ -245,7 +244,7 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
245
244
  "fi",
246
245
  'echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"',
247
246
  'echo "expected_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"',
248
- `echo "release_version=\${RELEASE_TAG#${tagPrefix}}" >> "$GITHUB_OUTPUT"`,
247
+ 'echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"',
249
248
  ].join("\n"),
250
249
  },
251
250
  {
package/src/scaffold.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Runs a projen re-synth, for the `sync` task and its watchers.
3
3
  */
4
4
  import { join } from "node:path";
5
- import { exec } from "@dbx-tools/core";
5
+ import * as exec from "@dbx-tools/core/exec";
6
6
  import { repoRoot } from "./packages.ts";
7
7
 
8
8
  /**
package/src/uniffi.ts CHANGED
@@ -31,6 +31,13 @@ export const makeDefaultedInterfaceParametersOptional = (source: string): string
31
31
  );
32
32
  };
33
33
 
34
+ /** Remove UBRN's obsolete `*Interface` aliases once the `*Like` owner exists. */
35
+ export const removeObsoleteInterfaceAliases = (source: string): string =>
36
+ source.replace(
37
+ /\n\/\*\*\n \* @deprecated Use `\w+Like` instead\.\n \*\/\nexport type \w+Interface = \w+Like;\n/g,
38
+ "\n",
39
+ );
40
+
34
41
  export interface TypeScriptBindingModule {
35
42
  readonly specifier: string;
36
43
  readonly source: string;
package/src/watch.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { isAbsolute, resolve } from "node:path";
19
19
  import { watch as pathWatch } from "@dbx-tools/path";
20
- import { async, log } from "@dbx-tools/shared-core";
20
+ import { asyncUtils, log } from "@dbx-tools/shared-core";
21
21
  import { isGeneratedFile, recordedRoots, repoRoot } from "./packages.ts";
22
22
 
23
23
  const logger = log.logger("projen:watch");
@@ -132,7 +132,7 @@ export function watchLoop(
132
132
  if (closing) return;
133
133
  closing = true;
134
134
  clearTimeout(timer);
135
- await Promise.race([watcher.close().catch(() => {}), async.sleep(CLOSE_GRACE_MS)]);
135
+ await Promise.race([watcher.close().catch(() => {}), asyncUtils.sleep(CLOSE_GRACE_MS)]);
136
136
  process.exit(code);
137
137
  }
138
138
 
@@ -17,7 +17,8 @@
17
17
  */
18
18
  import { chmodSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
- import { exec } from "@dbx-tools/core";
20
+ import { parse as parseVersion } from "semver";
21
+ import { captureTaskCommand } from "./_task-command.ts";
21
22
 
22
23
  /** Name of the repo-root file holding the workspace version. */
23
24
  export const VERSION_FILE = "VERSION";
@@ -33,10 +34,20 @@ export type Semver = [number, number, number];
33
34
  /** Supported semantic release increments. */
34
35
  export type VersionLevel = "patch" | "minor" | "major";
35
36
 
36
- /** Parse `x.y.z` (ignoring any leading `v`/prefix), or `undefined` when it does not match. */
37
+ /** Parse an exact stable `x.y.z`, or `undefined` for prefixes, prereleases, or embedded versions. */
37
38
  export function parseSemver(raw: string): Semver | undefined {
38
- const m = /(\d+)\.(\d+)\.(\d+)/.exec(raw.trim());
39
- return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : undefined;
39
+ const value = raw.trim();
40
+ if (!SEMVER.test(value)) return undefined;
41
+ const parsed = parseVersion(value);
42
+ if (
43
+ !parsed ||
44
+ parsed.version !== value ||
45
+ parsed.prerelease.length > 0 ||
46
+ parsed.build.length > 0
47
+ ) {
48
+ return undefined;
49
+ }
50
+ return [parsed.major, parsed.minor, parsed.patch];
40
51
  }
41
52
 
42
53
  /** Ordering comparator: negative when `a < b`, positive when `a > b`, zero when equal. */
@@ -66,7 +77,7 @@ export function readWorkspaceVersion(root: string): string {
66
77
  const path = versionPath(root);
67
78
  if (!existsSync(path)) return DEFAULT_VERSION;
68
79
  const raw = readFileSync(path, "utf8").trim();
69
- if (!SEMVER.test(raw)) {
80
+ if (!parseSemver(raw)) {
70
81
  throw new Error(`${VERSION_FILE} must contain an x.y.z version, got ${JSON.stringify(raw)}`);
71
82
  }
72
83
  return raw;
@@ -74,7 +85,7 @@ export function readWorkspaceVersion(root: string): string {
74
85
 
75
86
  /** Write the workspace version to `<root>/VERSION`. Only `bump` and bootstrap call this. */
76
87
  export function writeWorkspaceVersion(root: string, version: string): void {
77
- if (!SEMVER.test(version)) {
88
+ if (!parseSemver(version)) {
78
89
  throw new Error(`workspace version must be x.y.z, got ${JSON.stringify(version)}`);
79
90
  }
80
91
  writeFileSync(versionPath(root), `${version}\n`);
@@ -107,14 +118,7 @@ export function syncWorkspaceManifestVersion(manifestPath: string, version: stri
107
118
  /** Run git in `cwd`, capturing stdout and swallowing failure (offline, no repo). */
108
119
  function gitCapture(cwd: string, args: string[]): string {
109
120
  try {
110
- const res = exec.spawnSync("git", args, {
111
- cwd,
112
- stdout: "capture",
113
- stderr: "ignore",
114
- stdin: "ignore",
115
- check: false,
116
- });
117
- return res.stdout?.trim() ?? "";
121
+ return captureTaskCommand(cwd, "git", args);
118
122
  } catch {
119
123
  return "";
120
124
  }
@@ -131,7 +135,8 @@ export function latestTagVersion(cwd: string, prefix: string): Semver | undefine
131
135
  `${prefix}*`,
132
136
  ]);
133
137
  for (const tag of out.split("\n")) {
134
- const v = parseSemver(tag.replace(prefix, ""));
138
+ if (!tag.startsWith(prefix)) continue;
139
+ const v = parseSemver(tag.slice(prefix.length));
135
140
  if (v) return v;
136
141
  }
137
142
  return undefined;
package/tasks/barrels.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env -S bun
2
2
  import { sep } from "node:path";
3
3
  import { parseArgs } from "node:util";
4
- import { log, string } from "@dbx-tools/shared-core";
4
+ import { log, stringUtils } from "@dbx-tools/shared-core";
5
5
  import { generateBarrels } from "../src/barrels.ts";
6
6
  import { recordedPackages } from "../src/packages.ts";
7
7
  import { watchLoop, watchRoots } from "../src/watch.ts";
@@ -43,14 +43,14 @@ if (values.watch) {
43
43
  if (dirs.size === 0) {
44
44
  if (unowned.length) {
45
45
  logger.warn(
46
- `no recorded package owns ${string.pluralize(unowned.length, "change")}; ` +
46
+ `no recorded package owns ${stringUtils.pluralize(unowned.length, "change")}; ` +
47
47
  "run `bun run default` (or touch .projenrc.ts) to pick up a new package folder",
48
48
  );
49
49
  }
50
50
  return;
51
51
  }
52
52
  const n = generateBarrels({ dirs: [...dirs] });
53
- if (n) logger.success(`rebuilt ${string.pluralize(n, "barrel")}`);
53
+ if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
54
54
  });
55
55
  } else {
56
56
  const dirs = Array.isArray(values.dir)
@@ -58,6 +58,6 @@ if (values.watch) {
58
58
  : [];
59
59
  const n = generateBarrels(dirs.length ? { dirs } : undefined);
60
60
  logger.success(
61
- n === 0 ? "barrels already up to date" : `updated ${string.pluralize(n, "barrel")}`,
61
+ n === 0 ? "barrels already up to date" : `updated ${stringUtils.pluralize(n, "barrel")}`,
62
62
  );
63
63
  }
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env -S bun
2
+ /** Bundle the owning Rust release helper source into a standalone Node module. */
3
+ import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, resolve } from "node:path";
6
+ import * as exec from "@dbx-tools/core/exec";
7
+
8
+ const root = resolve(import.meta.dirname, "..");
9
+ const temporary = mkdtempSync(join(tmpdir(), "dbx-rust-release-bundle-"));
10
+ try {
11
+ const bundled = join(temporary, "rust-release.mjs");
12
+ const declarations = join(temporary, "declarations");
13
+ exec.spawnSync(
14
+ process.execPath,
15
+ [
16
+ "build",
17
+ resolve(root, "src/_rust-release.ts"),
18
+ "--target=node",
19
+ "--format=esm",
20
+ `--outfile=${bundled}`,
21
+ ],
22
+ {
23
+ cwd: root,
24
+ stdout: "inherit",
25
+ stderr: "inherit",
26
+ stdin: "ignore",
27
+ check: true,
28
+ },
29
+ );
30
+ const output = resolve(root, "tasks/rust-release.mjs");
31
+ const source = readFileSync(bundled, "utf8");
32
+ writeFileSync(
33
+ output,
34
+ `#!/usr/bin/env node\n// GENERATED from src/_rust-release.ts by tasks/build-rust-release.ts.\n${source.replace(/^#![^\n]*\n/, "")}`,
35
+ );
36
+ chmodSync(output, 0o755);
37
+ exec.spawnSync(
38
+ "bunx",
39
+ [
40
+ "tsc",
41
+ resolve(root, "src/_rust-release.ts"),
42
+ "--declaration",
43
+ "--emitDeclarationOnly",
44
+ "--module",
45
+ "ESNext",
46
+ "--moduleResolution",
47
+ "Bundler",
48
+ "--target",
49
+ "ES2022",
50
+ "--skipLibCheck",
51
+ "--outDir",
52
+ declarations,
53
+ ],
54
+ {
55
+ cwd: root,
56
+ stdout: "inherit",
57
+ stderr: "inherit",
58
+ stdin: "ignore",
59
+ check: true,
60
+ },
61
+ );
62
+ writeFileSync(
63
+ resolve(root, "tasks/rust-release.d.ts"),
64
+ readFileSync(join(declarations, "_rust-release.d.ts")),
65
+ );
66
+ } finally {
67
+ rmSync(temporary, { recursive: true, force: true });
68
+ }
package/tasks/bump.ts CHANGED
@@ -4,7 +4,8 @@
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 { exec, project } from "@dbx-tools/core";
7
+ import * as exec from "@dbx-tools/core/exec";
8
+ import * as projectUtils from "@dbx-tools/core/project-utils";
8
9
  import { log } from "@dbx-tools/shared-core";
9
10
  import { Command } from "commander";
10
11
  import {
@@ -36,7 +37,7 @@ program
36
37
  arch: ReleaseArch[];
37
38
  synth: boolean;
38
39
  }) => {
39
- const root = project.root() ?? process.cwd();
40
+ const root = projectUtils.root() ?? process.cwd();
40
41
  const next = resolveNextVersion(root, [opts.prefix], opts.level);
41
42
  const releasePlatforms = releasePlatformFilter(opts.os, opts.arch);
42
43
 
package/tasks/clean.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env -S bun
2
2
  import { relative } from "node:path";
3
- import { log, string } from "@dbx-tools/shared-core";
3
+ import { log, stringUtils } from "@dbx-tools/shared-core";
4
4
  import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/clean.ts";
5
5
  import { repoRoot, toPosix } from "../src/packages.ts";
6
6
 
@@ -24,7 +24,7 @@ const regenHint = (removedNodeModules: boolean): string =>
24
24
  if (yes) {
25
25
  const n = removePaths(targets);
26
26
  logger.success(
27
- `removed ${string.pluralize(n, "path")} (${files.length} generated + ${nodeModules.length} node_modules) - ${regenHint(nodeModules.length > 0)}`,
27
+ `removed ${stringUtils.pluralize(n, "path")} (${files.length} generated + ${nodeModules.length} node_modules) - ${regenHint(nodeModules.length > 0)}`,
28
28
  );
29
29
  process.exit(0);
30
30
  }
@@ -64,4 +64,4 @@ if (picked.length === 0) {
64
64
 
65
65
  const removedNodeModules = picked.some((p) => nodeModules.includes(p));
66
66
  const n = removePaths(picked);
67
- clack.outro(`removed ${string.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
67
+ clack.outro(`removed ${stringUtils.pluralize(n, "path")} - ${regenHint(removedNodeModules)}`);
@@ -3,7 +3,8 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { exec, project } from "@dbx-tools/core";
6
+ import * as exec from "@dbx-tools/core/exec";
7
+ import * as projectUtils from "@dbx-tools/core/project-utils";
7
8
  import { log, net } from "@dbx-tools/shared-core";
8
9
  import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
9
10
  import { readDbxToolsConfig } from "../src/packages.ts";
@@ -18,13 +19,15 @@ export interface LocalPublishOptions {
18
19
  readonly localPypi: string;
19
20
  readonly pythonRoot?: string;
20
21
  readonly localCargo: boolean;
22
+ /** Reuse the root TypeScript compile that immediately preceded local publication. */
23
+ readonly reuseValidatedNodeCompile?: boolean;
21
24
  }
22
25
 
23
26
  function resolveLocalRegistry(value: string): string | undefined {
24
27
  const trimmed = value.trim();
25
28
  if (!trimmed || trimmed.toLowerCase() === "false") return undefined;
26
29
  if (trimmed.toLowerCase() === "auto") {
27
- const registry = project.npmRegistry();
30
+ const registry = projectUtils.npmRegistry();
28
31
  return registry && net.isLoopbackHost(registry) ? registry.href : undefined;
29
32
  }
30
33
  return trimmed;
@@ -45,6 +48,21 @@ function localCargoRegistry(): string | undefined {
45
48
  return undefined;
46
49
  }
47
50
 
51
+ /** Arguments for the workspace npm publisher used by local release preflight. */
52
+ export function localNodePublishArguments(
53
+ publishScript: string,
54
+ options: Pick<LocalPublishOptions, "version" | "reuseValidatedNodeCompile">,
55
+ registry: string,
56
+ ): string[] {
57
+ return [
58
+ publishScript,
59
+ options.version,
60
+ "--registry",
61
+ registry,
62
+ ...(options.reuseValidatedNodeCompile ? ["--skip-compile"] : []),
63
+ ];
64
+ }
65
+
48
66
  /** Publish the exact release candidate to local npm, PyPI, and Cargo mirrors. */
49
67
  export async function publishLocalRelease(options: LocalPublishOptions): Promise<void> {
50
68
  const localRegistry = resolveLocalRegistry(options.localRegistry);
@@ -68,7 +86,7 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
68
86
  logger.info(`publishing ${options.version} to local registry ${localRegistry}`);
69
87
  localPublishes.push(
70
88
  exec
71
- .spawn(process.execPath, [publishScript, options.version, "--registry", localRegistry], {
89
+ .spawn(process.execPath, localNodePublishArguments(publishScript, options, localRegistry), {
72
90
  cwd: options.root,
73
91
  stdout: "inherit",
74
92
  stderr: "inherit",
package/tasks/openapi.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env -S bun
2
- import { log, string } from "@dbx-tools/shared-core";
2
+ import { log, stringUtils } from "@dbx-tools/shared-core";
3
3
  import { generateBarrels } from "../src/barrels.ts";
4
4
  import { generateOpenapi, isTsoaController } from "../src/openapi.ts";
5
5
  import { runSynth } from "../src/scaffold.ts";
@@ -16,7 +16,7 @@ if (process.argv.includes("--watch")) {
16
16
  const dirs = await generateOpenapi();
17
17
  if (dirs.length) {
18
18
  generateBarrels({ dirs });
19
- logger.success(`regenerated openapi (${string.pluralize(dirs.length, "package")})`);
19
+ logger.success(`regenerated openapi (${stringUtils.pluralize(dirs.length, "package")})`);
20
20
  }
21
21
  });
22
22
  } else {
@@ -12,14 +12,15 @@ import {
12
12
  } from "node:fs";
13
13
  import { tmpdir } from "node:os";
14
14
  import { join, relative, resolve } from "node:path";
15
- import { exec } from "@dbx-tools/core";
15
+ import * as exec from "@dbx-tools/core/exec";
16
16
  import { log } from "@dbx-tools/shared-core";
17
17
  import { Command } from "commander";
18
18
 
19
19
  const DEFAULT_REGISTRY = "https://registry.npmjs.org";
20
- const logger = log.logger("dbx-tools:publish-npm");
20
+ const logger = log.logger("projen:publish-npm");
21
21
 
22
22
  export interface NpmReleaseIdentity {
23
+ readonly access?: "public" | "restricted";
23
24
  readonly contentDigest?: string;
24
25
  readonly integrity?: string;
25
26
  readonly name: string;
@@ -101,7 +102,7 @@ export async function publishedNpmRelease(
101
102
  if (!archive.ok) {
102
103
  throw new Error(`npm tarball lookup failed for ${name}@${version}: ${archive.status}`);
103
104
  }
104
- const temp = mkdtempSync(join(tmpdir(), "dbx-tools-published-npm-"));
105
+ const temp = mkdtempSync(join(tmpdir(), "projen-published-npm-"));
105
106
  const path = join(temp, "package.tgz");
106
107
  try {
107
108
  writeFileSync(path, Buffer.from(await archive.arrayBuffer()));
@@ -132,13 +133,19 @@ export function readNpmArchiveIdentity(path: string): NpmReleaseIdentity {
132
133
  }
133
134
  const manifest = JSON.parse(result.stdout) as {
134
135
  name?: string;
136
+ publishConfig?: { access?: unknown };
135
137
  repository?: unknown;
136
138
  version?: string;
137
139
  };
138
140
  if (!manifest.name || !manifest.version) {
139
141
  throw new Error(`npm archive has no package name or version: ${path}`);
140
142
  }
143
+ const access = manifest.publishConfig?.access;
144
+ if (access !== undefined && access !== "public" && access !== "restricted") {
145
+ throw new Error(`npm archive has invalid publishConfig.access: ${String(access)}`);
146
+ }
141
147
  return {
148
+ ...(access ? { access } : {}),
142
149
  contentDigest: npmArchiveContentDigest(path),
143
150
  integrity: `sha512-${createHash("sha512").update(readFileSync(path)).digest("base64")}`,
144
151
  name: manifest.name,
@@ -149,7 +156,7 @@ export function readNpmArchiveIdentity(path: string): NpmReleaseIdentity {
149
156
 
150
157
  /** Hash paths, executable bits, symlink targets, and bytes while ignoring tar metadata. */
151
158
  export function npmArchiveContentDigest(path: string): string {
152
- const temp = mkdtempSync(join(tmpdir(), "dbx-tools-npm-content-"));
159
+ const temp = mkdtempSync(join(tmpdir(), "projen-npm-content-"));
153
160
  try {
154
161
  exec.spawnSync("tar", ["-xzf", path, "-C", temp], {
155
162
  cwd: process.cwd(),
@@ -237,8 +244,7 @@ export async function publishNpmArchives(options: {
237
244
  [
238
245
  "publish",
239
246
  archive,
240
- "--access",
241
- "public",
247
+ ...(local.access ? ["--access", local.access] : []),
242
248
  ...(options.registry ? ["--registry", options.registry] : []),
243
249
  ...(options.dryRun ? ["--dry-run"] : []),
244
250
  ],
@@ -11,7 +11,7 @@ import {
11
11
  } from "node:fs";
12
12
  import { tmpdir } from "node:os";
13
13
  import { basename, join, resolve } from "node:path";
14
- import { exec } from "@dbx-tools/core";
14
+ import * as exec from "@dbx-tools/core/exec";
15
15
  import { Command } from "commander";
16
16
  import { parse, stringify } from "smol-toml";
17
17
  import { pythonProjectInfo, stampPythonProject } from "./uniffi-python.js";
@@ -111,7 +111,7 @@ export function publishPythonProjects(options: {
111
111
  readonly plan?: ReleasePlan;
112
112
  }): void {
113
113
  const root = resolve(options.root);
114
- const output = mkdtempSync(join(tmpdir(), "dbx-tools-python-publish-"));
114
+ const output = mkdtempSync(join(tmpdir(), "projen-python-publish-"));
115
115
  const planned = new Map(
116
116
  options.plan?.pythonPackages.map((pkg) => [pkg.path.replace(/^.*\//, ""), pkg.version]) ?? [],
117
117
  );
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env -S bun
2
- import { spawnSync } from "node:child_process";
3
2
  import { chmodSync, existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
3
  import { arch, platform } from "node:os";
5
4
  import { dirname, join, resolve } from "node:path";
@@ -7,6 +6,7 @@ import { fileURLToPath } from "node:url";
7
6
  import { parseArgs } from "node:util";
8
7
  import { log } from "@dbx-tools/shared-core";
9
8
  import { parse, stringify } from "smol-toml";
9
+ import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
10
10
  import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
11
11
  import type { RustBindingMapping, RustWorkspaceMapping } from "../src/project-rs.ts";
12
12
 
@@ -22,17 +22,13 @@ const parsed = parseArgs({
22
22
  });
23
23
 
24
24
  function run(command: string, args: string[], capture = false): string {
25
- const result = spawnSync(command, args, {
26
- cwd: repoRoot,
27
- encoding: capture ? "utf8" : undefined,
28
- stdio: capture ? "pipe" : "inherit",
29
- });
30
- if (result.status !== 0) throw new Error(`${command} exited with ${result.status}`);
31
- return capture ? String(result.stdout).trim() : "";
25
+ if (capture) return captureTaskCommand(repoRoot, command, args, { check: true });
26
+ runTaskCommand(repoRoot, command, args);
27
+ return "";
32
28
  }
33
29
 
34
30
  function commandAvailable(command: string): boolean {
35
- return spawnSync(command, ["--version"], { stdio: "ignore" }).status === 0;
31
+ return taskCommandSucceeds(repoRoot, command, ["--version"]);
36
32
  }
37
33
 
38
34
  function rustHost(): string {
@@ -90,12 +86,12 @@ function artifacts(directory: string, suffix: string): string[] {
90
86
  }
91
87
 
92
88
  function cargoVersionExists(crateName: string, version: string, registry: string): boolean {
93
- return (
94
- spawnSync("cargo", ["info", `${crateName}@${version}`, "--registry", registry], {
95
- cwd: repoRoot,
96
- stdio: "ignore",
97
- }).status === 0
98
- );
89
+ return taskCommandSucceeds(repoRoot, "cargo", [
90
+ "info",
91
+ `${crateName}@${version}`,
92
+ "--registry",
93
+ registry,
94
+ ]);
99
95
  }
100
96
 
101
97
  function publishCargo(config: RustWorkspaceMapping, registry: string, version: string): void {