|
| 1 | +import {definePreset} from './util/standard-schema-preset' |
| 2 | + |
| 3 | +/** |
| 4 | + * Copies a whole other file into a const variable. Useful for capturing a file's full contents as a hard-coded string. |
| 5 | + * Obviously this creates duplicated data, so use judiciously! |
| 6 | + * |
| 7 | + * ##### basic usage |
| 8 | + * ```js |
| 9 | + * // codegen:start {preset: copy, source: ../../another-project/src/some-file.ts} |
| 10 | + * import {z} from 'zod' |
| 11 | + * export const MyObject = z.object({ foo: z.string() }) |
| 12 | + * // codegen:end |
| 13 | + * ``` |
| 14 | + * |
| 15 | + * #### excludeLines |
| 16 | + * |
| 17 | + * ```ts |
| 18 | + * ; |
| 19 | + * import {z} from 'zod/v4' // in this project we use zod v4, but we're copying from a project that uses zod v3 |
| 20 | + * // codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, excludeLines: ['^import']} |
| 21 | + * ; |
| 22 | + * export const MyObject = z.object({ foo: z.string() }) |
| 23 | + * // codegen:end |
| 24 | + * ``` |
| 25 | + * |
| 26 | + * #### onlyIfExists |
| 27 | + * ```js |
| 28 | + * // copy a file from a sibling project, but only if the sibling project actually exists |
| 29 | + * // in this case this will effectively skip the copying step on machines that don't have the sibling project installed |
| 30 | + * // e.g. on CI runners. |
| 31 | + * // codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, onlyIfExists: ../../another-project/package.json} |
| 32 | + * ; |
| 33 | + * import {z} from 'zod' |
| 34 | + * ; |
| 35 | + * export const MyObject = z.object({ foo: z.string() }); |
| 36 | + * ; |
| 37 | + * // codegen:end |
| 38 | + * ``` |
| 39 | + * |
| 40 | + * #### comparison |
| 41 | + * ```js |
| 42 | + * // by default, the content will perform a "simplified" comparison with existing content, so differences from tools like prettier |
| 43 | + * // are ignored. if you care about whitespace and similar differences, you can set the comparison option to `strict`. |
| 44 | + * // codegen:start {preset: copy, source: ../../another-project/src/some-file.ts, comparison: strict} |
| 45 | + * ; |
| 46 | + * import {z} from "zod" |
| 47 | + * ; |
| 48 | + * export const MyObject = z.object({ foo: z.string() }) |
| 49 | + * ; |
| 50 | + * // codegen:end |
| 51 | + * ``` |
| 52 | + */ |
| 53 | +export const str = definePreset( |
| 54 | + { |
| 55 | + source: 'string', |
| 56 | + const: 'string', |
| 57 | + 'export?': 'boolean', |
| 58 | + /** path to the file to copy. can be absolute or relative to the file being linted */ |
| 59 | + /** if provided, only runs if this file exists - if it's missing, the existing content is returned (defaulting to empty string) */ |
| 60 | + 'onlyIfExists?': 'string', |
| 61 | + /** if provided, these lines will be removed from the copied content. e.g. `excludeLines: ['^import', '// codegen:']` */ |
| 62 | + 'excludeLines?': 'string[]', |
| 63 | + /** |
| 64 | + * if set to `strict` the content will update if it's not a perfect match. by default (`simplified`) it will only update |
| 65 | + * if the "simplified" version of the content is different. |
| 66 | + */ |
| 67 | + 'comparison?': '"simplified" | "strict"', |
| 68 | + }, |
| 69 | + ({options, meta, context, dependencies: {fs, path, simplify}}) => { |
| 70 | + // todo: add an option to allow syncing the other way - that is, if the content on the file being linted is newer, |
| 71 | + // don't autofix - offer two suggestions: 1) write to the source file, 2) write to the file being linted. |
| 72 | + const getAbsolutePath = (filepath: string) => path.resolve(path.dirname(context.physicalFilename), filepath) |
| 73 | + const shouldRun = options.onlyIfExists ? fs.existsSync(getAbsolutePath(options.onlyIfExists)) : true |
| 74 | + if (!shouldRun) return meta.existingContent || '' |
| 75 | + |
| 76 | + let content = fs.readFileSync(getAbsolutePath(options.source), 'utf8') |
| 77 | + if (options.excludeLines) { |
| 78 | + const regexes = options.excludeLines.map(line => new RegExp(line)) |
| 79 | + const lines = content.split('\n') |
| 80 | + content = lines.filter(line => !regexes.some(regex => regex.test(line))).join('\n') |
| 81 | + } |
| 82 | + |
| 83 | + content = `const ${options.const} = ${JSON.stringify(content)}` |
| 84 | + if (options.export) content = `export ${content}` |
| 85 | + |
| 86 | + let isUpToDate: boolean |
| 87 | + // eslint-disable-next-line unicorn/prefer-ternary |
| 88 | + if (!options.comparison || options.comparison === 'simplified') { |
| 89 | + // we only want to declare it outdated if the simplified versions are different |
| 90 | + isUpToDate = simplify.equivalentSimplified(content, meta.existingContent) |
| 91 | + } else { |
| 92 | + isUpToDate = content === meta.existingContent |
| 93 | + } |
| 94 | + |
| 95 | + if (isUpToDate) return meta.existingContent || '' |
| 96 | + |
| 97 | + return content |
| 98 | + }, |
| 99 | +) |
0 commit comments