@dbx-tools/projen 0.6.229 → 0.7.0

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
@@ -17,6 +17,7 @@ import {
17
17
  import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
18
18
  import { pythonModuleName, type PythonPackageOptions } from "./project-py.ts";
19
19
  import type { DBXToolsProject } from "./project.ts";
20
+ import { defaultReleaseUnitId, type ReleaseDependencyInput } from "./release-catalog.ts";
20
21
  import {
21
22
  RELEASE_SHA,
22
23
  RELEASE_SUMMARY_FILE,
@@ -26,8 +27,10 @@ import {
26
27
  } from "./release-dispatch.ts";
27
28
  import {
28
29
  hasNodeRelease,
30
+ independentReleaseSetupSteps,
29
31
  npmPublishEnvironment,
30
32
  nodeReleaseSetupSteps,
33
+ registerIndependentPublicationJob,
31
34
  releaseArtifactSteps,
32
35
  releaseStageCondition,
33
36
  tryReleaseWorkflow,
@@ -330,6 +333,10 @@ export interface RustReleaseBinaryMapping {
330
333
  readonly command: string;
331
334
  readonly description: string;
332
335
  readonly binaryName: string;
336
+ readonly unit: string;
337
+ readonly component: string;
338
+ readonly version: string;
339
+ readonly tag: string;
333
340
  readonly repository: string;
334
341
  readonly assets: readonly RustReleaseBinaryAssetMapping[];
335
342
  }
@@ -426,6 +433,7 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
426
433
  readonly crateName: string;
427
434
  readonly packageOptions: RustPackageOptions;
428
435
  readonly uniffi: boolean;
436
+ readonly manifestFile: TomlFile;
429
437
 
430
438
  constructor(
431
439
  parent: javascript.NodeProject,
@@ -441,10 +449,18 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
441
449
  const library = existsSync(join(this.outdir, "src/lib.rs"));
442
450
  const binary = existsSync(join(this.outdir, "src/main.rs"));
443
451
  const binaryName = options.binaryName ?? crateName;
452
+ const packageVersion =
453
+ isDBXToolsJavaScriptProject()(parent) && parent.releaseCatalog.mode === "independent"
454
+ ? parent.releaseCatalog.versionForRegistration(
455
+ "rust",
456
+ crateName,
457
+ `${root}/${options.directory}`,
458
+ )
459
+ : { workspace: true };
444
460
  const manifest: Record<string, unknown> = {
445
461
  package: {
446
462
  name: crateName,
447
- version: { workspace: true },
463
+ version: packageVersion,
448
464
  edition: { workspace: true },
449
465
  "rust-version": { workspace: true },
450
466
  description: options.description ?? crateName,
@@ -499,7 +515,7 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
499
515
  }
500
516
  : {}),
501
517
  };
502
- new TomlFile(this, "Cargo.toml", { marker: false, obj: manifest });
518
+ this.manifestFile = new TomlFile(this, "Cargo.toml", { marker: false, obj: manifest });
503
519
  if (this.uniffi) {
504
520
  new TextFile(this, "uniffi-bindgen.rs", {
505
521
  lines: ["fn main() {", " uniffi::uniffi_bindgen_main();", "}", ""],
@@ -592,6 +608,7 @@ function createRustPackageDependencyResolver(
592
608
  }
593
609
 
594
610
  function planRustReleaseBinaries(
611
+ project: javascript.NodeProject,
595
612
  packages: readonly DBXToolsRustProject[],
596
613
  resolved: ResolvedRustWorkspaceOptions,
597
614
  ): RustReleaseBinaryMapping[] {
@@ -620,10 +637,20 @@ function planRustReleaseBinaries(
620
637
  pkg.crateName;
621
638
  const excludedOs = new Set(pkg.packageOptions.releaseExcludeOs ?? []);
622
639
  const binaryName = pkg.packageOptions.binaryName ?? pkg.crateName;
640
+ const identity = isDBXToolsJavaScriptProject()(project)
641
+ ? project.releaseCatalog.releaseIdentityFor(pkg)
642
+ : {
643
+ component: defaultReleaseUnitId("rust", pkg.crateName),
644
+ version: readWorkspaceVersion(project.outdir),
645
+ };
623
646
  return {
624
647
  command,
625
648
  description,
626
649
  binaryName,
650
+ unit: "id" in identity ? identity.id : identity.component,
651
+ component: identity.component,
652
+ version: identity.version,
653
+ tag: `${identity.component}-v${identity.version}`,
627
654
  repository: resolved.repository,
628
655
  assets: resolved.nativeTargets
629
656
  .filter((target) => !excludedOs.has(target.os))
@@ -782,11 +809,13 @@ function planNodeBindingPackages(
782
809
  plan: RustBindingPlan,
783
810
  resolved: ResolvedRustWorkspaceOptions,
784
811
  ): RustNodeBindingPackagePlan[] {
785
- const version = readWorkspaceVersion(project.outdir);
786
812
  return plan.packages
787
813
  .filter((pkg) => (pkg.packageOptions.bindings ?? ["node", "python"]).includes("node"))
788
814
  .map((binding) => {
789
815
  const directory = `${string.toSlug(binding.packageOptions.directory)}-rs`;
816
+ const version = isDBXToolsJavaScriptProject()(project)
817
+ ? project.releaseCatalog.versionFor(binding)
818
+ : readWorkspaceVersion(project.outdir);
790
819
  return {
791
820
  binding,
792
821
  memberPath: `${resolved.nodeRoot}/${directory}`,
@@ -945,7 +974,9 @@ function configureRustWorkspaceFiles(
945
974
  .map((pkg) => `${resolved.root}/${pkg.packageOptions.directory}`),
946
975
  resolver: "2",
947
976
  package: {
948
- version: readWorkspaceVersion(project.outdir),
977
+ ...(!isDBXToolsJavaScriptProject()(project) || project.releaseCatalog.mode === "fixed"
978
+ ? { version: readWorkspaceVersion(project.outdir) }
979
+ : {}),
949
980
  edition: options.edition ?? "2021",
950
981
  "rust-version": options.rustVersion ?? "1.82",
951
982
  license: options.license ?? DBX_TOOLS_LICENSE,
@@ -1120,9 +1151,10 @@ function configureRustReleaseTask(project: javascript.NodeProject, plan: RustRel
1120
1151
  }
1121
1152
  }
1122
1153
 
1123
- function rustBindingCommands(plan: RustReleasePlan): string[] {
1124
- return plan.bindings.map((binding) =>
1125
- [
1154
+ function rustBindingCommands(plan: RustReleasePlan, independent: boolean): string[] {
1155
+ return plan.bindings.map((binding) => {
1156
+ const unit = defaultReleaseUnitId("rust", binding.crate);
1157
+ const command = [
1126
1158
  `node ${plan.releaseTask} build`,
1127
1159
  `--crate "${binding.crate}"`,
1128
1160
  `--node "${binding.node}"`,
@@ -1136,11 +1168,20 @@ function rustBindingCommands(plan: RustReleasePlan): string[] {
1136
1168
  '--os "${{ matrix.os }}"',
1137
1169
  '--cpu "${{ matrix.cpu }}"',
1138
1170
  '--libc "${{ matrix.libc }}"',
1139
- '--version "$VERSION"',
1171
+ independent
1172
+ ? `--version "$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`
1173
+ : '--version "$VERSION"',
1140
1174
  `--output "dist/release/${binding.crate}/\${{ matrix.node }}"`,
1141
1175
  "--skip-build",
1142
- ].join(" \\\n "),
1143
- );
1176
+ ].join(" \\\n ");
1177
+ return independent
1178
+ ? [
1179
+ `if jq -e --arg unit "${unit}" '.units[] | select(.id == $unit)' dist/release-plan.json >/dev/null; then`,
1180
+ ` ${command.replaceAll("\n", "\n ")}`,
1181
+ "fi",
1182
+ ].join("\n")
1183
+ : command;
1184
+ });
1144
1185
  }
1145
1186
 
1146
1187
  function rustReleaseBinaryCondition(excludedOs: readonly RustReleaseOs[]): string | undefined {
@@ -1149,8 +1190,9 @@ function rustReleaseBinaryCondition(excludedOs: readonly RustReleaseOs[]): strin
1149
1190
  : undefined;
1150
1191
  }
1151
1192
 
1152
- function rustBinaryCommands(plan: RustReleasePlan): string[] {
1193
+ function rustBinaryCommands(plan: RustReleasePlan, independent: boolean): string[] {
1153
1194
  return plan.releaseBinaries.flatMap((pkg) => {
1195
+ const unit = defaultReleaseUnitId("rust", pkg.crate);
1154
1196
  const windowsAsset = releaseBinaryAssetName(
1155
1197
  pkg.binary,
1156
1198
  "${{ matrix.node }}",
@@ -1172,9 +1214,16 @@ function rustBinaryCommands(plan: RustReleasePlan): string[] {
1172
1214
  const excludedCondition = pkg.excludedOs
1173
1215
  .map((os) => `[ "\${{ matrix.os }}" != "${os}" ]`)
1174
1216
  .join(" && ");
1175
- return excludedCondition
1217
+ const platformCommands = excludedCondition
1176
1218
  ? [`if ${excludedCondition}; then`, ...commands.map((command) => ` ${command}`), "fi"]
1177
1219
  : commands;
1220
+ return independent
1221
+ ? [
1222
+ `if jq -e --arg unit "${unit}" '.units[] | select(.id == $unit)' dist/release-plan.json >/dev/null; then`,
1223
+ ...platformCommands.map((command) => ` ${command}`),
1224
+ "fi",
1225
+ ]
1226
+ : platformCommands;
1178
1227
  });
1179
1228
  }
1180
1229
 
@@ -1223,22 +1272,31 @@ function rustArtifactSteps(plan: RustReleasePlan): JobStep[] {
1223
1272
  ];
1224
1273
  }
1225
1274
 
1226
- function rustBuildJob(plan: RustReleasePlan): Job {
1227
- const bindingCommands = rustBindingCommands(plan);
1228
- const binaryCommands = rustBinaryCommands(plan);
1275
+ function rustBuildJob(plan: RustReleasePlan, independentSetup?: readonly JobStep[]): Job {
1276
+ const bindingCommands = rustBindingCommands(plan, Boolean(independentSetup));
1277
+ const binaryCommands = rustBinaryCommands(plan, Boolean(independentSetup));
1229
1278
  return {
1230
- if: "${{ github.event_name == 'push' || inputs.stage == 'all' }}",
1279
+ if: independentSetup
1280
+ ? "${{ needs.release-plan.outputs.rust_targets != '[]' && (github.event_name == 'push' || inputs.stage != 'docs') }}"
1281
+ : "${{ github.event_name == 'push' || inputs.stage == 'all' }}",
1231
1282
  name: "${{ matrix.node }}",
1232
- needs: ["verify-context"],
1283
+ needs: [independentSetup ? "release-plan" : "verify-context"],
1233
1284
  runsOn: ["${{ matrix.runner }}"],
1234
1285
  permissions: { contents: JobPermission.READ },
1235
- env: { ...RUST_CACHE_ENV },
1286
+ env: {
1287
+ ...RUST_CACHE_ENV,
1288
+ ...(independentSetup ? { BUN_VERSION } : {}),
1289
+ },
1236
1290
  strategy: {
1237
1291
  failFast: false,
1238
- matrix: { include: [...plan.targets] },
1292
+ matrix: {
1293
+ include: independentSetup
1294
+ ? ("${{ fromJSON(needs.release-plan.outputs.rust_targets) }}" as never)
1295
+ : [...plan.targets],
1296
+ },
1239
1297
  },
1240
1298
  steps: [
1241
- ...releaseSourceSteps(),
1299
+ ...(independentSetup ?? releaseSourceSteps()),
1242
1300
  ...(plan.hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
1243
1301
  {
1244
1302
  name: "Setup Rust",
@@ -1280,9 +1338,16 @@ function rustBuildJob(plan: RustReleasePlan): Job {
1280
1338
  },
1281
1339
  run: timedBash(
1282
1340
  "rust_workspace",
1283
- `cargo build --release --workspace${plan.usesCargoLock ? " --locked" : ""} --target "\${{ matrix.cargo }}"${
1284
- plan.hasReleaseExclusions ? " ${{ matrix.cargoExcludes }}" : ""
1285
- }`,
1341
+ independentSetup
1342
+ ? [
1343
+ "mapfile -t PACKAGES < <(jq -r '.[]' <<<'${{ toJSON(matrix.packages) }}')",
1344
+ "PACKAGE_ARGS=()",
1345
+ 'for PACKAGE in "${PACKAGES[@]}"; do PACKAGE_ARGS+=(--package "$PACKAGE"); done',
1346
+ `cargo build --release "\${PACKAGE_ARGS[@]}"${plan.usesCargoLock ? " --locked" : ""} --target "\${{ matrix.cargo }}"`,
1347
+ ].join("\n")
1348
+ : `cargo build --release --workspace${plan.usesCargoLock ? " --locked" : ""} --target "\${{ matrix.cargo }}"${
1349
+ plan.hasReleaseExclusions ? " ${{ matrix.cargoExcludes }}" : ""
1350
+ }`,
1286
1351
  ),
1287
1352
  },
1288
1353
  ...(bindingCommands.length
@@ -1290,7 +1355,7 @@ function rustBuildJob(plan: RustReleasePlan): Job {
1290
1355
  {
1291
1356
  name: "Package UniFFI outputs",
1292
1357
  shell: "bash",
1293
- env: { VERSION: RELEASE_VERSION },
1358
+ ...(independentSetup ? {} : { env: { VERSION: RELEASE_VERSION } }),
1294
1359
  run: timedBash("uniffi_packaging", bindingCommands.join("\n")),
1295
1360
  },
1296
1361
  ]
@@ -1339,6 +1404,86 @@ function rustCargoPublishJob(plan: RustReleasePlan, local: boolean): Job {
1339
1404
  };
1340
1405
  }
1341
1406
 
1407
+ function independentRustCargoPublishJob(
1408
+ project: DBXToolsJavaScriptProject,
1409
+ plan: RustReleasePlan,
1410
+ ): Job {
1411
+ const commands = plan.publicCrates.map((crate) =>
1412
+ [
1413
+ `VERSION="$(jq -r --arg package "${crate}" '.rustPackages[] | select(.identity == $package) | .version' dist/release-plan.json)"`,
1414
+ 'if [ -n "$VERSION" ]; then',
1415
+ ` if cargo info "${crate}@$VERSION" >/dev/null 2>&1; then`,
1416
+ ` echo "skip published ${crate}@$VERSION"`,
1417
+ " else",
1418
+ ` cargo publish --package "${crate}" --registry crates-io --no-verify`,
1419
+ " fi",
1420
+ "fi",
1421
+ ].join("\n"),
1422
+ );
1423
+ return {
1424
+ if: plan.hasTargetOutputs
1425
+ ? "${{ always() && needs.release-plan.outputs.rust == 'true' && needs.rust-build.result != 'failure' && needs.rust-build.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'rust') }}"
1426
+ : "${{ needs.release-plan.outputs.rust == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'rust') }}",
1427
+ needs: ["release-plan", ...(plan.hasTargetOutputs ? ["rust-build"] : [])],
1428
+ runsOn: ["ubuntu-latest"],
1429
+ permissions: { contents: JobPermission.READ },
1430
+ env: { BUN_VERSION },
1431
+ steps: [
1432
+ ...independentReleaseSetupSteps(project),
1433
+ {
1434
+ name: "Setup Rust",
1435
+ uses: `dtolnay/rust-toolchain@${plan.releaseRustVersion}`,
1436
+ },
1437
+ {
1438
+ name: "Publish affected Cargo crates",
1439
+ env: { CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}" },
1440
+ run: commands.join("\n"),
1441
+ },
1442
+ ],
1443
+ };
1444
+ }
1445
+
1446
+ function independentRustGitHubReleaseJob(
1447
+ project: DBXToolsJavaScriptProject,
1448
+ plan: RustReleasePlan,
1449
+ ): Job {
1450
+ return {
1451
+ if: "${{ needs.release-plan.outputs.github == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'github') }}",
1452
+ needs: ["release-plan", "rust-build"],
1453
+ runsOn: ["ubuntu-latest"],
1454
+ permissions: { contents: JobPermission.WRITE },
1455
+ env: { BUN_VERSION },
1456
+ steps: [
1457
+ ...independentReleaseSetupSteps(project),
1458
+ {
1459
+ name: "Download release binaries",
1460
+ uses: "actions/download-artifact@v8",
1461
+ with: {
1462
+ pattern: "*-binary",
1463
+ path: "dist/rust-release",
1464
+ "merge-multiple": true,
1465
+ },
1466
+ },
1467
+ {
1468
+ name: "Upload affected GitHub release assets",
1469
+ env: { GH_TOKEN: "${{ github.token }}" },
1470
+ shell: "bash",
1471
+ run: plan.releaseBinaries
1472
+ .map((binary) => {
1473
+ const unit = defaultReleaseUnitId("rust", binary.crate);
1474
+ return [
1475
+ `VERSION="$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`,
1476
+ 'if [ -n "$VERSION" ]; then',
1477
+ ` gh release upload "${unit}-v$VERSION" dist/rust-release/${binary.binary}-* --clobber`,
1478
+ "fi",
1479
+ ].join("\n");
1480
+ })
1481
+ .join("\n"),
1482
+ },
1483
+ ],
1484
+ };
1485
+ }
1486
+
1342
1487
  function rustGitHubReleaseJob(): Job {
1343
1488
  return {
1344
1489
  if: "${{ github.event_name == 'push' }}",
@@ -1371,6 +1516,70 @@ function rustGitHubReleaseJob(): Job {
1371
1516
  };
1372
1517
  }
1373
1518
 
1519
+ function independentRustNativeNpmPublishJob(project: DBXToolsJavaScriptProject): Job {
1520
+ return {
1521
+ if: "${{ needs.release-plan.outputs.node == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
1522
+ needs: ["release-plan", "rust-build"],
1523
+ runsOn: ["ubuntu-latest"],
1524
+ permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
1525
+ timeoutMinutes: 15,
1526
+ env: { BUN_VERSION, CI: "true" },
1527
+ steps: [
1528
+ ...independentReleaseSetupSteps(project),
1529
+ {
1530
+ name: "Download native npm packages",
1531
+ uses: "actions/download-artifact@v8",
1532
+ with: {
1533
+ pattern: "*-npm",
1534
+ path: "dist/uniffi/native",
1535
+ "merge-multiple": true,
1536
+ },
1537
+ },
1538
+ {
1539
+ name: "Publish affected native npm packages",
1540
+ env: npmPublishEnvironment(),
1541
+ run: "bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory dist/uniffi/native",
1542
+ },
1543
+ ],
1544
+ };
1545
+ }
1546
+
1547
+ function independentRustNodeFacadePublishJob(
1548
+ project: DBXToolsJavaScriptProject,
1549
+ bindings: readonly RustBindingMapping[],
1550
+ ): Job {
1551
+ return {
1552
+ if: "${{ needs.release-plan.outputs.node == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
1553
+ needs: ["release-plan", "publish-node", "publish-native-npm"],
1554
+ runsOn: ["ubuntu-latest"],
1555
+ permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
1556
+ timeoutMinutes: 30,
1557
+ env: { BUN_VERSION, CI: "true" },
1558
+ steps: [
1559
+ ...independentReleaseSetupSteps(project),
1560
+ {
1561
+ name: "Build and publish affected UniFFI npm facades",
1562
+ env: npmPublishEnvironment(),
1563
+ shell: "bash",
1564
+ run: bindings
1565
+ .flatMap((binding) => {
1566
+ if (!binding.node || !binding.nodePackage) return [];
1567
+ const unit = defaultReleaseUnitId("rust", binding.crate);
1568
+ const output = `dist/uniffi/facades/${binding.crate}`;
1569
+ return [
1570
+ `VERSION="$(jq -r --arg unit "${unit}" '.units[] | select(.id == $unit) | .newVersion' dist/release-plan.json)"`,
1571
+ 'if [ -n "$VERSION" ]; then',
1572
+ ` node .projen/uniffi-release.mjs facade --node "${binding.node}" --node-package "${binding.nodePackage}" --node-triple "linux-x64-gnu" --version "$VERSION" --output "${output}"`,
1573
+ ` bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory "${output}/npm-facade" --version "$VERSION"`,
1574
+ "fi",
1575
+ ];
1576
+ })
1577
+ .join("\n"),
1578
+ },
1579
+ ],
1580
+ };
1581
+ }
1582
+
1374
1583
  function rustNativeNpmPublishJob(project: DBXToolsJavaScriptProject): Job {
1375
1584
  return {
1376
1585
  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') }}",
@@ -1446,6 +1655,60 @@ function rustNodeFacadePublishJob(
1446
1655
  };
1447
1656
  }
1448
1657
 
1658
+ function rustReleaseDependencies(
1659
+ root: DBXToolsJavaScriptProject,
1660
+ project: DBXToolsRustProject,
1661
+ packages: readonly DBXToolsRustProject[],
1662
+ dependencies: Readonly<Record<string, CargoDependency>> | undefined,
1663
+ development: boolean,
1664
+ ): ReleaseDependencyInput[] {
1665
+ return Object.values(dependencies ?? {}).flatMap((dependency) => {
1666
+ if (typeof dependency === "string" || !dependency.path) return [];
1667
+ const dependencyPath = resolve(project.outdir, dependency.path);
1668
+ const target = packages.find((candidate) => resolve(candidate.outdir) === dependencyPath);
1669
+ if (!target) return [];
1670
+ const configuredRequirement = dependency.version;
1671
+ const requirement = configuredRequirement
1672
+ ? /^[0-9]/.test(configuredRequirement)
1673
+ ? `^${configuredRequirement}`
1674
+ : configuredRequirement
1675
+ : `^${root.releaseCatalog.versionFor(target)}`;
1676
+ return [
1677
+ {
1678
+ target: target.crateName,
1679
+ kind: development ? ("development" as const) : ("runtime" as const),
1680
+ requirement,
1681
+ propagation: development ? ("never" as const) : ("outside-range" as const),
1682
+ publishOrder: !development,
1683
+ internal: true,
1684
+ },
1685
+ ];
1686
+ });
1687
+ }
1688
+
1689
+ function configureIndependentRustVersions(
1690
+ project: DBXToolsJavaScriptProject,
1691
+ packages: readonly DBXToolsRustProject[],
1692
+ ): void {
1693
+ for (const pkg of packages) {
1694
+ for (const [section, dependencies] of [
1695
+ ["dependencies", pkg.packageOptions.dependencies],
1696
+ ["dev-dependencies", pkg.packageOptions.devDependencies],
1697
+ ] as const) {
1698
+ for (const [name, dependency] of Object.entries(dependencies ?? {})) {
1699
+ if (typeof dependency === "string" || !dependency.path) continue;
1700
+ const dependencyPath = resolve(pkg.outdir, dependency.path);
1701
+ const target = packages.find((candidate) => resolve(candidate.outdir) === dependencyPath);
1702
+ if (!target) continue;
1703
+ pkg.manifestFile.addOverride(
1704
+ `${section}.${name}.version`,
1705
+ project.releaseCatalog.versionFor(target),
1706
+ );
1707
+ }
1708
+ }
1709
+ }
1710
+ }
1711
+
1449
1712
  /** Generated Rust workspace plus convention-derived UniFFI binding packages. */
1450
1713
  export class DBXToolsRustWorkspace {
1451
1714
  readonly packages: readonly DBXToolsRustProject[];
@@ -1463,8 +1726,52 @@ export class DBXToolsRustWorkspace {
1463
1726
  project,
1464
1727
  options,
1465
1728
  );
1466
- this.releaseBinaries = planRustReleaseBinaries(this.packages, resolved);
1729
+ if (isDBXToolsJavaScriptProject()(project)) {
1730
+ for (const pkg of this.packages) {
1731
+ project.releaseCatalog.registerProject(pkg, {
1732
+ language: "rust",
1733
+ identity: pkg.crateName,
1734
+ publish: !pkg.packageOptions.private || pkg.packageOptions.release === true,
1735
+ sourcePaths: [`${relative(project.outdir, pkg.outdir).replaceAll("\\", "/")}/src`],
1736
+ dependencies: [
1737
+ ...rustReleaseDependencies(
1738
+ project,
1739
+ pkg,
1740
+ this.packages,
1741
+ pkg.packageOptions.dependencies,
1742
+ false,
1743
+ ),
1744
+ ...rustReleaseDependencies(
1745
+ project,
1746
+ pkg,
1747
+ this.packages,
1748
+ pkg.packageOptions.devDependencies,
1749
+ true,
1750
+ ),
1751
+ ],
1752
+ });
1753
+ }
1754
+ if (project.releaseCatalog.mode === "independent") {
1755
+ configureIndependentRustVersions(project, this.packages);
1756
+ }
1757
+ }
1758
+ this.releaseBinaries = planRustReleaseBinaries(project, this.packages, resolved);
1467
1759
  configureRustCliRegistry(project, options.cliRegistryPath, this.releaseBinaries);
1760
+ if (isDBXToolsJavaScriptProject()(project)) {
1761
+ for (const binary of this.releaseBinaries) {
1762
+ project.releaseCatalog.registerArtifact(binary.unit, {
1763
+ id: `${binary.unit}:github-binary`,
1764
+ kind: "github-binary",
1765
+ name: binary.binaryName,
1766
+ generated: true,
1767
+ data: {
1768
+ targets: resolved.nativeTargets.filter((target) =>
1769
+ binary.assets.some((asset) => asset.os === target.os && asset.cpu === target.cpu),
1770
+ ),
1771
+ },
1772
+ });
1773
+ }
1774
+ }
1468
1775
 
1469
1776
  const bindingPlan = planRustBindings(this.packages, packageDependencies, resolved);
1470
1777
  validateRustReleaseGraph(this.packages, packageDependencies);
@@ -1489,6 +1796,43 @@ export class DBXToolsRustWorkspace {
1489
1796
  project,
1490
1797
  planNodeBindingPackages(project, bindingPlan, resolved),
1491
1798
  );
1799
+ if (isDBXToolsJavaScriptProject()(project)) {
1800
+ for (const mapping of this.bindingMappings) {
1801
+ const unit = defaultReleaseUnitId("rust", mapping.crate);
1802
+ project.releaseCatalog.addUnit({
1803
+ id: unit,
1804
+ projectPaths: [mapping.rust, mapping.node, mapping.python].filter(
1805
+ (path): path is string => Boolean(path),
1806
+ ),
1807
+ });
1808
+ if (mapping.nodePackage) {
1809
+ project.releaseCatalog.registerArtifact(unit, {
1810
+ id: `${unit}:npm-facade`,
1811
+ kind: "npm",
1812
+ name: mapping.nodePackage,
1813
+ path: mapping.node,
1814
+ generated: true,
1815
+ });
1816
+ project.releaseCatalog.registerArtifact(unit, {
1817
+ id: `${unit}:npm-native`,
1818
+ kind: "npm",
1819
+ name: `${mapping.nodePackage}-native`,
1820
+ generated: true,
1821
+ data: { targets: resolved.nativeTargets },
1822
+ });
1823
+ }
1824
+ if (mapping.pythonPackage) {
1825
+ project.releaseCatalog.registerArtifact(unit, {
1826
+ id: `${unit}:python-wheel`,
1827
+ kind: "pypi",
1828
+ name: mapping.pythonPackage,
1829
+ path: mapping.python,
1830
+ generated: true,
1831
+ data: { targets: resolved.nativeTargets },
1832
+ });
1833
+ }
1834
+ }
1835
+ }
1492
1836
  configureRustWorkspaceFiles(project, this.packages, options, resolved);
1493
1837
  configureRustWorkspaceTasks(project);
1494
1838
  if (releaseEnabled) {
@@ -1508,6 +1852,29 @@ export class DBXToolsRustWorkspace {
1508
1852
  throw new Error("Rust release requires the root dbx-tools release mode");
1509
1853
  }
1510
1854
  configureRustReleaseTask(project, plan);
1855
+ if (project.releaseCatalog.mode === "independent") {
1856
+ if (plan.hasTargetOutputs && plan.targets.length) {
1857
+ workflow.addJob("rust-build", rustBuildJob(plan, independentReleaseSetupSteps(project)));
1858
+ }
1859
+ if (plan.publicCrates.length) {
1860
+ workflow.addJob("publish-cargo", independentRustCargoPublishJob(project, plan));
1861
+ registerIndependentPublicationJob(workflow, "publish-cargo");
1862
+ }
1863
+ if (plan.releaseBinaries.length) {
1864
+ workflow.addJob("publish-github-release", independentRustGitHubReleaseJob(project, plan));
1865
+ registerIndependentPublicationJob(workflow, "publish-github-release");
1866
+ }
1867
+ if (plan.nodeBindings.length && hasNodeRelease(project)) {
1868
+ workflow.addJob("publish-native-npm", independentRustNativeNpmPublishJob(project));
1869
+ registerIndependentPublicationJob(workflow, "publish-native-npm");
1870
+ workflow.addJob(
1871
+ "publish-node-facades",
1872
+ independentRustNodeFacadePublishJob(project, plan.nodeBindings),
1873
+ );
1874
+ registerIndependentPublicationJob(workflow, "publish-node-facades");
1875
+ }
1876
+ return;
1877
+ }
1511
1878
  if (plan.hasTargetOutputs && plan.targets.length) {
1512
1879
  workflow.addJob("rust-build", rustBuildJob(plan));
1513
1880
  }