@hubspot/ui-extensions-dev-server
Advanced tools
Comparing version 0.0.1-prealpha.8 to 0.1.0
const prompts = require('prompts'); | ||
const logger = require('./logger'); | ||
const path = require('path'); | ||
const { MAIN_APP_CONFIG, PROJECT_CONFIG } = require('./constants'); | ||
const { MAIN_APP_CONFIG } = require('./constants'); | ||
const { getUrlSafeFileName } = require('./utils'); | ||
async function getExtensionConfig(configuration, extension) { | ||
if (extension && configuration[extension]) { | ||
const { data } = configuration[extension]; | ||
const extensionOptions = Object.keys(configuration); | ||
if ( | ||
(extension && configuration[extension]) || | ||
extensionOptions.length === 1 | ||
) { | ||
const extensionToRun = extension || extensionOptions[0]; | ||
const { data } = configuration[extensionToRun]; | ||
return { | ||
key: extension, | ||
key: extensionToRun, | ||
name: data.title, | ||
@@ -19,3 +25,2 @@ file: data?.module?.file, | ||
const extensionOptions = Object.keys(configuration); | ||
const response = await prompts( | ||
@@ -69,6 +74,2 @@ [ | ||
const projectConfig = _loadRequiredConfigFile( | ||
path.join(process.cwd(), `../../../${PROJECT_CONFIG}`) | ||
); | ||
const mainAppConfig = _loadRequiredConfigFile(configPath); | ||
@@ -112,3 +113,3 @@ | ||
); | ||
outputConfig[entryPointPath].data.appName = projectConfig.name; | ||
outputConfig[entryPointPath].data.appName = mainAppConfig.name; | ||
} catch (e) { | ||
@@ -115,0 +116,0 @@ let errorMessage = e?.message; |
@@ -19,2 +19,5 @@ const VITE_DEFAULT_PORT = 5173; | ||
const EXTENSIONS_MESSAGE_VERSION = 0; | ||
const WEBSOCKET_MESSAGE_VERSION = 0; | ||
module.exports = { | ||
@@ -26,2 +29,4 @@ VITE_DEFAULT_PORT, | ||
OUTPUT_DIR, | ||
EXTENSIONS_MESSAGE_VERSION, | ||
WEBSOCKET_MESSAGE_VERSION, | ||
}; |
109
dev.js
@@ -1,15 +0,9 @@ | ||
const express = require('express'); | ||
const chokidar = require('chokidar'); | ||
const { WebSocketServer } = require('ws'); | ||
const http = require('http'); | ||
const logger = require('./logger'); | ||
const { build } = require('vite'); | ||
const { getExtensionConfig } = require('./config'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const { ROLLUP_OPTIONS } = require('./constants'); | ||
const startDevServer = require('./server'); | ||
const manifestPlugin = require('./plugins/manifestPlugin'); | ||
async function _devBuild(config, outputDir, extension) { | ||
const extensionConfig = await getExtensionConfig(config, extension); | ||
build({ | ||
async function _buildDevBundle(outputDir, extensionConfig) { | ||
await build({ | ||
define: { | ||
@@ -36,3 +30,9 @@ 'process.env.NODE_ENV': JSON.stringify( | ||
}, | ||
rollupOptions: ROLLUP_OPTIONS, | ||
rollupOptions: { | ||
...ROLLUP_OPTIONS, | ||
plugins: [ | ||
...(ROLLUP_OPTIONS.plugins || []), | ||
manifestPlugin({ minify: false, extensionConfig }), | ||
], | ||
}, | ||
outDir: outputDir, | ||
@@ -43,89 +43,8 @@ emptyOutDir: true, | ||
}); | ||
return extensionConfig; | ||
} | ||
function _startDevServer(outputDir, port, extensionConfig) { | ||
const app = express(); | ||
const server = http.createServer(app); | ||
// Host the OUTPUT_DIR | ||
app.use(express.static(outputDir)); | ||
// Setup websocket server to send messages to browser on bundle update | ||
const wss = new WebSocketServer({ server }); | ||
const callback = `http://localhost:${port}/${extensionConfig?.output}`; | ||
function broadcast(message) { | ||
if (wss.clients.size === 0) { | ||
logger.warn('No browsers connected to update'); | ||
return; | ||
} | ||
wss.clients.forEach(client => { | ||
client.send(JSON.stringify(message)); | ||
}); | ||
} | ||
wss.on('connection', client => { | ||
let base64Callback; | ||
try { | ||
base64Callback = fs | ||
.readFileSync( | ||
path.join(process.cwd(), outputDir, extensionConfig?.output) | ||
) | ||
.toString('base64'); | ||
} catch (e) { | ||
logger.warn( | ||
'File not found:', | ||
path.join(process.cwd(), outputDir, extensionConfig?.output) | ||
); | ||
} | ||
logger.info('Browser connected and listening for bundle updates'); | ||
client.send( | ||
JSON.stringify({ | ||
event: 'start', | ||
appName: extensionConfig?.appName, | ||
extension: extensionConfig?.name, | ||
callback: base64Callback | ||
? `data:text/javascript;base64,${base64Callback}` | ||
: undefined, | ||
}) | ||
); | ||
}); | ||
// Start the express and websocket servers | ||
server.listen({ port }, () => { | ||
logger.warn(`Listening at ${callback}`); | ||
}); | ||
// Setup a watcher on the dist directory and update broadcast | ||
//to all clients when an event is observed | ||
chokidar.watch(outputDir).on('change', file => { | ||
const base64Callback = fs | ||
.readFileSync(path.join(process.cwd(), file)) | ||
.toString('base64'); | ||
logger.debug(`${file} updated, reloading extension`); | ||
broadcast({ | ||
event: 'update', | ||
appName: extensionConfig?.appName, | ||
extension: extensionConfig?.name, | ||
callback: `data:text/javascript;base64,${base64Callback}`, | ||
}); | ||
}); | ||
process.on('SIGINT', () => { | ||
logger.warn('\nSending shutdown signal to connected browser'); | ||
broadcast({ | ||
event: 'shutdown', | ||
appName: extensionConfig?.appName, | ||
extension: extensionConfig?.name, | ||
}); | ||
process.exit(0); | ||
}); | ||
} | ||
async function startDevMode(config, outputDir, port, extension) { | ||
const extensionConfig = await _devBuild(config, outputDir, extension); | ||
_startDevServer(outputDir, port, extensionConfig); | ||
const extensionConfig = await getExtensionConfig(config, extension); | ||
await _buildDevBundle(outputDir, extensionConfig); | ||
startDevServer(outputDir, port, extensionConfig); | ||
} | ||
@@ -132,0 +51,0 @@ |
{ | ||
"name": "@hubspot/ui-extensions-dev-server", | ||
"version": "0.0.1-prealpha.8", | ||
"version": "0.1.0", | ||
"description": "", | ||
@@ -26,3 +26,4 @@ "main": "index.js", | ||
"plugins/manifestPlugin.js", | ||
"index.js" | ||
"index.js", | ||
"server.js" | ||
], | ||
@@ -35,2 +36,3 @@ "license": "MIT", | ||
"console-log-colors": "^0.4.0", | ||
"cors": "^2.8.5", | ||
"express": "^4.18.2", | ||
@@ -40,3 +42,3 @@ "process": "^0.11.10", | ||
"vite": "^4.0.4", | ||
"ws": "^8.12.1" | ||
"ws": "^8.13.0" | ||
}, | ||
@@ -62,3 +64,3 @@ "bin": { | ||
}, | ||
"gitHead": "b5968f431fe29ee9977e6aacdd9b9ab3aa08af0c" | ||
"gitHead": "abac5960e6277af827714dcd4150ad7606e6a481" | ||
} |
@@ -15,5 +15,9 @@ const { readFileSync } = require('fs'); | ||
generateBundle(_rollupOptions, bundle) { | ||
const { output = DEFAULT_MANIFEST_NAME, minify = false } = options; | ||
const { | ||
output = DEFAULT_MANIFEST_NAME, | ||
minify = false, | ||
extensionConfig, | ||
} = options; | ||
try { | ||
const manifest = _generateManifestContents(bundle); | ||
const manifest = _generateManifestContents(bundle, extensionConfig); | ||
this.emitFile({ | ||
@@ -33,5 +37,6 @@ type: 'asset', | ||
function _generateManifestContents(bundle) { | ||
function _generateManifestContents(bundle, extension) { | ||
const baseManifest = { | ||
package: _loadPackageFile(), | ||
extension, | ||
}; | ||
@@ -38,0 +43,0 @@ |
@@ -1,3 +0,5 @@ | ||
# ui-extensions-dev-server | ||
# UI Extensions β Dev Server | ||
Development server to run and test your HubSpot UI Extensions. | ||
## Overview | ||
@@ -4,0 +6,0 @@ This package contains the cli for running HubSpot UI extensions locally, as well as running a production build for debugging purposes. |
@@ -19,3 +19,4 @@ #!/usr/bin/env node | ||
process.on('uncaughtException', () => { | ||
process.on('uncaughtException', e => { | ||
console.error(e); | ||
handleFailure(); | ||
@@ -22,0 +23,0 @@ }); |
@@ -7,3 +7,5 @@ const { execSync } = require('child_process'); | ||
function _testHelper(command, outputDirFiles) { | ||
execSync(command); | ||
if (command) { | ||
execSync(command); | ||
} | ||
@@ -71,7 +73,7 @@ // Make sure the files are getting generated in the dist dir | ||
function testDefInfraBuildFileName(logger) { | ||
logger.warn('- Test build with entrypoint as arg π€'); | ||
_testHelper('hs-ui-extensions-remote-build ProgressBarApp.tsx', [ | ||
'ProgressBarApp.js', | ||
'manifest.json', | ||
]); | ||
const { remoteBuild } = require('../index'); | ||
logger.warn('- Test remoteBuild function π€'); | ||
remoteBuild(process.cwd(), 'ProgressBarApp.tsx', 'dist'); | ||
_testHelper(null, ['ProgressBarApp.js', 'manifest.json']); | ||
logger.info('- Test build with entrypoint as arg π'); | ||
@@ -78,0 +80,0 @@ } |
@@ -7,6 +7,11 @@ const { spawn } = require('child_process'); | ||
const WebSocket = require('ws'); | ||
const { | ||
WEBSOCKET_MESSAGE_VERSION, | ||
EXTENSIONS_MESSAGE_VERSION, | ||
} = require('../constants'); | ||
const testResults = { | ||
buildTestPassed: false, | ||
expressTestPassed: false, | ||
expressStaticTestPassed: false, | ||
extensionsEndpointPassed: false, | ||
webSocketTestPassed: false, | ||
@@ -16,2 +21,3 @@ }; | ||
const port = 5172; | ||
const host = 'hslocal.net'; | ||
@@ -37,3 +43,6 @@ function testDevServer(logger, devServerProcess) { | ||
} | ||
if (data.includes('listening') && data.includes(`localhost:${port}`)) { | ||
if ( | ||
data.includes('listening') && | ||
data.includes(`${host}:${port}/extensions`) | ||
) { | ||
setTimeout(() => { | ||
@@ -77,6 +86,12 @@ testExpressServer(testResults, logger); | ||
buildTestPassed, | ||
expressTestPassed, | ||
expressStaticTestPassed, | ||
extensionsEndpointPassed, | ||
webSocketTestPassed, | ||
} = testResults; | ||
return buildTestPassed && expressTestPassed && webSocketTestPassed; | ||
return ( | ||
buildTestPassed && | ||
expressStaticTestPassed && | ||
extensionsEndpointPassed && | ||
webSocketTestPassed | ||
); | ||
} | ||
@@ -90,3 +105,3 @@ | ||
const filesInOutputDir = fs.readdirSync(distDir); | ||
assert.deepStrictEqual(filesInOutputDir, ['PhoneLines.js']); | ||
assert.deepStrictEqual(filesInOutputDir, ['PhoneLines.js', 'manifest.json']); | ||
const fileContents = fs | ||
@@ -117,3 +132,3 @@ .readFileSync(path.join(distDir, filesInOutputDir[0])) | ||
{ | ||
host: 'localhost', | ||
host, | ||
port, | ||
@@ -127,5 +142,36 @@ path: '/PhoneLines.js', | ||
logger.info('- Express server connected and serving files π'); | ||
results.expressTestPassed = true; | ||
results.expressStaticTestPassed = true; | ||
} | ||
); | ||
http.get( | ||
{ | ||
host, | ||
port, | ||
path: '/extensions', | ||
}, | ||
response => { | ||
let body = ''; | ||
response.on('data', chunk => { | ||
body += chunk.toString(); | ||
}); | ||
response.on('end', () => { | ||
assert(response.statusCode === 200); | ||
body = JSON.parse(body); | ||
assert(body.extensions); | ||
assert.equal(body.extensions.length, 1); | ||
const extension = body.extensions.pop(); | ||
assert(extension.manifest); | ||
assert.equal(extension.appName, 'Example App React UI'); | ||
assert.equal(extension.title, 'Phone Lines'); | ||
assert.equal( | ||
extension.callback, | ||
`http://${host}:${port}/PhoneLines.js` | ||
); | ||
assert.equal(body.websocket, `ws://localhost:${port}`); | ||
assert.strictEquals(body.version, EXTENSIONS_MESSAGE_VERSION); | ||
logger.info('- Express serving extension data π'); | ||
results.extensionsEndpointPassed = true; | ||
}); | ||
} | ||
); | ||
} | ||
@@ -136,15 +182,12 @@ | ||
function testWebSocketServer(results, logger) { | ||
const fileContents = fs | ||
.readFileSync(path.join('dist', 'PhoneLines.js')) | ||
.toString('base64'); | ||
const ws = new WebSocket(`ws://localhost:${port}`); | ||
ws.on('message', messageBuffer => { | ||
const message = JSON.parse(messageBuffer.toString()); | ||
assert(message.event === 'start' || message.event === 'update'); | ||
assert(message.appName === 'example-app'); | ||
assert.strictEqual(message.extension, 'Phone Lines'); | ||
assert(message.callback, `data:text/javascript;base64,${fileContents}`); | ||
assert(message.event === 'start'); | ||
assert.strictEqual(message.appName, 'Example App React UI'); | ||
assert.strictEqual(message.title, 'Phone Lines'); | ||
assert.strictEqual(message.version, WEBSOCKET_MESSAGE_VERSION); | ||
logger.info('- WebSocket server connected and sending messages π'); | ||
results.webSocketTestPassed = true; | ||
ws.close(); // The test passed, close the connection | ||
}); | ||
@@ -151,0 +194,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30448
16
894
30
11
+ Addedcors@^2.8.5
+ Addedcors@2.8.5(transitive)
+ Addedobject-assign@4.1.1(transitive)
Updatedws@^8.13.0