@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/templates/backend/env.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
1
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -6,6 +7,8 @@ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
|
6
7
|
|
|
7
8
|
export interface AuthSecrets {
|
|
8
9
|
jwtSecret?: string;
|
|
10
|
+
jwtPublicKey?: string;
|
|
11
|
+
jwksUrl?: string;
|
|
9
12
|
jwtIssuer?: string;
|
|
10
13
|
jwtAudience?: string;
|
|
11
14
|
serviceTokens: string[];
|
|
@@ -26,6 +29,17 @@ export interface DatabaseConfig {
|
|
|
26
29
|
migrationsTable: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
export interface HttpConfig {
|
|
33
|
+
bodyLimitBytes: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface SessionConfig {
|
|
37
|
+
secret: string;
|
|
38
|
+
cookieName: string;
|
|
39
|
+
secure: boolean;
|
|
40
|
+
maxAgeSeconds: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
29
43
|
export interface AppEnv {
|
|
30
44
|
NODE_ENV: string;
|
|
31
45
|
PORT: number;
|
|
@@ -34,10 +48,15 @@ export interface AppEnv {
|
|
|
34
48
|
logging: LoggingConfig;
|
|
35
49
|
metrics: MetricsConfig;
|
|
36
50
|
database: DatabaseConfig;
|
|
51
|
+
http: HttpConfig;
|
|
52
|
+
sessions: SessionConfig;
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
const ENV_FILES = ['.env.local', '.env'];
|
|
56
|
+
const WORKSPACE_ROOT_PATTERN = /^(.*)[/\\](?:src|build)[/\\]backend(?:[/\\].*)?$/;
|
|
40
57
|
const WORKSPACE_ROOT = resolveWorkspaceRoot();
|
|
58
|
+
const DEFAULT_REQUEST_BODY_MAX_BYTES = 1024 * 1024;
|
|
59
|
+
const GENERATED_SESSION_SECRET = crypto.randomBytes(32).toString('hex');
|
|
41
60
|
let envLoaded = false;
|
|
42
61
|
|
|
43
62
|
export function loadEnv(): AppEnv {
|
|
@@ -46,26 +65,40 @@ export function loadEnv(): AppEnv {
|
|
|
46
65
|
envLoaded = true;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
|
-
const NODE_ENV =
|
|
68
|
+
const NODE_ENV = readNodeEnv();
|
|
50
69
|
const PORT = parsePort(process.env.PORT ?? '4000');
|
|
51
70
|
const API_BASE_URL = requireEnv('API_BASE_URL', 'http://localhost:4000');
|
|
52
71
|
const auth: AuthSecrets = {
|
|
53
72
|
jwtSecret: process.env.AUTH_JWT_SECRET,
|
|
73
|
+
jwtPublicKey: resolveJwtPublicKey(),
|
|
74
|
+
jwksUrl: normalizeOptionalText(process.env.AUTH_JWKS_URL),
|
|
54
75
|
jwtIssuer: process.env.AUTH_JWT_ISSUER,
|
|
55
76
|
jwtAudience: process.env.AUTH_JWT_AUDIENCE,
|
|
56
|
-
serviceTokens: parseList(process.env.AUTH_SERVICE_TOKENS)
|
|
77
|
+
serviceTokens: parseList(process.env.AUTH_SERVICE_TOKENS),
|
|
57
78
|
};
|
|
58
79
|
const logging: LoggingConfig = {
|
|
59
80
|
level: parseLogLevel(process.env.LOG_LEVEL),
|
|
60
|
-
serviceName: process.env.LOG_SERVICE_NAME ?? 'backend-template'
|
|
81
|
+
serviceName: process.env.LOG_SERVICE_NAME ?? 'backend-template',
|
|
61
82
|
};
|
|
62
83
|
const metrics: MetricsConfig = {
|
|
63
84
|
enabled: parseBoolean(process.env.METRICS_ENABLED, true),
|
|
64
|
-
windowSize: parsePositiveInt(process.env.METRICS_WINDOW, 200)
|
|
85
|
+
windowSize: parsePositiveInt(process.env.METRICS_WINDOW, 200),
|
|
65
86
|
};
|
|
66
87
|
const database: DatabaseConfig = {
|
|
67
88
|
url: process.env.DATABASE_URL ?? 'file:./data/dev.sqlite',
|
|
68
|
-
migrationsTable: process.env.DATABASE_MIGRATIONS_TABLE ?? '_webstir_migrations'
|
|
89
|
+
migrationsTable: process.env.DATABASE_MIGRATIONS_TABLE ?? '_webstir_migrations',
|
|
90
|
+
};
|
|
91
|
+
const http: HttpConfig = {
|
|
92
|
+
bodyLimitBytes: parsePositiveInt(
|
|
93
|
+
process.env.REQUEST_BODY_MAX_BYTES,
|
|
94
|
+
DEFAULT_REQUEST_BODY_MAX_BYTES,
|
|
95
|
+
),
|
|
96
|
+
};
|
|
97
|
+
const sessions: SessionConfig = {
|
|
98
|
+
secret: resolveSessionSecret(NODE_ENV),
|
|
99
|
+
cookieName: process.env.SESSION_COOKIE_NAME ?? 'webstir_session',
|
|
100
|
+
secure: parseBoolean(process.env.SESSION_COOKIE_SECURE, NODE_ENV === 'production'),
|
|
101
|
+
maxAgeSeconds: parsePositiveInt(process.env.SESSION_MAX_AGE, 60 * 60 * 24),
|
|
69
102
|
};
|
|
70
103
|
|
|
71
104
|
return {
|
|
@@ -75,10 +108,53 @@ export function loadEnv(): AppEnv {
|
|
|
75
108
|
auth,
|
|
76
109
|
logging,
|
|
77
110
|
metrics,
|
|
78
|
-
database
|
|
111
|
+
database,
|
|
112
|
+
http,
|
|
113
|
+
sessions,
|
|
79
114
|
};
|
|
80
115
|
}
|
|
81
116
|
|
|
117
|
+
function resolveSessionSecret(nodeEnv: string): string {
|
|
118
|
+
const explicitSecret = normalizeOptionalText(process.env.SESSION_SECRET);
|
|
119
|
+
if (explicitSecret) {
|
|
120
|
+
return explicitSecret;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (nodeEnv.trim().toLowerCase() === 'production') {
|
|
124
|
+
throw new Error('SESSION_SECRET is required when NODE_ENV=production.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return process.env.AUTH_JWT_SECRET ?? GENERATED_SESSION_SECRET;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readNodeEnv(): string {
|
|
131
|
+
const value = Reflect.get(process.env, 'NODE_ENV');
|
|
132
|
+
return typeof value === 'string' && value.length > 0 ? value : 'development';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveJwtPublicKey(): string | undefined {
|
|
136
|
+
const inline = normalizeOptionalText(process.env.AUTH_JWT_PUBLIC_KEY);
|
|
137
|
+
if (inline) {
|
|
138
|
+
return inline;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const filePath = normalizeOptionalText(process.env.AUTH_JWT_PUBLIC_KEY_FILE);
|
|
142
|
+
if (!filePath) {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const resolvedPath = path.isAbsolute(filePath)
|
|
147
|
+
? path.resolve(filePath)
|
|
148
|
+
: path.resolve(WORKSPACE_ROOT, filePath);
|
|
149
|
+
try {
|
|
150
|
+
return readFileSync(resolvedPath, 'utf8');
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Failed to read AUTH_JWT_PUBLIC_KEY_FILE at ${resolvedPath}: ${(error as Error).message}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
82
158
|
function loadEnvFiles(): void {
|
|
83
159
|
for (const file of ENV_FILES) {
|
|
84
160
|
const full = path.resolve(WORKSPACE_ROOT, file);
|
|
@@ -100,7 +176,10 @@ function applyEnvFile(filePath: string): void {
|
|
|
100
176
|
const key = line.slice(0, idx).trim();
|
|
101
177
|
if (!key || process.env[key] !== undefined) continue;
|
|
102
178
|
let value = line.slice(idx + 1).trim();
|
|
103
|
-
if (
|
|
179
|
+
if (
|
|
180
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
181
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
182
|
+
) {
|
|
104
183
|
value = value.slice(1, -1);
|
|
105
184
|
}
|
|
106
185
|
process.env[key] = value;
|
|
@@ -131,9 +210,21 @@ function parseList(value: string | undefined): string[] {
|
|
|
131
210
|
.filter((item) => item.length > 0);
|
|
132
211
|
}
|
|
133
212
|
|
|
213
|
+
function normalizeOptionalText(value: string | undefined): string | undefined {
|
|
214
|
+
const normalized = value?.trim();
|
|
215
|
+
return normalized || undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
134
218
|
function parseLogLevel(value: string | undefined): LogLevel {
|
|
135
219
|
const normalized = (value ?? 'info').toLowerCase();
|
|
136
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
normalized === 'trace' ||
|
|
222
|
+
normalized === 'debug' ||
|
|
223
|
+
normalized === 'info' ||
|
|
224
|
+
normalized === 'warn' ||
|
|
225
|
+
normalized === 'error' ||
|
|
226
|
+
normalized === 'fatal'
|
|
227
|
+
) {
|
|
137
228
|
return normalized;
|
|
138
229
|
}
|
|
139
230
|
return 'info';
|
|
@@ -155,20 +246,41 @@ function parsePositiveInt(value: string | undefined, fallback: number): number {
|
|
|
155
246
|
}
|
|
156
247
|
|
|
157
248
|
export function resolveWorkspaceRoot(): string {
|
|
158
|
-
|
|
159
|
-
|
|
249
|
+
const envWorkspaceRoot = resolveEnvWorkspaceRoot(process.env);
|
|
250
|
+
if (envWorkspaceRoot) {
|
|
251
|
+
return path.resolve(envWorkspaceRoot);
|
|
252
|
+
}
|
|
253
|
+
const inferredRoot = inferWorkspaceRootFromImportMetaUrl(import.meta.url);
|
|
254
|
+
if (inferredRoot) {
|
|
255
|
+
return inferredRoot;
|
|
256
|
+
}
|
|
257
|
+
return path.resolve(process.cwd());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function resolveEnvWorkspaceRoot(env: NodeJS.ProcessEnv): string | undefined {
|
|
261
|
+
const workspaceRoot = env.WORKSPACE_ROOT?.trim();
|
|
262
|
+
if (workspaceRoot) {
|
|
263
|
+
return workspaceRoot;
|
|
160
264
|
}
|
|
265
|
+
|
|
266
|
+
const webstirWorkspaceRoot = env.WEBSTIR_WORKSPACE_ROOT?.trim();
|
|
267
|
+
return webstirWorkspaceRoot || undefined;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function inferWorkspaceRootFromImportMetaUrl(importMetaUrl: string): string | undefined {
|
|
161
271
|
try {
|
|
162
|
-
|
|
163
|
-
const dir = path.dirname(filePath);
|
|
164
|
-
if (dir.endsWith(`${path.sep}src${path.sep}backend`)) {
|
|
165
|
-
return path.resolve(dir, '..', '..');
|
|
166
|
-
}
|
|
167
|
-
if (dir.endsWith(`${path.sep}build${path.sep}backend`)) {
|
|
168
|
-
return path.resolve(dir, '..', '..');
|
|
169
|
-
}
|
|
272
|
+
return inferWorkspaceRootFromFilePath(fileURLToPath(importMetaUrl));
|
|
170
273
|
} catch {
|
|
171
|
-
|
|
274
|
+
return undefined;
|
|
172
275
|
}
|
|
173
|
-
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function inferWorkspaceRootFromFilePath(filePath: string): string | undefined {
|
|
279
|
+
const normalizedFilePath = path.resolve(path.dirname(filePath));
|
|
280
|
+
const match = normalizedFilePath.match(WORKSPACE_ROOT_PATTERN);
|
|
281
|
+
if (!match) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return match[1] || path.parse(normalizedFilePath).root;
|
|
174
286
|
}
|
|
@@ -7,7 +7,7 @@ export async function run(): Promise<void> {
|
|
|
7
7
|
console.info('[function:hello] ran at', new Date().toISOString());
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
// Execute when launched directly: `
|
|
10
|
+
// Execute when launched directly: `bun build/backend/functions/hello/index.js`
|
|
11
11
|
const isMain = (() => {
|
|
12
12
|
try {
|
|
13
13
|
const argv1 = process.argv?.[1];
|
|
@@ -26,4 +26,3 @@ if (isMain) {
|
|
|
26
26
|
process.exitCode = 1;
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
|