@firestartr/cli 2.1.0-snapshot → 2.1.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.
Files changed (2) hide show
  1. package/build/index.js +63 -61
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -356187,39 +356187,33 @@ function createCRrefFrom(claimRef, needsSecret) {
356187
356187
  * @returns string
356188
356188
  */
356189
356189
  function createCodeOwnersData(claim, additionalRules) {
356190
- const paddedAsterisk = '*'.padEnd(25);
356191
- // we avoid generating the same line twice (idempotency)
356192
- const redactedLines = [];
356193
- const concatLine = function (messageFinal, line) {
356194
- if (redactedLines.includes(line)) {
356195
- return messageFinal;
356190
+ const rulesMap = new Map();
356191
+ const addOwnerToPath = (path, owner) => {
356192
+ const owners = rulesMap.get(path) ?? [];
356193
+ if (!owners.includes(owner)) {
356194
+ owners.push(owner);
356196
356195
  }
356197
- else {
356198
- redactedLines.push(line);
356199
- messageFinal += line;
356200
- }
356201
- return messageFinal;
356196
+ rulesMap.set(path, owners);
356202
356197
  };
356203
356198
  let message = `# This file was generated by firestartr.
356204
356199
  # WARNING: Please don't edit this file directly in the repository
356205
356200
  # Go to gitops repository to modify it!`;
356206
356201
  if (claim.owner) {
356207
- message = concatLine(message, `\n${paddedAsterisk}${resolveCodeownersRef(claim.owner, claim.providers.github.org)}`);
356202
+ addOwnerToPath('*', resolveCodeownersRef(claim.owner, claim.providers.github.org));
356208
356203
  }
356209
356204
  if (claim.platformOwner) {
356210
- message = concatLine(message, `\n${'/.github/'.padEnd(25)}${resolveCodeownersRef(claim.platformOwner, claim.providers.github.org)}`);
356205
+ addOwnerToPath('/.github/', resolveCodeownersRef(claim.platformOwner, claim.providers.github.org));
356211
356206
  }
356212
356207
  if (additionalRules) {
356213
356208
  for (const rule of additionalRules) {
356214
- let line = `\n${rule.path.padEnd(25)}`;
356215
356209
  for (const owner of rule.owners) {
356216
- line += `${resolveCodeownersRef(owner, claim.providers.github.org)} `;
356210
+ addOwnerToPath(rule.path, resolveCodeownersRef(owner, claim.providers.github.org));
356217
356211
  }
356218
- message = concatLine(message, line);
356219
- // Remove trailing <space> from the previous loop
356220
- message = message.substring(0, message.length - 1);
356221
356212
  }
356222
356213
  }
356214
+ for (const [path, owners] of rulesMap.entries()) {
356215
+ message += `\n${path.padEnd(25)} ${owners.join(' ')}`;
356216
+ }
356223
356217
  return message;
356224
356218
  }
356225
356219
  function resolveCodeownersRef(ref, org) {
@@ -367438,12 +367432,12 @@ class RepoGithubDecanter extends GithubDecanter {
367438
367432
  });
367439
367433
  }
367440
367434
  const directWriters = this.data.teamsAndMembers.directMembers
367441
- .filter((member) => member.role === 'push')
367435
+ .filter((member) => member.role === 'write')
367442
367436
  .map((member) => {
367443
367437
  return `user-imported-ref:${member.name}`;
367444
367438
  });
367445
367439
  const outsideWriters = this.data.teamsAndMembers.outsideMembers
367446
- .filter((member) => member.role === 'push')
367440
+ .filter((member) => member.role === 'write')
367447
367441
  .map((member) => {
367448
367442
  return `collaborator:${member.name.toLowerCase()}`;
367449
367443
  });
@@ -367462,12 +367456,12 @@ class RepoGithubDecanter extends GithubDecanter {
367462
367456
  });
367463
367457
  }
367464
367458
  const directReaders = this.data.teamsAndMembers.directMembers
367465
- .filter((member) => member.role === 'pull')
367459
+ .filter((member) => member.role === 'read')
367466
367460
  .map((member) => {
367467
367461
  return `user-imported-ref:${member.name}`;
367468
367462
  });
367469
367463
  const outsideReaders = this.data.teamsAndMembers.outsideMembers
367470
- .filter((member) => member.role === 'pull')
367464
+ .filter((member) => member.role === 'read')
367471
367465
  .map((member) => {
367472
367466
  return `collaborator:${member.name.toLowerCase()}`;
367473
367467
  });
@@ -367502,7 +367496,11 @@ class RepoGithubDecanter extends GithubDecanter {
367502
367496
  };
367503
367497
  });
367504
367498
  this.data['teamsAndMembers']['teams'] = teams;
367505
- this.data['teamsAndMembers']['directMembers'] = directMembers;
367499
+ // GitHub's `affiliation=direct` includes outside collaborators, so we must
367500
+ // exclude them from directMembers to avoid tagging them as user-imported-ref.
367501
+ const outsideMemberNames = new Set(outsideMembers.map((m) => m.name.toLowerCase()));
367502
+ const filteredDirectMembers = directMembers.filter((m) => !outsideMemberNames.has(m.name.toLowerCase()));
367503
+ this.data['teamsAndMembers']['directMembers'] = filteredDirectMembers;
367506
367504
  this.data['teamsAndMembers']['outsideMembers'] = outsideMembers;
367507
367505
  }
367508
367506
  async __gatherPages() {
@@ -367923,7 +367921,7 @@ async function reimportGithubGitopsRepository(org, crsPath, configPath, generate
367923
367921
  }
367924
367922
  cr.metadata.annotations[reImportAnnotation] = 'true';
367925
367923
  cr.metadata.annotations[importAnnotation] = 'true';
367926
- // we set an annotion to trigger the update
367924
+ // we set an annotation to trigger the update
367927
367925
  cr.metadata.annotations[reconcileAtAnnotation] = new Date()
367928
367926
  .toISOString()
367929
367927
  .replace(/\.\d{3}Z$/, 'Z'); // exact format: 2026-04-19T20:45:00Z
@@ -367935,10 +367933,10 @@ async function reimportGithubGitopsRepository(org, crsPath, configPath, generate
367935
367933
  // we need to search for dependencies if proceeds
367936
367934
  if (!collection.IS_SKIP_SET(generatedFilters, 'gh-repo')) {
367937
367935
  importer_src_logger.info(`Searching for dependencies to annotate with ${reImportAnnotation} and ${importAnnotation}`);
367938
- await searchForDependencies(org, crsPath, configPath, generatedFilters, crsWithDependenciesToAnnotate, reImportAnnotation, importAnnotation);
367936
+ await searchForDependencies(org, crsPath, configPath, generatedFilters, crsWithDependenciesToAnnotate, reImportAnnotation, importAnnotation, reconcileAtAnnotation);
367939
367937
  }
367940
367938
  }
367941
- async function searchForDependencies(org, crsPath, configPath, generatedFilters, crsWithDependenciesToAnnotate, reImportAnnotation, importAnnotation) {
367939
+ async function searchForDependencies(org, crsPath, configPath, generatedFilters, crsWithDependenciesToAnnotate, reImportAnnotation, importAnnotation, reconcileAtAnnotation) {
367942
367940
  // we need to perform a search in all the crsPath
367943
367941
  await searchCRs(crsPath, async (cr, filePath) => {
367944
367942
  importer_src_logger.debug(`${JSON.stringify(cr)}`);
@@ -367956,6 +367954,10 @@ async function searchForDependencies(org, crsPath, configPath, generatedFilters,
367956
367954
  }
367957
367955
  cr.metadata.annotations[reImportAnnotation] = 'true';
367958
367956
  cr.metadata.annotations[importAnnotation] = 'true';
367957
+ // we set an annotation to trigger the update
367958
+ cr.metadata.annotations[reconcileAtAnnotation] = new Date()
367959
+ .toISOString()
367960
+ .replace(/\.\d{3}Z$/, 'Z'); // exact format: 2026-04-19T20:45:00Z
367959
367961
  // we write the cr back to the file system
367960
367962
  const newContent = catalog_common.io.toYaml(cr);
367961
367963
  await promises_default().writeFile(filePath, newContent, 'utf-8');
@@ -369061,7 +369063,7 @@ function shouldForceReconcileByAnnotation(cr) {
369061
369063
  operator_src_logger.warn(`Timestamp is invalid for the annotation ${cr.kind}/${cr.metadata?.name || 'unknown'}: ${annotationValue}`);
369062
369064
  return false;
369063
369065
  }
369064
- // Buscamos el timestamp más reciente de TODAS las conditions
369066
+ // Find the most recent timestamp across all conditions
369065
369067
  let latestConditionTime = 0;
369066
369068
  for (const condition of cr.status?.conditions || []) {
369067
369069
  const timeStr = condition.lastUpdateTime || condition.lastTransitionTime;
@@ -369073,7 +369075,7 @@ function shouldForceReconcileByAnnotation(cr) {
369073
369075
  }
369074
369076
  }
369075
369077
  const shouldForce = annotationTime > latestConditionTime;
369076
- operator_src_logger.debug(shouldForce, `Evaluating force-reconcile for ${cr.metadata.name}: annotation time (${new Date(annotationTime).toISOString()}) vs latest condition time (${new Date(latestConditionTime).toISOString()})`);
369078
+ operator_src_logger.debug(`Evaluating force-reconcile for ${cr.metadata.name}: annotation time (${new Date(annotationTime).toISOString()}) vs latest condition time (${new Date(latestConditionTime).toISOString()})`, { metadata: { shouldForce } });
369077
369079
  if (shouldForce) {
369078
369080
  operator_src_logger.info(`force-reconcile activated for ${cr.metadata.name} → annotation (${annotationValue}) is newer than latest condition time (${new Date(latestConditionTime).toISOString()})`);
369079
369081
  }
@@ -370757,6 +370759,19 @@ function getQueueMetrics() {
370757
370759
  },
370758
370760
  };
370759
370761
  }
370762
+ // we need to assign weight to the deletion operation
370763
+ // to avoid blockades
370764
+ // https://github.com/prefapp/gitops-k8s/issues/1864
370765
+ // ghrepo feat | grss -> ghrepo -> ghgroup -> membership
370766
+ const DELETION_WEIGHTS = {
370767
+ FirestartrGithubRepositoryFeature: 5,
370768
+ FirestartrGithubRepositorySecretsSection: 4,
370769
+ FirestartrGithubRepository: 3,
370770
+ FirestartrGithubGroup: 2,
370771
+ FirestartrGithubMembership: 1,
370772
+ FirestartrTerraformWorkspace: 1,
370773
+ FirestartrGithubOrgWebhook: 1,
370774
+ };
370760
370775
  // Do the kinds need different weights for the operations?
370761
370776
  // We need to discuss this with the team in this issue: https://github.com/prefapp/gitops-k8s/issues/524
370762
370777
  // const KIND_WEIGHTS: any = {
@@ -370826,7 +370841,17 @@ function sortQueue(queue) {
370826
370841
  // If weightA is larger, return -1 (a comes before b)
370827
370842
  return weightB - weightA;
370828
370843
  }
370829
- // --- SECONDARY SORT: By upsertTime (Ascending) ---
370844
+ // in case of deletions we need to
370845
+ // establish a secondary sort by kind
370846
+ if (wa.operation === 'MARKED_TO_DELETION' &&
370847
+ wb.operation === 'MARKED_TO_DELETION') {
370848
+ const deletionWeightA = DELETION_WEIGHTS[wa.item.kind] || 0;
370849
+ const deletionWeightB = DELETION_WEIGHTS[wb.item.kind] || 0;
370850
+ if (deletionWeightA !== deletionWeightB) {
370851
+ return deletionWeightB - deletionWeightA;
370852
+ }
370853
+ }
370854
+ // --- 3rd SORT: By upsertTime (Ascending) ---
370830
370855
  // If weights are equal, sort by the oldest upsertTime (ascending)
370831
370856
  return wa.upsertTime - wb.upsertTime;
370832
370857
  });
@@ -371084,6 +371109,7 @@ function processHandler(processToHandle, ctl, onTimedOut) {
371084
371109
 
371085
371110
 
371086
371111
 
371112
+ const TOFU_LOCK_TIMEOUT = '-lock-timeout=60s';
371087
371113
  async function utils_validate(path, secrets) {
371088
371114
  return await tfExec(path, ['validate'], secrets);
371089
371115
  }
@@ -371104,7 +371130,7 @@ async function plan(path, secrets, format, args = ['plan'], stream, ctl) {
371104
371130
  }
371105
371131
  async function apply(path, secrets, stream, ctl) {
371106
371132
  terraform_provisioner_src_logger.debug(`Running terraform apply in path ${path}`);
371107
- return await tfExec(path, ['apply', '-auto-approve'], secrets, ['-input=false'], stream, ctl);
371133
+ return await tfExec(path, ['apply', '-auto-approve', TOFU_LOCK_TIMEOUT], secrets, ['-input=false'], stream, ctl);
371108
371134
  }
371109
371135
  async function customCommand(path, secrets, args, stream) {
371110
371136
  terraform_provisioner_src_logger.debug(`Running terraform customCommand in path ${path} ${args.join(',')}`);
@@ -371112,7 +371138,7 @@ async function customCommand(path, secrets, args, stream) {
371112
371138
  }
371113
371139
  async function destroy(path, secrets, stream, ctl) {
371114
371140
  terraform_provisioner_src_logger.debug(`Running terraform destroy in path ${path}`);
371115
- return await tfExec(path, ['destroy', '-auto-approve'], secrets, ['-input=false'], stream, ctl);
371141
+ return await tfExec(path, ['destroy', '-auto-approve', TOFU_LOCK_TIMEOUT], secrets, ['-input=false'], stream, ctl);
371116
371142
  }
371117
371143
  async function output(path, secrets) {
371118
371144
  terraform_provisioner_src_logger.debug(`Running terraform output in path ${path}`);
@@ -375266,47 +375292,23 @@ async function endDebug(entity) {
375266
375292
 
375267
375293
 
375268
375294
  const tp_bridge_TF_PROJECTS_PATH = '/tmp/gh-workspaces';
375269
- const STATE_LOCK_RETRY_ATTEMPTS = 3;
375270
- const STATE_LOCK_RETRY_DELAY_MS = 5000;
375271
- function isStateLockError(error) {
375272
- const message = String(error);
375273
- return (message.includes('Error acquiring the state lock') ||
375274
- message.includes('already locked by another tofu client'));
375275
- }
375276
- async function tp_bridge_sleep(ms) {
375277
- await new Promise((resolve) => setTimeout(resolve, ms));
375278
- }
375279
- async function runTerraformProvisionerWithLockRetry(entity, command, streaming, customArgs, ctl) {
375280
- for (let attempt = 1;; attempt++) {
375281
- try {
375282
- return await runTerraformProvisioner(tp_bridge_buildContext(entity), command, streaming, customArgs, ctl);
375283
- }
375284
- catch (error) {
375285
- if (!isStateLockError(error) || attempt >= STATE_LOCK_RETRY_ATTEMPTS) {
375286
- throw error;
375287
- }
375288
- gh_provisioner_src_logger.warn(`State lock detected for ${entity.k8sId} while running '${command}'. Retrying in ${STATE_LOCK_RETRY_DELAY_MS}ms (attempt ${attempt + 1}/${STATE_LOCK_RETRY_ATTEMPTS}).`);
375289
- await tp_bridge_sleep(STATE_LOCK_RETRY_DELAY_MS);
375290
- }
375291
- }
375292
- }
375293
375295
  async function runOnTerraform(entity, command, customArgs, opts = {}) {
375294
375296
  gh_provisioner_src_logger.info(`Running on terraform entity '${entity.k8sId}' with command '${command}' customArgs = ${customArgs ? customArgs.join(',') : 'null'} `);
375295
375297
  const streaming = entity.streamTFProvisioner;
375296
375298
  if (command === 'import-with-reimport') {
375297
375299
  // we nuke the tf state
375298
- await runTerraformProvisionerWithLockRetry(entity, 'destroy-state-only', streaming, customArgs, opts.ctl);
375300
+ await runTerraformProvisioner(tp_bridge_buildContext(entity), 'destroy-state-only', streaming, customArgs, opts.ctl);
375299
375301
  // we get the elements we need to import
375300
375302
  await entity.loadAddressesToImport();
375301
- return await runTerraformProvisionerWithLockRetry(entity, 'custom-import', streaming, entity.importDocument, opts.ctl);
375303
+ return await runTerraformProvisioner(tp_bridge_buildContext(entity), 'custom-import', streaming, entity.importDocument, opts.ctl);
375302
375304
  }
375303
375305
  else if (command === 'import') {
375304
375306
  // we get the elements we need to import
375305
375307
  await entity.loadAddressesToImport();
375306
- return await runTerraformProvisionerWithLockRetry(entity, 'custom-import', streaming, entity.importDocument, opts.ctl);
375308
+ return await runTerraformProvisioner(tp_bridge_buildContext(entity), 'custom-import', streaming, entity.importDocument, opts.ctl);
375307
375309
  }
375308
375310
  else {
375309
- return await runTerraformProvisionerWithLockRetry(entity, command, streaming, customArgs, opts.ctl);
375311
+ return await runTerraformProvisioner(tp_bridge_buildContext(entity), command, streaming, customArgs, opts.ctl);
375310
375312
  }
375311
375313
  }
375312
375314
  function tp_bridge_buildContext(entity) {
@@ -379455,9 +379457,9 @@ const crs_analyzerSubcommand = {
379455
379457
  };
379456
379458
 
379457
379459
  ;// CONCATENATED MODULE: ./package.json
379458
- const package_namespaceObject = JSON.parse('{"i8":"2.1.0-snapshot"}');
379460
+ const package_namespaceObject = {"i8":"2.1.0"};
379459
379461
  ;// CONCATENATED MODULE: ../../package.json
379460
- const package_namespaceObject_1 = {"i8":"2.0.0"};
379462
+ const package_namespaceObject_1 = {"i8":"2.1.0"};
379461
379463
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
379462
379464
 
379463
379465
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firestartr/cli",
3
- "version": "2.1.0-snapshot",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "Commandline tool",
6
6
  "main": "build/main.js",