@@ -23,6 +23,7 @@ import {
2323 buildPkMap ,
2424 buildRelationMap ,
2525 resolveDocsConfig ,
26+ apiLabel ,
2627} from "@metaobjectsdev/codegen-ts" ;
2728import type {
2829 GenContext ,
@@ -265,24 +266,33 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
265266 const emit : EmittedFile [ ] = [ ] ;
266267 let modelFiles : EmittedFile [ ] = [ ] ;
267268
268- // Both surfaces emitted together → cross-link them. The api surface only
269- // materializes with a loadable gen config, so guard on that too: when both are
270- // requested AND the config loaded, the model pages cross-link to `api/` and the
271- // api pages cross-link back to the model root. Otherwise each surface emits its
272- // historical, byte-identical standalone output.
273- const bothSurfaces =
274- docsCfg . surfaces . includes ( "model" ) &&
275- docsCfg . surfaces . includes ( "api" ) &&
276- loadedConfig !== undefined ;
269+ // The declared api surfaces, each tagged with its human label (apiLabel maps
270+ // the language key → label; never hardcode labels). The model page links one
271+ // reference per declared surface — across ALL ports, not just the surfaces
272+ // THIS command emits — so a polyglot model page points at every port's docs.
273+ const labeled = docsCfg . apiSurfaces . map ( ( s ) => ( { ...s , label : apiLabel ( s . lang ) } ) ) ;
274+
275+ // The api surface only materializes with a loadable gen config (there is
276+ // nothing generated to document otherwise), so gate api emit + cross-linking
277+ // on that. When false, the model surface emits its historical standalone form.
278+ const apiSelected = docsCfg . surfaces . includes ( "api" ) && loadedConfig !== undefined ;
277279
278280 // MODEL surface — the neutral metadata pages (<Entity>.md / <Template>.md +
279281 // README.md). Keep the render-error handling tight around docsFile() only.
280282 if ( docsCfg . surfaces . includes ( "model" ) ) {
283+ // When api is selected, link EVERY declared surface from the model page (so a
284+ // polyglot model page references each port's api docs). Otherwise no api refs.
285+ const modelOpts = apiSelected
286+ ? {
287+ apiSurfaces : labeled . map ( ( { label, subDir, baseUrl } ) => ( {
288+ label,
289+ subDir,
290+ ...( baseUrl ? { baseUrl } : { } ) ,
291+ } ) ) ,
292+ }
293+ : { } ;
281294 try {
282- modelFiles = await ( bothSurfaces
283- ? docsFile ( { apiSurface : { subDir : "api" } } )
284- : docsFile ( )
285- ) . generate ( ctx ) ;
295+ modelFiles = await docsFile ( modelOpts ) . generate ( ctx ) ;
286296 } catch ( err ) {
287297 const msg = ( err as Error ) . message ;
288298 // Duplicate output path (silent-overwrite backstop): the generator already
@@ -311,16 +321,24 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
311321 }
312322
313323 // API surface — the SDK reference for the GENERATED REST surface, side by side
314- // under api/. Only meaningful with a loadable gen config (there is nothing
315- // generated to document otherwise), so skip gracefully when absent.
324+ // under each surface's subDir. THIS command only OWNS the surfaces it can
325+ // generate — i.e. its own port (lang "ts"). Surfaces owned by other ports are
326+ // linked (above) but produced by running that port's docs command; we just log
327+ // a pointer. Only meaningful with a loadable gen config, so skip when absent.
316328 let apiFiles : EmittedFile [ ] = [ ] ;
317329 if ( docsCfg . surfaces . includes ( "api" ) ) {
318330 if ( loadedConfig !== undefined ) {
319331 try {
320- apiFiles = await apiDocsFile ( {
321- subDir : "api" ,
322- modelSurface : docsCfg . surfaces . includes ( "model" ) ,
323- } ) . generate ( ctx ) ;
332+ // Emit every surface THIS port owns (loop so it generalizes beyond the
333+ // single ts surface owned today).
334+ for ( const s of labeled . filter ( ( s ) => s . lang === "ts" ) ) {
335+ apiFiles . push (
336+ ...( await apiDocsFile ( {
337+ subDir : s . subDir ,
338+ modelSurface : docsCfg . surfaces . includes ( "model" ) ,
339+ } ) . generate ( ctx ) ) ,
340+ ) ;
341+ }
324342 } catch ( err ) {
325343 const msg = ( err as Error ) . message ;
326344 if ( msg . startsWith ( "docs: duplicate output path" ) ) {
@@ -331,6 +349,13 @@ export async function docsCommand(args: string[], cwd: string): Promise<number>
331349 return 1 ;
332350 }
333351 emit . push ( ...apiFiles ) ;
352+ // Surfaces owned by other ports: link only, with a pointer to where they
353+ // get produced.
354+ for ( const s of labeled . filter ( ( s ) => s . lang !== "ts" ) ) {
355+ log . info (
356+ `meta docs: api surface '${ s . lang } ' (${ s . subDir } ) is produced by that port's docs command — run it to populate those pages.` ,
357+ ) ;
358+ }
334359 } else if ( hasConfig ) {
335360 // Config present but failed to load — already warned above; don't claim an
336361 // api surface we couldn't build.
0 commit comments