@hubspot/ui-extensions-dev-server 0.0.1-prealpha.3 → 0.0.1-prealpha.5
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/build.js +16 -10
- package/cli.js +3 -1
- package/config.js +27 -10
- package/package.json +15 -3
- package/plugins/manifestPlugin.js +108 -0
- package/remoteBuild.js +19 -10
- package/run.js +7 -1
- package/tests/testBuild.js +33 -24
- package/utils.js +4 -3
package/build.js
CHANGED
|
@@ -6,21 +6,23 @@ async function buildAllExtensions(config, outputDir) {
|
|
|
6
6
|
for (let i = 0; i < extensionKeys.length; ++i) {
|
|
7
7
|
const { data } = config[extensionKeys[i]];
|
|
8
8
|
|
|
9
|
-
await buildSingleExtension(
|
|
10
|
-
data?.module.file,
|
|
11
|
-
data?.output,
|
|
9
|
+
await buildSingleExtension({
|
|
10
|
+
file: data?.module.file,
|
|
11
|
+
outputFileName: data?.output,
|
|
12
12
|
outputDir,
|
|
13
|
-
i === 0
|
|
14
|
-
);
|
|
13
|
+
emptyOutDir: i === 0,
|
|
14
|
+
});
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
async function buildSingleExtension(
|
|
18
|
+
async function buildSingleExtension({
|
|
19
19
|
file,
|
|
20
20
|
outputFileName,
|
|
21
21
|
outputDir,
|
|
22
|
-
emptyOutDir = true
|
|
23
|
-
|
|
22
|
+
emptyOutDir = true,
|
|
23
|
+
plugins = { rollup: [], vite: [] },
|
|
24
|
+
minify = false,
|
|
25
|
+
}) {
|
|
24
26
|
await build({
|
|
25
27
|
build: {
|
|
26
28
|
lib: {
|
|
@@ -29,10 +31,14 @@ async function buildSingleExtension(
|
|
|
29
31
|
formats: ['iife'],
|
|
30
32
|
fileName: () => outputFileName,
|
|
31
33
|
},
|
|
32
|
-
rollupOptions:
|
|
34
|
+
rollupOptions: {
|
|
35
|
+
...ROLLUP_OPTIONS,
|
|
36
|
+
plugins: [...(ROLLUP_OPTIONS.plugins || []), ...plugins?.rollup],
|
|
37
|
+
},
|
|
33
38
|
outDir: outputDir,
|
|
34
39
|
emptyOutDir,
|
|
35
|
-
minify
|
|
40
|
+
minify,
|
|
41
|
+
plugins: plugins?.vite,
|
|
36
42
|
},
|
|
37
43
|
});
|
|
38
44
|
}
|
package/cli.js
CHANGED
|
@@ -43,12 +43,14 @@ function showHelp(OUTPUT_DIR) {
|
|
|
43
43
|
optionList: [
|
|
44
44
|
{
|
|
45
45
|
name: 'extension',
|
|
46
|
+
alias: 'e',
|
|
46
47
|
typeLabel: '{underline file}',
|
|
47
48
|
description:
|
|
48
|
-
'The extension entrypoint file build or start local development for',
|
|
49
|
+
'The extension entrypoint file to build or start local development for',
|
|
49
50
|
},
|
|
50
51
|
{
|
|
51
52
|
name: 'help',
|
|
53
|
+
alias: 'h',
|
|
52
54
|
description: 'Print this usage guide.',
|
|
53
55
|
},
|
|
54
56
|
],
|
package/config.js
CHANGED
|
@@ -71,30 +71,47 @@ function loadConfig() {
|
|
|
71
71
|
|
|
72
72
|
const mainAppConfig = _loadRequiredConfigFile(configPath);
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
const crmCardsSubConfigFiles = mainAppConfig?.extensions?.crm?.cards;
|
|
75
|
+
if (!crmCardsSubConfigFiles) {
|
|
75
76
|
logger.error(
|
|
76
|
-
`"extensions.crm.cards"
|
|
77
|
+
`The "extensions.crm.cards" array in ${configPath} is missing, it is a required configuration property`
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
} else if (crmCardsSubConfigFiles.length === 0) {
|
|
81
|
+
logger.error(
|
|
82
|
+
`The "extensions.crm.cards" array in ${configPath} is empty, it is a required configuration property.`
|
|
77
83
|
);
|
|
78
84
|
process.exit(1);
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
const outputConfig = {};
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
crmCardsSubConfigFiles.forEach(card => {
|
|
84
90
|
const extensionsRemoved = card.file.replace('extensions/', '');
|
|
85
|
-
const
|
|
91
|
+
const cardConfigPath = path.join(process.cwd(), extensionsRemoved);
|
|
92
|
+
// Get the path to the config file relative to the extensions directory
|
|
93
|
+
const configPathRelativeToExtensions = path.parse(extensionsRemoved)?.dir;
|
|
94
|
+
|
|
86
95
|
try {
|
|
87
|
-
const cardConfig = require(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
const cardConfig = require(cardConfigPath);
|
|
97
|
+
|
|
98
|
+
// Join the two relative paths
|
|
99
|
+
const entryPointPath = path.join(
|
|
100
|
+
configPathRelativeToExtensions,
|
|
91
101
|
cardConfig.data?.module?.file
|
|
92
102
|
);
|
|
93
|
-
|
|
103
|
+
|
|
104
|
+
cardConfig.data.module.file = entryPointPath;
|
|
105
|
+
|
|
106
|
+
outputConfig[entryPointPath] = cardConfig;
|
|
107
|
+
outputConfig[entryPointPath].data.output = getUrlSafeFileName(
|
|
108
|
+
entryPointPath
|
|
109
|
+
);
|
|
110
|
+
outputConfig[entryPointPath].data.appName = projectConfig.name;
|
|
94
111
|
} catch (e) {
|
|
95
112
|
let errorMessage = e?.message;
|
|
96
113
|
if (e?.code === 'MODULE_NOT_FOUND') {
|
|
97
|
-
errorMessage = `Unable to load "${
|
|
114
|
+
errorMessage = `Unable to load "${cardConfigPath}" file. \nPlease make sure you are running the command from the src/app/extensions directory and that your card JSON config exists within it.`;
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
logger.error(errorMessage);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions-dev-server",
|
|
3
|
-
"version": "0.0.1-prealpha.
|
|
3
|
+
"version": "0.0.1-prealpha.5",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"utils.js",
|
|
23
23
|
"tests/runTests.js",
|
|
24
24
|
"tests/testBuild.js",
|
|
25
|
-
"tests/testDevServer.js"
|
|
25
|
+
"tests/testDevServer.js",
|
|
26
|
+
"plugins/manifestPlugin.js"
|
|
26
27
|
],
|
|
27
28
|
"license": "MIT",
|
|
28
29
|
"dependencies": {
|
|
@@ -45,5 +46,16 @@
|
|
|
45
46
|
"node": true
|
|
46
47
|
}
|
|
47
48
|
},
|
|
48
|
-
"
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=16"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"typescript": "^5.0.4"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"typescript": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"gitHead": "a4652eb58cbde813dbebc8b7127c36102b9b6ed6"
|
|
49
61
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const { readFileSync } = require('fs');
|
|
2
|
+
const { normalize } = require('path');
|
|
3
|
+
const logger = require('../logger');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_MANIFEST_NAME = 'manifest.json';
|
|
6
|
+
const PACKAGE_LOCK_FILE = 'package-lock.json';
|
|
7
|
+
const PACKAGE_FILE = 'package.json';
|
|
8
|
+
const EXTENSIONS_PATH = 'src/app/extensions/';
|
|
9
|
+
|
|
10
|
+
function plugin(options = {}) {
|
|
11
|
+
return {
|
|
12
|
+
name: 'ui-extensions-manifest-generation-plugin',
|
|
13
|
+
enforce: 'post', // run after default rollup plugins
|
|
14
|
+
generateBundle(_rollupOptions, bundle) {
|
|
15
|
+
const { output = DEFAULT_MANIFEST_NAME, minify = false } = options;
|
|
16
|
+
try {
|
|
17
|
+
const manifest = _generateManifestContents(bundle);
|
|
18
|
+
this.emitFile({
|
|
19
|
+
type: 'asset',
|
|
20
|
+
source: minify
|
|
21
|
+
? JSON.stringify(manifest)
|
|
22
|
+
: JSON.stringify(manifest, null, 2),
|
|
23
|
+
fileName: normalize(output),
|
|
24
|
+
});
|
|
25
|
+
} catch (e) {
|
|
26
|
+
logger.warn(`\nUnable to write manifest file in ${output}, ${e}`);
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _generateManifestContents(bundle) {
|
|
33
|
+
const baseManifest = {
|
|
34
|
+
package: _loadPackageFile(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// The keys to bundle are the filename without any path information
|
|
38
|
+
const bundles = Object.keys(bundle);
|
|
39
|
+
|
|
40
|
+
if (bundles.length === 1) {
|
|
41
|
+
return {
|
|
42
|
+
..._generateManifestEntry(bundle[bundles[0]]),
|
|
43
|
+
...baseManifest,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const manifest = bundles.reduce((acc, current) => {
|
|
48
|
+
return {
|
|
49
|
+
...acc,
|
|
50
|
+
[current]: _generateManifestEntry(bundle[current], false),
|
|
51
|
+
};
|
|
52
|
+
}, {});
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
...manifest,
|
|
56
|
+
...baseManifest,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function _generateManifestEntry(subBundle) {
|
|
61
|
+
const { facadeModuleId, moduleIds, modules } = subBundle;
|
|
62
|
+
return {
|
|
63
|
+
entry: _stripPathPriorToExtDir(facadeModuleId),
|
|
64
|
+
modules: _buildModulesInfo(moduleIds, modules),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function _loadJsonFileSafely(filename) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(readFileSync(filename).toString());
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function _loadPackageFile() {
|
|
77
|
+
// Look for package-lock.json then fallback to package.json
|
|
78
|
+
return (
|
|
79
|
+
_loadJsonFileSafely(PACKAGE_LOCK_FILE) || _loadJsonFileSafely(PACKAGE_FILE)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function _stripPathPriorToExtDir(filepath) {
|
|
84
|
+
return filepath.split(EXTENSIONS_PATH).pop();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _buildModulesInfo(moduleIds, modules) {
|
|
88
|
+
return moduleIds.reduce(
|
|
89
|
+
(acc, mod) => {
|
|
90
|
+
const { renderedExports } = modules[mod];
|
|
91
|
+
|
|
92
|
+
const moduleData = {
|
|
93
|
+
module: _stripPathPriorToExtDir(mod),
|
|
94
|
+
renderedExports,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (moduleData.module.includes('node_modules')) {
|
|
98
|
+
acc.external.push(moduleData);
|
|
99
|
+
} else {
|
|
100
|
+
acc.internal.push(moduleData);
|
|
101
|
+
}
|
|
102
|
+
return acc;
|
|
103
|
+
},
|
|
104
|
+
{ internal: [], external: [] }
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = plugin;
|
package/remoteBuild.js
CHANGED
|
@@ -2,20 +2,29 @@
|
|
|
2
2
|
const { OUTPUT_DIR } = require('./constants');
|
|
3
3
|
const { getUrlSafeFileName } = require('./utils');
|
|
4
4
|
const { buildSingleExtension } = require('./build');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
const entryPointRegex = new RegExp(/.*\.(ts|js)[x]?$/);
|
|
8
|
+
const entryPoint = process.argv[process.argv.length - 1];
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const fileInfo = path.parse(entryPoint);
|
|
13
|
+
|
|
14
|
+
if (!allowedExtensions.includes(fileInfo.ext)) {
|
|
12
15
|
throw new Error(
|
|
13
|
-
|
|
16
|
+
`The last argument should be the filename you wish to build. Supported file extensions are [${allowedExtensions.join(
|
|
17
|
+
', '
|
|
18
|
+
)}]`
|
|
14
19
|
);
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
buildSingleExtension({
|
|
23
|
+
file: entryPoint,
|
|
24
|
+
outputFileName: getUrlSafeFileName(entryPoint),
|
|
25
|
+
outputDir: OUTPUT_DIR,
|
|
26
|
+
plugins: {
|
|
27
|
+
rollup: [manifestPlugin({ minify: true })],
|
|
28
|
+
},
|
|
29
|
+
minify: true,
|
|
30
|
+
});
|
package/run.js
CHANGED
|
@@ -6,6 +6,7 @@ const { buildAllExtensions, buildSingleExtension } = require('./build');
|
|
|
6
6
|
const { VITE_DEFAULT_PORT, OUTPUT_DIR } = require('./constants');
|
|
7
7
|
const { parseArgs, showHelp } = require('./cli');
|
|
8
8
|
const { getUrlSafeFileName } = require('./utils');
|
|
9
|
+
const manifestPlugin = require('./plugins/manifestPlugin');
|
|
9
10
|
|
|
10
11
|
const { DEV_MODE, BUILD_MODE, port, extension, help } = parseArgs();
|
|
11
12
|
const PORT = port || VITE_DEFAULT_PORT;
|
|
@@ -18,7 +19,12 @@ if (DEV_MODE) {
|
|
|
18
19
|
startDevMode(loadConfig(), OUTPUT_DIR, PORT, extension);
|
|
19
20
|
} else if (BUILD_MODE) {
|
|
20
21
|
if (extension) {
|
|
21
|
-
buildSingleExtension(
|
|
22
|
+
buildSingleExtension({
|
|
23
|
+
file: extension,
|
|
24
|
+
outputFileName: getUrlSafeFileName(extension),
|
|
25
|
+
outputDir: OUTPUT_DIR,
|
|
26
|
+
plugins: { rollup: [manifestPlugin()] },
|
|
27
|
+
});
|
|
22
28
|
} else {
|
|
23
29
|
buildAllExtensions(loadConfig(), OUTPUT_DIR);
|
|
24
30
|
}
|
package/tests/testBuild.js
CHANGED
|
@@ -14,19 +14,37 @@ function _testHelper(command, outputDirFiles) {
|
|
|
14
14
|
// Spot check the file contents to make sure they seem ok
|
|
15
15
|
filesInOutputDir.forEach(file => {
|
|
16
16
|
const fileContents = fs.readFileSync(path.join(distDir, file)).toString();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
if (file === 'manifest.json') {
|
|
18
|
+
const manifest = JSON.parse(fileContents);
|
|
19
|
+
assert(manifest.entry);
|
|
20
|
+
assert(manifest.modules);
|
|
21
|
+
assert(manifest.modules.internal);
|
|
22
|
+
manifest.modules.internal.forEach(mod => {
|
|
23
|
+
assert(mod.module);
|
|
24
|
+
assert(mod.renderedExports);
|
|
25
|
+
});
|
|
26
|
+
assert(manifest.modules.external);
|
|
27
|
+
manifest.modules.external.forEach(mod => {
|
|
28
|
+
assert(mod.module);
|
|
29
|
+
assert(mod.renderedExports);
|
|
30
|
+
});
|
|
31
|
+
assert(manifest.package);
|
|
32
|
+
assert(manifest.package.packages);
|
|
33
|
+
} else {
|
|
34
|
+
const stringsToSpotCheck = [
|
|
35
|
+
'.createRemoteReactComponent',
|
|
36
|
+
'.createElement',
|
|
37
|
+
'hubspot.extend',
|
|
38
|
+
'React',
|
|
39
|
+
'RemoteUI',
|
|
40
|
+
];
|
|
41
|
+
stringsToSpotCheck.forEach(stringToCheck => {
|
|
42
|
+
assert(
|
|
43
|
+
fileContents.includes(stringToCheck),
|
|
44
|
+
`File ${file} contents should contain: "${stringToCheck}"`
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
30
48
|
});
|
|
31
49
|
}
|
|
32
50
|
|
|
@@ -43,7 +61,7 @@ function testBuildWithExtensionFlag(logger) {
|
|
|
43
61
|
logger.warn('- Test build with flags started 🤞');
|
|
44
62
|
_testHelper(
|
|
45
63
|
'hs-ui-extensions-dev-server build --extension ProgressBarApp.tsx',
|
|
46
|
-
['ProgressBarApp.js']
|
|
64
|
+
['ProgressBarApp.js', 'manifest.json']
|
|
47
65
|
);
|
|
48
66
|
logger.info('- Test build with flags passed 🚀');
|
|
49
67
|
}
|
|
@@ -52,19 +70,11 @@ function testDefInfraBuildFileName(logger) {
|
|
|
52
70
|
logger.warn('- Test build with entrypoint as arg 🤞');
|
|
53
71
|
_testHelper('hs-ui-extensions-remote-build ProgressBarApp.tsx', [
|
|
54
72
|
'ProgressBarApp.js',
|
|
73
|
+
'manifest.json',
|
|
55
74
|
]);
|
|
56
75
|
logger.info('- Test build with entrypoint as arg 🚀');
|
|
57
76
|
}
|
|
58
77
|
|
|
59
|
-
function testDevInfraBuildFileNameWithPathPrefix(logger) {
|
|
60
|
-
logger.warn('- Test build with config file as arg 🤞');
|
|
61
|
-
_testHelper(
|
|
62
|
-
'hs-ui-extensions-remote-build some/super/long/file/path/to/the/extensions/PhoneLines.tsx',
|
|
63
|
-
['PhoneLines.js']
|
|
64
|
-
);
|
|
65
|
-
logger.info('- Test build with config file as arg 🚀');
|
|
66
|
-
}
|
|
67
|
-
|
|
68
78
|
function testBuild(logger) {
|
|
69
79
|
logger.warn('\nBuild Tests started - External Devs 🤞');
|
|
70
80
|
testDefaultBuildPath(logger);
|
|
@@ -73,7 +83,6 @@ function testBuild(logger) {
|
|
|
73
83
|
|
|
74
84
|
logger.warn('\nBuild Tests started - Dev Infra 🤞');
|
|
75
85
|
testDefInfraBuildFileName(logger);
|
|
76
|
-
testDevInfraBuildFileNameWithPathPrefix(logger);
|
|
77
86
|
logger.info('Build Tests passed - Dev Infra 🚀');
|
|
78
87
|
}
|
|
79
88
|
|
package/utils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
1
3
|
function getUrlSafeFileName(filePath) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
return encodeURIComponent(fileNameWithJsExtension);
|
|
4
|
+
const { name } = path.parse(filePath);
|
|
5
|
+
return encodeURIComponent(`${name}.js`);
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
module.exports = {
|