Skip to content

Commit 3d824e1

Browse files
authored
feat(please-plugins): add monorepo workspace dependency scanning (#133)
* docs(track): add setup-monorepo-scan-20260329 * chore(track): setup-monorepo-scan-20260329 start implementation * feat(please-plugins): add monorepo workspace dependency scanning Add resolveWorkspacePackages() and collectAllDependencies() to scan workspace package.json files in monorepos. Supports npm/yarn/Bun workspaces field and pnpm-workspace.yaml. Integrates into both scanForSetup() and detectForEvent() with workspace-aware source tracking (e.g., "apps/web: @nuxt/ui"). Closes #132 * refactor(please-plugins): extract workspace resolver to separate module Move resolveWorkspacePackages, collectAllDependencies, and resolveGlobPatterns to workspace-resolver.ts to keep check-dependencies.ts under 500 LOC guideline. * docs(track): add retrospective for setup-monorepo-scan-20260329 * chore: apply AI code review suggestions - Replace custom glob resolver with Bun.Glob for accurate multi-segment pattern matching (fixes patterns like "apps/*/src" truncation bug) - Deduplicate resolved workspace paths to avoid duplicate entries when npm workspaces and pnpm-workspace.yaml share the same patterns - Improve pnpm-workspace.yaml parsing to handle inline comments and more robust top-level key detection - Tighten test assertions for deduplication (exact length 1) and negation patterns (exact length 2 with both expected dirs)
1 parent ff798ee commit 3d824e1

7 files changed

Lines changed: 574 additions & 11 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"track_id": "setup-monorepo-scan-20260329",
3+
"type": "feature",
4+
"status": "in_progress",
5+
"created_at": "2026-03-29T16:03:23+09:00",
6+
"updated_at": "2026-03-29T16:06:00+09:00",
7+
"issue": "#132",
8+
"pr": "",
9+
"project": ""
10+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Plan: Monorepo Workspace Dependency Scanning
2+
3+
> Track: setup-monorepo-scan-20260329
4+
> Spec: [spec.md](./spec.md)
5+
6+
## Overview
7+
8+
- **Source**: [spec.md](./spec.md)
9+
- **Issue**: #132
10+
- **Created**: 2026-03-29
11+
- **Approach**: Pragmatic
12+
13+
## Purpose
14+
15+
After this change, users running `/please-plugins:setup` in a monorepo project will see plugin recommendations based on dependencies from all workspace packages, not just the root. They can verify it works by running the setup command in a monorepo and confirming that dependencies from sub-packages (e.g., `apps/web/package.json`) trigger the correct plugin recommendations.
16+
17+
## Context
18+
19+
The `check-dependencies.ts` hook currently reads only the root `package.json` to detect dependencies that match plugin mappings. In monorepo projects using npm/yarn/Bun workspaces or pnpm workspaces, the root `package.json` often contains only shared dev dependencies (e.g., `vitest`, `eslint`), while framework-specific packages (e.g., `@nuxt/ui`, `vue`, `prisma`) live in workspace packages like `apps/web/package.json` or `packages/api/package.json`.
20+
21+
This means the plugin recommender misses most relevant plugins in monorepo setups. The fix requires resolving workspace package paths from the root configuration, reading each workspace `package.json`, and merging all dependencies before running detection.
22+
23+
Key constraints:
24+
- Must support npm/yarn/Bun `workspaces` field (array of glob patterns) and `pnpm-workspace.yaml`
25+
- Must use synchronous fs + glob only (no child processes) to keep hook latency low
26+
- Must gracefully skip missing or malformed workspace `package.json` files
27+
- Must not break non-monorepo projects (backward compatible)
28+
29+
Non-goals: nested workspace resolution, per-package detection reporting, Yarn PnP.
30+
31+
## Architecture Decision
32+
33+
The approach adds workspace resolution as a layer between `loadPackageJson()` and the existing detection functions. A new `resolveWorkspacePackages(cwd, rootPkg)` function resolves glob patterns from the `workspaces` field or `pnpm-workspace.yaml` into concrete directory paths. A new `collectAllDependencies(cwd)` function calls `loadPackageJson()` for each workspace package and merges all `dependencies` + `devDependencies` into a single record, tagging each entry with its source path for later source attribution.
34+
35+
This is minimally invasive: `detectPackages()` continues to receive a flat deps record, `scanForSetup()` and `detectForEvent()` simply call `collectAllDependencies()` instead of `loadPackageJson()`. Source tracking is enhanced to show `apps/web: @nuxt/ui` when a dependency was found in a workspace package.
36+
37+
Bun's built-in `Bun.Glob` provides synchronous, fast glob resolution without adding dependencies.
38+
39+
## Tasks
40+
41+
- [x] T001 Add workspace resolution function `resolveWorkspacePackages` (file: plugins/please-plugins/hooks/check-dependencies.ts)
42+
- [x] T002 Add dependency aggregation function `collectAllDependencies` (file: plugins/please-plugins/hooks/check-dependencies.ts) (depends on T001)
43+
- [x] T003 Integrate workspace scanning into `scanForSetup` and `detectForEvent` (file: plugins/please-plugins/hooks/check-dependencies.ts) (depends on T002)
44+
- [x] T004 Enhance source tracking for workspace-origin dependencies (file: plugins/please-plugins/hooks/check-dependencies.ts) (depends on T003)
45+
46+
## Key Files
47+
48+
### Modify
49+
50+
- `plugins/please-plugins/hooks/check-dependencies.ts` — Main scanner; add `resolveWorkspacePackages()`, `collectAllDependencies()`, update `scanForSetup()`, `detectForEvent()`, `resolvePackageSource()`
51+
- `plugins/please-plugins/hooks/check-dependencies.test.ts` — Add tests for workspace resolution, aggregation, and integration
52+
53+
### Reuse
54+
55+
- `plugins/please-plugins/hooks/plugin-mappings.json` — Existing package-to-plugin mappings (unchanged)
56+
- `plugins/please-plugins/hooks/tooling-mappings.json` — Existing tooling mappings (unchanged)
57+
58+
## Verification
59+
60+
### Automated Tests
61+
62+
- [ ] `resolveWorkspacePackages` resolves npm/yarn/Bun `workspaces` glob patterns to directory paths
63+
- [ ] `resolveWorkspacePackages` resolves `pnpm-workspace.yaml` patterns to directory paths
64+
- [ ] `resolveWorkspacePackages` returns empty array when no workspaces configured
65+
- [ ] `collectAllDependencies` merges deps from root + workspace packages
66+
- [ ] `collectAllDependencies` skips missing/malformed workspace package.json
67+
- [ ] `scanForSetup` detects plugins from workspace dependencies
68+
- [ ] `detectForEvent` (SessionStart) detects workspace dependencies
69+
- [ ] Source field shows workspace path prefix for workspace-origin deps
70+
- [ ] Non-monorepo projects continue to work unchanged
71+
72+
### Observable Outcomes
73+
74+
- Running `bun run plugins/please-plugins/hooks/check-dependencies.ts --setup` in this repo detects dependencies from `apps/web/package.json` (e.g., `@nuxt/ui`, `@nuxt/content`)
75+
- Running the same in a non-monorepo project produces identical output as before
76+
77+
### Acceptance Criteria Check
78+
79+
- [ ] AC-1: Dependencies from `apps/web/package.json` detected in a monorepo with `workspaces`
80+
- [ ] AC-2: Dependencies detected from `pnpm-workspace.yaml` projects
81+
- [ ] AC-3: SessionStart hook scans workspace packages
82+
- [ ] AC-4: `--setup` mode aggregates all workspace dependencies
83+
- [ ] AC-5: Non-monorepo projects unchanged
84+
85+
## Decision Log
86+
87+
- Decision: Use `Bun.Glob` for workspace pattern resolution
88+
Rationale: Built-in, synchronous, no additional dependencies, fast
89+
Date/Author: 2026-03-29 / Claude
90+
91+
- Decision: Merge all workspace deps into a single flat record rather than per-package detection
92+
Rationale: Simpler implementation, aligns with user preference from spec, `detectPackages()` API unchanged
93+
Date/Author: 2026-03-29 / Claude
94+
95+
## Outcomes & Retrospective
96+
97+
### What Was Shipped
98+
- Workspace resolution for npm/yarn/Bun `workspaces` and `pnpm-workspace.yaml`
99+
- Dependency aggregation across all workspace packages
100+
- Workspace-aware source tracking (e.g., `apps/web: @nuxt/ui`)
101+
- Extracted `workspace-resolver.ts` module to maintain file size guideline
102+
103+
### What Went Well
104+
- Existing test infrastructure made TDD straightforward
105+
- Clean extraction of workspace logic into separate module during review
106+
- All 62 tests passing with zero regressions
107+
108+
### What Could Improve
109+
- Initial implementation exceeded 500 LOC guideline; caught during review phase
110+
111+
### Tech Debt Created
112+
- None
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Monorepo Workspace Dependency Scanning for Setup Command
2+
3+
> Track: setup-monorepo-scan-20260329
4+
5+
## Overview
6+
7+
Enhance the `please-plugins` dependency scanner (`check-dependencies.ts`) to discover and aggregate dependencies from all workspace packages in monorepo projects. Currently, only the root `package.json` is read, which misses dependencies declared in workspace packages (e.g., `apps/web/package.json`, `packages/shared/package.json`).
8+
9+
## Requirements
10+
11+
### Functional Requirements
12+
13+
- [ ] FR-1: Resolve workspace package paths from the root `package.json` `workspaces` field (npm/yarn/Bun format — array of glob patterns)
14+
- [ ] FR-2: Resolve workspace package paths from `pnpm-workspace.yaml` (`packages` field — array of glob patterns)
15+
- [ ] FR-3: Read `package.json` from each resolved workspace directory and merge all `dependencies` + `devDependencies` into a single aggregated set
16+
- [ ] FR-4: Use the aggregated dependency set for plugin detection in both `scanForSetup()` and `detectForEvent()` (SessionStart hook)
17+
- [ ] FR-5: When a plugin is detected from a workspace package (not root), include the workspace path in the `source` field (e.g., `apps/web: @nuxt/ui`)
18+
19+
### Non-functional Requirements
20+
21+
- [ ] NFR-1: Workspace resolution must not significantly increase hook latency — use synchronous `fs` and `glob` only, no external processes
22+
- [ ] NFR-2: Gracefully handle missing or malformed workspace package.json files (skip with no error)
23+
24+
## Acceptance Criteria
25+
26+
- [ ] AC-1: In a monorepo with `workspaces: ["apps/*", "packages/*"]`, dependencies from `apps/web/package.json` are detected by the scanner
27+
- [ ] AC-2: In a monorepo with `pnpm-workspace.yaml`, workspace packages are resolved and scanned
28+
- [ ] AC-3: The SessionStart hook detects workspace dependencies, not just root dependencies
29+
- [ ] AC-4: The `--setup` mode aggregates all workspace dependencies for plugin detection
30+
- [ ] AC-5: Non-monorepo projects (no `workspaces` field, no `pnpm-workspace.yaml`) continue to work unchanged
31+
32+
## Out of Scope
33+
34+
- Nested workspace resolution (monorepo within a monorepo)
35+
- Per-package detection reporting (we merge all deps, not per-package)
36+
- Yarn PnP resolution or Yarn Berry-specific behaviors
37+
- Workspace dependency graph analysis
38+
39+
## Assumptions
40+
41+
- Glob resolution for workspace patterns uses `Bun.glob` or Node.js `glob` with synchronous API
42+
- The root `package.json` is always the entry point; workspace files are additive
43+
- Duplicate dependencies across workspaces are deduplicated naturally (merged into a single key set)

.please/docs/tracks/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
| [hooks-if-field-20260328](active/hooks-if-field-20260328/) | Add `if` field to hooks | feature || 2026-03-28 | planned |
1313
| [web-nuxt-update-20260328](active/web-nuxt-update-20260328/plan.md) | Web App Dependency Update | chore | #126 | 2026-03-28 | in_progress |
1414
| [fix-setup-glob-pattern-20260329](active/fix-setup-glob-pattern-20260329/plan.md) | Fix setup command package.json discovery | bugfix | #129 | 2026-03-29 | in_progress |
15+
| [setup-monorepo-scan-20260329](active/setup-monorepo-scan-20260329/plan.md) | Monorepo Workspace Dependency Scanning | feature | #132 | 2026-03-29 | in_progress |
1516

1617
## Recently Completed
1718

plugins/please-plugins/hooks/check-dependencies.test.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { join } from 'node:path'
44
import { tmpdir } from 'node:os'
55
import {
66
buildOutput,
7+
collectAllDependencies,
78
detectPackages,
89
detectTooling,
910
extractPackagesFromCommand,
1011
filterInstalledPlugins,
12+
resolveWorkspacePackages,
1113
scanForSetup,
1214
type DetectedPlugin,
1315
type PluginMapping,
@@ -381,4 +383,207 @@ describe('scanForSetup', () => {
381383
const pnpmResults = result.detected.filter(d => d.pluginName === 'pnpm')
382384
expect(pnpmResults).toHaveLength(1)
383385
})
386+
387+
test('detects plugins from workspace packages', () => {
388+
const dir = mkdtempSync(join(tmpdir(), 'setup-'))
389+
// Root package.json with workspaces but no matching deps
390+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
391+
workspaces: ['apps/*'],
392+
devDependencies: { 'typescript': '^5.0.0' },
393+
}))
394+
// Workspace package with matching deps
395+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
396+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), JSON.stringify({
397+
dependencies: { '@nuxt/ui': '^3.0.0' },
398+
}))
399+
const result = scanForSetup(dir)
400+
expect(result.detected).toContainEqual({ pluginName: 'nuxt-ui', source: 'apps/web: @nuxt/ui' })
401+
})
402+
403+
test('detects plugins from pnpm workspace packages', () => {
404+
const dir = mkdtempSync(join(tmpdir(), 'setup-'))
405+
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'root' }))
406+
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - "packages/*"\n')
407+
mkdirSync(join(dir, 'packages', 'api'), { recursive: true })
408+
writeFileSync(join(dir, 'packages', 'api', 'package.json'), JSON.stringify({
409+
dependencies: { '@prisma/client': '^5.0.0' },
410+
}))
411+
const result = scanForSetup(dir)
412+
expect(result.detected).toContainEqual({ pluginName: 'prisma', source: 'packages/api: @prisma/client' })
413+
})
414+
415+
test('merges root and workspace deps without duplicates', () => {
416+
const dir = mkdtempSync(join(tmpdir(), 'setup-'))
417+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
418+
workspaces: ['apps/*'],
419+
devDependencies: { 'vitest': '^4.0.0' },
420+
}))
421+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
422+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), JSON.stringify({
423+
dependencies: { '@nuxt/ui': '^3.0.0', 'vitest': '^4.0.0' },
424+
}))
425+
const result = scanForSetup(dir)
426+
const pluginNames = result.detected.map(d => d.pluginName)
427+
// vitest detected from root (no workspace prefix), nuxt-ui from workspace
428+
expect(pluginNames).toContain('vitest')
429+
expect(pluginNames).toContain('nuxt-ui')
430+
const vitestResult = result.detected.find(d => d.pluginName === 'vitest')
431+
expect(vitestResult?.source).toBe('vitest') // root, no prefix
432+
})
433+
})
434+
435+
describe('resolveWorkspacePackages', () => {
436+
test('resolves npm workspaces glob patterns', () => {
437+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
438+
const pkg = { workspaces: ['apps/*', 'packages/*'] }
439+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
440+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), '{}')
441+
mkdirSync(join(dir, 'packages', 'shared'), { recursive: true })
442+
writeFileSync(join(dir, 'packages', 'shared', 'package.json'), '{}')
443+
const result = resolveWorkspacePackages(dir, pkg)
444+
expect(result).toHaveLength(2)
445+
expect(result).toContainEqual(join(dir, 'apps', 'web'))
446+
expect(result).toContainEqual(join(dir, 'packages', 'shared'))
447+
})
448+
449+
test('resolves pnpm-workspace.yaml patterns', () => {
450+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
451+
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - "apps/*"\n - "packages/*"\n')
452+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
453+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), '{}')
454+
const result = resolveWorkspacePackages(dir, null)
455+
expect(result).toHaveLength(1)
456+
expect(result).toContainEqual(join(dir, 'apps', 'web'))
457+
})
458+
459+
test('returns empty array when no workspaces configured', () => {
460+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
461+
const result = resolveWorkspacePackages(dir, { name: 'test' })
462+
expect(result).toHaveLength(0)
463+
})
464+
465+
test('skips directories without package.json', () => {
466+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
467+
const pkg = { workspaces: ['apps/*'] }
468+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
469+
// No package.json in apps/web
470+
mkdirSync(join(dir, 'apps', 'api'), { recursive: true })
471+
writeFileSync(join(dir, 'apps', 'api', 'package.json'), '{}')
472+
const result = resolveWorkspacePackages(dir, pkg)
473+
expect(result).toHaveLength(1)
474+
expect(result).toContainEqual(join(dir, 'apps', 'api'))
475+
})
476+
477+
test('handles yarn workspaces { packages: [...] } format', () => {
478+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
479+
const pkg = { workspaces: { packages: ['apps/*'] } }
480+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
481+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), '{}')
482+
const result = resolveWorkspacePackages(dir, pkg)
483+
expect(result).toHaveLength(1)
484+
})
485+
486+
test('merges npm workspaces and pnpm-workspace.yaml without duplicates', () => {
487+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
488+
const pkg = { workspaces: ['apps/*'] }
489+
writeFileSync(join(dir, 'pnpm-workspace.yaml'), 'packages:\n - "apps/*"\n')
490+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
491+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), '{}')
492+
const result = resolveWorkspacePackages(dir, pkg)
493+
// Both sources resolve to the same directory; deduplication ensures exactly one entry
494+
expect(result).toHaveLength(1)
495+
expect(result).toContainEqual(join(dir, 'apps', 'web'))
496+
})
497+
498+
test('handles exact directory paths (no globs)', () => {
499+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
500+
const pkg = { workspaces: ['tools/cli'] }
501+
mkdirSync(join(dir, 'tools', 'cli'), { recursive: true })
502+
writeFileSync(join(dir, 'tools', 'cli', 'package.json'), '{}')
503+
const result = resolveWorkspacePackages(dir, pkg)
504+
expect(result).toHaveLength(1)
505+
expect(result).toContainEqual(join(dir, 'tools', 'cli'))
506+
})
507+
508+
test('skips negation patterns', () => {
509+
const dir = mkdtempSync(join(tmpdir(), 'ws-'))
510+
const pkg = { workspaces: ['packages/*', '!packages/internal'] }
511+
mkdirSync(join(dir, 'packages', 'shared'), { recursive: true })
512+
writeFileSync(join(dir, 'packages', 'shared', 'package.json'), '{}')
513+
mkdirSync(join(dir, 'packages', 'internal'), { recursive: true })
514+
writeFileSync(join(dir, 'packages', 'internal', 'package.json'), '{}')
515+
const result = resolveWorkspacePackages(dir, pkg)
516+
// Negation patterns are skipped (not filtered), so internal still appears from "packages/*"
517+
// This is a known limitation — true negation filtering is out of scope
518+
// Both shared and internal match the "packages/*" glob, so we expect exactly 2 results
519+
expect(result).toHaveLength(2)
520+
expect(result).toContainEqual(join(dir, 'packages', 'shared'))
521+
expect(result).toContainEqual(join(dir, 'packages', 'internal'))
522+
})
523+
})
524+
525+
describe('collectAllDependencies', () => {
526+
test('merges root and workspace deps', () => {
527+
const dir = mkdtempSync(join(tmpdir(), 'collect-'))
528+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
529+
workspaces: ['apps/*'],
530+
dependencies: { 'express': '^4.0.0' },
531+
}))
532+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
533+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), JSON.stringify({
534+
dependencies: { '@nuxt/ui': '^3.0.0' },
535+
}))
536+
const result = collectAllDependencies(dir)
537+
expect(result.deps).toHaveProperty('express')
538+
expect(result.deps).toHaveProperty('@nuxt/ui')
539+
expect(result.sources).not.toHaveProperty('express') // root dep, no source entry
540+
expect(result.sources['@nuxt/ui']).toBe('apps/web')
541+
})
542+
543+
test('root deps take priority over workspace deps', () => {
544+
const dir = mkdtempSync(join(tmpdir(), 'collect-'))
545+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
546+
workspaces: ['apps/*'],
547+
devDependencies: { 'vitest': '^4.0.0' },
548+
}))
549+
mkdirSync(join(dir, 'apps', 'web'), { recursive: true })
550+
writeFileSync(join(dir, 'apps', 'web', 'package.json'), JSON.stringify({
551+
devDependencies: { 'vitest': '^3.0.0' },
552+
}))
553+
const result = collectAllDependencies(dir)
554+
expect(result.deps['vitest']).toBe('^4.0.0') // root version wins
555+
expect(result.sources).not.toHaveProperty('vitest') // no workspace source for root dep
556+
})
557+
558+
test('skips malformed workspace package.json', () => {
559+
const dir = mkdtempSync(join(tmpdir(), 'collect-'))
560+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
561+
workspaces: ['apps/*'],
562+
}))
563+
mkdirSync(join(dir, 'apps', 'broken'), { recursive: true })
564+
writeFileSync(join(dir, 'apps', 'broken', 'package.json'), 'not valid json')
565+
mkdirSync(join(dir, 'apps', 'good'), { recursive: true })
566+
writeFileSync(join(dir, 'apps', 'good', 'package.json'), JSON.stringify({
567+
dependencies: { 'pinia': '^2.0.0' },
568+
}))
569+
const result = collectAllDependencies(dir)
570+
expect(result.deps).toHaveProperty('pinia')
571+
})
572+
573+
test('returns empty deps when no package.json', () => {
574+
const dir = mkdtempSync(join(tmpdir(), 'collect-'))
575+
const result = collectAllDependencies(dir)
576+
expect(Object.keys(result.deps)).toHaveLength(0)
577+
expect(Object.keys(result.sources)).toHaveLength(0)
578+
})
579+
580+
test('works with non-monorepo (no workspaces)', () => {
581+
const dir = mkdtempSync(join(tmpdir(), 'collect-'))
582+
writeFileSync(join(dir, 'package.json'), JSON.stringify({
583+
dependencies: { 'express': '^4.0.0' },
584+
}))
585+
const result = collectAllDependencies(dir)
586+
expect(result.deps).toHaveProperty('express')
587+
expect(Object.keys(result.sources)).toHaveLength(0)
588+
})
384589
})

0 commit comments

Comments
 (0)