Files
windmill/cli/src/utils/yaml.ts
T
centdix a8b651da9f fix(cli): preserve inline script files during flow generate-locks (#8561)
* fix(cli): preserve inline script files during flow generate-locks

Three bugs caused `wmill flow generate-locks` to destroy inline script
content and rename files:

1. YAML parser stripped unquoted `!inline` tags (treated as YAML tag,
   not string prefix), leaving just the filename as script content.
   Fix: register custom YAML tags for `!inline` and `!inline_fileset`.

2. Inline script files were renamed based on step summaries because
   `extractInlineScriptsForFlows` was called with empty mapping `{}`.
   Fix: call existing `extractCurrentMapping()` before replacement and
   pass the mapping to preserve original filenames.

3. Lock file paths were derived from the assigner instead of the mapped
   content path, causing inconsistent naming.
   Fix: derive lock base path from mapped content path when available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(cli): add unit tests for !inline YAML tag and mapping preservation

- YAML tag tests: unquoted/quoted !inline parsing, !inline_fileset,
  nested structures, round-trip stability
- Mapping tests: path preservation with mapping, fallthrough without
  mapping, lock path derivation from mapped content path, mixed
  mapped/unmapped modules, dotted path handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): correct yaml parse type cast and inline prefix check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(cli): harden lock path for extensionless files and merge customTags

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 19:27:56 +00:00

52 lines
1.6 KiB
TypeScript

import { parse as yamlParse } from "yaml";
import type { ParseOptions, DocumentOptions, SchemaOptions, ToJSOptions, ScalarTag } from "yaml";
import { readFile } from "node:fs/promises";
// Custom YAML tags that resolve `!inline value` and `!inline_fileset value`
// back to their string-prefix form ("!inline value").
// Without these, the yaml parser strips the tag and returns just the scalar,
// breaking the string-prefix-based !inline detection used throughout the CLI.
const inlineTag: ScalarTag = {
tag: "!inline",
resolve(value: string) {
return "!inline " + value;
},
};
const inlineFilesetTag: ScalarTag = {
tag: "!inline_fileset",
resolve(value: string) {
return "!inline_fileset " + value;
},
};
const WINDMILL_CUSTOM_TAGS: ScalarTag[] = [inlineTag, inlineFilesetTag];
type YamlParseOptions = ParseOptions & DocumentOptions & SchemaOptions & ToJSOptions;
export async function yamlParseFile(path: string, options: YamlParseOptions = {}) {
try {
return yamlParse(await readFile(path, "utf-8"), {
...options,
customTags: [...WINDMILL_CUSTOM_TAGS, ...((options.customTags as ScalarTag[] | undefined) ?? [])],
});
} catch (e) {
throw new Error(`Error parsing yaml ${path}`, { cause: e });
}
}
export function yamlParseContent(
path: string,
content: string,
options: YamlParseOptions = {},
) {
try {
return yamlParse(content, {
...options,
customTags: [...WINDMILL_CUSTOM_TAGS, ...((options.customTags as ScalarTag[] | undefined) ?? [])],
});
} catch (e) {
throw new Error(`Error parsing yaml ${path}`, { cause: e });
}
}