@ollie-shop/cli 1.4.1 → 1.6.0
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 +12 -0
- package/dist/index.js +523 -16
- package/package.json +1 -1
- package/src/commands/help.tsx +15 -0
- package/src/commands/setup-cmd.ts +142 -0
- package/src/commands/start.tsx +68 -1
- package/src/index.tsx +2 -0
- package/src/utils/config.ts +76 -0
- package/src/utils/esbuild.ts +376 -20
package/dist/index.js
CHANGED
|
@@ -70,6 +70,10 @@ function HelpCommand() {
|
|
|
70
70
|
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "init" }) }),
|
|
71
71
|
/* @__PURE__ */ jsx(Text, { children: "Write store/version IDs to ollie.json" })
|
|
72
72
|
] }),
|
|
73
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "setup" }) }),
|
|
75
|
+
/* @__PURE__ */ jsx(Text, { children: "Migrate legacy meta.json into the config components map" })
|
|
76
|
+
] }),
|
|
73
77
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
74
78
|
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "help" }) }),
|
|
75
79
|
/* @__PURE__ */ jsx(Text, { children: "Show this help message" })
|
|
@@ -111,6 +115,10 @@ function HelpCommand() {
|
|
|
111
115
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
112
116
|
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "--no-open" }) }),
|
|
113
117
|
/* @__PURE__ */ jsx(Text, { children: "start: don't auto-open Studio (also honored via CI env)" })
|
|
118
|
+
] }),
|
|
119
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
120
|
+
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "--browser-logs" }) }),
|
|
121
|
+
/* @__PURE__ */ jsx(Text, { children: "start: stream custom components' browser console to terminal" })
|
|
114
122
|
] })
|
|
115
123
|
] }),
|
|
116
124
|
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, children: "Examples:" }) }),
|
|
@@ -118,6 +126,7 @@ function HelpCommand() {
|
|
|
118
126
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop login" }),
|
|
119
127
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop start --stage dev" }),
|
|
120
128
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop start --no-open" }),
|
|
129
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop start --browser-logs" }),
|
|
121
130
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop whoami -o json" }),
|
|
122
131
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop schema store.create" }),
|
|
123
132
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: '$ ollieshop store create --name "My Store" --platform vtex --platform-store-id mystore' }),
|
|
@@ -386,9 +395,14 @@ import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } f
|
|
|
386
395
|
import fs2 from "fs/promises";
|
|
387
396
|
import path2 from "path";
|
|
388
397
|
import { z } from "zod";
|
|
398
|
+
var ComponentEntrySchema = z.object({
|
|
399
|
+
path: z.string(),
|
|
400
|
+
slot: z.string().optional()
|
|
401
|
+
});
|
|
389
402
|
var OllieConfigSchema = z.object({
|
|
390
403
|
storeId: z.string().uuid(),
|
|
391
|
-
versionId: z.string().uuid().optional()
|
|
404
|
+
versionId: z.string().uuid().optional(),
|
|
405
|
+
components: z.record(z.string(), ComponentEntrySchema).optional()
|
|
392
406
|
}).passthrough();
|
|
393
407
|
var CONFIG_FILE = "ollie.json";
|
|
394
408
|
function getConfigFileName(stage) {
|
|
@@ -443,6 +457,51 @@ async function saveConfig(config, options = {}) {
|
|
|
443
457
|
};
|
|
444
458
|
await fs2.writeFile(configPath, JSON.stringify(merged, null, 2));
|
|
445
459
|
}
|
|
460
|
+
async function listStages(cwd = process.cwd()) {
|
|
461
|
+
let files;
|
|
462
|
+
try {
|
|
463
|
+
files = await fs2.readdir(cwd);
|
|
464
|
+
} catch {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
const seen = /* @__PURE__ */ new Set();
|
|
468
|
+
const stages = [];
|
|
469
|
+
for (const file of files.sort()) {
|
|
470
|
+
const match = file.match(/^ollie(?:\.([^.]+))?\.json$/);
|
|
471
|
+
if (!match) continue;
|
|
472
|
+
const stage = match[1] ?? "prod";
|
|
473
|
+
if (seen.has(stage)) continue;
|
|
474
|
+
seen.add(stage);
|
|
475
|
+
let versionId;
|
|
476
|
+
try {
|
|
477
|
+
const config = await loadConfig({ cwd, stage: match[1] });
|
|
478
|
+
versionId = config?.versionId;
|
|
479
|
+
} catch {
|
|
480
|
+
}
|
|
481
|
+
stages.push({ stage, versionId });
|
|
482
|
+
}
|
|
483
|
+
return stages;
|
|
484
|
+
}
|
|
485
|
+
async function upsertComponentEntry(id, values, options = {}) {
|
|
486
|
+
const { cwd = process.cwd(), stage } = options;
|
|
487
|
+
const configPath = path2.join(cwd, getConfigFileName(stage));
|
|
488
|
+
const existing = await loadConfigFile(configPath) ?? {};
|
|
489
|
+
const components = {
|
|
490
|
+
...existing.components ?? {}
|
|
491
|
+
};
|
|
492
|
+
const prev = components[id];
|
|
493
|
+
const slot = values.slot ?? prev?.slot;
|
|
494
|
+
const entry = {
|
|
495
|
+
path: prev?.path ?? values.defaultPath,
|
|
496
|
+
...slot !== void 0 ? { slot } : {}
|
|
497
|
+
};
|
|
498
|
+
components[id] = entry;
|
|
499
|
+
await fs2.writeFile(
|
|
500
|
+
configPath,
|
|
501
|
+
JSON.stringify({ ...existing, components }, null, 2)
|
|
502
|
+
);
|
|
503
|
+
return entry;
|
|
504
|
+
}
|
|
446
505
|
function resolveStage(cliStage) {
|
|
447
506
|
return cliStage || process.env.OLLIE_STAGE || void 0;
|
|
448
507
|
}
|
|
@@ -579,19 +638,84 @@ async function readComponentMeta(componentDir, stage) {
|
|
|
579
638
|
return null;
|
|
580
639
|
}
|
|
581
640
|
}
|
|
641
|
+
async function componentsByDir(cwd, stageLabel) {
|
|
642
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
643
|
+
let config = null;
|
|
644
|
+
try {
|
|
645
|
+
config = await loadConfig({ cwd, stage: stageLabel });
|
|
646
|
+
} catch {
|
|
647
|
+
return byDir;
|
|
648
|
+
}
|
|
649
|
+
for (const [id, entry] of Object.entries(config?.components ?? {})) {
|
|
650
|
+
const dir = path4.resolve(cwd, entry.path);
|
|
651
|
+
if (!byDir.has(dir)) byDir.set(dir, { id, slot: entry.slot });
|
|
652
|
+
}
|
|
653
|
+
return byDir;
|
|
654
|
+
}
|
|
582
655
|
async function discoverComponents(options = {}) {
|
|
583
656
|
const { cwd = process.cwd(), stage } = options;
|
|
657
|
+
const activeStage = stage ?? "prod";
|
|
658
|
+
const buildDir = path4.join(cwd, "node_modules/.ollie", "build");
|
|
659
|
+
const components = [];
|
|
660
|
+
const mappedDirs = /* @__PURE__ */ new Set();
|
|
661
|
+
const activeByDir = await componentsByDir(cwd, activeStage);
|
|
662
|
+
const otherStageByDir = /* @__PURE__ */ new Map();
|
|
663
|
+
try {
|
|
664
|
+
for (const { stage: label } of await listStages(cwd)) {
|
|
665
|
+
if (label === activeStage) continue;
|
|
666
|
+
for (const [dir, entry] of await componentsByDir(cwd, label)) {
|
|
667
|
+
if (!activeByDir.has(dir) && !otherStageByDir.has(dir)) {
|
|
668
|
+
otherStageByDir.set(dir, entry);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
for (const [componentDir, entry] of activeByDir) {
|
|
675
|
+
const entryPoint = path4.join(componentDir, "index.tsx");
|
|
676
|
+
try {
|
|
677
|
+
await fs4.access(entryPoint);
|
|
678
|
+
} catch {
|
|
679
|
+
console.warn(
|
|
680
|
+
`${entry.id}: ${componentDir}/index.tsx not found - skipping`
|
|
681
|
+
);
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const name = path4.basename(componentDir);
|
|
685
|
+
mappedDirs.add(componentDir);
|
|
686
|
+
components.push({
|
|
687
|
+
id: entry.id,
|
|
688
|
+
name,
|
|
689
|
+
slot: entry.slot,
|
|
690
|
+
entryPoint,
|
|
691
|
+
outfile: path4.join(buildDir, name, "index.js")
|
|
692
|
+
});
|
|
693
|
+
}
|
|
584
694
|
const componentsDir = path4.join(cwd, "components");
|
|
585
695
|
try {
|
|
586
696
|
await fs4.access(componentsDir);
|
|
587
697
|
} catch {
|
|
588
|
-
return
|
|
698
|
+
return components;
|
|
589
699
|
}
|
|
590
700
|
const entries = await glob("*/index.tsx", { cwd: componentsDir });
|
|
591
|
-
const components = [];
|
|
592
701
|
for (const entry of entries) {
|
|
593
702
|
const name = path4.dirname(entry);
|
|
594
|
-
const componentDir = path4.
|
|
703
|
+
const componentDir = path4.resolve(componentsDir, name);
|
|
704
|
+
if (mappedDirs.has(componentDir)) continue;
|
|
705
|
+
const entryPoint = path4.join(componentsDir, entry);
|
|
706
|
+
const outfile = path4.join(buildDir, name, "index.js");
|
|
707
|
+
const other = otherStageByDir.get(componentDir);
|
|
708
|
+
if (other) {
|
|
709
|
+
components.push({
|
|
710
|
+
id: other.id,
|
|
711
|
+
name,
|
|
712
|
+
slot: other.slot,
|
|
713
|
+
otherStage: true,
|
|
714
|
+
entryPoint,
|
|
715
|
+
outfile
|
|
716
|
+
});
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
595
719
|
const meta = await readComponentMeta(componentDir, stage);
|
|
596
720
|
const id = meta?.id ?? `studio-${name}`;
|
|
597
721
|
const isUnlinked = !meta?.id;
|
|
@@ -604,14 +728,113 @@ async function discoverComponents(options = {}) {
|
|
|
604
728
|
id,
|
|
605
729
|
name,
|
|
606
730
|
slot: meta?.slot,
|
|
607
|
-
|
|
608
|
-
|
|
731
|
+
...isUnlinked ? { unlinked: true } : {},
|
|
732
|
+
entryPoint,
|
|
733
|
+
outfile
|
|
609
734
|
});
|
|
610
735
|
}
|
|
611
736
|
return components;
|
|
612
737
|
}
|
|
738
|
+
function browserLogBridge(endpoint) {
|
|
739
|
+
const target = JSON.stringify(endpoint);
|
|
740
|
+
return `(() => {
|
|
741
|
+
if (typeof window === "undefined" || window.__ollieLogBridge) return;
|
|
742
|
+
window.__ollieLogBridge = true;
|
|
743
|
+
|
|
744
|
+
const endpoint = ${target};
|
|
745
|
+
const SLOT_FRAME = /ollie-slot\\/([^.\\s:]+)\\.js/;
|
|
746
|
+
const LEVELS = ["log", "info", "warn", "error", "debug"];
|
|
747
|
+
const MAX_MESSAGE = 2000;
|
|
748
|
+
const MAX_BATCH = 20;
|
|
749
|
+
const FLUSH_MS = 200;
|
|
750
|
+
|
|
751
|
+
let queue = [];
|
|
752
|
+
let flushTimer = null;
|
|
753
|
+
|
|
754
|
+
function format(value) {
|
|
755
|
+
if (typeof value === "string") return value;
|
|
756
|
+
try {
|
|
757
|
+
return JSON.stringify(value);
|
|
758
|
+
} catch (_) {
|
|
759
|
+
return String(value);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function slotName(stack) {
|
|
764
|
+
const match = (stack || "").match(SLOT_FRAME);
|
|
765
|
+
return match ? match[1] : null;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function flush() {
|
|
769
|
+
if (flushTimer) {
|
|
770
|
+
clearTimeout(flushTimer);
|
|
771
|
+
flushTimer = null;
|
|
772
|
+
}
|
|
773
|
+
if (queue.length === 0) return;
|
|
774
|
+
const batch = queue;
|
|
775
|
+
queue = [];
|
|
776
|
+
try {
|
|
777
|
+
fetch(endpoint, {
|
|
778
|
+
method: "POST",
|
|
779
|
+
mode: "cors",
|
|
780
|
+
keepalive: true,
|
|
781
|
+
headers: { "Content-Type": "text/plain" },
|
|
782
|
+
body: JSON.stringify({ batch: batch }),
|
|
783
|
+
}).catch(function () {});
|
|
784
|
+
} catch (_) {}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function forward(level, component, args) {
|
|
788
|
+
try {
|
|
789
|
+
let message = args.map(format).join(" ");
|
|
790
|
+
if (message.length > MAX_MESSAGE) {
|
|
791
|
+
message = message.slice(0, MAX_MESSAGE) + " \u2026(truncated)";
|
|
792
|
+
}
|
|
793
|
+
queue.push({ level: level, component: component, message: message });
|
|
794
|
+
if (queue.length >= MAX_BATCH) {
|
|
795
|
+
flush();
|
|
796
|
+
} else if (!flushTimer) {
|
|
797
|
+
flushTimer = setTimeout(flush, FLUSH_MS);
|
|
798
|
+
}
|
|
799
|
+
} catch (_) {}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
for (const level of LEVELS) {
|
|
803
|
+
const original = console[level]
|
|
804
|
+
? console[level].bind(console)
|
|
805
|
+
: function () {};
|
|
806
|
+
console[level] = function () {
|
|
807
|
+
const args = Array.prototype.slice.call(arguments);
|
|
808
|
+
original.apply(null, args);
|
|
809
|
+
const component = slotName(new Error().stack);
|
|
810
|
+
if (component) forward(level, component, args);
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
window.addEventListener("error", function (event) {
|
|
815
|
+
const component = slotName(event.error && event.error.stack);
|
|
816
|
+
if (component) forward("error", component, [event.message]);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
window.addEventListener("unhandledrejection", function (event) {
|
|
820
|
+
const reason = event.reason;
|
|
821
|
+
const component = slotName(reason && reason.stack);
|
|
822
|
+
if (component) {
|
|
823
|
+
const message = reason && reason.message ? reason.message : String(reason);
|
|
824
|
+
forward("error", component, [message]);
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
window.addEventListener("pagehide", flush);
|
|
829
|
+
})();`;
|
|
830
|
+
}
|
|
613
831
|
async function createBuildContext(components, options = {}) {
|
|
614
|
-
const {
|
|
832
|
+
const {
|
|
833
|
+
cwd = process.cwd(),
|
|
834
|
+
stage,
|
|
835
|
+
onBuildEnd,
|
|
836
|
+
browserLogsEndpoint
|
|
837
|
+
} = options;
|
|
615
838
|
const outdir = path4.join(cwd, "node_modules/.ollie", "build");
|
|
616
839
|
await fs4.mkdir(outdir, { recursive: true });
|
|
617
840
|
const entryPoints = {};
|
|
@@ -669,7 +892,8 @@ async function createBuildContext(components, options = {}) {
|
|
|
669
892
|
logLevel: "silent",
|
|
670
893
|
// We handle logging ourselves
|
|
671
894
|
jsx: "automatic",
|
|
672
|
-
plugins: [manifestPlugin]
|
|
895
|
+
plugins: [manifestPlugin],
|
|
896
|
+
...browserLogsEndpoint ? { banner: { js: browserLogBridge(browserLogsEndpoint) } } : {}
|
|
673
897
|
});
|
|
674
898
|
return ctx;
|
|
675
899
|
}
|
|
@@ -680,29 +904,37 @@ async function startDevServer(options = {}) {
|
|
|
680
904
|
cwd = process.cwd(),
|
|
681
905
|
stage,
|
|
682
906
|
onRequest,
|
|
683
|
-
onBuildEnd
|
|
907
|
+
onBuildEnd,
|
|
908
|
+
onBrowserLogs
|
|
684
909
|
} = options;
|
|
685
910
|
const servedir = path4.join(cwd, "node_modules/.ollie", "build");
|
|
686
911
|
const componentsDir = path4.join(cwd, "components");
|
|
687
912
|
const internalPort = port + 1;
|
|
913
|
+
const browserLogsEndpoint = onBrowserLogs ? `http://${host}:${port}/__log` : void 0;
|
|
914
|
+
let activeStage = stage ?? "prod";
|
|
688
915
|
let ctx = null;
|
|
689
916
|
let entryNames = /* @__PURE__ */ new Set();
|
|
690
917
|
async function buildAndServe(components) {
|
|
691
918
|
entryNames = new Set(components.map((c) => c.name));
|
|
692
|
-
ctx = await createBuildContext(components, {
|
|
919
|
+
ctx = await createBuildContext(components, {
|
|
920
|
+
cwd,
|
|
921
|
+
stage: activeStage,
|
|
922
|
+
onBuildEnd,
|
|
923
|
+
browserLogsEndpoint
|
|
924
|
+
});
|
|
693
925
|
await ctx.rebuild();
|
|
694
926
|
await ctx.watch();
|
|
695
927
|
await ctx.serve({ port: internalPort, host, servedir, onRequest });
|
|
696
928
|
}
|
|
697
929
|
async function recreate() {
|
|
698
|
-
const components = await discoverComponents({ cwd, stage });
|
|
930
|
+
const components = await discoverComponents({ cwd, stage: activeStage });
|
|
699
931
|
const oldCtx = ctx;
|
|
700
932
|
ctx = null;
|
|
701
933
|
if (oldCtx) await oldCtx.dispose();
|
|
702
934
|
await buildAndServe(components);
|
|
703
935
|
notifyComponentsChanged(components);
|
|
704
936
|
}
|
|
705
|
-
await buildAndServe(await discoverComponents({ cwd, stage }));
|
|
937
|
+
await buildAndServe(await discoverComponents({ cwd, stage: activeStage }));
|
|
706
938
|
async function currentComponentNames() {
|
|
707
939
|
try {
|
|
708
940
|
const entries = await glob("*/index.tsx", { cwd: componentsDir });
|
|
@@ -859,6 +1091,55 @@ data: ${payload}
|
|
|
859
1091
|
}
|
|
860
1092
|
return;
|
|
861
1093
|
}
|
|
1094
|
+
if (url.pathname === "/stages" && req.method === "GET") {
|
|
1095
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1096
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
1097
|
+
res.setHeader("Content-Type", "application/json");
|
|
1098
|
+
const stages = await listStages(cwd);
|
|
1099
|
+
res.end(JSON.stringify({ active: activeStage, stages }));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (url.pathname === "/stage" && req.method === "POST") {
|
|
1103
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1104
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1105
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
1106
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
1107
|
+
let body = "";
|
|
1108
|
+
req.on("data", (chunk) => {
|
|
1109
|
+
body += chunk.toString();
|
|
1110
|
+
});
|
|
1111
|
+
req.on("end", async () => {
|
|
1112
|
+
try {
|
|
1113
|
+
const { stage: next } = JSON.parse(body);
|
|
1114
|
+
if (typeof next !== "string" || !next) {
|
|
1115
|
+
res.statusCode = 400;
|
|
1116
|
+
res.setHeader("Content-Type", "application/json");
|
|
1117
|
+
res.end(JSON.stringify({ error: "Missing 'stage' in body" }));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
activeStage = next;
|
|
1121
|
+
await recreate();
|
|
1122
|
+
let versionId;
|
|
1123
|
+
try {
|
|
1124
|
+
versionId = (await loadConfig({ cwd, stage: activeStage }))?.versionId;
|
|
1125
|
+
} catch {
|
|
1126
|
+
}
|
|
1127
|
+
res.setHeader("Content-Type", "application/json");
|
|
1128
|
+
res.end(
|
|
1129
|
+
JSON.stringify({ success: true, stage: activeStage, versionId })
|
|
1130
|
+
);
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
res.statusCode = 400;
|
|
1133
|
+
res.setHeader("Content-Type", "application/json");
|
|
1134
|
+
res.end(
|
|
1135
|
+
JSON.stringify({
|
|
1136
|
+
error: err instanceof Error ? err.message : "Invalid JSON body"
|
|
1137
|
+
})
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
});
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
862
1143
|
if (url.pathname === "/meta" && req.method === "POST") {
|
|
863
1144
|
const componentName = url.searchParams.get("component");
|
|
864
1145
|
if (!componentName) {
|
|
@@ -880,6 +1161,32 @@ data: ${payload}
|
|
|
880
1161
|
req.on("end", async () => {
|
|
881
1162
|
try {
|
|
882
1163
|
const updates = JSON.parse(body);
|
|
1164
|
+
let cfg = null;
|
|
1165
|
+
try {
|
|
1166
|
+
cfg = await loadConfig({ cwd, stage: activeStage });
|
|
1167
|
+
} catch {
|
|
1168
|
+
}
|
|
1169
|
+
if (cfg?.components !== void 0) {
|
|
1170
|
+
const id = typeof updates.id === "string" ? updates.id : void 0;
|
|
1171
|
+
if (!id) {
|
|
1172
|
+
res.statusCode = 400;
|
|
1173
|
+
res.setHeader("Content-Type", "application/json");
|
|
1174
|
+
res.end(JSON.stringify({ error: "Missing 'id' in body" }));
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
const slot = typeof updates.slot === "string" ? updates.slot : void 0;
|
|
1178
|
+
const entry = await upsertComponentEntry(
|
|
1179
|
+
id,
|
|
1180
|
+
{ defaultPath: `components/${componentName}`, slot },
|
|
1181
|
+
{ cwd, stage: activeStage }
|
|
1182
|
+
);
|
|
1183
|
+
await ctx?.rebuild();
|
|
1184
|
+
res.setHeader("Content-Type", "application/json");
|
|
1185
|
+
res.end(
|
|
1186
|
+
JSON.stringify({ success: true, component: { id, ...entry } })
|
|
1187
|
+
);
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
883
1190
|
const metaPath = path4.join(
|
|
884
1191
|
cwd,
|
|
885
1192
|
"components",
|
|
@@ -918,6 +1225,45 @@ data: ${payload}
|
|
|
918
1225
|
});
|
|
919
1226
|
return;
|
|
920
1227
|
}
|
|
1228
|
+
if (url.pathname === "/__log" && req.method === "POST") {
|
|
1229
|
+
if (!onBrowserLogs) {
|
|
1230
|
+
res.statusCode = 404;
|
|
1231
|
+
res.end();
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1235
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
1236
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
1237
|
+
const MAX_LOG_BODY = 64 * 1024;
|
|
1238
|
+
let body = "";
|
|
1239
|
+
let aborted = false;
|
|
1240
|
+
req.on("data", (chunk) => {
|
|
1241
|
+
if (aborted) return;
|
|
1242
|
+
body += chunk.toString();
|
|
1243
|
+
if (body.length > MAX_LOG_BODY) {
|
|
1244
|
+
aborted = true;
|
|
1245
|
+
res.statusCode = 413;
|
|
1246
|
+
res.end();
|
|
1247
|
+
req.destroy();
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
req.on("end", () => {
|
|
1251
|
+
if (aborted) return;
|
|
1252
|
+
try {
|
|
1253
|
+
const parsed2 = JSON.parse(body);
|
|
1254
|
+
const batch = parsed2.batch;
|
|
1255
|
+
const candidates = Array.isArray(batch) ? batch : [parsed2];
|
|
1256
|
+
const entries = candidates.filter(
|
|
1257
|
+
(entry) => typeof entry?.message === "string"
|
|
1258
|
+
);
|
|
1259
|
+
if (entries.length > 0) onBrowserLogs(entries);
|
|
1260
|
+
} catch {
|
|
1261
|
+
}
|
|
1262
|
+
res.statusCode = 204;
|
|
1263
|
+
res.end();
|
|
1264
|
+
});
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
921
1267
|
if (req.method === "OPTIONS") {
|
|
922
1268
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
923
1269
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
@@ -986,7 +1332,9 @@ async function writeManifest(components, buildResult, cwd = process.cwd()) {
|
|
|
986
1332
|
id: c.id,
|
|
987
1333
|
name: c.name,
|
|
988
1334
|
js: `/${c.name}/index.js`,
|
|
989
|
-
slot: c.slot
|
|
1335
|
+
slot: c.slot,
|
|
1336
|
+
...c.otherStage ? { otherStage: true } : {},
|
|
1337
|
+
...c.unlinked ? { unlinked: true } : {}
|
|
990
1338
|
};
|
|
991
1339
|
const cssPath = path4.join(outdir, c.name, "index.css");
|
|
992
1340
|
try {
|
|
@@ -1020,13 +1368,16 @@ function StartCommand({ args }) {
|
|
|
1020
1368
|
const [state, setState] = useState2({ status: "initializing" });
|
|
1021
1369
|
const [components, setComponents] = useState2([]);
|
|
1022
1370
|
const [logs, setLogs] = useState2([]);
|
|
1371
|
+
const [browserLogs, setBrowserLogs] = useState2([]);
|
|
1023
1372
|
const [buildCount, setBuildCount] = useState2(0);
|
|
1024
1373
|
const [lastBuildTime, setLastBuildTime] = useState2(null);
|
|
1025
1374
|
const logIdRef = useRef(0);
|
|
1375
|
+
const browserLogIdRef = useRef(0);
|
|
1026
1376
|
const rebuildRef = useRef(null);
|
|
1027
1377
|
const stopRef = useRef(null);
|
|
1028
1378
|
const stage = resolveStage(parseArg(args, "--stage", "-s"));
|
|
1029
1379
|
const noOpen = args.includes("--no-open") || Boolean(process.env.CI);
|
|
1380
|
+
const browserLogsEnabled = args.includes("--browser-logs");
|
|
1030
1381
|
const isInteractive = Boolean(process.stdin.isTTY);
|
|
1031
1382
|
const addLog = useCallback((log) => {
|
|
1032
1383
|
setLogs((prev) => {
|
|
@@ -1049,6 +1400,18 @@ function StartCommand({ args }) {
|
|
|
1049
1400
|
},
|
|
1050
1401
|
[addLog]
|
|
1051
1402
|
);
|
|
1403
|
+
const addBrowserLogs = useCallback((entries) => {
|
|
1404
|
+
if (entries.length === 0) return;
|
|
1405
|
+
setBrowserLogs((prev) => {
|
|
1406
|
+
const mapped = entries.map((entry) => ({
|
|
1407
|
+
...entry,
|
|
1408
|
+
message: sanitizeLogMessage(entry.message),
|
|
1409
|
+
id: ++browserLogIdRef.current,
|
|
1410
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
1411
|
+
}));
|
|
1412
|
+
return [...prev, ...mapped].slice(-MAX_LOGS);
|
|
1413
|
+
});
|
|
1414
|
+
}, []);
|
|
1052
1415
|
useEffect2(() => {
|
|
1053
1416
|
let mounted = true;
|
|
1054
1417
|
async function init() {
|
|
@@ -1079,6 +1442,7 @@ function StartCommand({ args }) {
|
|
|
1079
1442
|
port: PORT,
|
|
1080
1443
|
stage,
|
|
1081
1444
|
onRequest: handleRequest,
|
|
1445
|
+
onBrowserLogs: browserLogsEnabled ? addBrowserLogs : void 0,
|
|
1082
1446
|
onBuildEnd: (updatedComponents) => {
|
|
1083
1447
|
setComponents(updatedComponents);
|
|
1084
1448
|
setBuildCount((c) => c + 1);
|
|
@@ -1119,7 +1483,7 @@ function StartCommand({ args }) {
|
|
|
1119
1483
|
mounted = false;
|
|
1120
1484
|
stopRef.current?.();
|
|
1121
1485
|
};
|
|
1122
|
-
}, [stage, handleRequest, noOpen]);
|
|
1486
|
+
}, [stage, handleRequest, addBrowserLogs, browserLogsEnabled, noOpen]);
|
|
1123
1487
|
useInput(
|
|
1124
1488
|
(input, key) => {
|
|
1125
1489
|
if (input === "q" || input === "c" && key.ctrl) {
|
|
@@ -1176,6 +1540,7 @@ function StartCommand({ args }) {
|
|
|
1176
1540
|
/* @__PURE__ */ jsx3(ComponentList, { components }),
|
|
1177
1541
|
/* @__PURE__ */ jsx3(BuildInfo, { buildCount, lastBuildTime }),
|
|
1178
1542
|
/* @__PURE__ */ jsx3(RequestLogs, { logs }),
|
|
1543
|
+
browserLogsEnabled && /* @__PURE__ */ jsx3(BrowserLogs, { logs: browserLogs }),
|
|
1179
1544
|
/* @__PURE__ */ jsx3(Footer, { interactive: isInteractive })
|
|
1180
1545
|
] })
|
|
1181
1546
|
] });
|
|
@@ -1304,6 +1669,38 @@ function RequestLogs({ logs }) {
|
|
|
1304
1669
|
] }, log.id)) })
|
|
1305
1670
|
] });
|
|
1306
1671
|
}
|
|
1672
|
+
function sanitizeLogMessage(message) {
|
|
1673
|
+
let out = "";
|
|
1674
|
+
for (const ch of message) {
|
|
1675
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1676
|
+
out += code < 32 || code >= 127 && code <= 159 ? " " : ch;
|
|
1677
|
+
if (out.length >= 2e3) break;
|
|
1678
|
+
}
|
|
1679
|
+
return out;
|
|
1680
|
+
}
|
|
1681
|
+
function browserLogColor(level) {
|
|
1682
|
+
if (level === "error") return "red";
|
|
1683
|
+
if (level === "warn") return "yellow";
|
|
1684
|
+
if (level === "info") return "cyan";
|
|
1685
|
+
return "gray";
|
|
1686
|
+
}
|
|
1687
|
+
function BrowserLogs({ logs }) {
|
|
1688
|
+
return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1689
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Browser Logs:" }),
|
|
1690
|
+
/* @__PURE__ */ jsx3(Box3, { marginLeft: 2, flexDirection: "column", children: logs.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No browser logs yet..." }) : logs.map((log) => /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
1691
|
+
/* @__PURE__ */ jsxs3(Text3, { color: browserLogColor(log.level), children: [
|
|
1692
|
+
log.level.toUpperCase(),
|
|
1693
|
+
" "
|
|
1694
|
+
] }),
|
|
1695
|
+
log.component && /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1696
|
+
"[",
|
|
1697
|
+
log.component,
|
|
1698
|
+
"] "
|
|
1699
|
+
] }),
|
|
1700
|
+
/* @__PURE__ */ jsx3(Text3, { children: log.message })
|
|
1701
|
+
] }, log.id)) })
|
|
1702
|
+
] });
|
|
1703
|
+
}
|
|
1307
1704
|
function Footer({ interactive = true }) {
|
|
1308
1705
|
if (!interactive) {
|
|
1309
1706
|
return /* @__PURE__ */ jsx3(Box3, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Headless (no TTY) \u2014 Ctrl+C to stop" }) });
|
|
@@ -1340,7 +1737,7 @@ function App({ command, args }) {
|
|
|
1340
1737
|
}
|
|
1341
1738
|
}
|
|
1342
1739
|
function VersionCommand() {
|
|
1343
|
-
const version = "1.
|
|
1740
|
+
const version = "1.6.0" ? "1.6.0" : "unknown";
|
|
1344
1741
|
return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1345
1742
|
"ollieshop v",
|
|
1346
1743
|
version
|
|
@@ -2493,6 +2890,115 @@ async function schemaCommand(parsed2) {
|
|
|
2493
2890
|
`);
|
|
2494
2891
|
}
|
|
2495
2892
|
|
|
2893
|
+
// src/commands/setup-cmd.ts
|
|
2894
|
+
import fs5 from "fs/promises";
|
|
2895
|
+
import path5 from "path";
|
|
2896
|
+
import { glob as glob2 } from "glob";
|
|
2897
|
+
var META_FILE_RE = /^meta(?:\.([^.]+))?\.json$/;
|
|
2898
|
+
function configFileForStage(stage) {
|
|
2899
|
+
return !stage || stage === "prod" ? "ollie.json" : `ollie.${stage}.json`;
|
|
2900
|
+
}
|
|
2901
|
+
async function setupCommand(parsed2) {
|
|
2902
|
+
const format = detectOutputFormat(parsed2.global.output);
|
|
2903
|
+
const cwd = process.cwd();
|
|
2904
|
+
const dryRun = parsed2.global.dryRun;
|
|
2905
|
+
try {
|
|
2906
|
+
const componentsDir = path5.join(cwd, "components");
|
|
2907
|
+
let folders = [];
|
|
2908
|
+
try {
|
|
2909
|
+
await fs5.access(componentsDir);
|
|
2910
|
+
const entries = await glob2("*/index.tsx", { cwd: componentsDir });
|
|
2911
|
+
folders = entries.map((entry) => path5.dirname(entry));
|
|
2912
|
+
} catch {
|
|
2913
|
+
}
|
|
2914
|
+
const migrated = [];
|
|
2915
|
+
const skipped = [];
|
|
2916
|
+
for (const name of folders) {
|
|
2917
|
+
const folderDir = path5.join(componentsDir, name);
|
|
2918
|
+
const metaFiles = await glob2("meta*.json", { cwd: folderDir });
|
|
2919
|
+
for (const metaFile of metaFiles) {
|
|
2920
|
+
const match = metaFile.match(META_FILE_RE);
|
|
2921
|
+
if (!match) continue;
|
|
2922
|
+
const stage = match[1];
|
|
2923
|
+
let meta;
|
|
2924
|
+
try {
|
|
2925
|
+
meta = JSON.parse(
|
|
2926
|
+
await fs5.readFile(path5.join(folderDir, metaFile), "utf-8")
|
|
2927
|
+
);
|
|
2928
|
+
} catch {
|
|
2929
|
+
skipped.push({
|
|
2930
|
+
component: name,
|
|
2931
|
+
metaFile,
|
|
2932
|
+
reason: "unreadable meta"
|
|
2933
|
+
});
|
|
2934
|
+
continue;
|
|
2935
|
+
}
|
|
2936
|
+
const id = typeof meta.id === "string" ? meta.id : void 0;
|
|
2937
|
+
if (!id) {
|
|
2938
|
+
skipped.push({
|
|
2939
|
+
component: name,
|
|
2940
|
+
metaFile,
|
|
2941
|
+
reason: "no id (unlinked) - left as-is"
|
|
2942
|
+
});
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
const slot = typeof meta.slot === "string" ? meta.slot : void 0;
|
|
2946
|
+
if (!dryRun) {
|
|
2947
|
+
await upsertComponentEntry(
|
|
2948
|
+
id,
|
|
2949
|
+
{ defaultPath: `components/${name}`, slot },
|
|
2950
|
+
{ cwd, stage }
|
|
2951
|
+
);
|
|
2952
|
+
await fs5.rm(path5.join(folderDir, metaFile));
|
|
2953
|
+
}
|
|
2954
|
+
migrated.push({
|
|
2955
|
+
component: name,
|
|
2956
|
+
stage: stage ?? "prod",
|
|
2957
|
+
configFile: configFileForStage(stage),
|
|
2958
|
+
metaFile,
|
|
2959
|
+
id,
|
|
2960
|
+
slot
|
|
2961
|
+
});
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
if (format === "json") {
|
|
2965
|
+
outputResult(
|
|
2966
|
+
{ data: { dryRun, migrated, skipped } },
|
|
2967
|
+
format,
|
|
2968
|
+
parsed2.global.fields
|
|
2969
|
+
);
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2972
|
+
const prefix = dryRun ? "\x1B[33m[dry-run]\x1B[0m " : "";
|
|
2973
|
+
if (migrated.length === 0) {
|
|
2974
|
+
console.log(`${prefix}No legacy meta files to migrate.`);
|
|
2975
|
+
} else {
|
|
2976
|
+
console.log(
|
|
2977
|
+
`${prefix}\x1B[1mMigrated ${migrated.length} meta file(s) to config:\x1B[0m`
|
|
2978
|
+
);
|
|
2979
|
+
for (const entry of migrated) {
|
|
2980
|
+
const slotLabel = entry.slot ? `, slot ${entry.slot}` : "";
|
|
2981
|
+
const removed = dryRun ? "" : ` [removed ${entry.metaFile}]`;
|
|
2982
|
+
console.log(
|
|
2983
|
+
` ${entry.configFile.padEnd(16)} ${entry.component} (id ${entry.id}${slotLabel})${removed}`
|
|
2984
|
+
);
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
if (skipped.length > 0) {
|
|
2988
|
+
console.log("\n\x1B[2mSkipped:\x1B[0m");
|
|
2989
|
+
for (const entry of skipped) {
|
|
2990
|
+
console.log(` ${entry.component}/${entry.metaFile}: ${entry.reason}`);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
} catch (err) {
|
|
2994
|
+
outputResult(
|
|
2995
|
+
{ error: { message: err instanceof Error ? err.message : String(err) } },
|
|
2996
|
+
format
|
|
2997
|
+
);
|
|
2998
|
+
process.exit(1);
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
|
|
2496
3002
|
// src/commands/status-cmd.ts
|
|
2497
3003
|
async function statusCommand(parsed2) {
|
|
2498
3004
|
const format = detectOutputFormat(parsed2.global.output);
|
|
@@ -2876,7 +3382,8 @@ var AGENT_COMMANDS = {
|
|
|
2876
3382
|
schema: schemaCommand,
|
|
2877
3383
|
init: initCommand,
|
|
2878
3384
|
deploy: deployCommand,
|
|
2879
|
-
status: statusCommand
|
|
3385
|
+
status: statusCommand,
|
|
3386
|
+
setup: setupCommand
|
|
2880
3387
|
};
|
|
2881
3388
|
var parsed = parseArgs(process.argv);
|
|
2882
3389
|
if (parsed.command in AGENT_COMMANDS) {
|