@hubspot/ui-extensions-dev-server 0.1.0 → 0.3.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/cli/run.js +36 -0
- package/cli/userInput.js +32 -0
- package/index.js +9 -28
- package/lib/build.js +90 -0
- package/lib/config.js +81 -0
- package/{constants.js → lib/constants.js} +9 -6
- package/lib/dev.js +89 -0
- package/lib/extensions.js +9 -0
- package/lib/plugins/devBuildPlugin.js +139 -0
- package/{plugins → lib/plugins}/manifestPlugin.js +9 -13
- package/lib/server.js +91 -0
- package/lib/utils.js +20 -0
- package/package.json +25 -22
- package/build.js +0 -56
- package/config.js +0 -129
- package/dev.js +0 -52
- package/run.js +0 -31
- package/server.js +0 -148
- package/tests/runTests.js +0 -32
- package/tests/testBuild.js +0 -93
- package/tests/testDevServer.js +0 -190
- package/utils.js +0 -10
- /package/{logger.js → cli/logger.js} +0 -0
- /package/{cli.js → cli/utils.js} +0 -0
package/cli/run.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { parseArgs, showHelp } = require('../cli/utils');
|
|
4
|
+
const {
|
|
5
|
+
buildAllExtensions,
|
|
6
|
+
buildSingleExtension,
|
|
7
|
+
startDevMode,
|
|
8
|
+
} = require('../index');
|
|
9
|
+
|
|
10
|
+
const { promptForExtensionToRun } = require('./userInput');
|
|
11
|
+
const OUTPUT_DIR = 'dist';
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-floating-promise/no-floating-promise
|
|
15
|
+
(async () => {
|
|
16
|
+
const { DEV_MODE, BUILD_MODE, extension, help } = parseArgs();
|
|
17
|
+
|
|
18
|
+
if (help || !(DEV_MODE || BUILD_MODE)) {
|
|
19
|
+
showHelp(OUTPUT_DIR);
|
|
20
|
+
} else if (DEV_MODE) {
|
|
21
|
+
startDevMode({
|
|
22
|
+
extension: extension || (await promptForExtensionToRun()),
|
|
23
|
+
outputDir: OUTPUT_DIR,
|
|
24
|
+
logger,
|
|
25
|
+
});
|
|
26
|
+
} else if (BUILD_MODE) {
|
|
27
|
+
if (extension) {
|
|
28
|
+
buildSingleExtension({
|
|
29
|
+
file: extension,
|
|
30
|
+
outputDir: OUTPUT_DIR,
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
buildAllExtensions({ outputDir: OUTPUT_DIR });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})();
|
package/cli/userInput.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const prompts = require('prompts');
|
|
2
|
+
|
|
3
|
+
const { getExtensionsList } = require('../lib/extensions');
|
|
4
|
+
|
|
5
|
+
async function promptForExtensionToRun() {
|
|
6
|
+
const extensionOptions = getExtensionsList();
|
|
7
|
+
const response = await prompts(
|
|
8
|
+
[
|
|
9
|
+
{
|
|
10
|
+
type: 'select',
|
|
11
|
+
name: 'extension',
|
|
12
|
+
message: 'Which extension would you like to run?',
|
|
13
|
+
choices: extensionOptions.map(option => {
|
|
14
|
+
return {
|
|
15
|
+
title: option,
|
|
16
|
+
value: option,
|
|
17
|
+
};
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
{
|
|
22
|
+
onCancel: () => {
|
|
23
|
+
process.exit(0); // When the user cancels interaction, exit the script
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
return response.extension;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = {
|
|
31
|
+
promptForExtensionToRun,
|
|
32
|
+
};
|
package/index.js
CHANGED
|
@@ -1,33 +1,14 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
async function remoteBuild(root, entryPoint, outputDir) {
|
|
8
|
-
const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
|
|
9
|
-
const fileInfo = path.parse(entryPoint);
|
|
10
|
-
|
|
11
|
-
if (!allowedExtensions.includes(fileInfo.ext)) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
`The last argument should be the filename you wish to build. Supported file extensions are [${allowedExtensions.join(
|
|
14
|
-
', '
|
|
15
|
-
)}]`
|
|
16
|
-
);
|
|
17
|
-
}
|
|
1
|
+
const {
|
|
2
|
+
remoteBuild,
|
|
3
|
+
buildAllExtensions,
|
|
4
|
+
buildSingleExtension,
|
|
5
|
+
} = require('./lib/build');
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
file: entryPoint,
|
|
21
|
-
outputFileName: getUrlSafeFileName(entryPoint),
|
|
22
|
-
outputDir: outputDir || OUTPUT_DIR,
|
|
23
|
-
plugins: {
|
|
24
|
-
rollup: [manifestPlugin({ minify: true })],
|
|
25
|
-
},
|
|
26
|
-
minify: true,
|
|
27
|
-
root,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
7
|
+
const { startDevMode } = require('./lib/dev');
|
|
30
8
|
|
|
31
9
|
module.exports = {
|
|
32
10
|
remoteBuild,
|
|
11
|
+
buildAllExtensions,
|
|
12
|
+
buildSingleExtension,
|
|
13
|
+
startDevMode,
|
|
33
14
|
};
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const { build } = require('vite');
|
|
2
|
+
const { ROLLUP_OPTIONS, OUTPUT_DIR } = require('./constants');
|
|
3
|
+
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { getUrlSafeFileName } = require('./utils');
|
|
6
|
+
const { loadConfig } = require('./config');
|
|
7
|
+
|
|
8
|
+
const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
|
|
9
|
+
const extensionErrorBaseMessage = `Supported file extensions are [${allowedExtensions.join(
|
|
10
|
+
', '
|
|
11
|
+
)}], received:`;
|
|
12
|
+
|
|
13
|
+
async function buildAllExtensions({ outputDir }) {
|
|
14
|
+
const config = loadConfig();
|
|
15
|
+
const extensionKeys = Object.keys(config);
|
|
16
|
+
for (let i = 0; i < extensionKeys.length; ++i) {
|
|
17
|
+
const { data } = config[extensionKeys[i]];
|
|
18
|
+
|
|
19
|
+
await buildSingleExtension({
|
|
20
|
+
file: data.module.file,
|
|
21
|
+
outputDir,
|
|
22
|
+
emptyOutDir: i === 0,
|
|
23
|
+
plugins: {
|
|
24
|
+
rollup: [
|
|
25
|
+
manifestPlugin({ output: getUrlSafeFileName(data.module.file) }),
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function buildSingleExtension({
|
|
33
|
+
file,
|
|
34
|
+
outputDir = OUTPUT_DIR,
|
|
35
|
+
emptyOutDir = true,
|
|
36
|
+
minify = false,
|
|
37
|
+
root = process.cwd(), // This is the vite default, so using that as our default
|
|
38
|
+
}) {
|
|
39
|
+
const output = getUrlSafeFileName(file);
|
|
40
|
+
await build({
|
|
41
|
+
root,
|
|
42
|
+
define: {
|
|
43
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
44
|
+
process.env.NODE_ENV || 'production'
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
build: {
|
|
48
|
+
lib: {
|
|
49
|
+
entry: file,
|
|
50
|
+
name: output,
|
|
51
|
+
formats: ['iife'],
|
|
52
|
+
fileName: () => output,
|
|
53
|
+
},
|
|
54
|
+
rollupOptions: {
|
|
55
|
+
...ROLLUP_OPTIONS,
|
|
56
|
+
plugins: [manifestPlugin({ output })],
|
|
57
|
+
},
|
|
58
|
+
outDir: outputDir,
|
|
59
|
+
emptyOutDir,
|
|
60
|
+
minify,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function remoteBuild(root, entryPoint, outputDir = OUTPUT_DIR) {
|
|
66
|
+
const fileInfo = path.parse(entryPoint);
|
|
67
|
+
|
|
68
|
+
if (!allowedExtensions.includes(fileInfo.ext)) {
|
|
69
|
+
throw new Error(`${extensionErrorBaseMessage} ${fileInfo.ext}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const output = getUrlSafeFileName(entryPoint);
|
|
73
|
+
await buildSingleExtension({
|
|
74
|
+
file: entryPoint,
|
|
75
|
+
outputFileName: output,
|
|
76
|
+
outputDir,
|
|
77
|
+
plugins: {
|
|
78
|
+
rollup: [manifestPlugin({ minify: true, output })],
|
|
79
|
+
},
|
|
80
|
+
minify: true,
|
|
81
|
+
root,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
buildAllExtensions,
|
|
87
|
+
buildSingleExtension,
|
|
88
|
+
remoteBuild,
|
|
89
|
+
extensionErrorBaseMessage,
|
|
90
|
+
};
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { MAIN_APP_CONFIG } = require('./constants');
|
|
3
|
+
|
|
4
|
+
function _loadRequiredConfigFile(filePath) {
|
|
5
|
+
let config;
|
|
6
|
+
try {
|
|
7
|
+
config = require(filePath);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Unable to load ${filePath} file. Please make sure you are running the command from the src/app/extensions directory and that ${filePath} exists`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function loadConfig() {
|
|
17
|
+
// app.json is one level up from the extensions directory, which is where these commands
|
|
18
|
+
// will need to be ran from, the extensions directory
|
|
19
|
+
const configPath = path.join(process.cwd(), '..', MAIN_APP_CONFIG);
|
|
20
|
+
|
|
21
|
+
const mainAppConfig = _loadRequiredConfigFile(configPath);
|
|
22
|
+
|
|
23
|
+
const crmCardsSubConfigFiles = mainAppConfig?.extensions?.crm?.cards;
|
|
24
|
+
if (!crmCardsSubConfigFiles || crmCardsSubConfigFiles.length === 0) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`The "extensions.crm.cards" array in ${configPath} is missing or empty, it is a required configuration property`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const outputConfig = {};
|
|
31
|
+
|
|
32
|
+
crmCardsSubConfigFiles.forEach(card => {
|
|
33
|
+
const parsedFile = path.parse(card.file);
|
|
34
|
+
if (parsedFile.dir.startsWith('./extensions')) {
|
|
35
|
+
parsedFile.dir = parsedFile.dir.replace('./extensions', './'); // go up one level
|
|
36
|
+
}
|
|
37
|
+
// Get the path to the config file relative to the extensions directory
|
|
38
|
+
const configPathRelativeToExtensions = parsedFile.dir;
|
|
39
|
+
const extensionsPrefixRemoved = path.format(parsedFile);
|
|
40
|
+
const cardConfigPath = path.join(process.cwd(), extensionsPrefixRemoved);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const cardConfig = require(cardConfigPath);
|
|
44
|
+
if (!cardConfig.data) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Invalid config file at path ${cardConfigPath}, data is a required config property`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!cardConfig.data.module) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Invalid config file at path ${cardConfigPath}, data.module is a require property`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Join the two relative paths
|
|
57
|
+
const entryPointPath = path.join(
|
|
58
|
+
configPathRelativeToExtensions,
|
|
59
|
+
cardConfig.data.module.file
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
cardConfig.data.module.file = entryPointPath;
|
|
63
|
+
|
|
64
|
+
outputConfig[entryPointPath] = cardConfig;
|
|
65
|
+
outputConfig[entryPointPath].data.appName = mainAppConfig.name;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
if (e?.code === 'MODULE_NOT_FOUND') {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Unable to load "${cardConfigPath}" file. \nPlease make sure you are running the command from the src/app/extensions directory and that your card JSON config exists within it.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return outputConfig;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
loadConfig,
|
|
81
|
+
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
const VITE_DEFAULT_PORT = 5173;
|
|
2
|
-
const MAIN_APP_CONFIG = 'app.json';
|
|
3
|
-
const PROJECT_CONFIG = 'hsproject.json';
|
|
4
1
|
const OUTPUT_DIR = 'dist';
|
|
2
|
+
const MAIN_APP_CONFIG = 'app.json';
|
|
3
|
+
const MANIFEST_FILE = 'manifest.json';
|
|
4
|
+
|
|
5
|
+
const VITE_DEFAULT_PORT = 5173;
|
|
6
|
+
const WEBSOCKET_PORT = 5174;
|
|
5
7
|
|
|
6
8
|
const ROLLUP_OPTIONS = {
|
|
7
9
|
// Deps to exclude from the bundle
|
|
@@ -20,11 +22,12 @@ const EXTENSIONS_MESSAGE_VERSION = 0;
|
|
|
20
22
|
const WEBSOCKET_MESSAGE_VERSION = 0;
|
|
21
23
|
|
|
22
24
|
module.exports = {
|
|
23
|
-
VITE_DEFAULT_PORT,
|
|
24
25
|
ROLLUP_OPTIONS,
|
|
25
|
-
MAIN_APP_CONFIG,
|
|
26
|
-
PROJECT_CONFIG,
|
|
27
26
|
OUTPUT_DIR,
|
|
27
|
+
MAIN_APP_CONFIG,
|
|
28
|
+
MANIFEST_FILE,
|
|
28
29
|
EXTENSIONS_MESSAGE_VERSION,
|
|
30
|
+
VITE_DEFAULT_PORT,
|
|
29
31
|
WEBSOCKET_MESSAGE_VERSION,
|
|
32
|
+
WEBSOCKET_PORT,
|
|
30
33
|
};
|
package/lib/dev.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { createServer } = require('vite');
|
|
2
|
+
const startDevServer = require('./server');
|
|
3
|
+
const devBuildPlugin = require('./plugins/devBuildPlugin');
|
|
4
|
+
const { getUrlSafeFileName } = require('./utils');
|
|
5
|
+
const {
|
|
6
|
+
VITE_DEFAULT_PORT,
|
|
7
|
+
WEBSOCKET_PORT,
|
|
8
|
+
OUTPUT_DIR,
|
|
9
|
+
} = require('./constants');
|
|
10
|
+
const { loadConfig } = require('./config');
|
|
11
|
+
|
|
12
|
+
async function _createViteDevServer(
|
|
13
|
+
outputDir,
|
|
14
|
+
extensionConfig,
|
|
15
|
+
websocketPort,
|
|
16
|
+
baseMessage,
|
|
17
|
+
logger
|
|
18
|
+
) {
|
|
19
|
+
return await createServer({
|
|
20
|
+
appType: 'custom',
|
|
21
|
+
mode: 'development',
|
|
22
|
+
server: {
|
|
23
|
+
middlewareMode: true,
|
|
24
|
+
hmr: {
|
|
25
|
+
port: websocketPort,
|
|
26
|
+
},
|
|
27
|
+
watch: {
|
|
28
|
+
ignored: [`${process.cwd()}/${outputDir}/**/*`],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
build: {
|
|
32
|
+
rollupOptions: {
|
|
33
|
+
input: extensionConfig.data.module.file,
|
|
34
|
+
output: getUrlSafeFileName(extensionConfig.data.module.file),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
plugins: [
|
|
38
|
+
devBuildPlugin({ extensionConfig, outputDir, baseMessage, logger }),
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function startDevMode({
|
|
44
|
+
extension,
|
|
45
|
+
logger,
|
|
46
|
+
outputDir = OUTPUT_DIR,
|
|
47
|
+
expressPort = VITE_DEFAULT_PORT,
|
|
48
|
+
webSocketPort = WEBSOCKET_PORT,
|
|
49
|
+
}) {
|
|
50
|
+
if (!extension) {
|
|
51
|
+
throw new Error('Unable to determine which extension to run');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const config = loadConfig();
|
|
55
|
+
const extensionConfig = config[extension];
|
|
56
|
+
if (!extensionConfig) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unable to locate a configuration file for the specified extension ${extension}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
extensionConfig.output = getUrlSafeFileName(extensionConfig.data.module.file);
|
|
63
|
+
|
|
64
|
+
const baseMessage = Object.freeze({
|
|
65
|
+
appName: extensionConfig.data.appName,
|
|
66
|
+
title: extensionConfig.data.title,
|
|
67
|
+
callback: `http://hslocal.net:${expressPort}/${extensionConfig.output}`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const viteDevServer = await _createViteDevServer(
|
|
71
|
+
outputDir,
|
|
72
|
+
extensionConfig,
|
|
73
|
+
webSocketPort,
|
|
74
|
+
baseMessage,
|
|
75
|
+
logger
|
|
76
|
+
);
|
|
77
|
+
startDevServer(
|
|
78
|
+
outputDir,
|
|
79
|
+
expressPort,
|
|
80
|
+
webSocketPort,
|
|
81
|
+
baseMessage,
|
|
82
|
+
viteDevServer,
|
|
83
|
+
logger
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
startDevMode,
|
|
89
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const { ROLLUP_OPTIONS, WEBSOCKET_MESSAGE_VERSION } = require('../constants');
|
|
2
|
+
const { build } = require('vite');
|
|
3
|
+
const manifestPlugin = require('./manifestPlugin');
|
|
4
|
+
const { stripAnsiColorCodes } = require('../utils');
|
|
5
|
+
|
|
6
|
+
function devBuildPlugin(options = {}) {
|
|
7
|
+
const { extensionConfig, outputDir, baseMessage, logger } = options;
|
|
8
|
+
const versionedBaseMessage = {
|
|
9
|
+
...baseMessage,
|
|
10
|
+
version: WEBSOCKET_MESSAGE_VERSION,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const handleBuildError = (error, server) => {
|
|
14
|
+
const { plugin, hook, code, errors, frame, loc, id } = error;
|
|
15
|
+
if (
|
|
16
|
+
plugin === 'vite:esbuild' &&
|
|
17
|
+
hook === 'transform' &&
|
|
18
|
+
code === 'PLUGIN_ERROR'
|
|
19
|
+
) {
|
|
20
|
+
server.ws.send({
|
|
21
|
+
...versionedBaseMessage,
|
|
22
|
+
event: 'error',
|
|
23
|
+
error: {
|
|
24
|
+
type: 'transformation',
|
|
25
|
+
details: {
|
|
26
|
+
errors,
|
|
27
|
+
formattedError: stripAnsiColorCodes(frame),
|
|
28
|
+
location: loc,
|
|
29
|
+
file: id,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const devBuild = async server => {
|
|
37
|
+
try {
|
|
38
|
+
await build({
|
|
39
|
+
mode: 'development',
|
|
40
|
+
define: {
|
|
41
|
+
'process.env.NODE_ENV': JSON.stringify(
|
|
42
|
+
process.env.NODE_ENV || 'development'
|
|
43
|
+
),
|
|
44
|
+
},
|
|
45
|
+
build: {
|
|
46
|
+
lib: {
|
|
47
|
+
entry: extensionConfig.data.module.file,
|
|
48
|
+
name: extensionConfig.output,
|
|
49
|
+
formats: ['iife'],
|
|
50
|
+
fileName: () => extensionConfig.output,
|
|
51
|
+
},
|
|
52
|
+
rollupOptions: {
|
|
53
|
+
...ROLLUP_OPTIONS,
|
|
54
|
+
plugins: [
|
|
55
|
+
...(ROLLUP_OPTIONS.plugins || []),
|
|
56
|
+
manifestPlugin({
|
|
57
|
+
minify: false,
|
|
58
|
+
output: extensionConfig.output,
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
output: {
|
|
62
|
+
...ROLLUP_OPTIONS.output,
|
|
63
|
+
sourcemap: 'inline',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
outDir: outputDir,
|
|
67
|
+
emptyOutDir: true,
|
|
68
|
+
minify: false,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
handleBuildError(error, server);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let localServer;
|
|
79
|
+
return {
|
|
80
|
+
name: 'ui-extensibility-dev-build-plugin',
|
|
81
|
+
enforce: 'pre',
|
|
82
|
+
configureServer: async server => {
|
|
83
|
+
// Store a reference to the server to be used in hooks that don't get the server injected
|
|
84
|
+
// See https://vitejs.dev/guide/api-plugin.html#configureserver for information on this pattern
|
|
85
|
+
localServer = server;
|
|
86
|
+
localServer.ws.on('connection', () => {
|
|
87
|
+
logger.info('Browser connected and listening for bundle updates');
|
|
88
|
+
localServer.ws.send({
|
|
89
|
+
...versionedBaseMessage,
|
|
90
|
+
event: 'start',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
localServer.ws.on('build', async () => {
|
|
94
|
+
logger.info('Browser has requested a build, rebuilding');
|
|
95
|
+
const successful = await devBuild(localServer);
|
|
96
|
+
if (successful) {
|
|
97
|
+
server.ws.send({
|
|
98
|
+
...versionedBaseMessage,
|
|
99
|
+
event: 'update',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
await devBuild(localServer);
|
|
104
|
+
},
|
|
105
|
+
handleHotUpdate: async ({ server }) => {
|
|
106
|
+
const successful = await devBuild(server);
|
|
107
|
+
|
|
108
|
+
if (!successful) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (server.ws.clients.size === 0) {
|
|
113
|
+
logger.warn('Bundle updated, no browsers connected to notify');
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
logger.info('Bundle updated, notifying connected browsers');
|
|
118
|
+
server.ws.send({
|
|
119
|
+
...versionedBaseMessage,
|
|
120
|
+
event: 'update',
|
|
121
|
+
});
|
|
122
|
+
return [];
|
|
123
|
+
},
|
|
124
|
+
buildEnd(error) {
|
|
125
|
+
if (error) {
|
|
126
|
+
logger.error(error);
|
|
127
|
+
}
|
|
128
|
+
logger.warn('Sending shutdown message to connected browsers');
|
|
129
|
+
if (localServer && localServer.ws) {
|
|
130
|
+
localServer.ws.send({
|
|
131
|
+
...versionedBaseMessage,
|
|
132
|
+
event: 'shutdown',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = devBuildPlugin;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const { readFileSync } = require('fs');
|
|
2
2
|
const { normalize } = require('path');
|
|
3
|
-
const
|
|
3
|
+
const { MANIFEST_FILE } = require('../constants');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
|
|
5
|
-
const DEFAULT_MANIFEST_NAME = 'manifest.json';
|
|
6
6
|
const PACKAGE_LOCK_FILE = 'package-lock.json';
|
|
7
7
|
const PACKAGE_FILE = 'package.json';
|
|
8
8
|
const EXTENSIONS_PATH = 'src/app/extensions/';
|
|
@@ -12,19 +12,16 @@ function plugin(options = {}) {
|
|
|
12
12
|
name: 'ui-extensions-manifest-generation-plugin',
|
|
13
13
|
enforce: 'post', // run after default rollup plugins
|
|
14
14
|
generateBundle(_rollupOptions, bundle) {
|
|
15
|
-
const {
|
|
16
|
-
output = DEFAULT_MANIFEST_NAME,
|
|
17
|
-
minify = false,
|
|
18
|
-
extensionConfig,
|
|
19
|
-
} = options;
|
|
15
|
+
const { output, minify = false, logger } = options;
|
|
20
16
|
try {
|
|
21
|
-
const
|
|
17
|
+
const filename = path.parse(output).name;
|
|
18
|
+
const manifest = _generateManifestContents(bundle);
|
|
22
19
|
this.emitFile({
|
|
23
20
|
type: 'asset',
|
|
24
21
|
source: minify
|
|
25
22
|
? JSON.stringify(manifest)
|
|
26
23
|
: JSON.stringify(manifest, null, 2),
|
|
27
|
-
fileName: normalize(
|
|
24
|
+
fileName: normalize(`${filename}-${MANIFEST_FILE}`),
|
|
28
25
|
});
|
|
29
26
|
} catch (e) {
|
|
30
27
|
logger.warn(`\nUnable to write manifest file in ${output}, ${e}`);
|
|
@@ -33,14 +30,13 @@ function plugin(options = {}) {
|
|
|
33
30
|
};
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
function _generateManifestContents(bundle
|
|
33
|
+
function _generateManifestContents(bundle) {
|
|
37
34
|
const baseManifest = {
|
|
38
35
|
package: _loadPackageFile(),
|
|
39
|
-
extension,
|
|
40
36
|
};
|
|
41
37
|
|
|
42
38
|
// The keys to bundle are the filename without any path information
|
|
43
|
-
const bundles = Object.keys(bundle);
|
|
39
|
+
const bundles = Object.keys(bundle).filter(cur => cur.endsWith('.js'));
|
|
44
40
|
|
|
45
41
|
if (bundles.length === 1) {
|
|
46
42
|
return {
|
|
@@ -86,7 +82,7 @@ function _loadPackageFile() {
|
|
|
86
82
|
}
|
|
87
83
|
|
|
88
84
|
function _stripPathPriorToExtDir(filepath) {
|
|
89
|
-
return filepath
|
|
85
|
+
return filepath?.split(EXTENSIONS_PATH).pop();
|
|
90
86
|
}
|
|
91
87
|
|
|
92
88
|
function _buildModulesInfo(moduleIds, modules) {
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const cors = require('cors');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { EXTENSIONS_MESSAGE_VERSION, MANIFEST_FILE } = require('./constants');
|
|
6
|
+
|
|
7
|
+
function startDevServer(
|
|
8
|
+
outputDir,
|
|
9
|
+
expressPort,
|
|
10
|
+
webSocketPort,
|
|
11
|
+
baseMessage,
|
|
12
|
+
viteDevServer,
|
|
13
|
+
logger
|
|
14
|
+
) {
|
|
15
|
+
const app = express();
|
|
16
|
+
|
|
17
|
+
// Setup middleware
|
|
18
|
+
app.use(cors());
|
|
19
|
+
app.use(express.static(outputDir));
|
|
20
|
+
_addExtensionsEndpoint(
|
|
21
|
+
app,
|
|
22
|
+
expressPort,
|
|
23
|
+
webSocketPort,
|
|
24
|
+
outputDir,
|
|
25
|
+
baseMessage,
|
|
26
|
+
logger
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Vite middlewares needs to go last because it's greedy and will block other middleware
|
|
30
|
+
app.use(viteDevServer.middlewares);
|
|
31
|
+
|
|
32
|
+
const server = app.listen({ port: expressPort }, () => {
|
|
33
|
+
logger.warn(`Listening at ${baseMessage.callback}`);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
_configureShutDownHandlers(server, viteDevServer, logger);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function _addExtensionsEndpoint(
|
|
40
|
+
server,
|
|
41
|
+
expressPort,
|
|
42
|
+
webSocketPort,
|
|
43
|
+
outputDir,
|
|
44
|
+
baseMessage,
|
|
45
|
+
logger
|
|
46
|
+
) {
|
|
47
|
+
const endpoint = '/extensions';
|
|
48
|
+
server.get(endpoint, (_req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const output = path.parse(baseMessage.callback).name;
|
|
51
|
+
const manifest = JSON.parse(
|
|
52
|
+
fs.readFileSync(
|
|
53
|
+
path.join(process.cwd(), `${outputDir}/${output}-${MANIFEST_FILE}`)
|
|
54
|
+
)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const response = {
|
|
58
|
+
websocket: `ws://localhost:${webSocketPort}`,
|
|
59
|
+
version: EXTENSIONS_MESSAGE_VERSION,
|
|
60
|
+
extensions: [
|
|
61
|
+
{
|
|
62
|
+
...baseMessage,
|
|
63
|
+
manifest,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
res.status(200).json(response);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
res.status(500).json({
|
|
70
|
+
message: 'Unable to load manifest file',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
logger.warn(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _configureShutDownHandlers(server, viteDevServer, logger) {
|
|
78
|
+
async function shutdown() {
|
|
79
|
+
logger.warn('\nCleaning up after ourselves...');
|
|
80
|
+
await viteDevServer.pluginContainer.close();
|
|
81
|
+
// Stop new connections to express server
|
|
82
|
+
server.close(() => {});
|
|
83
|
+
logger.info('Clean up done, exiting.');
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.on('SIGINT', shutdown);
|
|
88
|
+
process.on('SIGTERM', shutdown);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = startDevServer;
|