@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.
- package/.env.example +9 -0
- package/.gitlab-ci.yml +2 -3
- package/CHANGELOG.md +14 -414
- package/jest.config.ts +9 -1
- package/jest.integration.config.ts +7 -2
- package/lib/component-dev-folder-structures.integration.spec.js +9 -9
- package/lib/component-dev-folder-structures.integration.spec.js.map +1 -1
- package/lib/component-dev.integration.spec.js +5 -5
- package/lib/component-dev.integration.spec.js.map +1 -1
- package/lib/component-dev.js +16 -9
- package/lib/component-dev.js.map +1 -1
- package/lib/integration-tests/__components__/cmp-format-string/manifest.json +40 -0
- package/lib/integration-tests/helper.d.ts +1 -0
- package/lib/integration-tests/helper.js +47 -1
- package/lib/integration-tests/helper.js.map +1 -1
- package/lib/integration-tests/upload-and-render-component.integration.spec.js +66 -12
- package/lib/integration-tests/upload-and-render-component.integration.spec.js.map +1 -1
- package/lib/upload-component-folder.d.ts +2 -1
- package/lib/upload-component-folder.js +20 -34
- package/lib/upload-component-folder.js.map +1 -1
- package/package.json +13 -12
- package/src/component-dev-folder-structures.integration.spec.ts +15 -9
- package/src/component-dev.integration.spec.ts +5 -5
- package/src/component-dev.ts +28 -16
- package/src/integration-tests/__components__/cmp-format-string/main.js +7 -0
- package/src/integration-tests/__components__/cmp-format-string/manifest.json +41 -0
- package/src/integration-tests/__components__/cmp-static-file-test/main.js +1 -5
- package/src/integration-tests/__components__/invalid-manifest/main.js +0 -4
- package/src/integration-tests/__components__/invalid-upload/main.js +0 -4
- package/src/integration-tests/helper.ts +54 -0
- package/src/integration-tests/upload-and-render-component.integration.spec.ts +82 -21
- package/src/upload-component-folder.ts +31 -39
- package/tsconfig.json +3 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
configObj.renderServiceUrl,
|
|
189
|
+
|
|
132
190
|
testFilesDir,
|
|
133
191
|
);
|
|
134
192
|
|
|
135
|
-
const uploadedComponent = '<a href="/
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
15
|
+
apiClient: AxiosInstance,
|
|
15
16
|
componentServiceManagementUrl: string,
|
|
16
|
-
|
|
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
|
-
|
|
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>(
|
|
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
|
|
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>(
|
|
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(
|
|
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(
|
|
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(
|
|
75
|
-
const
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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(
|
|
99
|
-
|
|
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": "../
|
|
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"],
|