@webstir-io/webstir 0.1.1 → 0.1.2

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 (77) hide show
  1. package/README.md +13 -0
  2. package/assets/deployment/docker/.dockerignore +7 -0
  3. package/assets/deployment/docker/Dockerfile +17 -0
  4. package/assets/deployment/docker/README.md +44 -0
  5. package/assets/deployment/docker/example.env +3 -0
  6. package/assets/features/client_nav/client_nav.ts +369 -264
  7. package/assets/features/client_nav/document_navigation.ts +344 -0
  8. package/assets/features/client_nav/form_enhancement.ts +275 -0
  9. package/assets/templates/api/src/backend/index.ts +71 -10
  10. package/assets/templates/api/src/backend/tsconfig.json +6 -1
  11. package/assets/templates/full/src/backend/index.ts +71 -10
  12. package/assets/templates/full/src/backend/module.ts +515 -0
  13. package/assets/templates/full/src/backend/tests/progressive-enhancement.test.ts +180 -0
  14. package/assets/templates/full/src/backend/tsconfig.json +6 -1
  15. package/assets/templates/full/src/frontend/app/scripts/features/client-nav.ts +574 -0
  16. package/assets/templates/full/src/frontend/app/scripts/features/document-navigation.ts +344 -0
  17. package/assets/templates/full/src/frontend/app/scripts/features/form-enhancement.ts +275 -0
  18. package/assets/templates/full/src/frontend/pages/home/index.css +8 -0
  19. package/assets/templates/full/src/frontend/pages/home/index.html +6 -1
  20. package/assets/templates/full/src/frontend/pages/home/tests/home.test.ts +12 -2
  21. package/assets/templates/spa/src/frontend/pages/home/tests/home.test.ts +10 -2
  22. package/package.json +31 -13
  23. package/scripts/check-feature-projections.mjs +87 -0
  24. package/scripts/check-full-demo-sync.mjs +89 -0
  25. package/scripts/check-package-install.mjs +537 -0
  26. package/scripts/check-standalone-install.mjs +221 -0
  27. package/scripts/pack-standalone.mjs +52 -28
  28. package/scripts/publish.sh +9 -0
  29. package/scripts/run-tests.mjs +99 -0
  30. package/scripts/sync-assets.mjs +175 -17
  31. package/src/add-backend-compat.ts +628 -0
  32. package/src/add-backend.ts +155 -27
  33. package/src/add.ts +111 -4
  34. package/src/agent.ts +393 -0
  35. package/src/api-watch.ts +7 -4
  36. package/src/backend-inspect.ts +70 -2
  37. package/src/backend-runtime.ts +22 -14
  38. package/src/build.ts +1 -3
  39. package/src/bun-generated-frontend-watch.ts +209 -0
  40. package/src/bun-globals.d.ts +23 -0
  41. package/src/bun-spa-document.ts +310 -0
  42. package/src/bun-spa-routes.ts +159 -0
  43. package/src/bun-spa-watch.ts +29 -0
  44. package/src/bun-ssg-watch.ts +304 -0
  45. package/src/cli.ts +381 -50
  46. package/src/compile-tests.ts +37 -29
  47. package/src/dev-server.ts +214 -143
  48. package/src/doctor.ts +164 -0
  49. package/src/enable-assets.ts +18 -1
  50. package/src/enable.ts +133 -41
  51. package/src/execute.ts +28 -4
  52. package/src/external-workspace.ts +178 -0
  53. package/src/format.ts +296 -17
  54. package/src/frontend-inspect.ts +32 -0
  55. package/src/frontend-watch.ts +27 -102
  56. package/src/full-watch.ts +13 -18
  57. package/src/index.ts +7 -0
  58. package/src/init-assets.ts +41 -11
  59. package/src/init.ts +85 -71
  60. package/src/inspect.ts +112 -0
  61. package/src/mcp/run-cli-json.ts +46 -0
  62. package/src/mcp/server.ts +307 -0
  63. package/src/operations.ts +176 -0
  64. package/src/providers.ts +20 -18
  65. package/src/refresh.ts +29 -3
  66. package/src/repair.ts +110 -43
  67. package/src/runtime-filter.ts +41 -0
  68. package/src/runtime.ts +1 -1
  69. package/src/smoke.ts +48 -16
  70. package/src/test.ts +54 -16
  71. package/src/testing-runtime.ts +273 -0
  72. package/src/types.ts +1 -4
  73. package/src/watch-events.ts +46 -17
  74. package/src/watch.ts +5 -1
  75. package/src/workspace-watcher.ts +10 -6
  76. package/src/workspace.ts +4 -2
  77. package/src/watch-daemon-client.ts +0 -171
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { cp, mkdir, rm } from 'node:fs/promises';
3
+ import { cp, mkdir, mkdtemp, readdir, readFile, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
4
5
  import path from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
@@ -8,8 +9,12 @@ const here = path.dirname(fileURLToPath(import.meta.url));
8
9
  const packageRoot = path.resolve(here, '..');
9
10
  const repoRoot = path.resolve(packageRoot, '..', '..');
10
11
  const assetsRoot = path.join(packageRoot, 'assets');
11
- const templatesRoot = path.join(assetsRoot, 'templates');
12
- const featuresRoot = path.join(assetsRoot, 'features');
12
+ const resourcesRoot = path.join(packageRoot, 'resources');
13
+ const templateSourcesRoot = path.join(resourcesRoot, 'templates');
14
+ const deploymentSourcesRoot = path.join(resourcesRoot, 'deployment');
15
+ const dotnetRoot = path.join(repoRoot, 'orchestrators', 'dotnet');
16
+ const demosRoot = path.join(repoRoot, 'examples', 'demos');
17
+ const checkOnly = process.argv.includes('--check');
13
18
 
14
19
  const rootAssets = [
15
20
  'Errors.404.html',
@@ -23,47 +28,87 @@ const modeTemplates = [
23
28
  {
24
29
  mode: 'ssg',
25
30
  roots: [
26
- { source: path.join(repoRoot, 'examples', 'demos', 'ssg', 'base', 'src', 'frontend'), target: path.join('src', 'frontend') },
31
+ {
32
+ source: path.join(templateSourcesRoot, 'ssg', 'src', 'frontend'),
33
+ target: path.join('src', 'frontend'),
34
+ },
27
35
  ],
28
36
  },
29
37
  {
30
38
  mode: 'spa',
31
39
  roots: [
32
- { source: path.join(repoRoot, 'examples', 'demos', 'spa', 'src', 'frontend'), target: path.join('src', 'frontend') },
33
- { source: path.join(repoRoot, 'examples', 'demos', 'spa', 'src', 'shared'), target: path.join('src', 'shared') },
40
+ {
41
+ source: path.join(templateSourcesRoot, 'spa', 'src', 'frontend'),
42
+ target: path.join('src', 'frontend'),
43
+ },
44
+ {
45
+ source: path.join(templateSourcesRoot, 'spa', 'src', 'shared'),
46
+ target: path.join('src', 'shared'),
47
+ },
34
48
  ],
35
49
  },
36
50
  {
37
51
  mode: 'api',
38
52
  roots: [
39
- { source: path.join(repoRoot, 'examples', 'demos', 'api', 'src', 'backend'), target: path.join('src', 'backend') },
40
- { source: path.join(repoRoot, 'examples', 'demos', 'api', 'src', 'shared'), target: path.join('src', 'shared') },
53
+ {
54
+ source: path.join(templateSourcesRoot, 'api', 'src', 'backend'),
55
+ target: path.join('src', 'backend'),
56
+ },
57
+ {
58
+ source: path.join(templateSourcesRoot, 'api', 'src', 'shared'),
59
+ target: path.join('src', 'shared'),
60
+ },
41
61
  ],
42
62
  },
43
63
  {
44
64
  mode: 'full',
45
65
  roots: [
46
- { source: path.join(repoRoot, 'examples', 'demos', 'full', 'src', 'frontend'), target: path.join('src', 'frontend') },
47
- { source: path.join(repoRoot, 'examples', 'demos', 'full', 'src', 'backend'), target: path.join('src', 'backend') },
48
- { source: path.join(repoRoot, 'examples', 'demos', 'full', 'src', 'shared'), target: path.join('src', 'shared') },
66
+ {
67
+ source: path.join(templateSourcesRoot, 'full', 'src', 'frontend'),
68
+ target: path.join('src', 'frontend'),
69
+ },
70
+ {
71
+ source: path.join(templateSourcesRoot, 'full', 'src', 'backend'),
72
+ target: path.join('src', 'backend'),
73
+ },
74
+ {
75
+ source: path.join(templateSourcesRoot, 'full', 'src', 'shared'),
76
+ target: path.join('src', 'shared'),
77
+ },
49
78
  ],
50
79
  },
51
80
  ];
52
81
 
53
82
  const features = [
54
- { source: path.join(repoRoot, 'orchestrators', 'dotnet', 'Engine', 'Resources', 'features', 'router'), target: 'router' },
55
- { source: path.join(repoRoot, 'orchestrators', 'dotnet', 'Engine', 'Resources', 'features', 'client_nav'), target: 'client_nav' },
56
- { source: path.join(repoRoot, 'orchestrators', 'dotnet', 'Engine', 'Resources', 'features', 'search'), target: 'search' },
57
- { source: path.join(repoRoot, 'orchestrators', 'dotnet', 'Engine', 'Resources', 'features', 'content_nav'), target: 'content_nav' },
83
+ { source: path.join(packageRoot, 'resources', 'features', 'router'), target: 'router' },
84
+ { source: path.join(packageRoot, 'resources', 'features', 'client_nav'), target: 'client_nav' },
85
+ { source: path.join(packageRoot, 'resources', 'features', 'search'), target: 'search' },
86
+ { source: path.join(packageRoot, 'resources', 'features', 'content_nav'), target: 'content_nav' },
58
87
  ];
59
88
 
60
89
  async function main() {
61
- await rm(assetsRoot, { recursive: true, force: true });
90
+ assertNoLegacyAssetReads();
91
+ if (checkOnly) {
92
+ await assertAssetsInSync();
93
+ console.log('[webstir] assets OK');
94
+ return;
95
+ }
96
+
97
+ await materializeAssets(assetsRoot);
98
+ }
99
+
100
+ async function materializeAssets(targetAssetsRoot) {
101
+ const templatesRoot = path.join(targetAssetsRoot, 'templates');
102
+ const featuresRoot = path.join(targetAssetsRoot, 'features');
103
+ const deploymentRoot = path.join(targetAssetsRoot, 'deployment');
104
+
105
+ await rm(targetAssetsRoot, { recursive: true, force: true });
62
106
  await mkdir(templatesRoot, { recursive: true });
63
107
  await mkdir(featuresRoot, { recursive: true });
108
+ await mkdir(deploymentRoot, { recursive: true });
64
109
 
65
110
  for (const relativePath of rootAssets) {
66
- const sourcePath = path.join(repoRoot, 'examples', 'demos', 'spa', relativePath);
111
+ const sourcePath = path.join(templateSourcesRoot, 'shared', relativePath);
67
112
  const targetPath = path.join(templatesRoot, 'shared', relativePath);
68
113
  await mkdir(path.dirname(targetPath), { recursive: true });
69
114
  await cp(sourcePath, targetPath, { recursive: true });
@@ -82,6 +127,119 @@ async function main() {
82
127
  await mkdir(path.dirname(targetPath), { recursive: true });
83
128
  await cp(feature.source, targetPath, { recursive: true });
84
129
  }
130
+
131
+ await cp(deploymentSourcesRoot, deploymentRoot, { recursive: true });
132
+ }
133
+
134
+ async function assertAssetsInSync() {
135
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'webstir-assets-'));
136
+ const expectedAssetsRoot = path.join(tempRoot, 'assets');
137
+
138
+ try {
139
+ await materializeAssets(expectedAssetsRoot);
140
+ const differences = await collectDirectoryDifferences(expectedAssetsRoot, assetsRoot);
141
+ if (differences.length === 0) {
142
+ return;
143
+ }
144
+
145
+ throw new Error(
146
+ [
147
+ 'Generated Bun assets are out of sync with orchestrators/bun/resources.',
148
+ 'Run `bun run --filter @webstir-io/webstir build` or `bun scripts/sync-assets.mjs` from orchestrators/bun.',
149
+ ...differences.map((difference) => ` - ${difference}`),
150
+ ].join('\n'),
151
+ );
152
+ } finally {
153
+ await rm(tempRoot, { recursive: true, force: true });
154
+ }
155
+ }
156
+
157
+ async function collectDirectoryDifferences(expectedRoot, actualRoot) {
158
+ const [expectedEntries, actualEntries] = await Promise.all([
159
+ collectEntries(expectedRoot),
160
+ collectEntries(actualRoot),
161
+ ]);
162
+
163
+ const differences = [];
164
+ const paths = [...new Set([...expectedEntries.keys(), ...actualEntries.keys()])].sort();
165
+
166
+ for (const relativePath of paths) {
167
+ const expected = expectedEntries.get(relativePath);
168
+ const actual = actualEntries.get(relativePath);
169
+
170
+ if (!expected) {
171
+ differences.push(`unexpected ${actual.type} in assets: ${relativePath}`);
172
+ continue;
173
+ }
174
+
175
+ if (!actual) {
176
+ differences.push(`missing ${expected.type} in assets: ${relativePath}`);
177
+ continue;
178
+ }
179
+
180
+ if (expected.type !== actual.type) {
181
+ differences.push(
182
+ `type mismatch for ${relativePath}: expected ${expected.type}, found ${actual.type}`,
183
+ );
184
+ continue;
185
+ }
186
+
187
+ if (expected.type === 'file' && !expected.content.equals(actual.content)) {
188
+ differences.push(`content mismatch in assets: ${relativePath}`);
189
+ }
190
+ }
191
+
192
+ return differences;
193
+ }
194
+
195
+ async function collectEntries(root, currentPath = root, entries = new Map()) {
196
+ const children = await readdir(currentPath, { withFileTypes: true });
197
+
198
+ for (const child of children) {
199
+ const childPath = path.join(currentPath, child.name);
200
+ const relativePath = path.relative(root, childPath);
201
+
202
+ if (child.isDirectory()) {
203
+ entries.set(relativePath, { type: 'directory' });
204
+ await collectEntries(root, childPath, entries);
205
+ continue;
206
+ }
207
+
208
+ if (child.isFile()) {
209
+ entries.set(relativePath, { type: 'file', content: await readFile(childPath) });
210
+ }
211
+ }
212
+
213
+ return entries;
214
+ }
215
+
216
+ function assertNoLegacyAssetReads() {
217
+ const sources = [
218
+ ...rootAssets.map((relativePath) => path.join(templateSourcesRoot, 'shared', relativePath)),
219
+ ...modeTemplates.flatMap((template) => template.roots.map((root) => root.source)),
220
+ ...features.map((feature) => feature.source),
221
+ deploymentSourcesRoot,
222
+ ];
223
+
224
+ assertSourcesOutsideRoot(dotnetRoot, 'orchestrators/dotnet', sources);
225
+ assertSourcesOutsideRoot(demosRoot, 'examples/demos', sources);
226
+ }
227
+
228
+ function assertSourcesOutsideRoot(disallowedRoot, label, sources) {
229
+ const violations = sources.filter((source) => isInside(disallowedRoot, source));
230
+ if (violations.length === 0) {
231
+ return;
232
+ }
233
+
234
+ const details = violations.map((source) => ` - ${path.relative(repoRoot, source)}`).join('\n');
235
+ throw new Error(
236
+ `Bun asset sync cannot read active assets from ${label}.\nMove the source into orchestrators/bun/resources first.\n${details}`,
237
+ );
238
+ }
239
+
240
+ function isInside(root, candidate) {
241
+ const relative = path.relative(root, path.resolve(candidate));
242
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
85
243
  }
86
244
 
87
245
  await main();