@@ -15,6 +15,7 @@ import { option } from "../lib/option.ts";
1515import { schema } from "../lib/param.ts" ;
1616import { parse } from "../lib/parse.ts" ;
1717import { printHelp } from "../lib/print.ts" ;
18+ import { cli } from "../lib/read.ts" ;
1819import { route , routes , version } from "../lib/route.ts" ;
1920import type {
2021 AnyRoute ,
@@ -147,6 +148,190 @@ describe("dynamic()", () => {
147148 > ( true ) ;
148149 } ) ;
149150
151+ it ( "preserves a concrete command supplied through dynamic input" , ( ) => {
152+ let plugin = command (
153+ name ( "serve" ) ,
154+ option ( name ( "port" ) , schema ( type ( "number" ) ) ) ,
155+ ) ;
156+ type Plugin = typeof plugin ;
157+ let app = command (
158+ name ( "simulacrum" ) ,
159+ dynamic ( ( plugin : Plugin ) => extend ( routes ( plugin ) ) ) ,
160+ ) ;
161+ type Next = ContinuationOf < typeof app > ;
162+
163+ expectType < Equal < RequirementOf < typeof app > , Plugin > > ( true ) ;
164+ expectType < Equal < ChildrenOf < Next > [ 0 ] [ "name" ] , "serve" > > ( true ) ;
165+ expectType < Equal < ModelOf < ChildrenOf < Next > [ 0 ] > , { port : number } > > ( true ) ;
166+
167+ let first = parse ( app , {
168+ argv : [ "serve" , "--port" , "4100" ] ,
169+ } ) ;
170+ assertIncrement ( first , { } ) ;
171+
172+ let result = first . resume ( { ok : true , value : plugin } ) ;
173+ expect ( result ) . toMatchObject ( {
174+ ok : true ,
175+ method : "execute" ,
176+ route : "/serve" ,
177+ model : { port : 4100 } ,
178+ } ) ;
179+ } ) ;
180+
181+ it ( "preserves the resume boundary for runtime-sized command extensions" , ( ) => {
182+ let app = command (
183+ name ( "simulacrum" ) ,
184+ dynamic ( ( plugins : PluginSet ) => extend ( routes ( ...plugins . commands ) ) ) ,
185+ ) ;
186+
187+ expectType < Equal < RequirementOf < typeof app > , PluginSet > > ( true ) ;
188+ expectType < Equal < ParseIncrement < typeof app > [ "model" ] , { } > > ( true ) ;
189+
190+ let first = parse ( app , { argv : [ "serve" ] } ) ;
191+ assertIncrement ( first , { } ) ;
192+
193+ let result = first . resume ( {
194+ ok : true ,
195+ value : { commands : [ command ( name ( "serve" ) ) ] } ,
196+ } ) ;
197+ expect ( result ) . toMatchObject ( {
198+ ok : true ,
199+ method : "execute" ,
200+ route : "/serve" ,
201+ } ) ;
202+ } ) ;
203+
204+ it ( "preserves the resume boundary for runtime-sized extensions" , ( ) => {
205+ let app = command (
206+ name ( "xmd" ) ,
207+ option ( name ( "path" ) , schema ( type ( "string" ) ) ) ,
208+ dynamic ( ( declared : readonly string [ ] ) =>
209+ extend (
210+ ...declared . map ( ( property ) =>
211+ option ( name ( property ) , schema ( type ( "string" ) ) )
212+ ) ,
213+ )
214+ ) ,
215+ option ( name ( "raw" ) , schema ( type ( "string | undefined" ) ) ) ,
216+ ) ;
217+
218+ expectType < Equal < RequirementOf < typeof app > , readonly string [ ] > > ( true ) ;
219+ expectType <
220+ Equal < ParseIncrement < typeof app > [ "model" ] , {
221+ path : string ;
222+ } >
223+ > ( true ) ;
224+
225+ type Continued = ModelOf < ContinuationOf < typeof app > > ;
226+ expectType < Equal < Continued [ "path" ] , string > > ( true ) ;
227+ expectType < Equal < Continued [ "raw" ] , string | undefined > > ( true ) ;
228+
229+ let first = parse ( app , {
230+ argv : [ "--path" , "doc.md" , "--author" , "Ada" , "--raw" , "true" ] ,
231+ } ) ;
232+ assertIncrement ( first , { path : "doc.md" } ) ;
233+
234+ let result = first . resume ( { ok : true , value : [ "author" ] } ) ;
235+ expect ( result ) . toMatchObject ( {
236+ ok : true ,
237+ model : { path : "doc.md" , author : "Ada" , raw : "true" } ,
238+ } ) ;
239+ if ( ! result . ok || result . method !== "execute" ) {
240+ throw new Error ( "expected execute result" ) ;
241+ }
242+ expectType <
243+ Equal < typeof result . model , {
244+ path : string ;
245+ raw : string | undefined ;
246+ } >
247+ > ( true ) ;
248+ } ) ;
249+
250+ it ( "preserves the boundary for generated options with explicit CLI readers" , ( ) => {
251+ let generated = ( property : string ) =>
252+ option (
253+ name ( property ) ,
254+ cli ( [ `--${ property } ` ] ) ,
255+ schema ( type ( "string | undefined" ) ) ,
256+ ) ;
257+ let app = command (
258+ name ( "xmd" ) ,
259+ option (
260+ name ( "file" ) ,
261+ cli ( [ "--file" ] ) ,
262+ schema ( type ( "string" ) ) ,
263+ ) ,
264+ dynamic ( ( frontmatter : Frontmatter ) =>
265+ extend ( ...frontmatter . properties . map ( generated ) )
266+ ) ,
267+ option (
268+ name ( "raw" ) ,
269+ cli ( [ "--raw" ] , { switch : true } ) ,
270+ schema ( type ( "boolean | undefined" ) ) ,
271+ ) ,
272+ ) ;
273+
274+ expectType < Equal < RequirementOf < typeof app > , Frontmatter > > ( true ) ;
275+
276+ let step = parse ( app , {
277+ argv : [ "--file" , "doc.md" , "--props-name" , "Ada" ] ,
278+ } ) ;
279+ if ( ! step . ok || ! ( "resume" in step ) ) throw new Error ( "expected increment" ) ;
280+ expectType < Equal < typeof step . model , { file : string } > > ( true ) ;
281+
282+ let result = step . resume ( {
283+ ok : true ,
284+ value : { properties : [ "props-name" ] } ,
285+ } ) ;
286+ if ( ! result . ok || result . method !== "execute" ) {
287+ throw new Error ( "expected execute result" ) ;
288+ }
289+ expect ( result ) . toMatchObject ( {
290+ model : { file : "doc.md" , "props-name" : "Ada" } ,
291+ } ) ;
292+ expectType <
293+ Equal < typeof result . model , {
294+ file : string ;
295+ raw : boolean | undefined ;
296+ } >
297+ > ( true ) ;
298+ } ) ;
299+
300+ it ( "preserves the resume boundary for unknown requirements" , ( ) => {
301+ let app = command (
302+ name ( "xmd" ) ,
303+ option ( name ( "path" ) , schema ( type ( "string" ) ) ) ,
304+ dynamic ( ( input : unknown ) => {
305+ let declared = Array . isArray ( input )
306+ ? input . filter ( ( value ) : value is string => typeof value === "string" )
307+ : [ ] ;
308+ return extend (
309+ ...declared . map ( ( property ) =>
310+ option ( name ( property ) , schema ( type ( "string" ) ) )
311+ ) ,
312+ ) ;
313+ } ) ,
314+ ) ;
315+
316+ expectType < Equal < RequirementOf < typeof app > , unknown > > ( true ) ;
317+ expectType <
318+ Equal < ParseIncrement < typeof app > [ "model" ] , {
319+ path : string ;
320+ } >
321+ > ( true ) ;
322+
323+ let first = parse ( app , {
324+ argv : [ "--path" , "doc.md" , "--author" , "Ada" ] ,
325+ } ) ;
326+ assertIncrement ( first , { path : "doc.md" } ) ;
327+
328+ let result = first . resume ( { ok : true , value : [ "author" ] } ) ;
329+ expect ( result ) . toMatchObject ( {
330+ ok : true ,
331+ model : { path : "doc.md" , author : "Ada" } ,
332+ } ) ;
333+ } ) ;
334+
150335 it ( "preserves route-level controls across phases" , ( ) => {
151336 let app = command (
152337 name ( "simulacrum" ) ,
@@ -797,6 +982,14 @@ interface Plugins {
797982 readonly names : readonly string [ ] ;
798983}
799984
985+ interface PluginSet {
986+ readonly commands : readonly AnyRoute [ ] ;
987+ }
988+
989+ interface Frontmatter {
990+ readonly properties : readonly string [ ] ;
991+ }
992+
800993interface Services {
801994 readonly count : number ;
802995}
0 commit comments