@firestartr/cli 2.7.0-snapshot-17 → 2.7.0-snapshot-19

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/build/index.js CHANGED
@@ -290172,7 +290172,7 @@ async function renderClaim(catalogScope, firestartrScope, claim, patches, previo
290172
290172
  .map((segment) => encodeURIComponent(segment))
290173
290173
  .join('/');
290174
290174
  apiAnnotations['backstage.io/edit-url'] =
290175
- `${repoUrl}/blob/${defaultBranch}/${relativePath}`;
290175
+ `${repoUrl}/blob/${getDefaultBranch()}/${relativePath}`;
290176
290176
  }
290177
290177
  const apiChart = new charts.CatalogApiChart(catalogScope, `${chartId}-api-${sanitizedApiName}`, firestartrId, {
290178
290178
  name: sanitizedApiName,
@@ -298747,16 +298747,16 @@ async function TFCheckRun(cmd, item) {
298747
298747
  checkRun.mdOptionsDetails({
298748
298748
  quotes: 'terraform',
298749
298749
  });
298750
- checkRun.update('Initiating', 'queued');
298750
+ void checkRun.update('Initiating', 'queued');
298751
298751
  return {
298752
298752
  fnData: (d) => {
298753
- checkRun.update(d.toString(), 'in_progress');
298753
+ void checkRun.update(d.toString(), 'in_progress');
298754
298754
  },
298755
298755
  fnEnd: () => {
298756
- checkRun.close('OK', true);
298756
+ void checkRun.close('OK', true);
298757
298757
  },
298758
298758
  fnOnError: (_) => {
298759
- checkRun.close('KO', false);
298759
+ void checkRun.close('KO', false);
298760
298760
  },
298761
298761
  };
298762
298762
  }
@@ -300635,8 +300635,8 @@ const MODULES = {
300635
300635
  },
300636
300636
  FirestartrGithubRepositorySecretsSection: {
300637
300637
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-repo-secrets-section',
300638
- // github-repo-secrets-section-v0.1.1
300639
- ref: '7753571515c6bd7311a7957b4b172d2787f1512d',
300638
+ // github-repo-secrets-section-v0.1.2
300639
+ ref: 'c2e983dc57d8f879fd8ec4df048fbb604e4f04b2',
300640
300640
  },
300641
300641
  FirestartrGithubRepository: {
300642
300642
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-repo',
@@ -300645,8 +300645,8 @@ const MODULES = {
300645
300645
  },
300646
300646
  FirestartrGithubRepositoryFeature: {
300647
300647
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-files-set',
300648
- // github-files-set-v0.1.0
300649
- ref: '2c4f39e995d165297550675b594c6b57812b3847',
300648
+ // fix/provision-once-then-untrack
300649
+ ref: 'fix/provision-once-then-untrack',
300650
300650
  },
300651
300651
  FirestartrGithubMembership: {
300652
300652
  module: 'git::https://github.com/prefapp/tfm.git//modules/github-membership',
@@ -300946,10 +300946,14 @@ class EntityGroup extends base_Entity {
300946
300946
  }
300947
300947
  }
300948
300948
 
300949
+ // EXTERNAL MODULE: external "crypto"
300950
+ var external_crypto_ = __nccwpck_require__(6113);
300951
+ var external_crypto_default = /*#__PURE__*/__nccwpck_require__.n(external_crypto_);
300949
300952
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghrepositorysecretssection/secrets.ts
300950
300953
 
300951
300954
 
300952
300955
 
300956
+
300953
300957
  async function provisionRepositorySecrets(grss, repo) {
300954
300958
  gh_provisioner_src_logger.info(`[gh-provisioner] ${grss.k8sId} provisioning secrets`);
300955
300959
  const sections = [
@@ -300990,14 +300994,21 @@ async function provisionRepositorySecret(grss, section, repoSecretName, secretRe
300990
300994
  }
300991
300995
  async function createEncryptedSecret(grss, secretRef, secretPatcherFunction, section, repoSecretName, repo) {
300992
300996
  gh_provisioner_src_logger.info(`[gh-provisioner] generating encrypted secret ${grss.k8sId}/${section}/${repoSecretName}`);
300993
- const { key_id, encrypted_value } = await encryptSecret(grss, secretRef, section, repo);
300997
+ const plainSecret = base_Entity.refResolver(secretRef);
300998
+ const plainTextSecret = plainSecret.getOutput(secretRef.key);
300999
+ const plaintextSha256 = external_crypto_default().createHash('sha256')
301000
+ .update(plainTextSecret)
301001
+ .digest('hex');
301002
+ const { key_id, encrypted_value } = await encryptSecret(grss, plainTextSecret, section, repo);
300994
301003
  secretPatcherFunction({
300995
301004
  secretName: repoSecretName,
300996
301005
  repository: repo.getDepName(),
300997
301006
  encryptedValue: encrypted_value,
300998
- lifecycle: {
300999
- ignoreChanges: ['encrypted_value'],
301000
- },
301007
+ });
301008
+ grss.patchData({
301009
+ path: `/config/${section}_sha256/${repoSecretName}`,
301010
+ op: PatchOperations.add,
301011
+ value: plaintextSha256,
301001
301012
  });
301002
301013
  }
301003
301014
  async function createUnencryptedSecret(grss, secretRef, secretPatcherFunction, section, repoSecretName, repo) {
@@ -301010,9 +301021,7 @@ async function createUnencryptedSecret(grss, secretRef, secretPatcherFunction, s
301010
301021
  plaintextValue: plainTextSecret,
301011
301022
  });
301012
301023
  }
301013
- async function encryptSecret(grss, secretRef, section, repo) {
301014
- const plainSecret = base_Entity.refResolver(secretRef);
301015
- const plainTextSecret = plainSecret.getOutput(secretRef.key);
301024
+ async function encryptSecret(grss, plainTextSecret, section, repo) {
301016
301025
  const v = await grss.runWithGithubProvider(async () => {
301017
301026
  return await github["default"].encryption.encryptRepoSecret(repo.cr.spec.org, repo.getDepName(), section, plainTextSecret);
301018
301027
  });
@@ -301030,6 +301039,9 @@ class EntityGHRepositorySecretsSection extends base_Entity {
301030
301039
  actions: {},
301031
301040
  codespaces: {},
301032
301041
  dependabot: {},
301042
+ actions_sha256: {},
301043
+ codespaces_sha256: {},
301044
+ dependabot_sha256: {},
301033
301045
  },
301034
301046
  });
301035
301047
  }
@@ -301711,27 +301723,83 @@ class EntityGHRepo extends base_Entity {
301711
301723
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghfeature/helpers/managed_files.ts
301712
301724
 
301713
301725
 
301726
+ /**
301727
+ * Migration reimport path for `import-with-reimport` operations.
301728
+ *
301729
+ * Populates `config.files` with ONLY non-user-managed files so the subsequent
301730
+ * `loadAddressesToImport` can generate correct import blocks for them.
301731
+ * Seeds `installed_managed_files` from ALL `userManaged: true` addresses in the
301732
+ * current CR spec so the TFM input is ready for the next regular apply cycle.
301733
+ */
301734
+ async function seedForMigrationReiimport(ghFeature) {
301735
+ const cr = ghFeature.cr;
301736
+ const repoInfo = cr.spec.repositoryTarget;
301737
+ // Only non-user-managed files go into TF config during migration reimport.
301738
+ // User-managed files receive no import block (see ADR 0005).
301739
+ for (const file of cr.spec.files) {
301740
+ if (!file.userManaged) {
301741
+ createFileProvision(ghFeature, file);
301742
+ }
301743
+ }
301744
+ // Seed installed_managed_files from ALL userManaged:true addresses in spec.
301745
+ // This ensures the TFM input is populated so the output is computable on the
301746
+ // operator's next regular apply (second cycle of the two-cycle migration).
301747
+ const allUserManagedAddresses = cr.spec.files
301748
+ .filter((f) => f.userManaged)
301749
+ .map((f) => `${f.path}/${f.targetBranch || repoInfo.branch}`);
301750
+ ghFeature.patchData({
301751
+ op: PatchOperations.replace,
301752
+ path: '/installed_managed_files',
301753
+ value: allUserManagedAddresses,
301754
+ });
301755
+ }
301714
301756
  async function provisionManagedFiles(ghFeature) {
301715
301757
  const cr = ghFeature.cr;
301716
301758
  const repoInfo = cr.spec.repositoryTarget;
301717
301759
  const repoRef = base_Entity.refResolver(cr.spec.repositoryTarget.ref);
301760
+ // Read the accumulated list of previously provisioned user-managed file addresses
301761
+ // from the entity's output secrets. Defaults to [] on first apply.
301762
+ const selfOutputs = base_Entity.refResolver({ kind: 'self', name: 'outputs' });
301763
+ const rawInstalledFiles = selfOutputs?.getOutput('installed_managed_files');
301764
+ const installedManagedFiles = Array.isArray(rawInstalledFiles)
301765
+ ? rawInstalledFiles
301766
+ : [];
301767
+ const newlyProvisionedAddresses = [];
301718
301768
  for (const file of cr.spec.files) {
301769
+ const branch = file.targetBranch || repoInfo.branch;
301770
+ const address = `${file.path}/${branch}`;
301719
301771
  if (file.userManaged) {
301772
+ if (installedManagedFiles.includes(address)) {
301773
+ // Already provisioned — skip from TF config entirely so Terraform
301774
+ // never overwrites or deletes user edits.
301775
+ continue;
301776
+ }
301777
+ // New user-managed file — provision once. Fetch existing content from
301778
+ // the repo if available so the seed commit preserves the current state.
301779
+ newlyProvisionedAddresses.push(address);
301720
301780
  try {
301721
301781
  const content = await ghFeature.getFileContentFromProvider(cr.spec.org, `/${cr.spec.org}/${repoRef.getDepName()}/${repoInfo.branch}/${file.path}`);
301722
- const newContent = Buffer.from(content, 'binary').toString('base64');
301723
- file.content = newContent;
301782
+ file.content = Buffer.from(content, 'binary').toString('base64');
301724
301783
  }
301725
301784
  catch (e) {
301726
- gh_provisioner_src_logger.error(`File ${file.path} not found in repo ${repoInfo.ref.name} on branch ${repoInfo.branch}. File content not updated`);
301785
+ gh_provisioner_src_logger.error(`File ${file.path} not found in repo ${repoInfo.ref?.name} on branch ${repoInfo.branch}. File content not updated`);
301727
301786
  }
301787
+ createFileProvision(ghFeature, file);
301788
+ }
301789
+ else {
301790
+ // Non-user-managed — add to TF config normally (Terraform manages content).
301791
+ createFileProvision(ghFeature, file);
301728
301792
  }
301729
301793
  }
301730
- // now we have everything in place
301731
- // let's create the provision itself
301732
- for (const file of cr.spec.files) {
301733
- createFileProvision(ghFeature, file);
301734
- }
301794
+ // Write the updated accumulated address list back into the document as a
301795
+ // top-level TFM input. The TFM output will grow this list monotonically
301796
+ // and subtract any files that transition away from userManaged.
301797
+ ghFeature.patchData({
301798
+ op: PatchOperations.replace,
301799
+ path: '/installed_managed_files',
301800
+ value: [...installedManagedFiles, ...newlyProvisionedAddresses],
301801
+ });
301802
+ return { newlyProvisionedAddresses, installedManagedFiles };
301735
301803
  }
301736
301804
  function createFileProvision(ghFeature, file) {
301737
301805
  const cr = ghFeature.cr;
@@ -302005,7 +302073,9 @@ class EntityGHFeature extends base_Entity {
302005
302073
  config: {
302006
302074
  files: [],
302007
302075
  },
302076
+ installed_managed_files: [],
302008
302077
  });
302078
+ this._newlyProvisionedAddresses = [];
302009
302079
  }
302010
302080
  async loadResources(tfOp) {
302011
302081
  gh_provisioner_src_logger.info(`[gh-provisioner] running ${tfOp} on ${this.k8sId}`);
@@ -302016,9 +302086,22 @@ class EntityGHFeature extends base_Entity {
302016
302086
  value: `${repoRef.cr.spec.org}/${repoRef.getDepName()}`,
302017
302087
  });
302018
302088
  try {
302019
- await provisionManagedFiles(this);
302020
- if (tfOp === 'destroy') {
302021
- await this.keepUntrackedFiles(tfOp);
302089
+ if (tfOp === 'import-with-reimport') {
302090
+ // Migration reimport path: only non-user-managed files go into config;
302091
+ // installed_managed_files is seeded from all userManaged:true addresses.
302092
+ // preApplyStateRm and keepUntrackedFiles are both skipped — the
302093
+ // destroy-state-only step inside runOnTerraform handles legacy state cleanup.
302094
+ await seedForMigrationReiimport(this);
302095
+ }
302096
+ else {
302097
+ const { newlyProvisionedAddresses, installedManagedFiles } = await provisionManagedFiles(this);
302098
+ this._newlyProvisionedAddresses = newlyProvisionedAddresses;
302099
+ if (tfOp === 'apply') {
302100
+ await this.preApplyStateRm(installedManagedFiles);
302101
+ }
302102
+ if (tfOp === 'destroy') {
302103
+ await this.keepUntrackedFiles(tfOp);
302104
+ }
302022
302105
  }
302023
302106
  gh_provisioner_src_logger.debug(`[gh-provisioner] ${this.k8sId} loaded its data`);
302024
302107
  this.synthMessage('Synth finished');
@@ -302029,31 +302112,107 @@ class EntityGHFeature extends base_Entity {
302029
302112
  throw `[gh-provisioner] ${this.k8sId} error loading resources: ${err}`;
302030
302113
  }
302031
302114
  }
302032
- async postProvision(tfOp) { }
302033
- async loadAddressesToImport() { }
302034
- async keepUntrackedFiles(tfOp) {
302115
+ async postProvision(tfOp) {
302116
+ if (tfOp !== 'apply' || this._newlyProvisionedAddresses.length === 0) {
302117
+ return;
302118
+ }
302119
+ const addresses = this._newlyProvisionedAddresses.map((addr) => `github_repository_file.user_managed["${addr}"]`);
302120
+ gh_provisioner_src_logger.info(`[gh-provisioner] ${this.k8sId} post-apply state rm for newly provisioned user-managed files: [${addresses.join(', ')}]`);
302121
+ await runOnTerraform(this, 'custom-command', ['state', 'rm'].concat(addresses));
302122
+ }
302123
+ /**
302124
+ * Called by `runOnTerraform` during `import-with-reimport` after
302125
+ * `loadResources('import-with-reimport')` has already seeded `config.files`
302126
+ * with only non-user-managed files.
302127
+ *
302128
+ * Emits one import block per file:
302129
+ * to: github_repository_file.managed["<file>/<branch>"]
302130
+ * id: <repoName>:<file>:<branch>
302131
+ *
302132
+ * User-managed files are intentionally excluded (see ADR 0005).
302133
+ */
302134
+ async loadAddressesToImport() {
302135
+ const repoRef = base_Entity.refResolver(this.cr.spec.repositoryTarget.ref);
302136
+ const repoName = repoRef.getDepName();
302137
+ for (const file of this.document.config.files) {
302138
+ this.patchImportData({
302139
+ op: PatchOperations.add,
302140
+ path: '/imports/-',
302141
+ value: {
302142
+ to: `github_repository_file.managed["${file.file}/${file.branch}"]`,
302143
+ id: `${repoName}:${file.file}:${file.branch}`,
302144
+ },
302145
+ });
302146
+ }
302147
+ }
302148
+ /**
302149
+ * Pre-apply state rm — runs inside loadResources before the main TF operation.
302150
+ *
302151
+ * Fetches the current TF state list, then removes any user-managed resource
302152
+ * addresses that are in installedManagedFiles AND are not currently
302153
+ * transitioning from userManaged:true to userManaged:false. This prevents
302154
+ * Terraform from planning a destroy for files that were dropped from the
302155
+ * feature definition or are still under user control from a previous apply.
302156
+ */
302157
+ async preApplyStateRm(installedManagedFiles) {
302158
+ if (installedManagedFiles.length === 0)
302159
+ return;
302035
302160
  try {
302036
- gh_provisioner_src_logger.debug(`[gh-provisioner] ${this.k8sId} loaded untrack files ${this.k8sId}`);
302037
- const untrackedFilesListForSynth = [];
302038
- const untrackedFiles = this.document.config.files
302039
- .filter((file) => file.userManaged)
302040
- .map((file) => {
302041
- // for information
302042
- untrackedFilesListForSynth.push(file);
302043
- const address = `${file.file}/${file.branch}`;
302044
- return 'github_repository_file.user_managed["' + address + '"]';
302161
+ const stateListOutput = await runOnTerraform(this, 'custom-command', [
302162
+ 'state',
302163
+ 'list',
302164
+ ]);
302165
+ const stateAddresses = parseStateList(stateListOutput);
302166
+ // Files that are transitioning from userManaged:true → false must NOT be
302167
+ // state-rm'd because Terraform needs them in state to manage them going forward.
302168
+ const transitioningToManaged = new Set((this.cr.spec.files ?? [])
302169
+ .filter((f) => !f.userManaged)
302170
+ .map((f) => `${f.path}/${f.targetBranch || this.cr.spec.repositoryTarget.branch}`));
302171
+ const addressesToRemove = stateAddresses.filter((addr) => {
302172
+ const match = addr.match(/^github_repository_file\.user_managed\["(.+)"\]$/);
302173
+ if (!match)
302174
+ return false;
302175
+ const fileAddress = match[1];
302176
+ return (installedManagedFiles.includes(fileAddress) &&
302177
+ !transitioningToManaged.has(fileAddress));
302045
302178
  });
302046
- gh_provisioner_src_logger.info(`[gh-provisioner] ${this.k8sId} keeping the following files [${untrackedFiles.join(',')}]`);
302179
+ if (addressesToRemove.length > 0) {
302180
+ gh_provisioner_src_logger.info(`[gh-provisioner] ${this.k8sId} pre-apply state rm: [${addressesToRemove.join(', ')}]`);
302181
+ await runOnTerraform(this, 'custom-command', ['state', 'rm'].concat(addressesToRemove));
302182
+ }
302183
+ }
302184
+ catch (err) {
302185
+ throw new Error(`[gh-provisioner] ${this.k8sId} pre-apply state rm failed: ${err}`);
302186
+ }
302187
+ }
302188
+ /**
302189
+ * Destroy path — removes user-managed file resources from TF state before
302190
+ * the destroy operation so Terraform does not delete them from GitHub.
302191
+ *
302192
+ * Uses `terraform state list` to discover what is actually in state rather
302193
+ * than reading config.files, making this robust whether the entity was last
302194
+ * provisioned with old or new code.
302195
+ */
302196
+ async keepUntrackedFiles(_tfOp) {
302197
+ try {
302198
+ gh_provisioner_src_logger.debug(`[gh-provisioner] ${this.k8sId} loaded untrack files ${this.k8sId}`);
302199
+ // Propagate session fields from parent if present
302200
+ if (this.parent) {
302201
+ this.sessionId = this.parent.sessionId;
302202
+ this.sessionProjectPath = this.parent.sessionProjectPath;
302203
+ this.__ghProvisionerSessionWorkspaceInitialized =
302204
+ this.parent.__ghProvisionerSessionWorkspaceInitialized;
302205
+ }
302206
+ const stateListOutput = await runOnTerraform(this, 'custom-command', [
302207
+ 'state',
302208
+ 'list',
302209
+ ]);
302210
+ const stateAddresses = parseStateList(stateListOutput);
302211
+ const untrackedFiles = stateAddresses.filter((addr) => /^github_repository_file\.user_managed\[".+"\]$/.test(addr));
302212
+ gh_provisioner_src_logger.info(`[gh-provisioner] ${this.k8sId} keeping the following files [${untrackedFiles.join(', ')}]`);
302047
302213
  if (untrackedFiles.length > 0) {
302048
- // Propagate session fields from parent if present
302049
- if (this.parent) {
302050
- this.sessionId = this.parent.sessionId;
302051
- this.sessionProjectPath = this.parent.sessionProjectPath;
302052
- this.__ghProvisionerSessionWorkspaceInitialized =
302053
- this.parent.__ghProvisionerSessionWorkspaceInitialized;
302054
- }
302055
302214
  const operationOutputs = await runOnTerraform(this, 'custom-command', ['state', 'rm'].concat(untrackedFiles));
302056
- this.synthMessage(`\nkeeping files after feature uninstalled: \n ${untrackedFilesListForSynth.map((f) => f.file + '/' + f.branch).join('\n - ')}`);
302215
+ this.synthMessage(`\nkeeping files after feature uninstalled: \n - ${untrackedFiles.join('\n - ')}`);
302057
302216
  gh_provisioner_src_logger.debug(`[gh-provisioner] ${this.k8sId} state rm output ${operationOutputs}`);
302058
302217
  }
302059
302218
  }
@@ -302072,6 +302231,14 @@ class EntityGHFeature extends base_Entity {
302072
302231
  return v;
302073
302232
  }
302074
302233
  }
302234
+ function parseStateList(output) {
302235
+ if (!output || typeof output !== 'string')
302236
+ return [];
302237
+ return output
302238
+ .split('\n')
302239
+ .map((line) => line.trim())
302240
+ .filter(Boolean);
302241
+ }
302075
302242
 
302076
302243
  ;// CONCATENATED MODULE: ../gh_provisioner/src/entities/ghmembership/helpers/index.ts
302077
302244
 
@@ -303364,16 +303531,16 @@ async function GHCheckRun(cmd, item) {
303364
303531
  checkRun.mdOptionsDetails({
303365
303532
  quotes: 'terraform',
303366
303533
  });
303367
- checkRun.update('Initiating', 'queued');
303534
+ void checkRun.update('Initiating', 'queued');
303368
303535
  return {
303369
303536
  fnData: (d) => {
303370
- checkRun.update(d.toString(), 'in_progress');
303537
+ void checkRun.update(d.toString(), 'in_progress');
303371
303538
  },
303372
303539
  fnEnd: () => {
303373
- checkRun.close('OK', true);
303540
+ void checkRun.close('OK', true);
303374
303541
  },
303375
303542
  fnOnError: (err) => {
303376
- checkRun.close('KO', false);
303543
+ void checkRun.close('KO', false);
303377
303544
  },
303378
303545
  };
303379
303546
  }
@@ -304517,7 +304684,7 @@ const crs_analyzerSubcommand = {
304517
304684
  };
304518
304685
 
304519
304686
  ;// CONCATENATED MODULE: ./package.json
304520
- const package_namespaceObject = JSON.parse('{"i8":"2.7.0-snapshot-17"}');
304687
+ const package_namespaceObject = JSON.parse('{"i8":"2.7.0-snapshot-19"}');
304521
304688
  ;// CONCATENATED MODULE: ../../package.json
304522
304689
  const package_namespaceObject_1 = {"i8":"2.6.4"};
304523
304690
  ;// CONCATENATED MODULE: ./src/subcommands/index.ts
@@ -1 +1,2 @@
1
- export { provisionManagedFiles } from './managed_files';
1
+ export { provisionManagedFiles, seedForMigrationReiimport, } from './managed_files';
2
+ export type { ManagedFilesResult } from './managed_files';
@@ -1,2 +1,15 @@
1
1
  import { EntityGHFeature } from '../';
2
- export declare function provisionManagedFiles(ghFeature: EntityGHFeature): Promise<void>;
2
+ export interface ManagedFilesResult {
3
+ newlyProvisionedAddresses: string[];
4
+ installedManagedFiles: string[];
5
+ }
6
+ /**
7
+ * Migration reimport path for `import-with-reimport` operations.
8
+ *
9
+ * Populates `config.files` with ONLY non-user-managed files so the subsequent
10
+ * `loadAddressesToImport` can generate correct import blocks for them.
11
+ * Seeds `installed_managed_files` from ALL `userManaged: true` addresses in the
12
+ * current CR spec so the TFM input is ready for the next regular apply cycle.
13
+ */
14
+ export declare function seedForMigrationReiimport(ghFeature: EntityGHFeature): Promise<void>;
15
+ export declare function provisionManagedFiles(ghFeature: EntityGHFeature): Promise<ManagedFilesResult>;
@@ -1,9 +1,39 @@
1
1
  import { Entity } from '../base';
2
2
  export declare class EntityGHFeature extends Entity {
3
+ _newlyProvisionedAddresses: string[];
3
4
  constructor(artifact: any);
4
5
  loadResources(tfOp: string): Promise<void>;
5
6
  postProvision(tfOp: string): Promise<void>;
7
+ /**
8
+ * Called by `runOnTerraform` during `import-with-reimport` after
9
+ * `loadResources('import-with-reimport')` has already seeded `config.files`
10
+ * with only non-user-managed files.
11
+ *
12
+ * Emits one import block per file:
13
+ * to: github_repository_file.managed["<file>/<branch>"]
14
+ * id: <repoName>:<file>:<branch>
15
+ *
16
+ * User-managed files are intentionally excluded (see ADR 0005).
17
+ */
6
18
  loadAddressesToImport(): Promise<void>;
7
- keepUntrackedFiles(tfOp: string): Promise<void>;
19
+ /**
20
+ * Pre-apply state rm — runs inside loadResources before the main TF operation.
21
+ *
22
+ * Fetches the current TF state list, then removes any user-managed resource
23
+ * addresses that are in installedManagedFiles AND are not currently
24
+ * transitioning from userManaged:true to userManaged:false. This prevents
25
+ * Terraform from planning a destroy for files that were dropped from the
26
+ * feature definition or are still under user control from a previous apply.
27
+ */
28
+ preApplyStateRm(installedManagedFiles: string[]): Promise<void>;
29
+ /**
30
+ * Destroy path — removes user-managed file resources from TF state before
31
+ * the destroy operation so Terraform does not delete them from GitHub.
32
+ *
33
+ * Uses `terraform state list` to discover what is actually in state rather
34
+ * than reading config.files, making this robust whether the entity was last
35
+ * provisioned with old or new code.
36
+ */
37
+ keepUntrackedFiles(_tfOp: string): Promise<void>;
8
38
  getFileContentFromProvider(org: string, url: string): Promise<string>;
9
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firestartr/cli",
3
- "version": "2.7.0-snapshot-17",
3
+ "version": "2.7.0-snapshot-19",
4
4
  "private": false,
5
5
  "description": "Commandline tool",
6
6
  "main": "build/main.js",