|
1 | 1 | /** |
2 | 2 | * @fileoverview Shared test utilities for npm package testing. |
3 | | - * Provides helpers for setting up isolated test environments for both |
4 | | - * socket-registry packages and local development packages. |
| 3 | + * Provides helpers for setting up isolated test environments. |
5 | 4 | */ |
6 | 5 |
|
7 | 6 | import { existsSync } from 'node:fs' |
8 | 7 | import path from 'node:path' |
9 | | -import { fileURLToPath } from 'node:url' |
10 | 8 |
|
11 | 9 | import constants from '../../scripts/constants.mjs' |
12 | | -import { installPackageForTesting } from '../../scripts/utils/package.mjs' |
13 | 10 | import { |
14 | 11 | readPackageJson, |
| 12 | + isolatePackage as registryIsolatePackage, |
15 | 13 | resolveOriginalPackageName, |
16 | 14 | } from '../../registry/dist/lib/packages.js' |
17 | | - |
18 | | -const __dirname = path.dirname(fileURLToPath(import.meta.url)) |
| 15 | +import { cleanTestScript } from './script-cleaning.mjs' |
| 16 | +import { testRunners } from './test-runners.mjs' |
19 | 17 |
|
20 | 18 | /** |
21 | 19 | * Isolates a package in a temporary test environment. |
22 | 20 | * |
23 | | - * Supports two modes: |
| 21 | + * Supports multiple input types: |
24 | 22 | * 1. Socket registry packages: Pass package name (e.g., '@socketregistry/packageurl-js') |
25 | 23 | * 2. Local development packages: Pass relative or absolute path (e.g., '../../socket-packageurl-js' or '.') |
| 24 | + * 3. npm package specs: Pass any npm-package-arg compatible spec |
26 | 25 | * |
27 | | - * The helper creates an isolated test environment by: |
28 | | - * - Installing the package in a temporary directory |
29 | | - * - Copying package files to node_modules |
30 | | - * - Installing dependencies |
31 | | - * - Preserving test scripts (for registry packages) |
32 | | - * |
33 | | - * @param {string} packageOrPath - Package name or local path to package directory. |
| 26 | + * @param {string} packageSpec - Package name, path, or npm package spec. |
34 | 27 | * @param {object} [options] - Optional configuration. |
35 | | - * @param {string[]} [options.entryPoints] - Array of entry point filenames to load. |
36 | | - * @returns {Promise<{pkgPath: string, modules?: any[]}>} |
| 28 | + * @param {Record<string, string>} [options.imports] - Map of import names to module specifiers (e.g., { PackageURL: './package-url.js' }). |
| 29 | + * @returns {Promise<{exports?: Record<string, any>, tmpdir: string}>} |
37 | 30 | * |
38 | 31 | * @example |
39 | | - * // Test a socket-registry package |
40 | | - * const { pkgPath } = await isolatePackage('@socketregistry/packageurl-js') |
| 32 | + * const { tmpdir } = await isolatePackage('@socketregistry/packageurl-js') |
41 | 33 | * |
42 | 34 | * @example |
43 | | - * // Test a local development package |
44 | | - * const { pkgPath } = await isolatePackage('../../socket-packageurl-js') |
| 35 | + * const { tmpdir } = await isolatePackage('../../socket-packageurl-js') |
45 | 36 | * |
46 | 37 | * @example |
47 | | - * // Load multiple entry points |
48 | | - * const { pkgPath, modules } = await isolatePackage('../../socket-packageurl-js', { |
49 | | - * entryPoints: ['package-url.js', 'url-converter.js'] |
| 38 | + * const { tmpdir, exports } = await isolatePackage('packageurl-js@1.0.0', { |
| 39 | + * imports: { PackageURL: './package-url.js', convert: './url-converter.js' } |
50 | 40 | * }) |
| 41 | + * // exports.PackageURL, exports.convert |
51 | 42 | */ |
52 | | -async function isolatePackage(packageOrPath, options = {}) { |
53 | | - const { entryPoints } = options |
54 | | - |
55 | | - // Determine if this is a path or package name |
56 | | - const isPath = packageOrPath.startsWith('.') || path.isAbsolute(packageOrPath) |
| 43 | +async function isolatePackage(packageSpec, options = {}) { |
| 44 | + const { imports } = options |
57 | 45 |
|
| 46 | + let resolvedSpec = packageSpec |
58 | 47 | let sourcePath |
59 | | - let packageName |
60 | | - let versionSpec |
61 | | - |
62 | | - if (isPath) { |
63 | | - // Local development package |
64 | | - sourcePath = path.resolve(__dirname, '..', '..', packageOrPath) |
65 | | - |
66 | | - // Read package.json to get the name |
67 | | - const pkgJson = await readPackageJson(sourcePath, { normalize: true }) |
68 | | - packageName = pkgJson.name |
69 | | - } else { |
70 | | - // Socket registry package |
71 | | - const socketPkgName = packageOrPath |
| 48 | + let hasSourcePath = false |
| 49 | + |
| 50 | + // Check if this is a Socket registry package. |
| 51 | + if ( |
| 52 | + packageSpec.startsWith('@socketregistry/') && |
| 53 | + !packageSpec.includes('@', 1) |
| 54 | + ) { |
| 55 | + const socketPkgName = packageSpec |
72 | 56 | sourcePath = path.join(constants.npmPackagesPath, socketPkgName) |
73 | 57 |
|
74 | 58 | if (!existsSync(sourcePath)) { |
75 | 59 | throw new Error(`No Socket override found for ${socketPkgName}`) |
76 | 60 | } |
77 | 61 |
|
78 | | - // Resolve to original npm package name |
79 | | - packageName = resolveOriginalPackageName(socketPkgName) |
| 62 | + // Resolve to original npm package name. |
| 63 | + const packageName = resolveOriginalPackageName(socketPkgName) |
80 | 64 |
|
81 | | - // Get version spec from test/npm/package.json |
| 65 | + // Get version spec from test/npm/package.json. |
82 | 66 | const testPkgJson = await readPackageJson(constants.testNpmPkgJsonPath, { |
83 | 67 | normalize: true, |
84 | 68 | }) |
85 | | - versionSpec = testPkgJson.devDependencies?.[packageName] |
| 69 | + const spec = testPkgJson.devDependencies?.[packageName] |
86 | 70 |
|
87 | | - if (!versionSpec) { |
| 71 | + if (!spec) { |
88 | 72 | throw new Error(`${packageName} not in devDependencies`) |
89 | 73 | } |
90 | | - } |
91 | 74 |
|
92 | | - const result = await installPackageForTesting(sourcePath, packageName, { |
93 | | - versionSpec, |
94 | | - }) |
95 | | - |
96 | | - if (!result.installed) { |
97 | | - throw new Error(`Failed to install package: ${result.reason}`) |
| 75 | + resolvedSpec = `${packageName}@${spec}` |
| 76 | + hasSourcePath = true |
98 | 77 | } |
99 | 78 |
|
100 | | - const pkgPath = result.packagePath |
101 | | - |
102 | | - if (entryPoints && entryPoints.length > 0) { |
103 | | - const modules = entryPoints.map(entryPoint => |
104 | | - require(path.join(pkgPath, entryPoint)), |
105 | | - ) |
106 | | - return { pkgPath, modules } |
107 | | - } |
| 79 | + const result = await registryIsolatePackage(resolvedSpec, { |
| 80 | + imports, |
| 81 | + onPackageJson: async pkgJson => { |
| 82 | + // Preserve test scripts for registry packages. |
| 83 | + if (hasSourcePath) { |
| 84 | + const originalScripts = pkgJson.scripts |
| 85 | + |
| 86 | + if (originalScripts) { |
| 87 | + pkgJson.scripts = pkgJson.scripts || {} |
| 88 | + |
| 89 | + const additionalTestRunners = [ |
| 90 | + ...testRunners, |
| 91 | + 'test:stock', |
| 92 | + 'test:all', |
| 93 | + ] |
| 94 | + let actualTestScript = additionalTestRunners.find( |
| 95 | + runner => originalScripts[runner], |
| 96 | + ) |
| 97 | + |
| 98 | + if (!actualTestScript && originalScripts.test) { |
| 99 | + const testMatch = originalScripts.test.match(/npm run ([-:\w]+)/) |
| 100 | + if (testMatch && originalScripts[testMatch[1]]) { |
| 101 | + actualTestScript = testMatch[1] |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + if (actualTestScript && originalScripts[actualTestScript]) { |
| 106 | + pkgJson.scripts.test = cleanTestScript( |
| 107 | + originalScripts[actualTestScript], |
| 108 | + ) |
| 109 | + if (actualTestScript !== 'test') { |
| 110 | + pkgJson.scripts[actualTestScript] = cleanTestScript( |
| 111 | + originalScripts[actualTestScript], |
| 112 | + ) |
| 113 | + } |
| 114 | + } else if (originalScripts.test) { |
| 115 | + pkgJson.scripts.test = cleanTestScript(originalScripts.test) |
| 116 | + } |
| 117 | + |
| 118 | + for (const { 0: key, 1: value } of Object.entries(originalScripts)) { |
| 119 | + if ( |
| 120 | + (key.startsWith('test:') || key.startsWith('tests')) && |
| 121 | + !pkgJson.scripts[key] |
| 122 | + ) { |
| 123 | + pkgJson.scripts[key] = cleanTestScript(value) |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + return pkgJson |
| 130 | + }, |
| 131 | + sourcePath, |
| 132 | + }) |
108 | 133 |
|
109 | | - return { pkgPath } |
| 134 | + return result |
110 | 135 | } |
111 | 136 |
|
112 | 137 | export { isolatePackage } |
0 commit comments