@webstir-io/webstir-backend 0.1.15

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 (79) hide show
  1. package/README.md +427 -0
  2. package/dist/build/artifacts.d.ts +113 -0
  3. package/dist/build/artifacts.js +53 -0
  4. package/dist/build/entries.d.ts +1 -0
  5. package/dist/build/entries.js +17 -0
  6. package/dist/build/pipeline.d.ts +31 -0
  7. package/dist/build/pipeline.js +424 -0
  8. package/dist/cache/diff.d.ts +4 -0
  9. package/dist/cache/diff.js +114 -0
  10. package/dist/cache/reporters.d.ts +12 -0
  11. package/dist/cache/reporters.js +23 -0
  12. package/dist/diagnostics/summary.d.ts +6 -0
  13. package/dist/diagnostics/summary.js +27 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +2 -0
  16. package/dist/manifest/pipeline.d.ts +13 -0
  17. package/dist/manifest/pipeline.js +224 -0
  18. package/dist/provider.d.ts +2 -0
  19. package/dist/provider.js +101 -0
  20. package/dist/scaffold/assets.d.ts +2 -0
  21. package/dist/scaffold/assets.js +77 -0
  22. package/dist/testing/context.d.ts +3 -0
  23. package/dist/testing/context.js +14 -0
  24. package/dist/testing/index.d.ts +6 -0
  25. package/dist/testing/index.js +208 -0
  26. package/dist/testing/types.d.ts +28 -0
  27. package/dist/testing/types.js +1 -0
  28. package/dist/watch.d.ts +8 -0
  29. package/dist/watch.js +159 -0
  30. package/dist/workspace.d.ts +4 -0
  31. package/dist/workspace.js +15 -0
  32. package/package.json +74 -0
  33. package/scripts/publish.sh +99 -0
  34. package/scripts/smoke.mjs +241 -0
  35. package/scripts/update-contract.sh +122 -0
  36. package/src/build/artifacts.ts +67 -0
  37. package/src/build/entries.ts +19 -0
  38. package/src/build/pipeline.ts +507 -0
  39. package/src/cache/diff.ts +128 -0
  40. package/src/cache/reporters.ts +41 -0
  41. package/src/diagnostics/summary.ts +32 -0
  42. package/src/index.ts +2 -0
  43. package/src/manifest/pipeline.ts +270 -0
  44. package/src/provider.ts +124 -0
  45. package/src/scaffold/assets.ts +81 -0
  46. package/src/testing/context.d.ts +3 -0
  47. package/src/testing/context.js +14 -0
  48. package/src/testing/context.ts +17 -0
  49. package/src/testing/index.d.ts +6 -0
  50. package/src/testing/index.js +208 -0
  51. package/src/testing/index.ts +252 -0
  52. package/src/testing/types.d.ts +28 -0
  53. package/src/testing/types.js +1 -0
  54. package/src/testing/types.ts +32 -0
  55. package/src/watch.ts +177 -0
  56. package/src/workspace.ts +22 -0
  57. package/templates/backend/.env.example +13 -0
  58. package/templates/backend/auth/adapter.ts +160 -0
  59. package/templates/backend/db/connection.ts +99 -0
  60. package/templates/backend/db/migrate.ts +231 -0
  61. package/templates/backend/db/migrations/0001-example.ts +17 -0
  62. package/templates/backend/db/types.d.ts +2 -0
  63. package/templates/backend/env.ts +174 -0
  64. package/templates/backend/functions/hello/index.ts +29 -0
  65. package/templates/backend/index.ts +532 -0
  66. package/templates/backend/jobs/nightly/index.ts +28 -0
  67. package/templates/backend/jobs/runtime.ts +103 -0
  68. package/templates/backend/jobs/scheduler.ts +193 -0
  69. package/templates/backend/module.ts +87 -0
  70. package/templates/backend/observability/logger.ts +24 -0
  71. package/templates/backend/observability/metrics.ts +78 -0
  72. package/templates/backend/server/fastify.ts +288 -0
  73. package/templates/backend/tsconfig.json +19 -0
  74. package/tests/cacheReporter.test.js +89 -0
  75. package/tests/envLoader.test.js +64 -0
  76. package/tests/integration.test.js +108 -0
  77. package/tests/manifest.test.js +159 -0
  78. package/tests/watch.test.js +100 -0
  79. package/tsconfig.json +27 -0
@@ -0,0 +1,89 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ import { createCacheReporter } from '../dist/cache/reporters.js';
8
+
9
+ async function createWorkspace(prefix = 'webstir-backend-cache-') {
10
+ return await fs.mkdtemp(path.join(os.tmpdir(), prefix));
11
+ }
12
+
13
+ function makeManifest(overrides = {}) {
14
+ return {
15
+ contractVersion: '1.0.0',
16
+ name: '@demo/test',
17
+ version: '0.0.1',
18
+ kind: 'backend',
19
+ capabilities: [],
20
+ routes: [],
21
+ views: [],
22
+ jobs: [],
23
+ events: [],
24
+ services: [],
25
+ ...overrides
26
+ };
27
+ }
28
+
29
+ test('cache reporter emits diagnostics for output and manifest diffs', async () => {
30
+ const workspaceRoot = await createWorkspace();
31
+ const diagnostics = [];
32
+ const reporter = createCacheReporter({
33
+ workspaceRoot,
34
+ buildRoot: path.join(workspaceRoot, 'build', 'backend'),
35
+ env: {},
36
+ diagnostics
37
+ });
38
+
39
+ await reporter.diffOutputs({ 'index.js': 128 }, 'build');
40
+ assert.ok(
41
+ diagnostics.some((d) => d.message.includes('changed 1 file')),
42
+ 'expected first diff to report changed files'
43
+ );
44
+
45
+ diagnostics.length = 0;
46
+
47
+ await reporter.diffOutputs({ 'index.js': 256 }, 'build');
48
+ assert.ok(
49
+ diagnostics.some((d) => d.message.includes('changed 1 file')),
50
+ 'expected subsequent diffs to report changed files'
51
+ );
52
+
53
+ diagnostics.length = 0;
54
+
55
+ await reporter.diffManifest(makeManifest());
56
+ assert.equal(diagnostics.length, 0, 'first manifest digest should not emit diagnostics');
57
+
58
+ await reporter.diffManifest(
59
+ makeManifest({
60
+ routes: [{ method: 'GET', path: '/accounts' }]
61
+ })
62
+ );
63
+ assert.ok(
64
+ diagnostics.some((d) => d.message.includes('manifest changed')),
65
+ 'expected manifest changes to produce diagnostics'
66
+ );
67
+ });
68
+
69
+ test('cache reporter can silence diagnostics via env', async () => {
70
+ const workspaceRoot = await createWorkspace();
71
+ const diagnostics = [];
72
+ const reporter = createCacheReporter({
73
+ workspaceRoot,
74
+ buildRoot: path.join(workspaceRoot, 'build', 'backend'),
75
+ env: { WEBSTIR_BACKEND_CACHE_LOG: 'off' },
76
+ diagnostics
77
+ });
78
+
79
+ await reporter.diffOutputs({ 'index.js': 128 }, 'build');
80
+ await reporter.diffOutputs({ 'index.js': 256 }, 'build');
81
+ await reporter.diffManifest(makeManifest());
82
+ await reporter.diffManifest(
83
+ makeManifest({
84
+ routes: [{ method: 'POST', path: '/silent' }]
85
+ })
86
+ );
87
+
88
+ assert.equal(diagnostics.length, 0, 'expected diagnostics to stay empty when logging disabled');
89
+ });
@@ -0,0 +1,64 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+
8
+ import { backendProvider } from '../dist/index.js';
9
+
10
+ async function createTempWorkspace(prefix = 'webstir-backend-env-') {
11
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
12
+ return dir;
13
+ }
14
+
15
+ async function copyFile(src, dest) {
16
+ await fs.mkdir(path.dirname(dest), { recursive: true });
17
+ await fs.copyFile(src, dest);
18
+ }
19
+
20
+ function getLocalBinPath() {
21
+ const here = path.dirname(fileURLToPath(import.meta.url));
22
+ const pkgRoot = path.resolve(here, '..');
23
+ return path.join(pkgRoot, 'node_modules', '.bin');
24
+ }
25
+
26
+ test('env loader reads .env files and surfaces typed config', async () => {
27
+ const workspace = await createTempWorkspace();
28
+ const assets = await backendProvider.getScaffoldAssets();
29
+ for (const asset of assets) {
30
+ await copyFile(asset.sourcePath, path.join(workspace, asset.targetPath));
31
+ }
32
+ await fs.writeFile(
33
+ path.join(workspace, 'package.json'),
34
+ JSON.stringify(
35
+ {
36
+ name: '@demo/env-loader',
37
+ version: '0.0.0',
38
+ type: 'module'
39
+ },
40
+ null,
41
+ 2
42
+ ),
43
+ 'utf8'
44
+ );
45
+
46
+ const envContents = `NODE_ENV=development\nPORT=5055\nAPI_BASE_URL=https://api.example.com\n`;
47
+ await fs.writeFile(path.join(workspace, '.env'), envContents, 'utf8');
48
+
49
+ const env = {
50
+ WEBSTIR_MODULE_MODE: 'build',
51
+ WEBSTIR_BACKEND_TYPECHECK: 'skip',
52
+ NODE_OPTIONS: '--experimental-transform-types',
53
+ PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`
54
+ };
55
+
56
+ await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
57
+
58
+ const builtEnvModule = path.join(workspace, 'build', 'backend', 'env.js');
59
+ const mod = await import(pathToFileURL(builtEnvModule).href);
60
+ const loaded = mod.loadEnv();
61
+
62
+ assert.equal(loaded.PORT, 5055);
63
+ assert.equal(loaded.API_BASE_URL, 'https://api.example.com');
64
+ });
@@ -0,0 +1,108 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import fssync from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ import { backendProvider } from '../dist/index.js';
10
+
11
+ async function createTempWorkspace(prefix = 'webstir-backend-workspace-') {
12
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
13
+ return dir;
14
+ }
15
+
16
+ async function ensureDir(dir) {
17
+ await fs.mkdir(dir, { recursive: true });
18
+ }
19
+
20
+ async function copyFile(src, dest) {
21
+ await ensureDir(path.dirname(dest));
22
+ await fs.copyFile(src, dest);
23
+ }
24
+
25
+ async function hydrateBackendScaffold(workspace) {
26
+ const assets = await backendProvider.getScaffoldAssets();
27
+
28
+ for (const asset of assets) {
29
+ const normalized = asset.targetPath.replace(/\\/g, '/');
30
+ if (!normalized.includes('src/backend/')) {
31
+ continue;
32
+ }
33
+
34
+ const target = path.join(workspace, asset.targetPath);
35
+ await copyFile(asset.sourcePath, target);
36
+ }
37
+ }
38
+
39
+
40
+ function getLocalBinPath() {
41
+ const here = path.dirname(fileURLToPath(import.meta.url));
42
+ const pkgRoot = path.resolve(here, '..');
43
+ return path.join(pkgRoot, 'node_modules', '.bin');
44
+ }
45
+
46
+ test('build mode produces transpiled output and manifest', async () => {
47
+ const workspace = await createTempWorkspace();
48
+ await hydrateBackendScaffold(workspace);
49
+
50
+ const bin = getLocalBinPath();
51
+ const env = {
52
+ WEBSTIR_MODULE_MODE: 'build',
53
+ WEBSTIR_BACKEND_TYPECHECK: 'skip',
54
+ PATH: `${bin}${path.delimiter}${process.env.PATH ?? ''}`,
55
+ };
56
+
57
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
58
+
59
+ const buildRoot = path.join(workspace, 'build', 'backend');
60
+ const outFile = path.join(buildRoot, 'index.js');
61
+ assert.equal(fssync.existsSync(outFile), true, 'expected build/backend/index.js to exist');
62
+
63
+ assert.ok(Array.isArray(result.manifest.entryPoints));
64
+ assert.ok(result.manifest.entryPoints.some((e) => e.endsWith('index.js')));
65
+ });
66
+
67
+ test('publish mode bundles output and manifest has entry', async () => {
68
+ const workspace = await createTempWorkspace();
69
+ await hydrateBackendScaffold(workspace);
70
+
71
+ const bin = getLocalBinPath();
72
+ const env = {
73
+ WEBSTIR_MODULE_MODE: 'publish',
74
+ WEBSTIR_BACKEND_TYPECHECK: 'skip',
75
+ PATH: `${bin}${path.delimiter}${process.env.PATH ?? ''}`,
76
+ };
77
+
78
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
79
+
80
+ const buildRoot = path.join(workspace, 'build', 'backend');
81
+ const outFile = path.join(buildRoot, 'index.js');
82
+ assert.equal(fssync.existsSync(outFile), true, 'expected build/backend/index.js to exist');
83
+
84
+ assert.ok(result.manifest.entryPoints.length >= 1);
85
+ });
86
+
87
+ test('publish mode emits sourcemaps when opt-in flag is set', async () => {
88
+ const workspace = await createTempWorkspace();
89
+ await hydrateBackendScaffold(workspace);
90
+
91
+ const bin = getLocalBinPath();
92
+ const env = {
93
+ WEBSTIR_MODULE_MODE: 'publish',
94
+ WEBSTIR_BACKEND_SOURCEMAPS: 'on',
95
+ WEBSTIR_BACKEND_TYPECHECK: 'skip',
96
+ PATH: `${bin}${path.delimiter}${process.env.PATH ?? ''}`,
97
+ };
98
+
99
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
100
+
101
+ const buildRoot = path.join(workspace, 'build', 'backend');
102
+ const mapFile = path.join(buildRoot, 'index.js.map');
103
+ assert.equal(fssync.existsSync(mapFile), true, 'expected build/backend/index.js.map to exist');
104
+ assert.ok(
105
+ result.artifacts.some((artifact) => artifact.path.endsWith('index.js.map') && artifact.type === 'asset'),
106
+ 'expected index.js.map to be included as an asset artifact'
107
+ );
108
+ });
@@ -0,0 +1,159 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { backendProvider } from '../dist/index.js';
9
+
10
+ async function createTempWorkspace(prefix = 'webstir-backend-manifest-') {
11
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
12
+ return dir;
13
+ }
14
+
15
+ async function ensureDir(dir) {
16
+ await fs.mkdir(dir, { recursive: true });
17
+ }
18
+
19
+ async function copyFile(src, dest) {
20
+ await ensureDir(path.dirname(dest));
21
+ await fs.copyFile(src, dest);
22
+ }
23
+
24
+ function getLocalBinPath() {
25
+ const here = path.dirname(fileURLToPath(import.meta.url));
26
+ const pkgRoot = path.resolve(here, '..');
27
+ return path.join(pkgRoot, 'node_modules', '.bin');
28
+ }
29
+
30
+ async function seedBackendEntry(workspace) {
31
+ const assets = await backendProvider.getScaffoldAssets();
32
+ for (const asset of assets) {
33
+ if (!asset.targetPath.endsWith(path.join('backend', 'index.ts'))) continue;
34
+ const target = path.join(workspace, asset.targetPath);
35
+ await copyFile(asset.sourcePath, target);
36
+ }
37
+ }
38
+
39
+ test('manifest loader honors package overrides', async () => {
40
+ const workspace = await createTempWorkspace();
41
+ await seedBackendEntry(workspace);
42
+
43
+ const pkgJson = {
44
+ name: '@demo/backend',
45
+ version: '1.0.0',
46
+ type: 'module',
47
+ webstir: {
48
+ moduleManifest: {
49
+ contractVersion: '1.0.0',
50
+ name: '@demo/custom',
51
+ version: '2.0.0',
52
+ kind: 'backend',
53
+ capabilities: ['db']
54
+ }
55
+ }
56
+ };
57
+ await fs.writeFile(path.join(workspace, 'package.json'), JSON.stringify(pkgJson, null, 2), 'utf8');
58
+
59
+ const env = {
60
+ WEBSTIR_MODULE_MODE: 'build',
61
+ PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`
62
+ };
63
+
64
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
65
+ const moduleManifest = result.manifest.module;
66
+
67
+ assert.equal(moduleManifest?.name, '@demo/custom');
68
+ assert.equal(moduleManifest?.version, '2.0.0');
69
+ assert.deepEqual(moduleManifest?.capabilities, ['db']);
70
+ });
71
+
72
+ test('manifest loader falls back to package name/version when no overrides present', async () => {
73
+ const workspace = await createTempWorkspace();
74
+ await seedBackendEntry(workspace);
75
+
76
+ const pkgJson = {
77
+ name: '@demo/fallback',
78
+ version: '4.5.6',
79
+ type: 'module'
80
+ };
81
+ await fs.writeFile(path.join(workspace, 'package.json'), JSON.stringify(pkgJson, null, 2), 'utf8');
82
+
83
+ const env = {
84
+ WEBSTIR_MODULE_MODE: 'build',
85
+ PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`
86
+ };
87
+
88
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
89
+ const moduleManifest = result.manifest.module;
90
+
91
+ assert.equal(moduleManifest?.name, '@demo/fallback');
92
+ assert.equal(moduleManifest?.version, '4.5.6');
93
+ });
94
+
95
+ test('manifest loader merges compiled module definition metadata', async () => {
96
+ const workspace = await createTempWorkspace();
97
+ await seedBackendEntry(workspace);
98
+
99
+ const moduleSource = `export const module = {
100
+ manifest: {
101
+ contractVersion: '1.0.0',
102
+ name: '@demo/with-module',
103
+ version: '9.9.9',
104
+ kind: 'backend',
105
+ capabilities: ['search'],
106
+ routes: [],
107
+ views: []
108
+ }
109
+ };
110
+ `;
111
+
112
+ await fs.writeFile(path.join(workspace, 'src', 'backend', 'module.ts'), moduleSource, 'utf8');
113
+ await fs.writeFile(
114
+ path.join(workspace, 'package.json'),
115
+ JSON.stringify({ name: '@demo/fallback-package', version: '0.0.1', type: 'module' }, null, 2),
116
+ 'utf8'
117
+ );
118
+
119
+ const env = {
120
+ WEBSTIR_MODULE_MODE: 'build',
121
+ PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`
122
+ };
123
+
124
+ const result = await backendProvider.build({ workspaceRoot: workspace, env, incremental: false });
125
+ const moduleManifest = result.manifest.module;
126
+
127
+ assert.equal(moduleManifest?.name, '@demo/with-module');
128
+ assert.equal(moduleManifest?.version, '9.9.9');
129
+ assert.deepEqual(moduleManifest?.capabilities, ['search']);
130
+ assert.deepEqual(moduleManifest?.capabilities, ['search']);
131
+ });
132
+
133
+ test('scaffold assets expose core backend templates', async () => {
134
+ const assets = await backendProvider.getScaffoldAssets();
135
+ const targetSet = new Set(assets.map((asset) => asset.targetPath));
136
+
137
+ const requiredTargets = [
138
+ path.join('src', 'backend', 'tsconfig.json'),
139
+ path.join('src', 'backend', 'index.ts'),
140
+ path.join('src', 'backend', 'module.ts'),
141
+ path.join('src', 'backend', 'server', 'fastify.ts'),
142
+ path.join('src', 'backend', 'auth', 'adapter.ts'),
143
+ path.join('src', 'backend', 'observability', 'logger.ts'),
144
+ path.join('src', 'backend', 'observability', 'metrics.ts'),
145
+ path.join('src', 'backend', 'functions', 'hello', 'index.ts'),
146
+ path.join('src', 'backend', 'jobs', 'nightly', 'index.ts'),
147
+ path.join('src', 'backend', 'jobs', 'runtime.ts'),
148
+ path.join('src', 'backend', 'jobs', 'scheduler.ts'),
149
+ path.join('src', 'backend', 'db', 'connection.ts'),
150
+ path.join('src', 'backend', 'db', 'migrate.ts'),
151
+ path.join('src', 'backend', 'db', 'migrations', '0001-example.ts'),
152
+ path.join('src', 'backend', 'db', 'types.d.ts'),
153
+ path.join('.env.example')
154
+ ];
155
+
156
+ for (const target of requiredTargets) {
157
+ assert.ok(targetSet.has(target), `expected scaffold assets to include ${target}`);
158
+ }
159
+ });
@@ -0,0 +1,100 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ import { startBackendWatch } from '../dist/watch.js';
9
+ import { backendProvider } from '../dist/index.js';
10
+
11
+ async function createTempWorkspace(prefix = 'webstir-backend-watch-') {
12
+ const dir = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
13
+ return dir;
14
+ }
15
+
16
+ async function ensureDir(dir) {
17
+ await fs.mkdir(dir, { recursive: true });
18
+ }
19
+
20
+ async function copyFile(src, dest) {
21
+ await ensureDir(path.dirname(dest));
22
+ await fs.copyFile(src, dest);
23
+ }
24
+
25
+ async function seedBackendEntry(workspace) {
26
+ const assets = await backendProvider.getScaffoldAssets();
27
+ for (const asset of assets) {
28
+ if (!asset.targetPath.endsWith(path.join('backend', 'index.ts'))) continue;
29
+ const target = path.join(workspace, asset.targetPath);
30
+ await copyFile(asset.sourcePath, target);
31
+ }
32
+ }
33
+
34
+ function getLocalBinPath() {
35
+ const here = path.dirname(fileURLToPath(import.meta.url));
36
+ const pkgRoot = path.resolve(here, '..');
37
+ return path.join(pkgRoot, 'node_modules', '.bin');
38
+ }
39
+
40
+ async function waitFor(checkFn, timeoutMs = 5000, pollMs = 100) {
41
+ const start = Date.now();
42
+ while (Date.now() - start < timeoutMs) {
43
+ if (await checkFn()) return;
44
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
45
+ }
46
+ throw new Error('waitFor timed out');
47
+ }
48
+
49
+ test('startBackendWatch updates cache files after rebuild', async () => {
50
+ const workspace = await createTempWorkspace();
51
+ await seedBackendEntry(workspace);
52
+
53
+ const env = {
54
+ WEBSTIR_MODULE_MODE: 'build',
55
+ WEBSTIR_BACKEND_TYPECHECK: 'skip',
56
+ PATH: `${getLocalBinPath()}${path.delimiter}${process.env.PATH ?? ''}`
57
+ };
58
+
59
+ const handle = await startBackendWatch({ workspaceRoot: workspace, env });
60
+ try {
61
+ const outputsPath = path.join(workspace, '.webstir', 'backend-outputs.json');
62
+
63
+ await waitFor(async () => {
64
+ try {
65
+ await fs.access(outputsPath);
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ });
71
+
72
+ const before = JSON.parse(await fs.readFile(outputsPath, 'utf8'));
73
+ const indexPath = path.join(workspace, 'src', 'backend', 'index.ts');
74
+ await fs.appendFile(indexPath, '\nconsole.log("watch-test");\n', 'utf8');
75
+
76
+ await waitFor(async () => {
77
+ try {
78
+ const after = JSON.parse(await fs.readFile(outputsPath, 'utf8'));
79
+ const key = Object.keys(after)[0];
80
+ return before[key] !== after[key];
81
+ } catch {
82
+ return false;
83
+ }
84
+ });
85
+
86
+ const manifestDigestPath = path.join(workspace, '.webstir', 'backend-manifest-digest.json');
87
+ await waitFor(async () => {
88
+ try {
89
+ await fs.access(manifestDigestPath);
90
+ return true;
91
+ } catch {
92
+ return false;
93
+ }
94
+ });
95
+
96
+ assert.ok(true, 'watch updated cache files after rebuild');
97
+ } finally {
98
+ await handle.stop();
99
+ }
100
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "skipLibCheck": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src",
12
+ "declaration": true,
13
+ "declarationMap": false,
14
+ "sourceMap": false,
15
+ "types": [
16
+ "node"
17
+ ],
18
+ "resolveJsonModule": true
19
+ },
20
+ "include": [
21
+ "src/**/*"
22
+ ],
23
+ "exclude": [
24
+ "dist",
25
+ "node_modules"
26
+ ]
27
+ }