@squiz/component-cli-lib 1.2.1-alpha.68 → 1.2.1-alpha.70
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/.gitlab-ci.yml +69 -7
- package/CHANGELOG.md +16 -0
- package/lib/component-dev.js +1 -0
- package/lib/component-dev.js.map +1 -1
- package/lib/component-dev.spec.js +72 -45
- package/lib/component-dev.spec.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +3 -87
- package/lib/index.js.map +1 -1
- package/lib/integration-tests/__components__/big-package/manifest.json +32 -0
- package/lib/integration-tests/helper.d.ts +2 -0
- package/lib/integration-tests/helper.js +12 -1
- package/lib/integration-tests/helper.js.map +1 -1
- package/lib/integration-tests/test-setup.js +1 -1
- package/lib/integration-tests/upload-and-render-component.spec.js +36 -9
- package/lib/integration-tests/upload-and-render-component.spec.js.map +1 -1
- package/lib/test.js +4 -10
- package/lib/test.js.map +1 -1
- package/lib/upload-component-folder.d.ts +1 -0
- package/lib/upload-component-folder.js +90 -0
- package/lib/upload-component-folder.js.map +1 -0
- package/package.json +8 -8
- package/src/component-dev.spec.ts +82 -42
- package/src/component-dev.ts +2 -0
- package/src/index.ts +1 -91
- package/src/integration-tests/__components__/big-package/manifest.json +33 -0
- package/src/integration-tests/__components__/big-package/render-json.js +5 -0
- package/src/integration-tests/helper.ts +10 -0
- package/src/integration-tests/test-setup.ts +1 -1
- package/src/integration-tests/upload-and-render-component.spec.ts +42 -14
- package/src/test.ts +4 -7
- package/src/upload-component-folder.ts +91 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadComponentFolder = void 0;
|
|
7
|
+
const virus_scanner_lib_1 = require("@squiz/virus-scanner-lib");
|
|
8
|
+
const component_lib_1 = require("@squiz/component-lib");
|
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
const cli_color_1 = __importDefault(require("cli-color"));
|
|
13
|
+
async function uploadComponentFolder(folderPath, componentServiceManagementUrl) {
|
|
14
|
+
const tmpDir = path_1.default.resolve(await promises_1.default.mkdtemp('cmp-upload'));
|
|
15
|
+
try {
|
|
16
|
+
const axiosInstance = axios_1.default.create({
|
|
17
|
+
baseURL: componentServiceManagementUrl,
|
|
18
|
+
});
|
|
19
|
+
console.log('Initial scanning');
|
|
20
|
+
const zip = await (0, component_lib_1.zipDirectory)(folderPath, tmpDir);
|
|
21
|
+
const initialUpload = await handleResponse(axiosInstance.post('upload-component'));
|
|
22
|
+
console.log(`deployment id: ${initialUpload.id} status: transferring`);
|
|
23
|
+
await (0, virus_scanner_lib_1.uploadFile)(initialUpload, zip);
|
|
24
|
+
await watchAndWaitForUploadAndScanComplete(initialUpload.id, axiosInstance);
|
|
25
|
+
console.log(`deployment id: ${initialUpload.id} status: deploying component folder`);
|
|
26
|
+
const result = await handleResponse(axiosInstance.post(`upload-component/next/${initialUpload.id}`));
|
|
27
|
+
await promises_1.default.rm(tmpDir, { force: true, recursive: true });
|
|
28
|
+
if (result.status === 'successful') {
|
|
29
|
+
console.log(`deployment id: ${initialUpload.id} status: ${cli_color_1.default.green('success')}`);
|
|
30
|
+
console.log(`uploaded location: ${result.accessLink}`);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.error('failed for an unknown reason', cli_color_1.default.red(result));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
await promises_1.default.rm(tmpDir, { force: true, recursive: true });
|
|
38
|
+
if (e instanceof Error) {
|
|
39
|
+
console.error(cli_color_1.default.red(e.message));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.uploadComponentFolder = uploadComponentFolder;
|
|
47
|
+
async function watchAndWaitForUploadAndScanComplete(id, axiosInstance) {
|
|
48
|
+
const poll = () => handleResponse(axiosInstance.get('upload-component/status/' + id));
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const recurse = () => poll().then(async (req) => {
|
|
51
|
+
if (req.status == 'Success') {
|
|
52
|
+
resolve();
|
|
53
|
+
}
|
|
54
|
+
else if (req.status == 'Flagged') {
|
|
55
|
+
reject(new Error('upload has been flagged as a virus'));
|
|
56
|
+
}
|
|
57
|
+
else if (req.status == 'Error') {
|
|
58
|
+
reject(new Error('there has been an error'));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
setTimeout(async () => {
|
|
62
|
+
await recurse();
|
|
63
|
+
}, 1000);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
recurse();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function handleResponse(axiosInstance) {
|
|
70
|
+
const result = axiosInstance
|
|
71
|
+
.then((response) => response.data)
|
|
72
|
+
.catch((error) => {
|
|
73
|
+
throw handleAxiosError(error);
|
|
74
|
+
});
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
function handleAxiosError(error) {
|
|
78
|
+
const { response } = error;
|
|
79
|
+
if (!response || (typeof error == 'object' && !error.isAxiosError)) {
|
|
80
|
+
return error;
|
|
81
|
+
}
|
|
82
|
+
const message = response.data.message;
|
|
83
|
+
if (message) {
|
|
84
|
+
return new Error(`Unexpected response code ${response.status}. message: ${message}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return new Error(`Unexpected response code ${response.status}.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=upload-component-folder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-component-folder.js","sourceRoot":"","sources":["../src/upload-component-folder.ts"],"names":[],"mappings":";;;;;;AAAA,gEAAkE;AAClE,wDAAoD;AACpD,2DAA8B;AAC9B,gDAAwB;AACxB,kDAAwE;AACxE,0DAA8B;AAEvB,KAAK,UAAU,qBAAqB,CAAC,UAAkB,EAAE,6BAAqC;IACnG,MAAM,MAAM,GAAG,cAAI,CAAC,OAAO,CAAC,MAAM,kBAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IAE7D,IAAI;QACF,MAAM,aAAa,GAAG,eAAK,CAAC,MAAM,CAAC;YACjC,OAAO,EAAE,6BAA6B;SACvC,CAAC,CAAC;QAEH,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAChC,MAAM,GAAG,GAAG,MAAM,IAAA,4BAAY,EAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAEnD,MAAM,aAAa,GAAG,MAAM,cAAc,CAAM,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;QAExF,OAAO,CAAC,GAAG,CAAC,kBAAkB,aAAa,CAAC,EAAE,uBAAuB,CAAC,CAAC;QACvE,MAAM,IAAA,8BAAU,EAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAErC,MAAM,oCAAoC,CAAC,aAAa,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QAE5E,OAAO,CAAC,GAAG,CAAC,kBAAkB,aAAa,CAAC,EAAE,qCAAqC,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,MAAM,cAAc,CAAM,aAAa,CAAC,IAAI,CAAC,yBAAyB,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAE1G,MAAM,kBAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvD,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,EAAE;YAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB,aAAa,CAAC,EAAE,YAAY,mBAAK,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;YACpF,OAAO,CAAC,GAAG,CAAC,sBAAsB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;SACxD;aAAM;YACL,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,mBAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;SAClE;KACF;IAAC,OAAO,CAAC,EAAE;QACV,MAAM,kBAAG,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEvD,IAAI,CAAC,YAAY,KAAK,EAAE;YACtB,OAAO,CAAC,KAAK,CAAC,mBAAK,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;SACrC;aAAM;YACL,MAAM,CAAC,CAAC;SACT;KACF;AACH,CAAC;AAtCD,sDAsCC;AAED,KAAK,UAAU,oCAAoC,CAAC,EAAU,EAAE,aAA4B;IAC1F,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,cAAc,CAAa,aAAa,CAAC,GAAG,CAAC,0BAA0B,GAAG,EAAE,CAAC,CAAC,CAAC;IAElG,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,GAAG,EAAE,CACnB,IAAI,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACxB,IAAI,GAAG,CAAC,MAAM,IAAI,SAAS,EAAE;gBAC3B,OAAO,EAAE,CAAC;aACX;iBAAM,IAAI,GAAG,CAAC,MAAM,IAAI,SAAS,EAAE;gBAClC,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAC;aACzD;iBAAM,IAAI,GAAG,CAAC,MAAM,IAAI,OAAO,EAAE;gBAChC,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;aAC9C;iBAAM;gBACL,UAAU,CAAC,KAAK,IAAI,EAAE;oBACpB,MAAM,OAAO,EAAE,CAAC;gBAClB,CAAC,EAAE,IAAI,CAAC,CAAC;aACV;QACH,CAAC,CAAC,CAAC;QAEL,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,cAAc,CAAI,aAAwC;IACvE,MAAM,MAAM,GAAG,aAAa;SACzB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;SACjC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QACf,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;IACL,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB;IACzC,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAC3B,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,KAAK,IAAI,QAAQ,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE;QAClE,OAAO,KAAK,CAAC;KACd;IACD,MAAM,OAAO,GAAI,QAAQ,CAAC,IAAY,CAAC,OAAO,CAAC;IAC/C,IAAI,OAAO,EAAE;QACX,OAAO,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,cAAc,OAAO,EAAE,CAAC,CAAC;KACtF;SAAM;QACL,OAAO,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;KAClE;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@squiz/component-cli-lib",
|
|
3
|
-
"version": "1.2.1-alpha.
|
|
3
|
+
"version": "1.2.1-alpha.70",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "node ./lib/index.js",
|
|
8
8
|
"compile": "tsc",
|
|
9
9
|
"lint": "eslint ./src --ext .ts",
|
|
10
|
-
"test": "jest",
|
|
10
|
+
"test": "jest -c jest.config.ts",
|
|
11
11
|
"test:integration": "jest -c jest.integration.config.ts",
|
|
12
12
|
"clean": "rimraf \".tsbuildinfo\" \"./lib\""
|
|
13
13
|
},
|
|
@@ -16,25 +16,25 @@
|
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/cli-color": "2.0.2",
|
|
18
18
|
"@types/express": "4.17.13",
|
|
19
|
-
"@types/jest": "28.1.
|
|
19
|
+
"@types/jest": "28.1.6",
|
|
20
20
|
"@types/node": "17.0.27",
|
|
21
21
|
"@types/supertest": "2.0.12",
|
|
22
22
|
"dotenv": "16.0.1",
|
|
23
23
|
"eslint": "8.18.0",
|
|
24
|
-
"jest": "28.1.
|
|
24
|
+
"jest": "28.1.3",
|
|
25
25
|
"ts-jest": "28.0.5",
|
|
26
26
|
"ts-loader": "9.3.1",
|
|
27
27
|
"ts-node": "10.8.1",
|
|
28
28
|
"typescript": "4.7.4"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@squiz/component-lib": "^1.2.1-alpha.
|
|
32
|
-
"@squiz/render-runtime-lib": "^1.2.1-alpha.
|
|
33
|
-
"@squiz/virus-scanner-lib": "^1.2.1-alpha.
|
|
31
|
+
"@squiz/component-lib": "^1.2.1-alpha.70",
|
|
32
|
+
"@squiz/render-runtime-lib": "^1.2.1-alpha.70",
|
|
33
|
+
"@squiz/virus-scanner-lib": "^1.2.1-alpha.70",
|
|
34
34
|
"archiver": "5.3.1",
|
|
35
35
|
"axios": "0.27.2",
|
|
36
36
|
"cli-color": "^2.0.2",
|
|
37
37
|
"supertest": "^6.2.3"
|
|
38
38
|
},
|
|
39
|
-
"gitHead": "
|
|
39
|
+
"gitHead": "f8d9c77cc30b5b66c4c3c66ac9cf8e30b5c27373"
|
|
40
40
|
}
|
|
@@ -1,65 +1,105 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import fsp from 'fs/promises';
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { routeTests, TestHelpers } from '@squiz/render-runtime-lib';
|
|
4
5
|
import { startDevelopmentRender } from './component-dev';
|
|
6
|
+
import supertest from 'supertest';
|
|
5
7
|
|
|
6
8
|
jest.setTimeout(20_000);
|
|
9
|
+
|
|
7
10
|
describe('component-dev', () => {
|
|
8
|
-
describe('folder
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
describe('production style folder structure', () => {
|
|
12
|
+
let doStopStack: Awaited<ReturnType<typeof startDevelopmentRender>>;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
doStopStack = await startDevelopmentRender(path.join(__dirname, '../../../test-components'), { port: 0 });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
await doStopStack();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should find the component', async () => {
|
|
23
|
+
const testServer = TestHelpers.getTestServer();
|
|
24
|
+
const response = await testServer.get(`/r/set/test-component/1.0.0?something=hello`);
|
|
25
|
+
expect(response.text).toEqual('<h1>hello</h1>');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('local developer style folders', () => {
|
|
30
|
+
let doStopStack: Awaited<ReturnType<typeof startDevelopmentRender>>;
|
|
31
|
+
let version: string;
|
|
32
|
+
let componentName: string;
|
|
33
|
+
let fixtureDirectory: string;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
const {
|
|
37
|
+
fixtureDirectory: ceratedFixtureDirectory,
|
|
38
|
+
version: createdVersion,
|
|
39
|
+
componentName: createdName,
|
|
40
|
+
} = await TestHelpers.ComponentFixture.setupFullComponentDirectory('<h1>Hello World</h1>');
|
|
41
|
+
|
|
42
|
+
version = createdVersion;
|
|
43
|
+
fixtureDirectory = ceratedFixtureDirectory;
|
|
44
|
+
componentName = createdName;
|
|
45
|
+
|
|
46
|
+
doStopStack = await startDevelopmentRender(fixtureDirectory, { port: 0 });
|
|
11
47
|
});
|
|
12
48
|
|
|
13
|
-
|
|
14
|
-
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
await doStopStack();
|
|
51
|
+
await fsp.rm(path.join(fixtureDirectory, componentName), { force: true, recursive: true });
|
|
15
52
|
});
|
|
16
53
|
|
|
17
|
-
it('
|
|
18
|
-
const
|
|
19
|
-
|
|
54
|
+
it('should handle recursing folders to find the component', async () => {
|
|
55
|
+
const testServer = TestHelpers.getTestServer();
|
|
56
|
+
const response = await testServer.get(`/r/set/${componentName}/${version}/`);
|
|
57
|
+
expect(response.text).toEqual('<h1>Hello World</h1>');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
20
60
|
|
|
21
|
-
|
|
22
|
-
|
|
61
|
+
describe('accessing local dev routes', () => {
|
|
62
|
+
let stopServer: Awaited<ReturnType<typeof startDevelopmentRender>>;
|
|
63
|
+
const port = 3006;
|
|
64
|
+
const url = `http://localhost:${port}`;
|
|
65
|
+
const request = supertest(url);
|
|
23
66
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
67
|
+
beforeAll(async () => {
|
|
68
|
+
const compDir = path.join(__dirname, '../', '../../test-components');
|
|
69
|
+
|
|
70
|
+
stopServer = await startDevelopmentRender(compDir, {
|
|
71
|
+
port: port,
|
|
72
|
+
});
|
|
31
73
|
});
|
|
32
74
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await stopServer();
|
|
77
|
+
});
|
|
36
78
|
|
|
37
|
-
|
|
38
|
-
|
|
79
|
+
describe('definition routes', () => {
|
|
80
|
+
routeTests.definition(url, url);
|
|
81
|
+
});
|
|
39
82
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const response = await testServer.get(`/r/set/${componentName}/${version}/`);
|
|
43
|
-
expect(response.text).toEqual('<h1>Hello World</h1>');
|
|
44
|
-
} finally {
|
|
45
|
-
await doStopStack();
|
|
46
|
-
}
|
|
83
|
+
describe('static routes', () => {
|
|
84
|
+
routeTests.static(url);
|
|
47
85
|
});
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
87
|
+
describe('render routes', () => {
|
|
88
|
+
routeTests.render(url, url);
|
|
89
|
+
});
|
|
52
90
|
|
|
53
|
-
|
|
54
|
-
|
|
91
|
+
describe('GET /r/set/test-component/1.0.1', () => {
|
|
92
|
+
//
|
|
93
|
+
// this scenario is different to the 'production' tests
|
|
94
|
+
// this is should 500 in prod and 404 locally
|
|
95
|
+
it('should fail validation if trying to render a component with an invalid schema', async () => {
|
|
96
|
+
const response = await request.get('/r/set/test-component/1.0.1?something=not-used');
|
|
55
97
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
await doStopStack();
|
|
62
|
-
}
|
|
98
|
+
expect(response.statusCode).toEqual(404);
|
|
99
|
+
expect(response.body).toEqual({
|
|
100
|
+
message: 'manifest could not be found',
|
|
101
|
+
});
|
|
102
|
+
});
|
|
63
103
|
});
|
|
64
104
|
});
|
|
65
105
|
});
|
package/src/component-dev.ts
CHANGED
|
@@ -16,12 +16,14 @@ export async function startDevelopmentRender(componentPath: string, options: { p
|
|
|
16
16
|
webserver: {
|
|
17
17
|
port: options.port,
|
|
18
18
|
rootUrl: `http://localhost:${options.port}`,
|
|
19
|
+
shouldRunMigrations: false,
|
|
19
20
|
},
|
|
20
21
|
componentRunner: {
|
|
21
22
|
// Considering the component path will be passed in from CLI
|
|
22
23
|
// We need to generate the path from the calling directory
|
|
23
24
|
dataMountPoint: path.resolve(process.cwd(), componentPath),
|
|
24
25
|
doDevReload: true,
|
|
26
|
+
|
|
25
27
|
workerTimeout: 20_000,
|
|
26
28
|
},
|
|
27
29
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,92 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { zipDirectory } from '@squiz/component-lib';
|
|
3
|
-
import fsp from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import axios, { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
|
|
6
|
-
import color from 'cli-color';
|
|
7
|
-
|
|
1
|
+
export { uploadComponentFolder } from './upload-component-folder';
|
|
8
2
|
export { startDevelopmentRender } from './component-dev';
|
|
9
|
-
|
|
10
|
-
export async function uploadComponentFolder(folderPath: string, componentServiceManagementUrl: string): Promise<void> {
|
|
11
|
-
const tmpDir = path.resolve(await fsp.mkdtemp('cmp-upload'));
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const axiosInstance = axios.create({
|
|
15
|
-
baseURL: componentServiceManagementUrl,
|
|
16
|
-
});
|
|
17
|
-
console.log('Initial scanning');
|
|
18
|
-
const zip = await zipDirectory(folderPath, tmpDir);
|
|
19
|
-
|
|
20
|
-
const initialUpload = await handleResponse<any>(axiosInstance.post('upload-component'));
|
|
21
|
-
|
|
22
|
-
console.log(`deployment id: ${initialUpload.id} status: transferring`);
|
|
23
|
-
await uploadFile(initialUpload, zip);
|
|
24
|
-
|
|
25
|
-
await watchAndWaitForUploadAndScanComplete(initialUpload.id, axiosInstance);
|
|
26
|
-
|
|
27
|
-
console.log(`deployment id: ${initialUpload.id} status: deploying component folder`);
|
|
28
|
-
const result = await handleResponse<any>(axiosInstance.post(`upload-component/next/${initialUpload.id}`));
|
|
29
|
-
|
|
30
|
-
await fsp.rm(tmpDir, { force: true, recursive: true });
|
|
31
|
-
|
|
32
|
-
if (result.status === 'successful') {
|
|
33
|
-
console.log(`deployment id: ${initialUpload.id} status: ${color.green('success')}`);
|
|
34
|
-
console.log(`uploaded location: ${result.accessLink}`);
|
|
35
|
-
} else {
|
|
36
|
-
console.error('failed for an unknown reason', color.red(result));
|
|
37
|
-
}
|
|
38
|
-
} catch (e) {
|
|
39
|
-
await fsp.rm(tmpDir, { force: true, recursive: true });
|
|
40
|
-
|
|
41
|
-
if (e instanceof Error) {
|
|
42
|
-
console.error(color.red(e.message));
|
|
43
|
-
} else {
|
|
44
|
-
throw e;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function watchAndWaitForUploadAndScanComplete(id: string, axiosInstance: AxiosInstance): Promise<void> {
|
|
50
|
-
const poll = () => handleResponse<ScanStatus>(axiosInstance.get('upload-component/status/' + id));
|
|
51
|
-
|
|
52
|
-
return new Promise((resolve, reject) => {
|
|
53
|
-
const recurse = () =>
|
|
54
|
-
poll().then(async (req) => {
|
|
55
|
-
if (req.status == 'Success') {
|
|
56
|
-
resolve();
|
|
57
|
-
} else if (req.status == 'Flagged') {
|
|
58
|
-
reject(new Error('upload has been flagged as a virus'));
|
|
59
|
-
} else if (req.status == 'Error') {
|
|
60
|
-
reject(new Error('there has been an error'));
|
|
61
|
-
} else {
|
|
62
|
-
setTimeout(async () => {
|
|
63
|
-
await recurse();
|
|
64
|
-
}, 1000);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
recurse();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function handleResponse<T>(axiosInstance: Promise<AxiosResponse<T>>): Promise<T> {
|
|
73
|
-
const result = axiosInstance
|
|
74
|
-
.then((response) => response.data)
|
|
75
|
-
.catch((error) => {
|
|
76
|
-
throw handleAxiosError(error);
|
|
77
|
-
});
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function handleAxiosError(error: AxiosError): Error {
|
|
82
|
-
const { response } = error;
|
|
83
|
-
if (!response || (typeof error == 'object' && !error.isAxiosError)) {
|
|
84
|
-
return error;
|
|
85
|
-
}
|
|
86
|
-
const message = (response.data as any).message;
|
|
87
|
-
if (message) {
|
|
88
|
-
return new Error(`Unexpected response code ${response.status}. message: ${message}`);
|
|
89
|
-
} else {
|
|
90
|
-
return new Error(`Unexpected response code ${response.status}.`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://localhost:3000/schemas/v1.json#",
|
|
3
|
+
|
|
4
|
+
"name": "big-package",
|
|
5
|
+
"version": "1.0.2",
|
|
6
|
+
"main-function": "render-json",
|
|
7
|
+
"functions": [
|
|
8
|
+
{
|
|
9
|
+
"name": "render-json",
|
|
10
|
+
"entry": "render-json.js",
|
|
11
|
+
"input": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"properties": {
|
|
14
|
+
"something": {
|
|
15
|
+
"type": "string"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": ["something"]
|
|
19
|
+
},
|
|
20
|
+
"output": {
|
|
21
|
+
"response-type": "json",
|
|
22
|
+
"definition": {
|
|
23
|
+
"properties": {
|
|
24
|
+
"my-prop": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"required": ["my-prop"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -2,6 +2,8 @@ import axios from 'axios';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import configVars from '../config';
|
|
5
|
+
import fsp from 'fs/promises';
|
|
6
|
+
import { randomBytes } from 'crypto';
|
|
5
7
|
|
|
6
8
|
export const managementService = axios.create({
|
|
7
9
|
baseURL: configVars.managementServiceUrl,
|
|
@@ -23,3 +25,11 @@ export function getTestComponents(): string[] {
|
|
|
23
25
|
}
|
|
24
26
|
return componets;
|
|
25
27
|
}
|
|
28
|
+
export async function createFile(filePath: string, sizeInMB: number) {
|
|
29
|
+
const content = randomBytes(sizeInMB * 1000000);
|
|
30
|
+
await fsp.writeFile(`${filePath}`, content);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function removeFile(filePath: string) {
|
|
34
|
+
fsp.unlink(filePath);
|
|
35
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
jest.setTimeout(
|
|
1
|
+
jest.setTimeout(60_000);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { uploadComponentFolder } from '../index';
|
|
2
|
-
import { renderService, managementService, getTestComponents } from './helper';
|
|
2
|
+
import { renderService, managementService, getTestComponents, createFile, removeFile } from './helper';
|
|
3
3
|
import configVars from '../config';
|
|
4
4
|
import color from 'cli-color';
|
|
5
5
|
import path from 'path';
|
|
@@ -13,11 +13,15 @@ const orgConsoleError = console.error;
|
|
|
13
13
|
afterAll(async () => {
|
|
14
14
|
// clean up the component added by the test
|
|
15
15
|
getTestComponents().forEach(async (componentName) => {
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
await managementService.delete(`/component/${componentName}`);
|
|
18
|
+
} catch {
|
|
19
|
+
// no op
|
|
20
|
+
}
|
|
17
21
|
});
|
|
18
22
|
});
|
|
19
23
|
|
|
20
|
-
describe('
|
|
24
|
+
describe('Test isolated test cases', () => {
|
|
21
25
|
beforeEach(() => {
|
|
22
26
|
console.error = mockConsoleError;
|
|
23
27
|
console.log = mockConsoleLog;
|
|
@@ -27,32 +31,56 @@ describe('Deploy basic component having a static file', () => {
|
|
|
27
31
|
console.error = orgConsoleError;
|
|
28
32
|
});
|
|
29
33
|
|
|
34
|
+
it('Should fail uploading the component that has size more than 100MB', async () => {
|
|
35
|
+
const componentPath = path.join(__dirname, '/__components__/big-package');
|
|
36
|
+
const filePath = `${componentPath}/105mb-file`;
|
|
37
|
+
await createFile(filePath, 105); // Higher limit has been used because compression reduces the size if you use closer to 100MB
|
|
38
|
+
await uploadComponentFolder(componentPath, configVars.managementServiceUrl);
|
|
39
|
+
expect(mockConsoleError.mock.calls[0][0]).toEqual(
|
|
40
|
+
color.red(['Request body larger than maxBodyLength limit'].join('')),
|
|
41
|
+
);
|
|
42
|
+
removeFile(filePath);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Deploy basic component having a static file', () => {
|
|
30
47
|
// component to deploy for this test
|
|
31
48
|
const componentPath = path.join(__dirname, '/__components__/cmp-static-file-test');
|
|
32
|
-
|
|
49
|
+
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
try {
|
|
52
|
+
await managementService.delete(`/component/cmp-static-file-test`);
|
|
53
|
+
} catch {
|
|
54
|
+
// no op
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
console.error = mockConsoleError;
|
|
60
|
+
console.log = mockConsoleLog;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
console.error = orgConsoleError;
|
|
65
|
+
});
|
|
33
66
|
|
|
34
67
|
it('Should upload the component and return a valid url to preview', async () => {
|
|
35
68
|
mockConsoleLog.mockClear();
|
|
36
|
-
|
|
37
69
|
await uploadComponentFolder(componentPath, configVars.managementServiceUrl);
|
|
38
70
|
|
|
39
|
-
url = /uploaded location: (.*)/.exec(mockConsoleLog.mock.lastCall)?.[1] || '';
|
|
40
|
-
|
|
71
|
+
const url = /uploaded location: (.*)/.exec(mockConsoleLog.mock.lastCall)?.[1] || '';
|
|
41
72
|
const uploadedComponent = '<a href="/r/set/cmp-static-file-test/1.0.0">1.0.0</a>';
|
|
42
|
-
const get = await supertest(url).get('');
|
|
73
|
+
const get = await supertest(url).get('/');
|
|
74
|
+
|
|
43
75
|
expect(get.status).toEqual(200);
|
|
44
76
|
expect((get as any)?.res?.text).toContain(uploadedComponent);
|
|
45
77
|
});
|
|
46
78
|
|
|
47
79
|
it('Should fail upload the component with same version', async () => {
|
|
80
|
+
mockConsoleError.mockClear();
|
|
48
81
|
await uploadComponentFolder(componentPath, configVars.managementServiceUrl);
|
|
49
82
|
expect(mockConsoleError.mock.calls[0][0]).toEqual(
|
|
50
|
-
color.red(
|
|
51
|
-
[
|
|
52
|
-
'Unexpected response code 400. message: ',
|
|
53
|
-
'cmp-static-file-test version 1.0.0 already exists and cannot be uploaded',
|
|
54
|
-
].join(''),
|
|
55
|
-
),
|
|
83
|
+
color.red('Unexpected response code 400. message: Component cmp-static-file-test 1.0.0 already exists'),
|
|
56
84
|
);
|
|
57
85
|
});
|
|
58
86
|
|
package/src/test.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import configVars from './config';
|
|
3
|
-
import path from 'path';
|
|
1
|
+
import { startDevelopmentRender } from './component-dev';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
uploadComponentFolder(dir, componentManagementURL);
|
|
3
|
+
startDevelopmentRender('/Users/wfagerstrom/Code/component-service/test-components', { port: 3000 }).then(() => {
|
|
4
|
+
console.log('starting');
|
|
5
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { ScanStatus, uploadFile } from '@squiz/virus-scanner-lib';
|
|
2
|
+
import { zipDirectory } from '@squiz/component-lib';
|
|
3
|
+
import fsp from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import axios, { AxiosResponse, AxiosError, AxiosInstance } from 'axios';
|
|
6
|
+
import color from 'cli-color';
|
|
7
|
+
|
|
8
|
+
export async function uploadComponentFolder(folderPath: string, componentServiceManagementUrl: string): Promise<void> {
|
|
9
|
+
const tmpDir = path.resolve(await fsp.mkdtemp('cmp-upload'));
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const axiosInstance = axios.create({
|
|
13
|
+
baseURL: componentServiceManagementUrl,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
console.log('Initial scanning');
|
|
17
|
+
const zip = await zipDirectory(folderPath, tmpDir);
|
|
18
|
+
|
|
19
|
+
const initialUpload = await handleResponse<any>(axiosInstance.post('upload-component'));
|
|
20
|
+
|
|
21
|
+
console.log(`deployment id: ${initialUpload.id} status: transferring`);
|
|
22
|
+
await uploadFile(initialUpload, zip);
|
|
23
|
+
|
|
24
|
+
await watchAndWaitForUploadAndScanComplete(initialUpload.id, axiosInstance);
|
|
25
|
+
|
|
26
|
+
console.log(`deployment id: ${initialUpload.id} status: deploying component folder`);
|
|
27
|
+
const result = await handleResponse<any>(axiosInstance.post(`upload-component/next/${initialUpload.id}`));
|
|
28
|
+
|
|
29
|
+
await fsp.rm(tmpDir, { force: true, recursive: true });
|
|
30
|
+
|
|
31
|
+
if (result.status === 'successful') {
|
|
32
|
+
console.log(`deployment id: ${initialUpload.id} status: ${color.green('success')}`);
|
|
33
|
+
console.log(`uploaded location: ${result.accessLink}`);
|
|
34
|
+
} else {
|
|
35
|
+
console.error('failed for an unknown reason', color.red(result));
|
|
36
|
+
}
|
|
37
|
+
} catch (e) {
|
|
38
|
+
await fsp.rm(tmpDir, { force: true, recursive: true });
|
|
39
|
+
|
|
40
|
+
if (e instanceof Error) {
|
|
41
|
+
console.error(color.red(e.message));
|
|
42
|
+
} else {
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function watchAndWaitForUploadAndScanComplete(id: string, axiosInstance: AxiosInstance): Promise<void> {
|
|
49
|
+
const poll = () => handleResponse<ScanStatus>(axiosInstance.get('upload-component/status/' + id));
|
|
50
|
+
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const recurse = () =>
|
|
53
|
+
poll().then(async (req) => {
|
|
54
|
+
if (req.status == 'Success') {
|
|
55
|
+
resolve();
|
|
56
|
+
} else if (req.status == 'Flagged') {
|
|
57
|
+
reject(new Error('upload has been flagged as a virus'));
|
|
58
|
+
} else if (req.status == 'Error') {
|
|
59
|
+
reject(new Error('there has been an error'));
|
|
60
|
+
} else {
|
|
61
|
+
setTimeout(async () => {
|
|
62
|
+
await recurse();
|
|
63
|
+
}, 1000);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
recurse();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function handleResponse<T>(axiosInstance: Promise<AxiosResponse<T>>): Promise<T> {
|
|
72
|
+
const result = axiosInstance
|
|
73
|
+
.then((response) => response.data)
|
|
74
|
+
.catch((error) => {
|
|
75
|
+
throw handleAxiosError(error);
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleAxiosError(error: AxiosError): Error {
|
|
81
|
+
const { response } = error;
|
|
82
|
+
if (!response || (typeof error == 'object' && !error.isAxiosError)) {
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
const message = (response.data as any).message;
|
|
86
|
+
if (message) {
|
|
87
|
+
return new Error(`Unexpected response code ${response.status}. message: ${message}`);
|
|
88
|
+
} else {
|
|
89
|
+
return new Error(`Unexpected response code ${response.status}.`);
|
|
90
|
+
}
|
|
91
|
+
}
|