@hubspot/ui-extensions-dev-server 0.8.3 → 0.8.6

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,6 +1,7 @@
1
1
  import type { ExtensionConfig, PlatformVersion, ProjectComponentMap } from './types';
2
2
  import type { ServiceConfiguration } from '@hubspot/app-functions-dev-server/dist/types';
3
3
  import { PromptModule } from 'inquirer';
4
+ import { DevServerState } from './DevServerState';
4
5
  interface SetupArguments {
5
6
  components: ProjectComponentMap;
6
7
  debug?: boolean;
@@ -17,6 +18,12 @@ interface StartArguments {
17
18
  accountId?: number;
18
19
  debug?: boolean;
19
20
  httpClient?: ServiceConfiguration['httpClient'];
21
+ requestPorts?: (requestPortsData: Array<{
22
+ instanceId: string;
23
+ port?: number;
24
+ }>) => Promise<{
25
+ [instanceId: string]: number;
26
+ }>;
20
27
  projectConfig?: ProjectConfig;
21
28
  }
22
29
  interface AppExtensionMapping {
@@ -24,18 +31,15 @@ interface AppExtensionMapping {
24
31
  value: ExtensionConfig;
25
32
  }
26
33
  declare class DevModeInterface {
27
- config?: ExtensionConfig;
28
- appName?: string;
29
- title?: string;
30
- extensionConfigPath?: string;
34
+ configs?: ExtensionConfig[];
35
+ devServerState?: DevServerState;
31
36
  onUploadRequired?: VoidFunction;
32
37
  shutdown?: () => Promise<void>;
33
- _setDataFromExtensionConfig(extensionConfig: ExtensionConfig): void;
34
38
  _generateAppExtensionMappings(components: ProjectComponentMap): AppExtensionMapping[];
35
39
  _getPlatformVersion(projectConfig?: ProjectConfig): PlatformVersion;
36
40
  setup({ components, debug, extensionConfig, onUploadRequired, promptUser, }: SetupArguments): Promise<void>;
37
41
  fileChange(filePath: string, __event: unknown): Promise<void>;
38
- start({ accountId, debug, httpClient, projectConfig }: StartArguments): Promise<void>;
42
+ start({ requestPorts, accountId, debug, httpClient, projectConfig, }: StartArguments): Promise<void>;
39
43
  cleanup(): Promise<void>;
40
44
  }
41
45
  declare const _default: DevModeInterface;
@@ -10,24 +10,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  step((generator = generator.apply(thisArg, _arguments || [])).next());
11
11
  });
12
12
  };
13
- var __importDefault = (this && this.__importDefault) || function (mod) {
14
- return (mod && mod.__esModule) ? mod : { "default": mod };
15
- };
16
13
  Object.defineProperty(exports, "__esModule", { value: true });
17
14
  const dev_1 = require("./dev");
18
- const path_1 = __importDefault(require("path"));
19
15
  const constants_1 = require("./constants");
16
+ const constants_2 = require("./constants");
20
17
  const config_1 = require("./config");
21
18
  // @ts-expect-error no type defs
22
19
  const logger_1 = require("@hubspot/cli-lib/logger");
20
+ const DevServerState_1 = require("./DevServerState");
23
21
  const utils_1 = require("./utils");
24
22
  class DevModeInterface {
25
- _setDataFromExtensionConfig(extensionConfig) {
26
- this.config = extensionConfig;
27
- this.extensionConfigPath = extensionConfig.extensionConfigPath;
28
- this.appName = extensionConfig.data.appName;
29
- this.title = extensionConfig.data.title;
30
- }
31
23
  _generateAppExtensionMappings(components) {
32
24
  // Loop over all of the app configs that are passed in
33
25
  const allComponentNames = Object.keys(components);
@@ -54,13 +46,13 @@ class DevModeInterface {
54
46
  _getPlatformVersion(projectConfig) {
55
47
  const { platformVersion } = projectConfig !== null && projectConfig !== void 0 ? projectConfig : {};
56
48
  if (!platformVersion) {
57
- return constants_1.PLATFORM_VERSION.V20231;
49
+ return constants_2.PLATFORM_VERSION.V20231;
58
50
  }
59
51
  switch (platformVersion) {
60
- case constants_1.PLATFORM_VERSION.V20231:
61
- return constants_1.PLATFORM_VERSION.V20231;
62
- case constants_1.PLATFORM_VERSION.V20232:
63
- return constants_1.PLATFORM_VERSION.V20232;
52
+ case constants_2.PLATFORM_VERSION.V20231:
53
+ return constants_2.PLATFORM_VERSION.V20231;
54
+ case constants_2.PLATFORM_VERSION.V20232:
55
+ return constants_2.PLATFORM_VERSION.V20232;
64
56
  default:
65
57
  return (0, utils_1.throwUnhandledPlatformVersionError)(platformVersion);
66
58
  }
@@ -70,7 +62,7 @@ class DevModeInterface {
70
62
  (0, logger_1.setLogLevel)(debug ? logger_1.LOG_LEVEL.DEBUG : logger_1.LOG_LEVEL.LOG);
71
63
  this.onUploadRequired = onUploadRequired;
72
64
  if (extensionConfig) {
73
- this._setDataFromExtensionConfig(extensionConfig);
65
+ this.configs = [extensionConfig];
74
66
  return;
75
67
  }
76
68
  const choices = this._generateAppExtensionMappings(components);
@@ -78,16 +70,26 @@ class DevModeInterface {
78
70
  throw new Error('No extensions to run');
79
71
  }
80
72
  else if (choices.length === 1) {
81
- this._setDataFromExtensionConfig(choices[0].value);
73
+ this.configs = [choices[0].value];
82
74
  }
83
75
  else {
84
76
  const answers = yield promptUser({
85
- type: 'list',
86
- name: 'extension',
87
- message: 'Which extension would you like to run?',
77
+ type: 'checkbox',
78
+ name: 'extensions',
79
+ message: 'Which extension(s) would you like to run?',
80
+ validate: (input) => {
81
+ if (!input || input.length === 0) {
82
+ return 'Select at least one extension to run';
83
+ }
84
+ const appNames = new Set(input.map(choice => choice.data.appName));
85
+ if (appNames.size > 1) {
86
+ return 'Running multiple extensions is only supported for a single application';
87
+ }
88
+ return true;
89
+ },
88
90
  choices,
89
91
  });
90
- this._setDataFromExtensionConfig(answers.extension);
92
+ this.configs = answers.extensions;
91
93
  }
92
94
  });
93
95
  }
@@ -95,36 +97,48 @@ class DevModeInterface {
95
97
  // eslint-disable-next-line require-await
96
98
  fileChange(filePath, __event) {
97
99
  return __awaiter(this, void 0, void 0, function* () {
98
- if (this.extensionConfigPath &&
99
- this.extensionConfigPath === filePath &&
100
- this.onUploadRequired) {
100
+ if (!this.devServerState || !this.devServerState.extensionsMetadata) {
101
+ return;
102
+ }
103
+ const relevantConfigFileChanged = this.devServerState.extensionsMetadata.some(metadata => metadata.config.extensionConfigPath === filePath);
104
+ if (relevantConfigFileChanged && this.onUploadRequired) {
101
105
  this.onUploadRequired();
102
106
  }
103
107
  });
104
108
  }
105
- start({ accountId, debug, httpClient, projectConfig }) {
109
+ start({ requestPorts, accountId, debug, httpClient, projectConfig, }) {
106
110
  return __awaiter(this, void 0, void 0, function* () {
107
111
  if (debug !== undefined) {
108
112
  (0, logger_1.setLogLevel)(debug ? logger_1.LOG_LEVEL.DEBUG : logger_1.LOG_LEVEL.LOG);
109
113
  }
110
- if (!this.config || !this.config.path) {
111
- throw new Error('Unable to load the required extension configuration files');
114
+ let expressPort = constants_1.EXPRESS_DEFAULT_PORT;
115
+ let webSocketPort = constants_1.WEBSOCKET_DEFAULT_PORT;
116
+ if (requestPorts) {
117
+ try {
118
+ const portData = yield requestPorts([
119
+ { instanceId: constants_1.EXPRESS_SERVER_ID, port: constants_1.EXPRESS_DEFAULT_PORT },
120
+ { instanceId: constants_1.VITE_DEV_SERVER_ID, port: constants_1.WEBSOCKET_DEFAULT_PORT },
121
+ ]);
122
+ expressPort = portData[constants_1.EXPRESS_SERVER_ID];
123
+ webSocketPort = portData[constants_1.VITE_DEV_SERVER_ID];
124
+ }
125
+ catch (e) {
126
+ logger_1.logger.debug('Call to port manager failed, using default ports');
127
+ }
112
128
  }
113
- const appPath = this.config.path;
114
- // Pass options from the CLI for running app functions locally
115
- const functionsConfig = {
116
- app: { path: appPath },
129
+ this.devServerState = new DevServerState_1.DevServerState({
130
+ extensionConfigs: this.configs,
117
131
  accountId,
118
132
  httpClient,
119
133
  platformVersion: this._getPlatformVersion(projectConfig),
120
- };
121
- this.shutdown = yield (0, dev_1.startDevMode)({
122
- extensionConfig: this.config,
123
- outputDir: path_1.default.join(this.config.extensionPath, constants_1.OUTPUT_DIR),
124
- functionsConfig,
125
- root: appPath,
134
+ expressPort,
135
+ webSocketPort,
136
+ });
137
+ this.shutdown = yield (0, dev_1.startDevMode)(this.devServerState);
138
+ this.devServerState.extensionsMetadata.forEach(metadata => {
139
+ const { config: { data: { title, appName }, }, } = metadata;
140
+ logger_1.logger.info(`Running extension '${title}' from app '${appName}'`);
126
141
  });
127
- logger_1.logger.info(`Running extension '${this.title}' from app '${this.appName}'`);
128
142
  });
129
143
  }
130
144
  cleanup() {
@@ -0,0 +1,28 @@
1
+ import { ExtensionConfig, ExtensionMetadata, PlatformVersion } from './types';
2
+ import { ServiceConfiguration } from '@hubspot/app-functions-dev-server/dist/types';
3
+ interface DevServerStateArgs {
4
+ extensionConfigs?: ExtensionConfig[];
5
+ accountId: number | undefined;
6
+ httpClient: ServiceConfiguration['httpClient'] | undefined;
7
+ expressPort: number;
8
+ webSocketPort: number;
9
+ platformVersion: PlatformVersion;
10
+ }
11
+ export declare class DevServerState {
12
+ private _webSocketPort;
13
+ private _expressPort;
14
+ private _functionsConfig;
15
+ private _outputDir;
16
+ private _appPath;
17
+ private _extensionsMetadata;
18
+ private _portalId?;
19
+ constructor({ extensionConfigs, accountId, httpClient, expressPort, webSocketPort, platformVersion, }: DevServerStateArgs);
20
+ get portalId(): number | undefined;
21
+ get webSocketPort(): number;
22
+ get expressPort(): number;
23
+ get extensionsMetadata(): ExtensionMetadata[];
24
+ get functionsConfig(): Partial<ServiceConfiguration>;
25
+ get outputDir(): string;
26
+ get appPath(): string;
27
+ }
28
+ export {};
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DevServerState = void 0;
7
+ const constants_1 = require("./constants");
8
+ const path_1 = __importDefault(require("path"));
9
+ class DevServerState {
10
+ constructor({ extensionConfigs, accountId, httpClient, expressPort, webSocketPort, platformVersion, }) {
11
+ if (!extensionConfigs) {
12
+ throw new Error('Unable to load the required extension configuration files');
13
+ }
14
+ const extensionsMetadata = [];
15
+ extensionConfigs.forEach(config => {
16
+ const { appName, title, sourceId } = config.data;
17
+ extensionsMetadata.push({
18
+ config,
19
+ baseMessage: {
20
+ appName,
21
+ title,
22
+ sourceId,
23
+ callback: `http://hslocal.net:${expressPort}/${config.output}`,
24
+ portalId: accountId,
25
+ },
26
+ });
27
+ });
28
+ this._webSocketPort = webSocketPort;
29
+ this._expressPort = expressPort;
30
+ this._extensionsMetadata = extensionsMetadata;
31
+ this._appPath = extensionConfigs[0].path;
32
+ this._portalId = accountId;
33
+ this._outputDir = path_1.default.join(this._appPath, constants_1.OUTPUT_DIR);
34
+ // Pass options from the CLI for running app functions locally
35
+ this._functionsConfig = {
36
+ app: { path: this._appPath },
37
+ accountId,
38
+ httpClient,
39
+ platformVersion,
40
+ };
41
+ Object.freeze(this);
42
+ }
43
+ get portalId() {
44
+ return this._portalId;
45
+ }
46
+ get webSocketPort() {
47
+ return this._webSocketPort;
48
+ }
49
+ get expressPort() {
50
+ return this._expressPort;
51
+ }
52
+ get extensionsMetadata() {
53
+ return this._extensionsMetadata;
54
+ }
55
+ get functionsConfig() {
56
+ return this._functionsConfig;
57
+ }
58
+ get outputDir() {
59
+ return this._outputDir;
60
+ }
61
+ get appPath() {
62
+ return this._appPath;
63
+ }
64
+ }
65
+ exports.DevServerState = DevServerState;
@@ -1,8 +1,10 @@
1
1
  export declare const OUTPUT_DIR = "dist";
2
2
  export declare const MAIN_APP_CONFIG = "app.json";
3
3
  export declare const MANIFEST_FILE = "manifest.json";
4
- export declare const VITE_DEFAULT_PORT = 5173;
5
- export declare const WEBSOCKET_PORT = 5174;
4
+ export declare const EXPRESS_SERVER_ID = "ui-extensions-dev-server";
5
+ export declare const VITE_DEV_SERVER_ID = "ui-extensions-vite-dev-server";
6
+ export declare const EXPRESS_DEFAULT_PORT = 5173;
7
+ export declare const WEBSOCKET_DEFAULT_PORT = 5174;
6
8
  export declare const ROLLUP_OPTIONS: {
7
9
  external: string[];
8
10
  output: {
@@ -1,11 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PLATFORM_VERSION = exports.SERVER_CAPABILITIES = exports.WEBSOCKET_MESSAGE_VERSION = exports.EXTENSIONS_MESSAGE_VERSION = exports.ROLLUP_OPTIONS = exports.WEBSOCKET_PORT = exports.VITE_DEFAULT_PORT = exports.MANIFEST_FILE = exports.MAIN_APP_CONFIG = exports.OUTPUT_DIR = void 0;
3
+ exports.PLATFORM_VERSION = exports.SERVER_CAPABILITIES = exports.WEBSOCKET_MESSAGE_VERSION = exports.EXTENSIONS_MESSAGE_VERSION = exports.ROLLUP_OPTIONS = exports.WEBSOCKET_DEFAULT_PORT = exports.EXPRESS_DEFAULT_PORT = exports.VITE_DEV_SERVER_ID = exports.EXPRESS_SERVER_ID = exports.MANIFEST_FILE = exports.MAIN_APP_CONFIG = exports.OUTPUT_DIR = void 0;
4
4
  exports.OUTPUT_DIR = 'dist';
5
5
  exports.MAIN_APP_CONFIG = 'app.json';
6
6
  exports.MANIFEST_FILE = 'manifest.json';
7
- exports.VITE_DEFAULT_PORT = 5173;
8
- exports.WEBSOCKET_PORT = 5174;
7
+ exports.EXPRESS_SERVER_ID = 'ui-extensions-dev-server';
8
+ exports.VITE_DEV_SERVER_ID = 'ui-extensions-vite-dev-server';
9
+ exports.EXPRESS_DEFAULT_PORT = 5173;
10
+ exports.WEBSOCKET_DEFAULT_PORT = 5174;
9
11
  exports.ROLLUP_OPTIONS = {
10
12
  // Deps to exclude from the bundle
11
13
  external: ['react', 'react-dom', '@remote-ui/react'],
package/dist/lib/dev.d.ts CHANGED
@@ -1,12 +1,2 @@
1
- import { ExtensionConfig } from './types';
2
- import { ServiceConfiguration } from '@hubspot/app-functions-dev-server/dist/types';
3
- interface StartDevModeArgs {
4
- outputDir?: string;
5
- extensionConfig: ExtensionConfig;
6
- webSocketPort?: number;
7
- expressPort?: number;
8
- root?: string;
9
- functionsConfig: Partial<ServiceConfiguration>;
10
- }
11
- export declare function startDevMode({ extensionConfig, functionsConfig, outputDir, expressPort, webSocketPort, root, }: StartDevModeArgs): Promise<() => Promise<void>>;
12
- export {};
1
+ import { DevServerState } from './DevServerState';
2
+ export declare function startDevMode(devServerState: DevServerState): Promise<() => Promise<void>>;
package/dist/lib/dev.js CHANGED
@@ -17,26 +17,23 @@ const vite_1 = require("vite");
17
17
  const path_1 = __importDefault(require("path"));
18
18
  const server_1 = __importDefault(require("./server"));
19
19
  const devBuildPlugin_1 = __importDefault(require("./plugins/devBuildPlugin"));
20
- const constants_1 = require("./constants");
21
- // @ts-expect-error no type defs
22
- const logger_1 = require("@hubspot/cli-lib/logger");
23
20
  // @ts-expect-error no type defs
24
21
  const detect_port_1 = __importDefault(require("detect-port"));
25
- function _createViteDevServer({ outputDir, extensionConfig, webSocketPort, baseMessage, root, }) {
22
+ function _createViteDevServer(devServerState) {
26
23
  return __awaiter(this, void 0, void 0, function* () {
27
24
  return yield (0, vite_1.createServer)({
28
- root,
25
+ root: devServerState.appPath,
29
26
  logLevel: 'silent',
30
27
  appType: 'custom',
31
28
  mode: 'development',
32
29
  server: {
33
30
  middlewareMode: true,
34
31
  hmr: {
35
- port: webSocketPort,
32
+ port: devServerState.webSocketPort,
36
33
  },
37
34
  watch: {
38
35
  ignored: [
39
- path_1.default.join(outputDir, '/**/*'),
36
+ path_1.default.join(devServerState.outputDir, '/**/*'),
40
37
  '**/src/app/app.functions/**/*',
41
38
  '**/app.json',
42
39
  '**/package.json',
@@ -46,9 +43,7 @@ function _createViteDevServer({ outputDir, extensionConfig, webSocketPort, baseM
46
43
  },
47
44
  plugins: [
48
45
  (0, devBuildPlugin_1.default)({
49
- extensionConfig,
50
- outputDir,
51
- baseMessage,
46
+ devServerState,
52
47
  }),
53
48
  ],
54
49
  clearScreen: false,
@@ -64,38 +59,16 @@ function throwIfPortTaken(port) {
64
59
  }
65
60
  });
66
61
  }
67
- function startDevMode({ extensionConfig, functionsConfig, outputDir = constants_1.OUTPUT_DIR, expressPort = constants_1.VITE_DEFAULT_PORT, webSocketPort = constants_1.WEBSOCKET_PORT, root = process.cwd(), }) {
62
+ function startDevMode(devServerState) {
68
63
  return __awaiter(this, void 0, void 0, function* () {
69
- if (!extensionConfig) {
64
+ if (!devServerState || !devServerState.extensionsMetadata) {
70
65
  throw new Error('Unable to determine which extension to run');
71
66
  }
72
- yield throwIfPortTaken(expressPort);
73
- const actualWebSocketPort = yield (0, detect_port_1.default)(webSocketPort);
74
- if (actualWebSocketPort !== webSocketPort) {
75
- logger_1.logger.debug(`WebSocket port ${webSocketPort} is in use; using next available port ${actualWebSocketPort}`);
76
- }
77
- const { appName, title, sourceId } = extensionConfig.data;
78
- const baseMessage = Object.freeze({
79
- appName,
80
- title,
81
- sourceId,
82
- callback: `http://hslocal.net:${expressPort}/${extensionConfig.output}`,
83
- portalId: functionsConfig.accountId,
84
- });
85
- const viteDevServer = yield _createViteDevServer({
86
- outputDir,
87
- extensionConfig,
88
- webSocketPort: actualWebSocketPort,
89
- baseMessage,
90
- root,
91
- });
67
+ yield throwIfPortTaken(devServerState.webSocketPort);
68
+ const viteDevServer = yield _createViteDevServer(devServerState);
92
69
  const shutdownServer = yield (0, server_1.default)({
93
- outputDir,
94
- expressPort,
95
- webSocketPort: actualWebSocketPort,
96
- baseMessage,
70
+ devServerState,
97
71
  viteDevServer,
98
- functionsConfig,
99
72
  });
100
73
  return shutdownServer;
101
74
  });
@@ -1,12 +1,12 @@
1
- import { BaseMessage } from './types';
2
1
  import { Response } from 'express/lib/response';
3
2
  import { Request } from 'express/lib/request';
4
3
  import { Application } from 'express/lib/application';
4
+ import { DevServerState } from './DevServerState';
5
5
  declare class ExtensionsService {
6
6
  endpoint: string;
7
7
  constructor();
8
- add(server: Application, webSocketPort: number, outputDir: string, baseMessage: BaseMessage, capabilities: string[]): string[];
9
- generateExtensionsHandler(baseMessage: BaseMessage, webSocketPort: number, outputDir: string, capabilities?: string[]): (_req: Request, res: Response) => void;
8
+ add(server: Application, devServerState: DevServerState, capabilities: string[]): string[];
9
+ generateExtensionsHandler(devServerState: DevServerState, capabilities?: string[]): (_req: Request, res: Response) => void;
10
10
  }
11
11
  declare const _default: ExtensionsService;
12
12
  export default _default;
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ /* eslint-disable hubspot-dev/no-unsupported-ts-syntax */
6
7
  const path_1 = __importDefault(require("path"));
7
8
  const constants_1 = require("./constants");
8
9
  const utils_1 = require("./utils");
@@ -10,22 +11,24 @@ class ExtensionsService {
10
11
  constructor() {
11
12
  this.endpoint = '/extensions';
12
13
  }
13
- add(server, webSocketPort, outputDir, baseMessage, capabilities) {
14
- server.get(this.endpoint, this.generateExtensionsHandler(baseMessage, webSocketPort, outputDir, capabilities));
14
+ add(server, devServerState, capabilities) {
15
+ server.get(this.endpoint, this.generateExtensionsHandler(devServerState, capabilities));
15
16
  return [this.endpoint];
16
17
  }
17
- generateExtensionsHandler(baseMessage, webSocketPort, outputDir, capabilities = []) {
18
+ generateExtensionsHandler(devServerState, capabilities = []) {
18
19
  return function extensionsHandler(_req, res) {
19
20
  try {
20
- const output = path_1.default.parse(baseMessage.callback).name;
21
+ const extensions = devServerState.extensionsMetadata.map(metadata => {
22
+ const { baseMessage } = metadata;
23
+ const output = path_1.default.parse(baseMessage.callback).name;
24
+ return Object.assign(Object.assign({}, baseMessage), { manifest: (0, utils_1.loadManifest)(devServerState.outputDir, output) });
25
+ });
21
26
  const response = {
22
- websocket: `ws://localhost:${webSocketPort}`,
27
+ websocket: `ws://localhost:${devServerState.webSocketPort}`,
23
28
  version: constants_1.EXTENSIONS_MESSAGE_VERSION,
24
29
  capabilities,
25
- portalId: baseMessage.portalId,
26
- extensions: [
27
- Object.assign(Object.assign({}, baseMessage), { manifest: (0, utils_1.loadManifest)(outputDir, output) }),
28
- ],
30
+ portalId: devServerState.portalId,
31
+ extensions,
29
32
  };
30
33
  res.status(200).json(response);
31
34
  }
@@ -14,7 +14,8 @@ const codeCheckingPlugin = options => {
14
14
  writeBundle(__options, __bundle) {
15
15
  try {
16
16
  const code = fs_1.default.readFileSync(output).toString();
17
- if (!code.includes('const extend = (...args) => self.extend(...args);')) {
17
+ if (!code.includes('const extend = (...args) => self.extend(...args);') &&
18
+ !code.includes('self.extend_V2(renderExtensionCallback)')) {
18
19
  logger_1.logger.warn('Unable to determine if your extension entry point is calling hubspot.extend, this may prevent it from rendering as expected');
19
20
  }
20
21
  }
@@ -1,9 +1,7 @@
1
1
  import Vite from 'vite';
2
- import { ExtensionConfig, BaseMessage } from '../types';
2
+ import { DevServerState } from '../DevServerState';
3
3
  export interface DevBuildPluginOptions {
4
- extensionConfig: ExtensionConfig;
5
- outputDir: string;
6
- baseMessage: BaseMessage;
4
+ devServerState: DevServerState;
7
5
  }
8
6
  export type DevBuildPlugin = (options: DevBuildPluginOptions) => Vite.Plugin;
9
7
  declare const devBuildPlugin: DevBuildPlugin;
@@ -47,16 +47,18 @@ const path_1 = __importDefault(require("path"));
47
47
  const logger_1 = require("@hubspot/cli-lib/logger");
48
48
  const friendlyLoggingPlugin_1 = __importDefault(require("./friendlyLoggingPlugin"));
49
49
  const relevantModulesPlugin_1 = __importStar(require("./relevantModulesPlugin"));
50
+ function addVersionToBaseMessage(baseMessage) {
51
+ return Object.assign(Object.assign({}, baseMessage), { version: constants_1.WEBSOCKET_MESSAGE_VERSION });
52
+ }
50
53
  const devBuildPlugin = options => {
51
- let lastBuildError;
52
- const { extensionConfig, outputDir, baseMessage } = options;
53
- const versionedBaseMessage = Object.assign(Object.assign({}, baseMessage), { version: constants_1.WEBSOCKET_MESSAGE_VERSION });
54
+ const { devServerState } = options;
55
+ let lastBuildErrorContext;
54
56
  const handleBuildError = (error, server) => {
55
- const { plugin, errors, frame, loc, id } = error;
57
+ const { error: { plugin, errors, frame, loc, id }, extensionMetadata, } = error;
56
58
  // Filter out our custom plugins, but send everything else
57
59
  if (!(plugin === null || plugin === void 0 ? void 0 : plugin.startsWith('ui-extensions'))) {
58
60
  // @ts-expect-error Our websocket messages don't match Vite format
59
- server.ws.send(Object.assign(Object.assign({}, versionedBaseMessage), { event: 'error', error: {
61
+ server.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(extensionMetadata.baseMessage)), { event: 'error', error: {
60
62
  details: {
61
63
  errors,
62
64
  formattedError: (0, utils_1.stripAnsiColorCodes)(frame),
@@ -66,8 +68,9 @@ const devBuildPlugin = options => {
66
68
  } }));
67
69
  }
68
70
  };
69
- const devBuild = (server) => __awaiter(void 0, void 0, void 0, function* () {
71
+ const devBuild = (server, extensionMetadata, emptyOutDir = false) => __awaiter(void 0, void 0, void 0, function* () {
70
72
  try {
73
+ const { config: extensionConfig } = extensionMetadata;
71
74
  yield (0, vite_1.build)({
72
75
  logLevel: 'warn',
73
76
  mode: 'development',
@@ -95,24 +98,27 @@ const devBuildPlugin = options => {
95
98
  extensionPath: extensionConfig.extensionPath,
96
99
  }),
97
100
  (0, codeCheckingPlugin_1.default)({
98
- output: path_1.default.join(outputDir, extensionConfig.output),
101
+ output: path_1.default.join(devServerState.outputDir, extensionConfig.output),
99
102
  }),
100
103
  (0, friendlyLoggingPlugin_1.default)(),
101
104
  (0, relevantModulesPlugin_1.default)({ output: extensionConfig.output }),
102
105
  ], output: Object.assign(Object.assign({}, constants_1.ROLLUP_OPTIONS.output), { sourcemap: 'inline' }) }),
103
- outDir: outputDir,
104
- emptyOutDir: true,
106
+ outDir: devServerState.outputDir,
107
+ emptyOutDir,
105
108
  minify: false,
106
109
  },
107
110
  clearScreen: false,
108
111
  });
109
- lastBuildError = null;
112
+ lastBuildErrorContext = null;
110
113
  return true;
111
114
  }
112
115
  catch (error) {
113
- lastBuildError = error;
116
+ lastBuildErrorContext = {
117
+ error: error,
118
+ extensionMetadata,
119
+ };
114
120
  logger_1.logger.debug(error);
115
- handleBuildError(lastBuildError, server);
121
+ handleBuildError(lastBuildErrorContext, server);
116
122
  return false;
117
123
  }
118
124
  });
@@ -126,41 +132,52 @@ const devBuildPlugin = options => {
126
132
  localServer = server;
127
133
  localServer.ws.on('connection', () => {
128
134
  logger_1.logger.info('Browser connected and listening for bundle updates');
129
- // @ts-expect-error Our websocket messages don't match Vite format
130
- localServer.ws.send(Object.assign(Object.assign({}, versionedBaseMessage), { event: 'start' }));
131
- if (lastBuildError) {
132
- handleBuildError(lastBuildError, server);
135
+ devServerState.extensionsMetadata.forEach(metadata => {
136
+ // @ts-expect-error Our websocket messages don't match Vite format
137
+ localServer.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(metadata.baseMessage)), { event: 'start' }));
138
+ });
139
+ if (lastBuildErrorContext) {
140
+ handleBuildError(lastBuildErrorContext, server);
133
141
  }
134
142
  });
135
- yield devBuild(localServer);
143
+ for (let i = 0; i < devServerState.extensionsMetadata.length; ++i) {
144
+ yield devBuild(localServer, devServerState.extensionsMetadata[i], i === 0);
145
+ }
136
146
  }),
137
147
  handleHotUpdate: ({ file, server }) => __awaiter(void 0, void 0, void 0, function* () {
138
148
  // If the file is not in the relevantModules list, it's update is inconsequential
139
- if (!(0, relevantModulesPlugin_1.getRelevantModules)(extensionConfig.output).includes(file)) {
140
- return [];
141
- }
142
- const successful = yield devBuild(server);
143
- if (!successful) {
144
- return [];
145
- }
146
- logger_1.logger.info(`Extension ${extensionConfig.data.title} updated, compiled`);
147
- if (server.ws.clients.size === 0) {
148
- logger_1.logger.debug('Bundle updated, no browsers connected to notify');
149
- return [];
149
+ const extensionsToRebuild = devServerState.extensionsMetadata.filter(metadata => {
150
+ const { config } = metadata;
151
+ return (0, relevantModulesPlugin_1.getRelevantModules)(config.output).includes(file);
152
+ });
153
+ for (let i = 0; i < extensionsToRebuild.length; ++i) {
154
+ const toRebuild = extensionsToRebuild[i];
155
+ const successful = yield devBuild(server, toRebuild);
156
+ if (!successful) {
157
+ return [];
158
+ }
159
+ const { config: extensionConfig } = toRebuild;
160
+ logger_1.logger.info(`Extension ${extensionConfig.data.title} updated, compiled`);
161
+ if (server.ws.clients.size === 0) {
162
+ logger_1.logger.debug('Bundle updated, no browsers connected to notify');
163
+ return [];
164
+ }
165
+ logger_1.logger.debug('Bundle updated, notifying connected browsers');
166
+ // @ts-expect-error Our websocket messages don't match Vite format
167
+ server.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(toRebuild.baseMessage)), { event: 'update' }));
150
168
  }
151
- logger_1.logger.debug('Bundle updated, notifying connected browsers');
152
- // @ts-expect-error Our websocket messages don't match Vite format
153
- server.ws.send(Object.assign(Object.assign({}, versionedBaseMessage), { event: 'update' }));
154
169
  return [];
155
170
  }),
156
171
  buildEnd(error) {
157
172
  if (error) {
158
173
  logger_1.logger.error(error);
159
174
  }
160
- logger_1.logger.debug('Sending shutdown message to connected browsers');
161
175
  if (localServer && localServer.ws) {
162
- // @ts-expect-error Our websocket messages don't match Vite format
163
- localServer.ws.send(Object.assign(Object.assign({}, versionedBaseMessage), { event: 'shutdown' }));
176
+ logger_1.logger.debug('Sending shutdown message to connected browsers');
177
+ devServerState.extensionsMetadata.forEach(metadata => {
178
+ // @ts-expect-error Our websocket messages don't match Vite format
179
+ localServer.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(metadata.baseMessage)), { event: 'shutdown' }));
180
+ });
164
181
  }
165
182
  },
166
183
  };
@@ -1,13 +1,8 @@
1
- import { BaseMessage } from './types';
2
1
  import { ViteDevServer } from 'vite';
3
- import { ServiceConfiguration } from '@hubspot/app-functions-dev-server/dist/types';
2
+ import { DevServerState } from './DevServerState';
4
3
  interface StartDevServerArgs {
5
- outputDir: string;
6
- expressPort: number;
7
- webSocketPort: number;
8
- baseMessage: BaseMessage;
4
+ devServerState: DevServerState;
9
5
  viteDevServer: ViteDevServer;
10
- functionsConfig: Partial<ServiceConfiguration>;
11
6
  }
12
- declare function startDevServer({ outputDir, expressPort, webSocketPort, baseMessage, viteDevServer, functionsConfig, }: StartDevServerArgs): Promise<() => Promise<void>>;
7
+ declare function startDevServer({ devServerState, viteDevServer, }: StartDevServerArgs): Promise<() => Promise<void>>;
13
8
  export default startDevServer;
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ /* eslint-disable hubspot-dev/no-unsupported-ts-syntax */
3
+ /* eslint-disable no-unused-expressions */
2
4
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
5
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
6
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -30,15 +32,17 @@ function listen(app, port) {
30
32
  });
31
33
  });
32
34
  }
33
- function startDevServer({ outputDir, expressPort, webSocketPort, baseMessage, viteDevServer, functionsConfig, }) {
35
+ function startDevServer({ devServerState, viteDevServer, }) {
36
+ var _a;
34
37
  return __awaiter(this, void 0, void 0, function* () {
35
38
  const app = (0, express_1.default)();
36
39
  // Setup middleware
37
40
  app.use((0, cors_1.default)());
38
- app.use(express_1.default.static(outputDir));
39
- app.use('/api/crm-extensibility/execution/internal/v3', (0, app_functions_dev_server_1.AppFunctionExecutionService)(Object.assign(Object.assign({}, functionsConfig), { logger: logger_1.logger })));
40
- logger_1.logger.info(`Serving app functions locally (platform version ${functionsConfig.platformVersion})`);
41
- const endpointsAdded = extensionsService_1.default.add(app, webSocketPort, outputDir, baseMessage, constants_1.SERVER_CAPABILITIES);
41
+ app.use(express_1.default.static(devServerState.outputDir));
42
+ app.use('/api/crm-extensibility/execution/internal/v3', (0, app_functions_dev_server_1.AppFunctionExecutionService)(Object.assign(Object.assign({}, devServerState.functionsConfig), { logger: logger_1.logger })));
43
+ logger_1.logger.info(`Serving app functions locally (platform version ${devServerState.functionsConfig.platformVersion})`);
44
+ const endpointsAdded = extensionsService_1.default.add(app, devServerState, constants_1.SERVER_CAPABILITIES);
45
+ const { expressPort } = devServerState;
42
46
  endpointsAdded.forEach(endpoint => {
43
47
  logger_1.logger.debug(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
44
48
  });
@@ -46,15 +50,18 @@ function startDevServer({ outputDir, expressPort, webSocketPort, baseMessage, vi
46
50
  app.use(viteDevServer.middlewares);
47
51
  let server;
48
52
  try {
49
- server = yield listen(app, expressPort);
53
+ server = yield listen(app, devServerState.expressPort);
50
54
  }
51
55
  catch (e) {
52
56
  if (e.code === 'EADDRINUSE') {
53
- throw new Error(`Port ${expressPort} is already in use.`);
57
+ throw new Error(`Port ${devServerState.expressPort} is already in use.`);
54
58
  }
55
59
  throw new Error(e);
56
60
  }
57
- logger_1.logger.debug(`Listening at ${baseMessage.callback}`);
61
+ (_a = devServerState.extensionsMetadata) === null || _a === void 0 ? void 0 : _a.forEach(metadata => {
62
+ const { baseMessage } = metadata;
63
+ logger_1.logger.debug(`Listening at ${baseMessage.callback}`);
64
+ });
58
65
  return function shutdown() {
59
66
  return __awaiter(this, void 0, void 0, function* () {
60
67
  yield viteDevServer.pluginContainer.close();
@@ -52,5 +52,9 @@ export interface BaseMessage {
52
52
  sourceId?: string | null;
53
53
  portalId?: number;
54
54
  }
55
+ export interface ExtensionMetadata {
56
+ baseMessage: BaseMessage;
57
+ config: ExtensionConfig;
58
+ }
55
59
  export type PlatformVersion = typeof PLATFORM_VERSION[keyof typeof PLATFORM_VERSION];
56
60
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/ui-extensions-dev-server",
3
- "version": "0.8.3",
3
+ "version": "0.8.6",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -63,5 +63,5 @@
63
63
  "optional": true
64
64
  }
65
65
  },
66
- "gitHead": "ab25629916c542f19bf8cde83a10ad8bd225078f"
66
+ "gitHead": "621bee406c8dfa9ca133f1d76f4bde725a2b9f43"
67
67
  }