@squiz/component-cli-lib 1.67.0 → 1.68.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.
@@ -1,3 +1,4 @@
1
+ import axios from 'axios';
1
2
  import { ComponentSetWebModelForCreate, Manifest } from '@squiz/component-lib';
2
3
  import { ContentApi } from '@squiz/component-web-api-lib';
3
4
  interface Config {
@@ -12,12 +13,12 @@ interface Config {
12
13
  }
13
14
  declare const configObj: Config;
14
15
  export default configObj;
15
- export declare const managementService: import("axios").AxiosInstance;
16
- export declare const managementServiceRoot: import("axios").AxiosInstance;
17
- export declare const renderService: import("axios").AxiosInstance;
18
- export declare const contentService: import("axios").AxiosInstance;
19
- export declare const pageRenderService: import("axios").AxiosInstance;
20
- export declare const jobService: import("axios").AxiosInstance;
16
+ export declare const managementService: axios.AxiosInstance;
17
+ export declare const managementServiceRoot: axios.AxiosInstance;
18
+ export declare const renderService: axios.AxiosInstance;
19
+ export declare const contentService: axios.AxiosInstance;
20
+ export declare const pageRenderService: axios.AxiosInstance;
21
+ export declare const jobService: axios.AxiosInstance;
21
22
  export declare const ci_buildVersion: string;
22
23
  export declare const ci_buildBranch: string;
23
24
  export declare function getTestComponents(): Promise<import("@squiz/component-lib").ManifestV1[]>;
@@ -0,0 +1 @@
1
+ export {};
package/lib/utils.d.ts CHANGED
@@ -6,3 +6,4 @@ export declare function handleError(error: any): Error;
6
6
  export declare function isAxiosError(error: any): error is AxiosError;
7
7
  export declare function isAxiosResponse(response: unknown): response is AxiosResponse;
8
8
  export declare function isUserInOrganization(organisationName?: string): boolean;
9
+ export declare function assertComponentTypeAllowed(componentType: unknown, apiClient: AxiosInstance, endpoint: string, managementURL: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/component-cli-lib",
3
- "version": "1.67.0",
3
+ "version": "1.68.1",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "private": false,
@@ -17,10 +17,10 @@
17
17
  "author": "",
18
18
  "license": "ISC",
19
19
  "devDependencies": {
20
- "@squiz/component-lib": "^1.64.0",
21
- "@squiz/component-web-api-lib": "^1.65.0",
22
- "@squiz/dx-common-lib": "^1.64.0",
23
- "@squiz/dx-json-schema-lib": "^1.68.0",
20
+ "@squiz/component-lib": "^1.78.1",
21
+ "@squiz/component-web-api-lib": "^1.65.1",
22
+ "@squiz/dx-common-lib": "^1.68.3",
23
+ "@squiz/dx-json-schema-lib": "^1.76.1",
24
24
  "@squiz/dx-logger-lib": "^1.64.0",
25
25
  "@squiz/dxp-session-lib": "^1.65.0",
26
26
  "@squiz/virus-scanner-lib": "^1.64.0",
@@ -37,9 +37,10 @@
37
37
  "typescript": "4.9.4"
38
38
  },
39
39
  "dependencies": {
40
- "@squiz/render-runtime-lib": "^1.64.0",
41
- "@squiz/edge-dev-render-runtime-lib": "^2.1.0",
42
- "@squiz/runtime-sandbox-node": "^1.64.0",
40
+ "@squiz/edge-dev-render-runtime-lib": "^2.1.1",
41
+ "@squiz/job-runner-lib": "file:job-runner-lib.tgz",
42
+ "@squiz/render-runtime-lib": "^1.76.0",
43
+ "@squiz/runtime-sandbox-node": "^1.64.2",
43
44
  "archiver": "5.3.1",
44
45
  "axios": "1.6.0",
45
46
  "cli-color": "^2.0.2",
@@ -2,7 +2,7 @@ import supertest from 'supertest';
2
2
  import path from 'path';
3
3
  import fsp from 'fs/promises';
4
4
 
5
- import { TestHelpers } from '@squiz/render-runtime-lib';
5
+ import { TestHelpers } from '@squiz/render-runtime-lib/test-helpers';
6
6
  import { startDevelopmentRender } from './component-dev';
7
7
  import { Server } from 'http';
8
8
 
@@ -1,4 +1,4 @@
1
- import { routeTests, TestHelpers } from '@squiz/render-runtime-lib';
1
+ import { routeTests, TestHelpers } from '@squiz/render-runtime-lib/test-helpers';
2
2
  import { startDevelopmentRender } from './component-dev';
3
3
  import supertest from 'supertest';
4
4
  import { Server } from 'http';
@@ -14,25 +14,29 @@ interface HealthInfo {
14
14
  }
15
15
 
16
16
  describe('Verify latest services deployments', () => {
17
- it('Should have latest Management API service', async () => {
17
+ it.skip('Should have latest Management API service', async () => {
18
18
  const response: HealthInfo = (await managementServiceRoot.get('/health')).data;
19
19
  expect(response.buildVersion).toBe(ci_buildVersion);
20
20
  expect(response.buildBranch).toBe(ci_buildBranch);
21
21
  });
22
22
 
23
23
  it('Should return 200 for Management API docs', async () => {
24
- const req = await managementService.get('/docs');
24
+ const req = await managementService.get('/v1/docs', {
25
+ baseURL: managementService.defaults.baseURL?.replace(/v1\/?/, ''),
26
+ });
25
27
  expect(req.status).toBe(200);
26
28
  expect(req.headers['content-type']).toEqual('text/html; charset=utf-8');
27
29
  });
28
30
 
29
31
  it('Should return 200 for Management API docs.json', async () => {
30
- const req = await managementService.get('/docs.json');
32
+ const req = await managementService.get('/static/v1/docs.json', {
33
+ baseURL: managementService.defaults.baseURL?.replace(/v1\/?/, ''),
34
+ });
31
35
  expect(req.status).toBe(200);
32
36
  expect(req.headers['content-type']).toEqual('application/json; charset=UTF-8');
33
37
  });
34
38
 
35
- it('Should have latest Render Runtime service', async () => {
39
+ it.skip('Should have latest Render Runtime service', async () => {
36
40
  const response: HealthInfo = (await renderService.get('/health')).data;
37
41
  expect(response.buildVersion).toBe(ci_buildVersion);
38
42
  expect(response.buildBranch).toBe(ci_buildBranch);
@@ -45,12 +49,12 @@ describe('Verify latest services deployments', () => {
45
49
  });
46
50
 
47
51
  it('Should return 200 for Render Runtime API docs.json', async () => {
48
- const req = await renderService.get('/docs.json');
52
+ const req = await renderService.get('/static/docs.json');
49
53
  expect(req.status).toBe(200);
50
54
  expect(req.headers['content-type']).toEqual('application/json; charset=UTF-8');
51
55
  });
52
56
 
53
- it('Should have latest Content API service', async () => {
57
+ it.skip('Should have latest Content API service', async () => {
54
58
  const response: HealthInfo = (await contentService.get('/health')).data;
55
59
  expect(response.buildVersion).toBe(ci_buildVersion);
56
60
  expect(response.buildBranch).toBe(ci_buildBranch);
@@ -63,7 +67,7 @@ describe('Verify latest services deployments', () => {
63
67
  });
64
68
 
65
69
  it('Should return 200 for Content API docs.json', async () => {
66
- const req = await contentService.get('/docs.json');
70
+ const req = await contentService.get('/static/docs.json');
67
71
  expect(req.status).toBe(200);
68
72
  expect(req.headers['content-type']).toEqual('application/json; charset=UTF-8');
69
73
  });
@@ -13,6 +13,7 @@ import {
13
13
  handleResponse,
14
14
  isAxiosResponse,
15
15
  isUserInOrganization,
16
+ assertComponentTypeAllowed,
16
17
  } from './utils';
17
18
 
18
19
  export const logger: Logger = getLogger({ name: 'upload-component', format: 'human' });
@@ -35,6 +36,9 @@ export async function uploadComponentFolder(
35
36
  const manifestService = new ManifestServiceForDev(folderPath, logger);
36
37
  const manifestPath = path.join(folderPath, `manifest.json`);
37
38
  const manifest = await manifestService.readManifest(manifestPath);
39
+
40
+ await assertComponentTypeAllowed(manifest.type, apiClient, '/health', componentServiceManagementUrl);
41
+
38
42
  const shouldValidateNamespace = !isUserInOrganization();
39
43
  await manifestService.assertManifestIsValid(manifestPath, manifest.getModel(), {
40
44
  validateNamespace: shouldValidateNamespace,
package/src/upload-job.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { uploadFile } from '@squiz/virus-scanner-lib';
2
2
 
3
3
  import { zipDirectory } from '@squiz/dx-common-lib';
4
- import { ManifestServiceForDev, JobManifestV1Model } from '@squiz/component-lib';
4
+ import { ManifestServiceForDev, JobManifestV1Model } from '@squiz/job-runner-lib';
5
5
  import fsp from 'fs/promises';
6
6
  import path from 'path';
7
7
  import { AxiosInstance } from 'axios';
package/src/utils.spec.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  isAxiosError,
7
7
  isAxiosResponse,
8
8
  isUserInOrganization,
9
+ assertComponentTypeAllowed,
9
10
  } from './utils';
10
11
  import axios, { AxiosResponse, AxiosError } from 'axios';
11
12
 
@@ -46,7 +47,7 @@ describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
46
47
  it('should pass if virus scan is successful', async () => {
47
48
  jest
48
49
  .spyOn(jobService as any, `get`)
49
- .mockImplementation(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));
50
+ .mockImplementation(() => Promise.resolve({ data: { status: 'Success' }, status: 200 } as AxiosResponse));
50
51
 
51
52
  const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
52
53
  expect(res).toBeUndefined();
@@ -55,7 +56,7 @@ describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
55
56
  it('should throw specific error if virus scan is flagged', async () => {
56
57
  jest
57
58
  .spyOn(jobService as any, `get`)
58
- .mockImplementation(() => Promise.resolve({ data: { status: 'Flagged' } } as AxiosResponse));
59
+ .mockImplementation(() => Promise.resolve({ data: { status: 'Flagged' }, status: 200 } as AxiosResponse));
59
60
 
60
61
  await expect(async () => {
61
62
  await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
@@ -65,7 +66,7 @@ describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
65
66
  it('should throw generic error if virus scan is Error', async () => {
66
67
  jest
67
68
  .spyOn(jobService as any, `get`)
68
- .mockImplementation(() => Promise.resolve({ data: { status: 'Error' } } as AxiosResponse));
69
+ .mockImplementation(() => Promise.resolve({ data: { status: 'Error' }, status: 200 } as AxiosResponse));
69
70
 
70
71
  await expect(async () => {
71
72
  await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
@@ -75,11 +76,11 @@ describe('cli lib utils watchAndWaitForUploadAndScanComplete', () => {
75
76
  it('should poll until a result is received', async () => {
76
77
  jest
77
78
  .spyOn(jobService as any, `get`)
78
- .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
79
- .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
80
- .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
81
- .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' } } as AxiosResponse))
82
- .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Success' } } as AxiosResponse));
79
+ .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' }, status: 200 } as AxiosResponse))
80
+ .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' }, status: 200 } as AxiosResponse))
81
+ .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' }, status: 200 } as AxiosResponse))
82
+ .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Scanning' }, status: 200 } as AxiosResponse))
83
+ .mockImplementationOnce(() => Promise.resolve({ data: { status: 'Success' }, status: 200 } as AxiosResponse));
83
84
 
84
85
  const res = await watchAndWaitForUploadAndScanComplete(jobService, '/upload-job/status/', '123');
85
86
  expect(res).toBeUndefined();
@@ -142,6 +143,20 @@ describe('cli lib utils handleResponse', () => {
142
143
  await handleResponse(response);
143
144
  }).rejects.toThrow('something really bad happened');
144
145
  });
146
+
147
+ it('should throw an error if response is not 200 status', async () => {
148
+ jest.spyOn(jobService as any, `get`).mockImplementation(
149
+ () =>
150
+ new Promise((resolve) => {
151
+ resolve({ status: 400, data: { message: 'some bad request' } } as AxiosResponse);
152
+ }),
153
+ );
154
+
155
+ const response = jobService.get('/upload-job/status/123');
156
+ await expect(async () => {
157
+ await handleResponse(response);
158
+ }).rejects.toThrow('some bad request');
159
+ });
145
160
  });
146
161
 
147
162
  describe('cli lib utils handleError', () => {
@@ -234,3 +249,122 @@ describe('isUserInFooOrganization', () => {
234
249
  expect(result).toBe(false);
235
250
  });
236
251
  });
252
+
253
+ describe('cli lib utils assertComponentTypeAllowed', () => {
254
+ it('"edge" component only - should NOT throw error if uploading "edge" component', async () => {
255
+ const response = {
256
+ data: {
257
+ edgeComponentsOnly: true,
258
+ },
259
+ } as AxiosResponse;
260
+ const spy = jest.spyOn(jobService as any, `get`);
261
+ spy.mockImplementation(() => Promise.resolve(response));
262
+
263
+ await assertComponentTypeAllowed('edge', jobService, 'foo', 'some-url');
264
+ expect(spy).toHaveBeenCalledTimes(0);
265
+ });
266
+
267
+ it('"edge" component only - should throw error if uploading "server" component', async () => {
268
+ const response = {
269
+ data: {
270
+ edgeComponentsOnly: true,
271
+ },
272
+ } as AxiosResponse;
273
+ const spy = jest.spyOn(jobService as any, `get`);
274
+ spy.mockImplementation(() => Promise.resolve(response));
275
+
276
+ await expect(assertComponentTypeAllowed('server', jobService, 'foo', 'some-url')).rejects.toThrow(
277
+ `Cannot upload "server" type component. ` +
278
+ `Tenant component service is configured to allow "edge" type components only.`,
279
+ );
280
+ expect(spy).toHaveBeenCalledTimes(1);
281
+ });
282
+
283
+ it('"edge" component only - should throw error if uploading "undefined" component', async () => {
284
+ const response = {
285
+ data: {
286
+ edgeComponentsOnly: true,
287
+ },
288
+ } as AxiosResponse;
289
+ const spy = jest.spyOn(jobService as any, `get`);
290
+ spy.mockImplementation(() => Promise.resolve(response));
291
+
292
+ await expect(assertComponentTypeAllowed(undefined, jobService, 'foo', 'some-url')).rejects.toThrow(
293
+ `Cannot upload "server" type component. ` +
294
+ `Tenant component service is configured to allow "edge" type components only.`,
295
+ );
296
+ expect(spy).toHaveBeenCalledTimes(1);
297
+ });
298
+
299
+ it('no restriction set - should NOT throw error uploading "edge" component', async () => {
300
+ const response = {
301
+ data: {
302
+ edgeComponentsOnly: false,
303
+ },
304
+ } as AxiosResponse;
305
+ const spy = jest.spyOn(jobService as any, `get`);
306
+ spy.mockImplementation(() => Promise.resolve(response));
307
+
308
+ await assertComponentTypeAllowed('edge', jobService, 'foo', 'some-url');
309
+ expect(spy).toHaveBeenCalledTimes(0);
310
+ });
311
+
312
+ it('no restriction set - should NOT throw error uploading "server" component', async () => {
313
+ const response = {
314
+ data: {
315
+ edgeComponentsOnly: false,
316
+ },
317
+ } as AxiosResponse;
318
+ const spy = jest.spyOn(jobService as any, `get`);
319
+ spy.mockImplementation(() => Promise.resolve(response));
320
+
321
+ assertComponentTypeAllowed('server', jobService, 'foo', 'some-url');
322
+ expect(spy).toHaveBeenCalledTimes(1);
323
+ });
324
+
325
+ it('no restriction set - should NOT throw error uploading "undefined" component', async () => {
326
+ const response = {
327
+ data: {
328
+ edgeComponentsOnly: false,
329
+ },
330
+ } as AxiosResponse;
331
+ const spy = jest.spyOn(jobService as any, `get`);
332
+ spy.mockImplementation(() => Promise.resolve(response));
333
+
334
+ assertComponentTypeAllowed(undefined, jobService, 'foo', 'some-url');
335
+ expect(spy).toHaveBeenCalledTimes(1);
336
+ });
337
+
338
+ it('settings undefined - should NOT throw error uploading "edge" component', async () => {
339
+ const response = {
340
+ data: {},
341
+ } as AxiosResponse;
342
+ const spy = jest.spyOn(jobService as any, `get`);
343
+ spy.mockImplementation(() => Promise.resolve(response));
344
+
345
+ await assertComponentTypeAllowed('edge', jobService, 'foo', 'some-url');
346
+ expect(spy).toHaveBeenCalledTimes(0);
347
+ });
348
+
349
+ it('settings undefined - should NOT throw error uploading "server" component', async () => {
350
+ const response = {
351
+ data: {},
352
+ } as AxiosResponse;
353
+ const spy = jest.spyOn(jobService as any, `get`);
354
+ spy.mockImplementation(() => Promise.resolve(response));
355
+
356
+ assertComponentTypeAllowed('server', jobService, 'foo', 'some-url');
357
+ expect(spy).toHaveBeenCalledTimes(1);
358
+ });
359
+
360
+ it('settings undefined - should NOT throw error uploading "undefined" component', async () => {
361
+ const response = {
362
+ data: {},
363
+ } as AxiosResponse;
364
+ const spy = jest.spyOn(jobService as any, `get`);
365
+ spy.mockImplementation(() => Promise.resolve(response));
366
+
367
+ assertComponentTypeAllowed(undefined, jobService, 'foo', 'some-url');
368
+ expect(spy).toHaveBeenCalledTimes(1);
369
+ });
370
+ });
package/src/utils.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
2
2
  import { ScanStatus } from '@squiz/virus-scanner-lib';
3
3
  import { execSync } from 'child_process';
4
+ import { ComponentType } from '@squiz/component-lib';
4
5
 
5
6
  export async function watchAndWaitForUploadAndScanComplete(
6
7
  apiClient: AxiosInstance,
@@ -8,7 +9,7 @@ export async function watchAndWaitForUploadAndScanComplete(
8
9
  id: string,
9
10
  managementURL?: string,
10
11
  ): Promise<void> {
11
- let poll: () => Promise<any>;
12
+ let poll: () => Promise<ScanStatus>;
12
13
  if (managementURL) {
13
14
  poll = () => handleResponse<ScanStatus>(apiClient.get(endpoint + id, { baseURL: managementURL }));
14
15
  } else {
@@ -64,7 +65,10 @@ export async function checkIfVersionExists(apiClient: AxiosInstance, endpoint: s
64
65
  export async function handleResponse<T>(axiosInstance: Promise<AxiosResponse<T>>): Promise<T> {
65
66
  try {
66
67
  const response = await axiosInstance;
67
- return response.data;
68
+ if (response.status.toString().startsWith('2')) {
69
+ return response.data;
70
+ }
71
+ throw handleError(response.data);
68
72
  } catch (error) {
69
73
  throw handleError(error);
70
74
  }
@@ -76,7 +80,7 @@ export function handleError(error: any): Error {
76
80
  const newError = new Error(errorMessage || 'An error has occurred');
77
81
 
78
82
  if (isAxiosError(error)) {
79
- const errorCode = response.status ?? 'unknown';
83
+ const errorCode = response?.status ?? 'unknown';
80
84
  if (response?.data?.message) {
81
85
  newError.message = `Unexpected response code ${errorCode}. ${response.data.message}`.trim();
82
86
  } else {
@@ -120,3 +124,26 @@ export function isUserInOrganization(organisationName: string = 'squiz'): boolea
120
124
 
121
125
  return isMember;
122
126
  }
127
+
128
+ export async function assertComponentTypeAllowed(
129
+ componentType: unknown,
130
+ apiClient: AxiosInstance,
131
+ endpoint: string,
132
+ managementURL: string,
133
+ ) {
134
+ if ((componentType as ComponentType) !== 'edge') {
135
+ try {
136
+ const response = await apiClient.get(endpoint, {
137
+ baseURL: managementURL,
138
+ });
139
+ if (response.data.edgeComponentsOnly) {
140
+ throw Error(
141
+ `Cannot upload "server" type component. ` +
142
+ `Tenant component service is configured to allow "edge" type components only.`,
143
+ );
144
+ }
145
+ } catch (error) {
146
+ throw handleError(error);
147
+ }
148
+ }
149
+ }
package/tsconfig.json CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  "compilerOptions": {
5
5
  "outDir": "lib",
6
+ "moduleResolution": "node16",
7
+ "module": "Node16",
6
8
  "resolveJsonModule": true,
7
9
  "composite": true,
8
10
  "rootDir": "./src",