@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.
Files changed (3) hide show
  1. package/README.md +7 -2
  2. package/dist/index.js +174 -12
  3. 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.6";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundfloorcloud/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Groundfloor gf CLI — sign in, deploy coderunners, publish Shell apps, and manage secrets, files, and Dataplane.",
5
5
  "type": "module",
6
6
  "bin": {