@payfit/iac 1.0.0-ephemeral-migration-scripts.7 → 1.0.0-ephemeral-migration-scripts.8

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 (42) hide show
  1. package/README.md +97 -4
  2. package/dist/executors/stack-migration/README.md +111 -11
  3. package/dist/executors/stack-migration/lib/constants.d.ts +0 -11
  4. package/dist/executors/stack-migration/lib/constants.js +1 -12
  5. package/dist/executors/stack-migration/lib/constants.js.map +1 -1
  6. package/dist/executors/stack-migration/lib/file-operations.d.ts +0 -43
  7. package/dist/executors/stack-migration/lib/file-operations.js +0 -88
  8. package/dist/executors/stack-migration/lib/file-operations.js.map +1 -1
  9. package/dist/executors/stack-migration/lib/git-operations.d.ts +0 -27
  10. package/dist/executors/stack-migration/lib/git-operations.js +2 -35
  11. package/dist/executors/stack-migration/lib/git-operations.js.map +1 -1
  12. package/dist/executors/stack-migration/lib/logger.d.ts +0 -3
  13. package/dist/executors/stack-migration/lib/logger.js +3 -6
  14. package/dist/executors/stack-migration/lib/logger.js.map +1 -1
  15. package/dist/executors/stack-migration/lib/migration-helper.d.ts +8 -44
  16. package/dist/executors/stack-migration/lib/migration-helper.js +23 -67
  17. package/dist/executors/stack-migration/lib/migration-helper.js.map +1 -1
  18. package/dist/executors/stack-migration/lib/migration-tool.d.ts +4 -15
  19. package/dist/executors/stack-migration/lib/migration-tool.js +77 -212
  20. package/dist/executors/stack-migration/lib/migration-tool.js.map +1 -1
  21. package/dist/executors/stack-migration/lib/path-builder.d.ts +0 -8
  22. package/dist/executors/stack-migration/lib/path-builder.js +2 -28
  23. package/dist/executors/stack-migration/lib/path-builder.js.map +1 -1
  24. package/dist/executors/stack-migration/lib/templates/index.d.ts +1 -0
  25. package/dist/executors/stack-migration/lib/templates/index.js +1 -0
  26. package/dist/executors/stack-migration/lib/templates/index.js.map +1 -1
  27. package/dist/executors/stack-migration/lib/templates/message-templates.d.ts +25 -0
  28. package/dist/executors/stack-migration/lib/templates/message-templates.js +107 -0
  29. package/dist/executors/stack-migration/lib/templates/message-templates.js.map +1 -0
  30. package/dist/executors/stack-migration/lib/templates/pr-templates.d.ts +0 -4
  31. package/dist/executors/stack-migration/lib/templates/pr-templates.js +3 -24
  32. package/dist/executors/stack-migration/lib/templates/pr-templates.js.map +1 -1
  33. package/dist/executors/stack-migration/lib/templates/procedure-template.js +5 -40
  34. package/dist/executors/stack-migration/lib/templates/procedure-template.js.map +1 -1
  35. package/dist/executors/stack-migration/lib/types.d.ts +0 -9
  36. package/dist/executors/stack-migration/lib/types.js +0 -3
  37. package/dist/executors/stack-migration/lib/types.js.map +1 -1
  38. package/dist/executors/stack-migration/schema.d.ts +2 -1
  39. package/dist/executors/stack-migration/schema.json +4 -3
  40. package/dist/executors/stack-migration/stack-migration.js +30 -60
  41. package/dist/executors/stack-migration/stack-migration.js.map +1 -1
  42. package/package.json +3 -2
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Main migration tool orchestrating all migration steps
4
- */
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.MigrationTool = void 0;
7
4
  const tslib_1 = require("tslib");
@@ -11,46 +8,29 @@ const migration_helper_1 = require("./migration-helper");
11
8
  const spacelift_client_1 = require("./spacelift-client");
12
9
  const templates_1 = require("./templates");
13
10
  class MigrationTool extends migration_helper_1.MigrationHelper {
14
- /**
15
- * Step 1: Setup Repositories
16
- * Clones required repos, validates spacectl auth
17
- */
18
11
  async setupRepositories(options) {
19
12
  this.logger.step(1, 9, 'Repository Setup and Validation');
20
- // Get service repository info (from options or prompts)
21
- if (options?.name) {
22
- this.config.serviceRepository.name = options.name;
13
+ if (this.config.resuming &&
14
+ this.config.serviceRepository.name &&
15
+ this.config.serviceRepository.domain &&
16
+ this.config.serviceRepository.team) {
17
+ this.logger.success('Using existing service repository configuration');
18
+ this.logger.info(` Service: ${this.config.serviceRepository.name}`);
19
+ this.logger.info(` Domain: ${this.config.serviceRepository.domain}`);
20
+ this.logger.info(` Team: ${this.config.serviceRepository.team}`);
23
21
  }
24
22
  else {
25
23
  const inferredName = path.basename(process.cwd());
26
- const repoName = await this.ask(`Service repository name [${inferredName}]`);
27
- this.config.serviceRepository.name = repoName || inferredName;
28
- }
29
- this.logger.info(`Using service repository: ${this.config.serviceRepository.name}`);
30
- // Get domain, team, and okta groups
31
- if (options?.domain) {
32
- this.config.serviceRepository.domain = options.domain;
33
- }
34
- else {
35
- this.config.serviceRepository.domain = await this.ask('Domain name (e.g., core-platform, ide, data-platform)');
36
- }
37
- if (options?.team) {
38
- this.config.serviceRepository.team = options.team;
39
- }
40
- else {
41
- this.config.serviceRepository.team = await this.ask('Team name (e.g., infrastructure-developer-experience)');
42
- }
43
- if (options?.oktaGroups && options.oktaGroups.length > 0) {
44
- this.config.serviceRepository.oktaGroups = options.oktaGroups;
45
- }
46
- else {
47
- const oktaGroupsInput = await this.ask('Okta groups (comma-separated, e.g., "Squad - Infrastructure & Developer Experience")');
24
+ this.config.serviceRepository.name = await this.getOrAskConfig(options?.name, 'Service repository name', inferredName);
25
+ this.logger.info(`Using service repository: ${this.config.serviceRepository.name}`);
26
+ this.config.serviceRepository.domain = await this.getOrAskConfig(options?.domain, 'Domain name (e.g., core-platform, ide, data-platform)');
27
+ this.config.serviceRepository.team = await this.getOrAskConfig(options?.team, 'Team name (e.g., infrastructure-developer-experience)');
28
+ const oktaGroupsInput = await this.getOrAskConfig(options?.oktaGroups, 'Okta groups (comma-separated, e.g., "Squad - Infrastructure & Developer Experience")');
48
29
  this.config.serviceRepository.oktaGroups = oktaGroupsInput
49
30
  .split(',')
50
31
  .map(g => g.trim())
51
32
  .filter(g => g.length > 0);
52
33
  }
53
- // Clone required repositories to temp directory
54
34
  this.logger.info('\nCloning required repositories...');
55
35
  if (!this.files.exists(this.paths.tmpDir)) {
56
36
  this.files.mkdir(this.paths.tmpDir, { recursive: true });
@@ -93,16 +73,24 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
93
73
  else {
94
74
  this.logger.info('.gitignore already contains migration entries');
95
75
  }
96
- // Save initial state
97
- this.config.migrationStartedAt = new Date().toISOString();
76
+ // Save initial state (only set timestamp if not resuming)
77
+ if (!this.config.migrationStartedAt) {
78
+ this.config.migrationStartedAt = new Date().toISOString();
79
+ }
98
80
  this.saveMigrationState();
99
81
  }
100
82
  /**
101
83
  * Step 2: Load Configuration
102
- * Load stacks and ECR repositories from iac-deploy and ecr-deploy
84
+ * Load stacks from iac-deploy
103
85
  */
104
86
  async loadConfiguration() {
105
- this.logger.step(2, 9, 'Loading Configuration from iac-deploy and ecr-deploy');
87
+ this.logger.step(2, 9, 'Loading Configuration from iac-deploy');
88
+ // Check if configuration is already loaded from state
89
+ if (this.config.resuming && this.config.stacks.length > 0) {
90
+ this.logger.success(`Configuration already loaded from state (${this.config.stacks.length} stack(s))`);
91
+ this.logger.info('Skipping file reads as configuration is already available');
92
+ return;
93
+ }
106
94
  // Load stacks from iac-deploy
107
95
  const iacConfigPath = this.paths.iacConfigFile(this.config.serviceRepository.name);
108
96
  if (!this.files.exists(iacConfigPath)) {
@@ -115,29 +103,6 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
115
103
  this.config.stacks = iacConfig.stacks;
116
104
  this.logger.success(`Found ${this.config.stacks.length} stack(s) in configuration`);
117
105
  }
118
- // Load ECR repositories from ecr-deploy
119
- const ecrConfigPath = this.paths.ecrConfigFile(constants_1.ECR_DEPLOY_PATHS.ecrRepos);
120
- if (this.files.exists(ecrConfigPath)) {
121
- this.logger.info(`Reading ECR configuration from ecr-deploy...`);
122
- const ecrConfig = this.files.readYaml(ecrConfigPath);
123
- const repoSource = `https://github.com/PayFit/${this.config.serviceRepository.name}`;
124
- // Filter ECR repos by source
125
- for (const [key, value] of Object.entries(ecrConfig)) {
126
- if (key === 'payfit_team' || key === 'anchors' || key.startsWith('.'))
127
- continue;
128
- const repo = value;
129
- if (repo?.source === repoSource) {
130
- this.config.ecrRepositories.push({
131
- name: key,
132
- team: repo.owner || this.config.serviceRepository.team,
133
- });
134
- }
135
- }
136
- this.logger.success(`Found ${this.config.ecrRepositories.length} ECR repository(ies)`);
137
- }
138
- else {
139
- this.logger.info('No ECR configuration found (this is fine)');
140
- }
141
106
  // Save state
142
107
  this.saveMigrationState();
143
108
  }
@@ -149,15 +114,9 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
149
114
  // Skip if already created
150
115
  if (this.config.generatedFiles.safetyNetPR) {
151
116
  this.logger.success(`Safety net PR already exists: ${this.config.generatedFiles.safetyNetPR.url}`);
152
- const shouldContinue = await this.confirm('\nHas this PR been merged?', false);
153
- if (!shouldContinue) {
154
- this.logger.warn('Please merge the safety net PR before continuing');
155
- this.logger.info('Run this script again when ready');
156
- process.exit(0);
157
- }
158
117
  return;
159
118
  }
160
- this.logger.info('\nThis PR will set autodeploy=false for all stacks to prevent unwanted changes during migration');
119
+ this.logger.info(templates_1.MessageTemplates.SAFETY_NET_DESCRIPTION);
161
120
  const iacConfigPath = this.paths.iacConfigFile(this.config.serviceRepository.name);
162
121
  // Read the current config
163
122
  const iacConfig = this.files.readYaml(iacConfigPath);
@@ -181,7 +140,7 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
181
140
  return;
182
141
  }
183
142
  // Create branch
184
- const branchName = `safety-net-${this.config.serviceRepository.name}-${Date.now()}`;
143
+ const branchName = this.generateBranchName(`safety-net-${this.config.serviceRepository.name}`);
185
144
  await this.git.createBranch(branchName, this.config.repositories.iacDeploy);
186
145
  // Update all stacks to have autodeploy: false
187
146
  this.files.updateAutodeployInYaml(iacConfigPath, false);
@@ -196,17 +155,9 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
196
155
  branch: branchName,
197
156
  url: prUrl,
198
157
  };
199
- // Save state after creating PR
200
158
  this.saveMigrationState();
201
159
  this.logger.success(`Safety net PR created: ${prUrl}`);
202
- this.logger.warn('\nIMPORTANT: Merge this PR before continuing with migration');
203
- const shouldContinue = await this.confirm('\nHave you merged the safety net PR?', false);
204
- if (!shouldContinue) {
205
- this.logger.warn('Please merge the safety net PR before continuing');
206
- this.logger.info('Run this script again when ready');
207
- process.exit(0);
208
- }
209
- this.logger.success('Safety net PR merged - stacks are now safe from changes');
160
+ this.logger.warn(templates_1.MessageTemplates.SAFETY_NET_IMPORTANT);
210
161
  }
211
162
  /**
212
163
  * Step 4: Create Domain Enrollment PR
@@ -218,9 +169,7 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
218
169
  this.logger.success(`Domain enrollment PR already exists: ${this.config.generatedFiles.domainEnrollmentPR.url}`);
219
170
  const shouldContinue = await this.confirm('\nHas this PR been merged?', false);
220
171
  if (!shouldContinue) {
221
- this.logger.warn('Please merge the PR before continuing');
222
- this.logger.info('Run this script again when ready');
223
- process.exit(0);
172
+ this.exitWithMessage('Please merge the PR before continuing');
224
173
  }
225
174
  return;
226
175
  }
@@ -299,7 +248,7 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
299
248
  }
300
249
  // Only create branch and commit if there are changes
301
250
  if (needsEnrollment) {
302
- const branchName = `enroll-${this.config.serviceRepository.name}-${Date.now()}`;
251
+ const branchName = this.generateBranchName(`enroll-${this.config.serviceRepository.name}`);
303
252
  await this.git.createBranch(branchName, this.config.repositories.domainEnrollment);
304
253
  await this.git.add(['.'], this.config.repositories.domainEnrollment);
305
254
  await this.git.commit(`feat: enroll ${this.config.serviceRepository.name} in ${this.config.serviceRepository.domain} domain`, this.config.repositories.domainEnrollment);
@@ -312,7 +261,6 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
312
261
  branch: branchName,
313
262
  url: prUrl,
314
263
  };
315
- // Save state after creating PR
316
264
  this.saveMigrationState();
317
265
  this.logger.info('\nNext steps for domain enrollment:');
318
266
  this.logger.info(`1. Review PR: ${prUrl}`);
@@ -320,11 +268,9 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
320
268
  this.logger.info('3. Merge PR to deploy repository metastack');
321
269
  const shouldContinue = await this.confirm('\nHave you merged the domain enrollment PR?', false);
322
270
  if (!shouldContinue) {
323
- this.logger.warn('Please merge the PR before continuing');
324
- this.logger.info('Run this script again when ready');
325
- process.exit(0);
271
+ this.exitWithMessage('Please merge the PR before continuing');
326
272
  }
327
- this.logger.success('Domain enrollment PR merged - metastack should be deploying');
273
+ this.logger.success(templates_1.MessageTemplates.DOMAIN_ENROLLMENT_SUCCESS);
328
274
  }
329
275
  else {
330
276
  this.logger.success('Repository already enrolled - skipping domain enrollment PR');
@@ -333,8 +279,8 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
333
279
  // Save state after marking enrollment as done
334
280
  this.saveMigrationState();
335
281
  // Check if metastack exists
336
- this.logger.info('\nAssuming metastack already exists for this repository');
337
- const shouldContinue = await this.confirm('Continue with migration? (metastack should already be deployed)', true);
282
+ this.logger.info(templates_1.MessageTemplates.DOMAIN_ENROLLMENT_ALREADY_EXISTS);
283
+ const shouldContinue = await this.confirm(templates_1.MessageTemplates.DOMAIN_ENROLLMENT_CONFIRM, true);
338
284
  if (!shouldContinue) {
339
285
  this.logger.info('Exiting migration');
340
286
  process.exit(0);
@@ -342,10 +288,10 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
342
288
  }
343
289
  }
344
290
  /**
345
- * Step 5: Create Service Repository PR (spacelift.yaml, ecr.yaml, import.tf)
291
+ * Step 5: Create Service Repository PR (spacelift.yaml, import.tf)
346
292
  */
347
293
  async createServiceRepositoryPR() {
348
- this.logger.step(5, 9, 'Create Service Repository PR (spacelift.yaml, ecr.yaml, .metastack/import.tf)');
294
+ this.logger.step(5, 9, 'Create Service Repository PR (spacelift.yaml, .metastack/import.tf)');
349
295
  // Create safety net PR first if not already created
350
296
  if (!this.config.generatedFiles.safetyNetPR) {
351
297
  this.logger.info('\nSafety net PR not yet created, creating it first...');
@@ -354,7 +300,7 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
354
300
  // Skip if already created
355
301
  if (this.config.generatedFiles.serviceRepoPR) {
356
302
  this.logger.success(`Service repository PR already exists: ${this.config.generatedFiles.serviceRepoPR.url}`);
357
- this.logger.warn('Remember: DO NOT MERGE THIS PR until cleanup PRs are merged');
303
+ this.logger.warn(templates_1.MessageTemplates.SERVICE_REPO_NOT_MERGE_WARNING);
358
304
  return;
359
305
  }
360
306
  // Ask for confirmation before creating PRs
@@ -370,18 +316,6 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
370
316
  if (!this.dryRun) {
371
317
  this.logger.success('Created spacelift.yaml');
372
318
  }
373
- // Create ecr.yaml
374
- if (this.config.ecrRepositories.length > 0) {
375
- const ecrData = {
376
- payfit_team: this.config.serviceRepository.team,
377
- repositories: this.config.ecrRepositories.map(r => ({ name: r.name })),
378
- };
379
- const ecrPath = this.paths.serviceRepoEcrYaml();
380
- this.files.writeYaml(ecrPath, ecrData);
381
- if (!this.dryRun) {
382
- this.logger.success('Created ecr.yaml');
383
- }
384
- }
385
319
  // Create .metastack/import.tf
386
320
  await this.generateImportTf();
387
321
  // Update .gitignore to exclude migration temporary files
@@ -399,10 +333,10 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
399
333
  }
400
334
  }
401
335
  // Create branch and commit
402
- const branchName = `metastack-migration-${Date.now()}`;
336
+ const branchName = this.generateBranchName('metastack-migration');
403
337
  await this.git.createBranch(branchName, this.config.serviceRepository.path);
404
- await this.git.add(['spacelift.yaml', 'ecr.yaml', '.metastack/', '.gitignore'], this.config.serviceRepository.path);
405
- await this.git.commit(`feat: migrate to metastack\n\nAdd spacelift.yaml, ecr.yaml, and import.tf for metastack migration`, this.config.serviceRepository.path);
338
+ await this.git.add(['spacelift.yaml', '.metastack/', '.gitignore'], this.config.serviceRepository.path);
339
+ await this.git.commit(`feat: migrate to metastack\n\nAdd spacelift.yaml and import.tf for metastack migration`, this.config.serviceRepository.path);
406
340
  if (!this.dryRun) {
407
341
  this.logger.success(`Created branch: ${branchName}`);
408
342
  }
@@ -412,18 +346,19 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
412
346
  branch: branchName,
413
347
  url: prUrl,
414
348
  };
415
- // Save state after creating PR
416
349
  this.saveMigrationState();
417
- this.logger.warn('\nDO NOT MERGE THIS PR YET');
418
- this.logger.info('This PR will be merged after cleaning old resources');
350
+ this.logger.warn(templates_1.MessageTemplates.CLEANUP_PR_NOT_MERGE_WARNING);
351
+ this.logger.info(templates_1.MessageTemplates.CLEANUP_PR_MERGE_TIMING);
419
352
  this.logger.info(`PR URL: ${prUrl}`);
420
353
  }
421
354
  /**
422
- * Generate import.tf file with import blocks for stacks and ECR repositories
355
+ * Generate import.tf file with import blocks for stacks
423
356
  */
424
357
  async generateImportTf() {
425
358
  const metastackDir = this.paths.metastackDir;
426
359
  this.files.mkdir(metastackDir, { recursive: true });
360
+ // Ensure spacectl is authenticated before fetching resources
361
+ await this.ensureSpacectlAuthentication();
427
362
  this.logger.info('Fetching stack resources from projects-manager...');
428
363
  // Fetch all resources from projects-manager
429
364
  let projectManagerResources = '';
@@ -438,28 +373,12 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
438
373
  let importTf = `# Import existing resources to metastack
439
374
  # Generated by migrate-to-metastack.ts
440
375
  # This file contains import blocks for:
441
- # - ECR repositories
442
376
  # - Spacelift stacks
443
377
  # - AWS integration attachments
444
378
  # - Context attachments
445
379
  # - Stack dependencies
446
380
 
447
381
  `;
448
- // Import ECR repositories
449
- if (this.config.ecrRepositories.length > 0) {
450
- importTf += `# ==============================================================================
451
- # ECR Repositories
452
- # ==============================================================================
453
- `;
454
- for (const repo of this.config.ecrRepositories) {
455
- importTf += `import {
456
- to = module.ecr_private["${repo.name}"].aws_ecr_repository.this
457
- id = "${repo.name}"
458
- }
459
-
460
- `;
461
- }
462
- }
463
382
  // Import Stacks and their attachments
464
383
  if (this.config.stacks.length > 0) {
465
384
  importTf += `# ==============================================================================
@@ -475,8 +394,8 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
475
394
  # 4. Stack dependencies (if any)
476
395
  \n`;
477
396
  for (const stack of this.config.stacks) {
478
- const stackId = `${stack.identifier}-${stack.environment}${stack.region ? `-${stack.region}` : ''}`;
479
- const moduleKey = `${stack.identifier}-${stack.environment}${stack.region ? `-${stack.region}` : ''}`;
397
+ const stackId = this.getStackModuleKey(stack);
398
+ const moduleKey = this.getStackModuleKey(stack);
480
399
  this.logger.info(` Processing stack: ${stackId}`);
481
400
  // Parse resources to extract attachments for this specific stack module
482
401
  const { contexts, dependencies } = this.parseStackModuleResources(projectManagerResources, moduleKey);
@@ -554,9 +473,6 @@ import {
554
473
  if (!this.dryRun) {
555
474
  this.logger.success('Created .metastack/import.tf with import blocks');
556
475
  this.logger.info(` Total stacks: ${this.config.stacks.length}`);
557
- if (this.config.ecrRepositories.length > 0) {
558
- this.logger.info(` Total ECR repositories: ${this.config.ecrRepositories.length}`);
559
- }
560
476
  }
561
477
  }
562
478
  /**
@@ -628,24 +544,20 @@ import {
628
544
  return { contexts, dependencies };
629
545
  }
630
546
  /**
631
- * Step 6: Create Cleanup PRs for iac-deploy and ecr-deploy
547
+ * Step 6: Create Cleanup PR for iac-deploy
632
548
  */
633
549
  async createCleanupPRs() {
634
- this.logger.step(6, 9, 'Create Cleanup PRs for iac-deploy and ecr-deploy (remove old resources)');
635
- // Ask for confirmation before creating cleanup PRs
636
- const shouldCreate = await this.confirm('\nCreate and push cleanup PRs (iac-deploy and ecr-deploy)?', true);
550
+ this.logger.step(6, 9, 'Create Cleanup PR for iac-deploy (remove old resources)');
551
+ // Ask for confirmation before creating cleanup PR
552
+ const shouldCreate = await this.confirm('\nCreate and push cleanup PR (iac-deploy)?', true);
637
553
  if (!shouldCreate) {
638
554
  this.logger.warn('Skipping cleanup PR creation');
639
555
  return;
640
556
  }
641
- // iac-deploy cleanup
642
557
  await this.createIacDeployCleanupPR();
643
- // ecr-deploy cleanup
644
- if (this.config.ecrRepositories.length > 0) {
645
- await this.createEcrDeployCleanupPR();
646
- }
647
- this.logger.warn('\nDO NOT MERGE THESE PRs YET');
648
- this.logger.info('State removal commands must be run first');
558
+ await this.generateMigrationProcedure();
559
+ this.logger.warn(templates_1.MessageTemplates.CLEANUP_PR_NOT_MERGE_WARNING);
560
+ this.logger.info(templates_1.MessageTemplates.CLEANUP_PR_STATE_REMOVAL_WARNING);
649
561
  }
650
562
  /**
651
563
  * Create cleanup PR for iac-deploy
@@ -656,7 +568,7 @@ import {
656
568
  // Remove the config file
657
569
  const shouldDelete = await this.confirm(`Delete ${this.config.serviceRepository.name}.yaml from iac-deploy?`, true);
658
570
  if (shouldDelete) {
659
- const branchName = `cleanup-${this.config.serviceRepository.name}-${Date.now()}`;
571
+ const branchName = this.generateBranchName(`cleanup-${this.config.serviceRepository.name}`);
660
572
  await this.git.createBranch(branchName, this.config.repositories.iacDeploy);
661
573
  this.files.deleteFile(configPath);
662
574
  await this.git.add(['.'], this.config.repositories.iacDeploy);
@@ -670,42 +582,6 @@ import {
670
582
  branch: branchName,
671
583
  url: prUrl,
672
584
  };
673
- // Save state after creating PR
674
- this.saveMigrationState();
675
- this.logger.info(`PR URL: ${prUrl}`);
676
- }
677
- }
678
- /**
679
- * Create cleanup PR for ecr-deploy
680
- */
681
- async createEcrDeployCleanupPR() {
682
- this.logger.info('\nCreating ecr-deploy cleanup PR...');
683
- const ecrConfigPath = this.paths.ecrConfigFile(constants_1.ECR_DEPLOY_PATHS.ecrRepos);
684
- // Check which repositories need to be removed
685
- const ecrConfig = this.files.readYaml(ecrConfigPath);
686
- const reposToRemove = this.config.ecrRepositories
687
- .map(r => r.name)
688
- .filter(name => ecrConfig[name] !== undefined);
689
- if (reposToRemove.length > 0) {
690
- reposToRemove.forEach(name => {
691
- this.logger.info(` Will remove ${name}`);
692
- });
693
- const branchName = `cleanup-${this.config.serviceRepository.name}-ecr-${Date.now()}`;
694
- await this.git.createBranch(branchName, this.config.repositories.ecrDeploy);
695
- // Use removeYamlKeys to preserve file formatting
696
- this.files.removeYamlKeys(ecrConfigPath, reposToRemove);
697
- await this.git.add(['.'], this.config.repositories.ecrDeploy);
698
- await this.git.commit(`chore: remove ECR repos for ${this.config.serviceRepository.name} (migrated to metastack)`, this.config.repositories.ecrDeploy);
699
- if (!this.dryRun) {
700
- this.logger.success(`Created ecr-deploy cleanup branch: ${branchName}`);
701
- }
702
- // Push and create draft PR
703
- const prUrl = await this.git.createPR(branchName, `chore: remove ECR repos for ${this.config.serviceRepository.name} (migrated to metastack)`, templates_1.PRTemplates.ecrDeployCleanupPRBody(this.config.serviceRepository.name, this.config.ecrRepositories.length), this.config.repositories.ecrDeploy);
704
- this.config.generatedFiles.ecrDeployPR = {
705
- branch: branchName,
706
- url: prUrl,
707
- };
708
- // Save state after creating PR
709
585
  this.saveMigrationState();
710
586
  this.logger.info(`PR URL: ${prUrl}`);
711
587
  }
@@ -720,18 +596,6 @@ import {
720
596
  # They use spacectl to safely remove resources from Terraform state
721
597
 
722
598
  `;
723
- // ECR state removal from ecr-deploy-shared-eu-west-3
724
- if (this.config.ecrRepositories.length > 0) {
725
- commands += `# Remove ECR repositories from ecr-deploy-shared-eu-west-3 stack
726
- # These repositories will be managed by the metastack after migration
727
- # The underlying ECR repositories remain untouched
728
-
729
- `;
730
- for (const repo of this.config.ecrRepositories) {
731
- commands += `spacectl stack task --id="${constants_1.STACK_MANAGERS.ecrDeployShared}" --command="tofu state rm 'module.ecr_private[\\"${repo.name}\\"]'"\n`;
732
- }
733
- commands += `\n`;
734
- }
735
599
  // Stack state removal from projects-manager
736
600
  if (this.config.stacks.length > 0) {
737
601
  commands += `# Remove stack definitions from projects-manager
@@ -741,7 +605,7 @@ import {
741
605
 
742
606
  `;
743
607
  for (const stack of this.config.stacks) {
744
- const moduleKey = `${stack.identifier}-${stack.environment}${stack.region ? `-${stack.region}` : ''}`;
608
+ const moduleKey = this.getStackModuleKey(stack);
745
609
  commands += `spacectl stack task --id="${constants_1.STACK_MANAGERS.projectsManager}" --command="tofu state rm 'module.stacks_generator[\\"${moduleKey}\\"]'"\n`;
746
610
  }
747
611
  commands += `\n`;
@@ -753,7 +617,7 @@ import {
753
617
  this.config.generatedFiles.stateRemovalCommands = commandsPath;
754
618
  // Save state after generating commands
755
619
  this.saveMigrationState();
756
- this.logger.info('\nReview these commands carefully before executing');
620
+ this.logger.info(templates_1.MessageTemplates.STATE_REMOVAL_REVIEW_WARNING);
757
621
  }
758
622
  }
759
623
  /**
@@ -761,17 +625,19 @@ import {
761
625
  */
762
626
  async updateStackSpaces() {
763
627
  this.logger.step(8, 9, 'Update Stack Spaces (Move stacks to domain space)');
764
- this.logger.info('\nThis step will update the space for all stacks to the newly created domain space.');
765
- this.logger.info('This is needed because stacks in the old space cannot be imported by the metastack.');
628
+ this.logger.info(templates_1.MessageTemplates.STACK_SPACES_DESCRIPTION);
629
+ this.logger.info(templates_1.MessageTemplates.STACK_SPACES_REASON);
766
630
  this.logger.info('');
767
- this.logger.warn('IMPORTANT: Run this AFTER state removal commands and BEFORE merging cleanup PRs.');
631
+ this.logger.warn(templates_1.MessageTemplates.STACK_SPACES_IMPORTANT_TIMING);
768
632
  this.logger.info('');
769
633
  const shouldUpdate = await this.confirm('Update stack spaces now?', false);
770
634
  if (!shouldUpdate) {
771
635
  this.logger.warn('Skipping stack space update');
772
- this.logger.info('You can run this step later by calling the updateStackSpaces function manually');
636
+ this.logger.info(templates_1.MessageTemplates.STACK_SPACES_SKIP_LATER);
773
637
  return;
774
638
  }
639
+ // Ensure spacectl is authenticated before making API calls
640
+ await this.ensureSpacectlAuthentication();
775
641
  // Determine the target space name
776
642
  const targetSpaceName = `domain-${this.config.serviceRepository.domain}`;
777
643
  this.logger.info(`Target space: ${targetSpaceName}`);
@@ -780,7 +646,7 @@ import {
780
646
  this.logger.info(` Target space: ${targetSpaceName}`);
781
647
  this.logger.info(` Stacks to update: ${this.config.stacks.length}`);
782
648
  for (const stack of this.config.stacks) {
783
- const stackId = `${stack.identifier}-${stack.environment}${stack.region ? `-${stack.region}` : ''}`;
649
+ const stackId = this.getStackModuleKey(stack);
784
650
  this.logger.info(` - ${stackId}`);
785
651
  }
786
652
  return;
@@ -798,7 +664,7 @@ import {
798
664
  this.logger.info(`\nUpdating ${this.config.stacks.length} stacks...`);
799
665
  const results = [];
800
666
  for (const stack of this.config.stacks) {
801
- const stackId = `${stack.identifier}-${stack.environment}${stack.region ? `-${stack.region}` : ''}`;
667
+ const stackId = this.getStackModuleKey(stack);
802
668
  try {
803
669
  // Check current space
804
670
  const currentStack = await client.getStack(stackId);
@@ -895,10 +761,6 @@ import {
895
761
  name: 'iac-deploy Cleanup',
896
762
  pr: this.config.generatedFiles.iacDeployPR,
897
763
  },
898
- {
899
- name: 'ecr-deploy Cleanup',
900
- pr: this.config.generatedFiles.ecrDeployPR,
901
- },
902
764
  ];
903
765
  for (const { name, pr } of prs) {
904
766
  if (pr) {
@@ -979,7 +841,13 @@ import {
979
841
  this.logger.info('This tool will guide you through the metastack migration');
980
842
  this.logger.info('');
981
843
  if (this.dryRun) {
982
- this.logger.warn('DRY RUN MODE - No changes will be made');
844
+ this.logger.warn(templates_1.MessageTemplates.DRY_RUN_MODE_WARNING);
845
+ this.logger.info('');
846
+ }
847
+ // Load existing migration state if available
848
+ const hasExistingState = this.loadMigrationState();
849
+ if (hasExistingState) {
850
+ this.logger.info('Resuming from existing migration state');
983
851
  this.logger.info('');
984
852
  }
985
853
  await this.setupRepositories();
@@ -989,8 +857,6 @@ import {
989
857
  await this.createServiceRepositoryPR();
990
858
  await this.createCleanupPRs();
991
859
  await this.generateStateRemovalCommands();
992
- // Optional: Update stack spaces
993
- await this.updateStackSpaces();
994
860
  await this.generateMigrationProcedure();
995
861
  this.logger.section('Migration Setup Complete!');
996
862
  this.logger.success('All preparation complete');
@@ -1009,20 +875,19 @@ import {
1009
875
  if (this.config.generatedFiles.iacDeployPR) {
1010
876
  this.logger.info(` iac-deploy Cleanup: ${this.config.generatedFiles.iacDeployPR.url}`);
1011
877
  }
1012
- if (this.config.generatedFiles.ecrDeployPR) {
1013
- this.logger.info(` ecr-deploy Cleanup: ${this.config.generatedFiles.ecrDeployPR.url}`);
1014
- }
1015
878
  this.logger.info('');
1016
879
  this.logger.info('Generated files:');
1017
880
  this.logger.info(` - ${this.config.generatedFiles.migrationProcedure}`);
1018
881
  this.logger.info(` - ${this.config.generatedFiles.stateRemovalCommands}`);
1019
882
  this.logger.info('');
1020
- this.logger.warn('NEXT STEPS:');
883
+ this.logger.warn(templates_1.MessageTemplates.FINAL_WORKFLOW_NEXT_STEPS);
1021
884
  this.logger.info('1. Review the PRs above');
1022
885
  this.logger.info('2. Review METASTACK_MIGRATION_PROCEDURE.md');
1023
- this.logger.info('3. Follow the migration procedure EXACTLY');
886
+ this.logger.info('3. Execute state removal commands from METASTACK_STATE_REMOVAL_COMMANDS.sh');
887
+ this.logger.info('4. Run migrate-spaces command manually: nx run <project>:stack-migration --command=migrate-spaces');
888
+ this.logger.info('5. Follow the migration procedure to merge PRs in correct order');
1024
889
  this.logger.info('');
1025
- this.logger.warn('IMPORTANT: Execute steps in the correct order!');
890
+ this.logger.warn(templates_1.MessageTemplates.FINAL_WORKFLOW_EXECUTE_ORDER_WARNING);
1026
891
  this.logger.info('');
1027
892
  this.logger.info('Note: Migration state saved to .migration-state.json for resumability');
1028
893
  }