@squiz/dxp-cli-next 5.31.0-develop.5 → 5.31.0-develop.6
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/lib/page/layouts/deploy/deploy.js +1 -1
- package/lib/page/layouts/dev/dev.js +11 -1
- package/lib/page/layouts/dev/dev.spec.js +82 -0
- package/lib/page/layouts/validation/validateLayoutFormat.js +4 -2
- package/lib/page/layouts/validation/validateLayoutFormat.spec.js +4 -2
- package/lib/page/utils/definitions.d.ts +2 -2
- package/lib/page/utils/definitions.js +1 -1
- package/lib/page/utils/definitions.spec.js +56 -0
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ const createDeployCommand = () => {
|
|
|
56
56
|
try {
|
|
57
57
|
const layout = yield (0, definitions_1.loadLayoutDefinition)(layoutFile);
|
|
58
58
|
if (layout !== undefined) {
|
|
59
|
-
// Pre-deployment validation: check zones match between
|
|
59
|
+
// Pre-deployment validation: check zones match between manifest and template
|
|
60
60
|
const zoneValidationError = (0, validation_1.validateZoneConsistency)(layout);
|
|
61
61
|
if (zoneValidationError) {
|
|
62
62
|
throw new Error(zoneValidationError);
|
|
@@ -34,7 +34,7 @@ const createDevCommand = () => {
|
|
|
34
34
|
.option('--stylesheet <path>', 'Path to CSS file to include')
|
|
35
35
|
.option('--zones <items>', 'Zone content mappings', parse_args_1.parseZonesList, {})
|
|
36
36
|
.option('--properties <items>', 'Layout properties', parse_args_1.parsePropertiesList, {})
|
|
37
|
-
.allowUnknownOption(
|
|
37
|
+
.allowUnknownOption(false)
|
|
38
38
|
.allowExcessArguments(true)
|
|
39
39
|
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
40
|
try {
|
|
@@ -45,6 +45,16 @@ const createDevCommand = () => {
|
|
|
45
45
|
throw new Error(`Failed to load layout definition from ${options.config}`);
|
|
46
46
|
}
|
|
47
47
|
(0, validation_1.validateLayoutFormat)(exports.logger, options.config);
|
|
48
|
+
// Check zones match between manifest and template
|
|
49
|
+
const zoneValidationError = (0, validation_1.validateZoneConsistency)(rawLayoutDefinition);
|
|
50
|
+
if (zoneValidationError) {
|
|
51
|
+
throw new Error(zoneValidationError);
|
|
52
|
+
}
|
|
53
|
+
// Validate Handlebars template only refers to properties if manifest.json is used
|
|
54
|
+
const propertyValidationError = (0, validation_1.validatePropertyConsistency)(rawLayoutDefinition, options.config);
|
|
55
|
+
if (propertyValidationError) {
|
|
56
|
+
throw new Error(propertyValidationError);
|
|
57
|
+
}
|
|
48
58
|
// Confirm for entry property
|
|
49
59
|
const layoutDefinition = Object.assign({}, rawLayoutDefinition);
|
|
50
60
|
// Normalize properties to convert string booleans to actual booleans
|
|
@@ -186,4 +186,86 @@ describe('devCommand', () => {
|
|
|
186
186
|
yield program.parseAsync(args);
|
|
187
187
|
expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringContaining('Server start failed'));
|
|
188
188
|
}));
|
|
189
|
+
it('should not allow unknown options (e.g. --options instead of --properties)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
190
|
+
const config = './src/__tests__/layout.yaml';
|
|
191
|
+
const program = (0, dev_1.default)();
|
|
192
|
+
const args = createMockArgs({ config });
|
|
193
|
+
args.push('--options=backgroundColor=red');
|
|
194
|
+
process.argv = args;
|
|
195
|
+
const errorSpy = jest.spyOn(program, 'error').mockImplementation();
|
|
196
|
+
yield program.parseAsync(args);
|
|
197
|
+
expect(errorSpy).toHaveBeenCalledWith("error: unknown option '--options=backgroundColor=red'", { code: 'commander.unknownOption' });
|
|
198
|
+
}));
|
|
199
|
+
describe('zone consistency validation', () => {
|
|
200
|
+
it('should handle zone consistency validation errors where zones are used but not defined in the layout', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
|
+
const config = './src/__tests__/layout.yaml';
|
|
202
|
+
// Mock layout with zones that don't match the template
|
|
203
|
+
const mockLayout = {
|
|
204
|
+
name: 'test-layout',
|
|
205
|
+
zones: [
|
|
206
|
+
{
|
|
207
|
+
key: 'col1',
|
|
208
|
+
displayName: 'Column 1',
|
|
209
|
+
description: 'The first column',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
template: '{{zones.col1}} {{zones.col2}}', // col2 is used but not defined in zones
|
|
213
|
+
};
|
|
214
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
215
|
+
const program = (0, dev_1.default)();
|
|
216
|
+
const args = createMockArgs({ config });
|
|
217
|
+
process.argv = args;
|
|
218
|
+
yield program.parseAsync(args);
|
|
219
|
+
expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringMatching(/Zone consistency validation failed[\s\S]*Zones used in template but not defined in layout definition: col2/));
|
|
220
|
+
}));
|
|
221
|
+
it('should handle zone consistency validation errors where zones are defined but not used in the template', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
+
const config = './src/__tests__/layout.yaml';
|
|
223
|
+
const mockLayout = {
|
|
224
|
+
name: 'test-layout',
|
|
225
|
+
zones: [
|
|
226
|
+
{
|
|
227
|
+
key: 'col1',
|
|
228
|
+
displayName: 'Column 1',
|
|
229
|
+
description: 'The first column',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
key: 'col2',
|
|
233
|
+
displayName: 'Column 2',
|
|
234
|
+
description: 'The second column',
|
|
235
|
+
}, // col2 is defined but not used in the template
|
|
236
|
+
],
|
|
237
|
+
template: '{{zones.col1}}',
|
|
238
|
+
};
|
|
239
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
240
|
+
const program = (0, dev_1.default)();
|
|
241
|
+
const args = createMockArgs({ config });
|
|
242
|
+
process.argv = args;
|
|
243
|
+
yield program.parseAsync(args);
|
|
244
|
+
expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringMatching(/Zone consistency validation failed[\s\S]*Zones defined in layout definition but not used in template: col2/));
|
|
245
|
+
}));
|
|
246
|
+
});
|
|
247
|
+
describe('property consistency validation', () => {
|
|
248
|
+
it('should handle property consistency validation errors where properties are used but not defined in the layout', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
249
|
+
const config = './src/__tests__/manifest.json';
|
|
250
|
+
// Mock layout with properties that don't match the template
|
|
251
|
+
const mockLayout = {
|
|
252
|
+
name: 'test-layout',
|
|
253
|
+
zones: [],
|
|
254
|
+
properties: {
|
|
255
|
+
title: {
|
|
256
|
+
type: 'string',
|
|
257
|
+
title: 'Title',
|
|
258
|
+
description: 'Page title',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
template: '{{properties.title}} {{properties.undefined}}', // undefined is used but not defined in properties
|
|
262
|
+
};
|
|
263
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
264
|
+
const program = (0, dev_1.default)();
|
|
265
|
+
const args = createMockArgs({ config });
|
|
266
|
+
process.argv = args;
|
|
267
|
+
yield program.parseAsync(args);
|
|
268
|
+
expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringMatching(/Property consistency validation failed[\s\S]*Properties used in template but not defined in layout definition: undefined/));
|
|
269
|
+
}));
|
|
270
|
+
});
|
|
189
271
|
});
|
|
@@ -11,7 +11,8 @@ function validateLayoutFormat(logger, layoutFile) {
|
|
|
11
11
|
if (!layoutFile) {
|
|
12
12
|
logger.warn(`⚠️ DEFAULT CONFIG FILE: "${definitions_1.LAYOUT_MANIFEST_FILE}" has replaced "page-layout.yaml" as the default config file. ` +
|
|
13
13
|
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
14
|
-
|
|
14
|
+
`Support for files other than "${definitions_1.LAYOUT_MANIFEST_FILE}" will be removed in a future version. ` +
|
|
15
|
+
'For more information, visit https://docs.squiz.net/component-service/latest/layouts/layout-files.html');
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
// Warn that any file other than manifest.json is deprecated
|
|
@@ -19,7 +20,8 @@ function validateLayoutFormat(logger, layoutFile) {
|
|
|
19
20
|
if (fileName !== definitions_1.LAYOUT_MANIFEST_FILE) {
|
|
20
21
|
logger.warn(`⚠️ DEPRECATED: The file name "${fileName}" is deprecated. ` +
|
|
21
22
|
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
22
|
-
|
|
23
|
+
`Support for files other than "${definitions_1.LAYOUT_MANIFEST_FILE}" will be removed in a future version. ` +
|
|
24
|
+
'For more information, visit https://docs.squiz.net/component-service/latest/layouts/layout-files.html');
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
exports.validateLayoutFormat = validateLayoutFormat;
|
|
@@ -23,7 +23,8 @@ describe('validateLayoutFormat', () => {
|
|
|
23
23
|
expect(mockLogger.warn).toHaveBeenCalledTimes(1);
|
|
24
24
|
expect(mockLogger.warn).toHaveBeenCalledWith('⚠️ DEPRECATED: The file name "page-layout.yaml" is deprecated. ' +
|
|
25
25
|
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
26
|
-
|
|
26
|
+
`Support for files other than "${definitions_1.LAYOUT_MANIFEST_FILE}" will be removed in a future version. ` +
|
|
27
|
+
'For more information, visit https://docs.squiz.net/component-service/latest/layouts/layout-files.html');
|
|
27
28
|
});
|
|
28
29
|
it('should extract filename correctly from full path', () => {
|
|
29
30
|
const layoutFile = '/very/deep/nested/path/to/layout/page-layout.yaml';
|
|
@@ -35,6 +36,7 @@ describe('validateLayoutFormat', () => {
|
|
|
35
36
|
expect(mockLogger.warn).toHaveBeenCalledTimes(1);
|
|
36
37
|
expect(mockLogger.warn).toHaveBeenCalledWith('⚠️ DEFAULT CONFIG FILE: "manifest.json" has replaced "page-layout.yaml" as the default config file. ' +
|
|
37
38
|
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
38
|
-
|
|
39
|
+
`Support for files other than "${definitions_1.LAYOUT_MANIFEST_FILE}" will be removed in a future version. ` +
|
|
40
|
+
'For more information, visit https://docs.squiz.net/component-service/latest/layouts/layout-files.html');
|
|
39
41
|
});
|
|
40
42
|
});
|
|
@@ -89,7 +89,7 @@ export declare const InputLayoutDefinitionV1: z.ZodObject<z.objectUtil.extendSha
|
|
|
89
89
|
values?: string[] | undefined;
|
|
90
90
|
valueType?: "boolean" | "text" | "string-enum" | undefined;
|
|
91
91
|
}>>>;
|
|
92
|
-
}>, "
|
|
92
|
+
}>, "strict", z.ZodTypeAny, {
|
|
93
93
|
name: string;
|
|
94
94
|
entry: string;
|
|
95
95
|
description: string;
|
|
@@ -300,7 +300,7 @@ export declare const InputLayoutDefinition: z.ZodUnion<[z.ZodObject<z.objectUtil
|
|
|
300
300
|
values?: string[] | undefined;
|
|
301
301
|
valueType?: "boolean" | "text" | "string-enum" | undefined;
|
|
302
302
|
}>>>;
|
|
303
|
-
}>, "
|
|
303
|
+
}>, "strict", z.ZodTypeAny, {
|
|
304
304
|
name: string;
|
|
305
305
|
entry: string;
|
|
306
306
|
description: string;
|
|
@@ -317,6 +317,40 @@ entry: template.hbs
|
|
|
317
317
|
|
|
318
318
|
Schema validation failed:
|
|
319
319
|
- Required at "name""
|
|
320
|
+
`);
|
|
321
|
+
}));
|
|
322
|
+
it('should throw an error if YAML contains properties (instead of options)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
323
|
+
const yamlContentWithProperties = `
|
|
324
|
+
name: test-layout
|
|
325
|
+
displayName: Test Layout
|
|
326
|
+
description: A test layout
|
|
327
|
+
zones:
|
|
328
|
+
main:
|
|
329
|
+
displayName: Main Zone
|
|
330
|
+
description: Main content area
|
|
331
|
+
properties:
|
|
332
|
+
color:
|
|
333
|
+
title: Color
|
|
334
|
+
description: Color options
|
|
335
|
+
type: string
|
|
336
|
+
enum: ['red', 'blue']
|
|
337
|
+
entry: template.hbs
|
|
338
|
+
`;
|
|
339
|
+
fs.readFile.mockImplementation((filePath) => {
|
|
340
|
+
if (filePath.endsWith('page-layout.yaml')) {
|
|
341
|
+
return yamlContentWithProperties;
|
|
342
|
+
}
|
|
343
|
+
if (filePath.endsWith('template.hbs')) {
|
|
344
|
+
return templateContent;
|
|
345
|
+
}
|
|
346
|
+
throw new Error('File not found');
|
|
347
|
+
});
|
|
348
|
+
yield expect((0, definitions_1.loadLayoutDefinition)(pageLayoutYaml)).rejects
|
|
349
|
+
.toThrowErrorMatchingInlineSnapshot(`
|
|
350
|
+
"Failed loading layout definition: Failed to parse ./some-dir/page-layout.yaml:
|
|
351
|
+
|
|
352
|
+
Schema validation failed:
|
|
353
|
+
- Unrecognized key(s) in object: 'properties'"
|
|
320
354
|
`);
|
|
321
355
|
}));
|
|
322
356
|
it('should throw an error if YAML layout file has invalid YAML', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -548,6 +582,28 @@ describe('LayoutDefinitionParse', () => {
|
|
|
548
582
|
expect((_g = layoutDefinition.options) === null || _g === void 0 ? void 0 : _g.withNeither.valueType).toBeUndefined();
|
|
549
583
|
expect((_h = layoutDefinition.options) === null || _h === void 0 ? void 0 : _h.withNeither.values).toBeUndefined();
|
|
550
584
|
});
|
|
585
|
+
it('should reject properties (instead of options)', () => {
|
|
586
|
+
expect(() => definitions_1.InputLayoutDefinitionV1.parse({
|
|
587
|
+
name: 'test-layout',
|
|
588
|
+
displayName: 'Test Layout',
|
|
589
|
+
description: 'A test layout',
|
|
590
|
+
entry: 'template.hbs',
|
|
591
|
+
zones: {
|
|
592
|
+
main: {
|
|
593
|
+
displayName: 'Main Zone',
|
|
594
|
+
description: 'Main content area',
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
properties: {
|
|
598
|
+
color: {
|
|
599
|
+
title: 'Color',
|
|
600
|
+
description: 'Color options',
|
|
601
|
+
type: 'string',
|
|
602
|
+
enum: ['red', 'blue'],
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
})).toThrow();
|
|
606
|
+
});
|
|
551
607
|
});
|
|
552
608
|
});
|
|
553
609
|
describe('V2 format - zones arrays and properties', () => {
|