Unverified Commit 749a9eba authored by Bradley Hilton's avatar Bradley Hilton
Browse files

Move the server pieces out to their own package, add the packaging script,...

Move the server pieces out to their own package, add the packaging script, start on the dev environment
parent 59aa004f
{
"extends": "eslint:recommended",
"env": {
"browser": false,
"commonjs": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"rules": {
"semi": ["error", "always"],
"no-console": "off",
"no-unused-vars": "error"
}
}
{
"json.schemas": [
{
"fileMatch": [
"*rocketlet.json"
],
"url": "./rocketlet-schema.json"
}
]
}
const gulp = require('gulp');
const fs = require('fs');
const path = require('path');
const async = require('async');
const figures = require('figures');
const del = require('del');
const through = require('through2');
const gulp = require('gulp');
const file = require('gulp-file');
const gutil = require('gulp-util');
const zip = require('gulp-zip');
const jsonSchema = require('gulp-json-schema');
const sourcemaps = require('gulp-sourcemaps');
const tsc = require('gulp-typescript');
const tslint = require('gulp-tslint');
const spawn = require('child_process').spawn;
const rocketletSchema = require('./rocketlet-schema.json');
function getFolders(dir) {
return fs.readdirSync(dir).filter((file) => fs.statSync(path.join(dir, file)).isDirectory());
}
const dest = 'dist';
const rocketletsPath = './rocketlets';
const tsp = tsc.createProject('tsconfig.json');
gulp.task('clean-generated', function _cleanTypescript() {
const distFiles = ['./dist/**/*.*'];
return del(distFiles);
return del(['./dist/**/*.*']);
});
gulp.task('lint-ts', function _lintTypescript() {
return tsp.src().pipe(tslint({ formatter: 'verbose' })).pipe(tslint.report());
});
gulp.task('compile-ts', ['clean-generated'], function _compileTypescript() {
gulp.task('lint-no-exit-ts', function _lintTypescript() {
return tsp.src().pipe(tslint({ formatter: 'verbose', emitError: false })).pipe(tslint.report());
});
gulp.task('compile-ts', ['clean-generated', 'lint-ts'], function _compileTypescript() {
return tsp.src().pipe(sourcemaps.init())
.pipe(tsp())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('dist'));
});
var server;
gulp.task('run-server', ['compile-ts'], function _runTheServer() {
let server;
gulp.task('run-server', ['compile-ts', 'lint-ts'], function _runTheServer() {
if (server) server.kill();
server = spawn('node', ['index.js'], { stdio: 'inherit' });
......@@ -32,9 +54,75 @@ gulp.task('run-server', ['compile-ts'], function _runTheServer() {
});
process.on('exit', () => {
if (server) server.kill()
if (server) server.kill();
});
gulp.task('watch-and-run', ['compile-ts', 'run-server'], function _watchCodeAndRun() {
gulp.watch('src/**/*.ts', ['compile-ts', 'run-server']);
gulp.task('default', ['clean-generated', 'lint-no-exit-ts', 'package-for-develop', 'run-server'], function _watchCodeAndRun() {
gulp.watch('rocketlets/**/*.ts', ['clean-generated', 'lint-no-exit-ts', 'package-for-develop', 'run-server']);
});
//Packaging related items
function _packageTheRocketlets(callback) {
const folders = getFolders(rocketletsPath)
.filter((folder) => fs.statSync(path.join(rocketletsPath, folder, 'rocketlet.json')).isFile())
.map((folder) => {
return {
folder,
dir: path.join(rocketletsPath, folder),
toZip: path.join(rocketletsPath, folder, '**'),
infoFile: path.join(rocketletsPath, folder, 'rocketlet.json'),
info: require('./' + path.join(rocketletsPath, folder, 'rocketlet.json'))
};
});
async.series([
function _readTheRocketletJsonFiles(next) {
const promises = folders.map((item) => {
return new Promise((resolve) => {
gulp.src(item.infoFile)
.pipe(jsonSchema({ schema: rocketletSchema, emitError: false }))
.pipe(through.obj(function transform(file, enc, done) {
if (file && !file.isNull() && file.jsonSchemaResult) {
item.valid = file.jsonSchemaResult.valid;
if (!item.valid) {
gutil.log(gutil.colors.red(figures.cross), gutil.colors.cyan(item.folder + path.sep + 'rocketlet.json'));
}
}
done(null, file);
}, function flush() {
resolve();
}));
});
});
Promise.all(promises).then(() => next());
},
function _onlyZipGoodRocketlets(next) {
const validItems = folders.filter((item) => item.valid);
if (validItems.length === 0) {
next(new Error('No valid Rocketlets.'));
return;
}
const zippers = validItems.map((item) => {
return new Promise((resolve) => {
gutil.log(gutil.colors.green(figures.tick), gutil.colors.cyan(item.info.name + ' ' + item.info.version));
return gulp.src(item.toZip)
.pipe(file('.packagedby', fs.readFileSync('package.json')))
.pipe(zip(item.info.name.toLowerCase().replace(/ /g, '_') + '-' + item.info.id + '-' + item.info.version + '.zip'))
.pipe(gulp.dest('dist'))
.pipe(through.obj((file, enc, done) => done(null, file), () => resolve()));
});
});
Promise.all(zippers).then(() => next());
}
], callback);
}
gulp.task('package-for-develop', ['clean-generated', 'lint-no-exit-ts'], _packageTheRocketlets);
gulp.task('package', ['clean-generated', 'lint-ts'], _packageTheRocketlets);
const fs = require('fs');
const express = require('express');
const rlserver = require('temporary-rocketlets-server');
const bodyParser = require('body-parser');
const m = require('./dist/manager.js');
const compiler = require('./dist/compiler.js');
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
const manager = new m.RocketletManager('./examples');
const manager = new rlserver.RocketletManager();
manager.load().then((items) => console.log(items)).catch((err) => console.warn(err));
function _loadRocketlets() {
fs.readdirSync('dist')
.filter((file) => fs.statSync(path.join('dist', file)).isFile() && file.endsWith('.zip'))
.map((zip) => fs.readFileSync(path.join('dist', zip), 'base64'))
.forEach((content) => manager.add(content));
}
app.use(bodyParser.json());
app.get('/', function (req, res) {
res.json({
'GET: /compile': {},
'POST: /event': {
msg: 'string'
}
});
});
app.get('/compile', (req, res) => {
const src = fs.readFileSync('./examples/preMessageSent.ts', 'utf8');
const result = compiler.compiler(src);
const Rocketlet = eval(result);
new Rocketlet();
res.json({ src, result });
});
app.post('/event', (req, res) => {
console.log(req.body, req.body.msg);
res.json({ success: true });
......@@ -38,4 +33,5 @@ app.post('/event', (req, res) => {
app.listen(3003, function _appListen() {
console.log('Example app listening on port 3003!');
console.log('http://localhost:3003/');
_loadRocketlets();
});
This diff is collapsed.
{
"name": "rocketlets",
"name": "rocketlets-dev-environment",
"version": "1.0.0",
"description": "Testing for rocketlets",
"description": "The development environment for the Rocketlets.",
"main": "index.js",
"scripts": {
"start": "npm install && node index"
"start": "npm install && node index",
"package": "gulp package"
},
"author": "Bradley Hilton",
"author": "Bradley Hilton <bradley.hilton@rocket.chat>",
"license": "MIT",
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.3",
"reflect-metadata": "^0.1.10",
"temporary-rocketlets-ts-definition": "0.5.2",
"tslint": "^5.4.3",
"typescript": "^2.3.4",
"vm2": "^3.4.6"
},
"devDependencies": {
"@types/node": "^8.0.1",
"async": "^2.5.0",
"body-parser": "^1.17.2",
"del": "^3.0.0",
"eslint": "^4.1.1",
"express": "^4.15.3",
"figures": "^2.0.0",
"gulp": "^3.9.1",
"gulp-file": "^0.3.0",
"gulp-json-schema": "^1.0.0",
"gulp-sourcemaps": "^2.6.0",
"gulp-tslint": "^8.1.1",
"gulp-typescript": "^3.1.7"
"gulp-tv4": "^0.1.0",
"gulp-typescript": "^3.1.7",
"gulp-util": "^3.0.8",
"gulp-zip": "^4.0.0",
"reflect-metadata": "^0.1.10",
"temporary-rocketlets-ts-definition": "^0.5.3",
"through2": "^2.0.3",
"tslint": "^5.4.3",
"typescript": "^2.4.1"
},
"dependencies": {
"temporary-rocketlets-server": "0.0.2"
}
}
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Rocket.Chat Rocketlet",
"description": "A Rocketlet declaration for usage inside of Rocket.Chat.",
"type": "object",
"properties": {
"id": {
"description": "The Rocketlet's unique identifier, obtained from Rocket.Chat, the company, or one in the range of private Rocketlets.",
"type": "integer"
},
"name": {
"description": "The public visible name of this Rocketlet.",
"type": "string"
},
"version": {
"description": "The version of this Rocketlet which will be used for display publicly and letting users know there is an update. This uses the semver format.",
"type": "string",
"pattern": "\\bv?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?\\b"
},
"description": {
"description": "A description of this Rocketlet, used to explain what this Rocketlet does and provides for the user.",
"type": "string"
},
"requiredApiVersion": {
"description": "The required version of the Rocketlet's API which this Rocketlet depends on. This uses the semver format.",
"type": "string",
"pattern": "\\bv?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?(?:\\+[\\da-z\\-]+(?:\\.[\\da-z\\-]+)*)?\\b"
},
"author": {
"type": "object",
"properties": {
"name": {
"description": "The author's name who created this Rocketlet.",
"type": "string"
},
"support": {
"description": "The place where people can get support for this Rocketlet, whether email or website.",
"type": "string"
},
"homepage": {
"description": "The homepage for this Rocketlet, it can be a Github or the author's website.",
"type": "string",
"format": "uri"
}
},
"required": ["name", "support"]
},
"classFile": {
"type": "string",
"description": "The name of the file which contains your Rocketlet TypeScript source code.",
"pattern": "^.*\\.(ts)$"
}
},
"required": ["id", "name", "version", "description", "requiredApiVersion", "author", "classFile"]
}
# Rocketlets Folder
Create a new folder per Rocketlet, the name of the folder won't really matter since the zip file produced will be named `name_id_version`.
import { IRocketletInfo } from 'temporary-rocketlets-ts-definition/metadata/IRocketletInfo';
import { Rocketlet } from 'temporary-rocketlets-ts-definition/Rocketlet';
export class GithubRocketlet extends Rocketlet {
constructor(info: IRocketletInfo) {
super(info);
}
}
import { IRocketletInfo } from 'temporary-rocketlets-ts-definition/metadata';
import { Rocketlet } from 'temporary-rocketlets-ts-definition/Rocketlet';
export class TodoListRocketlet extends Rocketlet {
constructor(info: IRocketletInfo) {
super(info);
}
}
{
"id": 1,
"name": "To Do Lists",
"version": "0.0.1",
"requiredApiVersion": "1.0.0",
"description": "Rocketlet which provides to do lists per channels, including associated commands.",
"author": {
"name": "Bradley Hilton <bradley.hilton@rocket.chat>",
"support": "https://github.com/graywolf336"
},
"classFile": "Rocketlet.ts"
}
import { ICompilerFile } from './interfaces';
import * as fs from 'fs';
import * as path from 'path';
import { Rocketlet } from 'temporary-rocketlets-ts-definition/Rocketlet';
import * as ts from 'typescript';
import * as vm from 'vm';
export class RocketletCompiler {
private readonly compilerOptions: ts.CompilerOptions;
private libraryFiles: { [s: string]: ICompilerFile };
constructor() {
this.compilerOptions = {
target: ts.ScriptTarget.ES2016,
module: ts.ModuleKind.CommonJS,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
declaration: false,
noImplicitAny: false,
removeComments: true,
noImplicitReturns: true,
experimentalDecorators: true,
emitDecoratorMetadata: true,
};
this.libraryFiles = {};
}
public getLibraryFile(fileName: string): ICompilerFile {
if (!fileName.endsWith('.d.ts')) {
return undefined;
}
const norm = path.normalize(fileName);
if (this.libraryFiles[norm]) {
return this.libraryFiles[norm];
}
if (!fs.existsSync(fileName)) {
return undefined;
}
this.libraryFiles[norm] = {
content: fs.readFileSync(fileName).toString(),
version: 0,
};
return this.libraryFiles[norm];
}
public linttTs(source: string): void {
throw new Error('Not implemented yet.');
}
public toJs(source: string): any {
const files: { [s: string]: ICompilerFile } = {
'rocketlet.ts': {
version: 0,
content: source,
},
};
const host: ts.LanguageServiceHost = {
getScriptFileNames: () => Object.keys(files),
getScriptVersion: (fileName) => {
fileName = path.normalize(fileName);
return files[fileName] && files[fileName].version.toString();
},
getScriptSnapshot: (fileName) => {
fileName = path.normalize(fileName);
const file = files[fileName] || this.getLibraryFile(fileName);
if (!file || !file.content) {
return;
}
return ts.ScriptSnapshot.fromString(file.content);
},
getCompilationSettings: () => this.compilerOptions,
getCurrentDirectory: () => process.cwd(),
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(this.compilerOptions),
};
const languageService = ts.createLanguageService(host, ts.createDocumentRegistry());
// const dia = languageService.getProgram().getGlobalDiagnostics();
const dia = languageService.getCompilerOptionsDiagnostics();
if (dia.length !== 0) {
return {
success: false,
others: dia,
};
}
function logErrors(fileName: string) {
const allDiagnostics = languageService.getCompilerOptionsDiagnostics()
.concat(languageService.getSyntacticDiagnostics(fileName))
.concat(languageService.getSemanticDiagnostics(fileName));
allDiagnostics.forEach((diagnostic) => {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (diagnostic.file) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
console.log(` Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
} else {
console.log(` Error: ${message}`);
}
});
}
const checker = languageService.getProgram().getTypeChecker();
const src = languageService.getProgram().getSourceFile('rocketlet.ts');
ts.forEachChild(src, (n) => {
if (n.kind === ts.SyntaxKind.ClassDeclaration) {
ts.forEachChild(n, (node) => {
if (node.kind === ts.SyntaxKind.HeritageClause) {
const e = node as ts.HeritageClause;
if (e.token === ts.SyntaxKind.ImplementsKeyword) {
console.log('Implements the following:');
} else if (e.token === ts.SyntaxKind.ExtendsKeyword) {
console.log('Extends the following:');
}
ts.forEachChild(node, (nn) => {
// console.log(ts.SyntaxKind[nn.kind]);
console.log(nn.getText());
});
}
});
// console.log(n);
// const sym = checker.getSymbolAtLocation((n as ts.ClassDeclaration).name);
// console.log(sym);
}
});
const output = languageService.getEmitOutput('rocketlet.ts');
if (output.emitSkipped) {
console.log('Emitting failed...');
logErrors('rocketlet.ts');
}
output.outputFiles.forEach((o) => console.log(o.name, 'output is:', o.text));
// const result = ts.transpileModule(source, { compilerOptions: this.compilerOptions });
// TODO: implement the `ts.createProject` so that we get `result.diagnostics`
// return result.outputText;
return {
success: true,
};
}
public toSandBox(js: string): Rocketlet {
const script = new vm.Script(js);
const context = vm.createContext({ require, exports });
const result = script.runInContext(context);
if (typeof result !== 'function') {
throw new Error('The provided script is not valid.');
}
const rl = new result();
if (!(rl instanceof Rocketlet)) {
throw new Error('The script must extend BaseRocketlet.');
}
if (typeof rl.getName !== 'function') {
throw new Error ('The Rocketlet doesn\'t have a `getName` function, this is required.');
}
if (typeof rl.getVersion !== 'function') {
throw new Error ('The Rocketlet doesn\'t have a `getVersion` function, this is required.');
}
if (typeof rl.getID !== 'function') {
throw new Error ('The Rocketlet doesn\'t have a `getID` function, this is required.');
}
if (typeof rl.getDescription !== 'function') {
throw new Error ('The Rocketlet doesn\'t have a `getDescription` function, this is required.');
}
if (typeof rl.getRequiredApiVersion !== 'function') {
throw new Error ('The Rocketlet doesn\'t have a `getRequiredApiVersion` function, this is required.');
}
return rl as Rocketlet;
}
}
import { RocketletCompiler } from './compiler';
import { RocketletManager} from './manager';
export { RocketletCompiler, RocketletManager };
export interface ICompilerFile {
content: string;
version: number;
}
export interface IGetRocketletsFilter {
ids?: Array<number>;
name?: string | RegExp;
count?: number;
enabled?: boolean;
disabled?: boolean;
}
import { ICompilerFile } from './ICompilerFile';
import { IGetRocketletsFilter } from './IGetRocketletsFilter';
export { ICompilerFile, IGetRocketletsFilter};
import { RocketletCompiler } from './compiler';
import { IGetRocketletsFilter } from './interfaces';