@squiz/dxp-cli-next 5.29.1 → 5.30.0-develop.1
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.
|
@@ -51,6 +51,11 @@ const createDeployCommand = () => {
|
|
|
51
51
|
try {
|
|
52
52
|
const layout = yield (0, definitions_1.loadLayoutDefinition)(layoutFile);
|
|
53
53
|
if (layout !== undefined) {
|
|
54
|
+
// Pre-deployment validation: check zones match between YAML and template
|
|
55
|
+
const zoneValidationError = validateZoneConsistency(layout);
|
|
56
|
+
if (zoneValidationError) {
|
|
57
|
+
throw new Error(zoneValidationError);
|
|
58
|
+
}
|
|
54
59
|
const response = yield uploadLayout(apiService.client, layout, contentServiceUrl, options.dryRun);
|
|
55
60
|
if (!options.dryRun) {
|
|
56
61
|
exports.logger.info(`Layout "${layout.name}" version ${response.data.version} deployed successfully.`);
|
|
@@ -106,3 +111,48 @@ function maybeGetApplicationConfig() {
|
|
|
106
111
|
catch (_a) { }
|
|
107
112
|
});
|
|
108
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Validates that zones defined in YAML match zones used in the Handlebars template
|
|
116
|
+
* @param layout The layout definition containing zones and template
|
|
117
|
+
* @returns Error message if validation fails, null if validation passes
|
|
118
|
+
*/
|
|
119
|
+
function validateZoneConsistency(layout) {
|
|
120
|
+
const yamlZones = Object.keys(layout.zones || {});
|
|
121
|
+
const template = layout.template;
|
|
122
|
+
// If no template is provided, skip validation
|
|
123
|
+
if (!template) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
// Extract zone references from Handlebars template
|
|
127
|
+
// Look for patterns like {{#each zones.zoneName}}, {{zones.zoneName}}, {{#if zones.zoneName}}
|
|
128
|
+
const zonePattern = /\{\{(?:#(?:each|if)\s+)?zones\.(\w+)/g;
|
|
129
|
+
const templateZones = new Set();
|
|
130
|
+
let match;
|
|
131
|
+
// Loop through the template and extract the zone names
|
|
132
|
+
while ((match = zonePattern.exec(template)) !== null) {
|
|
133
|
+
templateZones.add(match[1]);
|
|
134
|
+
}
|
|
135
|
+
// Convert the set to an array
|
|
136
|
+
const templateZoneArray = Array.from(templateZones);
|
|
137
|
+
// Find zones defined in YAML but not used in template
|
|
138
|
+
const unusedYamlZones = yamlZones.filter(zone => !templateZones.has(zone));
|
|
139
|
+
// Find zones used in template but not defined in YAML
|
|
140
|
+
const undefinedTemplateZones = templateZoneArray.filter(zone => !yamlZones.includes(zone));
|
|
141
|
+
// Create an array of errors
|
|
142
|
+
const errors = [];
|
|
143
|
+
// Add the unused zones to the errors
|
|
144
|
+
if (unusedYamlZones.length > 0) {
|
|
145
|
+
errors.push(`Zones defined in YAML but not used in template: ${unusedYamlZones.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
// Add the undefined zones to the errors
|
|
148
|
+
if (undefinedTemplateZones.length > 0) {
|
|
149
|
+
errors.push(`Zones used in template but not defined in YAML: ${undefinedTemplateZones.join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
// If there are errors, return the errors
|
|
152
|
+
if (errors.length > 0) {
|
|
153
|
+
return `Zone consistency validation failed:\n${errors
|
|
154
|
+
.map(err => ` - ${err}`)
|
|
155
|
+
.join('\n')}`;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
@@ -92,7 +92,13 @@ describe('deployCommand', () => {
|
|
|
92
92
|
it('deploys a layout successfully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
93
|
const file = './src/__tests__/layouts/page-layout.yaml';
|
|
94
94
|
const dxpBaseUrl = 'http://dxp-base-url.com';
|
|
95
|
-
const mockLayout = {
|
|
95
|
+
const mockLayout = {
|
|
96
|
+
name: 'Test Layout',
|
|
97
|
+
zones: {
|
|
98
|
+
content: { displayName: 'Content', description: 'Main content' },
|
|
99
|
+
},
|
|
100
|
+
template: '<div>{{zones.content}}</div>',
|
|
101
|
+
};
|
|
96
102
|
const mockResponse = { name: 'Test Layout', version: '12345' };
|
|
97
103
|
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
98
104
|
(0, nock_1.default)(dxpBaseUrl + '/__dxp/service/components-content')
|
|
@@ -107,7 +113,13 @@ describe('deployCommand', () => {
|
|
|
107
113
|
const file = './src/__tests__/layout.yaml';
|
|
108
114
|
const dxpBaseUrl = 'http://dxp-base-url.com';
|
|
109
115
|
const dryRun = true;
|
|
110
|
-
const mockLayout = {
|
|
116
|
+
const mockLayout = {
|
|
117
|
+
name: 'Test Layout',
|
|
118
|
+
zones: {
|
|
119
|
+
content: { displayName: 'Content', description: 'Main content' },
|
|
120
|
+
},
|
|
121
|
+
template: '<div>{{zones.content}}</div>',
|
|
122
|
+
};
|
|
111
123
|
const mockResponse = { name: 'Test Layout', version: '12345' };
|
|
112
124
|
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
113
125
|
(0, nock_1.default)(dxpBaseUrl + '/__dxp/service/components-content')
|
|
@@ -159,4 +171,40 @@ describe('deployCommand', () => {
|
|
|
159
171
|
yield program.parseAsync(createMockArgs({ contentServiceUrl }));
|
|
160
172
|
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('An unknown error occurred'));
|
|
161
173
|
}));
|
|
174
|
+
describe('zone consistency validation', () => {
|
|
175
|
+
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* () {
|
|
176
|
+
const file = './src/__tests__/layout.yaml';
|
|
177
|
+
const dxpBaseUrl = 'http://dxp-base-url.com';
|
|
178
|
+
// Mock layout with zones that don't match the template
|
|
179
|
+
const mockLayout = {
|
|
180
|
+
name: 'Test Layout',
|
|
181
|
+
zones: {
|
|
182
|
+
col1: { displayName: 'Column 1', description: 'The first column' },
|
|
183
|
+
},
|
|
184
|
+
template: '{{zones.col1}} {{zones.col2}}', // col2 is used but not defined in zones
|
|
185
|
+
};
|
|
186
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
187
|
+
const program = (0, deploy_1.default)();
|
|
188
|
+
errorSpy = jest.spyOn(program, 'error').mockImplementation();
|
|
189
|
+
yield program.parseAsync(createMockArgs({ config: file, dxpBaseUrl }));
|
|
190
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringMatching(/Zone consistency validation failed[\s\S]*Zones used in template but not defined in YAML: col2/));
|
|
191
|
+
}));
|
|
192
|
+
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* () {
|
|
193
|
+
const file = './src/__tests__/layout.yaml';
|
|
194
|
+
const dxpBaseUrl = 'http://dxp-base-url.com';
|
|
195
|
+
const mockLayout = {
|
|
196
|
+
name: 'Test Layout',
|
|
197
|
+
zones: {
|
|
198
|
+
col1: { displayName: 'Column 1', description: 'The first column' },
|
|
199
|
+
col2: { displayName: 'Column 2', description: 'The second column' }, // col2 is defined but not used in the template
|
|
200
|
+
},
|
|
201
|
+
template: '{{zones.col1}}',
|
|
202
|
+
};
|
|
203
|
+
definitions_1.loadLayoutDefinition.mockResolvedValue(mockLayout);
|
|
204
|
+
const program = (0, deploy_1.default)();
|
|
205
|
+
errorSpy = jest.spyOn(program, 'error').mockImplementation();
|
|
206
|
+
yield program.parseAsync(createMockArgs({ config: file, dxpBaseUrl }));
|
|
207
|
+
expect(errorSpy).toHaveBeenCalledWith(expect.stringMatching(/Zone consistency validation failed[\s\S]*Zones defined in YAML but not used in template: col2/));
|
|
208
|
+
}));
|
|
209
|
+
});
|
|
162
210
|
});
|