@dbx-tools/projen 0.9.43 → 0.9.47
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 +28 -34
- package/index.ts +6 -7
- 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 +1 -9
- package/src/codegen.ts +20 -17
- 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 -157
- package/src/project-predicate.ts +7 -3
- package/src/project-py.ts +122 -44
- 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 +30 -9
- 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 +77 -21
- package/tasks/lib/python-registry.ts +4 -0
- package/tasks/lib/python-release.ts +2 -0
- package/tasks/lib/release.ts +1 -1
- 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
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
interface PythonBridge {
|
|
2
|
+
eval(source: string): unknown;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface PythonCompletedProcess {
|
|
6
|
+
returncode: number;
|
|
7
|
+
stdout: string | null;
|
|
8
|
+
stderr: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface PythonHttpResponse {
|
|
12
|
+
status_code: number;
|
|
13
|
+
headers: Record<string, string>;
|
|
14
|
+
content: Iterable<number>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PythonFileHost {
|
|
18
|
+
appendBytes(path: string, content: number[]): Promise<void>;
|
|
19
|
+
chmod(path: string, mode: number): Promise<void>;
|
|
20
|
+
copy(source: string, destination: string): Promise<void>;
|
|
21
|
+
exists(path: string): boolean;
|
|
22
|
+
mkdir(path: string, recursive: boolean): Promise<boolean>;
|
|
23
|
+
mkdtemp(prefix: string): Promise<string>;
|
|
24
|
+
readBytes(path: string): Promise<number[]>;
|
|
25
|
+
readDirectory(path: string): Promise<{ name: string; directory: boolean; file: boolean }[]>;
|
|
26
|
+
readTextSync(path: string): string;
|
|
27
|
+
readDirectorySync(path: string): { name: string; directory: boolean; file: boolean }[];
|
|
28
|
+
readLinkSync(path: string): string;
|
|
29
|
+
realpath(path: string): Promise<string>;
|
|
30
|
+
realpathSync(path: string): string;
|
|
31
|
+
remove(path: string, recursive: boolean, force: boolean): Promise<void>;
|
|
32
|
+
rename(source: string, destination: string): Promise<void>;
|
|
33
|
+
stat(path: string): Promise<{
|
|
34
|
+
directory: boolean;
|
|
35
|
+
file: boolean;
|
|
36
|
+
mode: number;
|
|
37
|
+
mtimeMs: number;
|
|
38
|
+
size: number;
|
|
39
|
+
}>;
|
|
40
|
+
statSync(path: string): {
|
|
41
|
+
directory: boolean;
|
|
42
|
+
file: boolean;
|
|
43
|
+
mode: number;
|
|
44
|
+
mtimeMs: number;
|
|
45
|
+
size: number;
|
|
46
|
+
};
|
|
47
|
+
touch(path: string, atimeMs: number, mtimeMs: number): Promise<void>;
|
|
48
|
+
writeBytes(path: string, content: number[], mode?: number): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface PythonPathHost {
|
|
52
|
+
basename(path: string): string;
|
|
53
|
+
dirname(path: string): string;
|
|
54
|
+
fileUrlToPath(url: string): string;
|
|
55
|
+
isAbsolute(path: string): boolean;
|
|
56
|
+
join(parts: string[]): string;
|
|
57
|
+
relative(from: string, to: string): string;
|
|
58
|
+
resolve(parts: string[]): string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface PythonProcessResult {
|
|
62
|
+
exitCode: number;
|
|
63
|
+
stdout?: string;
|
|
64
|
+
stderr?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A started subprocess: await {@link wait} for its result, or {@link kill} it. */
|
|
68
|
+
export interface PythonProcessHandle {
|
|
69
|
+
/** Terminate the child (SIGKILL). No-op once it has already exited. */
|
|
70
|
+
kill(): void;
|
|
71
|
+
/** Resolve with the child's result once it exits (runs off the main thread). */
|
|
72
|
+
wait(): Promise<PythonProcessResult>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface PythonRuntimeHost {
|
|
76
|
+
crypto: {
|
|
77
|
+
digest(algorithm: string, content: number[], encoding?: "hex" | "base64"): string | number[];
|
|
78
|
+
hashes(): string[];
|
|
79
|
+
randomBytes(length: number): number[];
|
|
80
|
+
};
|
|
81
|
+
file: PythonFileHost;
|
|
82
|
+
os: {
|
|
83
|
+
homedir(): string;
|
|
84
|
+
tmpdir(): string;
|
|
85
|
+
};
|
|
86
|
+
path: PythonPathHost;
|
|
87
|
+
http: {
|
|
88
|
+
fetch(
|
|
89
|
+
url: string,
|
|
90
|
+
method?: string,
|
|
91
|
+
headers?: Record<string, string>,
|
|
92
|
+
body?: string,
|
|
93
|
+
timeoutMs?: number,
|
|
94
|
+
): Promise<{ status: number; headers: Record<string, string>; body: number[] }>;
|
|
95
|
+
};
|
|
96
|
+
process: {
|
|
97
|
+
start(
|
|
98
|
+
command: string,
|
|
99
|
+
args: string[],
|
|
100
|
+
environment: Record<string, string> | undefined,
|
|
101
|
+
input: string | undefined,
|
|
102
|
+
): PythonProcessHandle;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type PythonFunction = (...args: any[]) => any;
|
|
107
|
+
|
|
108
|
+
const python = (globalThis as typeof globalThis & { python?: PythonBridge }).python;
|
|
109
|
+
if (!python) throw new Error("PythonMonkey globalThis.python is unavailable");
|
|
110
|
+
|
|
111
|
+
function evaluate<T extends PythonFunction>(source: string): T {
|
|
112
|
+
return python!.eval(source) as T;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const toThread = evaluate<PythonFunction>("__import__('asyncio').to_thread");
|
|
116
|
+
const osPath = {
|
|
117
|
+
basename: evaluate<PythonFunction>("__import__('os').path.basename"),
|
|
118
|
+
dirname: evaluate<PythonFunction>("__import__('os').path.dirname"),
|
|
119
|
+
exists: evaluate<PythonFunction>("__import__('os').path.exists"),
|
|
120
|
+
isAbsolute: evaluate<PythonFunction>("__import__('os').path.isabs"),
|
|
121
|
+
join: evaluate<PythonFunction>("lambda parts: __import__('os').path.join(*list(parts))"),
|
|
122
|
+
realpath: evaluate<PythonFunction>("__import__('os').path.realpath"),
|
|
123
|
+
relative: evaluate<PythonFunction>("__import__('os').path.relpath"),
|
|
124
|
+
resolve: evaluate<PythonFunction>(
|
|
125
|
+
"lambda parts: __import__('os').path.abspath(__import__('os').path.join(*list(parts))) if list(parts) else __import__('os').getcwd()",
|
|
126
|
+
),
|
|
127
|
+
};
|
|
128
|
+
const readBytes = evaluate<PythonFunction>("lambda path: list(open(path, 'rb').read())");
|
|
129
|
+
const readText = evaluate<PythonFunction>("lambda path: open(path, encoding='utf-8').read()");
|
|
130
|
+
const writeBytes = evaluate<PythonFunction>(
|
|
131
|
+
"lambda path, content: open(path, 'wb').write(bytes(int(value) for value in content))",
|
|
132
|
+
);
|
|
133
|
+
const appendBytes = evaluate<PythonFunction>(
|
|
134
|
+
"lambda path, content: open(path, 'ab').write(bytes(int(value) for value in content))",
|
|
135
|
+
);
|
|
136
|
+
const mkdir = evaluate<PythonFunction>(
|
|
137
|
+
"lambda path, recursive: __import__('os').makedirs(path, exist_ok=recursive) if recursive else __import__('os').mkdir(path)",
|
|
138
|
+
);
|
|
139
|
+
const listDirectory = evaluate<PythonFunction>(
|
|
140
|
+
"lambda path: [{'name': entry.name, 'directory': entry.is_dir(), 'file': entry.is_file()} for entry in __import__('pathlib').Path(path).iterdir()]",
|
|
141
|
+
);
|
|
142
|
+
const readLink = evaluate<PythonFunction>("__import__('os').readlink");
|
|
143
|
+
const statPath = evaluate<PythonFunction>(
|
|
144
|
+
"lambda path: {'directory': __import__('os').path.isdir(path), 'file': __import__('os').path.isfile(path), 'mode': __import__('os').stat(path).st_mode, 'mtimeMs': __import__('os').stat(path).st_mtime * 1000, 'size': __import__('os').stat(path).st_size}",
|
|
145
|
+
);
|
|
146
|
+
const popen = evaluate<PythonFunction>(
|
|
147
|
+
"lambda command, args, environment, input_text: __import__('subprocess').Popen([command, *list(args)], env=dict(environment) if environment is not None else None, stdin=(__import__('subprocess').PIPE if input_text is not None else None), stdout=__import__('subprocess').PIPE, stderr=__import__('subprocess').PIPE, text=True)",
|
|
148
|
+
);
|
|
149
|
+
const communicate = evaluate<PythonFunction>(
|
|
150
|
+
"lambda proc, input_text: (lambda out: {'returncode': proc.returncode, 'stdout': out[0], 'stderr': out[1]})(proc.communicate(input=input_text))",
|
|
151
|
+
);
|
|
152
|
+
const killProcess = evaluate<PythonFunction>("lambda proc: proc.kill()");
|
|
153
|
+
const requestHttp = evaluate<PythonFunction>(
|
|
154
|
+
"lambda url, method, headers, body, timeout: __import__('httpx').request(method or 'GET', url, headers=dict(headers) if headers is not None else None, content=body, timeout=(timeout / 1000) if timeout is not None else 30, follow_redirects=True)",
|
|
155
|
+
);
|
|
156
|
+
const chmod = evaluate<PythonFunction>(
|
|
157
|
+
"lambda path, mode: __import__('os').chmod(path, int(mode))",
|
|
158
|
+
);
|
|
159
|
+
const isDir = evaluate<PythonFunction>("__import__('os').path.isdir");
|
|
160
|
+
const rmtree = evaluate<PythonFunction>("__import__('shutil').rmtree");
|
|
161
|
+
const rmdir = evaluate<PythonFunction>("__import__('os').rmdir");
|
|
162
|
+
const unlink = evaluate<PythonFunction>("__import__('os').unlink");
|
|
163
|
+
|
|
164
|
+
const host: PythonRuntimeHost = {
|
|
165
|
+
crypto: {
|
|
166
|
+
digest: evaluate<PythonFunction>(
|
|
167
|
+
"lambda algorithm, content, encoding: (lambda value: value.hexdigest() if encoding == 'hex' else __import__('base64').b64encode(value.digest()).decode('ascii') if encoding == 'base64' else list(value.digest()))(__import__('hashlib').new(algorithm, bytes(int(item) for item in content)))",
|
|
168
|
+
),
|
|
169
|
+
hashes: evaluate<PythonFunction>("lambda: sorted(__import__('hashlib').algorithms_available)"),
|
|
170
|
+
randomBytes: evaluate<PythonFunction>(
|
|
171
|
+
"lambda length: list(__import__('os').urandom(int(length)))",
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
file: {
|
|
175
|
+
async appendBytes(path, content) {
|
|
176
|
+
await toThread(appendBytes, path, content);
|
|
177
|
+
},
|
|
178
|
+
async chmod(path, mode) {
|
|
179
|
+
await toThread(chmod, path, mode);
|
|
180
|
+
},
|
|
181
|
+
async copy(source, destination) {
|
|
182
|
+
await toThread(
|
|
183
|
+
evaluate<PythonFunction>("__import__('shutil').copyfile"),
|
|
184
|
+
source,
|
|
185
|
+
destination,
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
exists: (path) => Boolean(osPath.exists(path)),
|
|
189
|
+
async mkdir(path, recursive) {
|
|
190
|
+
if (await toThread(osPath.exists, path)) return false;
|
|
191
|
+
await toThread(mkdir, path, recursive);
|
|
192
|
+
return true;
|
|
193
|
+
},
|
|
194
|
+
async mkdtemp(prefix) {
|
|
195
|
+
return toThread(
|
|
196
|
+
evaluate<PythonFunction>("__import__('tempfile').mkdtemp"),
|
|
197
|
+
undefined,
|
|
198
|
+
prefix,
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
async readBytes(path) {
|
|
202
|
+
return toThread(readBytes, path);
|
|
203
|
+
},
|
|
204
|
+
async readDirectory(path) {
|
|
205
|
+
return toThread(listDirectory, path);
|
|
206
|
+
},
|
|
207
|
+
readDirectorySync: (path) => listDirectory(path),
|
|
208
|
+
readLinkSync: (path) => String(readLink(path)),
|
|
209
|
+
readTextSync: (path) => String(readText(path)),
|
|
210
|
+
async realpath(path) {
|
|
211
|
+
return String(await toThread(osPath.realpath, path));
|
|
212
|
+
},
|
|
213
|
+
realpathSync: (path) => String(osPath.realpath(path)),
|
|
214
|
+
async remove(path, recursive, force) {
|
|
215
|
+
if (!(await toThread(osPath.exists, path))) {
|
|
216
|
+
if (!force) throw Object.assign(new Error(`ENOENT: ${path}`), { code: "ENOENT" });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const isDirectory = await toThread(isDir, path);
|
|
220
|
+
const operation = isDirectory ? (recursive ? rmtree : rmdir) : unlink;
|
|
221
|
+
await toThread(operation, path);
|
|
222
|
+
},
|
|
223
|
+
async rename(source, destination) {
|
|
224
|
+
await toThread(evaluate<PythonFunction>("__import__('os').replace"), source, destination);
|
|
225
|
+
},
|
|
226
|
+
async stat(path) {
|
|
227
|
+
return toThread(statPath, path);
|
|
228
|
+
},
|
|
229
|
+
statSync: (path) => statPath(path),
|
|
230
|
+
async touch(path, atimeMs, mtimeMs) {
|
|
231
|
+
await toThread(
|
|
232
|
+
evaluate<PythonFunction>(
|
|
233
|
+
"lambda path, atime, mtime: __import__('os').utime(path, (atime / 1000, mtime / 1000))",
|
|
234
|
+
),
|
|
235
|
+
path,
|
|
236
|
+
atimeMs,
|
|
237
|
+
mtimeMs,
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
async writeBytes(path, content, mode) {
|
|
241
|
+
await toThread(writeBytes, path, content);
|
|
242
|
+
if (mode !== undefined) await toThread(chmod, path, mode);
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
http: {
|
|
246
|
+
async fetch(url, method, headers, body, timeoutMs) {
|
|
247
|
+
const response = (await toThread(
|
|
248
|
+
requestHttp,
|
|
249
|
+
url,
|
|
250
|
+
method,
|
|
251
|
+
headers,
|
|
252
|
+
body,
|
|
253
|
+
timeoutMs,
|
|
254
|
+
)) as PythonHttpResponse;
|
|
255
|
+
return {
|
|
256
|
+
status: response.status_code,
|
|
257
|
+
headers: Object.fromEntries(Object.entries(response.headers)),
|
|
258
|
+
body: Array.from(response.content),
|
|
259
|
+
};
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
os: {
|
|
263
|
+
homedir: evaluate<PythonFunction>("lambda: __import__('pathlib').Path.home().as_posix()"),
|
|
264
|
+
tmpdir: evaluate<PythonFunction>("__import__('tempfile').gettempdir"),
|
|
265
|
+
},
|
|
266
|
+
path: {
|
|
267
|
+
basename: (path) => String(osPath.basename(path)),
|
|
268
|
+
dirname: (path) => String(osPath.dirname(path)),
|
|
269
|
+
fileUrlToPath: evaluate<PythonFunction>(
|
|
270
|
+
"lambda url: __import__('urllib.parse', fromlist=['urlparse']).unquote(__import__('urllib.parse', fromlist=['urlparse']).urlparse(url).path)",
|
|
271
|
+
),
|
|
272
|
+
isAbsolute: (path) => Boolean(osPath.isAbsolute(path)),
|
|
273
|
+
join: (parts) => String(osPath.join(parts)),
|
|
274
|
+
relative: (from, to) => String(osPath.relative(to, from)),
|
|
275
|
+
resolve: (parts) => String(osPath.resolve(parts)),
|
|
276
|
+
},
|
|
277
|
+
process: {
|
|
278
|
+
start(command, args, environment, input) {
|
|
279
|
+
// Popen (not subprocess.run) so the child is killable: an aborted caller
|
|
280
|
+
// can terminate it instead of leaking a background process. There is no
|
|
281
|
+
// imposed timeout - the child runs to completion (an interactive login may
|
|
282
|
+
// legitimately take minutes); the caller owns cancellation.
|
|
283
|
+
const proc = popen(command, args, environment, input);
|
|
284
|
+
let killed = false;
|
|
285
|
+
return {
|
|
286
|
+
kill() {
|
|
287
|
+
if (killed) return;
|
|
288
|
+
killed = true;
|
|
289
|
+
try {
|
|
290
|
+
killProcess(proc);
|
|
291
|
+
} catch {
|
|
292
|
+
// The child already exited; there is nothing to signal.
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
async wait() {
|
|
296
|
+
// communicate() blocks until exit, so run it off the main thread.
|
|
297
|
+
const result = (await toThread(communicate, proc, input)) as PythonCompletedProcess;
|
|
298
|
+
return {
|
|
299
|
+
exitCode: result.returncode,
|
|
300
|
+
...(result.stdout?.trim() ? { stdout: result.stdout.trim() } : {}),
|
|
301
|
+
...(result.stderr?.trim() ? { stderr: result.stderr.trim() } : {}),
|
|
302
|
+
};
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
export function installPythonGlobals(): void {
|
|
310
|
+
const globals = globalThis as typeof globalThis & {
|
|
311
|
+
global?: typeof globalThis;
|
|
312
|
+
self?: typeof globalThis;
|
|
313
|
+
window?: typeof globalThis;
|
|
314
|
+
process?: Record<string, unknown>;
|
|
315
|
+
};
|
|
316
|
+
globals.global = globalThis;
|
|
317
|
+
globals.self = globalThis;
|
|
318
|
+
globals.window = globalThis;
|
|
319
|
+
globals.process ??= {
|
|
320
|
+
arch: String(evaluate<PythonFunction>("__import__('platform').machine")())
|
|
321
|
+
.replace("aarch64", "arm64")
|
|
322
|
+
.replace("x86_64", "x64"),
|
|
323
|
+
argv: [],
|
|
324
|
+
browser: true,
|
|
325
|
+
cwd: evaluate<PythonFunction>("__import__('os').getcwd"),
|
|
326
|
+
env: evaluate<PythonFunction>("lambda: dict(__import__('os').environ)")(),
|
|
327
|
+
nextTick: (callback: (...args: unknown[]) => void, ...args: unknown[]) =>
|
|
328
|
+
Promise.resolve().then(() => callback(...args)),
|
|
329
|
+
platform: { Darwin: "darwin", Linux: "linux", Windows: "win32" }[
|
|
330
|
+
String(evaluate<PythonFunction>("__import__('platform').system")())
|
|
331
|
+
],
|
|
332
|
+
version: "v22.0.0",
|
|
333
|
+
versions: {},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
installPythonGlobals();
|
|
338
|
+
|
|
339
|
+
export function pythonHost(): PythonRuntimeHost {
|
|
340
|
+
return host;
|
|
341
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { pythonHost } from "./host.ts";
|
|
2
|
+
|
|
3
|
+
export const constants = {
|
|
4
|
+
errno: {
|
|
5
|
+
EEXIST: 17,
|
|
6
|
+
ENOTDIR: 20,
|
|
7
|
+
EISDIR: 21,
|
|
8
|
+
EINVAL: 22,
|
|
9
|
+
},
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export function homedir(): string {
|
|
13
|
+
return pythonHost().os.homedir();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function tmpdir(): string {
|
|
17
|
+
return pythonHost().os.tmpdir();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default { constants, homedir, tmpdir };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { installPythonGlobals } from "./host.ts";
|
|
2
|
+
|
|
3
|
+
installPythonGlobals();
|
|
4
|
+
|
|
5
|
+
export default process;
|
|
6
|
+
export const arch = process.arch;
|
|
7
|
+
export const argv = process.argv;
|
|
8
|
+
export const cwd = process.cwd;
|
|
9
|
+
export const env = process.env;
|
|
10
|
+
export const nextTick = process.nextTick;
|
|
11
|
+
export const platform = process.platform;
|
|
12
|
+
export const version = process.version;
|
|
13
|
+
export const versions = process.versions;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async line reader used by subprocess output capture in PythonMonkey bundles.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
interface ReadLineOptions {
|
|
8
|
+
readonly input: AsyncIterable<unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class PythonReadLine implements AsyncIterable<string> {
|
|
12
|
+
private closed = false;
|
|
13
|
+
|
|
14
|
+
constructor(private readonly input: AsyncIterable<unknown>) {}
|
|
15
|
+
|
|
16
|
+
async *[Symbol.asyncIterator](): AsyncIterator<string> {
|
|
17
|
+
let buffered = "";
|
|
18
|
+
for await (const chunk of this.input) {
|
|
19
|
+
if (this.closed) break;
|
|
20
|
+
buffered += String(chunk);
|
|
21
|
+
const lines = buffered.split(/\r?\n/);
|
|
22
|
+
buffered = lines.pop() ?? "";
|
|
23
|
+
for (const line of lines) yield line;
|
|
24
|
+
}
|
|
25
|
+
if (!this.closed && buffered) yield buffered.replace(/\r$/, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
close(): void {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createInterface(options: ReadLineOptions): PythonReadLine {
|
|
34
|
+
return new PythonReadLine(options.input);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default { createInterface };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ~~ Generated by projen. To modify, edit .projenrc.js and run "bunx projen".
|
|
2
|
+
{
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"lib": [
|
|
8
|
+
"ES2022",
|
|
9
|
+
"DOM"
|
|
10
|
+
],
|
|
11
|
+
"types": [
|
|
12
|
+
"node"
|
|
13
|
+
],
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strict": false
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"*.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules"
|
|
23
|
+
]
|
|
24
|
+
}
|
package/src/_lazy-require.ts
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Load heavy generator tools only when code generation has work to do.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pnpm that walk passes through `node_modules/.pnpm/node_modules` - the hidden
|
|
9
|
-
* directory holding every package installed ANYWHERE in the workspace. So a tool
|
|
10
|
-
* declared by some member package (`tsoa`, via the `server` tag) or by the root
|
|
11
|
-
* (`typescript`) resolves fine from here without the engine shipping its own copy.
|
|
12
|
-
*
|
|
13
|
-
* The failure mode is a consumer whose workspace never installed the tool, which
|
|
14
|
-
* surfaces as a bare MODULE_NOT_FOUND naming a package they never asked for. The
|
|
15
|
-
* helper below turns that into the install command instead.
|
|
4
|
+
* Code generation drives a large toolchain only when a package declares inputs,
|
|
5
|
+
* so those tools are not imported at module scope. This helper identifies a
|
|
6
|
+
* broken engine installation instead of exposing a bare MODULE_NOT_FOUND from a
|
|
7
|
+
* lazy require.
|
|
16
8
|
*/
|
|
17
9
|
|
|
18
|
-
/** How to obtain each lazily-loaded tool, for the error message. */
|
|
19
|
-
const INSTALL_HINTS: Record<string, string> = {
|
|
20
|
-
tsoa: "pnpm add tsoa",
|
|
21
|
-
typescript: "pnpm add -D typescript",
|
|
22
|
-
"ts-to-zod": "pnpm add -D ts-to-zod",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
10
|
/**
|
|
26
|
-
* `require`
|
|
27
|
-
* actionable error rather than a raw MODULE_NOT_FOUND.
|
|
11
|
+
* `require` an engine-owned generator dependency only when codegen needs it.
|
|
28
12
|
*
|
|
29
13
|
* @param require - a `createRequire(import.meta.url)` bound to the calling module,
|
|
30
14
|
* so the resolution walk starts at the engine and reaches the consumer's install.
|
|
@@ -36,10 +20,8 @@ export function lazyRequire<T>(require: NodeJS.Require, name: string, reason: st
|
|
|
36
20
|
return require(name) as T;
|
|
37
21
|
} catch (cause) {
|
|
38
22
|
if ((cause as NodeJS.ErrnoException)?.code !== "MODULE_NOT_FOUND") throw cause;
|
|
39
|
-
const hint = INSTALL_HINTS[name] ?? `pnpm add -D ${name}`;
|
|
40
23
|
throw new Error(
|
|
41
|
-
`${reason}
|
|
42
|
-
`Add it to the package that needs it (\`${hint}\`) and re-run.`,
|
|
24
|
+
`${reason} requires the \`${name}\` dependency shipped by @dbx-tools/projen; reinstall workspace dependencies and re-run`,
|
|
43
25
|
{ cause },
|
|
44
26
|
);
|
|
45
27
|
}
|
package/src/bun-app.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* imports `index.html` and serves the SPA with React hot module reloading.
|
|
12
12
|
* - {@link BunBuildFile} - `build.ts`: a Databricks-safe `Bun.build()` production
|
|
13
13
|
* bundle of `index.html` (the `bun-plugin-tailwind` plugin must run through the
|
|
14
|
-
* JS API, NOT the `bun build` CLI, which does not load it), plus
|
|
15
|
-
*
|
|
14
|
+
* JS API, NOT the `bun build` CLI, which does not load it), plus copying an
|
|
15
|
+
* optional `public/` directory.
|
|
16
16
|
*
|
|
17
17
|
* The dev and build scripts support unmanaged `bun-dev.override.ts` and
|
|
18
18
|
* `bun-build.override.ts` modules beside them. Each script imports the override's
|
package/src/bun-workflow.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
/** Shared Bun setup for generated workflows. */
|
|
2
2
|
|
|
3
|
+
/** Bun version used by package manifests, generated workflows, and CLI services. */
|
|
3
4
|
export const BUN_VERSION = "1.3.14";
|
|
4
|
-
|
|
5
|
-
/** Install the pinned Bun version. */
|
|
6
|
-
export function bunSetupStep(): Readonly<Record<string, unknown>> {
|
|
7
|
-
return {
|
|
8
|
-
name: "Setup Bun",
|
|
9
|
-
uses: "oven-sh/setup-bun@v2",
|
|
10
|
-
with: { "bun-version": "${{ env.BUN_VERSION }}" },
|
|
11
|
-
};
|
|
12
|
-
}
|
package/src/codegen.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Codegen generator (ts-to-zod based).
|
|
3
3
|
*
|
|
4
|
-
* Scans every package whose `
|
|
5
|
-
*
|
|
4
|
+
* Scans every package whose `dbxToolsConfig.codegenInputs` declares upstream
|
|
5
|
+
* `.d.ts` inputs and turns them into read-only `src/`
|
|
6
6
|
* modules of zod schemas plus matching inferred TypeScript types. Each input
|
|
7
7
|
* emits one `src/<name>.ts` (schemas + `export type X = z.infer<typeof
|
|
8
8
|
* xSchema>` lines); the barrel generator then namespaces it into the package's
|
|
9
9
|
* root barrel like any other `src/` module (`sdkModel.dashboards.genieMessageSchema`).
|
|
10
10
|
*
|
|
11
11
|
* The single source of truth for "which packages get generated content, from
|
|
12
|
-
* which inputs" is
|
|
12
|
+
* which inputs" is the typed config serialized into each package manifest:
|
|
13
13
|
*
|
|
14
14
|
* {
|
|
15
15
|
* "name": "@dbx-tools/shared-sdk-model",
|
|
16
|
-
* "
|
|
17
|
-
* "
|
|
16
|
+
* "dbxToolsConfig": {
|
|
17
|
+
* "codegenInputs": [
|
|
18
18
|
* "node_modules/@databricks/sdk-experimental/dist/apis/dashboards/model.d.ts"
|
|
19
19
|
* ]
|
|
20
20
|
* }
|
|
@@ -46,18 +46,23 @@
|
|
|
46
46
|
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
47
47
|
import { createRequire } from "node:module";
|
|
48
48
|
import { basename, dirname, join, resolve } from "node:path";
|
|
49
|
-
import { log
|
|
49
|
+
import { log } from "@dbx-tools/shared-core";
|
|
50
50
|
import type * as ts from "typescript";
|
|
51
51
|
import { lazyRequire } from "./_lazy-require.ts";
|
|
52
52
|
import { header, makeReadonly, makeWritable } from "./generated.ts";
|
|
53
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
readDbxToolsConfig,
|
|
55
|
+
readPackageManifest,
|
|
56
|
+
recordedPackages,
|
|
57
|
+
resolveRepoRoot,
|
|
58
|
+
} from "./packages.ts";
|
|
54
59
|
|
|
55
60
|
const logger = log.logger("projen:codegen");
|
|
56
61
|
|
|
57
62
|
/** Do-not-edit banner stamped on every generated codegen module. */
|
|
58
63
|
const HEADER = header({
|
|
59
64
|
tool: "projen synth (codegen: ts-to-zod)",
|
|
60
|
-
source: "the upstream .d.ts declared in
|
|
65
|
+
source: "the upstream .d.ts declared in dbxToolsConfig.codegenInputs",
|
|
61
66
|
});
|
|
62
67
|
|
|
63
68
|
interface CodegenInput {
|
|
@@ -77,11 +82,9 @@ function sourceLabel(source: string): string {
|
|
|
77
82
|
return source.startsWith(nodeModulesPrefix) ? source.slice(nodeModulesPrefix.length) : source;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
/** Read a package's
|
|
85
|
+
/** Read a package's serialized `dbxToolsConfig.codegenInputs`, or `undefined` if absent. */
|
|
81
86
|
function codegenInputs(dir: string): string[] | undefined {
|
|
82
|
-
const
|
|
83
|
-
if (!object.isRecord(codegen)) return undefined;
|
|
84
|
-
const inputs = codegen.inputs;
|
|
87
|
+
const inputs = readDbxToolsConfig(dir)?.codegenInputs;
|
|
85
88
|
return Array.isArray(inputs) && inputs.length > 0 ? (inputs as string[]) : undefined;
|
|
86
89
|
}
|
|
87
90
|
|
|
@@ -100,7 +103,7 @@ function parseInputArg(value: string): CodegenInput {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
/**
|
|
103
|
-
* The `src/` modules a set of
|
|
106
|
+
* The `src/` modules a set of codegen input specs will emit, as package-relative
|
|
104
107
|
* posix paths - WITHOUT resolving or reading any input.
|
|
105
108
|
*
|
|
106
109
|
* This exists so callers that only need to know WHICH files are generated (the
|
|
@@ -344,9 +347,9 @@ function generatePackage(
|
|
|
344
347
|
}
|
|
345
348
|
|
|
346
349
|
/**
|
|
347
|
-
* Regenerate
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
+
* Regenerate source modules for every package declaring `dbxToolsConfig.codegenInputs`.
|
|
351
|
+
* Returns the package dirs it wrote so the caller can rebuild their barrels.
|
|
352
|
+
* `ts-to-zod` and `typescript` are lazy-loaded.
|
|
350
353
|
*/
|
|
351
354
|
export function generateCodegen(
|
|
352
355
|
projectRoot: string = resolveRepoRoot(),
|
|
@@ -365,7 +368,7 @@ export function generateCodegen(
|
|
|
365
368
|
// than printing a line about work that was never asked for. `debug` keeps it
|
|
366
369
|
// reachable when someone is actually chasing why codegen produced nothing.
|
|
367
370
|
if (targets.length === 0) {
|
|
368
|
-
logger.debug("no packages declare
|
|
371
|
+
logger.debug("no packages declare dbxToolsConfig.codegenInputs");
|
|
369
372
|
return [];
|
|
370
373
|
}
|
|
371
374
|
|