@groundfloorcloud/cli 0.1.5 → 0.1.6
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 +6 -1
- package/dist/index.js +139 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,12 @@ Without a global install:
|
|
|
23
23
|
npx @groundfloorcloud/cli login
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Requires Node.js 20 or later. `gf
|
|
26
|
+
Requires Node.js 20 or later. `gf` checks npm at most once a day and prints a
|
|
27
|
+
one-line upgrade notice when a newer `@groundfloorcloud/cli` exists. Unknown
|
|
28
|
+
subcommands include the installed vs latest version so a stale binary is not
|
|
29
|
+
read as wrong docs.
|
|
30
|
+
|
|
31
|
+
`gf login` signs in to production
|
|
27
32
|
(`https://platform.groundfloor.cloud`). Use `--dev` or `--stage` for
|
|
28
33
|
other cells; later commands reuse that session.
|
|
29
34
|
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import path9 from "path";
|
|
5
6
|
|
|
6
7
|
// src/auth/store.ts
|
|
7
8
|
import { promises as fs } from "fs";
|
|
@@ -62,6 +63,7 @@ async function updateConfig(patch) {
|
|
|
62
63
|
await writeConfig(next);
|
|
63
64
|
return next;
|
|
64
65
|
}
|
|
66
|
+
var paths = { CONFIG_DIR, AUTH_FILE, CONFIG_FILE };
|
|
65
67
|
|
|
66
68
|
// src/auth/config.ts
|
|
67
69
|
var CELLS = {
|
|
@@ -533,9 +535,9 @@ async function parseError(res) {
|
|
|
533
535
|
}
|
|
534
536
|
return `${res.status} ${detail}`;
|
|
535
537
|
}
|
|
536
|
-
async function cpGet(apiUrl,
|
|
538
|
+
async function cpGet(apiUrl, path10) {
|
|
537
539
|
const token = await getValidAccessToken();
|
|
538
|
-
const res = await fetch(`${apiUrl}${
|
|
540
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
539
541
|
headers: {
|
|
540
542
|
Authorization: `Bearer ${token}`,
|
|
541
543
|
Accept: "application/json"
|
|
@@ -546,9 +548,9 @@ async function cpGet(apiUrl, path8) {
|
|
|
546
548
|
}
|
|
547
549
|
return await res.json();
|
|
548
550
|
}
|
|
549
|
-
async function cpPostJson(apiUrl,
|
|
551
|
+
async function cpPostJson(apiUrl, path10, body) {
|
|
550
552
|
const token = await getValidAccessToken();
|
|
551
|
-
const res = await fetch(`${apiUrl}${
|
|
553
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
552
554
|
method: "POST",
|
|
553
555
|
headers: {
|
|
554
556
|
Authorization: `Bearer ${token}`,
|
|
@@ -562,9 +564,9 @@ async function cpPostJson(apiUrl, path8, body) {
|
|
|
562
564
|
}
|
|
563
565
|
return await res.json();
|
|
564
566
|
}
|
|
565
|
-
async function cpDelete(apiUrl,
|
|
567
|
+
async function cpDelete(apiUrl, path10) {
|
|
566
568
|
const token = await getValidAccessToken();
|
|
567
|
-
const res = await fetch(`${apiUrl}${
|
|
569
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
568
570
|
method: "DELETE",
|
|
569
571
|
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
|
|
570
572
|
});
|
|
@@ -572,9 +574,9 @@ async function cpDelete(apiUrl, path8) {
|
|
|
572
574
|
throw new CpError(res.status, await parseError(res));
|
|
573
575
|
}
|
|
574
576
|
}
|
|
575
|
-
async function cpPutJson(apiUrl,
|
|
577
|
+
async function cpPutJson(apiUrl, path10, body) {
|
|
576
578
|
const token = await getValidAccessToken();
|
|
577
|
-
const res = await fetch(`${apiUrl}${
|
|
579
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
578
580
|
method: "PUT",
|
|
579
581
|
headers: {
|
|
580
582
|
Authorization: `Bearer ${token}`,
|
|
@@ -588,9 +590,9 @@ async function cpPutJson(apiUrl, path8, body) {
|
|
|
588
590
|
}
|
|
589
591
|
return await res.json();
|
|
590
592
|
}
|
|
591
|
-
async function cpPatchJson(apiUrl,
|
|
593
|
+
async function cpPatchJson(apiUrl, path10, body) {
|
|
592
594
|
const token = await getValidAccessToken();
|
|
593
|
-
const res = await fetch(`${apiUrl}${
|
|
595
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
594
596
|
method: "PATCH",
|
|
595
597
|
headers: {
|
|
596
598
|
Authorization: `Bearer ${token}`,
|
|
@@ -604,9 +606,9 @@ async function cpPatchJson(apiUrl, path8, body) {
|
|
|
604
606
|
}
|
|
605
607
|
return await res.json();
|
|
606
608
|
}
|
|
607
|
-
async function cpPutBytes(apiUrl,
|
|
609
|
+
async function cpPutBytes(apiUrl, path10, body, contentType) {
|
|
608
610
|
const token = await getValidAccessToken();
|
|
609
|
-
const res = await fetch(`${apiUrl}${
|
|
611
|
+
const res = await fetch(`${apiUrl}${path10}`, {
|
|
610
612
|
method: "PUT",
|
|
611
613
|
headers: {
|
|
612
614
|
Authorization: `Bearer ${token}`,
|
|
@@ -2004,21 +2006,21 @@ function chooseRemoteEntry(relpaths) {
|
|
|
2004
2006
|
})[0];
|
|
2005
2007
|
}
|
|
2006
2008
|
function flattenFederatedBundlePlan(relpaths) {
|
|
2007
|
-
const
|
|
2008
|
-
const pathset = new Set(
|
|
2009
|
+
const paths2 = relpaths.map(normalizeZipEntryName).filter((name) => name && !name.endsWith("/") && !name.split("/").includes(".."));
|
|
2010
|
+
const pathset = new Set(paths2);
|
|
2009
2011
|
const entry = chooseRemoteEntry([...pathset]);
|
|
2010
2012
|
if (!entry) return {};
|
|
2011
2013
|
const promoteDirs = /* @__PURE__ */ new Set();
|
|
2012
2014
|
const entryDir = parentDir(entry);
|
|
2013
2015
|
if (entryDir) promoteDirs.add(entryDir);
|
|
2014
|
-
for (const
|
|
2015
|
-
const parent = parentDir(
|
|
2016
|
+
for (const path10 of pathset) {
|
|
2017
|
+
const parent = parentDir(path10);
|
|
2016
2018
|
if (parent === "assets" || parent.endsWith("/assets") && parent.split("/").length - 1 < 2) {
|
|
2017
2019
|
promoteDirs.add(parent);
|
|
2018
2020
|
}
|
|
2019
2021
|
}
|
|
2020
2022
|
const destToSrc = {};
|
|
2021
|
-
for (const src of
|
|
2023
|
+
for (const src of paths2) {
|
|
2022
2024
|
const parent = parentDir(src);
|
|
2023
2025
|
if (!promoteDirs.has(parent)) continue;
|
|
2024
2026
|
const dest = basename(src);
|
|
@@ -2393,6 +2395,12 @@ import { promises as fs7 } from "fs";
|
|
|
2393
2395
|
import os3 from "os";
|
|
2394
2396
|
import path7 from "path";
|
|
2395
2397
|
import AdmZip4 from "adm-zip";
|
|
2398
|
+
|
|
2399
|
+
// src/version.ts
|
|
2400
|
+
var GF_VERSION = "0.1.6";
|
|
2401
|
+
var GF_NPM_PACKAGE = "@groundfloorcloud/cli";
|
|
2402
|
+
|
|
2403
|
+
// src/commands/apps-init.ts
|
|
2396
2404
|
var MAX_KIT_BYTES = 8 * 1024 * 1024;
|
|
2397
2405
|
function slugify3(input) {
|
|
2398
2406
|
const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -2465,7 +2473,7 @@ async function downloadKit(url) {
|
|
|
2465
2473
|
res = await fetch(url, {
|
|
2466
2474
|
redirect: "follow",
|
|
2467
2475
|
signal: AbortSignal.timeout(6e4),
|
|
2468
|
-
headers: { "User-Agent":
|
|
2476
|
+
headers: { "User-Agent": `groundfloor-cli/${GF_VERSION}` }
|
|
2469
2477
|
});
|
|
2470
2478
|
} catch (err) {
|
|
2471
2479
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -2567,6 +2575,9 @@ async function appsInitCommand(opts) {
|
|
|
2567
2575
|
process.stdout.write(
|
|
2568
2576
|
"Do not wrap src/App.tsx in BrowserRouter (DevShell already has one).\n"
|
|
2569
2577
|
);
|
|
2578
|
+
process.stdout.write(
|
|
2579
|
+
"MCP: .mcp.json is included (npx @groundfloorcloud/mcp). Cursor on macOS: copy .cursor/mcp.json.example.\n"
|
|
2580
|
+
);
|
|
2570
2581
|
process.stdout.write("Next:\n");
|
|
2571
2582
|
for (const step of result.next) {
|
|
2572
2583
|
process.stdout.write(` ${step}
|
|
@@ -2609,6 +2620,81 @@ async function dataplaneProvisionCommand(opts) {
|
|
|
2609
2620
|
}
|
|
2610
2621
|
}
|
|
2611
2622
|
|
|
2623
|
+
// src/update-check.ts
|
|
2624
|
+
import { promises as fs8 } from "fs";
|
|
2625
|
+
import path8 from "path";
|
|
2626
|
+
var UPDATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2627
|
+
function isNewer(latest, current) {
|
|
2628
|
+
const a = latest.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
2629
|
+
const b = current.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
2630
|
+
const len = Math.max(a.length, b.length);
|
|
2631
|
+
for (let i = 0; i < len; i += 1) {
|
|
2632
|
+
const lv = a[i] ?? 0;
|
|
2633
|
+
const cv = b[i] ?? 0;
|
|
2634
|
+
if (lv > cv) return true;
|
|
2635
|
+
if (lv < cv) return false;
|
|
2636
|
+
}
|
|
2637
|
+
return false;
|
|
2638
|
+
}
|
|
2639
|
+
function upgradeNotice(current, latest) {
|
|
2640
|
+
return `gf ${latest} is available (you have ${current}). npm i -g ${GF_NPM_PACKAGE}@latest`;
|
|
2641
|
+
}
|
|
2642
|
+
function unknownCommandNotice(command, current, latest) {
|
|
2643
|
+
if (latest && isNewer(latest, current)) {
|
|
2644
|
+
return `unknown command '${command}' \u2014 your gf is ${current}, latest is ${latest}. npm i -g ${GF_NPM_PACKAGE}@latest`;
|
|
2645
|
+
}
|
|
2646
|
+
return `unknown command '${command}' \u2014 your gf is ${current}`;
|
|
2647
|
+
}
|
|
2648
|
+
function parseUnknownCommandName(message) {
|
|
2649
|
+
const match = message.match(/unknown command ['"]([^'"]+)['"]/i);
|
|
2650
|
+
return match?.[1] ?? "that";
|
|
2651
|
+
}
|
|
2652
|
+
async function fetchNpmLatest(pkg = GF_NPM_PACKAGE) {
|
|
2653
|
+
try {
|
|
2654
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
2655
|
+
headers: { Accept: "application/json" },
|
|
2656
|
+
signal: AbortSignal.timeout(1500)
|
|
2657
|
+
});
|
|
2658
|
+
if (!res.ok) return null;
|
|
2659
|
+
const body = await res.json();
|
|
2660
|
+
return typeof body.version === "string" && body.version.trim() ? body.version.trim() : null;
|
|
2661
|
+
} catch {
|
|
2662
|
+
return null;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
async function readUpdateCache(file) {
|
|
2666
|
+
try {
|
|
2667
|
+
const raw = await fs8.readFile(file, "utf8");
|
|
2668
|
+
const parsed = JSON.parse(raw);
|
|
2669
|
+
if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string" || !parsed.latest.trim()) {
|
|
2670
|
+
return null;
|
|
2671
|
+
}
|
|
2672
|
+
return parsed;
|
|
2673
|
+
} catch {
|
|
2674
|
+
return null;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
async function writeUpdateCache(file, cache) {
|
|
2678
|
+
await fs8.mkdir(path8.dirname(file), { recursive: true, mode: 448 });
|
|
2679
|
+
await fs8.writeFile(file, `${JSON.stringify(cache)}
|
|
2680
|
+
`, { mode: 384 });
|
|
2681
|
+
}
|
|
2682
|
+
async function resolveLatest(opts) {
|
|
2683
|
+
const now = opts.now ?? Date.now();
|
|
2684
|
+
const ttl = opts.ttlMs ?? UPDATE_TTL_MS;
|
|
2685
|
+
const cached = await readUpdateCache(opts.cacheFile);
|
|
2686
|
+
if (cached && now - cached.checkedAt < ttl) {
|
|
2687
|
+
return cached.latest;
|
|
2688
|
+
}
|
|
2689
|
+
const fetchLatest = opts.fetchLatest ?? fetchNpmLatest;
|
|
2690
|
+
const latest = await fetchLatest();
|
|
2691
|
+
if (latest) {
|
|
2692
|
+
await writeUpdateCache(opts.cacheFile, { checkedAt: now, latest });
|
|
2693
|
+
return latest;
|
|
2694
|
+
}
|
|
2695
|
+
return cached?.latest ?? null;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2612
2698
|
// src/index.ts
|
|
2613
2699
|
function collect(value, previous) {
|
|
2614
2700
|
return previous.concat([value]);
|
|
@@ -2624,7 +2710,7 @@ function run(fn) {
|
|
|
2624
2710
|
var program = new Command();
|
|
2625
2711
|
program.name("gf").description(
|
|
2626
2712
|
"Groundfloor CLI \u2014 sign in, deploy coderunners, publish Shell apps, and manage workspace resources."
|
|
2627
|
-
).version(
|
|
2713
|
+
).version(GF_VERSION);
|
|
2628
2714
|
program.command("login").description(
|
|
2629
2715
|
"Log in via the browser (production by default; --dev / --stage for other cells)"
|
|
2630
2716
|
).option("--dev", "Sign in to Groundfloor dev").option("--stage", "Sign in to Groundfloor stage").option(
|
|
@@ -2759,7 +2845,40 @@ program.command("deploy").description(
|
|
|
2759
2845
|
"--app-id <id>",
|
|
2760
2846
|
"Bind newly created coderunner as a helper of this App (optional)"
|
|
2761
2847
|
).option("--cpu <cpu>", "CPU request, e.g. 250m").option("--memory <memory>", "Memory request, e.g. 256Mi").option("-e, --env <KEY=VALUE>", "Environment variable (repeatable)", collect, []).option("-m, --message <text>", "Version description").option("--skip-deploy", "Upload a new version but do not deploy it").action((opts) => run(() => deployCommand(opts)));
|
|
2762
|
-
|
|
2848
|
+
function wireUnknownCommands(root, current, latest) {
|
|
2849
|
+
const walk = (cmd) => {
|
|
2850
|
+
if (cmd.commands.length === 0) return;
|
|
2851
|
+
cmd.exitOverride((err) => {
|
|
2852
|
+
if (err.code === "commander.unknownCommand") {
|
|
2853
|
+
process.stderr.write(
|
|
2854
|
+
`${unknownCommandNotice(parseUnknownCommandName(err.message), current, latest)}
|
|
2855
|
+
`
|
|
2856
|
+
);
|
|
2857
|
+
process.exit(1);
|
|
2858
|
+
}
|
|
2859
|
+
throw err;
|
|
2860
|
+
});
|
|
2861
|
+
for (const child of cmd.commands) walk(child);
|
|
2862
|
+
};
|
|
2863
|
+
walk(root);
|
|
2864
|
+
}
|
|
2865
|
+
async function notifyUpgrade() {
|
|
2866
|
+
if (process.env.GF_SKIP_UPDATE_CHECK === "1") return null;
|
|
2867
|
+
const latest = await resolveLatest({
|
|
2868
|
+
cacheFile: path9.join(paths.CONFIG_DIR, "update-check.json")
|
|
2869
|
+
});
|
|
2870
|
+
if (latest && isNewer(latest, GF_VERSION)) {
|
|
2871
|
+
process.stderr.write(`${upgradeNotice(GF_VERSION, latest)}
|
|
2872
|
+
`);
|
|
2873
|
+
}
|
|
2874
|
+
return latest;
|
|
2875
|
+
}
|
|
2876
|
+
async function main() {
|
|
2877
|
+
const latest = await notifyUpgrade();
|
|
2878
|
+
wireUnknownCommands(program, GF_VERSION, latest);
|
|
2879
|
+
await program.parseAsync(process.argv);
|
|
2880
|
+
}
|
|
2881
|
+
main().catch((err) => {
|
|
2763
2882
|
const message = err instanceof Error ? err.message : String(err);
|
|
2764
2883
|
process.stderr.write(`Error: ${message}
|
|
2765
2884
|
`);
|
package/package.json
CHANGED