@greatstore/cli 0.0.19 → 0.0.21

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cli.js +157 -50
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to `@greatstore/cli` are recorded here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## 0.0.21 — 2026-05-31
7
+
8
+ ### Changed
9
+ - The `gs init` component scaffold now shows how to write **async**
10
+ components that load data before they render — including validating
11
+ inputs up front and signalling a failure by throwing. The scaffolded
12
+ component no longer includes an `onError` prop; throw from an async
13
+ component to report a failure instead.
14
+
15
+ ## 0.0.20 — 2026-05-29
16
+
17
+ ### Added
18
+ - `gs pull`, `gs push`, and `gs publish` now accept several component
19
+ names at once (e.g. `gs publish header footer cart`). Each component
20
+ is reported on its own line and one failure no longer stops the rest.
21
+
6
22
  ## 0.0.19 — 2026-05-28
7
23
 
8
24
  ### Fixed
package/dist/cli.js CHANGED
@@ -632,21 +632,32 @@ function safeComputeHashes(componentDir) {
632
632
  async function pullCommand(args) {
633
633
  const slug = requireProjectStore();
634
634
  const force = flagBool(args.flags, "force");
635
- const target = args.positional[0];
635
+ const targets = args.positional;
636
636
  const root = process.cwd();
637
- if (!target || target === "*") {
637
+ if (targets.length === 0 || targets.length === 1 && targets[0] === "*") {
638
638
  return pullAll({ slug, root, args, force });
639
639
  }
640
+ if (targets.length > 1 && flagString(args.flags, "version")) {
641
+ throw new Error(
642
+ "--version is per-component; pass it together with a single `gs pull <name>`."
643
+ );
644
+ }
640
645
  ensureComponentsDir(root);
641
- const outcome = await pullOne({
642
- slug,
643
- componentDir: path4.join(root, "components", target),
644
- name: target,
645
- revisionQuery: buildRevisionQuery(args),
646
- force
647
- });
648
- printOutcome(outcome, force);
649
- if (outcome.status === "failed") process.exitCode = 1;
646
+ const revisionQuery = buildRevisionQuery(args);
647
+ const outcomes = [];
648
+ for (const name of targets) {
649
+ const outcome = await pullOne({
650
+ slug,
651
+ componentDir: path4.join(root, "components", name),
652
+ name,
653
+ revisionQuery,
654
+ force
655
+ });
656
+ outcomes.push(outcome);
657
+ printOutcome(outcome, force);
658
+ }
659
+ if (outcomes.length > 1) printPullSummary(outcomes, slug);
660
+ if (outcomes.some((o) => o.status === "failed")) process.exitCode = 1;
650
661
  }
651
662
  async function pullAll(opts) {
652
663
  const { slug, root, args, force } = opts;
@@ -677,6 +688,10 @@ async function pullAll(opts) {
677
688
  outcomes.push(outcome);
678
689
  printOutcome(outcome, force);
679
690
  }
691
+ printPullSummary(outcomes, slug);
692
+ if (outcomes.some((o) => o.status === "failed")) process.exitCode = 1;
693
+ }
694
+ function printPullSummary(outcomes, slug) {
680
695
  const pulled = outcomes.filter((o) => o.status === "pulled").length;
681
696
  const skipped = outcomes.filter((o) => o.status === "skipped").length;
682
697
  const failed = outcomes.filter((o) => o.status === "failed").length;
@@ -687,7 +702,6 @@ async function pullAll(opts) {
687
702
  ${summary.join(", ")} (of ${outcomes.length}) \u2190 ${slug}
688
703
  `
689
704
  );
690
- if (failed > 0) process.exitCode = 1;
691
705
  }
692
706
  async function pullOne(opts) {
693
707
  const { slug, componentDir, name, revisionQuery, force } = opts;
@@ -783,25 +797,30 @@ async function pushCommand(args) {
783
797
  "No components/ directory here. Run `gs init <name>` to scaffold the project root and your first component."
784
798
  );
785
799
  }
786
- const named = args.positional[0];
800
+ const named = args.positional;
787
801
  const manifestOverride = flagString(args.flags, "manifest");
788
802
  const bundleOverride = flagString(args.flags, "bundle");
789
- if (!named && (manifestOverride || bundleOverride)) {
803
+ if (named.length !== 1 && (manifestOverride || bundleOverride)) {
790
804
  throw new Error(
791
- "--manifest / --bundle can only be used together with `gs push <name>`."
805
+ "--manifest / --bundle can only be used together with a single `gs push <name>`."
792
806
  );
793
807
  }
794
- if (named) {
795
- const outcome = await pushOne({
796
- slug,
797
- root,
798
- name: named,
799
- ...manifestOverride !== void 0 ? { manifestPath: manifestOverride } : {},
800
- ...bundleOverride !== void 0 ? { bundlePath: bundleOverride } : {},
801
- force: true
802
- });
803
- printOutcome2(outcome);
804
- if (outcome.status === "failed") process.exitCode = 1;
808
+ if (named.length > 0) {
809
+ const outcomes2 = [];
810
+ for (const name of named) {
811
+ const outcome = await pushOne({
812
+ slug,
813
+ root,
814
+ name,
815
+ ...manifestOverride !== void 0 ? { manifestPath: manifestOverride } : {},
816
+ ...bundleOverride !== void 0 ? { bundlePath: bundleOverride } : {},
817
+ force: true
818
+ });
819
+ outcomes2.push(outcome);
820
+ printOutcome2(outcome);
821
+ }
822
+ if (outcomes2.length > 1) printPushSummary(outcomes2, slug);
823
+ if (outcomes2.some((o) => o.status === "failed")) process.exitCode = 1;
805
824
  return;
806
825
  }
807
826
  const names = listLocalComponents(componentsDir);
@@ -817,6 +836,10 @@ async function pushCommand(args) {
817
836
  outcomes.push(outcome);
818
837
  printOutcome2(outcome);
819
838
  }
839
+ printPushSummary(outcomes, slug);
840
+ if (outcomes.some((o) => o.status === "failed")) process.exitCode = 1;
841
+ }
842
+ function printPushSummary(outcomes, slug) {
820
843
  const pushed = outcomes.filter((o) => o.status === "pushed").length;
821
844
  const unchanged = outcomes.filter((o) => o.status === "unchanged").length;
822
845
  const failed = outcomes.filter((o) => o.status === "failed").length;
@@ -825,7 +848,6 @@ async function pushCommand(args) {
825
848
  process.stdout.write(`
826
849
  ${summary.join(", ")} (of ${outcomes.length}) \u2192 ${slug}
827
850
  `);
828
- if (failed > 0) process.exitCode = 1;
829
851
  }
830
852
  async function pushOne(opts) {
831
853
  const { slug, root, name, force } = opts;
@@ -964,24 +986,83 @@ function printOutcome2(outcome) {
964
986
 
965
987
  // src/commands/publish.ts
966
988
  async function publishCommand(args) {
967
- const name = args.positional[0];
968
- if (!name) throw new Error("Usage: gs publish <name> [--version N]");
989
+ const names = args.positional;
990
+ if (names.length === 0) {
991
+ throw new Error("Usage: gs publish <name> [<name>...] [--version N]");
992
+ }
969
993
  const slug = requireProjectStore();
970
- const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/publish`;
971
994
  const versionFlag = flagString(args.flags, "version");
972
- let body = void 0;
995
+ let version;
973
996
  if (versionFlag !== void 0) {
997
+ if (names.length > 1) {
998
+ throw new Error(
999
+ "--version is per-component; pass it together with a single `gs publish <name>`."
1000
+ );
1001
+ }
974
1002
  const n = Number.parseInt(versionFlag, 10);
975
1003
  if (!Number.isInteger(n) || n < 1) {
976
1004
  throw new Error(`Invalid --version: ${versionFlag}`);
977
1005
  }
978
- body = { version: n };
979
- } else {
980
- body = {};
1006
+ version = n;
981
1007
  }
982
- const data = await request(url, { method: "POST", body });
983
- process.stdout.write(`Published ${name} (v${data.live_version}). ${data.permalink}
984
- `);
1008
+ if (names.length === 1) {
1009
+ const name = names[0];
1010
+ const data = await publish(slug, name, version);
1011
+ process.stdout.write(
1012
+ `Published ${name} (v${data.live_version}). ${data.permalink}
1013
+ `
1014
+ );
1015
+ return;
1016
+ }
1017
+ const outcomes = [];
1018
+ for (const name of names) {
1019
+ let outcome;
1020
+ try {
1021
+ const data = await publish(slug, name, version);
1022
+ outcome = {
1023
+ name,
1024
+ status: "published",
1025
+ version: data.live_version,
1026
+ permalink: data.permalink
1027
+ };
1028
+ } catch (err) {
1029
+ outcome = {
1030
+ name,
1031
+ status: "failed",
1032
+ message: err instanceof Error ? err.message : String(err)
1033
+ };
1034
+ }
1035
+ outcomes.push(outcome);
1036
+ printOutcome3(outcome);
1037
+ }
1038
+ const published = outcomes.filter((o) => o.status === "published").length;
1039
+ const failed = outcomes.filter((o) => o.status === "failed").length;
1040
+ const summary = [`${published} published`];
1041
+ if (failed > 0) summary.push(`${failed} failed`);
1042
+ process.stdout.write(
1043
+ `
1044
+ ${summary.join(", ")} (of ${outcomes.length}) \u2192 ${slug}
1045
+ `
1046
+ );
1047
+ if (failed > 0) process.exitCode = 1;
1048
+ }
1049
+ async function publish(slug, name, version) {
1050
+ const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/publish`;
1051
+ const body = version !== void 0 ? { version } : {};
1052
+ return request(url, { method: "POST", body });
1053
+ }
1054
+ function printOutcome3(outcome) {
1055
+ if (outcome.status === "published") {
1056
+ process.stdout.write(
1057
+ ` published ${outcome.name} v${outcome.version} \u2192 ${outcome.permalink ?? ""}
1058
+ `
1059
+ );
1060
+ return;
1061
+ }
1062
+ process.stdout.write(
1063
+ ` failed ${outcome.name}: ${outcome.message ?? "unknown error"}
1064
+ `
1065
+ );
985
1066
  }
986
1067
 
987
1068
  // src/commands/unpublish.ts
@@ -1179,14 +1260,6 @@ interface Props {
1179
1260
  // Dismiss the host slot (over-input clears the overlay, fullscreen
1180
1261
  // reverts the pane, inline is a no-op).
1181
1262
  onClose: () => void;
1182
- // Tell the in-store AI something went wrong so it can apologize or
1183
- // self-correct on its next turn. Use for *expected* failures inside
1184
- // async handlers (failed fetch, host-page action rejected, invalid
1185
- // host state). Render-time crashes are caught and reported for you
1186
- // \u2014 you don't need a try/catch just to forward exceptions. The
1187
- // message is read by the AI, not the shopper, so write it like a
1188
- // short engineering note.
1189
- onError: (message: string) => void;
1190
1263
  }
1191
1264
 
1192
1265
  export default function ${pascal(name)}(_props: Props): React.ReactElement {
@@ -1196,6 +1269,40 @@ export default function ${pascal(name)}(_props: Props): React.ReactElement {
1196
1269
  </div>
1197
1270
  );
1198
1271
  }
1272
+
1273
+ // ---- Async components (backend-backed, render-blocking data) ----
1274
+ // If this component must load data from a backend/API before it can
1275
+ // render correctly, make it async \u2014 don't render an empty shell and
1276
+ // fetch in a useEffect. Set "async": true in manifest.json and export
1277
+ // an async default. GreatStore waits for your promise (showing a normal
1278
+ // loading state, so you don't render your own placeholder), then renders
1279
+ // what it resolves to.
1280
+ //
1281
+ // A thrown error is a RETRY SIGNAL: the in-store AI sees it and usually
1282
+ // re-calls the tool. So only throw when a *different* call could help.
1283
+ // 1. await the backend call, then return the finished JSX.
1284
+ // 2. Validate the AI-passed props first and throw on bad input \u2014 the
1285
+ // AI can fix the args and retry. (Don't validate the API's output
1286
+ // and throw: the AI can't fix the backend, it'll just loop.)
1287
+ // 3. Broadcast a backend failure ONLY when retrying differently could
1288
+ // succeed, and say what to change (e.g. empty search \u2192 "try a
1289
+ // broader keyword"). For idempotent failures (500, timeout, missing
1290
+ // record) re-running the same call changes nothing \u2014 render a
1291
+ // graceful fallback instead of throwing.
1292
+ //
1293
+ // export default async function ${pascal(name)}(props: Props) {
1294
+ // if (!props.query?.trim()) throw new Error("missing required prop: query");
1295
+ // const res = await fetch(\`/api/search?q=\${encodeURIComponent(props.query)}\`);
1296
+ // if (res.ok) {
1297
+ // const { results } = await res.json();
1298
+ // if (results.length === 0)
1299
+ // throw new Error(\`no results for "\${props.query}" \u2014 try a broader keyword\`);
1300
+ // return <ul>{/* render results */}</ul>;
1301
+ // }
1302
+ // return <p>Couldn't load results right now.</p>; // idempotent: don't throw
1303
+ // }
1304
+ //
1305
+ // Components that render purely from their props stay synchronous.
1199
1306
  `;
1200
1307
  }
1201
1308
  function viteEditorConfig() {
@@ -1594,8 +1701,8 @@ function parseVer(v) {
1594
1701
  }
1595
1702
 
1596
1703
  // src/index.ts
1597
- var VERSION = true ? "0.0.19" : "0.0.0-dev";
1598
- var CHANGELOG = true ? '# Changelog\n\nAll notable changes to `@greatstore/cli` are recorded here. The format\nfollows [Keep a Changelog](https://keepachangelog.com/).\n\n## 0.0.19 \u2014 2026-05-28\n\n### Fixed\n- `gs login` on Windows no longer opens a sign-in URL with parameters\n stripped, which surfaced as a "Missing redirect_uri or state\n parameter" page in the browser.\n\n## 0.0.18 \u2014 2026-05-28\n\n### Changed\n- Push and publish errors now name the specific reason \u2014 including\n every failing field in `manifest.json` \u2014 instead of the previous\n generic message.\n\n## 0.0.17 \u2014 2026-05-28\n\n### Added\n- Each command now prints a one-line upgrade notice when a newer\n `@greatstore/cli` is available on npm.\n\n## 0.0.16 \u2014 2026-05-28\n\n### Changed\n- Simplified error messages.\n\n## 0.0.15 \u2014 2026-05-24\n\n### Added\n- Scaffolded `component.tsx` now declares the four injected lifecycle\n props (`onSendMessage`, `onCallTool`, `onClose`, `onError`) on\n `Props`. Use `onError(message)` to report expected failures (failed\n fetch, host action rejected, invalid host state) so the AI can\n recover on its next turn. Render-time crashes are reported for you.\n\n## 0.0.14 \u2014 2026-05-24\n\n### Changed\n- `gs build` output is now whitespace-minified \u2014 typically ~50% smaller.\n\n## 0.0.13 \u2014 2026-05-24\n\n### Changed\n- `gs build` prints the next-step hint (`gs push`, then `gs publish`).\n\n## 0.0.12 \u2014 2026-05-24\n\n### Fixed\n- `gs build` failing to load Vite in some setups.\n\n## 0.0.11 \u2014 2026-05-24\n\n### Added\n- Multi-component projects. `gs init` (no args) scaffolds the project\n root; `gs init <name>` adds a component under `components/<name>/`.\n- `gs build [<name>]` \u2014 compiles every `components/<name>/bundle.js`.\n- `gs push` (no args) uploads only the components that changed.\n- `gs pull` (no args, or `*`) downloads every component. Locally\n edited components are skipped; pass `--force` to overwrite.\n- Public `CHANGELOG.md`; `gs --version` prints recent entries.\n\n### Changed\n- A project folder ships to exactly one store. Only `gs init` accepts\n `--store`; every other command reads the slug from `.gsrc`. The old\n single-component layout is rejected with a migration hint.\n- `gs init` requires `--store <slug>` for a fresh root, and rejects\n `--store` on an existing root.\n- `gs init` no longer writes `build.mjs` \u2014 scripts call `gs build`.\n\n## 0.0.10 \u2014 2026-05-23\n\n### Changed\n- The sign-in browser tab auto-closes once `gs login` finishes.\n\n## 0.0.9 \u2014 2026-05-23\n\n### Changed\n- Scaffolded manifests include a `displayName` so the admin UI has a\n friendlier label.\n\n## 0.0.8 \u2014 2026-05-23\n\n### Changed\n- `gs push` and `gs publish` print a link to view the component.\n\n## 0.0.6 \u2014 2026-05-23\n\n### Changed\n- `gs --version` reads from the published package version.\n\n## 0.0.4 \u2014 2026-05-23\n\n### Changed\n- Scaffolded projects produce browser-ready bundles out of the box.\n\n## 0.0.3 \u2014 2026-05-23\n\n### Changed\n- Trimmed public README to the essentials.\n\n## 0.0.2 \u2014 2026-05-23\n\n### Fixed\n- Sign-in callback parameter handling.\n' : "";
1704
+ var VERSION = true ? "0.0.21" : "0.0.0-dev";
1705
+ var CHANGELOG = true ? '# Changelog\n\nAll notable changes to `@greatstore/cli` are recorded here. The format\nfollows [Keep a Changelog](https://keepachangelog.com/).\n\n## 0.0.21 \u2014 2026-05-31\n\n### Changed\n- The `gs init` component scaffold now shows how to write **async**\n components that load data before they render \u2014 including validating\n inputs up front and signalling a failure by throwing. The scaffolded\n component no longer includes an `onError` prop; throw from an async\n component to report a failure instead.\n\n## 0.0.20 \u2014 2026-05-29\n\n### Added\n- `gs pull`, `gs push`, and `gs publish` now accept several component\n names at once (e.g. `gs publish header footer cart`). Each component\n is reported on its own line and one failure no longer stops the rest.\n\n## 0.0.19 \u2014 2026-05-28\n\n### Fixed\n- `gs login` on Windows no longer opens a sign-in URL with parameters\n stripped, which surfaced as a "Missing redirect_uri or state\n parameter" page in the browser.\n\n## 0.0.18 \u2014 2026-05-28\n\n### Changed\n- Push and publish errors now name the specific reason \u2014 including\n every failing field in `manifest.json` \u2014 instead of the previous\n generic message.\n\n## 0.0.17 \u2014 2026-05-28\n\n### Added\n- Each command now prints a one-line upgrade notice when a newer\n `@greatstore/cli` is available on npm.\n\n## 0.0.16 \u2014 2026-05-28\n\n### Changed\n- Simplified error messages.\n\n## 0.0.15 \u2014 2026-05-24\n\n### Added\n- Scaffolded `component.tsx` now declares the four injected lifecycle\n props (`onSendMessage`, `onCallTool`, `onClose`, `onError`) on\n `Props`. Use `onError(message)` to report expected failures (failed\n fetch, host action rejected, invalid host state) so the AI can\n recover on its next turn. Render-time crashes are reported for you.\n\n## 0.0.14 \u2014 2026-05-24\n\n### Changed\n- `gs build` output is now whitespace-minified \u2014 typically ~50% smaller.\n\n## 0.0.13 \u2014 2026-05-24\n\n### Changed\n- `gs build` prints the next-step hint (`gs push`, then `gs publish`).\n\n## 0.0.12 \u2014 2026-05-24\n\n### Fixed\n- `gs build` failing to load Vite in some setups.\n\n## 0.0.11 \u2014 2026-05-24\n\n### Added\n- Multi-component projects. `gs init` (no args) scaffolds the project\n root; `gs init <name>` adds a component under `components/<name>/`.\n- `gs build [<name>]` \u2014 compiles every `components/<name>/bundle.js`.\n- `gs push` (no args) uploads only the components that changed.\n- `gs pull` (no args, or `*`) downloads every component. Locally\n edited components are skipped; pass `--force` to overwrite.\n- Public `CHANGELOG.md`; `gs --version` prints recent entries.\n\n### Changed\n- A project folder ships to exactly one store. Only `gs init` accepts\n `--store`; every other command reads the slug from `.gsrc`. The old\n single-component layout is rejected with a migration hint.\n- `gs init` requires `--store <slug>` for a fresh root, and rejects\n `--store` on an existing root.\n- `gs init` no longer writes `build.mjs` \u2014 scripts call `gs build`.\n\n## 0.0.10 \u2014 2026-05-23\n\n### Changed\n- The sign-in browser tab auto-closes once `gs login` finishes.\n\n## 0.0.9 \u2014 2026-05-23\n\n### Changed\n- Scaffolded manifests include a `displayName` so the admin UI has a\n friendlier label.\n\n## 0.0.8 \u2014 2026-05-23\n\n### Changed\n- `gs push` and `gs publish` print a link to view the component.\n\n## 0.0.6 \u2014 2026-05-23\n\n### Changed\n- `gs --version` reads from the published package version.\n\n## 0.0.4 \u2014 2026-05-23\n\n### Changed\n- Scaffolded projects produce browser-ready bundles out of the box.\n\n## 0.0.3 \u2014 2026-05-23\n\n### Changed\n- Trimmed public README to the essentials.\n\n## 0.0.2 \u2014 2026-05-23\n\n### Fixed\n- Sign-in callback parameter handling.\n' : "";
1599
1706
  var HELP = `gs \u2014 GreatStore CLI (v${VERSION})
1600
1707
 
1601
1708
  Usage:
@@ -1608,9 +1715,9 @@ Commands:
1608
1715
  init [<name>] Scaffold project root; add \`components/<name>/\` if name given.
1609
1716
  build [<name>] Build component bundle(s) via the project's Vite. No args = all.
1610
1717
  list List components in the current store.
1611
- pull [<name>|*] Download remote component(s) into \`components/<name>/\`. No args = all.
1612
- push [<name>] Upload changed components from \`components/\`. No args = all (skips unchanged).
1613
- publish <name> Promote the draft (or --version N) to live.
1718
+ pull [<name>...|*] Download remote component(s) into \`components/<name>/\`. No args = all.
1719
+ push [<name>...] Upload changed components from \`components/\`. No args = all (skips unchanged).
1720
+ publish <name>... Promote the draft(s) (or --version N) to live.
1614
1721
  unpublish <name> Clear the live pointer.
1615
1722
  delete <name> Soft-delete the component.
1616
1723
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatstore/cli",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "CLI for authoring and shipping GreatStore custom components.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",