Merge branch 'workstream-n-completion'

Resolve conflict: use --compile for both CLI binaries

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-09 17:21:12 +08:00
8 changed files with 148 additions and 12 deletions

View File

@@ -4,10 +4,11 @@
"private": true,
"type": "module",
"bin": {
"reviq": "./dist/reviq"
"reviq": "./dist/reviq",
"__reviq_bash_complete": "./dist/bash-complete"
},
"scripts": {
"build": "bun build src/bin/reviq.ts --compile --outfile dist/reviq",
"build": "bun build src/bin/reviq.ts --compile --outfile dist/reviq && bun build src/bin/bash-complete.ts --compile --outfile dist/bash-complete",
"cli": "bun run src/bin/reviq.ts",
"typecheck": "tsc --noEmit",
"lint": "eslint . --cache",
@@ -15,6 +16,7 @@
},
"dependencies": {
"@stricli/core": "^1.2.5",
"@stricli/auto-complete": "^1.0.0",
"@reviq/db": "workspace:*",
"@noble/hashes": "^2.0.1"
},

9
apps/cli/src/app.ts Normal file
View File

@@ -0,0 +1,9 @@
import { buildApplication } from "@stricli/core";
import { rootRouteMap } from "./routes/_command.js";
export const app = buildApplication(rootRouteMap, {
name: "reviq",
versionInfo: {
currentVersion: "0.0.0",
},
});

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bun
import type { LocalContext } from "../context.js";
import { proposeCompletions } from "@stricli/core";
import { app } from "../app.js";
const inputs = process.argv.slice(3);
// eslint-disable-next-line turbo/no-undeclared-env-vars -- COMP_LINE is set by bash completion
if (process.env.COMP_LINE?.endsWith(" ")) {
inputs.push("");
}
const context: LocalContext = { process };
try {
const completions = await proposeCompletions(app, inputs, context);
for (const { completion } of completions) {
process.stdout.write(`${completion}\n`);
}
} catch {
// Silently ignore errors during completion
}

View File

@@ -1,15 +1,8 @@
#!/usr/bin/env bun
import type { LocalContext } from "../context.js";
import { buildApplication, run } from "@stricli/core";
import { rootRouteMap } from "../routes/_command.js";
const app = buildApplication(rootRouteMap, {
name: "reviq",
versionInfo: {
currentVersion: "0.0.0",
},
});
import { run } from "@stricli/core";
import { app } from "../app.js";
const context: LocalContext = {
process,

View File

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

View File

@@ -0,0 +1,104 @@
import type { LocalContext } from "../context.js";
import { buildCommand } from "@stricli/core";
type Shell = "bash" | "zsh" | "fish";
const SUPPORTED_SHELLS: readonly Shell[] = ["bash", "zsh", "fish"] as const;
function parseShell(value: string): Shell {
const shell = value.toLowerCase();
if (!SUPPORTED_SHELLS.includes(shell as Shell)) {
throw new Error(
`Invalid shell: ${value}. Supported shells: ${SUPPORTED_SHELLS.join(", ")}`,
);
}
return shell as Shell;
}
const ZSH_COMPLETION_SCRIPT = `#compdef reviq
_reviq() {
local -a completions
local -a words_to_complete
# Build array of words up to cursor
words_to_complete=("\${words[@]:1:$((CURRENT-1))}")
# Add empty string if we're completing a new word
if [[ -z "\${words[CURRENT]}" ]] || [[ "\${BUFFER}" == *" " ]]; then
words_to_complete+=("")
fi
# Call the completion helper
completions=(\${(f)"$(COMP_LINE="$BUFFER" __reviq_bash_complete reviq "\${words_to_complete[@]}" 2>/dev/null)"})
if [[ \${#completions[@]} -gt 0 ]]; then
_describe 'command' completions
fi
}
_reviq "$@"
`;
function completions(
this: LocalContext,
_flags: Record<string, never>,
shell: Shell,
): void {
// biome-ignore lint/nursery/noUnnecessaryConditions: switch on union type is valid
switch (shell) {
case "bash":
console.log("To enable bash completions for reviq, run:\n");
console.log(
" npx @stricli/auto-complete install reviq --bash __reviq_bash_complete\n",
);
console.log(
"This will modify your ~/.bashrc to enable tab completion for reviq commands.",
);
console.log("\nTo uninstall, run:\n");
console.log(" npx @stricli/auto-complete uninstall reviq --bash");
break;
case "zsh":
console.log("# Add the following to your ~/.zshrc:\n");
console.log(ZSH_COMPLETION_SCRIPT);
console.log("\n# Or save to a file and source it:");
console.log(
"# reviq completions zsh > ~/.config/reviq/completions.zsh",
);
console.log(
'# echo "source ~/.config/reviq/completions.zsh" >> ~/.zshrc',
);
break;
case "fish":
console.log(`Shell completions for ${shell} are not yet supported.`);
console.log(
"\nCurrently only bash and zsh are supported. fish support is planned.",
);
this.process.exit(1);
break;
}
}
export const completionsCommand = buildCommand({
func: completions,
parameters: {
flags: {},
positional: {
kind: "tuple",
parameters: [
{
brief: "Shell to generate completions for (bash, zsh, fish)",
parse: parseShell,
placeholder: "shell",
},
],
},
},
docs: {
brief: "Generate shell completions",
fullDescription:
"Generate shell completion scripts for bash, zsh, or fish.\n\nCurrently bash and zsh are supported. fish support is planned.",
},
});