diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e6d0709..160d817 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: release: uses: saucebase-dev/saucebase/.github/workflows/semantic-release.yml@main with: - version_file: 'module.json' + version_file: 'composer.json' version_file_path: '.version' prerelease: false secrets: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fd84ecd..d29720f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,5 @@ jobs: test: uses: saucebase-dev/saucebase/.github/workflows/test-module.yml@main with: - module: Blog + module: blog dependencies: saucebase/auth diff --git a/CLAUDE.md b/CLAUDE.md index bc17df0..7a11c6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,7 +57,7 @@ When adding a field visible on the frontend, update **all three** in sync: php -d memory_limit=2048M artisan test --testsuite=Modules --filter='^Modules\\Blog\\Tests' # E2E -npx playwright test --project="@Blog*" +npx playwright test --project="@blog*" ``` --- diff --git a/LICENSE b/LICENSE index f9a509b..cf78a69 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Saucebase +Copyright (c) 2026 Saucebase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Taskfile.yml b/Taskfile.yml index f361133..46b1848 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -9,11 +9,17 @@ tasks: test:e2e: desc: Run E2E tests for Blog module - cmd: npx playwright test --project="@Blog*" {{.CLI_ARGS}} + cmd: npx playwright test --project="@blog*" {{.CLI_ARGS}} interactive: true + # ── Database ────────────────────────────────────────────────── + + db:seed: + desc: Seed the Blog module database + cmd: php artisan modules:seed --module=blog + # ── Code Generation ──────────────────────────────────────────── types:generate: desc: Generate TypeScript types from PHP DTOs and enums - cmd: php artisan module:generate-types Blog + cmd: php artisan module:generate-types blog diff --git a/app/Providers/BlogServiceProvider.php b/app/Providers/BlogServiceProvider.php deleted file mode 100644 index 17363c2..0000000 --- a/app/Providers/BlogServiceProvider.php +++ /dev/null @@ -1,16 +0,0 @@ -group(module_path('Blog', '/routes/web.php')); - Route::middleware('api') - ->group(module_path('Blog', '/routes/api.php')); - } -} diff --git a/composer.json b/composer.json index 48e9508..a1641c0 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,9 @@ { "name": "saucebase/blog", - "description": "", + "description": "Blog module", "type": "saucebase-module", + "license": "proprietary", + "version": "0.1.0", "authors": [ { "name": "Saucebase", @@ -10,13 +12,15 @@ ], "extra": { "laravel": { - "providers": [], + "providers": [ + "Modules\\Blog\\Providers\\BlogServiceProvider" + ], "aliases": {} } }, "autoload": { "psr-4": { - "Modules\\Blog\\": "app/", + "Modules\\Blog\\": "src/", "Modules\\Blog\\Database\\Factories\\": "database/factories/", "Modules\\Blog\\Database\\Seeders\\": "database/seeders/" } @@ -25,5 +29,6 @@ "psr-4": { "Modules\\Blog\\Tests\\": "tests/" } - } + }, + "minimum-stability": "stable" } diff --git a/database/seeders/BlogDatabaseSeeder.php b/database/seeders/BlogDatabaseSeeder.php index b565dc8..ee579b2 100644 --- a/database/seeders/BlogDatabaseSeeder.php +++ b/database/seeders/BlogDatabaseSeeder.php @@ -20,7 +20,7 @@ public function run(): void $content = fn (string $file): string => file_get_contents(__DIR__.'/content/'.$file); - Post::create([ + $this->createPost([ 'title' => 'What Is Saucebase? The Modular Laravel Starter Kit', 'excerpt' => 'Every SaaS starts the same way: weeks of setup before you write a single line of real product code. Saucebase is the modular Laravel starter kit that skips that part.', 'content' => $content('post-1-what-is-saucebase.html'), @@ -28,9 +28,9 @@ public function run(): void 'published_at' => now()->subMonths(5), 'category_id' => $gettingStarted->id, 'author_id' => $author?->id, - ]); + ], public_path('images/blog/what-is-saucebase.jpg')); - Post::create([ + $this->createPost([ 'title' => 'Stop Rebuilding the Same Boilerplate Every Project', 'excerpt' => "If you've launched more than one Laravel app, you know the feeling — two weeks gone before you write any real product code. There's a better way.", 'content' => $content('post-2-stop-rebuilding-boilerplate.html'), @@ -40,17 +40,17 @@ public function run(): void 'author_id' => $author?->id, ]); - Post::create([ - 'title' => 'The VILT Stack: Laravel, Vue, Inertia, and Tailwind Done Right', - 'excerpt' => "Modern web dev has a tension between server productivity and rich interactivity. The VILT stack resolves it — and Saucebase wires all four pieces together so you don't have to.", + $this->createPost([ + 'title' => 'The Modern Laravel Stack: Inertia.js, Vue 3 or React, and Tailwind Done Right', + 'excerpt' => "Modern web dev has a tension between server productivity and rich interactivity. Laravel, Inertia.js, and Tailwind resolve it — and Saucebase wires all the pieces together so you don't have to.", 'content' => $content('post-3-vilt-stack.html'), 'status' => PostStatus::Published, 'published_at' => now()->subMonths(3), 'category_id' => $devExperience->id, 'author_id' => $author?->id, - ]); + ], public_path('images/blog/tech-stack.jpg')); - $post4 = Post::create([ + $this->createPost([ 'title' => 'Your First Module: Scaffold and Ship in Under 10 Minutes', 'excerpt' => "One Artisan command scaffolds a fully structured module — service provider, controller, routes, Vue pages, migrations, factory, tests, and Vite config. Here's how it works.", 'content' => $content('post-4-your-first-module.html'), @@ -58,14 +58,9 @@ public function run(): void 'published_at' => now()->subMonths(2), 'category_id' => $featuresModules->id, 'author_id' => $author?->id, - ]); + ], public_path('images/blog/add-your-saucebase.jpg')); - $image4 = public_path('images/blog/add-your-saucebase.jpg'); - if (file_exists($image4)) { - $post4->addMedia($image4)->preservingOriginal()->toMediaCollection('cover'); - } - - $post5 = Post::create([ + $this->createPost([ 'title' => 'Auth, Billing, and Privacy: The Three Modules Every SaaS Needs', 'excerpt' => "Three modules every SaaS needs — and Saucebase ships them already built. Here's what's included and why they matter.", 'content' => $content('post-5-auth-billing-privacy.html'), @@ -73,14 +68,9 @@ public function run(): void 'published_at' => now()->subMonth(), 'category_id' => $featuresModules->id, 'author_id' => $author?->id, - ]); + ], public_path('images/blog/cookies-or-privacy.jpg')); - $image5 = public_path('images/blog/cookies-or-privacy.jpg'); - if (file_exists($image5)) { - $post5->addMedia($image5)->preservingOriginal()->toMediaCollection('cover'); - } - - $post6 = Post::create([ + $this->createPost([ 'title' => 'Copy-and-Own: The Philosophy Behind Saucebase Modules', 'excerpt' => "Most starter kits give you a locked box. Saucebase takes the opposite approach: when you install a module, the source code lands in your repo and it's yours to read, edit, and own.", 'content' => $content('post-6-copy-and-own.html'), @@ -88,11 +78,17 @@ public function run(): void 'published_at' => now()->subWeeks(2), 'category_id' => $devExperience->id, 'author_id' => $author?->id, - ]); + ], public_path('images/blog/your-recipes.jpg')); + } - $image6 = public_path('images/blog/your-recipes.jpg'); - if (file_exists($image6)) { - $post6->addMedia($image6)->preservingOriginal()->toMediaCollection('cover'); + private function createPost(array $data, string $coverImage = ''): Post + { + $post = Post::create($data); + + if ($coverImage && file_exists($coverImage)) { + $post->addMedia($coverImage)->preservingOriginal()->toMediaCollection('cover'); } + + return $post; } } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..f29ab7b --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,13 @@ +call(BlogDatabaseSeeder::class); + } +} diff --git a/database/seeders/content/post-1-what-is-saucebase.html b/database/seeders/content/post-1-what-is-saucebase.html index caa1a7e..2680d10 100644 --- a/database/seeders/content/post-1-what-is-saucebase.html +++ b/database/seeders/content/post-1-what-is-saucebase.html @@ -2,9 +2,9 @@
Saucebase exists to solve that problem.
-Saucebase is a modular Laravel SaaS starter kit built on the VILT stack: Vue 3, Inertia.js, Laravel 13, and Tailwind CSS 4. It ships with the infrastructure every SaaS needs and gets it out of your way so you can focus on building what makes your product unique.
+Saucebase is a modular Laravel SaaS starter kit built on Laravel 13, Inertia.js, and Tailwind CSS 4 — with your choice of Vue 3 or React. It ships with the infrastructure every SaaS needs and gets it out of your way so you can focus on building what makes your product unique.
-The core of Saucebase is its module system, powered by nwidart/laravel-modules. Features are packaged as self-contained modules — Auth, Billing, Settings, Blog — each with their own routes, controllers, models, migrations, and Vue pages. Enable what you need, disable what you don't.
The core of Saucebase is its module system, powered by internachi/modular. Features are packaged as self-contained modules — Auth, Billing, Settings, Blog — each with their own routes, controllers, models, migrations, and frontend pages. Modules are active when installed via Composer and live directly in your repository.
But here's what sets Saucebase apart: modules are copy-and-own. When you install a module, you own it. It lives in your repository. You can edit it, extend it, break it, and fix it. There are no locked vendor packages to work around, no version conflicts to manage. Just clean, readable Laravel code that does exactly what you'd write yourself — except already written.
diff --git a/database/seeders/content/post-3-vilt-stack.html b/database/seeders/content/post-3-vilt-stack.html index 3c91f42..ce98521 100644 --- a/database/seeders/content/post-3-vilt-stack.html +++ b/database/seeders/content/post-3-vilt-stack.html @@ -1,4 +1,4 @@ -Modern web development has a tension at its core: server-side frameworks are productive but struggle with rich interactivity, while JavaScript SPAs offer great UX but introduce enormous complexity. The VILT stack resolves that tension elegantly — and Saucebase is built on it from the ground up.
+Modern web development has a tension at its core: server-side frameworks are productive but struggle with rich interactivity, while JavaScript SPAs offer great UX but introduce enormous complexity. The Laravel + Inertia.js stack resolves that tension elegantly — and Saucebase is built on it from the ground up.
Version 3 replaces traditional Blade views with Vue components while keeping your server-side routing and controllers intact. No REST API, no GraphQL, no data-fetching layer to maintain. Your controller returns a component name and props. Inertia handles the rest.
+Version 3 replaces traditional Blade views with Vue or React components while keeping your server-side routing and controllers intact. No REST API, no GraphQL, no data-fetching layer to maintain. Your controller returns a component name and props. Inertia handles the rest.
Version 3 brings deferred props, optimistic updates with automatic rollback, instant visits, and simplified SSR via the Vite plugin — all of which Saucebase uses by default.
-Composition API, <script setup>, and TypeScript throughout. Pages live in resources/js/pages/, components in resources/js/components/. Types are auto-generated from your PHP enums and DTOs via php artisan module:generate-types — your backend and frontend stay in sync without manual work.
Saucebase ships with full support for both frameworks. Pick Vue 3 (Composition API, <script setup>) or React 19 (hooks, JSX) — the choice is made during setup and everything is wired up for you. TypeScript is used throughout either way, and types are auto-generated from your PHP enums and DTOs via php artisan module:generate-types — your backend and frontend stay in sync without manual work.
After scaffolding, three more commands and your module is live:
+After scaffolding, run your migrations and rebuild assets:
-composer dump-autoload
-php artisan module:enable ModuleName
-php artisan module:migrate ModuleName --seed
-
-Then rebuild assets with npm run build or restart npm run dev.
php artisan migrate
+npm run build
php artisan module:generate-typesmodule-loader.jsadmin and user seeded by default)When a bug appears in your authentication flow, you look at modules/Auth/app/Http/Controllers/. When you want to add a field to the billing portal, you edit modules/Billing/resources/js/pages/. There's no mental overhead of figuring out which hook to use or which config key controls the behaviour you need. You find the code and change it.
When a bug appears in your authentication flow, you look at modules/auth/app/Http/Controllers/. When you want to add a field to the billing portal, you edit modules/billing/resources/js/pages/. There's no mental overhead of figuring out which hook to use or which config key controls the behaviour you need. You find the code and change it.
diff --git a/resources/js/components/PostCard.vue b/resources/js/vue/components/PostCard.vue similarity index 97% rename from resources/js/components/PostCard.vue rename to resources/js/vue/components/PostCard.vue index 82e441b..9a32542 100644 --- a/resources/js/components/PostCard.vue +++ b/resources/js/vue/components/PostCard.vue @@ -33,7 +33,7 @@ defineProps<{