Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string[]>(
'--packagedDependencies <path>',
'Select packages that should be published only (includes dependencies)',
Expand All @@ -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>', '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
Expand Down Expand Up @@ -114,6 +116,8 @@ module.exports = function (argv: string[]): void {
.option('--baseImagesUrl <url>', '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 <path>', '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')
Expand Down Expand Up @@ -148,6 +152,7 @@ module.exports = function (argv: string[]): void {
baseContentUrl,
baseImagesUrl,
yarn,
bun,
ignoreFile,
gitHubIssueLinking,
gitLabIssueLinking,
Expand Down Expand Up @@ -181,6 +186,7 @@ module.exports = function (argv: string[]): void {
baseContentUrl,
baseImagesUrl,
useYarn: yarn,
useBun: bun,
ignoreFile,
gitHubIssueLinking,
gitLabIssueLinking,
Expand Down Expand Up @@ -235,6 +241,8 @@ module.exports = function (argv: string[]): void {
.option('--baseImagesUrl <url>', '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 <apis...>', 'Allow specific proposed APIs')
Expand Down Expand Up @@ -275,6 +283,7 @@ module.exports = function (argv: string[]): void {
baseContentUrl,
baseImagesUrl,
yarn,
bun,
verify,
noVerify,
allowProposedApis,
Expand Down Expand Up @@ -315,6 +324,7 @@ module.exports = function (argv: string[]): void {
baseContentUrl,
baseImagesUrl,
useYarn: yarn,
useBun: bun,
noVerify: noVerify || !verify,
allowProposedApis,
allowAllProposedApis,
Expand Down
57 changes: 30 additions & 27 deletions src/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ function exec(
disposeCancellationListener();
disposeCancellationListener = null;
}

if (err) {
return e(err);
}
Expand All @@ -51,21 +50,20 @@ function exec(
});
}
});

}

async function checkNPM(cancellationToken?: CancellationToken): Promise<void> {
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`);
}
}

function getNpmDependencies(cwd: string): Promise<string[]> {
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)));
}
Expand All @@ -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 };
}

Expand All @@ -128,7 +120,6 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st
return result;
}
})();

const reached = new (class {
values: YarnDependency[] = [];
add(dep: YarnDependency): boolean {
Expand All @@ -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)) {
Expand All @@ -155,46 +145,57 @@ function selectYarnDependencies(deps: YarnDependency[], packagedDependencies: st
}

async function getYarnProductionDependencies(cwd: string, packagedDependencies?: string[]): Promise<YarnDependency[]> {
const raw = await new Promise<string>((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<string>((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<string[]> {
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<boolean> {
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<string[]> {
// 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<boolean> {
for (const name of ['yarn.lock', '.yarnrc', '.yarnrc.yaml', '.pnp.cjs', '.yarn']) {
if (await exists(path.join(cwd, name))) {
Expand All @@ -211,11 +212,13 @@ export async function detectYarn(cwd: string): Promise<boolean> {

export async function getDependencies(
cwd: string,
dependencies: 'npm' | 'yarn' | 'none' | undefined,
dependencies: 'npm' | 'yarn' | 'bun' | 'none' | undefined,
packagedDependencies?: string[]
): Promise<string[]> {
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 {
Expand Down
38 changes: 29 additions & 9 deletions src/package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<string[]> {
Expand All @@ -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';
Expand All @@ -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[],
Expand Down Expand Up @@ -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<void> {
export async function prepublish(cwd: string, manifest: ManifestPackage, useYarn?: boolean, useBun?: boolean): Promise<void> {
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}'...`);
Expand Down Expand Up @@ -1986,7 +1998,7 @@ export async function packageCommand(options: IPackageOptions = {}): Promise<any
const manifest = await readManifest(cwd);
util.patchOptionsWithManifest(options, manifest);

await prepublish(cwd, manifest, options.useYarn);
await prepublish(cwd, manifest, options.useYarn, options.useBun);
await versionBump(options);

const { packagePath, files } = await pack(options);
Expand All @@ -2004,6 +2016,10 @@ export interface IListFilesOptions {
readonly cwd?: string;
readonly manifest?: ManifestPackage;
readonly useYarn?: boolean;
/**
* Should use Bun instead of NPM.
*/
readonly useBun?: boolean;
readonly packagedDependencies?: string[];
readonly ignoreFile?: string;
readonly dependencies?: boolean;
Expand All @@ -2020,7 +2036,7 @@ export async function listFiles(options: IListFilesOptions = {}): Promise<string
const manifest = options.manifest ?? await readManifest(cwd);

if (options.prepublish) {
await prepublish(cwd, manifest, options.useYarn);
await prepublish(cwd, manifest, options.useYarn, options.useBun);
}

return await collectFiles(cwd, getDependenciesOption(options), options.packagedDependencies, options.ignoreFile, manifest.files, options.readmePath, options.followSymlinks);
Expand All @@ -2029,6 +2045,10 @@ export async function listFiles(options: IListFilesOptions = {}): Promise<string
interface ILSOptions {
readonly tree?: boolean;
readonly useYarn?: boolean;
/**
* Should use Bun instead of NPM.
*/
readonly useBun?: boolean;
readonly packagedDependencies?: string[];
readonly ignoreFile?: string;
readonly dependencies?: boolean;
Expand Down
4 changes: 4 additions & 0 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface IPublishOptions {
* Should use Yarn instead of NPM.
*/
readonly useYarn?: boolean;
/**
* Should use Bun instead of NPM.
*/
readonly useBun?: boolean;
readonly dependencyEntryPoints?: string[];
readonly ignoreFile?: string;

Expand Down
10 changes: 10 additions & 0 deletions src/test/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,16 @@ describe('collect', function () {
}
});

it('should use bun when useBun is true', async () => {
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);
Expand Down