2020-11-28 13:33:56 +00:00
|
|
|
import child_process from 'child_process';
|
|
|
|
import fs from 'fs-extra';
|
|
|
|
import path from 'path';
|
|
|
|
import readline from 'readline';
|
|
|
|
import yargs from 'yargs';
|
2020-11-21 15:41:31 +00:00
|
|
|
|
2020-11-28 13:33:56 +00:00
|
|
|
import { logger } from '@squeebot/core/lib/core';
|
|
|
|
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'),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-11-21 15:41:31 +00:00
|
|
|
}
|
|
|
|
|
2020-11-28 13:33:56 +00:00
|
|
|
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;
|