@dbx-tools/projen 0.9.18 → 0.9.21

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 (105) hide show
  1. package/._LICENSE +0 -0
  2. package/._README.md +0 -0
  3. package/._index.ts +0 -0
  4. package/._package.json +0 -0
  5. package/README.md +72 -88
  6. package/index.ts +2 -7
  7. package/package.json +9 -5
  8. package/src/.__lazy-require.ts +0 -0
  9. package/src/.__release-platform.ts +0 -0
  10. package/src/.__rust-project.ts +0 -0
  11. package/src/.__rust-release-workflow.ts +0 -0
  12. package/src/.__task-command.ts +0 -0
  13. package/src/._barrels.ts +0 -0
  14. package/src/._bun-app.ts +0 -0
  15. package/src/._bun-workflow.ts +0 -0
  16. package/src/._clean.ts +0 -0
  17. package/src/._codegen.ts +0 -0
  18. package/src/._dbx-tools-config.ts +0 -0
  19. package/src/._engine-root.ts +0 -0
  20. package/src/._generated.ts +0 -0
  21. package/src/._mixin.ts +0 -0
  22. package/src/._module-exports.ts +0 -0
  23. package/src/._openapi.ts +0 -0
  24. package/src/._packages.ts +0 -0
  25. package/src/._pnpm-workspace.ts +0 -0
  26. package/src/._project-js.ts +0 -0
  27. package/src/._project-predicate.ts +0 -0
  28. package/src/._project-py.ts +0 -0
  29. package/src/._project-rs.ts +0 -0
  30. package/src/._project.ts +0 -0
  31. package/src/._projen-version.ts +0 -0
  32. package/src/._publish.ts +0 -0
  33. package/src/._release-catalog.ts +0 -0
  34. package/src/._release-dispatch.ts +0 -0
  35. package/src/._release-github.ts +0 -0
  36. package/src/._release.ts +0 -0
  37. package/src/._scaffold.ts +0 -0
  38. package/src/._tags.ts +0 -0
  39. package/src/._tsconfig.ts +0 -0
  40. package/src/._uniffi.ts +0 -0
  41. package/src/._vscode.ts +0 -0
  42. package/src/._watch.ts +0 -0
  43. package/src/._workspace-lock.ts +0 -0
  44. package/src/._workspace-version.ts +0 -0
  45. package/src/_rust-release-workflow.ts +18 -582
  46. package/src/_task-command.ts +75 -0
  47. package/src/project-js.ts +2 -42
  48. package/src/project-py.ts +60 -162
  49. package/src/project-rs.ts +4 -100
  50. package/src/release-catalog.ts +37 -203
  51. package/src/release.ts +96 -775
  52. package/src/scaffold.ts +2 -6
  53. package/src/workspace-version.ts +9 -121
  54. package/tasks/._barrels.ts +0 -0
  55. package/tasks/._bump.ts +0 -0
  56. package/tasks/._clean.ts +0 -0
  57. package/tasks/._compile-workspace.ts +0 -0
  58. package/tasks/._local-publish.ts +0 -0
  59. package/tasks/._openapi.ts +0 -0
  60. package/tasks/._projenrc.ts +0 -0
  61. package/tasks/._publish-npm.ts +0 -0
  62. package/tasks/._publish-python.ts +0 -0
  63. package/tasks/._publish-uniffi-local.ts +0 -0
  64. package/tasks/._publish.ts +0 -0
  65. package/tasks/._python-registry.ts +0 -0
  66. package/tasks/._release-assets.ts +0 -0
  67. package/tasks/._release-candidate.ts +0 -0
  68. package/tasks/._release-manifest.ts +0 -0
  69. package/tasks/._release-pr.ts +0 -0
  70. package/tasks/._release-summary.ts +0 -0
  71. package/tasks/._release-version.ts +0 -0
  72. package/tasks/._rust.ts +0 -0
  73. package/tasks/._sync.ts +0 -0
  74. package/tasks/._uniffi-python.d.ts +0 -0
  75. package/tasks/._uniffi-python.js +0 -0
  76. package/tasks/._uniffi-release.mjs +0 -0
  77. package/tasks/._uniffi.ts +0 -0
  78. package/tasks/._version-check.ts +0 -0
  79. package/tasks/bump.ts +17 -34
  80. package/tasks/local-publish.ts +124 -117
  81. package/tasks/publish-npm.ts +125 -41
  82. package/tasks/publish-python.ts +79 -106
  83. package/tasks/publish-uniffi-local.ts +129 -84
  84. package/tasks/publish.ts +44 -139
  85. package/tasks/python-registry.ts +2 -9
  86. package/tasks/release-assets.ts +56 -65
  87. package/tasks/release-candidate.ts +416 -0
  88. package/tasks/release-manifest.ts +273 -0
  89. package/tasks/release-pr.ts +312 -369
  90. package/tasks/release-summary.ts +8 -10
  91. package/tasks/release-version.ts +23 -2
  92. package/tasks/rust.ts +8 -13
  93. package/tasks/uniffi-python.d.ts +2 -3
  94. package/tasks/uniffi-python.js +7 -20
  95. package/tasks/uniffi-release.mjs +19 -13
  96. package/tasks/uniffi.ts +15 -24
  97. package/tasks/version-check.ts +24 -56
  98. package/src/release-plan.ts +0 -269
  99. package/src/release-please.ts +0 -242
  100. package/tasks/release-bootstrap.ts +0 -59
  101. package/tasks/release-plan.ts +0 -89
  102. package/tasks/release-please.ts +0 -162
  103. package/tasks/release-request.ts +0 -166
  104. package/tasks/release-summary-units.ts +0 -75
  105. package/tasks/stamp-python.ts +0 -13
@@ -1,54 +1,23 @@
1
- /** Rust release planning and generated GitHub workflow jobs. */
1
+ /** Rust release planning, candidate configuration, and Cargo promotion. */
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import { createRequire } from "node:module";
4
4
  import { dirname, join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { stringUtils } from "@dbx-tools/shared-core";
7
6
  import { TextFile, javascript } from "projen";
8
- import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
9
- import { releaseBinaryAssetName } from "./_release-platform.ts";
7
+ import { JobPermission, type Job } from "projen/lib/github/workflows-model";
10
8
  import { UNIFFI_BINDGEN_FEATURE, type RustProject } from "./_rust-project.ts";
11
- import { BUN_VERSION } from "./bun-workflow.ts";
12
- import type { DBXToolsJavaScriptProject } from "./project-js.ts";
13
9
  import type {
14
10
  DBXToolsRustWorkspaceOptions,
15
11
  RustBindingMapping,
16
12
  RustReleaseOs,
17
13
  UniFFIReleaseTarget,
18
14
  } from "./project-rs.ts";
19
- import { defaultReleaseUnitId } from "./release-catalog.ts";
20
- import {
21
- RELEASE_SHA,
22
- RELEASE_SUMMARY_FILE,
23
- RELEASE_TAG,
24
- RELEASE_VERSION,
25
- releaseSourceSteps,
26
- } from "./release-dispatch.ts";
27
- import {
28
- independentReleaseSetupSteps,
29
- npmPublishEnvironment,
30
- nodeReleaseSetupSteps,
31
- releaseArtifactSteps,
32
- releaseStageCondition,
33
- } from "./release.ts";
15
+ import { RELEASE_VERSION, releaseSourceSteps } from "./release-dispatch.ts";
16
+ import { releasePublishCondition } from "./release.ts";
34
17
 
35
18
  const require = createRequire(import.meta.url);
36
-
37
19
  type RustPackageDependencyResolver = (pkg: RustProject) => RustProject[];
38
20
 
39
- const RUST_BUILD_ENV = {
40
- CARGO_INCREMENTAL: "0",
41
- CARGO_TERM_COLOR: "always",
42
- } as const;
43
-
44
- function timedBash(phase: string, command: string): string {
45
- return [
46
- "SECONDS=0",
47
- `trap 'status=$?; echo "phase=${phase} duration_seconds=$SECONDS status=$status"; exit "$status"' EXIT`,
48
- command,
49
- ].join("\n");
50
- }
51
-
52
21
  function taskSource(name: string): string {
53
22
  const sourceDirectory = dirname(fileURLToPath(import.meta.url));
54
23
  const candidates = [
@@ -265,17 +234,6 @@ export function planRustRelease(
265
234
  hasTargetOutputs,
266
235
  };
267
236
  }
268
-
269
- function rustBuildJobIds(plan: RustReleasePlan): string[] {
270
- return plan.targets.length ? ["rust-build"] : [];
271
- }
272
-
273
- function rustBuildResultCondition(plan: RustReleasePlan): string {
274
- return rustBuildJobIds(plan)
275
- .map((job) => `needs.${job}.result != 'failure' && needs.${job}.result != 'cancelled'`)
276
- .join(" && ");
277
- }
278
-
279
237
  export function configureRustReleaseTask(
280
238
  project: javascript.NodeProject,
281
239
  plan: RustReleasePlan,
@@ -318,267 +276,12 @@ export function configureRustReleaseTask(
318
276
  project.tryRemoveFile(".projen/rust-release.mjs");
319
277
  }
320
278
 
321
- function rustBindingCommands(plan: RustReleasePlan, independent: boolean): string[] {
322
- return plan.bindings.map((binding) => {
323
- const unit = defaultReleaseUnitId("rust", binding.crate);
324
- const command = [
325
- `node ${plan.releaseTask} build`,
326
- `--crate "${binding.crate}"`,
327
- `--node "${binding.node}"`,
328
- `--python "${binding.python}"`,
329
- `--node-package "${binding.nodePackage}"`,
330
- `--python-package "${binding.pythonPackage}"`,
331
- `--python-module "${binding.pythonModule}"`,
332
- '--cargo-target "${{ matrix.cargo }}"',
333
- '--node-triple "${{ matrix.node }}"',
334
- '--python-tag "${{ matrix.python }}"',
335
- '--os "${{ matrix.os }}"',
336
- '--cpu "${{ matrix.cpu }}"',
337
- '--libc "${{ matrix.libc }}"',
338
- independent
339
- ? `--version "$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`
340
- : '--version "$VERSION"',
341
- `--output "dist/release/${binding.crate}/\${{ matrix.node }}"`,
342
- "--skip-build",
343
- ].join(" \\\n ");
344
- return independent
345
- ? [
346
- `if jq -e --arg unit "${unit}" '.units[] | select(.id == $unit)' dist/release-plan.json >/dev/null; then`,
347
- ` ${command.replaceAll("\n", "\n ")}`,
348
- "fi",
349
- ].join("\n")
350
- : command;
351
- });
352
- }
353
-
354
- function rustReleaseBinaryCondition(excludedOs: readonly RustReleaseOs[]): string | undefined {
355
- return excludedOs.length
356
- ? `\${{ ${excludedOs.map((os) => `matrix.os != '${os}'`).join(" && ")} }}`
357
- : undefined;
358
- }
359
-
360
- function rustBinaryCommands(plan: RustReleasePlan, independent: boolean): string[] {
361
- return plan.releaseBinaries.flatMap((pkg) => {
362
- const unit = defaultReleaseUnitId("rust", pkg.crate);
363
- const windowsAsset = releaseBinaryAssetName(pkg.binary, "${{ matrix.node }}", "win32");
364
- const unixAsset = releaseBinaryAssetName(pkg.binary, "${{ matrix.node }}", "linux");
365
- const commands = [
366
- `mkdir -p "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
367
- `SOURCE="target/\${{ matrix.cargo }}/release/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
368
- `DESTINATION="dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
369
- 'cp "$SOURCE" "$DESTINATION"',
370
- 'if [ "${{ matrix.os }}" = "win32" ]; then',
371
- ` 7z a "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${windowsAsset}" "$DESTINATION"`,
372
- "else",
373
- ` tar -C "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage" -czf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${unixAsset}" "${pkg.binary}"`,
374
- "fi",
375
- `rm -rf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
376
- ];
377
- const excludedCondition = pkg.excludedOs
378
- .map((os) => `[ "\${{ matrix.os }}" != "${os}" ]`)
379
- .join(" && ");
380
- const platformCommands = excludedCondition
381
- ? [`if ${excludedCondition}; then`, ...commands.map((command) => ` ${command}`), "fi"]
382
- : commands;
383
- return independent
384
- ? [
385
- `if jq -e --arg unit "${unit}" '.units[] | select(.id == $unit)' dist/release-plan.json >/dev/null; then`,
386
- ...platformCommands.map((command) => ` ${command}`),
387
- "fi",
388
- ]
389
- : platformCommands;
390
- });
391
- }
392
-
393
- function rustArtifactSteps(plan: RustReleasePlan): JobStep[] {
394
- const binaryCrates = [...new Set(plan.releaseBinaries.map((binary) => binary.crate))];
395
- return [
396
- ...plan.bindings.flatMap((binding) => [
397
- ...(binding.node
398
- ? [
399
- {
400
- name: `Upload ${binding.crate} native npm package`,
401
- uses: "actions/upload-artifact@v7",
402
- with: {
403
- name: `${binding.crate}-\${{ matrix.node }}-npm`,
404
- path: `dist/release/${binding.crate}/\${{ matrix.node }}/npm/*.tgz`,
405
- "retention-days": 7,
406
- },
407
- },
408
- ]
409
- : []),
410
- ...(binding.python
411
- ? [
412
- {
413
- name: `Upload ${binding.crate} Python wheel`,
414
- uses: "actions/upload-artifact@v7",
415
- with: {
416
- name: `${binding.pythonPackage}--\${{ matrix.python }}--python-wheel`,
417
- path: `dist/release/${binding.crate}/\${{ matrix.node }}/python/*.whl`,
418
- "retention-days": 7,
419
- },
420
- },
421
- ]
422
- : []),
423
- ]),
424
- ...binaryCrates.map((crate) => {
425
- const binaries = plan.releaseBinaries.filter((binary) => binary.crate === crate);
426
- const excludedOs =
427
- binaries[0]?.excludedOs.filter((os) =>
428
- binaries.every((binary) => binary.excludedOs.includes(os)),
429
- ) ?? [];
430
- return {
431
- name: `Upload ${crate} release binary`,
432
- uses: "actions/upload-artifact@v7",
433
- ...(rustReleaseBinaryCondition(excludedOs)
434
- ? { if: rustReleaseBinaryCondition(excludedOs) }
435
- : {}),
436
- with: {
437
- name: `${crate}-\${{ matrix.node }}-binary`,
438
- path: `dist/release/${crate}/\${{ matrix.node }}/binary/*`,
439
- "retention-days": 7,
440
- },
441
- };
442
- }),
443
- ];
444
- }
445
-
446
- /** Build a Bash loop that preserves matrix values across LF and CRLF runners. */
447
- function matrixArgumentLoop(variable: string, field: string, flag: string): string {
448
- return `while IFS= read -r ${variable}; do ${variable}="\${${variable}%$'\\r'}"; ${variable}_ARGS+=(${flag} "$${variable}"); done < <(jq -r '.[]' <<<'\${{ toJSON(matrix.${field}) }}')`;
449
- }
450
-
451
- function rustCargoBuildCommand(plan: RustReleasePlan): string {
452
- return [
453
- "PACKAGE_ARGS=()",
454
- "FEATURE_ARGS=()",
455
- matrixArgumentLoop("PACKAGE", "packages", "--package"),
456
- matrixArgumentLoop("FEATURE", "features", "--features"),
457
- `cargo build --release --timings "\${PACKAGE_ARGS[@]}" "\${FEATURE_ARGS[@]}"${
458
- plan.usesCargoLock ? " --locked" : ""
459
- } --target "\${{ matrix.cargo }}"`,
460
- ].join("\n");
461
- }
462
-
463
- export function rustBuildJob(plan: RustReleasePlan, independentSetup?: readonly JobStep[]): Job {
464
- const independent = Boolean(independentSetup);
465
- const bindingCommands = rustBindingCommands(plan, independent);
466
- const binaryCommands = rustBinaryCommands(plan, independent);
467
- return {
468
- if: independentSetup
469
- ? "${{ needs.release-plan.outputs.rust_targets != '[]' && (github.event_name == 'push' || inputs.stage != 'docs') }}"
470
- : "${{ needs.verify-context.outputs.build_mode == 'remote' && (github.event_name == 'push' || inputs.stage == 'all') }}",
471
- name: "Rust / ${{ matrix.node }}",
472
- needs: [independentSetup ? "release-plan" : "verify-context"],
473
- runsOn: ["${{ matrix.runner }}"],
474
- permissions: { contents: JobPermission.READ },
475
- env: {
476
- ...RUST_BUILD_ENV,
477
- ...(independentSetup ? { BUN_VERSION } : {}),
478
- },
479
- strategy: {
480
- failFast: false,
481
- matrix: {
482
- include: independentSetup
483
- ? ("${{ fromJSON(needs.release-plan.outputs.rust_targets) }}" as never)
484
- : ([...plan.targets] as never),
485
- },
486
- },
487
- steps: [
488
- ...(independentSetup ?? releaseSourceSteps()),
489
- ...(plan.hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
490
- {
491
- name: "Setup Rust",
492
- uses: `dtolnay/rust-toolchain@${plan.releaseRustVersion}`,
493
- with: { targets: "${{ matrix.cargo }}" },
494
- },
495
- {
496
- name: "Install Linux native dependencies",
497
- if: "${{ matrix.os == 'linux' }}",
498
- // prettier-ignore
499
- run: stringUtils.dedent(
500
- // ============================================================================
501
- /*bash*/`
502
- sudo rm -f /etc/apt/sources.list.d/google-chrome.list
503
- sudo apt-get update
504
- sudo apt-get install --yes libdbus-1-dev pkg-config
505
- `
506
- // ============================================================================
507
- ),
508
- },
509
- {
510
- name: "Build Rust release outputs",
511
- shell: "bash",
512
- run: timedBash("rust", rustCargoBuildCommand(plan)),
513
- },
514
- ...(bindingCommands.length
515
- ? [
516
- {
517
- name: "Package UniFFI outputs",
518
- shell: "bash",
519
- ...(independentSetup ? {} : { env: { VERSION: RELEASE_VERSION } }),
520
- run: timedBash("uniffi_packaging", bindingCommands.join("\n")),
521
- },
522
- ]
523
- : []),
524
- ...(binaryCommands.length
525
- ? [
526
- {
527
- name: "Package release binaries",
528
- shell: "bash",
529
- run: timedBash("binary_packaging", binaryCommands.join("\n")),
530
- },
531
- ]
532
- : []),
533
- ...rustArtifactSteps(plan),
534
- ],
535
- };
536
- }
537
-
538
- /** Wait for the selected local or GitHub-hosted native build source. */
539
- export function rustAssetsJob(): Job {
279
+ export function rustCargoPublishJob(plan: RustReleasePlan): Job {
540
280
  return {
541
- if: "${{ always() && needs.verify-context.result == 'success' && (needs.verify-context.outputs.build_mode == 'local' || needs.rust-build.result == 'success') }}",
542
- needs: ["verify-context", "rust-build"],
281
+ if: releasePublishCondition("cargo", ["needs.verify-context.result == 'success'"]),
282
+ needs: ["verify-context"],
543
283
  runsOn: ["ubuntu-latest"],
544
284
  permissions: { contents: JobPermission.READ },
545
- steps: [
546
- {
547
- name: "Wait for locally uploaded release assets",
548
- if: "${{ needs.verify-context.outputs.build_mode == 'local' }}",
549
- env: {
550
- GH_TOKEN: "${{ github.token }}",
551
- RELEASE_TAG: RELEASE_TAG,
552
- },
553
- shell: "bash",
554
- run: [
555
- "mkdir -p dist/local-release",
556
- "for ATTEMPT in $(seq 1 360); do",
557
- ' if gh release download "$RELEASE_TAG" --pattern rust-assets.json --dir dist/local-release --clobber; then exit 0; fi',
558
- ' echo "waiting for local release assets ($ATTEMPT/360)"',
559
- " sleep 5",
560
- "done",
561
- 'echo "::error::local release assets were not uploaded"',
562
- "exit 1",
563
- ].join("\n"),
564
- },
565
- {
566
- name: "Confirm native release assets",
567
- run: "true",
568
- },
569
- ],
570
- };
571
- }
572
-
573
- export function rustCargoPublishJob(plan: RustReleasePlan, local: boolean): Job {
574
- const registry = local ? '"${{ vars.LOCAL_CARGO_REGISTRY }}"' : "crates-io";
575
- return {
576
- if: local
577
- ? "${{ github.event_name == 'push' && vars.LOCAL_REPOSITORIES == 'true' }}"
578
- : "${{ github.event_name == 'push' }}",
579
- needs: ["verify-context", ...(plan.hasTargetOutputs ? ["rust-assets"] : [])],
580
- runsOn: [local ? "self-hosted" : "ubuntu-latest"],
581
- permissions: { contents: JobPermission.READ },
582
285
  steps: [
583
286
  ...releaseSourceSteps(),
584
287
  {
@@ -586,290 +289,23 @@ export function rustCargoPublishJob(plan: RustReleasePlan, local: boolean): Job
586
289
  uses: `dtolnay/rust-toolchain@${plan.releaseRustVersion}`,
587
290
  },
588
291
  {
589
- name: local ? "Publish Cargo crates locally" : "Publish public crates",
292
+ name: "Publish public crates",
590
293
  env: {
591
- CARGO_REGISTRY_TOKEN: local
592
- ? "${{ secrets.LOCAL_CARGO_TOKEN }}"
593
- : "${{ secrets.CARGO_REGISTRY_TOKEN }}",
294
+ RELEASE_VERSION,
295
+ CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}",
594
296
  },
595
297
  run: plan.publicCrates
596
- .map((crate) => `cargo publish --package "${crate}" --registry ${registry} --no-verify`)
597
- .join("\n"),
598
- },
599
- ],
600
- };
601
- }
602
-
603
- export function independentRustCargoPublishJob(
604
- project: DBXToolsJavaScriptProject,
605
- plan: RustReleasePlan,
606
- ): Job {
607
- const commands = plan.publicCrates.map((crate) =>
608
- [
609
- `VERSION="$(jq -r --arg package "${crate}" '.rustPackages[] | select(.identity == $package) | .version' dist/release-plan.json)"`,
610
- 'if [ -n "$VERSION" ]; then',
611
- ` if cargo info "${crate}@$VERSION" >/dev/null 2>&1; then`,
612
- ` echo "skip published ${crate}@$VERSION"`,
613
- " else",
614
- ` cargo publish --package "${crate}" --registry crates-io --no-verify`,
615
- " fi",
616
- "fi",
617
- ].join("\n"),
618
- );
619
- const buildCondition = rustBuildResultCondition(plan);
620
- return {
621
- if: plan.hasTargetOutputs
622
- ? `\${{ always() && needs.release-plan.outputs.rust == 'true' && ${buildCondition} && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'rust') }}`
623
- : "${{ needs.release-plan.outputs.rust == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'rust') }}",
624
- needs: ["release-plan", ...rustBuildJobIds(plan)],
625
- runsOn: ["ubuntu-latest"],
626
- permissions: { contents: JobPermission.READ },
627
- env: { BUN_VERSION },
628
- steps: [
629
- ...independentReleaseSetupSteps(project),
630
- {
631
- name: "Setup Rust",
632
- uses: `dtolnay/rust-toolchain@${plan.releaseRustVersion}`,
633
- },
634
- {
635
- name: "Publish affected Cargo crates",
636
- env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
637
- run: commands.join("\n"),
638
- },
639
- ],
640
- };
641
- }
642
-
643
- export function independentRustGitHubReleaseJob(
644
- project: DBXToolsJavaScriptProject,
645
- plan: RustReleasePlan,
646
- ): Job {
647
- return {
648
- if: "${{ needs.release-plan.outputs.github == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'github') }}",
649
- needs: ["release-plan", ...rustBuildJobIds(plan)],
650
- runsOn: ["ubuntu-latest"],
651
- permissions: { contents: JobPermission.WRITE },
652
- env: { BUN_VERSION },
653
- steps: [
654
- ...independentReleaseSetupSteps(project),
655
- {
656
- name: "Download release binaries",
657
- uses: "actions/download-artifact@v8",
658
- with: {
659
- pattern: "*-binary",
660
- path: "dist/rust-release",
661
- "merge-multiple": true,
662
- },
663
- },
664
- {
665
- name: "Upload affected GitHub release assets",
666
- env: { GH_TOKEN: "${{ github.token }}" },
667
- shell: "bash",
668
- run: plan.releaseBinaries
669
- .map((binary) => {
670
- const unit = defaultReleaseUnitId("rust", binary.crate);
671
- return [
672
- `VERSION="$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`,
673
- 'if [ -n "$VERSION" ]; then',
674
- ` gh release upload "${unit}-v$VERSION" dist/rust-release/${binary.binary}-* --clobber`,
675
- "fi",
676
- ].join("\n");
677
- })
678
- .join("\n"),
679
- },
680
- ],
681
- };
682
- }
683
-
684
- export function rustGitHubReleaseJob(plan: RustReleasePlan): Job {
685
- return {
686
- if: "${{ github.event_name == 'push' }}",
687
- needs: ["verify-context", ...(plan.hasTargetOutputs ? ["rust-assets"] : [])],
688
- runsOn: ["ubuntu-latest"],
689
- permissions: { contents: JobPermission.WRITE },
690
- steps: [
691
- ...releaseSourceSteps(),
692
- {
693
- name: "Download release binaries",
694
- if: "${{ needs.verify-context.outputs.build_mode == 'remote' }}",
695
- uses: "actions/download-artifact@v8",
696
- with: {
697
- pattern: "*-binary",
698
- path: "dist/rust-release",
699
- "merge-multiple": true,
700
- },
701
- },
702
- {
703
- name: "Publish GitHub release assets",
704
- if: "${{ needs.verify-context.outputs.build_mode == 'remote' }}",
705
- uses: "softprops/action-gh-release@v2",
706
- with: {
707
- files: "dist/rust-release/*",
708
- body_path: RELEASE_SUMMARY_FILE,
709
- generate_release_notes: true,
710
- tag_name: RELEASE_TAG,
711
- target_commitish: RELEASE_SHA,
712
- },
713
- },
714
- {
715
- name: "Publish locally built GitHub release",
716
- if: "${{ needs.verify-context.outputs.build_mode == 'local' }}",
717
- env: {
718
- GH_TOKEN: "${{ github.token }}",
719
- RELEASE_NOTES: RELEASE_SUMMARY_FILE,
720
- RELEASE_TAG,
721
- },
722
- run: 'gh release edit "$RELEASE_TAG" --draft=false --latest --notes-file "$RELEASE_NOTES"',
723
- },
724
- ],
725
- };
726
- }
727
-
728
- export function independentRustNativeNpmPublishJob(project: DBXToolsJavaScriptProject): Job {
729
- return {
730
- if: "${{ needs.release-plan.outputs.node == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
731
- needs: ["release-plan", "rust-build"],
732
- runsOn: ["ubuntu-latest"],
733
- permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
734
- timeoutMinutes: 15,
735
- env: { BUN_VERSION, CI: "true" },
736
- steps: [
737
- ...independentReleaseSetupSteps(project),
738
- {
739
- name: "Download native npm packages",
740
- uses: "actions/download-artifact@v8",
741
- with: {
742
- pattern: "*-npm",
743
- path: "dist/uniffi/native",
744
- "merge-multiple": true,
745
- },
746
- },
747
- {
748
- name: "Publish affected native npm packages",
749
- env: npmPublishEnvironment(),
750
- run: "bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory dist/uniffi/native",
751
- },
752
- ],
753
- };
754
- }
755
-
756
- export function independentRustNodeFacadePublishJob(
757
- project: DBXToolsJavaScriptProject,
758
- bindings: readonly RustBindingMapping[],
759
- ): Job {
760
- return {
761
- if: "${{ needs.release-plan.outputs.node == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
762
- needs: ["release-plan", "publish-node", "publish-native-npm"],
763
- runsOn: ["ubuntu-latest"],
764
- permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
765
- timeoutMinutes: 30,
766
- env: { BUN_VERSION, CI: "true" },
767
- steps: [
768
- ...independentReleaseSetupSteps(project),
769
- {
770
- name: "Build and publish affected UniFFI npm facades",
771
- env: npmPublishEnvironment(),
772
- shell: "bash",
773
- run: bindings
774
- .flatMap((binding) => {
775
- if (!binding.node || !binding.nodePackage) return [];
776
- const unit = defaultReleaseUnitId("rust", binding.crate);
777
- const output = `dist/uniffi/facades/${binding.crate}`;
778
- return [
779
- `VERSION="$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`,
780
- 'if [ -n "$VERSION" ]; then',
781
- ` node .projen/uniffi-release.mjs facade --node "${binding.node}" --node-package "${binding.nodePackage}" --node-triple "linux-x64-gnu" --version "$VERSION" --output "${output}"`,
782
- ` bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory "${output}/npm-facade" --version "$VERSION"`,
298
+ .map((crate) =>
299
+ [
300
+ `if cargo info "${crate}@$RELEASE_VERSION" >/dev/null 2>&1; then`,
301
+ ` echo "skip published ${crate}@$RELEASE_VERSION"`,
302
+ "else",
303
+ ` cargo publish --package "${crate}" --registry crates-io --locked`,
783
304
  "fi",
784
- ];
785
- })
786
- .join("\n"),
787
- },
788
- ],
789
- };
790
- }
791
-
792
- export function rustNativeNpmPublishJob(project: DBXToolsJavaScriptProject): Job {
793
- return {
794
- if: "${{ always() && needs.verify-context.result == 'success' && needs.rust-assets.result == 'success' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
795
- needs: ["verify-context", "rust-assets"],
796
- runsOn: ["ubuntu-latest"],
797
- permissions: {
798
- actions: JobPermission.READ,
799
- contents: JobPermission.READ,
800
- idToken: JobPermission.WRITE,
801
- },
802
- timeoutMinutes: 15,
803
- env: { BUN_VERSION, CI: "true" },
804
- steps: [
805
- ...nodeReleaseSetupSteps(project),
806
- ...releaseArtifactSteps({
807
- currentName: "Download native npm packages",
808
- recoveredName: "Download recovered native npm packages",
809
- pattern: "*-npm",
810
- path: "dist/uniffi/native",
811
- condition: "needs.verify-context.outputs.build_mode == 'remote'",
812
- }),
813
- {
814
- name: "Download locally built native npm packages",
815
- if: "${{ needs.verify-context.outputs.build_mode == 'local' }}",
816
- env: { GH_TOKEN: "${{ github.token }}", RELEASE_TAG },
817
- run: [
818
- "mkdir -p dist/uniffi/native",
819
- 'gh release download "$RELEASE_TAG" --pattern "*.tgz" --dir dist/uniffi/native',
820
- ].join("\n"),
821
- },
822
- {
823
- name: "Publish native npm packages",
824
- env: { RELEASE_VERSION, ...npmPublishEnvironment() },
825
- run: 'bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory dist/uniffi/native --version "$RELEASE_VERSION" $DRY_RUN',
826
- },
827
- ],
828
- };
829
- }
830
-
831
- export function rustNodeFacadePublishJob(
832
- project: DBXToolsJavaScriptProject,
833
- bindings: readonly RustReleaseBinding[],
834
- ): Job {
835
- return {
836
- if: releaseStageCondition("node"),
837
- needs: ["verify-context", "publish-node"],
838
- runsOn: ["ubuntu-latest"],
839
- permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
840
- timeoutMinutes: 30,
841
- env: { BUN_VERSION, CI: "true" },
842
- steps: [
843
- ...nodeReleaseSetupSteps(project),
844
- {
845
- name: "Build and publish UniFFI npm facades",
846
- env: { RELEASE_VERSION, ...npmPublishEnvironment() },
847
- run: bindings
848
- .flatMap((binding) => {
849
- const output = `dist/uniffi/facades/${binding.crate}`;
850
- return [
851
- `node .projen/uniffi-release.mjs facade --node "${binding.node}" --node-package "${binding.nodePackage}" --node-triple "linux-x64-gnu" --version "$RELEASE_VERSION" --output "${output}"`,
852
- `bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory "${output}/npm-facade" --version "$RELEASE_VERSION" $DRY_RUN`,
853
- ];
854
- })
305
+ ].join("\n"),
306
+ )
855
307
  .join("\n"),
856
308
  },
857
- {
858
- name: "Smoke test published UniFFI npm facades",
859
- if: "${{ github.event_name == 'push' && vars.UNIFFI_FACADE_SMOKE == 'true' }}",
860
- continueOnError: true,
861
- env: { RELEASE_VERSION },
862
- run: [
863
- 'SMOKE_DIR="$(mktemp -d)"',
864
- "trap 'rm -rf \"$SMOKE_DIR\"' EXIT",
865
- 'cd "$SMOKE_DIR"',
866
- "npm init --yes >/dev/null",
867
- ...bindings.flatMap((binding) => [
868
- `npm install --ignore-scripts --no-audit --no-fund --package-lock=false "${binding.nodePackage}@$RELEASE_VERSION"`,
869
- `node -e 'import("${binding.nodePackage}")'`,
870
- ]),
871
- ].join("\n"),
872
- },
873
309
  ],
874
310
  };
875
311
  }