Skip to content

Commit d3a6239

Browse files
committed
added composer version detection
1 parent 819bac1 commit d3a6239

File tree

2 files changed

+265
-1
lines changed

2 files changed

+265
-1
lines changed

src/options.mjs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,66 @@
1+
import fs from "fs";
2+
import path from "path";
3+
14
const CATEGORY_PHP = "PHP";
25

6+
/**
7+
* Detect minimum PHP version from the composer.json file
8+
* @param def {string} Default PHP version to use if not found
9+
* @return {string} The PHP version to use in the composer.json file.
10+
*/
11+
function getComposerPhpVer(def) {
12+
// Try to find composer.json
13+
const currentDir = process.cwd();
14+
let composerPath = null;
15+
16+
const directComposerPath = path.join(currentDir, "composer.json");
17+
if (fs.existsSync(directComposerPath)) {
18+
composerPath = directComposerPath;
19+
}
20+
21+
if (!composerPath) {
22+
let searchDir = path.dirname(currentDir);
23+
while (searchDir !== path.parse(searchDir).root) {
24+
const potentialComposerPath = path.join(searchDir, "composer.json");
25+
if (fs.existsSync(potentialComposerPath)) {
26+
composerPath = potentialComposerPath;
27+
break;
28+
}
29+
searchDir = path.dirname(searchDir);
30+
}
31+
}
32+
33+
if (composerPath) {
34+
try {
35+
const fileContent = fs.readFileSync(composerPath, "utf8");
36+
const composerJson = JSON.parse(fileContent);
37+
38+
if (composerJson.require && composerJson.require.php) {
39+
// Extract version from composer semver format
40+
const versionMatch = composerJson.require.php.match(
41+
/^(?:[^0-9]*)?([0-9]+)\.([0-9]+)/
42+
);
43+
44+
if (versionMatch) {
45+
return `${versionMatch[1]}.${versionMatch[2]}`;
46+
}
47+
}
48+
} catch (error) {
49+
return def;
50+
}
51+
}
52+
53+
return def;
54+
}
55+
56+
export { getComposerPhpVer };
57+
358
export default {
459
phpVersion: {
560
since: "0.13.0",
661
category: CATEGORY_PHP,
762
type: "choice",
8-
default: "8.3",
63+
default: getComposerPhpVer("8.3"),
964
description: "Minimum target PHP version.",
1065
choices: [
1166
{ value: "5.0" },
@@ -24,6 +79,7 @@ export default {
2479
{ value: "8.1" },
2580
{ value: "8.2" },
2681
{ value: "8.3" },
82+
{ value: "8.4" },
2783
],
2884
},
2985
trailingCommaPHP: {
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { getComposerPhpVer } from "../../src/options.mjs";
2+
import fs from "fs";
3+
import path from "path";
4+
import os from "os";
5+
6+
describe("getComposerPhpVer", () => {
7+
// Create a unique temporary directory for our tests
8+
const tempDir = path.join(os.tmpdir(), `composer-version-test-${Date.now()}`);
9+
const tempComposerPath = path.join(tempDir, "composer.json");
10+
const originalCwd = process.cwd();
11+
12+
beforeEach(() => {
13+
// Create temp directory if it doesn't exist
14+
if (!fs.existsSync(tempDir)) {
15+
fs.mkdirSync(tempDir, { recursive: true });
16+
}
17+
});
18+
19+
afterEach(() => {
20+
process.chdir(originalCwd);
21+
22+
// Clean up temp files and directories
23+
if (fs.existsSync(tempComposerPath)) {
24+
fs.unlinkSync(tempComposerPath);
25+
}
26+
27+
// Remove any nested directories we created
28+
if (fs.existsSync(tempDir)) {
29+
const deleteFolderRecursive = function(dirPath) {
30+
if (fs.existsSync(dirPath)) {
31+
fs.readdirSync(dirPath).forEach((file) => {
32+
const curPath = path.join(dirPath, file);
33+
if (fs.lstatSync(curPath).isDirectory()) {
34+
deleteFolderRecursive(curPath);
35+
} else {
36+
fs.unlinkSync(curPath);
37+
}
38+
});
39+
fs.rmdirSync(dirPath);
40+
}
41+
};
42+
43+
deleteFolderRecursive(tempDir);
44+
}
45+
});
46+
47+
test("returns default value when no composer.json is found", () => {
48+
// Create a directory with no composer.json
49+
const emptyDir = path.join(tempDir, "empty-dir");
50+
fs.mkdirSync(emptyDir, { recursive: true });
51+
52+
process.chdir(emptyDir);
53+
54+
const defaultVersion = "7.4";
55+
expect(getComposerPhpVer(defaultVersion)).toBe(defaultVersion);
56+
});
57+
58+
test.each([
59+
[">=7.1.0", "7.1"],
60+
["^8.0", "8.0"],
61+
["~7.4", "7.4"],
62+
[">=5.6.0 <8.0.0", "5.6"],
63+
["7.3.*", "7.3"]
64+
])("extracts correct version from %s", (versionConstraint, expectedVersion) => {
65+
const composerContent = JSON.stringify(
66+
{
67+
require: {
68+
php: versionConstraint,
69+
},
70+
},
71+
null,
72+
2
73+
);
74+
75+
fs.writeFileSync(tempComposerPath, composerContent);
76+
77+
process.chdir(tempDir);
78+
79+
// Call getComposerPhpVer to test version extraction
80+
const result = getComposerPhpVer("default");
81+
expect(result).toBe(expectedVersion);
82+
});
83+
84+
test.each([
85+
[">=7.1.0", "7.1"],
86+
["^8.0", "8.0"],
87+
["~7.4", "7.4"],
88+
[">=5.6.0 <8.0.0", "5.6"],
89+
["7.3.*", "7.3"]
90+
])("extracts correct version from %s by changing cwd", (versionConstraint, expectedVersion) => {
91+
const composerContent = JSON.stringify(
92+
{
93+
require: {
94+
php: versionConstraint,
95+
},
96+
},
97+
null,
98+
2
99+
);
100+
101+
fs.writeFileSync(tempComposerPath, composerContent);
102+
103+
process.chdir(tempDir);
104+
105+
const result = getComposerPhpVer("default");
106+
expect(result).toBe(expectedVersion);
107+
});
108+
109+
test("returns default when composer.json has no PHP requirement", () => {
110+
const composerContent = JSON.stringify(
111+
{
112+
require: {
113+
// No PHP requirement
114+
"some/package": "^1.0"
115+
},
116+
},
117+
null,
118+
2
119+
);
120+
121+
fs.writeFileSync(tempComposerPath, composerContent);
122+
123+
process.chdir(tempDir);
124+
125+
const defaultVersion = "8.3";
126+
const result = getComposerPhpVer(defaultVersion);
127+
expect(result).toBe(defaultVersion);
128+
});
129+
130+
test("returns default when composer.json has invalid PHP requirement", () => {
131+
const composerContent = JSON.stringify(
132+
{
133+
require: {
134+
php: "invalid-version"
135+
},
136+
},
137+
null,
138+
2
139+
);
140+
141+
fs.writeFileSync(tempComposerPath, composerContent);
142+
143+
process.chdir(tempDir);
144+
145+
const defaultVersion = "8.3";
146+
const result = getComposerPhpVer(defaultVersion);
147+
expect(result).toBe(defaultVersion);
148+
});
149+
150+
test("finds composer.json in parent directory when in nested child folder", () => {
151+
// Create a nested directory structure
152+
const nestedDir1 = path.join(tempDir, "level1");
153+
const nestedDir2 = path.join(nestedDir1, "level2");
154+
const nestedDir3 = path.join(nestedDir2, "level3");
155+
156+
fs.mkdirSync(nestedDir1, { recursive: true });
157+
fs.mkdirSync(nestedDir2, { recursive: true });
158+
fs.mkdirSync(nestedDir3, { recursive: true });
159+
160+
// Create composer.json in the root temp directory
161+
const composerContent = JSON.stringify(
162+
{
163+
require: {
164+
php: "^8.1"
165+
},
166+
},
167+
null,
168+
2
169+
);
170+
171+
fs.writeFileSync(tempComposerPath, composerContent);
172+
173+
process.chdir(nestedDir3);
174+
175+
const result = getComposerPhpVer("default");
176+
expect(result).toBe("8.1");
177+
});
178+
179+
test("finds composer.json in intermediate parent directory", () => {
180+
// Create a nested directory structure
181+
const nestedDir1 = path.join(tempDir, "folder1");
182+
const nestedDir2 = path.join(nestedDir1, "folder2");
183+
const nestedDir3 = path.join(nestedDir2, "folder3");
184+
185+
fs.mkdirSync(nestedDir1, { recursive: true });
186+
fs.mkdirSync(nestedDir2, { recursive: true });
187+
fs.mkdirSync(nestedDir3, { recursive: true });
188+
189+
// Create composer.json in the middle level directory
190+
const intermediateComposerPath = path.join(nestedDir2, "composer.json");
191+
const composerContent = JSON.stringify(
192+
{
193+
require: {
194+
php: "~7.2"
195+
},
196+
},
197+
null,
198+
2
199+
);
200+
201+
fs.writeFileSync(intermediateComposerPath, composerContent);
202+
203+
process.chdir(nestedDir3);
204+
205+
const result = getComposerPhpVer("default");
206+
expect(result).toBe("7.2");
207+
});
208+
});

0 commit comments

Comments
 (0)