mirror of
https://github.com/anomalyco/opencode.git
synced 2026-03-11 17:14:11 +00:00
Compare commits
11 Commits
opencode-2
...
improve-az
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4efdb8825 | ||
|
|
5dcd374df7 | ||
|
|
9c657f703b | ||
|
|
7ec398d855 | ||
|
|
4ab35d2c5c | ||
|
|
b4ae030fc2 | ||
|
|
0843964eb3 | ||
|
|
a1b06d63c9 | ||
|
|
1b6820bab5 | ||
|
|
89bf199c07 | ||
|
|
5acfdd1c5d |
1
.opencode/.gitignore
vendored
1
.opencode/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
plans/
|
||||
bun.lock
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
@@ -4,6 +4,24 @@
|
||||
"opencode": {
|
||||
"options": {},
|
||||
},
|
||||
"azure": {
|
||||
"models": {
|
||||
"kimi-k2.5": {
|
||||
"name": "kimi-k2.5",
|
||||
"limit": {
|
||||
"context": 262144,
|
||||
"output": 10000,
|
||||
},
|
||||
"provider": {
|
||||
"npm": "@ai-sdk/openai-compatible",
|
||||
"api": "https://aiden-azury-group.services.ai.azure.com/models",
|
||||
},
|
||||
},
|
||||
},
|
||||
"options": {
|
||||
"resourceName": "aiden-azury-group",
|
||||
},
|
||||
},
|
||||
},
|
||||
"permission": {
|
||||
"edit": {
|
||||
|
||||
@@ -137,4 +137,4 @@ OpenCode 内置两种 Agent,可用 `Tab` 键快速切换:
|
||||
|
||||
---
|
||||
|
||||
**加入我们的社区** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
**加入我们的社区** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode)
|
||||
|
||||
@@ -137,4 +137,4 @@ OpenCode 內建了兩種 Agent,您可以使用 `Tab` 鍵快速切換。
|
||||
|
||||
---
|
||||
|
||||
**加入我們的社群** [Discord](https://discord.gg/opencode) | [X.com](https://x.com/opencode)
|
||||
**加入我們的社群** [飞书](https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true) | [X.com](https://x.com/opencode)
|
||||
|
||||
@@ -9,14 +9,12 @@ test("/terminal toggles the terminal panel", async ({ page, gotoSession }) => {
|
||||
|
||||
await expect(terminal).not.toBeVisible()
|
||||
|
||||
await prompt.click()
|
||||
await page.keyboard.type("/terminal")
|
||||
await prompt.fill("/terminal")
|
||||
await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
await expect(terminal).toBeVisible()
|
||||
|
||||
await prompt.click()
|
||||
await page.keyboard.type("/terminal")
|
||||
await prompt.fill("/terminal")
|
||||
await expect(page.locator('[data-slash-id="terminal.toggle"]').first()).toBeVisible()
|
||||
await page.keyboard.press("Enter")
|
||||
await expect(terminal).not.toBeVisible()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const promptSelector = '[data-component="prompt-input"]'
|
||||
export const terminalSelector = '[data-component="terminal"]'
|
||||
export const terminalPanelSelector = '#terminal-panel[aria-hidden="false"]'
|
||||
export const terminalSelector = `${terminalPanelSelector} [data-component="terminal"]`
|
||||
export const sessionComposerDockSelector = '[data-component="session-prompt-dock"]'
|
||||
export const questionDockSelector = '[data-component="dock-prompt"][data-kind="question"]'
|
||||
export const permissionDockSelector = '[data-component="dock-prompt"][data-kind="permission"]'
|
||||
|
||||
@@ -8,6 +8,12 @@ import { useI18n } from "~/context/i18n"
|
||||
export function Footer() {
|
||||
const language = useLanguage()
|
||||
const i18n = useI18n()
|
||||
const community = createMemo(() => {
|
||||
const locale = language.locale()
|
||||
return locale === "zh" || locale === "zht"
|
||||
? ({ key: "footer.feishu", link: language.route("/feishu") } as const)
|
||||
: ({ key: "footer.discord", link: language.route("/discord") } as const)
|
||||
})
|
||||
const githubData = createAsync(() => github())
|
||||
const starCount = createMemo(() =>
|
||||
githubData()?.stars
|
||||
@@ -32,7 +38,7 @@ export function Footer() {
|
||||
<a href={language.route("/changelog")}>{i18n.t("footer.changelog")}</a>
|
||||
</div>
|
||||
<div data-slot="cell">
|
||||
<a href={language.route("/discord")}>{i18n.t("footer.discord")}</a>
|
||||
<a href={community().link}>{i18n.t(community().key)}</a>
|
||||
</div>
|
||||
<div data-slot="cell">
|
||||
<a href={config.social.twitter}>{i18n.t("footer.x")}</a>
|
||||
|
||||
@@ -21,6 +21,7 @@ export const dict = {
|
||||
"footer.github": "GitHub",
|
||||
"footer.docs": "Docs",
|
||||
"footer.changelog": "Changelog",
|
||||
"footer.feishu": "Feishu",
|
||||
"footer.discord": "Discord",
|
||||
"footer.x": "X",
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export const dict = {
|
||||
"footer.github": "GitHub",
|
||||
"footer.docs": "文档",
|
||||
"footer.changelog": "更新日志",
|
||||
"footer.feishu": "飞书",
|
||||
"footer.discord": "Discord",
|
||||
"footer.x": "X",
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export const dict = {
|
||||
"footer.github": "GitHub",
|
||||
"footer.docs": "文件",
|
||||
"footer.changelog": "更新日誌",
|
||||
"footer.feishu": "飞书",
|
||||
"footer.discord": "Discord",
|
||||
"footer.x": "X",
|
||||
|
||||
|
||||
7
packages/console/app/src/routes/feishu.ts
Normal file
7
packages/console/app/src/routes/feishu.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { redirect } from "@solidjs/router"
|
||||
|
||||
export async function GET() {
|
||||
return redirect(
|
||||
"https://applink.feishu.cn/client/chat/chatter/add_by_link?link_token=de8k6664-1b5e-43f2-8efd-21d6772647b5&qr_code=true",
|
||||
)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export function syncCli() {
|
||||
|
||||
let version = ""
|
||||
try {
|
||||
version = execFileSync(installPath, ["--version"]).toString().trim()
|
||||
version = execFileSync(installPath, ["--version"], { windowsHide: true }).toString().trim()
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -147,7 +147,7 @@ export function spawnCommand(args: string, extraEnv: Record<string, string>) {
|
||||
console.log(`[cli] Executing: ${cmd} ${cmdArgs.join(" ")}`)
|
||||
const child = spawn(cmd, cmdArgs, {
|
||||
env: envs,
|
||||
detached: true,
|
||||
detached: process.platform !== "win32",
|
||||
windowsHide: true,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
})
|
||||
|
||||
@@ -192,11 +192,17 @@ export class AccountService extends ServiceMap.Service<
|
||||
|
||||
const orgsByAccount = Effect.fn("AccountService.orgsByAccount")(function* () {
|
||||
const accounts = yield* repo.list()
|
||||
return yield* Effect.forEach(
|
||||
const [errors, results] = yield* Effect.partition(
|
||||
accounts,
|
||||
(account) => orgs(account.id).pipe(Effect.map((orgs) => ({ account, orgs }))),
|
||||
{ concurrency: 3 },
|
||||
)
|
||||
for (const error of errors) {
|
||||
yield* Effect.logWarning("failed to fetch orgs for account").pipe(
|
||||
Effect.annotateLogs({ error: String(error) }),
|
||||
)
|
||||
}
|
||||
return results
|
||||
})
|
||||
|
||||
const orgs = Effect.fn("AccountService.orgs")(function* (accountID: AccountID) {
|
||||
|
||||
@@ -114,6 +114,7 @@ export namespace LSP {
|
||||
return {
|
||||
process: spawn(item.command[0], item.command.slice(1), {
|
||||
cwd: root,
|
||||
windowsHide: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...item.env,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "child_process"
|
||||
import { spawn as launch, type ChildProcessWithoutNullStreams } from "child_process"
|
||||
import path from "path"
|
||||
import os from "os"
|
||||
import { Global } from "../global"
|
||||
@@ -14,6 +14,11 @@ import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
import { Module } from "@opencode-ai/util/module"
|
||||
|
||||
const spawn = ((cmd, args, opts) => {
|
||||
if (Array.isArray(args)) return launch(cmd, [...args], { ...(opts ?? {}), windowsHide: true })
|
||||
return launch(cmd, { ...(args ?? {}), windowsHide: true })
|
||||
}) as typeof launch
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
const pathExists = async (p: string) =>
|
||||
|
||||
@@ -40,14 +40,6 @@ export namespace ProviderError {
|
||||
return /^4(00|13)\s*(status code)?\s*\(no body\)/i.test(message)
|
||||
}
|
||||
|
||||
function error(providerID: string, error: APICallError) {
|
||||
if (providerID.includes("github-copilot") && error.statusCode === 403) {
|
||||
return "Please reauthenticate with the copilot provider to ensure your credentials work properly with OpenCode."
|
||||
}
|
||||
|
||||
return error.message
|
||||
}
|
||||
|
||||
function message(providerID: string, e: APICallError) {
|
||||
return iife(() => {
|
||||
const msg = e.message
|
||||
@@ -60,10 +52,6 @@ export namespace ProviderError {
|
||||
return "Unknown error"
|
||||
}
|
||||
|
||||
const transformed = error(providerID, e)
|
||||
if (transformed !== msg) {
|
||||
return transformed
|
||||
}
|
||||
if (!e.responseBody || (e.statusCode && msg !== STATUS_CODES[e.statusCode])) {
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -51,40 +51,15 @@ const DEFAULT_CHUNK_TIMEOUT = 120_000
|
||||
export namespace Provider {
|
||||
const log = Log.create({ service: "provider" })
|
||||
|
||||
function isGpt5OrLater(modelID: string): boolean {
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (!match) {
|
||||
return false
|
||||
}
|
||||
return Number(match[1]) >= 5
|
||||
}
|
||||
|
||||
function shouldUseCopilotResponsesApi(modelID: string): boolean {
|
||||
return isGpt5OrLater(modelID) && !modelID.startsWith("gpt-5-mini")
|
||||
}
|
||||
|
||||
function googleVertexVars(options: Record<string, any>) {
|
||||
const project =
|
||||
options["project"] ?? Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
|
||||
const location =
|
||||
options["location"] ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1"
|
||||
const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`
|
||||
|
||||
return {
|
||||
GOOGLE_VERTEX_PROJECT: project,
|
||||
GOOGLE_VERTEX_LOCATION: location,
|
||||
GOOGLE_VERTEX_ENDPOINT: endpoint,
|
||||
}
|
||||
}
|
||||
|
||||
function loadBaseURL(model: Model, options: Record<string, any>) {
|
||||
const raw = options["baseURL"] ?? model.api.url
|
||||
if (typeof raw !== "string") return raw
|
||||
const vars = model.providerID === "google-vertex" ? googleVertexVars(options) : undefined
|
||||
return raw.replace(/\$\{([^}]+)\}/g, (match, key) => {
|
||||
const val = Env.get(String(key)) ?? vars?.[String(key) as keyof typeof vars]
|
||||
return val ?? match
|
||||
const isGpt5OrLater = iife(() => {
|
||||
const match = /^gpt-(\d+)/.exec(modelID)
|
||||
if (!match) {
|
||||
return false
|
||||
}
|
||||
return Number(match[1]) >= 5
|
||||
})
|
||||
return isGpt5OrLater && !modelID.startsWith("gpt-5-mini")
|
||||
}
|
||||
|
||||
function wrapSSE(res: Response, ms: number, ctl: AbortController) {
|
||||
@@ -161,12 +136,18 @@ export namespace Provider {
|
||||
}
|
||||
|
||||
type CustomModelLoader = (sdk: any, modelID: string, options?: Record<string, any>) => Promise<any>
|
||||
type CustomVarsLoader = (options: Record<string, any>) => Record<string, string>
|
||||
type CustomLoader = (provider: Info) => Promise<{
|
||||
autoload: boolean
|
||||
getModel?: CustomModelLoader
|
||||
vars?: CustomVarsLoader
|
||||
options?: Record<string, any>
|
||||
}>
|
||||
|
||||
function useLanguageModel(sdk: any) {
|
||||
return sdk.responses === undefined && sdk.chat === undefined
|
||||
}
|
||||
|
||||
const CUSTOM_LOADERS: Record<string, CustomLoader> = {
|
||||
async anthropic() {
|
||||
return {
|
||||
@@ -214,7 +195,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID)
|
||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
||||
return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
|
||||
},
|
||||
options: {},
|
||||
@@ -224,7 +205,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, _options?: Record<string, any>) {
|
||||
if (sdk.responses === undefined && sdk.chat === undefined) return sdk.languageModel(modelID)
|
||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
||||
return shouldUseCopilotResponsesApi(modelID) ? sdk.responses(modelID) : sdk.chat(modelID)
|
||||
},
|
||||
options: {},
|
||||
@@ -234,6 +215,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
|
||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
||||
if (options?.["useCompletionUrls"]) {
|
||||
return sdk.chat(modelID)
|
||||
} else {
|
||||
@@ -248,6 +230,7 @@ export namespace Provider {
|
||||
return {
|
||||
autoload: false,
|
||||
async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
|
||||
if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
|
||||
if (options?.["useCompletionUrls"]) {
|
||||
return sdk.chat(modelID)
|
||||
} else {
|
||||
@@ -430,19 +413,33 @@ export namespace Provider {
|
||||
}
|
||||
},
|
||||
"google-vertex": async (provider) => {
|
||||
const project =
|
||||
const project = String(
|
||||
provider.options?.project ??
|
||||
Env.get("GOOGLE_CLOUD_PROJECT") ??
|
||||
Env.get("GCP_PROJECT") ??
|
||||
Env.get("GCLOUD_PROJECT")
|
||||
Env.get("GOOGLE_CLOUD_PROJECT") ??
|
||||
Env.get("GCP_PROJECT") ??
|
||||
Env.get("GCLOUD_PROJECT"),
|
||||
)
|
||||
|
||||
const location =
|
||||
provider.options?.location ?? Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "us-central1"
|
||||
const location = String(
|
||||
provider.options?.location ??
|
||||
Env.get("GOOGLE_VERTEX_LOCATION") ??
|
||||
Env.get("GOOGLE_CLOUD_LOCATION") ??
|
||||
Env.get("VERTEX_LOCATION") ??
|
||||
"us-central1",
|
||||
)
|
||||
|
||||
const autoload = Boolean(project)
|
||||
if (!autoload) return { autoload: false }
|
||||
return {
|
||||
autoload: true,
|
||||
vars(_options: Record<string, any>) {
|
||||
const endpoint = location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`
|
||||
return {
|
||||
GOOGLE_VERTEX_PROJECT: project,
|
||||
GOOGLE_VERTEX_LOCATION: location,
|
||||
GOOGLE_VERTEX_ENDPOINT: endpoint,
|
||||
}
|
||||
},
|
||||
options: {
|
||||
project,
|
||||
location,
|
||||
@@ -847,6 +844,9 @@ export namespace Provider {
|
||||
const modelLoaders: {
|
||||
[providerID: string]: CustomModelLoader
|
||||
} = {}
|
||||
const varsLoaders: {
|
||||
[providerID: string]: CustomVarsLoader
|
||||
} = {}
|
||||
const sdk = new Map<string, SDK>()
|
||||
|
||||
log.info("init")
|
||||
@@ -1043,6 +1043,7 @@ export namespace Provider {
|
||||
const result = await fn(data)
|
||||
if (result && (result.autoload || providers[providerID])) {
|
||||
if (result.getModel) modelLoaders[providerID] = result.getModel
|
||||
if (result.vars) varsLoaders[providerID] = result.vars
|
||||
const opts = result.options ?? {}
|
||||
const patch: Partial<Info> = providers[providerID] ? { options: opts } : { source: "custom", options: opts }
|
||||
mergeProvider(providerID, patch)
|
||||
@@ -1104,6 +1105,7 @@ export namespace Provider {
|
||||
providers,
|
||||
sdk,
|
||||
modelLoaders,
|
||||
varsLoaders,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1128,7 +1130,19 @@ export namespace Provider {
|
||||
options["includeUsage"] = true
|
||||
}
|
||||
|
||||
const baseURL = loadBaseURL(model, options)
|
||||
const baseURL = iife(() => {
|
||||
let url = String(options["baseURL"]) || model.api.url
|
||||
const loader = s.varsLoaders[model.providerID]
|
||||
if (loader) {
|
||||
const vars = loader(options)
|
||||
for (const [key, value] of Object.entries(vars)) {
|
||||
const field = `\$\{${key}\}`
|
||||
url = url.replaceAll(field, value)
|
||||
}
|
||||
}
|
||||
return url
|
||||
})
|
||||
|
||||
if (baseURL !== undefined) options["baseURL"] = baseURL
|
||||
if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
|
||||
if (model.headers)
|
||||
|
||||
@@ -1629,6 +1629,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
|
||||
const proc = spawn(shell, args, {
|
||||
cwd,
|
||||
detached: process.platform !== "win32",
|
||||
windowsHide: process.platform === "win32",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: {
|
||||
...process.env,
|
||||
|
||||
@@ -15,7 +15,10 @@ export namespace Shell {
|
||||
|
||||
if (process.platform === "win32") {
|
||||
await new Promise<void>((resolve) => {
|
||||
const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], { stdio: "ignore" })
|
||||
const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
|
||||
stdio: "ignore",
|
||||
windowsHide: true,
|
||||
})
|
||||
killer.once("exit", () => resolve())
|
||||
killer.once("error", () => resolve())
|
||||
})
|
||||
|
||||
@@ -173,6 +173,7 @@ export const BashTool = Tool.define("bash", async () => {
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
detached: process.platform !== "win32",
|
||||
windowsHide: process.platform === "win32",
|
||||
})
|
||||
|
||||
let output = ""
|
||||
|
||||
@@ -60,6 +60,7 @@ export namespace Process {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env === null ? {} : opts.env ? { ...process.env, ...opts.env } : undefined,
|
||||
stdio: [opts.stdin ?? "ignore", opts.stdout ?? "ignore", opts.stderr ?? "ignore"],
|
||||
windowsHide: process.platform === "win32",
|
||||
})
|
||||
|
||||
let closed = false
|
||||
|
||||
@@ -842,35 +842,6 @@ describe("session.message-v2.fromError", () => {
|
||||
})
|
||||
})
|
||||
|
||||
test("maps github-copilot 403 to reauth guidance", () => {
|
||||
const error = new APICallError({
|
||||
message: "forbidden",
|
||||
url: "https://api.githubcopilot.com/v1/chat/completions",
|
||||
requestBodyValues: {},
|
||||
statusCode: 403,
|
||||
responseHeaders: { "content-type": "application/json" },
|
||||
responseBody: '{"error":"forbidden"}',
|
||||
isRetryable: false,
|
||||
})
|
||||
|
||||
const result = MessageV2.fromError(error, { providerID: "github-copilot" })
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
name: "APIError",
|
||||
data: {
|
||||
message:
|
||||
"Please reauthenticate with the copilot provider to ensure your credentials work properly with OpenCode.",
|
||||
statusCode: 403,
|
||||
isRetryable: false,
|
||||
responseHeaders: { "content-type": "application/json" },
|
||||
responseBody: '{"error":"forbidden"}',
|
||||
metadata: {
|
||||
url: "https://api.githubcopilot.com/v1/chat/completions",
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("detects context overflow from APICallError provider messages", () => {
|
||||
const cases = [
|
||||
"prompt is too long: 213462 tokens > 200000 maximum",
|
||||
|
||||
37
test.ts
Normal file
37
test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { text } from "node:stream/consumers"
|
||||
import { Process } from "./packages/opencode/src/util/process"
|
||||
|
||||
function run() {
|
||||
return Process.spawn([process.execPath, "info", "semver", "version"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function old() {
|
||||
const proc = run()
|
||||
const code = await proc.exited
|
||||
const stdout = proc.stdout ? await text(proc.stdout) : ""
|
||||
const stderr = proc.stderr ? await text(proc.stderr) : ""
|
||||
return { code, stdout: stdout.trim(), stderr: stderr.trim() }
|
||||
}
|
||||
|
||||
async function next() {
|
||||
const out = await Process.text([process.execPath, "info", "semver", "version"], {
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return { code: out.code, stdout: out.stdout.toString().trim(), stderr: out.stderr.toString().trim() }
|
||||
}
|
||||
|
||||
const [a, b] = await Promise.all([old(), next()])
|
||||
|
||||
console.log("old", a)
|
||||
console.log("new", b)
|
||||
console.log("reproduced", a.stdout.length === 0 && b.stdout.length > 0)
|
||||
Reference in New Issue
Block a user