-
Notifications
You must be signed in to change notification settings - Fork 857
fix: make code mode provider-aware instead of hardcoding GitHub #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Victor Micó (victor-mico)
wants to merge
2
commits into
langchain-ai:main
Choose a base branch
from
victor-mico:fix/code-mode-provider-aware
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,25 @@ | ||
| import { execFile } from "node:child_process"; | ||
| import { mkdir, readFile, writeFile } from "node:fs/promises"; | ||
| import path from "node:path"; | ||
| import { promisify } from "node:util"; | ||
| import { isFileNotFoundError } from "./fs-errors.js"; | ||
|
|
||
| const execFileAsync = promisify(execFile); | ||
|
|
||
| const OPENWIKI_AGENTS_SNIPPET_START = "<!-- OPENWIKI:START -->"; | ||
| const OPENWIKI_AGENTS_SNIPPET_END = "<!-- OPENWIKI:END -->"; | ||
| const DEFAULT_CODE_MODE_CRON = "0 8 * * *"; | ||
|
|
||
| // Which CI host OpenWiki tailors its generated artifacts to. Only "github" | ||
| // gets a committed CI workflow (GitHub Actions); the others ship as examples/ | ||
| // the user wires up themselves, so we just keep the agent-file wording honest. | ||
| type CiProvider = "github" | "gitlab" | "bitbucket" | "none"; | ||
|
|
||
| // Environment override for provider detection, useful for self-hosted hosts | ||
| // whose remote URL doesn't advertise the provider, or to opt out entirely | ||
| // (e.g. `OPENWIKI_CI_PROVIDER=none` in a CI pipeline that manages its own PRs). | ||
| const CI_PROVIDER_ENV_KEY = "OPENWIKI_CI_PROVIDER"; | ||
|
|
||
| // Root agent-instruction files OpenWiki keeps pointed at the generated wiki. | ||
| // Each is created when missing and refreshed in place when already present. | ||
| const CODE_MODE_AGENT_FILES = ["AGENTS.md", "CLAUDE.md"]; | ||
|
|
@@ -14,8 +28,95 @@ export async function ensureCodeModeRepoSetup( | |
| cwd: string, | ||
| cronExpression = DEFAULT_CODE_MODE_CRON, | ||
| ): Promise<void> { | ||
| await writeCodeModeWorkflow(cwd, cronExpression); | ||
| await writeCodeModeAgentSnippets(cwd); | ||
| const provider = await detectCiProvider(cwd); | ||
|
|
||
| if (provider === "github") { | ||
| await writeCodeModeWorkflow(cwd, cronExpression); | ||
| } | ||
|
|
||
| await writeCodeModeAgentSnippets(cwd, provider); | ||
| } | ||
|
|
||
| async function detectCiProvider(cwd: string): Promise<CiProvider> { | ||
| const override = normalizeCiProvider(process.env[CI_PROVIDER_ENV_KEY]); | ||
| if (override) { | ||
| return override; | ||
| } | ||
|
|
||
| return classifyRemoteHost(await readGitRemoteUrl(cwd)); | ||
| } | ||
|
|
||
| function normalizeCiProvider(value: string | undefined): CiProvider | null { | ||
| switch (value?.trim().toLowerCase()) { | ||
| case "github": | ||
| return "github"; | ||
| case "gitlab": | ||
| return "gitlab"; | ||
| case "bitbucket": | ||
| return "bitbucket"; | ||
| case "none": | ||
| return "none"; | ||
| default: | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // Reads the repo's push/fetch remote URL so we can infer the git host. Prefers | ||
| // `origin`, falling back to whatever remote exists. Returns null when there is | ||
| // no git repo or no remote configured. | ||
| async function readGitRemoteUrl(cwd: string): Promise<string | null> { | ||
| const originUrl = await runGitQuietly(cwd, ["remote", "get-url", "origin"]); | ||
| if (originUrl) { | ||
| return originUrl; | ||
| } | ||
|
|
||
| const remotes = await runGitQuietly(cwd, ["remote"]); | ||
| const firstRemote = remotes | ||
| ?.split(/\r?\n/u) | ||
| .map((line) => line.trim()) | ||
| .find(Boolean); | ||
| if (!firstRemote) { | ||
| return null; | ||
| } | ||
|
|
||
| return runGitQuietly(cwd, ["remote", "get-url", firstRemote]); | ||
| } | ||
|
|
||
| async function runGitQuietly( | ||
| cwd: string, | ||
| args: string[], | ||
| ): Promise<string | null> { | ||
| try { | ||
| const { stdout } = await execFileAsync("git", args, { | ||
| cwd, | ||
| maxBuffer: 1024 * 1024, | ||
| }); | ||
| const trimmed = stdout.trim(); | ||
| return trimmed.length > 0 ? trimmed : null; | ||
| } catch { | ||
| // No git binary, not a repo, or no such remote — treat as undetectable. | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| // Classifies a git remote URL by host. Enterprise/self-hosted GitHub and GitLab | ||
| // instances typically keep the provider name in their hostname, so a substring | ||
| // match is deliberate. Unknown hosts (and missing remotes) fall back to GitHub | ||
| // to preserve the historical default; use OPENWIKI_CI_PROVIDER to override. | ||
| function classifyRemoteHost(remoteUrl: string | null): CiProvider { | ||
| if (!remoteUrl) { | ||
| return "github"; | ||
| } | ||
|
|
||
| const lower = remoteUrl.toLowerCase(); | ||
| if (lower.includes("gitlab")) { | ||
| return "gitlab"; | ||
| } | ||
| if (lower.includes("bitbucket")) { | ||
| return "bitbucket"; | ||
| } | ||
|
|
||
| return "github"; | ||
| } | ||
|
|
||
| async function writeCodeModeWorkflow( | ||
|
|
@@ -32,8 +133,11 @@ async function writeCodeModeWorkflow( | |
| await writeFile(workflowPath, createCodeModeWorkflow(cronExpression), "utf8"); | ||
| } | ||
|
|
||
| async function writeCodeModeAgentSnippets(cwd: string): Promise<void> { | ||
| const snippet = createCodeModeAgentsSnippet(); | ||
| async function writeCodeModeAgentSnippets( | ||
| cwd: string, | ||
| provider: CiProvider, | ||
| ): Promise<void> { | ||
| const snippet = createCodeModeAgentsSnippet(provider); | ||
|
|
||
| await Promise.all( | ||
| CODE_MODE_AGENT_FILES.map((fileName) => | ||
|
|
@@ -121,14 +225,29 @@ jobs: | |
| `; | ||
| } | ||
|
|
||
| function createCodeModeAgentsSnippet(): string { | ||
| function createCodeModeAgentsSnippet(provider: CiProvider): string { | ||
| return `${OPENWIKI_AGENTS_SNIPPET_START} | ||
|
|
||
| ## OpenWiki | ||
|
|
||
| This repository uses OpenWiki for recurring code documentation. Start with \`openwiki/quickstart.md\`, then follow its links to architecture, workflows, domain concepts, operations, integrations, testing guidance, and source maps. | ||
|
|
||
| The scheduled OpenWiki GitHub Actions workflow refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. | ||
| The ${describeCodeModeUpdateJob(provider)} refreshes the repository wiki. Do not hand-edit generated OpenWiki pages unless explicitly asked; prefer updating source code/docs and letting OpenWiki regenerate. | ||
|
|
||
| ${OPENWIKI_AGENTS_SNIPPET_END}`; | ||
| } | ||
|
|
||
| // Provider-aware phrasing for the OPENWIKI agent-file block so the sentence is | ||
| // accurate on non-GitHub hosts instead of always claiming a GitHub Actions run. | ||
| function describeCodeModeUpdateJob(provider: CiProvider): string { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this need a default case? |
||
| switch (provider) { | ||
| case "github": | ||
| return "scheduled OpenWiki GitHub Actions workflow"; | ||
| case "gitlab": | ||
| return "scheduled OpenWiki GitLab pipeline"; | ||
| case "bitbucket": | ||
| return "scheduled OpenWiki Bitbucket pipeline"; | ||
| case "none": | ||
| return "scheduled OpenWiki update job"; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it safe to match on the entire url? should we just be matching on the host part? for example, what if someone has something like: