( output: Output, directory: string, )
| 708 | } |
| 709 | |
| 710 | export function write( |
| 711 | output: Output, |
| 712 | directory: string, |
| 713 | ): Effect.Effect<void, GenerationError | PlatformError.PlatformError, FileSystem.FileSystem> { |
| 714 | return Effect.gen(function* () { |
| 715 | const paths = new Set<string>() |
| 716 | const normalizedPaths = new Set<string>() |
| 717 | for (const file of output.files) { |
| 718 | if (!isSafeOutputPath(file.path)) yield* new GenerationError({ reason: `Unsafe output path: ${file.path}` }) |
| 719 | const path = file.path.toLowerCase() |
| 720 | if (normalizedPaths.has(path)) yield* new GenerationError({ reason: `Duplicate output path: ${file.path}` }) |
| 721 | normalizedPaths.add(path) |
| 722 | paths.add(file.path) |
| 723 | } |
| 724 | const fs = yield* FileSystem.FileSystem |
| 725 | yield* fs.makeDirectory(directory, { recursive: true }) |
| 726 | const manifest = join(directory, manifestName) |
| 727 | const previous = (yield* fs.exists(manifest)) |
| 728 | ? yield* fs.readFileString(manifest).pipe( |
| 729 | Effect.flatMap(Schema.decodeUnknownEffect(Manifest)), |
| 730 | Effect.mapError(() => new GenerationError({ reason: `Invalid generated file manifest: ${manifest}` })), |
| 731 | ) |
| 732 | : [] |
| 733 | if (previous.some((path) => !isSafeOutputPath(path))) { |
| 734 | yield* new GenerationError({ reason: `Invalid generated file manifest: ${manifest}` }) |
| 735 | } |
| 736 | yield* Effect.forEach( |
| 737 | previous.filter((path) => !paths.has(path)), |
| 738 | (path) => fs.remove(join(directory, path), { force: true }), |
| 739 | { concurrency: 8, discard: true }, |
| 740 | ) |
| 741 | yield* Effect.forEach( |
| 742 | output.files, |
| 743 | (file) => |
| 744 | fs.exists(join(directory, file.path)).pipe( |
| 745 | Effect.flatMap((exists) => (exists ? fs.stat(join(directory, file.path)) : Effect.succeed(undefined))), |
| 746 | Effect.flatMap((info) => |
| 747 | info?.type === "SymbolicLink" |
| 748 | ? new GenerationError({ reason: `Unsafe output path: ${file.path}` }) |
| 749 | : Effect.void, |
| 750 | ), |
| 751 | ), |
| 752 | { concurrency: 8, discard: true }, |
| 753 | ) |
| 754 | yield* Effect.forEach( |
| 755 | output.files, |
| 756 | (file) => |
| 757 | Effect.tryPromise({ |
| 758 | try: () => format(file.content, { filepath: file.path, parser: "typescript", semi: false, printWidth: 120 }), |
| 759 | catch: (error) => new GenerationError({ reason: `Failed to format ${file.path}: ${String(error)}` }), |
| 760 | }).pipe(Effect.flatMap((content) => fs.writeFileString(join(directory, file.path), content))), |
| 761 | { concurrency: 8, discard: true }, |
| 762 | ) |
| 763 | yield* fs.writeFileString(manifest, JSON.stringify(output.files.map((file) => file.path).sort(), null, 2) + "\n") |
| 764 | }) |
| 765 | } |
| 766 | |
| 767 | function isSafeOutputPath(path: string) { |
no test coverage detected