From ea2a3f39337f5923484e99a711c68937844f94f7 Mon Sep 17 00:00:00 2001 From: JT Smith Date: Tue, 9 Apr 2024 23:11:44 -0500 Subject: [PATCH] Implemented: cli improvements #83 --- docs/change-log.md | 1 + ving.mjs | 3 +- ving/cli/alert.mjs | 41 ++++++++++------ ving/cli/cache.mjs | 44 ++++++++++------- ving/cli/drizzle.mjs | 103 +++++++++++++++++++++------------------- ving/cli/email.mjs | 30 +++++++----- ving/cli/jobs.mjs | 42 +++++++++------- ving/cli/messagebus.mjs | 34 +++++++------ ving/cli/record.mjs | 11 +++-- ving/cli/schema.mjs | 20 +++++--- ving/cli/token.mjs | 20 +++++--- ving/cli/user.mjs | 86 ++++++++++++++++++--------------- 12 files changed, 257 insertions(+), 178 deletions(-) diff --git a/docs/change-log.md b/docs/change-log.md index 166b48d9..55f2a1e4 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -13,6 +13,7 @@ outline: deep * Fixed: isRoleOrDie not documented correctly #77 * Implemented: document Nuxt stuff #76 * Implemented: add --bare as an option in generators that gets rid of the boiler plate #75 +* Implemented: cli improvements #83 ## 2024-04-08 * Breaking change: Refactored VingRecord isOwner(), canEdit(), and propsOptions() to be async. diff --git a/ving.mjs b/ving.mjs index e14a9273..0aecd3a7 100755 --- a/ving.mjs +++ b/ving.mjs @@ -6,6 +6,7 @@ const main = defineCommand({ name: "ving.mjs", description: "ving CLI", }, + cleanup: ving.close, subCommands: { alert: () => import('#ving/cli/alert.mjs').then((r) => r.default), cache: () => import('#ving/cli/cache.mjs').then((r) => r.default), @@ -20,4 +21,4 @@ const main = defineCommand({ }, }); -runMain(main); +runMain(main,); diff --git a/ving/cli/alert.mjs b/ving/cli/alert.mjs index a41f9050..c35e57da 100644 --- a/ving/cli/alert.mjs +++ b/ving/cli/alert.mjs @@ -1,11 +1,12 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "System Wide Alert", + name: "alert", description: "Manage system wide alerts from the command line", }, + cleanup: ving.close, args: { message: { type: "string", @@ -40,20 +41,30 @@ export default defineCommand({ alias: "g", }, }, - async run({ args }) { - if (args.message) { - await ving.useCache().set('system-wide-alert', { - message: args.message, - severity: args.severity, - ttl: 60 * 60 * 1000 * args.ttl, - }, 60 * 60 * 1000 * args.ttl); + async run({ args, cmd }) { + try { + if (args.message) { + const ttl = 60 * 60 * 1000 * args.ttl; + await ving.useCache().set('system-wide-alert', { + message: args.message, + severity: args.severity, + ttl, + }, ttl); + ving.log('cli').info(`Message set: "${args.message}"`); + } + else if (args.get) { + ving.log('cli').info(await ving.useCache().get('system-wide-alert')); + } + else if (args.delete) { + await ving.useCache().delete('system-wide-alert'); + ving.log('cli').info('Message deleted.'); + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } } - if (args.get) { - ving.log('cli').info(await ving.useCache().get('system-wide-alert')); + catch (e) { + ving.log('cli').error(e.message); } - if (args.delete) { - await ving.useCache().delete('system-wide-alert'); - } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/cache.mjs b/ving/cli/cache.mjs index d4caa68c..4c6a5640 100644 --- a/ving/cli/cache.mjs +++ b/ving/cli/cache.mjs @@ -1,11 +1,12 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Cache", + name: "cache", description: "CRUD cache entries", }, + cleanup: ving.close, args: { clear: { type: "boolean", @@ -44,23 +45,30 @@ export default defineCommand({ alias: "t", }, }, - async run({ args }) { - if (args.clear) { - await ving.useCache().clear(); - ving.log('cli').info('Cache cleared'); + async run({ args, cmd }) { + try { + if (args.clear) { + await ving.useCache().clear(); + ving.log('cli').info('Cache cleared'); + } + else if (args.get) { + const value = await ving.useCache().get(args.get); + ving.log('cli').info(`Value of ${args.get} is ${JSON.stringify(value)}`); + } + else if (args.delete) { + await ving.useCache().delete(args.delete); + ving.log('cli').info(`${args.delete} deleted`); + } + else if (args.set) { + await ving.useCache().set(args.set, args.value, Number(args.ttl)); + ving.log('cli').info(`${args.set} set to ${args.value} for ${args.ttl}ms`); + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } } - if (args.get) { - const value = await ving.useCache().get(args.get); - ving.log('cli').info(`Value of ${args.get} is ${JSON.stringify(value)}`); + catch (e) { + ving.log('cli').error(e.message); } - if (args.delete) { - await ving.useCache().delete(args.delete); - ving.log('cli').info(`${args.delete} deleted`); - } - if (args.set) { - await ving.useCache().set(args.set, args.value, Number(args.ttl)); - ving.log('cli').info(`${args.set} set to ${args.value} for ${args.ttl}ms`); - } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/drizzle.mjs b/ving/cli/drizzle.mjs index 37239c36..10629a97 100644 --- a/ving/cli/drizzle.mjs +++ b/ving/cli/drizzle.mjs @@ -1,4 +1,4 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { exec } from "child_process"; import { runMigrations } from '#ving/drizzle/migrate.mjs'; import { makeTableFile } from '#ving/generator/drizzletable.mjs'; @@ -9,9 +9,10 @@ import fs from "node:fs"; export default defineCommand({ meta: { - name: "Drizzle ORM", - description: "Manage Drizzle migrations and code generation", + name: "drizzle", + description: "Manage Drizzle ORM migrations and code generation", }, + cleanup: ving.close, args: { tables: { type: "boolean", @@ -34,57 +35,63 @@ export default defineCommand({ alias: "s", }, }, - async run({ args }) { - if (args.tables) { - for (const schema of vingSchemas) { - await makeTableFile({ schema }); - } - } - else if (args.status) { - const migrationFolderTo = './ving/drizzle/migrations'; - const migrations = []; - const journalAsString = fs.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString(); - const journal = JSON.parse(journalAsString); - for (const journalEntry of journal.entries) { - const query = fs.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString(); - migrations.push({ - tag: journalEntry.tag, - hash: crypto.createHash("sha256").update(query).digest("hex") - }); + async run({ args, cmd }) { + try { + if (args.tables) { + for (const schema of vingSchemas) { + await makeTableFile({ schema }); + } } - ving.log('cli').info(`Last Migration Available: ${migrations[migrations.length - 1].tag} [${migrations[migrations.length - 1].hash}]`); - try { - const [rows] = await ving.useDB().session.client.pool.promise().query('SELECT * from __drizzle_migrations order by created_at desc limit 1'); - const applied = migrations.find(m => m.hash == rows[0].hash); - if (applied) { - ving.log('cli').info(`Last Migration Applied: ${applied.tag} [${applied.hash}]`); + else if (args.status) { + const migrationFolderTo = './ving/drizzle/migrations'; + const migrations = []; + const journalAsString = fs.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString(); + const journal = JSON.parse(journalAsString); + for (const journalEntry of journal.entries) { + const query = fs.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString(); + migrations.push({ + tag: journalEntry.tag, + hash: crypto.createHash("sha256").update(query).digest("hex") + }); } - else { - ving.log('cli').error(`Last Migration Applied: None`); + ving.log('cli').info(`Last Migration Available: ${migrations[migrations.length - 1].tag} [${migrations[migrations.length - 1].hash}]`); + try { + const [rows] = await ving.useDB().session.client.pool.promise().query('SELECT * from __drizzle_migrations order by created_at desc limit 1'); + const applied = migrations.find(m => m.hash == rows[0].hash); + if (applied) { + ving.log('cli').info(`Last Migration Applied: ${applied.tag} [${applied.hash}]`); + } + else { + ving.log('cli').error(`Last Migration Applied: None`); + } } + catch { + ving.log('cli').error(`Last Migration Applied: Database not initialized`); + } + } - catch { - ving.log('cli').error(`Last Migration Applied: Database not initialized`); + else if (args.up) { + runMigrations(); + } + else if (args.prepare) { + exec("npx drizzle-kit generate:mysql --out ./ving/drizzle/migrations --schema ving/drizzle/schema", (error, stdout, stderr) => { + if (error) { + ving.log('cli').error(error.message); + return; + } + if (stderr) { + ving.log('cli').error(stderr); + return; + } + ving.log('cli').info(stdout); + }); + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); } - } - else if (args.up) { - runMigrations(); + catch (e) { + ving.log('cli').error(e.message); } - else if (args.prepare) { - exec("npx drizzle-kit generate:mysql --out ./ving/drizzle/migrations --schema ving/drizzle/schema", (error, stdout, stderr) => { - if (error) { - ving.log('cli').error(error.message); - return; - } - if (stderr) { - ving.log('cli').error(stderr); - return; - } - ving.log('cli').info(stdout); - }); - } - await ving.close(); - }, }); \ No newline at end of file diff --git a/ving/cli/email.mjs b/ving/cli/email.mjs index c62c44bb..2199dbbd 100644 --- a/ving/cli/email.mjs +++ b/ving/cli/email.mjs @@ -1,12 +1,13 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { generateTemplates } from '#ving/generator/emailtemplate.mjs'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Email", + name: "email", description: "Useful for testing email", }, + cleanup: ving.close, args: { to: { type: "string", @@ -32,16 +33,23 @@ export default defineCommand({ alias: 'c', }, }, - async run({ args }) { - if (args.to) { - ving.sendMail(args.template, { - options: { to: args.to, from: 'info@thegamecrafter.com', preview: args.preview }, - }); - ving.log('cli').info(`Email sent to ${args.to}`); + async run({ args, cmd }) { + try { + if (args.to) { + ving.sendMail(args.template, { + options: { to: args.to, from: 'info@thegamecrafter.com', preview: args.preview }, + }); + ving.log('cli').info(`Email sent to ${args.to}`); + } + else if (args.create) { + await generateTemplates({ name: args.create }); + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } } - else if (args.create) { - await generateTemplates({ name: args.create }); + catch (e) { + ving.log('cli').error(e.message); } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/jobs.mjs b/ving/cli/jobs.mjs index 83a49ccb..06d1aecc 100644 --- a/ving/cli/jobs.mjs +++ b/ving/cli/jobs.mjs @@ -1,10 +1,10 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { VingJobWorker } from '#ving/jobs/worker.mjs'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Jobs", + name: "jobs", description: "Manage background jobs", }, args: { @@ -40,22 +40,30 @@ export default defineCommand({ alias: 'd', }, }, - async run({ args }) { - if (args.addJob) { - await ving.addJob(args.addJob, JSON.parse(args.jobData), { - queueName: args.queueName, - }); - if (!args.worker) - ving.close(); - } - if (args.worker) { - const worker = new VingJobWorker(args.queueName); - await worker.start(); - if (args.ttl > 0) { - setTimeout(async () => { - await worker.end(); - }, 1000 * args.ttl); + async run({ args, cmd }) { + try { + if (args.worker) { + const worker = new VingJobWorker(args.queueName); + await worker.start(); + if (args.ttl > 0) { + setTimeout(async () => { + await worker.end(); + }, 1000 * args.ttl); + } + } + else if (args.addJob) { + await ving.addJob(args.addJob, JSON.parse(args.jobData), { + queueName: args.queueName, + }); + if (!args.worker) + ving.close(); } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } + } + catch (e) { + ving.log('cli').error(e.message); } }, }); \ No newline at end of file diff --git a/ving/cli/messagebus.mjs b/ving/cli/messagebus.mjs index 4dd3000b..42d0b035 100644 --- a/ving/cli/messagebus.mjs +++ b/ving/cli/messagebus.mjs @@ -1,19 +1,21 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { publishUserToast } from '#ving/messagebus.mjs'; import { eq } from '#ving/drizzle/orm.mjs'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Message Bus", + name: "messagebus", description: "Useful for testing the message bus", }, + cleanup: ving.close, args: { user: { type: "string", description: "Who to send it to", valueHint: "username", alias: "u", + required: true, }, message: { type: "string", @@ -28,22 +30,26 @@ export default defineCommand({ alias: "s", }, }, - async run({ args }) { - if (args.user) { - const users = await ving.useKind('User'); - const user = await users.findOne(eq(users.table.username, args.user)); - if (user) { - const severity = args.severity == 'info' || 'danger' || 'success' || 'warning' ? args.type : 'info'; - const pub = await publishUserToast(user.get('id'), args.message, severity); - await pub.quit(); + async run({ args, cmd }) { + try { + if (args.user) { + const users = await ving.useKind('User'); + const user = await users.findOne(eq(users.table.username, args.user)); + if (user) { + const severity = args.severity == 'info' || 'danger' || 'success' || 'warning' ? args.type : 'info'; + const pub = await publishUserToast(user.get('id'), args.message, severity); + await pub.quit(); + } + else { + ving.log('cli').error('user not found'); + } } else { - ving.log('cli').error('user not found'); + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); } } - else { - ving.log('cli').error('user is required'); + catch (e) { + ving.log('cli').error(e.message); } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/record.mjs b/ving/cli/record.mjs index dc343c14..20142cc2 100644 --- a/ving/cli/record.mjs +++ b/ving/cli/record.mjs @@ -1,4 +1,4 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { generateRecord } from '#ving/generator/vingrecord.mjs'; import { generateRest } from '#ving/generator/nuxtapis.mjs'; import { generateWeb } from '#ving/generator/nuxtpages.mjs'; @@ -7,9 +7,10 @@ import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Ving Record", + name: "record", description: "Ving Record code generation", }, + cleanup: ving.close, args: { new: { type: "string", @@ -40,7 +41,7 @@ export default defineCommand({ alias: "b", }, }, - async run({ args }) { + async run({ args, cmd }) { try { if (args.new) { await generateRecord({ name: args.new, bare: args.bare, schema: findVingSchema(args.new, 'kind') }); @@ -56,10 +57,12 @@ export default defineCommand({ await generateRest({ name: schema.kind, schema, skipExisting: true }); } } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } } catch (e) { ving.log('cli').error(e.message); } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/schema.mjs b/ving/cli/schema.mjs index 7bee9486..5b6b066b 100644 --- a/ving/cli/schema.mjs +++ b/ving/cli/schema.mjs @@ -1,12 +1,13 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { generateSchema } from '#ving/generator/vingschema.mjs'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Ving Schema", + name: "schema", description: "Ving Schema code generation", }, + cleanup: ving.close, args: { new: { type: "string", @@ -15,10 +16,17 @@ export default defineCommand({ alias: "n", }, }, - async run({ args }) { - if (args.new) { - await generateSchema({ name: args.new }); + async run({ args, cmd }) { + try { + if (args.new) { + await generateSchema({ name: args.new }); + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } + } + catch (e) { + ving.log('cli').error(e.message); } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/token.mjs b/ving/cli/token.mjs index 74a6b788..3d177ab6 100644 --- a/ving/cli/token.mjs +++ b/ving/cli/token.mjs @@ -1,12 +1,13 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import crypto from 'crypto'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "Token Generator", + name: "token", description: "Sometimes you need to generate a random token", }, + cleanup: ving.close, args: { new: { type: "boolean", @@ -20,10 +21,17 @@ export default defineCommand({ alias: "b", }, }, - async run({ args }) { - if (args.new) { - console.log(crypto.randomBytes(Number(args.bytes)).toString('hex')) + async run({ args, cmd }) { + try { + if (args.new) { + console.log(crypto.randomBytes(Number(args.bytes)).toString('hex')) + } + else { + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); + } + } + catch (e) { + ving.log('cli').error(e.message); } - await ving.close(); }, }); \ No newline at end of file diff --git a/ving/cli/user.mjs b/ving/cli/user.mjs index e53d352b..ad6b1247 100644 --- a/ving/cli/user.mjs +++ b/ving/cli/user.mjs @@ -1,12 +1,13 @@ -import { defineCommand } from "citty"; +import { defineCommand, showUsage } from "citty"; import { like, or, eq } from '#ving/drizzle/orm.mjs'; import ving from '#ving/index.mjs'; export default defineCommand({ meta: { - name: "User Administration", + name: "user", description: "Basic CRUD for users, use web UI for more", }, + cleanup: ving.close, args: { list: { type: "boolean", @@ -52,48 +53,57 @@ export default defineCommand({ description: "Should user NOT be admin", }, }, - async run({ args }) { - const users = await ving.useKind('User'); - if (args.list) { - formatList(await users.findMany()); - } - else if (args.admins) { - formatList(await users.findMany(eq(users.table.admin, true))); - } - else if (args.search) { - formatList(await users.findMany(or(like(users.table.username, `%${args.search}%`), like(users.table.realName, `%${args.search}%`), like(users.table.email, `%${args.search}%`)))); - } - else if (args.add) { - const user = users.mint({ - username: args.add, - realName: args.add, - email: args.email, - admin: args.admin, - }); - await user.insert(); - await user.setPassword(args.password); - await user.update(); - formatList([user]); - } - else if (args.modify) { - const user = await users.findOne(eq(users.table.username, args.modify)); - if (user) { - if (args.email) - user.set('email', args.email); - if (args.password) - await user.setPassword(args.password); - if (args.admin) - user.set('admin', true); - if (args.notAdmin) - user.set('admin', false); + async run({ args, cmd }) { + try { + const users = await ving.useKind('User'); + if (args.list) { + formatList(await users.findMany()); + } + else if (args.admins) { + formatList(await users.findMany(eq(users.table.admin, true))); + } + else if (args.search) { + formatList(await users.findMany(or(like(users.table.username, `%${args.search}%`), like(users.table.realName, `%${args.search}%`), like(users.table.email, `%${args.search}%`)))); + } + else if (args.add) { + if (!args.email) + throw ving.ouch(441, 'Email address required'); + const user = users.mint({ + username: args.add, + realName: args.add, + email: args.email, + admin: args.admin, + }); + await user.insert(); + await user.setPassword(args.password); await user.update(); formatList([user]); } + else if (args.modify) { + const user = await users.findOne(eq(users.table.username, args.modify)); + if (user) { + if (args.email) + user.set('email', args.email); + if (args.password) + await user.setPassword(args.password); + if (args.admin) + user.set('admin', true); + if (args.notAdmin) + user.set('admin', false); + await user.update(); + formatList([user]); + } + else { + ving.log('cli').error(`Could not find user: ${args.modify}`); + } + } else { - ving.log('cli').error(`Could not find user: ${args.modify}`); + await showUsage(cmd, { meta: { name: 'ving.mjs' } }); } } - ving.close(); + catch (e) { + ving.log('cli').error(e.message); + } }, });