chore(admin): add contract audit baseline and incomplete build notice

Introduce tools/audit-admin-contracts.php to fail on missing schemaPage
sections, missing REST $this callbacks, and incomplete admin build chunks.
Document menu/schema deploy rules and surface a wp-admin error when
assets/admin/build is missing modules instead of failing silently.
This commit is contained in:
2026-07-11 13:26:19 +08:00
parent 67fdf83db3
commit 52e9e2353f
4 changed files with 551 additions and 18 deletions
+71 -17
View File
@@ -66,6 +66,37 @@ class AKSRT_Admin {
$build_dir = trailingslashit(AKSRT_DIR) . 'assets/admin/build/';
$build_uri = trailingslashit(AKSRT_URI) . 'assets/admin/build/';
$entry = $build_dir . 'index.js';
if (!is_file($entry)) {
add_action('admin_notices', static function (): void {
if (!current_user_can('manage_options')) {
return;
}
echo '<div class="notice notice-error"><p>';
echo esc_html__('AKSRT 管理后台构建缺失:请在 assets/admin 执行 npm run build,并完整部署 build/ 目录。', 'aksrt');
echo '</p></div>';
});
return;
}
$missing_chunks = self::find_missing_admin_chunks($build_dir, $entry);
if ($missing_chunks !== []) {
add_action('admin_notices', static function () use ($missing_chunks): void {
if (!current_user_can('manage_options')) {
return;
}
echo '<div class="notice notice-error"><p>';
echo esc_html__('AKSRT 管理后台构建不完整(动态模块缺失),请重新完整部署 assets/admin/build/,不要只覆盖 index.js。缺失:', 'aksrt');
echo ' ' . esc_html(implode(', ', array_slice($missing_chunks, 0, 8)));
if (count($missing_chunks) > 8) {
echo '…';
}
echo '</p></div>';
});
// Still enqueue entry so console shows the real import error if an admin insists;
// notice makes the deploy mistake explicit.
}
$css_files = [
'naive-ui.css',
@@ -84,28 +115,51 @@ class AKSRT_Admin {
}
}
$entry = $build_dir . 'index.js';
if (file_exists($entry)) {
wp_enqueue_script(
'aksrt-admin-app',
$build_uri . 'index.js',
[],
(string) filemtime($entry),
true
);
wp_enqueue_script(
'aksrt-admin-app',
$build_uri . 'index.js',
[],
(string) filemtime($entry),
true
);
wp_localize_script('aksrt-admin-app', 'aksrtAdminData', [
'apiUrl' => esc_url_raw(rest_url(self::API_NAMESPACE)),
'nonce' => wp_create_nonce('wp_rest'),
'siteUrl' => home_url('/'),
'adminUrl' => admin_url('/'),
'themeVersion' => wp_get_theme()->get('Version') ?: '3.4.0-dev',
]);
}
wp_localize_script('aksrt-admin-app', 'aksrtAdminData', [
'apiUrl' => esc_url_raw(rest_url(self::API_NAMESPACE)),
'nonce' => wp_create_nonce('wp_rest'),
'siteUrl' => home_url('/'),
'adminUrl' => admin_url('/'),
'themeVersion' => wp_get_theme()->get('Version') ?: '3.4.0-dev',
'buildOk' => $missing_chunks === [],
]);
wp_enqueue_media();
}
/**
* Parse index.js dynamic imports and report missing chunk files.
*
* @return string[]
*/
private static function find_missing_admin_chunks(string $build_dir, string $entry_file): array {
$src = file_get_contents($entry_file);
if (!is_string($src) || $src === '') {
return ['index.js unreadable'];
}
if (!preg_match_all('/\\.\\/([A-Za-z0-9_.-]+\\.js)/', $src, $matches)) {
return [];
}
$missing = [];
foreach (array_unique($matches[1]) as $chunk) {
if (!is_file($build_dir . $chunk)) {
$missing[] = $chunk;
}
}
return $missing;
}
public function add_module_type_attribute(string $tag, string $handle): string {
if ('aksrt-admin-app' === $handle) {
return str_replace('<script ', '<script type="module" ', $tag);