mirror of
https://github.com/morten-olsen/mini-loader.git
synced 2026-02-08 01:36:26 +01:00
qa: add e2e tests
This commit is contained in:
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -3,7 +3,7 @@ name: Node.js Package
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened]
|
types: [opened, synchronize, reopened]
|
||||||
# release:
|
# release:
|
||||||
# types: [created]
|
# types: [created]
|
||||||
|
|
||||||
@@ -18,6 +18,7 @@ env:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: read
|
packages: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -34,6 +35,11 @@ jobs:
|
|||||||
|
|
||||||
- run: pnpm install
|
- run: pnpm install
|
||||||
- run: pnpm run test
|
- run: pnpm run test
|
||||||
|
- name: 'Report Coverage'
|
||||||
|
# Set if: always() to also generate the report if tests are failing
|
||||||
|
# Only works if you set `reportOnFailure: true` in your vite config as specified above
|
||||||
|
if: always()
|
||||||
|
uses: davelosert/vitest-coverage-report-action@v2
|
||||||
- run: pnpm run build
|
- run: pnpm run build
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
.turbo/
|
.turbo/
|
||||||
/.pnpm-store/
|
/.pnpm-store/
|
||||||
/out/
|
/out/
|
||||||
|
/coverage/
|
||||||
|
/e2e-tmp/
|
||||||
10
package.json
10
package.json
@@ -8,21 +8,25 @@
|
|||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"build:dev": "tsc --build --watch",
|
"build:dev": "tsc --build --watch",
|
||||||
"test:lint": "eslint ./packages/*/src",
|
"test:lint": "eslint ./packages/*/src",
|
||||||
"test": "pnpm run test:lint"
|
"test:code": "vitest --coverage",
|
||||||
|
"test": "pnpm run test:lint && pnpm run test:code"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/eslint-config": "^3.2.0",
|
"@react-native-community/eslint-config": "^3.2.0",
|
||||||
|
"@vitest/coverage-v8": "^1.2.0",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.53.0",
|
||||||
"prettier": "^2.8.8",
|
"prettier": "^2.8.8",
|
||||||
"turbo": "^1.10.16",
|
"turbo": "^1.10.16",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.0.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pnpm/find-workspace-packages": "^6.0.9",
|
"@pnpm/find-workspace-packages": "^6.0.9",
|
||||||
"@types/node": "^20.10.8",
|
"@types/node": "^20.10.8",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2",
|
||||||
|
"vitest": "^1.2.0"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/morten-olsen/mini-loader",
|
"homepage": "https://github.com/morten-olsen/mini-loader",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import 'source-map-support/register.js';
|
import 'source-map-support/register.js';
|
||||||
import '../dist/esm/index.js';
|
import '../dist/esm/bin.js';
|
||||||
|
|||||||
@@ -40,7 +40,8 @@
|
|||||||
"rollup": "^4.9.4",
|
"rollup": "^4.9.4",
|
||||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"superjson": "^2.2.1"
|
"superjson": "^2.2.1",
|
||||||
|
"typedi": "^0.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@morten-olsen/mini-loader-configs": "workspace:^",
|
"@morten-olsen/mini-loader-configs": "workspace:^",
|
||||||
|
|||||||
46
packages/cli/src/api/api.ts
Normal file
46
packages/cli/src/api/api.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { Context } from '../context/context.js';
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
|
import { Client, createClient } from '../client/client.js';
|
||||||
|
import type { Runtime } from '@morten-olsen/mini-loader-server';
|
||||||
|
import { Terminal } from './output.js';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
class CliApi {
|
||||||
|
#container: ContainerInstance;
|
||||||
|
#client?: Client;
|
||||||
|
|
||||||
|
constructor(container: ContainerInstance) {
|
||||||
|
this.#container = container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get log() {
|
||||||
|
return this.#container.get(Terminal).log;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get step() {
|
||||||
|
return this.#container.get(Terminal).step;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get output() {
|
||||||
|
return this.#container.get(Terminal).output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get config() {
|
||||||
|
return this.#container.get(Config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get client() {
|
||||||
|
if (!this.#client) {
|
||||||
|
this.#client = createClient(this.context);
|
||||||
|
}
|
||||||
|
return this.#client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get context() {
|
||||||
|
return new Context(this.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { Runtime };
|
||||||
|
export { CliApi };
|
||||||
27
packages/cli/src/api/output.ts
Normal file
27
packages/cli/src/api/output.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import ora from 'ora';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
class Terminal {
|
||||||
|
public log = (message: string) => {
|
||||||
|
console.log(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
public output = (data: unknown) => {
|
||||||
|
console.table(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
public step = async <T>(message: string, action: () => Promise<T>) => {
|
||||||
|
const spinner = ora(message).start();
|
||||||
|
try {
|
||||||
|
const result = await action();
|
||||||
|
await spinner.succeed();
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
await spinner.fail();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Terminal };
|
||||||
7
packages/cli/src/bin.ts
Normal file
7
packages/cli/src/bin.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
import { createClientCli } from './index.js';
|
||||||
|
|
||||||
|
const container = new ContainerInstance('client');
|
||||||
|
const program = createClientCli(container);
|
||||||
|
|
||||||
|
await program.parseAsync();
|
||||||
@@ -1,14 +1,9 @@
|
|||||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
||||||
import superjson from 'superjson';
|
import superjson from 'superjson';
|
||||||
import { createRequire } from 'module';
|
|
||||||
import type { Runtime } from '@morten-olsen/mini-loader-server';
|
import type { Runtime } from '@morten-olsen/mini-loader-server';
|
||||||
import type { RootRouter } from '@morten-olsen/mini-loader-server';
|
import type { RootRouter } from '@morten-olsen/mini-loader-server';
|
||||||
import { Context } from '../context/context.js';
|
import { Context } from '../context/context.js';
|
||||||
import { readFile } from 'fs/promises';
|
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
|
|
||||||
const pkg = JSON.parse(await readFile(require.resolve('#pkg'), 'utf-8'));
|
|
||||||
const createClient = (context: Context) => {
|
const createClient = (context: Context) => {
|
||||||
if (!context.host || !context.token) {
|
if (!context.host || !context.token) {
|
||||||
throw new Error('Not signed in');
|
throw new Error('Not signed in');
|
||||||
@@ -19,7 +14,6 @@ const createClient = (context: Context) => {
|
|||||||
httpBatchLink({
|
httpBatchLink({
|
||||||
url: `${context.host}/trpc`,
|
url: `${context.host}/trpc`,
|
||||||
headers: {
|
headers: {
|
||||||
'x-version': pkg.version,
|
|
||||||
authorization: `Bearer ${context.token}`,
|
authorization: `Bearer ${context.token}`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -22,11 +19,7 @@ list
|
|||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const { runId, loadId, offset, limit } = list.opts();
|
const { runId, loadId, offset, limit } = list.opts();
|
||||||
const config = new Config();
|
const { step, client } = getApi(list);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const artifacts = await step('Getting artifacts', async () => {
|
const artifacts = await step('Getting artifacts', async () => {
|
||||||
return await client.artifacts.find.query({
|
return await client.artifacts.find.query({
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { dirname, resolve } from 'path';
|
import { dirname, resolve } from 'path';
|
||||||
import { mkdir, writeFile } from 'fs/promises';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const pull = new Command('pull');
|
const pull = new Command('pull');
|
||||||
|
|
||||||
@@ -13,12 +10,8 @@ pull
|
|||||||
.argument('<artifact-id>', 'Artifact ID')
|
.argument('<artifact-id>', 'Artifact ID')
|
||||||
.argument('<file>', 'File to save')
|
.argument('<file>', 'File to save')
|
||||||
.action(async (id, file) => {
|
.action(async (id, file) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(pull);
|
||||||
const context = new Context(config.context);
|
|
||||||
const target = resolve(file);
|
const target = resolve(file);
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const artifact = await step('Getting artifact', async () => {
|
const artifact = await step('Getting artifact', async () => {
|
||||||
const result = await client.artifacts.get.query(id);
|
const result = await client.artifacts.get.query(id);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const remove = new Command('remove');
|
const remove = new Command('remove');
|
||||||
|
|
||||||
@@ -22,12 +19,8 @@ remove
|
|||||||
.option('-o, --offset <offset>', 'Offset')
|
.option('-o, --offset <offset>', 'Offset')
|
||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, client } = getApi(remove);
|
||||||
const { runId, loadId, offset, limit } = remove.opts();
|
const { runId, loadId, offset, limit } = remove.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const response = await step('Preparing to delete', async () => {
|
const response = await step('Preparing to delete', async () => {
|
||||||
return await client.artifacts.prepareRemove.query({
|
return await client.artifacts.prepareRemove.query({
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -1,28 +1,37 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import inquerer from 'inquirer';
|
import inquerer from 'inquirer';
|
||||||
import { Context } from '../../context/context.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const login = new Command('login');
|
const login = new Command('login');
|
||||||
|
|
||||||
login.description('Login to your account');
|
login.description('Login to your account');
|
||||||
|
login.option('--host <host>', 'The host of your server');
|
||||||
|
login.option('--token <token>', 'The token of your account');
|
||||||
login.action(async () => {
|
login.action(async () => {
|
||||||
const config = new Config();
|
let { host, token } = login.opts();
|
||||||
const context = new Context(config.context);
|
const { step, context } = getApi(login);
|
||||||
const { host, token } = await inquerer.prompt([
|
if (!host) {
|
||||||
{
|
const answers = await inquerer.prompt([
|
||||||
type: 'input',
|
{
|
||||||
name: 'host',
|
type: 'input',
|
||||||
message: 'Enter the host of your server',
|
name: 'host',
|
||||||
default: context.host ?? 'http://localhost:4500',
|
message: 'Enter the host of your server',
|
||||||
},
|
default: context.host || 'http://localhost:4500',
|
||||||
{
|
},
|
||||||
type: 'password',
|
]);
|
||||||
name: 'token',
|
host = answers.host;
|
||||||
message: 'Enter your token',
|
}
|
||||||
},
|
|
||||||
]);
|
if (!token) {
|
||||||
|
const answers = await inquerer.prompt([
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'token',
|
||||||
|
message: 'Enter your token',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
token = answers.token;
|
||||||
|
}
|
||||||
|
|
||||||
const healthResponse = await step('Getting auth status', async () => {
|
const healthResponse = await step('Getting auth status', async () => {
|
||||||
return await fetch(`${host}/health`, {
|
return await fetch(`${host}/health`, {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const current = new Command('current');
|
const current = new Command('current');
|
||||||
current.action(async () => {
|
current.action(async () => {
|
||||||
const config = new Config();
|
const { config } = getApi(current);
|
||||||
console.log(config.context);
|
console.log(config.context);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const use = new Command('use');
|
const use = new Command('use');
|
||||||
|
|
||||||
use.argument('<name>').action(async (name) => {
|
use.argument('<name>').action(async (name) => {
|
||||||
const config = new Config();
|
const { config } = getApi(use);
|
||||||
await config.setContext(name);
|
await config.setContext(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -10,15 +7,11 @@ list
|
|||||||
.alias('ls')
|
.alias('ls')
|
||||||
.description('List loads')
|
.description('List loads')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const config = new Config();
|
const { output, step, client } = getApi(list);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const loads = await step('Getting data', async () => {
|
const loads = await step('Getting data', async () => {
|
||||||
return await client.loads.find.query({});
|
return await client.loads.find.query({});
|
||||||
});
|
});
|
||||||
console.table(loads);
|
await output(loads);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { list };
|
export { list };
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { bundle } from '../../bundler/bundler.js';
|
import { bundle } from '../../bundler/bundler.js';
|
||||||
import { step } from '../../utils/step.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const push = new Command('push');
|
const push = new Command('push');
|
||||||
|
|
||||||
@@ -16,12 +13,8 @@ push
|
|||||||
.option('-ai, --auto-install', 'Auto install dependencies', false)
|
.option('-ai, --auto-install', 'Auto install dependencies', false)
|
||||||
.action(async (script) => {
|
.action(async (script) => {
|
||||||
const opts = push.opts();
|
const opts = push.opts();
|
||||||
const config = new Config();
|
const { step, log, client } = getApi(push);
|
||||||
const context = new Context(config.context);
|
|
||||||
const location = resolve(script);
|
const location = resolve(script);
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const code = await step('Bundling', async () => {
|
const code = await step('Bundling', async () => {
|
||||||
return await bundle({ entry: location, autoInstall: opts.autoInstall });
|
return await bundle({ entry: location, autoInstall: opts.autoInstall });
|
||||||
});
|
});
|
||||||
@@ -32,12 +25,12 @@ push
|
|||||||
script: code,
|
script: code,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.log('created load with id', id);
|
await log(`created load with id ${id}`);
|
||||||
if (opts.run) {
|
if (opts.run) {
|
||||||
const runId = await step('Creating run', async () => {
|
const runId = await step('Creating run', async () => {
|
||||||
return await client.runs.create.mutate({ loadId: id });
|
return await client.runs.create.mutate({ loadId: id });
|
||||||
});
|
});
|
||||||
console.log('created run with id', runId);
|
await log(`created run with id ${runId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { Command } from 'commander';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { run as runLoad } from '@morten-olsen/mini-loader-runner';
|
import { run as runLoad } from '@morten-olsen/mini-loader-runner';
|
||||||
import { bundle } from '../../bundler/bundler.js';
|
import { bundle } from '../../bundler/bundler.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { readSecrets } from './local.utils.js';
|
import { readSecrets } from './local.utils.js';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const run = new Command('run');
|
const run = new Command('run');
|
||||||
|
|
||||||
@@ -12,8 +11,8 @@ run
|
|||||||
.option('-ai, --auto-install', 'Auto install dependencies', false)
|
.option('-ai, --auto-install', 'Auto install dependencies', false)
|
||||||
.argument('script')
|
.argument('script')
|
||||||
.action(async (script) => {
|
.action(async (script) => {
|
||||||
|
const { step, config } = getApi(run);
|
||||||
const location = resolve(script);
|
const location = resolve(script);
|
||||||
const config = new Config();
|
|
||||||
const { autoInstall } = run.opts();
|
const { autoInstall } = run.opts();
|
||||||
const secrets = await readSecrets();
|
const secrets = await readSecrets();
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -24,11 +21,7 @@ list
|
|||||||
.option('-s, --sort <order>', 'Sort', 'desc')
|
.option('-s, --sort <order>', 'Sort', 'desc')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const { runId, loadId, severities, offset, limit, order } = list.opts();
|
const { runId, loadId, severities, offset, limit, order } = list.opts();
|
||||||
const config = new Config();
|
const { step, output, client } = getApi(list);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const logs = await step('Getting logs', async () => {
|
const logs = await step('Getting logs', async () => {
|
||||||
return await client.logs.find.query({
|
return await client.logs.find.query({
|
||||||
runId,
|
runId,
|
||||||
@@ -39,7 +32,7 @@ list
|
|||||||
order,
|
order,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.table(logs);
|
output(logs);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { list };
|
export { list };
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const remove = new Command('remove');
|
const remove = new Command('remove');
|
||||||
|
|
||||||
@@ -24,12 +21,8 @@ remove
|
|||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.option('-s, --sort <order>', 'Sort', 'desc')
|
.option('-s, --sort <order>', 'Sort', 'desc')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, client } = getApi(remove);
|
||||||
const { runId, loadId, severities, offset, limit, order } = remove.opts();
|
const { runId, loadId, severities, offset, limit, order } = remove.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const response = await step('Preparing to delete', async () => {
|
const response = await step('Preparing to delete', async () => {
|
||||||
return await client.logs.prepareRemove.query({
|
return await client.logs.prepareRemove.query({
|
||||||
runId,
|
runId,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const create = new Command('create');
|
const create = new Command('create');
|
||||||
|
|
||||||
@@ -10,11 +7,7 @@ create
|
|||||||
.description('Create a new run')
|
.description('Create a new run')
|
||||||
.argument('load-id', 'Load ID')
|
.argument('load-id', 'Load ID')
|
||||||
.action(async (loadId) => {
|
.action(async (loadId) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(create);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
await step('Creating run', async () => {
|
await step('Creating run', async () => {
|
||||||
await client.runs.create.mutate({ loadId });
|
await client.runs.create.mutate({ loadId });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -11,15 +8,11 @@ list
|
|||||||
.description('Find a run')
|
.description('Find a run')
|
||||||
.argument('[load-id]', 'Load ID')
|
.argument('[load-id]', 'Load ID')
|
||||||
.action(async (loadId) => {
|
.action(async (loadId) => {
|
||||||
const config = new Config();
|
const { step, output, client } = getApi(list);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const runs = await step('Getting runs', async () => {
|
const runs = await step('Getting runs', async () => {
|
||||||
return await client.runs.find.query({ loadId });
|
return await client.runs.find.query({ loadId });
|
||||||
});
|
});
|
||||||
console.table(runs);
|
await output(runs);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { list };
|
export { list };
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const remove = new Command('remove');
|
const remove = new Command('remove');
|
||||||
|
|
||||||
@@ -21,12 +18,8 @@ remove
|
|||||||
.option('-o, --offset <offset>', 'Offset')
|
.option('-o, --offset <offset>', 'Offset')
|
||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, client } = getApi(remove);
|
||||||
const { loadId, offset, limit } = remove.opts();
|
const { loadId, offset, limit } = remove.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const response = await step('Preparing to delete', async () => {
|
const response = await step('Preparing to delete', async () => {
|
||||||
return await client.runs.prepareRemove.query({
|
return await client.runs.prepareRemove.query({
|
||||||
loadId,
|
loadId,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const terminate = new Command('terminate');
|
const terminate = new Command('terminate');
|
||||||
|
|
||||||
@@ -10,11 +7,7 @@ terminate
|
|||||||
.description('Terminate an in progress run')
|
.description('Terminate an in progress run')
|
||||||
.argument('run-id', 'Run ID')
|
.argument('run-id', 'Run ID')
|
||||||
.action(async (runId) => {
|
.action(async (runId) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(terminate);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
await step('Terminating run', async () => {
|
await step('Terminating run', async () => {
|
||||||
await client.runs.terminate.mutate(runId);
|
await client.runs.terminate.mutate(runId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const add = new Command('add');
|
const add = new Command('add');
|
||||||
|
|
||||||
@@ -12,12 +9,8 @@ add
|
|||||||
.argument('<cron>', 'Cron')
|
.argument('<cron>', 'Cron')
|
||||||
.option('-n, --name <name>', 'Name')
|
.option('-n, --name <name>', 'Name')
|
||||||
.action(async (loadId, cron) => {
|
.action(async (loadId, cron) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(add);
|
||||||
const context = new Context(config.context);
|
|
||||||
const { name } = add.opts();
|
const { name } = add.opts();
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const id = await step('Adding schedule', async () => {
|
const id = await step('Adding schedule', async () => {
|
||||||
return await client.schedules.add.mutate({
|
return await client.schedules.add.mutate({
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -20,12 +17,8 @@ list
|
|||||||
.option('-o, --offset <offset>', 'Offset')
|
.option('-o, --offset <offset>', 'Offset')
|
||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, output, client } = getApi(list);
|
||||||
const { loadIds, offset, limit } = list.opts();
|
const { loadIds, offset, limit } = list.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const schedules = await step('Getting schedules', async () => {
|
const schedules = await step('Getting schedules', async () => {
|
||||||
return await client.schedules.find.query({
|
return await client.schedules.find.query({
|
||||||
loadIds,
|
loadIds,
|
||||||
@@ -33,7 +26,7 @@ list
|
|||||||
limit: toInt(limit),
|
limit: toInt(limit),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
console.table(schedules);
|
output(schedules);
|
||||||
});
|
});
|
||||||
|
|
||||||
export { list };
|
export { list };
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import inquirer from 'inquirer';
|
import inquirer from 'inquirer';
|
||||||
import { Config } from '../../config/config.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
|
|
||||||
const remove = new Command('remove');
|
const remove = new Command('remove');
|
||||||
|
|
||||||
@@ -22,12 +19,8 @@ remove
|
|||||||
.option('-o, --offset <offset>', 'Offset')
|
.option('-o, --offset <offset>', 'Offset')
|
||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, client } = getApi(remove);
|
||||||
const { ids, loadIds, offset, limit } = remove.opts();
|
const { ids, loadIds, offset, limit } = remove.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const response = await step('Preparing to delete', async () => {
|
const response = await step('Preparing to delete', async () => {
|
||||||
return await client.schedules.prepareRemove.query({
|
return await client.schedules.prepareRemove.query({
|
||||||
ids,
|
ids,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const list = new Command('list');
|
const list = new Command('list');
|
||||||
|
|
||||||
@@ -15,16 +12,12 @@ const toInt = (value?: string) => {
|
|||||||
|
|
||||||
list
|
list
|
||||||
.alias('ls')
|
.alias('ls')
|
||||||
.description('List logs')
|
.description('List secrets')
|
||||||
.option('-o, --offset <offset>', 'Offset')
|
.option('-o, --offset <offset>', 'Offset')
|
||||||
.option('-a, --limit <limit>', 'Limit', '1000')
|
.option('-a, --limit <limit>', 'Limit', '1000')
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
|
const { step, client } = getApi(list);
|
||||||
const { offset, limit } = list.opts();
|
const { offset, limit } = list.opts();
|
||||||
const config = new Config();
|
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
const secrets = await step('Getting secrets', async () => {
|
const secrets = await step('Getting secrets', async () => {
|
||||||
return await client.secrets.find.query({
|
return await client.secrets.find.query({
|
||||||
offset: toInt(offset),
|
offset: toInt(offset),
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const remove = new Command('remove');
|
const remove = new Command('remove');
|
||||||
|
|
||||||
@@ -10,11 +7,7 @@ remove
|
|||||||
.alias('rm')
|
.alias('rm')
|
||||||
.argument('<id>')
|
.argument('<id>')
|
||||||
.action(async (id) => {
|
.action(async (id) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(remove);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
await step('Removing', async () => {
|
await step('Removing', async () => {
|
||||||
await client.secrets.remove.mutate({
|
await client.secrets.remove.mutate({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { Command } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createClient } from '../../client/client.js';
|
import { getApi } from '../../utils/command.js';
|
||||||
import { step } from '../../utils/step.js';
|
|
||||||
import { Context } from '../../context/context.js';
|
|
||||||
import { Config } from '../../config/config.js';
|
|
||||||
|
|
||||||
const set = new Command('set');
|
const set = new Command('set');
|
||||||
|
|
||||||
@@ -10,11 +7,7 @@ set
|
|||||||
.argument('<id>')
|
.argument('<id>')
|
||||||
.argument('[value]')
|
.argument('[value]')
|
||||||
.action(async (id, value) => {
|
.action(async (id, value) => {
|
||||||
const config = new Config();
|
const { step, client } = getApi(set);
|
||||||
const context = new Context(config.context);
|
|
||||||
const client = await step('Connecting to server', async () => {
|
|
||||||
return createClient(context);
|
|
||||||
});
|
|
||||||
await step('Setting secret', async () => {
|
await step('Setting secret', async () => {
|
||||||
await client.secrets.set.mutate({
|
await client.secrets.set.mutate({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import envPaths from 'env-paths';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { mkdir } from 'fs/promises';
|
|
||||||
import { join, dirname } from 'path';
|
import { join, dirname } from 'path';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { Paths } from '../paths/paths.js';
|
||||||
|
|
||||||
type ConfigValues = {
|
type ConfigValues = {
|
||||||
context?: string;
|
context?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const paths = envPaths('mini-loader');
|
@Service()
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
|
#paths: Paths;
|
||||||
#location: string;
|
#location: string;
|
||||||
#config?: ConfigValues;
|
#config?: ConfigValues;
|
||||||
|
|
||||||
constructor() {
|
constructor(contianer: ContainerInstance) {
|
||||||
this.#location = join(paths.config, 'config.json');
|
this.#paths = contianer.get(Paths);
|
||||||
|
this.#location = join(this.#paths.config, 'config.json');
|
||||||
if (existsSync(this.#location)) {
|
if (existsSync(this.#location)) {
|
||||||
this.#config = JSON.parse(readFileSync(this.#location, 'utf-8'));
|
this.#config = JSON.parse(readFileSync(this.#location, 'utf-8'));
|
||||||
}
|
}
|
||||||
@@ -24,8 +26,12 @@ class Config {
|
|||||||
return this.#config?.context || 'default';
|
return this.#config?.context || 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get location() {
|
||||||
|
return this.#paths.config;
|
||||||
|
}
|
||||||
|
|
||||||
public get cacheLocation() {
|
public get cacheLocation() {
|
||||||
return join(paths.cache, this.context);
|
return join(this.#paths.cache, this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setContext = (context: string) => {
|
public setContext = (context: string) => {
|
||||||
@@ -42,7 +48,7 @@ class Config {
|
|||||||
}
|
}
|
||||||
const json = JSON.stringify(this.#config);
|
const json = JSON.stringify(this.#config);
|
||||||
mkdir(dirname(this.#location), { recursive: true });
|
mkdir(dirname(this.#location), { recursive: true });
|
||||||
writeFileSync(this.#location, json);
|
writeFile(this.#location, json);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import envPaths from 'env-paths';
|
|||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import { mkdir, readdir, writeFile } from 'fs/promises';
|
import { mkdir, readdir, writeFile } from 'fs/promises';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
|
|
||||||
type ContextValues = {
|
type ContextValues = {
|
||||||
host: string;
|
host: string;
|
||||||
@@ -12,9 +13,8 @@ class Context {
|
|||||||
#location: string;
|
#location: string;
|
||||||
#config?: ContextValues;
|
#config?: ContextValues;
|
||||||
|
|
||||||
constructor(name: string) {
|
constructor(config: Config) {
|
||||||
const paths = envPaths('mini-loader');
|
this.#location = join(config.location, 'contexts', config.context);
|
||||||
this.#location = join(paths.config, 'contexts', name);
|
|
||||||
if (existsSync(this.#location)) {
|
if (existsSync(this.#location)) {
|
||||||
this.#config = JSON.parse(readFileSync(this.#location, 'utf-8'));
|
this.#config = JSON.parse(readFileSync(this.#location, 'utf-8'));
|
||||||
}
|
}
|
||||||
@@ -28,13 +28,13 @@ class Context {
|
|||||||
return this.#config?.token;
|
return this.#config?.token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveLogin = (host: string, token: string) => {
|
public saveLogin = async (host: string, token: string) => {
|
||||||
this.#config = {
|
this.#config = {
|
||||||
...(this.#config || {}),
|
...(this.#config || {}),
|
||||||
host,
|
host,
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
this.save();
|
await this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
public save = async () => {
|
public save = async () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Command, program } from 'commander';
|
import { Command } from 'commander';
|
||||||
import { createRequire } from 'module';
|
|
||||||
import { loads } from './commands/loads/loads.js';
|
import { loads } from './commands/loads/loads.js';
|
||||||
import { runs } from './commands/runs/runs.js';
|
import { runs } from './commands/runs/runs.js';
|
||||||
import { logs } from './commands/logs/logs.js';
|
import { logs } from './commands/logs/logs.js';
|
||||||
@@ -9,28 +8,27 @@ import { local } from './commands/local/local.js';
|
|||||||
import { auth } from './commands/auth/auth.js';
|
import { auth } from './commands/auth/auth.js';
|
||||||
import { contexts } from './commands/contexts/contexts.js';
|
import { contexts } from './commands/contexts/contexts.js';
|
||||||
import { schedules } from './commands/schedules/schedules.js';
|
import { schedules } from './commands/schedules/schedules.js';
|
||||||
import { readFile } from 'fs/promises';
|
import { ContainerInstance } from 'typedi';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
const createClientCli = (container: ContainerInstance) => {
|
||||||
|
const program = new Command();
|
||||||
|
program.exitOverride();
|
||||||
|
program.setOptionValue('_container', container);
|
||||||
|
program.addCommand(loads);
|
||||||
|
program.addCommand(runs);
|
||||||
|
program.addCommand(logs);
|
||||||
|
program.addCommand(artifacts);
|
||||||
|
program.addCommand(secrets);
|
||||||
|
program.addCommand(local);
|
||||||
|
program.addCommand(auth);
|
||||||
|
program.addCommand(contexts);
|
||||||
|
program.addCommand(schedules);
|
||||||
|
|
||||||
const pkg = JSON.parse(await readFile(require.resolve('#pkg'), 'utf-8'));
|
return program;
|
||||||
|
};
|
||||||
|
|
||||||
program.addCommand(loads);
|
export { CliApi } from './api/api.js';
|
||||||
program.addCommand(runs);
|
export { Context } from './context/context.js';
|
||||||
program.addCommand(logs);
|
export { Terminal } from './api/output.js';
|
||||||
program.addCommand(artifacts);
|
export { Paths } from './paths/paths.js';
|
||||||
program.addCommand(secrets);
|
export { createClientCli };
|
||||||
program.addCommand(local);
|
|
||||||
program.addCommand(auth);
|
|
||||||
program.addCommand(contexts);
|
|
||||||
program.addCommand(schedules);
|
|
||||||
|
|
||||||
program.version(pkg.version);
|
|
||||||
|
|
||||||
const version = new Command('version');
|
|
||||||
version.action(() => {
|
|
||||||
console.log(pkg.version);
|
|
||||||
});
|
|
||||||
program.addCommand(version);
|
|
||||||
|
|
||||||
await program.parseAsync();
|
|
||||||
|
|||||||
17
packages/cli/src/paths/paths.ts
Normal file
17
packages/cli/src/paths/paths.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import envPaths from 'env-paths';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
const paths = envPaths('mini-loader');
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
class Paths {
|
||||||
|
public get config() {
|
||||||
|
return paths.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cache() {
|
||||||
|
return paths.cache;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Paths };
|
||||||
11
packages/cli/src/utils/command.ts
Normal file
11
packages/cli/src/utils/command.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Command } from 'commander';
|
||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
import { CliApi } from '../api/api.js';
|
||||||
|
|
||||||
|
const getApi = (command: Command) => {
|
||||||
|
const { _container } = command.optsWithGlobals() as { _container: ContainerInstance };
|
||||||
|
const api = _container.get(CliApi);
|
||||||
|
return api;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getApi };
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import ora from 'ora';
|
|
||||||
|
|
||||||
const step = async <T>(message: string, fn: () => Promise<T>): Promise<T> => {
|
|
||||||
const spinner = ora(message).start();
|
|
||||||
try {
|
|
||||||
const result = await fn();
|
|
||||||
await spinner.succeed();
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
await spinner.fail();
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export { step };
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import 'source-map-support/register.js';
|
import 'source-map-support/register.js';
|
||||||
import '../dist/esm/index.js';
|
import '../dist/esm/bin.js';
|
||||||
|
|||||||
@@ -41,9 +41,11 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"knex": "^3.1.0",
|
"knex": "^3.1.0",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
|
"reflect-metadata": "^0.2.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"superjson": "^2.2.1",
|
"superjson": "^2.2.1",
|
||||||
|
"typedi": "^0.10.0",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/morten-olsen/mini-loader",
|
"homepage": "https://github.com/morten-olsen/mini-loader",
|
||||||
|
|||||||
@@ -4,17 +4,21 @@ import { existsSync } from 'fs';
|
|||||||
import { mkdir, readFile, writeFile } from 'fs/promises';
|
import { mkdir, readFile, writeFile } from 'fs/promises';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { Config } from '../config/config.js';
|
import { Config } from '../config/config.js';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
|
||||||
type AuthOptions = {
|
type AuthOptions = {
|
||||||
config: Config;
|
config: Config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class Auth {
|
class Auth {
|
||||||
#options: AuthOptions;
|
#options: AuthOptions;
|
||||||
#data: Promise<{ secret: string }>;
|
#data: Promise<{ secret: string }>;
|
||||||
|
|
||||||
constructor(options: AuthOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
config: container.get(Config),
|
||||||
|
};
|
||||||
this.#data = this.#setup();
|
this.#data = this.#setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
packages/server/src/bin.ts
Normal file
14
packages/server/src/bin.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
import { createServerCli } from './index.js';
|
||||||
|
|
||||||
|
const program = createServerCli(new ContainerInstance('server'));
|
||||||
|
|
||||||
|
program.setOptionValue('output', (data: unknown) => {
|
||||||
|
console.log('got data', data);
|
||||||
|
});
|
||||||
|
|
||||||
|
await program.parseAsync(process.argv);
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
@@ -1,10 +1,22 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
import envPaths from 'env-paths';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
type Config = {
|
const paths = envPaths('mini-loader-server');
|
||||||
database: Omit<Knex.Config, 'migrations'>;
|
|
||||||
files: {
|
@Service()
|
||||||
data: string;
|
class Config {
|
||||||
cache: string;
|
database: Omit<Knex.Config, 'migrations'> = {
|
||||||
|
client: 'sqlite3',
|
||||||
|
connection: {
|
||||||
|
filename: join(paths.data, 'db.sqlite'),
|
||||||
|
},
|
||||||
|
useNullAsDefault: true,
|
||||||
|
};
|
||||||
|
files = {
|
||||||
|
data: paths.data,
|
||||||
|
cache: paths.cache,
|
||||||
};
|
};
|
||||||
auth?: {
|
auth?: {
|
||||||
oidc?: {
|
oidc?: {
|
||||||
@@ -15,6 +27,6 @@ type Config = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export type { Config };
|
export { Config };
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import knex, { Knex } from 'knex';
|
import knex, { Knex } from 'knex';
|
||||||
|
import { Service, ContainerInstance } from 'typedi';
|
||||||
|
|
||||||
|
import { Config } from '../config/config.js';
|
||||||
import { source } from './migrations/migrations.source.js';
|
import { source } from './migrations/migrations.source.js';
|
||||||
import { mkdir } from 'fs/promises';
|
import { mkdir } from 'fs/promises';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
@@ -8,13 +10,15 @@ const tableNames = {
|
|||||||
loads: 'loads',
|
loads: 'loads',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class Database {
|
class Database {
|
||||||
#instance?: Promise<Knex>;
|
#instance?: Promise<Knex>;
|
||||||
#config: Knex.Config;
|
#config: Knex.Config;
|
||||||
|
|
||||||
constructor(config: Knex.Config) {
|
constructor(container: ContainerInstance) {
|
||||||
|
const config = container.get(Config);
|
||||||
this.#config = {
|
this.#config = {
|
||||||
...config,
|
...config.database,
|
||||||
migrations: {
|
migrations: {
|
||||||
migrationSource: source,
|
migrationSource: source,
|
||||||
},
|
},
|
||||||
|
|||||||
9
packages/server/src/id/id.ts
Normal file
9
packages/server/src/id/id.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { Service } from 'typedi';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
class IdGenerator {
|
||||||
|
public generate = () => nanoid();
|
||||||
|
}
|
||||||
|
|
||||||
|
export { IdGenerator };
|
||||||
@@ -1,40 +1,42 @@
|
|||||||
import { program, Command } from 'commander';
|
import 'reflect-metadata';
|
||||||
|
import { Command } from 'commander';
|
||||||
import { Runtime } from './runtime/runtime.js';
|
import { Runtime } from './runtime/runtime.js';
|
||||||
import { createServer } from './server/server.js';
|
import { createServer } from './server/server.js';
|
||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
|
||||||
const start = new Command('start');
|
const createServerCli = (container: ContainerInstance) => {
|
||||||
start.action(async () => {
|
const program = new Command();
|
||||||
const port = 4500;
|
|
||||||
const runtime = await Runtime.create();
|
const start = new Command('start');
|
||||||
await runtime.scheduler.start();
|
start.action(async () => {
|
||||||
const server = await createServer(runtime);
|
const port = 4500;
|
||||||
await server.listen({
|
const runtime = container.get(Runtime);
|
||||||
port,
|
await runtime.scheduler.start();
|
||||||
host: '0.0.0.0',
|
const server = await createServer(container);
|
||||||
|
await server.listen({
|
||||||
|
port,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Server listening on port ${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Server listening on port ${port}`);
|
const createToken = new Command('create-token');
|
||||||
});
|
createToken.action(async () => {
|
||||||
|
const runtime = container.get(Runtime);
|
||||||
const createToken = new Command('create-token');
|
const token = await runtime.auth.createToken({
|
||||||
createToken.action(async () => {
|
policy: {
|
||||||
const runtime = await Runtime.create();
|
'*:*': ['*'],
|
||||||
const token = await runtime.auth.createToken({
|
},
|
||||||
policy: {
|
});
|
||||||
'*:*': ['*'],
|
console.log(token);
|
||||||
},
|
|
||||||
});
|
});
|
||||||
console.log(token);
|
|
||||||
});
|
|
||||||
|
|
||||||
program.addCommand(start);
|
program.addCommand(start);
|
||||||
program.addCommand(createToken);
|
program.addCommand(createToken);
|
||||||
|
return program;
|
||||||
|
};
|
||||||
|
|
||||||
await program.parseAsync(process.argv);
|
export { createServerCli, createServer, Runtime };
|
||||||
|
export { Config } from './config/config.js';
|
||||||
process.on('unhandledRejection', (reason, p) => {
|
|
||||||
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
|
||||||
});
|
|
||||||
|
|
||||||
export type { Runtime } from './runtime/runtime.js';
|
|
||||||
export type { RootRouter } from './router/router.js';
|
export type { RootRouter } from './router/router.js';
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { AddArtifactOptions, FindArtifactsOptions } from './artifacts.schemas.js';
|
import { AddArtifactOptions, FindArtifactsOptions } from './artifacts.schemas.js';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { IdGenerator } from '../../id/id.js';
|
||||||
|
|
||||||
type ArtifactRepoEvents = {};
|
type ArtifactRepoEvents = {};
|
||||||
|
|
||||||
type ArtifactRepoOptions = {
|
type ArtifactRepoOptions = {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
idGenerator: IdGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class ArtifactRepo extends EventEmitter<ArtifactRepoEvents> {
|
class ArtifactRepo extends EventEmitter<ArtifactRepoEvents> {
|
||||||
#options: ArtifactRepoOptions;
|
#options: ArtifactRepoOptions;
|
||||||
|
|
||||||
constructor(options: ArtifactRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
database: container.get(Database),
|
||||||
|
idGenerator: container.get(IdGenerator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public get = async (id: string) => {
|
public get = async (id: string) => {
|
||||||
@@ -26,9 +32,9 @@ class ArtifactRepo extends EventEmitter<ArtifactRepoEvents> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public add = async (options: AddArtifactOptions) => {
|
public add = async (options: AddArtifactOptions) => {
|
||||||
const { database } = this.#options;
|
const { database, idGenerator } = this.#options;
|
||||||
const db = await database.instance;
|
const db = await database.instance;
|
||||||
const id = nanoid();
|
const id = idGenerator.generate();
|
||||||
|
|
||||||
await db('artifacts').insert({
|
await db('artifacts').insert({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { FindLoadsOptions, SetLoadOptions } from './loads.schemas.js';
|
import { FindLoadsOptions, SetLoadOptions } from './loads.schemas.js';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import { Config } from '../../config/config.js';
|
import { Config } from '../../config/config.js';
|
||||||
import { mkdir, writeFile } from 'fs/promises';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { IdGenerator } from '../../id/id.js';
|
||||||
|
|
||||||
type LoadRepoEvents = {
|
type LoadRepoEvents = {
|
||||||
created: (id: string) => void;
|
created: (id: string) => void;
|
||||||
@@ -16,14 +17,20 @@ type LoadRepoEvents = {
|
|||||||
type LoadRepoOptions = {
|
type LoadRepoOptions = {
|
||||||
database: Database;
|
database: Database;
|
||||||
config: Config;
|
config: Config;
|
||||||
|
idGenerator: IdGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class LoadRepo extends EventEmitter<LoadRepoEvents> {
|
class LoadRepo extends EventEmitter<LoadRepoEvents> {
|
||||||
#options: LoadRepoOptions;
|
#options: LoadRepoOptions;
|
||||||
|
|
||||||
constructor(options: LoadRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
database: container.get(Database),
|
||||||
|
config: container.get(Config),
|
||||||
|
idGenerator: container.get(IdGenerator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public getById = async (id: string) => {
|
public getById = async (id: string) => {
|
||||||
@@ -58,9 +65,9 @@ class LoadRepo extends EventEmitter<LoadRepoEvents> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public set = async (options: SetLoadOptions) => {
|
public set = async (options: SetLoadOptions) => {
|
||||||
const { database } = this.#options;
|
const { database, idGenerator } = this.#options;
|
||||||
const db = await database.instance;
|
const db = await database.instance;
|
||||||
const id = options.id || nanoid();
|
const id = options.id || idGenerator.generate();
|
||||||
const script = createHash('sha256').update(options.script).digest('hex');
|
const script = createHash('sha256').update(options.script).digest('hex');
|
||||||
const scriptDir = resolve(this.#options.config.files.data, 'scripts');
|
const scriptDir = resolve(this.#options.config.files.data, 'scripts');
|
||||||
await mkdir(scriptDir, { recursive: true });
|
await mkdir(scriptDir, { recursive: true });
|
||||||
|
|||||||
@@ -1,27 +1,33 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { AddLogOptions, FindLogsOptions } from './logs.schemas.js';
|
import { AddLogOptions, FindLogsOptions } from './logs.schemas.js';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { IdGenerator } from '../../id/id.js';
|
||||||
|
|
||||||
type LogRepoEvents = {};
|
type LogRepoEvents = {};
|
||||||
|
|
||||||
type LogRepoOptions = {
|
type LogRepoOptions = {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
idGenerator: IdGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class LogRepo extends EventEmitter<LogRepoEvents> {
|
class LogRepo extends EventEmitter<LogRepoEvents> {
|
||||||
#options: LogRepoOptions;
|
#options: LogRepoOptions;
|
||||||
|
|
||||||
constructor(options: LogRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
database: container.get(Database),
|
||||||
|
idGenerator: container.get(IdGenerator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public add = async (options: AddLogOptions) => {
|
public add = async (options: AddLogOptions) => {
|
||||||
const { database } = this.#options;
|
const { database, idGenerator } = this.#options;
|
||||||
const db = await database.instance;
|
const db = await database.instance;
|
||||||
const id = nanoid();
|
const id = idGenerator.generate();
|
||||||
|
|
||||||
await db('logs').insert({
|
await db('logs').insert({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Config } from '../config/config.js';
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
import { Database } from '../database/database.js';
|
|
||||||
import { ArtifactRepo } from './artifacts/artifacts.js';
|
import { ArtifactRepo } from './artifacts/artifacts.js';
|
||||||
import { LoadRepo } from './loads/loads.js';
|
import { LoadRepo } from './loads/loads.js';
|
||||||
import { LogRepo } from './logs/logs.js';
|
import { LogRepo } from './logs/logs.js';
|
||||||
@@ -7,64 +6,36 @@ import { RunRepo } from './runs/runs.js';
|
|||||||
import { ScheduleRepo } from './schedules/schedules.js';
|
import { ScheduleRepo } from './schedules/schedules.js';
|
||||||
import { SecretRepo } from './secrets/secrets.js';
|
import { SecretRepo } from './secrets/secrets.js';
|
||||||
|
|
||||||
type ReposOptions = {
|
@Service()
|
||||||
database: Database;
|
|
||||||
config: Config;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Repos {
|
class Repos {
|
||||||
#loads: LoadRepo;
|
#container: ContainerInstance;
|
||||||
#runs: RunRepo;
|
|
||||||
#logs: LogRepo;
|
|
||||||
#artifacts: ArtifactRepo;
|
|
||||||
#secrets: SecretRepo;
|
|
||||||
#schedule: ScheduleRepo;
|
|
||||||
|
|
||||||
constructor({ database, config }: ReposOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
this.#loads = new LoadRepo({
|
this.#container = container;
|
||||||
database,
|
|
||||||
config,
|
|
||||||
});
|
|
||||||
this.#runs = new RunRepo({
|
|
||||||
database,
|
|
||||||
loads: this.#loads,
|
|
||||||
});
|
|
||||||
this.#logs = new LogRepo({
|
|
||||||
database,
|
|
||||||
});
|
|
||||||
this.#artifacts = new ArtifactRepo({
|
|
||||||
database,
|
|
||||||
});
|
|
||||||
this.#secrets = new SecretRepo({
|
|
||||||
database,
|
|
||||||
});
|
|
||||||
this.#schedule = new ScheduleRepo({
|
|
||||||
database,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get loads() {
|
public get loads() {
|
||||||
return this.#loads;
|
return this.#container.get(LoadRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get runs() {
|
public get runs() {
|
||||||
return this.#runs;
|
return this.#container.get(RunRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get logs() {
|
public get logs() {
|
||||||
return this.#logs;
|
return this.#container.get(LogRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get artifacts() {
|
public get artifacts() {
|
||||||
return this.#artifacts;
|
return this.#container.get(ArtifactRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get secrets() {
|
public get secrets() {
|
||||||
return this.#secrets;
|
return this.#container.get(SecretRepo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get schedules() {
|
public get schedules() {
|
||||||
return this.#schedule;
|
return this.#container.get(ScheduleRepo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { CreateRunOptions, FindRunsOptions, UpdateRunOptions } from './runs.schemas.js';
|
import { CreateRunOptions, FindRunsOptions, UpdateRunOptions } from './runs.schemas.js';
|
||||||
import { LoadRepo } from '../loads/loads.js';
|
import { LoadRepo } from '../loads/loads.js';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { IdGenerator } from '../../id/id.js';
|
||||||
|
|
||||||
type RunRepoEvents = {
|
type RunRepoEvents = {
|
||||||
created: (args: { id: string; loadId: string }) => void;
|
created: (args: { id: string; loadId: string }) => void;
|
||||||
@@ -15,16 +16,21 @@ type RunRepoEvents = {
|
|||||||
type RunRepoOptions = {
|
type RunRepoOptions = {
|
||||||
database: Database;
|
database: Database;
|
||||||
loads: LoadRepo;
|
loads: LoadRepo;
|
||||||
|
idGenerator: IdGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class RunRepo extends EventEmitter<RunRepoEvents> {
|
class RunRepo extends EventEmitter<RunRepoEvents> {
|
||||||
#options: RunRepoOptions;
|
#options: RunRepoOptions;
|
||||||
#isReady: Promise<void>;
|
#isSetup?: Promise<void>;
|
||||||
|
|
||||||
constructor(options: RunRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
this.#isReady = this.#setup();
|
database: container.get(Database),
|
||||||
|
loads: container.get(LoadRepo),
|
||||||
|
idGenerator: container.get(IdGenerator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#setup = async () => {
|
#setup = async () => {
|
||||||
@@ -33,6 +39,13 @@ class RunRepo extends EventEmitter<RunRepoEvents> {
|
|||||||
await db('runs').update({ status: 'failed', error: 'server was shut down' }).where({ status: 'running' });
|
await db('runs').update({ status: 'failed', error: 'server was shut down' }).where({ status: 'running' });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
get #isReady() {
|
||||||
|
if (!this.#isSetup) {
|
||||||
|
this.#isSetup = this.#setup();
|
||||||
|
}
|
||||||
|
return this.#isSetup;
|
||||||
|
}
|
||||||
|
|
||||||
public getById = async (id: string) => {
|
public getById = async (id: string) => {
|
||||||
await this.#isReady;
|
await this.#isReady;
|
||||||
const { database } = this.#options;
|
const { database } = this.#options;
|
||||||
@@ -150,8 +163,8 @@ class RunRepo extends EventEmitter<RunRepoEvents> {
|
|||||||
|
|
||||||
public create = async (options: CreateRunOptions) => {
|
public create = async (options: CreateRunOptions) => {
|
||||||
await this.#isReady;
|
await this.#isReady;
|
||||||
const { database, loads } = this.#options;
|
const { database, loads, idGenerator } = this.#options;
|
||||||
const id = nanoid();
|
const id = idGenerator.generate();
|
||||||
const db = await database.instance;
|
const db = await database.instance;
|
||||||
|
|
||||||
const script = await loads.getScript(options.loadId);
|
const script = await loads.getScript(options.loadId);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { AddScheduleOptions, FindSchedulesOptions } from './schedules.schemas.js';
|
import { AddScheduleOptions, FindSchedulesOptions } from './schedules.schemas.js';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
import { IdGenerator } from '../../id/id.js';
|
||||||
|
|
||||||
type ScheduleRepoEvents = {
|
type ScheduleRepoEvents = {
|
||||||
added: (id: string) => void;
|
added: (id: string) => void;
|
||||||
@@ -11,14 +12,19 @@ type ScheduleRepoEvents = {
|
|||||||
|
|
||||||
type ScheduleRepoOptions = {
|
type ScheduleRepoOptions = {
|
||||||
database: Database;
|
database: Database;
|
||||||
|
idGenerator: IdGenerator;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class ScheduleRepo extends EventEmitter<ScheduleRepoEvents> {
|
class ScheduleRepo extends EventEmitter<ScheduleRepoEvents> {
|
||||||
#options: ScheduleRepoOptions;
|
#options: ScheduleRepoOptions;
|
||||||
|
|
||||||
constructor(options: ScheduleRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
database: container.get(Database),
|
||||||
|
idGenerator: container.get(IdGenerator),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public get = async (id: string) => {
|
public get = async (id: string) => {
|
||||||
@@ -29,9 +35,9 @@ class ScheduleRepo extends EventEmitter<ScheduleRepoEvents> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public add = async (options: AddScheduleOptions) => {
|
public add = async (options: AddScheduleOptions) => {
|
||||||
const { database } = this.#options;
|
const { database, idGenerator } = this.#options;
|
||||||
const db = await database.instance;
|
const db = await database.instance;
|
||||||
const id = nanoid();
|
const id = idGenerator.generate();
|
||||||
|
|
||||||
await db('schedules').insert({
|
await db('schedules').insert({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { Database } from '../../database/database.js';
|
import { Database } from '../../database/database.js';
|
||||||
import { FindSecretOptions, SetSecretOptions } from './secrets.schemas.js';
|
import { FindSecretOptions, SetSecretOptions } from './secrets.schemas.js';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
|
||||||
type LogRepoEvents = {};
|
type LogRepoEvents = {};
|
||||||
|
|
||||||
@@ -8,12 +9,15 @@ type LogRepoOptions = {
|
|||||||
database: Database;
|
database: Database;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class SecretRepo extends EventEmitter<LogRepoEvents> {
|
class SecretRepo extends EventEmitter<LogRepoEvents> {
|
||||||
#options: LogRepoOptions;
|
#options: LogRepoOptions;
|
||||||
|
|
||||||
constructor(options: LogRepoOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
super();
|
super();
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
database: container.get(Database),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public set = async (options: SetSecretOptions) => {
|
public set = async (options: SetSecretOptions) => {
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { initTRPC } from '@trpc/server';
|
|||||||
import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
import { CreateFastifyContextOptions } from '@trpc/server/adapters/fastify';
|
||||||
import superjson from 'superjson';
|
import superjson from 'superjson';
|
||||||
import { Runtime } from '../runtime/runtime.js';
|
import { Runtime } from '../runtime/runtime.js';
|
||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
|
||||||
type ContextOptions = {
|
type ContextOptions = {
|
||||||
runtime: Runtime;
|
container: ContainerInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createContext = async ({ runtime }: ContextOptions) => {
|
const createContext = async ({ container }: ContextOptions) => {
|
||||||
return async ({ req }: CreateFastifyContextOptions) => {
|
return async ({ req }: CreateFastifyContextOptions) => {
|
||||||
|
const runtime = container.get(Runtime);
|
||||||
const { authorization } = req.headers;
|
const { authorization } = req.headers;
|
||||||
const { auth } = runtime;
|
const { auth } = runtime;
|
||||||
if (!authorization) {
|
if (!authorization) {
|
||||||
@@ -18,6 +20,7 @@ const createContext = async ({ runtime }: ContextOptions) => {
|
|||||||
await auth.validateToken(token);
|
await auth.validateToken(token);
|
||||||
return {
|
return {
|
||||||
runtime,
|
runtime,
|
||||||
|
container,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
import { Config } from '../config/config.js';
|
import { Config } from '../config/config.js';
|
||||||
import { Repos } from '../repos/repos.js';
|
import { Repos } from '../repos/repos.js';
|
||||||
import { RunnerInstance } from './runner.instance.js';
|
import { RunnerInstance } from './runner.instance.js';
|
||||||
@@ -7,13 +8,17 @@ type RunnerOptions = {
|
|||||||
config: Config;
|
config: Config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class Runner {
|
class Runner {
|
||||||
#options: RunnerOptions;
|
#options: RunnerOptions;
|
||||||
#instances: Map<string, RunnerInstance> = new Map();
|
#instances: Map<string, RunnerInstance> = new Map();
|
||||||
|
|
||||||
constructor(options: RunnerOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
const { repos } = options;
|
repos: container.get(Repos),
|
||||||
|
config: container.get(Config),
|
||||||
|
};
|
||||||
|
const { repos } = this.#options;
|
||||||
repos.runs.on('created', this.#start);
|
repos.runs.on('created', this.#start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,21 @@
|
|||||||
import { resolve } from 'path';
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
import envPaths from 'env-paths';
|
|
||||||
import { Database } from '../database/database.js';
|
|
||||||
import { Repos } from '../repos/repos.js';
|
import { Repos } from '../repos/repos.js';
|
||||||
import { Runner } from '../runner/runner.js';
|
import { Runner } from '../runner/runner.js';
|
||||||
import { Config } from '../config/config.js';
|
|
||||||
import { Auth } from '../auth/auth.js';
|
import { Auth } from '../auth/auth.js';
|
||||||
import { Scheduler } from '../scheduler/scheduler.js';
|
import { Scheduler } from '../scheduler/scheduler.js';
|
||||||
|
|
||||||
const paths = envPaths('mini-loader-server');
|
@Service()
|
||||||
|
|
||||||
class Runtime {
|
class Runtime {
|
||||||
#repos: Repos;
|
#repos: Repos;
|
||||||
#runner: Runner;
|
#runner: Runner;
|
||||||
#auth: Auth;
|
#auth: Auth;
|
||||||
#scheduler: Scheduler;
|
#scheduler: Scheduler;
|
||||||
|
|
||||||
constructor(options: Config) {
|
constructor(container: ContainerInstance) {
|
||||||
const database = new Database(options.database);
|
this.#repos = container.get(Repos);
|
||||||
this.#repos = new Repos({ database, config: options });
|
this.#runner = container.get(Runner);
|
||||||
this.#runner = new Runner({ repos: this.#repos, config: options });
|
this.#auth = container.get(Auth);
|
||||||
this.#auth = new Auth({ config: options });
|
this.#scheduler = container.get(Scheduler);
|
||||||
this.#scheduler = new Scheduler({ runs: this.#repos.runs, schedules: this.#repos.schedules });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get repos() {
|
public get repos() {
|
||||||
@@ -38,24 +33,6 @@ class Runtime {
|
|||||||
public get scheduler() {
|
public get scheduler() {
|
||||||
return this.#scheduler;
|
return this.#scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static create = async () => {
|
|
||||||
const runtime = new Runtime({
|
|
||||||
database: {
|
|
||||||
client: 'sqlite3',
|
|
||||||
connection: {
|
|
||||||
filename: resolve(paths.data, 'database.sqlite'),
|
|
||||||
},
|
|
||||||
useNullAsDefault: true,
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
data: process.env.DATA_DIR || resolve(paths.data, 'data', 'files'),
|
|
||||||
cache: process.env.CACHE_DIR || resolve(paths.cache, 'data', 'cache'),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return runtime;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Runtime };
|
export { Runtime };
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { CronJob } from 'cron';
|
import { CronJob } from 'cron';
|
||||||
import { ScheduleRepo } from '../repos/schedules/schedules.js';
|
import { ScheduleRepo } from '../repos/schedules/schedules.js';
|
||||||
import { RunRepo } from '../repos/runs/runs.js';
|
import { RunRepo } from '../repos/runs/runs.js';
|
||||||
|
import { ContainerInstance, Service } from 'typedi';
|
||||||
|
|
||||||
type SchedulerOptions = {
|
type SchedulerOptions = {
|
||||||
runs: RunRepo;
|
runs: RunRepo;
|
||||||
@@ -13,12 +14,16 @@ type RunningSchedule = {
|
|||||||
stop: () => Promise<void>;
|
stop: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
class Scheduler {
|
class Scheduler {
|
||||||
#running: RunningSchedule[] = [];
|
#running: RunningSchedule[] = [];
|
||||||
#options: SchedulerOptions;
|
#options: SchedulerOptions;
|
||||||
|
|
||||||
constructor(options: SchedulerOptions) {
|
constructor(container: ContainerInstance) {
|
||||||
this.#options = options;
|
this.#options = {
|
||||||
|
runs: container.get(RunRepo),
|
||||||
|
schedules: container.get(ScheduleRepo),
|
||||||
|
};
|
||||||
const { schedules } = this.#options;
|
const { schedules } = this.#options;
|
||||||
schedules.on('added', this.#add);
|
schedules.on('added', this.#add);
|
||||||
schedules.on('removed', this.#remove);
|
schedules.on('removed', this.#remove);
|
||||||
|
|||||||
@@ -2,17 +2,12 @@ import { fastifyTRPCPlugin, FastifyTRPCPluginOptions } from '@trpc/server/adapte
|
|||||||
import fastify from 'fastify';
|
import fastify from 'fastify';
|
||||||
import { RootRouter, rootRouter } from '../router/router.js';
|
import { RootRouter, rootRouter } from '../router/router.js';
|
||||||
import { createContext } from '../router/router.utils.js';
|
import { createContext } from '../router/router.utils.js';
|
||||||
import { Runtime } from '../runtime/runtime.js';
|
|
||||||
import { gateway } from '../gateway/gateway.js';
|
import { gateway } from '../gateway/gateway.js';
|
||||||
import { createRequire } from 'module';
|
import { ContainerInstance } from 'typedi';
|
||||||
import { readFile } from 'fs/promises';
|
import { Runtime } from '../runtime/runtime.js';
|
||||||
|
|
||||||
const require = createRequire(import.meta.url);
|
|
||||||
|
|
||||||
const createServer = async (runtime: Runtime) => {
|
|
||||||
const pkgLocation = require.resolve('#pkg');
|
|
||||||
const pkg = JSON.parse(await readFile(pkgLocation, 'utf-8'));
|
|
||||||
|
|
||||||
|
const createServer = async (container: ContainerInstance) => {
|
||||||
|
const runtime = container.get(Runtime);
|
||||||
const server = fastify({
|
const server = fastify({
|
||||||
maxParamLength: 10000,
|
maxParamLength: 10000,
|
||||||
bodyLimit: 30 * 1024 * 1024,
|
bodyLimit: 30 * 1024 * 1024,
|
||||||
@@ -31,14 +26,14 @@ const createServer = async (runtime: Runtime) => {
|
|||||||
authorized = true;
|
authorized = true;
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
return { authorized, status: 'ok', version: pkg.version };
|
return { authorized, status: 'ok' };
|
||||||
});
|
});
|
||||||
|
|
||||||
server.register(fastifyTRPCPlugin, {
|
server.register(fastifyTRPCPlugin, {
|
||||||
prefix: '/trpc',
|
prefix: '/trpc',
|
||||||
trpcOptions: {
|
trpcOptions: {
|
||||||
router: rootRouter,
|
router: rootRouter,
|
||||||
createContext: await createContext({ runtime }),
|
createContext: await createContext({ container }),
|
||||||
onError({ error }) {
|
onError({ error }) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
},
|
},
|
||||||
|
|||||||
4
packages/tests/.gitignore
vendored
Normal file
4
packages/tests/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/dist/
|
||||||
|
/node_modules/
|
||||||
|
/coverage/
|
||||||
|
/e2e-tmp/
|
||||||
1
packages/tests/README.md
Normal file
1
packages/tests/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[Go to documentation](https://github.com/morten-olsen/mini-loader)
|
||||||
1
packages/tests/assets/simple.js
Normal file
1
packages/tests/assets/simple.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
console.log('simple');
|
||||||
40
packages/tests/package.json
Normal file
40
packages/tests/package.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "@morten-olsen/mini-loader-tests",
|
||||||
|
"private": "true",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"main": "./dist/esm/index.js",
|
||||||
|
"types": "./dist/esm/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"files": [
|
||||||
|
"./dist"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/esm/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@morten-olsen/mini-loader-configs": "workspace:^",
|
||||||
|
"@types/node": "^20.10.8",
|
||||||
|
"fastify": "^4.25.2",
|
||||||
|
"sqlite3": "^5.1.7",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.0.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@morten-olsen/mini-loader": "workspace:^",
|
||||||
|
"@morten-olsen/mini-loader-cli": "workspace:^",
|
||||||
|
"@morten-olsen/mini-loader-server": "workspace:^",
|
||||||
|
"get-port": "^7.0.0",
|
||||||
|
"memfs": "^4.6.0",
|
||||||
|
"nanoid": "^5.0.4",
|
||||||
|
"typedi": "^0.10.0",
|
||||||
|
"vitest": "^1.2.0"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/morten-olsen/mini-loader",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/morten-olsen/mini-loader"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
packages/tests/src/quick-start.test.ts
Normal file
56
packages/tests/src/quick-start.test.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { describe, it, beforeAll, afterAll, vi } from 'vitest';
|
||||||
|
import { TestEnv, createEnv } from './utils/env.js';
|
||||||
|
import { writeFile } from 'fs/promises';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
describe('Quick start', () => {
|
||||||
|
let env: TestEnv;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
vi.mock('nanoid', () => {
|
||||||
|
let currentId = 0;
|
||||||
|
return {
|
||||||
|
nanoid: () => `id-${currentId++}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
env = await createEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
await env.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to start the server', async () => {
|
||||||
|
const { server, port } = env;
|
||||||
|
await server.listen({ port });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to login', async () => {
|
||||||
|
const { clientRun, port, token } = env;
|
||||||
|
await clientRun(['auth', 'login', '--host', `http://localhost:${port}`, '--token', token]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to push a load', async () => {
|
||||||
|
const { clientRun, location } = env;
|
||||||
|
const scriptPath = resolve(location, 'simple.js');
|
||||||
|
await writeFile(scriptPath, 'console.log("hello world")', 'utf-8');
|
||||||
|
await clientRun(['loads', 'push', scriptPath, '-i', 'demo', '-r']);
|
||||||
|
|
||||||
|
await sleep(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get the run', async () => {
|
||||||
|
const { clientRun } = env;
|
||||||
|
await clientRun(['runs', 'ls', 'demo']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to get logs', async () => {
|
||||||
|
const { clientRun, clientOutput } = env;
|
||||||
|
await clientRun(['logs', 'ls', '-l', 'demo']);
|
||||||
|
|
||||||
|
console.log(JSON.stringify(clientOutput, null, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
136
packages/tests/src/utils/env.ts
Normal file
136
packages/tests/src/utils/env.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { Paths, Terminal, createClientCli } from '@morten-olsen/mini-loader-cli';
|
||||||
|
import { Runtime, createServer, Config } from '@morten-olsen/mini-loader-server';
|
||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
import { ContainerInstance } from 'typedi';
|
||||||
|
import { join, resolve } from 'path';
|
||||||
|
import getPort from 'get-port';
|
||||||
|
import { rm } from 'fs/promises';
|
||||||
|
|
||||||
|
const cacheLocation = './e2e-tmp';
|
||||||
|
|
||||||
|
const getUniqueIdentifier = () => {
|
||||||
|
return Math.random().toString(36).substr(2, 9);
|
||||||
|
};
|
||||||
|
|
||||||
|
type CliActionOutput =
|
||||||
|
| {
|
||||||
|
type: 'stdout';
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'stderr';
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'step';
|
||||||
|
message: string;
|
||||||
|
state: 'pending' | 'success' | 'error';
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'output';
|
||||||
|
data: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createEnv = async () => {
|
||||||
|
const location = resolve(cacheLocation, getUniqueIdentifier());
|
||||||
|
const port = await getPort();
|
||||||
|
|
||||||
|
let clientOutput: CliActionOutput[] = [];
|
||||||
|
const serverContainer = new ContainerInstance(getUniqueIdentifier());
|
||||||
|
|
||||||
|
const clientRun = async (params: string[]) => {
|
||||||
|
const clientContainer = new ContainerInstance(getUniqueIdentifier());
|
||||||
|
clientContainer.set(Paths, {
|
||||||
|
config: join(location, 'client', 'config'),
|
||||||
|
cache: join(location, 'client', 'cache'),
|
||||||
|
});
|
||||||
|
clientContainer.set(Terminal, {
|
||||||
|
log: async (message: any) => {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'stdout',
|
||||||
|
data: message,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
output: async (data: any) => {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'output',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
step: async (message: any, action: any) => {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'step',
|
||||||
|
message,
|
||||||
|
state: 'pending',
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const result = await action();
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'step',
|
||||||
|
message,
|
||||||
|
state: 'success',
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'step',
|
||||||
|
message,
|
||||||
|
state: 'error',
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const client = createClientCli(clientContainer);
|
||||||
|
client.configureOutput({
|
||||||
|
writeOut: (data) => {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'stdout',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
writeErr: (data) => {
|
||||||
|
clientOutput.push({
|
||||||
|
type: 'stderr',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
throw new Error(`Error in client ${data}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.parseAsync(['', '', ...params]);
|
||||||
|
};
|
||||||
|
|
||||||
|
serverContainer.set(Config, {
|
||||||
|
database: {
|
||||||
|
client: 'sqlite3',
|
||||||
|
connection: {
|
||||||
|
filename: join(location, 'server', 'data', 'db.sqlite'),
|
||||||
|
},
|
||||||
|
useNullAsDefault: true,
|
||||||
|
},
|
||||||
|
files: {
|
||||||
|
data: join(location, 'server', 'data'),
|
||||||
|
cache: join(location, 'server', 'cache'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const server = await createServer(serverContainer);
|
||||||
|
const runtime = serverContainer.get(Runtime);
|
||||||
|
const token = await runtime.auth.createToken({
|
||||||
|
policy: {
|
||||||
|
'*:*': ['*'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cleanup = async () => {
|
||||||
|
await server.close();
|
||||||
|
await rm(location, { recursive: true, force: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
return { clientRun, clientOutput, runtime, server, token, location, port, cleanup };
|
||||||
|
};
|
||||||
|
|
||||||
|
type TestEnv = Awaited<ReturnType<typeof createEnv>>;
|
||||||
|
|
||||||
|
export type { TestEnv, FastifyInstance };
|
||||||
|
export { createEnv };
|
||||||
9
packages/tests/tsconfig.json
Normal file
9
packages/tests/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@morten-olsen/mini-loader-configs/tsconfig.esm.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist/esm",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
}
|
||||||
22
packages/tests/vitest.config.ts
Normal file
22
packages/tests/vitest.config.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
|
||||||
|
const server = require.resolve('../server/src/index.ts');
|
||||||
|
const cli = require.resolve('../cli/src/index.ts');
|
||||||
|
const runner = require.resolve('../runner/src/index.ts');
|
||||||
|
const sdk = require.resolve('../mini-loader/src/index.ts');
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
alias: {
|
||||||
|
'@morten-olsen/mini-loader-server': server,
|
||||||
|
'@morten-olsen/mini-loader-cli': cli,
|
||||||
|
'@morten-olsen/mini-loader-runner': runner,
|
||||||
|
'@morten-olsen/mini-loader': sdk,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
998
pnpm-lock.yaml
generated
998
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
14
vitest.config.ts
Normal file
14
vitest.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
coverage: {
|
||||||
|
reporter: ['text', 'json-summary', 'json', 'html'],
|
||||||
|
reportOnFailure: true,
|
||||||
|
include: ['packages/*/src/**/*.ts'],
|
||||||
|
exclude: ['packages/examples/**/*', 'packages/tests/**/*'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
1
vitest.workspace.ts
Normal file
1
vitest.workspace.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default ['packages/*'];
|
||||||
Reference in New Issue
Block a user