@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/index.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ declare function listCommands(metaConfig: MetaConfig): Array<{
|
|
|
112
112
|
name: string;
|
|
113
113
|
command: ResolvedCommand;
|
|
114
114
|
}>;
|
|
115
|
+
declare function addToGitignore(metaDir: string, entry: string): Promise<boolean>;
|
|
115
116
|
|
|
116
117
|
declare function execute(command: string, options: ExecutorOptions): Promise<ExecutorResult>;
|
|
117
118
|
declare function executeSync(command: string, options: ExecutorOptions): ExecutorResult;
|
|
@@ -164,4 +165,39 @@ declare function summary(results: {
|
|
|
164
165
|
}): void;
|
|
165
166
|
declare function formatDuration(ms: number): string;
|
|
166
167
|
|
|
167
|
-
|
|
168
|
+
/**
|
|
169
|
+
* Extracts the SSH host from a git URL.
|
|
170
|
+
* Supports formats like:
|
|
171
|
+
* - git@github.com:user/repo.git
|
|
172
|
+
* - ssh://git@github.com/user/repo.git
|
|
173
|
+
* - git@gitlab.example.com:2222:user/repo.git (custom port)
|
|
174
|
+
*
|
|
175
|
+
* Returns null for non-SSH URLs (https://, file://, etc.)
|
|
176
|
+
*/
|
|
177
|
+
declare function extractSshHost(url: string): string | null;
|
|
178
|
+
/**
|
|
179
|
+
* Extracts unique SSH hosts from a list of repository URLs.
|
|
180
|
+
*/
|
|
181
|
+
declare function extractUniqueSshHosts(urls: string[]): string[];
|
|
182
|
+
/**
|
|
183
|
+
* Checks if a host is already in the known_hosts file.
|
|
184
|
+
*/
|
|
185
|
+
declare function isHostKnown(host: string): Promise<boolean>;
|
|
186
|
+
/**
|
|
187
|
+
* Adds a host's SSH key to known_hosts using ssh-keyscan.
|
|
188
|
+
* Returns true if successful, false otherwise.
|
|
189
|
+
*/
|
|
190
|
+
declare function addHostKey(host: string): boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Ensures all SSH hosts for the given repository URLs are in known_hosts.
|
|
193
|
+
* Prompts or automatically adds missing host keys.
|
|
194
|
+
*
|
|
195
|
+
* @param urls - Array of git repository URLs
|
|
196
|
+
* @returns Object with arrays of added and failed hosts
|
|
197
|
+
*/
|
|
198
|
+
declare function ensureSshHostsKnown(urls: string[]): Promise<{
|
|
199
|
+
added: string[];
|
|
200
|
+
failed: string[];
|
|
201
|
+
}>;
|
|
202
|
+
|
|
203
|
+
export { type CommandConfig, type CommandConfigObject, CommandConfigObjectSchema, CommandConfigSchema, type CommandHandler, ConfigError, type ExecutorOptions, type ExecutorResult, type FilterOptions, LOOPRC_FILE, type LoopContext, type LoopOptions, type LoopRc, LoopRcSchema, type LoopResult, META_FILE, type MetaConfig, MetaConfigSchema, type ProjectInfo, type ResolvedCommand, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filterFromLoopRc, findFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { mkdir, appendFile, access, writeFile, readFile, unlink, symlink } from 'fs/promises';
|
|
4
4
|
import { dirname, join, basename } from 'path';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
|
+
import { homedir } from 'os';
|
|
6
7
|
import { Command } from 'commander';
|
|
7
8
|
import { readFileSync } from 'fs';
|
|
8
9
|
import { fileURLToPath } from 'url';
|
|
@@ -329,6 +330,23 @@ function listCommands(metaConfig) {
|
|
|
329
330
|
command: normalizeCommand(config)
|
|
330
331
|
}));
|
|
331
332
|
}
|
|
333
|
+
async function addToGitignore(metaDir, entry) {
|
|
334
|
+
const gitignorePath = join(metaDir, ".gitignore");
|
|
335
|
+
if (await fileExists(gitignorePath)) {
|
|
336
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
337
|
+
const lines = content.split("\n").map((line) => line.trim());
|
|
338
|
+
if (lines.includes(entry)) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
const suffix = content.endsWith("\n") ? "" : "\n";
|
|
342
|
+
await appendFile(gitignorePath, `${suffix}${entry}
|
|
343
|
+
`);
|
|
344
|
+
} else {
|
|
345
|
+
await writeFile(gitignorePath, `${entry}
|
|
346
|
+
`, "utf-8");
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
332
350
|
|
|
333
351
|
// src/core/index.ts
|
|
334
352
|
init_executor();
|
|
@@ -550,6 +568,79 @@ function hasFailures(results) {
|
|
|
550
568
|
function getExitCode(results) {
|
|
551
569
|
return hasFailures(results) ? 1 : 0;
|
|
552
570
|
}
|
|
571
|
+
|
|
572
|
+
// src/core/ssh.ts
|
|
573
|
+
init_executor();
|
|
574
|
+
function extractSshHost(url) {
|
|
575
|
+
if (url.startsWith("https://") || url.startsWith("http://") || url.startsWith("file://")) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
const sshMatch = url.match(/^ssh:\/\/[^@]+@([^/:]+)/);
|
|
579
|
+
if (sshMatch) {
|
|
580
|
+
return sshMatch[1] ?? null;
|
|
581
|
+
}
|
|
582
|
+
const gitMatch = url.match(/^[^@]+@([^:]+):/);
|
|
583
|
+
if (gitMatch) {
|
|
584
|
+
return gitMatch[1] ?? null;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
function extractUniqueSshHosts(urls) {
|
|
589
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
590
|
+
for (const url of urls) {
|
|
591
|
+
const host = extractSshHost(url);
|
|
592
|
+
if (host) {
|
|
593
|
+
hosts.add(host);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return Array.from(hosts);
|
|
597
|
+
}
|
|
598
|
+
async function isHostKnown(host) {
|
|
599
|
+
const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
|
|
600
|
+
try {
|
|
601
|
+
const content = await readFile(knownHostsPath, "utf-8");
|
|
602
|
+
const hostPatterns = [
|
|
603
|
+
new RegExp(`^${escapeRegex(host)}[,\\s]`, "m"),
|
|
604
|
+
new RegExp(`^\\[${escapeRegex(host)}\\]:\\d+[,\\s]`, "m")
|
|
605
|
+
];
|
|
606
|
+
return hostPatterns.some((pattern) => pattern.test(content));
|
|
607
|
+
} catch {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function escapeRegex(str) {
|
|
612
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
613
|
+
}
|
|
614
|
+
function addHostKey(host) {
|
|
615
|
+
const knownHostsPath = join(homedir(), ".ssh", "known_hosts");
|
|
616
|
+
const result = executeSync(`ssh-keyscan -H "${host}" >> "${knownHostsPath}" 2>/dev/null`, {
|
|
617
|
+
cwd: process.cwd()
|
|
618
|
+
});
|
|
619
|
+
return result.exitCode === 0;
|
|
620
|
+
}
|
|
621
|
+
async function ensureSshHostsKnown(urls) {
|
|
622
|
+
const hosts = extractUniqueSshHosts(urls);
|
|
623
|
+
const added = [];
|
|
624
|
+
const failed = [];
|
|
625
|
+
if (hosts.length === 0) {
|
|
626
|
+
return { added, failed };
|
|
627
|
+
}
|
|
628
|
+
for (const host of hosts) {
|
|
629
|
+
const known = await isHostKnown(host);
|
|
630
|
+
if (!known) {
|
|
631
|
+
info(`Adding SSH host key for ${host}...`);
|
|
632
|
+
const success2 = addHostKey(host);
|
|
633
|
+
if (success2) {
|
|
634
|
+
success(`Added host key for ${host}`);
|
|
635
|
+
added.push(host);
|
|
636
|
+
} else {
|
|
637
|
+
error(`Failed to add host key for ${host}`);
|
|
638
|
+
failed.push(host);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
return { added, failed };
|
|
643
|
+
}
|
|
553
644
|
async function initCommand(options = {}) {
|
|
554
645
|
const cwd = process.cwd();
|
|
555
646
|
const metaPath = join(cwd, META_FILE);
|
|
@@ -672,6 +763,7 @@ async function cloneCommand(url, options = {}) {
|
|
|
672
763
|
if (await fileExists(targetDir)) {
|
|
673
764
|
throw new Error(`Directory "${repoName}" already exists`);
|
|
674
765
|
}
|
|
766
|
+
await ensureSshHostsKnown([url]);
|
|
675
767
|
info(`Cloning meta repository: ${url}`);
|
|
676
768
|
const cloneResult = await execute(`git clone "${url}" "${repoName}"`, { cwd });
|
|
677
769
|
if (cloneResult.exitCode !== 0) {
|
|
@@ -692,6 +784,13 @@ async function cloneCommand(url, options = {}) {
|
|
|
692
784
|
info("No child repositories defined in .gogo");
|
|
693
785
|
return;
|
|
694
786
|
}
|
|
787
|
+
const urls = projects.map(([, url2]) => url2);
|
|
788
|
+
const { failed: failedHosts } = await ensureSshHostsKnown(urls);
|
|
789
|
+
if (failedHosts.length > 0) {
|
|
790
|
+
warning(
|
|
791
|
+
`Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
695
794
|
info(`Cloning ${projects.length} child repositories...`);
|
|
696
795
|
let successCount = 0;
|
|
697
796
|
let failCount = 0;
|
|
@@ -751,6 +850,13 @@ async function updateCommand(options = {}) {
|
|
|
751
850
|
success("All repositories are already cloned");
|
|
752
851
|
return;
|
|
753
852
|
}
|
|
853
|
+
const urls = missing.map(([, url]) => url);
|
|
854
|
+
const { failed: failedHosts } = await ensureSshHostsKnown(urls);
|
|
855
|
+
if (failedHosts.length > 0) {
|
|
856
|
+
warning(
|
|
857
|
+
`Could not verify SSH host keys for: ${failedHosts.join(", ")}. Clone may fail.`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
754
860
|
info(`Cloning ${missing.length} missing repositories...`);
|
|
755
861
|
let successCount = 0;
|
|
756
862
|
let failCount = 0;
|
|
@@ -1043,7 +1149,11 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1043
1149
|
const config2 = await readMetaConfig(metaDir);
|
|
1044
1150
|
const updatedConfig2 = addProject(config2, folder, url);
|
|
1045
1151
|
await writeMetaConfig(metaDir, updatedConfig2);
|
|
1152
|
+
const added2 = await addToGitignore(metaDir, folder);
|
|
1046
1153
|
success(`Registered project "${folder}" (not cloned)`);
|
|
1154
|
+
if (added2) {
|
|
1155
|
+
info(`Added ${folder} to .gitignore`);
|
|
1156
|
+
}
|
|
1047
1157
|
info(`Run "gogo git update" to clone missing projects`);
|
|
1048
1158
|
return;
|
|
1049
1159
|
}
|
|
@@ -1061,11 +1171,8 @@ async function importCommand(folder, url, options = {}) {
|
|
|
1061
1171
|
await writeMetaConfig(metaDir, updatedConfig);
|
|
1062
1172
|
success(`Imported project "${folder}"`);
|
|
1063
1173
|
}
|
|
1064
|
-
const
|
|
1065
|
-
if (
|
|
1066
|
-
await appendFile(gitignorePath, `
|
|
1067
|
-
${folder}
|
|
1068
|
-
`);
|
|
1174
|
+
const added = await addToGitignore(metaDir, folder);
|
|
1175
|
+
if (added) {
|
|
1069
1176
|
info(`Added ${folder} to .gitignore`);
|
|
1070
1177
|
}
|
|
1071
1178
|
}
|
|
@@ -1304,6 +1411,6 @@ async function main() {
|
|
|
1304
1411
|
}
|
|
1305
1412
|
main();
|
|
1306
1413
|
|
|
1307
|
-
export { CommandConfigObjectSchema, CommandConfigSchema, ConfigError, LOOPRC_FILE, LoopRcSchema, META_FILE, MetaConfigSchema, addProject, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, createProgram, dim, error, execute, executeStreaming, executeSync, fileExists, filterFromLoopRc, findFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
|
|
1414
|
+
export { CommandConfigObjectSchema, CommandConfigSchema, ConfigError, LOOPRC_FILE, LoopRcSchema, META_FILE, MetaConfigSchema, addHostKey, addProject, addToGitignore, applyFilters, bold, commandOutput, createDefaultConfig, createFilterOptions, createProgram, dim, ensureSshHostsKnown, error, execute, executeStreaming, executeSync, extractSshHost, extractUniqueSshHosts, fileExists, filterFromLoopRc, findFileUp, formatDuration, getCommand, getExitCode, getMetaDir, getProjectPaths, getProjectUrl, hasFailures, header, info, isHostKnown, listCommands, loop, normalizeCommand, parseFilterList, parseFilterPattern, projectStatus, readLoopRc, readMetaConfig, removeProject, success, summary, symbols, warning, writeMetaConfig };
|
|
1308
1415
|
//# sourceMappingURL=index.js.map
|
|
1309
1416
|
//# sourceMappingURL=index.js.map
|