@ollie-shop/cli 1.2.2 → 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 +51 -0
- package/CONTEXT.md +3 -3
- package/README.md +6 -10
- package/dist/index.js +280 -51
- package/package.json +1 -1
- package/src/cli.tsx +8 -1
- package/src/commands/function-cmd.ts +42 -10
- package/src/commands/help.tsx +5 -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 +1 -1
- package/src/core/schema.ts +8 -3
- package/src/core/store.ts +50 -3
- 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/tsup.config.ts +7 -0
package/.env.example
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# None of these are required in prod — the published CLI bakes in the prod
|
|
2
|
+
# Supabase URL/anon key and Builder URL. Set these only to point the CLI at a
|
|
3
|
+
# non-prod environment (local Supabase, staging Builder, etc.).
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
|
|
5
|
+
# Local dev preset (uncomment to target localhost Supabase):
|
|
6
|
+
# OLLIE_SUPABASE_URL=http://127.0.0.1:54321
|
|
7
|
+
# OLLIE_SUPABASE_ANON_KEY=your-anon-key-here
|
|
8
|
+
|
|
9
|
+
# Override Builder service URL (uncomment for staging):
|
|
10
|
+
# OLLIE_BUILDER_URL=https://staging-builder.example.com
|
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @ollie-shop/cli@1.
|
|
2
|
+
> @ollie-shop/cli@1.3.3 build /home/runner/work/ollie-shop/ollie-shop/packages/cli
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.tsx
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
[34mCLI[39m Target: node22
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
13
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/index.js [22m[32m92.64 KB[39m
|
|
13
|
+
[32mESM[39m ⚡️ Build success in 300ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @ollie-shop/cli
|
|
2
2
|
|
|
3
|
+
## 1.3.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 464da1e: adjusting cli to update trigger function
|
|
8
|
+
|
|
9
|
+
## 1.3.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- b8d922f: Detect newly created components without restarting the dev server. The esbuild context is now recreated when a component folder is added or removed, so `ollieshop start` serves new components immediately instead of returning 404 until restart.
|
|
14
|
+
- 3b01903: Fix `-v`/`--version` and enrich `whoami`.
|
|
15
|
+
|
|
16
|
+
`-v`/`--version` now report the real package version (inlined from
|
|
17
|
+
`package.json` at build time) instead of the hardcoded `ollie v0.1.0`, and the
|
|
18
|
+
brand string matches the `ollieshop` binary.
|
|
19
|
+
|
|
20
|
+
`whoami` now rejects expired sessions (local `exp` check) and, when an
|
|
21
|
+
`ollie.json` is present, resolves and prints the linked store and organization —
|
|
22
|
+
gated by Supabase RLS, so a store you cannot access is reported as not found
|
|
23
|
+
rather than leaked. With no config it returns just the user plus a hint to run
|
|
24
|
+
`ollieshop init`. The `--stage`/`-s` flag is now parsed as a global flag so
|
|
25
|
+
`whoami` (and other commands) can target `ollie.{stage}.json`.
|
|
26
|
+
|
|
27
|
+
- 2587194: Sync a component's slot back to its local `meta.json` when it's edited in Studio. The CLI `/meta` endpoint now only writes when the incoming `id` matches the component already linked in `meta.json`, so the slot is synced only for the matching local component and never written into the wrong one.
|
|
28
|
+
|
|
29
|
+
## 1.3.1
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- 2f535de: Bake the prod Builder URL into the published CLI so `ollieshop deploy` works zero-config.
|
|
34
|
+
|
|
35
|
+
`getBuilderUrl()` now falls back to `https://api.ollie.shop/builder/v1` when neither `OLLIE_BUILDER_URL` (runtime env) nor `__OLLIE_BUILDER_URL__` (CI build-time inline) is set. Same pattern is applied to the Supabase URL/anon key as a belt-and-braces fix in case CI inject is ever missing for those too. The CHANGELOG for 1.2.1/1.2.2 already promised this baking via a `PROD_DEFAULTS` const and `envOrDefault()` helper, but the actual code relied solely on the tsup `define` substitution — which silently fell through to an empty string whenever the CI `BUILDER_URL` variable was unset or stale. Result: every published CLI since 1.2.2 has required users to manually export `OLLIE_BUILDER_URL=…` before running `ollieshop deploy` or `ollieshop status`.
|
|
36
|
+
|
|
37
|
+
Resolution order is now explicit: `process.env.OLLIE_*` (local dev override) > tsup build-time inline (per-environment publish) > hardcoded prod default. All three values are public (anon Supabase JWT, public API URLs), so inlining carries no secret-leak risk. Local devs pointing the CLI at `http://127.0.0.1:54321` or a non-prod Supabase project are unaffected — the env var still wins.
|
|
38
|
+
|
|
39
|
+
## 1.3.0
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- 18c3e52: Add `--org <uuid>` flag to `ollieshop store create` and `ollieshop store list` so users who belong to multiple organizations can target a specific one.
|
|
44
|
+
|
|
45
|
+
Previously, `getOrganizationId()` blindly took the oldest org the user had a membership in, so multi-org users silently landed every `store create` in the same place with no escape hatch (no flag, no config, no env var). Now:
|
|
46
|
+
|
|
47
|
+
- `--org <uuid>` explicitly targets an org; validated via the existing `validateUuid` helper.
|
|
48
|
+
- When `--org` is omitted and the user has multiple orgs, the CLI throws an error listing every accessible org (id + name) instead of silently picking the oldest.
|
|
49
|
+
- When `--org` points to an org the user is not a member of, the same list is surfaced so they can see what they _can_ access.
|
|
50
|
+
- Single-org users are unaffected.
|
|
51
|
+
|
|
52
|
+
Scope is limited to `store` because `getOrganizationId` is only called from `core/store.ts` — other commands (`component`, `version`, `deploy`) derive org through `store_id` relationships.
|
|
53
|
+
|
|
3
54
|
## 1.2.2
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/CONTEXT.md
CHANGED
|
@@ -16,15 +16,15 @@ Run `ollieshop login` once. Credentials are stored in `~/.ollie-shop/credentials
|
|
|
16
16
|
|
|
17
17
|
## Environment Variables
|
|
18
18
|
|
|
19
|
-
Set these
|
|
19
|
+
None required for prod. Supabase URL, Supabase anon key, and Builder URL are all baked into the published binary. Set these only to override the defaults (e.g. point at local Supabase or a staging Builder):
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
OLLIE_SUPABASE_URL=http://127.0.0.1:54321
|
|
23
23
|
OLLIE_SUPABASE_ANON_KEY=<your-anon-key>
|
|
24
|
-
OLLIE_BUILDER_URL=https://builder.
|
|
24
|
+
OLLIE_BUILDER_URL=https://staging-builder.example.com
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Resolution order: runtime env var > CI build-time inline > hardcoded prod default. See `.env.example` for the local-dev preset.
|
|
28
28
|
|
|
29
29
|
## Typical Agent Workflow
|
|
30
30
|
|
package/README.md
CHANGED
|
@@ -21,14 +21,10 @@ node packages/cli/dist/index.js <command>
|
|
|
21
21
|
# 1. Authenticate
|
|
22
22
|
ollieshop login
|
|
23
23
|
|
|
24
|
-
# 2.
|
|
25
|
-
export OLLIE_SUPABASE_URL=https://your-project.supabase.co
|
|
26
|
-
export OLLIE_SUPABASE_ANON_KEY=your-anon-key
|
|
27
|
-
|
|
28
|
-
# 3. Verify identity
|
|
24
|
+
# 2. Verify identity (prod Supabase/Builder URLs are baked in — no env setup needed)
|
|
29
25
|
ollieshop whoami -o json
|
|
30
26
|
|
|
31
|
-
#
|
|
27
|
+
# 3. List your stores
|
|
32
28
|
ollieshop store list -o json --fields id,name,platform
|
|
33
29
|
```
|
|
34
30
|
|
|
@@ -36,11 +32,11 @@ ollieshop store list -o json --fields id,name,platform
|
|
|
36
32
|
|
|
37
33
|
| Variable | Required | Description |
|
|
38
34
|
|----------|----------|-------------|
|
|
39
|
-
| `OLLIE_SUPABASE_URL` |
|
|
40
|
-
| `OLLIE_SUPABASE_ANON_KEY` |
|
|
41
|
-
| `OLLIE_BUILDER_URL` |
|
|
35
|
+
| `OLLIE_SUPABASE_URL` | No (prod baked) | Override Supabase project URL — e.g. `http://127.0.0.1:54321` for local dev |
|
|
36
|
+
| `OLLIE_SUPABASE_ANON_KEY` | No (prod baked) | Override Supabase anon/public key |
|
|
37
|
+
| `OLLIE_BUILDER_URL` | No (prod baked) | Override Builder service URL — e.g. a staging endpoint |
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
All three default to prod and are baked into the published binary. Set the env vars only when pointing the CLI at a non-prod environment. See `.env.example` for local-dev defaults.
|
|
44
40
|
|
|
45
41
|
## Commands
|
|
46
42
|
|
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" }) }),
|
|
@@ -116,6 +116,7 @@ function HelpCommand() {
|
|
|
116
116
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop whoami -o json" }),
|
|
117
117
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "$ ollieshop schema store.create" }),
|
|
118
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' }),
|
|
119
120
|
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
120
121
|
"$ ollieshop store create --data",
|
|
121
122
|
" ",
|
|
@@ -304,6 +305,9 @@ async function getCurrentUser() {
|
|
|
304
305
|
if (!credentials) return null;
|
|
305
306
|
try {
|
|
306
307
|
const decoded = jwtDecode(credentials.accessToken);
|
|
308
|
+
if (decoded.exp && decoded.exp * 1e3 <= Date.now()) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
307
311
|
return decoded.email ? { email: decoded.email } : null;
|
|
308
312
|
} catch {
|
|
309
313
|
return null;
|
|
@@ -439,6 +443,7 @@ function resolveStage(cliStage) {
|
|
|
439
443
|
}
|
|
440
444
|
|
|
441
445
|
// src/utils/esbuild.ts
|
|
446
|
+
import { watch } from "fs";
|
|
442
447
|
import fs4 from "fs/promises";
|
|
443
448
|
import http from "http";
|
|
444
449
|
import path4 from "path";
|
|
@@ -663,16 +668,69 @@ async function createBuildContext(components, options = {}) {
|
|
|
663
668
|
});
|
|
664
669
|
return ctx;
|
|
665
670
|
}
|
|
666
|
-
async function startDevServer(
|
|
667
|
-
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;
|
|
668
680
|
const servedir = path4.join(cwd, "node_modules/.ollie", "build");
|
|
669
|
-
|
|
681
|
+
const componentsDir = path4.join(cwd, "components");
|
|
670
682
|
const internalPort = port + 1;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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);
|
|
676
734
|
});
|
|
677
735
|
const proxyServer = http.createServer(async (req, res) => {
|
|
678
736
|
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
@@ -756,6 +814,16 @@ async function startDevServer(ctx, options = {}) {
|
|
|
756
814
|
existingMeta = JSON.parse(content);
|
|
757
815
|
} catch {
|
|
758
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
|
+
}
|
|
759
827
|
const newMeta = { ...existingMeta, ...updates };
|
|
760
828
|
await fs4.writeFile(metaPath, JSON.stringify(newMeta, null, 2));
|
|
761
829
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -817,9 +885,14 @@ async function startDevServer(ctx, options = {}) {
|
|
|
817
885
|
return {
|
|
818
886
|
host,
|
|
819
887
|
port,
|
|
888
|
+
rebuild: async () => {
|
|
889
|
+
await ctx?.rebuild();
|
|
890
|
+
},
|
|
820
891
|
stop: async () => {
|
|
892
|
+
if (watchTimer) clearTimeout(watchTimer);
|
|
893
|
+
componentsWatcher.close();
|
|
821
894
|
proxyServer.close();
|
|
822
|
-
await ctx
|
|
895
|
+
await ctx?.dispose();
|
|
823
896
|
}
|
|
824
897
|
};
|
|
825
898
|
}
|
|
@@ -869,7 +942,7 @@ function StartCommand({ args }) {
|
|
|
869
942
|
const [buildCount, setBuildCount] = useState2(0);
|
|
870
943
|
const [lastBuildTime, setLastBuildTime] = useState2(null);
|
|
871
944
|
const logIdRef = useRef(0);
|
|
872
|
-
const
|
|
945
|
+
const rebuildRef = useRef(null);
|
|
873
946
|
const stopRef = useRef(null);
|
|
874
947
|
const stage = resolveStage(parseArg(args, "--stage", "-s"));
|
|
875
948
|
const addLog = useCallback((log) => {
|
|
@@ -919,21 +992,17 @@ function StartCommand({ args }) {
|
|
|
919
992
|
}
|
|
920
993
|
setComponents(found);
|
|
921
994
|
setState({ status: "building" });
|
|
922
|
-
const
|
|
995
|
+
const server = await startDevServer({
|
|
996
|
+
port: PORT,
|
|
923
997
|
stage,
|
|
998
|
+
onRequest: handleRequest,
|
|
924
999
|
onBuildEnd: (updatedComponents) => {
|
|
925
1000
|
setComponents(updatedComponents);
|
|
926
1001
|
setBuildCount((c) => c + 1);
|
|
927
1002
|
setLastBuildTime(/* @__PURE__ */ new Date());
|
|
928
1003
|
}
|
|
929
1004
|
});
|
|
930
|
-
|
|
931
|
-
await ctx.rebuild();
|
|
932
|
-
if (!mounted) return;
|
|
933
|
-
const server = await startDevServer(ctx, {
|
|
934
|
-
port: PORT,
|
|
935
|
-
onRequest: handleRequest
|
|
936
|
-
});
|
|
1005
|
+
rebuildRef.current = server.rebuild;
|
|
937
1006
|
stopRef.current = server.stop;
|
|
938
1007
|
if (!mounted) {
|
|
939
1008
|
await server.stop();
|
|
@@ -971,7 +1040,7 @@ function StartCommand({ args }) {
|
|
|
971
1040
|
stopRef.current?.().then(() => exit());
|
|
972
1041
|
}
|
|
973
1042
|
if (input === "r" && state.status === "running") {
|
|
974
|
-
|
|
1043
|
+
rebuildRef.current?.();
|
|
975
1044
|
}
|
|
976
1045
|
if (input === "o" && state.status === "running") {
|
|
977
1046
|
const studioUrl = new URL(STUDIO_BASE_URL);
|
|
@@ -1177,7 +1246,11 @@ function App({ command, args }) {
|
|
|
1177
1246
|
}
|
|
1178
1247
|
}
|
|
1179
1248
|
function VersionCommand() {
|
|
1180
|
-
|
|
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
|
+
] }) });
|
|
1181
1254
|
}
|
|
1182
1255
|
function UnknownCommand({ command }) {
|
|
1183
1256
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", gap: 1, children: [
|
|
@@ -1377,7 +1450,8 @@ function parseArgs(argv) {
|
|
|
1377
1450
|
output: flags.output || flags.o || void 0,
|
|
1378
1451
|
dryRun: flags["dry-run"] === true,
|
|
1379
1452
|
fields: flags.fields ? String(flags.fields).split(",").map((f) => f.trim()) : void 0,
|
|
1380
|
-
data: flags.data || flags.d || void 0
|
|
1453
|
+
data: flags.data || flags.d || void 0,
|
|
1454
|
+
stage: flags.stage || flags.s || void 0
|
|
1381
1455
|
};
|
|
1382
1456
|
return { command, subcommand, flags, global, positional };
|
|
1383
1457
|
}
|
|
@@ -1397,13 +1471,33 @@ function getBoolFlag(flags, ...names) {
|
|
|
1397
1471
|
|
|
1398
1472
|
// src/utils/supabase.ts
|
|
1399
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
|
+
}
|
|
1400
1486
|
async function getAuthenticatedClient() {
|
|
1401
1487
|
const credentials = await getCredentials();
|
|
1402
1488
|
if (!credentials) {
|
|
1403
1489
|
throw new Error("Not authenticated. Run `ollieshop login` first.");
|
|
1404
1490
|
}
|
|
1405
|
-
const supabaseUrl =
|
|
1406
|
-
|
|
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
|
+
);
|
|
1407
1501
|
const client = createClient(supabaseUrl, supabaseAnonKey, {
|
|
1408
1502
|
auth: {
|
|
1409
1503
|
autoRefreshToken: false,
|
|
@@ -1417,7 +1511,11 @@ async function getAuthenticatedClient() {
|
|
|
1417
1511
|
return client;
|
|
1418
1512
|
}
|
|
1419
1513
|
function getBuilderUrl() {
|
|
1420
|
-
return
|
|
1514
|
+
return envOrDefault(
|
|
1515
|
+
process.env.OLLIE_BUILDER_URL,
|
|
1516
|
+
"",
|
|
1517
|
+
PROD_DEFAULTS.builderUrl
|
|
1518
|
+
);
|
|
1421
1519
|
}
|
|
1422
1520
|
async function getAuthToken() {
|
|
1423
1521
|
const credentials = await getCredentials();
|
|
@@ -1426,17 +1524,35 @@ async function getAuthToken() {
|
|
|
1426
1524
|
}
|
|
1427
1525
|
return credentials.accessToken;
|
|
1428
1526
|
}
|
|
1429
|
-
async function getOrganizationId(client) {
|
|
1430
|
-
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 });
|
|
1431
1529
|
if (error) {
|
|
1432
1530
|
throw new Error(`Failed to get organization: ${error.message}`);
|
|
1433
1531
|
}
|
|
1434
|
-
if (!
|
|
1532
|
+
if (!orgs || orgs.length === 0) {
|
|
1435
1533
|
throw new Error(
|
|
1436
1534
|
"No organization found for your account. Create one at https://admin.ollie.shop"
|
|
1437
1535
|
);
|
|
1438
1536
|
}
|
|
1439
|
-
|
|
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;
|
|
1440
1556
|
}
|
|
1441
1557
|
|
|
1442
1558
|
// src/utils/validate.ts
|
|
@@ -1663,8 +1779,11 @@ var componentListSchema = z2.object({
|
|
|
1663
1779
|
var functionCreateSchema = z2.object({
|
|
1664
1780
|
versionId: z2.string().uuid().describe("Parent version UUID"),
|
|
1665
1781
|
name: z2.string().min(1).describe("Function name"),
|
|
1666
|
-
trigger: z2.
|
|
1667
|
-
"
|
|
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"
|
|
1668
1787
|
),
|
|
1669
1788
|
active: z2.boolean().default(false).describe("Whether function is active (default: false)"),
|
|
1670
1789
|
onError: z2.enum(["throw", "skip"]).optional().describe("Error handling: throw (default) or skip"),
|
|
@@ -2087,6 +2206,36 @@ async function listFunctions(client, storeId, versionId) {
|
|
|
2087
2206
|
|
|
2088
2207
|
// src/commands/function-cmd.ts
|
|
2089
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
|
+
}
|
|
2090
2239
|
async function functionCommand(parsed2) {
|
|
2091
2240
|
const sub = parsed2.subcommand;
|
|
2092
2241
|
if (sub === "create") return functionCreateCommand(parsed2);
|
|
@@ -2105,13 +2254,14 @@ async function functionCreateCommand(parsed2) {
|
|
|
2105
2254
|
input = {
|
|
2106
2255
|
versionId: validateUuid(raw.versionId, "versionId"),
|
|
2107
2256
|
name: validateRequired(raw.name, "name"),
|
|
2108
|
-
trigger:
|
|
2257
|
+
trigger: parseTriggerFromData(raw.trigger),
|
|
2109
2258
|
active: raw.active === true,
|
|
2110
2259
|
onError: raw.onError !== void 0 && raw.onError !== null ? validateEnum(String(raw.onError), ON_ERROR_VALUES, "on-error") : "throw",
|
|
2111
2260
|
priority: raw.priority !== void 0 && raw.priority !== null ? validateInteger(raw.priority, "priority", { min: 0 }) : 0
|
|
2112
2261
|
};
|
|
2113
2262
|
} else {
|
|
2114
|
-
const
|
|
2263
|
+
const triggerUrlFlag = getFlag(parsed2.flags, "trigger-url");
|
|
2264
|
+
const triggerExpressionFlag = getFlag(parsed2.flags, "trigger-expression");
|
|
2115
2265
|
const onErrorFlag = getFlag(parsed2.flags, "on-error");
|
|
2116
2266
|
const priorityFlag = getFlag(parsed2.flags, "priority");
|
|
2117
2267
|
input = {
|
|
@@ -2120,7 +2270,7 @@ async function functionCreateCommand(parsed2) {
|
|
|
2120
2270
|
"version-id"
|
|
2121
2271
|
),
|
|
2122
2272
|
name: validateRequired(getFlag(parsed2.flags, "name", "n"), "name"),
|
|
2123
|
-
trigger:
|
|
2273
|
+
trigger: parseTriggerFromFlags(triggerUrlFlag, triggerExpressionFlag),
|
|
2124
2274
|
active: getBoolFlag(parsed2.flags, "active"),
|
|
2125
2275
|
onError: onErrorFlag !== void 0 ? validateEnum(onErrorFlag, ON_ERROR_VALUES, "on-error") : "throw",
|
|
2126
2276
|
priority: priorityFlag !== void 0 ? validateInteger(priorityFlag, "priority", { min: 0 }) : 0
|
|
@@ -2304,14 +2454,14 @@ async function statusCommand(parsed2) {
|
|
|
2304
2454
|
}
|
|
2305
2455
|
|
|
2306
2456
|
// src/core/store.ts
|
|
2307
|
-
async function createStore(client, input) {
|
|
2457
|
+
async function createStore(client, input, orgId) {
|
|
2308
2458
|
const parsed2 = storeCreateSchema.safeParse(input);
|
|
2309
2459
|
if (!parsed2.success) {
|
|
2310
2460
|
return {
|
|
2311
2461
|
error: { message: parsed2.error.issues.map((i) => i.message).join("; ") }
|
|
2312
2462
|
};
|
|
2313
2463
|
}
|
|
2314
|
-
const organizationId = await getOrganizationId(client);
|
|
2464
|
+
const organizationId = await getOrganizationId(client, orgId);
|
|
2315
2465
|
const { data, error } = await client.from("stores").insert({
|
|
2316
2466
|
name: parsed2.data.name,
|
|
2317
2467
|
platform: parsed2.data.platform,
|
|
@@ -2325,8 +2475,33 @@ async function createStore(client, input) {
|
|
|
2325
2475
|
}
|
|
2326
2476
|
return { data: { id: data.id } };
|
|
2327
2477
|
}
|
|
2328
|
-
async function
|
|
2329
|
-
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);
|
|
2330
2505
|
const { data, error } = await client.from("stores").select(
|
|
2331
2506
|
"id, name, platform, platform_store_id, organization_id, logo, settings, created_at"
|
|
2332
2507
|
).eq("organization_id", organizationId).order("created_at", { ascending: false });
|
|
@@ -2378,12 +2553,14 @@ async function storeCreateCommand(parsed2) {
|
|
|
2378
2553
|
settings: getFlag(parsed2.flags, "settings")
|
|
2379
2554
|
};
|
|
2380
2555
|
}
|
|
2556
|
+
const orgFlag = getFlag(parsed2.flags, "org");
|
|
2557
|
+
const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
|
|
2381
2558
|
if (parsed2.global.dryRun) {
|
|
2382
|
-
outputDryRun("store.create", input, format);
|
|
2559
|
+
outputDryRun("store.create", { ...input, orgId }, format);
|
|
2383
2560
|
return;
|
|
2384
2561
|
}
|
|
2385
2562
|
const client = await getAuthenticatedClient();
|
|
2386
|
-
const result = await createStore(client, input);
|
|
2563
|
+
const result = await createStore(client, input, orgId);
|
|
2387
2564
|
outputResult(result, format, parsed2.global.fields);
|
|
2388
2565
|
if (result.error) process.exit(1);
|
|
2389
2566
|
} catch (err) {
|
|
@@ -2399,8 +2576,10 @@ async function storeCreateCommand(parsed2) {
|
|
|
2399
2576
|
async function storeListCommand(parsed2) {
|
|
2400
2577
|
const format = detectOutputFormat(parsed2.global.output);
|
|
2401
2578
|
try {
|
|
2579
|
+
const orgFlag = getFlag(parsed2.flags, "org");
|
|
2580
|
+
const orgId = orgFlag ? validateUuid(orgFlag, "org") : void 0;
|
|
2402
2581
|
const client = await getAuthenticatedClient();
|
|
2403
|
-
const result = await listStores(client);
|
|
2582
|
+
const result = await listStores(client, orgId);
|
|
2404
2583
|
outputResult(result, format, parsed2.global.fields);
|
|
2405
2584
|
if (result.error) process.exit(1);
|
|
2406
2585
|
} catch (err) {
|
|
@@ -2524,21 +2703,71 @@ async function whoamiCommand(parsed2) {
|
|
|
2524
2703
|
if (!user) {
|
|
2525
2704
|
outputResult(
|
|
2526
2705
|
{
|
|
2527
|
-
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) }
|
|
2528
2766
|
},
|
|
2529
2767
|
format
|
|
2530
2768
|
);
|
|
2531
2769
|
process.exit(1);
|
|
2532
2770
|
}
|
|
2533
|
-
outputResult(
|
|
2534
|
-
{
|
|
2535
|
-
data: {
|
|
2536
|
-
email: user.email
|
|
2537
|
-
}
|
|
2538
|
-
},
|
|
2539
|
-
format,
|
|
2540
|
-
parsed2.global.fields
|
|
2541
|
-
);
|
|
2542
2771
|
}
|
|
2543
2772
|
|
|
2544
2773
|
// src/index.tsx
|