|
@@ -412,6 +412,82 @@ function printIndexResult(clack: typeof import('@clack/prompts'), result: IndexR
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * When an `init`/`index` produced an EMPTY graph and the reason is that the
|
|
|
|
|
+ * project's own `.gitignore` excludes nested git repositories — the "super-repo
|
|
|
|
|
+ * gitignores its child repos" layout (#1156), where `init` at the parent
|
|
|
|
|
+ * correctly indexes ~nothing while `init` inside each child works — name those
|
|
|
|
|
+ * repos and offer to index them. An interactive terminal gets a yes/no prompt
|
|
|
|
|
+ * that writes `includeIgnored` to codegraph.json and re-indexes; a
|
|
|
|
|
+ * non-interactive run just prints the one-line opt-in snippet. The caller gates
|
|
|
|
|
+ * this on `nodesCreated === 0`, so a project that DID index real content is
|
|
|
|
|
+ * never nagged about the gitignored reference clones it deliberately keeps out
|
|
|
|
|
+ * (#970, #1065). Best-effort throughout: detection never breaks the command.
|
|
|
|
|
+ */
|
|
|
|
|
+async function offerIndexIgnoredRepos(
|
|
|
|
|
+ clack: typeof import('@clack/prompts'),
|
|
|
|
|
+ projectPath: string,
|
|
|
|
|
+ reindex: () => Promise<IndexResult>,
|
|
|
|
|
+ opts: { interactive: boolean },
|
|
|
|
|
+): Promise<IndexResult | undefined> {
|
|
|
|
|
+ let repos: string[];
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { findUnindexedIgnoredRepos } = await import('../extraction');
|
|
|
|
|
+ repos = findUnindexedIgnoredRepos(projectPath);
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return; // detection is advisory — never let it break the command
|
|
|
|
|
+ }
|
|
|
|
|
+ if (repos.length === 0) return;
|
|
|
|
|
+
|
|
|
|
|
+ const { PROJECT_CONFIG_FILENAME } = await import('../project-config');
|
|
|
|
|
+ const isOne = repos.length === 1;
|
|
|
|
|
+ const SHOWN = 6;
|
|
|
|
|
+ const names = repos.slice(0, SHOWN).map((r) => r.replace(/\/$/, ''));
|
|
|
|
|
+ const extra = repos.length > SHOWN ? ` (+${formatNumber(repos.length - SHOWN)} more)` : '';
|
|
|
|
|
+ const snippet = `{ "includeIgnored": [${repos.map((p) => JSON.stringify(p)).join(', ')}] }`;
|
|
|
|
|
+
|
|
|
|
|
+ clack.log.warn(
|
|
|
|
|
+ `Your .gitignore excludes ${isOne ? 'a nested git repository' : `${formatNumber(repos.length)} nested git repositories`} here, ` +
|
|
|
|
|
+ `so ${isOne ? 'it was' : 'they were'} not indexed: ${names.join(', ')}${extra}.`,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const manualHint = () => {
|
|
|
|
|
+ clack.log.info(
|
|
|
|
|
+ `If ${isOne ? "it's" : "they're"} your code, add ${isOne ? 'it' : 'them'} to ${PROJECT_CONFIG_FILENAME} and re-index:`,
|
|
|
|
|
+ );
|
|
|
|
|
+ clack.log.info(` ${snippet}`);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ if (!opts.interactive || !process.stdin.isTTY) {
|
|
|
|
|
+ manualHint();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const yes = await clack.confirm({
|
|
|
|
|
+ message: `Index ${isOne ? 'it' : `these ${formatNumber(repos.length)}`} now? Adds ${isOne ? 'it' : 'them'} to ${PROJECT_CONFIG_FILENAME}.`,
|
|
|
|
|
+ initialValue: true,
|
|
|
|
|
+ });
|
|
|
|
|
+ if (clack.isCancel(yes) || !yes) {
|
|
|
|
|
+ manualHint();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let added: number;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { addIncludeIgnoredPatterns } = await import('../project-config');
|
|
|
|
|
+ added = addIncludeIgnoredPatterns(projectPath, repos);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ clack.log.error(`Could not update ${PROJECT_CONFIG_FILENAME}: ${err instanceof Error ? err.message : String(err)}`);
|
|
|
|
|
+ manualHint();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ clack.log.success(`Added ${formatNumber(added)} ${added === 1 ? 'entry' : 'entries'} to ${PROJECT_CONFIG_FILENAME} ${getGlyphs().dash} re-indexing…`);
|
|
|
|
|
+
|
|
|
|
|
+ const result = await reindex();
|
|
|
|
|
+ printIndexResult(clack, result, projectPath);
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Write detailed error log to .codegraph/errors.log
|
|
* Write detailed error log to .codegraph/errors.log
|
|
|
*/
|
|
*/
|
|
@@ -522,28 +598,34 @@ program
|
|
|
// accepted (so existing muscle memory and scripts don't break) but is a
|
|
// accepted (so existing muscle memory and scripts don't break) but is a
|
|
|
// no-op — initializing always builds the initial index.
|
|
// no-op — initializing always builds the initial index.
|
|
|
// Supervise the index: self-terminate if orphaned or wedged (#999).
|
|
// Supervise the index: self-terminate if orphaned or wedged (#999).
|
|
|
- const supervision = installCommandSupervision('init');
|
|
|
|
|
- let result: IndexResult;
|
|
|
|
|
- try {
|
|
|
|
|
- if (options.verbose) {
|
|
|
|
|
- result = await cg.indexAll({
|
|
|
|
|
- onProgress: createVerboseProgress(),
|
|
|
|
|
- verbose: true,
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+ // A closure so we can re-run the exact same supervised, progress-rendered
|
|
|
|
|
+ // index if the user opts gitignored child repos in below (#1156).
|
|
|
|
|
+ const runIndex = async (): Promise<IndexResult> => {
|
|
|
|
|
+ const supervision = installCommandSupervision('init');
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (options.verbose) {
|
|
|
|
|
+ return await cg.indexAll({ onProgress: createVerboseProgress(), verbose: true });
|
|
|
|
|
+ }
|
|
|
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
|
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
|
|
const progress = createShimmerProgress();
|
|
const progress = createShimmerProgress();
|
|
|
- result = await cg.indexAll({
|
|
|
|
|
- onProgress: progress.onProgress,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const r = await cg.indexAll({ onProgress: progress.onProgress });
|
|
|
await progress.stop();
|
|
await progress.stop();
|
|
|
|
|
+ return r;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ supervision.stop();
|
|
|
}
|
|
}
|
|
|
- } finally {
|
|
|
|
|
- supervision.stop();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ const result = await runIndex();
|
|
|
printIndexResult(clack, result, projectPath);
|
|
printIndexResult(clack, result, projectPath);
|
|
|
await recordIndexTelemetry(cg, result);
|
|
await recordIndexTelemetry(cg, result);
|
|
|
|
|
|
|
|
|
|
+ // An empty graph at a git super-repo usually means `.gitignore` excludes
|
|
|
|
|
+ // the child repos that hold the code — surface them and offer to opt in
|
|
|
|
|
+ // rather than leaving the user with a silent 0-node "Done". (#1156)
|
|
|
|
|
+ if (result.nodesCreated === 0) {
|
|
|
|
|
+ await offerIndexIgnoredRepos(clack, projectPath, runIndex, { interactive: true });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
const { offerWatchFallback } = await import('../installer');
|
|
const { offerWatchFallback } = await import('../installer');
|
|
|
await offerWatchFallback(clack, projectPath);
|
|
await offerWatchFallback(clack, projectPath);
|
|
@@ -672,26 +754,32 @@ program
|
|
|
const clack = await importESM('@clack/prompts');
|
|
const clack = await importESM('@clack/prompts');
|
|
|
clack.intro('Indexing project');
|
|
clack.intro('Indexing project');
|
|
|
|
|
|
|
|
- let result: IndexResult;
|
|
|
|
|
-
|
|
|
|
|
- if (options.verbose) {
|
|
|
|
|
- result = await cg.indexAll({
|
|
|
|
|
- onProgress: createVerboseProgress(),
|
|
|
|
|
- verbose: true,
|
|
|
|
|
- });
|
|
|
|
|
- } else {
|
|
|
|
|
|
|
+ // A closure so a re-index (after opting gitignored child repos in, #1156)
|
|
|
|
|
+ // renders identically. Supervision already wraps the whole command.
|
|
|
|
|
+ const renderIndex = async (): Promise<IndexResult> => {
|
|
|
|
|
+ if (options.verbose) {
|
|
|
|
|
+ return await cg.indexAll({ onProgress: createVerboseProgress(), verbose: true });
|
|
|
|
|
+ }
|
|
|
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
|
process.stdout.write(`${colors.dim}${getGlyphs().rail}${colors.reset}\n`);
|
|
|
const progress = createShimmerProgress();
|
|
const progress = createShimmerProgress();
|
|
|
- result = await cg.indexAll({
|
|
|
|
|
- onProgress: progress.onProgress,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const r = await cg.indexAll({ onProgress: progress.onProgress });
|
|
|
await progress.stop();
|
|
await progress.stop();
|
|
|
- }
|
|
|
|
|
|
|
+ return r;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const result = await renderIndex();
|
|
|
|
|
|
|
|
printIndexResult(clack, result, projectPath);
|
|
printIndexResult(clack, result, projectPath);
|
|
|
await recordIndexTelemetry(cg, result);
|
|
await recordIndexTelemetry(cg, result);
|
|
|
|
|
|
|
|
- if (!result.success) {
|
|
|
|
|
|
|
+ // Empty graph at a git super-repo → likely `.gitignore`d child repos;
|
|
|
|
|
+ // name them and offer to opt in instead of a silent 0-node result (#1156).
|
|
|
|
|
+ let finalResult = result;
|
|
|
|
|
+ if (result.nodesCreated === 0) {
|
|
|
|
|
+ finalResult = (await offerIndexIgnoredRepos(clack, projectPath, renderIndex, { interactive: true })) ?? result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!finalResult.success) {
|
|
|
process.exit(1);
|
|
process.exit(1);
|
|
|
}
|
|
}
|
|
|
|
|
|