feat: report script metadata with no content file in wmill lint (#11053)

Claude-Session: https://claude.ai/code/session_01PENfHNJceAbrcUsgfFCjXN

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-09-10 09:15:44 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 9a563f6d72
commit 8820b9fc64
7 changed files with 314 additions and 12 deletions
+14 -4
View File
@@ -1075,10 +1075,12 @@ export class DbtPathCollisionError extends UnresolvableScriptContentFileError {}
* guard on one of them leaves the other silently overwriting.
*/
export async function collidingDbtProject(
basePath: string
basePath: string,
baseDir?: string
): Promise<string | undefined> {
const project = basePath + "__dbt/dbt_project.yml";
return (await stat(project).then(() => true).catch(() => false))
const onDisk = baseDir ? path.join(baseDir, project) : project;
return (await stat(onDisk).then(() => true).catch(() => false))
? project
: undefined;
}
@@ -1139,7 +1141,14 @@ async function readScriptContent(filePath: string): Promise<string> {
}
}
export async function findContentFile(filePath: string) {
/**
* The script file `filePath`'s metadata belongs to. `baseDir`, when given, is
* where the disk lookups happen, leaving `filePath` classified as written: the
* layout helpers below match their suffixes ANYWHERE in a path, so a caller
* that prefixed a checkout named `repo__mod` would have it read as the module.
*/
export async function findContentFile(filePath: string, baseDir?: string) {
const onDisk = (p: string) => (baseDir ? path.join(baseDir, p) : p);
// Folder layout: __mod/script.yaml -> __mod/script.ts
const isModuleFolderMeta = isModuleEntryMetadata(filePath);
const toCandidate = (ext: string) =>
@@ -1163,7 +1172,7 @@ export async function findContentFile(filePath: string) {
const validCandidates = (
await Promise.all(
candidates.map((x) => {
return stat(x)
return stat(onDisk(x))
.catch(() => undefined)
.then((x) => x?.isFile())
.then((e) => {
@@ -1183,6 +1192,7 @@ export async function findContentFile(filePath: string) {
const dbtCandidate = toCandidate("__dbt/" + DBT_DESCRIPTOR_NAME);
const dbtProject = await collidingDbtProject(
dbtCandidate.slice(0, -("__dbt/" + DBT_DESCRIPTOR_NAME).length),
baseDir,
);
const nonDbtCandidates = validCandidates.filter((c) => c !== dbtCandidate);
if (dbtProject && nonDbtCandidates.length > 0) {