create-nevela 0.1.0 → 0.1.2

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.
@@ -17,7 +17,7 @@ use Nevela\Laravel\Support\Naming;
17
17
  final class Nevela
18
18
  {
19
19
  /** Written by `pnpm release`; the same number as the repository's git tag. */
20
- public const VERSION = '0.1.0';
20
+ public const VERSION = '0.1.2';
21
21
 
22
22
  /** @var array<string, Descriptor> */
23
23
  private static array $descriptors = [];
@@ -1,65 +1,66 @@
1
- <?php
2
-
3
- namespace Nevela\Laravel;
4
-
5
- use Illuminate\Contracts\Debug\ExceptionHandler;
6
- use Illuminate\Support\Facades\Route;
7
- use Illuminate\Support\ServiceProvider;
8
- use Nevela\Laravel\Console\GenerateCommand;
9
- use Nevela\Laravel\Console\MakeResourceCommand;
10
- use Nevela\Laravel\Console\SeedCommand;
11
- use Nevela\Laravel\Console\UserCommand;
12
- use Nevela\Laravel\Http\FlareErrors;
13
- use Nevela\Laravel\Http\TokenController;
14
-
15
- final class NevelaServiceProvider extends ServiceProvider
16
- {
17
- public function register(): void
18
- {
19
- $this->mergeConfigFrom(__DIR__.'/../config/nevela.php', 'nevela');
20
- }
21
-
22
- public function boot(): void
23
- {
24
- if ($this->app->runningInConsole()) {
25
- $this->publishes([__DIR__.'/../config/nevela.php' => config_path('nevela.php')], 'nevela-config');
26
- $this->commands([MakeResourceCommand::class, GenerateCommand::class, SeedCommand::class, UserCommand::class]);
27
- }
28
-
29
- $this->callAfterResolving(ExceptionHandler::class, function ($handler) {
30
- if (method_exists($handler, 'renderable')) {
31
- $handler->renderable(fn (\Throwable $e, $request) => FlareErrors::render($e, $request));
32
- }
33
- });
34
-
35
- if (! $this->app->routesAreCached()) {
36
- $this->registerRoutes();
37
- }
38
- }
39
-
40
- private function registerRoutes(): void
41
- {
42
- $prefix = config('nevela.prefix', 'api');
43
-
44
- if (config('nevela.auth.enabled', true)) {
45
- Route::prefix($prefix)->middleware('api')->name('nevela.auth.')->group(function () {
46
- Route::post('auth/token', [TokenController::class, 'store'])->middleware('throttle:6,1')->name('store');
47
- Route::middleware('auth:sanctum')->group(function () {
48
- Route::get('auth/me', [TokenController::class, 'show'])->name('show');
49
- Route::delete('auth/token', [TokenController::class, 'destroy'])->name('destroy');
50
- });
51
- });
52
- }
53
-
54
- Route::prefix($prefix)->middleware(config('nevela.middleware', ['api', 'auth:sanctum']))->name('nevela.')->group(function () {
55
- // The descriptors, so the web app (and tooling) can check it matches the API.
56
- Route::get('_nevela/resources', fn () => response()->json([
57
- 'data' => array_map(fn ($d) => $d->toArray(), Nevela::all()),
58
- ]))->name('resources');
59
-
60
- if (is_file($routes = base_path('routes/nevela.php'))) {
61
- require $routes;
62
- }
63
- });
64
- }
65
- }
1
+ <?php
2
+
3
+ namespace Nevela\Laravel;
4
+
5
+ use Illuminate\Contracts\Debug\ExceptionHandler;
6
+ use Illuminate\Support\Facades\Route;
7
+ use Illuminate\Support\ServiceProvider;
8
+ use Nevela\Laravel\Console\GenerateCommand;
9
+ use Nevela\Laravel\Console\MakeResourceCommand;
10
+ use Nevela\Laravel\Console\SeedCommand;
11
+ use Nevela\Laravel\Console\UpdateCommand;
12
+ use Nevela\Laravel\Console\UserCommand;
13
+ use Nevela\Laravel\Http\FlareErrors;
14
+ use Nevela\Laravel\Http\TokenController;
15
+
16
+ final class NevelaServiceProvider extends ServiceProvider
17
+ {
18
+ public function register(): void
19
+ {
20
+ $this->mergeConfigFrom(__DIR__.'/../config/nevela.php', 'nevela');
21
+ }
22
+
23
+ public function boot(): void
24
+ {
25
+ if ($this->app->runningInConsole()) {
26
+ $this->publishes([__DIR__.'/../config/nevela.php' => config_path('nevela.php')], 'nevela-config');
27
+ $this->commands([MakeResourceCommand::class, GenerateCommand::class, SeedCommand::class, UserCommand::class, UpdateCommand::class]);
28
+ }
29
+
30
+ $this->callAfterResolving(ExceptionHandler::class, function ($handler) {
31
+ if (method_exists($handler, 'renderable')) {
32
+ $handler->renderable(fn (\Throwable $e, $request) => FlareErrors::render($e, $request));
33
+ }
34
+ });
35
+
36
+ if (! $this->app->routesAreCached()) {
37
+ $this->registerRoutes();
38
+ }
39
+ }
40
+
41
+ private function registerRoutes(): void
42
+ {
43
+ $prefix = config('nevela.prefix', 'api');
44
+
45
+ if (config('nevela.auth.enabled', true)) {
46
+ Route::prefix($prefix)->middleware('api')->name('nevela.auth.')->group(function () {
47
+ Route::post('auth/token', [TokenController::class, 'store'])->middleware('throttle:6,1')->name('store');
48
+ Route::middleware('auth:sanctum')->group(function () {
49
+ Route::get('auth/me', [TokenController::class, 'show'])->name('show');
50
+ Route::delete('auth/token', [TokenController::class, 'destroy'])->name('destroy');
51
+ });
52
+ });
53
+ }
54
+
55
+ Route::prefix($prefix)->middleware(config('nevela.middleware', ['api', 'auth:sanctum']))->name('nevela.')->group(function () {
56
+ // The descriptors, so the web app (and tooling) can check it matches the API.
57
+ Route::get('_nevela/resources', fn () => response()->json([
58
+ 'data' => array_map(fn ($d) => $d->toArray(), Nevela::all()),
59
+ ]))->name('resources');
60
+
61
+ if (is_file($routes = base_path('routes/nevela.php'))) {
62
+ require $routes;
63
+ }
64
+ });
65
+ }
66
+ }
@@ -0,0 +1,110 @@
1
+ <?php
2
+
3
+ namespace Nevela\Laravel\Support;
4
+
5
+ /**
6
+ * Works out how to bring an app's dashboard up to a newer template without losing what
7
+ * the developer changed.
8
+ *
9
+ * Three versions of every file are compared: the template the app was created from
10
+ * (base), the newer template (next) and what is in the app now (yours). Only a file the
11
+ * developer hasn't touched is replaced. Pure on purpose: no filesystem, no network.
12
+ */
13
+ final class DashboardUpdate
14
+ {
15
+ public const UPDATE = 'update'; // you didn't change it; the template did
16
+ public const ADD = 'add'; // new in the template
17
+ public const CONFLICT = 'conflict'; // you changed it and so did the template
18
+ public const REMOVED = 'removed'; // gone from the template; you still have it unchanged
19
+
20
+ /**
21
+ * @param array<string, string> $base path => contents, the template the app started from
22
+ * @param array<string, string> $next path => contents, the template to move to
23
+ * @param array<string, string|null> $yours path => contents in the app, null when the file is missing
24
+ * @return array<string, string> path => one of the constants above. Files needing nothing are left out.
25
+ */
26
+ public static function plan(array $base, array $next, array $yours): array
27
+ {
28
+ $plan = [];
29
+ foreach ($next as $path => $contents) {
30
+ $mine = $yours[$path] ?? null;
31
+ $before = $base[$path] ?? null;
32
+
33
+ if ($mine !== null && self::same($mine, $contents)) {
34
+ continue; // already there
35
+ }
36
+ if ($mine === null) {
37
+ // Not in the app. Add it if it is new; leave it out if you deleted it.
38
+ if ($before === null) {
39
+ $plan[$path] = self::ADD;
40
+ }
41
+
42
+ continue;
43
+ }
44
+ if ($before !== null && self::same($before, $contents)) {
45
+ continue; // the template didn't change this file; your version stands
46
+ }
47
+ $plan[$path] = $before !== null && self::same($mine, $before) ? self::UPDATE : self::CONFLICT;
48
+ }
49
+
50
+ foreach ($base as $path => $contents) {
51
+ $mine = $yours[$path] ?? null;
52
+ if (! isset($next[$path]) && $mine !== null && self::same($mine, $contents)) {
53
+ $plan[$path] = self::REMOVED;
54
+ }
55
+ }
56
+
57
+ ksort($plan);
58
+
59
+ return $plan;
60
+ }
61
+
62
+ /**
63
+ * Move the app's package.json to the template's dependency versions, keeping its name
64
+ * and anything else the developer added.
65
+ *
66
+ * A dependency is moved when the app still has the version the old template had, or
67
+ * doesn't have it at all. One the developer pinned differently is left and reported.
68
+ *
69
+ * @return array{json: string, changed: array<string, string>, kept: array<string, string>}
70
+ */
71
+ public static function mergePackageJson(string $yours, string $base, string $next): array
72
+ {
73
+ $app = json_decode($yours, true, 512, JSON_THROW_ON_ERROR);
74
+ $old = json_decode($base, true, 512, JSON_THROW_ON_ERROR);
75
+ $new = json_decode($next, true, 512, JSON_THROW_ON_ERROR);
76
+ $changed = [];
77
+ $kept = [];
78
+
79
+ foreach (['dependencies', 'devDependencies'] as $section) {
80
+ foreach ($new[$section] ?? [] as $name => $version) {
81
+ $mine = $app[$section][$name] ?? null;
82
+ if ($mine === $version) {
83
+ continue;
84
+ }
85
+ $before = $old[$section][$name] ?? null;
86
+ if ($mine === null || $mine === $before) {
87
+ $app[$section][$name] = $version;
88
+ $changed[$name] = ($mine ?? 'not installed').' → '.$version;
89
+ } else {
90
+ $kept[$name] = "{$mine} (template: {$version})";
91
+ }
92
+ }
93
+ if (isset($app[$section])) {
94
+ ksort($app[$section]);
95
+ }
96
+ }
97
+
98
+ // npm's own formatting: two spaces, unescaped slashes, a trailing newline.
99
+ $json = json_encode($app, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
100
+ $json = preg_replace_callback('/^( {4})+/m', fn ($m) => str_repeat(' ', strlen($m[0]) / 4), $json);
101
+
102
+ return ['json' => $json."\n", 'changed' => $changed, 'kept' => $kept];
103
+ }
104
+
105
+ /** Equal apart from line endings: git on Windows may have rewritten them. */
106
+ public static function same(string $a, string $b): bool
107
+ {
108
+ return $a === $b || str_replace("\r\n", "\n", $a) === str_replace("\r\n", "\n", $b);
109
+ }
110
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nevela-web",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {