import { Argument, Flag } from "effect/unstable/cli"; import type { FlagDescriptor } from "./descriptors.js"; import { descriptors, specsDescriptor } from "./descriptors.js"; /** The description, alias and metavar every flag shares. */ const common = (descriptor: FlagDescriptor) => (flag: Flag.Flag): Flag.Flag => { let out = Flag.withDescription(flag, descriptor.description); if (descriptor.alias !== undefined) out = Flag.withAlias(out, descriptor.alias); if (descriptor.metavar !== undefined) out = Flag.withMetavar(out, descriptor.metavar); return out; }; type Descriptor = FlagDescriptor & { readonly type: Extract; }; // One builder per shape, not one generic builder, because the types are the // point: `Command.make` derives the handler's argument from the exact type of // each `Flag`, so `Flag` and `Flag>` have to stay // distinguishable all the way through. A single builder returning a union would // collapse `Config` into something the handler could not read. `surface.test.ts` // asserts the result through the real parser. const booleanFlag = ( descriptor: Descriptor<"Boolean"> & { readonly type: { readonly default: boolean }; }, ): Flag.Flag => Flag.boolean(descriptor.name).pipe( common(descriptor), Flag.withDefault(descriptor.type.default), ); /** A boolean whose absence is meaningful — see `--force`. */ const booleanOptionalFlag = (descriptor: Descriptor<"Boolean">) => Flag.boolean(descriptor.name).pipe(common(descriptor), Flag.optional); const integerFlag = ( descriptor: Descriptor<"Integer"> & { readonly type: { readonly default: number }; }, ): Flag.Flag => Flag.integer(descriptor.name).pipe( common(descriptor), Flag.withDefault(descriptor.type.default), ); const integerOptionalFlag = (descriptor: Descriptor<"Integer">) => Flag.integer(descriptor.name).pipe(common(descriptor), Flag.optional); const textFlag = ( descriptor: Descriptor<"Text"> & { readonly type: { readonly default: string }; }, ): Flag.Flag => Flag.string(descriptor.name).pipe( common(descriptor), Flag.withDefault(descriptor.type.default), ); const textOptionalFlag = (descriptor: Descriptor<"Text">) => Flag.string(descriptor.name).pipe(common(descriptor), Flag.optional); /** Repeatable; absent yields an empty list rather than nothing. */ const textListFlag = (descriptor: Descriptor<"TextList">) => Flag.string(descriptor.name).pipe(common(descriptor), Flag.atLeast(0)); const choiceFlag = >( descriptor: Descriptor<"Choice"> & { readonly type: { readonly choices: T } }, ) => Flag.choice(descriptor.name, descriptor.type.choices).pipe(common(descriptor), Flag.optional); const fileFlag = (descriptor: Descriptor<"File">) => Flag.file(descriptor.name, { mustExist: descriptor.type.mustExist }).pipe( common(descriptor), Flag.optional, ); const directoryFlag = (descriptor: Descriptor<"Directory">): Flag.Flag => Flag.directory(descriptor.name).pipe( common(descriptor), Flag.withDefault(descriptor.type.default), ); export const specsArgument = Argument.string(specsDescriptor.name).pipe( Argument.withMetavar(specsDescriptor.metavar), Argument.withDescription(specsDescriptor.description), Argument.variadic, ); export const flags = { file: fileFlag(descriptors.file), prod: booleanFlag(descriptors.prod), lockfile: textFlag(descriptors.lockfile), out: directoryFlag(descriptors.out), layout: choiceFlag(descriptors.layout), layoutThreshold: integerOptionalFlag(descriptors.layoutThreshold), archiveName: textOptionalFlag(descriptors.archiveName), allVersions: booleanFlag(descriptors.allVersions), maxVersions: integerOptionalFlag(descriptors.maxVersions), includePrerelease: booleanFlag(descriptors.includePrerelease), optional: booleanFlag(descriptors.optional), peer: booleanFlag(descriptors.peer), platform: textListFlag(descriptors.platform), registry: textOptionalFlag(descriptors.registry), npmrc: fileFlag(descriptors.npmrc), rewriteTarballHost: booleanFlag(descriptors.rewriteTarballHost), timeout: integerFlag(descriptors.timeout), retries: integerFlag(descriptors.retries), concurrency: integerFlag(descriptors.concurrency), dryRun: booleanFlag(descriptors.dryRun), verify: booleanFlag(descriptors.verify), force: booleanOptionalFlag(descriptors.force), forceSpec: textListFlag(descriptors.forceSpec), printAbsolutePath: booleanFlag(descriptors.printAbsolutePath), json: booleanFlag(descriptors.json), quiet: booleanFlag(descriptors.quiet), interactive: booleanFlag(descriptors.interactive), yes: booleanFlag(descriptors.yes), } satisfies Record>;