create-nevela 0.1.2 → 0.1.4
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/README.md +12 -3
- package/index.mjs +194 -115
- package/package.json +41 -37
- package/packagist.mjs +17 -0
- package/project.mjs +126 -0
- package/selfupdate.mjs +94 -0
- package/shell.mjs +21 -0
- package/template/laravel/src/Console/DevCommand.php +104 -0
- package/template/laravel/src/Console/MakeResourceCommand.php +11 -3
- package/template/laravel/src/Console/SetupCommand.php +55 -0
- package/template/laravel/src/Console/StatusCommand.php +195 -0
- package/template/laravel/src/Console/UpdateCommand.php +180 -113
- package/template/laravel/src/Console/VersionCommand.php +20 -0
- package/template/laravel/src/Generator/ResourceGenerator.php +8 -8
- package/template/laravel/src/Nevela.php +161 -151
- package/template/laravel/src/NevelaServiceProvider.php +15 -1
- package/template/laravel/src/Support/DashboardState.php +118 -0
- package/template/laravel/src/Support/DashboardUpdate.php +57 -20
- package/template/laravel/src/Support/Releases.php +45 -0
- package/template/laravel/src/Support/TemplateSource.php +144 -0
- package/template/web/app/auth-actions.ts +12 -4
- package/template/web/package.json +1 -1
- package/template.mjs +127 -94
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
namespace Nevela\Laravel\Console;
|
|
4
4
|
|
|
5
5
|
use Illuminate\Console\Command;
|
|
6
|
-
use Illuminate\Support\Facades\Http;
|
|
7
6
|
use Nevela\Laravel\Nevela;
|
|
7
|
+
use Nevela\Laravel\Support\DashboardState;
|
|
8
8
|
use Nevela\Laravel\Support\DashboardUpdate;
|
|
9
|
-
use
|
|
9
|
+
use Nevela\Laravel\Support\Releases;
|
|
10
|
+
use Nevela\Laravel\Support\TemplateSource;
|
|
10
11
|
use RecursiveDirectoryIterator;
|
|
11
12
|
use RecursiveIteratorIterator;
|
|
12
13
|
use Symfony\Component\Process\PhpExecutableFinder;
|
|
@@ -17,7 +18,8 @@ final class UpdateCommand extends Command
|
|
|
17
18
|
{
|
|
18
19
|
protected $signature = 'nevela:update
|
|
19
20
|
{--check : Show what would change, and change nothing}
|
|
20
|
-
{--skip-package : Leave the nevela/laravel package as it is; update only the dashboard}
|
|
21
|
+
{--skip-package : Leave the nevela/laravel package as it is; update only the dashboard}
|
|
22
|
+
{--undo : Put the dashboard back as it was before the last update}';
|
|
21
23
|
|
|
22
24
|
protected $description = 'Update Nevela: the package, the generated code and the dashboard';
|
|
23
25
|
|
|
@@ -26,6 +28,9 @@ final class UpdateCommand extends Command
|
|
|
26
28
|
|
|
27
29
|
public function handle(): int
|
|
28
30
|
{
|
|
31
|
+
if ($this->option('undo')) {
|
|
32
|
+
return $this->undo();
|
|
33
|
+
}
|
|
29
34
|
$check = (bool) $this->option('check');
|
|
30
35
|
|
|
31
36
|
if (! $this->option('skip-package')) {
|
|
@@ -37,11 +42,32 @@ final class UpdateCommand extends Command
|
|
|
37
42
|
|
|
38
43
|
if (! $check) {
|
|
39
44
|
$this->components->task('Regenerating from the descriptors', fn () => $this->callSilently('nevela:generate') === self::SUCCESS);
|
|
45
|
+
$this->modernizeDevScript();
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
return $this->updateDashboard($check);
|
|
43
49
|
}
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Apps created before 0.1.3 run both servers with scripts/dev.mjs, which always uses
|
|
53
|
+
* port 8000 and so can end up talking to another program that already has it. Point
|
|
54
|
+
* their `dev` script at `php nevela dev`, which picks a free port, unless they changed it.
|
|
55
|
+
*/
|
|
56
|
+
private function modernizeDevScript(): void
|
|
57
|
+
{
|
|
58
|
+
$root = GenerateCommand::projectRoot();
|
|
59
|
+
$file = $root ? $root.DIRECTORY_SEPARATOR.'package.json' : null;
|
|
60
|
+
if (! $file || ! is_file($file)) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
$source = (string) file_get_contents($file);
|
|
64
|
+
$updated = preg_replace('/("dev"\s*:\s*)"node scripts\/dev\.mjs"/', '$1"php nevela dev"', $source, 1, $count);
|
|
65
|
+
if ($count === 1) {
|
|
66
|
+
file_put_contents($file, $updated);
|
|
67
|
+
$this->components->twoColumnDetail('package.json: dev', '<fg=blue>node scripts/dev.mjs → php nevela dev</>');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
45
71
|
/**
|
|
46
72
|
* Update the Composer package. Returns an exit code when the rest of the work was
|
|
47
73
|
* handed to a fresh process, or null to carry on in this one.
|
|
@@ -49,7 +75,7 @@ final class UpdateCommand extends Command
|
|
|
49
75
|
private function updatePackage(bool $check): ?int
|
|
50
76
|
{
|
|
51
77
|
$installed = Nevela::VERSION;
|
|
52
|
-
$latest =
|
|
78
|
+
$latest = Releases::latest();
|
|
53
79
|
|
|
54
80
|
if ($folder = $this->installedByPath()) {
|
|
55
81
|
$this->components->warn("nevela/laravel is installed from a folder ({$folder}), so Composer can't update it. The dashboard is checked against the version you have, {$installed}.");
|
|
@@ -105,58 +131,76 @@ final class UpdateCommand extends Command
|
|
|
105
131
|
|
|
106
132
|
private function updateDashboard(bool $check): int
|
|
107
133
|
{
|
|
108
|
-
$web =
|
|
109
|
-
if (
|
|
134
|
+
$web = $this->webPath();
|
|
135
|
+
if ($web === null) {
|
|
110
136
|
$this->components->info('No dashboard at nevela.web_path, so there is nothing more to update.');
|
|
111
137
|
|
|
112
138
|
return self::SUCCESS;
|
|
113
139
|
}
|
|
114
|
-
$
|
|
115
|
-
$
|
|
116
|
-
$from = is_file($marker) ? (json_decode((string) file_get_contents($marker), true)['template'] ?? self::FIRST_TRACKED) : self::FIRST_TRACKED;
|
|
140
|
+
$state = DashboardState::read($web);
|
|
141
|
+
$from = $state->template ?? self::FIRST_TRACKED;
|
|
117
142
|
// In --check the package hasn't been updated, so look at the newest dashboard there is.
|
|
118
|
-
$to = $check ? (
|
|
143
|
+
$to = $check ? (Releases::latest() ?? Nevela::VERSION) : Nevela::VERSION;
|
|
119
144
|
|
|
120
145
|
if (version_compare($from, $to, '>=')) {
|
|
121
146
|
$this->components->info("The dashboard is on the latest template ({$from}).");
|
|
147
|
+
// An app from before fingerprints were kept: record them now, so the next update
|
|
148
|
+
// knows what was changed without having to work it out from a download.
|
|
149
|
+
if (! $check && $state->files === null) {
|
|
150
|
+
try {
|
|
151
|
+
$state->adopt($from, TemplateSource::fetch($from)['files']);
|
|
152
|
+
$state->write($web);
|
|
153
|
+
} catch (Throwable) {
|
|
154
|
+
// Not worth failing an up-to-date app over; the next update can still download it.
|
|
155
|
+
}
|
|
156
|
+
}
|
|
122
157
|
|
|
123
158
|
return self::SUCCESS;
|
|
124
159
|
}
|
|
125
160
|
|
|
126
161
|
try {
|
|
127
|
-
$
|
|
128
|
-
|
|
162
|
+
$next = TemplateSource::fetch($to)['files'];
|
|
163
|
+
// What the app is on: its recorded fingerprints, or for an older app the old template itself.
|
|
164
|
+
if ($state->files === null) {
|
|
165
|
+
$state->adopt($from, TemplateSource::fetch($from)['files']);
|
|
166
|
+
}
|
|
129
167
|
} catch (Throwable $e) {
|
|
130
|
-
$this->components->error("Couldn't
|
|
168
|
+
$this->components->error("Couldn't get the dashboard template: {$e->getMessage()}. The dashboard was left as it is; run this again later.");
|
|
131
169
|
|
|
132
170
|
return self::FAILURE;
|
|
133
171
|
}
|
|
134
172
|
|
|
135
|
-
$
|
|
136
|
-
$
|
|
137
|
-
|
|
138
|
-
$yours[$path] = is_file("{$web}/{$path}") ? (string) file_get_contents("{$web}/{$path}") : null;
|
|
139
|
-
}
|
|
173
|
+
$base = $state->files;
|
|
174
|
+
$nextHashes = DashboardUpdate::hashes($next);
|
|
175
|
+
$yours = DashboardState::fingerprints($web, array_unique([...array_keys($base), ...array_keys($next)]));
|
|
140
176
|
|
|
141
177
|
// package.json is merged, not replaced: it has your app's name and your own dependencies.
|
|
142
178
|
$packages = null;
|
|
143
|
-
if (isset($
|
|
144
|
-
$packages = DashboardUpdate::mergePackageJson($
|
|
179
|
+
if (isset($next['package.json']) && is_file("{$web}/package.json")) {
|
|
180
|
+
$packages = DashboardUpdate::mergePackageJson((string) file_get_contents("{$web}/package.json"), $state->packageJson(), $next['package.json']);
|
|
145
181
|
}
|
|
146
|
-
unset($base['package.json'], $
|
|
182
|
+
unset($base['package.json'], $nextHashes['package.json'], $yours['package.json']);
|
|
147
183
|
|
|
148
|
-
$plan = DashboardUpdate::plan($base, $
|
|
184
|
+
$plan = DashboardUpdate::plan($base, $nextHashes, $yours);
|
|
149
185
|
$this->components->info(($check ? 'Dashboard: what would change, ' : 'Dashboard: ')."template {$from} → {$to}");
|
|
150
186
|
|
|
151
|
-
$
|
|
187
|
+
$done = [DashboardUpdate::UPDATE => [], DashboardUpdate::ADD => [], DashboardUpdate::CONFLICT => [], DashboardUpdate::REMOVED => []];
|
|
188
|
+
$backup = '.nevela/backups/'.gmdate('Ymd-His')."-{$from}-to-{$to}";
|
|
189
|
+
$incoming = ".nevela/incoming/{$to}";
|
|
190
|
+
|
|
152
191
|
foreach ($plan as $path => $action) {
|
|
153
|
-
$
|
|
154
|
-
if (! $check
|
|
155
|
-
$
|
|
156
|
-
|
|
157
|
-
|
|
192
|
+
$done[$action][] = $path;
|
|
193
|
+
if (! $check) {
|
|
194
|
+
if ($action === DashboardUpdate::UPDATE) {
|
|
195
|
+
// Untouched by you, so identical to the old template. Kept anyway: a copy costs nothing.
|
|
196
|
+
$this->put("{$web}/{$backup}/{$path}", (string) file_get_contents("{$web}/{$path}"));
|
|
197
|
+
$this->put("{$web}/{$path}", $next[$path]);
|
|
198
|
+
} elseif ($action === DashboardUpdate::ADD) {
|
|
199
|
+
$this->put("{$web}/{$path}", $next[$path]);
|
|
200
|
+
} elseif ($action === DashboardUpdate::CONFLICT) {
|
|
201
|
+
// Yours stays. The new version is put where you can compare the two.
|
|
202
|
+
$this->put("{$web}/{$incoming}/{$path}", $next[$path]);
|
|
158
203
|
}
|
|
159
|
-
file_put_contents($target, $next[$path]);
|
|
160
204
|
}
|
|
161
205
|
$this->components->twoColumnDetail($path, match ($action) {
|
|
162
206
|
DashboardUpdate::UPDATE => $check ? '<fg=blue>would update</>' : '<fg=blue>updated</>',
|
|
@@ -166,107 +210,137 @@ final class UpdateCommand extends Command
|
|
|
166
210
|
});
|
|
167
211
|
}
|
|
168
212
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
$this->components->twoColumnDetail("package.json: {$name}", '<fg=blue>'.$change.'</>');
|
|
172
|
-
}
|
|
173
|
-
if (! $check) {
|
|
174
|
-
file_put_contents("{$web}/package.json", $packages['json']);
|
|
175
|
-
}
|
|
213
|
+
foreach ($packages['changed'] ?? [] as $name => $change) {
|
|
214
|
+
$this->components->twoColumnDetail("package.json: {$name}", '<fg=blue>'.$change.'</>');
|
|
176
215
|
}
|
|
177
216
|
foreach ($packages['kept'] ?? [] as $name => $versions) {
|
|
178
217
|
$this->components->twoColumnDetail("package.json: {$name}", "<fg=yellow>kept yours, {$versions}</>");
|
|
179
218
|
}
|
|
180
|
-
|
|
181
219
|
if ($plan === [] && ! ($packages['changed'] ?? [])) {
|
|
182
220
|
$this->line(' Nothing in the dashboard needed changing.');
|
|
183
221
|
}
|
|
184
222
|
$this->newLine();
|
|
185
223
|
|
|
186
224
|
if (! $check) {
|
|
187
|
-
|
|
225
|
+
$touched = $done[DashboardUpdate::UPDATE] !== [] || $done[DashboardUpdate::ADD] !== [] || ($packages['changed'] ?? []) !== [];
|
|
226
|
+
if ($packages && $packages['changed']) {
|
|
227
|
+
$this->put("{$web}/{$backup}/package.json", (string) file_get_contents("{$web}/package.json"));
|
|
228
|
+
file_put_contents("{$web}/package.json", $packages['json']);
|
|
229
|
+
}
|
|
230
|
+
if ($touched) {
|
|
231
|
+
// The record as it was, so `--undo` can put that back too.
|
|
232
|
+
$this->put("{$web}/{$backup}/.nevela.json", (string) @file_get_contents(DashboardState::path($web)) ?: '{}');
|
|
233
|
+
}
|
|
234
|
+
// .nevela/ holds copies, not source: it keeps itself out of git.
|
|
235
|
+
if (is_dir("{$web}/.nevela") && ! is_file("{$web}/.nevela/.gitignore")) {
|
|
236
|
+
file_put_contents("{$web}/.nevela/.gitignore", "*\n");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
$state->adopt($to, $next);
|
|
240
|
+
$state->history[] = array_filter([
|
|
241
|
+
'at' => gmdate('c'),
|
|
242
|
+
'from' => $from,
|
|
243
|
+
'to' => $to,
|
|
244
|
+
'updated' => $done[DashboardUpdate::UPDATE],
|
|
245
|
+
'added' => $done[DashboardUpdate::ADD],
|
|
246
|
+
'kept' => $done[DashboardUpdate::CONFLICT],
|
|
247
|
+
'removed' => $done[DashboardUpdate::REMOVED],
|
|
248
|
+
'dependencies' => $packages['changed'] ?? [],
|
|
249
|
+
'backup' => $touched ? $backup : null,
|
|
250
|
+
]);
|
|
251
|
+
$state->write($web);
|
|
252
|
+
|
|
253
|
+
if ($touched) {
|
|
254
|
+
$this->line(" Every file this replaced was copied to <fg=cyan>{$this->relative($web)}/{$backup}</> first. To put it all back: <fg=cyan>php nevela update --undo</>");
|
|
255
|
+
}
|
|
188
256
|
}
|
|
189
|
-
if ($
|
|
190
|
-
$
|
|
257
|
+
if ($done[DashboardUpdate::CONFLICT] !== []) {
|
|
258
|
+
$count = count($done[DashboardUpdate::CONFLICT]);
|
|
259
|
+
$this->components->warn("{$count} file(s) changed in the template and in your app. Yours were kept.".($check ? '' : " The template's new versions are in {$this->relative($web)}/{$incoming} for you to compare."));
|
|
191
260
|
}
|
|
192
|
-
if ($
|
|
193
|
-
$this->line(
|
|
261
|
+
if ($done[DashboardUpdate::REMOVED] !== []) {
|
|
262
|
+
$this->line(' '.count($done[DashboardUpdate::REMOVED])." file(s) are no longer part of the template. They were left in place; delete them if you don't use them.");
|
|
194
263
|
}
|
|
195
264
|
if (! $check && ($packages['changed'] ?? [])) {
|
|
196
|
-
$this->line(' The dashboard\'s dependencies changed. Install them: <fg=cyan>cd '.$this->relative($web).' &&
|
|
265
|
+
$this->line(' The dashboard\'s dependencies changed. Install them: <fg=cyan>cd '.$this->relative($web).' && '.DevCommand::packageManager($web).' install</>');
|
|
197
266
|
}
|
|
198
267
|
|
|
199
268
|
return self::SUCCESS;
|
|
200
269
|
}
|
|
201
270
|
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
*
|
|
205
|
-
* It comes from the create-nevela package on npm, which carries the dashboard and is
|
|
206
|
-
* released with the same version number as this package.
|
|
207
|
-
*
|
|
208
|
-
* @return array<string, string>
|
|
209
|
-
*/
|
|
210
|
-
private function template(string $version): array
|
|
271
|
+
/** Put the dashboard back as it was before the last update that changed it. */
|
|
272
|
+
private function undo(): int
|
|
211
273
|
{
|
|
212
|
-
$
|
|
213
|
-
|
|
214
|
-
|
|
274
|
+
$web = $this->webPath();
|
|
275
|
+
if ($web === null) {
|
|
276
|
+
$this->components->error('No dashboard at nevela.web_path.');
|
|
215
277
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (! $response->successful()) {
|
|
225
|
-
throw new \RuntimeException("create-nevela {$version} is not on npm (HTTP {$response->status()})");
|
|
226
|
-
}
|
|
227
|
-
file_put_contents($archive, $response->body());
|
|
278
|
+
return self::FAILURE;
|
|
279
|
+
}
|
|
280
|
+
$state = DashboardState::read($web);
|
|
281
|
+
$last = null;
|
|
282
|
+
foreach (array_reverse($state->history) as $entry) {
|
|
283
|
+
if (isset($entry['backup']) && is_dir("{$web}/{$entry['backup']}")) {
|
|
284
|
+
$last = $entry;
|
|
285
|
+
break;
|
|
228
286
|
}
|
|
229
|
-
|
|
287
|
+
}
|
|
288
|
+
if ($last === null) {
|
|
289
|
+
$this->components->info('There is no update to undo: no backup was found.');
|
|
230
290
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
291
|
+
return self::SUCCESS;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
$dir = "{$web}/{$last['backup']}";
|
|
295
|
+
$restored = 0;
|
|
296
|
+
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)) as $file) {
|
|
297
|
+
$path = str_replace('\\', '/', substr($file->getPathname(), strlen($dir) + 1));
|
|
298
|
+
if ($path === '.nevela.json') {
|
|
299
|
+
continue;
|
|
234
300
|
}
|
|
235
|
-
$
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
301
|
+
$this->put("{$web}/{$path}", (string) file_get_contents($file->getPathname()));
|
|
302
|
+
$this->components->twoColumnDetail($path, '<fg=blue>restored</>');
|
|
303
|
+
$restored++;
|
|
304
|
+
}
|
|
305
|
+
// Files the update added are taken out again, unless you have changed them since.
|
|
306
|
+
foreach ($last['added'] ?? [] as $path) {
|
|
307
|
+
$recorded = $state->files[$path] ?? null;
|
|
308
|
+
if (is_file("{$web}/{$path}") && $recorded !== null && DashboardUpdate::hash((string) file_get_contents("{$web}/{$path}")) === $recorded) {
|
|
309
|
+
unlink("{$web}/{$path}");
|
|
310
|
+
$this->components->twoColumnDetail($path, '<fg=gray>removed (the update had added it)</>');
|
|
311
|
+
} elseif (is_file("{$web}/{$path}")) {
|
|
312
|
+
$this->components->twoColumnDetail($path, '<fg=yellow>kept: you changed it after the update</>');
|
|
241
313
|
}
|
|
242
|
-
|
|
243
|
-
return $files;
|
|
244
|
-
} finally {
|
|
245
|
-
$this->removeDirectory($dir);
|
|
246
314
|
}
|
|
315
|
+
// The record goes back too, so the same update can be run again later.
|
|
316
|
+
if (is_file("{$dir}/.nevela.json")) {
|
|
317
|
+
copy("{$dir}/.nevela.json", DashboardState::path($web));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
$this->newLine();
|
|
321
|
+
$this->components->info("Put back {$restored} file(s). The dashboard is as it was before the update from {$last['from']} to {$last['to']}.");
|
|
322
|
+
$this->line(' The nevela/laravel package was not changed by this. To go back a version there too: <fg=cyan>composer require nevela/laravel:'.$last['from'].'</>');
|
|
323
|
+
|
|
324
|
+
return self::SUCCESS;
|
|
247
325
|
}
|
|
248
326
|
|
|
249
|
-
/** The
|
|
250
|
-
private function
|
|
327
|
+
/** The dashboard's folder, with forward slashes, or null when there isn't one. */
|
|
328
|
+
private function webPath(): ?string
|
|
251
329
|
{
|
|
252
|
-
|
|
253
|
-
if ($
|
|
254
|
-
return
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
$releases = Http::timeout(10)->get('https://repo.packagist.org/p2/nevela/laravel.json')->json('packages.nevela/laravel') ?? [];
|
|
258
|
-
} catch (Throwable) {
|
|
259
|
-
return $latest = null;
|
|
260
|
-
}
|
|
261
|
-
$versions = [];
|
|
262
|
-
foreach ($releases as $release) {
|
|
263
|
-
if (preg_match('/^v?(\d+\.\d+\.\d+)$/', (string) ($release['version'] ?? ''), $m)) {
|
|
264
|
-
$versions[] = $m[1];
|
|
265
|
-
}
|
|
330
|
+
$web = config('nevela.web_path');
|
|
331
|
+
if (! $web || ! is_dir($web)) {
|
|
332
|
+
return null;
|
|
266
333
|
}
|
|
267
|
-
usort($versions, 'version_compare');
|
|
268
334
|
|
|
269
|
-
return
|
|
335
|
+
return rtrim(str_replace('\\', '/', realpath($web) ?: $web), '/');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private function put(string $file, string $contents): void
|
|
339
|
+
{
|
|
340
|
+
if (! is_dir(dirname($file))) {
|
|
341
|
+
mkdir(dirname($file), 0775, true);
|
|
342
|
+
}
|
|
343
|
+
file_put_contents($file, $contents);
|
|
270
344
|
}
|
|
271
345
|
|
|
272
346
|
/** The folder Composer installs the package from, when it isn't coming from Packagist. */
|
|
@@ -282,23 +356,16 @@ final class UpdateCommand extends Command
|
|
|
282
356
|
return null;
|
|
283
357
|
}
|
|
284
358
|
|
|
359
|
+
/** A path as someone at the top of the project would type it: apps/web/… */
|
|
285
360
|
private function relative(string $path): string
|
|
286
361
|
{
|
|
287
|
-
$
|
|
288
|
-
$
|
|
362
|
+
$root = GenerateCommand::projectRoot();
|
|
363
|
+
$root = $root === null ? null : rtrim(str_replace('\\', '/', $root), '/');
|
|
364
|
+
if ($root !== null && str_starts_with($path, $root.'/')) {
|
|
365
|
+
return substr($path, strlen($root) + 1);
|
|
366
|
+
}
|
|
367
|
+
$parent = dirname(rtrim(str_replace('\\', '/', base_path()), '/'));
|
|
289
368
|
|
|
290
369
|
return str_starts_with($path, $parent.'/') ? '../'.substr($path, strlen($parent) + 1) : $path;
|
|
291
370
|
}
|
|
292
|
-
|
|
293
|
-
private function removeDirectory(string $dir): void
|
|
294
|
-
{
|
|
295
|
-
if (! is_dir($dir)) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
$items = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST);
|
|
299
|
-
foreach ($items as $item) {
|
|
300
|
-
$item->isDir() ? @rmdir($item->getPathname()) : @unlink($item->getPathname());
|
|
301
|
-
}
|
|
302
|
-
@rmdir($dir);
|
|
303
|
-
}
|
|
304
371
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Nevela\Laravel\Console;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Console\Command;
|
|
6
|
+
use Nevela\Laravel\Nevela;
|
|
7
|
+
|
|
8
|
+
final class VersionCommand extends Command
|
|
9
|
+
{
|
|
10
|
+
protected $signature = 'nevela:version';
|
|
11
|
+
|
|
12
|
+
protected $description = 'Show which version of Nevela this app is on';
|
|
13
|
+
|
|
14
|
+
public function handle(): int
|
|
15
|
+
{
|
|
16
|
+
$this->line('Nevela '.Nevela::VERSION);
|
|
17
|
+
|
|
18
|
+
return self::SUCCESS;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -172,13 +172,10 @@ final class ResourceGenerator
|
|
|
172
172
|
\$name = array_shift(\$args) ?? 'help';
|
|
173
173
|
|
|
174
174
|
// What you type => the artisan command it runs.
|
|
175
|
-
\$nevela = ['resource', 'generate', 'seed', 'user', 'update'];
|
|
175
|
+
\$nevela = ['resource', 'generate', 'seed', 'user', 'update', 'dev', 'status', 'version'];
|
|
176
176
|
\$artisan = ['migrate', 'tinker', 'test', 'serve'];
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// The API and the dashboard together.
|
|
180
|
-
passthru('node '.escapeshellarg(__DIR__.'/scripts/dev.mjs'), \$status);
|
|
181
|
-
exit(\$status);
|
|
177
|
+
if (in_array(\$name, ['--version', '-v', '-V'], true)) {
|
|
178
|
+
\$name = 'version';
|
|
182
179
|
}
|
|
183
180
|
|
|
184
181
|
if (in_array(\$name, \$nevela, true)) {
|
|
@@ -197,12 +194,15 @@ final class ResourceGenerator
|
|
|
197
194
|
|
|
198
195
|
Nevela, from the top of your project.
|
|
199
196
|
|
|
200
|
-
php nevela
|
|
197
|
+
php nevela dev run the API and the dashboard
|
|
198
|
+
php nevela status check versions, migrations, users and the dashboard
|
|
199
|
+
php nevela resource Product --fields="name:string, price:money" --seed
|
|
200
|
+
add a resource, create its table, fill it
|
|
201
201
|
php nevela generate regenerate after editing a descriptor
|
|
202
202
|
php nevela seed Product fill a resource with records
|
|
203
203
|
php nevela user create someone who can sign in
|
|
204
204
|
php nevela update update Nevela and the dashboard
|
|
205
|
-
php nevela
|
|
205
|
+
php nevela version which Nevela this is
|
|
206
206
|
|
|
207
207
|
php nevela migrate php artisan migrate
|
|
208
208
|
php nevela artisan <command> any other artisan command
|