@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/scripts/publish.sh
CHANGED
|
@@ -2,98 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
set -euo pipefail
|
|
4
4
|
|
|
5
|
-
usage() {
|
|
6
|
-
cat <<'EOF'
|
|
7
|
-
Usage: scripts/publish.sh <patch|minor|major|x.y.z> [--no-push]
|
|
8
|
-
|
|
9
|
-
Examples:
|
|
10
|
-
scripts/publish.sh patch
|
|
11
|
-
scripts/publish.sh 0.1.0
|
|
12
|
-
|
|
13
|
-
The script requires a clean git worktree.
|
|
14
|
-
|
|
15
|
-
By default, the script pushes the version bump commit and tag. To skip pushing,
|
|
16
|
-
pass --no-push or set PUBLISH_NO_PUSH=1.
|
|
17
|
-
EOF
|
|
18
|
-
exit 1
|
|
19
|
-
}
|
|
20
|
-
|
|
21
5
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
22
6
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
7
|
+
MONOREPO_ROOT="$(git -C "$ROOT_DIR" rev-parse --show-toplevel)"
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
if [[ $# -lt 1 ]]; then
|
|
26
|
-
echo "error: version bump argument missing" >&2
|
|
27
|
-
usage
|
|
28
|
-
fi
|
|
29
|
-
|
|
30
|
-
local bump="$1"; shift || true
|
|
31
|
-
local no_push="false"
|
|
32
|
-
|
|
33
|
-
while [[ $# -gt 0 ]]; do
|
|
34
|
-
case "$1" in
|
|
35
|
-
--no-push)
|
|
36
|
-
no_push="true"
|
|
37
|
-
;;
|
|
38
|
-
*)
|
|
39
|
-
echo "error: unknown option '$1'" >&2
|
|
40
|
-
usage
|
|
41
|
-
;;
|
|
42
|
-
esac
|
|
43
|
-
shift || true
|
|
44
|
-
done
|
|
45
|
-
|
|
46
|
-
if [[ ! $bump =~ ^(patch|minor|major)$ && ! $bump =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
47
|
-
echo "error: invalid bump '$bump'" >&2
|
|
48
|
-
usage
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
ensure_clean_git
|
|
52
|
-
|
|
53
|
-
cd "$ROOT_DIR"
|
|
54
|
-
|
|
55
|
-
echo "› npm version $bump"
|
|
56
|
-
npm version "$bump" -m "v%s"
|
|
57
|
-
|
|
58
|
-
echo "› npm install --package-lock-only"
|
|
59
|
-
npm install --package-lock-only
|
|
60
|
-
|
|
61
|
-
echo "› npm run clean"
|
|
62
|
-
npm run clean
|
|
63
|
-
|
|
64
|
-
echo "› npm run build"
|
|
65
|
-
npm run build
|
|
66
|
-
|
|
67
|
-
echo "› npm test"
|
|
68
|
-
npm test
|
|
69
|
-
|
|
70
|
-
echo "› npm run smoke"
|
|
71
|
-
npm run smoke
|
|
72
|
-
|
|
73
|
-
echo "› Skipping direct npm publish; pushing commit+tag will trigger the release workflow."
|
|
74
|
-
|
|
75
|
-
if [[ "$no_push" == "true" || "${PUBLISH_NO_PUSH:-}" =~ ^([Yy][Ee][Ss]|[Yy]|1|true)$ ]]; then
|
|
76
|
-
echo "› Skipping git push (no-push)."
|
|
77
|
-
echo " To publish upstream later, run: git push && git push --tags"
|
|
78
|
-
return 0
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
echo "› git push"
|
|
82
|
-
git push
|
|
83
|
-
echo "› git push --tags"
|
|
84
|
-
git push --tags
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
ensure_clean_git() {
|
|
88
|
-
cd "$ROOT_DIR"
|
|
89
|
-
if ! git diff --quiet --ignore-submodules HEAD; then
|
|
90
|
-
echo "error: git worktree has uncommitted changes" >&2
|
|
91
|
-
exit 1
|
|
92
|
-
fi
|
|
93
|
-
if ! git diff --quiet --cached --ignore-submodules; then
|
|
94
|
-
echo "error: git index has staged changes" >&2
|
|
95
|
-
exit 1
|
|
96
|
-
fi
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
main "$@"
|
|
9
|
+
exec bun "$MONOREPO_ROOT/tools/release-package.mjs" --package-dir "$ROOT_DIR" "$@"
|
package/scripts/smoke.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { backendProvider } from '../dist/index.js';
|
|
7
7
|
import { CONTRACT_VERSION } from '@webstir-io/module-contract';
|
|
@@ -12,6 +12,11 @@ function getLocalBinPath() {
|
|
|
12
12
|
return path.join(pkgRoot, 'node_modules', '.bin');
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function getPackageRoot() {
|
|
16
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
return path.resolve(here, '..');
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
async function ensureDir(dir) {
|
|
16
21
|
await fs.mkdir(dir, { recursive: true });
|
|
17
22
|
}
|
|
@@ -23,17 +28,236 @@ async function copyFile(src, dest) {
|
|
|
23
28
|
|
|
24
29
|
async function installPackages(workspace, packages, options = { dev: false }) {
|
|
25
30
|
if (!packages || packages.length === 0) return;
|
|
26
|
-
const args = ['
|
|
31
|
+
const args = ['add', '--silent', ...packages];
|
|
27
32
|
if (options.dev) {
|
|
28
33
|
args.push('-D');
|
|
29
34
|
}
|
|
30
35
|
await new Promise((resolve, reject) => {
|
|
31
|
-
const child = spawn('
|
|
36
|
+
const child = spawn('bun', args, { cwd: workspace, stdio: 'ignore' });
|
|
32
37
|
child.on('error', reject);
|
|
33
|
-
child.on('close', (code) =>
|
|
38
|
+
child.on('close', (code) =>
|
|
39
|
+
code === 0 ? resolve() : reject(new Error(`bun add failed (${code})`)),
|
|
40
|
+
);
|
|
34
41
|
});
|
|
35
42
|
}
|
|
36
43
|
|
|
44
|
+
async function pathExists(target) {
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(target);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runBunProbe(script, { cwd, env }) {
|
|
54
|
+
return await new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn('bun', ['--eval', script], {
|
|
56
|
+
cwd,
|
|
57
|
+
env: {
|
|
58
|
+
...process.env,
|
|
59
|
+
...env,
|
|
60
|
+
},
|
|
61
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
let stdout = '';
|
|
65
|
+
let stderr = '';
|
|
66
|
+
child.stdout.on('data', (chunk) => {
|
|
67
|
+
stdout += chunk.toString();
|
|
68
|
+
});
|
|
69
|
+
child.stderr.on('data', (chunk) => {
|
|
70
|
+
stderr += chunk.toString();
|
|
71
|
+
});
|
|
72
|
+
child.on('error', reject);
|
|
73
|
+
child.on('close', (code) => {
|
|
74
|
+
if (code !== 0) {
|
|
75
|
+
reject(new Error(`bun probe failed (${code}).\nstdout:\n${stdout}\nstderr:\n${stderr}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const line = stdout
|
|
79
|
+
.split(/\r?\n/)
|
|
80
|
+
.map((value) => value.trim())
|
|
81
|
+
.find((value) => value.startsWith('{'));
|
|
82
|
+
if (!line) {
|
|
83
|
+
reject(new Error(`bun probe did not emit JSON.\nstdout:\n${stdout}\nstderr:\n${stderr}`));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
resolve(JSON.parse(line));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function linkWorkspaceNodeModules(workspace) {
|
|
92
|
+
const packageRoot = getPackageRoot();
|
|
93
|
+
const source = path.join(packageRoot, 'node_modules');
|
|
94
|
+
const target = path.join(workspace, 'node_modules');
|
|
95
|
+
await fs.mkdir(target, { recursive: true });
|
|
96
|
+
|
|
97
|
+
const entries = await fs.readdir(source, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (entry.name === '@webstir-io') {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
await createSymlinkIfMissing(
|
|
103
|
+
path.join(source, entry.name),
|
|
104
|
+
path.join(target, entry.name),
|
|
105
|
+
entry.isDirectory() ? 'dir' : 'file',
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const scopeSource = path.join(source, '@webstir-io');
|
|
110
|
+
const scopeTarget = path.join(target, '@webstir-io');
|
|
111
|
+
await fs.mkdir(scopeTarget, { recursive: true });
|
|
112
|
+
const scopeEntries = await fs.readdir(scopeSource, { withFileTypes: true });
|
|
113
|
+
for (const entry of scopeEntries) {
|
|
114
|
+
await createSymlinkIfMissing(
|
|
115
|
+
path.join(scopeSource, entry.name),
|
|
116
|
+
path.join(scopeTarget, entry.name),
|
|
117
|
+
entry.isDirectory() ? 'dir' : 'file',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await createSymlinkIfMissing(packageRoot, path.join(scopeTarget, 'webstir-backend'), 'dir');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function createSymlinkIfMissing(source, target, type) {
|
|
125
|
+
try {
|
|
126
|
+
await fs.symlink(source, target, type);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'EEXIST') {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function runTemplateSqliteProbes(workspace) {
|
|
136
|
+
const alternateCwd = await fs.mkdtemp(
|
|
137
|
+
path.join(os.tmpdir(), 'webstir-backend-smoke-sqlite-cwd-'),
|
|
138
|
+
);
|
|
139
|
+
const sessionStoreUrl = JSON.stringify(
|
|
140
|
+
pathToFileURL(path.join(workspace, 'src', 'backend', 'session', 'store.ts')).href,
|
|
141
|
+
);
|
|
142
|
+
const runtimeSessionUrl = JSON.stringify(
|
|
143
|
+
pathToFileURL(path.join(getPackageRoot(), 'dist', 'runtime', 'session.js')).href,
|
|
144
|
+
);
|
|
145
|
+
const dbConnectionUrl = JSON.stringify(
|
|
146
|
+
pathToFileURL(path.join(workspace, 'src', 'backend', 'db', 'connection.ts')).href,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const sessionProbe = `
|
|
150
|
+
const [{ sessionStore }, { prepareSessionState }] = await Promise.all([
|
|
151
|
+
import(${sessionStoreUrl}),
|
|
152
|
+
import(${runtimeSessionUrl})
|
|
153
|
+
]);
|
|
154
|
+
|
|
155
|
+
const config = {
|
|
156
|
+
secret: 'smoke-session-secret',
|
|
157
|
+
cookieName: 'webstir_session',
|
|
158
|
+
secure: false,
|
|
159
|
+
maxAgeSeconds: 60
|
|
160
|
+
};
|
|
161
|
+
const loginRoute = {
|
|
162
|
+
form: {
|
|
163
|
+
session: { write: true },
|
|
164
|
+
flash: {
|
|
165
|
+
publish: [{ key: 'signed-in', level: 'success', when: 'success' }]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const accountRoute = {
|
|
170
|
+
session: { mode: 'optional' },
|
|
171
|
+
flash: { consume: ['signed-in'] }
|
|
172
|
+
};
|
|
173
|
+
const created = prepareSessionState({
|
|
174
|
+
cookies: '',
|
|
175
|
+
route: loginRoute,
|
|
176
|
+
config,
|
|
177
|
+
store: sessionStore
|
|
178
|
+
});
|
|
179
|
+
const createdCommit = created.commit({
|
|
180
|
+
session: {
|
|
181
|
+
userId: 'ada@example.com'
|
|
182
|
+
},
|
|
183
|
+
route: loginRoute,
|
|
184
|
+
result: {
|
|
185
|
+
status: 303,
|
|
186
|
+
redirect: { location: '/session/account' }
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
const cookie = String(createdCommit.setCookie).split(';')[0];
|
|
190
|
+
const read = prepareSessionState({
|
|
191
|
+
cookies: cookie,
|
|
192
|
+
route: accountRoute,
|
|
193
|
+
config,
|
|
194
|
+
store: sessionStore
|
|
195
|
+
});
|
|
196
|
+
console.log(JSON.stringify({
|
|
197
|
+
userId: read.session?.userId ?? null,
|
|
198
|
+
flash: read.flash.map((message) => ({ key: message.key, level: message.level }))
|
|
199
|
+
}));
|
|
200
|
+
`;
|
|
201
|
+
|
|
202
|
+
const dbProbe = `
|
|
203
|
+
const { createDatabaseClient } = await import(${dbConnectionUrl});
|
|
204
|
+
const db = await createDatabaseClient();
|
|
205
|
+
await db.execute('CREATE TABLE IF NOT EXISTS smoke_items (id TEXT PRIMARY KEY, value TEXT NOT NULL)');
|
|
206
|
+
await db.execute('DELETE FROM smoke_items');
|
|
207
|
+
await db.execute('INSERT INTO smoke_items (id, value) VALUES (?, ?)', ['row-1', 'Ada']);
|
|
208
|
+
const rows = await db.query('SELECT value FROM smoke_items WHERE id = ?', ['row-1']);
|
|
209
|
+
const databases = await db.query('PRAGMA database_list');
|
|
210
|
+
const main = databases.find((row) => row.name === 'main');
|
|
211
|
+
console.log(JSON.stringify({ target: main?.file ?? null, value: rows[0]?.value ?? null }));
|
|
212
|
+
await db.close();
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
const sessionResult = await runBunProbe(sessionProbe, {
|
|
216
|
+
cwd: alternateCwd,
|
|
217
|
+
env: {
|
|
218
|
+
WORKSPACE_ROOT: ' ',
|
|
219
|
+
WEBSTIR_WORKSPACE_ROOT: workspace,
|
|
220
|
+
SESSION_STORE_DRIVER: 'sqlite',
|
|
221
|
+
SESSION_STORE_URL: 'file:./data/smoke-session.sqlite',
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
console.info('[smoke] sqlite session probe:', sessionResult);
|
|
225
|
+
if (sessionResult.userId !== 'ada@example.com') {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`[smoke] sqlite session probe returned unexpected userId ${sessionResult.userId}`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (!(await pathExists(path.join(workspace, 'data', 'smoke-session.sqlite')))) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
'[smoke] sqlite session probe did not create the workspace-root session database',
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (await pathExists(path.join(alternateCwd, 'data', 'smoke-session.sqlite'))) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
'[smoke] sqlite session probe wrote to the probe cwd instead of the workspace root',
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const dbResult = await runBunProbe(dbProbe, {
|
|
242
|
+
cwd: alternateCwd,
|
|
243
|
+
env: {
|
|
244
|
+
WORKSPACE_ROOT: ' ',
|
|
245
|
+
WEBSTIR_WORKSPACE_ROOT: workspace,
|
|
246
|
+
DATABASE_URL: 'file:./data/smoke-db.sqlite',
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
console.info('[smoke] sqlite db probe:', dbResult);
|
|
250
|
+
if (dbResult.value !== 'Ada') {
|
|
251
|
+
throw new Error(`[smoke] sqlite db probe returned unexpected value ${dbResult.value}`);
|
|
252
|
+
}
|
|
253
|
+
if (!(await pathExists(path.join(workspace, 'data', 'smoke-db.sqlite')))) {
|
|
254
|
+
throw new Error('[smoke] sqlite db probe did not create the workspace-root database');
|
|
255
|
+
}
|
|
256
|
+
if (await pathExists(path.join(alternateCwd, 'data', 'smoke-db.sqlite'))) {
|
|
257
|
+
throw new Error('[smoke] sqlite db probe wrote to the probe cwd instead of the workspace root');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
37
261
|
async function main() {
|
|
38
262
|
const workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'webstir-backend-smoke-'));
|
|
39
263
|
const assets = await backendProvider.getScaffoldAssets();
|
|
@@ -41,7 +265,7 @@ async function main() {
|
|
|
41
265
|
assets.map(async (asset) => {
|
|
42
266
|
const target = path.join(workspace, asset.targetPath);
|
|
43
267
|
await copyFile(asset.sourcePath, target);
|
|
44
|
-
})
|
|
268
|
+
}),
|
|
45
269
|
);
|
|
46
270
|
|
|
47
271
|
const backendTsconfigPath = path.join(workspace, 'src', 'backend', 'tsconfig.json');
|
|
@@ -51,7 +275,11 @@ async function main() {
|
|
|
51
275
|
if (backendTsconfig?.compilerOptions) {
|
|
52
276
|
delete backendTsconfig.compilerOptions.types;
|
|
53
277
|
}
|
|
54
|
-
await fs.writeFile(
|
|
278
|
+
await fs.writeFile(
|
|
279
|
+
backendTsconfigPath,
|
|
280
|
+
`${JSON.stringify(backendTsconfig, null, 2)}\n`,
|
|
281
|
+
'utf8',
|
|
282
|
+
);
|
|
55
283
|
} catch (error) {
|
|
56
284
|
console.warn('[smoke] failed to adjust backend tsconfig:', error);
|
|
57
285
|
}
|
|
@@ -70,24 +298,15 @@ async function main() {
|
|
|
70
298
|
kind: 'backend',
|
|
71
299
|
capabilities: [],
|
|
72
300
|
routes: [],
|
|
73
|
-
views: []
|
|
74
|
-
}
|
|
75
|
-
}
|
|
301
|
+
views: [],
|
|
302
|
+
},
|
|
303
|
+
},
|
|
76
304
|
};
|
|
77
305
|
await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
|
|
78
306
|
|
|
79
|
-
await installPackages(workspace, ['pino'
|
|
307
|
+
await installPackages(workspace, ['pino']);
|
|
80
308
|
|
|
81
|
-
|
|
82
|
-
// Add optional Fastify dependency so the scaffold type-checks if present
|
|
83
|
-
try {
|
|
84
|
-
await installPackages(workspace, ['fastify', '@types/node@^20'], { dev: true });
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.warn('[smoke] skipping Fastify install:', err);
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
console.info('[smoke] fastify install skipped by WEBSTIR_BACKEND_SMOKE_FASTIFY=skip');
|
|
90
|
-
}
|
|
309
|
+
await linkWorkspaceNodeModules(workspace);
|
|
91
310
|
|
|
92
311
|
const rootTsconfigPath = path.join(workspace, 'tsconfig.json');
|
|
93
312
|
const rootTsconfig = {
|
|
@@ -99,8 +318,8 @@ async function main() {
|
|
|
99
318
|
strict: true,
|
|
100
319
|
isolatedModules: true,
|
|
101
320
|
esModuleInterop: true,
|
|
102
|
-
skipLibCheck: true
|
|
103
|
-
}
|
|
321
|
+
skipLibCheck: true,
|
|
322
|
+
},
|
|
104
323
|
};
|
|
105
324
|
await fs.writeFile(rootTsconfigPath, `${JSON.stringify(rootTsconfig, null, 2)}\n`, 'utf8');
|
|
106
325
|
|
|
@@ -108,131 +327,87 @@ async function main() {
|
|
|
108
327
|
PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`,
|
|
109
328
|
WEBSTIR_BACKEND_TYPECHECK: 'skip',
|
|
110
329
|
// Exercise provider diagnostic filtering: suppress info by default
|
|
111
|
-
WEBSTIR_BACKEND_LOG_LEVEL: 'warn'
|
|
330
|
+
WEBSTIR_BACKEND_LOG_LEVEL: 'warn',
|
|
112
331
|
};
|
|
113
332
|
|
|
114
333
|
console.info('[smoke] build mode');
|
|
115
334
|
const buildResult = await backendProvider.build({
|
|
116
335
|
workspaceRoot: workspace,
|
|
117
336
|
env: { ...envBase, WEBSTIR_MODULE_MODE: 'build' },
|
|
118
|
-
incremental: false
|
|
337
|
+
incremental: false,
|
|
119
338
|
});
|
|
120
339
|
const buildEntries = buildResult.manifest.entryPoints;
|
|
121
340
|
const buildFunctions = buildEntries.filter((p) => p.startsWith('functions/')).length;
|
|
122
341
|
const buildJobs = buildEntries.filter((p) => p.startsWith('jobs/')).length;
|
|
123
|
-
const buildServer = buildEntries.filter(
|
|
342
|
+
const buildServer = buildEntries.filter(
|
|
343
|
+
(p) => p === 'index.js' || (/(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p)),
|
|
344
|
+
).length;
|
|
124
345
|
console.info('[smoke] build entryPoints:', buildEntries);
|
|
125
|
-
console.info('[smoke] build entry counts:', {
|
|
346
|
+
console.info('[smoke] build entry counts:', {
|
|
347
|
+
server: buildServer,
|
|
348
|
+
functions: buildFunctions,
|
|
349
|
+
jobs: buildJobs,
|
|
350
|
+
});
|
|
126
351
|
if (buildFunctions < 1 || buildJobs < 1) {
|
|
127
|
-
throw new Error(
|
|
352
|
+
throw new Error(
|
|
353
|
+
`[smoke] expected scaffold to include functions and jobs (got functions=${buildFunctions}, jobs=${buildJobs})`,
|
|
354
|
+
);
|
|
128
355
|
}
|
|
129
356
|
const buildModule = buildResult.manifest.module ?? {};
|
|
130
357
|
console.info('[smoke] build routes/views summary:', {
|
|
131
358
|
routes: Array.isArray(buildModule.routes) ? buildModule.routes.length : 0,
|
|
132
|
-
views: Array.isArray(buildModule.views) ? buildModule.views.length : 0
|
|
359
|
+
views: Array.isArray(buildModule.views) ? buildModule.views.length : 0,
|
|
133
360
|
});
|
|
134
|
-
console.info(
|
|
361
|
+
console.info(
|
|
362
|
+
'[smoke] build diagnostics (>=warn):',
|
|
363
|
+
buildResult.manifest.diagnostics.map((d) => d.message),
|
|
364
|
+
);
|
|
365
|
+
console.info('[smoke] sqlite template probes');
|
|
366
|
+
await runTemplateSqliteProbes(workspace);
|
|
135
367
|
|
|
136
368
|
console.info('[smoke] publish mode');
|
|
137
369
|
const publishResult = await backendProvider.build({
|
|
138
370
|
workspaceRoot: workspace,
|
|
139
371
|
// Intentionally clear PATH so `tsc` is not found; provider will warn and continue
|
|
140
372
|
env: { ...envBase, WEBSTIR_MODULE_MODE: 'publish', PATH: '' },
|
|
141
|
-
incremental: false
|
|
373
|
+
incremental: false,
|
|
142
374
|
});
|
|
143
375
|
const publishEntries = publishResult.manifest.entryPoints;
|
|
144
376
|
const publishFunctions = publishEntries.filter((p) => p.startsWith('functions/')).length;
|
|
145
377
|
const publishJobs = publishEntries.filter((p) => p.startsWith('jobs/')).length;
|
|
146
|
-
const publishServer = publishEntries.filter(
|
|
378
|
+
const publishServer = publishEntries.filter(
|
|
379
|
+
(p) => p === 'index.js' || (/(^|\/)index\.js$/.test(p) && !/^(functions|jobs)\//.test(p)),
|
|
380
|
+
).length;
|
|
147
381
|
console.info('[smoke] publish entryPoints:', publishEntries);
|
|
148
|
-
console.info('[smoke] publish entry counts:', {
|
|
382
|
+
console.info('[smoke] publish entry counts:', {
|
|
383
|
+
server: publishServer,
|
|
384
|
+
functions: publishFunctions,
|
|
385
|
+
jobs: publishJobs,
|
|
386
|
+
});
|
|
149
387
|
if (publishFunctions < 1 || publishJobs < 1) {
|
|
150
|
-
throw new Error(
|
|
388
|
+
throw new Error(
|
|
389
|
+
`[smoke] expected scaffold to include functions and jobs after publish (got functions=${publishFunctions}, jobs=${publishJobs})`,
|
|
390
|
+
);
|
|
151
391
|
}
|
|
152
392
|
const publishModule = publishResult.manifest.module ?? {};
|
|
153
393
|
console.info('[smoke] publish routes/views summary:', {
|
|
154
394
|
routes: Array.isArray(publishModule.routes) ? publishModule.routes.length : 0,
|
|
155
|
-
views: Array.isArray(publishModule.views) ? publishModule.views.length : 0
|
|
395
|
+
views: Array.isArray(publishModule.views) ? publishModule.views.length : 0,
|
|
156
396
|
});
|
|
157
397
|
const publishDiagnostics = publishResult.manifest.diagnostics
|
|
158
398
|
.map((d) => ({ ...d, message: d.message.trim() }))
|
|
159
399
|
.filter((d) => d.severity !== 'info');
|
|
160
|
-
const unexpectedPublishDiagnostics = publishDiagnostics.filter(
|
|
400
|
+
const unexpectedPublishDiagnostics = publishDiagnostics.filter(
|
|
401
|
+
(d) => !/TypeScript compiler \(tsc\) not found|Type checking failed/.test(d.message),
|
|
402
|
+
);
|
|
161
403
|
if (unexpectedPublishDiagnostics.length > 0) {
|
|
162
|
-
console.info(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// Fastify scaffold type-check (no run): ensure tsc sees server/fastify.ts
|
|
167
|
-
console.info('[smoke] fastify type-check');
|
|
168
|
-
const typecheckResult = await backendProvider.build({
|
|
169
|
-
workspaceRoot: workspace,
|
|
170
|
-
env: { PATH: envBase.PATH, WEBSTIR_BACKEND_LOG_LEVEL: 'warn', WEBSTIR_MODULE_MODE: 'build', WEBSTIR_BACKEND_TYPECHECK: 'skip' },
|
|
171
|
-
incremental: false
|
|
172
|
-
});
|
|
173
|
-
const typecheckErrors = typecheckResult.manifest.diagnostics.filter((d) => d.severity === 'error');
|
|
174
|
-
if (typecheckErrors.length > 0) {
|
|
175
|
-
throw new Error(`[smoke] fastify type-check reported errors: ${typecheckErrors.map((d) => d.message).join('; ')}`);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Optionally run server and hit /api/health
|
|
179
|
-
if (process.env.WEBSTIR_BACKEND_SMOKE_FASTIFY_RUN !== 'skip') {
|
|
180
|
-
console.info('[smoke] fastify run + health check');
|
|
181
|
-
const port = 47891;
|
|
182
|
-
const child = spawn(process.execPath, ['build/backend/server/fastify.js'], {
|
|
183
|
-
cwd: workspace,
|
|
184
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
185
|
-
env: { ...process.env, PORT: String(port) }
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
let ready = false;
|
|
189
|
-
const outChunks = [];
|
|
190
|
-
child.stdout.on('data', (c) => {
|
|
191
|
-
const s = c.toString();
|
|
192
|
-
outChunks.push(s);
|
|
193
|
-
if (!ready && s.includes('API server running')) {
|
|
194
|
-
ready = true;
|
|
195
|
-
(async () => {
|
|
196
|
-
try {
|
|
197
|
-
const res = await fetch(`http://127.0.0.1:${port}/api/health`);
|
|
198
|
-
if (!res.ok) throw new Error(`health returned ${res.status}`);
|
|
199
|
-
const json = await res.json();
|
|
200
|
-
if (!json || json.ok !== true) throw new Error('health payload invalid');
|
|
201
|
-
} catch (err) {
|
|
202
|
-
console.error('[smoke] fastify health check failed:', err);
|
|
203
|
-
child.kill();
|
|
204
|
-
throw err;
|
|
205
|
-
} finally {
|
|
206
|
-
child.kill();
|
|
207
|
-
}
|
|
208
|
-
})().catch((err) => {
|
|
209
|
-
console.error(err);
|
|
210
|
-
process.exitCode = 1;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await new Promise((resolve) => {
|
|
216
|
-
const timer = setTimeout(() => {
|
|
217
|
-
if (!ready) {
|
|
218
|
-
console.error('[smoke] fastify did not reach readiness');
|
|
219
|
-
child.kill();
|
|
220
|
-
}
|
|
221
|
-
resolve(null);
|
|
222
|
-
}, 8000);
|
|
223
|
-
child.on('close', () => {
|
|
224
|
-
clearTimeout(timer);
|
|
225
|
-
resolve(null);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
} else {
|
|
229
|
-
console.info('[smoke] fastify run skipped by WEBSTIR_BACKEND_SMOKE_FASTIFY_RUN=skip');
|
|
230
|
-
}
|
|
231
|
-
} else {
|
|
232
|
-
console.info('[smoke] fastify type-check skipped by WEBSTIR_BACKEND_SMOKE_FASTIFY=skip');
|
|
404
|
+
console.info(
|
|
405
|
+
'[smoke] publish diagnostics (non-info):',
|
|
406
|
+
unexpectedPublishDiagnostics.map((d) => d.message),
|
|
407
|
+
);
|
|
233
408
|
}
|
|
234
409
|
|
|
235
|
-
console.info('[smoke] completed: build ✔ publish ✔
|
|
410
|
+
console.info('[smoke] completed: build ✔ publish ✔');
|
|
236
411
|
}
|
|
237
412
|
|
|
238
413
|
main().catch((err) => {
|
|
@@ -22,6 +22,8 @@ EOF
|
|
|
22
22
|
|
|
23
23
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
24
|
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
25
|
+
MONOREPO_ROOT="$(git -C "$ROOT_DIR" rev-parse --show-toplevel)"
|
|
26
|
+
PACKAGE_NAME="$(node -p "require('./package.json').name" 2>/dev/null)"
|
|
25
27
|
|
|
26
28
|
has_script() {
|
|
27
29
|
local script_name="$1"
|
|
@@ -85,33 +87,33 @@ main() {
|
|
|
85
87
|
echo "› Setting @webstir-io/module-contract to $spec"
|
|
86
88
|
npm pkg set "dependencies.@webstir-io/module-contract=$spec"
|
|
87
89
|
|
|
88
|
-
echo "›
|
|
90
|
+
echo "› bun install (refresh workspace lockfile)"
|
|
89
91
|
if [[ "$fast" == "true" ]]; then
|
|
90
|
-
|
|
92
|
+
bun install --cwd "$MONOREPO_ROOT" --filter "$PACKAGE_NAME" --lockfile-only
|
|
91
93
|
else
|
|
92
|
-
|
|
94
|
+
bun install --cwd "$MONOREPO_ROOT" --filter "$PACKAGE_NAME"
|
|
93
95
|
fi
|
|
94
96
|
|
|
95
97
|
# Clarify versions in logs to avoid confusion with backend package version
|
|
96
98
|
local backend_ver
|
|
97
99
|
backend_ver="$(node -p "require('./package.json').version" 2>/dev/null || echo 'unknown')"
|
|
98
100
|
local installed_contract
|
|
99
|
-
installed_contract="$(
|
|
101
|
+
installed_contract="$(node -p "try { require('@webstir-io/module-contract/package.json').version } catch { 'unknown' }" 2>/dev/null || echo 'unknown')"
|
|
100
102
|
echo "› Backend package: @webstir-io/webstir-backend@${backend_ver}"
|
|
101
103
|
echo "› Contract installed: @webstir-io/module-contract@${installed_contract}"
|
|
102
104
|
|
|
103
105
|
if [[ "$fast" != "true" ]]; then
|
|
104
|
-
echo "›
|
|
105
|
-
|
|
106
|
+
echo "› bun run build"
|
|
107
|
+
bun run build
|
|
106
108
|
|
|
107
109
|
if has_script test; then
|
|
108
|
-
echo "›
|
|
109
|
-
|
|
110
|
+
echo "› bun run test"
|
|
111
|
+
bun run test
|
|
110
112
|
fi
|
|
111
113
|
|
|
112
114
|
if has_script smoke; then
|
|
113
|
-
echo "›
|
|
114
|
-
|
|
115
|
+
echo "› bun run smoke"
|
|
116
|
+
bun run smoke
|
|
115
117
|
fi
|
|
116
118
|
fi
|
|
117
119
|
|