@dbx-tools/projen 0.9.31 → 0.9.36

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 (55) hide show
  1. package/README.md +71 -65
  2. package/index.ts +5 -8
  3. package/package.json +8 -7
  4. package/src/_task-command.ts +0 -75
  5. package/src/barrels.ts +15 -23
  6. package/src/bun-app.ts +5 -6
  7. package/src/clean.ts +41 -15
  8. package/src/codegen.ts +2 -3
  9. package/src/dbx-tools-config.ts +23 -64
  10. package/src/generated.ts +7 -1
  11. package/src/module-exports.ts +35 -3
  12. package/src/openapi.ts +34 -38
  13. package/src/packages.ts +3 -30
  14. package/src/pnpm-workspace.ts +21 -26
  15. package/src/project-js.ts +152 -365
  16. package/src/project-predicate.ts +4 -5
  17. package/src/project-py.ts +55 -144
  18. package/src/publish.ts +1 -1
  19. package/src/python-node-bindings.ts +140 -67
  20. package/src/python-node-bundle.ts +6 -42
  21. package/src/release-context.ts +4 -4
  22. package/src/release.ts +88 -86
  23. package/src/tags.ts +20 -18
  24. package/src/tsconfig.ts +9 -5
  25. package/src/vscode.ts +47 -58
  26. package/src/watch.ts +2 -8
  27. package/src/workspace-version.ts +1 -6
  28. package/tasks/barrels.ts +43 -38
  29. package/tasks/bump.ts +24 -20
  30. package/tasks/clean.ts +54 -50
  31. package/tasks/compile-workspace.ts +2 -117
  32. package/tasks/lib/compile-workspace.ts +119 -0
  33. package/tasks/lib/local-publish.ts +122 -0
  34. package/tasks/lib/publish-npm.ts +458 -0
  35. package/tasks/lib/publish-python.ts +195 -0
  36. package/tasks/lib/publish.ts +264 -0
  37. package/tasks/lib/python-node-bindings.ts +1262 -0
  38. package/tasks/{python-registry.ts → lib/python-registry.ts} +21 -30
  39. package/tasks/{python-release.ts → lib/python-release.ts} +1 -0
  40. package/tasks/lib/release-version.ts +66 -0
  41. package/tasks/lib/release.ts +212 -0
  42. package/tasks/local-publish.ts +4 -105
  43. package/tasks/openapi.ts +25 -21
  44. package/tasks/projenrc.ts +14 -12
  45. package/tasks/publish-npm.ts +3 -364
  46. package/tasks/publish-python.ts +3 -184
  47. package/tasks/publish.ts +3 -279
  48. package/tasks/python-node-bindings.ts +3 -1014
  49. package/tasks/release-version.ts +3 -62
  50. package/tasks/release.ts +3 -96
  51. package/tasks/sync.ts +71 -70
  52. package/tasks/version-check.ts +3 -10
  53. package/src/release-catalog.ts +0 -684
  54. package/tasks/databricks-cli-assets.ts +0 -129
  55. package/tasks/python-node-bindings-watch.ts +0 -47
package/tasks/publish.ts CHANGED
@@ -1,282 +1,6 @@
1
1
  #!/usr/bin/env -S bun
2
- /**
3
- * `bun tasks/publish.ts <version> [--registry <url>] [--exclude <dir>] [--dry-run] [--skip-compile]`
4
- * - verify every workspace member carries the release version, then pack and
5
- * publish each package owned by the standard Node release.
6
- *
7
- * Bun has no `pnpm -r publish`, so this loop is the recursive-publish stand-in.
8
- * It leans on native bun for everything bun already does:
9
- *
10
- * - **version validation** fails when any member differs from the reviewed
11
- * `VERSION`; publication never repairs committed release metadata;
12
- * - **`workspace:` / `catalog:` rewriting** is NOT done here - `bun pm pack`
13
- * strips both protocols in the PACKED tarball, resolving `workspace:*` to the
14
- * sibling's version and `catalog:` to the root catalog entry. (Verified: a
15
- * packed manifest shows `"@scope/x": "<version>"` and the real catalog range,
16
- * while the on-disk manifest keeps the protocols.) Setting each member's
17
- * version first is the only prerequisite, so a sibling resolves the release
18
- * - **`publishConfig` substitution** (compiled `lib/` entry points) is done
19
- * inside the temporary archive, NOT in the checkout: unlike pnpm/npm, `bun
20
- * publish`/`bun pm pack` do NOT fold `publishConfig`'s `main`/`types`/`bin`/
21
- * `exports` into the packed manifest (verified: the packed manifest keeps the
22
- * raw `.ts` source paths and an inert `publishConfig`). Left unsubstituted, a
23
- * published CLI's `bin` points at `./bin/x.ts`, and because the bin runs via
24
- * its `#!/usr/bin/env node` shebang, node chokes on the `.ts`
25
- * (ERR_UNKNOWN_FILE_EXTENSION). We merge `publishConfig` onto the top-level
26
- * manifest after packing so the tarball advertises the compiled `lib/` tree;
27
- * - **compiled output** is emitted once, before packing, by one root-level
28
- * filtered `bun run` that fans out to every publishable member in parallel.
29
- * Every package is then packed once with lifecycle scripts disabled. The
30
- * exact validated archive is passed to `npm publish`, so upload never
31
- * repacks or repeats a member's `prepack`. Packages retain `prepack` for
32
- * standalone publishes.
33
- * - **release recovery** compares each packed archive's integrity and
34
- * repository identity with registry metadata. A matching immutable version
35
- * is skipped, while any mismatch fails the retry.
36
- *
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.
41
- *
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.
45
- */
46
- import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
47
- import { tmpdir } from "node:os";
48
- import { delimiter, dirname, join, resolve } from "node:path";
49
- import { asyncUtils, log } from "@dbx-tools/shared-core";
50
- import { parse } from "yaml";
51
- import {
52
- npmReleaseMatches,
53
- packNpmPackage,
54
- publishedNpmRelease,
55
- readNpmArchiveIdentity,
56
- } from "./publish-npm.ts";
57
- import { runTaskCommand, runTaskCommandAsync } from "../src/_task-command.ts";
2
+ import { main } from "./lib/publish.ts";
58
3
 
59
- const logger = log.logger("projen:publish");
4
+ export { applyPublishConfig, main } from "./lib/publish.ts";
60
5
 
61
- /**
62
- * Workspace member dirs (absolute), read from the root `pnpm-workspace.yaml` - the
63
- * file the engine keeps for the Databricks Apps pnpm deploy, which also lists every
64
- * bun workspace member. (`bun pm ls` reports installed deps, not the member globs,
65
- * so the manifest list is the source of truth.)
66
- */
67
- function workspaceMembers(root: string): string[] {
68
- const file = join(root, "pnpm-workspace.yaml");
69
- if (!existsSync(file)) return [];
70
- const doc = parse(readFileSync(file, "utf8")) as { packages?: string[] } | null;
71
- return (doc?.packages ?? []).map((m) => resolve(root, m));
72
- }
73
-
74
- /** Whether selected workspace members carry the reviewed workspace version. */
75
- function manifestsMatchVersion(members: readonly string[], version: string): boolean {
76
- return members.every((dir) => {
77
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
78
- version?: string;
79
- };
80
- return pkg.version === version;
81
- });
82
- }
83
-
84
- /** Entry-point fields projen writes as `.ts` source in-repo and rewrites to `lib/` for publish. */
85
- const PUBLISH_CONFIG_ENTRY_FIELDS = ["main", "types", "bin", "exports"] as const;
86
-
87
- /** Compiled files referenced by a publishConfig entry-point tree. */
88
- function compiledPublishTargets(value: unknown): string[] {
89
- if (typeof value === "string") return value.startsWith("./lib/") ? [value] : [];
90
- if (Array.isArray(value)) return value.flatMap(compiledPublishTargets);
91
- if (!value || typeof value !== "object") return [];
92
- return Object.values(value).flatMap(compiledPublishTargets);
93
- }
94
-
95
- /**
96
- * Fold a package's `publishConfig` entry-point fields onto the top-level manifest,
97
- * the way pnpm/npm do at pack time but `bun publish` does NOT (see the module
98
- * doc). The input is the temporary archive manifest, never the checkout.
99
- */
100
- export function applyPublishConfig(
101
- source: Readonly<Record<string, unknown>>,
102
- ): Readonly<Record<string, unknown>> {
103
- const pkg = { ...source };
104
- const publishConfig = pkg.publishConfig as Record<string, unknown> | undefined;
105
- if (!publishConfig) return pkg;
106
- for (const field of PUBLISH_CONFIG_ENTRY_FIELDS) {
107
- if (field in publishConfig) pkg[field] = publishConfig[field];
108
- }
109
- return pkg;
110
- }
111
-
112
- /**
113
- * PATH with the workspace-root `node_modules/.bin` prepended. The root-level
114
- * compile reaches each package's projen/dax task, which resolves `tsc` off PATH;
115
- * under the hoisted linker `tsc` lives only in the root `.bin`.
116
- */
117
- function enrichedPath(root: string): string {
118
- const binDir = join(root, "node_modules", ".bin");
119
- const current = process.env.PATH ?? "";
120
- return current.split(delimiter).includes(binDir) ? current : `${binDir}${delimiter}${current}`;
121
- }
122
-
123
- const argv = process.argv.slice(2);
124
- const version = argv[0] && !argv[0].startsWith("--") ? argv[0] : undefined;
125
- const rest = version ? argv.slice(1) : argv;
126
- const root = process.cwd();
127
- if (!version) {
128
- logger.error(
129
- "usage: bun tasks/publish.ts <version> [--registry <url>] [--output <dir>] [--exclude <dir>] [--dry-run] [--skip-compile]",
130
- );
131
- process.exit(1);
132
- }
133
- const registryIdx = rest.indexOf("--registry");
134
- const registry = registryIdx >= 0 ? rest[registryIdx + 1] : undefined;
135
- const outputIdx = rest.indexOf("--output");
136
- const output = outputIdx >= 0 ? resolve(root, rest[outputIdx + 1]) : undefined;
137
- const dryRun = rest.includes("--dry-run");
138
- const skipCompile = rest.includes("--skip-compile");
139
- const concurrencyIdx = rest.indexOf("--concurrency");
140
- const parsedConcurrency = Number(concurrencyIdx >= 0 ? rest[concurrencyIdx + 1] : 4);
141
- if (!Number.isInteger(parsedConcurrency) || parsedConcurrency < 1) {
142
- throw new Error(`--concurrency must be a positive integer, got ${String(parsedConcurrency)}`);
143
- }
144
- const concurrency = parsedConcurrency;
145
- const excluded = new Set(
146
- rest.reduce<string[]>((acc, arg, i) => (arg === "--exclude" ? [...acc, rest[i + 1]] : acc), []),
147
- );
148
-
149
- const path = enrichedPath(root);
150
- const allMembers = workspaceMembers(root)
151
- .filter((dir) => existsSync(join(dir, "package.json")))
152
- .filter((dir) => !excluded.has(resolve(root, dir).replace(`${resolve(root)}/`, "")));
153
- const members = allMembers;
154
-
155
- if (!manifestsMatchVersion(members, version)) {
156
- throw new Error(`workspace manifests do not match release ${version}; run projen`);
157
- }
158
- logger.info(`validated ${members.length} selected member manifests`);
159
-
160
- const publishArgs = [
161
- "--ignore-scripts",
162
- ...(registry ? ["--registry", registry] : []),
163
- ...(dryRun ? ["--dry-run"] : []),
164
- ];
165
- const publishable: Array<{
166
- dir: string;
167
- name: string;
168
- version: string;
169
- compile: boolean;
170
- compiledTargets: string[];
171
- access?: "public" | "restricted";
172
- }> = [];
173
- for (const dir of members) {
174
- const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
175
- name?: string;
176
- version?: string;
177
- private?: boolean;
178
- publishConfig?: { access?: unknown };
179
- };
180
- if (pkg.private) {
181
- logger.info(`skip private ${pkg.name ?? dirname(dir)}`);
182
- continue;
183
- }
184
- if (!pkg.version) throw new Error(`Missing package version for ${pkg.name ?? dirname(dir)}`);
185
- const access = pkg.publishConfig?.access;
186
- if (access !== undefined && access !== "public" && access !== "restricted") {
187
- throw new Error(
188
- `${pkg.name ?? dirname(dir)} has invalid publishConfig.access ${String(access)}`,
189
- );
190
- }
191
- const compiledTargets = [...new Set(compiledPublishTargets(pkg.publishConfig))];
192
- publishable.push({
193
- dir,
194
- name: pkg.name ?? dirname(dir),
195
- version: pkg.version,
196
- compile: compiledTargets.length > 0,
197
- compiledTargets,
198
- ...(access ? { access } : {}),
199
- });
200
- }
201
-
202
- const compiled = publishable.filter((pkg) => pkg.compile);
203
- if (compiled.length > 0) {
204
- if (skipCompile) {
205
- const missing = compiled.flatMap((pkg) =>
206
- pkg.compiledTargets
207
- .filter((target) => !existsSync(resolve(pkg.dir, target)))
208
- .map((target) => `${pkg.name}:${target}`),
209
- );
210
- if (missing.length > 0) {
211
- throw new Error(
212
- `--skip-compile requires validated compiled output; missing ${missing.join(", ")}`,
213
- );
214
- }
215
- logger.info(`reusing validated compiled output for ${compiled.length} publishable packages`);
216
- } else {
217
- logger.info(`compiling ${compiled.length} publishable packages from the workspace root`);
218
- runTaskCommand(
219
- root,
220
- "bun",
221
- ["run", ...compiled.flatMap((pkg) => ["--filter", pkg.name]), "compile"],
222
- { env: { ...process.env, PATH: path } },
223
- );
224
- }
225
- }
226
-
227
- logger.info(
228
- `${output ? "packing" : dryRun ? "dry-run packing" : "publishing"} ${publishable.length} packages with concurrency ${concurrency}`,
229
- );
230
- if (output) {
231
- rmSync(output, { recursive: true, force: true });
232
- mkdirSync(output, { recursive: true });
233
- }
234
- await asyncUtils.mapConcurrent(
235
- publishable,
236
- async ({ dir, name, version: packageVersion, access }) => {
237
- const packed = mkdtempSync(join(tmpdir(), "projen-npm-release-"));
238
- try {
239
- const archive = packNpmPackage(dir, packed, path, applyPublishConfig);
240
- const local = readNpmArchiveIdentity(archive);
241
- if (local.name !== name || local.version !== packageVersion) {
242
- throw new Error(
243
- `Packed npm identity ${local.name}@${local.version} does not match ${name}@${packageVersion}`,
244
- );
245
- }
246
- if (local.access !== access) {
247
- throw new Error(
248
- `Packed npm access ${String(local.access)} does not match ${String(access)} for ${name}`,
249
- );
250
- }
251
- if (output) {
252
- const destination = join(output, archive.split(/[\\/]/).at(-1)!);
253
- if (existsSync(destination)) {
254
- throw new Error(`Duplicate npm archive name: ${destination}`);
255
- }
256
- copyFileSync(archive, destination);
257
- logger.info(`packed ${name} @ ${packageVersion}`);
258
- return;
259
- }
260
- if (!dryRun) {
261
- const published = await publishedNpmRelease(local.name, local.version, registry);
262
- if (npmReleaseMatches(local, published)) {
263
- logger.info(`skip published ${name} @ ${packageVersion}`);
264
- return;
265
- }
266
- }
267
- logger.info(`${dryRun ? "dry-run publishing" : "publishing"} ${name} @ ${packageVersion}`);
268
- await runTaskCommandAsync(
269
- dir,
270
- "npm",
271
- ["publish", ...(access ? ["--access", access] : []), ...publishArgs, archive],
272
- { env: { ...process.env, PATH: path } },
273
- );
274
- } finally {
275
- rmSync(packed, { recursive: true, force: true });
276
- }
277
- },
278
- { concurrency, errorMode: "settle" },
279
- );
280
- logger.success(
281
- `${output || dryRun ? "packed" : "published"} ${publishable.length} packages${output ? ` to ${output}` : ""}`,
282
- );
6
+ if (import.meta.main) await main();