@chidchanun/bcp 0.1.10 → 0.1.11
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/CHANGELOG.md +43 -2
- package/README.md +131 -9
- package/docs/form-actions.md +314 -0
- package/package.json +1 -1
- package/packages/bundler/src/server-production-actions.ts +523 -0
- package/packages/bundler/src/server-production-middleware.ts +17 -0
- package/packages/client/src/form.tsx +548 -0
- package/packages/client/src/index.tsx +10 -0
- package/packages/client/src/server.ts +6 -0
- package/packages/server/src/form-action-dev-proxy.ts +601 -0
- package/packages/server/src/form-action-transport.ts +271 -0
- package/packages/server/src/form-action.ts +493 -0
- package/packages/server/src/middleware-dev-server.ts +56 -2
- package/packages/server/src/standalone-production-runtime-v2-action.ts +765 -0
- package/packages/server/src/standalone-production-runtime-v3.ts +1 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
pathToFileURL,
|
|
5
|
+
} from "node:url";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
executePageGuards,
|
|
9
|
+
type PageGuardData,
|
|
10
|
+
} from "./page-guard.js";
|
|
11
|
+
|
|
12
|
+
export const PAGE_ACTION_METHODS = [
|
|
13
|
+
"POST",
|
|
14
|
+
"PUT",
|
|
15
|
+
"PATCH",
|
|
16
|
+
"DELETE",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export type PageActionMethod =
|
|
20
|
+
typeof PAGE_ACTION_METHODS[number];
|
|
21
|
+
|
|
22
|
+
export interface PageActionContext {
|
|
23
|
+
params: Record<string, any>;
|
|
24
|
+
searchParams: URLSearchParams;
|
|
25
|
+
guardData: Readonly<PageGuardData>;
|
|
26
|
+
method: PageActionMethod;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type PageAction = (
|
|
30
|
+
formData: FormData,
|
|
31
|
+
context: PageActionContext
|
|
32
|
+
) =>
|
|
33
|
+
| unknown
|
|
34
|
+
| Promise<unknown>;
|
|
35
|
+
|
|
36
|
+
export interface PageActionExecution {
|
|
37
|
+
hasGuard: boolean;
|
|
38
|
+
guardData: PageGuardData;
|
|
39
|
+
data: unknown;
|
|
40
|
+
response: Response | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const ACTION_NAME_PATTERN =
|
|
44
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
45
|
+
|
|
46
|
+
const BLOCKED_ACTION_NAMES =
|
|
47
|
+
new Set([
|
|
48
|
+
"default",
|
|
49
|
+
"__proto__",
|
|
50
|
+
"prototype",
|
|
51
|
+
"constructor",
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
export function findPageActionFile(
|
|
55
|
+
pageFilePath: string
|
|
56
|
+
): string | null {
|
|
57
|
+
const directory =
|
|
58
|
+
path.dirname(
|
|
59
|
+
pageFilePath
|
|
60
|
+
);
|
|
61
|
+
const tsFile =
|
|
62
|
+
path.join(
|
|
63
|
+
directory,
|
|
64
|
+
"actions.ts"
|
|
65
|
+
);
|
|
66
|
+
const tsxFile =
|
|
67
|
+
path.join(
|
|
68
|
+
directory,
|
|
69
|
+
"actions.tsx"
|
|
70
|
+
);
|
|
71
|
+
const hasTs =
|
|
72
|
+
fs.existsSync(
|
|
73
|
+
tsFile
|
|
74
|
+
);
|
|
75
|
+
const hasTsx =
|
|
76
|
+
fs.existsSync(
|
|
77
|
+
tsxFile
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (hasTs && hasTsx) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`BCP Framework: both actions.ts and actions.tsx exist in "${directory}".`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (hasTs) {
|
|
87
|
+
return tsFile;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (hasTsx) {
|
|
91
|
+
return tsxFile;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function routeHasPageActions(
|
|
98
|
+
pageFilePath: string
|
|
99
|
+
): boolean {
|
|
100
|
+
return findPageActionFile(
|
|
101
|
+
pageFilePath
|
|
102
|
+
) !== null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function normalizePageActionMethod(
|
|
106
|
+
value: string | null | undefined
|
|
107
|
+
): PageActionMethod {
|
|
108
|
+
const method =
|
|
109
|
+
(value ?? "POST")
|
|
110
|
+
.trim()
|
|
111
|
+
.toUpperCase();
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
!PAGE_ACTION_METHODS.includes(
|
|
115
|
+
method as PageActionMethod
|
|
116
|
+
)
|
|
117
|
+
) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`BCP Framework: unsupported form action method "${method || String(value)}". Use POST, PUT, PATCH, or DELETE.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return method as PageActionMethod;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function assertPageActionName(
|
|
127
|
+
name: string
|
|
128
|
+
): void {
|
|
129
|
+
if (
|
|
130
|
+
!ACTION_NAME_PATTERN.test(
|
|
131
|
+
name
|
|
132
|
+
) ||
|
|
133
|
+
BLOCKED_ACTION_NAMES.has(
|
|
134
|
+
name
|
|
135
|
+
)
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`BCP Framework: invalid form action name "${name}".`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function executePageAction(
|
|
144
|
+
pageFilePath: string,
|
|
145
|
+
actionName: string,
|
|
146
|
+
method: PageActionMethod,
|
|
147
|
+
formData: FormData,
|
|
148
|
+
params: Record<string, any>,
|
|
149
|
+
requestUrl: URL,
|
|
150
|
+
moduleVersion = 0
|
|
151
|
+
): Promise<PageActionExecution> {
|
|
152
|
+
assertPageActionName(
|
|
153
|
+
actionName
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const appDirectory =
|
|
157
|
+
findAppDirectory(
|
|
158
|
+
pageFilePath
|
|
159
|
+
);
|
|
160
|
+
const guardExecution =
|
|
161
|
+
await executePageGuards(
|
|
162
|
+
appDirectory,
|
|
163
|
+
pageFilePath,
|
|
164
|
+
params,
|
|
165
|
+
requestUrl,
|
|
166
|
+
moduleVersion
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (
|
|
170
|
+
guardExecution.response
|
|
171
|
+
) {
|
|
172
|
+
return {
|
|
173
|
+
hasGuard:
|
|
174
|
+
guardExecution.hasGuard,
|
|
175
|
+
guardData: {
|
|
176
|
+
...guardExecution.data,
|
|
177
|
+
},
|
|
178
|
+
data:
|
|
179
|
+
null,
|
|
180
|
+
response:
|
|
181
|
+
guardExecution.response,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const actionFile =
|
|
186
|
+
findPageActionFile(
|
|
187
|
+
pageFilePath
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
if (!actionFile) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`BCP Framework: route "${pageFilePath}" has no actions.ts file.`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const moduleUrl =
|
|
197
|
+
pathToFileURL(
|
|
198
|
+
actionFile
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
moduleUrl.searchParams.set(
|
|
202
|
+
"bcp-action",
|
|
203
|
+
String(
|
|
204
|
+
moduleVersion
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const actionModule =
|
|
209
|
+
await import(
|
|
210
|
+
moduleUrl.href
|
|
211
|
+
) as Record<
|
|
212
|
+
string,
|
|
213
|
+
unknown
|
|
214
|
+
>;
|
|
215
|
+
const candidate =
|
|
216
|
+
actionModule[
|
|
217
|
+
actionName
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
typeof candidate !==
|
|
222
|
+
"function"
|
|
223
|
+
) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`BCP Framework: action "${actionName}" was not exported as a function from "${actionFile}".`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return executePageActionFunction(
|
|
230
|
+
candidate as PageAction,
|
|
231
|
+
actionName,
|
|
232
|
+
method,
|
|
233
|
+
formData,
|
|
234
|
+
params,
|
|
235
|
+
requestUrl,
|
|
236
|
+
guardExecution.data,
|
|
237
|
+
guardExecution.hasGuard,
|
|
238
|
+
actionFile
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function executePageActionFunction(
|
|
243
|
+
action: PageAction,
|
|
244
|
+
actionName: string,
|
|
245
|
+
method: PageActionMethod,
|
|
246
|
+
formData: FormData,
|
|
247
|
+
params: Record<string, any>,
|
|
248
|
+
requestUrl: URL,
|
|
249
|
+
guardData: PageGuardData = {},
|
|
250
|
+
hasGuard = false,
|
|
251
|
+
label = "actions.ts"
|
|
252
|
+
): Promise<PageActionExecution> {
|
|
253
|
+
assertPageActionName(
|
|
254
|
+
actionName
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const result =
|
|
258
|
+
await action(
|
|
259
|
+
formData,
|
|
260
|
+
{
|
|
261
|
+
params: {
|
|
262
|
+
...params,
|
|
263
|
+
},
|
|
264
|
+
searchParams:
|
|
265
|
+
new URLSearchParams(
|
|
266
|
+
requestUrl.searchParams
|
|
267
|
+
),
|
|
268
|
+
guardData: {
|
|
269
|
+
...guardData,
|
|
270
|
+
},
|
|
271
|
+
method,
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
result instanceof
|
|
277
|
+
Response
|
|
278
|
+
) {
|
|
279
|
+
return {
|
|
280
|
+
hasGuard,
|
|
281
|
+
guardData: {
|
|
282
|
+
...guardData,
|
|
283
|
+
},
|
|
284
|
+
data:
|
|
285
|
+
null,
|
|
286
|
+
response:
|
|
287
|
+
result,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const data =
|
|
292
|
+
result === undefined
|
|
293
|
+
? null
|
|
294
|
+
: result;
|
|
295
|
+
|
|
296
|
+
assertActionData(
|
|
297
|
+
data,
|
|
298
|
+
`${label}#${actionName}`
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
hasGuard,
|
|
303
|
+
guardData: {
|
|
304
|
+
...guardData,
|
|
305
|
+
},
|
|
306
|
+
data,
|
|
307
|
+
response:
|
|
308
|
+
null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function assertActionData(
|
|
313
|
+
value: unknown,
|
|
314
|
+
actionLabel = "action"
|
|
315
|
+
): void {
|
|
316
|
+
const seen =
|
|
317
|
+
new Set<object>();
|
|
318
|
+
|
|
319
|
+
validateJsonValue(
|
|
320
|
+
value,
|
|
321
|
+
"$",
|
|
322
|
+
seen,
|
|
323
|
+
actionLabel
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function findAppDirectory(
|
|
328
|
+
pageFilePath: string
|
|
329
|
+
): string {
|
|
330
|
+
let current =
|
|
331
|
+
path.dirname(
|
|
332
|
+
path.resolve(
|
|
333
|
+
pageFilePath
|
|
334
|
+
)
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
while (true) {
|
|
338
|
+
if (
|
|
339
|
+
path.basename(
|
|
340
|
+
current
|
|
341
|
+
) === "app"
|
|
342
|
+
) {
|
|
343
|
+
return current;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const parent =
|
|
347
|
+
path.dirname(
|
|
348
|
+
current
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
if (parent === current) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`BCP Framework: could not resolve app directory for page "${pageFilePath}".`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
current =
|
|
358
|
+
parent;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function validateJsonValue(
|
|
363
|
+
value: unknown,
|
|
364
|
+
location: string,
|
|
365
|
+
seen: Set<object>,
|
|
366
|
+
actionLabel: string
|
|
367
|
+
): void {
|
|
368
|
+
if (
|
|
369
|
+
value === null ||
|
|
370
|
+
typeof value ===
|
|
371
|
+
"string" ||
|
|
372
|
+
typeof value ===
|
|
373
|
+
"boolean"
|
|
374
|
+
) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (
|
|
379
|
+
typeof value ===
|
|
380
|
+
"number"
|
|
381
|
+
) {
|
|
382
|
+
if (
|
|
383
|
+
Number.isFinite(
|
|
384
|
+
value
|
|
385
|
+
)
|
|
386
|
+
) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
throwInvalidActionData(
|
|
391
|
+
actionLabel,
|
|
392
|
+
location,
|
|
393
|
+
"numbers must be finite"
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (
|
|
398
|
+
typeof value !==
|
|
399
|
+
"object"
|
|
400
|
+
) {
|
|
401
|
+
throwInvalidActionData(
|
|
402
|
+
actionLabel,
|
|
403
|
+
location,
|
|
404
|
+
`unsupported ${typeof value} value`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const objectValue =
|
|
409
|
+
value as object;
|
|
410
|
+
|
|
411
|
+
if (seen.has(objectValue)) {
|
|
412
|
+
throwInvalidActionData(
|
|
413
|
+
actionLabel,
|
|
414
|
+
location,
|
|
415
|
+
"circular references are not supported"
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
seen.add(
|
|
420
|
+
objectValue
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
if (Array.isArray(value)) {
|
|
425
|
+
value.forEach(
|
|
426
|
+
(
|
|
427
|
+
item,
|
|
428
|
+
index
|
|
429
|
+
) => {
|
|
430
|
+
validateJsonValue(
|
|
431
|
+
item,
|
|
432
|
+
`${location}[${index}]`,
|
|
433
|
+
seen,
|
|
434
|
+
actionLabel
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const prototype =
|
|
442
|
+
Object.getPrototypeOf(
|
|
443
|
+
value
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
if (
|
|
447
|
+
prototype !==
|
|
448
|
+
Object.prototype &&
|
|
449
|
+
prototype !==
|
|
450
|
+
null
|
|
451
|
+
) {
|
|
452
|
+
throwInvalidActionData(
|
|
453
|
+
actionLabel,
|
|
454
|
+
location,
|
|
455
|
+
"only plain objects and arrays are supported"
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
for (
|
|
460
|
+
const [
|
|
461
|
+
key,
|
|
462
|
+
item,
|
|
463
|
+
]
|
|
464
|
+
of Object.entries(
|
|
465
|
+
value as Record<
|
|
466
|
+
string,
|
|
467
|
+
unknown
|
|
468
|
+
>
|
|
469
|
+
)
|
|
470
|
+
) {
|
|
471
|
+
validateJsonValue(
|
|
472
|
+
item,
|
|
473
|
+
`${location}.${key}`,
|
|
474
|
+
seen,
|
|
475
|
+
actionLabel
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
} finally {
|
|
479
|
+
seen.delete(
|
|
480
|
+
objectValue
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function throwInvalidActionData(
|
|
486
|
+
actionLabel: string,
|
|
487
|
+
location: string,
|
|
488
|
+
reason: string
|
|
489
|
+
): never {
|
|
490
|
+
throw new Error(
|
|
491
|
+
`BCP Framework: action "${actionLabel}" returned non-serializable data at ${location}: ${reason}.`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
createCriticalCssProxy,
|
|
10
10
|
} from "./critical-css-proxy.js";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
createFormActionDevProxy,
|
|
14
|
+
} from "./form-action-dev-proxy.js";
|
|
15
|
+
|
|
12
16
|
import {
|
|
13
17
|
loadProjectMiddleware,
|
|
14
18
|
} from "./middleware-loader.js";
|
|
@@ -48,6 +52,12 @@ export function createMiddlewareDevServer(
|
|
|
48
52
|
> | null =
|
|
49
53
|
null;
|
|
50
54
|
|
|
55
|
+
let actionGateway:
|
|
56
|
+
ReturnType<
|
|
57
|
+
typeof createFormActionDevProxy
|
|
58
|
+
> | null =
|
|
59
|
+
null;
|
|
60
|
+
|
|
51
61
|
let middlewareGateway:
|
|
52
62
|
ReturnType<
|
|
53
63
|
typeof createMiddlewareProxy
|
|
@@ -76,6 +86,8 @@ export function createMiddlewareDevServer(
|
|
|
76
86
|
|
|
77
87
|
const applicationPort =
|
|
78
88
|
await findFreePort();
|
|
89
|
+
const actionPort =
|
|
90
|
+
await findFreePort();
|
|
79
91
|
const middlewarePort =
|
|
80
92
|
await findFreePort();
|
|
81
93
|
const securityPort =
|
|
@@ -93,6 +105,30 @@ export function createMiddlewareDevServer(
|
|
|
93
105
|
|
|
94
106
|
await internalServer.start();
|
|
95
107
|
|
|
108
|
+
actionGateway =
|
|
109
|
+
createFormActionDevProxy({
|
|
110
|
+
port:
|
|
111
|
+
actionPort,
|
|
112
|
+
hostname:
|
|
113
|
+
"127.0.0.1",
|
|
114
|
+
upstreamPort:
|
|
115
|
+
applicationPort,
|
|
116
|
+
upstreamHostname:
|
|
117
|
+
"127.0.0.1",
|
|
118
|
+
rootDirectory,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
await actionGateway.start();
|
|
123
|
+
} catch (error) {
|
|
124
|
+
await internalServer.stop();
|
|
125
|
+
internalServer =
|
|
126
|
+
null;
|
|
127
|
+
actionGateway =
|
|
128
|
+
null;
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
|
|
96
132
|
middlewareGateway =
|
|
97
133
|
createMiddlewareProxy({
|
|
98
134
|
port:
|
|
@@ -100,7 +136,7 @@ export function createMiddlewareDevServer(
|
|
|
100
136
|
hostname:
|
|
101
137
|
"127.0.0.1",
|
|
102
138
|
upstreamPort:
|
|
103
|
-
|
|
139
|
+
actionPort,
|
|
104
140
|
upstreamHostname:
|
|
105
141
|
"127.0.0.1",
|
|
106
142
|
getMiddleware:
|
|
@@ -113,9 +149,12 @@ export function createMiddlewareDevServer(
|
|
|
113
149
|
try {
|
|
114
150
|
await middlewareGateway.start();
|
|
115
151
|
} catch (error) {
|
|
152
|
+
await actionGateway.stop();
|
|
116
153
|
await internalServer.stop();
|
|
117
154
|
internalServer =
|
|
118
155
|
null;
|
|
156
|
+
actionGateway =
|
|
157
|
+
null;
|
|
119
158
|
middlewareGateway =
|
|
120
159
|
null;
|
|
121
160
|
throw error;
|
|
@@ -137,9 +176,12 @@ export function createMiddlewareDevServer(
|
|
|
137
176
|
await securityGateway.start();
|
|
138
177
|
} catch (error) {
|
|
139
178
|
await middlewareGateway.stop();
|
|
179
|
+
await actionGateway.stop();
|
|
140
180
|
await internalServer.stop();
|
|
141
181
|
internalServer =
|
|
142
182
|
null;
|
|
183
|
+
actionGateway =
|
|
184
|
+
null;
|
|
143
185
|
middlewareGateway =
|
|
144
186
|
null;
|
|
145
187
|
securityGateway =
|
|
@@ -168,9 +210,12 @@ export function createMiddlewareDevServer(
|
|
|
168
210
|
} catch (error) {
|
|
169
211
|
await securityGateway.stop();
|
|
170
212
|
await middlewareGateway.stop();
|
|
213
|
+
await actionGateway.stop();
|
|
171
214
|
await internalServer.stop();
|
|
172
215
|
internalServer =
|
|
173
216
|
null;
|
|
217
|
+
actionGateway =
|
|
218
|
+
null;
|
|
174
219
|
middlewareGateway =
|
|
175
220
|
null;
|
|
176
221
|
securityGateway =
|
|
@@ -186,6 +231,9 @@ export function createMiddlewareDevServer(
|
|
|
186
231
|
console.log(
|
|
187
232
|
`[BCP Security] Dev gateway: http://${hostname}:${port}`
|
|
188
233
|
);
|
|
234
|
+
console.log(
|
|
235
|
+
`[BCP Actions] Endpoint: /_bcp/action`
|
|
236
|
+
);
|
|
189
237
|
console.log(
|
|
190
238
|
`[BCP CSS] Inline /bcp.css when <= 8 KiB and allowed by CSP.`
|
|
191
239
|
);
|
|
@@ -214,6 +262,12 @@ export function createMiddlewareDevServer(
|
|
|
214
262
|
null;
|
|
215
263
|
}
|
|
216
264
|
|
|
265
|
+
if (actionGateway) {
|
|
266
|
+
await actionGateway.stop();
|
|
267
|
+
actionGateway =
|
|
268
|
+
null;
|
|
269
|
+
}
|
|
270
|
+
|
|
217
271
|
if (internalServer) {
|
|
218
272
|
await internalServer.stop();
|
|
219
273
|
internalServer =
|
|
@@ -313,4 +367,4 @@ async function findFreePort(): Promise<number> {
|
|
|
313
367
|
);
|
|
314
368
|
}
|
|
315
369
|
}
|
|
316
|
-
}
|
|
370
|
+
}
|