vite-wp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/astro.d.ts +5 -0
  4. package/dist/astro.js +27 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +40 -0
  7. package/dist/commands/dev.d.ts +1 -0
  8. package/dist/commands/dev.js +62 -0
  9. package/dist/commands/doctor.d.ts +6 -0
  10. package/dist/commands/doctor.js +139 -0
  11. package/dist/commands/init.d.ts +1 -0
  12. package/dist/commands/init.js +55 -0
  13. package/dist/commands/smoke.d.ts +1 -0
  14. package/dist/commands/smoke.js +138 -0
  15. package/dist/commands/types.d.ts +1 -0
  16. package/dist/commands/types.js +67 -0
  17. package/dist/config.d.ts +71 -0
  18. package/dist/config.js +74 -0
  19. package/dist/content.d.ts +44 -0
  20. package/dist/content.js +190 -0
  21. package/dist/dev-toolbar/vitewp-toolbar.d.ts +23 -0
  22. package/dist/dev-toolbar/vitewp-toolbar.js +144 -0
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.js +2 -0
  25. package/dist/live.config.d.ts +64 -0
  26. package/dist/live.config.js +21 -0
  27. package/dist/runtime/astro.d.ts +4 -0
  28. package/dist/runtime/astro.js +69 -0
  29. package/dist/runtime/composer.d.ts +2 -0
  30. package/dist/runtime/composer.js +36 -0
  31. package/dist/runtime/php.d.ts +3 -0
  32. package/dist/runtime/php.js +79 -0
  33. package/dist/runtime/ports.d.ts +3 -0
  34. package/dist/runtime/ports.js +38 -0
  35. package/dist/runtime/process.d.ts +12 -0
  36. package/dist/runtime/process.js +51 -0
  37. package/dist/runtime/proxy.d.ts +6 -0
  38. package/dist/runtime/proxy.js +204 -0
  39. package/dist/runtime/wp-config.d.ts +3 -0
  40. package/dist/runtime/wp-config.js +82 -0
  41. package/dist/wordpress/client.d.ts +98 -0
  42. package/dist/wordpress/client.js +133 -0
  43. package/dist/wordpress/generated-types.d.ts +33 -0
  44. package/dist/wordpress/generated-types.js +2 -0
  45. package/dist/wordpress/menus.d.ts +24 -0
  46. package/dist/wordpress/menus.js +20 -0
  47. package/dist/wordpress/schemas.d.ts +58 -0
  48. package/dist/wordpress/schemas.js +39 -0
  49. package/dist/wordpress/templates.d.ts +19 -0
  50. package/dist/wordpress/templates.js +88 -0
  51. package/package.json +78 -0
  52. package/starter/.env.example +15 -0
  53. package/starter/astro.config.mjs +16 -0
  54. package/starter/composer.json +37 -0
  55. package/starter/src/env.d.ts +1 -0
  56. package/starter/src/live.config.ts +22 -0
  57. package/starter/src/pages/[...slug].astro +101 -0
  58. package/starter/src/templates/404.astro +11 -0
  59. package/starter/src/templates/pages/.gitkeep +0 -0
  60. package/starter/src/templates/pages/default.astro +12 -0
  61. package/starter/src/templates/pages/front-page.astro +13 -0
  62. package/starter/src/templates/partials/Header.astro +23 -0
  63. package/starter/src/templates/partials/Pagination.astro +21 -0
  64. package/starter/src/templates/post-types/.gitkeep +0 -0
  65. package/starter/src/templates/posts/.gitkeep +0 -0
  66. package/starter/src/templates/posts/archive.astro +25 -0
  67. package/starter/src/templates/posts/single.astro +12 -0
  68. package/starter/src/templates/search.astro +30 -0
  69. package/starter/src/templates/taxonomies/.gitkeep +0 -0
  70. package/starter/src/templates/taxonomies/taxonomy-[taxonomy].astro +28 -0
  71. package/starter/src/wordpress/client.ts +263 -0
  72. package/starter/src/wordpress/generated-types.ts +38 -0
  73. package/starter/src/wordpress/menus.ts +51 -0
  74. package/starter/src/wordpress/schemas.ts +44 -0
  75. package/starter/src/wordpress/templates.ts +113 -0
  76. package/starter/tsconfig.json +4 -0
  77. package/starter/vitewp.config.ts +29 -0
  78. package/starter/wordpress/content/mu-plugins/.gitkeep +0 -0
  79. package/starter/wordpress/content/mu-plugins/vitewp-bridge.php +560 -0
  80. package/starter/wordpress/content/themes/vitewp/functions.php +9 -0
  81. package/starter/wordpress/content/themes/vitewp/index.php +22 -0
  82. package/starter/wordpress/content/themes/vitewp/style.css +10 -0
@@ -0,0 +1,560 @@
1
+ <?php
2
+ /**
3
+ * Plugin Name: ViteWP Bridge
4
+ * Description: Development bridge between WordPress and ViteWP.
5
+ */
6
+
7
+ const VITEWP_THEME = 'vitewp';
8
+
9
+ add_filter('pre_option_template', 'vitewp_bridge_force_theme');
10
+ add_filter('pre_option_stylesheet', 'vitewp_bridge_force_theme');
11
+ add_filter('pre_option_current_theme', function () {
12
+ return 'ViteWP';
13
+ });
14
+
15
+ add_filter('got_url_rewrite', '__return_true');
16
+ add_filter('got_rewrite', '__return_true');
17
+
18
+ add_action('admin_init', function () {
19
+ $structure = (string) get_option('permalink_structure');
20
+
21
+ if (str_starts_with($structure, '/index.php/')) {
22
+ update_option('permalink_structure', substr($structure, strlen('/index.php')));
23
+ flush_rewrite_rules(false);
24
+ }
25
+ });
26
+
27
+ function vitewp_bridge_force_theme(): string
28
+ {
29
+ return VITEWP_THEME;
30
+ }
31
+
32
+ add_action('rest_api_init', function () {
33
+ register_rest_route('vitewp/v1', '/routing', [
34
+ 'methods' => WP_REST_Server::READABLE,
35
+ 'permission_callback' => '__return_true',
36
+ 'callback' => function () {
37
+ $page_on_front = (int) get_option('page_on_front');
38
+ $page_for_posts = (int) get_option('page_for_posts');
39
+
40
+ return [
41
+ 'show_on_front' => get_option('show_on_front'),
42
+ 'page_on_front' => $page_on_front,
43
+ 'page_for_posts' => $page_for_posts,
44
+ 'front_page' => vitewp_bridge_page_summary($page_on_front),
45
+ 'posts_page' => vitewp_bridge_page_summary($page_for_posts),
46
+ ];
47
+ },
48
+ ]);
49
+
50
+ register_rest_route('vitewp/v1', '/types', [
51
+ 'methods' => WP_REST_Server::READABLE,
52
+ 'permission_callback' => '__return_true',
53
+ 'callback' => function () {
54
+ return [
55
+ 'postTypes' => vitewp_bridge_post_types(),
56
+ 'taxonomies' => vitewp_bridge_taxonomies(),
57
+ ];
58
+ },
59
+ ]);
60
+
61
+ register_rest_route('vitewp/v1', '/menus', [
62
+ 'methods' => WP_REST_Server::READABLE,
63
+ 'permission_callback' => '__return_true',
64
+ 'callback' => 'vitewp_bridge_menus',
65
+ ]);
66
+
67
+ register_rest_route('vitewp/v1', '/resolve', [
68
+ 'methods' => WP_REST_Server::READABLE,
69
+ 'permission_callback' => '__return_true',
70
+ 'args' => [
71
+ 'path' => [
72
+ 'type' => 'string',
73
+ 'required' => true,
74
+ ],
75
+ ],
76
+ 'callback' => function (WP_REST_Request $request) {
77
+ return vitewp_bridge_resolve_path((string) $request->get_param('path'));
78
+ },
79
+ ]);
80
+
81
+ register_rest_route('vitewp/v1', '/archive', [
82
+ 'methods' => WP_REST_Server::READABLE,
83
+ 'permission_callback' => '__return_true',
84
+ 'callback' => function (WP_REST_Request $request) {
85
+ return vitewp_bridge_archive($request);
86
+ },
87
+ ]);
88
+ });
89
+
90
+ function vitewp_bridge_post_types(): array
91
+ {
92
+ $post_types = get_post_types(['public' => true, 'show_in_rest' => true], 'objects');
93
+ $items = [];
94
+
95
+ foreach ($post_types as $post_type) {
96
+ $items[] = [
97
+ 'name' => $post_type->name,
98
+ 'restBase' => vitewp_bridge_rest_base($post_type),
99
+ 'archive' => (bool) $post_type->has_archive,
100
+ 'archiveSlug' => $post_type->has_archive ? vitewp_bridge_archive_slug($post_type) : null,
101
+ 'label' => $post_type->label,
102
+ 'singularLabel' => $post_type->labels->singular_name,
103
+ 'taxonomies' => array_values(get_object_taxonomies($post_type->name)),
104
+ 'supports' => get_all_post_type_supports($post_type->name),
105
+ ];
106
+ }
107
+
108
+ return $items;
109
+ }
110
+
111
+ function vitewp_bridge_taxonomies(): array
112
+ {
113
+ $taxonomies = get_taxonomies(['public' => true, 'show_in_rest' => true], 'objects');
114
+ $items = [];
115
+
116
+ foreach ($taxonomies as $taxonomy) {
117
+ $items[] = [
118
+ 'name' => $taxonomy->name,
119
+ 'restBase' => $taxonomy->rest_base ?: $taxonomy->name,
120
+ 'label' => $taxonomy->label,
121
+ 'objectTypes' => array_values($taxonomy->object_type),
122
+ 'hierarchical' => (bool) $taxonomy->hierarchical,
123
+ ];
124
+ }
125
+
126
+ return $items;
127
+ }
128
+
129
+ function vitewp_bridge_page_summary(int $page_id): ?array
130
+ {
131
+ if ($page_id <= 0) {
132
+ return null;
133
+ }
134
+
135
+ $page = get_post($page_id);
136
+
137
+ if (! $page || $page->post_type !== 'page') {
138
+ return null;
139
+ }
140
+
141
+ return [
142
+ 'id' => $page->ID,
143
+ 'slug' => $page->post_name,
144
+ 'title' => get_the_title($page),
145
+ 'link' => get_permalink($page),
146
+ ];
147
+ }
148
+
149
+ function vitewp_bridge_resolve_path(string $input): WP_REST_Response
150
+ {
151
+ $parts = parse_url($input);
152
+ $query = [];
153
+ parse_str($parts['query'] ?? '', $query);
154
+
155
+ $path = '/' . trim($parts['path'] ?? '/', '/');
156
+ [$path, $page] = vitewp_bridge_strip_pagination($path);
157
+ $trimmed_path = trim($path, '/');
158
+
159
+ $search = vitewp_bridge_search_query($trimmed_path, $query);
160
+ if ($search !== null) {
161
+ return new WP_REST_Response([
162
+ 'found' => true,
163
+ 'kind' => 'search',
164
+ 'slug' => $trimmed_path,
165
+ 'postType' => 'post',
166
+ 'restBase' => 'posts',
167
+ 'title' => sprintf(__('Search results for “%s”'), $search),
168
+ 'search' => $search,
169
+ 'page' => $page,
170
+ ]);
171
+ }
172
+
173
+ if ($path === '/') {
174
+ return vitewp_bridge_resolve_home($page);
175
+ }
176
+
177
+ $post_id = url_to_postid(home_url($path));
178
+
179
+ if ($post_id > 0) {
180
+ $post = get_post($post_id);
181
+
182
+ if ($post) {
183
+ $post_type = get_post_type_object($post->post_type);
184
+
185
+ return new WP_REST_Response([
186
+ 'found' => true,
187
+ 'kind' => $post->post_type === 'page' ? 'page' : 'single',
188
+ 'id' => $post->ID,
189
+ 'slug' => $post->post_name,
190
+ 'postType' => $post->post_type,
191
+ 'restBase' => vitewp_bridge_rest_base($post_type),
192
+ 'isFrontPage' => false,
193
+ 'isPostsPage' => (int) get_option('page_for_posts') === $post->ID,
194
+ ]);
195
+ }
196
+ }
197
+
198
+ $single = vitewp_bridge_resolve_single_by_slug($path);
199
+
200
+ if ($single) {
201
+ return new WP_REST_Response($single);
202
+ }
203
+
204
+ $taxonomy = vitewp_bridge_resolve_taxonomy_archive($path, $page);
205
+
206
+ if ($taxonomy) {
207
+ return new WP_REST_Response($taxonomy);
208
+ }
209
+
210
+ $archive = vitewp_bridge_resolve_post_type_archive($path, $page);
211
+
212
+ if ($archive) {
213
+ return new WP_REST_Response($archive);
214
+ }
215
+
216
+ return new WP_REST_Response([
217
+ 'found' => false,
218
+ 'kind' => 'notFound',
219
+ ], 404);
220
+ }
221
+
222
+ function vitewp_bridge_resolve_home(int $page): WP_REST_Response
223
+ {
224
+ if (get_option('show_on_front') === 'page') {
225
+ $front_page_id = (int) get_option('page_on_front');
226
+ $front_page = get_post($front_page_id);
227
+
228
+ if ($front_page && $page <= 1) {
229
+ return new WP_REST_Response([
230
+ 'found' => true,
231
+ 'kind' => 'page',
232
+ 'id' => $front_page->ID,
233
+ 'slug' => $front_page->post_name,
234
+ 'postType' => 'page',
235
+ 'restBase' => 'pages',
236
+ 'isFrontPage' => true,
237
+ 'isPostsPage' => false,
238
+ ]);
239
+ }
240
+ }
241
+
242
+ return new WP_REST_Response([
243
+ 'found' => true,
244
+ 'kind' => 'postsArchive',
245
+ 'slug' => '',
246
+ 'postType' => 'post',
247
+ 'restBase' => 'posts',
248
+ 'title' => vitewp_bridge_posts_archive_title(),
249
+ 'page' => $page,
250
+ ]);
251
+ }
252
+
253
+ function vitewp_bridge_resolve_single_by_slug(string $path): ?array
254
+ {
255
+ $slug = basename(trim($path, '/'));
256
+
257
+ if ($slug === '') {
258
+ return null;
259
+ }
260
+
261
+ $post_types = array_values(get_post_types(['public' => true], 'names'));
262
+
263
+ $query = new WP_Query([
264
+ 'name' => $slug,
265
+ 'post_type' => $post_types,
266
+ 'post_status' => 'publish',
267
+ 'posts_per_page' => 1,
268
+ 'no_found_rows' => true,
269
+ ]);
270
+
271
+ if (! $query->have_posts()) {
272
+ return null;
273
+ }
274
+
275
+ $post = $query->posts[0];
276
+ $post_type = get_post_type_object($post->post_type);
277
+
278
+ return [
279
+ 'found' => true,
280
+ 'kind' => $post->post_type === 'page' ? 'page' : 'single',
281
+ 'id' => $post->ID,
282
+ 'slug' => $post->post_name,
283
+ 'postType' => $post->post_type,
284
+ 'restBase' => vitewp_bridge_rest_base($post_type),
285
+ 'isFrontPage' => (int) get_option('page_on_front') === $post->ID,
286
+ 'isPostsPage' => (int) get_option('page_for_posts') === $post->ID,
287
+ ];
288
+ }
289
+
290
+ function vitewp_bridge_resolve_post_type_archive(string $path, int $page): ?array
291
+ {
292
+ $trimmed_path = trim($path, '/');
293
+
294
+ if ($trimmed_path === '') {
295
+ return null;
296
+ }
297
+
298
+ $page_for_posts = (int) get_option('page_for_posts');
299
+
300
+ if ($page_for_posts > 0) {
301
+ $posts_page = get_post($page_for_posts);
302
+
303
+ if ($posts_page && $trimmed_path === $posts_page->post_name) {
304
+ return [
305
+ 'found' => true,
306
+ 'kind' => 'postsArchive',
307
+ 'slug' => $posts_page->post_name,
308
+ 'postType' => 'post',
309
+ 'restBase' => 'posts',
310
+ 'title' => get_the_title($posts_page),
311
+ 'page' => $page,
312
+ ];
313
+ }
314
+ }
315
+
316
+ $post_types = get_post_types(['public' => true], 'objects');
317
+
318
+ foreach ($post_types as $post_type) {
319
+ if ($post_type->name === 'post' || $post_type->name === 'page') {
320
+ continue;
321
+ }
322
+
323
+ if (! $post_type->has_archive) {
324
+ continue;
325
+ }
326
+
327
+ $archive_slug = vitewp_bridge_archive_slug($post_type);
328
+
329
+ if ($trimmed_path === $archive_slug) {
330
+ return [
331
+ 'found' => true,
332
+ 'kind' => 'postTypeArchive',
333
+ 'slug' => $archive_slug,
334
+ 'postType' => $post_type->name,
335
+ 'restBase' => vitewp_bridge_rest_base($post_type),
336
+ 'title' => $post_type->labels->name,
337
+ 'page' => $page,
338
+ ];
339
+ }
340
+ }
341
+
342
+ return null;
343
+ }
344
+
345
+ function vitewp_bridge_resolve_taxonomy_archive(string $path, int $page): ?array
346
+ {
347
+ $trimmed_path = trim($path, '/');
348
+
349
+ if ($trimmed_path === '') {
350
+ return null;
351
+ }
352
+
353
+ $segments = explode('/', $trimmed_path);
354
+ $term_slug = end($segments);
355
+
356
+ foreach (get_taxonomies(['public' => true], 'objects') as $taxonomy) {
357
+ $base = vitewp_bridge_taxonomy_base($taxonomy);
358
+ $matches_base = count($segments) >= 2 && $segments[0] === $base;
359
+ $matches_direct = count($segments) === 1;
360
+
361
+ if (! $matches_base && ! $matches_direct) {
362
+ continue;
363
+ }
364
+
365
+ $term = get_term_by('slug', $term_slug, $taxonomy->name);
366
+
367
+ if (! $term || is_wp_error($term)) {
368
+ continue;
369
+ }
370
+
371
+ return [
372
+ 'found' => true,
373
+ 'kind' => 'taxonomyArchive',
374
+ 'slug' => $term->slug,
375
+ 'taxonomy' => $taxonomy->name,
376
+ 'taxonomyRestBase' => $taxonomy->rest_base ?: $taxonomy->name,
377
+ 'termId' => $term->term_id,
378
+ 'termName' => $term->name,
379
+ 'postType' => vitewp_bridge_taxonomy_primary_post_type($taxonomy),
380
+ 'restBase' => 'posts',
381
+ 'title' => $term->name,
382
+ 'page' => $page,
383
+ ];
384
+ }
385
+
386
+ return null;
387
+ }
388
+
389
+ function vitewp_bridge_archive(WP_REST_Request $request): array
390
+ {
391
+ $kind = (string) $request->get_param('kind');
392
+ $page = max(1, (int) ($request->get_param('page') ?: 1));
393
+ $per_page = min(100, max(1, (int) ($request->get_param('perPage') ?: 10)));
394
+
395
+ $args = [
396
+ 'post_status' => 'publish',
397
+ 'posts_per_page' => $per_page,
398
+ 'paged' => $page,
399
+ ];
400
+
401
+ if ($kind === 'search') {
402
+ $args['post_type'] = array_values(get_post_types(['public' => true], 'names'));
403
+ $args['s'] = (string) $request->get_param('search');
404
+ } elseif ($kind === 'taxonomyArchive') {
405
+ $taxonomy = (string) $request->get_param('taxonomy');
406
+ $term_id = (int) $request->get_param('termId');
407
+ $args['post_type'] = 'any';
408
+ $args['tax_query'] = [[
409
+ 'taxonomy' => $taxonomy,
410
+ 'field' => 'term_id',
411
+ 'terms' => [$term_id],
412
+ ]];
413
+ } else {
414
+ $args['post_type'] = (string) ($request->get_param('postType') ?: 'post');
415
+ }
416
+
417
+ $query = new WP_Query($args);
418
+
419
+ return [
420
+ 'items' => array_map('vitewp_bridge_post_item', $query->posts),
421
+ 'page' => $page,
422
+ 'perPage' => $per_page,
423
+ 'total' => (int) $query->found_posts,
424
+ 'totalPages' => (int) $query->max_num_pages,
425
+ ];
426
+ }
427
+
428
+ function vitewp_bridge_post_item(WP_Post $post): array
429
+ {
430
+ return [
431
+ 'id' => $post->ID,
432
+ 'slug' => $post->post_name,
433
+ 'type' => $post->post_type,
434
+ 'link' => get_permalink($post),
435
+ 'title' => ['rendered' => get_the_title($post)],
436
+ 'content' => ['rendered' => apply_filters('the_content', $post->post_content)],
437
+ 'excerpt' => ['rendered' => apply_filters('the_excerpt', get_the_excerpt($post))],
438
+ 'date' => get_post_time(DATE_ATOM, false, $post),
439
+ 'modified' => get_post_modified_time(DATE_ATOM, false, $post),
440
+ ];
441
+ }
442
+
443
+ function vitewp_bridge_menus(): array
444
+ {
445
+ $locations = get_nav_menu_locations();
446
+ $menus = [];
447
+
448
+ foreach (wp_get_nav_menus() as $menu) {
449
+ $items = wp_get_nav_menu_items($menu->term_id) ?: [];
450
+ $menus[] = [
451
+ 'id' => $menu->term_id,
452
+ 'slug' => $menu->slug,
453
+ 'name' => $menu->name,
454
+ 'items' => array_map('vitewp_bridge_menu_item', $items),
455
+ ];
456
+ }
457
+
458
+ return [
459
+ 'locations' => $locations,
460
+ 'menus' => $menus,
461
+ ];
462
+ }
463
+
464
+ function vitewp_bridge_menu_item(WP_Post $item): array
465
+ {
466
+ return [
467
+ 'id' => $item->ID,
468
+ 'parent' => (int) $item->menu_item_parent,
469
+ 'title' => $item->title,
470
+ 'url' => $item->url,
471
+ 'target' => $item->target,
472
+ 'classes' => array_values(array_filter($item->classes ?? [])),
473
+ 'object' => $item->object,
474
+ 'objectId' => (int) $item->object_id,
475
+ 'type' => $item->type,
476
+ ];
477
+ }
478
+
479
+ function vitewp_bridge_posts_archive_title(): string
480
+ {
481
+ $page_for_posts = (int) get_option('page_for_posts');
482
+
483
+ if ($page_for_posts > 0) {
484
+ return get_the_title($page_for_posts);
485
+ }
486
+
487
+ return __('Posts');
488
+ }
489
+
490
+ function vitewp_bridge_strip_pagination(string $path): array
491
+ {
492
+ if (preg_match('#^(.*)/page/([0-9]+)/?$#', $path, $matches)) {
493
+ $base = $matches[1] ?: '/';
494
+ return [$base === '' ? '/' : $base, max(1, (int) $matches[2])];
495
+ }
496
+
497
+ return [$path, 1];
498
+ }
499
+
500
+ function vitewp_bridge_search_query(string $trimmed_path, array $query): ?string
501
+ {
502
+ $search = trim((string) ($query['s'] ?? $query['q'] ?? ''));
503
+
504
+ if ($search !== '') {
505
+ return $search;
506
+ }
507
+
508
+ $segments = array_values(array_filter(explode('/', $trimmed_path)));
509
+
510
+ if (($segments[0] ?? '') === 'search') {
511
+ return isset($segments[1]) ? urldecode($segments[1]) : '';
512
+ }
513
+
514
+ return null;
515
+ }
516
+
517
+ function vitewp_bridge_archive_slug(WP_Post_Type $post_type): string
518
+ {
519
+ if (is_string($post_type->has_archive)) {
520
+ return trim($post_type->has_archive, '/');
521
+ }
522
+
523
+ if (is_array($post_type->rewrite) && isset($post_type->rewrite['slug'])) {
524
+ return trim((string) $post_type->rewrite['slug'], '/');
525
+ }
526
+
527
+ return $post_type->name;
528
+ }
529
+
530
+ function vitewp_bridge_taxonomy_base(WP_Taxonomy $taxonomy): string
531
+ {
532
+ if ($taxonomy->name === 'category') {
533
+ return trim((string) get_option('category_base') ?: 'category', '/');
534
+ }
535
+
536
+ if ($taxonomy->name === 'post_tag') {
537
+ return trim((string) get_option('tag_base') ?: 'tag', '/');
538
+ }
539
+
540
+ if (is_array($taxonomy->rewrite) && isset($taxonomy->rewrite['slug'])) {
541
+ return trim((string) $taxonomy->rewrite['slug'], '/');
542
+ }
543
+
544
+ return $taxonomy->name;
545
+ }
546
+
547
+ function vitewp_bridge_taxonomy_primary_post_type(WP_Taxonomy $taxonomy): string
548
+ {
549
+ $object_types = array_values($taxonomy->object_type);
550
+ return $object_types[0] ?? 'post';
551
+ }
552
+
553
+ function vitewp_bridge_rest_base(?WP_Post_Type $post_type): string
554
+ {
555
+ if (! $post_type) {
556
+ return 'posts';
557
+ }
558
+
559
+ return $post_type->rest_base ?: $post_type->name;
560
+ }
@@ -0,0 +1,9 @@
1
+ <?php
2
+ /**
3
+ * ViteWP placeholder theme functions.
4
+ */
5
+
6
+ add_action('after_setup_theme', function () {
7
+ add_theme_support('title-tag');
8
+ add_theme_support('post-thumbnails');
9
+ });
@@ -0,0 +1,22 @@
1
+ <?php
2
+ /**
3
+ * ViteWP placeholder theme.
4
+ *
5
+ * The public frontend is rendered by Astro through the ViteWP proxy.
6
+ */
7
+ ?><!doctype html>
8
+ <html <?php language_attributes(); ?>>
9
+ <head>
10
+ <meta charset="<?php bloginfo('charset'); ?>">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1">
12
+ <?php wp_head(); ?>
13
+ </head>
14
+ <body <?php body_class(); ?>>
15
+ <?php wp_body_open(); ?>
16
+ <main style="font-family: system-ui, sans-serif; max-width: 720px; margin: 10vh auto; padding: 24px;">
17
+ <h1>ViteWP</h1>
18
+ <p>This placeholder theme keeps WordPress admin healthy. The frontend is rendered by Astro.</p>
19
+ </main>
20
+ <?php wp_footer(); ?>
21
+ </body>
22
+ </html>
@@ -0,0 +1,10 @@
1
+ /*
2
+ Theme Name: ViteWP
3
+ Theme URI: https://example.com/vitewp
4
+ Author: ViteWP
5
+ Description: Placeholder theme used by ViteWP.
6
+ Version: 0.0.1
7
+ Requires at least: 7.0
8
+ Requires PHP: 8.2
9
+ Text Domain: vitewp
10
+ */