@squiz/component-cli-lib 1.2.11 → 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 (34) hide show
  1. package/.env.example +9 -0
  2. package/.gitlab-ci.yml +2 -3
  3. package/CHANGELOG.md +14 -414
  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 +9 -9
  7. package/lib/component-dev-folder-structures.integration.spec.js.map +1 -1
  8. package/lib/component-dev.integration.spec.js +5 -5
  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 +66 -12
  17. package/lib/integration-tests/upload-and-render-component.integration.spec.js.map +1 -1
  18. package/lib/upload-component-folder.d.ts +2 -1
  19. package/lib/upload-component-folder.js +20 -34
  20. package/lib/upload-component-folder.js.map +1 -1
  21. package/package.json +13 -12
  22. package/src/component-dev-folder-structures.integration.spec.ts +15 -9
  23. package/src/component-dev.integration.spec.ts +5 -5
  24. package/src/component-dev.ts +28 -16
  25. package/src/integration-tests/__components__/cmp-format-string/main.js +7 -0
  26. package/src/integration-tests/__components__/cmp-format-string/manifest.json +41 -0
  27. package/src/integration-tests/__components__/cmp-static-file-test/main.js +1 -5
  28. package/src/integration-tests/__components__/invalid-manifest/main.js +0 -4
  29. package/src/integration-tests/__components__/invalid-upload/main.js +0 -4
  30. package/src/integration-tests/helper.ts +54 -0
  31. package/src/integration-tests/upload-and-render-component.integration.spec.ts +82 -21
  32. package/src/upload-component-folder.ts +31 -39
  33. package/tsconfig.json +3 -2
  34. package/tsconfig.tsbuildinfo +1 -1
@@ -9,6 +9,7 @@ import configObj, {
9
9
  deleteComponentSet,
10
10
  addContentItem,
11
11
  deleteContentItem,
12
+ managementServiceRoot,
12
13
  } from './helper';
13
14
  import color from 'cli-color';
14
15
  import path from 'path';
@@ -58,12 +59,7 @@ describe('uploading a component', () => {
58
59
  it('Should fail uploading a component without manifest.json', async () => {
59
60
  mockConsoleError.mockClear();
60
61
  const componentPath = path.join(__dirname, '/__components__/invalid-upload');
61
- await uploadComponentFolder(
62
- componentPath,
63
- configObj.managementServiceUrl + '/v1',
64
- configObj.renderServiceUrl,
65
- testFilesDir,
66
- );
62
+ await uploadComponentFolder(managementServiceRoot, configObj.managementServiceUrl, componentPath, testFilesDir);
67
63
  expect(mockConsoleError.mock.calls[0][0]).toEqual(color.red('manifest could not be found'));
68
64
  });
69
65
 
@@ -71,13 +67,14 @@ describe('uploading a component', () => {
71
67
  mockConsoleError.mockClear();
72
68
  const componentPath = path.join(__dirname, '/__components__/invalid-manifest');
73
69
  await uploadComponentFolder(
70
+ managementServiceRoot,
71
+ configObj.managementServiceUrl,
74
72
  componentPath,
75
- configObj.managementServiceUrl + '/v1',
76
- configObj.renderServiceUrl,
73
+
77
74
  testFilesDir,
78
75
  );
79
76
  expect(mockConsoleError.mock.calls[0][0]).toEqual(
80
- 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_\\-]+$"'),
81
78
  );
82
79
  });
83
80
 
@@ -87,9 +84,10 @@ describe('uploading a component', () => {
87
84
  const filePath = `${componentPath}/105mb-file`;
88
85
  await createFile(filePath, 105); // Higher limit has been used because compression reduces the size if you use closer to 100MB
89
86
  await uploadComponentFolder(
87
+ managementServiceRoot,
88
+ configObj.managementServiceUrl,
90
89
  componentPath,
91
- configObj.managementServiceUrl + '/v1',
92
- configObj.renderServiceUrl,
90
+
93
91
  testFilesDir,
94
92
  );
95
93
  expect(mockConsoleError.mock.calls[0][0]).toEqual(
@@ -99,6 +97,65 @@ describe('uploading a component', () => {
99
97
  });
100
98
  });
101
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
+
102
159
  describe('Deploy basic component having a static file', () => {
103
160
  // component to deploy for this test
104
161
  const componentPath = path.join(__dirname, '/__components__/cmp-static-file-test');
@@ -126,13 +183,14 @@ describe('uploading a component', () => {
126
183
 
127
184
  it('Should upload the component and return a valid url to preview', async () => {
128
185
  await uploadComponentFolder(
186
+ managementServiceRoot,
187
+ configObj.managementServiceUrl,
129
188
  componentPath,
130
- configObj.managementServiceUrl + '/v1',
131
- configObj.renderServiceUrl,
189
+
132
190
  testFilesDir,
133
191
  );
134
192
 
135
- 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>';
136
194
  const get = await supertest(configObj.renderServiceUrl).get('/');
137
195
  expect(get.status).toEqual(200);
138
196
  expect((get as any)?.res?.text).toContain(uploadedComponent);
@@ -141,9 +199,10 @@ describe('uploading a component', () => {
141
199
  it('Should fail upload the component with same version', async () => {
142
200
  mockConsoleError.mockClear();
143
201
  await uploadComponentFolder(
202
+ managementServiceRoot,
203
+ configObj.managementServiceUrl,
144
204
  componentPath,
145
- configObj.managementServiceUrl + '/v1',
146
- configObj.renderServiceUrl,
205
+
147
206
  testFilesDir,
148
207
  );
149
208
  expect(mockConsoleError.mock.calls[0][0]).toEqual(
@@ -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
  });
@@ -4,41 +4,39 @@ import { zipDirectory } from '@squiz/dx-common-lib';
4
4
  import { Manifest, ManifestServiceForDev } from '@squiz/component-lib';
5
5
  import fsp from 'fs/promises';
6
6
  import path from 'path';
7
- import axios, { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
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
 
13
14
  export async function uploadComponentFolder(
14
- folderPath: string,
15
+ apiClient: AxiosInstance,
15
16
  componentServiceManagementUrl: string,
16
- componentRenderServiceUrl: string,
17
+ folderPath: string,
17
18
  baseTempDir: string = '',
18
19
  ): Promise<void> {
19
20
  const tmpDir = await fsp.mkdtemp(path.resolve(baseTempDir, 'cmp-upload'));
20
21
 
21
22
  try {
22
- const axiosInstance = axios.create({
23
- baseURL: componentServiceManagementUrl,
24
- headers: {
25
- 'content-type': 'application/json',
26
- },
27
- });
28
-
29
- await preUploadChecks(folderPath, componentRenderServiceUrl);
23
+ await preUploadChecks(apiClient, componentServiceManagementUrl, folderPath);
30
24
  logger.info('Initial scanning');
31
25
  const zip = await zipDirectory(folderPath, tmpDir);
32
26
 
33
- const initialUpload = await handleResponse<any>(axiosInstance.post('upload-component'));
27
+ const initialUpload = await handleResponse<any>(
28
+ apiClient.post('/v1/upload-component', {}, { baseURL: componentServiceManagementUrl }),
29
+ );
34
30
 
35
31
  logger.info(`deployment id: ${initialUpload.id} status: transferring`);
36
32
  await uploadFile(initialUpload, zip);
37
33
 
38
- await watchAndWaitForUploadAndScanComplete(initialUpload.id, axiosInstance);
34
+ await watchAndWaitForUploadAndScanComplete(apiClient, componentServiceManagementUrl, initialUpload.id);
39
35
 
40
36
  logger.info(`deployment id: ${initialUpload.id} status: deploying component folder`);
41
- const result = await handleResponse<any>(axiosInstance.post(`upload-component/next/${initialUpload.id}`));
37
+ const result = await handleResponse<any>(
38
+ apiClient.post(`/v1/upload-component/next/${initialUpload.id}`, {}, { baseURL: componentServiceManagementUrl }),
39
+ );
42
40
 
43
41
  await fsp.rm(tmpDir, { force: true, recursive: true });
44
42
 
@@ -59,44 +57,38 @@ export async function uploadComponentFolder(
59
57
  }
60
58
  }
61
59
 
62
- async function preUploadChecks(folderPath: string, renderService: string) {
63
- const service = new ManifestServiceForDev(folderPath, logger);
60
+ async function preUploadChecks(apiClient: AxiosInstance, managementURL: string, folderPath: string) {
61
+ const service = new ManifestServiceForDev(folderPath, logger, new JsonValidationService());
64
62
  const manifestPath = path.join(folderPath, `manifest.json`);
65
63
 
66
64
  const result = await service.readManifest(manifestPath);
67
65
  await service.assertManifestIsValid(manifestPath, result.getModel());
68
66
 
69
- if (await checkIfVersionExists(result, renderService)) {
67
+ if (await checkIfVersionExists(apiClient, managementURL, result)) {
70
68
  throw new Error(`Cannot upload component version, ${result.getName()} ${result.getVersion()} already exists`);
71
69
  }
72
70
  }
73
71
 
74
- async function checkIfVersionExists(inputManifest: Manifest, renderService: string) {
75
- const axiosInstance = axios.create({
76
- baseURL: renderService,
72
+ async function checkIfVersionExists(apiClient: AxiosInstance, managementURL: string, inputManifest: Manifest) {
73
+ const response = await apiClient.get(`/v1/component/${inputManifest.getName()}/${inputManifest.getVersion()}`, {
74
+ validateStatus: null,
75
+ baseURL: managementURL,
77
76
  });
78
-
79
- try {
80
- const response = await axiosInstance.get(
81
- `d/${inputManifest.getName()}/${inputManifest.getVersion()}/manifest.json`,
82
- );
83
- if (response.status === 200) {
84
- return true;
85
- }
86
- throw new Error(`Unexpected response code ${response.status}`);
87
- } catch (error) {
88
- if (isAxiosError(error)) {
89
- const { response } = error;
90
- if (response?.status === 404) {
91
- return false;
92
- }
93
- }
94
- throw error;
77
+ if (response.status === 200) {
78
+ return true;
79
+ } else if (response.status === 404) {
80
+ return false;
95
81
  }
82
+ throw new Error(`Unexpected response code ${response.status}`);
96
83
  }
97
84
 
98
- async function watchAndWaitForUploadAndScanComplete(id: string, axiosInstance: AxiosInstance): Promise<void> {
99
- const poll = () => handleResponse<ScanStatus>(axiosInstance.get('upload-component/status/' + id));
85
+ async function watchAndWaitForUploadAndScanComplete(
86
+ apiClient: AxiosInstance,
87
+ managementURL: string,
88
+ id: string,
89
+ ): Promise<void> {
90
+ const poll = () =>
91
+ handleResponse<ScanStatus>(apiClient.get('/v1/upload-component/status/' + id, { baseURL: managementURL }));
100
92
 
101
93
  return new Promise((resolve, reject) => {
102
94
  const recurse = () =>
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"],