@mutmutco/cli 4.5.24 → 4.5.26
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/dist/main.cjs +355 -58
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -791,6 +791,8 @@ var init_house_map = __esm({
|
|
|
791
791
|
// register/run/park schedule rows under the one lane contract
|
|
792
792
|
// --- devops — shipping --------------------------------------------------------------------------
|
|
793
793
|
pr: "devops",
|
|
794
|
+
api: "devops",
|
|
795
|
+
// allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
|
|
794
796
|
ci: "devops",
|
|
795
797
|
// the CI/gate audit
|
|
796
798
|
rcand: "devops",
|
|
@@ -1043,9 +1045,21 @@ function mutating(cmd, planFn) {
|
|
|
1043
1045
|
return cmd;
|
|
1044
1046
|
}
|
|
1045
1047
|
function installProcessBackstop() {
|
|
1048
|
+
installBrokenPipeGuard(process.stdout);
|
|
1049
|
+
installBrokenPipeGuard(process.stderr);
|
|
1046
1050
|
process.on("unhandledRejection", (reason) => void failGraceful(reason instanceof Error ? reason.message : String(reason)));
|
|
1047
1051
|
process.on("uncaughtException", (err) => void failGraceful(err instanceof Error ? err.message : String(err)));
|
|
1048
1052
|
}
|
|
1053
|
+
function installBrokenPipeGuard(stream) {
|
|
1054
|
+
stream.on("error", (err) => {
|
|
1055
|
+
if (err?.code !== "EPIPE") throw err;
|
|
1056
|
+
stream.write = (_chunk, enc, cb) => {
|
|
1057
|
+
const done = typeof enc === "function" ? enc : cb;
|
|
1058
|
+
if (typeof done === "function") queueMicrotask(() => done());
|
|
1059
|
+
return true;
|
|
1060
|
+
};
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1049
1063
|
function rawFlag(flag) {
|
|
1050
1064
|
return process.argv.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
1051
1065
|
}
|
|
@@ -6537,6 +6551,10 @@ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
|
|
|
6537
6551
|
}
|
|
6538
6552
|
let body = item.details?.body;
|
|
6539
6553
|
if (body === void 0) {
|
|
6554
|
+
if (item.detailsUnavailable) {
|
|
6555
|
+
results[index] = { bucket: "claimable", item, warnings: [] };
|
|
6556
|
+
continue;
|
|
6557
|
+
}
|
|
6540
6558
|
if (opts.requireBundledBody) {
|
|
6541
6559
|
results[index] = { bucket: "claimable", item, warnings: [] };
|
|
6542
6560
|
continue;
|
|
@@ -6544,7 +6562,7 @@ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
|
|
|
6544
6562
|
try {
|
|
6545
6563
|
const data = await client.rest("GET", `repos/${item.repository}/issues/${item.number}`);
|
|
6546
6564
|
body = data?.body ?? "";
|
|
6547
|
-
item.details = { body, comments: item.details?.comments ?? [] };
|
|
6565
|
+
item.details = { body, comments: item.details?.comments ?? [], ...item.details?.comments === void 0 ? { commentsUnread: true } : {} };
|
|
6548
6566
|
} catch (e) {
|
|
6549
6567
|
results[index] = {
|
|
6550
6568
|
bucket: "claimable",
|
|
@@ -8026,7 +8044,7 @@ async function probeHttpBounded(url, timeoutMs = 5e3) {
|
|
|
8026
8044
|
}
|
|
8027
8045
|
async function probePublicHealth(publicUrl) {
|
|
8028
8046
|
const root = await probeHttpBounded(publicUrl);
|
|
8029
|
-
if (root.status !== 401 && root.status !== 403) return root;
|
|
8047
|
+
if (root.status !== 401 && root.status !== 403 && root.status !== 404) return root;
|
|
8030
8048
|
return probeHttpBounded(`${publicUrl.replace(/\/$/, "")}/health`);
|
|
8031
8049
|
}
|
|
8032
8050
|
var TENANT_DEPLOY_RUN_SCAN_LIMIT = 100;
|
|
@@ -8048,7 +8066,7 @@ function tenantRuntimeHints(stage, fact, probe, edgeCertificate) {
|
|
|
8048
8066
|
if (!fact.sshHostPresent && fact.substrate === "hetzner-ssh") hints.push(`DEPLOY#${stage} has hetzner-ssh substrate but no sshHost presence; tenant-deploy cannot reach the box.`);
|
|
8049
8067
|
if (!fact.domain) hints.push(`DEPLOY#${stage} has no edgeVhost.domain; Cloudflare/Caddy public URL cannot be derived.`);
|
|
8050
8068
|
if (typeof fact.port !== "number") hints.push(`DEPLOY#${stage} has no edgeVhost.port; Caddy upstream/healthUrl hints cannot be checked.`);
|
|
8051
|
-
if (probe?.url?.endsWith("/health") && probe.ok === false) hints.push(`Root
|
|
8069
|
+
if (probe?.url?.endsWith("/health") && probe.ok === false) hints.push(`Root answered 401/403/404 and ${probe.url} answered ${probe.status ?? probe.error}; the app is up and the edge is fine \u2014 expose a 2xx /health route to make this probe green.`);
|
|
8052
8070
|
else if (edgeCertificate?.missing && edgeCertificate.hint) hints.push(edgeCertificate.hint);
|
|
8053
8071
|
else if (probe?.ok === false || probe?.status != null && probe.status >= 500) hints.push("Public URL probe failed; for Cloudflare 525 check Caddy TLS/origin certificate and Cloudflare SSL mode before changing app code.");
|
|
8054
8072
|
if (stage === "rc") hints.push("rc runtime is expected to be ephemeral: present between /rcand and /release, then retired after release.");
|
|
@@ -12447,6 +12465,7 @@ function renderBoardSource() {
|
|
|
12447
12465
|
return "source: live";
|
|
12448
12466
|
}
|
|
12449
12467
|
async function collectBoardPreferringSnapshot(cfg, options, deps) {
|
|
12468
|
+
if (options.snapshotOnly && !deps.snapshot) throw new Error("--snapshot-only needs the Hub snapshot transport (not with --direct)");
|
|
12450
12469
|
const attempt = deps.snapshot ? await fetchHubBoardSnapshot(
|
|
12451
12470
|
{
|
|
12452
12471
|
owner: cfg.projectOwner,
|
|
@@ -12486,6 +12505,9 @@ async function collectBoardPreferringSnapshot(cfg, options, deps) {
|
|
|
12486
12505
|
snapshot
|
|
12487
12506
|
};
|
|
12488
12507
|
}
|
|
12508
|
+
if (options.snapshotOnly && attempt?.state === "unavailable") {
|
|
12509
|
+
throw new Error(`Hub board snapshot unavailable (${attempt.reason}) \u2014 --snapshot-only refuses the direct user-auth fallback`);
|
|
12510
|
+
}
|
|
12489
12511
|
const collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
|
|
12490
12512
|
if (attempt?.state === "unavailable") {
|
|
12491
12513
|
collected.warnings.push(`Hub board snapshot unavailable (${attempt.reason}) \u2014 served by the direct user-auth read (emergency fallback)`);
|
|
@@ -12768,7 +12790,13 @@ function nodeToItem(node, cfg) {
|
|
|
12768
12790
|
priorityOptionId: fields.priority?.optionId,
|
|
12769
12791
|
assignees,
|
|
12770
12792
|
labels,
|
|
12771
|
-
type: labels.find((label) => TYPE_LABELS.includes(label))
|
|
12793
|
+
type: labels.find((label) => TYPE_LABELS.includes(label)),
|
|
12794
|
+
...content.__typename === "Issue" && content.claimEvidenceUnavailable ? { detailsUnavailable: "App-token claim evidence unread" } : {},
|
|
12795
|
+
...content.__typename === "Issue" && typeof content.body === "string" && Array.isArray(content.comments?.nodes) && typeof content.comments.totalCount === "number" && Number.isInteger(content.comments.totalCount) && content.comments.totalCount >= content.comments.nodes.length ? { details: {
|
|
12796
|
+
body: content.body,
|
|
12797
|
+
comments: content.comments.nodes.map((comment) => ({ author: comment.author?.login ?? "", body: comment.body ?? "" })),
|
|
12798
|
+
...content.comments.totalCount > content.comments.nodes.length ? { omittedComments: content.comments.totalCount - content.comments.nodes.length } : {}
|
|
12799
|
+
} } : {}
|
|
12772
12800
|
})
|
|
12773
12801
|
};
|
|
12774
12802
|
}
|
|
@@ -12783,7 +12811,8 @@ async function attachBundleDetails(report, client, allowPartial, options = {}) {
|
|
|
12783
12811
|
while (next < candidates.length) {
|
|
12784
12812
|
const item = candidates[next++];
|
|
12785
12813
|
try {
|
|
12786
|
-
item.details = await fetchIssueDetails(client, item.repository, item.number);
|
|
12814
|
+
if (!item.details && !item.detailsUnavailable) item.details = await fetchIssueDetails(client, item.repository, item.number);
|
|
12815
|
+
if (!item.details) continue;
|
|
12787
12816
|
const claim = boardItemClaim(item);
|
|
12788
12817
|
if (claim) item.claim = claim;
|
|
12789
12818
|
} catch (e) {
|
|
@@ -13017,7 +13046,8 @@ async function demoteLiveClaimedClaimables(scope, client, actor = describeSessio
|
|
|
13017
13046
|
const warnings = [];
|
|
13018
13047
|
let partial = false;
|
|
13019
13048
|
const fetchComments = deps.fetchComments ?? (async (item) => {
|
|
13020
|
-
if (item.
|
|
13049
|
+
if (item.detailsUnavailable) return void 0;
|
|
13050
|
+
if (item.details && !item.details.commentsUnread && !item.details.omittedComments) return item.details.comments;
|
|
13021
13051
|
const comments = await client.restPaginate(`repos/${item.repository}/issues/${item.number}/comments`);
|
|
13022
13052
|
return comments.map((comment) => ({ body: comment.body ?? "" }));
|
|
13023
13053
|
});
|
|
@@ -13600,11 +13630,6 @@ async function prepareClaimContext(options, selectors, deps, collected, snapshot
|
|
|
13600
13630
|
warnings: collected.warnings,
|
|
13601
13631
|
partial: collected.partial
|
|
13602
13632
|
};
|
|
13603
|
-
for (const scope of ["primary", "secondary"]) {
|
|
13604
|
-
const filtered = await filterDependencyBlockedClaimables(report[scope].claimable, client);
|
|
13605
|
-
report[scope].claimable = filtered.claimable;
|
|
13606
|
-
report.warnings.push(...filtered.warnings);
|
|
13607
|
-
}
|
|
13608
13633
|
return {
|
|
13609
13634
|
cfg,
|
|
13610
13635
|
client,
|
|
@@ -14061,7 +14086,7 @@ init_github_client();
|
|
|
14061
14086
|
var PRODUCT_RULESET_NAME = "mmi-product-required-checks";
|
|
14062
14087
|
var PRODUCT_GATE_CONTEXT = "gate";
|
|
14063
14088
|
var PRODUCT_RULESET_PATH = ".github/rulesets/mmi-product-required-checks.json";
|
|
14064
|
-
function reseedProductRulesetStrictness(current, seed) {
|
|
14089
|
+
function reseedProductRulesetStrictness(current, seed, declared = {}) {
|
|
14065
14090
|
const parse = (raw) => {
|
|
14066
14091
|
const policy2 = JSON.parse(raw);
|
|
14067
14092
|
if (!policy2 || typeof policy2 !== "object" || Array.isArray(policy2) || policy2.name !== PRODUCT_RULESET_NAME || policy2.target !== "branch" || !Array.isArray(policy2.rules) || policy2.rules.some((r) => !r || typeof r !== "object" || Array.isArray(r) || typeof r.type !== "string")) {
|
|
@@ -14074,14 +14099,28 @@ function reseedProductRulesetStrictness(current, seed) {
|
|
|
14074
14099
|
}
|
|
14075
14100
|
return { policy: policy2, parameters: parameters2 };
|
|
14076
14101
|
};
|
|
14077
|
-
const
|
|
14102
|
+
const canonical = parse(seed);
|
|
14103
|
+
const strict = canonical.parameters.strict_required_status_checks_policy;
|
|
14078
14104
|
if (typeof strict !== "boolean") throw new Error("canonical product ruleset must declare boolean strictness");
|
|
14079
14105
|
if (current === null) return seed;
|
|
14080
14106
|
const { policy, parameters } = parse(current);
|
|
14081
|
-
|
|
14107
|
+
let changed = parameters.strict_required_status_checks_policy !== strict;
|
|
14082
14108
|
parameters.strict_required_status_checks_policy = strict;
|
|
14083
|
-
|
|
14084
|
-
|
|
14109
|
+
if (declared.requiredChecks) {
|
|
14110
|
+
const checks = canonical.parameters.required_status_checks.map((check) => parameters.required_status_checks.find((old) => old.context === check.context) ?? check);
|
|
14111
|
+
if (JSON.stringify(parameters.required_status_checks) !== JSON.stringify(checks)) changed = true;
|
|
14112
|
+
parameters.required_status_checks = checks;
|
|
14113
|
+
}
|
|
14114
|
+
if (declared.requiredCheckBranches) {
|
|
14115
|
+
const refs = canonical.policy.conditions?.ref_name?.include;
|
|
14116
|
+
if (!Array.isArray(refs) || !refs.length) throw new Error("canonical product ruleset must declare branch includes");
|
|
14117
|
+
const conditions = policy.conditions;
|
|
14118
|
+
const refName = conditions?.ref_name;
|
|
14119
|
+
if (JSON.stringify(refName?.include) !== JSON.stringify(refs)) changed = true;
|
|
14120
|
+
policy.conditions = { ...conditions, ref_name: { ...refName, include: refs } };
|
|
14121
|
+
}
|
|
14122
|
+
return changed ? `${JSON.stringify(policy, null, 2)}
|
|
14123
|
+
` : current;
|
|
14085
14124
|
}
|
|
14086
14125
|
function stripRulesetComment(raw) {
|
|
14087
14126
|
const parsed = JSON.parse(raw);
|
|
@@ -15398,6 +15437,20 @@ function labelsToPrune(orgLabelNames) {
|
|
|
15398
15437
|
const org = new Set(orgLabelNames);
|
|
15399
15438
|
return GITHUB_DEFAULT_LABELS.filter((name) => !org.has(name));
|
|
15400
15439
|
}
|
|
15440
|
+
function planBootstrapLabels(labels, live) {
|
|
15441
|
+
const existing = new Set(live.map((label) => label.name));
|
|
15442
|
+
return [
|
|
15443
|
+
...labels.map((label) => ({ name: label.name, action: existing.has(label.name) ? "UPDATE" : "CREATE" })),
|
|
15444
|
+
...labelsToPrune(labels.map((label) => label.name)).filter((name) => existing.has(name)).map((name) => ({ name, action: "DELETE" }))
|
|
15445
|
+
];
|
|
15446
|
+
}
|
|
15447
|
+
function renderBootstrapAncillaryPlan(labels, environments) {
|
|
15448
|
+
return [
|
|
15449
|
+
"bootstrap apply \u2014 labels and environments (dry-run; no mutations):",
|
|
15450
|
+
...labels.map(({ action, name }) => ` ${action} label ${name}`),
|
|
15451
|
+
...environments.map(({ action, environment, reason }) => ` ${action} environment ${environment} (${reason})`)
|
|
15452
|
+
].join("\n");
|
|
15453
|
+
}
|
|
15401
15454
|
function resolveSeedContent(seed, vars, readFile9) {
|
|
15402
15455
|
if (seed.source === "self") return readFile9(seed.target);
|
|
15403
15456
|
if (seed.source.startsWith("seed:")) {
|
|
@@ -15418,6 +15471,11 @@ function resolveSeedWriteContent(seed, vars, readFile9, remoteContent) {
|
|
|
15418
15471
|
const result = upsertManagedSeedBlock(base, desired, seed.managedBlock.begin, seed.managedBlock.end);
|
|
15419
15472
|
return result.ok ? { ok: true, content: result.content, managed: true } : { ok: false, reason: result.reason, managed: true };
|
|
15420
15473
|
}
|
|
15474
|
+
function edgeCertificateStagesForBootstrap(track, domains) {
|
|
15475
|
+
if (!domains || typeof domains !== "object" || Array.isArray(domains)) return [];
|
|
15476
|
+
const stages = branchesForTrack(track);
|
|
15477
|
+
return ["dev", "rc"].filter((stage) => stages.includes(stage === "dev" ? "development" : stage) && Boolean(domains[stage]));
|
|
15478
|
+
}
|
|
15421
15479
|
function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
15422
15480
|
const parsedRepo = parseOwnerRepo(repo);
|
|
15423
15481
|
const slug = parsedRepo.slug;
|
|
@@ -15442,6 +15500,12 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
15442
15500
|
);
|
|
15443
15501
|
}
|
|
15444
15502
|
const deployModel = inferDeployModelForWrite({ ...shape, projectType }, parsedRepo.fullName);
|
|
15503
|
+
const releaseTrack = (isReleaseTrack(options.releaseTrack) ? options.releaseTrack : void 0) ?? (isReleaseTrack(options.existing?.releaseTrack) ? options.existing.releaseTrack : void 0) ?? resolveBootstrapReleaseTrack(cls, void 0);
|
|
15504
|
+
const inferredDomains = inferEdgeDomainsForWrite(slug);
|
|
15505
|
+
const derivedDomains = Object.fromEntries(branchesForTrack(releaseTrack).map((branch) => {
|
|
15506
|
+
const stage = branch === "development" ? "dev" : branch;
|
|
15507
|
+
return [stage, inferredDomains[stage]];
|
|
15508
|
+
}));
|
|
15445
15509
|
const num = (v) => {
|
|
15446
15510
|
if (v == null || v === "") return void 0;
|
|
15447
15511
|
const n = Number(v);
|
|
@@ -15471,10 +15535,9 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
15471
15535
|
deployModel,
|
|
15472
15536
|
// Persist the canonical stage hosts at the same write-time boundary as deployModel. Read paths never
|
|
15473
15537
|
// infer from an absent edgeDomains field.
|
|
15474
|
-
edgeDomains: options.existing?.edgeDomains ??
|
|
15538
|
+
edgeDomains: options.existing?.edgeDomains ?? derivedDomains,
|
|
15475
15539
|
// #1359: always persist an explicit track so release tooling never guesses from absence alone.
|
|
15476
|
-
|
|
15477
|
-
releaseTrack: options.releaseTrack ?? (isReleaseTrack(options.existing?.releaseTrack) ? options.existing.releaseTrack : void 0) ?? resolveBootstrapReleaseTrack(cls, void 0),
|
|
15540
|
+
releaseTrack,
|
|
15478
15541
|
// Board coords (from GraphQL at bootstrap, passed as --var by the skill).
|
|
15479
15542
|
projectOwner: vars.PROJECT_OWNER || void 0,
|
|
15480
15543
|
projectNumber: num(vars.PROJECT_NUMBER),
|
|
@@ -16753,10 +16816,10 @@ var rollout_plan_default = {
|
|
|
16753
16816
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
16754
16817
|
},
|
|
16755
16818
|
baseline: {
|
|
16756
|
-
version: "4.5.
|
|
16757
|
-
tag: "v4.5.
|
|
16758
|
-
commit: "
|
|
16759
|
-
npm: "@mutmutco/cli@4.5.
|
|
16819
|
+
version: "4.5.26",
|
|
16820
|
+
tag: "v4.5.26",
|
|
16821
|
+
commit: "f5ca4e782979",
|
|
16822
|
+
npm: "@mutmutco/cli@4.5.26"
|
|
16760
16823
|
},
|
|
16761
16824
|
exitCriterion: "fleet-n-of-n",
|
|
16762
16825
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16773,14 +16836,14 @@ var rollout_plan_default = {
|
|
|
16773
16836
|
repo: "mutmutco/mmi-hub",
|
|
16774
16837
|
role: "canary",
|
|
16775
16838
|
schedule: "train",
|
|
16776
|
-
v3Target: "v4.5.
|
|
16839
|
+
v3Target: "v4.5.26"
|
|
16777
16840
|
}
|
|
16778
16841
|
],
|
|
16779
16842
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
16780
16843
|
rollback: {
|
|
16781
16844
|
independent: true,
|
|
16782
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16783
|
-
v3Target: "v4.5.
|
|
16845
|
+
mechanism: "npm dist-tag latest -> 4.5.26 and redeploy the Hub Lambda from tag v4.5.26 (f5ca4e782979); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16846
|
+
v3Target: "v4.5.26 (@mutmutco/cli@4.5.26, tag commit f5ca4e782979 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16784
16847
|
}
|
|
16785
16848
|
},
|
|
16786
16849
|
{
|
|
@@ -17108,7 +17171,7 @@ function ensureField(fields, field) {
|
|
|
17108
17171
|
return list.join(",");
|
|
17109
17172
|
}
|
|
17110
17173
|
function resolveIssueViewGhFields(normalized) {
|
|
17111
|
-
const requested = normalized.split(",").map((f) => f.trim()).filter((f) => f && f !== "parent");
|
|
17174
|
+
const requested = normalized.split(",").map((f) => f.trim()).filter((f) => f && f !== "parent" && f !== "closedBy");
|
|
17112
17175
|
if (requested.length === 0) return { ghFields: "number", numberIsTransport: true };
|
|
17113
17176
|
return { ghFields: requested.join(","), numberIsTransport: false };
|
|
17114
17177
|
}
|
|
@@ -17144,11 +17207,11 @@ var QueryReadError = class extends Error {
|
|
|
17144
17207
|
this.code = code;
|
|
17145
17208
|
}
|
|
17146
17209
|
};
|
|
17147
|
-
function clampLimit(n, dflt) {
|
|
17210
|
+
function clampLimit(n, dflt, max = MAX_LIMIT) {
|
|
17148
17211
|
if (n === void 0 || !Number.isFinite(n)) return dflt;
|
|
17149
17212
|
const v = Math.floor(n);
|
|
17150
17213
|
if (v < 1) return 1;
|
|
17151
|
-
if (v >
|
|
17214
|
+
if (v > max) return max;
|
|
17152
17215
|
return v;
|
|
17153
17216
|
}
|
|
17154
17217
|
function isGhNotFound(e) {
|
|
@@ -17175,10 +17238,12 @@ function loginsOf(v) {
|
|
|
17175
17238
|
function namesOf(v) {
|
|
17176
17239
|
return Array.isArray(v) ? v.map((a) => String(a?.name ?? "")).filter(Boolean) : [];
|
|
17177
17240
|
}
|
|
17241
|
+
var ISSUE_LIST_MAX_LIMIT = 1e3;
|
|
17178
17242
|
function buildIssueListArgs(opts, repo) {
|
|
17179
|
-
const limit = clampLimit(opts.limit, DEFAULT_LIMIT);
|
|
17243
|
+
const limit = clampLimit(opts.limit, DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT);
|
|
17180
17244
|
const state = opts.state ?? "open";
|
|
17181
|
-
const
|
|
17245
|
+
const fields = opts.withBody ? `${ISSUE_LIST_FIELDS},body` : ISSUE_LIST_FIELDS;
|
|
17246
|
+
const args = ["issue", "list", "--repo", repo, "--state", state, "--limit", String(limit), "--json", fields];
|
|
17182
17247
|
if (opts.label) args.push("--label", opts.label);
|
|
17183
17248
|
if (opts.assignee) args.push("--assignee", opts.assignee);
|
|
17184
17249
|
if (opts.search) args.push("--search", opts.search);
|
|
@@ -17194,7 +17259,8 @@ function shapeIssueList(rows) {
|
|
|
17194
17259
|
state: String(row.state ?? ""),
|
|
17195
17260
|
url: String(row.url ?? ""),
|
|
17196
17261
|
assignees: loginsOf(row.assignees),
|
|
17197
|
-
labels: namesOf(row.labels)
|
|
17262
|
+
labels: namesOf(row.labels),
|
|
17263
|
+
...typeof row.body === "string" ? { body: row.body } : {}
|
|
17198
17264
|
};
|
|
17199
17265
|
});
|
|
17200
17266
|
}
|
|
@@ -17203,7 +17269,8 @@ async function runIssueList(deps, opts) {
|
|
|
17203
17269
|
if (!repo) {
|
|
17204
17270
|
throw new QueryReadError("BAD_INPUT", `could not resolve repo (pass --repo <owner/repo>)`);
|
|
17205
17271
|
}
|
|
17206
|
-
const
|
|
17272
|
+
const timeout = (opts.limit ?? 0) > MAX_LIMIT ? GH_LIST_TIMEOUT_MS * 4 : GH_LIST_TIMEOUT_MS;
|
|
17273
|
+
const rows = await deps.ghJson(buildIssueListArgs(opts, repo), timeout);
|
|
17207
17274
|
return shapeIssueList(rows);
|
|
17208
17275
|
}
|
|
17209
17276
|
function childrenGraphqlArgs(owner, name, number) {
|
|
@@ -17597,6 +17664,45 @@ async function runPrForIssue(deps, issueRef, opts) {
|
|
|
17597
17664
|
}
|
|
17598
17665
|
return extractPrForIssueResponse(resp);
|
|
17599
17666
|
}
|
|
17667
|
+
function closedByGraphqlArgs(owner, name, number) {
|
|
17668
|
+
const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number closedByPullRequestsReferences(first:100){nodes{number merged mergedAt mergeCommit{oid} repository{owner{login} name}}}}}}";
|
|
17669
|
+
return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
|
|
17670
|
+
}
|
|
17671
|
+
function extractClosedByResponse(resp) {
|
|
17672
|
+
const r = resp ?? {};
|
|
17673
|
+
if ((r.errors ?? []).some((e) => e?.type === "NOT_FOUND") || !r.data?.repository?.issue) {
|
|
17674
|
+
throw new QueryReadError("NOT_FOUND", "issue not found");
|
|
17675
|
+
}
|
|
17676
|
+
const out = [];
|
|
17677
|
+
for (const node of r.data.repository.issue.closedByPullRequestsReferences?.nodes ?? []) {
|
|
17678
|
+
const pr = node;
|
|
17679
|
+
if (typeof pr.number !== "number") continue;
|
|
17680
|
+
out.push({
|
|
17681
|
+
number: pr.number,
|
|
17682
|
+
merged: pr.merged === true,
|
|
17683
|
+
mergedAt: typeof pr.mergedAt === "string" ? pr.mergedAt : null,
|
|
17684
|
+
mergeCommit: pr.mergeCommit && typeof pr.mergeCommit.oid === "string" ? { oid: pr.mergeCommit.oid } : null,
|
|
17685
|
+
repository: {
|
|
17686
|
+
owner: { login: String(pr.repository?.owner?.login ?? "") },
|
|
17687
|
+
name: String(pr.repository?.name ?? "")
|
|
17688
|
+
}
|
|
17689
|
+
});
|
|
17690
|
+
}
|
|
17691
|
+
return out;
|
|
17692
|
+
}
|
|
17693
|
+
async function runClosedBy(deps, opts) {
|
|
17694
|
+
const { owner, name } = splitRepo(opts.repo);
|
|
17695
|
+
let resp;
|
|
17696
|
+
try {
|
|
17697
|
+
resp = await deps.ghJson(closedByGraphqlArgs(owner, name, opts.number), GH_LIST_TIMEOUT_MS);
|
|
17698
|
+
} catch (e) {
|
|
17699
|
+
if (isGhNotFound(e)) {
|
|
17700
|
+
throw new QueryReadError("NOT_FOUND", `#${opts.number} not found in ${opts.repo}`);
|
|
17701
|
+
}
|
|
17702
|
+
throw e;
|
|
17703
|
+
}
|
|
17704
|
+
return extractClosedByResponse(resp);
|
|
17705
|
+
}
|
|
17600
17706
|
async function runIssueViewContext(deps, opts) {
|
|
17601
17707
|
const baseFields = opts.context ? opts.jsonFields.split(",").map((field) => field.trim()).filter((field) => field !== "linkedPrs" && field !== "children").join(",") : opts.jsonFields;
|
|
17602
17708
|
const fields = opts.comments || opts.context ? ensureField(baseFields, "comments") : baseFields;
|
|
@@ -17772,10 +17878,10 @@ function registerQueryCommands(program3) {
|
|
|
17772
17878
|
const project2 = program3.commands.find((c) => c.name() === "project");
|
|
17773
17879
|
if (!issue || !pr) return;
|
|
17774
17880
|
const deps = queryDeps();
|
|
17775
|
-
issue.command("list").description("bounded issue read \u2014 filter by text, label, state, or assignee; always prints JSON").option("--label <label>", "filter by label").option("--search <query>", "free-text issue search").addOption(caseInsensitiveChoices(new Option("--state <state>", "open | closed | all").default("open"), ISSUE_STATES)).option("--assignee <login>", "filter by assignee login").option("--limit <n>", "max issues to return (capped at 100)", (v) => Number(v), DEFAULT_LIMIT).option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (o) => {
|
|
17881
|
+
issue.command("list").description("bounded issue read \u2014 filter by text, label, state, or assignee; always prints JSON").option("--label <label>", "filter by label").option("--search <query>", "free-text issue search").addOption(caseInsensitiveChoices(new Option("--state <state>", "open | closed | all").default("open"), ISSUE_STATES)).option("--assignee <login>", "filter by assignee login").option("--limit <n>", "max issues to return (capped at 1000; gh pages past 100)", (v) => Number(v), DEFAULT_LIMIT).option("--with-body", "include each issue's markdown body").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (o) => {
|
|
17776
17882
|
try {
|
|
17777
17883
|
const state = validateEnum("--state", ISSUE_STATES, o.state, "issue list");
|
|
17778
|
-
const rows = await runIssueList(deps, { label: o.label, search: o.search, state, assignee: o.assignee, limit: o.limit, repo: o.repo });
|
|
17884
|
+
const rows = await runIssueList(deps, { label: o.label, search: o.search, state, assignee: o.assignee, limit: o.limit, repo: o.repo, withBody: o.withBody });
|
|
17779
17885
|
console.log(JSON.stringify(rows));
|
|
17780
17886
|
} catch (e) {
|
|
17781
17887
|
queryFail("issue list", e);
|
|
@@ -26463,6 +26569,10 @@ var TAG_ADDRESSABLE_CONTEXTS_LABEL = "rc/main required contexts are tag-addressa
|
|
|
26463
26569
|
var RELEASE_BRANCH_REACHABLE_LABEL = "rc/main required contexts are reachable from a release-base head (#5193)";
|
|
26464
26570
|
var RULESET_TRACK_SCOPE_LABEL = "product ruleset branch scope covers the release track (#6327)";
|
|
26465
26571
|
var RECONCILE_SEED_BRANCH_PREFIX = "bootstrap-seed-ruleset-ref";
|
|
26572
|
+
var NPM_PUBLISH_SAFE_FLOOR_SHA = "b5632c8b940d8025fdb5630e576502e1397c4552";
|
|
26573
|
+
var SHARED_ACTION_PINS = [
|
|
26574
|
+
{ path: "npm-publish", floor: NPM_PUBLISH_SAFE_FLOOR_SHA }
|
|
26575
|
+
];
|
|
26466
26576
|
function slugFromRepo(repo) {
|
|
26467
26577
|
return (repo.includes("/") ? repo.split("/")[1] : repo).toLowerCase();
|
|
26468
26578
|
}
|
|
@@ -26486,6 +26596,43 @@ async function restJson2(deps, path2, fallback) {
|
|
|
26486
26596
|
return fallback;
|
|
26487
26597
|
}
|
|
26488
26598
|
}
|
|
26599
|
+
var sharedPinComparisons = /* @__PURE__ */ new WeakMap();
|
|
26600
|
+
function pinContainsFloor(deps, floor, pin) {
|
|
26601
|
+
let comparisons = sharedPinComparisons.get(deps);
|
|
26602
|
+
if (!comparisons) {
|
|
26603
|
+
comparisons = /* @__PURE__ */ new Map();
|
|
26604
|
+
sharedPinComparisons.set(deps, comparisons);
|
|
26605
|
+
}
|
|
26606
|
+
const key = `${floor}...${pin}`;
|
|
26607
|
+
let comparison = comparisons.get(key);
|
|
26608
|
+
if (!comparison) {
|
|
26609
|
+
comparison = deps.client.rest("GET", `repos/${HUB_REPO4}/compare/${key}`).then((result) => result?.status === "ahead" || result?.status === "identical" ? true : result?.status === "behind" || result?.status === "diverged" ? false : void 0).catch(() => void 0);
|
|
26610
|
+
comparisons.set(key, comparison);
|
|
26611
|
+
}
|
|
26612
|
+
return comparison;
|
|
26613
|
+
}
|
|
26614
|
+
async function auditSharedActionPins(files, deps) {
|
|
26615
|
+
const checks = [];
|
|
26616
|
+
for (const { path: path2, body } of files) {
|
|
26617
|
+
for (const [index, line] of body.split(/\r?\n/).entries()) {
|
|
26618
|
+
const match = /^\s*-?\s*uses:\s*['"]?mutmutco\/MMI-Hub\/\.github\/actions\/([^@\s'"]+)@([0-9a-f]{40})['"]?(?:\s*(?:#.*)?)$/.exec(line);
|
|
26619
|
+
if (!match) continue;
|
|
26620
|
+
const action = SHARED_ACTION_PINS.find((item) => item.path === match[1]);
|
|
26621
|
+
if (!action || match[2] === action.floor) continue;
|
|
26622
|
+
const safe = await pinContainsFloor(deps, action.floor, match[2]);
|
|
26623
|
+
if (safe === true) continue;
|
|
26624
|
+
const location = `${path2}:${index + 1}`;
|
|
26625
|
+
const replacement = `mutmutco/MMI-Hub/.github/actions/${action.path}@${action.floor}`;
|
|
26626
|
+
checks.push({
|
|
26627
|
+
ok: safe !== false,
|
|
26628
|
+
label: `${action.path} shared action pin is current`,
|
|
26629
|
+
detail: `${location}: ${action.path} pinned to ${match[2]} \u2014 ` + (safe === false ? `lacks the minimum safe floor ${action.floor}` : "could not verify ancestry against the safe floor; refresh the pin"),
|
|
26630
|
+
remediation: safe === false ? `Replace the uses: pin at ${location} with ${replacement}` : void 0
|
|
26631
|
+
});
|
|
26632
|
+
}
|
|
26633
|
+
}
|
|
26634
|
+
return checks;
|
|
26635
|
+
}
|
|
26489
26636
|
async function rulesetDetails(deps, repo, list) {
|
|
26490
26637
|
const details = [];
|
|
26491
26638
|
for (const ruleset of list) {
|
|
@@ -27022,6 +27169,10 @@ async function auditRepoCi(repo, deps) {
|
|
|
27022
27169
|
}
|
|
27023
27170
|
}
|
|
27024
27171
|
}
|
|
27172
|
+
if (repoClass !== "hub") {
|
|
27173
|
+
const workflows = await getBranchWorkflows();
|
|
27174
|
+
if (workflows) checks.push(...await auditSharedActionPins(workflows, deps));
|
|
27175
|
+
}
|
|
27025
27176
|
const ok = checks.every((c) => c.ok);
|
|
27026
27177
|
return { repo, class: repoClass, mergePolicy: policy, ok, checks, explicitNoCi };
|
|
27027
27178
|
}
|
|
@@ -27585,7 +27736,7 @@ var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata"
|
|
|
27585
27736
|
var PRIMARY_GROUPS = [
|
|
27586
27737
|
["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
|
|
27587
27738
|
["Plan and work", ["board", "issue", "stage"]],
|
|
27588
|
-
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
27739
|
+
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train", "api"]],
|
|
27589
27740
|
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
27590
27741
|
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
|
|
27591
27742
|
// joins them on the same footing (#3979); `dist status` does too (#5576) — the checkout's own
|
|
@@ -27633,6 +27784,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
27633
27784
|
issue: { module_owner: "cli/src/issue-commands.ts", consumer: "agent-workflow" },
|
|
27634
27785
|
stage: { module_owner: "cli/src/stage-commands.ts", consumer: "agent-workflow" },
|
|
27635
27786
|
pr: { module_owner: "cli/src/pr-commands.ts", consumer: "agent-workflow" },
|
|
27787
|
+
api: { module_owner: "cli/src/api-read.ts", consumer: "agent-workflow" },
|
|
27636
27788
|
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
27637
27789
|
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
27638
27790
|
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
@@ -28372,7 +28524,10 @@ var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
|
28372
28524
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
28373
28525
|
var APP_ELIGIBLE_COMMANDS = /* @__PURE__ */ new Set([
|
|
28374
28526
|
"pr checks-wait",
|
|
28375
|
-
"pr view"
|
|
28527
|
+
"pr view",
|
|
28528
|
+
"issue list",
|
|
28529
|
+
"issue view",
|
|
28530
|
+
"api get"
|
|
28376
28531
|
]);
|
|
28377
28532
|
var APP_POLL_PERMISSIONS = {
|
|
28378
28533
|
metadata: "read",
|
|
@@ -28602,8 +28757,8 @@ async function attachBoardIssues(selectors, repoOption, deps = {}) {
|
|
|
28602
28757
|
ref = `${parsed.repo}#${parsed.number}`;
|
|
28603
28758
|
const cfg = resolveBoardConfig(await (deps.config ?? loadConfigForBoardSelector2)(selector, repoOption));
|
|
28604
28759
|
const issue = await client.rest("GET", `repos/${parsed.repo}/issues/${parsed.number}`);
|
|
28605
|
-
if (!issue.
|
|
28606
|
-
const projectItemId = await (deps.attach ?? attachViaHubApp)(cfg, issue.
|
|
28760
|
+
if (!issue.node_id) throw new Error("issue node id could not be read");
|
|
28761
|
+
const projectItemId = await (deps.attach ?? attachViaHubApp)(cfg, issue.node_id);
|
|
28607
28762
|
return { ref, attached: true, projectItemId, credential: "app_installation" };
|
|
28608
28763
|
} catch (e) {
|
|
28609
28764
|
return { ref, attached: false, reason: e.message };
|
|
@@ -28652,7 +28807,8 @@ function registerBoardCommands(program3) {
|
|
|
28652
28807
|
repo: o.repo,
|
|
28653
28808
|
includeBundleDetails: o.bundleDetails,
|
|
28654
28809
|
includeAllBodies: o.bodies,
|
|
28655
|
-
allowPartial: o.allowPartial
|
|
28810
|
+
allowPartial: o.allowPartial,
|
|
28811
|
+
snapshotOnly: o.snapshotOnly
|
|
28656
28812
|
}, o.direct ? {} : { snapshot: registryClientDeps(config) });
|
|
28657
28813
|
const output = o.json ? JSON.stringify(report) : renderBoardReport(report);
|
|
28658
28814
|
if (!o.out) console.log(output);
|
|
@@ -28695,7 +28851,7 @@ function registerBoardCommands(program3) {
|
|
|
28695
28851
|
return `Claimed ${ref} for ${holder} - In Progress${reclaimed}`;
|
|
28696
28852
|
}
|
|
28697
28853
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
28698
|
-
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
28854
|
+
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--snapshot-only", "fail instead of falling back to your personal GraphQL pool when the Hub snapshot is unavailable \u2014 for ambient readers such as sidebars (#7203)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
28699
28855
|
withExamples(mutating(
|
|
28700
28856
|
board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs); the board scan rides the Hub snapshot, same as board read").addHelpText("after", "\nclaim reads the board through the same Hub snapshot leg as `board read` (the App-installation\ncredential, never your personal GraphQL pool); the direct user-auth read is an emergency fallback\nand is named in a Warning line after the verdict (#6162).\n\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n\nsame-owner resume (#6035): when the prior marker was posted by YOUR login on THIS host and its\nlocal session is verifiably dead (transcript probe), the claim proceeds as a resume without\n--force and names the evidence. A live, foreign-host, or unprobeable prior lane still refuses.\n\nreclaim from In Review (#6339): an In Review item with no holder, or one held by the claiming\nlogin, is claimable \u2014 it moves back to In Progress and the receipt names the status it came from.\nAn In Review item another login holds is refused (ask the holder, or wait for the review to land),\nand Done stays refused. The board status is the authority; no PR state is consulted.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
|
|
28701
28857
|
(_opts, args) => ({ command: "oracle board claim", issues: args[0] ?? [] })
|
|
@@ -29618,6 +29774,43 @@ function parseGateVar(raw) {
|
|
|
29618
29774
|
}
|
|
29619
29775
|
return out;
|
|
29620
29776
|
}
|
|
29777
|
+
var SECRET_OUTPUT_PATH_RE = /^\/[A-Za-z0-9._/-]+$/;
|
|
29778
|
+
var SECRET_OUTPUT_REPO_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
29779
|
+
var SECRET_OUTPUT_KEY_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
29780
|
+
function parseTenantTaskSecretOutput(raw, label) {
|
|
29781
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
29782
|
+
throw new Error(`org project set: ${label} must be an object {path,vault:{repo,key},overwrite?}`);
|
|
29783
|
+
}
|
|
29784
|
+
const value = raw;
|
|
29785
|
+
if (Object.keys(value).some((key) => !["path", "vault", "overwrite"].includes(key))) {
|
|
29786
|
+
throw new Error(`org project set: ${label} has an unknown field`);
|
|
29787
|
+
}
|
|
29788
|
+
if (typeof value.path !== "string" || value.path.length < 2 || value.path.length > 256 || !SECRET_OUTPUT_PATH_RE.test(value.path) || value.path.split("/").includes("..")) {
|
|
29789
|
+
throw new Error(`org project set: ${label}.path must be an absolute container path (no ".." segment)`);
|
|
29790
|
+
}
|
|
29791
|
+
const vault = value.vault;
|
|
29792
|
+
if (!vault || typeof vault !== "object" || Array.isArray(vault)) {
|
|
29793
|
+
throw new Error(`org project set: ${label}.vault must be an object {repo,key}`);
|
|
29794
|
+
}
|
|
29795
|
+
const vaultMap = vault;
|
|
29796
|
+
if (Object.keys(vaultMap).some((key) => !["repo", "key"].includes(key))) {
|
|
29797
|
+
throw new Error(`org project set: ${label}.vault has an unknown field`);
|
|
29798
|
+
}
|
|
29799
|
+
if (typeof vaultMap.repo !== "string" || !SECRET_OUTPUT_REPO_RE.test(vaultMap.repo)) {
|
|
29800
|
+
throw new Error(`org project set: ${label}.vault.repo must be owner/name`);
|
|
29801
|
+
}
|
|
29802
|
+
if (typeof vaultMap.key !== "string" || !SECRET_OUTPUT_KEY_RE.test(vaultMap.key)) {
|
|
29803
|
+
throw new Error(`org project set: ${label}.vault.key must be UPPER_SNAKE`);
|
|
29804
|
+
}
|
|
29805
|
+
if (value.overwrite !== void 0 && typeof value.overwrite !== "boolean") {
|
|
29806
|
+
throw new Error(`org project set: ${label}.overwrite must be true or false`);
|
|
29807
|
+
}
|
|
29808
|
+
return {
|
|
29809
|
+
path: value.path,
|
|
29810
|
+
vault: { repo: vaultMap.repo, key: vaultMap.key },
|
|
29811
|
+
...value.overwrite !== void 0 ? { overwrite: value.overwrite } : {}
|
|
29812
|
+
};
|
|
29813
|
+
}
|
|
29621
29814
|
function parseTenantTasksVar(raw) {
|
|
29622
29815
|
let parsed;
|
|
29623
29816
|
try {
|
|
@@ -29636,7 +29829,7 @@ function parseTenantTasksVar(raw) {
|
|
|
29636
29829
|
if (!nameRe.test(name)) throw new Error(`org project set: tenantTasks name ${JSON.stringify(name)} must be lowercase kebab-case`);
|
|
29637
29830
|
if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) throw new Error(`org project set: tenantTasks.${name} must be an object`);
|
|
29638
29831
|
const task = rawTask;
|
|
29639
|
-
if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
|
|
29832
|
+
if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
|
|
29640
29833
|
if (typeof task.service !== "string" || !serviceRe.test(task.service)) throw new Error(`org project set: tenantTasks.${name}.service has an unsafe shape`);
|
|
29641
29834
|
if (!Array.isArray(task.command) || task.command.length < 1 || task.command.length > 32 || task.command.some((arg) => typeof arg !== "string" || arg.length < 1 || arg.length > 256 || /[\u0000\r\n]/.test(arg))) throw new Error(`org project set: tenantTasks.${name}.command must be 1-32 bounded argv strings`);
|
|
29642
29835
|
if (!Array.isArray(task.stages) || task.stages.length < 1 || task.stages.length > 3 || task.stages.some((stage) => typeof stage !== "string" || !validStages.has(stage)) || new Set(task.stages).size !== task.stages.length) throw new Error(`org project set: tenantTasks.${name}.stages must be unique dev/rc/main values`);
|
|
@@ -29650,7 +29843,8 @@ function parseTenantTasksVar(raw) {
|
|
|
29650
29843
|
command: task.command,
|
|
29651
29844
|
stages: task.stages,
|
|
29652
29845
|
...task.timeoutSeconds !== void 0 ? { timeoutSeconds: task.timeoutSeconds } : {},
|
|
29653
|
-
...artifact !== "none" ? { artifact } : {}
|
|
29846
|
+
...artifact !== "none" ? { artifact } : {},
|
|
29847
|
+
...task.secretOutput !== void 0 ? { secretOutput: parseTenantTaskSecretOutput(task.secretOutput, `tenantTasks.${name}.secretOutput`) } : {}
|
|
29654
29848
|
};
|
|
29655
29849
|
}
|
|
29656
29850
|
return out;
|
|
@@ -29708,7 +29902,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
29708
29902
|
requiredGcpApis: "comma-string",
|
|
29709
29903
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
29710
29904
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
29711
|
-
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required"}}; use {artifact} in argv when required',
|
|
29905
|
+
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}}; use {artifact} in argv when required; a secretOutput is master-only',
|
|
29712
29906
|
secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[]} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
|
|
29713
29907
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
29714
29908
|
statusOptions: "JSON name\u2192id map",
|
|
@@ -32321,9 +32515,7 @@ function registerBootstrapCommands(program3) {
|
|
|
32321
32515
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
32322
32516
|
deployFacts,
|
|
32323
32517
|
deployFactsRead: deployFacts !== null,
|
|
32324
|
-
edgeCertificates: await Promise.all(
|
|
32325
|
-
const domain = meta?.edgeDomains?.[stage];
|
|
32326
|
-
if (!domain) return null;
|
|
32518
|
+
edgeCertificates: await Promise.all(edgeCertificateStagesForBootstrap(effectiveTrack, meta?.edgeDomains).map(async (stage) => {
|
|
32327
32519
|
try {
|
|
32328
32520
|
return { stage, status: await edgeCertificateForStage(slug, stage, reg) };
|
|
32329
32521
|
} catch (e) {
|
|
@@ -32823,7 +33015,18 @@ function registerBootstrapCommands(program3) {
|
|
|
32823
33015
|
if (isLegacyBlock) {
|
|
32824
33016
|
content = upsertManagedGitignoreBlock(preservedContent).content;
|
|
32825
33017
|
} else {
|
|
32826
|
-
const
|
|
33018
|
+
const rulesetVars = preserveRuleset ? gateSeedVars(
|
|
33019
|
+
o.class,
|
|
33020
|
+
effectiveTrack,
|
|
33021
|
+
"node",
|
|
33022
|
+
registryMeta?.requiredCheckBranches,
|
|
33023
|
+
registryMeta?.requiredChecks
|
|
33024
|
+
) : null;
|
|
33025
|
+
const writeContent = resolveSeedWriteContent(resolved, preserveRuleset && rulesetVars ? {
|
|
33026
|
+
...vars,
|
|
33027
|
+
...registryMeta?.requiredChecks?.length ? { GATE_RULESET_CONTEXTS_JSON: rulesetVars.GATE_RULESET_CONTEXTS_JSON } : {},
|
|
33028
|
+
...registryMeta?.requiredCheckBranches?.length ? { GATE_RULESET_BRANCH_REFS_JSON: rulesetVars.GATE_RULESET_BRANCH_REFS_JSON } : {}
|
|
33029
|
+
} : vars, readFile9, preservedContent);
|
|
32827
33030
|
if (!writeContent.ok) {
|
|
32828
33031
|
return fail(`bootstrap apply: ${resolved.target}: ${writeContent.reason} \u2014 refusing to overwrite repo-owned content`);
|
|
32829
33032
|
}
|
|
@@ -32831,7 +33034,10 @@ function registerBootstrapCommands(program3) {
|
|
|
32831
33034
|
if (preserveRuleset) {
|
|
32832
33035
|
if (content === null) return fail("bootstrap apply: canonical product ruleset seed is unreadable");
|
|
32833
33036
|
try {
|
|
32834
|
-
content = reseedProductRulesetStrictness(baseContent, content
|
|
33037
|
+
content = reseedProductRulesetStrictness(baseContent, content, {
|
|
33038
|
+
requiredChecks: !!registryMeta?.requiredChecks?.length,
|
|
33039
|
+
requiredCheckBranches: !!registryMeta?.requiredCheckBranches?.length
|
|
33040
|
+
});
|
|
32835
33041
|
} catch (e) {
|
|
32836
33042
|
return fail(`bootstrap apply: product ruleset preservation refused: ${e.message}`);
|
|
32837
33043
|
}
|
|
@@ -33054,8 +33260,7 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
33054
33260
|
if (res.ok) {
|
|
33055
33261
|
ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
|
|
33056
33262
|
applied.push(`ddb register ${registerPayload.slug}`);
|
|
33057
|
-
for (const stage of
|
|
33058
|
-
if (!registerPayload.edgeDomains?.[stage]) continue;
|
|
33263
|
+
for (const stage of edgeCertificateStagesForBootstrap(registerPayload.releaseTrack, registerPayload.edgeDomains)) {
|
|
33059
33264
|
try {
|
|
33060
33265
|
const cert = await edgeCertificateForStage(slug, stage, reg, true);
|
|
33061
33266
|
if (cert.required) applied.push(`edge certificate ${stage}: ${cert.action ?? "checked"}`);
|
|
@@ -33135,11 +33340,25 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
33135
33340
|
});
|
|
33136
33341
|
}
|
|
33137
33342
|
}
|
|
33138
|
-
|
|
33343
|
+
let ancillaryPlan;
|
|
33344
|
+
let environmentPlan;
|
|
33345
|
+
if (!o.execute && !onlyTarget) {
|
|
33346
|
+
try {
|
|
33347
|
+
const liveLabels = JSON.parse((await gh(["label", "list", "-R", repo, "--limit", "1000", "--json", "name"])).stdout);
|
|
33348
|
+
if (!Array.isArray(liveLabels)) throw new Error("label list is not an array");
|
|
33349
|
+
ancillaryPlan = planBootstrapLabels(manifest.labels, liveLabels);
|
|
33350
|
+
environmentPlan = (await verifyRepoEnvironments(repo, effectiveTrack, controlClient)).map(classifyEnvironmentDrift);
|
|
33351
|
+
} catch (e) {
|
|
33352
|
+
return failGraceful(`bootstrap apply: cannot plan labels/environments; no writes made: ${e.message}`);
|
|
33353
|
+
}
|
|
33354
|
+
}
|
|
33355
|
+
if (o.json) console.log(JSON.stringify({ repo, class: o.class, sourceSha: seedSource.sha, only: onlyTarget || null, execute: o.execute, seedDelivery: seedPlan.mode, seedBranch: seedPlan.branch, seedBaseSha: scopedPlan?.baseSha, seedPrUrl, actions, controlPlane: controlRows, labels: ancillaryPlan, environments: environmentPlan, applied, ddbWrites }, null, 2));
|
|
33139
33356
|
else {
|
|
33140
33357
|
console.log(renderSeedPlan(actions));
|
|
33141
33358
|
if (controlRows.length) console.log(`
|
|
33142
33359
|
${renderBootstrapControlPlan(controlRows)}`);
|
|
33360
|
+
if (ancillaryPlan && environmentPlan) console.log(`
|
|
33361
|
+
${renderBootstrapAncillaryPlan(ancillaryPlan, environmentPlan)}`);
|
|
33143
33362
|
if (o.execute) console.log(`
|
|
33144
33363
|
LIVE apply to ${repo}:
|
|
33145
33364
|
${applied.join("\n ")}`);
|
|
@@ -33649,7 +33868,7 @@ function formatDeployStatus(r) {
|
|
|
33649
33868
|
var STAGES2 = ["dev", "rc", "main"];
|
|
33650
33869
|
function registerDeployCommands(program3) {
|
|
33651
33870
|
const deploy = program3.command("deploy").description("per-stage deploy observability \u2014 last run, health, and running version (#2688)");
|
|
33652
|
-
deploy.command("status <stage>").description("last deploy run + runtime health probe + running version for a stage (defaults to the current repo);
|
|
33871
|
+
deploy.command("status <stage>").description("last deploy run + runtime health probe + running version for a stage (defaults to the current repo); a root answering 401/403/404 is re-probed at /health and the `url` field names the endpoint that answered (#6137)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (stage, o) => {
|
|
33653
33872
|
if (!STAGES2.includes(stage)) {
|
|
33654
33873
|
return fail(`runtime deploy status: <stage> must be dev, rc, or main`);
|
|
33655
33874
|
}
|
|
@@ -38035,6 +38254,70 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
38035
38254
|
});
|
|
38036
38255
|
}
|
|
38037
38256
|
|
|
38257
|
+
// src/api-read.ts
|
|
38258
|
+
init_cli_shared();
|
|
38259
|
+
var API_ACCEPT_HEADERS = {
|
|
38260
|
+
json: "application/vnd.github+json",
|
|
38261
|
+
raw: "application/vnd.github.raw"
|
|
38262
|
+
};
|
|
38263
|
+
function acceptHeaderFor(accept) {
|
|
38264
|
+
const v = (accept ?? "json").trim().toLowerCase();
|
|
38265
|
+
const header = API_ACCEPT_HEADERS[v];
|
|
38266
|
+
if (!header) {
|
|
38267
|
+
throw new Error(`api get: --accept must be one of: json, raw (received '${accept}')`);
|
|
38268
|
+
}
|
|
38269
|
+
return header;
|
|
38270
|
+
}
|
|
38271
|
+
var NAME = "[A-Za-z0-9_.-]+";
|
|
38272
|
+
var FREE = "[^?#%\\\\\\s]+";
|
|
38273
|
+
var PULL_FILES_RE = new RegExp(`^repos/${NAME}/${NAME}/pulls/\\d+/files$`);
|
|
38274
|
+
var COMPARE_RE = new RegExp(`^repos/${NAME}/${NAME}/compare/${FREE}\\.\\.\\.${FREE}$`);
|
|
38275
|
+
var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
38276
|
+
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
38277
|
+
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>]";
|
|
38278
|
+
function parseApiGetPath(rawPath) {
|
|
38279
|
+
const path2 = rawPath.trim();
|
|
38280
|
+
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
38281
|
+
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
38282
|
+
if (PULL_FILES_RE.test(path2)) return { kind: "pull-files", path: path2, paginate: true };
|
|
38283
|
+
if (COMPARE_RE.test(path2)) return { kind: "compare", path: path2, paginate: false };
|
|
38284
|
+
const q = path2.indexOf("?");
|
|
38285
|
+
const base = q === -1 ? path2 : path2.slice(0, q);
|
|
38286
|
+
const query = q === -1 ? "" : path2.slice(q + 1);
|
|
38287
|
+
if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
|
|
38288
|
+
return { kind: "contents", path: path2, paginate: false };
|
|
38289
|
+
}
|
|
38290
|
+
throw new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
38291
|
+
}
|
|
38292
|
+
function buildApiGetArgs(path2, accept) {
|
|
38293
|
+
const req = parseApiGetPath(path2);
|
|
38294
|
+
const args = ["api", req.path];
|
|
38295
|
+
if (req.paginate) args.push("--paginate");
|
|
38296
|
+
const header = acceptHeaderFor(accept);
|
|
38297
|
+
if (header !== API_ACCEPT_HEADERS.json) args.push("-H", `Accept: ${header}`);
|
|
38298
|
+
return args;
|
|
38299
|
+
}
|
|
38300
|
+
async function runApiGet(run, path2, accept) {
|
|
38301
|
+
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
38302
|
+
}
|
|
38303
|
+
function registerApiReadCommands(program3) {
|
|
38304
|
+
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents)");
|
|
38305
|
+
api.command("get <path>").description("GET one allowlisted REST path and print the response body to stdout (JSON, or raw file bytes with --accept raw); refuses everything outside the allowlist").option("--accept <type>", "response media type: json (default) or raw (file bytes)", "json").action(async (path2, o) => {
|
|
38306
|
+
let body;
|
|
38307
|
+
try {
|
|
38308
|
+
body = await runApiGet(
|
|
38309
|
+
async (args, timeoutMs) => (await execFileP("gh", args, { timeout: timeoutMs })).stdout,
|
|
38310
|
+
path2,
|
|
38311
|
+
o.accept
|
|
38312
|
+
);
|
|
38313
|
+
} catch (e) {
|
|
38314
|
+
const err = e;
|
|
38315
|
+
return fail(`api get: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
38316
|
+
}
|
|
38317
|
+
process.stdout.write(body);
|
|
38318
|
+
});
|
|
38319
|
+
}
|
|
38320
|
+
|
|
38038
38321
|
// src/pr-commands.ts
|
|
38039
38322
|
var import_promises4 = require("node:fs/promises");
|
|
38040
38323
|
init_cli_shared();
|
|
@@ -43515,6 +43798,14 @@ function registerCollaborationCommands(program3) {
|
|
|
43515
43798
|
}
|
|
43516
43799
|
return resolveParentField(payload);
|
|
43517
43800
|
}
|
|
43801
|
+
async function readClosedByField(number, repo) {
|
|
43802
|
+
try {
|
|
43803
|
+
return { closedBy: await runClosedBy(queryDeps(), { number, repo }) };
|
|
43804
|
+
} catch (e) {
|
|
43805
|
+
const err = e;
|
|
43806
|
+
return { closedByReadError: (err.stderr || err.message || String(e)).trim() };
|
|
43807
|
+
}
|
|
43808
|
+
}
|
|
43518
43809
|
function withoutTransportNumber(data) {
|
|
43519
43810
|
const rest = { ...data };
|
|
43520
43811
|
delete rest.number;
|
|
@@ -43533,7 +43824,7 @@ function registerCollaborationCommands(program3) {
|
|
|
43533
43824
|
return void 0;
|
|
43534
43825
|
}
|
|
43535
43826
|
}
|
|
43536
|
-
issue.command("view <ref>").aliases(["show", "get", "read"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show`, `get` and `read` alias view (#5483/#5706/#6078)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). `parent` is synthetic: selectable here, composed by the wrapper via a REST parent read, never sent to gh (#5970). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (ref, o) => {
|
|
43827
|
+
issue.command("view <ref>").aliases(["show", "get", "read"]).description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894). `show`, `get` and `read` alias view (#5483/#5706/#6078)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). `parent` is synthetic: selectable here, composed by the wrapper via a REST parent read, never sent to gh (#5970). `closedBy` is synthetic too: the closing PRs with merged/mergedAt/mergeCommit.oid, composed via one fixed GraphQL read (#7219). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (ref, o) => {
|
|
43537
43828
|
let parsed;
|
|
43538
43829
|
try {
|
|
43539
43830
|
parsed = parseIssueRef(ref, o.repo);
|
|
@@ -43544,6 +43835,7 @@ function registerCollaborationCommands(program3) {
|
|
|
43544
43835
|
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
43545
43836
|
if (!repo) return fail("issue view: could not resolve repo (pass --repo <owner/repo>)");
|
|
43546
43837
|
const fields = normalizeIssueViewJsonFields(o.json);
|
|
43838
|
+
const wantsClosedBy = fields.split(",").map((f) => f.trim()).includes("closedBy");
|
|
43547
43839
|
const gh = resolveIssueViewGhFields(fields);
|
|
43548
43840
|
const emit = (data, parent) => console.log(JSON.stringify({ ...gh.numberIsTransport ? withoutTransportNumber(data) : data, ...parent }));
|
|
43549
43841
|
const readIssue = async (targetRepo2, targetNumber) => {
|
|
@@ -43555,9 +43847,10 @@ function registerCollaborationCommands(program3) {
|
|
|
43555
43847
|
const data = await ghJson(["issue", "view", String(targetNumber), "--repo", targetRepo2, "--json", gh.ghFields]);
|
|
43556
43848
|
return { data, parent: await readParentField(targetNumber, targetRepo2) };
|
|
43557
43849
|
};
|
|
43850
|
+
const readClosedBy = async (targetRepo2, targetNumber) => wantsClosedBy ? readClosedByField(targetNumber, targetRepo2) : {};
|
|
43558
43851
|
try {
|
|
43559
43852
|
const { data, parent } = await readIssue(repo, n);
|
|
43560
|
-
emit(data, parent);
|
|
43853
|
+
emit(data, { ...parent, ...await readClosedBy(repo, n) });
|
|
43561
43854
|
} catch (e) {
|
|
43562
43855
|
const err = e;
|
|
43563
43856
|
const raw = (err.stderr || err.message || String(e)).trim();
|
|
@@ -43566,7 +43859,7 @@ function registerCollaborationCommands(program3) {
|
|
|
43566
43859
|
if (moved) {
|
|
43567
43860
|
try {
|
|
43568
43861
|
const { data, parent } = await readIssue(moved.repo, moved.number);
|
|
43569
|
-
emit({ ...data, transferred: { from: `${repo}#${n}`, to: `${moved.repo}#${moved.number}`, url: moved.url } }, parent);
|
|
43862
|
+
emit({ ...data, transferred: { from: `${repo}#${n}`, to: `${moved.repo}#${moved.number}`, url: moved.url } }, { ...parent, ...await readClosedBy(moved.repo, moved.number) });
|
|
43570
43863
|
return;
|
|
43571
43864
|
} catch (reRead) {
|
|
43572
43865
|
const reErr = reRead;
|
|
@@ -48711,7 +49004,7 @@ tenant.command("reconcile <owner/repo> <stage>").description("re-render this ten
|
|
|
48711
49004
|
return failGraceful(`runtime tenant reconcile: ${e.message}`);
|
|
48712
49005
|
}
|
|
48713
49006
|
});
|
|
48714
|
-
tenant.command("status <owner/repo> <stage>").description("read tenant runtime readiness without dispatching tenant-control: DEPLOY row, last deploy run, public URL probe, and TLS/Caddy/Cloudflare hints;
|
|
49007
|
+
tenant.command("status <owner/repo> <stage>").description("read tenant runtime readiness without dispatching tenant-control: DEPLOY row, last deploy run, public URL probe, and TLS/Caddy/Cloudflare hints; a root answering 401/403/404 is re-probed at /health and the `url` field names the endpoint that answered (#6137)").action(async (repo, stage) => {
|
|
48715
49008
|
if (!["dev", "rc", "main"].includes(stage)) return fail("runtime tenant status: <stage> must be dev, rc, or main");
|
|
48716
49009
|
const cfg = await loadConfig();
|
|
48717
49010
|
const result = await buildTenantRuntimeStatusFor(repo, stage, cfg);
|
|
@@ -49195,7 +49488,7 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
49195
49488
|
return reportWrite("org project set-deploy", res);
|
|
49196
49489
|
});
|
|
49197
49490
|
var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for every stage of your own repo \u2014 new-task-only; editing an existing task stays master-only (`org project set --var tenantTasks=...`)");
|
|
49198
|
-
projectTenantTasks.command("declare <name>").description("register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?}; required artifact tasks must carry {artifact} in the command argv.").option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
|
|
49491
|
+
projectTenantTasks.command("declare <name>").description("register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?}; required artifact tasks must carry {artifact} in the command argv.").option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", "task command as argv (repeatable; required unless --var carries it)").option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..3600").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags. A task carrying secretOutput:{path,vault:{repo,key},overwrite?} is master-only; declare that via `org project set --var tenantTasks=...`").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
|
|
49199
49492
|
const cfg = await loadConfig();
|
|
49200
49493
|
let target;
|
|
49201
49494
|
try {
|
|
@@ -49223,6 +49516,9 @@ projectTenantTasks.command("declare <name>").description("register a NEW tenant
|
|
|
49223
49516
|
...o.artifact !== void 0 ? { artifact: o.artifact } : {}
|
|
49224
49517
|
};
|
|
49225
49518
|
}
|
|
49519
|
+
if (task && typeof task === "object" && task.secretOutput !== void 0) {
|
|
49520
|
+
return fail("org project tenant-tasks declare: a task with secretOutput names a vault destination and is master-only (#7213) \u2014 declare it via `mmi-cli oracle org project set <owner/repo> --var 'tenantTasks=...'`");
|
|
49521
|
+
}
|
|
49226
49522
|
const res = await postJson(`/projects/${encodeURIComponent(slug)}/tenant-tasks`, { repo: target, tenantTasks: { [name]: task } }, registryClientDeps(cfg), "POST", { noRetry: true });
|
|
49227
49523
|
if (o.json) {
|
|
49228
49524
|
console.log(JSON.stringify(res, null, 2));
|
|
@@ -49370,6 +49666,7 @@ registerDeployCommands(program2);
|
|
|
49370
49666
|
registerDiscoveryCommands(program2);
|
|
49371
49667
|
registerExplainCommand(program2);
|
|
49372
49668
|
registerPrLifecycleCommands(program2);
|
|
49669
|
+
registerApiReadCommands(program2);
|
|
49373
49670
|
registerSessionReport(program2);
|
|
49374
49671
|
registerLearningClosureRateCommand(program2);
|
|
49375
49672
|
registerLearningPickupCommand(program2);
|
package/package.json
CHANGED