@hubspot/ui-extensions-dev-server 0.2.0 → 0.3.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,287 +0,0 @@
1
- const { spawn } = require('child_process');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const assert = require('assert');
5
- const axios = require('axios');
6
- const WebSocket = require('ws');
7
- const {
8
- VITE_DEFAULT_PORT,
9
- WEBSOCKET_PORT,
10
- WEBSOCKET_MESSAGE_VERSION,
11
- EXTENSIONS_MESSAGE_VERSION,
12
- } = require('../constants');
13
- const { verifyFileContents, generateManifestOutputPair } = require('./utils');
14
- const { getUrlSafeFileName } = require('../utils');
15
-
16
- const preShutdownResults = {
17
- buildTestPassed: false,
18
- expressStaticTestPassed: false,
19
- extensionsEndpointPassed: false,
20
- webSocketTestPassed: false,
21
- };
22
-
23
- const postShutdownResults = {
24
- websocketShutdownReceived: false,
25
- expressServerShutdown: false,
26
- };
27
-
28
- const host = 'http://hslocal.net';
29
-
30
- let running = false;
31
-
32
- function resetResults() {
33
- Object.keys(preShutdownResults).forEach(result => {
34
- preShutdownResults[result] = false;
35
- });
36
- Object.keys(postShutdownResults).forEach(result => {
37
- postShutdownResults[result] = false;
38
- });
39
- }
40
-
41
- function testDevServer(logger, devServerProcess, specs) {
42
- const devServerInterval = setInterval(runDevServer, 1000);
43
- let runCount = 0;
44
-
45
- function runDevServer() {
46
- if (running === false && !devServerProcess) {
47
- resetResults();
48
- _testDevServer(logger, devServerProcess, specs[runCount]);
49
- runCount += 1;
50
- }
51
- if (runCount === specs.length) {
52
- clearInterval(devServerInterval);
53
- }
54
- }
55
- }
56
-
57
- function _testDevServer(logger, devServerProcess, spec) {
58
- running = true;
59
- const { entrypoint, expected } = spec;
60
-
61
- const filename = getUrlSafeFileName(entrypoint);
62
- const localExpectations = {
63
- callback: `${host}:${VITE_DEFAULT_PORT}/${filename}`,
64
- websocketUrl: `ws://localhost:${WEBSOCKET_PORT}`,
65
- ...expected,
66
- };
67
-
68
- logger.warn(`\nDev Server Tests started - ${entrypoint} 🤞`);
69
- // We need to use spawn here because it will put the process into the background,
70
- // which is required because dev mode is a blocking process and we want to test that
71
- // the express server and websocket server are starting properly
72
- devServerProcess = spawn('hs-ui-extensions-dev-server', [
73
- 'dev',
74
- '--extension',
75
- entrypoint,
76
- ]);
77
-
78
- devServerProcess.stdout.on('data', buffer => {
79
- const data = buffer.toString().toLowerCase();
80
- console.debug('[Dev Server]:', data);
81
- if (data.includes('built in')) {
82
- testBuild(logger, entrypoint);
83
- }
84
- if (
85
- data.includes('listening') &&
86
- data.includes(`${host}:${VITE_DEFAULT_PORT}/extensions`)
87
- ) {
88
- setTimeout(() => {
89
- testExpressServer(logger, localExpectations, filename);
90
- testWebSocketServer(logger, localExpectations);
91
- }, 1000);
92
- }
93
- });
94
-
95
- // If the dev server writes to stderr, log the error and throw a new error
96
- devServerProcess.stderr.on('data', buffer => {
97
- const data = buffer.toString();
98
- logger.error(data.toString());
99
- throw new Error(data);
100
- });
101
-
102
- // When the process closes make sure we met all the success conditions
103
- devServerProcess.on('close', () => {
104
- if (metPreShutdownConditions()) {
105
- logger.info(`- Dev Server pre-shutdown tests passed - ${entrypoint} 🚀`);
106
- testPostShutdown(logger, filename);
107
- } else {
108
- console.log(preShutdownResults);
109
- logger.error('Tests failed 😭');
110
- }
111
- });
112
-
113
- const preShutdownInterval = setInterval(preShutdownCallback, 1000);
114
- let preShutdownCheckCount = 0;
115
- function preShutdownCallback() {
116
- preShutdownCheckCount += 1;
117
- if (metPreShutdownConditions() || preShutdownCheckCount === 5) {
118
- clearInterval(preShutdownInterval);
119
- devServerProcess.kill();
120
- }
121
- }
122
- }
123
-
124
- function metPreShutdownConditions() {
125
- const {
126
- buildTestPassed,
127
- expressStaticTestPassed,
128
- extensionsEndpointPassed,
129
- webSocketTestPassed,
130
- } = preShutdownResults;
131
- return (
132
- buildTestPassed &&
133
- expressStaticTestPassed &&
134
- extensionsEndpointPassed &&
135
- webSocketTestPassed
136
- );
137
- }
138
-
139
- function metPostShutdownConditions() {
140
- const {
141
- websocketShutdownReceived,
142
- expressServerShutdown,
143
- } = postShutdownResults;
144
-
145
- return websocketShutdownReceived && expressServerShutdown;
146
- }
147
-
148
- // Test that the files were built in the proper location and spot
149
- // check the contents of the files.
150
- function testBuild(logger, entryPoint) {
151
- // // Make sure the files are getting generated in the dist dir
152
- const distDir = path.join(process.cwd(), 'dist');
153
- const filesInOutputDir = fs.readdirSync(distDir);
154
- const filename = path.parse(entryPoint).name;
155
- assert.deepStrictEqual(
156
- filesInOutputDir,
157
- generateManifestOutputPair(entryPoint)
158
- );
159
- const fileContents = fs
160
- .readFileSync(path.join(distDir, `${filename}.js`))
161
- .toString();
162
- verifyFileContents(`${filename}.js`, fileContents);
163
-
164
- // Check for the inlined source map
165
- assert(
166
- fileContents.includes(
167
- '//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
168
- )
169
- );
170
- logger.info('- Build succeeded 🚀');
171
- preShutdownResults.buildTestPassed = true;
172
- }
173
-
174
- // Test that the express server is running on the expected port
175
- // and that it is serving the files as expected.
176
- function testExpressServer(logger, expected, filename) {
177
- axios
178
- .get(`${host}:${VITE_DEFAULT_PORT}/${filename}`)
179
- .then(response => {
180
- assert.strictEqual(response.status, 200, 'Bundle status code');
181
- logger.info('- Express server connected and serving files 🚀');
182
- preShutdownResults.expressStaticTestPassed = true;
183
- })
184
- .catch(logger.error);
185
- axios
186
- .get(`${host}:${VITE_DEFAULT_PORT}/extensions`)
187
- .then(response => {
188
- const { data, status } = response;
189
- assert.strictEqual(status, 200, '/extensions status code');
190
-
191
- // Top level response assertions
192
- assert.strictEqual(data.websocket, expected.websocketUrl);
193
- assert.strictEqual(data.version, EXTENSIONS_MESSAGE_VERSION);
194
- assert(data.extensions);
195
-
196
- // Extension level response assertions
197
- assert.equal(data.extensions.length, 1, '/extensions body length');
198
- const extension = data.extensions.pop();
199
- assert(extension.manifest);
200
- assert.strictEqual(
201
- extension.appName,
202
- expected.appName,
203
- '/extensions appName'
204
- );
205
- assert.strictEqual(extension.title, expected.title, '/extensions title');
206
- assert.strictEqual(
207
- extension.callback,
208
- expected.callback,
209
- '/extensions callback'
210
- );
211
- logger.info('- Express serving extension data 🚀');
212
- preShutdownResults.extensionsEndpointPassed = true;
213
- })
214
- .catch(logger.error);
215
- }
216
-
217
- // Test the the web socket server is running on the expected port and
218
- // that we are able to receive messages from it.
219
- function testWebSocketServer(logger, expected) {
220
- const ws = new WebSocket(expected.websocketUrl);
221
- ws.on('message', messageBuffer => {
222
- const message = JSON.parse(messageBuffer.toString());
223
- console.debug('[WebSocket Message]:', message);
224
- // Vite sends { type: 'connected'} as a greeting on connect
225
- if (message.type === 'connected') {
226
- return;
227
- }
228
-
229
- assert(['start', 'shutdown'].includes(message.event), 'Message Event');
230
- assert.strictEqual(message.appName, expected.appName, 'Message appName');
231
- assert.strictEqual(message.title, expected.title, 'Message title');
232
- assert.strictEqual(message.version, WEBSOCKET_MESSAGE_VERSION);
233
- logger.info(
234
- `- WebSocket server connected and sending '${message.event}' messages 🚀`
235
- );
236
- if (message.event === 'start') {
237
- preShutdownResults.webSocketTestPassed = true;
238
- } else if (message.event === 'shutdown') {
239
- postShutdownResults.websocketShutdownReceived = true;
240
- }
241
- });
242
- }
243
-
244
- function testPostShutdown(logger, filename) {
245
- testExpressHasShutdown(filename);
246
-
247
- const shutdownInterval = setInterval(shutdownCallback, 1000);
248
- let shutdownCheckCount = 0;
249
- function shutdownCallback() {
250
- shutdownCheckCount += 1;
251
- if (metPostShutdownConditions()) {
252
- clearInterval(shutdownInterval);
253
- logger.info('- Dev Server post-shutdown tests passed 🚀');
254
- logger.info('Dev Server tests passed 🚀');
255
- running = false;
256
- } else if (shutdownCheckCount === 5) {
257
- clearInterval(shutdownInterval);
258
- console.log(postShutdownResults);
259
- logger.error('Tests failed 😭');
260
- }
261
- }
262
- }
263
-
264
- function testExpressHasShutdown(filename) {
265
- axios
266
- .get(`${host}:${VITE_DEFAULT_PORT}/${filename}`)
267
- .then(response => {
268
- console.debug(response);
269
- })
270
- .catch(err => {
271
- assert.strictEqual(
272
- err.syscall,
273
- 'connect',
274
- 'The connect call should fail'
275
- );
276
- assert.strictEqual(
277
- err.code,
278
- 'ECONNREFUSED',
279
- 'Express should refuse connection post shutdown'
280
- );
281
- postShutdownResults.expressServerShutdown = true;
282
- });
283
- }
284
-
285
- module.exports = {
286
- testDevServer,
287
- };
package/tests/utils.js DELETED
@@ -1,76 +0,0 @@
1
- const path = require('path');
2
- const { MANIFEST_FILE } = require('../constants');
3
- const { getUrlSafeFileName } = require('../utils');
4
- const assert = require('assert');
5
- const { loadConfig } = require('../config');
6
-
7
- function generateManifestOutputPair(entrypoint) {
8
- const fileName = path.parse(entrypoint).name;
9
- return [`${fileName}-${MANIFEST_FILE}`, getUrlSafeFileName(entrypoint)];
10
- }
11
-
12
- function verifyFileContents(file, contents) {
13
- if (file.endsWith(MANIFEST_FILE)) {
14
- const manifest = JSON.parse(contents);
15
- assert(manifest.entry);
16
- assert(manifest.modules);
17
- assert(manifest.modules.internal);
18
- manifest.modules.internal.forEach(mod => {
19
- assert(mod.module);
20
- assert(mod.renderedExports);
21
- });
22
- assert(manifest.modules.external);
23
- manifest.modules.external.forEach(mod => {
24
- assert(mod.module);
25
- assert(mod.renderedExports);
26
- });
27
- assert(manifest.package);
28
- assert(manifest.package.packages);
29
- } else {
30
- const stringsToSpotCheck = [
31
- '.createRemoteReactComponent',
32
- '.createElement',
33
- 'hubspot.extend',
34
- 'React',
35
- 'RemoteUI',
36
- ];
37
- stringsToSpotCheck.forEach(stringToCheck => {
38
- assert(
39
- contents.includes(stringToCheck),
40
- `File ${file} contents should contain: "${stringToCheck}"`
41
- );
42
- });
43
- }
44
- }
45
-
46
- function generateSpec() {
47
- const config = loadConfig();
48
- const entrypoints = Object.keys(config);
49
- if (entrypoints.length === 0) {
50
- throw new Error(
51
- 'Unable to determine testable entrypoints from config files'
52
- );
53
- }
54
-
55
- const dev = entrypoints.map(entrypoint => {
56
- const { data } = config[entrypoint];
57
- return {
58
- entrypoint,
59
- expected: {
60
- ...data,
61
- },
62
- };
63
- });
64
- return {
65
- build: {
66
- entrypoints,
67
- },
68
- dev,
69
- };
70
- }
71
-
72
- module.exports = {
73
- generateManifestOutputPair,
74
- verifyFileContents,
75
- generateSpec,
76
- };
package/utils.js DELETED
@@ -1,10 +0,0 @@
1
- const path = require('path');
2
-
3
- function getUrlSafeFileName(filePath) {
4
- const { name } = path.parse(filePath);
5
- return encodeURIComponent(`${name}.js`);
6
- }
7
-
8
- module.exports = {
9
- getUrlSafeFileName,
10
- };
File without changes
File without changes