@hubspot/ui-extensions-dev-server 0.1.0 → 0.2.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/build.js +4 -0
- package/constants.js +4 -0
- package/dev.js +47 -35
- package/index.js +2 -1
- package/package.json +5 -4
- package/plugins/devBuildPlugin.js +98 -0
- package/plugins/manifestPlugin.js +9 -12
- package/run.js +16 -6
- package/server.js +42 -101
- package/tests/runTests.js +30 -12
- package/tests/testBuild.js +45 -56
- package/tests/testDevServer.js +191 -94
- package/tests/utils.js +76 -0
package/build.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { build } = require('vite');
|
|
2
2
|
const { ROLLUP_OPTIONS } = require('./constants');
|
|
3
|
+
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
3
4
|
|
|
4
5
|
async function buildAllExtensions(config, outputDir) {
|
|
5
6
|
const extensionKeys = Object.keys(config);
|
|
@@ -11,6 +12,9 @@ async function buildAllExtensions(config, outputDir) {
|
|
|
11
12
|
outputFileName: data?.output,
|
|
12
13
|
outputDir,
|
|
13
14
|
emptyOutDir: i === 0,
|
|
15
|
+
plugins: {
|
|
16
|
+
rollup: [manifestPlugin({ output: data?.output })],
|
|
17
|
+
},
|
|
14
18
|
});
|
|
15
19
|
}
|
|
16
20
|
}
|
package/constants.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const VITE_DEFAULT_PORT = 5173;
|
|
2
|
+
const WEBSOCKET_PORT = 5174;
|
|
2
3
|
const MAIN_APP_CONFIG = 'app.json';
|
|
3
4
|
const PROJECT_CONFIG = 'hsproject.json';
|
|
4
5
|
const OUTPUT_DIR = 'dist';
|
|
6
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
5
7
|
|
|
6
8
|
const ROLLUP_OPTIONS = {
|
|
7
9
|
// Deps to exclude from the bundle
|
|
@@ -25,6 +27,8 @@ module.exports = {
|
|
|
25
27
|
MAIN_APP_CONFIG,
|
|
26
28
|
PROJECT_CONFIG,
|
|
27
29
|
OUTPUT_DIR,
|
|
30
|
+
MANIFEST_FILE,
|
|
31
|
+
WEBSOCKET_PORT,
|
|
28
32
|
EXTENSIONS_MESSAGE_VERSION,
|
|
29
33
|
WEBSOCKET_MESSAGE_VERSION,
|
|
30
34
|
};
|
package/dev.js
CHANGED
|
@@ -1,50 +1,62 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { createServer } = require('vite');
|
|
2
2
|
const { getExtensionConfig } = require('./config');
|
|
3
|
-
const { ROLLUP_OPTIONS } = require('./constants');
|
|
4
3
|
const startDevServer = require('./server');
|
|
5
|
-
const
|
|
4
|
+
const devBuildPlugin = require('./plugins/devBuildPlugin');
|
|
6
5
|
|
|
7
|
-
async function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
'package-lock.json',
|
|
21
|
-
'app.json',
|
|
22
|
-
],
|
|
6
|
+
async function _createViteDevServer(
|
|
7
|
+
outputDir,
|
|
8
|
+
extensionConfig,
|
|
9
|
+
websocketPort,
|
|
10
|
+
baseMessage
|
|
11
|
+
) {
|
|
12
|
+
return await createServer({
|
|
13
|
+
appType: 'custom',
|
|
14
|
+
mode: 'development',
|
|
15
|
+
server: {
|
|
16
|
+
middlewareMode: true,
|
|
17
|
+
hmr: {
|
|
18
|
+
port: websocketPort,
|
|
23
19
|
},
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
name: extensionConfig?.output,
|
|
27
|
-
formats: ['iife'],
|
|
28
|
-
fileName: () => extensionConfig?.output,
|
|
20
|
+
watch: {
|
|
21
|
+
ignored: ['**/src/app/extensions/dist/**/*'],
|
|
29
22
|
},
|
|
23
|
+
},
|
|
24
|
+
build: {
|
|
30
25
|
rollupOptions: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
...(ROLLUP_OPTIONS.plugins || []),
|
|
34
|
-
manifestPlugin({ minify: false, extensionConfig }),
|
|
35
|
-
],
|
|
26
|
+
input: extensionConfig?.file,
|
|
27
|
+
output: extensionConfig.output,
|
|
36
28
|
},
|
|
37
|
-
outDir: outputDir,
|
|
38
|
-
emptyOutDir: true,
|
|
39
|
-
minify: false,
|
|
40
29
|
},
|
|
30
|
+
plugins: [devBuildPlugin({ extensionConfig, outputDir, baseMessage })],
|
|
41
31
|
});
|
|
42
32
|
}
|
|
43
33
|
|
|
44
|
-
async function startDevMode(
|
|
34
|
+
async function startDevMode(
|
|
35
|
+
config,
|
|
36
|
+
outputDir,
|
|
37
|
+
expressPort,
|
|
38
|
+
webSocketPort,
|
|
39
|
+
extension
|
|
40
|
+
) {
|
|
45
41
|
const extensionConfig = await getExtensionConfig(config, extension);
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
const baseMessage = Object.freeze({
|
|
43
|
+
appName: extensionConfig?.appName,
|
|
44
|
+
title: extensionConfig?.name,
|
|
45
|
+
callback: `http://hslocal.net:${expressPort}/${extensionConfig?.output}`,
|
|
46
|
+
});
|
|
47
|
+
const viteDevServer = await _createViteDevServer(
|
|
48
|
+
outputDir,
|
|
49
|
+
extensionConfig,
|
|
50
|
+
webSocketPort,
|
|
51
|
+
baseMessage
|
|
52
|
+
);
|
|
53
|
+
startDevServer(
|
|
54
|
+
outputDir,
|
|
55
|
+
expressPort,
|
|
56
|
+
webSocketPort,
|
|
57
|
+
baseMessage,
|
|
58
|
+
viteDevServer
|
|
59
|
+
);
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
module.exports = {
|
package/index.js
CHANGED
|
@@ -16,12 +16,13 @@ async function remoteBuild(root, entryPoint, outputDir) {
|
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
const output = getUrlSafeFileName(entryPoint);
|
|
19
20
|
await buildSingleExtension({
|
|
20
21
|
file: entryPoint,
|
|
21
22
|
outputFileName: getUrlSafeFileName(entryPoint),
|
|
22
23
|
outputDir: outputDir || OUTPUT_DIR,
|
|
23
24
|
plugins: {
|
|
24
|
-
rollup: [manifestPlugin({ minify: true })],
|
|
25
|
+
rollup: [manifestPlugin({ minify: true, output })],
|
|
25
26
|
},
|
|
26
27
|
minify: true,
|
|
27
28
|
root,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,13 +22,14 @@
|
|
|
22
22
|
"tests/runTests.js",
|
|
23
23
|
"tests/testBuild.js",
|
|
24
24
|
"tests/testDevServer.js",
|
|
25
|
-
"
|
|
25
|
+
"tests/utils.js",
|
|
26
|
+
"plugins/*",
|
|
26
27
|
"index.js",
|
|
27
28
|
"server.js"
|
|
28
29
|
],
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"
|
|
32
|
+
"axios": "^1.4.0",
|
|
32
33
|
"command-line-args": "^5.2.1",
|
|
33
34
|
"command-line-usage": "^7.0.1",
|
|
34
35
|
"console-log-colors": "^0.4.0",
|
|
@@ -58,5 +59,5 @@
|
|
|
58
59
|
"optional": true
|
|
59
60
|
}
|
|
60
61
|
},
|
|
61
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "ab7ad192e9d98163d2613d77f68677e3b48898aa"
|
|
62
63
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { ROLLUP_OPTIONS, WEBSOCKET_MESSAGE_VERSION } = require('../constants');
|
|
2
|
+
const { build } = require('vite');
|
|
3
|
+
const manifestPlugin = require('./manifestPlugin');
|
|
4
|
+
const logger = require('../logger');
|
|
5
|
+
|
|
6
|
+
function devBuildPlugin(options = {}) {
|
|
7
|
+
const { extensionConfig, outputDir, baseMessage } = options;
|
|
8
|
+
const versionedBaseMessage = {
|
|
9
|
+
...baseMessage,
|
|
10
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const devBuild = async () => {
|
|
14
|
+
try {
|
|
15
|
+
await build({
|
|
16
|
+
mode: 'development',
|
|
17
|
+
define: {
|
|
18
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
19
|
+
process.env.NODE_ENV || 'development'
|
|
20
|
+
),
|
|
21
|
+
},
|
|
22
|
+
build: {
|
|
23
|
+
lib: {
|
|
24
|
+
entry: extensionConfig?.file,
|
|
25
|
+
name: extensionConfig?.output,
|
|
26
|
+
formats: ['iife'],
|
|
27
|
+
fileName: () => extensionConfig?.output,
|
|
28
|
+
},
|
|
29
|
+
rollupOptions: {
|
|
30
|
+
...ROLLUP_OPTIONS,
|
|
31
|
+
plugins: [
|
|
32
|
+
...(ROLLUP_OPTIONS.plugins || []),
|
|
33
|
+
manifestPlugin({
|
|
34
|
+
minify: false,
|
|
35
|
+
output: extensionConfig?.output,
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
output: {
|
|
39
|
+
...ROLLUP_OPTIONS.output,
|
|
40
|
+
sourcemap: 'inline',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
outDir: outputDir,
|
|
44
|
+
emptyOutDir: true,
|
|
45
|
+
minify: false,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
} catch (e) {
|
|
49
|
+
logger.error(e.message);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let localServer;
|
|
54
|
+
return {
|
|
55
|
+
name: 'ui-extensibility-dev-build-plugin',
|
|
56
|
+
enforce: 'pre',
|
|
57
|
+
configureServer: async server => {
|
|
58
|
+
// Store a reference to the server to be used in hooks that don't get the server injected
|
|
59
|
+
// See https://vitejs.dev/guide/api-plugin.html#configureserver for information on this pattern
|
|
60
|
+
localServer = server;
|
|
61
|
+
localServer.ws.on('connection', () => {
|
|
62
|
+
logger.info('Browser connected and listening for bundle updates');
|
|
63
|
+
localServer.ws.send({
|
|
64
|
+
...versionedBaseMessage,
|
|
65
|
+
event: 'start',
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
await devBuild();
|
|
69
|
+
},
|
|
70
|
+
handleHotUpdate: async ({ server }) => {
|
|
71
|
+
await devBuild();
|
|
72
|
+
if (server.ws.clients.size) {
|
|
73
|
+
logger.info('Bundle updated, notifying connected browsers');
|
|
74
|
+
} else {
|
|
75
|
+
logger.warn('Bundle updated, no browsers connected to notify');
|
|
76
|
+
}
|
|
77
|
+
server.ws.send({
|
|
78
|
+
...versionedBaseMessage,
|
|
79
|
+
event: 'update',
|
|
80
|
+
});
|
|
81
|
+
return [];
|
|
82
|
+
},
|
|
83
|
+
buildEnd(error) {
|
|
84
|
+
if (error) {
|
|
85
|
+
logger.error(error);
|
|
86
|
+
}
|
|
87
|
+
logger.warn('Sending shutdown message to connected browsers');
|
|
88
|
+
if (localServer && localServer.ws) {
|
|
89
|
+
localServer.ws.send({
|
|
90
|
+
...versionedBaseMessage,
|
|
91
|
+
event: 'shutdown',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = devBuildPlugin;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const { readFileSync } = require('fs');
|
|
2
2
|
const { normalize } = require('path');
|
|
3
|
+
const { MANIFEST_FILE } = require('../constants');
|
|
3
4
|
const logger = require('../logger');
|
|
5
|
+
const path = require('path');
|
|
4
6
|
|
|
5
|
-
const DEFAULT_MANIFEST_NAME = 'manifest.json';
|
|
6
7
|
const PACKAGE_LOCK_FILE = 'package-lock.json';
|
|
7
8
|
const PACKAGE_FILE = 'package.json';
|
|
8
9
|
const EXTENSIONS_PATH = 'src/app/extensions/';
|
|
@@ -12,19 +13,16 @@ function plugin(options = {}) {
|
|
|
12
13
|
name: 'ui-extensions-manifest-generation-plugin',
|
|
13
14
|
enforce: 'post', // run after default rollup plugins
|
|
14
15
|
generateBundle(_rollupOptions, bundle) {
|
|
15
|
-
const {
|
|
16
|
-
output = DEFAULT_MANIFEST_NAME,
|
|
17
|
-
minify = false,
|
|
18
|
-
extensionConfig,
|
|
19
|
-
} = options;
|
|
16
|
+
const { output, minify = false } = options;
|
|
20
17
|
try {
|
|
21
|
-
const
|
|
18
|
+
const filename = path.parse(output).name;
|
|
19
|
+
const manifest = _generateManifestContents(bundle);
|
|
22
20
|
this.emitFile({
|
|
23
21
|
type: 'asset',
|
|
24
22
|
source: minify
|
|
25
23
|
? JSON.stringify(manifest)
|
|
26
24
|
: JSON.stringify(manifest, null, 2),
|
|
27
|
-
fileName: normalize(
|
|
25
|
+
fileName: normalize(`${filename}-${MANIFEST_FILE}`),
|
|
28
26
|
});
|
|
29
27
|
} catch (e) {
|
|
30
28
|
logger.warn(`\nUnable to write manifest file in ${output}, ${e}`);
|
|
@@ -33,14 +31,13 @@ function plugin(options = {}) {
|
|
|
33
31
|
};
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
function _generateManifestContents(bundle
|
|
34
|
+
function _generateManifestContents(bundle) {
|
|
37
35
|
const baseManifest = {
|
|
38
36
|
package: _loadPackageFile(),
|
|
39
|
-
extension,
|
|
40
37
|
};
|
|
41
38
|
|
|
42
39
|
// The keys to bundle are the filename without any path information
|
|
43
|
-
const bundles = Object.keys(bundle);
|
|
40
|
+
const bundles = Object.keys(bundle).filter(cur => cur.endsWith('.js'));
|
|
44
41
|
|
|
45
42
|
if (bundles.length === 1) {
|
|
46
43
|
return {
|
|
@@ -86,7 +83,7 @@ function _loadPackageFile() {
|
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
function _stripPathPriorToExtDir(filepath) {
|
|
89
|
-
return filepath
|
|
86
|
+
return filepath?.split(EXTENSIONS_PATH).pop();
|
|
90
87
|
}
|
|
91
88
|
|
|
92
89
|
function _buildModulesInfo(moduleIds, modules) {
|
package/run.js
CHANGED
|
@@ -3,27 +3,37 @@
|
|
|
3
3
|
const { startDevMode } = require('./dev');
|
|
4
4
|
const { loadConfig } = require('./config');
|
|
5
5
|
const { buildAllExtensions, buildSingleExtension } = require('./build');
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
VITE_DEFAULT_PORT,
|
|
8
|
+
OUTPUT_DIR,
|
|
9
|
+
WEBSOCKET_PORT,
|
|
10
|
+
} = require('./constants');
|
|
7
11
|
const { parseArgs, showHelp } = require('./cli');
|
|
8
12
|
const { getUrlSafeFileName } = require('./utils');
|
|
9
13
|
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
10
14
|
|
|
11
|
-
const { DEV_MODE, BUILD_MODE,
|
|
12
|
-
const PORT = port || VITE_DEFAULT_PORT;
|
|
15
|
+
const { DEV_MODE, BUILD_MODE, extension, help } = parseArgs();
|
|
13
16
|
|
|
14
17
|
if (help || !(DEV_MODE || BUILD_MODE)) {
|
|
15
18
|
showHelp(OUTPUT_DIR);
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
if (DEV_MODE) {
|
|
19
|
-
startDevMode(
|
|
22
|
+
startDevMode(
|
|
23
|
+
loadConfig(),
|
|
24
|
+
OUTPUT_DIR,
|
|
25
|
+
VITE_DEFAULT_PORT,
|
|
26
|
+
WEBSOCKET_PORT,
|
|
27
|
+
extension
|
|
28
|
+
);
|
|
20
29
|
} else if (BUILD_MODE) {
|
|
21
30
|
if (extension) {
|
|
31
|
+
const output = getUrlSafeFileName(extension);
|
|
22
32
|
buildSingleExtension({
|
|
23
33
|
file: extension,
|
|
24
|
-
outputFileName:
|
|
34
|
+
outputFileName: output,
|
|
25
35
|
outputDir: OUTPUT_DIR,
|
|
26
|
-
plugins: { rollup: [manifestPlugin()] },
|
|
36
|
+
plugins: { rollup: [manifestPlugin({ output })] },
|
|
27
37
|
});
|
|
28
38
|
} else {
|
|
29
39
|
buildAllExtensions(loadConfig(), OUTPUT_DIR);
|
package/server.js
CHANGED
|
@@ -1,120 +1,64 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const chokidar = require('chokidar');
|
|
3
|
-
const { WebSocketServer } = require('ws');
|
|
4
|
-
const http = require('http');
|
|
5
2
|
const logger = require('./logger');
|
|
6
3
|
const path = require('path');
|
|
7
4
|
const cors = require('cors');
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const { EXTENSIONS_MESSAGE_VERSION, MANIFEST_FILE } = require('./constants');
|
|
7
|
+
|
|
8
|
+
function startDevServer(
|
|
9
|
+
outputDir,
|
|
10
|
+
expressPort,
|
|
11
|
+
webSocketPort,
|
|
12
|
+
baseMessage,
|
|
13
|
+
viteDevServer
|
|
14
|
+
) {
|
|
16
15
|
const app = express();
|
|
17
16
|
|
|
18
|
-
// Using http.createServer so WebSocket server can be attached before initializing express.
|
|
19
|
-
const server = http.createServer(app);
|
|
20
|
-
|
|
21
17
|
// Setup middleware
|
|
22
18
|
app.use(cors());
|
|
23
19
|
app.use(express.static(outputDir));
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
callback
|
|
20
|
+
_addExtensionsEndpoint(
|
|
21
|
+
app,
|
|
22
|
+
expressPort,
|
|
23
|
+
webSocketPort,
|
|
24
|
+
outputDir,
|
|
25
|
+
baseMessage
|
|
31
26
|
);
|
|
32
|
-
_setupChokidarWatcher(broadcast, outputDir);
|
|
33
|
-
|
|
34
|
-
server.listen({ port }, () => {
|
|
35
|
-
logger.warn(`Listening at ${callback}`);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
_configureShutDownHandlers(server, wss, broadcast);
|
|
39
|
-
}
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
// Vite middlewares needs to go last because it's greedy and will block other middleware
|
|
29
|
+
app.use(viteDevServer.middlewares);
|
|
43
30
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
title: extensionConfig?.name,
|
|
47
|
-
callback,
|
|
48
|
-
version: WEBSOCKET_MESSAGE_VERSION,
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
wss.on('connection', client => {
|
|
52
|
-
logger.info('Browser connected and listening for bundle updates');
|
|
53
|
-
client.send(
|
|
54
|
-
JSON.stringify({
|
|
55
|
-
event: 'start',
|
|
56
|
-
...baseMessage,
|
|
57
|
-
})
|
|
58
|
-
);
|
|
31
|
+
const server = app.listen({ port: expressPort }, () => {
|
|
32
|
+
logger.warn(`Listening at ${baseMessage.callback}`);
|
|
59
33
|
});
|
|
60
34
|
|
|
61
|
-
|
|
62
|
-
wss,
|
|
63
|
-
broadcast: message => {
|
|
64
|
-
if (wss.clients.size === 0) {
|
|
65
|
-
logger.warn('No browsers connected to notify');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
wss.clients.forEach(client => {
|
|
69
|
-
client.send(
|
|
70
|
-
JSON.stringify({
|
|
71
|
-
...message,
|
|
72
|
-
...baseMessage,
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Setup a watcher on the dist directory and update broadcast
|
|
81
|
-
//to all clients when an event is observed
|
|
82
|
-
function _setupChokidarWatcher(broadcast, outputDir) {
|
|
83
|
-
chokidar.watch(outputDir).on('all', (event, file) => {
|
|
84
|
-
if (event !== 'change' || event !== 'add' || file === 'manifest.json') {
|
|
85
|
-
// We need to listen to 'change' and 'add' because sometimes chokidar
|
|
86
|
-
// sees it as an 'unlink' and 'add' instead of just a 'change'
|
|
87
|
-
// Since we are adding the manifest.json to the build, we want to ignore changes to that so we don't double send messages
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
logger.info('Bundle updated notifying browser');
|
|
91
|
-
broadcast({
|
|
92
|
-
event: 'update',
|
|
93
|
-
});
|
|
94
|
-
});
|
|
35
|
+
_configureShutDownHandlers(server, viteDevServer);
|
|
95
36
|
}
|
|
96
37
|
|
|
97
|
-
function _addExtensionsEndpoint(
|
|
38
|
+
function _addExtensionsEndpoint(
|
|
39
|
+
server,
|
|
40
|
+
expressPort,
|
|
41
|
+
webSocketPort,
|
|
42
|
+
outputDir,
|
|
43
|
+
baseMessage
|
|
44
|
+
) {
|
|
98
45
|
const endpoint = '/extensions';
|
|
99
46
|
server.get(endpoint, (_req, res) => {
|
|
100
47
|
try {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
48
|
+
const output = path.parse(baseMessage.callback).name;
|
|
49
|
+
const manifest = JSON.parse(
|
|
50
|
+
fs.readFileSync(
|
|
51
|
+
path.join(process.cwd(), `${outputDir}/${output}-${MANIFEST_FILE}`)
|
|
52
|
+
)
|
|
53
|
+
);
|
|
105
54
|
|
|
106
55
|
const response = {
|
|
107
|
-
websocket: `ws://localhost:${
|
|
56
|
+
websocket: `ws://localhost:${webSocketPort}`,
|
|
108
57
|
version: EXTENSIONS_MESSAGE_VERSION,
|
|
109
58
|
extensions: [
|
|
110
59
|
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
callback: `http://hslocal.net:${port}/${output}`,
|
|
114
|
-
manifest: {
|
|
115
|
-
...manifest,
|
|
116
|
-
extension: undefined,
|
|
117
|
-
},
|
|
60
|
+
...baseMessage,
|
|
61
|
+
manifest,
|
|
118
62
|
},
|
|
119
63
|
],
|
|
120
64
|
};
|
|
@@ -125,19 +69,16 @@ function _addExtensionsEndpoint(server, port) {
|
|
|
125
69
|
});
|
|
126
70
|
}
|
|
127
71
|
});
|
|
128
|
-
logger.warn(`Listening at http://hslocal.net:${
|
|
72
|
+
logger.warn(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
|
|
129
73
|
}
|
|
130
74
|
|
|
131
|
-
function _configureShutDownHandlers(server,
|
|
132
|
-
function shutdown() {
|
|
75
|
+
function _configureShutDownHandlers(server, viteDevServer) {
|
|
76
|
+
async function shutdown() {
|
|
133
77
|
logger.warn('\nCleaning up after ourselves...');
|
|
78
|
+
await viteDevServer.pluginContainer.close();
|
|
134
79
|
// Stop new connections to express server
|
|
135
|
-
broadcast({
|
|
136
|
-
event: 'shutdown',
|
|
137
|
-
});
|
|
138
|
-
wss.close(() => {});
|
|
139
80
|
server.close(() => {});
|
|
140
|
-
logger.
|
|
81
|
+
logger.info('Clean up done, exiting.');
|
|
141
82
|
process.exit(0);
|
|
142
83
|
}
|
|
143
84
|
|
package/tests/runTests.js
CHANGED
|
@@ -3,30 +3,48 @@
|
|
|
3
3
|
const { testBuild } = require('./testBuild');
|
|
4
4
|
const { testDevServer } = require('./testDevServer');
|
|
5
5
|
const logger = require('../logger');
|
|
6
|
+
const { generateSpec } = require('./utils');
|
|
6
7
|
|
|
7
8
|
let devServerProcess;
|
|
8
9
|
|
|
10
|
+
// Overwrite console.debug to only log when in DEBUG mode
|
|
11
|
+
console.debug = (...args) => {
|
|
12
|
+
if (process.env.DEBUG) {
|
|
13
|
+
console.log(...args);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
function handleFailure() {
|
|
10
18
|
if (devServerProcess) {
|
|
11
19
|
devServerProcess.kill();
|
|
12
20
|
}
|
|
13
21
|
}
|
|
14
22
|
|
|
15
|
-
process.on('SIGINT',
|
|
16
|
-
|
|
17
|
-
});
|
|
23
|
+
process.on('SIGINT', handleFailure);
|
|
24
|
+
process.on('SIGTERM', handleFailure);
|
|
18
25
|
|
|
19
26
|
process.on('uncaughtException', e => {
|
|
20
27
|
console.error(e);
|
|
21
28
|
handleFailure();
|
|
22
29
|
});
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
31
|
+
// eslint-disable-next-line no-floating-promise/no-floating-promise
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
const spec = generateSpec();
|
|
35
|
+
|
|
36
|
+
logger.warn(
|
|
37
|
+
`Running tests for the following entrypoints: [\n\t${spec.build.entrypoints.join(
|
|
38
|
+
',\n\t'
|
|
39
|
+
)}\n]`
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
await testBuild(logger, spec.build);
|
|
43
|
+
testDevServer(logger, devServerProcess, spec.dev);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(e.message);
|
|
46
|
+
logger.error('Tests failed 😭');
|
|
47
|
+
handleFailure();
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
})();
|
package/tests/testBuild.js
CHANGED
|
@@ -2,6 +2,7 @@ const { execSync } = require('child_process');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const assert = require('assert');
|
|
5
|
+
const { generateManifestOutputPair, verifyFileContents } = require('./utils');
|
|
5
6
|
|
|
6
7
|
function _testHelper(command, outputDirFiles) {
|
|
7
8
|
if (command) {
|
|
@@ -16,76 +17,64 @@ function _testHelper(command, outputDirFiles) {
|
|
|
16
17
|
// Spot check the file contents to make sure they seem ok
|
|
17
18
|
filesInOutputDir.forEach(file => {
|
|
18
19
|
const fileContents = fs.readFileSync(path.join(distDir, file)).toString();
|
|
19
|
-
|
|
20
|
-
const manifest = JSON.parse(fileContents);
|
|
21
|
-
assert(manifest.entry);
|
|
22
|
-
assert(manifest.modules);
|
|
23
|
-
assert(manifest.modules.internal);
|
|
24
|
-
manifest.modules.internal.forEach(mod => {
|
|
25
|
-
assert(mod.module);
|
|
26
|
-
assert(mod.renderedExports);
|
|
27
|
-
});
|
|
28
|
-
assert(manifest.modules.external);
|
|
29
|
-
manifest.modules.external.forEach(mod => {
|
|
30
|
-
assert(mod.module);
|
|
31
|
-
assert(mod.renderedExports);
|
|
32
|
-
});
|
|
33
|
-
assert(manifest.package);
|
|
34
|
-
assert(manifest.package.packages);
|
|
35
|
-
} else {
|
|
36
|
-
const stringsToSpotCheck = [
|
|
37
|
-
'.createRemoteReactComponent',
|
|
38
|
-
'.createElement',
|
|
39
|
-
'hubspot.extend',
|
|
40
|
-
'React',
|
|
41
|
-
'RemoteUI',
|
|
42
|
-
];
|
|
43
|
-
stringsToSpotCheck.forEach(stringToCheck => {
|
|
44
|
-
assert(
|
|
45
|
-
fileContents.includes(stringToCheck),
|
|
46
|
-
`File ${file} contents should contain: "${stringToCheck}"`
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
20
|
+
verifyFileContents(file, fileContents);
|
|
50
21
|
});
|
|
51
22
|
}
|
|
52
23
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
logger.
|
|
24
|
+
function testBuildAll(logger, entrypoints) {
|
|
25
|
+
const expected = entrypoints
|
|
26
|
+
.reduce((acc, cur) => {
|
|
27
|
+
return acc.concat(generateManifestOutputPair(cur));
|
|
28
|
+
}, [])
|
|
29
|
+
.sort();
|
|
30
|
+
logger.warn('- Test build all extensions started 🤞');
|
|
31
|
+
_testHelper('hs-ui-extensions-dev-server build', expected);
|
|
32
|
+
logger.info('- Test build all extensions passed 🚀');
|
|
60
33
|
}
|
|
61
34
|
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
35
|
+
function testBuildSingle(logger, entrypoints) {
|
|
36
|
+
entrypoints.forEach(entrypoint => {
|
|
37
|
+
logger.warn(
|
|
38
|
+
`- Test build single extension started for entrypoint ${entrypoint} 🤞`
|
|
39
|
+
);
|
|
40
|
+
_testHelper(
|
|
41
|
+
`hs-ui-extensions-dev-server build --extension ${entrypoint}`,
|
|
42
|
+
generateManifestOutputPair(entrypoint)
|
|
43
|
+
);
|
|
44
|
+
logger.info(
|
|
45
|
+
`- Test build single extension passed for entrypoint ${entrypoint} 🚀`
|
|
46
|
+
);
|
|
47
|
+
});
|
|
69
48
|
}
|
|
70
49
|
|
|
71
|
-
function
|
|
50
|
+
async function testBuildRemote(logger, entrypoints) {
|
|
72
51
|
const { remoteBuild } = require('../index');
|
|
73
|
-
|
|
52
|
+
for (let i = 0; i < entrypoints.length; ++i) {
|
|
53
|
+
const entrypoint = entrypoints[i];
|
|
54
|
+
logger.warn(
|
|
55
|
+
`- Test remoteBuild function started for entrypoint ${entrypoint} 🤞`
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await remoteBuild(process.cwd(), entrypoint, 'dist');
|
|
59
|
+
_testHelper(null, generateManifestOutputPair(entrypoint));
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
61
|
+
logger.info(
|
|
62
|
+
`- Test remoteBuild function passed for entrypoint ${entrypoint} 🚀`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
78
65
|
}
|
|
79
66
|
|
|
80
|
-
function testBuild(logger) {
|
|
67
|
+
async function testBuild(logger, buildSpec) {
|
|
68
|
+
const { entrypoints } = buildSpec;
|
|
69
|
+
|
|
81
70
|
logger.warn('\nBuild Tests started - External Devs 🤞');
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
testBuildAll(logger, entrypoints);
|
|
72
|
+
testBuildSingle(logger, entrypoints);
|
|
84
73
|
logger.info('Build Tests passed - External Devs🚀');
|
|
85
74
|
|
|
86
|
-
logger.warn('\nBuild Tests started -
|
|
87
|
-
|
|
88
|
-
logger.info('Build Tests passed -
|
|
75
|
+
logger.warn('\nBuild Tests started - Remote 🤞');
|
|
76
|
+
await testBuildRemote(logger, entrypoints);
|
|
77
|
+
logger.info('Build Tests passed - Remote 🚀');
|
|
89
78
|
}
|
|
90
79
|
|
|
91
80
|
module.exports = {
|
package/tests/testDevServer.js
CHANGED
|
@@ -2,49 +2,92 @@ const { spawn } = require('child_process');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const assert = require('assert');
|
|
5
|
-
const
|
|
5
|
+
const axios = require('axios');
|
|
6
6
|
const WebSocket = require('ws');
|
|
7
7
|
const {
|
|
8
|
+
VITE_DEFAULT_PORT,
|
|
9
|
+
WEBSOCKET_PORT,
|
|
8
10
|
WEBSOCKET_MESSAGE_VERSION,
|
|
9
11
|
EXTENSIONS_MESSAGE_VERSION,
|
|
10
12
|
} = require('../constants');
|
|
13
|
+
const { verifyFileContents, generateManifestOutputPair } = require('./utils');
|
|
14
|
+
const { getUrlSafeFileName } = require('../utils');
|
|
11
15
|
|
|
12
|
-
const
|
|
16
|
+
const preShutdownResults = {
|
|
13
17
|
buildTestPassed: false,
|
|
14
18
|
expressStaticTestPassed: false,
|
|
15
19
|
extensionsEndpointPassed: false,
|
|
16
20
|
webSocketTestPassed: false,
|
|
17
21
|
};
|
|
18
22
|
|
|
19
|
-
const
|
|
20
|
-
|
|
23
|
+
const postShutdownResults = {
|
|
24
|
+
websocketShutdownReceived: false,
|
|
25
|
+
expressServerShutdown: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const host = 'http://hslocal.net';
|
|
29
|
+
|
|
30
|
+
let running = false;
|
|
21
31
|
|
|
22
|
-
function
|
|
23
|
-
|
|
32
|
+
function resetResults() {
|
|
33
|
+
Object.keys(preShutdownResults).forEach(result => {
|
|
34
|
+
preShutdownResults[result] = false;
|
|
35
|
+
});
|
|
36
|
+
Object.keys(postShutdownResults).forEach(result => {
|
|
37
|
+
postShutdownResults[result] = false;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
24
40
|
|
|
41
|
+
function testDevServer(logger, devServerProcess, specs) {
|
|
42
|
+
const devServerInterval = setInterval(runDevServer, 1000);
|
|
43
|
+
let runCount = 0;
|
|
44
|
+
|
|
45
|
+
function runDevServer() {
|
|
46
|
+
if (running === false && !devServerProcess) {
|
|
47
|
+
resetResults();
|
|
48
|
+
_testDevServer(logger, devServerProcess, specs[runCount]);
|
|
49
|
+
runCount += 1;
|
|
50
|
+
}
|
|
51
|
+
if (runCount === specs.length) {
|
|
52
|
+
clearInterval(devServerInterval);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function _testDevServer(logger, devServerProcess, spec) {
|
|
58
|
+
running = true;
|
|
59
|
+
const { entrypoint, expected } = spec;
|
|
60
|
+
|
|
61
|
+
const filename = getUrlSafeFileName(entrypoint);
|
|
62
|
+
const localExpectations = {
|
|
63
|
+
callback: `${host}:${VITE_DEFAULT_PORT}/${filename}`,
|
|
64
|
+
websocketUrl: `ws://localhost:${WEBSOCKET_PORT}`,
|
|
65
|
+
...expected,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
logger.warn(`\nDev Server Tests started - ${entrypoint} 🤞`);
|
|
25
69
|
// We need to use spawn here because it will put the process into the background,
|
|
26
70
|
// which is required because dev mode is a blocking process and we want to test that
|
|
27
71
|
// the express server and websocket server are starting properly
|
|
28
72
|
devServerProcess = spawn('hs-ui-extensions-dev-server', [
|
|
29
73
|
'dev',
|
|
30
74
|
'--extension',
|
|
31
|
-
|
|
32
|
-
'--port',
|
|
33
|
-
`${port}`,
|
|
75
|
+
entrypoint,
|
|
34
76
|
]);
|
|
35
77
|
|
|
36
78
|
devServerProcess.stdout.on('data', buffer => {
|
|
37
79
|
const data = buffer.toString().toLowerCase();
|
|
80
|
+
console.debug('[Dev Server]:', data);
|
|
38
81
|
if (data.includes('built in')) {
|
|
39
|
-
testBuild(
|
|
82
|
+
testBuild(logger, entrypoint);
|
|
40
83
|
}
|
|
41
84
|
if (
|
|
42
85
|
data.includes('listening') &&
|
|
43
|
-
data.includes(`${host}:${
|
|
86
|
+
data.includes(`${host}:${VITE_DEFAULT_PORT}/extensions`)
|
|
44
87
|
) {
|
|
45
88
|
setTimeout(() => {
|
|
46
|
-
testExpressServer(
|
|
47
|
-
testWebSocketServer(
|
|
89
|
+
testExpressServer(logger, localExpectations, filename);
|
|
90
|
+
testWebSocketServer(logger, localExpectations);
|
|
48
91
|
}, 1000);
|
|
49
92
|
}
|
|
50
93
|
});
|
|
@@ -58,32 +101,33 @@ function testDevServer(logger, devServerProcess) {
|
|
|
58
101
|
|
|
59
102
|
// When the process closes make sure we met all the success conditions
|
|
60
103
|
devServerProcess.on('close', () => {
|
|
61
|
-
if (
|
|
62
|
-
logger.info(
|
|
104
|
+
if (metPreShutdownConditions()) {
|
|
105
|
+
logger.info(`- Dev Server pre-shutdown tests passed - ${entrypoint} 🚀`);
|
|
106
|
+
testPostShutdown(logger, filename);
|
|
63
107
|
} else {
|
|
64
|
-
console.log(
|
|
108
|
+
console.log(preShutdownResults);
|
|
65
109
|
logger.error('Tests failed 😭');
|
|
66
110
|
}
|
|
67
111
|
});
|
|
68
112
|
|
|
69
|
-
const
|
|
70
|
-
let
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
if (
|
|
113
|
+
const preShutdownInterval = setInterval(preShutdownCallback, 1000);
|
|
114
|
+
let preShutdownCheckCount = 0;
|
|
115
|
+
function preShutdownCallback() {
|
|
116
|
+
preShutdownCheckCount += 1;
|
|
117
|
+
if (metPreShutdownConditions() || preShutdownCheckCount === 5) {
|
|
118
|
+
clearInterval(preShutdownInterval);
|
|
74
119
|
devServerProcess.kill();
|
|
75
|
-
clearInterval(interval);
|
|
76
120
|
}
|
|
77
121
|
}
|
|
78
122
|
}
|
|
79
123
|
|
|
80
|
-
function
|
|
124
|
+
function metPreShutdownConditions() {
|
|
81
125
|
const {
|
|
82
126
|
buildTestPassed,
|
|
83
127
|
expressStaticTestPassed,
|
|
84
128
|
extensionsEndpointPassed,
|
|
85
129
|
webSocketTestPassed,
|
|
86
|
-
} =
|
|
130
|
+
} = preShutdownResults;
|
|
87
131
|
return (
|
|
88
132
|
buildTestPassed &&
|
|
89
133
|
expressStaticTestPassed &&
|
|
@@ -92,99 +136,152 @@ function metConditions() {
|
|
|
92
136
|
);
|
|
93
137
|
}
|
|
94
138
|
|
|
139
|
+
function metPostShutdownConditions() {
|
|
140
|
+
const {
|
|
141
|
+
websocketShutdownReceived,
|
|
142
|
+
expressServerShutdown,
|
|
143
|
+
} = postShutdownResults;
|
|
144
|
+
|
|
145
|
+
return websocketShutdownReceived && expressServerShutdown;
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
// Test that the files were built in the proper location and spot
|
|
96
149
|
// check the contents of the files.
|
|
97
|
-
function testBuild(
|
|
150
|
+
function testBuild(logger, entryPoint) {
|
|
98
151
|
// // Make sure the files are getting generated in the dist dir
|
|
99
152
|
const distDir = path.join(process.cwd(), 'dist');
|
|
100
153
|
const filesInOutputDir = fs.readdirSync(distDir);
|
|
101
|
-
|
|
154
|
+
const filename = path.parse(entryPoint).name;
|
|
155
|
+
assert.deepStrictEqual(
|
|
156
|
+
filesInOutputDir,
|
|
157
|
+
generateManifestOutputPair(entryPoint)
|
|
158
|
+
);
|
|
102
159
|
const fileContents = fs
|
|
103
|
-
.readFileSync(path.join(distDir,
|
|
160
|
+
.readFileSync(path.join(distDir, `${filename}.js`))
|
|
104
161
|
.toString();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
assert(
|
|
114
|
-
fileContents.includes(stringToCheck),
|
|
115
|
-
`File ${filesInOutputDir[0]} contents should contain: "${stringToCheck}"`
|
|
116
|
-
);
|
|
117
|
-
});
|
|
162
|
+
verifyFileContents(`${filename}.js`, fileContents);
|
|
163
|
+
|
|
164
|
+
// Check for the inlined source map
|
|
165
|
+
assert(
|
|
166
|
+
fileContents.includes(
|
|
167
|
+
'//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
|
|
168
|
+
)
|
|
169
|
+
);
|
|
118
170
|
logger.info('- Build succeeded 🚀');
|
|
119
|
-
|
|
171
|
+
preShutdownResults.buildTestPassed = true;
|
|
120
172
|
}
|
|
121
173
|
|
|
122
174
|
// Test that the express server is running on the expected port
|
|
123
175
|
// and that it is serving the files as expected.
|
|
124
|
-
function testExpressServer(
|
|
125
|
-
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
path: '/PhoneLines.js',
|
|
130
|
-
},
|
|
131
|
-
response => {
|
|
132
|
-
if (response.statusCode !== 200) {
|
|
133
|
-
throw Error('Error with express server');
|
|
134
|
-
}
|
|
176
|
+
function testExpressServer(logger, expected, filename) {
|
|
177
|
+
axios
|
|
178
|
+
.get(`${host}:${VITE_DEFAULT_PORT}/${filename}`)
|
|
179
|
+
.then(response => {
|
|
180
|
+
assert.strictEqual(response.status, 200, 'Bundle status code');
|
|
135
181
|
logger.info('- Express server connected and serving files 🚀');
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
);
|
|
182
|
+
preShutdownResults.expressStaticTestPassed = true;
|
|
183
|
+
})
|
|
184
|
+
.catch(logger.error);
|
|
185
|
+
axios
|
|
186
|
+
.get(`${host}:${VITE_DEFAULT_PORT}/extensions`)
|
|
187
|
+
.then(response => {
|
|
188
|
+
const { data, status } = response;
|
|
189
|
+
assert.strictEqual(status, 200, '/extensions status code');
|
|
190
|
+
|
|
191
|
+
// Top level response assertions
|
|
192
|
+
assert.strictEqual(data.websocket, expected.websocketUrl);
|
|
193
|
+
assert.strictEqual(data.version, EXTENSIONS_MESSAGE_VERSION);
|
|
194
|
+
assert(data.extensions);
|
|
195
|
+
|
|
196
|
+
// Extension level response assertions
|
|
197
|
+
assert.equal(data.extensions.length, 1, '/extensions body length');
|
|
198
|
+
const extension = data.extensions.pop();
|
|
199
|
+
assert(extension.manifest);
|
|
200
|
+
assert.strictEqual(
|
|
201
|
+
extension.appName,
|
|
202
|
+
expected.appName,
|
|
203
|
+
'/extensions appName'
|
|
204
|
+
);
|
|
205
|
+
assert.strictEqual(extension.title, expected.title, '/extensions title');
|
|
206
|
+
assert.strictEqual(
|
|
207
|
+
extension.callback,
|
|
208
|
+
expected.callback,
|
|
209
|
+
'/extensions callback'
|
|
210
|
+
);
|
|
211
|
+
logger.info('- Express serving extension data 🚀');
|
|
212
|
+
preShutdownResults.extensionsEndpointPassed = true;
|
|
213
|
+
})
|
|
214
|
+
.catch(logger.error);
|
|
170
215
|
}
|
|
171
216
|
|
|
172
217
|
// Test the the web socket server is running on the expected port and
|
|
173
218
|
// that we are able to receive messages from it.
|
|
174
|
-
function testWebSocketServer(
|
|
175
|
-
const ws = new WebSocket(
|
|
219
|
+
function testWebSocketServer(logger, expected) {
|
|
220
|
+
const ws = new WebSocket(expected.websocketUrl);
|
|
176
221
|
ws.on('message', messageBuffer => {
|
|
177
222
|
const message = JSON.parse(messageBuffer.toString());
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
223
|
+
console.debug('[WebSocket Message]:', message);
|
|
224
|
+
// Vite sends { type: 'connected'} as a greeting on connect
|
|
225
|
+
if (message.type === 'connected') {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
assert(['start', 'shutdown'].includes(message.event), 'Message Event');
|
|
230
|
+
assert.strictEqual(message.appName, expected.appName, 'Message appName');
|
|
231
|
+
assert.strictEqual(message.title, expected.title, 'Message title');
|
|
181
232
|
assert.strictEqual(message.version, WEBSOCKET_MESSAGE_VERSION);
|
|
182
|
-
logger.info(
|
|
183
|
-
|
|
184
|
-
|
|
233
|
+
logger.info(
|
|
234
|
+
`- WebSocket server connected and sending '${message.event}' messages 🚀`
|
|
235
|
+
);
|
|
236
|
+
if (message.event === 'start') {
|
|
237
|
+
preShutdownResults.webSocketTestPassed = true;
|
|
238
|
+
} else if (message.event === 'shutdown') {
|
|
239
|
+
postShutdownResults.websocketShutdownReceived = true;
|
|
240
|
+
}
|
|
185
241
|
});
|
|
186
242
|
}
|
|
187
243
|
|
|
244
|
+
function testPostShutdown(logger, filename) {
|
|
245
|
+
testExpressHasShutdown(filename);
|
|
246
|
+
|
|
247
|
+
const shutdownInterval = setInterval(shutdownCallback, 1000);
|
|
248
|
+
let shutdownCheckCount = 0;
|
|
249
|
+
function shutdownCallback() {
|
|
250
|
+
shutdownCheckCount += 1;
|
|
251
|
+
if (metPostShutdownConditions()) {
|
|
252
|
+
clearInterval(shutdownInterval);
|
|
253
|
+
logger.info('- Dev Server post-shutdown tests passed 🚀');
|
|
254
|
+
logger.info('Dev Server tests passed 🚀');
|
|
255
|
+
running = false;
|
|
256
|
+
} else if (shutdownCheckCount === 5) {
|
|
257
|
+
clearInterval(shutdownInterval);
|
|
258
|
+
console.log(postShutdownResults);
|
|
259
|
+
logger.error('Tests failed 😭');
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function testExpressHasShutdown(filename) {
|
|
265
|
+
axios
|
|
266
|
+
.get(`${host}:${VITE_DEFAULT_PORT}/${filename}`)
|
|
267
|
+
.then(response => {
|
|
268
|
+
console.debug(response);
|
|
269
|
+
})
|
|
270
|
+
.catch(err => {
|
|
271
|
+
assert.strictEqual(
|
|
272
|
+
err.syscall,
|
|
273
|
+
'connect',
|
|
274
|
+
'The connect call should fail'
|
|
275
|
+
);
|
|
276
|
+
assert.strictEqual(
|
|
277
|
+
err.code,
|
|
278
|
+
'ECONNREFUSED',
|
|
279
|
+
'Express should refuse connection post shutdown'
|
|
280
|
+
);
|
|
281
|
+
postShutdownResults.expressServerShutdown = true;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
188
285
|
module.exports = {
|
|
189
286
|
testDevServer,
|
|
190
287
|
};
|
package/tests/utils.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { MANIFEST_FILE } = require('../constants');
|
|
3
|
+
const { getUrlSafeFileName } = require('../utils');
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const { loadConfig } = require('../config');
|
|
6
|
+
|
|
7
|
+
function generateManifestOutputPair(entrypoint) {
|
|
8
|
+
const fileName = path.parse(entrypoint).name;
|
|
9
|
+
return [`${fileName}-${MANIFEST_FILE}`, getUrlSafeFileName(entrypoint)];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function verifyFileContents(file, contents) {
|
|
13
|
+
if (file.endsWith(MANIFEST_FILE)) {
|
|
14
|
+
const manifest = JSON.parse(contents);
|
|
15
|
+
assert(manifest.entry);
|
|
16
|
+
assert(manifest.modules);
|
|
17
|
+
assert(manifest.modules.internal);
|
|
18
|
+
manifest.modules.internal.forEach(mod => {
|
|
19
|
+
assert(mod.module);
|
|
20
|
+
assert(mod.renderedExports);
|
|
21
|
+
});
|
|
22
|
+
assert(manifest.modules.external);
|
|
23
|
+
manifest.modules.external.forEach(mod => {
|
|
24
|
+
assert(mod.module);
|
|
25
|
+
assert(mod.renderedExports);
|
|
26
|
+
});
|
|
27
|
+
assert(manifest.package);
|
|
28
|
+
assert(manifest.package.packages);
|
|
29
|
+
} else {
|
|
30
|
+
const stringsToSpotCheck = [
|
|
31
|
+
'.createRemoteReactComponent',
|
|
32
|
+
'.createElement',
|
|
33
|
+
'hubspot.extend',
|
|
34
|
+
'React',
|
|
35
|
+
'RemoteUI',
|
|
36
|
+
];
|
|
37
|
+
stringsToSpotCheck.forEach(stringToCheck => {
|
|
38
|
+
assert(
|
|
39
|
+
contents.includes(stringToCheck),
|
|
40
|
+
`File ${file} contents should contain: "${stringToCheck}"`
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function generateSpec() {
|
|
47
|
+
const config = loadConfig();
|
|
48
|
+
const entrypoints = Object.keys(config);
|
|
49
|
+
if (entrypoints.length === 0) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'Unable to determine testable entrypoints from config files'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const dev = entrypoints.map(entrypoint => {
|
|
56
|
+
const { data } = config[entrypoint];
|
|
57
|
+
return {
|
|
58
|
+
entrypoint,
|
|
59
|
+
expected: {
|
|
60
|
+
...data,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
build: {
|
|
66
|
+
entrypoints,
|
|
67
|
+
},
|
|
68
|
+
dev,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
generateManifestOutputPair,
|
|
74
|
+
verifyFileContents,
|
|
75
|
+
generateSpec,
|
|
76
|
+
};
|