@dbx-tools/projen 0.9.93 → 0.9.99
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 +42 -6
- package/index.ts +3 -1
- package/package.json +4 -4
- package/src/dev-watch.ts +15 -0
- package/src/pnpm-workspace.ts +1 -0
- package/src/project-js.ts +67 -14
- package/src/python-node-bindings.ts +279 -21
- package/src/release.ts +0 -10
- package/src/tags.ts +2 -6
- package/src/tsconfig.ts +1 -1
- package/tasks/dev-watch.ts +339 -0
- package/tasks/python-node-bindings.ts +64 -100
- package/tasks/release-notes.ts +24 -1
- package/tasks/release.ts +45 -4
|
@@ -1,14 +1,58 @@
|
|
|
1
1
|
/** Shared pyproject-backed configuration for PythonMonkey Node bindings. */
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { find } from "@dbx-tools/path";
|
|
6
7
|
import { object, stringUtils } from "@dbx-tools/shared-core";
|
|
7
8
|
import { parse } from "smol-toml";
|
|
8
9
|
import ts from "typescript";
|
|
9
10
|
import { runTaskCommand } from "./_task-command.ts";
|
|
11
|
+
import { makeReadonly } from "./generated.ts";
|
|
12
|
+
import { publicFunctionExports, publicNamespaceExports } from "./module-exports.ts";
|
|
10
13
|
import { resolveRepoRoot, workspaceDependencyDirectories } from "./packages.ts";
|
|
11
14
|
|
|
15
|
+
const FINGERPRINT_VERSION = 5;
|
|
16
|
+
const INPUT_FINGERPRINT_LABEL = "dbx-tools binding inputs sha256";
|
|
17
|
+
const CONTENT_FINGERPRINT_LABEL = "dbx-tools binding content sha256";
|
|
18
|
+
const PYTHON_KEYWORDS = new Set([
|
|
19
|
+
"and",
|
|
20
|
+
"as",
|
|
21
|
+
"assert",
|
|
22
|
+
"async",
|
|
23
|
+
"await",
|
|
24
|
+
"break",
|
|
25
|
+
"class",
|
|
26
|
+
"continue",
|
|
27
|
+
"def",
|
|
28
|
+
"del",
|
|
29
|
+
"elif",
|
|
30
|
+
"else",
|
|
31
|
+
"except",
|
|
32
|
+
"False",
|
|
33
|
+
"finally",
|
|
34
|
+
"for",
|
|
35
|
+
"from",
|
|
36
|
+
"global",
|
|
37
|
+
"if",
|
|
38
|
+
"import",
|
|
39
|
+
"in",
|
|
40
|
+
"is",
|
|
41
|
+
"lambda",
|
|
42
|
+
"None",
|
|
43
|
+
"nonlocal",
|
|
44
|
+
"not",
|
|
45
|
+
"or",
|
|
46
|
+
"pass",
|
|
47
|
+
"raise",
|
|
48
|
+
"return",
|
|
49
|
+
"True",
|
|
50
|
+
"try",
|
|
51
|
+
"while",
|
|
52
|
+
"with",
|
|
53
|
+
"yield",
|
|
54
|
+
]);
|
|
55
|
+
|
|
12
56
|
/** Resolved function replacement consumed while generating one PythonMonkey bundle. */
|
|
13
57
|
export interface ResolvedPythonNodeFunctionOverride {
|
|
14
58
|
readonly handlerExport: string;
|
|
@@ -38,6 +82,8 @@ export interface ResolvedPythonNodeBindings {
|
|
|
38
82
|
export interface GeneratePythonNodeBindingsOptions {
|
|
39
83
|
/** Verify generated output without changing it. */
|
|
40
84
|
readonly check?: boolean;
|
|
85
|
+
/** Regenerate outputs without consulting fingerprints. */
|
|
86
|
+
readonly force?: boolean;
|
|
41
87
|
}
|
|
42
88
|
|
|
43
89
|
/** Generate or verify every pyproject-configured Node binding in a repository. */
|
|
@@ -46,10 +92,14 @@ export function generatePythonNodeBindings(
|
|
|
46
92
|
options: GeneratePythonNodeBindingsOptions = {},
|
|
47
93
|
): void {
|
|
48
94
|
const task = resolve(dirname(fileURLToPath(import.meta.url)), "../tasks/python-node-bindings.ts");
|
|
49
|
-
const
|
|
95
|
+
const force = options.force ?? process.env.CI === "true";
|
|
96
|
+
const configuredProjects = pythonNodeBindingProjects(projectRoot).map((project) => ({
|
|
97
|
+
project,
|
|
98
|
+
configs: resolvePythonNodeBindings(projectRoot, project).map(resolvePythonNodeBindingModules),
|
|
99
|
+
}));
|
|
50
100
|
const directories = new Set<string>();
|
|
51
|
-
for (const
|
|
52
|
-
for (const config of
|
|
101
|
+
for (const { configs } of configuredProjects) {
|
|
102
|
+
for (const config of configs) {
|
|
53
103
|
directories.add(relative(projectRoot, join(config.moduleDirectory, "_generated", "node")));
|
|
54
104
|
}
|
|
55
105
|
}
|
|
@@ -64,7 +114,12 @@ export function generatePythonNodeBindings(
|
|
|
64
114
|
for (const directory of stale) {
|
|
65
115
|
rmSync(resolve(projectRoot, directory), { recursive: true, force: true });
|
|
66
116
|
}
|
|
67
|
-
for (const project of configuredProjects) {
|
|
117
|
+
for (const { configs, project } of configuredProjects) {
|
|
118
|
+
const inputFingerprint = pythonNodeBindingInputFingerprint(projectRoot, configs);
|
|
119
|
+
if (!force && pythonNodeBindingOutputsCurrent(configs, inputFingerprint)) {
|
|
120
|
+
for (const output of pythonNodeBindingOutputs(configs)) makeReadonly(output);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
68
123
|
runTaskCommand(projectRoot, "bun", [
|
|
69
124
|
task,
|
|
70
125
|
"--root",
|
|
@@ -72,10 +127,194 @@ export function generatePythonNodeBindings(
|
|
|
72
127
|
"--project",
|
|
73
128
|
project,
|
|
74
129
|
...(options.check ? ["--check"] : []),
|
|
130
|
+
...(force ? ["--force"] : []),
|
|
75
131
|
]);
|
|
76
132
|
}
|
|
77
133
|
}
|
|
78
134
|
|
|
135
|
+
/** Resolve automatically discovered bindable namespaces for one configured package. */
|
|
136
|
+
export function resolvePythonNodeBindingModules(
|
|
137
|
+
config: ResolvedPythonNodeBindings,
|
|
138
|
+
): ResolvedPythonNodeBindings {
|
|
139
|
+
if (config.modules.length > 0) return config;
|
|
140
|
+
const packageEntrypoint = Bun.resolveSync(config.entrypoint, config.projectDirectory);
|
|
141
|
+
const modules = publicNamespaceExports(packageEntrypoint).filter(
|
|
142
|
+
(module) =>
|
|
143
|
+
publicFunctionExports(resolvePythonNodeBindingModule(packageEntrypoint, module)).length > 0,
|
|
144
|
+
);
|
|
145
|
+
return { ...config, modules };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Map a JavaScript export or namespace to its generated Python identifier. */
|
|
149
|
+
export function pythonNodeBindingFunctionName(javascriptName: string): string {
|
|
150
|
+
const name = stringUtils.toIdentifierWithOptions({ delimiter: "_" }, javascriptName);
|
|
151
|
+
if (!isPythonNodeBindingIdentifier(name)) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`JavaScript export ${javascriptName} does not map to a valid Python function name`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return name;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Whether a value is a legal non-keyword Python identifier. */
|
|
160
|
+
export function isPythonNodeBindingIdentifier(value: string): boolean {
|
|
161
|
+
return /^[_A-Za-z]\w*$/.test(value) && !PYTHON_KEYWORDS.has(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Content fingerprint for every source and toolchain input affecting generated bindings. */
|
|
165
|
+
export function pythonNodeBindingInputFingerprint(
|
|
166
|
+
root: string,
|
|
167
|
+
configs: readonly ResolvedPythonNodeBindings[],
|
|
168
|
+
): string {
|
|
169
|
+
const canonicalRoot = canonicalPath(root);
|
|
170
|
+
const fingerprintPath = (path: string): string => relative(canonicalRoot, canonicalPath(path));
|
|
171
|
+
const sourceFile = canonicalPath(fileURLToPath(import.meta.url));
|
|
172
|
+
const sourceDirectory = dirname(sourceFile);
|
|
173
|
+
const files = new Set<string>([
|
|
174
|
+
sourceFile,
|
|
175
|
+
canonicalPath(resolve(sourceDirectory, "../tasks/python-node-bindings.ts")),
|
|
176
|
+
canonicalPath(join(sourceDirectory, "generated.ts")),
|
|
177
|
+
canonicalPath(join(sourceDirectory, "module-exports.ts")),
|
|
178
|
+
canonicalPath(join(sourceDirectory, "packages.ts")),
|
|
179
|
+
canonicalPath(join(sourceDirectory, "python-node-runtime.ts")),
|
|
180
|
+
]);
|
|
181
|
+
const versionFile = join(root, "VERSION");
|
|
182
|
+
if (existsSync(versionFile)) files.add(canonicalPath(versionFile));
|
|
183
|
+
for (const config of configs) {
|
|
184
|
+
for (const input of config.watchInputs) files.add(canonicalPath(resolve(root, input)));
|
|
185
|
+
for (const { handlerFile } of config.functionOverrides) files.add(canonicalPath(handlerFile));
|
|
186
|
+
const packageEntrypoint = canonicalPath(
|
|
187
|
+
Bun.resolveSync(config.entrypoint, config.projectDirectory),
|
|
188
|
+
);
|
|
189
|
+
files.add(packageEntrypoint);
|
|
190
|
+
const packageManifest = nearestPackageManifest(packageEntrypoint);
|
|
191
|
+
if (packageManifest) files.add(canonicalPath(packageManifest));
|
|
192
|
+
for (const module of config.modules) {
|
|
193
|
+
files.add(canonicalPath(resolvePythonNodeBindingModule(packageEntrypoint, module)));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const hash = createHash("sha256");
|
|
198
|
+
hash.update(
|
|
199
|
+
JSON.stringify({
|
|
200
|
+
configs: configs.map((config) => ({
|
|
201
|
+
bindingDirectory: fingerprintPath(config.bindingDirectory),
|
|
202
|
+
bindingName: config.bindingName,
|
|
203
|
+
entrypoint: config.entrypoint,
|
|
204
|
+
functionOverrides: config.functionOverrides.map((override) => ({
|
|
205
|
+
handlerExport: override.handlerExport,
|
|
206
|
+
handlerFile: fingerprintPath(override.handlerFile),
|
|
207
|
+
targetExport: override.targetExport,
|
|
208
|
+
targetModule: override.targetModule,
|
|
209
|
+
})),
|
|
210
|
+
moduleDirectory: fingerprintPath(config.moduleDirectory),
|
|
211
|
+
modules: config.modules,
|
|
212
|
+
package: config.package,
|
|
213
|
+
project: config.project,
|
|
214
|
+
runtimeOutput: fingerprintPath(config.runtimeOutput),
|
|
215
|
+
})),
|
|
216
|
+
format: FINGERPRINT_VERSION,
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
for (const { file, path } of [...files]
|
|
220
|
+
.map((file) => ({ file, path: fingerprintPath(file) }))
|
|
221
|
+
.sort((left, right) => left.path.localeCompare(right.path))) {
|
|
222
|
+
hash.update("\0file\0");
|
|
223
|
+
hash.update(path);
|
|
224
|
+
hash.update("\0");
|
|
225
|
+
hash.update(existsSync(file) ? readFileSync(file) : "<missing>");
|
|
226
|
+
}
|
|
227
|
+
return hash.digest("hex");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Complete generated output set for one Python project's Node bindings. */
|
|
231
|
+
export function pythonNodeBindingOutputs(configs: readonly ResolvedPythonNodeBindings[]): string[] {
|
|
232
|
+
const nodeDirectory = pythonNodeBindingDirectory(configs);
|
|
233
|
+
return [
|
|
234
|
+
join(nodeDirectory, "_runtime.js"),
|
|
235
|
+
join(nodeDirectory, "_runtime.py"),
|
|
236
|
+
...configs.flatMap((config) =>
|
|
237
|
+
config.modules.length > 0
|
|
238
|
+
? config.modules.map((module) =>
|
|
239
|
+
join(config.bindingDirectory, `${pythonNodeBindingFunctionName(module)}.py`),
|
|
240
|
+
)
|
|
241
|
+
: [join(config.bindingDirectory, "index.py")],
|
|
242
|
+
),
|
|
243
|
+
].sort();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Whether all expected outputs exactly match their input and body fingerprints. */
|
|
247
|
+
export function pythonNodeBindingOutputsCurrent(
|
|
248
|
+
configs: readonly ResolvedPythonNodeBindings[],
|
|
249
|
+
inputFingerprint: string,
|
|
250
|
+
): boolean {
|
|
251
|
+
const generatedPackage = join(configs[0]!.moduleDirectory, "_generated", "__init__.py");
|
|
252
|
+
if (existsSync(generatedPackage)) return false;
|
|
253
|
+
const expected = pythonNodeBindingOutputs(configs);
|
|
254
|
+
const actual = filesBelow(pythonNodeBindingDirectory(configs)).sort();
|
|
255
|
+
if (actual.length !== expected.length || actual.some((file, index) => file !== expected[index])) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return expected.every((output) => pythonNodeBindingFingerprintCurrent(output, inputFingerprint));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Prefix generated output with its input fingerprint and a self-validating body digest. */
|
|
262
|
+
export function fingerprintPythonNodeBindingOutput(
|
|
263
|
+
contents: string,
|
|
264
|
+
inputFingerprint: string,
|
|
265
|
+
output: string,
|
|
266
|
+
): string {
|
|
267
|
+
const prefix = output.endsWith(".py") ? "#" : "//";
|
|
268
|
+
const contentFingerprint = createHash("sha256").update(contents).digest("hex");
|
|
269
|
+
return [
|
|
270
|
+
`${prefix} ${INPUT_FINGERPRINT_LABEL}: ${inputFingerprint}`,
|
|
271
|
+
`${prefix} ${CONTENT_FINGERPRINT_LABEL}: ${contentFingerprint}`,
|
|
272
|
+
contents,
|
|
273
|
+
].join("\n");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function pythonNodeBindingDirectory(configs: readonly ResolvedPythonNodeBindings[]): string {
|
|
277
|
+
const moduleDirectory = configs[0]?.moduleDirectory;
|
|
278
|
+
if (!moduleDirectory) throw new Error("Expected at least one Node binding configuration");
|
|
279
|
+
return join(moduleDirectory, "_generated", "node");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function pythonNodeBindingFingerprintCurrent(output: string, inputFingerprint: string): boolean {
|
|
283
|
+
try {
|
|
284
|
+
const [inputLine, contentLine, ...bodyLines] = readFileSync(output, "utf8").split("\n");
|
|
285
|
+
const prefix = output.endsWith(".py") ? "#" : "//";
|
|
286
|
+
if (inputLine !== `${prefix} ${INPUT_FINGERPRINT_LABEL}: ${inputFingerprint}`) return false;
|
|
287
|
+
const escapedPrefix = prefix === "//" ? "\\/\\/" : "#";
|
|
288
|
+
const expectedContent = contentLine?.match(
|
|
289
|
+
new RegExp(`^${escapedPrefix} ${CONTENT_FINGERPRINT_LABEL}: ([a-f0-9]{64})$`),
|
|
290
|
+
)?.[1];
|
|
291
|
+
if (!expectedContent) return false;
|
|
292
|
+
return createHash("sha256").update(bodyLines.join("\n")).digest("hex") === expectedContent;
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function filesBelow(directory: string): string[] {
|
|
299
|
+
if (!existsSync(directory)) return [];
|
|
300
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
301
|
+
if (entry.name === "__pycache__" || entry.name.endsWith(".pyc")) return [];
|
|
302
|
+
const path = join(directory, entry.name);
|
|
303
|
+
return entry.isDirectory() ? filesBelow(path) : [path];
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function nearestPackageManifest(path: string): string | undefined {
|
|
308
|
+
let directory = dirname(path);
|
|
309
|
+
while (true) {
|
|
310
|
+
const manifest = join(directory, "package.json");
|
|
311
|
+
if (existsSync(manifest)) return manifest;
|
|
312
|
+
const parent = dirname(directory);
|
|
313
|
+
if (parent === directory) return undefined;
|
|
314
|
+
directory = parent;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
79
318
|
/** Resolve every configured binding in a repository. */
|
|
80
319
|
export function resolveAllPythonNodeBindings(
|
|
81
320
|
projectRoot: string = resolveRepoRoot(),
|
|
@@ -102,16 +341,28 @@ export function pythonNodeBindingProjects(projectRoot: string): string[] {
|
|
|
102
341
|
|
|
103
342
|
function existingBindingDirectories(projectRoot: string): string[] {
|
|
104
343
|
const directories = new Set<string>();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
344
|
+
const ignored = new Set([
|
|
345
|
+
".git",
|
|
346
|
+
".mypy_cache",
|
|
347
|
+
".pytest_cache",
|
|
348
|
+
".ruff_cache",
|
|
349
|
+
".venv",
|
|
350
|
+
"__pycache__",
|
|
351
|
+
"dist",
|
|
352
|
+
"node_modules",
|
|
353
|
+
]);
|
|
354
|
+
const visit = (directory: string): void => {
|
|
355
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
356
|
+
if (!entry.isDirectory() || ignored.has(entry.name)) continue;
|
|
357
|
+
const path = join(directory, entry.name);
|
|
358
|
+
if (entry.name === "node" && basename(directory) === "_generated") {
|
|
359
|
+
directories.add(relative(projectRoot, path));
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
visit(path);
|
|
113
363
|
}
|
|
114
|
-
}
|
|
364
|
+
};
|
|
365
|
+
visit(projectRoot);
|
|
115
366
|
return [...directories].sort();
|
|
116
367
|
}
|
|
117
368
|
|
|
@@ -192,16 +443,23 @@ type BindingSourceConfig = Omit<ResolvedPythonNodeBindings, "watchInputs">;
|
|
|
192
443
|
|
|
193
444
|
/** Transitive workspace source files reachable from configured binding modules. */
|
|
194
445
|
function bindingSourceInputs(root: string, config: BindingSourceConfig): string[] {
|
|
195
|
-
const allowed = config.workspaceDirectories.map(canonicalPath);
|
|
196
446
|
const packageEntrypoint = Bun.resolveSync(config.entrypoint, config.projectDirectory);
|
|
197
447
|
const entrypoints =
|
|
198
448
|
config.modules.length > 0
|
|
199
449
|
? config.modules.map((module) => resolvePythonNodeBindingModule(packageEntrypoint, module))
|
|
200
450
|
: [packageEntrypoint];
|
|
201
|
-
|
|
202
|
-
...entrypoints,
|
|
203
|
-
|
|
204
|
-
|
|
451
|
+
return transitiveSourceFiles(
|
|
452
|
+
[...entrypoints, ...config.functionOverrides.map(({ handlerFile }) => handlerFile)],
|
|
453
|
+
config.workspaceDirectories,
|
|
454
|
+
).map((path) => relative(root, path));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function transitiveSourceFiles(
|
|
458
|
+
entrypoints: readonly string[],
|
|
459
|
+
allowedDirectories: readonly string[],
|
|
460
|
+
): string[] {
|
|
461
|
+
const allowed = allowedDirectories.map(canonicalPath);
|
|
462
|
+
const pending = [...entrypoints];
|
|
205
463
|
const visited = new Set<string>();
|
|
206
464
|
while (pending.length > 0) {
|
|
207
465
|
const candidate = canonicalPath(pending.pop()!);
|
|
@@ -227,7 +485,7 @@ function bindingSourceInputs(root: string, config: BindingSourceConfig): string[
|
|
|
227
485
|
}
|
|
228
486
|
}
|
|
229
487
|
}
|
|
230
|
-
return [...visited].sort()
|
|
488
|
+
return [...visited].sort();
|
|
231
489
|
}
|
|
232
490
|
|
|
233
491
|
/** Resolve one configured namespace from a package entrypoint barrel. */
|
package/src/release.ts
CHANGED
|
@@ -51,8 +51,6 @@ export interface DBXToolsReleaseOptions {
|
|
|
51
51
|
readonly validationTasks?: readonly string[];
|
|
52
52
|
/** Repository prerequisites installed before release validation and artifact builds. */
|
|
53
53
|
readonly setupSteps?: readonly JobStep[];
|
|
54
|
-
/** Repository-specific synthesis commands run before the root Projen synthesis check. */
|
|
55
|
-
readonly synthesisCommands?: readonly string[];
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
/** Locate the unified workflow when release generation is enabled. */
|
|
@@ -204,14 +202,6 @@ function releaseBuildJob(
|
|
|
204
202
|
// ============================================================================
|
|
205
203
|
),
|
|
206
204
|
},
|
|
207
|
-
{
|
|
208
|
-
name: "Verify generated sources",
|
|
209
|
-
run: [
|
|
210
|
-
...(options.synthesisCommands ?? []),
|
|
211
|
-
"bunx projen",
|
|
212
|
-
"git diff --ignore-space-at-eol --exit-code",
|
|
213
|
-
].join("\n"),
|
|
214
|
-
},
|
|
215
205
|
{ name: "Verify workspace versions", run: "bun run version:check" },
|
|
216
206
|
...(options.setupSteps ?? []),
|
|
217
207
|
...releaseBuildSteps(
|
package/src/tags.ts
CHANGED
|
@@ -163,15 +163,11 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
163
163
|
addPackageFiles(p, "bin");
|
|
164
164
|
}),
|
|
165
165
|
server: create(projectPredicate.hasTag("server"), (p) => {
|
|
166
|
-
// A Node/Express service.
|
|
167
|
-
//
|
|
166
|
+
// A Node/Express service. The root's generic `dev:watch` task owns watched
|
|
167
|
+
// execution because this tag cannot assume an entrypoint or command shape.
|
|
168
168
|
p.addDeps("express@catalog:");
|
|
169
169
|
p.addDevDeps("@types/node@catalog:", "@types/express@catalog:");
|
|
170
170
|
applyCompilerOptions(p, NODE_COMPILER_OPTIONS);
|
|
171
|
-
// bun runs the server `.ts` directly (native TS, no tsx). `--watch` restarts
|
|
172
|
-
// on change - the tsx-watch replacement.
|
|
173
|
-
(p.tasks.tryFind("dev") ?? p.addTask("dev")).reset("bun --watch src/server.ts");
|
|
174
|
-
(p.tasks.tryFind("start") ?? p.addTask("start")).reset("bun src/server.ts");
|
|
175
171
|
}),
|
|
176
172
|
node: create(projectPredicate.hasTag("node"), (p) => {
|
|
177
173
|
p.addDevDeps("@types/node@catalog:");
|
package/src/tsconfig.ts
CHANGED
|
@@ -55,7 +55,7 @@ export class DBXToolsRootTsconfig extends Component {
|
|
|
55
55
|
extends: javascript.TypescriptConfigExtends.fromTypescriptConfigs([this.base]),
|
|
56
56
|
compilerOptions: {
|
|
57
57
|
lib: ["ESNext"],
|
|
58
|
-
types: ["node"],
|
|
58
|
+
types: ["node", "bun"],
|
|
59
59
|
},
|
|
60
60
|
include: [".projenrc.ts"],
|
|
61
61
|
exclude: ["node_modules", "**/dist", "**/node_modules"],
|