@squiz/dxp-cli-next 5.33.0-develop.2 → 5.33.0-develop.4
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/README.md +1 -0
- package/lib/migration/batch-get/batch-get.d.ts +3 -0
- package/lib/migration/batch-get/batch-get.js +45 -0
- package/lib/migration/batch-get/batch-get.spec.js +182 -0
- package/lib/migration/batch-next/batch-next.d.ts +3 -0
- package/lib/migration/batch-next/batch-next.js +60 -0
- package/lib/migration/batch-next/batch-next.spec.d.ts +1 -0
- package/lib/migration/batch-next/batch-next.spec.js +251 -0
- package/lib/migration/batch-revert/batch-revert.d.ts +3 -0
- package/lib/migration/batch-revert/batch-revert.js +45 -0
- package/lib/migration/batch-revert/batch-revert.spec.d.ts +1 -0
- package/lib/migration/batch-revert/batch-revert.spec.js +197 -0
- package/lib/migration/create/create.js +27 -7
- package/lib/migration/create/create.spec.js +48 -20
- package/lib/migration/get/get.js +3 -3
- package/lib/migration/get/get.spec.js +8 -19
- package/lib/migration/index.js +9 -1
- package/lib/migration/list/list.spec.js +7 -6
- package/lib/migration/mark-deployed/mark-deployed.d.ts +3 -0
- package/lib/migration/mark-deployed/mark-deployed.js +55 -0
- package/lib/migration/mark-deployed/mark-deployed.spec.d.ts +1 -0
- package/lib/migration/mark-deployed/mark-deployed.spec.js +217 -0
- package/lib/migration/next/next.spec.js +6 -5
- package/lib/migration/pre/pre.js +3 -3
- package/lib/migration/pre/pre.spec.js +8 -8
- package/lib/migration/revert/revert.js +1 -1
- package/lib/migration/revert/revert.spec.js +1 -1
- package/lib/migration/settings/settings.spec.js +2 -2
- package/lib/migration/types/common.types.d.ts +33 -2
- package/lib/migration/types/common.types.js +17 -0
- package/lib/migration/types/createMigration.types.d.ts +41 -3
- package/lib/migration/types/getMigration.types.d.ts +5 -1
- package/lib/migration/types/index.d.ts +1 -0
- package/lib/migration/types/index.js +1 -0
- package/lib/migration/types/markDeployed.types.d.ts +20 -0
- package/lib/migration/types/markDeployed.types.js +2 -0
- package/lib/migration/types/nextStage.types.d.ts +22 -1
- package/lib/migration/types/revert.types.d.ts +12 -0
- package/lib/migration/utils/common.d.ts +17 -3
- package/lib/migration/utils/common.js +56 -24
- package/lib/migration/utils/common.spec.js +73 -2
- package/lib/migration/utils/createMigration.d.ts +4 -21
- package/lib/migration/utils/createMigration.js +23 -32
- package/lib/migration/utils/createMigration.spec.js +19 -18
- package/lib/migration/utils/getMigration.d.ts +2 -1
- package/lib/migration/utils/getMigration.js +23 -18
- package/lib/migration/utils/getMigration.spec.js +146 -7
- package/lib/migration/utils/index.d.ts +1 -0
- package/lib/migration/utils/index.js +1 -0
- package/lib/migration/utils/listMigrations.spec.js +7 -6
- package/lib/migration/utils/loadAssetIdsFromFile.d.ts +9 -0
- package/lib/migration/utils/loadAssetIdsFromFile.js +40 -0
- package/lib/migration/utils/loadAssetIdsFromFile.spec.d.ts +1 -0
- package/lib/migration/utils/{loadCctIdsFromFile.spec.js → loadAssetIdsFromFile.spec.js} +13 -13
- package/lib/migration/utils/loadStageOptionsFromFile.d.ts +2 -1
- package/lib/migration/utils/markComponentsDeployed.d.ts +2 -0
- package/lib/migration/utils/markComponentsDeployed.js +37 -0
- package/lib/migration/utils/markComponentsDeployed.spec.d.ts +1 -0
- package/lib/migration/utils/markComponentsDeployed.spec.js +222 -0
- package/lib/migration/utils/nextStage.d.ts +2 -1
- package/lib/migration/utils/nextStage.js +32 -22
- package/lib/migration/utils/nextStage.spec.js +179 -7
- package/lib/migration/utils/options.d.ts +4 -1
- package/lib/migration/utils/options.js +32 -2
- package/lib/migration/utils/revertMigration.d.ts +2 -1
- package/lib/migration/utils/revertMigration.js +21 -1
- package/lib/migration/utils/revertMigration.spec.js +115 -0
- package/lib/page/utils/definitions.js +1 -1
- package/lib/page/utils/definitions.spec.js +19 -8
- package/package.json +1 -1
- package/lib/migration/utils/loadCctIdsFromFile.d.ts +0 -1
- package/lib/migration/utils/loadCctIdsFromFile.js +0 -32
- /package/lib/migration/{utils/loadCctIdsFromFile.spec.d.ts → batch-get/batch-get.spec.d.ts} +0 -0
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { NextStageApiResponse, NextStageParsedOptions } from '../types';
|
|
1
|
+
import { NextStageApiResponse, NextStageParsedOptions, BatchNextStageApiResponse, BatchNextStageParsedOptions } from '../types';
|
|
2
2
|
export declare function nextStage(options: NextStageParsedOptions): Promise<NextStageApiResponse>;
|
|
3
|
+
export declare function batchNextStage(options: BatchNextStageParsedOptions): Promise<BatchNextStageApiResponse>;
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.nextStage = void 0;
|
|
12
|
+
exports.batchNextStage = exports.nextStage = void 0;
|
|
13
13
|
const ApiService_1 = require("../../ApiService");
|
|
14
14
|
const _1 = require(".");
|
|
15
15
|
function nextStage(options) {
|
|
@@ -18,28 +18,38 @@ function nextStage(options) {
|
|
|
18
18
|
validateStatus: _1.validateAxiosStatus,
|
|
19
19
|
});
|
|
20
20
|
const migrationUrl = yield (0, _1.buildMigrationUrl)(options.tenant, options.overrideUrl);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
const { message } = response.data || {};
|
|
30
|
-
if (!message) {
|
|
31
|
-
throw new Error('Invalid response format from next stage');
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
message,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
if (error instanceof Error) {
|
|
39
|
-
throw error;
|
|
40
|
-
}
|
|
41
|
-
throw new Error(`Failed to start next stage: ${error}`);
|
|
21
|
+
const payload = Object.assign({}, (options.stageOptions && { stageOptions: options.stageOptions }));
|
|
22
|
+
const response = yield apiService.client.post(`${migrationUrl}/${options.migrationId}/assets/${options.assetId}/next`, payload, {
|
|
23
|
+
headers: yield (0, _1.getMigrationHeaders)(options.tenant),
|
|
24
|
+
});
|
|
25
|
+
const { message } = response.data || {};
|
|
26
|
+
if (!message) {
|
|
27
|
+
throw new Error('Invalid response format from next stage');
|
|
42
28
|
}
|
|
29
|
+
return {
|
|
30
|
+
message,
|
|
31
|
+
};
|
|
43
32
|
});
|
|
44
33
|
}
|
|
45
34
|
exports.nextStage = nextStage;
|
|
35
|
+
function batchNextStage(options) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const apiService = new ApiService_1.ApiService({
|
|
38
|
+
validateStatus: _1.validateAxiosStatus,
|
|
39
|
+
});
|
|
40
|
+
const migrationUrl = yield (0, _1.buildMigrationUrl)(options.tenant, options.overrideUrl);
|
|
41
|
+
const payload = Object.assign(Object.assign({}, (options.stageOptions && { stageOptions: options.stageOptions })), { currentStage: options.stage });
|
|
42
|
+
const response = yield apiService.client.post(`${migrationUrl}/${options.migrationId}/next`, payload, {
|
|
43
|
+
headers: yield (0, _1.getMigrationHeaders)(options.tenant),
|
|
44
|
+
});
|
|
45
|
+
const { success, failed } = response.data || {};
|
|
46
|
+
if (!success && !failed) {
|
|
47
|
+
throw new Error('Invalid response format from batch next stage');
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
success: success || [],
|
|
51
|
+
failed: failed || [],
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
exports.batchNextStage = batchNextStage;
|
|
@@ -34,6 +34,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
35
|
const ApiService_1 = require("../../ApiService");
|
|
36
36
|
const nextStage_1 = require("./nextStage");
|
|
37
|
+
const types_1 = require("../types");
|
|
37
38
|
const utils = __importStar(require("."));
|
|
38
39
|
jest.mock('../../ApiService');
|
|
39
40
|
jest.mock('.');
|
|
@@ -83,13 +84,16 @@ describe('nextStage', () => {
|
|
|
83
84
|
},
|
|
84
85
|
});
|
|
85
86
|
}));
|
|
86
|
-
it('handles
|
|
87
|
-
|
|
87
|
+
it('handles API errors (4xx/5xx status codes)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
// In production, Axios throws AxiosError for 4xx/5xx status codes
|
|
89
|
+
const axiosError = new Error('Request failed with status code 400');
|
|
90
|
+
axiosError.isAxiosError = true;
|
|
91
|
+
axiosError.response = {
|
|
88
92
|
status: 400,
|
|
89
|
-
data: {},
|
|
93
|
+
data: { message: 'Bad Request' },
|
|
90
94
|
};
|
|
91
|
-
mockApiServiceInstance.client.post.
|
|
92
|
-
yield expect((0, nextStage_1.nextStage)(mockOptions)).rejects.toThrow('
|
|
95
|
+
mockApiServiceInstance.client.post.mockRejectedValue(axiosError);
|
|
96
|
+
yield expect((0, nextStage_1.nextStage)(mockOptions)).rejects.toThrow('Request failed with status code 400');
|
|
93
97
|
}));
|
|
94
98
|
it('handles missing message in response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
99
|
const mockResponse = {
|
|
@@ -104,9 +108,10 @@ describe('nextStage', () => {
|
|
|
104
108
|
mockApiServiceInstance.client.post.mockRejectedValue(error);
|
|
105
109
|
yield expect((0, nextStage_1.nextStage)(mockOptions)).rejects.toThrow('Network error');
|
|
106
110
|
}));
|
|
107
|
-
it('
|
|
111
|
+
it('propagates rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
|
+
// Non-Error values are propagated without wrapping
|
|
108
113
|
mockApiServiceInstance.client.post.mockRejectedValue('Unknown error');
|
|
109
|
-
yield expect((0, nextStage_1.nextStage)(mockOptions)).rejects.
|
|
114
|
+
yield expect((0, nextStage_1.nextStage)(mockOptions)).rejects.toBe('Unknown error');
|
|
110
115
|
}));
|
|
111
116
|
it('uses override URL when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
112
117
|
const optionsWithOverride = Object.assign(Object.assign({}, mockOptions), { overrideUrl: 'https://custom.migration.url' });
|
|
@@ -149,3 +154,170 @@ describe('nextStage', () => {
|
|
|
149
154
|
});
|
|
150
155
|
}));
|
|
151
156
|
});
|
|
157
|
+
describe('batchNextStage', () => {
|
|
158
|
+
let mockApiServiceInstance;
|
|
159
|
+
let mockOptions;
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
mockApiServiceInstance = {
|
|
162
|
+
client: {
|
|
163
|
+
post: jest.fn(),
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
mockApiService.mockImplementation(() => mockApiServiceInstance);
|
|
167
|
+
mockOptions = {
|
|
168
|
+
migrationId: 'migration-123',
|
|
169
|
+
stage: types_1.MigrationStage.preview,
|
|
170
|
+
tenant: 'test-tenant',
|
|
171
|
+
};
|
|
172
|
+
mockUtils.buildMigrationUrl.mockResolvedValue('https://example.com/__dxp/service/aiapps/migration/migrations');
|
|
173
|
+
mockUtils.getMigrationHeaders.mockResolvedValue({
|
|
174
|
+
'Content-Type': 'application/json',
|
|
175
|
+
'x-dxp-tenant': 'test-tenant',
|
|
176
|
+
});
|
|
177
|
+
mockUtils.validateAxiosStatus.mockImplementation(status => status >= 200 && status < 300);
|
|
178
|
+
});
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
jest.clearAllMocks();
|
|
181
|
+
});
|
|
182
|
+
it('triggers batch next stage successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
+
const mockResponse = {
|
|
184
|
+
status: 202,
|
|
185
|
+
data: {
|
|
186
|
+
success: [
|
|
187
|
+
{ assetIds: ['asset-1', 'asset-2'], message: 'Stage completed' },
|
|
188
|
+
],
|
|
189
|
+
failed: [],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
193
|
+
const result = yield (0, nextStage_1.batchNextStage)(mockOptions);
|
|
194
|
+
expect(result).toEqual({
|
|
195
|
+
success: [
|
|
196
|
+
{ assetIds: ['asset-1', 'asset-2'], message: 'Stage completed' },
|
|
197
|
+
],
|
|
198
|
+
failed: [],
|
|
199
|
+
});
|
|
200
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/migration-123/next', { currentStage: types_1.MigrationStage.preview }, {
|
|
201
|
+
headers: {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
'x-dxp-tenant': 'test-tenant',
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}));
|
|
207
|
+
it('handles mixed success and failure response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
208
|
+
const mockResponse = {
|
|
209
|
+
status: 202,
|
|
210
|
+
data: {
|
|
211
|
+
success: [{ assetIds: ['asset-1'], message: 'Stage completed' }],
|
|
212
|
+
failed: [{ assetIds: ['asset-2'], error: 'Asset not ready' }],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
216
|
+
const result = yield (0, nextStage_1.batchNextStage)(mockOptions);
|
|
217
|
+
expect(result).toEqual({
|
|
218
|
+
success: [{ assetIds: ['asset-1'], message: 'Stage completed' }],
|
|
219
|
+
failed: [{ assetIds: ['asset-2'], error: 'Asset not ready' }],
|
|
220
|
+
});
|
|
221
|
+
}));
|
|
222
|
+
it('handles API errors (4xx/5xx status codes)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
223
|
+
const axiosError = new Error('Request failed with status code 400');
|
|
224
|
+
axiosError.isAxiosError = true;
|
|
225
|
+
axiosError.response = {
|
|
226
|
+
status: 400,
|
|
227
|
+
data: { message: 'Bad Request' },
|
|
228
|
+
};
|
|
229
|
+
mockApiServiceInstance.client.post.mockRejectedValue(axiosError);
|
|
230
|
+
yield expect((0, nextStage_1.batchNextStage)(mockOptions)).rejects.toThrow('Request failed with status code 400');
|
|
231
|
+
}));
|
|
232
|
+
it('handles missing success and failed in response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
233
|
+
const mockResponse = {
|
|
234
|
+
status: 202,
|
|
235
|
+
data: {},
|
|
236
|
+
};
|
|
237
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
238
|
+
yield expect((0, nextStage_1.batchNextStage)(mockOptions)).rejects.toThrow('Invalid response format from batch next stage');
|
|
239
|
+
}));
|
|
240
|
+
it('handles API service errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
241
|
+
const error = new Error('Network error');
|
|
242
|
+
mockApiServiceInstance.client.post.mockRejectedValue(error);
|
|
243
|
+
yield expect((0, nextStage_1.batchNextStage)(mockOptions)).rejects.toThrow('Network error');
|
|
244
|
+
}));
|
|
245
|
+
it('propagates rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
246
|
+
mockApiServiceInstance.client.post.mockRejectedValue('Unknown error');
|
|
247
|
+
yield expect((0, nextStage_1.batchNextStage)(mockOptions)).rejects.toBe('Unknown error');
|
|
248
|
+
}));
|
|
249
|
+
it('uses override URL when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
250
|
+
const optionsWithOverride = Object.assign(Object.assign({}, mockOptions), { overrideUrl: 'https://custom.migration.url' });
|
|
251
|
+
mockUtils.buildMigrationUrl.mockResolvedValue('https://custom.migration.url/migrations');
|
|
252
|
+
const mockResponse = {
|
|
253
|
+
status: 202,
|
|
254
|
+
data: {
|
|
255
|
+
success: [],
|
|
256
|
+
failed: [],
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
260
|
+
yield (0, nextStage_1.batchNextStage)(optionsWithOverride);
|
|
261
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://custom.migration.url/migrations/migration-123/next', expect.any(Object), expect.any(Object));
|
|
262
|
+
}));
|
|
263
|
+
it('constructs correct URL path without assetId', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
264
|
+
const mockResponse = {
|
|
265
|
+
status: 202,
|
|
266
|
+
data: {
|
|
267
|
+
success: [],
|
|
268
|
+
failed: [],
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
272
|
+
yield (0, nextStage_1.batchNextStage)(mockOptions);
|
|
273
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://example.com/__dxp/service/aiapps/migration/migrations/migration-123/next', expect.any(Object), expect.any(Object));
|
|
274
|
+
}));
|
|
275
|
+
it('uses correct headers', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
276
|
+
const mockResponse = {
|
|
277
|
+
status: 202,
|
|
278
|
+
data: {
|
|
279
|
+
success: [],
|
|
280
|
+
failed: [],
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
284
|
+
yield (0, nextStage_1.batchNextStage)(mockOptions);
|
|
285
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith(expect.any(String), expect.any(Object), {
|
|
286
|
+
headers: {
|
|
287
|
+
'Content-Type': 'application/json',
|
|
288
|
+
'x-dxp-tenant': 'test-tenant',
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}));
|
|
292
|
+
it('includes stageOptions in payload when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
293
|
+
const optionsWithStageOptions = Object.assign(Object.assign({}, mockOptions), { stageOptions: { skipValidation: true } });
|
|
294
|
+
const mockResponse = {
|
|
295
|
+
status: 202,
|
|
296
|
+
data: {
|
|
297
|
+
success: [],
|
|
298
|
+
failed: [],
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
302
|
+
yield (0, nextStage_1.batchNextStage)(optionsWithStageOptions);
|
|
303
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith(expect.any(String), {
|
|
304
|
+
stageOptions: { skipValidation: true },
|
|
305
|
+
currentStage: types_1.MigrationStage.preview,
|
|
306
|
+
}, expect.any(Object));
|
|
307
|
+
}));
|
|
308
|
+
it('returns empty arrays when response has empty success and failed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
309
|
+
const mockResponse = {
|
|
310
|
+
status: 202,
|
|
311
|
+
data: {
|
|
312
|
+
success: [],
|
|
313
|
+
failed: [],
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
317
|
+
const result = yield (0, nextStage_1.batchNextStage)(mockOptions);
|
|
318
|
+
expect(result).toEqual({
|
|
319
|
+
success: [],
|
|
320
|
+
failed: [],
|
|
321
|
+
});
|
|
322
|
+
}));
|
|
323
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command, Option } from 'commander';
|
|
2
2
|
export declare enum OptionName {
|
|
3
3
|
ASSET_ID = "asset-id",
|
|
4
|
+
ASSET_IDS = "asset-ids",
|
|
4
5
|
PREVIEW_ASSET_ID = "preview-asset-id",
|
|
5
6
|
MATRIX_URL = "matrix-url",
|
|
6
7
|
TENANT = "tenant",
|
|
@@ -8,8 +9,10 @@ export declare enum OptionName {
|
|
|
8
9
|
MATRIX_IDENTIFIER = "matrix-identifier",
|
|
9
10
|
MATRIX_KEY = "matrix-key",
|
|
10
11
|
CONTENT_API_KEY = "content-api-key",
|
|
12
|
+
STAGE = "stage",
|
|
11
13
|
STAGE_OPTIONS = "stage-options",
|
|
12
|
-
CCT_IDS = "cct-ids"
|
|
14
|
+
CCT_IDS = "cct-ids",
|
|
15
|
+
TRIGGER_NEXT_STAGE = "trigger-next-stage"
|
|
13
16
|
}
|
|
14
17
|
export declare const getParamOption: (param: OptionName, makeOptionMandatory?: boolean) => Option;
|
|
15
18
|
export declare const addOverrideUrlOption: (command: Command) => void;
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.addOverrideUrlOption = exports.getParamOption = exports.OptionName = void 0;
|
|
4
4
|
const commander_1 = require("commander");
|
|
5
|
+
const types_1 = require("../types");
|
|
5
6
|
var OptionName;
|
|
6
7
|
(function (OptionName) {
|
|
7
8
|
OptionName["ASSET_ID"] = "asset-id";
|
|
9
|
+
OptionName["ASSET_IDS"] = "asset-ids";
|
|
8
10
|
OptionName["PREVIEW_ASSET_ID"] = "preview-asset-id";
|
|
9
11
|
OptionName["MATRIX_URL"] = "matrix-url";
|
|
10
12
|
OptionName["TENANT"] = "tenant";
|
|
@@ -12,8 +14,10 @@ var OptionName;
|
|
|
12
14
|
OptionName["MATRIX_IDENTIFIER"] = "matrix-identifier";
|
|
13
15
|
OptionName["MATRIX_KEY"] = "matrix-key";
|
|
14
16
|
OptionName["CONTENT_API_KEY"] = "content-api-key";
|
|
17
|
+
OptionName["STAGE"] = "stage";
|
|
15
18
|
OptionName["STAGE_OPTIONS"] = "stage-options";
|
|
16
19
|
OptionName["CCT_IDS"] = "cct-ids";
|
|
20
|
+
OptionName["TRIGGER_NEXT_STAGE"] = "trigger-next-stage";
|
|
17
21
|
})(OptionName = exports.OptionName || (exports.OptionName = {}));
|
|
18
22
|
const params = new Map([
|
|
19
23
|
[
|
|
@@ -23,6 +27,13 @@ const params = new Map([
|
|
|
23
27
|
description: 'The ID of the asset to be migrated',
|
|
24
28
|
},
|
|
25
29
|
],
|
|
30
|
+
[
|
|
31
|
+
OptionName.ASSET_IDS,
|
|
32
|
+
{
|
|
33
|
+
flags: '--asset-ids <string[]>',
|
|
34
|
+
description: 'Path to a JSON file containing the IDs of the assets to be migrated',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
26
37
|
[
|
|
27
38
|
OptionName.PREVIEW_ASSET_ID,
|
|
28
39
|
{
|
|
@@ -72,6 +83,14 @@ const params = new Map([
|
|
|
72
83
|
description: 'The Content API key for the Migrator API to interact with Content API',
|
|
73
84
|
},
|
|
74
85
|
],
|
|
86
|
+
[
|
|
87
|
+
OptionName.STAGE,
|
|
88
|
+
{
|
|
89
|
+
flags: '--stage <string>',
|
|
90
|
+
description: 'The current stage of the migrations to trigger the next stage for',
|
|
91
|
+
choices: Object.values(types_1.MigrationStage),
|
|
92
|
+
},
|
|
93
|
+
],
|
|
75
94
|
[
|
|
76
95
|
OptionName.STAGE_OPTIONS,
|
|
77
96
|
{
|
|
@@ -83,7 +102,14 @@ const params = new Map([
|
|
|
83
102
|
OptionName.CCT_IDS,
|
|
84
103
|
{
|
|
85
104
|
flags: '--cct-ids <string>',
|
|
86
|
-
description: '
|
|
105
|
+
description: 'Path to a JSON file containing the CCT IDs to be converted in a pre-migration, or to be skipped in a migration (already converted)',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
OptionName.TRIGGER_NEXT_STAGE,
|
|
110
|
+
{
|
|
111
|
+
flags: '--trigger-next-stage <boolean>',
|
|
112
|
+
description: 'Whether to trigger the next stage of the migration automatically. This will automatically be set to true for pre and bulk migrations.',
|
|
87
113
|
},
|
|
88
114
|
],
|
|
89
115
|
]);
|
|
@@ -92,7 +118,11 @@ const getParamOption = (param, makeOptionMandatory = true) => {
|
|
|
92
118
|
if (!paramInfo) {
|
|
93
119
|
throw new Error(`Param ${param} not found`);
|
|
94
120
|
}
|
|
95
|
-
|
|
121
|
+
const option = new commander_1.Option(paramInfo.flags, paramInfo.description).makeOptionMandatory(makeOptionMandatory);
|
|
122
|
+
if (paramInfo.choices) {
|
|
123
|
+
option.choices(paramInfo.choices);
|
|
124
|
+
}
|
|
125
|
+
return option;
|
|
96
126
|
};
|
|
97
127
|
exports.getParamOption = getParamOption;
|
|
98
128
|
const addOverrideUrlOption = (command) => {
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import { RevertMigrationOptions, RevertMigrationApiResponse } from '../types';
|
|
1
|
+
import { RevertMigrationOptions, RevertMigrationApiResponse, BatchRevertMigrationOptions, BatchRevertMigrationApiResponse } from '../types';
|
|
2
2
|
export declare function revertMigration(options: RevertMigrationOptions): Promise<RevertMigrationApiResponse>;
|
|
3
|
+
export declare function batchRevertMigration(options: BatchRevertMigrationOptions): Promise<BatchRevertMigrationApiResponse>;
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.revertMigration = void 0;
|
|
12
|
+
exports.batchRevertMigration = exports.revertMigration = void 0;
|
|
13
13
|
const ApiService_1 = require("../../ApiService");
|
|
14
14
|
const _1 = require(".");
|
|
15
15
|
function revertMigration(options) {
|
|
@@ -36,3 +36,23 @@ function revertMigration(options) {
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
exports.revertMigration = revertMigration;
|
|
39
|
+
function batchRevertMigration(options) {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const apiService = new ApiService_1.ApiService({
|
|
42
|
+
validateStatus: _1.validateAxiosStatus,
|
|
43
|
+
});
|
|
44
|
+
const migrationUrl = yield (0, _1.buildMigrationUrl)(options.tenant, options.overrideUrl);
|
|
45
|
+
const response = yield apiService.client.post(`${migrationUrl}/${options.migrationId}/rollback`, {}, {
|
|
46
|
+
headers: yield (0, _1.getMigrationHeaders)(options.tenant),
|
|
47
|
+
});
|
|
48
|
+
const { success, failed } = response.data || {};
|
|
49
|
+
if (!success && !failed) {
|
|
50
|
+
throw new Error('Invalid response format from batch revert');
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
success: success || [],
|
|
54
|
+
failed: failed || [],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
exports.batchRevertMigration = batchRevertMigration;
|
|
@@ -93,3 +93,118 @@ describe('revertMigration', () => {
|
|
|
93
93
|
});
|
|
94
94
|
}));
|
|
95
95
|
});
|
|
96
|
+
describe('batchRevertMigration', () => {
|
|
97
|
+
const mockOptions = {
|
|
98
|
+
migrationId: 'migration-123',
|
|
99
|
+
tenant: 'test-tenant',
|
|
100
|
+
};
|
|
101
|
+
const mockApiServiceInstance = {
|
|
102
|
+
client: {
|
|
103
|
+
post: jest.fn(),
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
jest.clearAllMocks();
|
|
108
|
+
mockApiService.mockImplementation(() => mockApiServiceInstance);
|
|
109
|
+
mockBuildMigrationUrl.mockResolvedValue('https://migration.example.com');
|
|
110
|
+
mockGetMigrationHeaders.mockResolvedValue({
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
'x-dxp-tenant': 'test-tenant',
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
it('should batch revert migration successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
const mockResponse = {
|
|
117
|
+
status: 200,
|
|
118
|
+
data: {
|
|
119
|
+
success: [{ assetIds: ['asset-1', 'asset-2'], message: 'Reverted' }],
|
|
120
|
+
failed: [],
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
124
|
+
const result = yield (0, revertMigration_1.batchRevertMigration)(mockOptions);
|
|
125
|
+
expect(result).toEqual({
|
|
126
|
+
success: [{ assetIds: ['asset-1', 'asset-2'], message: 'Reverted' }],
|
|
127
|
+
failed: [],
|
|
128
|
+
});
|
|
129
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://migration.example.com/migration-123/rollback', {}, {
|
|
130
|
+
headers: {
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
'x-dxp-tenant': 'test-tenant',
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}));
|
|
136
|
+
it('should handle mixed success and failure response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
137
|
+
const mockResponse = {
|
|
138
|
+
status: 200,
|
|
139
|
+
data: {
|
|
140
|
+
success: [{ assetIds: ['asset-1'], message: 'Reverted' }],
|
|
141
|
+
failed: [{ assetIds: ['asset-2'], error: 'Cannot revert' }],
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
145
|
+
const result = yield (0, revertMigration_1.batchRevertMigration)(mockOptions);
|
|
146
|
+
expect(result).toEqual({
|
|
147
|
+
success: [{ assetIds: ['asset-1'], message: 'Reverted' }],
|
|
148
|
+
failed: [{ assetIds: ['asset-2'], error: 'Cannot revert' }],
|
|
149
|
+
});
|
|
150
|
+
}));
|
|
151
|
+
it('should handle missing success and failed in response', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
152
|
+
const mockResponse = {
|
|
153
|
+
status: 200,
|
|
154
|
+
data: {},
|
|
155
|
+
};
|
|
156
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
157
|
+
yield expect((0, revertMigration_1.batchRevertMigration)(mockOptions)).rejects.toThrow('Invalid response format from batch revert');
|
|
158
|
+
}));
|
|
159
|
+
it('should handle API service errors', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
160
|
+
const error = new Error('Network error');
|
|
161
|
+
mockApiServiceInstance.client.post.mockRejectedValue(error);
|
|
162
|
+
yield expect((0, revertMigration_1.batchRevertMigration)(mockOptions)).rejects.toThrow('Network error');
|
|
163
|
+
}));
|
|
164
|
+
it('should propagate rejected values as-is', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
165
|
+
mockApiServiceInstance.client.post.mockRejectedValue('Unknown error');
|
|
166
|
+
yield expect((0, revertMigration_1.batchRevertMigration)(mockOptions)).rejects.toBe('Unknown error');
|
|
167
|
+
}));
|
|
168
|
+
it('should use override URL when provided', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
+
const mockResponse = {
|
|
170
|
+
status: 200,
|
|
171
|
+
data: {
|
|
172
|
+
success: [],
|
|
173
|
+
failed: [],
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
177
|
+
mockBuildMigrationUrl.mockResolvedValue('https://custom.migration.url');
|
|
178
|
+
const optionsWithOverride = Object.assign(Object.assign({}, mockOptions), { overrideUrl: 'https://custom.migration.url' });
|
|
179
|
+
yield (0, revertMigration_1.batchRevertMigration)(optionsWithOverride);
|
|
180
|
+
expect(mockBuildMigrationUrl).toHaveBeenCalledWith(mockOptions.tenant, 'https://custom.migration.url');
|
|
181
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://custom.migration.url/migration-123/rollback', {}, expect.any(Object));
|
|
182
|
+
}));
|
|
183
|
+
it('should construct correct URL path without assetId', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
184
|
+
const mockResponse = {
|
|
185
|
+
status: 200,
|
|
186
|
+
data: {
|
|
187
|
+
success: [],
|
|
188
|
+
failed: [],
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
192
|
+
yield (0, revertMigration_1.batchRevertMigration)(mockOptions);
|
|
193
|
+
expect(mockApiServiceInstance.client.post).toHaveBeenCalledWith('https://migration.example.com/migration-123/rollback', {}, expect.any(Object));
|
|
194
|
+
}));
|
|
195
|
+
it('should return empty arrays when response has empty success and failed', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
+
const mockResponse = {
|
|
197
|
+
status: 200,
|
|
198
|
+
data: {
|
|
199
|
+
success: [],
|
|
200
|
+
failed: [],
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
mockApiServiceInstance.client.post.mockResolvedValue(mockResponse);
|
|
204
|
+
const result = yield (0, revertMigration_1.batchRevertMigration)(mockOptions);
|
|
205
|
+
expect(result).toEqual({
|
|
206
|
+
success: [],
|
|
207
|
+
failed: [],
|
|
208
|
+
});
|
|
209
|
+
}));
|
|
210
|
+
});
|
|
@@ -82,7 +82,7 @@ function loadLayoutFromFile(layoutFile) {
|
|
|
82
82
|
encoding: 'utf-8',
|
|
83
83
|
});
|
|
84
84
|
try {
|
|
85
|
-
const layout = (0, yaml_1.parse)(content);
|
|
85
|
+
const layout = fileType === 'json' ? JSON.parse(content) : (0, yaml_1.parse)(content);
|
|
86
86
|
const fileName = path.basename(layoutFile);
|
|
87
87
|
if (fileName === exports.LAYOUT_MANIFEST_FILE) {
|
|
88
88
|
exports.InputLayoutDefinitionV2.parse(layout);
|
|
@@ -198,14 +198,25 @@ entry: template.hbs
|
|
|
198
198
|
}
|
|
199
199
|
throw new Error('File not found');
|
|
200
200
|
});
|
|
201
|
-
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects.toThrowErrorMatchingInlineSnapshot('"Failed loading layout definition: Failed to parse ./some-dir/manifest.json: Expected property name or \'}\' in JSON at position 1 (line 1 column 2)"');
|
|
202
|
+
}));
|
|
203
|
+
it('should throw an error if JSON layout file uses single quotes instead of double quotes', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
204
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
205
|
+
if (filePath.endsWith('manifest.json')) {
|
|
206
|
+
// Single quotes are not valid JSON - only double quotes are allowed for string values
|
|
207
|
+
return `{
|
|
208
|
+
"name": 'test-layout',
|
|
209
|
+
"displayName": "Test Layout",
|
|
210
|
+
"entry": "template.hbs",
|
|
211
|
+
"zones": []
|
|
212
|
+
}`;
|
|
213
|
+
}
|
|
214
|
+
if (filePath.endsWith('template.hbs')) {
|
|
215
|
+
return templateContent;
|
|
216
|
+
}
|
|
217
|
+
throw new Error('File not found');
|
|
218
|
+
});
|
|
219
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(manifestJson)).rejects.toThrowErrorMatchingInlineSnapshot('"Failed loading layout definition: Failed to parse ./some-dir/manifest.json: Unexpected token \'\'\', ..." "name": \'test-layo"... is not valid JSON"');
|
|
209
220
|
}));
|
|
210
221
|
it('should throw an error if layout file do not have valid extension', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
211
222
|
yield expect((0, definitions_1.loadLayoutDefinition)('/foo/manifest.html')).rejects.toThrow('Layout file must have a valid extension: [yaml|json]');
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const loadCctIdsFromFile: (filePath?: string) => string[] | undefined;
|
|
@@ -1,32 +0,0 @@
|
|
|
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;
|
|
File without changes
|