@dbx-tools/projen 0.6.177 → 0.6.179

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.
package/src/project-rs.ts CHANGED
@@ -4,22 +4,27 @@ import { dirname, join, relative, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { project as coreProject } from "@dbx-tools/core";
6
6
  import { string } from "@dbx-tools/shared-core";
7
- import { Project, TextFile, YamlFile, javascript } from "projen";
8
- import {
9
- DBXToolsTypeScriptProject,
10
- projectReleaseBranch,
11
- projectRepositoryUrl,
12
- } from "./project-js.ts";
7
+ import { Project, TextFile, javascript } from "projen";
8
+ import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
9
+ import { BUN_VERSION } from "./bun-workflow.ts";
10
+ import { DBXToolsTypeScriptProject, projectRepositoryUrl } from "./project-js.ts";
13
11
  import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
14
12
  import { pythonModuleName, type PythonPackageOptions } from "./project-py.ts";
15
13
  import type { DBXToolsProject } from "./project.ts";
16
14
  import {
17
- DOWNSTREAM_RELEASE_EVENT,
18
15
  RELEASE_SHA,
19
16
  RELEASE_TAG,
20
- RUST_RELEASE_EVENT,
17
+ RELEASE_VERSION,
21
18
  releaseSourceSteps,
22
19
  } from "./release-dispatch.ts";
20
+ import {
21
+ hasNodeRelease,
22
+ npmPublishEnvironment,
23
+ nodeReleaseSetupSteps,
24
+ releaseArtifactSteps,
25
+ releaseStageCondition,
26
+ releaseWorkflow,
27
+ } from "./release.ts";
23
28
  import { readWorkspaceVersion } from "./workspace-version.ts";
24
29
 
25
30
  export interface CargoDependencyOptions {
@@ -75,12 +80,6 @@ export interface DBXToolsRustWorkspaceOptions {
75
80
  readonly releaseTargets?: readonly UniFFIReleaseTarget[];
76
81
  /** Maintained OS/CPU combinations to release. Defaults to every supported target. */
77
82
  readonly releasePlatforms?: readonly RustReleasePlatform[];
78
- /** Workflow name used by downstream release stages. Defaults to `rust-release`. */
79
- readonly releaseWorkflowName?: string;
80
- /** Workflow that publishes generated Python wheels. Defaults to `python-release`. */
81
- readonly pythonReleaseWorkflowName?: string;
82
- /** Tag prefix dispatched into the branch-scoped release workflow. Defaults to `v`. */
83
- readonly releaseTagPrefix?: string;
84
83
  }
85
84
 
86
85
  export enum RustReleaseOs {
@@ -258,7 +257,6 @@ export interface RustWorkspaceMapping {
258
257
  readonly root: string;
259
258
  readonly crates: readonly string[];
260
259
  readonly bindings: readonly RustBindingMapping[];
261
- readonly releaseWorkflow?: string;
262
260
  }
263
261
 
264
262
  export function orderRustBindings(bindings: readonly RustBindingMapping[]): RustBindingMapping[] {
@@ -552,7 +550,6 @@ export class DBXToolsRustWorkspace {
552
550
  root,
553
551
  crates: this.packages.map((pkg) => `${root}/${pkg.packageOptions.directory}`),
554
552
  bindings: this.bindingMappings,
555
- ...(releaseEnabled ? { releaseWorkflow: options.releaseWorkflowName ?? "rust-release" } : {}),
556
553
  };
557
554
  if (dbxToolsProject) {
558
555
  dbxToolsProject.dbxToolsConfig.rust = this.workspaceMapping;
@@ -594,7 +591,6 @@ export class DBXToolsRustWorkspace {
594
591
  `src/${module.replaceAll(".", "/")}/__init__.py`,
595
592
  ],
596
593
  trustedPublisher: {
597
- workflowName: options.pythonReleaseWorkflowName ?? "python-release",
598
594
  environment: `pypi-${pkg.crateName}`,
599
595
  artifacts: `platform-specific wheels for ${releaseTargets(options)
600
596
  .map((target) => `${target.os}-${target.cpu}`)
@@ -721,11 +717,8 @@ export class DBXToolsRustWorkspace {
721
717
  options: DBXToolsRustWorkspaceOptions,
722
718
  targets: readonly UniFFIReleaseTarget[],
723
719
  ): void {
724
- if (!project.github) return;
725
- const workflowName = options.releaseWorkflowName ?? "rust-release";
726
- const releaseTagPrefix = options.releaseTagPrefix ?? "v";
720
+ if (!project.github || !isDBXToolsJavaScriptProject()(project)) return;
727
721
  const releaseRustVersion = options.releaseRustVersion ?? "stable";
728
- const releaseBranch = projectReleaseBranch(project);
729
722
  const bindings = this.bindingMappings.map((binding) => ({
730
723
  ...binding,
731
724
  node: binding.node ?? "",
@@ -739,14 +732,19 @@ export class DBXToolsRustWorkspace {
739
732
  crate: pkg.crateName,
740
733
  binary: pkg.packageOptions.binaryName ?? pkg.crateName,
741
734
  }));
735
+ const orderedBindingCrates = this.bindingMappings.map((binding) => binding.crate);
742
736
  const publicCrates = this.packages
743
737
  .filter((pkg) => !pkg.packageOptions.private)
744
738
  .sort((first, second) => {
745
- const order = this.bindingMappings.map((binding) => binding.crate);
746
- return order.indexOf(first.crateName) - order.indexOf(second.crateName);
739
+ // Binding crates publish in topological order before release-only crates that may consume them.
740
+ const firstIndex = orderedBindingCrates.indexOf(first.crateName);
741
+ const secondIndex = orderedBindingCrates.indexOf(second.crateName);
742
+ if (firstIndex < 0) return secondIndex < 0 ? 0 : 1;
743
+ if (secondIndex < 0) return -1;
744
+ return firstIndex - secondIndex;
747
745
  })
748
746
  .map((pkg) => pkg.crateName);
749
- const targetMatrix = targets.map((target) => ({ target }));
747
+ const targetMatrix = targets.map((target) => ({ ...target }));
750
748
  const hasPythonBindings = bindings.some((binding) => binding.python);
751
749
  const usePreinstalledWindowsRust = releaseRustVersion === "stable";
752
750
  const hasTargetOutputs =
@@ -770,30 +768,30 @@ export class DBXToolsRustWorkspace {
770
768
  `--python "${binding.python}"`,
771
769
  `--node-package "${binding.nodePackage}"`,
772
770
  `--python-package "${binding.pythonPackage}"`,
773
- '--cargo-target "${{ matrix.target.cargo }}"',
774
- '--node-triple "${{ matrix.target.node }}"',
775
- '--python-tag "${{ matrix.target.python }}"',
776
- '--os "${{ matrix.target.os }}"',
777
- '--cpu "${{ matrix.target.cpu }}"',
778
- '--libc "${{ matrix.target.libc }}"',
779
- `--version "\${VERSION#${releaseTagPrefix}}"`,
780
- `--output "dist/release/${binding.crate}/\${{ matrix.target.node }}"`,
771
+ '--cargo-target "${{ matrix.cargo }}"',
772
+ '--node-triple "${{ matrix.node }}"',
773
+ '--python-tag "${{ matrix.python }}"',
774
+ '--os "${{ matrix.os }}"',
775
+ '--cpu "${{ matrix.cpu }}"',
776
+ '--libc "${{ matrix.libc }}"',
777
+ '--version "$VERSION"',
778
+ `--output "dist/release/${binding.crate}/\${{ matrix.node }}"`,
781
779
  "--skip-build",
782
780
  ].join(" \\\n "),
783
781
  );
784
782
  const binaryCommands = releaseBinaries.flatMap((pkg) => [
785
- `mkdir -p "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage"`,
786
- `SOURCE="target/\${{ matrix.target.cargo }}/release/${pkg.binary}\${{ matrix.target.os == 'win32' && '.exe' || '' }}"`,
787
- `DESTINATION="dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage/${pkg.binary}\${{ matrix.target.os == 'win32' && '.exe' || '' }}"`,
783
+ `mkdir -p "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
784
+ `SOURCE="target/\${{ matrix.cargo }}/release/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
785
+ `DESTINATION="dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage/${pkg.binary}\${{ matrix.os == 'win32' && '.exe' || '' }}"`,
788
786
  'cp "$SOURCE" "$DESTINATION"',
789
- 'if [ "${{ matrix.target.os }}" = "win32" ]; then',
790
- ` 7z a "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/${pkg.binary}-\${{ matrix.target.node }}.zip" "$DESTINATION"`,
787
+ 'if [ "${{ matrix.os }}" = "win32" ]; then',
788
+ ` 7z a "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${pkg.binary}-\${{ matrix.node }}.zip" "$DESTINATION"`,
791
789
  "else",
792
- ` tar -C "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage" -czf "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/${pkg.binary}-\${{ matrix.target.node }}.tar.gz" "${pkg.binary}"`,
790
+ ` tar -C "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage" -czf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/${pkg.binary}-\${{ matrix.node }}.tar.gz" "${pkg.binary}"`,
793
791
  "fi",
794
- `rm -rf "dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/stage"`,
792
+ `rm -rf "dist/release/${pkg.crate}/\${{ matrix.node }}/binary/stage"`,
795
793
  ]);
796
- const artifactSteps = [
794
+ const artifactSteps: JobStep[] = [
797
795
  ...bindings.flatMap((binding) => [
798
796
  ...(binding.node
799
797
  ? [
@@ -801,8 +799,8 @@ export class DBXToolsRustWorkspace {
801
799
  name: `Upload ${binding.crate} native npm package`,
802
800
  uses: "actions/upload-artifact@v7",
803
801
  with: {
804
- name: `${binding.crate}-\${{ matrix.target.node }}-npm`,
805
- path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/npm/*.tgz`,
802
+ name: `${binding.crate}-\${{ matrix.node }}-npm`,
803
+ path: `dist/release/${binding.crate}/\${{ matrix.node }}/npm/*.tgz`,
806
804
  "retention-days": 7,
807
805
  },
808
806
  },
@@ -814,8 +812,8 @@ export class DBXToolsRustWorkspace {
814
812
  name: `Upload ${binding.crate} Python wheel`,
815
813
  uses: "actions/upload-artifact@v7",
816
814
  with: {
817
- name: `${binding.crate}--\${{ matrix.target.python }}--python-wheel`,
818
- path: `dist/release/${binding.crate}/\${{ matrix.target.node }}/python/*.whl`,
815
+ name: `${binding.crate}--\${{ matrix.python }}--python-wheel`,
816
+ path: `dist/release/${binding.crate}/\${{ matrix.node }}/python/*.whl`,
819
817
  "retention-days": 7,
820
818
  },
821
819
  },
@@ -826,22 +824,24 @@ export class DBXToolsRustWorkspace {
826
824
  name: `Upload ${pkg.crate} release binary`,
827
825
  uses: "actions/upload-artifact@v7",
828
826
  with: {
829
- name: `${pkg.crate}-\${{ matrix.target.node }}-binary`,
830
- path: `dist/release/${pkg.crate}/\${{ matrix.target.node }}/binary/*`,
827
+ name: `${pkg.crate}-\${{ matrix.node }}-binary`,
828
+ path: `dist/release/${pkg.crate}/\${{ matrix.node }}/binary/*`,
831
829
  "retention-days": 7,
832
830
  },
833
831
  })),
834
832
  ];
835
833
  const buildJob = {
836
- name: "${{ matrix.target.node }}",
834
+ if: "${{ github.event_name == 'push' || inputs.stage == 'all' }}",
835
+ name: "${{ matrix.node }}",
837
836
  needs: ["verify-context"],
838
- "runs-on": "${{ matrix.target.runner }}",
837
+ runsOn: ["${{ matrix.runner }}"],
838
+ permissions: { contents: JobPermission.READ },
839
839
  env: {
840
840
  ...RUST_CACHE_ENV,
841
- SCCACHE_GHA_VERSION: `release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`,
841
+ SCCACHE_GHA_VERSION: `release-\${{ matrix.cargo }}-rust-${releaseRustVersion}`,
842
842
  },
843
843
  strategy: {
844
- "fail-fast": false,
844
+ failFast: false,
845
845
  matrix: { include: targetMatrix },
846
846
  },
847
847
  steps: [
@@ -849,31 +849,31 @@ export class DBXToolsRustWorkspace {
849
849
  ...(hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
850
850
  {
851
851
  name: "Setup Rust",
852
- ...(usePreinstalledWindowsRust ? { if: "${{ matrix.target.os != 'win32' }}" } : {}),
852
+ ...(usePreinstalledWindowsRust ? { if: "${{ matrix.os != 'win32' }}" } : {}),
853
853
  uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
854
- with: { targets: "${{ matrix.target.cargo }}" },
854
+ with: { targets: "${{ matrix.cargo }}" },
855
855
  },
856
856
  ...(usePreinstalledWindowsRust
857
857
  ? [
858
858
  {
859
859
  name: "Verify preinstalled Windows Rust",
860
- if: "${{ matrix.target.os == 'win32' }}",
860
+ if: "${{ matrix.os == 'win32' }}",
861
861
  shell: "bash",
862
862
  run: [
863
863
  "rustc --version --verbose",
864
864
  "cargo --version",
865
- 'rustup target list --installed | grep -Fx "${{ matrix.target.cargo }}"',
866
- 'test -f "$(rustc --print sysroot)/lib/rustlib/${{ matrix.target.cargo }}/bin/rust-lld.exe"',
865
+ 'rustup target list --installed | grep -Fx "${{ matrix.cargo }}"',
866
+ 'test -f "$(rustc --print sysroot)/lib/rustlib/${{ matrix.cargo }}/bin/rust-lld.exe"',
867
867
  ].join("\n"),
868
868
  },
869
869
  ]
870
870
  : []),
871
- ...rustCacheSteps(`release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`),
871
+ ...rustCacheSteps(`release-\${{ matrix.cargo }}-rust-${releaseRustVersion}`),
872
872
  {
873
873
  name: "Log cache configuration",
874
874
  shell: "bash",
875
875
  run: [
876
- `echo "cargo_cache_namespace=release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}"`,
876
+ `echo "cargo_cache_namespace=release-\${{ matrix.cargo }}-rust-${releaseRustVersion}"`,
877
877
  'echo "cargo_cache_hit=${{ steps.cargo_cache.outputs.cache-hit }}"',
878
878
  'echo "sccache_scope=${{ github.ref }}"',
879
879
  'echo "sccache_namespace=${SCCACHE_GHA_VERSION}"',
@@ -881,7 +881,7 @@ export class DBXToolsRustWorkspace {
881
881
  },
882
882
  {
883
883
  name: "Install Linux native dependencies",
884
- if: "${{ matrix.target.os == 'linux' }}",
884
+ if: "${{ matrix.os == 'linux' }}",
885
885
  run: "sudo apt-get update && sudo apt-get install --yes libdbus-1-dev pkg-config",
886
886
  },
887
887
  {
@@ -889,11 +889,11 @@ export class DBXToolsRustWorkspace {
889
889
  shell: "bash",
890
890
  env: {
891
891
  CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER:
892
- "${{ matrix.target.os == 'win32' && 'rust-lld' || '' }}",
892
+ "${{ matrix.os == 'win32' && 'rust-lld' || '' }}",
893
893
  },
894
894
  run: timedBash(
895
895
  "rust_workspace",
896
- 'cargo build --release --workspace --target "${{ matrix.target.cargo }}"',
896
+ 'cargo build --release --workspace --target "${{ matrix.cargo }}"',
897
897
  ),
898
898
  },
899
899
  ...(bindingCommands.length
@@ -901,7 +901,7 @@ export class DBXToolsRustWorkspace {
901
901
  {
902
902
  name: "Package UniFFI outputs",
903
903
  shell: "bash",
904
- env: { VERSION: RELEASE_TAG },
904
+ env: { VERSION: RELEASE_VERSION },
905
905
  run: timedBash("uniffi_packaging", bindingCommands.join("\n")),
906
906
  },
907
907
  ]
@@ -924,194 +924,160 @@ export class DBXToolsRustWorkspace {
924
924
  },
925
925
  ],
926
926
  };
927
- const releaseCompletionJobs = [
928
- "build",
929
- ...(publicCrates.length ? ["publish-cargo"] : []),
930
- ...(releaseBinaries.length ? ["publish-github-release"] : []),
931
- ];
932
- new YamlFile(project, `.github/workflows/${workflowName}.yml`, {
933
- obj: {
934
- name: workflowName,
935
- "run-name": `${workflowName} ${RELEASE_TAG}`,
936
- on: {
937
- repository_dispatch: {
938
- types: [RUST_RELEASE_EVENT],
927
+ const workflow = releaseWorkflow(project);
928
+ if (hasTargetOutputs && targetMatrix.length) {
929
+ workflow.addJob("rust-build", buildJob);
930
+ }
931
+ if (publicCrates.length) {
932
+ workflow.addJob("publish-cargo", {
933
+ if: "${{ github.event_name == 'push' }}",
934
+ needs: ["verify-context", "rust-build"],
935
+ runsOn: ["ubuntu-latest"],
936
+ permissions: { contents: JobPermission.READ },
937
+ steps: [
938
+ ...releaseSourceSteps(),
939
+ {
940
+ name: "Setup Rust",
941
+ uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
939
942
  },
940
- workflow_dispatch: {
941
- inputs: {
942
- release_tag: {
943
- description: "Annotated release tag to build",
944
- type: "string",
945
- required: true,
946
- },
947
- expected_sha: {
948
- description: "Commit the release tag must reference",
949
- type: "string",
950
- required: true,
951
- },
943
+ {
944
+ name: "Publish public crates",
945
+ env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
946
+ run: publicCrates
947
+ .map((crate) => `cargo publish --package "${crate}" --registry crates-io --no-verify`)
948
+ .join("\n"),
949
+ },
950
+ ],
951
+ });
952
+ workflow.addJob("publish-local-cargo", {
953
+ if: "${{ github.event_name == 'push' && vars.LOCAL_REPOSITORIES == 'true' }}",
954
+ needs: ["verify-context", "rust-build"],
955
+ runsOn: ["self-hosted"],
956
+ permissions: { contents: JobPermission.READ },
957
+ steps: [
958
+ ...releaseSourceSteps(),
959
+ {
960
+ name: "Setup Rust",
961
+ uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
962
+ },
963
+ {
964
+ name: "Publish Cargo crates locally",
965
+ env: { CARGO_REGISTRY_TOKEN: "${{ secrets.LOCAL_CARGO_TOKEN }}" },
966
+ run: publicCrates
967
+ .map(
968
+ (crate) =>
969
+ `cargo publish --package "${crate}" --registry "\${{ vars.LOCAL_CARGO_REGISTRY }}" --no-verify`,
970
+ )
971
+ .join("\n"),
972
+ },
973
+ ],
974
+ });
975
+ }
976
+ if (releaseBinaries.length) {
977
+ workflow.addJob("publish-github-release", {
978
+ if: "${{ github.event_name == 'push' }}",
979
+ needs: ["verify-context", "rust-build"],
980
+ runsOn: ["ubuntu-latest"],
981
+ permissions: { contents: JobPermission.WRITE },
982
+ steps: [
983
+ {
984
+ name: "Download release binaries",
985
+ uses: "actions/download-artifact@v8",
986
+ with: {
987
+ pattern: "*-binary",
988
+ path: "dist/rust-release",
989
+ "merge-multiple": true,
952
990
  },
953
991
  },
992
+ {
993
+ name: "Publish GitHub release assets",
994
+ uses: "softprops/action-gh-release@v2",
995
+ with: {
996
+ files: "dist/rust-release/*",
997
+ "generate-release-notes": true,
998
+ tag_name: RELEASE_TAG,
999
+ target_commitish: RELEASE_SHA,
1000
+ },
1001
+ },
1002
+ ],
1003
+ });
1004
+ }
1005
+
1006
+ const nodeBindings = bindings.filter((binding) => Boolean(binding.node && binding.nodePackage));
1007
+ if (nodeBindings.length && hasNodeRelease(project)) {
1008
+ workflow.addJob("publish-native-npm", {
1009
+ if: "${{ always() && needs.verify-context.result == 'success' && needs.rust-build.result != 'failure' && needs.rust-build.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
1010
+ needs: ["verify-context", "rust-build"],
1011
+ runsOn: ["ubuntu-latest"],
1012
+ permissions: {
1013
+ actions: JobPermission.READ,
1014
+ contents: JobPermission.READ,
1015
+ idToken: JobPermission.WRITE,
954
1016
  },
955
- concurrency: {
956
- group: workflowName,
957
- "cancel-in-progress": true,
958
- },
959
- permissions: { contents: "read" },
960
- jobs: {
961
- "verify-context": {
962
- "runs-on": "ubuntu-latest",
963
- steps: [
964
- {
965
- name: "Require the default branch cache scope",
966
- shell: "bash",
967
- env: {
968
- RELEASE_BRANCH: releaseBranch,
969
- },
970
- run: 'test "$GITHUB_REF_NAME" = "$RELEASE_BRANCH"',
971
- },
972
- {
973
- name: "Write release metadata",
974
- shell: "bash",
975
- env: {
976
- RELEASE_TAG,
977
- EXPECTED_SHA: RELEASE_SHA,
978
- },
979
- run: [
980
- "mkdir -p .release",
981
- 'printf "%s\\n" "$RELEASE_TAG" > .release/tag',
982
- 'printf "%s\\n" "$EXPECTED_SHA" > .release/sha',
983
- ].join("\n"),
984
- },
985
- {
986
- name: "Upload release metadata",
987
- uses: "actions/upload-artifact@v7",
988
- with: {
989
- name: "release-metadata",
990
- path: ".release",
991
- },
992
- },
993
- ],
1017
+ timeoutMinutes: 15,
1018
+ env: { BUN_VERSION, CI: "true" },
1019
+ steps: [
1020
+ ...nodeReleaseSetupSteps(project),
1021
+ ...releaseArtifactSteps({
1022
+ currentName: "Download native npm packages",
1023
+ recoveredName: "Download recovered native npm packages",
1024
+ pattern: "*-npm",
1025
+ path: "dist/uniffi/native",
1026
+ }),
1027
+ {
1028
+ name: "Publish native npm packages",
1029
+ env: { RELEASE_VERSION, ...npmPublishEnvironment() },
1030
+ run: 'bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory dist/uniffi/native --version "$RELEASE_VERSION" $DRY_RUN',
994
1031
  },
995
- ...(hasTargetOutputs && targetMatrix.length ? { build: buildJob } : {}),
996
- ...(publicCrates.length
997
- ? {
998
- "publish-cargo": {
999
- if: "${{ github.event_name == 'repository_dispatch' }}",
1000
- needs: ["build"],
1001
- "runs-on": "ubuntu-latest",
1002
- permissions: { contents: "read" },
1003
- steps: [
1004
- ...releaseSourceSteps(),
1005
- {
1006
- name: "Setup Rust",
1007
- uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
1008
- },
1009
- {
1010
- name: "Publish public crates",
1011
- env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
1012
- run: publicCrates
1013
- .map(
1014
- (crate) =>
1015
- `cargo publish --package "${crate}" --registry crates-io --no-verify`,
1016
- )
1017
- .join("\n"),
1018
- },
1019
- ],
1020
- },
1021
- }
1022
- : {}),
1023
- ...(publicCrates.length
1024
- ? {
1025
- "publish-local-cargo": {
1026
- if: "${{ github.event_name == 'repository_dispatch' && vars.LOCAL_REPOSITORIES == 'true' }}",
1027
- needs: ["build"],
1028
- "runs-on": ["self-hosted"],
1029
- permissions: { contents: "read" },
1030
- steps: [
1031
- ...releaseSourceSteps(),
1032
- {
1033
- name: "Setup Rust",
1034
- uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
1035
- },
1036
- {
1037
- name: "Publish Cargo crates locally",
1038
- env: {
1039
- CARGO_REGISTRY_TOKEN: "${{ secrets.LOCAL_CARGO_TOKEN }}",
1040
- },
1041
- run: publicCrates
1042
- .map(
1043
- (crate) =>
1044
- `cargo publish --package "${crate}" --registry "\${{ vars.LOCAL_CARGO_REGISTRY }}" --no-verify`,
1045
- )
1046
- .join("\n"),
1047
- },
1048
- ],
1049
- },
1050
- }
1051
- : {}),
1052
- ...(releaseBinaries.length
1053
- ? {
1054
- "publish-github-release": {
1055
- if: "${{ github.event_name == 'repository_dispatch' }}",
1056
- needs: ["build"],
1057
- "runs-on": "ubuntu-latest",
1058
- permissions: { contents: "write" },
1059
- steps: [
1060
- {
1061
- name: "Download release binaries",
1062
- uses: "actions/download-artifact@v8",
1063
- with: {
1064
- pattern: "*-binary",
1065
- path: "dist/rust-release",
1066
- "merge-multiple": true,
1067
- },
1068
- },
1069
- {
1070
- name: "Publish GitHub release assets",
1071
- uses: "softprops/action-gh-release@v2",
1072
- with: {
1073
- files: "dist/rust-release/*",
1074
- "generate-release-notes": true,
1075
- tag_name: RELEASE_TAG,
1076
- target_commitish: RELEASE_SHA,
1077
- },
1078
- },
1079
- ],
1080
- },
1081
- }
1082
- : {}),
1083
- "dispatch-downstream": {
1084
- if: "${{ github.event_name == 'repository_dispatch' }}",
1085
- needs: releaseCompletionJobs,
1086
- "runs-on": "ubuntu-latest",
1087
- permissions: { contents: "write" },
1088
- steps: [
1089
- {
1090
- name: "Dispatch downstream releases",
1091
- shell: "bash",
1092
- env: {
1093
- GH_TOKEN: "${{ github.token }}",
1094
- RELEASE_TAG,
1095
- EXPECTED_SHA: RELEASE_SHA,
1096
- RELEASE_EVENT: DOWNSTREAM_RELEASE_EVENT,
1097
- RUST_RUN_ID: "${{ github.run_id }}",
1098
- RUST_RUN_ATTEMPT: "${{ github.run_attempt }}",
1099
- },
1100
- run: [
1101
- [
1102
- 'gh api --method POST "repos/$GITHUB_REPOSITORY/dispatches"',
1103
- '--raw-field event_type="$RELEASE_EVENT"',
1104
- '--raw-field "client_payload[release_tag]=$RELEASE_TAG"',
1105
- '--raw-field "client_payload[expected_sha]=$EXPECTED_SHA"',
1106
- '--raw-field "client_payload[rust_run_id]=$RUST_RUN_ID"',
1107
- '--raw-field "client_payload[rust_run_attempt]=$RUST_RUN_ATTEMPT"',
1108
- ].join(" \\\n "),
1109
- ].join("\n"),
1110
- },
1111
- ],
1032
+ ],
1033
+ });
1034
+ const nodeJob = workflow.getJob("publish-node");
1035
+ if ("uses" in nodeJob) throw new Error("publish-node must be a workflow job");
1036
+ workflow.updateJob("publish-node", {
1037
+ ...nodeJob,
1038
+ if: "${{ always() && needs.verify-context.result == 'success' && needs.publish-native-npm.result == 'success' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
1039
+ needs: ["verify-context", "publish-native-npm"],
1040
+ });
1041
+ workflow.addJob("publish-node-facades", {
1042
+ if: releaseStageCondition("node"),
1043
+ needs: ["verify-context", "publish-node"],
1044
+ runsOn: ["ubuntu-latest"],
1045
+ permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
1046
+ timeoutMinutes: 30,
1047
+ env: { BUN_VERSION, CI: "true" },
1048
+ steps: [
1049
+ ...nodeReleaseSetupSteps(project),
1050
+ {
1051
+ name: "Build and publish UniFFI npm facades",
1052
+ env: { RELEASE_VERSION, ...npmPublishEnvironment() },
1053
+ run: nodeBindings
1054
+ .flatMap((binding) => {
1055
+ const output = `dist/uniffi/facades/${binding.crate}`;
1056
+ return [
1057
+ `node .projen/uniffi-release.mjs facade --node "${binding.node}" --node-package "${binding.nodePackage}" --node-triple "linux-x64-gnu" --version "$RELEASE_VERSION" --output "${output}"`,
1058
+ `bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory "${output}/npm-facade" --version "$RELEASE_VERSION" $DRY_RUN`,
1059
+ ];
1060
+ })
1061
+ .join("\n"),
1112
1062
  },
1113
- },
1114
- },
1115
- });
1063
+ {
1064
+ name: "Smoke test published UniFFI npm facades",
1065
+ if: "${{ github.event_name == 'push' && vars.UNIFFI_FACADE_SMOKE == 'true' }}",
1066
+ continueOnError: true,
1067
+ env: { RELEASE_VERSION },
1068
+ run: [
1069
+ 'SMOKE_DIR="$(mktemp -d)"',
1070
+ "trap 'rm -rf \"$SMOKE_DIR\"' EXIT",
1071
+ 'cd "$SMOKE_DIR"',
1072
+ "npm init --yes >/dev/null",
1073
+ ...nodeBindings.flatMap((binding) => [
1074
+ `npm install --ignore-scripts --no-audit --no-fund --package-lock=false "${binding.nodePackage}@$RELEASE_VERSION"`,
1075
+ `node -e 'import("${binding.nodePackage}")'`,
1076
+ ]),
1077
+ ].join("\n"),
1078
+ },
1079
+ ],
1080
+ });
1081
+ }
1116
1082
  }
1117
1083
  }
@@ -1,13 +1,12 @@
1
- /** Shared release events and immutable source verification. */
2
- export const DOWNSTREAM_RELEASE_EVENT = "release";
3
- export const RUST_RELEASE_EVENT = "rust-release";
4
- export const RELEASE_TAG =
5
- "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_tag || inputs.release_tag }}";
6
- export const RELEASE_SHA =
7
- "${{ github.event_name == 'repository_dispatch' && github.event.client_payload.expected_sha || inputs.expected_sha }}";
1
+ /** Verified release context shared by every job in the unified workflow. */
2
+ import type { JobStep } from "projen/lib/github/workflows-model";
8
3
 
9
- /** Check out and verify the exact commit carried by a release event. */
10
- export function releaseSourceSteps(): readonly Record<string, unknown>[] {
4
+ export const RELEASE_TAG = "${{ needs.verify-context.outputs.release_tag }}";
5
+ export const RELEASE_SHA = "${{ needs.verify-context.outputs.expected_sha }}";
6
+ export const RELEASE_VERSION = "${{ needs.verify-context.outputs.release_version }}";
7
+
8
+ /** Check out and verify the immutable commit selected by the release tag. */
9
+ export function releaseSourceSteps(): readonly JobStep[] {
11
10
  return [
12
11
  {
13
12
  name: "Checkout release commit",
@@ -25,9 +24,8 @@ export function releaseSourceSteps(): readonly Record<string, unknown>[] {
25
24
  EXPECTED_SHA: RELEASE_SHA,
26
25
  },
27
26
  run: [
28
- 'test -n "$RELEASE_TAG"',
29
- 'test -n "$EXPECTED_SHA"',
30
27
  'git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
28
+ 'test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
31
29
  'test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$EXPECTED_SHA"',
32
30
  'test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"',
33
31
  ].join("\n"),