@dbx-tools/projen 0.9.19 → 0.9.28

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 (56) hide show
  1. package/README.md +83 -229
  2. package/index.ts +11 -18
  3. package/package.json +9 -5
  4. package/src/_task-command.ts +75 -0
  5. package/src/barrels.ts +1 -74
  6. package/src/module-exports.ts +175 -1
  7. package/src/openapi.ts +5 -72
  8. package/src/packages.ts +45 -0
  9. package/src/project-js.ts +5 -48
  10. package/src/project-predicate.ts +1 -2
  11. package/src/project-py.ts +87 -199
  12. package/src/project.ts +2 -2
  13. package/src/python-node-bindings.ts +138 -0
  14. package/src/python-node-bundle.ts +92 -0
  15. package/src/release-catalog.ts +40 -208
  16. package/src/{release-dispatch.ts → release-context.ts} +0 -11
  17. package/src/release.ts +74 -778
  18. package/src/scaffold.ts +2 -6
  19. package/src/workspace-version.ts +9 -121
  20. package/tasks/bump.ts +17 -34
  21. package/tasks/databricks-cli-assets.ts +129 -0
  22. package/tasks/local-publish.ts +61 -138
  23. package/tasks/publish-npm.ts +71 -30
  24. package/tasks/publish-python.ts +61 -148
  25. package/tasks/publish.ts +32 -182
  26. package/tasks/python-node-bindings-watch.ts +47 -0
  27. package/tasks/python-node-bindings.ts +1017 -0
  28. package/tasks/python-registry.ts +2 -9
  29. package/tasks/python-release.ts +74 -0
  30. package/tasks/release-version.ts +23 -2
  31. package/tasks/release.ts +99 -0
  32. package/tasks/sync.ts +13 -18
  33. package/tasks/version-check.ts +13 -72
  34. package/src/_rust-project.ts +0 -429
  35. package/src/_rust-release-workflow.ts +0 -698
  36. package/src/project-rs.ts +0 -1127
  37. package/src/release-plan.ts +0 -269
  38. package/src/release-please.ts +0 -242
  39. package/src/uniffi.ts +0 -65
  40. package/tasks/publish-uniffi-local.ts +0 -314
  41. package/tasks/release-assets.ts +0 -476
  42. package/tasks/release-bootstrap.ts +0 -59
  43. package/tasks/release-candidate.ts +0 -383
  44. package/tasks/release-manifest.ts +0 -273
  45. package/tasks/release-plan.ts +0 -89
  46. package/tasks/release-please.ts +0 -162
  47. package/tasks/release-pr.ts +0 -563
  48. package/tasks/release-request.ts +0 -166
  49. package/tasks/release-summary-units.ts +0 -75
  50. package/tasks/release-summary.ts +0 -381
  51. package/tasks/rust.ts +0 -178
  52. package/tasks/stamp-python.ts +0 -13
  53. package/tasks/uniffi-python.d.ts +0 -61
  54. package/tasks/uniffi-python.js +0 -191
  55. package/tasks/uniffi-release.mjs +0 -573
  56. package/tasks/uniffi.ts +0 -271
@@ -1,476 +0,0 @@
1
- #!/usr/bin/env -S bun
2
- /** Build and optionally upload every native release target from macOS. */
3
-
4
- import { createHash } from "node:crypto";
5
- import {
6
- chmodSync,
7
- existsSync,
8
- mkdirSync,
9
- readFileSync,
10
- readdirSync,
11
- rmSync,
12
- statSync,
13
- writeFileSync,
14
- } from "node:fs";
15
- import { platform } from "node:os";
16
- import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
17
- import * as exec from "@dbx-tools/core/exec";
18
- import * as projectUtils from "@dbx-tools/core/project-utils";
19
- import { json, log } from "@dbx-tools/shared-core";
20
- import { Command } from "commander";
21
- import { releaseBinaryAssetName } from "../src/_release-platform.ts";
22
- import type {
23
- RustReleaseConfiguration,
24
- RustReleaseTargetPlan,
25
- } from "../src/_rust-release-workflow.ts";
26
-
27
- const logger = log.logger("projen:release-assets");
28
- const emoji = /[\p{Extended_Pictographic}\uFE0F\u200D]/gu;
29
-
30
- interface CommandInvocation {
31
- readonly command: string;
32
- readonly args: readonly string[];
33
- }
34
-
35
- interface ReleaseAsset {
36
- readonly name: string;
37
- readonly path: string;
38
- readonly sha256: string;
39
- readonly size: number;
40
- }
41
-
42
- function run(
43
- root: string,
44
- command: string,
45
- args: readonly string[],
46
- env: NodeJS.ProcessEnv = process.env,
47
- ): void {
48
- const result = exec.spawnSync(command, [...args], {
49
- cwd: root,
50
- env,
51
- stdin: "ignore",
52
- stdout: "capture",
53
- stderr: "capture",
54
- check: false,
55
- });
56
- for (const line of `${result.stdout}\n${result.stderr}`.split(/\r?\n/)) {
57
- if (line) logger.info(line.replace(emoji, ""));
58
- }
59
- if (result.exitCode !== 0) {
60
- throw new Error(`${command} exited with ${result.exitCode}`);
61
- }
62
- }
63
-
64
- function capture(
65
- root: string,
66
- command: string,
67
- args: readonly string[],
68
- env: NodeJS.ProcessEnv,
69
- ): string {
70
- return exec.spawnSync(command, [...args], {
71
- cwd: root,
72
- env,
73
- stdin: "ignore",
74
- stdout: "capture",
75
- stderr: "inherit",
76
- check: true,
77
- }).stdout;
78
- }
79
-
80
- function commandSucceeds(root: string, command: string, args: readonly string[]): boolean {
81
- return (
82
- exec.spawnSync(command, [...args], {
83
- cwd: root,
84
- env: { ...process.env, PATH: releasePath() },
85
- stdin: "ignore",
86
- stdout: "ignore",
87
- stderr: "ignore",
88
- check: false,
89
- }).exitCode === 0
90
- );
91
- }
92
-
93
- function releasePath(): string {
94
- const llvm = "/opt/homebrew/opt/llvm/bin";
95
- return [existsSync(llvm) ? llvm : undefined, process.env.PATH].filter(Boolean).join(delimiter);
96
- }
97
-
98
- /** Return missing commands required for a complete local release build. */
99
- export function missingLocalReleaseTools(root: string): string[] {
100
- if (platform() !== "darwin") return ["macOS"];
101
- const checks: Array<readonly [string, string, readonly string[]]> = [
102
- ["cargo", "cargo", ["--version"]],
103
- ["cargo-zigbuild", "cargo", ["zigbuild", "--help"]],
104
- ["cargo-xwin", "cargo", ["xwin", "--help"]],
105
- ["node", "node", ["--version"]],
106
- ["llvm-lib", "llvm-lib", ["--version"]],
107
- ["rustup", "rustup", ["--version"]],
108
- ["uv", "uv", ["--version"]],
109
- ["zig", "zig", ["version"]],
110
- ["zip", "zip", ["-v"]],
111
- ];
112
- return checks
113
- .filter(([, command, args]) => !commandSucceeds(root, command, args))
114
- .map(([name]) => name);
115
- }
116
-
117
- function readConfiguration(root: string): RustReleaseConfiguration {
118
- return json.parse(
119
- readFileSync(join(root, ".projen/rust-release.json"), "utf8"),
120
- ) as RustReleaseConfiguration;
121
- }
122
-
123
- /** Whether the synthesized repository has native release targets. */
124
- export function hasLocalReleaseTargets(root: string): boolean {
125
- const path = join(root, ".projen/rust-release.json");
126
- return existsSync(path) && readConfiguration(root).targets.length > 0;
127
- }
128
-
129
- function buildArguments(
130
- configuration: RustReleaseConfiguration,
131
- target: RustReleaseTargetPlan,
132
- targetName: string,
133
- ): string[] {
134
- return [
135
- "build",
136
- "--release",
137
- "--timings",
138
- ...(configuration.usesCargoLock ? ["--locked"] : []),
139
- ...target.packages.flatMap((pkg) => ["--package", pkg]),
140
- ...target.features.flatMap((feature) => ["--features", feature]),
141
- "--target",
142
- targetName,
143
- ];
144
- }
145
-
146
- /** Resolve the native macOS command used for one release target. */
147
- export function localTargetCommand(
148
- configuration: RustReleaseConfiguration,
149
- target: RustReleaseTargetPlan,
150
- ): CommandInvocation {
151
- const localTarget = {
152
- ...target,
153
- features: [...target.features, ...(target.os === "linux" ? target.localFeatures : [])],
154
- };
155
- if (target.os === "darwin") {
156
- return {
157
- command: "cargo",
158
- args: buildArguments(configuration, localTarget, target.cargo),
159
- };
160
- }
161
- if (target.os === "linux") {
162
- const cargoTarget = target.glibcVersion
163
- ? `${target.cargo}.${target.glibcVersion}`
164
- : target.cargo;
165
- return {
166
- command: "cargo",
167
- args: ["zigbuild", ...buildArguments(configuration, localTarget, cargoTarget).slice(1)],
168
- };
169
- }
170
- if (target.os === "win32") {
171
- return {
172
- command: "cargo",
173
- args: ["xwin", "build", ...buildArguments(configuration, localTarget, target.cargo).slice(1)],
174
- };
175
- }
176
- throw new Error(`Unsupported local release target: ${target.os}-${target.cpu}`);
177
- }
178
-
179
- function targetEnvironment(): NodeJS.ProcessEnv {
180
- return {
181
- ...process.env,
182
- CARGO_INCREMENTAL: "0",
183
- PATH: releasePath(),
184
- RUSTC_WRAPPER: "",
185
- SCCACHE_DISABLE: "1",
186
- };
187
- }
188
-
189
- function targetBuildEnvironment(
190
- root: string,
191
- target: RustReleaseTargetPlan,
192
- env: NodeJS.ProcessEnv,
193
- ): NodeJS.ProcessEnv {
194
- if (target.os !== "win32" || target.cpu !== "arm64") return env;
195
- const tools = join(root, "target/release-tools/windows-arm64");
196
- mkdirSync(tools, { recursive: true });
197
- const clang = join(tools, "clang");
198
- writeFileSync(clang, '#!/bin/sh\nexec /opt/homebrew/opt/llvm/bin/clang --driver-mode=cl "$@"\n');
199
- chmodSync(clang, 0o755);
200
- return {
201
- ...env,
202
- PATH: [tools, env.PATH].filter(Boolean).join(delimiter),
203
- };
204
- }
205
-
206
- function cargoTargetRoot(root: string, env: NodeJS.ProcessEnv): string {
207
- const metadata = json.parseRecord(
208
- capture(root, "cargo", ["metadata", "--format-version", "1", "--no-deps"], env),
209
- );
210
- if (typeof metadata?.target_directory !== "string") {
211
- throw new Error("Cargo metadata returned no target directory");
212
- }
213
- return metadata.target_directory;
214
- }
215
-
216
- function rustHostTarget(root: string, env: NodeJS.ProcessEnv): string {
217
- const host = /^host:\s+(.+)$/m.exec(capture(root, "rustc", ["-vV"], env))?.[1];
218
- if (!host) throw new Error("rustc did not report a host target");
219
- return host;
220
- }
221
-
222
- function buildBindingGenerators(
223
- root: string,
224
- configuration: RustReleaseConfiguration,
225
- hostTarget: string,
226
- env: NodeJS.ProcessEnv,
227
- ): void {
228
- if (configuration.bindings.length === 0) return;
229
- run(
230
- root,
231
- "cargo",
232
- [
233
- "build",
234
- "--release",
235
- ...(configuration.usesCargoLock ? ["--locked"] : []),
236
- ...configuration.bindings.flatMap((binding) => ["--package", binding.crate]),
237
- ...configuration.bindings.flatMap((binding) => [
238
- "--features",
239
- `${binding.crate}/uniffi-bindgen`,
240
- ]),
241
- "--target",
242
- hostTarget,
243
- ],
244
- env,
245
- );
246
- }
247
-
248
- function generatePythonBindings(
249
- root: string,
250
- configuration: RustReleaseConfiguration,
251
- hostTarget: string,
252
- env: NodeJS.ProcessEnv,
253
- ): Map<string, string> {
254
- const generated = new Map<string, string>();
255
- for (const binding of configuration.bindings) {
256
- if (!binding.python) continue;
257
- const output = join(root, "dist/local-bindings", binding.crate);
258
- run(
259
- root,
260
- "node",
261
- [
262
- ".projen/uniffi-release.mjs",
263
- "generate-python",
264
- "--crate",
265
- binding.crate,
266
- "--cargo-target",
267
- hostTarget,
268
- "--os",
269
- "darwin",
270
- "--output",
271
- output,
272
- ],
273
- env,
274
- );
275
- generated.set(binding.crate, join(output, `${binding.crate.replaceAll("-", "_")}.py`));
276
- }
277
- return generated;
278
- }
279
-
280
- function packageBindings(
281
- root: string,
282
- configuration: RustReleaseConfiguration,
283
- target: RustReleaseTargetPlan,
284
- version: string,
285
- pythonBindings: ReadonlyMap<string, string>,
286
- env: NodeJS.ProcessEnv,
287
- ): void {
288
- for (const binding of configuration.bindings) {
289
- run(
290
- root,
291
- "node",
292
- [
293
- ".projen/uniffi-release.mjs",
294
- "build",
295
- "--crate",
296
- binding.crate,
297
- "--node",
298
- binding.node,
299
- "--python",
300
- binding.python,
301
- "--node-package",
302
- binding.nodePackage,
303
- "--python-package",
304
- binding.pythonPackage,
305
- "--python-module",
306
- binding.pythonModule ?? "",
307
- "--cargo-target",
308
- target.cargo,
309
- "--node-triple",
310
- target.node,
311
- "--python-tag",
312
- target.python,
313
- "--os",
314
- target.os,
315
- "--cpu",
316
- target.cpu,
317
- "--libc",
318
- target.libc,
319
- "--version",
320
- version,
321
- "--output",
322
- `dist/release/${binding.crate}/${target.node}`,
323
- "--skip-build",
324
- ...(pythonBindings.has(binding.crate)
325
- ? ["--python-bindings", pythonBindings.get(binding.crate)!]
326
- : []),
327
- ],
328
- env,
329
- );
330
- }
331
- }
332
-
333
- function packageBinaries(
334
- root: string,
335
- configuration: RustReleaseConfiguration,
336
- target: RustReleaseTargetPlan,
337
- targetRoot: string,
338
- env: NodeJS.ProcessEnv,
339
- ): void {
340
- const selected = configuration.binaries.filter(
341
- (binary) => !binary.excludedOs.includes(target.os),
342
- );
343
- for (const crate of new Set(selected.map((binary) => binary.crate))) {
344
- const output = join(root, "dist/release", crate, target.node, "binary");
345
- rmSync(output, { recursive: true, force: true });
346
- mkdirSync(output, { recursive: true });
347
- }
348
- for (const binary of selected) {
349
- const extension = target.os === "win32" ? ".exe" : "";
350
- const source = resolve(targetRoot, target.cargo, "release", `${binary.binary}${extension}`);
351
- if (!existsSync(source)) throw new Error(`Missing release binary: ${source}`);
352
- const output = join(root, "dist/release", binary.crate, target.node, "binary");
353
- const asset = join(output, releaseBinaryAssetName(binary.binary, target.node, target.os));
354
- if (target.os === "win32") {
355
- run(root, "zip", ["-j", asset, source], env);
356
- } else {
357
- run(root, "tar", ["-C", dirname(source), "-czf", asset, basename(source)], env);
358
- }
359
- }
360
- }
361
-
362
- function files(root: string, directory: string): string[] {
363
- const absolute = resolve(root, directory);
364
- if (!existsSync(absolute)) return [];
365
- return readdirSync(absolute, { withFileTypes: true }).flatMap((entry) => {
366
- const child = join(absolute, entry.name);
367
- return entry.isDirectory() ? files(root, child) : [child];
368
- });
369
- }
370
-
371
- function releaseAssets(root: string): ReleaseAsset[] {
372
- const allowed = new Set([".tgz", ".whl", ".zip", ".gz"]);
373
- const found = files(root, "dist/release").filter((path) =>
374
- [...allowed].some((extension) => path.endsWith(extension)),
375
- );
376
- const names = new Set<string>();
377
- return found.map((path) => {
378
- const name = basename(path);
379
- if (names.has(name)) throw new Error(`Duplicate release asset name: ${name}`);
380
- names.add(name);
381
- const content = readFileSync(path);
382
- return {
383
- name,
384
- path,
385
- sha256: createHash("sha256").update(content).digest("hex"),
386
- size: statSync(path).size,
387
- };
388
- });
389
- }
390
-
391
- function writeReleaseManifest(
392
- root: string,
393
- tag: string | undefined,
394
- version: string,
395
- ): { readonly assets: ReleaseAsset[]; readonly manifest: string } {
396
- const assets = releaseAssets(root);
397
- if (assets.length === 0) throw new Error("Local release build produced no assets");
398
- const manifest = join(root, "dist/release/rust-assets.json");
399
- writeFileSync(
400
- manifest,
401
- `${JSON.stringify(
402
- {
403
- schemaVersion: 1,
404
- tag,
405
- version,
406
- assets: assets.map((asset) => ({ ...asset, path: relative(root, asset.path) })),
407
- },
408
- null,
409
- 2,
410
- )}\n`,
411
- );
412
- return { assets, manifest };
413
- }
414
-
415
- function uploadAssets(
416
- root: string,
417
- tag: string,
418
- assets: readonly ReleaseAsset[],
419
- manifest: string,
420
- ): void {
421
- run(root, "gh", ["release", "upload", tag, ...assets.map((asset) => asset.path), "--clobber"]);
422
- run(root, "gh", ["release", "upload", tag, manifest, "--clobber"]);
423
- }
424
-
425
- /** Build all configured targets sequentially and optionally upload them. */
426
- export function buildReleaseAssets(options: {
427
- readonly root: string;
428
- readonly version: string;
429
- readonly tag?: string;
430
- readonly upload?: boolean;
431
- }): void {
432
- const root = resolve(options.root);
433
- const missing = missingLocalReleaseTools(root);
434
- if (missing.length > 0) {
435
- throw new Error(`Local release build requires: ${missing.join(", ")}`);
436
- }
437
- const configuration = readConfiguration(root);
438
- const env = targetEnvironment();
439
- const targetRoot = cargoTargetRoot(root, env);
440
- const hostTarget = rustHostTarget(root, env);
441
- rmSync(join(root, "dist/release"), { recursive: true, force: true });
442
- rmSync(join(root, "dist/local-bindings"), { recursive: true, force: true });
443
- buildBindingGenerators(root, configuration, hostTarget, env);
444
- const pythonBindings = generatePythonBindings(root, configuration, hostTarget, env);
445
- for (const target of configuration.targets) {
446
- run(root, "rustup", ["target", "add", target.cargo], env);
447
- const invocation = localTargetCommand(configuration, target);
448
- const buildEnv = targetBuildEnvironment(root, target, env);
449
- logger.info("building Rust release target", { target: target.cargo });
450
- run(root, invocation.command, invocation.args, buildEnv);
451
- packageBindings(root, configuration, target, options.version, pythonBindings, env);
452
- packageBinaries(root, configuration, target, targetRoot, env);
453
- }
454
- const prepared = writeReleaseManifest(root, options.tag, options.version);
455
- if (options.upload) {
456
- if (!options.tag) throw new Error("--tag is required with --upload");
457
- uploadAssets(root, options.tag, prepared.assets, prepared.manifest);
458
- }
459
- }
460
-
461
- if (import.meta.main) {
462
- new Command()
463
- .requiredOption("--version <version>", "release version")
464
- .option("--root <path>", "repository root")
465
- .option("--tag <tag>", "GitHub Release tag")
466
- .option("--upload", "upload assets to the GitHub Release")
467
- .action((options: { version: string; root?: string; tag?: string; upload?: boolean }) => {
468
- buildReleaseAssets({
469
- root: options.root ?? projectUtils.root() ?? process.cwd(),
470
- version: options.version,
471
- tag: options.tag,
472
- upload: options.upload,
473
- });
474
- })
475
- .parse();
476
- }
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env -S bun
2
- /** Bootstrap Release Please state from the generated release-unit graph. */
3
-
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { join, resolve } from "node:path";
6
- import * as projectUtils from "@dbx-tools/core/project-utils";
7
- import { json, log } from "@dbx-tools/shared-core";
8
- import { Command } from "commander";
9
- import type { ReleaseUnitGraph } from "../src/release-catalog.ts";
10
-
11
- const logger = log.logger("projen:release-bootstrap");
12
-
13
- function readGraph(root: string): ReleaseUnitGraph {
14
- const path = join(root, ".projen/release-units.json");
15
- const graph = json.parse(readFileSync(path, "utf8")) as ReleaseUnitGraph;
16
- if (graph.schemaVersion !== 1) {
17
- throw new Error(`Unsupported release-unit graph schema: ${String(graph.schemaVersion)}`);
18
- }
19
- return graph;
20
- }
21
-
22
- export function bootstrapReleaseUnits(root: string): void {
23
- const graph = readGraph(root);
24
- const manifestPath = join(root, ".release-please-manifest.json");
25
- const expected = Object.fromEntries(
26
- graph.units.map((unit) => [`.release-units/${unit.component}`, unit.version]),
27
- );
28
- if (existsSync(manifestPath)) {
29
- const current = json.parseRecord(readFileSync(manifestPath, "utf8"));
30
- for (const [path, version] of Object.entries(expected)) {
31
- if (current?.[path] !== version) {
32
- throw new Error(
33
- `Release Please manifest already contains ${path}=${String(current?.[path])}; expected ${version}`,
34
- );
35
- }
36
- }
37
- } else {
38
- writeFileSync(manifestPath, `${JSON.stringify(expected, null, 2)}\n`);
39
- }
40
-
41
- for (const unit of graph.units) {
42
- const directory = join(root, ".release-units", unit.component);
43
- mkdirSync(directory, { recursive: true });
44
- const versionPath = join(directory, "version.txt");
45
- if (!existsSync(versionPath)) writeFileSync(versionPath, `${unit.version}\n`);
46
- const changelogPath = join(directory, "CHANGELOG.md");
47
- if (!existsSync(changelogPath)) writeFileSync(changelogPath, "# Changelog\n");
48
- }
49
- logger.success(`bootstrapped ${graph.units.length} release units`);
50
- }
51
-
52
- if (import.meta.main) {
53
- new Command()
54
- .option("--root <path>", "repository root")
55
- .action((options: { root?: string }) => {
56
- bootstrapReleaseUnits(resolve(options.root ?? projectUtils.root() ?? process.cwd()));
57
- })
58
- .parse();
59
- }