@wp-typia/project-tools 0.11.1

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 (187) hide show
  1. package/README.md +32 -0
  2. package/dist/runtime/cli-add.d.ts +38 -0
  3. package/dist/runtime/cli-add.js +561 -0
  4. package/dist/runtime/cli-core.d.ts +25 -0
  5. package/dist/runtime/cli-core.js +25 -0
  6. package/dist/runtime/cli-doctor.d.ts +34 -0
  7. package/dist/runtime/cli-doctor.js +131 -0
  8. package/dist/runtime/cli-help.d.ts +9 -0
  9. package/dist/runtime/cli-help.js +37 -0
  10. package/dist/runtime/cli-prompt.d.ts +21 -0
  11. package/dist/runtime/cli-prompt.js +53 -0
  12. package/dist/runtime/cli-scaffold.d.ts +79 -0
  13. package/dist/runtime/cli-scaffold.js +206 -0
  14. package/dist/runtime/cli-templates.d.ts +30 -0
  15. package/dist/runtime/cli-templates.js +61 -0
  16. package/dist/runtime/index.d.ts +9 -0
  17. package/dist/runtime/index.js +7 -0
  18. package/dist/runtime/json-utils.d.ts +10 -0
  19. package/dist/runtime/json-utils.js +12 -0
  20. package/dist/runtime/local-dev-presets.d.ts +26 -0
  21. package/dist/runtime/local-dev-presets.js +132 -0
  22. package/dist/runtime/metadata-analysis.d.ts +11 -0
  23. package/dist/runtime/metadata-analysis.js +285 -0
  24. package/dist/runtime/metadata-model.d.ts +84 -0
  25. package/dist/runtime/metadata-model.js +59 -0
  26. package/dist/runtime/metadata-parser.d.ts +53 -0
  27. package/dist/runtime/metadata-parser.js +794 -0
  28. package/dist/runtime/metadata-php-render.d.ts +29 -0
  29. package/dist/runtime/metadata-php-render.js +549 -0
  30. package/dist/runtime/metadata-projection.d.ts +7 -0
  31. package/dist/runtime/metadata-projection.js +233 -0
  32. package/dist/runtime/migration-constants.d.ts +15 -0
  33. package/dist/runtime/migration-constants.js +16 -0
  34. package/dist/runtime/migration-diff.d.ts +2 -0
  35. package/dist/runtime/migration-diff.js +537 -0
  36. package/dist/runtime/migration-fixtures.d.ts +8 -0
  37. package/dist/runtime/migration-fixtures.js +94 -0
  38. package/dist/runtime/migration-fuzz-plan.d.ts +2 -0
  39. package/dist/runtime/migration-fuzz-plan.js +50 -0
  40. package/dist/runtime/migration-manifest.d.ts +19 -0
  41. package/dist/runtime/migration-manifest.js +129 -0
  42. package/dist/runtime/migration-project.d.ts +94 -0
  43. package/dist/runtime/migration-project.js +1101 -0
  44. package/dist/runtime/migration-render.d.ts +11 -0
  45. package/dist/runtime/migration-render.js +741 -0
  46. package/dist/runtime/migration-risk.d.ts +4 -0
  47. package/dist/runtime/migration-risk.js +52 -0
  48. package/dist/runtime/migration-types.d.ts +249 -0
  49. package/dist/runtime/migration-types.js +1 -0
  50. package/dist/runtime/migration-ui-capability.d.ts +17 -0
  51. package/dist/runtime/migration-ui-capability.js +190 -0
  52. package/dist/runtime/migration-utils.d.ts +69 -0
  53. package/dist/runtime/migration-utils.js +246 -0
  54. package/dist/runtime/migrations.d.ts +249 -0
  55. package/dist/runtime/migrations.js +1061 -0
  56. package/dist/runtime/object-utils.d.ts +12 -0
  57. package/dist/runtime/object-utils.js +14 -0
  58. package/dist/runtime/package-managers.d.ts +28 -0
  59. package/dist/runtime/package-managers.js +156 -0
  60. package/dist/runtime/package-versions.d.ts +10 -0
  61. package/dist/runtime/package-versions.js +68 -0
  62. package/dist/runtime/scaffold-onboarding.d.ts +32 -0
  63. package/dist/runtime/scaffold-onboarding.js +99 -0
  64. package/dist/runtime/scaffold.d.ts +146 -0
  65. package/dist/runtime/scaffold.js +612 -0
  66. package/dist/runtime/schema-core.d.ts +267 -0
  67. package/dist/runtime/schema-core.js +597 -0
  68. package/dist/runtime/starter-manifests.d.ts +25 -0
  69. package/dist/runtime/starter-manifests.js +383 -0
  70. package/dist/runtime/string-case.d.ts +36 -0
  71. package/dist/runtime/string-case.js +69 -0
  72. package/dist/runtime/template-builtins.d.ts +38 -0
  73. package/dist/runtime/template-builtins.js +72 -0
  74. package/dist/runtime/template-defaults.d.ts +75 -0
  75. package/dist/runtime/template-defaults.js +65 -0
  76. package/dist/runtime/template-registry.d.ts +36 -0
  77. package/dist/runtime/template-registry.js +94 -0
  78. package/dist/runtime/template-render.d.ts +24 -0
  79. package/dist/runtime/template-render.js +113 -0
  80. package/dist/runtime/template-source.d.ts +71 -0
  81. package/dist/runtime/template-source.js +821 -0
  82. package/dist/runtime/typia-tags.d.ts +1 -0
  83. package/dist/runtime/typia-tags.js +1 -0
  84. package/package.json +79 -0
  85. package/templates/_shared/base/languages/.gitkeep +1 -0
  86. package/templates/_shared/base/package.json.mustache +41 -0
  87. package/templates/_shared/base/scripts/sync-types-to-block-json.ts.mustache +118 -0
  88. package/templates/_shared/base/src/hooks.ts.mustache +19 -0
  89. package/templates/_shared/base/src/validator-toolkit.ts.mustache +31 -0
  90. package/templates/_shared/base/tsconfig.json.mustache +21 -0
  91. package/templates/_shared/base/webpack.config.js.mustache +99 -0
  92. package/templates/_shared/base/{{slugKebabCase}}.php.mustache +53 -0
  93. package/templates/_shared/compound/core/package.json.mustache +45 -0
  94. package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +559 -0
  95. package/templates/_shared/compound/core/scripts/block-config.ts.mustache +13 -0
  96. package/templates/_shared/compound/core/scripts/sync-types-to-block-json.ts.mustache +53 -0
  97. package/templates/_shared/compound/core/webpack.config.js.mustache +141 -0
  98. package/templates/_shared/compound/core/{{slugKebabCase}}.php.mustache +51 -0
  99. package/templates/_shared/compound/persistence/package.json.mustache +50 -0
  100. package/templates/_shared/compound/persistence/scripts/block-config.ts.mustache +59 -0
  101. package/templates/_shared/compound/persistence/scripts/sync-rest-contracts.ts.mustache +101 -0
  102. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-types.ts.mustache +21 -0
  103. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api-validators.ts.mustache +32 -0
  104. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/api.ts.mustache +68 -0
  105. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/block.json.mustache +52 -0
  106. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/data.ts.mustache +192 -0
  107. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +123 -0
  108. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  109. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +132 -0
  110. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/render.php.mustache +158 -0
  111. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/save.tsx.mustache +3 -0
  112. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/types.ts.mustache +56 -0
  113. package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/validators.ts.mustache +32 -0
  114. package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +294 -0
  115. package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +312 -0
  116. package/templates/_shared/migration-ui/common/src/admin/migration-dashboard.tsx +394 -0
  117. package/templates/_shared/migration-ui/common/src/migration-detector.ts +9 -0
  118. package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +490 -0
  119. package/templates/_shared/migration-ui/common/src/migrations/index.ts +886 -0
  120. package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +290 -0
  121. package/templates/_shared/persistence/core/package.json.mustache +46 -0
  122. package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +113 -0
  123. package/templates/_shared/persistence/core/scripts/sync-types-to-block-json.ts.mustache +125 -0
  124. package/templates/_shared/persistence/core/src/api-types.ts.mustache +21 -0
  125. package/templates/_shared/persistence/core/src/api-validators.ts.mustache +32 -0
  126. package/templates/_shared/persistence/core/src/api.ts.mustache +68 -0
  127. package/templates/_shared/persistence/core/src/data.ts.mustache +192 -0
  128. package/templates/_shared/persistence/core/src/index.tsx.mustache +25 -0
  129. package/templates/_shared/persistence/core/src/interactivity.ts.mustache +134 -0
  130. package/templates/_shared/persistence/core/src/save.tsx.mustache +5 -0
  131. package/templates/_shared/persistence/core/src/validators.ts.mustache +32 -0
  132. package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +336 -0
  133. package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +308 -0
  134. package/templates/_shared/presets/test-preset/.wp-env.test.json.mustache +16 -0
  135. package/templates/_shared/presets/test-preset/playwright.config.ts.mustache +22 -0
  136. package/templates/_shared/presets/test-preset/scripts/wait-for-wp-env.mjs.mustache +102 -0
  137. package/templates/_shared/presets/test-preset/scripts/wp-env-utils.cjs.mustache +32 -0
  138. package/templates/_shared/presets/test-preset/tests/e2e/smoke.spec.ts.mustache +34 -0
  139. package/templates/_shared/presets/wp-env/.wp-env.json.mustache +16 -0
  140. package/templates/_shared/rest-helpers/auth/inc/rest-auth.php.mustache +37 -0
  141. package/templates/_shared/rest-helpers/public/inc/rest-public.php.mustache +314 -0
  142. package/templates/_shared/rest-helpers/shared/inc/rest-shared.php.mustache +58 -0
  143. package/templates/_shared/workspace/persistence-auth/inc/rest-auth.php.mustache +36 -0
  144. package/templates/_shared/workspace/persistence-auth/inc/rest-shared.php.mustache +55 -0
  145. package/templates/_shared/workspace/persistence-auth/server.php.mustache +237 -0
  146. package/templates/_shared/workspace/persistence-public/inc/rest-public.php.mustache +273 -0
  147. package/templates/_shared/workspace/persistence-public/inc/rest-shared.php.mustache +55 -0
  148. package/templates/_shared/workspace/persistence-public/server.php.mustache +252 -0
  149. package/templates/basic/src/block.json.mustache +51 -0
  150. package/templates/basic/src/edit.tsx.mustache +128 -0
  151. package/templates/basic/src/editor.scss.mustache +8 -0
  152. package/templates/basic/src/hooks.ts.mustache +18 -0
  153. package/templates/basic/src/index.tsx.mustache +45 -0
  154. package/templates/basic/src/save.tsx.mustache +30 -0
  155. package/templates/basic/src/style.scss.mustache +40 -0
  156. package/templates/basic/src/types.ts.mustache +56 -0
  157. package/templates/basic/src/validators.ts.mustache +26 -0
  158. package/templates/compound/src/blocks/{{slugKebabCase}}/block.json.mustache +37 -0
  159. package/templates/compound/src/blocks/{{slugKebabCase}}/children.ts.mustache +25 -0
  160. package/templates/compound/src/blocks/{{slugKebabCase}}/edit.tsx.mustache +93 -0
  161. package/templates/compound/src/blocks/{{slugKebabCase}}/hooks.ts.mustache +11 -0
  162. package/templates/compound/src/blocks/{{slugKebabCase}}/index.tsx.mustache +25 -0
  163. package/templates/compound/src/blocks/{{slugKebabCase}}/save.tsx.mustache +32 -0
  164. package/templates/compound/src/blocks/{{slugKebabCase}}/style.scss.mustache +31 -0
  165. package/templates/compound/src/blocks/{{slugKebabCase}}/types.ts.mustache +13 -0
  166. package/templates/compound/src/blocks/{{slugKebabCase}}/validators.ts.mustache +17 -0
  167. package/templates/compound/src/blocks/{{slugKebabCase}}-item/block.json.mustache +35 -0
  168. package/templates/compound/src/blocks/{{slugKebabCase}}-item/edit.tsx.mustache +50 -0
  169. package/templates/compound/src/blocks/{{slugKebabCase}}-item/hooks.ts.mustache +11 -0
  170. package/templates/compound/src/blocks/{{slugKebabCase}}-item/index.tsx.mustache +25 -0
  171. package/templates/compound/src/blocks/{{slugKebabCase}}-item/save.tsx.mustache +24 -0
  172. package/templates/compound/src/blocks/{{slugKebabCase}}-item/types.ts.mustache +12 -0
  173. package/templates/compound/src/blocks/{{slugKebabCase}}-item/validators.ts.mustache +17 -0
  174. package/templates/interactivity/package.json.mustache +42 -0
  175. package/templates/interactivity/src/block.json.mustache +73 -0
  176. package/templates/interactivity/src/edit.tsx.mustache +270 -0
  177. package/templates/interactivity/src/index.tsx.mustache +32 -0
  178. package/templates/interactivity/src/interactivity.ts.mustache +152 -0
  179. package/templates/interactivity/src/save.tsx.mustache +101 -0
  180. package/templates/interactivity/src/style.scss.mustache +60 -0
  181. package/templates/interactivity/src/types.ts.mustache +32 -0
  182. package/templates/interactivity/src/validators.ts.mustache +36 -0
  183. package/templates/persistence/src/block.json.mustache +52 -0
  184. package/templates/persistence/src/edit.tsx.mustache +165 -0
  185. package/templates/persistence/src/render.php.mustache +126 -0
  186. package/templates/persistence/src/style.scss.mustache +46 -0
  187. package/templates/persistence/src/types.ts.mustache +55 -0
@@ -0,0 +1,290 @@
1
+ <?php
2
+ /**
3
+ * Plugin Name: {{title}}
4
+ * Description: {{description}}
5
+ * Version: 0.1.0
6
+ * Requires at least: 6.7
7
+ * Tested up to: 6.9
8
+ * Requires PHP: 8.0
9
+ * Author: {{author}}
10
+ * License: GPL-2.0-or-later
11
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12
+ * Text Domain: {{textDomain}}
13
+ * Domain Path: /languages
14
+ */
15
+
16
+ if ( ! defined( 'ABSPATH' ) ) {
17
+ exit;
18
+ }
19
+
20
+ function {{phpPrefix}}_load_textdomain() {
21
+ load_plugin_textdomain(
22
+ '{{textDomain}}',
23
+ false,
24
+ dirname( plugin_basename( __FILE__ ) ) . '/languages'
25
+ );
26
+ }
27
+
28
+ define( '{{phpPrefixUpper}}_DATA_STORAGE_MODE', '{{dataStorageMode}}' );
29
+ // These helper files are scaffold-owned plumbing. Customize them only when you are changing auth or request validation behavior.
30
+ require_once __DIR__ . '/inc/rest-shared.php';
31
+ require_once __DIR__ . '/inc/rest-auth.php';
32
+
33
+ function {{phpPrefix}}_get_build_dir() {
34
+ $candidates = array(
35
+ __DIR__ . '/build',
36
+ __DIR__ . '/build/{{slugKebabCase}}',
37
+ );
38
+
39
+ foreach ( $candidates as $candidate ) {
40
+ if ( file_exists( $candidate . '/block.json' ) ) {
41
+ return $candidate;
42
+ }
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ function {{phpPrefix}}_get_counter_table_name() {
49
+ global $wpdb;
50
+ return $wpdb->prefix . '{{phpPrefix}}_counters';
51
+ }
52
+
53
+ function {{phpPrefix}}_get_counter_lock_name( $post_id, $resource_key ) {
54
+ return 'wpt_pcl_' . md5(
55
+ '{{phpPrefix}}|' . (int) $post_id . '|' . (string) $resource_key
56
+ );
57
+ }
58
+
59
+ function {{phpPrefix}}_with_counter_lock( $post_id, $resource_key, $callback ) {
60
+ global $wpdb;
61
+
62
+ $lock_name = {{phpPrefix}}_get_counter_lock_name( $post_id, $resource_key );
63
+ $acquired = (int) $wpdb->get_var(
64
+ $wpdb->prepare(
65
+ 'SELECT GET_LOCK(%s, 5)',
66
+ $lock_name
67
+ )
68
+ );
69
+
70
+ if ( 1 !== $acquired ) {
71
+ return new WP_Error( 'counter_lock_timeout', 'Could not acquire the counter lock.', array( 'status' => 503 ) );
72
+ }
73
+
74
+ try {
75
+ return $callback();
76
+ } finally {
77
+ $wpdb->get_var(
78
+ $wpdb->prepare(
79
+ 'SELECT RELEASE_LOCK(%s)',
80
+ $lock_name
81
+ )
82
+ );
83
+ }
84
+ }
85
+
86
+ function {{phpPrefix}}_maybe_install_storage() {
87
+ if ( 'custom-table' !== '{{dataStorageMode}}' ) {
88
+ return;
89
+ }
90
+
91
+ global $wpdb;
92
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
93
+
94
+ $table_name = {{phpPrefix}}_get_counter_table_name();
95
+ $charset_collate = $wpdb->get_charset_collate();
96
+ $sql = "CREATE TABLE {$table_name} (
97
+ post_id bigint(20) unsigned NOT NULL,
98
+ resource_key varchar(100) NOT NULL,
99
+ count bigint(20) unsigned NOT NULL DEFAULT 0,
100
+ updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
101
+ PRIMARY KEY (post_id, resource_key)
102
+ ) {$charset_collate};";
103
+
104
+ dbDelta( $sql );
105
+ $table_exists = $wpdb->get_var(
106
+ $wpdb->prepare(
107
+ 'SHOW TABLES LIKE %s',
108
+ $table_name
109
+ )
110
+ );
111
+
112
+ if ( $table_name === $table_exists ) {
113
+ update_option( '{{phpPrefix}}_storage_version', '1.0.0' );
114
+ }
115
+ }
116
+
117
+ function {{phpPrefix}}_ensure_storage_installed() {
118
+ if ( 'custom-table' === '{{dataStorageMode}}' && '1.0.0' !== get_option( '{{phpPrefix}}_storage_version', '' ) ) {
119
+ {{phpPrefix}}_maybe_install_storage();
120
+ }
121
+ }
122
+
123
+ function {{phpPrefix}}_get_rest_build_dir() {
124
+ return {{phpPrefix}}_get_build_dir();
125
+ }
126
+
127
+ function {{phpPrefix}}_get_counter( $post_id, $resource_key ) {
128
+ global $wpdb;
129
+
130
+ if ( 'custom-table' === '{{dataStorageMode}}' ) {
131
+ $table_name = {{phpPrefix}}_get_counter_table_name();
132
+ $count = $wpdb->get_var(
133
+ $wpdb->prepare(
134
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name comes from an internal helper.
135
+ "SELECT count FROM {$table_name} WHERE post_id = %d AND resource_key = %s",
136
+ $post_id,
137
+ $resource_key
138
+ )
139
+ );
140
+
141
+ return null === $count ? 0 : (int) $count;
142
+ }
143
+
144
+ $meta_key = '_' . '{{phpPrefix}}' . '_counter_' . sanitize_key( $resource_key );
145
+ return (int) get_post_meta( $post_id, $meta_key, true );
146
+ }
147
+
148
+ // Customize storage helpers here when you need a different backend or aggregate behavior.
149
+ function {{phpPrefix}}_increment_counter( $post_id, $resource_key, $delta ) {
150
+ global $wpdb;
151
+
152
+ if ( 'custom-table' === '{{dataStorageMode}}' ) {
153
+ $table_name = {{phpPrefix}}_get_counter_table_name();
154
+ $delta_value = (int) $delta;
155
+ $initial_count = max( 0, $delta_value );
156
+ $result = $wpdb->query(
157
+ $wpdb->prepare(
158
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name comes from an internal helper.
159
+ "INSERT INTO {$table_name} (post_id, resource_key, count, updated_at)
160
+ VALUES (%d, %s, %d, %s)
161
+ ON DUPLICATE KEY UPDATE
162
+ count = GREATEST(0, count + %d),
163
+ updated_at = VALUES(updated_at)",
164
+ $post_id,
165
+ $resource_key,
166
+ $initial_count,
167
+ current_time( 'mysql', true ),
168
+ $delta_value
169
+ )
170
+ );
171
+
172
+ if ( false === $result ) {
173
+ return new WP_Error( 'counter_update_failed', 'Failed to update the counter.', array( 'status' => 500 ) );
174
+ }
175
+
176
+ return {{phpPrefix}}_get_counter( $post_id, $resource_key );
177
+ }
178
+
179
+ return {{phpPrefix}}_with_counter_lock(
180
+ $post_id,
181
+ $resource_key,
182
+ function () use ( $delta, $post_id, $resource_key ) {
183
+ $meta_key = '_' . '{{phpPrefix}}' . '_counter_' . sanitize_key( $resource_key );
184
+ $next_count = max( 0, {{phpPrefix}}_get_counter( $post_id, $resource_key ) + (int) $delta );
185
+ update_post_meta( $post_id, $meta_key, $next_count );
186
+ return $next_count;
187
+ }
188
+ );
189
+ }
190
+
191
+ function {{phpPrefix}}_build_state_response( $post_id, $resource_key, $count ) {
192
+ return array(
193
+ 'postId' => (int) $post_id,
194
+ 'resourceKey' => (string) $resource_key,
195
+ 'count' => (int) $count,
196
+ 'storage' => '{{dataStorageMode}}',
197
+ );
198
+ }
199
+
200
+ // Route handlers are the main product-level extension point for request/response shaping.
201
+ function {{phpPrefix}}_handle_get_state( WP_REST_Request $request ) {
202
+ $payload = {{phpPrefix}}_validate_and_sanitize_request(
203
+ array(
204
+ 'postId' => $request->get_param( 'postId' ),
205
+ 'resourceKey' => $request->get_param( 'resourceKey' ),
206
+ ),
207
+ {{phpPrefix}}_get_rest_build_dir(),
208
+ 'state-query',
209
+ 'query'
210
+ );
211
+
212
+ if ( is_wp_error( $payload ) ) {
213
+ return $payload;
214
+ }
215
+
216
+ $count = {{phpPrefix}}_get_counter( (int) $payload['postId'], (string) $payload['resourceKey'] );
217
+ return rest_ensure_response(
218
+ {{phpPrefix}}_build_state_response(
219
+ (int) $payload['postId'],
220
+ (string) $payload['resourceKey'],
221
+ $count
222
+ )
223
+ );
224
+ }
225
+
226
+ function {{phpPrefix}}_handle_write_state( WP_REST_Request $request ) {
227
+ $payload = {{phpPrefix}}_validate_and_sanitize_request(
228
+ $request->get_json_params(),
229
+ {{phpPrefix}}_get_rest_build_dir(),
230
+ 'write-state-request',
231
+ 'body'
232
+ );
233
+
234
+ if ( is_wp_error( $payload ) ) {
235
+ return $payload;
236
+ }
237
+
238
+ $count = {{phpPrefix}}_increment_counter(
239
+ (int) $payload['postId'],
240
+ (string) $payload['resourceKey'],
241
+ isset( $payload['delta'] ) ? (int) $payload['delta'] : 1
242
+ );
243
+
244
+ if ( is_wp_error( $count ) ) {
245
+ return $count;
246
+ }
247
+
248
+ return rest_ensure_response(
249
+ {{phpPrefix}}_build_state_response(
250
+ (int) $payload['postId'],
251
+ (string) $payload['resourceKey'],
252
+ $count
253
+ )
254
+ );
255
+ }
256
+
257
+ // Add or reorganize endpoints here as the REST surface grows.
258
+ function {{phpPrefix}}_register_routes() {
259
+ register_rest_route(
260
+ '{{namespace}}/v1',
261
+ '/{{slugKebabCase}}/state',
262
+ array(
263
+ array(
264
+ 'methods' => WP_REST_Server::READABLE,
265
+ 'callback' => '{{phpPrefix}}_handle_get_state',
266
+ 'permission_callback' => '__return_true',
267
+ ),
268
+ array(
269
+ 'methods' => WP_REST_Server::CREATABLE,
270
+ 'callback' => '{{phpPrefix}}_handle_write_state',
271
+ 'permission_callback' => '{{phpPrefix}}_can_write_authenticated',
272
+ ),
273
+ )
274
+ );
275
+ }
276
+
277
+ function {{phpPrefix}}_register_block() {
278
+ $build_dir = {{phpPrefix}}_get_build_dir();
279
+ if ( ! $build_dir ) {
280
+ return;
281
+ }
282
+
283
+ register_block_type( $build_dir );
284
+ }
285
+
286
+ register_activation_hook( __FILE__, '{{phpPrefix}}_maybe_install_storage' );
287
+ add_action( 'init', '{{phpPrefix}}_load_textdomain' );
288
+ add_action( 'init', '{{phpPrefix}}_ensure_storage_installed' );
289
+ add_action( 'init', '{{phpPrefix}}_register_block' );
290
+ add_action( 'rest_api_init', '{{phpPrefix}}_register_routes' );
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "{{slug}}",
3
+ "version": "0.1.0",
4
+ "packageManager": "bun@1.3.10",
5
+ "description": "{{description}}",
6
+ "author": "{{author}}",
7
+ "license": "GPL-2.0-or-later",
8
+ "main": "build/index.js",
9
+ "scripts": {
10
+ "sync-types": "tsx scripts/sync-types-to-block-json.ts",
11
+ "sync-rest": "tsx scripts/sync-rest-contracts.ts",
12
+ "build": "bun run sync-types --check && bun run sync-rest --check && wp-scripts build --experimental-modules",
13
+ "start": "bun run sync-types && bun run sync-rest && wp-scripts start --experimental-modules",
14
+ "dev": "bun run start",
15
+ "lint:js": "wp-scripts lint-js",
16
+ "lint:css": "wp-scripts lint-style",
17
+ "lint": "bun run lint:js && bun run lint:css",
18
+ "format": "wp-scripts format",
19
+ "typecheck": "bun run sync-types --check && bun run sync-rest --check && tsc --noEmit"
20
+ },
21
+ "devDependencies": {
22
+ "@wp-typia/block-runtime": "{{blockRuntimePackageVersion}}",
23
+ "@wp-typia/api-client": "{{apiClientPackageVersion}}",
24
+ "@wp-typia/block-types": "{{blockTypesPackageVersion}}",
25
+ "@wp-typia/rest": "{{restPackageVersion}}",
26
+ "ajv": "^8.18.0",
27
+ "@types/wordpress__block-editor": "^11.5.17",
28
+ "@types/wordpress__blocks": "^12.5.18",
29
+ "@wordpress/browserslist-config": "^6.42.0",
30
+ "@wordpress/scripts": "^30.22.0",
31
+ "eslint-plugin-jsx-a11y": "^6.10.2",
32
+ "@typia/unplugin": "^12.0.1",
33
+ "tsx": "^4.20.5",
34
+ "typescript": "^5.9.2",
35
+ "typia": "^12.0.1"
36
+ },
37
+ "dependencies": {
38
+ "@wordpress/api-fetch": "^7.42.0",
39
+ "@wordpress/block-editor": "^15.2.0",
40
+ "@wordpress/blocks": "^15.2.0",
41
+ "@wordpress/components": "^30.2.0",
42
+ "@wordpress/element": "^6.29.0",
43
+ "@wordpress/i18n": "^6.2.0",
44
+ "@wordpress/interactivity": "^6.29.0"
45
+ }
46
+ }
@@ -0,0 +1,113 @@
1
+ /* eslint-disable no-console */
2
+ import {
3
+ defineEndpointManifest,
4
+ syncEndpointClient,
5
+ syncRestOpenApi,
6
+ syncTypeSchemas,
7
+ } from '@wp-typia/block-runtime/metadata-core';
8
+
9
+ function parseCliOptions( argv: string[] ) {
10
+ const options = {
11
+ check: false,
12
+ };
13
+
14
+ for ( const argument of argv ) {
15
+ if ( argument === '--check' ) {
16
+ options.check = true;
17
+ continue;
18
+ }
19
+
20
+ throw new Error( `Unknown sync-rest flag: ${ argument }` );
21
+ }
22
+
23
+ return options;
24
+ }
25
+
26
+ const REST_ENDPOINT_MANIFEST = defineEndpointManifest( {
27
+ contracts: {
28
+ 'state-query': {
29
+ sourceTypeName: '{{pascalCase}}StateQuery',
30
+ },
31
+ 'write-state-request': {
32
+ sourceTypeName: '{{pascalCase}}WriteStateRequest',
33
+ },
34
+ 'state-response': {
35
+ sourceTypeName: '{{pascalCase}}StateResponse',
36
+ },
37
+ },
38
+ endpoints: [
39
+ {
40
+ auth: 'public',
41
+ method: 'GET',
42
+ operationId: 'get{{pascalCase}}State',
43
+ path: '/{{namespace}}/v1/{{slugKebabCase}}/state',
44
+ queryContract: 'state-query',
45
+ responseContract: 'state-response',
46
+ summary: 'Read the current persisted state.',
47
+ tags: [ '{{title}}' ],
48
+ },
49
+ {
50
+ auth: '{{restWriteAuthIntent}}',
51
+ bodyContract: 'write-state-request',
52
+ method: 'POST',
53
+ operationId: 'write{{pascalCase}}State',
54
+ path: '/{{namespace}}/v1/{{slugKebabCase}}/state',
55
+ responseContract: 'state-response',
56
+ summary: 'Write the current persisted state.',
57
+ tags: [ '{{title}}' ],
58
+ wordpressAuth: {
59
+ mechanism: '{{restWriteAuthMechanism}}',
60
+ },
61
+ },
62
+ ],
63
+ info: {
64
+ title: '{{title}} REST API',
65
+ version: '1.0.0',
66
+ },
67
+ } );
68
+
69
+ async function main() {
70
+ const options = parseCliOptions( process.argv.slice( 2 ) );
71
+
72
+ for ( const [ baseName, contract ] of Object.entries( REST_ENDPOINT_MANIFEST.contracts ) ) {
73
+ await syncTypeSchemas( {
74
+ jsonSchemaFile: `src/api-schemas/${ baseName }.schema.json`,
75
+ openApiFile: `src/api-schemas/${ baseName }.openapi.json`,
76
+ openApiInfo: {
77
+ title: `${ contract.sourceTypeName }`,
78
+ version: '1.0.0',
79
+ },
80
+ sourceTypeName: contract.sourceTypeName,
81
+ typesFile: 'src/api-types.ts',
82
+ }, {
83
+ check: options.check,
84
+ } );
85
+ }
86
+
87
+ await syncRestOpenApi( {
88
+ manifest: REST_ENDPOINT_MANIFEST,
89
+ openApiFile: 'src/api.openapi.json',
90
+ typesFile: 'src/api-types.ts',
91
+ }, {
92
+ check: options.check,
93
+ } );
94
+
95
+ await syncEndpointClient( {
96
+ clientFile: 'src/api-client.ts',
97
+ manifest: REST_ENDPOINT_MANIFEST,
98
+ typesFile: 'src/api-types.ts',
99
+ }, {
100
+ check: options.check,
101
+ } );
102
+
103
+ console.log(
104
+ options.check
105
+ ? '✅ REST schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date with the TypeScript contracts!'
106
+ : '✅ REST schemas, portable API clients, and endpoint-aware OpenAPI documents were generated from TypeScript contracts!'
107
+ );
108
+ }
109
+
110
+ main().catch( ( error ) => {
111
+ console.error( '❌ REST contract sync failed:', error );
112
+ process.exit( 1 );
113
+ } );
@@ -0,0 +1,125 @@
1
+ /* eslint-disable no-console */
2
+ import { runSyncBlockMetadata } from '@wp-typia/block-runtime/metadata-core';
3
+
4
+ type SyncTypesReportMode = 'human' | 'json';
5
+
6
+ interface SyncTypesCliOptions {
7
+ check: boolean;
8
+ failOnLossy: boolean;
9
+ report: SyncTypesReportMode;
10
+ strict: boolean;
11
+ }
12
+
13
+ function parseCliOptions( argv: string[] ): SyncTypesCliOptions {
14
+ const options: SyncTypesCliOptions = {
15
+ check: false,
16
+ failOnLossy: false,
17
+ report: 'human',
18
+ strict: false,
19
+ };
20
+
21
+ for ( let index = 0; index < argv.length; index += 1 ) {
22
+ const argument = argv[ index ];
23
+
24
+ if ( argument === '--strict' ) {
25
+ options.strict = true;
26
+ continue;
27
+ }
28
+
29
+ if ( argument === '--fail-on-lossy' ) {
30
+ options.failOnLossy = true;
31
+ continue;
32
+ }
33
+
34
+ if ( argument === '--check' ) {
35
+ options.check = true;
36
+ continue;
37
+ }
38
+
39
+ if ( argument === '--report' ) {
40
+ const reportMode = argv[ index + 1 ];
41
+ if ( reportMode !== 'json' ) {
42
+ throw new Error( 'The `--report` flag currently supports only `json`.' );
43
+ }
44
+ options.report = reportMode;
45
+ index += 1;
46
+ continue;
47
+ }
48
+
49
+ throw new Error( `Unknown sync-types flag: ${ argument }` );
50
+ }
51
+
52
+ return options;
53
+ }
54
+
55
+ function printHumanReport(
56
+ options: SyncTypesCliOptions,
57
+ report: Awaited< ReturnType< typeof runSyncBlockMetadata > >
58
+ ) {
59
+ if ( report.failure ) {
60
+ console.error( '❌ Type sync failed:', report.failure.message );
61
+ return;
62
+ }
63
+
64
+ console.log(
65
+ options.check
66
+ ? '✅ block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json are already up to date with the TypeScript types!'
67
+ : '✅ block.json, typia.manifest.json, typia-validator.php, typia.schema.json, and typia.openapi.json were generated from TypeScript types!'
68
+ );
69
+ console.log( '📝 Generated attributes:', report.attributeNames );
70
+
71
+ if ( report.lossyProjectionWarnings.length > 0 ) {
72
+ console.warn(
73
+ '⚠️ Some Typia constraints were preserved only in typia.manifest.json:'
74
+ );
75
+ for ( const warning of report.lossyProjectionWarnings ) {
76
+ console.warn( ` - ${ warning }` );
77
+ }
78
+ }
79
+
80
+ if ( report.phpGenerationWarnings.length > 0 ) {
81
+ console.warn(
82
+ '⚠️ Some Typia constraints are not yet enforced by typia-validator.php:'
83
+ );
84
+ for ( const warning of report.phpGenerationWarnings ) {
85
+ console.warn( ` - ${ warning }` );
86
+ }
87
+ }
88
+
89
+ if ( report.status === 'error' ) {
90
+ console.error(
91
+ '❌ Type sync completed with warnings treated as errors because of the selected flags.'
92
+ );
93
+ }
94
+ }
95
+
96
+ async function main() {
97
+ const options = parseCliOptions( process.argv.slice( 2 ) );
98
+ const report = await runSyncBlockMetadata( {
99
+ blockJsonFile: 'src/block.json',
100
+ jsonSchemaFile: 'src/typia.schema.json',
101
+ manifestFile: 'src/typia.manifest.json',
102
+ openApiFile: 'src/typia.openapi.json',
103
+ sourceTypeName: '{{pascalCase}}Attributes',
104
+ typesFile: 'src/types.ts',
105
+ }, {
106
+ check: options.check,
107
+ failOnLossy: options.failOnLossy,
108
+ strict: options.strict,
109
+ } );
110
+
111
+ if ( options.report === 'json' ) {
112
+ process.stdout.write( `${ JSON.stringify( report, null, 2 ) }\n` );
113
+ } else {
114
+ printHumanReport( options, report );
115
+ }
116
+
117
+ if ( report.status === 'error' ) {
118
+ process.exitCode = 1;
119
+ }
120
+ }
121
+
122
+ main().catch( ( error ) => {
123
+ console.error( '❌ Type sync failed:', error );
124
+ process.exit( 1 );
125
+ } );
@@ -0,0 +1,21 @@
1
+ import { tags } from 'typia';
2
+
3
+ export interface {{pascalCase}}StateQuery {
4
+ postId: number & tags.Type< 'uint32' >;
5
+ resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
6
+ }
7
+
8
+ export interface {{pascalCase}}WriteStateRequest {
9
+ postId: number & tags.Type< 'uint32' >;
10
+ {{publicWriteRequestIdDeclaration}}
11
+ publicWriteToken?: string & tags.MinLength< 1 > & tags.MaxLength< 512 >;
12
+ resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
13
+ delta?: number & tags.Minimum< 1 > & tags.Type< 'uint32' > & tags.Default< 1 >;
14
+ }
15
+
16
+ export interface {{pascalCase}}StateResponse {
17
+ postId: number & tags.Type< 'uint32' >;
18
+ resourceKey: string & tags.MinLength< 1 > & tags.MaxLength< 100 >;
19
+ count: number & tags.Minimum< 0 > & tags.Type< 'uint32' >;
20
+ storage: ( 'post-meta' | 'custom-table' );
21
+ }
@@ -0,0 +1,32 @@
1
+ import typia from 'typia';
2
+
3
+ import {
4
+ toValidationResult,
5
+ type ValidationResult,
6
+ } from '@wp-typia/api-client';
7
+ import type {
8
+ {{pascalCase}}StateQuery,
9
+ {{pascalCase}}StateResponse,
10
+ {{pascalCase}}WriteStateRequest,
11
+ } from './api-types';
12
+
13
+ const validateStateQuery = typia.createValidate< {{pascalCase}}StateQuery >();
14
+ const validateWriteStateRequest =
15
+ typia.createValidate< {{pascalCase}}WriteStateRequest >();
16
+ const validateStateResponse =
17
+ typia.createValidate< {{pascalCase}}StateResponse >();
18
+
19
+ export const apiValidators = {
20
+ stateQuery: (
21
+ input: unknown
22
+ ): ValidationResult< {{pascalCase}}StateQuery > =>
23
+ toValidationResult( validateStateQuery( input ) ),
24
+ stateResponse: (
25
+ input: unknown
26
+ ): ValidationResult< {{pascalCase}}StateResponse > =>
27
+ toValidationResult( validateStateResponse( input ) ),
28
+ writeStateRequest: (
29
+ input: unknown
30
+ ): ValidationResult< {{pascalCase}}WriteStateRequest > =>
31
+ toValidationResult( validateWriteStateRequest( input ) ),
32
+ };
@@ -0,0 +1,68 @@
1
+ import {
2
+ callEndpoint,
3
+ resolveRestRouteUrl,
4
+ } from '@wp-typia/rest';
5
+
6
+ import {
7
+ type {{pascalCase}}StateQuery,
8
+ type {{pascalCase}}WriteStateRequest,
9
+ } from './api-types';
10
+ import {
11
+ get{{pascalCase}}StateEndpoint,
12
+ write{{pascalCase}}StateEndpoint,
13
+ } from './api-client';
14
+
15
+ export function resolveRestNonce( fallback?: string ): string | undefined {
16
+ if ( typeof fallback === 'string' && fallback.length > 0 ) {
17
+ return fallback;
18
+ }
19
+
20
+ if ( typeof window === 'undefined' ) {
21
+ return undefined;
22
+ }
23
+
24
+ const wpApiSettings = ( window as typeof window & {
25
+ wpApiSettings?: { nonce?: string };
26
+ } ).wpApiSettings;
27
+
28
+ return typeof wpApiSettings?.nonce === 'string' && wpApiSettings.nonce.length > 0
29
+ ? wpApiSettings.nonce
30
+ : undefined;
31
+ }
32
+
33
+ export const stateEndpoint = {
34
+ ...get{{pascalCase}}StateEndpoint,
35
+ buildRequestOptions: () => ( {
36
+ url: resolveRestRouteUrl( get{{pascalCase}}StateEndpoint.path ),
37
+ } ),
38
+ };
39
+
40
+ export const writeStateEndpoint = {
41
+ ...write{{pascalCase}}StateEndpoint,
42
+ buildRequestOptions: () => ( {
43
+ url: resolveRestRouteUrl( write{{pascalCase}}StateEndpoint.path ),
44
+ } ),
45
+ };
46
+
47
+ export function fetchState(
48
+ request: {{pascalCase}}StateQuery
49
+ ) {
50
+ return callEndpoint( stateEndpoint, request );
51
+ }
52
+
53
+ export function writeState(
54
+ request: {{pascalCase}}WriteStateRequest,
55
+ restNonce?: string
56
+ ) {
57
+ const nonce = resolveRestNonce( restNonce );
58
+
59
+ return callEndpoint( writeStateEndpoint, request, {
60
+ requestOptions: nonce
61
+ ? {
62
+ headers: {
63
+ 'X-WP-Nonce': nonce,
64
+ },
65
+ }
66
+ : undefined,
67
+ } );
68
+ }