@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.
Files changed (123) hide show
  1. package/README.md +106 -79
  2. package/dist/add.d.ts +59 -0
  3. package/dist/add.js +626 -0
  4. package/dist/build/artifacts.d.ts +115 -1
  5. package/dist/build/artifacts.js +4 -4
  6. package/dist/build/entries.js +1 -1
  7. package/dist/build/pipeline.d.ts +33 -1
  8. package/dist/build/pipeline.js +307 -65
  9. package/dist/cache/diff.js +9 -8
  10. package/dist/cache/reporters.js +1 -1
  11. package/dist/deploy-cli.d.ts +2 -0
  12. package/dist/deploy-cli.js +86 -0
  13. package/dist/diagnostics/summary.js +2 -2
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +4 -0
  16. package/dist/manifest/pipeline.js +103 -32
  17. package/dist/provider.js +35 -17
  18. package/dist/runtime/bun.d.ts +51 -0
  19. package/dist/runtime/bun.js +499 -0
  20. package/dist/runtime/core.d.ts +141 -0
  21. package/dist/runtime/core.js +316 -0
  22. package/dist/runtime/deploy-backend.d.ts +20 -0
  23. package/dist/runtime/deploy-backend.js +175 -0
  24. package/dist/runtime/deploy-shared.d.ts +43 -0
  25. package/dist/runtime/deploy-shared.js +75 -0
  26. package/dist/runtime/deploy-static.d.ts +2 -0
  27. package/dist/runtime/deploy-static.js +161 -0
  28. package/dist/runtime/deploy.d.ts +3 -0
  29. package/dist/runtime/deploy.js +91 -0
  30. package/dist/runtime/forms.d.ts +73 -0
  31. package/dist/runtime/forms.js +236 -0
  32. package/dist/runtime/request-hooks.d.ts +47 -0
  33. package/dist/runtime/request-hooks.js +102 -0
  34. package/dist/runtime/session-metadata.d.ts +13 -0
  35. package/dist/runtime/session-metadata.js +98 -0
  36. package/dist/runtime/session-runtime.d.ts +28 -0
  37. package/dist/runtime/session-runtime.js +180 -0
  38. package/dist/runtime/session.d.ts +83 -0
  39. package/dist/runtime/session.js +396 -0
  40. package/dist/runtime/views.d.ts +74 -0
  41. package/dist/runtime/views.js +221 -0
  42. package/dist/scaffold/assets.js +25 -21
  43. package/dist/testing/context.js +1 -1
  44. package/dist/testing/index.d.ts +1 -1
  45. package/dist/testing/index.js +100 -56
  46. package/dist/utils/bun.d.ts +2 -0
  47. package/dist/utils/bun.js +13 -0
  48. package/dist/watch.d.ts +13 -1
  49. package/dist/watch.js +345 -97
  50. package/dist/workspace.d.ts +8 -0
  51. package/dist/workspace.js +44 -3
  52. package/package.json +49 -14
  53. package/scripts/publish.sh +2 -92
  54. package/scripts/smoke.mjs +282 -107
  55. package/scripts/update-contract.sh +12 -10
  56. package/src/add.ts +964 -0
  57. package/src/build/artifacts.ts +49 -46
  58. package/src/build/entries.ts +12 -12
  59. package/src/build/pipeline.ts +779 -403
  60. package/src/cache/diff.ts +111 -105
  61. package/src/cache/reporters.ts +26 -26
  62. package/src/deploy-cli.ts +111 -0
  63. package/src/diagnostics/summary.ts +28 -22
  64. package/src/index.ts +11 -0
  65. package/src/manifest/pipeline.ts +328 -215
  66. package/src/provider.ts +115 -98
  67. package/src/runtime/bun.ts +793 -0
  68. package/src/runtime/core.ts +598 -0
  69. package/src/runtime/deploy-backend.ts +239 -0
  70. package/src/runtime/deploy-shared.ts +136 -0
  71. package/src/runtime/deploy-static.ts +191 -0
  72. package/src/runtime/deploy.ts +143 -0
  73. package/src/runtime/forms.ts +364 -0
  74. package/src/runtime/request-hooks.ts +165 -0
  75. package/src/runtime/session-metadata.ts +135 -0
  76. package/src/runtime/session-runtime.ts +267 -0
  77. package/src/runtime/session.ts +642 -0
  78. package/src/runtime/views.ts +385 -0
  79. package/src/scaffold/assets.ts +77 -73
  80. package/src/testing/context.js +8 -9
  81. package/src/testing/context.ts +9 -9
  82. package/src/testing/index.d.ts +14 -3
  83. package/src/testing/index.js +254 -175
  84. package/src/testing/index.ts +298 -195
  85. package/src/testing/types.d.ts +18 -19
  86. package/src/testing/types.ts +18 -18
  87. package/src/utils/bun.ts +26 -0
  88. package/src/watch.ts +503 -99
  89. package/src/workspace.ts +59 -3
  90. package/templates/backend/.env.example +15 -0
  91. package/templates/backend/auth/adapter.ts +335 -36
  92. package/templates/backend/db/connection.ts +190 -65
  93. package/templates/backend/db/migrate.ts +149 -43
  94. package/templates/backend/db/types.d.ts +1 -1
  95. package/templates/backend/env.ts +132 -20
  96. package/templates/backend/functions/hello/index.ts +1 -2
  97. package/templates/backend/index.ts +15 -508
  98. package/templates/backend/jobs/nightly/index.ts +1 -1
  99. package/templates/backend/jobs/runtime.ts +24 -11
  100. package/templates/backend/jobs/scheduler.ts +208 -46
  101. package/templates/backend/module.ts +227 -13
  102. package/templates/backend/observability/logger.ts +2 -12
  103. package/templates/backend/observability/metrics.ts +8 -5
  104. package/templates/backend/session/sqlite.ts +152 -0
  105. package/templates/backend/session/store.ts +45 -0
  106. package/templates/backend/tsconfig.json +1 -1
  107. package/tests/add.test.js +327 -0
  108. package/tests/authAdapter.test.js +315 -0
  109. package/tests/bundlerParity.test.js +217 -0
  110. package/tests/cacheReporter.test.js +10 -10
  111. package/tests/dbConnection.test.js +209 -0
  112. package/tests/deploy.test.js +357 -0
  113. package/tests/envLoader.test.js +271 -17
  114. package/tests/integration.test.js +2432 -3
  115. package/tests/jobsScheduler.test.js +253 -0
  116. package/tests/manifest.test.js +287 -12
  117. package/tests/migrationRunner.test.js +249 -0
  118. package/tests/sessionScaffoldStore.test.js +752 -0
  119. package/tests/sessionStore.test.js +490 -0
  120. package/tests/testing.test.js +252 -0
  121. package/tests/watch.test.js +192 -32
  122. package/tsconfig.json +3 -10
  123. package/templates/backend/server/fastify.ts +0 -288
package/src/cache/diff.ts CHANGED
@@ -1,128 +1,134 @@
1
1
  import path from 'node:path';
2
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { mkdir } from 'node:fs/promises';
3
3
 
4
4
  import type { ModuleManifest, ModuleDiagnostic } from '@webstir-io/module-contract';
5
5
 
6
6
  import type { BackendBuildMode } from '../workspace.js';
7
+ import { readTextFile, writeTextFile } from '../utils/bun.js';
8
+
9
+ type DigestRouteLike = { readonly method?: string; readonly path?: string };
10
+ type DigestViewLike = { readonly path?: string };
7
11
 
8
12
  export async function persistAndDiffOutputs(
9
- workspaceRoot: string,
10
- _buildRoot: string,
11
- outputs: Record<string, number> | undefined,
12
- env: Record<string, string | undefined>,
13
- diagnostics: ModuleDiagnostic[],
14
- mode: BackendBuildMode
13
+ workspaceRoot: string,
14
+ _buildRoot: string,
15
+ outputs: Record<string, number> | undefined,
16
+ env: Record<string, string | undefined>,
17
+ diagnostics: ModuleDiagnostic[],
18
+ mode: BackendBuildMode,
15
19
  ): Promise<void> {
16
- if (!outputs) return;
20
+ if (!outputs) return;
21
+ try {
22
+ const diagMax = resolveDiagMax(env);
23
+ const webstirDir = path.join(workspaceRoot, '.webstir');
24
+ const cachePath = path.join(webstirDir, 'backend-outputs.json');
25
+ await mkdir(webstirDir, { recursive: true });
26
+
27
+ let previous: Record<string, number> = {};
17
28
  try {
18
- const diagMax = resolveDiagMax(env);
19
- const webstirDir = path.join(workspaceRoot, '.webstir');
20
- const cachePath = path.join(webstirDir, 'backend-outputs.json');
21
- await mkdir(webstirDir, { recursive: true });
22
-
23
- let previous: Record<string, number> = {};
24
- try {
25
- const raw = await readFile(cachePath, 'utf8');
26
- previous = JSON.parse(raw) as Record<string, number>;
27
- } catch {
28
- // first run or unreadable cache
29
- }
30
-
31
- const changed: string[] = [];
32
- for (const [rel, bytes] of Object.entries(outputs)) {
33
- if (previous[rel] !== bytes) {
34
- changed.push(rel);
35
- }
36
- }
37
- const removed = Object.keys(previous).filter((rel) => outputs[rel] === undefined);
38
-
39
- if (changed.length + removed.length > 0) {
40
- const list = changed.slice(0, diagMax).join(', ');
41
- const omitted = changed.length > diagMax ? ` (+${changed.length - diagMax} more)` : '';
42
- const removedInfo = removed.length > 0 ? `, removed=${removed.length}` : '';
43
- diagnostics.push({
44
- severity: 'info',
45
- message: `[webstir-backend] ${mode}:changed ${changed.length} file(s): ${list}${omitted}${removedInfo}`
46
- });
47
- }
48
-
49
- await writeFile(cachePath, JSON.stringify(outputs, null, 2), 'utf8');
29
+ const raw = await readTextFile(cachePath);
30
+ previous = JSON.parse(raw) as Record<string, number>;
50
31
  } catch {
51
- // ignore cache errors
32
+ // first run or unreadable cache
33
+ }
34
+
35
+ const changed: string[] = [];
36
+ for (const [rel, bytes] of Object.entries(outputs)) {
37
+ if (previous[rel] !== bytes) {
38
+ changed.push(rel);
39
+ }
52
40
  }
41
+ const removed = Object.keys(previous).filter((rel) => outputs[rel] === undefined);
42
+
43
+ if (changed.length + removed.length > 0) {
44
+ const list = changed.slice(0, diagMax).join(', ');
45
+ const omitted = changed.length > diagMax ? ` (+${changed.length - diagMax} more)` : '';
46
+ const removedInfo = removed.length > 0 ? `, removed=${removed.length}` : '';
47
+ diagnostics.push({
48
+ severity: 'info',
49
+ message: `[webstir-backend] ${mode}:changed ${changed.length} file(s): ${list}${omitted}${removedInfo}`,
50
+ });
51
+ }
52
+
53
+ await writeTextFile(cachePath, JSON.stringify(outputs, null, 2));
54
+ } catch {
55
+ // ignore cache errors
56
+ }
53
57
  }
54
58
 
55
59
  export async function persistAndDiffManifest(
56
- workspaceRoot: string,
57
- manifest: ModuleManifest,
58
- env: Record<string, string | undefined>,
59
- diagnostics: ModuleDiagnostic[]
60
+ workspaceRoot: string,
61
+ manifest: ModuleManifest,
62
+ env: Record<string, string | undefined>,
63
+ diagnostics: ModuleDiagnostic[],
60
64
  ): Promise<void> {
65
+ try {
66
+ const diagMax = resolveDiagMax(env);
67
+ const webstirDir = path.join(workspaceRoot, '.webstir');
68
+ const cachePath = path.join(webstirDir, 'backend-manifest-digest.json');
69
+ await mkdir(webstirDir, { recursive: true });
70
+
71
+ const routeKeys = Array.isArray(manifest.routes)
72
+ ? (manifest.routes as readonly DigestRouteLike[]).map(
73
+ (route) => `${(route.method ?? '').toUpperCase()} ${route.path ?? ''}`,
74
+ )
75
+ : [];
76
+ const viewPaths = Array.isArray(manifest.views)
77
+ ? (manifest.views as readonly DigestViewLike[]).map((view) => `${view.path ?? ''}`)
78
+ : [];
79
+ const caps = Array.isArray(manifest.capabilities) ? manifest.capabilities : [];
80
+
81
+ type Digest = { routes: string[]; views: string[]; capabilities: string[] };
82
+ let previous: Digest | undefined;
61
83
  try {
62
- const diagMax = resolveDiagMax(env);
63
- const webstirDir = path.join(workspaceRoot, '.webstir');
64
- const cachePath = path.join(webstirDir, 'backend-manifest-digest.json');
65
- await mkdir(webstirDir, { recursive: true });
66
-
67
- const routeKeys = Array.isArray(manifest.routes)
68
- ? (manifest.routes as any[]).map((r) => `${(r.method ?? '').toUpperCase()} ${r.path ?? ''}`)
69
- : [];
70
- const viewPaths = Array.isArray(manifest.views)
71
- ? (manifest.views as any[]).map((v) => `${v.path ?? ''}`)
72
- : [];
73
- const caps = Array.isArray(manifest.capabilities) ? manifest.capabilities : [];
74
-
75
- type Digest = { routes: string[]; views: string[]; capabilities: string[] };
76
- let previous: Digest | undefined;
77
- try {
78
- const raw = await readFile(cachePath, 'utf8');
79
- previous = JSON.parse(raw) as Digest;
80
- } catch {
81
- // first run; no diff
82
- }
84
+ const raw = await readTextFile(cachePath);
85
+ previous = JSON.parse(raw) as Digest;
86
+ } catch {
87
+ // first run; no diff
88
+ }
83
89
 
84
- if (previous) {
85
- const prevRoutes = new Set(previous.routes);
86
- const prevViews = new Set(previous.views);
87
- const nextRoutes = new Set(routeKeys);
88
- const nextViews = new Set(viewPaths);
89
-
90
- const addedRoutes: string[] = [];
91
- const removedRoutes: string[] = [];
92
- const addedViews: string[] = [];
93
- const removedViews: string[] = [];
94
-
95
- for (const r of nextRoutes) if (!prevRoutes.has(r)) addedRoutes.push(r);
96
- for (const r of prevRoutes) if (!nextRoutes.has(r)) removedRoutes.push(r);
97
- for (const v of nextViews) if (!prevViews.has(v)) addedViews.push(v);
98
- for (const v of prevViews) if (!nextViews.has(v)) removedViews.push(v);
99
-
100
- if (addedRoutes.length + removedRoutes.length + addedViews.length + removedViews.length > 0) {
101
- const list = (items: string[]) => items.slice(0, diagMax).join(', ');
102
- const routeDelta = `routes +${addedRoutes.length}/-${removedRoutes.length}`;
103
- const viewDelta = `views +${addedViews.length}/-${removedViews.length}`;
104
- let msg = `[webstir-backend] manifest changed: ${routeDelta}; ${viewDelta}`;
105
- const details: string[] = [];
106
- if (addedRoutes.length > 0) details.push(`added routes: ${list(addedRoutes)}`);
107
- if (removedRoutes.length > 0) details.push(`removed routes: ${list(removedRoutes)}`);
108
- if (addedViews.length > 0) details.push(`added views: ${list(addedViews)}`);
109
- if (removedViews.length > 0) details.push(`removed views: ${list(removedViews)}`);
110
- if (details.length > 0) {
111
- msg += ` — ${details.join(' | ')}`;
112
- }
113
- diagnostics.push({ severity: 'info', message: msg });
114
- }
90
+ if (previous) {
91
+ const prevRoutes = new Set(previous.routes);
92
+ const prevViews = new Set(previous.views);
93
+ const nextRoutes = new Set(routeKeys);
94
+ const nextViews = new Set(viewPaths);
95
+
96
+ const addedRoutes: string[] = [];
97
+ const removedRoutes: string[] = [];
98
+ const addedViews: string[] = [];
99
+ const removedViews: string[] = [];
100
+
101
+ for (const r of nextRoutes) if (!prevRoutes.has(r)) addedRoutes.push(r);
102
+ for (const r of prevRoutes) if (!nextRoutes.has(r)) removedRoutes.push(r);
103
+ for (const v of nextViews) if (!prevViews.has(v)) addedViews.push(v);
104
+ for (const v of prevViews) if (!nextViews.has(v)) removedViews.push(v);
105
+
106
+ if (addedRoutes.length + removedRoutes.length + addedViews.length + removedViews.length > 0) {
107
+ const list = (items: string[]) => items.slice(0, diagMax).join(', ');
108
+ const routeDelta = `routes +${addedRoutes.length}/-${removedRoutes.length}`;
109
+ const viewDelta = `views +${addedViews.length}/-${removedViews.length}`;
110
+ let msg = `[webstir-backend] manifest changed: ${routeDelta}; ${viewDelta}`;
111
+ const details: string[] = [];
112
+ if (addedRoutes.length > 0) details.push(`added routes: ${list(addedRoutes)}`);
113
+ if (removedRoutes.length > 0) details.push(`removed routes: ${list(removedRoutes)}`);
114
+ if (addedViews.length > 0) details.push(`added views: ${list(addedViews)}`);
115
+ if (removedViews.length > 0) details.push(`removed views: ${list(removedViews)}`);
116
+ if (details.length > 0) {
117
+ msg += ` — ${details.join(' | ')}`;
115
118
  }
116
-
117
- const digest: Digest = { routes: routeKeys, views: viewPaths, capabilities: caps };
118
- await writeFile(cachePath, JSON.stringify(digest, null, 2), 'utf8');
119
- } catch {
120
- // ignore cache errors
119
+ diagnostics.push({ severity: 'info', message: msg });
120
+ }
121
121
  }
122
+
123
+ const digest: Digest = { routes: routeKeys, views: viewPaths, capabilities: caps };
124
+ await writeTextFile(cachePath, JSON.stringify(digest, null, 2));
125
+ } catch {
126
+ // ignore cache errors
127
+ }
122
128
  }
123
129
 
124
130
  function resolveDiagMax(env: Record<string, string | undefined>, fallback = 50): number {
125
- const raw = env?.WEBSTIR_BACKEND_DIAG_MAX;
126
- const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
127
- return Number.isFinite(n) && n > 0 ? n : fallback;
131
+ const raw = env?.WEBSTIR_BACKEND_DIAG_MAX;
132
+ const n = typeof raw === 'string' ? parseInt(raw, 10) : NaN;
133
+ return Number.isFinite(n) && n > 0 ? n : fallback;
128
134
  }
@@ -4,38 +4,38 @@ import type { BackendBuildMode } from '../workspace.js';
4
4
  import { persistAndDiffManifest, persistAndDiffOutputs } from './diff.js';
5
5
 
6
6
  export interface CacheReporter {
7
- readonly diffOutputs: (
8
- outputs: Record<string, number> | undefined,
9
- mode: BackendBuildMode
10
- ) => Promise<void>;
11
- readonly diffManifest: (manifest: ModuleManifest) => Promise<void>;
7
+ readonly diffOutputs: (
8
+ outputs: Record<string, number> | undefined,
9
+ mode: BackendBuildMode,
10
+ ) => Promise<void>;
11
+ readonly diffManifest: (manifest: ModuleManifest) => Promise<void>;
12
12
  }
13
13
 
14
14
  export function createCacheReporter(options: {
15
- readonly workspaceRoot: string;
16
- readonly buildRoot: string;
17
- readonly env: Record<string, string | undefined>;
18
- readonly diagnostics: ModuleDiagnostic[];
15
+ readonly workspaceRoot: string;
16
+ readonly buildRoot: string;
17
+ readonly env: Record<string, string | undefined>;
18
+ readonly diagnostics: ModuleDiagnostic[];
19
19
  }): CacheReporter {
20
- const { workspaceRoot, buildRoot, env, diagnostics } = options;
21
- const diagnosticsTarget = shouldLogCacheDiffs(env) ? diagnostics : [];
20
+ const { workspaceRoot, buildRoot, env, diagnostics } = options;
21
+ const diagnosticsTarget = shouldLogCacheDiffs(env) ? diagnostics : [];
22
22
 
23
- return {
24
- async diffOutputs(outputs, mode) {
25
- await persistAndDiffOutputs(workspaceRoot, buildRoot, outputs, env, diagnosticsTarget, mode);
26
- },
27
- async diffManifest(manifest) {
28
- await persistAndDiffManifest(workspaceRoot, manifest, env, diagnosticsTarget);
29
- }
30
- };
23
+ return {
24
+ async diffOutputs(outputs, mode) {
25
+ await persistAndDiffOutputs(workspaceRoot, buildRoot, outputs, env, diagnosticsTarget, mode);
26
+ },
27
+ async diffManifest(manifest) {
28
+ await persistAndDiffManifest(workspaceRoot, manifest, env, diagnosticsTarget);
29
+ },
30
+ };
31
31
  }
32
32
 
33
33
  function shouldLogCacheDiffs(env: Record<string, string | undefined>): boolean {
34
- const raw = env?.WEBSTIR_BACKEND_CACHE_LOG;
35
- if (!raw) return true;
36
- const normalized = raw.trim().toLowerCase();
37
- if (['off', '0', 'false', 'quiet', 'silent', 'skip'].includes(normalized)) {
38
- return false;
39
- }
40
- return true;
34
+ const raw = env?.WEBSTIR_BACKEND_CACHE_LOG;
35
+ if (!raw) return true;
36
+ const normalized = raw.trim().toLowerCase();
37
+ if (['off', '0', 'false', 'quiet', 'silent', 'skip'].includes(normalized)) {
38
+ return false;
39
+ }
40
+ return true;
41
41
  }
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import path from 'node:path';
4
+
5
+ import { startPublishedWorkspaceServer } from './runtime/deploy.js';
6
+
7
+ async function main(argv: readonly string[]): Promise<void> {
8
+ const args = parseArgs(argv);
9
+ if (args.help) {
10
+ printHelp();
11
+ return;
12
+ }
13
+
14
+ const workspaceRoot = path.resolve(args.workspace ?? process.cwd());
15
+ const server = await startPublishedWorkspaceServer({
16
+ workspaceRoot,
17
+ host: args.host,
18
+ port: args.port,
19
+ });
20
+
21
+ const shutdown = async (signal: NodeJS.Signals): Promise<void> => {
22
+ process.stderr.write(`[webstir-backend-deploy] received ${signal}, stopping.\n`);
23
+ await server.stop();
24
+ process.exit(0);
25
+ };
26
+
27
+ process.on('SIGINT', () => void shutdown('SIGINT'));
28
+ process.on('SIGTERM', () => void shutdown('SIGTERM'));
29
+
30
+ process.stdout.write(
31
+ `[webstir-backend-deploy] serving ${server.mode} workspace at ${server.origin}\n`,
32
+ );
33
+
34
+ await new Promise<void>(() => {
35
+ // Keep the process alive until it receives a signal.
36
+ });
37
+ }
38
+
39
+ function parseArgs(argv: readonly string[]): {
40
+ readonly workspace?: string;
41
+ readonly host?: string;
42
+ readonly port?: number;
43
+ readonly help: boolean;
44
+ } {
45
+ let workspace: string | undefined;
46
+ let host: string | undefined;
47
+ let port: number | undefined;
48
+ let help = false;
49
+
50
+ for (let index = 0; index < argv.length; index += 1) {
51
+ const arg = argv[index];
52
+ if (!arg) {
53
+ continue;
54
+ }
55
+
56
+ if (arg === '--help' || arg === '-h') {
57
+ help = true;
58
+ continue;
59
+ }
60
+
61
+ if (arg === '--workspace') {
62
+ const next = argv[index + 1];
63
+ if (!next) {
64
+ throw new Error('Missing value for --workspace.');
65
+ }
66
+ workspace = next;
67
+ index += 1;
68
+ continue;
69
+ }
70
+
71
+ if (arg === '--host') {
72
+ const next = argv[index + 1];
73
+ if (!next) {
74
+ throw new Error('Missing value for --host.');
75
+ }
76
+ host = next;
77
+ index += 1;
78
+ continue;
79
+ }
80
+
81
+ if (arg === '--port') {
82
+ const next = argv[index + 1];
83
+ if (!next) {
84
+ throw new Error('Missing value for --port.');
85
+ }
86
+ const parsed = Number(next);
87
+ if (!Number.isInteger(parsed) || parsed <= 0) {
88
+ throw new Error(`Invalid value for --port: ${next}`);
89
+ }
90
+ port = parsed;
91
+ index += 1;
92
+ continue;
93
+ }
94
+
95
+ throw new Error(`Unknown argument: ${arg}`);
96
+ }
97
+
98
+ return { workspace, host, port, help };
99
+ }
100
+
101
+ function printHelp(): void {
102
+ process.stdout.write(`Usage: webstir-backend-deploy [--workspace <path>] [--host <host>] [--port <port>]
103
+
104
+ Starts a published api or full Webstir workspace with a single Bun entrypoint.
105
+ `);
106
+ }
107
+
108
+ main(process.argv.slice(2)).catch((error) => {
109
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
110
+ process.exit(1);
111
+ });
@@ -1,32 +1,38 @@
1
1
  import type { ModuleDiagnostic } from '@webstir-io/module-contract';
2
2
 
3
- export function pushEntryBucketSummary(diagnostics: ModuleDiagnostic[], entryPoints: readonly string[]): void {
4
- try {
5
- const server = entryPoints.filter(
6
- (p) => p === 'index.js' || /(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p)
7
- ).length;
8
- const functionsCount = entryPoints.filter((p) => p.startsWith('functions/')).length;
9
- const jobsCount = entryPoints.filter((p) => p.startsWith('jobs/')).length;
10
- diagnostics.push({
11
- severity: 'info',
12
- message: `[webstir-backend] entries by bucket: server=${server} functions=${functionsCount} jobs=${jobsCount}`
13
- });
14
- } catch {
15
- // best-effort only
16
- }
3
+ export function pushEntryBucketSummary(
4
+ diagnostics: ModuleDiagnostic[],
5
+ entryPoints: readonly string[],
6
+ ): void {
7
+ try {
8
+ const server = entryPoints.filter(
9
+ (p) => p === 'index.js' || (/(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p)),
10
+ ).length;
11
+ const functionsCount = entryPoints.filter((p) => p.startsWith('functions/')).length;
12
+ const jobsCount = entryPoints.filter((p) => p.startsWith('jobs/')).length;
13
+ diagnostics.push({
14
+ severity: 'info',
15
+ message: `[webstir-backend] entries by bucket: server=${server} functions=${functionsCount} jobs=${jobsCount}`,
16
+ });
17
+ } catch {
18
+ // best-effort only
19
+ }
17
20
  }
18
21
 
19
22
  type Severity = 'info' | 'warn' | 'error';
20
23
 
21
24
  export function normalizeLogLevel(value: unknown): Severity {
22
- if (typeof value !== 'string') return 'info';
23
- const v = value.toLowerCase();
24
- if (v === 'error' || v === 'warn' || v === 'info') return v;
25
- return 'info';
25
+ if (typeof value !== 'string') return 'info';
26
+ const v = value.toLowerCase();
27
+ if (v === 'error' || v === 'warn' || v === 'info') return v;
28
+ return 'info';
26
29
  }
27
30
 
28
- export function filterDiagnostics(list: readonly ModuleDiagnostic[], min: Severity): readonly ModuleDiagnostic[] {
29
- const rank = (s: Severity) => (s === 'error' ? 3 : s === 'warn' ? 2 : 1);
30
- const threshold = rank(min);
31
- return list.filter((d) => rank(d.severity as Severity) >= threshold);
31
+ export function filterDiagnostics(
32
+ list: readonly ModuleDiagnostic[],
33
+ min: Severity,
34
+ ): readonly ModuleDiagnostic[] {
35
+ const rank = (s: Severity) => (s === 'error' ? 3 : s === 'warn' ? 2 : 1);
36
+ const threshold = rank(min);
37
+ return list.filter((d) => rank(d.severity as Severity) >= threshold);
32
38
  }
package/src/index.ts CHANGED
@@ -1,2 +1,13 @@
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 {
8
+ BunRuntimeEnvLike,
9
+ DefaultBunBackendBootstrapOptions,
10
+ MetricsTracker,
11
+ RuntimeLogger,
12
+ } from './runtime/bun.js';
13
+ export { startPublishedWorkspaceServer } from './runtime/deploy.js';