mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 16:02:36 +00:00
canonicalize a script module path before the add-file checks (#10445)
* fix: canonicalize a script module path before the add-file checks * fix: match bundle keys without trimming, and stop offering a no-op rename
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
function canonicalize(path: string): { path: string } | { error: string } {
|
||||
if (path.startsWith('/')) {
|
||||
return { error: `File path must be relative, without a leading /` }
|
||||
}
|
||||
const segments = path.split('/').filter((s) => s !== '' && s !== '.')
|
||||
if (segments.includes('..')) {
|
||||
return { error: `File path cannot contain ..` }
|
||||
}
|
||||
if (segments.length === 0) {
|
||||
return { error: `File name cannot be empty` }
|
||||
}
|
||||
return { path: segments.join('/') }
|
||||
}
|
||||
|
||||
/**
|
||||
* The canonical spelling of a script module's path (the key of the module
|
||||
* bundle), or the reason it cannot be one.
|
||||
*
|
||||
* The worker resolves `.` and `//` away when it materialises the bundle into
|
||||
* the job directory, so `./dbt_project.yml` and `dbt_project.yml` are two keys
|
||||
* for one file on disk; the bundle is a Rust `HashMap`, so which content lands
|
||||
* there is undefined. Canonicalising before the duplicate and reserved-name
|
||||
* checks is what keeps them from being walked past.
|
||||
*
|
||||
* Redundant spellings are rewritten rather than refused: they name the file the
|
||||
* user meant, and the tree shows the canonical form regardless. `..` and
|
||||
* absolute paths name a file OUTSIDE the bundle, which has no canonical form
|
||||
* inside it, so they are refused here — the worker drops them, which would
|
||||
* otherwise show up as a file that was added and then silently never written.
|
||||
*
|
||||
* Surrounding whitespace is dropped because this is what someone typed into a
|
||||
* text box. A key already in a bundle gets no such courtesy — see
|
||||
* `findModulePathClash`.
|
||||
*/
|
||||
export function canonicalModulePath(path: string): { path: string } | { error: string } {
|
||||
return canonicalize(path.trim())
|
||||
}
|
||||
|
||||
/**
|
||||
* The existing module key that would land on the same file as `canonicalPath`,
|
||||
* spelled as the bundle holds it (which is what the file tree shows).
|
||||
*
|
||||
* Keys already in the bundle are not canonical either: nothing on the push path
|
||||
* rewrites them, so a project imported with `./dbt_project.yml` in it must still
|
||||
* refuse a second `dbt_project.yml`. They are matched WITHOUT trimming, because
|
||||
* the worker does not trim path components: an imported `x.sql ` is its own file
|
||||
* on disk and must not stand in the way of adding `x.sql`.
|
||||
*
|
||||
* `ignoreKey` is the module being renamed. It has to be skipped inside the
|
||||
* search rather than compared against the result: a bundle can hold BOTH
|
||||
* spellings, and stopping at the renamed one would hide the other and let the
|
||||
* rename overwrite it.
|
||||
*/
|
||||
export function findModulePathClash(
|
||||
modules: Record<string, unknown> | null | undefined,
|
||||
canonicalPath: string,
|
||||
ignoreKey?: string
|
||||
): string | undefined {
|
||||
return Object.keys(modules ?? {}).find((key) => {
|
||||
if (key === ignoreKey) return false
|
||||
const canonical = canonicalize(key)
|
||||
return 'path' in canonical && canonical.path === canonicalPath
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user