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
10 changes: 7 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 34 additions & 18 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { publish, unpublish } from './publish';
import { show } from './show';
import { search } from './search';
import { listPublishers, deletePublisher, loginPublisher, logoutPublisher, verifyPat } from './store';
import { getLatestVersion } from './npm';
import { CancellationToken, log } from './util';
import * as semver from 'semver';
import { isatty } from 'tty';
import { getPackageManager, Managers, PackageManagerLiteral } from './managers';

const pkg = require('../package.json');

Expand All @@ -35,13 +35,19 @@ See https://code.visualstudio.com/api/working-with-extensions/publishing-extensi
process.exit(1);
}

function main(task: Promise<any>): void {
export interface IMainOptions {
readonly packageManager?: PackageManagerLiteral;
readonly useYarn?: boolean;
}

function main(task: Promise<any>, options: IMainOptions): void {
let latestVersion: string | null = null;

const token = new CancellationToken();
const manager = getPackageManager(options.packageManager);

if (isatty(1)) {
getLatestVersion(pkg.name, token)
manager.pkgRequestLatest(pkg.name, token)
.then(version => (latestVersion = version))
.catch(_ => {
/* noop */
Expand All @@ -50,14 +56,15 @@ function main(task: Promise<any>): void {

task.catch(fatal).then(() => {
if (latestVersion && semver.gt(latestVersion, pkg.version)) {
log.warn(`The latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: npm install -g ${pkg.name}`);
log.warn(`The latest version of ${pkg.name} is ${latestVersion} and you have ${pkg.version}.\nUpdate it now: ${manager.commandInstall(pkg.name, true)}`);
} else {
token.cancel();
}
});
}

const ValidTargets = [...Targets].join(', ');
const ValidManagers = [...Managers].join(', ');

module.exports = function (argv: string[]): void {
const program = new Command();
Expand All @@ -68,6 +75,7 @@ module.exports = function (argv: string[]): void {
.command('ls')
.description('Lists all the files that will be published/packaged')
.option('--tree', 'Prints the files in a tree format', false)
.option('--packageManager <name>', `Specify package manager to use. Valid managers: ${ValidManagers}`, undefined)
.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<string[]>(
Expand All @@ -82,8 +90,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, packageManager, yarn, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks }) =>
main(ls({ tree, packageManager, useYarn: yarn, packagedDependencies, ignoreFile, dependencies, readmePath, followSymlinks }), { packageManager, useYarn: yarn })
);

program
Expand Down Expand Up @@ -112,6 +120,7 @@ module.exports = function (argv: string[]): void {
.option('--no-rewrite-relative-links', 'Skip rewriting relative links.')
.option('--baseContentUrl <url>', 'Prepend all relative links in README.md with the specified URL.')
.option('--baseImagesUrl <url>', 'Prepend all relative image links in README.md with the specified URL.')
.option('--packageManager <name>', `Specify package manager to use. Valid managers: ${ValidManagers}`, undefined)
.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('--ignoreFile <path>', 'Indicate alternative .vscodeignore')
Expand Down Expand Up @@ -147,6 +156,7 @@ module.exports = function (argv: string[]): void {
rewriteRelativeLinks,
baseContentUrl,
baseImagesUrl,
packageManager,
yarn,
ignoreFile,
gitHubIssueLinking,
Expand Down Expand Up @@ -180,6 +190,7 @@ module.exports = function (argv: string[]): void {
rewriteRelativeLinks,
baseContentUrl,
baseImagesUrl,
packageManager,
useYarn: yarn,
ignoreFile,
gitHubIssueLinking,
Expand All @@ -195,7 +206,8 @@ module.exports = function (argv: string[]): void {
skipLicense,
signTool,
followSymlinks,
})
}),
{ packageManager, useYarn: yarn }
)
);

Expand Down Expand Up @@ -233,6 +245,7 @@ module.exports = function (argv: string[]): void {
)
.option('--baseContentUrl <url>', 'Prepend all relative links in README.md with the specified URL.')
.option('--baseImagesUrl <url>', 'Prepend all relative image links in README.md with the specified URL.')
.option('--packageManager <name>', `Specify package manager to use. Valid managers: ${ValidManagers}`, undefined)
.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('--no-verify', 'Allow all proposed APIs (deprecated: use --allow-all-proposed-apis instead)')
Expand Down Expand Up @@ -274,6 +287,7 @@ module.exports = function (argv: string[]): void {
gitlabBranch,
baseContentUrl,
baseImagesUrl,
packageManager,
yarn,
verify,
noVerify,
Expand Down Expand Up @@ -314,6 +328,7 @@ module.exports = function (argv: string[]): void {
gitlabBranch,
baseContentUrl,
baseImagesUrl,
packageManager,
useYarn: yarn,
noVerify: noVerify || !verify,
allowProposedApis,
Expand All @@ -331,7 +346,8 @@ module.exports = function (argv: string[]): void {
skipLicense,
signTool,
followSymlinks
})
}),
{ packageManager, useYarn: yarn }
)
);

Expand All @@ -341,42 +357,42 @@ module.exports = function (argv: string[]): void {
.option('-p, --pat <token>', 'Personal Access Token')
.option('--azure-credential', 'Use Microsoft Entra ID for authentication')
.option('-f, --force', 'Skip confirmation prompt when unpublishing an extension')
.action((id, { pat, azureCredential, force }) => main(unpublish({ id, pat, azureCredential, force })));
.action((id, { pat, azureCredential, force }) => main(unpublish({ id, pat, azureCredential, force }), { packageManager: 'npm', useYarn: false }));

program
.command('generate-manifest')
.description('Generates the extension manifest from the provided VSIX package.')
.requiredOption('-i, --packagePath <path>', 'Path to the VSIX package')
.option('-o, --out <path>', 'Output the extension manifest to <path> location (defaults to <packagename>.manifest)')
.action(({ packagePath, out }) => main(generateManifest(packagePath, out)));
.action(({ packagePath, out }) => main(generateManifest(packagePath, out), { packageManager: 'npm', useYarn: false }));

program
.command('verify-signature')
.description('Verifies the provided signature file against the provided VSIX package and manifest.')
.requiredOption('-i, --packagePath <path>', 'Path to the VSIX package')
.requiredOption('-m, --manifestPath <path>', 'Path to the Manifest file')
.requiredOption('-s, --signaturePath <path>', 'Path to the Signature file')
.action(({ packagePath, manifestPath, signaturePath }) => main(verifySignature(packagePath, manifestPath, signaturePath)));
.action(({ packagePath, manifestPath, signaturePath }) => main(verifySignature(packagePath, manifestPath, signaturePath), { packageManager: 'npm', useYarn: false }));

program
.command('ls-publishers')
.description('Lists all known publishers')
.action(() => main(listPublishers()));
.action(() => main(listPublishers(), { packageManager: 'npm', useYarn: false }));

program
.command('delete-publisher <publisher>')
.description('Deletes a publisher from marketplace')
.action(publisher => main(deletePublisher(publisher)));
.action(publisher => main(deletePublisher(publisher), { packageManager: 'npm', useYarn: false }));

program
.command('login <publisher>')
.description('Adds a publisher to the list of known publishers')
.action(name => main(loginPublisher(name)));
.action(name => main(loginPublisher(name), { packageManager: 'npm', useYarn: false }));

program
.command('logout <publisher>')
.description('Removes a publisher from the list of known publishers')
.action(name => main(logoutPublisher(name)));
.action(name => main(logoutPublisher(name), { packageManager: 'npm', useYarn: false }));

program
.command('verify-pat [publisher]')
Expand All @@ -387,21 +403,21 @@ module.exports = function (argv: string[]): void {
process.env['VSCE_PAT']
)
.option('--azure-credential', 'Use Microsoft Entra ID for authentication')
.action((publisherName, { pat, azureCredential }) => main(verifyPat({ publisherName, pat, azureCredential })));
.action((publisherName, { pat, azureCredential }) => main(verifyPat({ publisherName, pat, azureCredential }), { packageManager: 'npm', useYarn: false }));

program
.command('show <extensionid>')
.description(`Shows an extension's metadata`)
.option('--json', 'Outputs data in json format', false)
.action((extensionid, { json }) => main(show(extensionid, json)));
.action((extensionid, { json }) => main(show(extensionid, json), { packageManager: 'npm', useYarn: false }));

program
.command('search <text>')
.description('Searches extension gallery')
.option('--json', 'Output results in json format', false)
.option('--stats', 'Shows extensions rating and download count', false)
.option('-p, --pagesize [value]', 'Number of results to return', '100')
.action((text, { json, pagesize, stats }) => main(search(text, json, parseInt(pagesize), stats)));
.action((text, { json, pagesize, stats }) => main(search(text, json, parseInt(pagesize), stats), { packageManager: 'npm', useYarn: false }));

program.on('command:*', ([cmd]: string) => {
if (cmd === 'create-publisher') {
Expand Down
41 changes: 41 additions & 0 deletions src/managers/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as cp from "node:child_process";
import { CancellationToken } from "../util";

interface IOptions {
cwd?: string;
stdio?: any;
customFds?: any;
env?: any;
timeout?: number;
maxBuffer?: number;
killSignal?: string;
}

export function exec(
command: string,
options: IOptions = {},
cancellationToken?: CancellationToken
): Promise<{ stdout: string; stderr: string }> {
return new Promise((c, e) => {
let disposeCancellationListener: Function | null = null;

const child = cp.exec(command, { ...options, encoding: 'utf8' } as any, (err, stdout: string, stderr: string) => {
if (disposeCancellationListener) {
disposeCancellationListener();
disposeCancellationListener = null;
}

if (err) {
return e(err);
}
c({ stdout, stderr });
});

if (cancellationToken) {
disposeCancellationListener = cancellationToken.subscribe((err: any) => {
child.kill();
e(err);
});
}
});
}
38 changes: 38 additions & 0 deletions src/managers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PackageManager } from "./manager";
import { pmNone } from "./none";
import { pmNPM } from "./npm";
import { pmYarn } from "./yarn";

// Reminder: scr/api.ts (PackageManager enum).
const managers = ['none', 'npm', 'yarn'] as const
export const Managers = new Set(managers);
export type PackageManagerLiteral = typeof managers[number];

/**
* Get package manager by preference.
* @returns Package manager implementation.
*/
export function getPackageManager(
preference: PackageManagerLiteral = "npm",
): PackageManager {
const choice = {
"none": pmNone,
"npm": pmNPM,
"yarn": pmYarn,
} as Record<PackageManagerLiteral, PackageManager>

return choice[preference]
}

/**
* Throws only for strings that are not valid package managers.
* `undefiend` is allowed.
*/
export function assertPackageManager(packageManager: string | undefined): asserts packageManager is PackageManagerLiteral | undefined {
if (packageManager === undefined) {
return
}
if (!Managers.has(packageManager as PackageManagerLiteral)) {
throw new Error(`'${packageManager}' is not a supported package manager. Valid managers: ${[...Managers].join(', ')}`);
}
}
49 changes: 49 additions & 0 deletions src/managers/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { CancellationToken } from "../util";

/**
* Interface for package manager implementations.
* Using interface to force explicit implementations.
*/
export abstract class PackageManager {
/**
* The binary name of the package manager.
* @example 'yarn' | 'npm' | 'bun'
*/
abstract binaryName: string;

/**
* Get the version of the package manager itself.
*/
abstract selfVersion(cancellationToken?: CancellationToken): Promise<string>;

/**
* Check if the package manager version and configs are compatible.
*/
abstract selfCheck(cancellationToken?: CancellationToken): Promise<void>;

/**
* Get the command to run a script.
*/
abstract commandRun(scriptName: string): string;

/**
* Get the command to install a package.
*/
abstract commandInstall(packageName: string, global: boolean): string;

/**
* Request the latest version of a package from the registry.
*/
abstract pkgRequestLatest(name: string, cancellationToken?: CancellationToken): Promise<string>;

/**
* Get the production dependencies of a package.
*/
abstract pkgProdDependencies(cwd: string, packagedDependencies?: string[]): Promise<string[]>;

/**
* Get the files of production dependencies of a package.
* Should use pkgProdDependencies first to get the dependencies.
*/
abstract pkgProdDependenciesFiles(cwd: string, deps: string[], followSymlinks?: boolean): Promise<string[]>;
}
11 changes: 11 additions & 0 deletions src/managers/none.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PackageManagerNpm } from './npm';

class PackageManagerNone extends PackageManagerNpm {
binaryName = ""

async pkgProdDependencies(cwd: string, _?: string[]): Promise<string[]> {
return [cwd]
}
}

export const pmNone = new PackageManagerNone()
Loading