@greatstore/cli 0.0.9 → 0.0.11-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/cli.js +555 -158
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,13 @@ gs push
|
|
|
11
11
|
gs publish my-widget
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## Release channels
|
|
15
|
+
|
|
16
|
+
- `npm install -g @greatstore/cli` — the stable production release (`@latest`).
|
|
17
|
+
- `npm install -g @greatstore/cli@beta` — the prerelease channel for testing in-flight changes before they cut over to `@latest`.
|
|
18
|
+
|
|
19
|
+
Cut a beta by bumping `cli/package.json` to `<x.y.z>-beta.<n>` (e.g. `0.0.12-beta.0`), then push tag `cli-v0.0.12-beta.0`. The publish workflow extracts the prerelease identifier from the version and publishes under that dist-tag, so `@latest` is unaffected. Drop the `-beta.<n>` suffix and tag again to promote to `@latest`.
|
|
20
|
+
|
|
14
21
|
## Commands
|
|
15
22
|
|
|
16
23
|
| Command | What it does |
|
package/dist/cli.js
CHANGED
|
@@ -71,7 +71,9 @@ var SUCCESS_HTML = `<!doctype html>
|
|
|
71
71
|
<style>body{font:14px/1.5 system-ui;margin:4rem auto;max-width:32rem;color:#222;text-align:center}
|
|
72
72
|
h1{font-size:1.4rem}code{background:#f4f4f4;padding:.1em .3em;border-radius:.2em}</style></head>
|
|
73
73
|
<body><h1>You're signed in.</h1>
|
|
74
|
-
<p>
|
|
74
|
+
<p>This window will close automatically. If it doesn't, you can close it and return to your terminal.</p>
|
|
75
|
+
<script>setTimeout(function(){window.close();},250);</script>
|
|
76
|
+
</body></html>`;
|
|
75
77
|
var FAILURE_HTML = `<!doctype html>
|
|
76
78
|
<html lang="en"><head><meta charset="utf-8"><title>GreatStore CLI</title>
|
|
77
79
|
<style>body{font:14px/1.5 system-ui;margin:4rem auto;max-width:32rem;color:#222;text-align:center}
|
|
@@ -90,7 +92,7 @@ async function captureLoopbackToken(options) {
|
|
|
90
92
|
const open = options.openBrowser ?? ((url) => openInBrowser(url, options.browserCmdEnv));
|
|
91
93
|
const server = http.createServer();
|
|
92
94
|
try {
|
|
93
|
-
await new Promise((
|
|
95
|
+
await new Promise((resolve4) => server.listen(0, "127.0.0.1", resolve4));
|
|
94
96
|
const address = server.address();
|
|
95
97
|
const redirectUri = `http://127.0.0.1:${address.port}${CALLBACK_PATH}`;
|
|
96
98
|
const authUrl = `${options.navBaseUrl}/connect_oauth_done?redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}`;
|
|
@@ -102,7 +104,7 @@ async function captureLoopbackToken(options) {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
function waitForCallback(server, expectedState, timeoutMs) {
|
|
105
|
-
return new Promise((
|
|
107
|
+
return new Promise((resolve4, reject) => {
|
|
106
108
|
let settled = false;
|
|
107
109
|
const settle = (fn) => {
|
|
108
110
|
if (settled) return;
|
|
@@ -148,7 +150,7 @@ function waitForCallback(server, expectedState, timeoutMs) {
|
|
|
148
150
|
res.writeHead(200, { "content-type": "text/html" });
|
|
149
151
|
res.end(SUCCESS_HTML);
|
|
150
152
|
clearTimeout(timer);
|
|
151
|
-
settle(() =>
|
|
153
|
+
settle(() => resolve4({ token }));
|
|
152
154
|
});
|
|
153
155
|
});
|
|
154
156
|
}
|
|
@@ -271,16 +273,11 @@ var StoreResolutionError = class extends Error {
|
|
|
271
273
|
this.name = "StoreResolutionError";
|
|
272
274
|
}
|
|
273
275
|
};
|
|
274
|
-
function
|
|
275
|
-
const flag = input.flag?.trim();
|
|
276
|
-
if (flag) return flag;
|
|
276
|
+
function requireProjectStore(input = {}) {
|
|
277
277
|
const fromRc = findGsrc(input.cwd ?? process.cwd());
|
|
278
278
|
if (fromRc) return fromRc;
|
|
279
|
-
const env = input.env ?? process.env;
|
|
280
|
-
const fromEnv = env.GS_STORE?.trim();
|
|
281
|
-
if (fromEnv) return fromEnv;
|
|
282
279
|
throw new StoreResolutionError(
|
|
283
|
-
|
|
280
|
+
"No .gsrc found in this directory or any ancestor. Run `gs init --store <slug>` first to scaffold a GreatStore project."
|
|
284
281
|
);
|
|
285
282
|
}
|
|
286
283
|
function findGsrc(cwd) {
|
|
@@ -477,7 +474,7 @@ function whoamiCommand() {
|
|
|
477
474
|
|
|
478
475
|
// src/commands/list.ts
|
|
479
476
|
async function listCommand(args) {
|
|
480
|
-
const slug =
|
|
477
|
+
const slug = requireProjectStore();
|
|
481
478
|
const url = `${apiBaseFor(slug)}/api/builder/components`;
|
|
482
479
|
const data = await request(url);
|
|
483
480
|
if (flagBool(args.flags, "json")) {
|
|
@@ -514,34 +511,181 @@ function pad(s, width) {
|
|
|
514
511
|
}
|
|
515
512
|
|
|
516
513
|
// src/commands/pull.ts
|
|
514
|
+
import * as fs4 from "fs";
|
|
515
|
+
import * as path4 from "path";
|
|
516
|
+
|
|
517
|
+
// src/sync.ts
|
|
518
|
+
import * as crypto2 from "crypto";
|
|
517
519
|
import * as fs3 from "fs";
|
|
518
520
|
import * as path3 from "path";
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
521
|
+
var SYNC_FILE = ".gssync.json";
|
|
522
|
+
function hashString(text) {
|
|
523
|
+
return crypto2.createHash("sha256").update(text).digest("hex");
|
|
524
|
+
}
|
|
525
|
+
function hashFile(filePath) {
|
|
526
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
527
|
+
return hashString(fs3.readFileSync(filePath, "utf8"));
|
|
528
|
+
}
|
|
529
|
+
function computeComponentHashes(componentDir) {
|
|
530
|
+
const manifestHash = hashFile(path3.join(componentDir, "manifest.json"));
|
|
531
|
+
if (manifestHash === null) {
|
|
532
|
+
throw new Error(`Missing manifest.json in ${componentDir}`);
|
|
523
533
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
534
|
+
return {
|
|
535
|
+
manifestHash,
|
|
536
|
+
sourceHash: hashFile(path3.join(componentDir, "component.tsx"))
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function readSyncState(componentDir) {
|
|
540
|
+
const file = path3.join(componentDir, SYNC_FILE);
|
|
541
|
+
if (!fs3.existsSync(file)) return null;
|
|
542
|
+
try {
|
|
543
|
+
const parsed = JSON.parse(fs3.readFileSync(file, "utf8"));
|
|
544
|
+
if (typeof parsed.version === "number" && typeof parsed.manifestHash === "string" && typeof parsed.sourceHash === "string") {
|
|
545
|
+
return {
|
|
546
|
+
version: parsed.version,
|
|
547
|
+
manifestHash: parsed.manifestHash,
|
|
548
|
+
sourceHash: parsed.sourceHash
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function writeSyncState(componentDir, state) {
|
|
530
557
|
fs3.writeFileSync(
|
|
531
|
-
path3.join(
|
|
532
|
-
JSON.stringify(
|
|
558
|
+
path3.join(componentDir, SYNC_FILE),
|
|
559
|
+
JSON.stringify(state, null, 2) + "\n"
|
|
533
560
|
);
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
561
|
+
}
|
|
562
|
+
function hasLocalChanges(componentDir, state) {
|
|
563
|
+
const hashes = safeComputeHashes(componentDir);
|
|
564
|
+
if (!hashes) return false;
|
|
565
|
+
if (state === null) return true;
|
|
566
|
+
if (hashes.manifestHash !== state.manifestHash) return true;
|
|
567
|
+
const localSource = hashes.sourceHash ?? "";
|
|
568
|
+
const storedSource = state.sourceHash ?? "";
|
|
569
|
+
return localSource !== storedSource;
|
|
570
|
+
}
|
|
571
|
+
function safeComputeHashes(componentDir) {
|
|
572
|
+
try {
|
|
573
|
+
return computeComponentHashes(componentDir);
|
|
574
|
+
} catch {
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/commands/pull.ts
|
|
580
|
+
async function pullCommand(args) {
|
|
581
|
+
const slug = requireProjectStore();
|
|
582
|
+
const force = flagBool(args.flags, "force");
|
|
583
|
+
const target = args.positional[0];
|
|
584
|
+
const root = process.cwd();
|
|
585
|
+
if (!target || target === "*") {
|
|
586
|
+
return pullAll({ slug, root, args, force });
|
|
587
|
+
}
|
|
588
|
+
ensureComponentsDir(root);
|
|
589
|
+
const outcome = await pullOne({
|
|
590
|
+
slug,
|
|
591
|
+
componentDir: path4.join(root, "components", target),
|
|
592
|
+
name: target,
|
|
593
|
+
revisionQuery: buildRevisionQuery(args),
|
|
594
|
+
force
|
|
595
|
+
});
|
|
596
|
+
printOutcome(outcome, force);
|
|
597
|
+
if (outcome.status === "failed") process.exitCode = 1;
|
|
598
|
+
}
|
|
599
|
+
async function pullAll(opts) {
|
|
600
|
+
const { slug, root, args, force } = opts;
|
|
601
|
+
if (flagString(args.flags, "version")) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
"--version is per-component; pass it together with `gs pull <name>`."
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
ensureComponentsDir(root);
|
|
607
|
+
const revisionQuery = buildRevisionQuery(args);
|
|
608
|
+
const listUrl = `${apiBaseFor(slug)}/api/builder/components`;
|
|
609
|
+
const list = await request(listUrl);
|
|
610
|
+
if (list.components.length === 0) {
|
|
611
|
+
process.stdout.write(`(no components in ${slug})
|
|
612
|
+
`);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const outcomes = [];
|
|
616
|
+
for (const entry of list.components) {
|
|
617
|
+
const componentDir = path4.join(root, "components", entry.name);
|
|
618
|
+
const outcome = await pullOne({
|
|
619
|
+
slug,
|
|
620
|
+
componentDir,
|
|
621
|
+
name: entry.name,
|
|
622
|
+
revisionQuery,
|
|
623
|
+
force
|
|
624
|
+
});
|
|
625
|
+
outcomes.push(outcome);
|
|
626
|
+
printOutcome(outcome, force);
|
|
539
627
|
}
|
|
540
|
-
const
|
|
628
|
+
const pulled = outcomes.filter((o) => o.status === "pulled").length;
|
|
629
|
+
const skipped = outcomes.filter((o) => o.status === "skipped").length;
|
|
630
|
+
const failed = outcomes.filter((o) => o.status === "failed").length;
|
|
631
|
+
const summary = [`${pulled} pulled`, `${skipped} skipped`];
|
|
632
|
+
if (failed > 0) summary.push(`${failed} failed`);
|
|
541
633
|
process.stdout.write(
|
|
542
|
-
`
|
|
634
|
+
`
|
|
635
|
+
${summary.join(", ")} (of ${outcomes.length}) \u2190 ${slug}
|
|
543
636
|
`
|
|
544
637
|
);
|
|
638
|
+
if (failed > 0) process.exitCode = 1;
|
|
639
|
+
}
|
|
640
|
+
async function pullOne(opts) {
|
|
641
|
+
const { slug, componentDir, name, revisionQuery, force } = opts;
|
|
642
|
+
if (!force && fs4.existsSync(componentDir)) {
|
|
643
|
+
const sync = readSyncState(componentDir);
|
|
644
|
+
if (hasLocalChanges(componentDir, sync)) {
|
|
645
|
+
return {
|
|
646
|
+
name,
|
|
647
|
+
status: "skipped",
|
|
648
|
+
message: "local has uncommitted changes; pass --force to overwrite"
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}${revisionQuery}`;
|
|
653
|
+
let data;
|
|
654
|
+
try {
|
|
655
|
+
data = await request(url);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
return {
|
|
658
|
+
name,
|
|
659
|
+
status: "failed",
|
|
660
|
+
message: err instanceof Error ? err.message : String(err)
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
fs4.mkdirSync(componentDir, { recursive: true });
|
|
664
|
+
const manifestText = JSON.stringify(data.manifest, null, 2) + "\n";
|
|
665
|
+
fs4.writeFileSync(path4.join(componentDir, "manifest.json"), manifestText);
|
|
666
|
+
fs4.writeFileSync(path4.join(componentDir, "bundle.js"), data.bundle);
|
|
667
|
+
const wrote = ["manifest.json", "bundle.js"];
|
|
668
|
+
let sourceHash = "";
|
|
669
|
+
if (data.source !== null) {
|
|
670
|
+
fs4.writeFileSync(path4.join(componentDir, "component.tsx"), data.source);
|
|
671
|
+
wrote.push("component.tsx");
|
|
672
|
+
sourceHash = hashString(data.source);
|
|
673
|
+
}
|
|
674
|
+
writeSyncState(componentDir, {
|
|
675
|
+
version: data.version,
|
|
676
|
+
manifestHash: hashString(manifestText),
|
|
677
|
+
sourceHash
|
|
678
|
+
});
|
|
679
|
+
const onDisk = computeComponentHashes(componentDir);
|
|
680
|
+
writeSyncState(componentDir, {
|
|
681
|
+
version: data.version,
|
|
682
|
+
manifestHash: onDisk.manifestHash,
|
|
683
|
+
sourceHash: onDisk.sourceHash ?? ""
|
|
684
|
+
});
|
|
685
|
+
return { name, status: "pulled", version: data.version, wrote };
|
|
686
|
+
}
|
|
687
|
+
function ensureComponentsDir(root) {
|
|
688
|
+
fs4.mkdirSync(path4.join(root, "components"), { recursive: true });
|
|
545
689
|
}
|
|
546
690
|
function buildRevisionQuery(args) {
|
|
547
691
|
const version = flagString(args.flags, "version");
|
|
@@ -550,65 +694,227 @@ function buildRevisionQuery(args) {
|
|
|
550
694
|
if (flagBool(args.flags, "live")) return `?revision=live`;
|
|
551
695
|
return "";
|
|
552
696
|
}
|
|
697
|
+
function printOutcome(outcome, force) {
|
|
698
|
+
switch (outcome.status) {
|
|
699
|
+
case "pulled": {
|
|
700
|
+
const files = outcome.wrote ? `{${outcome.wrote.join(", ")}}` : "";
|
|
701
|
+
process.stdout.write(
|
|
702
|
+
` pulled ${outcome.name} v${outcome.version} ${files}
|
|
703
|
+
`
|
|
704
|
+
);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
case "skipped":
|
|
708
|
+
process.stdout.write(` skipped ${outcome.name}: ${outcome.message}
|
|
709
|
+
`);
|
|
710
|
+
return;
|
|
711
|
+
case "failed":
|
|
712
|
+
process.stdout.write(
|
|
713
|
+
` failed ${outcome.name}: ${outcome.message ?? "unknown error"}
|
|
714
|
+
`
|
|
715
|
+
);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
void force;
|
|
719
|
+
}
|
|
553
720
|
|
|
554
721
|
// src/commands/push.ts
|
|
555
|
-
import * as
|
|
556
|
-
import * as
|
|
722
|
+
import * as fs5 from "fs";
|
|
723
|
+
import * as path5 from "path";
|
|
557
724
|
async function pushCommand(args) {
|
|
558
|
-
const slug =
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
const
|
|
562
|
-
if (!
|
|
563
|
-
throw new Error(
|
|
725
|
+
const slug = requireProjectStore();
|
|
726
|
+
const root = process.cwd();
|
|
727
|
+
rejectLegacyLayout(root);
|
|
728
|
+
const componentsDir = path5.join(root, "components");
|
|
729
|
+
if (!fs5.existsSync(componentsDir)) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
"No components/ directory here. Run `gs init <name>` to scaffold the project root and your first component."
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const named = args.positional[0];
|
|
735
|
+
const manifestOverride = flagString(args.flags, "manifest");
|
|
736
|
+
const bundleOverride = flagString(args.flags, "bundle");
|
|
737
|
+
if (!named && (manifestOverride || bundleOverride)) {
|
|
738
|
+
throw new Error(
|
|
739
|
+
"--manifest / --bundle can only be used together with `gs push <name>`."
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
if (named) {
|
|
743
|
+
const outcome = await pushOne({
|
|
744
|
+
slug,
|
|
745
|
+
root,
|
|
746
|
+
name: named,
|
|
747
|
+
...manifestOverride !== void 0 ? { manifestPath: manifestOverride } : {},
|
|
748
|
+
...bundleOverride !== void 0 ? { bundlePath: bundleOverride } : {},
|
|
749
|
+
force: true
|
|
750
|
+
});
|
|
751
|
+
printOutcome2(outcome);
|
|
752
|
+
if (outcome.status === "failed") process.exitCode = 1;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
const names = listLocalComponents(componentsDir);
|
|
756
|
+
if (names.length === 0) {
|
|
757
|
+
process.stdout.write(
|
|
758
|
+
"(no components in ./components \u2014 run `gs init <name>` to add one)\n"
|
|
759
|
+
);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const outcomes = [];
|
|
763
|
+
for (const name of names) {
|
|
764
|
+
const outcome = await pushOne({ slug, root, name, force: false });
|
|
765
|
+
outcomes.push(outcome);
|
|
766
|
+
printOutcome2(outcome);
|
|
767
|
+
}
|
|
768
|
+
const pushed = outcomes.filter((o) => o.status === "pushed").length;
|
|
769
|
+
const unchanged = outcomes.filter((o) => o.status === "unchanged").length;
|
|
770
|
+
const failed = outcomes.filter((o) => o.status === "failed").length;
|
|
771
|
+
const summary = [`${pushed} pushed`, `${unchanged} unchanged`];
|
|
772
|
+
if (failed > 0) summary.push(`${failed} failed`);
|
|
773
|
+
process.stdout.write(`
|
|
774
|
+
${summary.join(", ")} (of ${outcomes.length}) \u2192 ${slug}
|
|
775
|
+
`);
|
|
776
|
+
if (failed > 0) process.exitCode = 1;
|
|
777
|
+
}
|
|
778
|
+
async function pushOne(opts) {
|
|
779
|
+
const { slug, root, name, force } = opts;
|
|
780
|
+
const componentDir = path5.join(root, "components", name);
|
|
781
|
+
if (!fs5.existsSync(componentDir)) {
|
|
782
|
+
return {
|
|
783
|
+
name,
|
|
784
|
+
status: "failed",
|
|
785
|
+
message: `components/${name}/ does not exist`
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
const manifestPath = opts.manifestPath ? path5.resolve(opts.manifestPath) : path5.join(componentDir, "manifest.json");
|
|
789
|
+
const bundlePath = opts.bundlePath ? path5.resolve(opts.bundlePath) : path5.join(componentDir, "bundle.js");
|
|
790
|
+
const sourcePath = path5.join(componentDir, "component.tsx");
|
|
791
|
+
if (!fs5.existsSync(manifestPath)) {
|
|
792
|
+
return { name, status: "failed", message: `manifest not found: ${manifestPath}` };
|
|
564
793
|
}
|
|
565
|
-
if (!
|
|
566
|
-
|
|
794
|
+
if (!fs5.existsSync(bundlePath)) {
|
|
795
|
+
return {
|
|
796
|
+
name,
|
|
797
|
+
status: "failed",
|
|
798
|
+
message: `bundle not found: ${bundlePath} (did you run \`npm run build\`?)`
|
|
799
|
+
};
|
|
567
800
|
}
|
|
568
|
-
const manifestText =
|
|
801
|
+
const manifestText = fs5.readFileSync(manifestPath, "utf8");
|
|
569
802
|
let manifestName;
|
|
570
803
|
try {
|
|
571
804
|
const parsed = JSON.parse(manifestText);
|
|
572
805
|
if (typeof parsed.name === "string") manifestName = parsed.name;
|
|
573
806
|
} catch (err) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
807
|
+
return {
|
|
808
|
+
name,
|
|
809
|
+
status: "failed",
|
|
810
|
+
message: `manifest is not valid JSON: ${err.message}`
|
|
811
|
+
};
|
|
579
812
|
}
|
|
580
813
|
if (manifestName && manifestName !== name) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
814
|
+
return {
|
|
815
|
+
name,
|
|
816
|
+
status: "failed",
|
|
817
|
+
message: `manifest name "${manifestName}" does not match folder name "${name}"`
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
if (!force) {
|
|
821
|
+
const hashes2 = computeComponentHashes(componentDir);
|
|
822
|
+
const sync = readSyncState(componentDir);
|
|
823
|
+
const unchanged = sync !== null && sync.manifestHash === hashes2.manifestHash && sync.sourceHash === (hashes2.sourceHash ?? "");
|
|
824
|
+
if (unchanged) {
|
|
825
|
+
return { name, status: "unchanged" };
|
|
826
|
+
}
|
|
584
827
|
}
|
|
585
|
-
const bundleText =
|
|
586
|
-
const sourceText =
|
|
828
|
+
const bundleText = fs5.readFileSync(bundlePath, "utf8");
|
|
829
|
+
const sourceText = fs5.existsSync(sourcePath) ? fs5.readFileSync(sourcePath, "utf8") : null;
|
|
587
830
|
const form = new FormData();
|
|
588
|
-
form.append(
|
|
589
|
-
|
|
831
|
+
form.append(
|
|
832
|
+
"manifest",
|
|
833
|
+
new Blob([manifestText], { type: "application/json" }),
|
|
834
|
+
"manifest.json"
|
|
835
|
+
);
|
|
836
|
+
form.append(
|
|
837
|
+
"bundle",
|
|
838
|
+
new Blob([bundleText], { type: "text/javascript" }),
|
|
839
|
+
"bundle.js"
|
|
840
|
+
);
|
|
590
841
|
if (sourceText !== null) {
|
|
591
842
|
form.append(
|
|
592
843
|
"source",
|
|
593
844
|
new Blob([sourceText], { type: "text/plain; charset=utf-8" }),
|
|
594
|
-
|
|
845
|
+
"component.tsx"
|
|
595
846
|
);
|
|
596
847
|
}
|
|
597
848
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}`;
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
849
|
+
let data;
|
|
850
|
+
try {
|
|
851
|
+
data = await request(url, { method: "POST", multipart: form });
|
|
852
|
+
} catch (err) {
|
|
853
|
+
return {
|
|
854
|
+
name,
|
|
855
|
+
status: "failed",
|
|
856
|
+
message: err instanceof Error ? err.message : String(err)
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
const hashes = computeComponentHashes(componentDir);
|
|
860
|
+
writeSyncState(componentDir, {
|
|
861
|
+
version: data.version,
|
|
862
|
+
manifestHash: hashes.manifestHash,
|
|
863
|
+
sourceHash: hashes.sourceHash ?? ""
|
|
864
|
+
});
|
|
865
|
+
return {
|
|
866
|
+
name,
|
|
867
|
+
status: "pushed",
|
|
868
|
+
version: data.version,
|
|
869
|
+
permalink: data.permalink
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
function listLocalComponents(componentsDir) {
|
|
873
|
+
return fs5.readdirSync(componentsDir).filter((entry) => {
|
|
874
|
+
const dir = path5.join(componentsDir, entry);
|
|
875
|
+
return fs5.statSync(dir).isDirectory() && fs5.existsSync(path5.join(dir, "manifest.json"));
|
|
876
|
+
}).sort();
|
|
877
|
+
}
|
|
878
|
+
function rejectLegacyLayout(root) {
|
|
879
|
+
const rootManifest = path5.join(root, "manifest.json");
|
|
880
|
+
const componentsDir = path5.join(root, "components");
|
|
881
|
+
if (fs5.existsSync(rootManifest) && !fs5.existsSync(componentsDir)) {
|
|
882
|
+
throw new Error(
|
|
883
|
+
[
|
|
884
|
+
"Detected the old single-component layout (manifest.json at the project root).",
|
|
885
|
+
"The Builder CLI now uses a multi-component layout: scaffold a fresh dir with",
|
|
886
|
+
"`gs init`, then move your component into `components/<name>/`. Re-run `gs push`",
|
|
887
|
+
"from the new project root."
|
|
888
|
+
].join(" ")
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function printOutcome2(outcome) {
|
|
893
|
+
switch (outcome.status) {
|
|
894
|
+
case "pushed":
|
|
895
|
+
process.stdout.write(
|
|
896
|
+
` pushed ${outcome.name} v${outcome.version} \u2192 ${outcome.permalink ?? ""}
|
|
603
897
|
`
|
|
604
|
-
|
|
898
|
+
);
|
|
899
|
+
return;
|
|
900
|
+
case "unchanged":
|
|
901
|
+
process.stdout.write(` unchanged ${outcome.name}
|
|
902
|
+
`);
|
|
903
|
+
return;
|
|
904
|
+
case "failed":
|
|
905
|
+
process.stdout.write(
|
|
906
|
+
` failed ${outcome.name}: ${outcome.message ?? "unknown error"}
|
|
907
|
+
`
|
|
908
|
+
);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
605
911
|
}
|
|
606
912
|
|
|
607
913
|
// src/commands/publish.ts
|
|
608
914
|
async function publishCommand(args) {
|
|
609
915
|
const name = args.positional[0];
|
|
610
916
|
if (!name) throw new Error("Usage: gs publish <name> [--version N]");
|
|
611
|
-
const slug =
|
|
917
|
+
const slug = requireProjectStore();
|
|
612
918
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/publish`;
|
|
613
919
|
const versionFlag = flagString(args.flags, "version");
|
|
614
920
|
let body = void 0;
|
|
@@ -632,7 +938,7 @@ async function publishCommand(args) {
|
|
|
632
938
|
async function unpublishCommand(args) {
|
|
633
939
|
const name = args.positional[0];
|
|
634
940
|
if (!name) throw new Error("Usage: gs unpublish <name>");
|
|
635
|
-
const slug =
|
|
941
|
+
const slug = requireProjectStore();
|
|
636
942
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/unpublish`;
|
|
637
943
|
await request(url, { method: "POST", body: {} });
|
|
638
944
|
process.stdout.write(`Unpublished ${name}. The draft and version history are retained.
|
|
@@ -644,7 +950,7 @@ import * as readline from "readline";
|
|
|
644
950
|
async function deleteCommand(args) {
|
|
645
951
|
const name = args.positional[0];
|
|
646
952
|
if (!name) throw new Error("Usage: gs delete <name> [--yes]");
|
|
647
|
-
const slug =
|
|
953
|
+
const slug = requireProjectStore();
|
|
648
954
|
if (!flagBool(args.flags, "yes")) {
|
|
649
955
|
const confirmed = await prompt(
|
|
650
956
|
`Soft-delete component "${name}" in store "${slug}"? Type "yes" to confirm: `
|
|
@@ -660,71 +966,116 @@ async function deleteCommand(args) {
|
|
|
660
966
|
`);
|
|
661
967
|
}
|
|
662
968
|
function prompt(question) {
|
|
663
|
-
return new Promise((
|
|
969
|
+
return new Promise((resolve4) => {
|
|
664
970
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
665
971
|
rl.question(question, (answer) => {
|
|
666
972
|
rl.close();
|
|
667
|
-
|
|
973
|
+
resolve4(answer);
|
|
668
974
|
});
|
|
669
975
|
});
|
|
670
976
|
}
|
|
671
977
|
|
|
672
978
|
// src/commands/init.ts
|
|
673
|
-
import * as
|
|
674
|
-
import * as
|
|
979
|
+
import * as fs6 from "fs";
|
|
980
|
+
import * as path6 from "path";
|
|
675
981
|
var NAME_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
676
982
|
function initCommand(args) {
|
|
677
983
|
const name = args.positional[0];
|
|
678
|
-
if (!name) {
|
|
679
|
-
throw new Error("Usage: gs init <name> [--out <dir>] [--store <slug>] [--force]");
|
|
680
|
-
}
|
|
681
|
-
if (!NAME_REGEX.test(name)) {
|
|
984
|
+
if (name !== void 0 && !NAME_REGEX.test(name)) {
|
|
682
985
|
throw new Error(`Invalid component name: "${name}" (must match ${NAME_REGEX}).`);
|
|
683
986
|
}
|
|
684
|
-
const outRel = flagString(args.flags, "out") ?? name;
|
|
685
|
-
const out = path5.resolve(outRel);
|
|
686
987
|
const force = flagBool(args.flags, "force");
|
|
687
988
|
const storeFlag = flagString(args.flags, "store");
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
989
|
+
const outRel = flagString(args.flags, "out") ?? ".";
|
|
990
|
+
const root = path6.resolve(outRel);
|
|
991
|
+
const rootExisted = hasRootScaffold(root);
|
|
992
|
+
ensureRoot(root, rootExisted, force, { store: storeFlag });
|
|
993
|
+
if (!name) {
|
|
994
|
+
const created = rootExisted ? "Updated" : "Scaffolded";
|
|
995
|
+
process.stdout.write(
|
|
996
|
+
[
|
|
997
|
+
`${created} GreatStore project root in ${root}.`,
|
|
998
|
+
"",
|
|
999
|
+
"Next steps:",
|
|
1000
|
+
` cd ${path6.relative(process.cwd(), root) || "."}`,
|
|
1001
|
+
" npm install",
|
|
1002
|
+
" gs init <component_name> # add your first component",
|
|
1003
|
+
""
|
|
1004
|
+
].join("\n")
|
|
1005
|
+
);
|
|
1006
|
+
return;
|
|
694
1007
|
}
|
|
1008
|
+
const componentDir = path6.join(root, "components", name);
|
|
1009
|
+
ensureComponent(componentDir, name, force);
|
|
1010
|
+
const projectLabel = path6.relative(process.cwd(), root) || ".";
|
|
695
1011
|
process.stdout.write(
|
|
696
1012
|
[
|
|
697
|
-
`
|
|
1013
|
+
`Added component "${name}" at components/${name}/.`,
|
|
698
1014
|
"",
|
|
699
1015
|
"Next steps:",
|
|
700
|
-
` cd ${
|
|
701
|
-
|
|
1016
|
+
...rootExisted ? [] : [` cd ${projectLabel}`, " npm install"],
|
|
1017
|
+
` # edit components/${name}/component.tsx and manifest.json`,
|
|
702
1018
|
" npm run build",
|
|
703
1019
|
" gs push",
|
|
704
1020
|
""
|
|
705
1021
|
].join("\n")
|
|
706
1022
|
);
|
|
707
1023
|
}
|
|
708
|
-
function
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1024
|
+
function hasRootScaffold(dir) {
|
|
1025
|
+
return fs6.existsSync(path6.join(dir, "package.json")) && fs6.existsSync(path6.join(dir, "build.mjs"));
|
|
1026
|
+
}
|
|
1027
|
+
function ensureRoot(root, rootExisted, force, opts) {
|
|
1028
|
+
fs6.mkdirSync(root, { recursive: true });
|
|
1029
|
+
if (rootExisted) {
|
|
1030
|
+
fs6.mkdirSync(path6.join(root, "components"), { recursive: true });
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (!force) {
|
|
1034
|
+
const entries = fs6.readdirSync(root).filter((e) => e !== ".gsrc");
|
|
1035
|
+
if (entries.length > 0) {
|
|
1036
|
+
throw new Error(
|
|
1037
|
+
`Refusing to scaffold project root into non-empty directory ${root}. Pass --force to override.`
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
for (const [relPath, content] of rootFiles({ store: opts.store })) {
|
|
1042
|
+
const full = path6.join(root, relPath);
|
|
1043
|
+
fs6.mkdirSync(path6.dirname(full), { recursive: true });
|
|
1044
|
+
if (force || !fs6.existsSync(full)) {
|
|
1045
|
+
fs6.writeFileSync(full, content);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
fs6.mkdirSync(path6.join(root, "components"), { recursive: true });
|
|
716
1049
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
1050
|
+
function ensureComponent(componentDir, name, force) {
|
|
1051
|
+
if (fs6.existsSync(componentDir) && !force) {
|
|
1052
|
+
const entries = fs6.readdirSync(componentDir);
|
|
1053
|
+
if (entries.length > 0) {
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
`Refusing to overwrite existing components/${name}/. Pass --force to override.`
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
fs6.mkdirSync(componentDir, { recursive: true });
|
|
1060
|
+
for (const [relPath, content] of componentFiles(name)) {
|
|
1061
|
+
fs6.writeFileSync(path6.join(componentDir, relPath), content);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
function rootFiles(opts) {
|
|
719
1065
|
return [
|
|
720
|
-
["
|
|
721
|
-
["
|
|
722
|
-
["vite.config.ts",
|
|
723
|
-
["tsconfig.json",
|
|
724
|
-
["
|
|
725
|
-
[".gsrc", gsrc(store)],
|
|
1066
|
+
["package.json", rootPackageJson()],
|
|
1067
|
+
["build.mjs", buildScript()],
|
|
1068
|
+
["vite.config.ts", viteEditorConfig()],
|
|
1069
|
+
["tsconfig.json", rootTsconfig()],
|
|
1070
|
+
[".gsrc", gsrc(opts.store)],
|
|
726
1071
|
[".gitignore", gitignore()],
|
|
727
|
-
["README.md",
|
|
1072
|
+
["README.md", rootReadme()]
|
|
1073
|
+
];
|
|
1074
|
+
}
|
|
1075
|
+
function componentFiles(name) {
|
|
1076
|
+
return [
|
|
1077
|
+
["manifest.json", manifest(name)],
|
|
1078
|
+
["component.tsx", component(name)]
|
|
728
1079
|
];
|
|
729
1080
|
}
|
|
730
1081
|
function manifest(name) {
|
|
@@ -765,51 +1116,86 @@ export default function ${pascal(name)}(_props: Props): React.ReactElement {
|
|
|
765
1116
|
}
|
|
766
1117
|
`;
|
|
767
1118
|
}
|
|
768
|
-
function
|
|
769
|
-
return `import {
|
|
1119
|
+
function buildScript() {
|
|
1120
|
+
return `import { build } from "vite";
|
|
770
1121
|
import react from "@vitejs/plugin-react";
|
|
1122
|
+
import * as fs from "node:fs";
|
|
1123
|
+
import * as path from "node:path";
|
|
771
1124
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1125
|
+
const COMPONENTS_DIR = path.resolve("components");
|
|
1126
|
+
|
|
1127
|
+
function listComponents() {
|
|
1128
|
+
if (!fs.existsSync(COMPONENTS_DIR)) return [];
|
|
1129
|
+
return fs
|
|
1130
|
+
.readdirSync(COMPONENTS_DIR)
|
|
1131
|
+
.filter((entry) => {
|
|
1132
|
+
const dir = path.join(COMPONENTS_DIR, entry);
|
|
1133
|
+
return (
|
|
1134
|
+
fs.statSync(dir).isDirectory() &&
|
|
1135
|
+
fs.existsSync(path.join(dir, "component.tsx"))
|
|
1136
|
+
);
|
|
1137
|
+
})
|
|
1138
|
+
.sort();
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const components = listComponents();
|
|
1142
|
+
if (components.length === 0) {
|
|
1143
|
+
console.log("(no components in ./components \u2014 run \`gs init <name>\` to add one)");
|
|
1144
|
+
process.exit(0);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const target = process.argv[2];
|
|
1148
|
+
const queue = target ? components.filter((c) => c === target) : components;
|
|
1149
|
+
if (target && queue.length === 0) {
|
|
1150
|
+
console.error(\`No component named "\${target}" in ./components\`);
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
for (const name of queue) {
|
|
1155
|
+
const dir = path.join(COMPONENTS_DIR, name);
|
|
1156
|
+
await build({
|
|
1157
|
+
plugins: [react()],
|
|
1158
|
+
logLevel: "warn",
|
|
1159
|
+
build: {
|
|
1160
|
+
lib: {
|
|
1161
|
+
entry: path.join(dir, "component.tsx"),
|
|
1162
|
+
formats: ["es"],
|
|
1163
|
+
fileName: () => "bundle.js",
|
|
1164
|
+
},
|
|
1165
|
+
outDir: dir,
|
|
1166
|
+
emptyOutDir: false,
|
|
1167
|
+
rollupOptions: {
|
|
1168
|
+
external: ["react", "react-dom", "react/jsx-runtime"],
|
|
1169
|
+
output: {
|
|
1170
|
+
entryFileNames: "bundle.js",
|
|
1171
|
+
paths: {
|
|
1172
|
+
react: "/assets/remote-components/_runtime.js",
|
|
1173
|
+
"react-dom": "/assets/remote-components/_runtime.js",
|
|
1174
|
+
"react/jsx-runtime": "/assets/remote-components/_runtime.js",
|
|
1175
|
+
},
|
|
803
1176
|
},
|
|
804
1177
|
},
|
|
1178
|
+
minify: true,
|
|
1179
|
+
sourcemap: false,
|
|
805
1180
|
},
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1181
|
+
});
|
|
1182
|
+
console.log(\`built components/\${name}/bundle.js\`);
|
|
1183
|
+
}
|
|
1184
|
+
`;
|
|
1185
|
+
}
|
|
1186
|
+
function viteEditorConfig() {
|
|
1187
|
+
return `import { defineConfig } from "vite";
|
|
1188
|
+
import react from "@vitejs/plugin-react";
|
|
1189
|
+
|
|
1190
|
+
// Real builds happen in build.mjs (one Vite invocation per component).
|
|
1191
|
+
// This file exists only so editors / language servers can resolve the
|
|
1192
|
+
// React plugin when inspecting components/*/component.tsx.
|
|
1193
|
+
export default defineConfig({
|
|
1194
|
+
plugins: [react()],
|
|
809
1195
|
});
|
|
810
1196
|
`;
|
|
811
1197
|
}
|
|
812
|
-
function
|
|
1198
|
+
function rootTsconfig() {
|
|
813
1199
|
return JSON.stringify(
|
|
814
1200
|
{
|
|
815
1201
|
compilerOptions: {
|
|
@@ -824,22 +1210,22 @@ function tsconfig() {
|
|
|
824
1210
|
isolatedModules: true,
|
|
825
1211
|
noEmit: true
|
|
826
1212
|
},
|
|
827
|
-
include: ["component.tsx", "vite.config.ts"]
|
|
1213
|
+
include: ["components/**/component.tsx", "vite.config.ts", "build.mjs"]
|
|
828
1214
|
},
|
|
829
1215
|
null,
|
|
830
1216
|
2
|
|
831
1217
|
) + "\n";
|
|
832
1218
|
}
|
|
833
|
-
function
|
|
1219
|
+
function rootPackageJson() {
|
|
834
1220
|
return JSON.stringify(
|
|
835
1221
|
{
|
|
836
|
-
name,
|
|
1222
|
+
name: "greatstore-components",
|
|
837
1223
|
version: "0.0.1",
|
|
838
1224
|
private: true,
|
|
839
1225
|
type: "module",
|
|
840
1226
|
scripts: {
|
|
841
|
-
build: "
|
|
842
|
-
push: "
|
|
1227
|
+
build: "node build.mjs",
|
|
1228
|
+
push: "node build.mjs && gs push"
|
|
843
1229
|
},
|
|
844
1230
|
dependencies: {
|
|
845
1231
|
react: "^19.0.0",
|
|
@@ -861,22 +1247,34 @@ function gsrc(store) {
|
|
|
861
1247
|
return JSON.stringify({ store: store ?? "<your-store-slug>" }, null, 2) + "\n";
|
|
862
1248
|
}
|
|
863
1249
|
function gitignore() {
|
|
864
|
-
return [
|
|
1250
|
+
return [
|
|
1251
|
+
"node_modules/",
|
|
1252
|
+
"components/*/bundle.js",
|
|
1253
|
+
"components/*/.gssync.json",
|
|
1254
|
+
"*.tsbuildinfo",
|
|
1255
|
+
".DS_Store",
|
|
1256
|
+
""
|
|
1257
|
+
].join("\n");
|
|
865
1258
|
}
|
|
866
|
-
function
|
|
867
|
-
return `#
|
|
1259
|
+
function rootReadme() {
|
|
1260
|
+
return `# GreatStore components
|
|
868
1261
|
|
|
869
|
-
|
|
1262
|
+
Custom React components published to your GreatStore tenant. Each
|
|
1263
|
+
component lives in its own folder under \`components/\`.
|
|
870
1264
|
|
|
871
1265
|
\`\`\`
|
|
872
1266
|
npm install
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
gs
|
|
1267
|
+
gs init <component_name> # add a new component
|
|
1268
|
+
npm run build # builds every component/<name>/bundle.js
|
|
1269
|
+
gs push # uploads every changed component as a draft
|
|
1270
|
+
gs publish <component_name> # promote a specific component to live
|
|
876
1271
|
\`\`\`
|
|
877
1272
|
|
|
878
|
-
|
|
879
|
-
that
|
|
1273
|
+
- \`gs push\` (no args) hashes each component and only uploads the ones
|
|
1274
|
+
that have changed since the last sync.
|
|
1275
|
+
- \`gs pull\` (no args) refreshes every remote component into
|
|
1276
|
+
\`components/<name>/\`. Components with unsaved local edits are skipped
|
|
1277
|
+
with a warning; pass \`--force\` to overwrite.
|
|
880
1278
|
`;
|
|
881
1279
|
}
|
|
882
1280
|
function pascal(name) {
|
|
@@ -884,7 +1282,7 @@ function pascal(name) {
|
|
|
884
1282
|
}
|
|
885
1283
|
|
|
886
1284
|
// src/index.ts
|
|
887
|
-
var VERSION = true ? "0.0.
|
|
1285
|
+
var VERSION = true ? "0.0.11-beta.0" : "0.0.0-dev";
|
|
888
1286
|
var HELP = `gs \u2014 GreatStore CLI (v${VERSION})
|
|
889
1287
|
|
|
890
1288
|
Usage:
|
|
@@ -894,28 +1292,27 @@ Commands:
|
|
|
894
1292
|
login Open browser, capture nav token, persist credentials.
|
|
895
1293
|
logout Wipe ~/.greatstore/credentials.json.
|
|
896
1294
|
whoami Print the identity stored locally.
|
|
897
|
-
init <name>
|
|
1295
|
+
init [<name>] Scaffold project root; add \`components/<name>/\` if name given.
|
|
898
1296
|
list List components in the current store.
|
|
899
|
-
pull <name
|
|
900
|
-
push [<name>] Upload
|
|
1297
|
+
pull [<name>|*] Download remote component(s) into \`components/<name>/\`. No args = all.
|
|
1298
|
+
push [<name>] Upload changed components from \`components/\`. No args = all (skips unchanged).
|
|
901
1299
|
publish <name> Promote the draft (or --version N) to live.
|
|
902
1300
|
unpublish <name> Clear the live pointer.
|
|
903
1301
|
delete <name> Soft-delete the component.
|
|
904
1302
|
|
|
905
1303
|
Common flags:
|
|
906
|
-
--store <slug>
|
|
1304
|
+
--store <slug> \`init\` only \u2014 writes the slug into .gsrc. Other commands read .gsrc.
|
|
907
1305
|
--json Machine-readable output for \`list\`.
|
|
908
1306
|
--draft | --live | --version N Revision selector for \`pull\`.
|
|
909
1307
|
-o, --out <dir> Output directory for \`pull\` / \`init\`.
|
|
910
1308
|
--manifest <path> Path to manifest for \`push\`.
|
|
911
1309
|
--bundle <path> Path to bundle for \`push\`.
|
|
912
|
-
--force Overwrite for \`init\`; skip confirmation for \`delete\`.
|
|
1310
|
+
--force Overwrite for \`init\` / \`pull\`; skip confirmation for \`delete\`.
|
|
913
1311
|
--yes Skip confirmation for \`delete\`.
|
|
914
1312
|
-h, --help Show this help.
|
|
915
1313
|
-v, --version Print version.
|
|
916
1314
|
|
|
917
1315
|
Environment:
|
|
918
|
-
GS_STORE Fallback for --store.
|
|
919
1316
|
GS_API_BASE Override API base (full URL, no trailing slash).
|
|
920
1317
|
GS_NAV_BASE Override nav OAuth base.
|
|
921
1318
|
GS_BROWSER_CMD Override the browser-open command.
|