@dbx-tools/projen 0.9.12 → 0.9.17

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.
@@ -29,7 +29,7 @@ import { parseArgs } from "node:util";
29
29
  import * as exec from "@dbx-tools/core/exec";
30
30
  import { parse, stringify, type TomlTable } from "smol-toml";
31
31
 
32
- export const FINGERPRINT_SCHEMA = 3;
32
+ export const FINGERPRINT_SCHEMA = 4;
33
33
  export const VERSION_SLOT_SCHEMA = 1;
34
34
  export const VERSION_CAPACITY = 64;
35
35
 
@@ -161,11 +161,18 @@ function toPosix(path: string): string {
161
161
  return path.split(sep).join("/");
162
162
  }
163
163
 
164
- function sourceRoots(root: string, metadata: CargoMetadata, sources: readonly string[]): string[] {
164
+ function sourceRoots(
165
+ root: string,
166
+ metadata: CargoMetadata,
167
+ sources: readonly string[],
168
+ includeWorkspace: boolean,
169
+ ): string[] {
165
170
  const members = new Set(metadata.workspace_members);
166
- const discovered = metadata.packages
167
- .filter((candidate) => members.has(candidate.id))
168
- .map((candidate) => dirname(candidate.manifest_path));
171
+ const discovered = includeWorkspace
172
+ ? metadata.packages
173
+ .filter((candidate) => members.has(candidate.id))
174
+ .map((candidate) => dirname(candidate.manifest_path))
175
+ : [];
169
176
  return [...new Set([...discovered, ...sources.map((source) => resolve(root, source))])]
170
177
  .map((path) => toPosix(relative(root, path)) || ".")
171
178
  .sort();
@@ -243,8 +250,12 @@ function normalizedInput(
243
250
  return content;
244
251
  }
245
252
 
246
- /** Hash Cargo-discovered members and every tracked or unignored file beneath them. */
247
- export function sourceHash(root: string, sources: readonly string[] = []): string {
253
+ /** Hash shared Cargo inputs plus selected or Cargo-discovered workspace sources. */
254
+ export function sourceHash(
255
+ root: string,
256
+ sources: readonly string[] = [],
257
+ includeWorkspace = true,
258
+ ): string {
248
259
  const resolvedRoot = resolve(root);
249
260
  const metadata = cargoMetadata(resolvedRoot);
250
261
  const members = new Set(metadata.workspace_members);
@@ -254,7 +265,10 @@ export function sourceHash(root: string, sources: readonly string[] = []): strin
254
265
  .map((candidate) => candidate.name),
255
266
  );
256
267
  const hash = createHash("sha256");
257
- for (const file of inputFiles(resolvedRoot, sourceRoots(resolvedRoot, metadata, sources))) {
268
+ for (const file of inputFiles(
269
+ resolvedRoot,
270
+ sourceRoots(resolvedRoot, metadata, sources, includeWorkspace),
271
+ )) {
258
272
  const absolute = join(resolvedRoot, file);
259
273
  const stat = lstatSync(absolute);
260
274
  hash.update(file);
@@ -280,6 +294,7 @@ export function linkerIdentity(target: string): string {
280
294
 
281
295
  export interface TargetKeyOptions {
282
296
  readonly rustSourceHash: string;
297
+ readonly namespace?: string;
283
298
  readonly target: string;
284
299
  readonly targetConfig?: string;
285
300
  readonly toolchain?: string;
@@ -288,6 +303,7 @@ export interface TargetKeyOptions {
288
303
 
289
304
  export function targetKey({
290
305
  rustSourceHash,
306
+ namespace = "workspace",
291
307
  target,
292
308
  targetConfig = "",
293
309
  toolchain = "stable",
@@ -297,6 +313,7 @@ export function targetKey({
297
313
  for (const value of [
298
314
  String(FINGERPRINT_SCHEMA),
299
315
  String(VERSION_SLOT_SCHEMA),
316
+ namespace,
300
317
  rustSourceHash,
301
318
  target,
302
319
  targetConfig,
@@ -327,11 +344,14 @@ export interface FingerprintOptions {
327
344
  readonly toolchain?: string;
328
345
  readonly portable?: boolean;
329
346
  readonly sources?: readonly string[];
347
+ readonly sourceOnly?: boolean;
348
+ readonly namespace?: string;
330
349
  }
331
350
 
332
351
  export interface RustBuildFingerprint {
333
352
  readonly schemaVersion: number;
334
353
  readonly versionSlotSchema: number;
354
+ readonly namespace: string;
335
355
  readonly rustSourceHash: string;
336
356
  readonly targets: Readonly<Record<string, string>>;
337
357
  }
@@ -344,10 +364,12 @@ export function fingerprint({
344
364
  toolchain = "stable",
345
365
  portable = false,
346
366
  sources = [],
367
+ sourceOnly = false,
368
+ namespace = "workspace",
347
369
  }: FingerprintOptions): RustBuildFingerprint {
348
370
  if (!targets.length) throw new Error("at least one --target is required");
349
371
  const resolvedRoot = resolve(root);
350
- const rustSourceHash = sourceHash(resolvedRoot, sources);
372
+ const rustSourceHash = sourceHash(resolvedRoot, sources, !sourceOnly);
351
373
  const rustc = portable ? undefined : rustcIdentity();
352
374
  const targetEntries = targets
353
375
  .map((specification) => {
@@ -356,13 +378,14 @@ export function fingerprint({
356
378
  const targetConfig = separator < 0 ? "" : specification.slice(separator + 1);
357
379
  return [
358
380
  target,
359
- targetKey({ rustSourceHash, target, targetConfig, toolchain, rustc }),
381
+ targetKey({ rustSourceHash, namespace, target, targetConfig, toolchain, rustc }),
360
382
  ] as const;
361
383
  })
362
384
  .sort(([left], [right]) => left.localeCompare(right));
363
385
  const manifest = {
364
386
  schemaVersion: FINGERPRINT_SCHEMA,
365
387
  versionSlotSchema: VERSION_SLOT_SCHEMA,
388
+ namespace,
366
389
  rustSourceHash,
367
390
  targets: Object.fromEntries(targetEntries),
368
391
  };
@@ -373,6 +396,7 @@ export function fingerprint({
373
396
  if (
374
397
  current.schemaVersion !== manifest.schemaVersion ||
375
398
  current.versionSlotSchema !== manifest.versionSlotSchema ||
399
+ current.namespace !== manifest.namespace ||
376
400
  current.rustSourceHash !== manifest.rustSourceHash ||
377
401
  !targetsMatch
378
402
  ) {
@@ -538,6 +562,8 @@ function fingerprintCommand(args: readonly string[]): void {
538
562
  toolchain: { type: "string", default: "stable" },
539
563
  portable: { type: "boolean", default: false },
540
564
  source: { type: "string", multiple: true },
565
+ "source-only": { type: "boolean", default: false },
566
+ namespace: { type: "string", default: "workspace" },
541
567
  },
542
568
  strict: true,
543
569
  });
@@ -549,6 +575,8 @@ function fingerprintCommand(args: readonly string[]): void {
549
575
  toolchain: values.toolchain,
550
576
  portable: values.portable,
551
577
  sources: values.source ?? [],
578
+ sourceOnly: values["source-only"],
579
+ namespace: values.namespace,
552
580
  });
553
581
  }
554
582
 
package/src/openapi.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  /**
2
- * OpenAPI generator (tsoa-based).
2
+ * OpenAPI generator for tsoa controllers and code-first Rust producers.
3
3
  *
4
- * Scans `server`/`node` packages for modules that **import tsoa**
5
- * (`from 'tsoa'` / `from '@tsoa/runtime'`) and, for each package that has them,
6
- * generates a read-only `<root>/openapi/<name>` package:
4
+ * TypeScript packages are discovered from tsoa imports. Rust producers are
5
+ * recorded by `DBXToolsRustWorkspace`. Both feed the same optimized generated
6
+ * package:
7
7
  *
8
8
  * - `openapi.json` - the OpenAPI 3 spec (tsoa `generateSpec`, then Speakeasy
9
9
  * optimization to extract duplicate inline schemas into components).
@@ -33,11 +33,11 @@ import {
33
33
  writeFileSync,
34
34
  } from "node:fs";
35
35
  import { createRequire } from "node:module";
36
- import { join } from "node:path";
36
+ import { isAbsolute, join, relative, resolve } from "node:path";
37
37
  import { promisify } from "node:util";
38
38
  import * as bin from "@dbx-tools/core/bin";
39
39
  import { find } from "@dbx-tools/path";
40
- import { log, stringUtils } from "@dbx-tools/shared-core";
40
+ import { log, object, stringUtils } from "@dbx-tools/shared-core";
41
41
  import type * as ts from "typescript";
42
42
  import { lazyRequire } from "./_lazy-require.ts";
43
43
  import { makeReadonly, makeWritable, stampGenerated } from "./generated.ts";
@@ -48,6 +48,7 @@ import {
48
48
  toPosix,
49
49
  recordedPackages,
50
50
  } from "./packages.ts";
51
+ import type { RustOpenApiMapping, RustWorkspaceMapping } from "./project-rs.ts";
51
52
  import type { ReleaseUnitGraph } from "./release-catalog.ts";
52
53
  import { readWorkspaceVersion } from "./workspace-version.ts";
53
54
 
@@ -62,45 +63,95 @@ const SPEAKEASY_OPENAPI_RELEASE_URL = `https://github.com/speakeasy-api/openapi/
62
63
  const execFileAsync = promisify(execFile);
63
64
 
64
65
  // prettier-ignore
65
- const CLIENT_TEMPLATE = (
66
- // ============================================================================
67
- /*ts*/`
68
- import createClient, { type ClientOptions } from "openapi-fetch";
69
- import type { paths } from "./schema";
70
-
71
- /** Create a typed OpenAPI client (openapi-fetch); safe to use in the browser. */
72
- export function createApiClient(options?: ClientOptions) {
73
- return createClient<paths>(options);
74
- }
75
- `
76
- // ============================================================================
77
- );
78
- const CLIENT_SRC = stringUtils.dedent(CLIENT_TEMPLATE, { trimEnd: false });
66
+ const CLIENT_SRC =
67
+ stringUtils.dedent(
68
+ // ============================================================================
69
+ /*ts*/`
70
+ import createClient, { type ClientOptions } from "openapi-fetch";
71
+ import type { paths } from "./schema";
72
+
73
+ /** Create a typed OpenAPI client (openapi-fetch); safe to use in the browser. */
74
+ export function createApiClient(options?: ClientOptions) {
75
+ return createClient<paths>(options);
76
+ }
77
+ `
78
+ // ============================================================================
79
+ ) + "\n";
79
80
 
80
81
  /** True if any module file in `<pkg>/src` matches {@link TSOA_IMPORT}. */
81
82
  function hasTsoaControllers(pkg: Pick<RecordedPackage, "dir">): boolean {
82
- const srcDir = join(pkg.dir, "src");
83
- return [...find.findFiles("**/*", { cwd: srcDir })]
83
+ const srcDir = tsoaSource(pkg);
84
+ return object
85
+ .sequence(find.findFiles("**/*", { cwd: srcDir }))
84
86
  .filter(isModuleFile)
85
- .some((f) => TSOA_IMPORT.test(readFileSync(join(srcDir, f), "utf8")));
87
+ .some((file) => TSOA_IMPORT.test(readFileSync(join(srcDir, file), "utf8")));
88
+ }
89
+
90
+ function tsoaSource(pkg: Pick<RecordedPackage, "dir">): string {
91
+ return join(pkg.dir, "src");
92
+ }
93
+
94
+ function isTsoaCandidate(pkg: RecordedPackage): boolean {
95
+ return pkg.tags.includes("server") || pkg.tags.includes("node");
86
96
  }
87
97
 
88
98
  /** `server`/`node` packages (never the generated `openapi` tag) with a tsoa import. */
89
- function controllerPackages(): RecordedPackage[] {
90
- return recordedPackages().filter(
91
- (p) => (p.tags.includes("server") || p.tags.includes("node")) && hasTsoaControllers(p),
92
- );
99
+ function controllerPackages(): object.Sequence<RecordedPackage> {
100
+ return object.sequence(recordedPackages()).filter(isTsoaCandidate).filter(hasTsoaControllers);
101
+ }
102
+
103
+ function rustOpenapiMappings(): RustOpenApiMapping[] {
104
+ const manifest = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")) as unknown;
105
+ if (!object.isRecord(manifest)) return [];
106
+ const config = manifest.dbxToolsConfig;
107
+ if (!object.isRecord(config) || !object.isRecord(config.rust)) return [];
108
+ const mapping = config.rust as unknown as Partial<RustWorkspaceMapping>;
109
+ return Array.isArray(mapping.openapi) ? [...mapping.openapi] : [];
110
+ }
111
+
112
+ function inside(path: string, directory: string): boolean {
113
+ const child = relative(resolve(directory), resolve(path));
114
+ return child === "" || (!child.startsWith("..") && !isAbsolute(child));
115
+ }
116
+
117
+ /** Whether one changed source belongs to any producing package directory. */
118
+ export function isOpenapiProducerSource(path: string, producers: readonly string[]): boolean {
119
+ const absolute = resolve(path);
120
+ return object.sequence(producers).some((producer) => inside(absolute, producer));
121
+ }
122
+
123
+ function rustOpenapiSources(mapping: RustOpenApiMapping): string[] {
124
+ return [resolve(repoRoot, mapping.rust, "src"), resolve(repoRoot, mapping.rust, "Cargo.toml")];
93
125
  }
94
126
 
95
- /** True if the changed path is a source file that matches {@link TSOA_IMPORT}. */
96
- export function isTsoaController(path: string): boolean {
97
- const posix = toPosix(path);
98
- return (
99
- !posix.includes(`/${OPENAPI_TAG}/`) &&
100
- isModuleFile(path) &&
101
- existsSync(path) &&
102
- TSOA_IMPORT.test(readFileSync(path, "utf8"))
103
- );
127
+ /** Source roots consumed by TypeScript and Rust OpenAPI producers. */
128
+ export function openapiWatchRoots(): string[] {
129
+ return [
130
+ ...controllerPackages()
131
+ .map((pkg) => resolve(tsoaSource(pkg)))
132
+ .join(object.sequence(rustOpenapiMappings()).flatMap(rustOpenapiSources))
133
+ .distinct(),
134
+ ];
135
+ }
136
+
137
+ /** Whether one changed source belongs to an OpenAPI-producing package. */
138
+ export function isOpenapiSource(path: string): boolean {
139
+ const absolute = resolve(path);
140
+ const segments = toPosix(relative(repoRoot, absolute)).split("/");
141
+ if (segments.includes(OPENAPI_TAG)) return false;
142
+ if (
143
+ object
144
+ .sequence(rustOpenapiMappings())
145
+ .flatMap(rustOpenapiSources)
146
+ .some((source) => inside(absolute, source))
147
+ ) {
148
+ return true;
149
+ }
150
+ return object
151
+ .sequence(recordedPackages())
152
+ .filter(isTsoaCandidate)
153
+ .filter((pkg) => inside(absolute, tsoaSource(pkg)))
154
+ .some(hasTsoaControllers);
104
155
  }
105
156
 
106
157
  /** GitHub release asset name for Speakeasy's OpenAPI binary. */
@@ -145,6 +196,59 @@ export async function optimizeOpenapiSpec(specPath: string, executable?: string)
145
196
  await execFileAsync(openapi, ["spec", "optimize", specPath, "--write", "--non-interactive"]);
146
197
  }
147
198
 
199
+ /** Cargo arguments that export one Rust producer's aide document. */
200
+ export function rustOpenapiArgs(
201
+ mapping: RustOpenApiMapping,
202
+ output: string,
203
+ targetDir = join(repoRoot, "target/openapi"),
204
+ ): string[] {
205
+ return [
206
+ "run",
207
+ "--quiet",
208
+ "--target-dir",
209
+ targetDir,
210
+ "--package",
211
+ mapping.crate,
212
+ ...(mapping.noDefaultFeatures ? ["--no-default-features"] : []),
213
+ ...(mapping.features.length ? ["--features", mapping.features.join(",")] : []),
214
+ ...(mapping.binary ? ["--bin", mapping.binary] : []),
215
+ "--",
216
+ "--generate-spec",
217
+ output,
218
+ ];
219
+ }
220
+
221
+ type OpenApiTypeTools = {
222
+ openapiTS: typeof import("openapi-typescript").default;
223
+ astToString: typeof import("openapi-typescript").astToString;
224
+ };
225
+
226
+ async function writeClientPackage(
227
+ outDir: string,
228
+ source: string,
229
+ tools: OpenApiTypeTools,
230
+ ): Promise<void> {
231
+ const srcDir = join(outDir, "src");
232
+ mkdirSync(srcDir, { recursive: true });
233
+ const specPath = join(outDir, "openapi.json");
234
+ const spec = JSON.parse(readFileSync(specPath, "utf8"));
235
+ const schemaPath = join(srcDir, "schema.ts");
236
+ makeWritable(schemaPath);
237
+ writeFileSync(schemaPath, tools.astToString(await tools.openapiTS(spec)));
238
+ stampGenerated(schemaPath, {
239
+ tool: "projen openapi (Speakeasy + openapi-typescript)",
240
+ source,
241
+ });
242
+
243
+ const clientPath = join(srcDir, "client.ts");
244
+ makeWritable(clientPath);
245
+ writeFileSync(clientPath, CLIENT_SRC);
246
+ stampGenerated(clientPath, {
247
+ tool: "projen openapi (openapi-fetch)",
248
+ source: "./schema",
249
+ });
250
+ }
251
+
148
252
  /**
149
253
  * Regenerate the `openapi` packages from every server/node package with a tsoa
150
254
  * import. Returns the package dirs it wrote so the caller can rebuild their barrels.
@@ -152,35 +256,43 @@ export async function optimizeOpenapiSpec(specPath: string, executable?: string)
152
256
  * members in `pnpm-workspace.yaml`.
153
257
  */
154
258
  export async function generateOpenapi(): Promise<string[]> {
155
- const pkgs = controllerPackages();
259
+ const pkgs = controllerPackages().toArray();
260
+ const rustMappings = rustOpenapiMappings();
156
261
  // Same reasoning as codegen's empty case: a workspace with no tsoa controllers
157
262
  // is not a condition worth a line on every synth.
158
- if (pkgs.length === 0) {
159
- logger.debug("no tsoa controllers found in any server/node package");
263
+ if (pkgs.length === 0 && rustMappings.length === 0) {
264
+ logger.debug("no TypeScript or Rust OpenAPI producers found");
160
265
  return [];
161
266
  }
162
267
 
163
268
  // Lazy, resilient loads: tsoa + typescript are CJS (require), openapi-typescript
164
269
  // is ESM (dynamic import).
165
- const require = createRequire(import.meta.url);
166
- const { generateSpec } = lazyRequire<typeof import("tsoa")>(
167
- require,
168
- "tsoa",
169
- "openapi generation",
170
- );
171
- const tsRuntime = lazyRequire<typeof ts>(require, "typescript", "openapi generation");
172
270
  const { default: openapiTS, astToString } = await import("openapi-typescript");
271
+ const tools = { openapiTS, astToString };
173
272
 
174
- // Read tsoa's controllers with decorator support; skipLibCheck keeps third-party
175
- // `.d.ts` out of the spec-generation compile.
176
- const compilerOptions: ts.CompilerOptions = {
177
- experimentalDecorators: true,
178
- target: tsRuntime.ScriptTarget.ES2022,
179
- module: tsRuntime.ModuleKind.ESNext,
180
- moduleResolution: tsRuntime.ModuleResolutionKind.Bundler,
181
- esModuleInterop: true,
182
- skipLibCheck: true,
183
- };
273
+ const typescript =
274
+ pkgs.length > 0
275
+ ? (() => {
276
+ const require = createRequire(import.meta.url);
277
+ const { generateSpec } = lazyRequire<typeof import("tsoa")>(
278
+ require,
279
+ "tsoa",
280
+ "openapi generation",
281
+ );
282
+ const runtime = lazyRequire<typeof ts>(require, "typescript", "openapi generation");
283
+ return {
284
+ generateSpec,
285
+ compilerOptions: {
286
+ experimentalDecorators: true,
287
+ target: runtime.ScriptTarget.ES2022,
288
+ module: runtime.ModuleKind.ESNext,
289
+ moduleResolution: runtime.ModuleResolutionKind.Bundler,
290
+ esModuleInterop: true,
291
+ skipLibCheck: true,
292
+ } satisfies ts.CompilerOptions,
293
+ };
294
+ })()
295
+ : undefined;
184
296
 
185
297
  const releaseGraphPath = join(repoRoot, ".projen/release-units.json");
186
298
  const releaseGraph = existsSync(releaseGraphPath)
@@ -196,8 +308,7 @@ export async function generateOpenapi(): Promise<string[]> {
196
308
  // its npm name - `p.name` is the (possibly-overridden) manifest name.
197
309
  const leaf = p.relPath.split("/").pop() ?? p.relPath;
198
310
  const outDir = join(repoRoot, p.root, OPENAPI_TAG, leaf);
199
- const srcDir = join(outDir, "src");
200
- mkdirSync(srcDir, { recursive: true });
311
+ mkdirSync(outDir, { recursive: true });
201
312
 
202
313
  // 1) tsoa writes a temporary openapi.json, Speakeasy optimizes it there, then
203
314
  // the complete spec moves into place so readers never observe an intermediate file.
@@ -205,7 +316,7 @@ export async function generateOpenapi(): Promise<string[]> {
205
316
  const tempDir = mkdtempSync(join(outDir, ".openapi-"));
206
317
  const tempSpecPath = join(tempDir, "openapi.json");
207
318
  try {
208
- await generateSpec(
319
+ await typescript!.generateSpec(
209
320
  {
210
321
  entryFile: "",
211
322
  noImplicitAdditionalProperties: "throw-on-extras",
@@ -216,7 +327,7 @@ export async function generateOpenapi(): Promise<string[]> {
216
327
  name: `${p.relPath} API`,
217
328
  version: specVersion,
218
329
  },
219
- compilerOptions,
330
+ typescript!.compilerOptions,
220
331
  );
221
332
  await optimizeOpenapiSpec(tempSpecPath);
222
333
  makeWritable(specPath);
@@ -225,28 +336,29 @@ export async function generateOpenapi(): Promise<string[]> {
225
336
  rmSync(tempDir, { recursive: true, force: true });
226
337
  }
227
338
  makeReadonly(specPath);
228
-
229
- // 2) src/schema.ts: types generated from the spec (openapi-typescript).
230
- const spec = JSON.parse(readFileSync(specPath, "utf8"));
231
- const schemaPath = join(srcDir, "schema.ts");
232
- makeWritable(schemaPath);
233
- writeFileSync(schemaPath, astToString(await openapiTS(spec)));
234
- stampGenerated(schemaPath, {
235
- tool: "projen openapi (tsoa + Speakeasy + openapi-typescript)",
236
- source: `the tsoa controllers in ${p.relPath}`,
237
- });
238
-
239
- // 3) src/client.ts: a typed openapi-fetch client over those types.
240
- const clientPath = join(srcDir, "client.ts");
241
- makeWritable(clientPath);
242
- writeFileSync(clientPath, CLIENT_SRC);
243
- stampGenerated(clientPath, {
244
- tool: "projen openapi (openapi-fetch)",
245
- source: "./schema",
246
- });
339
+ await writeClientPackage(outDir, `the tsoa controllers in ${p.relPath}`, tools);
247
340
 
248
341
  written.push(outDir);
249
342
  logger.success(`openapi/${leaf} (from ${p.relPath})`);
250
343
  }
344
+ for (const mapping of rustMappings) {
345
+ const outDir = resolve(repoRoot, mapping.output);
346
+ mkdirSync(outDir, { recursive: true });
347
+ const specPath = join(outDir, "openapi.json");
348
+ const tempDir = mkdtempSync(join(outDir, ".openapi-"));
349
+ const tempSpecPath = join(tempDir, "openapi.json");
350
+ try {
351
+ await execFileAsync("cargo", rustOpenapiArgs(mapping, tempSpecPath), { cwd: repoRoot });
352
+ await optimizeOpenapiSpec(tempSpecPath);
353
+ makeWritable(specPath);
354
+ renameSync(tempSpecPath, specPath);
355
+ } finally {
356
+ rmSync(tempDir, { recursive: true, force: true });
357
+ }
358
+ makeReadonly(specPath);
359
+ await writeClientPackage(outDir, `the aide routes in ${mapping.rust}`, tools);
360
+ written.push(outDir);
361
+ logger.success(`${mapping.output} (from ${mapping.rust})`);
362
+ }
251
363
  return written;
252
364
  }
package/src/project-py.ts CHANGED
@@ -465,9 +465,9 @@ export class DBXToolsPythonWorkspace extends Component {
465
465
  if (project.releaseCatalog.mode === "independent") {
466
466
  workflow.addJob("build-python", {
467
467
  if: usesRustArtifacts
468
- ? "${{ always() && needs.release-plan.outputs.python == 'true' && needs.rust-build.result != 'failure' && needs.rust-build.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'python') }}"
468
+ ? "${{ always() && needs.release-plan.outputs.python == 'true' && needs.rust-uniffi.result != 'failure' && needs.rust-uniffi.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'python') }}"
469
469
  : "${{ needs.release-plan.outputs.python == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'python') }}",
470
- needs: ["release-plan", ...(usesRustArtifacts ? ["rust-build"] : [])],
470
+ needs: ["release-plan", ...(usesRustArtifacts ? ["rust-uniffi"] : [])],
471
471
  runsOn: ["ubuntu-latest"],
472
472
  permissions: { actions: JobPermission.READ, contents: JobPermission.READ },
473
473
  timeoutMinutes: 20,
@@ -546,9 +546,9 @@ export class DBXToolsPythonWorkspace extends Component {
546
546
  }
547
547
  workflow.addJob("build-python", {
548
548
  if: usesRustArtifacts
549
- ? "${{ 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 == 'python') }}"
549
+ ? "${{ always() && needs.verify-context.result == 'success' && needs.rust-uniffi.result != 'failure' && needs.rust-uniffi.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'python') }}"
550
550
  : releaseStageCondition("python"),
551
- needs: ["verify-context", ...(usesRustArtifacts ? ["rust-build"] : [])],
551
+ needs: ["verify-context", ...(usesRustArtifacts ? ["rust-uniffi"] : [])],
552
552
  runsOn: ["ubuntu-latest"],
553
553
  permissions: { actions: JobPermission.READ, contents: JobPermission.READ },
554
554
  timeoutMinutes: 20,