git0 0.2.99 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -23601,6 +23601,9 @@ var chalk = createChalk();
23601
23601
  var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
23602
23602
  var source_default = chalk;
23603
23603
 
23604
+ // src/cli.ts
23605
+ import path6 from "path";
23606
+
23604
23607
  // node_modules/grab-api.js/dist/grab-api.es.js
23605
23608
  function log(message = "", options = {}) {
23606
23609
  let {
@@ -27326,11 +27329,60 @@ function getAvailableDirectoryName(basePath) {
27326
27329
  counter++;
27327
27330
  }
27328
27331
  }
27329
- async function streamTarball(res, extractPath) {
27332
+ function normalizeSubPath(raw) {
27333
+ if (!raw)
27334
+ return "";
27335
+ const segments = raw.replace(/\\/g, "/").split("/").filter((segment) => segment.length > 0 && segment !== ".");
27336
+ if (segments.includes("..")) {
27337
+ throw new Error(`Invalid path "${raw}" — ".." segments are not allowed.`);
27338
+ }
27339
+ return segments.join("/");
27340
+ }
27341
+ function subPathFilter(subPath) {
27342
+ if (!subPath)
27343
+ return () => true;
27344
+ const prefix = `${subPath}/`;
27345
+ return (entryPath) => {
27346
+ const relative = entryPath.replace(/\\/g, "/").split("/").slice(1).join("/").replace(/\/+$/, "");
27347
+ return relative === subPath || relative.startsWith(prefix);
27348
+ };
27349
+ }
27350
+ function hoistSubPath(extractPath, subPath) {
27351
+ if (!subPath)
27352
+ return;
27353
+ const landed = path2.join(extractPath, subPath);
27354
+ if (!fs2.existsSync(landed)) {
27355
+ throw new Error(`"${subPath}" was not found in this repository — check the path and the branch.`);
27356
+ }
27357
+ const staging = fs2.mkdtempSync(path2.join(path2.dirname(extractPath), ".git0-hoist-"));
27358
+ const parked = path2.join(staging, "payload");
27359
+ try {
27360
+ fs2.renameSync(landed, parked);
27361
+ fs2.rmSync(path2.join(extractPath, subPath.split("/")[0]), { recursive: true, force: true });
27362
+ if (fs2.statSync(parked).isDirectory()) {
27363
+ for (const child of fs2.readdirSync(parked)) {
27364
+ fs2.renameSync(path2.join(parked, child), path2.join(extractPath, child));
27365
+ }
27366
+ } else {
27367
+ fs2.renameSync(parked, path2.join(extractPath, path2.basename(subPath)));
27368
+ }
27369
+ } finally {
27370
+ fs2.rmSync(staging, { recursive: true, force: true });
27371
+ }
27372
+ }
27373
+ function subPathDirName(subPath, type) {
27374
+ if (!subPath)
27375
+ return;
27376
+ const segments = subPath.split("/");
27377
+ if (type === "blob")
27378
+ segments.pop();
27379
+ return segments.pop();
27380
+ }
27381
+ async function streamTarball(res, extractPath, subPath = "") {
27330
27382
  const { Readable } = await import("stream");
27331
27383
  const nodeStream = Readable.fromWeb(res);
27332
27384
  await new Promise((resolve, reject) => {
27333
- nodeStream.pipe(fo({ C: extractPath, strip: 1 })).on("finish", resolve).on("error", reject);
27385
+ nodeStream.pipe(fo({ C: extractPath, strip: 1, filter: subPathFilter(subPath) })).on("finish", resolve).on("error", reject);
27334
27386
  });
27335
27387
  }
27336
27388
  async function streamToFile(res, dest) {
@@ -27340,22 +27392,31 @@ async function streamToFile(res, dest) {
27340
27392
  nodeStream.pipe(fs2.createWriteStream(dest)).on("finish", resolve).on("error", reject);
27341
27393
  });
27342
27394
  }
27343
- async function downloadRepo(callGithub, repo, targetDir = null) {
27395
+ async function downloadRepo(callGithub, repo, targetDir = null, options = {}) {
27344
27396
  const parsed = import_git_url_parse.default(repo);
27345
27397
  if (parsed.owner.includes("/")) {
27346
27398
  parsed.owner = parsed.owner.split("/").slice(-1)[0];
27347
27399
  }
27348
- const defaultDir = path2.resolve(process.cwd(), targetDir?.length ? targetDir : parsed.name);
27400
+ const subPath = normalizeSubPath(options.subPath);
27401
+ const defaultName = (targetDir?.length ? targetDir : undefined) ?? subPathDirName(subPath, options.subPathType) ?? parsed.name;
27402
+ const defaultDir = path2.resolve(process.cwd(), defaultName);
27349
27403
  const extractPath = getAvailableDirectoryName(defaultDir);
27350
27404
  fs2.mkdirSync(extractPath, { recursive: true });
27351
- console.log(source_default.blue(`\uD83D\uDCE6 Downloading ${parsed.name} into ${path2.basename(extractPath)}...`));
27352
- const defaultBranch = parsed.default_branch || "master";
27405
+ const what = subPath ? `${parsed.name}/${subPath}` : parsed.name;
27406
+ console.log(source_default.blue(`\uD83D\uDCE6 Downloading ${what} into ${path2.basename(extractPath)}...`));
27407
+ const requestedRef = options.ref || parsed.ref || "";
27408
+ const defaultBranch = requestedRef || parsed.default_branch || "master";
27353
27409
  const tarballUrl = `/repos/${parsed.owner}/${parsed.name}/tarball/${defaultBranch}`;
27354
- const params = { onStream: (res) => streamTarball(res, extractPath) };
27410
+ const params = { onStream: (res) => streamTarball(res, extractPath, subPath) };
27355
27411
  let response = await callGithub(tarballUrl, params);
27412
+ if (response.error && !requestedRef) {
27413
+ response = await callGithub(tarballUrl.replace(/\/master$/, "/main"), params);
27414
+ }
27356
27415
  if (response.error) {
27357
- response = await callGithub(tarballUrl.replace("/master", "/main"), params);
27416
+ fs2.rmSync(extractPath, { recursive: true, force: true });
27417
+ throw new Error(`Could not download ${parsed.owner}/${parsed.name}${requestedRef ? ` at ${requestedRef}` : ""}: ${response.error}`);
27358
27418
  }
27419
+ hoistSubPath(extractPath, subPath);
27359
27420
  return extractPath;
27360
27421
  }
27361
27422
  async function downloadPackage(callGithub, packageURL, downloadPath) {
@@ -27421,6 +27482,36 @@ function printInstallInstructions(filePath, fileName) {
27421
27482
  }
27422
27483
 
27423
27484
  // src/github-api.ts
27485
+ function parseTarget(query) {
27486
+ const looksLikeUrl = query.includes("github.com") || query.startsWith("git@github.com:") || query.startsWith("https://") || query.startsWith("git://");
27487
+ if (!looksLikeUrl) {
27488
+ const shorthand = query.match(/^([\w-]+)\/([\w.-]+)(?:\/(.+))?$/);
27489
+ if (!shorthand)
27490
+ return false;
27491
+ const [, owner2, name, subPath = ""] = shorthand;
27492
+ return {
27493
+ owner: owner2,
27494
+ name,
27495
+ href: `https://github.com/${owner2}/${name}`,
27496
+ ref: "",
27497
+ subPath,
27498
+ subPathType: ""
27499
+ };
27500
+ }
27501
+ const parsed = import_git_url_parse2.default(query);
27502
+ if (!parsed?.owner || !parsed?.name)
27503
+ return false;
27504
+ const owner = parsed.owner.includes("/") ? parsed.owner.split("/").slice(-1)[0] : parsed.owner;
27505
+ return {
27506
+ owner,
27507
+ name: parsed.name,
27508
+ href: `https://github.com/${owner}/${parsed.name}`,
27509
+ ref: parsed.ref || "",
27510
+ subPath: parsed.filepath || "",
27511
+ subPathType: parsed.filepathtype || ""
27512
+ };
27513
+ }
27514
+
27424
27515
  class GithubAPI {
27425
27516
  static DEFAULT_RESULTS_PER_PAGE = 10;
27426
27517
  callGithub;
@@ -27442,8 +27533,8 @@ class GithubAPI {
27442
27533
  }
27443
27534
  });
27444
27535
  }
27445
- async downloadRepo(repo, targetDir = null) {
27446
- return downloadRepo(this.callGithub, repo, targetDir);
27536
+ async downloadRepo(repo, targetDir = null, options = {}) {
27537
+ return downloadRepo(this.callGithub, repo, targetDir, options);
27447
27538
  }
27448
27539
  async searchRepositories(query, options = {}) {
27449
27540
  const {
@@ -27484,6 +27575,9 @@ class GithubAPI {
27484
27575
  async downloadPackage(packageURL, downloadPath) {
27485
27576
  return downloadPackage(this.callGithub, packageURL, downloadPath);
27486
27577
  }
27578
+ parseTarget(query) {
27579
+ return parseTarget(query);
27580
+ }
27487
27581
  parseURL(query) {
27488
27582
  if (query.includes("github.com") || query.startsWith("git@github.com:") || query.startsWith("https://") || query.startsWith("git://")) {
27489
27583
  return import_git_url_parse2.default(query);
@@ -27574,7 +27668,11 @@ import fs4 from "fs";
27574
27668
 
27575
27669
  // src/utils.ts
27576
27670
  import { execSync as execSync2 } from "child_process";
27671
+ var logoPrinted = false;
27577
27672
  function printLogo() {
27673
+ if (logoPrinted)
27674
+ return;
27675
+ logoPrinted = true;
27578
27676
  console.log(source_default.cyan(` ___
27579
27677
  __ _(_)‾|_ / _ \\
27580
27678
  / _ | | __| | | |
@@ -27716,22 +27814,151 @@ async function showPackageMenu(selectedRepo, downloadPackage2, currentPlatform)
27716
27814
  await downloadPackage2(selectedPackage.asset.browser_download_url, downloadPath);
27717
27815
  }
27718
27816
 
27817
+ // src/history.ts
27818
+ import { execFile } from "child_process";
27819
+ import fs6 from "fs";
27820
+ import path5 from "path";
27821
+ import { promisify } from "util";
27822
+ var run = promisify(execFile);
27823
+ async function git(args, cwd) {
27824
+ const { stdout } = await run("git", args, { cwd, maxBuffer: 1024 * 1024 * 64 });
27825
+ return stdout.trim();
27826
+ }
27827
+ async function gitAvailable() {
27828
+ try {
27829
+ await git(["--version"]);
27830
+ return true;
27831
+ } catch {
27832
+ return false;
27833
+ }
27834
+ }
27835
+ function assertSafeRemote(url) {
27836
+ if (!/^(https?|git|file):\/\/[^\s]+$/.test(url)) {
27837
+ throw new Error(`Refusing to clone from "${url}" — expected an https:// repository URL.`);
27838
+ }
27839
+ return url;
27840
+ }
27841
+ async function attachGitHistory(extractPath, repoUrl, options = {}) {
27842
+ assertSafeRemote(repoUrl);
27843
+ const gitDir = path5.join(extractPath, ".git");
27844
+ if (fs6.existsSync(gitDir)) {
27845
+ throw new Error(`${gitDir} already exists — not overwriting it.`);
27846
+ }
27847
+ if (!await gitAvailable()) {
27848
+ throw new Error("git is not installed, so the history cannot be downloaded.");
27849
+ }
27850
+ const staging = fs6.mkdtempSync(path5.join(path5.dirname(extractPath), ".git0-history-"));
27851
+ const cloned = path5.join(staging, "repo.git");
27852
+ try {
27853
+ await git(["clone", options.mirror ? "--mirror" : "--bare", "--quiet", repoUrl, cloned]);
27854
+ fs6.renameSync(cloned, gitDir);
27855
+ } catch (error) {
27856
+ fs6.rmSync(staging, { recursive: true, force: true });
27857
+ throw error;
27858
+ }
27859
+ fs6.rmSync(staging, { recursive: true, force: true });
27860
+ await git(["config", "--local", "--bool", "core.bare", "false"], extractPath);
27861
+ await git(["config", "--local", "--bool", "core.logallrefupdates", "true"], extractPath);
27862
+ await git(["config", "--local", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"], extractPath);
27863
+ await git(["fetch", "--quiet", "origin"], extractPath).catch(() => {});
27864
+ if (options.ref) {
27865
+ await git(["symbolic-ref", "HEAD", `refs/heads/${options.ref}`], extractPath).catch(() => {});
27866
+ }
27867
+ await git(["reset", "--mixed", "--quiet", "HEAD"], extractPath).catch(() => {});
27868
+ return gitDir;
27869
+ }
27870
+ async function cloneHistoryOnly(repoUrl, targetDir, options = {}) {
27871
+ assertSafeRemote(repoUrl);
27872
+ if (fs6.existsSync(targetDir)) {
27873
+ throw new Error(`${targetDir} already exists — not overwriting it.`);
27874
+ }
27875
+ if (!await gitAvailable()) {
27876
+ throw new Error("git is not installed, so the history cannot be downloaded.");
27877
+ }
27878
+ await git(["clone", options.bare ? "--bare" : "--mirror", "--quiet", repoUrl, targetDir]);
27879
+ return targetDir;
27880
+ }
27881
+ function attachGitHistoryLazily(extractPath, repoUrl, options = {}) {
27882
+ console.log(source_default.blue("⏳ Fetching .git history in the background..."));
27883
+ return attachGitHistory(extractPath, repoUrl, options).then(() => {
27884
+ console.log(source_default.green("✅ Git history attached — `git log` is ready."));
27885
+ }, (error) => {
27886
+ console.log(source_default.yellow(`⚠️ Could not attach git history: ${error.message}`));
27887
+ console.log(source_default.gray(" The downloaded source is unaffected."));
27888
+ });
27889
+ }
27890
+
27891
+ // src/args.ts
27892
+ function parseArgs(argv) {
27893
+ const positional = [];
27894
+ const flags = { history: false, historyOnly: false, mirror: false };
27895
+ for (const arg of argv) {
27896
+ if (!arg.startsWith("--")) {
27897
+ positional.push(arg);
27898
+ continue;
27899
+ }
27900
+ const [name, ...rest] = arg.slice(2).split("=");
27901
+ const value = rest.join("=");
27902
+ if (name === "path")
27903
+ flags.path = value;
27904
+ else if (name === "branch" || name === "ref")
27905
+ flags.branch = value;
27906
+ else if (name === "history" || name === "git")
27907
+ flags.history = true;
27908
+ else if (name === "history-only" || name === "git-only")
27909
+ flags.historyOnly = true;
27910
+ else if (name === "mirror")
27911
+ flags.mirror = true;
27912
+ else
27913
+ console.log(source_default.yellow(`Ignoring unknown flag: ${arg}`));
27914
+ }
27915
+ return { positional, flags };
27916
+ }
27917
+
27719
27918
  // src/cli.ts
27720
27919
  var Github = new github_api_default({ debug: false });
27721
- async function downloadRepoAndSetup(repo, folderPath = null) {
27920
+ async function downloadRepoAndSetup(target, folderPath = null, flags = { history: false, historyOnly: false, mirror: false }) {
27722
27921
  printLogo();
27723
- const extractPath = await Github.downloadRepo(repo, folderPath);
27922
+ const ref = flags.branch || target.ref;
27923
+ const extractPath = await Github.downloadRepo(target.href, folderPath, {
27924
+ subPath: flags.path ?? target.subPath,
27925
+ subPathType: flags.path ? "" : target.subPathType,
27926
+ ref
27927
+ });
27928
+ const history = flags.history ? attachGitHistoryLazily(extractPath, target.href, { mirror: flags.mirror, ref }) : Promise.resolve();
27724
27929
  setTimeout(() => openInIDE(extractPath), 500);
27725
27930
  await installDependencies(extractPath);
27931
+ await history;
27932
+ }
27933
+ async function downloadHistoryOnly(target, flags) {
27934
+ printLogo();
27935
+ const targetDir = getAvailableDirectoryName(path6.resolve(process.cwd(), `${target.name}.git`));
27936
+ console.log(source_default.blue(`\uD83D\uDCE6 Cloning ${target.name} history into ${path6.basename(targetDir)}...`));
27937
+ try {
27938
+ await cloneHistoryOnly(target.href, targetDir, { bare: !flags.mirror });
27939
+ console.log(source_default.green(`✅ History-only clone at ${targetDir}`));
27940
+ console.log(source_default.white(" Read the log without checking anything out:"));
27941
+ console.log(source_default.gray(` git --git-dir="${targetDir}" log --all --oneline`));
27942
+ console.log(source_default.white(" Or restore a working copy from it:"));
27943
+ console.log(source_default.gray(` git clone "${targetDir}" ${target.name}`));
27944
+ } catch (error) {
27945
+ console.error(source_default.red("❌ History clone failed:"), error.message);
27946
+ process.exit(1);
27947
+ }
27726
27948
  }
27727
27949
  function formatRepoChoice(repo) {
27728
27950
  const packageInfo = repo.hasCompatibleReleases ? source_default.green(" \uD83D\uDCE6 Packages available") : repo.hasReleases ? source_default.yellow(" \uD83D\uDCE6 Packages (other platforms)") : "";
27729
27951
  return `${source_default.bold(repo.full_name)} - ${source_default.gray(repo.description || "No description")}
27730
27952
  ` + ` ${source_default.yellow(`★ ${repo.stargazers_count}`)} | ${source_default.blue(repo.language || "Unknown")}${packageInfo}`;
27731
27953
  }
27732
- async function handleRepoDownload(selectedRepo) {
27954
+ async function handleRepoDownload(selectedRepo, flags) {
27955
+ const target = Github.parseTarget(selectedRepo.url);
27956
+ if (!target) {
27957
+ console.log(source_default.yellow(`Could not parse repository URL: ${selectedRepo.url}`));
27958
+ return;
27959
+ }
27733
27960
  if (!selectedRepo.hasReleases && !selectedRepo.hasCompatibleReleases) {
27734
- await downloadRepoAndSetup(selectedRepo.url);
27961
+ await downloadRepoAndSetup(target, null, flags);
27735
27962
  return;
27736
27963
  }
27737
27964
  const { downloadChoice } = await esm_default12.prompt({
@@ -27748,20 +27975,29 @@ async function handleRepoDownload(selectedRepo) {
27748
27975
  await showPackageMenu(selectedRepo, Github.downloadPackage.bind(Github), Github.getCurrentPlatform());
27749
27976
  }
27750
27977
  if (downloadChoice === "source" || downloadChoice === "both") {
27751
- await downloadRepoAndSetup(selectedRepo.url);
27978
+ await downloadRepoAndSetup(target, null, flags);
27752
27979
  }
27753
27980
  }
27754
27981
  async function main() {
27755
27982
  printLogo();
27756
- const args = process.argv.slice(2);
27757
- if (!args.length) {
27758
- console.log(source_default.yellow("Usage: git0 <github-url | owner/repo | search-query>"));
27983
+ const { positional, flags } = parseArgs(process.argv.slice(2));
27984
+ if (!positional.length) {
27985
+ console.log(source_default.yellow("Usage: git0 <github-url | owner/repo[/path] | search-query>"));
27986
+ console.log(source_default.gray(" --path=<path> download only that folder or file"));
27987
+ console.log(source_default.gray(" --branch=<ref> download a branch, tag or commit"));
27988
+ console.log(source_default.gray(" --history also fetch .git history, in the background"));
27989
+ console.log(source_default.gray(" --history-only clone only the history, no working files"));
27990
+ console.log(source_default.gray(" --mirror keep every ref when cloning history"));
27759
27991
  process.exit(1);
27760
27992
  }
27761
- const query = args.join(" ");
27762
- const parsed = Github.parseURL(query);
27763
- if (parsed && parsed.owner && parsed.name) {
27764
- await downloadRepoAndSetup(parsed.href, args[1] ?? null);
27993
+ const query = positional.join(" ");
27994
+ const target = Github.parseTarget(query);
27995
+ if (target) {
27996
+ if (flags.historyOnly) {
27997
+ await downloadHistoryOnly(target, flags);
27998
+ return;
27999
+ }
28000
+ await downloadRepoAndSetup(target, positional[1] ?? null, flags);
27765
28001
  return;
27766
28002
  }
27767
28003
  const results = await Github.searchRepositories(query);
@@ -27775,9 +28011,12 @@ async function main() {
27775
28011
  message: "Select a repository to download:",
27776
28012
  choices: results.map((repo) => ({ name: formatRepoChoice(repo), value: repo }))
27777
28013
  });
27778
- await handleRepoDownload(selectedRepo);
28014
+ await handleRepoDownload(selectedRepo, flags);
27779
28015
  }
27780
- main();
28016
+ main().catch((error) => {
28017
+ console.error(source_default.red(`❌ ${error.message}`));
28018
+ process.exit(1);
28019
+ });
27781
28020
 
27782
- //# debugId=5F7BB20F8A6EEFB864756E2164756E21
28021
+ //# debugId=0AD82F200D77527064756E2164756E21
27783
28022
  //# sourceMappingURL=cli.js.map