From 851a0aaf029f28b7eb8b8c4e86a3cdb1d482e963 Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Thu, 7 May 2026 12:43:48 +0200 Subject: [PATCH 1/4] fix(hf): duplojs/utils version --- .codex/config.toml | 4 ++-- package-lock.json | 9 +++++---- package.json | 2 +- scripts/command/types/index.ts | 3 ++- scripts/dataParser/extended/file.ts | 4 ++-- scripts/dataParser/parsers/file.ts | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.codex/config.toml b/.codex/config.toml index 7d2d3e0..7503b7e 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -1,5 +1,5 @@ -model = "gpt-5.5" -review_model = "gpt-5.5" +model = "gpt-5.3-codex" +review_model = "gpt-5.3-codex" model_provider = "openai" approval_policy = "untrusted" diff --git a/package-lock.json b/package-lock.json index 7d969a5..9b92ded 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "node": ">=22.15.1" }, "peerDependencies": { - "@duplojs/utils": ">=1.7.0 <2.0.0" + "@duplojs/utils": ">=1.8.0 <2.0.0" } }, "docs": { @@ -56,6 +56,7 @@ } }, "docs/libs/v0": { + "name": "@server-utils/v0", "dev": true, "peerDependencies": { "@duplojs/utils": ">=1.6.5 <2.0.0" @@ -1104,9 +1105,9 @@ "link": true }, "node_modules/@duplojs/utils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.7.0.tgz", - "integrity": "sha512-LBb/ZpBh7OGJbGYKx6cvq6dmPXTt8EH6FOpPZUBgEcerb2P4boW6gMX/MkGnJ4maR921V3KGZNiIeBA+uK5mWw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@duplojs/utils/-/utils-1.8.0.tgz", + "integrity": "sha512-taQ+tA7r5J6ycLvnVMluFlJ3T3uoNZSi7MbAOw3bwMfpx96Mmxut5XWXf3rGK4HSA1zWVzQZyI5pcHhJa2wPBQ==", "license": "MIT", "peer": true, "workspaces": [ diff --git a/package.json b/package.json index abaf1e4..f23abbf 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "@duplojs/dev-tools": "0.2.0" }, "peerDependencies": { - "@duplojs/utils": ">=1.7.0 <2.0.0" + "@duplojs/utils": ">=1.8.0 <2.0.0" }, "workspaces": [ "integration", diff --git a/scripts/command/types/index.ts b/scripts/command/types/index.ts index d02bdc3..9aae344 100644 --- a/scripts/command/types/index.ts +++ b/scripts/command/types/index.ts @@ -1,3 +1,4 @@ export * from "./eligibleDataParser"; export * from "./eligibleCleanType"; -export * from "./eligibleContract"; +export * from "./eligibleSpec"; +export * from "./forbiddenDuplicateName"; diff --git a/scripts/dataParser/extended/file.ts b/scripts/dataParser/extended/file.ts index e702815..c953031 100644 --- a/scripts/dataParser/extended/file.ts +++ b/scripts/dataParser/extended/file.ts @@ -7,7 +7,7 @@ type _DataParserFileExtended< GenericDefinition extends dataParsers.DataParserDefinitionFile, > = ( & Kind - & DDP.DataParserExtended< + & DDP.DataParserBaseExtended< GenericDefinition, FileInterface, FileInterface @@ -88,7 +88,7 @@ export function file< NeverCoalescing > > { - const self = DDP.dataParserExtendedInit< + const self = DDP.dataParserBaseExtendedInit< dataParsers.DataParserFile, DataParserFileExtended >( diff --git a/scripts/dataParser/parsers/file.ts b/scripts/dataParser/parsers/file.ts index d500cab..20d810e 100644 --- a/scripts/dataParser/parsers/file.ts +++ b/scripts/dataParser/parsers/file.ts @@ -33,7 +33,7 @@ export const fileKind = createDataParserKind("file"); type _DataParserFile< GenericDefinition extends DataParserDefinitionFile, > = ( - & DDP.DataParser< + & DDP.DataParserBase< GenericDefinition, FileInterface, FileInterface @@ -89,7 +89,7 @@ export function file< NeverCoalescing > > { - const self = DDP.dataParserInit( + const self = DDP.dataParserBaseInit( fileKind, { errorMessage: definition?.errorMessage, From 1a91e0e983ff8fb4bf66bae9e0cfc5b70888cc1a Mon Sep 17 00:00:00 2001 From: ZeRiix Date: Thu, 7 May 2026 13:06:50 +0200 Subject: [PATCH 2/4] feat(12): add Command.createArgument --- .../node/__snapshots__/index.test.ts.snap | 30 +- integration/node/index.test.ts | 34 +- scripts/command/argument.ts | 112 +++ scripts/command/create.ts | 306 ++++++++ scripts/command/create/index.ts | 249 ------ scripts/command/create/subject.ts | 132 ---- scripts/command/error.ts | 15 +- scripts/command/exec.ts | 46 +- scripts/command/execOptions.ts | 3 +- scripts/command/help.ts | 168 ++-- scripts/command/index.ts | 1 + scripts/command/options/array.ts | 198 ++--- scripts/command/options/base.ts | 2 +- scripts/command/options/boolean.ts | 34 +- scripts/command/options/simple.ts | 166 ++-- .../options/types/computeOptionContract.ts | 10 - .../options/types/computeOptionSpec.ts | 10 + scripts/command/options/types/index.ts | 3 +- scripts/command/options/types/options.ts | 12 + scripts/command/spec.ts | 40 + scripts/command/types/eligibleCleanType.ts | 1 - scripts/command/types/eligibleContract.ts | 7 - scripts/command/types/eligibleDataParser.ts | 8 - scripts/command/types/eligibleSpec.ts | 16 + .../command/types/forbiddenDuplicateName.ts | 18 + tests/command/argument.test.ts | 51 ++ tests/command/create.test.ts | 736 ++++-------------- tests/command/error.test.ts | 168 ++-- tests/command/exec.test.ts | 66 +- tests/command/help.test.ts | 283 +++---- tests/command/options/array.test.ts | 10 +- tests/command/options/simple.test.ts | 42 +- 32 files changed, 1287 insertions(+), 1690 deletions(-) create mode 100644 scripts/command/argument.ts create mode 100644 scripts/command/create.ts delete mode 100644 scripts/command/create/index.ts delete mode 100644 scripts/command/create/subject.ts delete mode 100644 scripts/command/options/types/computeOptionContract.ts create mode 100644 scripts/command/options/types/computeOptionSpec.ts create mode 100644 scripts/command/options/types/options.ts create mode 100644 scripts/command/spec.ts delete mode 100644 scripts/command/types/eligibleContract.ts create mode 100644 scripts/command/types/eligibleSpec.ts create mode 100644 scripts/command/types/forbiddenDuplicateName.ts create mode 100644 tests/command/argument.test.ts diff --git a/integration/node/__snapshots__/index.test.ts.snap b/integration/node/__snapshots__/index.test.ts.snap index 781edf6..8d9ea21 100644 --- a/integration/node/__snapshots__/index.test.ts.snap +++ b/integration/node/__snapshots__/index.test.ts.snap @@ -9,32 +9,38 @@ exports[`node integration > command integration renders interpreted errors > com `; exports[`node integration > command integration with nested help and execute > help command 1`] = ` -"NAME:db +"COMMAND: db DESCRIPTION: Database commands - NAME:help-db - NAME:seed + COMMAND: help-db + COMMAND: seed DESCRIPTION: Seed a target OPTIONS: - - force: -f, --force + - force: -f, --force force execution - SUBJECT:[string]" + - str: --str [string] + test option + ARGUMENTS: + - src: string" `; exports[`node integration > command integration with nested help and execute > help root 1`] = ` -"NAME:root - NAME:db +"COMMAND: root + COMMAND: db DESCRIPTION: Database commands - NAME:help-db - NAME:seed + COMMAND: help-db + COMMAND: seed DESCRIPTION: Seed a target OPTIONS: - - force: -f, --force + - force: -f, --force force execution - SUBJECT:[string]" + - str: --str [string] + test option + ARGUMENTS: + - src: string" `; -exports[`node integration > command integration with nested help and execute > help sub-command 1`] = `"NAME:help-db"`; +exports[`node integration > command integration with nested help and execute > help sub-command 1`] = `"COMMAND: help-db"`; diff --git a/integration/node/index.test.ts b/integration/node/index.test.ts index ced1fd2..09222d7 100644 --- a/integration/node/index.test.ts +++ b/integration/node/index.test.ts @@ -241,26 +241,30 @@ describe("node integration", () => { description: "force execution", }, ), + SC.createOption("str", DP.string(), { description: "test option" }), ], - subject: DP.tuple([DP.string()]), + subjects: [SC.createArgument("src", DP.string())], }, - ({ options, subject }) => { + ({ options, args }) => { type _CheckOptions = ExpectType< typeof options, { + str: string | undefined; force: boolean; }, "strict" >; type _CheckSubject = ExpectType< - typeof subject, - [string], + typeof args, + { + src: string; + }, "strict" >; subCommandExecuteSpy({ options, - subject, + args, }); }, ); @@ -269,7 +273,7 @@ describe("node integration", () => { "db", { description: "Database commands", - subject: [subHelpCommand, subCommand], + subjects: [subHelpCommand, subCommand], }, commandExecuteSpy, ); @@ -277,7 +281,7 @@ describe("node integration", () => { getProcessArgumentsSpy.mockReturnValue(["--help"]); await SC.exec( { - subject: command, + subjects: [command], }, rootExecuteSpy, ); @@ -293,7 +297,7 @@ describe("node integration", () => { getProcessArgumentsSpy.mockReturnValue(["db", "--help"]); await SC.exec( { - subject: command, + subjects: [command], }, rootExecuteSpy, ); @@ -307,7 +311,7 @@ describe("node integration", () => { getProcessArgumentsSpy.mockReturnValue(["db", "help-db", "--help"]); await SC.exec( { - subject: command, + subjects: [command], }, rootExecuteSpy, ); @@ -320,7 +324,7 @@ describe("node integration", () => { getProcessArgumentsSpy.mockReturnValue(["db", "seed", "--force", "users"]); await SC.exec( { - subject: command, + subjects: [command], }, rootExecuteSpy, ); @@ -332,7 +336,9 @@ describe("node integration", () => { options: { force: true, }, - subject: ["users"], + args: { + src: "users", + }, }); setEnvironment("NODE"); @@ -366,7 +372,7 @@ describe("node integration", () => { }, ), ], - subject: DP.tuple([DP.string()]), + subjects: [SC.createArgument("src", DP.string())], }, subCommandExecuteSpy, ); @@ -375,7 +381,7 @@ describe("node integration", () => { "db", { description: "Database commands", - subject: [subCommand], + subjects: [subCommand], }, commandExecuteSpy, ); @@ -383,7 +389,7 @@ describe("node integration", () => { getProcessArgumentsSpy.mockReturnValue(["db", "seed", "--force=true", "users"]); await SC.exec( { - subject: command, + subjects: [command], }, rootExecuteSpy, ); diff --git a/scripts/command/argument.ts b/scripts/command/argument.ts new file mode 100644 index 0000000..f766959 --- /dev/null +++ b/scripts/command/argument.ts @@ -0,0 +1,112 @@ +import { type RemoveKind, type Kind, unwrap } from "@duplojs/utils"; +import * as EE from "@duplojs/utils/either"; +import type * as DDP from "@duplojs/utils/dataParser"; +import { createDuplojsServerUtilsKind } from "@scripts/kind"; +import type { EligibleSpec, EligibleSpecOutput } from "./types"; +import { addIssue, addIssueDataParser, type CommandError, type SymbolCommandError } from "./error"; +import { specToDataParser } from "./spec"; + +export const argumentKind = createDuplojsServerUtilsKind("command-argument"); + +export interface Argument< + GenericName extends string = string, + GenericExecuteOutput extends unknown = unknown, +> extends Kind { + readonly name: GenericName; + readonly spec: EligibleSpec; + readonly dataParser: DDP.DataParser; + readonly optional: boolean; + readonly description?: string; + execute( + argument: string | undefined, + error: CommandError, + ): Promise< + | GenericExecuteOutput + | SymbolCommandError + >; +} + +export function createArgument< + GenericName extends string, + GenericEligibleSpec extends EligibleSpec, +>( + name: GenericName, + spec: GenericEligibleSpec, + params?: { + readonly description?: string; + readonly optional?: false; + }, +): Argument>; + +export function createArgument< + GenericName extends string, + GenericEligibleSpec extends EligibleSpec, +>( + name: GenericName, + spec: GenericEligibleSpec, + params?: { + readonly description?: string; + readonly optional: boolean; + }, +): Argument | undefined>; + +export function createArgument< + GenericName extends string, + GenericEligibleSpec extends EligibleSpec, +>( + name: GenericName, + spec: GenericEligibleSpec, + params?: { + readonly description?: string; + readonly optional?: boolean; + }, +): any { + const dataParser = specToDataParser(spec); + + const self = argumentKind.setTo( + { + name, + spec, + dataParser, + description: params?.description, + optional: params?.optional ?? false, + execute: async(argument, error) => { + if (self.optional === false && argument === undefined) { + return addIssue( + error, + { + type: "argument", + target: name, + expected: `required argument ${name}`, + received: argument, + message: `Argument "${name}" is required.`, + }, + ); + } + + if (self.optional === true && argument === undefined) { + return undefined; + } + + const result = dataParser.isAsynchronous() + ? await dataParser.asyncParse(argument) + : dataParser.parse(argument); + + if (EE.isLeft(result)) { + return addIssueDataParser( + error, + unwrap(result), + { + type: "argument", + target: name, + }, + ); + } + + return unwrap(result); + }, + } satisfies RemoveKind>, + ); + + return self; +} diff --git a/scripts/command/create.ts b/scripts/command/create.ts new file mode 100644 index 0000000..e4a5f65 --- /dev/null +++ b/scripts/command/create.ts @@ -0,0 +1,306 @@ +import { type Kind, type AnyFunction, type RemoveKind, type MaybePromise, type AnyTuple, justExec, type ComputedTypeError, type IsEqual, type And, type Not, type UnionContain, type NeverCoalescing } from "@duplojs/utils"; +import * as AA from "@duplojs/utils/array"; +import * as GG from "@duplojs/utils/generator"; +import * as OO from "@duplojs/utils/object"; +import { createDuplojsServerUtilsKind } from "@scripts/kind"; +import { exitProcess } from "@scripts/common/exitProcess"; +import type { Option } from "./options"; +import { addIssue, SymbolCommandError, type CommandError } from "./error"; +import { logCommandHelp, helpOption } from "./help"; +import { type Argument } from "./argument"; +import { type ForbiddenDuplicateName } from "./types"; + +const commandKind = createDuplojsServerUtilsKind("command"); + +export function isCommands(input: unknown): input is AnyTuple { + return input instanceof Array + ? input.every(commandKind.has) + : false; +} + +type CommandSubject = { + readonly type: "subCommand"; + readonly subCommands: readonly Command[]; +} | { + readonly type: "argument"; + readonly args: readonly Argument[]; +}; + +export interface Command< + GenericName extends string = string, +> extends Kind< + typeof commandKind.definition + > { + readonly name: GenericName; + readonly description: string | null; + readonly subject: CommandSubject | null; + readonly options: readonly Option[]; + execute(args: readonly string[], error: CommandError): Promise; +} + +export type Subjects = AnyTuple | AnyTuple; + +export type ForbiddenBadOrderArguments< + GenericSubject extends readonly Subjects[number][], + GenericContainOptional extends boolean = false, +> = GenericSubject extends readonly [ + Argument, + ...infer InferredRest extends Argument[], +] + ? And<[ + IsEqual, + Not>, + ]> extends true + ? ComputedTypeError<"Optional argument can't be define before required argument"> + : ForbiddenBadOrderArguments> + : unknown; + +export interface CreateCommandParams< + GenericOptions extends AnyTuple