|
9 | 9 | import { Args, Command } from "@effect/cli"; |
10 | 10 | import { Effect } from "effect"; |
11 | 11 | import { Console } from "effect"; |
| 12 | +import { spawnSync } from "node:child_process"; |
12 | 13 | import { dirname } from "node:path"; |
13 | 14 | import { fileURLToPath } from "node:url"; |
14 | 15 | import { authCommand } from "./auth-commands.js"; |
@@ -147,6 +148,7 @@ const adminCliRunner = Command.run(adminRootCommand, { |
147 | 148 |
|
148 | 149 | const EP_ADMIN_DOCS_URL = |
149 | 150 | "https://github.com/PaulJPhilp/EffectPatterns/tree/main/EP-ADMIN-CLI-README.md"; |
| 151 | +const HELP_RENDER_BYPASS_ENV = "EP_ADMIN_HELP_RENDER_BYPASS"; |
150 | 152 |
|
151 | 153 | type PreparedArgv = { |
152 | 154 | readonly argv: ReadonlyArray<string>; |
@@ -357,6 +359,26 @@ const isRootHelpRequest = (argv: ReadonlyArray<string>): boolean => { |
357 | 359 | const normalizeArgsForSuggestion = (argv: ReadonlyArray<string>): string[] => |
358 | 360 | argv.slice(2).filter((token) => token.length > 0 && !token.startsWith("-")); |
359 | 361 |
|
| 362 | +const isNestedHelpRequest = (argv: ReadonlyArray<string>): boolean => { |
| 363 | + const args = argv.slice(2); |
| 364 | + const hasHelpFlag = args.includes("--help") || args.includes("-h"); |
| 365 | + if (!hasHelpFlag) return false; |
| 366 | + return normalizeArgsForSuggestion(argv).length > 0; |
| 367 | +}; |
| 368 | + |
| 369 | +const rewriteHelpUsageWithFullPath = ( |
| 370 | + helpOutput: string, |
| 371 | + argv: ReadonlyArray<string> |
| 372 | +): string => { |
| 373 | + const pathTokens = normalizeArgsForSuggestion(argv); |
| 374 | + if (pathTokens.length === 0) { |
| 375 | + return helpOutput; |
| 376 | + } |
| 377 | + |
| 378 | + const fullPath = `ep-admin ${pathTokens.join(" ")}`; |
| 379 | + return helpOutput.replace(/^\$ \S+/m, `$ ${fullPath}`); |
| 380 | +}; |
| 381 | + |
360 | 382 | const levenshtein = (a: string, b: string): number => { |
361 | 383 | const dp: number[][] = Array.from({ length: a.length + 1 }, () => |
362 | 384 | Array.from({ length: b.length + 1 }, () => 0) |
@@ -759,6 +781,54 @@ export const runCli = ( |
759 | 781 | return yield* Effect.fail(new Error(mismatch.message)); |
760 | 782 | } |
761 | 783 |
|
| 784 | + if ( |
| 785 | + isNestedHelpRequest(prepared.argv) && |
| 786 | + process.env[HELP_RENDER_BYPASS_ENV] !== "1" |
| 787 | + ) { |
| 788 | + const [runtime, ...childArgs] = prepared.argv; |
| 789 | + const helpOutput = yield* Effect.try({ |
| 790 | + try: () => |
| 791 | + spawnSync(runtime ?? "bun", childArgs, { |
| 792 | + encoding: "utf8", |
| 793 | + env: { |
| 794 | + ...process.env, |
| 795 | + [HELP_RENDER_BYPASS_ENV]: "1", |
| 796 | + }, |
| 797 | + }), |
| 798 | + catch: (error) => |
| 799 | + new Error( |
| 800 | + `Failed to render contextual help: ${ |
| 801 | + error instanceof Error ? error.message : String(error) |
| 802 | + }` |
| 803 | + ), |
| 804 | + }); |
| 805 | + |
| 806 | + const rewrittenStdout = rewriteHelpUsageWithFullPath( |
| 807 | + helpOutput.stdout ?? "", |
| 808 | + prepared.argv |
| 809 | + ); |
| 810 | + |
| 811 | + yield* Effect.sync(() => { |
| 812 | + if (rewrittenStdout.length > 0) { |
| 813 | + process.stdout.write(rewrittenStdout); |
| 814 | + } |
| 815 | + if ((helpOutput.stderr ?? "").length > 0) { |
| 816 | + process.stderr.write(helpOutput.stderr ?? ""); |
| 817 | + } |
| 818 | + }); |
| 819 | + |
| 820 | + if (helpOutput.status !== 0) { |
| 821 | + return yield* Effect.fail( |
| 822 | + new Error( |
| 823 | + `Contextual help command failed with exit code ${ |
| 824 | + helpOutput.status ?? "unknown" |
| 825 | + }.` |
| 826 | + ) |
| 827 | + ); |
| 828 | + } |
| 829 | + return; |
| 830 | + } |
| 831 | + |
762 | 832 | // Validate env before runtime operations. |
763 | 833 | yield* validateEnvironment; |
764 | 834 |
|
|
0 commit comments