@payfit/iac 1.0.3 → 1.0.4-ephemeral-iac-migration-docs.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/dist/executors/stack-migration/README.md +71 -115
- package/dist/executors/stack-migration/lib/migration-tool.d.ts +1 -5
- package/dist/executors/stack-migration/lib/migration-tool.js +213 -211
- package/dist/executors/stack-migration/lib/migration-tool.js.map +1 -1
- package/dist/executors/stack-migration/lib/templates/index.d.ts +1 -0
- package/dist/executors/stack-migration/lib/templates/index.js +1 -0
- package/dist/executors/stack-migration/lib/templates/index.js.map +1 -1
- package/dist/executors/stack-migration/lib/templates/mermaid-diagram.d.ts +13 -0
- package/dist/executors/stack-migration/lib/templates/mermaid-diagram.js +46 -0
- package/dist/executors/stack-migration/lib/templates/mermaid-diagram.js.map +1 -0
- package/dist/executors/stack-migration/lib/templates/message-templates.d.ts +15 -20
- package/dist/executors/stack-migration/lib/templates/message-templates.js +34 -93
- package/dist/executors/stack-migration/lib/templates/message-templates.js.map +1 -1
- package/dist/executors/stack-migration/lib/templates/pr-templates.d.ts +16 -9
- package/dist/executors/stack-migration/lib/templates/pr-templates.js +77 -121
- package/dist/executors/stack-migration/lib/templates/pr-templates.js.map +1 -1
- package/dist/executors/stack-migration/lib/templates/procedure-template.d.ts +1 -0
- package/dist/executors/stack-migration/lib/templates/procedure-template.js +37 -118
- package/dist/executors/stack-migration/lib/templates/procedure-template.js.map +1 -1
- package/dist/executors/stack-migration/schema.d.ts +2 -3
- package/dist/executors/stack-migration/schema.json +2 -3
- package/dist/executors/stack-migration/stack-migration.js +2 -5
- package/dist/executors/stack-migration/stack-migration.js.map +1 -1
- package/package.json +1 -1
|
@@ -9,9 +9,8 @@ const spacelift_client_1 = require("./spacelift-client");
|
|
|
9
9
|
const templates_1 = require("./templates");
|
|
10
10
|
class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
11
11
|
async setupRepositories(options) {
|
|
12
|
-
this.logger.step(1, 9, 'Repository Setup
|
|
13
|
-
|
|
14
|
-
this.logger.info('\nChecking spacectl CLI installation...');
|
|
12
|
+
this.logger.step(1, 9, 'Repository Setup');
|
|
13
|
+
this.logger.info('Checking spacectl CLI...');
|
|
15
14
|
await this.ensureSpacectlInstalled();
|
|
16
15
|
if (this.config.resuming &&
|
|
17
16
|
this.config.serviceRepository.name &&
|
|
@@ -34,7 +33,7 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
34
33
|
.map(g => g.trim())
|
|
35
34
|
.filter(g => g.length > 0);
|
|
36
35
|
}
|
|
37
|
-
this.logger.info('
|
|
36
|
+
this.logger.info('Cloning repositories...');
|
|
38
37
|
if (!this.files.exists(this.paths.tmpDir)) {
|
|
39
38
|
this.files.mkdir(this.paths.tmpDir, { recursive: true });
|
|
40
39
|
}
|
|
@@ -42,24 +41,30 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
42
41
|
const repoPath = this.paths.clonedRepo(key);
|
|
43
42
|
// Check if already cloned
|
|
44
43
|
if (this.files.exists(repoPath)) {
|
|
45
|
-
this.
|
|
46
|
-
|
|
44
|
+
if (this.dryRun) {
|
|
45
|
+
this.logger.info(`[DRY RUN] Would pull ${key}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
this.logger.info(`${key} already cloned, pulling latest`);
|
|
47
49
|
await this.git.pull(repoPath);
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
else {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
if (this.dryRun) {
|
|
54
|
+
this.logger.info(`[DRY RUN] Would clone ${key}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await this.git.clone(url, repoPath, this.paths.tmpDir);
|
|
58
|
+
this.logger.success(`Cloned ${key}`);
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
this.config.repositories[key] =
|
|
55
62
|
repoPath;
|
|
56
63
|
}
|
|
57
|
-
|
|
58
|
-
this.logger.info('\nChecking spacectl authentication...');
|
|
64
|
+
this.logger.info('Checking spacectl authentication...');
|
|
59
65
|
await this.ensureSpacectlAuthentication();
|
|
60
|
-
this.logger.success('
|
|
61
|
-
|
|
62
|
-
this.logger.info('\nUpdating .gitignore...');
|
|
66
|
+
this.logger.success('Repositories ready');
|
|
67
|
+
this.logger.info('Updating .gitignore...');
|
|
63
68
|
const gitignorePath = this.paths.serviceRepoGitignore();
|
|
64
69
|
let gitignoreContent = '';
|
|
65
70
|
if (this.files.exists(gitignorePath)) {
|
|
@@ -69,18 +74,13 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
69
74
|
if (!gitignoreContent.includes('.migration-tmp/')) {
|
|
70
75
|
gitignoreContent += '\n' + constants_1.GITIGNORE_ENTRIES;
|
|
71
76
|
this.files.writeFile(gitignorePath, gitignoreContent);
|
|
72
|
-
if (!this.dryRun) {
|
|
73
|
-
this.logger.success('Updated .gitignore with migration entries');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
this.logger.info('.gitignore already contains migration entries');
|
|
78
77
|
}
|
|
79
78
|
// Save initial state (only set timestamp if not resuming)
|
|
80
79
|
if (!this.config.migrationStartedAt) {
|
|
81
80
|
this.config.migrationStartedAt = new Date().toISOString();
|
|
82
81
|
}
|
|
83
82
|
this.saveMigrationState();
|
|
83
|
+
this.logger.success('Repository setup complete');
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* Step 2: Load Configuration
|
|
@@ -99,21 +99,21 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
99
99
|
if (!this.files.exists(iacConfigPath)) {
|
|
100
100
|
throw new Error(`Stack configuration not found at: ${iacConfigPath}`);
|
|
101
101
|
}
|
|
102
|
-
this.logger.info(
|
|
102
|
+
this.logger.info('Reading stack configuration...');
|
|
103
103
|
const iacConfig = this.files.readYaml(iacConfigPath);
|
|
104
104
|
// Extract stacks
|
|
105
105
|
if (iacConfig.stacks && Array.isArray(iacConfig.stacks)) {
|
|
106
106
|
this.config.stacks = iacConfig.stacks;
|
|
107
|
-
this.logger.success(`Found ${this.config.stacks.length} stack(s) in configuration`);
|
|
108
107
|
}
|
|
109
108
|
// Save state
|
|
110
109
|
this.saveMigrationState();
|
|
110
|
+
this.logger.success(`Configuration loaded (${this.config.stacks.length} stacks)`);
|
|
111
111
|
}
|
|
112
112
|
/**
|
|
113
113
|
* Step 3: Create Safety Net PR (disable autodeploy for stacks)
|
|
114
114
|
*/
|
|
115
115
|
async createSafetyNetPR() {
|
|
116
|
-
this.logger.step(3, 9, '
|
|
116
|
+
this.logger.step(3, 9, 'Safety Net PR');
|
|
117
117
|
// Skip if already created
|
|
118
118
|
if (this.config.generatedFiles.safetyNetPR) {
|
|
119
119
|
this.logger.success(`Safety net PR already exists: ${this.config.generatedFiles.safetyNetPR.url}`);
|
|
@@ -144,14 +144,21 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
144
144
|
}
|
|
145
145
|
// Create branch
|
|
146
146
|
const branchName = this.generateBranchName(`safety-net-${this.config.serviceRepository.name}`);
|
|
147
|
+
if (this.dryRun) {
|
|
148
|
+
const stacks = iacConfig.stacks || [];
|
|
149
|
+
const stackNames = stacks.map(s => s.name).filter(Boolean);
|
|
150
|
+
this.logger.info(`[DRY RUN] Would create safety net PR`);
|
|
151
|
+
this.logger.info(` → branch: ${branchName}`);
|
|
152
|
+
this.logger.info(` → stacks: ${stackNames.join(', ')}`);
|
|
153
|
+
this.logger.success('Safety net PR step complete (dry run)');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
147
156
|
await this.git.createBranch(branchName, this.config.repositories.iacDeploy);
|
|
148
157
|
// Update all stacks to have autodeploy: false
|
|
149
158
|
this.files.updateAutodeployInYaml(iacConfigPath, false);
|
|
150
159
|
await this.git.add(['.'], this.config.repositories.iacDeploy);
|
|
151
160
|
await this.git.commit(`chore: disable autodeploy for ${this.config.serviceRepository.name} stacks (migration safety net)`, this.config.repositories.iacDeploy);
|
|
152
|
-
|
|
153
|
-
this.logger.success(`Created safety net branch: ${branchName}`);
|
|
154
|
-
}
|
|
161
|
+
this.logger.success(`Created safety net branch: ${branchName}`);
|
|
155
162
|
// Push and create draft PR
|
|
156
163
|
const prUrl = await this.git.createPR(branchName, `chore: disable autodeploy for ${this.config.serviceRepository.name} stacks (migration safety net)`, templates_1.PRTemplates.safetyNetPRBody(this.config.serviceRepository.name, this.config.jiraTicket), this.config.repositories.iacDeploy);
|
|
157
164
|
this.config.generatedFiles.safetyNetPR = {
|
|
@@ -160,13 +167,12 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
160
167
|
};
|
|
161
168
|
this.saveMigrationState();
|
|
162
169
|
this.logger.success(`Safety net PR created: ${prUrl}`);
|
|
163
|
-
this.logger.warn(templates_1.MessageTemplates.SAFETY_NET_IMPORTANT);
|
|
164
170
|
}
|
|
165
171
|
/**
|
|
166
172
|
* Step 4: Create Domain Enrollment PR
|
|
167
173
|
*/
|
|
168
174
|
async createDomainEnrollmentPR() {
|
|
169
|
-
this.logger.step(4, 9, '
|
|
175
|
+
this.logger.step(4, 9, 'Domain Enrollment PR');
|
|
170
176
|
// Skip if already created
|
|
171
177
|
if (this.config.generatedFiles.domainEnrollmentPR) {
|
|
172
178
|
this.logger.success(`Domain enrollment PR already exists: ${this.config.generatedFiles.domainEnrollmentPR.url}`);
|
|
@@ -208,8 +214,14 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
208
214
|
},
|
|
209
215
|
},
|
|
210
216
|
};
|
|
211
|
-
this.
|
|
212
|
-
|
|
217
|
+
if (this.dryRun) {
|
|
218
|
+
this.logger.info('[DRY RUN] Would create new domain directory');
|
|
219
|
+
this.logger.info(` → domain: ${this.config.serviceRepository.domain}`);
|
|
220
|
+
this.logger.info(` → team: ${this.config.serviceRepository.team}`);
|
|
221
|
+
this.logger.info(` → okta: ${this.config.serviceRepository.oktaGroups.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
this.files.writeYaml(spaceliftYamlPath, initialConfig);
|
|
213
225
|
this.logger.success('Created domain directory and spacelift.yaml');
|
|
214
226
|
}
|
|
215
227
|
needsEnrollment = true;
|
|
@@ -242,8 +254,12 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
242
254
|
autodeploy: false,
|
|
243
255
|
};
|
|
244
256
|
spaceliftConfig.metastacks = metastacks;
|
|
245
|
-
this.
|
|
246
|
-
|
|
257
|
+
if (this.dryRun) {
|
|
258
|
+
this.logger.info('[DRY RUN] Would add metastack to existing domain');
|
|
259
|
+
this.logger.info(` → domain: ${this.config.serviceRepository.domain}`);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
this.files.writeYaml(spaceliftYamlPath, spaceliftConfig);
|
|
247
263
|
this.logger.success('Added repository to domain enrollment');
|
|
248
264
|
}
|
|
249
265
|
needsEnrollment = true;
|
|
@@ -252,23 +268,24 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
252
268
|
// Only create branch and commit if there are changes
|
|
253
269
|
if (needsEnrollment) {
|
|
254
270
|
const branchName = this.generateBranchName(`enroll-${this.config.serviceRepository.name}`);
|
|
271
|
+
if (this.dryRun) {
|
|
272
|
+
this.logger.info('[DRY RUN] Would create domain enrollment PR');
|
|
273
|
+
this.logger.info(` → branch: ${branchName}`);
|
|
274
|
+
this.logger.success('Domain enrollment PR step complete (dry run)');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
255
277
|
await this.git.createBranch(branchName, this.config.repositories.domainEnrollment);
|
|
256
278
|
await this.git.add(['.'], this.config.repositories.domainEnrollment);
|
|
257
279
|
await this.git.commit(`chore: enroll ${this.config.serviceRepository.name} in ${this.config.serviceRepository.domain} domain`, this.config.repositories.domainEnrollment);
|
|
258
|
-
|
|
259
|
-
this.logger.success(`Created branch: ${branchName}`);
|
|
260
|
-
}
|
|
280
|
+
this.logger.success(`Created branch: ${branchName}`);
|
|
261
281
|
// Push and create draft PR
|
|
262
|
-
const prUrl = await this.git.createPR(branchName, `chore: enroll ${this.config.serviceRepository.name} in ${this.config.serviceRepository.domain} domain`, templates_1.PRTemplates.domainEnrollmentPRBody(this.config.serviceRepository.name, this.config.serviceRepository.domain, this.config.jiraTicket), this.config.repositories.domainEnrollment);
|
|
282
|
+
const prUrl = await this.git.createPR(branchName, `chore: enroll ${this.config.serviceRepository.name} in ${this.config.serviceRepository.domain} domain`, templates_1.PRTemplates.domainEnrollmentPRBody(this.config.serviceRepository.name, this.config.serviceRepository.domain, this.config.jiraTicket, this.config.generatedFiles.safetyNetPR?.url), this.config.repositories.domainEnrollment);
|
|
263
283
|
this.config.generatedFiles.domainEnrollmentPR = {
|
|
264
284
|
branch: branchName,
|
|
265
285
|
url: prUrl,
|
|
266
286
|
};
|
|
267
287
|
this.saveMigrationState();
|
|
268
|
-
this.logger.info(
|
|
269
|
-
this.logger.info(`1. Review PR: ${prUrl}`);
|
|
270
|
-
this.logger.info(`2. Get it reviewed and approved`);
|
|
271
|
-
this.logger.info('3. Merge PR to deploy repository metastack');
|
|
288
|
+
this.logger.info(`Next: Review, approve and merge the PR: ${prUrl}`);
|
|
272
289
|
const shouldContinue = await this.confirm('\nHave you merged the domain enrollment PR?', false);
|
|
273
290
|
if (!shouldContinue) {
|
|
274
291
|
this.exitWithMessage('Please merge the PR before continuing');
|
|
@@ -294,10 +311,10 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
294
311
|
* Step 5: Create Service Repository PR (spacelift.yaml, import.tf)
|
|
295
312
|
*/
|
|
296
313
|
async createServiceRepositoryPR() {
|
|
297
|
-
this.logger.step(5, 9, '
|
|
314
|
+
this.logger.step(5, 9, 'Service Repository PR');
|
|
298
315
|
// Create safety net PR first if not already created
|
|
299
316
|
if (!this.config.generatedFiles.safetyNetPR) {
|
|
300
|
-
this.logger.info('
|
|
317
|
+
this.logger.info('Creating safety net PR first...');
|
|
301
318
|
await this.createSafetyNetPR();
|
|
302
319
|
}
|
|
303
320
|
// Skip if already created
|
|
@@ -315,8 +332,11 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
315
332
|
// Copy spacelift.yaml from iac-deploy
|
|
316
333
|
const iacConfigPath = this.paths.iacConfigFile(this.config.serviceRepository.name);
|
|
317
334
|
const targetSpacelliftPath = this.paths.serviceRepoSpacelliftYaml();
|
|
318
|
-
this.
|
|
319
|
-
|
|
335
|
+
if (this.dryRun) {
|
|
336
|
+
this.logger.info('[DRY RUN] Would copy spacelift.yaml from iac-deploy');
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
this.files.copyFile(iacConfigPath, targetSpacelliftPath);
|
|
320
340
|
this.logger.success('Created spacelift.yaml');
|
|
321
341
|
}
|
|
322
342
|
// Create .metastack/import.tf
|
|
@@ -329,30 +349,41 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
329
349
|
}
|
|
330
350
|
// Only add if not already present
|
|
331
351
|
if (!gitignoreContent.includes('.migration-tmp/')) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
352
|
+
if (this.dryRun) {
|
|
353
|
+
this.logger.info('[DRY RUN] Would update .gitignore');
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
gitignoreContent += constants_1.GITIGNORE_ENTRIES + '\n';
|
|
357
|
+
this.files.writeFile(gitignorePath, gitignoreContent);
|
|
335
358
|
this.logger.success('Updated .gitignore');
|
|
336
359
|
}
|
|
337
360
|
}
|
|
338
361
|
// Create branch and commit
|
|
339
362
|
const branchName = this.generateBranchName('metastack-migration');
|
|
363
|
+
if (this.dryRun) {
|
|
364
|
+
this.logger.info('[DRY RUN] Would create service repository PR');
|
|
365
|
+
this.logger.info(` → branch: ${branchName}`);
|
|
366
|
+
this.logger.info(` → files: spacelift.yaml, .metastack/, .gitignore`);
|
|
367
|
+
this.logger.success('Service repository PR step complete (dry run)');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
340
370
|
await this.git.createBranch(branchName, this.config.serviceRepository.path);
|
|
341
371
|
await this.git.add(['spacelift.yaml', '.metastack/', '.gitignore'], this.config.serviceRepository.path);
|
|
342
372
|
await this.git.commit(`chore: migrate to metastack\n\nAdd spacelift.yaml and import.tf for metastack migration`, this.config.serviceRepository.path);
|
|
343
|
-
|
|
344
|
-
this.logger.success(`Created branch: ${branchName}`);
|
|
345
|
-
}
|
|
373
|
+
this.logger.success(`Created branch: ${branchName}`);
|
|
346
374
|
// Push and create draft PR
|
|
347
|
-
const prUrl = await this.git.createPR(branchName, `chore: migrate to metastack`, templates_1.PRTemplates.serviceRepoPRBody(this.config.serviceRepository.name, this.config.jiraTicket
|
|
375
|
+
const prUrl = await this.git.createPR(branchName, `chore: migrate to metastack`, templates_1.PRTemplates.serviceRepoPRBody(this.config.serviceRepository.name, this.config.jiraTicket, {
|
|
376
|
+
safetyNet: this.config.generatedFiles.safetyNetPR?.url,
|
|
377
|
+
domainEnrollment: this.config.generatedFiles.domainEnrollmentPR?.url,
|
|
378
|
+
iacDeployCleanup: this.config.generatedFiles.iacDeployPR?.url,
|
|
379
|
+
}), this.config.serviceRepository.path);
|
|
348
380
|
this.config.generatedFiles.serviceRepoPR = {
|
|
349
381
|
branch: branchName,
|
|
350
382
|
url: prUrl,
|
|
351
383
|
};
|
|
352
384
|
this.saveMigrationState();
|
|
385
|
+
this.logger.success(`Service repository PR created: ${prUrl}`);
|
|
353
386
|
this.logger.warn(templates_1.MessageTemplates.CLEANUP_PR_NOT_MERGE_WARNING);
|
|
354
|
-
this.logger.info(templates_1.MessageTemplates.CLEANUP_PR_MERGE_TIMING);
|
|
355
|
-
this.logger.info(`PR URL: ${prUrl}`);
|
|
356
387
|
}
|
|
357
388
|
/**
|
|
358
389
|
* Generate import.tf file with import blocks for stacks
|
|
@@ -362,12 +393,12 @@ class MigrationTool extends migration_helper_1.MigrationHelper {
|
|
|
362
393
|
this.files.mkdir(metastackDir, { recursive: true });
|
|
363
394
|
// Ensure spacectl is authenticated before fetching resources
|
|
364
395
|
await this.ensureSpacectlAuthentication();
|
|
365
|
-
this.logger.info('Fetching stack resources
|
|
396
|
+
this.logger.info('Fetching stack resources...');
|
|
366
397
|
// Fetch all resources from projects-manager
|
|
367
398
|
let projectManagerResources = '';
|
|
368
399
|
try {
|
|
369
400
|
projectManagerResources = this.getStackResourcesFromDirectory(constants_1.STACK_MANAGERS.projectsManager, this.config.repositories.iacDeploy);
|
|
370
|
-
this.logger.success('Fetched resources
|
|
401
|
+
this.logger.success('Fetched resources');
|
|
371
402
|
}
|
|
372
403
|
catch (error) {
|
|
373
404
|
this.logger.error(`Failed to fetch resources from projects-manager: ${error}`);
|
|
@@ -456,10 +487,17 @@ import {
|
|
|
456
487
|
}
|
|
457
488
|
}
|
|
458
489
|
const importPath = this.paths.importTfFile();
|
|
459
|
-
this.
|
|
460
|
-
|
|
461
|
-
this.
|
|
462
|
-
|
|
490
|
+
if (this.dryRun) {
|
|
491
|
+
const stackIds = this.config.stacks.map(s => s.identifier).slice(0, 5);
|
|
492
|
+
const more = this.config.stacks.length > 5
|
|
493
|
+
? ` (+${this.config.stacks.length - 5} more)`
|
|
494
|
+
: '';
|
|
495
|
+
this.logger.info(`[DRY RUN] Would create import.tf (${this.config.stacks.length} stacks)`);
|
|
496
|
+
this.logger.info(` → stacks: ${stackIds.join(', ')}${more}`);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
this.files.writeFile(importPath, importTf);
|
|
500
|
+
this.logger.success(`Created .metastack/import.tf (${this.config.stacks.length} stacks)`);
|
|
463
501
|
}
|
|
464
502
|
// Update version.tf with Spacelift provider
|
|
465
503
|
const versionTfPath = path.join(metastackDir, 'version.tf');
|
|
@@ -472,20 +510,23 @@ import {
|
|
|
472
510
|
const existingContent = this.files.readFile(versionTfPath);
|
|
473
511
|
// Check if spacelift provider is already configured
|
|
474
512
|
if (!existingContent.includes('spacelift')) {
|
|
475
|
-
this.logger.info('
|
|
513
|
+
this.logger.info('Adding Spacelift provider to version.tf...');
|
|
476
514
|
const updatedContent = this.addSpaceliftProviderToVersionTf(existingContent);
|
|
477
515
|
if (updatedContent !== existingContent) {
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
516
|
+
if (this.dryRun) {
|
|
517
|
+
this.logger.info('[DRY RUN] Would add Spacelift provider to version.tf');
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
this.files.writeFile(versionTfPath, updatedContent);
|
|
521
|
+
this.logger.success('Added Spacelift provider');
|
|
481
522
|
}
|
|
482
523
|
}
|
|
483
524
|
else {
|
|
484
|
-
this.logger.warn('Could not update version.tf
|
|
525
|
+
this.logger.warn('Could not update version.tf, you should add the Spacelift provider manually');
|
|
485
526
|
}
|
|
486
527
|
}
|
|
487
528
|
else {
|
|
488
|
-
this.logger.info('
|
|
529
|
+
this.logger.info('Spacelift provider already in version.tf');
|
|
489
530
|
}
|
|
490
531
|
}
|
|
491
532
|
/**
|
|
@@ -597,7 +638,7 @@ $2`);
|
|
|
597
638
|
* Step 6: Create Cleanup PR for iac-deploy
|
|
598
639
|
*/
|
|
599
640
|
async createCleanupPRs() {
|
|
600
|
-
this.logger.step(6, 9, '
|
|
641
|
+
this.logger.step(6, 9, 'Cleanup PR iac-deploy');
|
|
601
642
|
// Ask for confirmation before creating cleanup PR
|
|
602
643
|
const shouldCreate = await this.confirm('\nCreate and push cleanup PR (iac-deploy)?', true);
|
|
603
644
|
if (!shouldCreate) {
|
|
@@ -606,41 +647,49 @@ $2`);
|
|
|
606
647
|
}
|
|
607
648
|
await this.createIacDeployCleanupPR();
|
|
608
649
|
await this.generateMigrationProcedure();
|
|
650
|
+
this.logger.success('Cleanup PR step complete');
|
|
609
651
|
this.logger.warn(templates_1.MessageTemplates.CLEANUP_PR_NOT_MERGE_WARNING);
|
|
610
|
-
this.logger.info(templates_1.MessageTemplates.CLEANUP_PR_STATE_REMOVAL_WARNING);
|
|
611
652
|
}
|
|
612
653
|
/**
|
|
613
654
|
* Create cleanup PR for iac-deploy
|
|
614
655
|
*/
|
|
615
656
|
async createIacDeployCleanupPR() {
|
|
616
|
-
this.logger.info('
|
|
657
|
+
this.logger.info('Creating iac-deploy cleanup PR...');
|
|
617
658
|
const configPath = this.paths.iacConfigFile(this.config.serviceRepository.name);
|
|
618
659
|
// Remove the config file
|
|
619
660
|
const shouldDelete = await this.confirm(`Delete ${this.config.serviceRepository.name}.yaml from iac-deploy?`, true);
|
|
620
661
|
if (shouldDelete) {
|
|
621
662
|
const branchName = this.generateBranchName(`cleanup-${this.config.serviceRepository.name}`);
|
|
663
|
+
if (this.dryRun) {
|
|
664
|
+
this.logger.info('[DRY RUN] Would create iac-deploy cleanup PR');
|
|
665
|
+
this.logger.info(` → branch: ${branchName}`);
|
|
666
|
+
this.logger.info(` → removes: ${this.config.serviceRepository.name}.yaml`);
|
|
667
|
+
this.logger.success('iac-deploy cleanup PR step complete (dry run)');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
622
670
|
await this.git.createBranch(branchName, this.config.repositories.iacDeploy);
|
|
623
671
|
this.files.deleteFile(configPath);
|
|
624
672
|
await this.git.add(['.'], this.config.repositories.iacDeploy);
|
|
625
673
|
await this.git.commit(`chore: remove ${this.config.serviceRepository.name} config (migrated to metastack)`, this.config.repositories.iacDeploy);
|
|
626
|
-
|
|
627
|
-
this.logger.success(`Created iac-deploy cleanup branch: ${branchName}`);
|
|
628
|
-
}
|
|
674
|
+
this.logger.success(`Created iac-deploy cleanup branch: ${branchName}`);
|
|
629
675
|
// Push and create draft PR
|
|
630
|
-
const prUrl = await this.git.createPR(branchName, `chore: remove ${this.config.serviceRepository.name} config (migrated to metastack)`, templates_1.PRTemplates.iacDeployCleanupPRBody(this.config.serviceRepository.name, this.config.jiraTicket
|
|
676
|
+
const prUrl = await this.git.createPR(branchName, `chore: remove ${this.config.serviceRepository.name} config (migrated to metastack)`, templates_1.PRTemplates.iacDeployCleanupPRBody(this.config.serviceRepository.name, this.config.jiraTicket, {
|
|
677
|
+
safetyNet: this.config.generatedFiles.safetyNetPR?.url,
|
|
678
|
+
domainEnrollment: this.config.generatedFiles.domainEnrollmentPR?.url,
|
|
679
|
+
}), this.config.repositories.iacDeploy);
|
|
631
680
|
this.config.generatedFiles.iacDeployPR = {
|
|
632
681
|
branch: branchName,
|
|
633
682
|
url: prUrl,
|
|
634
683
|
};
|
|
635
684
|
this.saveMigrationState();
|
|
636
|
-
this.logger.
|
|
685
|
+
this.logger.success(`iac-deploy cleanup PR created: ${prUrl}`);
|
|
637
686
|
}
|
|
638
687
|
}
|
|
639
688
|
/**
|
|
640
689
|
* Step 7: Generate State Removal Commands
|
|
641
690
|
*/
|
|
642
691
|
async generateStateRemovalCommands() {
|
|
643
|
-
this.logger.step(7, 9, '
|
|
692
|
+
this.logger.step(7, 9, 'State Removal Commands');
|
|
644
693
|
let commands = `# State Removal Commands
|
|
645
694
|
# These commands must be run BEFORE merging cleanup PRs
|
|
646
695
|
# They use spacectl to safely remove resources from Terraform state
|
|
@@ -666,24 +715,18 @@ $2`);
|
|
|
666
715
|
}
|
|
667
716
|
const commandsPath = this.paths.stateRemovalCommands;
|
|
668
717
|
this.files.writeFile(commandsPath, commands);
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
this.saveMigrationState();
|
|
674
|
-
this.logger.info(templates_1.MessageTemplates.STATE_REMOVAL_REVIEW_WARNING);
|
|
675
|
-
}
|
|
718
|
+
this.config.generatedFiles.stateRemovalCommands = commandsPath;
|
|
719
|
+
this.saveMigrationState();
|
|
720
|
+
this.logger.success(`State removal commands generated: ${commandsPath}`);
|
|
721
|
+
this.logger.warn(templates_1.MessageTemplates.STATE_REMOVAL_REVIEW_WARNING);
|
|
676
722
|
}
|
|
677
723
|
/**
|
|
678
724
|
* Step 8: Update Stack Spaces
|
|
679
725
|
*/
|
|
680
726
|
async updateStackSpaces() {
|
|
681
|
-
this.logger.step(8, 9, 'Update Stack Spaces
|
|
727
|
+
this.logger.step(8, 9, 'Update Stack Spaces to domain space');
|
|
682
728
|
this.logger.info(templates_1.MessageTemplates.STACK_SPACES_DESCRIPTION);
|
|
683
|
-
this.logger.info(templates_1.MessageTemplates.STACK_SPACES_REASON);
|
|
684
|
-
this.logger.info('');
|
|
685
729
|
this.logger.warn(templates_1.MessageTemplates.STACK_SPACES_IMPORTANT_TIMING);
|
|
686
|
-
this.logger.info('');
|
|
687
730
|
const shouldUpdate = await this.confirm('Update stack spaces now?', false);
|
|
688
731
|
if (!shouldUpdate) {
|
|
689
732
|
this.logger.warn('Skipping stack space update');
|
|
@@ -696,26 +739,24 @@ $2`);
|
|
|
696
739
|
const targetSpaceName = `domain-${this.config.serviceRepository.domain}`;
|
|
697
740
|
this.logger.info(`Target space: ${targetSpaceName}`);
|
|
698
741
|
if (this.dryRun) {
|
|
699
|
-
this.
|
|
700
|
-
this.
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
742
|
+
const stackIds = this.config.stacks.map(s => s.identifier).slice(0, 5);
|
|
743
|
+
const more = this.config.stacks.length > 5
|
|
744
|
+
? ` (+${this.config.stacks.length - 5} more)`
|
|
745
|
+
: '';
|
|
746
|
+
this.logger.info(`[DRY RUN] Would move ${this.config.stacks.length} stacks to ${targetSpaceName}`);
|
|
747
|
+
this.logger.info(` → stacks: ${stackIds.join(', ')}${more}`);
|
|
748
|
+
this.logger.success('Migrate spaces step complete (dry run)');
|
|
706
749
|
return;
|
|
707
750
|
}
|
|
708
751
|
// Initialize GraphQL client
|
|
709
752
|
const client = new spacelift_client_1.SpaceliftGraphQLClient();
|
|
710
|
-
|
|
711
|
-
this.logger.info(`\nLooking up space ID for "${targetSpaceName}"...`);
|
|
753
|
+
this.logger.info(`Looking up space ID for ${targetSpaceName}...`);
|
|
712
754
|
const targetSpaceId = await client.getSpaceByPath(targetSpaceName);
|
|
713
755
|
if (!targetSpaceId) {
|
|
714
756
|
throw new Error(`Space "${targetSpaceName}" not found. Please ensure the domain enrollment PR has been merged and the space has been created.`);
|
|
715
757
|
}
|
|
716
758
|
this.logger.success(`Found target space ID: ${targetSpaceId}`);
|
|
717
|
-
|
|
718
|
-
this.logger.info(`\nUpdating ${this.config.stacks.length} stacks...`);
|
|
759
|
+
this.logger.info(`Updating ${this.config.stacks.length} stacks...`);
|
|
719
760
|
const results = [];
|
|
720
761
|
for (const stack of this.config.stacks) {
|
|
721
762
|
const stackId = this.getStackModuleKey(stack);
|
|
@@ -758,35 +799,35 @@ $2`);
|
|
|
758
799
|
const successful = results.filter(r => r.success).length;
|
|
759
800
|
const alreadyInTarget = results.filter(r => r.alreadyInTargetSpace).length;
|
|
760
801
|
const failed = results.filter(r => !r.success).length;
|
|
761
|
-
this.logger.info(
|
|
762
|
-
this.logger.info(` Successful updates: ${successful - alreadyInTarget}`);
|
|
763
|
-
this.logger.info(` Already in target space: ${alreadyInTarget}`);
|
|
802
|
+
this.logger.info(`Summary: ${successful - alreadyInTarget} updated, ${alreadyInTarget} already in place`);
|
|
764
803
|
if (failed > 0) {
|
|
765
|
-
this.logger.error(`
|
|
766
|
-
this.logger.info('\nFailed stacks:');
|
|
804
|
+
this.logger.error(`Failed: ${failed}`);
|
|
767
805
|
results
|
|
768
806
|
.filter(r => !r.success)
|
|
769
|
-
.forEach(r => {
|
|
770
|
-
|
|
771
|
-
});
|
|
772
|
-
throw new Error(`Failed to update ${failed} stack(s). Please review the errors above.`);
|
|
807
|
+
.forEach(r => this.logger.error(` ${r.stackId}: ${r.error}`));
|
|
808
|
+
throw new Error(`Failed to update ${failed} stack(s)`);
|
|
773
809
|
}
|
|
774
|
-
this.logger.success('
|
|
810
|
+
this.logger.success('All stacks updated');
|
|
775
811
|
}
|
|
776
812
|
/**
|
|
777
813
|
* Step 9: Generate Migration Procedure
|
|
778
814
|
*/
|
|
779
815
|
async generateMigrationProcedure() {
|
|
780
|
-
this.logger.step(9, 9, 'Generate
|
|
816
|
+
this.logger.step(9, 9, 'Generate Procedure doc');
|
|
781
817
|
const procedure = (0, templates_1.generateMigrationProcedure)(this.config);
|
|
782
818
|
const procedurePath = this.paths.procedure;
|
|
783
|
-
this.
|
|
784
|
-
|
|
785
|
-
this.logger.
|
|
786
|
-
this.config.
|
|
787
|
-
|
|
788
|
-
this.
|
|
819
|
+
if (this.dryRun) {
|
|
820
|
+
this.logger.info('[DRY RUN] Would generate migration procedure');
|
|
821
|
+
this.logger.info(` → path: ${procedurePath}`);
|
|
822
|
+
this.logger.info(` → domain: ${this.config.serviceRepository.domain}`);
|
|
823
|
+
this.logger.info(` → stacks: ${this.config.stacks.length}`);
|
|
824
|
+
this.logger.success('Generate procedure step complete (dry run)');
|
|
825
|
+
return;
|
|
789
826
|
}
|
|
827
|
+
this.files.writeFile(procedurePath, procedure);
|
|
828
|
+
this.logger.success(`Generated migration procedure: ${procedurePath}`);
|
|
829
|
+
this.config.generatedFiles.migrationProcedure = procedurePath;
|
|
830
|
+
this.saveMigrationState();
|
|
790
831
|
}
|
|
791
832
|
/**
|
|
792
833
|
* Display migration state
|
|
@@ -844,21 +885,65 @@ $2`);
|
|
|
844
885
|
}
|
|
845
886
|
}
|
|
846
887
|
/**
|
|
847
|
-
* Clean up migration files
|
|
888
|
+
* Clean up migration (creates PR + removes local files)
|
|
848
889
|
*/
|
|
849
890
|
async cleanupMigration() {
|
|
850
891
|
this.logger.section('Migration Cleanup');
|
|
851
|
-
this.logger.
|
|
852
|
-
this.logger.info(` - ${this.paths.tmpDir} (cloned repositories)`);
|
|
853
|
-
this.logger.info(` - ${this.paths.migrationState} (migration state)`);
|
|
854
|
-
this.logger.info(` - ${this.paths.stateRemovalCommands}`);
|
|
855
|
-
this.logger.info(` - ${this.paths.procedure}`);
|
|
892
|
+
this.logger.warn('⚠️ Only run this AFTER all migration PRs are merged and resources are imported!');
|
|
856
893
|
this.logger.info('');
|
|
857
|
-
|
|
894
|
+
this.logger.info('This will:');
|
|
895
|
+
this.logger.info(' 1. Create a PR to remove import.tf and spacelift provider');
|
|
896
|
+
this.logger.info(' 2. Remove local temp files (.migration-tmp, state, scripts)');
|
|
897
|
+
this.logger.info('');
|
|
898
|
+
const shouldCleanup = await this.confirm('Proceed with cleanup?', true);
|
|
858
899
|
if (!shouldCleanup) {
|
|
859
900
|
this.logger.info('Cleanup cancelled');
|
|
860
901
|
return;
|
|
861
902
|
}
|
|
903
|
+
// --- Part 1: Create cleanup PR ---
|
|
904
|
+
const importTfPath = this.paths.importTfFile();
|
|
905
|
+
const versionTfPath = path.join(this.paths.metastackDir, 'version.tf');
|
|
906
|
+
const branchName = this.generateBranchName('cleanup');
|
|
907
|
+
const canCreatePR = this.files.exists(importTfPath) && this.files.exists(versionTfPath);
|
|
908
|
+
if (canCreatePR) {
|
|
909
|
+
if (this.dryRun) {
|
|
910
|
+
this.logger.info('[DRY RUN] Would create cleanup PR');
|
|
911
|
+
this.logger.info(` → branch: ${branchName}`);
|
|
912
|
+
this.logger.info(` → removes: import.tf, spacelift provider`);
|
|
913
|
+
}
|
|
914
|
+
else {
|
|
915
|
+
await this.git.createBranch(branchName, this.config.serviceRepository.path);
|
|
916
|
+
// Remove import.tf
|
|
917
|
+
this.files.deleteFile(importTfPath);
|
|
918
|
+
this.logger.success('Deleted .metastack/import.tf');
|
|
919
|
+
// Remove spacelift provider from version.tf
|
|
920
|
+
const versionTfContent = this.files.readFile(versionTfPath);
|
|
921
|
+
const updatedVersionTf = this.removeSpaceliftProviderFromVersionTf(versionTfContent);
|
|
922
|
+
if (updatedVersionTf !== versionTfContent) {
|
|
923
|
+
this.files.writeFile(versionTfPath, updatedVersionTf);
|
|
924
|
+
this.logger.success('Removed Spacelift provider from version.tf');
|
|
925
|
+
}
|
|
926
|
+
// Commit and create PR
|
|
927
|
+
await this.git.add(['.metastack/'], this.config.serviceRepository.path);
|
|
928
|
+
await this.git.commit(`chore: remove migration files (import.tf and spacelift provider)`, this.config.serviceRepository.path);
|
|
929
|
+
const prUrl = await this.git.createPR(branchName, 'chore: remove migration files (import.tf and spacelift provider)', templates_1.PRTemplates.postMigrationCleanupPRBody(this.config.serviceRepository.name, this.config.jiraTicket), this.config.serviceRepository.path);
|
|
930
|
+
this.logger.success(`Cleanup PR created: ${prUrl}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
else {
|
|
934
|
+
this.logger.info('Skipping PR creation (import.tf already removed)');
|
|
935
|
+
}
|
|
936
|
+
// --- Part 2: Remove local temp files ---
|
|
937
|
+
this.logger.info('');
|
|
938
|
+
if (this.dryRun) {
|
|
939
|
+
this.logger.info('[DRY RUN] Would remove local temp files');
|
|
940
|
+
this.logger.info(` → ${this.paths.tmpDir}`);
|
|
941
|
+
this.logger.info(` → ${this.paths.migrationState}`);
|
|
942
|
+
this.logger.info(` → ${this.paths.stateRemovalCommands}`);
|
|
943
|
+
this.logger.info(` → ${this.paths.procedure}`);
|
|
944
|
+
this.logger.success('Cleanup step complete (dry run)');
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
862
947
|
const filesToRemove = [
|
|
863
948
|
this.paths.tmpDir,
|
|
864
949
|
this.paths.migrationState,
|
|
@@ -869,82 +954,15 @@ $2`);
|
|
|
869
954
|
if (this.files.exists(file)) {
|
|
870
955
|
if (this.files.isDirectory(file)) {
|
|
871
956
|
this.files.deleteDirectory(file);
|
|
872
|
-
this.logger.success(`Removed
|
|
957
|
+
this.logger.success(`Removed: ${path.basename(file)}`);
|
|
873
958
|
}
|
|
874
959
|
else {
|
|
875
960
|
this.files.deleteFile(file);
|
|
876
|
-
this.logger.success(`Removed
|
|
961
|
+
this.logger.success(`Removed: ${path.basename(file)}`);
|
|
877
962
|
}
|
|
878
963
|
}
|
|
879
964
|
}
|
|
880
|
-
this.logger.info('');
|
|
881
|
-
const shouldRemoveScript = await this.confirm('Remove the migration script (migrate-to-metastack.ts)?', false);
|
|
882
|
-
if (shouldRemoveScript) {
|
|
883
|
-
const scriptPath = path.join(this.config.serviceRepository.path, 'migrate-to-metastack.ts');
|
|
884
|
-
this.files.deleteFile(scriptPath);
|
|
885
|
-
this.logger.success('Removed migrate-to-metastack.ts');
|
|
886
|
-
}
|
|
887
965
|
this.logger.success('\nCleanup complete!');
|
|
888
|
-
this.logger.info('Migration files have been removed');
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* Create Final Cleanup PR (remove import.tf and spacelift provider from version.tf)
|
|
892
|
-
*/
|
|
893
|
-
async createFinalCleanupPR() {
|
|
894
|
-
this.logger.section('Final Cleanup PR');
|
|
895
|
-
this.logger.info('This command creates a PR to remove temporary migration files:');
|
|
896
|
-
this.logger.info(' - .metastack/import.tf');
|
|
897
|
-
this.logger.info(' - Spacelift provider from .metastack/version.tf');
|
|
898
|
-
this.logger.info('');
|
|
899
|
-
this.logger.warn('⚠️ Only run this AFTER all migration PRs are merged and resources are imported!');
|
|
900
|
-
this.logger.info('');
|
|
901
|
-
const shouldCreate = await this.confirm('Create final cleanup PR on service repository?', true);
|
|
902
|
-
if (!shouldCreate) {
|
|
903
|
-
this.logger.warn('Skipping final cleanup PR creation');
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
const importTfPath = this.paths.importTfFile();
|
|
907
|
-
const versionTfPath = path.join(this.paths.metastackDir, 'version.tf');
|
|
908
|
-
// Check if files exist
|
|
909
|
-
if (!this.files.exists(importTfPath)) {
|
|
910
|
-
this.logger.warn(`import.tf not found at ${importTfPath} - it may have already been removed`);
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
if (!this.files.exists(versionTfPath)) {
|
|
914
|
-
this.logger.error(`version.tf not found at ${versionTfPath}`);
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
// Create branch
|
|
918
|
-
const branchName = this.generateBranchName('final-cleanup');
|
|
919
|
-
await this.git.createBranch(branchName, this.config.serviceRepository.path);
|
|
920
|
-
// Remove import.tf
|
|
921
|
-
this.files.deleteFile(importTfPath);
|
|
922
|
-
if (!this.dryRun) {
|
|
923
|
-
this.logger.success('Deleted .metastack/import.tf');
|
|
924
|
-
}
|
|
925
|
-
// Remove spacelift provider from version.tf
|
|
926
|
-
const versionTfContent = this.files.readFile(versionTfPath);
|
|
927
|
-
const updatedVersionTf = this.removeSpaceliftProviderFromVersionTf(versionTfContent);
|
|
928
|
-
if (updatedVersionTf !== versionTfContent) {
|
|
929
|
-
this.files.writeFile(versionTfPath, updatedVersionTf);
|
|
930
|
-
if (!this.dryRun) {
|
|
931
|
-
this.logger.success('Removed Spacelift provider from version.tf');
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
else {
|
|
935
|
-
this.logger.warn('Spacelift provider not found in version.tf or could not be removed');
|
|
936
|
-
}
|
|
937
|
-
// Commit changes
|
|
938
|
-
await this.git.add(['.metastack/'], this.config.serviceRepository.path);
|
|
939
|
-
await this.git.commit(`chore: remove migration files (import.tf and spacelift provider)\n\nRemove temporary migration files after successful import`, this.config.serviceRepository.path);
|
|
940
|
-
if (!this.dryRun) {
|
|
941
|
-
this.logger.success(`Created branch: ${branchName}`);
|
|
942
|
-
}
|
|
943
|
-
// Push and create draft PR
|
|
944
|
-
const prUrl = await this.git.createPR(branchName, 'chore: remove migration files (import.tf and spacelift provider)', templates_1.PRTemplates.finalCleanupPRBody(this.config.serviceRepository.name, this.config.jiraTicket), this.config.serviceRepository.path);
|
|
945
|
-
this.logger.success(`Final cleanup PR created: ${prUrl}`);
|
|
946
|
-
this.logger.info('');
|
|
947
|
-
this.logger.warn('⚠️ Only merge this PR after confirming all resources are imported!');
|
|
948
966
|
}
|
|
949
967
|
/**
|
|
950
968
|
* Full migration workflow
|
|
@@ -952,10 +970,8 @@ $2`);
|
|
|
952
970
|
async run() {
|
|
953
971
|
this.logger.section('Metastack Migration Tool');
|
|
954
972
|
this.logger.info('This tool will guide you through the metastack migration');
|
|
955
|
-
this.logger.info('');
|
|
956
973
|
if (this.dryRun) {
|
|
957
974
|
this.logger.warn(templates_1.MessageTemplates.DRY_RUN_MODE_WARNING);
|
|
958
|
-
this.logger.info('');
|
|
959
975
|
}
|
|
960
976
|
// Load existing migration state if available
|
|
961
977
|
const hasExistingState = this.loadMigrationState();
|
|
@@ -972,37 +988,23 @@ $2`);
|
|
|
972
988
|
await this.generateStateRemovalCommands();
|
|
973
989
|
await this.generateMigrationProcedure();
|
|
974
990
|
this.logger.section('Migration Setup Complete!');
|
|
975
|
-
this.logger.success('All preparation complete');
|
|
976
991
|
this.logger.info('');
|
|
977
992
|
// Display PR URLs
|
|
978
993
|
this.logger.info('Created Pull Requests:');
|
|
979
994
|
if (this.config.generatedFiles.safetyNetPR) {
|
|
980
|
-
this.logger.info(` Safety Net
|
|
995
|
+
this.logger.info(` 1. Safety Net: ${this.config.generatedFiles.safetyNetPR.url}`);
|
|
981
996
|
}
|
|
982
997
|
if (this.config.generatedFiles.domainEnrollmentPR) {
|
|
983
|
-
this.logger.info(` Domain Enrollment: ${this.config.generatedFiles.domainEnrollmentPR.url}`);
|
|
984
|
-
}
|
|
985
|
-
if (this.config.generatedFiles.serviceRepoPR) {
|
|
986
|
-
this.logger.info(` Service Repository: ${this.config.generatedFiles.serviceRepoPR.url}`);
|
|
998
|
+
this.logger.info(` 2. Domain Enrollment: ${this.config.generatedFiles.domainEnrollmentPR.url}`);
|
|
987
999
|
}
|
|
988
1000
|
if (this.config.generatedFiles.iacDeployPR) {
|
|
989
|
-
this.logger.info(` iac-deploy Cleanup: ${this.config.generatedFiles.iacDeployPR.url}`);
|
|
1001
|
+
this.logger.info(` 3. iac-deploy Cleanup: ${this.config.generatedFiles.iacDeployPR.url}`);
|
|
1002
|
+
}
|
|
1003
|
+
if (this.config.generatedFiles.serviceRepoPR) {
|
|
1004
|
+
this.logger.info(` 4. Service Repository: ${this.config.generatedFiles.serviceRepoPR.url}`);
|
|
990
1005
|
}
|
|
991
1006
|
this.logger.info('');
|
|
992
|
-
this.logger.info('
|
|
993
|
-
this.logger.info(` - ${this.config.generatedFiles.migrationProcedure}`);
|
|
994
|
-
this.logger.info(` - ${this.config.generatedFiles.stateRemovalCommands}`);
|
|
995
|
-
this.logger.info('');
|
|
996
|
-
this.logger.warn(templates_1.MessageTemplates.FINAL_WORKFLOW_NEXT_STEPS);
|
|
997
|
-
this.logger.info('1. Review the PRs above');
|
|
998
|
-
this.logger.info('2. Review METASTACK_MIGRATION_PROCEDURE.md');
|
|
999
|
-
this.logger.info('3. Execute state removal commands from METASTACK_STATE_REMOVAL_COMMANDS.sh');
|
|
1000
|
-
this.logger.info('4. Run migrate-spaces command manually: nx run <project>:stack-migration --command=migrate-spaces');
|
|
1001
|
-
this.logger.info('5. Follow the migration procedure to merge PRs in correct order');
|
|
1002
|
-
this.logger.info('');
|
|
1003
|
-
this.logger.warn(templates_1.MessageTemplates.FINAL_WORKFLOW_EXECUTE_ORDER_WARNING);
|
|
1004
|
-
this.logger.info('');
|
|
1005
|
-
this.logger.info('Note: Migration state saved to .migration-state.json for resumability');
|
|
1007
|
+
this.logger.info('Follow the migration procedure in METASTACK_MIGRATION_PROCEDURE.md');
|
|
1006
1008
|
}
|
|
1007
1009
|
}
|
|
1008
1010
|
exports.MigrationTool = MigrationTool;
|