Skip to content

Commit e8bc2df

Browse files
committed
feat: mark padroneEnv and padroneConfig as async at the type level
Add WithAsync<T> utility type. padroneEnv and padroneConfig now return WithAsync<T> so eval()/cli() correctly type their results as Promises.
1 parent 46cf13f commit e8bc2df

11 files changed

Lines changed: 110 additions & 79 deletions

File tree

.changeset/env-config-async.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'padrone': minor
3+
---
4+
5+
Mark `padroneEnv` and `padroneConfig` as async at the type level. Extensions now return `WithAsync<T>` so `eval()` and `cli()` correctly return `Promise` when used.

packages/padrone/src/extension/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ConfigError } from '../core/errors.ts';
44
import { defineInterceptor } from '../core/interceptors.ts';
55
import { thenMaybe } from '../core/results.ts';
66
import type { AnyPadroneBuilder, CommandTypesBase, InterceptorValidateContext } from '../types/index.ts';
7+
import type { WithAsync } from '../util/type-utils.ts';
78
import { getRootCommand } from '../util/utils.ts';
89

910
// ── Types ────────────────────────────────────────────────────────────────
@@ -189,7 +190,7 @@ function loadConfig(
189190
* }))
190191
* ```
191192
*/
192-
export function padroneConfig(options?: PadroneConfigOptions): <T extends CommandTypesBase>(builder: T) => T {
193+
export function padroneConfig(options?: PadroneConfigOptions): <T extends CommandTypesBase>(builder: T) => WithAsync<T> {
193194
if (options?.disabled) {
194195
const disabled = defineInterceptor({ id: 'padrone:config', name: 'padrone:config', order: -999, disabled: true }, () => ({}));
195196
return ((builder: AnyPadroneBuilder) => builder.intercept(disabled)) as any;

packages/padrone/src/extension/env.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { thenMaybe } from '../core/results.ts';
55
import type { AnyPadroneBuilder, CommandTypesBase, InterceptorValidateContext } from '../types/index.ts';
66
import type { LoadEnvFilesOptions } from '../util/dotenv.ts';
77
import { loadEnvFiles } from '../util/dotenv.ts';
8+
import type { WithAsync } from '../util/type-utils.ts';
89

910
// ── Types ────────────────────────────────────────────────────────────────
1011

@@ -53,13 +54,13 @@ function isSchema(value: unknown): value is StandardSchemaV1 {
5354
*
5455
* Env values have lower precedence than CLI args and stdin, but higher than config files.
5556
*/
56-
export function padroneEnv(schema: StandardSchemaV1): <T extends CommandTypesBase>(builder: T) => T;
57-
export function padroneEnv(schema: StandardSchemaV1, options: PadroneEnvOptions): <T extends CommandTypesBase>(builder: T) => T;
58-
export function padroneEnv(options: PadroneEnvOptions): <T extends CommandTypesBase>(builder: T) => T;
57+
export function padroneEnv(schema: StandardSchemaV1): <T extends CommandTypesBase>(builder: T) => WithAsync<T>;
58+
export function padroneEnv(schema: StandardSchemaV1, options: PadroneEnvOptions): <T extends CommandTypesBase>(builder: T) => WithAsync<T>;
59+
export function padroneEnv(options: PadroneEnvOptions): <T extends CommandTypesBase>(builder: T) => WithAsync<T>;
5960
export function padroneEnv(
6061
schemaOrOptions: StandardSchemaV1 | PadroneEnvOptions,
6162
maybeOptions?: PadroneEnvOptions,
62-
): <T extends CommandTypesBase>(builder: T) => T {
63+
): <T extends CommandTypesBase>(builder: T) => WithAsync<T> {
6364
const schema = isSchema(schemaOrOptions) ? schemaOrOptions : undefined;
6465
const options = isSchema(schemaOrOptions) ? maybeOptions : schemaOrOptions;
6566
const hasFiles = options?.modes !== undefined;

packages/padrone/src/extension/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export type { WithAsync } from '../util/type-utils.ts';
12
export type { PadroneAutoOutputOptions } from './auto-output.ts';
23
export { padroneAutoOutput } from './auto-output.ts';
34
export { padroneColor } from './color.ts';

packages/padrone/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export type {
3838
PadroneTracer,
3939
PadroneTracingConfig,
4040
VersionCommand,
41+
WithAsync,
4142
WithCompletion,
4243
WithHelp,
4344
WithLogger,

packages/padrone/src/util/type-utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,28 @@ export type WithInterceptor<T, TProvides> = T extends {
210210
: PadroneBuilder<PN, N, PaN, A, R, C, any, AS, CTX, CTXP & TProvides>
211211
: T;
212212

213+
/**
214+
* Utility type for extensions that force the builder/program into async mode.
215+
* Sets `TAsync` to `true` while preserving all other type params.
216+
*/
217+
export type WithAsync<T> = T extends {
218+
'~types': {
219+
programName: infer PN extends string;
220+
name: infer N extends string;
221+
parentName: infer PaN extends string;
222+
argsSchema: infer A extends PadroneSchema;
223+
result: infer R;
224+
commands: infer C extends [...AnyPadroneCommand[]];
225+
async: any;
226+
context: infer CTX;
227+
contextProvided: infer CTXP;
228+
};
229+
}
230+
? T extends { run: any }
231+
? PadroneProgram<PN, N, PaN, A, R, C, any, true, CTX, CTXP>
232+
: PadroneBuilder<PN, N, PaN, A, R, C, any, true, CTX, CTXP>
233+
: T;
234+
213235
export type PickCommandByName<
214236
TCommands extends AnyPadroneCommand[],
215237
TName extends string | AnyPadroneCommand,

packages/padrone/tests/cli-validation.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ describe('CLI validation improvements', () => {
194194
expect(result.error).toBeInstanceOf(ValidationError);
195195
});
196196

197-
it('should not flag framework keys like --config when config extension is used', () => {
197+
it('should not flag framework keys like --config when config extension is used', async () => {
198198
const program = createPadrone('app').command('connect', (c) =>
199199
c
200200
.arguments(z.object({ host: z.string().optional() }))
@@ -203,7 +203,7 @@ describe('CLI validation improvements', () => {
203203
);
204204

205205
// --config is consumed by the config extension, should not be flagged as unknown
206-
const result = program.eval('connect --config some-config.json --host localhost');
206+
const result = await program.eval('connect --config some-config.json --host localhost');
207207
expect(result.argsResult?.issues).toBeUndefined();
208208
expect(result.args?.host).toBe('localhost');
209209
});

0 commit comments

Comments
 (0)