@@ -7,7 +7,7 @@ sync-tool-hints-from-workspace-manager.mjs
77
88How to run:
991) npm run sync:tool-hints
10- - Sync toolHints into games/metadata/games.index.metadata.json from Workspace Manager game JSON
10+ - Sync toolHints into games/metadata/games.index.metadata.json from per- game manifests
11112) node ./scripts/sync-tool-hints-from-workspace-manager.mjs --dry-run
1212 - Validate and print what would change without writing
1313*/
@@ -18,9 +18,11 @@ import { getToolRegistry } from "../tools/toolRegistry.js";
1818
1919const ROOT = process . cwd ( ) ;
2020const METADATA_PATH = path . join ( ROOT , "games" , "metadata" , "games.index.metadata.json" ) ;
21+
2122const GAME_ASSET_CATALOG_FILENAME = "workspace.asset-catalog.json" ;
2223const GAME_ASSET_CATALOG_SCHEMA = "html-js-gaming.game-asset-catalog" ;
2324const GAME_ASSET_CATALOG_VERSION = 1 ;
25+
2426const GAME_TOOLS_MANIFEST_FILENAME = "tools.manifest.json" ;
2527const GAME_TOOLS_MANIFEST_SCHEMA = "html-js-gaming.game-asset-manifest" ;
2628const GAME_TOOLS_MANIFEST_VERSION = 1 ;
@@ -59,23 +61,21 @@ function normalizeToolHints(value) {
5961 if ( ! Array . isArray ( value ) ) {
6062 return [ ] ;
6163 }
62- const out = [ ] ;
64+ const output = [ ] ;
6365 const seen = new Set ( ) ;
64- for ( const entry of value ) {
66+ value . forEach ( ( entry ) => {
6567 const token = normalizeToken ( entry ) ;
6668 if ( ! token || seen . has ( token ) ) {
67- continue ;
69+ return ;
6870 }
6971 seen . add ( token ) ;
70- out . push ( token ) ;
71- }
72- return out ;
72+ output . push ( token ) ;
73+ } ) ;
74+ return output ;
7375}
7476
7577function parseArgs ( argv ) {
76- const args = {
77- dryRun : false
78- } ;
78+ const args = { dryRun : false } ;
7979 for ( let i = 0 ; i < argv . length ; i += 1 ) {
8080 const value = argv [ i ] ;
8181 if ( value === "--dry-run" ) {
@@ -123,64 +123,122 @@ function getGameDirFromHref(gameHref) {
123123 return path . join ( ROOT , relative ) ;
124124}
125125
126- function readWorkspaceAssetKinds ( gameDir ) {
126+ function readWorkspaceAssetCatalog ( gameDir ) {
127127 if ( ! gameDir ) {
128- return [ ] ;
128+ return { valid : false , reason : "missing-game-dir" , kinds : [ ] , assetCount : 0 } ;
129129 }
130+
130131 const catalogPath = path . join ( gameDir , "assets" , GAME_ASSET_CATALOG_FILENAME ) ;
131132 if ( ! fs . existsSync ( catalogPath ) ) {
132- return [ ] ;
133- }
134- const source = readJson ( catalogPath ) ;
135- const schema = normalizeText ( source ?. schema ) ;
136- const version = Number ( source ?. version ) ;
137- if ( schema !== GAME_ASSET_CATALOG_SCHEMA || version !== GAME_ASSET_CATALOG_VERSION ) {
138- return [ ] ;
133+ return { valid : false , reason : "missing-file" , kinds : [ ] , assetCount : 0 } ;
139134 }
140- const assets = source ?. assets && typeof source . assets === "object" ? source . assets : { } ;
141- const kinds = [ ] ;
142- Object . values ( assets ) . forEach ( ( entry ) => {
143- const kind = normalizeToken ( entry ?. kind ) ;
144- if ( kind ) {
145- kinds . push ( kind ) ;
135+
136+ try {
137+ const source = readJson ( catalogPath ) ;
138+ const schema = normalizeText ( source ?. schema ) ;
139+ const version = Number ( source ?. version ) ;
140+ if ( schema !== GAME_ASSET_CATALOG_SCHEMA || version !== GAME_ASSET_CATALOG_VERSION ) {
141+ return { valid : false , reason : "invalid-schema-or-version" , kinds : [ ] , assetCount : 0 } ;
146142 }
147- } ) ;
148- return [ ...new Set ( kinds ) ] ;
143+
144+ const assets = source ?. assets && typeof source . assets === "object" ? source . assets : { } ;
145+ const kinds = [ ] ;
146+ let assetCount = 0 ;
147+
148+ Object . values ( assets ) . forEach ( ( entry ) => {
149+ if ( ! entry || typeof entry !== "object" ) {
150+ return ;
151+ }
152+ const assetPath = normalizeText ( entry . path ) ;
153+ if ( ! assetPath ) {
154+ return ;
155+ }
156+ assetCount += 1 ;
157+ const kind = normalizeToken ( entry . kind ) ;
158+ if ( kind ) {
159+ kinds . push ( kind ) ;
160+ }
161+ } ) ;
162+
163+ return {
164+ valid : true ,
165+ reason : "ok" ,
166+ kinds : [ ...new Set ( kinds ) ] ,
167+ assetCount
168+ } ;
169+ } catch {
170+ return { valid : false , reason : "invalid-json" , kinds : [ ] , assetCount : 0 } ;
171+ }
149172}
150173
151- function readToolHintsFromToolsManifest ( gameDir ) {
174+ function readGameToolsManifest ( gameDir ) {
152175 if ( ! gameDir ) {
153- return [ ] ;
154- }
155- const toolsManifestPath = path . join ( gameDir , "assets" , GAME_TOOLS_MANIFEST_FILENAME ) ;
156- if ( ! fs . existsSync ( toolsManifestPath ) ) {
157- return [ ] ;
176+ return { valid : false , reason : "missing-game-dir" , hints : [ ] } ;
158177 }
159- const source = readJson ( toolsManifestPath ) ;
160- const schema = normalizeText ( source ?. schema ) ;
161- const version = Number ( source ?. version ) ;
162- if ( schema !== GAME_TOOLS_MANIFEST_SCHEMA || version !== GAME_TOOLS_MANIFEST_VERSION ) {
163- return [ ] ;
178+
179+ const manifestPath = path . join ( gameDir , "assets" , GAME_TOOLS_MANIFEST_FILENAME ) ;
180+ if ( ! fs . existsSync ( manifestPath ) ) {
181+ return { valid : false , reason : "missing-file" , hints : [ ] } ;
164182 }
165- const domains = source ?. domains && typeof source . domains === "object" ? source . domains : { } ;
166- const hints = [ ] ;
167- let hasDomainRecords = false ;
168- Object . values ( domains ) . forEach ( ( records ) => {
169- if ( ! Array . isArray ( records ) || records . length === 0 ) {
170- return ;
183+
184+ try {
185+ const source = readJson ( manifestPath ) ;
186+ const schema = normalizeText ( source ?. schema ) ;
187+ const version = Number ( source ?. version ) ;
188+ if ( schema !== GAME_TOOLS_MANIFEST_SCHEMA || version !== GAME_TOOLS_MANIFEST_VERSION ) {
189+ return { valid : false , reason : "invalid-schema-or-version" , hints : [ ] } ;
171190 }
172- hasDomainRecords = true ;
173- records . forEach ( ( record ) => {
174- const sourceToolId = normalizeToken ( record ?. sourceToolId ) ;
175- if ( sourceToolId ) {
176- hints . push ( sourceToolId ) ;
191+
192+ const domains = source ?. domains && typeof source . domains === "object" ? source . domains : { } ;
193+ const hints = [ ] ;
194+ let hasDomainRecords = false ;
195+
196+ Object . values ( domains ) . forEach ( ( records ) => {
197+ if ( ! Array . isArray ( records ) || records . length === 0 ) {
198+ return ;
177199 }
200+ hasDomainRecords = true ;
201+ records . forEach ( ( record ) => {
202+ const sourceToolId = normalizeToken ( record ?. sourceToolId ) ;
203+ if ( sourceToolId ) {
204+ hints . push ( sourceToolId ) ;
205+ }
206+ } ) ;
178207 } ) ;
179- } ) ;
180- if ( hasDomainRecords ) {
181- hints . push ( "asset-pipeline-tool" ) ;
208+
209+ if ( hasDomainRecords ) {
210+ hints . push ( "asset-pipeline-tool" ) ;
211+ }
212+
213+ return {
214+ valid : true ,
215+ reason : "ok" ,
216+ hints : normalizeToolHints ( hints )
217+ } ;
218+ } catch {
219+ return { valid : false , reason : "invalid-json" , hints : [ ] } ;
182220 }
183- return normalizeToolHints ( hints ) ;
221+ }
222+
223+ function deriveToolHintsFromManifests ( catalogInfo , toolsManifestInfo ) {
224+ const derived = [ ] ;
225+
226+ if ( catalogInfo . valid ) {
227+ catalogInfo . kinds . forEach ( ( kind ) => {
228+ const mapped = KIND_TO_TOOL_HINTS [ kind ] || [ ] ;
229+ mapped . forEach ( ( toolId ) => derived . push ( toolId ) ) ;
230+ } ) ;
231+
232+ if ( catalogInfo . assetCount > 0 ) {
233+ derived . push ( "asset-browser" ) ;
234+ }
235+ }
236+
237+ if ( toolsManifestInfo . valid ) {
238+ toolsManifestInfo . hints . forEach ( ( toolId ) => derived . push ( toolId ) ) ;
239+ }
240+
241+ return normalizeToolHints ( derived ) ;
184242}
185243
186244function syncToolHints ( metadata ) {
@@ -192,31 +250,27 @@ function syncToolHints(metadata) {
192250 const gameId = normalizeText ( game ?. id ) || "<unknown>" ;
193251 const gameDir = getGameDirFromHref ( game ?. href ) ;
194252 const existing = normalizeToolHints ( game ?. toolHints ) ;
195- const derivedFromKinds = [ ] ;
196- const hasCatalog = ! ! gameDir && fs . existsSync ( path . join ( gameDir , "assets" , GAME_ASSET_CATALOG_FILENAME ) ) ;
197253
198- readWorkspaceAssetKinds ( gameDir ) . forEach ( ( kind ) => {
199- const mapped = KIND_TO_TOOL_HINTS [ kind ] || [ ] ;
200- mapped . forEach ( ( toolId ) => derivedFromKinds . push ( toolId ) ) ;
201- } ) ;
254+ if ( ! gameDir ) {
255+ warnings . push ( `${ gameId } : skipped derivation (missing/invalid href)` ) ;
256+ continue ;
257+ }
258+
259+ const catalogInfo = readWorkspaceAssetCatalog ( gameDir ) ;
260+ const toolsManifestInfo = readGameToolsManifest ( gameDir ) ;
202261
203- const derivedFromToolsManifest = readToolHintsFromToolsManifest ( gameDir ) ;
204- if ( hasCatalog ) {
205- derivedFromKinds . push ( "asset-browser" ) ;
262+ const hasAnyValidManifest = catalogInfo . valid || toolsManifestInfo . valid ;
263+ if ( ! hasAnyValidManifest ) {
264+ warnings . push ( `${ gameId } : no valid manifest source (${ GAME_ASSET_CATALOG_FILENAME } / ${ GAME_TOOLS_MANIFEST_FILENAME } )` ) ;
265+ continue ;
206266 }
207- const derived = normalizeToolHints ( [ ... derivedFromKinds , ... derivedFromToolsManifest ] ) ;
208- const next = hasCatalog ? derived : existing ;
267+
268+ const next = deriveToolHintsFromManifests ( catalogInfo , toolsManifestInfo ) ;
209269 const invalid = next . filter ( ( toolId ) => ! knownToolIds . has ( toolId ) ) ;
210270 if ( invalid . length > 0 ) {
211271 throw new Error ( `${ gameId } : unknown tool id(s): ${ invalid . join ( ", " ) } ` ) ;
212272 }
213273
214- if ( ! gameDir ) {
215- warnings . push ( `${ gameId } : skipped derivation (missing/invalid href)` ) ;
216- } else if ( ! fs . existsSync ( path . join ( gameDir , "assets" , GAME_ASSET_CATALOG_FILENAME ) ) ) {
217- warnings . push ( `${ gameId } : no ${ GAME_ASSET_CATALOG_FILENAME } ` ) ;
218- }
219-
220274 if ( JSON . stringify ( existing ) !== JSON . stringify ( next ) || ! Array . isArray ( game . toolHints ) ) {
221275 game . toolHints = next ;
222276 updated += 1 ;
@@ -237,11 +291,9 @@ function main() {
237291
238292 const warningText = result . warnings . length ? ` warnings=${ result . warnings . length } ` : "" ;
239293 console . log ( `OK updated=${ result . updated } ${ warningText } dryRun=${ args . dryRun ? "true" : "false" } ` ) ;
240- if ( result . warnings . length ) {
241- result . warnings . forEach ( ( warning ) => {
242- console . log ( `WARN ${ warning } ` ) ;
243- } ) ;
244- }
294+ result . warnings . forEach ( ( warning ) => {
295+ console . log ( `WARN ${ warning } ` ) ;
296+ } ) ;
245297}
246298
247299try {
0 commit comments