@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 = { name: 'Test Layout' };
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 = { name: 'Test Layout' };
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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/dxp-cli-next",
3
- "version": "5.29.1",
3
+ "version": "5.30.0-develop.1",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
6
6
  },