@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/README.md +20 -15
- package/index.ts +4 -3
- package/package.json +4 -4
- package/src/_rust-project.ts +37 -13
- package/src/_rust-release-workflow.ts +267 -164
- package/src/_rust-release.ts +38 -10
- package/src/openapi.ts +191 -79
- package/src/project-py.ts +4 -4
- package/src/project-rs.ts +57 -6
- package/src/release-plan.ts +57 -16
- package/src/release.ts +2 -1
- package/src/watch.ts +19 -2
- package/src/workspace-lock.ts +32 -6
- package/tasks/barrels.ts +47 -30
- package/tasks/openapi.ts +15 -11
- package/tasks/release-plan.ts +2 -1
- package/tasks/release-pr.ts +1 -1
- package/tasks/rust-release.d.ts +9 -5
- package/tasks/rust-release.mjs +20 -11
- package/tasks/rust.ts +62 -33
- package/tasks/uniffi-release.mjs +5 -1
- package/tasks/uniffi.ts +2 -0
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.
|
|
1063
|
-
workflow.addJob(
|
|
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.
|
|
1085
|
-
workflow.addJob("rust-
|
|
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));
|
package/src/release-plan.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
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
|
|
144
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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:
|
|
222
|
+
schemaVersion: 2,
|
|
183
223
|
units,
|
|
184
224
|
publishBatches,
|
|
185
225
|
nodePackages,
|
|
186
226
|
pythonPackages,
|
|
187
227
|
rustPackages,
|
|
188
228
|
artifacts,
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
package/src/workspace-lock.ts
CHANGED
|
@@ -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
|
-
|
|
28
|
+
options?: WorkspaceMutationLockOptions,
|
|
29
|
+
): Promise<T | undefined> {
|
|
17
30
|
const repository = resolve(root);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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(
|
|
21
|
-
|
|
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(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,
|
|
4
|
+
import { generateOpenapi, isOpenapiSource, openapiWatchRoots } from "../src/openapi.ts";
|
|
5
5
|
import { runSynth } from "../src/scaffold.ts";
|
|
6
|
-
import { watchLoop
|
|
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(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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).
|
package/tasks/release-plan.ts
CHANGED
|
@@ -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("
|
|
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);
|
package/tasks/release-pr.ts
CHANGED
|
@@ -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
|
|
package/tasks/rust-release.d.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
export declare const FINGERPRINT_SCHEMA =
|
|
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
|
|
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;
|