@vcp-dev/site-edit-bridge 0.6.3 → 0.6.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.
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes the complete Bridge mutation boundary for one preview server.
|
|
3
|
+
*
|
|
4
|
+
* The queue deliberately covers assertion verification, durable-history session
|
|
5
|
+
* binding and the Engine mutation itself. Serializing only history-store open is
|
|
6
|
+
* insufficient because another session can replace/dispose the active runtime
|
|
7
|
+
* between bind and execute.
|
|
8
|
+
*/
|
|
9
|
+
export class SiteEditBridgeMutationQueue {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.tail = Promise.resolve();
|
|
12
|
+
}
|
|
13
|
+
run(operation) {
|
|
14
|
+
const result = this.tail.then(operation, operation);
|
|
15
|
+
this.tail = result.then(() => undefined, () => undefined);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { encodeRenderDocument, } from "./document-codec.js";
|
|
2
|
+
export class SiteEditBridgeTransport {
|
|
3
|
+
constructor() { }
|
|
4
|
+
static parseMessage(raw, options = {}) {
|
|
5
|
+
const parsed = JSON.parse(raw);
|
|
6
|
+
if (!SiteEditBridgeTransport.isMessage(parsed, options)) {
|
|
7
|
+
throw new Error("Invalid site edit bridge message");
|
|
8
|
+
}
|
|
9
|
+
return parsed;
|
|
10
|
+
}
|
|
11
|
+
static stringifyMessage(message) {
|
|
12
|
+
return JSON.stringify(message);
|
|
13
|
+
}
|
|
14
|
+
static extractRequestInfo(message) {
|
|
15
|
+
if (typeof message === "object" &&
|
|
16
|
+
message !== null &&
|
|
17
|
+
"payload" in message &&
|
|
18
|
+
typeof message.payload === "object" &&
|
|
19
|
+
message.payload !== null) {
|
|
20
|
+
const payload = message.payload;
|
|
21
|
+
const id = typeof payload.id === "string" ? payload.id : "unknown";
|
|
22
|
+
const kind = SiteEditBridgeTransport.isRequestKind(payload.kind)
|
|
23
|
+
? payload.kind
|
|
24
|
+
: "get-document";
|
|
25
|
+
return { id, kind };
|
|
26
|
+
}
|
|
27
|
+
return { id: "unknown", kind: "get-document" };
|
|
28
|
+
}
|
|
29
|
+
static isRequestKind(value) {
|
|
30
|
+
return (value === "initialize-session" ||
|
|
31
|
+
value === "get-document" ||
|
|
32
|
+
value === "get-history-status" ||
|
|
33
|
+
value === "get-style-context" ||
|
|
34
|
+
value === "get-object-edit-context" ||
|
|
35
|
+
value === "get-class-name-source" ||
|
|
36
|
+
value === "get-source-tree" ||
|
|
37
|
+
value === "get-source-file" ||
|
|
38
|
+
value === "resolve-source-location" ||
|
|
39
|
+
value === "execute" ||
|
|
40
|
+
value === "undo" ||
|
|
41
|
+
value === "redo");
|
|
42
|
+
}
|
|
43
|
+
static isMessage(value, options = {}) {
|
|
44
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (value.type === "request") {
|
|
48
|
+
return SiteEditBridgeTransport.isRequestPayload(value.payload, options);
|
|
49
|
+
}
|
|
50
|
+
if (value.type === "response" || value.type === "error") {
|
|
51
|
+
return isRecord(value.payload) && typeof value.payload.id === "string";
|
|
52
|
+
}
|
|
53
|
+
if (value.type === "event") {
|
|
54
|
+
return isRecord(value.payload) && isRecord(value.payload.event);
|
|
55
|
+
}
|
|
56
|
+
if (value.type === "auth") {
|
|
57
|
+
return isRecord(value.payload) && typeof value.payload.key === "string";
|
|
58
|
+
}
|
|
59
|
+
if (value.type === "auth-ok") {
|
|
60
|
+
return isRecord(value.payload) && value.payload.ok === true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
static isRequestPayload(payload, options = {}) {
|
|
65
|
+
if (!isRecord(payload) ||
|
|
66
|
+
typeof payload.id !== "string" ||
|
|
67
|
+
!SiteEditBridgeTransport.isRequestKind(payload.kind)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
switch (payload.kind) {
|
|
71
|
+
case "initialize-session":
|
|
72
|
+
return (typeof payload.routeId === "string" &&
|
|
73
|
+
(payload.mutationContext === undefined ||
|
|
74
|
+
isValidMutationContext(payload.mutationContext)));
|
|
75
|
+
case "get-document":
|
|
76
|
+
case "get-style-context":
|
|
77
|
+
return typeof payload.routeId === "string";
|
|
78
|
+
case "get-object-edit-context":
|
|
79
|
+
case "get-class-name-source":
|
|
80
|
+
case "resolve-source-location":
|
|
81
|
+
return (typeof payload.key === "string" &&
|
|
82
|
+
(payload.routeId === undefined || typeof payload.routeId === "string"));
|
|
83
|
+
case "get-history-status":
|
|
84
|
+
return payload.mutationContext === undefined;
|
|
85
|
+
case "get-source-tree":
|
|
86
|
+
return true;
|
|
87
|
+
case "get-source-file":
|
|
88
|
+
return typeof payload.path === "string";
|
|
89
|
+
case "execute":
|
|
90
|
+
return (isOperationRequestLike(payload.request) &&
|
|
91
|
+
(payload.request.mutationContext === undefined ||
|
|
92
|
+
isValidMutationContext(payload.request.mutationContext)) &&
|
|
93
|
+
(options.validateOperationRequest?.(payload.request) ?? true));
|
|
94
|
+
case "undo":
|
|
95
|
+
case "redo":
|
|
96
|
+
return (payload.mutationContext === undefined ||
|
|
97
|
+
isValidMutationContext(payload.mutationContext));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export class SiteEditBridgeSocketServer {
|
|
102
|
+
constructor(options) {
|
|
103
|
+
this.options = options;
|
|
104
|
+
this.messageQueue = new SerializedSiteEditMessageQueue();
|
|
105
|
+
this.eventDispatcher = new BufferedSiteEditEventDispatcher((message) => {
|
|
106
|
+
this.sendJson(message);
|
|
107
|
+
});
|
|
108
|
+
this.unsubscribe = options.engine.subscribe((event) => {
|
|
109
|
+
this.eventDispatcher.push(event);
|
|
110
|
+
});
|
|
111
|
+
options.socket.onmessage = (event) => {
|
|
112
|
+
try {
|
|
113
|
+
void this.messageQueue.enqueue(() => this.handleRawMessage(event.data));
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.sendJson({
|
|
117
|
+
type: "error",
|
|
118
|
+
payload: {
|
|
119
|
+
id: "unknown",
|
|
120
|
+
kind: "get-document",
|
|
121
|
+
message: error instanceof Error ? error.message : "Bridge queue overflow",
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
options.socket.close(1013, "bridge busy");
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
options.socket.onclose = () => {
|
|
128
|
+
this.unsubscribe();
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
async handleMessage(message) {
|
|
132
|
+
try {
|
|
133
|
+
const parsed = message;
|
|
134
|
+
if (parsed.type !== "request") {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const payload = parsed.payload;
|
|
138
|
+
const { id } = payload;
|
|
139
|
+
switch (payload.kind) {
|
|
140
|
+
case "initialize-session": {
|
|
141
|
+
const result = await this.options.engine.initializeSession({
|
|
142
|
+
routeId: payload.routeId,
|
|
143
|
+
...(payload.mutationContext
|
|
144
|
+
? { mutationContext: payload.mutationContext }
|
|
145
|
+
: {}),
|
|
146
|
+
});
|
|
147
|
+
this.sendJson({
|
|
148
|
+
type: "response",
|
|
149
|
+
payload: {
|
|
150
|
+
id,
|
|
151
|
+
kind: payload.kind,
|
|
152
|
+
result: {
|
|
153
|
+
ok: true,
|
|
154
|
+
data: {
|
|
155
|
+
document: encodeRenderDocument(result.data.document),
|
|
156
|
+
historyStatus: result.data.historyStatus,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
case "get-document":
|
|
164
|
+
this.sendJson({
|
|
165
|
+
type: "response",
|
|
166
|
+
payload: {
|
|
167
|
+
id,
|
|
168
|
+
kind: payload.kind,
|
|
169
|
+
result: {
|
|
170
|
+
ok: true,
|
|
171
|
+
data: encodeRenderDocument((await this.options.engine.getDocument(payload.routeId)).data),
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
return;
|
|
176
|
+
case "get-history-status":
|
|
177
|
+
this.sendJson({
|
|
178
|
+
type: "response",
|
|
179
|
+
payload: {
|
|
180
|
+
id,
|
|
181
|
+
kind: payload.kind,
|
|
182
|
+
result: await this.options.engine.getHistoryStatus(),
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
case "get-style-context":
|
|
187
|
+
this.sendJson({
|
|
188
|
+
type: "response",
|
|
189
|
+
payload: {
|
|
190
|
+
id,
|
|
191
|
+
kind: payload.kind,
|
|
192
|
+
result: await this.options.engine.getStyleContext(payload.routeId),
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
return;
|
|
196
|
+
case "get-object-edit-context":
|
|
197
|
+
this.sendJson({
|
|
198
|
+
type: "response",
|
|
199
|
+
payload: {
|
|
200
|
+
id,
|
|
201
|
+
kind: payload.kind,
|
|
202
|
+
result: await this.options.engine.getObjectEditContext(payload.key, payload.routeId),
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return;
|
|
206
|
+
case "get-class-name-source":
|
|
207
|
+
this.sendJson({
|
|
208
|
+
type: "response",
|
|
209
|
+
payload: {
|
|
210
|
+
id,
|
|
211
|
+
kind: payload.kind,
|
|
212
|
+
result: await this.options.engine.getClassNameSource(payload.key, payload.routeId),
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
case "get-source-tree":
|
|
217
|
+
this.sendJson({
|
|
218
|
+
type: "response",
|
|
219
|
+
payload: {
|
|
220
|
+
id,
|
|
221
|
+
kind: payload.kind,
|
|
222
|
+
result: await this.options.engine.getSourceTree(),
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
case "get-source-file":
|
|
227
|
+
this.sendJson({
|
|
228
|
+
type: "response",
|
|
229
|
+
payload: {
|
|
230
|
+
id,
|
|
231
|
+
kind: payload.kind,
|
|
232
|
+
result: await this.options.engine.getSourceFile({
|
|
233
|
+
path: payload.path,
|
|
234
|
+
}),
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
case "resolve-source-location":
|
|
239
|
+
this.sendJson({
|
|
240
|
+
type: "response",
|
|
241
|
+
payload: {
|
|
242
|
+
id,
|
|
243
|
+
kind: payload.kind,
|
|
244
|
+
result: await this.options.engine.resolveSourceLocation({
|
|
245
|
+
key: payload.key,
|
|
246
|
+
routeId: payload.routeId,
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
case "execute": {
|
|
252
|
+
await this.handleBufferedOperation(id, payload.kind, () => this.options.engine.execute(payload.request));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
case "undo":
|
|
256
|
+
await this.handleBufferedOperation(id, payload.kind, () => this.options.engine.undo());
|
|
257
|
+
return;
|
|
258
|
+
case "redo":
|
|
259
|
+
await this.handleBufferedOperation(id, payload.kind, () => this.options.engine.redo());
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
const requestInfo = SiteEditBridgeTransport.extractRequestInfo(message);
|
|
265
|
+
this.sendJson({
|
|
266
|
+
type: "error",
|
|
267
|
+
payload: {
|
|
268
|
+
...requestInfo,
|
|
269
|
+
message: getBridgeErrorMessage(error),
|
|
270
|
+
code: getBridgeErrorCode(error),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
async handleRawMessage(raw) {
|
|
276
|
+
try {
|
|
277
|
+
await this.handleMessage(SiteEditBridgeTransport.parseMessage(raw, {
|
|
278
|
+
validateOperationRequest: this.options.validateOperationRequest,
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
const message = error instanceof Error ? error.message : "Invalid websocket message";
|
|
283
|
+
const requestInfo = extractRawRequestInfo(raw);
|
|
284
|
+
this.sendJson({
|
|
285
|
+
type: "error",
|
|
286
|
+
payload: { ...requestInfo, message },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
sendJson(message) {
|
|
291
|
+
this.options.socket.send(SiteEditBridgeTransport.stringifyMessage(message));
|
|
292
|
+
}
|
|
293
|
+
async handleBufferedOperation(id, kind, operation) {
|
|
294
|
+
this.eventDispatcher.beginBuffering();
|
|
295
|
+
try {
|
|
296
|
+
const result = await operation();
|
|
297
|
+
this.sendJson({ type: "response", payload: { id, kind, result } });
|
|
298
|
+
this.eventDispatcher.flush();
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
this.sendJson({
|
|
302
|
+
type: "error",
|
|
303
|
+
payload: {
|
|
304
|
+
id,
|
|
305
|
+
kind,
|
|
306
|
+
message: getBridgeErrorMessage(error),
|
|
307
|
+
code: getBridgeErrorCode(error),
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
this.eventDispatcher.flush();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
class BufferedSiteEditEventDispatcher {
|
|
315
|
+
constructor(send) {
|
|
316
|
+
this.send = send;
|
|
317
|
+
this.bufferedEvents = null;
|
|
318
|
+
}
|
|
319
|
+
push(event) {
|
|
320
|
+
const message = {
|
|
321
|
+
type: "event",
|
|
322
|
+
payload: { event },
|
|
323
|
+
};
|
|
324
|
+
if (this.bufferedEvents) {
|
|
325
|
+
this.bufferedEvents.push(message);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
this.send(message);
|
|
329
|
+
}
|
|
330
|
+
beginBuffering() {
|
|
331
|
+
this.bufferedEvents = [];
|
|
332
|
+
}
|
|
333
|
+
flush() {
|
|
334
|
+
const buffered = this.bufferedEvents ?? [];
|
|
335
|
+
this.bufferedEvents = null;
|
|
336
|
+
buffered.forEach((message) => {
|
|
337
|
+
this.send(message);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
class SerializedSiteEditMessageQueue {
|
|
342
|
+
constructor(maxQueued = 64) {
|
|
343
|
+
this.maxQueued = maxQueued;
|
|
344
|
+
this.queue = Promise.resolve();
|
|
345
|
+
this.queued = 0;
|
|
346
|
+
}
|
|
347
|
+
enqueue(task) {
|
|
348
|
+
if (this.queued >= this.maxQueued) {
|
|
349
|
+
throw new Error("Bridge request queue is full");
|
|
350
|
+
}
|
|
351
|
+
this.queued += 1;
|
|
352
|
+
const run = async () => {
|
|
353
|
+
this.queued -= 1;
|
|
354
|
+
await task();
|
|
355
|
+
};
|
|
356
|
+
const next = this.queue.then(run, run);
|
|
357
|
+
this.queue = next.then(() => undefined, () => undefined);
|
|
358
|
+
return next;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function isRecord(value) {
|
|
362
|
+
return typeof value === "object" && value !== null;
|
|
363
|
+
}
|
|
364
|
+
function isOperationRequestLike(value) {
|
|
365
|
+
return (isRecord(value) &&
|
|
366
|
+
typeof value.id === "string" &&
|
|
367
|
+
typeof value.siteId === "string" &&
|
|
368
|
+
typeof value.baseSnapshotId === "string" &&
|
|
369
|
+
(value.routeId === undefined || typeof value.routeId === "string") &&
|
|
370
|
+
typeof value.kind === "string" &&
|
|
371
|
+
isRecord(value.target) &&
|
|
372
|
+
typeof value.documentVersion === "number" &&
|
|
373
|
+
Number.isInteger(value.documentVersion) &&
|
|
374
|
+
value.documentVersion >= 0 &&
|
|
375
|
+
isRecord(value.params));
|
|
376
|
+
}
|
|
377
|
+
function isValidMutationContext(value) {
|
|
378
|
+
return isMutationContextLike(value);
|
|
379
|
+
}
|
|
380
|
+
function getBridgeErrorCode(error) {
|
|
381
|
+
if (!error || typeof error !== "object") {
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
const code = error.code;
|
|
385
|
+
return typeof code === "string" && code.length > 0 ? code : undefined;
|
|
386
|
+
}
|
|
387
|
+
function getBridgeErrorMessage(error) {
|
|
388
|
+
if (error instanceof Error) {
|
|
389
|
+
return error.message;
|
|
390
|
+
}
|
|
391
|
+
if (!error || typeof error !== "object") {
|
|
392
|
+
return "Unknown websocket operation failure";
|
|
393
|
+
}
|
|
394
|
+
const message = error.message;
|
|
395
|
+
return typeof message === "string" && message.length > 0
|
|
396
|
+
? message
|
|
397
|
+
: "Unknown websocket operation failure";
|
|
398
|
+
}
|
|
399
|
+
function isMutationContextLike(value) {
|
|
400
|
+
if (!isRecord(value)) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
return (typeof value.editingSessionId === "string" &&
|
|
404
|
+
value.editingSessionId.length > 0 &&
|
|
405
|
+
typeof value.lockId === "string" &&
|
|
406
|
+
value.lockId.length > 0 &&
|
|
407
|
+
typeof value.ownerId === "string" &&
|
|
408
|
+
value.ownerId.length > 0 &&
|
|
409
|
+
typeof value.leaseAssertion === "string" &&
|
|
410
|
+
value.leaseAssertion.length > 0);
|
|
411
|
+
}
|
|
412
|
+
function extractRawRequestInfo(raw) {
|
|
413
|
+
try {
|
|
414
|
+
return SiteEditBridgeTransport.extractRequestInfo(JSON.parse(raw));
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
return { id: "unknown", kind: "get-document" };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { startSiteEditNextDevServer } from "./next-dev-server.js";
|
|
2
|
+
export async function runStartNextDevServerCli(argv, dependencies = {}) {
|
|
3
|
+
const env = dependencies.env ?? process.env;
|
|
4
|
+
const input = parseInput(argv);
|
|
5
|
+
const port = readPort(input.port ?? env.PORT ?? env.VCP_PREVIEW_PORT, 3000);
|
|
6
|
+
const host = input.host ?? env.HOST ?? "0.0.0.0";
|
|
7
|
+
const projectRoot = input.projectRoot ?? (dependencies.cwd ?? process.cwd)();
|
|
8
|
+
const preview = await (dependencies.startServer ?? startSiteEditNextDevServer)({
|
|
9
|
+
host,
|
|
10
|
+
port,
|
|
11
|
+
projectRoot,
|
|
12
|
+
siteId: env.VCP_SITE_ID ?? "site_preview",
|
|
13
|
+
snapshotId: env.VCP_SNAPSHOT_ID,
|
|
14
|
+
});
|
|
15
|
+
(dependencies.log ?? console.log)(JSON.stringify({
|
|
16
|
+
status: "ready",
|
|
17
|
+
kind: "site-edit-next-dev-server",
|
|
18
|
+
projectRoot,
|
|
19
|
+
preview_url: `http://127.0.0.1:${preview.port}`,
|
|
20
|
+
bridge_path: "/__vcp/site-edit/bridge",
|
|
21
|
+
bundler: preview.bundler,
|
|
22
|
+
}, null, 2));
|
|
23
|
+
return preview;
|
|
24
|
+
}
|
|
25
|
+
export async function main(argv, dependencies = {}) {
|
|
26
|
+
try {
|
|
27
|
+
await runStartNextDevServerCli(argv, dependencies);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
(dependencies.error ?? console.error)(error);
|
|
31
|
+
(dependencies.setExitCode ?? ((exitCode) => (process.exitCode = exitCode)))(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function parseInput(argv) {
|
|
35
|
+
const input = {};
|
|
36
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
37
|
+
const arg = argv[index];
|
|
38
|
+
switch (arg) {
|
|
39
|
+
case "--host":
|
|
40
|
+
input.host = readValue(argv, (index += 1), arg);
|
|
41
|
+
break;
|
|
42
|
+
case "--port":
|
|
43
|
+
input.port = readValue(argv, (index += 1), arg);
|
|
44
|
+
break;
|
|
45
|
+
case "--project-root":
|
|
46
|
+
input.projectRoot = readValue(argv, (index += 1), arg);
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return input;
|
|
53
|
+
}
|
|
54
|
+
function readValue(argv, index, optionName) {
|
|
55
|
+
const value = argv[index];
|
|
56
|
+
if (!value ||
|
|
57
|
+
(value.startsWith("-") && !(optionName === "--port" && /^-\d/.test(value)))) {
|
|
58
|
+
throw new Error(`${optionName} requires a value.`);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
export function readPort(value, fallback) {
|
|
63
|
+
if (value === undefined || value.trim() === "") {
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
if (!/^(?:0|[1-9]\d*)$/.test(value)) {
|
|
67
|
+
throw new Error(`Invalid port: ${value}. Expected an integer from 0 to 65535.`);
|
|
68
|
+
}
|
|
69
|
+
const parsed = Number(value);
|
|
70
|
+
if (parsed > 65_535) {
|
|
71
|
+
throw new Error(`Invalid port: ${value}. Expected an integer from 0 to 65535.`);
|
|
72
|
+
}
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
@@ -1,83 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
export async function runStartNextDevServerCli(argv, dependencies = {}) {
|
|
5
|
-
const env = dependencies.env ?? process.env;
|
|
6
|
-
const input = parseInput(argv);
|
|
7
|
-
const port = readPort(input.port ?? env.PORT ?? env.VCP_PREVIEW_PORT, 3000);
|
|
8
|
-
const host = input.host ?? env.HOST ?? "0.0.0.0";
|
|
9
|
-
const projectRoot = input.projectRoot ?? (dependencies.cwd ?? process.cwd)();
|
|
10
|
-
const preview = await (dependencies.startServer ?? startSiteEditNextDevServer)({
|
|
11
|
-
host,
|
|
12
|
-
port,
|
|
13
|
-
projectRoot,
|
|
14
|
-
siteId: env.VCP_SITE_ID ?? "site_preview",
|
|
15
|
-
snapshotId: env.VCP_SNAPSHOT_ID,
|
|
16
|
-
});
|
|
17
|
-
(dependencies.log ?? console.log)(JSON.stringify({
|
|
18
|
-
status: "ready",
|
|
19
|
-
kind: "site-edit-next-dev-server",
|
|
20
|
-
projectRoot: projectRoot,
|
|
21
|
-
preview_url: `http://127.0.0.1:${preview.port}`,
|
|
22
|
-
bridge_path: "/__vcp/site-edit/bridge",
|
|
23
|
-
bundler: preview.bundler,
|
|
24
|
-
}, null, 2));
|
|
25
|
-
return preview;
|
|
26
|
-
}
|
|
27
|
-
export async function main(argv, dependencies = {}) {
|
|
28
|
-
try {
|
|
29
|
-
await runStartNextDevServerCli(argv, dependencies);
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
(dependencies.error ?? console.error)(error);
|
|
33
|
-
(dependencies.setExitCode ?? ((exitCode) => (process.exitCode = exitCode)))(1);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
export function parseInput(argv) {
|
|
37
|
-
const input = {};
|
|
38
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
39
|
-
const arg = argv[index];
|
|
40
|
-
switch (arg) {
|
|
41
|
-
case "--host":
|
|
42
|
-
input.host = readValue(argv, (index += 1), arg);
|
|
43
|
-
break;
|
|
44
|
-
case "--port":
|
|
45
|
-
input.port = readValue(argv, (index += 1), arg);
|
|
46
|
-
break;
|
|
47
|
-
case "--project-root":
|
|
48
|
-
input.projectRoot = readValue(argv, (index += 1), arg);
|
|
49
|
-
break;
|
|
50
|
-
default:
|
|
51
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return input;
|
|
55
|
-
}
|
|
56
|
-
function readValue(argv, index, optionName) {
|
|
57
|
-
const value = argv[index];
|
|
58
|
-
if (!value ||
|
|
59
|
-
(value.startsWith("-") && !(optionName === "--port" && /^-\d/.test(value)))) {
|
|
60
|
-
throw new Error(`${optionName} requires a value.`);
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
export function readPort(value, fallback) {
|
|
65
|
-
if (value === undefined || value.trim() === "") {
|
|
66
|
-
return fallback;
|
|
67
|
-
}
|
|
68
|
-
if (!/^(?:0|[1-9]\d*)$/.test(value)) {
|
|
69
|
-
throw new Error(`Invalid port: ${value}. Expected an integer from 0 to 65535.`);
|
|
70
|
-
}
|
|
71
|
-
const parsed = Number(value);
|
|
72
|
-
if (parsed > 65_535) {
|
|
73
|
-
throw new Error(`Invalid port: ${value}. Expected an integer from 0 to 65535.`);
|
|
74
|
-
}
|
|
75
|
-
return parsed;
|
|
76
|
-
}
|
|
77
|
-
function isMainModule() {
|
|
78
|
-
const entryPath = process.argv[1];
|
|
79
|
-
return Boolean(entryPath && import.meta.url === pathToFileURL(entryPath).href);
|
|
80
|
-
}
|
|
81
|
-
if (isMainModule()) {
|
|
82
|
-
void main(process.argv.slice(2));
|
|
83
|
-
}
|
|
2
|
+
import { main } from "./start-next-dev-server-cli.js";
|
|
3
|
+
void main(process.argv.slice(2));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vcp-dev/site-edit-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@vcp-dev/contracts": "0.6.
|
|
78
|
-
"@vcp-dev/site-edit-engine": "0.6.
|
|
77
|
+
"@vcp-dev/contracts": "0.6.4",
|
|
78
|
+
"@vcp-dev/site-edit-engine": "0.6.4",
|
|
79
79
|
"@types/ws": "^8.18.1",
|
|
80
80
|
"ws": "^8.20.1"
|
|
81
81
|
},
|