lagora-cli 1.1.0
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 +138 -0
- package/dist/help.txt +70 -0
- package/dist/lagora.js +342 -0
- package/dist/report-help.txt +5 -0
- package/dist/scripts/agora_playground_harness.py +263 -0
- package/dist/scripts/announce.js +41 -0
- package/dist/scripts/check-kernel-submission.py +90 -0
- package/dist/scripts/chunk-2EAJVB5D.js +100 -0
- package/dist/scripts/chunk-2KTLCUFI.js +29 -0
- package/dist/scripts/chunk-AZ3EEBVD.js +137 -0
- package/dist/scripts/chunk-NBJMYAOA.js +2128 -0
- package/dist/scripts/chunk-NCJMUBTG.js +125 -0
- package/dist/scripts/chunk-QJPQHKIO.js +23 -0
- package/dist/scripts/chunk-RIR5KGHC.js +33 -0
- package/dist/scripts/chunk-TJZVQYBL.js +8 -0
- package/dist/scripts/chunk-UHJXD4TG.js +18 -0
- package/dist/scripts/chunk-UQ6I6VTY.js +117 -0
- package/dist/scripts/cli-auth.js +348 -0
- package/dist/scripts/cli-config-IA7EOSYD.js +7 -0
- package/dist/scripts/install-skill.js +199 -0
- package/dist/scripts/issue-local-client-DZUXZOKY.js +22 -0
- package/dist/scripts/issue-search.js +1823 -0
- package/dist/scripts/issue.js +386 -0
- package/dist/scripts/keycloak-provision.js +986 -0
- package/dist/scripts/legato-fsim-runner.py +126 -0
- package/dist/scripts/legato-lowering-runner.py +156 -0
- package/dist/scripts/legato_runner_annotations.py +235 -0
- package/dist/scripts/legato_runner_env.py +91 -0
- package/dist/scripts/legato_runner_launchers.py +287 -0
- package/dist/scripts/legato_runner_script_wrapper.py +193 -0
- package/dist/scripts/notifications-EU43SIEV.js +624 -0
- package/dist/scripts/playground.js +408 -0
- package/dist/scripts/report-bundle-sync-3U7QTP4Z.js +215 -0
- package/dist/scripts/report.js +104 -0
- package/dist/scripts/resolve-sdk-package-version.py +151 -0
- package/dist/scripts/sdk-runtime-JE6H2PB2.js +992 -0
- package/dist/scripts/sdk-runtime-kubernetes-job-KOWL4ITV.js +479 -0
- package/dist/scripts/sdk-runtime-smoke.py +168 -0
- package/dist/scripts/sdk.js +256 -0
- package/dist/scripts/site-feedback-CAPE5MPX.js +136 -0
- package/dist/scripts/site-feedback-rate-limit-5BU2WSFE.js +86 -0
- package/dist/scripts/site-feedback.js +117 -0
- package/dist/scripts/storage-234FBH54.js +67 -0
- package/dist/scripts/submit-issue.sh +489 -0
- package/dist/scripts/verification-3QCY66QW.js +772 -0
- package/dist/scripts/verify-issue.js +144 -0
- package/dist/skills/legato-agora-cli/SKILL.md +556 -0
- package/dist/skills/legato-agora-cli/agents/openai.yaml +7 -0
- package/dist/skills/legato-agora-cli/reference/kernel-with-golden.py +84 -0
- package/dist/skills/legato-site-feedback/SKILL.md +49 -0
- package/dist/skills/legato-site-feedback/agents/openai.yaml +7 -0
- package/package.json +16 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LagoraCliConfigStore,
|
|
3
|
+
apiSession,
|
|
4
|
+
baseApiUrl,
|
|
5
|
+
readJsonFromApi,
|
|
6
|
+
requestHeaders,
|
|
7
|
+
writeJsonToApi
|
|
8
|
+
} from "./chunk-AZ3EEBVD.js";
|
|
9
|
+
import "./chunk-TJZVQYBL.js";
|
|
10
|
+
|
|
11
|
+
// scripts/playground.ts
|
|
12
|
+
import { readFile } from "node:fs/promises";
|
|
13
|
+
import path2 from "node:path";
|
|
14
|
+
|
|
15
|
+
// scripts/playground-submission-contract.ts
|
|
16
|
+
import { execFile } from "node:child_process";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { promisify } from "node:util";
|
|
19
|
+
var execFileAsync = promisify(execFile);
|
|
20
|
+
async function hasPlaygroundContract(kernelPath) {
|
|
21
|
+
const scriptsDir = import.meta.dirname;
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await execFileAsync("python3", ["-c", `
|
|
24
|
+
import json, sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
sys.path.insert(0, ${JSON.stringify(scriptsDir)})
|
|
27
|
+
from agora_playground_harness import has_playground_contract
|
|
28
|
+
from legato_runner_launchers import kernel_has_launcher, statically_decorated_kernel_names
|
|
29
|
+
|
|
30
|
+
path = Path(sys.argv[1])
|
|
31
|
+
kernels = sorted(statically_decorated_kernel_names(path))
|
|
32
|
+
print(json.dumps({
|
|
33
|
+
"ok": has_playground_contract(path),
|
|
34
|
+
"kernels": kernels,
|
|
35
|
+
"launcher": kernel_has_launcher(path),
|
|
36
|
+
}))
|
|
37
|
+
`.trim(), path.resolve(kernelPath)]);
|
|
38
|
+
const payload = JSON.parse(stdout);
|
|
39
|
+
if (!isRecord(payload) || !Array.isArray(payload.kernels) || !payload.kernels.every((item) => typeof item === "string")) {
|
|
40
|
+
return { ok: false, error: "Production dispatch returned an invalid submission verdict" };
|
|
41
|
+
}
|
|
42
|
+
if (payload.ok === true) return { ok: true, kernels: payload.kernels };
|
|
43
|
+
if (payload.kernels.length === 0) {
|
|
44
|
+
return { ok: false, error: "submission must define a module-level @legato.compile or @legato.primitive kernel" };
|
|
45
|
+
}
|
|
46
|
+
if (payload.launcher === true) {
|
|
47
|
+
return { ok: false, error: "submission must not define a launcher; the Agora harness owns stage selection" };
|
|
48
|
+
}
|
|
49
|
+
return { ok: false, error: "submission does not satisfy the Agora Playground contract" };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
52
|
+
return { ok: false, error: `cannot inspect Playground submission: ${detail}` };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function isRecord(value) {
|
|
56
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// scripts/playground-api-client.ts
|
|
60
|
+
async function createPlaygroundRun(input) {
|
|
61
|
+
const payload = await writeJsonToApi(`${baseApiUrl(input.session.apiUrl)}/api/playground/runs`, "POST", {
|
|
62
|
+
filename: input.filename,
|
|
63
|
+
source: input.source,
|
|
64
|
+
...input.sdkName ? { sdkName: input.sdkName } : {},
|
|
65
|
+
...input.legatoSdkName ? { legatoSdkName: input.legatoSdkName } : {},
|
|
66
|
+
...input.legatoVersion ? { legatoVersion: input.legatoVersion } : {},
|
|
67
|
+
...input.sdkVersion ? { sdkVersion: input.sdkVersion } : {},
|
|
68
|
+
mode: input.mode,
|
|
69
|
+
profile: input.profile
|
|
70
|
+
}, input.session);
|
|
71
|
+
return readRunEnvelope(payload);
|
|
72
|
+
}
|
|
73
|
+
async function readPlaygroundRun(session, id) {
|
|
74
|
+
const payload = await readJsonFromApi(
|
|
75
|
+
`${baseApiUrl(session.apiUrl)}/api/playground/runs/${encodeURIComponent(id)}`,
|
|
76
|
+
session
|
|
77
|
+
);
|
|
78
|
+
if (!isRecord2(payload)) throw new Error("Playground API returned an invalid run detail");
|
|
79
|
+
const source = Reflect.get(payload, "source");
|
|
80
|
+
const log = Reflect.get(payload, "log");
|
|
81
|
+
const artifacts = Reflect.get(payload, "artifacts");
|
|
82
|
+
if (typeof source !== "string" || typeof log !== "string" || !isStringArray(artifacts)) {
|
|
83
|
+
throw new Error("Playground API returned an invalid run detail");
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
run: readRun(Reflect.get(payload, "run")),
|
|
87
|
+
source,
|
|
88
|
+
log,
|
|
89
|
+
artifacts
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
async function listPlaygroundRuntimes(session) {
|
|
93
|
+
const payload = await readJsonFromApi(`${baseApiUrl(session.apiUrl)}/api/playground/runtimes`, session);
|
|
94
|
+
if (!isRecord2(payload)) throw new Error("Playground API returned an invalid runtime list");
|
|
95
|
+
const runtimes = Reflect.get(payload, "runtimes");
|
|
96
|
+
if (!Array.isArray(runtimes)) throw new Error("Playground API returned an invalid runtime list");
|
|
97
|
+
return runtimes.map(readRuntime);
|
|
98
|
+
}
|
|
99
|
+
async function reportPlaygroundRun(input) {
|
|
100
|
+
if (input.detail.run.status !== "complete" && input.detail.run.status !== "error") {
|
|
101
|
+
throw new Error(`Playground run ${input.detail.run.id} must finish before it can be reported`);
|
|
102
|
+
}
|
|
103
|
+
const endpoint = `${baseApiUrl(input.session.apiUrl)}/api/report-bundles`;
|
|
104
|
+
const form = new FormData();
|
|
105
|
+
form.set("title", input.title);
|
|
106
|
+
form.set("reporter", input.reporter);
|
|
107
|
+
form.set("description", input.description);
|
|
108
|
+
form.set("tags", input.tags);
|
|
109
|
+
form.set("idempotencyKey", `playground-run-${input.detail.run.id}`);
|
|
110
|
+
form.set("gitMetadata", JSON.stringify({}));
|
|
111
|
+
form.set("environmentMetadata", JSON.stringify(playgroundEnvironment(input.detail)));
|
|
112
|
+
form.set("kernelFile", new File([input.detail.source], input.detail.run.filename, { type: "text/x-python" }));
|
|
113
|
+
if (input.detail.log) {
|
|
114
|
+
form.set("errorLogFile", new File([input.detail.log], `${input.detail.run.id}.log`, { type: "text/plain" }));
|
|
115
|
+
}
|
|
116
|
+
const response = await fetch(endpoint, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: requestHeaders(input.session),
|
|
119
|
+
body: form
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const body = await response.text();
|
|
123
|
+
throw new Error(`API request failed: HTTP ${response.status} ${body.slice(0, 240)}`);
|
|
124
|
+
}
|
|
125
|
+
const payload = await response.json();
|
|
126
|
+
if (!isRecord2(payload)) throw new Error("Report API returned an invalid response");
|
|
127
|
+
return {
|
|
128
|
+
issueId: requiredString(payload, "issueId"),
|
|
129
|
+
bundleId: requiredString(payload, "bundleId"),
|
|
130
|
+
duplicate: Reflect.get(payload, "duplicate") === true,
|
|
131
|
+
...optionalStrings(payload, ["githubIssueUrl"])
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function playgroundEnvironment(detail) {
|
|
135
|
+
const run = detail.run;
|
|
136
|
+
return {
|
|
137
|
+
playgroundRunId: run.id,
|
|
138
|
+
playgroundMode: run.mode,
|
|
139
|
+
playgroundProfile: run.profile,
|
|
140
|
+
playgroundStatus: run.status,
|
|
141
|
+
lower: run.lower,
|
|
142
|
+
fsim: run.fsim,
|
|
143
|
+
value: run.value,
|
|
144
|
+
sdkName: run.sdkName,
|
|
145
|
+
sdkBranch: run.sdkBranch,
|
|
146
|
+
...run.sdkCommitSha ? { sdkCommitSha: run.sdkCommitSha } : {},
|
|
147
|
+
...run.legatoSdkName ? { legatoSdkName: run.legatoSdkName } : {},
|
|
148
|
+
...run.legatoSdkBranch ? { legatoSdkBranch: run.legatoSdkBranch } : {},
|
|
149
|
+
...run.legatoSdkCommitSha ? { legatoSdkCommitSha: run.legatoSdkCommitSha } : {},
|
|
150
|
+
...run.valueDetail ? { valueDetail: run.valueDetail } : {},
|
|
151
|
+
...run.error ? { error: run.error } : {},
|
|
152
|
+
...detail.artifacts.length > 0 ? { playgroundArtifacts: detail.artifacts.join(",") } : {}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function readRunEnvelope(payload) {
|
|
156
|
+
if (!isRecord2(payload)) throw new Error("Playground API returned an invalid response");
|
|
157
|
+
if (Reflect.get(payload, "preparing") === true) {
|
|
158
|
+
const percent = Reflect.get(payload, "percent");
|
|
159
|
+
const step = Reflect.get(payload, "step");
|
|
160
|
+
throw new Error(`Runtime is preparing${typeof percent === "number" ? ` (${percent}%)` : ""}${typeof step === "string" ? `: ${step}` : ""}; rerun the command when preparation completes`);
|
|
161
|
+
}
|
|
162
|
+
return readRun(Reflect.get(payload, "run"));
|
|
163
|
+
}
|
|
164
|
+
function readRun(value) {
|
|
165
|
+
if (!isRecord2(value)) throw new Error("Playground API returned an invalid run");
|
|
166
|
+
const id = requiredString(value, "id");
|
|
167
|
+
const filename = requiredString(value, "filename");
|
|
168
|
+
const mode = enumString(value, "mode", ["lower", "fsim"]);
|
|
169
|
+
const profile = enumString(value, "profile", ["none", "cycle", "hapti", "full"]);
|
|
170
|
+
const status = enumString(value, "status", ["queued", "running", "complete", "error"]);
|
|
171
|
+
const lower = enumString(value, "lower", ["pending", "running", "pass", "fail"]);
|
|
172
|
+
const fsim = enumString(value, "fsim", ["pending", "running", "pass", "fail"]);
|
|
173
|
+
const verdict = enumString(value, "value", ["pending", "running", "pass", "fail"]);
|
|
174
|
+
return {
|
|
175
|
+
id,
|
|
176
|
+
filename,
|
|
177
|
+
mode,
|
|
178
|
+
profile,
|
|
179
|
+
status,
|
|
180
|
+
lower,
|
|
181
|
+
fsim,
|
|
182
|
+
value: verdict,
|
|
183
|
+
sdkName: requiredString(value, "sdkName"),
|
|
184
|
+
sdkBranch: requiredString(value, "sdkBranch"),
|
|
185
|
+
...optionalStrings(value, [
|
|
186
|
+
"sdkCommitSha",
|
|
187
|
+
"legatoSdkName",
|
|
188
|
+
"legatoSdkBranch",
|
|
189
|
+
"legatoSdkCommitSha",
|
|
190
|
+
"valueDetail",
|
|
191
|
+
"startedAt",
|
|
192
|
+
"completedAt",
|
|
193
|
+
"error"
|
|
194
|
+
]),
|
|
195
|
+
createdAt: requiredString(value, "createdAt")
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function readRuntime(value) {
|
|
199
|
+
if (!isRecord2(value)) throw new Error("Playground API returned an invalid runtime");
|
|
200
|
+
return {
|
|
201
|
+
name: requiredString(value, "name"),
|
|
202
|
+
label: requiredString(value, "label"),
|
|
203
|
+
compilerLabel: requiredString(value, "compilerLabel"),
|
|
204
|
+
status: enumString(value, "status", ["not_prepared", "preparing", "ready", "failed"]),
|
|
205
|
+
branch: requiredString(value, "branch"),
|
|
206
|
+
...optionalStrings(value, ["commitSha", "legatoVersion", "sdkVersion"])
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function requiredString(value, key) {
|
|
210
|
+
const field = Reflect.get(value, key);
|
|
211
|
+
if (typeof field !== "string" || !field) throw new Error(`Playground API omitted ${key}`);
|
|
212
|
+
return field;
|
|
213
|
+
}
|
|
214
|
+
function optionalStrings(value, keys) {
|
|
215
|
+
return Object.fromEntries(keys.flatMap((key) => {
|
|
216
|
+
const field = Reflect.get(value, key);
|
|
217
|
+
return typeof field === "string" && field ? [[key, field]] : [];
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
function enumString(value, key, allowed) {
|
|
221
|
+
const field = requiredString(value, key);
|
|
222
|
+
if (!allowed.includes(field)) throw new Error(`Playground API returned invalid ${key}: ${field}`);
|
|
223
|
+
return field;
|
|
224
|
+
}
|
|
225
|
+
function isRecord2(value) {
|
|
226
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
227
|
+
}
|
|
228
|
+
function isStringArray(value) {
|
|
229
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// scripts/playground.ts
|
|
233
|
+
var USAGE = `Usage:
|
|
234
|
+
lagora playground validate --kernel path
|
|
235
|
+
lagora playground runtimes [--api-url url] [--json]
|
|
236
|
+
lagora playground run --kernel path (--sdk-name name | --legato version --sdk version) [--legato-sdk-name name] [--mode lower|fsim] [--profile none|cycle|hapti|full] [--api-url url] [--json]
|
|
237
|
+
lagora playground status --run id [--api-url url] [--json]
|
|
238
|
+
lagora playground report --run id --title title [--reporter name] [--description text] [--tags tags] [--api-url url] [--json]`;
|
|
239
|
+
var VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
240
|
+
"--api-url",
|
|
241
|
+
"--kernel",
|
|
242
|
+
"--run",
|
|
243
|
+
"--title",
|
|
244
|
+
"--reporter",
|
|
245
|
+
"--description",
|
|
246
|
+
"--tags",
|
|
247
|
+
"--sdk-name",
|
|
248
|
+
"--legato-sdk-name",
|
|
249
|
+
"--legato",
|
|
250
|
+
"--sdk",
|
|
251
|
+
"--mode",
|
|
252
|
+
"--profile"
|
|
253
|
+
]);
|
|
254
|
+
function parsePlaygroundArgs(argv) {
|
|
255
|
+
const values = /* @__PURE__ */ new Map();
|
|
256
|
+
const flags = /* @__PURE__ */ new Set();
|
|
257
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
258
|
+
const option = argv[index];
|
|
259
|
+
if (!option?.startsWith("--")) throw new Error(`Unexpected argument: ${option}`);
|
|
260
|
+
if (option === "--json") {
|
|
261
|
+
flags.add(option);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (!VALUE_OPTIONS.has(option)) throw new Error(`Unknown option: ${option}`);
|
|
265
|
+
const value = argv[index + 1];
|
|
266
|
+
if (!value || value.startsWith("--")) throw new Error(`${option} requires a value`);
|
|
267
|
+
values.set(option, value);
|
|
268
|
+
index += 1;
|
|
269
|
+
}
|
|
270
|
+
const mode = values.get("--mode") ?? "lower";
|
|
271
|
+
const profile = values.get("--profile") ?? "none";
|
|
272
|
+
if (mode !== "lower" && mode !== "fsim") throw new Error(`Invalid --mode: ${mode}`);
|
|
273
|
+
if (!["none", "cycle", "hapti", "full"].includes(profile)) throw new Error(`Invalid --profile: ${profile}`);
|
|
274
|
+
return {
|
|
275
|
+
command: argv[0] ?? "help",
|
|
276
|
+
apiUrl: values.get("--api-url"),
|
|
277
|
+
kernel: values.get("--kernel"),
|
|
278
|
+
runId: values.get("--run"),
|
|
279
|
+
title: values.get("--title"),
|
|
280
|
+
reporter: values.get("--reporter"),
|
|
281
|
+
description: values.get("--description") ?? "",
|
|
282
|
+
tags: values.get("--tags") ?? "",
|
|
283
|
+
sdkName: values.get("--sdk-name"),
|
|
284
|
+
legatoSdkName: values.get("--legato-sdk-name"),
|
|
285
|
+
legatoVersion: values.get("--legato"),
|
|
286
|
+
sdkVersion: values.get("--sdk"),
|
|
287
|
+
mode,
|
|
288
|
+
profile,
|
|
289
|
+
json: flags.has("--json")
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
async function runPlaygroundCli(argv, config = new LagoraCliConfigStore()) {
|
|
293
|
+
const args = parsePlaygroundArgs(argv);
|
|
294
|
+
if (args.command === "help" || args.command === "--help" || args.command === "-h") {
|
|
295
|
+
console.log(USAGE);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!["validate", "runtimes", "run", "status", "report"].includes(args.command)) throw new Error(USAGE);
|
|
299
|
+
if (args.command === "validate") {
|
|
300
|
+
if (!args.kernel) throw new Error("playground validate requires --kernel");
|
|
301
|
+
const contract = await hasPlaygroundContract(args.kernel);
|
|
302
|
+
if (!contract.ok) throw new Error(contract.error);
|
|
303
|
+
if (args.json) console.log(JSON.stringify(contract));
|
|
304
|
+
else console.log(`ok: ${args.kernel} is harness-compatible`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (args.command === "run") {
|
|
308
|
+
const usesPreparedName = Boolean(args.sdkName || args.legatoSdkName);
|
|
309
|
+
const usesVersionPair = Boolean(args.legatoVersion || args.sdkVersion);
|
|
310
|
+
if (usesPreparedName && usesVersionPair) {
|
|
311
|
+
throw new Error("Choose either --sdk-name (with optional --legato-sdk-name) or --legato with --sdk");
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const stored = await config.apiSession(args.apiUrl);
|
|
315
|
+
const session = apiSession(stored.apiUrl, { cookie: stored.cookie, token: stored.token });
|
|
316
|
+
if (args.command === "runtimes") {
|
|
317
|
+
const runtimes = await listPlaygroundRuntimes(session);
|
|
318
|
+
if (args.json) {
|
|
319
|
+
console.log(JSON.stringify({ runtimes }));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
for (const runtime of runtimes) {
|
|
323
|
+
console.log(`${runtime.name} ${runtime.status} legato=${runtime.legatoVersion ?? "-"} sdk=${runtime.sdkVersion ?? "-"}`);
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (args.command === "run") {
|
|
328
|
+
if (!args.kernel) throw new Error("playground run requires --kernel");
|
|
329
|
+
if (!args.sdkName && !(args.legatoVersion && args.sdkVersion)) {
|
|
330
|
+
throw new Error("playground run requires --sdk-name or both --legato and --sdk");
|
|
331
|
+
}
|
|
332
|
+
const source = await readFile(args.kernel, "utf8");
|
|
333
|
+
const run = await createPlaygroundRun({
|
|
334
|
+
session,
|
|
335
|
+
filename: path2.basename(args.kernel),
|
|
336
|
+
source,
|
|
337
|
+
sdkName: args.sdkName,
|
|
338
|
+
legatoSdkName: args.legatoSdkName,
|
|
339
|
+
legatoVersion: args.legatoVersion,
|
|
340
|
+
sdkVersion: args.sdkVersion,
|
|
341
|
+
mode: args.mode,
|
|
342
|
+
profile: args.profile
|
|
343
|
+
});
|
|
344
|
+
const output = { ...run, url: `${baseApiUrl(session.apiUrl)}/playground?run=${encodeURIComponent(run.id)}` };
|
|
345
|
+
if (args.json) console.log(JSON.stringify(output));
|
|
346
|
+
else {
|
|
347
|
+
console.log(`Playground run ${run.id} queued`);
|
|
348
|
+
console.log(`Open: ${output.url}`);
|
|
349
|
+
console.log(`Status: lagora playground status --run ${run.id}`);
|
|
350
|
+
}
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (args.command === "status") {
|
|
354
|
+
if (!args.runId) throw new Error("playground status requires --run");
|
|
355
|
+
const detail = await readPlaygroundRun(session, args.runId);
|
|
356
|
+
if (args.json) console.log(JSON.stringify(detail));
|
|
357
|
+
else printRunDetail(detail, session.apiUrl);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (args.command === "report") {
|
|
361
|
+
if (!args.runId) throw new Error("playground report requires --run");
|
|
362
|
+
if (!args.title) throw new Error("playground report requires --title");
|
|
363
|
+
const detail = await readPlaygroundRun(session, args.runId);
|
|
364
|
+
const reporter = await config.author(args.reporter);
|
|
365
|
+
const issue = await reportPlaygroundRun({
|
|
366
|
+
session,
|
|
367
|
+
detail,
|
|
368
|
+
title: args.title,
|
|
369
|
+
reporter,
|
|
370
|
+
description: args.description,
|
|
371
|
+
tags: args.tags
|
|
372
|
+
});
|
|
373
|
+
const output = {
|
|
374
|
+
...issue,
|
|
375
|
+
runId: args.runId,
|
|
376
|
+
url: issue.githubIssueUrl ?? `${baseApiUrl(session.apiUrl)}/issues/${encodeURIComponent(issue.issueId)}`
|
|
377
|
+
};
|
|
378
|
+
if (args.json) console.log(JSON.stringify(output));
|
|
379
|
+
else {
|
|
380
|
+
console.log(`${issue.duplicate ? "Matched existing" : "Created"} issue ${issue.issueId} from Playground run ${args.runId}`);
|
|
381
|
+
console.log(`Open: ${output.url}`);
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
throw new Error(USAGE);
|
|
386
|
+
}
|
|
387
|
+
function printRunDetail(detail, apiUrl) {
|
|
388
|
+
const run = detail.run;
|
|
389
|
+
console.log(`${run.id} ${run.status} lower=${run.lower} fsim=${run.fsim} value=${run.value}`);
|
|
390
|
+
console.log(`Runtime: ${run.sdkName}${run.legatoSdkName ? ` (legato: ${run.legatoSdkName})` : ""}`);
|
|
391
|
+
console.log(`Open: ${baseApiUrl(apiUrl)}/playground?run=${encodeURIComponent(run.id)}`);
|
|
392
|
+
if (detail.artifacts.length > 0) {
|
|
393
|
+
console.log("Artifacts:");
|
|
394
|
+
for (const artifact of detail.artifacts) console.log(` ${artifact}`);
|
|
395
|
+
}
|
|
396
|
+
if (run.valueDetail) console.log(`Value detail: ${run.valueDetail}`);
|
|
397
|
+
if (run.error) console.log(`Error: ${run.error}`);
|
|
398
|
+
}
|
|
399
|
+
if (true) {
|
|
400
|
+
runPlaygroundCli(process.argv.slice(2)).catch((error) => {
|
|
401
|
+
console.error(error instanceof Error ? error.message : error);
|
|
402
|
+
process.exit(1);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
export {
|
|
406
|
+
parsePlaygroundArgs,
|
|
407
|
+
runPlaygroundCli
|
|
408
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configuredGithubToken,
|
|
3
|
+
gitEnv
|
|
4
|
+
} from "./chunk-2EAJVB5D.js";
|
|
5
|
+
|
|
6
|
+
// lib/server/report-bundle-sync.ts
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
9
|
+
import https from "node:https";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
var execFileAsync = promisify(execFile);
|
|
13
|
+
async function prepareGitBackedIssueStore() {
|
|
14
|
+
const config = await gitConfig();
|
|
15
|
+
if (!config) return;
|
|
16
|
+
await mkdir(config.worktree, { recursive: true });
|
|
17
|
+
if (await hasGitDirectory(config.worktree)) return;
|
|
18
|
+
await prepareEmptyCloneTarget(config.worktree);
|
|
19
|
+
await gitCommand(config, ["clone", "--branch", config.branch, config.remote, config.worktree]);
|
|
20
|
+
}
|
|
21
|
+
async function prepareEmptyCloneTarget(worktree) {
|
|
22
|
+
await mkdir(worktree, { recursive: true });
|
|
23
|
+
const entries = await readdir(worktree);
|
|
24
|
+
if (entries.length === 0) return void 0;
|
|
25
|
+
const backupPath = `${worktree}.pre-clone-${Date.now()}-${process.pid}`;
|
|
26
|
+
await rename(worktree, backupPath);
|
|
27
|
+
await mkdir(worktree, { recursive: true });
|
|
28
|
+
return backupPath;
|
|
29
|
+
}
|
|
30
|
+
async function syncStoredReportBundle(stored, intake) {
|
|
31
|
+
const firstCommit = await commitBundle(stored, `Add Legato issue ${stored.canonicalIssueId} [skip ci]`);
|
|
32
|
+
if (!firstCommit) return stored;
|
|
33
|
+
const github = await createGitHubIssue(stored, intake, firstCommit);
|
|
34
|
+
const patched = await patchIssueMetadata(stored, firstCommit, github);
|
|
35
|
+
if (patched) {
|
|
36
|
+
await commitBundle(stored, `Link Legato issue ${stored.canonicalIssueId} metadata [skip ci]`);
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...stored,
|
|
40
|
+
storeCommitSha: firstCommit.commitSha,
|
|
41
|
+
storeBundleUrl: firstCommit.bundleUrl,
|
|
42
|
+
githubIssueUrl: github?.issueUrl,
|
|
43
|
+
githubIssueNumber: github?.issueNumber
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async function deleteStoredReportBundle(issueId) {
|
|
47
|
+
assertSafeIssueId(issueId);
|
|
48
|
+
const config = await gitConfig();
|
|
49
|
+
if (!config) return false;
|
|
50
|
+
await prepareGitBackedIssueStore();
|
|
51
|
+
await gitCommand(config, ["-C", config.worktree, "config", "user.name", config.authorName]);
|
|
52
|
+
await gitCommand(config, ["-C", config.worktree, "config", "user.email", config.authorEmail]);
|
|
53
|
+
await gitCommand(config, ["-C", config.worktree, "pull", "--rebase", "--autostash", "origin", config.branch]);
|
|
54
|
+
await gitCommand(config, ["-C", config.worktree, "rm", "-r", "--ignore-unmatch", "--", path.posix.join("issues", issueId)]);
|
|
55
|
+
const hasDiff = await hasStagedDiff(config);
|
|
56
|
+
if (!hasDiff) return false;
|
|
57
|
+
await gitCommand(config, ["-C", config.worktree, "commit", "-m", `Delete Legato issue ${issueId} [skip ci]`]);
|
|
58
|
+
await gitCommand(config, ["-C", config.worktree, "push", "origin", config.branch]);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
function assertSafeIssueId(issueId) {
|
|
62
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(issueId)) {
|
|
63
|
+
throw new Error(`Unsafe issue id: ${issueId}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function commitBundle(stored, message) {
|
|
67
|
+
const config = await gitConfig();
|
|
68
|
+
if (!config) return void 0;
|
|
69
|
+
await prepareGitBackedIssueStore();
|
|
70
|
+
const changedPaths = stored.changedPaths.map((changedPath) => path.relative(config.worktree, changedPath));
|
|
71
|
+
await gitCommand(config, ["-C", config.worktree, "config", "user.name", config.authorName]);
|
|
72
|
+
await gitCommand(config, ["-C", config.worktree, "config", "user.email", config.authorEmail]);
|
|
73
|
+
await gitCommand(config, ["-C", config.worktree, "pull", "--rebase", "--autostash", "origin", config.branch]);
|
|
74
|
+
await gitCommand(config, ["-C", config.worktree, "add", "--", ...changedPaths]);
|
|
75
|
+
const hasDiff = await hasStagedDiff(config);
|
|
76
|
+
if (!hasDiff) {
|
|
77
|
+
const head = await gitOutput(config, ["-C", config.worktree, "rev-parse", "HEAD"]);
|
|
78
|
+
return { commitSha: head, bundleUrl: bundleUrl(config.repository, head, stored.bundlePath) };
|
|
79
|
+
}
|
|
80
|
+
await gitCommand(config, ["-C", config.worktree, "commit", "-m", message]);
|
|
81
|
+
await gitCommand(config, ["-C", config.worktree, "push", "origin", config.branch]);
|
|
82
|
+
const commitSha = await gitOutput(config, ["-C", config.worktree, "rev-parse", "HEAD"]);
|
|
83
|
+
return { commitSha, bundleUrl: bundleUrl(config.repository, commitSha, stored.bundlePath) };
|
|
84
|
+
}
|
|
85
|
+
async function createGitHubIssue(stored, intake, commit) {
|
|
86
|
+
const repository = process.env.AGORA_GITHUB_REPOSITORY;
|
|
87
|
+
const token = await configuredGithubToken();
|
|
88
|
+
if (!repository || !token || stored.duplicate) return void 0;
|
|
89
|
+
const response = await githubJson({
|
|
90
|
+
repository,
|
|
91
|
+
token,
|
|
92
|
+
path: `/repos/${repository}/issues`,
|
|
93
|
+
body: {
|
|
94
|
+
title: `[Legato] ${intake.title}`,
|
|
95
|
+
labels: ["legato", "agora", ...intake.tags],
|
|
96
|
+
body: [
|
|
97
|
+
`Reporter: ${intake.reporter}`,
|
|
98
|
+
`Bundle: ${commit.bundleUrl ?? stored.bundlePath}`,
|
|
99
|
+
"",
|
|
100
|
+
intake.description
|
|
101
|
+
].join("\n")
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (typeof response.number !== "number" || typeof response.html_url !== "string") {
|
|
105
|
+
throw new Error("GitHub issue response did not include number/html_url");
|
|
106
|
+
}
|
|
107
|
+
return { repository, issueNumber: response.number, issueUrl: response.html_url };
|
|
108
|
+
}
|
|
109
|
+
async function patchIssueMetadata(stored, commit, github) {
|
|
110
|
+
const raw = await readFile(stored.issueJsonPath, "utf8");
|
|
111
|
+
const parsed = JSON.parse(raw);
|
|
112
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
|
|
113
|
+
const current = parsed;
|
|
114
|
+
current.store = {
|
|
115
|
+
...isRecord(current.store) ? current.store : {},
|
|
116
|
+
commitSha: commit.commitSha,
|
|
117
|
+
bundlePath: stored.bundlePath,
|
|
118
|
+
bundleUrl: commit.bundleUrl
|
|
119
|
+
};
|
|
120
|
+
if (github) {
|
|
121
|
+
current.github = {
|
|
122
|
+
repository: github.repository,
|
|
123
|
+
issueNumber: github.issueNumber,
|
|
124
|
+
issueUrl: github.issueUrl,
|
|
125
|
+
createdBy: "legato-dev-agora",
|
|
126
|
+
lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
await writeFile(stored.issueJsonPath, `${JSON.stringify(current, null, 2)}
|
|
130
|
+
`);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
async function gitConfig() {
|
|
134
|
+
const worktree = process.env.AGORA_GIT_WORKTREE;
|
|
135
|
+
const remote = process.env.AGORA_GIT_REMOTE;
|
|
136
|
+
if (!worktree || !remote) return void 0;
|
|
137
|
+
return {
|
|
138
|
+
worktree: path.resolve(worktree),
|
|
139
|
+
remote,
|
|
140
|
+
branch: process.env.AGORA_GIT_BRANCH ?? "main",
|
|
141
|
+
repository: process.env.AGORA_GITHUB_REPOSITORY,
|
|
142
|
+
token: await configuredGithubToken(),
|
|
143
|
+
authorName: process.env.AGORA_GIT_AUTHOR_NAME ?? "Legato Dev Agora",
|
|
144
|
+
authorEmail: process.env.AGORA_GIT_AUTHOR_EMAIL ?? "legato-dev-agora@hyperaccel.net"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function hasGitDirectory(worktree) {
|
|
148
|
+
try {
|
|
149
|
+
await execFileAsync("git", ["-C", worktree, "rev-parse", "--is-inside-work-tree"]);
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof Error) return false;
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function hasStagedDiff(config) {
|
|
157
|
+
try {
|
|
158
|
+
await gitCommand(config, ["-C", config.worktree, "diff", "--cached", "--quiet"]);
|
|
159
|
+
return false;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof Error) return true;
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function gitCommand(config, args) {
|
|
166
|
+
await execFileAsync("git", [...args], { env: gitEnv(config.token) });
|
|
167
|
+
}
|
|
168
|
+
async function gitOutput(config, args) {
|
|
169
|
+
const output = await execFileAsync("git", [...args], { env: gitEnv(config.token) });
|
|
170
|
+
return output.stdout.trim();
|
|
171
|
+
}
|
|
172
|
+
function bundleUrl(repository, commitSha, bundlePath) {
|
|
173
|
+
return repository ? `https://github.com/${repository}/tree/${commitSha}/${bundlePath}` : void 0;
|
|
174
|
+
}
|
|
175
|
+
function isRecord(value) {
|
|
176
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
177
|
+
}
|
|
178
|
+
function githubJson(input) {
|
|
179
|
+
const payload = JSON.stringify(input.body);
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
const request = https.request({
|
|
182
|
+
method: "POST",
|
|
183
|
+
hostname: "api.github.com",
|
|
184
|
+
path: input.path,
|
|
185
|
+
headers: {
|
|
186
|
+
Accept: "application/vnd.github+json",
|
|
187
|
+
Authorization: `Bearer ${input.token}`,
|
|
188
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
189
|
+
"Content-Type": "application/json",
|
|
190
|
+
"User-Agent": "legato-dev-agora",
|
|
191
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
192
|
+
}
|
|
193
|
+
}, (response) => {
|
|
194
|
+
const chunks = [];
|
|
195
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
196
|
+
response.on("end", () => {
|
|
197
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
198
|
+
if (!response.statusCode || response.statusCode < 200 || response.statusCode >= 300) {
|
|
199
|
+
reject(new Error(`GitHub API failed ${response.statusCode}: ${text}`));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
resolve(JSON.parse(text));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
request.on("error", reject);
|
|
206
|
+
request.write(payload);
|
|
207
|
+
request.end();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
deleteStoredReportBundle,
|
|
212
|
+
prepareEmptyCloneTarget,
|
|
213
|
+
prepareGitBackedIssueStore,
|
|
214
|
+
syncStoredReportBundle
|
|
215
|
+
};
|