Skip to content

Commit 6c755ec

Browse files
shanerbaner82claude
andcommitted
Add review checks email notification with doc links
Added "Email Developer" button to the Review Checks section header that sends the plugin owner an email showing passing/failing checks with links to the relevant docs for each failing item. Updated the best-practices doc with an Automated Review Checks section covering all 7 required items. Also updated PluginSubmitted notification to include the min_version checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3bbefda commit 6c755ec

File tree

5 files changed

+360
-1
lines changed

5 files changed

+360
-1
lines changed

app/Filament/Resources/PluginResource.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use App\Jobs\ReviewPluginRepository;
1111
use App\Jobs\SyncPlugin;
1212
use App\Models\Plugin;
13+
use App\Notifications\PluginReviewChecksIncomplete;
1314
use Filament\Forms;
1415
use Filament\Forms\Form;
1516
use Filament\Notifications\Notification;
@@ -121,6 +122,30 @@ public static function form(Form $form): Form
121122
: '❌ Missing'),
122123
])
123124
->columns(4)
125+
->headerActions([
126+
Forms\Components\Actions\Action::make('emailReviewChecks')
127+
->label('Email Developer')
128+
->icon('heroicon-o-envelope')
129+
->color('warning')
130+
->requiresConfirmation()
131+
->modalHeading('Email Review Check Results')
132+
->modalDescription(fn (?Plugin $record): ?string => $record
133+
? "This will email {$record->user->email} with the passing and failing review checks for '{$record->name}', including links to the relevant docs."
134+
: null)
135+
->action(function (?Plugin $record): void {
136+
if (! $record) {
137+
return;
138+
}
139+
140+
$record->user->notify(new PluginReviewChecksIncomplete($record));
141+
142+
Notification::make()
143+
->title('Email sent')
144+
->body("Review check results emailed to {$record->user->email}.")
145+
->success()
146+
->send();
147+
}),
148+
])
124149
->visible(fn (?Plugin $record) => $record?->review_checks !== null),
125150

126151
Forms\Components\Section::make('Submission Info')
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
<?php
2+
3+
namespace App\Notifications;
4+
5+
use App\Models\Plugin;
6+
use Illuminate\Bus\Queueable;
7+
use Illuminate\Contracts\Queue\ShouldQueue;
8+
use Illuminate\Notifications\Messages\MailMessage;
9+
use Illuminate\Notifications\Notification;
10+
11+
class PluginReviewChecksIncomplete extends Notification implements ShouldQueue
12+
{
13+
use Queueable;
14+
15+
private const DOCS_BASE = 'https://nativephp.com/docs/mobile/3/plugins';
16+
17+
/**
18+
* @var array<string, array{label: string, passing_label: string, docs_url: string, docs_label: string}>
19+
*/
20+
private const CHECK_DEFINITIONS = [
21+
'supports_ios' => [
22+
'label' => 'iOS native code in `resources/ios/Sources/`',
23+
'passing_label' => 'iOS native code',
24+
'docs_url' => self::DOCS_BASE.'/bridge-functions',
25+
'docs_label' => 'Bridge Functions guide',
26+
],
27+
'supports_android' => [
28+
'label' => 'Android native code in `resources/android/src/`',
29+
'passing_label' => 'Android native code',
30+
'docs_url' => self::DOCS_BASE.'/bridge-functions',
31+
'docs_label' => 'Bridge Functions guide',
32+
],
33+
'supports_js' => [
34+
'label' => 'JavaScript library in `resources/js/`',
35+
'passing_label' => 'JavaScript library',
36+
'docs_url' => self::DOCS_BASE.'/creating-plugins',
37+
'docs_label' => 'Creating Plugins guide',
38+
],
39+
'has_support_email' => [
40+
'label' => 'Support email in your README',
41+
'passing_label' => 'Support email',
42+
'docs_url' => self::DOCS_BASE.'/best-practices',
43+
'docs_label' => 'Best Practices guide',
44+
],
45+
'requires_mobile_sdk' => [
46+
'label' => '`nativephp/mobile` required in `composer.json`',
47+
'passing_label' => 'Requires nativephp/mobile',
48+
'docs_url' => self::DOCS_BASE.'/creating-plugins',
49+
'docs_label' => 'Creating Plugins guide',
50+
],
51+
'has_ios_min_version' => [
52+
'label' => 'iOS `min_version` set in `nativephp.json`',
53+
'passing_label' => 'iOS min_version',
54+
'docs_url' => self::DOCS_BASE.'/advanced-configuration',
55+
'docs_label' => 'Advanced Configuration guide',
56+
],
57+
'has_android_min_version' => [
58+
'label' => 'Android `min_version` set in `nativephp.json`',
59+
'passing_label' => 'Android min_version',
60+
'docs_url' => self::DOCS_BASE.'/advanced-configuration',
61+
'docs_label' => 'Advanced Configuration guide',
62+
],
63+
];
64+
65+
public function __construct(public Plugin $plugin) {}
66+
67+
/**
68+
* @return array<int, string>
69+
*/
70+
public function via(object $notifiable): array
71+
{
72+
return ['mail'];
73+
}
74+
75+
public function toMail(object $notifiable): MailMessage
76+
{
77+
$checks = $this->plugin->review_checks ?? [];
78+
$passing = $this->getPassingChecks($checks);
79+
$failing = $this->getFailingChecks($checks);
80+
81+
$message = (new MailMessage)
82+
->subject('Action Required: '.$this->plugin->name.' — Review Checks')
83+
->greeting('Hello,')
84+
->line("We've run automated checks against your plugin **{$this->plugin->name}** and found some items that need your attention before we can approve it.");
85+
86+
if (count($passing) > 0) {
87+
$message->line('**Passing checks:**');
88+
89+
foreach ($passing as $item) {
90+
$message->line("{$item}");
91+
}
92+
}
93+
94+
if (count($failing) > 0) {
95+
$message->line('**Missing items:**');
96+
97+
foreach ($failing as $item) {
98+
$message->line("{$item['label']} — [{$item['docs_label']}]({$item['docs_url']})");
99+
}
100+
}
101+
102+
$message
103+
->line('Please update your repository to address the missing items above. Once updated, we\'ll re-run the checks automatically.')
104+
->action('View Best Practices', self::DOCS_BASE.'/best-practices')
105+
->salutation("Happy coding!\n\nThe NativePHP Team");
106+
107+
return $message;
108+
}
109+
110+
/**
111+
* @return array<string, mixed>
112+
*/
113+
public function toArray(object $notifiable): array
114+
{
115+
return [
116+
'plugin_id' => $this->plugin->id,
117+
'plugin_name' => $this->plugin->name,
118+
];
119+
}
120+
121+
/**
122+
* @return array<int, string>
123+
*/
124+
private function getPassingChecks(array $checks): array
125+
{
126+
$passing = [];
127+
128+
foreach (self::CHECK_DEFINITIONS as $key => $definition) {
129+
if (! empty($checks[$key])) {
130+
$passing[] = $definition['passing_label'];
131+
}
132+
}
133+
134+
return $passing;
135+
}
136+
137+
/**
138+
* @return array<int, array{label: string, docs_url: string, docs_label: string}>
139+
*/
140+
private function getFailingChecks(array $checks): array
141+
{
142+
$failing = [];
143+
144+
foreach (self::CHECK_DEFINITIONS as $key => $definition) {
145+
if (empty($checks[$key])) {
146+
$failing[] = [
147+
'label' => $definition['label'],
148+
'docs_url' => $definition['docs_url'],
149+
'docs_label' => $definition['docs_label'],
150+
];
151+
}
152+
}
153+
154+
return $failing;
155+
}
156+
}

app/Notifications/PluginSubmitted.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ protected function getFailingChecks(): array
7979
'supports_js' => 'Add JavaScript support (resources/js/)',
8080
'has_support_email' => 'Add a support email to your README',
8181
'requires_mobile_sdk' => 'Require the nativephp/mobile SDK in composer.json',
82+
'has_ios_min_version' => 'Set iOS min_version in nativephp.json',
83+
'has_android_min_version' => 'Set Android min_version in nativephp.json',
8284
];
8385

8486
$failing = [];

resources/views/docs/mobile/3/plugins/best-practices.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,77 @@ checks beyond the Artisan command.
249249
Fix every warning too — they often indicate issues that will cause confusing failures for your users at build
250250
time or runtime.
251251

252+
## Automated Review Checks
253+
254+
When you submit your plugin, we run automated checks against your repository. These must all pass before
255+
your plugin can be approved. You can also run `php artisan native:plugin:validate` locally to catch issues early.
256+
257+
### Required Items
258+
259+
**iOS native code** — Your plugin must include native Swift code in `resources/ios/Sources/`. See
260+
[Bridge Functions](/docs/mobile/3/plugins/bridge-functions) for the implementation pattern.
261+
262+
**Android native code** — Your plugin must include native Kotlin code in `resources/android/src/`. See
263+
[Bridge Functions](/docs/mobile/3/plugins/bridge-functions) for the implementation pattern.
264+
265+
**JavaScript library** — Your plugin must include a JavaScript library in `resources/js/` that exports
266+
a function for every bridge function. This allows Inertia + Vue/React developers to call your native functions
267+
directly. See the [JavaScript Implementations](#javascript-implementations) section above.
268+
269+
**Support email** — Your README must include a valid support email address so developers can reach you
270+
with questions or issues.
271+
272+
**Require `nativephp/mobile`** — Your `composer.json` must require the `nativephp/mobile` SDK. This ensures
273+
your plugin is properly integrated with the NativePHP build pipeline:
274+
275+
```json
276+
{
277+
"require": {
278+
"nativephp/mobile": "^3.0"
279+
}
280+
}
281+
```
282+
283+
**iOS `min_version`** — Your `nativephp.json` must specify a minimum iOS version. See
284+
[Advanced Configuration](/docs/mobile/3/plugins/advanced-configuration) for details:
285+
286+
```json
287+
{
288+
"ios": {
289+
"min_version": "18.0"
290+
}
291+
}
292+
```
293+
294+
**Android `min_version`** — Your `nativephp.json` must specify a minimum Android SDK version. See
295+
[Advanced Configuration](/docs/mobile/3/plugins/advanced-configuration) for details:
296+
297+
```json
298+
{
299+
"android": {
300+
"min_version": 33
301+
}
302+
}
303+
```
304+
252305
## Checklist
253306

254307
Before submitting your plugin to the [NativePHP Plugin Marketplace](https://nativephp.com/plugins), verify:
255308

309+
**Automated checks (must pass):**
310+
311+
- [ ] iOS native code in `resources/ios/Sources/`
312+
- [ ] Android native code in `resources/android/src/`
313+
- [ ] JavaScript library in `resources/js/`
314+
- [ ] Support email in your README
315+
- [ ] `nativephp/mobile` required in `composer.json`
316+
- [ ] iOS `min_version` set in `nativephp.json`
317+
- [ ] Android `min_version` set in `nativephp.json`
318+
319+
**Documentation & quality:**
320+
256321
- [ ] README documents installation, PHP usage, and JS usage with complete examples
257322
- [ ] README documents all public methods, events, and required permissions
258-
- [ ] JavaScript library exports a function for every bridge function
259323
- [ ] `php artisan native:plugin:validate` passes with zero errors
260324
- [ ] Tested on a physical Android device
261325
- [ ] Tested on a physical iOS device (if iOS is supported)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
namespace Tests\Feature\Notifications;
4+
5+
use App\Models\Plugin;
6+
use App\Models\User;
7+
use App\Notifications\PluginReviewChecksIncomplete;
8+
use Illuminate\Foundation\Testing\RefreshDatabase;
9+
use Tests\TestCase;
10+
11+
class PluginReviewChecksIncompleteTest extends TestCase
12+
{
13+
use RefreshDatabase;
14+
15+
public function test_email_shows_passing_and_failing_checks(): void
16+
{
17+
$user = User::factory()->create();
18+
19+
$plugin = Plugin::factory()->create([
20+
'user_id' => $user->id,
21+
'name' => 'acme/test-plugin',
22+
'review_checks' => [
23+
'supports_ios' => true,
24+
'supports_android' => true,
25+
'supports_js' => false,
26+
'has_support_email' => true,
27+
'support_email' => 'dev@acme.io',
28+
'requires_mobile_sdk' => false,
29+
'mobile_sdk_constraint' => null,
30+
'has_ios_min_version' => true,
31+
'ios_min_version' => '18.0',
32+
'has_android_min_version' => false,
33+
'android_min_version' => null,
34+
],
35+
]);
36+
37+
$notification = new PluginReviewChecksIncomplete($plugin);
38+
$rendered = $notification->toMail($user)->render()->toHtml();
39+
40+
// Passing checks shown
41+
$this->assertStringContainsString('iOS native code', $rendered);
42+
$this->assertStringContainsString('Android native code', $rendered);
43+
$this->assertStringContainsString('Support email', $rendered);
44+
$this->assertStringContainsString('iOS min_version', $rendered);
45+
46+
// Failing checks shown with doc links
47+
$this->assertStringContainsString('JavaScript library', $rendered);
48+
$this->assertStringContainsString('nativephp/mobile', $rendered);
49+
$this->assertStringContainsString('Android', $rendered);
50+
$this->assertStringContainsString('creating-plugins', $rendered);
51+
$this->assertStringContainsString('advanced-configuration', $rendered);
52+
}
53+
54+
public function test_email_shows_all_failing_when_no_checks_pass(): void
55+
{
56+
$user = User::factory()->create();
57+
58+
$plugin = Plugin::factory()->create([
59+
'user_id' => $user->id,
60+
'name' => 'acme/empty-plugin',
61+
'review_checks' => [
62+
'supports_ios' => false,
63+
'supports_android' => false,
64+
'supports_js' => false,
65+
'has_support_email' => false,
66+
'support_email' => null,
67+
'requires_mobile_sdk' => false,
68+
'mobile_sdk_constraint' => null,
69+
'has_ios_min_version' => false,
70+
'ios_min_version' => null,
71+
'has_android_min_version' => false,
72+
'android_min_version' => null,
73+
],
74+
]);
75+
76+
$notification = new PluginReviewChecksIncomplete($plugin);
77+
$rendered = $notification->toMail($user)->render()->toHtml();
78+
79+
$this->assertStringContainsString('bridge-functions', $rendered);
80+
$this->assertStringContainsString('creating-plugins', $rendered);
81+
$this->assertStringContainsString('best-practices', $rendered);
82+
$this->assertStringContainsString('advanced-configuration', $rendered);
83+
}
84+
85+
public function test_email_subject_includes_plugin_name(): void
86+
{
87+
$user = User::factory()->create();
88+
89+
$plugin = Plugin::factory()->create([
90+
'user_id' => $user->id,
91+
'name' => 'acme/cool-plugin',
92+
'review_checks' => [
93+
'supports_ios' => true,
94+
'supports_android' => true,
95+
'supports_js' => true,
96+
'has_support_email' => true,
97+
'support_email' => 'dev@acme.io',
98+
'requires_mobile_sdk' => true,
99+
'mobile_sdk_constraint' => '^3.0',
100+
'has_ios_min_version' => true,
101+
'ios_min_version' => '18.0',
102+
'has_android_min_version' => true,
103+
'android_min_version' => '33',
104+
],
105+
]);
106+
107+
$notification = new PluginReviewChecksIncomplete($plugin);
108+
$mail = $notification->toMail($user);
109+
110+
$this->assertEquals('Action Required: acme/cool-plugin — Review Checks', $mail->subject);
111+
}
112+
}

0 commit comments

Comments
 (0)