@groundfloorcloud/cli 0.1.4 → 0.1.5
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 +5 -2
- package/dist/index.js +362 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ Authentication, mode `groundfloor` or `external`) until the realm is active.
|
|
|
65
65
|
|
|
66
66
|
| `--workload-type` | Also called | Package notes |
|
|
67
67
|
|-------------------|-------------|---------------|
|
|
68
|
-
| `function` | Function | HTTP on `PORT`/8080. Node needs `scripts.start`. |
|
|
68
|
+
| `function` | Function | HTTP on `0.0.0.0:PORT`/8080. Node needs `scripts.start`. |
|
|
69
69
|
| `job` | Background job | Run-to-completion. Then `gf coderunner run`. |
|
|
70
70
|
| `schedule` | Scheduled job | Cron. No public URL. |
|
|
71
71
|
| `service` | **Deployment** | Long-running HTTP. **Root `Dockerfile` is required.** |
|
|
@@ -88,7 +88,9 @@ How the target is chosen:
|
|
|
88
88
|
|
|
89
89
|
Runtime is auto-detected when omitted (`package.json` → node,
|
|
90
90
|
`requirements.txt` / `pyproject.toml` → python), defaulting to python.
|
|
91
|
-
Node
|
|
91
|
+
Node without a Dockerfile uses the platform bootstrap (`CMD ["npm","start"]`,
|
|
92
|
+
port 8080). Add `"scripts": { "start": "node index.js" }` and bind
|
|
93
|
+
`0.0.0.0`. `gf deploy` fails immediately if `scripts.start` is missing.
|
|
92
94
|
|
|
93
95
|
### Local with live data
|
|
94
96
|
|
|
@@ -201,6 +203,7 @@ gf apps get <id|slug>
|
|
|
201
203
|
| `gf workspaces` (`ws`) | List workspaces; `gf workspaces use <id>` sets the default. |
|
|
202
204
|
| `gf coderunner ls` (`cr ls`) | List coderunners. |
|
|
203
205
|
| `gf coderunner status -c <id\|slug>` | Status and current deployment. |
|
|
206
|
+
| `gf coderunner logs -c <id\|slug>` | Process Log (stdout/stderr). Also printed automatically on `gf deploy` failure. |
|
|
204
207
|
| `gf coderunner run -c <id\|slug> [--payload '{}']` | Invoke a deployed function or job. |
|
|
205
208
|
| `gf deploy` | Package code and deploy as a coderunner. |
|
|
206
209
|
| `gf domains ls\|add\|rm\|verify -c <id\|slug>` | Custom domains for a service. |
|
package/dist/index.js
CHANGED
|
@@ -533,9 +533,9 @@ async function parseError(res) {
|
|
|
533
533
|
}
|
|
534
534
|
return `${res.status} ${detail}`;
|
|
535
535
|
}
|
|
536
|
-
async function cpGet(apiUrl,
|
|
536
|
+
async function cpGet(apiUrl, path8) {
|
|
537
537
|
const token = await getValidAccessToken();
|
|
538
|
-
const res = await fetch(`${apiUrl}${
|
|
538
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
539
539
|
headers: {
|
|
540
540
|
Authorization: `Bearer ${token}`,
|
|
541
541
|
Accept: "application/json"
|
|
@@ -546,9 +546,9 @@ async function cpGet(apiUrl, path7) {
|
|
|
546
546
|
}
|
|
547
547
|
return await res.json();
|
|
548
548
|
}
|
|
549
|
-
async function cpPostJson(apiUrl,
|
|
549
|
+
async function cpPostJson(apiUrl, path8, body) {
|
|
550
550
|
const token = await getValidAccessToken();
|
|
551
|
-
const res = await fetch(`${apiUrl}${
|
|
551
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
552
552
|
method: "POST",
|
|
553
553
|
headers: {
|
|
554
554
|
Authorization: `Bearer ${token}`,
|
|
@@ -562,9 +562,9 @@ async function cpPostJson(apiUrl, path7, body) {
|
|
|
562
562
|
}
|
|
563
563
|
return await res.json();
|
|
564
564
|
}
|
|
565
|
-
async function cpDelete(apiUrl,
|
|
565
|
+
async function cpDelete(apiUrl, path8) {
|
|
566
566
|
const token = await getValidAccessToken();
|
|
567
|
-
const res = await fetch(`${apiUrl}${
|
|
567
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
568
568
|
method: "DELETE",
|
|
569
569
|
headers: { Authorization: `Bearer ${token}`, Accept: "application/json" }
|
|
570
570
|
});
|
|
@@ -572,9 +572,9 @@ async function cpDelete(apiUrl, path7) {
|
|
|
572
572
|
throw new CpError(res.status, await parseError(res));
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
|
-
async function cpPutJson(apiUrl,
|
|
575
|
+
async function cpPutJson(apiUrl, path8, body) {
|
|
576
576
|
const token = await getValidAccessToken();
|
|
577
|
-
const res = await fetch(`${apiUrl}${
|
|
577
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
578
578
|
method: "PUT",
|
|
579
579
|
headers: {
|
|
580
580
|
Authorization: `Bearer ${token}`,
|
|
@@ -588,9 +588,9 @@ async function cpPutJson(apiUrl, path7, body) {
|
|
|
588
588
|
}
|
|
589
589
|
return await res.json();
|
|
590
590
|
}
|
|
591
|
-
async function cpPatchJson(apiUrl,
|
|
591
|
+
async function cpPatchJson(apiUrl, path8, body) {
|
|
592
592
|
const token = await getValidAccessToken();
|
|
593
|
-
const res = await fetch(`${apiUrl}${
|
|
593
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
594
594
|
method: "PATCH",
|
|
595
595
|
headers: {
|
|
596
596
|
Authorization: `Bearer ${token}`,
|
|
@@ -604,9 +604,9 @@ async function cpPatchJson(apiUrl, path7, body) {
|
|
|
604
604
|
}
|
|
605
605
|
return await res.json();
|
|
606
606
|
}
|
|
607
|
-
async function cpPutBytes(apiUrl,
|
|
607
|
+
async function cpPutBytes(apiUrl, path8, body, contentType) {
|
|
608
608
|
const token = await getValidAccessToken();
|
|
609
|
-
const res = await fetch(`${apiUrl}${
|
|
609
|
+
const res = await fetch(`${apiUrl}${path8}`, {
|
|
610
610
|
method: "PUT",
|
|
611
611
|
headers: {
|
|
612
612
|
Authorization: `Bearer ${token}`,
|
|
@@ -724,6 +724,18 @@ function runCoderunner(apiUrl, workspaceId, coderunnerId, payload) {
|
|
|
724
724
|
payload
|
|
725
725
|
);
|
|
726
726
|
}
|
|
727
|
+
function getProcessLogs(apiUrl, workspaceId, query = {}) {
|
|
728
|
+
const params = new URLSearchParams();
|
|
729
|
+
for (const [key, value] of Object.entries(query)) {
|
|
730
|
+
if (value == null || value === "") continue;
|
|
731
|
+
params.set(key, String(value));
|
|
732
|
+
}
|
|
733
|
+
const qs = params.toString();
|
|
734
|
+
return cpGet(
|
|
735
|
+
apiUrl,
|
|
736
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/logs${qs ? `?${qs}` : ""}`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
727
739
|
function domainsBase(workspaceId, coderunnerId) {
|
|
728
740
|
return `/v1/workspaces/${encodeURIComponent(
|
|
729
741
|
workspaceId
|
|
@@ -1003,9 +1015,188 @@ async function coderunnerListCommand(opts) {
|
|
|
1003
1015
|
}
|
|
1004
1016
|
}
|
|
1005
1017
|
|
|
1018
|
+
// src/process-log.ts
|
|
1019
|
+
var PROCESS_STATUS_LABELS = {
|
|
1020
|
+
1: "Pending",
|
|
1021
|
+
2: "Processing",
|
|
1022
|
+
3: "Completed",
|
|
1023
|
+
4: "Failed",
|
|
1024
|
+
5: "Deleting",
|
|
1025
|
+
6: "Deleted",
|
|
1026
|
+
7: "Stopped"
|
|
1027
|
+
};
|
|
1028
|
+
function statusLabel(status) {
|
|
1029
|
+
if (status == null) return "unknown";
|
|
1030
|
+
const name = PROCESS_STATUS_LABELS[status];
|
|
1031
|
+
return name ? `${name} (${status})` : `status=${status}`;
|
|
1032
|
+
}
|
|
1033
|
+
function customerPortalOrigin(apiUrl) {
|
|
1034
|
+
try {
|
|
1035
|
+
const url = new URL(apiUrl);
|
|
1036
|
+
url.host = url.host.replace(/^platform\./, "app.");
|
|
1037
|
+
return url.origin;
|
|
1038
|
+
} catch {
|
|
1039
|
+
return "https://app.groundfloor.cloud";
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function processLogPortalUrl(portalOrigin, workspaceId, runnerId) {
|
|
1043
|
+
const params = new URLSearchParams({
|
|
1044
|
+
workspace_id: workspaceId,
|
|
1045
|
+
runner_id: runnerId
|
|
1046
|
+
});
|
|
1047
|
+
return `${portalOrigin.replace(/\/+$/, "")}/process-log?${params.toString()}`;
|
|
1048
|
+
}
|
|
1049
|
+
function formatLogPreview(entries, limit = 20) {
|
|
1050
|
+
const slice = entries.slice(0, limit);
|
|
1051
|
+
return [...slice].reverse().map((entry) => {
|
|
1052
|
+
const ts = entry.ts?.trim() ? `${entry.ts} ` : "";
|
|
1053
|
+
return `${ts}${entry.line}`;
|
|
1054
|
+
}).join("\n");
|
|
1055
|
+
}
|
|
1056
|
+
function formatDeployFailure(input) {
|
|
1057
|
+
const lines = [input.headline, ""];
|
|
1058
|
+
lines.push(`coderunner: ${input.coderunnerId}`);
|
|
1059
|
+
if (input.versionId) lines.push(`version: ${input.versionId}`);
|
|
1060
|
+
if (input.deploymentId) lines.push(`deployment: ${input.deploymentId}`);
|
|
1061
|
+
if (input.status != null) lines.push(`status: ${statusLabel(input.status)}`);
|
|
1062
|
+
if (input.logLines) {
|
|
1063
|
+
lines.push("", "logs:", input.logLines);
|
|
1064
|
+
} else {
|
|
1065
|
+
lines.push("", "logs: (none yet)");
|
|
1066
|
+
}
|
|
1067
|
+
if (input.portalUrl) {
|
|
1068
|
+
lines.push("", `portal: ${input.portalUrl}`);
|
|
1069
|
+
}
|
|
1070
|
+
lines.push(`more logs: gf coderunner logs -c ${input.coderunnerId}`);
|
|
1071
|
+
return lines.join("\n");
|
|
1072
|
+
}
|
|
1073
|
+
async function fetchFailureLogEntries(apiUrl, workspaceId, ids) {
|
|
1074
|
+
const attempts = [];
|
|
1075
|
+
if (ids.deploymentId) attempts.push({ deployment_id: ids.deploymentId });
|
|
1076
|
+
attempts.push({ runner_id: ids.coderunnerId });
|
|
1077
|
+
if (ids.versionId && ids.versionId !== ids.coderunnerId) {
|
|
1078
|
+
attempts.push({ runner_id: ids.versionId });
|
|
1079
|
+
}
|
|
1080
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1081
|
+
let lastError;
|
|
1082
|
+
let anyOk = false;
|
|
1083
|
+
for (const query of attempts) {
|
|
1084
|
+
const key = JSON.stringify(query);
|
|
1085
|
+
if (seen.has(key)) continue;
|
|
1086
|
+
seen.add(key);
|
|
1087
|
+
try {
|
|
1088
|
+
const page = await getProcessLogs(apiUrl, workspaceId, {
|
|
1089
|
+
...query,
|
|
1090
|
+
limit: 20,
|
|
1091
|
+
since_seconds: 3600
|
|
1092
|
+
});
|
|
1093
|
+
anyOk = true;
|
|
1094
|
+
if (page.entries?.length) return page.entries;
|
|
1095
|
+
} catch (err) {
|
|
1096
|
+
lastError = err;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (!anyOk && lastError) throw lastError;
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
1102
|
+
async function diagnoseDeployFailure(opts) {
|
|
1103
|
+
let logLines = "";
|
|
1104
|
+
try {
|
|
1105
|
+
const entries = await fetchFailureLogEntries(opts.apiUrl, opts.workspaceId, {
|
|
1106
|
+
coderunnerId: opts.coderunnerId,
|
|
1107
|
+
versionId: opts.versionId ?? void 0,
|
|
1108
|
+
deploymentId: opts.deploymentId ?? void 0
|
|
1109
|
+
});
|
|
1110
|
+
logLines = formatLogPreview(entries);
|
|
1111
|
+
} catch (err) {
|
|
1112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1113
|
+
logLines = `(could not fetch Process Log: ${message})`;
|
|
1114
|
+
}
|
|
1115
|
+
const runnerForPortal = opts.deploymentId || opts.coderunnerId;
|
|
1116
|
+
return formatDeployFailure({
|
|
1117
|
+
headline: opts.headline,
|
|
1118
|
+
coderunnerId: opts.coderunnerId,
|
|
1119
|
+
versionId: opts.versionId,
|
|
1120
|
+
deploymentId: opts.deploymentId,
|
|
1121
|
+
status: opts.status,
|
|
1122
|
+
logLines,
|
|
1123
|
+
portalUrl: processLogPortalUrl(
|
|
1124
|
+
customerPortalOrigin(opts.apiUrl),
|
|
1125
|
+
opts.workspaceId,
|
|
1126
|
+
runnerForPortal
|
|
1127
|
+
)
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/resolve-coderunner.ts
|
|
1132
|
+
async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
|
|
1133
|
+
const list = await listCoderunners(apiUrl, workspaceId);
|
|
1134
|
+
const match = list.find((c) => c.id === idOrSlug || c.slug === idOrSlug);
|
|
1135
|
+
if (!match) {
|
|
1136
|
+
throw new Error(`Coderunner not found: ${idOrSlug}`);
|
|
1137
|
+
}
|
|
1138
|
+
return match.id;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/commands/logs.ts
|
|
1142
|
+
async function logsCommand(opts) {
|
|
1143
|
+
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
1144
|
+
if (!cfg.workspaceId) {
|
|
1145
|
+
throw new Error(
|
|
1146
|
+
"No workspace selected. Run `gf workspaces use <id>` or pass --workspace <id>."
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
const coderunnerId = await resolveCoderunnerId(
|
|
1150
|
+
cfg.apiUrl,
|
|
1151
|
+
cfg.workspaceId,
|
|
1152
|
+
opts.coderunner
|
|
1153
|
+
);
|
|
1154
|
+
const limit = opts.limit ? Number(opts.limit) : 50;
|
|
1155
|
+
const sinceSeconds = opts.sinceSeconds ? Number(opts.sinceSeconds) : 3600;
|
|
1156
|
+
if (!Number.isFinite(limit) || limit < 1) {
|
|
1157
|
+
throw new Error("Invalid --limit (expected a positive number).");
|
|
1158
|
+
}
|
|
1159
|
+
if (!Number.isFinite(sinceSeconds) || sinceSeconds < 10) {
|
|
1160
|
+
throw new Error("Invalid --since-seconds (minimum 10).");
|
|
1161
|
+
}
|
|
1162
|
+
const page = await getProcessLogs(cfg.apiUrl, cfg.workspaceId, {
|
|
1163
|
+
runner_id: coderunnerId,
|
|
1164
|
+
limit,
|
|
1165
|
+
since_seconds: sinceSeconds,
|
|
1166
|
+
search: opts.search
|
|
1167
|
+
});
|
|
1168
|
+
if (opts.json) {
|
|
1169
|
+
process.stdout.write(`${JSON.stringify(page, null, 2)}
|
|
1170
|
+
`);
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
const preview = formatLogPreview(page.entries ?? [], limit);
|
|
1174
|
+
if (!preview) {
|
|
1175
|
+
process.stdout.write("No log entries.\n");
|
|
1176
|
+
} else {
|
|
1177
|
+
process.stdout.write(`${preview}
|
|
1178
|
+
`);
|
|
1179
|
+
}
|
|
1180
|
+
if (page.has_more) {
|
|
1181
|
+
process.stdout.write(
|
|
1182
|
+
`(more available \u2014 raise --limit or narrow --since-seconds)
|
|
1183
|
+
`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
process.stdout.write(
|
|
1187
|
+
`
|
|
1188
|
+
portal: ${processLogPortalUrl(
|
|
1189
|
+
customerPortalOrigin(cfg.apiUrl),
|
|
1190
|
+
cfg.workspaceId,
|
|
1191
|
+
coderunnerId
|
|
1192
|
+
)}
|
|
1193
|
+
`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1006
1197
|
// src/commands/deploy.ts
|
|
1007
|
-
import { promises as
|
|
1008
|
-
import
|
|
1198
|
+
import { promises as fs4 } from "fs";
|
|
1199
|
+
import path4 from "path";
|
|
1009
1200
|
|
|
1010
1201
|
// src/deploy/package.ts
|
|
1011
1202
|
import { execFile } from "child_process";
|
|
@@ -1173,6 +1364,45 @@ async function detectRuntime(dir) {
|
|
|
1173
1364
|
return null;
|
|
1174
1365
|
}
|
|
1175
1366
|
|
|
1367
|
+
// src/deploy/preflight.ts
|
|
1368
|
+
import { promises as fs3 } from "fs";
|
|
1369
|
+
import path3 from "path";
|
|
1370
|
+
var NODE_BOOTSTRAP_MISSING_START = 'Node packages without a Dockerfile use the platform bootstrap image (CMD ["npm","start"], EXPOSE 8080). Add package.json "scripts": { "start": "node <entrypoint>" }. The process must listen on process.env.PORT || 8080 and bind 0.0.0.0 (not 127.0.0.1).';
|
|
1371
|
+
var NODE_BOOTSTRAP_MISSING_PACKAGE = 'Node packages without a Dockerfile need package.json with "scripts.start" (bootstrap image runs `npm start` and probes 0.0.0.0:8080).';
|
|
1372
|
+
var NODE_DOCKERFILE_NPM_START_MISSING = 'Dockerfile runs `npm start` but package.json has no "scripts.start". Add "scripts": { "start": "node <entrypoint>" }.';
|
|
1373
|
+
function dockerfileRunsNpmStart(text) {
|
|
1374
|
+
return /npm["'\s,]+start/.test(text);
|
|
1375
|
+
}
|
|
1376
|
+
async function readOptional(filePath) {
|
|
1377
|
+
try {
|
|
1378
|
+
return await fs3.readFile(filePath, "utf8");
|
|
1379
|
+
} catch {
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
async function preflightNodePackage(sourceDir) {
|
|
1384
|
+
const dockerfile = await readOptional(path3.join(sourceDir, "Dockerfile"));
|
|
1385
|
+
const needsStartScript = dockerfile == null || dockerfileRunsNpmStart(dockerfile);
|
|
1386
|
+
if (!needsStartScript) return;
|
|
1387
|
+
const raw = await readOptional(path3.join(sourceDir, "package.json"));
|
|
1388
|
+
if (raw == null) {
|
|
1389
|
+
throw new Error(
|
|
1390
|
+
dockerfile == null ? NODE_BOOTSTRAP_MISSING_PACKAGE : NODE_DOCKERFILE_NPM_START_MISSING
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
let npm;
|
|
1394
|
+
try {
|
|
1395
|
+
npm = JSON.parse(raw);
|
|
1396
|
+
} catch {
|
|
1397
|
+
throw new Error("package.json is not valid JSON.");
|
|
1398
|
+
}
|
|
1399
|
+
if (!npm.scripts?.start?.trim()) {
|
|
1400
|
+
throw new Error(
|
|
1401
|
+
dockerfile == null ? NODE_BOOTSTRAP_MISSING_START : NODE_DOCKERFILE_NPM_START_MISSING
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1176
1406
|
// src/commands/deploy.ts
|
|
1177
1407
|
var RUNTIMES = /* @__PURE__ */ new Set(["python", "node", "dotnet-script"]);
|
|
1178
1408
|
var WORKLOAD_TYPES = /* @__PURE__ */ new Set(["function", "service", "job", "schedule"]);
|
|
@@ -1202,6 +1432,9 @@ function parseEnvPairs(pairs) {
|
|
|
1202
1432
|
}
|
|
1203
1433
|
var log = (msg) => process.stderr.write(`${msg}
|
|
1204
1434
|
`);
|
|
1435
|
+
async function failDeploy(opts) {
|
|
1436
|
+
throw new Error(await diagnoseDeployFailure(opts));
|
|
1437
|
+
}
|
|
1205
1438
|
async function deployCommand(opts) {
|
|
1206
1439
|
const cfg = await resolveConfig({
|
|
1207
1440
|
apiUrl: opts.apiUrl,
|
|
@@ -1214,7 +1447,7 @@ async function deployCommand(opts) {
|
|
|
1214
1447
|
}
|
|
1215
1448
|
const ws = cfg.workspaceId;
|
|
1216
1449
|
log(
|
|
1217
|
-
opts.git ? `Cloning ${opts.git}${opts.ref ? ` (${opts.ref})` : ""}...` : `Packaging ${
|
|
1450
|
+
opts.git ? `Cloning ${opts.git}${opts.ref ? ` (${opts.ref})` : ""}...` : `Packaging ${path4.resolve(opts.path ?? process.cwd())}...`
|
|
1218
1451
|
);
|
|
1219
1452
|
const pkg = await packageSource({
|
|
1220
1453
|
path: opts.path,
|
|
@@ -1241,7 +1474,7 @@ async function deployCommand(opts) {
|
|
|
1241
1474
|
}
|
|
1242
1475
|
if (workloadType === "service") {
|
|
1243
1476
|
try {
|
|
1244
|
-
await
|
|
1477
|
+
await fs4.access(path4.join(pkg.sourceDir, "Dockerfile"));
|
|
1245
1478
|
} catch {
|
|
1246
1479
|
throw new Error(
|
|
1247
1480
|
'workload type "service" (also called Deployment) requires a Dockerfile at the project root.'
|
|
@@ -1249,26 +1482,11 @@ async function deployCommand(opts) {
|
|
|
1249
1482
|
}
|
|
1250
1483
|
}
|
|
1251
1484
|
if (runtime === "node") {
|
|
1252
|
-
|
|
1253
|
-
const raw = await fs3.readFile(
|
|
1254
|
-
path3.join(pkg.sourceDir, "package.json"),
|
|
1255
|
-
"utf8"
|
|
1256
|
-
);
|
|
1257
|
-
const npm = JSON.parse(raw);
|
|
1258
|
-
if (!npm.scripts?.start?.trim()) {
|
|
1259
|
-
throw new Error(
|
|
1260
|
-
'Node Coderunner packages require package.json "scripts.start" (runtime CMD is npm start).'
|
|
1261
|
-
);
|
|
1262
|
-
}
|
|
1263
|
-
} catch (err) {
|
|
1264
|
-
if (err instanceof Error && err.message.includes("scripts.start")) {
|
|
1265
|
-
throw err;
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1485
|
+
await preflightNodePackage(pkg.sourceDir);
|
|
1268
1486
|
}
|
|
1269
1487
|
let coderunnerId = opts.coderunner;
|
|
1270
1488
|
if (!coderunnerId) {
|
|
1271
|
-
const name = opts.name ?? manifest.name ?? (opts.git ? nameFromGitUrl(opts.git) :
|
|
1489
|
+
const name = opts.name ?? manifest.name ?? (opts.git ? nameFromGitUrl(opts.git) : path4.basename(path4.resolve(opts.path ?? process.cwd())));
|
|
1272
1490
|
const slug = manifest.slug ?? slugify(name);
|
|
1273
1491
|
const existing = await listCoderunners(cfg.apiUrl, ws);
|
|
1274
1492
|
const match = existing.find((c) => c.slug === slug || c.name === name);
|
|
@@ -1317,16 +1535,38 @@ async function deployCommand(opts) {
|
|
|
1317
1535
|
break;
|
|
1318
1536
|
}
|
|
1319
1537
|
if (status === 4) {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1538
|
+
return await failDeploy({
|
|
1539
|
+
apiUrl: cfg.apiUrl,
|
|
1540
|
+
workspaceId: ws,
|
|
1541
|
+
headline: `Build failed for version ${uploaded.version_id} (${statusLabel(4)}).`,
|
|
1542
|
+
coderunnerId,
|
|
1543
|
+
versionId: uploaded.version_id,
|
|
1544
|
+
status: 4
|
|
1545
|
+
});
|
|
1323
1546
|
}
|
|
1324
1547
|
process.stderr.write(".");
|
|
1325
1548
|
await new Promise((r) => setTimeout(r, VERSION_POLL_INTERVAL_MS));
|
|
1326
1549
|
}
|
|
1327
1550
|
process.stderr.write("\n");
|
|
1328
1551
|
if (!built) {
|
|
1329
|
-
|
|
1552
|
+
let lastStatus = null;
|
|
1553
|
+
try {
|
|
1554
|
+
lastStatus = (await getVersion(
|
|
1555
|
+
cfg.apiUrl,
|
|
1556
|
+
ws,
|
|
1557
|
+
coderunnerId,
|
|
1558
|
+
uploaded.version_id
|
|
1559
|
+
)).status ?? null;
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
return await failDeploy({
|
|
1563
|
+
apiUrl: cfg.apiUrl,
|
|
1564
|
+
workspaceId: ws,
|
|
1565
|
+
headline: "Timed out waiting for the build to complete.",
|
|
1566
|
+
coderunnerId,
|
|
1567
|
+
versionId: uploaded.version_id,
|
|
1568
|
+
status: lastStatus
|
|
1569
|
+
});
|
|
1330
1570
|
}
|
|
1331
1571
|
log("Build completed.");
|
|
1332
1572
|
if (opts.skipDeploy) {
|
|
@@ -1358,16 +1598,38 @@ async function deployCommand(opts) {
|
|
|
1358
1598
|
break;
|
|
1359
1599
|
}
|
|
1360
1600
|
if (dStatus === 4) {
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1601
|
+
return await failDeploy({
|
|
1602
|
+
apiUrl: cfg.apiUrl,
|
|
1603
|
+
workspaceId: ws,
|
|
1604
|
+
headline: `Deployment failed for coderunner ${coderunnerId} (${statusLabel(4)}).`,
|
|
1605
|
+
coderunnerId,
|
|
1606
|
+
versionId: uploaded.version_id,
|
|
1607
|
+
deploymentId: result.deployment_id ?? deployment?.id,
|
|
1608
|
+
status: 4
|
|
1609
|
+
});
|
|
1364
1610
|
}
|
|
1365
1611
|
process.stderr.write(".");
|
|
1366
1612
|
await new Promise((r) => setTimeout(r, DEPLOY_POLL_INTERVAL_MS));
|
|
1367
1613
|
}
|
|
1368
1614
|
process.stderr.write("\n");
|
|
1369
1615
|
if (!deployOk) {
|
|
1370
|
-
|
|
1616
|
+
let lastStatus = null;
|
|
1617
|
+
let deploymentId = result.deployment_id;
|
|
1618
|
+
try {
|
|
1619
|
+
const latest = await getDeployment(cfg.apiUrl, ws, coderunnerId);
|
|
1620
|
+
lastStatus = latest?.status ?? null;
|
|
1621
|
+
deploymentId = latest?.id ?? deploymentId;
|
|
1622
|
+
} catch {
|
|
1623
|
+
}
|
|
1624
|
+
return await failDeploy({
|
|
1625
|
+
apiUrl: cfg.apiUrl,
|
|
1626
|
+
workspaceId: ws,
|
|
1627
|
+
headline: "Timed out waiting for the deployment to complete.",
|
|
1628
|
+
coderunnerId,
|
|
1629
|
+
versionId: uploaded.version_id,
|
|
1630
|
+
deploymentId,
|
|
1631
|
+
status: lastStatus
|
|
1632
|
+
});
|
|
1371
1633
|
}
|
|
1372
1634
|
log("Deployment completed.");
|
|
1373
1635
|
process.stdout.write(`
|
|
@@ -1388,21 +1650,13 @@ Deployed coderunner ${coderunnerId}
|
|
|
1388
1650
|
}
|
|
1389
1651
|
} finally {
|
|
1390
1652
|
if (pkg.cleanup) {
|
|
1391
|
-
await
|
|
1653
|
+
await fs4.rm(pkg.cleanup, { recursive: true, force: true }).catch(() => {
|
|
1392
1654
|
});
|
|
1393
1655
|
}
|
|
1394
1656
|
}
|
|
1395
1657
|
}
|
|
1396
1658
|
|
|
1397
1659
|
// src/commands/run.ts
|
|
1398
|
-
async function resolveCoderunnerId(apiUrl, workspaceId, idOrSlug) {
|
|
1399
|
-
const list = await listCoderunners(apiUrl, workspaceId);
|
|
1400
|
-
const match = list.find((c) => c.id === idOrSlug || c.slug === idOrSlug);
|
|
1401
|
-
if (!match) {
|
|
1402
|
-
throw new Error(`Coderunner not found: ${idOrSlug}`);
|
|
1403
|
-
}
|
|
1404
|
-
return match.id;
|
|
1405
|
-
}
|
|
1406
1660
|
async function runCommand(opts) {
|
|
1407
1661
|
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
1408
1662
|
if (!cfg.workspaceId) {
|
|
@@ -1436,14 +1690,6 @@ async function runCommand(opts) {
|
|
|
1436
1690
|
}
|
|
1437
1691
|
|
|
1438
1692
|
// src/commands/status.ts
|
|
1439
|
-
async function resolveCoderunnerId2(apiUrl, workspaceId, idOrSlug) {
|
|
1440
|
-
const list = await listCoderunners(apiUrl, workspaceId);
|
|
1441
|
-
const match = list.find((c) => c.id === idOrSlug || c.slug === idOrSlug);
|
|
1442
|
-
if (!match) {
|
|
1443
|
-
throw new Error(`Coderunner not found: ${idOrSlug}`);
|
|
1444
|
-
}
|
|
1445
|
-
return match.id;
|
|
1446
|
-
}
|
|
1447
1693
|
async function statusCommand(opts) {
|
|
1448
1694
|
const cfg = await resolveConfig({ workspaceId: opts.workspace });
|
|
1449
1695
|
if (!cfg.workspaceId) {
|
|
@@ -1451,7 +1697,7 @@ async function statusCommand(opts) {
|
|
|
1451
1697
|
"No workspace selected. Run `gf workspaces use <id>` or pass --workspace <id>."
|
|
1452
1698
|
);
|
|
1453
1699
|
}
|
|
1454
|
-
const coderunnerId = await
|
|
1700
|
+
const coderunnerId = await resolveCoderunnerId(
|
|
1455
1701
|
cfg.apiUrl,
|
|
1456
1702
|
cfg.workspaceId,
|
|
1457
1703
|
opts.coderunner
|
|
@@ -1634,10 +1880,10 @@ async function secretsRmCommand(key, opts) {
|
|
|
1634
1880
|
}
|
|
1635
1881
|
|
|
1636
1882
|
// src/commands/files.ts
|
|
1637
|
-
import { promises as
|
|
1638
|
-
import
|
|
1883
|
+
import { promises as fs5 } from "fs";
|
|
1884
|
+
import path5 from "path";
|
|
1639
1885
|
function guessContentType(filename) {
|
|
1640
|
-
const ext =
|
|
1886
|
+
const ext = path5.extname(filename).toLowerCase();
|
|
1641
1887
|
const types = {
|
|
1642
1888
|
".json": "application/json",
|
|
1643
1889
|
".txt": "text/plain",
|
|
@@ -1672,9 +1918,9 @@ async function filesListCommand(opts) {
|
|
|
1672
1918
|
}
|
|
1673
1919
|
async function filesUploadCommand(filePath, opts) {
|
|
1674
1920
|
const cfg = await requireWorkspace(opts.workspace);
|
|
1675
|
-
const resolved =
|
|
1676
|
-
const buf = await
|
|
1677
|
-
const name = opts.name ??
|
|
1921
|
+
const resolved = path5.resolve(filePath);
|
|
1922
|
+
const buf = await fs5.readFile(resolved);
|
|
1923
|
+
const name = opts.name ?? path5.basename(resolved);
|
|
1678
1924
|
const contentType = guessContentType(name);
|
|
1679
1925
|
const init = await initFileUpload(cfg.apiUrl, cfg.workspaceId, {
|
|
1680
1926
|
name,
|
|
@@ -1711,8 +1957,8 @@ async function filesDownloadCommand(fileId, opts) {
|
|
|
1711
1957
|
throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
1712
1958
|
}
|
|
1713
1959
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
1714
|
-
const dest =
|
|
1715
|
-
await
|
|
1960
|
+
const dest = path5.resolve(opts.out ?? fileId);
|
|
1961
|
+
await fs5.writeFile(dest, buf);
|
|
1716
1962
|
process.stdout.write(`Wrote ${dest}
|
|
1717
1963
|
`);
|
|
1718
1964
|
}
|
|
@@ -1724,8 +1970,8 @@ async function filesRmCommand(fileId, opts) {
|
|
|
1724
1970
|
}
|
|
1725
1971
|
|
|
1726
1972
|
// src/commands/apps.ts
|
|
1727
|
-
import { promises as
|
|
1728
|
-
import
|
|
1973
|
+
import { promises as fs6 } from "fs";
|
|
1974
|
+
import path6 from "path";
|
|
1729
1975
|
|
|
1730
1976
|
// src/release-zip.ts
|
|
1731
1977
|
import AdmZip2 from "adm-zip";
|
|
@@ -1765,8 +2011,8 @@ function flattenFederatedBundlePlan(relpaths) {
|
|
|
1765
2011
|
const promoteDirs = /* @__PURE__ */ new Set();
|
|
1766
2012
|
const entryDir = parentDir(entry);
|
|
1767
2013
|
if (entryDir) promoteDirs.add(entryDir);
|
|
1768
|
-
for (const
|
|
1769
|
-
const parent = parentDir(
|
|
2014
|
+
for (const path8 of pathset) {
|
|
2015
|
+
const parent = parentDir(path8);
|
|
1770
2016
|
if (parent === "assets" || parent.endsWith("/assets") && parent.split("/").length - 1 < 2) {
|
|
1771
2017
|
promoteDirs.add(parent);
|
|
1772
2018
|
}
|
|
@@ -1897,7 +2143,7 @@ function formatApp(app) {
|
|
|
1897
2143
|
return `${app.id} ${app.name ?? app.slug ?? ""} ${app.app_kind ?? ""} [${app.status ?? ""}]`;
|
|
1898
2144
|
}
|
|
1899
2145
|
async function readJsonObject(filePath) {
|
|
1900
|
-
const raw = await
|
|
2146
|
+
const raw = await fs6.readFile(filePath, "utf8");
|
|
1901
2147
|
const parsed = JSON.parse(raw);
|
|
1902
2148
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1903
2149
|
throw new Error(`${filePath} must contain a JSON object`);
|
|
@@ -1906,13 +2152,13 @@ async function readJsonObject(filePath) {
|
|
|
1906
2152
|
}
|
|
1907
2153
|
async function maybeReadManifest(filePath) {
|
|
1908
2154
|
if (!filePath) return void 0;
|
|
1909
|
-
return readJsonObject(
|
|
2155
|
+
return readJsonObject(path6.resolve(filePath));
|
|
1910
2156
|
}
|
|
1911
2157
|
async function findSiblingManifest(bundlePath) {
|
|
1912
|
-
const dir = await isDirectory(bundlePath) ? bundlePath :
|
|
1913
|
-
const candidate =
|
|
2158
|
+
const dir = await isDirectory(bundlePath) ? bundlePath : path6.dirname(bundlePath);
|
|
2159
|
+
const candidate = path6.join(dir, "groundfloor.manifest.json");
|
|
1914
2160
|
try {
|
|
1915
|
-
await
|
|
2161
|
+
await fs6.access(candidate);
|
|
1916
2162
|
} catch {
|
|
1917
2163
|
return void 0;
|
|
1918
2164
|
}
|
|
@@ -1948,7 +2194,7 @@ Prefer: ${federatedReleaseZipHint(slug)}
|
|
|
1948
2194
|
}
|
|
1949
2195
|
async function isDirectory(filePath) {
|
|
1950
2196
|
try {
|
|
1951
|
-
return (await
|
|
2197
|
+
return (await fs6.stat(filePath)).isDirectory();
|
|
1952
2198
|
} catch {
|
|
1953
2199
|
return false;
|
|
1954
2200
|
}
|
|
@@ -2068,11 +2314,11 @@ Do not scaffold src/App.tsx from scratch \u2014 BrowserRouter in App.tsx crashes
|
|
|
2068
2314
|
}
|
|
2069
2315
|
async function appsPublishCommand(appRef, opts) {
|
|
2070
2316
|
const cfg = await requireWorkspace(opts.workspace);
|
|
2071
|
-
const inputPath =
|
|
2072
|
-
const bundlePath = await isDirectory(inputPath) ?
|
|
2317
|
+
const inputPath = path6.resolve(opts.path ?? "release.zip");
|
|
2318
|
+
const bundlePath = await isDirectory(inputPath) ? path6.join(inputPath, "release.zip") : inputPath;
|
|
2073
2319
|
let bytes;
|
|
2074
2320
|
try {
|
|
2075
|
-
bytes = await
|
|
2321
|
+
bytes = await fs6.readFile(bundlePath);
|
|
2076
2322
|
} catch {
|
|
2077
2323
|
throw new Error(
|
|
2078
2324
|
`No bundle at ${bundlePath}. ${initThenPublishHint()}`
|
|
@@ -2099,11 +2345,11 @@ async function appsPublishCommand(appRef, opts) {
|
|
|
2099
2345
|
`Releases are only for shell_federated apps (this app is ${app.app_kind}). Use \`gf deploy\` for coderunner workloads.`
|
|
2100
2346
|
);
|
|
2101
2347
|
}
|
|
2102
|
-
const ext =
|
|
2348
|
+
const ext = path6.extname(bundlePath).toLowerCase();
|
|
2103
2349
|
const isZip = ext === ".zip";
|
|
2104
2350
|
if (!isZip) {
|
|
2105
2351
|
throw new Error(
|
|
2106
|
-
`Publish release.zip from \`npm run release\`, not a lone ${
|
|
2352
|
+
`Publish release.zip from \`npm run release\`, not a lone ${path6.basename(bundlePath)}. ` + initThenPublishHint(app.slug)
|
|
2107
2353
|
);
|
|
2108
2354
|
}
|
|
2109
2355
|
bytes = prepareReleaseZip(bytes, app.slug);
|
|
@@ -2143,9 +2389,9 @@ async function appsPublishCommand(appRef, opts) {
|
|
|
2143
2389
|
}
|
|
2144
2390
|
|
|
2145
2391
|
// src/commands/apps-init.ts
|
|
2146
|
-
import { promises as
|
|
2392
|
+
import { promises as fs7 } from "fs";
|
|
2147
2393
|
import os3 from "os";
|
|
2148
|
-
import
|
|
2394
|
+
import path7 from "path";
|
|
2149
2395
|
import AdmZip4 from "adm-zip";
|
|
2150
2396
|
var MAX_KIT_BYTES = 8 * 1024 * 1024;
|
|
2151
2397
|
function slugify3(input) {
|
|
@@ -2157,7 +2403,7 @@ function titleFromSlug(slug) {
|
|
|
2157
2403
|
}
|
|
2158
2404
|
async function pathExists(filePath) {
|
|
2159
2405
|
try {
|
|
2160
|
-
await
|
|
2406
|
+
await fs7.access(filePath);
|
|
2161
2407
|
return true;
|
|
2162
2408
|
} catch {
|
|
2163
2409
|
return false;
|
|
@@ -2165,7 +2411,7 @@ async function pathExists(filePath) {
|
|
|
2165
2411
|
}
|
|
2166
2412
|
async function isEmptyDir(dir) {
|
|
2167
2413
|
try {
|
|
2168
|
-
const entries = await
|
|
2414
|
+
const entries = await fs7.readdir(dir);
|
|
2169
2415
|
return entries.length === 0;
|
|
2170
2416
|
} catch (err) {
|
|
2171
2417
|
if (err.code === "ENOENT") return true;
|
|
@@ -2174,17 +2420,17 @@ async function isEmptyDir(dir) {
|
|
|
2174
2420
|
}
|
|
2175
2421
|
async function findKitRoot(extracted) {
|
|
2176
2422
|
const candidates = [
|
|
2177
|
-
|
|
2423
|
+
path7.join(extracted, "shell-starter-kit"),
|
|
2178
2424
|
extracted
|
|
2179
2425
|
];
|
|
2180
|
-
const entries = await
|
|
2426
|
+
const entries = await fs7.readdir(extracted, { withFileTypes: true });
|
|
2181
2427
|
for (const entry of entries) {
|
|
2182
2428
|
if (entry.isDirectory()) {
|
|
2183
|
-
candidates.push(
|
|
2429
|
+
candidates.push(path7.join(extracted, entry.name));
|
|
2184
2430
|
}
|
|
2185
2431
|
}
|
|
2186
2432
|
for (const candidate of candidates) {
|
|
2187
|
-
if (await pathExists(
|
|
2433
|
+
if (await pathExists(path7.join(candidate, "groundfloor.manifest.json"))) {
|
|
2188
2434
|
return candidate;
|
|
2189
2435
|
}
|
|
2190
2436
|
}
|
|
@@ -2266,7 +2512,7 @@ async function appsInitCommand(opts) {
|
|
|
2266
2512
|
`);
|
|
2267
2513
|
}
|
|
2268
2514
|
const name = opts.name?.trim() || titleFromSlug(slug);
|
|
2269
|
-
const dest =
|
|
2515
|
+
const dest = path7.resolve(opts.dir?.trim() || path7.join(process.cwd(), slug));
|
|
2270
2516
|
const cfg = await resolveConfig();
|
|
2271
2517
|
const cell = cfg.environment ?? "production";
|
|
2272
2518
|
const kitUrl = opts.kitUrl?.trim() || starterKitZipUrl(cell);
|
|
@@ -2276,26 +2522,26 @@ async function appsInitCommand(opts) {
|
|
|
2276
2522
|
);
|
|
2277
2523
|
}
|
|
2278
2524
|
if (opts.force && await pathExists(dest)) {
|
|
2279
|
-
await
|
|
2525
|
+
await fs7.rm(dest, { recursive: true, force: true });
|
|
2280
2526
|
}
|
|
2281
2527
|
const bytes = await downloadKit(kitUrl);
|
|
2282
2528
|
const zip = new AdmZip4(bytes);
|
|
2283
2529
|
assertSafeZipEntries(zip);
|
|
2284
|
-
const tmp = await
|
|
2530
|
+
const tmp = await fs7.mkdtemp(path7.join(os3.tmpdir(), "gf-starter-kit-"));
|
|
2285
2531
|
try {
|
|
2286
2532
|
zip.extractAllTo(tmp, true);
|
|
2287
2533
|
const kitRoot = await findKitRoot(tmp);
|
|
2288
|
-
await
|
|
2289
|
-
await
|
|
2534
|
+
await fs7.mkdir(dest, { recursive: true });
|
|
2535
|
+
await fs7.cp(kitRoot, dest, { recursive: true, force: true });
|
|
2290
2536
|
} finally {
|
|
2291
|
-
await
|
|
2292
|
-
}
|
|
2293
|
-
const identityPath =
|
|
2294
|
-
const manifestPath =
|
|
2295
|
-
const identity = await
|
|
2296
|
-
await
|
|
2297
|
-
const manifest = await
|
|
2298
|
-
await
|
|
2537
|
+
await fs7.rm(tmp, { recursive: true, force: true });
|
|
2538
|
+
}
|
|
2539
|
+
const identityPath = path7.join(dest, "src", "appIdentity.ts");
|
|
2540
|
+
const manifestPath = path7.join(dest, "groundfloor.manifest.json");
|
|
2541
|
+
const identity = await fs7.readFile(identityPath, "utf8");
|
|
2542
|
+
await fs7.writeFile(identityPath, stampAppIdentity(identity, slug), "utf8");
|
|
2543
|
+
const manifest = await fs7.readFile(manifestPath, "utf8");
|
|
2544
|
+
await fs7.writeFile(manifestPath, stampManifest(manifest, slug, name), "utf8");
|
|
2299
2545
|
const result = {
|
|
2300
2546
|
dir: dest,
|
|
2301
2547
|
slug,
|
|
@@ -2378,7 +2624,7 @@ function run(fn) {
|
|
|
2378
2624
|
var program = new Command();
|
|
2379
2625
|
program.name("gf").description(
|
|
2380
2626
|
"Groundfloor CLI \u2014 sign in, deploy coderunners, publish Shell apps, and manage workspace resources."
|
|
2381
|
-
).version("0.1.
|
|
2627
|
+
).version("0.1.5");
|
|
2382
2628
|
program.command("login").description(
|
|
2383
2629
|
"Log in via the browser (production by default; --dev / --stage for other cells)"
|
|
2384
2630
|
).option("--dev", "Sign in to Groundfloor dev").option("--stage", "Sign in to Groundfloor stage").option(
|
|
@@ -2403,6 +2649,18 @@ workspaces.command("use <workspaceId>").description("Set the default workspace")
|
|
|
2403
2649
|
var coderunner = program.command("coderunner").alias("cr").description("Coderunner commands");
|
|
2404
2650
|
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)));
|
|
2405
2651
|
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)));
|
|
2652
|
+
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(
|
|
2653
|
+
(opts) => run(
|
|
2654
|
+
() => logsCommand({
|
|
2655
|
+
workspace: opts.workspace,
|
|
2656
|
+
coderunner: opts.coderunner,
|
|
2657
|
+
limit: opts.limit,
|
|
2658
|
+
sinceSeconds: opts.sinceSeconds,
|
|
2659
|
+
search: opts.search,
|
|
2660
|
+
json: opts.json
|
|
2661
|
+
})
|
|
2662
|
+
)
|
|
2663
|
+
);
|
|
2406
2664
|
coderunner.command("run").description("Invoke a deployed function/job (smoke run)").requiredOption("-c, --coderunner <id|slug>", "Coderunner id or slug").option("-w, --workspace <id>", "Workspace id override").option("--payload <json>", "JSON payload object", "{}").option("--json", "Output raw JSON").action(
|
|
2407
2665
|
(opts) => run(
|
|
2408
2666
|
() => runCommand({
|
package/package.json
CHANGED