@dbx-tools/projen 0.6.74 → 0.6.76
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 +1 -1
- package/package.json +4 -4
- package/src/bun-app.ts +4 -6
- package/src/openapi.ts +87 -20
- package/src/project.ts +4 -4
- package/src/release.ts +3 -3
- package/src/tags.ts +1 -1
- package/src/tsconfig.ts +2 -2
package/README.md
CHANGED
|
@@ -204,7 +204,7 @@ file contract as the CLI.
|
|
|
204
204
|
The engine registers its commands as projen tasks on the workspace root, so run
|
|
205
205
|
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `openapi`, and
|
|
206
206
|
`clean`.
|
|
207
|
-
[`@dbx-tools/cli`](../
|
|
207
|
+
[`@dbx-tools/cli`](../packages/js/cli/dbx-tools) is only needed to
|
|
208
208
|
bootstrap a folder that has no `.projenrc.ts` or toolchain yet.
|
|
209
209
|
|
|
210
210
|
## Run Tasks From The ROOT
|
package/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "0.6.
|
|
30
|
-
"@dbx-tools/path": "0.6.
|
|
31
|
-
"@dbx-tools/shared-core": "0.6.
|
|
29
|
+
"@dbx-tools/core": "0.6.76",
|
|
30
|
+
"@dbx-tools/path": "0.6.76",
|
|
31
|
+
"@dbx-tools/shared-core": "0.6.76",
|
|
32
32
|
"commander": "^15.0.0",
|
|
33
33
|
"concurrently": "^10.0.3",
|
|
34
34
|
"constructs": "^10.6.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"main": "index.ts",
|
|
49
49
|
"license": "Apache-2.0",
|
|
50
|
-
"version": "0.6.
|
|
50
|
+
"version": "0.6.76",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/bun-app.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bun browser-app scaffolding as first-class projen file components.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* An `app`-tagged package gets three generated, read-only files that provide its
|
|
5
|
+
* whole dev/build toolchain:
|
|
6
6
|
*
|
|
7
7
|
* - {@link BunfigFile} - `bunfig.toml` wiring `bun-plugin-tailwind` into both the
|
|
8
8
|
* dev server (`[serve.static]`) and `Bun.build` (`[build]`) so Tailwind v4's
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
* Each file supports an unmanaged OVERRIDE beside it (`bunfig.override.toml`,
|
|
18
18
|
* `bun-dev.override.ts`, `bun-build.override.ts`): the dev/build scripts import
|
|
19
19
|
* the override's default export and merge it over the generated options, so a
|
|
20
|
-
* package tweaks its server/bundle WITHOUT editing the projen-owned file
|
|
21
|
-
* same escape hatch the Vite generator offered via `vite.config.override.*`.
|
|
20
|
+
* package tweaks its server/bundle WITHOUT editing the projen-owned file.
|
|
22
21
|
*
|
|
23
22
|
* bun runs these `.ts` files directly. Being package-ROOT files (not under
|
|
24
23
|
* `src/`), they are outside the package's `tsconfig` include, so their `Bun.*`
|
|
@@ -42,8 +41,7 @@ export const BUN_APP_OVERRIDES = ["bunfig.override.toml", BUN_DEV_OVERRIDE, BUN_
|
|
|
42
41
|
* rejects passing a value built against one to an API typed by the other
|
|
43
42
|
* ("separate declarations of a private property"). The hoisted linker de-dupes to
|
|
44
43
|
* a single flat copy (npm-style), which is what keeps identity-based singletons
|
|
45
|
-
* (AppKit `CacheManager`, Mastra classes, one React) coherent
|
|
46
|
-
* the old cross-workspace `.pnpmfile.cjs` bridge used to guarantee.
|
|
44
|
+
* (AppKit `CacheManager`, Mastra classes, one React) coherent.
|
|
47
45
|
*/
|
|
48
46
|
export class RootBunfigFile extends TextFile {
|
|
49
47
|
constructor(project: Project) {
|
package/src/openapi.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* (`from 'tsoa'` / `from '@tsoa/runtime'`) and, for each package that has them,
|
|
6
6
|
* generates a read-only `<root>/openapi/<name>` package:
|
|
7
7
|
*
|
|
8
|
-
* - `openapi.json` - the OpenAPI 3 spec (tsoa `generateSpec`,
|
|
8
|
+
* - `openapi.json` - the OpenAPI 3 spec (tsoa `generateSpec`, then Speakeasy
|
|
9
|
+
* optimization to extract duplicate inline schemas into components).
|
|
9
10
|
* - `src/schema.ts` - types generated from the spec (openapi-typescript).
|
|
10
11
|
* - `src/client.ts` - a typed `openapi-fetch` client, usable client-side.
|
|
11
12
|
*
|
|
@@ -18,11 +19,23 @@
|
|
|
18
19
|
* `tsoa`, `typescript`, and `openapi-typescript` are loaded lazily (heavy, and only
|
|
19
20
|
* needed for `bun run openapi`), so importing this module stays cheap. `tsoa` and
|
|
20
21
|
* `typescript` are not engine dependencies at all - both are resolved out of the
|
|
21
|
-
* consuming workspace, which is where they already live.
|
|
22
|
+
* consuming workspace, which is where they already live. Speakeasy's `openapi`
|
|
23
|
+
* binary is installed lazily through `@dbx-tools/core`'s binary cache.
|
|
22
24
|
*/
|
|
23
|
-
import {
|
|
25
|
+
import { execFile } from "node:child_process";
|
|
26
|
+
import {
|
|
27
|
+
existsSync,
|
|
28
|
+
mkdirSync,
|
|
29
|
+
mkdtempSync,
|
|
30
|
+
readFileSync,
|
|
31
|
+
renameSync,
|
|
32
|
+
rmSync,
|
|
33
|
+
writeFileSync,
|
|
34
|
+
} from "node:fs";
|
|
24
35
|
import { createRequire } from "node:module";
|
|
25
36
|
import { join } from "node:path";
|
|
37
|
+
import { promisify } from "node:util";
|
|
38
|
+
import { bin } from "@dbx-tools/core";
|
|
26
39
|
import { find } from "@dbx-tools/path";
|
|
27
40
|
import { log } from "@dbx-tools/shared-core";
|
|
28
41
|
import type * as ts from "typescript";
|
|
@@ -42,6 +55,9 @@ const logger = log.logger("projen:openapi");
|
|
|
42
55
|
const OPENAPI_TAG = "openapi";
|
|
43
56
|
/** Heuristic: a module file whose source imports tsoa's runtime package. */
|
|
44
57
|
const TSOA_IMPORT = /from\s+['"](?:tsoa|@tsoa\/runtime)['"]/;
|
|
58
|
+
const SPEAKEASY_OPENAPI_VERSION = "1.24.0";
|
|
59
|
+
const SPEAKEASY_OPENAPI_RELEASE_URL = `https://github.com/speakeasy-api/openapi/releases/download/v${SPEAKEASY_OPENAPI_VERSION}`;
|
|
60
|
+
const execFileAsync = promisify(execFile);
|
|
45
61
|
|
|
46
62
|
const CLIENT_SRC = `import createClient, { type ClientOptions } from "openapi-fetch";
|
|
47
63
|
import type { paths } from "./schema";
|
|
@@ -78,6 +94,48 @@ export function isTsoaController(path: string): boolean {
|
|
|
78
94
|
);
|
|
79
95
|
}
|
|
80
96
|
|
|
97
|
+
/** GitHub release asset name for Speakeasy's OpenAPI binary. */
|
|
98
|
+
export function speakeasyOpenapiAssetName(
|
|
99
|
+
platform: NodeJS.Platform = process.platform,
|
|
100
|
+
arch: string = process.arch,
|
|
101
|
+
): string {
|
|
102
|
+
const osName =
|
|
103
|
+
platform === "darwin"
|
|
104
|
+
? "Darwin"
|
|
105
|
+
: platform === "linux"
|
|
106
|
+
? "Linux"
|
|
107
|
+
: platform === "win32"
|
|
108
|
+
? "Windows"
|
|
109
|
+
: undefined;
|
|
110
|
+
const archName = arch === "arm64" ? "arm64" : arch === "x64" ? "x86_64" : undefined;
|
|
111
|
+
if (!osName || !archName) {
|
|
112
|
+
throw new Error(`Speakeasy openapi has no supported release asset for ${platform}/${arch}`);
|
|
113
|
+
}
|
|
114
|
+
const extension = platform === "win32" ? "zip" : "tar.gz";
|
|
115
|
+
return `openapi_${osName}_${archName}.${extension}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function speakeasyOpenapiPath(): Promise<string> {
|
|
119
|
+
const assetName = speakeasyOpenapiAssetName();
|
|
120
|
+
const context = await bin.ensure("openapi", `${SPEAKEASY_OPENAPI_RELEASE_URL}/${assetName}`, {
|
|
121
|
+
autoUnpackage: true,
|
|
122
|
+
minVersion: SPEAKEASY_OPENAPI_VERSION,
|
|
123
|
+
selector: ({ source }) =>
|
|
124
|
+
join(source, process.platform === "win32" ? "openapi.exe" : "openapi"),
|
|
125
|
+
versionParser: (output) => {
|
|
126
|
+
const version = bin.parseVersion(output);
|
|
127
|
+
return version === SPEAKEASY_OPENAPI_VERSION ? version : undefined;
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
return context.path;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Deduplicate inline schemas into `components.schemas` with Speakeasy. */
|
|
134
|
+
export async function optimizeOpenapiSpec(specPath: string, executable?: string): Promise<void> {
|
|
135
|
+
const openapi = executable ?? (await speakeasyOpenapiPath());
|
|
136
|
+
await execFileAsync(openapi, ["spec", "optimize", specPath, "--write", "--non-interactive"]);
|
|
137
|
+
}
|
|
138
|
+
|
|
81
139
|
/**
|
|
82
140
|
* Regenerate the `openapi` packages from every server/node package with a tsoa
|
|
83
141
|
* import. Returns the package dirs it wrote so the caller can rebuild their barrels.
|
|
@@ -118,28 +176,37 @@ export async function generateOpenapi(): Promise<string[]> {
|
|
|
118
176
|
const written: string[] = [];
|
|
119
177
|
for (const p of pkgs) {
|
|
120
178
|
// The generated package's folder is the source's leaf folder name (`api`), not
|
|
121
|
-
// its npm name - `p.name` is
|
|
179
|
+
// its npm name - `p.name` is the (possibly-overridden) manifest name.
|
|
122
180
|
const leaf = p.relPath.split("/").pop() ?? p.relPath;
|
|
123
181
|
const outDir = join(repoRoot, p.root, OPENAPI_TAG, leaf);
|
|
124
182
|
const srcDir = join(outDir, "src");
|
|
125
183
|
mkdirSync(srcDir, { recursive: true });
|
|
126
184
|
|
|
127
|
-
// 1) tsoa writes
|
|
185
|
+
// 1) tsoa writes a temporary openapi.json, Speakeasy optimizes it there, then
|
|
186
|
+
// the complete spec moves into place so readers never observe an intermediate file.
|
|
128
187
|
const specPath = join(outDir, "openapi.json");
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
188
|
+
const tempDir = mkdtempSync(join(outDir, ".openapi-"));
|
|
189
|
+
const tempSpecPath = join(tempDir, "openapi.json");
|
|
190
|
+
try {
|
|
191
|
+
await generateSpec(
|
|
192
|
+
{
|
|
193
|
+
entryFile: "",
|
|
194
|
+
noImplicitAdditionalProperties: "throw-on-extras",
|
|
195
|
+
controllerPathGlobs: [join(p.dir, "src/**/*.ts")],
|
|
196
|
+
outputDirectory: tempDir,
|
|
197
|
+
specFileBaseName: "openapi",
|
|
198
|
+
specVersion: 3,
|
|
199
|
+
name: `${p.relPath} API`,
|
|
200
|
+
version: "0.0.0",
|
|
201
|
+
},
|
|
202
|
+
compilerOptions,
|
|
203
|
+
);
|
|
204
|
+
await optimizeOpenapiSpec(tempSpecPath);
|
|
205
|
+
makeWritable(specPath);
|
|
206
|
+
renameSync(tempSpecPath, specPath);
|
|
207
|
+
} finally {
|
|
208
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
209
|
+
}
|
|
143
210
|
makeReadonly(specPath);
|
|
144
211
|
|
|
145
212
|
// 2) src/schema.ts: types generated from the spec (openapi-typescript).
|
|
@@ -148,7 +215,7 @@ export async function generateOpenapi(): Promise<string[]> {
|
|
|
148
215
|
makeWritable(schemaPath);
|
|
149
216
|
writeFileSync(schemaPath, astToString(await openapiTS(spec)));
|
|
150
217
|
stampGenerated(schemaPath, {
|
|
151
|
-
tool: "projen openapi (tsoa + openapi-typescript)",
|
|
218
|
+
tool: "projen openapi (tsoa + Speakeasy + openapi-typescript)",
|
|
152
219
|
source: `the tsoa controllers in ${p.relPath}`,
|
|
153
220
|
});
|
|
154
221
|
|
package/src/project.ts
CHANGED
|
@@ -203,8 +203,8 @@ export function applyTasks(pkg: javascript.NodeProject, tasks?: Record<string, T
|
|
|
203
203
|
*/
|
|
204
204
|
export function applyExports(pkg: javascript.NodeProject, exports: Record<string, string>): void {
|
|
205
205
|
pkg.package.addField("exports", exports);
|
|
206
|
-
// Keep the
|
|
207
|
-
//
|
|
206
|
+
// Keep the `main`/`types` entry points consistent with the map. A subpath-only
|
|
207
|
+
// surface (the `ui` tag's `./react` + `./styles.css`) has no
|
|
208
208
|
// `.` export, so the constructor's `main`/`types` would keep advertising a
|
|
209
209
|
// root entry that every exports-aware resolver ignores - the contradiction
|
|
210
210
|
// publint reports as "exports is missing the root entrypoint".
|
|
@@ -673,8 +673,8 @@ export class DBXToolsTypeScriptProject
|
|
|
673
673
|
// `bun test` intercepts `node:test` (the suites keep using node:test) and
|
|
674
674
|
// runs it with bun's own fast runner. Args are FILTERS, not globs; a bare
|
|
675
675
|
// directory auto-discovers `*.test.ts` recursively. But `bun test` EXITS 1
|
|
676
|
-
// when it matches no files
|
|
677
|
-
//
|
|
676
|
+
// when it matches no files, so guard it: only invoke when a `*.test.ts`
|
|
677
|
+
// exists, else succeed.
|
|
678
678
|
this.testTask.exec("bun test test", {
|
|
679
679
|
condition: 'find test -name "*.test.ts" 2>/dev/null | grep -q .',
|
|
680
680
|
});
|
package/src/release.ts
CHANGED
|
@@ -88,9 +88,9 @@ function publishSetupSteps(): JobStep[] {
|
|
|
88
88
|
*
|
|
89
89
|
* Declaring one also enlists it in the root's `bump`, which cuts BOTH tags at one
|
|
90
90
|
* shared version. The separate tag namespace still lets it be released alone
|
|
91
|
-
* (`cd <directory> && bun run bump`) for a consumer who wants only this package
|
|
92
|
-
*
|
|
93
|
-
*
|
|
91
|
+
* (`cd <directory> && bun run bump`) for a consumer who wants only this package.
|
|
92
|
+
* Enlisting it is what keeps a routine root bump from leaving it behind and letting
|
|
93
|
+
* its version drift away from the packages'.
|
|
94
94
|
*/
|
|
95
95
|
export interface StandaloneRelease {
|
|
96
96
|
/** Workflow name (and `.github/workflows/<name>.yml` file). E.g. `projen-release`. */
|
package/src/tags.ts
CHANGED
|
@@ -105,7 +105,7 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
105
105
|
// generated `dev.ts`/`build.ts` use; no `vite/client`.
|
|
106
106
|
types: ["bun"],
|
|
107
107
|
// `@/` -> `src/` alias, resolved by both tsc and bun's bundler (bun reads
|
|
108
|
-
// tsconfig `paths`).
|
|
108
|
+
// tsconfig `paths`).
|
|
109
109
|
baseUrl: ".",
|
|
110
110
|
paths: { "@/*": ["./src/*"] },
|
|
111
111
|
});
|
package/src/tsconfig.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { Component, JsonFile, type Project, javascript } from "projen";
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Compiler options for the ROOT program only (`.projenrc.ts` + the engine as
|
|
8
|
-
* seen from the root). Each package
|
|
8
|
+
* seen from the root). Each package owns its own projen-generated
|
|
9
9
|
* `tsconfig.json` (scope `lib`/`jsx`/`types` overlaid on projen's defaults), so
|
|
10
|
-
* this base
|
|
10
|
+
* this base does not feed packages - it just gives the projenrc/editor program
|
|
11
11
|
* a sane ESM config. `lib` is set narrowly in the root `tsconfig.json`.
|
|
12
12
|
*/
|
|
13
13
|
const BASE_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
|