@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/dist/watch.js
CHANGED
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, rm, stat } from 'node:fs/promises';
|
|
3
4
|
import { spawn } from 'node:child_process';
|
|
4
5
|
import { performance } from 'node:perf_hooks';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import { ensureModuleDefinitionBuild, formatEsbuildMessage, shouldTypeCheck, } from './build/pipeline.js';
|
|
7
8
|
import { discoverEntryPoints } from './build/entries.js';
|
|
8
9
|
import { loadBackendModuleManifest } from './manifest/pipeline.js';
|
|
9
10
|
import { createCacheReporter } from './cache/reporters.js';
|
|
10
|
-
import { normalizeMode, resolveWorkspacePaths } from './workspace.js';
|
|
11
|
+
import { normalizeMode, resolveWorkspacePaths, resolveWorkspaceRoot } from './workspace.js';
|
|
12
|
+
const WATCH_POLL_INTERVAL_MS = 250;
|
|
11
13
|
export async function startBackendWatch(options) {
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
14
|
+
const env = { ...process.env, ...(options.env ?? {}) };
|
|
15
|
+
const workspaceRoot = resolveWorkspaceRoot({
|
|
16
|
+
workspaceRoot: options.workspaceRoot,
|
|
17
|
+
env,
|
|
18
|
+
});
|
|
14
19
|
const paths = resolveWorkspacePaths(workspaceRoot);
|
|
15
20
|
const tsconfigPath = path.join(paths.sourceRoot, 'tsconfig.json');
|
|
16
21
|
const mode = normalizeMode(env.WEBSTIR_MODULE_MODE);
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
22
|
+
const initialEntryPoints = await discoverEntryPoints(paths.sourceRoot);
|
|
23
|
+
if (initialEntryPoints.length === 0) {
|
|
19
24
|
console.warn(`[webstir-backend] watch: no entry found under ${paths.sourceRoot} (index.ts/js)`);
|
|
20
25
|
throw new Error('No backend entry point found.');
|
|
21
26
|
}
|
|
22
27
|
const nodeEnv = env.NODE_ENV ?? (mode === 'publish' ? 'production' : 'development');
|
|
28
|
+
const shouldReportBunBenchmark = isEnabled(env.WEBSTIR_BACKEND_WATCH_BUN_BENCHMARK);
|
|
23
29
|
const diagMax = (() => {
|
|
24
30
|
const raw = env.WEBSTIR_BACKEND_DIAG_MAX;
|
|
25
31
|
const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
|
|
26
32
|
return Number.isFinite(n) && n > 0 ? n : 20;
|
|
27
33
|
})();
|
|
28
34
|
console.info(`[webstir-backend] watch:start (${mode})`);
|
|
29
|
-
// Start type-checker in watch mode (no emit) unless explicitly skipped for DX.
|
|
30
|
-
const shouldRunTypecheck = shouldTypeCheck(mode, env);
|
|
31
35
|
let tscProc;
|
|
32
|
-
if (
|
|
36
|
+
if (shouldTypeCheck(mode, env)) {
|
|
33
37
|
const tscArgs = ['-p', tsconfigPath, '--noEmit', '--watch'];
|
|
34
38
|
tscProc = spawn('tsc', tscArgs, {
|
|
35
39
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
|
-
env: { ...
|
|
40
|
+
env: { ...env, NODE_ENV: nodeEnv },
|
|
37
41
|
cwd: workspaceRoot,
|
|
38
42
|
});
|
|
39
43
|
tscProc.stdout?.on('data', (chunk) => {
|
|
@@ -54,98 +58,86 @@ export async function startBackendWatch(options) {
|
|
|
54
58
|
else {
|
|
55
59
|
console.info('[webstir-backend] watch: type-check skipped by WEBSTIR_BACKEND_TYPECHECK');
|
|
56
60
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
if (shouldReportBunBenchmark) {
|
|
62
|
+
console.info('[webstir-backend] watch: reporting primary Bun build timings via bunBenchmark* event fields.');
|
|
63
|
+
}
|
|
64
|
+
let stopping = false;
|
|
65
|
+
let watchTimer;
|
|
66
|
+
let currentSnapshot = await takeWatchSnapshot(workspaceRoot, paths.sourceRoot, tsconfigPath);
|
|
67
|
+
let buildInFlight = false;
|
|
68
|
+
let pendingBuild = false;
|
|
69
|
+
let buildFailure;
|
|
70
|
+
const runBuild = async () => {
|
|
71
|
+
if (stopping) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (buildInFlight) {
|
|
75
|
+
pendingBuild = true;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
buildInFlight = true;
|
|
79
|
+
try {
|
|
80
|
+
do {
|
|
81
|
+
pendingBuild = false;
|
|
82
|
+
const nextSnapshot = await takeWatchSnapshot(workspaceRoot, paths.sourceRoot, tsconfigPath);
|
|
83
|
+
currentSnapshot = nextSnapshot;
|
|
84
|
+
const result = await performWatchBuild({
|
|
85
|
+
workspaceRoot,
|
|
86
|
+
sourceRoot: paths.sourceRoot,
|
|
87
|
+
buildRoot: paths.buildRoot,
|
|
88
|
+
tsconfigPath,
|
|
89
|
+
mode,
|
|
90
|
+
env,
|
|
91
|
+
nodeEnv,
|
|
92
|
+
diagMax,
|
|
93
|
+
shouldReportBunBenchmark,
|
|
94
|
+
onEvent: options.onEvent,
|
|
95
|
+
});
|
|
96
|
+
if (!result.succeeded) {
|
|
97
|
+
buildFailure = new Error('Backend watch build failed.');
|
|
88
98
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const diagBuffer = [];
|
|
92
|
-
const cacheReporter = createCacheReporter({
|
|
93
|
-
workspaceRoot,
|
|
94
|
-
buildRoot: paths.buildRoot,
|
|
95
|
-
env,
|
|
96
|
-
diagnostics: diagBuffer
|
|
97
|
-
});
|
|
98
|
-
try {
|
|
99
|
-
const metafile = result.metafile;
|
|
100
|
-
if (metafile && metafile.outputs) {
|
|
101
|
-
const outputs = collectOutputSizes(metafile, paths.buildRoot);
|
|
102
|
-
await cacheReporter.diffOutputs(outputs, mode);
|
|
103
|
-
}
|
|
104
|
-
const manifest = await loadBackendModuleManifest({
|
|
105
|
-
workspaceRoot,
|
|
106
|
-
buildRoot: paths.buildRoot,
|
|
107
|
-
entryPoints,
|
|
108
|
-
diagnostics: diagBuffer
|
|
109
|
-
});
|
|
110
|
-
await cacheReporter.diffManifest(manifest);
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// cache or manifest diff failure should not break watch
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
for (const diag of diagBuffer) {
|
|
117
|
-
const logger = diag.severity === 'error' ? console.error : diag.severity === 'warn' ? console.warn : console.info;
|
|
118
|
-
logger(diag.message);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
99
|
+
else {
|
|
100
|
+
buildFailure = undefined;
|
|
121
101
|
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
102
|
+
} while (pendingBuild && !stopping);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
buildInFlight = false;
|
|
106
|
+
}
|
|
124
107
|
};
|
|
125
|
-
|
|
126
|
-
entryPoints,
|
|
127
|
-
bundle: false,
|
|
128
|
-
platform: 'node',
|
|
129
|
-
target: 'node20',
|
|
130
|
-
format: 'esm',
|
|
131
|
-
sourcemap: true,
|
|
132
|
-
outdir: paths.buildRoot,
|
|
133
|
-
outbase: paths.sourceRoot,
|
|
134
|
-
metafile: true,
|
|
135
|
-
tsconfig: existsSync(tsconfigPath) ? tsconfigPath : undefined,
|
|
136
|
-
define: { 'process.env.NODE_ENV': JSON.stringify(nodeEnv) },
|
|
137
|
-
logLevel: 'silent',
|
|
138
|
-
plugins: [timingPlugin],
|
|
139
|
-
});
|
|
140
|
-
await ctx.watch();
|
|
108
|
+
await runBuild();
|
|
141
109
|
console.info('[webstir-backend] watch:ready');
|
|
110
|
+
const poll = async () => {
|
|
111
|
+
if (stopping) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const nextSnapshot = await takeWatchSnapshot(workspaceRoot, paths.sourceRoot, tsconfigPath);
|
|
116
|
+
if (nextSnapshot !== currentSnapshot) {
|
|
117
|
+
currentSnapshot = nextSnapshot;
|
|
118
|
+
await runBuild();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
console.warn(`[webstir-backend] watch:poll failed: ${message}`);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
if (!stopping) {
|
|
127
|
+
watchTimer = setTimeout(() => {
|
|
128
|
+
void poll();
|
|
129
|
+
}, WATCH_POLL_INTERVAL_MS);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
watchTimer = setTimeout(() => {
|
|
134
|
+
void poll();
|
|
135
|
+
}, WATCH_POLL_INTERVAL_MS);
|
|
142
136
|
return {
|
|
143
137
|
async stop() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
catch {
|
|
148
|
-
// ignore
|
|
138
|
+
stopping = true;
|
|
139
|
+
if (watchTimer) {
|
|
140
|
+
clearTimeout(watchTimer);
|
|
149
141
|
}
|
|
150
142
|
try {
|
|
151
143
|
tscProc?.kill('SIGINT');
|
|
@@ -154,6 +146,262 @@ export async function startBackendWatch(options) {
|
|
|
154
146
|
// ignore
|
|
155
147
|
}
|
|
156
148
|
console.info('[webstir-backend] watch:stopped');
|
|
149
|
+
if (buildFailure) {
|
|
150
|
+
buildFailure = undefined;
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async function performWatchBuild(options) {
|
|
156
|
+
const start = performance.now();
|
|
157
|
+
await emitWatchEvent(options.onEvent, {
|
|
158
|
+
type: 'build-start',
|
|
159
|
+
});
|
|
160
|
+
const diagnostics = [];
|
|
161
|
+
const entryPoints = await discoverEntryPoints(options.sourceRoot);
|
|
162
|
+
if (entryPoints.length === 0) {
|
|
163
|
+
diagnostics.push({
|
|
164
|
+
severity: 'error',
|
|
165
|
+
message: `No backend entry points found under ${options.sourceRoot}.`,
|
|
166
|
+
});
|
|
167
|
+
flushDiagnostics(diagnostics);
|
|
168
|
+
const end = performance.now();
|
|
169
|
+
await emitWatchEvent(options.onEvent, {
|
|
170
|
+
type: 'build-complete',
|
|
171
|
+
succeeded: false,
|
|
172
|
+
errorCount: 1,
|
|
173
|
+
warningCount: 0,
|
|
174
|
+
durationMs: end - start,
|
|
175
|
+
});
|
|
176
|
+
return {
|
|
177
|
+
succeeded: false,
|
|
178
|
+
errorCount: 1,
|
|
179
|
+
warningCount: 0,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const buildResult = await runPrimaryBunWatchBuild({
|
|
183
|
+
entryPoints,
|
|
184
|
+
sourceRoot: options.sourceRoot,
|
|
185
|
+
buildRoot: options.buildRoot,
|
|
186
|
+
tsconfigPath: options.tsconfigPath,
|
|
187
|
+
nodeEnv: options.nodeEnv,
|
|
188
|
+
diagMax: options.diagMax,
|
|
189
|
+
});
|
|
190
|
+
console.info(`[webstir-backend] watch:bun ${buildResult.errorCount} error(s), ${buildResult.warningCount} warning(s) in ${buildResult.durationMs.toFixed(1)}ms`);
|
|
191
|
+
if (buildResult.succeeded) {
|
|
192
|
+
const cacheReporter = createCacheReporter({
|
|
193
|
+
workspaceRoot: options.workspaceRoot,
|
|
194
|
+
buildRoot: options.buildRoot,
|
|
195
|
+
env: options.env,
|
|
196
|
+
diagnostics,
|
|
197
|
+
});
|
|
198
|
+
try {
|
|
199
|
+
await ensureModuleDefinitionBuild({
|
|
200
|
+
sourceRoot: options.sourceRoot,
|
|
201
|
+
buildRoot: options.buildRoot,
|
|
202
|
+
tsconfigPath: options.tsconfigPath,
|
|
203
|
+
mode: options.mode,
|
|
204
|
+
env: options.env,
|
|
205
|
+
diagnostics,
|
|
206
|
+
});
|
|
207
|
+
await cacheReporter.diffOutputs(collectBunOutputSizes(buildResult.outputs, options.buildRoot), options.mode);
|
|
208
|
+
const manifest = await loadBackendModuleManifest({
|
|
209
|
+
workspaceRoot: options.workspaceRoot,
|
|
210
|
+
buildRoot: options.buildRoot,
|
|
211
|
+
entryPoints,
|
|
212
|
+
diagnostics,
|
|
213
|
+
});
|
|
214
|
+
await cacheReporter.diffManifest(manifest);
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// cache or manifest diff failure should not break watch
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
flushDiagnostics(diagnostics);
|
|
221
|
+
const end = performance.now();
|
|
222
|
+
const bunBenchmark = options.shouldReportBunBenchmark
|
|
223
|
+
? {
|
|
224
|
+
succeeded: buildResult.succeeded,
|
|
225
|
+
errorCount: buildResult.errorCount,
|
|
226
|
+
warningCount: buildResult.warningCount,
|
|
227
|
+
durationMs: buildResult.durationMs,
|
|
228
|
+
}
|
|
229
|
+
: undefined;
|
|
230
|
+
await emitWatchEvent(options.onEvent, {
|
|
231
|
+
type: 'build-complete',
|
|
232
|
+
succeeded: buildResult.succeeded,
|
|
233
|
+
errorCount: buildResult.errorCount,
|
|
234
|
+
warningCount: buildResult.warningCount,
|
|
235
|
+
durationMs: end - start,
|
|
236
|
+
bunBenchmarkSucceeded: bunBenchmark?.succeeded,
|
|
237
|
+
bunBenchmarkErrorCount: bunBenchmark?.errorCount,
|
|
238
|
+
bunBenchmarkWarningCount: bunBenchmark?.warningCount,
|
|
239
|
+
bunBenchmarkDurationMs: bunBenchmark?.durationMs,
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
succeeded: buildResult.succeeded,
|
|
243
|
+
errorCount: buildResult.errorCount,
|
|
244
|
+
warningCount: buildResult.warningCount,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async function runPrimaryBunWatchBuild(options) {
|
|
248
|
+
const build = getBunBuild();
|
|
249
|
+
if (!build) {
|
|
250
|
+
throw new Error('Bun.build() is not available in the current runtime.');
|
|
251
|
+
}
|
|
252
|
+
await rm(options.buildRoot, { recursive: true, force: true });
|
|
253
|
+
await mkdir(options.buildRoot, { recursive: true });
|
|
254
|
+
const start = performance.now();
|
|
255
|
+
const result = await build({
|
|
256
|
+
entrypoints: [...options.entryPoints],
|
|
257
|
+
root: options.sourceRoot,
|
|
258
|
+
outdir: options.buildRoot,
|
|
259
|
+
target: 'node',
|
|
260
|
+
format: 'esm',
|
|
261
|
+
splitting: false,
|
|
262
|
+
packages: 'external',
|
|
263
|
+
sourcemap: 'linked',
|
|
264
|
+
tsconfig: existsSync(options.tsconfigPath) ? options.tsconfigPath : undefined,
|
|
265
|
+
define: {
|
|
266
|
+
'process.env.NODE_ENV': JSON.stringify(options.nodeEnv),
|
|
157
267
|
},
|
|
268
|
+
// Preserve the old esbuild watch behavior: transpile entries without requiring
|
|
269
|
+
// every relative import target to exist in minimal seeded workspaces.
|
|
270
|
+
plugins: [createRelativeImportPassthroughPlugin()],
|
|
271
|
+
throw: false,
|
|
272
|
+
});
|
|
273
|
+
const end = performance.now();
|
|
274
|
+
const { errorCount, warningCount } = logBunBuildResult(result, options.diagMax);
|
|
275
|
+
return {
|
|
276
|
+
succeeded: result.success && errorCount === 0,
|
|
277
|
+
errorCount,
|
|
278
|
+
warningCount,
|
|
279
|
+
durationMs: end - start,
|
|
280
|
+
outputs: result.outputs,
|
|
158
281
|
};
|
|
159
282
|
}
|
|
283
|
+
function logBunBuildResult(result, diagMax) {
|
|
284
|
+
const logs = Array.isArray(result.logs) ? result.logs : [];
|
|
285
|
+
const errorLogs = logs.filter((log) => log.level === 'error');
|
|
286
|
+
const warningLogs = logs.filter((log) => log.level === 'warning');
|
|
287
|
+
for (const log of errorLogs.slice(0, diagMax)) {
|
|
288
|
+
console.error(`[webstir-backend][bun] ${formatEsbuildMessage(log)}`);
|
|
289
|
+
}
|
|
290
|
+
if (errorLogs.length > diagMax) {
|
|
291
|
+
console.error(`[webstir-backend][bun] ... ${errorLogs.length - diagMax} more error(s) omitted`);
|
|
292
|
+
}
|
|
293
|
+
for (const log of warningLogs.slice(0, diagMax)) {
|
|
294
|
+
console.warn(`[webstir-backend][bun] ${formatEsbuildMessage(log)}`);
|
|
295
|
+
}
|
|
296
|
+
if (warningLogs.length > diagMax) {
|
|
297
|
+
console.warn(`[webstir-backend][bun] ... ${warningLogs.length - diagMax} more warning(s) omitted`);
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
errorCount: errorLogs.length,
|
|
301
|
+
warningCount: warningLogs.length,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function collectBunOutputSizes(outputs, buildRoot) {
|
|
305
|
+
const collected = {};
|
|
306
|
+
for (const output of outputs ?? []) {
|
|
307
|
+
const rel = path.relative(buildRoot, output.path);
|
|
308
|
+
collected[rel] = typeof output.size === 'number' ? output.size : 0;
|
|
309
|
+
}
|
|
310
|
+
return collected;
|
|
311
|
+
}
|
|
312
|
+
async function takeWatchSnapshot(workspaceRoot, sourceRoot, tsconfigPath) {
|
|
313
|
+
const watchFiles = new Set();
|
|
314
|
+
const packageJsonPath = path.join(workspaceRoot, 'package.json');
|
|
315
|
+
if (existsSync(packageJsonPath)) {
|
|
316
|
+
watchFiles.add(packageJsonPath);
|
|
317
|
+
}
|
|
318
|
+
if (existsSync(tsconfigPath)) {
|
|
319
|
+
watchFiles.add(tsconfigPath);
|
|
320
|
+
}
|
|
321
|
+
const typesRoot = path.join(workspaceRoot, 'types');
|
|
322
|
+
const directoryRoots = [sourceRoot];
|
|
323
|
+
if (existsSync(typesRoot)) {
|
|
324
|
+
directoryRoots.push(typesRoot);
|
|
325
|
+
}
|
|
326
|
+
for (const directoryRoot of directoryRoots) {
|
|
327
|
+
for (const filePath of await listWatchFiles(directoryRoot)) {
|
|
328
|
+
watchFiles.add(filePath);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const entries = await Promise.all(Array.from(watchFiles)
|
|
332
|
+
.sort()
|
|
333
|
+
.map(async (filePath) => {
|
|
334
|
+
const fileStat = await stat(filePath);
|
|
335
|
+
return `${filePath}:${fileStat.size}:${fileStat.mtimeMs}`;
|
|
336
|
+
}));
|
|
337
|
+
return entries.join('|');
|
|
338
|
+
}
|
|
339
|
+
async function listWatchFiles(root) {
|
|
340
|
+
if (!existsSync(root)) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
const entries = await glob('**/*', {
|
|
344
|
+
cwd: root,
|
|
345
|
+
absolute: true,
|
|
346
|
+
dot: false,
|
|
347
|
+
nodir: false,
|
|
348
|
+
});
|
|
349
|
+
const files = [];
|
|
350
|
+
for (const entry of entries) {
|
|
351
|
+
try {
|
|
352
|
+
const entryStat = await stat(entry);
|
|
353
|
+
if (entryStat.isFile()) {
|
|
354
|
+
files.push(entry);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// Ignore files deleted between glob and stat.
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return files;
|
|
362
|
+
}
|
|
363
|
+
function flushDiagnostics(diagnostics) {
|
|
364
|
+
for (const diag of diagnostics) {
|
|
365
|
+
const logger = diag.severity === 'error'
|
|
366
|
+
? console.error
|
|
367
|
+
: diag.severity === 'warn'
|
|
368
|
+
? console.warn
|
|
369
|
+
: console.info;
|
|
370
|
+
logger(diag.message);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function emitWatchEvent(onEvent, event) {
|
|
374
|
+
if (!onEvent) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
await onEvent(event);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
382
|
+
console.warn(`[webstir-backend] watch:event failed: ${message}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function getBunBuild() {
|
|
386
|
+
const runtime = globalThis;
|
|
387
|
+
const build = runtime.Bun?.build;
|
|
388
|
+
return typeof build === 'function' ? build.bind(runtime.Bun) : undefined;
|
|
389
|
+
}
|
|
390
|
+
function createRelativeImportPassthroughPlugin() {
|
|
391
|
+
return {
|
|
392
|
+
name: 'webstir-backend-watch-relative-imports',
|
|
393
|
+
setup(build) {
|
|
394
|
+
build.onResolve({ filter: /^\.\.?\// }, (args) => ({
|
|
395
|
+
path: args.path,
|
|
396
|
+
external: true,
|
|
397
|
+
}));
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function isEnabled(value) {
|
|
402
|
+
if (typeof value !== 'string') {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
const normalized = value.trim().toLowerCase();
|
|
406
|
+
return normalized === '1' || normalized === 'true' || normalized === 'on' || normalized === 'yes';
|
|
407
|
+
}
|
package/dist/workspace.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { ResolvedModuleWorkspace } from '@webstir-io/module-contract';
|
|
2
2
|
export type BackendBuildMode = 'build' | 'publish' | 'test';
|
|
3
|
+
interface ResolveWorkspaceRootOptions {
|
|
4
|
+
readonly workspaceRoot?: string;
|
|
5
|
+
readonly env?: Record<string, string | undefined>;
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
readonly importMetaUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveWorkspaceRoot(options?: string | ResolveWorkspaceRootOptions): string;
|
|
3
10
|
export declare function resolveWorkspacePaths(workspaceRoot: string): ResolvedModuleWorkspace;
|
|
4
11
|
export declare function normalizeMode(rawMode: unknown): BackendBuildMode;
|
|
12
|
+
export {};
|
package/dist/workspace.js
CHANGED
|
@@ -1,9 +1,33 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
const WORKSPACE_ROOT_PATTERN = /^(.*)[/\\](?:src|build)[/\\]backend(?:[/\\].*)?$/;
|
|
4
|
+
export function resolveWorkspaceRoot(options) {
|
|
5
|
+
if (typeof options === 'string') {
|
|
6
|
+
return path.resolve(options);
|
|
7
|
+
}
|
|
8
|
+
const explicitRoot = options?.workspaceRoot?.trim();
|
|
9
|
+
if (explicitRoot) {
|
|
10
|
+
return path.resolve(explicitRoot);
|
|
11
|
+
}
|
|
12
|
+
const env = options?.env ?? process.env;
|
|
13
|
+
const envRoot = env.WORKSPACE_ROOT?.trim() || env.WEBSTIR_WORKSPACE_ROOT?.trim();
|
|
14
|
+
if (envRoot) {
|
|
15
|
+
return path.resolve(envRoot);
|
|
16
|
+
}
|
|
17
|
+
const inferredRoot = options?.importMetaUrl
|
|
18
|
+
? inferWorkspaceRootFromImportMetaUrl(options.importMetaUrl)
|
|
19
|
+
: undefined;
|
|
20
|
+
if (inferredRoot) {
|
|
21
|
+
return inferredRoot;
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(options?.cwd ?? process.cwd());
|
|
24
|
+
}
|
|
2
25
|
export function resolveWorkspacePaths(workspaceRoot) {
|
|
26
|
+
const resolvedWorkspaceRoot = resolveWorkspaceRoot(workspaceRoot);
|
|
3
27
|
return {
|
|
4
|
-
sourceRoot: path.join(
|
|
5
|
-
buildRoot: path.join(
|
|
6
|
-
testsRoot: path.join(
|
|
28
|
+
sourceRoot: path.join(resolvedWorkspaceRoot, 'src', 'backend'),
|
|
29
|
+
buildRoot: path.join(resolvedWorkspaceRoot, 'build', 'backend'),
|
|
30
|
+
testsRoot: path.join(resolvedWorkspaceRoot, 'src', 'backend', 'tests'),
|
|
7
31
|
};
|
|
8
32
|
}
|
|
9
33
|
export function normalizeMode(rawMode) {
|
|
@@ -13,3 +37,20 @@ export function normalizeMode(rawMode) {
|
|
|
13
37
|
const normalized = rawMode.toLowerCase();
|
|
14
38
|
return normalized === 'publish' || normalized === 'test' ? normalized : 'build';
|
|
15
39
|
}
|
|
40
|
+
function inferWorkspaceRootFromImportMetaUrl(importMetaUrl) {
|
|
41
|
+
try {
|
|
42
|
+
return inferWorkspaceRootFromFilePath(fileURLToPath(importMetaUrl));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function inferWorkspaceRootFromFilePath(filePath) {
|
|
49
|
+
const normalizedFilePath = path.resolve(filePath);
|
|
50
|
+
const match = normalizedFilePath.match(WORKSPACE_ROOT_PATTERN);
|
|
51
|
+
if (!match) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const inferredRoot = match[1];
|
|
55
|
+
return inferredRoot || path.parse(normalizedFilePath).root;
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstir-io/webstir-backend",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.16",
|
|
4
|
+
"description": "Backend build and runtime tooling for Webstir workspaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"webstir-backend-deploy": "dist/deploy-cli.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./dist/index.d.ts",
|
|
11
14
|
"import": "./dist/index.js",
|
|
12
15
|
"default": "./dist/index.js"
|
|
13
16
|
},
|
|
17
|
+
"./runtime/forms": {
|
|
18
|
+
"types": "./dist/runtime/forms.d.ts",
|
|
19
|
+
"import": "./dist/runtime/forms.js",
|
|
20
|
+
"default": "./dist/runtime/forms.js"
|
|
21
|
+
},
|
|
22
|
+
"./runtime/request-hooks": {
|
|
23
|
+
"types": "./dist/runtime/request-hooks.d.ts",
|
|
24
|
+
"import": "./dist/runtime/request-hooks.js",
|
|
25
|
+
"default": "./dist/runtime/request-hooks.js"
|
|
26
|
+
},
|
|
27
|
+
"./runtime/session": {
|
|
28
|
+
"types": "./dist/runtime/session.d.ts",
|
|
29
|
+
"import": "./dist/runtime/session.js",
|
|
30
|
+
"default": "./dist/runtime/session.js"
|
|
31
|
+
},
|
|
32
|
+
"./runtime/views": {
|
|
33
|
+
"types": "./dist/runtime/views.d.ts",
|
|
34
|
+
"import": "./dist/runtime/views.js",
|
|
35
|
+
"default": "./dist/runtime/views.js"
|
|
36
|
+
},
|
|
37
|
+
"./runtime/bun": {
|
|
38
|
+
"types": "./dist/runtime/bun.d.ts",
|
|
39
|
+
"import": "./dist/runtime/bun.js",
|
|
40
|
+
"default": "./dist/runtime/bun.js"
|
|
41
|
+
},
|
|
42
|
+
"./runtime/deploy": {
|
|
43
|
+
"types": "./dist/runtime/deploy.d.ts",
|
|
44
|
+
"import": "./dist/runtime/deploy.js",
|
|
45
|
+
"default": "./dist/runtime/deploy.js"
|
|
46
|
+
},
|
|
14
47
|
"./provider": {
|
|
15
48
|
"types": "./dist/provider.d.ts",
|
|
16
49
|
"import": "./dist/provider.js",
|
|
@@ -30,13 +63,12 @@
|
|
|
30
63
|
},
|
|
31
64
|
"scripts": {
|
|
32
65
|
"build": "tsc -p tsconfig.json",
|
|
33
|
-
"test": "
|
|
66
|
+
"test": "tsc -p tsconfig.json && bun test tests",
|
|
34
67
|
"clean": "rm -rf dist",
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"dev": "
|
|
38
|
-
"
|
|
39
|
-
"smoke": "npm run build && node scripts/smoke.mjs",
|
|
68
|
+
"watch": "tsc -p tsconfig.json && bun -e \"import('./dist/watch.js').then(m=>m.startBackendWatch({env:{WEBSTIR_MODULE_MODE:'build'}}));\"",
|
|
69
|
+
"dev": "bun run watch",
|
|
70
|
+
"dev:fast": "WEBSTIR_BACKEND_TYPECHECK=skip bun run watch",
|
|
71
|
+
"smoke": "tsc -p tsconfig.json && bun scripts/smoke.mjs",
|
|
40
72
|
"release": "bash scripts/publish.sh"
|
|
41
73
|
},
|
|
42
74
|
"files": [
|
|
@@ -45,25 +77,28 @@
|
|
|
45
77
|
"src",
|
|
46
78
|
"scripts",
|
|
47
79
|
"tests",
|
|
48
|
-
"tsconfig.json"
|
|
49
|
-
"package-lock.json"
|
|
80
|
+
"tsconfig.json"
|
|
50
81
|
],
|
|
51
82
|
"engines": {
|
|
52
|
-
"
|
|
83
|
+
"bun": ">=1.3.11"
|
|
53
84
|
},
|
|
54
85
|
"license": "MIT",
|
|
55
86
|
"repository": {
|
|
56
87
|
"type": "git",
|
|
57
|
-
"url": "git+https://github.com/webstir-io/webstir
|
|
88
|
+
"url": "git+https://github.com/webstir-io/webstir.git",
|
|
89
|
+
"directory": "packages/tooling/webstir-backend"
|
|
90
|
+
},
|
|
91
|
+
"homepage": "https://github.com/webstir-io/webstir/tree/main/packages/tooling/webstir-backend#readme",
|
|
92
|
+
"bugs": {
|
|
93
|
+
"url": "https://github.com/webstir-io/webstir/issues"
|
|
58
94
|
},
|
|
59
95
|
"dependencies": {
|
|
60
|
-
"@webstir-io/module-contract": "^0.1.
|
|
96
|
+
"@webstir-io/module-contract": "^0.1.16",
|
|
61
97
|
"esbuild": "^0.25.10",
|
|
62
98
|
"glob": "^10.4.1"
|
|
63
99
|
},
|
|
64
100
|
"devDependencies": {
|
|
65
101
|
"@types/node": "^20.19.21",
|
|
66
|
-
"fastify": "^5.6.2",
|
|
67
102
|
"pino": "^10.1.0",
|
|
68
103
|
"typescript": "^5.7.2"
|
|
69
104
|
},
|