├── dist
├── css
│ └── tool.css
├── mix-manifest.json
└── js
│ └── tool.js
├── resources
├── sass
│ └── tool.scss
├── js
│ ├── tool.js
│ └── components
│ │ └── Tool.vue
└── views
│ ├── navigation.blade.php
│ └── changelog.blade.php
├── webpack.mix.js
├── .gitignore
├── README.md
├── routes
├── web.php
└── api.php
├── src
├── Entry.php
├── Http
│ ├── Controllers
│ │ ├── Api
│ │ │ └── ChangelogController.php
│ │ └── ChangelogController.php
│ └── Middleware
│ │ └── Authorize.php
├── NovaChangelog.php
├── Providers
│ └── Tool.php
└── Changelog.php
├── .github
└── FUNDING.yml
├── composer.json
├── docker-compose.yml
├── package.json
└── .devcontainer
└── devcontainer.json
/dist/css/tool.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/sass/tool.scss:
--------------------------------------------------------------------------------
1 | // Nova Tool CSS
2 |
--------------------------------------------------------------------------------
/dist/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/js/tool.js": "/js/tool.js",
3 | "/css/tool.css": "/css/tool.css"
4 | }
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | let mix = require('laravel-mix')
2 |
3 | mix.setPublicPath('dist')
4 | .js('resources/js/tool.js', 'js')
5 | .sass('resources/sass/tool.scss', 'css')
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /vendor
3 | /node_modules
4 | package-lock.json
5 | composer.phar
6 | composer.lock
7 | phpunit.xml
8 | .phpunit.result.cache
9 | .DS_Store
10 | Thumbs.db
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Changelog for Laravel
2 |
3 | 
4 |
5 | Easily integrate and display your changelog in your app.
6 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 | {
2 | router.addRoutes([
3 | {
4 | name: 'laravel-changelog',
5 | path: '/laravel-changelog',
6 | component: require('./components/Tool'),
7 | },
8 | ])
9 | })
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mikebronner # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: #mikebronner
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | custom: # Replace with a single custom sponsorship URL
10 |
--------------------------------------------------------------------------------
/resources/views/navigation.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/ChangelogController.php:
--------------------------------------------------------------------------------
1 | entries;
14 | }
15 |
16 | public function show(string $version) : Entry
17 | {
18 | return (new Changelog)
19 | ->find($version);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/routes/api.php:
--------------------------------------------------------------------------------
1 | Change Log
5 |
6 | @foreach ($entries as $entry)
7 | @if ($entry->details)
8 |
9 | {{ $entry->version }}
10 |
11 | {{ $entry->date }}
12 |
13 |
14 |
15 |
16 | {!! $entry->details !!}
17 |
18 |
19 | @endif
20 | @endforeach
21 | @endsection
22 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ChangelogController.php:
--------------------------------------------------------------------------------
1 | entries;
14 |
15 | return view("laravel-changelog::changelog")
16 | ->with(compact("entries"));
17 | // return (new Changelog)
18 | // ->entries;
19 | }
20 |
21 | public function show(string $version) : Entry
22 | {
23 | return (new Changelog)
24 | ->find($version);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "genealabs/laravel-changelog",
3 | "description": "A Laravel Nova tool.",
4 | "keywords": [
5 | "laravel",
6 | "nova"
7 | ],
8 | "license": "MIT",
9 | "require": {
10 | "illuminate/filesystem": "^10.0|^11.0",
11 | "illuminate/support": "^10.0|^11.0",
12 | "jenssegers/model": "^1.5",
13 | "spatie/laravel-markdown": "^2.4"
14 | },
15 | "autoload": {
16 | "psr-4": {
17 | "GeneaLabs\\LaravelChangelog\\": "src/"
18 | }
19 | },
20 | "extra": {
21 | "laravel": {
22 | "providers": [
23 | "GeneaLabs\\LaravelChangelog\\Providers\\Tool"
24 | ]
25 | }
26 | },
27 | "config": {
28 | "sort-packages": true
29 | },
30 | "minimum-stability": "dev",
31 | "prefer-stable": true
32 | }
33 |
--------------------------------------------------------------------------------
/src/NovaChangelog.php:
--------------------------------------------------------------------------------
1 | latestVersion;
27 |
28 | return view('laravel-changelog::navigation')
29 | ->with(compact(
30 | "latestVersion"
31 | ));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Http/Middleware/Authorize.php:
--------------------------------------------------------------------------------
1 | first([$this, 'matchesTool']);
18 |
19 | return optional($tool)->authorize($request)
20 | ? $next($request)
21 | : abort(403);
22 | }
23 |
24 | /**
25 | * Determine whether this tool belongs to the package.
26 | *
27 | * @param \Laravel\Nova\Tool $tool
28 | * @return bool
29 | */
30 | public function matchesTool($tool)
31 | {
32 | return $tool instanceof LaravelChangelog;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | # For more information: https://laravel.com/docs/sail
2 | version: '3'
3 |
4 | services:
5 | laravel.test:
6 | image: ghcr.io/mikebronner/sail/php-8.2:latest
7 | extra_hosts:
8 | - 'host.docker.internal:host-gateway'
9 | ports:
10 | - '${APP_PORT:-80}:80'
11 | - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
12 | environment:
13 | WWWUSER: '${WWWUSER}'
14 | LARAVEL_SAIL: 1
15 | XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}'
16 | XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}'
17 | volumes:
18 | - '.:/var/www/html'
19 | networks:
20 | - sail
21 |
22 | networks:
23 | sail:
24 | driver: bridge
25 |
26 | volumes:
27 | sail-meilisearch:
28 | driver: local
29 | sail-minio:
30 | driver: local
31 | sail-pgsql:
32 | driver: local
33 | sail-redis:
34 | driver: local
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "cross-env": "^5.0.0",
14 | "laravel-mix": "^1.0"
15 | },
16 | "dependencies": {
17 | "vue": "^2.5.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Providers/Tool.php:
--------------------------------------------------------------------------------
1 | loadViewsFrom(__DIR__.'/../../resources/views', 'laravel-changelog');
12 |
13 | $this->app->booted(function () {
14 | $this->routes();
15 | });
16 | }
17 |
18 | protected function routes() : void
19 | {
20 | $namespace = 'GeneaLabs\LaravelChangelog\Http\Controllers';
21 |
22 | if ($this->app->routesAreCached()) {
23 | return;
24 | }
25 |
26 | app("router")
27 | ->middleware(['nova'])
28 | ->prefix('genealabs/laravel-changelog/api')
29 | ->namespace($namespace . "\Api")
30 | ->group(__DIR__ . '/../../routes/api.php');
31 |
32 | app("router")
33 | ->middleware(['web'])
34 | ->namespace($namespace)
35 | ->group(__DIR__ . '/../../routes/web.php');
36 | }
37 |
38 | public function register() : void
39 | {
40 | //
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/resources/js/components/Tool.vue:
--------------------------------------------------------------------------------
1 |
35 |
36 |
37 |
38 |
Change Log
39 |
40 |
43 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
64 |
--------------------------------------------------------------------------------
/src/Changelog.php:
--------------------------------------------------------------------------------
1 | reject(function ($path): bool {
19 | return is_dir($path);
20 | })
21 | ->filter(function ($existingFileName): bool {
22 | return Str::lower($existingFileName) === Str::lower($this->fileName);
23 | })
24 | ->first();
25 |
26 | if (! $changelog) {
27 | return "";
28 | }
29 |
30 | return file_get_contents(base_path($changelog));
31 | }
32 |
33 | protected function parseChangelog() : Collection
34 | {
35 | $changelog = $this->getChangelog();
36 | $versions = collect(explode("\n## ", $changelog));
37 | $versions->shift();
38 |
39 | return $versions
40 | ->reject(function ($entry): bool {
41 | return Str::startsWith($entry, "Unreleased");
42 | })
43 | ->map(function ($entry): Entry {
44 | $matches = [];
45 | preg_match("/\[(.*?)\].*?(\d{4}-\d{2}-\d{2})?\n(.*)?/s", $entry, $matches);
46 | $entry = new Entry;
47 | $entry->date = data_get($matches, 2)
48 | ?? "";
49 | $entry->version = data_get($matches, 1)
50 | ?? "";
51 | $entry->details = Str::markdown(data_get($matches, 3) ?? "");
52 |
53 | return $entry;
54 | });
55 | }
56 |
57 | public function find(string $version) : Entry
58 | {
59 | return $this
60 | ->entries
61 | ->filter(function ($entry) use ($version) {
62 | return version_compare($entry->version, $version, "==");
63 | })
64 | ->first();
65 | }
66 |
67 | public function load(?string $filename = "CHANGELOG.md"): self
68 | {
69 | $this->fileName = $filename;
70 |
71 | return $this;
72 | }
73 |
74 | public function getEntriesAttribute() : Collection
75 | {
76 | return $this
77 | ->parseChangelog()
78 | ->sort(function ($entry1, $entry2) {
79 | return version_compare($entry1->version, $entry2->version, ">")
80 | ? -1
81 | : 1;
82 | })
83 | ->values();
84 | }
85 |
86 | public function getLatestVersionEntryAttribute() : Entry
87 | {
88 | return $this
89 | ->entries
90 | ->filter(function ($entry) {
91 | return version_compare($entry->version, "0.0.1", ">=") >= 0
92 | && $entry->date;
93 | })
94 | ->first();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | // https://aka.ms/devcontainer.json
2 | {
3 | "name": "Existing Docker Compose (Extend)",
4 | "dockerComposeFile": [
5 | "../docker-compose.yml"
6 | ],
7 | "features": {
8 | "ghcr.io/devcontainers/features/sshd:1": {
9 | "version": "latest"
10 | }
11 | },
12 | "service": "laravel.test",
13 | "workspaceFolder": "/var/www/html",
14 | "customizations": {
15 | "vscode": {
16 | "settings": {},
17 | "extensions": [
18 | "aaron-bond.better-comments",
19 | "adrianwilczynski.alpine-js-intellisense",
20 | "AlexArthurs.todo-pusher",
21 | "amiralizadeh9480.laravel-extra-intellisense",
22 | "austenc.laravel-blade-spacer",
23 | "beyondcode.tinkerwell",
24 | "bmewburn.vscode-intelephense-client",
25 | "bradlc.vscode-tailwindcss",
26 | "christian-kohler.npm-intellisense",
27 | "christian-kohler.path-intellisense",
28 | "cierra.livewire-vscode",
29 | "codecov.codecov",
30 | "codingyu.laravel-goto-view",
31 | "davidanson.vscode-markdownlint",
32 | "davidbwaters.macos-modern-theme",
33 | "eamodio.gitlens",
34 | "editorconfig.editorconfig",
35 | "ericcheng.codesongclear",
36 | "faelv.composer-companion",
37 | "file-icons.file-icons",
38 | "foxundermoon.shell-format",
39 | "georgykurian.laravel-ide-helper",
40 | "github.codespaces",
41 | "GitHub.copilot-chat",
42 | "GitHub.copilot-nightly",
43 | "GitHub.copilot",
44 | "github.vscode-github-actions",
45 | "github.vscode-pull-request-github",
46 | "Gruntfuggly.todo-tree",
47 | "heissenbergerlab.php-array-from-json",
48 | "heybourn.headwind",
49 | "huibizhang.codesnap-plus",
50 | "irongeek.vscode-env",
51 | "kencocaceo.customvscodeuicss",
52 | "m4ns0ur.base64",
53 | "maciejdems.add-to-gitignore",
54 | "mahmoudshahin.laravel-routes",
55 | "markis.code-coverage",
56 | "martybegood.single-editor-tabs",
57 | "mechatroner.rainbow-csv",
58 | "mehedidracula.php-namespace-resolver",
59 | "mhutchie.git-graph",
60 | "mikestead.dotenv",
61 | "mohamedbenhida.laravel-intellisense",
62 | "mrmlnc.vscode-duplicate",
63 | "naoray.laravel-goto-components",
64 | "oderwat.indent-rainbow",
65 | "pcbowers.alpine-intellisense",
66 | "recca0120.vscode-phpunit",
67 | "redhat.vscode-yaml",
68 | "rifi2k.format-html-in-php",
69 | "shevaua.phpcs",
70 | "shufo.vscode-blade-formatter",
71 | "sperovita.alpinejs-syntax-highlight",
72 | "streetsidesoftware.code-spell-checker",
73 | "syler.ignore",
74 | "teabyii.ayu",
75 | "usernamehw.errorlens",
76 | "vincaslt.highlight-matching-tag",
77 | "WakaTime.vscode-wakatime",
78 | "withfig.fig",
79 | "xdebug.php-debug"
80 | ]
81 | }
82 | },
83 | "remoteUser": "sail",
84 | "postCreateCommand": "sudo chown -R 1000:1000 /var/www/html",
85 | // "forwardPorts": [
86 | // ],
87 | // "portsAttributes": {
88 | // },
89 | "mounts": [
90 | "source=${localEnv:HOME}/.wakatime.cfg,target=/home/sail/.wakatime.cfg,type=bind,consistency=delegated"
91 | ]
92 | // "runServices": [],
93 | // "shutdownAction": "none",
94 | }
--------------------------------------------------------------------------------
/dist/js/tool.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=0)}([function(e,t,n){n(1),e.exports=n(11)},function(e,t,n){Nova.booting(function(e,t,r){t.addRoutes([{name:"laravel-changelog",path:"/laravel-changelog",component:n(2)}])})},function(e,t,n){var r=n(8)(n(9),n(10),!1,function(e){n(3)},"data-v-4e88d959",null);e.exports=r.exports},function(e,t,n){var r=n(4);"string"==typeof r&&(r=[[e.i,r,""]]),r.locals&&(e.exports=r.locals);n(6)("446af118",r,!0,{})},function(e,t,n){(e.exports=n(5)(!1)).push([e.i,"",""])},function(e,t){e.exports=function(e){var t=[];return t.toString=function(){return this.map(function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(i=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(i))))+" */"),a=r.sources.map(function(e){return"/*# sourceURL="+r.sourceRoot+e+" */"});return[n].concat(a).concat([o]).join("\n")}var i;return[n].join("\n")}(t,e);return t[2]?"@media "+t[2]+"{"+n+"}":n}).join("")},t.i=function(e,n){"string"==typeof e&&(e=[[null,e,""]]);for(var r={},o=0;on.parts.length&&(r.parts.length=n.parts.length)}else{var i=[];for(o=0;o0&&void 0!==arguments[0]?arguments[0]:"",t=this;Nova.request().get("/genealabs/laravel-changelog/api/entries/"+e).then(function(e){console.log(e.data),t.changelog=e.data,t.nextLink=e.next,t.previousLink=e.previous,t.isLoading=!1}).catch(function(e){console.error(e,e.data)})}}}},function(e,t){e.exports={render:function(){var e=this,t=e.$createElement,n=e._self._c||t;return n("div",[n("h1",{staticClass:"mb-6"},[e._v("Change Log")]),e._v(" "),n("loading-view",{attrs:{loading:e.isLoading}},e._l(e.changelog,function(t,r){return n("div",{key:r},[t.details?n("div",[n("h2",{staticClass:"mb-2"},[n("span",{domProps:{textContent:e._s(t.version)}}),e._v(" "),n("span",{staticClass:"text-70 font-normal ml-6",domProps:{textContent:e._s(t.date)}})]),e._v(" "),n("card",{staticClass:"mb-8 p-4"},[n("p",{staticClass:"details",domProps:{innerHTML:e._s(t.details)}})])],1):e._e()])}),0)],1)},staticRenderFns:[]}},function(e,t){}]);
--------------------------------------------------------------------------------