From 3b4a47272b589522dcd1829a57f2fe2de4ea332b Mon Sep 17 00:00:00 2001 From: Dicky Moore Date: Mon, 8 Dec 2025 21:03:31 +0000 Subject: [PATCH 1/2] fix: normalize workflow manifest schema --- .../installers/lib/core/manifest-generator.js | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 71b23605..5f2bb325 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -581,6 +581,11 @@ class ManifestGenerator { */ async writeWorkflowManifest(cfgDir) { const csvPath = path.join(cfgDir, 'workflow-manifest.csv'); + const escapeCsv = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`; + const parseCsvLine = (line) => { + const columns = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || []; + return columns.map((c) => c.replaceAll(/^"|"$/g, '')); + }; // Read existing manifest to preserve entries const existingEntries = new Map(); @@ -592,18 +597,21 @@ class ManifestGenerator { for (let i = 1; i < lines.length; i++) { const line = lines[i]; if (line) { - // Parse CSV (simple parsing assuming no commas in quoted fields) - const parts = line.split('","'); + const parts = parseCsvLine(line); if (parts.length >= 4) { - const name = parts[0].replace(/^"/, ''); - const module = parts[2]; - existingEntries.set(`${module}:${name}`, line); + const [name, description, module, workflowPath] = parts; + existingEntries.set(`${module}:${name}`, { + name, + description, + module, + path: workflowPath, + }); } } } } - // Create CSV header - removed standalone column as ALL workflows now generate commands + // Create CSV header - standalone column removed, everything is canonicalized to 4 columns let csv = 'name,description,module,path\n'; // Combine existing and new workflows @@ -617,12 +625,23 @@ class ManifestGenerator { // Add/update new workflows for (const workflow of this.workflows) { const key = `${workflow.module}:${workflow.name}`; - allWorkflows.set(key, `"${workflow.name}","${workflow.description}","${workflow.module}","${workflow.path}"`); + allWorkflows.set(key, { + name: workflow.name, + description: workflow.description, + module: workflow.module, + path: workflow.path, + }); } // Write all workflows for (const [, value] of allWorkflows) { - csv += value + '\n'; + const row = [ + escapeCsv(value.name), + escapeCsv(value.description), + escapeCsv(value.module), + escapeCsv(value.path), + ].join(','); + csv += row + '\n'; } await fs.writeFile(csvPath, csv); From e6ff8ed23ff6fec88c367a7bf47813280137af04 Mon Sep 17 00:00:00 2001 From: Dicky Moore Date: Mon, 8 Dec 2025 21:54:13 +0000 Subject: [PATCH 2/2] fix: escape workflow manifest values safely --- tools/cli/installers/lib/core/manifest-generator.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 5f2bb325..6ad7b790 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -581,7 +581,7 @@ class ManifestGenerator { */ async writeWorkflowManifest(cfgDir) { const csvPath = path.join(cfgDir, 'workflow-manifest.csv'); - const escapeCsv = (value) => `"${String(value ?? '').replace(/"/g, '""')}"`; + const escapeCsv = (value) => `"${String(value ?? '').replaceAll('"', '""')}"`; const parseCsvLine = (line) => { const columns = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g) || []; return columns.map((c) => c.replaceAll(/^"|"$/g, '')); @@ -635,12 +635,7 @@ class ManifestGenerator { // Write all workflows for (const [, value] of allWorkflows) { - const row = [ - escapeCsv(value.name), - escapeCsv(value.description), - escapeCsv(value.module), - escapeCsv(value.path), - ].join(','); + const row = [escapeCsv(value.name), escapeCsv(value.description), escapeCsv(value.module), escapeCsv(value.path)].join(','); csv += row + '\n'; }