@codedrifters/configulator 0.0.428 → 0.0.430

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/lib/index.js CHANGED
@@ -184,6 +184,7 @@ __export(index_exports, {
184
184
  AUDIT_CATEGORY_ORDER: () => AUDIT_CATEGORY_ORDER,
185
185
  AgentConfig: () => AgentConfig,
186
186
  ApiExtractor: () => ApiExtractor,
187
+ ApplyWorkflow: () => ApplyWorkflow,
187
188
  AstroConfig: () => AstroConfig,
188
189
  AstroOutput: () => AstroOutput,
189
190
  AstroProject: () => AstroProject,
@@ -279,7 +280,12 @@ __export(index_exports, {
279
280
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED: () => DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
280
281
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED: () => DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
281
282
  DEPLOY_GATE: () => DEPLOY_GATE,
283
+ DIFF_ARTIFACT_NAME: () => DIFF_ARTIFACT_NAME,
284
+ DIFF_OUTPUT_DIRECTORY: () => DIFF_OUTPUT_DIRECTORY,
285
+ DIFF_PART_ARTIFACT_PREFIX: () => DIFF_PART_ARTIFACT_PREFIX,
286
+ DIFF_REPORT_JOB_ID: () => DIFF_REPORT_JOB_ID,
282
287
  DOCS_SYNC_AUDIT_SCHEMA_VERSION: () => DOCS_SYNC_AUDIT_SCHEMA_VERSION,
288
+ DiffReportJob: () => DiffReportJob,
283
289
  GITHUB_ISSUE_TYPES: () => GITHUB_ISSUE_TYPES,
284
290
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX: () => GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
285
291
  ISSUE_TEMPLATES_GENERATED_SUFFIX: () => ISSUE_TEMPLATES_GENERATED_SUFFIX,
@@ -338,6 +344,7 @@ __export(index_exports, {
338
344
  VERSION_NPM_PACKAGES: () => VERSION_NPM_PACKAGES,
339
345
  VSCodeConfig: () => VSCodeConfig,
340
346
  Vitest: () => Vitest,
347
+ WorkflowHomeRepository: () => WorkflowHomeRepository,
341
348
  addApproveMergeUpgradeWorkflow: () => addApproveMergeUpgradeWorkflow,
342
349
  addBuildCompleteJob: () => addBuildCompleteJob,
343
350
  addPlaywright: () => addPlaywright,
@@ -458,6 +465,7 @@ __export(index_exports, {
458
465
  renderCheckLinksProcedure: () => renderCheckLinksProcedure,
459
466
  renderCustomDocSectionBlock: () => renderCustomDocSectionBlock,
460
467
  renderCustomDocSections: () => renderCustomDocSections,
468
+ renderDiffPartUploadStep: () => renderDiffPartUploadStep,
461
469
  renderExtractApiProcedure: () => renderExtractApiProcedure,
462
470
  renderFocusSection: () => renderFocusSection,
463
471
  renderGithubIssueTypeSection: () => renderGithubIssueTypeSection,
@@ -489,6 +497,8 @@ __export(index_exports, {
489
497
  renderScopeGateSection: () => renderScopeGateSection,
490
498
  renderScopeGateShellHelpers: () => renderScopeGateShellHelpers,
491
499
  renderSetIssueTypeFallbackLines: () => renderSetIssueTypeFallbackLines,
500
+ renderSetupNode: () => renderSetupNode,
501
+ renderSetupPnpm: () => renderSetupPnpm,
492
502
  renderSharedEditingBundleHook: () => renderSharedEditingBundleHook,
493
503
  renderSharedEditingHelperScript: () => renderSharedEditingHelperScript,
494
504
  renderSharedEditingRuleContent: () => renderSharedEditingRuleContent,
@@ -42290,69 +42300,23 @@ var DEFAULT_FAVICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
42290
42300
  `;
42291
42301
 
42292
42302
  // src/projects/aws-cdk-project.ts
42293
- var import_projen28 = require("projen");
42303
+ var import_projen31 = require("projen");
42294
42304
  var import_javascript5 = require("projen/lib/javascript");
42295
42305
  var import_release2 = require("projen/lib/release");
42296
42306
  var import_ts_deepmerge4 = require("ts-deepmerge");
42297
42307
 
42298
42308
  // src/workflows/aws-deploy-workflow.ts
42299
42309
  var import_utils11 = __toESM(require_lib());
42300
- var import_projen26 = require("projen");
42310
+ var import_projen29 = require("projen");
42301
42311
  var import_build = require("projen/lib/build");
42312
+ var import_github8 = require("projen/lib/github");
42313
+ var import_workflows_model7 = require("projen/lib/github/workflows-model");
42314
+
42315
+ // src/workflows/apply-workflow.ts
42316
+ var import_projen26 = require("projen");
42302
42317
  var import_github6 = require("projen/lib/github");
42303
42318
  var import_workflows_model5 = require("projen/lib/github/workflows-model");
42304
42319
 
42305
- // src/workflows/deploy-gate.ts
42306
- var DEPLOY_GATE = {
42307
- /**
42308
- * Hold each deploy job behind a GitHub environment so its protection rules —
42309
- * required reviewers, wait timers, deployment branch policies — apply before
42310
- * the job is sent to a runner.
42311
- */
42312
- ENVIRONMENT: "environment",
42313
- /**
42314
- * Split the workflow in two. A **plan** workflow builds and diffs but never
42315
- * deploys; a dispatch-only **apply** workflow deploys the exact cloud
42316
- * assembly a nominated plan run produced.
42317
- *
42318
- * Needs no protection rule, so unlike {@link DEPLOY_GATE.ENVIRONMENT} it
42319
- * works on every GitHub plan. `environmentName` is optional here and layers
42320
- * an environment onto the apply jobs.
42321
- */
42322
- PLAN_APPLY: "plan-apply"
42323
- };
42324
- var resolveEnvironmentGate = (gate, environmentName, componentName) => {
42325
- const trimmed = environmentName?.trim();
42326
- if (gate === void 0) {
42327
- if (trimmed) {
42328
- throw new Error(
42329
- `${componentName} requires \`gate\` to be set when \`environmentName\` is supplied, got "${environmentName}" with no gate`
42330
- );
42331
- }
42332
- return void 0;
42333
- }
42334
- if (gate === DEPLOY_GATE.ENVIRONMENT && !trimmed) {
42335
- throw new Error(
42336
- `${componentName} requires a non-empty \`environmentName\` when \`gate\` is "${gate}"`
42337
- );
42338
- }
42339
- return trimmed;
42340
- };
42341
-
42342
- // src/workflows/home-repository.ts
42343
- var HOME_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
42344
- var renderHomeRepositoryCondition = (homeRepository, componentName) => {
42345
- if (homeRepository === void 0) {
42346
- return void 0;
42347
- }
42348
- if (!HOME_REPOSITORY_PATTERN.test(homeRepository)) {
42349
- throw new Error(
42350
- `${componentName} requires \`homeRepository\` to be an \`owner/repo\` slug, got "${homeRepository}"`
42351
- );
42352
- }
42353
- return `github.repository == '${homeRepository}'`;
42354
- };
42355
-
42356
42320
  // src/workflows/plan-apply.ts
42357
42321
  var VALIDATE_PLAN_JOB_ID = "validate-plan";
42358
42322
  var PLAN_RUN_ID_INPUT = "plan_run_id";
@@ -42452,46 +42416,532 @@ var renderPlanValidationScript = (options) => {
42452
42416
  ].join("\n");
42453
42417
  };
42454
42418
 
42455
- // src/workflows/aws-deploy-workflow.ts
42456
- var PROD_DEPLOY_NAME = "prod-deploy";
42457
- var DIFF_OUTPUT_FILE = "cdk-diff.txt";
42419
+ // src/workflows/apply-workflow.ts
42420
+ var ApplyWorkflow = class _ApplyWorkflow extends import_projen26.Component {
42421
+ constructor(project, github, options) {
42422
+ super(project);
42423
+ /**
42424
+ * Every attached component's targets, in attach order. Held as targets rather
42425
+ * than branches because `AwsDeploymentTarget.branches` is mutable — the
42426
+ * branch union is read at `preSynthesize`, once every component has attached.
42427
+ */
42428
+ this.attachedTargets = [];
42429
+ /**
42430
+ * Attach another component's apply jobs to this workflow.
42431
+ *
42432
+ * Every setting the shared `validate-plan` job bakes in must match what the
42433
+ * workflow was created with — there is one validation job for the whole file,
42434
+ * so a disagreement cannot be honoured and is rejected rather than resolved
42435
+ * in favour of whichever component happened to be constructed first.
42436
+ */
42437
+ this.attach = (options) => {
42438
+ const { componentName } = options;
42439
+ const name = this.workflow.name;
42440
+ if (options.planWorkflowName !== this.contract.planWorkflowName) {
42441
+ throw new Error(
42442
+ `${componentName} cannot attach to the apply workflow "${name}": it plans from build workflow "${options.planWorkflowName}", but that apply workflow only accepts runs of "${this.contract.planWorkflowName}". Every component sharing an apply workflow must share one build workflow.`
42443
+ );
42444
+ }
42445
+ this.requireAgreement(
42446
+ componentName,
42447
+ "requireCurrentHead",
42448
+ options.requireCurrentHead,
42449
+ this.contract.requireCurrentHead
42450
+ );
42451
+ this.requireAgreement(
42452
+ componentName,
42453
+ "verifyArtifactDigest",
42454
+ options.verifyArtifactDigest,
42455
+ this.contract.verifyArtifactDigest
42456
+ );
42457
+ this.attachedTargets.push(...options.awsDeploymentTargets);
42458
+ };
42459
+ /**
42460
+ * Reject a boolean `planApply` setting that differs from the one this
42461
+ * workflow was created with.
42462
+ */
42463
+ this.requireAgreement = (componentName, option, supplied, expected) => {
42464
+ if (supplied === expected) {
42465
+ return;
42466
+ }
42467
+ throw new Error(
42468
+ `${componentName} cannot attach to the apply workflow "${this.workflow.name}": \`planApply.${option}\` is ${supplied} here and ${expected} on the components already attached. The setting is baked into the one shared \`${VALIDATE_PLAN_JOB_ID}\` job, so every component sharing an apply workflow must agree on it.`
42469
+ );
42470
+ };
42471
+ /**
42472
+ * Render the validation job every apply job depends on.
42473
+ *
42474
+ * `allowedBranches` is the union of every attached target's branches, deduped
42475
+ * and left in attach order. The apply run's own `github.ref` says nothing
42476
+ * about what was planned, so the list is checked against the *plan run's*
42477
+ * `head_branch` rather than against a workflow-level branch filter.
42478
+ */
42479
+ this.renderValidateJob = () => {
42480
+ const { planWorkflowName, requireCurrentHead, verifyArtifactDigest } = this.contract;
42481
+ const allowedBranches = Array.from(
42482
+ new Set(
42483
+ this.attachedTargets.flatMap(
42484
+ (target) => target.branches.map((b) => b.branch)
42485
+ )
42486
+ )
42487
+ );
42488
+ return {
42489
+ name: "Validate plan run",
42490
+ runsOn: ["ubuntu-latest"],
42491
+ permissions: {
42492
+ actions: import_workflows_model5.JobPermission.READ,
42493
+ contents: import_workflows_model5.JobPermission.READ
42494
+ },
42495
+ ...this.homeRepositoryCondition ? { if: `\${{ ${this.homeRepositoryCondition} }}` } : {},
42496
+ outputs: {
42497
+ head_sha: { stepId: "validate_plan_run", outputName: "head_sha" },
42498
+ head_branch: { stepId: "validate_plan_run", outputName: "head_branch" },
42499
+ ...verifyArtifactDigest ? {
42500
+ artifact_digest: {
42501
+ stepId: "validate_plan_run",
42502
+ outputName: "artifact_digest"
42503
+ }
42504
+ } : {}
42505
+ },
42506
+ steps: [
42507
+ {
42508
+ name: "Validate plan run",
42509
+ id: "validate_plan_run",
42510
+ uses: "actions/github-script@v9",
42511
+ /**
42512
+ * The dispatch input reaches the script through the environment
42513
+ * rather than a `${{ }}` interpolation, so a crafted run id cannot
42514
+ * inject JavaScript into the validation itself.
42515
+ */
42516
+ env: {
42517
+ PLAN_RUN_ID: `\${{ inputs.${PLAN_RUN_ID_INPUT} }}`
42518
+ },
42519
+ with: {
42520
+ script: renderPlanValidationScript({
42521
+ planWorkflowPath: `.github/workflows/${planWorkflowName}.yml`,
42522
+ allowedBranches,
42523
+ requireCurrentHead,
42524
+ verifyArtifactDigest
42525
+ })
42526
+ }
42527
+ }
42528
+ ]
42529
+ };
42530
+ };
42531
+ const { applyWorkflowName } = options;
42532
+ if (github.tryFindWorkflow(applyWorkflowName)) {
42533
+ throw new Error(
42534
+ `${options.componentName} cannot create the apply workflow "${applyWorkflowName}" because a workflow with that name already exists. Set \`planApply.applyWorkflow\` to share the existing one, or \`planApply.applyWorkflowName\` to something unique.`
42535
+ );
42536
+ }
42537
+ this.contract = {
42538
+ planWorkflowName: options.planWorkflowName,
42539
+ requireCurrentHead: options.requireCurrentHead,
42540
+ verifyArtifactDigest: options.verifyArtifactDigest
42541
+ };
42542
+ this.homeRepositoryCondition = options.homeRepositoryCondition;
42543
+ this.attachedTargets.push(...options.awsDeploymentTargets);
42544
+ this.workflow = new import_github6.GithubWorkflow(github, applyWorkflowName);
42545
+ this.workflow.on({
42546
+ workflowDispatch: {
42547
+ inputs: {
42548
+ [PLAN_RUN_ID_INPUT]: {
42549
+ description: "Run id of the plan workflow run to apply. Its build artifact is deployed verbatim.",
42550
+ required: true,
42551
+ type: "string"
42552
+ }
42553
+ }
42554
+ }
42555
+ });
42556
+ this.workflow.addJob(VALIDATE_PLAN_JOB_ID, this.renderValidateJob());
42557
+ }
42558
+ static of(project, workflow) {
42559
+ const isDefined = (c) => c instanceof _ApplyWorkflow && c.workflow === workflow;
42560
+ return project.components.find(isDefined);
42561
+ }
42562
+ preSynthesize() {
42563
+ this.workflow.updateJob(VALIDATE_PLAN_JOB_ID, this.renderValidateJob());
42564
+ super.preSynthesize();
42565
+ }
42566
+ };
42567
+
42568
+ // src/workflows/deploy-gate.ts
42569
+ var DEPLOY_GATE = {
42570
+ /**
42571
+ * Hold each deploy job behind a GitHub environment so its protection rules —
42572
+ * required reviewers, wait timers, deployment branch policies — apply before
42573
+ * the job is sent to a runner.
42574
+ */
42575
+ ENVIRONMENT: "environment",
42576
+ /**
42577
+ * Split the workflow in two. A **plan** workflow builds and diffs but never
42578
+ * deploys; a dispatch-only **apply** workflow deploys the exact cloud
42579
+ * assembly a nominated plan run produced.
42580
+ *
42581
+ * Needs no protection rule, so unlike {@link DEPLOY_GATE.ENVIRONMENT} it
42582
+ * works on every GitHub plan. `environmentName` is optional here and layers
42583
+ * an environment onto the apply jobs.
42584
+ */
42585
+ PLAN_APPLY: "plan-apply"
42586
+ };
42587
+ var resolveEnvironmentGate = (gate, environmentName, componentName) => {
42588
+ const trimmed = environmentName?.trim();
42589
+ if (gate === void 0) {
42590
+ if (trimmed) {
42591
+ throw new Error(
42592
+ `${componentName} requires \`gate\` to be set when \`environmentName\` is supplied, got "${environmentName}" with no gate`
42593
+ );
42594
+ }
42595
+ return void 0;
42596
+ }
42597
+ if (gate === DEPLOY_GATE.ENVIRONMENT && !trimmed) {
42598
+ throw new Error(
42599
+ `${componentName} requires a non-empty \`environmentName\` when \`gate\` is "${gate}"`
42600
+ );
42601
+ }
42602
+ return trimmed;
42603
+ };
42604
+
42605
+ // src/workflows/diff-report-job.ts
42606
+ var import_projen27 = require("projen");
42607
+ var import_github7 = require("projen/lib/github");
42608
+ var import_workflows_model6 = require("projen/lib/github/workflows-model");
42609
+ var DIFF_REPORT_JOB_ID = "diff-report";
42610
+ var DIFF_OUTPUT_DIRECTORY = "cdk-diff";
42611
+ var DIFF_PART_ARTIFACT_PREFIX = "cdk-diff-part";
42612
+ var DIFF_ARTIFACT_NAME = "cdk-diff";
42613
+ var DIFF_PART_RETENTION_DAYS = 1;
42458
42614
  var DIFF_SUMMARY_BYTE_LIMIT = 9e5;
42459
- var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Component {
42460
- constructor(project, options = {}) {
42615
+ var RUN_URL = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
42616
+ var DiffReportJob = class _DiffReportJob extends import_projen27.Component {
42617
+ constructor(project, options) {
42461
42618
  super(project);
42462
- this.project = project;
42463
- this.options = options;
42464
42619
  /**
42465
- * AWS environment type, such as primary or secondary.
42620
+ * Every contributed target, in attach order.
42621
+ */
42622
+ this.targets = [];
42623
+ /**
42624
+ * Publish settings are OR-ed rather than first-wins: one report means one
42625
+ * answer, and a component that asked for a summary should get one even when
42626
+ * a sibling did not.
42627
+ */
42628
+ this.summary = false;
42629
+ this.artifact = false;
42630
+ /**
42631
+ * Fold one component's contribution in.
42632
+ */
42633
+ this.record = (options) => {
42634
+ this.targets.push(...options.targets);
42635
+ this.summary = this.summary || options.summary;
42636
+ this.artifact = this.artifact || options.artifact;
42637
+ };
42638
+ /**
42639
+ * Compose the report's own condition.
42466
42640
  *
42467
- * @deprecated Use deployment target role terminology elsewhere. This property is maintained for backward compatibility.
42468
- * @default 'primary' (this is the only type supported currently)
42641
+ * `!cancelled()` leads, and it is load-bearing: a job whose `needs` failed or
42642
+ * were skipped is skipped by default, so without a status function the report
42643
+ * would vanish in exactly the cases it is most wanted — one target's diff
42644
+ * failing, or some targets not applying to this branch.
42469
42645
  */
42470
- this.awsEnvironmentType = import_utils11.DEPLOYMENT_TARGET_ROLE.PRIMARY;
42471
- this.setupNode = () => {
42646
+ this.renderJobCondition = () => {
42647
+ const branchConditions = this.targets.map(
42648
+ (target) => target.branchCondition
42649
+ );
42650
+ const anyBranch = branchConditions.some((condition) => !condition);
42651
+ const branchClause = anyBranch ? void 0 : Array.from(new Set(branchConditions)).join(" || ");
42652
+ return "${{ " + [
42653
+ "!cancelled()",
42654
+ "!needs.build.outputs.self_mutation_happened",
42655
+ ...this.homeRepositoryCondition ? [this.homeRepositoryCondition] : [],
42656
+ ...branchClause ? [`(${branchClause})`] : []
42657
+ ].join(" && ") + " }}";
42658
+ };
42659
+ /**
42660
+ * Pull every target's part into one directory.
42661
+ *
42662
+ * `continueOnError` because a run where every target's diff job was skipped
42663
+ * by its branch filter matches no parts at all, and a report with nothing to
42664
+ * report is not a failure.
42665
+ */
42666
+ this.renderDownloadStep = () => {
42472
42667
  return [
42473
- {
42474
- name: "Setup Node",
42475
- uses: `actions/setup-node@${VERSION.SETUP_NODE_ACTION_VERSION}`,
42668
+ import_github7.WorkflowSteps.downloadArtifact({
42669
+ name: "Download target diffs",
42670
+ continueOnError: true,
42476
42671
  with: {
42477
- ["node-version"]: VERSION.NODE_WORKFLOWS
42478
- },
42479
- // occasionally this step fails due to internal issues at github
42480
- timeoutMinutes: 1
42672
+ pattern: `${DIFF_PART_ARTIFACT_PREFIX}-*`,
42673
+ mergeMultiple: true,
42674
+ path: DIFF_OUTPUT_DIRECTORY
42675
+ }
42676
+ })
42677
+ ];
42678
+ };
42679
+ /**
42680
+ * Render every target's diff into the job summary, one collapsed `<details>`
42681
+ * block per target.
42682
+ *
42683
+ * A target whose part is absent renders as "did not run", which is what
42684
+ * distinguishes a target that was skipped or whose diff failed before it
42685
+ * captured anything from one that simply had no changes — the latter has a
42686
+ * file, carrying the CDK CLI's own no-differences wording, rather than being
42687
+ * detected by matching on it.
42688
+ */
42689
+ this.renderSummaryStep = () => {
42690
+ if (!this.summary || this.targets.length === 0) {
42691
+ return [];
42692
+ }
42693
+ const budget = Math.floor(DIFF_SUMMARY_BYTE_LIMIT / this.targets.length);
42694
+ const rows = this.targets.map(
42695
+ (target) => `'${target.label}|${target.outputFile}'`
42696
+ );
42697
+ return [
42698
+ {
42699
+ name: "Render diff report",
42700
+ if: "!cancelled()",
42701
+ shell: "bash",
42702
+ run: [
42703
+ "{",
42704
+ " while IFS='|' read -r label file; do",
42705
+ ' echo "## cdk diff \u2014 $label"',
42706
+ ' echo ""',
42707
+ ' if [ ! -f "$file" ]; then',
42708
+ " echo '_Did not run._'",
42709
+ ' echo ""',
42710
+ " continue",
42711
+ " fi",
42712
+ " echo '<details><summary>cdk diff output</summary>'",
42713
+ ' echo ""',
42714
+ " echo '```'",
42715
+ ` if [ "$(wc -c < "$file")" -gt ${budget} ]; then`,
42716
+ ` head -c ${budget} "$file"`,
42717
+ ` printf '\\n... truncated at ${budget} bytes ...\\n'`,
42718
+ " else",
42719
+ ' cat "$file"',
42720
+ " fi",
42721
+ " echo '```'",
42722
+ ' echo ""',
42723
+ " echo '</details>'",
42724
+ ' echo ""',
42725
+ ` done < <(printf '%s\\n' ${rows.join(" ")})`,
42726
+ ...this.artifact ? [
42727
+ ` echo 'Full diff: the \`${DIFF_ARTIFACT_NAME}\` artifact on [this run](${RUN_URL}).'`
42728
+ ] : [],
42729
+ '} >> "$GITHUB_STEP_SUMMARY"'
42730
+ ].join("\n")
42481
42731
  }
42482
42732
  ];
42483
42733
  };
42484
- this.setupPnpm = () => {
42734
+ /**
42735
+ * Fail the report when any target's diff failed.
42736
+ *
42737
+ * Runs last, so the summary and the artifact are published first — a reviewer
42738
+ * still gets the whole picture, including whatever the failing target managed
42739
+ * to capture. But the report must not end up green: everything gated behind
42740
+ * it `needs` it, and a job whose `needs` failed is skipped, which is what
42741
+ * keeps a failed diff from letting a gated deploy through to *Waiting for
42742
+ * approval*. Without this the report would always succeed and a diff failure
42743
+ * would only soft-block behind a human.
42744
+ *
42745
+ * Only `failure` counts. A target skipped by its branch filter reports
42746
+ * `skipped`, which is normal and must not fail the report.
42747
+ */
42748
+ this.renderFailureStep = () => {
42749
+ const jobNames = Array.from(
42750
+ new Set(this.targets.map((target) => target.jobName))
42751
+ );
42752
+ if (jobNames.length === 0) {
42753
+ return [];
42754
+ }
42485
42755
  return [
42486
42756
  {
42487
- name: "Setup PNPM",
42488
- uses: `pnpm/action-setup@${VERSION.PNPM_ACTION_SETUP_VERSION}`,
42757
+ name: "Fail if any target's diff failed",
42758
+ if: "!cancelled()",
42759
+ shell: "bash",
42760
+ run: [
42761
+ ...jobNames.map(
42762
+ (jobName) => `if [ "\${{ needs.${jobName}.result }}" = "failure" ]; then echo "::error::Diff job ${jobName} failed"; failed=1; fi`
42763
+ ),
42764
+ 'if [ "${failed:-0}" = "1" ]; then exit 1; fi'
42765
+ ].join("\n")
42766
+ }
42767
+ ];
42768
+ };
42769
+ /**
42770
+ * Upload the merged directory as the one artifact meant to be read.
42771
+ */
42772
+ this.renderUploadStep = () => {
42773
+ if (!this.artifact || this.targets.length === 0) {
42774
+ return [];
42775
+ }
42776
+ return [
42777
+ import_github7.WorkflowSteps.uploadArtifact({
42778
+ name: "Upload combined diff",
42779
+ if: "!cancelled()",
42489
42780
  with: {
42490
- version: this.rootProject.pnpmVersion
42781
+ name: DIFF_ARTIFACT_NAME,
42782
+ path: DIFF_OUTPUT_DIRECTORY,
42783
+ ifNoFilesFound: "ignore"
42491
42784
  }
42492
- }
42785
+ })
42493
42786
  ];
42494
42787
  };
42788
+ this.buildWorkflow = options.buildWorkflow;
42789
+ this.homeRepositoryCondition = options.homeRepositoryCondition;
42790
+ this.record(options);
42791
+ this.buildWorkflow.workflow.addJob(DIFF_REPORT_JOB_ID, {
42792
+ name: "Diff report",
42793
+ needs: ["build"],
42794
+ runsOn: ["ubuntu-latest"],
42795
+ permissions: {
42796
+ contents: import_workflows_model6.JobPermission.READ
42797
+ },
42798
+ steps: []
42799
+ });
42800
+ }
42801
+ static of(project, buildWorkflow) {
42802
+ const isDefined = (c) => c instanceof _DiffReportJob && c.buildWorkflow === buildWorkflow;
42803
+ return project.components.find(isDefined);
42804
+ }
42805
+ /**
42806
+ * Add a component's targets to the plan workflow's diff report, creating the
42807
+ * report job the first time it is called for that workflow.
42808
+ */
42809
+ static attach(project, options) {
42810
+ const existing = _DiffReportJob.of(project, options.buildWorkflow);
42811
+ if (existing) {
42812
+ existing.record(options);
42813
+ return existing;
42814
+ }
42815
+ return new _DiffReportJob(project, options);
42816
+ }
42817
+ preSynthesize() {
42818
+ this.buildWorkflow.workflow.updateJob(DIFF_REPORT_JOB_ID, {
42819
+ name: "Diff report",
42820
+ /**
42821
+ * `build` is a direct dependency so the job can read its
42822
+ * self-mutation output, which is otherwise unreachable — the `needs`
42823
+ * context only carries direct dependencies, not transitive ones.
42824
+ */
42825
+ needs: [
42826
+ "build",
42827
+ ...Array.from(new Set(this.targets.map((target) => target.jobName)))
42828
+ ],
42829
+ runsOn: ["ubuntu-latest"],
42830
+ permissions: {
42831
+ contents: import_workflows_model6.JobPermission.READ
42832
+ },
42833
+ if: this.renderJobCondition(),
42834
+ steps: [
42835
+ ...this.renderDownloadStep(),
42836
+ ...this.renderSummaryStep(),
42837
+ ...this.renderUploadStep(),
42838
+ ...this.renderFailureStep()
42839
+ ]
42840
+ });
42841
+ super.preSynthesize();
42842
+ }
42843
+ };
42844
+ var renderDiffPartUploadStep = (options) => {
42845
+ return import_github7.WorkflowSteps.uploadArtifact({
42846
+ name: `Upload diff ${options.label}`,
42847
+ if: "!cancelled()",
42848
+ with: {
42849
+ name: options.artifactName,
42850
+ path: options.outputFile,
42851
+ ifNoFilesFound: "ignore",
42852
+ retentionDays: DIFF_PART_RETENTION_DAYS
42853
+ }
42854
+ });
42855
+ };
42856
+
42857
+ // src/workflows/home-repository.ts
42858
+ var import_projen28 = require("projen");
42859
+ var HOME_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
42860
+ var renderHomeRepositoryCondition = (homeRepository, componentName) => {
42861
+ if (homeRepository === void 0) {
42862
+ return void 0;
42863
+ }
42864
+ if (!HOME_REPOSITORY_PATTERN.test(homeRepository)) {
42865
+ throw new Error(
42866
+ `${componentName} requires \`homeRepository\` to be an \`owner/repo\` slug, got "${homeRepository}"`
42867
+ );
42868
+ }
42869
+ return `github.repository == '${homeRepository}'`;
42870
+ };
42871
+ var describePin = (homeRepository) => homeRepository === void 0 ? "unset" : `"${homeRepository}"`;
42872
+ var WorkflowHomeRepository = class _WorkflowHomeRepository extends import_projen28.Component {
42873
+ static of(project, workflow) {
42874
+ const isDefined = (c) => c instanceof _WorkflowHomeRepository && c.workflow === workflow;
42875
+ return project.components.find(isDefined);
42876
+ }
42877
+ /**
42878
+ * Record a pin against a workflow, throwing when it disagrees with what an
42879
+ * earlier component declared for the same one.
42880
+ *
42881
+ * @param project - Project the record is attached to. Use the root project,
42882
+ * so components on sibling sub-projects find each other.
42883
+ */
42884
+ static require(project, options) {
42885
+ const { workflow, workflowName, homeRepository, componentName } = options;
42886
+ const existing = _WorkflowHomeRepository.of(project, workflow);
42887
+ if (!existing) {
42888
+ return new _WorkflowHomeRepository(project, options);
42889
+ }
42890
+ if (existing.homeRepository !== homeRepository) {
42891
+ throw new Error(
42892
+ `${componentName} requires every component sharing the workflow "${workflowName}" to declare the same \`homeRepository\`, got ${describePin(homeRepository)} here and ${describePin(existing.homeRepository)} already declared. The pin guards every job in one workflow file, so it cannot differ between them.`
42893
+ );
42894
+ }
42895
+ return existing;
42896
+ }
42897
+ constructor(project, options) {
42898
+ super(project);
42899
+ this.workflow = options.workflow;
42900
+ this.homeRepository = options.homeRepository;
42901
+ }
42902
+ };
42903
+
42904
+ // src/workflows/node-setup.ts
42905
+ var renderSetupNode = () => {
42906
+ return [
42907
+ {
42908
+ name: "Setup Node",
42909
+ uses: `actions/setup-node@${VERSION.SETUP_NODE_ACTION_VERSION}`,
42910
+ with: {
42911
+ ["node-version"]: VERSION.NODE_WORKFLOWS
42912
+ },
42913
+ timeoutMinutes: 1
42914
+ }
42915
+ ];
42916
+ };
42917
+ var renderSetupPnpm = (pnpmVersion) => {
42918
+ return [
42919
+ {
42920
+ name: "Setup PNPM",
42921
+ uses: `pnpm/action-setup@${VERSION.PNPM_ACTION_SETUP_VERSION}`,
42922
+ with: {
42923
+ version: pnpmVersion
42924
+ }
42925
+ }
42926
+ ];
42927
+ };
42928
+
42929
+ // src/workflows/aws-deploy-workflow.ts
42930
+ var PROD_DEPLOY_NAME = "prod-deploy";
42931
+ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Component {
42932
+ constructor(project, options = {}) {
42933
+ super(project);
42934
+ this.project = project;
42935
+ this.options = options;
42936
+ /**
42937
+ * AWS environment type, such as primary or secondary.
42938
+ *
42939
+ * @deprecated Use deployment target role terminology elsewhere. This property is maintained for backward compatibility.
42940
+ * @default 'primary' (this is the only type supported currently)
42941
+ */
42942
+ this.awsEnvironmentType = import_utils11.DEPLOYMENT_TARGET_ROLE.PRIMARY;
42943
+ this.setupNode = () => renderSetupNode();
42944
+ this.setupPnpm = () => renderSetupPnpm(this.rootProject.pnpmVersion);
42495
42945
  /**
42496
42946
  * Build the deterministic GitHub Actions job name for a deploy target.
42497
42947
  *
@@ -42509,45 +42959,43 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42509
42959
  ].join("-");
42510
42960
  };
42511
42961
  /**
42512
- * Build the deterministic GitHub Actions job name for a target's `cdk diff`
42513
- * job. Mirrors {@link buildJobName} with a `diff` verb so the diff and deploy
42514
- * jobs for one target sort next to each other in the run view.
42962
+ * Build the deterministic GitHub Actions job name for a target's apply job.
42963
+ * Mirrors {@link buildJobName} with an `apply` verb.
42515
42964
  */
42516
- this.buildDiffJobName = (target) => {
42965
+ this.buildApplyJobName = (target) => {
42517
42966
  return [
42518
42967
  target.awsStageType,
42519
42968
  target.deploymentTargetRole,
42520
- "diff",
42969
+ "apply",
42521
42970
  target.project.name,
42522
42971
  target.account,
42523
42972
  target.region
42524
42973
  ].join("-");
42525
42974
  };
42526
42975
  /**
42527
- * Build the deterministic GitHub Actions job name for a target's apply job.
42528
- * Mirrors {@link buildJobName} with an `apply` verb.
42976
+ * Build the deterministic GitHub Actions job name for a target's `cdk diff`
42977
+ * job. Mirrors {@link buildJobName} with a `diff` verb so the diff and deploy
42978
+ * jobs for one target sort next to each other in the run view.
42529
42979
  */
42530
- this.buildApplyJobName = (target) => {
42980
+ this.buildDiffJobName = (target) => {
42531
42981
  return [
42532
42982
  target.awsStageType,
42533
42983
  target.deploymentTargetRole,
42534
- "apply",
42984
+ "diff",
42535
42985
  target.project.name,
42536
42986
  target.account,
42537
42987
  target.region
42538
42988
  ].join("-");
42539
42989
  };
42540
42990
  /**
42541
- * Build the CI artifact name for a target's captured diff.
42991
+ * Build the slug that makes a target unique among every target in the plan
42992
+ * workflow, across every deploy component feeding it.
42542
42993
  *
42543
- * Carries every component that makes a target unique — including
42544
- * `deploymentTargetRole`, which two otherwise-identical targets can differ
42545
- * on. `actions/upload-artifact` v4+ treats artifacts as immutable and rejects
42546
- * a duplicate name within a run, so parallel diff jobs cannot share one.
42994
+ * Includes `deploymentTargetRole`, which two otherwise-identical targets can
42995
+ * differ on.
42547
42996
  */
42548
- this.buildDiffArtifactName = (target) => {
42997
+ this.buildDiffSlug = (target) => {
42549
42998
  return [
42550
- "cdk-diff",
42551
42999
  target.awsStageType,
42552
43000
  target.deploymentTargetRole,
42553
43001
  target.project.name,
@@ -42556,83 +43004,45 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42556
43004
  ].join("-");
42557
43005
  };
42558
43006
  /**
42559
- * Create the dispatch-only apply workflow and its validation job.
43007
+ * Build the file a target's captured diff is written to, both in its own diff
43008
+ * job's workspace and in the merged directory the report job assembles.
43009
+ */
43010
+ this.buildDiffOutputFile = (target) => {
43011
+ return `${DIFF_OUTPUT_DIRECTORY}/${this.buildDiffSlug(target)}.txt`;
43012
+ };
43013
+ /**
43014
+ * Build the intermediate artifact name carrying one target's captured diff.
43015
+ *
43016
+ * The diff jobs run in parallel and `actions/upload-artifact` v4+ rejects a
43017
+ * duplicate name within a run, so each target needs its own. The report job
43018
+ * merges them into a single artifact, which is the one meant to be read.
43019
+ */
43020
+ this.buildDiffPartArtifactName = (target) => {
43021
+ return `${DIFF_PART_ARTIFACT_PREFIX}-${this.buildDiffSlug(target)}`;
43022
+ };
43023
+ /**
43024
+ * Join an apply workflow another `AwsDeployWorkflow` already created, so this
43025
+ * component's apply jobs land in that file rather than a second one.
42560
43026
  *
42561
- * The workflow file name defaults to the plan workflow's name suffixed with
42562
- * `-apply`. A name already in use throws rather than silently colliding on
42563
- * the underlying `.github/workflows/<name>.yml` file the usual cause is two
42564
- * `AwsDeployWorkflow`s sharing one build workflow, and the fix is an explicit
42565
- * `planApply.applyWorkflowName`.
43027
+ * Rejects a `GithubWorkflow` that is not an apply workflow: attaching to an
43028
+ * arbitrary one would add apply jobs depending on a `validate-plan` job that
43029
+ * does not exist there, which GitHub rejects only at run time.
42566
43030
  */
42567
- this.createApplyWorkflow = (github, homeRepositoryCondition) => {
42568
- const { applyWorkflowName, requireCurrentHead, verifyArtifactDigest } = this.planApplyOptions;
42569
- if (github.tryFindWorkflow(applyWorkflowName)) {
43031
+ this.attachApplyWorkflow = (workflow) => {
43032
+ const shared = ApplyWorkflow.of(this.rootProject, workflow);
43033
+ if (!shared) {
42570
43034
  throw new Error(
42571
- `AwsDeployWorkflow cannot create the apply workflow "${applyWorkflowName}" because a workflow with that name already exists. Set \`planApply.applyWorkflowName\` to something unique.`
43035
+ `AwsDeployWorkflow cannot attach to the workflow "${workflow.name}" because it is not an apply workflow. Pass the \`applyWorkflow\` of an AwsDeployWorkflow built with the \`plan-apply\` gate.`
42572
43036
  );
42573
43037
  }
42574
- const workflow = new import_github6.GithubWorkflow(github, applyWorkflowName);
42575
- workflow.on({
42576
- workflowDispatch: {
42577
- inputs: {
42578
- [PLAN_RUN_ID_INPUT]: {
42579
- description: "Run id of the plan workflow run to apply. Its build artifact is deployed verbatim.",
42580
- required: true,
42581
- type: "string"
42582
- }
42583
- }
42584
- }
43038
+ shared.attach({
43039
+ planWorkflowName: this.buildWorkflow.name,
43040
+ requireCurrentHead: this.planApplyOptions.requireCurrentHead,
43041
+ verifyArtifactDigest: this.planApplyOptions.verifyArtifactDigest,
43042
+ awsDeploymentTargets: this.awsDeploymentTargets,
43043
+ componentName: "AwsDeployWorkflow"
42585
43044
  });
42586
- const allowedBranches = Array.from(
42587
- new Set(
42588
- this.awsDeploymentTargets.flatMap(
42589
- (target) => target.branches.map((b) => b.branch)
42590
- )
42591
- )
42592
- );
42593
- workflow.addJob(VALIDATE_PLAN_JOB_ID, {
42594
- name: "Validate plan run",
42595
- runsOn: ["ubuntu-latest"],
42596
- permissions: {
42597
- actions: import_workflows_model5.JobPermission.READ,
42598
- contents: import_workflows_model5.JobPermission.READ
42599
- },
42600
- ...homeRepositoryCondition ? { if: `\${{ ${homeRepositoryCondition} }}` } : {},
42601
- outputs: {
42602
- head_sha: { stepId: "validate_plan_run", outputName: "head_sha" },
42603
- head_branch: { stepId: "validate_plan_run", outputName: "head_branch" },
42604
- ...verifyArtifactDigest ? {
42605
- artifact_digest: {
42606
- stepId: "validate_plan_run",
42607
- outputName: "artifact_digest"
42608
- }
42609
- } : {}
42610
- },
42611
- steps: [
42612
- {
42613
- name: "Validate plan run",
42614
- id: "validate_plan_run",
42615
- uses: "actions/github-script@v9",
42616
- /**
42617
- * The dispatch input reaches the script through the environment
42618
- * rather than a `${{ }}` interpolation, so a crafted run id cannot
42619
- * inject JavaScript into the validation itself.
42620
- */
42621
- env: {
42622
- PLAN_RUN_ID: `\${{ inputs.${PLAN_RUN_ID_INPUT} }}`
42623
- },
42624
- with: {
42625
- script: renderPlanValidationScript({
42626
- planWorkflowPath: `.github/workflows/${this.buildWorkflow.name}.yml`,
42627
- allowedBranches,
42628
- requireCurrentHead,
42629
- verifyArtifactDigest
42630
- })
42631
- }
42632
- }
42633
- ]
42634
- });
42635
- return workflow;
43045
+ return shared;
42636
43046
  };
42637
43047
  /**
42638
43048
  * Register one target's apply job on the apply workflow.
@@ -42667,9 +43077,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42667
43077
  ],
42668
43078
  runsOn: ["ubuntu-latest"],
42669
43079
  permissions: {
42670
- actions: import_workflows_model5.JobPermission.READ,
42671
- contents: import_workflows_model5.JobPermission.READ,
42672
- idToken: import_workflows_model5.JobPermission.WRITE
43080
+ actions: import_workflows_model7.JobPermission.READ,
43081
+ contents: import_workflows_model7.JobPermission.READ,
43082
+ idToken: import_workflows_model7.JobPermission.WRITE
42673
43083
  },
42674
43084
  ...this.environmentName ? { environment: this.environmentName } : void 0,
42675
43085
  /**
@@ -42859,9 +43269,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42859
43269
  const { roleArn, stackPattern } = ciDeploymentConfig ?? {};
42860
43270
  const { rootCdkOut, cdkCli } = awsDeploymentConfig;
42861
43271
  const label = `${awsStageType}/${deploymentTargetRole}/${account}/${region}`;
42862
- const artifactName = this.buildDiffArtifactName(target);
42863
- const capturedFileGuard = `always() && hashFiles('${DIFF_OUTPUT_FILE}') != ''`;
42864
- const runUrl = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
43272
+ const outputFile = this.buildDiffOutputFile(target);
42865
43273
  return [
42866
43274
  ...this.setupPnpm(),
42867
43275
  ...this.setupNode(),
@@ -42881,69 +43289,36 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42881
43289
  /**
42882
43290
  * Run CDK Diff, capturing the output while leaving it visible in the job
42883
43291
  * log. `pipefail` keeps the CLI's exit code from being masked by `tee`.
43292
+ *
43293
+ * The capture lands under the same path the report job will merge it
43294
+ * back to, so a target's file name is identical in both places.
42884
43295
  */
42885
43296
  {
42886
43297
  name: `Diff ${label}`,
42887
43298
  shell: "bash",
42888
43299
  run: [
42889
43300
  "set -o pipefail",
43301
+ `mkdir -p ${DIFF_OUTPUT_DIRECTORY}`,
42890
43302
  `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkDiff({
42891
43303
  ...cdkCli.diffOptionsFor(target),
42892
43304
  app: rootCdkOut,
42893
43305
  ci: true,
42894
43306
  noColor: true,
42895
43307
  stackPatterns: stackPattern ? [stackPattern] : void 0
42896
- })} 2>&1 | tee ${DIFF_OUTPUT_FILE}`
43308
+ })} 2>&1 | tee ${outputFile}`
42897
43309
  ].join("\n")
42898
43310
  },
42899
43311
  /**
42900
- * Render the captured diff into the job summary inside a collapsed
42901
- * `<details>` block, truncated below GitHub's 1 MiB per-step cap — a
42902
- * summary that exceeds the cap is dropped in full rather than clipped.
43312
+ * Hand the capture to the report job. Guarded on `!cancelled()` rather
43313
+ * than success, so a diff that exits non-zero — a synth error, or a
43314
+ * consumer who opted into `--fail` still contributes what it captured
43315
+ * instead of leaving a hole in the report.
42903
43316
  */
42904
- ...this.diffOptions.summary ? [
42905
- {
42906
- name: `Render diff summary ${label}`,
42907
- if: capturedFileGuard,
42908
- shell: "bash",
42909
- run: [
42910
- `size=$(wc -c < ${DIFF_OUTPUT_FILE})`,
42911
- "{",
42912
- ` echo '## cdk diff \u2014 ${label}'`,
42913
- ' echo ""',
42914
- " echo '<details><summary>cdk diff output</summary>'",
42915
- ' echo ""',
42916
- " echo '```'",
42917
- ` if [ "$size" -gt ${DIFF_SUMMARY_BYTE_LIMIT} ]; then`,
42918
- ` head -c ${DIFF_SUMMARY_BYTE_LIMIT} ${DIFF_OUTPUT_FILE}`,
42919
- ` printf '\\n... truncated at ${DIFF_SUMMARY_BYTE_LIMIT} bytes ...\\n'`,
42920
- " else",
42921
- ` cat ${DIFF_OUTPUT_FILE}`,
42922
- " fi",
42923
- " echo '```'",
42924
- ' echo ""',
42925
- " echo '</details>'",
42926
- ...this.diffOptions.artifact ? [
42927
- ' echo ""',
42928
- ` echo 'Full diff: the \`${artifactName}\` artifact on [this run](${runUrl}).'`
42929
- ] : [],
42930
- '} >> "$GITHUB_STEP_SUMMARY"'
42931
- ].join("\n")
42932
- }
42933
- ] : [],
42934
- /**
42935
- * Upload the untruncated diff as a per-run artifact.
42936
- */
42937
- ...this.diffOptions.artifact ? [
42938
- import_github6.WorkflowSteps.uploadArtifact({
42939
- name: `Upload diff ${label}`,
42940
- if: capturedFileGuard,
42941
- with: {
42942
- name: artifactName,
42943
- path: DIFF_OUTPUT_FILE
42944
- }
42945
- })
42946
- ] : []
43317
+ renderDiffPartUploadStep({
43318
+ label,
43319
+ artifactName: this.buildDiffPartArtifactName(target),
43320
+ outputFile
43321
+ })
42947
43322
  ];
42948
43323
  };
42949
43324
  if (!(project.root instanceof MonorepoProject)) {
@@ -42952,7 +43327,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
42952
43327
  );
42953
43328
  }
42954
43329
  this.rootProject = project.root;
42955
- const github = import_github6.GitHub.of(this.rootProject);
43330
+ const github = import_github8.GitHub.of(this.rootProject);
42956
43331
  if (!github) {
42957
43332
  throw new Error(
42958
43333
  "AwsDeployWorkflow requires a GitHub component in the root project"
@@ -43009,14 +43384,20 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43009
43384
  buildTask: this.rootProject.buildTask,
43010
43385
  /**
43011
43386
  * Use push triggers based n the branch config for each environment.
43387
+ *
43388
+ * Deduped: targets in one workflow routinely share a branch — two prod
43389
+ * targets both default to `main` — and a repeated entry is only noise
43390
+ * in the generated file, since GitHub treats this as a filter list.
43012
43391
  */
43013
43392
  workflowTriggers: {
43014
43393
  push: {
43015
- branches: [
43016
- ...this.awsDeploymentTargets.flatMap(
43017
- (t) => t.branches.map((b) => b.branch)
43394
+ branches: Array.from(
43395
+ new Set(
43396
+ this.awsDeploymentTargets.flatMap(
43397
+ (t) => t.branches.map((b) => b.branch)
43398
+ )
43018
43399
  )
43019
- ]
43400
+ )
43020
43401
  },
43021
43402
  workflowDispatch: {},
43022
43403
  ...options.buildWorkflowOptions?.workflowTriggers
@@ -43059,16 +43440,40 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43059
43440
  ...buildWorkflowOptions?.preBuildSteps ?? []
43060
43441
  ]
43061
43442
  });
43443
+ WorkflowHomeRepository.require(this.rootProject, {
43444
+ workflow: this.buildWorkflow,
43445
+ workflowName: this.buildWorkflow.name,
43446
+ homeRepository: this.homeRepository,
43447
+ componentName: "AwsDeployWorkflow"
43448
+ });
43449
+ const sharedApplyWorkflow = options.planApply?.applyWorkflow;
43450
+ if (sharedApplyWorkflow && options.planApply?.applyWorkflowName) {
43451
+ throw new Error(
43452
+ "Cannot provide both planApply.applyWorkflow and planApply.applyWorkflowName: the workflow being joined already has a name."
43453
+ );
43454
+ }
43062
43455
  this.planApplyOptions = {
43063
- applyWorkflowName: options.planApply?.applyWorkflowName ?? `${this.buildWorkflow.name}-apply`,
43456
+ applyWorkflowName: sharedApplyWorkflow?.name ?? options.planApply?.applyWorkflowName ?? `${this.buildWorkflow.name}-apply`,
43064
43457
  requireCurrentHead: options.planApply?.requireCurrentHead ?? false,
43065
43458
  verifyArtifactDigest: options.planApply?.verifyArtifactDigest ?? false
43066
43459
  };
43067
43460
  if (isPlanApply) {
43068
- this.applyWorkflow = this.createApplyWorkflow(
43069
- github,
43070
- homeRepositoryCondition
43071
- );
43461
+ const applyWorkflow = sharedApplyWorkflow ? this.attachApplyWorkflow(sharedApplyWorkflow) : new ApplyWorkflow(this.rootProject, github, {
43462
+ applyWorkflowName: this.planApplyOptions.applyWorkflowName,
43463
+ planWorkflowName: this.buildWorkflow.name,
43464
+ requireCurrentHead: this.planApplyOptions.requireCurrentHead,
43465
+ verifyArtifactDigest: this.planApplyOptions.verifyArtifactDigest,
43466
+ homeRepositoryCondition,
43467
+ awsDeploymentTargets: this.awsDeploymentTargets,
43468
+ componentName: "AwsDeployWorkflow"
43469
+ });
43470
+ this.applyWorkflow = applyWorkflow.workflow;
43471
+ WorkflowHomeRepository.require(this.rootProject, {
43472
+ workflow: applyWorkflow.workflow,
43473
+ workflowName: this.planApplyOptions.applyWorkflowName,
43474
+ homeRepository: this.homeRepository,
43475
+ componentName: "AwsDeployWorkflow"
43476
+ });
43072
43477
  }
43073
43478
  this.awsDeploymentTargets.forEach((target) => {
43074
43479
  const deployJobName = this.buildJobName(target);
@@ -43088,16 +43493,16 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43088
43493
  name: `Deploy ${this.project.name} ${target.awsStageType}/${target.deploymentTargetRole}/${target.account}/${target.region}`,
43089
43494
  needs: [
43090
43495
  "build",
43091
- ...gatedOnDiff ? [this.buildDiffJobName(target)] : [],
43496
+ ...gatedOnDiff ? [DIFF_REPORT_JOB_ID] : [],
43092
43497
  ...this.deployAfterTargets.map((p) => {
43093
43498
  return this.buildJobName(p);
43094
43499
  })
43095
43500
  ],
43096
43501
  runsOn: ["ubuntu-latest"],
43097
43502
  permissions: {
43098
- contents: import_workflows_model5.JobPermission.READ,
43099
- idToken: import_workflows_model5.JobPermission.WRITE,
43100
- pullRequests: import_workflows_model5.JobPermission.WRITE
43503
+ contents: import_workflows_model7.JobPermission.READ,
43504
+ idToken: import_workflows_model7.JobPermission.WRITE,
43505
+ pullRequests: import_workflows_model7.JobPermission.WRITE
43101
43506
  },
43102
43507
  ...this.environmentName ? { environment: this.environmentName } : void 0,
43103
43508
  concurrency: deployJobName,
@@ -43112,14 +43517,28 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43112
43517
  needs: ["build"],
43113
43518
  runsOn: ["ubuntu-latest"],
43114
43519
  permissions: {
43115
- contents: import_workflows_model5.JobPermission.READ,
43116
- idToken: import_workflows_model5.JobPermission.WRITE
43520
+ contents: import_workflows_model7.JobPermission.READ,
43521
+ idToken: import_workflows_model7.JobPermission.WRITE
43117
43522
  },
43118
43523
  if: jobCondition,
43119
43524
  steps: [...this.diffSteps(target)]
43120
43525
  });
43121
43526
  }
43122
43527
  });
43528
+ if (this.diffOptions.enabled && this.awsDeploymentTargets.length > 0) {
43529
+ DiffReportJob.attach(this.rootProject, {
43530
+ buildWorkflow: this.buildWorkflow,
43531
+ homeRepositoryCondition,
43532
+ summary: this.diffOptions.summary,
43533
+ artifact: this.diffOptions.artifact,
43534
+ targets: this.awsDeploymentTargets.map((target) => ({
43535
+ label: `${this.project.name} ${target.awsStageType}/${target.deploymentTargetRole}/${target.account}/${target.region}`,
43536
+ jobName: this.buildDiffJobName(target),
43537
+ outputFile: this.buildDiffOutputFile(target),
43538
+ branchCondition: this.buildBranchFilterCondition(target.branches)
43539
+ }))
43540
+ });
43541
+ }
43123
43542
  addBuildCompleteJob(this.buildWorkflow);
43124
43543
  }
43125
43544
  static of(project, buildWorkflow) {
@@ -43133,7 +43552,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43133
43552
  name: "Build Sub Projects",
43134
43553
  run: `pnpm exec projen ${ROOT_CI_TASK_NAME}`
43135
43554
  },
43136
- import_github6.WorkflowSteps.uploadArtifact({
43555
+ import_github8.WorkflowSteps.uploadArtifact({
43137
43556
  name: "Upload Turbo runs",
43138
43557
  if: "always()",
43139
43558
  continueOnError: true,
@@ -43149,9 +43568,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen26.Compone
43149
43568
  };
43150
43569
 
43151
43570
  // src/workflows/aws-teardown-workflow.ts
43152
- var import_projen27 = require("projen");
43153
- var import_github7 = require("projen/lib/github");
43154
- var import_workflows_model6 = require("projen/lib/github/workflows-model");
43571
+ var import_projen30 = require("projen");
43572
+ var import_github9 = require("projen/lib/github");
43573
+ var import_workflows_model8 = require("projen/lib/github/workflows-model");
43155
43574
  var DEFAULT_TEARDOWN_BRANCH_PATTERNS = [
43156
43575
  "feat/*",
43157
43576
  "fix/*",
@@ -43171,7 +43590,7 @@ var resolveBranchPatterns = (explicit, targets) => {
43171
43590
  }
43172
43591
  return [...DEFAULT_TEARDOWN_BRANCH_PATTERNS];
43173
43592
  };
43174
- var AwsTeardownWorkflow = class extends import_projen27.Component {
43593
+ var AwsTeardownWorkflow = class extends import_projen30.Component {
43175
43594
  constructor(rootProject, options) {
43176
43595
  super(rootProject);
43177
43596
  this.rootProject = rootProject;
@@ -43201,7 +43620,7 @@ var AwsTeardownWorkflow = class extends import_projen27.Component {
43201
43620
  "AwsTeardownWorkflow requires the root project to be a MonorepoProject"
43202
43621
  );
43203
43622
  }
43204
- const github = import_github7.GitHub.of(this.rootProject);
43623
+ const github = import_github9.GitHub.of(this.rootProject);
43205
43624
  if (!github) {
43206
43625
  throw new Error(
43207
43626
  "AwsTeardownWorkflow requires a GitHub component in the root project"
@@ -43215,7 +43634,7 @@ var AwsTeardownWorkflow = class extends import_projen27.Component {
43215
43634
  homeRepository,
43216
43635
  "AwsTeardownWorkflow"
43217
43636
  );
43218
- const workflow = new import_github7.GithubWorkflow(github, "teardown-dev");
43637
+ const workflow = new import_github9.GithubWorkflow(github, "teardown-dev");
43219
43638
  workflow.on({
43220
43639
  workflowDispatch: {},
43221
43640
  schedule: [
@@ -43243,8 +43662,8 @@ var AwsTeardownWorkflow = class extends import_projen27.Component {
43243
43662
  runsOn: ["ubuntu-latest"],
43244
43663
  ...homeRepositoryCondition ? { if: `\${{ ${homeRepositoryCondition} }}` } : {},
43245
43664
  permissions: {
43246
- contents: import_workflows_model6.JobPermission.READ,
43247
- idToken: import_workflows_model6.JobPermission.WRITE
43665
+ contents: import_workflows_model8.JobPermission.READ,
43666
+ idToken: import_workflows_model8.JobPermission.WRITE
43248
43667
  },
43249
43668
  env: {
43250
43669
  REPO: "${{ github.repository }}",
@@ -43359,7 +43778,7 @@ var AwsTeardownWorkflow = class extends import_projen27.Component {
43359
43778
  };
43360
43779
 
43361
43780
  // src/projects/aws-cdk-project.ts
43362
- var AwsCdkProject = class extends import_projen28.awscdk.AwsCdkTypeScriptApp {
43781
+ var AwsCdkProject = class extends import_projen31.awscdk.AwsCdkTypeScriptApp {
43363
43782
  constructor(userOptions) {
43364
43783
  if (!(userOptions.parent instanceof MonorepoProject)) {
43365
43784
  throw new Error(
@@ -43575,7 +43994,7 @@ var AwsCdkProject = class extends import_projen28.awscdk.AwsCdkTypeScriptApp {
43575
43994
  };
43576
43995
 
43577
43996
  // src/projects/react-vite-site-project.ts
43578
- var import_projen29 = require("projen");
43997
+ var import_projen32 = require("projen");
43579
43998
  var import_ts_deepmerge5 = require("ts-deepmerge");
43580
43999
  var ReactViteSiteProject = class extends TypeScriptProject {
43581
44000
  constructor(userOptions) {
@@ -43614,7 +44033,7 @@ var ReactViteSiteProject = class extends TypeScriptProject {
43614
44033
  };
43615
44034
  super(options);
43616
44035
  this.package.addField("type", "module");
43617
- new import_projen29.TextFile(this, ".nvmrc", { lines: ["v24.11.0"] });
44036
+ new import_projen32.TextFile(this, ".nvmrc", { lines: ["v24.11.0"] });
43618
44037
  this.tsconfig?.file.addOverride("compilerOptions.target", "ES2020");
43619
44038
  this.tsconfig?.file.addOverride("compilerOptions.lib", [
43620
44039
  "ES2020",
@@ -43649,7 +44068,7 @@ var ReactViteSiteProject = class extends TypeScriptProject {
43649
44068
  "vitest/globals",
43650
44069
  "vite/client"
43651
44070
  ]);
43652
- new import_projen29.SampleFile(this, "vite.config.ts", {
44071
+ new import_projen32.SampleFile(this, "vite.config.ts", {
43653
44072
  contents: `import { defineConfig } from 'vite';
43654
44073
  import react from '@vitejs/plugin-react';
43655
44074
  import tailwindcss from '@tailwindcss/vite';
@@ -43661,7 +44080,7 @@ export default defineConfig({
43661
44080
  });
43662
44081
  `
43663
44082
  });
43664
- new import_projen29.SampleFile(this, "src/vite-env.d.ts", {
44083
+ new import_projen32.SampleFile(this, "src/vite-env.d.ts", {
43665
44084
  contents: `/// <reference types="vite/client" />
43666
44085
 
43667
44086
  interface ImportMetaEnv {
@@ -43676,7 +44095,7 @@ interface ImportMeta {
43676
44095
  });
43677
44096
  if (options.testRunner !== TestRunner.JEST) {
43678
44097
  this.tryRemoveFile("vitest.config.ts");
43679
- new import_projen29.SampleFile(this, "vitest.config.ts", {
44098
+ new import_projen32.SampleFile(this, "vitest.config.ts", {
43680
44099
  contents: `import { defineConfig, mergeConfig } from 'vitest/config';
43681
44100
  import react from '@vitejs/plugin-react';
43682
44101
  import viteConfig from './vite.config';
@@ -43820,7 +44239,7 @@ export default mergeConfig(
43820
44239
  }
43821
44240
  if (userOptions.sampleCode === true) {
43822
44241
  const siteName = options.name;
43823
- new import_projen29.SampleFile(this, "index.html", {
44242
+ new import_projen32.SampleFile(this, "index.html", {
43824
44243
  contents: `<!doctype html>
43825
44244
  <html>
43826
44245
  <head>
@@ -43835,7 +44254,7 @@ export default mergeConfig(
43835
44254
  </html>
43836
44255
  `
43837
44256
  });
43838
- new import_projen29.SampleFile(this, "src/routes.tsx", {
44257
+ new import_projen32.SampleFile(this, "src/routes.tsx", {
43839
44258
  contents: `import { createBrowserRouter } from 'react-router-dom';
43840
44259
  import App from './App';
43841
44260
 
@@ -43844,7 +44263,7 @@ export const router = createBrowserRouter([
43844
44263
  ]);
43845
44264
  `
43846
44265
  });
43847
- new import_projen29.SampleFile(this, "src/main.tsx", {
44266
+ new import_projen32.SampleFile(this, "src/main.tsx", {
43848
44267
  contents: `import React from 'react';
43849
44268
  import ReactDOM from 'react-dom/client';
43850
44269
  import { RouterProvider } from 'react-router-dom';
@@ -43858,7 +44277,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
43858
44277
  );
43859
44278
  `
43860
44279
  });
43861
- new import_projen29.SampleFile(this, "src/App.tsx", {
44280
+ new import_projen32.SampleFile(this, "src/App.tsx", {
43862
44281
  contents: `export default function App() {
43863
44282
  return (
43864
44283
  <main className="p-4">
@@ -43868,11 +44287,11 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
43868
44287
  }
43869
44288
  `
43870
44289
  });
43871
- new import_projen29.SampleFile(this, "src/index.css", {
44290
+ new import_projen32.SampleFile(this, "src/index.css", {
43872
44291
  contents: `@import "tailwindcss";
43873
44292
  `
43874
44293
  });
43875
- new import_projen29.SampleFile(this, "src/setupTests.ts", {
44294
+ new import_projen32.SampleFile(this, "src/setupTests.ts", {
43876
44295
  contents: `import '@testing-library/jest-dom';
43877
44296
  `
43878
44297
  });
@@ -43881,7 +44300,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
43881
44300
  };
43882
44301
 
43883
44302
  // src/projects/starlight-project.ts
43884
- var import_projen30 = require("projen");
44303
+ var import_projen33 = require("projen");
43885
44304
  var STARLIGHT_ROLE = {
43886
44305
  DOCS: "docs",
43887
44306
  SITE: "site"
@@ -43923,10 +44342,10 @@ var StarlightProject = class extends AstroProject {
43923
44342
  turbo.compileTask.inputs.push("src/content/**");
43924
44343
  }
43925
44344
  if (userOptions.sampleContent === true) {
43926
- new import_projen30.SampleFile(this, "src/content/docs/index.mdx", {
44345
+ new import_projen33.SampleFile(this, "src/content/docs/index.mdx", {
43927
44346
  contents: DEFAULT_INDEX_MDX
43928
44347
  });
43929
- new import_projen30.SampleFile(this, "src/content.config.ts", {
44348
+ new import_projen33.SampleFile(this, "src/content.config.ts", {
43930
44349
  contents: DEFAULT_CONTENT_CONFIG_TS
43931
44350
  });
43932
44351
  }
@@ -44009,6 +44428,7 @@ export const collections = {
44009
44428
  AUDIT_CATEGORY_ORDER,
44010
44429
  AgentConfig,
44011
44430
  ApiExtractor,
44431
+ ApplyWorkflow,
44012
44432
  AstroConfig,
44013
44433
  AstroOutput,
44014
44434
  AstroProject,
@@ -44104,7 +44524,12 @@ export const collections = {
44104
44524
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
44105
44525
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
44106
44526
  DEPLOY_GATE,
44527
+ DIFF_ARTIFACT_NAME,
44528
+ DIFF_OUTPUT_DIRECTORY,
44529
+ DIFF_PART_ARTIFACT_PREFIX,
44530
+ DIFF_REPORT_JOB_ID,
44107
44531
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
44532
+ DiffReportJob,
44108
44533
  GITHUB_ISSUE_TYPES,
44109
44534
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
44110
44535
  ISSUE_TEMPLATES_GENERATED_SUFFIX,
@@ -44163,6 +44588,7 @@ export const collections = {
44163
44588
  VERSION_NPM_PACKAGES,
44164
44589
  VSCodeConfig,
44165
44590
  Vitest,
44591
+ WorkflowHomeRepository,
44166
44592
  addApproveMergeUpgradeWorkflow,
44167
44593
  addBuildCompleteJob,
44168
44594
  addPlaywright,
@@ -44283,6 +44709,7 @@ export const collections = {
44283
44709
  renderCheckLinksProcedure,
44284
44710
  renderCustomDocSectionBlock,
44285
44711
  renderCustomDocSections,
44712
+ renderDiffPartUploadStep,
44286
44713
  renderExtractApiProcedure,
44287
44714
  renderFocusSection,
44288
44715
  renderGithubIssueTypeSection,
@@ -44314,6 +44741,8 @@ export const collections = {
44314
44741
  renderScopeGateSection,
44315
44742
  renderScopeGateShellHelpers,
44316
44743
  renderSetIssueTypeFallbackLines,
44744
+ renderSetupNode,
44745
+ renderSetupPnpm,
44317
44746
  renderSharedEditingBundleHook,
44318
44747
  renderSharedEditingHelperScript,
44319
44748
  renderSharedEditingRuleContent,