@hubspot/ui-extensions-dev-server 0.3.1 → 0.5.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/{lib → cli}/config.js +54 -13
- package/cli/run.js +34 -6
- package/index.js +2 -2
- package/lib/DevModeInterface.js +130 -0
- package/lib/build.js +15 -6
- package/lib/constants.js +7 -1
- package/lib/dev.js +20 -18
- package/lib/extensionsService.js +47 -0
- package/lib/plugins/devBuildPlugin.js +4 -2
- package/lib/plugins/manifestPlugin.js +2 -3
- package/lib/server.js +21 -57
- package/lib/utils.js +13 -0
- package/package.json +7 -6
- package/cli/userInput.js +0 -32
- package/lib/extensions.js +0 -9
package/{lib → cli}/config.js
RENAMED
|
@@ -1,24 +1,63 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
|
-
const {
|
|
3
|
+
const { getUrlSafeFileName } = require('../lib/utils');
|
|
4
|
+
const { MAIN_APP_CONFIG } = require('../lib/constants');
|
|
3
5
|
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
6
|
+
function loadConfigByPath(configPath) {
|
|
7
|
+
const source = fs.readFileSync(configPath);
|
|
8
|
+
return JSON.parse(source);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function loadExtensionConfig(appConfig, appPath) {
|
|
12
|
+
const crmCardsSubConfigFiles = appConfig?.extensions?.crm?.cards;
|
|
13
|
+
|
|
14
|
+
const outputConfig = {};
|
|
15
|
+
|
|
16
|
+
crmCardsSubConfigFiles.forEach(card => {
|
|
17
|
+
const cardConfigPath = path.join(appPath, card.file);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const cardConfig = loadConfigByPath(cardConfigPath);
|
|
21
|
+
|
|
22
|
+
if (cardConfig && cardConfig.data) {
|
|
23
|
+
const cardConfigDir = path.parse(cardConfigPath).dir;
|
|
24
|
+
|
|
25
|
+
const entryPointPath = path.join(
|
|
26
|
+
cardConfigDir,
|
|
27
|
+
cardConfig.data?.module?.file
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
cardConfig.data.module.file = entryPointPath;
|
|
31
|
+
|
|
32
|
+
outputConfig[entryPointPath] = {
|
|
33
|
+
...cardConfig,
|
|
34
|
+
output: getUrlSafeFileName(entryPointPath),
|
|
35
|
+
path: appPath,
|
|
36
|
+
extensionPath: path.parse(entryPointPath).dir,
|
|
37
|
+
data: {
|
|
38
|
+
...cardConfig.data,
|
|
39
|
+
appName: appConfig.name,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw new Error(`Unable to load ${cardConfigPath}`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return outputConfig;
|
|
14
49
|
}
|
|
15
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Will be removed after integration with hubspot-cli is complete
|
|
53
|
+
* This version of load config makes assumptions about the location it is being ran from where the others do not
|
|
54
|
+
*/
|
|
16
55
|
function loadConfig() {
|
|
17
56
|
// app.json is one level up from the extensions directory, which is where these commands
|
|
18
57
|
// will need to be ran from, the extensions directory
|
|
19
58
|
const configPath = path.join(process.cwd(), '..', MAIN_APP_CONFIG);
|
|
20
59
|
|
|
21
|
-
const mainAppConfig =
|
|
60
|
+
const mainAppConfig = loadConfigByPath(configPath);
|
|
22
61
|
|
|
23
62
|
const crmCardsSubConfigFiles = mainAppConfig?.extensions?.crm?.cards;
|
|
24
63
|
if (!crmCardsSubConfigFiles || crmCardsSubConfigFiles.length === 0) {
|
|
@@ -32,7 +71,7 @@ function loadConfig() {
|
|
|
32
71
|
crmCardsSubConfigFiles.forEach(card => {
|
|
33
72
|
const cardConfigPath = path.join(process.cwd(), '..', card.file);
|
|
34
73
|
try {
|
|
35
|
-
const cardConfig =
|
|
74
|
+
const cardConfig = loadConfigByPath(cardConfigPath);
|
|
36
75
|
if (!cardConfig.data) {
|
|
37
76
|
throw new Error(
|
|
38
77
|
`Invalid config file at path ${cardConfigPath}, data is a required config property`
|
|
@@ -66,5 +105,7 @@ function loadConfig() {
|
|
|
66
105
|
}
|
|
67
106
|
|
|
68
107
|
module.exports = {
|
|
108
|
+
loadConfigByPath,
|
|
109
|
+
loadExtensionConfig,
|
|
69
110
|
loadConfig,
|
|
70
111
|
};
|
package/cli/run.js
CHANGED
|
@@ -4,12 +4,14 @@ const { parseArgs, showHelp } = require('../cli/utils');
|
|
|
4
4
|
const {
|
|
5
5
|
buildAllExtensions,
|
|
6
6
|
buildSingleExtension,
|
|
7
|
-
|
|
7
|
+
DevModeInterface,
|
|
8
8
|
} = require('../index');
|
|
9
9
|
|
|
10
|
-
const { promptForExtensionToRun } = require('./userInput');
|
|
11
|
-
const OUTPUT_DIR = 'dist';
|
|
12
10
|
const logger = require('./logger');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { MAIN_APP_CONFIG, OUTPUT_DIR } = require('../lib/constants');
|
|
13
|
+
const inquirer = require('inquirer');
|
|
14
|
+
const { loadConfigByPath, loadExtensionConfig } = require('./config');
|
|
13
15
|
|
|
14
16
|
// eslint-disable-next-line no-floating-promise/no-floating-promise
|
|
15
17
|
(async () => {
|
|
@@ -18,11 +20,29 @@ const logger = require('./logger');
|
|
|
18
20
|
if (help || !(DEV_MODE || BUILD_MODE)) {
|
|
19
21
|
showHelp(OUTPUT_DIR);
|
|
20
22
|
} else if (DEV_MODE) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const extensionPath = process.cwd(); // Assumed to be /path/to/src/app/extensions
|
|
24
|
+
const appPath = path.join(extensionPath, '..');
|
|
25
|
+
const appConfig = loadConfigByPath(path.join(appPath, MAIN_APP_CONFIG));
|
|
26
|
+
|
|
27
|
+
let extensionConfig;
|
|
28
|
+
if (extension) {
|
|
29
|
+
const allExtensionsConfig = loadExtensionConfig(appConfig, appPath);
|
|
30
|
+
extensionConfig =
|
|
31
|
+
allExtensionsConfig[path.join(extensionPath, extension)];
|
|
32
|
+
}
|
|
33
|
+
await DevModeInterface.setup({
|
|
34
|
+
promptUser: inquirer.createPromptModule(),
|
|
35
|
+
components: {
|
|
36
|
+
[appConfig.name]: {
|
|
37
|
+
config: appConfig,
|
|
38
|
+
path: path.join(extensionPath, '..'),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
extensionConfig,
|
|
24
42
|
logger,
|
|
25
43
|
});
|
|
44
|
+
|
|
45
|
+
await DevModeInterface.start({});
|
|
26
46
|
} else if (BUILD_MODE) {
|
|
27
47
|
if (extension) {
|
|
28
48
|
buildSingleExtension({
|
|
@@ -34,3 +54,11 @@ const logger = require('./logger');
|
|
|
34
54
|
}
|
|
35
55
|
}
|
|
36
56
|
})();
|
|
57
|
+
|
|
58
|
+
async function shutdown() {
|
|
59
|
+
await DevModeInterface.cleanup();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.on('SIGINT', shutdown);
|
|
64
|
+
process.on('SIGTERM', shutdown);
|
package/index.js
CHANGED
|
@@ -4,11 +4,11 @@ const {
|
|
|
4
4
|
buildSingleExtension,
|
|
5
5
|
} = require('./lib/build');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const DevModeInterface = require('./lib/DevModeInterface');
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
remoteBuild,
|
|
11
11
|
buildAllExtensions,
|
|
12
12
|
buildSingleExtension,
|
|
13
|
-
|
|
13
|
+
DevModeInterface,
|
|
14
14
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const { startDevMode } = require('./dev');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { OUTPUT_DIR } = require('./constants');
|
|
4
|
+
const { loadExtensionConfig } = require('../cli/config');
|
|
5
|
+
|
|
6
|
+
class DevModeInterface {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._setupLogger(console);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
_setupLogger(_logger) {
|
|
12
|
+
this.logger = {
|
|
13
|
+
..._logger,
|
|
14
|
+
debug: (...args) => {
|
|
15
|
+
if (this.debug) {
|
|
16
|
+
_logger.debug(...args);
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_setDataFromExtensionConfig(extensionConfig) {
|
|
23
|
+
this.config = extensionConfig;
|
|
24
|
+
this.appName = extensionConfig.data.appName;
|
|
25
|
+
this.title = extensionConfig.data.title;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_generateAppExtensionMappings(components) {
|
|
29
|
+
// Loop over all of the app configs that are passed in
|
|
30
|
+
const allComponentNames = Object.keys(components);
|
|
31
|
+
|
|
32
|
+
return allComponentNames.reduce((appExtensionMappings, componentName) => {
|
|
33
|
+
const component = components[componentName];
|
|
34
|
+
if (!component.config.extensions?.crm?.cards) {
|
|
35
|
+
return appExtensionMappings; // It's not an app
|
|
36
|
+
}
|
|
37
|
+
// Load all of the extension configs for a particular app.json file
|
|
38
|
+
const extensionsConfigForApp = loadExtensionConfig(
|
|
39
|
+
component.config,
|
|
40
|
+
component.path
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const extensionFilePaths = Object.keys(extensionsConfigForApp);
|
|
44
|
+
// Loop over the loaded extension configs and generate the list of choices to use to prompt the user for input
|
|
45
|
+
extensionFilePaths.forEach(extensionPath => {
|
|
46
|
+
const extensionConfig = extensionsConfigForApp[extensionPath];
|
|
47
|
+
appExtensionMappings.push({
|
|
48
|
+
name: `${componentName}/${extensionConfig.data.title}`,
|
|
49
|
+
value: extensionConfig,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return appExtensionMappings;
|
|
54
|
+
}, []);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async setup({
|
|
58
|
+
debug = false,
|
|
59
|
+
accountId,
|
|
60
|
+
httpClient,
|
|
61
|
+
promptUser,
|
|
62
|
+
components,
|
|
63
|
+
extensionConfig,
|
|
64
|
+
logger,
|
|
65
|
+
}) {
|
|
66
|
+
this.debug = debug;
|
|
67
|
+
this.accountId = accountId;
|
|
68
|
+
this.httpClient = httpClient;
|
|
69
|
+
if (logger) {
|
|
70
|
+
this._setupLogger(logger);
|
|
71
|
+
}
|
|
72
|
+
if (extensionConfig) {
|
|
73
|
+
this._setDataFromExtensionConfig(extensionConfig);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const choices = this._generateAppExtensionMappings(components);
|
|
78
|
+
|
|
79
|
+
if (choices.length === 0) {
|
|
80
|
+
throw new Error('No extensions to run');
|
|
81
|
+
} else if (choices.length === 1) {
|
|
82
|
+
this._setDataFromExtensionConfig(choices[0].value);
|
|
83
|
+
} else {
|
|
84
|
+
const answers = await promptUser({
|
|
85
|
+
type: 'list',
|
|
86
|
+
name: 'extension',
|
|
87
|
+
message: 'Which extension would you like to run?',
|
|
88
|
+
choices,
|
|
89
|
+
});
|
|
90
|
+
this._setDataFromExtensionConfig(answers.extension);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async start({ debug }) {
|
|
95
|
+
this.debug = debug;
|
|
96
|
+
if (!this.config || !this.config.path) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Unable to load the required extension configuration files'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
const appPath = this.config.path;
|
|
102
|
+
|
|
103
|
+
// Pass options from the CLI for running app functions locally
|
|
104
|
+
const functionsConfig = {
|
|
105
|
+
app: { path: appPath },
|
|
106
|
+
accountId: this.accountId,
|
|
107
|
+
httpClient: this.httpClient,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
this.shutdown = await startDevMode({
|
|
111
|
+
extensionConfig: this.config,
|
|
112
|
+
outputDir: path.join(this.config.extensionPath, OUTPUT_DIR),
|
|
113
|
+
functionsConfig,
|
|
114
|
+
logger: this.logger,
|
|
115
|
+
root: appPath,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.logger.info(
|
|
119
|
+
`Running extension '${this.title}' from app '${this.appName}'`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async cleanup() {
|
|
124
|
+
if (this.shutdown) {
|
|
125
|
+
await this.shutdown();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = new DevModeInterface();
|
package/lib/build.js
CHANGED
|
@@ -3,14 +3,14 @@ const { ROLLUP_OPTIONS, OUTPUT_DIR } = require('./constants');
|
|
|
3
3
|
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getUrlSafeFileName } = require('./utils');
|
|
6
|
-
const { loadConfig } = require('
|
|
6
|
+
const { loadConfig } = require('../cli/config');
|
|
7
7
|
|
|
8
8
|
const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
|
|
9
9
|
const extensionErrorBaseMessage = `Supported file extensions are [${allowedExtensions.join(
|
|
10
10
|
', '
|
|
11
11
|
)}], received:`;
|
|
12
12
|
|
|
13
|
-
async function buildAllExtensions({ outputDir }) {
|
|
13
|
+
async function buildAllExtensions({ outputDir, logger = console }) {
|
|
14
14
|
const config = loadConfig();
|
|
15
15
|
const extensionKeys = Object.keys(config);
|
|
16
16
|
for (let i = 0; i < extensionKeys.length; ++i) {
|
|
@@ -22,7 +22,10 @@ async function buildAllExtensions({ outputDir }) {
|
|
|
22
22
|
emptyOutDir: i === 0,
|
|
23
23
|
plugins: {
|
|
24
24
|
rollup: [
|
|
25
|
-
manifestPlugin({
|
|
25
|
+
manifestPlugin({
|
|
26
|
+
output: getUrlSafeFileName(data.module.file),
|
|
27
|
+
logger,
|
|
28
|
+
}),
|
|
26
29
|
],
|
|
27
30
|
},
|
|
28
31
|
});
|
|
@@ -35,6 +38,7 @@ async function buildSingleExtension({
|
|
|
35
38
|
emptyOutDir = true,
|
|
36
39
|
minify = false,
|
|
37
40
|
root = process.cwd(), // This is the vite default, so using that as our default
|
|
41
|
+
logger = console,
|
|
38
42
|
}) {
|
|
39
43
|
const output = getUrlSafeFileName(file);
|
|
40
44
|
await build({
|
|
@@ -53,7 +57,7 @@ async function buildSingleExtension({
|
|
|
53
57
|
},
|
|
54
58
|
rollupOptions: {
|
|
55
59
|
...ROLLUP_OPTIONS,
|
|
56
|
-
plugins: [manifestPlugin({ output })],
|
|
60
|
+
plugins: [manifestPlugin({ output, logger })],
|
|
57
61
|
},
|
|
58
62
|
outDir: outputDir,
|
|
59
63
|
emptyOutDir,
|
|
@@ -62,7 +66,12 @@ async function buildSingleExtension({
|
|
|
62
66
|
});
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
async function remoteBuild(
|
|
69
|
+
async function remoteBuild(
|
|
70
|
+
root,
|
|
71
|
+
entryPoint,
|
|
72
|
+
outputDir = OUTPUT_DIR,
|
|
73
|
+
logger = console
|
|
74
|
+
) {
|
|
66
75
|
const fileInfo = path.parse(entryPoint);
|
|
67
76
|
|
|
68
77
|
if (!allowedExtensions.includes(fileInfo.ext)) {
|
|
@@ -75,7 +84,7 @@ async function remoteBuild(root, entryPoint, outputDir = OUTPUT_DIR) {
|
|
|
75
84
|
outputFileName: output,
|
|
76
85
|
outputDir,
|
|
77
86
|
plugins: {
|
|
78
|
-
rollup: [manifestPlugin({ minify: true, output })],
|
|
87
|
+
rollup: [manifestPlugin({ minify: true, output, logger })],
|
|
79
88
|
},
|
|
80
89
|
minify: true,
|
|
81
90
|
root,
|
package/lib/constants.js
CHANGED
|
@@ -18,9 +18,14 @@ const ROLLUP_OPTIONS = {
|
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
const EXTENSIONS_MESSAGE_VERSION =
|
|
21
|
+
const EXTENSIONS_MESSAGE_VERSION = 1;
|
|
22
22
|
const WEBSOCKET_MESSAGE_VERSION = 0;
|
|
23
23
|
|
|
24
|
+
const SERVER_CAPABILITIES = [
|
|
25
|
+
// Supports running app functions locally
|
|
26
|
+
'app-functions-local-dev',
|
|
27
|
+
];
|
|
28
|
+
|
|
24
29
|
module.exports = {
|
|
25
30
|
ROLLUP_OPTIONS,
|
|
26
31
|
OUTPUT_DIR,
|
|
@@ -30,4 +35,5 @@ module.exports = {
|
|
|
30
35
|
VITE_DEFAULT_PORT,
|
|
31
36
|
WEBSOCKET_MESSAGE_VERSION,
|
|
32
37
|
WEBSOCKET_PORT,
|
|
38
|
+
SERVER_CAPABILITIES,
|
|
33
39
|
};
|
package/lib/dev.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { createServer } = require('vite');
|
|
2
|
+
const path = require('path');
|
|
2
3
|
const startDevServer = require('./server');
|
|
3
4
|
const devBuildPlugin = require('./plugins/devBuildPlugin');
|
|
4
5
|
const { getUrlSafeFileName } = require('./utils');
|
|
@@ -7,16 +8,17 @@ const {
|
|
|
7
8
|
WEBSOCKET_PORT,
|
|
8
9
|
OUTPUT_DIR,
|
|
9
10
|
} = require('./constants');
|
|
10
|
-
const { loadConfig } = require('./config');
|
|
11
11
|
|
|
12
12
|
async function _createViteDevServer(
|
|
13
13
|
outputDir,
|
|
14
14
|
extensionConfig,
|
|
15
15
|
websocketPort,
|
|
16
16
|
baseMessage,
|
|
17
|
-
logger
|
|
17
|
+
logger,
|
|
18
|
+
root
|
|
18
19
|
) {
|
|
19
20
|
return await createServer({
|
|
21
|
+
root,
|
|
20
22
|
appType: 'custom',
|
|
21
23
|
mode: 'development',
|
|
22
24
|
server: {
|
|
@@ -25,7 +27,7 @@ async function _createViteDevServer(
|
|
|
25
27
|
port: websocketPort,
|
|
26
28
|
},
|
|
27
29
|
watch: {
|
|
28
|
-
ignored: [
|
|
30
|
+
ignored: [path.join(outputDir, '/**/*')],
|
|
29
31
|
},
|
|
30
32
|
},
|
|
31
33
|
build: {
|
|
@@ -35,32 +37,30 @@ async function _createViteDevServer(
|
|
|
35
37
|
},
|
|
36
38
|
},
|
|
37
39
|
plugins: [
|
|
38
|
-
devBuildPlugin({
|
|
40
|
+
devBuildPlugin({
|
|
41
|
+
extensionConfig,
|
|
42
|
+
outputDir,
|
|
43
|
+
baseMessage,
|
|
44
|
+
logger,
|
|
45
|
+
}),
|
|
39
46
|
],
|
|
47
|
+
clearScreen: false,
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
async function startDevMode({
|
|
44
|
-
|
|
52
|
+
extensionConfig,
|
|
53
|
+
functionsConfig,
|
|
45
54
|
logger,
|
|
46
55
|
outputDir = OUTPUT_DIR,
|
|
47
56
|
expressPort = VITE_DEFAULT_PORT,
|
|
48
57
|
webSocketPort = WEBSOCKET_PORT,
|
|
58
|
+
root = process.cwd(),
|
|
49
59
|
}) {
|
|
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
60
|
if (!extensionConfig) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`Unable to locate a configuration file for the specified extension ${extension}`
|
|
59
|
-
);
|
|
61
|
+
throw new Error('Unable to determine which extension to run');
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
extensionConfig.output = getUrlSafeFileName(extensionConfig.data.module.file);
|
|
63
|
-
|
|
64
64
|
const baseMessage = Object.freeze({
|
|
65
65
|
appName: extensionConfig.data.appName,
|
|
66
66
|
title: extensionConfig.data.title,
|
|
@@ -72,14 +72,16 @@ async function startDevMode({
|
|
|
72
72
|
extensionConfig,
|
|
73
73
|
webSocketPort,
|
|
74
74
|
baseMessage,
|
|
75
|
-
logger
|
|
75
|
+
logger,
|
|
76
|
+
root
|
|
76
77
|
);
|
|
77
|
-
startDevServer(
|
|
78
|
+
return startDevServer(
|
|
78
79
|
outputDir,
|
|
79
80
|
expressPort,
|
|
80
81
|
webSocketPort,
|
|
81
82
|
baseMessage,
|
|
82
83
|
viteDevServer,
|
|
84
|
+
functionsConfig,
|
|
83
85
|
logger
|
|
84
86
|
);
|
|
85
87
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const {
|
|
3
|
+
EXTENSIONS_MESSAGE_VERSION,
|
|
4
|
+
SERVER_CAPABILITIES,
|
|
5
|
+
} = require('./constants');
|
|
6
|
+
const { loadManifest } = require('./utils');
|
|
7
|
+
|
|
8
|
+
class ExtensionsService {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.endpoint = '/extensions';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
add(server, webSocketPort, outputDir, baseMessage) {
|
|
14
|
+
server.get(
|
|
15
|
+
this.endpoint,
|
|
16
|
+
this.generateExtensionsHandler(baseMessage, webSocketPort, outputDir)
|
|
17
|
+
);
|
|
18
|
+
return [this.endpoint];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
generateExtensionsHandler(baseMessage, webSocketPort, outputDir) {
|
|
22
|
+
return function extensionsHandler(_req, res) {
|
|
23
|
+
try {
|
|
24
|
+
const output = path.parse(baseMessage.callback).name;
|
|
25
|
+
|
|
26
|
+
const response = {
|
|
27
|
+
websocket: `ws://localhost:${webSocketPort}`,
|
|
28
|
+
version: EXTENSIONS_MESSAGE_VERSION,
|
|
29
|
+
capabilities: SERVER_CAPABILITIES,
|
|
30
|
+
extensions: [
|
|
31
|
+
{
|
|
32
|
+
...baseMessage,
|
|
33
|
+
manifest: loadManifest(outputDir, output),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
res.status(200).json(response);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
res.status(500).json({
|
|
40
|
+
message: 'Unable to determine which extensions are running',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = new ExtensionsService();
|
|
@@ -36,6 +36,7 @@ function devBuildPlugin(options = {}) {
|
|
|
36
36
|
const devBuild = async server => {
|
|
37
37
|
try {
|
|
38
38
|
await build({
|
|
39
|
+
logLevel: 'warn',
|
|
39
40
|
mode: 'development',
|
|
40
41
|
define: {
|
|
41
42
|
'process.env.NODE_ENV': JSON.stringify(
|
|
@@ -52,10 +53,10 @@ function devBuildPlugin(options = {}) {
|
|
|
52
53
|
rollupOptions: {
|
|
53
54
|
...ROLLUP_OPTIONS,
|
|
54
55
|
plugins: [
|
|
55
|
-
...(ROLLUP_OPTIONS.plugins || []),
|
|
56
56
|
manifestPlugin({
|
|
57
57
|
minify: false,
|
|
58
58
|
output: extensionConfig.output,
|
|
59
|
+
logger,
|
|
59
60
|
}),
|
|
60
61
|
],
|
|
61
62
|
output: {
|
|
@@ -67,6 +68,7 @@ function devBuildPlugin(options = {}) {
|
|
|
67
68
|
emptyOutDir: true,
|
|
68
69
|
minify: false,
|
|
69
70
|
},
|
|
71
|
+
clearScreen: false,
|
|
70
72
|
});
|
|
71
73
|
return true;
|
|
72
74
|
} catch (error) {
|
|
@@ -125,7 +127,7 @@ function devBuildPlugin(options = {}) {
|
|
|
125
127
|
if (error) {
|
|
126
128
|
logger.error(error);
|
|
127
129
|
}
|
|
128
|
-
logger.
|
|
130
|
+
logger.info('Sending shutdown message to connected browsers');
|
|
129
131
|
if (localServer && localServer.ws) {
|
|
130
132
|
localServer.ws.send({
|
|
131
133
|
...versionedBaseMessage,
|
|
@@ -7,7 +7,7 @@ const PACKAGE_LOCK_FILE = 'package-lock.json';
|
|
|
7
7
|
const PACKAGE_FILE = 'package.json';
|
|
8
8
|
const EXTENSIONS_PATH = 'src/app/extensions/';
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
function manifestPlugin(options = {}) {
|
|
11
11
|
return {
|
|
12
12
|
name: 'ui-extensions-manifest-generation-plugin',
|
|
13
13
|
enforce: 'post', // run after default rollup plugins
|
|
@@ -37,7 +37,6 @@ function _generateManifestContents(bundle) {
|
|
|
37
37
|
|
|
38
38
|
// The keys to bundle are the filename without any path information
|
|
39
39
|
const bundles = Object.keys(bundle).filter(cur => cur.endsWith('.js'));
|
|
40
|
-
|
|
41
40
|
if (bundles.length === 1) {
|
|
42
41
|
return {
|
|
43
42
|
..._generateManifestEntry(bundle[bundles[0]]),
|
|
@@ -106,4 +105,4 @@ function _buildModulesInfo(moduleIds, modules) {
|
|
|
106
105
|
);
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
module.exports =
|
|
108
|
+
module.exports = manifestPlugin;
|
package/lib/server.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
|
-
const path = require('path');
|
|
3
2
|
const cors = require('cors');
|
|
4
|
-
const
|
|
5
|
-
const {
|
|
3
|
+
const extensionsService = require('./extensionsService');
|
|
4
|
+
const {
|
|
5
|
+
AppFunctionExecutionService,
|
|
6
|
+
} = require('@hubspot/app-functions-dev-server');
|
|
6
7
|
|
|
7
8
|
function startDevServer(
|
|
8
9
|
outputDir,
|
|
@@ -10,6 +11,7 @@ function startDevServer(
|
|
|
10
11
|
webSocketPort,
|
|
11
12
|
baseMessage,
|
|
12
13
|
viteDevServer,
|
|
14
|
+
functionsConfig,
|
|
13
15
|
logger
|
|
14
16
|
) {
|
|
15
17
|
const app = express();
|
|
@@ -17,75 +19,37 @@ function startDevServer(
|
|
|
17
19
|
// Setup middleware
|
|
18
20
|
app.use(cors());
|
|
19
21
|
app.use(express.static(outputDir));
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
app.use(
|
|
24
|
+
'/api/crm-extensibility/execution/internal/v3',
|
|
25
|
+
AppFunctionExecutionService(functionsConfig)
|
|
26
|
+
);
|
|
27
|
+
logger.info('Serving app functions locally');
|
|
28
|
+
|
|
29
|
+
const endpointsAdded = extensionsService.add(
|
|
21
30
|
app,
|
|
22
|
-
expressPort,
|
|
23
31
|
webSocketPort,
|
|
24
32
|
outputDir,
|
|
25
|
-
baseMessage
|
|
26
|
-
logger
|
|
33
|
+
baseMessage
|
|
27
34
|
);
|
|
28
35
|
|
|
36
|
+
endpointsAdded.forEach(endpoint => {
|
|
37
|
+
logger.debug(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
|
|
38
|
+
});
|
|
39
|
+
|
|
29
40
|
// Vite middlewares needs to go last because it's greedy and will block other middleware
|
|
30
41
|
app.use(viteDevServer.middlewares);
|
|
31
42
|
|
|
32
43
|
const server = app.listen({ port: expressPort }, () => {
|
|
33
|
-
logger.
|
|
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
|
-
}
|
|
44
|
+
logger.debug(`Listening at ${baseMessage.callback}`);
|
|
73
45
|
});
|
|
74
|
-
logger.warn(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
|
|
75
|
-
}
|
|
76
46
|
|
|
77
|
-
function
|
|
78
|
-
async function shutdown() {
|
|
79
|
-
logger.warn('\nCleaning up after ourselves...');
|
|
47
|
+
return async function shutdown() {
|
|
80
48
|
await viteDevServer.pluginContainer.close();
|
|
81
49
|
// Stop new connections to express server
|
|
82
50
|
server.close(() => {});
|
|
83
51
|
logger.info('Clean up done, exiting.');
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
process.on('SIGINT', shutdown);
|
|
88
|
-
process.on('SIGTERM', shutdown);
|
|
52
|
+
};
|
|
89
53
|
}
|
|
90
54
|
|
|
91
55
|
module.exports = startDevServer;
|
package/lib/utils.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { MANIFEST_FILE } = require('./constants');
|
|
2
4
|
|
|
3
5
|
function getUrlSafeFileName(filePath) {
|
|
4
6
|
const { name } = path.parse(filePath);
|
|
@@ -14,7 +16,18 @@ function stripAnsiColorCodes(string) {
|
|
|
14
16
|
);
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
function loadManifest(outputDir, output) {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(
|
|
22
|
+
fs.readFileSync(path.join(outputDir, `${output}-${MANIFEST_FILE}`))
|
|
23
|
+
);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
module.exports = {
|
|
18
30
|
getUrlSafeFileName,
|
|
19
31
|
stripAnsiColorCodes,
|
|
32
|
+
loadManifest,
|
|
20
33
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,16 +11,16 @@
|
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
|
+
"cli/config.js",
|
|
14
15
|
"cli/logger.js",
|
|
15
16
|
"cli/run.js",
|
|
16
|
-
"cli/userInput.js",
|
|
17
17
|
"cli/utils.js",
|
|
18
18
|
"lib/plugins/*",
|
|
19
19
|
"lib/build.js",
|
|
20
|
-
"lib/config.js",
|
|
21
20
|
"lib/constants.js",
|
|
22
21
|
"lib/dev.js",
|
|
23
|
-
"lib/
|
|
22
|
+
"lib/extensionsService.js",
|
|
23
|
+
"lib/DevModeInterface.js",
|
|
24
24
|
"lib/server.js",
|
|
25
25
|
"lib/utils.js",
|
|
26
26
|
"index.js",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
],
|
|
29
29
|
"license": "MIT",
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@hubspot/app-functions-dev-server": "^0.5.0",
|
|
31
32
|
"command-line-args": "^5.2.1",
|
|
32
33
|
"command-line-usage": "^7.0.1",
|
|
33
34
|
"console-log-colors": "^0.4.0",
|
|
34
35
|
"cors": "^2.8.5",
|
|
35
36
|
"express": "^4.18.2",
|
|
36
|
-
"
|
|
37
|
+
"inquirer": "8.2.0",
|
|
37
38
|
"vite": "^4.0.4"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
@@ -61,5 +62,5 @@
|
|
|
61
62
|
"optional": true
|
|
62
63
|
}
|
|
63
64
|
},
|
|
64
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "d11a7bb73cf91ed44279b041f17778833948839f"
|
|
65
66
|
}
|
package/cli/userInput.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
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
|
-
};
|