@dbx-tools/projen 0.9.21 → 0.9.29

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 (111) hide show
  1. package/README.md +77 -207
  2. package/index.ts +10 -14
  3. package/package.json +4 -4
  4. package/src/barrels.ts +1 -74
  5. package/src/module-exports.ts +175 -1
  6. package/src/openapi.ts +5 -72
  7. package/src/packages.ts +45 -0
  8. package/src/project-js.ts +3 -6
  9. package/src/project-predicate.ts +1 -2
  10. package/src/project-py.ts +82 -103
  11. package/src/project.ts +2 -2
  12. package/src/python-node-bindings.ts +138 -0
  13. package/src/python-node-bundle.ts +92 -0
  14. package/src/release-catalog.ts +3 -5
  15. package/src/{release-dispatch.ts → release-context.ts} +0 -11
  16. package/src/release.ts +70 -152
  17. package/tasks/bump.ts +12 -21
  18. package/tasks/databricks-cli-assets.ts +129 -0
  19. package/tasks/local-publish.ts +23 -107
  20. package/tasks/publish-npm.ts +10 -1
  21. package/tasks/publish-python.ts +26 -11
  22. package/tasks/publish.ts +12 -48
  23. package/tasks/python-node-bindings-watch.ts +47 -0
  24. package/tasks/python-node-bindings.ts +1017 -0
  25. package/tasks/python-release.ts +74 -0
  26. package/tasks/release.ts +99 -0
  27. package/tasks/sync.ts +13 -18
  28. package/tasks/version-check.ts +1 -28
  29. package/._LICENSE +0 -0
  30. package/._README.md +0 -0
  31. package/._index.ts +0 -0
  32. package/._package.json +0 -0
  33. package/src/.__lazy-require.ts +0 -0
  34. package/src/.__release-platform.ts +0 -0
  35. package/src/.__rust-project.ts +0 -0
  36. package/src/.__rust-release-workflow.ts +0 -0
  37. package/src/.__task-command.ts +0 -0
  38. package/src/._barrels.ts +0 -0
  39. package/src/._bun-app.ts +0 -0
  40. package/src/._bun-workflow.ts +0 -0
  41. package/src/._clean.ts +0 -0
  42. package/src/._codegen.ts +0 -0
  43. package/src/._dbx-tools-config.ts +0 -0
  44. package/src/._engine-root.ts +0 -0
  45. package/src/._generated.ts +0 -0
  46. package/src/._mixin.ts +0 -0
  47. package/src/._module-exports.ts +0 -0
  48. package/src/._openapi.ts +0 -0
  49. package/src/._packages.ts +0 -0
  50. package/src/._pnpm-workspace.ts +0 -0
  51. package/src/._project-js.ts +0 -0
  52. package/src/._project-predicate.ts +0 -0
  53. package/src/._project-py.ts +0 -0
  54. package/src/._project-rs.ts +0 -0
  55. package/src/._project.ts +0 -0
  56. package/src/._projen-version.ts +0 -0
  57. package/src/._publish.ts +0 -0
  58. package/src/._release-catalog.ts +0 -0
  59. package/src/._release-dispatch.ts +0 -0
  60. package/src/._release-github.ts +0 -0
  61. package/src/._release.ts +0 -0
  62. package/src/._scaffold.ts +0 -0
  63. package/src/._tags.ts +0 -0
  64. package/src/._tsconfig.ts +0 -0
  65. package/src/._uniffi.ts +0 -0
  66. package/src/._vscode.ts +0 -0
  67. package/src/._watch.ts +0 -0
  68. package/src/._workspace-lock.ts +0 -0
  69. package/src/._workspace-version.ts +0 -0
  70. package/src/_release-platform.ts +0 -78
  71. package/src/_rust-project.ts +0 -429
  72. package/src/_rust-release-workflow.ts +0 -311
  73. package/src/project-rs.ts +0 -1054
  74. package/src/release-github.ts +0 -109
  75. package/src/uniffi.ts +0 -65
  76. package/tasks/._barrels.ts +0 -0
  77. package/tasks/._bump.ts +0 -0
  78. package/tasks/._clean.ts +0 -0
  79. package/tasks/._compile-workspace.ts +0 -0
  80. package/tasks/._local-publish.ts +0 -0
  81. package/tasks/._openapi.ts +0 -0
  82. package/tasks/._projenrc.ts +0 -0
  83. package/tasks/._publish-npm.ts +0 -0
  84. package/tasks/._publish-python.ts +0 -0
  85. package/tasks/._publish-uniffi-local.ts +0 -0
  86. package/tasks/._publish.ts +0 -0
  87. package/tasks/._python-registry.ts +0 -0
  88. package/tasks/._release-assets.ts +0 -0
  89. package/tasks/._release-candidate.ts +0 -0
  90. package/tasks/._release-manifest.ts +0 -0
  91. package/tasks/._release-pr.ts +0 -0
  92. package/tasks/._release-summary.ts +0 -0
  93. package/tasks/._release-version.ts +0 -0
  94. package/tasks/._rust.ts +0 -0
  95. package/tasks/._sync.ts +0 -0
  96. package/tasks/._uniffi-python.d.ts +0 -0
  97. package/tasks/._uniffi-python.js +0 -0
  98. package/tasks/._uniffi-release.mjs +0 -0
  99. package/tasks/._uniffi.ts +0 -0
  100. package/tasks/._version-check.ts +0 -0
  101. package/tasks/publish-uniffi-local.ts +0 -359
  102. package/tasks/release-assets.ts +0 -467
  103. package/tasks/release-candidate.ts +0 -416
  104. package/tasks/release-manifest.ts +0 -273
  105. package/tasks/release-pr.ts +0 -589
  106. package/tasks/release-summary.ts +0 -379
  107. package/tasks/rust.ts +0 -173
  108. package/tasks/uniffi-python.d.ts +0 -60
  109. package/tasks/uniffi-python.js +0 -178
  110. package/tasks/uniffi-release.mjs +0 -579
  111. package/tasks/uniffi.ts +0 -262
@@ -1,27 +1,20 @@
1
- /** Publish one verified release candidate to configured loopback registries. */
2
- import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
- import { homedir, tmpdir } from "node:os";
4
- import { join, resolve } from "node:path";
1
+ /** Build and publish the current workspace to configured loopback registries. */
2
+ import { resolve } from "node:path";
5
3
  import { fileURLToPath } from "node:url";
6
4
  import * as projectUtils from "@dbx-tools/core/project-utils";
7
5
  import { log, net } from "@dbx-tools/shared-core";
8
6
  import { Command } from "commander";
9
7
  import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
10
- import { verifyReleaseManifest } from "./release-manifest.ts";
11
8
  import { runTaskCommandAsync } from "../src/_task-command.ts";
12
- import { readDbxToolsConfig } from "../src/packages.ts";
13
9
 
14
10
  const logger = log.logger("projen:local-publish");
15
11
 
16
- /** Options for publishing an immutable release candidate to local registries. */
12
+ /** Options for publishing the current workspace to local registries. */
17
13
  export interface LocalPublishOptions {
18
- readonly candidateDirectory: string;
19
- readonly gitSha: string;
20
- readonly localCargo: boolean;
21
14
  readonly localPypi: string;
22
15
  readonly localRegistry: string;
16
+ readonly pythonRoot: string;
23
17
  readonly root: string;
24
- readonly tag: string;
25
18
  readonly version: string;
26
19
  }
27
20
 
@@ -35,43 +28,9 @@ export function resolveLocalRegistry(value: string): string | undefined {
35
28
  return trimmed;
36
29
  }
37
30
 
38
- export function localCargoRegistry(): string | undefined {
39
- if (process.env.LOCAL_CARGO_REGISTRY) return process.env.LOCAL_CARGO_REGISTRY;
40
- const config = join(homedir(), ".cargo", "config.toml");
41
- if (!existsSync(config)) return undefined;
42
- const source = readFileSync(config, "utf8");
43
- const sections = [
44
- ...source.matchAll(/^\[registries\.([^\]]+)\]\s*\n([\s\S]*?)(?=^\[|(?![\s\S]))/gm),
45
- ];
46
- for (const section of sections) {
47
- const index = section[2]?.match(/^\s*index\s*=\s*["']([^"']+)["']/m)?.[1];
48
- if (index && net.isLoopbackHost(index.replace(/^sparse\+/, ""))) return section[1];
49
- }
50
- return undefined;
51
- }
52
-
53
- function hasRustCrates(root: string): boolean {
54
- const rust = readDbxToolsConfig(root)?.rust;
55
- return Boolean(
56
- rust &&
57
- typeof rust === "object" &&
58
- !Array.isArray(rust) &&
59
- Array.isArray((rust as { crates?: unknown }).crates) &&
60
- (rust as { crates: unknown[] }).crates.length > 0,
61
- );
62
- }
63
-
64
- /** Publish the exact candidate archives before they are attached to the draft release. */
31
+ /** Build and publish the current workspace directly to local registries. */
65
32
  export async function publishLocalRelease(options: LocalPublishOptions): Promise<void> {
66
33
  const root = resolve(options.root);
67
- const candidateDirectory = resolve(options.candidateDirectory);
68
- verifyReleaseManifest({
69
- directory: candidateDirectory,
70
- gitSha: options.gitSha,
71
- tag: options.tag,
72
- version: options.version,
73
- });
74
-
75
34
  const localRegistry = resolveLocalRegistry(options.localRegistry);
76
35
  const activeIndexes = activePythonIndexes();
77
36
  const localPypi = resolveLocalPypi(options.localPypi, activeIndexes);
@@ -88,14 +47,11 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
88
47
  }
89
48
 
90
49
  if (localRegistry) {
91
- const publishNpmScript = fileURLToPath(new URL("./publish-npm.ts", import.meta.url));
92
- logger.info(`publishing approved npm archives to ${localRegistry}`);
50
+ const publishNpmScript = fileURLToPath(new URL("./publish.ts", import.meta.url));
51
+ logger.info(`building and publishing npm workspace to ${localRegistry}`);
93
52
  publishes.push(
94
53
  runTaskCommandAsync(root, process.execPath, [
95
54
  publishNpmScript,
96
- "--directory",
97
- candidateDirectory,
98
- "--version",
99
55
  options.version,
100
56
  "--registry",
101
57
  localRegistry,
@@ -104,86 +60,46 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
104
60
  }
105
61
 
106
62
  if (localPypi) {
107
- const pythonDirectory = mkdtempSync(join(tmpdir(), "dbx-tools-local-pypi-"));
108
- verifyReleaseManifest({
109
- directory: candidateDirectory,
110
- gitSha: options.gitSha,
111
- kind: "pypi",
112
- output: pythonDirectory,
113
- tag: options.tag,
114
- version: options.version,
115
- });
116
- logger.info(`publishing approved Python distributions to ${localPypi.publishUrl}`);
63
+ const publishPythonScript = fileURLToPath(new URL("./publish-python.ts", import.meta.url));
64
+ logger.info(`building and publishing Python workspace to ${localPypi.publishUrl}`);
117
65
  publishes.push(
118
- runTaskCommandAsync(
119
- root,
120
- "uvx",
121
- [
122
- "--from",
123
- "devpi-client",
124
- "devpi",
125
- "upload",
126
- "--index",
127
- localPypi.publishUrl,
128
- "--from-dir",
129
- pythonDirectory,
130
- ],
131
- { env: { ...process.env, UV_DEFAULT_INDEX: localPypi.indexUrl } },
132
- ).finally(() => rmSync(pythonDirectory, { recursive: true, force: true })),
66
+ runTaskCommandAsync(root, process.execPath, [
67
+ publishPythonScript,
68
+ options.version,
69
+ "--root",
70
+ options.pythonRoot,
71
+ "--index-url",
72
+ localPypi.indexUrl,
73
+ "--publish-url",
74
+ localPypi.publishUrl,
75
+ ]),
133
76
  );
134
77
  }
135
78
 
136
79
  await Promise.all(publishes);
137
-
138
- if (options.localCargo && hasRustCrates(root)) {
139
- const registry = localCargoRegistry();
140
- if (registry) {
141
- const publishCargoScript = fileURLToPath(
142
- new URL("./publish-uniffi-local.ts", import.meta.url),
143
- );
144
- await runTaskCommandAsync(root, process.execPath, [
145
- publishCargoScript,
146
- "--version",
147
- options.version,
148
- "--cargo-registry",
149
- registry,
150
- ]);
151
- logger.success(`published Rust ${options.version} to ${registry}`);
152
- }
153
- }
154
-
155
- logger.success(`published approved local candidate ${options.tag}`);
80
+ logger.success(`published local workspace ${options.version}`);
156
81
  }
157
82
 
158
83
  if (import.meta.main) {
159
84
  await new Command()
160
- .requiredOption("--candidate-directory <path>", "verified release candidate directory")
161
- .requiredOption("--sha <sha>", "exact release commit")
162
- .requiredOption("--tag <tag>", "annotated release tag")
163
85
  .requiredOption("--version <version>", "release version")
164
86
  .option("--root <path>", "repository root")
87
+ .option("--python-root <path>", "Python package root", "packages/py")
165
88
  .option("--local-registry <url>", "local npm registry", "auto")
166
89
  .option("--local-pypi <url>", "local devpi index", "auto")
167
- .option("--local-cargo", "publish Cargo crates to a configured local registry")
168
90
  .action(
169
91
  (options: {
170
- candidateDirectory: string;
171
- sha: string;
172
- tag: string;
173
92
  version: string;
174
93
  root?: string;
94
+ pythonRoot: string;
175
95
  localRegistry: string;
176
96
  localPypi: string;
177
- localCargo?: boolean;
178
97
  }) =>
179
98
  publishLocalRelease({
180
- candidateDirectory: options.candidateDirectory,
181
- gitSha: options.sha,
182
- localCargo: Boolean(options.localCargo),
183
99
  localPypi: options.localPypi,
184
100
  localRegistry: options.localRegistry,
101
+ pythonRoot: options.pythonRoot,
185
102
  root: options.root ?? projectUtils.root() ?? process.cwd(),
186
- tag: options.tag,
187
103
  version: options.version,
188
104
  }),
189
105
  )
@@ -44,6 +44,15 @@ export type NpmArchiveManifestTransform = (
44
44
  manifest: NpmArchiveManifest,
45
45
  ) => Readonly<Record<string, unknown>>;
46
46
 
47
+ function archiveFileEntries(directory: string, prefix = ""): string[] {
48
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
49
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
50
+ return entry.isDirectory()
51
+ ? archiveFileEntries(join(directory, entry.name), relativePath)
52
+ : [relativePath];
53
+ });
54
+ }
55
+
47
56
  function normalizedRepository(value: unknown): string | undefined {
48
57
  const url =
49
58
  typeof value === "string"
@@ -291,7 +300,7 @@ export function packNpmPackage(
291
300
  const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as NpmArchiveManifest;
292
301
  writeFileSync(manifestPath, `${JSON.stringify(transformManifest(manifest), null, 2)}\n`);
293
302
  rmSync(archive, { force: true });
294
- exec.spawnSync("tar", ["-czf", archive, "-C", temp, "package"], {
303
+ exec.spawnSync("tar", ["-czf", archive, "-C", temp, ...archiveFileEntries(temp)], {
295
304
  cwd: process.cwd(),
296
305
  stdout: "ignore",
297
306
  stderr: "inherit",
@@ -14,7 +14,7 @@ import { tmpdir } from "node:os";
14
14
  import { basename, dirname, join, resolve } from "node:path";
15
15
  import { Command } from "commander";
16
16
  import { parse, stringify } from "smol-toml";
17
- import { preparePythonProjectForPublication, pythonProjectInfo } from "./uniffi-python.js";
17
+ import { preparePythonProjectForPublication, pythonProjectInfo } from "./python-release.ts";
18
18
  import { runTaskCommand } from "../src/_task-command.ts";
19
19
 
20
20
  interface PythonProjectFile {
@@ -24,7 +24,6 @@ interface PythonProjectFile {
24
24
  readonly path: string;
25
25
  readonly private: boolean;
26
26
  readonly source: string;
27
- readonly uniffi: boolean;
28
27
  }
29
28
 
30
29
  /** Python wheel and source archives accepted by Twine and package indexes. */
@@ -43,7 +42,7 @@ function pythonProjects(root: string): PythonProjectFile[] {
43
42
  .sort();
44
43
  const allProjects: PythonProjectFile[] = packageFiles.map((path) => {
45
44
  const source = readFileSync(path, "utf8");
46
- const info = pythonProjectInfo(source, { parse, stringify });
45
+ const info = pythonProjectInfo(source, { parse });
47
46
  return {
48
47
  directory: basename(resolve(path, "..")),
49
48
  name: info.name,
@@ -51,7 +50,6 @@ function pythonProjects(root: string): PythonProjectFile[] {
51
50
  path,
52
51
  private: info.private,
53
52
  source,
54
- uniffi: info.uniffi,
55
53
  };
56
54
  });
57
55
  return allProjects;
@@ -91,8 +89,8 @@ export function publishPythonProjects(options: {
91
89
  /** Build the exact Python release distributions without publishing them. */
92
90
  export function buildPythonProjects(options: {
93
91
  readonly allowEmpty?: boolean;
94
- readonly excludeUniFFI?: boolean;
95
92
  readonly output: string;
93
+ readonly packages?: readonly string[];
96
94
  readonly root: string;
97
95
  readonly version: string;
98
96
  }): void {
@@ -100,8 +98,13 @@ export function buildPythonProjects(options: {
100
98
  const output = resolve(options.output);
101
99
  rmSync(output, { recursive: true, force: true });
102
100
  const allProjects = pythonProjects(root);
101
+ const requested = new Set(options.packages ?? []);
102
+ const missing = [...requested].filter(
103
+ (directory) => !allProjects.some((project) => project.directory === directory),
104
+ );
105
+ if (missing.length > 0) throw new Error(`Unknown Python packages: ${missing.join(", ")}`);
103
106
  const projects = allProjects.filter(
104
- (project) => !project.private && (!options.excludeUniFFI || !project.uniffi),
107
+ (project) => !project.private && (requested.size === 0 || requested.has(project.directory)),
105
108
  );
106
109
  if (projects.length === 0 && options.allowEmpty) {
107
110
  mkdirSync(output, { recursive: true });
@@ -141,9 +144,10 @@ if (import.meta.main) {
141
144
  const program = new Command();
142
145
  program
143
146
  .argument("<version>", "Python package version")
144
- .requiredOption("--index-url <url>", "devpi Simple API URL")
145
- .requiredOption("--publish-url <url>", "devpi writable index URL")
147
+ .option("--index-url <url>", "devpi Simple API URL")
148
+ .option("--publish-url <url>", "devpi writable index URL")
146
149
  .option("--root <path>", "Python workspace package root", "packages/py")
150
+ .option("--package <directory...>", "Build only selected package directories")
147
151
  .option("--output <path>", "Build distributions into a directory without publishing")
148
152
  .option("--dry-run", "build and inspect distributions without uploading")
149
153
  .action(
@@ -151,8 +155,9 @@ if (import.meta.main) {
151
155
  version: string,
152
156
  options: {
153
157
  dryRun?: boolean;
154
- indexUrl: string;
155
- publishUrl: string;
158
+ indexUrl?: string;
159
+ package?: string[];
160
+ publishUrl?: string;
156
161
  root: string;
157
162
  output?: string;
158
163
  },
@@ -160,11 +165,21 @@ if (import.meta.main) {
160
165
  if (options.output) {
161
166
  buildPythonProjects({
162
167
  output: options.output,
168
+ packages: options.package,
163
169
  root: options.root,
164
170
  version,
165
171
  });
166
172
  } else {
167
- publishPythonProjects({ ...options, version });
173
+ if (!options.indexUrl || !options.publishUrl) {
174
+ throw new Error("--index-url and --publish-url are required when publishing");
175
+ }
176
+ publishPythonProjects({
177
+ dryRun: options.dryRun,
178
+ indexUrl: options.indexUrl,
179
+ publishUrl: options.publishUrl,
180
+ root: options.root,
181
+ version,
182
+ });
168
183
  }
169
184
  },
170
185
  );
package/tasks/publish.ts CHANGED
@@ -1,22 +1,20 @@
1
1
  #!/usr/bin/env -S bun
2
2
  /**
3
3
  * `bun tasks/publish.ts <version> [--registry <url>] [--exclude <dir>] [--dry-run] [--skip-compile]`
4
- * - verify every workspace member and the Bun lock carry the release version,
5
- * then pack and publish each package owned by the standard Node release.
4
+ * - verify every workspace member carries the release version, then pack and
5
+ * publish each package owned by the standard Node release.
6
6
  *
7
7
  * Bun has no `pnpm -r publish`, so this loop is the recursive-publish stand-in.
8
8
  * It leans on native bun for everything bun already does:
9
9
  *
10
10
  * - **version validation** fails when any member differs from the reviewed
11
11
  * `VERSION`; publication never repairs committed release metadata;
12
- * - **`workspace:` / `catalog:` rewriting** is NOT done here - `bun publish`
12
+ * - **`workspace:` / `catalog:` rewriting** is NOT done here - `bun pm pack`
13
13
  * strips both protocols in the PACKED tarball, resolving `workspace:*` to the
14
14
  * sibling's version and `catalog:` to the root catalog entry. (Verified: a
15
15
  * packed manifest shows `"@scope/x": "<version>"` and the real catalog range,
16
16
  * while the on-disk manifest keeps the protocols.) Setting each member's
17
17
  * version first is the only prerequisite, so a sibling resolves the release
18
- * version. When Bun's workspace lock already carries the version, its
19
- * refresh is skipped;
20
18
  * - **`publishConfig` substitution** (compiled `lib/` entry points) is done
21
19
  * inside the temporary archive, NOT in the checkout: unlike pnpm/npm, `bun
22
20
  * publish`/`bun pm pack` do NOT fold `publishConfig`'s `main`/`types`/`bin`/
@@ -29,27 +27,26 @@
29
27
  * - **compiled output** is emitted once, before packing, by one root-level
30
28
  * filtered `bun run` that fans out to every publishable member in parallel.
31
29
  * Every package is then packed once with lifecycle scripts disabled. The
32
- * exact validated archive is passed to `bun publish`, so upload never
30
+ * exact validated archive is passed to `npm publish`, so upload never
33
31
  * repacks or repeats a member's `prepack`. Packages retain `prepack` for
34
32
  * standalone publishes.
35
33
  * - **release recovery** compares each packed archive's integrity and
36
34
  * repository identity with registry metadata. A matching immutable version
37
35
  * is skipped, while any mismatch fails the retry.
38
36
  *
39
- * `--dry-run` forwards to `bun publish`: it packs + validates
40
- * but uploads nothing, so the `release` workflow is testable end-to-end via a
41
- * `workflow_dispatch` run without anything reaching npm. `--registry` targets a
42
- * non-default registry (a local verdaccio); `--exclude <dir>` (repeatable,
43
- * repo-relative) skips a member owned by another publication flow.
37
+ * `--dry-run` forwards to `npm publish`: it packs + validates
38
+ * but uploads nothing. `--registry` targets a non-default registry (a local
39
+ * verdaccio); `--exclude <dir>` (repeatable, repo-relative) skips a member owned
40
+ * by another publication flow.
44
41
  *
45
- * The checkout remains byte-for-byte unchanged. A stale manifest or lockfile is
46
- * a release error rather than something publication repairs.
42
+ * The checkout remains byte-for-byte unchanged. A stale manifest is a release
43
+ * error rather than something publication repairs. Local lockfiles are ignored
44
+ * because configured registries can leak into them.
47
45
  */
48
46
  import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
49
47
  import { tmpdir } from "node:os";
50
48
  import { delimiter, dirname, join, resolve } from "node:path";
51
49
  import { asyncUtils, log } from "@dbx-tools/shared-core";
52
- import ts from "typescript";
53
50
  import { parse } from "yaml";
54
51
  import {
55
52
  npmReleaseMatches,
@@ -58,7 +55,6 @@ import {
58
55
  readNpmArchiveIdentity,
59
56
  } from "./publish-npm.ts";
60
57
  import { runTaskCommand, runTaskCommandAsync } from "../src/_task-command.ts";
61
- import { toPosix } from "../src/packages.ts";
62
58
 
63
59
  const logger = log.logger("projen:publish");
64
60
 
@@ -85,28 +81,6 @@ function manifestsMatchVersion(members: readonly string[], version: string): boo
85
81
  });
86
82
  }
87
83
 
88
- /** Whether Bun's workspace lock records each live workspace manifest version. */
89
- function lockfileMatchesManifestVersions(root: string, members: readonly string[]): boolean {
90
- const lockfile = join(root, "bun.lock");
91
- if (!existsSync(lockfile)) return false;
92
- try {
93
- const parsed = ts.parseConfigFileTextToJson(lockfile, readFileSync(lockfile, "utf8"));
94
- if (parsed.error) return false;
95
- const lock = parsed.config as {
96
- workspaces?: Record<string, { version?: string }>;
97
- };
98
- return members.every((dir) => {
99
- const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
100
- version?: string;
101
- };
102
- const relative = toPosix(dir.slice(resolve(root).length + 1));
103
- return lock.workspaces?.[relative]?.version === manifest.version;
104
- });
105
- } catch {
106
- return false;
107
- }
108
- }
109
-
110
84
  /** Entry-point fields projen writes as `.ts` source in-repo and rewrites to `lib/` for publish. */
111
85
  const PUBLISH_CONFIG_ENTRY_FIELDS = ["main", "types", "bin", "exports"] as const;
112
86
 
@@ -183,11 +157,6 @@ if (!manifestsMatchVersion(members, version)) {
183
157
  }
184
158
  logger.info(`validated ${members.length} selected member manifests`);
185
159
 
186
- if (!lockfileMatchesManifestVersions(root, allMembers)) {
187
- throw new Error("bun.lock does not match workspace versions; run bun install and commit it");
188
- }
189
- logger.info("validated workspace lock versions");
190
-
191
160
  const publishArgs = [
192
161
  "--ignore-scripts",
193
162
  ...(registry ? ["--registry", registry] : []),
@@ -206,17 +175,12 @@ for (const dir of members) {
206
175
  name?: string;
207
176
  version?: string;
208
177
  private?: boolean;
209
- dbxToolsConfig?: { uniffi?: boolean };
210
178
  publishConfig?: { access?: unknown };
211
179
  };
212
180
  if (pkg.private) {
213
181
  logger.info(`skip private ${pkg.name ?? dirname(dir)}`);
214
182
  continue;
215
183
  }
216
- if (pkg.dbxToolsConfig?.uniffi === true) {
217
- logger.info(`skip UniFFI ${pkg.name ?? dirname(dir)}`);
218
- continue;
219
- }
220
184
  if (!pkg.version) throw new Error(`Missing package version for ${pkg.name ?? dirname(dir)}`);
221
185
  const access = pkg.publishConfig?.access;
222
186
  if (access !== undefined && access !== "public" && access !== "restricted") {
@@ -303,7 +267,7 @@ await asyncUtils.mapConcurrent(
303
267
  logger.info(`${dryRun ? "dry-run publishing" : "publishing"} ${name} @ ${packageVersion}`);
304
268
  await runTaskCommandAsync(
305
269
  dir,
306
- "bun",
270
+ "npm",
307
271
  ["publish", ...(access ? ["--access", access] : []), ...publishArgs, archive],
308
272
  { env: { ...process.env, PATH: path } },
309
273
  );
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env -S bun
2
+ import { dirname, isAbsolute, resolve, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parseArgs } from "node:util";
5
+ import { log } from "@dbx-tools/shared-core";
6
+ import { runTaskCommand } from "../src/_task-command.ts";
7
+ import { pythonNodeBindingProjects, repoRoot } from "../src/packages.ts";
8
+ import {
9
+ pythonNodeBindingWatchInputs,
10
+ resolvePythonNodeBindings,
11
+ type ResolvedPythonNodeBindings,
12
+ } from "../src/python-node-bindings.ts";
13
+ import { watchLoop } from "../src/watch.ts";
14
+
15
+ const logger = log.logger("projen:python-node-bindings");
16
+ const task = resolve(dirname(fileURLToPath(import.meta.url)), "python-node-bindings.ts");
17
+ const { values } = parseArgs({ options: { project: { type: "string" } } });
18
+ const projects = values.project ? [values.project] : pythonNodeBindingProjects(repoRoot);
19
+ const configs = projects.map((project) => resolvePythonNodeBindings(repoRoot, project));
20
+
21
+ if (configs.length > 0) {
22
+ watchLoop(
23
+ "python-node-bindings",
24
+ [...new Set(configs.flatMap(pythonNodeBindingWatchInputs))],
25
+ (changed) => {
26
+ for (const config of configs.filter((candidate) => affected(candidate, changed))) {
27
+ logger.start(`generating ${config.project} Node bindings`);
28
+ runTaskCommand(repoRoot, "bun", [task, "--project", config.project]);
29
+ logger.success(`generated ${config.project} Node bindings`);
30
+ }
31
+ },
32
+ {
33
+ check: (changed) => configs.some((config) => affected(config, changed)),
34
+ },
35
+ );
36
+ }
37
+
38
+ function affected(config: ResolvedPythonNodeBindings, changed: readonly string[]): boolean {
39
+ const inputs = pythonNodeBindingWatchInputs(config);
40
+ return changed.some((path) => inputs.some((input) => contains(input, path)));
41
+ }
42
+
43
+ function contains(input: string, candidate: string): boolean {
44
+ const root = isAbsolute(input) ? input : resolve(repoRoot, input);
45
+ const path = isAbsolute(candidate) ? candidate : resolve(repoRoot, candidate);
46
+ return path === root || path.startsWith(root + sep);
47
+ }