@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.
package/src/project-rs.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** Filesystem-discovered Rust workspaces and UniFFI binding package wiring. */
2
2
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
- import { join, relative, resolve } from "node:path";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
4
  import * as projectUtils from "@dbx-tools/core/project-utils";
5
5
  import { stringUtils } from "@dbx-tools/shared-core";
6
6
  import { Component, Project, TextFile, TomlFile, javascript } from "projen";
@@ -15,6 +15,7 @@ import {
15
15
  type DBXToolsRustProjectOptions as RustProjectOptions,
16
16
  type RustCrateOptions as RustCrateConfiguration,
17
17
  type RustCliOptions as RustCliConfiguration,
18
+ type RustOpenApiOptions as RustOpenApiConfiguration,
18
19
  } from "./_rust-project.ts";
19
20
  import {
20
21
  configureRustReleaseTask,
@@ -57,6 +58,7 @@ export type CargoExampleOptions = RustCargoExampleOptions;
57
58
  export type DBXToolsRustProjectOptions = RustProjectOptions;
58
59
  export type RustCrateOptions = RustCrateConfiguration;
59
60
  export type RustCliOptions = RustCliConfiguration;
61
+ export type RustOpenApiOptions = RustOpenApiConfiguration;
60
62
 
61
63
  /** Public Rust project facade; implementation lives apart from workspace/release coordination. */
62
64
  export class DBXToolsRustProject extends RustProject {
@@ -269,12 +271,23 @@ export interface RustBindingMapping {
269
271
  readonly dependencies?: readonly string[];
270
272
  }
271
273
 
274
+ /** Persisted Rust OpenAPI producer consumed by the shared OpenAPI task. */
275
+ export interface RustOpenApiMapping {
276
+ readonly crate: string;
277
+ readonly rust: string;
278
+ readonly output: string;
279
+ readonly binary?: string;
280
+ readonly features: readonly string[];
281
+ readonly noDefaultFeatures: boolean;
282
+ }
283
+
272
284
  /** Persisted Rust workspace state consumed by `sync --watch`. */
273
285
  export interface RustWorkspaceMapping {
274
286
  readonly root: string;
275
287
  readonly crates: readonly string[];
276
288
  readonly bindings: readonly RustBindingMapping[];
277
289
  readonly binaries: readonly RustReleaseBinaryMapping[];
290
+ readonly openapi: readonly RustOpenApiMapping[];
278
291
  }
279
292
 
280
293
  /** One platform archive published for a Rust CLI binary. */
@@ -296,6 +309,10 @@ export interface RustReleaseBinaryMapping {
296
309
  readonly tagPrefix: string;
297
310
  readonly tag: string;
298
311
  readonly repository: string;
312
+ /** Cargo crate published or mirrored for `cargo install` when GitHub has no archive. */
313
+ readonly crateName: string;
314
+ /** Feature flags passed to `cargo install --features` for this binary target. */
315
+ readonly cargoFeatures: readonly string[];
299
316
  readonly assets: readonly RustReleaseBinaryAssetMapping[];
300
317
  }
301
318
 
@@ -392,6 +409,7 @@ function planRustReleaseBinaries(
392
409
  release: pkg.packageOptions.release,
393
410
  excludedOs: pkg.packageOptions.releaseExcludeOs,
394
411
  cli: pkg.packageOptions.cli,
412
+ requiredFeatures: undefined,
395
413
  },
396
414
  ]
397
415
  : []),
@@ -404,6 +422,7 @@ function planRustReleaseBinaries(
404
422
  release: binary.release,
405
423
  excludedOs: binary.releaseExcludeOs,
406
424
  cli: binary.cli!,
425
+ requiredFeatures: binary.requiredFeatures,
407
426
  })),
408
427
  ];
409
428
  return configuredBinaries.map((binary) => {
@@ -450,6 +469,8 @@ function planRustReleaseBinaries(
450
469
  tagPrefix,
451
470
  tag: `${tagPrefix}${identity.version}`,
452
471
  repository: resolved.repository,
472
+ crateName: pkg.crateName,
473
+ cargoFeatures: [...(binary.requiredFeatures ?? [])],
453
474
  assets: resolved.nativeTargets
454
475
  .filter((target) => !excludedOs.has(target.os))
455
476
  .map((target) => ({
@@ -770,11 +791,26 @@ function createRustWorkspaceMapping(
770
791
  binaries: readonly RustReleaseBinaryMapping[],
771
792
  resolved: ResolvedRustWorkspaceOptions,
772
793
  ): RustWorkspaceMapping {
794
+ const openapiRoot = join(dirname(resolved.nodeRoot), "openapi");
773
795
  return {
774
796
  root: resolved.root,
775
797
  crates: packages.map((pkg) => `${resolved.root}/${pkg.packageOptions.directory}`),
776
798
  bindings,
777
799
  binaries,
800
+ openapi: packages.flatMap((pkg) => {
801
+ if (!pkg.packageOptions.openapi) return [];
802
+ const options = pkg.packageOptions.openapi === true ? {} : pkg.packageOptions.openapi;
803
+ return [
804
+ {
805
+ crate: pkg.crateName,
806
+ rust: `${resolved.root}/${pkg.packageOptions.directory}`,
807
+ output: toPosix(join(openapiRoot, pkg.packageOptions.directory)),
808
+ ...(options.binary ? { binary: options.binary } : {}),
809
+ features: [...(options.features ?? [])],
810
+ noDefaultFeatures: options.noDefaultFeatures ?? false,
811
+ },
812
+ ];
813
+ }),
778
814
  };
779
815
  }
780
816
 
@@ -963,6 +999,9 @@ export class DBXToolsRustWorkspace extends Component {
963
999
  name: binary.binaryName,
964
1000
  generated: true,
965
1001
  data: {
1002
+ binary: binary.binaryName,
1003
+ crate: binary.crateName,
1004
+ features: binary.cargoFeatures,
966
1005
  targets: resolved.nativeTargets.filter((target) =>
967
1006
  binary.assets.some((asset) => asset.os === target.os && asset.cpu === target.cpu),
968
1007
  ),
@@ -1059,8 +1098,17 @@ export class DBXToolsRustWorkspace extends Component {
1059
1098
  }
1060
1099
  configureRustReleaseTask(project, plan);
1061
1100
  if (project.releaseCatalog.mode === "independent") {
1062
- if (plan.hasTargetOutputs && plan.targets.length) {
1063
- workflow.addJob("rust-build", rustBuildJob(plan, independentReleaseSetupSteps(project)));
1101
+ if (plan.uniffiTargets.length) {
1102
+ workflow.addJob(
1103
+ "rust-uniffi",
1104
+ rustBuildJob(plan, "uniffi", independentReleaseSetupSteps(project)),
1105
+ );
1106
+ }
1107
+ if (plan.binaryTargets.length) {
1108
+ workflow.addJob(
1109
+ "rust-binaries",
1110
+ rustBuildJob(plan, "binaries", independentReleaseSetupSteps(project)),
1111
+ );
1064
1112
  }
1065
1113
  if (plan.publicCrates.length) {
1066
1114
  workflow.addJob("publish-cargo", independentRustCargoPublishJob(project, plan));
@@ -1081,15 +1129,18 @@ export class DBXToolsRustWorkspace extends Component {
1081
1129
  }
1082
1130
  return;
1083
1131
  }
1084
- if (plan.hasTargetOutputs && plan.targets.length) {
1085
- workflow.addJob("rust-build", rustBuildJob(plan));
1132
+ if (plan.uniffiTargets.length) {
1133
+ workflow.addJob("rust-uniffi", rustBuildJob(plan, "uniffi"));
1134
+ }
1135
+ if (plan.binaryTargets.length) {
1136
+ workflow.addJob("rust-binaries", rustBuildJob(plan, "binaries"));
1086
1137
  }
1087
1138
  if (plan.publicCrates.length) {
1088
1139
  workflow.addJob("publish-cargo", rustCargoPublishJob(plan, false));
1089
1140
  workflow.addJob("publish-local-cargo", rustCargoPublishJob(plan, true));
1090
1141
  }
1091
1142
  if (plan.releaseBinaries.length) {
1092
- workflow.addJob("publish-github-release", rustGitHubReleaseJob());
1143
+ workflow.addJob("publish-github-release", rustGitHubReleaseJob(plan));
1093
1144
  }
1094
1145
  if (plan.nodeBindings.length && hasNodeRelease(project)) {
1095
1146
  workflow.addJob("publish-native-npm", rustNativeNpmPublishJob(project));
@@ -47,20 +47,23 @@ export interface PlannedRustTarget {
47
47
  readonly runner: string;
48
48
  readonly python: string;
49
49
  readonly libc: string;
50
- readonly cargoExcludes: string;
51
50
  readonly packages: readonly string[];
51
+ readonly binaries: readonly string[];
52
+ readonly features: readonly string[];
53
+ readonly fingerprintConfig: string;
52
54
  }
53
55
 
54
56
  /** Complete plan persisted as a workflow artifact and used for recovery. */
55
57
  export interface ReleasePlan {
56
- readonly schemaVersion: 1;
58
+ readonly schemaVersion: 2;
57
59
  readonly units: readonly PlannedReleaseUnit[];
58
60
  readonly publishBatches: readonly (readonly string[])[];
59
61
  readonly nodePackages: readonly PlannedPackage[];
60
62
  readonly pythonPackages: readonly PlannedPackage[];
61
63
  readonly rustPackages: readonly PlannedPackage[];
62
64
  readonly artifacts: readonly PlannedArtifact[];
63
- readonly rustTargets: readonly PlannedRustTarget[];
65
+ readonly rustUniffiTargets: readonly PlannedRustTarget[];
66
+ readonly rustBinaryTargets: readonly PlannedRustTarget[];
64
67
  readonly stages: {
65
68
  readonly rust: boolean;
66
69
  readonly python: boolean;
@@ -130,18 +133,53 @@ export function buildReleasePlan(
130
133
  ]);
131
134
  }
132
135
  }
133
- const rustTargetsByKey = new Map<string, PlannedRustTarget>();
136
+ const rustTargetsByKind = {
137
+ uniffi: new Map<string, PlannedRustTarget>(),
138
+ binaries: new Map<string, PlannedRustTarget>(),
139
+ };
134
140
  for (const artifact of artifacts) {
135
141
  const targets = artifact.data?.targets;
136
142
  if (!Array.isArray(targets)) continue;
143
+ const kind = artifact.kind === "github-binary" ? "binaries" : "uniffi";
137
144
  for (const value of targets) {
138
145
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
139
146
  const target = value as Record<string, unknown>;
140
147
  const required = ["os", "cpu", "node", "cargo", "runner", "python"] as const;
141
148
  if (!required.every((field) => typeof target[field] === "string")) continue;
142
149
  const key = String(target.node);
143
- const currentTarget = rustTargetsByKey.get(key);
144
- rustTargetsByKey.set(key, {
150
+ const targetsByKey = rustTargetsByKind[kind];
151
+ const currentTarget = targetsByKey.get(key);
152
+ const packages = [
153
+ ...new Set([
154
+ ...(currentTarget?.packages ?? []),
155
+ ...(rustPackagesByUnit.get(artifact.unit) ?? []),
156
+ ]),
157
+ ].sort();
158
+ const binaries =
159
+ kind === "binaries"
160
+ ? [
161
+ ...new Set([
162
+ ...(currentTarget?.binaries ?? []),
163
+ ...(typeof artifact.data?.binary === "string" ? [artifact.data.binary] : []),
164
+ ]),
165
+ ].sort()
166
+ : [];
167
+ const configuredFeatures = Array.isArray(artifact.data?.features)
168
+ ? artifact.data.features.filter((feature): feature is string => typeof feature === "string")
169
+ : [];
170
+ const crate = typeof artifact.data?.crate === "string" ? artifact.data.crate : undefined;
171
+ const features =
172
+ kind === "uniffi"
173
+ ? packages.map((pkg) => `${pkg}/uniffi-bindgen`)
174
+ : [
175
+ ...new Set([
176
+ ...(currentTarget?.features ?? []),
177
+ ...(crate
178
+ ? configuredFeatures.map((feature) => `${crate}/${feature}`)
179
+ : configuredFeatures),
180
+ ]),
181
+ ].sort();
182
+ targetsByKey.set(key, {
145
183
  os: String(target.os),
146
184
  cpu: String(target.cpu),
147
185
  node: String(target.node),
@@ -149,17 +187,19 @@ export function buildReleasePlan(
149
187
  runner: String(target.runner),
150
188
  python: String(target.python),
151
189
  libc: typeof target.libc === "string" ? target.libc : "",
152
- cargoExcludes: typeof target.cargoExcludes === "string" ? target.cargoExcludes : "",
153
- packages: [
154
- ...new Set([
155
- ...(currentTarget?.packages ?? []),
156
- ...(rustPackagesByUnit.get(artifact.unit) ?? []),
157
- ]),
158
- ].sort(),
190
+ packages,
191
+ binaries,
192
+ features,
193
+ fingerprintConfig: JSON.stringify({ packages, binaries, features }),
159
194
  });
160
195
  }
161
196
  }
162
- const rustTargets = [...rustTargetsByKey.values()].sort((a, b) => a.node.localeCompare(b.node));
197
+ const rustUniffiTargets = [...rustTargetsByKind.uniffi.values()].sort((a, b) =>
198
+ a.node.localeCompare(b.node),
199
+ );
200
+ const rustBinaryTargets = [...rustTargetsByKind.binaries.values()].sort((a, b) =>
201
+ a.node.localeCompare(b.node),
202
+ );
163
203
  const publishBatches = current.publishBatches
164
204
  .map((batch) => batch.filter((unit) => selected.has(unit)))
165
205
  .filter((batch) => batch.length > 0);
@@ -179,14 +219,15 @@ export function buildReleasePlan(
179
219
  .filter(([, enabled]) => !enabled)
180
220
  .map(([name]) => name);
181
221
  return {
182
- schemaVersion: 1,
222
+ schemaVersion: 2,
183
223
  units,
184
224
  publishBatches,
185
225
  nodePackages,
186
226
  pythonPackages,
187
227
  rustPackages,
188
228
  artifacts,
189
- rustTargets,
229
+ rustUniffiTargets,
230
+ rustBinaryTargets,
190
231
  stages,
191
232
  omittedStages,
192
233
  };
package/src/release.ts CHANGED
@@ -591,7 +591,8 @@ function independentReleasePlanJob(project: DBXToolsJavaScriptProject): Job {
591
591
  python_packages: { stepId: "plan", outputName: "python_packages" },
592
592
  rust_packages: { stepId: "plan", outputName: "rust_packages" },
593
593
  artifacts: { stepId: "plan", outputName: "artifacts" },
594
- rust_targets: { stepId: "plan", outputName: "rust_targets" },
594
+ rust_uniffi_targets: { stepId: "plan", outputName: "rust_uniffi_targets" },
595
+ rust_binary_targets: { stepId: "plan", outputName: "rust_binary_targets" },
595
596
  },
596
597
  steps: [
597
598
  {
package/src/watch.ts CHANGED
@@ -81,13 +81,28 @@ function ignoredPath(path: string): boolean {
81
81
  * (e.g. the projenrc watcher passes `{ dot: false }` so its lone dotfile target,
82
82
  * `.projenrc.ts`, isn't pruned by the default dotfile group and left with nothing to
83
83
  * watch - which would let the process exit immediately).
84
+ *
85
+ * `check` is forwarded to the workspace mutation lock so a watcher that can tell
86
+ * the batch is a no-op never waits on an unrelated holder.
84
87
  */
88
+ export interface WatchLoopOptions {
89
+ ignoreOptions?: IgnoreGroupOptions;
90
+ check?: (changed: string[]) => boolean | Promise<boolean>;
91
+ }
92
+
93
+ function resolveWatchLoopOptions(config?: IgnoreGroupOptions | WatchLoopOptions): WatchLoopOptions {
94
+ if (!config) return {};
95
+ if ("check" in config || "ignoreOptions" in config) return config as WatchLoopOptions;
96
+ return { ignoreOptions: config as IgnoreGroupOptions };
97
+ }
98
+
85
99
  export function watchLoop(
86
100
  tag: string,
87
101
  paths: string[],
88
102
  onBatch: (changed: string[]) => void | Promise<void>,
89
- ignoreOptions?: IgnoreGroupOptions,
103
+ config?: IgnoreGroupOptions | WatchLoopOptions,
90
104
  ): void {
105
+ const { ignoreOptions, check } = resolveWatchLoopOptions(config);
91
106
  const pending = new Set<string>();
92
107
  let timer: ReturnType<typeof setTimeout> | undefined;
93
108
  let running = false;
@@ -105,7 +120,9 @@ export function watchLoop(
105
120
  pending.clear();
106
121
  try {
107
122
  if (relevant.length) {
108
- await withWorkspaceMutationLock(repoRoot, () => onBatch(relevant));
123
+ await withWorkspaceMutationLock(repoRoot, () => onBatch(relevant), {
124
+ check: check ? () => check(relevant) : undefined,
125
+ });
109
126
  }
110
127
  } catch (err) {
111
128
  logger.error(`${tag} cycle failed:`, err instanceof Error ? err.message : err);
@@ -6,17 +6,43 @@ import { log } from "@dbx-tools/shared-core";
6
6
  const MUTATION_LOCK_SCOPE = "dbx-tools-workspace-mutation";
7
7
  const logger = log.logger("projen:workspace-lock");
8
8
 
9
+ export interface WorkspaceMutationLockOptions {
10
+ /**
11
+ * When this returns false, skip both the lock and the callback. After the
12
+ * lock is held it runs again so a completed holder can make the work a no-op
13
+ * without taking the critical section only to return.
14
+ */
15
+ check?: () => boolean | Promise<boolean>;
16
+ }
17
+
9
18
  /**
10
19
  * Serialize synthesis, generated-file watchers, and release preparation for one
11
20
  * repository without coordinating unrelated worktrees or repositories.
21
+ *
22
+ * `check` is check-lock-check: a false result before acquire never waits, and a
23
+ * false result after acquire releases immediately without running `callback`.
12
24
  */
13
- export function withWorkspaceMutationLock<T>(
25
+ export async function withWorkspaceMutationLock<T>(
14
26
  root: string,
15
27
  callback: () => T | Promise<T>,
16
- ): Promise<T> {
28
+ options?: WorkspaceMutationLockOptions,
29
+ ): Promise<T | undefined> {
17
30
  const repository = resolve(root);
18
- return fileLock.withFileLock([MUTATION_LOCK_SCOPE, repository], callback, {
19
- backends: process.platform === "win32" ? ["file"] : ["flock", "file"],
20
- onWait: () => logger.info("waiting for workspace mutation lock", { repository }),
21
- });
31
+ const check = options?.check;
32
+ if (check && !(await check())) {
33
+ return undefined;
34
+ }
35
+ return fileLock.withFileLock(
36
+ [MUTATION_LOCK_SCOPE, repository],
37
+ async () => {
38
+ if (check && !(await check())) {
39
+ return undefined;
40
+ }
41
+ return callback();
42
+ },
43
+ {
44
+ backends: process.platform === "win32" ? ["file"] : ["flock", "file"],
45
+ onWait: () => logger.info("waiting for workspace mutation lock", { repository }),
46
+ },
47
+ );
22
48
  }
package/tasks/barrels.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env -S bun
2
2
  import { sep } from "node:path";
3
3
  import { parseArgs } from "node:util";
4
- import { log, stringUtils } from "@dbx-tools/shared-core";
4
+ import { log, object, stringUtils } from "@dbx-tools/shared-core";
5
5
  import { generateBarrels } from "../src/barrels.ts";
6
6
  import { recordedPackages } from "../src/packages.ts";
7
7
  import { watchLoop, watchRoots } from "../src/watch.ts";
@@ -17,41 +17,58 @@ const { values } = parseArgs({
17
17
  });
18
18
 
19
19
  /** The recorded package dir that owns `abs`, if any (for a targeted barrel rebuild). */
20
- function ownerPackageDir(abs: string, pkgDirs: string[]): string | undefined {
21
- return pkgDirs.find((dir) => abs === dir || abs.startsWith(dir + sep));
20
+ function ownerPackageDir(
21
+ abs: string,
22
+ packages: readonly { readonly dir: string; readonly tags: readonly string[] }[],
23
+ ): string | undefined {
24
+ // The OpenAPI task rebuilds generated-client barrels after writing its files.
25
+ return packages.find(
26
+ ({ dir, tags }) => !tags.includes("openapi") && (abs === dir || abs.startsWith(dir + sep)),
27
+ )?.dir;
28
+ }
29
+
30
+ function changedBarrelTargets(changed: readonly string[]): object.Sequence<string> {
31
+ const packages = recordedPackages();
32
+ return object
33
+ .sequence(changed)
34
+ .map((path) => ownerPackageDir(path, packages))
35
+ .nonNull()
36
+ .distinct();
37
+ }
38
+
39
+ function warnUnownedChanges(changed: readonly string[]): void {
40
+ if (changed.length) {
41
+ logger.warn(
42
+ `no recorded package owns ${stringUtils.pluralize(changed.length, "change")}; ` +
43
+ "run `bun run default` (or touch .projenrc.ts) to pick up a new package folder",
44
+ );
45
+ }
22
46
  }
23
47
 
24
48
  if (values.watch) {
25
49
  // Watch the package roots; a source edit inside a package rebuilds just that
26
50
  // package's `index.ts` barrel (no re-synth - the projenrc watcher owns that).
27
51
  // watchLoop already drops generated paths, so a barrel write never re-triggers us.
28
- watchLoop("barrels", watchRoots(), (changed) => {
29
- const pkgDirs = recordedPackages().map((p) => p.dir);
30
- const dirs = new Set<string>();
31
- const unowned: string[] = [];
32
- for (const p of changed) {
33
- const owner = ownerPackageDir(p, pkgDirs);
34
- if (owner) dirs.add(owner);
35
- else unowned.push(p);
36
- }
37
- // A change under a package root that no RECORDED package owns is almost always a
38
- // NEW package folder: it has no `pnpm-workspace.yaml` member yet, so there is no
39
- // barrel for this watcher to target and only a re-synth can create one. Say so,
40
- // rather than rebuilding every barrel in the repo - the previous fallback, which
41
- // did a full-repo sweep on behalf of a file it could not barrel anyway, and in
42
- // doing so raced the projenrc watcher's own post-synth sweep.
43
- if (dirs.size === 0) {
44
- if (unowned.length) {
45
- logger.warn(
46
- `no recorded package owns ${stringUtils.pluralize(unowned.length, "change")}; ` +
47
- "run `bun run default` (or touch .projenrc.ts) to pick up a new package folder",
48
- );
49
- }
50
- return;
51
- }
52
- const n = generateBarrels({ dirs: [...dirs] });
53
- if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
54
- });
52
+ watchLoop(
53
+ "barrels",
54
+ watchRoots(),
55
+ (changed) => {
56
+ const n = generateBarrels({ dirs: [...changedBarrelTargets(changed)] });
57
+ if (n) logger.success(`rebuilt ${stringUtils.pluralize(n, "barrel")}`);
58
+ },
59
+ {
60
+ check: (changed) => {
61
+ const targets = changedBarrelTargets(changed);
62
+ // A change under a package root that no recorded package owns is a new
63
+ // folder: only a re-synth can create its barrel, so this watcher skips.
64
+ if (!targets.some(() => true)) {
65
+ warnUnownedChanges(changed);
66
+ return false;
67
+ }
68
+ return true;
69
+ },
70
+ },
71
+ );
55
72
  } else {
56
73
  const dirs = Array.isArray(values.dir)
57
74
  ? values.dir.filter((value): value is string => typeof value === "string")
package/tasks/openapi.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env -S bun
2
- import { log, stringUtils } from "@dbx-tools/shared-core";
2
+ import { log, object, stringUtils } from "@dbx-tools/shared-core";
3
3
  import { generateBarrels } from "../src/barrels.ts";
4
- import { generateOpenapi, isTsoaController } from "../src/openapi.ts";
4
+ import { generateOpenapi, isOpenapiSource, openapiWatchRoots } from "../src/openapi.ts";
5
5
  import { runSynth } from "../src/scaffold.ts";
6
- import { watchLoop, watchRoots } from "../src/watch.ts";
6
+ import { watchLoop } from "../src/watch.ts";
7
7
 
8
8
  const logger = log.logger("projen:openapi");
9
9
 
@@ -11,14 +11,18 @@ if (process.argv.includes("--watch")) {
11
11
  // Watch the package roots; a changed tsoa controller regenerates the openapi
12
12
  // packages (spec + client) and rebuilds just their barrels. The projenrc watcher
13
13
  // (alongside under `concurrently`) owns full re-synth, so no re-synth here.
14
- watchLoop("openapi", watchRoots(), async (changed) => {
15
- if (!changed.some(isTsoaController)) return;
16
- const dirs = await generateOpenapi();
17
- if (dirs.length) {
18
- generateBarrels({ dirs });
19
- logger.success(`regenerated openapi (${stringUtils.pluralize(dirs.length, "package")})`);
20
- }
21
- });
14
+ watchLoop(
15
+ "openapi",
16
+ openapiWatchRoots(),
17
+ async () => {
18
+ const dirs = await generateOpenapi();
19
+ if (dirs.length) {
20
+ generateBarrels({ dirs });
21
+ logger.success(`regenerated openapi (${stringUtils.pluralize(dirs.length, "package")})`);
22
+ }
23
+ },
24
+ { check: (changed) => object.sequence(changed).some(isOpenapiSource) },
25
+ );
22
26
  } else {
23
27
  // One-shot: regenerate, then re-synth so any newly created openapi folder becomes a
24
28
  // workspace member (a new package needs install + linking, which the watch path skips).
@@ -75,7 +75,8 @@ new Command()
75
75
  writeOutput("python_packages", plan.pythonPackages);
76
76
  writeOutput("rust_packages", plan.rustPackages);
77
77
  writeOutput("artifacts", plan.artifacts);
78
- writeOutput("rust_targets", plan.rustTargets);
78
+ writeOutput("rust_uniffi_targets", plan.rustUniffiTargets);
79
+ writeOutput("rust_binary_targets", plan.rustBinaryTargets);
79
80
  writeOutput("stages", plan.stages);
80
81
  for (const [stage, enabled] of Object.entries(plan.stages)) {
81
82
  writeOutput(stage, enabled);
@@ -43,12 +43,12 @@ import {
43
43
  githubTokenArguments,
44
44
  githubTokenEnvironmentName,
45
45
  } from "../src/release-github.ts";
46
+ import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
46
47
  import {
47
48
  readWorkspaceVersion,
48
49
  resolveBaseVersion,
49
50
  resolveNextVersion,
50
51
  } from "../src/workspace-version.ts";
51
- import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
52
52
 
53
53
  const logger = log.logger("projen:release");
54
54
 
@@ -1,21 +1,22 @@
1
- export declare const FINGERPRINT_SCHEMA = 3;
1
+ export declare const FINGERPRINT_SCHEMA = 4;
2
2
  export declare const VERSION_SLOT_SCHEMA = 1;
3
3
  export declare const VERSION_CAPACITY = 64;
4
4
  /** Canonical TOML representation with workspace-owned release versions removed. */
5
5
  export declare function normalizeManifest(text: string): string;
6
6
  /** Canonical Cargo.lock representation with local package versions normalized. */
7
7
  export declare function normalizeLockfile(text: string, workspaceVersion: string, packageNames?: ReadonlySet<string>): string;
8
- /** Hash Cargo-discovered members and every tracked or unignored file beneath them. */
9
- export declare function sourceHash(root: string, sources?: readonly string[]): string;
8
+ /** Hash shared Cargo inputs plus selected or Cargo-discovered workspace sources. */
9
+ export declare function sourceHash(root: string, sources?: readonly string[], includeWorkspace?: boolean): string;
10
10
  export declare function linkerIdentity(target: string): string;
11
11
  export interface TargetKeyOptions {
12
12
  readonly rustSourceHash: string;
13
+ readonly namespace?: string;
13
14
  readonly target: string;
14
15
  readonly targetConfig?: string;
15
16
  readonly toolchain?: string;
16
17
  readonly rustc?: string;
17
18
  }
18
- export declare function targetKey({ rustSourceHash, target, targetConfig, toolchain, rustc, }: TargetKeyOptions): string;
19
+ export declare function targetKey({ rustSourceHash, namespace, target, targetConfig, toolchain, rustc, }: TargetKeyOptions): string;
19
20
  export interface FingerprintOptions {
20
21
  readonly root?: string;
21
22
  readonly output?: string;
@@ -24,14 +25,17 @@ export interface FingerprintOptions {
24
25
  readonly toolchain?: string;
25
26
  readonly portable?: boolean;
26
27
  readonly sources?: readonly string[];
28
+ readonly sourceOnly?: boolean;
29
+ readonly namespace?: string;
27
30
  }
28
31
  export interface RustBuildFingerprint {
29
32
  readonly schemaVersion: number;
30
33
  readonly versionSlotSchema: number;
34
+ readonly namespace: string;
31
35
  readonly rustSourceHash: string;
32
36
  readonly targets: Readonly<Record<string, string>>;
33
37
  }
34
- export declare function fingerprint({ root, output, check, targets, toolchain, portable, sources, }: FingerprintOptions): RustBuildFingerprint;
38
+ export declare function fingerprint({ root, output, check, targets, toolchain, portable, sources, sourceOnly, namespace, }: FingerprintOptions): RustBuildFingerprint;
35
39
  export interface StampOptions {
36
40
  readonly objcopy?: string;
37
41
  readonly codesign?: string;