generator-code
Version:
Yeoman generator for Visual Studio Code extensions.
312 lines (269 loc) • 12.4 kB
JavaScript
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
'use strict';
import Generator from 'yeoman-generator';
import yosay from 'yosay';
import { fileURLToPath } from 'url';
import * as path from 'path';
import * as env from './env.js';
import which from 'which';
import colortheme from './generate-colortheme.js';
import commandjs from './generate-command-js.js';
import commandts from './generate-command-ts.js';
import commandweb from './generate-command-web.js';
import extensionpack from './generate-extensionpack.js';
import keymap from './generate-keymap.js';
import language from './generate-language.js';
import localization from './generate-localization.js';
import notebook from './generate-notebook-renderer.js';
import snippets from './generate-snippets.js';
/**
* @typedef {{
* insiders: boolean,
* installDependencies: boolean,
* dependencyVersions: Object.<string, string>,
* dep: (name: string) => string,
* vsCodeEngine: string,
* type: string,
* name: string,
* description: string,
* displayName: string,
* pkgManager: 'npm' | 'yarn' | 'pnpm',
* gitInit: boolean,
* bundler: 'webpack' | 'esbuild' | 'unbundled',
* proposedAPI: boolean,
* }} ExtensionConfig
*
* @typedef {{
* id: string,
* insidersName?: string,
* aliases: string[],
* name: string,
* update?: boolean,
* prompting: (generator: Generator, extensionConfig: ExtensionConfig) => Promise<void>,
* writing: (generator: Generator, extensionConfig: ExtensionConfig) => void,
* endMessage?: (generator: Generator, extensionConfig: ExtensionConfig) => void,
* }} ExtensionGenerator
*/
/**
* @type {ExtensionGenerator[]}
*/
const extensionGenerators = [
commandts, commandjs, colortheme, language, snippets, keymap, extensionpack, localization,
commandweb, notebook
]
export default class extends Generator {
/**
* @type {ExtensionConfig}
*/
extensionConfig;
constructor(args, opts) {
super(args, opts);
this.description = 'Generates a Visual Studio Code extension ready for development.';
this.argument('destination', { type: String, required: false, description: `\n The folder to create the extension in, absolute or relative to the current working directory.\n Use '.' for the current folder. If not provided, defaults to a folder with the extension display name.\n ` })
this.option('insiders', { type: Boolean, alias: 'i', description: 'Show the insiders options for the generator' });
this.option('quick', { type: Boolean, alias: 'q', description: 'Quick mode, skip all optional prompts and use defaults' });
this.option('open', { type: Boolean, alias: 'o', description: 'Open the generated extension in Visual Studio Code' });
this.option('openInInsiders', { type: Boolean, alias: 'O', description: 'Open the generated extension in Visual Studio Code Insiders' });
this.option('skipOpen', { type: Boolean, alias:'s', description: 'Skip opening the generated extension in Visual Studio Code' });
this.option('extensionType', { type: String, alias: 't', description: extensionGenerators.slice(0, 6).map(e => e.aliases[0]).join(', ') + '...' });
this.option('extensionDisplayName', { type: String, alias: 'n', description: 'Display name of the extension' });
this.option('extensionId', { type: String, description: 'Id of the extension' });
this.option('extensionDescription', { type: String, description: 'Description of the extension' });
this.option('pkgManager', { type: String, description: `'npm', 'yarn' or 'pnpm'` });
this.option('bundler', { type: String, description: `Bundle the extension: 'webpack', 'esbuild'` });
this.option('gitInit', { type: Boolean, description: `Initialize a git repo` });
this.option('snippetFolder', { type: String, description: `Snippet folder location` });
this.option('snippetLanguage', { type: String, description: `Snippet language` });
this.extensionConfig = Object.create(null);
this.extensionConfig.installDependencies = false;
this.extensionConfig.insiders = false;
this.extensionGenerator = undefined;
this.abort = false;
}
async initializing() {
if (this.options['insiders']) {
this.extensionConfig.insiders = true;
}
// Welcome
if (!this.extensionConfig.insiders) {
this.log(yosay('Welcome to the Visual Studio Code Extension generator!'));
} else {
this.log(yosay('Welcome to the Visual Studio Code Insiders Extension generator!'));
}
const destination = this.options['destination'];
if (destination) {
const folderPath = path.resolve(this.destinationPath(), destination);
this.destinationRoot(folderPath);
}
// evaluateEngineVersion
const dependencyVersions = await env.getDependencyVersions();
this.extensionConfig.dependencyVersions = dependencyVersions;
this.extensionConfig.dep = function (name) {
const version = dependencyVersions[name];
if (typeof version === 'undefined') {
throw new Error(`Module ${name} is not listed in env.js`);
}
return `${JSON.stringify(name)}: ${JSON.stringify(version)}`;
};
this.extensionConfig.vsCodeEngine = await env.getLatestVSCodeVersion();
}
async prompting() {
// Ask for extension type
const extensionType = this.options['extensionType'];
if (extensionType) {
const extensionTypeId = 'ext-' + extensionType;
const extensionGenerator = extensionGenerators.find(g => g.aliases.indexOf(extensionType) !== -1);
if (extensionGenerator) {
this.extensionConfig.type = extensionGenerator.id;
} else {
this.log("Invalid extension type: " + extensionType + '\nPossible types are: ' + extensionGenerators.map(g => g.aliases.join(', ')).join(', '));
this.abort = true;
}
} else {
const choices = [];
for (const g of extensionGenerators) {
const name = this.extensionConfig.insiders ? g.insidersName : g.name;
if (name) {
choices.push({ name, value: g.id })
}
}
this.extensionConfig.type = (await this.prompt({
type: 'list',
name: 'type',
message: 'What type of extension do you want to create?',
pageSize: choices.length,
choices,
})).type;
}
this.extensionGenerator = extensionGenerators.find(g => g.id === this.extensionConfig.type);
try {
await this.extensionGenerator.prompting(this, this.extensionConfig);
} catch (e) {
console.log(e);
this.abort = true;
}
}
// Write files
writing() {
if (this.abort) {
return;
}
if (!this.options['destination'] && !this.extensionGenerator.update) {
this.destinationRoot(this.destinationPath(this.extensionConfig.name))
}
this.env.cwd = this.destinationPath();
this.log();
this.log(`Writing in ${this.destinationPath()}...`);
const currentFilename = fileURLToPath(import.meta.url);
this.sourceRoot(path.join(currentFilename, '../templates/' + this.extensionConfig.type));
return this.extensionGenerator.writing(this, this.extensionConfig);
}
// Installation
install() {
if (this.abort) {
// @ts-ignore
this.env.options.skipInstall = true;
return;
}
if (this.extensionConfig.installDependencies) {
// @ts-ignore
this.env.options.nodePackageManager = this.extensionConfig.pkgManager;
} else {
// @ts-ignore
this.env.options.skipInstall = true;
}
}
// End
async end() {
if (this.abort) {
return;
}
if (this.extensionGenerator.update) {
this.log('');
this.log('Your extension has been updated!');
this.log('');
this.log('To start editing with Visual Studio Code, use the following commands:');
this.log('');
if (!this.extensionConfig.insiders) {
this.log(' code .');
} else {
this.log(' code-insiders .');
}
this.log(` ${this.extensionConfig.pkgManager} run compile-web`);
this.log('');
return;
}
// Git init
if (this.extensionConfig.gitInit) {
await this.spawn('git', ['init', '--quiet']);
}
if (this.extensionConfig.proposedAPI) {
await this.spawn(this.extensionConfig.pkgManager, ['run', 'update-proposed-api']);
}
this.log('');
this.log('Your extension ' + this.extensionConfig.name + ' has been created!');
this.log('');
const [codeStableLocation, codeInsidersLocation] = await Promise.all([which('code').catch(() => undefined), which('code-insiders').catch(() => undefined)]);
if (!this.extensionConfig.insiders && !this.options['open'] && !this.options['openInInsiders'] && !this.options['quick'] && !this.options['skipOpen']) {
const cdLocation = this.options['destination'] || this.extensionConfig.name;
this.log('To start editing with Visual Studio Code, use the following commands:');
this.log('');
if (!this.extensionConfig.insiders) {
this.log(' code ' + cdLocation);
} else {
this.log(' code-insiders ' + cdLocation);
}
this.log('');
}
this.log('Open vsc-extension-quickstart.md inside the new extension for further instructions');
this.log('on how to modify, test and publish your extension.');
this.log('');
if (this.extensionGenerator.endMessage) {
this.extensionGenerator.endMessage(this, this.extensionConfig);
}
this.log('For more information, also visit http://code.visualstudio.com and follow us @code.');
this.log('\r\n');
if (this.options['skipOpen']) {
return;
}
if (this.options["open"]) {
if (codeStableLocation) {
this.log(`Opening ${this.destinationPath()} in Visual Studio Code...`);
await this.spawn(codeStableLocation, [this.destinationPath()]);
} else {
this.log(`'code' command not found.`);
}
} else if (this.options["openInInsiders"]) {
if (codeInsidersLocation) {
this.log(`Opening ${this.destinationPath()} with Visual Studio Code Insiders...`);
await this.spawn(codeInsidersLocation, [this.destinationPath()]);
} else {
this.log(`'code-insiders' command not found.`);
}
} else if (codeInsidersLocation || codeStableLocation) {
if (this.options["quick"]) {
await this.spawn(codeInsidersLocation || codeStableLocation, [this.destinationPath()]);
} else {
const choices = [];
if (codeInsidersLocation) {
choices.push({ name: "Open with `code-insiders`", value: codeInsidersLocation });
}
if (codeStableLocation) {
choices.push({ name: "Open with `code`", value: codeStableLocation });
}
choices.push({ name: "Skip", value: 'skip' });
const answer = await this.prompt({
type: "list",
name: "openWith",
message: "Do you want to open the new folder with Visual Studio Code?",
choices
});
if (answer && answer.openWith && answer.openWith !== 'skip') {
await this.spawn(answer.openWith, [this.destinationPath()]);
}
}
}
}
}