@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.
@@ -1,24 +1,63 @@
1
+ const fs = require('fs');
1
2
  const path = require('path');
2
- const { MAIN_APP_CONFIG } = require('./constants');
3
+ const { getUrlSafeFileName } = require('../lib/utils');
4
+ const { MAIN_APP_CONFIG } = require('../lib/constants');
3
5
 
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;
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 = _loadRequiredConfigFile(configPath);
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 = require(cardConfigPath);
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
- startDevMode,
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
- startDevMode({
22
- extension: extension || (await promptForExtensionToRun()),
23
- outputDir: OUTPUT_DIR,
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 { startDevMode } = require('./lib/dev');
7
+ const DevModeInterface = require('./lib/DevModeInterface');
8
8
 
9
9
  module.exports = {
10
10
  remoteBuild,
11
11
  buildAllExtensions,
12
12
  buildSingleExtension,
13
- startDevMode,
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('./config');
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({ output: getUrlSafeFileName(data.module.file) }),
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(root, entryPoint, outputDir = OUTPUT_DIR) {
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 = 0;
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: [`${process.cwd()}/${outputDir}/**/*`],
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({ extensionConfig, outputDir, baseMessage, logger }),
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
- extension,
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.warn('Sending shutdown message to connected browsers');
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 plugin(options = {}) {
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 = plugin;
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 fs = require('fs');
5
- const { EXTENSIONS_MESSAGE_VERSION, MANIFEST_FILE } = require('./constants');
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
- _addExtensionsEndpoint(
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.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
- }
44
+ logger.debug(`Listening at ${baseMessage.callback}`);
73
45
  });
74
- logger.warn(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
75
- }
76
46
 
77
- function _configureShutDownHandlers(server, viteDevServer, logger) {
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
- process.exit(0);
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.1",
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/extensions.js",
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
- "prompts": "^2.4.2",
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": "3da4a07183d48528ef431d46a8d327c053c4fe94"
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
- };
package/lib/extensions.js DELETED
@@ -1,9 +0,0 @@
1
- const { loadConfig } = require('./config');
2
-
3
- function getExtensionsList() {
4
- return Object.keys(loadConfig());
5
- }
6
-
7
- module.exports = {
8
- getExtensionsList,
9
- };