@venn-lang/project 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/paths/paths.ts","../src/discover/conventional-targets.ts","../src/workspace/inherit.ts","../src/glob/expand-members.ts","../src/glob/matches-member.ts","../src/workspace/member-dirs.ts","../src/discover/read-manifest.ts","../src/discover/load-package.ts","../src/discover/find-project.ts","../src/npm/hash-package.ts","../src/npm/lockfile.types.ts","../src/target/layout.ts","../src/target/build-record.ts","../src/npm/read-installed.ts","../src/npm/lockfile.ts","../src/npm/manager-command.ts","../src/npm/package-json.ts","../src/npm/verify-lock.ts","../src/scaffold/scaffold.ts"],"sourcesContent":["/**\n * Path arithmetic on manifest paths, with no `node:path`.\n *\n * This package runs in a Web Worker as well as in the CLI, so paths are handled\n * as text with forward slashes. A Windows path arrives with backslashes and is\n * normalised on the way in, which is the one place the difference is allowed to\n * exist.\n */\n\n/**\n * One path, written the one way the rest of this package understands.\n *\n * @returns The path with backslashes turned into forward slashes, runs of\n * separators collapsed, and any trailing separator dropped. `\"/\"` stays `\"/\"`.\n */\nexport function normalise(path: string): string {\n const flat = path.replaceAll(\"\\\\\", \"/\").replace(/\\/+/g, \"/\");\n return flat.length > 1 ? flat.replace(/\\/$/, \"\") : flat;\n}\n\n/**\n * Joins path segments, ignoring empty ones.\n *\n * @returns The joined path, normalised. No `..` is resolved: these are manifest\n * paths, and what a manifest wrote is what gets written down.\n */\nexport function join(...parts: readonly string[]): string {\n const joined = parts.filter((part) => part !== \"\").join(\"/\");\n return normalise(joined);\n}\n\n/**\n * The directory holding this path.\n *\n * @returns The parent, or `undefined` when there is nothing above. A relative\n * path with one segment has the empty string as its parent, which is the\n * directory it was written against; stopping short of it would leave a walk up\n * from `packages/api` never reaching the workspace root beside it.\n */\nexport function parentOf(path: string): string | undefined {\n const flat = normalise(path);\n if (flat === \"\" || flat === \"/\") return undefined;\n const slash = flat.lastIndexOf(\"/\");\n if (slash < 0) return \"\";\n return slash === 0 ? \"/\" : flat.slice(0, slash);\n}\n\n/** The last segment of a path, which is the file or directory's own name. */\nexport function baseName(path: string): string {\n const flat = normalise(path);\n return flat.slice(flat.lastIndexOf(\"/\") + 1);\n}\n\n/**\n * Every directory from `path` up to the top, `path` itself first.\n *\n * @returns The chain, ending in `\"\"` for a relative path and `\"/\"` for an\n * absolute one.\n */\nexport function ancestors(path: string): string[] {\n const found: string[] = [];\n // Tested against undefined, not for truth: the top of a relative walk is the\n // empty string, which is falsy but is still a directory to look in.\n for (let at: string | undefined = normalise(path); at !== undefined; at = parentOf(at)) {\n found.push(at);\n }\n return found;\n}\n\n/** Whether `path` is `base` or sits inside it. */\nexport function isInside(path: string, base: string): boolean {\n const one = normalise(path);\n const other = normalise(base);\n return one === other || one.startsWith(`${other}/`);\n}\n\n/** `path` written relative to `base`, or the path itself when it is not inside. */\nexport function relativeTo(path: string, base: string): string {\n const one = normalise(path);\n const other = normalise(base);\n return one.startsWith(`${other}/`) ? one.slice(other.length + 1) : one;\n}\n\n/**\n * A relative path written in one directory, rewritten to mean the same thing\n * from another inside it.\n *\n * A workspace root writes `\"#shared\" = \"./shared\"` once and every member uses\n * it, but a member reads it from further down, where `./shared` is somewhere\n * else entirely.\n *\n * @param args.declaredIn Where the path was written.\n * @param args.usedIn Where it will be read.\n * @returns The path with one `../` per directory of depth. An absolute path is\n * returned unchanged: it already means the same thing everywhere.\n */\nexport function reanchor(args: { path: string; declaredIn: string; usedIn: string }): string {\n if (!args.path.startsWith(\".\")) return args.path;\n const down = relativeTo(args.usedIn, args.declaredIn);\n const depth = down === normalise(args.usedIn) ? 0 : down.split(\"/\").filter(Boolean).length;\n if (depth === 0) return args.path;\n return `${\"../\".repeat(depth)}${args.path.replace(/^\\.\\//, \"\")}`;\n}\n","import {\n BIN_DIR,\n type BuildTarget,\n type FileSystem,\n LIB_ROOT,\n MAIN_ROOT,\n} from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\n\n/**\n * What a package builds when its manifest does not say.\n *\n * `src/lib.vn` is a library, `src/main.vn` is a program, and each file in\n * `src/bin/` is another program. A package that builds the obvious thing should\n * not have to write it down. A declared target always wins, matched by kind and\n * name, so writing `[lib]` with a different `path` moves the library rather\n * than adding a second one.\n *\n * @param args.declared What the manifest spelled out.\n * @param args.packageName Names the `lib` and the `src/main.vn` program.\n * @returns The declared targets, followed by every convention found on disk.\n */\nexport async function conventionalTargets(args: {\n fs: FileSystem;\n dir: string;\n declared: readonly BuildTarget[];\n packageName: string;\n}): Promise<BuildTarget[]> {\n const found = [...args.declared];\n const add = async (target: BuildTarget): Promise<void> => {\n if (found.some((one) => one.kind === target.kind && one.name === target.name)) return;\n if (await args.fs.exists(join(args.dir, target.path))) found.push(target);\n };\n await add({ kind: \"lib\", name: args.packageName, path: LIB_ROOT });\n await add({ kind: \"bin\", name: args.packageName, path: MAIN_ROOT });\n for (const name of await binNames(args.fs, args.dir)) {\n await add({ kind: \"bin\", name, path: `${BIN_DIR}/${name}.vn` });\n }\n return found;\n}\n\n/** Each `.vn` in `src/bin/` is a program named after the file. */\nasync function binNames(fs: FileSystem, dir: string): Promise<string[]> {\n const entries = await fs.list(join(dir, BIN_DIR));\n return entries\n .filter((entry) => !entry.directory && entry.name.endsWith(\".vn\"))\n .map((entry) => entry.name.slice(0, -\".vn\".length))\n .sort();\n}\n","import type { Dependency, Manifest, PackageInfo } from \"@venn-lang/contracts\";\n\n/**\n * A member manifest with what the workspace root supplies filled in.\n *\n * Two things are inherited and they work differently. `[workspace.package]` is\n * a default: the member's own value wins wherever it wrote one. A dependency\n * marked `{ workspace = true }` is a request, so the root's answer replaces\n * whatever was there. Writing both a version and `workspace = true` is a\n * contradiction, and the request is the deliberate half.\n *\n * @param args.from The workspace root's manifest. A root with no `[workspace]`\n * table supplies nothing, and the member is returned untouched.\n * @returns The merged manifest. The member's own `name` always survives.\n */\nexport function inherit(args: { manifest: Manifest; from: Manifest }): Manifest {\n const shared = args.from.workspace;\n if (!shared) return args.manifest;\n const pkg = withDefaults(args.manifest.package, shared.package);\n return {\n ...args.manifest,\n name: pkg.name,\n version: pkg.version ?? args.manifest.version,\n package: pkg,\n dependencies: pinned(args.manifest.dependencies, shared.dependencies),\n devDependencies: pinned(args.manifest.devDependencies, shared.dependencies),\n ...settings(args.manifest, args.from),\n };\n}\n\n/**\n * The environments and path aliases a member gets from its root.\n *\n * A workspace almost always wants `[env.staging]` and `#shared` written once.\n * The alternative, every member repeating every variable, drifts silently until\n * two members disagree about a URL. A member that writes its own key wins, per\n * key rather than per table.\n */\nfunction settings(own: Manifest, root: Manifest): Pick<Manifest, \"env\" | \"paths\"> {\n const env: Record<string, Record<string, string>> = { ...root.env };\n for (const [name, vars] of Object.entries(own.env)) env[name] = { ...env[name], ...vars };\n return { env, paths: { ...root.paths, ...own.paths } };\n}\n\n/** The member's own value wins; the root fills only what was left unsaid. */\nfunction withDefaults(own: PackageInfo, shared: Partial<PackageInfo>): PackageInfo {\n const merged = { ...own };\n for (const [key, value] of Object.entries(shared) as [keyof PackageInfo, never][]) {\n if (merged[key] === undefined || merged[key] === \"\") merged[key] = value;\n }\n return { ...merged, name: own.name };\n}\n\nfunction pinned(deps: readonly Dependency[], shared: readonly Dependency[]): readonly Dependency[] {\n return deps.map((dep) => {\n if (!dep.fromWorkspace) return dep;\n const found = shared.find((one) => one.name === dep.name);\n return found ? { ...found, optional: dep.optional, fromWorkspace: true } : dep;\n });\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\n\n/**\n * Expands a workspace's `members` patterns against the disk.\n *\n * `*` stands for one path segment, the way Cargo reads it, so `packages/*` is\n * every package one level down and nothing deeper. `**` is deliberately not\n * read: a member list that can reach arbitrarily far is one nobody can predict.\n *\n * @param args.root The workspace root the patterns are written against.\n * @returns The directories matched, duplicates dropped, in pattern order with\n * names sorted inside each pattern, so two machines list a workspace alike.\n */\nexport async function expandMembers(args: {\n fs: FileSystem;\n root: string;\n patterns: readonly string[];\n}): Promise<string[]> {\n const found: string[] = [];\n for (const pattern of args.patterns) {\n for (const dir of await expandOne(args.fs, args.root, pattern)) {\n if (!found.includes(dir)) found.push(dir);\n }\n }\n return found;\n}\n\nasync function expandOne(fs: FileSystem, root: string, pattern: string): Promise<string[]> {\n let at = [root];\n for (const segment of pattern.split(\"/\").filter((part) => part !== \"\")) {\n at = segment === \"*\" ? await childrenOf(fs, at) : at.map((dir) => join(dir, segment));\n }\n return at;\n}\n\nasync function childrenOf(fs: FileSystem, dirs: readonly string[]): Promise<string[]> {\n const found: string[] = [];\n for (const dir of dirs) {\n const entries = await fs.list(dir);\n const names = entries.filter((entry) => entry.directory).map((entry) => entry.name);\n for (const name of names.sort()) found.push(join(dir, name));\n }\n return found;\n}\n","import { normalise } from \"../paths/index.js\";\n\n/**\n * Whether a path would be caught by a workspace's `members`, without looking at\n * the disk.\n *\n * Needed before the directory exists: creating `packages/api` inside a\n * workspace should produce a manifest that inherits, and expanding the globs\n * cannot answer that yet because there is nothing there to find.\n *\n * @returns `true` when some pattern matches segment for segment, `*` standing\n * for exactly one segment.\n */\nexport function matchesMember(args: {\n /** The candidate directory, written relative to the workspace root. */\n path: string;\n patterns: readonly string[];\n}): boolean {\n const parts = segmentsOf(args.path);\n return args.patterns.some((pattern) => matchesOne(parts, segmentsOf(pattern)));\n}\n\nfunction matchesOne(path: readonly string[], pattern: readonly string[]): boolean {\n if (path.length !== pattern.length) return false;\n return pattern.every((part, index) => part === \"*\" || part === path[index]);\n}\n\nfunction segmentsOf(path: string): string[] {\n return normalise(path)\n .split(\"/\")\n .filter((part) => part !== \"\" && part !== \".\");\n}\n","import type { FileSystem, WorkspaceSettings } from \"@venn-lang/contracts\";\nimport { expandMembers } from \"../glob/index.js\";\nimport { isInside, join, normalise } from \"../paths/index.js\";\n\n/**\n * The directories a workspace's members occupy, exclusions already applied.\n *\n * @returns The member directories, normalised. One holding no `venn.toml` is\n * dropped rather than reported: a glob describes a shape, and `packages/*`\n * catching a `dist` folder on the way past is the glob working, not the\n * workspace being wrong.\n */\nexport async function memberDirs(args: {\n fs: FileSystem;\n root: string;\n workspace: WorkspaceSettings;\n}): Promise<string[]> {\n const matched = await expandMembers({\n fs: args.fs,\n root: args.root,\n patterns: args.workspace.members,\n });\n const excluded = args.workspace.exclude.map((path) => normalise(join(args.root, path)));\n const kept = matched.filter((dir) => !excluded.some((one) => isInside(dir, one)));\n return withManifest(args.fs, kept);\n}\n\nasync function withManifest(fs: FileSystem, dirs: readonly string[]): Promise<string[]> {\n const found: string[] = [];\n for (const dir of dirs) {\n if (await fs.exists(join(dir, \"venn.toml\"))) found.push(normalise(dir));\n }\n return found;\n}\n","import { createTomlManifest, type FileSystem, type Manifest } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\n\n/** The file that makes a directory a package or a workspace root. */\nexport const MANIFEST_FILE = \"venn.toml\";\n\n/**\n * Reads and parses the `venn.toml` in one directory.\n *\n * @returns The manifest, or `undefined` when the file is absent or unreadable.\n * A directory that cannot be read is a directory without a manifest, so the\n * caller keeps walking instead of failing.\n */\nexport async function readManifest(args: {\n fs: FileSystem;\n dir: string;\n}): Promise<Manifest | undefined> {\n const path = join(args.dir, MANIFEST_FILE);\n if (!(await args.fs.exists(path))) return undefined;\n const bytes = await args.fs.read(path).catch(() => undefined);\n if (!bytes) return undefined;\n return createTomlManifest({ content: new TextDecoder().decode(bytes) }).load();\n}\n","import type { FileSystem, Manifest } from \"@venn-lang/contracts\";\nimport type { Package } from \"../model/project.types.js\";\nimport { reanchor } from \"../paths/index.js\";\nimport { inherit } from \"../workspace/index.js\";\nimport { conventionalTargets } from \"./conventional-targets.js\";\nimport { readManifest } from \"./read-manifest.js\";\n\n/**\n * One package: its manifest, whatever it inherits, and what it builds.\n *\n * Inheritance is applied here rather than at the point of use, so nothing\n * downstream ever holds a manifest that is only half the answer.\n *\n * @param args.dir The directory holding the `venn.toml`.\n * @returns The package, or `undefined` when that directory has no manifest.\n */\nexport async function loadPackage(args: {\n fs: FileSystem;\n dir: string;\n /** The workspace this package belongs to, when it belongs to one. */\n workspace?: Manifest;\n /** Where that workspace's manifest lives, which is what its paths mean. */\n workspaceDir?: string;\n}): Promise<Package | undefined> {\n const own = await readManifest({ fs: args.fs, dir: args.dir });\n if (!own) return undefined;\n const merged = args.workspace ? inherit({ manifest: own, from: args.workspace }) : own;\n const manifest = anchorPaths({ manifest: merged, own, args });\n const targets = await conventionalTargets({\n fs: args.fs,\n dir: args.dir,\n declared: manifest.targets,\n packageName: manifest.name,\n });\n return { dir: args.dir, manifest, targets };\n}\n\n/**\n * Inherited path aliases, rewritten to mean the same place from down here.\n *\n * An alias the member wrote itself is already anchored where it will be read,\n * so only the ones that came from above move. Hence the member's own table is\n * consulted rather than the merged one.\n */\nfunction anchorPaths(input: {\n manifest: Manifest;\n own: Manifest;\n args: { dir: string; workspaceDir?: string };\n}): Manifest {\n const root = input.args.workspaceDir;\n if (root === undefined || root === input.args.dir) return input.manifest;\n const paths: Record<string, string> = {};\n for (const [alias, value] of Object.entries(input.manifest.paths)) {\n const mine = input.own.paths[alias] !== undefined;\n paths[alias] = mine\n ? value\n : reanchor({ path: value, declaredIn: root, usedIn: input.args.dir });\n }\n return { ...input.manifest, paths };\n}\n","import type { FileSystem, Manifest } from \"@venn-lang/contracts\";\nimport type { FoundProject, Package } from \"../model/project.types.js\";\nimport { ancestors, join, normalise } from \"../paths/index.js\";\nimport { memberDirs } from \"../workspace/index.js\";\nimport { loadPackage } from \"./load-package.js\";\nimport { readManifest } from \"./read-manifest.js\";\n\n/**\n * The project a path belongs to: the nearest package, and the root that owns it.\n *\n * Walking up is what lets a command run from anywhere inside a project, the way\n * `cargo` and `git` do. A package is claimed by an ancestor workspace only when\n * that workspace's members name it, so a project checked out inside another\n * never joins it by accident.\n *\n * @param args.fs Where the manifests are read from.\n * @param args.from Any path inside the project; the walk starts here.\n * @returns The project with its members loaded and inheritance applied, or one\n * `VN2101` problem when no `venn.toml` sits here or above. Reading a project\n * never throws: a failure comes back as a problem.\n */\nexport async function findProject(args: { fs: FileSystem; from: string }): Promise<FoundProject> {\n const nearest = await nearestManifest(args.fs, args.from);\n if (!nearest) return { problems: [noManifest(args.from)] };\n if (nearest.manifest.workspace) return workspaceAt(args.fs, nearest.dir, nearest.manifest);\n const owner = await owningWorkspace(args.fs, nearest.dir);\n return owner\n ? workspaceAt(args.fs, owner.dir, owner.manifest)\n : lonePackage(args.fs, nearest.dir);\n}\n\ninterface Found {\n dir: string;\n manifest: Manifest;\n}\n\nasync function nearestManifest(fs: FileSystem, from: string): Promise<Found | undefined> {\n for (const dir of ancestors(from)) {\n const manifest = await readManifest({ fs, dir });\n if (manifest) return { dir, manifest };\n }\n return undefined;\n}\n\n/** The nearest workspace above this package that lists it among its members. */\nasync function owningWorkspace(fs: FileSystem, dir: string): Promise<Found | undefined> {\n for (const above of ancestors(dir).slice(1)) {\n const manifest = await readManifest({ fs, dir: above });\n if (!manifest?.workspace) continue;\n const members = await memberDirs({ fs, root: above, workspace: manifest.workspace });\n if (members.includes(normalise(dir))) return { dir: above, manifest };\n }\n return undefined;\n}\n\nasync function lonePackage(fs: FileSystem, dir: string): Promise<FoundProject> {\n const found = await loadPackage({ fs, dir });\n if (!found) return { problems: [noManifest(dir)] };\n return {\n project: {\n root: dir,\n isWorkspace: false,\n rootManifest: found.manifest,\n packages: [found],\n defaultPackages: [found],\n },\n problems: [],\n };\n}\n\nasync function workspaceAt(\n fs: FileSystem,\n root: string,\n manifest: Manifest,\n): Promise<FoundProject> {\n const settings = manifest.workspace;\n if (!settings) return lonePackage(fs, root);\n const dirs = await memberDirs({ fs, root, workspace: settings });\n const packages = await loadEach({ fs, dirs, workspace: manifest, workspaceDir: root });\n const rootPackage = manifest.name === \"\" ? undefined : await loadPackage({ fs, dir: root });\n const all = rootPackage ? [rootPackage, ...packages] : packages;\n return {\n project: {\n root,\n isWorkspace: true,\n rootManifest: manifest,\n packages: all,\n defaultPackages: defaults(all, settings.defaultMembers, root),\n },\n problems: [],\n };\n}\n\nasync function loadEach(args: {\n fs: FileSystem;\n dirs: readonly string[];\n workspace: Manifest;\n workspaceDir: string;\n}): Promise<Package[]> {\n const found: Package[] = [];\n for (const dir of args.dirs) {\n const one = await loadPackage({\n fs: args.fs,\n dir,\n workspace: args.workspace,\n workspaceDir: args.workspaceDir,\n });\n if (one) found.push(one);\n }\n return found;\n}\n\n/** `default-members`, or every member when the root did not narrow it. */\nfunction defaults(all: readonly Package[], names: readonly string[], root: string): Package[] {\n if (names.length === 0) return [...all];\n const wanted = new Set(names.map((name) => join(root, name)));\n return all.filter((one) => wanted.has(one.dir) || wanted.has(one.manifest.name));\n}\n\nfunction noManifest(path: string): { code: string; title: string; path: string } {\n return {\n code: \"VN2101\",\n title: \"No venn.toml here, or in any folder above it.\",\n path: normalise(path),\n };\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\n\n/** Directories inside a package that are not part of it. */\nconst SKIP = new Set([\"node_modules\", \".git\", \".bin\"]);\n\n/**\n * A hash of everything a package installed.\n *\n * Not the registry's integrity hash, which lives in each manager's own lock in\n * each manager's format. This is computed from what landed on disk, which every\n * manager produces the same way. The guarantee differs: the registry's hash\n * says \"this is the tarball that was served\", this one says \"this is the tree\n * that was installed when the lock was written\". Trust on first use, and any\n * divergence after that is caught.\n *\n * A hash of hashes rather than of the concatenated bytes, because a package can\n * hold tens of megabytes and digesting it in one buffer costs more than the\n * answer is worth.\n *\n * @param args.dir The installed package directory.\n * @returns `sha256-…` over every file under `dir`, sorted by path, with\n * `node_modules`, `.git` and `.bin` skipped. A file that cannot be read is left\n * out rather than raising.\n */\nexport async function hashPackage(args: { fs: FileSystem; dir: string }): Promise<string> {\n const files = (await filesUnder(args.fs, args.dir, \"\")).sort();\n const lines: string[] = [];\n for (const path of files) {\n // Deliberately unguarded: the walk above already found this file, so a read\n // that fails is a real fault. Skipping it would hash a subset of the tree\n // and call it the tree, which is the one answer this function must not give.\n const bytes = await args.fs.read(join(args.dir, path));\n lines.push(`${path}\\t${await digest(bytes)}`);\n }\n return `sha256-${await digest(new TextEncoder().encode(lines.join(\"\\n\")))}`;\n}\n\nasync function filesUnder(fs: FileSystem, root: string, at: string): Promise<string[]> {\n const found: string[] = [];\n for (const entry of await fs.list(join(root, at))) {\n const path = at === \"\" ? entry.name : `${at}/${entry.name}`;\n if (!entry.directory) found.push(path);\n else if (!SKIP.has(entry.name)) found.push(...(await filesUnder(fs, root, path)));\n }\n return found;\n}\n\n/**\n * Web Crypto, so this runs wherever the rest of this package runs.\n *\n * The assertion narrows which buffer backs the array rather than claiming the\n * array is a buffer: `BufferSource` wants a view onto an `ArrayBuffer`, and\n * these come from a file read and from `TextEncoder`, both of which give one.\n */\nasync function digest(bytes: Uint8Array): Promise<string> {\n const hashed = await crypto.subtle.digest(\"SHA-256\", bytes as Uint8Array<ArrayBuffer>);\n return base64(new Uint8Array(hashed));\n}\n\nfunction base64(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary);\n}\n","/** One package that ended up installed. */\nexport interface LockedPackage {\n name: string;\n version: string;\n /**\n * `sha256-…` over the files this package installed.\n *\n * Not the registry's integrity hash, which lives in each manager's own lock\n * in each manager's format. This is computed from what landed on disk and\n * catches any divergence from the moment the lock was written. Absent in a\n * lock written before hashes existed. See `hashPackage`.\n */\n integrity?: string;\n /** What it says it needs, so the graph can be read without the disk. */\n dependencies?: Record<string, string>;\n}\n\n/**\n * `venn.lock`: what this project resolved to, in a form no tool owns.\n *\n * Read from what is actually installed rather than translated out of whichever\n * manager's lock produced it, because tracking three formats that change\n * without asking would mean two machines building different things the day the\n * translation drifts.\n *\n * It pins the exact version of every package and a hash of the files each one\n * installed, so `venn install --frozen` can refuse a tree that has drifted,\n * whether a registry answered differently or something was changed by hand.\n */\nexport interface Lockfile {\n version: 1;\n /** Which tool did the resolving. The answer can differ between them. */\n manager: string;\n /** Every package installed, in name order. */\n packages: readonly LockedPackage[];\n}\n\n/** The lock file's name, at the project root beside the manifest. */\nexport const LOCK_FILE = \"venn.lock\";\n\n/** The format version written into every lock. */\nexport const LOCK_VERSION = 1;\n","import { join } from \"../paths/index.js\";\n\n/**\n * Where everything a build produces or fetches goes.\n *\n * One directory at the workspace root holds every derived thing, the way\n * Cargo's `target/` does, so deleting it costs time and nothing else.\n *\n * The dependencies live inside it, and that placement is load-bearing rather\n * than tidiness: Node resolves a package by walking up from the importing file\n * looking for `node_modules`, and built output sits in `target/debug` or\n * `target/release`, one level below `target/node_modules`. The ordinary\n * resolver finds them with no loader and no symlink. Moving the output out of\n * `target/` breaks that quietly.\n */\nexport const TARGET_DIR = \"target\";\n\n/** The names inside `target/`. */\nexport const TARGET_LAYOUT = {\n /** What the package manager installs, and what Node's resolver will find. */\n modules: \"node_modules\",\n /** Compiled addons, kept apart because they are per-platform, not per-project. */\n native: \"native_modules\",\n /** Built output, one directory per profile. */\n debug: \"debug\",\n release: \"release\",\n} as const;\n\n/** Which build profile an output directory belongs to. */\nexport type ProfileName = \"debug\" | \"release\";\n\n/** The `target/` of a project, given its root. */\nexport function targetDir(root: string): string {\n return join(root, TARGET_DIR);\n}\n\n/** Where the package manager installs, and where Node's resolver will look. */\nexport function modulesDir(root: string): string {\n return join(targetDir(root), TARGET_LAYOUT.modules);\n}\n\n/** Where compiled addons go, kept apart because they are per-platform. */\nexport function nativeModulesDir(root: string): string {\n return join(targetDir(root), TARGET_LAYOUT.native);\n}\n\n/** Where a build of this profile is written. */\nexport function outputDir(args: { root: string; profile: ProfileName }): string {\n return join(targetDir(args.root), TARGET_LAYOUT[args.profile]);\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\nimport { outputDir, type ProfileName } from \"./layout.js\";\n\n/** One target a build covered, and where it starts. */\nexport interface BuiltTarget {\n package: string;\n kind: string;\n name: string;\n /** The entry file, relative to the workspace root. */\n path: string;\n}\n\n/** What one build covered, and how it went. */\nexport interface BuildRecord {\n profile: ProfileName;\n targets: readonly BuiltTarget[];\n /** How many `.vn` files were read, and how many problems were found. */\n files: number;\n problems: number;\n}\n\n/** The record's name, inside the profile's output directory. */\nexport const RECORD_FILE = \"build.json\";\n\n/**\n * Writes what a build produced, where that build's outputs go.\n *\n * It answers \"what does this project build, and did it hold together\", and it\n * is what an incremental build compares against once there is generated code to\n * skip.\n *\n * @returns The path written.\n * @throws Whatever the file system raises when the write fails.\n */\nexport async function writeBuildRecord(args: {\n fs: FileSystem;\n root: string;\n record: BuildRecord;\n}): Promise<string> {\n const path = join(outputDir({ root: args.root, profile: args.record.profile }), RECORD_FILE);\n const text = `${JSON.stringify(args.record, null, 2)}\\n`;\n await args.fs.write(path, new TextEncoder().encode(text));\n return path;\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\nimport { modulesDir } from \"../target/index.js\";\nimport { hashPackage } from \"./hash-package.js\";\nimport type { LockedPackage } from \"./lockfile.types.js\";\n\n/**\n * Every package installed, read from `target/node_modules`.\n *\n * The installed tree is the strongest statement there is about what a project\n * resolved to: it is what will actually be imported, whichever tool put it\n * there and whatever that tool's own lock says. One walk, the same for every\n * manager. A scope is a directory of packages rather than a package, so\n * `@types/node` is found one level further down.\n *\n * @param args.root The project root, not the modules directory.\n * @returns Every package with its version and content hash, in name order.\n * Dot-directories and anything without a readable `package.json` are skipped,\n * because a directory that is not a package is not a package.\n */\nexport async function readInstalled(args: {\n fs: FileSystem;\n root: string;\n}): Promise<LockedPackage[]> {\n const modules = modulesDir(args.root);\n const found: LockedPackage[] = [];\n for (const entry of await args.fs.list(modules)) {\n if (!entry.directory || entry.name.startsWith(\".\")) continue;\n if (entry.name.startsWith(\"@\")) found.push(...(await scoped(args.fs, modules, entry.name)));\n else pushIf(found, await readPackage(args.fs, join(modules, entry.name)));\n }\n return found.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nasync function scoped(fs: FileSystem, modules: string, scope: string): Promise<LockedPackage[]> {\n const found: LockedPackage[] = [];\n for (const entry of await fs.list(join(modules, scope))) {\n if (entry.directory) pushIf(found, await readPackage(fs, join(modules, scope, entry.name)));\n }\n return found;\n}\n\nfunction pushIf(into: LockedPackage[], one: LockedPackage | undefined): void {\n if (one) into.push(one);\n}\n\nasync function readPackage(fs: FileSystem, dir: string): Promise<LockedPackage | undefined> {\n const bytes = await fs.read(join(dir, \"package.json\")).catch(() => undefined);\n if (!bytes) return undefined;\n const data = parse(new TextDecoder().decode(bytes));\n if (typeof data?.name !== \"string\" || typeof data.version !== \"string\") return undefined;\n return {\n name: data.name,\n version: data.version,\n integrity: await hashPackage({ fs, dir }),\n ...deps(data.dependencies),\n };\n}\n\nfunction deps(value: unknown): { dependencies?: Record<string, string> } {\n if (typeof value !== \"object\" || value === null) return {};\n const entries = Object.entries(value as Record<string, unknown>).map(([k, v]) => [k, String(v)]);\n return entries.length > 0 ? { dependencies: Object.fromEntries(entries) } : {};\n}\n\nfunction parse(text: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(text) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport { join } from \"../paths/index.js\";\nimport { LOCK_FILE, LOCK_VERSION, type Lockfile } from \"./lockfile.types.js\";\nimport { readInstalled } from \"./read-installed.js\";\n\n/**\n * Writes `venn.lock` from what is installed.\n *\n * It goes at the project root, beside the manifest, and is meant to be\n * committed. The manager's own lock stays inside `target/`, which is derived\n * and thrown away.\n *\n * @param args.manager Which tool did the resolving, recorded in the file.\n * @returns The lock as written.\n * @throws Whatever the file system raises when the write fails.\n */\nexport async function writeLockfile(args: {\n fs: FileSystem;\n root: string;\n manager: string;\n}): Promise<Lockfile> {\n const lock: Lockfile = {\n version: LOCK_VERSION,\n manager: args.manager,\n packages: await readInstalled({ fs: args.fs, root: args.root }),\n };\n const text = `${JSON.stringify(lock, null, 2)}\\n`;\n await args.fs.write(join(args.root, LOCK_FILE), new TextEncoder().encode(text));\n return lock;\n}\n\n/**\n * Reads the lock this project committed.\n *\n * @returns The lock, or `undefined` when the file is absent or will not parse.\n * A lock nobody can read is a project without one, which `install` can fix.\n */\nexport async function readLockfile(args: {\n fs: FileSystem;\n root: string;\n}): Promise<Lockfile | undefined> {\n const bytes = await args.fs.read(join(args.root, LOCK_FILE)).catch(() => undefined);\n if (!bytes) return undefined;\n try {\n return JSON.parse(new TextDecoder().decode(bytes)) as Lockfile;\n } catch {\n return undefined;\n }\n}\n","import type { PackageManagerName } from \"@venn-lang/contracts\";\n\n/** The four verbs Venn proxies to whichever package manager the project chose. */\nexport type ProxiedVerb = \"add\" | \"remove\" | \"update\" | \"install\";\n\n/** A package manager invocation, ready to spawn. */\nexport interface ManagerCommand {\n command: string;\n args: readonly string[];\n /**\n * Whether it has to go through a shell.\n *\n * Every one of these managers ships as a script, and on Windows that means a\n * `.cmd`, which Node refuses to spawn directly because `.cmd` files were used\n * to slip arguments past programs that believed they were spawning an\n * executable. So a shell it is, and because a shell re-reads what it is\n * handed, what goes in is checked first. See {@link isSafeSpec}.\n */\n shell: boolean;\n}\n\n/**\n * The command a verb becomes, in the manager this project chose.\n *\n * The interface is ours, the resolution is not: working out which versions\n * satisfy which ranges is years of work against three moving targets, so the\n * four verbs are spelled once here and handed to the tool the project named.\n *\n * @param args.packages Specifiers to act on. Pass each through\n * {@link isSafeSpec} first when `shell` comes back `true`.\n * @returns The command, its arguments, and whether a shell is required.\n */\nexport function managerCommand(args: {\n manager: PackageManagerName;\n verb: ProxiedVerb;\n packages?: readonly string[];\n dev?: boolean;\n /** `process.platform`. It decides whether a shell is needed. */\n platform: string;\n}): ManagerCommand {\n // `-D` means \"a development dependency\" in every one of them.\n const dev = args.dev ? [\"-D\"] : [];\n return {\n command: args.manager,\n args: [verbFor(args), ...dev, ...(args.packages ?? [])],\n shell: args.platform === \"win32\",\n };\n}\n\n/** npm spells `add` and `remove` its own way; every other manager agrees. */\nfunction verbFor(args: { manager: PackageManagerName; verb: ProxiedVerb }): string {\n return args.manager === \"npm\" ? NPM[args.verb] : NON_NPM[args.verb];\n}\n\nconst NON_NPM: Record<ProxiedVerb, string> = {\n add: \"add\",\n remove: \"remove\",\n update: \"update\",\n install: \"install\",\n};\n\nconst NPM: Record<ProxiedVerb, string> = {\n add: \"install\",\n remove: \"uninstall\",\n update: \"update\",\n install: \"install\",\n};\n\n/** A package name, optionally scoped, optionally with a version range. */\nconst SPEC = /^(@[a-z0-9-~][a-z0-9-._~]*\\/)?[a-z0-9-~][a-z0-9-._~]*(@[A-Za-z0-9.^~><=*-]+)?$/;\n\n/**\n * Whether a string is a package specifier and nothing else.\n *\n * The command goes through a shell on Windows, and a shell reads `&`, `|` and\n * `>` as instructions rather than text, so `venn add \"x & del /q .\"` would\n * delete a directory while looking like an install. A real package name holds\n * none of those characters, so checking costs nothing and closes the hole\n * instead of documenting it.\n *\n * @returns `true` when the string is safe to hand to a shell.\n */\nexport function isSafeSpec(spec: string): boolean {\n return SPEC.test(spec);\n}\n","import type { Dependency, Manifest } from \"@venn-lang/contracts\";\n\n/**\n * The `package.json` a package manager is shown, generated from `venn.toml`.\n *\n * Nobody edits it and nobody commits it: the manifest is the source, this is\n * what the tool underneath happens to read. It is written into `target/`, and\n * that placement is the whole trick: a manager writes `node_modules` beside the\n * `package.json` it was pointed at, so the modules land in\n * `target/node_modules` with nothing fighting anything.\n *\n * @param args.members Their dependencies are merged in too, when the root is a\n * workspace.\n * @returns The file's text, marked private, with path dependencies left out and\n * `[patch]` turned into `overrides`.\n */\nexport function packageJsonFor(args: {\n manifest: Manifest;\n /** Every member's dependencies as well, when the root is a workspace. */\n members?: readonly Manifest[];\n}): string {\n const all = [args.manifest, ...(args.members ?? [])];\n const body = {\n name: `${args.manifest.name || \"venn-project\"}-target`,\n private: true,\n type: \"module\",\n dependencies: merged(all.flatMap((one) => one.dependencies)),\n devDependencies: merged(all.flatMap((one) => one.devDependencies)),\n ...overrides(args.manifest.patch),\n };\n return `${JSON.stringify(body, null, 2)}\\n`;\n}\n\n/**\n * The versions asked for, by name.\n *\n * A dependency on a path is not the package manager's business: it is another\n * package in this workspace, resolved by the language, and handing it over as a\n * version range would send the tool looking for it in the registry.\n */\nfunction merged(deps: readonly Dependency[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const dep of deps) {\n if (dep.path !== undefined) continue;\n out[dep.name] = dep.version ?? \"*\";\n }\n return sorted(out);\n}\n\n/** `[patch]`: the version this project insists on, wherever it is asked for. */\nfunction overrides(patch: readonly Dependency[]): { overrides?: Record<string, string> } {\n const found = merged(patch);\n return Object.keys(found).length > 0 ? { overrides: found } : {};\n}\n\n/** Written in name order, so regenerating it produces no diff of its own. */\nfunction sorted(entries: Record<string, string>): Record<string, string> {\n return Object.fromEntries(Object.entries(entries).sort(([a], [b]) => a.localeCompare(b)));\n}\n","import type { FileSystem } from \"@venn-lang/contracts\";\nimport type { Lockfile } from \"./lockfile.types.js\";\nimport { readInstalled } from \"./read-installed.js\";\n\n/** How one package differs from what the lock recorded. */\nexport interface Drift {\n name: string;\n /**\n * In the lock but absent, installed but not locked, a different version, or\n * the same version with different files.\n */\n kind: \"missing\" | \"unexpected\" | \"version\" | \"contents\";\n /** What the lock recorded: a version, or an `integrity` hash. */\n expected?: string;\n /** What is on disk, when the two can be compared side by side. */\n found?: string;\n}\n\n/**\n * Checks what is installed against what the lock says should be.\n *\n * This is what the hashes are for. Checked, they turn a registry that answered\n * differently today, or a file edited by hand inside `node_modules`, into\n * something a build refuses to start with rather than something found in\n * production.\n *\n * @returns One `Drift` per difference, empty when the tree matches. A lock\n * entry carrying no `integrity` is not checked for contents, so a lock written\n * before hashes existed still passes.\n */\nexport async function verifyLock(args: {\n fs: FileSystem;\n root: string;\n lock: Lockfile;\n}): Promise<Drift[]> {\n const installed = await readInstalled({ fs: args.fs, root: args.root });\n const byName = new Map(installed.map((one) => [one.name, one]));\n const drift = args.lock.packages.flatMap((locked) => compare(locked, byName.get(locked.name)));\n const expected = new Set(args.lock.packages.map((one) => one.name));\n const extra = installed.filter((one) => !expected.has(one.name));\n return [...drift, ...extra.map((one) => ({ name: one.name, kind: \"unexpected\" as const }))];\n}\n\ntype Locked = Lockfile[\"packages\"][number];\n\nfunction compare(locked: Locked, found: Locked | undefined): Drift[] {\n if (!found) return [{ name: locked.name, kind: \"missing\", expected: locked.version }];\n if (found.version !== locked.version) {\n return [{ name: locked.name, kind: \"version\", expected: locked.version, found: found.version }];\n }\n // Only when the lock carried one. A lock written before hashes existed is\n // still a lock, and refusing it would break a project for being older.\n if (locked.integrity && found.integrity !== locked.integrity) {\n return [{ name: locked.name, kind: \"contents\", expected: locked.integrity }];\n }\n return [];\n}\n\n/**\n * Renders drift for a person to read.\n *\n * @returns One indented line per package that differs, in the voice of what\n * went wrong. Empty for an empty list.\n */\nexport function describeDrift(drift: readonly Drift[]): string {\n return drift.map(lineFor).join(\"\\n\");\n}\n\nconst LINES: Record<Drift[\"kind\"], (drift: Drift) => string> = {\n missing: (one) => ` ${one.name}@${one.expected} is in the lock but not installed`,\n unexpected: (one) => ` ${one.name} is installed but not in the lock`,\n version: (one) => ` ${one.name}: lock says ${one.expected}, installed is ${one.found}`,\n contents: (one) => ` ${one.name}: installed files differ from what was locked`,\n};\n\nfunction lineFor(one: Drift): string {\n return LINES[one.kind](one);\n}\n","// biome-ignore-all lint/suspicious/noTemplateCurlyInString: ${name} is Venn's own interpolation inside a generated file, not a JavaScript template.\nimport { TARGET_DIR } from \"../target/index.js\";\nimport type { ScaffoldFile, ScaffoldRequest } from \"./scaffold.types.js\";\n\n/**\n * The files a new project starts as.\n *\n * Pure: it says what should exist and the caller writes it, so `--dry-run`, the\n * tests and the disk all see one answer. What is generated is deliberately\n * small, because a starting point that has to be deleted before it can be used\n * is worse than one that is missing something.\n *\n * @returns The manifest, a source file for anything that is not a workspace,\n * and a `.gitignore` unless a workspace above already owns one.\n */\nexport function scaffold(request: ScaffoldRequest): ScaffoldFile[] {\n const files = [{ path: \"venn.toml\", content: manifestFor(request) }];\n if (request.kind !== \"workspace\") files.push(sourceFor(request));\n // One `target/` per workspace means one line ignoring it, at the root that\n // owns it. A member repeating that would be ignoring something it has not got.\n if (!request.insideWorkspace) files.push(ignoreFile());\n return files;\n}\n\n/** A member leaves out what it inherits; writing it down would only shadow it. */\nfunction manifestFor(request: ScaffoldRequest): string {\n if (request.kind === \"workspace\") return workspaceManifest();\n const version = request.insideWorkspace ? [] : ['version = \"0.1.0\"'];\n return [\"[package]\", `name = \"${request.name}\"`, ...version, \"\", \"[dependencies]\", \"\"].join(\"\\n\");\n}\n\nfunction workspaceManifest(): string {\n return [\n \"[workspace]\",\n 'members = [\"packages/*\"]',\n \"\",\n \"# Metadata a member inherits by leaving it out.\",\n \"[workspace.package]\",\n 'version = \"0.1.0\"',\n \"\",\n \"# Versions a member takes with `dep = { workspace = true }`.\",\n \"[workspace.dependencies]\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** A library exports; a program runs. Each starts as the smallest of itself. */\nfunction sourceFor(request: ScaffoldRequest): ScaffoldFile {\n if (request.kind === \"lib\") {\n return {\n path: \"src/lib.vn\",\n content: [\"pub fn greet(name: string) -> string {\", ' \"Hello, ${name}\"', \"}\", \"\"].join(\"\\n\"),\n };\n }\n return { path: \"src/main.vn\", content: `print \"Hello from ${request.name}\"\\n` };\n}\n\n/** Everything derived lives in one directory, so ignoring it is one line. */\nfunction ignoreFile(): ScaffoldFile {\n return { path: \".gitignore\", content: `${TARGET_DIR}/\\n` };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@venn-lang/project",
3
+ "version": "0.1.0",
4
+ "description": "What a Venn project *is*: its manifest, its workspace members, what it builds, and where the build goes.",
5
+ "keywords": [
6
+ "venn",
7
+ "testing",
8
+ "e2e",
9
+ "workspace",
10
+ "manifest"
11
+ ],
12
+ "homepage": "https://github.com/venn-lang/venn/tree/main/packages/project#readme",
13
+ "bugs": "https://github.com/venn-lang/venn/issues",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/venn-lang/venn.git",
17
+ "directory": "packages/project"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Vinicius Borges",
21
+ "type": "module",
22
+ "sideEffects": false,
23
+ "exports": {
24
+ ".": {
25
+ "development": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "src",
34
+ "!src/**/*.test.ts",
35
+ "!src/**/*.suite.ts"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "@venn-lang/contracts": "0.1.0"
42
+ },
43
+ "devDependencies": {
44
+ "tsdown": "^0.22.14",
45
+ "typescript": "^7.0.2",
46
+ "vitest": "^4.1.10"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "test": "vitest run",
51
+ "typecheck": "tsc --noEmit"
52
+ }
53
+ }
@@ -0,0 +1,49 @@
1
+ import {
2
+ BIN_DIR,
3
+ type BuildTarget,
4
+ type FileSystem,
5
+ LIB_ROOT,
6
+ MAIN_ROOT,
7
+ } from "@venn-lang/contracts";
8
+ import { join } from "../paths/index.js";
9
+
10
+ /**
11
+ * What a package builds when its manifest does not say.
12
+ *
13
+ * `src/lib.vn` is a library, `src/main.vn` is a program, and each file in
14
+ * `src/bin/` is another program. A package that builds the obvious thing should
15
+ * not have to write it down. A declared target always wins, matched by kind and
16
+ * name, so writing `[lib]` with a different `path` moves the library rather
17
+ * than adding a second one.
18
+ *
19
+ * @param args.declared What the manifest spelled out.
20
+ * @param args.packageName Names the `lib` and the `src/main.vn` program.
21
+ * @returns The declared targets, followed by every convention found on disk.
22
+ */
23
+ export async function conventionalTargets(args: {
24
+ fs: FileSystem;
25
+ dir: string;
26
+ declared: readonly BuildTarget[];
27
+ packageName: string;
28
+ }): Promise<BuildTarget[]> {
29
+ const found = [...args.declared];
30
+ const add = async (target: BuildTarget): Promise<void> => {
31
+ if (found.some((one) => one.kind === target.kind && one.name === target.name)) return;
32
+ if (await args.fs.exists(join(args.dir, target.path))) found.push(target);
33
+ };
34
+ await add({ kind: "lib", name: args.packageName, path: LIB_ROOT });
35
+ await add({ kind: "bin", name: args.packageName, path: MAIN_ROOT });
36
+ for (const name of await binNames(args.fs, args.dir)) {
37
+ await add({ kind: "bin", name, path: `${BIN_DIR}/${name}.vn` });
38
+ }
39
+ return found;
40
+ }
41
+
42
+ /** Each `.vn` in `src/bin/` is a program named after the file. */
43
+ async function binNames(fs: FileSystem, dir: string): Promise<string[]> {
44
+ const entries = await fs.list(join(dir, BIN_DIR));
45
+ return entries
46
+ .filter((entry) => !entry.directory && entry.name.endsWith(".vn"))
47
+ .map((entry) => entry.name.slice(0, -".vn".length))
48
+ .sort();
49
+ }
@@ -0,0 +1,126 @@
1
+ import type { FileSystem, Manifest } from "@venn-lang/contracts";
2
+ import type { FoundProject, Package } from "../model/project.types.js";
3
+ import { ancestors, join, normalise } from "../paths/index.js";
4
+ import { memberDirs } from "../workspace/index.js";
5
+ import { loadPackage } from "./load-package.js";
6
+ import { readManifest } from "./read-manifest.js";
7
+
8
+ /**
9
+ * The project a path belongs to: the nearest package, and the root that owns it.
10
+ *
11
+ * Walking up is what lets a command run from anywhere inside a project, the way
12
+ * `cargo` and `git` do. A package is claimed by an ancestor workspace only when
13
+ * that workspace's members name it, so a project checked out inside another
14
+ * never joins it by accident.
15
+ *
16
+ * @param args.fs Where the manifests are read from.
17
+ * @param args.from Any path inside the project; the walk starts here.
18
+ * @returns The project with its members loaded and inheritance applied, or one
19
+ * `VN2101` problem when no `venn.toml` sits here or above. Reading a project
20
+ * never throws: a failure comes back as a problem.
21
+ */
22
+ export async function findProject(args: { fs: FileSystem; from: string }): Promise<FoundProject> {
23
+ const nearest = await nearestManifest(args.fs, args.from);
24
+ if (!nearest) return { problems: [noManifest(args.from)] };
25
+ if (nearest.manifest.workspace) return workspaceAt(args.fs, nearest.dir, nearest.manifest);
26
+ const owner = await owningWorkspace(args.fs, nearest.dir);
27
+ return owner
28
+ ? workspaceAt(args.fs, owner.dir, owner.manifest)
29
+ : lonePackage(args.fs, nearest.dir);
30
+ }
31
+
32
+ interface Found {
33
+ dir: string;
34
+ manifest: Manifest;
35
+ }
36
+
37
+ async function nearestManifest(fs: FileSystem, from: string): Promise<Found | undefined> {
38
+ for (const dir of ancestors(from)) {
39
+ const manifest = await readManifest({ fs, dir });
40
+ if (manifest) return { dir, manifest };
41
+ }
42
+ return undefined;
43
+ }
44
+
45
+ /** The nearest workspace above this package that lists it among its members. */
46
+ async function owningWorkspace(fs: FileSystem, dir: string): Promise<Found | undefined> {
47
+ for (const above of ancestors(dir).slice(1)) {
48
+ const manifest = await readManifest({ fs, dir: above });
49
+ if (!manifest?.workspace) continue;
50
+ const members = await memberDirs({ fs, root: above, workspace: manifest.workspace });
51
+ if (members.includes(normalise(dir))) return { dir: above, manifest };
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ async function lonePackage(fs: FileSystem, dir: string): Promise<FoundProject> {
57
+ const found = await loadPackage({ fs, dir });
58
+ if (!found) return { problems: [noManifest(dir)] };
59
+ return {
60
+ project: {
61
+ root: dir,
62
+ isWorkspace: false,
63
+ rootManifest: found.manifest,
64
+ packages: [found],
65
+ defaultPackages: [found],
66
+ },
67
+ problems: [],
68
+ };
69
+ }
70
+
71
+ async function workspaceAt(
72
+ fs: FileSystem,
73
+ root: string,
74
+ manifest: Manifest,
75
+ ): Promise<FoundProject> {
76
+ const settings = manifest.workspace;
77
+ if (!settings) return lonePackage(fs, root);
78
+ const dirs = await memberDirs({ fs, root, workspace: settings });
79
+ const packages = await loadEach({ fs, dirs, workspace: manifest, workspaceDir: root });
80
+ const rootPackage = manifest.name === "" ? undefined : await loadPackage({ fs, dir: root });
81
+ const all = rootPackage ? [rootPackage, ...packages] : packages;
82
+ return {
83
+ project: {
84
+ root,
85
+ isWorkspace: true,
86
+ rootManifest: manifest,
87
+ packages: all,
88
+ defaultPackages: defaults(all, settings.defaultMembers, root),
89
+ },
90
+ problems: [],
91
+ };
92
+ }
93
+
94
+ async function loadEach(args: {
95
+ fs: FileSystem;
96
+ dirs: readonly string[];
97
+ workspace: Manifest;
98
+ workspaceDir: string;
99
+ }): Promise<Package[]> {
100
+ const found: Package[] = [];
101
+ for (const dir of args.dirs) {
102
+ const one = await loadPackage({
103
+ fs: args.fs,
104
+ dir,
105
+ workspace: args.workspace,
106
+ workspaceDir: args.workspaceDir,
107
+ });
108
+ if (one) found.push(one);
109
+ }
110
+ return found;
111
+ }
112
+
113
+ /** `default-members`, or every member when the root did not narrow it. */
114
+ function defaults(all: readonly Package[], names: readonly string[], root: string): Package[] {
115
+ if (names.length === 0) return [...all];
116
+ const wanted = new Set(names.map((name) => join(root, name)));
117
+ return all.filter((one) => wanted.has(one.dir) || wanted.has(one.manifest.name));
118
+ }
119
+
120
+ function noManifest(path: string): { code: string; title: string; path: string } {
121
+ return {
122
+ code: "VN2101",
123
+ title: "No venn.toml here, or in any folder above it.",
124
+ path: normalise(path),
125
+ };
126
+ }
@@ -0,0 +1,4 @@
1
+ export { conventionalTargets } from "./conventional-targets.js";
2
+ export { findProject } from "./find-project.js";
3
+ export { loadPackage } from "./load-package.js";
4
+ export { MANIFEST_FILE, readManifest } from "./read-manifest.js";
@@ -0,0 +1,60 @@
1
+ import type { FileSystem, Manifest } from "@venn-lang/contracts";
2
+ import type { Package } from "../model/project.types.js";
3
+ import { reanchor } from "../paths/index.js";
4
+ import { inherit } from "../workspace/index.js";
5
+ import { conventionalTargets } from "./conventional-targets.js";
6
+ import { readManifest } from "./read-manifest.js";
7
+
8
+ /**
9
+ * One package: its manifest, whatever it inherits, and what it builds.
10
+ *
11
+ * Inheritance is applied here rather than at the point of use, so nothing
12
+ * downstream ever holds a manifest that is only half the answer.
13
+ *
14
+ * @param args.dir The directory holding the `venn.toml`.
15
+ * @returns The package, or `undefined` when that directory has no manifest.
16
+ */
17
+ export async function loadPackage(args: {
18
+ fs: FileSystem;
19
+ dir: string;
20
+ /** The workspace this package belongs to, when it belongs to one. */
21
+ workspace?: Manifest;
22
+ /** Where that workspace's manifest lives, which is what its paths mean. */
23
+ workspaceDir?: string;
24
+ }): Promise<Package | undefined> {
25
+ const own = await readManifest({ fs: args.fs, dir: args.dir });
26
+ if (!own) return undefined;
27
+ const merged = args.workspace ? inherit({ manifest: own, from: args.workspace }) : own;
28
+ const manifest = anchorPaths({ manifest: merged, own, args });
29
+ const targets = await conventionalTargets({
30
+ fs: args.fs,
31
+ dir: args.dir,
32
+ declared: manifest.targets,
33
+ packageName: manifest.name,
34
+ });
35
+ return { dir: args.dir, manifest, targets };
36
+ }
37
+
38
+ /**
39
+ * Inherited path aliases, rewritten to mean the same place from down here.
40
+ *
41
+ * An alias the member wrote itself is already anchored where it will be read,
42
+ * so only the ones that came from above move. Hence the member's own table is
43
+ * consulted rather than the merged one.
44
+ */
45
+ function anchorPaths(input: {
46
+ manifest: Manifest;
47
+ own: Manifest;
48
+ args: { dir: string; workspaceDir?: string };
49
+ }): Manifest {
50
+ const root = input.args.workspaceDir;
51
+ if (root === undefined || root === input.args.dir) return input.manifest;
52
+ const paths: Record<string, string> = {};
53
+ for (const [alias, value] of Object.entries(input.manifest.paths)) {
54
+ const mine = input.own.paths[alias] !== undefined;
55
+ paths[alias] = mine
56
+ ? value
57
+ : reanchor({ path: value, declaredIn: root, usedIn: input.args.dir });
58
+ }
59
+ return { ...input.manifest, paths };
60
+ }
@@ -0,0 +1,23 @@
1
+ import { createTomlManifest, type FileSystem, type Manifest } from "@venn-lang/contracts";
2
+ import { join } from "../paths/index.js";
3
+
4
+ /** The file that makes a directory a package or a workspace root. */
5
+ export const MANIFEST_FILE = "venn.toml";
6
+
7
+ /**
8
+ * Reads and parses the `venn.toml` in one directory.
9
+ *
10
+ * @returns The manifest, or `undefined` when the file is absent or unreadable.
11
+ * A directory that cannot be read is a directory without a manifest, so the
12
+ * caller keeps walking instead of failing.
13
+ */
14
+ export async function readManifest(args: {
15
+ fs: FileSystem;
16
+ dir: string;
17
+ }): Promise<Manifest | undefined> {
18
+ const path = join(args.dir, MANIFEST_FILE);
19
+ if (!(await args.fs.exists(path))) return undefined;
20
+ const bytes = await args.fs.read(path).catch(() => undefined);
21
+ if (!bytes) return undefined;
22
+ return createTomlManifest({ content: new TextDecoder().decode(bytes) }).load();
23
+ }
@@ -0,0 +1,45 @@
1
+ import type { FileSystem } from "@venn-lang/contracts";
2
+ import { join } from "../paths/index.js";
3
+
4
+ /**
5
+ * Expands a workspace's `members` patterns against the disk.
6
+ *
7
+ * `*` stands for one path segment, the way Cargo reads it, so `packages/*` is
8
+ * every package one level down and nothing deeper. `**` is deliberately not
9
+ * read: a member list that can reach arbitrarily far is one nobody can predict.
10
+ *
11
+ * @param args.root The workspace root the patterns are written against.
12
+ * @returns The directories matched, duplicates dropped, in pattern order with
13
+ * names sorted inside each pattern, so two machines list a workspace alike.
14
+ */
15
+ export async function expandMembers(args: {
16
+ fs: FileSystem;
17
+ root: string;
18
+ patterns: readonly string[];
19
+ }): Promise<string[]> {
20
+ const found: string[] = [];
21
+ for (const pattern of args.patterns) {
22
+ for (const dir of await expandOne(args.fs, args.root, pattern)) {
23
+ if (!found.includes(dir)) found.push(dir);
24
+ }
25
+ }
26
+ return found;
27
+ }
28
+
29
+ async function expandOne(fs: FileSystem, root: string, pattern: string): Promise<string[]> {
30
+ let at = [root];
31
+ for (const segment of pattern.split("/").filter((part) => part !== "")) {
32
+ at = segment === "*" ? await childrenOf(fs, at) : at.map((dir) => join(dir, segment));
33
+ }
34
+ return at;
35
+ }
36
+
37
+ async function childrenOf(fs: FileSystem, dirs: readonly string[]): Promise<string[]> {
38
+ const found: string[] = [];
39
+ for (const dir of dirs) {
40
+ const entries = await fs.list(dir);
41
+ const names = entries.filter((entry) => entry.directory).map((entry) => entry.name);
42
+ for (const name of names.sort()) found.push(join(dir, name));
43
+ }
44
+ return found;
45
+ }
@@ -0,0 +1,2 @@
1
+ export { expandMembers } from "./expand-members.js";
2
+ export { matchesMember } from "./matches-member.js";
@@ -0,0 +1,32 @@
1
+ import { normalise } from "../paths/index.js";
2
+
3
+ /**
4
+ * Whether a path would be caught by a workspace's `members`, without looking at
5
+ * the disk.
6
+ *
7
+ * Needed before the directory exists: creating `packages/api` inside a
8
+ * workspace should produce a manifest that inherits, and expanding the globs
9
+ * cannot answer that yet because there is nothing there to find.
10
+ *
11
+ * @returns `true` when some pattern matches segment for segment, `*` standing
12
+ * for exactly one segment.
13
+ */
14
+ export function matchesMember(args: {
15
+ /** The candidate directory, written relative to the workspace root. */
16
+ path: string;
17
+ patterns: readonly string[];
18
+ }): boolean {
19
+ const parts = segmentsOf(args.path);
20
+ return args.patterns.some((pattern) => matchesOne(parts, segmentsOf(pattern)));
21
+ }
22
+
23
+ function matchesOne(path: readonly string[], pattern: readonly string[]): boolean {
24
+ if (path.length !== pattern.length) return false;
25
+ return pattern.every((part, index) => part === "*" || part === path[index]);
26
+ }
27
+
28
+ function segmentsOf(path: string): string[] {
29
+ return normalise(path)
30
+ .split("/")
31
+ .filter((part) => part !== "" && part !== ".");
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * What a project is: its root, its members, and where its build goes.
3
+ *
4
+ * Every path here is text and every byte comes through the `FileSystem` port,
5
+ * so this package imports no `node:*` and the editor reads a workspace exactly
6
+ * the way the CLI does.
7
+ */
8
+
9
+ export type { BuildTarget, Dependency, Manifest, PackageInfo, Profile } from "@venn-lang/contracts";
10
+ export {
11
+ conventionalTargets,
12
+ findProject,
13
+ loadPackage,
14
+ MANIFEST_FILE,
15
+ readManifest,
16
+ } from "./discover/index.js";
17
+ export { expandMembers, matchesMember } from "./glob/index.js";
18
+ export type {
19
+ FoundProject,
20
+ Package,
21
+ Project,
22
+ ProjectProblem,
23
+ } from "./model/project.types.js";
24
+ export {
25
+ type Drift,
26
+ describeDrift,
27
+ hashPackage,
28
+ isSafeSpec,
29
+ LOCK_FILE,
30
+ LOCK_VERSION,
31
+ type LockedPackage,
32
+ type Lockfile,
33
+ type ManagerCommand,
34
+ managerCommand,
35
+ type ProxiedVerb,
36
+ packageJsonFor,
37
+ readInstalled,
38
+ readLockfile,
39
+ verifyLock,
40
+ writeLockfile,
41
+ } from "./npm/index.js";
42
+ export {
43
+ ancestors,
44
+ baseName,
45
+ isInside,
46
+ join,
47
+ normalise,
48
+ parentOf,
49
+ reanchor,
50
+ relativeTo,
51
+ } from "./paths/index.js";
52
+ export type { ScaffoldFile, ScaffoldKind, ScaffoldRequest } from "./scaffold/index.js";
53
+ export { scaffold } from "./scaffold/index.js";
54
+ export {
55
+ type BuildRecord,
56
+ type BuiltTarget,
57
+ modulesDir,
58
+ nativeModulesDir,
59
+ outputDir,
60
+ type ProfileName,
61
+ RECORD_FILE,
62
+ TARGET_DIR,
63
+ TARGET_LAYOUT,
64
+ targetDir,
65
+ writeBuildRecord,
66
+ } from "./target/index.js";
67
+ export { inherit, memberDirs } from "./workspace/index.js";