@squiz/dxp-cli-next 5.30.0-develop.2 → 5.30.0-develop.3

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.
@@ -19,6 +19,7 @@ const ApiService_1 = require("../../../ApiService");
19
19
  const ApplicationConfig_1 = require("../../../ApplicationConfig");
20
20
  const constants_1 = require("../../../constants");
21
21
  const definitions_1 = require("../../utils/definitions");
22
+ const validation_1 = require("../validation");
22
23
  const dx_logger_lib_1 = require("@squiz/dx-logger-lib");
23
24
  exports.logger = (0, dx_logger_lib_1.getLogger)({
24
25
  name: 'upload-layout',
@@ -52,7 +53,7 @@ const createDeployCommand = () => {
52
53
  const layout = yield (0, definitions_1.loadLayoutDefinition)(layoutFile);
53
54
  if (layout !== undefined) {
54
55
  // Pre-deployment validation: check zones match between YAML and template
55
- const zoneValidationError = validateZoneConsistency(layout);
56
+ const zoneValidationError = (0, validation_1.validateZoneConsistency)(layout);
56
57
  if (zoneValidationError) {
57
58
  throw new Error(zoneValidationError);
58
59
  }
@@ -119,48 +120,3 @@ function maybeGetApplicationConfig() {
119
120
  catch (_a) { }
120
121
  });
121
122
  }
122
- /**
123
- * Validates that zones defined in YAML match zones used in the Handlebars template
124
- * @param layout The layout definition containing zones and template
125
- * @returns Error message if validation fails, null if validation passes
126
- */
127
- function validateZoneConsistency(layout) {
128
- const yamlZones = Object.keys(layout.zones || {});
129
- const template = layout.template;
130
- // If no template is provided, skip validation
131
- if (!template) {
132
- return null;
133
- }
134
- // Extract zone references from Handlebars template
135
- // Look for patterns like {{#each zones.zoneName}}, {{zones.zoneName}}, {{#if zones.zoneName}}
136
- const zonePattern = /\{\{(?:#(?:each|if)\s+)?zones\.(\w+)/g;
137
- const templateZones = new Set();
138
- let match;
139
- // Loop through the template and extract the zone names
140
- while ((match = zonePattern.exec(template)) !== null) {
141
- templateZones.add(match[1]);
142
- }
143
- // Convert the set to an array
144
- const templateZoneArray = Array.from(templateZones);
145
- // Find zones defined in YAML but not used in template
146
- const unusedYamlZones = yamlZones.filter(zone => !templateZones.has(zone));
147
- // Find zones used in template but not defined in YAML
148
- const undefinedTemplateZones = templateZoneArray.filter(zone => !yamlZones.includes(zone));
149
- // Create an array of errors
150
- const errors = [];
151
- // Add the unused zones to the errors
152
- if (unusedYamlZones.length > 0) {
153
- errors.push(`Zones defined in YAML but not used in template: ${unusedYamlZones.join(', ')}`);
154
- }
155
- // Add the undefined zones to the errors
156
- if (undefinedTemplateZones.length > 0) {
157
- errors.push(`Zones used in template but not defined in YAML: ${undefinedTemplateZones.join(', ')}`);
158
- }
159
- // If there are errors, return the errors
160
- if (errors.length > 0) {
161
- return `Zone consistency validation failed:\n${errors
162
- .map(err => ` - ${err}`)
163
- .join('\n')}`;
164
- }
165
- return null;
166
- }
@@ -0,0 +1 @@
1
+ export { validateZoneConsistency } from './zone-consistency';
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateZoneConsistency = void 0;
4
+ var zone_consistency_1 = require("./zone-consistency");
5
+ 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 zones defined in YAML match zones used in the Handlebars template
4
+ * @param layout The layout definition containing zones and template
5
+ * @returns Error message if validation fails, null if validation passes
6
+ */
7
+ export declare function validateZoneConsistency(layout: LayoutDefinition): string | null;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateZoneConsistency = void 0;
4
+ /**
5
+ * Validates that zones defined in YAML match zones used in the Handlebars template
6
+ * @param layout The layout definition containing zones and template
7
+ * @returns Error message if validation fails, null if validation passes
8
+ */
9
+ function validateZoneConsistency(layout) {
10
+ const yamlZones = Object.keys(layout.zones || {});
11
+ const template = layout.template;
12
+ // If no template is provided, skip validation
13
+ if (!template) {
14
+ return null;
15
+ }
16
+ // Extract zone references from Handlebars template
17
+ // Look for patterns like {{#each zones.zoneName}}, {{zones.zoneName}}, {{#if zones.zoneName}}
18
+ const zonePattern = /\{\{(?:#(?:each|if)\s+)?zones\.(\w+)/g;
19
+ const templateZones = new Set();
20
+ let match;
21
+ // Loop through the template and extract the zone names
22
+ while ((match = zonePattern.exec(template)) !== null) {
23
+ templateZones.add(match[1]);
24
+ }
25
+ // Convert the set to an array
26
+ const templateZoneArray = Array.from(templateZones);
27
+ // Find zones defined in YAML but not used in template
28
+ const unusedYamlZones = yamlZones.filter(zone => !templateZones.has(zone));
29
+ // Find zones used in template but not defined in YAML
30
+ const undefinedTemplateZones = templateZoneArray.filter(zone => !yamlZones.includes(zone));
31
+ // Create an array of errors
32
+ const errors = [];
33
+ // Add the unused zones to the errors
34
+ if (unusedYamlZones.length > 0) {
35
+ errors.push(`Zones defined in YAML but not used in template: ${unusedYamlZones.join(', ')}`);
36
+ }
37
+ // Add the undefined zones to the errors
38
+ if (undefinedTemplateZones.length > 0) {
39
+ errors.push(`Zones used in template but not defined in YAML: ${undefinedTemplateZones.join(', ')}`);
40
+ }
41
+ // If there are errors, return the errors
42
+ if (errors.length > 0) {
43
+ return `Zone consistency validation failed:\n${errors
44
+ .map(err => ` - ${err}`)
45
+ .join('\n')}`;
46
+ }
47
+ return null;
48
+ }
49
+ exports.validateZoneConsistency = validateZoneConsistency;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zone_consistency_1 = require("./zone-consistency");
4
+ describe('validateZoneConsistency', () => {
5
+ const createMockLayout = (zones, template) => ({
6
+ name: 'test-layout',
7
+ displayName: 'Test Layout',
8
+ description: 'A test layout',
9
+ zones,
10
+ template,
11
+ });
12
+ it('should return null for valid zone consistency', () => {
13
+ const layout = createMockLayout({
14
+ header: { displayName: 'Header', description: 'Header zone' },
15
+ content: { displayName: 'Content', description: 'Content zone' },
16
+ }, '<div>{{zones.header}}</div><div>{{zones.content}}</div>');
17
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
18
+ expect(result).toBeNull();
19
+ });
20
+ it('should return null when no template is provided', () => {
21
+ const layout = createMockLayout({
22
+ header: { displayName: 'Header', description: 'Header zone' },
23
+ }, '');
24
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
25
+ expect(result).toBeNull();
26
+ });
27
+ it('should detect zones defined in YAML but not used in template', () => {
28
+ const layout = createMockLayout({
29
+ header: { displayName: 'Header', description: 'Header zone' },
30
+ sidebar: { displayName: 'Sidebar', description: 'Sidebar zone' },
31
+ }, '<div>{{zones.header}}</div>');
32
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
33
+ expect(result).toContain('Zone consistency validation failed');
34
+ expect(result).toContain('Zones defined in YAML but not used in template: sidebar');
35
+ });
36
+ it('should detect zones used in template but not defined in YAML', () => {
37
+ const layout = createMockLayout({
38
+ header: { displayName: 'Header', description: 'Header zone' },
39
+ }, '<div>{{zones.header}}</div><div>{{zones.footer}}</div>');
40
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
41
+ expect(result).toContain('Zone consistency validation failed');
42
+ expect(result).toContain('Zones used in template but not defined in YAML: footer');
43
+ });
44
+ it('should detect both unused and undefined zones', () => {
45
+ const layout = createMockLayout({
46
+ header: { displayName: 'Header', description: 'Header zone' },
47
+ sidebar: { displayName: 'Sidebar', description: 'Sidebar zone' },
48
+ }, '<div>{{zones.header}}</div><div>{{zones.footer}}</div>');
49
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
50
+ expect(result).toContain('Zone consistency validation failed');
51
+ expect(result).toContain('Zones defined in YAML but not used in template: sidebar');
52
+ expect(result).toContain('Zones used in template but not defined in YAML: footer');
53
+ });
54
+ it('should handle Handlebars each loops', () => {
55
+ const layout = createMockLayout({
56
+ items: { displayName: 'Items', description: 'Items zone' },
57
+ }, '<div>{{#each zones.items}}<p>{{this}}</p>{{/each}}</div>');
58
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
59
+ expect(result).toBeNull();
60
+ });
61
+ it('should handle Handlebars if conditions', () => {
62
+ const layout = createMockLayout({
63
+ optional: { displayName: 'Optional', description: 'Optional zone' },
64
+ }, '<div>{{#if zones.optional}}{{zones.optional}}{{/if}}</div>');
65
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
66
+ expect(result).toBeNull();
67
+ });
68
+ it('should handle complex Handlebars patterns', () => {
69
+ const layout = createMockLayout({
70
+ header: { displayName: 'Header', description: 'Header zone' },
71
+ content: { displayName: 'Content', description: 'Content zone' },
72
+ sidebar: { displayName: 'Sidebar', description: 'Sidebar zone' },
73
+ }, `
74
+ <div>{{zones.header}}</div>
75
+ <div>
76
+ {{#if zones.sidebar}}
77
+ <aside>{{zones.sidebar}}</aside>
78
+ {{/if}}
79
+ <main>{{zones.content}}</main>
80
+ </div>
81
+ `);
82
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
83
+ expect(result).toBeNull();
84
+ });
85
+ it('should handle empty zones object', () => {
86
+ const layout = createMockLayout({}, '<div>Static content only</div>');
87
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
88
+ expect(result).toBeNull();
89
+ });
90
+ it('should handle undefined zones property', () => {
91
+ const layout = {
92
+ name: 'test-layout',
93
+ displayName: 'Test Layout',
94
+ description: 'A test layout',
95
+ zones: undefined,
96
+ template: '<div>Static content only</div>',
97
+ };
98
+ const result = (0, zone_consistency_1.validateZoneConsistency)(layout);
99
+ expect(result).toBeNull();
100
+ });
101
+ });
@@ -0,0 +1,11 @@
1
+ /*!
2
+ * @license
3
+ * Copyright (c) 2025, Squiz Australia Pty Ltd.
4
+ * All rights reserved.
5
+ */
6
+ /**
7
+ * Validates a template file and returns detailed results
8
+ * @param content - The template to validate
9
+ * @returns An array of errors
10
+ */
11
+ export declare function validateTemplateFile(content: string): string[];
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /*!
3
+ * @license
4
+ * Copyright (c) 2025, Squiz Australia Pty Ltd.
5
+ * All rights reserved.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.validateTemplateFile = void 0;
9
+ /**
10
+ * Validates a template file and returns detailed results
11
+ * @param content - The template to validate
12
+ * @returns An array of errors
13
+ */
14
+ function validateTemplateFile(content) {
15
+ const errors = [];
16
+ // Check for top-level HTML structure tags that should not be in body content
17
+ const topLevelTags = [
18
+ { name: 'html', regex: /<html(\s|>)/gi },
19
+ { name: 'head', regex: /<head(\s|>)/gi },
20
+ { name: 'body', regex: /<body(\s|>)/gi },
21
+ { name: 'doctype', regex: /<!DOCTYPE[^>]*>/gi },
22
+ ];
23
+ for (const tag of topLevelTags) {
24
+ if (tag.regex.test(content)) {
25
+ errors.push(`Template should not contain top-level HTML structure tag: <${tag.name}>. Templates should only contain body content.`);
26
+ }
27
+ }
28
+ return errors;
29
+ }
30
+ exports.validateTemplateFile = validateTemplateFile;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const validation_1 = require("./validation");
4
+ describe('Handlebars Template Validation', () => {
5
+ describe('validateTemplateFile', () => {
6
+ it('should validate an empty template', () => {
7
+ const template = '';
8
+ const errors = (0, validation_1.validateTemplateFile)(template);
9
+ expect(errors).toHaveLength(0);
10
+ });
11
+ it('should validate a simple valid template', () => {
12
+ const template = '<div>{{zones.content}}</div>';
13
+ const errors = (0, validation_1.validateTemplateFile)(template);
14
+ expect(errors).toHaveLength(0);
15
+ });
16
+ it('should detect top-level HTML tags', () => {
17
+ const template = '<html><body><div>{{zones.content}}</div></body></html>';
18
+ const errors = (0, validation_1.validateTemplateFile)(template);
19
+ expect(errors.length).toBeGreaterThan(0);
20
+ expect(errors).toEqual(expect.arrayContaining([
21
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <html>'),
22
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <body>'),
23
+ ]));
24
+ });
25
+ it('should detect DOCTYPE declarations', () => {
26
+ const template = '<!DOCTYPE html><div>{{zones.content}}</div>';
27
+ const errors = (0, validation_1.validateTemplateFile)(template);
28
+ expect(errors.length).toBeGreaterThan(0);
29
+ expect(errors).toEqual(expect.arrayContaining([
30
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <doctype>'),
31
+ ]));
32
+ });
33
+ it('should detect head tags', () => {
34
+ const template = '<head><title>Test</title></head><div>{{zones.content}}</div>';
35
+ const errors = (0, validation_1.validateTemplateFile)(template);
36
+ expect(errors.length).toBeGreaterThan(0);
37
+ expect(errors).toEqual(expect.arrayContaining([
38
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <head>'),
39
+ ]));
40
+ });
41
+ it('should allow body content elements', () => {
42
+ const template = `
43
+ <div class="layout">
44
+ <header>{{zones.header}}</header>
45
+ <main>{{zones.content}}</main>
46
+ <aside>{{zones.sidebar}}</aside>
47
+ <footer>{{zones.footer}}</footer>
48
+ </div>
49
+ `;
50
+ const errors = (0, validation_1.validateTemplateFile)(template);
51
+ expect(errors).toHaveLength(0);
52
+ });
53
+ it('should allow complex Handlebars expressions', () => {
54
+ const template = `
55
+ <div class="layout">
56
+ {{#if zones.header}}
57
+ <header>{{zones.header}}</header>
58
+ {{/if}}
59
+ <main>{{zones.content}}</main>
60
+ {{#each zones.sidebar}}
61
+ <aside>{{this}}</aside>
62
+ {{/each}}
63
+ </div>
64
+ `;
65
+ const errors = (0, validation_1.validateTemplateFile)(template);
66
+ expect(errors).toHaveLength(0);
67
+ });
68
+ it('should allow all valid HTML body elements', () => {
69
+ const template = `
70
+ <div>{{zones.content}}</div>
71
+ <p>{{zones.text}}</p>
72
+ <span>{{zones.inline}}</span>
73
+ <section>{{zones.section}}</section>
74
+ <article>{{zones.article}}</article>
75
+ <nav>{{zones.navigation}}</nav>
76
+ <header>{{zones.header}}</header>
77
+ <footer>{{zones.footer}}</footer>
78
+ <aside>{{zones.sidebar}}</aside>
79
+ <main>{{zones.main}}</main>
80
+ <h1>{{zones.title}}</h1>
81
+ <img src="{{zones.image}}" alt="{{zones.alt}}">
82
+ <a href="{{zones.link}}">{{zones.linkText}}</a>
83
+ <ul>{{#each zones.list}}<li>{{this}}</li>{{/each}}</ul>
84
+ `;
85
+ const errors = (0, validation_1.validateTemplateFile)(template);
86
+ expect(errors).toHaveLength(0);
87
+ });
88
+ });
89
+ });
@@ -1,3 +1,8 @@
1
+ /*!
2
+ * @license
3
+ * Copyright (c) 2025, Squiz Australia Pty Ltd.
4
+ * All rights reserved.
5
+ */
1
6
  import { z } from 'zod';
2
7
  export declare function loadLayoutDefinition(layoutFile: string): Promise<LayoutDefinition>;
3
8
  export declare function loadLayoutFromFile(layoutFile: string): Promise<InputLayoutDefinition>;
@@ -1,4 +1,9 @@
1
1
  "use strict";
2
+ /*!
3
+ * @license
4
+ * Copyright (c) 2025, Squiz Australia Pty Ltd.
5
+ * All rights reserved.
6
+ */
2
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
8
  if (k2 === undefined) k2 = k;
4
9
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -44,10 +49,13 @@ var __rest = (this && this.__rest) || function (s, e) {
44
49
  };
45
50
  Object.defineProperty(exports, "__esModule", { value: true });
46
51
  exports.LayoutDefinition = exports.InputLayoutDefinition = exports.BaseLayoutDefinition = exports.loadLayoutFromFile = exports.loadLayoutDefinition = void 0;
52
+ // External
47
53
  const fs = __importStar(require("node:fs/promises"));
48
54
  const path = __importStar(require("node:path"));
49
55
  const zod_1 = require("zod");
50
56
  const yaml_1 = require("yaml");
57
+ // Local
58
+ const validation_1 = require("../templates/validation");
51
59
  function loadLayoutDefinition(layoutFile) {
52
60
  return __awaiter(this, void 0, void 0, function* () {
53
61
  try {
@@ -83,12 +91,24 @@ function loadLayoutFromFile(layoutFile) {
83
91
  });
84
92
  }
85
93
  exports.loadLayoutFromFile = loadLayoutFromFile;
94
+ /**
95
+ * Loads a template file from a file system
96
+ * @param layoutDirectory - The path to the layout directory
97
+ * @param templateFile - The path to the template file
98
+ * @returns The template
99
+ */
86
100
  function loadTemplate(layoutDirectory, templateFile) {
87
101
  return __awaiter(this, void 0, void 0, function* () {
88
102
  try {
89
- return yield fs.readFile(path.resolve(layoutDirectory, templateFile), {
103
+ const template = yield fs.readFile(path.resolve(layoutDirectory, templateFile), {
90
104
  encoding: 'utf-8',
91
105
  });
106
+ // Validate the template
107
+ const validationErrors = yield (0, validation_1.validateTemplateFile)(template);
108
+ if (validationErrors.length > 0) {
109
+ throw new Error(validationErrors.join('\n'));
110
+ }
111
+ return template;
92
112
  }
93
113
  catch (e) {
94
114
  throw Error(`Failed loading template file "${templateFile}": ${e.message}`);
@@ -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 fs = __importStar(require("node:fs/promises"));
36
36
  const definitions_1 = require("./definitions");
37
+ const validation_1 = require("../templates/validation");
37
38
  jest.mock('node:fs/promises');
38
39
  describe('loadLayoutDefinition', () => {
39
40
  const paintLayoutFileYaml = './some-dir/page-layout.yaml';
@@ -436,3 +437,64 @@ describe('LayoutDefinitionParse', () => {
436
437
  expect(layoutDefinition.zones.main.minNodes).toBe(0);
437
438
  });
438
439
  });
440
+ describe('validateTemplateFile', () => {
441
+ describe('valid templates', () => {
442
+ it('should validate a simple valid template', () => __awaiter(void 0, void 0, void 0, function* () {
443
+ const template = '<div>{{zones.content}}</div>';
444
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
445
+ expect(errors).toHaveLength(0);
446
+ }));
447
+ it('should validate a complex valid template', () => __awaiter(void 0, void 0, void 0, function* () {
448
+ const template = `
449
+ <div class="layout">
450
+ {{#if zones.header}}
451
+ <header>{{zones.header}}</header>
452
+ {{/if}}
453
+ <main>{{zones.content}}</main>
454
+ {{#each zones.sidebar}}
455
+ <aside>{{this}}</aside>
456
+ {{/each}}
457
+ </div>
458
+ `;
459
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
460
+ expect(errors).toHaveLength(0);
461
+ }));
462
+ it('should validate template with options', () => __awaiter(void 0, void 0, void 0, function* () {
463
+ const template = '<div class="{{options.theme}}">{{zones.content}}</div>';
464
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
465
+ expect(errors).toHaveLength(0);
466
+ }));
467
+ it('should allow empty template', () => __awaiter(void 0, void 0, void 0, function* () {
468
+ const template = '';
469
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
470
+ expect(errors).toHaveLength(0);
471
+ }));
472
+ });
473
+ describe('invalid templates', () => {
474
+ it('should detect top-level HTML tags', () => __awaiter(void 0, void 0, void 0, function* () {
475
+ const template = '<html><body>{{zones.content}}</body></html>';
476
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
477
+ expect(errors.length).toBeGreaterThan(0);
478
+ expect(errors).toEqual(expect.arrayContaining([
479
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <html>'),
480
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <body>'),
481
+ ]));
482
+ }));
483
+ it('should detect DOCTYPE declarations', () => __awaiter(void 0, void 0, void 0, function* () {
484
+ const template = '<!DOCTYPE html><div>{{zones.content}}</div>';
485
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
486
+ expect(errors.length).toBeGreaterThan(0);
487
+ expect(errors).toEqual(expect.arrayContaining([
488
+ expect.stringContaining('Template should not contain top-level HTML structure tag: <doctype>'),
489
+ ]));
490
+ }));
491
+ });
492
+ describe('error handling', () => {
493
+ it('should handle null template gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
494
+ // Test with a null template
495
+ const template = null;
496
+ const errors = yield (0, validation_1.validateTemplateFile)(template);
497
+ expect(errors).toHaveLength(0);
498
+ }));
499
+ });
500
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/dxp-cli-next",
3
- "version": "5.30.0-develop.2",
3
+ "version": "5.30.0-develop.3",
4
4
  "repository": {
5
5
  "url": "https://gitlab.squiz.net/dxp/dxp-cli-next"
6
6
  },