@dbx-tools/projen 0.9.19 → 0.9.28
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 +83 -229
- package/index.ts +11 -18
- package/package.json +9 -5
- package/src/_task-command.ts +75 -0
- package/src/barrels.ts +1 -74
- package/src/module-exports.ts +175 -1
- package/src/openapi.ts +5 -72
- package/src/packages.ts +45 -0
- package/src/project-js.ts +5 -48
- package/src/project-predicate.ts +1 -2
- package/src/project-py.ts +87 -199
- package/src/project.ts +2 -2
- package/src/python-node-bindings.ts +138 -0
- package/src/python-node-bundle.ts +92 -0
- package/src/release-catalog.ts +40 -208
- package/src/{release-dispatch.ts → release-context.ts} +0 -11
- package/src/release.ts +74 -778
- package/src/scaffold.ts +2 -6
- package/src/workspace-version.ts +9 -121
- package/tasks/bump.ts +17 -34
- package/tasks/databricks-cli-assets.ts +129 -0
- package/tasks/local-publish.ts +61 -138
- package/tasks/publish-npm.ts +71 -30
- package/tasks/publish-python.ts +61 -148
- package/tasks/publish.ts +32 -182
- package/tasks/python-node-bindings-watch.ts +47 -0
- package/tasks/python-node-bindings.ts +1017 -0
- package/tasks/python-registry.ts +2 -9
- package/tasks/python-release.ts +74 -0
- package/tasks/release-version.ts +23 -2
- package/tasks/release.ts +99 -0
- package/tasks/sync.ts +13 -18
- package/tasks/version-check.ts +13 -72
- package/src/_rust-project.ts +0 -429
- package/src/_rust-release-workflow.ts +0 -698
- package/src/project-rs.ts +0 -1127
- package/src/release-plan.ts +0 -269
- package/src/release-please.ts +0 -242
- package/src/uniffi.ts +0 -65
- package/tasks/publish-uniffi-local.ts +0 -314
- package/tasks/release-assets.ts +0 -476
- package/tasks/release-bootstrap.ts +0 -59
- package/tasks/release-candidate.ts +0 -383
- package/tasks/release-manifest.ts +0 -273
- package/tasks/release-plan.ts +0 -89
- package/tasks/release-please.ts +0 -162
- package/tasks/release-pr.ts +0 -563
- package/tasks/release-request.ts +0 -166
- package/tasks/release-summary-units.ts +0 -75
- package/tasks/release-summary.ts +0 -381
- package/tasks/rust.ts +0 -178
- package/tasks/stamp-python.ts +0 -13
- package/tasks/uniffi-python.d.ts +0 -61
- package/tasks/uniffi-python.js +0 -191
- package/tasks/uniffi-release.mjs +0 -573
- package/tasks/uniffi.ts +0 -271
package/src/barrels.ts
CHANGED
|
@@ -16,11 +16,6 @@
|
|
|
16
16
|
* takes priority. This keeps the barrel auto-generated while letting you add or
|
|
17
17
|
* override individual exports.
|
|
18
18
|
*
|
|
19
|
-
* A UniFFI source triplet (`bindings.ts`, `_bindings.ts`, and
|
|
20
|
-
* `_bindings-ffi.ts`) is one special case: the internal underscore modules stay
|
|
21
|
-
* private, while `bindings.ts` is re-exported directly instead of under a
|
|
22
|
-
* `bindings` namespace. Generation fails when one of those direct binding names
|
|
23
|
-
* conflicts with another top-level package export.
|
|
24
19
|
*
|
|
25
20
|
* Each eligible module becomes `export * as <name> from "./src/x.ts"` (camelCase
|
|
26
21
|
* namespace from its path segments; invalid identifiers suffixed with `Module`),
|
|
@@ -121,9 +116,6 @@ const PACKAGE_IDENTIFIER_LINE = `export const ${PACKAGE_IDENTIFIER_EXPORT} = "";
|
|
|
121
116
|
const PACKAGE_VERSION_LINE = `export const ${PACKAGE_VERSION_EXPORT} = "";`;
|
|
122
117
|
const PACKAGE_IDENTIFIER_LINE_RE = /^export const PACKAGE_IDENTIFIER = .*;$/m;
|
|
123
118
|
const PACKAGE_VERSION_LINE_RE = /^export const PACKAGE_VERSION = .*;$/m;
|
|
124
|
-
const UNIFFI_BINDINGS_FILE = "bindings.ts";
|
|
125
|
-
const UNIFFI_GENERATED_FILE = "_bindings.ts";
|
|
126
|
-
const UNIFFI_FFI_FILE = "_bindings-ffi.ts";
|
|
127
119
|
|
|
128
120
|
/** `config-tools` / `config_tools` -> `configTools`; `local-fs` -> `localFS`. */
|
|
129
121
|
function moduleSegmentToCamel(segment: string): string {
|
|
@@ -295,61 +287,6 @@ function isLegacyBindingsExport(file: string): boolean {
|
|
|
295
287
|
return readFileSync(file, "utf8").trim() === 'export * from "./src/bindings.ts";';
|
|
296
288
|
}
|
|
297
289
|
|
|
298
|
-
function hasUniFFIBindings(srcDir: string): boolean {
|
|
299
|
-
return [UNIFFI_BINDINGS_FILE, UNIFFI_GENERATED_FILE, UNIFFI_FFI_FILE].every((file) =>
|
|
300
|
-
existsSync(join(srcDir, file)),
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function bindingExportNames(srcDir: string): Set<string> {
|
|
305
|
-
return new Set(
|
|
306
|
-
[UNIFFI_BINDINGS_FILE, UNIFFI_GENERATED_FILE].flatMap((file) =>
|
|
307
|
-
moduleExports(join(srcDir, file)).map((entry) => entry.name),
|
|
308
|
-
),
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function barrelExportNames(content: string, customPath: string): Set<string> {
|
|
313
|
-
const names = new Set<string>([PACKAGE_IDENTIFIER_EXPORT, PACKAGE_VERSION_EXPORT]);
|
|
314
|
-
for (const match of content.matchAll(/^export \* as (\w+) from /gm)) {
|
|
315
|
-
names.add(match[1]!);
|
|
316
|
-
}
|
|
317
|
-
for (const match of content.matchAll(/^export(?: type)? \{([^}]+)\} from /gm)) {
|
|
318
|
-
for (const specifier of match[1]!.split(",")) {
|
|
319
|
-
const parts = specifier.trim().split(/\s+as\s+/);
|
|
320
|
-
const name = parts.at(-1);
|
|
321
|
-
if (name) names.add(name);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (existsSync(customPath) && !isLegacyBindingsExport(customPath)) {
|
|
325
|
-
const statements = moduleStatements(customPath) as ReadonlyArray<{
|
|
326
|
-
type: string;
|
|
327
|
-
exported?: unknown;
|
|
328
|
-
}>;
|
|
329
|
-
if (
|
|
330
|
-
statements.some(
|
|
331
|
-
(statement) => statement.type === "ExportAllDeclaration" && !statement.exported,
|
|
332
|
-
)
|
|
333
|
-
) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
`Cannot verify UniFFI export conflicts through a bare export in ${customPath}`,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
for (const name of customExportNames(customPath)) names.add(name);
|
|
339
|
-
}
|
|
340
|
-
return names;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function assertNoBindingExportConflicts(srcDir: string, content: string, customPath: string): void {
|
|
344
|
-
const other = barrelExportNames(content, customPath);
|
|
345
|
-
const conflicts = [...bindingExportNames(srcDir)].filter((name) => other.has(name)).sort();
|
|
346
|
-
if (conflicts.length > 0) {
|
|
347
|
-
throw new Error(
|
|
348
|
-
`UniFFI binding exports conflict with package exports in ${srcDir}: ${conflicts.join(", ")}`,
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
290
|
/** Read the authoritative npm package name and version emitted by the package project. */
|
|
354
291
|
function packageMetadata(pkgDir: string): { identifier: string; version: string } {
|
|
355
292
|
const manifestPath = join(pkgDir, "package.json");
|
|
@@ -441,10 +378,7 @@ function generateForPackage(pkgDir: string): number {
|
|
|
441
378
|
if (!existing || (!isSourceExt(existing) && isSourceExt(f))) byModulePath.set(stem, f);
|
|
442
379
|
}
|
|
443
380
|
const modulePaths = [...byModulePath.keys()].sort((a, b) => a.localeCompare(b));
|
|
444
|
-
const
|
|
445
|
-
const namespacedModulePaths = uniffiBindings
|
|
446
|
-
? modulePaths.filter((modulePath) => modulePath !== "bindings")
|
|
447
|
-
: modulePaths;
|
|
381
|
+
const namespacedModulePaths = modulePaths;
|
|
448
382
|
|
|
449
383
|
// No eligible modules -> no barrel: drop any stale root barrel and bail.
|
|
450
384
|
if (modulePaths.length === 0) {
|
|
@@ -479,13 +413,6 @@ function generateForPackage(pkgDir: string): number {
|
|
|
479
413
|
content = hoistUniqueExports(content, pkgDir, suppress);
|
|
480
414
|
// A sibling `exports.ts` overrides/extends the generated barrel and wins on conflict.
|
|
481
415
|
content = mergeCustomExports(content, pkgDir);
|
|
482
|
-
if (uniffiBindings) {
|
|
483
|
-
assertNoBindingExportConflicts(srcDir, content, customPath);
|
|
484
|
-
content = content.replace(
|
|
485
|
-
PACKAGE_VERSION_LINE,
|
|
486
|
-
`${PACKAGE_VERSION_LINE}\nexport * from "./src/bindings.ts";`,
|
|
487
|
-
);
|
|
488
|
-
}
|
|
489
416
|
|
|
490
417
|
// Package metadata is blanked for the structural comparison, then restored
|
|
491
418
|
// from package.json so a version-only change still rewrites the barrel.
|
package/src/module-exports.ts
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* `export { ... }` is a hard error (TS1205). `export function` names are marked
|
|
20
20
|
* `isFunction` so the barrel leaves them namespace-only.
|
|
21
21
|
*/
|
|
22
|
-
import { readFileSync } from "node:fs";
|
|
22
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
23
23
|
import { createRequire } from "node:module";
|
|
24
|
+
import { dirname, extname, resolve } from "node:path";
|
|
24
25
|
import type { parseSync as OxcParseSync } from "oxc-parser";
|
|
25
26
|
|
|
26
27
|
const require = createRequire(import.meta.url);
|
|
@@ -63,6 +64,13 @@ export interface ModuleExport {
|
|
|
63
64
|
readonly isFunction: boolean;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/** Plain public function reachable through a module's re-export graph. */
|
|
68
|
+
export interface PublicFunctionExport {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly sourceFile: string;
|
|
71
|
+
readonly sourceName: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
/** Declaration node types that are inherently type-only. */
|
|
67
75
|
const TYPE_DECLARATIONS = new Set(["TSInterfaceDeclaration", "TSTypeAliasDeclaration"]);
|
|
68
76
|
|
|
@@ -132,3 +140,169 @@ export function moduleExports(file: string): ModuleExport[] {
|
|
|
132
140
|
}
|
|
133
141
|
return [...byName.values()];
|
|
134
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve every plain named function exported by `file`, following relative
|
|
146
|
+
* named and star re-exports. Non-function values and types are ignored. A
|
|
147
|
+
* generator, declaration-only overload, or conflicting function definition is
|
|
148
|
+
* rejected because a generic runtime bridge cannot preserve those semantics.
|
|
149
|
+
*/
|
|
150
|
+
export function publicFunctionExports(file: string): PublicFunctionExport[] {
|
|
151
|
+
return [...collectPublicFunctions(resolve(file), new Set()).values()]
|
|
152
|
+
.map(({ name, sourceFile, sourceName }) => ({ name, sourceFile, sourceName }))
|
|
153
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
interface ResolvedFunctionExport extends PublicFunctionExport {
|
|
157
|
+
readonly source: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function collectPublicFunctions(
|
|
161
|
+
file: string,
|
|
162
|
+
visiting: Set<string>,
|
|
163
|
+
): Map<string, ResolvedFunctionExport> {
|
|
164
|
+
if (visiting.has(file)) return new Map();
|
|
165
|
+
visiting.add(file);
|
|
166
|
+
const source = readFileSync(file, "utf8");
|
|
167
|
+
const body = parseSync(file, source).program.body;
|
|
168
|
+
const functions = new Map<string, ResolvedFunctionExport>();
|
|
169
|
+
const localFunctions = new Map<string, FunctionNode>();
|
|
170
|
+
|
|
171
|
+
for (const statement of body) {
|
|
172
|
+
const node = statement as FunctionNode & { declaration?: FunctionNode | null };
|
|
173
|
+
const declaration =
|
|
174
|
+
node.type === "FunctionDeclaration" || node.type === "TSDeclareFunction"
|
|
175
|
+
? node
|
|
176
|
+
: node.declaration;
|
|
177
|
+
if (declaration?.type === "FunctionDeclaration" || declaration?.type === "TSDeclareFunction") {
|
|
178
|
+
const name = declaration.id?.name;
|
|
179
|
+
if (name) localFunctions.set(name, declaration);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const add = (exported: ResolvedFunctionExport): void => {
|
|
184
|
+
const existing = functions.get(exported.name);
|
|
185
|
+
if (existing && existing.source !== exported.source) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Conflicting function export ${exported.name} from ${existing.source} and ${exported.source}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
functions.set(exported.name, exported);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
for (const statement of body) {
|
|
194
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
195
|
+
const node = statement as { exported?: unknown; source?: { value?: string } };
|
|
196
|
+
if (node.exported || !node.source?.value) continue;
|
|
197
|
+
for (const exported of collectPublicFunctions(
|
|
198
|
+
resolveModuleSpecifier(file, node.source.value),
|
|
199
|
+
visiting,
|
|
200
|
+
).values()) {
|
|
201
|
+
add(exported);
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (statement.type !== "ExportNamedDeclaration") continue;
|
|
206
|
+
const node = statement as {
|
|
207
|
+
declaration?: FunctionNode | null;
|
|
208
|
+
source?: { value?: string } | null;
|
|
209
|
+
specifiers?: {
|
|
210
|
+
local?: { name?: string; value?: string };
|
|
211
|
+
exported?: { name?: string; value?: string };
|
|
212
|
+
exportKind?: "value" | "type";
|
|
213
|
+
}[];
|
|
214
|
+
exportKind?: "value" | "type";
|
|
215
|
+
};
|
|
216
|
+
if (
|
|
217
|
+
node.declaration?.type === "FunctionDeclaration" ||
|
|
218
|
+
node.declaration?.type === "TSDeclareFunction"
|
|
219
|
+
) {
|
|
220
|
+
add(parseFunctionNode(file, node.declaration));
|
|
221
|
+
}
|
|
222
|
+
if (node.exportKind === "type") continue;
|
|
223
|
+
if (node.source?.value) {
|
|
224
|
+
const available = collectPublicFunctions(
|
|
225
|
+
resolveModuleSpecifier(file, node.source.value),
|
|
226
|
+
visiting,
|
|
227
|
+
);
|
|
228
|
+
for (const specifier of node.specifiers ?? []) {
|
|
229
|
+
if (specifier.exportKind === "type") continue;
|
|
230
|
+
const local = specifier.local?.name ?? specifier.local?.value;
|
|
231
|
+
const exportedName = specifier.exported?.name ?? specifier.exported?.value;
|
|
232
|
+
const target = local ? available.get(local) : undefined;
|
|
233
|
+
if (target && exportedName)
|
|
234
|
+
add({
|
|
235
|
+
name: exportedName,
|
|
236
|
+
source: target.source,
|
|
237
|
+
sourceFile: target.sourceFile,
|
|
238
|
+
sourceName: target.sourceName,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
for (const specifier of node.specifiers ?? []) {
|
|
244
|
+
if (specifier.exportKind === "type") continue;
|
|
245
|
+
const local = specifier.local?.name ?? specifier.local?.value;
|
|
246
|
+
const exportedName = specifier.exported?.name ?? specifier.exported?.value;
|
|
247
|
+
const target = local ? localFunctions.get(local) : undefined;
|
|
248
|
+
if (target && exportedName) {
|
|
249
|
+
const parsed = parseFunctionNode(file, target);
|
|
250
|
+
add({
|
|
251
|
+
name: exportedName,
|
|
252
|
+
source: parsed.source,
|
|
253
|
+
sourceFile: parsed.sourceFile,
|
|
254
|
+
sourceName: parsed.sourceName,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
visiting.delete(file);
|
|
261
|
+
return functions;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface FunctionNode {
|
|
265
|
+
readonly type: string;
|
|
266
|
+
readonly id?: { readonly name?: string } | null;
|
|
267
|
+
readonly generator?: boolean;
|
|
268
|
+
readonly declare?: boolean;
|
|
269
|
+
readonly body?: unknown;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function parseFunctionNode(file: string, node: FunctionNode): ResolvedFunctionExport {
|
|
273
|
+
const name = node.id?.name;
|
|
274
|
+
if (!name) throw new Error(`Anonymous exported function is not supported in ${file}`);
|
|
275
|
+
if (node.generator) throw new Error(`Generator export ${name} is not supported in ${file}`);
|
|
276
|
+
if (node.type === "TSDeclareFunction" || node.declare || !node.body) {
|
|
277
|
+
throw new Error(`Declaration-only function export ${name} is not supported in ${file}`);
|
|
278
|
+
}
|
|
279
|
+
return { name, source: `${file}#${name}`, sourceFile: file, sourceName: name };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function resolveModuleSpecifier(importer: string, specifier: string): string {
|
|
283
|
+
if (!specifier.startsWith(".")) return createRequire(importer).resolve(specifier);
|
|
284
|
+
const base = resolve(dirname(importer), specifier);
|
|
285
|
+
const extension = extname(base);
|
|
286
|
+
const candidates = extension
|
|
287
|
+
? [
|
|
288
|
+
base,
|
|
289
|
+
...(extension === ".js" || extension === ".jsx"
|
|
290
|
+
? [`${base.slice(0, -extension.length)}.ts`, `${base.slice(0, -extension.length)}.tsx`]
|
|
291
|
+
: []),
|
|
292
|
+
]
|
|
293
|
+
: [
|
|
294
|
+
`${base}.ts`,
|
|
295
|
+
`${base}.tsx`,
|
|
296
|
+
`${base}.js`,
|
|
297
|
+
`${base}.jsx`,
|
|
298
|
+
`${base}.mjs`,
|
|
299
|
+
`${base}.cjs`,
|
|
300
|
+
resolve(base, "index.ts"),
|
|
301
|
+
resolve(base, "index.tsx"),
|
|
302
|
+
resolve(base, "index.js"),
|
|
303
|
+
];
|
|
304
|
+
for (const candidate of candidates) {
|
|
305
|
+
if (existsSync(candidate)) return candidate;
|
|
306
|
+
}
|
|
307
|
+
throw new Error(`Could not resolve ${specifier} from ${importer}`);
|
|
308
|
+
}
|
package/src/openapi.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenAPI generator for tsoa controllers
|
|
2
|
+
* OpenAPI generator for tsoa controllers.
|
|
3
3
|
*
|
|
4
|
-
* TypeScript packages are discovered from tsoa imports
|
|
5
|
-
* recorded by `DBXToolsRustWorkspace`. Both feed the same optimized generated
|
|
6
|
-
* package:
|
|
4
|
+
* TypeScript packages are discovered from tsoa imports and feed the optimized generated package:
|
|
7
5
|
*
|
|
8
6
|
* - `openapi.json` - the OpenAPI 3 spec (tsoa `generateSpec`, then Speakeasy
|
|
9
7
|
* optimization to extract duplicate inline schemas into components).
|
|
@@ -48,7 +46,6 @@ import {
|
|
|
48
46
|
toPosix,
|
|
49
47
|
recordedPackages,
|
|
50
48
|
} from "./packages.ts";
|
|
51
|
-
import type { RustOpenApiMapping, RustWorkspaceMapping } from "./project-rs.ts";
|
|
52
49
|
import type { ReleaseUnitGraph } from "./release-catalog.ts";
|
|
53
50
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
54
51
|
|
|
@@ -100,15 +97,6 @@ function controllerPackages(): object.Sequence<RecordedPackage> {
|
|
|
100
97
|
return object.sequence(recordedPackages()).filter(isTsoaCandidate).filter(hasTsoaControllers);
|
|
101
98
|
}
|
|
102
99
|
|
|
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
100
|
function inside(path: string, directory: string): boolean {
|
|
113
101
|
const child = relative(resolve(directory), resolve(path));
|
|
114
102
|
return child === "" || (!child.startsWith("..") && !isAbsolute(child));
|
|
@@ -120,16 +108,11 @@ export function isOpenapiProducerSource(path: string, producers: readonly string
|
|
|
120
108
|
return object.sequence(producers).some((producer) => inside(absolute, producer));
|
|
121
109
|
}
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
return [resolve(repoRoot, mapping.rust, "src"), resolve(repoRoot, mapping.rust, "Cargo.toml")];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/** Source roots consumed by TypeScript and Rust OpenAPI producers. */
|
|
111
|
+
/** Source roots consumed by TypeScript OpenAPI producers. */
|
|
128
112
|
export function openapiWatchRoots(): string[] {
|
|
129
113
|
return [
|
|
130
114
|
...controllerPackages()
|
|
131
115
|
.map((pkg) => resolve(tsoaSource(pkg)))
|
|
132
|
-
.join(object.sequence(rustOpenapiMappings()).flatMap(rustOpenapiSources))
|
|
133
116
|
.distinct(),
|
|
134
117
|
];
|
|
135
118
|
}
|
|
@@ -139,14 +122,6 @@ export function isOpenapiSource(path: string): boolean {
|
|
|
139
122
|
const absolute = resolve(path);
|
|
140
123
|
const segments = toPosix(relative(repoRoot, absolute)).split("/");
|
|
141
124
|
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
125
|
return object
|
|
151
126
|
.sequence(recordedPackages())
|
|
152
127
|
.filter(isTsoaCandidate)
|
|
@@ -196,28 +171,6 @@ export async function optimizeOpenapiSpec(specPath: string, executable?: string)
|
|
|
196
171
|
await execFileAsync(openapi, ["spec", "optimize", specPath, "--write", "--non-interactive"]);
|
|
197
172
|
}
|
|
198
173
|
|
|
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
174
|
type OpenApiTypeTools = {
|
|
222
175
|
openapiTS: typeof import("openapi-typescript").default;
|
|
223
176
|
astToString: typeof import("openapi-typescript").astToString;
|
|
@@ -257,11 +210,10 @@ async function writeClientPackage(
|
|
|
257
210
|
*/
|
|
258
211
|
export async function generateOpenapi(): Promise<string[]> {
|
|
259
212
|
const pkgs = controllerPackages().toArray();
|
|
260
|
-
const rustMappings = rustOpenapiMappings();
|
|
261
213
|
// Same reasoning as codegen's empty case: a workspace with no tsoa controllers
|
|
262
214
|
// is not a condition worth a line on every synth.
|
|
263
|
-
if (pkgs.length === 0
|
|
264
|
-
logger.debug("no TypeScript
|
|
215
|
+
if (pkgs.length === 0) {
|
|
216
|
+
logger.debug("no TypeScript OpenAPI producers found");
|
|
265
217
|
return [];
|
|
266
218
|
}
|
|
267
219
|
|
|
@@ -341,24 +293,5 @@ export async function generateOpenapi(): Promise<string[]> {
|
|
|
341
293
|
written.push(outDir);
|
|
342
294
|
logger.success(`openapi/${leaf} (from ${p.relPath})`);
|
|
343
295
|
}
|
|
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
|
-
}
|
|
363
296
|
return written;
|
|
364
297
|
}
|
package/src/packages.ts
CHANGED
|
@@ -291,6 +291,19 @@ export function syncResynthPaths(projectRoot: string = resolveRepoRoot()): strin
|
|
|
291
291
|
return Array.isArray(paths) ? stringUtils.parseList(paths.map((p) => String(p))) : [];
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/** Python projects with Node bindings registered for the root sync watcher. */
|
|
295
|
+
export function pythonNodeBindingProjects(projectRoot: string = resolveRepoRoot()): string[] {
|
|
296
|
+
const configured = readDbxToolsConfig(projectRoot)?.pythonNodeBindings;
|
|
297
|
+
if (!Array.isArray(configured)) return [];
|
|
298
|
+
return [
|
|
299
|
+
...new Set(
|
|
300
|
+
configured.flatMap((value) =>
|
|
301
|
+
typeof value === "string" && value.trim() ? [value.trim()] : [],
|
|
302
|
+
),
|
|
303
|
+
),
|
|
304
|
+
].sort();
|
|
305
|
+
}
|
|
306
|
+
|
|
294
307
|
/**
|
|
295
308
|
* The recorded workspace members from `pnpm-workspace.yaml` (the source of truth),
|
|
296
309
|
* each augmented with the `tags` read back from its `package.json`. This is what
|
|
@@ -314,6 +327,38 @@ export function recordedPackages(projectRoot: string = resolveRepoRoot()): Recor
|
|
|
314
327
|
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
315
328
|
}
|
|
316
329
|
|
|
330
|
+
/**
|
|
331
|
+
* Absolute directories for a workspace package and its transitive runtime
|
|
332
|
+
* workspace dependencies. Dependency edges come from package manifests; external
|
|
333
|
+
* packages are ignored. Returns `[]` when `packageName` is not a workspace member.
|
|
334
|
+
*/
|
|
335
|
+
export function workspaceDependencyDirectories(
|
|
336
|
+
packageName: string,
|
|
337
|
+
projectRoot: string = resolveRepoRoot(),
|
|
338
|
+
): string[] {
|
|
339
|
+
const packages = new Map<string, { dir: string; manifest: Record<string, unknown> }>();
|
|
340
|
+
for (const pkg of recordedPackages(projectRoot)) {
|
|
341
|
+
const manifest = readPackageManifest(pkg.dir);
|
|
342
|
+
if (!manifest || typeof manifest.name !== "string") continue;
|
|
343
|
+
packages.set(manifest.name, { dir: pkg.dir, manifest });
|
|
344
|
+
}
|
|
345
|
+
if (!packages.has(packageName)) return [];
|
|
346
|
+
|
|
347
|
+
const directories = new Set<string>();
|
|
348
|
+
const visit = (name: string): void => {
|
|
349
|
+
const pkg = packages.get(name);
|
|
350
|
+
if (!pkg || directories.has(pkg.dir)) return;
|
|
351
|
+
directories.add(pkg.dir);
|
|
352
|
+
for (const field of ["dependencies", "optionalDependencies", "peerDependencies"] as const) {
|
|
353
|
+
const dependencies = pkg.manifest[field];
|
|
354
|
+
if (!object.isRecord(dependencies)) continue;
|
|
355
|
+
for (const dependency of Object.keys(dependencies)) visit(dependency);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
visit(packageName);
|
|
359
|
+
return [...directories].sort();
|
|
360
|
+
}
|
|
361
|
+
|
|
317
362
|
/**
|
|
318
363
|
* The configured roots recorded in the root manifest. Older workspaces without
|
|
319
364
|
* that field fall back to the distinct first segment of every recorded member,
|
package/src/project-js.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { type IConstruct } from "constructs";
|
|
|
15
15
|
import { Component, IgnoreFile, Project, type TaskOptions, javascript, typescript } from "projen";
|
|
16
16
|
import { BuildWorkflow } from "projen/lib/build";
|
|
17
17
|
import { AutoMerge } from "projen/lib/github";
|
|
18
|
-
import { JobPermission, type JobStep
|
|
18
|
+
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
19
19
|
import type { ReleaseProjectOptions } from "projen/lib/release";
|
|
20
20
|
import { mixin } from "..";
|
|
21
21
|
import { generateBarrels } from "./barrels.ts";
|
|
@@ -44,12 +44,11 @@ import { PROJEN_VERSION } from "./projen-version.ts";
|
|
|
44
44
|
import { applyCompiledPublish } from "./publish.ts";
|
|
45
45
|
import {
|
|
46
46
|
DBXToolsReleaseCatalog,
|
|
47
|
-
type DBXToolsVersioningMode,
|
|
48
47
|
type ExternalReleaseProjectRegistration,
|
|
49
48
|
type ReleaseDependencyInput,
|
|
50
49
|
type ReleaseUnitRule,
|
|
51
50
|
} from "./release-catalog.ts";
|
|
52
|
-
import { DBXToolsRelease, type ReleaseDocsOptions
|
|
51
|
+
import { DBXToolsRelease, type ReleaseDocsOptions } from "./release.ts";
|
|
53
52
|
import { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS, type PackageTag } from "./tags.ts";
|
|
54
53
|
import { DBXToolsRootTsconfig } from "./tsconfig.ts";
|
|
55
54
|
import { DBXToolsVsCode } from "./vscode.ts";
|
|
@@ -385,7 +384,7 @@ const PRETTIER_SETTINGS: javascript.PrettierSettings = {
|
|
|
385
384
|
|
|
386
385
|
export { PROJEN_VERSION } from "./projen-version.ts";
|
|
387
386
|
|
|
388
|
-
/** SPDX license shared by generated JavaScript
|
|
387
|
+
/** SPDX license shared by generated JavaScript and Python packages. */
|
|
389
388
|
export const DBX_TOOLS_LICENSE = "Apache-2.0";
|
|
390
389
|
|
|
391
390
|
/**
|
|
@@ -626,20 +625,12 @@ export type DBXToolsJavaScriptProjectOptions = CommonProjectOptions &
|
|
|
626
625
|
readonly releasePythonRoot?: string;
|
|
627
626
|
/** Repository task names run during local release preparation before publication. */
|
|
628
627
|
readonly releaseValidationTasks?: readonly string[];
|
|
629
|
-
/** Optional AI-generated release summary. Defaults to enabled. */
|
|
630
|
-
readonly releaseSummary?: boolean | ReleaseSummaryOptions;
|
|
631
|
-
/** Existing source branch safely fast-forwarded after an independent release. */
|
|
632
|
-
readonly releaseSyncBranch?: string | false;
|
|
633
628
|
/** Set to `false` to omit normal npm workspace publication. */
|
|
634
629
|
readonly nodeRelease?: boolean;
|
|
635
630
|
/** Unified dbx-tools release workflow, or no release surface. Defaults to `dbx-tools`. */
|
|
636
631
|
readonly releaseMode?: DBXToolsReleaseMode;
|
|
637
|
-
/** Package version ownership mode. Defaults to `fixed`. */
|
|
638
|
-
readonly versioningMode?: DBXToolsVersioningMode;
|
|
639
632
|
/** Explicit stable release-unit grouping rules. */
|
|
640
633
|
readonly releaseUnits?: readonly ReleaseUnitRule[];
|
|
641
|
-
/** Commit before the first independent Release Please history. */
|
|
642
|
-
readonly releaseBootstrapSha?: string;
|
|
643
634
|
/** Publishable workspace members synthesized outside the attached project tree. */
|
|
644
635
|
readonly externalReleaseProjects?: readonly ExternalReleaseProjectRegistration[];
|
|
645
636
|
/** Prefix for generated release tags. Defaults to `v`. */
|
|
@@ -723,10 +714,8 @@ export class DBXToolsNodeProject
|
|
|
723
714
|
// (its build phase installs with pnpm and reads catalog + `allowBuilds`).
|
|
724
715
|
pnpmWorkspace.attachWorkspaceFile(this);
|
|
725
716
|
this.releaseCatalog = new DBXToolsReleaseCatalog(this, {
|
|
726
|
-
mode: options.versioningMode,
|
|
727
717
|
units: options.releaseUnits,
|
|
728
718
|
externalProjects: options.externalReleaseProjects,
|
|
729
|
-
bootstrapSha: options.releaseBootstrapSha,
|
|
730
719
|
});
|
|
731
720
|
registerJavaScriptReleaseProject(this);
|
|
732
721
|
this.package.addField("version", () => this.releaseCatalog.versionFor(this));
|
|
@@ -862,10 +851,8 @@ export class DBXToolsTypeScriptProject
|
|
|
862
851
|
(parent instanceof DBXToolsNodeProject || parent instanceof DBXToolsTypeScriptProject)
|
|
863
852
|
? parent.releaseCatalog
|
|
864
853
|
: new DBXToolsReleaseCatalog(this, {
|
|
865
|
-
mode: options.versioningMode,
|
|
866
854
|
units: options.releaseUnits,
|
|
867
855
|
externalProjects: options.externalReleaseProjects,
|
|
868
|
-
bootstrapSha: options.releaseBootstrapSha,
|
|
869
856
|
});
|
|
870
857
|
// Pairs with `jsx` in SHARED_COMPILER_OPTIONS: projen's default `include` is
|
|
871
858
|
// `src/**/*.ts` only, which silently omits a `.tsx` file from the program
|
|
@@ -937,35 +924,7 @@ function configureBuildWorkflow(
|
|
|
937
924
|
});
|
|
938
925
|
validation.exec("bunx projen default");
|
|
939
926
|
validation.exec("bun run compile");
|
|
940
|
-
const configured = {
|
|
941
|
-
...options.buildWorkflowOptions,
|
|
942
|
-
...(!options.buildWorkflowOptions?.workflowTriggers &&
|
|
943
|
-
project.releaseCatalog.mode === "independent"
|
|
944
|
-
? {
|
|
945
|
-
workflowTriggers: {
|
|
946
|
-
pullRequest: {
|
|
947
|
-
types: ["opened", "synchronize", "reopened", "closed"],
|
|
948
|
-
paths: [
|
|
949
|
-
"**",
|
|
950
|
-
"!.projen/release-units.json",
|
|
951
|
-
"!.release-please-manifest.json",
|
|
952
|
-
"!.release-units/**",
|
|
953
|
-
"!.release-notes/**",
|
|
954
|
-
"!docs/releases/**",
|
|
955
|
-
"!Cargo.lock",
|
|
956
|
-
"!**/Cargo.toml",
|
|
957
|
-
"!**/package.json",
|
|
958
|
-
"!**/pyproject.toml",
|
|
959
|
-
"!**/index.ts",
|
|
960
|
-
"!packages/js/node/rust-binary/src/_release-binaries.ts",
|
|
961
|
-
"!packages/js/node/appkit-graphiti/src/_python-runtime.ts",
|
|
962
|
-
],
|
|
963
|
-
},
|
|
964
|
-
workflowDispatch: {},
|
|
965
|
-
} satisfies Triggers,
|
|
966
|
-
}
|
|
967
|
-
: {}),
|
|
968
|
-
};
|
|
927
|
+
const configured = { ...options.buildWorkflowOptions };
|
|
969
928
|
const compatibility = nodeWorkflowCompatibility(project);
|
|
970
929
|
const configuredRunner = configured.runsOn !== undefined || configured.runsOnGroup !== undefined;
|
|
971
930
|
const workflow = new BuildWorkflow(project, {
|
|
@@ -1363,7 +1322,7 @@ class PublishedPackageDescriptionValidation extends Component {
|
|
|
1363
1322
|
* fail with ERR_MODULE_NOT_FOUND after such an update. bun runs the `.ts`
|
|
1364
1323
|
* directly (no tsx, no build step).
|
|
1365
1324
|
*/
|
|
1366
|
-
export function taskScript(_project:
|
|
1325
|
+
export function taskScript(_project: Project, script: string, args = ""): string {
|
|
1367
1326
|
const scriptPath = toPosix(join("node_modules", "@dbx-tools", "projen", "tasks", script));
|
|
1368
1327
|
return args ? `bun ${scriptPath} ${args}` : `bun ${scriptPath}`;
|
|
1369
1328
|
}
|
|
@@ -1619,8 +1578,6 @@ function initProject(
|
|
|
1619
1578
|
docs: options.releaseDocs,
|
|
1620
1579
|
pythonRoot: options.releasePythonRoot,
|
|
1621
1580
|
validationTasks: options.releaseValidationTasks,
|
|
1622
|
-
summary: options.releaseSummary,
|
|
1623
|
-
syncBranch: options.releaseSyncBranch,
|
|
1624
1581
|
});
|
|
1625
1582
|
}
|
|
1626
1583
|
}
|
package/src/project-predicate.ts
CHANGED
|
@@ -33,8 +33,7 @@ export function isDBXToolsProject(): Predicate<IConstruct, DBXToolsProject> {
|
|
|
33
33
|
return isProject().and(
|
|
34
34
|
(project): project is DBXToolsProject =>
|
|
35
35
|
(project as Partial<DBXToolsProject>).language === "javascript" ||
|
|
36
|
-
(project as Partial<DBXToolsProject>).language === "python"
|
|
37
|
-
(project as Partial<DBXToolsProject>).language === "rust",
|
|
36
|
+
(project as Partial<DBXToolsProject>).language === "python",
|
|
38
37
|
);
|
|
39
38
|
}
|
|
40
39
|
|