@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 +9 -2
- package/dist/cli.js +102 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +101 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|