@artilingo/artiframe-cli 1.3.0 → 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/docs/de.html +4187 -4463
- package/core-stubs/docs/en.html +4186 -4463
- package/core-stubs/docs/es.html +4187 -4463
- package/core-stubs/docs/fr.html +4186 -4463
- package/core-stubs/docs/tr.html +4184 -5022
- package/package.json +1 -1
- package/src/App.php +30 -0
- package/src/Commands/AuthCommand.php +219 -0
- package/src/Commands/IssuesCommand.php +174 -0
- package/src/Commands/SuggestCommand.php +143 -0
- package/src/Lang/de.php +2 -0
- package/src/Lang/en.php +2 -0
- package/src/Lang/es.php +2 -0
- package/src/Lang/fr.php +2 -0
- package/src/Lang/tr.php +3 -1
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
|
@@ -230,6 +230,18 @@ class App
|
|
|
230
230
|
(new \ArtiFrame\Cli\Commands\ServeCommand($this->translator))->execute($commandArgs);
|
|
231
231
|
break;
|
|
232
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
|
+
|
|
233
245
|
case 'add':
|
|
234
246
|
(new \ArtiFrame\Cli\Commands\AddCommand($this->translator))->execute($commandArgs);
|
|
235
247
|
break;
|
|
@@ -383,6 +395,24 @@ class App
|
|
|
383
395
|
echo " " . $d . "└── " . $r . $lg . "add list" . $r . $d . " " . $t->get('HELP_ADD_LIST') . $r . PHP_EOL;
|
|
384
396
|
echo PHP_EOL;
|
|
385
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
|
+
|
|
386
416
|
// help / exit
|
|
387
417
|
echo " " . $d . "─────────────────────────────────────────────────────────────" . $r . PHP_EOL;
|
|
388
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
|
+
}
|
package/src/Lang/de.php
CHANGED
|
@@ -75,6 +75,8 @@ return [
|
|
|
75
75
|
'HELP_MAJOR_DOWN' => 'Letzte Major-Version zurücksetzen.',
|
|
76
76
|
'HELP_HELP_DESC' => 'Diese Hilfemeldung anzeigen.',
|
|
77
77
|
'HELP_EXIT_DESC' => 'Die interaktive Shell beenden.',
|
|
78
|
+
'HELP_AUTH_DESC' => 'Authentifizieren Sie sich bei GitHub, um Vorschläge einzureichen.',
|
|
79
|
+
'HELP_SUGGEST_DESC' => 'Reichen Sie einen Funktionswunsch, Fehlerbericht oder Vorschlag direkt bei GitHub ein.',
|
|
78
80
|
|
|
79
81
|
// LangCommand
|
|
80
82
|
'LANG_CURRENT' => 'Aktuelle Sprache:',
|
package/src/Lang/en.php
CHANGED
|
@@ -75,6 +75,8 @@ return [
|
|
|
75
75
|
'HELP_MAJOR_DOWN' => 'Roll back last major release.',
|
|
76
76
|
'HELP_HELP_DESC' => 'Show this help message.',
|
|
77
77
|
'HELP_EXIT_DESC' => 'Exit the interactive shell.',
|
|
78
|
+
'HELP_AUTH_DESC' => 'Authenticate with GitHub to enable issue suggestions.',
|
|
79
|
+
'HELP_SUGGEST_DESC' => 'Submit a feature request, bug report, or helper suggestion directly to GitHub.',
|
|
78
80
|
|
|
79
81
|
// LangCommand
|
|
80
82
|
'LANG_CURRENT' => 'Current language:',
|
package/src/Lang/es.php
CHANGED
|
@@ -75,6 +75,8 @@ return [
|
|
|
75
75
|
'HELP_MAJOR_DOWN' => 'Revertir la última versión mayor.',
|
|
76
76
|
'HELP_HELP_DESC' => 'Mostrar este mensaje de ayuda.',
|
|
77
77
|
'HELP_EXIT_DESC' => 'Salir del shell interactivo.',
|
|
78
|
+
'HELP_AUTH_DESC' => 'Autentifícate con GitHub para habilitar las sugerencias de issues.',
|
|
79
|
+
'HELP_SUGGEST_DESC' => 'Envía una solicitud de función, un informe de error o una sugerencia de ayuda directamente a GitHub.',
|
|
78
80
|
|
|
79
81
|
// LangCommand
|
|
80
82
|
'LANG_CURRENT' => 'Idioma actual:',
|
package/src/Lang/fr.php
CHANGED
|
@@ -75,6 +75,8 @@ return [
|
|
|
75
75
|
'HELP_MAJOR_DOWN' => 'Annuler la dernière version majeure.',
|
|
76
76
|
'HELP_HELP_DESC' => 'Afficher ce message d\'aide.',
|
|
77
77
|
'HELP_EXIT_DESC' => 'Quitter le shell interactif.',
|
|
78
|
+
'HELP_AUTH_DESC' => 'Authentifiez-vous avec GitHub pour permettre les suggestions.',
|
|
79
|
+
'HELP_SUGGEST_DESC' => 'Soumettez une demande de fonctionnalité, un rapport de bogue ou une suggestion d\'aide directement sur GitHub.',
|
|
78
80
|
|
|
79
81
|
// LangCommand
|
|
80
82
|
'LANG_CURRENT' => 'Langue actuelle :',
|
package/src/Lang/tr.php
CHANGED
|
@@ -74,7 +74,9 @@ return [
|
|
|
74
74
|
'HELP_MINOR_DOWN' => 'Son minör sürümün geri alınması.',
|
|
75
75
|
'HELP_MAJOR_DOWN' => 'Son majör sürümün geri alınması.',
|
|
76
76
|
'HELP_HELP_DESC' => 'Bu yardım mesajını gösterir.',
|
|
77
|
-
'HELP_EXIT_DESC' => '
|
|
77
|
+
'HELP_EXIT_DESC' => 'Etkileşimli kabuktan çık.',
|
|
78
|
+
'HELP_AUTH_DESC' => 'Issue önerileri için GitHub ile kimlik doğrulaması yapın.',
|
|
79
|
+
'HELP_SUGGEST_DESC' => 'GitHub\'a doğrudan özellik isteği, hata bildirimi veya öneri gönderin.',
|
|
78
80
|
|
|
79
81
|
// LangCommand
|
|
80
82
|
'LANG_CURRENT' => 'Mevcut dil:',
|