@artilingo/artiframe-cli 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core-stubs/bin/SystemMethod.php +106 -10
- package/core-stubs/bin/ViewMethod.php +16 -0
- package/core-stubs/docs/de.html +4310 -4432
- package/core-stubs/docs/en.html +4309 -4432
- package/core-stubs/docs/es.html +4310 -4432
- package/core-stubs/docs/fr.html +4309 -4432
- package/core-stubs/docs/tr.html +4307 -4991
- package/package.json +1 -1
- package/src/App.php +48 -1
- package/src/Commands/AuthCommand.php +219 -0
- package/src/Commands/IssuesCommand.php +174 -0
- package/src/Commands/SuggestCommand.php +143 -0
- package/src/Commands/TableCommand.php +82 -0
- package/src/Lang/de.php +14 -0
- package/src/Lang/en.php +14 -0
- package/src/Lang/es.php +14 -0
- package/src/Lang/fr.php +14 -0
- package/src/Lang/tr.php +15 -1
- package/stubs/sql.stub +46 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artilingo/artiframe-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "ArtiFrame — Zero-dependency native PHP framework with a powerful multilingual CLI. Scaffold projects, generate views, APIs and classes instantly.",
|
|
5
5
|
"main": "bin/artiframe.js",
|
|
6
6
|
"bin": {
|
package/src/App.php
CHANGED
|
@@ -170,6 +170,17 @@ class App
|
|
|
170
170
|
|
|
171
171
|
private function routeCommand(string $commandName, array $commandArgs): void
|
|
172
172
|
{
|
|
173
|
+
if (strpos($commandName, 'table:') === 0) {
|
|
174
|
+
$tableName = substr($commandName, 6);
|
|
175
|
+
(new \ArtiFrame\Cli\Commands\TableCommand($this->translator))->execute(array_merge([$tableName], $commandArgs));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if ($commandName === 'table' && !empty($commandArgs) && $commandArgs[0] === 'list') {
|
|
180
|
+
(new \ArtiFrame\Cli\Commands\TableCommand($this->translator))->execute(['list']);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
switch ($commandName) {
|
|
174
185
|
|
|
175
186
|
|
|
@@ -219,6 +230,18 @@ class App
|
|
|
219
230
|
(new \ArtiFrame\Cli\Commands\ServeCommand($this->translator))->execute($commandArgs);
|
|
220
231
|
break;
|
|
221
232
|
|
|
233
|
+
case 'auth':
|
|
234
|
+
(new \ArtiFrame\Cli\Commands\AuthCommand($this->translator))->execute($commandArgs);
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case 'suggest':
|
|
238
|
+
(new \ArtiFrame\Cli\Commands\SuggestCommand($this->translator))->execute($commandArgs);
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'issues':
|
|
242
|
+
(new \ArtiFrame\Cli\Commands\IssuesCommand($this->translator))->execute($commandArgs);
|
|
243
|
+
break;
|
|
244
|
+
|
|
222
245
|
case 'add':
|
|
223
246
|
(new \ArtiFrame\Cli\Commands\AddCommand($this->translator))->execute($commandArgs);
|
|
224
247
|
break;
|
|
@@ -302,7 +325,13 @@ class App
|
|
|
302
325
|
// make:class
|
|
303
326
|
echo " " . $g . "make:class" . $r . " " . $y . "<path>" . $r . PHP_EOL;
|
|
304
327
|
echo " " . $d . "│" . $r . " " . $t->get('HELP_MAKECLASS_DESC') . PHP_EOL;
|
|
305
|
-
echo " " . $d . "
|
|
328
|
+
echo " " . $d . "└─ " . $r . "Example: " . $lg . "make:class app/Services/UserManager.php" . $r . PHP_EOL;
|
|
329
|
+
echo PHP_EOL;
|
|
330
|
+
|
|
331
|
+
// table
|
|
332
|
+
echo " " . $g . "table:<table>" . $r . PHP_EOL;
|
|
333
|
+
echo " " . $d . "│" . $r . " " . $t->get('HELP_TABLE_DESC') . PHP_EOL;
|
|
334
|
+
echo " " . $d . "└─ " . $r . "Example: " . $lg . "table:users" . $r . PHP_EOL;
|
|
306
335
|
echo PHP_EOL;
|
|
307
336
|
|
|
308
337
|
// cli v / cli version — CLI self-version
|
|
@@ -366,6 +395,24 @@ class App
|
|
|
366
395
|
echo " " . $d . "└── " . $r . $lg . "add list" . $r . $d . " " . $t->get('HELP_ADD_LIST') . $r . PHP_EOL;
|
|
367
396
|
echo PHP_EOL;
|
|
368
397
|
|
|
398
|
+
// auth
|
|
399
|
+
echo " " . $g . "auth" . $r . PHP_EOL;
|
|
400
|
+
echo " " . $d . "│" . $r . " " . $t->get('HELP_AUTH_DESC') . PHP_EOL;
|
|
401
|
+
echo " " . $d . "└── " . $r . "Example: " . $lg . "auth" . $r . PHP_EOL;
|
|
402
|
+
echo PHP_EOL;
|
|
403
|
+
|
|
404
|
+
// suggest
|
|
405
|
+
echo " " . $g . "suggest" . $r . PHP_EOL;
|
|
406
|
+
echo " " . $d . "│" . $r . " " . $t->get('HELP_SUGGEST_DESC') . PHP_EOL;
|
|
407
|
+
echo " " . $d . "└── " . $r . "Example: " . $lg . "suggest" . $r . PHP_EOL;
|
|
408
|
+
echo PHP_EOL;
|
|
409
|
+
|
|
410
|
+
// issues
|
|
411
|
+
echo " " . $g . "issues" . $r . " " . $y . "[id]" . $r . PHP_EOL;
|
|
412
|
+
echo " " . $d . "│" . $r . " " . $t->get('HELP_ISSUES_DESC') . PHP_EOL;
|
|
413
|
+
echo " " . $d . "└── " . $r . "Example: " . $lg . "issues" . $r . " or " . $lg . "issues 4" . $r . PHP_EOL;
|
|
414
|
+
echo PHP_EOL;
|
|
415
|
+
|
|
369
416
|
// help / exit
|
|
370
417
|
echo " " . $d . "─────────────────────────────────────────────────────────────" . $r . PHP_EOL;
|
|
371
418
|
echo " " . $g . "help" . $r . " " . $t->get('HELP_HELP_DESC') . PHP_EOL;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
|
|
6
|
+
class AuthCommand
|
|
7
|
+
{
|
|
8
|
+
private Translator $translator;
|
|
9
|
+
|
|
10
|
+
// Placeholder Client ID as requested
|
|
11
|
+
const CLIENT_ID = 'Ov23libWFmWDGtyaSEiT';
|
|
12
|
+
|
|
13
|
+
public function __construct(Translator $translator)
|
|
14
|
+
{
|
|
15
|
+
$this->translator = $translator;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public function execute(array $args): void
|
|
19
|
+
{
|
|
20
|
+
echo "\033[1;36mInitializing GitHub Authentication...\033[0m" . PHP_EOL;
|
|
21
|
+
|
|
22
|
+
// 1. Request Device Code
|
|
23
|
+
$deviceCodeData = $this->requestDeviceCode();
|
|
24
|
+
if (!$deviceCodeData) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$deviceCode = $deviceCodeData['device_code'];
|
|
29
|
+
$userCode = $deviceCodeData['user_code'];
|
|
30
|
+
$verificationUri = $deviceCodeData['verification_uri'];
|
|
31
|
+
$interval = (int)($deviceCodeData['interval'] ?? 5);
|
|
32
|
+
|
|
33
|
+
// 2. Display instructions
|
|
34
|
+
echo PHP_EOL;
|
|
35
|
+
echo "\033[1;33mAction Required:\033[0m" . PHP_EOL;
|
|
36
|
+
echo "1. Please open your browser to: \033[1;34m" . $verificationUri . "\033[0m" . PHP_EOL;
|
|
37
|
+
echo "2. Enter the following code to authorize: \033[1;32m" . $userCode . "\033[0m" . PHP_EOL;
|
|
38
|
+
echo PHP_EOL;
|
|
39
|
+
echo "Waiting for authorization..." . PHP_EOL;
|
|
40
|
+
|
|
41
|
+
// 3. Open browser automatically
|
|
42
|
+
$this->openBrowser($verificationUri);
|
|
43
|
+
|
|
44
|
+
// 4. Poll for Access Token
|
|
45
|
+
$accessToken = $this->pollForToken($deviceCode, $interval);
|
|
46
|
+
|
|
47
|
+
if ($accessToken) {
|
|
48
|
+
// 5. Fetch user profile
|
|
49
|
+
$userProfile = $this->fetchUserProfile($accessToken);
|
|
50
|
+
$username = $userProfile['login'] ?? 'Unknown User';
|
|
51
|
+
|
|
52
|
+
// 6. Save configuration
|
|
53
|
+
$this->saveConfig($accessToken, $username);
|
|
54
|
+
|
|
55
|
+
echo "\033[1;32m✔ Successfully authenticated as @{$username}!\033[0m" . PHP_EOL;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private function requestDeviceCode(): ?array
|
|
60
|
+
{
|
|
61
|
+
$ch = curl_init('https://github.com/login/device/code');
|
|
62
|
+
$postData = http_build_query([
|
|
63
|
+
'client_id' => self::CLIENT_ID,
|
|
64
|
+
'scope' => 'public_repo'
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
68
|
+
curl_setopt($ch, CURLOPT_POST, true);
|
|
69
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
|
70
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
71
|
+
'Accept: application/json'
|
|
72
|
+
]);
|
|
73
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
74
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
75
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
76
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
77
|
+
curl_setopt($ch, CURLOPT_USERAGENT, 'ArtiFrame-CLI');
|
|
78
|
+
|
|
79
|
+
$response = curl_exec($ch);
|
|
80
|
+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
81
|
+
|
|
82
|
+
if ($httpCode !== 200 || !$response) {
|
|
83
|
+
echo "\033[1;31m[-] Failed to request device code from GitHub.\033[0m" . PHP_EOL;
|
|
84
|
+
curl_close($ch);
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
curl_close($ch);
|
|
88
|
+
|
|
89
|
+
return json_decode($response, true);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private function pollForToken(string $deviceCode, int $interval): ?string
|
|
93
|
+
{
|
|
94
|
+
$url = 'https://github.com/login/oauth/access_token';
|
|
95
|
+
|
|
96
|
+
while (true) {
|
|
97
|
+
sleep($interval);
|
|
98
|
+
|
|
99
|
+
$ch = curl_init($url);
|
|
100
|
+
$postData = http_build_query([
|
|
101
|
+
'client_id' => self::CLIENT_ID,
|
|
102
|
+
'device_code' => $deviceCode,
|
|
103
|
+
'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code'
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
107
|
+
curl_setopt($ch, CURLOPT_POST, true);
|
|
108
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
|
109
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
110
|
+
'Accept: application/json'
|
|
111
|
+
]);
|
|
112
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
113
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
114
|
+
curl_setopt($ch, CURLOPT_USERAGENT, 'ArtiFrame-CLI');
|
|
115
|
+
|
|
116
|
+
$response = curl_exec($ch);
|
|
117
|
+
curl_close($ch);
|
|
118
|
+
|
|
119
|
+
if ($response) {
|
|
120
|
+
$data = json_decode($response, true);
|
|
121
|
+
|
|
122
|
+
if (isset($data['access_token'])) {
|
|
123
|
+
return $data['access_token'];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isset($data['error'])) {
|
|
127
|
+
if ($data['error'] === 'authorization_pending') {
|
|
128
|
+
// Keep waiting
|
|
129
|
+
continue;
|
|
130
|
+
} elseif ($data['error'] === 'slow_down') {
|
|
131
|
+
$interval += 5; // Slow down polling
|
|
132
|
+
continue;
|
|
133
|
+
} elseif ($data['error'] === 'expired_token') {
|
|
134
|
+
echo "\033[1;31m[-] The device code expired. Please run the auth command again.\033[0m" . PHP_EOL;
|
|
135
|
+
return null;
|
|
136
|
+
} elseif ($data['error'] === 'access_denied') {
|
|
137
|
+
echo "\033[1;31m[-] Authorization was denied.\033[0m" . PHP_EOL;
|
|
138
|
+
return null;
|
|
139
|
+
} else {
|
|
140
|
+
echo "\033[1;31m[-] An error occurred: " . $data['error'] . "\033[0m" . PHP_EOL;
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private function fetchUserProfile(string $token): ?array
|
|
149
|
+
{
|
|
150
|
+
$ch = curl_init('https://api.github.com/user');
|
|
151
|
+
|
|
152
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
153
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
154
|
+
'Accept: application/vnd.github.v3+json',
|
|
155
|
+
'Authorization: Bearer ' . $token,
|
|
156
|
+
'User-Agent: ArtiFrame-CLI'
|
|
157
|
+
]);
|
|
158
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
159
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
160
|
+
|
|
161
|
+
$response = curl_exec($ch);
|
|
162
|
+
curl_close($ch);
|
|
163
|
+
|
|
164
|
+
if ($response) {
|
|
165
|
+
return json_decode($response, true);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private function saveConfig(string $token, string $username): void
|
|
172
|
+
{
|
|
173
|
+
$home = getenv('HOME') ?: getenv('USERPROFILE');
|
|
174
|
+
$configDir = $home . \DIRECTORY_SEPARATOR . '.artiframe';
|
|
175
|
+
|
|
176
|
+
if (!is_dir($configDir)) {
|
|
177
|
+
mkdir($configDir, 0755, true);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$configFile = $configDir . \DIRECTORY_SEPARATOR . 'config.json';
|
|
181
|
+
|
|
182
|
+
$config = [];
|
|
183
|
+
if (file_exists($configFile)) {
|
|
184
|
+
$existing = json_decode(file_get_contents($configFile), true);
|
|
185
|
+
if (is_array($existing)) {
|
|
186
|
+
$config = $existing;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
$config['github_token'] = $token;
|
|
191
|
+
$config['github_user'] = $username;
|
|
192
|
+
$config['logged_in_at'] = date('c');
|
|
193
|
+
|
|
194
|
+
file_put_contents($configFile, json_encode($config, JSON_PRETTY_PRINT));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private function openBrowser(string $url): void
|
|
198
|
+
{
|
|
199
|
+
$os = php_uname('s');
|
|
200
|
+
$command = '';
|
|
201
|
+
|
|
202
|
+
if (stripos($os, 'win') === 0) {
|
|
203
|
+
$command = 'start "" "' . $url . '"';
|
|
204
|
+
} elseif (stripos($os, 'darwin') === 0) {
|
|
205
|
+
$command = 'open "' . $url . '"';
|
|
206
|
+
} else {
|
|
207
|
+
$command = 'xdg-open "' . $url . '"';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if ($command) {
|
|
211
|
+
// execute command silently in background
|
|
212
|
+
if (stripos($os, 'win') === 0) {
|
|
213
|
+
pclose(popen($command, "r"));
|
|
214
|
+
} else {
|
|
215
|
+
exec($command . ' > /dev/null 2>&1 &');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
|
|
6
|
+
class IssuesCommand
|
|
7
|
+
{
|
|
8
|
+
private Translator $translator;
|
|
9
|
+
const REPO = 'utkuthecoder/artiframe-cli';
|
|
10
|
+
|
|
11
|
+
public function __construct(Translator $translator)
|
|
12
|
+
{
|
|
13
|
+
$this->translator = $translator;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public function execute(array $args = []): void
|
|
17
|
+
{
|
|
18
|
+
$home = getenv('HOME') ?: getenv('USERPROFILE');
|
|
19
|
+
$configPath = $home . \DIRECTORY_SEPARATOR . '.artiframe' . \DIRECTORY_SEPARATOR . 'config.json';
|
|
20
|
+
|
|
21
|
+
if (!file_exists($configPath)) {
|
|
22
|
+
echo "\033[1;31m[-] " . $this->translator->get('SUGGEST_NOT_AUTH') . "\033[0m" . PHP_EOL;
|
|
23
|
+
echo "Run: \033[1;32martiframe auth\033[0m" . PHP_EOL;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$config = json_decode(file_get_contents($configPath), true);
|
|
28
|
+
$token = $config['github_token'] ?? null;
|
|
29
|
+
$username = $config['github_user'] ?? null;
|
|
30
|
+
|
|
31
|
+
if (!$token || !$username) {
|
|
32
|
+
echo "\033[1;31m[-] " . $this->translator->get('SUGGEST_NOT_AUTH') . "\033[0m" . PHP_EOL;
|
|
33
|
+
echo "Run: \033[1;32martiframe auth\033[0m" . PHP_EOL;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
$issueId = $args[0] ?? null;
|
|
38
|
+
|
|
39
|
+
if ($issueId) {
|
|
40
|
+
$this->showIssueDetails($token, $username, $issueId);
|
|
41
|
+
} else {
|
|
42
|
+
$this->listIssues($token, $username);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private function listIssues(string $token, string $username): void
|
|
47
|
+
{
|
|
48
|
+
echo PHP_EOL . "\033[1;36mFetching issues for @{$username}...\033[0m" . PHP_EOL;
|
|
49
|
+
|
|
50
|
+
$url = 'https://api.github.com/repos/' . self::REPO . '/issues?creator=' . urlencode($username) . '&state=all';
|
|
51
|
+
$response = $this->githubApiRequest($url, $token);
|
|
52
|
+
|
|
53
|
+
if (!$response) {
|
|
54
|
+
echo "\033[1;31m[-] Failed to fetch issues from GitHub.\033[0m" . PHP_EOL;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$issues = json_decode($response, true);
|
|
59
|
+
|
|
60
|
+
if (isset($issues['message'])) {
|
|
61
|
+
echo "\033[1;31m[-] GitHub Error: " . $issues['message'] . "\033[0m" . PHP_EOL;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (empty($issues)) {
|
|
66
|
+
echo "\033[1;33mYou haven't opened any issues yet.\033[0m" . PHP_EOL;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
echo "\033[1;37m" . str_pad("ID", 6) . str_pad("STATUS", 10) . str_pad("COMMENTS", 10) . "TITLE\033[0m" . PHP_EOL;
|
|
71
|
+
echo str_repeat("-", 80) . PHP_EOL;
|
|
72
|
+
|
|
73
|
+
foreach ($issues as $issue) {
|
|
74
|
+
if (isset($issue['pull_request'])) {
|
|
75
|
+
continue; // Skip PRs if they show up in issue search
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$id = "#" . $issue['number'];
|
|
79
|
+
$state = $issue['state']; // open or closed
|
|
80
|
+
$comments = $issue['comments'];
|
|
81
|
+
$title = strlen($issue['title']) > 50 ? substr($issue['title'], 0, 47) . '...' : $issue['title'];
|
|
82
|
+
|
|
83
|
+
$stateColor = $state === 'open' ? "\033[1;32m" : "\033[1;31m"; // Green for open, red for closed
|
|
84
|
+
$displayState = ucfirst($state);
|
|
85
|
+
|
|
86
|
+
echo str_pad($id, 6) .
|
|
87
|
+
$stateColor . str_pad($displayState, 10) . "\033[0m" .
|
|
88
|
+
str_pad((string)$comments, 10) .
|
|
89
|
+
$title . PHP_EOL;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
echo PHP_EOL . "\033[38;5;240mTip: To view comments for a specific issue, run `artiframe issues <ID>`\033[0m" . PHP_EOL;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private function showIssueDetails(string $token, string $username, string $issueId): void
|
|
96
|
+
{
|
|
97
|
+
$issueId = ltrim($issueId, '#');
|
|
98
|
+
echo PHP_EOL . "\033[1;36mFetching details for Issue #{$issueId}...\033[0m" . PHP_EOL;
|
|
99
|
+
|
|
100
|
+
// Fetch Issue Details
|
|
101
|
+
$url = 'https://api.github.com/repos/' . self::REPO . '/issues/' . $issueId;
|
|
102
|
+
$response = $this->githubApiRequest($url, $token);
|
|
103
|
+
|
|
104
|
+
if (!$response) {
|
|
105
|
+
echo "\033[1;31m[-] Failed to fetch issue details.\033[0m" . PHP_EOL;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
$issue = json_decode($response, true);
|
|
110
|
+
|
|
111
|
+
if (isset($issue['message'])) {
|
|
112
|
+
echo "\033[1;31m[-] GitHub Error: " . $issue['message'] . "\033[0m" . PHP_EOL;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
$stateColor = $issue['state'] === 'open' ? "\033[1;32m" : "\033[1;31m";
|
|
117
|
+
|
|
118
|
+
echo PHP_EOL;
|
|
119
|
+
echo "\033[1;37m[" . $issue['state'] . "] " . $issue['title'] . "\033[0m" . PHP_EOL;
|
|
120
|
+
echo "\033[38;5;240mOpened by @" . $issue['user']['login'] . " at " . date('Y-m-d H:i', strtotime($issue['created_at'])) . "\033[0m" . PHP_EOL;
|
|
121
|
+
echo str_repeat("=", 80) . PHP_EOL;
|
|
122
|
+
echo $this->wordWrapWithIndentation($issue['body']) . PHP_EOL;
|
|
123
|
+
echo str_repeat("=", 80) . PHP_EOL;
|
|
124
|
+
|
|
125
|
+
if ($issue['comments'] > 0) {
|
|
126
|
+
echo PHP_EOL . "\033[1;36mComments ({$issue['comments']}):\033[0m" . PHP_EOL;
|
|
127
|
+
|
|
128
|
+
$commentsUrl = $issue['comments_url'];
|
|
129
|
+
$commentsResponse = $this->githubApiRequest($commentsUrl, $token);
|
|
130
|
+
$comments = json_decode($commentsResponse, true);
|
|
131
|
+
|
|
132
|
+
if (is_array($comments)) {
|
|
133
|
+
foreach ($comments as $comment) {
|
|
134
|
+
echo PHP_EOL;
|
|
135
|
+
echo "\033[1;34m@" . $comment['user']['login'] . "\033[0m \033[38;5;240m(" . date('Y-m-d H:i', strtotime($comment['created_at'])) . "):\033[0m" . PHP_EOL;
|
|
136
|
+
echo str_repeat("-", 80) . PHP_EOL;
|
|
137
|
+
echo $this->wordWrapWithIndentation($comment['body']) . PHP_EOL;
|
|
138
|
+
echo str_repeat("-", 80) . PHP_EOL;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
echo PHP_EOL . "\033[38;5;240mNo comments on this issue yet.\033[0m" . PHP_EOL;
|
|
143
|
+
}
|
|
144
|
+
echo PHP_EOL;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private function githubApiRequest(string $url, string $token): ?string
|
|
148
|
+
{
|
|
149
|
+
$ch = curl_init($url);
|
|
150
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
151
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
152
|
+
'Accept: application/vnd.github.v3+json',
|
|
153
|
+
'Authorization: Bearer ' . $token,
|
|
154
|
+
'User-Agent: ArtiFrame-CLI'
|
|
155
|
+
]);
|
|
156
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
157
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
158
|
+
|
|
159
|
+
$response = curl_exec($ch);
|
|
160
|
+
curl_close($ch);
|
|
161
|
+
|
|
162
|
+
return $response ?: null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private function wordWrapWithIndentation(string $text, int $width = 80): string
|
|
166
|
+
{
|
|
167
|
+
$lines = explode("\n", $text);
|
|
168
|
+
$wrapped = [];
|
|
169
|
+
foreach ($lines as $line) {
|
|
170
|
+
$wrapped[] = wordwrap(trim($line), $width, "\n");
|
|
171
|
+
}
|
|
172
|
+
return implode("\n", $wrapped);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
namespace ArtiFrame\Cli\Commands;
|
|
3
|
+
|
|
4
|
+
use ArtiFrame\Cli\Services\Translator;
|
|
5
|
+
|
|
6
|
+
class SuggestCommand
|
|
7
|
+
{
|
|
8
|
+
private Translator $translator;
|
|
9
|
+
const API_ENDPOINT = 'https://api.artilingo.com/artiframe/v1/suggest.php';
|
|
10
|
+
|
|
11
|
+
public function __construct(Translator $translator)
|
|
12
|
+
{
|
|
13
|
+
$this->translator = $translator;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public function execute(array $args): void
|
|
17
|
+
{
|
|
18
|
+
$home = getenv('HOME') ?: getenv('USERPROFILE');
|
|
19
|
+
$configFile = $home . \DIRECTORY_SEPARATOR . '.artiframe' . \DIRECTORY_SEPARATOR . 'config.json';
|
|
20
|
+
|
|
21
|
+
if (!file_exists($configFile)) {
|
|
22
|
+
echo "\033[1;31m[-] Not authenticated. Please run 'artiframe auth' first.\033[0m" . PHP_EOL;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
$config = json_decode(file_get_contents($configFile), true);
|
|
27
|
+
if (!$config || !isset($config['github_token'])) {
|
|
28
|
+
echo "\033[1;31m[-] Not authenticated. Please run 'artiframe auth' first.\033[0m" . PHP_EOL;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
$token = $config['github_token'];
|
|
33
|
+
$username = $config['github_user'] ?? 'User';
|
|
34
|
+
|
|
35
|
+
echo "\033[1;36mHello @{$username}, welcome to ArtiFrame Issue Submitter!\033[0m" . PHP_EOL;
|
|
36
|
+
echo PHP_EOL;
|
|
37
|
+
|
|
38
|
+
// 1. Ask for Category
|
|
39
|
+
$categories = [
|
|
40
|
+
'1' => 'Feature Request',
|
|
41
|
+
'2' => 'Bug Report',
|
|
42
|
+
'3' => 'Helper Suggestion'
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
echo "Please select a Category:" . PHP_EOL;
|
|
46
|
+
echo " [1] Feature Request" . PHP_EOL;
|
|
47
|
+
echo " [2] Bug Report" . PHP_EOL;
|
|
48
|
+
echo " [3] Helper Suggestion" . PHP_EOL;
|
|
49
|
+
|
|
50
|
+
$categoryId = null;
|
|
51
|
+
while (true) {
|
|
52
|
+
echo "Select [1-3]: ";
|
|
53
|
+
$input = trim(fgets(STDIN));
|
|
54
|
+
if (isset($categories[$input])) {
|
|
55
|
+
$categoryId = $input;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
echo "\033[1;31mInvalid selection.\033[0m" . PHP_EOL;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
$category = $categories[$categoryId];
|
|
62
|
+
|
|
63
|
+
// 2. Ask for Title
|
|
64
|
+
echo PHP_EOL;
|
|
65
|
+
$title = '';
|
|
66
|
+
while (true) {
|
|
67
|
+
echo "Title (Short summary, max 120 chars): ";
|
|
68
|
+
$title = trim(fgets(STDIN));
|
|
69
|
+
if (strlen($title) === 0) {
|
|
70
|
+
echo "\033[1;31mTitle cannot be empty.\033[0m" . PHP_EOL;
|
|
71
|
+
} elseif (strlen($title) > 120) {
|
|
72
|
+
echo "\033[1;31mTitle is too long. Max 120 chars.\033[0m" . PHP_EOL;
|
|
73
|
+
} else {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 3. Ask for Description
|
|
79
|
+
echo PHP_EOL;
|
|
80
|
+
echo "Description (Detailed explanation or code sample):" . PHP_EOL;
|
|
81
|
+
echo "\033[1;33m(Type 'END' on a new line and press Enter to finish)\033[0m" . PHP_EOL;
|
|
82
|
+
|
|
83
|
+
$bodyLines = [];
|
|
84
|
+
while (true) {
|
|
85
|
+
$line = fgets(STDIN);
|
|
86
|
+
if ($line === false || trim(strtoupper($line)) === 'END') {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
$bodyLines[] = rtrim($line, "\r\n");
|
|
90
|
+
}
|
|
91
|
+
$body = implode(PHP_EOL, $bodyLines);
|
|
92
|
+
|
|
93
|
+
if (empty(trim($body))) {
|
|
94
|
+
echo "\033[1;31m[-] Description cannot be empty. Aborting.\033[0m" . PHP_EOL;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. Send Payload
|
|
99
|
+
echo PHP_EOL . "\033[1;36mSubmitting to ArtiFrame...\033[0m" . PHP_EOL;
|
|
100
|
+
|
|
101
|
+
$payload = json_encode([
|
|
102
|
+
'token' => $token,
|
|
103
|
+
'category' => $category,
|
|
104
|
+
'title' => $title,
|
|
105
|
+
'body' => $body
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
$ch = curl_init(self::API_ENDPOINT);
|
|
109
|
+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
110
|
+
curl_setopt($ch, CURLOPT_POST, true);
|
|
111
|
+
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
|
112
|
+
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
113
|
+
'Content-Type: application/json',
|
|
114
|
+
'Accept: application/json'
|
|
115
|
+
]);
|
|
116
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
117
|
+
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|
118
|
+
curl_setopt($ch, CURLOPT_USERAGENT, 'ArtiFrame-CLI');
|
|
119
|
+
|
|
120
|
+
$response = curl_exec($ch);
|
|
121
|
+
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
122
|
+
curl_close($ch);
|
|
123
|
+
|
|
124
|
+
if ($response) {
|
|
125
|
+
$data = json_decode($response, true);
|
|
126
|
+
if ($httpCode === 200 && isset($data['status']) && $data['status'] === 'success') {
|
|
127
|
+
echo "\033[1;32m🎉 Issue successfully created!\033[0m" . PHP_EOL;
|
|
128
|
+
echo "URL: " . ($data['issue_url'] ?? 'N/A') . PHP_EOL;
|
|
129
|
+
} else {
|
|
130
|
+
if ($data && isset($data['message'])) {
|
|
131
|
+
echo "\033[1;31m[-] Server Error: " . $data['message'] . "\033[0m" . PHP_EOL;
|
|
132
|
+
} else {
|
|
133
|
+
echo "\033[1;31m[-] Server Error: Unknown response format.\033[0m" . PHP_EOL;
|
|
134
|
+
echo "HTTP Code: " . $httpCode . PHP_EOL;
|
|
135
|
+
echo "Raw Response: " . substr($response, 0, 300) . PHP_EOL;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
echo "\033[1;31m[-] Failed to communicate with the server.\033[0m" . PHP_EOL;
|
|
140
|
+
echo "cURL Error: " . curl_error($ch) . PHP_EOL;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|