diff --git a/src/main.ts b/src/main.ts index 68d14e3a..616ffbda 100644 --- a/src/main.ts +++ b/src/main.ts @@ -70,6 +70,8 @@ module.exports = function (argv: string[]): void { .option('--tree', 'Prints the files in a tree format', false) .option('--yarn', 'Use yarn instead of npm (default inferred from presence of yarn.lock or .yarnrc)') .option('--no-yarn', 'Use npm instead of yarn (default inferred from absence of yarn.lock or .yarnrc)') + .option('--bun', "Use bun instead of npm (default inferred from presence of bun.lock or bun.lockb)") + .option('--no-bun', "Use npm instead of bun (default inferred from absence of bun.lock or bun.lockb)") .option( '--packagedDependencies ', 'Select packages that should be published only (includes dependencies)', @@ -82,8 +84,8 @@ module.exports = function (argv: string[]): void { .option('--no-dependencies', 'Disable dependency detection via npm or yarn', undefined) .option('--readme-path ', 'Path to README file (defaults to README.md)') .option('--follow-symlinks', 'Recurse into symlinked directories instead of treating them as files') - .action(({ tree, yarn, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks }) => - main(ls({ tree, useYarn: yarn, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks })) + .action(({ tree, yarn, bun, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks }) => + main(ls({ tree, useYarn: yarn, useBun: bun, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks })) ); program @@ -114,6 +116,8 @@ module.exports = function (argv: string[]): void { .option('--baseImagesUrl ', 'Prepend all relative image links in README.md with the specified URL.') .option('--yarn', 'Use yarn instead of npm (default inferred from presence of yarn.lock or .yarnrc)') .option('--no-yarn', 'Use npm instead of yarn (default inferred from absence of yarn.lock or .yarnrc)') + .option('--bun', "Use bun instead of npm (default inferred from presence of bun.lock or bun.lockb)") + .option('--no-bun', "Use npm instead of bun (default inferred from absence of bun.lock or bun.lockb)") .option('--ignoreFile ', 'Indicate alternative .vscodeignore') .option('--no-gitHubIssueLinking', 'Disable automatic expansion of GitHub-style issue syntax into links') .option('--no-gitLabIssueLinking', 'Disable automatic expansion of GitLab-style issue syntax into links') @@ -148,6 +152,7 @@ module.exports = function (argv: string[]): void { baseContentUrl, baseImagesUrl, yarn, + bun, ignoreFile, gitHubIssueLinking, gitLabIssueLinking, @@ -181,6 +186,7 @@ module.exports = function (argv: string[]): void { baseContentUrl, baseImagesUrl, useYarn: yarn, + useBun: bun, ignoreFile, gitHubIssueLinking, gitLabIssueLinking, @@ -235,6 +241,8 @@ module.exports = function (argv: string[]): void { .option('--baseImagesUrl ', 'Prepend all relative image links in README.md with the specified URL.') .option('--yarn', 'Use yarn instead of npm (default inferred from presence of yarn.lock or .yarnrc)') .option('--no-yarn', 'Use npm instead of yarn (default inferred from absence of yarn.lock or .yarnrc)') + .option('--bun', "Use bun instead of npm (default inferred from presence of bun.lock or bun.lockb)") + .option('--no-bun', "Use npm instead of bun (default inferred from absence of bun.lock or bun.lockb)") .option('--no-verify', 'Allow all proposed APIs (deprecated: use --allow-all-proposed-apis instead)') .addOption(new Option('--noVerify', 'Allow all proposed APIs (deprecated: use --allow-all-proposed-apis instead)').hideHelp(true)) .option('--allow-proposed-apis ', 'Allow specific proposed APIs') @@ -275,6 +283,7 @@ module.exports = function (argv: string[]): void { baseContentUrl, baseImagesUrl, yarn, + bun, verify, noVerify, allowProposedApis, @@ -315,6 +324,7 @@ module.exports = function (argv: string[]): void { baseContentUrl, baseImagesUrl, useYarn: yarn, + useBun: bun, noVerify: noVerify || !verify, allowProposedApis, allowAllProposedApis, diff --git a/src/npm.ts b/src/npm.ts index 6ea6de5d..ddb8ba21 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -37,7 +37,6 @@ function exec( disposeCancellationListener(); disposeCancellationListener = null; } - if (err) { return e(err); } @@ -51,12 +50,12 @@ function exec( }); } }); + } async function checkNPM(cancellationToken?: CancellationToken): Promise { const { stdout } = await exec('npm -v', {}, cancellationToken); const version = stdout.trim(); - if (/^3\.7\.[0123]$/.test(version)) { throw new Error(`npm@${version} doesn't work with vsce. Please update npm: npm install -g npm`); } @@ -64,8 +63,7 @@ async function checkNPM(cancellationToken?: CancellationToken): Promise { function getNpmDependencies(cwd: string): Promise { return checkNPM() - .then(() => - exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 }) + .then(() => exec('npm list --production --parseable --depth=99999 --loglevel=error', { cwd, maxBuffer: 5000 * 1024 }) ) .then(({ stdout }) => stdout.split(/[\r\n]/).filter(dir => path.isAbsolute(dir))); } @@ -85,27 +83,21 @@ function asYarnDependency(prefix: string, tree: YarnTreeNode, prune: boolean): Y if (prune && /@[\^~]/.test(tree.name)) { return null; } - let name: string; - try { const parseResult = parseSemver(tree.name); name = parseResult.name; } catch (err) { name = tree.name.replace(/^([^@+])@.*$/, '$1'); } - const dependencyPath = path.join(prefix, name); const children: YarnDependency[] = []; - for (const child of tree.children || []) { const dep = asYarnDependency(path.join(prefix, name, 'node_modules'), child, prune); - if (dep) { children.push(dep); } } - return { name, path: dependencyPath, children }; } @@ -128,7 +120,6 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st return result; } })(); - const reached = new (class { values: YarnDependency[] = []; add(dep: YarnDependency): boolean { @@ -139,7 +130,6 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st return false; } })(); - const visit = (name: string) => { let dep = index.find(name); if (!reached.add(dep)) { @@ -155,46 +145,57 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st } async function getYarnProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise { - const raw = await new Promise((c, e) => - cp.exec( - 'yarn list --prod --json', - { cwd, encoding: 'utf8', env: { DISABLE_V8_COMPILE_CACHE: "1", ...process.env }, maxBuffer: 5000 * 1024 }, - (err, stdout) => (err ? e(err) : c(stdout)) - ) - ); + const raw = await new Promise((c, e) => cp.exec( + 'yarn list --prod --json', + { cwd, encoding: 'utf8', env: { DISABLE_V8_COMPILE_CACHE: "1", ...process.env }, maxBuffer: 5000 * 1024 }, + (err, stdout) => (err ? e(err) : c(stdout)) + )); const match = /^{"type":"tree".*$/m.exec(raw); - if (!match || match.length !== 1) { throw new Error('Could not parse result of `yarn list --json`'); } - const usingPackagedDependencies = Array.isArray(packagedDependencies); const trees = JSON.parse(match[0]).data.trees as YarnTreeNode[]; - let result = trees .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree, !usingPackagedDependencies)) .filter(nonnull); - if (usingPackagedDependencies) { result = selectYarnDependencies(result, packagedDependencies!); } - return result; } async function getYarnDependencies(cwd: string, packagedDependencies?: string[]): Promise { const result = new Set([cwd]); - const deps = await getYarnProductionDependencies(cwd, packagedDependencies); const flatten = (dep: YarnDependency) => { result.add(dep.path); dep.children.forEach(flatten); }; deps.forEach(flatten); - return [...result]; } +export async function detectBun(cwd: string): Promise { + for (const name of ['bun.lock', 'bun.lockb']) { + if (await exists(path.join(cwd, name))) { + if (!process.env['VSCE_TESTS']) { + log.info( + `Detected presence of ${name}. Using 'bun' instead of 'npm' (to override this pass '--no-bun' on the command line).` + ); + } + return true; + } + } + return false; +} + +export async function getBunDependencies(cwd: string): Promise { + // Bun doesn't have a direct equivalent to 'npm list --production' + // We simply return the current directory since bun.lock handles dependencies + return [cwd]; +} + export async function detectYarn(cwd: string): Promise { for (const name of ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']) { if (await exists(path.join(cwd, name))) { @@ -211,11 +212,13 @@ export async function detectYarn(cwd: string): Promise { export async function getDependencies( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + dependencies: 'npm' | 'yarn' | 'bun' | 'none' | undefined, packagedDependencies?: string[] ): Promise { if (dependencies === 'none') { return [cwd]; + } else if (dependencies === 'bun' || (dependencies === undefined && (await detectBun(cwd)))) { + return await getBunDependencies(cwd); } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd)))) { return await getYarnDependencies(cwd, packagedDependencies); } else { diff --git a/src/package.ts b/src/package.ts index 2aad8818..e3553d7a 100644 --- a/src/package.ts +++ b/src/package.ts @@ -23,7 +23,7 @@ import { validatePublisher, validateExtensionDependencies, } from './validation'; -import { detectYarn, getDependencies } from './npm'; +import { detectBun, detectYarn, getDependencies } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -150,6 +150,10 @@ export interface IPackageOptions { * Should use Yarn instead of NPM. */ readonly useYarn?: boolean; + /** + * Should use Bun instead of NPM. + */ + readonly useBun?: boolean; readonly dependencyEntryPoints?: string[]; readonly ignoreFile?: string; readonly gitHubIssueLinking?: boolean; @@ -1667,7 +1671,7 @@ const defaultIgnore = [ async function collectAllFiles( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + dependencies: 'npm' | 'yarn' | 'bun' | 'none' | undefined, dependencyEntryPoints?: string[], followSymlinks: boolean = true ): Promise { @@ -1681,11 +1685,15 @@ async function collectAllFiles( return Promise.all(promises).then(util.flatten); } -function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'none' | undefined { +function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'bun' | 'none' | undefined { if (options.dependencies === false) { return 'none'; } + if (options.useBun) { + return 'bun'; + } + switch (options.useYarn) { case true: return 'yarn'; @@ -1698,7 +1706,7 @@ function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'none function collectFiles( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + dependencies: 'npm' | 'yarn' | 'bun' | 'none' | undefined, dependencyEntryPoints?: string[], ignoreFile?: string, manifestFileIncludes?: string[], @@ -1871,16 +1879,20 @@ function getDefaultPackageName(manifest: ManifestPackage, options: IPackageOptio return `${manifest.name}-${version}.vsix`; } -export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn?: boolean): Promise { +export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn?: boolean, useBun?: boolean): Promise { if (!manifest.scripts || !manifest.scripts['vscode:prepublish']) { return; } - if (useYarn === undefined) { + if (useBun === undefined) { + useBun = await detectBun(cwd); + } + + if (useYarn === undefined && !useBun) { useYarn = await detectYarn(cwd); } - const tool = useYarn ? 'yarn' : 'npm'; + const tool = useBun ? 'bun' : (useYarn ? 'yarn' : 'npm'); const prepublish = `${tool} run vscode:prepublish`; console.log(`Executing prepublish script '${prepublish}'...`); @@ -1986,7 +1998,7 @@ export async function packageCommand(options: IPackageOptions = {}): Promise { + const cwd = fixture('devDependencies'); + const manifest = await readManifest(cwd); + const files = await collect(manifest, { cwd, useBun: true }); + // Bun returns only cwd, no node_modules included + for (const file of files) { + assert.ok(!/\bnode_modules\b/i.test(file.path)); + } + }); + it('should handle relative icon paths', async function () { const cwd = fixture('icon'); const manifest = await readManifest(cwd);