From 9cc5a27c7da2f89e0516f8fb8a95c57984e51857 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sat, 4 Apr 2026 23:57:25 +0200 Subject: [PATCH 01/17] the actual fix --- src/package.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/package.ts b/src/package.ts index 91f315b5..d78ed7b5 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1673,12 +1673,12 @@ async function collectAllFiles( ): Promise { const deps = await getDependencies(cwd, dependencies, dependencyEntryPoints); const promises = deps.map(dep => - glob('**', { cwd: dep, nodir: true, follow: followSymlinks, dot: true, ignore: 'node_modules/**' }).then(files => + glob('**', { cwd: dep, nodir: true, follow: followSymlinks, dot: true, ignore: ['node_modules/**', ".git/**"] }).then(files => files.map(f => path.relative(cwd, path.join(dep, f))).map(f => f.replace(/\\/g, '/')) ) ); - - return Promise.all(promises).then(util.flatten); + const files = (await Promise.all(promises)).flat(); + return files; } function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'none' | undefined { From d73369530a2337b74e16f3609da0379e4e0e02c7 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 01:25:25 +0200 Subject: [PATCH 02/17] done --- src/manifest.ts | 5 ++ src/npm.ts | 81 +++++++++++++++++++++++++++--- src/package.ts | 130 +++++++++++++++++++++++------------------------- 3 files changed, 141 insertions(+), 75 deletions(-) diff --git a/src/manifest.ts b/src/manifest.ts index cb2d8234..07a4cf39 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -108,6 +108,11 @@ export interface ManifestPackage { private?: boolean; pricing?: string; files?: string[]; + packageManager?: string; + devEngines?: { packageManager?: { + name?: string; version?: string; + onFail: "error" | "warn" | "ignore" + } }; // vsce vsce?: any; diff --git a/src/npm.ts b/src/npm.ts index 6ea6de5d..de95140b 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -3,6 +3,7 @@ import * as fs from 'fs'; import * as cp from 'child_process'; import parseSemver from 'parse-semver'; import { CancellationToken, log, nonnull } from './util'; +import type { ManifestPackage } from './manifest'; const exists = (file: string) => fs.promises.stat(file).then( @@ -195,13 +196,52 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[]) return [...result]; } -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))) { +import * as semver from 'semver'; + +export async function isNonNpmOrModernYarn(cwd: string, manifest: ManifestPackage): Promise { + if (await detectPnpm(cwd, manifest) || await detectBun(cwd, manifest) || await detectVlt(cwd, manifest)) { + return true; + } + + const isYarn = await detectYarn(cwd, manifest); + if (isYarn) { + // Try to extract version from manifest (e.g., "yarn@3.6.4") + const pmString = manifest?.devEngines?.packageManager?.version || manifest?.packageManager; + + if (pmString && pmString.startsWith('yarn@')) { + const version = pmString.split('@')[1]; + if (version && semver.valid(version)) { + return semver.gte(version, '2.0.0'); // Returns true if Yarn 2, 3, or 4 + } + } + + // Fallback: If it's Yarn but we can't find a version, + // we assume it's modern if a .yarn directory or .pnp.cjs exists + if (await exists(path.join(cwd, '.pnp.cjs')) || await exists(path.join(cwd, '.yarn/releases'))) { + return true; + } + } + + return false; // It's likely standard npm or classic yarn +} + +async function detectPackageManager( + cwd: string, + manifest: ManifestPackage, + name: string, + lockfiles: string[] +): Promise { + if (manifest?.devEngines?.packageManager?.name === name || manifest?.packageManager?.startsWith(`${name}@`)) { + return true; + } + + for (const filename of lockfiles) { + if (await exists(path.join(cwd, filename))) { if (!process.env['VSCE_TESTS']) { - log.info( - `Detected presence of ${name}. Using 'yarn' instead of 'npm' (to override this pass '--no-yarn' on the command line).` - ); + const suffix = name === 'yarn' + ? " instead of 'npm' (to override this pass '--no-yarn' on the command line)." + : ' logic.'; + log.info(`Detected presence of ${filename}. Using '${name}'${suffix}`); } return true; } @@ -209,14 +249,41 @@ export async function detectYarn(cwd: string): Promise { return false; } +export const detectPnpm = (cwd: string, pkg: ManifestPackage) => + detectPackageManager(cwd, pkg, 'pnpm', ['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.pnpmfile.cjs']); + +export const detectBun = (cwd: string, pkg: ManifestPackage) => + detectPackageManager(cwd, pkg, 'bun', ['bun.lockb', 'bun.lock', 'bunfig.toml']); + +export const detectVlt = (cwd: string, pkg: ManifestPackage) => + detectPackageManager(cwd, pkg, 'vlt', ['vlt-lock.json', '.vltrc']); + +export const detectYarn = (cwd: string, pkg: ManifestPackage) => + detectPackageManager(cwd, pkg, 'yarn', ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']); + +export async function getPrepublishCommand(cwd: string, manifest: ManifestPackage): Promise { + const customCommand = process.env['VSCE_RUN_PREPUBLISH'] || manifest?.vsce?.runPrepublish; + if (customCommand) { + return customCommand; + } + + if (await detectPnpm(cwd, manifest)) return 'pnpm run vscode:prepublish'; + if (await detectBun(cwd, manifest)) return 'bun run vscode:prepublish'; + if (await detectVlt(cwd, manifest)) return 'vlt run vscode:prepublish'; + if (await detectYarn(cwd, manifest)) return 'yarn run vscode:prepublish'; // Yarn usually doesn't need 'run' for scripts + + return 'npm run vscode:prepublish'; +} + export async function getDependencies( cwd: string, dependencies: 'npm' | 'yarn' | 'none' | undefined, + manifest: ManifestPackage, packagedDependencies?: string[] ): Promise { if (dependencies === 'none') { return [cwd]; - } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd)))) { + } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd, manifest)))) { return await getYarnDependencies(cwd, packagedDependencies); } else { return await getNpmDependencies(cwd); diff --git a/src/package.ts b/src/package.ts index d78ed7b5..b40ee3e1 100644 --- a/src/package.ts +++ b/src/package.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { promisify } from 'util'; import * as cp from 'child_process'; import * as yazl from 'yazl'; -import { ExtensionKind, ManifestPackage, UnverifiedManifest } from './manifest'; +import type { ExtensionKind, ManifestPackage, UnverifiedManifest } from './manifest'; import { ITranslations, patchNLS } from './nls'; import * as util from './util'; import { glob } from 'glob'; @@ -23,7 +23,7 @@ import { validatePublisher, validateExtensionDependencies, } from './validation'; -import { detectYarn, getDependencies } from './npm'; +import { detectYarn, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -1667,11 +1667,12 @@ const defaultIgnore = [ async function collectAllFiles( cwd: string, + manifest: ManifestPackage, dependencies: 'npm' | 'yarn' | 'none' | undefined, dependencyEntryPoints?: string[], followSymlinks: boolean = true ): Promise { - const deps = await getDependencies(cwd, dependencies, dependencyEntryPoints); + const deps = await getDependencies(cwd, dependencies, manifest, dependencyEntryPoints); const promises = deps.map(dep => glob('**', { cwd: dep, nodir: true, follow: followSymlinks, dot: true, ignore: ['node_modules/**', ".git/**"] }).then(files => files.map(f => path.relative(cwd, path.join(dep, f))).map(f => f.replace(/\\/g, '/')) @@ -1681,11 +1682,14 @@ async function collectAllFiles( return files; } -function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'none' | undefined { +async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions): Promise<'npm' | 'yarn' | 'none' | undefined> { if (options.dependencies === false) { return 'none'; } + const isUnknown = await isNonNpmOrModernYarn(options.cwd || process.cwd(), manifest); + if (isUnknown) return 'none'; + switch (options.useYarn) { case true: return 'yarn'; @@ -1696,74 +1700,65 @@ function getDependenciesOption(options: IPackageOptions): 'npm' | 'yarn' | 'none } } -function collectFiles( +async function collectFiles( cwd: string, + manifest: ManifestPackage, dependencies: 'npm' | 'yarn' | 'none' | undefined, dependencyEntryPoints?: string[], ignoreFile?: string, - manifestFileIncludes?: string[], readmePath?: string, followSymlinks: boolean = false ): Promise { + const manifestFileIncludes = manifest?.files; readmePath = readmePath ?? 'README.md'; const notIgnored = ['!package.json', `!${readmePath}`]; - return collectAllFiles(cwd, dependencies, dependencyEntryPoints, followSymlinks).then(files => { - files = files.filter(f => !/\r$/m.test(f)); - - return ( - fs.promises - .readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8') - .catch(err => - err.code !== 'ENOENT' ? - Promise.reject(err) : - ignoreFile ? - Promise.reject(err) : - // No .vscodeignore file exists - manifestFileIncludes ? - // include all files in manifestFileIncludes and ignore the rest - Promise.resolve(manifestFileIncludes.map(file => `!${file}`).concat(['**']).join('\n\r')) : - // "files" property not used in package.json - Promise.resolve('') - ) - - // Parse raw ignore by splitting output into lines and filtering out empty lines and comments - .then(rawIgnore => - rawIgnore - .split(/[\n\r]/) - .map(s => s.trim()) - .filter(s => !!s) - .filter(i => !/^\s*#/.test(i)) - ) + const files = (await collectAllFiles(cwd, manifest, dependencies, dependencyEntryPoints, followSymlinks)) + .filter(f => !/\r$/m.test(f)); - // Add '/**' to possible folder names - .then(ignore => [ - ...ignore, - ...ignore.filter(i => !/(^|\/)[^/]*\*[^/]*$/.test(i)).map(i => (/\/$/.test(i) ? `${i}**` : `${i}/**`)), - ]) - - // Combine with default ignore list - .then(ignore => [...defaultIgnore, ...ignore, ...notIgnored]) - - // Split into ignore and negate list - .then(ignore => - ignore.reduce<[string[], string[]]>( - (r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e]]), - [[], []] - ) - ) - .then(r => ({ ignore: r[0], negate: r[1] })) - - // Filter out files - .then(({ ignore, negate }) => - files.filter( - f => - !ignore.some(i => minimatch(f, i, minimatchOptions)) || - negate.some(i => minimatch(f, i.substr(1), minimatchOptions)) - ) - ) - ); - }); + let rawIgnore = ''; + try { + rawIgnore = await fs.promises.readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8'); + } catch (err) { + if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err; + if (ignoreFile) throw err; // No .vscodeignore file exists + // include all files in manifestFileIncludes and ignore the rest + if(manifestFileIncludes) { + rawIgnore = manifestFileIncludes + .map(file => `!${file}`) + .concat(['**']) + .join('\n\r') + } + // otherwise "files" property not used in package.json + } + + // Parse raw ignore by splitting output into lines and filtering out empty lines and comments + let i = rawIgnore + .split(/[\n\r]/) + .map(s => s.trim()) + .filter(s => !!s) + .filter(i => !/^\s*#/.test(i)) + + // Add '/**' to possible folder names + // Combine with default ignore list + i = [ + ...defaultIgnore, + ...i, + ...i.filter(i => !/(^|\/)[^/]*\*[^/]*$/.test(i)).map(i => (/\/$/.test(i) ? `${i}**` : `${i}/**`)), + ...notIgnored, + ] + + // Split into ignore and negate list + const [ignore, negate] = i.reduce<[string[], string[]]>( + (r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e.substring(1)]]), + [[], []] + ) + + return files.filter( + f => + !ignore.some(i => minimatch(f, i, minimatchOptions)) || + negate.some(i => minimatch(f, i, minimatchOptions)) + ); } export function processFiles(processors: IProcessor[], files: IFile[]): Promise { @@ -1809,13 +1804,13 @@ export function createDefaultProcessors(manifest: ManifestPackage, options: IPac ]; } -export function collect(manifest: ManifestPackage, options: IPackageOptions = {}): Promise { +export async function collect(manifest: ManifestPackage, options: IPackageOptions = {}): Promise { const cwd = options.cwd || process.cwd(); const packagedDependencies = options.dependencyEntryPoints || undefined; const ignoreFile = options.ignoreFile || undefined; const processors = createDefaultProcessors(manifest, options); - return collectFiles(cwd, getDependenciesOption(options), packagedDependencies, ignoreFile, manifest.files, options.readmePath, options.followSymlinks).then(fileNames => { + return collectFiles(cwd, manifest, await getDependenciesOption(manifest, options), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { const files = fileNames.map(f => ({ path: util.filePathToVsixPath(f), localPath: path.join(cwd, f) })); return processFiles(processors, files); @@ -1877,18 +1872,17 @@ export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn } if (useYarn === undefined) { - useYarn = await detectYarn(cwd); + useYarn = await detectYarn(cwd, manifest); } - const tool = useYarn ? 'yarn' : 'npm'; - const prepublish = `${tool} run vscode:prepublish`; + const prepublish = await getPrepublishCommand(cwd, manifest); console.log(`Executing prepublish script '${prepublish}'...`); await new Promise((c, e) => { // Use string command to avoid Node.js DEP0190 warning (args + shell: true is deprecated). const child = cp.spawn(prepublish, { cwd, shell: true, stdio: 'inherit' }); - child.on('exit', code => (code === 0 ? c() : e(`${tool} failed with exit code ${code}`))); + child.on('exit', code => (code === 0 ? c() : e(`'${prepublish}' failed with exit code ${code}`))); child.on('error', e); }); } @@ -2023,7 +2017,7 @@ export async function listFiles(options: IListFilesOptions = {}): Promise Date: Sun, 5 Apr 2026 02:48:08 +0200 Subject: [PATCH 03/17] use bun for bump --- src/package.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/package.ts b/src/package.ts index b40ee3e1..b804ba0e 100644 --- a/src/package.ts +++ b/src/package.ts @@ -23,7 +23,7 @@ import { validatePublisher, validateExtensionDependencies, } from './validation'; -import { detectYarn, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; +import { detectBun, detectYarn, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -411,9 +411,10 @@ export async function versionBump(options: IVersionBumpOptions): Promise { } } + const isBun = await detectBun(cwd, manifest) - // call `npm version` to do our dirty work - const args = ['version', options.version]; + // call `npm version` or `bun pm version` to do our dirty work + const args = isBun ? ['pm', 'version', options.version] : ['version', options.version]; const isWindows = process.platform === 'win32'; @@ -426,7 +427,12 @@ export async function versionBump(options: IVersionBumpOptions): Promise { args.push('--no-git-tag-version'); } - const { stdout, stderr } = await promisify(cp.execFile)(isWindows ? 'npm.cmd' : 'npm', args, { cwd, shell: isWindows /* https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2 */ }); + const bin = isBun ? 'bun' + : isWindows ? 'npm.cmd' : 'npm' + const { stdout, stderr } = await promisify(cp.execFile)(bin, args, { + cwd, + shell: isWindows /* https://nodejs.org/en/blog/vulnerability/april-2024-security-releases-2 */ + }); if (!process.env['VSCE_TESTS']) { process.stdout.write(stdout); process.stderr.write(stderr); From 634a922ff584620d236d395656992bc703211c4f Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 03:14:33 +0200 Subject: [PATCH 04/17] warn --- src/package.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/package.ts b/src/package.ts index b804ba0e..f5e94f5e 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1694,7 +1694,12 @@ async function getDependenciesOption(manifest: ManifestPackage, options: IPackag } const isUnknown = await isNonNpmOrModernYarn(options.cwd || process.cwd(), manifest); - if (isUnknown) return 'none'; + if (isUnknown) { + if (options.dependencies) { + util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") + } + return 'none' + }; switch (options.useYarn) { case true: From 55ffdf54e5391808261e52c84d132f3f7319b866 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 03:48:15 +0200 Subject: [PATCH 05/17] speed --- src/package.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/package.ts b/src/package.ts index f5e94f5e..abceb408 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1724,9 +1724,6 @@ async function collectFiles( readmePath = readmePath ?? 'README.md'; const notIgnored = ['!package.json', `!${readmePath}`]; - const files = (await collectAllFiles(cwd, manifest, dependencies, dependencyEntryPoints, followSymlinks)) - .filter(f => !/\r$/m.test(f)); - let rawIgnore = ''; try { rawIgnore = await fs.promises.readFile(ignoreFile ? ignoreFile : path.join(cwd, '.vscodeignore'), 'utf8'); @@ -1759,12 +1756,27 @@ async function collectFiles( ...notIgnored, ] + const anyNM = [ + "node_modules/**", + "node_modules", + "**/node_modules/**", + + "**/node_modules", + "/node_modules", + "/node_modules/**", + ] + const isNMCompletelyIgnored = anyNM.some(p => i.some(j => j === p)); + if (process.env['VSCE_TESTS']) console.log("Node_modules completely ignored:", String(isNMCompletelyIgnored)); + // Split into ignore and negate list const [ignore, negate] = i.reduce<[string[], string[]]>( (r, e) => (!/^\s*!/.test(e) ? [[...r[0], e], r[1]] : [r[0], [...r[1], e.substring(1)]]), [[], []] ) + const files = (await collectAllFiles(cwd, manifest, isNMCompletelyIgnored ? "none" : dependencies, dependencyEntryPoints, followSymlinks)) + .filter(f => !/\r$/m.test(f)); + return files.filter( f => !ignore.some(i => minimatch(f, i, minimatchOptions)) || From 6612b9a8bc0ca570495efb23c15607cd85d9ae93 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 04:00:02 +0200 Subject: [PATCH 06/17] allow empty prepublish --- src/npm.ts | 4 +++- src/package.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/npm.ts b/src/npm.ts index de95140b..b0cff235 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -262,7 +262,9 @@ export const detectYarn = (cwd: string, pkg: ManifestPackage) => detectPackageManager(cwd, pkg, 'yarn', ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']); export async function getPrepublishCommand(cwd: string, manifest: ManifestPackage): Promise { - const customCommand = process.env['VSCE_RUN_PREPUBLISH'] || manifest?.vsce?.runPrepublish; + const envv = process.env['VSCE_RUN_PREPUBLISH'] + if (envv === "" || manifest?.vsce?.runPrepublish === false) return ""; + const customCommand = envv || manifest?.vsce?.runPrepublish; if (customCommand) { return customCommand; } diff --git a/src/package.ts b/src/package.ts index abceb408..c75a434f 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1900,6 +1900,8 @@ export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn const prepublish = await getPrepublishCommand(cwd, manifest); + if (!prepublish) return; + console.log(`Executing prepublish script '${prepublish}'...`); await new Promise((c, e) => { From a4b871abcfa704c87676d89b67206c2ba9a69f1f Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:56:19 +0200 Subject: [PATCH 07/17] fix yarn a bit --- src/npm.ts | 11 +++++++---- src/package.ts | 27 ++++++++++++--------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/npm.ts b/src/npm.ts index b0cff235..59de50ff 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -261,7 +261,11 @@ export const detectVlt = (cwd: string, pkg: ManifestPackage) => export const detectYarn = (cwd: string, pkg: ManifestPackage) => detectPackageManager(cwd, pkg, 'yarn', ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']); -export async function getPrepublishCommand(cwd: string, manifest: ManifestPackage): Promise { +export async function getPrepublishCommand(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined): Promise { + if (useYarn === true) { + return 'yarn run vscode:prepublish'; + } + const envv = process.env['VSCE_RUN_PREPUBLISH'] if (envv === "" || manifest?.vsce?.runPrepublish === false) return ""; const customCommand = envv || manifest?.vsce?.runPrepublish; @@ -279,13 +283,12 @@ export async function getPrepublishCommand(cwd: string, manifest: ManifestPackag export async function getDependencies( cwd: string, - dependencies: 'npm' | 'yarn' | 'none' | undefined, - manifest: ManifestPackage, + dependencies: 'npm' | 'yarn' | 'none', packagedDependencies?: string[] ): Promise { if (dependencies === 'none') { return [cwd]; - } else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd, manifest)))) { + } else if (dependencies === 'yarn') { return await getYarnDependencies(cwd, packagedDependencies); } else { return await getNpmDependencies(cwd); diff --git a/src/package.ts b/src/package.ts index c75a434f..770600cb 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1673,12 +1673,11 @@ const defaultIgnore = [ async function collectAllFiles( cwd: string, - manifest: ManifestPackage, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + dependencies: 'npm' | 'yarn' | 'none', dependencyEntryPoints?: string[], followSymlinks: boolean = true ): Promise { - const deps = await getDependencies(cwd, dependencies, manifest, dependencyEntryPoints); + const deps = await getDependencies(cwd, dependencies, dependencyEntryPoints); const promises = deps.map(dep => glob('**', { cwd: dep, nodir: true, follow: followSymlinks, dot: true, ignore: ['node_modules/**', ".git/**"] }).then(files => files.map(f => path.relative(cwd, path.join(dep, f))).map(f => f.replace(/\\/g, '/')) @@ -1688,12 +1687,14 @@ async function collectAllFiles( return files; } -async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions): Promise<'npm' | 'yarn' | 'none' | undefined> { +async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions): Promise<'npm' | 'yarn' | 'none'> { if (options.dependencies === false) { return 'none'; } - const isUnknown = await isNonNpmOrModernYarn(options.cwd || process.cwd(), manifest); + const cwd = options.cwd || process.cwd(); + + const isUnknown = await isNonNpmOrModernYarn(cwd, manifest); if (isUnknown) { if (options.dependencies) { util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") @@ -1707,14 +1708,14 @@ async function getDependenciesOption(manifest: ManifestPackage, options: IPackag case false: return 'npm'; default: - return undefined; + return await detectYarn(cwd, manifest) ? 'yarn' : 'npm'; } } async function collectFiles( cwd: string, manifest: ManifestPackage, - dependencies: 'npm' | 'yarn' | 'none' | undefined, + dependencies: 'npm' | 'yarn' | 'none', dependencyEntryPoints?: string[], ignoreFile?: string, readmePath?: string, @@ -1766,7 +1767,7 @@ async function collectFiles( "/node_modules/**", ] const isNMCompletelyIgnored = anyNM.some(p => i.some(j => j === p)); - if (process.env['VSCE_TESTS']) console.log("Node_modules completely ignored:", String(isNMCompletelyIgnored)); + if (process.env['VSCE_DEBUG'] && isNMCompletelyIgnored) console.log("node_modules are completely ignored."); // Split into ignore and negate list const [ignore, negate] = i.reduce<[string[], string[]]>( @@ -1774,7 +1775,7 @@ async function collectFiles( [[], []] ) - const files = (await collectAllFiles(cwd, manifest, isNMCompletelyIgnored ? "none" : dependencies, dependencyEntryPoints, followSymlinks)) + const files = (await collectAllFiles(cwd, isNMCompletelyIgnored ? "none" : dependencies, dependencyEntryPoints, followSymlinks)) .filter(f => !/\r$/m.test(f)); return files.filter( @@ -1889,16 +1890,12 @@ 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 | undefined): Promise { if (!manifest.scripts || !manifest.scripts['vscode:prepublish']) { return; } - if (useYarn === undefined) { - useYarn = await detectYarn(cwd, manifest); - } - - const prepublish = await getPrepublishCommand(cwd, manifest); + const prepublish = await getPrepublishCommand(cwd, manifest, useYarn); if (!prepublish) return; From d45be96a80ad65c8ba50d044d4826bca7440827e Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:55:03 +0200 Subject: [PATCH 08/17] refactor detection --- src/api.ts | 21 +++++++-- src/npm.ts | 120 ++++++++++++++++++++++++------------------------- src/package.ts | 62 ++++++++++++++++--------- src/publish.ts | 12 +++-- 4 files changed, 125 insertions(+), 90 deletions(-) diff --git a/src/api.ts b/src/api.ts index 1f4a3b99..758e413e 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,5 +1,6 @@ import { publish as _publish, IPublishOptions, unpublish as _unpublish, IUnpublishOptions } from './publish'; -import { packageCommand, listFiles as _listFiles, IPackageOptions } from './package'; +import { packageCommand, listFiles as _listFiles, IPackageOptions, readManifest } from './package'; +import { detectPackageManager } from './npm'; /** * @deprecated prefer IPackageOptions instead @@ -80,12 +81,26 @@ export function publish(options: IPublishOptions = {}): Promise { * Lists the files included in the extension's package. * @public */ -export function listFiles(options: IListFilesOptions = {}): Promise { +export async function listFiles(options: IListFilesOptions = {}): Promise { + const cwd = options.cwd || process.cwd(); + const manifest = await readManifest(cwd); + + let pm: string | null; + switch (options.packageManager) { + case PackageManager.Yarn: pm = 'yarn'; break; + case PackageManager.Npm: pm = 'npm'; break; + + case PackageManager.None: + default: + // cares all: detect prepublish script launcher + pm = await detectPackageManager(cwd, manifest, false); break; + } + return _listFiles({ ...options, useYarn: options.packageManager === PackageManager.Yarn, dependencies: options.packageManager !== PackageManager.None, - }); + }, pm); } /** diff --git a/src/npm.ts b/src/npm.ts index 59de50ff..ba84fed7 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import * as fs from 'fs'; import * as cp from 'child_process'; +import * as semver from 'semver'; import parseSemver from 'parse-semver'; import { CancellationToken, log, nonnull } from './util'; import type { ManifestPackage } from './manifest'; @@ -196,76 +197,77 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[]) return [...result]; } -import * as semver from 'semver'; +/** + * @param pm Cares only about yarn. + */ +export async function isNonNpmOrModernYarn(cwd: string, manifest: ManifestPackage, pm: string | null): Promise { + if (pm === 'yarn') { + // Try to extract version from manifest (e.g., "yarn@3.6.4") + const pmString = manifest?.devEngines?.packageManager?.version || manifest?.packageManager; -export async function isNonNpmOrModernYarn(cwd: string, manifest: ManifestPackage): Promise { - if (await detectPnpm(cwd, manifest) || await detectBun(cwd, manifest) || await detectVlt(cwd, manifest)) { - return true; - } + if (pmString && pmString.startsWith('yarn@')) { + const version = pmString.split('@')[1]; + if (version && semver.valid(version)) { + return semver.gte(version, '2.0.0'); // Returns true if Yarn 2, 3, or 4 + } + } - const isYarn = await detectYarn(cwd, manifest); - if (isYarn) { - // Try to extract version from manifest (e.g., "yarn@3.6.4") - const pmString = manifest?.devEngines?.packageManager?.version || manifest?.packageManager; + if (await exists(path.join(cwd, '.pnp.cjs')) || await exists(path.join(cwd, '.yarn/releases'))) { + return true; + } - if (pmString && pmString.startsWith('yarn@')) { - const version = pmString.split('@')[1]; - if (version && semver.valid(version)) { - return semver.gte(version, '2.0.0'); // Returns true if Yarn 2, 3, or 4 - } - } - - // Fallback: If it's Yarn but we can't find a version, - // we assume it's modern if a .yarn directory or .pnp.cjs exists - if (await exists(path.join(cwd, '.pnp.cjs')) || await exists(path.join(cwd, '.yarn/releases'))) { - return true; - } - } - - return false; // It's likely standard npm or classic yarn + return false; + } + + return pm !== null; // It's likely standard npm or classic yarn } -async function detectPackageManager( - cwd: string, - manifest: ManifestPackage, - name: string, - lockfiles: string[] -): Promise { - if (manifest?.devEngines?.packageManager?.name === name || manifest?.packageManager?.startsWith(`${name}@`)) { - return true; +const MANAGERS = [ + { name: 'pnpm', files: ['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.pnpmfile.cjs'] }, + { name: 'bun', files: ['bun.lockb', 'bun.lock', 'bunfig.toml'] }, + { name: 'vlt', files: ['vlt-lock.json', '.vltrc'] }, + { name: 'yarn', files: ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn'] } +] as const; + +export type ManagerName = (typeof MANAGERS)[number]['name'] + +export async function detectPackageManager(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined, care?: ManagerName[]): Promise { + if (useYarn) { + if (process.env['VSCE_DEBUG']) console.log('Package manager: yarn'); + return 'yarn'; + } + const m = care ? MANAGERS.filter(({name}) => care.includes(name)) : MANAGERS + for (const { name } of m) { + if ( + manifest?.devEngines?.packageManager?.name === name || + manifest?.packageManager?.startsWith(`${name}@`) + ) { + if (process.env['VSCE_DEBUG']) console.log('Package manager:', name); + return name; + } } - for (const filename of lockfiles) { - if (await exists(path.join(cwd, filename))) { + for (const mgr of m) { + for (const filename of mgr.files) { + if (!await exists(path.join(cwd, filename))) { + continue; + } if (!process.env['VSCE_TESTS']) { - const suffix = name === 'yarn' + const suffix = mgr.name === 'yarn' ? " instead of 'npm' (to override this pass '--no-yarn' on the command line)." : ' logic.'; - log.info(`Detected presence of ${filename}. Using '${name}'${suffix}`); + log.info(`Detected presence of ${filename}. Using '${mgr.name}'${suffix}`); } - return true; + if (process.env['VSCE_DEBUG']) console.log('Package manager:', mgr.name); + return mgr.name; } } - return false; -} - -export const detectPnpm = (cwd: string, pkg: ManifestPackage) => - detectPackageManager(cwd, pkg, 'pnpm', ['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.pnpmfile.cjs']); -export const detectBun = (cwd: string, pkg: ManifestPackage) => - detectPackageManager(cwd, pkg, 'bun', ['bun.lockb', 'bun.lock', 'bunfig.toml']); - -export const detectVlt = (cwd: string, pkg: ManifestPackage) => - detectPackageManager(cwd, pkg, 'vlt', ['vlt-lock.json', '.vltrc']); - -export const detectYarn = (cwd: string, pkg: ManifestPackage) => - detectPackageManager(cwd, pkg, 'yarn', ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']); - -export async function getPrepublishCommand(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined): Promise { - if (useYarn === true) { - return 'yarn run vscode:prepublish'; - } + if (process.env['VSCE_DEBUG']) console.log('Package manager: null (npm or unknown manager)'); + return null; // npm or unknown manager +} +export async function getPrepublishCommand(manifest: ManifestPackage, pm: string | null): Promise { const envv = process.env['VSCE_RUN_PREPUBLISH'] if (envv === "" || manifest?.vsce?.runPrepublish === false) return ""; const customCommand = envv || manifest?.vsce?.runPrepublish; @@ -273,12 +275,8 @@ export async function getPrepublishCommand(cwd: string, manifest: ManifestPackag return customCommand; } - if (await detectPnpm(cwd, manifest)) return 'pnpm run vscode:prepublish'; - if (await detectBun(cwd, manifest)) return 'bun run vscode:prepublish'; - if (await detectVlt(cwd, manifest)) return 'vlt run vscode:prepublish'; - if (await detectYarn(cwd, manifest)) return 'yarn run vscode:prepublish'; // Yarn usually doesn't need 'run' for scripts - - return 'npm run vscode:prepublish'; + if (pm === null) return 'npm run vscode:prepublish'; + return pm + ' run vscode:prepublish'; } export async function getDependencies( diff --git a/src/package.ts b/src/package.ts index 770600cb..5d44bb9a 100644 --- a/src/package.ts +++ b/src/package.ts @@ -23,7 +23,7 @@ import { validatePublisher, validateExtensionDependencies, } from './validation'; -import { detectBun, detectYarn, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; +import { detectPackageManager, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -378,7 +378,7 @@ export interface IVersionBumpOptions { readonly updatePackageJson?: boolean; } -export async function versionBump(options: IVersionBumpOptions): Promise { +export async function versionBump(options: IVersionBumpOptions, pm?: string | null): Promise { if (!options.version) { return; } @@ -389,6 +389,8 @@ export async function versionBump(options: IVersionBumpOptions): Promise { const cwd = options.cwd ?? process.cwd(); const manifest = await readManifest(cwd); + // cares bun: bun pm version + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, undefined, ['bun']); if (manifest.version === options.version) { return; @@ -411,10 +413,8 @@ export async function versionBump(options: IVersionBumpOptions): Promise { } } - const isBun = await detectBun(cwd, manifest) - // call `npm version` or `bun pm version` to do our dirty work - const args = isBun ? ['pm', 'version', options.version] : ['version', options.version]; + const args = pm === 'bun' ? ['pm', 'version', options.version] : ['version', options.version]; const isWindows = process.platform === 'win32'; @@ -427,7 +427,7 @@ export async function versionBump(options: IVersionBumpOptions): Promise { args.push('--no-git-tag-version'); } - const bin = isBun ? 'bun' + const bin = pm === 'bun' ? 'bun' : isWindows ? 'npm.cmd' : 'npm' const { stdout, stderr } = await promisify(cp.execFile)(bin, args, { cwd, @@ -1687,28 +1687,33 @@ async function collectAllFiles( return files; } -async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions): Promise<'npm' | 'yarn' | 'none'> { +async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions, pm: string | null): Promise<'npm' | 'yarn' | 'none'> { if (options.dependencies === false) { + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (options.dependencies is false)'); return 'none'; } const cwd = options.cwd || process.cwd(); - const isUnknown = await isNonNpmOrModernYarn(cwd, manifest); + const isUnknown = await isNonNpmOrModernYarn(cwd, manifest, pm); if (isUnknown) { if (options.dependencies) { util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") } + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (unknown package manager or npm)'); return 'none' }; switch (options.useYarn) { case true: + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'yarn'); return 'yarn'; case false: + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'npm'); return 'npm'; default: - return await detectYarn(cwd, manifest) ? 'yarn' : 'npm'; + if (process.env['VSCE_DEBUG']) console.log('Dep option:', pm === 'yarn' ? 'yarn' : 'npm'); + return pm === 'yarn' ? 'yarn' : 'npm'; } } @@ -1828,13 +1833,16 @@ export function createDefaultProcessors(manifest: ManifestPackage, options: IPac ]; } -export async function collect(manifest: ManifestPackage, options: IPackageOptions = {}): Promise { +export async function collect(manifest: ManifestPackage, options: IPackageOptions = {}, pm?: string | null): Promise { const cwd = options.cwd || process.cwd(); const packagedDependencies = options.dependencyEntryPoints || undefined; const ignoreFile = options.ignoreFile || undefined; const processors = createDefaultProcessors(manifest, options); - return collectFiles(cwd, manifest, await getDependenciesOption(manifest, options), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { + // cares yarn: only yarn and npm deps are supported + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['yarn']); + + return collectFiles(cwd, manifest, await getDependenciesOption(manifest, options, pm), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { const files = fileNames.map(f => ({ path: util.filePathToVsixPath(f), localPath: path.join(cwd, f) })); return processFiles(processors, files); @@ -1890,12 +1898,12 @@ function getDefaultPackageName(manifest: ManifestPackage, options: IPackageOptio return `${manifest.name}-${version}.vsix`; } -export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined): Promise { +export async function prepublish(cwd: string, manifest: ManifestPackage, pm: string | null): Promise { if (!manifest.scripts || !manifest.scripts['vscode:prepublish']) { return; } - const prepublish = await getPrepublishCommand(cwd, manifest, useYarn); + const prepublish = await getPrepublishCommand(manifest, pm); if (!prepublish) return; @@ -1927,10 +1935,13 @@ async function getPackagePath(cwd: string, manifest: ManifestPackage, options: I } } -export async function pack(options: IPackageOptions = {}): Promise { +export async function pack(options: IPackageOptions = {}, pm?: string | null): Promise { const cwd = options.cwd || process.cwd(); const manifest = await readManifest(cwd); - const files = await collect(manifest, options); + + // cares yarn: only yarn and npm deps are supported + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['yarn']); + const files = await collect(manifest, options, pm); await printAndValidatePackagedFiles(files, cwd, manifest, options); @@ -2002,10 +2013,12 @@ export async function packageCommand(options: IPackageOptions = {}): Promise { +export async function listFiles(options: IListFilesOptions = {}, pm: string | null): Promise { const cwd = options.cwd ?? process.cwd(); const manifest = options.manifest ?? await readManifest(cwd); if (options.prepublish) { - await prepublish(cwd, manifest, options.useYarn); + // cares all: detect prepublish script launcher + await prepublish(cwd, manifest, pm); } - return await collectFiles(cwd, manifest, await getDependenciesOption(manifest, options), options.packagedDependencies, options.ignoreFile, options.readmePath, options.followSymlinks); + // cares yarn + return await collectFiles(cwd, manifest, await getDependenciesOption(manifest, options, pm), options.packagedDependencies, options.ignoreFile, options.readmePath, options.followSymlinks); } interface ILSOptions { @@ -2059,7 +2074,10 @@ export async function ls(options: ILSOptions = {}): Promise { const cwd = process.cwd(); const manifest = await readManifest(cwd); - const files = await listFiles({ ...options, cwd, manifest }); + // cares all: detect prepublish script launcher + const pm = await detectPackageManager(cwd, manifest, options.useYarn); + + const files = await listFiles({ ...options, cwd, manifest }, pm); if (options.tree) { const printableFileStructure = await util.generateFileStructureTree( diff --git a/src/publish.ts b/src/publish.ts index 7475afcb..0b3e3abf 100644 --- a/src/publish.ts +++ b/src/publish.ts @@ -14,6 +14,7 @@ import FormData from 'form-data'; import { basename } from 'path'; import { IterableBackoff, handleWhen, retry } from 'cockatiel'; import { getAzureCredentialAccessToken } from './auth'; +import { detectPackageManager } from './npm'; const tmpName = promisify(tmp.tmpName); @@ -159,20 +160,23 @@ export async function publish(options: IPublishOptions = {}): Promise { // Validate marketplace requirements before prepublish to avoid unnecessary work validateManifestForPublishing(manifest, options); - await prepublish(cwd, manifest, options.useYarn); - await versionBump(options); + // cares all: detect prepublish script launcher + const pm = await detectPackageManager(cwd, manifest, options.useYarn); + + await prepublish(cwd, manifest, pm); + await versionBump(options, pm); if (options.targets) { for (const target of options.targets) { const packagePath = await tmpName(); - const packageResult = await pack({ ...options, target, packagePath }); + const packageResult = await pack({ ...options, target, packagePath }, pm); const manifestValidated = validateManifestForPublishing(packageResult.manifest, options); const sigzipPath = options.signTool ? await signPackage(packagePath, options.signTool) : undefined; await _publish(packagePath, sigzipPath, manifestValidated, { ...options, target }); } } else { const packagePath = await tmpName(); - const packageResult = await pack({ ...options, packagePath }); + const packageResult = await pack({ ...options, packagePath }, pm); const manifestValidated = validateManifestForPublishing(packageResult.manifest, options); const sigzipPath = options.signTool ? await signPackage(packagePath, options.signTool) : undefined; await _publish(packagePath, sigzipPath, manifestValidated, options); From f7426fcc5a022463fd7d5c9410e25b8bd32e0446 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:35:06 +0200 Subject: [PATCH 09/17] make docs clear --- README.md | 20 ++++++++++++++++++-- src/main.ts | 6 +++--- src/npm.ts | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fa43956c..b70486c5 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,15 @@ Supported package managers: - `npm >=6` - `yarn >=1 <2` +These and other package managers should work if you are bundling your extension. + +### Override Prepublish Script + +This section is optional. By default, `vsce` automatically detects and uses `npm`, `yarn`, `pnpm`, `vlt` and `bun` to generate this the `run vscode:prepublish` command. + +- Use `VSCE_RUN_PREPUBLISH="npm run vscode:prepublish"` or `vsce.runPrepublish: "npm run vscode:prepublish"` in your manifest file to override the auto-detected command. +- `vsce.runPrepublish: false` and `VSCE_RUN_PREPUBLISH=0` can disable the prepublish script. This is useful when you already transpiled your extension and you want to compile `.vsix` faster. + ## Configuration You can configure the behavior of `vsce` by using CLI flags (run `vsce --help` to list them all). Example: @@ -52,12 +61,17 @@ Or you can also set them in the `package.json`, so that you avoid having to rety { "vsce": { "baseImagesUrl": "https://my.custom/base/images/url", - "dependencies": true, + "runPrepublish": "pnpm run vscode:prepublish", + "dependencies": false, "yarn": false - } + }, + "packageManager": "pnpm@11.0.0", // optional } ``` +> **Note:** If you are bundling your extension, always set the `dependencies` option to `false`. +In newer version of `vsce` this option is set to `false` automatically, but providing it can make things a bit faster. Same applies to the `yarn` option. + ## Development First clone this repository, then: @@ -79,4 +93,6 @@ Tests can be executed with: $ npm test ``` +Use `VSCE_DEBUG=1` to enable debug output. + > **Note:** [Yarn](https://www.npmjs.com/package/yarn) is required to run the tests. diff --git a/src/main.ts b/src/main.ts index 68d14e3a..0cff90bc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -78,7 +78,7 @@ module.exports = function (argv: string[]): void { ) .option('--ignoreFile ', 'Indicate alternative .vscodeignore') // default must remain undefined for dependencies or we will fail to load defaults from package.json - .option('--dependencies', 'Enable dependency detection via npm or yarn', undefined) + .option('--dependencies', 'Enable dependency detection via npm or yarn. Never enable this option if you are bundling your extension', undefined) .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') @@ -118,7 +118,7 @@ module.exports = function (argv: string[]): void { .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') // default must remain undefined for dependencies or we will fail to load defaults from package.json - .option('--dependencies', 'Enable dependency detection via npm or yarn', undefined) + .option('--dependencies', 'Enable dependency detection via npm or yarn. Never enable this option if you are bundling your extension', undefined) .option('--no-dependencies', 'Disable dependency detection via npm or yarn', undefined) .option('--pre-release', 'Mark this package as a pre-release') .option('--allow-star-activation', 'Allow using * in activation events') @@ -244,7 +244,7 @@ module.exports = function (argv: string[]): void { .option('--allow-package-env-file', 'Allow packaging .env files') .option('--ignoreFile ', 'Indicate alternative .vscodeignore') // default must remain undefined for dependencies or we will fail to load defaults from package.json - .option('--dependencies', 'Enable dependency detection via npm or yarn', undefined) + .option('--dependencies', 'Enable dependency detection via npm or yarn. Never enable this option if you are bundling your extension', undefined) .option('--no-dependencies', 'Disable dependency detection via npm or yarn', undefined) .option('--pre-release', 'Mark this package as a pre-release') .option('--allow-star-activation', 'Allow using * in activation events') diff --git a/src/npm.ts b/src/npm.ts index ba84fed7..1d93e788 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -269,7 +269,7 @@ export async function detectPackageManager(cwd: string, manifest: ManifestPackag export async function getPrepublishCommand(manifest: ManifestPackage, pm: string | null): Promise { const envv = process.env['VSCE_RUN_PREPUBLISH'] - if (envv === "" || manifest?.vsce?.runPrepublish === false) return ""; + if (envv === "" || envv === "0" || manifest?.vsce?.runPrepublish === false) return ""; const customCommand = envv || manifest?.vsce?.runPrepublish; if (customCommand) { return customCommand; From 3434a4eef1442fc3533558ea713fe49643f64b43 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:40:14 +0200 Subject: [PATCH 10/17] do not detect for none --- src/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api.ts b/src/api.ts index 758e413e..9eb10f5d 100644 --- a/src/api.ts +++ b/src/api.ts @@ -89,8 +89,9 @@ export async function listFiles(options: IListFilesOptions = {}): Promise Date: Sun, 5 Apr 2026 19:01:19 +0200 Subject: [PATCH 11/17] detect deno, npm and yarn1 --- README.md | 2 +- src/npm.ts | 52 ++++++++++++++++++-------------------------------- src/package.ts | 42 ++++++++++++++++++---------------------- 3 files changed, 39 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index b70486c5..306a7f5d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ These and other package managers should work if you are bundling your extension. ### Override Prepublish Script -This section is optional. By default, `vsce` automatically detects and uses `npm`, `yarn`, `pnpm`, `vlt` and `bun` to generate this the `run vscode:prepublish` command. +This section is optional. By default, `vsce` automatically detects and uses `npm`, `yarn`, `pnpm`, `vlt`, `deno` and `bun` to generate this the `run vscode:prepublish` command. - Use `VSCE_RUN_PREPUBLISH="npm run vscode:prepublish"` or `vsce.runPrepublish: "npm run vscode:prepublish"` in your manifest file to override the auto-detected command. - `vsce.runPrepublish: false` and `VSCE_RUN_PREPUBLISH=0` can disable the prepublish script. This is useful when you already transpiled your extension and you want to compile `.vsix` faster. diff --git a/src/npm.ts b/src/npm.ts index 1d93e788..95b2ef30 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -1,7 +1,6 @@ import * as path from 'path'; import * as fs from 'fs'; import * as cp from 'child_process'; -import * as semver from 'semver'; import parseSemver from 'parse-semver'; import { CancellationToken, log, nonnull } from './util'; import type { ManifestPackage } from './manifest'; @@ -198,45 +197,30 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[]) } /** - * @param pm Cares only about yarn. + * Check if the package manager can be used for unbundled extensions. */ -export async function isNonNpmOrModernYarn(cwd: string, manifest: ManifestPackage, pm: string | null): Promise { - if (pm === 'yarn') { - // Try to extract version from manifest (e.g., "yarn@3.6.4") - const pmString = manifest?.devEngines?.packageManager?.version || manifest?.packageManager; - - if (pmString && pmString.startsWith('yarn@')) { - const version = pmString.split('@')[1]; - if (version && semver.valid(version)) { - return semver.gte(version, '2.0.0'); // Returns true if Yarn 2, 3, or 4 - } - } - - if (await exists(path.join(cwd, '.pnp.cjs')) || await exists(path.join(cwd, '.yarn/releases'))) { - return true; - } - - return false; - } - - return pm !== null; // It's likely standard npm or classic yarn +export function canNotBeUnbundled(pm: string | null): boolean { + return pm !== 'npm' && pm !== 'yarn1'; } +const YARN = [ + { name: 'yarn', files: ['.yarnrc.yaml', '.pnp.cjs', '.yarn/releases'] }, + { name: 'yarn1', files: ['.yarnrc', 'yarn.lock'] }, +] as const + const MANAGERS = [ + { name: 'npm', files: ['package-lock.json'] }, + ...YARN, { name: 'pnpm', files: ['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.pnpmfile.cjs'] }, - { name: 'bun', files: ['bun.lockb', 'bun.lock', 'bunfig.toml'] }, + { name: 'bun', files: ['bun.lock', 'bunfig.toml', 'bun.lockb'] }, { name: 'vlt', files: ['vlt-lock.json', '.vltrc'] }, - { name: 'yarn', files: ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn'] } + { name: 'deno', files: ['deno.lock', 'deno.json', 'deno.jsonc'] }, ] as const; -export type ManagerName = (typeof MANAGERS)[number]['name'] +export type ManagerName = (typeof MANAGERS)[number]['name']; export async function detectPackageManager(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined, care?: ManagerName[]): Promise { - if (useYarn) { - if (process.env['VSCE_DEBUG']) console.log('Package manager: yarn'); - return 'yarn'; - } - const m = care ? MANAGERS.filter(({name}) => care.includes(name)) : MANAGERS + const m = useYarn ? YARN : care ? MANAGERS.filter(({name}) => care.includes(name)) : MANAGERS; for (const { name } of m) { if ( manifest?.devEngines?.packageManager?.name === name || @@ -263,8 +247,8 @@ export async function detectPackageManager(cwd: string, manifest: ManifestPackag } } - if (process.env['VSCE_DEBUG']) console.log('Package manager: null (npm or unknown manager)'); - return null; // npm or unknown manager + if (process.env['VSCE_DEBUG']) console.log('Package manager: null'); + return null; } export async function getPrepublishCommand(manifest: ManifestPackage, pm: string | null): Promise { @@ -276,7 +260,9 @@ export async function getPrepublishCommand(manifest: ManifestPackage, pm: string } if (pm === null) return 'npm run vscode:prepublish'; - return pm + ' run vscode:prepublish'; + if (pm === 'deno') return 'npm run vscode:prepublish'; + if (pm === 'yarn1') return 'yarn run vscode:prepublish'; + return pm + ' run vscode:prepublish'; // known pms } export async function getDependencies( diff --git a/src/package.ts b/src/package.ts index 5d44bb9a..3747ffa1 100644 --- a/src/package.ts +++ b/src/package.ts @@ -23,7 +23,12 @@ import { validatePublisher, validateExtensionDependencies, } from './validation'; -import { detectPackageManager, getDependencies, getPrepublishCommand, isNonNpmOrModernYarn } from './npm'; +import { + detectPackageManager, + getDependencies, + getPrepublishCommand, + canNotBeUnbundled, +} from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; import * as jsonc from 'jsonc-parser'; @@ -390,7 +395,7 @@ export async function versionBump(options: IVersionBumpOptions, pm?: string | nu const cwd = options.cwd ?? process.cwd(); const manifest = await readManifest(cwd); // cares bun: bun pm version - if (pm === undefined) pm = await detectPackageManager(cwd, manifest, undefined, ['bun']); + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, undefined, ['npm', 'bun']); if (manifest.version === options.version) { return; @@ -1687,34 +1692,25 @@ async function collectAllFiles( return files; } -async function getDependenciesOption(manifest: ManifestPackage, options: IPackageOptions, pm: string | null): Promise<'npm' | 'yarn' | 'none'> { +async function getDependenciesOption(options: IPackageOptions, pm: string | null): Promise<'npm' | 'yarn' | 'none'> { if (options.dependencies === false) { if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (options.dependencies is false)'); return 'none'; } - const cwd = options.cwd || process.cwd(); - - const isUnknown = await isNonNpmOrModernYarn(cwd, manifest, pm); - if (isUnknown) { + const isUnsupported = canNotBeUnbundled(pm ?? "npm"); + if (isUnsupported) { if (options.dependencies) { util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") } - if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (unknown package manager or npm)'); + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (can not be unbundled)'); return 'none' }; - switch (options.useYarn) { - case true: - if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'yarn'); - return 'yarn'; - case false: - if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'npm'); - return 'npm'; - default: - if (process.env['VSCE_DEBUG']) console.log('Dep option:', pm === 'yarn' ? 'yarn' : 'npm'); - return pm === 'yarn' ? 'yarn' : 'npm'; - } + const useYarn = options.useYarn ?? pm === 'yarn1'; + const depOption = useYarn ? 'yarn' : 'npm'; + if (process.env['VSCE_DEBUG']) console.log('Dep option:', depOption); + return depOption; } async function collectFiles( @@ -1840,9 +1836,9 @@ export async function collect(manifest: ManifestPackage, options: IPackageOption const processors = createDefaultProcessors(manifest, options); // cares yarn: only yarn and npm deps are supported - if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['yarn']); + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['npm', 'yarn1']); - return collectFiles(cwd, manifest, await getDependenciesOption(manifest, options, pm), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { + return collectFiles(cwd, manifest, await getDependenciesOption(options, pm), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { const files = fileNames.map(f => ({ path: util.filePathToVsixPath(f), localPath: path.join(cwd, f) })); return processFiles(processors, files); @@ -1940,7 +1936,7 @@ export async function pack(options: IPackageOptions = {}, pm?: string | null): P const manifest = await readManifest(cwd); // cares yarn: only yarn and npm deps are supported - if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['yarn']); + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['npm', 'yarn1']); const files = await collect(manifest, options, pm); await printAndValidatePackagedFiles(files, cwd, manifest, options); @@ -2054,7 +2050,7 @@ export async function listFiles(options: IListFilesOptions = {}, pm: string | nu } // cares yarn - return await collectFiles(cwd, manifest, await getDependenciesOption(manifest, options, pm), options.packagedDependencies, options.ignoreFile, options.readmePath, options.followSymlinks); + return await collectFiles(cwd, manifest, await getDependenciesOption(options, pm), options.packagedDependencies, options.ignoreFile, options.readmePath, options.followSymlinks); } interface ILSOptions { From 967ac36e6bfebad5d3e4cc61d05aab574d3ffb03 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:40:50 +0200 Subject: [PATCH 12/17] detect npm correctly --- src/npm.ts | 4 ++-- src/package.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/npm.ts b/src/npm.ts index 95b2ef30..36eb94de 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -209,12 +209,12 @@ const YARN = [ ] as const const MANAGERS = [ - { name: 'npm', files: ['package-lock.json'] }, - ...YARN, + ...YARN, { name: 'pnpm', files: ['pnpm-lock.yaml', 'pnpm-workspace.yaml', '.pnpmfile.cjs'] }, { name: 'bun', files: ['bun.lock', 'bunfig.toml', 'bun.lockb'] }, { name: 'vlt', files: ['vlt-lock.json', '.vltrc'] }, { name: 'deno', files: ['deno.lock', 'deno.json', 'deno.jsonc'] }, + { name: 'npm', files: ['package.json', 'package-lock.json'] }, ] as const; export type ManagerName = (typeof MANAGERS)[number]['name']; diff --git a/src/package.ts b/src/package.ts index 3747ffa1..86549d30 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1698,7 +1698,7 @@ async function getDependenciesOption(options: IPackageOptions, pm: string | null return 'none'; } - const isUnsupported = canNotBeUnbundled(pm ?? "npm"); + const isUnsupported = canNotBeUnbundled(pm); if (isUnsupported) { if (options.dependencies) { util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") From 1ee104ed0ef76c42e2af4a43a51c98fd5288807a Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sat, 11 Apr 2026 22:56:22 +0200 Subject: [PATCH 13/17] refactor: node_modules ignoring --- src/package.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/package.ts b/src/package.ts index 86549d30..3cbb6738 100644 --- a/src/package.ts +++ b/src/package.ts @@ -1698,6 +1698,13 @@ async function getDependenciesOption(options: IPackageOptions, pm: string | null return 'none'; } + const cwd = options.cwd || process.cwd() + const isNodeModulesExist = fs.existsSync(path.join(cwd, "node_modules")) + if (!isNodeModulesExist) { + if (process.env['VSCE_DEBUG']) console.log('Dep option:', 'none (node_modules directory does not exist)'); + return 'none' + } + const isUnsupported = canNotBeUnbundled(pm); if (isUnsupported) { if (options.dependencies) { @@ -1758,17 +1765,11 @@ async function collectFiles( ...notIgnored, ] - const anyNM = [ - "node_modules/**", - "node_modules", - "**/node_modules/**", - - "**/node_modules", - "/node_modules", - "/node_modules/**", - ] - const isNMCompletelyIgnored = anyNM.some(p => i.some(j => j === p)); - if (process.env['VSCE_DEBUG'] && isNMCompletelyIgnored) console.log("node_modules are completely ignored."); + const isNMIgnored = dependencies === 'none' || i.some(j => j.includes("node_modules") && !j.startsWith("!")); + if (process.env['VSCE_DEBUG'] && isNMIgnored) { + if (dependencies !== 'none') console.log("node_modules directory is completely ignored.") + else console.log("node_modules directory does not exist.") + } // Split into ignore and negate list const [ignore, negate] = i.reduce<[string[], string[]]>( @@ -1776,7 +1777,7 @@ async function collectFiles( [[], []] ) - const files = (await collectAllFiles(cwd, isNMCompletelyIgnored ? "none" : dependencies, dependencyEntryPoints, followSymlinks)) + const files = (await collectAllFiles(cwd, isNMIgnored ? 'none' : dependencies, dependencyEntryPoints, followSymlinks)) .filter(f => !/\r$/m.test(f)); return files.filter( From 2ec2dc0b7c97905600a8a71bb73635fc8e02cfcb Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:09:53 +0200 Subject: [PATCH 14/17] readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 306a7f5d..35639a27 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,9 @@ These and other package managers should work if you are bundling your extension. ### Override Prepublish Script -This section is optional. By default, `vsce` automatically detects and uses `npm`, `yarn`, `pnpm`, `vlt`, `deno` and `bun` to generate this the `run vscode:prepublish` command. +This section is optional. By default, `vsce` automatically detects and uses `npm`, `yarn`, `pnpm`, `vlt`, `deno` and `bun` to generate the ` run vscode:prepublish` command. -- Use `VSCE_RUN_PREPUBLISH="npm run vscode:prepublish"` or `vsce.runPrepublish: "npm run vscode:prepublish"` in your manifest file to override the auto-detected command. +- Use `VSCE_RUN_PREPUBLISH="npm run vscode:prepublish"` or `vsce.runPrepublish: "npm run vscode:prepublish"` in the "package.json" to override the auto-detected command. - `vsce.runPrepublish: false` and `VSCE_RUN_PREPUBLISH=0` can disable the prepublish script. This is useful when you already transpiled your extension and you want to compile `.vsix` faster. ## Configuration From 136cb40a33dae53d334b5628c68879d15fd89669 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Sun, 12 Apr 2026 00:34:17 +0200 Subject: [PATCH 15/17] refactor: replace canNotBeUnbundled with supportedRaw for package manager validation --- README.md | 2 +- src/npm.ts | 6 ++---- src/package.ts | 8 ++++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 35639a27..82d6adb3 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Or you can also set them in the `package.json`, so that you avoid having to rety { "vsce": { "baseImagesUrl": "https://my.custom/base/images/url", - "runPrepublish": "pnpm run vscode:prepublish", + "runPrepublish": "pnpm run vscode:prepublish", "dependencies": false, "yarn": false }, diff --git a/src/npm.ts b/src/npm.ts index 36eb94de..370763a5 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -197,11 +197,9 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[]) } /** - * Check if the package manager can be used for unbundled extensions. + * A list of supported package managers that can be used for unbundled extensions. */ -export function canNotBeUnbundled(pm: string | null): boolean { - return pm !== 'npm' && pm !== 'yarn1'; -} +export const supportedRaw = ['npm', 'yarn1'] as ManagerName[] const YARN = [ { name: 'yarn', files: ['.yarnrc.yaml', '.pnp.cjs', '.yarn/releases'] }, diff --git a/src/package.ts b/src/package.ts index 3cbb6738..bce157ea 100644 --- a/src/package.ts +++ b/src/package.ts @@ -27,7 +27,7 @@ import { detectPackageManager, getDependencies, getPrepublishCommand, - canNotBeUnbundled, + supportedRaw, } from './npm'; import * as GitHost from 'hosted-git-info'; import parseSemver from 'parse-semver'; @@ -1705,7 +1705,7 @@ async function getDependenciesOption(options: IPackageOptions, pm: string | null return 'none' } - const isUnsupported = canNotBeUnbundled(pm); + const isUnsupported = !supportedRaw.includes(pm as any); if (isUnsupported) { if (options.dependencies) { util.log.warn("You are trying to include node_modules into your extension, but it should be bundled. Do not use --dependencies.") @@ -1837,7 +1837,7 @@ export async function collect(manifest: ManifestPackage, options: IPackageOption const processors = createDefaultProcessors(manifest, options); // cares yarn: only yarn and npm deps are supported - if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['npm', 'yarn1']); + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, supportedRaw); return collectFiles(cwd, manifest, await getDependenciesOption(options, pm), packagedDependencies, ignoreFile, options.readmePath, options.followSymlinks).then(fileNames => { const files = fileNames.map(f => ({ path: util.filePathToVsixPath(f), localPath: path.join(cwd, f) })); @@ -1937,7 +1937,7 @@ export async function pack(options: IPackageOptions = {}, pm?: string | null): P const manifest = await readManifest(cwd); // cares yarn: only yarn and npm deps are supported - if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, ['npm', 'yarn1']); + if (pm === undefined) pm = await detectPackageManager(cwd, manifest, options.useYarn, supportedRaw); const files = await collect(manifest, options, pm); await printAndValidatePackagedFiles(files, cwd, manifest, options); From b66dc533611f85c28a768f29bc3285fe33625438 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Fri, 22 May 2026 16:11:48 +0200 Subject: [PATCH 16/17] clarify deno bump-version --- src/package.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/package.ts b/src/package.ts index bce157ea..32d5e931 100644 --- a/src/package.ts +++ b/src/package.ts @@ -395,6 +395,9 @@ export async function versionBump(options: IVersionBumpOptions, pm?: string | nu const cwd = options.cwd ?? process.cwd(); const manifest = await readManifest(cwd); // cares bun: bun pm version + // ignores deno: 2.8 deno's bump-version isn't npm-cli-compatible + // you would think we should throw "Deno isn't supported", but + // we can force npm here if (pm === undefined) pm = await detectPackageManager(cwd, manifest, undefined, ['npm', 'bun']); if (manifest.version === options.version) { From 4f29e28ed85a1819cd957c055044f7a7d5c69b98 Mon Sep 17 00:00:00 2001 From: Mopsgamer <79159094+Mopsgamer@users.noreply.github.com> Date: Fri, 22 May 2026 16:39:51 +0200 Subject: [PATCH 17/17] add vp --- src/npm.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/npm.ts b/src/npm.ts index 370763a5..fef7b7aa 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -212,6 +212,7 @@ const MANAGERS = [ { name: 'bun', files: ['bun.lock', 'bunfig.toml', 'bun.lockb'] }, { name: 'vlt', files: ['vlt-lock.json', '.vltrc'] }, { name: 'deno', files: ['deno.lock', 'deno.json', 'deno.jsonc'] }, + { name: 'vp', files: ['vite.config.ts', 'vite.config.js', 'vite.config.mts', 'vite.config.cts', 'vite.config.mjs', 'vite.config.cjs'] }, { name: 'npm', files: ['package.json', 'package-lock.json'] }, ] as const;