fix: restore .gitignore logic for config dirs and migrate to shared Npm service (#22772)
This commit is contained in:
-31
@@ -1,31 +0,0 @@
|
|||||||
{
|
|
||||||
"name": ".opencode",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/plugin": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@opencode-ai/plugin": {
|
|
||||||
"version": "1.2.6",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/sdk": "1.2.6",
|
|
||||||
"zod": "4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@opencode-ai/sdk": {
|
|
||||||
"version": "1.2.6",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/zod": {
|
|
||||||
"version": "4.1.8",
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,8 +20,7 @@ import {
|
|||||||
} from "jsonc-parser"
|
} from "jsonc-parser"
|
||||||
import { Instance, type InstanceContext } from "../project/instance"
|
import { Instance, type InstanceContext } from "../project/instance"
|
||||||
import * as LSPServer from "../lsp/server"
|
import * as LSPServer from "../lsp/server"
|
||||||
import { Installation } from "@/installation"
|
import { InstallationLocal, InstallationVersion } from "@/installation/version"
|
||||||
import { InstallationVersion } from "@/installation/version"
|
|
||||||
import * as ConfigMarkdown from "./markdown"
|
import * as ConfigMarkdown from "./markdown"
|
||||||
import { existsSync } from "fs"
|
import { existsSync } from "fs"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
@@ -38,8 +37,8 @@ import { Context, Duration, Effect, Exit, Fiber, Layer, Option } from "effect"
|
|||||||
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
|
import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
|
||||||
|
|
||||||
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
|
import { isPathPluginSpec, parsePluginSpecifier, resolvePathPluginTarget } from "@/plugin/shared"
|
||||||
import { Npm } from "../npm"
|
|
||||||
import { InstanceRef } from "@/effect/instance-ref"
|
import { InstanceRef } from "@/effect/instance-ref"
|
||||||
|
import { Npm } from "@opencode-ai/shared/npm"
|
||||||
|
|
||||||
const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" })
|
const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" })
|
||||||
const PluginOptions = z.record(z.string(), z.unknown())
|
const PluginOptions = z.record(z.string(), z.unknown())
|
||||||
@@ -141,10 +140,6 @@ export type InstallInput = {
|
|||||||
waitTick?: (input: { dir: string; attempt: number; delay: number; waited: number }) => void | Promise<void>
|
waitTick?: (input: { dir: string; attempt: number; delay: number; waited: number }) => void | Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
type Package = {
|
|
||||||
dependencies?: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
function rel(item: string, patterns: string[]) {
|
function rel(item: string, patterns: string[]) {
|
||||||
const normalizedItem = item.replaceAll("\\", "/")
|
const normalizedItem = item.replaceAll("\\", "/")
|
||||||
for (const pattern of patterns) {
|
for (const pattern of patterns) {
|
||||||
@@ -1059,7 +1054,6 @@ export interface Interface {
|
|||||||
readonly get: () => Effect.Effect<Info>
|
readonly get: () => Effect.Effect<Info>
|
||||||
readonly getGlobal: () => Effect.Effect<Info>
|
readonly getGlobal: () => Effect.Effect<Info>
|
||||||
readonly getConsoleState: () => Effect.Effect<ConsoleState>
|
readonly getConsoleState: () => Effect.Effect<ConsoleState>
|
||||||
readonly installDependencies: (dir: string, input?: InstallInput) => Effect.Effect<void, AppFileSystem.Error>
|
|
||||||
readonly update: (config: Info) => Effect.Effect<void>
|
readonly update: (config: Info) => Effect.Effect<void>
|
||||||
readonly updateGlobal: (config: Info) => Effect.Effect<Info>
|
readonly updateGlobal: (config: Info) => Effect.Effect<Info>
|
||||||
readonly invalidate: (wait?: boolean) => Effect.Effect<void>
|
readonly invalidate: (wait?: boolean) => Effect.Effect<void>
|
||||||
@@ -1146,18 +1140,14 @@ export const ConfigDirectoryTypoError = NamedError.create(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const layer: Layer.Layer<
|
export const layer = Layer.effect(
|
||||||
Service,
|
|
||||||
never,
|
|
||||||
AppFileSystem.Service | Auth.Service | Account.Service | Env.Service | EffectFlock.Service
|
|
||||||
> = Layer.effect(
|
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const fs = yield* AppFileSystem.Service
|
const fs = yield* AppFileSystem.Service
|
||||||
const authSvc = yield* Auth.Service
|
const authSvc = yield* Auth.Service
|
||||||
const accountSvc = yield* Account.Service
|
const accountSvc = yield* Account.Service
|
||||||
const env = yield* Env.Service
|
const env = yield* Env.Service
|
||||||
const flock = yield* EffectFlock.Service
|
const npmSvc = yield* Npm.Service
|
||||||
|
|
||||||
const readConfigFile = Effect.fnUntraced(function* (filepath: string) {
|
const readConfigFile = Effect.fnUntraced(function* (filepath: string) {
|
||||||
return yield* fs.readFileString(filepath).pipe(
|
return yield* fs.readFileString(filepath).pipe(
|
||||||
@@ -1263,53 +1253,18 @@ export const layer: Layer.Layer<
|
|||||||
return yield* cachedGlobal
|
return yield* cachedGlobal
|
||||||
})
|
})
|
||||||
|
|
||||||
const install = Effect.fn("Config.install")(function* (dir: string) {
|
const setupConfigDir = Effect.fnUntraced(function* (dir: string) {
|
||||||
const pkg = path.join(dir, "package.json")
|
|
||||||
const gitignore = path.join(dir, ".gitignore")
|
const gitignore = path.join(dir, ".gitignore")
|
||||||
const plugin = path.join(dir, "node_modules", "@opencode-ai", "plugin", "package.json")
|
|
||||||
const target = Installation.isLocal() ? "*" : InstallationVersion
|
|
||||||
const json = yield* fs.readJson(pkg).pipe(
|
|
||||||
Effect.catch(() => Effect.succeed({} satisfies Package)),
|
|
||||||
Effect.map((x): Package => (isRecord(x) ? (x as Package) : {})),
|
|
||||||
)
|
|
||||||
const hasDep = json.dependencies?.["@opencode-ai/plugin"] === target
|
|
||||||
const hasIgnore = yield* fs.existsSafe(gitignore)
|
const hasIgnore = yield* fs.existsSafe(gitignore)
|
||||||
const hasPkg = yield* fs.existsSafe(plugin)
|
|
||||||
|
|
||||||
if (!hasDep) {
|
|
||||||
yield* fs.writeJson(pkg, {
|
|
||||||
...json,
|
|
||||||
dependencies: {
|
|
||||||
...json.dependencies,
|
|
||||||
"@opencode-ai/plugin": target,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasIgnore) {
|
if (!hasIgnore) {
|
||||||
yield* fs.writeFileString(
|
yield* fs.writeFileString(
|
||||||
gitignore,
|
gitignore,
|
||||||
["node_modules", "package.json", "package-lock.json", "bun.lock", ".gitignore"].join("\n"),
|
["node_modules", "package.json", "package-lock.json", "bun.lock", ".gitignore"].join("\n"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
yield* npmSvc.install(dir, {
|
||||||
if (hasDep && hasIgnore && hasPkg) return
|
add: ["@opencode-ai/plugin" + (InstallationLocal ? "" : "@" + InstallationVersion)],
|
||||||
|
})
|
||||||
yield* Effect.promise(() => Npm.install(dir))
|
|
||||||
})
|
|
||||||
|
|
||||||
const installDependencies = Effect.fn("Config.installDependencies")(function* (dir: string, _input?: InstallInput) {
|
|
||||||
if (
|
|
||||||
!(yield* fs.access(dir, { writable: true }).pipe(
|
|
||||||
Effect.as(true),
|
|
||||||
Effect.orElseSucceed(() => false),
|
|
||||||
))
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
const key = process.platform === "win32" ? "config-install:win32" : `config-install:${AppFileSystem.resolve(dir)}`
|
|
||||||
|
|
||||||
yield* flock.withLock(install(dir), key).pipe(Effect.orDie)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadInstanceState = Effect.fn("Config.loadInstanceState")(function* (ctx: InstanceContext) {
|
const loadInstanceState = Effect.fn("Config.loadInstanceState")(function* (ctx: InstanceContext) {
|
||||||
@@ -1404,7 +1359,7 @@ export const layer: Layer.Layer<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dep = yield* installDependencies(dir).pipe(
|
const dep = yield* setupConfigDir(dir).pipe(
|
||||||
Effect.exit,
|
Effect.exit,
|
||||||
Effect.tap((exit) =>
|
Effect.tap((exit) =>
|
||||||
Exit.isFailure(exit)
|
Exit.isFailure(exit)
|
||||||
@@ -1611,7 +1566,6 @@ export const layer: Layer.Layer<
|
|||||||
get,
|
get,
|
||||||
getGlobal,
|
getGlobal,
|
||||||
getConsoleState,
|
getConsoleState,
|
||||||
installDependencies,
|
|
||||||
update,
|
update,
|
||||||
updateGlobal,
|
updateGlobal,
|
||||||
invalidate,
|
invalidate,
|
||||||
@@ -1627,4 +1581,5 @@ export const defaultLayer = layer.pipe(
|
|||||||
Layer.provide(Env.defaultLayer),
|
Layer.provide(Env.defaultLayer),
|
||||||
Layer.provide(Auth.defaultLayer),
|
Layer.provide(Auth.defaultLayer),
|
||||||
Layer.provide(Account.defaultLayer),
|
Layer.provide(Account.defaultLayer),
|
||||||
|
Layer.provide(Npm.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ import { Global } from "../../src/global"
|
|||||||
import { ProjectID } from "../../src/project/schema"
|
import { ProjectID } from "../../src/project/schema"
|
||||||
import { Filesystem } from "../../src/util"
|
import { Filesystem } from "../../src/util"
|
||||||
import * as Network from "../../src/util/network"
|
import * as Network from "../../src/util/network"
|
||||||
import { Npm } from "../../src/npm"
|
|
||||||
import { ConfigPlugin } from "@/config/plugin"
|
import { ConfigPlugin } from "@/config/plugin"
|
||||||
|
import { Npm } from "@opencode-ai/shared/npm"
|
||||||
|
|
||||||
const emptyAccount = Layer.mock(Account.Service)({
|
const emptyAccount = Layer.mock(Account.Service)({
|
||||||
active: () => Effect.succeed(Option.none()),
|
active: () => Effect.succeed(Option.none()),
|
||||||
@@ -46,6 +46,7 @@ const layer = Config.layer.pipe(
|
|||||||
Layer.provide(emptyAuth),
|
Layer.provide(emptyAuth),
|
||||||
Layer.provide(emptyAccount),
|
Layer.provide(emptyAccount),
|
||||||
Layer.provideMerge(infra),
|
Layer.provideMerge(infra),
|
||||||
|
Layer.provide(Npm.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
const it = testEffect(layer)
|
const it = testEffect(layer)
|
||||||
@@ -60,9 +61,6 @@ const listDirs = () =>
|
|||||||
const ready = () =>
|
const ready = () =>
|
||||||
Effect.runPromise(Config.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.scoped, Effect.provide(layer)))
|
Effect.runPromise(Config.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.scoped, Effect.provide(layer)))
|
||||||
|
|
||||||
const installDeps = (dir: string, input?: Config.InstallInput) =>
|
|
||||||
Config.Service.use((svc) => svc.installDependencies(dir, input))
|
|
||||||
|
|
||||||
// Get managed config directory from environment (set in preload.ts)
|
// Get managed config directory from environment (set in preload.ts)
|
||||||
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
|
const managedConfigDir = process.env.OPENCODE_TEST_MANAGED_CONFIG_DIR!
|
||||||
|
|
||||||
@@ -355,7 +353,7 @@ test("resolves env templates in account config with account token", async () =>
|
|||||||
expect(config.provider?.["opencode"]?.options?.apiKey).toBe("st_test_token")
|
expect(config.provider?.["opencode"]?.options?.apiKey).toBe("st_test_token")
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
).pipe(Effect.scoped, Effect.provide(layer), Effect.runPromise)
|
).pipe(Effect.scoped, Effect.provide(layer), Effect.provide(Npm.defaultLayer), Effect.runPromise)
|
||||||
} finally {
|
} finally {
|
||||||
if (originalControlToken !== undefined) {
|
if (originalControlToken !== undefined) {
|
||||||
process.env["OPENCODE_CONSOLE_TOKEN"] = originalControlToken
|
process.env["OPENCODE_CONSOLE_TOKEN"] = originalControlToken
|
||||||
@@ -820,156 +818,45 @@ test("installs dependencies in writable OPENCODE_CONFIG_DIR", async () => {
|
|||||||
|
|
||||||
const prev = process.env.OPENCODE_CONFIG_DIR
|
const prev = process.env.OPENCODE_CONFIG_DIR
|
||||||
process.env.OPENCODE_CONFIG_DIR = tmp.extra
|
process.env.OPENCODE_CONFIG_DIR = tmp.extra
|
||||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
|
||||||
const install = spyOn(Npm, "install").mockImplementation(async (dir: string) => {
|
const noopNpm = Layer.mock(Npm.Service)({
|
||||||
const mod = path.join(dir, "node_modules", "@opencode-ai", "plugin")
|
install: () => Effect.void,
|
||||||
await fs.mkdir(mod, { recursive: true })
|
add: () => Effect.die("not implemented"),
|
||||||
await Filesystem.write(
|
outdated: () => Effect.succeed(false),
|
||||||
path.join(mod, "package.json"),
|
which: () => Effect.succeed(Option.none()),
|
||||||
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
const testLayer = Config.layer.pipe(
|
||||||
|
Layer.provide(testFlock),
|
||||||
|
Layer.provide(AppFileSystem.defaultLayer),
|
||||||
|
Layer.provide(Env.defaultLayer),
|
||||||
|
Layer.provide(emptyAuth),
|
||||||
|
Layer.provide(emptyAccount),
|
||||||
|
Layer.provideMerge(infra),
|
||||||
|
Layer.provide(noopNpm),
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Instance.provide({
|
await Instance.provide({
|
||||||
directory: tmp.path,
|
directory: tmp.path,
|
||||||
fn: async () => {
|
fn: async () => {
|
||||||
await load()
|
await Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(testLayer)))
|
||||||
await ready()
|
await Effect.runPromise(
|
||||||
|
Config.Service.use((svc) => svc.waitForDependencies()).pipe(Effect.scoped, Effect.provide(testLayer)),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await Filesystem.exists(path.join(tmp.extra, "package.json"))).toBe(true)
|
|
||||||
expect(await Filesystem.exists(path.join(tmp.extra, ".gitignore"))).toBe(true)
|
expect(await Filesystem.exists(path.join(tmp.extra, ".gitignore"))).toBe(true)
|
||||||
expect(await Filesystem.readText(path.join(tmp.extra, ".gitignore"))).toContain("package-lock.json")
|
expect(await Filesystem.readText(path.join(tmp.extra, ".gitignore"))).toContain("package-lock.json")
|
||||||
} finally {
|
} finally {
|
||||||
online.mockRestore()
|
|
||||||
install.mockRestore()
|
|
||||||
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
if (prev === undefined) delete process.env.OPENCODE_CONFIG_DIR
|
||||||
else process.env.OPENCODE_CONFIG_DIR = prev
|
else process.env.OPENCODE_CONFIG_DIR = prev
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it.live("dedupes concurrent config dependency installs for the same dir", () =>
|
// Note: deduplication and serialization of npm installs is now handled by the
|
||||||
Effect.gen(function* () {
|
// shared Npm.Service (via EffectFlock). Those behaviors are tested in the shared
|
||||||
const tmp = yield* tmpdirScoped()
|
// package's npm tests, not here.
|
||||||
const dir = path.join(tmp, "a")
|
|
||||||
yield* Effect.promise(() => fs.mkdir(dir, { recursive: true }))
|
|
||||||
|
|
||||||
let calls = 0
|
|
||||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
|
||||||
const ready = Deferred.makeUnsafe<void>()
|
|
||||||
const hold = Deferred.makeUnsafe<void>()
|
|
||||||
const target = path.normalize(dir)
|
|
||||||
const run = spyOn(Npm, "install").mockImplementation(async (d: string) => {
|
|
||||||
if (path.normalize(d) !== target) return
|
|
||||||
calls += 1
|
|
||||||
Deferred.doneUnsafe(ready, Effect.void)
|
|
||||||
await Effect.runPromise(Deferred.await(hold))
|
|
||||||
const mod = path.join(d, "node_modules", "@opencode-ai", "plugin")
|
|
||||||
await fs.mkdir(mod, { recursive: true })
|
|
||||||
await Filesystem.write(
|
|
||||||
path.join(mod, "package.json"),
|
|
||||||
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
online.mockRestore()
|
|
||||||
run.mockRestore()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const first = yield* installDeps(dir).pipe(Effect.forkScoped)
|
|
||||||
yield* Deferred.await(ready)
|
|
||||||
|
|
||||||
let done = false
|
|
||||||
const second = yield* installDeps(dir).pipe(
|
|
||||||
Effect.tap(() =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
done = true
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Effect.forkScoped,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Give the second fiber time to hit the lock retry loop
|
|
||||||
yield* Effect.sleep(500)
|
|
||||||
expect(done).toBe(false)
|
|
||||||
|
|
||||||
yield* Deferred.succeed(hold, void 0)
|
|
||||||
yield* Fiber.join(first)
|
|
||||||
yield* Fiber.join(second)
|
|
||||||
|
|
||||||
expect(calls).toBe(1)
|
|
||||||
expect(yield* Effect.promise(() => Filesystem.exists(path.join(dir, "package.json")))).toBe(true)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
it.live("serializes config dependency installs across dirs", () =>
|
|
||||||
Effect.gen(function* () {
|
|
||||||
if (process.platform !== "win32") return
|
|
||||||
|
|
||||||
const tmp = yield* tmpdirScoped()
|
|
||||||
const a = path.join(tmp, "a")
|
|
||||||
const b = path.join(tmp, "b")
|
|
||||||
yield* Effect.promise(() => fs.mkdir(a, { recursive: true }))
|
|
||||||
yield* Effect.promise(() => fs.mkdir(b, { recursive: true }))
|
|
||||||
|
|
||||||
let calls = 0
|
|
||||||
let open = 0
|
|
||||||
let peak = 0
|
|
||||||
const ready = Deferred.makeUnsafe<void>()
|
|
||||||
const hold = Deferred.makeUnsafe<void>()
|
|
||||||
|
|
||||||
const online = spyOn(Network, "online").mockReturnValue(false)
|
|
||||||
const run = spyOn(Npm, "install").mockImplementation(async (dir: string) => {
|
|
||||||
const cwd = path.normalize(dir)
|
|
||||||
const hit = cwd === path.normalize(a) || cwd === path.normalize(b)
|
|
||||||
if (hit) {
|
|
||||||
calls += 1
|
|
||||||
open += 1
|
|
||||||
peak = Math.max(peak, open)
|
|
||||||
if (calls === 1) {
|
|
||||||
Deferred.doneUnsafe(ready, Effect.void)
|
|
||||||
await Effect.runPromise(Deferred.await(hold))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mod = path.join(cwd, "node_modules", "@opencode-ai", "plugin")
|
|
||||||
await fs.mkdir(mod, { recursive: true })
|
|
||||||
await Filesystem.write(
|
|
||||||
path.join(mod, "package.json"),
|
|
||||||
JSON.stringify({ name: "@opencode-ai/plugin", version: "1.0.0" }),
|
|
||||||
)
|
|
||||||
if (hit) {
|
|
||||||
open -= 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
yield* Effect.addFinalizer(() =>
|
|
||||||
Effect.sync(() => {
|
|
||||||
online.mockRestore()
|
|
||||||
run.mockRestore()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const first = yield* installDeps(a).pipe(Effect.forkScoped)
|
|
||||||
yield* Deferred.await(ready)
|
|
||||||
|
|
||||||
const second = yield* installDeps(b).pipe(Effect.forkScoped)
|
|
||||||
// Give the second fiber time to hit the lock retry loop
|
|
||||||
yield* Effect.sleep(500)
|
|
||||||
expect(peak).toBe(1)
|
|
||||||
|
|
||||||
yield* Deferred.succeed(hold, void 0)
|
|
||||||
yield* Fiber.join(first)
|
|
||||||
yield* Fiber.join(second)
|
|
||||||
|
|
||||||
expect(calls).toBe(2)
|
|
||||||
expect(peak).toBe(1)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
test("resolves scoped npm plugins in config", async () => {
|
test("resolves scoped npm plugins in config", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
@@ -1831,6 +1718,7 @@ test("project config overrides remote well-known config", async () => {
|
|||||||
Layer.provide(fakeAuth),
|
Layer.provide(fakeAuth),
|
||||||
Layer.provide(emptyAccount),
|
Layer.provide(emptyAccount),
|
||||||
Layer.provideMerge(infra),
|
Layer.provideMerge(infra),
|
||||||
|
Layer.provide(Npm.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1888,6 +1776,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
|
|||||||
Layer.provide(fakeAuth),
|
Layer.provide(fakeAuth),
|
||||||
Layer.provide(emptyAccount),
|
Layer.provide(emptyAccount),
|
||||||
Layer.provideMerge(infra),
|
Layer.provideMerge(infra),
|
||||||
|
Layer.provide(Npm.defaultLayer),
|
||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user