@ollie-shop/cli 1.2.1 → 1.3.3

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" }) }),
@@ -50,6 +50,10 @@ function HelpCommand() {
50
50
  /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "component create|list" }) }),
51
51
  /* @__PURE__ */ jsx(Text, { children: "Create or list components" })
52
52
  ] }),
53
+ /* @__PURE__ */ jsxs(Box, { children: [
54
+ /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "function create|list" }) }),
55
+ /* @__PURE__ */ jsx(Text, { children: "Create or list functions" })
56
+ ] }),
53
57
  /* @__PURE__ */ jsxs(Box, { children: [
54
58
  /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { color: "green", children: "deploy" }) }),
55
59
  /* @__PURE__ */ jsx(Text, { children: "Bundle and upload a component/function build" })
@@ -112,6 +116,7 @@ function HelpCommand() {
112
116
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop whoami -o json" }),
113
117
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop schema store.create" }),
114
118
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: '$ ollieshop store create --name "My Store" --platform vtex --platform-store-id mystore' }),
119
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: '$ ollieshop store create --org UUID --name "My Store" --platform vtex --platform-store-id mystore' }),
115
120
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
116
121
  "$ ollieshop store create --data",
117
122
  " ",
@@ -121,6 +126,7 @@ function HelpCommand() {
121
126
  ] }),
122
127
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop version create --store-id UUID --name v1 --active" }),
123
128
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop init --store-id UUID --version-id UUID" }),
129
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop function create --version-id UUID --name myHook" }),
124
130
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop deploy --component-id UUID --name FreeShippingBar --wait" }),
125
131
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop status --build-id BUILD_ID --wait -o json" })
126
132
  ] })
@@ -299,6 +305,9 @@ async function getCurrentUser() {
299
305
  if (!credentials) return null;
300
306
  try {
301
307
  const decoded = jwtDecode(credentials.accessToken);
308
+ if (decoded.exp && decoded.exp * 1e3 <= Date.now()) {
309
+ return null;
310
+ }
302
311
  return decoded.email ? { email: decoded.email } : null;
303
312
  } catch {
304
313
  return null;
@@ -434,6 +443,7 @@ function resolveStage(cliStage) {
434
443
  }
435
444
 
436
445
  // src/utils/esbuild.ts
446
+ import { watch } from "fs";
437
447
  import fs4 from "fs/promises";
438
448
  import http from "http";
439
449
  import path4 from "path";
@@ -658,16 +668,69 @@ async function createBuildContext(components, options = {}) {
658
668
  });
659
669
  return ctx;
660
670
  }
661
- async function startDevServer(ctx, options = {}) {
662
- const { port = 4e3, host = "localhost", cwd = process.cwd() } = options;
671
+ async function startDevServer(options = {}) {
672
+ const {
673
+ port = 4e3,
674
+ host = "localhost",
675
+ cwd = process.cwd(),
676
+ stage,
677
+ onRequest,
678
+ onBuildEnd
679
+ } = options;
663
680
  const servedir = path4.join(cwd, "node_modules/.ollie", "build");
664
- await ctx.watch();
681
+ const componentsDir = path4.join(cwd, "components");
665
682
  const internalPort = port + 1;
666
- await ctx.serve({
667
- port: internalPort,
668
- host,
669
- servedir,
670
- onRequest: options.onRequest
683
+ let ctx = null;
684
+ let entryNames = /* @__PURE__ */ new Set();
685
+ async function buildAndServe(components) {
686
+ entryNames = new Set(components.map((c) => c.name));
687
+ ctx = await createBuildContext(components, { cwd, stage, onBuildEnd });
688
+ await ctx.rebuild();
689
+ await ctx.watch();
690
+ await ctx.serve({ port: internalPort, host, servedir, onRequest });
691
+ }
692
+ async function recreate() {
693
+ const components = await discoverComponents({ cwd, stage });
694
+ const oldCtx = ctx;
695
+ ctx = null;
696
+ if (oldCtx) await oldCtx.dispose();
697
+ await buildAndServe(components);
698
+ }
699
+ await buildAndServe(await discoverComponents({ cwd, stage }));
700
+ async function currentComponentNames() {
701
+ try {
702
+ const entries = await glob("*/index.tsx", { cwd: componentsDir });
703
+ return new Set(entries.map((e) => path4.dirname(e)));
704
+ } catch {
705
+ return /* @__PURE__ */ new Set();
706
+ }
707
+ }
708
+ let watchTimer = null;
709
+ let recreating = false;
710
+ let pending = false;
711
+ async function maybeRecreate() {
712
+ if (recreating) {
713
+ pending = true;
714
+ return;
715
+ }
716
+ recreating = true;
717
+ try {
718
+ do {
719
+ pending = false;
720
+ const names = await currentComponentNames();
721
+ const changed = names.size !== entryNames.size || [...names].some((n) => !entryNames.has(n));
722
+ if (!changed) break;
723
+ await recreate();
724
+ } while (pending);
725
+ } catch (err) {
726
+ console.error("[DevServer] Failed to reload components:", err);
727
+ } finally {
728
+ recreating = false;
729
+ }
730
+ }
731
+ const componentsWatcher = watch(componentsDir, { recursive: true }, () => {
732
+ if (watchTimer) clearTimeout(watchTimer);
733
+ watchTimer = setTimeout(maybeRecreate, 150);
671
734
  });
672
735
  const proxyServer = http.createServer(async (req, res) => {
673
736
  const url = new URL(req.url || "/", `http://${host}:${port}`);
@@ -751,6 +814,16 @@ async function startDevServer(ctx, options = {}) {
751
814
  existingMeta = JSON.parse(content);
752
815
  } catch {
753
816
  }
817
+ if (typeof updates.id === "string" && typeof existingMeta.id === "string" && existingMeta.id !== updates.id) {
818
+ res.statusCode = 409;
819
+ res.setHeader("Content-Type", "application/json");
820
+ res.end(
821
+ JSON.stringify({
822
+ error: `id mismatch: ${componentName} is linked to ${existingMeta.id}`
823
+ })
824
+ );
825
+ return;
826
+ }
754
827
  const newMeta = { ...existingMeta, ...updates };
755
828
  await fs4.writeFile(metaPath, JSON.stringify(newMeta, null, 2));
756
829
  res.setHeader("Content-Type", "application/json");
@@ -812,9 +885,14 @@ async function startDevServer(ctx, options = {}) {
812
885
  return {
813
886
  host,
814
887
  port,
888
+ rebuild: async () => {
889
+ await ctx?.rebuild();
890
+ },
815
891
  stop: async () => {
892
+ if (watchTimer) clearTimeout(watchTimer);
893
+ componentsWatcher.close();
816
894
  proxyServer.close();
817
- await ctx.dispose();
895
+ await ctx?.dispose();
818
896
  }
819
897
  };
820
898
  }
@@ -864,7 +942,7 @@ function StartCommand({ args }) {
864
942
  const [buildCount, setBuildCount] = useState2(0);
865
943
  const [lastBuildTime, setLastBuildTime] = useState2(null);
866
944
  const logIdRef = useRef(0);
867
- const ctxRef = useRef(null);
945
+ const rebuildRef = useRef(null);
868
946
  const stopRef = useRef(null);
869
947
  const stage = resolveStage(parseArg(args, "--stage", "-s"));
870
948
  const addLog = useCallback((log) => {
@@ -914,21 +992,17 @@ function StartCommand({ args }) {
914
992
  }
915
993
  setComponents(found);
916
994
  setState({ status: "building" });
917
- const ctx = await createBuildContext(found, {
995
+ const server = await startDevServer({
996
+ port: PORT,
918
997
  stage,
998
+ onRequest: handleRequest,
919
999
  onBuildEnd: (updatedComponents) => {
920
1000
  setComponents(updatedComponents);
921
1001
  setBuildCount((c) => c + 1);
922
1002
  setLastBuildTime(/* @__PURE__ */ new Date());
923
1003
  }
924
1004
  });
925
- ctxRef.current = ctx;
926
- await ctx.rebuild();
927
- if (!mounted) return;
928
- const server = await startDevServer(ctx, {
929
- port: PORT,
930
- onRequest: handleRequest
931
- });
1005
+ rebuildRef.current = server.rebuild;
932
1006
  stopRef.current = server.stop;
933
1007
  if (!mounted) {
934
1008
  await server.stop();
@@ -966,7 +1040,7 @@ function StartCommand({ args }) {
966
1040
  stopRef.current?.().then(() => exit());
967
1041
  }
968
1042
  if (input === "r" && state.status === "running") {
969
- ctxRef.current?.rebuild();
1043
+ rebuildRef.current?.();
970
1044
  }
971
1045
  if (input === "o" && state.status === "running") {
972
1046
  const studioUrl = new URL(STUDIO_BASE_URL);
@@ -1172,7 +1246,11 @@ function App({ command, args }) {
1172
1246
  }
1173
1247
  }
1174
1248
  function VersionCommand() {
1175
- return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(Text4, { children: "ollie v0.1.0" }) });
1249
+ const version = "1.3.3" ? "1.3.3" : "unknown";
1250
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { children: [
1251
+ "ollieshop v",
1252
+ version
1253
+ ] }) });
1176
1254
  }
1177
1255
  function UnknownCommand({ command }) {
1178
1256
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
@@ -1372,7 +1450,8 @@ function parseArgs(argv) {
1372
1450
  output: flags.output || flags.o || void 0,
1373
1451
  dryRun: flags["dry-run"] === true,
1374
1452
  fields: flags.fields ? String(flags.fields).split(",").map((f) => f.trim()) : void 0,
1375
- data: flags.data || flags.d || void 0
1453
+ data: flags.data || flags.d || void 0,
1454
+ stage: flags.stage || flags.s || void 0
1376
1455
  };
1377
1456
  return { command, subcommand, flags, global, positional };
1378
1457
  }
@@ -1392,13 +1471,33 @@ function getBoolFlag(flags, ...names) {
1392
1471
 
1393
1472
  // src/utils/supabase.ts
1394
1473
  import { createClient } from "@supabase/supabase-js";
1474
+ var PROD_DEFAULTS = {
1475
+ supabaseUrl: "https://aazahtmqrhjqsyqoqdkm.supabase.co",
1476
+ supabaseAnonKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM",
1477
+ builderUrl: "https://api.ollie.shop/builder/v1"
1478
+ };
1479
+ function envOrDefault(envValue, buildTimeValue, prodDefault) {
1480
+ return envValue || buildTimeValue || prodDefault;
1481
+ }
1482
+ function unwrapToOne(embed) {
1483
+ const record = Array.isArray(embed) ? embed[0] : embed;
1484
+ return record && typeof record === "object" ? record : null;
1485
+ }
1395
1486
  async function getAuthenticatedClient() {
1396
1487
  const credentials = await getCredentials();
1397
1488
  if (!credentials) {
1398
1489
  throw new Error("Not authenticated. Run `ollieshop login` first.");
1399
1490
  }
1400
- const supabaseUrl = process.env.OLLIE_SUPABASE_URL || "https://aazahtmqrhjqsyqoqdkm.supabase.co";
1401
- const supabaseAnonKey = process.env.OLLIE_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM";
1491
+ const supabaseUrl = envOrDefault(
1492
+ process.env.OLLIE_SUPABASE_URL,
1493
+ "https://aazahtmqrhjqsyqoqdkm.supabase.co",
1494
+ PROD_DEFAULTS.supabaseUrl
1495
+ );
1496
+ const supabaseAnonKey = envOrDefault(
1497
+ process.env.OLLIE_SUPABASE_ANON_KEY,
1498
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImFhemFodG1xcmhqcXN5cW9xZGttIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDg2MzEwOTcsImV4cCI6MjA2NDIwNzA5N30.VuAbAyjDe0HcL09SZtQ-UmP1o7Z6qwGuOtvfFhnyAcM",
1499
+ PROD_DEFAULTS.supabaseAnonKey
1500
+ );
1402
1501
  const client = createClient(supabaseUrl, supabaseAnonKey, {
1403
1502
  auth: {
1404
1503
  autoRefreshToken: false,
@@ -1412,7 +1511,11 @@ async function getAuthenticatedClient() {
1412
1511
  return client;
1413
1512
  }
1414
1513
  function getBuilderUrl() {
1415
- return process.env.OLLIE_BUILDER_URL || "";
1514
+ return envOrDefault(
1515
+ process.env.OLLIE_BUILDER_URL,
1516
+ "",
1517
+ PROD_DEFAULTS.builderUrl
1518
+ );
1416
1519
  }
1417
1520
  async function getAuthToken() {
1418
1521
  const credentials = await getCredentials();
@@ -1421,17 +1524,35 @@ async function getAuthToken() {
1421
1524
  }
1422
1525
  return credentials.accessToken;
1423
1526
  }
1424
- async function getOrganizationId(client) {
1425
- const { data: org, error } = await client.from("organizations").select("id").order("created_at", { ascending: true }).limit(1).maybeSingle();
1527
+ async function getOrganizationId(client, preferredOrgId) {
1528
+ const { data: orgs, error } = await client.from("organizations").select("id, name").order("created_at", { ascending: true });
1426
1529
  if (error) {
1427
1530
  throw new Error(`Failed to get organization: ${error.message}`);
1428
1531
  }
1429
- if (!org) {
1532
+ if (!orgs || orgs.length === 0) {
1430
1533
  throw new Error(
1431
1534
  "No organization found for your account. Create one at https://admin.ollie.shop"
1432
1535
  );
1433
1536
  }
1434
- return org.id;
1537
+ if (preferredOrgId) {
1538
+ const match = orgs.find((o) => o.id === preferredOrgId);
1539
+ if (!match) {
1540
+ const list = orgs.map((o) => ` - ${o.id} ${o.name}`).join("\n");
1541
+ throw new Error(
1542
+ `Organization ${preferredOrgId} not found or you are not a member. Orgs you can access:
1543
+ ${list}`
1544
+ );
1545
+ }
1546
+ return match.id;
1547
+ }
1548
+ if (orgs.length > 1) {
1549
+ const list = orgs.map((o) => ` - ${o.id} ${o.name}`).join("\n");
1550
+ throw new Error(
1551
+ `You belong to multiple organizations. Pass --org <id> to select one:
1552
+ ${list}`
1553
+ );
1554
+ }
1555
+ return orgs[0].id;
1435
1556
  }
1436
1557
 
1437
1558
  // src/utils/validate.ts
@@ -1467,6 +1588,39 @@ function validateRequired(value, name) {
1467
1588
  }
1468
1589
  return rejectControlChars(value.trim(), name);
1469
1590
  }
1591
+ function validateInteger(value, name, opts) {
1592
+ const n = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
1593
+ if (!Number.isFinite(n) || !Number.isInteger(n)) {
1594
+ throw new Error(`Invalid ${name}: "${String(value)}". Must be an integer.`);
1595
+ }
1596
+ if (opts?.min !== void 0 && n < opts.min) {
1597
+ throw new Error(`Invalid ${name}: ${n}. Must be >= ${opts.min}.`);
1598
+ }
1599
+ return n;
1600
+ }
1601
+ function validateTriggerUrl(value, name) {
1602
+ rejectControlChars(value, name);
1603
+ const trimmed = value.trim();
1604
+ if (trimmed === "") {
1605
+ throw new Error(
1606
+ `Invalid ${name}: must be a non-empty absolute http(s) URL or a relative path starting with "/".`
1607
+ );
1608
+ }
1609
+ if (trimmed.startsWith("/")) {
1610
+ return trimmed;
1611
+ }
1612
+ try {
1613
+ const parsed2 = new URL(trimmed);
1614
+ if (parsed2.protocol !== "http:" && parsed2.protocol !== "https:") {
1615
+ throw new Error("unsupported protocol");
1616
+ }
1617
+ return trimmed;
1618
+ } catch {
1619
+ throw new Error(
1620
+ `Invalid ${name}: "${value}". Must be an absolute http(s) URL or a relative path starting with "/".`
1621
+ );
1622
+ }
1623
+ }
1470
1624
 
1471
1625
  // src/commands/business-rule-cmd.ts
1472
1626
  async function businessRuleCommand(parsed2) {
@@ -1625,8 +1779,15 @@ var componentListSchema = z2.object({
1625
1779
  var functionCreateSchema = z2.object({
1626
1780
  versionId: z2.string().uuid().describe("Parent version UUID"),
1627
1781
  name: z2.string().min(1).describe("Function name"),
1628
- trigger: z2.string().min(1).describe("Function trigger (e.g. beforePayment, afterShipping)"),
1629
- active: z2.boolean().default(true).describe("Whether function is active")
1782
+ trigger: z2.object({
1783
+ url: z2.string().min(1).describe("Trigger URL \u2014 absolute http(s) URL"),
1784
+ expression: z2.string().min(1).describe(`JSONata-like match expression (e.g. 'method in ["GET"]')`)
1785
+ }).optional().describe(
1786
+ "Function trigger \u2014 both url and expression are required together"
1787
+ ),
1788
+ active: z2.boolean().default(false).describe("Whether function is active (default: false)"),
1789
+ onError: z2.enum(["throw", "skip"]).optional().describe("Error handling: throw (default) or skip"),
1790
+ priority: z2.number().int().min(0).optional().describe("Execution order priority (default: 0)")
1630
1791
  });
1631
1792
  var functionListSchema = z2.object({
1632
1793
  versionId: z2.string().uuid().describe("Version UUID to list functions for")
@@ -2005,6 +2166,168 @@ async function deployCommand(parsed2) {
2005
2166
  }
2006
2167
  }
2007
2168
 
2169
+ // src/core/function.ts
2170
+ async function createFunction(client, input) {
2171
+ const parsed2 = functionCreateSchema.safeParse(input);
2172
+ if (!parsed2.success) {
2173
+ return {
2174
+ success: false,
2175
+ error: {
2176
+ message: parsed2.error.issues.map((i) => i.message).join("; ")
2177
+ }
2178
+ };
2179
+ }
2180
+ const { data, error } = await client.from("functions").insert({
2181
+ name: parsed2.data.name,
2182
+ active: parsed2.data.active,
2183
+ version_id: parsed2.data.versionId,
2184
+ trigger: parsed2.data.trigger ?? null,
2185
+ on_error: parsed2.data.onError ?? "throw",
2186
+ priority: parsed2.data.priority ?? 0
2187
+ }).select("id").single();
2188
+ if (error) {
2189
+ return { success: false, error: { message: error.message } };
2190
+ }
2191
+ return { success: true, data: { id: data.id } };
2192
+ }
2193
+ async function listFunctions(client, storeId, versionId) {
2194
+ let query = client.from("functions").select(
2195
+ "id, name, active, urn, on_error, priority, trigger, invocation, version_id, created_at, versions!inner(id, name)"
2196
+ ).eq("versions.store_id", storeId);
2197
+ if (versionId) {
2198
+ query = query.eq("versions.id", versionId);
2199
+ }
2200
+ const { data, error } = await query;
2201
+ if (error) {
2202
+ return { success: false, error: { message: error.message } };
2203
+ }
2204
+ return { success: true, data: data ?? [] };
2205
+ }
2206
+
2207
+ // src/commands/function-cmd.ts
2208
+ var ON_ERROR_VALUES = ["throw", "skip"];
2209
+ function parseTriggerFromFlags(urlFlag, expressionFlag) {
2210
+ if (urlFlag === void 0 && expressionFlag === void 0) return void 0;
2211
+ if (urlFlag === void 0 || expressionFlag === void 0) {
2212
+ throw new Error(
2213
+ "Both --trigger-url and --trigger-expression are required when configuring a trigger."
2214
+ );
2215
+ }
2216
+ return {
2217
+ url: validateTriggerUrl(urlFlag, "trigger-url"),
2218
+ expression: validateRequired(expressionFlag, "trigger-expression")
2219
+ };
2220
+ }
2221
+ function parseTriggerFromData(raw) {
2222
+ if (raw === void 0 || raw === null) return void 0;
2223
+ if (typeof raw !== "object" || Array.isArray(raw)) {
2224
+ throw new Error(
2225
+ 'Invalid trigger: must be an object with "url" and "expression" string properties.'
2226
+ );
2227
+ }
2228
+ const obj = raw;
2229
+ if (typeof obj.url !== "string" || typeof obj.expression !== "string") {
2230
+ throw new Error(
2231
+ 'Invalid trigger: both "url" and "expression" must be non-empty strings.'
2232
+ );
2233
+ }
2234
+ return {
2235
+ url: validateTriggerUrl(obj.url, "trigger.url"),
2236
+ expression: validateRequired(obj.expression, "trigger.expression")
2237
+ };
2238
+ }
2239
+ async function functionCommand(parsed2) {
2240
+ const sub = parsed2.subcommand;
2241
+ if (sub === "create") return functionCreateCommand(parsed2);
2242
+ if (sub === "list" || sub === "ls") return functionListCommand(parsed2);
2243
+ console.error(
2244
+ `Unknown function subcommand: ${sub}. Use: function create | function list`
2245
+ );
2246
+ process.exit(1);
2247
+ }
2248
+ async function functionCreateCommand(parsed2) {
2249
+ const format = detectOutputFormat(parsed2.global.output);
2250
+ try {
2251
+ let input;
2252
+ if (parsed2.global.data) {
2253
+ const raw = JSON.parse(parsed2.global.data);
2254
+ input = {
2255
+ versionId: validateUuid(raw.versionId, "versionId"),
2256
+ name: validateRequired(raw.name, "name"),
2257
+ trigger: parseTriggerFromData(raw.trigger),
2258
+ active: raw.active === true,
2259
+ onError: raw.onError !== void 0 && raw.onError !== null ? validateEnum(String(raw.onError), ON_ERROR_VALUES, "on-error") : "throw",
2260
+ priority: raw.priority !== void 0 && raw.priority !== null ? validateInteger(raw.priority, "priority", { min: 0 }) : 0
2261
+ };
2262
+ } else {
2263
+ const triggerUrlFlag = getFlag(parsed2.flags, "trigger-url");
2264
+ const triggerExpressionFlag = getFlag(parsed2.flags, "trigger-expression");
2265
+ const onErrorFlag = getFlag(parsed2.flags, "on-error");
2266
+ const priorityFlag = getFlag(parsed2.flags, "priority");
2267
+ input = {
2268
+ versionId: validateUuid(
2269
+ validateRequired(getFlag(parsed2.flags, "version-id"), "version-id"),
2270
+ "version-id"
2271
+ ),
2272
+ name: validateRequired(getFlag(parsed2.flags, "name", "n"), "name"),
2273
+ trigger: parseTriggerFromFlags(triggerUrlFlag, triggerExpressionFlag),
2274
+ active: getBoolFlag(parsed2.flags, "active"),
2275
+ onError: onErrorFlag !== void 0 ? validateEnum(onErrorFlag, ON_ERROR_VALUES, "on-error") : "throw",
2276
+ priority: priorityFlag !== void 0 ? validateInteger(priorityFlag, "priority", { min: 0 }) : 0
2277
+ };
2278
+ }
2279
+ const validated = functionCreateSchema.safeParse(input);
2280
+ if (!validated.success) {
2281
+ throw new Error(validated.error.issues.map((i) => i.message).join("; "));
2282
+ }
2283
+ if (parsed2.global.dryRun) {
2284
+ outputDryRun(
2285
+ "function.create",
2286
+ validated.data,
2287
+ format
2288
+ );
2289
+ return;
2290
+ }
2291
+ const client = await getAuthenticatedClient();
2292
+ const result = await createFunction(client, validated.data);
2293
+ outputResult(result, format, parsed2.global.fields);
2294
+ if (!result.success) process.exit(1);
2295
+ } catch (err) {
2296
+ outputResult(
2297
+ {
2298
+ success: false,
2299
+ error: { message: err instanceof Error ? err.message : String(err) }
2300
+ },
2301
+ format
2302
+ );
2303
+ process.exit(1);
2304
+ }
2305
+ }
2306
+ async function functionListCommand(parsed2) {
2307
+ const format = detectOutputFormat(parsed2.global.output);
2308
+ try {
2309
+ const storeId = validateUuid(
2310
+ validateRequired(getFlag(parsed2.flags, "store-id"), "store-id"),
2311
+ "store-id"
2312
+ );
2313
+ const versionId = getFlag(parsed2.flags, "version-id");
2314
+ if (versionId) validateUuid(versionId, "version-id");
2315
+ const client = await getAuthenticatedClient();
2316
+ const result = await listFunctions(client, storeId, versionId);
2317
+ outputResult(result, format, parsed2.global.fields);
2318
+ if (!result.success) process.exit(1);
2319
+ } catch (err) {
2320
+ outputResult(
2321
+ {
2322
+ success: false,
2323
+ error: { message: err instanceof Error ? err.message : String(err) }
2324
+ },
2325
+ format
2326
+ );
2327
+ process.exit(1);
2328
+ }
2329
+ }
2330
+
2008
2331
  // src/commands/init-cmd.ts
2009
2332
  async function initCommand(parsed2) {
2010
2333
  const format = detectOutputFormat(parsed2.global.output);
@@ -2131,14 +2454,14 @@ async function statusCommand(parsed2) {
2131
2454
  }
2132
2455
 
2133
2456
  // src/core/store.ts
2134
- async function createStore(client, input) {
2457
+ async function createStore(client, input, orgId) {
2135
2458
  const parsed2 = storeCreateSchema.safeParse(input);
2136
2459
  if (!parsed2.success) {
2137
2460
  return {
2138
2461
  error: { message: parsed2.error.issues.map((i) => i.message).join("; ") }
2139
2462
  };
2140
2463
  }
2141
- const organizationId = await getOrganizationId(client);
2464
+ const organizationId = await getOrganizationId(client, orgId);
2142
2465
  const { data, error } = await client.from("stores").insert({
2143
2466
  name: parsed2.data.name,
2144
2467
  platform: parsed2.data.platform,
@@ -2152,8 +2475,33 @@ async function createStore(client, input) {
2152
2475
  }
2153
2476
  return { data: { id: data.id } };
2154
2477
  }
2155
- async function listStores(client) {
2156
- const organizationId = await getOrganizationId(client);
2478
+ async function getStoreById(client, storeId) {
2479
+ const { data, error } = await client.from("stores").select("id, name, organization_id, organizations(id, name)").eq("id", storeId).maybeSingle();
2480
+ if (error) {
2481
+ return { error: { message: error.message } };
2482
+ }
2483
+ if (!data) {
2484
+ return {
2485
+ error: {
2486
+ message: `Store ${storeId} not found or you don't have access to it.`
2487
+ }
2488
+ };
2489
+ }
2490
+ const org = unwrapToOne(
2491
+ data.organizations
2492
+ );
2493
+ const orgName = org?.name ?? null;
2494
+ return {
2495
+ data: {
2496
+ id: data.id,
2497
+ name: data.name,
2498
+ organizationId: data.organization_id,
2499
+ organizationName: orgName
2500
+ }
2501
+ };
2502
+ }
2503
+ async function listStores(client, orgId) {
2504
+ const organizationId = await getOrganizationId(client, orgId);
2157
2505
  const { data, error } = await client.from("stores").select(
2158
2506
  "id, name, platform, platform_store_id, organization_id, logo, settings, created_at"
2159
2507
  ).eq("organization_id", organizationId).order("created_at", { ascending: false });
@@ -2205,12 +2553,14 @@ async function storeCreateCommand(parsed2) {
2205
2553
  settings: getFlag(parsed2.flags, "settings")
2206
2554
  };
2207
2555
  }
2556
+ const orgFlag = getFlag(parsed2.flags, "org");
2557
+ const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
2208
2558
  if (parsed2.global.dryRun) {
2209
- outputDryRun("store.create", input, format);
2559
+ outputDryRun("store.create", { ...input, orgId }, format);
2210
2560
  return;
2211
2561
  }
2212
2562
  const client = await getAuthenticatedClient();
2213
- const result = await createStore(client, input);
2563
+ const result = await createStore(client, input, orgId);
2214
2564
  outputResult(result, format, parsed2.global.fields);
2215
2565
  if (result.error) process.exit(1);
2216
2566
  } catch (err) {
@@ -2226,8 +2576,10 @@ async function storeCreateCommand(parsed2) {
2226
2576
  async function storeListCommand(parsed2) {
2227
2577
  const format = detectOutputFormat(parsed2.global.output);
2228
2578
  try {
2579
+ const orgFlag = getFlag(parsed2.flags, "org");
2580
+ const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
2229
2581
  const client = await getAuthenticatedClient();
2230
- const result = await listStores(client);
2582
+ const result = await listStores(client, orgId);
2231
2583
  outputResult(result, format, parsed2.global.fields);
2232
2584
  if (result.error) process.exit(1);
2233
2585
  } catch (err) {
@@ -2351,21 +2703,71 @@ async function whoamiCommand(parsed2) {
2351
2703
  if (!user) {
2352
2704
  outputResult(
2353
2705
  {
2354
- error: { message: "Not logged in. Run `ollieshop login`." }
2706
+ error: {
2707
+ message: "Not logged in or session expired. Run `ollieshop login`."
2708
+ }
2709
+ },
2710
+ format
2711
+ );
2712
+ process.exit(1);
2713
+ }
2714
+ try {
2715
+ const stage = resolveStage(parsed2.global.stage);
2716
+ const config = await loadConfig({ stage });
2717
+ if (!config?.storeId) {
2718
+ outputResult(
2719
+ {
2720
+ data: {
2721
+ email: user.email,
2722
+ hint: "No ollie.json found. Run `ollieshop init` to link a store."
2723
+ }
2724
+ },
2725
+ format,
2726
+ parsed2.global.fields
2727
+ );
2728
+ return;
2729
+ }
2730
+ const client = await getAuthenticatedClient();
2731
+ const result = await getStoreById(client, config.storeId);
2732
+ if (result.error || !result.data) {
2733
+ outputResult(
2734
+ {
2735
+ data: {
2736
+ email: user.email,
2737
+ storeId: config.storeId,
2738
+ store: null,
2739
+ note: result.error?.message ?? "Store not found. Run `ollieshop login` or check the storeId in ollie.json."
2740
+ }
2741
+ },
2742
+ format,
2743
+ parsed2.global.fields
2744
+ );
2745
+ return;
2746
+ }
2747
+ const store = result.data;
2748
+ outputResult(
2749
+ {
2750
+ data: {
2751
+ email: user.email,
2752
+ orgId: store.organizationId,
2753
+ org: store.organizationName,
2754
+ storeId: store.id,
2755
+ store: store.name,
2756
+ ...config.versionId ? { versionId: config.versionId } : {}
2757
+ }
2758
+ },
2759
+ format,
2760
+ parsed2.global.fields
2761
+ );
2762
+ } catch (err) {
2763
+ outputResult(
2764
+ {
2765
+ error: { message: err instanceof Error ? err.message : String(err) }
2355
2766
  },
2356
2767
  format
2357
2768
  );
2358
2769
  process.exit(1);
2359
2770
  }
2360
- outputResult(
2361
- {
2362
- data: {
2363
- email: user.email
2364
- }
2365
- },
2366
- format,
2367
- parsed2.global.fields
2368
- );
2369
2771
  }
2370
2772
 
2371
2773
  // src/index.tsx
@@ -2376,6 +2778,7 @@ var AGENT_COMMANDS = {
2376
2778
  version: versionCommand,
2377
2779
  component: componentCommand,
2378
2780
  "business-rule": businessRuleCommand,
2781
+ function: functionCommand,
2379
2782
  schema: schemaCommand,
2380
2783
  init: initCommand,
2381
2784
  deploy: deployCommand,