Unverified Commit 5e9052be authored by Douglas Gubert's avatar Douglas Gubert Committed by GitHub
Browse files

[NEW] Use the new Apps-Compiler package (#85)

* Change package command to use the Apps-Compiler

* WIP

* Revert submit to packaging the source code in TS instead of JS

* Fix inconsistencies

* Adapt to new Apps-Compiler structure

* Add "no-compile" flag to package command

* Change deploy to upload compiled version depending on Rocket.Chat
version

* Ignore package-lock in AppPackager

* Update Apps-Compiler
parent 49783b90
......@@ -638,6 +638,32 @@
"tslib": "^1.9.3"
}
},
"@oclif/plugin-autocomplete": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-0.2.0.tgz",
"integrity": "sha512-pHbaE2PH7d9lHjCgFrrQ+ZIwvY+7OAQaGoaANqDbicBNDK/Rszt4N4oGj22dJT7sCQ8a/3Eh942rjxYIq9Mi9Q==",
"requires": {
"@oclif/command": "^1.5.13",
"@oclif/config": "^1.13.0",
"chalk": "^2.4.1",
"cli-ux": "^5.2.1",
"debug": "^4.0.0",
"fs-extra": "^7.0.0",
"moment": "^2.22.1"
},
"dependencies": {
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
}
}
},
"@oclif/plugin-help": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.2.0.tgz",
......@@ -761,6 +787,47 @@
"tslint-xo": "^0.9.0"
}
},
"@rocket.chat/apps-compiler": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@rocket.chat/apps-compiler/-/apps-compiler-0.1.2.tgz",
"integrity": "sha512-9URh25ugN3R52uUHEqeLdly5s9fLE75niLmVr8xC9dyj8pJUOvnH0EVHzlSOySZ+TR+pjidTeEkHFL01f4rt1g==",
"requires": {
"@rocket.chat/apps-engine": "^1.19.0-alpha.4006",
"figures": "^3.0.0",
"fs-extra": "^8.1.0",
"glob": "^7.1.4",
"lodash.clonedeep": "^4.5.0",
"simple-node-logger": "^18.12.24",
"tv4": "^1.3.0",
"typescript": "^2.9.2",
"yazl": "^2.5.1"
},
"dependencies": {
"@rocket.chat/apps-engine": {
"version": "1.19.0-alpha.4006",
"resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.19.0-alpha.4006.tgz",
"integrity": "sha512-0OczVTVa9ZGqIlCoKSYhT3AuPwlnRPkOsPApk5L3Dx667Ch6VSpsjur8WPbRDfLR8T/XNIs026RMVIu0DxA9kQ==",
"requires": {
"adm-zip": "^0.4.9",
"cryptiles": "^4.1.3",
"lodash.clonedeep": "^4.5.0",
"semver": "^5.5.0",
"stack-trace": "0.0.10",
"uuid": "^3.2.1"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
},
"typescript": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w=="
}
}
},
"@rocket.chat/apps-engine": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@rocket.chat/apps-engine/-/apps-engine-1.12.0.tgz",
......@@ -2991,6 +3058,11 @@
"integrity": "sha1-xlfZZC2QeGQ1xkyl6Zu9TQm9fdM=",
"dev": true
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
......@@ -3777,6 +3849,15 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-node-logger": {
"version": "18.12.24",
"resolved": "https://registry.npmjs.org/simple-node-logger/-/simple-node-logger-18.12.24.tgz",
"integrity": "sha512-4dTqpYecHsvPjWo+i+J3pLty8WJDNbxOVesNj5ch8pYH95LIGAFH4dxMSqyf+Os0RTchXifEtI/mfm3AVJftmg==",
"requires": {
"lodash": "^4.17.12",
"moment": "^2.20.1"
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
......
import { Command, flags } from '@oclif/command';
import chalk from 'chalk';
import cli from 'cli-ux';
import * as semver from 'semver';
import { FolderDetails, unicodeSymbols } from '../misc';
import { checkReport, getServerInfo, packageAndZip, uploadApp } from '../misc/deployHelpers';
import { ICompilerDiagnostic } from '@rocket.chat/apps-compiler/definition';
import { AppCompiler, AppPackager, FolderDetails, unicodeSymbols } from '../misc';
import { getServerInfo, uploadApp } from '../misc/deployHelpers';
export default class Deploy extends Command {
public static description = 'allows deploying an App to a server';
......@@ -46,20 +48,38 @@ export default class Deploy extends Command {
} catch (e) {
this.error(e && e.message ? e.message : e, {exit: 2});
}
if (flags.i2fa) {
flags.code = await cli.prompt('2FA code', { type: 'hide' });
}
let serverInfo;
let zipName;
cli.log(chalk.bold.greenBright(' Starting App Deployment to Server\n'));
try {
cli.action.start(chalk.bold.greenBright(' Getting Server Info'));
serverInfo = await getServerInfo(fd, flags);
const serverInfo = await getServerInfo(fd, flags);
cli.action.stop(chalk.bold.greenBright(unicodeSymbols.get('checkMark')));
cli.action.start(chalk.bold.greenBright(' Packaging the app'));
checkReport(this, fd, flags);
zipName = await packageAndZip(this, fd);
const compiler = new AppCompiler(fd);
const result = await compiler.compile();
if (result.diagnostics.length && !flags.force) {
this.reportDiagnostics(result.diagnostics);
this.error('TypeScript compiler error(s) occurred');
this.exit(1);
return;
}
let zipName: string;
if (semver.satisfies(semver.coerce(serverInfo.serverVersion), '>=3.8')) {
zipName = await compiler.outputZip();
} else {
const packager = new AppPackager(this, fd);
zipName = await packager.zipItUp();
}
cli.action.stop(chalk.bold.greenBright(unicodeSymbols.get('checkMark')));
cli.action.start(chalk.bold.greenBright(' Uploading App'));
......@@ -67,8 +87,12 @@ export default class Deploy extends Command {
cli.action.stop(chalk.bold.greenBright(unicodeSymbols.get('checkMark')));
} catch (e) {
cli.action.stop(chalk.red(unicodeSymbols.get('heavyMultiplicationX')));
this.log(chalk.bold.redBright(
this.error(chalk.bold.redBright(
` ${unicodeSymbols.get('longRightwardsSquiggleArrow')} ${e && e.message ? e.message : e}`));
}
}
private reportDiagnostics(diag: Array<ICompilerDiagnostic>): void {
diag.forEach((d) => this.error(d.message));
}
}
import { Command, flags } from '@oclif/command';
import { ICompilerDiagnostic } from '@rocket.chat/apps-compiler/definition';
import chalk from 'chalk';
import cli from 'cli-ux';
import * as path from 'path';
import {
AppCompiler,
AppPackager,
FolderDetails,
} from '../misc';
import { AppCompiler, AppPackager, FolderDetails } from '../misc';
export default class Package extends Command {
public static description = 'packages up your App in a distributable format';
public static aliases = ['p', 'pack'];
public static flags = {
help: flags.help({ char: 'h' }),
'help': flags.help({ char: 'h' }),
// flag with no value (-f, --force)
force: flags.boolean({
'no-compile': flags.boolean({
description: "don't compile the source, package as is (for older Rocket.Chat versions)",
}),
'force': flags.boolean({
char: 'f',
description: 'forcefully package the App, ignores lint & TypeScript errors',
}),
};
public async run(): Promise<void> {
const { flags } = this.parse(Package);
cli.action.start('packaging your app');
......@@ -36,21 +34,35 @@ export default class Package extends Command {
return;
}
const compiler = new AppCompiler(this, fd);
const report = compiler.logDiagnostics();
const compiler = new AppCompiler(fd);
const result = await compiler.compile();
const { flags } = this.parse(Package);
if (!report.isValid && !flags.force) {
if (result.diagnostics.length && !flags.force) {
this.reportDiagnostics(result.diagnostics);
this.error('TypeScript compiler error(s) occurred');
this.exit(1);
return;
}
const packager = new AppPackager(this, fd);
const zipName = await packager.zipItUp();
let zipName: string;
if (flags['no-compile']) {
const packager = new AppPackager(this, fd);
zipName = await packager.zipItUp();
} else {
zipName = await compiler.outputZip();
}
cli.action.stop('finished!');
this.log(chalk.black(' '));
this.log(chalk.green('App packaged up at:'), path.join(fd.folder, zipName));
this.log(chalk.green('App packaged up at:'), fd.mergeWithFolder(zipName));
}
private reportDiagnostics(diag: Array<ICompilerDiagnostic>): void {
diag.forEach((d) => this.error(d.message));
}
}
......@@ -39,10 +39,10 @@ export default class Submit extends Command {
this.error(e && e.message ? e.message : e);
}
const compiler = new AppCompiler(this, fd);
const report = compiler.logDiagnostics();
const compiler = new AppCompiler(fd);
const report = await compiler.compile();
if (!report.isValid) {
if (!report.diagnostics.length) {
this.error('TypeScript compiler error(s) occurred');
}
......@@ -139,7 +139,7 @@ export default class Submit extends Command {
return true;
},
// tslint:disable:promise-function-async
source: (answersSoFar: object, input: string) => {
source: (_answersSoFar: object, input: string) => {
input = input || '';
return new Promise((resolve) => {
......
......@@ -3,9 +3,9 @@ import chalk from 'chalk';
import * as chokidar from 'chokidar';
import cli from 'cli-ux';
import { FolderDetails, unicodeSymbols } from '../misc';
import { checkReport, checkUpload, getIgnoredFiles, getServerInfo,
packageAndZip, uploadApp } from '../misc/deployHelpers';
import { ICompilerDiagnostic } from '@rocket.chat/apps-compiler/definition';
import { AppCompiler, FolderDetails, unicodeSymbols } from '../misc';
import { checkUpload, getIgnoredFiles, getServerInfo, uploadApp } from '../misc/deployHelpers';
export default class Watch extends Command {
......@@ -39,9 +39,10 @@ export default class Watch extends Command {
};
public async run() {
const { flags } = this.parse(Watch);
const fd = new FolderDetails(this);
try {
await fd.readInfoFile();
} catch (e) {
......@@ -51,6 +52,7 @@ export default class Watch extends Command {
if (flags.i2fa) {
flags.code = await cli.prompt('2FA code', { type: 'hide' });
}
let ignoredFiles: Array<string>;
try {
ignoredFiles = await getIgnoredFiles(fd);
......@@ -58,42 +60,49 @@ export default class Watch extends Command {
this.error(chalk.bold.red(e && e.message ? e.message : e));
}
const watcher = chokidar.watch(fd.folder, {
chokidar.watch(fd.folder, {
ignored: ignoredFiles,
awaitWriteFinish: true,
persistent: true,
interval: 300,
});
watcher
.on('change', async () => {
tasks(this, fd, flags)
.catch((e) => {
this.log(chalk.bold.redBright(
}).on('change', async () => {
tasks(this, fd, flags)
.catch((e) => {
this.log(chalk.bold.redBright(
` ${unicodeSymbols.get('longRightwardsSquiggleArrow')} ${e && e.message ? e.message : e}`));
});
})
.on('ready', async () => {
tasks(this, fd, flags)
.catch((e) => {
this.log(chalk.bold.redBright(
});
}).on('ready', async () => {
tasks(this, fd, flags)
.catch((e) => {
this.log(chalk.bold.redBright(
` ${unicodeSymbols.get('longRightwardsSquiggleArrow')} ${e && e.message ? e.message : e}`));
});
});
});
}
}
function reportDiagnostics(command: Command, diag: Array<ICompilerDiagnostic>): void {
diag.forEach((d) => command.error(d.message));
}
const tasks = async (command: Command, fd: FolderDetails, flags: { [key: string]: any }): Promise<void> => {
let serverInfo;
let zipName;
try {
command.log('\n');
cli.action.start(chalk.bold.greenBright(' Packaging the app'));
checkReport(command, fd, flags);
zipName = await packageAndZip(command, fd);
const compiler = new AppCompiler(fd);
const result = await compiler.compile();
if (result.diagnostics.length && !flags.force) {
reportDiagnostics(command, result.diagnostics);
command.error('TypeScript compiler error(s) occurred');
command.exit(1);
return;
}
const zipName = await compiler.outputZip();
cli.action.stop(chalk.bold.greenBright(unicodeSymbols.get('checkMark')));
cli.action.start(chalk.bold.greenBright(' Getting Server Info'));
serverInfo = await getServerInfo(fd, flags);
const serverInfo = await getServerInfo(fd, flags);
cli.action.stop(chalk.bold.greenBright(unicodeSymbols.get('checkMark')));
const status = await checkUpload({...flags, ...serverInfo}, fd);
......
import { Command } from '@oclif/command';
import chalk from 'chalk';
import * as ts from 'typescript';
import * as path from 'path';
import { AppsCompiler } from '@rocket.chat/apps-compiler';
import { ICompilerResult } from '@rocket.chat/apps-compiler/definition';
import { compilerOptions } from './compilerOptions';
import { DiagnosticReport } from './diagnosticReport';
import { FolderDetails } from './folderDetails';
export class AppCompiler {
public program: ts.Program;
import packageInfo = require('../../package.json');
constructor(private command: Command, private fd: FolderDetails) {
this.program = ts.createProgram({
rootNames: [this.fd.mainFile],
options: compilerOptions,
});
}
// tslint:disable-next-line:no-var-requires
const createRequire = require('module').createRequire;
// TODO: Determine which diagnostics we need to actually report, or is preEmit fine?
public logDiagnostics(): DiagnosticReport {
const report = new DiagnosticReport();
export function getTypescriptForApp(fd: FolderDetails): any {
const appRequire = createRequire(fd.mergeWithFolder('app.json'));
report.options = this.reportDiagnostics(this.program.getOptionsDiagnostics());
report.syntactic = this.reportDiagnostics(this.program.getSyntacticDiagnostics());
report.global = this.reportDiagnostics(this.program.getGlobalDiagnostics());
// report.semantic = this.reportDiagnostics(this.program.getSemanticDiagnostics());
report.declaration = this.reportDiagnostics(this.program.getDeclarationDiagnostics());
report.emit = this.reportDiagnostics(ts.getPreEmitDiagnostics(this.program));
return appRequire('typescript');
}
return report;
export class AppCompiler {
private compiler: AppsCompiler;
constructor(private fd: FolderDetails) {
this.compiler = new AppsCompiler({
tool: packageInfo.name,
version: packageInfo.version,
when: new Date(),
}, getTypescriptForApp(fd));
}
private reportDiagnostics(diagnos: ReadonlyArray<ts.Diagnostic>): number {
for (const d of diagnos) {
if (!d.file || !d.start) {
this.command.warn(ts.DiagnosticCategory[d.category].toLowerCase() +
d.code + ': ' + ts.flattenDiagnosticMessageText(d.messageText, '\n'));
continue;
}
public async compile(): Promise<ICompilerResult> {
return this.compiler.compile(this.fd.folder);
}
const startPos = ts.getLineAndCharacterOfPosition(d.file, d.start);
public async outputZip(): Promise<string> {
const zipName = path.join('dist', `${this.fd.info.nameSlug}_${this.fd.info.version}.zip`);
// tslint:disable-next-line:max-line-length
const redPart = chalk.red(`${ d.file.fileName } (${ startPos.line + 1 },${ startPos.character + 1 }): \n `);
this.command.error(redPart + ts.flattenDiagnosticMessageText(d.messageText, '\n'), { exit: false });
}
await this.compiler.outputZip(zipName);
return diagnos.length;
return zipName;
}
}
......@@ -4,6 +4,8 @@ import * as glob from 'glob';
import * as path from 'path';
import * as Yazl from 'yazl';
import packageInfo = require('../../package.json');
import { FolderDetails } from './folderDetails';
export class AppPackager {
......@@ -12,9 +14,8 @@ export class AppPackager {
silent: true,
ignore: [
'**/README.md',
'**/package-lock.json',
'**/package.json',
'**/tslint.json',
'**/package-lock.json',
'**/tsconfig.json',
'**/*.js',
'**/*.js.map',
......@@ -27,8 +28,8 @@ export class AppPackager {
};
public static PackagerInfo: { [key: string]: string } = {
tool: '@rocket.chat/apps-cli',
version: '1.6.0',
tool: packageInfo.name,
version: packageInfo.version,
};
constructor(private command: Command, private fd: FolderDetails) {}
......
......@@ -4,17 +4,7 @@ import * as fs from 'fs';
import fetch from 'node-fetch';
import { Response } from 'node-fetch';
import { AppCompiler, AppPackager, FolderDetails } from '.';
export const checkReport = (command: Command, fd: FolderDetails, flags: { [key: string]: any }): void => {
const compiler = new AppCompiler(command, fd);
const report = compiler.logDiagnostics();
if (!report.isValid && !flags.force) {
throw new Error('TypeScript compiler error(s) occurred');
}
return;
};
import { AppPackager, FolderDetails } from '.';
export const getServerInfo = async (fd: FolderDetails, flags: {[key: string]: any}):
Promise<{[key: string]: any}> => {
......@@ -27,11 +17,22 @@ export const getServerInfo = async (fd: FolderDetails, flags: {[key: string]: a
} catch (e) {
throw new Error(e && e.message ? e.message : e);
}
try {
const serverInfo = await fetch(loginInfo.url + '/api/info').then((response) => response.json());
loginInfo.serverVersion = serverInfo.version;
} catch (e) {
throw new Error(`Problems conecting to Rocket.Chat at ${loginInfo.url} - please check the address`);
}
// tslint:disable-next-line:max-line-length
const providedLoginArguments = ((loginInfo.username && loginInfo.password) || (loginInfo.userId && loginInfo.token));
if (loginInfo.url && providedLoginArguments) {
return loginInfo;
} else if (!loginInfo.url && providedLoginArguments) {
}
if (!loginInfo.url && providedLoginArguments) {
throw new Error(`
No url found.
Consider adding url with the flag --url
......
// tslint:disable: max-line-length
export const readmeTemplate = (name: string, description: string): string => {
return `# ${ name }
${ description }
......
import { expect, test } from '@oclif/test';
describe('deploy', () => {
test
.stdout()
.command(['deploy'])
.exit(2)
.it('runs and fails');
// test
// .stdout()
// .command(['deploy'])
// .exit(2)
// .it('runs and fails');
});
......@@ -5,9 +5,9 @@
"module": "commonjs",
"declaration": true,
"noImplicitAny": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
// "strict": true,
"outDir": "./lib",
"rootDirs": [ "./src" ],
"typeRoots": [ "./node_modules/@types" ]
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment