@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/.env.example +9 -5
- package/.turbo/turbo-build.log +3 -3
- package/CHANGELOG.md +61 -0
- package/CONTEXT.md +11 -6
- package/README.md +20 -10
- package/dist/index.js +451 -48
- package/package.json +1 -1
- package/src/cli.tsx +8 -1
- package/src/commands/function-cmd.ts +178 -0
- package/src/commands/help.tsx +14 -1
- package/src/commands/start.tsx +8 -18
- package/src/commands/store-cmd.ts +14 -4
- package/src/commands/whoami.ts +70 -9
- package/src/core/function.ts +92 -0
- package/src/core/schema.ts +25 -4
- package/src/core/store.ts +50 -3
- package/src/index.tsx +2 -0
- package/src/utils/auth.ts +4 -0
- package/src/utils/esbuild.ts +99 -15
- package/src/utils/parse-args.ts +2 -0
- package/src/utils/supabase.ts +66 -11
- package/src/utils/validate.ts +44 -0
- package/tsup.config.ts +7 -0
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
|
|
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(
|
|
662
|
-
const {
|
|
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
|
-
|
|
681
|
+
const componentsDir = path4.join(cwd, "components");
|
|
665
682
|
const internalPort = port + 1;
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1401
|
-
|
|
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
|
|
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:
|
|
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 (!
|
|
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
|
-
|
|
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.
|
|
1629
|
-
|
|
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
|
|
2156
|
-
const
|
|
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: {
|
|
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,
|