@dafish/gogo-meta 1.0.0 → 1.1.1

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/README.md CHANGED
@@ -366,12 +366,19 @@ gogo project import api git@github.com:org/api.git
366
366
 
367
367
  # Import an existing local directory (reads remote from git)
368
368
  gogo project import existing-folder
369
+
370
+ # Register without cloning (clone later with gogo git update)
371
+ gogo project import api git@github.com:org/api.git --no-clone
369
372
  ```
370
373
 
374
+ | Option | Description |
375
+ |--------|-------------|
376
+ | `--no-clone` | Register project in `.gogo` without cloning |
377
+
371
378
  This will:
372
- 1. Clone the repository (if URL provided and directory doesn't exist)
379
+ 1. Clone the repository (if URL provided and directory doesn't exist, unless `--no-clone`)
373
380
  2. Add the project to `.gogo`
374
- 3. Add the path to `.gitignore`
381
+ 3. Add the path to `.gitignore` (unless `--no-clone`)
375
382
 
376
383
  ---
377
384
 
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ import { dirname, join, basename } from 'path';
6
6
  import { mkdir, appendFile, access, writeFile, readFile, unlink, symlink } from 'fs/promises';
7
7
  import { z } from 'zod';
8
8
  import pc from 'picocolors';
9
+ import { homedir } from 'os';
9
10
 
10
11
  var __defProp = Object.defineProperty;
11
12
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -635,6 +636,81 @@ function registerRunCommand(program) {
635
636
 
636
637
  // src/commands/git/clone.ts
637
638
  init_executor();
639
+
640
+ // src/core/ssh.ts
641
+ init_executor();
642
+ function extractSshHost(url) {
643
+ if (url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://")) {
644
+ return null;
645
+ }
646
+ const sshMatch = url.match(/^ssh:\/\/[^@]+@([^/:]+)/);
647
+ if (sshMatch) {
648
+ return sshMatch[1] ?? null;
649
+ }
650
+ const gitMatch = url.match(/^[^@]+@([^:]+):/);
651
+ if (gitMatch) {
652
+ return gitMatch[1] ?? null;
653
+ }
654
+ return null;
655
+ }
656
+ function extractUniqueSshHosts(urls) {
657
+ const hosts = /* @__PURE__ */ new Set();
658
+ for (const url of urls) {
659
+ const host = extractSshHost(url);
660
+ if (host) {
661
+ hosts.add(host);
662
+ }
663
+ }
664
+ return Array.from(hosts);
665
+ }
666
+ async function isHostKnown(host) {
667
+ const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
668
+ try {
669
+ const content = await readFile(knownHostsPath, "utf-8");
670
+ const hostPatterns = [
671
+ new RegExp(`^${escapeRegex(host)}[,\\s]`, "m"),
672
+ new RegExp(`^\\[${escapeRegex(host)}\\]:\\d+[,\\s]`, "m")
673
+ ];
674
+ return hostPatterns.some((pattern) => pattern.test(content));
675
+ } catch {
676
+ return false;
677
+ }
678
+ }
679
+ function escapeRegex(str) {
680
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
681
+ }
682
+ function addHostKey(host) {
683
+ const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
684
+ const result = executeSync(`ssh-keyscan -H "${host}" >> "${knownHostsPath}" 2>/dev/null`, {
685
+ cwd: process.cwd()
686
+ });
687
+ return result.exitCode === 0;
688
+ }
689
+ async function ensureSshHostsKnown(urls) {
690
+ const hosts = extractUniqueSshHosts(urls);
691
+ const added = [];
692
+ const failed = [];
693
+ if (hosts.length === 0) {
694
+ return { added, failed };
695
+ }
696
+ for (const host of hosts) {
697
+ const known = await isHostKnown(host);
698
+ if (!known) {
699
+ info(`Adding SSH host key for ${host}...`);
700
+ const success2 = addHostKey(host);
701
+ if (success2) {
702
+ success(`Added host key for ${host}`);
703
+ added.push(host);
704
+ } else {
705
+ error(`Failed to add host key for ${host}`);
706
+ failed.push(host);
707
+ }
708
+ }
709
+ }
710
+ return { added, failed };
711
+ }
712
+
713
+ // src/commands/git/clone.ts
638
714
  function extractRepoName(url) {
639
715
  const match = url.match(/\/([^/]+?)(\.git)?$/);
640
716
  return match?.[1] ?? "repo";
@@ -646,6 +722,7 @@ async function cloneCommand(url, options = {}) {
646
722
  if (await fileExists(targetDir)) {
647
723
  throw new Error(`Directory "${repoName}" already exists`);
648
724
  }
725
+ await ensureSshHostsKnown([url]);
649
726
  info(`Cloning meta repository: ${url}`);
650
727
  const cloneResult = await execute(`git clone "${url}" "${repoName}"`, { cwd });
651
728
  if (cloneResult.exitCode !== 0) {
@@ -666,6 +743,13 @@ async function cloneCommand(url, options = {}) {
666
743
  info("No child repositories defined in .gogo");
667
744
  return;
668
745
  }
746
+ const urls = projects.map(([, url2]) => url2);
747
+ const { failed: failedHosts } = await ensureSshHostsKnown(urls);
748
+ if (failedHosts.length > 0) {
749
+ warning(
750
+ `Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
751
+ );
752
+ }
669
753
  info(`Cloning ${projects.length} child repositories...`);
670
754
  let successCount = 0;
671
755
  let failCount = 0;
@@ -725,6 +809,13 @@ async function updateCommand(options = {}) {
725
809
  success("All repositories are already cloned");
726
810
  return;
727
811
  }
812
+ const urls = missing.map(([, url]) => url);
813
+ const { failed: failedHosts } = await ensureSshHostsKnown(urls);
814
+ if (failedHosts.length > 0) {
815
+ warning(
816
+ `Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
817
+ );
818
+ }
728
819
  info(`Cloning ${missing.length} missing repositories...`);
729
820
  let successCount = 0;
730
821
  let failCount = 0;
@@ -983,7 +1074,7 @@ async function getRemoteUrl(dir) {
983
1074
  }
984
1075
  return null;
985
1076
  }
986
- async function importCommand(folder, url) {
1077
+ async function importCommand(folder, url, options = {}) {
987
1078
  const cwd = process.cwd();
988
1079
  const metaDir = await getMetaDir(cwd);
989
1080
  if (!metaDir) {
@@ -1013,6 +1104,14 @@ async function importCommand(folder, url) {
1013
1104
  if (!url) {
1014
1105
  throw new Error("URL is required when importing a non-existent project");
1015
1106
  }
1107
+ if (options.noClone) {
1108
+ const config2 = await readMetaConfig(metaDir);
1109
+ const updatedConfig2 = addProject(config2, folder, url);
1110
+ await writeMetaConfig(metaDir, updatedConfig2);
1111
+ success(`Registered project "${folder}" (not cloned)`);
1112
+ info(`Run "gogo git update" to clone missing projects`);
1113
+ return;
1114
+ }
1016
1115
  info(`Cloning ${url} into ${folder}...`);
1017
1116
  const parentDir = join(metaDir, folder, "..");
1018
1117
  await mkdir(parentDir, { recursive: true });
@@ -1042,8 +1141,8 @@ function registerProjectCommands(program) {
1042
1141
  project.command("create <folder> <url>").description("Create and initialize a new child repository").action(async (folder, url) => {
1043
1142
  await createCommand(folder, url);
1044
1143
  });
1045
- project.command("import <folder> [url]").description("Import an existing repository as a child project").action(async (folder, url) => {
1046
- await importCommand(folder, url);
1144
+ project.command("import <folder> [url]").description("Import an existing repository as a child project").option("--no-clone", "Register project without cloning").action(async (folder, url, options) => {
1145
+ await importCommand(folder, url, { noClone: options.clone === false });
1047
1146
  });
1048
1147
  }
1049
1148