Skip to content

Commit f733695

Browse files
committed
Initial commit
1 parent 0f16449 commit f733695

File tree

543 files changed

+101548
-14921
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

543 files changed

+101548
-14921
lines changed

.config/esbuild.config.mjs

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/**
2+
* @fileoverview esbuild configuration for socket-lib
3+
* Fast JS compilation with esbuild, declarations with tsgo
4+
*/
5+
6+
import path from 'node:path'
7+
import { fileURLToPath } from 'node:url'
8+
import fg from 'fast-glob'
9+
10+
import { envAsBoolean } from '@socketsecurity/lib-stable/env/helpers'
11+
12+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
13+
const rootPath = path.join(__dirname, '..')
14+
const srcPath = path.join(rootPath, 'src')
15+
const distPath = path.join(rootPath, 'dist')
16+
17+
// Find all TypeScript source files
18+
const entryPoints = fg.sync('**/*.{ts,mts,cts}', {
19+
cwd: srcPath,
20+
absolute: true,
21+
// Skip declaration files.
22+
ignore: ['**/*.d.ts', '**/types/**', '**/external/**'],
23+
})
24+
25+
/**
26+
* Plugin to shorten module paths in bundled output with conflict detection.
27+
* Uses @babel/parser and magic-string for precise AST-based modifications.
28+
*/
29+
function createPathShorteningPlugin() {
30+
return {
31+
name: 'shorten-module-paths',
32+
setup(build) {
33+
build.onEnd(async result => {
34+
if (!result.outputFiles && result.metafile) {
35+
// Dynamic imports to avoid adding to production dependencies
36+
const fs = await import('node:fs/promises')
37+
const { parse } = await import('@babel/parser')
38+
const MagicString = (await import('magic-string')).default
39+
40+
const outputs = Object.keys(result.metafile.outputs).filter(f =>
41+
f.endsWith('.js'),
42+
)
43+
44+
for (const outputPath of outputs) {
45+
// eslint-disable-next-line no-await-in-loop
46+
const content = await fs.readFile(outputPath, 'utf8')
47+
const magicString = new MagicString(content)
48+
49+
// Track module paths and their shortened versions
50+
// Map<originalPath, shortenedPath>
51+
const pathMap = new Map()
52+
// Track shortened paths to detect conflicts
53+
// Map<shortenedPath, originalPath>
54+
const conflictDetector = new Map()
55+
56+
/**
57+
* Shorten a module path and detect conflicts.
58+
*/
59+
// eslint-disable-next-line unicorn/consistent-function-scoping
60+
const shortenPath = longPath => {
61+
if (pathMap.has(longPath)) {
62+
return pathMap.get(longPath)
63+
}
64+
65+
let shortPath = longPath
66+
67+
// Handle pnpm scoped packages
68+
// node_modules/.pnpm/@scope+pkg@version/node_modules/@scope/pkg/dist/file.js
69+
// -> @scope/pkg/dist/file.js
70+
const scopedPnpmMatch = longPath.match(
71+
/node_modules\/\.pnpm\/@([^+/]+)\+([^@/]+)@[^/]+\/node_modules\/(@[^/]+\/[^/]+)\/(.+)/,
72+
)
73+
if (scopedPnpmMatch) {
74+
const [, _scope, _pkg, packageName, subpath] = scopedPnpmMatch
75+
shortPath = `${packageName}/${subpath}`
76+
} else {
77+
// Handle pnpm non-scoped packages
78+
// node_modules/.pnpm/pkg@version/node_modules/pkg/dist/file.js
79+
// -> pkg/dist/file.js
80+
const pnpmMatch = longPath.match(
81+
/node_modules\/\.pnpm\/([^@/]+)@[^/]+\/node_modules\/([^/]+)\/(.+)/,
82+
)
83+
if (pnpmMatch) {
84+
const [, _pkgName, packageName, subpath] = pnpmMatch
85+
shortPath = `${packageName}/${subpath}`
86+
}
87+
}
88+
89+
// Detect conflicts
90+
if (conflictDetector.has(shortPath)) {
91+
const existingPath = conflictDetector.get(shortPath)
92+
if (existingPath !== longPath) {
93+
// Conflict detected - keep original path
94+
console.warn(
95+
`⚠ Path conflict detected:\n "${shortPath}"\n Maps to: "${existingPath}"\n Also from: "${longPath}"\n Keeping original paths to avoid conflict.`,
96+
)
97+
shortPath = longPath
98+
}
99+
} else {
100+
conflictDetector.set(shortPath, longPath)
101+
}
102+
103+
pathMap.set(longPath, shortPath)
104+
return shortPath
105+
}
106+
107+
// Parse AST to find all string literals containing module paths
108+
try {
109+
const ast = parse(content, {
110+
sourceType: 'module',
111+
plugins: [],
112+
})
113+
114+
// Walk through all comments (esbuild puts module paths in comments)
115+
for (const comment of ast.comments || []) {
116+
if (
117+
comment.type === 'CommentLine' &&
118+
comment.value.includes('node_modules')
119+
) {
120+
const originalPath = comment.value.trim()
121+
const shortPath = shortenPath(originalPath)
122+
123+
if (shortPath !== originalPath) {
124+
// Replace in comment
125+
const commentStart = comment.start
126+
const commentEnd = comment.end
127+
magicString.overwrite(
128+
commentStart,
129+
commentEnd,
130+
`// ${shortPath}`,
131+
)
132+
}
133+
}
134+
}
135+
136+
// Walk through all string literals in __commonJS calls
137+
const walk = node => {
138+
if (!node || typeof node !== 'object') {
139+
return
140+
}
141+
142+
// Check for string literals containing node_modules paths
143+
if (
144+
node.type === 'StringLiteral' &&
145+
node.value &&
146+
node.value.includes('node_modules')
147+
) {
148+
const originalPath = node.value
149+
const shortPath = shortenPath(originalPath)
150+
151+
if (shortPath !== originalPath) {
152+
// Replace the string content (keep quotes)
153+
magicString.overwrite(
154+
node.start + 1,
155+
node.end - 1,
156+
shortPath,
157+
)
158+
}
159+
}
160+
161+
// Recursively walk all properties
162+
for (const key of Object.keys(node)) {
163+
if (key === 'start' || key === 'end' || key === 'loc') {
164+
continue
165+
}
166+
const value = node[key]
167+
if (Array.isArray(value)) {
168+
for (const item of value) {
169+
walk(item)
170+
}
171+
} else {
172+
walk(value)
173+
}
174+
}
175+
}
176+
177+
walk(ast.program)
178+
179+
// Write the modified content
180+
// eslint-disable-next-line no-await-in-loop
181+
await fs.writeFile(outputPath, magicString.toString(), 'utf8')
182+
} catch (error) {
183+
console.error(
184+
`Failed to shorten paths in ${outputPath}:`,
185+
error.message,
186+
)
187+
// Continue without failing the build
188+
}
189+
}
190+
}
191+
})
192+
},
193+
}
194+
}
195+
196+
/**
197+
* Plugin to resolve internal path aliases (#lib/*, #constants/*, etc.) to relative paths
198+
*/
199+
function createPathAliasPlugin() {
200+
return {
201+
name: 'internal-path-aliases',
202+
setup(build) {
203+
// Map of path aliases to their actual directories
204+
const pathAliases = {
205+
'#lib/': srcPath,
206+
'#constants/': path.join(srcPath, 'constants'),
207+
'#env/': path.join(srcPath, 'env'),
208+
'#packages/': path.join(srcPath, 'packages'),
209+
'#utils/': path.join(srcPath, 'utils'),
210+
'#types': path.join(srcPath, 'types'),
211+
}
212+
213+
// Intercept imports for path aliases
214+
for (const [alias, basePath] of Object.entries(pathAliases)) {
215+
const isExact = !alias.endsWith('/')
216+
const filter = isExact
217+
? new RegExp(`^${alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`)
218+
: new RegExp(`^${alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)
219+
220+
build.onResolve({ filter }, args => {
221+
// Calculate the subpath after the alias
222+
const subpath = isExact ? '' : args.path.slice(alias.length)
223+
const targetPath = subpath ? path.join(basePath, subpath) : basePath
224+
225+
// Calculate relative path from the importing file to the target
226+
const importer = args.importer || srcPath
227+
const importerDir = path.dirname(importer)
228+
let relativePath = path.relative(importerDir, targetPath)
229+
230+
// Ensure relative paths start with ./ or ../
231+
if (!relativePath.startsWith('.')) {
232+
relativePath = `./${relativePath}`
233+
}
234+
235+
// Normalize to forward slashes for consistency
236+
relativePath = relativePath.replace(/\\/g, '/')
237+
238+
return { path: relativePath, external: true }
239+
})
240+
}
241+
},
242+
}
243+
}
244+
245+
// Build configuration for CommonJS output
246+
export const buildConfig = {
247+
entryPoints,
248+
outdir: distPath,
249+
outbase: srcPath,
250+
// Don't bundle - library pattern (each file separate).
251+
bundle: false,
252+
format: 'cjs',
253+
platform: 'node',
254+
target: 'node18',
255+
// Enable source maps for coverage (set COVERAGE=true env var)
256+
sourcemap: envAsBoolean(process.env.COVERAGE),
257+
// Don't minify - this is a library and minification breaks ESM/CJS interop.
258+
minify: false,
259+
// Tree-shaking optimization.
260+
treeShaking: true,
261+
metafile: true,
262+
logLevel: 'info',
263+
264+
// Use plugins for path shortening and aliases
265+
plugins: [createPathShorteningPlugin(), createPathAliasPlugin()].filter(
266+
Boolean,
267+
),
268+
269+
// Note: Cannot use "external" with bundle: false.
270+
// esbuild automatically treats all imports as external when not bundling.
271+
272+
// Define constants for optimization
273+
define: {
274+
'process.env.NODE_ENV': JSON.stringify(
275+
process.env.NODE_ENV || 'production',
276+
),
277+
},
278+
279+
// Banner for generated code
280+
banner: {
281+
js: '"use strict";\n/* Socket Lib - Built with esbuild */',
282+
},
283+
}
284+
285+
// Watch configuration for development with incremental builds
286+
export const watchConfig = {
287+
...buildConfig,
288+
minify: false,
289+
sourcemap: 'inline',
290+
logLevel: 'debug',
291+
}
292+
293+
/**
294+
* Analyze build output for size information
295+
*/
296+
export function analyzeMetafile(metafile) {
297+
const outputs = Object.keys(metafile.outputs)
298+
let totalSize = 0
299+
300+
const files = outputs.map(file => {
301+
const output = metafile.outputs[file]
302+
totalSize += output.bytes
303+
return {
304+
name: path.relative(rootPath, file),
305+
size: `${(output.bytes / 1024).toFixed(2)} KB`,
306+
}
307+
})
308+
309+
return {
310+
files,
311+
totalSize: `${(totalSize / 1024).toFixed(2)} KB`,
312+
}
313+
}

0 commit comments

Comments
 (0)