@firestartr/cli 2.5.0-snapshot-1 → 2.5.0

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/README.md CHANGED
@@ -9,7 +9,6 @@
9
9
  # requirements
10
10
 
11
11
  - terraform 1.6.2
12
- - cdktf-cli@0.19.0
13
12
  - lerna@7.4.1
14
13
 
15
14
  To install it, run the next commands:
@@ -21,7 +20,6 @@ mv terraform /usr/local/bin/; \
21
20
  chmod a+x /usr/local/bin/terraform; \
22
21
  rm -f terraform_1.6.2_linux_amd64.zip; \
23
22
  terraform --version; \
24
- npm install -g cdktf-cli@0.19.0; \
25
23
  npm install -g lerna@7.4.1;
26
24
  ```
27
25
 
package/build/index.js CHANGED
@@ -268553,12 +268553,7 @@ var envVars;
268553
268553
  envVars["s3Bucket"] = "S3_BUCKET";
268554
268554
  envVars["s3Lock"] = "S3_LOCK";
268555
268555
  envVars["s3Region"] = "S3_REGION";
268556
- // ---- CDKTF/LOCAL VARIABLES -----------------------------------------------
268557
- envVars["cdktfConfigFiles"] = "CDKTF_CONFIG_FILES";
268558
268556
  envVars["exclusionsYamlPath"] = "EXCLUSIONS_PATH";
268559
- envVars["cdktfEntityPath"] = "FIRESTARTR_CDKTF_ENTITY_PATH";
268560
- envVars["cdktfDepsPath"] = "FIRESTARTR_CDKTF_DEPS_PATH";
268561
- envVars["cdktfIsImport"] = "FIRESTARTR_CDKTF_IS_IMPORT";
268562
268557
  // ---- GITHUB APP VARIABLES -----------------------------------------------
268563
268558
  envVars["githubAppId"] = "GITHUB_APP_ID";
268564
268559
  envVars["githubAppInstallationId"] = "GITHUB_APP_INSTALLATION_ID";
@@ -268980,6 +268975,128 @@ class SimpleTokenizer {
268980
268975
  SimpleTokenizer,
268981
268976
  });
268982
268977
 
268978
+ ;// CONCATENATED MODULE: ../catalog_common/src/codeowners/index.ts
268979
+ // Common CODEOWNERS machinery (pure TypeScript/data; NO IO, NO classes)
268980
+ // Strictly follows GitHub CODEOWNERS syntax.
268981
+ // Spec: see specs/common/001-common-codeowners-machinery/spec.md
268982
+
268983
+ const OWNER_RE = /^@[\w.-]+(\/[\w.-]+)?$/;
268984
+ function codeowners_parse(raw) {
268985
+ if (typeof raw !== 'string' || raw === '')
268986
+ return [];
268987
+ return raw.split(/\r?\n/).map((line) => {
268988
+ const trimmed = line.trim();
268989
+ if (trimmed === '') {
268990
+ return { type: 'blank', raw: line };
268991
+ }
268992
+ if (/^#/.test(trimmed)) {
268993
+ return { type: 'comment', raw: line };
268994
+ }
268995
+ // Rule line: parse pattern & owners structurally, no ref resolution
268996
+ const tokens = trimmed.split(/\s+/);
268997
+ const pattern = tokens[0];
268998
+ const owners = tokens.slice(1);
268999
+ return { type: 'rule', pattern, owners, raw: line };
269000
+ });
269001
+ }
269002
+ function format(entries) {
269003
+ let end = entries.length;
269004
+ while (end > 0 && entries[end - 1].type === 'blank') {
269005
+ end -= 1;
269006
+ }
269007
+ const lines = entries.slice(0, end).map((entry) => {
269008
+ switch (entry.type) {
269009
+ case 'blank':
269010
+ return '';
269011
+ case 'comment':
269012
+ return entry.raw.trimEnd();
269013
+ case 'rule': {
269014
+ const owners = entry.owners ?? [];
269015
+ return owners.length > 0
269016
+ ? `${entry.pattern} ${owners.join(' ')}`
269017
+ : `${entry.pattern}`;
269018
+ }
269019
+ }
269020
+ });
269021
+ return lines.join('\n') + '\n';
269022
+ }
269023
+ function codeowners_validate(raw) {
269024
+ const errors = [];
269025
+ const lines = raw.split(/\r?\n/);
269026
+ for (let i = 0; i < lines.length; i++) {
269027
+ const line = lines[i];
269028
+ if (/^\s*$/.test(line))
269029
+ continue;
269030
+ if (/^\s*#/.test(line))
269031
+ continue;
269032
+ const tokens = line.trim().split(/\s+/);
269033
+ const pattern = tokens[0];
269034
+ const owners = tokens.slice(1);
269035
+ if (!pattern) {
269036
+ errors.push(`Line ${i + 1}: empty pattern`);
269037
+ continue;
269038
+ }
269039
+ if (owners.length === 0) {
269040
+ errors.push(`Line ${i + 1}: rule must have at least one owner`);
269041
+ continue;
269042
+ }
269043
+ for (const owner of owners) {
269044
+ if (!OWNER_RE.test(owner)) {
269045
+ errors.push(`Line ${i + 1}: invalid owner '${owner}' (must be @username or @org/team-slug)`);
269046
+ }
269047
+ }
269048
+ if (line.includes('#')) {
269049
+ errors.push(`Line ${i + 1}: inline comments are not allowed`);
269050
+ }
269051
+ }
269052
+ return { valid: errors.length === 0, errors };
269053
+ }
269054
+ function updateRule(entries, pattern, owners) {
269055
+ const idx = entries.findIndex((e) => e.type === 'rule' && e.pattern === pattern);
269056
+ const newEntry = {
269057
+ type: 'rule',
269058
+ pattern,
269059
+ owners,
269060
+ raw: `${pattern} ${owners.join(' ')}`,
269061
+ };
269062
+ if (idx >= 0) {
269063
+ const result = [...entries];
269064
+ result[idx] = newEntry;
269065
+ return result;
269066
+ }
269067
+ return [...entries, newEntry];
269068
+ }
269069
+ function remove(entries, pattern) {
269070
+ return entries.filter((e) => !(e.type === 'rule' && e.pattern === pattern));
269071
+ }
269072
+ function getOwners(entries) {
269073
+ const owners = new Set();
269074
+ for (const entry of entries) {
269075
+ if (entry.type === 'rule' && entry.owners) {
269076
+ for (const owner of entry.owners) {
269077
+ owners.add(owner);
269078
+ }
269079
+ }
269080
+ }
269081
+ return [...owners];
269082
+ }
269083
+ function resolveRefs(raw, replacements) {
269084
+ const tokenizer = new SimpleTokenizer(/\{\{\s*([\w./-]+)\s*\}\}/);
269085
+ return tokenizer.replace(raw, (token) => {
269086
+ const key = token.groups?.[0] ?? '';
269087
+ return key in replacements ? replacements[key] : token.value;
269088
+ });
269089
+ }
269090
+ /* harmony default export */ const codeowners = ({
269091
+ parse: codeowners_parse,
269092
+ format,
269093
+ validate: codeowners_validate,
269094
+ updateRule,
269095
+ remove,
269096
+ getOwners,
269097
+ resolveRefs,
269098
+ });
269099
+
268983
269100
  // EXTERNAL MODULE: ../../node_modules/cron-parser/dist/index.js
268984
269101
  var cron_parser_dist = __nccwpck_require__(98370);
268985
269102
  ;// CONCATENATED MODULE: ../catalog_common/src/cron.ts
@@ -269023,6 +269140,7 @@ function getCronNextInterval(cronLine, tz) {
269023
269140
 
269024
269141
 
269025
269142
 
269143
+
269026
269144
  /* harmony default export */ const catalog_common = ({
269027
269145
  io: io,
269028
269146
  generic: generic,
@@ -269033,6 +269151,7 @@ function getCronNextInterval(cronLine, tz) {
269033
269151
  policies: policies,
269034
269152
  logger: logger_logger,
269035
269153
  tokenizer: tokenizer,
269154
+ codeowners: codeowners,
269036
269155
  cron: {
269037
269156
  validateCron: validateCron,
269038
269157
  isValidCron: isValidCron,
@@ -290043,7 +290162,7 @@ class RepoGithubDecanter extends GithubDecanter {
290043
290162
  if (this.data.isEmpty) {
290044
290163
  const repoFullName = `${this.org}/${this.data.repoDetails.name}`;
290045
290164
  throw new Error(`The repo ${repoFullName} is empty - no commits, therefore CAN NOT be imported. ` +
290046
- 'Create it with firestartr or fill it with some README.md or another initial file or code');
290165
+ 'Create it with Firestartr or fill it with some README.md or another initial file or code');
290047
290166
  }
290048
290167
  }
290049
290168
  constructor(data, org, githubTeams = {}) {
@@ -291151,6 +291270,27 @@ function definitions_getPluralFromKind(kind) {
291151
291270
  const plural = Object.keys(kindPluralMap).find((key) => kindPluralMap[key] === kind);
291152
291271
  return plural;
291153
291272
  }
291273
+ // ----------------------------------------------------
291274
+ // Operations, workitems and handlers definitions
291275
+ // ----------------------------------------------------
291276
+ /**
291277
+ * Helper to extract the canonical identity (metadata.uid) from a WorkItem or its item.
291278
+ *
291279
+ * Use this helper when you need a stable identity key for WorkItems, e.g. for
291280
+ * in-memory deferred-scheduling bookkeeping.
291281
+ *
291282
+ * Note: other subsystems may still use kind/namespace/name keys; this helper is
291283
+ * specifically intended for UID-based WorkItem identity.
291284
+ * Any future change to identity logic MUST be made here and nowhere else.
291285
+ *
291286
+ * Returns undefined if .metadata.uid is missing so scheduler/bookkeeping paths
291287
+ * can safely short-circuit instead of throwing and aborting an entire pass.
291288
+ */
291289
+ function getWorkItemIdentity(wi) {
291290
+ // Support either .item.metadata.uid or direct .metadata.uid for future extensibility
291291
+ const meta = wi.item && wi.item.metadata ? wi.item.metadata : wi.metadata;
291292
+ return meta && meta.uid ? meta.uid : undefined;
291293
+ }
291154
291294
  var OperationType;
291155
291295
  (function (OperationType) {
291156
291296
  OperationType["RENAMED"] = "RENAMED";
@@ -293242,7 +293382,7 @@ async function* markedToDeletion(item, op, handler) {
293242
293382
  reason: op,
293243
293383
  type: 'PROVISIONED',
293244
293384
  status: 'False',
293245
- message: 'Synth CDKTF',
293385
+ message: 'Synth',
293246
293386
  };
293247
293387
  yield {
293248
293388
  item,
@@ -293394,6 +293534,134 @@ function fdummies_fWait(ms) {
293394
293534
  });
293395
293535
  }
293396
293536
 
293537
+ ;// CONCATENATED MODULE: ../operator/src/processItem.blocks.ts
293538
+ // processItem.blocks.ts
293539
+ // Responsible for all in-memory deferred-scheduling bookkeeping for blocked parent deletions.
293540
+ //
293541
+ // Always extract WorkItem identity for all in-memory maps and keys by calling
293542
+ // getWorkItemIdentity() from definitions.ts, and NOT by constructing your own key.
293543
+ // This ensures spec compliance and future maintainability.
293544
+ //
293545
+ // Does NOT detect "blocked" state (caller must supply outcome). Purely key/value scheduler-local state logic per spec.
293546
+
293547
+ // Only use getWorkItemIdentity(item) for identity bookkeeping and maps.
293548
+ // --- Config: Deferral Policy ---
293549
+ /**
293550
+ * Maximum number of blocked attempts allowed before an item is moved to the deferred segment.
293551
+ * An item becomes deferred once its blocked-attempt count exceeds this value.
293552
+ */
293553
+ const MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER = 5;
293554
+ // Initial supported parent kind(s): extensible for future cases
293555
+ const SUPPORTED_PARENT_KINDS = new Set(['FirestartrGithubRepository']);
293556
+ // --- Bookkeeper Class ---
293557
+ /**
293558
+ * Encapsulates all deferred-scheduling bookkeeping state for one scheduler queue.
293559
+ * Instantiate once per queue (alongside processItemSlotsManager) inside loop().
293560
+ */
293561
+ class DeferredSchedulingBookkeeper {
293562
+ constructor() {
293563
+ this._blockedAttempts = new Map();
293564
+ this._deferredEntryOrder = new Map();
293565
+ this._deferredOrderCounter = 0;
293566
+ }
293567
+ // --- Applicability Logic ---
293568
+ /**
293569
+ * Returns true only for supported parent kinds AND only for deletion operations.
293570
+ * Per spec Definition 2: applicable when operation is MARKED_TO_DELETION,
293571
+ * or RETRY with metadata.deletionTimestamp set.
293572
+ */
293573
+ isDeferredSchedulingApplicable(item) {
293574
+ const kind = item.item && item.item.kind ? item.item.kind : item.kind;
293575
+ if (!SUPPORTED_PARENT_KINDS.has(kind))
293576
+ return false;
293577
+ const op = item.operation;
293578
+ if (op === OperationType.MARKED_TO_DELETION)
293579
+ return true;
293580
+ if (op === OperationType.RETRY && item.item?.metadata?.deletionTimestamp)
293581
+ return true;
293582
+ return false;
293583
+ }
293584
+ // --- Bookkeeping API ---
293585
+ /**
293586
+ * Increments block counter for item (only call when item has been detected as blocked).
293587
+ * Returns new value. Promotes item to deferred segment if threshold crossed.
293588
+ */
293589
+ incrementBlockedAttempt(item) {
293590
+ if (!this.isDeferredSchedulingApplicable(item))
293591
+ return 0;
293592
+ const id = getWorkItemIdentity(item);
293593
+ if (id === undefined)
293594
+ return 0;
293595
+ const prev = this._blockedAttempts.get(id) || 0;
293596
+ const next = prev + 1;
293597
+ this._blockedAttempts.set(id, next);
293598
+ if (next > MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER &&
293599
+ !this._deferredEntryOrder.has(id)) {
293600
+ this._deferredEntryOrder.set(id, ++this._deferredOrderCounter);
293601
+ }
293602
+ return next;
293603
+ }
293604
+ getBlockedAttempts(item) {
293605
+ if (!this.isDeferredSchedulingApplicable(item))
293606
+ return 0;
293607
+ const id = getWorkItemIdentity(item);
293608
+ if (id === undefined)
293609
+ return 0;
293610
+ return this._blockedAttempts.get(id) || 0;
293611
+ }
293612
+ isDeferred(item) {
293613
+ if (!this.isDeferredSchedulingApplicable(item))
293614
+ return false;
293615
+ return this.getBlockedAttempts(item) > MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER;
293616
+ }
293617
+ getDeferredOrder(item) {
293618
+ if (!this.isDeferredSchedulingApplicable(item))
293619
+ return undefined;
293620
+ const id = getWorkItemIdentity(item);
293621
+ if (id === undefined)
293622
+ return undefined;
293623
+ return this._deferredEntryOrder.get(id);
293624
+ }
293625
+ removeBookkeeping(item) {
293626
+ if (!this.isDeferredSchedulingApplicable(item))
293627
+ return;
293628
+ const id = getWorkItemIdentity(item);
293629
+ if (id === undefined)
293630
+ return;
293631
+ this._blockedAttempts.delete(id);
293632
+ this._deferredEntryOrder.delete(id);
293633
+ }
293634
+ // --- Queue Partitioning ---
293635
+ /**
293636
+ * Splits queue into non-deferred and deferred, preserving original order,
293637
+ * but sorts deferred by stable insertion order.
293638
+ */
293639
+ partitionQueueByDeferred(queue) {
293640
+ const nonDeferred = [];
293641
+ const deferred = [];
293642
+ for (const item of queue) {
293643
+ if (this.isDeferredSchedulingApplicable(item) && this.isDeferred(item)) {
293644
+ deferred.push(item);
293645
+ }
293646
+ else {
293647
+ nonDeferred.push(item);
293648
+ }
293649
+ }
293650
+ deferred.sort((a, b) => {
293651
+ const orderA = this.getDeferredOrder(a) || 0;
293652
+ const orderB = this.getDeferredOrder(b) || 0;
293653
+ return orderA - orderB;
293654
+ });
293655
+ return { nonDeferred, deferred };
293656
+ }
293657
+ // --- TESTING / DEBUG ---
293658
+ clearAllBookkeeping() {
293659
+ this._blockedAttempts.clear();
293660
+ this._deferredEntryOrder.clear();
293661
+ this._deferredOrderCounter = 0;
293662
+ }
293663
+ }
293664
+
293397
293665
  ;// CONCATENATED MODULE: ../operator/src/processItem.debug.ts
293398
293666
 
293399
293667
 
@@ -293884,12 +294152,12 @@ async function startCRStates(meter, kindList, namespace) {
293884
294152
 
293885
294153
 
293886
294154
 
293887
- function initProcessItemsSlots(nSlots, initMetrics = true) {
294155
+ function initProcessItemsSlots(nSlots, initMetrics = true, onWorkItemBlockedByHandler) {
293888
294156
  operator_src_logger.info(`Initializing ${nSlots} processing slots...`);
293889
- return new ProcessItemSlots(nSlots, initMetrics);
294157
+ return new ProcessItemSlots(nSlots, initMetrics, onWorkItemBlockedByHandler);
293890
294158
  }
293891
294159
  class ProcessItemSlots {
293892
- constructor(nSlots, initMetrics) {
294160
+ constructor(nSlots, initMetrics, onWorkItemBlockedByHandler) {
293893
294161
  this._guardRails = new GuardRails();
293894
294162
  // let's init the slots
293895
294163
  this._nSlots = nSlots;
@@ -293906,6 +294174,7 @@ class ProcessItemSlots {
293906
294174
  slot.actions = {
293907
294175
  blockItem: (item) => this._guardRails.block(item),
293908
294176
  unblockItem: (item) => this._guardRails.unblock(item),
294177
+ onWorkItemBlockedByHandler,
293909
294178
  };
293910
294179
  return slot;
293911
294180
  });
@@ -294016,6 +294285,9 @@ class ProcessItemSlot {
294016
294285
  if ('needsBlocking' in workItem.handler &&
294017
294286
  workItem.handler.needsBlocking(item, workItem.operation)) {
294018
294287
  operator_src_logger.debug(`Item ${item.kind}/${item.metadata.namespace} needs blocking`);
294288
+ // Fire-and-forget: the callback is synchronous bookkeeping only;
294289
+ // no async side effects are expected from this callback.
294290
+ this._actions?.onWorkItemBlockedByHandler?.(workItem);
294019
294291
  return;
294020
294292
  }
294021
294293
  workItem.workStatus = WorkStatus.PROCESSING;
@@ -294037,6 +294309,7 @@ class ProcessItemSlot {
294037
294309
 
294038
294310
 
294039
294311
 
294312
+
294040
294313
  const queue = [];
294041
294314
  const WEIGHTS = {
294042
294315
  RENAMED: 15,
@@ -294157,12 +294430,26 @@ async function processItem(workItem) {
294157
294430
  async function processItem_loop() {
294158
294431
  loopWorkItemDebug(queue);
294159
294432
  let podIsTerminating = false;
294160
- const processItemSlotsManager = initProcessItemsSlots(MAX_SLOTS);
294161
- const nextWorkItem = () => sortQueue(queue)
294162
- .filter((w) => !w.isBlocked &&
294163
- !w.isPicked &&
294164
- !processItemSlotsManager.isWorkItemBlocked(w))
294165
- .find((w) => w.workStatus === WorkStatus.PENDING);
294433
+ const deferredBookkeeper = new DeferredSchedulingBookkeeper();
294434
+ const processItemSlotsManager = initProcessItemsSlots(MAX_SLOTS, true, (w) => deferredBookkeeper.incrementBlockedAttempt(w));
294435
+ if (process.env.GARBAGE_QUEUE_COLLECTOR) {
294436
+ void workItemGarbageCollector(queue, deferredBookkeeper);
294437
+ }
294438
+ const nextWorkItem = () => {
294439
+ // First, sort the queue and partition it for deferred scheduling.
294440
+ const sorted = sortQueue(queue);
294441
+ const { nonDeferred, deferred } = deferredBookkeeper.partitionQueueByDeferred(sorted);
294442
+ // Helper for selection logic
294443
+ function pickEligible(arr) {
294444
+ return arr
294445
+ .filter((w) => !w.isBlocked &&
294446
+ !w.isPicked &&
294447
+ !processItemSlotsManager.isWorkItemBlocked(w))
294448
+ .find((w) => w.workStatus === WorkStatus.PENDING);
294449
+ }
294450
+ // Always prefer non-deferred, only defer if absolutely nothing eligible
294451
+ return pickEligible(nonDeferred) || pickEligible(deferred);
294452
+ };
294166
294453
  initSignalsHandler(new Map([['SIGTERM', () => (podIsTerminating = true)]]));
294167
294454
  operator_src_logger.info(`
294168
294455
  ------- Processor main loop started with a maximum of '${MAX_SLOTS}' concurrent slots to process items. -------
@@ -294239,24 +294526,23 @@ function processItem_wait(t = 2000) {
294239
294526
  * Periodically checks the queue for finished WorkItems and removes them
294240
294527
  * @param {WorkItem[]} queue - Queue of WorkItems
294241
294528
  */
294242
- async function workItemGarbageCollector(queue) {
294529
+ async function workItemGarbageCollector(queue, bookkeeper) {
294243
294530
  while (1) {
294244
294531
  operator_src_logger.debug(`The garbage collector processed '${queue.length}' work items.`);
294245
- for (const [index, wi] of queue.entries()) {
294532
+ for (let i = queue.length - 1; i >= 0; i--) {
294533
+ const wi = queue[i];
294246
294534
  if (wi.workStatus === WorkStatus.FINISHED) {
294535
+ bookkeeper?.removeBookkeeping(wi);
294247
294536
  // Because the queue is a constant, we cannot reassign it, instead we
294248
294537
  // use splice which modifies the array in place
294249
294538
  //wi.onDelete()
294250
- queue.splice(index, 1);
294539
+ queue.splice(i, 1);
294251
294540
  }
294252
294541
  }
294253
294542
  operator_src_logger.debug(`The garbage collector finished its run, leaving '${queue.length}' work items in the queue.`);
294254
294543
  await processItem_wait(10 * 1000);
294255
294544
  }
294256
294545
  }
294257
- if (process.env.GARBAGE_QUEUE_COLLECTOR) {
294258
- void workItemGarbageCollector(queue);
294259
- }
294260
294546
 
294261
294547
  ;// CONCATENATED MODULE: ../terraform_provisioner/src/tf/analyzer.ts
294262
294548
 
@@ -296792,7 +297078,7 @@ async function* process_operation_markedToDeletion(item, op, handler) {
296792
297078
  reason: op,
296793
297079
  type: 'PROVISIONED',
296794
297080
  status: 'False',
296795
- message: 'Synth CDKTF',
297081
+ message: 'Synth',
296796
297082
  };
296797
297083
  yield {
296798
297084
  item,
@@ -296914,7 +297200,7 @@ async function* doApply(item, op, handler) {
296914
297200
  reason: op,
296915
297201
  type: 'PROVISIONED',
296916
297202
  status: 'False',
296917
- message: 'Synth CDKTF',
297203
+ message: 'Synth',
296918
297204
  };
296919
297205
  let output = '';
296920
297206
  const type = 'PROVISIONING';
@@ -298218,8 +298504,8 @@ const MODULES = {
298218
298504
  },
298219
298505
  FirestartrGithubRepository: {
298220
298506
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-repo',
298221
- // github-repo-v0.4.1
298222
- ref: 'addd7ce4a1054933c7f254a3d650c550c6a44024',
298507
+ // github-repo-v0.5.1
298508
+ ref: '67c7afaec38c1fdc9f1928090c181ef310fb81ec',
298223
298509
  },
298224
298510
  FirestartrGithubRepositoryFeature: {
298225
298511
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-files-set',
@@ -298683,16 +298969,67 @@ function provisionDefaultBranch(fsGithubRepository) {
298683
298969
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/codeowners.ts
298684
298970
 
298685
298971
 
298972
+ // Helper: Escape RegExp special characters in owner strings
298973
+ function codeowners_escapeRegExp(string) {
298974
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
298975
+ }
298686
298976
  async function provisionCodeowners(fsGithubRepository) {
298687
298977
  const cr = fsGithubRepository.cr;
298978
+ let codeownersText = cr.spec.repo.codeowners;
298979
+ const org = cr.spec.org;
298980
+ if (typeof codeownersText !== 'string') {
298981
+ gh_provisioner_src_logger.debug('[provisionCodeowners] No CODEOWNERS content configured; skipping provisioning.');
298982
+ return;
298983
+ }
298984
+ // 1. Gather all group refs present in cr.spec.permissions (if any)
298985
+ const allRefs = [];
298986
+ if (cr.spec.permissions && Array.isArray(cr.spec.permissions)) {
298987
+ for (const perm of cr.spec.permissions) {
298988
+ if (perm.ref && perm.ref.kind === 'FirestartrGithubGroup') {
298989
+ allRefs.push(perm.ref);
298990
+ }
298991
+ }
298992
+ }
298993
+ // (Extend here if group refs appear in other fields in future specs)
298994
+ // 2. For each group dependency: get external name & slug
298995
+ const replacements = [];
298996
+ for (const ref of allRefs) {
298997
+ let dep;
298998
+ try {
298999
+ dep = base_Entity.refResolver(ref);
299000
+ }
299001
+ catch (e) {
299002
+ gh_provisioner_src_logger.error(`[provisionCodeowners] Failed to resolve group ref ${ref.name}: ${String(e)}`);
299003
+ continue;
299004
+ }
299005
+ const extName = dep?.cr?.metadata?.annotations?.['firestartr.dev/external-name'];
299006
+ const slug = dep?.getOutput && dep.getOutput('slug');
299007
+ if (!extName) {
299008
+ gh_provisioner_src_logger.error(`[provisionCodeowners] Missing external-name annotation for group ref ${ref.name}`);
299009
+ continue;
299010
+ }
299011
+ if (!slug) {
299012
+ gh_provisioner_src_logger.error(`[provisionCodeowners] Missing slug output for group '${extName}' (${ref.name}) - will preserve as-is.`);
299013
+ continue;
299014
+ }
299015
+ const from = `@${org}/${extName}`;
299016
+ const to = `@${org}/${slug}`;
299017
+ replacements.push({ from, to });
299018
+ }
299019
+ // 3. Replace all exact occurrences of each external name owner with slug owner
299020
+ for (const { from, to } of replacements) {
299021
+ // Needs literal match, even if source owner has spaces/punctuation
299022
+ codeownersText = codeownersText.replace(new RegExp(codeowners_escapeRegExp(from), 'g'), to);
299023
+ }
299024
+ // Do not reformat, parse, or canonicalize further (per spec: preserve layout except for replacements)
298688
299025
  const config = {
298689
299026
  branch: cr.spec.repo.defaultBranch,
298690
299027
  commitMessage: 'ci: provision CODEOWNERS file',
298691
- content: cr.spec.repo.codeowners,
299028
+ content: codeownersText,
298692
299029
  file: '.github/CODEOWNERS',
298693
299030
  overwriteOnCreate: true,
298694
299031
  };
298695
- gh_provisioner_src_logger.debug(`Content of the codeowners: ${cr.spec.repo.codeowners}`);
299032
+ gh_provisioner_src_logger.debug(`[provisionCodeowners] Final CODEOWNERS content: ${codeownersText}`);
298696
299033
  fsGithubRepository.patchData({
298697
299034
  path: '/config/files/-',
298698
299035
  op: PatchOperations.add,
@@ -298886,6 +299223,48 @@ function checkIfLabelExists(labelName, repoIssuesList) {
298886
299223
  return repoIssuesList.some((label) => label.name === labelName);
298887
299224
  }
298888
299225
 
299226
+ ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/branch_protections.ts
299227
+
299228
+
299229
+ /**
299230
+ * Adds branch protection config to the synthesized Terraform config,
299231
+ * preserving legacy field semantics exactly (empty lists, explicit booleans, names).
299232
+ * @param entity The EntityGHRepo instance
299233
+ */
299234
+ function provisionBranchProtections(entity) {
299235
+ const protections = entity.cr.spec.branchProtections;
299236
+ if (!protections || !Array.isArray(protections) || protections.length === 0) {
299237
+ return;
299238
+ }
299239
+ for (const protection of protections) {
299240
+ // This shape should match the compatible Terraform contract
299241
+ const output = {};
299242
+ // Only emit legacy fields in allowed order
299243
+ output.branch = protection.branch;
299244
+ output.statusChecks =
299245
+ 'statusChecks' in protection ? (protection.statusChecks ?? []) : [];
299246
+ if ('requiredReviewersCount' in protection)
299247
+ output.requiredReviewersCount = protection.requiredReviewersCount;
299248
+ if ('requiredCodeownersReviewers' in protection)
299249
+ output.requiredCodeownersReviewers =
299250
+ protection.requiredCodeownersReviewers;
299251
+ if ('enforceAdmins' in protection)
299252
+ output.enforceAdmins = protection.enforceAdmins;
299253
+ if ('requireSignedCommits' in protection)
299254
+ output.requireSignedCommits = protection.requireSignedCommits;
299255
+ if ('requireConversationResolution' in protection)
299256
+ output.requireConversationResolution =
299257
+ protection.requireConversationResolution;
299258
+ // Only keep fields that are present, including explicit false/empty
299259
+ gh_provisioner_src_logger.debug(`[gh-provisioner] Adding branch protection config: ${JSON.stringify(output)}`);
299260
+ entity.patchData({
299261
+ op: PatchOperations.add,
299262
+ path: '/config/branch_protections/-',
299263
+ value: output,
299264
+ });
299265
+ }
299266
+ }
299267
+
298889
299268
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/helpers/index.ts
298890
299269
 
298891
299270
 
@@ -298894,6 +299273,7 @@ function checkIfLabelExists(labelName, repoIssuesList) {
298894
299273
 
298895
299274
 
298896
299275
 
299276
+
298897
299277
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepo/post/additional_branches.ts
298898
299278
 
298899
299279
 
@@ -298960,6 +299340,9 @@ async function provisionRegularBranch(repo, branchName, sourceBranch, org) {
298960
299340
 
298961
299341
 
298962
299342
  class EntityGHRepo extends base_Entity {
299343
+ escapeTerraformBranchName(value) {
299344
+ return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
299345
+ }
298963
299346
  constructor(artifact) {
298964
299347
  super(artifact, {
298965
299348
  config: {
@@ -298968,6 +299351,7 @@ class EntityGHRepo extends base_Entity {
298968
299351
  teams: [],
298969
299352
  collaborators: [],
298970
299353
  labels: [],
299354
+ branch_protections: [],
298971
299355
  },
298972
299356
  });
298973
299357
  }
@@ -299022,6 +299406,8 @@ class EntityGHRepo extends base_Entity {
299022
299406
  await provisionDefaultBranch(this);
299023
299407
  await provisionCodeowners(this);
299024
299408
  await provisionVariables(this);
299409
+ // Add branch protections to config if defined
299410
+ provisionBranchProtections(this);
299025
299411
  await provisionOIDCSubjectClaim(this);
299026
299412
  await provisionPermissions(this);
299027
299413
  await provisionLabels(this, repoAlreadyExists);
@@ -299071,6 +299457,21 @@ class EntityGHRepo extends base_Entity {
299071
299457
  id: this.cr.name,
299072
299458
  },
299073
299459
  });
299460
+ // Import github_branch_protection ONLY for protections declared in CR, not GitHub state
299461
+ const protections = this.cr.spec.branchProtections;
299462
+ if (protections && Array.isArray(protections) && protections.length > 0) {
299463
+ for (const protection of protections) {
299464
+ const branch = protection.branch;
299465
+ this.patchImportData({
299466
+ op: PatchOperations.add,
299467
+ path: '/imports/-',
299468
+ value: {
299469
+ to: `github_branch_protection.this["${this.escapeTerraformBranchName(branch)}"]`,
299470
+ id: `${this.cr.name}:${this.escapeTerraformBranchName(branch)}`,
299471
+ },
299472
+ });
299473
+ }
299474
+ }
299074
299475
  // we cannot ensure the files exist, thus we need to check
299075
299476
  // it they exist before importing them
299076
299477
  //for (const file of this.document.config.files) {
@@ -299093,6 +299494,19 @@ class EntityGHRepo extends base_Entity {
299093
299494
  },
299094
299495
  });
299095
299496
  }
299497
+ // Import Pages resource if configured.
299498
+ // When a repository is manually created with Pages already enabled,
299499
+ // we must import the existing resource to avoid a 409 conflict on apply.
299500
+ if (this.cr.spec.pages) {
299501
+ this.patchImportData({
299502
+ op: PatchOperations.add,
299503
+ path: '/imports/-',
299504
+ value: {
299505
+ to: 'github_repository_pages.this[0]',
299506
+ id: this.cr.name,
299507
+ },
299508
+ });
299509
+ }
299096
299510
  }
299097
299511
  async provisionRepository() {
299098
299512
  this.patchData({
@@ -301739,9 +302153,9 @@ const crs_analyzerSubcommand = {
301739
302153
  };
301740
302154
 
301741
302155
  ;// CONCATENATED MODULE: ./package.json
301742
- const package_namespaceObject = JSON.parse('{"i8":"2.5.0-snapshot-1"}');
302156
+ const package_namespaceObject = {"i8":"2.5.0"};
301743
302157
  ;// CONCATENATED MODULE: ../../package.json
301744
- const package_namespaceObject_1 = {"i8":"2.4.0"};
302158
+ const package_namespaceObject_1 = {"i8":"2.5.0"};
301745
302159
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
301746
302160
 
301747
302161
 
@@ -104,6 +104,15 @@ declare const _default: {
104
104
  tokenizer: {
105
105
  SimpleTokenizer: typeof import("./src/tokenizer").SimpleTokenizer;
106
106
  };
107
+ codeowners: {
108
+ parse: typeof import("./src/codeowners").parse;
109
+ format: typeof import("./src/codeowners").format;
110
+ validate: typeof import("./src/codeowners").validate;
111
+ updateRule: typeof import("./src/codeowners").updateRule;
112
+ remove: typeof import("./src/codeowners").remove;
113
+ getOwners: typeof import("./src/codeowners").getOwners;
114
+ resolveRefs: typeof import("./src/codeowners").resolveRefs;
115
+ };
107
116
  cron: {
108
117
  validateCron: typeof validateCron;
109
118
  isValidCron: typeof isValidCron;
@@ -0,0 +1,26 @@
1
+ export interface CodeownersEntry {
2
+ type: 'rule' | 'comment' | 'blank';
3
+ pattern?: string;
4
+ owners?: string[];
5
+ raw: string;
6
+ }
7
+ export declare function parse(raw: string): CodeownersEntry[];
8
+ export declare function format(entries: CodeownersEntry[]): string;
9
+ export declare function validate(raw: string): {
10
+ valid: boolean;
11
+ errors: string[];
12
+ };
13
+ export declare function updateRule(entries: CodeownersEntry[], pattern: string, owners: string[]): CodeownersEntry[];
14
+ export declare function remove(entries: CodeownersEntry[], pattern: string): CodeownersEntry[];
15
+ export declare function getOwners(entries: CodeownersEntry[]): string[];
16
+ export declare function resolveRefs(raw: string, replacements: Record<string, string>): string;
17
+ declare const _default: {
18
+ parse: typeof parse;
19
+ format: typeof format;
20
+ validate: typeof validate;
21
+ updateRule: typeof updateRule;
22
+ remove: typeof remove;
23
+ getOwners: typeof getOwners;
24
+ resolveRefs: typeof resolveRefs;
25
+ };
26
+ export default _default;
@@ -25,11 +25,7 @@ export declare enum envVars {
25
25
  s3Bucket = "S3_BUCKET",
26
26
  s3Lock = "S3_LOCK",
27
27
  s3Region = "S3_REGION",
28
- cdktfConfigFiles = "CDKTF_CONFIG_FILES",
29
28
  exclusionsYamlPath = "EXCLUSIONS_PATH",
30
- cdktfEntityPath = "FIRESTARTR_CDKTF_ENTITY_PATH",
31
- cdktfDepsPath = "FIRESTARTR_CDKTF_DEPS_PATH",
32
- cdktfIsImport = "FIRESTARTR_CDKTF_IS_IMPORT",
33
29
  githubAppId = "GITHUB_APP_ID",
34
30
  githubAppInstallationId = "GITHUB_APP_INSTALLATION_ID",
35
31
  githubAppInstallationIdPrefapp = "GITHUB_APP_INSTALLATION_ID_PREFAPP",
@@ -0,0 +1,7 @@
1
+ import { EntityGHRepo } from '../';
2
+ /**
3
+ * Adds branch protection config to the synthesized Terraform config,
4
+ * preserving legacy field semantics exactly (empty lists, explicit booleans, names).
5
+ * @param entity The EntityGHRepo instance
6
+ */
7
+ export declare function provisionBranchProtections(entity: EntityGHRepo): void;
@@ -4,3 +4,4 @@ export { provisionVariables } from './variables';
4
4
  export { provisionOIDCSubjectClaim } from './actions_oidc';
5
5
  export { provisionPermissions } from './teams';
6
6
  export { provisionLabels } from './labels';
7
+ export { provisionBranchProtections } from './branch_protections';
@@ -1,5 +1,6 @@
1
1
  import { Entity } from '../base';
2
2
  export declare class EntityGHRepo extends Entity {
3
+ private escapeTerraformBranchName;
3
4
  constructor(artifact: any);
4
5
  /**
5
6
  * Validation logic for GitHub Pages branch settings:
@@ -1,6 +1,20 @@
1
+ import type { Dependencies } from './resolver';
1
2
  export declare function getKindFromPlural(plural: string): any;
2
3
  export declare function getPluralFromKind(kind: string): string;
3
- import type { Dependencies } from './resolver';
4
+ /**
5
+ * Helper to extract the canonical identity (metadata.uid) from a WorkItem or its item.
6
+ *
7
+ * Use this helper when you need a stable identity key for WorkItems, e.g. for
8
+ * in-memory deferred-scheduling bookkeeping.
9
+ *
10
+ * Note: other subsystems may still use kind/namespace/name keys; this helper is
11
+ * specifically intended for UID-based WorkItem identity.
12
+ * Any future change to identity logic MUST be made here and nowhere else.
13
+ *
14
+ * Returns undefined if .metadata.uid is missing so scheduler/bookkeeping paths
15
+ * can safely short-circuit instead of throwing and aborting an entire pass.
16
+ */
17
+ export declare function getWorkItemIdentity(wi: WorkItem): string | undefined;
4
18
  export declare enum OperationType {
5
19
  RENAMED = "RENAMED",
6
20
  UPDATED = "UPDATED",
@@ -0,0 +1,40 @@
1
+ import type { WorkItem } from './definitions';
2
+ /**
3
+ * Maximum number of blocked attempts allowed before an item is moved to the deferred segment.
4
+ * An item becomes deferred once its blocked-attempt count exceeds this value.
5
+ */
6
+ export declare const MAX_BLOCKED_ATTEMPTS_BEFORE_DEFER = 5;
7
+ export declare const SUPPORTED_PARENT_KINDS: Set<string>;
8
+ /**
9
+ * Encapsulates all deferred-scheduling bookkeeping state for one scheduler queue.
10
+ * Instantiate once per queue (alongside processItemSlotsManager) inside loop().
11
+ */
12
+ export declare class DeferredSchedulingBookkeeper {
13
+ private _blockedAttempts;
14
+ private _deferredEntryOrder;
15
+ private _deferredOrderCounter;
16
+ /**
17
+ * Returns true only for supported parent kinds AND only for deletion operations.
18
+ * Per spec Definition 2: applicable when operation is MARKED_TO_DELETION,
19
+ * or RETRY with metadata.deletionTimestamp set.
20
+ */
21
+ isDeferredSchedulingApplicable(item: WorkItem): boolean;
22
+ /**
23
+ * Increments block counter for item (only call when item has been detected as blocked).
24
+ * Returns new value. Promotes item to deferred segment if threshold crossed.
25
+ */
26
+ incrementBlockedAttempt(item: WorkItem): number;
27
+ getBlockedAttempts(item: WorkItem): number;
28
+ isDeferred(item: WorkItem): boolean;
29
+ getDeferredOrder(item: WorkItem): number | undefined;
30
+ removeBookkeeping(item: WorkItem): void;
31
+ /**
32
+ * Splits queue into non-deferred and deferred, preserving original order,
33
+ * but sorts deferred by stable insertion order.
34
+ */
35
+ partitionQueueByDeferred<T extends WorkItem>(queue: T[]): {
36
+ nonDeferred: T[];
37
+ deferred: T[];
38
+ };
39
+ clearAllBookkeeping(): void;
40
+ }
@@ -1,4 +1,5 @@
1
1
  import { WorkItem } from './informer';
2
+ import { DeferredSchedulingBookkeeper } from './processItem.blocks';
2
3
  export declare function getQueueMetrics(): {
3
4
  nItems: number;
4
5
  nItemsFinished: number;
@@ -25,4 +26,4 @@ export declare function processItem(workItem: WorkItem): Promise<void>;
25
26
  * Periodically checks the queue for finished WorkItems and removes them
26
27
  * @param {WorkItem[]} queue - Queue of WorkItems
27
28
  */
28
- export declare function workItemGarbageCollector(queue: WorkItem[]): Promise<void>;
29
+ export declare function workItemGarbageCollector(queue: WorkItem[], bookkeeper?: DeferredSchedulingBookkeeper): Promise<void>;
@@ -1,11 +1,11 @@
1
1
  import { WorkItem } from './definitions';
2
2
  import { ProcessItemSlotMetrics } from './metrics/processItem.slot.metrics';
3
- export declare function initProcessItemsSlots(nSlots: number, initMetrics?: boolean): ProcessItemSlots;
3
+ export declare function initProcessItemsSlots(nSlots: number, initMetrics?: boolean, onWorkItemBlockedByHandler?: (workItem: WorkItem) => void): ProcessItemSlots;
4
4
  export declare class ProcessItemSlots {
5
5
  _slots: ProcessItemSlot[];
6
6
  _nSlots: number;
7
7
  _guardRails: GuardRails;
8
- constructor(nSlots: number, initMetrics: boolean);
8
+ constructor(nSlots: number, initMetrics: boolean, onWorkItemBlockedByHandler?: (workItem: WorkItem) => void);
9
9
  getIdleSlots(): Generator<ProcessItemSlot, void, unknown>;
10
10
  allSlotsAreIdle(): boolean;
11
11
  isWorkItemBlocked(workItem: WorkItem): boolean;
@@ -24,16 +24,19 @@ export declare class ProcessItemSlot {
24
24
  _actions: {
25
25
  blockItem: (item: any) => void;
26
26
  unblockItem: (item: any) => void;
27
+ onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
27
28
  } | undefined;
28
29
  _metrics: ProcessItemSlotMetrics;
29
30
  constructor(id: number);
30
31
  set actions(actions: {
31
32
  blockItem: (item: any) => void;
32
33
  unblockItem: (item: any) => void;
34
+ onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
33
35
  });
34
36
  get actions(): {
35
37
  blockItem: (item: any) => void;
36
38
  unblockItem: (item: any) => void;
39
+ onWorkItemBlockedByHandler?: (workItem: WorkItem) => void;
37
40
  };
38
41
  get idle(): boolean;
39
42
  get id(): number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firestartr/cli",
3
- "version": "2.5.0-snapshot-1",
3
+ "version": "2.5.0",
4
4
  "private": false,
5
5
  "description": "Commandline tool",
6
6
  "main": "build/main.js",