@mutmutco/codex-plugin 4.5.150 → 4.5.152
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +120 -41
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/bootstrap/seeds/suite-guide.template.md +1 -1
- package/skills/bootstrap/seeds/suite-module-package.template.json +1 -1
- package/skills/bootstrap/seeds/suite-files.template.json +0 -1
- package/skills/bootstrap/seeds/suite-screens.template.json +0 -170
- package/skills/bootstrap/seeds/suite-screens.template.yml +0 -63
package/cli/dist/main.cjs
CHANGED
|
@@ -7152,7 +7152,7 @@ async function readAppVaultSecret(read) {
|
|
|
7152
7152
|
if (/^(Hub GitHub App allowance used up|rate limited)/.test(reason)) {
|
|
7153
7153
|
throw new AppActorError(`app actor: the vault could not read the App credentials because GitHub is rate-limited, not a denial: ${reason}`, "mint-failed");
|
|
7154
7154
|
}
|
|
7155
|
-
if (/^authority refused|\(HTTP
|
|
7155
|
+
if (/^authority refused|\(HTTP 40[14]\b/.test(reason)) return null;
|
|
7156
7156
|
throw new AppActorError(`app actor: the vault read of the App credentials failed, not a denial: ${reason}`, "mint-failed");
|
|
7157
7157
|
}
|
|
7158
7158
|
}
|
|
@@ -7203,7 +7203,8 @@ async function mintInstallationToken(deps) {
|
|
|
7203
7203
|
}
|
|
7204
7204
|
return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
|
|
7205
7205
|
}
|
|
7206
|
-
async function pollTokenOrUndefined(deps) {
|
|
7206
|
+
async function pollTokenOrUndefined(deps, env = process.env) {
|
|
7207
|
+
if (env[APP_ACTOR_ENV]?.trim() === "personal") return void 0;
|
|
7207
7208
|
try {
|
|
7208
7209
|
return (await mintInstallationToken(deps)).token;
|
|
7209
7210
|
} catch (e) {
|
|
@@ -7212,25 +7213,42 @@ async function pollTokenOrUndefined(deps) {
|
|
|
7212
7213
|
}
|
|
7213
7214
|
}
|
|
7214
7215
|
var notedIneligibleCommand = false;
|
|
7216
|
+
var notedVaultDenied = false;
|
|
7215
7217
|
async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
|
|
7216
7218
|
`)) {
|
|
7217
7219
|
activeQuotaScope = void 0;
|
|
7218
7220
|
activeToken = void 0;
|
|
7219
7221
|
const requested = (env[APP_ACTOR_ENV] ?? "").trim();
|
|
7220
|
-
if (
|
|
7221
|
-
if (requested !== "app") {
|
|
7222
|
-
throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app'
|
|
7222
|
+
if (requested === "personal") return "personal";
|
|
7223
|
+
if (requested && requested !== "app") {
|
|
7224
|
+
throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app' and 'personal' are supported; refusing to guess an identity`);
|
|
7223
7225
|
}
|
|
7224
7226
|
if (!APP_ELIGIBLE_COMMANDS.has(commandPath3)) {
|
|
7225
|
-
if (!notedIneligibleCommand) {
|
|
7227
|
+
if (requested === "app" && !notedIneligibleCommand) {
|
|
7226
7228
|
notedIneligibleCommand = true;
|
|
7227
7229
|
note(`app actor: '${commandPath3}' is personal-only (viewer/@me/authorship) \u2014 running on the personal token despite ${APP_ACTOR_ENV}=app`);
|
|
7228
7230
|
}
|
|
7229
7231
|
return "personal";
|
|
7230
7232
|
}
|
|
7233
|
+
const personalGh = env.GH_TOKEN;
|
|
7234
|
+
const personalGithub = env.GITHUB_TOKEN;
|
|
7231
7235
|
delete env.GH_TOKEN;
|
|
7232
7236
|
delete env.GITHUB_TOKEN;
|
|
7233
|
-
|
|
7237
|
+
let minted;
|
|
7238
|
+
try {
|
|
7239
|
+
minted = await mint();
|
|
7240
|
+
} catch (e) {
|
|
7241
|
+
if (!requested && e instanceof AppActorError && e.code === "vault-denied") {
|
|
7242
|
+
if (personalGh !== void 0) env.GH_TOKEN = personalGh;
|
|
7243
|
+
if (personalGithub !== void 0) env.GITHUB_TOKEN = personalGithub;
|
|
7244
|
+
if (!notedVaultDenied) {
|
|
7245
|
+
notedVaultDenied = true;
|
|
7246
|
+
note("app actor: vault denied App credentials \u2014 running on the personal token on this host");
|
|
7247
|
+
}
|
|
7248
|
+
return "personal";
|
|
7249
|
+
}
|
|
7250
|
+
throw e;
|
|
7251
|
+
}
|
|
7234
7252
|
env.GH_TOKEN = minted.token;
|
|
7235
7253
|
activeQuotaScope = minted.quotaScope;
|
|
7236
7254
|
activeToken = minted.token;
|
|
@@ -7741,7 +7759,7 @@ function setPollTokenProvider(provider) {
|
|
|
7741
7759
|
observedPool.clear();
|
|
7742
7760
|
}
|
|
7743
7761
|
async function pollToken() {
|
|
7744
|
-
if (!pollTokenProvider) return void 0;
|
|
7762
|
+
if (process.env.MMI_ACTOR?.trim() === "personal" || !pollTokenProvider) return void 0;
|
|
7745
7763
|
pollTokenOnce ??= pollTokenProvider().catch((e) => {
|
|
7746
7764
|
if (e instanceof AppActorError && e.code === "mint-failed") throw e;
|
|
7747
7765
|
return void 0;
|
|
@@ -17125,9 +17143,6 @@ function suiteModuleSeeds(seeds, suite, deployModel) {
|
|
|
17125
17143
|
"module/.npmrc": "suite-npmrc.template",
|
|
17126
17144
|
"module/src/module.jsx": "suite-module.template.jsx",
|
|
17127
17145
|
"module/vite.config.mjs": "suite-vite.template.mjs",
|
|
17128
|
-
"module/screens/screens.json": "suite-screens.template.json",
|
|
17129
|
-
"module/screens/files.json": "suite-files.template.json",
|
|
17130
|
-
".github/workflows/screens.yml": "suite-screens.template.yml",
|
|
17131
17146
|
"docs/Guides/mm-work-module.md": "suite-guide.template.md"
|
|
17132
17147
|
};
|
|
17133
17148
|
return [
|
|
@@ -18669,10 +18684,10 @@ var rollout_plan_default = {
|
|
|
18669
18684
|
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)."
|
|
18670
18685
|
},
|
|
18671
18686
|
baseline: {
|
|
18672
|
-
version: "4.5.
|
|
18673
|
-
tag: "v4.5.
|
|
18674
|
-
commit: "
|
|
18675
|
-
npm: "@mutmutco/cli@4.5.
|
|
18687
|
+
version: "4.5.152",
|
|
18688
|
+
tag: "v4.5.152",
|
|
18689
|
+
commit: "8437cd11d3f1",
|
|
18690
|
+
npm: "@mutmutco/cli@4.5.152"
|
|
18676
18691
|
},
|
|
18677
18692
|
exitCriterion: "fleet-n-of-n",
|
|
18678
18693
|
hubOnlyShortcut: "forbidden",
|
|
@@ -18689,14 +18704,14 @@ var rollout_plan_default = {
|
|
|
18689
18704
|
repo: "mutmutco/mmi-hub",
|
|
18690
18705
|
role: "canary",
|
|
18691
18706
|
schedule: "train",
|
|
18692
|
-
v3Target: "v4.5.
|
|
18707
|
+
v3Target: "v4.5.152"
|
|
18693
18708
|
}
|
|
18694
18709
|
],
|
|
18695
18710
|
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.",
|
|
18696
18711
|
rollback: {
|
|
18697
18712
|
independent: true,
|
|
18698
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
18699
|
-
v3Target: "v4.5.
|
|
18713
|
+
mechanism: "npm dist-tag latest -> 4.5.152 and redeploy the Hub Lambda from tag v4.5.152 (8437cd11d3f1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
18714
|
+
v3Target: "v4.5.152 (@mutmutco/cli@4.5.152, tag commit 8437cd11d3f1 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
18700
18715
|
}
|
|
18701
18716
|
},
|
|
18702
18717
|
{
|
|
@@ -22987,6 +23002,14 @@ async function runTenantControl(deps, options) {
|
|
|
22987
23002
|
if (output) result.taskOutput = clampTaskOutput(output);
|
|
22988
23003
|
}
|
|
22989
23004
|
}
|
|
23005
|
+
if (watch && runId != null && action === "run-task" && conclusion === "success") {
|
|
23006
|
+
try {
|
|
23007
|
+
const pages = JSON.parse(await deps.run("gh", ["api", `repos/${HUB_REPO2}/actions/runs/${runId}/artifacts`, "--paginate", "--slurp"]));
|
|
23008
|
+
result.outputArtifacts = pages.flatMap((page) => page.artifacts ?? []).filter((a) => a.name.startsWith("task-output-") && Number.isSafeInteger(a.id) && a.id > 0).map((a) => ({ id: a.id, name: a.name, runId, downloadCommand: `MMI_ACTOR=app mmi-cli devops api get repos/${HUB_REPO2}/actions/artifacts/${a.id}/zip --out tmp/task-output-${runId}-${a.id}.zip` }));
|
|
23009
|
+
} catch {
|
|
23010
|
+
result.outputArtifactReadError = `Could not read output artifacts; retry with --resume ${runId} --watch (no re-dispatch)`;
|
|
23011
|
+
}
|
|
23012
|
+
}
|
|
22990
23013
|
result.note = conclusion === "success" ? `tenant-control ${action} run succeeded` : conclusion === "failure" ? `tenant-control ${action} run failed \u2014 inspect the run` : runId == null ? `dispatched tenant-control.yml (${action}) \u2014 run not correlated; check the Actions tab` : `dispatched tenant-control.yml (${action}) \u2014 not watched`;
|
|
22991
23014
|
if (conclusion === "pending" && runId != null && action === "run-task") {
|
|
22992
23015
|
result.note += `; run ${runId} \u2014 resume without re-dispatching: mmi-cli devops runtime tenant control ${repo} ${stage} run-task --resume ${runId} --watch`;
|
|
@@ -23001,6 +23024,8 @@ function renderTenantControl(r) {
|
|
|
23001
23024
|
if (r.secrets?.length) {
|
|
23002
23025
|
for (const s of r.secrets) lines2.push(` ${s.key}: ${s.status}`);
|
|
23003
23026
|
}
|
|
23027
|
+
for (const a of r.outputArtifacts ?? []) lines2.push(` output artifact: ${a.name} (id ${a.id}, run ${a.runId})`, ` ${a.downloadCommand}`);
|
|
23028
|
+
if (r.outputArtifactReadError) lines2.push(` ${r.outputArtifactReadError}`);
|
|
23004
23029
|
if (r.taskOutput) {
|
|
23005
23030
|
lines2.push(" task output:");
|
|
23006
23031
|
for (const l of r.taskOutput.split("\n")) lines2.push(` ${l}`);
|
|
@@ -32901,7 +32926,7 @@ function parseTenantTasksVar(raw) {
|
|
|
32901
32926
|
if (!nameRe.test(name)) throw new Error(`org project set: tenantTasks name ${JSON.stringify(name)} must be lowercase kebab-case`);
|
|
32902
32927
|
if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) throw new Error(`org project set: tenantTasks.${name} must be an object`);
|
|
32903
32928
|
const task = rawTask;
|
|
32904
|
-
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`);
|
|
32929
|
+
if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "output", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
|
|
32905
32930
|
if (typeof task.service !== "string" || !serviceRe.test(task.service)) throw new Error(`org project set: tenantTasks.${name}.service has an unsafe shape`);
|
|
32906
32931
|
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`);
|
|
32907
32932
|
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`);
|
|
@@ -32911,12 +32936,16 @@ function parseTenantTasksVar(raw) {
|
|
|
32911
32936
|
if (artifact !== "none" && artifact !== "required") throw new Error(`org project set: tenantTasks.${name}.artifact must be none or required`);
|
|
32912
32937
|
const carriesPlaceholder = task.command.some((arg) => arg.includes("{artifact}"));
|
|
32913
32938
|
if (artifact === "required" !== carriesPlaceholder) throw new Error(`org project set: tenantTasks.${name} must use {artifact} exactly when artifact is required`);
|
|
32939
|
+
if (task.output !== void 0 && task.output !== "file") throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
|
|
32940
|
+
const outputPlaceholder = task.command.some((arg) => arg.includes("{output}"));
|
|
32941
|
+
if (task.output === "file" !== outputPlaceholder || task.output !== void 0 && task.secretOutput !== void 0) throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
|
|
32914
32942
|
out[name] = {
|
|
32915
32943
|
service: task.service,
|
|
32916
32944
|
command: task.command,
|
|
32917
32945
|
stages: task.stages,
|
|
32918
32946
|
...task.timeoutSeconds !== void 0 ? { timeoutSeconds: task.timeoutSeconds } : {},
|
|
32919
32947
|
...artifact !== "none" ? { artifact } : {},
|
|
32948
|
+
...task.output === "file" ? { output: "file" } : {},
|
|
32920
32949
|
...task.secretOutput !== void 0 ? { secretOutput: parseTenantTaskSecretOutput(task.secretOutput, `tenantTasks.${name}.secretOutput`) } : {}
|
|
32921
32950
|
};
|
|
32922
32951
|
}
|
|
@@ -32979,7 +33008,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
32979
33008
|
requiredGcpApis: "comma-string",
|
|
32980
33009
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
32981
33010
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
32982
|
-
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
33011
|
+
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",output?:"file",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
32983
33012
|
internalPeers: "JSON {owner/repo:[dev|rc|main]} \u2014 same-box suite peers via the box Caddy; clear with --unset internalPeers (#7886)",
|
|
32984
33013
|
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",
|
|
32985
33014
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
@@ -35655,8 +35684,8 @@ function suiteModulePackageAccessCheck(suite, deployModel) {
|
|
|
35655
35684
|
if (suite !== "mm-work" || deployModel !== "tenant-container") return void 0;
|
|
35656
35685
|
return {
|
|
35657
35686
|
ok: true,
|
|
35658
|
-
label: "mmwork-ds
|
|
35659
|
-
detail:
|
|
35687
|
+
label: "mmwork-ds package access (manual org-admin step; unverified)",
|
|
35688
|
+
detail: "An org admin must add this repository with Read under @mutmutco/mmwork-ds \u2192 Package settings \u2192 Manage Actions access (#7984). No GitHub REST API can verify or grant this; without it the image build fails with 403 read_package."
|
|
35660
35689
|
};
|
|
35661
35690
|
}
|
|
35662
35691
|
function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
@@ -40608,6 +40637,7 @@ var TRAILER_KEY = "Test-Policy-Override";
|
|
|
40608
40637
|
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
40609
40638
|
var REC = "";
|
|
40610
40639
|
var FLD = "";
|
|
40640
|
+
var GH_SQUASH_COAUTHORS_RE = /\r?\n(?:[ \t]*\r?\n)+---------\r?\n(?:[ \t]*\r?\n)+(?:Co-authored-by:[ \t]+\S[^\r\n]*(?:\r?\n|$))+[ \t\r\n]*$/i;
|
|
40611
40641
|
var WAIVABLE_KINDS = [
|
|
40612
40642
|
"mandatory-zone-untested",
|
|
40613
40643
|
"unrequested-test-file",
|
|
@@ -40765,6 +40795,21 @@ function isShallowRepository(cwd) {
|
|
|
40765
40795
|
return true;
|
|
40766
40796
|
}
|
|
40767
40797
|
}
|
|
40798
|
+
function readOverrideTrailer(trailer, body, cwd) {
|
|
40799
|
+
const direct = (trailer ?? "").trim();
|
|
40800
|
+
if (direct) return direct;
|
|
40801
|
+
const raw = body ?? "";
|
|
40802
|
+
const stripped = raw.replace(GH_SQUASH_COAUTHORS_RE, "\n");
|
|
40803
|
+
if (stripped === raw) return "";
|
|
40804
|
+
const prefix = `${TRAILER_KEY.toLowerCase()}:`;
|
|
40805
|
+
return (0, import_node_child_process12.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
40806
|
+
windowsHide: true,
|
|
40807
|
+
cwd,
|
|
40808
|
+
input: stripped,
|
|
40809
|
+
encoding: "utf8",
|
|
40810
|
+
maxBuffer: 32 * 1024 * 1024
|
|
40811
|
+
}).split(/\r?\n/).filter((line) => line.trim().toLowerCase().startsWith(prefix)).map((line) => line.trim().slice(prefix.length).trim()).join("\n").trim();
|
|
40812
|
+
}
|
|
40768
40813
|
function readOverride(base, cwd) {
|
|
40769
40814
|
const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
|
|
40770
40815
|
const refusals = [];
|
|
@@ -40773,7 +40818,7 @@ function readOverride(base, cwd) {
|
|
|
40773
40818
|
const [sha, trailer, body] = record.replace(/^\s+/, "").split(FLD);
|
|
40774
40819
|
if (!sha) continue;
|
|
40775
40820
|
if (override) break;
|
|
40776
|
-
const value = (trailer
|
|
40821
|
+
const value = readOverrideTrailer(trailer, body, cwd);
|
|
40777
40822
|
if (!value) {
|
|
40778
40823
|
const shaped = OVERRIDE_RE.exec(body ?? "");
|
|
40779
40824
|
if (shaped) refusals.push(invisibleTrailer(sha, shaped[0].trim()));
|
|
@@ -53243,8 +53288,9 @@ async function runBrokerRestChecks(deps, spec, resolveRepo2) {
|
|
|
53243
53288
|
return { rows: read.rows, state: read.state, exitCode: checkRowsExitCode(read.state) };
|
|
53244
53289
|
}
|
|
53245
53290
|
function requireBrokerApp(label) {
|
|
53246
|
-
|
|
53247
|
-
|
|
53291
|
+
const actor = (process.env.MMI_ACTOR ?? "").trim();
|
|
53292
|
+
if (actor && actor !== "app") {
|
|
53293
|
+
fail(`${label}: App-only read \u2014 re-run with spawn-scoped MMI_ACTOR=app (explicit personal opt-out uses gh and the human commands instead)`);
|
|
53248
53294
|
}
|
|
53249
53295
|
}
|
|
53250
53296
|
function registerPrChecksRequiredCommand(prRoot, resolveRepo2, overrides) {
|
|
@@ -53400,27 +53446,58 @@ var COMPARE_RE = new RegExp(`^repos/${NAME}/${NAME}/compare/${FREE}\\.\\.\\.${FR
|
|
|
53400
53446
|
var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
53401
53447
|
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
53402
53448
|
var ACTIONS_RUNS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs$`);
|
|
53449
|
+
var RUNS_QUERY_VALUE = {
|
|
53450
|
+
per_page: /^(?:[1-9]\d?|100)$/,
|
|
53451
|
+
page: /^[1-9]\d{0,5}$/,
|
|
53452
|
+
status: /^[A-Za-z_]+$/,
|
|
53453
|
+
branch: /^[A-Za-z0-9_.\/-]+$/,
|
|
53454
|
+
event: /^[A-Za-z_]+$/,
|
|
53455
|
+
head_sha: /^[0-9a-fA-F]{40}$/,
|
|
53456
|
+
created: /^[0-9:TZ.<>=-]+$/,
|
|
53457
|
+
actor: /^[A-Za-z0-9_.\[\]-]+$/
|
|
53458
|
+
};
|
|
53459
|
+
function validRunsQuery(query) {
|
|
53460
|
+
const seen = /* @__PURE__ */ new Set();
|
|
53461
|
+
for (const pair of query.split("&")) {
|
|
53462
|
+
const eq = pair.indexOf("=");
|
|
53463
|
+
if (eq < 1) return false;
|
|
53464
|
+
const key = pair.slice(0, eq);
|
|
53465
|
+
const value = pair.slice(eq + 1);
|
|
53466
|
+
const re = Object.prototype.hasOwnProperty.call(RUNS_QUERY_VALUE, key) ? RUNS_QUERY_VALUE[key] : void 0;
|
|
53467
|
+
if (!re || seen.has(key) || !re.test(value)) return false;
|
|
53468
|
+
seen.add(key);
|
|
53469
|
+
}
|
|
53470
|
+
return true;
|
|
53471
|
+
}
|
|
53472
|
+
var JSON_MAX_BUFFER = 64 * 1024 * 1024;
|
|
53403
53473
|
var ACTIONS_RUN_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+$`);
|
|
53404
53474
|
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+(?:/attempts/\\d+)?/jobs$`);
|
|
53405
53475
|
var ACTIONS_ARTIFACTS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/artifacts$`);
|
|
53406
53476
|
var ACTIONS_ARTIFACT_ZIP_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/artifacts/\\d+/zip$`);
|
|
53407
53477
|
var ACTIONS_JOB_LOGS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/jobs/\\d+/logs$`);
|
|
53408
|
-
var
|
|
53478
|
+
var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
|
|
53479
|
+
var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
|
|
53480
|
+
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
53481
|
+
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
53409
53482
|
function parseApiGetPath(rawPath) {
|
|
53410
53483
|
const path2 = rawPath.trim();
|
|
53411
53484
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
53412
53485
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
53486
|
+
const q = path2.indexOf("?");
|
|
53487
|
+
const base = q === -1 ? path2 : path2.slice(0, q);
|
|
53488
|
+
const query = q === -1 ? "" : path2.slice(q + 1);
|
|
53413
53489
|
if (PULL_FILES_RE.test(path2)) return { kind: "pull-files", path: path2, paginate: true };
|
|
53414
53490
|
if (COMPARE_RE.test(path2)) return { kind: "compare", path: path2, paginate: false };
|
|
53415
|
-
if (ACTIONS_RUNS_RE.test(
|
|
53491
|
+
if (ACTIONS_RUNS_RE.test(base) && (q === -1 || validRunsQuery(query))) return { kind: "actions-runs", path: path2, paginate: false };
|
|
53416
53492
|
if (ACTIONS_RUN_RE.test(path2)) return { kind: "actions-run", path: path2, paginate: false };
|
|
53417
53493
|
if (ACTIONS_JOBS_RE.test(path2)) return { kind: "actions-jobs", path: path2, paginate: true };
|
|
53418
53494
|
if (ACTIONS_ARTIFACTS_RE.test(path2)) return { kind: "actions-artifacts", path: path2, paginate: true };
|
|
53419
53495
|
if (ACTIONS_ARTIFACT_ZIP_RE.test(path2)) return { kind: "actions-artifact-zip", path: path2, paginate: false };
|
|
53420
53496
|
if (ACTIONS_JOB_LOGS_RE.test(path2)) return { kind: "actions-job-logs", path: path2, paginate: false };
|
|
53421
|
-
|
|
53422
|
-
const
|
|
53423
|
-
|
|
53497
|
+
if (RELEASE_LATEST_RE.test(path2)) return { kind: "release-latest", path: path2, paginate: false };
|
|
53498
|
+
const tag = RELEASE_TAG_RE.exec(path2)?.[1];
|
|
53499
|
+
if (tag && !tag.includes("..")) return { kind: "release-tag", path: path2, paginate: false };
|
|
53500
|
+
if (RELEASE_ASSET_RE.test(path2)) return { kind: "release-asset", path: path2, paginate: false };
|
|
53424
53501
|
if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
|
|
53425
53502
|
return { kind: "contents", path: path2, paginate: false };
|
|
53426
53503
|
}
|
|
@@ -53432,14 +53509,15 @@ function buildApiGetArgs(path2, accept) {
|
|
|
53432
53509
|
const req = parseApiGetPath(path2);
|
|
53433
53510
|
const args = ["api", req.path];
|
|
53434
53511
|
if (req.paginate) args.push("--paginate");
|
|
53435
|
-
const
|
|
53512
|
+
const requestedHeader = acceptHeaderFor(accept);
|
|
53513
|
+
const header = req.kind === "release-asset" ? "application/octet-stream" : requestedHeader;
|
|
53436
53514
|
if (header !== API_ACCEPT_HEADERS.json) args.push("-H", `Accept: ${header}`);
|
|
53437
53515
|
return args;
|
|
53438
53516
|
}
|
|
53439
53517
|
async function runApiGet(run, path2, accept) {
|
|
53440
53518
|
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
53441
53519
|
}
|
|
53442
|
-
var apiReadRunner = async (args, timeoutMs, env, binary) => (await execFileP("gh", args, { timeout: timeoutMs, ...binary ? { encoding: "latin1", maxBuffer: ZIP_MAX_BUFFER } : {}, ...env ? { env } : {} })).stdout;
|
|
53520
|
+
var apiReadRunner = async (args, timeoutMs, env, binary) => (await execFileP("gh", args, { timeout: timeoutMs, ...binary ? { encoding: "latin1", maxBuffer: ZIP_MAX_BUFFER } : { maxBuffer: JSON_MAX_BUFFER }, ...env ? { env } : {} })).stdout;
|
|
53443
53521
|
async function readAppApi(path2, runner = apiReadRunner) {
|
|
53444
53522
|
const req = parseApiGetPath(path2);
|
|
53445
53523
|
requireBrokerApp("read run log");
|
|
@@ -53449,16 +53527,16 @@ async function readAppApi(path2, runner = apiReadRunner) {
|
|
|
53449
53527
|
}
|
|
53450
53528
|
function registerApiReadCommands(program3, overrides) {
|
|
53451
53529
|
const spawn6 = overrides?.runner ?? apiReadRunner;
|
|
53452
|
-
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts)");
|
|
53530
|
+
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts, releases)");
|
|
53453
53531
|
api.command("get").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)", (v) => {
|
|
53454
53532
|
acceptHeaderFor(v);
|
|
53455
53533
|
return v.trim().toLowerCase();
|
|
53456
|
-
}, "json").option("--out <file>", "write
|
|
53457
|
-
const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs";
|
|
53458
|
-
if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs`);
|
|
53459
|
-
const
|
|
53460
|
-
if (
|
|
53461
|
-
const env =
|
|
53534
|
+
}, "json").option("--out <file>", "write response bytes to a file (required for, and only valid with, actions artifact zips, job logs, and release assets)").argument("<path>", "allowlisted GET path (shapes named on refusal)", (v) => parseApiGetPath(v)).action(async (req, o) => {
|
|
53535
|
+
const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs" || req.kind === "release-asset";
|
|
53536
|
+
if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs and repos/{owner}/{repo}/releases/assets/{id}`);
|
|
53537
|
+
const appRoute = req.kind.startsWith("actions-") || req.kind.startsWith("release-");
|
|
53538
|
+
if (appRoute) requireBrokerApp("api get");
|
|
53539
|
+
const env = appRoute || process.env.MMI_ACTOR?.trim() !== "personal" ? brokerChildEnv() : void 0;
|
|
53462
53540
|
let body;
|
|
53463
53541
|
try {
|
|
53464
53542
|
body = await runApiGet(
|
|
@@ -57166,7 +57244,7 @@ ${filelessTransitionGuide(target, o.stage)}`
|
|
|
57166
57244
|
return reportWrite("org project set-deploy", res);
|
|
57167
57245
|
});
|
|
57168
57246
|
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=...`)");
|
|
57169
|
-
projectTenantTasks.command("declare <name>").description(
|
|
57247
|
+
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?,output?:"file"}; 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). An argv token that starts with "-" (python -m mod) is read as an option and the whole call fails to parse: declare such a command with --var '{"service":"<svc>","command":["python","-m","mod"],"stages":["dev"]}' instead (#7668)`).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..10800 (1..3600 with a secretOutput)").option("--output <file>", "return {output} as a private 1-day Actions artifact (cannot combine with secretOutput)").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?,output?:"file"} \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) => {
|
|
57170
57248
|
const cfg = await loadConfig();
|
|
57171
57249
|
let target;
|
|
57172
57250
|
try {
|
|
@@ -57191,7 +57269,8 @@ projectTenantTasks.command("declare <name>").description("register a NEW tenant
|
|
|
57191
57269
|
command: o.command,
|
|
57192
57270
|
stages: o.stages.split(",").map((s) => s.trim()).filter(Boolean),
|
|
57193
57271
|
...o.timeoutSeconds !== void 0 ? { timeoutSeconds: Number(o.timeoutSeconds) } : {},
|
|
57194
|
-
...o.artifact !== void 0 ? { artifact: o.artifact } : {}
|
|
57272
|
+
...o.artifact !== void 0 ? { artifact: o.artifact } : {},
|
|
57273
|
+
...o.output !== void 0 ? { output: o.output } : {}
|
|
57195
57274
|
};
|
|
57196
57275
|
}
|
|
57197
57276
|
if (task && typeof task === "object" && task.secretOutput !== void 0) {
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
|
@@ -8,4 +8,4 @@ Before the first CI/deploy, an org admin must grant this repo **Read** on `@mutm
|
|
|
8
8
|
|
|
9
9
|
The server exposes `/health` and `/module.js`. Register the module's bundle URL, id, name and capabilities with MM-Work following the GUIDE. The seed deliberately exposes no authenticated product API yet.
|
|
10
10
|
|
|
11
|
-
`gate` installs and checks the backend and module.
|
|
11
|
+
`gate` installs and checks the backend and module. Product repos carry no screenshot or browser tests (owner ruling 2026-10-15, MM-Work#5388).
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "result": { "items": [{ "id": "sample", "name": "Sample brief" }] } }
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"module": {
|
|
3
|
-
"id": "{{REPO_SLUG}}",
|
|
4
|
-
"name": "{{REPO_NAME}}",
|
|
5
|
-
"glyph": "grid",
|
|
6
|
-
"tone": "blue",
|
|
7
|
-
"description": "Offline module screen check",
|
|
8
|
-
"sections": [
|
|
9
|
-
{
|
|
10
|
-
"label": "Home",
|
|
11
|
-
"path": "/"
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
"label": "Details",
|
|
15
|
-
"path": "/details"
|
|
16
|
-
}
|
|
17
|
-
],
|
|
18
|
-
"help": {
|
|
19
|
-
"home": {
|
|
20
|
-
"name": "Example home",
|
|
21
|
-
"howTos": [
|
|
22
|
-
{
|
|
23
|
-
"id": "recent",
|
|
24
|
-
"title": "See recent files",
|
|
25
|
-
"intro": "The module counts the files you opened lately in Google Drive.",
|
|
26
|
-
"steps": [
|
|
27
|
-
{
|
|
28
|
-
"title": "Connect Drive",
|
|
29
|
-
"text": "Connect your Google account in Files to see your recent files.",
|
|
30
|
-
"scene": "connect"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"title": "Come back here",
|
|
34
|
-
"text": "The overview shows how many recent files MM-Work found."
|
|
35
|
-
}
|
|
36
|
-
]
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
"id": "details",
|
|
40
|
-
"title": "Open the details page",
|
|
41
|
-
"steps": [
|
|
42
|
-
{
|
|
43
|
-
"title": "Press Open a page inside the module",
|
|
44
|
-
"text": "It is under the overview. Details is also in the section row.",
|
|
45
|
-
"scene": "screen"
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
"title": "Go back",
|
|
49
|
-
"text": "Press Home in the section row."
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
],
|
|
54
|
-
"faq": [
|
|
55
|
-
{
|
|
56
|
-
"id": "drive",
|
|
57
|
-
"question": "Where are my files?",
|
|
58
|
-
"answer": "Recent files come from your connected Google Drive."
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"id": "job",
|
|
62
|
-
"question": "What is the Review files job?",
|
|
63
|
-
"answer": "A job your admin can give you. The overview says whether you hold it."
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "access",
|
|
67
|
-
"question": "Does this module get my Google password?",
|
|
68
|
-
"answer": "No. It asks MM-Work for your files, and MM-Work uses the access you granted it."
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"tour": [
|
|
72
|
-
{
|
|
73
|
-
"anchor": "example-intro",
|
|
74
|
-
"title": "What this is",
|
|
75
|
-
"text": "A module's own screen, drawn with MM-Work's design system."
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"anchor": "example-overview",
|
|
79
|
-
"title": "Your overview",
|
|
80
|
-
"text": "See your workspace and recent Drive files here."
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
"anchor": "example-actions",
|
|
84
|
-
"title": "Where to go next",
|
|
85
|
-
"text": "Open the details page, or go to Files."
|
|
86
|
-
}
|
|
87
|
-
]
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
"privacy": [
|
|
91
|
-
{
|
|
92
|
-
"class": "company-visible",
|
|
93
|
-
"what": {
|
|
94
|
-
"en": "The example reads recent Google Drive file metadata through MM-Work after the person connects Files.",
|
|
95
|
-
"tr": "Örnek modül, kişi Files bağlantısını kurduktan sonra MM-Work üzerinden son Google Drive dosyalarının meta verilerini okur."
|
|
96
|
-
},
|
|
97
|
-
"who": {
|
|
98
|
-
"en": "The signed-in person sees the count on this example screen; no file content is saved by the example.",
|
|
99
|
-
"tr": "Oturum açmış kişi örnek ekranda sayıyı görür; örnek modül dosya içeriğini kaydetmez."
|
|
100
|
-
},
|
|
101
|
-
"evidence": [
|
|
102
|
-
"repo:MM-Work:examples/example-module/src/module.jsx"
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
]
|
|
106
|
-
},
|
|
107
|
-
"screens": [
|
|
108
|
-
{
|
|
109
|
-
"name": "home",
|
|
110
|
-
"path": "/m/example",
|
|
111
|
-
"jobs": [
|
|
112
|
-
"files.review"
|
|
113
|
-
],
|
|
114
|
-
"routes": {
|
|
115
|
-
"POST /api/actions/files.list": "files.json"
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
"name": "open details journey",
|
|
120
|
-
"path": "/m/example",
|
|
121
|
-
"steps": [
|
|
122
|
-
{
|
|
123
|
-
"assert": {
|
|
124
|
-
"selector": "[data-tour=example-actions] button",
|
|
125
|
-
"count": 2
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
"click": "[data-tour=example-actions] button:first-child"
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
"assert": {
|
|
133
|
-
"url": "/m/example/details"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
"routes": {
|
|
138
|
-
"POST /api/actions/files.list": "files.json"
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
"name": "details",
|
|
143
|
-
"path": "/m/example/details",
|
|
144
|
-
"routes": {
|
|
145
|
-
"POST /api/actions/files.list": "files.json"
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
"name": "loading",
|
|
150
|
-
"path": "/m/example",
|
|
151
|
-
"query": "state=loading",
|
|
152
|
-
"routes": {
|
|
153
|
-
"POST /api/actions/files.list": "pending"
|
|
154
|
-
}
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
"name": "error",
|
|
158
|
-
"path": "/m/example",
|
|
159
|
-
"query": "state=error",
|
|
160
|
-
"routes": {
|
|
161
|
-
"POST /api/actions/files.list": {
|
|
162
|
-
"status": 500,
|
|
163
|
-
"body": {
|
|
164
|
-
"error": "offline failure"
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
]
|
|
170
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
name: screens
|
|
2
|
-
# A required check (#250): it runs wherever gate runs, so no PR into development, rc or main and no train tag
|
|
3
|
-
# waits on a context that never reports.
|
|
4
|
-
on:
|
|
5
|
-
pull_request:
|
|
6
|
-
merge_group:
|
|
7
|
-
types: [checks_requested]
|
|
8
|
-
push:
|
|
9
|
-
branches: {{GATE_PUSH_BRANCHES_YAML}}
|
|
10
|
-
tags: ['v*']
|
|
11
|
-
# Cancel superseded runs per PR, or per branch for pushes (MMI-Hub#7515, #3001; as MM-Strategy#415).
|
|
12
|
-
concurrency:
|
|
13
|
-
group: screens-${{ github.event.pull_request.number || (github.event_name == 'push' && github.sha) || github.ref }}
|
|
14
|
-
cancel-in-progress: true
|
|
15
|
-
permissions:
|
|
16
|
-
contents: read
|
|
17
|
-
packages: read
|
|
18
|
-
pull-requests: read
|
|
19
|
-
jobs:
|
|
20
|
-
# Owner ruling (#474, 6 Oct 2026): browser checks block nothing, releases included. Every PR, push and tag
|
|
21
|
-
# reports this required context without a browser audit (run: false); screens-sweep.yml audits nightly and
|
|
22
|
-
# on demand (issue-on-red). A development push still reports
|
|
23
|
-
# the required "screens / screens" on the SHA, which the dev deploy needs (MMI-Hub dev-auto-deploy).
|
|
24
|
-
changes:
|
|
25
|
-
runs-on: [self-hosted, linux, x64, mmi-live]
|
|
26
|
-
outputs:
|
|
27
|
-
run: ${{ steps.pick.outputs.run }}
|
|
28
|
-
full: ${{ steps.pick.outputs.full }}
|
|
29
|
-
steps:
|
|
30
|
-
- id: pick
|
|
31
|
-
env:
|
|
32
|
-
GH_TOKEN: ${{ github.token }}
|
|
33
|
-
EVENT: ${{ github.event_name }}
|
|
34
|
-
BASE: ${{ github.base_ref }}
|
|
35
|
-
REF: ${{ github.ref_name }}
|
|
36
|
-
REF_TYPE: ${{ github.ref_type }}
|
|
37
|
-
PR: ${{ github.event.pull_request.number }}
|
|
38
|
-
REPO: ${{ github.repository }}
|
|
39
|
-
run: |
|
|
40
|
-
set -euo pipefail
|
|
41
|
-
# #474: no browser audit on any PR, push or tag; screens-sweep.yml runs it nightly.
|
|
42
|
-
run=false; full=false
|
|
43
|
-
echo "run=$run full=$full"
|
|
44
|
-
{ echo "run=$run"; echo "full=$full"; } >> "$GITHUB_OUTPUT"
|
|
45
|
-
screens:
|
|
46
|
-
needs: changes
|
|
47
|
-
# Fail closed (MMI-Hub, 6 Oct): a SKIPPED required check counts as passing, so this job runs even when
|
|
48
|
-
# `changes` failed, and a missing answer means the full audit, never a skip.
|
|
49
|
-
if: ${{ !cancelled() }}
|
|
50
|
-
uses: mutmutco/MM-Work/.github/workflows/module-screens.yml@development
|
|
51
|
-
with:
|
|
52
|
-
run: ${{ needs.changes.outputs.run != 'false' }}
|
|
53
|
-
audit-sizes: ${{ needs.changes.outputs.full != 'false' && '["phone","768","1024","1280"]' || '["phone"]' }}
|
|
54
|
-
# Pinned to the shell built from the kit this module ships (ds-v0.71.42): the runner MM-Work
|
|
55
|
-
# published with that kit; bump with the kit.
|
|
56
|
-
# 0.1.55+ checks MM-Work #1575: selectedStyle, pageBackground, noJump (#418).
|
|
57
|
-
screens-version: '0.1.113'
|
|
58
|
-
working-directory: 'module'
|
|
59
|
-
build-command: 'npm run build'
|
|
60
|
-
bundle-path: 'dist/module.js'
|
|
61
|
-
fixtures-path: 'screens/screens.json'
|
|
62
|
-
# Every screen in English and Turkish (#369): Turkish words run longer, so they get their own audit.
|
|
63
|
-
languages: 'en,tr'
|