Skip to content

Commit 4e9fa66

Browse files
committed
add test
1 parent c35c783 commit 4e9fa66

8 files changed

Lines changed: 787 additions & 25 deletions

File tree

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,9 @@ coverage/
88
.idea/
99
*.swp
1010
*.swo
11-
*~
11+
*~
12+
13+
# Test fixtures (generated)
14+
test/fixtures/js-*/
15+
test/fixtures/py-*/
16+
test/fixtures/workspace-*/

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"build": "tsc",
1212
"dev": "tsc --watch",
1313
"prepublishOnly": "npm run build",
14-
"test": "echo \"No tests yet\"",
14+
"test": "./test/run-tests.sh",
15+
"test:js": "node ./test/test-suite.js",
16+
"test:fixtures": "./test/fixtures/create-fixtures.sh",
1517
"ci:version": "changeset version && pnpm install --no-frozen-lockfile",
1618
"ci:publish": "npm publish --access public"
1719
},

src/detector.ts

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,45 +19,82 @@ export function detectPackageManager(startDir = process.cwd()): DetectionResult
1919
throw new Error('No project root found. Not in a recognized project directory.');
2020
}
2121

22-
// Detect the package manager
23-
let detectedManager = registry.detectFromDirectory(projectRoot);
24-
25-
// For JavaScript projects, check for workspace root
22+
// For JavaScript projects, first search up for lockfiles to determine package manager
23+
let detectedManager: BasePackageManager | undefined;
2624
let workspaceRoot: string | undefined;
2725
let isWorkspace = false;
2826

29-
if (detectedManager && detectedManager.ecosystem === 'javascript') {
30-
// Search up for workspace root (lockfile might be in parent)
31-
let dir = projectRoot;
32-
let lockfileDir: string | null = null;
27+
const ecosystem = registry.detectEcosystem(projectRoot);
28+
29+
if (ecosystem === 'javascript') {
30+
// Search up the tree for lockfiles to determine the package manager
31+
let dir = startDir;
32+
let lockfileDir: string | undefined;
33+
let lockfileManager: BasePackageManager | undefined;
3334

3435
while (dir !== path.dirname(dir)) {
35-
// Check for lockfiles in current directory
36-
for (const lockFileName of detectedManager.lockFileNames) {
37-
if (fs.existsSync(path.join(dir, lockFileName))) {
38-
lockfileDir = dir;
39-
break;
36+
// Check all JavaScript package managers' lockfiles
37+
const jsManagers = registry.getByEcosystem('javascript');
38+
for (const manager of jsManagers) {
39+
for (const lockFileName of manager.lockFileNames) {
40+
if (fs.existsSync(path.join(dir, lockFileName))) {
41+
lockfileDir = dir;
42+
lockfileManager = manager;
43+
break;
44+
}
4045
}
46+
if (lockfileManager) break;
4147
}
4248

43-
if (lockfileDir && lockfileDir !== projectRoot) {
44-
workspaceRoot = lockfileDir;
45-
isWorkspace = true;
49+
if (lockfileManager) {
50+
detectedManager = lockfileManager;
51+
if (lockfileDir !== projectRoot) {
52+
workspaceRoot = lockfileDir;
53+
isWorkspace = true;
54+
}
4655
break;
4756
}
4857

4958
// Move up one directory
5059
const parentDir = path.dirname(dir);
5160

52-
// Continue searching if parent has package.json (could be workspace root)
53-
if (fs.existsSync(path.join(parentDir, 'package.json'))) {
61+
// Stop if we've reached the root of the filesystem
62+
if (parentDir === dir) {
63+
break;
64+
}
65+
66+
// Continue searching if:
67+
// 1. Parent has package.json (could be workspace root)
68+
// 2. Parent's parent has package.json (we might be in packages/ dir)
69+
// 3. We're still within a reasonable project structure (max 5 levels up)
70+
const parentHasPackageJson = fs.existsSync(path.join(parentDir, 'package.json'));
71+
const grandparentDir = path.dirname(parentDir);
72+
const grandparentHasPackageJson = grandparentDir !== parentDir &&
73+
fs.existsSync(path.join(grandparentDir, 'package.json'));
74+
75+
if (parentHasPackageJson || grandparentHasPackageJson) {
5476
dir = parentDir;
5577
} else {
56-
break;
78+
// One more check: if parent has common workspace config files
79+
const hasWorkspaceConfig = fs.existsSync(path.join(parentDir, 'pnpm-workspace.yaml')) ||
80+
fs.existsSync(path.join(parentDir, 'pnpm-workspace.yml')) ||
81+
fs.existsSync(path.join(parentDir, 'lerna.json'));
82+
if (hasWorkspaceConfig) {
83+
dir = parentDir;
84+
} else {
85+
break;
86+
}
5787
}
5888
}
89+
}
5990

60-
// Try to read package.json for corepack config
91+
// If no lockfile found, detect from the project root directory
92+
if (!detectedManager) {
93+
detectedManager = registry.detectFromDirectory(projectRoot);
94+
}
95+
96+
// For JavaScript projects, check for corepack config
97+
if (detectedManager && detectedManager.ecosystem === 'javascript') {
6198
const detectionRoot = workspaceRoot || projectRoot;
6299
try {
63100
const packageJson = JSON.parse(fs.readFileSync(path.join(detectionRoot, 'package.json'), 'utf-8'));

src/package-manager-registry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class PackageManagerRegistry {
103103
return undefined;
104104
}
105105

106-
private detectEcosystem(dir: string): Ecosystem | undefined {
106+
detectEcosystem(dir: string): Ecosystem | undefined {
107107
// JavaScript ecosystem detection
108108
if (fs.existsSync(path.join(dir, 'package.json')) ||
109109
fs.existsSync(path.join(dir, 'node_modules'))) {

src/xpm.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { synchronizeDependencies } from './dependency-synchronizer';
44
import { mapCommand } from './command-mapper';
55
import { setDefaultPackageManager, setGlobalPackageManager, getGlobalPackageManager } from './config';
66
import { registry } from './package-manager-registry';
7+
import { hasScript } from './package-json';
78
import { SKIP_SYNC_COMMANDS, GLOBAL_SUPPORT_COMMANDS } from './command-constants';
89

910
export class XPM {
@@ -149,9 +150,10 @@ Supported package managers:
149150
try {
150151
const { packageManager, projectRoot, isWorkspace, workspaceRoot } = detectPackageManager();
151152

152-
// Auto-sync dependencies unless it's an install-like command or no command
153+
// Auto-sync dependencies unless it's an install-like command, no command, or a script
153154
// Only for JavaScript projects currently
154-
if (command && !SKIP_SYNC_COMMANDS.includes(command as any) && packageManager.ecosystem === 'javascript') {
155+
const isScript = command && packageManager.ecosystem === 'javascript' && hasScript(command, projectRoot);
156+
if (command && !SKIP_SYNC_COMMANDS.includes(command as any) && !isScript && packageManager.ecosystem === 'javascript') {
155157
synchronizeDependencies({ packageManager, projectRoot, workspaceRoot, dryRun: this.dryRun });
156158
}
157159

test/fixtures/create-fixtures.sh

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#!/bin/bash
2+
3+
# Create test fixtures for all package managers
4+
FIXTURES_DIR="$(dirname "$0")"
5+
6+
# JavaScript Package Managers
7+
echo "Creating JavaScript package manager fixtures..."
8+
9+
# NPM
10+
mkdir -p "$FIXTURES_DIR/js-npm"
11+
cat > "$FIXTURES_DIR/js-npm/package.json" << 'EOF'
12+
{
13+
"name": "test-npm",
14+
"version": "1.0.0",
15+
"scripts": {
16+
"test": "echo 'npm test'",
17+
"build": "echo 'npm build'"
18+
}
19+
}
20+
EOF
21+
touch "$FIXTURES_DIR/js-npm/package-lock.json"
22+
23+
# Yarn
24+
mkdir -p "$FIXTURES_DIR/js-yarn"
25+
cat > "$FIXTURES_DIR/js-yarn/package.json" << 'EOF'
26+
{
27+
"name": "test-yarn",
28+
"version": "1.0.0",
29+
"packageManager": "yarn@3.0.0",
30+
"scripts": {
31+
"test": "echo 'yarn test'",
32+
"build": "echo 'yarn build'"
33+
}
34+
}
35+
EOF
36+
touch "$FIXTURES_DIR/js-yarn/yarn.lock"
37+
38+
# PNPM
39+
mkdir -p "$FIXTURES_DIR/js-pnpm"
40+
cat > "$FIXTURES_DIR/js-pnpm/package.json" << 'EOF'
41+
{
42+
"name": "test-pnpm",
43+
"version": "1.0.0",
44+
"scripts": {
45+
"test": "echo 'pnpm test'",
46+
"build": "echo 'pnpm build'"
47+
}
48+
}
49+
EOF
50+
touch "$FIXTURES_DIR/js-pnpm/pnpm-lock.yaml"
51+
52+
# Bun
53+
mkdir -p "$FIXTURES_DIR/js-bun"
54+
cat > "$FIXTURES_DIR/js-bun/package.json" << 'EOF'
55+
{
56+
"name": "test-bun",
57+
"version": "1.0.0",
58+
"scripts": {
59+
"test": "echo 'bun test'",
60+
"build": "echo 'bun build'"
61+
}
62+
}
63+
EOF
64+
touch "$FIXTURES_DIR/js-bun/bun.lockb"
65+
66+
# Python Package Managers
67+
echo "Creating Python package manager fixtures..."
68+
69+
# Pip
70+
mkdir -p "$FIXTURES_DIR/py-pip"
71+
cat > "$FIXTURES_DIR/py-pip/requirements.txt" << 'EOF'
72+
flask==2.0.0
73+
requests==2.28.0
74+
EOF
75+
76+
# Pipenv
77+
mkdir -p "$FIXTURES_DIR/py-pipenv"
78+
cat > "$FIXTURES_DIR/py-pipenv/Pipfile" << 'EOF'
79+
[[source]]
80+
url = "https://pypi.org/simple"
81+
verify_ssl = true
82+
name = "pypi"
83+
84+
[packages]
85+
flask = "==2.0.0"
86+
87+
[dev-packages]
88+
pytest = "*"
89+
EOF
90+
touch "$FIXTURES_DIR/py-pipenv/Pipfile.lock"
91+
92+
# Poetry
93+
mkdir -p "$FIXTURES_DIR/py-poetry"
94+
cat > "$FIXTURES_DIR/py-poetry/pyproject.toml" << 'EOF'
95+
[tool.poetry]
96+
name = "test-poetry"
97+
version = "0.1.0"
98+
description = "Test Poetry project"
99+
100+
[tool.poetry.dependencies]
101+
python = "^3.8"
102+
flask = "^2.0.0"
103+
104+
[tool.poetry.dev-dependencies]
105+
pytest = "^7.0.0"
106+
EOF
107+
touch "$FIXTURES_DIR/py-poetry/poetry.lock"
108+
109+
# UV
110+
mkdir -p "$FIXTURES_DIR/py-uv"
111+
cat > "$FIXTURES_DIR/py-uv/pyproject.toml" << 'EOF'
112+
[project]
113+
name = "test-uv"
114+
version = "0.1.0"
115+
dependencies = [
116+
"flask>=2.0.0",
117+
]
118+
119+
[tool.uv]
120+
dev-dependencies = [
121+
"pytest>=7.0.0",
122+
]
123+
EOF
124+
touch "$FIXTURES_DIR/py-uv/uv.lock"
125+
126+
# Conda
127+
mkdir -p "$FIXTURES_DIR/py-conda"
128+
cat > "$FIXTURES_DIR/py-conda/environment.yml" << 'EOF'
129+
name: test-conda
130+
dependencies:
131+
- python=3.9
132+
- flask=2.0.0
133+
- pip:
134+
- requests==2.28.0
135+
EOF
136+
137+
# Workspace configurations
138+
echo "Creating workspace fixtures..."
139+
140+
# NPM Workspace
141+
mkdir -p "$FIXTURES_DIR/workspace-npm/packages/app"
142+
cat > "$FIXTURES_DIR/workspace-npm/package.json" << 'EOF'
143+
{
144+
"name": "npm-workspace",
145+
"version": "1.0.0",
146+
"workspaces": [
147+
"packages/*"
148+
]
149+
}
150+
EOF
151+
cat > "$FIXTURES_DIR/workspace-npm/packages/app/package.json" << 'EOF'
152+
{
153+
"name": "@workspace/app",
154+
"version": "1.0.0"
155+
}
156+
EOF
157+
touch "$FIXTURES_DIR/workspace-npm/package-lock.json"
158+
159+
# PNPM Workspace
160+
mkdir -p "$FIXTURES_DIR/workspace-pnpm/packages/app"
161+
cat > "$FIXTURES_DIR/workspace-pnpm/pnpm-workspace.yaml" << 'EOF'
162+
packages:
163+
- 'packages/*'
164+
EOF
165+
cat > "$FIXTURES_DIR/workspace-pnpm/package.json" << 'EOF'
166+
{
167+
"name": "pnpm-workspace",
168+
"version": "1.0.0"
169+
}
170+
EOF
171+
cat > "$FIXTURES_DIR/workspace-pnpm/packages/app/package.json" << 'EOF'
172+
{
173+
"name": "@workspace/app",
174+
"version": "1.0.0"
175+
}
176+
EOF
177+
touch "$FIXTURES_DIR/workspace-pnpm/pnpm-lock.yaml"
178+
179+
# Yarn Workspace
180+
mkdir -p "$FIXTURES_DIR/workspace-yarn/packages/app"
181+
cat > "$FIXTURES_DIR/workspace-yarn/package.json" << 'EOF'
182+
{
183+
"name": "yarn-workspace",
184+
"version": "1.0.0",
185+
"workspaces": [
186+
"packages/*"
187+
]
188+
}
189+
EOF
190+
cat > "$FIXTURES_DIR/workspace-yarn/packages/app/package.json" << 'EOF'
191+
{
192+
"name": "@workspace/app",
193+
"version": "1.0.0"
194+
}
195+
EOF
196+
touch "$FIXTURES_DIR/workspace-yarn/yarn.lock"
197+
198+
echo "Fixtures created successfully!"

0 commit comments

Comments
 (0)