@groundfloorcloud/cli 0.1.6 → 0.1.8
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 -2
- package/dist/index.js +174 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Authentication, mode `groundfloor` or `external`) until the realm is active.
|
|
|
72
72
|
|-------------------|-------------|---------------|
|
|
73
73
|
| `function` | Function | HTTP on `0.0.0.0:PORT`/8080. Node needs `scripts.start`. |
|
|
74
74
|
| `job` | Background job | Run-to-completion. Then `gf coderunner run`. |
|
|
75
|
-
| `schedule` | Scheduled job | Cron. No public URL. |
|
|
75
|
+
| `schedule` | Scheduled job | Cron via `--schedule` or `groundfloor.json` `schedule`. No public URL. |
|
|
76
76
|
| `service` | **Deployment** | Long-running HTTP. **Root `Dockerfile` is required.** |
|
|
77
77
|
|
|
78
78
|
```bash
|
|
@@ -81,6 +81,7 @@ gf deploy --git https://github.com/me/my-fn.git --ref main
|
|
|
81
81
|
gf deploy --name my-fn --runtime python --cpu 250m --memory 256Mi \
|
|
82
82
|
-e API_KEY=secret
|
|
83
83
|
gf deploy --workload-type service
|
|
84
|
+
gf deploy --workload-type schedule --schedule "*/5 * * * *"
|
|
84
85
|
gf deploy --app-id <app-uuid>
|
|
85
86
|
```
|
|
86
87
|
|
|
@@ -118,7 +119,8 @@ Then `gf deploy` the same tree. Do not zip `.env` files.
|
|
|
118
119
|
"workloadType": "function",
|
|
119
120
|
"cpu": "250m",
|
|
120
121
|
"memory": "256Mi",
|
|
121
|
-
"env": { "STAGE": "dev" }
|
|
122
|
+
"env": { "STAGE": "dev" },
|
|
123
|
+
"schedule": "*/5 * * * *"
|
|
122
124
|
}
|
|
123
125
|
```
|
|
124
126
|
|
|
@@ -132,6 +134,7 @@ Then `gf deploy` the same tree. Do not zip `.env` files.
|
|
|
132
134
|
| `--subdir <dir>` | Package a subdirectory of the source. |
|
|
133
135
|
| `--runtime <r>` | `python` \| `node` \| `dotnet-script`. |
|
|
134
136
|
| `--workload-type <t>` | `function` \| `service` (Deployment) \| `job` \| `schedule`. Service requires a Dockerfile. |
|
|
137
|
+
| `--schedule <cron>` | Cron for `schedule` workloads (or `groundfloor.json` `schedule`). |
|
|
135
138
|
| `--cpu` / `--memory` | Resource requests, e.g. `250m` / `256Mi`. |
|
|
136
139
|
| `-e, --env KEY=VALUE` | Deployment env var (repeatable). |
|
|
137
140
|
| `-m, --message <text>` | Version description. |
|
|
@@ -205,10 +208,12 @@ gf apps get <id|slug>
|
|
|
205
208
|
| `gf whoami` | Show identity and verify the Control Plane session. |
|
|
206
209
|
| `gf token` | Print a fresh access token. |
|
|
207
210
|
| `gf env` | Print `export …` lines for `eval "$(gf env)"`. |
|
|
211
|
+
| `gf environments ls` (`envs`) | List workspace environments. |
|
|
208
212
|
| `gf workspaces` (`ws`) | List workspaces; `gf workspaces use <id>` sets the default. |
|
|
209
213
|
| `gf coderunner ls` (`cr ls`) | List coderunners. |
|
|
210
214
|
| `gf coderunner status -c <id\|slug>` | Status and current deployment. |
|
|
211
215
|
| `gf coderunner logs -c <id\|slug>` | Process Log (stdout/stderr). Also printed automatically on `gf deploy` failure. |
|
|
216
|
+
| `gf coderunner cancel-deployment -c <id\|slug>` | Delete the current (or `--deployment-id`) deployment. |
|
|
212
217
|
| `gf coderunner run -c <id\|slug> [--payload '{}']` | Invoke a deployed function or job. |
|
|
213
218
|
| `gf deploy` | Package code and deploy as a coderunner. |
|
|
214
219
|
| `gf domains ls\|add\|rm\|verify -c <id\|slug>` | Custom domains for a service. |
|
package/dist/index.js
CHANGED
|
@@ -709,6 +709,22 @@ async function getDeployment(apiUrl, workspaceId, coderunnerId) {
|
|
|
709
709
|
}
|
|
710
710
|
return await res.json();
|
|
711
711
|
}
|
|
712
|
+
function deleteCoderunnerDeployment(apiUrl, workspaceId, coderunnerId, deploymentId) {
|
|
713
|
+
return cpDelete(
|
|
714
|
+
apiUrl,
|
|
715
|
+
`/v1/workspaces/${encodeURIComponent(
|
|
716
|
+
workspaceId
|
|
717
|
+
)}/coderunners/${encodeURIComponent(
|
|
718
|
+
coderunnerId
|
|
719
|
+
)}/deployments/${encodeURIComponent(deploymentId)}`
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
function listWorkspaceEnvironments(apiUrl, workspaceId) {
|
|
723
|
+
return cpGet(
|
|
724
|
+
apiUrl,
|
|
725
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/environments`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
712
728
|
function getCoderunnerStatus(apiUrl, workspaceId, coderunnerId) {
|
|
713
729
|
return cpGet(
|
|
714
730
|
apiUrl,
|
|
@@ -1017,6 +1033,79 @@ async function coderunnerListCommand(opts) {
|
|
|
1017
1033
|
}
|
|
1018
1034
|
}
|
|
1019
1035
|
|
|
1036
|
+
// src/resolve-coderunner.ts
|
|
1037
|
+
async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
|
|
1038
|
+
const list = await listCoderunners(apiUrl, workspaceId);
|
|
1039
|
+
const match = list.find((c) => c.id === idOrSlug || c.slug === idOrSlug);
|
|
1040
|
+
if (!match) {
|
|
1041
|
+
throw new Error(`Coderunner not found: ${idOrSlug}`);
|
|
1042
|
+
}
|
|
1043
|
+
return match.id;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/commands/cancel-deployment.ts
|
|
1047
|
+
async function cancelDeploymentCommand(opts) {
|
|
1048
|
+
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
1049
|
+
if (!cfg.workspaceId) {
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
"No workspace selected. Run `gf workspaces use <id>` or pass --workspace <id>."
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const coderunnerId = await resolveCoderunnerId(
|
|
1055
|
+
cfg.apiUrl,
|
|
1056
|
+
cfg.workspaceId,
|
|
1057
|
+
opts.coderunner
|
|
1058
|
+
);
|
|
1059
|
+
let deploymentId = opts.deploymentId?.trim();
|
|
1060
|
+
if (!deploymentId) {
|
|
1061
|
+
const current = await getDeployment(cfg.apiUrl, cfg.workspaceId, coderunnerId);
|
|
1062
|
+
deploymentId = current?.id?.trim();
|
|
1063
|
+
}
|
|
1064
|
+
if (!deploymentId) {
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`No current deployment for coderunner ${coderunnerId}. Pass --deployment-id.`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
await deleteCoderunnerDeployment(
|
|
1070
|
+
cfg.apiUrl,
|
|
1071
|
+
cfg.workspaceId,
|
|
1072
|
+
coderunnerId,
|
|
1073
|
+
deploymentId
|
|
1074
|
+
);
|
|
1075
|
+
process.stdout.write(
|
|
1076
|
+
`Deleted deployment ${deploymentId} for coderunner ${coderunnerId}
|
|
1077
|
+
`
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/commands/environments.ts
|
|
1082
|
+
async function environmentsListCommand(opts) {
|
|
1083
|
+
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
1084
|
+
if (!cfg.workspaceId) {
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
"No workspace selected. Run `gf workspaces use <id>` or pass --workspace <id>."
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
const environments2 = await listWorkspaceEnvironments(
|
|
1090
|
+
cfg.apiUrl,
|
|
1091
|
+
cfg.workspaceId
|
|
1092
|
+
);
|
|
1093
|
+
if (opts.json) {
|
|
1094
|
+
process.stdout.write(`${JSON.stringify(environments2, null, 2)}
|
|
1095
|
+
`);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (environments2.length === 0) {
|
|
1099
|
+
process.stdout.write("No environments in this workspace.\n");
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
for (const env of environments2) {
|
|
1103
|
+
const status = env.status ?? "";
|
|
1104
|
+
process.stdout.write(`${env.id} ${env.name} [${status}]
|
|
1105
|
+
`);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1020
1109
|
// src/process-log.ts
|
|
1021
1110
|
var PROCESS_STATUS_LABELS = {
|
|
1022
1111
|
1: "Pending",
|
|
@@ -1130,16 +1219,6 @@ async function diagnoseDeployFailure(opts) {
|
|
|
1130
1219
|
});
|
|
1131
1220
|
}
|
|
1132
1221
|
|
|
1133
|
-
// src/resolve-coderunner.ts
|
|
1134
|
-
async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
|
|
1135
|
-
const list = await listCoderunners(apiUrl, workspaceId);
|
|
1136
|
-
const match = list.find((c) => c.id === idOrSlug || c.slug === idOrSlug);
|
|
1137
|
-
if (!match) {
|
|
1138
|
-
throw new Error(`Coderunner not found: ${idOrSlug}`);
|
|
1139
|
-
}
|
|
1140
|
-
return match.id;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
1222
|
// src/commands/logs.ts
|
|
1144
1223
|
async function logsCommand(opts) {
|
|
1145
1224
|
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
@@ -1405,6 +1484,30 @@ async function preflightNodePackage(sourceDir) {
|
|
|
1405
1484
|
}
|
|
1406
1485
|
}
|
|
1407
1486
|
|
|
1487
|
+
// src/deploy/schedule.ts
|
|
1488
|
+
var SCHEDULE_REQUIRED = 'workload type "schedule" requires --schedule "<cron>" or groundfloor.json "schedule".';
|
|
1489
|
+
var SCHEDULE_WRONG_TYPE = '--schedule / groundfloor.json "schedule" is only valid with --workload-type schedule.';
|
|
1490
|
+
function resolveDeploySchedule(opts) {
|
|
1491
|
+
const cron = (opts.flag ?? opts.manifestSchedule)?.trim() || void 0;
|
|
1492
|
+
if (opts.workloadType === "schedule") {
|
|
1493
|
+
if (!cron) {
|
|
1494
|
+
throw new Error(SCHEDULE_REQUIRED);
|
|
1495
|
+
}
|
|
1496
|
+
return cron;
|
|
1497
|
+
}
|
|
1498
|
+
if (cron) {
|
|
1499
|
+
throw new Error(SCHEDULE_WRONG_TYPE);
|
|
1500
|
+
}
|
|
1501
|
+
return void 0;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
// src/deploy/target.ts
|
|
1505
|
+
function formatDeployTarget(input) {
|
|
1506
|
+
const label = input.workspaceName?.trim() ? `${input.workspaceName.trim()} (${input.workspaceId})` : input.workspaceId;
|
|
1507
|
+
const cell = input.cell ?? "unknown";
|
|
1508
|
+
return `Deploy target: workspace=${label} cell=${cell} api=${input.apiUrl}`;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1408
1511
|
// src/commands/deploy.ts
|
|
1409
1512
|
var RUNTIMES = /* @__PURE__ */ new Set(["python", "node", "dotnet-script"]);
|
|
1410
1513
|
var WORKLOAD_TYPES = /* @__PURE__ */ new Set(["function", "service", "job", "schedule"]);
|
|
@@ -1448,6 +1551,20 @@ async function deployCommand(opts) {
|
|
|
1448
1551
|
);
|
|
1449
1552
|
}
|
|
1450
1553
|
const ws = cfg.workspaceId;
|
|
1554
|
+
let workspaceName;
|
|
1555
|
+
try {
|
|
1556
|
+
const workspaces2 = await listWorkspaces(cfg.apiUrl);
|
|
1557
|
+
workspaceName = workspaces2.find((item) => item.id === ws)?.name;
|
|
1558
|
+
} catch {
|
|
1559
|
+
}
|
|
1560
|
+
log(
|
|
1561
|
+
formatDeployTarget({
|
|
1562
|
+
workspaceId: ws,
|
|
1563
|
+
workspaceName,
|
|
1564
|
+
cell: cfg.environment,
|
|
1565
|
+
apiUrl: cfg.apiUrl
|
|
1566
|
+
})
|
|
1567
|
+
);
|
|
1451
1568
|
log(
|
|
1452
1569
|
opts.git ? `Cloning ${opts.git}${opts.ref ? ` (${opts.ref})` : ""}...` : `Packaging ${path4.resolve(opts.path ?? process.cwd())}...`
|
|
1453
1570
|
);
|
|
@@ -1486,6 +1603,11 @@ async function deployCommand(opts) {
|
|
|
1486
1603
|
if (runtime === "node") {
|
|
1487
1604
|
await preflightNodePackage(pkg.sourceDir);
|
|
1488
1605
|
}
|
|
1606
|
+
const schedule = resolveDeploySchedule({
|
|
1607
|
+
workloadType,
|
|
1608
|
+
flag: opts.schedule,
|
|
1609
|
+
manifestSchedule: manifest.schedule
|
|
1610
|
+
});
|
|
1489
1611
|
let coderunnerId = opts.coderunner;
|
|
1490
1612
|
if (!coderunnerId) {
|
|
1491
1613
|
const name = opts.name ?? manifest.name ?? (opts.git ? nameFromGitUrl(opts.git) : path4.basename(path4.resolve(opts.path ?? process.cwd())));
|
|
@@ -1587,7 +1709,8 @@ async function deployCommand(opts) {
|
|
|
1587
1709
|
version_id: uploaded.version_id,
|
|
1588
1710
|
cpu: opts.cpu ?? manifest.cpu,
|
|
1589
1711
|
memory: opts.memory ?? manifest.memory,
|
|
1590
|
-
envVariable: Object.keys(envVariable).length ? envVariable : void 0
|
|
1712
|
+
envVariable: Object.keys(envVariable).length ? envVariable : void 0,
|
|
1713
|
+
schedule
|
|
1591
1714
|
});
|
|
1592
1715
|
log("Waiting for deployment...");
|
|
1593
1716
|
const deployDeadline = Date.now() + DEPLOY_POLL_TIMEOUT_MS;
|
|
@@ -2397,7 +2520,7 @@ import path7 from "path";
|
|
|
2397
2520
|
import AdmZip4 from "adm-zip";
|
|
2398
2521
|
|
|
2399
2522
|
// src/version.ts
|
|
2400
|
-
var GF_VERSION = "0.1.
|
|
2523
|
+
var GF_VERSION = "0.1.8";
|
|
2401
2524
|
var GF_NPM_PACKAGE = "@groundfloorcloud/cli";
|
|
2402
2525
|
|
|
2403
2526
|
// src/commands/apps-init.ts
|
|
@@ -2695,6 +2818,24 @@ async function resolveLatest(opts) {
|
|
|
2695
2818
|
return cached?.latest ?? null;
|
|
2696
2819
|
}
|
|
2697
2820
|
|
|
2821
|
+
// src/commander-exit.ts
|
|
2822
|
+
var QUIET_EXIT_CODES = /* @__PURE__ */ new Set([
|
|
2823
|
+
"commander.version",
|
|
2824
|
+
"commander.helpDisplayed"
|
|
2825
|
+
]);
|
|
2826
|
+
function isQuietCommanderExit(err) {
|
|
2827
|
+
if (err == null || typeof err !== "object") return false;
|
|
2828
|
+
const code = err.code;
|
|
2829
|
+
return typeof code === "string" && QUIET_EXIT_CODES.has(code);
|
|
2830
|
+
}
|
|
2831
|
+
function commanderExitCode(err) {
|
|
2832
|
+
if (err != null && typeof err === "object") {
|
|
2833
|
+
const code = err.exitCode;
|
|
2834
|
+
if (typeof code === "number") return code;
|
|
2835
|
+
}
|
|
2836
|
+
return 0;
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2698
2839
|
// src/index.ts
|
|
2699
2840
|
function collect(value, previous) {
|
|
2700
2841
|
return previous.concat([value]);
|
|
@@ -2727,6 +2868,8 @@ program.command("logout").description("Clear the cached session").action(() => r
|
|
|
2727
2868
|
program.command("whoami").description("Show the current identity and verify the Control Plane session").action(() => run(() => whoamiCommand()));
|
|
2728
2869
|
program.command("token").description("Print a valid (auto-refreshed) access token to stdout").action(() => run(() => tokenCommand()));
|
|
2729
2870
|
program.command("env").description('Print shell exports for `eval "$(gf env)"`').action(() => run(() => envCommand()));
|
|
2871
|
+
var environments = program.command("environments").alias("envs").description("List workspace environments");
|
|
2872
|
+
environments.command("list", { isDefault: true }).alias("ls").description("List environments in the default (or --workspace) workspace").option("-w, --workspace <id>", "Workspace id override").option("--json", "Output raw JSON").action((opts) => run(() => environmentsListCommand(opts)));
|
|
2730
2873
|
var workspaces = program.command("workspaces").alias("ws").description("List workspaces or set the default workspace");
|
|
2731
2874
|
workspaces.command("list", { isDefault: true }).description("List workspaces visible to your user").option("--json", "Output raw JSON").action((opts) => run(() => workspacesListCommand(opts)));
|
|
2732
2875
|
workspaces.command("use <workspaceId>").description("Set the default workspace").action(
|
|
@@ -2735,6 +2878,15 @@ workspaces.command("use <workspaceId>").description("Set the default workspace")
|
|
|
2735
2878
|
var coderunner = program.command("coderunner").alias("cr").description("Coderunner commands");
|
|
2736
2879
|
coderunner.command("ls").alias("list").description("List coderunners in the default (or --workspace) workspace").option("-w, --workspace <id>", "Workspace id override").option("--json", "Output raw JSON").action((opts) => run(() => coderunnerListCommand(opts)));
|
|
2737
2880
|
coderunner.command("status").description("Show coderunner status + current deployment JSON").requiredOption("-c, --coderunner <id|slug>", "Coderunner id or slug").option("-w, --workspace <id>", "Workspace id override").option("--json", "Output raw JSON").action((opts) => run(() => statusCommand(opts)));
|
|
2881
|
+
coderunner.command("cancel-deployment").description("Delete the current (or --deployment-id) deployment").requiredOption("-c, --coderunner <id|slug>", "Coderunner id or slug").option("-w, --workspace <id>", "Workspace id override").option("--deployment-id <id>", "Deployment id (default: current deployment)").action(
|
|
2882
|
+
(opts) => run(
|
|
2883
|
+
() => cancelDeploymentCommand({
|
|
2884
|
+
workspace: opts.workspace,
|
|
2885
|
+
coderunner: opts.coderunner,
|
|
2886
|
+
deploymentId: opts.deploymentId
|
|
2887
|
+
})
|
|
2888
|
+
)
|
|
2889
|
+
);
|
|
2738
2890
|
coderunner.command("logs").description("Show Process Log lines for a coderunner (stdout/stderr)").requiredOption("-c, --coderunner <id|slug>", "Coderunner id or slug").option("-w, --workspace <id>", "Workspace id override").option("--limit <n>", "Max lines (default 50)").option("--since-seconds <n>", "Lookback window in seconds (default 3600)").option("--search <text>", "Substring filter").option("--json", "Output raw JSON").action(
|
|
2739
2891
|
(opts) => run(
|
|
2740
2892
|
() => logsCommand({
|
|
@@ -2841,6 +2993,9 @@ program.command("deploy").description(
|
|
|
2841
2993
|
).option("-n, --name <name>", "Coderunner name (default: folder or repo name)").option("-p, --path <dir>", "Local directory to package (default: cwd)").option("--git <url>", "Clone and deploy a remote git/bitbucket repo").option("--ref <ref>", "Branch/tag/commit for --git").option("--subdir <dir>", "Subdirectory within the source to package").option("--runtime <runtime>", "python | node | dotnet-script").option(
|
|
2842
2994
|
"--workload-type <type>",
|
|
2843
2995
|
"function | service | job | schedule"
|
|
2996
|
+
).option(
|
|
2997
|
+
"--schedule <cron>",
|
|
2998
|
+
'Cron for --workload-type schedule (or groundfloor.json "schedule")'
|
|
2844
2999
|
).option(
|
|
2845
3000
|
"--app-id <id>",
|
|
2846
3001
|
"Bind newly created coderunner as a helper of this App (optional)"
|
|
@@ -2856,6 +3011,9 @@ function wireUnknownCommands(root, current, latest) {
|
|
|
2856
3011
|
);
|
|
2857
3012
|
process.exit(1);
|
|
2858
3013
|
}
|
|
3014
|
+
if (isQuietCommanderExit(err)) {
|
|
3015
|
+
process.exit(commanderExitCode(err));
|
|
3016
|
+
}
|
|
2859
3017
|
throw err;
|
|
2860
3018
|
});
|
|
2861
3019
|
for (const child of cmd.commands) walk(child);
|
|
@@ -2879,6 +3037,10 @@ async function main() {
|
|
|
2879
3037
|
await program.parseAsync(process.argv);
|
|
2880
3038
|
}
|
|
2881
3039
|
main().catch((err) => {
|
|
3040
|
+
if (isQuietCommanderExit(err)) {
|
|
3041
|
+
process.exitCode = commanderExitCode(err);
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
2882
3044
|
const message = err instanceof Error ? err.message : String(err);
|
|
2883
3045
|
process.stderr.write(`Error: ${message}
|
|
2884
3046
|
`);
|
package/package.json
CHANGED