Repository tools

This commit is contained in:
Evert Prants 2020-11-28 15:33:56 +02:00
parent d63409494c
commit df17cb9cba
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
3 changed files with 338 additions and 7 deletions

View File

@ -62,6 +62,8 @@ export class Squeebot {
// Start channels // Start channels
this.channelManager.initialize(this.config.get('channels')); this.channelManager.initialize(this.config.get('channels'));
logger.debug(this.channelManager);
if (autostart) { if (autostart) {
// Start enabled plugins // Start enabled plugins
await this.startPlugins(); await this.startPlugins();
@ -79,12 +81,15 @@ export class Squeebot {
try { try {
await this.pluginManager.load(getManifest); await this.pluginManager.load(getManifest);
} catch (e) { } catch (e) {
logger.error(new Error(`Failed to start ${pluginName}: ${e.message}`)); logger.error('Failed to start %s:', pluginName, e.stack);
} }
} }
} }
public attachReadline(rl: any): void { public attachReadline(rl: any): void {
// Pass readline to logger
logger.setReadline(rl);
// Send readline on request // Send readline on request
this.stream.on('core', 'request-cli', () => this.stream.on('core', 'request-cli', () =>
this.stream.emit('cli', rl)); this.stream.emit('cli', rl));
@ -104,7 +109,7 @@ export class Squeebot {
this.shuttingDown = true; this.shuttingDown = true;
this.stream.on('core', 'shutdown', (state: number) => { this.stream.on('core', 'shutdown', (state: number) => {
if (state > 0) { if (state > 0) {
this.config.save(true).then((x) => process.exit(0)); this.config.save().then((x) => process.exit(0));
} }
}); });
this.stream.emitTo('core', 'shutdown', 0); this.stream.emitTo('core', 'shutdown', 0);

View File

@ -1,6 +1,334 @@
import child_process from 'child_process';
import fs from 'fs-extra';
import path from 'path';
import readline from 'readline';
import yargs from 'yargs';
function call(test: string): void { import { logger } from '@squeebot/core/lib/core';
console.log('it works!', test); import { NPMExecutor } from '@squeebot/core/lib/npm';
import { IEnvironment } from '@squeebot/core/lib/types';
import { loadEnvironment } from '@squeebot/core/lib/core';
import { PluginMetaLoader } from '@squeebot/core/lib/plugin';
const tsConfig = {
compilerOptions: {
downlevelIteration: true,
esModuleInterop: true,
experimentalDecorators: true,
forceConsistentCasingInFileNames: true,
skipLibCheck: true,
sourceMap: false,
strict: true,
target: 'es5',
},
};
const gitignore = `/node_modules/
deployment.json`;
function dummyEnvironment(location: string): IEnvironment {
return {
configurationPath: path.join(location, 'plugins'),
environment: 'development',
path: location,
pluginsPath: path.join(location, 'plugins'),
repositoryPath: path.join(location, 'plugins'),
};
} }
call('hi'); function execute(cmd: any[], loc: string): Promise<void> {
return new Promise((resolve, reject) => {
const cproc = child_process.spawn(cmd[0], cmd.slice(1), {
cwd: loc,
stdio: 'pipe',
});
cproc.on('exit', (code: number) => {
if (code !== 0) {
return reject(new Error('Build failed!'));
}
resolve();
});
});
}
async function newEnvironment(
name?: string,
location?: string): Promise<void> {
if (!name) {
name = 'squeebot';
}
if (!location) {
location = name;
}
location = path.resolve(process.cwd(), location);
const envFile = path.join(location, name + '.json');
await fs.ensureDir(location);
await fs.writeJson(envFile, {
environment: 'production',
path: location,
});
console.log('\nNew environment file:\t', envFile);
console.log('Environment path:\t', location);
}
async function newRepository(
name: string,
location?: string,
typescript = true): Promise<void> {
if (!name) {
throw new Error('Repository needs a name!');
}
if (!location) {
location = name;
}
location = path.resolve(process.cwd(), location);
console.log('Creating a new repository development environment at', location);
await fs.ensureDir(location);
const env = dummyEnvironment(location);
console.log('Creating package.json and installing base requirements');
const executor = new NPMExecutor(env, '/home/evert/Squeebot/core');
await executor.loadPackageFile();
let gitIgnore = gitignore + '';
if (typescript) {
console.log('Installing TypeScript support');
await executor.installPackage('typescript');
await fs.writeJson(path.join(location, 'tsconfig.json'), tsConfig);
gitIgnore += '\n*.js';
gitIgnore += '\n*.d.ts';
console.log('Adding TypeScript scripts to package.json');
const pkgjson = path.join(location, 'package.json');
const contents = await fs.readJson(pkgjson);
contents.scripts = {
build: 'tsc',
watch: 'tsc -w',
};
await fs.writeJson(pkgjson, contents, { spaces: 2 });
}
console.log('Creating .gitignore');
await fs.writeFile(path.join(location, '.gitignore'), gitIgnore);
console.log('Writing build metadata');
await fs.writeJson(path.join(location, 'squeebot.repo.json'), {
name,
plugins: [],
typescript,
}, { spaces: 2 });
console.log('\nDone! Your repository "%s" lives at:', name, location);
}
async function deploy(
name: string,
location: string,
outDir: string,
deployment: string): Promise<void> {
const deployFile = path.join(location, 'deployment.json');
if (!await fs.pathExists(deployFile)) {
throw new Error('deployment.json file is missing! Can\'t deploy!');
}
const meta = await fs.readJson(deployFile);
// If we have a path to a metadata file, we copy the output directory to
// the plugins directory, overwriting existing plugins with the same name!
if ((deployment.indexOf('dev') === 0 || deployment.indexOf('squee') === 0) &&
meta.devSqueebot) {
console.log('Deploying to a configured Squeebot development environment');
const devbotPath = path.resolve(location, meta.devSqueebot);
console.log('Path:', devbotPath);
if (!await fs.pathExists(devbotPath)) {
throw new Error('Development Squeebot environment file doesn\'t exist!');
}
const devEnv = await loadEnvironment(devbotPath);
if (!devEnv.pluginsPath) {
throw new Error('Bad development Squeebot environment file!');
}
const pluginsPath = path.resolve(devEnv.path, devEnv.pluginsPath);
console.log('Copying plugins to', pluginsPath);
const listAllFiles = await fs.readdir(outDir);
for (const f of listAllFiles) {
if (f === 'repository') {
continue;
}
const dst = path.join(pluginsPath, f);
await fs.copy(path.join(outDir, f), dst, {
overwrite: true,
});
}
console.log('Done!');
}
}
async function buildRepository(
location?: string,
out = true,
doDeploy?: string,
onlyDeploy = false): Promise<void> {
if (!location) {
location = process.cwd();
}
location = path.resolve(process.cwd(), location);
const outDir = path.join(location, '.out');
const buildMetaFile = path.join(location, 'squeebot.repo.json');
if (!await fs.pathExists(buildMetaFile)) {
throw new Error(`${location} is not a valid squeebot repository development environment!`);
}
const meta = await fs.readJson(buildMetaFile);
const env = dummyEnvironment(location);
env.pluginsPath = location;
if (!meta.name) {
throw new Error(`${location} is not a valid squeebot repository development environment!`);
}
console.log('Detected repository "%s"!', meta.name);
if (onlyDeploy) {
if (!await fs.pathExists(outDir)) {
throw new Error(`You need to build before deploying!`);
} else {
return deploy(meta.name, location, outDir, doDeploy as string);
}
}
console.log('Detecting plugins in this environment..');
const loader = new PluginMetaLoader(env);
const plugins = await loader.loadAll(false);
const savedList: any[] = [];
let indexFileContents = 'name=' + meta.name + '\n';
indexFileContents += 'created=' + Math.floor(Date.now() / 1000) + '\n';
console.log('Found the following plugins:', plugins.map((plugin) => {
return `${plugin.name}@${plugin.version}`;
}).join(', '));
plugins.forEach((plugin) => {
savedList.push({
name: plugin.name,
version: plugin.version,
});
indexFileContents += `${plugin.name} ${plugin.version} "${plugin.description}"\n`;
});
meta.plugins = savedList;
await fs.writeJson(buildMetaFile, meta, { spaces: 2 });
if (meta.typescript) {
console.log('Running build task..');
await execute(['npm', 'run', 'build'], location);
}
if (!out) {
console.log('Done!');
return;
}
console.log('Creating repository index');
await fs.remove(outDir);
await fs.ensureDir(outDir);
await fs.writeFile(path.join(outDir, 'repository'), indexFileContents);
console.log('Copying plugins');
for (const plugin of plugins) {
const src = path.join(location, plugin.name);
const dst = path.join(outDir, plugin.name);
await fs.copy(src, dst);
}
if (meta.typescript) {
console.log('Stripping source files');
for (const plugin of plugins) {
const plOut = path.join(outDir, plugin.name);
const listAllFiles = await fs.readdir(plOut);
for (const f of listAllFiles) {
if (f.match(/(\.d)?\.ts$/i) != null) {
await fs.remove(path.join(plOut, f));
}
}
}
}
if (doDeploy == null) {
console.log('Done!');
return;
}
console.log('\n [!!!] DEPLOYING REPOSITORY [!!!]\n');
deploy(meta.name, location, outDir, doDeploy);
}
const yar = yargs.scriptName('squeebot')
.command('new [name] [path]', 'create a new Squeebot environment', (y) => {
y.positional('name', {
describe: 'The name of the new environment',
})
.positional('path', {
describe: 'The path to create a new Squeebot environment at (default: working directory)',
});
}, (v) => newEnvironment(v.name as string, v.path as string))
.command('repository', 'manage repositories', (y) => {
y.command('new [name] [path]', 'create a new repository', (yi) => {
yi.positional('name', {
demandOption: 'The repository requires a name',
describe: 'The name of the new repository',
})
.positional('path', {
describe: 'The path to create the new Squeebot plugin repository at (default: working directory)',
})
.option('t', {
alias: 'no-typescript',
describe: 'Do not include typescript in the development environment',
type: 'boolean',
});
}, (v) => newRepository(v.name as string, v.path as string, v.t !== true));
y.command('build [path]', 'build a repository of plugins and generate the index file', (yi) => {
yi.positional('path', {
describe: 'The path of the repository',
})
.option('p', {
alias: 'no-output',
describe: 'Do not create an output directory, just build',
type: 'boolean',
})
.option('d', {
alias: 'deploy',
describe: 'Deploy the output directory as configured',
nargs: 1,
type: 'string',
})
.option('o', {
alias: 'deploy-only',
describe: 'Deploy only, without rebuilding',
type: 'boolean',
});
}, (v) => buildRepository(v.path as string,
v.p !== true,
v.d as string,
v.o === true));
})
.argv;

View File

@ -29,8 +29,6 @@ async function start(argv: any): Promise<void> {
const sb = new Squeebot(env); const sb = new Squeebot(env);
sb.stream.on('logger', 'debug', (data: string) => console.log('DEBUG', data));
await sb.initialize(); await sb.initialize();
sb.attachReadline(rl); sb.attachReadline(rl);