{"version":3,"file":"FileSystemIdentity.mjs","names":[],"sources":["../../src/executable/FileSystemIdentity.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport namespace FileSystemIdentity {\n  export interface IIdentity {\n    readonly caseSensitive: boolean;\n    contains(file: string, directory: string): boolean;\n    filesystemKey(file: string): string;\n    isDeclarationFile(file: string): boolean;\n    isSamePath(x: string, y: string): boolean;\n    isSupportedExtension(file: string): boolean;\n    projectFileKey(file: string): string;\n  }\n\n  export function create(\n    caseSensitive: boolean,\n    pathApi: typeof path.posix = path,\n  ): IIdentity {\n    const fold = (value: string): string =>\n      caseSensitive ? value : value.toLowerCase();\n    const filesystemKey = (file: string): string =>\n      fold(pathApi.normalize(file));\n    return {\n      caseSensitive,\n      contains: (file, directory) => {\n        const fileKey: string = filesystemKey(pathApi.resolve(file));\n        const directoryKey: string = filesystemKey(pathApi.resolve(directory));\n        const prefix: string = directoryKey.endsWith(pathApi.sep)\n          ? directoryKey\n          : `${directoryKey}${pathApi.sep}`;\n        return fileKey === directoryKey || fileKey.startsWith(prefix);\n      },\n      filesystemKey,\n      isDeclarationFile: (file) =>\n        DTS_PATTERN.test(fold(pathApi.basename(file))),\n      isSamePath: (x, y) => filesystemKey(x) === filesystemKey(y),\n      isSupportedExtension: (file) => {\n        const filename: string = fold(pathApi.basename(file));\n        return (\n          TS_PATTERN.test(filename) && DTS_PATTERN.test(filename) === false\n        );\n      },\n      projectFileKey: fold,\n    };\n  }\n\n  export class Policy {\n    private caseSensitive_: boolean | undefined;\n    private location_: string | undefined;\n\n    public observe(caseSensitive: boolean | undefined, location: string): void {\n      if (caseSensitive === undefined) return;\n      if (this.caseSensitive_ === undefined) {\n        this.caseSensitive_ = caseSensitive;\n        this.location_ = location;\n      } else if (this.caseSensitive_ !== caseSensitive) {\n        throw new URIError(\n          `Error on TypiaGenerateWizard.generate(): inconsistent filesystem case behavior between ${this.location_} and ${location}.`,\n        );\n      }\n    }\n\n    public get(): IIdentity {\n      if (this.caseSensitive_ === undefined) {\n        throw new URIError(\n          \"Error on TypiaGenerateWizard.generate(): unable to determine filesystem case behavior for this run.\",\n        );\n      }\n      return create(this.caseSensitive_);\n    }\n  }\n\n  export async function inspectDirectory(\n    directory: string,\n  ): Promise<boolean | undefined> {\n    const names: string[] = await fs.promises.readdir(directory);\n    const entries: Set<string> = new Set(names);\n    for (const name of names) {\n      const alternate: string | undefined = alternateCase(name);\n      if (alternate === undefined) continue;\n      if (entries.has(alternate)) return true;\n      try {\n        await fs.promises.lstat(path.join(directory, alternate));\n        return false;\n      } catch (error) {\n        if (isMissingFileError(error)) return true;\n        throw error;\n      }\n    }\n    return undefined;\n  }\n\n  export async function probeDirectory(directory: string): Promise<boolean> {\n    const inspected: boolean | undefined = await inspectDirectory(directory);\n    if (inspected !== undefined) return inspected;\n\n    for (let attempt = 0; attempt < 10; ++attempt) {\n      const name: string =\n        `.typia-case-probe-${process.pid}-${Date.now()}-${Math.random()\n          .toString(36)\n          .slice(2)}`.toLowerCase();\n      const original: string = path.join(directory, name);\n      const alternate: string = path.join(directory, name.toUpperCase());\n      let handle: fs.promises.FileHandle;\n      try {\n        handle = await fs.promises.open(original, \"wx\");\n      } catch (error) {\n        if (isAlreadyExistsError(error)) continue;\n        throw new URIError(\n          `Error on TypiaGenerateWizard.generate(): unable to probe filesystem case behavior at ${directory}: ${formatUnknownError(error)}`,\n        );\n      }\n      let closed: boolean = false;\n      try {\n        await handle.close();\n        closed = true;\n        try {\n          await fs.promises.lstat(alternate);\n          return false;\n        } catch (error) {\n          if (isMissingFileError(error)) return true;\n          throw error;\n        }\n      } finally {\n        let cleanupError: unknown;\n        if (closed === false) {\n          try {\n            await handle.close();\n          } catch (error) {\n            cleanupError = error;\n          }\n        }\n        try {\n          await fs.promises.unlink(original);\n        } catch (error) {\n          if (isMissingFileError(error) === false) cleanupError ??= error;\n        }\n        if (cleanupError !== undefined) {\n          throw new URIError(\n            `Error on TypiaGenerateWizard.generate(): unable to clean filesystem case probe ${original}: ${formatUnknownError(cleanupError)}`,\n          );\n        }\n      }\n    }\n    throw new URIError(\n      `Error on TypiaGenerateWizard.generate(): unable to reserve a filesystem case probe at ${directory}.`,\n    );\n  }\n\n  /**\n   * Builds the key that decides whether two paths are the same filesystem\n   * object.\n   *\n   * The identity is read from `fs.BigIntStats` rather than `fs.Stats`, because\n   * `fs.Stats.ino` is a JavaScript number. An NTFS file ID is `(sequenceNumber\n   * << 48) | mftRecordIndex` and routinely exceeds `Number.MAX_SAFE_INTEGER` —\n   * in a 4000-directory probe, 1879 of them did — where the spacing between\n   * representable doubles is 8 or more. Two entries sharing a sequence number\n   * with MFT record indices within that spacing round to one double. The\n   * traversal callers would then treat a distinct directory or file as already\n   * visited and skip it with no diagnostic, and the overwrite guard would\n   * refuse a legitimate output (samchon/typia#2269).\n   *\n   * A filesystem that reports no inode at all keeps the canonical path\n   * fallback, which is the only identity available there.\n   *\n   * @param stat Stats read with `{ bigint: true }`.\n   * @param realpath Canonical path of the same object.\n   * @returns Key that is equal for two paths only when they are one object.\n   */\n  export function identityKey(\n    stat: Pick<fs.BigIntStats, \"dev\" | \"ino\">,\n    realpath: string,\n  ): string {\n    // `BigInt(0)` rather than `0n`: this package compiles at ES2016, where a\n    // BigInt literal is a syntax error, while the constructor and the `bigint`\n    // type are fine.\n    return stat.ino === BigInt(0)\n      ? `path:${path.normalize(realpath)}`\n      : `inode:${stat.dev}:${stat.ino}`;\n  }\n\n  function alternateCase(name: string): string | undefined {\n    let changed: boolean = false;\n    let output: string = \"\";\n    for (const character of name) {\n      if (character >= \"a\" && character <= \"z\") {\n        output += character.toUpperCase();\n        changed = true;\n      } else if (character >= \"A\" && character <= \"Z\") {\n        output += character.toLowerCase();\n        changed = true;\n      } else output += character;\n    }\n    return changed ? output : undefined;\n  }\n\n  function isAlreadyExistsError(error: unknown): boolean {\n    return (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"EEXIST\"\n    );\n  }\n\n  function isMissingFileError(error: unknown): boolean {\n    return (\n      typeof error === \"object\" &&\n      error !== null &&\n      \"code\" in error &&\n      error.code === \"ENOENT\"\n    );\n  }\n\n  function formatUnknownError(error: unknown): string {\n    return error instanceof Error ? error.message : String(error);\n  }\n}\n\nconst TS_PATTERN = /\\.[cm]?tsx?$/;\nconst DTS_PATTERN = /\\.d\\.[cm]?tsx?$/;\n"],"mappings":"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"}