@squiz/dxp-cli-next 5.31.0 → 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.
- package/lib/cdp/instance/activate/activate.js +6 -7
- package/lib/cdp/instance/activate/activate.spec.js +2 -8
- package/lib/cdp/utils.d.ts +2 -1
- package/lib/cdp/utils.js +4 -2
- package/lib/migration/create/create.js +5 -8
- package/lib/migration/create/create.spec.js +1 -0
- package/lib/migration/index.js +2 -0
- package/lib/migration/pre/pre.d.ts +3 -0
- package/lib/migration/pre/pre.js +54 -0
- package/lib/migration/pre/pre.spec.d.ts +1 -0
- package/lib/migration/pre/pre.spec.js +269 -0
- package/lib/migration/types/common.types.d.ts +10 -0
- package/lib/migration/types/createMigration.types.d.ts +4 -0
- package/lib/migration/types/index.d.ts +1 -0
- package/lib/migration/types/index.js +1 -0
- package/lib/migration/types/preMigration.types.d.ts +10 -0
- package/lib/migration/types/preMigration.types.js +2 -0
- package/lib/migration/utils/common.d.ts +8 -0
- package/lib/migration/utils/common.js +19 -1
- package/lib/migration/utils/createMigration.d.ts +26 -2
- package/lib/migration/utils/createMigration.js +13 -6
- package/lib/migration/utils/loadCctIdsFromFile.d.ts +1 -0
- package/lib/migration/utils/loadCctIdsFromFile.js +32 -0
- package/lib/migration/utils/loadCctIdsFromFile.spec.d.ts +1 -0
- package/lib/migration/utils/loadCctIdsFromFile.spec.js +91 -0
- package/lib/migration/utils/options.d.ts +2 -1
- package/lib/migration/utils/options.js +8 -0
- package/lib/page/layouts/deploy/deploy.js +44 -9
- package/lib/page/layouts/deploy/deploy.spec.js +110 -19
- package/lib/page/layouts/dev/dev.js +18 -4
- package/lib/page/layouts/dev/dev.spec.js +117 -8
- package/lib/page/layouts/validation/index.d.ts +2 -0
- package/lib/page/layouts/validation/index.js +5 -1
- package/lib/page/layouts/validation/property-consistency.d.ts +7 -0
- package/lib/page/layouts/validation/property-consistency.js +92 -0
- package/lib/page/layouts/validation/property-consistency.spec.d.ts +1 -0
- package/lib/page/layouts/validation/property-consistency.spec.js +305 -0
- package/lib/page/layouts/validation/validateLayoutFormat.d.ts +2 -0
- package/lib/page/layouts/validation/validateLayoutFormat.js +27 -0
- package/lib/page/layouts/validation/validateLayoutFormat.spec.d.ts +1 -0
- package/lib/page/layouts/validation/validateLayoutFormat.spec.js +42 -0
- package/lib/page/layouts/validation/zone-consistency.d.ts +1 -1
- package/lib/page/layouts/validation/zone-consistency.js +10 -9
- package/lib/page/layouts/validation/zone-consistency.spec.js +32 -34
- package/lib/page/utils/definitions.d.ts +347 -50
- package/lib/page/utils/definitions.js +103 -22
- package/lib/page/utils/definitions.spec.js +516 -267
- package/lib/page/utils/normalize.d.ts +8 -0
- package/lib/page/utils/normalize.js +61 -0
- package/lib/page/utils/normalize.spec.d.ts +1 -0
- package/lib/page/utils/normalize.spec.js +315 -0
- package/lib/page/utils/parse-args.d.ts +20 -4
- package/lib/page/utils/parse-args.js +48 -13
- package/lib/page/utils/parse-args.spec.js +159 -21
- package/lib/page/utils/render.d.ts +27 -9
- package/lib/page/utils/render.js +66 -12
- package/lib/page/utils/render.spec.js +14 -14
- package/lib/page/utils/server.d.ts +1 -1
- package/lib/page/utils/server.js +2 -2
- package/lib/page/utils/server.spec.js +13 -13
- package/package.json +1 -1
|
@@ -47,9 +47,9 @@ function createMockArgs(opts) {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
if (opts.
|
|
51
|
-
Object.entries(opts.
|
|
52
|
-
args.push(`--
|
|
50
|
+
if (opts.layoutProperties) {
|
|
51
|
+
Object.entries(opts.layoutProperties).forEach(([propertyName, value]) => {
|
|
52
|
+
args.push(`--properties=${propertyName}=${value}`);
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
return args;
|
|
@@ -65,7 +65,7 @@ describe('devCommand', () => {
|
|
|
65
65
|
process.argv = args;
|
|
66
66
|
yield program.parseAsync(args);
|
|
67
67
|
const opts = program.opts();
|
|
68
|
-
expect(opts.config).toEqual(
|
|
68
|
+
expect(opts.config).toEqual(`./${definitions_1.LAYOUT_MANIFEST_FILE}`);
|
|
69
69
|
expect(opts.port).toEqual('4040');
|
|
70
70
|
expect(opts.open).toEqual(true);
|
|
71
71
|
expect(opts.stylesheet).toBeUndefined();
|
|
@@ -121,19 +121,46 @@ describe('devCommand', () => {
|
|
|
121
121
|
zoneContent,
|
|
122
122
|
}));
|
|
123
123
|
}));
|
|
124
|
-
it('parses and passes layout
|
|
124
|
+
it('parses and passes layout properties to the server', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
125
|
const mockLayout = { name: 'Test Layout' };
|
|
126
126
|
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
127
|
-
const
|
|
127
|
+
const layoutProperties = {
|
|
128
128
|
sizing: 'large',
|
|
129
129
|
theme: 'dark',
|
|
130
130
|
};
|
|
131
131
|
const program = (0, dev_1.default)();
|
|
132
|
-
const args = createMockArgs({
|
|
132
|
+
const args = createMockArgs({ layoutProperties });
|
|
133
133
|
process.argv = args;
|
|
134
134
|
yield program.parseAsync(args);
|
|
135
135
|
expect(server_1.startDevServer).toHaveBeenCalledWith(expect.objectContaining({
|
|
136
|
-
|
|
136
|
+
layoutProperties,
|
|
137
|
+
}));
|
|
138
|
+
}));
|
|
139
|
+
it('normalizes boolean properties from strings to actual booleans', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
|
+
const mockLayout = {
|
|
141
|
+
name: 'Test Layout',
|
|
142
|
+
properties: {
|
|
143
|
+
showFooter: { type: 'boolean' },
|
|
144
|
+
showHeader: { type: 'boolean' },
|
|
145
|
+
theme: { type: 'string' },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
149
|
+
const layoutProperties = {
|
|
150
|
+
showFooter: 'true',
|
|
151
|
+
showHeader: 'false',
|
|
152
|
+
theme: 'dark',
|
|
153
|
+
};
|
|
154
|
+
const program = (0, dev_1.default)();
|
|
155
|
+
const args = createMockArgs({ layoutProperties });
|
|
156
|
+
process.argv = args;
|
|
157
|
+
yield program.parseAsync(args);
|
|
158
|
+
expect(server_1.startDevServer).toHaveBeenCalledWith(expect.objectContaining({
|
|
159
|
+
layoutProperties: {
|
|
160
|
+
showFooter: true,
|
|
161
|
+
showHeader: false,
|
|
162
|
+
theme: 'dark', // Remains string
|
|
163
|
+
},
|
|
137
164
|
}));
|
|
138
165
|
}));
|
|
139
166
|
it('handles failure to load layout definition', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -159,4 +186,86 @@ describe('devCommand', () => {
|
|
|
159
186
|
yield program.parseAsync(args);
|
|
160
187
|
expect(mockLoggerErrorFn).toHaveBeenCalledWith(expect.stringContaining('Server start failed'));
|
|
161
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
|
+
});
|
|
162
271
|
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateZoneConsistency = void 0;
|
|
3
|
+
exports.validateZoneConsistency = exports.validatePropertyConsistency = exports.validateLayoutFormat = void 0;
|
|
4
|
+
var validateLayoutFormat_1 = require("./validateLayoutFormat");
|
|
5
|
+
Object.defineProperty(exports, "validateLayoutFormat", { enumerable: true, get: function () { return validateLayoutFormat_1.validateLayoutFormat; } });
|
|
6
|
+
var property_consistency_1 = require("./property-consistency");
|
|
7
|
+
Object.defineProperty(exports, "validatePropertyConsistency", { enumerable: true, get: function () { return property_consistency_1.validatePropertyConsistency; } });
|
|
4
8
|
var zone_consistency_1 = require("./zone-consistency");
|
|
5
9
|
Object.defineProperty(exports, "validateZoneConsistency", { enumerable: true, get: function () { return zone_consistency_1.validateZoneConsistency; } });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LayoutDefinition } from '../../utils/definitions';
|
|
2
|
+
/**
|
|
3
|
+
* Validates that options/properties defined in layout definition match options/properties used in the Handlebars template
|
|
4
|
+
* @param layout The layout definition containing options/properties and template
|
|
5
|
+
* @returns Error message if validation fails, null if validation passes
|
|
6
|
+
*/
|
|
7
|
+
export declare function validatePropertyConsistency(layout: LayoutDefinition, filePath: string): string | null;
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.validatePropertyConsistency = void 0;
|
|
27
|
+
const path = __importStar(require("node:path"));
|
|
28
|
+
const definitions_1 = require("../../utils/definitions");
|
|
29
|
+
/**
|
|
30
|
+
* Validates that options/properties defined in layout definition match options/properties used in the Handlebars template
|
|
31
|
+
* @param layout The layout definition containing options/properties and template
|
|
32
|
+
* @returns Error message if validation fails, null if validation passes
|
|
33
|
+
*/
|
|
34
|
+
function validatePropertyConsistency(layout, filePath) {
|
|
35
|
+
var _a;
|
|
36
|
+
const layoutProperties = (_a = layout.properties) !== null && _a !== void 0 ? _a : {};
|
|
37
|
+
const template = layout.template;
|
|
38
|
+
// If no template or properties are provided, skip validation
|
|
39
|
+
if (!template) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const uniquePropertyKeys = new Set(Object.keys(layoutProperties));
|
|
43
|
+
if (uniquePropertyKeys.size !== Object.keys(layoutProperties).length) {
|
|
44
|
+
return 'Duplicate property keys found in layout definition';
|
|
45
|
+
}
|
|
46
|
+
// Extract property references from Handlebars template
|
|
47
|
+
// Look for patterns like {{properties.propertyName}}, {{#if properties.propertyName}} {{#ifEq 'value' properties.propertyName}}
|
|
48
|
+
const propertyPattern = /\{\{#*(?:[^\}]+\s)*properties\.(\w+)/g;
|
|
49
|
+
const optionPattern = /\{\{#*(?:[^\}]+\s)*options\.(\w+)/g;
|
|
50
|
+
// Keep track of properties/options used in the Handlebars template
|
|
51
|
+
const templateProperties = getTemplateMatches(template, propertyPattern);
|
|
52
|
+
const templateOptions = getTemplateMatches(template, optionPattern);
|
|
53
|
+
// Create an array of errors
|
|
54
|
+
const errors = [];
|
|
55
|
+
// Check that properties/options are being used correctly based on the layout definition file format
|
|
56
|
+
const fileName = path.basename(filePath);
|
|
57
|
+
const isNewFormat = fileName === definitions_1.LAYOUT_MANIFEST_FILE;
|
|
58
|
+
const templateSetToCheck = isNewFormat ? templateProperties : templateOptions;
|
|
59
|
+
// Check for incorrect option/property usage based on the layout definition file format
|
|
60
|
+
if (isNewFormat) {
|
|
61
|
+
if (templateOptions.size > 0) {
|
|
62
|
+
errors.push('Options are not supported in the Handlebars template for a layout defined with "manifest.json". Please use properties instead.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
if (templateProperties.size > 0) {
|
|
67
|
+
errors.push('Properties are only allowed in the Handlebars template for a layout defined with "manifest.json". Please use options instead.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Find options/properties used in template but not defined in layout definition
|
|
71
|
+
const undefinedTemplateProperties = Array.from(templateSetToCheck).filter(property => !Object.keys(layoutProperties).includes(property));
|
|
72
|
+
// Add the undefined options/properties to the errors
|
|
73
|
+
if (undefinedTemplateProperties.length > 0) {
|
|
74
|
+
errors.push(`${isNewFormat ? 'Properties' : 'Options'} used in template but not defined in layout definition: ${undefinedTemplateProperties.join(', ')}`);
|
|
75
|
+
}
|
|
76
|
+
// If there are errors, return the errors
|
|
77
|
+
if (errors.length > 0) {
|
|
78
|
+
return `${isNewFormat ? 'Property' : 'Option'} consistency validation failed:\n${errors
|
|
79
|
+
.map(err => ` - ${err}`)
|
|
80
|
+
.join('\n')}`;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
exports.validatePropertyConsistency = validatePropertyConsistency;
|
|
85
|
+
function getTemplateMatches(template, pattern) {
|
|
86
|
+
const matches = new Set();
|
|
87
|
+
let match;
|
|
88
|
+
while ((match = pattern.exec(template)) !== null) {
|
|
89
|
+
matches.add(match[1]);
|
|
90
|
+
}
|
|
91
|
+
return matches;
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const property_consistency_1 = require("./property-consistency");
|
|
4
|
+
describe('validatePropertyConsistency', () => {
|
|
5
|
+
const createMockLayout = (layout) => (Object.assign({ name: 'test-layout', displayName: 'Test Layout', description: 'A test layout', zones: [], template: '' }, layout));
|
|
6
|
+
describe('new format (manifest.json)', () => {
|
|
7
|
+
it('should return null for valid property consistency', () => {
|
|
8
|
+
const layout = createMockLayout({
|
|
9
|
+
properties: {
|
|
10
|
+
title: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
title: 'Title',
|
|
13
|
+
description: 'Page title',
|
|
14
|
+
},
|
|
15
|
+
showHeader: {
|
|
16
|
+
type: 'boolean',
|
|
17
|
+
title: 'Show Header',
|
|
18
|
+
description: 'Whether to show header',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
template: '<div>{{properties.title}}</div><div>{{#if properties.showHeader}}Header{{/if}}</div>',
|
|
22
|
+
});
|
|
23
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
24
|
+
expect(result).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
it('should return null when no template is provided', () => {
|
|
27
|
+
const layout = createMockLayout({
|
|
28
|
+
properties: {
|
|
29
|
+
title: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
title: 'Title',
|
|
32
|
+
description: 'Page title',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
37
|
+
expect(result).toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it('should return null when layout has empty properties object', () => {
|
|
40
|
+
const layout = createMockLayout({
|
|
41
|
+
properties: {},
|
|
42
|
+
template: '<div>Static content only</div>',
|
|
43
|
+
});
|
|
44
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
45
|
+
expect(result).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
it('should return null when layout has undefined properties', () => {
|
|
48
|
+
const layout = createMockLayout({
|
|
49
|
+
template: '<div>Static content only</div>',
|
|
50
|
+
});
|
|
51
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
52
|
+
expect(result).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
it('should allow properties that are defined in the layout, but not used in template', () => {
|
|
55
|
+
const layout = createMockLayout({
|
|
56
|
+
properties: {
|
|
57
|
+
title: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
title: 'Title',
|
|
60
|
+
description: 'Page title',
|
|
61
|
+
},
|
|
62
|
+
unused: {
|
|
63
|
+
type: 'string',
|
|
64
|
+
title: 'Unused',
|
|
65
|
+
description: 'Unused property',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
template: '<div>{{properties.title}}</div>',
|
|
69
|
+
});
|
|
70
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
71
|
+
expect(result).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
it('should detect properties used in template but not defined in layout', () => {
|
|
74
|
+
const layout = createMockLayout({
|
|
75
|
+
properties: {
|
|
76
|
+
title: {
|
|
77
|
+
type: 'string',
|
|
78
|
+
title: 'Title',
|
|
79
|
+
description: 'Page title',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
template: '<div>{{properties.title}}</div><div>{{properties.undefined}}</div>',
|
|
83
|
+
});
|
|
84
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
85
|
+
expect(result).toContain('Property consistency validation failed');
|
|
86
|
+
expect(result).toContain('Properties used in template but not defined in layout definition: undefined');
|
|
87
|
+
});
|
|
88
|
+
it('should handle both unused and undefined properties', () => {
|
|
89
|
+
const layout = createMockLayout({
|
|
90
|
+
properties: {
|
|
91
|
+
title: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
title: 'Title',
|
|
94
|
+
description: 'Page title',
|
|
95
|
+
},
|
|
96
|
+
unused: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
title: 'Unused',
|
|
99
|
+
description: 'Unused property',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
template: '<div>{{properties.title}}</div><div>{{properties.undefined}}</div>',
|
|
103
|
+
});
|
|
104
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
105
|
+
expect(result).toContain('Property consistency validation failed');
|
|
106
|
+
expect(result).toContain('Properties used in template but not defined in layout definition: undefined');
|
|
107
|
+
});
|
|
108
|
+
it('should reject options usage in manifest.json format', () => {
|
|
109
|
+
const layout = createMockLayout({
|
|
110
|
+
properties: {
|
|
111
|
+
title: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
title: 'Title',
|
|
114
|
+
description: 'Page title',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
template: '<div>{{properties.title}}</div><div>{{options.invalid}}</div>',
|
|
118
|
+
});
|
|
119
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
120
|
+
expect(result).toContain('Property consistency validation failed');
|
|
121
|
+
expect(result).toContain('Options are not supported in the Handlebars template for a layout defined with "manifest.json". Please use properties instead.');
|
|
122
|
+
});
|
|
123
|
+
it('should handle complex Handlebars patterns', () => {
|
|
124
|
+
const layout = createMockLayout({
|
|
125
|
+
properties: {
|
|
126
|
+
title: {
|
|
127
|
+
type: 'string',
|
|
128
|
+
title: 'Title',
|
|
129
|
+
description: 'Page title',
|
|
130
|
+
},
|
|
131
|
+
showHeader: {
|
|
132
|
+
type: 'boolean',
|
|
133
|
+
title: 'Show Header',
|
|
134
|
+
description: 'Whether to show header',
|
|
135
|
+
},
|
|
136
|
+
theme: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
title: 'Theme',
|
|
139
|
+
description: 'Page theme',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
template: `
|
|
143
|
+
<div>{{properties.title}}</div>
|
|
144
|
+
<div>{{toLowerCase properties.title}}</div>
|
|
145
|
+
<div>
|
|
146
|
+
{{#if properties.showHeader}}
|
|
147
|
+
<header>Header</header>
|
|
148
|
+
{{/if}}
|
|
149
|
+
{{#ifEq "dark" properties.theme}}
|
|
150
|
+
<div class="dark">Dark theme</div>
|
|
151
|
+
{{/ifEq}}
|
|
152
|
+
</div>
|
|
153
|
+
`,
|
|
154
|
+
});
|
|
155
|
+
const manifestResult = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
156
|
+
expect(manifestResult).toBeNull();
|
|
157
|
+
const pageLayoutResult = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
158
|
+
expect(pageLayoutResult).toContain('Option consistency validation failed');
|
|
159
|
+
expect(pageLayoutResult).toContain('Properties are only allowed in the Handlebars template for a layout defined with "manifest.json". Please use options instead.');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('old format (page-layout.yaml)', () => {
|
|
163
|
+
it('should return null for valid option consistency', () => {
|
|
164
|
+
const layout = createMockLayout({
|
|
165
|
+
properties: {
|
|
166
|
+
title: {
|
|
167
|
+
type: 'string',
|
|
168
|
+
title: 'Title',
|
|
169
|
+
description: 'Page title',
|
|
170
|
+
},
|
|
171
|
+
showHeader: {
|
|
172
|
+
type: 'boolean',
|
|
173
|
+
title: 'Show Header',
|
|
174
|
+
description: 'Whether to show header',
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
template: '<div>{{options.title}}</div><div>{{#if options.showHeader}}Header{{/if}}</div>',
|
|
178
|
+
});
|
|
179
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
180
|
+
expect(result).toBeNull();
|
|
181
|
+
});
|
|
182
|
+
it('should allow options that are defined in the layout, but not used in template', () => {
|
|
183
|
+
const layout = createMockLayout({
|
|
184
|
+
properties: {
|
|
185
|
+
title: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
title: 'Title',
|
|
188
|
+
description: 'Page title',
|
|
189
|
+
},
|
|
190
|
+
unused: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
title: 'Unused',
|
|
193
|
+
description: 'Unused option',
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
template: '<div>{{options.title}}</div>',
|
|
197
|
+
});
|
|
198
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
199
|
+
expect(result).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
it('should detect options used in template but not defined in layout', () => {
|
|
202
|
+
const layout = createMockLayout({
|
|
203
|
+
properties: {
|
|
204
|
+
title: {
|
|
205
|
+
type: 'string',
|
|
206
|
+
title: 'Title',
|
|
207
|
+
description: 'Page title',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
template: '<div>{{options.title}}</div><div>{{options.undefined}}</div>',
|
|
211
|
+
});
|
|
212
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
213
|
+
expect(result).toContain('Option consistency validation failed');
|
|
214
|
+
expect(result).toContain('Options used in template but not defined in layout definition: undefined');
|
|
215
|
+
});
|
|
216
|
+
it('should reject properties usage in old format', () => {
|
|
217
|
+
const layout = createMockLayout({
|
|
218
|
+
properties: {
|
|
219
|
+
title: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
title: 'Title',
|
|
222
|
+
description: 'Page title',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
template: '<div>{{options.title}}</div><div>{{properties.invalid}}</div>',
|
|
226
|
+
});
|
|
227
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
228
|
+
expect(result).toContain('Option consistency validation failed');
|
|
229
|
+
expect(result).toContain('Properties are only allowed in the Handlebars template for a layout defined with "manifest.json". Please use options instead.');
|
|
230
|
+
});
|
|
231
|
+
it('should handle complex Handlebars patterns', () => {
|
|
232
|
+
const layout = createMockLayout({
|
|
233
|
+
properties: {
|
|
234
|
+
title: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
title: 'Title',
|
|
237
|
+
description: 'Page title',
|
|
238
|
+
},
|
|
239
|
+
showHeader: {
|
|
240
|
+
type: 'boolean',
|
|
241
|
+
title: 'Show Header',
|
|
242
|
+
description: 'Whether to show header',
|
|
243
|
+
},
|
|
244
|
+
theme: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
title: 'Theme',
|
|
247
|
+
description: 'Page theme',
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
template: `
|
|
251
|
+
<div>{{options.title}}</div>
|
|
252
|
+
<div>{{toLowerCase options.title}}</div>
|
|
253
|
+
<div>
|
|
254
|
+
{{#if options.showHeader}}
|
|
255
|
+
<header>Header</header>
|
|
256
|
+
{{/if}}
|
|
257
|
+
{{#ifEq "dark" options.theme}}
|
|
258
|
+
<div class="dark">Dark theme</div>
|
|
259
|
+
{{/ifEq}}
|
|
260
|
+
</div>
|
|
261
|
+
`,
|
|
262
|
+
});
|
|
263
|
+
const pageLayoutResult = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/page-layout.yaml');
|
|
264
|
+
expect(pageLayoutResult).toBeNull();
|
|
265
|
+
const manifestResult = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
266
|
+
expect(manifestResult).toContain('Property consistency validation failed');
|
|
267
|
+
expect(manifestResult).toContain('Options are not supported in the Handlebars template for a layout defined with "manifest.json". Please use properties instead.');
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
describe('edge cases', () => {
|
|
271
|
+
it('should handle multiple property references in same template', () => {
|
|
272
|
+
const layout = createMockLayout({
|
|
273
|
+
properties: {
|
|
274
|
+
title: {
|
|
275
|
+
type: 'string',
|
|
276
|
+
title: 'Title',
|
|
277
|
+
description: 'Page title',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
template: '<div>{{properties.title}}</div><div>{{properties.title}}</div><div>{{#if properties.title}}Yes{{/if}}</div>',
|
|
281
|
+
});
|
|
282
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
283
|
+
expect(result).toBeNull();
|
|
284
|
+
});
|
|
285
|
+
it('should handle property names with different casing', () => {
|
|
286
|
+
const layout = createMockLayout({
|
|
287
|
+
properties: {
|
|
288
|
+
title: {
|
|
289
|
+
type: 'string',
|
|
290
|
+
title: 'Title',
|
|
291
|
+
description: 'Page title',
|
|
292
|
+
},
|
|
293
|
+
Title: {
|
|
294
|
+
type: 'string',
|
|
295
|
+
title: 'Title Capitalized',
|
|
296
|
+
description: 'Title with capital T',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
template: '<div>{{properties.title}}</div><div>{{properties.Title}}</div>',
|
|
300
|
+
});
|
|
301
|
+
const result = (0, property_consistency_1.validatePropertyConsistency)(layout, '/path/to/manifest.json');
|
|
302
|
+
expect(result).toBeNull();
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
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.validateLayoutFormat = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const definitions_1 = require("../../utils/definitions");
|
|
9
|
+
function validateLayoutFormat(logger, layoutFile) {
|
|
10
|
+
// Warn that the default layout file has been changed
|
|
11
|
+
if (!layoutFile) {
|
|
12
|
+
logger.warn(`⚠️ DEFAULT CONFIG FILE: "${definitions_1.LAYOUT_MANIFEST_FILE}" has replaced "page-layout.yaml" as the default config file. ` +
|
|
13
|
+
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
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');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
// Warn that any file other than manifest.json is deprecated
|
|
19
|
+
const fileName = node_path_1.default.basename(layoutFile);
|
|
20
|
+
if (fileName !== definitions_1.LAYOUT_MANIFEST_FILE) {
|
|
21
|
+
logger.warn(`⚠️ DEPRECATED: The file name "${fileName}" is deprecated. ` +
|
|
22
|
+
`Please migrate to "${definitions_1.LAYOUT_MANIFEST_FILE}" using the new layout definition format. ` +
|
|
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');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.validateLayoutFormat = validateLayoutFormat;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|