@codedrifters/configulator 0.0.430 → 0.0.432

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
@@ -281,6 +281,7 @@ __export(index_exports, {
281
281
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED: () => DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
282
282
  DEPLOY_GATE: () => DEPLOY_GATE,
283
283
  DIFF_ARTIFACT_NAME: () => DIFF_ARTIFACT_NAME,
284
+ DIFF_NEW_STACK_MARKER: () => DIFF_NEW_STACK_MARKER,
284
285
  DIFF_OUTPUT_DIRECTORY: () => DIFF_OUTPUT_DIRECTORY,
285
286
  DIFF_PART_ARTIFACT_PREFIX: () => DIFF_PART_ARTIFACT_PREFIX,
286
287
  DIFF_REPORT_JOB_ID: () => DIFF_REPORT_JOB_ID,
@@ -42610,6 +42611,7 @@ var DIFF_REPORT_JOB_ID = "diff-report";
42610
42611
  var DIFF_OUTPUT_DIRECTORY = "cdk-diff";
42611
42612
  var DIFF_PART_ARTIFACT_PREFIX = "cdk-diff-part";
42612
42613
  var DIFF_ARTIFACT_NAME = "cdk-diff";
42614
+ var DIFF_NEW_STACK_MARKER = "NEW STACK";
42613
42615
  var DIFF_PART_RETENTION_DAYS = 1;
42614
42616
  var DIFF_SUMMARY_BYTE_LIMIT = 9e5;
42615
42617
  var RUN_URL = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
@@ -42685,6 +42687,11 @@ var DiffReportJob = class _DiffReportJob extends import_projen27.Component {
42685
42687
  * captured anything from one that simply had no changes — the latter has a
42686
42688
  * file, carrying the CDK CLI's own no-differences wording, rather than being
42687
42689
  * detected by matching on it.
42690
+ *
42691
+ * A capture carrying {@link DIFF_NEW_STACK_MARKER} lines has its banners
42692
+ * lifted out of the `<details>` block and quoted under the heading, so a
42693
+ * target that will be created from scratch reads as such without expanding
42694
+ * anything.
42688
42695
  */
42689
42696
  this.renderSummaryStep = () => {
42690
42697
  if (!this.summary || this.targets.length === 0) {
@@ -42709,6 +42716,10 @@ var DiffReportJob = class _DiffReportJob extends import_projen27.Component {
42709
42716
  ' echo ""',
42710
42717
  " continue",
42711
42718
  " fi",
42719
+ ` if grep -q '^${DIFF_NEW_STACK_MARKER}' "$file"; then`,
42720
+ ` grep '^${DIFF_NEW_STACK_MARKER}' "$file" | sed 's/^/> **/; s/$/**/'`,
42721
+ ' echo ""',
42722
+ " fi",
42712
42723
  " echo '<details><summary>cdk diff output</summary>'",
42713
42724
  ' echo ""',
42714
42725
  " echo '```'",
@@ -42928,6 +42939,7 @@ var renderSetupPnpm = (pnpmVersion) => {
42928
42939
 
42929
42940
  // src/workflows/aws-deploy-workflow.ts
42930
42941
  var PROD_DEPLOY_NAME = "prod-deploy";
42942
+ var BRANCH_NAME_EXPRESSION = "${{ github.head_ref || github.ref_name }}";
42931
42943
  var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Component {
42932
42944
  constructor(project, options = {}) {
42933
42945
  super(project);
@@ -42958,6 +42970,28 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
42958
42970
  target.region
42959
42971
  ].join("-");
42960
42972
  };
42973
+ /**
42974
+ * Build a deploy job's `concurrency` setting from its job name.
42975
+ *
42976
+ * Two shapes, deliberately:
42977
+ *
42978
+ * - Unscoped (the default) stays the bare string the job has always carried,
42979
+ * so an existing consumer's workflow is byte-identical.
42980
+ * - Branch-scoped spells out `cancel-in-progress: false` rather than leaning
42981
+ * on GitHub's default, the same way the apply job does. A group keyed on
42982
+ * the branch reads like a "cancel superseded pushes" group, and that is the
42983
+ * one thing it must not be — cancelling a half-finished `cdk deploy`
42984
+ * strands a CloudFormation stack mid-update.
42985
+ */
42986
+ this.buildDeployConcurrency = (deployJobName) => {
42987
+ if (!this.branchScopedConcurrency) {
42988
+ return deployJobName;
42989
+ }
42990
+ return {
42991
+ group: `${deployJobName}-${BRANCH_NAME_EXPRESSION}`,
42992
+ "cancel-in-progress": false
42993
+ };
42994
+ };
42961
42995
  /**
42962
42996
  * Build the deterministic GitHub Actions job name for a target's apply job.
42963
42997
  * Mirrors {@link buildJobName} with an `apply` verb.
@@ -43087,6 +43121,13 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43087
43121
  * same-target deploy. `cancel-in-progress` is spelled out rather than
43088
43122
  * left to GitHub's default: cancelling a half-finished `cdk deploy`
43089
43123
  * strands a CloudFormation stack mid-update.
43124
+ *
43125
+ * `branchScopedConcurrency` deliberately does not reach here. An apply is
43126
+ * dispatched from whatever ref the operator happens to be on — unrelated
43127
+ * to the branch the nominated plan ran against, which is the only branch
43128
+ * that describes what is about to be deployed. Keying on the dispatch ref
43129
+ * would hand out separate locks to applies of the same target, so the
43130
+ * unsuffixed group stays.
43090
43131
  */
43091
43132
  concurrency: {
43092
43133
  group: this.buildJobName(target),
@@ -43256,6 +43297,11 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43256
43297
  * `app` and `stackPatterns` target the built cloud assembly, and `ci` /
43257
43298
  * `noColor` force the human-readable diff onto stdout without ANSI escapes
43258
43299
  * (without `--ci` the CLI interleaves it into stderr alongside logs).
43300
+ *
43301
+ * `method` is the one resolved flag not baked into the rendered command. A
43302
+ * from-scratch target has to be classified before it can be diffed — see
43303
+ * {@link diffProbeLines} — so the command reads the effective method from
43304
+ * `$method`.
43259
43305
  */
43260
43306
  this.diffSteps = (target) => {
43261
43307
  const {
@@ -43270,6 +43316,20 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43270
43316
  const { rootCdkOut, cdkCli } = awsDeploymentConfig;
43271
43317
  const label = `${awsStageType}/${deploymentTargetRole}/${account}/${region}`;
43272
43318
  const outputFile = this.buildDiffOutputFile(target);
43319
+ const stackPatterns = stackPattern ? [stackPattern] : void 0;
43320
+ const diffCommand = [
43321
+ `pnpm dlx "aws-cdk@${cdkCli.cliVersion}"`,
43322
+ renderCdkDiff({
43323
+ ...cdkCli.diffOptionsFor(target),
43324
+ app: rootCdkOut,
43325
+ ci: true,
43326
+ noColor: true,
43327
+ method: void 0,
43328
+ stackPatterns: void 0
43329
+ }),
43330
+ '--method="$method"',
43331
+ ...(stackPatterns ?? []).map((pattern) => `"${pattern}"`)
43332
+ ].join(" ");
43273
43333
  return [
43274
43334
  ...this.setupPnpm(),
43275
43335
  ...this.setupNode(),
@@ -43287,11 +43347,14 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43287
43347
  }
43288
43348
  },
43289
43349
  /**
43290
- * Run CDK Diff, capturing the output while leaving it visible in the job
43291
- * log. `pipefail` keeps the CLI's exit code from being masked by `tee`.
43350
+ * Classify the target's stacks, then run CDK Diff, capturing the output
43351
+ * while leaving it visible in the job log. `pipefail` keeps the CLI's
43352
+ * exit code from being masked by `tee`.
43292
43353
  *
43293
43354
  * 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.
43355
+ * back to, so a target's file name is identical in both places. It is
43356
+ * truncated first and appended to from there, so a re-run never reads a
43357
+ * previous attempt's banners.
43295
43358
  */
43296
43359
  {
43297
43360
  name: `Diff ${label}`,
@@ -43299,13 +43362,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43299
43362
  run: [
43300
43363
  "set -o pipefail",
43301
43364
  `mkdir -p ${DIFF_OUTPUT_DIRECTORY}`,
43302
- `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkDiff({
43303
- ...cdkCli.diffOptionsFor(target),
43304
- app: rootCdkOut,
43305
- ci: true,
43306
- noColor: true,
43307
- stackPatterns: stackPattern ? [stackPattern] : void 0
43308
- })} 2>&1 | tee ${outputFile}`
43365
+ `: > ${outputFile}`,
43366
+ ...this.diffProbeLines(target, outputFile),
43367
+ `${diffCommand} 2>&1 | tee -a ${outputFile}`
43309
43368
  ].join("\n")
43310
43369
  },
43311
43370
  /**
@@ -43321,6 +43380,70 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43321
43380
  })
43322
43381
  ];
43323
43382
  };
43383
+ /**
43384
+ * Lines that classify every stack a target's pattern matches before the diff
43385
+ * runs, so a stack that will be created from scratch is reported as such
43386
+ * instead of failing the job.
43387
+ *
43388
+ * `cdk diff --method=change-set` asks CloudFormation for an `UPDATE` change
43389
+ * set whenever the stack exists — and a `ROLLBACK_COMPLETE` /
43390
+ * `ROLLBACK_FAILED` tombstone left by a failed first create does exist, so
43391
+ * CloudFormation rejects the request and the job goes red. `cdk deploy`
43392
+ * consults `stackStatus.isCreationFailure`, deletes the tombstone and
43393
+ * recreates the stack, so the apply succeeds on exactly the stack the diff
43394
+ * refuses to look at. Under the `plan-apply` gate that asymmetry is a hard
43395
+ * blocker rather than a cosmetic one: the apply requires a successful plan
43396
+ * run, so one from-scratch target blocks applying any plan for the stage.
43397
+ *
43398
+ * `cdk list --long --json` resolves the pattern to CloudFormation stack names
43399
+ * out of the built assembly's manifest — no synth, no cloud call — and each
43400
+ * name costs one `describe-stacks`, which needs only
43401
+ * `cloudformation:DescribeStacks`. Any role that can run a change-set diff
43402
+ * already carries it. A `cdk list` that fails contributes no names, leaving
43403
+ * the diff to run exactly as it does today.
43404
+ *
43405
+ * A genuinely absent stack keeps the configured method — CDK gives it a
43406
+ * `CREATE` change set and diffs it fine. Only a tombstone moves the method,
43407
+ * and only to `template`, never to `auto`: `auto` would swallow a real
43408
+ * permission failure and present a degraded diff as authoritative, which is
43409
+ * what an explicit `change-set` pin exists to prevent. `template`
43410
+ * over-reporting replacements is vacuous here, because every resource in a
43411
+ * stack built from scratch is an add.
43412
+ *
43413
+ * The method is per invocation, not per stack — `cdk diff` diffs everything
43414
+ * its pattern matches in one pass. A pattern matching several stacks where
43415
+ * only one is a tombstone therefore diffs all of them with `template`, and
43416
+ * the banner names the stack that caused it.
43417
+ */
43418
+ this.diffProbeLines = (target, outputFile) => {
43419
+ const { ciDeploymentConfig, awsDeploymentConfig } = target;
43420
+ const { stackPattern } = ciDeploymentConfig ?? {};
43421
+ const { rootCdkOut, cdkCli } = awsDeploymentConfig;
43422
+ const configuredMethod = cdkCli.diffOptionsFor(target).method ?? CDK_DIFF_METHOD.ChangeSet;
43423
+ const listCommand = `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkList(
43424
+ {
43425
+ app: rootCdkOut,
43426
+ json: true,
43427
+ long: true,
43428
+ stackPatterns: stackPattern ? [stackPattern] : void 0
43429
+ }
43430
+ )}`;
43431
+ return [
43432
+ `method=${configuredMethod}`,
43433
+ "while read -r stack; do",
43434
+ ' status=$(aws cloudformation describe-stacks --stack-name "$stack" --query "Stacks[0].StackStatus" --output text 2>/dev/null || echo NOT_FOUND)',
43435
+ ' case "$status" in',
43436
+ " NOT_FOUND|REVIEW_IN_PROGRESS)",
43437
+ ` echo "${DIFF_NEW_STACK_MARKER} \u2014 $stack does not exist yet, so this target will be created from scratch." | tee -a ${outputFile}`,
43438
+ " ;;",
43439
+ " ROLLBACK_COMPLETE|ROLLBACK_FAILED)",
43440
+ ` method=${CDK_DIFF_METHOD.Template}`,
43441
+ ` echo "${DIFF_NEW_STACK_MARKER} \u2014 $stack exists only as a failed-create tombstone ($status), so this target will be created from scratch. Diffing it with --method=${CDK_DIFF_METHOD.Template}." | tee -a ${outputFile}`,
43442
+ " ;;",
43443
+ " esac",
43444
+ `done < <(${listCommand} 2>/dev/null | jq -r '.[].name' 2>/dev/null)`
43445
+ ];
43446
+ };
43324
43447
  if (!(project.root instanceof MonorepoProject)) {
43325
43448
  throw new Error(
43326
43449
  "AwsDeployWorkflow requires the root project to be a MonorepoProject"
@@ -43352,6 +43475,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43352
43475
  artifact: options.diff?.artifact ?? true,
43353
43476
  summary: options.diff?.summary ?? true
43354
43477
  };
43478
+ this.branchScopedConcurrency = options.branchScopedConcurrency ?? false;
43355
43479
  this.environmentName = resolveEnvironmentGate(
43356
43480
  options.gate,
43357
43481
  options.environmentName,
@@ -43415,7 +43539,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43415
43539
  * Set GIT_BRANCH_NAME so Turborepo remote cache hashes match between local and CI.
43416
43540
  */
43417
43541
  env: {
43418
- GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}",
43542
+ GIT_BRANCH_NAME: BRANCH_NAME_EXPRESSION,
43419
43543
  ...options.buildWorkflowOptions?.env,
43420
43544
  ...buildWorkflowOptions?.env
43421
43545
  },
@@ -43505,7 +43629,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends import_projen29.Compone
43505
43629
  pullRequests: import_workflows_model7.JobPermission.WRITE
43506
43630
  },
43507
43631
  ...this.environmentName ? { environment: this.environmentName } : void 0,
43508
- concurrency: deployJobName,
43632
+ concurrency: this.buildDeployConcurrency(deployJobName),
43509
43633
  if: jobCondition,
43510
43634
  steps: [...this.deploySteps(target)]
43511
43635
  });
@@ -44525,6 +44649,7 @@ export const collections = {
44525
44649
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
44526
44650
  DEPLOY_GATE,
44527
44651
  DIFF_ARTIFACT_NAME,
44652
+ DIFF_NEW_STACK_MARKER,
44528
44653
  DIFF_OUTPUT_DIRECTORY,
44529
44654
  DIFF_PART_ARTIFACT_PREFIX,
44530
44655
  DIFF_REPORT_JOB_ID,