{"version":3,"file":"bunWorkspaces.cjs","names":["fg","path","fs"],"sources":["../src/bunWorkspaces.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport fg from 'fast-glob';\n\n/** The `workspaces` field of a root package.json: the array form or Yarn v1's object form. */\nexport type WorkspacesDeclaration = string[] | { packages?: string[] } | undefined;\n\n/**\n * Every workspace package.json path (relative to the monorepo root, sorted) that Bun would link\n * for the given root package.json `workspaces` declaration. Canonicalizes the declared patterns\n * (see getMeaningfulDeclaredWorkspacePatterns), drops patterns escaping the repository (see\n * isInRepositoryWorkspacePattern), and resolves the rest with Bun's sequential semantics (see\n * resolveWorkspacePackageJsonPaths).\n */\nexport function resolveBunWorkspacePackageJsonPaths(workspaces: WorkspacesDeclaration, rootDirPath: string): string[] {\n  return resolveWorkspacePackageJsonPaths(\n    getMeaningfulDeclaredWorkspacePatterns(workspaces).filter((workspacePattern) =>\n      isInRepositoryWorkspacePattern(workspacePattern)\n    ),\n    rootDirPath\n  );\n}\n\n/**\n * Resolves workspace patterns to package.json paths mimicking Bun's SEQUENTIAL evaluation,\n * derived empirically with Bun 1.3.14 (fixture repos observed via `bun install --lockfile-only`;\n * WillBooster/shared#1004 / WillBooster/shared#1005):\n * 1. Patterns are evaluated in declaration order into an accumulating set: a positive glob\n *    pattern adds every package.json it matches; a negation deletes its matches from the set\n *    accumulated SO FAR, so a later positive re-adds them (`[\"!apps/excluded\", \"apps/*\"]` links\n *    apps/excluded, while `[\"apps/*\", \"!apps/excluded\"]` does not).\n * 2. A negation whose normalized body has EXACTLY two segments, whose last segment is a star-run\n *    (`*`, `**`, `***`, …), and whose first segment is NOT itself a star-run first seeds the\n *    implicit baseline into the set, then deletes its own matches: a `**` last segment seeds\n *    `**` (packages at any depth), any other star-run seeds `*\\/*` (depth 2 only). Seeding\n *    happens even alongside positive patterns (`[\"apps/*\", \"!other/*\"]` links every depth-2\n *    package outside other/). No other negation shape seeds: `!*`, `!**`, `!*\\/*`, `!*\\/**`,\n *    `!**\\/*`, `!a/b/*`, `!a/b/**`, `!dir`, and `!dir/*x` each link nothing on their own.\n *    `?`, brace, and character-class last segments behave ERRATICALLY (`[\"!other/?\"]` links\n *    packages/a yet not the equally baseline-shaped other/yy, `[\"!other/??\"]` links nothing, and\n *    `[\"!?/?\"]` links the very other/x it matches), so no consistent rule can model them; they\n *    are deliberately treated as not seeding — see hasImplicitWorkspaceBaseline and #1005.\n * 3. A non-glob positive pattern PINS its directory: it stays a workspace regardless of where a\n *    matching negation appears (`[\"other/x\", \"!other/x\"]` and `[\"!other/x\", \"other/x\"]` both\n *    link other/x).\n * 4. `**` matches zero or more path segments (`[\"apps/**\"]` links the package at apps itself, and\n *    `!apps/**` deletes it), matching fast-glob's file-glob semantics.\n * Do not apply globIgnore here: workspace membership is defined solely by the declared patterns,\n * and source-scanning ignores such as `build` or `dist` would hide legitimately named workspace\n * directories.\n */\nexport function resolveWorkspacePackageJsonPaths(workspacePatterns: string[], rootDirPath: string): string[] {\n  // followSymbolicLinks: false stops GLOB traversal through symlinks, but a non-glob pattern\n  // naming a symlinked directory (e.g. `linked` with `linked -> ../other-repo`) still matches —\n  // fast-glob resolves static patterns with direct fs checks, and Bun does link such a workspace.\n  // Deliberately diverge from Bun there: consumers such as node_modules cleanup and manifest\n  // rewriting would otherwise delete and rewrite files in ANOTHER repository through the symlink,\n  // so keep only manifests whose real path stays inside the repository's real root. Scanning each\n  // pattern separately (no cross-pattern caching) is deliberate: declarations hold a handful of\n  // patterns, so a cache would complicate this shared code without measurable gain.\n  const globManifestPaths = (pattern: string): string[] => {\n    // Bun links dot-directory packages only through fully static patterns: with Bun 1.3.14,\n    // `.hidden/x` pins the package while `.hidden/*`, `.*/*`, and `**` all link nothing under\n    // .hidden — even when the dotted segment itself is literal — so any dynamic pattern must\n    // drop matches containing a dot-led segment (fast-glob's `dot: false` only covers segments\n    // a wildcard matches).\n    const excludesDotSegments = fg.isDynamicPattern(pattern);\n    const globOptions = { cwd: rootDirPath, followSymbolicLinks: false, ignore: ['**/node_modules/**'] };\n    // fast-glob 3.3.3 returns no matches for file globs with a lone-`?` segment (e.g.\n    // `packages/?/package.json`), although micromatch matches them and Bun 1.3.14 links such\n    // workspaces; for `?`-carrying patterns only (a directory glob for e.g. `**` would scan every\n    // directory in the repository), globbing the directories (where `?` works) and checking their\n    // manifests complements the manifest glob, which stays necessary for `**`'s zero-segment\n    // matches — `dir/**` does not return dir itself as a directory.\n    const manifestPaths = new Set(fg.globSync(path.posix.join(pattern, 'package.json'), globOptions));\n    if (pattern.includes('?')) {\n      for (const dirPath of fg.globSync(pattern, { ...globOptions, onlyDirectories: true })) {\n        const packageJsonPath = path.posix.join(dirPath, 'package.json');\n        if (fs.existsSync(path.join(rootDirPath, packageJsonPath))) manifestPaths.add(packageJsonPath);\n      }\n    }\n    // A zero-segment `**` match reaches the root's own manifest, but Bun never treats the\n    // monorepo root as its own workspace.\n    return [...manifestPaths].filter(\n      (packageJsonPath) =>\n        packageJsonPath !== 'package.json' &&\n        (!excludesDotSegments || !packageJsonPath.split('/').some((segment) => segment.startsWith('.'))) &&\n        isInsideRealRoot(packageJsonPath)\n    );\n  };\n  let realRootDirPath: string | undefined;\n  const isInsideRealRoot = (packageJsonPath: string): boolean => {\n    try {\n      realRootDirPath ??= fs.realpathSync(rootDirPath);\n      const relativePath = path.relative(realRootDirPath, fs.realpathSync(path.join(rootDirPath, packageJsonPath)));\n      // Compare whole segments, not a `..` prefix: a directory literally named e.g. `..pkg` is\n      // inside the root, while a plain startsWith('..') would misread it as parent traversal.\n      return relativePath !== '..' && !relativePath.startsWith('../') && !path.isAbsolute(relativePath);\n    } catch {\n      // A manifest that vanished between the glob and the realpath call is not a workspace.\n      return false;\n    }\n  };\n  const accumulatedPaths = new Set<string>();\n  const pinnedPaths = new Set<string>();\n  for (const workspacePattern of workspacePatterns) {\n    const isNegative = workspacePattern.startsWith('!');\n    const patternBody = normalizeWorkspacePatternBody(isNegative ? workspacePattern.slice(1) : workspacePattern);\n    if (isNegative) {\n      const baselineGlob = getSeededBaselineGlob(patternBody);\n      if (baselineGlob !== undefined) {\n        for (const packageJsonPath of globManifestPaths(baselineGlob)) accumulatedPaths.add(packageJsonPath);\n      }\n      for (const packageJsonPath of globManifestPaths(patternBody)) accumulatedPaths.delete(packageJsonPath);\n    } else {\n      const targetPaths = fg.isDynamicPattern(patternBody) ? accumulatedPaths : pinnedPaths;\n      for (const packageJsonPath of globManifestPaths(patternBody)) {\n        targetPaths.add(packageJsonPath);\n      }\n    }\n  }\n  return [...new Set([...accumulatedPaths, ...pinnedPaths])].toSorted();\n}\n\n/**\n * Whether the declaration seeds Bun's implicit workspace baseline: it contains at least one\n * negation of the seeding shape (see resolveWorkspacePackageJsonPaths rule 2). Measured with Bun\n * 1.3.14, seeding is per-negation and happens even alongside positive patterns; concrete or\n * mixed-literal last segments (`!apps/excluded`, `!apps/*d`) never seed, and `?`, brace, and\n * character-class last segments behaved inconsistently across fixtures (sometimes dropping even\n * unrelated sibling workspaces), so they are conservatively treated as not seeding; see\n * WillBooster/shared#1005.\n */\nexport function hasImplicitWorkspaceBaseline(workspaces: WorkspacesDeclaration): boolean {\n  return getMeaningfulDeclaredWorkspacePatterns(workspaces).some(\n    (workspacePattern) =>\n      workspacePattern.startsWith('!') &&\n      getSeededBaselineGlob(normalizeWorkspacePatternBody(workspacePattern.slice(1))) !== undefined\n  );\n}\n\n/**\n * Declared workspace patterns without Bun's no-op ones (`\"\"`, a lone `\"!\"`, and `\".\"`), which Bun\n * ignores entirely — a lone `\"!\"` must not activate the negative-only implicit baseline, and `\"\"`\n * must not make the repository root itself a discovered workspace. Declaration order and\n * duplicates are preserved — Bun evaluates the patterns sequentially, so position matters.\n */\nexport function getMeaningfulDeclaredWorkspacePatterns(workspaces: WorkspacesDeclaration): string[] {\n  return getDeclaredWorkspacePatterns(workspaces)\n    .map((workspacePattern) => {\n      // Bun applies leading-bang PARITY (verified with Bun 1.3.14): `!!p` is the positive `p`\n      // and `!!!p` the negation `!p`, so canonicalize to at most one bang.\n      const bangCount = /^!*/u.exec(workspacePattern)![0].length;\n      const patternBody = workspacePattern.slice(bangCount);\n      return bangCount % 2 === 1 ? `!${patternBody}` : patternBody;\n    })\n    .filter((workspacePattern) => {\n      const patternBody = workspacePattern.startsWith('!') ? workspacePattern.slice(1) : workspacePattern;\n      // Normalize so spellings like `./`, `./.`, and `!./` are recognized as the same no-ops\n      // (path.posix.normalize('') === '.', so the empty pattern is covered too).\n      return normalizeWorkspacePatternBody(patternBody) !== '.';\n    });\n}\n\n/**\n * Collapses `//`, resolves `./`, and strips trailing slashes, mirroring Bun's path handling.\n * A pure star-run segment of three or more stars behaves like `*` under Bun (`!other/***`\n * matches other/x) but not under fast-glob, so canonicalize it to `*`.\n */\nexport function normalizeWorkspacePatternBody(patternBody: string): string {\n  return path.posix\n    .normalize(patternBody)\n    .replace(/\\/+$/u, '')\n    .split('/')\n    .map((segment) => (/^\\*{3,}$/u.test(segment) ? '*' : segment))\n    .join('/');\n}\n\n/** The implicit baseline glob a negation seeds under Bun's rule 2 above, or undefined if none. */\nexport function getSeededBaselineGlob(negationBody: string): string | undefined {\n  const segments = negationBody.split('/');\n  if (segments.length !== 2) return undefined;\n  const [firstSegment, lastSegment] = segments as [string, string];\n  if (!/^\\*+$/u.test(lastSegment) || /^\\*+$/u.test(firstSegment)) return undefined;\n  return lastSegment === '**' ? '**' : '*/*';\n}\n\n/** Workspace patterns from either the array form or Yarn v1's `{ packages: […] }` object form. */\nexport function getDeclaredWorkspacePatterns(workspaces: WorkspacesDeclaration): string[] {\n  if (Array.isArray(workspaces)) return workspaces;\n  return Array.isArray(workspaces?.packages) ? workspaces.packages : [];\n}\n\n/**\n * Whether a declared workspace pattern (negative ones included) stays inside the repository:\n * absolute or `..`-traversing patterns would make consumers such as node_modules cleanup operate\n * on another repository's files.\n */\nexport function isInRepositoryWorkspacePattern(workspacePattern: string): boolean {\n  const patternBody = workspacePattern.startsWith('!') ? workspacePattern.slice(1) : workspacePattern;\n  return !path.posix.isAbsolute(patternBody) && !patternBody.split('/').includes('..');\n}\n"],"mappings":"mMAeA,SAAgB,EAAoC,EAAmC,EAA+B,CACpH,OAAO,EACL,EAAuC,CAAU,CAAC,CAAC,OAAQ,GACzD,EAA+B,CAAgB,CACjD,EACA,CACF,CACF,CA8BA,SAAgB,EAAiC,EAA6B,EAA+B,CAS3G,IAAM,EAAqB,GAA8B,CAMvD,IAAM,EAAsBA,EAAAA,QAAG,iBAAiB,CAAO,EACjD,EAAc,CAAE,IAAK,EAAa,oBAAqB,GAAO,OAAQ,CAAC,oBAAoB,CAAE,EAO7F,EAAgB,IAAI,IAAIA,EAAAA,QAAG,SAASC,EAAAA,QAAK,MAAM,KAAK,EAAS,cAAc,EAAG,CAAW,CAAC,EAChG,GAAI,EAAQ,SAAS,GAAG,EACtB,IAAK,IAAM,KAAWD,EAAAA,QAAG,SAAS,EAAS,CAAE,GAAG,EAAa,gBAAiB,EAAK,CAAC,EAAG,CACrF,IAAM,EAAkBC,EAAAA,QAAK,MAAM,KAAK,EAAS,cAAc,EAC3DC,EAAAA,QAAG,WAAWD,EAAAA,QAAK,KAAK,EAAa,CAAe,CAAC,GAAG,EAAc,IAAI,CAAe,CAC/F,CAIF,MAAO,CAAC,GAAG,CAAa,CAAC,CAAC,OACvB,GACC,IAAoB,iBACnB,CAAC,GAAuB,CAAC,EAAgB,MAAM,GAAG,CAAC,CAAC,KAAM,GAAY,EAAQ,WAAW,GAAG,CAAC,IAC9F,EAAiB,CAAe,CACpC,CACF,EACI,EACE,EAAoB,GAAqC,CAC7D,GAAI,CACF,IAAoBC,EAAAA,QAAG,aAAa,CAAW,EAC/C,IAAM,EAAeD,EAAAA,QAAK,SAAS,EAAiBC,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAK,EAAa,CAAe,CAAC,CAAC,EAG5G,OAAO,IAAiB,MAAQ,CAAC,EAAa,WAAW,KAAK,GAAK,CAACA,EAAAA,QAAK,WAAW,CAAY,CAClG,MAAQ,CAEN,MAAO,EACT,CACF,EACM,EAAmB,IAAI,IACvB,EAAc,IAAI,IACxB,IAAK,IAAM,KAAoB,EAAmB,CAChD,IAAM,EAAa,EAAiB,WAAW,GAAG,EAC5C,EAAc,EAA8B,EAAa,EAAiB,MAAM,CAAC,EAAI,CAAgB,EAC3G,GAAI,EAAY,CACd,IAAM,EAAe,EAAsB,CAAW,EACtD,GAAI,IAAiB,IAAA,GACnB,IAAK,IAAM,KAAmB,EAAkB,CAAY,EAAG,EAAiB,IAAI,CAAe,EAErG,IAAK,IAAM,KAAmB,EAAkB,CAAW,EAAG,EAAiB,OAAO,CAAe,CACvG,KAAO,CACL,IAAM,EAAcD,EAAAA,QAAG,iBAAiB,CAAW,EAAI,EAAmB,EAC1E,IAAK,IAAM,KAAmB,EAAkB,CAAW,EACzD,EAAY,IAAI,CAAe,CAEnC,CACF,CACA,MAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAkB,GAAG,CAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CACtE,CAWA,SAAgB,EAA6B,EAA4C,CACvF,OAAO,EAAuC,CAAU,CAAC,CAAC,KACvD,GACC,EAAiB,WAAW,GAAG,GAC/B,EAAsB,EAA8B,EAAiB,MAAM,CAAC,CAAC,CAAC,IAAM,IAAA,EACxF,CACF,CAQA,SAAgB,EAAuC,EAA6C,CAClG,OAAO,EAA6B,CAAU,CAAC,CAC5C,IAAK,GAAqB,CAGzB,IAAM,EAAY,OAAO,KAAK,CAAgB,CAAC,CAAE,EAAE,CAAC,OAC9C,EAAc,EAAiB,MAAM,CAAS,EACpD,OAAO,EAAY,GAAM,EAAI,IAAI,IAAgB,CACnD,CAAC,CAAC,CACD,OAAQ,GAIA,EAHa,EAAiB,WAAW,GAAG,EAAI,EAAiB,MAAM,CAAC,EAAI,CAGnC,IAAM,GACvD,CACL,CAOA,SAAgB,EAA8B,EAA6B,CACzE,OAAOC,EAAAA,QAAK,MACT,UAAU,CAAW,CAAC,CACtB,QAAQ,QAAS,EAAE,CAAC,CACpB,MAAM,GAAG,CAAC,CACV,IAAK,GAAa,YAAY,KAAK,CAAO,EAAI,IAAM,CAAQ,CAAC,CAC7D,KAAK,GAAG,CACb,CAGA,SAAgB,EAAsB,EAA0C,CAC9E,IAAM,EAAW,EAAa,MAAM,GAAG,EACvC,GAAI,EAAS,SAAW,EAAG,OAC3B,GAAM,CAAC,EAAc,GAAe,EAChC,MAAC,SAAS,KAAK,CAAW,GAAK,SAAS,KAAK,CAAY,GAC7D,OAAO,IAAgB,KAAO,KAAO,KACvC,CAGA,SAAgB,EAA6B,EAA6C,CAExF,OADI,MAAM,QAAQ,CAAU,EAAU,EAC/B,MAAM,QAAQ,GAAY,QAAQ,EAAI,EAAW,SAAW,CAAC,CACtE,CAOA,SAAgB,EAA+B,EAAmC,CAChF,IAAM,EAAc,EAAiB,WAAW,GAAG,EAAI,EAAiB,MAAM,CAAC,EAAI,EACnF,MAAO,CAACA,EAAAA,QAAK,MAAM,WAAW,CAAW,GAAK,CAAC,EAAY,MAAM,GAAG,CAAC,CAAC,SAAS,IAAI,CACrF"}