@async/pipeline 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_SURFACE.md +3 -1
- package/api-contract.json +19 -0
- package/dist/internal/core/index.d.ts +33 -0
- package/dist/internal/core/index.d.ts.map +1 -1
- package/dist/internal/core/index.js +122 -4
- package/dist/internal/core/index.js.map +1 -1
- package/dist/internal/node/github.d.ts +11 -1
- package/dist/internal/node/github.d.ts.map +1 -1
- package/dist/internal/node/github.js +200 -17
- package/dist/internal/node/github.js.map +1 -1
- package/dist/internal/node/sources.d.ts +25 -1
- package/dist/internal/node/sources.d.ts.map +1 -1
- package/dist/internal/node/sources.js +72 -2
- package/dist/internal/node/sources.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,9 +3,10 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, relative, resolve } from "node:path";
|
|
5
5
|
import { githubConfigForJob, pipelineError } from "../core/index.js";
|
|
6
|
+
import { sourceImpactPlanForJob } from "./sources.js";
|
|
6
7
|
export const GITHUB_WORKFLOW_PATH = ".github/workflows/async-pipeline.yml";
|
|
7
8
|
export const GITHUB_LOCK_PATH = ".github/async-pipeline.lock.json";
|
|
8
|
-
const GENERATOR_VERSION =
|
|
9
|
+
const GENERATOR_VERSION = 16;
|
|
9
10
|
const DEFAULT_NODE_VERSION = "24";
|
|
10
11
|
const DEFAULT_DENO_VERSION = "2";
|
|
11
12
|
const DEFAULT_PNPM_VERSION = "11.1.0";
|
|
@@ -19,8 +20,8 @@ function defineActionRef(id, uses, sha, label) {
|
|
|
19
20
|
ref: `${uses}@${sha} # ${label}`
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
|
-
const ASYNC_ACTIONS_SHA = "
|
|
23
|
-
const ASYNC_ACTIONS_LABEL = "v0.1.
|
|
23
|
+
const ASYNC_ACTIONS_SHA = "cef0f1a3b7dd1300a16004e6d69b472261a3272f";
|
|
24
|
+
const ASYNC_ACTIONS_LABEL = "v0.1.7";
|
|
24
25
|
const GENERATED_ACTIONS = [
|
|
25
26
|
defineActionRef("async.actions.setup", "async/actions/setup", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
26
27
|
defineActionRef("async.actions.run", "async/actions/run", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
@@ -28,6 +29,8 @@ const GENERATED_ACTIONS = [
|
|
|
28
29
|
defineActionRef("async.actions.preview", "async/actions/preview", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
29
30
|
defineActionRef("async.actions.publish", "async/actions/publish", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
30
31
|
defineActionRef("async.actions.dependabot-merge", "async/actions/dependabot-merge", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
32
|
+
defineActionRef("async.actions.evidence", "async/actions/evidence", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
33
|
+
defineActionRef("async.actions.source-impact", "async/actions/source-impact", ASYNC_ACTIONS_SHA, ASYNC_ACTIONS_LABEL),
|
|
31
34
|
defineActionRef("actions.checkout", "actions/checkout", "de0fac2e4500dabe0009e67214ff5f5447ce83dd", "v6.0.2"),
|
|
32
35
|
defineActionRef("actions.cache", "actions/cache", "0057852bfaa89a56745cba8c7296529d2fc39830", "v4"),
|
|
33
36
|
defineActionRef("pnpm.setup", "pnpm/setup", "cf03a9b516e09bc5a90f041fc26fc930c9dc631b", "v1.0.0"),
|
|
@@ -44,6 +47,8 @@ const ASYNC_PAGES_ACTION = actionRef("async.actions.pages");
|
|
|
44
47
|
const ASYNC_PREVIEW_ACTION = actionRef("async.actions.preview");
|
|
45
48
|
const ASYNC_PUBLISH_ACTION = actionRef("async.actions.publish");
|
|
46
49
|
const ASYNC_DEPENDABOT_MERGE_ACTION = actionRef("async.actions.dependabot-merge");
|
|
50
|
+
const ASYNC_EVIDENCE_ACTION = actionRef("async.actions.evidence");
|
|
51
|
+
const ASYNC_SOURCE_IMPACT_ACTION = actionRef("async.actions.source-impact");
|
|
47
52
|
const CHECKOUT_ACTION = actionRef("actions.checkout");
|
|
48
53
|
const CACHE_ACTION = actionRef("actions.cache");
|
|
49
54
|
const PNPM_SETUP_ACTION = actionRef("pnpm.setup");
|
|
@@ -63,6 +68,7 @@ export async function renderGitHubWorkflow(pipeline, options) {
|
|
|
63
68
|
const packageInfo = await readPackageInfo(options.cwd);
|
|
64
69
|
const renderModel = buildRenderModel(pipeline, {
|
|
65
70
|
...packageInfo,
|
|
71
|
+
cwd: options.cwd,
|
|
66
72
|
configPath: relativePath(options.cwd, options.configPath),
|
|
67
73
|
workflowPath
|
|
68
74
|
});
|
|
@@ -85,6 +91,8 @@ export async function renderGitHubWorkflow(pipeline, options) {
|
|
|
85
91
|
dependencyCachePath: renderModel.dependencyCachePath,
|
|
86
92
|
dependabotAutoMerge: renderModel.dependabotAutoMerge,
|
|
87
93
|
packagePreviews: renderModel.packagePreviews,
|
|
94
|
+
evidence: renderModel.evidence,
|
|
95
|
+
sourceImpact: renderModel.sourceImpact,
|
|
88
96
|
bridge: renderModel.bridge,
|
|
89
97
|
pages: renderModel.pages,
|
|
90
98
|
manualDispatchJobs: renderModel.manualDispatchJobs,
|
|
@@ -112,6 +120,8 @@ export async function renderGitHubWorkflow(pipeline, options) {
|
|
|
112
120
|
dependencyCachePath: renderModel.dependencyCachePath,
|
|
113
121
|
dependabotAutoMerge: renderModel.dependabotAutoMerge,
|
|
114
122
|
packagePreviews: renderModel.packagePreviews,
|
|
123
|
+
evidence: renderModel.evidence,
|
|
124
|
+
sourceImpact: renderModel.sourceImpact,
|
|
115
125
|
bridge: renderModel.bridge,
|
|
116
126
|
pages: renderModel.pages,
|
|
117
127
|
manualDispatchJobs: renderModel.manualDispatchJobs
|
|
@@ -231,6 +241,7 @@ function buildRenderModel(pipeline, options) {
|
|
|
231
241
|
if (packagePreviews.enabled) {
|
|
232
242
|
addPullRequestTrigger(triggers, "pull_request");
|
|
233
243
|
}
|
|
244
|
+
const evidence = resolveGitHubEvidence(pipeline);
|
|
234
245
|
const pages = resolveGitHubPages(pipeline);
|
|
235
246
|
const bridge = resolveGitHubBridge(pipeline);
|
|
236
247
|
if (pages.enabled) {
|
|
@@ -259,25 +270,38 @@ function buildRenderModel(pipeline, options) {
|
|
|
259
270
|
const nodeVersion = pipeline.sync.github.nodeVersion ?? DEFAULT_NODE_VERSION;
|
|
260
271
|
const runtime = resolveRuntimeSpecs(pipeline.sync.github.runtime, options.projectKind, nodeVersion);
|
|
261
272
|
const setup = resolveGitHubSetup(pipeline.sync.github.setup, options.packageManager, options.packageManagerVersion);
|
|
273
|
+
const jobs = Object.values(pipeline.jobs)
|
|
274
|
+
.map((job) => ({
|
|
275
|
+
id: job.id,
|
|
276
|
+
target: [...job.target],
|
|
277
|
+
trigger: [...job.trigger],
|
|
278
|
+
env: { ...pipeline.env, ...(job.env ?? {}) },
|
|
279
|
+
environment: job.environment,
|
|
280
|
+
requires: job.requires,
|
|
281
|
+
execution: job.execution,
|
|
282
|
+
github: githubConfigForJob(pipeline, job),
|
|
283
|
+
if: renderGitHubJobCondition(job, pipeline.triggers)
|
|
284
|
+
}))
|
|
285
|
+
.sort((left, right) => left.id.localeCompare(right.id));
|
|
286
|
+
const sourceImpactJobs = resolveGitHubSourceImpactJobs(pipeline, options.cwd, jobs);
|
|
287
|
+
const sourceImpact = {
|
|
288
|
+
...pipeline.sync.github.sourceImpact,
|
|
289
|
+
generatedJobs: sourceImpactJobs.map((job) => ({
|
|
290
|
+
job: job.job,
|
|
291
|
+
planJob: job.planJob,
|
|
292
|
+
matrixJob: job.matrixJob,
|
|
293
|
+
matrixRows: job.plan.matrix.include.length,
|
|
294
|
+
sources: Object.keys(job.plan.sources).sort((left, right) => left.localeCompare(right))
|
|
295
|
+
}))
|
|
296
|
+
};
|
|
262
297
|
return {
|
|
263
298
|
name: "Async Pipeline",
|
|
264
299
|
configPath: options.configPath,
|
|
265
300
|
workflowPath: options.workflowPath,
|
|
266
301
|
projectKind: options.projectKind,
|
|
267
302
|
triggers,
|
|
268
|
-
jobs
|
|
269
|
-
|
|
270
|
-
id: job.id,
|
|
271
|
-
target: [...job.target],
|
|
272
|
-
trigger: [...job.trigger],
|
|
273
|
-
env: { ...pipeline.env, ...(job.env ?? {}) },
|
|
274
|
-
environment: job.environment,
|
|
275
|
-
requires: job.requires,
|
|
276
|
-
execution: job.execution,
|
|
277
|
-
github: githubConfigForJob(pipeline, job),
|
|
278
|
-
if: renderGitHubJobCondition(job, pipeline.triggers)
|
|
279
|
-
}))
|
|
280
|
-
.sort((left, right) => left.id.localeCompare(right.id)),
|
|
303
|
+
jobs,
|
|
304
|
+
sourceImpactJobs,
|
|
281
305
|
tasks: pipeline.tasks,
|
|
282
306
|
packageManager: options.packageManager,
|
|
283
307
|
packageManagerVersion: options.packageManagerVersion,
|
|
@@ -291,11 +315,83 @@ function buildRenderModel(pipeline, options) {
|
|
|
291
315
|
dependencyCachePath: pipeline.sync.github.dependencyCache === false ? undefined : options.dependencyCachePath,
|
|
292
316
|
dependabotAutoMerge: pipeline.sync.github.dependabotAutoMerge,
|
|
293
317
|
packagePreviews,
|
|
318
|
+
evidence,
|
|
319
|
+
sourceImpact,
|
|
294
320
|
bridge,
|
|
295
321
|
pages,
|
|
296
322
|
manualDispatchJobs
|
|
297
323
|
};
|
|
298
324
|
}
|
|
325
|
+
function resolveGitHubEvidence(pipeline) {
|
|
326
|
+
const config = pipeline.sync.github.evidence;
|
|
327
|
+
if (!config.enabled)
|
|
328
|
+
return config;
|
|
329
|
+
if (pipeline.jobs[config.job]) {
|
|
330
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_EVIDENCE_JOB_CONFLICT", `sync.github.evidence.job "${config.job}" conflicts with an existing pipeline job. Remove the explicit job or set sync.github.evidence.job to a different id.`);
|
|
331
|
+
}
|
|
332
|
+
const generatedJobs = new Set(["package-preview", "dependabot-auto-merge", pipeline.sync.github.pages.job, "async-bridge"]);
|
|
333
|
+
if (generatedJobs.has(config.job)) {
|
|
334
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_EVIDENCE_JOB_CONFLICT", `sync.github.evidence.job "${config.job}" conflicts with a generated GitHub job. Set sync.github.evidence.job to a different id.`);
|
|
335
|
+
}
|
|
336
|
+
return config;
|
|
337
|
+
}
|
|
338
|
+
function resolveGitHubSourceImpactJobs(pipeline, cwd, jobs) {
|
|
339
|
+
const config = pipeline.sync.github.sourceImpact;
|
|
340
|
+
if (!config.enabled)
|
|
341
|
+
return [];
|
|
342
|
+
const jobsById = new Map(jobs.map((job) => [job.id, job]));
|
|
343
|
+
const explicitJobs = new Set(config.jobs);
|
|
344
|
+
const selectedJobIds = config.jobs.length > 0 ? config.jobs : jobs.map((job) => job.id);
|
|
345
|
+
const generatedIds = new Set([
|
|
346
|
+
"package-preview",
|
|
347
|
+
"dependabot-auto-merge",
|
|
348
|
+
"async-bridge",
|
|
349
|
+
pipeline.sync.github.evidence.job,
|
|
350
|
+
pipeline.sync.github.pages.job
|
|
351
|
+
].map((id) => id.toLowerCase()));
|
|
352
|
+
const existingJobIds = new Set(Object.keys(pipeline.jobs).map((id) => id.toLowerCase()));
|
|
353
|
+
const result = [];
|
|
354
|
+
for (const jobId of selectedJobIds) {
|
|
355
|
+
const job = jobsById.get(jobId);
|
|
356
|
+
if (!job) {
|
|
357
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_SOURCE_IMPACT_INVALID", `sync.github.sourceImpact references missing job "${jobId}".`);
|
|
358
|
+
}
|
|
359
|
+
if (job.github?.runsOnMatrix) {
|
|
360
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_SOURCE_IMPACT_INVALID", `sync.github.sourceImpact cannot target job "${jobId}" because that job already uses github.runsOnMatrix.`);
|
|
361
|
+
}
|
|
362
|
+
const plan = sourceImpactPlanForJob(pipeline, cwd, jobId);
|
|
363
|
+
if (plan.matrix.include.length === 0) {
|
|
364
|
+
if (explicitJobs.has(jobId)) {
|
|
365
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_SOURCE_IMPACT_INVALID", `sync.github.sourceImpact job "${jobId}" has no source task refs.`);
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
const generatedJobPrefix = safeGeneratedJobId(jobId);
|
|
370
|
+
const planJob = `${generatedJobPrefix}-source-plan`;
|
|
371
|
+
const matrixJob = `${generatedJobPrefix}-sources`;
|
|
372
|
+
for (const generatedJob of [planJob, matrixJob]) {
|
|
373
|
+
const generatedJobKey = generatedJob.toLowerCase();
|
|
374
|
+
if (existingJobIds.has(generatedJobKey)) {
|
|
375
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_SOURCE_IMPACT_JOB_CONFLICT", `Generated source-impact job "${generatedJob}" conflicts with an existing pipeline job.`);
|
|
376
|
+
}
|
|
377
|
+
if (generatedIds.has(generatedJobKey)) {
|
|
378
|
+
throw pipelineError("ASYNC_PIPELINE_GITHUB_SOURCE_IMPACT_JOB_CONFLICT", `Generated source-impact job "${generatedJob}" conflicts with another generated GitHub job.`);
|
|
379
|
+
}
|
|
380
|
+
generatedIds.add(generatedJobKey);
|
|
381
|
+
}
|
|
382
|
+
result.push({
|
|
383
|
+
job: jobId,
|
|
384
|
+
planJob,
|
|
385
|
+
matrixJob,
|
|
386
|
+
planPath: `.async/actions/source-impact/${safeArtifactPart(jobId)}-source-plan.json`,
|
|
387
|
+
plan,
|
|
388
|
+
if: job.if,
|
|
389
|
+
github: job.github,
|
|
390
|
+
env: job.env
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return result.sort((left, right) => left.job.localeCompare(right.job));
|
|
394
|
+
}
|
|
299
395
|
function resolveGitHubBridge(pipeline) {
|
|
300
396
|
const config = pipeline.sync.github.bridge;
|
|
301
397
|
const actionsJobEnabled = gitHubBridgeActionsEnabled(config);
|
|
@@ -447,6 +543,10 @@ function renderWorkflow(model) {
|
|
|
447
543
|
renderPagesDeployJob(lines, job);
|
|
448
544
|
}
|
|
449
545
|
}
|
|
546
|
+
for (const sourceJob of model.sourceImpactJobs) {
|
|
547
|
+
renderSourceImpactPlanJob(lines, model, sourceJob);
|
|
548
|
+
renderSourceImpactMatrixJob(lines, model, sourceJob);
|
|
549
|
+
}
|
|
450
550
|
if (model.pages.enabled) {
|
|
451
551
|
renderGeneratedPagesJob(lines, model);
|
|
452
552
|
renderPagesDeployJob(lines, {
|
|
@@ -469,6 +569,9 @@ function renderWorkflow(model) {
|
|
|
469
569
|
if (model.bridge.actionsJob.enabled) {
|
|
470
570
|
renderBridgeJob(lines, model);
|
|
471
571
|
}
|
|
572
|
+
if (model.evidence.enabled) {
|
|
573
|
+
renderEvidenceFanInJob(lines, model);
|
|
574
|
+
}
|
|
472
575
|
return `${lines.join("\n").replace(/\n+$/u, "")}\n`;
|
|
473
576
|
}
|
|
474
577
|
function renderJob(lines, model, job) {
|
|
@@ -548,8 +651,48 @@ function renderJob(lines, model, job) {
|
|
|
548
651
|
lines.push("");
|
|
549
652
|
renderPagesBuildSteps(lines, job.github.pages);
|
|
550
653
|
}
|
|
654
|
+
renderEvidenceCollectStep(lines, model, { matrix: Boolean(runnerMatrix && runnerMatrix.length > 0) });
|
|
551
655
|
lines.push("");
|
|
552
656
|
}
|
|
657
|
+
function renderSourceImpactPlanJob(lines, model, sourceJob) {
|
|
658
|
+
const runsOn = sourceJob.github?.runsOn ?? "ubuntu-latest";
|
|
659
|
+
lines.push(` ${yamlKey(sourceJob.planJob)}:`, ` name: ${sourceJob.planJob}`);
|
|
660
|
+
if (sourceJob.if) {
|
|
661
|
+
lines.push(` if: ${sourceJob.if}`);
|
|
662
|
+
}
|
|
663
|
+
lines.push(` runs-on: ${Array.isArray(runsOn) ? JSON.stringify(runsOn) : runsOn}`, " permissions:", " contents: read", " outputs:", " matrix: ${{ steps.source-plan.outputs.matrix }}", " steps:", " - name: Checkout", ` uses: ${CHECKOUT_ACTION}`, "", ...renderSetupSteps(model));
|
|
664
|
+
renderWriteSourceImpactPlanStep(lines, sourceJob);
|
|
665
|
+
lines.push("", " - name: Plan source impact matrix", " id: source-plan", ` uses: ${ASYNC_SOURCE_IMPACT_ACTION}`, " with:", " mode: plan", ` source-plan: ${sourceJob.planPath}`, " output-matrix: true");
|
|
666
|
+
renderEvidenceCollectStep(lines, model);
|
|
667
|
+
lines.push("");
|
|
668
|
+
}
|
|
669
|
+
function renderSourceImpactMatrixJob(lines, model, sourceJob) {
|
|
670
|
+
const runsOn = sourceJob.github?.runsOn ?? "ubuntu-latest";
|
|
671
|
+
lines.push(` ${yamlKey(sourceJob.matrixJob)}:`, ` name: ${sourceJob.job} source (\${{ matrix.source }}:\${{ matrix.taskId }})`, ` needs: ${JSON.stringify(sourceJob.planJob)}`, ` if: \${{ always() && needs['${sourceJob.planJob}'].result == 'success' }}`, " strategy:", " fail-fast: false", ` matrix: \${{ fromJSON(needs['${sourceJob.planJob}'].outputs.matrix || '{"include":[]}') }}`, ` runs-on: ${Array.isArray(runsOn) ? JSON.stringify(runsOn) : runsOn}`, " permissions:", " contents: read", " steps:", " - name: Checkout", ` uses: ${CHECKOUT_ACTION}`, "", ...(model.taskCache
|
|
672
|
+
? [
|
|
673
|
+
" - name: Restore task cache",
|
|
674
|
+
` uses: ${CACHE_ACTION}`,
|
|
675
|
+
" with:",
|
|
676
|
+
" path: .async/cache",
|
|
677
|
+
" key: async-pipeline-${{ runner.os }}-${{ github.sha }}",
|
|
678
|
+
" restore-keys: |",
|
|
679
|
+
" async-pipeline-${{ runner.os }}-",
|
|
680
|
+
""
|
|
681
|
+
]
|
|
682
|
+
: []), ...renderSetupSteps(model), ...renderDependencyInstallSteps(model));
|
|
683
|
+
if (model.buildCommand) {
|
|
684
|
+
lines.push("", " - name: Build pipeline CLI", ` run: ${model.buildCommand}`);
|
|
685
|
+
}
|
|
686
|
+
renderWriteSourceImpactPlanStep(lines, sourceJob);
|
|
687
|
+
lines.push("", " - name: Validate source checkout", ` uses: ${ASYNC_SOURCE_IMPACT_ACTION}`, " with:", " mode: checkout", ` source-plan: ${sourceJob.planPath}`, " source-id: ${{ matrix.source }}", " ref: ${{ matrix.ref }}", " path: ${{ matrix.path }}", "", " - name: Prepare source checkout", ` uses: ${ASYNC_SOURCE_IMPACT_ACTION}`, " with:", " mode: prepare", ` source-plan: ${sourceJob.planPath}`, " source-id: ${{ matrix.source }}", " path: ${{ matrix.path }}");
|
|
688
|
+
renderRunActionStep(lines, "Run source task", `${model.command} github check && ${model.command} run-task "\${{ matrix.task }}"`, scopeActionEnv(sourceJob.env, new Set()), { artifactName: "async-pipeline-${{ github.job }}-${{ matrix.source }}-${{ matrix.taskId }}-runs" });
|
|
689
|
+
renderEvidenceCollectStep(lines, model, { matrix: true });
|
|
690
|
+
lines.push("");
|
|
691
|
+
}
|
|
692
|
+
function renderWriteSourceImpactPlanStep(lines, sourceJob) {
|
|
693
|
+
const planJson = JSON.stringify(sourceJob.plan, null, 2);
|
|
694
|
+
lines.push("", " - name: Write generated source plan", " run: |", ` mkdir -p ${shellWord(dirname(sourceJob.planPath))}`, ` cat > ${shellWord(sourceJob.planPath)} <<'ASYNC_SOURCE_PLAN'`, ...planJson.split("\n").map((line) => ` ${line}`), " ASYNC_SOURCE_PLAN");
|
|
695
|
+
}
|
|
553
696
|
function renderGeneratedPagesJob(lines, model) {
|
|
554
697
|
const pages = model.pages;
|
|
555
698
|
if (!pages.target)
|
|
@@ -572,6 +715,7 @@ function renderGeneratedPagesJob(lines, model) {
|
|
|
572
715
|
renderRunActionStep(lines, "Run Pages target", `${model.command} github check && ${model.command} run-task ${shellWord(pages.target)}`, {});
|
|
573
716
|
lines.push("");
|
|
574
717
|
renderPagesBuildSteps(lines, pages);
|
|
718
|
+
renderEvidenceCollectStep(lines, model);
|
|
575
719
|
lines.push("");
|
|
576
720
|
}
|
|
577
721
|
function renderGeneratedPagesCondition(pages) {
|
|
@@ -605,6 +749,17 @@ function renderRunActionStep(lines, name, command, env, options = {}) {
|
|
|
605
749
|
lines.push("", ` - name: ${name}`, ` uses: ${ASYNC_RUN_ACTION}`, " with:", ` command: ${JSON.stringify(command)}`, " check-generated: false", ` artifact-name: ${options.artifactName ?? "async-pipeline-${{ github.job }}-runs"}`);
|
|
606
750
|
renderActionEnv(lines, env);
|
|
607
751
|
}
|
|
752
|
+
function renderEvidenceCollectStep(lines, model, options = {}) {
|
|
753
|
+
if (!model.evidence.enabled)
|
|
754
|
+
return;
|
|
755
|
+
const suffix = options.matrix ? "${{ github.job }}-${{ strategy.job-index }}" : "${{ github.job }}";
|
|
756
|
+
lines.push("", " - name: Collect evidence manifest", " if: ${{ always() }}", ` uses: ${ASYNC_EVIDENCE_ACTION}`, " with:", " mode: collect", " paths: |", ...model.evidence.paths.map((path) => ` ${path}`), ...(model.evidence.receiptPaths.length > 0
|
|
757
|
+
? [
|
|
758
|
+
" receipt-paths: |",
|
|
759
|
+
...model.evidence.receiptPaths.map((path) => ` ${path}`)
|
|
760
|
+
]
|
|
761
|
+
: []), ` manifest-path: ".async/evidence/${suffix}/manifest.json"`, ` summary-path: ".async/evidence/${suffix}/summary.md"`, ` artifact-name: ${model.evidence.artifactNamePrefix}-${suffix}`, ` retention-days: ${model.evidence.retentionDays}`, ` if-no-files-found: ${model.evidence.ifNoFilesFound}`, ` include-summary: ${model.evidence.includeSummary ? "true" : "false"}`);
|
|
762
|
+
}
|
|
608
763
|
function resolveLifecycleJobPlan(model, job) {
|
|
609
764
|
if (job.execution || job.target.length !== 1)
|
|
610
765
|
return undefined;
|
|
@@ -852,6 +1007,10 @@ function splitShellWords(command) {
|
|
|
852
1007
|
function safeArtifactPart(value) {
|
|
853
1008
|
return value.replace(/[^A-Za-z0-9_.-]+/gu, "-");
|
|
854
1009
|
}
|
|
1010
|
+
function safeGeneratedJobId(value) {
|
|
1011
|
+
const normalized = value.toLowerCase().replace(/[^a-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "");
|
|
1012
|
+
return normalized || "job";
|
|
1013
|
+
}
|
|
855
1014
|
function renderPackagePreviewJob(lines, model) {
|
|
856
1015
|
const preview = model.packagePreviews;
|
|
857
1016
|
if (!preview.package || !preview.target)
|
|
@@ -872,7 +1031,8 @@ function renderPackagePreviewJob(lines, model) {
|
|
|
872
1031
|
lines.push("", " - name: Build pipeline CLI", ` run: ${model.buildCommand}`);
|
|
873
1032
|
}
|
|
874
1033
|
renderRunActionStep(lines, "Run package preview target", `${model.command} github check && ${model.command} run-task ${shellWord(preview.target)}`, {});
|
|
875
|
-
lines.push("", " - name: Publish package preview", ` uses: ${ASYNC_PREVIEW_ACTION}`, " with:", ` package-path: ${JSON.stringify(preview.package)}`, ` target-registry: ${JSON.stringify(preview.registry)}`, ...(preview.namespace ? [` namespace: ${JSON.stringify(preview.namespace)}`] : []), " mode: pr", ` comment: ${preview.comment ? "true" : "false"}`, ` token-env-name: ${JSON.stringify(preview.tokenEnv)}`, " env:", " CI: true", ` ${preview.tokenEnv}: \${{ secrets.${preview.tokenEnv} }}
|
|
1034
|
+
lines.push("", " - name: Publish package preview", ` uses: ${ASYNC_PREVIEW_ACTION}`, " with:", ` package-path: ${JSON.stringify(preview.package)}`, ` target-registry: ${JSON.stringify(preview.registry)}`, ...(preview.namespace ? [` namespace: ${JSON.stringify(preview.namespace)}`] : []), " mode: pr", ` comment: ${preview.comment ? "true" : "false"}`, ` token-env-name: ${JSON.stringify(preview.tokenEnv)}`, " env:", " CI: true", ` ${preview.tokenEnv}: \${{ secrets.${preview.tokenEnv} }}`);
|
|
1035
|
+
renderEvidenceCollectStep(lines, model);
|
|
876
1036
|
lines.push("");
|
|
877
1037
|
}
|
|
878
1038
|
function renderBridgeJob(lines, model) {
|
|
@@ -894,6 +1054,7 @@ function renderBridgeJob(lines, model) {
|
|
|
894
1054
|
}
|
|
895
1055
|
renderRunActionStep(lines, "Check generated workflow", `${model.command} github check`, {});
|
|
896
1056
|
renderBridgePullStep(lines, bridge);
|
|
1057
|
+
renderEvidenceCollectStep(lines, model);
|
|
897
1058
|
lines.push("");
|
|
898
1059
|
}
|
|
899
1060
|
function renderBridgeCondition(bridge) {
|
|
@@ -921,6 +1082,28 @@ function renderBridgePullStep(lines, bridge) {
|
|
|
921
1082
|
].map(shellWord).join(" ");
|
|
922
1083
|
lines.push("", " - name: Pull and apply Async bridge change sets", ` uses: ${ASYNC_RUN_ACTION}`, " with:", ` command: ${JSON.stringify(command)}`, " check-generated: false", " artifact-name: async-bridge-${{ github.run_id }}", " env:", " CI: true", ` ASYNC_PROJECT_URL: \${{ vars.${bridge.endpointVar} }}`, ` ASYNC_PROJECT_TOKEN: \${{ secrets.${bridge.tokenEnv} }}`, " GITHUB_REPOSITORY: ${{ github.repository }}", " GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}");
|
|
923
1084
|
}
|
|
1085
|
+
function renderEvidenceFanInJob(lines, model) {
|
|
1086
|
+
const needs = evidenceProducerJobIds(model);
|
|
1087
|
+
if (needs.length === 0)
|
|
1088
|
+
return;
|
|
1089
|
+
const evidence = model.evidence;
|
|
1090
|
+
lines.push(` ${yamlKey(evidence.job)}:`, ` name: ${evidence.job}`, ` needs: ${JSON.stringify(needs)}`, " if: always()", " runs-on: ubuntu-latest", " permissions:", " contents: read", " steps:", " - name: Merge evidence manifests", ` uses: ${ASYNC_EVIDENCE_ACTION}`, " with:", " mode: merge", ` artifact-pattern: ${evidence.artifactNamePrefix}-*`, " manifest-path: .async/evidence/index.json", " summary-path: .async/evidence/index.md", ` artifact-name: ${evidence.artifactNamePrefix}-index`, ` retention-days: ${evidence.retentionDays}`, ` if-no-files-found: ${evidence.ifNoFilesFound}`, ` include-summary: ${evidence.includeSummary ? "true" : "false"}`, "");
|
|
1091
|
+
}
|
|
1092
|
+
function evidenceProducerJobIds(model) {
|
|
1093
|
+
const ids = new Set(model.jobs.map((job) => job.id));
|
|
1094
|
+
for (const sourceJob of model.sourceImpactJobs) {
|
|
1095
|
+
ids.add(sourceJob.planJob);
|
|
1096
|
+
ids.add(sourceJob.matrixJob);
|
|
1097
|
+
}
|
|
1098
|
+
if (model.pages.enabled)
|
|
1099
|
+
ids.add(model.pages.job);
|
|
1100
|
+
if (model.packagePreviews.enabled)
|
|
1101
|
+
ids.add("package-preview");
|
|
1102
|
+
if (model.bridge.actionsJob.enabled)
|
|
1103
|
+
ids.add(model.bridge.job);
|
|
1104
|
+
ids.delete(model.evidence.job);
|
|
1105
|
+
return [...ids].sort((left, right) => left.localeCompare(right));
|
|
1106
|
+
}
|
|
924
1107
|
function renderDependabotAutoMergeJob(lines, ecosystems) {
|
|
925
1108
|
lines.push(" dependabot-auto-merge:", " name: dependabot-auto-merge", " if: github.event.pull_request.user.login == 'dependabot[bot]' && github.event.pull_request.draft == false", " runs-on: ubuntu-latest", " permissions:", " contents: write", " pull-requests: write", " steps:", " - name: Fetch Dependabot metadata", " id: dependabot-metadata", ` uses: ${DEPENDABOT_FETCH_METADATA_ACTION}`, " with:", " github-token: ${{ secrets.GITHUB_TOKEN }}", "", " - name: Merge validated Dependabot PR", ` uses: ${ASYNC_DEPENDABOT_MERGE_ACTION}`, " with:", " pull-request-number: ${{ github.event.pull_request.number }}", " actor: ${{ github.event.pull_request.user.login }}", " dependency-ecosystem: ${{ steps.dependabot-metadata.outputs.package-ecosystem }}", " allowed-ecosystems: |", ...ecosystems.map((ecosystem) => ` ${ecosystem}`), " env:", " GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}", "");
|
|
926
1109
|
}
|