adopt built-in extensions

This commit is contained in:
João Moreno 2021-02-01 14:29:07 +01:00
parent ec805db5ed
commit 38db1778aa
3 changed files with 228 additions and 194 deletions

View file

@ -1,207 +1,127 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const fs = require('fs').promises;
const path = require('path');
const cp = require('child_process');
const os = require('os');
const mkdirp = require('mkdirp');
const product = require('../product.json');
const root = path.resolve(path.join(__dirname, '..', '..'));
const exists = (path) => fs.stat(path).then(() => true, () => false);
const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
async function readControlFile() {
try {
return JSON.parse(await fs.readFile(controlFilePath, 'utf8'));
} catch (err) {
return {};
}
}
async function writeControlFile(control) {
await mkdirp(path.dirname(controlFilePath));
await fs.writeFile(controlFilePath, JSON.stringify(control, null, ' '));
}
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = require("fs");
const path = require("path");
const cp = require("child_process");
const commander_1 = require("commander");
const root = path.resolve(path.join(__dirname, '..'));
var ExtensionType;
(function (ExtensionType) {
ExtensionType["Grammar"] = "grammar";
ExtensionType["Theme"] = "theme";
ExtensionType["Misc"] = "misc";
})(ExtensionType || (ExtensionType = {}));
// const exists = (path) => fs.stat(path).then(() => true, () => false);
// const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
// async function readControlFile() {
// try {
// return JSON.parse(await fs.readFile(controlFilePath, 'utf8'));
// } catch (err) {
// return {};
// }
// }
// async function writeControlFile(control) {
// await mkdirp(path.dirname(controlFilePath));
// await fs.writeFile(controlFilePath, JSON.stringify(control, null, ' '));
// }
async function exec(cmd, args, opts = {}) {
return new Promise((c, e) => {
const child = cp.spawn(cmd, args, { stdio: 'inherit', env: process.env, ...opts });
child.on('close', code => code === 0 ? c() : e(`Returned ${code}`));
});
return new Promise((c, e) => {
const child = cp.spawn(cmd, args, Object.assign({ stdio: 'inherit', env: process.env }, opts));
child.on('close', code => code === 0 ? c() : e(`Returned ${code}`));
});
}
function getFolderPath(extDesc) {
const folder = extDesc.repo.replace(/.*\//, '');
return folderPath = path.join(root, folder);
function getExtensionType(packageJson) {
var _a, _b, _c;
if (((_a = packageJson.contributes) === null || _a === void 0 ? void 0 : _a.themes) || ((_b = packageJson.contributes) === null || _b === void 0 ? void 0 : _b.iconThemes)) {
return "theme" /* Theme */;
}
else if ((_c = packageJson.contributes) === null || _c === void 0 ? void 0 : _c.grammars) {
return "grammar" /* Grammar */;
}
else {
return "misc" /* Misc */;
}
}
async function getExtensionType(folderPath) {
const pkg = JSON.parse(await fs.readFile(path.join(folderPath, 'package.json'), 'utf8'));
if (pkg['contributes']['themes'] || pkg['contributes']['iconThemes']) {
return 'theme';
} else if (pkg['contributes']['grammars']) {
return 'grammar';
} else {
return 'misc';
}
async function getExtension(extensionPath) {
const packageJsonPath = path.join(extensionPath, 'package.json');
const packageJson = JSON.parse(await fs_1.promises.readFile(packageJsonPath, 'utf8'));
const type = getExtensionType(packageJson);
return {
name: packageJson.name,
path: extensionPath,
type
};
}
async function initExtension(extDesc) {
const folderPath = getFolderPath(extDesc);
if (!await exists(folderPath)) {
console.log(`⏳ git clone: ${extDesc.name}`);
await exec('git', ['clone', `${extDesc.repo}.git`], { cwd: root });
}
const type = await getExtensionType(folderPath);
return { path: folderPath, type, ...extDesc };
function getExtensions() {
return __asyncGenerator(this, arguments, function* getExtensions_1() {
const extensionsPath = path.join(root, 'extensions');
const children = yield __await(fs_1.promises.readdir(extensionsPath));
for (const child of children) {
try {
yield yield __await(yield __await(getExtension(path.join(extensionsPath, child))));
}
catch (err) {
if (/ENOENT|ENOTDIR/.test(err.message)) {
continue;
}
throw err;
}
}
});
}
async function createWorkspace(type, extensions) {
const workspaceName = `vscode-${type}-extensions.code-workspace`;
const workspacePath = path.join(root, workspaceName);
const workspace = { folders: extensions.map(ext => ({ path: path.basename(ext.path) })) };
if (!await exists(workspacePath)) {
console.log(`✅ create workspace: ${workspaceName}`);
}
await fs.writeFile(workspacePath, JSON.stringify(workspace, undefined, ' '));
}
async function init() {
const extensions = [];
for (const extDesc of product.builtInExtensions) {
extensions.push(await initExtension(extDesc));
}
await createWorkspace('all', extensions);
const byType = extensions
.reduce((m, e) => m.set(e.type, [...(m.get(e.type) || []), e]), new Map());
for (const [type, extensions] of byType) {
await createWorkspace(type, extensions);
}
return byType;
}
async function status() {
const byType = await init();
const control = await readControlFile();
for (const [type, extensions] of byType) {
console.log(`${type} (${extensions.length} extensions):`);
const maxWidth = Math.max(...extensions.map(e => e.name.length));
for (const ext of extensions) {
console.log(` ${ext.name.padEnd(maxWidth, ' ')}${control[ext.name]}`);
}
}
console.log(`total: ${product.builtInExtensions.length} extensions`);
}
async function each([cmd, ...args], opts) {
await init();
for (const extDesc of product.builtInExtensions) {
const folderPath = getFolderPath(extDesc);
if (opts.type) {
const type = await getExtensionType(folderPath);
if (type !== opts.type) {
continue;
}
}
console.log(`👉 ${extDesc.name}`);
await exec(cmd, args, { cwd: folderPath });
}
var e_1, _a;
try {
for (var _b = __asyncValues(getExtensions()), _c; _c = await _b.next(), !_c.done;) {
const extension = _c.value;
if (opts.type && extension.type !== opts.type) {
continue;
}
console.log(`👉 ${extension.name}`);
await exec(cmd, args, { cwd: extension.path });
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) await _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
async function _link(extensions, opts, fn) {
await init();
const control = await readControlFile();
for (const extDesc of product.builtInExtensions) {
if (extensions.length > 0 && extensions.indexOf(extDesc.name) === -1) {
continue;
}
if (opts.type) {
const folderPath = getFolderPath(extDesc);
const type = await getExtensionType(folderPath);
if (type !== opts.type) {
continue;
}
}
await fn(control, extDesc);
}
await writeControlFile(control);
}
async function link(extensions, opts) {
await _link(extensions, opts, async (control, extDesc) => {
const ext = await initExtension(extDesc);
control[extDesc.name] = ext.path;
console.log(`👉 link: ${extDesc.name}${ext.path}`);
});
}
async function unlink(extensions, opts) {
await _link(extensions, opts, async (control, extDesc) => {
control[extDesc.name] = 'marketplace';
console.log(`👉 unlink: ${extDesc.name}`);
});
}
if (require.main === module) {
const { program } = require('commander');
program.version('0.0.1');
program
.command('init')
.description('Initialize workspace with built-in extensions')
.action(init);
program
.command('status')
.description('Print extension status')
.action(status);
program
.command('each <command...>')
.option('-t, --type <type>', 'Specific type only')
.description('Run a command in each extension repository')
.allowUnknownOption()
.action(each);
program
.command('link [extensions...]')
.option('-t, --type <type>', 'Specific type only')
.description('Link with code-oss')
.action(link);
program
.command('unlink [extensions...]')
.option('-t, --type <type>', 'Specific type only')
.description('Unlink from code-oss')
.action(unlink);
program.parseAsync(process.argv);
commander_1.program.version('0.0.1');
commander_1.program
.command('each <command...>')
.option('-t, --type <type>', 'Specific type only')
.description('Run a command in each extension repository')
.allowUnknownOption()
.action(each);
commander_1.program.parseAsync(process.argv).catch(err => {
console.error(err);
process.exit(1);
});
}

113
build/ext.ts Normal file
View file

@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { promises as fs } from 'fs';
import * as path from 'path';
import * as cp from 'child_process';
import { program } from 'commander';
const root = path.resolve(path.join(__dirname, '..'));
const enum ExtensionType {
Grammar = 'grammar',
Theme = 'theme',
Misc = 'misc'
}
interface IExtension {
readonly name: string;
readonly path: string;
readonly type: ExtensionType;
}
// const exists = (path) => fs.stat(path).then(() => true, () => false);
// const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json');
// async function readControlFile() {
// try {
// return JSON.parse(await fs.readFile(controlFilePath, 'utf8'));
// } catch (err) {
// return {};
// }
// }
// async function writeControlFile(control) {
// await mkdirp(path.dirname(controlFilePath));
// await fs.writeFile(controlFilePath, JSON.stringify(control, null, ' '));
// }
async function exec(cmd: string, args: string[], opts: cp.SpawnOptions = {}): Promise<void> {
return new Promise((c, e) => {
const child = cp.spawn(cmd, args, { stdio: 'inherit', env: process.env, ...opts });
child.on('close', code => code === 0 ? c() : e(`Returned ${code}`));
});
}
function getExtensionType(packageJson: any): ExtensionType {
if (packageJson.contributes?.themes || packageJson.contributes?.iconThemes) {
return ExtensionType.Theme;
} else if (packageJson.contributes?.grammars) {
return ExtensionType.Grammar;
} else {
return ExtensionType.Misc;
}
}
async function getExtension(extensionPath: string): Promise<IExtension> {
const packageJsonPath = path.join(extensionPath, 'package.json');
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
const type = getExtensionType(packageJson);
return {
name: packageJson.name,
path: extensionPath,
type
};
}
async function* getExtensions(): AsyncGenerator<IExtension, void, any> {
const extensionsPath = path.join(root, 'extensions');
const children = await fs.readdir(extensionsPath);
for (const child of children) {
try {
yield await getExtension(path.join(extensionsPath, child));
} catch (err) {
if (/ENOENT|ENOTDIR/.test(err.message)) {
continue;
}
throw err;
}
}
}
async function each([cmd, ...args]: string[], opts: { type?: string }) {
for await (const extension of getExtensions()) {
if (opts.type && extension.type !== opts.type) {
continue;
}
console.log(`👉 ${extension.name}`);
await exec(cmd, args, { cwd: extension.path });
}
}
if (require.main === module) {
program.version('0.0.1');
program
.command('each <command...>')
.option('-t, --type <type>', 'Specific type only')
.description('Run a command in each extension repository')
.allowUnknownOption()
.action(each);
program.parseAsync(process.argv).catch(err => {
console.error(err);
process.exit(1);
});
}

View file

@ -44,6 +44,7 @@ module.exports.indentationFilter = [
'!src/vs/base/node/cpuUsage.sh',
'!test/unit/assert.js',
'!resources/linux/snap/electron-launch',
'!build/ext.js',
// except specific folders
'!test/automation/out/**',