Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-links-sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"packref": patch
---

Fix `packref sync` in monorepos that have private workspace packages with no `version` field. Packref does not show a manifest parse error for these packages now. Packref also stops at the nearest installed package, and does not use a version from a different package with the same name in a parent `node_modules`.
65 changes: 64 additions & 1 deletion src/lib/manifests/__tests__/javascript.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as FileSystem from "effect/FileSystem"
import type * as Path from "effect/Path"
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join } from "node:path"
import * as NodeServices from "@effect/platform-node/NodeServices"
Expand Down Expand Up @@ -485,6 +485,69 @@ importers:
expect(dependencies[0]?.exactVersion).toBe("4.0.0-beta.55")
})

it("stops at the nearest installed package when it has no version", async () => {
const workspaceRoot = await makeTempDirectory()
const projectPath = join(workspaceRoot, "apps", "web")
const workspacePackagePath = join(workspaceRoot, "tooling", "linting")
await mkdir(projectPath, { recursive: true })
await mkdir(workspacePackagePath, { recursive: true })
await writeFile(
join(projectPath, "package.json"),
JSON.stringify({
dependencies: {
effect: "^4.0.0-beta.50",
},
devDependencies: {
"@tooling/linting": "workspace:*",
},
})
)
await writeFile(
join(workspacePackagePath, "package.json"),
JSON.stringify({ name: "@tooling/linting", private: true })
)
await mkdir(join(projectPath, "node_modules", "@tooling"), { recursive: true })
await symlink(workspacePackagePath, join(projectPath, "node_modules", "@tooling", "linting"))
await mkdir(join(workspaceRoot, "node_modules", "@tooling", "linting"), { recursive: true })
await writeFile(
join(workspaceRoot, "node_modules", "@tooling", "linting", "package.json"),
JSON.stringify({ name: "@tooling/linting", version: "2.0.0" })
)
await mkdir(join(projectPath, "node_modules", "effect"), { recursive: true })
await writeFile(
join(projectPath, "node_modules", "effect", "package.json"),
JSON.stringify({ version: "4.0.0-beta.55" })
)

const dependencies = await run(readJavascriptManifest(projectPath))

expect(dependencies.map(({ exactVersion, name }) => [name, exactVersion])).toEqual([
["effect", "4.0.0-beta.55"],
["@tooling/linting", undefined],
])
})

it("keeps the installed version for a local path dependency", async () => {
const projectPath = await makeTempDirectory()
await writeFile(
join(projectPath, "package.json"),
JSON.stringify({
dependencies: {
example: "file:../example",
},
})
)
await mkdir(join(projectPath, "node_modules", "example"), { recursive: true })
await writeFile(
join(projectPath, "node_modules", "example", "package.json"),
JSON.stringify({ name: "example", version: "1.0.0" })
)

const dependencies = await run(readJavascriptManifest(projectPath))

expect(dependencies[0]?.exactVersion).toBe("1.0.0")
})

it("reports no dependency for installed packages absent from the manifest", async () => {
const projectPath = await makeTempDirectory()
await writeFile(
Expand Down
13 changes: 9 additions & 4 deletions src/lib/manifests/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const JavascriptPackageManifestSchema = Schema.StructWithRest(

const InstalledPackageSchema = Schema.StructWithRest(
Schema.Struct({
version: Schema.String,
version: Schema.optional(Schema.String),
}),
[Schema.Record(Schema.String, Schema.Unknown)]
)
Expand Down Expand Up @@ -485,10 +485,15 @@ const readNodeModulesVersion = Effect.fn("readNodeModulesVersion")(function* (
toManifestParseError(packageJsonPath)
)

return valid(manifest.version) === null
? Result.fail(void 0)
: Result.succeed(manifest.version)
// Like Node resolution, stop at the nearest installed package even if it has no version.
return Result.succeed(manifest.version)
})
).pipe(
Effect.map(
Option.filter(
(version): version is string => version !== undefined && valid(version) !== null
)
)
)
})

Expand Down
Loading