Skip to content

Commit b4d6bbc

Browse files
author
DavidQ
committed
fix(tool-hints): derive strictly from workspace and tools manifests instead of copying all active tools
1 parent 9a69c48 commit b4d6bbc

1 file changed

Lines changed: 127 additions & 75 deletions

File tree

scripts/sync-tool-hints-from-workspace-manager.mjs

Lines changed: 127 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ sync-tool-hints-from-workspace-manager.mjs
77
88
How to run:
99
1) 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
1111
2) 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

1919
const ROOT = process.cwd();
2020
const METADATA_PATH = path.join(ROOT, "games", "metadata", "games.index.metadata.json");
21+
2122
const GAME_ASSET_CATALOG_FILENAME = "workspace.asset-catalog.json";
2223
const GAME_ASSET_CATALOG_SCHEMA = "html-js-gaming.game-asset-catalog";
2324
const GAME_ASSET_CATALOG_VERSION = 1;
25+
2426
const GAME_TOOLS_MANIFEST_FILENAME = "tools.manifest.json";
2527
const GAME_TOOLS_MANIFEST_SCHEMA = "html-js-gaming.game-asset-manifest";
2628
const 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

7577
function 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

186244
function 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

247299
try {

0 commit comments

Comments
 (0)