From 34289026084c1509aca6a180f5e2714e7802c031 Mon Sep 17 00:00:00 2001 From: Maxwell Brown Date: Fri, 17 Nov 2023 16:41:14 -0500 Subject: [PATCH] fix generation of completion script --- .changeset/wild-bees-learn.md | 5 +++ examples/git.ts | 9 ++--- examples/prompt.ts | 9 ++--- package.json | 18 +++++----- pnpm-lock.yaml | 68 +++++++++++++++++------------------ src/internal/cliApp.ts | 42 +++++++++++----------- src/internal/command.ts | 26 -------------- src/internal/completion.ts | 32 ++++++++--------- 8 files changed, 89 insertions(+), 120 deletions(-) create mode 100644 .changeset/wild-bees-learn.md diff --git a/.changeset/wild-bees-learn.md b/.changeset/wild-bees-learn.md new file mode 100644 index 0000000..437c889 --- /dev/null +++ b/.changeset/wild-bees-learn.md @@ -0,0 +1,5 @@ +--- +"@effect/cli": patch +--- + +fix completion script generation diff --git a/examples/git.ts b/examples/git.ts index e29b06b..7b1ada0 100644 --- a/examples/git.ts +++ b/examples/git.ts @@ -5,10 +5,9 @@ import * as HelpDoc from "@effect/cli/HelpDoc" import * as Span from "@effect/cli/HelpDoc/Span" import * as Options from "@effect/cli/Options" import * as NodeContext from "@effect/platform-node/NodeContext" -import * as Terminal from "@effect/platform-node/Terminal" +import * as Runtime from "@effect/platform-node/Runtime" import * as Data from "effect/Data" import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" import * as Option from "effect/Option" export interface Git extends Data.Case { @@ -134,8 +133,6 @@ const cli = CliApp.make({ footer: HelpDoc.p("Copyright 2023") }) -const MainLive = Layer.merge(NodeContext.layer, Terminal.layer) - Effect.sync(() => process.argv.slice(2)).pipe( Effect.flatMap((args) => CliApp.run(cli, args, (command) => @@ -147,6 +144,6 @@ Effect.sync(() => process.argv.slice(2)).pipe( onSome: handleGitSubcommand })) ), - Effect.provide(MainLive), - Effect.runFork + Effect.provide(NodeContext.layer), + Runtime.runMain ) diff --git a/examples/prompt.ts b/examples/prompt.ts index 6772b92..f1d95b6 100644 --- a/examples/prompt.ts +++ b/examples/prompt.ts @@ -2,9 +2,8 @@ import * as CliApp from "@effect/cli/CliApp" import * as Command from "@effect/cli/Command" import * as Prompt from "@effect/cli/Prompt" import * as NodeContext from "@effect/platform-node/NodeContext" -import * as Terminal from "@effect/platform-node/Terminal" +import * as Runtime from "@effect/platform-node/Runtime" import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" const colorPrompt = Prompt.select({ message: "Pick your favorite color", @@ -64,10 +63,8 @@ const cli = CliApp.make({ command: Command.prompt("favorites", prompt) }) -const MainLive = Layer.merge(NodeContext.layer, Terminal.layer) - Effect.sync(() => process.argv.slice(2)).pipe( Effect.flatMap((args) => CliApp.run(cli, args, (input) => Effect.log(input))), - Effect.provide(MainLive), - Effect.runFork + Effect.provide(NodeContext.layer), + Runtime.runMain ) diff --git a/package.json b/package.json index a0fc4a3..3c9db32 100644 --- a/package.json +++ b/package.json @@ -57,10 +57,10 @@ "coverage": "vitest run --coverage" }, "peerDependencies": { - "@effect/platform": "^0.30.0", - "@effect/printer": "^0.23.0", - "@effect/printer-ansi": "^0.23.0", - "@effect/schema": "^0.48.0", + "@effect/platform": "^0.30.5", + "@effect/printer": "^0.23.1", + "@effect/printer-ansi": "^0.23.1", + "@effect/schema": "^0.48.3", "effect": "2.0.0-next.55" }, "devDependencies": { @@ -74,12 +74,12 @@ "@effect/docgen": "^0.3.4", "@effect/eslint-plugin": "^0.1.2", "@effect/language-service": "^0.0.21", - "@effect/platform": "^0.30.2", - "@effect/platform-node": "^0.31.2", + "@effect/platform": "^0.30.5", + "@effect/platform-node": "^0.31.6", "@effect/printer": "^0.23.1", "@effect/printer-ansi": "^0.23.1", - "@effect/schema": "^0.48.0", - "@types/node": "^20.9.0", + "@effect/schema": "^0.48.3", + "@types/node": "^20.9.1", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", "@vitest/coverage-v8": "^0.34.6", @@ -97,7 +97,7 @@ "madge": "^6.1.0", "rimraf": "^5.0.5", "stackframe": "^1.3.4", - "tsx": "^4.1.2", + "tsx": "^4.1.3", "typescript": "^5.2.2", "vite": "^5.0.0", "vitest": "^0.34.6" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c23b76a..45953f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ devDependencies: version: 0.5.0 '@effect/docgen': specifier: ^0.3.4 - version: 0.3.4(tsx@4.1.2)(typescript@5.2.2) + version: 0.3.4(tsx@4.1.3)(typescript@5.2.2) '@effect/eslint-plugin': specifier: ^0.1.2 version: 0.1.2 @@ -36,11 +36,11 @@ devDependencies: specifier: ^0.0.21 version: 0.0.21 '@effect/platform': - specifier: ^0.30.2 - version: 0.30.2(@effect/schema@0.48.0)(effect@2.0.0-next.55) + specifier: ^0.30.5 + version: 0.30.5(@effect/schema@0.48.3)(effect@2.0.0-next.55) '@effect/platform-node': - specifier: ^0.31.2 - version: 0.31.2(@effect/schema@0.48.0)(effect@2.0.0-next.55) + specifier: ^0.31.6 + version: 0.31.6(@effect/schema@0.48.3)(effect@2.0.0-next.55) '@effect/printer': specifier: ^0.23.1 version: 0.23.1(@effect/typeclass@0.15.0)(effect@2.0.0-next.55) @@ -48,11 +48,11 @@ devDependencies: specifier: ^0.23.1 version: 0.23.1(@effect/typeclass@0.15.0)(effect@2.0.0-next.55) '@effect/schema': - specifier: ^0.48.0 - version: 0.48.0(effect@2.0.0-next.55)(fast-check@3.13.2) + specifier: ^0.48.3 + version: 0.48.3(effect@2.0.0-next.55)(fast-check@3.13.2) '@types/node': - specifier: ^20.9.0 - version: 20.9.0 + specifier: ^20.9.1 + version: 20.9.1 '@typescript-eslint/eslint-plugin': specifier: ^6.11.0 version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2) @@ -105,14 +105,14 @@ devDependencies: specifier: ^1.3.4 version: 1.3.4 tsx: - specifier: ^4.1.2 - version: 4.1.2 + specifier: ^4.1.3 + version: 4.1.3 typescript: specifier: ^5.2.2 version: 5.2.2 vite: specifier: ^5.0.0 - version: 5.0.0(@types/node@20.9.0) + version: 5.0.0(@types/node@20.9.1) vitest: specifier: ^0.34.6 version: 0.34.6 @@ -669,7 +669,7 @@ packages: hasBin: true dev: true - /@effect/docgen@0.3.4(tsx@4.1.2)(typescript@5.2.2): + /@effect/docgen@0.3.4(tsx@4.1.3)(typescript@5.2.2): resolution: {integrity: sha512-NO0CkND5oH9/pvqpViXARGjRxGD70iBpouimaPJ3nMpRi91gBLLwyWSxYeXoYN1Sva9WPq93O4DRSOmtnFQWNA==} engines: {node: '>=16.17.1'} hasBin: true @@ -681,7 +681,7 @@ packages: glob: 10.3.10 markdown-toc: github.com/effect-ts/markdown-toc/4bfeb0f140105440ea0d12df2fa23199cc3ec1d5 prettier: 3.1.0 - tsx: 4.1.2 + tsx: 4.1.3 typescript: 5.2.2 dev: true @@ -697,12 +697,12 @@ packages: resolution: {integrity: sha512-e8vfKbjnbYiyneBincEFS0tzXluopGK77OkVFbPRtUbNDS5tJfb+jiwOQEiqASDsadcZmd+9J9+Q6v/z7GuN2g==} dev: true - /@effect/platform-node@0.31.2(@effect/schema@0.48.0)(effect@2.0.0-next.55): - resolution: {integrity: sha512-pNJfcgM8aYAxGt7ir2/qbN+LIAnN3O2TXK0akPEowptFRRUpvjEwDhOGcLUje/1BHk4b1bikmU1tLdPOFxivOg==} + /@effect/platform-node@0.31.6(@effect/schema@0.48.3)(effect@2.0.0-next.55): + resolution: {integrity: sha512-Hc9YM5dOXjZ2hhLXvPOhCIWnzIW4F41uypIzbv/HMsOIiPbGBLi9o/w7xOJYHHl8DVEKR4mCA41aEjDG8jffnw==} peerDependencies: effect: 2.0.0-next.55 dependencies: - '@effect/platform': 0.30.2(@effect/schema@0.48.0)(effect@2.0.0-next.55) + '@effect/platform': 0.30.5(@effect/schema@0.48.3)(effect@2.0.0-next.55) effect: 2.0.0-next.55 mime: 3.0.0 multipasta: 0.1.18 @@ -710,13 +710,13 @@ packages: - '@effect/schema' dev: true - /@effect/platform@0.30.2(@effect/schema@0.48.0)(effect@2.0.0-next.55): - resolution: {integrity: sha512-NdvaINbYwmOpvRdfqlmEwdeD1fH8u1XkgUGoIjatWMn1GQr46xLcZAVZAakRjGLmlI0cPx9BWZfRjRaFgUcN2w==} + /@effect/platform@0.30.5(@effect/schema@0.48.3)(effect@2.0.0-next.55): + resolution: {integrity: sha512-zWQ09GWGjNBYyRIcEgfXwH+TFwucET4ziV4xAHvJCzrFXN43Z2AHb/LtIby28IBNBtxcSWa8pAe76S20WkALtw==} peerDependencies: '@effect/schema': ^0.48.0 effect: 2.0.0-next.55 dependencies: - '@effect/schema': 0.48.0(effect@2.0.0-next.55)(fast-check@3.13.2) + '@effect/schema': 0.48.3(effect@2.0.0-next.55)(fast-check@3.13.2) effect: 2.0.0-next.55 find-my-way: 7.7.0 multipasta: 0.1.18 @@ -744,8 +744,8 @@ packages: effect: 2.0.0-next.55 dev: true - /@effect/schema@0.48.0(effect@2.0.0-next.55)(fast-check@3.13.2): - resolution: {integrity: sha512-IpAik8hX4kAuOdfNBfxfE8mYVfiikheSxFyWRkVQUutD1E86hDXG90EUYu2/zL2pHkgoS6c2cnY7uoKYwKSP2w==} + /@effect/schema@0.48.3(effect@2.0.0-next.55)(fast-check@3.13.2): + resolution: {integrity: sha512-6tvwQrav5Q2Z7FlA6Kpo0ufKANZXi9DME8BPDCFVstGMwvt1SGXemj77ypEGrbaJKNpYqUhrcNERhpEv5adExQ==} peerDependencies: effect: 2.0.0-next.55 fast-check: ^3.13.2 @@ -1245,7 +1245,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.9.0 + '@types/node': 20.9.1 '@types/yargs': 15.0.17 chalk: 4.1.2 dev: true @@ -1497,8 +1497,8 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node@20.9.0: - resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} + /@types/node@20.9.1: + resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==} dependencies: undici-types: 5.26.5 dev: true @@ -5873,8 +5873,8 @@ packages: typescript: 5.2.2 dev: true - /tsx@4.1.2: - resolution: {integrity: sha512-1spM1bFV6MP2s4tO4tDC7g52fsaFdtEWdO4GfGdqi20qUgPbnAJqixOyIAvCSx1DDj3YIUB4CD06owTWUsOAuQ==} + /tsx@4.1.3: + resolution: {integrity: sha512-DLiTy1eri4nhqgVVy+15YKC6Ij2BMFxGdDMkVrSDkNuISUJLv7n0NgZpFLpdM+qmwXar34XllgYi4cxkNMbDwQ==} engines: {node: '>=18.0.0'} hasBin: true dependencies: @@ -6054,7 +6054,7 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node@0.34.6(@types/node@20.9.0): + /vite-node@0.34.6(@types/node@20.9.1): resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true @@ -6064,7 +6064,7 @@ packages: mlly: 1.4.2 pathe: 1.1.1 picocolors: 1.0.0 - vite: 5.0.0(@types/node@20.9.0) + vite: 5.0.0(@types/node@20.9.1) transitivePeerDependencies: - '@types/node' - less @@ -6076,7 +6076,7 @@ packages: - terser dev: true - /vite@5.0.0(@types/node@20.9.0): + /vite@5.0.0(@types/node@20.9.1): resolution: {integrity: sha512-ESJVM59mdyGpsiNAeHQOR/0fqNoOyWPYesFto8FFZugfmhdHx8Fzd8sF3Q/xkVhZsyOxHfdM7ieiVAorI9RjFw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -6104,7 +6104,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.9.0 + '@types/node': 20.9.1 esbuild: 0.19.5 postcss: 8.4.31 rollup: 4.4.1 @@ -6145,7 +6145,7 @@ packages: dependencies: '@types/chai': 4.3.10 '@types/chai-subset': 1.3.3 - '@types/node': 20.9.0 + '@types/node': 20.9.1 '@vitest/expect': 0.34.6 '@vitest/runner': 0.34.6 '@vitest/snapshot': 0.34.6 @@ -6164,8 +6164,8 @@ packages: strip-literal: 1.3.0 tinybench: 2.5.1 tinypool: 0.7.0 - vite: 5.0.0(@types/node@20.9.0) - vite-node: 0.34.6(@types/node@20.9.0) + vite: 5.0.0(@types/node@20.9.1) + vite-node: 0.34.6(@types/node@20.9.1) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/internal/cliApp.ts b/src/internal/cliApp.ts index 13f97ba..ea39fae 100644 --- a/src/internal/cliApp.ts +++ b/src/internal/cliApp.ts @@ -1,4 +1,3 @@ -import * as Path from "@effect/platform/Path" import type * as Terminal from "@effect/platform/Terminal" import * as Console from "effect/Console" import * as Context from "effect/Context" @@ -100,6 +99,11 @@ export const run = dual< // Internals // ============================================================================= +const getEnv = () => + typeof process !== "undefined" && "env" in process && typeof process.env === "object" + ? process.env + : {} + const printDocs = (error: HelpDoc.HelpDoc): Effect.Effect => Console.log(InternalHelpDoc.toAnsiText(error)) @@ -142,33 +146,29 @@ const handleBuiltInOption = ( return Console.log(InternalHelpDoc.toAnsiText(helpDoc)) } case "ShowCompletionScript": { - return Effect.flatMap(Path.Path, (path) => { - const commandNames = ReadonlyArray.fromIterable(InternalCommand.getNames(self.command)) - const programNames = ReadonlyArray.isNonEmptyReadonlyArray(commandNames) - ? commandNames - : ReadonlyArray.of(self.name) - const script = InternalCompletion.getCompletionScript( - builtIn.pathToExecutable, - programNames, - builtIn.shellType, - path - ) - return Console.log(script) - }) + const commandNames = ReadonlyArray.fromIterable(InternalCommand.getNames(self.command)) + const programNames = ReadonlyArray.isNonEmptyReadonlyArray(commandNames) + ? commandNames + : ReadonlyArray.of(self.name) + const script = InternalCompletion.getCompletionScript( + builtIn.pathToExecutable, + programNames, + builtIn.shellType + ) + return Console.log(script) } case "ShowCompletions": { return Effect.all([ InternalCompgen.Tag, - Effect.sync(() => globalThis.process.env) + Effect.sync(getEnv) ]).pipe(Effect.flatMap(([compgen, env]) => { const tupleOrder = Order.mapInput(Order.number, (tuple: [number, string]) => tuple[0]) const compWords = pipe( - ReadonlyRecord.collect( - env, - (key, value) => - key.startsWith("COMP_WORD_") && value !== undefined - ? Option.some<[number, string]>([key.replace("COMP_WORD_", "").length, value]) - : Option.none() + env, + ReadonlyRecord.collect((key, value) => + key.startsWith("COMP_WORD_") && value !== undefined + ? Option.some<[number, string]>([key.replace("COMP_WORD_", "").length, value]) + : Option.none() ), ReadonlyArray.compact, ReadonlyArray.sortBy(tupleOrder), diff --git a/src/internal/command.ts b/src/internal/command.ts index 1248fec..a8f151a 100644 --- a/src/internal/command.ts +++ b/src/internal/command.ts @@ -936,29 +936,3 @@ const wizardInternal = (self: Instruction, config: CliConfig.CliConfig): Effect. } } } - -// /** @internal */ -// export const withHelp = dual< -// (help: string | HelpDoc.HelpDoc) => (self: Command.Command) => Command.Command, -// (self: Command.Command, help: string | HelpDoc.HelpDoc) => Command.Command -// >(2, (self: Command.Command, help: string | HelpDoc.HelpDoc): Command.Command => { -// if (isStandard(self)) { -// const helpDoc = typeof help === "string" ? HelpDoc.p(help) : help -// return new Standard(self.name, helpDoc, self.options, self.args) as Command.Command -// } -// // TODO: if (isPrompt(self)) {} -// if (isMap(self)) { -// return new Map(withHelp(self.command, help), self.f) as Command.Command -// } -// if (isOrElse(self)) { -// // TODO: if both the left and right commands also have help defined, that -// // help will be overwritten by this method which may be undesirable -// return new OrElse(withHelp(self.left, help), withHelp(self.right, help)) as Command.Command -// } -// if (isSubcommands(self)) { -// return new Subcommands(withHelp(self.parent, help), self.child) as Command.Command -// } -// throw new Error( -// `[BUG]: Command.withHelp - received unknown command type: ${JSON.stringify(self)}` -// ) -// }) diff --git a/src/internal/completion.ts b/src/internal/completion.ts index 7c348cf..d21503b 100644 --- a/src/internal/completion.ts +++ b/src/internal/completion.ts @@ -1,5 +1,4 @@ import type * as FileSystem from "@effect/platform/FileSystem" -import type * as Path from "@effect/platform/Path" import * as Effect from "effect/Effect" import { pipe } from "effect/Function" import * as Order from "effect/Order" @@ -59,12 +58,11 @@ export const getCompletions = ( export const getCompletionScript = ( pathToExecutable: string, programNames: ReadonlyArray.NonEmptyReadonlyArray, - shellType: ShellType.ShellType, - path: Path.Path + shellType: ShellType.ShellType ): string => { switch (shellType._tag) { case "Bash": { - return createBashCompletionScript(pathToExecutable, programNames, path) + return createBashCompletionScript(pathToExecutable, programNames) } case "Fish": case "Zsh": { @@ -79,15 +77,20 @@ export const getCompletionScript = ( const createBashCompletionScript = ( pathToExecutable: string, - programNames: ReadonlyArray.NonEmptyReadonlyArray, - path: Path.Path + programNames: ReadonlyArray.NonEmptyReadonlyArray ): string => { - const script = String.stripMargin( + const completions = pipe( + programNames, + ReadonlyArray.map((programName) => + `complete -F _${ReadonlyArray.headNonEmpty(programNames)} ${programName}` + ), + ReadonlyArray.join("\n") + ) + return String.stripMargin( `|#!/usr/bin/env bash - |_${ReadonlyArray.headNonEmpty(programNames)}() - |{ + |_${ReadonlyArray.headNonEmpty(programNames)}() { | local CMDLINE - | local IFS=$'\n' + | local IFS=$'\\n' | CMDLINE=(--shell-type bash --shell-completion-index $COMP_CWORD) | | INDEX=0 @@ -101,13 +104,6 @@ const createBashCompletionScript = ( | # Unset the environment variables. | unset $(compgen -v | grep "^COMP_WORD_") |} - |` - ) - return pipe( - ReadonlyArray.prepend(programNames, script), - ReadonlyArray.map((programName) => - `complete -F _${ReadonlyArray.headNonEmpty(programNames)} ${programName}` - ), - ReadonlyArray.join(path.sep) + |${completions}` ) }