@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.mjs CHANGED
@@ -42230,6 +42230,7 @@ var DIFF_REPORT_JOB_ID = "diff-report";
42230
42230
  var DIFF_OUTPUT_DIRECTORY = "cdk-diff";
42231
42231
  var DIFF_PART_ARTIFACT_PREFIX = "cdk-diff-part";
42232
42232
  var DIFF_ARTIFACT_NAME = "cdk-diff";
42233
+ var DIFF_NEW_STACK_MARKER = "NEW STACK";
42233
42234
  var DIFF_PART_RETENTION_DAYS = 1;
42234
42235
  var DIFF_SUMMARY_BYTE_LIMIT = 9e5;
42235
42236
  var RUN_URL = "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}";
@@ -42305,6 +42306,11 @@ var DiffReportJob = class _DiffReportJob extends Component23 {
42305
42306
  * captured anything from one that simply had no changes — the latter has a
42306
42307
  * file, carrying the CDK CLI's own no-differences wording, rather than being
42307
42308
  * detected by matching on it.
42309
+ *
42310
+ * A capture carrying {@link DIFF_NEW_STACK_MARKER} lines has its banners
42311
+ * lifted out of the `<details>` block and quoted under the heading, so a
42312
+ * target that will be created from scratch reads as such without expanding
42313
+ * anything.
42308
42314
  */
42309
42315
  this.renderSummaryStep = () => {
42310
42316
  if (!this.summary || this.targets.length === 0) {
@@ -42329,6 +42335,10 @@ var DiffReportJob = class _DiffReportJob extends Component23 {
42329
42335
  ' echo ""',
42330
42336
  " continue",
42331
42337
  " fi",
42338
+ ` if grep -q '^${DIFF_NEW_STACK_MARKER}' "$file"; then`,
42339
+ ` grep '^${DIFF_NEW_STACK_MARKER}' "$file" | sed 's/^/> **/; s/$/**/'`,
42340
+ ' echo ""',
42341
+ " fi",
42332
42342
  " echo '<details><summary>cdk diff output</summary>'",
42333
42343
  ' echo ""',
42334
42344
  " echo '```'",
@@ -42548,6 +42558,7 @@ var renderSetupPnpm = (pnpmVersion) => {
42548
42558
 
42549
42559
  // src/workflows/aws-deploy-workflow.ts
42550
42560
  var PROD_DEPLOY_NAME = "prod-deploy";
42561
+ var BRANCH_NAME_EXPRESSION = "${{ github.head_ref || github.ref_name }}";
42551
42562
  var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42552
42563
  constructor(project, options = {}) {
42553
42564
  super(project);
@@ -42578,6 +42589,28 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42578
42589
  target.region
42579
42590
  ].join("-");
42580
42591
  };
42592
+ /**
42593
+ * Build a deploy job's `concurrency` setting from its job name.
42594
+ *
42595
+ * Two shapes, deliberately:
42596
+ *
42597
+ * - Unscoped (the default) stays the bare string the job has always carried,
42598
+ * so an existing consumer's workflow is byte-identical.
42599
+ * - Branch-scoped spells out `cancel-in-progress: false` rather than leaning
42600
+ * on GitHub's default, the same way the apply job does. A group keyed on
42601
+ * the branch reads like a "cancel superseded pushes" group, and that is the
42602
+ * one thing it must not be — cancelling a half-finished `cdk deploy`
42603
+ * strands a CloudFormation stack mid-update.
42604
+ */
42605
+ this.buildDeployConcurrency = (deployJobName) => {
42606
+ if (!this.branchScopedConcurrency) {
42607
+ return deployJobName;
42608
+ }
42609
+ return {
42610
+ group: `${deployJobName}-${BRANCH_NAME_EXPRESSION}`,
42611
+ "cancel-in-progress": false
42612
+ };
42613
+ };
42581
42614
  /**
42582
42615
  * Build the deterministic GitHub Actions job name for a target's apply job.
42583
42616
  * Mirrors {@link buildJobName} with an `apply` verb.
@@ -42707,6 +42740,13 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42707
42740
  * same-target deploy. `cancel-in-progress` is spelled out rather than
42708
42741
  * left to GitHub's default: cancelling a half-finished `cdk deploy`
42709
42742
  * strands a CloudFormation stack mid-update.
42743
+ *
42744
+ * `branchScopedConcurrency` deliberately does not reach here. An apply is
42745
+ * dispatched from whatever ref the operator happens to be on — unrelated
42746
+ * to the branch the nominated plan ran against, which is the only branch
42747
+ * that describes what is about to be deployed. Keying on the dispatch ref
42748
+ * would hand out separate locks to applies of the same target, so the
42749
+ * unsuffixed group stays.
42710
42750
  */
42711
42751
  concurrency: {
42712
42752
  group: this.buildJobName(target),
@@ -42876,6 +42916,11 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42876
42916
  * `app` and `stackPatterns` target the built cloud assembly, and `ci` /
42877
42917
  * `noColor` force the human-readable diff onto stdout without ANSI escapes
42878
42918
  * (without `--ci` the CLI interleaves it into stderr alongside logs).
42919
+ *
42920
+ * `method` is the one resolved flag not baked into the rendered command. A
42921
+ * from-scratch target has to be classified before it can be diffed — see
42922
+ * {@link diffProbeLines} — so the command reads the effective method from
42923
+ * `$method`.
42879
42924
  */
42880
42925
  this.diffSteps = (target) => {
42881
42926
  const {
@@ -42890,6 +42935,20 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42890
42935
  const { rootCdkOut, cdkCli } = awsDeploymentConfig;
42891
42936
  const label = `${awsStageType}/${deploymentTargetRole}/${account}/${region}`;
42892
42937
  const outputFile = this.buildDiffOutputFile(target);
42938
+ const stackPatterns = stackPattern ? [stackPattern] : void 0;
42939
+ const diffCommand = [
42940
+ `pnpm dlx "aws-cdk@${cdkCli.cliVersion}"`,
42941
+ renderCdkDiff({
42942
+ ...cdkCli.diffOptionsFor(target),
42943
+ app: rootCdkOut,
42944
+ ci: true,
42945
+ noColor: true,
42946
+ method: void 0,
42947
+ stackPatterns: void 0
42948
+ }),
42949
+ '--method="$method"',
42950
+ ...(stackPatterns ?? []).map((pattern) => `"${pattern}"`)
42951
+ ].join(" ");
42893
42952
  return [
42894
42953
  ...this.setupPnpm(),
42895
42954
  ...this.setupNode(),
@@ -42907,11 +42966,14 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42907
42966
  }
42908
42967
  },
42909
42968
  /**
42910
- * Run CDK Diff, capturing the output while leaving it visible in the job
42911
- * log. `pipefail` keeps the CLI's exit code from being masked by `tee`.
42969
+ * Classify the target's stacks, then run CDK Diff, capturing the output
42970
+ * while leaving it visible in the job log. `pipefail` keeps the CLI's
42971
+ * exit code from being masked by `tee`.
42912
42972
  *
42913
42973
  * The capture lands under the same path the report job will merge it
42914
- * back to, so a target's file name is identical in both places.
42974
+ * back to, so a target's file name is identical in both places. It is
42975
+ * truncated first and appended to from there, so a re-run never reads a
42976
+ * previous attempt's banners.
42915
42977
  */
42916
42978
  {
42917
42979
  name: `Diff ${label}`,
@@ -42919,13 +42981,9 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42919
42981
  run: [
42920
42982
  "set -o pipefail",
42921
42983
  `mkdir -p ${DIFF_OUTPUT_DIRECTORY}`,
42922
- `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkDiff({
42923
- ...cdkCli.diffOptionsFor(target),
42924
- app: rootCdkOut,
42925
- ci: true,
42926
- noColor: true,
42927
- stackPatterns: stackPattern ? [stackPattern] : void 0
42928
- })} 2>&1 | tee ${outputFile}`
42984
+ `: > ${outputFile}`,
42985
+ ...this.diffProbeLines(target, outputFile),
42986
+ `${diffCommand} 2>&1 | tee -a ${outputFile}`
42929
42987
  ].join("\n")
42930
42988
  },
42931
42989
  /**
@@ -42941,6 +42999,70 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42941
42999
  })
42942
43000
  ];
42943
43001
  };
43002
+ /**
43003
+ * Lines that classify every stack a target's pattern matches before the diff
43004
+ * runs, so a stack that will be created from scratch is reported as such
43005
+ * instead of failing the job.
43006
+ *
43007
+ * `cdk diff --method=change-set` asks CloudFormation for an `UPDATE` change
43008
+ * set whenever the stack exists — and a `ROLLBACK_COMPLETE` /
43009
+ * `ROLLBACK_FAILED` tombstone left by a failed first create does exist, so
43010
+ * CloudFormation rejects the request and the job goes red. `cdk deploy`
43011
+ * consults `stackStatus.isCreationFailure`, deletes the tombstone and
43012
+ * recreates the stack, so the apply succeeds on exactly the stack the diff
43013
+ * refuses to look at. Under the `plan-apply` gate that asymmetry is a hard
43014
+ * blocker rather than a cosmetic one: the apply requires a successful plan
43015
+ * run, so one from-scratch target blocks applying any plan for the stage.
43016
+ *
43017
+ * `cdk list --long --json` resolves the pattern to CloudFormation stack names
43018
+ * out of the built assembly's manifest — no synth, no cloud call — and each
43019
+ * name costs one `describe-stacks`, which needs only
43020
+ * `cloudformation:DescribeStacks`. Any role that can run a change-set diff
43021
+ * already carries it. A `cdk list` that fails contributes no names, leaving
43022
+ * the diff to run exactly as it does today.
43023
+ *
43024
+ * A genuinely absent stack keeps the configured method — CDK gives it a
43025
+ * `CREATE` change set and diffs it fine. Only a tombstone moves the method,
43026
+ * and only to `template`, never to `auto`: `auto` would swallow a real
43027
+ * permission failure and present a degraded diff as authoritative, which is
43028
+ * what an explicit `change-set` pin exists to prevent. `template`
43029
+ * over-reporting replacements is vacuous here, because every resource in a
43030
+ * stack built from scratch is an add.
43031
+ *
43032
+ * The method is per invocation, not per stack — `cdk diff` diffs everything
43033
+ * its pattern matches in one pass. A pattern matching several stacks where
43034
+ * only one is a tombstone therefore diffs all of them with `template`, and
43035
+ * the banner names the stack that caused it.
43036
+ */
43037
+ this.diffProbeLines = (target, outputFile) => {
43038
+ const { ciDeploymentConfig, awsDeploymentConfig } = target;
43039
+ const { stackPattern } = ciDeploymentConfig ?? {};
43040
+ const { rootCdkOut, cdkCli } = awsDeploymentConfig;
43041
+ const configuredMethod = cdkCli.diffOptionsFor(target).method ?? CDK_DIFF_METHOD.ChangeSet;
43042
+ const listCommand = `pnpm dlx "aws-cdk@${cdkCli.cliVersion}" ${renderCdkList(
43043
+ {
43044
+ app: rootCdkOut,
43045
+ json: true,
43046
+ long: true,
43047
+ stackPatterns: stackPattern ? [stackPattern] : void 0
43048
+ }
43049
+ )}`;
43050
+ return [
43051
+ `method=${configuredMethod}`,
43052
+ "while read -r stack; do",
43053
+ ' status=$(aws cloudformation describe-stacks --stack-name "$stack" --query "Stacks[0].StackStatus" --output text 2>/dev/null || echo NOT_FOUND)',
43054
+ ' case "$status" in',
43055
+ " NOT_FOUND|REVIEW_IN_PROGRESS)",
43056
+ ` echo "${DIFF_NEW_STACK_MARKER} \u2014 $stack does not exist yet, so this target will be created from scratch." | tee -a ${outputFile}`,
43057
+ " ;;",
43058
+ " ROLLBACK_COMPLETE|ROLLBACK_FAILED)",
43059
+ ` method=${CDK_DIFF_METHOD.Template}`,
43060
+ ` 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}`,
43061
+ " ;;",
43062
+ " esac",
43063
+ `done < <(${listCommand} 2>/dev/null | jq -r '.[].name' 2>/dev/null)`
43064
+ ];
43065
+ };
42944
43066
  if (!(project.root instanceof MonorepoProject)) {
42945
43067
  throw new Error(
42946
43068
  "AwsDeployWorkflow requires the root project to be a MonorepoProject"
@@ -42972,6 +43094,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
42972
43094
  artifact: options.diff?.artifact ?? true,
42973
43095
  summary: options.diff?.summary ?? true
42974
43096
  };
43097
+ this.branchScopedConcurrency = options.branchScopedConcurrency ?? false;
42975
43098
  this.environmentName = resolveEnvironmentGate(
42976
43099
  options.gate,
42977
43100
  options.environmentName,
@@ -43035,7 +43158,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
43035
43158
  * Set GIT_BRANCH_NAME so Turborepo remote cache hashes match between local and CI.
43036
43159
  */
43037
43160
  env: {
43038
- GIT_BRANCH_NAME: "${{ github.head_ref || github.ref_name }}",
43161
+ GIT_BRANCH_NAME: BRANCH_NAME_EXPRESSION,
43039
43162
  ...options.buildWorkflowOptions?.env,
43040
43163
  ...buildWorkflowOptions?.env
43041
43164
  },
@@ -43125,7 +43248,7 @@ var AwsDeployWorkflow = class _AwsDeployWorkflow extends Component25 {
43125
43248
  pullRequests: JobPermission7.WRITE
43126
43249
  },
43127
43250
  ...this.environmentName ? { environment: this.environmentName } : void 0,
43128
- concurrency: deployJobName,
43251
+ concurrency: this.buildDeployConcurrency(deployJobName),
43129
43252
  if: jobCondition,
43130
43253
  steps: [...this.deploySteps(target)]
43131
43254
  });
@@ -44144,6 +44267,7 @@ export {
44144
44267
  DEFAULT_UPSTREAM_CONFIGULATOR_ENABLED,
44145
44268
  DEPLOY_GATE,
44146
44269
  DIFF_ARTIFACT_NAME,
44270
+ DIFF_NEW_STACK_MARKER,
44147
44271
  DIFF_OUTPUT_DIRECTORY,
44148
44272
  DIFF_PART_ARTIFACT_PREFIX,
44149
44273
  DIFF_REPORT_JOB_ID,