@greatstore/cli 0.0.10 → 0.0.11-beta.1
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 +568 -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
|
@@ -92,7 +92,7 @@ async function captureLoopbackToken(options) {
|
|
|
92
92
|
const open = options.openBrowser ?? ((url) => openInBrowser(url, options.browserCmdEnv));
|
|
93
93
|
const server = http.createServer();
|
|
94
94
|
try {
|
|
95
|
-
await new Promise((
|
|
95
|
+
await new Promise((resolve4) => server.listen(0, "127.0.0.1", resolve4));
|
|
96
96
|
const address = server.address();
|
|
97
97
|
const redirectUri = `http://127.0.0.1:${address.port}${CALLBACK_PATH}`;
|
|
98
98
|
const authUrl = `${options.navBaseUrl}/connect_oauth_done?redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}`;
|
|
@@ -104,7 +104,7 @@ async function captureLoopbackToken(options) {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
function waitForCallback(server, expectedState, timeoutMs) {
|
|
107
|
-
return new Promise((
|
|
107
|
+
return new Promise((resolve4, reject) => {
|
|
108
108
|
let settled = false;
|
|
109
109
|
const settle = (fn) => {
|
|
110
110
|
if (settled) return;
|
|
@@ -150,7 +150,7 @@ function waitForCallback(server, expectedState, timeoutMs) {
|
|
|
150
150
|
res.writeHead(200, { "content-type": "text/html" });
|
|
151
151
|
res.end(SUCCESS_HTML);
|
|
152
152
|
clearTimeout(timer);
|
|
153
|
-
settle(() =>
|
|
153
|
+
settle(() => resolve4({ token }));
|
|
154
154
|
});
|
|
155
155
|
});
|
|
156
156
|
}
|
|
@@ -273,16 +273,11 @@ var StoreResolutionError = class extends Error {
|
|
|
273
273
|
this.name = "StoreResolutionError";
|
|
274
274
|
}
|
|
275
275
|
};
|
|
276
|
-
function
|
|
277
|
-
const flag = input.flag?.trim();
|
|
278
|
-
if (flag) return flag;
|
|
276
|
+
function requireProjectStore(input = {}) {
|
|
279
277
|
const fromRc = findGsrc(input.cwd ?? process.cwd());
|
|
280
278
|
if (fromRc) return fromRc;
|
|
281
|
-
const env = input.env ?? process.env;
|
|
282
|
-
const fromEnv = env.GS_STORE?.trim();
|
|
283
|
-
if (fromEnv) return fromEnv;
|
|
284
279
|
throw new StoreResolutionError(
|
|
285
|
-
|
|
280
|
+
"No .gsrc found in this directory or any ancestor. Run `gs init --store <slug>` first to scaffold a GreatStore project."
|
|
286
281
|
);
|
|
287
282
|
}
|
|
288
283
|
function findGsrc(cwd) {
|
|
@@ -479,7 +474,7 @@ function whoamiCommand() {
|
|
|
479
474
|
|
|
480
475
|
// src/commands/list.ts
|
|
481
476
|
async function listCommand(args) {
|
|
482
|
-
const slug =
|
|
477
|
+
const slug = requireProjectStore();
|
|
483
478
|
const url = `${apiBaseFor(slug)}/api/builder/components`;
|
|
484
479
|
const data = await request(url);
|
|
485
480
|
if (flagBool(args.flags, "json")) {
|
|
@@ -516,34 +511,181 @@ function pad(s, width) {
|
|
|
516
511
|
}
|
|
517
512
|
|
|
518
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";
|
|
519
519
|
import * as fs3 from "fs";
|
|
520
520
|
import * as path3 from "path";
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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}`);
|
|
525
533
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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) {
|
|
532
557
|
fs3.writeFileSync(
|
|
533
|
-
path3.join(
|
|
534
|
-
JSON.stringify(
|
|
558
|
+
path3.join(componentDir, SYNC_FILE),
|
|
559
|
+
JSON.stringify(state, null, 2) + "\n"
|
|
535
560
|
);
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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 });
|
|
541
587
|
}
|
|
542
|
-
|
|
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);
|
|
627
|
+
}
|
|
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`);
|
|
543
633
|
process.stdout.write(
|
|
544
|
-
`
|
|
634
|
+
`
|
|
635
|
+
${summary.join(", ")} (of ${outcomes.length}) \u2190 ${slug}
|
|
545
636
|
`
|
|
546
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 });
|
|
547
689
|
}
|
|
548
690
|
function buildRevisionQuery(args) {
|
|
549
691
|
const version = flagString(args.flags, "version");
|
|
@@ -552,65 +694,227 @@ function buildRevisionQuery(args) {
|
|
|
552
694
|
if (flagBool(args.flags, "live")) return `?revision=live`;
|
|
553
695
|
return "";
|
|
554
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
|
+
}
|
|
555
720
|
|
|
556
721
|
// src/commands/push.ts
|
|
557
|
-
import * as
|
|
558
|
-
import * as
|
|
722
|
+
import * as fs5 from "fs";
|
|
723
|
+
import * as path5 from "path";
|
|
559
724
|
async function pushCommand(args) {
|
|
560
|
-
const slug =
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
if (!
|
|
565
|
-
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;
|
|
566
754
|
}
|
|
567
|
-
|
|
568
|
-
|
|
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;
|
|
569
761
|
}
|
|
570
|
-
const
|
|
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}` };
|
|
793
|
+
}
|
|
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
|
+
};
|
|
800
|
+
}
|
|
801
|
+
const manifestText = fs5.readFileSync(manifestPath, "utf8");
|
|
571
802
|
let manifestName;
|
|
572
803
|
try {
|
|
573
804
|
const parsed = JSON.parse(manifestText);
|
|
574
805
|
if (typeof parsed.name === "string") manifestName = parsed.name;
|
|
575
806
|
} catch (err) {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
807
|
+
return {
|
|
808
|
+
name,
|
|
809
|
+
status: "failed",
|
|
810
|
+
message: `manifest is not valid JSON: ${err.message}`
|
|
811
|
+
};
|
|
581
812
|
}
|
|
582
813
|
if (manifestName && manifestName !== name) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
+
}
|
|
586
827
|
}
|
|
587
|
-
const bundleText =
|
|
588
|
-
const sourceText =
|
|
828
|
+
const bundleText = fs5.readFileSync(bundlePath, "utf8");
|
|
829
|
+
const sourceText = fs5.existsSync(sourcePath) ? fs5.readFileSync(sourcePath, "utf8") : null;
|
|
589
830
|
const form = new FormData();
|
|
590
|
-
form.append(
|
|
591
|
-
|
|
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
|
+
);
|
|
592
841
|
if (sourceText !== null) {
|
|
593
842
|
form.append(
|
|
594
843
|
"source",
|
|
595
844
|
new Blob([sourceText], { type: "text/plain; charset=utf-8" }),
|
|
596
|
-
|
|
845
|
+
"component.tsx"
|
|
597
846
|
);
|
|
598
847
|
}
|
|
599
848
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}`;
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
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 ?? ""}
|
|
605
897
|
`
|
|
606
|
-
|
|
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
|
+
}
|
|
607
911
|
}
|
|
608
912
|
|
|
609
913
|
// src/commands/publish.ts
|
|
610
914
|
async function publishCommand(args) {
|
|
611
915
|
const name = args.positional[0];
|
|
612
916
|
if (!name) throw new Error("Usage: gs publish <name> [--version N]");
|
|
613
|
-
const slug =
|
|
917
|
+
const slug = requireProjectStore();
|
|
614
918
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/publish`;
|
|
615
919
|
const versionFlag = flagString(args.flags, "version");
|
|
616
920
|
let body = void 0;
|
|
@@ -634,7 +938,7 @@ async function publishCommand(args) {
|
|
|
634
938
|
async function unpublishCommand(args) {
|
|
635
939
|
const name = args.positional[0];
|
|
636
940
|
if (!name) throw new Error("Usage: gs unpublish <name>");
|
|
637
|
-
const slug =
|
|
941
|
+
const slug = requireProjectStore();
|
|
638
942
|
const url = `${apiBaseFor(slug)}/api/builder/components/${encodeURIComponent(name)}/unpublish`;
|
|
639
943
|
await request(url, { method: "POST", body: {} });
|
|
640
944
|
process.stdout.write(`Unpublished ${name}. The draft and version history are retained.
|
|
@@ -646,7 +950,7 @@ import * as readline from "readline";
|
|
|
646
950
|
async function deleteCommand(args) {
|
|
647
951
|
const name = args.positional[0];
|
|
648
952
|
if (!name) throw new Error("Usage: gs delete <name> [--yes]");
|
|
649
|
-
const slug =
|
|
953
|
+
const slug = requireProjectStore();
|
|
650
954
|
if (!flagBool(args.flags, "yes")) {
|
|
651
955
|
const confirmed = await prompt(
|
|
652
956
|
`Soft-delete component "${name}" in store "${slug}"? Type "yes" to confirm: `
|
|
@@ -662,71 +966,131 @@ async function deleteCommand(args) {
|
|
|
662
966
|
`);
|
|
663
967
|
}
|
|
664
968
|
function prompt(question) {
|
|
665
|
-
return new Promise((
|
|
969
|
+
return new Promise((resolve4) => {
|
|
666
970
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
667
971
|
rl.question(question, (answer) => {
|
|
668
972
|
rl.close();
|
|
669
|
-
|
|
973
|
+
resolve4(answer);
|
|
670
974
|
});
|
|
671
975
|
});
|
|
672
976
|
}
|
|
673
977
|
|
|
674
978
|
// src/commands/init.ts
|
|
675
|
-
import * as
|
|
676
|
-
import * as
|
|
979
|
+
import * as fs6 from "fs";
|
|
980
|
+
import * as path6 from "path";
|
|
677
981
|
var NAME_REGEX = /^[a-z][a-z0-9_]*$/;
|
|
678
982
|
function initCommand(args) {
|
|
679
983
|
const name = args.positional[0];
|
|
680
|
-
if (!name) {
|
|
681
|
-
throw new Error("Usage: gs init <name> [--out <dir>] [--store <slug>] [--force]");
|
|
682
|
-
}
|
|
683
|
-
if (!NAME_REGEX.test(name)) {
|
|
984
|
+
if (name !== void 0 && !NAME_REGEX.test(name)) {
|
|
684
985
|
throw new Error(`Invalid component name: "${name}" (must match ${NAME_REGEX}).`);
|
|
685
986
|
}
|
|
686
|
-
const outRel = flagString(args.flags, "out") ?? name;
|
|
687
|
-
const out = path5.resolve(outRel);
|
|
688
987
|
const force = flagBool(args.flags, "force");
|
|
689
988
|
const storeFlag = flagString(args.flags, "store");
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
989
|
+
const outRel = flagString(args.flags, "out") ?? ".";
|
|
990
|
+
const root = path6.resolve(outRel);
|
|
991
|
+
const rootExisted = hasRootScaffold(root);
|
|
992
|
+
if (rootExisted) {
|
|
993
|
+
if (storeFlag !== void 0) {
|
|
994
|
+
throw new Error(
|
|
995
|
+
`Project at ${root} is already pinned to a store via .gsrc. Drop --store; a project folder ships to exactly one store.`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
} else if (storeFlag === void 0) {
|
|
999
|
+
const example = name ? `gs init ${name} --store <slug>` : `gs init --store <slug>`;
|
|
1000
|
+
throw new Error(
|
|
1001
|
+
`--store <slug> is required when scaffolding a new project root. Run \`${example}\`.`
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
ensureRoot(root, rootExisted, force, { store: storeFlag });
|
|
1005
|
+
if (!name) {
|
|
1006
|
+
const created = rootExisted ? "Updated" : "Scaffolded";
|
|
1007
|
+
process.stdout.write(
|
|
1008
|
+
[
|
|
1009
|
+
`${created} GreatStore project root in ${root}.`,
|
|
1010
|
+
"",
|
|
1011
|
+
"Next steps:",
|
|
1012
|
+
` cd ${path6.relative(process.cwd(), root) || "."}`,
|
|
1013
|
+
" npm install",
|
|
1014
|
+
" gs init <component_name> # add your first component",
|
|
1015
|
+
""
|
|
1016
|
+
].join("\n")
|
|
1017
|
+
);
|
|
1018
|
+
return;
|
|
696
1019
|
}
|
|
1020
|
+
const componentDir = path6.join(root, "components", name);
|
|
1021
|
+
ensureComponent(componentDir, name, force);
|
|
1022
|
+
const projectLabel = path6.relative(process.cwd(), root) || ".";
|
|
697
1023
|
process.stdout.write(
|
|
698
1024
|
[
|
|
699
|
-
`
|
|
1025
|
+
`Added component "${name}" at components/${name}/.`,
|
|
700
1026
|
"",
|
|
701
1027
|
"Next steps:",
|
|
702
|
-
` cd ${
|
|
703
|
-
|
|
1028
|
+
...rootExisted ? [] : [` cd ${projectLabel}`, " npm install"],
|
|
1029
|
+
` # edit components/${name}/component.tsx and manifest.json`,
|
|
704
1030
|
" npm run build",
|
|
705
1031
|
" gs push",
|
|
706
1032
|
""
|
|
707
1033
|
].join("\n")
|
|
708
1034
|
);
|
|
709
1035
|
}
|
|
710
|
-
function
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1036
|
+
function hasRootScaffold(dir) {
|
|
1037
|
+
return fs6.existsSync(path6.join(dir, "package.json")) && fs6.existsSync(path6.join(dir, "build.mjs"));
|
|
1038
|
+
}
|
|
1039
|
+
function ensureRoot(root, rootExisted, force, opts) {
|
|
1040
|
+
fs6.mkdirSync(root, { recursive: true });
|
|
1041
|
+
if (rootExisted) {
|
|
1042
|
+
fs6.mkdirSync(path6.join(root, "components"), { recursive: true });
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
if (!force) {
|
|
1046
|
+
const entries = fs6.readdirSync(root).filter((e) => e !== ".gsrc");
|
|
1047
|
+
if (entries.length > 0) {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`Refusing to scaffold project root into non-empty directory ${root}. Pass --force to override.`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
for (const [relPath, content] of rootFiles({ store: opts.store })) {
|
|
1054
|
+
const full = path6.join(root, relPath);
|
|
1055
|
+
fs6.mkdirSync(path6.dirname(full), { recursive: true });
|
|
1056
|
+
if (force || !fs6.existsSync(full)) {
|
|
1057
|
+
fs6.writeFileSync(full, content);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
fs6.mkdirSync(path6.join(root, "components"), { recursive: true });
|
|
718
1061
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
1062
|
+
function ensureComponent(componentDir, name, force) {
|
|
1063
|
+
if (fs6.existsSync(componentDir) && !force) {
|
|
1064
|
+
const entries = fs6.readdirSync(componentDir);
|
|
1065
|
+
if (entries.length > 0) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`Refusing to overwrite existing components/${name}/. Pass --force to override.`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
fs6.mkdirSync(componentDir, { recursive: true });
|
|
1072
|
+
for (const [relPath, content] of componentFiles(name)) {
|
|
1073
|
+
fs6.writeFileSync(path6.join(componentDir, relPath), content);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function rootFiles(opts) {
|
|
1077
|
+
if (!opts.store) {
|
|
1078
|
+
throw new Error("internal: rootFiles called without a store slug");
|
|
1079
|
+
}
|
|
721
1080
|
return [
|
|
722
|
-
["
|
|
723
|
-
["
|
|
724
|
-
["vite.config.ts",
|
|
725
|
-
["tsconfig.json",
|
|
726
|
-
["
|
|
727
|
-
[".gsrc", gsrc(store)],
|
|
1081
|
+
["package.json", rootPackageJson()],
|
|
1082
|
+
["build.mjs", buildScript()],
|
|
1083
|
+
["vite.config.ts", viteEditorConfig()],
|
|
1084
|
+
["tsconfig.json", rootTsconfig()],
|
|
1085
|
+
[".gsrc", gsrc(opts.store)],
|
|
728
1086
|
[".gitignore", gitignore()],
|
|
729
|
-
["README.md",
|
|
1087
|
+
["README.md", rootReadme()]
|
|
1088
|
+
];
|
|
1089
|
+
}
|
|
1090
|
+
function componentFiles(name) {
|
|
1091
|
+
return [
|
|
1092
|
+
["manifest.json", manifest(name)],
|
|
1093
|
+
["component.tsx", component(name)]
|
|
730
1094
|
];
|
|
731
1095
|
}
|
|
732
1096
|
function manifest(name) {
|
|
@@ -767,51 +1131,86 @@ export default function ${pascal(name)}(_props: Props): React.ReactElement {
|
|
|
767
1131
|
}
|
|
768
1132
|
`;
|
|
769
1133
|
}
|
|
770
|
-
function
|
|
771
|
-
return `import {
|
|
1134
|
+
function buildScript() {
|
|
1135
|
+
return `import { build } from "vite";
|
|
772
1136
|
import react from "@vitejs/plugin-react";
|
|
1137
|
+
import * as fs from "node:fs";
|
|
1138
|
+
import * as path from "node:path";
|
|
773
1139
|
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
1140
|
+
const COMPONENTS_DIR = path.resolve("components");
|
|
1141
|
+
|
|
1142
|
+
function listComponents() {
|
|
1143
|
+
if (!fs.existsSync(COMPONENTS_DIR)) return [];
|
|
1144
|
+
return fs
|
|
1145
|
+
.readdirSync(COMPONENTS_DIR)
|
|
1146
|
+
.filter((entry) => {
|
|
1147
|
+
const dir = path.join(COMPONENTS_DIR, entry);
|
|
1148
|
+
return (
|
|
1149
|
+
fs.statSync(dir).isDirectory() &&
|
|
1150
|
+
fs.existsSync(path.join(dir, "component.tsx"))
|
|
1151
|
+
);
|
|
1152
|
+
})
|
|
1153
|
+
.sort();
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const components = listComponents();
|
|
1157
|
+
if (components.length === 0) {
|
|
1158
|
+
console.log("(no components in ./components \u2014 run \`gs init <name>\` to add one)");
|
|
1159
|
+
process.exit(0);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const target = process.argv[2];
|
|
1163
|
+
const queue = target ? components.filter((c) => c === target) : components;
|
|
1164
|
+
if (target && queue.length === 0) {
|
|
1165
|
+
console.error(\`No component named "\${target}" in ./components\`);
|
|
1166
|
+
process.exit(1);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
for (const name of queue) {
|
|
1170
|
+
const dir = path.join(COMPONENTS_DIR, name);
|
|
1171
|
+
await build({
|
|
1172
|
+
plugins: [react()],
|
|
1173
|
+
logLevel: "warn",
|
|
1174
|
+
build: {
|
|
1175
|
+
lib: {
|
|
1176
|
+
entry: path.join(dir, "component.tsx"),
|
|
1177
|
+
formats: ["es"],
|
|
1178
|
+
fileName: () => "bundle.js",
|
|
1179
|
+
},
|
|
1180
|
+
outDir: dir,
|
|
1181
|
+
emptyOutDir: false,
|
|
1182
|
+
rollupOptions: {
|
|
1183
|
+
external: ["react", "react-dom", "react/jsx-runtime"],
|
|
1184
|
+
output: {
|
|
1185
|
+
entryFileNames: "bundle.js",
|
|
1186
|
+
paths: {
|
|
1187
|
+
react: "/assets/remote-components/_runtime.js",
|
|
1188
|
+
"react-dom": "/assets/remote-components/_runtime.js",
|
|
1189
|
+
"react/jsx-runtime": "/assets/remote-components/_runtime.js",
|
|
1190
|
+
},
|
|
805
1191
|
},
|
|
806
1192
|
},
|
|
1193
|
+
minify: true,
|
|
1194
|
+
sourcemap: false,
|
|
807
1195
|
},
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1196
|
+
});
|
|
1197
|
+
console.log(\`built components/\${name}/bundle.js\`);
|
|
1198
|
+
}
|
|
1199
|
+
`;
|
|
1200
|
+
}
|
|
1201
|
+
function viteEditorConfig() {
|
|
1202
|
+
return `import { defineConfig } from "vite";
|
|
1203
|
+
import react from "@vitejs/plugin-react";
|
|
1204
|
+
|
|
1205
|
+
// Real builds happen in build.mjs (one Vite invocation per component).
|
|
1206
|
+
// This file exists only so editors / language servers can resolve the
|
|
1207
|
+
// React plugin when inspecting components/*/component.tsx.
|
|
1208
|
+
export default defineConfig({
|
|
1209
|
+
plugins: [react()],
|
|
811
1210
|
});
|
|
812
1211
|
`;
|
|
813
1212
|
}
|
|
814
|
-
function
|
|
1213
|
+
function rootTsconfig() {
|
|
815
1214
|
return JSON.stringify(
|
|
816
1215
|
{
|
|
817
1216
|
compilerOptions: {
|
|
@@ -826,22 +1225,22 @@ function tsconfig() {
|
|
|
826
1225
|
isolatedModules: true,
|
|
827
1226
|
noEmit: true
|
|
828
1227
|
},
|
|
829
|
-
include: ["component.tsx", "vite.config.ts"]
|
|
1228
|
+
include: ["components/**/component.tsx", "vite.config.ts", "build.mjs"]
|
|
830
1229
|
},
|
|
831
1230
|
null,
|
|
832
1231
|
2
|
|
833
1232
|
) + "\n";
|
|
834
1233
|
}
|
|
835
|
-
function
|
|
1234
|
+
function rootPackageJson() {
|
|
836
1235
|
return JSON.stringify(
|
|
837
1236
|
{
|
|
838
|
-
name,
|
|
1237
|
+
name: "greatstore-components",
|
|
839
1238
|
version: "0.0.1",
|
|
840
1239
|
private: true,
|
|
841
1240
|
type: "module",
|
|
842
1241
|
scripts: {
|
|
843
|
-
build: "
|
|
844
|
-
push: "
|
|
1242
|
+
build: "node build.mjs",
|
|
1243
|
+
push: "node build.mjs && gs push"
|
|
845
1244
|
},
|
|
846
1245
|
dependencies: {
|
|
847
1246
|
react: "^19.0.0",
|
|
@@ -860,25 +1259,37 @@ function packageJson(name) {
|
|
|
860
1259
|
) + "\n";
|
|
861
1260
|
}
|
|
862
1261
|
function gsrc(store) {
|
|
863
|
-
return JSON.stringify({ store
|
|
1262
|
+
return JSON.stringify({ store }, null, 2) + "\n";
|
|
864
1263
|
}
|
|
865
1264
|
function gitignore() {
|
|
866
|
-
return [
|
|
1265
|
+
return [
|
|
1266
|
+
"node_modules/",
|
|
1267
|
+
"components/*/bundle.js",
|
|
1268
|
+
"components/*/.gssync.json",
|
|
1269
|
+
"*.tsbuildinfo",
|
|
1270
|
+
".DS_Store",
|
|
1271
|
+
""
|
|
1272
|
+
].join("\n");
|
|
867
1273
|
}
|
|
868
|
-
function
|
|
869
|
-
return `#
|
|
1274
|
+
function rootReadme() {
|
|
1275
|
+
return `# GreatStore components
|
|
870
1276
|
|
|
871
|
-
|
|
1277
|
+
Custom React components published to your GreatStore tenant. Each
|
|
1278
|
+
component lives in its own folder under \`components/\`.
|
|
872
1279
|
|
|
873
1280
|
\`\`\`
|
|
874
1281
|
npm install
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
gs
|
|
1282
|
+
gs init <component_name> # add a new component
|
|
1283
|
+
npm run build # builds every component/<name>/bundle.js
|
|
1284
|
+
gs push # uploads every changed component as a draft
|
|
1285
|
+
gs publish <component_name> # promote a specific component to live
|
|
878
1286
|
\`\`\`
|
|
879
1287
|
|
|
880
|
-
|
|
881
|
-
that
|
|
1288
|
+
- \`gs push\` (no args) hashes each component and only uploads the ones
|
|
1289
|
+
that have changed since the last sync.
|
|
1290
|
+
- \`gs pull\` (no args) refreshes every remote component into
|
|
1291
|
+
\`components/<name>/\`. Components with unsaved local edits are skipped
|
|
1292
|
+
with a warning; pass \`--force\` to overwrite.
|
|
882
1293
|
`;
|
|
883
1294
|
}
|
|
884
1295
|
function pascal(name) {
|
|
@@ -886,7 +1297,7 @@ function pascal(name) {
|
|
|
886
1297
|
}
|
|
887
1298
|
|
|
888
1299
|
// src/index.ts
|
|
889
|
-
var VERSION = true ? "0.0.
|
|
1300
|
+
var VERSION = true ? "0.0.11-beta.1" : "0.0.0-dev";
|
|
890
1301
|
var HELP = `gs \u2014 GreatStore CLI (v${VERSION})
|
|
891
1302
|
|
|
892
1303
|
Usage:
|
|
@@ -896,28 +1307,27 @@ Commands:
|
|
|
896
1307
|
login Open browser, capture nav token, persist credentials.
|
|
897
1308
|
logout Wipe ~/.greatstore/credentials.json.
|
|
898
1309
|
whoami Print the identity stored locally.
|
|
899
|
-
init <name>
|
|
1310
|
+
init [<name>] Scaffold project root; add \`components/<name>/\` if name given.
|
|
900
1311
|
list List components in the current store.
|
|
901
|
-
pull <name
|
|
902
|
-
push [<name>] Upload
|
|
1312
|
+
pull [<name>|*] Download remote component(s) into \`components/<name>/\`. No args = all.
|
|
1313
|
+
push [<name>] Upload changed components from \`components/\`. No args = all (skips unchanged).
|
|
903
1314
|
publish <name> Promote the draft (or --version N) to live.
|
|
904
1315
|
unpublish <name> Clear the live pointer.
|
|
905
1316
|
delete <name> Soft-delete the component.
|
|
906
1317
|
|
|
907
1318
|
Common flags:
|
|
908
|
-
--store <slug>
|
|
1319
|
+
--store <slug> \`init\` only \u2014 writes the slug into .gsrc. Other commands read .gsrc.
|
|
909
1320
|
--json Machine-readable output for \`list\`.
|
|
910
1321
|
--draft | --live | --version N Revision selector for \`pull\`.
|
|
911
1322
|
-o, --out <dir> Output directory for \`pull\` / \`init\`.
|
|
912
1323
|
--manifest <path> Path to manifest for \`push\`.
|
|
913
1324
|
--bundle <path> Path to bundle for \`push\`.
|
|
914
|
-
--force Overwrite for \`init\`; skip confirmation for \`delete\`.
|
|
1325
|
+
--force Overwrite for \`init\` / \`pull\`; skip confirmation for \`delete\`.
|
|
915
1326
|
--yes Skip confirmation for \`delete\`.
|
|
916
1327
|
-h, --help Show this help.
|
|
917
1328
|
-v, --version Print version.
|
|
918
1329
|
|
|
919
1330
|
Environment:
|
|
920
|
-
GS_STORE Fallback for --store.
|
|
921
1331
|
GS_API_BASE Override API base (full URL, no trailing slash).
|
|
922
1332
|
GS_NAV_BASE Override nav OAuth base.
|
|
923
1333
|
GS_BROWSER_CMD Override the browser-open command.
|