Repository navigation
Expand file tree
/
Copy pathProjectDetector.php
More file actions
142 lines (116 loc) · 4.71 KB
/
Copy pathProjectDetector.php
File metadata and controls
142 lines (116 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<?php
declare(strict_types=1);
namespace App\Launcher;
use function is_dir;
use function is_file;
use function dirname;
/**
* Decides whether a directory is a Portable project or a Composer project.
*
* This class is deliberately free of framework dependencies: it is required by
* explicit path from the console entry point and runs before any autoloader
* exists, which is what makes it safe to dispatch a Composer project into
* its own dependency graph without ever touching the embedded one.
*/
final class ProjectDetector
{
/**
* Files and directories that mark a directory as the root of a Hyde site.
*
* Their presence stops the upwards search: a directory that is itself a site
* root must never be attributed to an unrelated Composer project further up
* the tree (a portable site checked out inside a PHP repository, say).
*
* @var list<string>
*/
private const PORTABLE_MARKERS = ['_pages', '_posts', '_docs', '_media', 'hyde.yml', 'hyde.yaml'];
/** A hard stop for the upwards search, so a pathological path can never spin. */
private const MAX_DEPTH = 64;
/**
* Detect the project rooted at, or containing, the given directory.
*
* The search walks upwards from the given directory. For each directory:
*
* 1. A composer.json that actually declares Hyde makes it a Composer project root.
* 2. Otherwise, portable markers make it a Portable project root, and stop the search.
* 3. Otherwise, a composer.json is a boundary: it is not a Hyde project, so the
* search fails rather than walking into an enclosing project.
* 4. Otherwise, we continue with the parent directory.
*
* When nothing matches all the way up, the starting directory is a Portable project.
* Portable is the fallback only when no Composer manifest was encountered.
*
* @throws \App\Launcher\LauncherException If a composer.json is present but does not
* declare Hyde, or cannot be parsed.
*/
public function detect(string $directory): Project
{
return $this->detectProject($directory);
}
/**
* Detect a project for a command owned by the executable itself.
*
* Launcher-owned commands have always been usable from an unrelated Composer
* project. They boot the embedded application in isolation, so that project is
* represented as the same Portable context used by the previous fallback. A
* malformed manifest still fails: it is not safe to guess about one.
*/
public function detectForLauncherCommand(string $directory): Project
{
return $this->detectProject($directory, allowUnrelatedComposer: true);
}
private function detectProject(string $directory, bool $allowUnrelatedComposer = false): Project
{
$start = Project::canonicalize($directory);
$current = $start;
for ($depth = 0; $depth < self::MAX_DEPTH; $depth++) {
$manifest = $current.'/composer.json';
$hasManifest = is_file($manifest);
if ($hasManifest && $this->declaresHyde($current)) {
return Project::composer($current, $start);
}
if ($this->hasPortableMarkers($current)) {
return new Project(ProjectType::Portable, $current, $start);
}
if ($hasManifest) {
if ($allowUnrelatedComposer) {
return Project::portable($start);
}
throw new LauncherException(<<<TEXT
This is a Composer project, but it does not declare Hyde.
Found: $manifest
Run Hyde from a Hyde project, or use `hyde new` to create one.
TEXT);
}
$parent = Project::normalize(dirname($current));
if ($parent === $current) {
break; // We reached the filesystem root.
}
$current = $parent;
}
return Project::portable($start);
}
/**
* Does the given directory contain a composer.json that declares Hyde?
*
* @throws \App\Launcher\LauncherException If the manifest exists but cannot be parsed.
*/
public function declaresHyde(string $directory): bool
{
$manifest = $directory.'/composer.json';
if (! is_file($manifest)) {
return false;
}
return ComposerManifest::read($manifest)->declaresHyde();
}
private function hasPortableMarkers(string $directory): bool
{
foreach (self::PORTABLE_MARKERS as $marker) {
$path = $directory.'/'.$marker;
if (is_dir($path) || is_file($path)) {
return true;
}
}
return false;
}
}