split squeebot executable

This commit is contained in:
Evert Prants 2021-09-19 01:20:34 +03:00
parent 0fe2d6dd85
commit 1ec3870ad7
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
17 changed files with 544 additions and 462 deletions

View File

@ -14,8 +14,8 @@ The primary configuration will be located in `<path>/configs/squeebot.json`.
In order to install plugins, you have to add a repository. Repositories are JSON files served over HTTP.
For example, installing core plugins (Interactive mode `-i` on `squeebotd`):
1. `repository install https://(TODO)/repository.json`
2. `plugin install control mqtt`
1. `repository install https://squeebot.lunasqu.ee/pkg/plugins-core/repository.json`
2. `plugin install control simplecommands`
3. `plugin list`
## Interactive mode commands
@ -58,7 +58,31 @@ Plugin manifest (`plugin.json`) example:
```
### Deploying the repository
You can configure deployment options using a file called `deployment.json` in your repository root. Currently supported deployment methods:
1. TODO!
You can configure deployment options using a file called `deployment.json` in your repository root.
In order to activate your configured deployment, use the `-d` flag when building your repository. `-o` flag skips build and deploys immediately.
#### Development
```
{
"devSqueebot": "<full path to environment>.json",
}
```
Use `-d dev` to deploy this.
#### SSH deployment
```
{
"prod": {
"ssh": true,
"key": "<path to id_rsa>",
"host": "<ssh host>",
"user": "<ssh user>",
"target": "<full remote path>"
}
}
```
## Official repositories
All official repositories [are here](https://gitlab.icynet.eu/Squeebot/official-plugins). Production-ready builds are available to download via Squeebot CLI from `https://squeebot.lunasqu.ee/pkg/<repo name>/repository.json` by using the `repository install` command.

84
package-lock.json generated
View File

@ -1,15 +1,15 @@
{
"name": "@squeebot/cli",
"version": "3.1.1",
"version": "3.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@squeebot/cli",
"version": "3.1.1",
"version": "3.4.0",
"license": "MIT",
"dependencies": {
"@squeebot/core": "^3.3.1",
"@squeebot/core": "^3.3.3",
"fs-extra": "^10.0.0",
"node-watch": "^0.7.1",
"tar": "^6.1.11",
@ -76,28 +76,14 @@
}
},
"node_modules/@squeebot/core": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@squeebot/core/-/core-3.3.1.tgz",
"integrity": "sha512-pkLMbZ0ZLC0isBlGbOCyu28NalBOjmwAkC0RmPN19DysohEF7XBdMMBss72IT3fXeAK6mEKO7pHZS4XAHVZa0Q==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@squeebot/core/-/core-3.3.3.tgz",
"integrity": "sha512-JKmHXAl2E3nXk5lE96DVyLFp/oQXu7zQb0ZcNuL6p5tdzjJW2f9GMzKYkc/6UxqijqRuwX0Km5qcSBYkp1JRJg==",
"dependencies": {
"dateformat": "^4.0.0",
"fs-extra": "^9.0.1",
"semver": "^7.3.2",
"tar": "^6.0.5"
}
},
"node_modules/@squeebot/core/node_modules/fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"dependencies": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=10"
"dateformat": "^4.5.1",
"fs-extra": "^10.0.0",
"semver": "^7.3.5",
"tar": "^6.1.11"
}
},
"node_modules/@squeebot/core/node_modules/semver": {
@ -192,14 +178,6 @@
"sprintf-js": "~1.0.2"
}
},
"node_modules/at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -575,9 +553,9 @@
}
},
"node_modules/path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/require-directory": {
@ -852,27 +830,16 @@
}
},
"@squeebot/core": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@squeebot/core/-/core-3.3.1.tgz",
"integrity": "sha512-pkLMbZ0ZLC0isBlGbOCyu28NalBOjmwAkC0RmPN19DysohEF7XBdMMBss72IT3fXeAK6mEKO7pHZS4XAHVZa0Q==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@squeebot/core/-/core-3.3.3.tgz",
"integrity": "sha512-JKmHXAl2E3nXk5lE96DVyLFp/oQXu7zQb0ZcNuL6p5tdzjJW2f9GMzKYkc/6UxqijqRuwX0Km5qcSBYkp1JRJg==",
"requires": {
"dateformat": "^4.0.0",
"fs-extra": "^9.0.1",
"semver": "^7.3.2",
"tar": "^6.0.5"
"dateformat": "^4.5.1",
"fs-extra": "^10.0.0",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"dependencies": {
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
"requires": {
"at-least-node": "^1.0.0",
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@ -955,11 +922,6 @@
"sprintf-js": "~1.0.2"
}
},
"at-least-node": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -1262,9 +1224,9 @@
"dev": true
},
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"require-directory": {

View File

@ -1,6 +1,6 @@
{
"name": "@squeebot/cli",
"version": "3.3.2",
"version": "3.4.0",
"description": "Squeebot v3 runtime, environments and configuration",
"main": "dist/squeebot.js",
"bin": {
@ -29,7 +29,7 @@
"typescript": "^4.4.2"
},
"dependencies": {
"@squeebot/core": "^3.3.1",
"@squeebot/core": "^3.3.3",
"fs-extra": "^10.0.0",
"node-watch": "^0.7.1",
"tar": "^6.1.11",

View File

@ -0,0 +1,12 @@
import { IEnvironment } from '@squeebot/core/lib/types';
import path from 'path';
export function defaultEnvironment(location: string): IEnvironment {
return {
configurationPath: path.join(location, 'plugins'),
environment: 'development',
path: location,
pluginsPath: path.join(location, 'plugins'),
repositoryPath: path.join(location, 'plugins'),
};
}

View File

@ -0,0 +1,4 @@
export const gitignore = `/node_modules/
/.out/
deployment.json`;

View File

@ -0,0 +1,12 @@
export const tsConfig = {
compilerOptions: {
downlevelIteration: true,
esModuleInterop: true,
experimentalDecorators: true,
forceConsistentCasingInFileNames: true,
skipLibCheck: true,
sourceMap: false,
strict: true,
target: 'es5',
},
};

32
src/build/environment.ts Normal file
View File

@ -0,0 +1,32 @@
import path from 'path';
import fs from 'fs-extra';
/**
* Initialize a Squeebot environment
* @param name Environment name
* @param location Install path
*/
export 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);
}

24
src/build/execute.ts Normal file
View File

@ -0,0 +1,24 @@
import child_process from 'child_process';
/**
* Execute a shell command
* @param cmd Shell command
* @param loc Working directory
*/
export 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,
});
cproc.stdout.pipe(process.stdout);
cproc.stderr.pipe(process.stderr);
cproc.on('exit', (code: number) => {
if (code !== 0) {
return reject(new Error('Build failed!'));
}
resolve();
});
});
}

View File

@ -0,0 +1,130 @@
import { PluginMetaLoader } from '@squeebot/core/lib/plugin';
import fs from 'fs-extra';
import path from 'path';
import tar from 'tar';
import { defaultEnvironment } from '../default/environment.default';
import { execute } from '../execute';
import { deploy } from './deploy';
/**
* Build repository ready for deployment
* @param location Repository location
* @param out Create output files for deployment (false - only compile tsc in-place)
* @param doDeploy Deployment name
* @param onlyDeploy Deploy only without building
*/
export 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');
// Check for repository metadata
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!`);
}
// Read repository metadata
const meta = await fs.readJson(buildMetaFile);
const env = defaultEnvironment(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);
}
}
// Run typescript build
if (meta.typescript) {
console.log('Running build task..');
await execute(['npm', 'run', 'build'], location);
}
console.log('Detecting plugins in this environment..');
const loader = new PluginMetaLoader(env);
const plugins = await loader.loadAll(false);
console.log('Found the following plugins:', plugins.map((plugin) => {
return `${plugin.name}@${plugin.version}`;
}).join(', '));
const savedList = plugins.map((plugin) => ({
name: plugin.name,
version: plugin.version,
}));
meta.plugins = savedList;
await fs.writeJson(buildMetaFile, meta, { spaces: 2 });
if (!out) {
console.log('Done!');
return;
}
console.log('Creating repository index');
await fs.remove(outDir);
await fs.ensureDir(outDir);
await fs.writeJSON(path.join(outDir, 'repository.json'), {
created: Math.floor(Date.now() / 1000),
name: meta.name,
plugins: meta.plugins,
schema: 1,
});
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));
}
}
}
}
console.log('Creating tarballs');
for (const plugin of plugins) {
const plOut = path.join(outDir, plugin.name);
await tar.c({
gzip: true,
file: plOut + '.plugin.tgz',
C: outDir,
}, [plugin.name]);
}
if (doDeploy == null) {
console.log('Done!');
return;
}
deploy(meta.name, location, outDir, doDeploy);
}

View File

@ -0,0 +1,69 @@
import path from 'path';
import fs from 'fs-extra';
import { NPMExecutor } from '@squeebot/core/lib/npm';
import { defaultEnvironment } from '../default/environment.default';
import { gitignore } from '../default/gitignore.default';
import { tsConfig } from '../default/tsconfig.default';
/**
* Create a new repository, complete with typescript and git.
* @param name Repository name
* @param location Repository path
* @param typescript Use typescript
*/
export 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 = defaultEnvironment(location);
console.log('Creating package.json and installing base requirements');
const executor = new NPMExecutor(env, '@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';
gitIgnore += '\n*.tsbuildinfo';
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);
}

View File

@ -0,0 +1,45 @@
import fs from 'fs-extra';
import path from 'path';
import { sshDeploy } from './deployment/ssh';
import { developDeploy } from './deployment/develop';
/**
* Deploy according to configuration
* @param name Repository name
* @param location Repository path
* @param outDir Output directory
* @param deployment Deployment name
*/
export 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('\n [!!!] DEPLOYING REPOSITORY FOR DEVELOPMENT [!!!]\n');
developDeploy(location, meta, outDir);
return;
}
// Other deployment methods come here
if (deployment in meta) {
console.log('\n [!!!] DEPLOYING REPOSITORY [!!!]\n');
const dpl = meta[deployment];
if (dpl.ssh) {
await sshDeploy(outDir, dpl);
}
}
}

View File

@ -0,0 +1,46 @@
import fs from 'fs-extra';
import path from 'path';
import { loadEnvironment } from '@squeebot/core/lib/core';
/**
* Deploy to a local environment
* @param location Repository location
* @param meta Deployment metadata
* @param outDir Repository output files
*/
export async function developDeploy(
location: string,
meta: Record<string, string | number>,
outDir: string,
): Promise<void> {
console.log('Deploying to a configured Squeebot development environment');
const devbotPath = path.resolve(location, meta.devSqueebot as string);
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' || f.indexOf('.tgz') !== -1) {
continue;
}
const dst = path.join(pluginsPath, f);
await fs.copy(path.join(outDir, f), dst, {
overwrite: true,
});
}
console.log('Done!');
}

View File

@ -0,0 +1,46 @@
import fs from 'fs-extra';
import path from 'path';
import { execute } from '../../execute';
/**
* Deploy using ssh and rsync
* @param outDir Repoistory output
* @param sshconf ssh deployment configuration
*/
export async function sshDeploy(
outDir: string,
sshconf: any
): Promise<void> {
const key = sshconf.key;
const host = sshconf.host;
const username = sshconf.user;
const port = sshconf.port || 22;
const target = sshconf.target;
const args = sshconf.args || '-rltgoDzvO';
if (!key || !host || !username || !target) {
console.error('SSH deployment requires more arguments.');
return;
}
console.log('Deploying to %s@%s:%d%s', username, host, port, target);
const fileList = (await fs.readdir(outDir)).filter((val) => {
return val === 'repository.json' || val.match('.tgz$');
}).map((x) => path.join(outDir, x));
const pargs = [
'rsync',
args,
'-e',
`ssh -i ${key} -p ${port}`,
...fileList,
`${username}@${host}:${target}`,
];
console.log(pargs.join(' '));
await execute(pargs, outDir);
console.log('Done!');
}

57
src/build/watch.ts Normal file
View File

@ -0,0 +1,57 @@
import fs from 'fs-extra';
import watch from 'node-watch';
import path from 'path';
import { buildRepository } from './repository/build';
/**
* Create a filesystem watch task for a repository directory
* Equivalent to tsc -w
* @param location Repository path
* @param out Create output files. False - dry run.
* @param doDeploy Use deployment after build success
*/
export async function watchRepository(
location?: string,
out = true,
doDeploy?: string): Promise<void> {
if (!location) {
location = process.cwd();
}
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);
if (!meta.name) {
throw new Error(`${location} is not a valid squeebot repository development environment!`);
}
console.log('Watching repository "%s"!', meta.name);
watch(location, {
recursive: true,
filter(f, skip): any {
if (/\/node_modules/.test(f) ||
/\.out/.test(f) ||
/\.git/.test(f) ||
/\.json$/.test(f)) {
return skip;
}
if (meta.typescript && /\.js$/.test(f)) {
return skip;
}
return true;
},
}, (evt, f) => {
console.log(`\n ==> ${f} changed, rebuilding...`);
buildRepository(location, out, doDeploy).catch((e) => {
console.error(e);
});
});
}

View File

@ -20,6 +20,9 @@ const defaultConfiguration: {[key: string]: any} = {
name: 'squeebot',
};
/**
* Reference implementation of a Squeebot system using core classes.
*/
export class Squeebot implements ISqueebotCore {
public npm: NPMExecutor = new NPMExecutor(this.environment, '@squeebot/core');
@ -86,7 +89,7 @@ export class Squeebot implements ISqueebotCore {
const getManifest = this.pluginManager.getAvailableByName(pluginName);
if (!getManifest) {
logger.error(new Error(`Failed to start ${pluginName}: no manifest available. You might not have installed it.`));
return;
continue;
}
try {
@ -116,13 +119,16 @@ export class Squeebot implements ISqueebotCore {
}
logger.warn('Shutting down..');
this.shuttingDown = true;
// Subsystems confirmed shutdown, go ahead and exit.
this.stream.on('core', 'shutdown', (state: number) => {
if (state > 0) {
this.config.save().then((x) => process.exit(0));
}
});
// Prompt subsystems to prepare for shutting down
this.stream.emitTo('core', 'shutdown', 0);
}
}

View File

@ -1,394 +1,10 @@
#!/usr/bin/env node
import child_process from 'child_process';
import fs from 'fs-extra';
import watch from 'node-watch';
import path from 'path';
import yargs from 'yargs';
import tar from 'tar';
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/
/.out/
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,
});
cproc.stdout.pipe(process.stdout);
cproc.stderr.pipe(process.stderr);
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, '@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';
gitIgnore += '\n*.tsbuildinfo';
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 sshDeploy(
outDir: string,
sshconf: any): Promise<void> {
const key = sshconf.key;
const host = sshconf.host;
const username = sshconf.user;
const port = sshconf.port || 22;
const target = sshconf.target;
const args = sshconf.args || '-rltgoDzvO';
if (!key || !host || !username || !target) {
console.error('SSH deployment requires more arguments.');
return;
}
console.log('Deploying to %s@%s:%d%s', username, host, port, target);
const fileList = (await fs.readdir(outDir)).filter((val) => {
return val === 'repository.json' || val.match('.tgz$');
}).map((x) => path.join(outDir, x));
const pargs = [
'rsync',
args,
'-e',
`ssh -i ${key} -p ${port}`,
...fileList,
`${username}@${host}:${target}`,
];
console.log(pargs.join(' '));
await execute(pargs, outDir);
console.log('Done!');
}
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' || f.indexOf('.tgz') !== -1) {
continue;
}
const dst = path.join(pluginsPath, f);
await fs.copy(path.join(outDir, f), dst, {
overwrite: true,
});
}
console.log('Done!');
}
if (deployment in meta) {
const dpl = meta[deployment];
if (dpl.ssh) {
await sshDeploy(outDir, dpl);
}
}
}
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);
}
}
if (meta.typescript) {
console.log('Running build task..');
await execute(['npm', 'run', 'build'], location);
}
console.log('Detecting plugins in this environment..');
const loader = new PluginMetaLoader(env);
const plugins = await loader.loadAll(false);
const savedList: any[] = [];
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,
});
});
meta.plugins = savedList;
await fs.writeJson(buildMetaFile, meta, { spaces: 2 });
if (!out) {
console.log('Done!');
return;
}
console.log('Creating repository index');
await fs.remove(outDir);
await fs.ensureDir(outDir);
await fs.writeJSON(path.join(outDir, 'repository.json'), {
created: Math.floor(Date.now() / 1000),
name: meta.name,
plugins: meta.plugins,
schema: 1,
});
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));
}
}
}
}
console.log('Creating tarballs');
for (const plugin of plugins) {
const plOut = path.join(outDir, plugin.name);
await tar.c({
gzip: true,
file: plOut + '.plugin.tgz',
C: outDir,
}, [plugin.name]);
}
if (doDeploy == null) {
console.log('Done!');
return;
}
console.log('\n [!!!] DEPLOYING REPOSITORY [!!!]\n');
deploy(meta.name, location, outDir, doDeploy);
}
async function watchRepository(
location?: string,
out = true,
doDeploy?: string,
onlyDeploy = false): Promise<void> {
if (!location) {
location = process.cwd();
}
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);
if (!meta.name) {
throw new Error(`${location} is not a valid squeebot repository development environment!`);
}
console.log('Watching repository "%s"!', meta.name);
watch(location, {
recursive: true,
filter(f, skip): any {
if (/\/node_modules/.test(f) ||
/\.out/.test(f) ||
/\.git/.test(f) ||
/\.json$/.test(f)) {
return skip;
}
if (meta.typescript && /\.js$/.test(f)) {
return skip;
}
return true;
},
}, (evt, f) => {
console.log(`\n ==> ${f} changed, rebuilding...`);
buildRepository(location, out, doDeploy, onlyDeploy).catch((e) => {
console.error(e);
});
});
}
import { newEnvironment } from './build/environment';
import { buildRepository } from './build/repository/build';
import { newRepository } from './build/repository/create';
import { watchRepository } from './build/watch';
const yar = yargs.scriptName('squeebot')
.command('new [name] [path]', 'create a new Squeebot environment', (y) => {
@ -452,7 +68,6 @@ const yar = yargs.scriptName('squeebot')
dargs[0] as string,
dargs[1] as boolean,
dargs[2] as string,
dargs[3] as boolean,
);
}

View File

@ -4,7 +4,6 @@ import readline from 'readline';
import yargs from 'yargs';
import { loadEnvironment } from '@squeebot/core/lib/core';
import { IEnvironment } from '@squeebot/core/lib/types';
import { Squeebot } from './core';
import { SqueebotCLI } from './cli';
@ -24,14 +23,13 @@ async function start(argv: any): Promise<void> {
root = path.resolve(argv.root);
}
const enviroFile = path.resolve(process.cwd(), argv.environment);
const env: IEnvironment = await loadEnvironment(argv.environment, root);
const env = await loadEnvironment(argv.environment, root);
// Change working directory to the environment
process.chdir(env.path as string);
// Create and initialize the Squeebot instance
const sb = new Squeebot(env);
await sb.initialize(argv.e !== true);
// Create a CLI if interactive mode is enabled