This commit is contained in:
Morten Olsen
2024-01-11 09:03:14 +01:00
commit 74a3d0f925
92 changed files with 7488 additions and 0 deletions

3
packages/cli/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/dist/
/node_modules/
/coverage/

4
packages/cli/bin/index.mjs Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env node
import 'source-map-support/register.js';
import '../dist/esm/index.js';

42
packages/cli/package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "@morten-olsen/mini-loader-cli",
"version": "1.0.0",
"main": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"bin": {
"mini-loader": "./bin/index.mjs"
},
"scripts": {
"build": "tsc --build"
},
"type": "module",
"files": [
"./dist"
],
"exports": {
".": {
"import": "./dist/esm/index.js"
}
},
"dependencies": {
"@rollup/plugin-auto-install": "^3.0.5",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-sucrase": "^5.0.2",
"@trpc/client": "^10.45.0",
"commander": "^11.1.0",
"ora": "^8.0.1",
"rollup": "^4.9.4",
"rollup-plugin-node-polyfills": "^0.2.1",
"source-map-support": "^0.5.21",
"superjson": "^2.2.1"
},
"devDependencies": {
"@morten-olsen/mini-loader-configs": "workspace:^",
"@morten-olsen/mini-loader-runner": "workspace:^",
"@morten-olsen/mini-loader-server": "workspace:^",
"typescript": "^5.3.3"
}
}

View File

@@ -0,0 +1,42 @@
import { rollup } from 'rollup';
import sucrase from '@rollup/plugin-sucrase';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import auto from '@rollup/plugin-auto-install';
import { resolve } from 'path';
const fix = <T extends { default: any }>(f: T) => f as T['default'];
type BundleOptions = {
entry: string;
autoInstall?: boolean;
};
const bundle = async ({ entry, autoInstall }: BundleOptions) => {
const entryFile = resolve(entry);
const codeBundler = await rollup({
plugins: [
fix(sucrase)({
transforms: ['typescript', 'jsx'],
}),
...[autoInstall ? fix(auto) : []],
nodeResolve({ extensions: ['.js', '.jsx', '.ts', '.tsx'] }),
fix(json)(),
fix(commonjs)({ include: /node_modules/ }),
],
input: entryFile,
});
const { output: codeOutput } = await codeBundler.generate({
format: 'cjs',
});
if (codeOutput.length > 1) {
throw new Error('Multiple outputs are not supported');
}
const [codeResult] = codeOutput;
const { code } = codeResult;
return code;
};
export { bundle };

View File

@@ -0,0 +1,20 @@
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import type { Runtime } from '@morten-olsen/mini-loader-server';
import type { RootRouter } from '@morten-olsen/mini-loader-server';
const createClient = () => {
return createTRPCProxyClient<RootRouter>({
transformer: superjson,
links: [
httpBatchLink({
url: 'http://localhost:4500',
}),
],
});
};
type Client = ReturnType<typeof createClient>;
export type { Client, Runtime };
export { createClient };

View File

@@ -0,0 +1,37 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const list = new Command('list');
const toInt = (value?: string) => {
if (!value) {
return undefined;
}
return parseInt(value, 10);
};
list
.alias('ls')
.description('List logs')
.option('-r, --run-id <runId>', 'Run ID')
.option('-l, --load-id <loadId>', 'Load ID')
.option('-o, --offset <offset>', 'Offset')
.option('-a, --limit <limit>', 'Limit', '1000')
.action(async () => {
const { runId, loadId, offset, limit } = list.opts();
const client = await step('Connecting to server', async () => {
return createClient();
});
const artifacts = await step('Getting artifacts', async () => {
return await client.artifacts.find.query({
runId,
loadId,
offset: toInt(offset),
limit: toInt(limit),
});
});
console.table(artifacts);
});
export { list };

View File

@@ -0,0 +1,7 @@
import { Command } from 'commander';
import { list } from './artifacts.list.js';
const artifacts = new Command('artifacts');
artifacts.addCommand(list);
export { artifacts };

View File

@@ -0,0 +1,20 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const list = new Command('list');
list
.alias('ls')
.description('List loads')
.action(async () => {
const client = await step('Connecting to server', async () => {
return createClient();
});
const loads = step('Getting data', async () => {
await client.loads.find.query({});
});
console.table(loads);
});
export { list };

View File

@@ -0,0 +1,39 @@
import { Command } from 'commander';
import { resolve } from 'path';
import { createClient } from '../../client/client.js';
import { bundle } from '../../bundler/bundler.js';
import { step } from '../../utils/step.js';
const push = new Command('push');
push
.argument('script')
.option('-i, --id <id>', 'Load id')
.option('-n, --name <name>')
.option('-r, --run', 'Run the load')
.option('-ai, --auto-install', 'Auto install dependencies', false)
.action(async (script) => {
const opts = push.opts();
const location = resolve(script);
const client = await step('Connecting to server', async () => {
return createClient();
});
const code = await step('Bundling', async () => {
return await bundle({ entry: location, autoInstall: opts.autoInstall });
});
const id = await step('Creating load', async () => {
return await client.loads.set.mutate({
id: opts.id,
name: opts.name,
script: code,
});
});
console.log('created load with id', id);
if (opts.run) {
await step('Creating run', async () => {
await client.runs.create.mutate({ loadId: id });
});
}
});
export { push };

View File

@@ -0,0 +1,10 @@
import { Command } from 'commander';
import { push } from './loads.push.js';
import { list } from './loads.list.js';
const loads = new Command('loads');
loads.addCommand(push);
loads.addCommand(list);
export { loads };

View File

@@ -0,0 +1,35 @@
import { Command } from 'commander';
import { resolve } from 'path';
import { run as runLoad } from '@morten-olsen/mini-loader-runner';
import { bundle } from '../../bundler/bundler.js';
import { step } from '../../utils/step.js';
const run = new Command('run');
run
.option('-ai, --auto-install', 'Auto install dependencies', false)
.argument('script')
.action(async (script) => {
const location = resolve(script);
const { autoInstall } = run.opts();
const code = await step('Bundling', async () => {
return await bundle({ entry: location, autoInstall });
});
const { promise, emitter } = await runLoad({
script: code,
});
emitter.addListener('message', (message) => {
switch (message.type) {
case 'log':
console.log(message.payload);
break;
case 'artifact:create':
console.log('artifact:create', message.payload.name);
break;
}
});
await promise;
});
export { run };

View File

@@ -0,0 +1,8 @@
import { Command } from 'commander';
import { run } from './local.run.js';
const local = new Command('local');
local.addCommand(run);
export { local };

View File

@@ -0,0 +1,41 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const list = new Command('list');
const toInt = (value?: string) => {
if (!value) {
return undefined;
}
return parseInt(value, 10);
};
list
.alias('ls')
.description('List logs')
.option('-r, --run-id <runId>', 'Run ID')
.option('-l, --load-id <loadId>', 'Load ID')
.option('--severities <severities...>', 'Severities')
.option('-o, --offset <offset>', 'Offset')
.option('-a, --limit <limit>', 'Limit', '1000')
.option('-s, --sort <order>', 'Sort', 'desc')
.action(async () => {
const { runId, loadId, severities, offset, limit, order } = list.opts();
const client = await step('Connecting to server', async () => {
return createClient();
});
const logs = await step('Getting logs', async () => {
return await client.logs.find.query({
runId,
loadId,
severities,
offset: toInt(offset),
limit: toInt(limit),
order,
});
});
console.table(logs.reverse());
});
export { list };

View File

@@ -0,0 +1,7 @@
import { Command } from 'commander';
import { list } from './logs.list.js';
const logs = new Command('logs');
logs.addCommand(list);
export { logs };

View File

@@ -0,0 +1,19 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const create = new Command('create');
create
.description('Create a new run')
.argument('load-id', 'Load ID')
.action(async (loadId) => {
const client = await step('Connecting to server', async () => {
return createClient();
});
await step('Creating run', async () => {
await client.runs.create.mutate({ loadId });
});
});
export { create };

View File

@@ -0,0 +1,21 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const list = new Command('create');
list
.alias('ls')
.description('Find a run')
.argument('[load-id]', 'Load ID')
.action(async (loadId) => {
const client = await step('Connecting to server', async () => {
return createClient();
});
const runs = await step('Getting runs', async () => {
return await client.runs.find.query({ loadId });
});
console.table(runs);
});
export { list };

View File

@@ -0,0 +1,8 @@
import { Command } from 'commander';
import { create } from './runs.create.js';
import { list } from './runs.list.js';
const runs = new Command('runs');
runs.description('Manage runs').addCommand(create).addCommand(list);
export { runs };

View File

@@ -0,0 +1,33 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const list = new Command('list');
const toInt = (value?: string) => {
if (!value) {
return undefined;
}
return parseInt(value, 10);
};
list
.alias('ls')
.description('List logs')
.option('-o, --offset <offset>', 'Offset')
.option('-a, --limit <limit>', 'Limit', '1000')
.action(async () => {
const { offset, limit } = list.opts();
const client = await step('Connecting to server', async () => {
return createClient();
});
const secrets = await step('Getting secrets', async () => {
return await client.secrets.find.query({
offset: toInt(offset),
limit: toInt(limit),
});
});
console.table(secrets);
});
export { list };

View File

@@ -0,0 +1,21 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const remove = new Command('remove');
remove
.alias('rm')
.argument('<id>')
.action(async (id) => {
const client = await step('Connecting to server', async () => {
return createClient();
});
await step('Removing', async () => {
await client.secrets.remove.mutate({
id,
});
});
});
export { remove };

View File

@@ -0,0 +1,22 @@
import { Command } from 'commander';
import { createClient } from '../../client/client.js';
import { step } from '../../utils/step.js';
const set = new Command('set');
set
.argument('<id>')
.argument('[value]')
.action(async (id, value) => {
const client = await step('Connecting to server', async () => {
return createClient();
});
await step('Setting secret', async () => {
await client.secrets.set.mutate({
id,
value,
});
});
});
export { set };

View File

@@ -0,0 +1,11 @@
import { Command } from 'commander';
import { list } from './secrets.list.js';
import { set } from './secrets.set.js';
import { remove } from './secrets.remove.js';
const secrets = new Command('secrets');
secrets.addCommand(list);
secrets.addCommand(set);
secrets.addCommand(remove);
export { secrets };

16
packages/cli/src/index.ts Normal file
View File

@@ -0,0 +1,16 @@
import { program } from 'commander';
import { loads } from './commands/loads/loads.js';
import { runs } from './commands/runs/runs.js';
import { logs } from './commands/logs/logs.js';
import { artifacts } from './commands/artifacts/artifacts.js';
import { secrets } from './commands/secrets/secrets.js';
import { local } from './commands/local/local.js';
program.addCommand(loads);
program.addCommand(runs);
program.addCommand(logs);
program.addCommand(artifacts);
program.addCommand(secrets);
program.addCommand(local);
await program.parseAsync();

View File

@@ -0,0 +1,15 @@
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();
spinner.succeed();
return result;
} catch (err) {
spinner.fail();
throw err;
}
};
export { step };

View File

@@ -0,0 +1,9 @@
{
"extends": "@morten-olsen/mini-loader-configs/tsconfig.esm.json",
"compilerOptions": {
"outDir": "dist/esm",
},
"include": [
"./src/**/*.ts"
],
}