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

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.
@@ -33,7 +33,7 @@ class DevModeInterface {
33
33
  const extensionsConfigForApp = (0, config_1.loadExtensionConfig)(component.config, component.path);
34
34
  const extensionConfigKeys = Object.keys(extensionsConfigForApp);
35
35
  // Loop over the loaded extension configs and generate the list of choices to use to prompt the user for input
36
- extensionConfigKeys.forEach(extensionKey => {
36
+ extensionConfigKeys.forEach((extensionKey) => {
37
37
  const extensionConfig = extensionsConfigForApp[extensionKey];
38
38
  appExtensionMappings.push({
39
39
  name: `${componentName}/${extensionConfig.data.title}`,
@@ -81,7 +81,7 @@ class DevModeInterface {
81
81
  if (!input || input.length === 0) {
82
82
  return 'Select at least one extension to run';
83
83
  }
84
- const appNames = new Set(input.map(choice => choice.data.appName));
84
+ const appNames = new Set(input.map((choice) => choice.data.appName));
85
85
  if (appNames.size > 1) {
86
86
  return 'Running multiple extensions is only supported for a single application';
87
87
  }
@@ -100,7 +100,7 @@ class DevModeInterface {
100
100
  if (!this.devServerState || !this.devServerState.extensionsMetadata) {
101
101
  return;
102
102
  }
103
- const relevantConfigFileChanged = this.devServerState.extensionsMetadata.some(metadata => metadata.config.extensionConfigPath === filePath);
103
+ const relevantConfigFileChanged = this.devServerState.extensionsMetadata.some((metadata) => metadata.config.extensionConfigPath === filePath);
104
104
  if (relevantConfigFileChanged && this.onUploadRequired) {
105
105
  this.onUploadRequired();
106
106
  }
@@ -135,7 +135,7 @@ class DevModeInterface {
135
135
  webSocketPort,
136
136
  });
137
137
  this.shutdown = yield (0, dev_1.startDevMode)(this.devServerState);
138
- this.devServerState.extensionsMetadata.forEach(metadata => {
138
+ this.devServerState.extensionsMetadata.forEach((metadata) => {
139
139
  const { config: { data: { title, appName }, }, } = metadata;
140
140
  logger_1.logger.info(`Running extension '${title}' from app '${appName}'`);
141
141
  });
@@ -12,7 +12,7 @@ class DevServerState {
12
12
  throw new Error('Unable to load the required extension configuration files');
13
13
  }
14
14
  const extensionsMetadata = [];
15
- extensionConfigs.forEach(config => {
15
+ extensionConfigs.forEach((config) => {
16
16
  const { appName, title, sourceId } = config.data;
17
17
  extensionsMetadata.push({
18
18
  config,
@@ -0,0 +1,5 @@
1
+ import { SourceCodeChecks } from './types';
2
+ import { Program } from 'estree';
3
+ export declare function traverseAbstractSyntaxTree(ast: Program, checks: SourceCodeChecks): {
4
+ functions: {};
5
+ };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /* eslint-disable hubspot-dev/no-unsupported-ts-syntax */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.traverseAbstractSyntaxTree = void 0;
5
+ // @ts-expect-error no type defs
6
+ const estraverse_1 = require("estraverse");
7
+ function _isVariableImported(node, variableName) {
8
+ if (!node) {
9
+ return false;
10
+ }
11
+ return ((node.type === 'ImportSpecifier' && node.imported.name === variableName) ||
12
+ (node.type === 'ImportDefaultSpecifier' &&
13
+ node.local.name === variableName) ||
14
+ (node.type === 'ImportNamespaceSpecifier' &&
15
+ node.local.name === variableName));
16
+ }
17
+ function _isIdentifierDefined(node, parent, name) {
18
+ if (parent &&
19
+ (parent.type === 'MemberExpression' || parent.type === 'CallExpression')) {
20
+ return false;
21
+ }
22
+ return node.type === 'Identifier' && node.name === name;
23
+ }
24
+ function _isFunctionInvoked(node, functionName) {
25
+ return (node.type === 'CallExpression' &&
26
+ node.callee &&
27
+ 'name' in node.callee &&
28
+ node.callee.name === functionName);
29
+ }
30
+ function _checkForFunctionMetadata(node, parent, output, functionName) {
31
+ if (!node) {
32
+ return;
33
+ }
34
+ if (!output.functions[functionName]) {
35
+ output.functions[functionName] = {};
36
+ }
37
+ if (_isFunctionInvoked(node, functionName)) {
38
+ output.functions[functionName].invoked = true;
39
+ // If the function is invoked before being defined we will assume it is a global function
40
+ output.functions[functionName].scope = output.functions[functionName]
41
+ .defined
42
+ ? 'Local'
43
+ : 'Global';
44
+ }
45
+ else if (_isIdentifierDefined(node, parent, functionName) ||
46
+ _isVariableImported(node, functionName)) {
47
+ output.functions[functionName].defined = true;
48
+ }
49
+ }
50
+ // Traverses an ESTree as defined by the EsTree spec https://github.com/estree/estree
51
+ // Uses the checks array to search the source code for matches
52
+ function traverseAbstractSyntaxTree(ast, checks) {
53
+ const state = {
54
+ functions: {},
55
+ };
56
+ (0, estraverse_1.traverse)(ast, {
57
+ enter(node, parent) {
58
+ checks.forEach((check) => {
59
+ _checkForFunctionMetadata(node, parent, state, check.functionName);
60
+ });
61
+ },
62
+ });
63
+ return state;
64
+ }
65
+ exports.traverseAbstractSyntaxTree = traverseAbstractSyntaxTree;
package/dist/lib/build.js CHANGED
@@ -19,6 +19,7 @@ const manifestPlugin_1 = __importDefault(require("./plugins/manifestPlugin"));
19
19
  const path_1 = __importDefault(require("path"));
20
20
  const utils_1 = require("./utils");
21
21
  const codeInjectionPlugin_1 = __importDefault(require("./plugins/codeInjectionPlugin"));
22
+ const codeBlockingPlugin_1 = __importDefault(require("./plugins/codeBlockingPlugin"));
22
23
  const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
23
24
  exports.extensionErrorBaseMessage = `Supported file extensions are [${allowedExtensions.join(', ')}], received:`;
24
25
  function buildSingleExtension({ file, outputDir = constants_1.OUTPUT_DIR, emptyOutDir = true, minify = false, root = process.cwd(), // This is the vite default, so using that as our default
@@ -40,6 +41,7 @@ function buildSingleExtension({ file, outputDir = constants_1.OUTPUT_DIR, emptyO
40
41
  rollupOptions: Object.assign(Object.assign({}, constants_1.ROLLUP_OPTIONS), { plugins: [
41
42
  (0, manifestPlugin_1.default)({ output, extensionPath: root }),
42
43
  (0, codeInjectionPlugin_1.default)({ file }),
44
+ (0, codeBlockingPlugin_1.default)(),
43
45
  ] }),
44
46
  outDir: outputDir,
45
47
  emptyOutDir,
@@ -18,7 +18,7 @@ function loadExtensionConfig(appConfig, appPath) {
18
18
  var _a, _b;
19
19
  const crmCardsSubConfigFiles = (_b = (_a = appConfig === null || appConfig === void 0 ? void 0 : appConfig.extensions) === null || _a === void 0 ? void 0 : _a.crm) === null || _b === void 0 ? void 0 : _b.cards;
20
20
  const outputConfig = {};
21
- crmCardsSubConfigFiles.forEach(card => {
21
+ crmCardsSubConfigFiles.forEach((card) => {
22
22
  var _a, _b;
23
23
  const cardConfigPath = path_1.default.join(appPath, card.file);
24
24
  try {
@@ -18,7 +18,7 @@ class ExtensionsService {
18
18
  generateExtensionsHandler(devServerState, capabilities = []) {
19
19
  return function extensionsHandler(_req, res) {
20
20
  try {
21
- const extensions = devServerState.extensionsMetadata.map(metadata => {
21
+ const extensions = devServerState.extensionsMetadata.map((metadata) => {
22
22
  const { baseMessage } = metadata;
23
23
  const output = path_1.default.parse(baseMessage.callback).name;
24
24
  return Object.assign(Object.assign({}, baseMessage), { manifest: (0, utils_1.loadManifest)(devServerState.outputDir, output) });
@@ -0,0 +1,4 @@
1
+ import { Rollup } from 'vite';
2
+ export type CodeBlockingPlugin = () => Rollup.Plugin;
3
+ declare const codeBlockingPlugin: CodeBlockingPlugin;
4
+ export default codeBlockingPlugin;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ /* eslint-disable hubspot-dev/no-unsupported-ts-syntax */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const utils_1 = require("../utils");
5
+ const ast_1 = require("../ast");
6
+ // @ts-expect-error no types
7
+ const logger_1 = require("@hubspot/cli-lib/logger");
8
+ const codeBlockingPlugin = () => {
9
+ return {
10
+ name: 'ui-extensions-code-blocking-plugin',
11
+ enforce: 'post',
12
+ transform(code, filename) {
13
+ if ((0, utils_1.isNodeModule)(filename)) {
14
+ return { code, map: null }; // We don't want to parse node modules
15
+ }
16
+ let sourceCodeMetadata = { functions: {} };
17
+ const requireFunctionName = 'require';
18
+ try {
19
+ // Not sure why the types don't match for this.parse and the Rollup docs, but
20
+ // the docs over on rollup's site specify ESTree.Program as the return type,
21
+ // and the underlying data matches that https://rollupjs.org/plugin-development/#this-parse
22
+ const abstractSyntaxTree = this.parse(code);
23
+ sourceCodeMetadata = (0, ast_1.traverseAbstractSyntaxTree)(abstractSyntaxTree, [
24
+ { functionName: requireFunctionName },
25
+ ]);
26
+ }
27
+ catch (e) {
28
+ logger_1.logger.debug('Unable to parse and traverse source code');
29
+ return { code, map: null };
30
+ }
31
+ if (sourceCodeMetadata.functions[requireFunctionName] &&
32
+ sourceCodeMetadata.functions[requireFunctionName].scope === 'Global') {
33
+ logger_1.logger.warn('require statements are not supported, replace require statements with import');
34
+ }
35
+ return { code, map: null };
36
+ },
37
+ };
38
+ };
39
+ exports.default = codeBlockingPlugin;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const fs_1 = __importDefault(require("fs"));
7
7
  // @ts-expect-error no type defs for the logger
8
8
  const logger_1 = require("@hubspot/cli-lib/logger");
9
- const codeCheckingPlugin = options => {
9
+ const codeCheckingPlugin = (options) => {
10
10
  const { output } = options;
11
11
  return {
12
12
  name: 'ui-extensions-code-checking-plugin',
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const path_1 = __importDefault(require("path"));
7
- const codeInjectionPlugin = options => {
7
+ const codeInjectionPlugin = (options) => {
8
8
  const { file, root = process.cwd() } = options;
9
9
  return {
10
10
  name: 'ui-extensions-code-injection-plugin',
@@ -47,10 +47,11 @@ 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
+ const codeBlockingPlugin_1 = __importDefault(require("./codeBlockingPlugin"));
50
51
  function addVersionToBaseMessage(baseMessage) {
51
52
  return Object.assign(Object.assign({}, baseMessage), { version: constants_1.WEBSOCKET_MESSAGE_VERSION });
52
53
  }
53
- const devBuildPlugin = options => {
54
+ const devBuildPlugin = (options) => {
54
55
  const { devServerState } = options;
55
56
  let lastBuildErrorContext;
56
57
  const handleBuildError = (error, server) => {
@@ -102,6 +103,7 @@ const devBuildPlugin = options => {
102
103
  }),
103
104
  (0, friendlyLoggingPlugin_1.default)(),
104
105
  (0, relevantModulesPlugin_1.default)({ output: extensionConfig.output }),
106
+ (0, codeBlockingPlugin_1.default)(),
105
107
  ], output: Object.assign(Object.assign({}, constants_1.ROLLUP_OPTIONS.output), { sourcemap: 'inline' }) }),
106
108
  outDir: devServerState.outputDir,
107
109
  emptyOutDir,
@@ -132,7 +134,7 @@ const devBuildPlugin = options => {
132
134
  localServer = server;
133
135
  localServer.ws.on('connection', () => {
134
136
  logger_1.logger.info('Browser connected and listening for bundle updates');
135
- devServerState.extensionsMetadata.forEach(metadata => {
137
+ devServerState.extensionsMetadata.forEach((metadata) => {
136
138
  // @ts-expect-error Our websocket messages don't match Vite format
137
139
  localServer.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(metadata.baseMessage)), { event: 'start' }));
138
140
  });
@@ -146,7 +148,7 @@ const devBuildPlugin = options => {
146
148
  }),
147
149
  handleHotUpdate: ({ file, server }) => __awaiter(void 0, void 0, void 0, function* () {
148
150
  // If the file is not in the relevantModules list, it's update is inconsequential
149
- const extensionsToRebuild = devServerState.extensionsMetadata.filter(metadata => {
151
+ const extensionsToRebuild = devServerState.extensionsMetadata.filter((metadata) => {
150
152
  const { config } = metadata;
151
153
  return (0, relevantModulesPlugin_1.getRelevantModules)(config.output).includes(file);
152
154
  });
@@ -174,7 +176,7 @@ const devBuildPlugin = options => {
174
176
  }
175
177
  if (localServer && localServer.ws) {
176
178
  logger_1.logger.debug('Sending shutdown message to connected browsers');
177
- devServerState.extensionsMetadata.forEach(metadata => {
179
+ devServerState.extensionsMetadata.forEach((metadata) => {
178
180
  // @ts-expect-error Our websocket messages don't match Vite format
179
181
  localServer.ws.send(Object.assign(Object.assign({}, addVersionToBaseMessage(metadata.baseMessage)), { event: 'shutdown' }));
180
182
  });
@@ -15,7 +15,7 @@ const utils_1 = require("../utils");
15
15
  const PACKAGE_LOCK_FILE = 'package-lock.json';
16
16
  const PACKAGE_FILE = 'package.json';
17
17
  const EXTENSIONS_PATH = 'src/app/extensions/';
18
- const manifestPlugin = options => {
18
+ const manifestPlugin = (options) => {
19
19
  return {
20
20
  name: 'ui-extensions-manifest-generation-plugin',
21
21
  enforce: 'post',
@@ -43,7 +43,7 @@ function _generateManifestContents(bundle, extensionPath) {
43
43
  package: _loadPackageFile(extensionPath),
44
44
  };
45
45
  // The keys to bundle are the filename without any path information
46
- const bundles = Object.keys(bundle).filter(cur => cur.endsWith('.js'));
46
+ const bundles = Object.keys(bundle).filter((cur) => cur.endsWith('.js'));
47
47
  if (bundles.length === 1) {
48
48
  return Object.assign(Object.assign({}, _generateManifestEntry(bundle[bundles[0]])), baseManifest);
49
49
  }
@@ -19,7 +19,7 @@ const relevantModulesPlugin = ({ output }) => {
19
19
  logger_1.logger.error('Invalid bundle format, please try saving the extension again. If the problem persists try restarting `hs project dev`');
20
20
  return;
21
21
  }
22
- const updatedRelevantModules = subBundle.moduleIds.filter(moduleId => !(0, utils_1.isNodeModule)(moduleId));
22
+ const updatedRelevantModules = subBundle.moduleIds.filter((moduleId) => !(0, utils_1.isNodeModule)(moduleId));
23
23
  if (updatedRelevantModules.length === 0) {
24
24
  logger_1.logger.error('Unable to determine relevant files to watch, please try saving the extension again. If the problem persists try restarting `hs project dev`');
25
25
  return;
@@ -27,7 +27,7 @@ function listen(app, port) {
27
27
  .listen({ port }, () => {
28
28
  resolve(server);
29
29
  })
30
- .on('error', err => {
30
+ .on('error', (err) => {
31
31
  reject(err);
32
32
  });
33
33
  });
@@ -43,7 +43,7 @@ function startDevServer({ devServerState, viteDevServer, }) {
43
43
  logger_1.logger.info(`Serving app functions locally (platform version ${devServerState.functionsConfig.platformVersion})`);
44
44
  const endpointsAdded = extensionsService_1.default.add(app, devServerState, constants_1.SERVER_CAPABILITIES);
45
45
  const { expressPort } = devServerState;
46
- endpointsAdded.forEach(endpoint => {
46
+ endpointsAdded.forEach((endpoint) => {
47
47
  logger_1.logger.debug(`Listening at http://hslocal.net:${expressPort}${endpoint}`);
48
48
  });
49
49
  // Vite middlewares needs to go last because it's greedy and will block other middleware
@@ -58,7 +58,7 @@ function startDevServer({ devServerState, viteDevServer, }) {
58
58
  }
59
59
  throw new Error(e);
60
60
  }
61
- (_a = devServerState.extensionsMetadata) === null || _a === void 0 ? void 0 : _a.forEach(metadata => {
61
+ (_a = devServerState.extensionsMetadata) === null || _a === void 0 ? void 0 : _a.forEach((metadata) => {
62
62
  const { baseMessage } = metadata;
63
63
  logger_1.logger.debug(`Listening at ${baseMessage.callback}`);
64
64
  });
@@ -56,5 +56,19 @@ export interface ExtensionMetadata {
56
56
  baseMessage: BaseMessage;
57
57
  config: ExtensionConfig;
58
58
  }
59
- export type PlatformVersion = typeof PLATFORM_VERSION[keyof typeof PLATFORM_VERSION];
59
+ export type PlatformVersion = (typeof PLATFORM_VERSION)[keyof typeof PLATFORM_VERSION];
60
+ export interface FunctionMetadata {
61
+ scope?: 'Global' | 'Local';
62
+ defined?: boolean;
63
+ invoked?: boolean;
64
+ }
65
+ export interface SourceCodeMetadata {
66
+ functions: {
67
+ [functionName: string]: FunctionMetadata;
68
+ };
69
+ }
70
+ export interface FunctionInvocationCheck {
71
+ functionName: string;
72
+ }
73
+ export type SourceCodeChecks = FunctionInvocationCheck[];
60
74
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubspot/ui-extensions-dev-server",
3
- "version": "0.8.6",
3
+ "version": "0.8.9",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -9,7 +9,8 @@
9
9
  "watch": "npm run clean && tsc --watch",
10
10
  "prepare": "npm run build",
11
11
  "lint": "echo 'no lint step for @hubspot/ui-extensions-dev-server'",
12
- "jest": "jest --watch"
12
+ "jest": "jest --watch",
13
+ "integration-test": "npm run build && npm run integration-test --prefix ./integrationTests/fixtures"
13
14
  },
14
15
  "publishConfig": {
15
16
  "access": "public"
@@ -24,21 +25,24 @@
24
25
  ],
25
26
  "license": "MIT",
26
27
  "dependencies": {
27
- "@hubspot/app-functions-dev-server": "^0.8.3",
28
+ "@hubspot/app-functions-dev-server": "^0.8.9",
28
29
  "@hubspot/cli-lib": "^4.1.6",
29
30
  "command-line-args": "^5.2.1",
30
31
  "command-line-usage": "^7.0.1",
31
32
  "console-log-colors": "^0.4.0",
32
33
  "cors": "^2.8.5",
33
34
  "detect-port": "1.5.1",
35
+ "estraverse": "^5.3.0",
34
36
  "express": "^4.18.2",
35
37
  "inquirer": "8.2.0",
36
38
  "vite": "^4.4.9"
37
39
  },
38
40
  "devDependencies": {
41
+ "@types/estree": "^1.0.5",
39
42
  "@types/express": "types/express",
40
43
  "@types/inquirer": "^9.0.3",
41
44
  "@types/jest": "^29.5.4",
45
+ "acorn": "^8.11.2",
42
46
  "axios": "^1.4.0",
43
47
  "jest": "^29.5.0",
44
48
  "ts-jest": "^29.1.1",
@@ -63,5 +67,5 @@
63
67
  "optional": true
64
68
  }
65
69
  },
66
- "gitHead": "621bee406c8dfa9ca133f1d76f4bde725a2b9f43"
70
+ "gitHead": "03c10fd116330e174654829acfa99c5e41ff870e"
67
71
  }