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

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);
@@ -170,17 +170,14 @@ export declare const InputLayoutDefinitionV2: z.ZodObject<z.objectUtil.extendSha
170
170
  title: z.ZodString;
171
171
  description: z.ZodString;
172
172
  type: z.ZodLiteral<"boolean">;
173
- enum: z.ZodOptional<z.ZodArray<z.ZodBoolean, "many">>;
174
173
  }, "strict", z.ZodTypeAny, {
175
174
  type: "boolean";
176
175
  description: string;
177
176
  title: string;
178
- enum?: boolean[] | undefined;
179
177
  }, {
180
178
  type: "boolean";
181
179
  description: string;
182
180
  title: string;
183
- enum?: boolean[] | undefined;
184
181
  }>, z.ZodObject<{
185
182
  title: z.ZodString;
186
183
  description: z.ZodString;
@@ -211,7 +208,6 @@ export declare const InputLayoutDefinitionV2: z.ZodObject<z.objectUtil.extendSha
211
208
  type: "boolean";
212
209
  description: string;
213
210
  title: string;
214
- enum?: boolean[] | undefined;
215
211
  } | {
216
212
  type: "string";
217
213
  description: string;
@@ -232,7 +228,6 @@ export declare const InputLayoutDefinitionV2: z.ZodObject<z.objectUtil.extendSha
232
228
  type: "boolean";
233
229
  description: string;
234
230
  title: string;
235
- enum?: boolean[] | undefined;
236
231
  } | {
237
232
  type: "string";
238
233
  description: string;
@@ -377,17 +372,14 @@ export declare const InputLayoutDefinition: z.ZodUnion<[z.ZodObject<z.objectUtil
377
372
  title: z.ZodString;
378
373
  description: z.ZodString;
379
374
  type: z.ZodLiteral<"boolean">;
380
- enum: z.ZodOptional<z.ZodArray<z.ZodBoolean, "many">>;
381
375
  }, "strict", z.ZodTypeAny, {
382
376
  type: "boolean";
383
377
  description: string;
384
378
  title: string;
385
- enum?: boolean[] | undefined;
386
379
  }, {
387
380
  type: "boolean";
388
381
  description: string;
389
382
  title: string;
390
- enum?: boolean[] | undefined;
391
383
  }>, z.ZodObject<{
392
384
  title: z.ZodString;
393
385
  description: z.ZodString;
@@ -418,7 +410,6 @@ export declare const InputLayoutDefinition: z.ZodUnion<[z.ZodObject<z.objectUtil
418
410
  type: "boolean";
419
411
  description: string;
420
412
  title: string;
421
- enum?: boolean[] | undefined;
422
413
  } | {
423
414
  type: "string";
424
415
  description: string;
@@ -439,7 +430,6 @@ export declare const InputLayoutDefinition: z.ZodUnion<[z.ZodObject<z.objectUtil
439
430
  type: "boolean";
440
431
  description: string;
441
432
  title: string;
442
- enum?: boolean[] | undefined;
443
433
  } | {
444
434
  type: "string";
445
435
  description: string;
@@ -490,17 +480,14 @@ export declare const LayoutDefinition: z.ZodObject<z.objectUtil.extendShape<Omit
490
480
  title: z.ZodString;
491
481
  description: z.ZodString;
492
482
  type: z.ZodLiteral<"boolean">;
493
- enum: z.ZodOptional<z.ZodArray<z.ZodBoolean, "many">>;
494
483
  }, "strict", z.ZodTypeAny, {
495
484
  type: "boolean";
496
485
  description: string;
497
486
  title: string;
498
- enum?: boolean[] | undefined;
499
487
  }, {
500
488
  type: "boolean";
501
489
  description: string;
502
490
  title: string;
503
- enum?: boolean[] | undefined;
504
491
  }>, z.ZodObject<{
505
492
  title: z.ZodString;
506
493
  description: z.ZodString;
@@ -533,7 +520,6 @@ export declare const LayoutDefinition: z.ZodObject<z.objectUtil.extendShape<Omit
533
520
  type: "boolean";
534
521
  description: string;
535
522
  title: string;
536
- enum?: boolean[] | undefined;
537
523
  } | {
538
524
  type: "string";
539
525
  description: string;
@@ -554,7 +540,6 @@ export declare const LayoutDefinition: z.ZodObject<z.objectUtil.extendShape<Omit
554
540
  type: "boolean";
555
541
  description: string;
556
542
  title: string;
557
- enum?: boolean[] | undefined;
558
543
  } | {
559
544
  type: "string";
560
545
  description: string;
@@ -215,7 +215,6 @@ exports.InputLayoutDefinitionV2 = exports.BaseLayoutDefinition.extend({
215
215
  title: zod_1.z.string(),
216
216
  description: zod_1.z.string(),
217
217
  type: zod_1.z.literal(render_1.LayoutPropertyType.boolean),
218
- enum: zod_1.z.array(zod_1.z.boolean()).optional(),
219
218
  })
220
219
  .strict(),
221
220
  // String
@@ -46,14 +46,7 @@ function normalizeProperties(layout) {
46
46
  break;
47
47
  case render_1.LayoutOptionValueType.stringEnum:
48
48
  default:
49
- acc[key] = {
50
- type: render_1.LayoutPropertyType.string,
51
- title: option.displayName,
52
- description: option.description,
53
- };
54
- if (option.values !== undefined) {
55
- acc[key].enum = option.values;
56
- }
49
+ acc[key] = Object.assign({ type: render_1.LayoutPropertyType.string, title: option.displayName, description: option.description }, (option.values !== undefined && { enum: option.values }));
57
50
  }
58
51
  return acc;
59
52
  }, {});
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.3",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
6
6
  },