@typokit/plugin-axum 0.2.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.
@@ -0,0 +1,593 @@
1
+ use super::GeneratedOutput;
2
+
3
+ /// Generate the complete Rust project scaffold.
4
+ ///
5
+ /// Produces:
6
+ /// - `Cargo.toml` — dependencies and package metadata (overwrite: true)
7
+ /// - `src/main.rs` — tokio entrypoint with dotenvy, tracing, db pool, axum::serve (overwrite: true)
8
+ /// - `src/lib.rs` — #[path] attributes bridging .typokit/ modules plus pub mod for user code (overwrite: true)
9
+ /// - `.typokit/app.rs` — AppState struct with PgPool (overwrite: true)
10
+ /// - `.typokit/error.rs` — AppError enum implementing IntoResponse (overwrite: true)
11
+ pub fn generate_project() -> Vec<GeneratedOutput> {
12
+ vec![
13
+ generate_cargo_toml(),
14
+ generate_main_rs(),
15
+ generate_lib_rs(),
16
+ generate_app_rs(),
17
+ generate_error_rs(),
18
+ ]
19
+ }
20
+
21
+ // ─────────────────────────── Cargo.toml ──────────────────────────────────────
22
+
23
+ fn generate_cargo_toml() -> GeneratedOutput {
24
+ let content = r#"# AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT
25
+
26
+ [package]
27
+ name = "server"
28
+ version = "0.1.0"
29
+ edition = "2024"
30
+
31
+ [dependencies]
32
+ axum = "0.8"
33
+ tokio = { version = "1", features = ["full"] }
34
+ serde = { version = "1", features = ["derive"] }
35
+ serde_json = "1"
36
+ validator = { version = "0.19", features = ["derive"] }
37
+ chrono = { version = "0.4", features = ["serde"] }
38
+ uuid = "1"
39
+ sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }
40
+ tower-http = { version = "0.6", features = ["cors", "trace"] }
41
+ tracing = "0.1"
42
+ tracing-subscriber = "0.3"
43
+ dotenvy = "0.15"
44
+ "#;
45
+
46
+ GeneratedOutput {
47
+ path: "Cargo.toml".to_string(),
48
+ content: content.to_string(),
49
+ overwrite: true,
50
+ }
51
+ }
52
+
53
+ // ─────────────────────────── src/main.rs ─────────────────────────────────────
54
+
55
+ fn generate_main_rs() -> GeneratedOutput {
56
+ let content = r#"// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT
57
+
58
+ use server::app::AppState;
59
+ use server::db;
60
+ use server::router::create_router;
61
+
62
+ #[tokio::main]
63
+ async fn main() {
64
+ dotenvy::dotenv().ok();
65
+
66
+ tracing_subscriber::fmt::init();
67
+
68
+ let database_url = std::env::var("DATABASE_URL")
69
+ .expect("DATABASE_URL must be set");
70
+
71
+ let pool = db::connect(&database_url).await;
72
+
73
+ let state = AppState { pool };
74
+ let app = create_router().with_state(state);
75
+
76
+ let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
77
+ let addr = format!("0.0.0.0:{}", port);
78
+
79
+ let listener = tokio::net::TcpListener::bind(&addr)
80
+ .await
81
+ .unwrap_or_else(|_| panic!("Failed to bind to {}", addr));
82
+
83
+ tracing::info!("Server listening on {}", addr);
84
+
85
+ axum::serve(listener, app)
86
+ .await
87
+ .expect("Server error");
88
+ }
89
+ "#;
90
+
91
+ GeneratedOutput {
92
+ path: "src/main.rs".to_string(),
93
+ content: content.to_string(),
94
+ overwrite: true,
95
+ }
96
+ }
97
+
98
+ // ─────────────────────────── src/lib.rs ──────────────────────────────────────
99
+
100
+ fn generate_lib_rs() -> GeneratedOutput {
101
+ let content = r#"// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT
102
+
103
+ #[path = "../.typokit/models/mod.rs"]
104
+ pub mod models;
105
+
106
+ #[path = "../.typokit/router.rs"]
107
+ pub mod router;
108
+
109
+ #[path = "../.typokit/db/mod.rs"]
110
+ pub mod db;
111
+
112
+ #[path = "../.typokit/app.rs"]
113
+ pub mod app;
114
+
115
+ #[path = "../.typokit/error.rs"]
116
+ pub mod error;
117
+
118
+ pub mod handlers;
119
+ pub mod services;
120
+ pub mod middleware;
121
+ "#;
122
+
123
+ GeneratedOutput {
124
+ path: "src/lib.rs".to_string(),
125
+ content: content.to_string(),
126
+ overwrite: true,
127
+ }
128
+ }
129
+
130
+ // ─────────────────────────── .typokit/app.rs ─────────────────────────────────
131
+
132
+ fn generate_app_rs() -> GeneratedOutput {
133
+ let content = r#"// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT
134
+
135
+ use sqlx::postgres::PgPool;
136
+
137
+ /// Shared application state passed to all route handlers.
138
+ #[derive(Clone)]
139
+ pub struct AppState {
140
+ pub pool: PgPool,
141
+ }
142
+ "#;
143
+
144
+ GeneratedOutput {
145
+ path: ".typokit/app.rs".to_string(),
146
+ content: content.to_string(),
147
+ overwrite: true,
148
+ }
149
+ }
150
+
151
+ // ─────────────────────────── .typokit/error.rs ───────────────────────────────
152
+
153
+ fn generate_error_rs() -> GeneratedOutput {
154
+ let content = r#"// AUTO-GENERATED by @typokit/transform-native — DO NOT EDIT
155
+
156
+ use axum::{
157
+ http::StatusCode,
158
+ response::{IntoResponse, Response},
159
+ Json,
160
+ };
161
+ use serde_json::json;
162
+
163
+ /// Application error type with HTTP status code mapping.
164
+ ///
165
+ /// Handlers return `Result<T, AppError>` and Axum automatically
166
+ /// converts `AppError` into an HTTP response via `IntoResponse`.
167
+ #[derive(Debug)]
168
+ pub enum AppError {
169
+ NotFound(String),
170
+ BadRequest(String),
171
+ Unauthorized(String),
172
+ Forbidden(String),
173
+ Conflict(String),
174
+ Internal(String),
175
+ }
176
+
177
+ impl IntoResponse for AppError {
178
+ fn into_response(self) -> Response {
179
+ let (status, message) = match self {
180
+ AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
181
+ AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
182
+ AppError::Unauthorized(msg) => (StatusCode::UNAUTHORIZED, msg),
183
+ AppError::Forbidden(msg) => (StatusCode::FORBIDDEN, msg),
184
+ AppError::Conflict(msg) => (StatusCode::CONFLICT, msg),
185
+ AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
186
+ };
187
+
188
+ let body = Json(json!({
189
+ "error": status.as_u16(),
190
+ "message": message,
191
+ }));
192
+
193
+ (status, body).into_response()
194
+ }
195
+ }
196
+
197
+ impl AppError {
198
+ pub fn not_found(msg: impl Into<String>) -> Self {
199
+ Self::NotFound(msg.into())
200
+ }
201
+
202
+ pub fn bad_request(msg: impl Into<String>) -> Self {
203
+ Self::BadRequest(msg.into())
204
+ }
205
+
206
+ pub fn unauthorized(msg: impl Into<String>) -> Self {
207
+ Self::Unauthorized(msg.into())
208
+ }
209
+
210
+ pub fn forbidden(msg: impl Into<String>) -> Self {
211
+ Self::Forbidden(msg.into())
212
+ }
213
+
214
+ pub fn conflict(msg: impl Into<String>) -> Self {
215
+ Self::Conflict(msg.into())
216
+ }
217
+
218
+ pub fn internal(msg: impl Into<String>) -> Self {
219
+ Self::Internal(msg.into())
220
+ }
221
+ }
222
+
223
+ impl std::fmt::Display for AppError {
224
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225
+ match self {
226
+ AppError::NotFound(msg) => write!(f, "Not Found: {}", msg),
227
+ AppError::BadRequest(msg) => write!(f, "Bad Request: {}", msg),
228
+ AppError::Unauthorized(msg) => write!(f, "Unauthorized: {}", msg),
229
+ AppError::Forbidden(msg) => write!(f, "Forbidden: {}", msg),
230
+ AppError::Conflict(msg) => write!(f, "Conflict: {}", msg),
231
+ AppError::Internal(msg) => write!(f, "Internal Server Error: {}", msg),
232
+ }
233
+ }
234
+ }
235
+ "#;
236
+
237
+ GeneratedOutput {
238
+ path: ".typokit/error.rs".to_string(),
239
+ content: content.to_string(),
240
+ overwrite: true,
241
+ }
242
+ }
243
+
244
+ #[cfg(test)]
245
+ mod tests {
246
+ use super::*;
247
+
248
+ #[test]
249
+ fn test_generate_project_produces_five_files() {
250
+ let outputs = generate_project();
251
+ assert_eq!(outputs.len(), 5);
252
+ }
253
+
254
+ // ─── Cargo.toml ─────────────────────────────────────────────────────
255
+
256
+ #[test]
257
+ fn test_cargo_toml_path_and_overwrite() {
258
+ let outputs = generate_project();
259
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
260
+ assert!(cargo.overwrite);
261
+ }
262
+
263
+ #[test]
264
+ fn test_cargo_toml_edition_2024() {
265
+ let outputs = generate_project();
266
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
267
+ assert!(cargo.content.contains("edition = \"2024\""));
268
+ }
269
+
270
+ #[test]
271
+ fn test_cargo_toml_package_metadata() {
272
+ let outputs = generate_project();
273
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
274
+ assert!(cargo.content.contains("[package]"));
275
+ assert!(cargo.content.contains("name = \"server\""));
276
+ assert!(cargo.content.contains("version = \"0.1.0\""));
277
+ }
278
+
279
+ #[test]
280
+ fn test_cargo_toml_axum_dependency() {
281
+ let outputs = generate_project();
282
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
283
+ assert!(cargo.content.contains("axum = \"0.8\""));
284
+ }
285
+
286
+ #[test]
287
+ fn test_cargo_toml_tokio_full() {
288
+ let outputs = generate_project();
289
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
290
+ assert!(cargo.content.contains("tokio = { version = \"1\", features = [\"full\"] }"));
291
+ }
292
+
293
+ #[test]
294
+ fn test_cargo_toml_serde_derive() {
295
+ let outputs = generate_project();
296
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
297
+ assert!(cargo.content.contains("serde = { version = \"1\", features = [\"derive\"] }"));
298
+ assert!(cargo.content.contains("serde_json = \"1\""));
299
+ }
300
+
301
+ #[test]
302
+ fn test_cargo_toml_validator() {
303
+ let outputs = generate_project();
304
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
305
+ assert!(cargo.content.contains("validator = { version = \"0.19\", features = [\"derive\"] }"));
306
+ }
307
+
308
+ #[test]
309
+ fn test_cargo_toml_chrono_serde() {
310
+ let outputs = generate_project();
311
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
312
+ assert!(cargo.content.contains("chrono = { version = \"0.4\", features = [\"serde\"] }"));
313
+ }
314
+
315
+ #[test]
316
+ fn test_cargo_toml_uuid() {
317
+ let outputs = generate_project();
318
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
319
+ assert!(cargo.content.contains("uuid = \"1\""));
320
+ }
321
+
322
+ #[test]
323
+ fn test_cargo_toml_sqlx_postgres() {
324
+ let outputs = generate_project();
325
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
326
+ assert!(cargo.content.contains("sqlx = { version = \"0.8\", features = [\"runtime-tokio\", \"postgres\"] }"));
327
+ }
328
+
329
+ #[test]
330
+ fn test_cargo_toml_tower_http() {
331
+ let outputs = generate_project();
332
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
333
+ assert!(cargo.content.contains("tower-http = { version = \"0.6\", features = [\"cors\", \"trace\"] }"));
334
+ }
335
+
336
+ #[test]
337
+ fn test_cargo_toml_tracing() {
338
+ let outputs = generate_project();
339
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
340
+ assert!(cargo.content.contains("tracing = \"0.1\""));
341
+ assert!(cargo.content.contains("tracing-subscriber = \"0.3\""));
342
+ }
343
+
344
+ #[test]
345
+ fn test_cargo_toml_dotenvy() {
346
+ let outputs = generate_project();
347
+ let cargo = outputs.iter().find(|o| o.path == "Cargo.toml").unwrap();
348
+ assert!(cargo.content.contains("dotenvy = \"0.15\""));
349
+ }
350
+
351
+ // ─── src/main.rs ────────────────────────────────────────────────────
352
+
353
+ #[test]
354
+ fn test_main_rs_path_and_overwrite() {
355
+ let outputs = generate_project();
356
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
357
+ assert!(main.overwrite);
358
+ }
359
+
360
+ #[test]
361
+ fn test_main_rs_tokio_main() {
362
+ let outputs = generate_project();
363
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
364
+ assert!(main.content.contains("#[tokio::main]"));
365
+ assert!(main.content.contains("async fn main()"));
366
+ }
367
+
368
+ #[test]
369
+ fn test_main_rs_dotenvy() {
370
+ let outputs = generate_project();
371
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
372
+ assert!(main.content.contains("dotenvy::dotenv().ok()"));
373
+ }
374
+
375
+ #[test]
376
+ fn test_main_rs_database_url() {
377
+ let outputs = generate_project();
378
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
379
+ assert!(main.content.contains("DATABASE_URL"));
380
+ assert!(main.content.contains("db::connect"));
381
+ }
382
+
383
+ #[test]
384
+ fn test_main_rs_tracing_init() {
385
+ let outputs = generate_project();
386
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
387
+ assert!(main.content.contains("tracing_subscriber::fmt::init()"));
388
+ }
389
+
390
+ #[test]
391
+ fn test_main_rs_router_creation() {
392
+ let outputs = generate_project();
393
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
394
+ assert!(main.content.contains("create_router()"));
395
+ assert!(main.content.contains(".with_state(state)"));
396
+ }
397
+
398
+ #[test]
399
+ fn test_main_rs_axum_serve() {
400
+ let outputs = generate_project();
401
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
402
+ assert!(main.content.contains("axum::serve(listener, app)"));
403
+ assert!(main.content.contains(r#"std::env::var("PORT")"#));
404
+ assert!(main.content.contains(r#""3000""#));
405
+ assert!(main.content.contains(r#"format!("0.0.0.0:{}", port)"#));
406
+ }
407
+
408
+ #[test]
409
+ fn test_main_rs_app_state() {
410
+ let outputs = generate_project();
411
+ let main = outputs.iter().find(|o| o.path == "src/main.rs").unwrap();
412
+ assert!(main.content.contains("AppState { pool }"));
413
+ }
414
+
415
+ // ─── src/lib.rs ─────────────────────────────────────────────────────
416
+
417
+ #[test]
418
+ fn test_lib_rs_path_and_overwrite() {
419
+ let outputs = generate_project();
420
+ let lib = outputs.iter().find(|o| o.path == "src/lib.rs").unwrap();
421
+ assert!(lib.overwrite);
422
+ }
423
+
424
+ #[test]
425
+ fn test_lib_rs_path_attributes_for_typokit_modules() {
426
+ let outputs = generate_project();
427
+ let lib = outputs.iter().find(|o| o.path == "src/lib.rs").unwrap();
428
+ assert!(lib.content.contains("#[path = \"../.typokit/models/mod.rs\"]"));
429
+ assert!(lib.content.contains("pub mod models;"));
430
+ assert!(lib.content.contains("#[path = \"../.typokit/router.rs\"]"));
431
+ assert!(lib.content.contains("pub mod router;"));
432
+ assert!(lib.content.contains("#[path = \"../.typokit/db/mod.rs\"]"));
433
+ assert!(lib.content.contains("pub mod db;"));
434
+ assert!(lib.content.contains("#[path = \"../.typokit/app.rs\"]"));
435
+ assert!(lib.content.contains("pub mod app;"));
436
+ assert!(lib.content.contains("#[path = \"../.typokit/error.rs\"]"));
437
+ assert!(lib.content.contains("pub mod error;"));
438
+ }
439
+
440
+ #[test]
441
+ fn test_lib_rs_user_code_modules() {
442
+ let outputs = generate_project();
443
+ let lib = outputs.iter().find(|o| o.path == "src/lib.rs").unwrap();
444
+ assert!(lib.content.contains("pub mod handlers;"));
445
+ assert!(lib.content.contains("pub mod services;"));
446
+ assert!(lib.content.contains("pub mod middleware;"));
447
+ }
448
+
449
+ // ─── .typokit/app.rs ───────────────────────────────────────────────
450
+
451
+ #[test]
452
+ fn test_app_rs_path_and_overwrite() {
453
+ let outputs = generate_project();
454
+ let app = outputs.iter().find(|o| o.path == ".typokit/app.rs").unwrap();
455
+ assert!(app.overwrite);
456
+ }
457
+
458
+ #[test]
459
+ fn test_app_rs_derive_clone() {
460
+ let outputs = generate_project();
461
+ let app = outputs.iter().find(|o| o.path == ".typokit/app.rs").unwrap();
462
+ assert!(app.content.contains("#[derive(Clone)]"));
463
+ }
464
+
465
+ #[test]
466
+ fn test_app_rs_app_state_struct() {
467
+ let outputs = generate_project();
468
+ let app = outputs.iter().find(|o| o.path == ".typokit/app.rs").unwrap();
469
+ assert!(app.content.contains("pub struct AppState"));
470
+ assert!(app.content.contains("pub pool: PgPool"));
471
+ }
472
+
473
+ #[test]
474
+ fn test_app_rs_imports() {
475
+ let outputs = generate_project();
476
+ let app = outputs.iter().find(|o| o.path == ".typokit/app.rs").unwrap();
477
+ assert!(app.content.contains("use sqlx::postgres::PgPool;"));
478
+ }
479
+
480
+ // ─── .typokit/error.rs ─────────────────────────────────────────────
481
+
482
+ #[test]
483
+ fn test_error_rs_path_and_overwrite() {
484
+ let outputs = generate_project();
485
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
486
+ assert!(err.overwrite);
487
+ }
488
+
489
+ #[test]
490
+ fn test_error_rs_enum_variants() {
491
+ let outputs = generate_project();
492
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
493
+ assert!(err.content.contains("NotFound(String)"));
494
+ assert!(err.content.contains("BadRequest(String)"));
495
+ assert!(err.content.contains("Unauthorized(String)"));
496
+ assert!(err.content.contains("Forbidden(String)"));
497
+ assert!(err.content.contains("Conflict(String)"));
498
+ assert!(err.content.contains("Internal(String)"));
499
+ }
500
+
501
+ #[test]
502
+ fn test_error_rs_into_response_impl() {
503
+ let outputs = generate_project();
504
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
505
+ assert!(err.content.contains("impl IntoResponse for AppError"));
506
+ assert!(err.content.contains("fn into_response(self) -> Response"));
507
+ }
508
+
509
+ #[test]
510
+ fn test_error_rs_status_code_mapping() {
511
+ let outputs = generate_project();
512
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
513
+ assert!(err.content.contains("StatusCode::NOT_FOUND"));
514
+ assert!(err.content.contains("StatusCode::BAD_REQUEST"));
515
+ assert!(err.content.contains("StatusCode::UNAUTHORIZED"));
516
+ assert!(err.content.contains("StatusCode::FORBIDDEN"));
517
+ assert!(err.content.contains("StatusCode::CONFLICT"));
518
+ assert!(err.content.contains("StatusCode::INTERNAL_SERVER_ERROR"));
519
+ }
520
+
521
+ #[test]
522
+ fn test_error_rs_convenience_methods() {
523
+ let outputs = generate_project();
524
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
525
+ assert!(err.content.contains("pub fn not_found("));
526
+ assert!(err.content.contains("pub fn bad_request("));
527
+ assert!(err.content.contains("pub fn unauthorized("));
528
+ assert!(err.content.contains("pub fn forbidden("));
529
+ assert!(err.content.contains("pub fn conflict("));
530
+ assert!(err.content.contains("pub fn internal("));
531
+ }
532
+
533
+ #[test]
534
+ fn test_error_rs_imports() {
535
+ let outputs = generate_project();
536
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
537
+ assert!(err.content.contains("use axum::"));
538
+ assert!(err.content.contains("StatusCode"));
539
+ assert!(err.content.contains("IntoResponse"));
540
+ assert!(err.content.contains("Json"));
541
+ assert!(err.content.contains("use serde_json::json;"));
542
+ }
543
+
544
+ #[test]
545
+ fn test_error_rs_display_impl() {
546
+ let outputs = generate_project();
547
+ let err = outputs.iter().find(|o| o.path == ".typokit/error.rs").unwrap();
548
+ assert!(err.content.contains("impl std::fmt::Display for AppError"));
549
+ }
550
+
551
+ // ─── all files overwrite: true ──────────────────────────────────────
552
+
553
+ #[test]
554
+ fn test_all_outputs_overwrite_true() {
555
+ let outputs = generate_project();
556
+ for output in &outputs {
557
+ assert!(
558
+ output.overwrite,
559
+ "Expected overwrite: true for {}, got false",
560
+ output.path
561
+ );
562
+ }
563
+ }
564
+
565
+ // ─── deterministic output ───────────────────────────────────────────
566
+
567
+ #[test]
568
+ fn test_deterministic_output() {
569
+ let outputs1 = generate_project();
570
+ let outputs2 = generate_project();
571
+
572
+ assert_eq!(outputs1.len(), outputs2.len());
573
+ for (a, b) in outputs1.iter().zip(outputs2.iter()) {
574
+ assert_eq!(a.path, b.path);
575
+ assert_eq!(a.content, b.content);
576
+ assert_eq!(a.overwrite, b.overwrite);
577
+ }
578
+ }
579
+
580
+ // ─── auto-generated marker ──────────────────────────────────────────
581
+
582
+ #[test]
583
+ fn test_all_files_have_auto_generated_marker() {
584
+ let outputs = generate_project();
585
+ for output in &outputs {
586
+ assert!(
587
+ output.content.contains("AUTO-GENERATED by @typokit/transform-native"),
588
+ "Missing auto-generated marker in {}",
589
+ output.path
590
+ );
591
+ }
592
+ }
593
+ }