{"version":3,"file":"cli.mjs","sources":["../src/git.ts"],"sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport {\n  resolve,\n  join,\n  normalize,\n  isAbsolute,\n  sep,\n  basename,\n} from \"node:path\";\nimport {\n  existsSync,\n  rmSync,\n  readdirSync,\n  mkdtempSync,\n  mkdirSync,\n  copyFileSync,\n  lstatSync,\n  readlinkSync,\n  symlinkSync,\n} from \"node:fs\";\nimport { tmpdir } from \"node:os\";\n\nexport const defaultScmHostPattern =\n  /^(https:\\/\\/(?:github|gitlab|bitbucket)\\.com)\\/([^\\/]+)\\/([^\\/]+)\\/(?:tree|src)\\/([^\\/]+)\\/(.+)$/;\n\nexport const performSparseCheckout = (\n  repoUrl: string,\n  destFolder?: string,\n  scmHostPattern = defaultScmHostPattern\n) => {\n  try {\n    const match = repoUrl.match(scmHostPattern);\n\n    if (!match) {\n      throw new Error(\n        \"Invalid URL format. Use a subdirectory URL (https) from GitHub, GitLab, or Bitbucket.\"\n      );\n    }\n\n    const [, platformUrl, owner, repo, branch, subdir] = match;\n\n    // Validate inputs to prevent command injection and path traversal\n    [owner, repo, branch].forEach((input) => {\n      if (!/^[\\w\\-]+$/.test(input)) {\n        throw new Error(`Invalid characters in input: ${input}`);\n      }\n    });\n\n    // Validate subdir\n    const subdirNormalized = normalize(subdir);\n\n    // Check if subdir is absolute or contains path traversal\n    if (\n      isAbsolute(subdirNormalized) ||\n      subdirNormalized.startsWith(\"..\") ||\n      subdirNormalized.includes(`${sep}..${sep}`)\n    ) {\n      throw new Error(\"Invalid subdirectory path.\");\n    }\n\n    const sanitizedSubdir = subdirNormalized;\n\n    const fallbackDestFolder = destFolder || basename(sanitizedSubdir);\n    const targetPath = resolve(process.cwd(), fallbackDestFolder);\n\n    if (existsSync(targetPath)) {\n      throw new Error(`Destination folder \"${fallbackDestFolder}\" already exists.`);\n    }\n\n    // Create the destination directory\n    mkdirSync(targetPath, { recursive: true });\n\n    // Create a temporary directory for the clone\n    const tempDir = mkdtempSync(join(tmpdir(), \"sparse-checkout-\"));\n\n    console.log(\"Cloning repository with sparse checkout into temporary directory...\");\n    const cloneResult = spawnSync(\n      \"git\",\n      [\"clone\", \"--no-checkout\", `${platformUrl}/${owner}/${repo}.git`, tempDir],\n      { stdio: \"inherit\" }\n    );\n    if (cloneResult.status !== 0) {\n      throw new Error(\"Git clone failed.\");\n    }\n\n    const subdirPath = resolve(tempDir, sanitizedSubdir);\n\n    // Ensure subdirPath is within tempDir\n    if (!subdirPath.startsWith(tempDir + sep) && subdirPath !== tempDir) {\n      throw new Error(\"Subdirectory path traversal detected.\");\n    }\n\n    console.log(\"Initializing sparse-checkout...\");\n    const initResult = spawnSync(\"git\", [\"-C\", tempDir, \"sparse-checkout\", \"init\"], {\n      stdio: \"inherit\",\n    });\n    if (initResult.status !== 0) {\n      throw new Error(\"Git sparse-checkout init failed.\");\n    }\n\n    console.log(`Setting sparse-checkout to subdirectory: ${sanitizedSubdir}`);\n    const setResult = spawnSync(\n      \"git\",\n      [\"-C\", tempDir, \"sparse-checkout\", \"set\", sanitizedSubdir],\n      { stdio: \"inherit\" }\n    );\n    if (setResult.status !== 0) {\n      throw new Error(\"Git sparse-checkout set failed.\");\n    }\n\n    console.log(`Checking out branch: ${branch}...`);\n    const checkoutResult = spawnSync(\"git\", [\"-C\", tempDir, \"checkout\", branch], {\n      stdio: \"inherit\",\n    });\n    if (checkoutResult.status !== 0) {\n      throw new Error(\"Git checkout failed.\");\n    }\n\n    if (!existsSync(subdirPath)) {\n      throw new Error(`Subdirectory \"${sanitizedSubdir}\" does not exist in the repository.`);\n    }\n\n    console.log(\"Copying files to the destination directory...\");\n    copyDirectoryContents(subdirPath, targetPath);\n\n    console.log(\"Cleaning up temporary directory...\");\n    rmSync(tempDir, { recursive: true, force: true });\n\n    console.log(\"Initializing a new git repository...\");\n    const initNewRepoResult = spawnSync(\"git\", [\"init\"], {\n      cwd: targetPath,\n      stdio: \"inherit\",\n    });\n    if (initNewRepoResult.status !== 0) {\n      throw new Error(\"Initializing new git repository failed.\");\n    }\n\n    console.log(\"🎉 All done! Your new project has been set up!\");\n\n    console.log(`\\nTo get started, run the following commands:\\n\\n  cd ${fallbackDestFolder}\\n`);\n  } catch (err) {\n    console.error(\"Error during sparse checkout:\", (err as Error).message);\n    process.exit(1);\n  }\n};\n\n/**\n * Recursively copies all files and directories from `source` to `destination`.\n */\nfunction copyDirectoryContents(source: string, destination: string) {\n  if (!existsSync(source)) {\n    throw new Error(`Source directory \"${source}\" does not exist.`);\n  }\n\n  const items = readdirSync(source);\n\n  for (const item of items) {\n    const srcPath = join(source, item);\n    const destPath = join(destination, item);\n    const stats = lstatSync(srcPath);\n\n    if (stats.isDirectory()) {\n      mkdirSync(destPath, { recursive: true });\n      copyDirectoryContents(srcPath, destPath);\n    } else if (stats.isSymbolicLink()) {\n      const symlink = readlinkSync(srcPath);\n      symlinkSync(symlink, destPath);\n    } else {\n      copyFileSync(srcPath, destPath);\n    }\n  }\n}\n"],"names":[],"mappings":";;;;;;AAsBY,CAAA,CAAA,CAAA,CAAA,CAAA,CAAC,qBAAqB,CAAG,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;"}