{"record":{"id":"914b347149ddf4ef","repo":"santifer/career-ops","slug":"plugin-name-must-match-a-z0-9-got-name","errorCode":null,"errorMessage":"plugin name must match [a-z0-9-] (got \"${name}\")","messagePattern":"plugin name must match \\[a-z0-9-\\] \\(got \"(.+?)\"\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugin-install.mjs","lineNumber":121,"sourceCode":" * audit is static. Returns problems (empty = clean).\n * @returns {string[]}\n */\nexport function auditRegistryEntry(url, sha, expectId) {\n  let parsed;\n  try { parsed = parseRepoArg(url); } catch (e) { return [e.message]; }\n  if (expectId && parsed.id !== expectId) return [`repo \"${url}\" → id \"${parsed.id}\" but registry id is \"${expectId}\"`];\n  let dir;\n  try { dir = safeClone(parsed.url, sha); } catch (e) { return [e.message]; }\n  let result;\n  try { result = validateInstall(dir, parsed.id); }\n  catch (e) { rmSync(dir, { recursive: true, force: true }); return [e.message]; }\n  try { rmSync(result.dir || dir, { recursive: true, force: true }); } catch { /* best-effort */ }\n  return result.ok ? [] : result.problems;\n}\n\n/** Scaffold a new local plugin from plugins/_template/. */\nexport function scaffoldNew(root, name) {\n  if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) throw new Error(`plugin name must match [a-z0-9-] (got \"${name}\")`);\n  const tpl = path.join(root, 'plugins', '_template');\n  if (!existsSync(tpl)) throw new Error('plugins/_template/ not found');\n  const dest = path.join(root, 'plugins.local', name);\n  if (existsSync(dest)) throw new Error(`plugins.local/${name} already exists`);\n  mkdirSync(path.join(root, 'plugins.local'), { recursive: true });\n  cpSync(tpl, dest, { recursive: true });\n  // Substitute {{NAME}} placeholders in shipped text files.\n  const sub = (p) => { if (existsSync(p)) writeFileSync(p, readFileSync(p, 'utf8').replaceAll('{{NAME}}', name), 'utf8'); };\n  for (const f of readdirSync(dest, { withFileTypes: true })) {\n    if (f.isFile()) sub(path.join(dest, f.name));\n  }\n  if (existsSync(path.join(dest, 'test'))) for (const f of readdirSync(path.join(dest, 'test'))) sub(path.join(dest, 'test', f));\n  return dest;\n}\n\n// ── CLI flags + help ────────────────────────────────────────────────\n//\n// This file is a library first: plugins.mjs owns the user-facing `new`/`add`","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugin-install.mjs#L103-L139","documentation":"scaffoldNew validates the requested plugin name against /^[a-z0-9][a-z0-9-]*$/ before scaffolding from plugins/_template/. Lowercase alphanumerics and hyphens only, and it must not start with a hyphen. Any other name throws this error before any files are created.","triggerScenarios":"Calling scaffoldNew(root, name) with uppercase letters, underscores, spaces, dots, leading hyphen, or an empty/undefined name.","commonSituations":"Typing a plugin name in CamelCase or snake_case; passing a CLI arg with spaces; scaffolding with a version string or org prefix like '@scope/name'; forgetting to quote/normalize user input.","solutions":["Rename the plugin to lowercase kebab-case, e.g. 'my-cool-plugin'","Strip or replace invalid characters: name.toLowerCase().replace(/[^a-z0-9-]/g, '-')","Ensure the name doesn't start with a hyphen (regex requires an alphanumeric first char)"],"exampleFix":"// before\nawait scaffoldNew(root, 'My_Plugin');\n// Error: plugin name must match [a-z0-9-] (got \"My_Plugin\")\n// after\nconst name = 'My_Plugin'.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/^-+/, '');\nawait scaffoldNew(root, name); // 'my-plugin'","handlingStrategy":"validation","validationCode":"const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;\nif (!NAME_RE.test(name)) throw new Error(`invalid plugin name: ${name} (use lowercase kebab-case)`);","typeGuard":"const isValidPluginName = (n) => typeof n === 'string' && /^[a-z0-9][a-z0-9-]*$/.test(n);","tryCatchPattern":"try { await scaffoldNew(root, name); } catch (e) { if (e.message.includes('plugin name must match')) { name = slugify(name); return scaffoldNew(root, name); } throw e; }","preventionTips":["Always slugify user-supplied names before scaffolding","Document the kebab-case constraint in your CLI's help text","Sanitize in the UI layer so invalid names never reach the API"],"tags":["plugins","naming","validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}