@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { startPublishedWorkspaceServer } from './runtime/deploy.js';
|
|
4
|
+
async function main(argv) {
|
|
5
|
+
const args = parseArgs(argv);
|
|
6
|
+
if (args.help) {
|
|
7
|
+
printHelp();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const workspaceRoot = path.resolve(args.workspace ?? process.cwd());
|
|
11
|
+
const server = await startPublishedWorkspaceServer({
|
|
12
|
+
workspaceRoot,
|
|
13
|
+
host: args.host,
|
|
14
|
+
port: args.port,
|
|
15
|
+
});
|
|
16
|
+
const shutdown = async (signal) => {
|
|
17
|
+
process.stderr.write(`[webstir-backend-deploy] received ${signal}, stopping.\n`);
|
|
18
|
+
await server.stop();
|
|
19
|
+
process.exit(0);
|
|
20
|
+
};
|
|
21
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
22
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
23
|
+
process.stdout.write(`[webstir-backend-deploy] serving ${server.mode} workspace at ${server.origin}\n`);
|
|
24
|
+
await new Promise(() => {
|
|
25
|
+
// Keep the process alive until it receives a signal.
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
let workspace;
|
|
30
|
+
let host;
|
|
31
|
+
let port;
|
|
32
|
+
let help = false;
|
|
33
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
+
const arg = argv[index];
|
|
35
|
+
if (!arg) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (arg === '--help' || arg === '-h') {
|
|
39
|
+
help = true;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (arg === '--workspace') {
|
|
43
|
+
const next = argv[index + 1];
|
|
44
|
+
if (!next) {
|
|
45
|
+
throw new Error('Missing value for --workspace.');
|
|
46
|
+
}
|
|
47
|
+
workspace = next;
|
|
48
|
+
index += 1;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (arg === '--host') {
|
|
52
|
+
const next = argv[index + 1];
|
|
53
|
+
if (!next) {
|
|
54
|
+
throw new Error('Missing value for --host.');
|
|
55
|
+
}
|
|
56
|
+
host = next;
|
|
57
|
+
index += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (arg === '--port') {
|
|
61
|
+
const next = argv[index + 1];
|
|
62
|
+
if (!next) {
|
|
63
|
+
throw new Error('Missing value for --port.');
|
|
64
|
+
}
|
|
65
|
+
const parsed = Number(next);
|
|
66
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
67
|
+
throw new Error(`Invalid value for --port: ${next}`);
|
|
68
|
+
}
|
|
69
|
+
port = parsed;
|
|
70
|
+
index += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
74
|
+
}
|
|
75
|
+
return { workspace, host, port, help };
|
|
76
|
+
}
|
|
77
|
+
function printHelp() {
|
|
78
|
+
process.stdout.write(`Usage: webstir-backend-deploy [--workspace <path>] [--host <host>] [--port <port>]
|
|
79
|
+
|
|
80
|
+
Starts a published api or full Webstir workspace with a single Bun entrypoint.
|
|
81
|
+
`);
|
|
82
|
+
}
|
|
83
|
+
main(process.argv.slice(2)).catch((error) => {
|
|
84
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export function pushEntryBucketSummary(diagnostics, entryPoints) {
|
|
2
2
|
try {
|
|
3
|
-
const server = entryPoints.filter((p) => p === 'index.js' || /(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p)).length;
|
|
3
|
+
const server = entryPoints.filter((p) => p === 'index.js' || (/(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p))).length;
|
|
4
4
|
const functionsCount = entryPoints.filter((p) => p.startsWith('functions/')).length;
|
|
5
5
|
const jobsCount = entryPoints.filter((p) => p.startsWith('jobs/')).length;
|
|
6
6
|
diagnostics.push({
|
|
7
7
|
severity: 'info',
|
|
8
|
-
message: `[webstir-backend] entries by bucket: server=${server} functions=${functionsCount} jobs=${jobsCount}
|
|
8
|
+
message: `[webstir-backend] entries by bucket: server=${server} functions=${functionsCount} jobs=${jobsCount}`,
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
catch {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
export { runAddJob, runAddRoute, runUpdateRouteContract } from './add.js';
|
|
2
|
+
export type { AddJobOptions, AddRouteOptions, UpdateRouteContractOptions } from './add.js';
|
|
1
3
|
export { backendProvider } from './provider.js';
|
|
2
4
|
export { startBackendWatch } from './watch.js';
|
|
5
|
+
export { getBackendScaffoldAssets } from './scaffold/assets.js';
|
|
6
|
+
export { createDefaultBunBackendBootstrap, startBunBackend } from './runtime/bun.js';
|
|
7
|
+
export type { BunRuntimeEnvLike, DefaultBunBackendBootstrapOptions, MetricsTracker, RuntimeLogger, } from './runtime/bun.js';
|
|
8
|
+
export { startPublishedWorkspaceServer } from './runtime/deploy.js';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
export { runAddJob, runAddRoute, runUpdateRouteContract } from './add.js';
|
|
1
2
|
export { backendProvider } from './provider.js';
|
|
2
3
|
export { startBackendWatch } from './watch.js';
|
|
4
|
+
export { getBackendScaffoldAssets } from './scaffold/assets.js';
|
|
5
|
+
export { createDefaultBunBackendBootstrap, startBunBackend } from './runtime/bun.js';
|
|
6
|
+
export { startPublishedWorkspaceServer } from './runtime/deploy.js';
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import { pathToFileURL } from 'node:url';
|
|
5
4
|
import { moduleManifestSchema, CONTRACT_VERSION } from '@webstir-io/module-contract';
|
|
5
|
+
import { readTextFile } from '../utils/bun.js';
|
|
6
6
|
export async function loadBackendModuleManifest(options) {
|
|
7
7
|
const { workspaceRoot, buildRoot, entryPoints, diagnostics } = options;
|
|
8
8
|
const pkgPath = path.join(workspaceRoot, 'package.json');
|
|
9
9
|
let workspacePackage;
|
|
10
10
|
try {
|
|
11
|
-
const raw = await
|
|
11
|
+
const raw = await readTextFile(pkgPath);
|
|
12
12
|
workspacePackage = JSON.parse(raw);
|
|
13
13
|
}
|
|
14
14
|
catch (error) {
|
|
@@ -17,15 +17,21 @@ export async function loadBackendModuleManifest(options) {
|
|
|
17
17
|
if (err.code !== 'ENOENT') {
|
|
18
18
|
diagnostics.push({
|
|
19
19
|
severity: 'warn',
|
|
20
|
-
message: `[webstir-backend] unable to read ${pkgPath}: ${err.message}. Using defaults
|
|
20
|
+
message: `[webstir-backend] unable to read ${pkgPath}: ${err.message}. Using defaults.`,
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
const moduleConfig = workspacePackage?.webstir?.moduleManifest ?? {};
|
|
25
25
|
let manifestCandidate = {
|
|
26
|
-
contractVersion: typeof moduleConfig.contractVersion === 'string'
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
contractVersion: typeof moduleConfig.contractVersion === 'string'
|
|
27
|
+
? moduleConfig.contractVersion
|
|
28
|
+
: CONTRACT_VERSION,
|
|
29
|
+
name: typeof moduleConfig.name === 'string'
|
|
30
|
+
? moduleConfig.name
|
|
31
|
+
: deriveModuleName(workspacePackage, workspaceRoot),
|
|
32
|
+
version: typeof moduleConfig.version === 'string'
|
|
33
|
+
? moduleConfig.version
|
|
34
|
+
: deriveModuleVersion(workspacePackage),
|
|
29
35
|
kind: 'backend',
|
|
30
36
|
capabilities: Array.isArray(moduleConfig.capabilities) ? moduleConfig.capabilities : [],
|
|
31
37
|
routes: moduleConfig.routes ?? [],
|
|
@@ -34,25 +40,28 @@ export async function loadBackendModuleManifest(options) {
|
|
|
34
40
|
events: moduleConfig.events ?? [],
|
|
35
41
|
services: moduleConfig.services ?? [],
|
|
36
42
|
init: moduleConfig.init,
|
|
37
|
-
dispose: moduleConfig.dispose
|
|
43
|
+
dispose: moduleConfig.dispose,
|
|
38
44
|
};
|
|
39
45
|
const definition = await loadModuleDefinition(buildRoot, diagnostics);
|
|
40
46
|
if (definition) {
|
|
41
47
|
const definitionManifest = definition.manifest ?? {};
|
|
42
48
|
const routesFromDefinition = definition.routes?.map((route) => route.definition);
|
|
43
49
|
const viewsFromDefinition = definition.views?.map((view) => view.definition);
|
|
44
|
-
const mergedCapabilities = Array.from(new Set([
|
|
50
|
+
const mergedCapabilities = Array.from(new Set([
|
|
51
|
+
...(manifestCandidate.capabilities ?? []),
|
|
52
|
+
...(definitionManifest.capabilities ?? []),
|
|
53
|
+
]));
|
|
45
54
|
manifestCandidate = {
|
|
46
55
|
...manifestCandidate,
|
|
47
56
|
...definitionManifest,
|
|
48
57
|
capabilities: mergedCapabilities,
|
|
49
|
-
routes: routesFromDefinition ?? definitionManifest.routes
|
|
58
|
+
routes: mergeRouteDefinitions(routesFromDefinition ?? definitionManifest.routes, manifestCandidate.routes),
|
|
50
59
|
views: viewsFromDefinition ?? definitionManifest.views ?? manifestCandidate.views ?? [],
|
|
51
|
-
jobs: definitionManifest.jobs
|
|
60
|
+
jobs: mergeJobDefinitions(definitionManifest.jobs, manifestCandidate.jobs),
|
|
52
61
|
events: definitionManifest.events ?? manifestCandidate.events ?? [],
|
|
53
62
|
services: definitionManifest.services ?? manifestCandidate.services ?? [],
|
|
54
63
|
init: definitionManifest.init ?? manifestCandidate.init,
|
|
55
|
-
dispose: definitionManifest.dispose ?? manifestCandidate.dispose
|
|
64
|
+
dispose: definitionManifest.dispose ?? manifestCandidate.dispose,
|
|
56
65
|
};
|
|
57
66
|
}
|
|
58
67
|
const validation = moduleManifestSchema.safeParse(manifestCandidate);
|
|
@@ -62,7 +71,7 @@ export async function loadBackendModuleManifest(options) {
|
|
|
62
71
|
.join('; ');
|
|
63
72
|
diagnostics.push({
|
|
64
73
|
severity: 'error',
|
|
65
|
-
message: `[webstir-backend] module manifest validation failed (${problems}). Falling back to defaults
|
|
74
|
+
message: `[webstir-backend] module manifest validation failed (${problems}). Falling back to defaults.`,
|
|
66
75
|
});
|
|
67
76
|
return {
|
|
68
77
|
contractVersion: CONTRACT_VERSION,
|
|
@@ -74,7 +83,7 @@ export async function loadBackendModuleManifest(options) {
|
|
|
74
83
|
views: [],
|
|
75
84
|
jobs: [],
|
|
76
85
|
events: [],
|
|
77
|
-
services: []
|
|
86
|
+
services: [],
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
const manifest = validation.data;
|
|
@@ -82,23 +91,26 @@ export async function loadBackendModuleManifest(options) {
|
|
|
82
91
|
const normalizePath = (p) => {
|
|
83
92
|
let s = typeof p === 'string' ? p : '';
|
|
84
93
|
if (!s.startsWith('/'))
|
|
85
|
-
s =
|
|
94
|
+
s = `/${s}`;
|
|
86
95
|
s = s.replace(/\/+/, '/');
|
|
87
96
|
if (s.length > 1 && s.endsWith('/'))
|
|
88
97
|
s = s.slice(0, -1);
|
|
89
98
|
return s;
|
|
90
99
|
};
|
|
91
100
|
const seen = new Map();
|
|
92
|
-
for (const
|
|
93
|
-
const method = typeof
|
|
94
|
-
const pathKey = normalizePath(
|
|
101
|
+
for (const route of (manifest.routes ?? [])) {
|
|
102
|
+
const method = typeof route.method === 'string' ? route.method.toUpperCase() : '';
|
|
103
|
+
const pathKey = normalizePath(route.path);
|
|
95
104
|
const key = `${method} ${pathKey}`;
|
|
96
105
|
seen.set(key, (seen.get(key) ?? 0) + 1);
|
|
97
106
|
}
|
|
98
107
|
const dups = Array.from(seen.entries()).filter(([, count]) => count > 1);
|
|
99
108
|
if (dups.length > 0) {
|
|
100
109
|
const list = dups.map(([k, c]) => `${k} (${c}x)`).join(', ');
|
|
101
|
-
diagnostics.push({
|
|
110
|
+
diagnostics.push({
|
|
111
|
+
severity: 'warn',
|
|
112
|
+
message: `[webstir-backend] duplicate route definitions: ${list}`,
|
|
113
|
+
});
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
116
|
catch {
|
|
@@ -107,7 +119,7 @@ export async function loadBackendModuleManifest(options) {
|
|
|
107
119
|
if (manifest.routes?.length && entryPoints.length === 0) {
|
|
108
120
|
diagnostics.push({
|
|
109
121
|
severity: 'warn',
|
|
110
|
-
message: '[webstir-backend] module manifest defines routes but no entry points were built. Ensure backend compilation produced handlers.'
|
|
122
|
+
message: '[webstir-backend] module manifest defines routes but no entry points were built. Ensure backend compilation produced handlers.',
|
|
111
123
|
});
|
|
112
124
|
}
|
|
113
125
|
try {
|
|
@@ -117,17 +129,20 @@ export async function loadBackendModuleManifest(options) {
|
|
|
117
129
|
if (jobs.length + events.length + services.length > 0) {
|
|
118
130
|
diagnostics.push({
|
|
119
131
|
severity: 'info',
|
|
120
|
-
message: `[webstir-backend] manifest jobs=${jobs.length} events=${events.length} services=${services.length}
|
|
132
|
+
message: `[webstir-backend] manifest jobs=${jobs.length} events=${events.length} services=${services.length}`,
|
|
121
133
|
});
|
|
122
134
|
}
|
|
123
|
-
const noSchedule = jobs.filter((
|
|
135
|
+
const noSchedule = jobs.filter((job) => typeof job.name === 'string' && (job.schedule === undefined || job.schedule === null));
|
|
124
136
|
if (noSchedule.length > 0) {
|
|
125
137
|
const MAX_LIST = 10;
|
|
126
|
-
const names = noSchedule
|
|
138
|
+
const names = noSchedule
|
|
139
|
+
.map((job) => job.name)
|
|
140
|
+
.slice(0, MAX_LIST)
|
|
141
|
+
.join(', ');
|
|
127
142
|
const omitted = noSchedule.length > MAX_LIST ? ` (+${noSchedule.length - MAX_LIST} more)` : '';
|
|
128
143
|
diagnostics.push({
|
|
129
144
|
severity: 'warn',
|
|
130
|
-
message: `[webstir-backend] jobs without schedules: ${names}${omitted}
|
|
145
|
+
message: `[webstir-backend] jobs without schedules: ${names}${omitted}`,
|
|
131
146
|
});
|
|
132
147
|
}
|
|
133
148
|
}
|
|
@@ -137,8 +152,13 @@ export async function loadBackendModuleManifest(options) {
|
|
|
137
152
|
try {
|
|
138
153
|
const routes = Array.isArray(manifest.routes) ? manifest.routes.length : 0;
|
|
139
154
|
const views = Array.isArray(manifest.views) ? manifest.views.length : 0;
|
|
140
|
-
const caps = Array.isArray(manifest.capabilities) && manifest.capabilities.length > 0
|
|
141
|
-
|
|
155
|
+
const caps = Array.isArray(manifest.capabilities) && manifest.capabilities.length > 0
|
|
156
|
+
? ` [${manifest.capabilities.join(', ')}]`
|
|
157
|
+
: '';
|
|
158
|
+
diagnostics.push({
|
|
159
|
+
severity: 'info',
|
|
160
|
+
message: `[webstir-backend] manifest routes=${routes} views=${views}${caps}`,
|
|
161
|
+
});
|
|
142
162
|
}
|
|
143
163
|
catch {
|
|
144
164
|
// ignore
|
|
@@ -150,7 +170,7 @@ async function loadModuleDefinition(buildRoot, diagnostics) {
|
|
|
150
170
|
path.join(buildRoot, 'module.js'),
|
|
151
171
|
path.join(buildRoot, 'module.mjs'),
|
|
152
172
|
path.join(buildRoot, 'module/index.js'),
|
|
153
|
-
path.join(buildRoot, 'module/index.mjs')
|
|
173
|
+
path.join(buildRoot, 'module/index.mjs'),
|
|
154
174
|
];
|
|
155
175
|
for (const fullPath of candidates) {
|
|
156
176
|
if (!existsSync(fullPath)) {
|
|
@@ -165,13 +185,13 @@ async function loadModuleDefinition(buildRoot, diagnostics) {
|
|
|
165
185
|
}
|
|
166
186
|
diagnostics.push({
|
|
167
187
|
severity: 'warn',
|
|
168
|
-
message: `[webstir-backend] module definition at ${fullPath} does not export a createModule() definition
|
|
188
|
+
message: `[webstir-backend] module definition at ${fullPath} does not export a createModule() definition.`,
|
|
169
189
|
});
|
|
170
190
|
}
|
|
171
191
|
catch (error) {
|
|
172
192
|
diagnostics.push({
|
|
173
193
|
severity: 'warn',
|
|
174
|
-
message: `[webstir-backend] failed to load module definition from ${fullPath}: ${error.message}
|
|
194
|
+
message: `[webstir-backend] failed to load module definition from ${fullPath}: ${error.message}`,
|
|
175
195
|
});
|
|
176
196
|
}
|
|
177
197
|
}
|
|
@@ -190,10 +210,11 @@ function extractModuleDefinition(exports) {
|
|
|
190
210
|
return undefined;
|
|
191
211
|
}
|
|
192
212
|
function isModuleDefinition(value) {
|
|
193
|
-
return typeof value === 'object' && value !== null && 'manifest' in value;
|
|
213
|
+
return (typeof value === 'object' && value !== null && 'manifest' in value);
|
|
194
214
|
}
|
|
195
215
|
function deriveModuleName(pkg, workspaceRoot) {
|
|
196
|
-
if (typeof pkg?.webstir?.moduleManifest?.name === 'string' &&
|
|
216
|
+
if (typeof pkg?.webstir?.moduleManifest?.name === 'string' &&
|
|
217
|
+
pkg.webstir.moduleManifest.name.length > 0) {
|
|
197
218
|
return pkg.webstir.moduleManifest.name;
|
|
198
219
|
}
|
|
199
220
|
if (typeof pkg?.name === 'string' && pkg.name.length > 0) {
|
|
@@ -202,7 +223,8 @@ function deriveModuleName(pkg, workspaceRoot) {
|
|
|
202
223
|
return `backend-module-${path.basename(workspaceRoot)}`;
|
|
203
224
|
}
|
|
204
225
|
function deriveModuleVersion(pkg) {
|
|
205
|
-
if (typeof pkg?.webstir?.moduleManifest?.version === 'string' &&
|
|
226
|
+
if (typeof pkg?.webstir?.moduleManifest?.version === 'string' &&
|
|
227
|
+
pkg.webstir.moduleManifest.version.length > 0) {
|
|
206
228
|
return pkg.webstir.moduleManifest.version;
|
|
207
229
|
}
|
|
208
230
|
if (typeof pkg?.version === 'string' && pkg.version.length > 0) {
|
|
@@ -210,6 +232,55 @@ function deriveModuleVersion(pkg) {
|
|
|
210
232
|
}
|
|
211
233
|
return '0.0.0';
|
|
212
234
|
}
|
|
235
|
+
function mergeRouteDefinitions(definitionRoutes, packageRoutes) {
|
|
236
|
+
const merged = Array.isArray(definitionRoutes) ? [...definitionRoutes] : [];
|
|
237
|
+
const seen = new Set(merged.map((route) => getRouteKey(route)).filter(Boolean));
|
|
238
|
+
for (const route of packageRoutes ?? []) {
|
|
239
|
+
const key = getRouteKey(route);
|
|
240
|
+
if (!key || seen.has(key)) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
merged.push(route);
|
|
244
|
+
seen.add(key);
|
|
245
|
+
}
|
|
246
|
+
return merged;
|
|
247
|
+
}
|
|
248
|
+
function mergeJobDefinitions(definitionJobs, packageJobs) {
|
|
249
|
+
const merged = Array.isArray(definitionJobs) ? [...definitionJobs] : [];
|
|
250
|
+
const seen = new Set(merged.map((job) => getJobKey(job)).filter(Boolean));
|
|
251
|
+
for (const job of packageJobs ?? []) {
|
|
252
|
+
const key = getJobKey(job);
|
|
253
|
+
if (!key || seen.has(key)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
merged.push(job);
|
|
257
|
+
seen.add(key);
|
|
258
|
+
}
|
|
259
|
+
return merged;
|
|
260
|
+
}
|
|
261
|
+
function getJobKey(job) {
|
|
262
|
+
return typeof job?.name === 'string' && job.name.length > 0 ? job.name : undefined;
|
|
263
|
+
}
|
|
264
|
+
function getRouteKey(route) {
|
|
265
|
+
const method = typeof route?.method === 'string' ? route.method.toUpperCase() : '';
|
|
266
|
+
const routePath = normalizeRoutePath(route?.path);
|
|
267
|
+
if (!method || !routePath) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return `${method} ${routePath}`;
|
|
271
|
+
}
|
|
272
|
+
function normalizeRoutePath(routePath) {
|
|
273
|
+
if (typeof routePath !== 'string' || routePath.length === 0) {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
let normalized = routePath;
|
|
277
|
+
if (!normalized.startsWith('/'))
|
|
278
|
+
normalized = `/${normalized}`;
|
|
279
|
+
normalized = normalized.replace(/\/+/g, '/');
|
|
280
|
+
if (normalized.length > 1 && normalized.endsWith('/'))
|
|
281
|
+
normalized = normalized.slice(0, -1);
|
|
282
|
+
return normalized;
|
|
283
|
+
}
|
|
213
284
|
export async function summarizeBuiltManifest(buildRoot) {
|
|
214
285
|
const definition = await loadModuleDefinition(buildRoot, []);
|
|
215
286
|
if (!definition || !definition.manifest) {
|
|
@@ -219,6 +290,6 @@ export async function summarizeBuiltManifest(buildRoot) {
|
|
|
219
290
|
return {
|
|
220
291
|
routes: Array.isArray(manifest.routes) ? manifest.routes.length : 0,
|
|
221
292
|
views: Array.isArray(manifest.views) ? manifest.views.length : 0,
|
|
222
|
-
capabilities: manifest.capabilities
|
|
293
|
+
capabilities: manifest.capabilities,
|
|
223
294
|
};
|
|
224
295
|
}
|
package/dist/provider.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { collectArtifacts, createBuildManifest } from './build/artifacts.js';
|
|
4
|
-
import { buildSupportFile, runBackendBuildPipeline } from './build/pipeline.js';
|
|
4
|
+
import { buildSupportFile, resolveBackendBundler, runBackendBuildPipeline, } from './build/pipeline.js';
|
|
5
5
|
import { loadBackendModuleManifest } from './manifest/pipeline.js';
|
|
6
6
|
import { createCacheReporter } from './cache/reporters.js';
|
|
7
7
|
import { normalizeLogLevel, filterDiagnostics } from './diagnostics/summary.js';
|
|
8
8
|
import { getBackendScaffoldAssets } from './scaffold/assets.js';
|
|
9
|
-
import { normalizeMode, resolveWorkspacePaths } from './workspace.js';
|
|
9
|
+
import { normalizeMode, resolveWorkspacePaths, resolveWorkspaceRoot } from './workspace.js';
|
|
10
10
|
import packageJson from '../package.json' with { type: 'json' };
|
|
11
11
|
const pkg = packageJson;
|
|
12
12
|
export const backendProvider = {
|
|
@@ -16,19 +16,32 @@ export const backendProvider = {
|
|
|
16
16
|
version: pkg.version ?? '0.0.0',
|
|
17
17
|
compatibility: {
|
|
18
18
|
minCliVersion: '0.1.0',
|
|
19
|
-
nodeRange: pkg.engines?.node ?? '>=20.18.1'
|
|
20
|
-
|
|
19
|
+
nodeRange: pkg.engines?.node ?? '>=20.18.1',
|
|
20
|
+
...(pkg.engines?.bun ? { notes: `Requires Bun ${pkg.engines.bun} at runtime.` } : {}),
|
|
21
|
+
},
|
|
21
22
|
},
|
|
22
23
|
resolveWorkspace(options) {
|
|
23
|
-
|
|
24
|
+
const workspaceRoot = resolveWorkspaceRoot({
|
|
25
|
+
workspaceRoot: options.workspaceRoot,
|
|
26
|
+
});
|
|
27
|
+
return resolveWorkspacePaths(workspaceRoot);
|
|
24
28
|
},
|
|
25
29
|
async build(options) {
|
|
26
|
-
const
|
|
30
|
+
const env = options.env ?? {};
|
|
31
|
+
const workspaceRoot = resolveWorkspaceRoot({
|
|
32
|
+
workspaceRoot: options.workspaceRoot,
|
|
33
|
+
env,
|
|
34
|
+
});
|
|
35
|
+
const paths = resolveWorkspacePaths(workspaceRoot);
|
|
27
36
|
const tsconfigPath = path.join(paths.sourceRoot, 'tsconfig.json');
|
|
28
37
|
const diagnostics = [];
|
|
29
38
|
const incremental = options.incremental === true;
|
|
30
|
-
const env = options.env ?? {};
|
|
31
39
|
const mode = normalizeMode(env.WEBSTIR_MODULE_MODE);
|
|
40
|
+
const bundler = resolveBackendBundler({
|
|
41
|
+
env,
|
|
42
|
+
incremental,
|
|
43
|
+
diagnostics,
|
|
44
|
+
});
|
|
32
45
|
console.info(`[webstir-backend] ${mode}:start`);
|
|
33
46
|
const { entryPoints, outputs, includePublishSourcemaps } = await runBackendBuildPipeline({
|
|
34
47
|
sourceRoot: paths.sourceRoot,
|
|
@@ -37,7 +50,8 @@ export const backendProvider = {
|
|
|
37
50
|
mode,
|
|
38
51
|
env,
|
|
39
52
|
incremental,
|
|
40
|
-
diagnostics
|
|
53
|
+
diagnostics,
|
|
54
|
+
bundler,
|
|
41
55
|
});
|
|
42
56
|
const artifacts = await collectArtifacts(paths.buildRoot, includePublishSourcemaps);
|
|
43
57
|
const envSource = path.join(paths.sourceRoot, 'env.ts');
|
|
@@ -50,7 +64,8 @@ export const backendProvider = {
|
|
|
50
64
|
tsconfigPath,
|
|
51
65
|
mode,
|
|
52
66
|
env,
|
|
53
|
-
diagnostics
|
|
67
|
+
diagnostics,
|
|
68
|
+
bundler,
|
|
54
69
|
});
|
|
55
70
|
}
|
|
56
71
|
catch {
|
|
@@ -58,19 +73,22 @@ export const backendProvider = {
|
|
|
58
73
|
}
|
|
59
74
|
}
|
|
60
75
|
const moduleManifest = await loadBackendModuleManifest({
|
|
61
|
-
workspaceRoot
|
|
76
|
+
workspaceRoot,
|
|
62
77
|
buildRoot: paths.buildRoot,
|
|
63
78
|
entryPoints,
|
|
64
|
-
diagnostics
|
|
79
|
+
diagnostics,
|
|
65
80
|
});
|
|
66
81
|
const manifest = createBuildManifest(paths.buildRoot, artifacts, diagnostics, moduleManifest);
|
|
67
82
|
console.info(`[webstir-backend] ${mode}:complete (entries=${manifest.entryPoints.length})`);
|
|
68
|
-
diagnostics.push({
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
severity: 'info',
|
|
85
|
+
message: `[webstir-backend] ${mode}:built entries=${manifest.entryPoints.length}`,
|
|
86
|
+
});
|
|
69
87
|
const cacheReporter = createCacheReporter({
|
|
70
|
-
workspaceRoot
|
|
88
|
+
workspaceRoot,
|
|
71
89
|
buildRoot: paths.buildRoot,
|
|
72
90
|
env,
|
|
73
|
-
diagnostics
|
|
91
|
+
diagnostics,
|
|
74
92
|
});
|
|
75
93
|
try {
|
|
76
94
|
await cacheReporter.diffOutputs(outputs, mode);
|
|
@@ -91,11 +109,11 @@ export const backendProvider = {
|
|
|
91
109
|
artifacts,
|
|
92
110
|
manifest: {
|
|
93
111
|
...manifest,
|
|
94
|
-
diagnostics: filteredDiagnostics
|
|
95
|
-
}
|
|
112
|
+
diagnostics: filteredDiagnostics,
|
|
113
|
+
},
|
|
96
114
|
};
|
|
97
115
|
},
|
|
98
116
|
async getScaffoldAssets() {
|
|
99
117
|
return await getBackendScaffoldAssets();
|
|
100
|
-
}
|
|
118
|
+
},
|
|
101
119
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type SessionCookieConfig, type SessionStore } from './session.js';
|
|
2
|
+
export type { RouteHandlerResult } from './core.js';
|
|
3
|
+
export interface RuntimeLogger {
|
|
4
|
+
child(bindings: Record<string, unknown>): RuntimeLogger;
|
|
5
|
+
info(value: unknown, message?: string): void;
|
|
6
|
+
warn(value: unknown, message?: string): void;
|
|
7
|
+
error(value: unknown, message?: string): void;
|
|
8
|
+
}
|
|
9
|
+
export interface MetricsTracker {
|
|
10
|
+
record(metric: {
|
|
11
|
+
method: string;
|
|
12
|
+
route: string;
|
|
13
|
+
status: number;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
}): void;
|
|
16
|
+
snapshot(): unknown;
|
|
17
|
+
}
|
|
18
|
+
export interface BunRuntimeEnvLike<TAuthConfig = unknown, TMetricsConfig = unknown> {
|
|
19
|
+
NODE_ENV: string;
|
|
20
|
+
PORT: number;
|
|
21
|
+
auth: TAuthConfig;
|
|
22
|
+
metrics: TMetricsConfig;
|
|
23
|
+
http: {
|
|
24
|
+
bodyLimitBytes: number;
|
|
25
|
+
};
|
|
26
|
+
sessions: SessionCookieConfig;
|
|
27
|
+
}
|
|
28
|
+
export interface BunRuntimeBootstrapOptions<TEnv extends BunRuntimeEnvLike = BunRuntimeEnvLike, TLogger extends RuntimeLogger = RuntimeLogger, TSession extends Record<string, unknown> = Record<string, unknown>, TAuth = unknown, TMetricsTracker extends MetricsTracker = MetricsTracker> {
|
|
29
|
+
importMetaUrl: string;
|
|
30
|
+
moduleCandidates?: readonly string[];
|
|
31
|
+
loadEnv(): TEnv;
|
|
32
|
+
resolveWorkspaceRoot(): string;
|
|
33
|
+
resolveRequestAuth(request: Request, auth: TEnv['auth'], logger?: {
|
|
34
|
+
warn?(message: string, metadata?: Record<string, unknown>): void;
|
|
35
|
+
}): Promise<TAuth | undefined>;
|
|
36
|
+
createBaseLogger(env: TEnv): TLogger;
|
|
37
|
+
createMetricsTracker(config: TEnv['metrics']): TMetricsTracker;
|
|
38
|
+
sessionStore: SessionStore<TSession>;
|
|
39
|
+
}
|
|
40
|
+
export interface DefaultBunBackendBootstrapOptions<TEnv extends BunRuntimeEnvLike = BunRuntimeEnvLike, TLogger extends RuntimeLogger = RuntimeLogger, TSession extends Record<string, unknown> = Record<string, unknown>, TAuth = unknown, TMetricsTracker extends MetricsTracker = MetricsTracker> {
|
|
41
|
+
importMetaUrl: string;
|
|
42
|
+
loadEnv(): TEnv;
|
|
43
|
+
moduleCandidates?: readonly string[];
|
|
44
|
+
resolveWorkspaceRoot?: () => string;
|
|
45
|
+
resolveRequestAuth?: BunRuntimeBootstrapOptions<TEnv, TLogger, TSession, TAuth, TMetricsTracker>['resolveRequestAuth'];
|
|
46
|
+
createBaseLogger?: (env: TEnv) => TLogger;
|
|
47
|
+
createMetricsTracker?: (config: TEnv['metrics']) => TMetricsTracker;
|
|
48
|
+
sessionStore?: SessionStore<TSession>;
|
|
49
|
+
}
|
|
50
|
+
export declare function startBunBackend<TEnv extends BunRuntimeEnvLike, TLogger extends RuntimeLogger, TSession extends Record<string, unknown>, TAuth, TMetricsTracker extends MetricsTracker>(options: BunRuntimeBootstrapOptions<TEnv, TLogger, TSession, TAuth, TMetricsTracker>): Promise<void>;
|
|
51
|
+
export declare function createDefaultBunBackendBootstrap<TEnv extends BunRuntimeEnvLike, TLogger extends RuntimeLogger = RuntimeLogger, TSession extends Record<string, unknown> = Record<string, unknown>, TAuth = unknown, TMetricsTracker extends MetricsTracker = MetricsTracker>(options: DefaultBunBackendBootstrapOptions<TEnv, TLogger, TSession, TAuth, TMetricsTracker>): BunRuntimeBootstrapOptions<TEnv, TLogger, TSession, TAuth, TMetricsTracker>;
|