split squeebot executable
This commit is contained in:
parent
0fe2d6dd85
commit
1ec3870ad7
32
README.md
32
README.md
@ -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
84
package-lock.json
generated
@ -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": {
|
||||
|
@ -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",
|
||||
|
12
src/build/default/environment.default.ts
Normal file
12
src/build/default/environment.default.ts
Normal 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'),
|
||||
};
|
||||
}
|
4
src/build/default/gitignore.default.ts
Normal file
4
src/build/default/gitignore.default.ts
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
export const gitignore = `/node_modules/
|
||||
/.out/
|
||||
deployment.json`;
|
12
src/build/default/tsconfig.default.ts
Normal file
12
src/build/default/tsconfig.default.ts
Normal 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
32
src/build/environment.ts
Normal 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
24
src/build/execute.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
}
|
130
src/build/repository/build.ts
Normal file
130
src/build/repository/build.ts
Normal 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);
|
||||
}
|
69
src/build/repository/create.ts
Normal file
69
src/build/repository/create.ts
Normal 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);
|
||||
}
|
45
src/build/repository/deploy.ts
Normal file
45
src/build/repository/deploy.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
46
src/build/repository/deployment/develop.ts
Normal file
46
src/build/repository/deployment/develop.ts
Normal 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!');
|
||||
}
|
46
src/build/repository/deployment/ssh.ts
Normal file
46
src/build/repository/deployment/ssh.ts
Normal 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
57
src/build/watch.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
10
src/core.ts
10
src/core.ts
@ -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);
|
||||
}
|
||||
}
|
||||
|
393
src/squeebot.ts
393
src/squeebot.ts
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user