-
-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add runtime fallback and webcontainer support #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "napi-postinstall": minor | ||
| --- | ||
|
|
||
| feat: add runtime fallback and webcontainer support |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| import { execFileSync } from 'node:child_process' | ||
| import * as fs from 'node:fs' | ||
| import * as os from 'node:os' | ||
| import * as path from 'node:path' | ||
|
|
||
| import { WASM32_WASI } from './constants.js' | ||
| import { errorMessage, getNapiInfoFromPackageJson } from './helpers.js' | ||
| import type { PackageJson } from './types.js' | ||
|
|
||
| const EXECUTORS = { | ||
| npm: 'npx', | ||
| pnpm: 'pnpm', | ||
| yarn: 'yarn', | ||
| bun: 'bun', | ||
| deno: (args: string[]) => ['deno', 'run', `npm:${args[0]}`, ...args.slice(1)], | ||
| } | ||
|
|
||
| function constructCommand( | ||
| value: string[] | string | ((args: string[]) => string[]), | ||
| args: string[], | ||
| ) { | ||
| const list = | ||
| typeof value === 'function' | ||
| ? value(args) | ||
| : // eslint-disable-next-line unicorn-x/prefer-spread | ||
| ([] as string[]).concat(value, args) | ||
| return { | ||
| command: list[0], | ||
| args: list.slice(1), | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fallback for webcontainer and docker environments like | ||
| * @see https://github.com/un-ts/eslint-plugin-import-x/issues/337. | ||
| * | ||
| * @param packageJsonPath The absolute path to the package.json file. | ||
| * @param checkVersion Whether to check version matching | ||
| */ | ||
| function fallback<T = unknown>( | ||
| packageJsonPath: string, | ||
| checkVersion?: boolean, | ||
| ) { | ||
| const packageJson = require(packageJsonPath) as PackageJson | ||
|
|
||
| const { name, version: pkgVersion, optionalDependencies } = packageJson | ||
|
|
||
| const { napi, version = pkgVersion } = getNapiInfoFromPackageJson( | ||
| packageJson, | ||
| checkVersion, | ||
| ) | ||
|
|
||
| if (checkVersion && pkgVersion !== version) { | ||
| throw new Error( | ||
| errorMessage( | ||
| `Inconsistent package versions found for \`${name}\` v${pkgVersion} vs \`${napi.packageName}\` v${version}.`, | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| if (process.versions.webcontainer) { | ||
| const bindingPkgName = `${napi.packageName}-${WASM32_WASI}` | ||
|
|
||
| if (!optionalDependencies?.[bindingPkgName]) { | ||
| throw new Error( | ||
| errorMessage( | ||
| `\`${WASM32_WASI}\` target is unavailable for \`${name}\` v${version}`, | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| const baseDir = path.resolve(os.tmpdir(), `${name}-${version}`) | ||
|
|
||
| const bindingEntry = path.resolve( | ||
| baseDir, | ||
| `node_modules/${bindingPkgName}/${napi.binaryName}.wasi.cjs`, | ||
| ) | ||
|
|
||
| if (!fs.existsSync(bindingEntry)) { | ||
| fs.rmSync(baseDir, { recursive: true, force: true }) | ||
| fs.mkdirSync(baseDir, { recursive: true }) | ||
|
|
||
| const bindingPkg = `${bindingPkgName}@${version}` | ||
|
|
||
| console.log( | ||
| errorMessage(`Downloading \`${bindingPkg}\` on WebContainer...`), | ||
| ) | ||
|
|
||
| execFileSync('pnpm', ['i', bindingPkg], { | ||
|
Check failure on line 89 in src/fallback.ts
|
||
| cwd: baseDir, | ||
| stdio: 'inherit', | ||
| }) | ||
| } | ||
|
|
||
| return require(bindingEntry) as T | ||
| } | ||
|
|
||
| const userAgent = ((process.env.npm_config_user_agent || '').split('/')[0] || | ||
| 'npm') as keyof typeof EXECUTORS | ||
|
|
||
| const executor = EXECUTORS[userAgent] | ||
|
|
||
| if (!executor) { | ||
| throw new Error( | ||
| errorMessage( | ||
| `Unsupported package manager: ${userAgent}. Supported managers are: ${Object.keys( | ||
| EXECUTORS, | ||
| ).join(', ')}.`, | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| const { command, args } = constructCommand(executor, [ | ||
| 'napi-postinstall', | ||
| name, | ||
| version, | ||
| checkVersion ? '1' : '0', | ||
| ]) | ||
|
|
||
| const pkgDir = path.dirname(packageJsonPath) | ||
|
|
||
| execFileSync(command, args, { | ||
|
Check failure on line 122 in src/fallback.ts
|
||
| cwd: pkgDir, | ||
| stdio: 'inherit', | ||
| }) | ||
|
|
||
| // eslint-disable-next-line unicorn-x/prefer-string-replace-all | ||
| process.env[`SKIP_${name.replace(/-/g, '_').toUpperCase()}_FALLBACK`] = '1' | ||
|
|
||
| const PKG_RESOLVED_PATH = require.resolve(pkgDir) | ||
|
|
||
| delete require.cache[PKG_RESOLVED_PATH] | ||
|
|
||
| return require(pkgDir) as T | ||
| } | ||
|
|
||
| export = fallback | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import unrsResolver = require('unrs-resolver') | ||
|
|
||
| // @ts-expect-error -- don't use `import fallback = require('napi-postinstall/fallback')` due to vitest code coverage | ||
| import fallback_ from 'napi-postinstall/fallback' | ||
|
|
||
| const fallback = fallback_ as typeof import('napi-postinstall/fallback') | ||
|
|
||
| describe('fallback', () => { | ||
| afterEach(() => { | ||
| delete process.env.SKIP_UNRS_RESOLVER_FALLBACK | ||
| delete process.versions.webcontainer | ||
| }) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| it('should resolve napi package successfully and set skip env after processing', () => { | ||
| expect(fallback(require.resolve('unrs-resolver/package.json'))).toBe( | ||
| unrsResolver, | ||
| ) | ||
| expect(process.env.SKIP_UNRS_RESOLVER_FALLBACK).toBe('1') | ||
| }) | ||
|
|
||
| it('should support webcontainer', () => { | ||
| process.versions.webcontainer = '1' | ||
| const resolved = fallback<typeof unrsResolver>( | ||
| require.resolve('unrs-resolver/package.json'), | ||
| ) | ||
| expect(resolved).not.toBe(unrsResolver) | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| expect(typeof resolved.sync).toBe('function') | ||
| }) | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.