diff --git a/docs/build-guide.md b/docs/build-guide.md index 8a104d709..5a13a0cde 100644 --- a/docs/build-guide.md +++ b/docs/build-guide.md @@ -284,23 +284,25 @@ pnpm clean Located in `packages/cli/.config/`: -| Config | Output | Description | -| -------------------------- | --------------- | ------------------ | -| `esbuild.cli.build.mjs` | `build/cli.js` | Main CLI bundle | -| `esbuild.index.config.mjs` | `dist/index.js` | Entry point loader | +| Config | Output | Description | +| -------------------- | --------------- | ------------------------------------------------ | +| `esbuild.cli.mjs` | `build/cli.js` | Main CLI bundle — bundles all source into one JS | +| `esbuild.index.mjs` | `dist/index.js` | Entry point loader — thin shim that loads cli.js | +| `esbuild.build.mjs` | (orchestrator) | Runs both cli and index builds in parallel | ### Build Variants -The unified esbuild config (`esbuild.config.mjs`) orchestrates all variants: +The orchestrator (`esbuild.build.mjs`) accepts an optional variant argument: ```bash -# Build all variants -node .config/esbuild.config.mjs all +# Build all variants (default) +node .config/esbuild.build.mjs -# Build specific variant -node .config/esbuild.config.mjs cli -node .config/esbuild.config.mjs index -node .config/esbuild.config.mjs inject +# Build only the CLI bundle +node .config/esbuild.build.mjs cli + +# Build only the entry point loader +node .config/esbuild.build.mjs index ``` --- diff --git a/packages/build-infra/README.md b/packages/build-infra/README.md index edcb1cbe4..e14184def 100644 --- a/packages/build-infra/README.md +++ b/packages/build-infra/README.md @@ -272,7 +272,7 @@ ensureOutputDir('/path/to/output/file.js') ### esbuild Configuration ```javascript -// .config/esbuild.cli.build.mjs +// .config/esbuild.cli.mjs import { IMPORT_META_URL_BANNER } from 'build-infra/lib/esbuild-helpers' import { unicodeTransformPlugin } from 'build-infra/lib/esbuild-plugin-unicode-transform' @@ -413,7 +413,7 @@ Assets are cached per tag to avoid re-downloading across builds. **Consumers:** -- `packages/cli/.config/esbuild.cli.build.mjs` - Main CLI bundle config +- `packages/cli/.config/esbuild.cli.mjs` - Main CLI bundle config - `packages/cli/scripts/download-assets.mjs` - Unified asset downloader - `packages/cli/scripts/sea-build-utils/builder.mjs` - SEA binary builder diff --git a/packages/cli/.config/esbuild.build.mjs b/packages/cli/.config/esbuild.build.mjs new file mode 100644 index 000000000..b662f3ee6 --- /dev/null +++ b/packages/cli/.config/esbuild.build.mjs @@ -0,0 +1,58 @@ +/** + * esbuild build orchestrator for Socket CLI. + * Builds all variants (CLI bundle + entry point) in parallel. + * + * Usage: + * node .config/esbuild.build.mjs # Build all variants + * node .config/esbuild.build.mjs cli # Build CLI bundle + * node .config/esbuild.build.mjs index # Build entry point + */ + +import { fileURLToPath } from 'node:url' + +import { getDefaultLogger } from '@socketsecurity/lib/logger' + +import { runBuild } from '../scripts/esbuild-utils.mjs' +import cliConfig from './esbuild.cli.mjs' +import indexConfig from './esbuild.index.mjs' + +const logger = getDefaultLogger() + +export const CONFIGS = { + __proto__: null, + cli: cliConfig, + index: indexConfig, +} + +async function main() { + const variant = process.argv[2] || 'all' + + if (variant !== 'all' && !(variant in CONFIGS)) { + logger.error(`Unknown variant: ${variant}`) + logger.error(`Available variants: all, ${Object.keys(CONFIGS).join(', ')}`) + process.exitCode = 1 + return + } + + const targets = + variant === 'all' + ? Object.entries(CONFIGS) + : [[variant, CONFIGS[variant]]] + + const results = await Promise.allSettled( + targets.map(({ 0: name, 1: config }) => runBuild(config, name)), + ) + const failed = results.filter(r => r.status === 'rejected') + if (failed.length > 0) { + process.exitCode = 1 + } +} + +if (fileURLToPath(import.meta.url) === process.argv[1]) { + main().catch(error => { + logger.error('Build failed:', error) + process.exitCode = 1 + }) +} + +export default CONFIGS diff --git a/packages/cli/.config/esbuild.cli.build.mjs b/packages/cli/.config/esbuild.cli.build.mjs deleted file mode 100644 index c93066bf0..000000000 --- a/packages/cli/.config/esbuild.cli.build.mjs +++ /dev/null @@ -1,323 +0,0 @@ -/** - * esbuild configuration for building Socket CLI as a SINGLE unified file. - * - * esbuild is much faster than Rollup and doesn't have template literal corruption issues. - */ - -import { existsSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import { IMPORT_META_URL_BANNER } from 'build-infra/lib/esbuild-helpers' -import { unicodeTransformPlugin } from 'build-infra/lib/esbuild-plugin-unicode-transform' - -import { - createBuildRunner, - createDefineEntries, - envVarReplacementPlugin, - getInlinedEnvVars, -} from '../scripts/esbuild-shared.mjs' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const rootPath = path.join(__dirname, '..') - -// Get all inlined environment variables from shared utility. -const inlinedEnvVars = getInlinedEnvVars() - -// Regex pattern for matching relative paths to socket-lib's external/ directory. -// Matches ./external/, ../external/, ../../external/, etc. -// Supports both forward slashes (Unix/Mac) and backslashes (Windows). -const socketLibExternalPathRegExp = /^(?:\.[/\\]|(?:\.\.[/\\])+)external[/\\]/ - -// Helper to find socket-lib directory (either local sibling or node_modules). -function findSocketLibPath(importerPath) { - // Try to extract socket-lib base path from the importer. - const match = importerPath.match(/^(.*\/@socketsecurity\/lib)\b/) - if (match) { - return match[1] - } - - // Fallback to local sibling directory. - const localPath = path.join(rootPath, '..', '..', '..', 'socket-lib') - if (existsSync(localPath)) { - return localPath - } - - return null -} - -const config = { - entryPoints: [path.join(rootPath, 'src/cli-dispatch.mts')], - bundle: true, - outfile: path.join(rootPath, 'build/cli.js'), - // Target Node.js environment (not browser). - platform: 'node', - // Target Node.js 18+ features. - target: 'node18', - format: 'cjs', - - // With platform: 'node', esbuild automatically externalizes all Node.js built-ins. - external: [], - - // Suppress warnings for intentional CommonJS compatibility code. - logOverride: { - 'commonjs-variable-in-esm': 'silent', - // Suppress warnings about require.resolve for node-gyp (it's external). - 'require-resolve-not-external': 'silent', - }, - - // Add loader for .cs files (node-gyp on Windows). - loader: { - '.cs': 'empty', - }, - - // Source maps off for production. - sourcemap: false, - - // Don't minify (keep readable for debugging). - minify: false, - - // Keep names for better stack traces. - keepNames: true, - - // Plugin needs to transform output. - write: false, - - // Generate metafile for debugging. - metafile: true, - - // Define environment variables and import.meta. - define: { - 'process.env.NODE_ENV': '"production"', - 'import.meta.url': '__importMetaUrl', - // Inject build metadata using shared utility. - ...createDefineEntries(inlinedEnvVars), - }, - - // Add shebang and import.meta.url polyfill at top of bundle. - banner: { - js: `#!/usr/bin/env node\n"use strict";\n${IMPORT_META_URL_BANNER.js}`, - }, - - // Handle special cases with plugins. - plugins: [ - unicodeTransformPlugin(), - // Environment variable replacement must run AFTER unicode transform. - envVarReplacementPlugin(inlinedEnvVars), - { - name: 'resolve-socket-lib-internals', - setup(build) { - build.onResolve({ filter: /^\.\.\/constants\// }, args => { - // Only handle imports from socket-lib's dist directory. - if (!args.importer.includes('/socket-lib/dist/')) { - return null - } - - const socketLibPath = findSocketLibPath(args.importer) - if (!socketLibPath) { - return null - } - - const constantName = args.path.replace(/^\.\.\/constants\//, '') - const resolvedPath = path.join( - socketLibPath, - 'dist', - 'constants', - `${constantName}.js`, - ) - if (existsSync(resolvedPath)) { - return { path: resolvedPath } - } - return null - }) - - build.onResolve({ filter: /^\.\.\/\.\.\/constants\// }, args => { - // Handle ../../constants/ imports. - if (!args.importer.includes('/socket-lib/dist/')) { - return null - } - - const socketLibPath = findSocketLibPath(args.importer) - if (!socketLibPath) { - return null - } - - const constantName = args.path.replace(/^\.\.\/\.\.\/constants\//, '') - const resolvedPath = path.join( - socketLibPath, - 'dist', - 'constants', - `${constantName}.js`, - ) - if (existsSync(resolvedPath)) { - return { path: resolvedPath } - } - return null - }) - - // Resolve relative paths to socket-lib's external/ directory. - // Handles ./external/, ../external/, ../../external/, etc. - // Supports both forward slashes and backslashes for cross-platform compatibility. - // This supports any nesting depth in socket-lib's dist/ directory structure. - build.onResolve({ filter: socketLibExternalPathRegExp }, args => { - // Only handle imports from socket-lib's dist directory. - if (!args.importer.includes('@socketsecurity/lib/dist/')) { - return null - } - - const socketLibPath = findSocketLibPath(args.importer) - if (!socketLibPath) { - return null - } - - // Extract the package path after the relative prefix and external/, and remove .js extension. - // Handles both forward slashes and backslashes. - const externalPath = args.path - .replace(socketLibExternalPathRegExp, '') - .replace(/\.js$/, '') - - // Build the resolved path to socket-lib's bundled external. - let resolvedPath = null - if (externalPath.startsWith('@')) { - // Scoped package like @npmcli/arborist. - const [scope, name] = externalPath.split('/') - const scopedPath = path.join( - socketLibPath, - 'dist', - 'external', - scope, - `${name}.js`, - ) - if (existsSync(scopedPath)) { - resolvedPath = scopedPath - } - } else { - // Regular package. - const packageName = externalPath.split('/')[0] - const regularPath = path.join( - socketLibPath, - 'dist', - 'external', - `${packageName}.js`, - ) - if (existsSync(regularPath)) { - resolvedPath = regularPath - } - } - - if (resolvedPath) { - return { path: resolvedPath } - } - - return null - }) - - // Resolve external dependencies that socket-lib bundles in dist/external/. - // Automatically handles any bundled dependency (e.g., @inquirer/*, zod, semver). - build.onResolve({ filter: /^(@[^/]+\/[^/]+|[^./][^/]*)/ }, args => { - if (!args.importer.includes('/socket-lib/dist/')) { - return null - } - - const socketLibPath = findSocketLibPath(args.importer) - if (!socketLibPath) { - return null - } - - // Extract package name (handle scoped packages). - const packageName = args.path.startsWith('@') - ? args.path.split('/').slice(0, 2).join('/') - : args.path.split('/')[0] - - // Check if this package has a bundled version in dist/external/. - let resolvedPath = null - if (packageName.startsWith('@')) { - // Scoped package like @inquirer/confirm. - const [scope, name] = packageName.split('/') - const scopedPath = path.join( - socketLibPath, - 'dist', - 'external', - scope, - `${name}.js`, - ) - if (existsSync(scopedPath)) { - resolvedPath = scopedPath - } - } else { - // Regular package like zod, semver, etc. - const regularPath = path.join( - socketLibPath, - 'dist', - 'external', - `${packageName}.js`, - ) - if (existsSync(regularPath)) { - resolvedPath = regularPath - } - } - - if (resolvedPath) { - return { path: resolvedPath } - } - - return null - }) - }, - }, - - { - name: 'yoga-wasm-alias', - setup(build) { - // Redirect yoga-layout to our custom synchronous implementation. - build.onResolve({ filter: /^yoga-layout$/ }, () => { - return { - path: path.join(rootPath, 'build/yoga-sync.mjs'), - } - }) - }, - }, - - { - name: 'stub-problematic-packages', - setup(build) { - // Stub iconv-lite and encoding to avoid bundling issues. - build.onResolve({ filter: /^(iconv-lite|encoding)(\/|$)/ }, args => { - return { - path: args.path, - namespace: 'stub', - } - }) - - build.onLoad({ filter: /.*/, namespace: 'stub' }, () => { - return { - contents: 'module.exports = {}', - loader: 'js', - } - }) - }, - }, - - { - name: 'ignore-unsupported-files', - setup(build) { - // Prevent bundling @npmcli/arborist from workspace node_modules. - // This includes the main package and all subpaths like /lib/edge.js. - build.onResolve({ filter: /@npmcli\/arborist/ }, args => { - // Only redirect if it's not already coming from socket-lib's external bundle. - if (args.importer.includes('/socket-lib/dist/')) { - return null - } - return { path: args.path, external: true } - }) - - // Mark node-gyp as external (used by arborist but optionally resolved). - build.onResolve({ filter: /node-gyp/ }, args => { - return { path: args.path, external: true } - }) - }, - }, - ], -} - -export default createBuildRunner(config, 'CLI bundle', import.meta) diff --git a/packages/cli/.config/esbuild.cli.mjs b/packages/cli/.config/esbuild.cli.mjs new file mode 100644 index 000000000..634a33bbc --- /dev/null +++ b/packages/cli/.config/esbuild.cli.mjs @@ -0,0 +1,206 @@ +/** + * esbuild configuration for building Socket CLI as a SINGLE unified file. + * + * esbuild is much faster than Rollup and doesn't have template literal corruption issues. + */ + +import { existsSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { IMPORT_META_URL_BANNER } from 'build-infra/lib/esbuild-helpers' +import { unicodeTransformPlugin } from 'build-infra/lib/esbuild-plugin-unicode-transform' + +import { + createDefineEntries, + envVarReplacementPlugin, + getInlinedEnvVars, + runBuild, +} from '../scripts/esbuild-utils.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootPath = path.join(__dirname, '..') + +const inlinedEnvVars = getInlinedEnvVars() + +// Matches ./external/, ../external/, ../../external/, etc. (forward and back slashes). +const socketLibExternalPathRegExp = /^(?:\.[/\\]|(?:\.\.[/\\])+)external[/\\]/ + +function findSocketLibPath(importerPath) { + const match = importerPath.match(/^(.*\/@socketsecurity\/lib)\b/) + if (match) { + return match[1] + } + const localPath = path.join(rootPath, '..', '..', '..', 'socket-lib') + if (existsSync(localPath)) { + return localPath + } + return null +} + +function resolveSocketLibExternal(socketLibPath, packageName) { + if (packageName.startsWith('@')) { + const [scope, name] = packageName.split('/') + const p = path.join(socketLibPath, 'dist', 'external', scope, `${name}.js`) + return existsSync(p) ? p : null + } + const p = path.join( + socketLibPath, + 'dist', + 'external', + `${packageName.split('/')[0]}.js`, + ) + return existsSync(p) ? p : null +} + + +const config = { + entryPoints: [path.join(rootPath, 'src/cli-dispatch.mts')], + bundle: true, + outfile: path.join(rootPath, 'build/cli.js'), + platform: 'node', + target: 'node18', + format: 'cjs', + logOverride: { + 'commonjs-variable-in-esm': 'silent', + 'require-resolve-not-external': 'silent', + }, + // .cs files used by node-gyp on Windows. + loader: { '.cs': 'empty' }, + sourcemap: false, + minify: false, + keepNames: true, + write: false, + metafile: true, + define: { + 'process.env.NODE_ENV': '"production"', + 'import.meta.url': '__importMetaUrl', + ...createDefineEntries(inlinedEnvVars), + }, + banner: { + js: `#!/usr/bin/env node\n"use strict";\n${IMPORT_META_URL_BANNER.js}`, + }, + + plugins: [ + unicodeTransformPlugin(), + // Environment variable replacement must run AFTER unicode transform. + envVarReplacementPlugin(inlinedEnvVars), + { + name: 'resolve-socket-lib-internals', + setup(build) { + function resolveConstant(args, strip) { + if (!args.importer.includes('/socket-lib/dist/')) { + return null + } + const socketLibPath = findSocketLibPath(args.importer) + if (!socketLibPath) { + return null + } + const p = path.join( + socketLibPath, + 'dist', + 'constants', + `${args.path.replace(strip, '')}.js`, + ) + return existsSync(p) ? { path: p } : null + } + + build.onResolve({ filter: /^\.\.\/constants\// }, args => + resolveConstant(args, /^\.\.\/constants\//), + ) + + build.onResolve({ filter: /^\.\.\/\.\.\/constants\// }, args => + resolveConstant(args, /^\.\.\/\.\.\/constants\//), + ) + + build.onResolve({ filter: socketLibExternalPathRegExp }, args => { + if (!args.importer.includes('@socketsecurity/lib/dist/')) { + return null + } + const socketLibPath = findSocketLibPath(args.importer) + if (!socketLibPath) { + return null + } + const externalPath = args.path + .replace(socketLibExternalPathRegExp, '') + .replace(/\.js$/, '') + const p = resolveSocketLibExternal(socketLibPath, externalPath) + return p ? { path: p } : null + }) + + build.onResolve({ filter: /^(@[^/]+\/[^/]+|[^./][^/]*)/ }, args => { + if (!args.importer.includes('/socket-lib/dist/')) { + return null + } + const socketLibPath = findSocketLibPath(args.importer) + if (!socketLibPath) { + return null + } + const packageName = args.path.startsWith('@') + ? args.path.split('/').slice(0, 2).join('/') + : args.path.split('/')[0] + const p = resolveSocketLibExternal(socketLibPath, packageName) + return p ? { path: p } : null + }) + }, + }, + + { + name: 'yoga-wasm-alias', + setup(build) { + // Redirect yoga-layout to our custom synchronous implementation. + build.onResolve({ filter: /^yoga-layout$/ }, () => { + return { + path: path.join(rootPath, 'build/yoga-sync.mjs'), + } + }) + }, + }, + + { + name: 'stub-problematic-packages', + setup(build) { + // Stub iconv-lite and encoding to avoid bundling issues. + build.onResolve({ filter: /^(iconv-lite|encoding)(\/|$)/ }, args => { + return { + path: args.path, + namespace: 'stub', + } + }) + + build.onLoad({ filter: /.*/, namespace: 'stub' }, () => { + return { + contents: 'module.exports = {}', + loader: 'js', + } + }) + }, + }, + + { + name: 'ignore-unsupported-files', + setup(build) { + // Prevent bundling @npmcli/arborist from workspace node_modules. + // This includes the main package and all subpaths like /lib/edge.js. + build.onResolve({ filter: /@npmcli\/arborist/ }, args => { + // Only redirect if it's not already coming from socket-lib's external bundle. + if (args.importer.includes('/socket-lib/dist/')) { + return null + } + return { path: args.path, external: true } + }) + + // Mark node-gyp as external (used by arborist but optionally resolved). + build.onResolve({ filter: /node-gyp/ }, args => { + return { path: args.path, external: true } + }) + }, + }, + ], +} + +if (fileURLToPath(import.meta.url) === process.argv[1]) { + runBuild(config, 'CLI bundle').catch(() => { process.exitCode = 1 }) +} + +export default config diff --git a/packages/cli/.config/esbuild.config.mjs b/packages/cli/.config/esbuild.config.mjs deleted file mode 100644 index 84dceefa5..000000000 --- a/packages/cli/.config/esbuild.config.mjs +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Unified esbuild configuration orchestrator for Socket CLI. - * Supports building all variants by delegating to individual config files. - * - * Usage: - * node .config/esbuild.config.mjs [variant] - * node .config/esbuild.config.mjs cli # Build CLI bundle - * node .config/esbuild.config.mjs index # Build entry point - * node .config/esbuild.config.mjs all # Build all variants - */ - -import { spawn } from 'node:child_process' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import cliConfig from './esbuild.cli.build.mjs' -import indexConfig from './esbuild.index.config.mjs' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) - -/** - * Config mapping for each build variant (exports for programmatic use). - */ -export const CONFIGS = { - __proto__: null, - cli: cliConfig, - index: indexConfig, -} - -/** - * Config file paths for each build variant. - */ -const VARIANT_FILES = { - __proto__: null, - all: null, // Special variant to build all. - cli: path.join(__dirname, 'esbuild.cli.build.mjs'), - index: path.join(__dirname, 'esbuild.index.config.mjs'), -} - -/** - * Build a single variant by executing its config file. - */ -async function buildVariant(name, configPath) { - return new Promise(resolve => { - const child = spawn('node', [configPath], { stdio: 'inherit' }) - - child.on('close', code => { - if (code === 0) { - resolve({ name, ok: true }) - } else { - resolve({ name, ok: false }) - } - }) - }) -} - -/** - * Build all variants in parallel. - */ -async function buildAll() { - const variants = ['cli', 'index'] - const results = await Promise.all( - variants.map(name => buildVariant(name, VARIANT_FILES[name])), - ) - - const failed = results.filter(r => !r.ok) - if (failed.length > 0) { - console.error(`\n${failed.length} build(s) failed:`) - for (const { name } of failed) { - console.error(` - ${name}`) - } - process.exitCode = 1 - } else { - console.log(`\n✔ All ${results.length} builds succeeded`) - } -} - -/** - * Main entry point. - */ -async function main() { - const variant = process.argv[2] || 'all' - - if (!(variant in VARIANT_FILES)) { - console.error(`Unknown variant: ${variant}`) - console.error( - `Available variants: ${Object.keys(VARIANT_FILES).join(', ')}`, - ) - process.exitCode = 1 - return - } - - if (variant === 'all') { - await buildAll() - } else { - const result = await buildVariant(variant, VARIANT_FILES[variant]) - if (!result.ok) { - process.exitCode = 1 - } - } -} - -// Run if invoked directly. -if (fileURLToPath(import.meta.url) === process.argv[1]) { - main().catch(error => { - console.error('Build failed:', error) - process.exitCode = 1 - }) -} - -export default CONFIGS diff --git a/packages/cli/.config/esbuild.index.config.mjs b/packages/cli/.config/esbuild.index.mjs similarity index 69% rename from packages/cli/.config/esbuild.index.config.mjs rename to packages/cli/.config/esbuild.index.mjs index 9eac12a2e..3499d65c9 100644 --- a/packages/cli/.config/esbuild.index.config.mjs +++ b/packages/cli/.config/esbuild.index.mjs @@ -7,9 +7,9 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import { - createBuildRunner, createIndexConfig, -} from '../scripts/esbuild-shared.mjs' + runBuild, +} from '../scripts/esbuild-utils.mjs' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.resolve(__dirname, '..') @@ -19,4 +19,8 @@ const config = createIndexConfig({ outfile: path.join(rootPath, 'dist', 'index.js'), }) -export default createBuildRunner(config, 'Entry point', import.meta) +if (fileURLToPath(import.meta.url) === process.argv[1]) { + runBuild(config, 'Entry point').catch(() => { process.exitCode = 1 }) +} + +export default config diff --git a/packages/cli/scripts/build-js.mjs b/packages/cli/scripts/build-js.mjs index 4a9368ae4..ef7899381 100644 --- a/packages/cli/scripts/build-js.mjs +++ b/packages/cli/scripts/build-js.mjs @@ -35,9 +35,14 @@ async function main() { logger.step('Building CLI bundle') const buildResult = await spawn( 'node', - ['--max-old-space-size=8192', '.config/esbuild.config.mjs', 'cli'], + ['--max-old-space-size=8192', '.config/esbuild.build.mjs', 'cli'], { stdio: 'inherit' }, ) + if (!buildResult) { + logger.error('Failed to start CLI build') + process.exitCode = 1 + return + } if (buildResult.code !== 0) { process.exitCode = buildResult.code return diff --git a/packages/cli/scripts/build.mjs b/packages/cli/scripts/build.mjs index dc8234bca..8b69a718a 100644 --- a/packages/cli/scripts/build.mjs +++ b/packages/cli/scripts/build.mjs @@ -65,7 +65,7 @@ async function fixNodeGypStrings(dir, options = {}) { await fixNodeGypStrings(filePath, options) } else if (file.name.endsWith('.js')) { // Read file contents. - const contents = await fs.readFile(filePath, 'utf8') + const contents = await fs.readFile(filePath, 'utf-8') // Check if file contains the problematic pattern. if (contents.includes('node-gyp/bin/node-gyp.js')) { @@ -75,7 +75,7 @@ async function fixNodeGypStrings(dir, options = {}) { '"node-" + "gyp/bin/node-gyp.js"', ) - await fs.writeFile(filePath, fixed, 'utf8') + await fs.writeFile(filePath, fixed, 'utf-8') if (!quiet && verbose) { logger.info( @@ -134,7 +134,7 @@ async function main() { // Then start esbuild in watch mode. const watchResult = await spawn( 'node', - [...NODE_MEMORY_FLAGS, '.config/esbuild.cli.build.mjs', '--watch'], + [...NODE_MEMORY_FLAGS, '.config/esbuild.cli.mjs', '--watch'], { shell: WIN32, stdio: 'inherit', @@ -240,7 +240,7 @@ async function main() { const buildResult = await spawn( 'node', - [...NODE_MEMORY_FLAGS, '.config/esbuild.config.mjs', 'all'], + [...NODE_MEMORY_FLAGS, '.config/esbuild.build.mjs', 'all'], { shell: WIN32, stdio: 'inherit', diff --git a/packages/cli/scripts/cover.mjs b/packages/cli/scripts/cover.mjs index 532fc2c07..3e5630da9 100644 --- a/packages/cli/scripts/cover.mjs +++ b/packages/cli/scripts/cover.mjs @@ -22,25 +22,16 @@ import { spawn } from '@socketsecurity/lib/spawn' const logger = getDefaultLogger() -/** - * Print a header message. - */ function printHeader(message) { logger.error('\n═══════════════════════════════════════════════════════') logger.error(` ${message}`) logger.error('═══════════════════════════════════════════════════════\n') } -/** - * Print a success message. - */ function printSuccess(message) { logger.log(`✔ ${message}`) } -/** - * Print an error message. - */ function printError(message) { logger.error(`✖ ${message}`) } diff --git a/packages/cli/scripts/environment-variables.mjs b/packages/cli/scripts/environment-variables.mjs index 4b03cba04..6e7a859dc 100644 --- a/packages/cli/scripts/environment-variables.mjs +++ b/packages/cli/scripts/environment-variables.mjs @@ -3,7 +3,7 @@ * Single source of truth for all inlined environment variables. * * This module consolidates environment variable loading that was previously duplicated between: - * - esbuild-shared.mjs (full build-time inlining with 18 variables) + * - esbuild-utils.mjs (full build-time inlining with 18 variables) * - test-wrapper.mjs (partial test environment with 4 variables) * * Usage: @@ -158,7 +158,7 @@ export class EnvironmentVariables { static loadSafe() { try { const externalTools = JSON.parse( - readFileSync(path.join(rootPath, 'external-tools.json'), 'utf8'), + readFileSync(path.join(rootPath, 'external-tools.json'), 'utf-8'), ) return { INLINED_COANA_VERSION: diff --git a/packages/cli/scripts/esbuild-shared.mjs b/packages/cli/scripts/esbuild-utils.mjs similarity index 63% rename from packages/cli/scripts/esbuild-shared.mjs rename to packages/cli/scripts/esbuild-utils.mjs index 751a0ab99..9b31bf0a0 100644 --- a/packages/cli/scripts/esbuild-shared.mjs +++ b/packages/cli/scripts/esbuild-utils.mjs @@ -3,14 +3,18 @@ * Contains helpers for environment variable inlining and build metadata. */ -import { execSync } from 'node:child_process' -import { randomUUID } from 'node:crypto' -import { readFileSync } from 'node:fs' +import { mkdirSync, writeFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' +import { build } from 'esbuild' + +import { getDefaultLogger } from '@socketsecurity/lib/logger' + import { EnvironmentVariables } from './environment-variables.mjs' +const logger = getDefaultLogger() + const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.join(__dirname, '..') @@ -32,12 +36,12 @@ export function createIndexConfig({ entryPoint, minify = false, outfile }) { }, bundle: true, entryPoints: [entryPoint], - external: [], format: 'cjs', outfile, platform: 'node', + // Source maps off for entry point production build. + sourcemap: false, target: 'node18', - treeShaking: true, // Define environment variables for inlining. define: { 'process.env.NODE_ENV': '"production"', @@ -54,7 +58,6 @@ export function createIndexConfig({ entryPoint, minify = false, outfile }) { } else { config.minifyWhitespace = true config.minifyIdentifiers = true - config.minifySyntax = false } return config @@ -127,65 +130,34 @@ export function getInlinedEnvVars() { } /** - * Create a build runner function that executes esbuild config when run as main module. - * This eliminates boilerplate code repeated across all esbuild config files. + * Run an esbuild config, writing output files if write: false. * * @param {Object} config - esbuild configuration object - * @param {string} [description] - Optional description of what this build does - * @param {ImportMeta} importMeta - The import.meta from the calling config file - * @returns {Object} The same config object (for chaining) - * - * @example - * ```javascript - * import { build } from 'esbuild' - * import { createBuildRunner } from './esbuild-shared.mjs' - * - * const config = { ... } - * export default createBuildRunner(config, 'CLI bundle', import.meta) - * ``` + * @param {string} [description] - Description logged before/after build */ -export function createBuildRunner(config, description = 'Build', importMeta) { - // Only run if the caller's file is the main module (executed directly). - // This allows configs to be imported without side effects. - if ( - importMeta && - fileURLToPath(importMeta.url) === process.argv[1]?.replace(/\\/g, '/') - ) { - ;(async () => { - try { - // Import esbuild dynamically to avoid loading it during imports. - const { build } = await import('esbuild') - - if (description) { - console.log(`Building: ${description}`) - } - - const result = await build(config) - - // If write: false, manually write outputFiles. - if (result.outputFiles && result.outputFiles.length > 0) { - const { writeFileSync } = await import('node:fs') - const { dirname } = await import('node:path') - const { mkdirSync } = await import('node:fs') - - for (const output of result.outputFiles) { - // Ensure directory exists. - mkdirSync(dirname(output.path), { recursive: true }) - // Write output file. - writeFileSync(output.path, output.contents) - } +export async function runBuild(config, description = 'Build') { + try { + if (description) { + logger.info(`Building: ${description}`) + } + + const result = await build(config) + + // If write: false, manually write outputFiles. + if (result.outputFiles && result.outputFiles.length > 0) { + for (const output of result.outputFiles) { + mkdirSync(path.dirname(output.path), { recursive: true }) + writeFileSync(output.path, output.contents) + } - if (description) { - console.log(`✓ ${description} complete`) - } - } - } catch (error) { - console.error(`Build failed: ${description || 'Unknown'}`) - console.error(error) - process.exitCode = 1 + if (description) { + logger.success(`${description} complete`) } - })() + } + } catch (e) { + logger.error(`Build failed: ${description || 'Unknown'}`) + logger.error(e) + process.exitCode = 1 + throw e } - - return config } diff --git a/packages/cli/scripts/test-wrapper.mjs b/packages/cli/scripts/test-wrapper.mjs index 75bdcb249..0db2d5a39 100644 --- a/packages/cli/scripts/test-wrapper.mjs +++ b/packages/cli/scripts/test-wrapper.mjs @@ -154,17 +154,12 @@ async function main() { ...(WIN32 ? { shell: true } : {}), } - const child = spawn(dotenvxPath, dotenvxArgs, spawnOptions) - - child.on('exit', code => { - process.exitCode = code || 0 - }) - - child.on('error', e => { - logger.error('Failed to spawn test process:', e) - process.exitCode = 1 - }) - } catch {} + const result = await spawn(dotenvxPath, dotenvxArgs, spawnOptions) + process.exitCode = result?.code || 0 + } catch (e) { + logger.error('Failed to spawn test process:', e) + process.exitCode = 1 + } } main().catch(e => { diff --git a/packages/cli/scripts/utils/patches.mjs b/packages/cli/scripts/utils/patches.mjs index 1da7fa936..c73a3bc73 100644 --- a/packages/cli/scripts/utils/patches.mjs +++ b/packages/cli/scripts/utils/patches.mjs @@ -13,6 +13,8 @@ import { WIN32 } from '@socketsecurity/lib/constants/platform' import { getDefaultLogger } from '@socketsecurity/lib/logger' import { spawn } from '@socketsecurity/lib/spawn' +const logger = getDefaultLogger() + /** * Parse JavaScript/TypeScript code into a Babel AST. * @@ -90,7 +92,6 @@ async function promptYesNo(question, defaultAnswer = false) { * @returns {Promise} Path to temporary patch directory. */ export async function startPatch(packageSpec) { - const logger = getDefaultLogger() logger.log(`Starting patch for ${packageSpec}...`) // First, try to run pnpm patch to see if directory already exists. diff --git a/packages/cli/scripts/validate-bundle.mjs b/packages/cli/scripts/validate-bundle.mjs index 2e3338f15..6f3189143 100644 --- a/packages/cli/scripts/validate-bundle.mjs +++ b/packages/cli/scripts/validate-bundle.mjs @@ -23,10 +23,10 @@ const buildPath = path.join(__dirname, '..', 'build', 'cli.js') function validateBundle() { let content try { - content = readFileSync(buildPath, 'utf8') - } catch (error) { - logger.fail(`Failed to read bundle: ${error.message}`) - return false + content = readFileSync(buildPath, 'utf-8') + } catch (e) { + logger.fail(`Failed to read bundle: ${e.message}`) + return null } const violations = [] @@ -49,6 +49,11 @@ async function main() { try { const violations = validateBundle() + if (!violations) { + process.exitCode = 1 + return + } + if (violations.length === 0) { logger.success('Bundle validation passed') process.exitCode = 0 @@ -82,7 +87,7 @@ async function main() { } } -main().catch(error => { - console.error(`Validation failed: ${error}`) +main().catch(e => { + logger.error(`Validation failed: ${e}`) process.exitCode = 1 }) diff --git a/packages/cli/scripts/verify-package.mjs b/packages/cli/scripts/verify-package.mjs index be0ac7232..a7935bea3 100644 --- a/packages/cli/scripts/verify-package.mjs +++ b/packages/cli/scripts/verify-package.mjs @@ -1,33 +1,17 @@ -import { promises as fs } from 'node:fs' +import { existsSync, promises as fs } from 'node:fs' import path from 'node:path' -import process from 'node:process' import { fileURLToPath } from 'node:url' import colors from 'yoctocolors-cjs' import { getDefaultLogger } from '@socketsecurity/lib/logger' -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) +const __dirname = path.dirname(fileURLToPath(import.meta.url)) const packageRoot = path.resolve(__dirname, '..') -/** - * Check if a file exists and is readable. - */ -async function fileExists(filePath) { - try { - await fs.access(filePath) - return true - } catch { - return false - } -} +const logger = getDefaultLogger() -/** - * Main validation function. - */ async function validate() { - const logger = getDefaultLogger() logger.log('') logger.log('='.repeat(60)) logger.log(`${colors.blue('CLI Package Validation')}`) @@ -39,7 +23,7 @@ async function validate() { // Check package.json exists and has correct files array. logger.info('Checking package.json...') const pkgPath = path.join(packageRoot, 'package.json') - if (!(await fileExists(pkgPath))) { + if (!(existsSync(pkgPath))) { errors.push('package.json does not exist') } else { logger.success('package.json exists') @@ -75,7 +59,7 @@ async function validate() { for (const file of rootFiles) { logger.info(`Checking ${file}...`) const filePath = path.join(packageRoot, file) - if (!(await fileExists(filePath))) { + if (!(existsSync(filePath))) { errors.push(`${file} does not exist`) } else { logger.success(`${file} exists`) @@ -87,7 +71,7 @@ async function validate() { for (const file of distFiles) { logger.info(`Checking dist/${file}...`) const filePath = path.join(packageRoot, 'dist', file) - if (!(await fileExists(filePath))) { + if (!(existsSync(filePath))) { errors.push(`dist/${file} does not exist`) } else { logger.success(`dist/${file} exists`) @@ -97,7 +81,7 @@ async function validate() { // Check data directory exists. logger.info('Checking data directory...') const dataPath = path.join(packageRoot, 'data') - if (!(await fileExists(dataPath))) { + if (!(existsSync(dataPath))) { errors.push('data directory does not exist') } else { logger.success('data directory exists') @@ -110,7 +94,7 @@ async function validate() { for (const file of dataFiles) { logger.info(`Checking data/${file}...`) const filePath = path.join(dataPath, file) - if (!(await fileExists(filePath))) { + if (!(existsSync(filePath))) { errors.push(`data/${file} does not exist`) } else { logger.success(`data/${file} exists`) @@ -128,7 +112,7 @@ async function validate() { if (errors.length > 0) { logger.log(`${colors.red('Errors:')}`) for (const err of errors) { - logger.log(` ${error(err)}`) + logger.log(` ${err}`) } logger.log('') logger.fail('Package validation FAILED') diff --git a/packages/cli/scripts/wasm.mjs b/packages/cli/scripts/wasm.mjs index e5816a0d0..8386278fe 100644 --- a/packages/cli/scripts/wasm.mjs +++ b/packages/cli/scripts/wasm.mjs @@ -24,6 +24,8 @@ import { fileURLToPath } from 'node:url' import { getDefaultLogger } from '@socketsecurity/lib/logger' import { spawn } from '@socketsecurity/lib/spawn' +const logger = getDefaultLogger() + const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.join(__dirname, '..') const externalDir = path.join(rootPath, 'external') @@ -40,7 +42,6 @@ function checkNodeVersion() { const major = Number.parseInt(nodeVersion.split('.')[0], 10) if (major < 18) { - const logger = getDefaultLogger() logger.error(' Node.js version 18 or higher is required') logger.error(`Current version: ${nodeVersion}`) logger.error('Please upgrade: https://nodejs.org/') diff --git a/packages/package-builder/templates/cli-package/.config/esbuild.cli.build.mjs b/packages/package-builder/templates/cli-package/.config/esbuild.cli.build.mjs deleted file mode 100644 index 99382576a..000000000 --- a/packages/package-builder/templates/cli-package/.config/esbuild.cli.build.mjs +++ /dev/null @@ -1,48 +0,0 @@ -/** - * esbuild configuration for building Socket CLI. - * Extends the base CLI build configuration. - */ - -import { mkdirSync, writeFileSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import { build } from 'esbuild' - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -import baseConfig from '../../cli/.config/esbuild.cli.build.mjs' - -const logger = getDefaultLogger() - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const rootPath = path.join(__dirname, '..') - -// Create standard CLI configuration. -const config = { - ...baseConfig, - // Use the standard entry point. - entryPoints: [path.join(rootPath, '..', 'cli', 'src', 'cli-dispatch.mts')], - // Output to build directory. - outfile: path.join(rootPath, 'build/cli.js'), -} - -// Run build if invoked directly. -if (fileURLToPath(import.meta.url) === process.argv[1]) { - build(config) - .then(result => { - // Write the transformed output (build had write: false). - if (result.outputFiles && result.outputFiles.length > 0) { - mkdirSync(path.dirname(config.outfile), { recursive: true }) - for (const output of result.outputFiles) { - writeFileSync(output.path, output.contents) - } - } - }) - .catch(error => { - logger.error('Build failed:', error) - process.exitCode = 1 - }) -} - -export default config diff --git a/packages/package-builder/templates/cli-package/.config/esbuild.cli.mjs b/packages/package-builder/templates/cli-package/.config/esbuild.cli.mjs new file mode 100644 index 000000000..d21e6efa9 --- /dev/null +++ b/packages/package-builder/templates/cli-package/.config/esbuild.cli.mjs @@ -0,0 +1,26 @@ +/** + * esbuild configuration for building Socket CLI. + * Extends the base CLI build configuration. + */ + +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import { runBuild } from '../../cli/scripts/esbuild-utils.mjs' + +import baseConfig from '../../cli/.config/esbuild.cli.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const rootPath = path.join(__dirname, '..') + +const config = { + ...baseConfig, + entryPoints: [path.join(rootPath, '..', 'cli', 'src', 'cli-dispatch.mts')], + outfile: path.join(rootPath, 'build/cli.js'), +} + +if (fileURLToPath(import.meta.url) === process.argv[1]) { + runBuild(config, 'CLI bundle').catch(() => { process.exitCode = 1 }) +} + +export default config diff --git a/packages/package-builder/templates/cli-package/.config/esbuild.config.mjs b/packages/package-builder/templates/cli-package/.config/esbuild.config.mjs deleted file mode 100644 index 5e69e8e7a..000000000 --- a/packages/package-builder/templates/cli-package/.config/esbuild.config.mjs +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @fileoverview esbuild configuration for Socket CLI with Sentry telemetry. - * Builds a Sentry-enabled version of the CLI with error reporting. - */ - -import { mkdirSync, writeFileSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import { build } from 'esbuild' - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -// Import base esbuild config from main CLI. -import baseConfig from '../../cli/.config/esbuild.cli.build.mjs' - -const logger = getDefaultLogger() - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const rootPath = path.join(__dirname, '..') -const cliPath = path.join(__dirname, '..', '..', 'cli') - -// Override entry point to use CLI dispatch with Sentry telemetry. -const config = { - ...baseConfig, - entryPoints: [path.join(cliPath, 'src/cli-dispatch-with-sentry.mts')], - outfile: path.join(rootPath, 'build/cli.js'), - - // Override define to enable Sentry build. - define: { - ...baseConfig.define, - 'process.env.INLINED_SENTRY_BUILD': JSON.stringify('1'), - }, - - // Make @sentry/node external (not bundled). - external: [...(baseConfig.external || []), '@sentry/node'], -} - -// Run build if invoked directly. -if (import.meta.url === `file://${process.argv[1]}`) { - build(config) - .then(result => { - // Write the transformed output (build had write: false). - if (result.outputFiles && result.outputFiles.length > 0) { - mkdirSync(path.dirname(config.outfile), { recursive: true }) - for (const output of result.outputFiles) { - writeFileSync(output.path, output.contents) - } - } - }) - .catch(error => { - logger.error('Build failed:', error) - process.exitCode = 1 - }) -} - -export default config diff --git a/packages/package-builder/templates/cli-package/.config/esbuild.index.config.mjs b/packages/package-builder/templates/cli-package/.config/esbuild.index.mjs similarity index 56% rename from packages/package-builder/templates/cli-package/.config/esbuild.index.config.mjs rename to packages/package-builder/templates/cli-package/.config/esbuild.index.mjs index 759dd4692..1c3973c24 100644 --- a/packages/package-builder/templates/cli-package/.config/esbuild.index.config.mjs +++ b/packages/package-builder/templates/cli-package/.config/esbuild.index.mjs @@ -1,18 +1,15 @@ /** - * esbuild configuration for Socket CLI with Sentry index loader. + * esbuild configuration for Socket CLI index loader. * Builds the index loader that executes the CLI. */ import path from 'node:path' import { fileURLToPath } from 'node:url' -import { build } from 'esbuild' - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -import { createIndexConfig } from '../../cli/scripts/esbuild-shared.mjs' - -const logger = getDefaultLogger() +import { + createIndexConfig, + runBuild, +} from '../../cli/scripts/esbuild-utils.mjs' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.resolve(__dirname, '..') @@ -24,12 +21,8 @@ const config = createIndexConfig({ minify: true, }) -// Run build if invoked directly. if (fileURLToPath(import.meta.url) === process.argv[1]) { - build(config).catch(error => { - logger.error('Index loader build failed:', error) - process.exitCode = 1 - }) + runBuild(config, 'Entry point') } export default config diff --git a/packages/package-builder/templates/cli-package/scripts/build.mjs b/packages/package-builder/templates/cli-package/scripts/build.mjs index aa02c1e1a..b8934ed59 100644 --- a/packages/package-builder/templates/cli-package/scripts/build.mjs +++ b/packages/package-builder/templates/cli-package/scripts/build.mjs @@ -24,7 +24,7 @@ async function main() { // Build CLI bundle. logger.info('Building CLI bundle...') - let result = await spawn('node', ['.config/esbuild.cli.build.mjs'], { + let result = await spawn('node', ['.config/esbuild.cli.mjs'], { shell: WIN32, stdio: 'inherit', cwd: rootPath, @@ -41,7 +41,7 @@ async function main() { // Build index loader. logger.info('Building index loader...') - result = await spawn('node', ['.config/esbuild.index.config.mjs'], { + result = await spawn('node', ['.config/esbuild.index.mjs'], { shell: WIN32, stdio: 'inherit', cwd: rootPath, diff --git a/packages/package-builder/templates/cli-package/test/package.test.mjs b/packages/package-builder/templates/cli-package/test/package.test.mjs index 2115466be..9b4f50523 100644 --- a/packages/package-builder/templates/cli-package/test/package.test.mjs +++ b/packages/package-builder/templates/cli-package/test/package.test.mjs @@ -125,7 +125,7 @@ describe('@socketsecurity/cli-with-sentry package', () => { const content = await fs.readFile(esbuildPath, 'utf-8') expect(content).toContain( - "import baseConfig from '../../cli/.config/esbuild.cli.build.mjs'", + "import baseConfig from '../../cli/.config/esbuild.cli.mjs'", ) }) diff --git a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.cli-sentry.build.mjs b/packages/package-builder/templates/cli-sentry-package/.config/esbuild.cli-sentry.build.mjs index 3cf673699..45d37f3db 100644 --- a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.cli-sentry.build.mjs +++ b/packages/package-builder/templates/cli-sentry-package/.config/esbuild.cli-sentry.build.mjs @@ -3,50 +3,31 @@ * Extends the base CLI build configuration with Sentry-specific settings. */ -import { mkdirSync, writeFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { build } from 'esbuild' +import { runBuild } from '../../cli/scripts/esbuild-utils.mjs' -import baseConfig from '../../cli/.config/esbuild.cli.build.mjs' +import baseConfig from '../../cli/.config/esbuild.cli.mjs' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.join(__dirname, '..') +const cliPath = path.join(__dirname, '..', '..', 'cli') -// Create Sentry-enabled configuration. const config = { ...baseConfig, - // Use the Sentry-enabled entry point. - entryPoints: [ - path.join(rootPath, '..', 'cli', 'src', 'cli-dispatch-with-sentry.mts'), - ], - // Output to build directory. + entryPoints: [path.join(cliPath, 'src/cli-dispatch-with-sentry.mts')], outfile: path.join(rootPath, 'build/cli.js'), - // Override define to enable Sentry build flag. define: { ...baseConfig.define, 'process.env.INLINED_SENTRY_BUILD': JSON.stringify('1'), 'process.env["INLINED_SENTRY_BUILD"]': JSON.stringify('1'), }, + external: [...(baseConfig.external || []), '@sentry/node'], } -// Run build if invoked directly. if (fileURLToPath(import.meta.url) === process.argv[1]) { - build(config) - .then(result => { - // Write the transformed output (build had write: false). - if (result.outputFiles && result.outputFiles.length > 0) { - mkdirSync(path.dirname(config.outfile), { recursive: true }) - for (const output of result.outputFiles) { - writeFileSync(output.path, output.contents) - } - } - }) - .catch(error => { - logger.error('Build failed:', error) - process.exitCode = 1 - }) + runBuild(config, 'CLI bundle (Sentry)').catch(() => { process.exitCode = 1 }) } export default config diff --git a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.config.mjs b/packages/package-builder/templates/cli-sentry-package/.config/esbuild.config.mjs deleted file mode 100644 index 5e69e8e7a..000000000 --- a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.config.mjs +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @fileoverview esbuild configuration for Socket CLI with Sentry telemetry. - * Builds a Sentry-enabled version of the CLI with error reporting. - */ - -import { mkdirSync, writeFileSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -import { build } from 'esbuild' - -import { getDefaultLogger } from '@socketsecurity/lib/logger' - -// Import base esbuild config from main CLI. -import baseConfig from '../../cli/.config/esbuild.cli.build.mjs' - -const logger = getDefaultLogger() - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const rootPath = path.join(__dirname, '..') -const cliPath = path.join(__dirname, '..', '..', 'cli') - -// Override entry point to use CLI dispatch with Sentry telemetry. -const config = { - ...baseConfig, - entryPoints: [path.join(cliPath, 'src/cli-dispatch-with-sentry.mts')], - outfile: path.join(rootPath, 'build/cli.js'), - - // Override define to enable Sentry build. - define: { - ...baseConfig.define, - 'process.env.INLINED_SENTRY_BUILD': JSON.stringify('1'), - }, - - // Make @sentry/node external (not bundled). - external: [...(baseConfig.external || []), '@sentry/node'], -} - -// Run build if invoked directly. -if (import.meta.url === `file://${process.argv[1]}`) { - build(config) - .then(result => { - // Write the transformed output (build had write: false). - if (result.outputFiles && result.outputFiles.length > 0) { - mkdirSync(path.dirname(config.outfile), { recursive: true }) - for (const output of result.outputFiles) { - writeFileSync(output.path, output.contents) - } - } - }) - .catch(error => { - logger.error('Build failed:', error) - process.exitCode = 1 - }) -} - -export default config diff --git a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.config.mjs b/packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.mjs similarity index 70% rename from packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.config.mjs rename to packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.mjs index 6cf1525ef..5932b1e45 100644 --- a/packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.config.mjs +++ b/packages/package-builder/templates/cli-sentry-package/.config/esbuild.index.mjs @@ -6,9 +6,10 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' -import { build } from 'esbuild' - -import { createIndexConfig } from '../../cli/scripts/esbuild-shared.mjs' +import { + createIndexConfig, + runBuild, +} from '../../cli/scripts/esbuild-utils.mjs' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const rootPath = path.resolve(__dirname, '..') @@ -20,12 +21,8 @@ const config = createIndexConfig({ minify: true, }) -// Run build if invoked directly. if (fileURLToPath(import.meta.url) === process.argv[1]) { - build(config).catch(error => { - logger.error('Index loader build failed:', error) - process.exitCode = 1 - }) + runBuild(config, 'Entry point').catch(() => { process.exitCode = 1 }) } export default config diff --git a/packages/package-builder/templates/cli-sentry-package/scripts/build.mjs b/packages/package-builder/templates/cli-sentry-package/scripts/build.mjs index 4bd47ba82..53ede83a6 100644 --- a/packages/package-builder/templates/cli-sentry-package/scripts/build.mjs +++ b/packages/package-builder/templates/cli-sentry-package/scripts/build.mjs @@ -29,6 +29,9 @@ async function main() { stdio: 'inherit', cwd: rootPath, }) + if (!result) { + throw new Error('Failed to start CLI bundle build') + } if (result.code !== 0) { throw new Error(`CLI bundle build failed with exit code ${result.code}`) } @@ -36,11 +39,14 @@ async function main() { // Build index loader. logger.info('Building index loader...') - result = await spawn('node', ['.config/esbuild.index.config.mjs'], { + result = await spawn('node', ['.config/esbuild.index.mjs'], { shell: WIN32, stdio: 'inherit', cwd: rootPath, }) + if (!result) { + throw new Error('Failed to start index loader build') + } if (result.code !== 0) { throw new Error(`Index loader build failed with exit code ${result.code}`) } diff --git a/packages/package-builder/templates/cli-sentry-package/test/package.test.mjs b/packages/package-builder/templates/cli-sentry-package/test/package.test.mjs index b24020278..e23dd6dc0 100644 --- a/packages/package-builder/templates/cli-sentry-package/test/package.test.mjs +++ b/packages/package-builder/templates/cli-sentry-package/test/package.test.mjs @@ -125,7 +125,7 @@ describe('@socketsecurity/cli-with-sentry package', () => { const content = await fs.readFile(esbuildPath, 'utf-8') expect(content).toContain( - "import baseConfig from '../../cli/.config/esbuild.cli.build.mjs'", + "import baseConfig from '../../cli/.config/esbuild.cli.mjs'", ) })