{"version":3,"file":"local.cjs","sources":["../../src/utils/fileReader.ts","../../src/utils/constants.ts","../../src/utils/validator.ts","../../src/local.ts"],"sourcesContent":["import {readdir, readFile} from 'node:fs/promises'\nimport {join} from 'node:path'\n\nimport type {GitHubDirectoryEntry} from './types'\n\n/** @public */\nexport interface FileReader {\n  readFile(filePath: string): Promise<{exists: boolean; content: string}>\n  readDir(dirPath: string): Promise<string[]>\n}\n\n/** @public */\nexport class GitHubFileReader implements FileReader {\n  private baseUrl: string\n  private headers: Record<string, string>\n\n  constructor(baseUrl: string, headers: Record<string, string> = {}) {\n    this.baseUrl = baseUrl.replace(/\\/+$/, '') // Remove trailing slashes\n    this.headers = headers\n  }\n\n  async readFile(filePath: string): Promise<{exists: boolean; content: string}> {\n    const response = await fetch(`${this.baseUrl}/${filePath}`, {headers: this.headers})\n    return {\n      exists: response.status === 200,\n      content: await response.text(),\n    }\n  }\n\n  async readDir(dirPath: string): Promise<string[]> {\n    try {\n      // Convert raw GitHub URL to API URL for directory listing\n      // From: https://raw.githubusercontent.com/owner/repo/branch/path\n      // To: https://api.github.com/repos/owner/repo/contents/path?ref=branch\n      const url = new URL(this.baseUrl)\n      const [, owner, repo, branch = '', ...rest] = url.pathname.split('/')\n      const dirSlug = join(`repos/${owner}/${repo}/contents`, ...rest, dirPath)\n      const apiUrl = new URL(dirSlug, 'https://api.github.com')\n      apiUrl.searchParams.set('ref', branch) // Set branch ref\n\n      const response = await fetch(apiUrl, {headers: this.headers})\n      if (!response.ok) return []\n\n      const data: GitHubDirectoryEntry[] = await response.json()\n      return Array.isArray(data)\n        ? data.filter((item) => item.type === 'dir').map((item) => item.name)\n        : []\n    } catch {\n      return []\n    }\n  }\n}\n\n/** @public */\nexport class LocalFileReader implements FileReader {\n  private basePath: string\n\n  constructor(basePath: string) {\n    this.basePath = basePath\n  }\n\n  async readFile(filePath: string): Promise<{exists: boolean; content: string}> {\n    try {\n      const fullPath = join(this.basePath, filePath)\n      const content = await readFile(fullPath, 'utf-8')\n      return {\n        exists: true,\n        content,\n      }\n    } catch (error) {\n      return {\n        exists: false,\n        content: '',\n      }\n    }\n  }\n\n  async readDir(dirPath: string): Promise<string[]> {\n    try {\n      const fullPath = join(this.basePath, dirPath)\n      const entries = await readdir(fullPath, {withFileTypes: true})\n      return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name)\n    } catch {\n      return []\n    }\n  }\n}\n","/** @public */\nexport const REQUIRED_ENV_VAR = {\n  PROJECT_ID: /SANITY(?:_STUDIO)?_PROJECT_ID/,\n  DATASET: /SANITY(?:_STUDIO)?_DATASET/,\n} as const\n\n/** @public */\nexport const ENV_FILE = {\n  TEMPLATE: '.env.template',\n  EXAMPLE: '.env.example',\n  LOCAL_EXAMPLE: '.env.local.example',\n  LOCAL_TEMPLATE: '.env.local.template',\n} as const\n\n/** @public */\nexport const ENV_TEMPLATE_FILES = [\n  ENV_FILE.TEMPLATE,\n  ENV_FILE.EXAMPLE,\n  ENV_FILE.LOCAL_EXAMPLE,\n  ENV_FILE.LOCAL_TEMPLATE,\n] as const\n\n/** @public */\nexport const ROOT_PACKAGE_NAME = 'root package' as const\n","import {join} from 'node:path'\n\nimport {parse as parseYaml} from 'yaml'\n\nimport {ENV_TEMPLATE_FILES, REQUIRED_ENV_VAR, ROOT_PACKAGE_NAME} from './constants'\nimport type {FileReader} from './fileReader'\nimport type {PackageJson, ValidationResult} from './types'\n\n/** @public */\nexport async function getMonoRepo(fileReader: FileReader): Promise<string[] | undefined> {\n  const expandWildcards = async (patterns: string[]): Promise<string[]> => {\n    return Promise.all(\n      patterns.map(async (pattern) => {\n        if (!pattern.includes('*')) return pattern.replace(/\\/$/, '')\n        const [baseDirRaw = ''] = pattern.split('/*')\n        const baseDir = baseDirRaw.replace(/\\/$/, '')\n        const contents = await fileReader.readDir(baseDir).catch(() => [])\n        return contents.map((dir) => join(baseDir, dir))\n      }),\n    ).then((results) => results.flat())\n  }\n\n  const handlers = {\n    'package.json': {\n      check: async (content: string) => {\n        try {\n          const pkg = JSON.parse(content)\n          if (!pkg.workspaces) return undefined\n          const patterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages\n          return patterns ? await expandWildcards(patterns) : undefined\n        } catch {\n          return undefined\n        }\n      },\n    },\n    'pnpm-workspace.yaml': {\n      check: async (content: string) => {\n        try {\n          const config = parseYaml(content)\n          return config.packages ? await expandWildcards(config.packages) : undefined\n        } catch {\n          return undefined\n        }\n      },\n    },\n    'lerna.json': {\n      check: async (content: string) => {\n        try {\n          const config = JSON.parse(content)\n          return config.packages ? await expandWildcards(config.packages) : undefined\n        } catch {\n          return undefined\n        }\n      },\n    },\n    'rush.json': {\n      check: async (content: string) => {\n        try {\n          const config = JSON.parse(content)\n          return config.projects?.map((p: {packageName: string}) => p.packageName)\n        } catch {\n          return undefined\n        }\n      },\n    },\n  } as const\n\n  const fileChecks = await Promise.all(\n    Object.keys(handlers).map(async (file) => {\n      const result = await fileReader.readFile(file)\n      return {file, ...result}\n    }),\n  )\n\n  for (const check of fileChecks) {\n    if (!check.exists) continue\n    const result = await handlers[check.file as keyof typeof handlers].check(check.content)\n    if (result) return result\n  }\n\n  return undefined\n}\n\n/** @public */\nasync function validatePackage(\n  fileReader: FileReader,\n  packagePath: string,\n): Promise<{\n  hasSanityConfig: boolean\n  hasSanityCli: boolean\n  hasEnvFile: boolean\n  hasSanityDep: boolean\n  errors: string[]\n}> {\n  const packageName = packagePath || ROOT_PACKAGE_NAME\n  const errors: string[] = []\n\n  const requiredFiles = [\n    'package.json',\n    'sanity.config.ts',\n    'sanity.config.js',\n    'sanity.config.tsx',\n    'sanity.cli.ts',\n    'sanity.cli.js',\n    ...ENV_TEMPLATE_FILES,\n  ]\n\n  const fileChecks = await Promise.all(\n    requiredFiles.map(async (file) => {\n      const filePath = packagePath ? join(packagePath, file) : file\n      const result = await fileReader.readFile(filePath)\n      return {file, ...result}\n    }),\n  )\n\n  const packageJson = fileChecks.find((f) => f.file === 'package.json')\n  if (!packageJson?.exists) {\n    errors.push(`Package at ${packageName} must include a package.json file`)\n  }\n\n  let hasSanityDep = false\n  if (packageJson?.exists) {\n    try {\n      const pkg: PackageJson = JSON.parse(packageJson.content)\n      hasSanityDep = Boolean(\n        pkg.dependencies?.['sanity'] ||\n        pkg.dependencies?.['next-sanity'] ||\n        pkg.dependencies?.['@sanity/client'],\n      )\n    } catch {\n      errors.push(`Invalid package.json file in ${packageName}`)\n    }\n  }\n\n  const hasSanityConfig = fileChecks.some(\n    ({exists, file}) =>\n      exists &&\n      (file === 'sanity.config.ts' || file === 'sanity.config.js' || file === 'sanity.config.tsx'),\n  )\n\n  const hasSanityCli = fileChecks.some(\n    ({exists, file}) => exists && (file === 'sanity.cli.ts' || file === 'sanity.cli.js'),\n  )\n\n  const envFile = fileChecks.find(\n    ({exists, file}) =>\n      exists && ENV_TEMPLATE_FILES.includes(file as (typeof ENV_TEMPLATE_FILES)[number]),\n  )\n\n  if (envFile) {\n    const envContent = envFile.content\n    const hasSpacesBeforeEqual = /\\w+\\s+=/.test(envContent)\n    if (hasSpacesBeforeEqual) {\n      errors.push(\n        `Environment template in ${packageName} contains invalid environment variable syntax. Please see https://dotenvx.com/docs/env-file for proper formatting.`,\n      )\n    }\n\n    for (const [name, pattern] of Object.entries(REQUIRED_ENV_VAR)) {\n      if (!envContent.match(pattern)) {\n        errors.push(`Environment template in ${packageName} is missing required variable: ${name}`)\n      }\n    }\n  }\n\n  return {\n    hasSanityConfig,\n    hasSanityCli,\n    hasEnvFile: Boolean(envFile),\n    hasSanityDep,\n    errors,\n  }\n}\n\n/** @public */\nexport async function validateTemplate(\n  fileReader: FileReader,\n  packages: string[] = [''],\n): Promise<ValidationResult> {\n  const errors: string[] = []\n  const validations = await Promise.all(packages.map((pkg) => validatePackage(fileReader, pkg)))\n\n  for (const v of validations) {\n    errors.push(...v.errors)\n  }\n\n  const hasSanityDep = validations.some((v) => v.hasSanityDep)\n  if (!hasSanityDep) {\n    errors.push('At least one package must include \"sanity\" as a dependency in package.json')\n  }\n\n  const hasSanityConfig = validations.some((v) => v.hasSanityConfig)\n  if (!hasSanityConfig) {\n    errors.push('At least one package must include a sanity.config.[js|ts|tsx] file')\n  }\n\n  const hasSanityCli = validations.some((v) => v.hasSanityCli)\n  if (!hasSanityCli) {\n    errors.push('At least one package must include a sanity.cli.[js|ts] file')\n  }\n\n  const missingEnvTemplates = packages\n    .filter((_, i) => validations[i] && validations[i].hasSanityDep && !validations[i].hasEnvFile)\n    .map((p) => p || ROOT_PACKAGE_NAME)\n  const envExamples = ENV_TEMPLATE_FILES.join(', ')\n  const missingTemplatesStr = missingEnvTemplates.join(', ')\n  if (missingEnvTemplates.length) {\n    errors.push(`Missing env template in packages: ${missingTemplatesStr}. [${envExamples}]`)\n  } else if (!validations.some((v) => v.hasEnvFile)) {\n    errors.push(`At least one package must include an env template file [${envExamples}]`)\n  }\n\n  return {\n    isValid: errors.length === 0,\n    errors,\n  }\n}\n","import {LocalFileReader} from './utils/fileReader'\nimport type {ValidationResult} from './utils/types'\nimport {getMonoRepo, validateTemplate} from './utils/validator'\n\n/** @public */\nexport async function validateLocalTemplate(directory: string): Promise<ValidationResult> {\n  const fileReader = new LocalFileReader(directory)\n  const packages = (await getMonoRepo(fileReader)) || ['']\n  return validateTemplate(fileReader, packages)\n}\n"],"names":["join","readFile","readdir","parseYaml"],"mappings":"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"}