@squiz/component-cli-lib 1.2.12 → 1.2.13-alpha.0

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.
Files changed (33) hide show
  1. package/.env.example +9 -0
  2. package/.gitlab-ci.yml +2 -3
  3. package/CHANGELOG.md +11 -419
  4. package/jest.config.ts +9 -1
  5. package/jest.integration.config.ts +7 -2
  6. package/lib/component-dev-folder-structures.integration.spec.js +7 -7
  7. package/lib/component-dev-folder-structures.integration.spec.js.map +1 -1
  8. package/lib/component-dev.integration.spec.js +1 -1
  9. package/lib/component-dev.integration.spec.js.map +1 -1
  10. package/lib/component-dev.js +16 -9
  11. package/lib/component-dev.js.map +1 -1
  12. package/lib/integration-tests/__components__/cmp-format-string/manifest.json +40 -0
  13. package/lib/integration-tests/helper.d.ts +1 -0
  14. package/lib/integration-tests/helper.js +47 -1
  15. package/lib/integration-tests/helper.js.map +1 -1
  16. package/lib/integration-tests/upload-and-render-component.integration.spec.js +61 -7
  17. package/lib/integration-tests/upload-and-render-component.integration.spec.js.map +1 -1
  18. package/lib/upload-component-folder.js +2 -1
  19. package/lib/upload-component-folder.js.map +1 -1
  20. package/package.json +12 -11
  21. package/src/component-dev-folder-structures.integration.spec.ts +13 -7
  22. package/src/component-dev.integration.spec.ts +1 -1
  23. package/src/component-dev.ts +28 -16
  24. package/src/integration-tests/__components__/cmp-format-string/main.js +7 -0
  25. package/src/integration-tests/__components__/cmp-format-string/manifest.json +41 -0
  26. package/src/integration-tests/__components__/cmp-static-file-test/main.js +1 -5
  27. package/src/integration-tests/__components__/invalid-manifest/main.js +0 -4
  28. package/src/integration-tests/__components__/invalid-upload/main.js +0 -4
  29. package/src/integration-tests/helper.ts +54 -0
  30. package/src/integration-tests/upload-and-render-component.integration.spec.ts +68 -7
  31. package/src/upload-component-folder.ts +2 -1
  32. package/tsconfig.json +3 -2
  33. package/tsconfig.tsbuildinfo +1 -1
@@ -21,7 +21,7 @@ describe('component-dev', () => {
21
21
 
22
22
  it('should fail validation when requesting a function with a missing entry file', async () => {
23
23
  const response = await request.get(
24
- '/r/set/unit-test-components/test-component/1.0.3/non-existent-entry-file?something=not-used',
24
+ '/r/unit-test-components/test-component/1.0.3/non-existent-entry-file?_componentSet=set&something=not-used',
25
25
  );
26
26
 
27
27
  expect(response.body).toEqual({
@@ -1,12 +1,15 @@
1
1
  import {
2
- ComponentPreviewWithDataService,
2
+ ComponentPreviewService,
3
3
  ComponentRunnerServiceWithWorkers,
4
+ RenderInputService,
4
5
  setupRenderRuntimeServer,
5
6
  } from '@squiz/render-runtime-lib';
6
7
  import { getLogger, LoggerOptions } from '@squiz/dx-logger-lib';
7
8
  import path from 'path';
8
9
  import { ComponentFunctionService, ComponentSetServiceForLocalDev, ManifestServiceForDev } from '@squiz/component-lib';
9
10
  import open from 'open';
11
+ import { DevelopmentApiKeyService } from '@squiz/dx-common-lib';
12
+ import { JsonValidationService } from '@squiz/dx-json-schema-lib';
10
13
 
11
14
  /**
12
15
  * startDevelopmentRender starts a dev-mode render stack for any
@@ -22,9 +25,10 @@ export function startDevelopmentRender(
22
25
  options: { port: number; loggingFormat?: LoggerOptions['format']; noBrowser?: boolean },
23
26
  ) {
24
27
  const logger = getLogger({ name: 'component-dev', format: options.loggingFormat || 'human' });
25
- const rootUrl = `http://localhost:${options.port}`;
26
28
  const dataMountPoint = path.resolve(process.cwd(), componentPath);
27
29
 
30
+ const rootUrl = `http://localhost:${options.port}`;
31
+
28
32
  const componentRunnerService = new ComponentRunnerServiceWithWorkers(
29
33
  {
30
34
  dataMountPoint,
@@ -33,23 +37,31 @@ export function startDevelopmentRender(
33
37
  },
34
38
  logger,
35
39
  );
36
- const componentFunctionService = new ComponentFunctionService(rootUrl);
37
- const webServer = setupRenderRuntimeServer(
38
- {
39
- logger,
40
- componentRunnerService,
41
- componentFunctionService,
42
- componentPreviewService: new ComponentPreviewWithDataService(componentRunnerService, componentFunctionService, {
43
- rootUrl,
44
- }),
45
- componentSetService: new ComponentSetServiceForLocalDev(logger),
46
- manifestService: new ManifestServiceForDev(dataMountPoint, logger),
47
- },
48
- { rootUrl },
40
+ const jsonValidationService = new JsonValidationService();
41
+ const componentFunctionService = new ComponentFunctionService(rootUrl, jsonValidationService);
42
+ const componentSetService = new ComponentSetServiceForLocalDev(logger);
43
+ const manifestService = new ManifestServiceForDev(dataMountPoint, logger, jsonValidationService);
44
+ const contentItemService = undefined;
45
+ const renderInputService = new RenderInputService(
46
+ componentSetService,
47
+ manifestService,
48
+ componentFunctionService,
49
+ contentItemService,
50
+ rootUrl,
49
51
  );
52
+ const webServer = setupRenderRuntimeServer({
53
+ logger,
54
+ componentRunnerService,
55
+ componentFunctionService,
56
+ componentPreviewService: new ComponentPreviewService(),
57
+ componentSetService,
58
+ manifestService,
59
+ renderInputService,
60
+ apiKeyService: new DevelopmentApiKeyService(),
61
+ });
50
62
 
51
63
  const server = webServer.listen(options.port, () => {
52
- logger.info(`Component development webserver started on port ${options.port}`);
64
+ logger.info(`Component development webserver started on port ${rootUrl}`);
53
65
  });
54
66
 
55
67
  if (options?.noBrowser !== true) {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @param {object} input
3
+ * @param {ComponentInfo} info
4
+ */
5
+ module.exports = async function (input, info) {
6
+ return `<div>Input: ${input.text}</div>`;
7
+ };
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "http://localhost:3000/schemas/v1.json#",
3
+
4
+ "name": "cmp-format-string",
5
+ "version": "1.0.0",
6
+ "mainFunction": "main",
7
+ "displayName": "some-display-name",
8
+ "namespace": "fixtures",
9
+ "description": "some-description",
10
+ "functions": [
11
+ {
12
+ "name": "main",
13
+ "entry": "main.js",
14
+ "input": {
15
+ "type": "object",
16
+ "properties": {
17
+ "text": {
18
+ "type": "string",
19
+ "format": "multi-line"
20
+ }
21
+ },
22
+ "required": ["text"]
23
+ },
24
+ "output": { "responseType": "html" }
25
+ }
26
+ ],
27
+ "previews": {
28
+ "test-preview": {
29
+ "functionData": {
30
+ "main": {
31
+ "inputData": {
32
+ "type": "inline",
33
+ "value": {
34
+ "text": "this is a test"
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }
@@ -1,10 +1,6 @@
1
- /**
2
- * @param {object} input
3
- * @param {ComponentInfo} info
4
- */
5
1
  module.exports = async function (input, info) {
6
2
  return (
7
3
  `<div>Input: ${input.something}</div>` +
8
- `<div>${info.ctx.componentName} ${info.ctx.version} ${info.ctx.getStaticResourceUrl('birthday-cake.png')}</div>`
4
+ `<div>${info.componentName} ${info.version} ${info.ctx.getStaticResourceUrl('birthday-cake.png')}</div>`
9
5
  );
10
6
  };
@@ -1,7 +1,3 @@
1
- /**
2
- * @param {object} input
3
- * @param {ComponentInfo} info
4
- */
5
1
  module.exports = async function (input, info) {
6
2
  return '<div>test</div>';
7
3
  };
@@ -1,7 +1,3 @@
1
- /**
2
- * @param {object} input
3
- * @param {ComponentInfo} info
4
- */
5
1
  module.exports = async function (input, info) {
6
2
  return '<div>test</div>';
7
3
  };
@@ -8,6 +8,7 @@ import { ComponentSetWebModelForCreate } from '@squiz/component-lib';
8
8
  import { parseEnvVarForVar } from '@squiz/dx-common-lib';
9
9
  import { ContentApi } from '@squiz/component-web-api-lib';
10
10
  import { config } from 'dotenv';
11
+ import { execSync } from 'child_process';
11
12
 
12
13
  config();
13
14
 
@@ -27,22 +28,75 @@ const configObj: Config = {
27
28
  ci_buildBranch: parseEnvVarForVar('CI_COMMIT_REF_NAME'),
28
29
  };
29
30
 
31
+ if (!configObj.ci_buildVersion) {
32
+ configObj.ci_buildVersion = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
33
+ }
34
+
35
+ if (!configObj.ci_buildBranch) {
36
+ configObj.ci_buildBranch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
37
+ }
38
+
30
39
  export default configObj;
31
40
 
41
+ const DXP_SERVICE_NAME = 'dxpComponents';
42
+ const ALL_PERMISSIONS_ROLE = {
43
+ privileges: [
44
+ 'COMPONENT_DEPLOY',
45
+ 'COMPONENT_DELETE',
46
+ 'COMPONENT_READ',
47
+ 'COMPONENT_SET_RULES_READ',
48
+ 'COMPONENT_SET_RULES_WRITE',
49
+
50
+ 'COMPONENT_SET_READ',
51
+ 'COMPONENT_SET_WRITE',
52
+
53
+ 'COMPONENT_SET_ENVIRONMENT_READ',
54
+ 'COMPONENT_SET_ENVIRONMENT_WRITE',
55
+ ],
56
+ };
57
+
58
+ const validToken = {
59
+ organisationId: 'aa',
60
+ permission: '',
61
+ tenant: 'zz',
62
+ tenantId: 'zz',
63
+ userId: 'zz',
64
+
65
+ service: {
66
+ [DXP_SERVICE_NAME]: {
67
+ privileges: ALL_PERMISSIONS_ROLE.privileges,
68
+ },
69
+ },
70
+ };
71
+
72
+ export const authToken = `Bearer xxx.${Buffer.from(JSON.stringify(validToken)).toString('base64')}.zzz`;
73
+
32
74
  export const managementService = axios.create({
33
75
  baseURL: configObj.managementServiceUrl + '/v1',
76
+ headers: {
77
+ authorization: authToken,
78
+ },
34
79
  });
35
80
 
36
81
  export const managementServiceRoot = axios.create({
37
82
  baseURL: configObj.managementServiceUrl,
83
+ headers: {
84
+ authorization: authToken,
85
+ },
38
86
  });
39
87
 
40
88
  export const renderService = axios.create({
41
89
  baseURL: configObj.renderServiceUrl,
90
+ headers: {
91
+ authorization: authToken,
92
+ },
42
93
  });
43
94
 
44
95
  export const contentService = axios.create({
45
96
  baseURL: configObj.contentServiceUrl,
97
+ headers: {
98
+ authorization: authToken,
99
+ },
46
100
  });
47
101
 
48
102
  export const ci_buildVersion = configObj.ci_buildVersion;
@@ -74,7 +74,7 @@ describe('uploading a component', () => {
74
74
  testFilesDir,
75
75
  );
76
76
  expect(mockConsoleError.mock.calls[0][0]).toEqual(
77
- color.red('failed validation: /name pattern must match pattern "^[a-zA-Z0-9_\\-]+$"'),
77
+ color.red('failed validation: /name: pattern must match pattern "^[a-zA-Z0-9_\\-]+$"'),
78
78
  );
79
79
  });
80
80
 
@@ -97,6 +97,65 @@ describe('uploading a component', () => {
97
97
  });
98
98
  });
99
99
 
100
+ describe('Deploy a basic component having a input with multiline format', () => {
101
+ beforeAll(async () => {
102
+ await deleteComponentSet(webPath);
103
+ await deleteContentItem(contentItemId);
104
+ for (const componentName of getTestComponents()) {
105
+ try {
106
+ await managementService.delete(`/component/${componentName}`);
107
+ } catch {
108
+ // no op
109
+ }
110
+ }
111
+ });
112
+
113
+ beforeEach(() => {
114
+ console.error = mockConsoleError;
115
+ console.log = mockConsoleLog;
116
+ });
117
+
118
+ afterEach(() => {
119
+ console.error = orgConsoleError;
120
+ });
121
+
122
+ it('Should upload the component and return a valid url to preview', async () => {
123
+ const componentPath = path.join(__dirname, '/__components__/cmp-format-string');
124
+ await uploadComponentFolder(managementServiceRoot, configObj.managementServiceUrl, componentPath, testFilesDir);
125
+ const uploadedComponent = '<a href="/r/fixtures/cmp-format-string/1.0.0?_previewKey=test-preview">1.0.0</a>';
126
+ const get = await supertest(configObj.renderServiceUrl).get('/');
127
+ expect(get.status).toEqual(200);
128
+ expect((get as any)?.res?.text).toContain(uploadedComponent);
129
+ });
130
+
131
+ it('Should render a component with multi-line format string input', async () => {
132
+ const componentSet: ComponentSetWebModelForCreate = {
133
+ webPath,
134
+ displayName: 'some-display-name',
135
+ description: 'Set description',
136
+ headers: {},
137
+ environmentVariables: {},
138
+ components: {
139
+ 'fixtures/cmp-format-string': [{ environmentVariables: {}, version: '1.0.0' }],
140
+ },
141
+ componentVersionRules: {},
142
+ };
143
+ await addComponentSet(componentSet);
144
+ await addContentItem({
145
+ id: contentItemId,
146
+ schemaName: 'fixtures/cmp-format-string/1.0.0/main',
147
+ content: {
148
+ text: 'from-content-item-service',
149
+ },
150
+ });
151
+ const response = await renderService.get(
152
+ `/r/fixtures/cmp-format-string/1.0.0/?_contentItemId=${contentItemId}&_componentSet=${webPath}`,
153
+ );
154
+ expect(response.status).toEqual(200);
155
+ expect(response.data).toEqual(`<div>Input: from-content-item-service</div>`);
156
+ });
157
+ });
158
+
100
159
  describe('Deploy basic component having a static file', () => {
101
160
  // component to deploy for this test
102
161
  const componentPath = path.join(__dirname, '/__components__/cmp-static-file-test');
@@ -131,7 +190,7 @@ describe('uploading a component', () => {
131
190
  testFilesDir,
132
191
  );
133
192
 
134
- const uploadedComponent = '<a href="/preview/smoke-test-components/cmp-static-file-test/1.0.0">1.0.0</a>';
193
+ const uploadedComponent = '<a href="/r/smoke-test-components/cmp-static-file-test/1.0.0">1.0.0</a>';
135
194
  const get = await supertest(configObj.renderServiceUrl).get('/');
136
195
  expect(get.status).toEqual(200);
137
196
  expect((get as any)?.res?.text).toContain(uploadedComponent);
@@ -172,14 +231,14 @@ describe('uploading a component', () => {
172
231
  await addComponentSet(componentSet);
173
232
 
174
233
  const response = await renderService.get(
175
- `/r/${webPath}/smoke-test-components/cmp-static-file-test/1.0.0/?something=hello`,
234
+ `/r/smoke-test-components/cmp-static-file-test/1.0.0/?_componentSet=${webPath}&something=hello`,
176
235
  );
177
236
  expect(response.status).toEqual(200);
178
237
  expect(response.data).toEqual(
179
238
  [
180
239
  '<div>Input: hello</div>',
181
240
  '<div>smoke-test-components/cmp-static-file-test 1.0.0 ',
182
- `${configObj.renderServiceUrl}/s/${webPath}/smoke-test-components/cmp-static-file-test/1.0.0/birthday-cake.png</div>`,
241
+ `${configObj.renderServiceUrl}/s/smoke-test-components/cmp-static-file-test/1.0.0/birthday-cake.png?_componentSet=${webPath}</div>`,
183
242
  ].join(''),
184
243
  );
185
244
  });
@@ -207,17 +266,19 @@ describe('uploading a component', () => {
207
266
  },
208
267
  });
209
268
 
210
- console.log(`/r/${webPath}/smoke-test-components/cmp-static-file-test/1.0.0/?_contentItemId=` + contentItemId);
269
+ console.log(
270
+ `/r/smoke-test-components/cmp-static-file-test/1.0.0/?_componentSet=${webPath}&_contentItemId=${contentItemId}`,
271
+ );
211
272
 
212
273
  const response = await renderService.get(
213
- `/r/${webPath}/smoke-test-components/cmp-static-file-test/1.0.0/?_contentItemId=` + contentItemId,
274
+ `/r/smoke-test-components/cmp-static-file-test/1.0.0/?_componentSet=${webPath}&_contentItemId=${contentItemId}`,
214
275
  );
215
276
  expect(response.status).toEqual(200);
216
277
  expect(response.data).toEqual(
217
278
  [
218
279
  '<div>Input: from-content-item-service</div>',
219
280
  '<div>smoke-test-components/cmp-static-file-test 1.0.0 ',
220
- `${configObj.renderServiceUrl}/s/${webPath}/smoke-test-components/cmp-static-file-test/1.0.0/birthday-cake.png</div>`,
281
+ `${configObj.renderServiceUrl}/s/smoke-test-components/cmp-static-file-test/1.0.0/birthday-cake.png?_componentSet=${webPath}</div>`,
221
282
  ].join(''),
222
283
  );
223
284
  });
@@ -7,6 +7,7 @@ import path from 'path';
7
7
  import { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
8
8
  import color from 'cli-color';
9
9
  import { getLogger, Logger } from '@squiz/dx-logger-lib';
10
+ import { JsonValidationService } from '@squiz/dx-json-schema-lib';
10
11
 
11
12
  export const logger: Logger = getLogger({ name: 'upload-component', format: 'human' });
12
13
 
@@ -57,7 +58,7 @@ export async function uploadComponentFolder(
57
58
  }
58
59
 
59
60
  async function preUploadChecks(apiClient: AxiosInstance, managementURL: string, folderPath: string) {
60
- const service = new ManifestServiceForDev(folderPath, logger);
61
+ const service = new ManifestServiceForDev(folderPath, logger, new JsonValidationService());
61
62
  const manifestPath = path.join(folderPath, `manifest.json`);
62
63
 
63
64
  const result = await service.readManifest(manifestPath);
package/tsconfig.json CHANGED
@@ -11,9 +11,10 @@
11
11
 
12
12
  "references": [
13
13
  { "path": "../virus-scanner-lib" },
14
- { "path": "../component-lib" },
15
14
  { "path": "../render-runtime-lib" },
16
- { "path": "../dx-logger-lib" }
15
+ { "path": "../component-lib" },
16
+ { "path": "../dx-logger-lib" },
17
+ { "path": "../dx-json-schema-lib" }
17
18
  ],
18
19
 
19
20
  "include": ["src/**/*.ts", "src/**/*.json"],