@ollie-shop/cli 1.2.2 → 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.
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ function HelpCommand() {
36
36
  /* @__PURE__ */ jsxs(Box, { marginLeft: 2, flexDirection: "column", gap: 0, children: [
37
37
  /* @__PURE__ */ jsxs(Box, { children: [
38
38
  /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "whoami" }) }),
39
- /* @__PURE__ */ jsx(Text, { children: "Show current user and organization" })
39
+ /* @__PURE__ */ jsx(Text, { children: "Show current user (and linked store/org from ollie.json)" })
40
40
  ] }),
41
41
  /* @__PURE__ */ jsxs(Box, { children: [
42
42
  /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "store create|list" }) }),
@@ -107,15 +107,21 @@ 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' }),
124
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: '$ ollieshop store create --org UUID --name "My Store" --platform vtex --platform-store-id mystore' }),
119
125
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
120
126
  "$ ollieshop store create --data",
121
127
  " ",
@@ -304,6 +310,9 @@ async function getCurrentUser() {
304
310
  if (!credentials) return null;
305
311
  try {
306
312
  const decoded = jwtDecode(credentials.accessToken);
313
+ if (decoded.exp && decoded.exp * 1e3 <= Date.now()) {
314
+ return null;
315
+ }
307
316
  return decoded.email ? { email: decoded.email } : null;
308
317
  } catch {
309
318
  return null;
@@ -439,6 +448,7 @@ function resolveStage(cliStage) {
439
448
  }
440
449
 
441
450
  // src/utils/esbuild.ts
451
+ import { watch } from "fs";
442
452
  import fs4 from "fs/promises";
443
453
  import http from "http";
444
454
  import path4 from "path";
@@ -663,16 +673,69 @@ async function createBuildContext(components, options = {}) {
663
673
  });
664
674
  return ctx;
665
675
  }
666
- async function startDevServer(ctx, options = {}) {
667
- const { port = 4e3, host = "localhost", cwd = process.cwd() } = options;
676
+ async function startDevServer(options = {}) {
677
+ const {
678
+ port = 4e3,
679
+ host = "localhost",
680
+ cwd = process.cwd(),
681
+ stage,
682
+ onRequest,
683
+ onBuildEnd
684
+ } = options;
668
685
  const servedir = path4.join(cwd, "node_modules/.ollie", "build");
669
- await ctx.watch();
686
+ const componentsDir = path4.join(cwd, "components");
670
687
  const internalPort = port + 1;
671
- await ctx.serve({
672
- port: internalPort,
673
- host,
674
- servedir,
675
- onRequest: options.onRequest
688
+ let ctx = null;
689
+ let entryNames = /* @__PURE__ */ new Set();
690
+ async function buildAndServe(components) {
691
+ entryNames = new Set(components.map((c) => c.name));
692
+ ctx = await createBuildContext(components, { cwd, stage, onBuildEnd });
693
+ await ctx.rebuild();
694
+ await ctx.watch();
695
+ await ctx.serve({ port: internalPort, host, servedir, onRequest });
696
+ }
697
+ async function recreate() {
698
+ const components = await discoverComponents({ cwd, stage });
699
+ const oldCtx = ctx;
700
+ ctx = null;
701
+ if (oldCtx) await oldCtx.dispose();
702
+ await buildAndServe(components);
703
+ }
704
+ await buildAndServe(await discoverComponents({ cwd, stage }));
705
+ async function currentComponentNames() {
706
+ try {
707
+ const entries = await glob("*/index.tsx", { cwd: componentsDir });
708
+ return new Set(entries.map((e) => path4.dirname(e)));
709
+ } catch {
710
+ return /* @__PURE__ */ new Set();
711
+ }
712
+ }
713
+ let watchTimer = null;
714
+ let recreating = false;
715
+ let pending = false;
716
+ async function maybeRecreate() {
717
+ if (recreating) {
718
+ pending = true;
719
+ return;
720
+ }
721
+ recreating = true;
722
+ try {
723
+ do {
724
+ pending = false;
725
+ const names = await currentComponentNames();
726
+ const changed = names.size !== entryNames.size || [...names].some((n) => !entryNames.has(n));
727
+ if (!changed) break;
728
+ await recreate();
729
+ } while (pending);
730
+ } catch (err) {
731
+ console.error("[DevServer] Failed to reload components:", err);
732
+ } finally {
733
+ recreating = false;
734
+ }
735
+ }
736
+ const componentsWatcher = watch(componentsDir, { recursive: true }, () => {
737
+ if (watchTimer) clearTimeout(watchTimer);
738
+ watchTimer = setTimeout(maybeRecreate, 150);
676
739
  });
677
740
  const proxyServer = http.createServer(async (req, res) => {
678
741
  const url = new URL(req.url || "/", `http://${host}:${port}`);
@@ -756,6 +819,16 @@ async function startDevServer(ctx, options = {}) {
756
819
  existingMeta = JSON.parse(content);
757
820
  } catch {
758
821
  }
822
+ if (typeof updates.id === "string" && typeof existingMeta.id === "string" && existingMeta.id !== updates.id) {
823
+ res.statusCode = 409;
824
+ res.setHeader("Content-Type", "application/json");
825
+ res.end(
826
+ JSON.stringify({
827
+ error: `id mismatch: ${componentName} is linked to ${existingMeta.id}`
828
+ })
829
+ );
830
+ return;
831
+ }
759
832
  const newMeta = { ...existingMeta, ...updates };
760
833
  await fs4.writeFile(metaPath, JSON.stringify(newMeta, null, 2));
761
834
  res.setHeader("Content-Type", "application/json");
@@ -817,9 +890,14 @@ async function startDevServer(ctx, options = {}) {
817
890
  return {
818
891
  host,
819
892
  port,
893
+ rebuild: async () => {
894
+ await ctx?.rebuild();
895
+ },
820
896
  stop: async () => {
897
+ if (watchTimer) clearTimeout(watchTimer);
898
+ componentsWatcher.close();
821
899
  proxyServer.close();
822
- await ctx.dispose();
900
+ await ctx?.dispose();
823
901
  }
824
902
  };
825
903
  }
@@ -869,9 +947,11 @@ function StartCommand({ args }) {
869
947
  const [buildCount, setBuildCount] = useState2(0);
870
948
  const [lastBuildTime, setLastBuildTime] = useState2(null);
871
949
  const logIdRef = useRef(0);
872
- const ctxRef = useRef(null);
950
+ const rebuildRef = useRef(null);
873
951
  const stopRef = useRef(null);
874
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);
875
955
  const addLog = useCallback((log) => {
876
956
  setLogs((prev) => {
877
957
  const newLog = {
@@ -919,21 +999,17 @@ function StartCommand({ args }) {
919
999
  }
920
1000
  setComponents(found);
921
1001
  setState({ status: "building" });
922
- const ctx = await createBuildContext(found, {
1002
+ const server = await startDevServer({
1003
+ port: PORT,
923
1004
  stage,
1005
+ onRequest: handleRequest,
924
1006
  onBuildEnd: (updatedComponents) => {
925
1007
  setComponents(updatedComponents);
926
1008
  setBuildCount((c) => c + 1);
927
1009
  setLastBuildTime(/* @__PURE__ */ new Date());
928
1010
  }
929
1011
  });
930
- ctxRef.current = ctx;
931
- await ctx.rebuild();
932
- if (!mounted) return;
933
- const server = await startDevServer(ctx, {
934
- port: PORT,
935
- onRequest: handleRequest
936
- });
1012
+ rebuildRef.current = server.rebuild;
937
1013
  stopRef.current = server.stop;
938
1014
  if (!mounted) {
939
1015
  await server.stop();
@@ -946,12 +1022,14 @@ function StartCommand({ args }) {
946
1022
  storeId: config.storeId,
947
1023
  versionId: config.versionId
948
1024
  });
949
- const studioUrl = new URL(STUDIO_BASE_URL);
950
- studioUrl.searchParams.set("storeId", config.storeId);
951
- if (config.versionId) {
952
- 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());
953
1032
  }
954
- open(studioUrl.toString());
955
1033
  } catch (error) {
956
1034
  if (!mounted) return;
957
1035
  setState({
@@ -965,23 +1043,26 @@ function StartCommand({ args }) {
965
1043
  mounted = false;
966
1044
  stopRef.current?.();
967
1045
  };
968
- }, [stage, handleRequest]);
969
- useInput((input, key) => {
970
- if (input === "q" || input === "c" && key.ctrl) {
971
- stopRef.current?.().then(() => exit());
972
- }
973
- if (input === "r" && state.status === "running") {
974
- ctxRef.current?.rebuild();
975
- }
976
- if (input === "o" && state.status === "running") {
977
- const studioUrl = new URL(STUDIO_BASE_URL);
978
- studioUrl.searchParams.set("storeId", state.storeId);
979
- if (state.versionId) {
980
- 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());
981
1051
  }
982
- open(studioUrl.toString());
983
- }
984
- });
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
+ );
985
1066
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", gap: 1, children: [
986
1067
  /* @__PURE__ */ jsx3(Header, {}),
987
1068
  state.status === "initializing" && /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -1012,13 +1093,14 @@ function StartCommand({ args }) {
1012
1093
  port: state.port,
1013
1094
  stage,
1014
1095
  storeId: state.storeId,
1015
- versionId: state.versionId
1096
+ versionId: state.versionId,
1097
+ noOpen
1016
1098
  }
1017
1099
  ),
1018
1100
  /* @__PURE__ */ jsx3(ComponentList, { components }),
1019
1101
  /* @__PURE__ */ jsx3(BuildInfo, { buildCount, lastBuildTime }),
1020
1102
  /* @__PURE__ */ jsx3(RequestLogs, { logs }),
1021
- /* @__PURE__ */ jsx3(Footer, {})
1103
+ /* @__PURE__ */ jsx3(Footer, { interactive: isInteractive })
1022
1104
  ] })
1023
1105
  ] });
1024
1106
  }
@@ -1033,7 +1115,8 @@ function ServerInfo({
1033
1115
  port,
1034
1116
  stage,
1035
1117
  storeId,
1036
- versionId
1118
+ versionId,
1119
+ noOpen
1037
1120
  }) {
1038
1121
  const studioUrl = new URL(STUDIO_BASE_URL);
1039
1122
  studioUrl.searchParams.set("storeId", storeId);
@@ -1060,7 +1143,8 @@ function ServerInfo({
1060
1143
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, children: [
1061
1144
  /* @__PURE__ */ jsx3(Text3, { color: "green", children: "\u2713 " }),
1062
1145
  /* @__PURE__ */ jsx3(Text3, { children: "Studio: " }),
1063
- /* @__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)" })
1064
1148
  ] }),
1065
1149
  /* @__PURE__ */ jsx3(Box3, { marginLeft: 2, marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1066
1150
  "Components: http://",
@@ -1144,7 +1228,10 @@ function RequestLogs({ logs }) {
1144
1228
  ] }, log.id)) })
1145
1229
  ] });
1146
1230
  }
1147
- 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
+ }
1148
1235
  return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
1149
1236
  /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Press " }),
1150
1237
  /* @__PURE__ */ jsx3(Text3, { bold: true, children: "q" }),
@@ -1177,7 +1264,11 @@ function App({ command, args }) {
1177
1264
  }
1178
1265
  }
1179
1266
  function VersionCommand() {
1180
- return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { children: "ollie v0.1.0" }) });
1267
+ const version = "1.4.0" ? "1.4.0" : "unknown";
1268
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
1269
+ "ollieshop v",
1270
+ version
1271
+ ] }) });
1181
1272
  }
1182
1273
  function UnknownCommand({ command }) {
1183
1274
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
@@ -1377,7 +1468,8 @@ function parseArgs(argv) {
1377
1468
  output: flags.output || flags.o || void 0,
1378
1469
  dryRun: flags["dry-run"] === true,
1379
1470
  fields: flags.fields ? String(flags.fields).split(",").map((f) => f.trim()) : void 0,
1380
- data: flags.data || flags.d || void 0
1471
+ data: flags.data || flags.d || void 0,
1472
+ stage: flags.stage || flags.s || void 0
1381
1473
  };
1382
1474
  return { command, subcommand, flags, global, positional };
1383
1475
  }
@@ -1397,13 +1489,33 @@ function getBoolFlag(flags, ...names) {
1397
1489
 
1398
1490
  // src/utils/supabase.ts
1399
1491
  import { createClient } from "@supabase/supabase-js";
1492
+ var PROD_DEFAULTS = {
1493
+ supabaseUrl: "https://aazahtmqrhjqsyqoqdkm.supabase.co",
1494
+ supabaseAnonKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM",
1495
+ builderUrl: "https://api.ollie.shop/builder/v1"
1496
+ };
1497
+ function envOrDefault(envValue, buildTimeValue, prodDefault) {
1498
+ return envValue || buildTimeValue || prodDefault;
1499
+ }
1500
+ function unwrapToOne(embed) {
1501
+ const record = Array.isArray(embed) ? embed[0] : embed;
1502
+ return record && typeof record === "object" ? record : null;
1503
+ }
1400
1504
  async function getAuthenticatedClient() {
1401
1505
  const credentials = await getCredentials();
1402
1506
  if (!credentials) {
1403
1507
  throw new Error("Not authenticated. Run `ollieshop login` first.");
1404
1508
  }
1405
- const supabaseUrl = process.env.OLLIE_SUPABASE_URL || "https://aazahtmqrhjqsyqoqdkm.supabase.co";
1406
- const supabaseAnonKey = process.env.OLLIE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM";
1509
+ const supabaseUrl = envOrDefault(
1510
+ process.env.OLLIE_SUPABASE_URL,
1511
+ "https://aazahtmqrhjqsyqoqdkm.supabase.co",
1512
+ PROD_DEFAULTS.supabaseUrl
1513
+ );
1514
+ const supabaseAnonKey = envOrDefault(
1515
+ process.env.OLLIE_SUPABASE_ANON_KEY,
1516
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM",
1517
+ PROD_DEFAULTS.supabaseAnonKey
1518
+ );
1407
1519
  const client = createClient(supabaseUrl, supabaseAnonKey, {
1408
1520
  auth: {
1409
1521
  autoRefreshToken: false,
@@ -1417,7 +1529,11 @@ async function getAuthenticatedClient() {
1417
1529
  return client;
1418
1530
  }
1419
1531
  function getBuilderUrl() {
1420
- return process.env.OLLIE_BUILDER_URL || "";
1532
+ return envOrDefault(
1533
+ process.env.OLLIE_BUILDER_URL,
1534
+ "",
1535
+ PROD_DEFAULTS.builderUrl
1536
+ );
1421
1537
  }
1422
1538
  async function getAuthToken() {
1423
1539
  const credentials = await getCredentials();
@@ -1426,17 +1542,35 @@ async function getAuthToken() {
1426
1542
  }
1427
1543
  return credentials.accessToken;
1428
1544
  }
1429
- async function getOrganizationId(client) {
1430
- const { data: org, error } = await client.from("organizations").select("id").order("created_at", { ascending: true }).limit(1).maybeSingle();
1545
+ async function getOrganizationId(client, preferredOrgId) {
1546
+ const { data: orgs, error } = await client.from("organizations").select("id, name").order("created_at", { ascending: true });
1431
1547
  if (error) {
1432
1548
  throw new Error(`Failed to get organization: ${error.message}`);
1433
1549
  }
1434
- if (!org) {
1550
+ if (!orgs || orgs.length === 0) {
1435
1551
  throw new Error(
1436
1552
  "No organization found for your account. Create one at https://admin.ollie.shop"
1437
1553
  );
1438
1554
  }
1439
- return org.id;
1555
+ if (preferredOrgId) {
1556
+ const match = orgs.find((o) => o.id === preferredOrgId);
1557
+ if (!match) {
1558
+ const list = orgs.map((o) => ` - ${o.id} ${o.name}`).join("\n");
1559
+ throw new Error(
1560
+ `Organization ${preferredOrgId} not found or you are not a member. Orgs you can access:
1561
+ ${list}`
1562
+ );
1563
+ }
1564
+ return match.id;
1565
+ }
1566
+ if (orgs.length > 1) {
1567
+ const list = orgs.map((o) => ` - ${o.id} ${o.name}`).join("\n");
1568
+ throw new Error(
1569
+ `You belong to multiple organizations. Pass --org <id> to select one:
1570
+ ${list}`
1571
+ );
1572
+ }
1573
+ return orgs[0].id;
1440
1574
  }
1441
1575
 
1442
1576
  // src/utils/validate.ts
@@ -1663,8 +1797,11 @@ var componentListSchema = z2.object({
1663
1797
  var functionCreateSchema = z2.object({
1664
1798
  versionId: z2.string().uuid().describe("Parent version UUID"),
1665
1799
  name: z2.string().min(1).describe("Function name"),
1666
- trigger: z2.string().min(1).optional().describe(
1667
- "Function trigger URL \u2014 absolute http(s) URL or relative path starting with /"
1800
+ trigger: z2.object({
1801
+ url: z2.string().min(1).describe("Trigger URL \u2014 absolute http(s) URL"),
1802
+ expression: z2.string().min(1).describe(`JSONata-like match expression (e.g. 'method in ["GET"]')`)
1803
+ }).optional().describe(
1804
+ "Function trigger \u2014 both url and expression are required together"
1668
1805
  ),
1669
1806
  active: z2.boolean().default(false).describe("Whether function is active (default: false)"),
1670
1807
  onError: z2.enum(["throw", "skip"]).optional().describe("Error handling: throw (default) or skip"),
@@ -2087,6 +2224,36 @@ async function listFunctions(client, storeId, versionId) {
2087
2224
 
2088
2225
  // src/commands/function-cmd.ts
2089
2226
  var ON_ERROR_VALUES = ["throw", "skip"];
2227
+ function parseTriggerFromFlags(urlFlag, expressionFlag) {
2228
+ if (urlFlag === void 0 && expressionFlag === void 0) return void 0;
2229
+ if (urlFlag === void 0 || expressionFlag === void 0) {
2230
+ throw new Error(
2231
+ "Both --trigger-url and --trigger-expression are required when configuring a trigger."
2232
+ );
2233
+ }
2234
+ return {
2235
+ url: validateTriggerUrl(urlFlag, "trigger-url"),
2236
+ expression: validateRequired(expressionFlag, "trigger-expression")
2237
+ };
2238
+ }
2239
+ function parseTriggerFromData(raw) {
2240
+ if (raw === void 0 || raw === null) return void 0;
2241
+ if (typeof raw !== "object" || Array.isArray(raw)) {
2242
+ throw new Error(
2243
+ 'Invalid trigger: must be an object with "url" and "expression" string properties.'
2244
+ );
2245
+ }
2246
+ const obj = raw;
2247
+ if (typeof obj.url !== "string" || typeof obj.expression !== "string") {
2248
+ throw new Error(
2249
+ 'Invalid trigger: both "url" and "expression" must be non-empty strings.'
2250
+ );
2251
+ }
2252
+ return {
2253
+ url: validateTriggerUrl(obj.url, "trigger.url"),
2254
+ expression: validateRequired(obj.expression, "trigger.expression")
2255
+ };
2256
+ }
2090
2257
  async function functionCommand(parsed2) {
2091
2258
  const sub = parsed2.subcommand;
2092
2259
  if (sub === "create") return functionCreateCommand(parsed2);
@@ -2105,13 +2272,14 @@ async function functionCreateCommand(parsed2) {
2105
2272
  input = {
2106
2273
  versionId: validateUuid(raw.versionId, "versionId"),
2107
2274
  name: validateRequired(raw.name, "name"),
2108
- trigger: raw.trigger !== void 0 && raw.trigger !== null ? validateTriggerUrl(String(raw.trigger), "trigger") : void 0,
2275
+ trigger: parseTriggerFromData(raw.trigger),
2109
2276
  active: raw.active === true,
2110
2277
  onError: raw.onError !== void 0 && raw.onError !== null ? validateEnum(String(raw.onError), ON_ERROR_VALUES, "on-error") : "throw",
2111
2278
  priority: raw.priority !== void 0 && raw.priority !== null ? validateInteger(raw.priority, "priority", { min: 0 }) : 0
2112
2279
  };
2113
2280
  } else {
2114
- const triggerFlag = getFlag(parsed2.flags, "trigger");
2281
+ const triggerUrlFlag = getFlag(parsed2.flags, "trigger-url");
2282
+ const triggerExpressionFlag = getFlag(parsed2.flags, "trigger-expression");
2115
2283
  const onErrorFlag = getFlag(parsed2.flags, "on-error");
2116
2284
  const priorityFlag = getFlag(parsed2.flags, "priority");
2117
2285
  input = {
@@ -2120,7 +2288,7 @@ async function functionCreateCommand(parsed2) {
2120
2288
  "version-id"
2121
2289
  ),
2122
2290
  name: validateRequired(getFlag(parsed2.flags, "name", "n"), "name"),
2123
- trigger: triggerFlag !== void 0 ? validateTriggerUrl(triggerFlag, "trigger") : void 0,
2291
+ trigger: parseTriggerFromFlags(triggerUrlFlag, triggerExpressionFlag),
2124
2292
  active: getBoolFlag(parsed2.flags, "active"),
2125
2293
  onError: onErrorFlag !== void 0 ? validateEnum(onErrorFlag, ON_ERROR_VALUES, "on-error") : "throw",
2126
2294
  priority: priorityFlag !== void 0 ? validateInteger(priorityFlag, "priority", { min: 0 }) : 0
@@ -2304,14 +2472,14 @@ async function statusCommand(parsed2) {
2304
2472
  }
2305
2473
 
2306
2474
  // src/core/store.ts
2307
- async function createStore(client, input) {
2475
+ async function createStore(client, input, orgId) {
2308
2476
  const parsed2 = storeCreateSchema.safeParse(input);
2309
2477
  if (!parsed2.success) {
2310
2478
  return {
2311
2479
  error: { message: parsed2.error.issues.map((i) => i.message).join("; ") }
2312
2480
  };
2313
2481
  }
2314
- const organizationId = await getOrganizationId(client);
2482
+ const organizationId = await getOrganizationId(client, orgId);
2315
2483
  const { data, error } = await client.from("stores").insert({
2316
2484
  name: parsed2.data.name,
2317
2485
  platform: parsed2.data.platform,
@@ -2325,8 +2493,33 @@ async function createStore(client, input) {
2325
2493
  }
2326
2494
  return { data: { id: data.id } };
2327
2495
  }
2328
- async function listStores(client) {
2329
- const organizationId = await getOrganizationId(client);
2496
+ async function getStoreById(client, storeId) {
2497
+ const { data, error } = await client.from("stores").select("id, name, organization_id, organizations(id, name)").eq("id", storeId).maybeSingle();
2498
+ if (error) {
2499
+ return { error: { message: error.message } };
2500
+ }
2501
+ if (!data) {
2502
+ return {
2503
+ error: {
2504
+ message: `Store ${storeId} not found or you don't have access to it.`
2505
+ }
2506
+ };
2507
+ }
2508
+ const org = unwrapToOne(
2509
+ data.organizations
2510
+ );
2511
+ const orgName = org?.name ?? null;
2512
+ return {
2513
+ data: {
2514
+ id: data.id,
2515
+ name: data.name,
2516
+ organizationId: data.organization_id,
2517
+ organizationName: orgName
2518
+ }
2519
+ };
2520
+ }
2521
+ async function listStores(client, orgId) {
2522
+ const organizationId = await getOrganizationId(client, orgId);
2330
2523
  const { data, error } = await client.from("stores").select(
2331
2524
  "id, name, platform, platform_store_id, organization_id, logo, settings, created_at"
2332
2525
  ).eq("organization_id", organizationId).order("created_at", { ascending: false });
@@ -2378,12 +2571,14 @@ async function storeCreateCommand(parsed2) {
2378
2571
  settings: getFlag(parsed2.flags, "settings")
2379
2572
  };
2380
2573
  }
2574
+ const orgFlag = getFlag(parsed2.flags, "org");
2575
+ const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
2381
2576
  if (parsed2.global.dryRun) {
2382
- outputDryRun("store.create", input, format);
2577
+ outputDryRun("store.create", { ...input, orgId }, format);
2383
2578
  return;
2384
2579
  }
2385
2580
  const client = await getAuthenticatedClient();
2386
- const result = await createStore(client, input);
2581
+ const result = await createStore(client, input, orgId);
2387
2582
  outputResult(result, format, parsed2.global.fields);
2388
2583
  if (result.error) process.exit(1);
2389
2584
  } catch (err) {
@@ -2399,8 +2594,10 @@ async function storeCreateCommand(parsed2) {
2399
2594
  async function storeListCommand(parsed2) {
2400
2595
  const format = detectOutputFormat(parsed2.global.output);
2401
2596
  try {
2597
+ const orgFlag = getFlag(parsed2.flags, "org");
2598
+ const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
2402
2599
  const client = await getAuthenticatedClient();
2403
- const result = await listStores(client);
2600
+ const result = await listStores(client, orgId);
2404
2601
  outputResult(result, format, parsed2.global.fields);
2405
2602
  if (result.error) process.exit(1);
2406
2603
  } catch (err) {
@@ -2524,21 +2721,71 @@ async function whoamiCommand(parsed2) {
2524
2721
  if (!user) {
2525
2722
  outputResult(
2526
2723
  {
2527
- error: { message: "Not logged in. Run `ollieshop login`." }
2724
+ error: {
2725
+ message: "Not logged in or session expired. Run `ollieshop login`."
2726
+ }
2727
+ },
2728
+ format
2729
+ );
2730
+ process.exit(1);
2731
+ }
2732
+ try {
2733
+ const stage = resolveStage(parsed2.global.stage);
2734
+ const config = await loadConfig({ stage });
2735
+ if (!config?.storeId) {
2736
+ outputResult(
2737
+ {
2738
+ data: {
2739
+ email: user.email,
2740
+ hint: "No ollie.json found. Run `ollieshop init` to link a store."
2741
+ }
2742
+ },
2743
+ format,
2744
+ parsed2.global.fields
2745
+ );
2746
+ return;
2747
+ }
2748
+ const client = await getAuthenticatedClient();
2749
+ const result = await getStoreById(client, config.storeId);
2750
+ if (result.error || !result.data) {
2751
+ outputResult(
2752
+ {
2753
+ data: {
2754
+ email: user.email,
2755
+ storeId: config.storeId,
2756
+ store: null,
2757
+ note: result.error?.message ?? "Store not found. Run `ollieshop login` or check the storeId in ollie.json."
2758
+ }
2759
+ },
2760
+ format,
2761
+ parsed2.global.fields
2762
+ );
2763
+ return;
2764
+ }
2765
+ const store = result.data;
2766
+ outputResult(
2767
+ {
2768
+ data: {
2769
+ email: user.email,
2770
+ orgId: store.organizationId,
2771
+ org: store.organizationName,
2772
+ storeId: store.id,
2773
+ store: store.name,
2774
+ ...config.versionId ? { versionId: config.versionId } : {}
2775
+ }
2776
+ },
2777
+ format,
2778
+ parsed2.global.fields
2779
+ );
2780
+ } catch (err) {
2781
+ outputResult(
2782
+ {
2783
+ error: { message: err instanceof Error ? err.message : String(err) }
2528
2784
  },
2529
2785
  format
2530
2786
  );
2531
2787
  process.exit(1);
2532
2788
  }
2533
- outputResult(
2534
- {
2535
- data: {
2536
- email: user.email
2537
- }
2538
- },
2539
- format,
2540
- parsed2.global.fields
2541
- );
2542
2789
  }
2543
2790
 
2544
2791
  // src/index.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ollie-shop/cli",
3
- "version": "1.2.2",
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
  },