diff --git a/.gitignore b/.gitignore index ba0b509c..d129bf2d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,8 @@ restore-helper-monoliths.php # OS / editor noise .DS_Store Thumbs.db -*.log \ No newline at end of file +*.log + + +# Generated contract audit artifact +tools/admin-contract-report.json diff --git a/inc/classes/class-aksrt-admin.php b/inc/classes/class-aksrt-admin.php index ebfb4909..25dcf8cb 100644 --- a/inc/classes/class-aksrt-admin.php +++ b/inc/classes/class-aksrt-admin.php @@ -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 '

'; + echo esc_html__('AKSRT 管理后台构建缺失:请在 assets/admin 执行 npm run build,并完整部署 build/ 目录。', 'aksrt'); + echo '

'; + }); + 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 '

'; + 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 '

'; + }); + // 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('