@webstir-io/webstir-backend 0.1.15 → 0.1.16
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 +106 -79
- package/dist/add.d.ts +59 -0
- package/dist/add.js +626 -0
- package/dist/build/artifacts.d.ts +115 -1
- package/dist/build/artifacts.js +4 -4
- package/dist/build/entries.js +1 -1
- package/dist/build/pipeline.d.ts +33 -1
- package/dist/build/pipeline.js +307 -65
- package/dist/cache/diff.js +9 -8
- package/dist/cache/reporters.js +1 -1
- package/dist/deploy-cli.d.ts +2 -0
- package/dist/deploy-cli.js +86 -0
- package/dist/diagnostics/summary.js +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +4 -0
- package/dist/manifest/pipeline.js +103 -32
- package/dist/provider.js +35 -17
- package/dist/runtime/bun.d.ts +51 -0
- package/dist/runtime/bun.js +499 -0
- package/dist/runtime/core.d.ts +141 -0
- package/dist/runtime/core.js +316 -0
- package/dist/runtime/deploy-backend.d.ts +20 -0
- package/dist/runtime/deploy-backend.js +175 -0
- package/dist/runtime/deploy-shared.d.ts +43 -0
- package/dist/runtime/deploy-shared.js +75 -0
- package/dist/runtime/deploy-static.d.ts +2 -0
- package/dist/runtime/deploy-static.js +161 -0
- package/dist/runtime/deploy.d.ts +3 -0
- package/dist/runtime/deploy.js +91 -0
- package/dist/runtime/forms.d.ts +73 -0
- package/dist/runtime/forms.js +236 -0
- package/dist/runtime/request-hooks.d.ts +47 -0
- package/dist/runtime/request-hooks.js +102 -0
- package/dist/runtime/session-metadata.d.ts +13 -0
- package/dist/runtime/session-metadata.js +98 -0
- package/dist/runtime/session-runtime.d.ts +28 -0
- package/dist/runtime/session-runtime.js +180 -0
- package/dist/runtime/session.d.ts +83 -0
- package/dist/runtime/session.js +396 -0
- package/dist/runtime/views.d.ts +74 -0
- package/dist/runtime/views.js +221 -0
- package/dist/scaffold/assets.js +25 -21
- package/dist/testing/context.js +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +100 -56
- package/dist/utils/bun.d.ts +2 -0
- package/dist/utils/bun.js +13 -0
- package/dist/watch.d.ts +13 -1
- package/dist/watch.js +345 -97
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.js +44 -3
- package/package.json +49 -14
- package/scripts/publish.sh +2 -92
- package/scripts/smoke.mjs +282 -107
- package/scripts/update-contract.sh +12 -10
- package/src/add.ts +964 -0
- package/src/build/artifacts.ts +49 -46
- package/src/build/entries.ts +12 -12
- package/src/build/pipeline.ts +779 -403
- package/src/cache/diff.ts +111 -105
- package/src/cache/reporters.ts +26 -26
- package/src/deploy-cli.ts +111 -0
- package/src/diagnostics/summary.ts +28 -22
- package/src/index.ts +11 -0
- package/src/manifest/pipeline.ts +328 -215
- package/src/provider.ts +115 -98
- package/src/runtime/bun.ts +793 -0
- package/src/runtime/core.ts +598 -0
- package/src/runtime/deploy-backend.ts +239 -0
- package/src/runtime/deploy-shared.ts +136 -0
- package/src/runtime/deploy-static.ts +191 -0
- package/src/runtime/deploy.ts +143 -0
- package/src/runtime/forms.ts +364 -0
- package/src/runtime/request-hooks.ts +165 -0
- package/src/runtime/session-metadata.ts +135 -0
- package/src/runtime/session-runtime.ts +267 -0
- package/src/runtime/session.ts +642 -0
- package/src/runtime/views.ts +385 -0
- package/src/scaffold/assets.ts +77 -73
- package/src/testing/context.js +8 -9
- package/src/testing/context.ts +9 -9
- package/src/testing/index.d.ts +14 -3
- package/src/testing/index.js +254 -175
- package/src/testing/index.ts +298 -195
- package/src/testing/types.d.ts +18 -19
- package/src/testing/types.ts +18 -18
- package/src/utils/bun.ts +26 -0
- package/src/watch.ts +503 -99
- package/src/workspace.ts +59 -3
- package/templates/backend/.env.example +15 -0
- package/templates/backend/auth/adapter.ts +335 -36
- package/templates/backend/db/connection.ts +190 -65
- package/templates/backend/db/migrate.ts +149 -43
- package/templates/backend/db/types.d.ts +1 -1
- package/templates/backend/env.ts +132 -20
- package/templates/backend/functions/hello/index.ts +1 -2
- package/templates/backend/index.ts +15 -508
- package/templates/backend/jobs/nightly/index.ts +1 -1
- package/templates/backend/jobs/runtime.ts +24 -11
- package/templates/backend/jobs/scheduler.ts +208 -46
- package/templates/backend/module.ts +227 -13
- package/templates/backend/observability/logger.ts +2 -12
- package/templates/backend/observability/metrics.ts +8 -5
- package/templates/backend/session/sqlite.ts +152 -0
- package/templates/backend/session/store.ts +45 -0
- package/templates/backend/tsconfig.json +1 -1
- package/tests/add.test.js +327 -0
- package/tests/authAdapter.test.js +315 -0
- package/tests/bundlerParity.test.js +217 -0
- package/tests/cacheReporter.test.js +10 -10
- package/tests/dbConnection.test.js +209 -0
- package/tests/deploy.test.js +357 -0
- package/tests/envLoader.test.js +271 -17
- package/tests/integration.test.js +2432 -3
- package/tests/jobsScheduler.test.js +253 -0
- package/tests/manifest.test.js +287 -12
- package/tests/migrationRunner.test.js +249 -0
- package/tests/sessionScaffoldStore.test.js +752 -0
- package/tests/sessionStore.test.js +490 -0
- package/tests/testing.test.js +252 -0
- package/tests/watch.test.js +192 -32
- package/tsconfig.json +3 -10
- package/templates/backend/server/fastify.ts +0 -288
package/src/build/pipeline.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
3
5
|
import { spawn } from 'node:child_process';
|
|
4
6
|
import { performance } from 'node:perf_hooks';
|
|
5
7
|
|
|
6
8
|
import { build as esbuild, context as esbuildContext } from 'esbuild';
|
|
7
9
|
import { glob } from 'glob';
|
|
8
|
-
import type { BuildContext as EsbuildContext } from 'esbuild';
|
|
10
|
+
import type { BuildContext as EsbuildContext, BuildFailure } from 'esbuild';
|
|
9
11
|
|
|
10
12
|
import type { ModuleDiagnostic } from '@webstir-io/module-contract';
|
|
11
13
|
|
|
@@ -13,495 +15,869 @@ import type { BackendBuildMode } from '../workspace.js';
|
|
|
13
15
|
import { discoverEntryPoints } from './entries.js';
|
|
14
16
|
|
|
15
17
|
export interface BackendBuildPipelineOptions {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
readonly sourceRoot: string;
|
|
19
|
+
readonly buildRoot: string;
|
|
20
|
+
readonly tsconfigPath: string;
|
|
21
|
+
readonly mode: BackendBuildMode;
|
|
22
|
+
readonly env: Record<string, string | undefined>;
|
|
23
|
+
readonly incremental: boolean;
|
|
24
|
+
readonly diagnostics: ModuleDiagnostic[];
|
|
25
|
+
readonly bundler?: BackendBundler;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export interface BackendBuildPipelineResult {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
readonly entryPoints: readonly string[];
|
|
30
|
+
readonly outputs?: Record<string, number>;
|
|
31
|
+
readonly includePublishSourcemaps: boolean;
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export type BackendBundler = 'esbuild' | 'bun';
|
|
35
|
+
|
|
31
36
|
interface IncrementalBuildEntry {
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
entrySignature: string;
|
|
38
|
+
context: EsbuildContext;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
const incrementalBuildCache = new Map<string, IncrementalBuildEntry>();
|
|
37
42
|
|
|
38
43
|
if (typeof process !== 'undefined' && typeof process.once === 'function') {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
process.once('exit', () => {
|
|
45
|
+
clearIncrementalCache();
|
|
46
|
+
});
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
export async function runBackendBuildPipeline(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
export async function runBackendBuildPipeline(
|
|
50
|
+
options: BackendBuildPipelineOptions,
|
|
51
|
+
): Promise<BackendBuildPipelineResult> {
|
|
52
|
+
const { sourceRoot, buildRoot, tsconfigPath, diagnostics, incremental, mode } = options;
|
|
53
|
+
const env = options.env ?? {};
|
|
54
|
+
const bundler =
|
|
55
|
+
options.bundler ??
|
|
56
|
+
resolveBackendBundler({
|
|
57
|
+
env,
|
|
58
|
+
incremental,
|
|
59
|
+
diagnostics,
|
|
60
|
+
});
|
|
61
|
+
console.info(`[webstir-backend] ${mode}:tsc start`);
|
|
62
|
+
if (shouldTypeCheck(mode, env)) {
|
|
63
|
+
await runTypeCheck(tsconfigPath, env, diagnostics);
|
|
64
|
+
} else {
|
|
65
|
+
diagnostics.push({
|
|
66
|
+
severity: 'info',
|
|
67
|
+
message: '[webstir-backend] type-check skipped by WEBSTIR_BACKEND_TYPECHECK',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
console.info(`[webstir-backend] ${mode}:tsc done`);
|
|
71
|
+
|
|
72
|
+
const entryPoints = await discoverEntryPoints(sourceRoot);
|
|
73
|
+
if (entryPoints.length === 0) {
|
|
74
|
+
diagnostics.push({
|
|
75
|
+
severity: 'warn',
|
|
76
|
+
message: `No backend entry points found under ${sourceRoot} (expected index.* or functions/*/index.* or jobs/*/index.*).`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (bundler === 'bun') {
|
|
81
|
+
await resetBuildRoot(buildRoot);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.info(`[webstir-backend] ${mode}:${bundler} start`);
|
|
85
|
+
const outputs =
|
|
86
|
+
bundler === 'bun'
|
|
87
|
+
? await runBunBuild({
|
|
88
|
+
sourceRoot,
|
|
89
|
+
buildRoot,
|
|
90
|
+
tsconfigPath,
|
|
91
|
+
mode,
|
|
92
|
+
env,
|
|
93
|
+
incremental,
|
|
94
|
+
diagnostics,
|
|
95
|
+
entryPoints,
|
|
96
|
+
})
|
|
97
|
+
: await runEsbuild({
|
|
98
|
+
sourceRoot,
|
|
99
|
+
buildRoot,
|
|
100
|
+
tsconfigPath,
|
|
101
|
+
mode,
|
|
102
|
+
env,
|
|
103
|
+
incremental,
|
|
104
|
+
diagnostics,
|
|
105
|
+
entryPoints,
|
|
60
106
|
});
|
|
61
|
-
|
|
107
|
+
console.info(`[webstir-backend] ${mode}:${bundler} done`);
|
|
108
|
+
|
|
109
|
+
await ensureModuleDefinitionBuild({
|
|
110
|
+
sourceRoot,
|
|
111
|
+
buildRoot,
|
|
112
|
+
tsconfigPath,
|
|
113
|
+
mode,
|
|
114
|
+
env,
|
|
115
|
+
diagnostics,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const includePublishSourcemaps = mode === 'publish' && shouldEmitPublishSourcemaps(env);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
entryPoints,
|
|
122
|
+
outputs,
|
|
123
|
+
includePublishSourcemaps,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
62
126
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
127
|
+
async function runTypeCheck(
|
|
128
|
+
tsconfigPath: string,
|
|
129
|
+
env: Record<string, string | undefined>,
|
|
130
|
+
diagnostics: ModuleDiagnostic[],
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
if (!existsSync(tsconfigPath)) {
|
|
133
|
+
diagnostics.push({
|
|
134
|
+
severity: 'warn',
|
|
135
|
+
message: `TypeScript config not found at ${tsconfigPath}; skipping type-check.`,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await new Promise<void>((resolve, reject) => {
|
|
141
|
+
const child = spawn('tsc', ['-p', tsconfigPath, '--noEmit'], {
|
|
142
|
+
stdio: 'pipe',
|
|
143
|
+
env: {
|
|
144
|
+
...process.env,
|
|
145
|
+
...env,
|
|
146
|
+
},
|
|
73
147
|
});
|
|
74
|
-
console.info(`[webstir-backend] ${mode}:esbuild done`);
|
|
75
|
-
|
|
76
|
-
const moduleSource = await discoverModuleDefinitionSource(sourceRoot);
|
|
77
|
-
if (moduleSource) {
|
|
78
|
-
await buildModuleDefinition({
|
|
79
|
-
sourceFile: moduleSource,
|
|
80
|
-
sourceRoot,
|
|
81
|
-
buildRoot,
|
|
82
|
-
tsconfigPath,
|
|
83
|
-
mode,
|
|
84
|
-
env,
|
|
85
|
-
diagnostics
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
148
|
|
|
89
|
-
|
|
149
|
+
let stdout = '';
|
|
150
|
+
let stderr = '';
|
|
90
151
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
includePublishSourcemaps
|
|
95
|
-
};
|
|
96
|
-
}
|
|
152
|
+
child.stdout?.on('data', (chunk) => {
|
|
153
|
+
stdout += chunk.toString();
|
|
154
|
+
});
|
|
97
155
|
|
|
98
|
-
|
|
99
|
-
|
|
156
|
+
child.stderr?.on('data', (chunk) => {
|
|
157
|
+
stderr += chunk.toString();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
child.on('error', (err: NodeJS.ErrnoException) => {
|
|
161
|
+
const code = err.code;
|
|
162
|
+
if (code === 'ENOENT') {
|
|
100
163
|
diagnostics.push({
|
|
101
|
-
|
|
102
|
-
|
|
164
|
+
severity: 'warn',
|
|
165
|
+
message: 'TypeScript compiler (tsc) not found in PATH; skipping type-check.',
|
|
103
166
|
});
|
|
167
|
+
resolve();
|
|
104
168
|
return;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
}
|
|
170
|
+
reject(err);
|
|
171
|
+
});
|
|
172
|
+
child.on('close', (code) => {
|
|
173
|
+
if (code === 0) {
|
|
174
|
+
resolve();
|
|
175
|
+
} else {
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
severity: 'error',
|
|
178
|
+
message: `Type checking failed (exit code ${code}).`,
|
|
179
|
+
file: tsconfigPath,
|
|
114
180
|
});
|
|
181
|
+
if (stderr.trim()) {
|
|
182
|
+
diagnostics.push({ severity: 'error', message: stderr.trim() });
|
|
183
|
+
}
|
|
184
|
+
if (stdout.trim()) {
|
|
185
|
+
diagnostics.push({ severity: 'info', message: stdout.trim() });
|
|
186
|
+
}
|
|
187
|
+
reject(new Error('Type checking failed.'));
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
115
192
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
193
|
+
export function shouldTypeCheck(
|
|
194
|
+
mode: BackendBuildMode,
|
|
195
|
+
env: Record<string, string | undefined>,
|
|
196
|
+
): boolean {
|
|
197
|
+
const flag = env?.WEBSTIR_BACKEND_TYPECHECK;
|
|
198
|
+
if (typeof flag === 'string' && flag.toLowerCase() === 'skip') {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (mode === 'publish') {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
122
206
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
207
|
+
interface ResolveBackendBundlerOptions {
|
|
208
|
+
readonly env: Record<string, string | undefined>;
|
|
209
|
+
readonly incremental: boolean;
|
|
210
|
+
readonly diagnostics?: ModuleDiagnostic[];
|
|
211
|
+
}
|
|
126
212
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
if (stdout.trim()) {
|
|
149
|
-
diagnostics.push({ severity: 'info', message: stdout.trim() });
|
|
150
|
-
}
|
|
151
|
-
reject(new Error('Type checking failed.'));
|
|
152
|
-
}
|
|
153
|
-
});
|
|
213
|
+
export function resolveBackendBundler(options: ResolveBackendBundlerOptions): BackendBundler {
|
|
214
|
+
const requestedBundler = normalizeBackendBundler(options.env?.WEBSTIR_BACKEND_BUNDLER);
|
|
215
|
+
if (requestedBundler !== 'bun') {
|
|
216
|
+
return 'esbuild';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (options.incremental) {
|
|
220
|
+
options.diagnostics?.push({
|
|
221
|
+
severity: 'info',
|
|
222
|
+
message:
|
|
223
|
+
'[webstir-backend] WEBSTIR_BACKEND_BUNDLER=bun requested for an incremental build; falling back to esbuild.',
|
|
224
|
+
});
|
|
225
|
+
return 'esbuild';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!getBunBuild()) {
|
|
229
|
+
options.diagnostics?.push({
|
|
230
|
+
severity: 'warn',
|
|
231
|
+
message:
|
|
232
|
+
'[webstir-backend] WEBSTIR_BACKEND_BUNDLER=bun requested outside a Bun runtime; falling back to esbuild.',
|
|
154
233
|
});
|
|
234
|
+
return 'esbuild';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return 'bun';
|
|
155
238
|
}
|
|
156
239
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
if (mode === 'publish') {
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
return true;
|
|
240
|
+
function normalizeBackendBundler(rawBundler: unknown): BackendBundler {
|
|
241
|
+
return typeof rawBundler === 'string' && rawBundler.trim().toLowerCase() === 'bun'
|
|
242
|
+
? 'bun'
|
|
243
|
+
: 'esbuild';
|
|
166
244
|
}
|
|
167
245
|
|
|
168
246
|
function shouldEmitPublishSourcemaps(env: Record<string, string | undefined>): boolean {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
247
|
+
const flag = env?.WEBSTIR_BACKEND_SOURCEMAPS;
|
|
248
|
+
if (typeof flag !== 'string') {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const normalized = flag.trim().toLowerCase();
|
|
252
|
+
return normalized === 'on' || normalized === 'true' || normalized === '1' || normalized === 'yes';
|
|
175
253
|
}
|
|
176
254
|
|
|
177
255
|
async function discoverModuleDefinitionSource(sourceRoot: string): Promise<string | undefined> {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
256
|
+
const patterns = ['module.{ts,tsx,js,mjs}', 'module/index.{ts,tsx,js,mjs}'];
|
|
257
|
+
|
|
258
|
+
for (const pattern of patterns) {
|
|
259
|
+
const matches = await glob(pattern, {
|
|
260
|
+
cwd: sourceRoot,
|
|
261
|
+
absolute: true,
|
|
262
|
+
nodir: true,
|
|
263
|
+
dot: false,
|
|
264
|
+
});
|
|
187
265
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
266
|
+
if (matches.length > 0) {
|
|
267
|
+
return matches[0];
|
|
191
268
|
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const indexPatterns = ['index.{ts,tsx,js,mjs}'];
|
|
272
|
+
for (const pattern of indexPatterns) {
|
|
273
|
+
const matches = await glob(pattern, {
|
|
274
|
+
cwd: sourceRoot,
|
|
275
|
+
absolute: true,
|
|
276
|
+
nodir: true,
|
|
277
|
+
dot: false,
|
|
278
|
+
});
|
|
192
279
|
|
|
193
|
-
|
|
280
|
+
for (const candidate of matches) {
|
|
281
|
+
if (await sourceExportsNamedModuleDefinition(candidate)) {
|
|
282
|
+
return candidate;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return undefined;
|
|
194
288
|
}
|
|
195
289
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
290
|
+
async function sourceExportsNamedModuleDefinition(sourceFile: string): Promise<boolean> {
|
|
291
|
+
try {
|
|
292
|
+
const source = await readFile(sourceFile, 'utf8');
|
|
293
|
+
return (
|
|
294
|
+
/\bexport\s+(const|let|var)\s+module\b/.test(source) ||
|
|
295
|
+
/\bexport\s*\{\s*module\b/.test(source)
|
|
296
|
+
);
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
204
300
|
}
|
|
205
301
|
|
|
206
|
-
|
|
207
|
-
|
|
302
|
+
export interface EnsureModuleDefinitionBuildOptions {
|
|
303
|
+
readonly sourceRoot: string;
|
|
304
|
+
readonly buildRoot: string;
|
|
305
|
+
readonly tsconfigPath: string;
|
|
306
|
+
readonly mode: BackendBuildMode;
|
|
307
|
+
readonly env: Record<string, string | undefined>;
|
|
308
|
+
readonly diagnostics: ModuleDiagnostic[];
|
|
309
|
+
}
|
|
208
310
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
311
|
+
export async function ensureModuleDefinitionBuild(
|
|
312
|
+
options: EnsureModuleDefinitionBuildOptions,
|
|
313
|
+
): Promise<void> {
|
|
314
|
+
const moduleSource = await discoverModuleDefinitionSource(options.sourceRoot);
|
|
315
|
+
if (!moduleSource) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await buildModuleDefinition({
|
|
320
|
+
sourceFile: moduleSource,
|
|
321
|
+
sourceRoot: options.sourceRoot,
|
|
322
|
+
buildRoot: options.buildRoot,
|
|
323
|
+
tsconfigPath: options.tsconfigPath,
|
|
324
|
+
mode: options.mode,
|
|
325
|
+
env: options.env,
|
|
326
|
+
diagnostics: options.diagnostics,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
215
329
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
330
|
+
interface ModuleDefinitionBuildOptions {
|
|
331
|
+
readonly sourceFile: string;
|
|
332
|
+
readonly sourceRoot: string;
|
|
333
|
+
readonly buildRoot: string;
|
|
334
|
+
readonly tsconfigPath: string;
|
|
335
|
+
readonly mode: BackendBuildMode;
|
|
336
|
+
readonly env: Record<string, string | undefined>;
|
|
337
|
+
readonly diagnostics: ModuleDiagnostic[];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function buildModuleDefinition(options: ModuleDefinitionBuildOptions): Promise<void> {
|
|
341
|
+
const { sourceFile, buildRoot, tsconfigPath, mode, env, diagnostics } = options;
|
|
342
|
+
|
|
343
|
+
const isProduction = mode === 'publish';
|
|
344
|
+
const nodeEnv = env?.NODE_ENV ?? (isProduction ? 'production' : 'development');
|
|
345
|
+
const emitPublishSourcemaps = isProduction && shouldEmitPublishSourcemaps(env);
|
|
346
|
+
const define: Record<string, string> = {
|
|
347
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
await esbuild({
|
|
352
|
+
entryPoints: [sourceFile],
|
|
353
|
+
bundle: true,
|
|
354
|
+
packages: 'external',
|
|
355
|
+
platform: 'node',
|
|
356
|
+
target: 'node20',
|
|
357
|
+
format: 'esm',
|
|
358
|
+
sourcemap: isProduction ? emitPublishSourcemaps : true,
|
|
359
|
+
outfile: path.join(buildRoot, 'module.js'),
|
|
360
|
+
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
361
|
+
define,
|
|
362
|
+
logLevel: 'silent',
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (isEsbuildFailure(error)) {
|
|
366
|
+
for (const e of error.errors ?? []) {
|
|
367
|
+
diagnostics.push({ severity: 'error', message: formatEsbuildMessage(e) });
|
|
368
|
+
}
|
|
369
|
+
for (const w of error.warnings ?? []) {
|
|
370
|
+
diagnostics.push({ severity: 'warn', message: formatEsbuildMessage(w) });
|
|
371
|
+
}
|
|
372
|
+
} else if (error instanceof Error) {
|
|
373
|
+
diagnostics.push({ severity: 'error', message: error.message });
|
|
374
|
+
} else {
|
|
375
|
+
diagnostics.push({ severity: 'error', message: String(error) });
|
|
244
376
|
}
|
|
377
|
+
}
|
|
245
378
|
}
|
|
246
379
|
|
|
247
380
|
interface SupportFileBuildOptions {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
381
|
+
readonly sourceFile: string;
|
|
382
|
+
readonly sourceRoot: string;
|
|
383
|
+
readonly buildRoot: string;
|
|
384
|
+
readonly tsconfigPath: string;
|
|
385
|
+
readonly mode: BackendBuildMode;
|
|
386
|
+
readonly env: Record<string, string | undefined>;
|
|
387
|
+
readonly diagnostics: ModuleDiagnostic[];
|
|
388
|
+
readonly bundler?: BackendBundler;
|
|
255
389
|
}
|
|
256
390
|
|
|
257
391
|
export async function buildSupportFile(options: SupportFileBuildOptions): Promise<void> {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
392
|
+
const { sourceFile, sourceRoot, buildRoot, tsconfigPath, mode, env, diagnostics } = options;
|
|
393
|
+
const isProduction = mode === 'publish';
|
|
394
|
+
const nodeEnv = env?.NODE_ENV ?? (isProduction ? 'production' : 'development');
|
|
395
|
+
const emitPublishSourcemaps = isProduction && shouldEmitPublishSourcemaps(env);
|
|
396
|
+
const define: Record<string, string> = {
|
|
397
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
398
|
+
};
|
|
399
|
+
const bundler =
|
|
400
|
+
options.bundler ??
|
|
401
|
+
resolveBackendBundler({
|
|
402
|
+
env,
|
|
403
|
+
incremental: false,
|
|
404
|
+
diagnostics,
|
|
405
|
+
});
|
|
406
|
+
const diagMax = readDiagMax(env, 50);
|
|
265
407
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
408
|
+
try {
|
|
409
|
+
if (bundler === 'bun') {
|
|
410
|
+
const result = await runBunCompile({
|
|
411
|
+
entryPoints: [sourceFile],
|
|
412
|
+
sourceRoot,
|
|
413
|
+
buildRoot,
|
|
414
|
+
tsconfigPath,
|
|
415
|
+
define,
|
|
416
|
+
minify: false,
|
|
417
|
+
includeSourceMaps: isProduction ? emitPublishSourcemaps : true,
|
|
418
|
+
});
|
|
419
|
+
ensureBunCompileSucceeded(result, diagnostics, `${mode}:bun:support`, diagMax);
|
|
420
|
+
} else {
|
|
421
|
+
await esbuild({
|
|
422
|
+
entryPoints: [sourceFile],
|
|
423
|
+
bundle: false,
|
|
424
|
+
platform: 'node',
|
|
425
|
+
target: 'node20',
|
|
426
|
+
format: 'esm',
|
|
427
|
+
sourcemap: isProduction ? emitPublishSourcemaps : true,
|
|
428
|
+
outdir: buildRoot,
|
|
429
|
+
outbase: sourceRoot,
|
|
430
|
+
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
431
|
+
define,
|
|
432
|
+
logLevel: 'silent',
|
|
433
|
+
});
|
|
287
434
|
}
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (error instanceof Error) {
|
|
437
|
+
diagnostics.push({ severity: 'error', message: error.message });
|
|
438
|
+
} else {
|
|
439
|
+
diagnostics.push({ severity: 'error', message: String(error) });
|
|
440
|
+
}
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
288
443
|
}
|
|
289
444
|
|
|
290
445
|
interface BuildOptions {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
446
|
+
readonly sourceRoot: string;
|
|
447
|
+
readonly buildRoot: string;
|
|
448
|
+
readonly tsconfigPath: string;
|
|
449
|
+
readonly mode: BackendBuildMode;
|
|
450
|
+
readonly env: Record<string, string | undefined>;
|
|
451
|
+
readonly incremental: boolean;
|
|
452
|
+
readonly diagnostics: ModuleDiagnostic[];
|
|
453
|
+
readonly entryPoints: readonly string[];
|
|
299
454
|
}
|
|
300
455
|
|
|
301
456
|
async function runEsbuild(options: BuildOptions): Promise<Record<string, number> | undefined> {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
457
|
+
const { sourceRoot, buildRoot, tsconfigPath, mode, env, diagnostics, entryPoints } = options;
|
|
458
|
+
const isProduction = mode === 'publish';
|
|
459
|
+
const useIncremental = !isProduction && options.incremental === true;
|
|
460
|
+
const incrementalKey = useIncremental ? createIncrementalKey(mode, buildRoot) : undefined;
|
|
461
|
+
|
|
462
|
+
if (!entryPoints || entryPoints.length === 0) {
|
|
463
|
+
if (incrementalKey) {
|
|
464
|
+
await disposeIncrementalBuild(incrementalKey);
|
|
465
|
+
}
|
|
466
|
+
return undefined;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const entrySignature = useIncremental ? createEntrySignature(entryPoints) : undefined;
|
|
470
|
+
const nodeEnv = env?.NODE_ENV ?? (isProduction ? 'production' : 'development');
|
|
471
|
+
const diagMax = readDiagMax(env, 50);
|
|
472
|
+
|
|
473
|
+
const define: Record<string, string> = {
|
|
474
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const emitPublishSourcemaps = isProduction && shouldEmitPublishSourcemaps(env);
|
|
478
|
+
const start = performance.now();
|
|
479
|
+
try {
|
|
480
|
+
let reusedIncremental = false;
|
|
481
|
+
let result: Awaited<ReturnType<typeof esbuild>>;
|
|
482
|
+
|
|
483
|
+
if (isProduction) {
|
|
484
|
+
if (incrementalKey) {
|
|
485
|
+
await disposeIncrementalBuild(incrementalKey);
|
|
486
|
+
}
|
|
487
|
+
result = await esbuild({
|
|
488
|
+
entryPoints: entryPoints as string[],
|
|
489
|
+
bundle: true,
|
|
490
|
+
packages: 'external',
|
|
491
|
+
platform: 'node',
|
|
492
|
+
target: 'node20',
|
|
493
|
+
format: 'esm',
|
|
494
|
+
minify: true,
|
|
495
|
+
sourcemap: emitPublishSourcemaps,
|
|
496
|
+
legalComments: 'none',
|
|
497
|
+
outdir: buildRoot,
|
|
498
|
+
outbase: sourceRoot,
|
|
499
|
+
entryNames: '[dir]/[name]',
|
|
500
|
+
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
501
|
+
define,
|
|
502
|
+
logLevel: 'silent',
|
|
503
|
+
metafile: true,
|
|
504
|
+
});
|
|
505
|
+
} else if (useIncremental && incrementalKey && entrySignature) {
|
|
506
|
+
const cached = incrementalBuildCache.get(incrementalKey);
|
|
507
|
+
if (cached && cached.entrySignature === entrySignature) {
|
|
508
|
+
reusedIncremental = true;
|
|
509
|
+
result = await cached.context.rebuild();
|
|
510
|
+
} else {
|
|
511
|
+
if (cached) {
|
|
512
|
+
await disposeIncrementalBuild(incrementalKey);
|
|
310
513
|
}
|
|
311
|
-
|
|
514
|
+
const ctx = await esbuildContext({
|
|
515
|
+
entryPoints: entryPoints as string[],
|
|
516
|
+
bundle: false,
|
|
517
|
+
platform: 'node',
|
|
518
|
+
target: 'node20',
|
|
519
|
+
format: 'esm',
|
|
520
|
+
sourcemap: true,
|
|
521
|
+
outdir: buildRoot,
|
|
522
|
+
outbase: sourceRoot,
|
|
523
|
+
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
524
|
+
define,
|
|
525
|
+
logLevel: 'silent',
|
|
526
|
+
metafile: true,
|
|
527
|
+
});
|
|
528
|
+
incrementalBuildCache.set(incrementalKey, {
|
|
529
|
+
entrySignature,
|
|
530
|
+
context: ctx,
|
|
531
|
+
});
|
|
532
|
+
result = await ctx.rebuild();
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
if (incrementalKey) {
|
|
536
|
+
await disposeIncrementalBuild(incrementalKey);
|
|
537
|
+
}
|
|
538
|
+
result = await esbuild({
|
|
539
|
+
entryPoints: entryPoints as string[],
|
|
540
|
+
bundle: false,
|
|
541
|
+
platform: 'node',
|
|
542
|
+
target: 'node20',
|
|
543
|
+
format: 'esm',
|
|
544
|
+
sourcemap: true,
|
|
545
|
+
outdir: buildRoot,
|
|
546
|
+
outbase: sourceRoot,
|
|
547
|
+
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
548
|
+
define,
|
|
549
|
+
logLevel: 'silent',
|
|
550
|
+
metafile: true,
|
|
551
|
+
});
|
|
312
552
|
}
|
|
313
553
|
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
let result: Awaited<ReturnType<typeof esbuild>>;
|
|
331
|
-
|
|
332
|
-
if (isProduction) {
|
|
333
|
-
if (incrementalKey) {
|
|
334
|
-
await disposeIncrementalBuild(incrementalKey);
|
|
335
|
-
}
|
|
336
|
-
result = await esbuild({
|
|
337
|
-
entryPoints: entryPoints as string[],
|
|
338
|
-
bundle: true,
|
|
339
|
-
packages: 'external',
|
|
340
|
-
platform: 'node',
|
|
341
|
-
target: 'node20',
|
|
342
|
-
format: 'esm',
|
|
343
|
-
minify: true,
|
|
344
|
-
sourcemap: emitPublishSourcemaps,
|
|
345
|
-
legalComments: 'none',
|
|
346
|
-
outdir: buildRoot,
|
|
347
|
-
outbase: sourceRoot,
|
|
348
|
-
entryNames: '[dir]/[name]',
|
|
349
|
-
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
350
|
-
define,
|
|
351
|
-
logLevel: 'silent',
|
|
352
|
-
metafile: true
|
|
353
|
-
});
|
|
354
|
-
} else if (useIncremental && incrementalKey && entrySignature) {
|
|
355
|
-
const cached = incrementalBuildCache.get(incrementalKey);
|
|
356
|
-
if (cached && cached.entrySignature === entrySignature) {
|
|
357
|
-
reusedIncremental = true;
|
|
358
|
-
result = await cached.context.rebuild();
|
|
359
|
-
} else {
|
|
360
|
-
if (cached) {
|
|
361
|
-
await disposeIncrementalBuild(incrementalKey);
|
|
362
|
-
}
|
|
363
|
-
const ctx = await esbuildContext({
|
|
364
|
-
entryPoints: entryPoints as string[],
|
|
365
|
-
bundle: false,
|
|
366
|
-
platform: 'node',
|
|
367
|
-
target: 'node20',
|
|
368
|
-
format: 'esm',
|
|
369
|
-
sourcemap: true,
|
|
370
|
-
outdir: buildRoot,
|
|
371
|
-
outbase: sourceRoot,
|
|
372
|
-
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
373
|
-
define,
|
|
374
|
-
logLevel: 'silent',
|
|
375
|
-
metafile: true
|
|
376
|
-
});
|
|
377
|
-
incrementalBuildCache.set(incrementalKey, {
|
|
378
|
-
entrySignature,
|
|
379
|
-
context: ctx
|
|
380
|
-
});
|
|
381
|
-
result = await ctx.rebuild();
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
if (incrementalKey) {
|
|
385
|
-
await disposeIncrementalBuild(incrementalKey);
|
|
386
|
-
}
|
|
387
|
-
result = await esbuild({
|
|
388
|
-
entryPoints: entryPoints as string[],
|
|
389
|
-
bundle: false,
|
|
390
|
-
platform: 'node',
|
|
391
|
-
target: 'node20',
|
|
392
|
-
format: 'esm',
|
|
393
|
-
sourcemap: true,
|
|
394
|
-
outdir: buildRoot,
|
|
395
|
-
outbase: sourceRoot,
|
|
396
|
-
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
397
|
-
define,
|
|
398
|
-
logLevel: 'silent',
|
|
399
|
-
metafile: true
|
|
400
|
-
});
|
|
401
|
-
}
|
|
554
|
+
const warnCount = result.warnings?.length ?? 0;
|
|
555
|
+
for (const w of (result.warnings ?? []).slice(0, diagMax)) {
|
|
556
|
+
diagnostics.push({ severity: 'warn', message: formatEsbuildMessage(w) });
|
|
557
|
+
}
|
|
558
|
+
if (warnCount > diagMax) {
|
|
559
|
+
diagnostics.push({
|
|
560
|
+
severity: 'info',
|
|
561
|
+
message: `[webstir-backend] ${isProduction ? 'publish:esbuild' : `${mode}:esbuild`} ... ${warnCount - diagMax} more warning(s) omitted`,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
const end = performance.now();
|
|
565
|
+
const reuseSuffix = reusedIncremental ? ' (incremental)' : '';
|
|
566
|
+
diagnostics.push({
|
|
567
|
+
severity: 'info',
|
|
568
|
+
message: `[webstir-backend] ${isProduction ? 'publish:esbuild' : `${mode}:esbuild`} 0 error(s), ${warnCount} warning(s) in ${(end - start).toFixed(1)}ms${reuseSuffix}`,
|
|
569
|
+
});
|
|
402
570
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
571
|
+
return collectOutputSizes(result.metafile, buildRoot);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
const end = performance.now();
|
|
574
|
+
if (incrementalKey) {
|
|
575
|
+
await disposeIncrementalBuild(incrementalKey);
|
|
576
|
+
}
|
|
577
|
+
if (isEsbuildFailure(error)) {
|
|
578
|
+
const errs = error.errors ?? [];
|
|
579
|
+
const warns = error.warnings ?? [];
|
|
580
|
+
for (const e of errs.slice(0, diagMax)) {
|
|
581
|
+
diagnostics.push({ severity: 'error', message: formatEsbuildMessage(e) });
|
|
582
|
+
}
|
|
583
|
+
for (const w of warns.slice(0, diagMax)) {
|
|
584
|
+
diagnostics.push({ severity: 'warn', message: formatEsbuildMessage(w) });
|
|
585
|
+
}
|
|
586
|
+
if (errs.length > diagMax) {
|
|
415
587
|
diagnostics.push({
|
|
416
|
-
|
|
417
|
-
|
|
588
|
+
severity: 'info',
|
|
589
|
+
message: `[webstir-backend] ${mode}:esbuild ... ${errs.length - diagMax} more error(s) omitted`,
|
|
418
590
|
});
|
|
591
|
+
}
|
|
592
|
+
if (warns.length > diagMax) {
|
|
593
|
+
diagnostics.push({
|
|
594
|
+
severity: 'info',
|
|
595
|
+
message: `[webstir-backend] ${mode}:esbuild ... ${warns.length - diagMax} more warning(s) omitted`,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
diagnostics.push({
|
|
599
|
+
severity: 'info',
|
|
600
|
+
message: `[webstir-backend] ${mode}:esbuild ${errs.length} error(s), ${warns.length} warning(s) in ${(end - start).toFixed(1)}ms`,
|
|
601
|
+
});
|
|
602
|
+
} else if (error instanceof Error) {
|
|
603
|
+
diagnostics.push({ severity: 'error', message: error.message });
|
|
604
|
+
} else {
|
|
605
|
+
diagnostics.push({ severity: 'error', message: String(error) });
|
|
606
|
+
}
|
|
607
|
+
throw new Error('esbuild failed.');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
419
610
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
611
|
+
interface BunBuildOutputFile {
|
|
612
|
+
readonly path: string;
|
|
613
|
+
readonly kind?: string;
|
|
614
|
+
readonly size?: number;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
interface BunBuildLog {
|
|
618
|
+
readonly level?: string;
|
|
619
|
+
readonly message?: string;
|
|
620
|
+
readonly text?: string;
|
|
621
|
+
readonly position?: {
|
|
622
|
+
readonly file?: string;
|
|
623
|
+
readonly line?: number;
|
|
624
|
+
readonly column?: number;
|
|
625
|
+
} | null;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
interface BuildMessageLike {
|
|
629
|
+
readonly message?: string;
|
|
630
|
+
readonly text?: string;
|
|
631
|
+
readonly location?: {
|
|
632
|
+
readonly file?: string;
|
|
633
|
+
readonly line?: number;
|
|
634
|
+
readonly column?: number;
|
|
635
|
+
} | null;
|
|
636
|
+
readonly position?: {
|
|
637
|
+
readonly file?: string;
|
|
638
|
+
readonly line?: number;
|
|
639
|
+
readonly column?: number;
|
|
640
|
+
} | null;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
interface BunBuildOutput {
|
|
644
|
+
readonly success: boolean;
|
|
645
|
+
readonly outputs?: readonly BunBuildOutputFile[];
|
|
646
|
+
readonly logs?: readonly BunBuildLog[];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
type BunBuildFunction = (config: Record<string, unknown>) => Promise<BunBuildOutput>;
|
|
650
|
+
|
|
651
|
+
interface BunCompileOptions {
|
|
652
|
+
readonly entryPoints: readonly string[];
|
|
653
|
+
readonly sourceRoot: string;
|
|
654
|
+
readonly buildRoot: string;
|
|
655
|
+
readonly tsconfigPath: string;
|
|
656
|
+
readonly define: Record<string, string>;
|
|
657
|
+
readonly minify: boolean;
|
|
658
|
+
readonly includeSourceMaps: boolean;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
async function runBunBuild(options: BuildOptions): Promise<Record<string, number> | undefined> {
|
|
662
|
+
const { sourceRoot, buildRoot, tsconfigPath, mode, env, diagnostics, entryPoints } = options;
|
|
663
|
+
const isProduction = mode === 'publish';
|
|
664
|
+
|
|
665
|
+
if (!entryPoints || entryPoints.length === 0) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const nodeEnv = env?.NODE_ENV ?? (isProduction ? 'production' : 'development');
|
|
670
|
+
const diagMax = readDiagMax(env, 50);
|
|
671
|
+
const define: Record<string, string> = {
|
|
672
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
673
|
+
};
|
|
674
|
+
const emitPublishSourcemaps = isProduction && shouldEmitPublishSourcemaps(env);
|
|
675
|
+
const start = performance.now();
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
const result = await runBunCompile({
|
|
679
|
+
entryPoints,
|
|
680
|
+
sourceRoot,
|
|
681
|
+
buildRoot,
|
|
682
|
+
tsconfigPath,
|
|
683
|
+
define,
|
|
684
|
+
minify: isProduction,
|
|
685
|
+
includeSourceMaps: isProduction ? emitPublishSourcemaps : true,
|
|
686
|
+
});
|
|
687
|
+
const { errorCount, warningCount } = pushBunLogs(
|
|
688
|
+
diagnostics,
|
|
689
|
+
result.logs,
|
|
690
|
+
`${mode}:bun`,
|
|
691
|
+
diagMax,
|
|
692
|
+
);
|
|
693
|
+
|
|
694
|
+
const end = performance.now();
|
|
695
|
+
diagnostics.push({
|
|
696
|
+
severity: 'info',
|
|
697
|
+
message: `[webstir-backend] ${mode}:bun ${errorCount} error(s), ${warningCount} warning(s) in ${(end - start).toFixed(1)}ms`,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
if (!result.success || errorCount > 0) {
|
|
701
|
+
throw new Error('bun build failed.');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return collectBunOutputSizes(result.outputs, buildRoot);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
const end = performance.now();
|
|
707
|
+
if (error instanceof Error) {
|
|
708
|
+
diagnostics.push({ severity: 'error', message: error.message });
|
|
709
|
+
} else {
|
|
710
|
+
diagnostics.push({ severity: 'error', message: String(error) });
|
|
448
711
|
}
|
|
712
|
+
diagnostics.push({
|
|
713
|
+
severity: 'info',
|
|
714
|
+
message: `[webstir-backend] ${mode}:bun failed in ${(end - start).toFixed(1)}ms`,
|
|
715
|
+
});
|
|
716
|
+
throw new Error('bun build failed.');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
async function runBunCompile(options: BunCompileOptions): Promise<BunBuildOutput> {
|
|
721
|
+
const build = getBunBuild();
|
|
722
|
+
if (!build) {
|
|
723
|
+
throw new Error('Bun.build() is not available in the current runtime.');
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return await build({
|
|
727
|
+
entrypoints: [...options.entryPoints],
|
|
728
|
+
root: options.sourceRoot,
|
|
729
|
+
outdir: options.buildRoot,
|
|
730
|
+
target: 'node',
|
|
731
|
+
format: 'esm',
|
|
732
|
+
splitting: false,
|
|
733
|
+
packages: 'external',
|
|
734
|
+
minify: options.minify,
|
|
735
|
+
sourcemap: options.includeSourceMaps ? 'linked' : 'none',
|
|
736
|
+
tsconfig: existsSync(options.tsconfigPath) ? options.tsconfigPath : undefined,
|
|
737
|
+
define: options.define,
|
|
738
|
+
throw: false,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function ensureBunCompileSucceeded(
|
|
743
|
+
result: BunBuildOutput,
|
|
744
|
+
diagnostics: ModuleDiagnostic[],
|
|
745
|
+
label: string,
|
|
746
|
+
diagMax: number,
|
|
747
|
+
): void {
|
|
748
|
+
const { errorCount } = pushBunLogs(diagnostics, result.logs, label, diagMax);
|
|
749
|
+
if (!result.success || errorCount > 0) {
|
|
750
|
+
throw new Error('bun build failed.');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function pushBunLogs(
|
|
755
|
+
diagnostics: ModuleDiagnostic[],
|
|
756
|
+
logs: readonly BunBuildLog[] | undefined,
|
|
757
|
+
label: string,
|
|
758
|
+
diagMax: number,
|
|
759
|
+
): { errorCount: number; warningCount: number } {
|
|
760
|
+
const entries = Array.isArray(logs) ? logs : [];
|
|
761
|
+
const errorLogs = entries.filter((log) => log.level === 'error');
|
|
762
|
+
const warningLogs = entries.filter((log) => log.level === 'warning');
|
|
763
|
+
|
|
764
|
+
for (const entry of errorLogs.slice(0, diagMax)) {
|
|
765
|
+
diagnostics.push({ severity: 'error', message: formatEsbuildMessage(entry) });
|
|
766
|
+
}
|
|
767
|
+
for (const entry of warningLogs.slice(0, diagMax)) {
|
|
768
|
+
diagnostics.push({ severity: 'warn', message: formatEsbuildMessage(entry) });
|
|
769
|
+
}
|
|
770
|
+
if (errorLogs.length > diagMax) {
|
|
771
|
+
diagnostics.push({
|
|
772
|
+
severity: 'info',
|
|
773
|
+
message: `[webstir-backend] ${label} ... ${errorLogs.length - diagMax} more error(s) omitted`,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
if (warningLogs.length > diagMax) {
|
|
777
|
+
diagnostics.push({
|
|
778
|
+
severity: 'info',
|
|
779
|
+
message: `[webstir-backend] ${label} ... ${warningLogs.length - diagMax} more warning(s) omitted`,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return {
|
|
784
|
+
errorCount: errorLogs.length,
|
|
785
|
+
warningCount: warningLogs.length,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function getBunBuild(): BunBuildFunction | undefined {
|
|
790
|
+
const runtime = globalThis as typeof globalThis & {
|
|
791
|
+
Bun?: {
|
|
792
|
+
build?: BunBuildFunction;
|
|
793
|
+
};
|
|
794
|
+
};
|
|
795
|
+
const build = runtime.Bun?.build;
|
|
796
|
+
return typeof build === 'function' ? build.bind(runtime.Bun) : undefined;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function collectBunOutputSizes(
|
|
800
|
+
outputs: readonly BunBuildOutputFile[] | undefined,
|
|
801
|
+
buildRoot: string,
|
|
802
|
+
): Record<string, number> {
|
|
803
|
+
const collected: Record<string, number> = {};
|
|
804
|
+
for (const output of outputs ?? []) {
|
|
805
|
+
const rel = path.relative(buildRoot, output.path);
|
|
806
|
+
collected[rel] = typeof output.size === 'number' ? output.size : 0;
|
|
807
|
+
}
|
|
808
|
+
return collected;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async function resetBuildRoot(buildRoot: string): Promise<void> {
|
|
812
|
+
await rm(buildRoot, { recursive: true, force: true });
|
|
813
|
+
await mkdir(buildRoot, { recursive: true });
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function readDiagMax(env: Record<string, string | undefined>, fallback: number): number {
|
|
817
|
+
const raw = env?.WEBSTIR_BACKEND_DIAG_MAX;
|
|
818
|
+
const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
|
|
819
|
+
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
449
820
|
}
|
|
450
821
|
|
|
451
822
|
function createIncrementalKey(mode: BackendBuildMode, buildRoot: string): string {
|
|
452
|
-
|
|
823
|
+
return `${mode}:${path.resolve(buildRoot)}`;
|
|
453
824
|
}
|
|
454
825
|
|
|
455
826
|
async function disposeIncrementalBuild(key: string): Promise<void> {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
incrementalBuildCache.delete(key);
|
|
827
|
+
const cached = incrementalBuildCache.get(key);
|
|
828
|
+
if (cached) {
|
|
829
|
+
try {
|
|
830
|
+
await cached.context.dispose();
|
|
831
|
+
} catch {
|
|
832
|
+
// ignore
|
|
464
833
|
}
|
|
834
|
+
incrementalBuildCache.delete(key);
|
|
835
|
+
}
|
|
465
836
|
}
|
|
466
837
|
|
|
467
838
|
function clearIncrementalCache(): void {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
incrementalBuildCache.delete(key);
|
|
839
|
+
for (const [key, entry] of incrementalBuildCache.entries()) {
|
|
840
|
+
try {
|
|
841
|
+
entry.context.dispose();
|
|
842
|
+
} catch {
|
|
843
|
+
// ignore
|
|
475
844
|
}
|
|
845
|
+
incrementalBuildCache.delete(key);
|
|
846
|
+
}
|
|
476
847
|
}
|
|
477
848
|
|
|
478
849
|
function createEntrySignature(entryPoints: readonly string[]): string {
|
|
479
|
-
|
|
850
|
+
return Array.from(entryPoints).sort().join('|');
|
|
480
851
|
}
|
|
481
852
|
|
|
482
853
|
export function collectOutputSizes(metafile: unknown, buildRoot: string): Record<string, number> {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
return outputs;
|
|
486
|
-
}
|
|
487
|
-
const mf = metafile as { outputs?: Record<string, { bytes?: number }> };
|
|
488
|
-
for (const [outPath, info] of Object.entries(mf.outputs ?? {})) {
|
|
489
|
-
const rel = path.relative(buildRoot, outPath);
|
|
490
|
-
outputs[rel] = typeof info.bytes === 'number' ? info.bytes : 0;
|
|
491
|
-
}
|
|
854
|
+
const outputs: Record<string, number> = {};
|
|
855
|
+
if (!metafile || typeof metafile !== 'object') {
|
|
492
856
|
return outputs;
|
|
857
|
+
}
|
|
858
|
+
const mf = metafile as { outputs?: Record<string, { bytes?: number }> };
|
|
859
|
+
for (const [outPath, info] of Object.entries(mf.outputs ?? {})) {
|
|
860
|
+
const rel = path.relative(buildRoot, outPath);
|
|
861
|
+
outputs[rel] = typeof info.bytes === 'number' ? info.bytes : 0;
|
|
862
|
+
}
|
|
863
|
+
return outputs;
|
|
493
864
|
}
|
|
494
865
|
|
|
495
|
-
function isEsbuildFailure(error: unknown): error is
|
|
496
|
-
|
|
866
|
+
function isEsbuildFailure(error: unknown): error is BuildFailure {
|
|
867
|
+
return typeof error === 'object' && error !== null && ('errors' in error || 'warnings' in error);
|
|
497
868
|
}
|
|
498
869
|
|
|
499
|
-
export function formatEsbuildMessage(msg:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
870
|
+
export function formatEsbuildMessage(msg: BuildMessageLike): string {
|
|
871
|
+
const text =
|
|
872
|
+
typeof msg.message === 'string'
|
|
873
|
+
? msg.message
|
|
874
|
+
: typeof msg.text === 'string'
|
|
875
|
+
? msg.text
|
|
876
|
+
: String(msg);
|
|
877
|
+
const loc = msg.location ?? msg.position;
|
|
878
|
+
if (loc && typeof loc.file === 'string') {
|
|
879
|
+
const position = typeof loc.line === 'number' ? `${loc.line}:${loc.column ?? 1}` : '1:1';
|
|
880
|
+
return `${loc.file}:${position} ${text}`;
|
|
881
|
+
}
|
|
882
|
+
return text;
|
|
507
883
|
}
|