Skip to content

Commit a726150

Browse files
committed
[#2162] Updated --uri format of the installer.
1 parent 10bcc63 commit a726150

7 files changed

Lines changed: 473 additions & 184 deletions

File tree

.vortex/installer/src/Command/InstallCommand.php

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,26 @@ protected function configure(): void {
9292
$this->setName('install');
9393
$this->setDescription('Install Vortex from remote or local repository.');
9494
$this->setHelp(<<<EOF
95-
Interactively install Vortex from the latest stable release into the current directory:
96-
php installer --destination=.
97-
98-
Non-interactively install Vortex from the latest stable release into the specified directory:
99-
php installer --no-interaction --destination=path/to/destination
100-
101-
Install Vortex from the stable branch into the specified directory:
102-
php installer --uri=https://github.com/drevops/vortex.git@stable --destination=path/to/destination
103-
104-
Install Vortex from a specific release into the specified directory:
105-
php installer --uri=https://github.com/drevops/vortex.git@1.2.3 --destination=path/to/destination
106-
107-
Install Vortex from a specific commit into the specified directory:
108-
php installer --uri=https://github.com/drevops/vortex.git@abcd123 --destination=path/to/destination
95+
<info>Interactively install Vortex from the latest stable release into the current directory:</info>
96+
php installer.php --destination=.
97+
98+
<info>Non-interactively install Vortex from the latest stable release into the specified directory:</info>
99+
php installer.php --no-interaction --destination=path/to/destination
100+
101+
<info>Install from the latest auto-discovered stable release (default behavior if --uri is specified):</info>
102+
php installer.php --uri=https://github.com/drevops/vortex.git
103+
php installer.php --uri=https://github.com/drevops/vortex.git#stable
104+
105+
<info>Install using repository URL with specific git ref after #:</info>
106+
php installer.php --uri=https://github.com/drevops/vortex.git#25.11.0
107+
php installer.php --uri=https://github.com/drevops/vortex.git#v1.2.3
108+
php installer.php --uri=https://github.com/drevops/vortex.git#main
109+
110+
<info>Copy GitHub URL directly from your browser:</info>
111+
php installer.php --uri=https://github.com/drevops/vortex/releases/tag/25.11.0
112+
php installer.php --uri=https://github.com/drevops/vortex/tree/1.2.3
113+
php installer.php --uri=https://github.com/drevops/vortex/tree/main
114+
php installer.php --uri=https://github.com/drevops/vortex/commit/abcd123
109115
EOF
110116
);
111117
$this->addOption(static::OPTION_DESTINATION, NULL, InputOption::VALUE_REQUIRED, 'Destination directory. Defaults to the current directory.');
@@ -157,7 +163,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
157163
$this->config->set(Config::VERSION, $version);
158164
return $version;
159165
},
160-
hint: fn(): string => sprintf('Downloading from "%s" repository at commit "%s"', ...RepositoryDownloader::parseUri($this->config->get(Config::REPO))),
166+
hint: fn(): string => sprintf('Downloading from "%s" repository at ref "%s"', $this->config->get(Config::REPO), $this->config->get(Config::REF)),
161167
success: fn(string $return): string => sprintf('Vortex downloaded (%s)', $return)
162168
);
163169

.vortex/installer/src/Downloader/RepositoryDownloader.php

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,42 +60,101 @@ public function download(string $repo, string $ref, ?string $dst = NULL): string
6060
}
6161

6262
public static function parseUri(string $src): array {
63+
// Try GitHub-specific patterns first (Phase 2).
64+
$github_pattern = static::detectGitHubUrlPattern($src);
65+
if ($github_pattern !== NULL) {
66+
[$repo, $ref] = $github_pattern;
67+
68+
// Validate the extracted ref.
69+
if (!Validator::gitRef($ref)) {
70+
throw new \RuntimeException(sprintf('Invalid git reference: "%s". Reference must be a valid git tag, branch, or commit hash.', $ref));
71+
}
72+
73+
return [$repo, $ref];
74+
}
75+
76+
// Fall back to #ref parsing (standard git reference syntax).
6377
if (str_starts_with($src, 'https://')) {
64-
if (!preg_match('#^(https://[^/]+/[^/]+/[^@]+)(?:@(.+))?$#', $src, $matches)) {
65-
throw new \RuntimeException(sprintf('Invalid remote repository format: "%s".', $src));
78+
if (!preg_match('~^(https://[^/]+/[^/]+/[^#]+)(?:#(.+))?$~', $src, $matches)) {
79+
throw new \RuntimeException(sprintf('Invalid remote repository format: "%s". Use # to specify a reference (e.g., repo.git#tag).', $src));
6680
}
6781
$repo = $matches[1];
6882
$ref = $matches[2] ?? static::REF_HEAD;
6983
}
7084
elseif (str_starts_with($src, 'git@')) {
71-
if (!preg_match('#^(git@[^:]+:[^/]+/[^@]+)(?:@(.+))?$#', $src, $matches)) {
72-
throw new \RuntimeException(sprintf('Invalid remote repository format: "%s".', $src));
85+
if (!preg_match('~^(git@[^:]+:[^#]+)(?:#(.+))?$~', $src, $matches)) {
86+
throw new \RuntimeException(sprintf('Invalid remote repository format: "%s". Use # to specify a reference (e.g., git@host:repo#tag).', $src));
7387
}
7488
$repo = $matches[1];
7589
$ref = $matches[2] ?? static::REF_HEAD;
7690
}
7791
elseif (str_starts_with($src, 'file://')) {
78-
if (!preg_match('#^file://(.+?)(?:@(.+))?$#', $src, $matches)) {
79-
throw new \RuntimeException(sprintf('Invalid local repository format: "%s".', $src));
92+
if (!preg_match('~^file://(.+?)(?:#(.+))?$~', $src, $matches)) {
93+
throw new \RuntimeException(sprintf('Invalid local repository format: "%s". Use # to specify a reference.', $src));
8094
}
8195
$repo = $matches[1];
8296
$ref = $matches[2] ?? static::REF_HEAD;
8397
}
8498
else {
85-
if (!preg_match('#^(.+?)(?:@(.+))?$#', $src, $matches)) {
86-
throw new \RuntimeException(sprintf('Invalid local repository format: "%s".', $src));
99+
if (!preg_match('~^(.+?)(?:#(.+))?$~', $src, $matches)) {
100+
throw new \RuntimeException(sprintf('Invalid local repository format: "%s". Use # to specify a reference.', $src));
87101
}
88102
$repo = rtrim($matches[1], '/');
89103
$ref = $matches[2] ?? static::REF_HEAD;
90104
}
91105

92-
if ($ref != static::REF_STABLE && $ref != static::REF_HEAD && !Validator::gitCommitSha($ref) && !Validator::gitCommitShaShort($ref)) {
93-
throw new \RuntimeException(sprintf('Invalid reference format: "%s". Supported formats are: %s, %s, %s, %s.', $ref, static::REF_STABLE, static::REF_HEAD, '40-character commit hash', '7-character commit hash'));
106+
if (!Validator::gitRef($ref)) {
107+
throw new \RuntimeException(sprintf('Invalid git reference: "%s". Reference must be a valid git tag, branch, or commit hash.', $ref));
94108
}
95109

96110
return [$repo, $ref];
97111
}
98112

113+
/**
114+
* Detect and parse GitHub-specific URL patterns.
115+
*
116+
* Supports direct GitHub URLs copied from browser and alternative # syntax.
117+
*
118+
* @param string $uri
119+
* The input URI.
120+
*
121+
* @return array{0: string, 1: string}|null
122+
* Array of [repo_url, ref] if GitHub pattern detected, NULL otherwise.
123+
*/
124+
protected static function detectGitHubUrlPattern(string $uri): ?array {
125+
// Pattern 1: /releases/tag/{ref}
126+
// Example: https://github.com/drevops/vortex/releases/tag/25.11.0
127+
if (preg_match('#^(https://github\.com/[^/]+/[^/]+)/releases/tag/(.+)$#', $uri, $matches)) {
128+
return [$matches[1], $matches[2]];
129+
}
130+
131+
// Pattern 2: /tree/{ref}
132+
// Example: https://github.com/drevops/vortex/tree/1.2.3
133+
if (preg_match('#^(https://github\.com/[^/]+/[^/]+)/tree/(.+)$#', $uri, $matches)) {
134+
return [$matches[1], $matches[2]];
135+
}
136+
137+
// Pattern 3: /commit/{ref}
138+
// Example: https://github.com/drevops/vortex/commit/abcd123
139+
if (preg_match('#^(https://github\.com/[^/]+/[^/]+)/commit/(.+)$#', $uri, $matches)) {
140+
return [$matches[1], $matches[2]];
141+
}
142+
143+
// Pattern 4: .git#{ref} (HTTPS) - alternative to @ syntax
144+
// Example: https://github.com/drevops/vortex.git#25.11.0
145+
if (preg_match('~^(https://[^#]+\.git)#(.+)$~', $uri, $matches)) {
146+
return [$matches[1], $matches[2]];
147+
}
148+
149+
// Pattern 5: git@...#{ref} (SSH) - alternative to @ syntax
150+
// Example: git@github.com:drevops/vortex#stable
151+
if (preg_match('~^(git@[^#]+)#(.+)$~', $uri, $matches)) {
152+
return [$matches[1], $matches[2]];
153+
}
154+
155+
return NULL;
156+
}
157+
99158
protected function downloadFromRemote(string $repo, string $ref, ?string $destination): string {
100159
if ($destination === NULL) {
101160
throw new \InvalidArgumentException('Destination cannot be null for remote downloads.');

.vortex/installer/src/Utils/Validator.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,62 @@ public static function gitCommitShaShort(string $value): bool {
4141
return (bool) preg_match('/^[0-9a-f]{7}$/i', $value);
4242
}
4343

44+
/**
45+
* Validate a git reference (tag, branch, or commit).
46+
*
47+
* Accepts any valid git reference format including:
48+
* - Special keywords: "stable", "HEAD"
49+
* - Commit hashes: 40-character or 7-character SHA-1 hashes
50+
* - Version tags: "1.2.3", "v1.2.3", "25.11.0", "1.0.0-2025.11.0"
51+
* - Drupal-style tags: "8.x-1.10", "9.x-2.3"
52+
* - Pre-release tags: "1.x-rc1", "2.0.0-beta"
53+
* - Branch names: "main", "develop", "feature/my-feature"
54+
*
55+
* Follows git reference naming rules:
56+
* - Can contain alphanumeric, dot, hyphen, underscore, slash
57+
* - Cannot start with dot or hyphen
58+
* - Cannot contain: @, ^, ~, :, ?, *, [, space, \, @{
59+
* - Cannot end with .lock or contain ..
60+
*
61+
* @see https://git-scm.com/docs/git-check-ref-format
62+
*
63+
* @param string $value
64+
* The reference string to validate.
65+
*
66+
* @return bool
67+
* TRUE if valid, FALSE otherwise.
68+
*/
69+
public static function gitRef(string $value): bool {
70+
// Reserved keywords have special meaning.
71+
if (in_array($value, ['stable', 'HEAD'], TRUE)) {
72+
return TRUE;
73+
}
74+
75+
// Already supported: commit hashes.
76+
if (self::gitCommitSha($value) || self::gitCommitShaShort($value)) {
77+
return TRUE;
78+
}
79+
80+
// Git ref naming rules (simplified):
81+
// - Can contain alphanumeric, dot, hyphen, underscore, slash, plus.
82+
// - Cannot start with dot or hyphen.
83+
// - Cannot contain .. or end with .lock.
84+
$pattern = '/^(?![.\-])(?!.*\.\.)[a-zA-Z0-9._\/+-]+(?<!\.lock)$/';
85+
86+
if (!preg_match($pattern, $value)) {
87+
return FALSE;
88+
}
89+
90+
// Additional disallowed patterns.
91+
$disallowed = ['@', '^', '~', ':', '?', '*', '[', ' ', '\\', '@{'];
92+
foreach ($disallowed as $char) {
93+
if (str_contains($value, $char)) {
94+
return FALSE;
95+
}
96+
}
97+
98+
return TRUE;
99+
}
100+
44101
}
102+

0 commit comments

Comments
 (0)