@ollie-shop/cli 1.4.0 → 1.4.1
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/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +6 -0
- package/dist/index.js +77 -1
- package/package.json +1 -1
- package/src/utils/esbuild.ts +86 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @ollie-shop/cli@1.4.
|
|
2
|
+
> @ollie-shop/cli@1.4.1 build /home/runner/work/ollie-shop/ollie-shop/packages/cli
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.tsx
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
[34mCLI[39m Target: node22
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m95.89 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 248ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @ollie-shop/cli
|
|
2
2
|
|
|
3
|
+
## 1.4.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 630ae3e: Keep Studio live reload working after a component is added or removed. The dev server proxy now owns the `/esbuild` connection and re-subscribes across context recreates, so the browser's EventSource no longer drops when the esbuild context is disposed.
|
|
8
|
+
|
|
3
9
|
## 1.4.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/index.js
CHANGED
|
@@ -700,6 +700,7 @@ async function startDevServer(options = {}) {
|
|
|
700
700
|
ctx = null;
|
|
701
701
|
if (oldCtx) await oldCtx.dispose();
|
|
702
702
|
await buildAndServe(components);
|
|
703
|
+
notifyComponentsChanged(components);
|
|
703
704
|
}
|
|
704
705
|
await buildAndServe(await discoverComponents({ cwd, stage }));
|
|
705
706
|
async function currentComponentNames() {
|
|
@@ -737,8 +738,80 @@ async function startDevServer(options = {}) {
|
|
|
737
738
|
if (watchTimer) clearTimeout(watchTimer);
|
|
738
739
|
watchTimer = setTimeout(maybeRecreate, 150);
|
|
739
740
|
});
|
|
741
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
742
|
+
let upstreamReq = null;
|
|
743
|
+
let upstreamRetry = null;
|
|
744
|
+
function broadcast(chunk) {
|
|
745
|
+
for (const client of sseClients) {
|
|
746
|
+
if (!client.writableEnded) client.write(chunk);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
function connectUpstream() {
|
|
750
|
+
if (upstreamReq || sseClients.size === 0) return;
|
|
751
|
+
const req = http.request(
|
|
752
|
+
{
|
|
753
|
+
hostname: host,
|
|
754
|
+
port: internalPort,
|
|
755
|
+
path: "/esbuild",
|
|
756
|
+
method: "GET",
|
|
757
|
+
headers: { accept: "text/event-stream" }
|
|
758
|
+
},
|
|
759
|
+
(upstream) => {
|
|
760
|
+
upstream.on("data", broadcast);
|
|
761
|
+
upstream.on("close", onUpstreamLost);
|
|
762
|
+
upstream.on("error", onUpstreamLost);
|
|
763
|
+
}
|
|
764
|
+
);
|
|
765
|
+
req.on("error", onUpstreamLost);
|
|
766
|
+
req.end();
|
|
767
|
+
upstreamReq = req;
|
|
768
|
+
}
|
|
769
|
+
function onUpstreamLost() {
|
|
770
|
+
if (!upstreamReq) return;
|
|
771
|
+
upstreamReq = null;
|
|
772
|
+
if (upstreamRetry) clearTimeout(upstreamRetry);
|
|
773
|
+
if (sseClients.size === 0) return;
|
|
774
|
+
upstreamRetry = setTimeout(connectUpstream, 200);
|
|
775
|
+
}
|
|
776
|
+
function teardownUpstream() {
|
|
777
|
+
if (upstreamRetry) {
|
|
778
|
+
clearTimeout(upstreamRetry);
|
|
779
|
+
upstreamRetry = null;
|
|
780
|
+
}
|
|
781
|
+
const req = upstreamReq;
|
|
782
|
+
upstreamReq = null;
|
|
783
|
+
req?.destroy();
|
|
784
|
+
}
|
|
785
|
+
function notifyComponentsChanged(components) {
|
|
786
|
+
if (sseClients.size === 0) return;
|
|
787
|
+
const updated = components.map((c) => `/${c.name}/index.js`);
|
|
788
|
+
const payload = JSON.stringify({ added: [], removed: [], updated });
|
|
789
|
+
broadcast(`event: change
|
|
790
|
+
data: ${payload}
|
|
791
|
+
|
|
792
|
+
`);
|
|
793
|
+
}
|
|
740
794
|
const proxyServer = http.createServer(async (req, res) => {
|
|
741
795
|
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
796
|
+
if (url.pathname === "/esbuild" && req.method === "GET") {
|
|
797
|
+
res.writeHead(200, {
|
|
798
|
+
"Content-Type": "text/event-stream",
|
|
799
|
+
"Cache-Control": "no-cache",
|
|
800
|
+
Connection: "keep-alive",
|
|
801
|
+
"Access-Control-Allow-Origin": "*",
|
|
802
|
+
"Access-Control-Allow-Private-Network": "true"
|
|
803
|
+
});
|
|
804
|
+
res.write("retry: 500\n\n");
|
|
805
|
+
sseClients.add(res);
|
|
806
|
+
connectUpstream();
|
|
807
|
+
const dropClient = () => {
|
|
808
|
+
sseClients.delete(res);
|
|
809
|
+
if (sseClients.size === 0) teardownUpstream();
|
|
810
|
+
};
|
|
811
|
+
req.on("close", dropClient);
|
|
812
|
+
res.on("error", dropClient);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
742
815
|
if (url.pathname === "/bundle" && req.method === "GET") {
|
|
743
816
|
const componentPath = url.searchParams.get("path");
|
|
744
817
|
if (!componentPath) {
|
|
@@ -896,6 +969,9 @@ async function startDevServer(options = {}) {
|
|
|
896
969
|
stop: async () => {
|
|
897
970
|
if (watchTimer) clearTimeout(watchTimer);
|
|
898
971
|
componentsWatcher.close();
|
|
972
|
+
teardownUpstream();
|
|
973
|
+
for (const client of sseClients) client.end();
|
|
974
|
+
sseClients.clear();
|
|
899
975
|
proxyServer.close();
|
|
900
976
|
await ctx?.dispose();
|
|
901
977
|
}
|
|
@@ -1264,7 +1340,7 @@ function App({ command, args }) {
|
|
|
1264
1340
|
}
|
|
1265
1341
|
}
|
|
1266
1342
|
function VersionCommand() {
|
|
1267
|
-
const version = "1.4.
|
|
1343
|
+
const version = "1.4.1" ? "1.4.1" : "unknown";
|
|
1268
1344
|
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1269
1345
|
"ollieshop v",
|
|
1270
1346
|
version
|
package/package.json
CHANGED
package/src/utils/esbuild.ts
CHANGED
|
@@ -267,6 +267,7 @@ export async function startDevServer(
|
|
|
267
267
|
ctx = null;
|
|
268
268
|
if (oldCtx) await oldCtx.dispose();
|
|
269
269
|
await buildAndServe(components);
|
|
270
|
+
notifyComponentsChanged(components);
|
|
270
271
|
}
|
|
271
272
|
|
|
272
273
|
await buildAndServe(await discoverComponents({ cwd, stage }));
|
|
@@ -313,10 +314,92 @@ export async function startDevServer(
|
|
|
313
314
|
watchTimer = setTimeout(maybeRecreate, 150);
|
|
314
315
|
});
|
|
315
316
|
|
|
317
|
+
// esbuild's /esbuild live-reload stream lives on the context, so disposing it
|
|
318
|
+
// during a recreate drops the browser's EventSource — and Studio closes that
|
|
319
|
+
// connection for good on the first error. The proxy owns the downstream
|
|
320
|
+
// connection instead and re-subscribes to esbuild's stream across recreates,
|
|
321
|
+
// so live reload survives a component being added or removed.
|
|
322
|
+
const sseClients = new Set<http.ServerResponse>();
|
|
323
|
+
let upstreamReq: http.ClientRequest | null = null;
|
|
324
|
+
let upstreamRetry: ReturnType<typeof setTimeout> | null = null;
|
|
325
|
+
|
|
326
|
+
function broadcast(chunk: string | Buffer): void {
|
|
327
|
+
for (const client of sseClients) {
|
|
328
|
+
if (!client.writableEnded) client.write(chunk);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function connectUpstream(): void {
|
|
333
|
+
if (upstreamReq || sseClients.size === 0) return;
|
|
334
|
+
const req = http.request(
|
|
335
|
+
{
|
|
336
|
+
hostname: host,
|
|
337
|
+
port: internalPort,
|
|
338
|
+
path: "/esbuild",
|
|
339
|
+
method: "GET",
|
|
340
|
+
headers: { accept: "text/event-stream" },
|
|
341
|
+
},
|
|
342
|
+
(upstream) => {
|
|
343
|
+
upstream.on("data", broadcast);
|
|
344
|
+
upstream.on("close", onUpstreamLost);
|
|
345
|
+
upstream.on("error", onUpstreamLost);
|
|
346
|
+
},
|
|
347
|
+
);
|
|
348
|
+
req.on("error", onUpstreamLost);
|
|
349
|
+
req.end();
|
|
350
|
+
upstreamReq = req;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function onUpstreamLost(): void {
|
|
354
|
+
if (!upstreamReq) return;
|
|
355
|
+
upstreamReq = null;
|
|
356
|
+
if (upstreamRetry) clearTimeout(upstreamRetry);
|
|
357
|
+
if (sseClients.size === 0) return;
|
|
358
|
+
upstreamRetry = setTimeout(connectUpstream, 200);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function teardownUpstream(): void {
|
|
362
|
+
if (upstreamRetry) {
|
|
363
|
+
clearTimeout(upstreamRetry);
|
|
364
|
+
upstreamRetry = null;
|
|
365
|
+
}
|
|
366
|
+
const req = upstreamReq;
|
|
367
|
+
upstreamReq = null;
|
|
368
|
+
req?.destroy();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function notifyComponentsChanged(components: ComponentInfo[]): void {
|
|
372
|
+
if (sseClients.size === 0) return;
|
|
373
|
+
const updated = components.map((c) => `/${c.name}/index.js`);
|
|
374
|
+
const payload = JSON.stringify({ added: [], removed: [], updated });
|
|
375
|
+
broadcast(`event: change\ndata: ${payload}\n\n`);
|
|
376
|
+
}
|
|
377
|
+
|
|
316
378
|
// Create proxy server that handles /bundle/* and forwards to esbuild
|
|
317
379
|
const proxyServer = http.createServer(async (req, res) => {
|
|
318
380
|
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
319
381
|
|
|
382
|
+
// Hold the Studio live-reload stream open across esbuild recreates
|
|
383
|
+
if (url.pathname === "/esbuild" && req.method === "GET") {
|
|
384
|
+
res.writeHead(200, {
|
|
385
|
+
"Content-Type": "text/event-stream",
|
|
386
|
+
"Cache-Control": "no-cache",
|
|
387
|
+
Connection: "keep-alive",
|
|
388
|
+
"Access-Control-Allow-Origin": "*",
|
|
389
|
+
"Access-Control-Allow-Private-Network": "true",
|
|
390
|
+
});
|
|
391
|
+
res.write("retry: 500\n\n");
|
|
392
|
+
sseClients.add(res);
|
|
393
|
+
connectUpstream();
|
|
394
|
+
const dropClient = () => {
|
|
395
|
+
sseClients.delete(res);
|
|
396
|
+
if (sseClients.size === 0) teardownUpstream();
|
|
397
|
+
};
|
|
398
|
+
req.on("close", dropClient);
|
|
399
|
+
res.on("error", dropClient);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
320
403
|
// Handle /bundle?path=/ComponentName/index.js
|
|
321
404
|
if (url.pathname === "/bundle" && req.method === "GET") {
|
|
322
405
|
const componentPath = url.searchParams.get("path");
|
|
@@ -520,6 +603,9 @@ export async function startDevServer(
|
|
|
520
603
|
stop: async () => {
|
|
521
604
|
if (watchTimer) clearTimeout(watchTimer);
|
|
522
605
|
componentsWatcher.close();
|
|
606
|
+
teardownUpstream();
|
|
607
|
+
for (const client of sseClients) client.end();
|
|
608
|
+
sseClients.clear();
|
|
523
609
|
proxyServer.close();
|
|
524
610
|
await ctx?.dispose();
|
|
525
611
|
},
|