@dafish/gogo-meta 1.1.0 → 1.2.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 +114 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +37 -1
- package/dist/index.js +113 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -321,6 +322,23 @@ function listCommands(metaConfig) {
|
|
|
321
322
|
command: normalizeCommand(config)
|
|
322
323
|
}));
|
|
323
324
|
}
|
|
325
|
+
async function addToGitignore(metaDir, entry) {
|
|
326
|
+
const gitignorePath = join(metaDir, ".gitignore");
|
|
327
|
+
if (await fileExists(gitignorePath)) {
|
|
328
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
329
|
+
const lines = content.split("\n").map((line) => line.trim());
|
|
330
|
+
if (lines.includes(entry)) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
334
|
+
await appendFile(gitignorePath, `${suffix}${entry}
|
|
335
|
+
`);
|
|
336
|
+
} else {
|
|
337
|
+
await writeFile(gitignorePath, `${entry}
|
|
338
|
+
`, "utf-8");
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
324
342
|
var symbols = {
|
|
325
343
|
success: pc.green("\u2713"),
|
|
326
344
|
error: pc.red("\u2717"),
|
|
@@ -635,6 +653,81 @@ function registerRunCommand(program) {
|
|
|
635
653
|
|
|
636
654
|
// src/commands/git/clone.ts
|
|
637
655
|
init_executor();
|
|
656
|
+
|
|
657
|
+
// src/core/ssh.ts
|
|
658
|
+
init_executor();
|
|
659
|
+
function extractSshHost(url) {
|
|
660
|
+
if (url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://")) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
const sshMatch = url.match(/^ssh:\/\/[^@]+@([^/:]+)/);
|
|
664
|
+
if (sshMatch) {
|
|
665
|
+
return sshMatch[1] ?? null;
|
|
666
|
+
}
|
|
667
|
+
const gitMatch = url.match(/^[^@]+@([^:]+):/);
|
|
668
|
+
if (gitMatch) {
|
|
669
|
+
return gitMatch[1] ?? null;
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
function extractUniqueSshHosts(urls) {
|
|
674
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
675
|
+
for (const url of urls) {
|
|
676
|
+
const host = extractSshHost(url);
|
|
677
|
+
if (host) {
|
|
678
|
+
hosts.add(host);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return Array.from(hosts);
|
|
682
|
+
}
|
|
683
|
+
async function isHostKnown(host) {
|
|
684
|
+
const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
|
|
685
|
+
try {
|
|
686
|
+
const content = await readFile(knownHostsPath, "utf-8");
|
|
687
|
+
const hostPatterns = [
|
|
688
|
+
new RegExp(`^${escapeRegex(host)}[,\\s]`, "m"),
|
|
689
|
+
new RegExp(`^\\[${escapeRegex(host)}\\]:\\d+[,\\s]`, "m")
|
|
690
|
+
];
|
|
691
|
+
return hostPatterns.some((pattern) => pattern.test(content));
|
|
692
|
+
} catch {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function escapeRegex(str) {
|
|
697
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
698
|
+
}
|
|
699
|
+
function addHostKey(host) {
|
|
700
|
+
const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
|
|
701
|
+
const result = executeSync(`ssh-keyscan -H "${host}" >> "${knownHostsPath}" 2>/dev/null`, {
|
|
702
|
+
cwd: process.cwd()
|
|
703
|
+
});
|
|
704
|
+
return result.exitCode === 0;
|
|
705
|
+
}
|
|
706
|
+
async function ensureSshHostsKnown(urls) {
|
|
707
|
+
const hosts = extractUniqueSshHosts(urls);
|
|
708
|
+
const added = [];
|
|
709
|
+
const failed = [];
|
|
710
|
+
if (hosts.length === 0) {
|
|
711
|
+
return { added, failed };
|
|
712
|
+
}
|
|
713
|
+
for (const host of hosts) {
|
|
714
|
+
const known = await isHostKnown(host);
|
|
715
|
+
if (!known) {
|
|
716
|
+
info(`Adding SSH host key for ${host}...`);
|
|
717
|
+
const success2 = addHostKey(host);
|
|
718
|
+
if (success2) {
|
|
719
|
+
success(`Added host key for ${host}`);
|
|
720
|
+
added.push(host);
|
|
721
|
+
} else {
|
|
722
|
+
error(`Failed to add host key for ${host}`);
|
|
723
|
+
failed.push(host);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return { added, failed };
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/commands/git/clone.ts
|
|
638
731
|
function extractRepoName(url) {
|
|
639
732
|
const match = url.match(/\/([^/]+?)(\.git)?$/);
|
|
640
733
|
return match?.[1] ?? "repo";
|
|
@@ -646,6 +739,7 @@ async function cloneCommand(url, options = {}) {
|
|
|
646
739
|
if (await fileExists(targetDir)) {
|
|
647
740
|
throw new Error(`Directory "${repoName}" already exists`);
|
|
648
741
|
}
|
|
742
|
+
await ensureSshHostsKnown([url]);
|
|
649
743
|
info(`Cloning meta repository: ${url}`);
|
|
650
744
|
const cloneResult = await execute(`git clone "${url}" "${repoName}"`, { cwd });
|
|
651
745
|
if (cloneResult.exitCode !== 0) {
|
|
@@ -666,6 +760,13 @@ async function cloneCommand(url, options = {}) {
|
|
|
666
760
|
info("No child repositories defined in .gogo");
|
|
667
761
|
return;
|
|
668
762
|
}
|
|
763
|
+
const urls = projects.map(([, url2]) => url2);
|
|
764
|
+
const { failed: failedHosts } = await ensureSshHostsKnown(urls);
|
|
765
|
+
if (failedHosts.length > 0) {
|
|
766
|
+
warning(
|
|
767
|
+
`Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
669
770
|
info(`Cloning ${projects.length} child repositories...`);
|
|
670
771
|
let successCount = 0;
|
|
671
772
|
let failCount = 0;
|
|
@@ -725,6 +826,13 @@ async function updateCommand(options = {}) {
|
|
|
725
826
|
success("All repositories are already cloned");
|
|
726
827
|
return;
|
|
727
828
|
}
|
|
829
|
+
const urls = missing.map(([, url]) => url);
|
|
830
|
+
const { failed: failedHosts } = await ensureSshHostsKnown(urls);
|
|
831
|
+
if (failedHosts.length > 0) {
|
|
832
|
+
warning(
|
|
833
|
+
`Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
728
836
|
info(`Cloning ${missing.length} missing repositories...`);
|
|
729
837
|
let successCount = 0;
|
|
730
838
|
let failCount = 0;
|
|
@@ -1017,7 +1125,11 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1017
1125
|
const config2 = await readMetaConfig(metaDir);
|
|
1018
1126
|
const updatedConfig2 = addProject(config2, folder, url);
|
|
1019
1127
|
await writeMetaConfig(metaDir, updatedConfig2);
|
|
1128
|
+
const added2 = await addToGitignore(metaDir, folder);
|
|
1020
1129
|
success(`Registered project "${folder}" (not cloned)`);
|
|
1130
|
+
if (added2) {
|
|
1131
|
+
info(`Added ${folder} to .gitignore`);
|
|
1132
|
+
}
|
|
1021
1133
|
info(`Run "gogo git update" to clone missing projects`);
|
|
1022
1134
|
return;
|
|
1023
1135
|
}
|
|
@@ -1035,11 +1147,8 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1035
1147
|
await writeMetaConfig(metaDir, updatedConfig);
|
|
1036
1148
|
success(`Imported project "${folder}"`);
|
|
1037
1149
|
}
|
|
1038
|
-
const
|
|
1039
|
-
if (
|
|
1040
|
-
await appendFile(gitignorePath, `
|
|
1041
|
-
${folder}
|
|
1042
|
-
`);
|
|
1150
|
+
const added = await addToGitignore(metaDir, folder);
|
|
1151
|
+
if (added) {
|
|
1043
1152
|
info(`Added ${folder} to .gitignore`);
|
|
1044
1153
|
}
|
|
1045
1154
|
}
|