@codedrifters/configulator 0.0.429 → 0.0.431

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.mjs CHANGED
@@ -41927,10 +41927,10 @@ import { merge as merge4 } from "ts-deepmerge";
41927
41927
 
41928
41928
  // src/workflows/aws-deploy-workflow.ts
41929
41929
  var import_utils11 = __toESM(require_lib());
41930
- import { Component as Component24 } from "projen";
41930
+ import { Component as Component25 } from "projen";
41931
41931
  import { BuildWorkflow } from "projen/lib/build";
41932
- import { GitHub as GitHub6, WorkflowSteps as WorkflowSteps2 } from "projen/lib/github";
41933
- import { JobPermission as JobPermission6 } from "projen/lib/github/workflows-model";
41932
+ import { GitHub as GitHub6, WorkflowSteps as WorkflowSteps3 } from "projen/lib/github";
41933
+ import { JobPermission as JobPermission7 } from "projen/lib/github/workflows-model";
41934
41934
 
41935
41935
  // src/workflows/apply-workflow.ts
41936
41936
  import { Component as Component22 } from "projen";
@@ -42222,8 +42222,260 @@ var resolveEnvironmentGate = (gate, environmentName, componentName) => {
42222
42222
  return trimmed;
42223
42223
  };
42224
42224
 
42225
- // src/workflows/home-repository.ts
42225
+ // src/workflows/diff-report-job.ts
42226
42226
  import { Component as Component23 } from "projen";
42227
+ import { WorkflowSteps as WorkflowSteps2 } from "projen/lib/github";
42228
+ import { JobPermission as JobPermission6 } from "projen/lib/github/workflows-model";
42229
+ var DIFF_REPORT_JOB_ID = "diff-report";
42230
+ var DIFF_OUTPUT_DIRECTORY = "cdk-diff";
42231
+ var DIFF_PART_ARTIFACT_PREFIX = "cdk-diff-part";
42232
+ var DIFF_ARTIFACT_NAME = "cdk-diff";
42233
+ var DIFF_PART_RETENTION_DAYS = 1;
42234
+ var DIFF_SUMMARY_BYTE_LIMIT = 9e5;
42235
+ var RUN_URL = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
42236
+ var DiffReportJob = class _DiffReportJob extends Component23 {
42237
+ constructor(project, options) {
42238
+ super(project);
42239
+ /**
42240
+ * Every contributed target, in attach order.
42241
+ */
42242
+ this.targets = [];
42243
+ /**
42244
+ * Publish settings are OR-ed rather than first-wins: one report means one
42245
+ * answer, and a component that asked for a summary should get one even when
42246
+ * a sibling did not.
42247
+ */
42248
+ this.summary = false;
42249
+ this.artifact = false;
42250
+ /**
42251
+ * Fold one component's contribution in.
42252
+ */
42253
+ this.record = (options) => {
42254
+ this.targets.push(...options.targets);
42255
+ this.summary = this.summary || options.summary;
42256
+ this.artifact = this.artifact || options.artifact;
42257
+ };
42258
+ /**
42259
+ * Compose the report's own condition.
42260
+ *
42261
+ * `!cancelled()` leads, and it is load-bearing: a job whose `needs` failed or
42262
+ * were skipped is skipped by default, so without a status function the report
42263
+ * would vanish in exactly the cases it is most wanted — one target's diff
42264
+ * failing, or some targets not applying to this branch.
42265
+ */
42266
+ this.renderJobCondition = () => {
42267
+ const branchConditions = this.targets.map(
42268
+ (target) => target.branchCondition
42269
+ );
42270
+ const anyBranch = branchConditions.some((condition) => !condition);
42271
+ const branchClause = anyBranch ? void 0 : Array.from(new Set(branchConditions)).join(" || ");
42272
+ return "${{ " + [
42273
+ "!cancelled()",
42274
+ "!needs.build.outputs.self_mutation_happened",
42275
+ ...this.homeRepositoryCondition ? [this.homeRepositoryCondition] : [],
42276
+ ...branchClause ? [`(${branchClause})`] : []
42277
+ ].join(" && ") + " }}";
42278
+ };
42279
+ /**
42280
+ * Pull every target's part into one directory.
42281
+ *
42282
+ * `continueOnError` because a run where every target's diff job was skipped
42283
+ * by its branch filter matches no parts at all, and a report with nothing to
42284
+ * report is not a failure.
42285
+ */
42286
+ this.renderDownloadStep = () => {
42287
+ return [
42288
+ WorkflowSteps2.downloadArtifact({
42289
+ name: "Download target diffs",
42290
+ continueOnError: true,
42291
+ with: {
42292
+ pattern: `${DIFF_PART_ARTIFACT_PREFIX}-*`,
42293
+ mergeMultiple: true,
42294
+ path: DIFF_OUTPUT_DIRECTORY
42295
+ }
42296
+ })
42297
+ ];
42298
+ };
42299
+ /**
42300
+ * Render every target's diff into the job summary, one collapsed `<details>`
42301
+ * block per target.
42302
+ *
42303
+ * A target whose part is absent renders as "did not run", which is what
42304
+ * distinguishes a target that was skipped or whose diff failed before it
42305
+ * captured anything from one that simply had no changes — the latter has a
42306
+ * file, carrying the CDK CLI's own no-differences wording, rather than being
42307
+ * detected by matching on it.
42308
+ */
42309
+ this.renderSummaryStep = () => {
42310
+ if (!this.summary || this.targets.length === 0) {
42311
+ return [];
42312
+ }
42313
+ const budget = Math.floor(DIFF_SUMMARY_BYTE_LIMIT / this.targets.length);
42314
+ const rows = this.targets.map(
42315
+ (target) => `'${target.label}|${target.outputFile}'`
42316
+ );
42317
+ return [
42318
+ {
42319
+ name: "Render diff report",
42320
+ if: "!cancelled()",
42321
+ shell: "bash",
42322
+ run: [
42323
+ "{",
42324
+ " while IFS='|' read -r label file; do",
42325
+ ' echo "## cdk diff \u2014 $label"',
42326
+ ' echo ""',
42327
+ ' if [ ! -f "$file" ]; then',
42328
+ " echo '_Did not run._'",
42329
+ ' echo ""',
42330
+ " continue",
42331
+ " fi",
42332
+ " echo '<details><summary>cdk diff output</summary>'",
42333
+ ' echo ""',
42334
+ " echo '```'",
42335
+ ` if [ "$(wc -c < "$file")" -gt ${budget} ]; then`,
42336
+ ` head -c ${budget} "$file"`,
42337
+ ` printf '\\n... truncated at ${budget} bytes ...\\n'`,
42338
+ " else",
42339
+ ' cat "$file"',
42340
+ " fi",
42341
+ " echo '```'",
42342
+ ' echo ""',
42343
+ " echo '</details>'",
42344
+ ' echo ""',
42345
+ ` done < <(printf '%s\\n' ${rows.join(" ")})`,
42346
+ ...this.artifact ? [
42347
+ ` echo 'Full diff: the \`${DIFF_ARTIFACT_NAME}\` artifact on [this run](${RUN_URL}).'`
42348
+ ] : [],
42349
+ '} >> "$GITHUB_STEP_SUMMARY"'
42350
+ ].join("\n")
42351
+ }
42352
+ ];
42353
+ };
42354
+ /**
42355
+ * Fail the report when any target's diff failed.
42356
+ *
42357
+ * Runs last, so the summary and the artifact are published first — a reviewer
42358
+ * still gets the whole picture, including whatever the failing target managed
42359
+ * to capture. But the report must not end up green: everything gated behind
42360
+ * it `needs` it, and a job whose `needs` failed is skipped, which is what
42361
+ * keeps a failed diff from letting a gated deploy through to *Waiting for
42362
+ * approval*. Without this the report would always succeed and a diff failure
42363
+ * would only soft-block behind a human.
42364
+ *
42365
+ * Only `failure` counts. A target skipped by its branch filter reports
42366
+ * `skipped`, which is normal and must not fail the report.
42367
+ */
42368
+ this.renderFailureStep = () => {
42369
+ const jobNames = Array.from(
42370
+ new Set(this.targets.map((target) => target.jobName))
42371
+ );
42372
+ if (jobNames.length === 0) {
42373
+ return [];
42374
+ }
42375
+ return [
42376
+ {
42377
+ name: "Fail if any target's diff failed",
42378
+ if: "!cancelled()",
42379
+ shell: "bash",
42380
+ run: [
42381
+ ...jobNames.map(
42382
+ (jobName) => `if [ "\${{ needs.${jobName}.result }}" = "failure" ]; then echo "::error::Diff job ${jobName} failed"; failed=1; fi`
42383
+ ),
42384
+ 'if [ "${failed:-0}" = "1" ]; then exit 1; fi'
42385
+ ].join("\n")
42386
+ }
42387
+ ];
42388
+ };
42389
+ /**
42390
+ * Upload the merged directory as the one artifact meant to be read.
42391
+ */
42392
+ this.renderUploadStep = () => {
42393
+ if (!this.artifact || this.targets.length === 0) {
42394
+ return [];
42395
+ }
42396
+ return [
42397
+ WorkflowSteps2.uploadArtifact({
42398
+ name: "Upload combined diff",
42399
+ if: "!cancelled()",
42400
+ with: {
42401
+ name: DIFF_ARTIFACT_NAME,
42402
+ path: DIFF_OUTPUT_DIRECTORY,
42403
+ ifNoFilesFound: "ignore"
42404
+ }
42405
+ })
42406
+ ];
42407
+ };
42408
+ this.buildWorkflow = options.buildWorkflow;
42409
+ this.homeRepositoryCondition = options.homeRepositoryCondition;
42410
+ this.record(options);
42411
+ this.buildWorkflow.workflow.addJob(DIFF_REPORT_JOB_ID, {
42412
+ name: "Diff report",
42413
+ needs: ["build"],
42414
+ runsOn: ["ubuntu-latest"],
42415
+ permissions: {
42416
+ contents: JobPermission6.READ
42417
+ },
42418
+ steps: []
42419
+ });
42420
+ }
42421
+ static of(project, buildWorkflow) {
42422
+ const isDefined = (c) => c instanceof _DiffReportJob && c.buildWorkflow === buildWorkflow;
42423
+ return project.components.find(isDefined);
42424
+ }
42425
+ /**
42426
+ * Add a component's targets to the plan workflow's diff report, creating the
42427
+ * report job the first time it is called for that workflow.
42428
+ */
42429
+ static attach(project, options) {
42430
+ const existing = _DiffReportJob.of(project, options.buildWorkflow);
42431
+ if (existing) {
42432
+ existing.record(options);
42433
+ return existing;
42434
+ }
42435
+ return new _DiffReportJob(project, options);
42436
+ }
42437
+ preSynthesize() {
42438
+ this.buildWorkflow.workflow.updateJob(DIFF_REPORT_JOB_ID, {
42439
+ name: "Diff report",
42440
+ /**
42441
+ * `build` is a direct dependency so the job can read its
42442
+ * self-mutation output, which is otherwise unreachable — the `needs`
42443
+ * context only carries direct dependencies, not transitive ones.
42444
+ */
42445
+ needs: [
42446
+ "build",
42447
+ ...Array.from(new Set(this.targets.map((target) => target.jobName)))
42448
+ ],
42449
+ runsOn: ["ubuntu-latest"],
42450
+ permissions: {
42451
+ contents: JobPermission6.READ
42452
+ },
42453
+ if: this.renderJobCondition(),
42454
+ steps: [
42455
+ ...this.renderDownloadStep(),
42456
+ ...this.renderSummaryStep(),
42457
+ ...this.renderUploadStep(),
42458
+ ...this.renderFailureStep()
42459
+ ]
42460
+ });
42461
+ super.preSynthesize();
42462
+ }
42463
+ };
42464
+ var renderDiffPartUploadStep = (options) => {
42465
+ return WorkflowSteps2.uploadArtifact({
42466
+ name: `Upload diff ${options.label}`,
42467
+ if: "!cancelled()",
42468
+ with: {
42469
+ name: options.artifactName,
42470
+ path: options.outputFile,
42471
+ ifNoFilesFound: "ignore",
42472
+ retentionDays: DIFF_PART_RETENTION_DAYS
42473
+ }
42474
+ });
42475
+ };
42476
+
42477
+ // src/workflows/home-repository.ts
42478
+ import { Component as Component24 } from "projen";
42227
42479
  var HOME_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
42228
42480
  var renderHomeRepositoryCondition = (homeRepository, componentName) => {
42229
42481
  if (homeRepository === void 0) {
@@ -42237,7 +42489,7 @@ var renderHomeRepositoryCondition = (homeRepository, componentName) => {
42237
42489
  return `github.repository == '${homeRepository}'`;
42238
42490
  };
42239
42491
  var describePin = (homeRepository) => homeRepository === void 0 ? "unset" : `"${homeRepository}"`;
42240
- var WorkflowHomeRepository = class _WorkflowHomeRepository extends Component23 {
42492
+ var WorkflowHomeRepository = class _WorkflowHomeRepository extends Component24 {
42241
42493
  static of(project, workflow) {
42242
42494
  const isDefined = (c) => c instanceof _WorkflowHomeRepository && c.workflow === workflow;
42243
42495
  return project.components.find(isDefined);
@@ -42269,11 +42521,35 @@ var WorkflowHomeRepository = class _WorkflowHomeRepository extends Component23 {
42269
42521
  }
42270
42522
  };
42271
42523
 
42524
+ // src/workflows/node-setup.ts
42525
+ var renderSetupNode = () => {
42526
+ return [
42527
+ {
42528
+ name: "Setup Node",
42529
+ uses: `actions/setup-node@${VERSION.SETUP_NODE_ACTION_VERSION}`,
42530
+ with: {
42531
+ ["node-version"]: VERSION.NODE_WORKFLOWS
42532
+ },
42533
+ timeoutMinutes: 1
42534
+ }
42535
+ ];
42536
+ };
42537
+ var renderSetupPnpm = (pnpmVersion) => {
42538
+ return [
42539
+ {
42540
+ name: "Setup PNPM",
42541
+ uses: `pnpm/action-setup@${VERSION.PNPM_ACTION_SETUP_VERSION}`,
42542
+ with: {
42543
+ version: pnpmVersion
42544
+ }
42545
+ }
42546
+ ];
42547
+ };
42548
+
42272
42549
  // src/workflows/aws-deploy-workflow.ts
42273
42550
  var PROD_DEPLOY_NAME = "prod-deploy";
42274
- var DIFF_OUTPUT_FILE = "cdk-diff.txt";
42275
- var DIFF_SUMMARY_BYTE_LIMIT = 9e5;
42276
- var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42551
+ var BRANCH_NAME_EXPRESSION = "${{ github.head_ref || github.ref_name }}";
42552
+ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42277
42553
  constructor(project, options = {}) {
42278
42554
  super(project);
42279
42555
  this.project = project;
@@ -42285,30 +42561,8 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42285
42561
  * @default 'primary' (this is the only type supported currently)
42286
42562
  */
42287
42563
  this.awsEnvironmentType = import_utils11.DEPLOYMENT_TARGET_ROLE.PRIMARY;
42288
- this.setupNode = () => {
42289
- return [
42290
- {
42291
- name: "Setup Node",
42292
- uses: `actions/setup-node@${VERSION.SETUP_NODE_ACTION_VERSION}`,
42293
- with: {
42294
- ["node-version"]: VERSION.NODE_WORKFLOWS
42295
- },
42296
- // occasionally this step fails due to internal issues at github
42297
- timeoutMinutes: 1
42298
- }
42299
- ];
42300
- };
42301
- this.setupPnpm = () => {
42302
- return [
42303
- {
42304
- name: "Setup PNPM",
42305
- uses: `pnpm/action-setup@${VERSION.PNPM_ACTION_SETUP_VERSION}`,
42306
- with: {
42307
- version: this.rootProject.pnpmVersion
42308
- }
42309
- }
42310
- ];
42311
- };
42564
+ this.setupNode = () => renderSetupNode();
42565
+ this.setupPnpm = () => renderSetupPnpm(this.rootProject.pnpmVersion);
42312
42566
  /**
42313
42567
  * Build the deterministic GitHub Actions job name for a deploy target.
42314
42568
  *
@@ -42326,45 +42580,65 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42326
42580
  ].join("-");
42327
42581
  };
42328
42582
  /**
42329
- * Build the deterministic GitHub Actions job name for a target's `cdk diff`
42330
- * job. Mirrors {@link buildJobName} with a `diff` verb so the diff and deploy
42331
- * jobs for one target sort next to each other in the run view.
42583
+ * Build a deploy job's `concurrency` setting from its job name.
42584
+ *
42585
+ * Two shapes, deliberately:
42586
+ *
42587
+ * - Unscoped (the default) stays the bare string the job has always carried,
42588
+ * so an existing consumer's workflow is byte-identical.
42589
+ * - Branch-scoped spells out `cancel-in-progress: false` rather than leaning
42590
+ * on GitHub's default, the same way the apply job does. A group keyed on
42591
+ * the branch reads like a "cancel superseded pushes" group, and that is the
42592
+ * one thing it must not be — cancelling a half-finished `cdk deploy`
42593
+ * strands a CloudFormation stack mid-update.
42332
42594
  */
42333
- this.buildDiffJobName = (target) => {
42595
+ this.buildDeployConcurrency = (deployJobName) => {
42596
+ if (!this.branchScopedConcurrency) {
42597
+ return deployJobName;
42598
+ }
42599
+ return {
42600
+ group: `${deployJobName}-${BRANCH_NAME_EXPRESSION}`,
42601
+ "cancel-in-progress": false
42602
+ };
42603
+ };
42604
+ /**
42605
+ * Build the deterministic GitHub Actions job name for a target's apply job.
42606
+ * Mirrors {@link buildJobName} with an `apply` verb.
42607
+ */
42608
+ this.buildApplyJobName = (target) => {
42334
42609
  return [
42335
42610
  target.awsStageType,
42336
42611
  target.deploymentTargetRole,
42337
- "diff",
42612
+ "apply",
42338
42613
  target.project.name,
42339
42614
  target.account,
42340
42615
  target.region
42341
42616
  ].join("-");
42342
42617
  };
42343
42618
  /**
42344
- * Build the deterministic GitHub Actions job name for a target's apply job.
42345
- * Mirrors {@link buildJobName} with an `apply` verb.
42619
+ * Build the deterministic GitHub Actions job name for a target's `cdk diff`
42620
+ * job. Mirrors {@link buildJobName} with a `diff` verb so the diff and deploy
42621
+ * jobs for one target sort next to each other in the run view.
42346
42622
  */
42347
- this.buildApplyJobName = (target) => {
42623
+ this.buildDiffJobName = (target) => {
42348
42624
  return [
42349
42625
  target.awsStageType,
42350
42626
  target.deploymentTargetRole,
42351
- "apply",
42627
+ "diff",
42352
42628
  target.project.name,
42353
42629
  target.account,
42354
42630
  target.region
42355
42631
  ].join("-");
42356
42632
  };
42357
42633
  /**
42358
- * Build the CI artifact name for a target's captured diff.
42634
+ * Build the slug that makes a target unique among every target in the plan
42635
+ * workflow, across every deploy component feeding it.
42359
42636
  *
42360
- * Carries every component that makes a target unique — including
42361
- * `deploymentTargetRole`, which two otherwise-identical targets can differ
42362
- * on. `actions/upload-artifact` v4+ treats artifacts as immutable and rejects
42363
- * a duplicate name within a run, so parallel diff jobs cannot share one.
42637
+ * Includes `deploymentTargetRole`, which two otherwise-identical targets can
42638
+ * differ on.
42364
42639
  */
42365
- this.buildDiffArtifactName = (target) => {
42640
+ this.buildDiffSlug = (target) => {
42366
42641
  return [
42367
- "cdk-diff",
42368
42642
  target.awsStageType,
42369
42643
  target.deploymentTargetRole,
42370
42644
  target.project.name,
@@ -42372,6 +42646,23 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42372
42646
  target.region
42373
42647
  ].join("-");
42374
42648
  };
42649
+ /**
42650
+ * Build the file a target's captured diff is written to, both in its own diff
42651
+ * job's workspace and in the merged directory the report job assembles.
42652
+ */
42653
+ this.buildDiffOutputFile = (target) => {
42654
+ return `${DIFF_OUTPUT_DIRECTORY}/${this.buildDiffSlug(target)}.txt`;
42655
+ };
42656
+ /**
42657
+ * Build the intermediate artifact name carrying one target's captured diff.
42658
+ *
42659
+ * The diff jobs run in parallel and `actions/upload-artifact` v4+ rejects a
42660
+ * duplicate name within a run, so each target needs its own. The report job
42661
+ * merges them into a single artifact, which is the one meant to be read.
42662
+ */
42663
+ this.buildDiffPartArtifactName = (target) => {
42664
+ return `${DIFF_PART_ARTIFACT_PREFIX}-${this.buildDiffSlug(target)}`;
42665
+ };
42375
42666
  /**
42376
42667
  * Join an apply workflow another `AwsDeployWorkflow` already created, so this
42377
42668
  * component's apply jobs land in that file rather than a second one.
@@ -42429,9 +42720,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42429
42720
  ],
42430
42721
  runsOn: ["ubuntu-latest"],
42431
42722
  permissions: {
42432
- actions: JobPermission6.READ,
42433
- contents: JobPermission6.READ,
42434
- idToken: JobPermission6.WRITE
42723
+ actions: JobPermission7.READ,
42724
+ contents: JobPermission7.READ,
42725
+ idToken: JobPermission7.WRITE
42435
42726
  },
42436
42727
  ...this.environmentName ? { environment: this.environmentName } : void 0,
42437
42728
  /**
@@ -42439,6 +42730,13 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42439
42730
  * same-target deploy. `cancel-in-progress` is spelled out rather than
42440
42731
  * left to GitHub's default: cancelling a half-finished `cdk deploy`
42441
42732
  * strands a CloudFormation stack mid-update.
42733
+ *
42734
+ * `branchScopedConcurrency` deliberately does not reach here. An apply is
42735
+ * dispatched from whatever ref the operator happens to be on — unrelated
42736
+ * to the branch the nominated plan ran against, which is the only branch
42737
+ * that describes what is about to be deployed. Keying on the dispatch ref
42738
+ * would hand out separate locks to applies of the same target, so the
42739
+ * unsuffixed group stays.
42442
42740
  */
42443
42741
  concurrency: {
42444
42742
  group: this.buildJobName(target),
@@ -42621,9 +42919,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42621
42919
  const { roleArn, stackPattern } = ciDeploymentConfig ?? {};
42622
42920
  const { rootCdkOut, cdkCli } = awsDeploymentConfig;
42623
42921
  const label = `${awsStageType}/${deploymentTargetRole}/${account}/${region}`;
42624
- const artifactName = this.buildDiffArtifactName(target);
42625
- const capturedFileGuard = `always() && hashFiles('${DIFF_OUTPUT_FILE}') != ''`;
42626
- const runUrl = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
42922
+ const outputFile = this.buildDiffOutputFile(target);
42627
42923
  return [
42628
42924
  ...this.setupPnpm(),
42629
42925
  ...this.setupNode(),
@@ -42643,69 +42939,36 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42643
42939
  /**
42644
42940
  * Run CDK Diff, capturing the output while leaving it visible in the job
42645
42941
  * log. `pipefail` keeps the CLI's exit code from being masked by `tee`.
42942
+ *
42943
+ * The capture lands under the same path the report job will merge it
42944
+ * back to, so a target's file name is identical in both places.
42646
42945
  */
42647
42946
  {
42648
42947
  name: `Diff ${label}`,
42649
42948
  shell: "bash",
42650
42949
  run: [
42651
42950
  "set -o pipefail",
42951
+ `mkdir -p ${DIFF_OUTPUT_DIRECTORY}`,
42652
42952
  `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkDiff({
42653
42953
  ...cdkCli.diffOptionsFor(target),
42654
42954
  app: rootCdkOut,
42655
42955
  ci: true,
42656
42956
  noColor: true,
42657
42957
  stackPatterns: stackPattern ? [stackPattern] : void 0
42658
- })} 2>&1 | tee ${DIFF_OUTPUT_FILE}`
42958
+ })} 2>&1 | tee ${outputFile}`
42659
42959
  ].join("\n")
42660
42960
  },
42661
42961
  /**
42662
- * Render the captured diff into the job summary inside a collapsed
42663
- * `<details>` block, truncated below GitHub's 1 MiB per-step cap — a
42664
- * summary that exceeds the cap is dropped in full rather than clipped.
42962
+ * Hand the capture to the report job. Guarded on `!cancelled()` rather
42963
+ * than success, so a diff that exits non-zero — a synth error, or a
42964
+ * consumer who opted into `--fail` still contributes what it captured
42965
+ * instead of leaving a hole in the report.
42665
42966
  */
42666
- ...this.diffOptions.summary ? [
42667
- {
42668
- name: `Render diff summary ${label}`,
42669
- if: capturedFileGuard,
42670
- shell: "bash",
42671
- run: [
42672
- `size=$(wc -c < ${DIFF_OUTPUT_FILE})`,
42673
- "{",
42674
- ` echo '## cdk diff \u2014 ${label}'`,
42675
- ' echo ""',
42676
- " echo '<details><summary>cdk diff output</summary>'",
42677
- ' echo ""',
42678
- " echo '```'",
42679
- ` if [ "$size" -gt ${DIFF_SUMMARY_BYTE_LIMIT} ]; then`,
42680
- ` head -c ${DIFF_SUMMARY_BYTE_LIMIT} ${DIFF_OUTPUT_FILE}`,
42681
- ` printf '\\n... truncated at ${DIFF_SUMMARY_BYTE_LIMIT} bytes ...\\n'`,
42682
- " else",
42683
- ` cat ${DIFF_OUTPUT_FILE}`,
42684
- " fi",
42685
- " echo '```'",
42686
- ' echo ""',
42687
- " echo '</details>'",
42688
- ...this.diffOptions.artifact ? [
42689
- ' echo ""',
42690
- ` echo 'Full diff: the \`${artifactName}\` artifact on [this run](${runUrl}).'`
42691
- ] : [],
42692
- '} >> "$GITHUB_STEP_SUMMARY"'
42693
- ].join("\n")
42694
- }
42695
- ] : [],
42696
- /**
42697
- * Upload the untruncated diff as a per-run artifact.
42698
- */
42699
- ...this.diffOptions.artifact ? [
42700
- WorkflowSteps2.uploadArtifact({
42701
- name: `Upload diff ${label}`,
42702
- if: capturedFileGuard,
42703
- with: {
42704
- name: artifactName,
42705
- path: DIFF_OUTPUT_FILE
42706
- }
42707
- })
42708
- ] : []
42967
+ renderDiffPartUploadStep({
42968
+ label,
42969
+ artifactName: this.buildDiffPartArtifactName(target),
42970
+ outputFile
42971
+ })
42709
42972
  ];
42710
42973
  };
42711
42974
  if (!(project.root instanceof MonorepoProject)) {
@@ -42739,6 +43002,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42739
43002
  artifact: options.diff?.artifact ?? true,
42740
43003
  summary: options.diff?.summary ?? true
42741
43004
  };
43005
+ this.branchScopedConcurrency = options.branchScopedConcurrency ?? false;
42742
43006
  this.environmentName = resolveEnvironmentGate(
42743
43007
  options.gate,
42744
43008
  options.environmentName,
@@ -42771,14 +43035,20 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42771
43035
  buildTask: this.rootProject.buildTask,
42772
43036
  /**
42773
43037
  * Use push triggers based n the branch config for each environment.
43038
+ *
43039
+ * Deduped: targets in one workflow routinely share a branch — two prod
43040
+ * targets both default to `main` — and a repeated entry is only noise
43041
+ * in the generated file, since GitHub treats this as a filter list.
42774
43042
  */
42775
43043
  workflowTriggers: {
42776
43044
  push: {
42777
- branches: [
42778
- ...this.awsDeploymentTargets.flatMap(
42779
- (t) => t.branches.map((b) => b.branch)
43045
+ branches: Array.from(
43046
+ new Set(
43047
+ this.awsDeploymentTargets.flatMap(
43048
+ (t) => t.branches.map((b) => b.branch)
43049
+ )
42780
43050
  )
42781
- ]
43051
+ )
42782
43052
  },
42783
43053
  workflowDispatch: {},
42784
43054
  ...options.buildWorkflowOptions?.workflowTriggers
@@ -42796,7 +43066,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42796
43066
  * Set GIT_BRANCH_NAME so Turborepo remote cache hashes match between local and CI.
42797
43067
  */
42798
43068
  env: {
42799
- GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}",
43069
+ GIT_BRANCH_NAME: BRANCH_NAME_EXPRESSION,
42800
43070
  ...options.buildWorkflowOptions?.env,
42801
43071
  ...buildWorkflowOptions?.env
42802
43072
  },
@@ -42874,19 +43144,19 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42874
43144
  name: `Deploy ${this.project.name} ${target.awsStageType}/${target.deploymentTargetRole}/${target.account}/${target.region}`,
42875
43145
  needs: [
42876
43146
  "build",
42877
- ...gatedOnDiff ? [this.buildDiffJobName(target)] : [],
43147
+ ...gatedOnDiff ? [DIFF_REPORT_JOB_ID] : [],
42878
43148
  ...this.deployAfterTargets.map((p) => {
42879
43149
  return this.buildJobName(p);
42880
43150
  })
42881
43151
  ],
42882
43152
  runsOn: ["ubuntu-latest"],
42883
43153
  permissions: {
42884
- contents: JobPermission6.READ,
42885
- idToken: JobPermission6.WRITE,
42886
- pullRequests: JobPermission6.WRITE
43154
+ contents: JobPermission7.READ,
43155
+ idToken: JobPermission7.WRITE,
43156
+ pullRequests: JobPermission7.WRITE
42887
43157
  },
42888
43158
  ...this.environmentName ? { environment: this.environmentName } : void 0,
42889
- concurrency: deployJobName,
43159
+ concurrency: this.buildDeployConcurrency(deployJobName),
42890
43160
  if: jobCondition,
42891
43161
  steps: [...this.deploySteps(target)]
42892
43162
  });
@@ -42898,14 +43168,28 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42898
43168
  needs: ["build"],
42899
43169
  runsOn: ["ubuntu-latest"],
42900
43170
  permissions: {
42901
- contents: JobPermission6.READ,
42902
- idToken: JobPermission6.WRITE
43171
+ contents: JobPermission7.READ,
43172
+ idToken: JobPermission7.WRITE
42903
43173
  },
42904
43174
  if: jobCondition,
42905
43175
  steps: [...this.diffSteps(target)]
42906
43176
  });
42907
43177
  }
42908
43178
  });
43179
+ if (this.diffOptions.enabled && this.awsDeploymentTargets.length > 0) {
43180
+ DiffReportJob.attach(this.rootProject, {
43181
+ buildWorkflow: this.buildWorkflow,
43182
+ homeRepositoryCondition,
43183
+ summary: this.diffOptions.summary,
43184
+ artifact: this.diffOptions.artifact,
43185
+ targets: this.awsDeploymentTargets.map((target) => ({
43186
+ label: `${this.project.name} ${target.awsStageType}/${target.deploymentTargetRole}/${target.account}/${target.region}`,
43187
+ jobName: this.buildDiffJobName(target),
43188
+ outputFile: this.buildDiffOutputFile(target),
43189
+ branchCondition: this.buildBranchFilterCondition(target.branches)
43190
+ }))
43191
+ });
43192
+ }
42909
43193
  addBuildCompleteJob(this.buildWorkflow);
42910
43194
  }
42911
43195
  static of(project, buildWorkflow) {
@@ -42919,7 +43203,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42919
43203
  name: "Build Sub Projects",
42920
43204
  run: `pnpm exec projen ${ROOT_CI_TASK_NAME}`
42921
43205
  },
42922
- WorkflowSteps2.uploadArtifact({
43206
+ WorkflowSteps3.uploadArtifact({
42923
43207
  name: "Upload Turbo runs",
42924
43208
  if: "always()",
42925
43209
  continueOnError: true,
@@ -42935,9 +43219,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component24 {
42935
43219
  };
42936
43220
 
42937
43221
  // src/workflows/aws-teardown-workflow.ts
42938
- import { Component as Component25 } from "projen";
43222
+ import { Component as Component26 } from "projen";
42939
43223
  import { GitHub as GitHub7, GithubWorkflow as GithubWorkflow3 } from "projen/lib/github";
42940
- import { JobPermission as JobPermission7 } from "projen/lib/github/workflows-model";
43224
+ import { JobPermission as JobPermission8 } from "projen/lib/github/workflows-model";
42941
43225
  var DEFAULT_TEARDOWN_BRANCH_PATTERNS = [
42942
43226
  "feat/*",
42943
43227
  "fix/*",
@@ -42957,7 +43241,7 @@ var resolveBranchPatterns = (explicit, targets) => {
42957
43241
  }
42958
43242
  return [...DEFAULT_TEARDOWN_BRANCH_PATTERNS];
42959
43243
  };
42960
- var AwsTeardownWorkflow = class extends Component25 {
43244
+ var AwsTeardownWorkflow = class extends Component26 {
42961
43245
  constructor(rootProject, options) {
42962
43246
  super(rootProject);
42963
43247
  this.rootProject = rootProject;
@@ -43029,8 +43313,8 @@ var AwsTeardownWorkflow = class extends Component25 {
43029
43313
  runsOn: ["ubuntu-latest"],
43030
43314
  ...homeRepositoryCondition ? { if: `\${{ ${homeRepositoryCondition} }}` } : {},
43031
43315
  permissions: {
43032
- contents: JobPermission7.READ,
43033
- idToken: JobPermission7.WRITE
43316
+ contents: JobPermission8.READ,
43317
+ idToken: JobPermission8.WRITE
43034
43318
  },
43035
43319
  env: {
43036
43320
  REPO: "${{ github.repository }}",
@@ -43890,7 +44174,12 @@ export {
43890
44174
  DEFAULT_UNBLOCK_DEPENDENTS_ENABLED,
43891
44175
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
43892
44176
  DEPLOY_GATE,
44177
+ DIFF_ARTIFACT_NAME,
44178
+ DIFF_OUTPUT_DIRECTORY,
44179
+ DIFF_PART_ARTIFACT_PREFIX,
44180
+ DIFF_REPORT_JOB_ID,
43893
44181
  DOCS_SYNC_AUDIT_SCHEMA_VERSION,
44182
+ DiffReportJob,
43894
44183
  GITHUB_ISSUE_TYPES,
43895
44184
  GITHUB_ISSUE_TYPE_BY_TITLE_PREFIX,
43896
44185
  ISSUE_TEMPLATES_GENERATED_SUFFIX,
@@ -44070,6 +44359,7 @@ export {
44070
44359
  renderCheckLinksProcedure,
44071
44360
  renderCustomDocSectionBlock,
44072
44361
  renderCustomDocSections,
44362
+ renderDiffPartUploadStep,
44073
44363
  renderExtractApiProcedure,
44074
44364
  renderFocusSection,
44075
44365
  renderGithubIssueTypeSection,
@@ -44101,6 +44391,8 @@ export {
44101
44391
  renderScopeGateSection,
44102
44392
  renderScopeGateShellHelpers,
44103
44393
  renderSetIssueTypeFallbackLines,
44394
+ renderSetupNode,
44395
+ renderSetupPnpm,
44104
44396
  renderSharedEditingBundleHook,
44105
44397
  renderSharedEditingHelperScript,
44106
44398
  renderSharedEditingRuleContent,