@dbx-tools/projen 0.9.40 → 0.9.46
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 +58 -67
- package/index.ts +6 -8
- package/package.json +9 -10
- package/shims/python-node/bootstrap.ts +129 -0
- package/shims/python-node/child-process.ts +96 -0
- package/shims/python-node/crypto.ts +60 -0
- package/shims/python-node/fs-promises.ts +179 -0
- package/shims/python-node/fs.ts +173 -0
- package/shims/python-node/headers.ts +75 -0
- package/shims/python-node/host.ts +341 -0
- package/shims/python-node/os.ts +20 -0
- package/shims/python-node/process.ts +13 -0
- package/shims/python-node/readline.ts +37 -0
- package/shims/python-node/tsconfig.json +24 -0
- package/src/_lazy-require.ts +7 -25
- package/src/bun-app.ts +2 -2
- package/src/bun-workflow.ts +2 -170
- package/src/codegen.ts +23 -20
- package/src/dbx-tools-config.ts +52 -9
- package/src/generated.ts +4 -17
- package/src/mixin.ts +1 -5
- package/src/packages.ts +18 -15
- package/src/pnpm-workspace.ts +1 -4
- package/src/project-js.ts +65 -196
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +127 -37
- package/src/publish.ts +13 -10
- package/src/python-node-bindings.ts +2 -0
- package/src/python-node-bundle.ts +6 -0
- package/src/release-options.ts +17 -17
- package/src/release.ts +31 -14
- package/src/scaffold.ts +4 -7
- package/src/tags.ts +6 -16
- package/src/vscode.ts +1 -1
- package/src/watch.ts +5 -5
- package/src/workspace-lock.ts +1 -0
- package/src/workspace-version.ts +4 -4
- package/tasks/barrels.ts +3 -5
- package/tasks/clean.ts +1 -0
- package/tasks/lib/compile-workspace.ts +41 -19
- package/tasks/lib/local-publish.ts +0 -28
- package/tasks/lib/publish-npm.ts +8 -0
- package/tasks/lib/publish-python.ts +4 -0
- package/tasks/lib/python-node-bindings.ts +107 -26
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +2 -2
- package/tasks/projenrc.ts +1 -0
- package/tasks/sync.ts +4 -4
- package/src/openapi.ts +0 -293
- package/tasks/local-publish.ts +0 -6
- package/tasks/openapi.ts +0 -35
package/tasks/sync.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
|
+
/** Synthesize once or supervise the focused Projen and barrel watch loops. */
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { log } from "@dbx-tools/shared-core";
|
|
4
5
|
import concurrently from "concurrently";
|
|
@@ -46,8 +47,8 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
46
47
|
// Watch: one initial full synth to bring the tree up to date, then focused
|
|
47
48
|
// watchers under `concurrently`. The projenrc watcher is the intelligent stand-in
|
|
48
49
|
// for stock `projen --watch` - it re-synths (+install) ONLY when `.projenrc.ts` or
|
|
49
|
-
// a configured `syncResynthPaths` entry changes, while
|
|
50
|
-
//
|
|
50
|
+
// a configured `syncResynthPaths` entry changes, while the barrel watcher keeps
|
|
51
|
+
// generated package exports fresh on source edits with no full synth.
|
|
51
52
|
//
|
|
52
53
|
// VS Code auto-runs this on folder open, so from here down nothing is allowed to be
|
|
53
54
|
// fatal: errors are logged and retried, and only a stop signal ends the task.
|
|
@@ -67,11 +68,10 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
67
68
|
const watchers = [
|
|
68
69
|
watcher("projenrc.ts", "projenrc", "magenta"),
|
|
69
70
|
watcher("barrels.ts", "barrels", "cyan", "--watch"),
|
|
70
|
-
watcher("openapi.ts", "openapi", "green", "--watch"),
|
|
71
71
|
];
|
|
72
72
|
const { result } = concurrently(watchers, {
|
|
73
73
|
prefix: "name",
|
|
74
|
-
// No `killOthersOn`: one watcher falling over is no reason to tear the other
|
|
74
|
+
// No `killOthersOn`: one watcher falling over is no reason to tear the other
|
|
75
75
|
// down. `-1` is concurrently's spelling for "restart forever", so a crashed
|
|
76
76
|
// watcher comes back instead of silently leaving its outputs stale.
|
|
77
77
|
restartTries: -1,
|
package/src/openapi.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenAPI generator for tsoa controllers.
|
|
3
|
-
*
|
|
4
|
-
* TypeScript packages are discovered from tsoa imports and feed the optimized generated package:
|
|
5
|
-
*
|
|
6
|
-
* - `openapi.json` - the OpenAPI 3 spec (tsoa `generateSpec`, then Speakeasy
|
|
7
|
-
* optimization to extract duplicate inline schemas into components).
|
|
8
|
-
* - `src/schema.ts` - types generated from the spec (openapi-typescript).
|
|
9
|
-
* - `src/client.ts` - a typed `openapi-fetch` client, usable client-side.
|
|
10
|
-
*
|
|
11
|
-
* `generateSpec` then reads the actual controller decorators + TypeScript types from
|
|
12
|
-
* those files, so the API surface is annotated on the methods and nothing is
|
|
13
|
-
* hand-written twice. The generated client stack is openapi-typescript +
|
|
14
|
-
* openapi-fetch (openapi-ts.dev), the best-of-2026 choice since AppKit ships no
|
|
15
|
-
* OpenAPI client generator.
|
|
16
|
-
*
|
|
17
|
-
* `tsoa`, `typescript`, and `openapi-typescript` are loaded lazily (heavy, and only
|
|
18
|
-
* needed for `bun run openapi`), so importing this module stays cheap. `tsoa` and
|
|
19
|
-
* `typescript` are not engine dependencies at all - both are resolved out of the
|
|
20
|
-
* consuming workspace, which is where they already live. Speakeasy's `openapi`
|
|
21
|
-
* binary is installed lazily through `@dbx-tools/core`'s binary cache.
|
|
22
|
-
*/
|
|
23
|
-
import { mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
24
|
-
import { createRequire } from "node:module";
|
|
25
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
26
|
-
import * as bin from "@dbx-tools/core/bin";
|
|
27
|
-
import * as exec from "@dbx-tools/core/exec";
|
|
28
|
-
import { find } from "@dbx-tools/path";
|
|
29
|
-
import { log, object, stringUtils } from "@dbx-tools/shared-core";
|
|
30
|
-
import type * as ts from "typescript";
|
|
31
|
-
import { lazyRequire } from "./_lazy-require.ts";
|
|
32
|
-
import { makeReadonly, makeWritable, stampGenerated } from "./generated.ts";
|
|
33
|
-
import {
|
|
34
|
-
type RecordedPackage,
|
|
35
|
-
isModuleFile,
|
|
36
|
-
repoRoot,
|
|
37
|
-
toPosix,
|
|
38
|
-
recordedPackages,
|
|
39
|
-
} from "./packages.ts";
|
|
40
|
-
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
41
|
-
|
|
42
|
-
const logger = log.logger("projen:openapi");
|
|
43
|
-
|
|
44
|
-
/** The tag (and folder) the generated openapi client packages are written under. */
|
|
45
|
-
const OPENAPI_TAG = "openapi";
|
|
46
|
-
/** Heuristic: a module file whose source imports tsoa's runtime package. */
|
|
47
|
-
const TSOA_IMPORT = /from\s+['"](?:tsoa|@tsoa\/runtime)['"]/;
|
|
48
|
-
const SPEAKEASY_OPENAPI_VERSION = "1.24.0";
|
|
49
|
-
const SPEAKEASY_OPENAPI_RELEASE_URL = `https://github.com/speakeasy-api/openapi/releases/download/v${SPEAKEASY_OPENAPI_VERSION}`;
|
|
50
|
-
|
|
51
|
-
// prettier-ignore
|
|
52
|
-
const CLIENT_SRC =
|
|
53
|
-
stringUtils.dedent(
|
|
54
|
-
// ============================================================================
|
|
55
|
-
/*ts*/`
|
|
56
|
-
import createClient, { type ClientOptions } from "openapi-fetch";
|
|
57
|
-
import type { paths } from "./schema";
|
|
58
|
-
|
|
59
|
-
/** Create a typed OpenAPI client (openapi-fetch); safe to use in the browser. */
|
|
60
|
-
export function createApiClient(options?: ClientOptions) {
|
|
61
|
-
return createClient<paths>(options);
|
|
62
|
-
}
|
|
63
|
-
`
|
|
64
|
-
// ============================================================================
|
|
65
|
-
) + "\n";
|
|
66
|
-
|
|
67
|
-
/** True if any module file in `<pkg>/src` matches {@link TSOA_IMPORT}. */
|
|
68
|
-
function hasTsoaControllers(pkg: Pick<RecordedPackage, "dir">): boolean {
|
|
69
|
-
const srcDir = tsoaSource(pkg);
|
|
70
|
-
return object
|
|
71
|
-
.sequence(find.findFiles("**/*", { cwd: srcDir }))
|
|
72
|
-
.filter(isModuleFile)
|
|
73
|
-
.some((file) => TSOA_IMPORT.test(readFileSync(join(srcDir, file), "utf8")));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function tsoaSource(pkg: Pick<RecordedPackage, "dir">): string {
|
|
77
|
-
return join(pkg.dir, "src");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isTsoaCandidate(pkg: RecordedPackage): boolean {
|
|
81
|
-
return pkg.tags.includes("server") || pkg.tags.includes("node");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/** `server`/`node` packages (never the generated `openapi` tag) with a tsoa import. */
|
|
85
|
-
function controllerPackages(): object.Sequence<RecordedPackage> {
|
|
86
|
-
return object.sequence(recordedPackages()).filter(isTsoaCandidate).filter(hasTsoaControllers);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function inside(path: string, directory: string): boolean {
|
|
90
|
-
const child = relative(resolve(directory), resolve(path));
|
|
91
|
-
return child === "" || (!child.startsWith("..") && !isAbsolute(child));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Source roots consumed by TypeScript OpenAPI producers. */
|
|
95
|
-
export function openapiWatchRoots(): string[] {
|
|
96
|
-
return [
|
|
97
|
-
...controllerPackages()
|
|
98
|
-
.map((pkg) => resolve(tsoaSource(pkg)))
|
|
99
|
-
.distinct(),
|
|
100
|
-
];
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Whether one changed source belongs to an OpenAPI-producing package. */
|
|
104
|
-
export function isOpenapiSource(path: string): boolean {
|
|
105
|
-
const absolute = resolve(path);
|
|
106
|
-
const segments = toPosix(relative(repoRoot, absolute)).split("/");
|
|
107
|
-
if (segments.includes(OPENAPI_TAG)) return false;
|
|
108
|
-
return object
|
|
109
|
-
.sequence(recordedPackages())
|
|
110
|
-
.filter(isTsoaCandidate)
|
|
111
|
-
.filter((pkg) => inside(absolute, tsoaSource(pkg)))
|
|
112
|
-
.some(hasTsoaControllers);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** GitHub release asset name for Speakeasy's OpenAPI binary. */
|
|
116
|
-
export function speakeasyOpenapiAssetName(
|
|
117
|
-
platform: NodeJS.Platform = process.platform,
|
|
118
|
-
arch: string = process.arch,
|
|
119
|
-
): string {
|
|
120
|
-
const osName =
|
|
121
|
-
platform === "darwin"
|
|
122
|
-
? "Darwin"
|
|
123
|
-
: platform === "linux"
|
|
124
|
-
? "Linux"
|
|
125
|
-
: platform === "win32"
|
|
126
|
-
? "Windows"
|
|
127
|
-
: undefined;
|
|
128
|
-
const archName = arch === "arm64" ? "arm64" : arch === "x64" ? "x86_64" : undefined;
|
|
129
|
-
if (!osName || !archName) {
|
|
130
|
-
throw new Error(`Speakeasy openapi has no supported release asset for ${platform}/${arch}`);
|
|
131
|
-
}
|
|
132
|
-
const extension = platform === "win32" ? "zip" : "tar.gz";
|
|
133
|
-
return `openapi_${osName}_${archName}.${extension}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function speakeasyOpenapiPath(): Promise<string> {
|
|
137
|
-
const assetName = speakeasyOpenapiAssetName();
|
|
138
|
-
const context = await bin.ensure("openapi", `${SPEAKEASY_OPENAPI_RELEASE_URL}/${assetName}`, {
|
|
139
|
-
autoUnpackage: true,
|
|
140
|
-
minVersion: SPEAKEASY_OPENAPI_VERSION,
|
|
141
|
-
selector: ({ source }) =>
|
|
142
|
-
join(source, process.platform === "win32" ? "openapi.exe" : "openapi"),
|
|
143
|
-
versionParser: (output) => {
|
|
144
|
-
const version = bin.parseVersion(output);
|
|
145
|
-
return version === SPEAKEASY_OPENAPI_VERSION ? version : undefined;
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
return context.path;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Deduplicate inline schemas into `components.schemas` with Speakeasy. */
|
|
152
|
-
export async function optimizeOpenapiSpec(specPath: string, executable?: string): Promise<void> {
|
|
153
|
-
const openapi = executable ?? (await speakeasyOpenapiPath());
|
|
154
|
-
await exec.spawn(openapi, ["spec", "optimize", specPath, "--write", "--non-interactive"], {
|
|
155
|
-
stdin: "ignore",
|
|
156
|
-
check: true,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
type OpenApiTypeTools = {
|
|
161
|
-
openapiTS: typeof import("openapi-typescript").default;
|
|
162
|
-
astToString: typeof import("openapi-typescript").astToString;
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
async function writeClientPackage(
|
|
166
|
-
outDir: string,
|
|
167
|
-
source: string,
|
|
168
|
-
tools: OpenApiTypeTools,
|
|
169
|
-
): Promise<void> {
|
|
170
|
-
const srcDir = join(outDir, "src");
|
|
171
|
-
mkdirSync(srcDir, { recursive: true });
|
|
172
|
-
const specPath = join(outDir, "openapi.json");
|
|
173
|
-
const spec = JSON.parse(readFileSync(specPath, "utf8"));
|
|
174
|
-
const schemaPath = join(srcDir, "schema.ts");
|
|
175
|
-
makeWritable(schemaPath);
|
|
176
|
-
writeFileSync(schemaPath, tools.astToString(await tools.openapiTS(spec)));
|
|
177
|
-
stampGenerated(schemaPath, {
|
|
178
|
-
tool: "projen openapi (Speakeasy + openapi-typescript)",
|
|
179
|
-
source,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const clientPath = join(srcDir, "client.ts");
|
|
183
|
-
makeWritable(clientPath);
|
|
184
|
-
writeFileSync(clientPath, CLIENT_SRC);
|
|
185
|
-
stampGenerated(clientPath, {
|
|
186
|
-
tool: "projen openapi (openapi-fetch)",
|
|
187
|
-
source: "./schema",
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Regenerate the `openapi` packages from every server/node package with a tsoa
|
|
193
|
-
* import. Returns the package dirs it wrote so the caller can rebuild their barrels.
|
|
194
|
-
* A separate projen synth is still needed before new openapi folders become workspace
|
|
195
|
-
* members in `pnpm-workspace.yaml`.
|
|
196
|
-
*/
|
|
197
|
-
export async function generateOpenapi(): Promise<string[]> {
|
|
198
|
-
const pkgs = controllerPackages().toArray();
|
|
199
|
-
// Same reasoning as codegen's empty case: a workspace with no tsoa controllers
|
|
200
|
-
// is not a condition worth a line on every synth.
|
|
201
|
-
if (pkgs.length === 0) {
|
|
202
|
-
logger.debug("no TypeScript OpenAPI producers found");
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Lazy, resilient loads: tsoa + typescript are CJS (require), openapi-typescript
|
|
207
|
-
// is ESM (dynamic import).
|
|
208
|
-
const { default: openapiTS, astToString } = await import("openapi-typescript");
|
|
209
|
-
const tools = { openapiTS, astToString };
|
|
210
|
-
|
|
211
|
-
const typescript =
|
|
212
|
-
pkgs.length > 0
|
|
213
|
-
? (() => {
|
|
214
|
-
const require = createRequire(import.meta.url);
|
|
215
|
-
const { generateSpec } = lazyRequire<typeof import("tsoa")>(
|
|
216
|
-
require,
|
|
217
|
-
"tsoa",
|
|
218
|
-
"openapi generation",
|
|
219
|
-
);
|
|
220
|
-
const runtime = lazyRequire<typeof ts>(require, "typescript", "openapi generation");
|
|
221
|
-
return {
|
|
222
|
-
generateSpec,
|
|
223
|
-
runtime,
|
|
224
|
-
};
|
|
225
|
-
})()
|
|
226
|
-
: undefined;
|
|
227
|
-
|
|
228
|
-
const version = readWorkspaceVersion(repoRoot);
|
|
229
|
-
const written: string[] = [];
|
|
230
|
-
for (const p of pkgs) {
|
|
231
|
-
// The generated package's folder is the source's leaf folder name (`api`), not
|
|
232
|
-
// its npm name - `p.name` is the (possibly-overridden) manifest name.
|
|
233
|
-
const leaf = p.relPath.split("/").pop() ?? p.relPath;
|
|
234
|
-
const outDir = join(repoRoot, p.root, OPENAPI_TAG, leaf);
|
|
235
|
-
mkdirSync(outDir, { recursive: true });
|
|
236
|
-
|
|
237
|
-
// 1) tsoa writes a temporary openapi.json, Speakeasy optimizes it there, then
|
|
238
|
-
// the complete spec moves into place so readers never observe an intermediate file.
|
|
239
|
-
const specPath = join(outDir, "openapi.json");
|
|
240
|
-
const tempDir = mkdtempSync(join(outDir, ".openapi-"));
|
|
241
|
-
const tempSpecPath = join(tempDir, "openapi.json");
|
|
242
|
-
try {
|
|
243
|
-
const tsconfigPath = join(p.dir, "tsconfig.json");
|
|
244
|
-
const config = typescript!.runtime.readConfigFile(
|
|
245
|
-
tsconfigPath,
|
|
246
|
-
typescript!.runtime.sys.readFile,
|
|
247
|
-
);
|
|
248
|
-
if (config.error) {
|
|
249
|
-
throw new Error(
|
|
250
|
-
typescript!.runtime.flattenDiagnosticMessageText(config.error.messageText, "\n"),
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
const parsed = typescript!.runtime.parseJsonConfigFileContent(
|
|
254
|
-
config.config,
|
|
255
|
-
typescript!.runtime.sys,
|
|
256
|
-
p.dir,
|
|
257
|
-
);
|
|
258
|
-
if (parsed.errors.length > 0) {
|
|
259
|
-
throw new Error(
|
|
260
|
-
parsed.errors
|
|
261
|
-
.map((error) =>
|
|
262
|
-
typescript!.runtime.flattenDiagnosticMessageText(error.messageText, "\n"),
|
|
263
|
-
)
|
|
264
|
-
.join("\n"),
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
await typescript!.generateSpec(
|
|
268
|
-
{
|
|
269
|
-
entryFile: "",
|
|
270
|
-
noImplicitAdditionalProperties: "throw-on-extras",
|
|
271
|
-
controllerPathGlobs: [join(p.dir, "src/**/*.ts")],
|
|
272
|
-
outputDirectory: tempDir,
|
|
273
|
-
specFileBaseName: "openapi",
|
|
274
|
-
specVersion: 3,
|
|
275
|
-
name: `${p.relPath} API`,
|
|
276
|
-
version,
|
|
277
|
-
},
|
|
278
|
-
parsed.options,
|
|
279
|
-
);
|
|
280
|
-
await optimizeOpenapiSpec(tempSpecPath);
|
|
281
|
-
makeWritable(specPath);
|
|
282
|
-
renameSync(tempSpecPath, specPath);
|
|
283
|
-
} finally {
|
|
284
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
285
|
-
}
|
|
286
|
-
makeReadonly(specPath);
|
|
287
|
-
await writeClientPackage(outDir, `the tsoa controllers in ${p.relPath}`, tools);
|
|
288
|
-
|
|
289
|
-
written.push(outDir);
|
|
290
|
-
logger.success(`openapi/${leaf} (from ${p.relPath})`);
|
|
291
|
-
}
|
|
292
|
-
return written;
|
|
293
|
-
}
|
package/tasks/local-publish.ts
DELETED
package/tasks/openapi.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun
|
|
2
|
-
import { log, object, stringUtils } from "@dbx-tools/shared-core";
|
|
3
|
-
import { generateBarrels } from "../src/barrels.ts";
|
|
4
|
-
import { generateOpenapi, isOpenapiSource, openapiWatchRoots } from "../src/openapi.ts";
|
|
5
|
-
import { runSynth } from "../src/scaffold.ts";
|
|
6
|
-
import { watchLoop } from "../src/watch.ts";
|
|
7
|
-
|
|
8
|
-
const logger = log.logger("projen:openapi");
|
|
9
|
-
|
|
10
|
-
export async function main(args: string[] = process.argv.slice(2)): Promise<void> {
|
|
11
|
-
if (args.includes("--watch")) {
|
|
12
|
-
// Watch the package roots; a changed tsoa controller regenerates the openapi
|
|
13
|
-
// packages (spec + client) and rebuilds just their barrels. The projenrc watcher
|
|
14
|
-
// (alongside under `concurrently`) owns full re-synth, so no re-synth here.
|
|
15
|
-
watchLoop(
|
|
16
|
-
"openapi",
|
|
17
|
-
openapiWatchRoots(),
|
|
18
|
-
async () => {
|
|
19
|
-
const dirs = await generateOpenapi();
|
|
20
|
-
if (dirs.length) {
|
|
21
|
-
generateBarrels({ dirs });
|
|
22
|
-
logger.success(`regenerated openapi (${stringUtils.pluralize(dirs.length, "package")})`);
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{ check: (changed) => object.sequence(changed).some(isOpenapiSource) },
|
|
26
|
-
);
|
|
27
|
-
} else {
|
|
28
|
-
// One-shot: regenerate, then re-synth so any newly created openapi folder becomes a
|
|
29
|
-
// workspace member (a new package needs install + linking, which the watch path skips).
|
|
30
|
-
const dirs = await generateOpenapi();
|
|
31
|
-
if (dirs.length > 0) runSynth({ post: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (import.meta.main) await main();
|