Add ESLint to all packages and reorganize CLI

ESLint:
- Add @macalinao/eslint-config and eslint to all packages/apps
- Add lint scripts to all package.json files
- Create eslint.config.js for all apps
- Add lint task to turbo.json
- Add @macalinao/eslint-config and @types/bun to catalog

Biome:
- Exclude docs/ from biome checks

CLI Reorganization:
- Restructure CLI to use route maps with one command per file
- Move commands to routes/ directory structure
- Use func property instead of async loaders
- Route maps in _command.ts files for each directory

Environment:
- Use Bun.env instead of process.env for env vars
- Add DATABASE_URL and PORT to turbo.json globalEnv

Lint Fixes:
- Fix nullish coalescing operator usage
- Update deprecated Zod API (z.email() instead of .string().email())
- Fix import sorting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-09 12:01:41 +08:00
parent b687d9cd1d
commit 8f3f711af0
42 changed files with 3291 additions and 279 deletions

View File

@@ -1,121 +1,17 @@
#!/usr/bin/env bun
import {
buildApplication,
buildCommand,
buildRouteMap,
run,
} from "@stricli/core";
import type { LocalContext } from "../context.js";
import { buildApplication, run } from "@stricli/core";
import { rootRouteMap } from "../routes/_command.js";
// Lazy load command implementations
const bootstrap = buildCommand({
loader: async () => import("../commands/bootstrap.js"),
parameters: {},
docs: {
brief: "Create a superuser account",
},
});
const authLogin = buildCommand({
loader: async () => import("../commands/auth.js").then((m) => m.login),
parameters: {},
docs: { brief: "Login to RevIQ (stub)" },
});
const authLogout = buildCommand({
loader: async () => import("../commands/auth.js").then((m) => m.logout),
parameters: {},
docs: { brief: "Logout from RevIQ (stub)" },
});
const authStatus = buildCommand({
loader: async () => import("../commands/auth.js").then((m) => m.status),
parameters: {},
docs: { brief: "Check authentication status (stub)" },
});
const authCommand = buildRouteMap({
routes: {
login: authLogin,
logout: authLogout,
status: authStatus,
},
docs: {
brief: "Authentication commands",
},
});
const userCreate = buildCommand({
loader: async () => import("../commands/user.js").then((m) => m.create),
parameters: {},
docs: { brief: "Create a new user (stub)" },
});
const userConfirmEmail = buildCommand({
loader: async () => import("../commands/user.js").then((m) => m.confirmEmail),
parameters: {},
docs: { brief: "Confirm user email (stub)" },
});
const userCommand = buildRouteMap({
routes: {
create: userCreate,
"confirm-email": userConfirmEmail,
},
docs: {
brief: "User management commands",
},
});
const orgCreate = buildCommand({
loader: async () => import("../commands/org.js").then((m) => m.create),
parameters: {},
docs: { brief: "Create an organization (stub)" },
});
const orgList = buildCommand({
loader: async () => import("../commands/org.js").then((m) => m.list),
parameters: {},
docs: { brief: "List organizations (stub)" },
});
const orgAddSite = buildCommand({
loader: async () => import("../commands/org.js").then((m) => m.addSite),
parameters: {},
docs: { brief: "Add a site to an organization (stub)" },
});
const orgCommand = buildRouteMap({
routes: {
create: orgCreate,
list: orgList,
"add-site": orgAddSite,
},
docs: {
brief: "Organization management commands",
},
});
const rootMap = buildRouteMap({
routes: {
bootstrap,
auth: authCommand,
user: userCommand,
org: orgCommand,
},
docs: {
brief: "RevIQ CLI for database and user management",
},
});
const app = buildApplication(rootMap, {
const app = buildApplication(rootRouteMap, {
name: "reviq",
versionInfo: {
currentVersion: "0.0.0",
},
});
const context = {
const context: LocalContext = {
process,
};

View File

@@ -1,25 +0,0 @@
import type { CommandContext } from "@stricli/core";
/**
* Login command stub
*/
export async function login(this: CommandContext): Promise<void> {
console.log("Auth login command - Not implemented");
console.log("This command will authenticate a user and store credentials");
}
/**
* Logout command stub
*/
export async function logout(this: CommandContext): Promise<void> {
console.log("Auth logout command - Not implemented");
console.log("This command will clear stored authentication credentials");
}
/**
* Status command stub
*/
export async function status(this: CommandContext): Promise<void> {
console.log("Auth status command - Not implemented");
console.log("This command will show current authentication status");
}

View File

@@ -1,77 +0,0 @@
import type { CommandContext } from "@stricli/core";
// Password hashing imports (for future implementation)
// import { scrypt } from "@noble/hashes/scrypt";
// import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
/**
* Bootstrap command - creates a superuser account
*
* This command should be run after dbmate migration to set up
* the initial superuser account.
*
* Uses scrypt for password hashing (Cloudflare Workers compatible via @noble/hashes)
*/
export default async function (this: CommandContext): Promise<void> {
console.log("RevIQ Bootstrap - Create Superuser");
console.log("===================================\n");
// In a real implementation, we would:
// 1. Prompt for email and password using readline or prompts
// 2. Validate the input
// 3. Hash the password with scrypt (via @noble/hashes)
// 4. Connect to the database using @reviq/db
// 5. Insert the user with is_superuser=true
// 6. Handle errors appropriately
console.log("TODO: Implement bootstrap command");
console.log("\nThis command will:");
console.log(" 1. Prompt for email address");
console.log(" 2. Prompt for password (with confirmation)");
console.log(" 3. Hash password using scrypt (@noble/hashes)");
console.log(" 4. Create user in database with is_superuser=true");
console.log("\nRequirements:");
console.log(" - Database must be migrated (run 'dbmate up' first)");
console.log(" - DATABASE_URL environment variable must be set");
// Example of what the implementation would look like:
/*
import readline from 'readline';
import { db } from '@reviq/db';
import { randomBytes } from 'crypto';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const email = await new Promise<string>((resolve) => {
rl.question('Email: ', resolve);
});
const password = await new Promise<string>((resolve) => {
rl.question('Password: ', resolve);
});
// Generate a random salt
const salt = randomBytes(16);
// Hash with scrypt using recommended parameters
// N=2^14 (16384), r=8, p=1 - good balance of security and performance
const hash = scrypt(utf8ToBytes(password), salt, { N: 16384, r: 8, p: 1, dkLen: 32 });
// Store as: $scrypt$N=16384,r=8,p=1$<salt hex>$<hash hex>
const hashedPassword = `$scrypt$N=16384,r=8,p=1$${bytesToHex(salt)}$${bytesToHex(hash)}`;
await db.insertInto('users')
.values({
email: email.toLowerCase(),
password_hash: hashedPassword,
is_superuser: true,
email_verified_at: new Date(),
})
.execute();
console.log('Superuser created successfully!');
rl.close();
*/
}

View File

@@ -1,25 +0,0 @@
import type { CommandContext } from "@stricli/core";
/**
* Create organization command stub
*/
export async function create(this: CommandContext): Promise<void> {
console.log("Org create command - Not implemented");
console.log("This command will create a new organization");
}
/**
* List organizations command stub
*/
export async function list(this: CommandContext): Promise<void> {
console.log("Org list command - Not implemented");
console.log("This command will list all organizations");
}
/**
* Add site to organization command stub
*/
export async function addSite(this: CommandContext): Promise<void> {
console.log("Org add-site command - Not implemented");
console.log("This command will add a site to an organization");
}

View File

@@ -1,17 +0,0 @@
import type { CommandContext } from "@stricli/core";
/**
* Create user command stub
*/
export async function create(this: CommandContext): Promise<void> {
console.log("User create command - Not implemented");
console.log("This command will create a new user account");
}
/**
* Confirm email command stub
*/
export async function confirmEmail(this: CommandContext): Promise<void> {
console.log("User confirm-email command - Not implemented");
console.log("This command will confirm a user's email address");
}

View File

@@ -1,6 +1,8 @@
import type { CommandContext } from "@stricli/core";
/**
* Local context for CLI application
*/
export interface LocalContext {
export interface LocalContext extends CommandContext {
readonly process: NodeJS.Process;
}

View File

@@ -0,0 +1,17 @@
import { buildRouteMap } from "@stricli/core";
import { authRouteMap } from "./auth/_command.js";
import { bootstrapCommand } from "./bootstrap.js";
import { orgRouteMap } from "./org/_command.js";
import { userRouteMap } from "./user/_command.js";
export const rootRouteMap = buildRouteMap({
routes: {
bootstrap: bootstrapCommand,
auth: authRouteMap,
user: userRouteMap,
org: orgRouteMap,
},
docs: {
brief: "RevIQ CLI for database and user management",
},
});

View File

@@ -0,0 +1,15 @@
import { buildRouteMap } from "@stricli/core";
import { loginCommand } from "./login.js";
import { logoutCommand } from "./logout.js";
import { statusCommand } from "./status.js";
export const authRouteMap = buildRouteMap({
routes: {
login: loginCommand,
logout: logoutCommand,
status: statusCommand,
},
docs: {
brief: "Authentication commands",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function login(this: LocalContext): void {
console.log("Auth login command - Not implemented");
console.log("This command will authenticate a user and store credentials");
}
export const loginCommand = buildCommand({
func: login,
parameters: {},
docs: {
brief: "Login to RevIQ",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function logout(this: LocalContext): void {
console.log("Auth logout command - Not implemented");
console.log("This command will clear stored authentication credentials");
}
export const logoutCommand = buildCommand({
func: logout,
parameters: {},
docs: {
brief: "Logout from RevIQ",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function status(this: LocalContext): void {
console.log("Auth status command - Not implemented");
console.log("This command will show current authentication status");
}
export const statusCommand = buildCommand({
func: status,
parameters: {},
docs: {
brief: "Check authentication status",
},
});

View File

@@ -0,0 +1,25 @@
import type { LocalContext } from "../context.js";
import { buildCommand } from "@stricli/core";
function bootstrap(this: LocalContext): void {
console.log("RevIQ Bootstrap - Create Superuser");
console.log("===================================\n");
console.log("TODO: Implement bootstrap command");
console.log("\nThis command will:");
console.log(" 1. Prompt for email address");
console.log(" 2. Prompt for password (with confirmation)");
console.log(" 3. Hash password using scrypt (@noble/hashes)");
console.log(" 4. Create user in database with is_superuser=true");
console.log("\nRequirements:");
console.log(" - Database must be migrated (run 'dbmate up' first)");
console.log(" - DATABASE_URL environment variable must be set");
}
export const bootstrapCommand = buildCommand({
func: bootstrap,
parameters: {},
docs: {
brief: "Create a superuser account",
},
});

View File

@@ -0,0 +1,15 @@
import { buildRouteMap } from "@stricli/core";
import { addSiteCommand } from "./add-site.js";
import { createCommand } from "./create.js";
import { listCommand } from "./list.js";
export const orgRouteMap = buildRouteMap({
routes: {
create: createCommand,
list: listCommand,
"add-site": addSiteCommand,
},
docs: {
brief: "Organization management commands",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function addSite(this: LocalContext): void {
console.log("Org add-site command - Not implemented");
console.log("This command will add a site to an organization");
}
export const addSiteCommand = buildCommand({
func: addSite,
parameters: {},
docs: {
brief: "Add a site to an organization",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function create(this: LocalContext): void {
console.log("Org create command - Not implemented");
console.log("This command will create a new organization");
}
export const createCommand = buildCommand({
func: create,
parameters: {},
docs: {
brief: "Create an organization",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function list(this: LocalContext): void {
console.log("Org list command - Not implemented");
console.log("This command will list all organizations");
}
export const listCommand = buildCommand({
func: list,
parameters: {},
docs: {
brief: "List organizations",
},
});

View File

@@ -0,0 +1,13 @@
import { buildRouteMap } from "@stricli/core";
import { confirmEmailCommand } from "./confirm-email.js";
import { createCommand } from "./create.js";
export const userRouteMap = buildRouteMap({
routes: {
create: createCommand,
"confirm-email": confirmEmailCommand,
},
docs: {
brief: "User management commands",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function confirmEmail(this: LocalContext): void {
console.log("User confirm-email command - Not implemented");
console.log("This command will confirm a user's email address");
}
export const confirmEmailCommand = buildCommand({
func: confirmEmail,
parameters: {},
docs: {
brief: "Confirm user email",
},
});

View File

@@ -0,0 +1,15 @@
import type { LocalContext } from "../../context.js";
import { buildCommand } from "@stricli/core";
function create(this: LocalContext): void {
console.log("User create command - Not implemented");
console.log("This command will create a new user account");
}
export const createCommand = buildCommand({
func: create,
parameters: {},
docs: {
brief: "Create a new user",
},
});