{"record":{"id":"a9bfee616aace6c8","repo":"pbakaus/impeccable","slug":"font-match-no-spec-at-spec-path-run-comp-spec","errorCode":null,"errorMessage":"font-match: no spec at {spec_path}; run comp-spec.mjs first\n","messagePattern":"font-match: no spec at (.+?); run comp-spec\\.mjs first\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/comp-verbs/src/font_match.rs","lineNumber":434,"sourceCode":"    }\n    let _ = std::fs::write(out, util::json_pretty(spec));\n}\n\n// ---- CLI -------------------------------------------------------------------\n\n/// `impeccable font-match --measure <id> | --rank <id> ...`\npub fn run(argv: &[String], io: &mut Io, renderer: &mut dyn FontRenderer) -> i32 {\n    let spec_path = arg_or(argv, \"spec\", SPEC_PATH).to_string();\n    let mut spec = load_spec(&resolve(io, &spec_path));\n    let measure_id = arg(argv, \"measure\");\n    let rank_id = arg(argv, \"rank\");\n    let id = measure_id.or(rank_id);\n    let Some(id) = id else {\n        io.err(\"usage: font-match.mjs --measure <text-region-id> | --rank <text-region-id> [--candidates \\\"Family:700,Family2:400,...\\\"] [--text \\\"...\\\"] [--transform uppercase] [--category sans,serif,display,handwriting,mono]\\n\");\n        return 1;\n    };\n    let Some(spec_val) = spec.as_mut() else {\n        io.err(&format!(\"font-match: no spec at {spec_path}; run comp-spec.mjs first\\n\"));\n        return 1;\n    };\n    let region = spec_val\n        .get(\"regions\")\n        .and_then(Value::as_array)\n        .and_then(|a| a.iter().find(|r| r.get(\"id\").and_then(Value::as_str) == Some(id)))\n        .cloned();\n    let Some(region) = region else {\n        let ids = spec_val\n            .get(\"regions\")\n            .and_then(Value::as_array)\n            .map(|a| a.iter().filter_map(|r| r.get(\"id\").and_then(Value::as_str)).collect::<Vec<_>>().join(\", \"))\n            .unwrap_or_default();\n        io.err(&format!(\"font-match: no region {id}; ids: {ids}\\n\"));\n        return 1;\n    };\n    let comp_file = spec_val.get(\"comp\").and_then(Value::as_str).unwrap_or(\"\").to_string();\n    let comp = match png_io::load_raster(&resolve(io, &comp_file)) {","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/pbakaus/impeccable/blob/2bc2879276c1f321a53c4ca99d3371e411329b52/crates/comp-verbs/src/font_match.rs#L416-L452","documentation":"`font-match.mjs` operates on the JSON spec produced by `comp-spec`. Before doing any region work it mutably loads the spec from the resolved `--spec` path (default SPEC_PATH); if the file is missing or empty (`spec.as_mut()` is None), it emits this message and exits 1, explicitly telling you to run `comp-spec` first. It is an ordering/dependency guard between the two verbs.","triggerScenarios":"Running `font-match.mjs --measure <id>` before `comp-spec` has ever been run in the directory; the spec file was deleted or moved; `--spec` points at a wrong path; the spec file exists but contains no JSON object (e.g. empty or `null`).","commonSituations":"Fresh checkout or new project folder where only font-match is invoked; a clean-build/clean script removed generated spec files; running font-match from a different working directory so the default relative spec path resolves elsewhere; a previous comp-spec run failed (see error 50) so the spec was never written.","solutions":["Run `comp-spec` (with `--grid`/`--regions` or `--auto`) first to generate the spec file.","Verify the spec path: pass `--spec <path>` explicitly or run from the directory containing the default spec file.","Check the spec file exists and is non-empty valid JSON (`cat <spec-path>`).","If comp-spec previously failed, fix its error (e.g. bad --regions input) so the spec actually gets written."],"exampleFix":"// before\nnode font-match.mjs --measure hero-title\n// font-match: no spec at comp-spec.json; run comp-spec.mjs first\n// after\nnode comp-spec.mjs --comp shot.png --auto\nnode font-match.mjs --measure hero-title","handlingStrategy":"validation","validationCode":"import { existsSync, statSync } from 'node:fs';\nconst specPath = specArg ?? 'comp-spec.json';\nif (!existsSync(specPath) || statSync(specPath).size === 0) {\n  throw new Error(`${specPath} missing or empty — run comp-spec first`);\n}","typeGuard":"function hasSpec(path) {\n  try { return existsSync(path) && statSync(path).size > 0; } catch { return false; }\n}","tryCatchPattern":"try {\n  runFontMatch(args);\n} catch (e) {\n  if (String(e.stderr).includes('no spec at')) {\n    runCompSpec();       // generate spec, then retry once\n    runFontMatch(args);\n  } else throw e;\n}","preventionTips":["Treat comp-spec as a hard prerequisite; generate the spec as part of any script that calls font-match.","Run all comp/font-match commands from the same working directory or pass --spec explicitly.","Add the spec file to your build artifacts list so cleans don't silently delete it mid-workflow."],"tags":["cli","missing-file","workflow-order"],"backgroundTag":"config-file-not-found","analyzedSha":"2bc2879276c1f321a53c4ca99d3371e411329b52","analyzedAt":"2026-09-08T04:51:14.109Z","contentChangedAt":"2026-09-08T04:51:14.109Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}