diff --git a/.gitignore b/.gitignore index 1738bbf6d..677a128e8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ yarn-error.log *.theia asset-* .eslintcache +plugins/vscode-* # MacOS .DS_Store diff --git a/extensions/eclipse-che-theia-plugin-ext/src/node/che-plugin-api-provider.ts b/extensions/eclipse-che-theia-plugin-ext/src/node/che-plugin-api-provider.ts index 437b05f70..31b492df3 100644 --- a/extensions/eclipse-che-theia-plugin-ext/src/node/che-plugin-api-provider.ts +++ b/extensions/eclipse-che-theia-plugin-ext/src/node/che-plugin-api-provider.ts @@ -8,8 +8,6 @@ * SPDX-License-Identifier: EPL-2.0 ***********************************************************************/ -import * as path from 'path'; - import { ExtPluginApi, ExtPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution'; import { injectable } from 'inversify'; @@ -23,7 +21,7 @@ export class ChePluginApiProvider implements ExtPluginApiProvider { initFunction: 'initializeApi', initVariable: 'che_api_provider', }, - backendInitPath: path.join('@eclipse-che/theia-plugin-ext/lib/plugin/node/che-api-node-provider.js'), + backendInitPath: require.resolve('@eclipse-che/theia-plugin-ext/lib/plugin/node/che-api-node-provider'), }; } } diff --git a/extensions/eclipse-che-theia-plugin-remote/tests/node/che-content-aware-utils.spec.ts b/extensions/eclipse-che-theia-plugin-remote/tests/node/che-content-aware-utils.spec.ts index e1751fb14..05648fc8c 100644 --- a/extensions/eclipse-che-theia-plugin-remote/tests/node/che-content-aware-utils.spec.ts +++ b/extensions/eclipse-che-theia-plugin-remote/tests/node/che-content-aware-utils.spec.ts @@ -13,6 +13,8 @@ import { overrideUri } from '../../src/node/che-content-aware-utils'; describe('Test overrideUri', () => { test('Should return the same uri if machine name is not defined', () => { + process.env.CHE_MACHINE_NAME = ''; + const uri = URI.from({ scheme: 'file', path: '/path', diff --git a/generator/package.json b/generator/package.json index f95495faa..b6859c1f8 100644 --- a/generator/package.json +++ b/generator/package.json @@ -30,6 +30,9 @@ "@types/yargs": "12.0.1", "json2yaml": "^1.1.0" }, + "resolutions": { + "temp": "0.8.4" + }, "files": [ "dist", "src" diff --git a/generator/src/generate-assembly.ts b/generator/src/generate-assembly.ts new file mode 100644 index 000000000..e37eeb654 --- /dev/null +++ b/generator/src/generate-assembly.ts @@ -0,0 +1,111 @@ +/********************************************************************** + * Copyright (c) 2018-2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +import * as fs from 'fs-extra'; +import * as mustache from 'mustache'; +import * as path from 'path'; + +import { CommandBuilder } from 'yargs'; +import { Init } from './init'; +import { Logger } from './logger'; +import { getFullPackageName } from './yarn'; +import { rewriteJson } from './json-utils'; + +export const builder: CommandBuilder = { + 'che-theia': { + describe: 'Path of the che-theia project source', + requiresArg: true, + type: 'string', + demandOption: false, + }, + 'theia-version': { + describe: 'Theia version to set', + requiresArg: true, + type: 'string', + demandOption: false, + default: 'next', + }, +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function handleCommand(args: any) { + const cheTheiaDir = args['che-theia '] || process.cwd(); + const theiaVersion = args['theia-version']!; + const monacoVersion = await getFullPackageName(cheTheiaDir, Init.MONACO_CORE_PKG); + + if (!monacoVersion) { + throw new Error(`Package not found: ${Init.MONACO_CORE_PKG}`); + } + + const assemblyDir = path.resolve(cheTheiaDir, 'assembly'); + await generateAssembly(assemblyDir, { + theiaVersion, + monacoVersion, + configDirPrefix: '../../', + packageRefPrefix: '../extensions/', + }); + + const extensionsDir = path.resolve(__dirname, '../../extensions'); + const folderNames = await fs.readdir(extensionsDir); + const extensions = new Map(); + for (const folderName of folderNames) { + const pkgJson = require(path.resolve(extensionsDir, folderName, 'package.json')); + extensions.set(pkgJson.name, pkgJson.version); + } + + const theiaPlugins = await fs.readJson(path.resolve(__dirname, '../src/templates/theiaPlugins.json')); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rewriteJson(path.resolve(assemblyDir, 'package.json'), (json: any) => { + const deps = json.dependencies || {}; + extensions.forEach((version: string, name: string) => { + deps[name] = version; + }); + json.dependencies = deps; + json.scripts['download:plugins'] = 'theia download:plugins'; + json.scripts.prepare = json.scripts.prepare + ' && yarn download:plugins'; + json.theiaPluginsDir = '../plugins'; + json.theiaPlugins = theiaPlugins; + }); +} + +export interface AssemblyConfiguration { + theiaVersion: string; + monacoVersion: string; + configDirPrefix: string; + packageRefPrefix: string; +} + +export async function generateAssembly(examplesAssemblyFolder: string, config: AssemblyConfiguration): Promise { + const srcDir = path.resolve(__dirname, '../src'); + const distDir = path.resolve(__dirname, '../dist'); + const templateDir = path.join(srcDir, 'templates'); + const compileTsConfig = path.join(templateDir, 'assembly-compile.tsconfig.mst.json'); + + // generate assembly if does not exists + await fs.ensureDir(examplesAssemblyFolder); + + const template = path.join(templateDir, 'assembly-package.mst.json'); + const target = path.join(examplesAssemblyFolder, 'package.json'); + + await renderTemplate(template, target, config); + await renderTemplate(compileTsConfig, path.join(examplesAssemblyFolder, 'compile.tsconfig.mst.json'), config); + + Logger.info(`copying ${path.join(templateDir, 'cdn')} to ${path.join(examplesAssemblyFolder, 'cdn')}`); + await fs.copy(path.join(templateDir, 'cdn'), path.join(examplesAssemblyFolder, 'cdn')); + Logger.info('distdir=' + distDir); + await fs.copy(path.join(distDir, 'cdn'), path.join(examplesAssemblyFolder, 'cdn')); + await fs.copy(path.join(srcDir, 'scripts'), path.join(examplesAssemblyFolder, 'scripts')); +} + +async function renderTemplate(template: string, target: string, config: Object): Promise { + const content = await fs.readFile(template); + const rendered = mustache.render(content.toString(), config).replace(///g, '/'); + await fs.writeFile(target, rendered); +} diff --git a/generator/src/init.ts b/generator/src/init.ts index 83b783d5a..ae96ce2de 100644 --- a/generator/src/init.ts +++ b/generator/src/init.ts @@ -9,13 +9,12 @@ ***********************************************************************/ import * as fs from 'fs-extra'; -import * as mustache from 'mustache'; import * as path from 'path'; import * as readPkg from 'read-pkg'; import { Command } from './command'; import { ISource } from './init-sources'; -import { Logger } from './logger'; +import { generateAssembly } from './generate-assembly'; /** * Generates the examples/assembly @@ -36,44 +35,19 @@ export class Init { return (await readPkg(path.join(this.rootFolder, 'packages/core/package.json'))).version; } - async getPackageWithVersion(name: string): Promise { - const pkg = JSON.parse( - await new Command(path.resolve(this.rootFolder)).exec(Init.GET_PACKAGE_WITH_VERSION_CMD + name) - ).data.trees[0]; - return pkg ? pkg.name : ''; - } - - async generate(): Promise { - const srcDir = path.resolve(__dirname, '../src'); - const distDir = path.resolve(__dirname, '../dist'); - const templateDir = path.join(srcDir, 'templates'); - const compileTsConfig = path.join(templateDir, 'assembly-compile.tsconfig.json'); - const packageJsonContent = await fs.readFile(path.join(templateDir, 'assembly-package.mst')); - - // generate assembly if does not exists - const rendered = await this.generateAssemblyPackage(packageJsonContent.toString()); - await fs.ensureDir(this.examplesAssemblyFolder); - await fs.writeFile(path.join(this.examplesAssemblyFolder, 'package.json'), rendered); - await fs.copy(compileTsConfig, path.join(this.examplesAssemblyFolder, 'compile.tsconfig.json')); - await fs.copy(path.join(templateDir, 'cdn'), path.join(this.examplesAssemblyFolder, 'cdn')); - Logger.info(distDir); - await fs.copy(path.join(distDir, 'cdn'), path.join(this.examplesAssemblyFolder, 'cdn')); - await fs.copy(path.join(srcDir, 'scripts'), path.join(this.examplesAssemblyFolder, 'scripts')); - + async generate() { + await generateAssembly(this.examplesAssemblyFolder, { + theiaVersion: '^' + (await this.getCurrentVersion()), + monacoVersion: await this.getPackageWithVersion(Init.MONACO_CORE_PKG), + configDirPrefix: '../../packages/@che-', + packageRefPrefix: '../../config/', + }); // Generate checkout folder is does not exist await fs.ensureDir(this.checkoutFolder); // copy build all plugins scripts await fs.ensureDir(this.pluginsFolder); - await fs.copy(path.join(srcDir, 'foreach_yarn'), path.join(this.pluginsFolder, 'foreach_yarn')); - } - - async generateAssemblyPackage(template: string): Promise { - const tags = { - version: await this.getCurrentVersion(), - monacopkg: await this.getPackageWithVersion(Init.MONACO_CORE_PKG), - }; - return mustache.render(template, tags).replace(///g, '/'); + await fs.copy(path.resolve(__dirname, '../src/foreach_yarn'), path.join(this.pluginsFolder, 'foreach_yarn')); } async updadeBuildConfiguration(extensions: ISource[]): Promise { @@ -126,21 +100,28 @@ export class Init { await fs.writeFile(theiaPackagePath, json); } - async updatePluginsConfigurtion(): Promise { + async updatePluginsConfiguration(): Promise { const theiaPackagePath = path.join(this.rootFolder, 'package.json'); const theiaPackage = await readPkg(theiaPackagePath); - theiaPackage['theiaPlugins'] = await this.getPluginsList(); + theiaPackage['theiaPlugins'] = await Init.getPluginsList(); const json = JSON.stringify(theiaPackage, undefined, 2); await fs.writeFile(theiaPackagePath, json); } // eslint-disable-next-line @typescript-eslint/no-explicit-any - private async getPluginsList(): Promise { + private static async getPluginsList(): Promise { const srcDir = path.resolve(__dirname, '../src'); const templateDir = path.join(srcDir, 'templates'); const pluginsJsonContent = await fs.readFile(path.join(templateDir, 'theiaPlugins.json')); return JSON.parse(pluginsJsonContent.toString()); } + + async getPackageWithVersion(name: string): Promise { + const pkg = JSON.parse( + await new Command(path.resolve(this.rootFolder)).exec(Init.GET_PACKAGE_WITH_VERSION_CMD + name) + ).data.trees[0]; + return pkg ? pkg.name : ''; + } } diff --git a/generator/src/json-utils.ts b/generator/src/json-utils.ts new file mode 100644 index 000000000..e773ee460 --- /dev/null +++ b/generator/src/json-utils.ts @@ -0,0 +1,38 @@ +/********************************************************************** + * Copyright (c) 2018-2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +/** + * Simple code to rewrite parts of JSON files + * @author Thomas Mäder + */ + +import * as fs from 'fs-extra'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function rewriteJson(packageJSONPath: string, rewriteFunction: (json: any) => void) { + const json = await fs.readJSON(packageJSONPath); + rewriteFunction(json); + + await fs.writeJson(packageJSONPath, json, { encoding: 'utf-8', spaces: 2 }); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function replaceInSection(section: any, replaceVersion: (key: string) => string | undefined) { + if (section) { + for (const dep in section) { + if (section.hasOwnProperty(dep)) { + const replacement = replaceVersion(dep); + if (replacement) { + section[dep] = replacement; + } + } + } + } +} diff --git a/generator/src/link.ts b/generator/src/link.ts new file mode 100644 index 000000000..2458e280c --- /dev/null +++ b/generator/src/link.ts @@ -0,0 +1,77 @@ +/********************************************************************** + * Copyright (c) 2018-2021 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +/** + * A command to yarn link theia dependencies into che-theia + * @author Thomas Mäder + */ + +import * as fs from 'fs-extra'; +import * as os from 'os'; +import * as path from 'path'; + +import { Command } from './command'; +import { CommandBuilder } from 'yargs'; + +export const builder: CommandBuilder = { + theia: { + describe: 'Path of the theia project source', + requiresArg: true, + type: 'string', + demandOption: false, + }, + 'che-theia': { + describe: 'Path of the che-theia project source', + requiresArg: true, + type: 'string', + demandOption: false, + }, +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function handleCommand(args: any): Promise { + const theiaDir = args.theia || path.resolve(process.cwd(), '../theia'); + const cheTheiaDir = args['che-theia'] || process.cwd(); + + const cfg = await new Command(cheTheiaDir).exec('yarn --silent --json --non-interactive config current'); + + try { + const yarnConfig = JSON.parse(JSON.parse(cfg).data); + let linkDir = yarnConfig['linkFolder'] || path.resolve(os.homedir(), '.yarn/link'); + await fs.ensureDir(linkDir); + linkDir = await fs.realpath(linkDir); + await link(cheTheiaDir, theiaDir, linkDir); + } catch (e) { + console.error(e); + } +} + +export async function link(cheTheiaProjectPath: string, theiaProjectPath: string, yarnLinkFolder: string) { + await linkTheia(yarnLinkFolder, theiaProjectPath); + await linkChe(yarnLinkFolder, cheTheiaProjectPath); +} + +async function linkTheia(yarnLinkFolder: string, theiaProjectPath: string) { + for (const rootName of ['packages', 'dev-packages', 'examples']) { + const rootPath = path.resolve(theiaProjectPath, rootName); + const folderNames = await fs.readdir(rootPath); + for (const folderName of folderNames) { + await new Command(path.resolve(rootPath, folderName)).exec(`yarn link --link-folder=${yarnLinkFolder}`); + } + } +} + +async function linkChe(yarnLinkFolder: string, cheTheiaProjectPath: string) { + const packages = await fs.readdir(path.resolve(yarnLinkFolder, '@theia')); + const cmd = new Command(cheTheiaProjectPath); + for (const pkg of packages) { + await cmd.exec(`yarn link @theia/${pkg}`); + } +} diff --git a/generator/src/templates/assembly-compile.tsconfig.json b/generator/src/templates/assembly-compile.tsconfig.json deleted file mode 100644 index 4d46df871..000000000 --- a/generator/src/templates/assembly-compile.tsconfig.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "extends": "../../configs/base.tsconfig", - "compilerOptions": { - "composite": true, - "rootDir": "src", - "outDir": "lib" - }, - "include": [ - "src" - ], - "references": [ - { - "path": "../../packages/@che-eclipse-che-theia-plugin-ext/tsconfig.json" - }, - { - "path": "../../packages/@che-che-theia-hosted-plugin-manager-extension/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-about/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-activity-tracker/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-dashboard/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-user-preferences/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-git-provisioner/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-plugin-remote/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-preferences-provider-extension/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-terminal/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-logging/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-remote-api/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-messaging/tsconfig.json" - }, - { - "path": "../../packages/@che-eclipse-che-theia-mini-browser/tsconfig.json" - } - ] -} diff --git a/generator/src/templates/assembly-compile.tsconfig.mst.json b/generator/src/templates/assembly-compile.tsconfig.mst.json new file mode 100644 index 000000000..cf94075e6 --- /dev/null +++ b/generator/src/templates/assembly-compile.tsconfig.mst.json @@ -0,0 +1,55 @@ +{ + "extends": "{{ configDirPrefix}}base.tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-plugin-ext/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}che-theia-hosted-plugin-manager-extension/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-about/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-activity-tracker/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-dashboard/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-user-preferences/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-git-provisioner/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-plugin-remote/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-preferences-provider-extension/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-terminal/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-logging/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-remote-api/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-messaging/tsconfig.json" + }, + { + "path": "{{ packageRefPrefix}}eclipse-che-theia-mini-browser/tsconfig.json" + } + ] +} diff --git a/generator/src/templates/assembly-package.mst b/generator/src/templates/assembly-package.mst.json similarity index 55% rename from generator/src/templates/assembly-package.mst rename to generator/src/templates/assembly-package.mst.json index 242135285..b38b7c9e5 100644 --- a/generator/src/templates/assembly-package.mst +++ b/generator/src/templates/assembly-package.mst.json @@ -1,7 +1,7 @@ { "private": true, "name": "@eclipse-che/theia-assembly", - "version": "{{ version }}", + "version": "0.0.1", "theia": { "frontend": { "config": { @@ -19,33 +19,33 @@ } }, "dependencies": { - "@theia/callhierarchy": "^{{ version }}", - "@theia/console": "^{{ version }}", - "@theia/core": "^{{ version }}", - "@theia/debug": "^{{ version }}", - "@theia/editor": "^{{ version }}", - "@theia/file-search": "^{{ version }}", - "@theia/filesystem": "^{{ version }}", - "@theia/keymaps": "^{{ version }}", - "@theia/markers": "^{{ version }}", - "@theia/messages": "^{{ version }}", - "@theia/metrics": "^{{ version }}", - "@theia/mini-browser": "^{{ version }}", - "@theia/monaco": "^{{ version }}", - "@theia/navigator": "^{{ version }}", - "@theia/outline-view": "^{{ version }}", - "@theia/output": "^{{ version }}", - "@theia/plugin-dev": "^{{ version }}", - "@theia/plugin-ext": "^{{ version }}", - "@theia/plugin-ext-vscode": "^{{ version }}", - "@theia/preferences": "^{{ version }}", - "@theia/preview": "^{{ version }}", - "@theia/process": "^{{ version }}", - "@theia/search-in-workspace": "^{{ version }}", - "@theia/task": "^{{ version }}", - "@theia/userstorage": "^{{ version }}", - "@theia/variable-resolver": "^{{ version }}", - "@theia/workspace": "^{{ version }}" + "@theia/callhierarchy": "{{ theiaVersion }}", + "@theia/console": "{{ theiaVersion }}", + "@theia/core": "{{ theiaVersion }}", + "@theia/debug": "{{ theiaVersion }}", + "@theia/editor": "{{ theiaVersion }}", + "@theia/file-search": "{{ theiaVersion }}", + "@theia/filesystem": "{{ theiaVersion }}", + "@theia/keymaps": "{{ theiaVersion }}", + "@theia/markers": "{{ theiaVersion }}", + "@theia/messages": "{{ theiaVersion }}", + "@theia/metrics": "{{ theiaVersion }}", + "@theia/mini-browser": "{{ theiaVersion }}", + "@theia/monaco": "{{ theiaVersion }}", + "@theia/navigator": "{{ theiaVersion }}", + "@theia/outline-view": "{{ theiaVersion }}", + "@theia/output": "{{ theiaVersion }}", + "@theia/plugin-dev": "{{ theiaVersion }}", + "@theia/plugin-ext": "{{ theiaVersion }}", + "@theia/plugin-ext-vscode": "{{ theiaVersion }}", + "@theia/preferences": "{{ theiaVersion }}", + "@theia/preview": "{{ theiaVersion }}", + "@theia/process": "{{ theiaVersion }}", + "@theia/search-in-workspace": "{{ theiaVersion }}", + "@theia/task": "{{ theiaVersion }}", + "@theia/userstorage": "{{ theiaVersion }}", + "@theia/variable-resolver": "{{ theiaVersion }}", + "@theia/workspace": "{{ theiaVersion }}" }, "bin": { "override-vs-loader": "./scripts/override-vs-loader.js" @@ -53,7 +53,7 @@ "scripts": { "prepare": "yarn run clean && yarn build", "clean": "theia clean && rimraf errorShots", - "build": "theia build --mode production --config cdn/webpack.config.js --env.cdn=./cdn.json --env.monacopkg={{ monacopkg }} && yarn run override-vs-loader", + "build": "theia build --mode production --config cdn/webpack.config.js --env.cdn=./cdn.json --env.monacopkg={{ monacoVersion }} && yarn run override-vs-loader", "override-vs-loader": "override-vs-loader", "watch": "concurrently -n compile,bundle \"theiaext watch --preserveWatchOutput\" \"theia build --watch --mode development\"", "start": "theia start", @@ -67,7 +67,7 @@ "coverage": "yarn coverage:compile && yarn test && yarn coverage:remap && yarn coverage:report:lcov && yarn coverage:report:html" }, "devDependencies": { - "@theia/cli": "^{{ version }}", + "@theia/cli": "{{ theiaVersion }}", "html-webpack-plugin": "^3.2.0", "async-limiter": "^2.0.0" } diff --git a/generator/src/update-dependencies.ts b/generator/src/update-dependencies.ts new file mode 100644 index 000000000..8b6139122 --- /dev/null +++ b/generator/src/update-dependencies.ts @@ -0,0 +1,88 @@ +/********************************************************************** + * Copyright (c) 2018-2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +/** + * Simple code to rewrite versions of a dependency in package.json files + * @author Thomas Mäder + */ + +import * as glob from 'glob'; +import * as path from 'path'; + +import { CommandBuilder } from 'yargs'; +import { promisify } from 'util'; +import { rewriteJson } from './json-utils'; + +export const builder: CommandBuilder = { + 'theia-version': { + describe: 'The version of Theia to uses in che-theia', + requiresArg: true, + type: 'string', + demandOption: true, + }, + 'che-theia': { + describe: 'Path of the che-theia project source', + requiresArg: true, + type: 'string', + demandOption: false, + }, +}; + +const EXCLUSIONS = ['@theia/plugin-packager']; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export async function handleCommand(args: any) { + const cheTheiaDir = args['che-theia '] || process.cwd(); + const theiaVersion = args['theia-version']; + + const packageJsons = await promisify(glob)(cheTheiaDir + '/extensions/*/package.json'); + packageJsons.push(...(await promisify(glob)(cheTheiaDir + '/plugins/*/package.json'))); + packageJsons.push(path.resolve(cheTheiaDir, 'generator/package.json')); + packageJsons.push(path.resolve(cheTheiaDir, 'assembly/package.json')); + packageJsons.push(path.resolve(cheTheiaDir, 'package.json')); + + for (const packageJson of packageJsons) { + await updateDependencies(packageJson, packageName => { + if (packageName.startsWith('@theia') && !EXCLUSIONS.includes(packageName)) { + return theiaVersion; + } + }); + } +} + +async function updateDependencies( + packageJSONPath: string, + replaceVersion: (packageName: string) => string | undefined +) { + try { + await rewriteJson(packageJSONPath, pkgJson => { + // we're assuming the package.json is well formed + replaceInSection(pkgJson.dependencies, replaceVersion); + replaceInSection(pkgJson.devDependencies, replaceVersion); + }); + } catch (e) { + console.warn('could not find package json: ' + packageJSONPath); + return; + } +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function replaceInSection(section: any, replaceVersion: (packageName: string) => string | undefined) { + if (section) { + for (const dep in section) { + if (section.hasOwnProperty(dep)) { + const replacement = replaceVersion(dep); + if (replacement) { + section[dep] = replacement; + } + } + } + } +} diff --git a/generator/src/yargs.ts b/generator/src/yargs.ts index 6fdf73154..0c26ef162 100644 --- a/generator/src/yargs.ts +++ b/generator/src/yargs.ts @@ -11,6 +11,10 @@ import * as path from 'path'; import * as yargs from 'yargs'; +import { builder as generateArgsBuilder, handleCommand as handleGenerateCommand } from './generate-assembly'; +import { handleCommand as handleLinkCommand, builder as linkArgsBuilder } from './link'; +import { handleCommand as handleUpdateDepsCommand, builder as updateDepsBuilder } from './update-dependencies'; + import { Cdn } from './cdn'; import { Clean } from './clean'; import { CliError } from './cli-error'; @@ -29,7 +33,7 @@ const commandArgs = yargs .usage('$0 [args]') .command({ command: 'init', - describe: 'Initialize current theia to beahve like a Che/Theia', + describe: 'Initialize current theia to behave like a Che/Theia', builder: InitSources.argBuilder, handler: async args => { try { @@ -40,7 +44,7 @@ const commandArgs = yargs const init = new Init(process.cwd(), assemblyFolder, cheFolder, pluginsFolder); const version = await init.getCurrentVersion(); await init.generate(); - await init.updatePluginsConfigurtion(); + await init.updatePluginsConfiguration(); const initSources = new InitSources( process.cwd(), packagesFolder, @@ -101,6 +105,25 @@ const commandArgs = yargs } }, }) + // commands related to the "linkless" mode of building che-theia + .command({ + command: 'link', + describe: 'Yarn link to a given theia source tree', + handler: handleLinkCommand, + builder: linkArgsBuilder, + }) + .command({ + command: 'generate', + describe: 'Yarn link to a given theia source tree', + handler: handleGenerateCommand, + builder: generateArgsBuilder, + }) + .command({ + command: 'update-dependencies', + describe: 'Update the theia version in all package.json files', + handler: handleUpdateDepsCommand, + builder: updateDepsBuilder, + }) .help() .strict() .demandCommand().argv; diff --git a/generator/src/yarn.ts b/generator/src/yarn.ts index 81f7ef05c..911239d73 100644 --- a/generator/src/yarn.ts +++ b/generator/src/yarn.ts @@ -12,6 +12,7 @@ import * as path from 'path'; import { CliError } from './cli-error'; import { Command } from './command'; +import { Init } from './init'; import { Logger } from './logger'; /** @@ -206,3 +207,20 @@ export interface IYarnNode { name: string; children: IYarnNode[]; } + +/** + * Returns the full package name of an installed package or the emtpy string if not installed + */ +export async function getFullPackageName(rootFolder: string, name: string): Promise { + const pkg = JSON.parse(await new Command(path.resolve(rootFolder)).exec(Init.GET_PACKAGE_WITH_VERSION_CMD + name)) + .data.trees[0]; + return pkg ? pkg.name : ''; +} + +/** + * Returns the version of an installed package or the emtpy string if not installed + */ +export async function getPackageVersion(rootFolder: string, name: string) { + const nameWithVersion = getFullPackageName(rootFolder, name); + return (await nameWithVersion).split('@').pop() || ''; +} diff --git a/generator/tests/init-sources/assembly-example/package.json b/generator/tests/init-sources/assembly-example/package.json index b1633e9aa..e13aacf02 100644 --- a/generator/tests/init-sources/assembly-example/package.json +++ b/generator/tests/init-sources/assembly-example/package.json @@ -16,7 +16,7 @@ "@types/mocha": "^2.2.41", "@types/node": "^12.0.0", "@types/sinon": "^2.3.5", - "@types/temp": "^0.8.29", + "@types/temp": "^0.9.1", "@types/webdriverio": "^4.7.0", "chai": "^4.1.0", "chai-string": "^1.4.0", diff --git a/generator/tests/link.spec.ts b/generator/tests/link.spec.ts new file mode 100644 index 000000000..8fa340ca2 --- /dev/null +++ b/generator/tests/link.spec.ts @@ -0,0 +1,52 @@ +/********************************************************************** + * Copyright (c) 2018-2020 Red Hat, Inc. + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + ***********************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as tmp from 'tmp'; + +import { handleCommand } from '../src/link'; + +describe('Test link command', () => { + let rootFolderTmp: string; + let srcFolder: string; + let theiaFolder: string; + + beforeEach(async () => { + rootFolderTmp = tmp.dirSync({ mode: 0o750, prefix: 'tmpLink', postfix: '' }).name; + srcFolder = path.resolve(rootFolderTmp, 'src'); + fs.ensureDirSync(srcFolder); + const theiaSource = path.resolve(__dirname, 'link'); + theiaFolder = path.resolve(rootFolderTmp, 'link'); + const linkFolder = path.resolve(rootFolderTmp, 'links'); + fs.writeFileSync(path.resolve(rootFolderTmp, '.yarnrc'), `--link-folder ${linkFolder}`); + fs.copySync(theiaSource, theiaFolder); + if (!fs.existsSync(theiaFolder)) { + throw new Error('no copy'); + } + await fs.ensureDir(rootFolderTmp); + }); + + test('test link', async () => { + await handleCommand({ + theia: theiaFolder, + 'che-theia': srcFolder, + }); + + const fooPackage = path.resolve(srcFolder, 'node_modules/@theia/foo/package.json'); + const barPackage = path.resolve(srcFolder, 'node_modules/@theia/bar/package.json'); + const zozPackage = path.resolve(srcFolder, 'node_modules/@theia/zoz/package.json'); + + expect(fs.existsSync(fooPackage)).toBeTruthy(); + expect(fs.existsSync(barPackage)).toBeTruthy(); + expect(fs.existsSync(zozPackage)).toBeTruthy(); + }); +}); diff --git a/generator/tests/link/dev-packages/foo/package.json b/generator/tests/link/dev-packages/foo/package.json new file mode 100644 index 000000000..67eea155c --- /dev/null +++ b/generator/tests/link/dev-packages/foo/package.json @@ -0,0 +1,8 @@ +{ + "name": "@theia/foo", + "description": "description", + "authors": "author", + "version": "1.0.0", + "main": "pathToMain", + "dependencies": {} +} diff --git a/generator/tests/link/examples/zoz/package.json b/generator/tests/link/examples/zoz/package.json new file mode 100644 index 000000000..f58f7ba1c --- /dev/null +++ b/generator/tests/link/examples/zoz/package.json @@ -0,0 +1,8 @@ +{ + "name": "@theia/zoz", + "description": "description", + "authors": "author", + "version": "1.0.0", + "main": "pathToMain", + "dependencies": {} +} diff --git a/generator/tests/link/packages/bar/package.json b/generator/tests/link/packages/bar/package.json new file mode 100644 index 000000000..23fe66bcc --- /dev/null +++ b/generator/tests/link/packages/bar/package.json @@ -0,0 +1,8 @@ +{ + "name": "@theia/bar", + "description": "description", + "authors": "author", + "version": "1.0.0", + "main": "pathToMain", + "dependencies": {} +} diff --git a/linkless-build.md b/linkless-build.md new file mode 100644 index 000000000..52c831e10 --- /dev/null +++ b/linkless-build.md @@ -0,0 +1,46 @@ +# Contributing to che-theia with no source linking +This document describes how to develop for che-theia using yarn linking instead of soure links + +# Developing against @theia:next +The simplest way to program che-theia is to develop against latest "next" tag of theia published +to npmjs. + +1. Check out the source code of che-theia + + cd /projects + git clone https://github.com/eclipse-che/che-theia +2. Generate the che-theia assembly folder + + cd che-theia + che-theia generate + + This will generate a theia application that will include the necessary theia and che-theia extensions + +3. Build the project + + yarn + This builds all parts of che-theia. You are now ready to run che-theia. For example, you can execute `yarn run start` inside the `assembly` folder + +# Building against Theia source code +To build against a Theia version built from source, we need to clone the Theia repository and set up yarn linking +to use that version in che-theia. + +1. Clone Theia + + cd /projects + git clone https://github.com/eclipse-theia/theia + git checkout +2. Build theia + + cd /projects/theia + yarn +3. Link into che-theia + + cd /projects/che-theia + che-theia link + This will `yarn link` all necessary modules from the Theia source into the node modules folder inside the + che-theia project. At this time, it's a good idea to rebuild the che-theia project: +4. Rebuild che-theia + + rm yarn.lock + yarn diff --git a/package.json b/package.json index 04304a26b..0e292b444 100644 --- a/package.json +++ b/package.json @@ -48,10 +48,11 @@ }, "workspaces": { "packages": [ - "extensions/*", - "plugins/*", "generator", - "tools/*" + "tools/*", + "extensions/*", + "assembly", + "plugins/*" ] }, "prettier": { diff --git a/yarn.lock b/yarn.lock index 02e4cf7d2..2be0523ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16938,14 +16938,14 @@ xterm@~4.11.0: integrity sha512-NeJH909WTO2vth/ZlC0gkP3AGzupbvVHVlmtrpBw56/sGFXaF9bNdKgqKa3tf8qbGvXMzL2JhCcHVklqFztIRw== "y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + version "4.0.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.2.tgz#c504495ba9b59230dd60226d1dd89c3c0a1b745e" + integrity sha512-DnBDwcL54b5xWMM/7RfFg4xs5amYxq2ot49aUfLjQSAracXkGvlZq0txzqr3Pa6Q0ayuCxBcwTzrPUScKY0O8w== y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + version "5.0.7" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.7.tgz#0c514aba53fc40e2db911aeb8b51566a3374efe7" + integrity sha512-oOhslryvNcA1lB9WYr+M6TMyLkLg81Dgmyb48ZDU0lvR+5bmNDTMz7iobM1QXooaLhbbrcHrlNaABhI6Vo6StQ== yaeti@^0.0.4: version "0.0.4"