@webstir-io/webstir 0.1.1 → 0.1.3
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 +13 -0
- package/assets/deployment/docker/.dockerignore +7 -0
- package/assets/deployment/docker/Dockerfile +17 -0
- package/assets/deployment/docker/README.md +44 -0
- package/assets/deployment/docker/example.env +3 -0
- package/assets/features/client_nav/client_nav.ts +369 -264
- package/assets/features/client_nav/document_navigation.ts +344 -0
- package/assets/features/client_nav/form_enhancement.ts +275 -0
- package/assets/templates/api/src/backend/index.ts +71 -10
- package/assets/templates/api/src/backend/tsconfig.json +6 -1
- package/assets/templates/full/src/backend/index.ts +71 -10
- package/assets/templates/full/src/backend/module.ts +515 -0
- package/assets/templates/full/src/backend/tests/progressive-enhancement.test.ts +180 -0
- package/assets/templates/full/src/backend/tsconfig.json +6 -1
- package/assets/templates/full/src/frontend/app/scripts/features/client-nav.ts +574 -0
- package/assets/templates/full/src/frontend/app/scripts/features/document-navigation.ts +344 -0
- package/assets/templates/full/src/frontend/app/scripts/features/form-enhancement.ts +275 -0
- package/assets/templates/full/src/frontend/pages/home/index.css +8 -0
- package/assets/templates/full/src/frontend/pages/home/index.html +6 -1
- package/assets/templates/full/src/frontend/pages/home/tests/home.test.ts +12 -2
- package/assets/templates/spa/src/frontend/pages/home/tests/home.test.ts +10 -2
- package/package.json +31 -13
- package/scripts/check-feature-projections.mjs +87 -0
- package/scripts/check-full-demo-sync.mjs +89 -0
- package/scripts/check-package-install.mjs +537 -0
- package/scripts/check-standalone-install.mjs +221 -0
- package/scripts/pack-standalone.mjs +52 -28
- package/scripts/publish.sh +9 -0
- package/scripts/run-tests.mjs +103 -0
- package/scripts/sync-assets.mjs +175 -17
- package/src/add-backend-compat.ts +628 -0
- package/src/add-backend.ts +155 -27
- package/src/add.ts +111 -4
- package/src/agent.ts +393 -0
- package/src/api-watch.ts +7 -4
- package/src/backend-inspect.ts +70 -2
- package/src/backend-runtime.ts +22 -14
- package/src/build.ts +1 -3
- package/src/bun-generated-frontend-watch.ts +209 -0
- package/src/bun-globals.d.ts +23 -0
- package/src/bun-spa-document.ts +310 -0
- package/src/bun-spa-routes.ts +159 -0
- package/src/bun-spa-watch.ts +29 -0
- package/src/bun-ssg-watch.ts +304 -0
- package/src/cli.ts +381 -50
- package/src/compile-tests.ts +37 -29
- package/src/dev-server.ts +215 -144
- package/src/doctor.ts +164 -0
- package/src/enable-assets.ts +18 -1
- package/src/enable.ts +133 -41
- package/src/execute.ts +30 -4
- package/src/external-workspace.ts +178 -0
- package/src/format.ts +296 -17
- package/src/frontend-inspect.ts +32 -0
- package/src/frontend-watch.ts +27 -102
- package/src/full-watch.ts +13 -18
- package/src/index.ts +7 -0
- package/src/init-assets.ts +41 -11
- package/src/init.ts +85 -71
- package/src/inspect.ts +112 -0
- package/src/mcp/run-cli-json.ts +46 -0
- package/src/mcp/server.ts +307 -0
- package/src/operations.ts +176 -0
- package/src/providers.ts +20 -18
- package/src/refresh.ts +29 -3
- package/src/repair.ts +110 -43
- package/src/runtime-filter.ts +41 -0
- package/src/runtime.ts +1 -1
- package/src/smoke.ts +48 -16
- package/src/test.ts +54 -16
- package/src/testing-runtime.ts +273 -0
- package/src/types.ts +1 -4
- package/src/watch-events.ts +46 -17
- package/src/watch.ts +25 -14
- package/src/workspace-lock.ts +207 -0
- package/src/workspace-watcher.ts +10 -6
- package/src/workspace.ts +4 -2
- package/src/watch-daemon-client.ts +0 -171
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { assert, test } from '@webstir-io/webstir-testing';
|
|
2
|
+
|
|
3
|
+
test('progressive enhancement demo page renders a form shell', async () => {
|
|
4
|
+
const ctx = requireBackendTestContext();
|
|
5
|
+
const response = await ctx.request('/demo/progressive-enhancement');
|
|
6
|
+
const html = await response.text();
|
|
7
|
+
|
|
8
|
+
assert.equal(response.status, 200);
|
|
9
|
+
assert.isTrue(html.includes('<title>Progressive Enhancement Demo</title>'));
|
|
10
|
+
assert.isTrue(html.includes('id="greeting-form"'));
|
|
11
|
+
assert.isTrue(html.includes('data-webstir-fragment-target="greeting-preview"'));
|
|
12
|
+
assert.isTrue(html.includes('data-webstir-fragment-target="session-panel"'));
|
|
13
|
+
assert.isTrue(
|
|
14
|
+
html.includes('type="module" src="/src/frontend/app/app.ts"')
|
|
15
|
+
|| html.includes('type="module" src="/app/')
|
|
16
|
+
|| html.includes('type="module" src="/pages/home/index.js"')
|
|
17
|
+
);
|
|
18
|
+
assert.isTrue(
|
|
19
|
+
html.includes('rel="stylesheet" href="/src/frontend/app/app.css"')
|
|
20
|
+
|| html.includes('rel="stylesheet" href="/app/')
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('native form submissions redirect back to the document route', async () => {
|
|
25
|
+
const ctx = requireBackendTestContext();
|
|
26
|
+
const response = await ctx.request('/demo/progressive-enhancement', {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: {
|
|
29
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
30
|
+
},
|
|
31
|
+
body: 'name=Native+Flow',
|
|
32
|
+
redirect: 'manual'
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
assert.equal(response.status, 303);
|
|
36
|
+
assert.equal(
|
|
37
|
+
response.headers.get('location'),
|
|
38
|
+
'/demo/progressive-enhancement?source=redirect&name=Native%20Flow'
|
|
39
|
+
);
|
|
40
|
+
assert.equal(response.headers.get('x-webstir-fragment-target'), null);
|
|
41
|
+
assert.equal(response.headers.get('content-type'), null);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('redirected document route preserves the no-javascript form flow', async () => {
|
|
45
|
+
const ctx = requireBackendTestContext();
|
|
46
|
+
const response = await ctx.request('/demo/progressive-enhancement?source=redirect&name=Native%20Flow');
|
|
47
|
+
const html = await response.text();
|
|
48
|
+
|
|
49
|
+
assert.equal(response.status, 200);
|
|
50
|
+
assert.isTrue(html.includes('Last submit used the no-JavaScript redirect path.'));
|
|
51
|
+
assert.isTrue(html.includes('Hello, Native Flow'));
|
|
52
|
+
assert.isTrue(html.includes('The browser completed a full-page redirect after the form POST.'));
|
|
53
|
+
assert.isTrue(html.includes('id="greeting-form"'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('enhanced form submissions return fragment metadata and html', async () => {
|
|
57
|
+
const ctx = requireBackendTestContext();
|
|
58
|
+
const response = await ctx.request('/demo/progressive-enhancement', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
62
|
+
'x-webstir-client-nav': '1'
|
|
63
|
+
},
|
|
64
|
+
body: 'name=Fragment+Flow'
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const html = await response.text();
|
|
68
|
+
|
|
69
|
+
assert.equal(response.status, 200);
|
|
70
|
+
assert.isTrue(String(response.headers.get('content-type')).includes('text/html'));
|
|
71
|
+
assert.equal(response.headers.get('x-webstir-fragment-target'), 'greeting-preview');
|
|
72
|
+
assert.equal(response.headers.get('x-webstir-fragment-selector'), '#greeting-preview');
|
|
73
|
+
assert.equal(response.headers.get('x-webstir-fragment-mode'), 'replace');
|
|
74
|
+
assert.isTrue(html.startsWith('<section id="greeting-preview"'));
|
|
75
|
+
assert.equal(html.includes('<!DOCTYPE html>'), false);
|
|
76
|
+
assert.isTrue(html.includes('Hello, Fragment Flow'));
|
|
77
|
+
assert.isTrue(html.includes('replace just this region'));
|
|
78
|
+
assert.isTrue(html.includes('id="greeting-update-focus"'));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('native session sign-in redirects and sets a session cookie', async () => {
|
|
82
|
+
const ctx = requireBackendTestContext();
|
|
83
|
+
const response = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
'content-type': 'application/x-www-form-urlencoded'
|
|
87
|
+
},
|
|
88
|
+
body: 'sessionName=Casey+Proxy',
|
|
89
|
+
redirect: 'manual'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
assert.equal(response.status, 303);
|
|
93
|
+
assert.equal(response.headers.get('location'), '/demo/progressive-enhancement?session=signed-in');
|
|
94
|
+
assert.isTrue(String(response.headers.get('set-cookie')).includes('webstir_demo_session=Casey%20Proxy'));
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('enhanced session sign-in returns a fragment and persists on the next document request', async () => {
|
|
98
|
+
const ctx = requireBackendTestContext();
|
|
99
|
+
const response = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
103
|
+
'x-webstir-client-nav': '1'
|
|
104
|
+
},
|
|
105
|
+
body: 'sessionName=Casey+Proxy'
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const html = await response.text();
|
|
109
|
+
const cookie = requireCookie(response.headers.get('set-cookie'));
|
|
110
|
+
|
|
111
|
+
assert.equal(response.status, 200);
|
|
112
|
+
assert.equal(response.headers.get('x-webstir-fragment-target'), 'session-panel');
|
|
113
|
+
assert.equal(response.headers.get('x-webstir-fragment-selector'), '#session-panel');
|
|
114
|
+
assert.equal(response.headers.get('x-webstir-fragment-mode'), 'replace');
|
|
115
|
+
assert.isTrue(html.includes('Signed in as <strong>Casey Proxy</strong>'));
|
|
116
|
+
assert.isTrue(html.includes('id="demo-sign-out"'));
|
|
117
|
+
|
|
118
|
+
const documentResponse = await ctx.request('/demo/progressive-enhancement', {
|
|
119
|
+
headers: {
|
|
120
|
+
cookie
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const documentHtml = await documentResponse.text();
|
|
124
|
+
|
|
125
|
+
assert.equal(documentResponse.status, 200);
|
|
126
|
+
assert.isTrue(documentHtml.includes('data-session-user="Casey Proxy"'));
|
|
127
|
+
assert.isTrue(documentHtml.includes('Reload the page to confirm the session persists.'));
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test('enhanced session sign-out returns a fragment and clears the session cookie', async () => {
|
|
131
|
+
const ctx = requireBackendTestContext();
|
|
132
|
+
const signInResponse = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
136
|
+
'x-webstir-client-nav': '1'
|
|
137
|
+
},
|
|
138
|
+
body: 'sessionName=Casey+Proxy'
|
|
139
|
+
});
|
|
140
|
+
const cookie = requireCookie(signInResponse.headers.get('set-cookie'));
|
|
141
|
+
|
|
142
|
+
const response = await ctx.request('/demo/progressive-enhancement/session/sign-out', {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'x-webstir-client-nav': '1',
|
|
146
|
+
cookie
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const html = await response.text();
|
|
151
|
+
|
|
152
|
+
assert.equal(response.status, 200);
|
|
153
|
+
assert.equal(response.headers.get('x-webstir-fragment-target'), 'session-panel');
|
|
154
|
+
assert.equal(response.headers.get('x-webstir-fragment-selector'), '#session-panel');
|
|
155
|
+
assert.equal(response.headers.get('x-webstir-fragment-mode'), 'replace');
|
|
156
|
+
assert.isTrue(String(response.headers.get('set-cookie')).includes('Max-Age=0'));
|
|
157
|
+
assert.isTrue(html.includes('Signed out without a full page reload.'));
|
|
158
|
+
assert.isTrue(html.includes('id="demo-sign-in"'));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
interface BackendTestContext {
|
|
162
|
+
request(pathOrUrl?: string | URL, init?: RequestInit): Promise<Response>;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function requireBackendTestContext(): BackendTestContext {
|
|
166
|
+
const store = globalThis as Record<string | symbol, unknown>;
|
|
167
|
+
const context = store[Symbol.for('webstir.backendTestContext')] as BackendTestContext | undefined;
|
|
168
|
+
if (!context) {
|
|
169
|
+
throw new Error('Backend test context not available.');
|
|
170
|
+
}
|
|
171
|
+
return context;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function requireCookie(value: string | null): string {
|
|
175
|
+
if (!value) {
|
|
176
|
+
throw new Error('Expected a session cookie header.');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return value.split(';', 1)[0] ?? value;
|
|
180
|
+
}
|
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
"extends": "../../base.tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
5
7
|
"lib": ["ES2022"],
|
|
6
8
|
"outDir": "../../build/backend",
|
|
7
9
|
"rootDir": ".",
|
|
8
10
|
"baseUrl": ".",
|
|
9
11
|
"incremental": true,
|
|
10
|
-
"tsBuildInfoFile": "../../build/backend/.tsbuildinfo"
|
|
12
|
+
"tsBuildInfoFile": "../../build/backend/.tsbuildinfo",
|
|
13
|
+
"types": ["node"],
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
11
16
|
},
|
|
12
17
|
"include": ["**/*.ts", "../../types/**/*.d.ts"],
|
|
13
18
|
"exclude": ["node_modules", "../../build", "../../dist"],
|