1// Alchemy modifications are licensed under Apache-2.0.
2// This file includes third-party code; see /THIRD_PARTY_LICENSES.md.
3import ignore from "@alchemy.run/node-utils/ignore";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7import mime from "mime";
8import { isAbsolute, sep } from "node:path";
9import {
10 CF_ASSETS_IGNORE_FILENAME,
11 HEADERS_FILENAME,
12 REDIRECTS_FILENAME,
13} from "../shared/constants.ts";
14
15/** normalises sep for windows and prefix with `/` */
16export const normalizeFilePath = (relativeFilepath: string) => {
17 if (isAbsolute(relativeFilepath)) {
18 throw new Error(`Expected relative path`);
19 }
20 return "/" + relativeFilepath.split(sep).join("/");
21};
22
23export const getContentType = (absFilePath: string) => {
24 let contentType = mime.getType(absFilePath);
25 if (
26 contentType &&
27 contentType.startsWith("text/") &&
28 !contentType.includes("charset")
29 ) {
30 contentType = `${contentType}; charset=utf-8`;
31 }
32 return contentType;
33};
34
35/**
36 * Generate a function that can match relative filepaths against a list of gitignore formatted patterns.
37 */
38export function createPatternMatcher(
39 patterns: Array<string>,
40 exclude: boolean,
41): (filePath: string) => boolean {
42 if (patterns.length === 0) {
43 return (_filePath) => !exclude;
44 } else {
45 const ignorer = ignore().add(patterns);
46 return (filePath) => ignorer.test(filePath).ignored;
47 }
48}
49
50/**
51 * Create a function for filtering out ignored assets.
52 *
53 * The generated function takes an asset path, relative to the asset directory,
54 * and returns true if the asset should not be ignored.
55 */
56export const createAssetsIgnoreFunction = Effect.fn(function* (dir: string) {
57 const fs = yield* FileSystem.FileSystem;
58 const path = yield* Path.Path;
59 const cfAssetIgnorePath = path.join(dir, CF_ASSETS_IGNORE_FILENAME);
60
61 const ignorePatterns = [
62 // Ignore the `.assetsignore` file and other metafiles by default.
63 // The ignore lib expects unix-style paths for its patterns
64 `/${CF_ASSETS_IGNORE_FILENAME}`,
65 `/${REDIRECTS_FILENAME}`,
66 `/${HEADERS_FILENAME}`,
67 ];
68
69 const assetsIgnore = yield* fs.readFileString(cfAssetIgnorePath).pipe(
70 Effect.catchIf(
71 (error) => error.reason._tag === "NotFound",
72 () => Effect.succeed(undefined),
73 ),
74 );
75 if (assetsIgnore !== undefined) {
76 ignorePatterns.push(...assetsIgnore.split("\n"));
77 }
78
79 return {
80 assetsIgnoreFunction: createPatternMatcher(ignorePatterns, true),
81 assetsIgnoreFilePresent: assetsIgnore !== undefined,
82 };
83});