@webstir-io/webstir 0.1.2 → 0.1.4
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/assets/features/search/search.ts +0 -9
- package/assets/templates/api/src/backend/index.ts +4 -13
- package/assets/templates/full/src/backend/index.ts +4 -13
- package/assets/templates/full/src/backend/module.ts +2 -2
- package/assets/templates/full/src/backend/tests/progressive-enhancement.test.ts +11 -11
- package/package.json +3 -3
- package/scripts/run-tests.mjs +15 -11
- package/src/add-backend.ts +10 -72
- package/src/bun-spa-routes.ts +5 -18
- package/src/dev-server.ts +6 -19
- package/src/execute.ts +2 -0
- package/src/watch.ts +23 -16
- package/src/workspace-lock.ts +207 -0
- package/src/add-backend-compat.ts +0 -628
|
@@ -313,15 +313,6 @@ function ensureBackdrop(root: HTMLElement): void {
|
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
const legacy = root.querySelector<HTMLElement>('.webstir-search__backdrop');
|
|
317
|
-
if (legacy) {
|
|
318
|
-
legacy.classList.add('ws-drawer-backdrop');
|
|
319
|
-
legacy.classList.remove('webstir-search__backdrop');
|
|
320
|
-
legacy.setAttribute('data-webstir-search-close', '');
|
|
321
|
-
legacy.setAttribute('aria-hidden', 'true');
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
316
|
const created = document.createElement('div');
|
|
326
317
|
created.className = 'ws-drawer-backdrop';
|
|
327
318
|
created.setAttribute('data-webstir-search-close', '');
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
createDefaultBunBackendBootstrap,
|
|
@@ -54,19 +56,8 @@ export async function start(): Promise<void> {
|
|
|
54
56
|
);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const argv1 = process.argv?.[1];
|
|
60
|
-
if (!argv1) return false;
|
|
61
|
-
const here = new URL(import.meta.url);
|
|
62
|
-
const run = new URL(`file://${argv1}`);
|
|
63
|
-
return here.pathname === run.pathname;
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
})();
|
|
68
|
-
|
|
69
|
-
if (isMain) {
|
|
59
|
+
const entrypointPath = process.argv[1];
|
|
60
|
+
if (entrypointPath && path.resolve(entrypointPath) === fileURLToPath(import.meta.url)) {
|
|
70
61
|
start().catch((err) => {
|
|
71
62
|
console.error(err);
|
|
72
63
|
process.exitCode = 1;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
2
4
|
|
|
3
5
|
import {
|
|
4
6
|
createDefaultBunBackendBootstrap,
|
|
@@ -54,19 +56,8 @@ export async function start(): Promise<void> {
|
|
|
54
56
|
);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const argv1 = process.argv?.[1];
|
|
60
|
-
if (!argv1) return false;
|
|
61
|
-
const here = new URL(import.meta.url);
|
|
62
|
-
const run = new URL(`file://${argv1}`);
|
|
63
|
-
return here.pathname === run.pathname;
|
|
64
|
-
} catch {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
})();
|
|
68
|
-
|
|
69
|
-
if (isMain) {
|
|
59
|
+
const entrypointPath = process.argv[1];
|
|
60
|
+
if (entrypointPath && path.resolve(entrypointPath) === fileURLToPath(import.meta.url)) {
|
|
70
61
|
start().catch((err) => {
|
|
71
62
|
console.error(err);
|
|
72
63
|
process.exitCode = 1;
|
|
@@ -4,8 +4,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
|
|
5
5
|
import type { RouteHandlerResult } from '@webstir-io/webstir-backend/runtime/bun';
|
|
6
6
|
|
|
7
|
-
const ROOT_PATH = '/';
|
|
8
|
-
const DEMO_PATH = '/demo/progressive-enhancement';
|
|
7
|
+
const ROOT_PATH = '/api';
|
|
8
|
+
const DEMO_PATH = '/api/demo/progressive-enhancement';
|
|
9
9
|
const FRAGMENT_TARGET = 'greeting-preview';
|
|
10
10
|
const SESSION_PANEL_TARGET = 'session-panel';
|
|
11
11
|
const SESSION_COOKIE_NAME = 'webstir_demo_session';
|
|
@@ -2,7 +2,7 @@ import { assert, test } from '@webstir-io/webstir-testing';
|
|
|
2
2
|
|
|
3
3
|
test('progressive enhancement demo page renders a form shell', async () => {
|
|
4
4
|
const ctx = requireBackendTestContext();
|
|
5
|
-
const response = await ctx.request('/demo/progressive-enhancement');
|
|
5
|
+
const response = await ctx.request('/api/demo/progressive-enhancement');
|
|
6
6
|
const html = await response.text();
|
|
7
7
|
|
|
8
8
|
assert.equal(response.status, 200);
|
|
@@ -23,7 +23,7 @@ test('progressive enhancement demo page renders a form shell', async () => {
|
|
|
23
23
|
|
|
24
24
|
test('native form submissions redirect back to the document route', async () => {
|
|
25
25
|
const ctx = requireBackendTestContext();
|
|
26
|
-
const response = await ctx.request('/demo/progressive-enhancement', {
|
|
26
|
+
const response = await ctx.request('/api/demo/progressive-enhancement', {
|
|
27
27
|
method: 'POST',
|
|
28
28
|
headers: {
|
|
29
29
|
'content-type': 'application/x-www-form-urlencoded'
|
|
@@ -35,7 +35,7 @@ test('native form submissions redirect back to the document route', async () =>
|
|
|
35
35
|
assert.equal(response.status, 303);
|
|
36
36
|
assert.equal(
|
|
37
37
|
response.headers.get('location'),
|
|
38
|
-
'/demo/progressive-enhancement?source=redirect&name=Native%20Flow'
|
|
38
|
+
'/api/demo/progressive-enhancement?source=redirect&name=Native%20Flow'
|
|
39
39
|
);
|
|
40
40
|
assert.equal(response.headers.get('x-webstir-fragment-target'), null);
|
|
41
41
|
assert.equal(response.headers.get('content-type'), null);
|
|
@@ -43,7 +43,7 @@ test('native form submissions redirect back to the document route', async () =>
|
|
|
43
43
|
|
|
44
44
|
test('redirected document route preserves the no-javascript form flow', async () => {
|
|
45
45
|
const ctx = requireBackendTestContext();
|
|
46
|
-
const response = await ctx.request('/demo/progressive-enhancement?source=redirect&name=Native%20Flow');
|
|
46
|
+
const response = await ctx.request('/api/demo/progressive-enhancement?source=redirect&name=Native%20Flow');
|
|
47
47
|
const html = await response.text();
|
|
48
48
|
|
|
49
49
|
assert.equal(response.status, 200);
|
|
@@ -55,7 +55,7 @@ test('redirected document route preserves the no-javascript form flow', async ()
|
|
|
55
55
|
|
|
56
56
|
test('enhanced form submissions return fragment metadata and html', async () => {
|
|
57
57
|
const ctx = requireBackendTestContext();
|
|
58
|
-
const response = await ctx.request('/demo/progressive-enhancement', {
|
|
58
|
+
const response = await ctx.request('/api/demo/progressive-enhancement', {
|
|
59
59
|
method: 'POST',
|
|
60
60
|
headers: {
|
|
61
61
|
'content-type': 'application/x-www-form-urlencoded',
|
|
@@ -80,7 +80,7 @@ test('enhanced form submissions return fragment metadata and html', async () =>
|
|
|
80
80
|
|
|
81
81
|
test('native session sign-in redirects and sets a session cookie', async () => {
|
|
82
82
|
const ctx = requireBackendTestContext();
|
|
83
|
-
const response = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
83
|
+
const response = await ctx.request('/api/demo/progressive-enhancement/session/sign-in', {
|
|
84
84
|
method: 'POST',
|
|
85
85
|
headers: {
|
|
86
86
|
'content-type': 'application/x-www-form-urlencoded'
|
|
@@ -90,13 +90,13 @@ test('native session sign-in redirects and sets a session cookie', async () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
assert.equal(response.status, 303);
|
|
93
|
-
assert.equal(response.headers.get('location'), '/demo/progressive-enhancement?session=signed-in');
|
|
93
|
+
assert.equal(response.headers.get('location'), '/api/demo/progressive-enhancement?session=signed-in');
|
|
94
94
|
assert.isTrue(String(response.headers.get('set-cookie')).includes('webstir_demo_session=Casey%20Proxy'));
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
test('enhanced session sign-in returns a fragment and persists on the next document request', async () => {
|
|
98
98
|
const ctx = requireBackendTestContext();
|
|
99
|
-
const response = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
99
|
+
const response = await ctx.request('/api/demo/progressive-enhancement/session/sign-in', {
|
|
100
100
|
method: 'POST',
|
|
101
101
|
headers: {
|
|
102
102
|
'content-type': 'application/x-www-form-urlencoded',
|
|
@@ -115,7 +115,7 @@ test('enhanced session sign-in returns a fragment and persists on the next docum
|
|
|
115
115
|
assert.isTrue(html.includes('Signed in as <strong>Casey Proxy</strong>'));
|
|
116
116
|
assert.isTrue(html.includes('id="demo-sign-out"'));
|
|
117
117
|
|
|
118
|
-
const documentResponse = await ctx.request('/demo/progressive-enhancement', {
|
|
118
|
+
const documentResponse = await ctx.request('/api/demo/progressive-enhancement', {
|
|
119
119
|
headers: {
|
|
120
120
|
cookie
|
|
121
121
|
}
|
|
@@ -129,7 +129,7 @@ test('enhanced session sign-in returns a fragment and persists on the next docum
|
|
|
129
129
|
|
|
130
130
|
test('enhanced session sign-out returns a fragment and clears the session cookie', async () => {
|
|
131
131
|
const ctx = requireBackendTestContext();
|
|
132
|
-
const signInResponse = await ctx.request('/demo/progressive-enhancement/session/sign-in', {
|
|
132
|
+
const signInResponse = await ctx.request('/api/demo/progressive-enhancement/session/sign-in', {
|
|
133
133
|
method: 'POST',
|
|
134
134
|
headers: {
|
|
135
135
|
'content-type': 'application/x-www-form-urlencoded',
|
|
@@ -139,7 +139,7 @@ test('enhanced session sign-out returns a fragment and clears the session cookie
|
|
|
139
139
|
});
|
|
140
140
|
const cookie = requireCookie(signInResponse.headers.get('set-cookie'));
|
|
141
141
|
|
|
142
|
-
const response = await ctx.request('/demo/progressive-enhancement/session/sign-out', {
|
|
142
|
+
const response = await ctx.request('/api/demo/progressive-enhancement/session/sign-out', {
|
|
143
143
|
method: 'POST',
|
|
144
144
|
headers: {
|
|
145
145
|
'x-webstir-client-nav': '1',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webstir-io/webstir",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"webstir": "src/cli.ts"
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"@modelcontextprotocol/sdk": "1.28.0",
|
|
47
47
|
"@webstir-io/module-contract": "^0.1.16",
|
|
48
48
|
"@webstir-io/testing-contract": "^0.1.8",
|
|
49
|
-
"@webstir-io/webstir-backend": "^0.1.
|
|
50
|
-
"@webstir-io/webstir-frontend": "^0.1.
|
|
49
|
+
"@webstir-io/webstir-backend": "^0.1.17",
|
|
50
|
+
"@webstir-io/webstir-frontend": "^0.1.42",
|
|
51
51
|
"@webstir-io/webstir-testing": "^0.1.6",
|
|
52
52
|
"typescript": "^5.7.2",
|
|
53
53
|
"zod": "^3.23.8"
|
package/scripts/run-tests.mjs
CHANGED
|
@@ -17,6 +17,13 @@ const watchBrowserTestFiles = [
|
|
|
17
17
|
const publishModeFilter = 'publish mode';
|
|
18
18
|
const watchModeFilter = 'watch mode';
|
|
19
19
|
|
|
20
|
+
function buildSingleFileTestSteps(labelPrefix, files) {
|
|
21
|
+
return files.map((file) => ({
|
|
22
|
+
label: `${labelPrefix}: ${path.basename(file)}`,
|
|
23
|
+
args: ['test', '--bail=1', file],
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
export function listCoreTestFiles() {
|
|
21
28
|
return readdirSync(testsDir)
|
|
22
29
|
.filter((file) => file.endsWith('.ts'))
|
|
@@ -27,26 +34,23 @@ export function listCoreTestFiles() {
|
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export function buildTestPlan(mode) {
|
|
30
|
-
const coreTests =
|
|
31
|
-
label: 'core orchestrator tests',
|
|
32
|
-
args: ['test', '--bail=1', ...listCoreTestFiles()],
|
|
33
|
-
};
|
|
37
|
+
const coreTests = buildSingleFileTestSteps('core orchestrator test', listCoreTestFiles());
|
|
34
38
|
const publishBrowserTests = {
|
|
35
39
|
label: 'browser publish proofs',
|
|
36
40
|
args: ['test', '--bail=1', browserTestFile, '-t', publishModeFilter],
|
|
37
41
|
};
|
|
38
|
-
const integrationWatchBrowserTests =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const integrationWatchBrowserTests = buildSingleFileTestSteps(
|
|
43
|
+
'browser watch integration proof',
|
|
44
|
+
watchBrowserTestFiles,
|
|
45
|
+
);
|
|
42
46
|
const watchBrowserTests = {
|
|
43
47
|
label: 'browser watch proofs',
|
|
44
48
|
args: ['test', '--bail=1', browserTestFile, '-t', watchModeFilter],
|
|
45
49
|
};
|
|
46
50
|
const requiredPlan = [
|
|
47
|
-
coreTests,
|
|
51
|
+
...coreTests,
|
|
48
52
|
publishBrowserTests,
|
|
49
|
-
integrationWatchBrowserTests,
|
|
53
|
+
...integrationWatchBrowserTests,
|
|
50
54
|
watchBrowserTests,
|
|
51
55
|
];
|
|
52
56
|
|
|
@@ -56,7 +60,7 @@ export function buildTestPlan(mode) {
|
|
|
56
60
|
case 'publish-browser':
|
|
57
61
|
return [publishBrowserTests];
|
|
58
62
|
case 'watch-browser':
|
|
59
|
-
return [integrationWatchBrowserTests, watchBrowserTests];
|
|
63
|
+
return [...integrationWatchBrowserTests, watchBrowserTests];
|
|
60
64
|
case 'all':
|
|
61
65
|
case 'with-watch-browser':
|
|
62
66
|
return requiredPlan;
|
package/src/add-backend.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
runAddJob as runBackendAddJob,
|
|
3
|
+
runAddRoute as runBackendAddRoute,
|
|
4
|
+
runUpdateRouteContract as runBackendUpdateRouteContract,
|
|
5
|
+
type AddJobOptions,
|
|
6
|
+
type AddRouteOptions,
|
|
7
|
+
type UpdateRouteContractOptions as BackendUpdateRouteContractOptions,
|
|
5
8
|
} from '@webstir-io/webstir-backend';
|
|
6
9
|
|
|
7
10
|
import type { AddCommandResult } from './add.ts';
|
|
8
11
|
|
|
9
|
-
import { monorepoRoot } from './paths.ts';
|
|
10
|
-
|
|
11
12
|
export interface RunAddBackendOptions {
|
|
12
13
|
readonly workspaceRoot: string;
|
|
13
14
|
readonly rawArgs: readonly string[];
|
|
@@ -27,8 +28,7 @@ export async function runAddRouteCommand(options: RunAddBackendOptions): Promise
|
|
|
27
28
|
export async function runAddRouteScaffold(
|
|
28
29
|
options: AddRouteScaffoldOptions,
|
|
29
30
|
): Promise<AddCommandResult> {
|
|
30
|
-
const
|
|
31
|
-
const result = await backendAdd.runAddRoute(options);
|
|
31
|
+
const result = await runBackendAddRoute(options);
|
|
32
32
|
|
|
33
33
|
return {
|
|
34
34
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -46,8 +46,7 @@ export async function runAddJobCommand(options: RunAddBackendOptions): Promise<A
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
export async function runAddJobScaffold(options: AddJobScaffoldOptions): Promise<AddCommandResult> {
|
|
49
|
-
const
|
|
50
|
-
const result = await backendAdd.runAddJob(options);
|
|
49
|
+
const result = await runBackendAddJob(options);
|
|
51
50
|
|
|
52
51
|
return {
|
|
53
52
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -60,8 +59,7 @@ export async function runAddJobScaffold(options: AddJobScaffoldOptions): Promise
|
|
|
60
59
|
export async function runUpdateRouteContract(
|
|
61
60
|
options: UpdateRouteContractOptions,
|
|
62
61
|
): Promise<AddCommandResult> {
|
|
63
|
-
const
|
|
64
|
-
const result = await backendAdd.runUpdateRouteContract(options);
|
|
62
|
+
const result = await runBackendUpdateRouteContract(options);
|
|
65
63
|
|
|
66
64
|
return {
|
|
67
65
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -167,66 +165,6 @@ interface ParsedBackendCommandArgs {
|
|
|
167
165
|
readonly booleans: ReadonlySet<string>;
|
|
168
166
|
}
|
|
169
167
|
|
|
170
|
-
interface BackendAddResult {
|
|
171
|
-
readonly target: string;
|
|
172
|
-
readonly changes: readonly string[];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
interface BackendAddModule {
|
|
176
|
-
readonly runAddRoute: (options: AddRouteOptions) => Promise<BackendAddResult>;
|
|
177
|
-
readonly runAddJob: (options: AddJobOptions) => Promise<BackendAddResult>;
|
|
178
|
-
readonly runUpdateRouteContract: (
|
|
179
|
-
options: UpdateRouteContractOptions,
|
|
180
|
-
) => Promise<BackendAddResult>;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
let backendAddModulePromise: Promise<BackendAddModule> | null = null;
|
|
184
|
-
|
|
185
|
-
async function loadBackendAddModule(): Promise<BackendAddModule> {
|
|
186
|
-
if (backendAddModulePromise) {
|
|
187
|
-
return await backendAddModulePromise;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
backendAddModulePromise = import('@webstir-io/webstir-backend').then(async (module) => {
|
|
191
|
-
if (
|
|
192
|
-
typeof module.runAddRoute === 'function' &&
|
|
193
|
-
typeof module.runAddJob === 'function' &&
|
|
194
|
-
typeof module.runUpdateRouteContract === 'function'
|
|
195
|
-
) {
|
|
196
|
-
return module as BackendAddModule;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (monorepoRoot) {
|
|
200
|
-
throw new Error(
|
|
201
|
-
'Installed @webstir-io/webstir-backend package does not export runAddRoute/runAddJob/runUpdateRouteContract.',
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const compat = await import('./add-backend-compat.ts');
|
|
206
|
-
return {
|
|
207
|
-
async runAddRoute(options: AddRouteOptions): Promise<BackendAddResult> {
|
|
208
|
-
return await compat.runAddRoute(options);
|
|
209
|
-
},
|
|
210
|
-
async runAddJob(options: AddJobOptions): Promise<BackendAddResult> {
|
|
211
|
-
return await compat.runAddJob({
|
|
212
|
-
workspaceRoot: options.workspaceRoot,
|
|
213
|
-
name: options.name,
|
|
214
|
-
schedule: options.schedule,
|
|
215
|
-
description: options.description,
|
|
216
|
-
...(options.priority !== undefined ? { priority: String(options.priority) } : {}),
|
|
217
|
-
});
|
|
218
|
-
},
|
|
219
|
-
async runUpdateRouteContract(): Promise<BackendAddResult> {
|
|
220
|
-
throw new Error(
|
|
221
|
-
'Installed @webstir-io/webstir-backend package does not export runUpdateRouteContract.',
|
|
222
|
-
);
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
return await backendAddModulePromise;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
168
|
function parseBackendCommandArgs(
|
|
231
169
|
rawArgs: readonly string[],
|
|
232
170
|
spec: ParseSpec,
|
package/src/bun-spa-routes.ts
CHANGED
|
@@ -57,15 +57,10 @@ export function createBunFrontendFetchHandler(options: BunFrontendFetchHandlerOp
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
function getApiProxyPath(pathname: string): string | null {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const normalizedPath = pathname.startsWith('/') ? path.posix.normalize(pathname) : null;
|
|
61
|
+
if (normalizedPath === '/api' || normalizedPath?.startsWith('/api/')) {
|
|
62
|
+
return normalizedPath;
|
|
62
63
|
}
|
|
63
|
-
|
|
64
|
-
if (pathname.startsWith('/api/')) {
|
|
65
|
-
const normalizedPath = path.posix.normalize(pathname.slice('/api'.length));
|
|
66
|
-
return normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
64
|
return null;
|
|
70
65
|
}
|
|
71
66
|
|
|
@@ -132,7 +127,7 @@ function rewriteProxyLocation(value: string, targetUrl: URL): string {
|
|
|
132
127
|
}
|
|
133
128
|
|
|
134
129
|
if (trimmed.startsWith('/')) {
|
|
135
|
-
return
|
|
130
|
+
return trimmed;
|
|
136
131
|
}
|
|
137
132
|
|
|
138
133
|
try {
|
|
@@ -140,20 +135,12 @@ function rewriteProxyLocation(value: string, targetUrl: URL): string {
|
|
|
140
135
|
if (resolved.origin !== targetUrl.origin) {
|
|
141
136
|
return value;
|
|
142
137
|
}
|
|
143
|
-
return
|
|
138
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
144
139
|
} catch {
|
|
145
140
|
return value;
|
|
146
141
|
}
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
function prefixApiMount(pathname: string): string {
|
|
150
|
-
if (pathname === '/api' || pathname.startsWith('/api/')) {
|
|
151
|
-
return pathname;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return pathname === '/' ? '/api' : `/api${pathname}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
144
|
function methodAllowsBody(method: string): boolean {
|
|
158
145
|
return method !== 'GET' && method !== 'HEAD';
|
|
159
146
|
}
|
package/src/dev-server.ts
CHANGED
|
@@ -303,15 +303,10 @@ export function getStaticCandidatePaths(pathname: string): readonly string[] {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
export function getApiProxyPath(pathname: string): string | null {
|
|
306
|
-
|
|
307
|
-
|
|
306
|
+
const normalizedPath = pathname.startsWith('/') ? path.posix.normalize(pathname) : null;
|
|
307
|
+
if (normalizedPath === '/api' || normalizedPath?.startsWith('/api/')) {
|
|
308
|
+
return normalizedPath;
|
|
308
309
|
}
|
|
309
|
-
|
|
310
|
-
if (pathname.startsWith('/api/')) {
|
|
311
|
-
const normalizedPath = path.posix.normalize(pathname.slice('/api'.length));
|
|
312
|
-
return normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
310
|
return null;
|
|
316
311
|
}
|
|
317
312
|
|
|
@@ -357,7 +352,7 @@ function rewriteProxyLocation(value: string, targetUrl: URL): string {
|
|
|
357
352
|
}
|
|
358
353
|
|
|
359
354
|
if (trimmed.startsWith('/')) {
|
|
360
|
-
return
|
|
355
|
+
return trimmed;
|
|
361
356
|
}
|
|
362
357
|
|
|
363
358
|
try {
|
|
@@ -365,20 +360,12 @@ function rewriteProxyLocation(value: string, targetUrl: URL): string {
|
|
|
365
360
|
if (resolved.origin !== targetUrl.origin) {
|
|
366
361
|
return value;
|
|
367
362
|
}
|
|
368
|
-
return
|
|
363
|
+
return `${resolved.pathname}${resolved.search}${resolved.hash}`;
|
|
369
364
|
} catch {
|
|
370
365
|
return value;
|
|
371
366
|
}
|
|
372
367
|
}
|
|
373
368
|
|
|
374
|
-
function prefixApiMount(pathname: string): string {
|
|
375
|
-
if (pathname === '/api' || pathname.startsWith('/api/')) {
|
|
376
|
-
return pathname;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return pathname === '/' ? '/api' : `/api${pathname}`;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
369
|
async function resolveStaticFile(
|
|
383
370
|
buildRoot: string,
|
|
384
371
|
relativePaths: readonly string[],
|
|
@@ -434,7 +421,7 @@ function setCacheHeaders(headers: Headers, relativePath: string): void {
|
|
|
434
421
|
}
|
|
435
422
|
|
|
436
423
|
const extension = path.extname(relativePath).toLowerCase();
|
|
437
|
-
if (extension === '.html' || extension === '') {
|
|
424
|
+
if (extension === '.html' || extension === '' || extension === '.json') {
|
|
438
425
|
setNoCacheHeaders(headers);
|
|
439
426
|
return;
|
|
440
427
|
}
|
package/src/execute.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
CommandMode,
|
|
11
11
|
} from './types.ts';
|
|
12
12
|
import { readWorkspaceDescriptor } from './workspace.ts';
|
|
13
|
+
import { assertNoActiveWorkspaceWatch } from './workspace-lock.ts';
|
|
13
14
|
|
|
14
15
|
export interface RunCommandOptions {
|
|
15
16
|
readonly workspaceRoot: string;
|
|
@@ -22,6 +23,7 @@ export async function runCommand(
|
|
|
22
23
|
options: RunCommandOptions,
|
|
23
24
|
): Promise<CommandExecutionResult> {
|
|
24
25
|
const workspace = await readWorkspaceDescriptor(options.workspaceRoot);
|
|
26
|
+
await assertNoActiveWorkspaceWatch(workspace.root, mode);
|
|
25
27
|
const providerLoader = options.loadProvider ?? loadProvider;
|
|
26
28
|
const targets = [];
|
|
27
29
|
|
package/src/watch.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { runFrontendWatch } from './frontend-watch.ts';
|
|
|
4
4
|
import { runFullWatch } from './full-watch.ts';
|
|
5
5
|
import type { WorkspaceDescriptor } from './types.ts';
|
|
6
6
|
import { readWorkspaceDescriptor } from './workspace.ts';
|
|
7
|
+
import { acquireWorkspaceWatchLock } from './workspace-lock.ts';
|
|
7
8
|
|
|
8
9
|
interface WatchStream {
|
|
9
10
|
write(message: string): void;
|
|
@@ -42,24 +43,30 @@ const defaultIo: WatchIo = {
|
|
|
42
43
|
|
|
43
44
|
export async function runWatch(options: RunWatchOptions): Promise<void> {
|
|
44
45
|
const io = options.io ?? defaultIo;
|
|
45
|
-
await materializeRepoLocalWorkspaceDependencies(options.workspaceRoot);
|
|
46
46
|
const workspace = await readWorkspaceDescriptor(options.workspaceRoot);
|
|
47
|
+
const watchLock = await acquireWorkspaceWatchLock(workspace.root);
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
try {
|
|
50
|
+
await materializeRepoLocalWorkspaceDependencies(options.workspaceRoot);
|
|
51
|
+
|
|
52
|
+
switch (workspace.mode) {
|
|
53
|
+
case 'spa':
|
|
54
|
+
await runFrontendWatch(workspace, options, io);
|
|
55
|
+
return;
|
|
56
|
+
case 'ssg':
|
|
57
|
+
await runFrontendWatch(workspace, options, io);
|
|
58
|
+
return;
|
|
59
|
+
case 'api':
|
|
60
|
+
await runApiWatch(workspace, options, io);
|
|
61
|
+
return;
|
|
62
|
+
case 'full':
|
|
63
|
+
await runFullWatch(workspace, options, io);
|
|
64
|
+
return;
|
|
65
|
+
default:
|
|
66
|
+
throwUnsupportedWatchMode(workspace);
|
|
67
|
+
}
|
|
68
|
+
} finally {
|
|
69
|
+
await watchLock.release();
|
|
63
70
|
}
|
|
64
71
|
}
|
|
65
72
|
|