Skip to content
Open
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`, `deno` and `bun` to generate the `<manager> run vscode:prepublish` 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

You can configure the behavior of `vsce` by using CLI flags (run `vsce --help` to list them all). Example:
Expand All @@ -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:
Expand All @@ -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.
22 changes: 19 additions & 3 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -80,12 +81,27 @@ export function publish(options: IPublishOptions = {}): Promise<any> {
* Lists the files included in the extension's package.
* @public
*/
export function listFiles(options: IListFilesOptions = {}): Promise<string[]> {
export async function listFiles(options: IListFilesOptions = {}): Promise<string[]> {
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: pm = null; break;

case undefined:
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);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ module.exports = function (argv: string[]): void {
)
.option('--ignoreFile <path>', '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>', 'Path to README file (defaults to README.md)')
.option('--follow-symlinks', 'Recurse into symlinked directories instead of treating them as files')
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -244,7 +244,7 @@ module.exports = function (argv: string[]): void {
.option('--allow-package-env-file', 'Allow packaging .env files')
.option('--ignoreFile <path>', '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')
Expand Down
5 changes: 5 additions & 0 deletions src/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
75 changes: 65 additions & 10 deletions src/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -195,28 +196,82 @@ async function getYarnDependencies(cwd: string, packagedDependencies?: string[])
return [...result];
}

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))) {
/**
* A list of supported package managers that can be used for unbundled extensions.
*/
export const supportedRaw = ['npm', 'yarn1'] as ManagerName[]

const YARN = [
{ name: 'yarn', files: ['.yarnrc.yaml', '.pnp.cjs', '.yarn/releases'] },
{ name: 'yarn1', files: ['.yarnrc', 'yarn.lock'] },
] as const

const MANAGERS = [
...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: '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;

export type ManagerName = (typeof MANAGERS)[number]['name'];

export async function detectPackageManager(cwd: string, manifest: ManifestPackage, useYarn: boolean | undefined, care?: ManagerName[]): Promise<string | null> {
const m = useYarn ? YARN : 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 mgr of m) {
for (const filename of mgr.files) {
if (!await exists(path.join(cwd, filename))) {
continue;
}
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 = mgr.name === 'yarn'
? " instead of 'npm' (to override this pass '--no-yarn' on the command line)."
: ' logic.';
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;

if (process.env['VSCE_DEBUG']) console.log('Package manager: null');
return null;
}

export async function getPrepublishCommand(manifest: ManifestPackage, pm: string | null): Promise<string> {
const envv = process.env['VSCE_RUN_PREPUBLISH']
if (envv === "" || envv === "0" || manifest?.vsce?.runPrepublish === false) return "";
const customCommand = envv || manifest?.vsce?.runPrepublish;
if (customCommand) {
return customCommand;
}

if (pm === null) return 'npm 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(
cwd: string,
dependencies: 'npm' | 'yarn' | 'none' | undefined,
dependencies: 'npm' | 'yarn' | 'none',
packagedDependencies?: string[]
): Promise<string[]> {
if (dependencies === 'none') {
return [cwd];
} else if (dependencies === 'yarn' || (dependencies === undefined && (await detectYarn(cwd)))) {
} else if (dependencies === 'yarn') {
return await getYarnDependencies(cwd, packagedDependencies);
} else {
return await getNpmDependencies(cwd);
Expand Down
Loading