@hubspot/ui-extensions-dev-server 1.1.1 → 1.1.3
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/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +13 -0
- package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +51 -0
- package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +35 -1
- package/dist/lib/plugins/codeCheckingPlugin.js +5 -1
- package/dist/lib/plugins/devBuildPlugin.js +15 -4
- package/dist/lib/plugins/relevantModulesPlugin.d.ts +1 -0
- package/dist/lib/plugins/relevantModulesPlugin.js +8 -0
- package/package.json +3 -2
|
@@ -107,5 +107,18 @@ describe('codeCheckingPlugin', () => {
|
|
|
107
107
|
await runViteBuild('ts-withHubspotImport.ts', plugin);
|
|
108
108
|
expect(logger.warn).not.toHaveBeenCalled();
|
|
109
109
|
});
|
|
110
|
+
it('should not log a warning when the build fails with an error', async () => {
|
|
111
|
+
const { logger, plugin } = createPlugin();
|
|
112
|
+
// Use a fixture file that would normally trigger the warning,
|
|
113
|
+
// but cause a build error by importing a non-existent file
|
|
114
|
+
try {
|
|
115
|
+
await runViteBuild('invalidSyntax.js', plugin);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Build should fail, which is expected
|
|
119
|
+
}
|
|
120
|
+
// Verify warning was not logged despite the fixture missing hubspot import
|
|
121
|
+
expect(logger.warn).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
110
123
|
});
|
|
111
124
|
});
|
|
@@ -5,6 +5,7 @@ vi.mock('../../plugins/relevantModulesPlugin', () => {
|
|
|
5
5
|
getRelevantModules: () => {
|
|
6
6
|
return ['extension.js', 'helper-file.js'];
|
|
7
7
|
},
|
|
8
|
+
addRelevantModule: vi.fn(),
|
|
8
9
|
default: () => {
|
|
9
10
|
return {
|
|
10
11
|
name: 'ui-extensions-relevant-modules-plugin',
|
|
@@ -14,6 +15,7 @@ vi.mock('../../plugins/relevantModulesPlugin', () => {
|
|
|
14
15
|
});
|
|
15
16
|
vi.mock('vite');
|
|
16
17
|
import devBuildPlugin from "../../plugins/devBuildPlugin.js";
|
|
18
|
+
import { addRelevantModule } from "../../plugins/relevantModulesPlugin.js";
|
|
17
19
|
import { build } from 'vite';
|
|
18
20
|
import { PLATFORM_VERSION, WEBSOCKET_MESSAGE_VERSION, } from "../../constants.js";
|
|
19
21
|
import { DevServerState } from "../../DevServerState.js";
|
|
@@ -148,6 +150,7 @@ describe('devBuildPlugin', () => {
|
|
|
148
150
|
it('should broadcast error message on build failure', async () => {
|
|
149
151
|
const error = {
|
|
150
152
|
plugin: 'vite:esbuild',
|
|
153
|
+
message: '[vite:esbuild] Transform failed with 1 error',
|
|
151
154
|
errors: ['you did something wrong'],
|
|
152
155
|
frame: 'It broke on this line',
|
|
153
156
|
loc: {
|
|
@@ -162,6 +165,12 @@ describe('devBuildPlugin', () => {
|
|
|
162
165
|
});
|
|
163
166
|
// @ts-expect-error TS thinks these aren't functions
|
|
164
167
|
await plugin.handleHotUpdate({ file: 'extension.js', server });
|
|
168
|
+
expect(logger.error).toHaveBeenCalledWith(error.message);
|
|
169
|
+
// Verify entry file and error file are added to relevant modules for HMR
|
|
170
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
171
|
+
const entryFile = extensionConfig.data.module.file;
|
|
172
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, entryFile);
|
|
173
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, error.id);
|
|
165
174
|
expect(mockWebSocket.broadcast).toHaveBeenCalled();
|
|
166
175
|
const baseMessage = options.devServerState.extensionsMetadata[0].baseMessage;
|
|
167
176
|
expect(mockWebSocket.broadcast).toHaveBeenCalledWith({
|
|
@@ -178,6 +187,27 @@ describe('devBuildPlugin', () => {
|
|
|
178
187
|
version: WEBSOCKET_MESSAGE_VERSION,
|
|
179
188
|
});
|
|
180
189
|
});
|
|
190
|
+
it('should add only entry file when error file is same as entry file', async () => {
|
|
191
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
192
|
+
const entryFile = extensionConfig.data.module.file;
|
|
193
|
+
const error = {
|
|
194
|
+
plugin: 'vite:esbuild',
|
|
195
|
+
message: '[vite:esbuild] Transform failed with 1 error',
|
|
196
|
+
errors: ['syntax error in entry file'],
|
|
197
|
+
frame: 'Error on this line',
|
|
198
|
+
loc: { column: 1, line: 5 },
|
|
199
|
+
id: entryFile, // Same as entry file
|
|
200
|
+
};
|
|
201
|
+
vi.mocked(build).mockImplementationOnce(() => {
|
|
202
|
+
throw error;
|
|
203
|
+
});
|
|
204
|
+
// @ts-expect-error TS thinks these aren't functions
|
|
205
|
+
await plugin.handleHotUpdate({ file: 'extension.js', server });
|
|
206
|
+
// Verify entry file is added
|
|
207
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, entryFile);
|
|
208
|
+
// Verify addRelevantModule is only called once (not twice for the same file)
|
|
209
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledTimes(1);
|
|
210
|
+
});
|
|
181
211
|
it('should not broadcast when error is from ui-extensions plugin', async () => {
|
|
182
212
|
const error = {
|
|
183
213
|
plugin: 'ui-extensions-some-plugin-that-threw-an-error',
|
|
@@ -260,6 +290,7 @@ describe('devBuildPlugin', () => {
|
|
|
260
290
|
options.devServerState._mutableState.extensionsWebSocket = null;
|
|
261
291
|
const error = {
|
|
262
292
|
plugin: 'vite:esbuild',
|
|
293
|
+
message: '[vite:esbuild] Build error',
|
|
263
294
|
errors: ['build error'],
|
|
264
295
|
frame: 'Error on this line',
|
|
265
296
|
loc: { column: 1, line: 5 },
|
|
@@ -272,6 +303,12 @@ describe('devBuildPlugin', () => {
|
|
|
272
303
|
await expect(
|
|
273
304
|
// @ts-expect-error TS thinks this isn't a function
|
|
274
305
|
plugin.configureServer(server)).resolves.not.toThrow();
|
|
306
|
+
expect(logger.error).toHaveBeenCalledWith(error.message);
|
|
307
|
+
// Verify entry file and error file are added to relevant modules
|
|
308
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
309
|
+
const entryFile = extensionConfig.data.module.file;
|
|
310
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, entryFile);
|
|
311
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, error.id);
|
|
275
312
|
// Verify broadcast was NOT called (since websocket doesn't exist)
|
|
276
313
|
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
277
314
|
});
|
|
@@ -279,6 +316,7 @@ describe('devBuildPlugin', () => {
|
|
|
279
316
|
// Websocket is already set up in beforeEach
|
|
280
317
|
const error = {
|
|
281
318
|
plugin: 'vite:esbuild',
|
|
319
|
+
message: '[vite:esbuild] Build error',
|
|
282
320
|
errors: ['build error'],
|
|
283
321
|
frame: 'Error on this line',
|
|
284
322
|
loc: { column: 1, line: 5 },
|
|
@@ -289,6 +327,12 @@ describe('devBuildPlugin', () => {
|
|
|
289
327
|
});
|
|
290
328
|
// @ts-expect-error TS thinks this isn't a function
|
|
291
329
|
await plugin.configureServer(server);
|
|
330
|
+
expect(logger.error).toHaveBeenCalledWith(error.message);
|
|
331
|
+
// Verify entry file and error file are added to relevant modules
|
|
332
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
333
|
+
const entryFile = extensionConfig.data.module.file;
|
|
334
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, entryFile);
|
|
335
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, error.id);
|
|
292
336
|
options.devServerState.triggerWebSocketSetup();
|
|
293
337
|
// Get the connection handler
|
|
294
338
|
const connectionHandler = mockWebSocket.onConnection.mock.calls[0][0];
|
|
@@ -309,6 +353,7 @@ describe('devBuildPlugin', () => {
|
|
|
309
353
|
options.devServerState._mutableState.extensionsWebSocket = null;
|
|
310
354
|
const error = {
|
|
311
355
|
plugin: 'vite:esbuild',
|
|
356
|
+
message: '[vite:esbuild] Build error',
|
|
312
357
|
errors: ['build error'],
|
|
313
358
|
frame: 'Error on this line',
|
|
314
359
|
loc: { column: 1, line: 5 },
|
|
@@ -320,6 +365,12 @@ describe('devBuildPlugin', () => {
|
|
|
320
365
|
// configureServer runs with error, but websocket doesn't exist yet
|
|
321
366
|
// @ts-expect-error TS thinks this isn't a function
|
|
322
367
|
await plugin.configureServer(server);
|
|
368
|
+
expect(logger.error).toHaveBeenCalledWith(error.message);
|
|
369
|
+
// Verify entry file and error file are added to relevant modules
|
|
370
|
+
const extensionConfig = options.devServerState.extensionsMetadata[0].config;
|
|
371
|
+
const entryFile = extensionConfig.data.module.file;
|
|
372
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, entryFile);
|
|
373
|
+
expect(vi.mocked(addRelevantModule)).toHaveBeenCalledWith(extensionConfig.output, error.id);
|
|
323
374
|
// At this point, broadcast should not have been called
|
|
324
375
|
expect(mockWebSocket.broadcast).not.toHaveBeenCalled();
|
|
325
376
|
// Now initialize the websocket (simulating what happens in server.ts)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
-
import relevantModulesPlugin, { getRelevantModules, } from "../../plugins/relevantModulesPlugin.js";
|
|
2
|
+
import relevantModulesPlugin, { getRelevantModules, addRelevantModule, } from "../../plugins/relevantModulesPlugin.js";
|
|
3
3
|
describe('manifestPlugin', () => {
|
|
4
4
|
let logger;
|
|
5
5
|
let options;
|
|
@@ -49,6 +49,40 @@ describe('manifestPlugin', () => {
|
|
|
49
49
|
expect(actual).toStrictEqual([]);
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
|
+
describe('addRelevantModule', () => {
|
|
53
|
+
it('should add module to empty list', () => {
|
|
54
|
+
const output = 'TestOutput.js';
|
|
55
|
+
const modulePath = '/path/to/module.js';
|
|
56
|
+
addRelevantModule(output, modulePath);
|
|
57
|
+
const actual = getRelevantModules(output);
|
|
58
|
+
expect(actual).toStrictEqual([modulePath]);
|
|
59
|
+
});
|
|
60
|
+
it('should add module to existing list', () => {
|
|
61
|
+
const output = 'AnotherOutput.js';
|
|
62
|
+
addRelevantModule(output, 'module1.js');
|
|
63
|
+
addRelevantModule(output, 'module2.js');
|
|
64
|
+
const actual = getRelevantModules(output);
|
|
65
|
+
expect(actual).toStrictEqual(['module1.js', 'module2.js']);
|
|
66
|
+
});
|
|
67
|
+
it('should not add duplicate modules', () => {
|
|
68
|
+
const output = 'DuplicateTest.js';
|
|
69
|
+
const modulePath = '/path/to/module.js';
|
|
70
|
+
addRelevantModule(output, modulePath);
|
|
71
|
+
addRelevantModule(output, modulePath);
|
|
72
|
+
addRelevantModule(output, modulePath);
|
|
73
|
+
const actual = getRelevantModules(output);
|
|
74
|
+
expect(actual).toStrictEqual([modulePath]);
|
|
75
|
+
});
|
|
76
|
+
it('should create output entry if it does not exist', () => {
|
|
77
|
+
const output = 'NewOutput.js';
|
|
78
|
+
const modulePath = '/path/to/new/module.js';
|
|
79
|
+
// Verify output doesn't exist yet
|
|
80
|
+
expect(getRelevantModules(output)).toStrictEqual([]);
|
|
81
|
+
addRelevantModule(output, modulePath);
|
|
82
|
+
// Verify output was created with the module
|
|
83
|
+
expect(getRelevantModules(output)).toStrictEqual([modulePath]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
52
86
|
describe('generateBundle', () => {
|
|
53
87
|
it('should not update the relevantModules when output is not found in the bundle', () => {
|
|
54
88
|
const plugin = relevantModulesPlugin(options);
|
|
@@ -64,7 +64,11 @@ const codeCheckingPlugin = (options) => {
|
|
|
64
64
|
* If not found, we log a warning to help developers understand why their extension
|
|
65
65
|
* might not render correctly.
|
|
66
66
|
*/
|
|
67
|
-
buildEnd() {
|
|
67
|
+
buildEnd(error) {
|
|
68
|
+
// Don't display the warning if there was a build error
|
|
69
|
+
if (error) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
68
72
|
if (!foundHubspotImport) {
|
|
69
73
|
logger.warn(`
|
|
70
74
|
|
|
@@ -4,7 +4,7 @@ import manifestPlugin from "./manifestPlugin.js";
|
|
|
4
4
|
import { stripAnsiColorCodes } from "../utils.js";
|
|
5
5
|
import codeCheckingPlugin from "./codeCheckingPlugin.js";
|
|
6
6
|
import friendlyLoggingPlugin from "./friendlyLoggingPlugin.js";
|
|
7
|
-
import relevantModulesPlugin, { getRelevantModules, } from "./relevantModulesPlugin.js";
|
|
7
|
+
import relevantModulesPlugin, { getRelevantModules, addRelevantModule, } from "./relevantModulesPlugin.js";
|
|
8
8
|
import codeBlockingPlugin from "./codeBlockingPlugin.js";
|
|
9
9
|
function addVersionToBaseMessage(baseMessage) {
|
|
10
10
|
return {
|
|
@@ -52,9 +52,9 @@ const devBuildPlugin = (options) => {
|
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
54
|
const devBuild = async (server, extensionMetadata, emptyOutDir = false) => {
|
|
55
|
+
const { config: extensionConfig } = extensionMetadata;
|
|
56
|
+
const { extensionPath } = extensionConfig;
|
|
55
57
|
try {
|
|
56
|
-
const { config: extensionConfig } = extensionMetadata;
|
|
57
|
-
const { extensionPath } = extensionConfig;
|
|
58
58
|
let manifestConfig = {};
|
|
59
59
|
if (devServerState.appConfig &&
|
|
60
60
|
'variables' in devServerState.appConfig &&
|
|
@@ -122,7 +122,18 @@ const devBuildPlugin = (options) => {
|
|
|
122
122
|
error: error,
|
|
123
123
|
extensionMetadata,
|
|
124
124
|
};
|
|
125
|
-
|
|
125
|
+
// Log the error for user visibility
|
|
126
|
+
const errorPayload = error;
|
|
127
|
+
if (errorPayload.message) {
|
|
128
|
+
logger.error(errorPayload.message);
|
|
129
|
+
}
|
|
130
|
+
// Ensure the files are tracked for HMR even when the build fails
|
|
131
|
+
const entryFile = extensionConfig.data.module.file;
|
|
132
|
+
addRelevantModule(extensionConfig.output, entryFile);
|
|
133
|
+
if (errorPayload.id && errorPayload.id !== entryFile) {
|
|
134
|
+
addRelevantModule(extensionConfig.output, errorPayload.id);
|
|
135
|
+
}
|
|
136
|
+
logger.debug('Full build error:', error);
|
|
126
137
|
handleBuildError(lastBuildErrorContext);
|
|
127
138
|
return false;
|
|
128
139
|
}
|
|
@@ -4,6 +4,7 @@ export interface RelevantModules {
|
|
|
4
4
|
[key: string]: string[];
|
|
5
5
|
}
|
|
6
6
|
export declare function getRelevantModules(output: string): string[];
|
|
7
|
+
export declare function addRelevantModule(output: string, modulePath: string): void;
|
|
7
8
|
export interface RelevantModulesPluginOptions {
|
|
8
9
|
output: string;
|
|
9
10
|
logger: Logger;
|
|
@@ -3,6 +3,14 @@ const relevantModules = {};
|
|
|
3
3
|
export function getRelevantModules(output) {
|
|
4
4
|
return relevantModules[output] || [];
|
|
5
5
|
}
|
|
6
|
+
export function addRelevantModule(output, modulePath) {
|
|
7
|
+
if (!relevantModules[output]) {
|
|
8
|
+
relevantModules[output] = [];
|
|
9
|
+
}
|
|
10
|
+
if (!relevantModules[output].includes(modulePath)) {
|
|
11
|
+
relevantModules[output].push(modulePath);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
6
14
|
const relevantModulesPlugin = ({ output, logger }) => {
|
|
7
15
|
return {
|
|
8
16
|
name: 'ui-extensions-relevant-modules-plugin',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions-dev-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"build": "npm run clean && tsc",
|
|
13
13
|
"watch": "npm run clean && tsc --watch",
|
|
14
14
|
"prepare": "husky",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
15
16
|
"prettier:write": "prettier --write '**/*.{ts,js,json}'",
|
|
16
17
|
"lint": "echo 'Linting is disabled for Blazar'",
|
|
17
18
|
"lint:local": "eslint . && prettier --check '**/*.{ts,js,json}'",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
],
|
|
33
34
|
"license": "MIT",
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"@hubspot/app-functions-dev-server": "0.11.
|
|
36
|
+
"@hubspot/app-functions-dev-server": "0.11.1",
|
|
36
37
|
"chalk": "5.4.1",
|
|
37
38
|
"commander": "13.1.0",
|
|
38
39
|
"cors": "2.8.5",
|