{"record":{"id":"e05775e8779803f9","repo":"laurent22/joplin","slug":"you-must-specify-a-src-for-each-screenshot","errorCode":null,"errorMessage":"You must specify a src for each screenshot","messagePattern":"You must specify a src for each screenshot","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/generator-joplin/generators/app/templates/webpack.config.js","lineNumber":103,"sourceCode":"\t\tconsole.info(chalk.cyan('Could not get git commit (not a git repo?):', messages[0].trim()));\n\t\tconsole.info(chalk.cyan('Git information will not be stored in plugin info file'));\n\t\treturn '';\n\t}\n}\n\nfunction validateCategories(categories) {\n\tif (!categories) return null;\n\tif ((categories.length !== new Set(categories).size)) throw new Error('Repeated categories are not allowed');\n\t// eslint-disable-next-line github/array-foreach -- Old code before rule was applied\n\tcategories.forEach(category => {\n\t\tif (!allPossibleCategories.map(category => { return category.name; }).includes(category)) throw new Error(`${category} is not a valid category. Please make sure that the category name is lowercase. Valid categories are: \\n${allPossibleCategories.map(category => { return category.name; })}\\n`);\n\t});\n}\n\nfunction validateScreenshots(screenshots) {\n\tif (!screenshots) return null;\n\tfor (const screenshot of screenshots) {\n\t\tif (!screenshot.src) throw new Error('You must specify a src for each screenshot');\n\n\t\t// Avoid attempting to download and verify URL screenshots.\n\t\tif (screenshot.src.startsWith('https://') || screenshot.src.startsWith('http://')) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst screenshotType = screenshot.src.split('.').pop();\n\t\tif (!allPossibleScreenshotsType.includes(screenshotType)) throw new Error(`${screenshotType} is not a valid screenshot type. Valid types are: \\n${allPossibleScreenshotsType}\\n`);\n\n\t\tconst screenshotPath = path.resolve(rootDir, screenshot.src);\n\n\t\t// Max file size is 1MB\n\t\tconst fileMaxSize = 1024;\n\t\tconst fileSize = fs.statSync(screenshotPath).size / 1024;\n\t\tif (fileSize > fileMaxSize) throw new Error(`Max screenshot file size is ${fileMaxSize}KB. ${screenshotPath} is ${fileSize}KB`);\n\t}\n}\n","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/generator-joplin/generators/app/templates/webpack.config.js#L85-L121","documentation":"Thrown by validateScreenshots() when iterating the manifest's `screenshots` array. Every screenshot object must have a `src` property pointing at the image file (or URL). Missing src is treated as a malformed manifest and aborts the build before any further checks (type, size) run.","triggerScenarios":"readManifest() -> validateScreenshots(output.screenshots) -> for a screenshot object, `!screenshot.src` is truthy (undefined, empty string, or null).","commonSituations":"Author adds a screenshot entry with only a `caption` or wrong key name (e.g. 'path' instead of 'src'); copies a template entry and forgets to fill in the path; manifest.json was hand-edited and the key was accidentally removed.","solutions":["Open manifest.json and add a `src` field to every object in the `screenshots` array.","Verify the key is exactly `src` (not `source`, `path`, `url`).","For local files confirm the path is relative to the project root and the file exists; for remote images use a full https:// or http:// URL (those skip local checks downstream).","Re-run the build."],"exampleFix":"// before (manifest.json)\n\"screenshots\": [{ \"caption\": \"Main view\" }]\n// after\n\"screenshots\": [{ \"src\": \"screenshots/main.png\", \"caption\": \"Main view\" }]","handlingStrategy":"validation","validationCode":"// Pre-check every screenshot has a non-empty src.\nconst manifest = require('./manifest.json');\nfor (const s of manifest.screenshots || []) {\n  if (!s.src || typeof s.src !== 'string') throw new Error(`Screenshot missing src: ${JSON.stringify(s)}`);\n}","typeGuard":"const hasSrc = (s) => !!s && typeof s.src === 'string' && s.src.length > 0;","tryCatchPattern":"try { validateScreenshots(manifest.screenshots); }\ncatch (e) { if (/specify a src/.test(e.message)) { /* prompt author for missing src */ } else throw e; }","preventionTips":["Use a manifest schema (JSON Schema) that requires src on every screenshot entry and validate in CI.","When templating manifest.json from a scaffold, default src to a placeholder rather than omitting it."],"tags":["plugin-build","manifest-validation","generator-joplin"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}