@ollie-shop/cli 1.3.3 → 1.4.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @ollie-shop/cli@1.3.3 build /home/runner/work/ollie-shop/ollie-shop/packages/cli
2
+ > @ollie-shop/cli@1.4.0 build /home/runner/work/ollie-shop/ollie-shop/packages/cli
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/index.tsx
@@ -9,5 +9,5 @@
9
9
  CLI Target: node22
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
- ESM dist/index.js 92.64 KB
13
- ESM ⚡️ Build success in 300ms
12
+ ESM dist/index.js 93.69 KB
13
+ ESM ⚡️ Build success in 295ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @ollie-shop/cli
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - bac681e: Add a `--no-open` flag to `ollieshop start` (also honored via the `CI` env var) so the dev server can run headless without auto-opening Studio in the browser — useful when an agent spawns it and drives its own preview harness. `ollieshop start` now also runs without a TTY: it no longer crashes with "Raw mode is not supported" when backgrounded, and simply disables keyboard shortcuts in that case. Upgrade the CLI to React 19 and Ink 6.
8
+
3
9
  ## 1.3.3
4
10
 
5
11
  ### Patch Changes
package/CONTEXT.md CHANGED
@@ -78,6 +78,14 @@ The `deploy` command bundles a component directory into a zip, uploads it to the
78
78
 
79
79
  Terminal build statuses: `SUCCEEDED`, `FAILED`, `STOPPED`, `TIMED_OUT`, `FAULT`
80
80
 
81
+ ## Dev Server (agent / harness mode)
82
+
83
+ `ollieshop start` runs the local build+serve dev server (port 4000: `/manifest.json`, `/<Name>/index.js`, `/<Name>/index.css`, SSE `/esbuild`) and normally **auto-opens the admin Studio in a browser**. When you (an agent) are spawning it, pass `--no-open` so it does **not** open a browser — then drive your own preview harness against the dev server instead. The flag is also honored automatically when the `CI` env var is set.
84
+
85
+ ```bash
86
+ ollieshop start --no-open # build+serve only; no browser is opened
87
+ ```
88
+
81
89
  ## Response Format
82
90
 
83
91
  All commands return:
package/README.md CHANGED
@@ -172,6 +172,7 @@ ollieshop init --store-id <STORE_UUID> --version-id <VERSION_UUID> -o json
172
172
  | `--fields a,b,c` | | Limit output fields (comma-separated) |
173
173
  | `--data '{...}'` | `-d` | Raw JSON payload for mutations (alternative to individual flags) |
174
174
  | `--stage <name>` | `-s` | Config stage — loads `ollie.<stage>.json` instead of `ollie.json` |
175
+ | `--no-open` | | `start` only: don't auto-open Studio in the browser (also honored via the `CI` env var) |
175
176
 
176
177
  ## Response Format
177
178
 
package/dist/index.js CHANGED
@@ -107,12 +107,17 @@ function HelpCommand() {
107
107
  "{stage}",
108
108
  ".json)"
109
109
  ] })
110
+ ] }),
111
+ /* @__PURE__ */ jsxs(Box, { children: [
112
+ /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "--no-open" }) }),
113
+ /* @__PURE__ */ jsx(Text, { children: "start: don't auto-open Studio (also honored via CI env)" })
110
114
  ] })
111
115
  ] }),
112
116
  /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, children: "Examples:" }) }),
113
117
  /* @__PURE__ */ jsxs(Box, { marginLeft: 2, flexDirection: "column", children: [
114
118
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop login" }),
115
119
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop start --stage dev" }),
120
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop start --no-open" }),
116
121
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop whoami -o json" }),
117
122
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop schema store.create" }),
118
123
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: '$ ollieshop store create --name "My Store" --platform vtex --platform-store-id mystore' }),
@@ -945,6 +950,8 @@ function StartCommand({ args }) {
945
950
  const rebuildRef = useRef(null);
946
951
  const stopRef = useRef(null);
947
952
  const stage = resolveStage(parseArg(args, "--stage", "-s"));
953
+ const noOpen = args.includes("--no-open") || Boolean(process.env.CI);
954
+ const isInteractive = Boolean(process.stdin.isTTY);
948
955
  const addLog = useCallback((log) => {
949
956
  setLogs((prev) => {
950
957
  const newLog = {
@@ -1015,12 +1022,14 @@ function StartCommand({ args }) {
1015
1022
  storeId: config.storeId,
1016
1023
  versionId: config.versionId
1017
1024
  });
1018
- const studioUrl = new URL(STUDIO_BASE_URL);
1019
- studioUrl.searchParams.set("storeId", config.storeId);
1020
- if (config.versionId) {
1021
- studioUrl.searchParams.set("versionId", config.versionId);
1025
+ if (!noOpen) {
1026
+ const studioUrl = new URL(STUDIO_BASE_URL);
1027
+ studioUrl.searchParams.set("storeId", config.storeId);
1028
+ if (config.versionId) {
1029
+ studioUrl.searchParams.set("versionId", config.versionId);
1030
+ }
1031
+ open(studioUrl.toString());
1022
1032
  }
1023
- open(studioUrl.toString());
1024
1033
  } catch (error) {
1025
1034
  if (!mounted) return;
1026
1035
  setState({
@@ -1034,23 +1043,26 @@ function StartCommand({ args }) {
1034
1043
  mounted = false;
1035
1044
  stopRef.current?.();
1036
1045
  };
1037
- }, [stage, handleRequest]);
1038
- useInput((input, key) => {
1039
- if (input === "q" || input === "c" && key.ctrl) {
1040
- stopRef.current?.().then(() => exit());
1041
- }
1042
- if (input === "r" && state.status === "running") {
1043
- rebuildRef.current?.();
1044
- }
1045
- if (input === "o" && state.status === "running") {
1046
- const studioUrl = new URL(STUDIO_BASE_URL);
1047
- studioUrl.searchParams.set("storeId", state.storeId);
1048
- if (state.versionId) {
1049
- studioUrl.searchParams.set("versionId", state.versionId);
1046
+ }, [stage, handleRequest, noOpen]);
1047
+ useInput(
1048
+ (input, key) => {
1049
+ if (input === "q" || input === "c" && key.ctrl) {
1050
+ stopRef.current?.().then(() => exit());
1050
1051
  }
1051
- open(studioUrl.toString());
1052
- }
1053
- });
1052
+ if (input === "r" && state.status === "running") {
1053
+ rebuildRef.current?.();
1054
+ }
1055
+ if (input === "o" && state.status === "running") {
1056
+ const studioUrl = new URL(STUDIO_BASE_URL);
1057
+ studioUrl.searchParams.set("storeId", state.storeId);
1058
+ if (state.versionId) {
1059
+ studioUrl.searchParams.set("versionId", state.versionId);
1060
+ }
1061
+ open(studioUrl.toString());
1062
+ }
1063
+ },
1064
+ { isActive: isInteractive }
1065
+ );
1054
1066
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
1055
1067
  /* @__PURE__ */ jsx3(Header, {}),
1056
1068
  state.status === "initializing" && /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -1081,13 +1093,14 @@ function StartCommand({ args }) {
1081
1093
  port: state.port,
1082
1094
  stage,
1083
1095
  storeId: state.storeId,
1084
- versionId: state.versionId
1096
+ versionId: state.versionId,
1097
+ noOpen
1085
1098
  }
1086
1099
  ),
1087
1100
  /* @__PURE__ */ jsx3(ComponentList, { components }),
1088
1101
  /* @__PURE__ */ jsx3(BuildInfo, { buildCount, lastBuildTime }),
1089
1102
  /* @__PURE__ */ jsx3(RequestLogs, { logs }),
1090
- /* @__PURE__ */ jsx3(Footer, {})
1103
+ /* @__PURE__ */ jsx3(Footer, { interactive: isInteractive })
1091
1104
  ] })
1092
1105
  ] });
1093
1106
  }
@@ -1102,7 +1115,8 @@ function ServerInfo({
1102
1115
  port,
1103
1116
  stage,
1104
1117
  storeId,
1105
- versionId
1118
+ versionId,
1119
+ noOpen
1106
1120
  }) {
1107
1121
  const studioUrl = new URL(STUDIO_BASE_URL);
1108
1122
  studioUrl.searchParams.set("storeId", storeId);
@@ -1129,7 +1143,8 @@ function ServerInfo({
1129
1143
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
1130
1144
  /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 " }),
1131
1145
  /* @__PURE__ */ jsx3(Text3, { children: "Studio: " }),
1132
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: "magenta", children: studioUrl.toString() })
1146
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "magenta", children: studioUrl.toString() }),
1147
+ noOpen && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " (auto-open off \u2014 press o)" })
1133
1148
  ] }),
1134
1149
  /* @__PURE__ */ jsx3(Box3, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1135
1150
  "Components: http://",
@@ -1213,7 +1228,10 @@ function RequestLogs({ logs }) {
1213
1228
  ] }, log.id)) })
1214
1229
  ] });
1215
1230
  }
1216
- function Footer() {
1231
+ function Footer({ interactive = true }) {
1232
+ if (!interactive) {
1233
+ 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" }) });
1234
+ }
1217
1235
  return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
1218
1236
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Press " }),
1219
1237
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: "q" }),
@@ -1246,7 +1264,7 @@ function App({ command, args }) {
1246
1264
  }
1247
1265
  }
1248
1266
  function VersionCommand() {
1249
- const version = "1.3.3" ? "1.3.3" : "unknown";
1267
+ const version = "1.4.0" ? "1.4.0" : "unknown";
1250
1268
  return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
1251
1269
  "ollieshop v",
1252
1270
  version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ollie-shop/cli",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Ollie Shop CLI - Development tools for custom checkouts",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,18 +13,17 @@
13
13
  "archiver": "^7.0.1",
14
14
  "esbuild": "^0.24.0",
15
15
  "glob": "^11.0.0",
16
- "ink": "^5.0.1",
16
+ "ink": "^6.8.0",
17
17
  "jwt-decode": "^4.0.0",
18
18
  "open": "^10.1.0",
19
- "react": "^18.3.1",
19
+ "react": "^19.2.0",
20
20
  "zod": "^3.24.2",
21
21
  "zod-to-json-schema": "^3.24.5"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/archiver": "^6.0.3",
25
- "@types/ink": "^2.0.3",
26
25
  "@types/node": "^22.15.23",
27
- "@types/react": "^18.3.12",
26
+ "@types/react": "^19.2.0",
28
27
  "tsup": "^8.0.1",
29
28
  "typescript": "^5.7.3"
30
29
  },
@@ -139,6 +139,12 @@ export function HelpCommand() {
139
139
  </Box>
140
140
  <Text>Config stage (loads ollie.{"{stage}"}.json)</Text>
141
141
  </Box>
142
+ <Box>
143
+ <Box width={24}>
144
+ <Text color="yellow">--no-open</Text>
145
+ </Box>
146
+ <Text>start: don't auto-open Studio (also honored via CI env)</Text>
147
+ </Box>
142
148
  </Box>
143
149
 
144
150
  <Box marginTop={1}>
@@ -147,6 +153,7 @@ export function HelpCommand() {
147
153
  <Box marginLeft={2} flexDirection="column">
148
154
  <Text dimColor>$ ollieshop login</Text>
149
155
  <Text dimColor>$ ollieshop start --stage dev</Text>
156
+ <Text dimColor>$ ollieshop start --no-open</Text>
150
157
  <Text dimColor>$ ollieshop whoami -o json</Text>
151
158
  <Text dimColor>$ ollieshop schema store.create</Text>
152
159
  <Text dimColor>
@@ -59,6 +59,15 @@ export function StartCommand({ args }: StartCommandProps) {
59
59
 
60
60
  // Parse args
61
61
  const stage = resolveStage(parseArg(args, "--stage", "-s"));
62
+ // Suppress auto-opening Studio in the browser (Storybook-style). Useful when an
63
+ // agent spawns the dev server and drives its own harness instead. Also honored
64
+ // via the conventional CI env var.
65
+ const noOpen = args.includes("--no-open") || Boolean(process.env.CI);
66
+ // Keyboard shortcuts need raw mode, which Ink can only enable on a TTY stdin.
67
+ // When spawned headless (agent/CI/backgrounded), stdin isn't a TTY — guard the
68
+ // input handler so the server runs instead of crashing with "Raw mode is not
69
+ // supported on the current process.stdin".
70
+ const isInteractive = Boolean(process.stdin.isTTY);
62
71
 
63
72
  const addLog = useCallback((log: Omit<RequestLog, "id" | "timestamp">) => {
64
73
  setLogs((prev) => {
@@ -148,13 +157,15 @@ export function StartCommand({ args }: StartCommandProps) {
148
157
  versionId: config.versionId,
149
158
  });
150
159
 
151
- // Open Studio in browser
152
- const studioUrl = new URL(STUDIO_BASE_URL);
153
- studioUrl.searchParams.set("storeId", config.storeId);
154
- if (config.versionId) {
155
- studioUrl.searchParams.set("versionId", config.versionId);
160
+ // Open Studio in browser (unless suppressed)
161
+ if (!noOpen) {
162
+ const studioUrl = new URL(STUDIO_BASE_URL);
163
+ studioUrl.searchParams.set("storeId", config.storeId);
164
+ if (config.versionId) {
165
+ studioUrl.searchParams.set("versionId", config.versionId);
166
+ }
167
+ open(studioUrl.toString());
156
168
  }
157
- open(studioUrl.toString());
158
169
  } catch (error) {
159
170
  if (!mounted) return;
160
171
  setState({
@@ -170,29 +181,32 @@ export function StartCommand({ args }: StartCommandProps) {
170
181
  mounted = false;
171
182
  stopRef.current?.();
172
183
  };
173
- }, [stage, handleRequest]);
184
+ }, [stage, handleRequest, noOpen]);
174
185
 
175
- // Handle keyboard input
176
- useInput((input, key) => {
177
- if (input === "q" || (input === "c" && key.ctrl)) {
178
- stopRef.current?.().then(() => exit());
179
- }
186
+ // Handle keyboard input (only when attached to a TTY — see isInteractive above)
187
+ useInput(
188
+ (input, key) => {
189
+ if (input === "q" || (input === "c" && key.ctrl)) {
190
+ stopRef.current?.().then(() => exit());
191
+ }
180
192
 
181
- // Manual rebuild with 'r' (manifest is updated by the plugin)
182
- if (input === "r" && state.status === "running") {
183
- rebuildRef.current?.();
184
- }
193
+ // Manual rebuild with 'r' (manifest is updated by the plugin)
194
+ if (input === "r" && state.status === "running") {
195
+ rebuildRef.current?.();
196
+ }
185
197
 
186
- // Open Studio in browser with 'o'
187
- if (input === "o" && state.status === "running") {
188
- const studioUrl = new URL(STUDIO_BASE_URL);
189
- studioUrl.searchParams.set("storeId", state.storeId);
190
- if (state.versionId) {
191
- studioUrl.searchParams.set("versionId", state.versionId);
198
+ // Open Studio in browser with 'o'
199
+ if (input === "o" && state.status === "running") {
200
+ const studioUrl = new URL(STUDIO_BASE_URL);
201
+ studioUrl.searchParams.set("storeId", state.storeId);
202
+ if (state.versionId) {
203
+ studioUrl.searchParams.set("versionId", state.versionId);
204
+ }
205
+ open(studioUrl.toString());
192
206
  }
193
- open(studioUrl.toString());
194
- }
195
- });
207
+ },
208
+ { isActive: isInteractive },
209
+ );
196
210
 
197
211
  return (
198
212
  <Box flexDirection="column" gap={1}>
@@ -236,11 +250,12 @@ export function StartCommand({ args }: StartCommandProps) {
236
250
  stage={stage}
237
251
  storeId={state.storeId}
238
252
  versionId={state.versionId}
253
+ noOpen={noOpen}
239
254
  />
240
255
  <ComponentList components={components} />
241
256
  <BuildInfo buildCount={buildCount} lastBuildTime={lastBuildTime} />
242
257
  <RequestLogs logs={logs} />
243
- <Footer />
258
+ <Footer interactive={isInteractive} />
244
259
  </>
245
260
  )}
246
261
  </Box>
@@ -264,12 +279,14 @@ function ServerInfo({
264
279
  stage,
265
280
  storeId,
266
281
  versionId,
282
+ noOpen,
267
283
  }: {
268
284
  host: string;
269
285
  port: number;
270
286
  stage?: string;
271
287
  storeId: string;
272
288
  versionId?: string;
289
+ noOpen?: boolean;
273
290
  }) {
274
291
  const studioUrl = new URL(STUDIO_BASE_URL);
275
292
 
@@ -301,6 +318,7 @@ function ServerInfo({
301
318
  <Text bold color="magenta">
302
319
  {studioUrl.toString()}
303
320
  </Text>
321
+ {noOpen && <Text dimColor> (auto-open off — press o)</Text>}
304
322
  </Box>
305
323
  <Box marginLeft={2} marginTop={1}>
306
324
  <Text dimColor>
@@ -386,7 +404,15 @@ function RequestLogs({ logs }: { logs: RequestLog[] }) {
386
404
  );
387
405
  }
388
406
 
389
- function Footer() {
407
+ function Footer({ interactive = true }: { interactive?: boolean }) {
408
+ if (!interactive) {
409
+ return (
410
+ <Box marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
411
+ <Text dimColor>Headless (no TTY) — Ctrl+C to stop</Text>
412
+ </Box>
413
+ );
414
+ }
415
+
390
416
  return (
391
417
  <Box marginTop={1} borderStyle="single" borderColor="gray" paddingX={1}>
392
418
  <Text dimColor>Press </Text>