@hubspot/ui-extensions-dev-server 1.1.0 → 1.1.1

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.
Files changed (95) hide show
  1. package/dist/index.d.ts +4 -0
  2. package/dist/index.js +4 -0
  3. package/dist/lib/DevModeInterface.d.ts +9 -0
  4. package/dist/lib/DevModeInterface.js +36 -0
  5. package/dist/lib/DevModeParentInterface.d.ts +19 -0
  6. package/dist/lib/DevModeParentInterface.js +181 -0
  7. package/dist/lib/DevModeUnifiedInterface.d.ts +9 -0
  8. package/dist/lib/DevModeUnifiedInterface.js +118 -0
  9. package/dist/lib/DevServerState.d.ts +44 -0
  10. package/dist/lib/DevServerState.js +95 -0
  11. package/dist/lib/ExtensionsWebSocket.d.ts +25 -0
  12. package/dist/lib/ExtensionsWebSocket.js +110 -0
  13. package/dist/lib/__mocks__/config.d.ts +2 -0
  14. package/dist/lib/__mocks__/config.js +5 -0
  15. package/dist/lib/__mocks__/isExtensionFile.d.ts +5 -0
  16. package/dist/lib/__mocks__/isExtensionFile.js +11 -0
  17. package/dist/lib/__tests__/DevModeInterface.spec.d.ts +1 -0
  18. package/dist/lib/__tests__/DevModeInterface.spec.js +155 -0
  19. package/dist/lib/__tests__/DevModeParentInterface.spec.d.ts +1 -0
  20. package/dist/lib/__tests__/DevModeParentInterface.spec.js +179 -0
  21. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.d.ts +1 -0
  22. package/dist/lib/__tests__/DevModeUnifiedInterface.spec.js +236 -0
  23. package/dist/lib/__tests__/ExtensionsWebSocket.spec.d.ts +1 -0
  24. package/dist/lib/__tests__/ExtensionsWebSocket.spec.js +304 -0
  25. package/dist/lib/__tests__/ast.spec.d.ts +1 -0
  26. package/dist/lib/__tests__/ast.spec.js +737 -0
  27. package/dist/lib/__tests__/build.spec.d.ts +1 -0
  28. package/dist/lib/__tests__/build.spec.js +159 -0
  29. package/dist/lib/__tests__/config.spec.d.ts +1 -0
  30. package/dist/lib/__tests__/config.spec.js +291 -0
  31. package/dist/lib/__tests__/dev.spec.d.ts +1 -0
  32. package/dist/lib/__tests__/dev.spec.js +80 -0
  33. package/dist/lib/__tests__/extensionsService.spec.d.ts +1 -0
  34. package/dist/lib/__tests__/extensionsService.spec.js +150 -0
  35. package/dist/lib/__tests__/factories.d.ts +48 -0
  36. package/dist/lib/__tests__/factories.js +32 -0
  37. package/dist/lib/__tests__/fixtures/extensionConfig.d.ts +182 -0
  38. package/dist/lib/__tests__/fixtures/extensionConfig.js +304 -0
  39. package/dist/lib/__tests__/fixtures/urls.d.ts +4 -0
  40. package/dist/lib/__tests__/fixtures/urls.js +4 -0
  41. package/dist/lib/__tests__/parsing-utils.spec.d.ts +1 -0
  42. package/dist/lib/__tests__/parsing-utils.spec.js +467 -0
  43. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.d.ts +1 -0
  44. package/dist/lib/__tests__/plugins/codeBlockingPlugin.spec.js +112 -0
  45. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.d.ts +1 -0
  46. package/dist/lib/__tests__/plugins/codeCheckingPlugin.spec.js +111 -0
  47. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.d.ts +1 -0
  48. package/dist/lib/__tests__/plugins/devBuildPlugin.spec.js +345 -0
  49. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.d.ts +1 -0
  50. package/dist/lib/__tests__/plugins/friendlyLoggingPlugin.spec.js +65 -0
  51. package/dist/lib/__tests__/plugins/manifestPlugin.spec.d.ts +1 -0
  52. package/dist/lib/__tests__/plugins/manifestPlugin.spec.js +455 -0
  53. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.d.ts +1 -0
  54. package/dist/lib/__tests__/plugins/relevantModulesPlugin.spec.js +81 -0
  55. package/dist/lib/__tests__/server.spec.d.ts +1 -0
  56. package/dist/lib/__tests__/server.spec.js +152 -0
  57. package/dist/lib/__tests__/test-utils/ast.d.ts +1 -0
  58. package/dist/lib/__tests__/test-utils/ast.js +4 -0
  59. package/dist/lib/__tests__/utils.spec.d.ts +1 -0
  60. package/dist/lib/__tests__/utils.spec.js +176 -0
  61. package/dist/lib/ast.d.ts +16 -0
  62. package/dist/lib/ast.js +281 -0
  63. package/dist/lib/bin/cli.d.ts +2 -0
  64. package/dist/lib/bin/cli.js +143 -0
  65. package/dist/lib/build.d.ts +24 -0
  66. package/dist/lib/build.js +73 -0
  67. package/dist/lib/config.d.ts +7 -0
  68. package/dist/lib/config.js +124 -0
  69. package/dist/lib/constants.d.ts +32 -0
  70. package/dist/lib/constants.js +43 -0
  71. package/dist/lib/dev.d.ts +2 -0
  72. package/dist/lib/dev.js +58 -0
  73. package/dist/lib/extensionsService.d.ts +10 -0
  74. package/dist/lib/extensionsService.js +45 -0
  75. package/dist/lib/parsing-utils.d.ts +31 -0
  76. package/dist/lib/parsing-utils.js +289 -0
  77. package/dist/lib/plugins/codeBlockingPlugin.d.ts +8 -0
  78. package/dist/lib/plugins/codeBlockingPlugin.js +45 -0
  79. package/dist/lib/plugins/codeCheckingPlugin.d.ts +8 -0
  80. package/dist/lib/plugins/codeCheckingPlugin.js +89 -0
  81. package/dist/lib/plugins/devBuildPlugin.d.ts +8 -0
  82. package/dist/lib/plugins/devBuildPlugin.js +201 -0
  83. package/dist/lib/plugins/friendlyLoggingPlugin.d.ts +14 -0
  84. package/dist/lib/plugins/friendlyLoggingPlugin.js +36 -0
  85. package/dist/lib/plugins/manifestPlugin.d.ts +12 -0
  86. package/dist/lib/plugins/manifestPlugin.js +158 -0
  87. package/dist/lib/plugins/relevantModulesPlugin.d.ts +13 -0
  88. package/dist/lib/plugins/relevantModulesPlugin.js +25 -0
  89. package/dist/lib/server.d.ts +13 -0
  90. package/dist/lib/server.js +99 -0
  91. package/dist/lib/types.d.ts +290 -0
  92. package/dist/lib/types.js +12 -0
  93. package/dist/lib/utils.d.ts +25 -0
  94. package/dist/lib/utils.js +113 -0
  95. package/package.json +1 -1
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { promisify } from 'node:util';
5
+ import { exec } from 'node:child_process';
6
+ import { Command } from 'commander';
7
+ import ora from 'ora';
8
+ import chalk from 'chalk';
9
+ import { remoteBuild, buildSingleExtension } from "../build.js";
10
+ const program = new Command()
11
+ .name('ui-extension-tools')
12
+ .alias('uie')
13
+ .description('Tools for managing and building HubSpot UI Extensions.');
14
+ const buildSteps = {
15
+ lockfile: {
16
+ start: 'Processing lockfile...',
17
+ fn: generateLockfile,
18
+ success: 'Lockfile ready',
19
+ error: 'Error generating lockfile',
20
+ },
21
+ install: {
22
+ start: 'Installing dependencies...',
23
+ fn: installDeps,
24
+ success: 'Dependencies installed',
25
+ error: 'Error installing dependencies',
26
+ },
27
+ build: {
28
+ start: 'Building remote extension...',
29
+ fn: build,
30
+ success: 'Extension build complete',
31
+ error: 'Error building extension',
32
+ },
33
+ devBuild: {
34
+ start: 'Building single extension in development mode...',
35
+ fn: devBuild,
36
+ success: 'Extension build complete',
37
+ error: 'Error building extension',
38
+ },
39
+ };
40
+ program
41
+ .option('-v, --profile-variables <path>', 'path to a json file with profile variables')
42
+ .command('build <module>', { isDefault: true })
43
+ .description('Build extension source code.')
44
+ .action(async (module) => {
45
+ const options = getOptions(module);
46
+ await runCommand('lockfile', options);
47
+ await runCommand('install', options);
48
+ await runCommand('build', options);
49
+ });
50
+ program
51
+ .command('build-dev <module>')
52
+ .description('Build extension source code in development mode (no minification).')
53
+ .action(async (module) => {
54
+ const options = getOptions(module);
55
+ await runCommand('devBuild', options);
56
+ });
57
+ program.parse(process.argv);
58
+ async function runCommand(command, options) {
59
+ const { start, fn, success, error } = buildSteps[command];
60
+ const spinner = ora();
61
+ try {
62
+ spinner.start(start);
63
+ await fn(options);
64
+ spinner.succeed(chalk.green(success));
65
+ }
66
+ catch (err) {
67
+ spinner.fail(chalk.red(error));
68
+ process.exit(1);
69
+ }
70
+ finally {
71
+ spinner.stop();
72
+ }
73
+ }
74
+ // Mirrors: https://git.hubteam.com/HubSpot/Artifactor/blob/f5cbea91d7a7dfb6278e878ae583e69022384fb5/ArtifactorFunctions/functions/node_18x/uie-remote-build/index.js#L59-L63
75
+ function build({ extensionRoot, extensionDist, entrypoint, profileVariables, }) {
76
+ return remoteBuild({
77
+ root: extensionRoot,
78
+ entryPoint: entrypoint,
79
+ outputDir: extensionDist,
80
+ appConfig: {
81
+ variables: profileVariables,
82
+ },
83
+ });
84
+ }
85
+ async function devBuild({ extensionRoot, extensionDist, entrypoint, profileVariables, }) {
86
+ return buildSingleExtension({
87
+ file: entrypoint,
88
+ outputDir: extensionDist,
89
+ root: extensionRoot,
90
+ minify: false,
91
+ appConfig: {
92
+ variables: profileVariables,
93
+ },
94
+ codeCheckingPluginEnabled: true,
95
+ });
96
+ }
97
+ // Mirrors: https://git.hubteam.com/HubSpot/Artifactor/blob/f5cbea91d7a7dfb6278e878ae583e69022384fb5/ArtifactorFunctions/functions/node_18x/uie-remote-build/index.js#L53-L57
98
+ async function installDeps(options) {
99
+ await execAsync('npm ci --ignore-scripts --no-audit --no-optional --no-fund', options);
100
+ }
101
+ // Mirrors: https://git.hubteam.com/HubSpot/Artifactor/blob/f5cbea91d7a7dfb6278e878ae583e69022384fb5/ArtifactorFunctions/functions/node_18x/uie-remote-build/index.js#L131-L136
102
+ async function generateLockfile(options) {
103
+ const { extensionRoot, spinner } = options;
104
+ if (fs.existsSync(path.join(extensionRoot, 'package-lock.json'))) {
105
+ spinner.info('Existing lockfile found, skipping generation');
106
+ }
107
+ else {
108
+ spinner.info('No lockfile found, generating...');
109
+ await execAsync('npm install --package-lock-only', options);
110
+ }
111
+ }
112
+ function getOptions(modulePath) {
113
+ const entrypoint = path.isAbsolute(modulePath)
114
+ ? modulePath
115
+ : path.resolve(process.cwd(), modulePath);
116
+ const extensionRoot = path.dirname(entrypoint);
117
+ const extensionDist = path.join(extensionRoot, 'dist');
118
+ const spinner = ora();
119
+ // The Artifactor changes related to this are not yet available.
120
+ // When it is, this will mirror the process where if applicable, a profile-variables.json file is available in the build bundle.
121
+ const programOptions = program.opts();
122
+ let profileVariables = {};
123
+ if (programOptions.profileVariables) {
124
+ const profileVariablePath = programOptions.profileVariables;
125
+ const profileVariableFullPath = path.isAbsolute(profileVariablePath)
126
+ ? profileVariablePath
127
+ : path.resolve(process.cwd(), profileVariablePath);
128
+ if (fs.existsSync(profileVariableFullPath) &&
129
+ path.extname(profileVariableFullPath) === '.json') {
130
+ profileVariables = JSON.parse(fs.readFileSync(profileVariableFullPath, 'utf-8'));
131
+ }
132
+ }
133
+ return {
134
+ extensionRoot,
135
+ extensionDist,
136
+ entrypoint,
137
+ spinner,
138
+ profileVariables,
139
+ };
140
+ }
141
+ function execAsync(command, options) {
142
+ return promisify(exec)(command, { cwd: options.extensionRoot });
143
+ }
@@ -0,0 +1,24 @@
1
+ import { InlineConfig } from 'vite';
2
+ import { ManifestConfig } from './types.ts';
3
+ interface BuildSingleExtensionArgs {
4
+ file: string;
5
+ outputDir?: string;
6
+ emptyOutDir?: boolean;
7
+ minify?: boolean;
8
+ root?: string;
9
+ logLevel?: InlineConfig['logLevel'];
10
+ appConfig?: ManifestConfig;
11
+ codeCheckingPluginEnabled?: boolean;
12
+ }
13
+ interface RemoteBuildArgs {
14
+ root: string;
15
+ entryPoint: string;
16
+ outputDir?: string;
17
+ logLevel?: BuildSingleExtensionArgs['logLevel'];
18
+ appConfig?: ManifestConfig;
19
+ }
20
+ export declare const extensionErrorBaseMessage: string;
21
+ export declare function buildSingleExtension({ file, outputDir, emptyOutDir, minify, root, // This is the vite default, so using that as our default
22
+ logLevel, appConfig, codeCheckingPluginEnabled, }: BuildSingleExtensionArgs): Promise<void>;
23
+ export declare function remoteBuild(args: RemoteBuildArgs): Promise<void>;
24
+ export {};
@@ -0,0 +1,73 @@
1
+ import { build } from 'vite';
2
+ import { ROLLUP_OPTIONS, OUTPUT_DIR } from "./constants.js";
3
+ import manifestPlugin from "./plugins/manifestPlugin.js";
4
+ import path from 'path';
5
+ import { getUrlSafeFileName } from "./utils.js";
6
+ import codeBlockingPlugin from "./plugins/codeBlockingPlugin.js";
7
+ import friendlyLoggingPlugin from "./plugins/friendlyLoggingPlugin.js";
8
+ import codeCheckingPlugin from "./plugins/codeCheckingPlugin.js";
9
+ const allowedExtensions = ['.js', '.ts', '.tsx', '.jsx'];
10
+ export const extensionErrorBaseMessage = `Supported file extensions are [${allowedExtensions.join(', ')}], received:`;
11
+ export async function buildSingleExtension({ file, outputDir = OUTPUT_DIR, emptyOutDir = true, minify = false, root = process.cwd(), // This is the vite default, so using that as our default
12
+ logLevel = 'info', appConfig, codeCheckingPluginEnabled = false, }) {
13
+ const output = getUrlSafeFileName(file);
14
+ await build({
15
+ logLevel,
16
+ root,
17
+ define: {
18
+ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
19
+ },
20
+ build: {
21
+ lib: {
22
+ entry: file,
23
+ name: output,
24
+ formats: ['iife'],
25
+ fileName: () => output,
26
+ },
27
+ rollupOptions: {
28
+ ...ROLLUP_OPTIONS,
29
+ plugins: [
30
+ manifestPlugin({
31
+ output,
32
+ extensionPath: root,
33
+ logger: console,
34
+ manifestConfig: appConfig,
35
+ }),
36
+ friendlyLoggingPlugin({ logger: console }),
37
+ codeBlockingPlugin({ logger: console, extensionPath: root }),
38
+ codeCheckingPluginEnabled &&
39
+ codeCheckingPlugin({
40
+ logger: console,
41
+ }),
42
+ ],
43
+ },
44
+ outDir: outputDir,
45
+ emptyOutDir,
46
+ minify,
47
+ },
48
+ });
49
+ }
50
+ export async function remoteBuild(args) {
51
+ const { root, entryPoint, outputDir = OUTPUT_DIR, logLevel, appConfig, } = args;
52
+ if (!root) {
53
+ console.error('remoteBuild Error: root is required');
54
+ return;
55
+ }
56
+ if (!entryPoint) {
57
+ console.error('remoteBuild Error: entryPoint is required');
58
+ return;
59
+ }
60
+ const fileInfo = path.parse(entryPoint);
61
+ if (!allowedExtensions.includes(fileInfo.ext)) {
62
+ throw new Error(`${extensionErrorBaseMessage} ${fileInfo.ext}`);
63
+ }
64
+ await buildSingleExtension({
65
+ file: entryPoint,
66
+ outputDir,
67
+ minify: true,
68
+ root,
69
+ logLevel,
70
+ appConfig,
71
+ codeCheckingPluginEnabled: false,
72
+ });
73
+ }
@@ -0,0 +1,7 @@
1
+ import { AppConfig, ExtensionConfigMap, LocalAppConfig, Logger } from './types.ts';
2
+ export declare function loadConfigByPath<T = unknown>(configPath: string): T;
3
+ export declare function validateCardConfig(config?: unknown): Error | true;
4
+ export declare function loadExtensionConfig(appConfig: AppConfig, appPath: string): ExtensionConfigMap;
5
+ export declare function validateProxyConfigKey(urlKey: string, logger: Logger, localConfigPath: string): void;
6
+ export declare function validateProxyConfigValue(value: string, key: string, logger: Logger, localConfigPath: string): void;
7
+ export declare function loadLocalConfig(appPath: string, logger: Logger): LocalAppConfig | undefined;
@@ -0,0 +1,124 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { buildSourceId, getUrlSafeFileName } from "./utils.js";
4
+ export function loadConfigByPath(configPath) {
5
+ const source = fs.readFileSync(configPath).toString();
6
+ return JSON.parse(source);
7
+ }
8
+ export function validateCardConfig(config) {
9
+ if (!config || typeof config !== 'object') {
10
+ return new Error('Card config must be an object');
11
+ }
12
+ if (!('type' in config) || config.type !== 'crm-card') {
13
+ return new Error('`type` must be "crm-card"');
14
+ }
15
+ if (!('data' in config) ||
16
+ typeof config.data !== 'object' ||
17
+ config.data === null) {
18
+ return new Error('`data` must be an object');
19
+ }
20
+ const { data } = config;
21
+ if (!('title' in data) || typeof data.title !== 'string') {
22
+ return new Error('`data` must have a `title` string');
23
+ }
24
+ if (!('location' in data) || typeof data.location !== 'string') {
25
+ return new Error('`data` must have a `location` string');
26
+ }
27
+ if (!('module' in data) ||
28
+ typeof data.module !== 'object' ||
29
+ data.module === null) {
30
+ return new Error('`data` must have a `module` object');
31
+ }
32
+ if (!('file' in data.module) || typeof data.module.file !== 'string') {
33
+ return new Error('`data.module` must have a `file` string');
34
+ }
35
+ if (!('objectTypes' in data) || !Array.isArray(data.objectTypes)) {
36
+ return new Error('`data.module` must have an `objectTypes` array');
37
+ }
38
+ if (data.objectTypes.length === 0 ||
39
+ data.objectTypes.some(({ name }) => typeof name !== 'string')) {
40
+ return new Error('all `data.module.objectTypes` objects must have `name` strings');
41
+ }
42
+ return true;
43
+ }
44
+ export function loadExtensionConfig(appConfig, appPath) {
45
+ const crmCardsSubConfigFiles = appConfig?.extensions?.crm?.cards;
46
+ if (!crmCardsSubConfigFiles) {
47
+ throw new Error("Unable to find extensions files, make sure the 'extensions.crm.cards' array is defined in the application configuration file");
48
+ }
49
+ const outputConfig = {};
50
+ crmCardsSubConfigFiles.forEach((card) => {
51
+ const cardConfigPath = path.join(appPath, card.file);
52
+ try {
53
+ const cardConfig = loadConfigByPath(cardConfigPath);
54
+ const validation = validateCardConfig(cardConfig);
55
+ if (validation !== true) {
56
+ console.error(`[DevServer] Extension config ${cardConfigPath} is invalid: ${validation.message}`);
57
+ }
58
+ if (cardConfig && cardConfig.data) {
59
+ const cardConfigDir = path.parse(cardConfigPath).dir;
60
+ const entryPointPath = path.join(cardConfigDir, cardConfig.data?.module?.file);
61
+ cardConfig.data.module.file = entryPointPath;
62
+ const sourceId = buildSourceId(appConfig, cardConfig);
63
+ outputConfig[sourceId || `${entryPointPath}-${cardConfig.data.location}`] = {
64
+ ...cardConfig,
65
+ output: getUrlSafeFileName(entryPointPath),
66
+ path: appPath,
67
+ extensionPath: path.parse(entryPointPath).dir,
68
+ extensionConfigPath: cardConfigPath,
69
+ data: {
70
+ ...cardConfig.data,
71
+ appName: appConfig.name,
72
+ sourceId,
73
+ },
74
+ appConfig,
75
+ };
76
+ }
77
+ }
78
+ catch (e) {
79
+ throw new Error(`Unable to load ${cardConfigPath}`);
80
+ }
81
+ });
82
+ return outputConfig;
83
+ }
84
+ export function validateProxyConfigKey(urlKey, logger, localConfigPath) {
85
+ try {
86
+ const url = new URL(urlKey);
87
+ if (url.pathname !== '/') {
88
+ logger.warn(`The key "${urlKey}" in "${localConfigPath}" is invalid, paths are not supported for keys`);
89
+ }
90
+ }
91
+ catch (e) {
92
+ logger.warn(`The key "${urlKey}" in "${localConfigPath}" is an invalid url`);
93
+ }
94
+ }
95
+ export function validateProxyConfigValue(value, key, logger, localConfigPath) {
96
+ try {
97
+ // eslint-disable-next-line no-new
98
+ new URL(value);
99
+ }
100
+ catch (e) {
101
+ logger.warn(`The value "${value}" for key "${key}" in "${localConfigPath}" is an invalid url`);
102
+ }
103
+ }
104
+ export function loadLocalConfig(appPath, logger) {
105
+ const localConfigFilename = 'local.json';
106
+ const localConfigPath = path.join(appPath, localConfigFilename);
107
+ if (!fs.existsSync(localConfigPath)) {
108
+ return undefined;
109
+ }
110
+ try {
111
+ const localConfig = JSON.parse(fs.readFileSync(localConfigPath).toString());
112
+ const { proxy = {} } = localConfig;
113
+ Object.entries(proxy).forEach((entry) => {
114
+ const [key, value] = entry;
115
+ validateProxyConfigKey(key, logger, localConfigFilename);
116
+ validateProxyConfigValue(value, key, logger, localConfigFilename);
117
+ });
118
+ return localConfig;
119
+ }
120
+ catch (e) {
121
+ logger.error(`Error loading and parsing ${localConfigPath}, ${e}`);
122
+ return undefined;
123
+ }
124
+ }
@@ -0,0 +1,32 @@
1
+ export declare const OUTPUT_DIR = "dist";
2
+ export declare const MANIFEST_FILE = "manifest.json";
3
+ export declare const EXPRESS_SERVER_ID = "ui-extensions-dev-server";
4
+ export declare const EXPRESS_DEFAULT_PORT = 5173;
5
+ export declare const ROLLUP_OPTIONS: {
6
+ external: string[];
7
+ output: {
8
+ globals: {
9
+ react: string;
10
+ '@remote-ui/react': string;
11
+ };
12
+ extend: boolean;
13
+ };
14
+ };
15
+ export declare const EXTENSIONS_MESSAGE_VERSION = 2;
16
+ export declare const WEBSOCKET_MESSAGE_VERSION = 1;
17
+ export declare const PROXY_CAPABILITY = "app-backend-proxy-server";
18
+ export declare const SERVER_CAPABILITIES: string[];
19
+ export declare const PLATFORM_VERSION: {
20
+ readonly V20231: "2023.1";
21
+ readonly V20232: "2023.2";
22
+ readonly V20251: "2025.1";
23
+ readonly V20252: "2025.2";
24
+ readonly UNSTABLE: "unstable";
25
+ };
26
+ export declare const PUBLIC_APP = "public-app";
27
+ export declare const PRIVATE_APP = "private-app";
28
+ export declare const CARD_EXTENSION = "CARD";
29
+ export declare const SETTINGS_EXTENSION = "SETTINGS";
30
+ export declare const PAGE_EXTENSION = "PAGE";
31
+ export declare const SUPPORTED_APP_TYPES: string[];
32
+ export declare const SUPPORTED_EXTENSION_TYPES: string[];
@@ -0,0 +1,43 @@
1
+ export const OUTPUT_DIR = 'dist';
2
+ export const MANIFEST_FILE = 'manifest.json';
3
+ export const EXPRESS_SERVER_ID = 'ui-extensions-dev-server';
4
+ export const EXPRESS_DEFAULT_PORT = 5173;
5
+ export const ROLLUP_OPTIONS = {
6
+ // Deps to exclude from the bundle
7
+ external: ['react', 'react-dom', '@remote-ui/react'],
8
+ output: {
9
+ // Maps libs to the variables to be injected via the window
10
+ globals: {
11
+ react: 'React',
12
+ '@remote-ui/react': 'RemoteUI',
13
+ },
14
+ extend: true,
15
+ },
16
+ };
17
+ export const EXTENSIONS_MESSAGE_VERSION = 2;
18
+ export const WEBSOCKET_MESSAGE_VERSION = 1;
19
+ export const PROXY_CAPABILITY = 'app-backend-proxy-server';
20
+ export const SERVER_CAPABILITIES = [
21
+ // Supports running app functions locally
22
+ 'app-functions-local-dev',
23
+ 'source-id',
24
+ 'account-id',
25
+ ];
26
+ export const PLATFORM_VERSION = {
27
+ V20231: '2023.1',
28
+ V20232: '2023.2',
29
+ V20251: '2025.1',
30
+ V20252: '2025.2',
31
+ UNSTABLE: 'unstable',
32
+ };
33
+ export const PUBLIC_APP = 'public-app';
34
+ export const PRIVATE_APP = 'private-app';
35
+ export const CARD_EXTENSION = 'CARD';
36
+ export const SETTINGS_EXTENSION = 'SETTINGS';
37
+ export const PAGE_EXTENSION = 'PAGE';
38
+ export const SUPPORTED_APP_TYPES = [PUBLIC_APP, PRIVATE_APP];
39
+ export const SUPPORTED_EXTENSION_TYPES = [
40
+ CARD_EXTENSION,
41
+ SETTINGS_EXTENSION,
42
+ PAGE_EXTENSION,
43
+ ];
@@ -0,0 +1,2 @@
1
+ import { DevServerState } from './DevServerState.ts';
2
+ export declare function startDevMode(devServerState: DevServerState): Promise<() => Promise<void>>;
@@ -0,0 +1,58 @@
1
+ import { createServer } from 'vite';
2
+ import path from 'path';
3
+ import startDevServer from "./server.js";
4
+ import devBuildPlugin from "./plugins/devBuildPlugin.js";
5
+ import { ALLOWED_ORIGIN_PATTERNS } from "./ExtensionsWebSocket.js";
6
+ import detect from 'detect-port';
7
+ async function _createViteDevServer(devServerState) {
8
+ return await createServer({
9
+ root: devServerState.appPath,
10
+ logLevel: 'silent',
11
+ appType: 'custom',
12
+ mode: 'development',
13
+ server: {
14
+ middlewareMode: true,
15
+ hmr: {
16
+ server: null, // We use our own WebSocket server (ExtensionsWebSocket) instead of Vite's
17
+ },
18
+ cors: {
19
+ origin: ALLOWED_ORIGIN_PATTERNS,
20
+ credentials: true,
21
+ },
22
+ watch: {
23
+ ignored: [
24
+ path.join(devServerState.outputDir, '/**/*'),
25
+ '**/src/app/app.functions/**/*',
26
+ '**/app.json',
27
+ '**/package.json',
28
+ '**/package-lock.json',
29
+ ],
30
+ },
31
+ },
32
+ plugins: [
33
+ devBuildPlugin({
34
+ devServerState,
35
+ }),
36
+ ],
37
+ clearScreen: false,
38
+ });
39
+ }
40
+ async function throwIfPortTaken(port) {
41
+ // detect takes a port and returns the next available port
42
+ // so a mismatch means the requested port was not available
43
+ if ((await detect(port)) !== port) {
44
+ throw new Error(`Unable to start because port ${port} is already in use`);
45
+ }
46
+ }
47
+ export async function startDevMode(devServerState) {
48
+ if (!devServerState || !devServerState.extensionsMetadata) {
49
+ throw new Error('Unable to determine which extension to run');
50
+ }
51
+ await throwIfPortTaken(devServerState.expressPort);
52
+ const viteDevServer = await _createViteDevServer(devServerState);
53
+ const { shutdown } = await startDevServer({
54
+ devServerState,
55
+ viteDevServer,
56
+ });
57
+ return shutdown;
58
+ }
@@ -0,0 +1,10 @@
1
+ import { DevServerState } from './DevServerState.ts';
2
+ import { Request, Response, Application } from 'express';
3
+ declare class ExtensionsService {
4
+ endpoint: string;
5
+ constructor();
6
+ add(server: Application, devServerState: DevServerState, capabilities: string[]): string[];
7
+ generateExtensionsHandler(devServerState: DevServerState, capabilities?: string[]): (_req: Request, res: Response) => void;
8
+ }
9
+ declare const _default: ExtensionsService;
10
+ export default _default;
@@ -0,0 +1,45 @@
1
+ import path from 'path';
2
+ import { EXTENSIONS_MESSAGE_VERSION } from "./constants.js";
3
+ import { loadManifest } from "./utils.js";
4
+ class ExtensionsService {
5
+ endpoint;
6
+ constructor() {
7
+ this.endpoint = '/extensions';
8
+ }
9
+ add(server, devServerState, capabilities) {
10
+ server.get(this.endpoint, this.generateExtensionsHandler(devServerState, capabilities));
11
+ return [this.endpoint];
12
+ }
13
+ generateExtensionsHandler(devServerState, capabilities = []) {
14
+ return function extensionsHandler(_req, res) {
15
+ try {
16
+ const extensions = devServerState.extensionsMetadata.map((metadata) => {
17
+ const { baseMessage } = metadata;
18
+ const output = path.parse(baseMessage.callback).name;
19
+ return {
20
+ ...baseMessage,
21
+ manifest: loadManifest(devServerState.outputDir, output),
22
+ };
23
+ });
24
+ const response = {
25
+ websocket: `ws://localhost:${devServerState.expressPort}`,
26
+ version: EXTENSIONS_MESSAGE_VERSION,
27
+ capabilities,
28
+ portalId: devServerState.portalId,
29
+ extensions,
30
+ ...(devServerState.functionsConfig.platformVersion && {
31
+ platformVersion: devServerState.functionsConfig.platformVersion,
32
+ }),
33
+ };
34
+ res.status(200).json(response);
35
+ }
36
+ catch (e) {
37
+ devServerState.logger.error(`Error in /extensions endpoint: ${e}`);
38
+ res.status(500).json({
39
+ message: 'Unable to determine which extensions are running',
40
+ });
41
+ }
42
+ };
43
+ }
44
+ }
45
+ export default new ExtensionsService();
@@ -0,0 +1,31 @@
1
+ import { Node } from 'estree';
2
+ import { NodeValue, SourceCodeMetadata } from './types.ts';
3
+ /**
4
+ * Extracts the value from a given AST node based on its type.
5
+ * This function handles various node types such as Literal, Identifier (aka variables), ArrayExpression,
6
+ * ObjectExpression, SpreadElement, TemplateLiteral, UnaryExpression, and MemberExpression (accessing props on objects).
7
+ * It also supports nested structures and handles special cases like undefined, NaN, and Infinity.
8
+ *
9
+ * It does not handle function calls, complex expressions, or anything that requires evaluation of code logic.
10
+ * This is not a full JavaScript interpreter, but rather a utility to extract static values from the AST.
11
+ *
12
+ * Use this function carefully, as it assumes that the AST is well-formed and that the node types are as expected.
13
+ * Many types are not yet supported. Anything this function is used for should have a backup runtime evaluation,
14
+ * or be set up to fail gracefully if parsing fails.
15
+ *
16
+ * @param node - The AST node from which to extract the value.
17
+ * @param state - The current state of the source code metadata, including variable declarations.
18
+ * @returns An object containing the status of the operation, the extracted node value,
19
+ * or an error message if the extraction fails.
20
+ * The status can be 'SUCCESS' or 'FAIL'.
21
+ * If the status is 'SUCCESS', nodeValue will contain the extracted value.
22
+ * If the status is 'FAIL', error will contain the error message.
23
+ */
24
+ export declare function getValueFromNode(node: Node, state: SourceCodeMetadata): {
25
+ status: 'SUCCESS' | 'FAIL';
26
+ nodeValue?: NodeValue;
27
+ error?: string;
28
+ };
29
+ export declare function isVariableImported(node: Node, variableName: string): boolean;
30
+ export declare function isIdentifierDefined(node: Node, parent: Node | null, name: string): boolean;
31
+ export declare function isFunctionInvoked(node: Node, functionName: string): boolean;