Skip to content

Commit e33fa94

Browse files
committed
Initial development of PHP package.json management
1 parent 7631957 commit e33fa94

7 files changed

Lines changed: 284 additions & 0 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php namespace System\Classes;
2+
3+
use File;
4+
use Winter\Storm\Exception\SystemException;
5+
use Winter\Storm\Support\Arr;
6+
7+
/**
8+
* PHP-based "package.json" handler.
9+
*
10+
* Allows for the management of "package.json" files through PHP, handling creation and modification of the file and
11+
* dependency management.
12+
*
13+
* @author Winter CMS
14+
*/
15+
class PackageJson
16+
{
17+
/**
18+
* Path to the "package.json" file.
19+
*
20+
* @var string|null
21+
*/
22+
protected $path;
23+
24+
/**
25+
* Structure of the "package.json" file.
26+
*
27+
* @var array
28+
*/
29+
protected $structure = [];
30+
31+
/**
32+
* Standard structure for new "package.json" files.
33+
*
34+
* @var array
35+
*/
36+
protected $defaultStructure = [
37+
'private' => true,
38+
'dependencies' => [],
39+
'devDependencies' => [],
40+
'engines' => [
41+
'node' => '>= 14',
42+
'npm' => '>= 7',
43+
],
44+
];
45+
46+
/**
47+
* Constructor.
48+
*
49+
* @param string|null $path
50+
*/
51+
public function __construct($path = null)
52+
{
53+
$this->path = $path;
54+
55+
if ($this->exists()) {
56+
$this->parseStructure();
57+
}
58+
}
59+
60+
/**
61+
* Gets a value from the structure using dot notation.
62+
*
63+
* @param string|null $keyPath
64+
* @param mixed|null $default Default value
65+
* @return mixed|null
66+
*/
67+
public function get($keyPath = null, $default = null)
68+
{
69+
if (is_null($keyPath)) {
70+
return $this->structure;
71+
}
72+
73+
return Arr::get($this->structure, $keyPath, $default);
74+
}
75+
76+
/**
77+
* Sets a value in the structure using dot notation.
78+
*
79+
* @param string $keyPath
80+
* @param mixed $value
81+
* @return void
82+
*/
83+
public function set($keyPath, $value)
84+
{
85+
Arr::set($this->structure, $keyPath, $value);
86+
}
87+
88+
/**
89+
* Sets the path of the "package.json" file.
90+
*
91+
* @param string $path
92+
* @return void
93+
*/
94+
public function setPath($path)
95+
{
96+
$this->path = $path;
97+
98+
if ($this->exists()) {
99+
$this->parseStructure();
100+
}
101+
}
102+
103+
/**
104+
* Sets the default structure for newly created "package.json" files.
105+
*
106+
* @param array $structure
107+
* @return void
108+
*/
109+
public function setDefaultStructure(array $structure)
110+
{
111+
$this->defaultStructure = $structure;
112+
}
113+
114+
/**
115+
* Determines if the "package.json" file eixsts.
116+
*
117+
* @return bool
118+
*/
119+
public function exists()
120+
{
121+
if (is_null($this->path)) {
122+
return false;
123+
}
124+
125+
return File::exists($this->path);
126+
}
127+
128+
/**
129+
* Creates a new "package.json" file in the path.
130+
*
131+
* @param bool $mergeDefaults If `true`, the given structure will be merged with the default structure.
132+
* @return void
133+
*/
134+
public function create($structure = [], $mergeDefaults = true)
135+
{
136+
$newStructure = ($mergeDefaults) ? $this->defaultStructure : [];
137+
$newStructure = array_replace($newStructure, $structure);
138+
139+
if ($this->exists()) {
140+
File::delete($this->path);
141+
}
142+
143+
// Set structure
144+
$this->structure = $newStructure;
145+
146+
if (!File::put($this->path, $this->buildFile())) {
147+
throw new SystemException(sprintf(
148+
'Unable to write to file "%s". Please check your permissions on this path.',
149+
$this->path
150+
));
151+
}
152+
}
153+
154+
/**
155+
* Writes the "package.json" file.
156+
*
157+
* @return void
158+
*/
159+
public function write()
160+
{
161+
if (!$this->exists()) {
162+
throw new SystemException(sprintf(
163+
'Package file "%s" does not exist. Please create it before writing.',
164+
$this->path
165+
));
166+
}
167+
168+
if (!File::put($this->path, $this->buildFile())) {
169+
throw new SystemException(sprintf(
170+
'Unable to write to file "%s". Please check your permissions on this path.',
171+
$this->path
172+
));
173+
}
174+
}
175+
176+
/**
177+
* Renders the contents of the "package.json" file.
178+
*
179+
* @return string
180+
*/
181+
protected function buildFile()
182+
{
183+
return json_encode($this->structure, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
184+
}
185+
186+
/**
187+
* Parses the structure of an existing "package.json" file.
188+
*
189+
* @return void
190+
*/
191+
protected function parseStructure()
192+
{
193+
$this->structure = json_decode(File::get($this->path));
194+
}
195+
}

tests/TestCase.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ public function createApplication()
2424
return $app;
2525
}
2626

27+
/**
28+
* Generates a path to a fixture
29+
*
30+
* @param string $appendPath
31+
* @return string
32+
*/
33+
public function fixturePath($appendPath = '')
34+
{
35+
$appendPath = trim($appendPath, DIRECTORY_SEPARATOR);
36+
return base_path('tests/fixtures/' . $appendPath);
37+
}
38+
2739
//
2840
// Helpers
2941
//
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Override for package.json tests to ensure package file contents are exactly the same as generated from
2+
# System\Classes\PackageJson class
3+
4+
[*]
5+
insert_final_newline = false
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"private": true
3+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"private": true,
3+
"dependencies": [],
4+
"devDependencies": [],
5+
"engines": {
6+
"node": ">= 14",
7+
"npm": ">= 7"
8+
}
9+
}

tests/fixtures/package-json/complex.package.json

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
use System\Classes\PackageJson;
4+
5+
class PackageJsonTest extends TestCase
6+
{
7+
public function tearDown(): void
8+
{
9+
if (file_exists($this->fixturePath('package-json/created.package.json'))) {
10+
@unlink($this->fixturePath('package-json/created.package.json'));
11+
}
12+
13+
parent::tearDown();
14+
}
15+
16+
public function testExists(): void
17+
{
18+
$package = new PackageJson($this->fixturePath('package-json/complex.package.json'));
19+
$this->assertTrue($package->exists());
20+
21+
$package = new PackageJson($this->fixturePath('package-json/missing.package.json'));
22+
$this->assertFalse($package->exists());
23+
}
24+
25+
public function testCreate(): void
26+
{
27+
$package = new PackageJson($this->fixturePath('package-json/created.package.json'));
28+
$package->create(['private' => true], false);
29+
30+
$this->assertEquals([
31+
'private' => true,
32+
], $package->get());
33+
34+
$this->assertFileEquals(
35+
$this->fixturePath('package-json/assertions/testCreate.package.json'),
36+
$this->fixturePath('package-json/created.package.json')
37+
);
38+
}
39+
40+
public function testCreateDefaults(): void
41+
{
42+
$package = new PackageJson($this->fixturePath('package-json/created.package.json'));
43+
$package->create();
44+
45+
$this->assertEquals([
46+
'private' => true,
47+
'dependencies' => [],
48+
'devDependencies' => [],
49+
'engines' => [
50+
'node' => '>= 14',
51+
'npm' => '>= 7',
52+
],
53+
], $package->get());
54+
55+
$this->assertFileEquals(
56+
$this->fixturePath('package-json/assertions/testCreateDefaults.package.json'),
57+
$this->fixturePath('package-json/created.package.json')
58+
);
59+
}
60+
}

0 commit comments

Comments
 (0)