@squiz/dxp-cli-next 5.32.0-develop.1 → 5.32.0-develop.2

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.
@@ -16,6 +16,7 @@ const commander_1 = require("commander");
16
16
  const chalk_1 = __importDefault(require("chalk"));
17
17
  const ora_1 = __importDefault(require("ora"));
18
18
  const utils_1 = require("../utils");
19
+ const loadCctIdsFromFile_1 = require("../utils/loadCctIdsFromFile");
19
20
  const createMigrationCommand = () => {
20
21
  const createCommand = new commander_1.Command('create')
21
22
  .name('create')
@@ -23,6 +24,7 @@ const createMigrationCommand = () => {
23
24
  .addOption((0, utils_1.getParamOption)(utils_1.OptionName.ASSET_ID))
24
25
  .addOption((0, utils_1.getParamOption)(utils_1.OptionName.PREVIEW_ASSET_ID))
25
26
  .addOption((0, utils_1.getParamOption)(utils_1.OptionName.MATRIX_URL))
27
+ .addOption((0, utils_1.getParamOption)(utils_1.OptionName.CCT_IDS, false))
26
28
  .addOption((0, utils_1.getParamOption)(utils_1.OptionName.TENANT, false))
27
29
  .configureOutput({
28
30
  outputError(str, write) {
@@ -33,18 +35,13 @@ const createMigrationCommand = () => {
33
35
  yield (0, utils_1.throwErrorIfNotLoggedIn)(createCommand);
34
36
  const spinner = (0, ora_1.default)('Creating migration').start();
35
37
  try {
38
+ const cctIds = (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(options.cctIds);
36
39
  // Create migration
37
- const response = yield (0, utils_1.createMigration)(options);
40
+ const response = yield (0, utils_1.createMigration)(Object.assign(Object.assign({}, options), { cctIds }));
38
41
  if (!response) {
39
42
  throw new Error('Migration creation failed');
40
43
  }
41
- spinner.succeed('Migration created successfully');
42
- spinner.succeed(`Successfully created migration: ${JSON.stringify({
43
- migrationId: response.assetMigration.migrationId,
44
- assetId: response.assetMigration.assetId,
45
- stage: response.assetMigration.stage,
46
- status: response.assetMigration.status,
47
- })}`);
44
+ spinner.succeed((0, utils_1.buildMigrationSuccessMessage)('migration', response.assetMigration));
48
45
  }
49
46
  catch (error) {
50
47
  spinner.fail();
@@ -52,6 +52,7 @@ describe('createMigrationCommand', () => {
52
52
  jest.resetAllMocks();
53
53
  logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
54
54
  mockUtils.throwErrorIfNotLoggedIn.mockResolvedValue(undefined);
55
+ mockUtils.buildMigrationSuccessMessage.mockReturnValue('Migration created successfully');
55
56
  mockCreateMigrationResponse = {
56
57
  assetMigration: {
57
58
  migrationId: 'migration-123',
@@ -7,6 +7,7 @@ const commander_1 = require("commander");
7
7
  const create_1 = __importDefault(require("./create/create"));
8
8
  const get_1 = __importDefault(require("./get/get"));
9
9
  const next_1 = __importDefault(require("./next/next"));
10
+ const pre_1 = __importDefault(require("./pre/pre"));
10
11
  const settings_1 = __importDefault(require("./settings/settings"));
11
12
  const revert_1 = __importDefault(require("./revert/revert"));
12
13
  const list_1 = __importDefault(require("./list/list"));
@@ -17,6 +18,7 @@ migrationCommand
17
18
  .addCommand((0, get_1.default)())
18
19
  .addCommand((0, list_1.default)())
19
20
  .addCommand((0, next_1.default)())
21
+ .addCommand((0, pre_1.default)())
20
22
  .addCommand((0, settings_1.default)())
21
23
  .addCommand((0, revert_1.default)());
22
24
  exports.default = migrationCommand;
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ declare const preMigrationCommand: () => Command;
3
+ export default preMigrationCommand;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const commander_1 = require("commander");
16
+ const chalk_1 = __importDefault(require("chalk"));
17
+ const ora_1 = __importDefault(require("ora"));
18
+ const utils_1 = require("../utils");
19
+ const loadCctIdsFromFile_1 = require("../utils/loadCctIdsFromFile");
20
+ const preMigrationCommand = () => {
21
+ const preCommand = new commander_1.Command('pre')
22
+ .name('pre')
23
+ .description('Create a new pre-migration using the AI Page Migration service')
24
+ .addOption((0, utils_1.getParamOption)(utils_1.OptionName.MATRIX_URL))
25
+ .addOption((0, utils_1.getParamOption)(utils_1.OptionName.CCT_IDS))
26
+ .addOption((0, utils_1.getParamOption)(utils_1.OptionName.TENANT, false))
27
+ .configureOutput({
28
+ outputError(str, write) {
29
+ write(chalk_1.default.red(str));
30
+ },
31
+ })
32
+ .action((options) => __awaiter(void 0, void 0, void 0, function* () {
33
+ yield (0, utils_1.throwErrorIfNotLoggedIn)(preCommand);
34
+ const spinner = (0, ora_1.default)('Creating pre-migration').start();
35
+ try {
36
+ const cctIds = (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(options.cctIds);
37
+ // Create pre-migration
38
+ const response = yield (0, utils_1.createMigration)(Object.assign(Object.assign({}, options), { cctIds }));
39
+ if (!response) {
40
+ throw new Error('Pre-migration creation failed');
41
+ }
42
+ spinner.succeed((0, utils_1.buildMigrationSuccessMessage)('pre-migration', response.assetMigration));
43
+ }
44
+ catch (error) {
45
+ spinner.fail();
46
+ (0, utils_1.handleCommandError)(preCommand, error);
47
+ }
48
+ }));
49
+ if (process.env.ENABLE_OVERRIDE_MIGRATION_URL === 'true') {
50
+ preCommand.addOption(new commander_1.Option('-ou, --overrideUrl <string>', 'Developer option to override the entire migration url with a custom value'));
51
+ }
52
+ return preCommand;
53
+ };
54
+ exports.default = preMigrationCommand;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,269 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ var __importDefault = (this && this.__importDefault) || function (mod) {
35
+ return (mod && mod.__esModule) ? mod : { "default": mod };
36
+ };
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const nock_1 = __importDefault(require("nock"));
39
+ const pre_1 = __importDefault(require("./pre"));
40
+ const utils = __importStar(require("../utils/common"));
41
+ const createMigration = __importStar(require("../utils/createMigration"));
42
+ const loadCctIdsFromFile = __importStar(require("../utils/loadCctIdsFromFile"));
43
+ jest.mock('../utils/common');
44
+ jest.mock('../utils/createMigration');
45
+ jest.mock('../utils/loadCctIdsFromFile');
46
+ const mockUtils = utils;
47
+ const mockCreateMigration = createMigration;
48
+ const mockLoadCctIdsFromFile = loadCctIdsFromFile;
49
+ describe('preMigrationCommand', () => {
50
+ let logSpy;
51
+ let mockCreateMigrationResponse;
52
+ beforeEach(() => {
53
+ nock_1.default.cleanAll();
54
+ jest.clearAllMocks();
55
+ jest.resetAllMocks();
56
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
57
+ mockUtils.throwErrorIfNotLoggedIn.mockResolvedValue(undefined);
58
+ mockUtils.buildMigrationSuccessMessage.mockReturnValue('Pre-migration created successfully');
59
+ mockLoadCctIdsFromFile.loadCctIdsFromFile.mockReturnValue([
60
+ 'cct-id-1',
61
+ 'cct-id-2',
62
+ ]);
63
+ mockCreateMigrationResponse = {
64
+ assetMigration: {
65
+ migrationId: 'migration-123',
66
+ assetId: 'asset-456',
67
+ stage: 'pending',
68
+ status: 'created',
69
+ xmlFilePath: '/path/to/xml',
70
+ matrixUrl: 'https://matrix.example.com',
71
+ previewAssetId: 'preview-789',
72
+ created: 1234567890,
73
+ updated: 1234567890,
74
+ migrationIdAssetId: 'migration-123-asset-456',
75
+ },
76
+ };
77
+ mockCreateMigration.createMigration.mockResolvedValue(mockCreateMigrationResponse);
78
+ });
79
+ afterEach(() => {
80
+ logSpy.mockRestore();
81
+ });
82
+ describe('successful pre-migration creation', () => {
83
+ it('should create pre-migration successfully with all required options', () => __awaiter(void 0, void 0, void 0, function* () {
84
+ const program = (0, pre_1.default)();
85
+ yield program.parseAsync([
86
+ 'node',
87
+ 'dxp-cli',
88
+ '--matrix-url',
89
+ 'https://matrix.example.com',
90
+ '--cct-ids',
91
+ '/path/to/cct-ids.json',
92
+ ]);
93
+ expect(mockUtils.throwErrorIfNotLoggedIn).toHaveBeenCalledWith(program);
94
+ expect(mockLoadCctIdsFromFile.loadCctIdsFromFile).toHaveBeenCalledWith('/path/to/cct-ids.json');
95
+ expect(mockCreateMigration.createMigration).toHaveBeenCalledWith({
96
+ matrixUrl: 'https://matrix.example.com',
97
+ cctIds: ['cct-id-1', 'cct-id-2'],
98
+ });
99
+ expect(mockCreateMigration.createMigration).toHaveBeenCalledTimes(1);
100
+ expect(mockUtils.handleCommandError).not.toHaveBeenCalled();
101
+ }));
102
+ it('should create pre-migration with tenant option', () => __awaiter(void 0, void 0, void 0, function* () {
103
+ const program = (0, pre_1.default)();
104
+ yield program.parseAsync([
105
+ 'node',
106
+ 'dxp-cli',
107
+ '--matrix-url',
108
+ 'https://matrix.example.com',
109
+ '--cct-ids',
110
+ '/path/to/cct-ids.json',
111
+ '--tenant',
112
+ 'test-tenant',
113
+ ]);
114
+ expect(mockCreateMigration.createMigration).toHaveBeenCalledWith({
115
+ matrixUrl: 'https://matrix.example.com',
116
+ cctIds: ['cct-id-1', 'cct-id-2'],
117
+ tenant: 'test-tenant',
118
+ });
119
+ }));
120
+ it('should create pre-migration with override URL when environment variable is set', () => __awaiter(void 0, void 0, void 0, function* () {
121
+ const originalEnv = process.env.ENABLE_OVERRIDE_MIGRATION_URL;
122
+ process.env.ENABLE_OVERRIDE_MIGRATION_URL = 'true';
123
+ const program = (0, pre_1.default)();
124
+ yield program.parseAsync([
125
+ 'node',
126
+ 'dxp-cli',
127
+ '--matrix-url',
128
+ 'https://matrix.example.com',
129
+ '--cct-ids',
130
+ '/path/to/cct-ids.json',
131
+ '--overrideUrl',
132
+ 'https://custom.migration.url',
133
+ ]);
134
+ expect(mockCreateMigration.createMigration).toHaveBeenCalledWith({
135
+ matrixUrl: 'https://matrix.example.com',
136
+ cctIds: ['cct-id-1', 'cct-id-2'],
137
+ overrideUrl: 'https://custom.migration.url',
138
+ });
139
+ process.env.ENABLE_OVERRIDE_MIGRATION_URL = originalEnv;
140
+ }));
141
+ });
142
+ describe('error scenarios', () => {
143
+ it('should handle pre-migration creation failure', () => __awaiter(void 0, void 0, void 0, function* () {
144
+ mockCreateMigration.createMigration.mockResolvedValue(null);
145
+ mockUtils.handleCommandError.mockImplementation(() => { });
146
+ const program = (0, pre_1.default)();
147
+ yield program.parseAsync([
148
+ 'node',
149
+ 'dxp-cli',
150
+ '--matrix-url',
151
+ 'https://matrix.example.com',
152
+ '--cct-ids',
153
+ '/path/to/cct-ids.json',
154
+ ]);
155
+ expect(mockUtils.handleCommandError).toHaveBeenCalledWith(program, new Error('Pre-migration creation failed'));
156
+ }));
157
+ it('should handle migration API error', () => __awaiter(void 0, void 0, void 0, function* () {
158
+ const apiError = new Error('Migration API failed');
159
+ mockCreateMigration.createMigration.mockRejectedValue(apiError);
160
+ mockUtils.handleCommandError.mockImplementation(() => { });
161
+ const program = (0, pre_1.default)();
162
+ yield program.parseAsync([
163
+ 'node',
164
+ 'dxp-cli',
165
+ '--matrix-url',
166
+ 'https://matrix.example.com',
167
+ '--cct-ids',
168
+ '/path/to/cct-ids.json',
169
+ ]);
170
+ expect(mockUtils.handleCommandError).toHaveBeenCalledWith(program, apiError);
171
+ }));
172
+ it('should handle not being logged in', () => __awaiter(void 0, void 0, void 0, function* () {
173
+ const loginError = new Error('Not logged in');
174
+ mockUtils.throwErrorIfNotLoggedIn.mockImplementation(() => {
175
+ throw loginError;
176
+ });
177
+ const program = (0, pre_1.default)();
178
+ yield expect(program.parseAsync([
179
+ 'node',
180
+ 'dxp-cli',
181
+ '--matrix-url',
182
+ 'https://matrix.example.com',
183
+ '--cct-ids',
184
+ '/path/to/cct-ids.json',
185
+ ])).rejects.toThrow('Not logged in');
186
+ expect(mockCreateMigration.createMigration).not.toHaveBeenCalled();
187
+ }));
188
+ it('should handle loadCctIdsFromFile error', () => __awaiter(void 0, void 0, void 0, function* () {
189
+ const loadError = new Error('Failed to load CCT IDs from file');
190
+ mockLoadCctIdsFromFile.loadCctIdsFromFile.mockImplementation(() => {
191
+ throw loadError;
192
+ });
193
+ mockUtils.handleCommandError.mockImplementation(() => { });
194
+ const program = (0, pre_1.default)();
195
+ yield program.parseAsync([
196
+ 'node',
197
+ 'dxp-cli',
198
+ '--matrix-url',
199
+ 'https://matrix.example.com',
200
+ '--cct-ids',
201
+ '/path/to/invalid.json',
202
+ ]);
203
+ expect(mockUtils.handleCommandError).toHaveBeenCalledWith(program, loadError);
204
+ expect(mockCreateMigration.createMigration).not.toHaveBeenCalled();
205
+ }));
206
+ });
207
+ describe('required options validation', () => {
208
+ it('should require matrixUrl option', () => {
209
+ const program = (0, pre_1.default)().exitOverride();
210
+ expect(() => {
211
+ program.parse([
212
+ 'node',
213
+ 'dxp-cli',
214
+ '--cct-ids',
215
+ '/path/to/cct-ids.json',
216
+ ]);
217
+ }).toThrow();
218
+ });
219
+ it('should require cctIds option', () => {
220
+ const program = (0, pre_1.default)().exitOverride();
221
+ expect(() => {
222
+ program.parse([
223
+ 'node',
224
+ 'dxp-cli',
225
+ '--matrix-url',
226
+ 'https://matrix.example.com',
227
+ ]);
228
+ }).toThrow();
229
+ });
230
+ });
231
+ describe('command configuration', () => {
232
+ it('should have correct command name and description', () => {
233
+ const program = (0, pre_1.default)();
234
+ expect(program.name()).toBe('pre');
235
+ expect(program.description()).toBe('Create a new pre-migration using the AI Page Migration service');
236
+ });
237
+ it('should parse options correctly', () => {
238
+ const program = (0, pre_1.default)();
239
+ program.parse([
240
+ 'node',
241
+ 'dxp-cli',
242
+ '--matrix-url',
243
+ 'https://matrix.example.com',
244
+ '--cct-ids',
245
+ '/path/to/cct-ids.json',
246
+ '--tenant',
247
+ 'test-tenant',
248
+ ]);
249
+ const opts = program.opts();
250
+ expect(opts.matrixUrl).toBe('https://matrix.example.com');
251
+ expect(opts.cctIds).toBe('/path/to/cct-ids.json');
252
+ expect(opts.tenant).toBe('test-tenant');
253
+ });
254
+ });
255
+ describe('spinner and output behavior', () => {
256
+ it('should display appropriate spinner messages during execution', () => __awaiter(void 0, void 0, void 0, function* () {
257
+ const program = (0, pre_1.default)();
258
+ yield program.parseAsync([
259
+ 'node',
260
+ 'dxp-cli',
261
+ '--matrix-url',
262
+ 'https://matrix.example.com',
263
+ '--cct-ids',
264
+ '/path/to/cct-ids.json',
265
+ ]);
266
+ expect(mockCreateMigration.createMigration).toHaveBeenCalled();
267
+ }));
268
+ });
269
+ });
@@ -3,6 +3,16 @@ export interface CommonCommandOptions {
3
3
  overrideUrl?: string;
4
4
  }
5
5
  export interface AssetMigration {
6
+ /**
7
+ * Will be programmatically set while creating a migration or pre-migration,
8
+ * depending on whether assetId and cctIds are provided.
9
+ */
10
+ isPreMigration?: boolean;
11
+ /**
12
+ * In pre-migrations, this will be the CCT IDs to generate specs for.
13
+ * In migrations, this will be the CCT IDs to be skipped (because they were already converted and deployed).
14
+ */
15
+ cctIds?: string[];
6
16
  migrationId: string;
7
17
  assetId: string;
8
18
  xmlFilePath: string;
@@ -1,5 +1,9 @@
1
1
  import { AssetMigration, CommonCommandOptions } from '.';
2
2
  export interface CreateMigrationOptions extends CommonCommandOptions, Pick<AssetMigration, 'assetId' | 'previewAssetId' | 'matrixUrl'> {
3
+ /**
4
+ * The path to a JSON file containing the CCT IDs to be skipped in a migration (because they were already converted and deployed)
5
+ */
6
+ cctIds?: string;
3
7
  }
4
8
  export interface CreateMigrationApiResponse {
5
9
  assetMigration: AssetMigration;
@@ -2,5 +2,6 @@ export * from './common.types';
2
2
  export * from './createMigration.types';
3
3
  export * from './getMigration.types';
4
4
  export * from './nextStage.types';
5
+ export * from './preMigration.types';
5
6
  export * from './settings.types';
6
7
  export * from './revert.types';
@@ -18,5 +18,6 @@ __exportStar(require("./common.types"), exports);
18
18
  __exportStar(require("./createMigration.types"), exports);
19
19
  __exportStar(require("./getMigration.types"), exports);
20
20
  __exportStar(require("./nextStage.types"), exports);
21
+ __exportStar(require("./preMigration.types"), exports);
21
22
  __exportStar(require("./settings.types"), exports);
22
23
  __exportStar(require("./revert.types"), exports);
@@ -0,0 +1,10 @@
1
+ import { AssetMigration, CommonCommandOptions } from '.';
2
+ export interface PreMigrationOptions extends CommonCommandOptions, Pick<AssetMigration, 'matrixUrl'> {
3
+ /**
4
+ * The path to a JSON file containing the CCT IDs to generate specs
5
+ */
6
+ cctIds: string;
7
+ }
8
+ export interface PreMigrationApiResponse {
9
+ assetMigration: AssetMigration;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,5 @@
1
1
  import { Command } from 'commander';
2
+ import { AssetMigration } from '../types';
2
3
  /**
3
4
  * The purpose of this file is to provide utilities in common
4
5
  * such as functions that can be reused across the migration service.
@@ -29,3 +30,10 @@ export declare function buildMigrationUrl(tenantID?: string, overrideUrl?: strin
29
30
  export declare function validateAxiosStatus(status: number): boolean;
30
31
  export declare function getMigrationHeaders(tenantID?: string, isJson?: boolean): Promise<Record<string, string>>;
31
32
  export declare function redactKey(key: string, visibleChars?: number): string;
33
+ /**
34
+ * Builds a success message for migration creation.
35
+ * @param label - The label for the migration type (e.g., "migration" or "pre-migration").
36
+ * @param assetMigration - The asset migration response.
37
+ * @returns The formatted success message.
38
+ */
39
+ export declare function buildMigrationSuccessMessage(label: string, assetMigration: AssetMigration): string;
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.redactKey = exports.getMigrationHeaders = exports.validateAxiosStatus = exports.buildMigrationUrl = exports.throwErrorIfNotLoggedIn = exports.handleCommandError = void 0;
15
+ exports.buildMigrationSuccessMessage = exports.redactKey = exports.getMigrationHeaders = exports.validateAxiosStatus = exports.buildMigrationUrl = exports.throwErrorIfNotLoggedIn = exports.handleCommandError = void 0;
16
16
  const chalk_1 = __importDefault(require("chalk"));
17
17
  const ApplicationConfig_1 = require("../../ApplicationConfig");
18
18
  const axios_1 = __importDefault(require("axios"));
@@ -114,3 +114,21 @@ function redactKey(key, visibleChars = 3) {
114
114
  return masked + key.slice(-visibleChars);
115
115
  }
116
116
  exports.redactKey = redactKey;
117
+ /**
118
+ * Builds a success message for migration creation.
119
+ * @param label - The label for the migration type (e.g., "migration" or "pre-migration").
120
+ * @param assetMigration - The asset migration response.
121
+ * @returns The formatted success message.
122
+ */
123
+ function buildMigrationSuccessMessage(label, assetMigration) {
124
+ const { isPreMigration, cctIds, migrationId, assetId, stage, status } = assetMigration;
125
+ return `Successfully created ${label}: ${JSON.stringify({
126
+ isPreMigration,
127
+ migrationId,
128
+ assetId,
129
+ stage,
130
+ status,
131
+ cctIds: (cctIds === null || cctIds === void 0 ? void 0 : cctIds.length) ? JSON.stringify(cctIds).substring(0, 100) : '[]',
132
+ })}`;
133
+ }
134
+ exports.buildMigrationSuccessMessage = buildMigrationSuccessMessage;
@@ -1,5 +1,29 @@
1
- import { CreateMigrationOptions, CreateMigrationApiResponse } from '../types';
2
- export declare function createMigration(options: CreateMigrationOptions): Promise<CreateMigrationApiResponse>;
1
+ import { CommonCommandOptions, CreateMigrationApiResponse } from '../types';
2
+ export interface CreateMigrationInput extends CommonCommandOptions {
3
+ /**
4
+ * Optional for pre-migrations, required for migrations.
5
+ */
6
+ assetId?: string;
7
+ /**
8
+ * Optional for pre-migrations, required for migrations.
9
+ */
10
+ previewAssetId?: string;
11
+ /**
12
+ * Required for both migrations and pre-migrations.
13
+ */
14
+ matrixUrl: string;
15
+ /**
16
+ * Optional for migrations, required for pre-migrations.
17
+ */
18
+ cctIds?: string[];
19
+ }
20
+ /**
21
+ * Creates a new migration or pre-migration using the AI Page Migration service.
22
+ * Pre-migrations are used to generate specs for CCTs so they can be converted and deployed in advance.
23
+ * @param {CreateMigrationInput} input - The input parameters for the migration/pre-migration creation.
24
+ * @returns {Promise<CreateMigrationApiResponse>}
25
+ */
26
+ export declare function createMigration({ tenant, overrideUrl, assetId, previewAssetId, matrixUrl, cctIds, }: CreateMigrationInput): Promise<CreateMigrationApiResponse>;
3
27
  export declare function validateExportFolder(exportPath: string): void;
4
28
  /**
5
29
  * Creates a tar file from the export path.
@@ -18,20 +18,27 @@ const path_1 = __importDefault(require("path"));
18
18
  const ApiService_1 = require("../../ApiService");
19
19
  const _1 = require(".");
20
20
  const child_process_1 = require("child_process");
21
- function createMigration(options) {
21
+ /**
22
+ * Creates a new migration or pre-migration using the AI Page Migration service.
23
+ * Pre-migrations are used to generate specs for CCTs so they can be converted and deployed in advance.
24
+ * @param {CreateMigrationInput} input - The input parameters for the migration/pre-migration creation.
25
+ * @returns {Promise<CreateMigrationApiResponse>}
26
+ */
27
+ function createMigration({ tenant, overrideUrl, assetId, previewAssetId, matrixUrl, cctIds, }) {
22
28
  return __awaiter(this, void 0, void 0, function* () {
23
29
  const apiService = new ApiService_1.ApiService({
24
30
  validateStatus: _1.validateAxiosStatus,
25
31
  });
26
- const migrationUrl = yield (0, _1.buildMigrationUrl)(options.tenant, options.overrideUrl);
32
+ const migrationUrl = yield (0, _1.buildMigrationUrl)(tenant, overrideUrl);
27
33
  try {
28
34
  const payload = {
29
- assetId: options.assetId,
30
- previewAssetId: options.previewAssetId,
31
- matrixUrl: options.matrixUrl,
35
+ assetId,
36
+ previewAssetId,
37
+ matrixUrl,
38
+ cctIds,
32
39
  };
33
40
  const response = yield apiService.client.post(`${migrationUrl}`, payload, {
34
- headers: yield (0, _1.getMigrationHeaders)(options.tenant),
41
+ headers: yield (0, _1.getMigrationHeaders)(tenant),
35
42
  });
36
43
  if (response.status !== 200 && response.status !== 201) {
37
44
  throw new Error(`Migration creation failed with status: ${response.status}`);
@@ -0,0 +1 @@
1
+ export declare const loadCctIdsFromFile: (filePath?: string) => string[] | undefined;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadCctIdsFromFile = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const loadCctIdsFromFile = (filePath) => {
9
+ if (!filePath) {
10
+ return undefined;
11
+ }
12
+ try {
13
+ if (!fs_1.default.existsSync(filePath)) {
14
+ throw new Error(`CCT IDs file not found: ${filePath}`);
15
+ }
16
+ const fileContent = fs_1.default.readFileSync(filePath, 'utf8');
17
+ const cctIds = JSON.parse(fileContent);
18
+ if (!Array.isArray(cctIds) ||
19
+ cctIds.some(id => typeof id !== 'string' || !id.trim()) ||
20
+ cctIds.length === 0) {
21
+ throw new Error('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
22
+ }
23
+ return cctIds;
24
+ }
25
+ catch (error) {
26
+ if (error instanceof Error) {
27
+ throw new Error(`Failed to load CCT IDs from file: ${error.message}`);
28
+ }
29
+ throw new Error(`Failed to load CCT IDs from file: ${error}`);
30
+ }
31
+ };
32
+ exports.loadCctIdsFromFile = loadCctIdsFromFile;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const loadCctIdsFromFile_1 = require("./loadCctIdsFromFile");
8
+ jest.mock('fs');
9
+ const mockFs = fs_1.default;
10
+ describe('loadCctIdsFromFile', () => {
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+ it('should return undefined when no file path is provided', () => {
15
+ const result = (0, loadCctIdsFromFile_1.loadCctIdsFromFile)();
16
+ expect(result).toBeUndefined();
17
+ });
18
+ it('should load and parse valid JSON file with CCT IDs', () => {
19
+ const mockFilePath = '/test/path/cct-ids.json';
20
+ const mockJsonContent = '["cct-id-1", "cct-id-2", "cct-id-3"]';
21
+ const expectedResult = ['cct-id-1', 'cct-id-2', 'cct-id-3'];
22
+ mockFs.existsSync.mockReturnValue(true);
23
+ mockFs.readFileSync.mockReturnValue(mockJsonContent);
24
+ const result = (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath);
25
+ expect(mockFs.existsSync).toHaveBeenCalledWith(mockFilePath);
26
+ expect(mockFs.readFileSync).toHaveBeenCalledWith(mockFilePath, 'utf8');
27
+ expect(result).toEqual(expectedResult);
28
+ });
29
+ it('should throw error when file does not exist', () => {
30
+ const mockFilePath = '/test/path/nonexistent.json';
31
+ mockFs.existsSync.mockReturnValue(false);
32
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow(`Failed to load CCT IDs from file: CCT IDs file not found: ${mockFilePath}`);
33
+ });
34
+ it('should throw error when JSON is invalid', () => {
35
+ const mockFilePath = '/test/path/invalid.json';
36
+ const invalidJson = '["invalid": json}';
37
+ mockFs.existsSync.mockReturnValue(true);
38
+ mockFs.readFileSync.mockReturnValue(invalidJson);
39
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('Failed to load CCT IDs from file:');
40
+ });
41
+ it('should throw error when JSON is not an array', () => {
42
+ const mockFilePath = '/test/path/object.json';
43
+ const objectJson = '{"id": "cct-id-1"}';
44
+ mockFs.existsSync.mockReturnValue(true);
45
+ mockFs.readFileSync.mockReturnValue(objectJson);
46
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
47
+ });
48
+ it('should throw error when array is empty', () => {
49
+ const mockFilePath = '/test/path/empty.json';
50
+ const emptyArrayJson = '[]';
51
+ mockFs.existsSync.mockReturnValue(true);
52
+ mockFs.readFileSync.mockReturnValue(emptyArrayJson);
53
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
54
+ });
55
+ it('should throw error when array contains non-string values', () => {
56
+ const mockFilePath = '/test/path/mixed.json';
57
+ const mixedArrayJson = '["cct-id-1", 123, "cct-id-3"]';
58
+ mockFs.existsSync.mockReturnValue(true);
59
+ mockFs.readFileSync.mockReturnValue(mixedArrayJson);
60
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
61
+ });
62
+ it('should throw error when array contains empty strings', () => {
63
+ const mockFilePath = '/test/path/empty-strings.json';
64
+ const emptyStringsJson = '["cct-id-1", "", "cct-id-3"]';
65
+ mockFs.existsSync.mockReturnValue(true);
66
+ mockFs.readFileSync.mockReturnValue(emptyStringsJson);
67
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
68
+ });
69
+ it('should throw error when array contains whitespace-only strings', () => {
70
+ const mockFilePath = '/test/path/whitespace.json';
71
+ const whitespaceJson = '["cct-id-1", " ", "cct-id-3"]';
72
+ mockFs.existsSync.mockReturnValue(true);
73
+ mockFs.readFileSync.mockReturnValue(whitespaceJson);
74
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
75
+ });
76
+ it('should throw error when JSON is null', () => {
77
+ const mockFilePath = '/test/path/null.json';
78
+ const nullJson = 'null';
79
+ mockFs.existsSync.mockReturnValue(true);
80
+ mockFs.readFileSync.mockReturnValue(nullJson);
81
+ expect(() => (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath)).toThrow('CCT IDs file must contain a valid array of strings representing CCT IDs with at least one CCT ID');
82
+ });
83
+ it('should handle single CCT ID in array', () => {
84
+ const mockFilePath = '/test/path/single.json';
85
+ const singleIdJson = '["cct-id-only"]';
86
+ mockFs.existsSync.mockReturnValue(true);
87
+ mockFs.readFileSync.mockReturnValue(singleIdJson);
88
+ const result = (0, loadCctIdsFromFile_1.loadCctIdsFromFile)(mockFilePath);
89
+ expect(result).toEqual(['cct-id-only']);
90
+ });
91
+ });
@@ -8,7 +8,8 @@ export declare enum OptionName {
8
8
  MATRIX_IDENTIFIER = "matrix-identifier",
9
9
  MATRIX_KEY = "matrix-key",
10
10
  CONTENT_API_KEY = "content-api-key",
11
- STAGE_OPTIONS = "stage-options"
11
+ STAGE_OPTIONS = "stage-options",
12
+ CCT_IDS = "cct-ids"
12
13
  }
13
14
  export declare const getParamOption: (param: OptionName, makeOptionMandatory?: boolean) => Option;
14
15
  export declare const addOverrideUrlOption: (command: Command) => void;
@@ -13,6 +13,7 @@ var OptionName;
13
13
  OptionName["MATRIX_KEY"] = "matrix-key";
14
14
  OptionName["CONTENT_API_KEY"] = "content-api-key";
15
15
  OptionName["STAGE_OPTIONS"] = "stage-options";
16
+ OptionName["CCT_IDS"] = "cct-ids";
16
17
  })(OptionName = exports.OptionName || (exports.OptionName = {}));
17
18
  const params = new Map([
18
19
  [
@@ -78,6 +79,13 @@ const params = new Map([
78
79
  description: 'Path to a JSON file containing stage options',
79
80
  },
80
81
  ],
82
+ [
83
+ OptionName.CCT_IDS,
84
+ {
85
+ flags: '--cct-ids <string>',
86
+ description: 'The IDs of the CCTs to be converted in a pre-migration, or to be skipped in a migration (already converted)',
87
+ },
88
+ ],
81
89
  ]);
82
90
  const getParamOption = (param, makeOptionMandatory = true) => {
83
91
  const paramInfo = params.get(param);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/dxp-cli-next",
3
- "version": "5.32.0-develop.1",
3
+ "version": "5.32.0-develop.2",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
6
6
  },