diff --git a/scripts/generate-manifest.js b/scripts/generate-manifest.js index d0b4d241..e5e6a7b4 100644 --- a/scripts/generate-manifest.js +++ b/scripts/generate-manifest.js @@ -27,6 +27,70 @@ function parseArgs(argv) { }; } +function parseScalar(rawValue) { + const value = rawValue.trim(); + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'null') return null; + if (/^-?\d+$/.test(value)) return Number(value); + if (value.startsWith('[') && value.endsWith(']')) { + const inner = value.slice(1, -1).trim(); + if (!inner) return []; + return inner.split(',').map(entry => entry.trim().replace(/^"|"$/g, '')); + } + if (value.startsWith('"') && value.endsWith('"')) { + return value.slice(1, -1); + } + return value; +} + +function parseMetadataYaml(content) { + const result = {}; + const stack = []; + + for (const rawLine of content.split(/\r?\n/)) { + if (!rawLine.trim() || rawLine.trim().startsWith('#')) continue; + + const indent = rawLine.match(/^\s*/)[0].length; + const trimmed = rawLine.trim(); + + if (trimmed.startsWith('- ')) { + const currentPath = stack.map(entry => entry.key).join('.'); + const listValue = parseScalar(trimmed.slice(2)); + + if (currentPath === 'tags') { + result.tags = Array.isArray(result.tags) ? result.tags : []; + result.tags.push(String(listValue)); + } + continue; + } + + while (stack.length > 0 && indent <= stack[stack.length - 1].indent) { + stack.pop(); + } + + const separator = trimmed.indexOf(':'); + if (separator < 0) continue; + + const key = trimmed.slice(0, separator).trim(); + const rawValue = trimmed.slice(separator + 1); + const pathParts = [...stack.map(entry => entry.key), key]; + const dottedPath = pathParts.join('.'); + + if (!rawValue.trim()) { + stack.push({ key, indent }); + if (dottedPath === 'tags') { + result.tags = Array.isArray(result.tags) ? result.tags : []; + } + continue; + } + + setNested(result, pathParts, parseScalar(rawValue)); + } + + return result; +} + function listMetadataFiles(dir, results = []) { if (!fs.existsSync(dir)) return results; diff --git a/scripts/validate-metadata.js b/scripts/validate-metadata.js index 20d5b2e2..d54a6e46 100644 --- a/scripts/validate-metadata.js +++ b/scripts/validate-metadata.js @@ -66,22 +66,6 @@ function parseScalar(rawValue) { return value; } -function setNested(target, dottedPath, value) { - const parts = dottedPath.split('.'); - const blockedKeys = new Set(['__proto__', 'constructor', 'prototype']); - if (parts.some((part) => blockedKeys.has(part))) return; - - let cursor = target; - for (let index = 0; index < parts.length - 1; index += 1) { - const part = parts[index]; - if (!cursor[part] || typeof cursor[part] !== 'object' || Array.isArray(cursor[part])) { - cursor[part] = {}; - } - cursor = cursor[part]; - } - cursor[parts[parts.length - 1]] = value; -} - function parseMetadataYaml(content) { const result = {}; const stack = []; @@ -111,7 +95,8 @@ function parseMetadataYaml(content) { const key = trimmed.slice(0, separator).trim(); const rawValue = trimmed.slice(separator + 1); - const dottedPath = stack.length > 0 ? stack[stack.length - 1].path + '.' + key : key; + const pathParts = [...stack.map((entry) => entry.key), key]; + const dottedPath = pathParts.join('.'); if (!rawValue.trim()) { stack.push({ key, indent, path: dottedPath }); @@ -121,7 +106,7 @@ function parseMetadataYaml(content) { continue; } - setNested(result, dottedPath, parseScalar(rawValue)); + setNested(result, pathParts, parseScalar(rawValue)); } return result; diff --git a/scripts/validate-metadata.test.js b/scripts/validate-metadata.test.js index 2e9270bf..68344f8d 100644 --- a/scripts/validate-metadata.test.js +++ b/scripts/validate-metadata.test.js @@ -369,3 +369,11 @@ test('listMetadataFiles', async (t) => { assert.deepStrictEqual(result, []); }); }); + +test('listMetadataFiles', async (t) => { + await t.test('returns empty array for non-existent directory', () => { + const nonExistentPath = '/path/that/does/not/exist/for/sure/12345'; + const result = listMetadataFiles(nonExistentPath); + assert.deepStrictEqual(result, []); + }); +});