@teardown/cli 2.0.74 → 2.0.75

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teardown/cli",
3
- "version": "2.0.74",
3
+ "version": "2.0.75",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -76,7 +76,7 @@
76
76
  },
77
77
  "devDependencies": {
78
78
  "@biomejs/biome": "2.3.11",
79
- "@teardown/tsconfig": "2.0.74",
79
+ "@teardown/tsconfig": "2.0.75",
80
80
  "@types/bun": "1.3.5",
81
81
  "@types/ejs": "^3.1.5",
82
82
  "typescript": "5.9.3"
@@ -2,11 +2,11 @@
2
2
  * Dev command - starts the Metro bundler
3
3
  */
4
4
 
5
- import { spawn } from "node:child_process";
6
5
  import { existsSync } from "node:fs";
7
6
  import { join, resolve } from "node:path";
8
7
  import chalk from "chalk";
9
8
  import { Command } from "commander";
9
+ import { attachBundlerToForeground, startBundlerBackground } from "../../utils/bundler";
10
10
  import { getNavigationConfig } from "../../utils/metro-config";
11
11
 
12
12
  /**
@@ -86,37 +86,23 @@ export function createDevCommand(): Command {
86
86
 
87
87
  console.log(chalk.blue("Starting Metro bundler...\n"));
88
88
 
89
- const args = ["react-native", "start", "--port", options.port];
90
-
91
- if (options.resetCache) {
92
- args.push("--reset-cache");
93
- }
94
-
95
- if (options.verbose) {
96
- args.push("--verbose");
97
- }
98
-
99
- const proc = spawn("npx", args, {
100
- stdio: "inherit",
101
- shell: true,
89
+ const metroProcess = startBundlerBackground({
90
+ port: options.port,
91
+ resetCache: options.resetCache,
92
+ verbose: options.verbose,
102
93
  cwd: projectRoot,
103
- env: {
104
- ...process.env,
105
- // Ensure proper encoding for CocoaPods compatibility
106
- LANG: "en_US.UTF-8",
107
- },
108
94
  });
109
95
 
110
- proc.on("error", (error) => {
111
- console.error(chalk.red(`Failed to start Metro: ${error.message}`));
112
- process.exit(1);
113
- });
96
+ // Handle cleanup on SIGINT/SIGTERM
97
+ const cleanup = () => {
98
+ metroProcess.kill("SIGTERM");
99
+ process.exit(0);
100
+ };
101
+ process.on("SIGINT", cleanup);
102
+ process.on("SIGTERM", cleanup);
114
103
 
115
- proc.on("close", (code) => {
116
- if (code !== 0 && code !== null) {
117
- process.exit(code);
118
- }
119
- });
104
+ // Attach to foreground with interactive input (r, j, d, etc.)
105
+ attachBundlerToForeground(metroProcess);
120
106
  });
121
107
 
122
108
  return dev;
@@ -358,6 +358,11 @@ export default defineConfig({
358
358
  backgroundColor: '#FFFFFF',
359
359
  },
360
360
 
361
+ navigation: {
362
+ routesDir: './src/_routes',
363
+ generatedDir: './.teardown',
364
+ },
365
+
361
366
  plugins: [
362
367
  // Add your plugins here
363
368
  ],
@@ -108,6 +108,20 @@ export const PluginEntrySchema = z.union([
108
108
  z.tuple([z.any()]),
109
109
  ]);
110
110
 
111
+ /**
112
+ * Navigation configuration schema
113
+ */
114
+ export const NavigationConfigSchema = z.object({
115
+ /** Path to routes directory relative to project root */
116
+ routesDir: z.string().optional().default("./src/_routes"),
117
+
118
+ /** Path for generated type files */
119
+ generatedDir: z.string().optional().default("./.teardown"),
120
+
121
+ /** Auto-populate new route files with template content */
122
+ autoTemplate: z.boolean().optional().default(true),
123
+ });
124
+
111
125
  /**
112
126
  * Main Teardown configuration schema
113
127
  */
@@ -150,6 +164,9 @@ export const TeardownConfigSchema = z.object({
150
164
 
151
165
  /** Environment variables to inject */
152
166
  env: z.record(z.string(), z.string()).optional(),
167
+
168
+ /** Navigation configuration */
169
+ navigation: NavigationConfigSchema.optional(),
153
170
  });
154
171
 
155
172
  /**
@@ -241,3 +258,4 @@ export type TeardownConfig = z.infer<typeof TeardownConfigSchema>;
241
258
  export type iOSConfig = z.infer<typeof iOSConfigSchema>;
242
259
  export type AndroidConfig = z.infer<typeof AndroidConfigSchema>;
243
260
  export type SplashConfig = z.infer<typeof SplashConfigSchema>;
261
+ export type NavigationConfig = z.infer<typeof NavigationConfigSchema>;
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Metro Config Parser
3
3
  *
4
- * Extracts navigation configuration from metro.config.js
4
+ * Extracts navigation configuration from teardown.config.ts or metro.config.js
5
5
  */
6
6
 
7
7
  import { existsSync, readFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
 
10
10
  /**
11
- * Navigation options extracted from metro.config.js
11
+ * Navigation options extracted from config files
12
12
  */
13
13
  export interface NavigationConfig {
14
14
  routesDir: string;
@@ -19,10 +19,20 @@ export interface NavigationConfig {
19
19
  * Default navigation configuration
20
20
  */
21
21
  const DEFAULT_CONFIG: NavigationConfig = {
22
- routesDir: "./src/routes",
22
+ routesDir: "./src/_routes",
23
23
  generatedDir: "./.teardown",
24
24
  };
25
25
 
26
+ /**
27
+ * Config file names to search for teardown config (in order of priority)
28
+ */
29
+ const TEARDOWN_CONFIG_FILES = [
30
+ "teardown.config.ts",
31
+ "teardown.config.js",
32
+ "teardown.config.mjs",
33
+ "launchpad.config.ts",
34
+ ];
35
+
26
36
  /**
27
37
  * Finds metro.config.js in the project
28
38
  */
@@ -78,14 +88,61 @@ export function parseNavigationConfig(projectRoot: string): NavigationConfig | n
78
88
  }
79
89
  }
80
90
 
91
+ /**
92
+ * Parses teardown.config.ts to extract navigation options
93
+ *
94
+ * This uses simple regex parsing since we can't safely evaluate the TS file at runtime.
95
+ */
96
+ function parseTeardownConfig(projectRoot: string): NavigationConfig | null {
97
+ for (const fileName of TEARDOWN_CONFIG_FILES) {
98
+ const configPath = join(projectRoot, fileName);
99
+ if (existsSync(configPath)) {
100
+ try {
101
+ const content = readFileSync(configPath, "utf-8");
102
+
103
+ // Check if navigation config exists
104
+ if (!content.includes("navigation:") && !content.includes("navigation :")) {
105
+ continue;
106
+ }
107
+
108
+ // Extract routesDir using regex
109
+ const routesDirMatch = content.match(/routesDir:\s*['"]([^'"]+)['"]/);
110
+ const generatedDirMatch = content.match(/generatedDir:\s*['"]([^'"]+)['"]/);
111
+
112
+ // Only return if we found at least one navigation property
113
+ if (routesDirMatch || generatedDirMatch) {
114
+ return {
115
+ routesDir: routesDirMatch?.[1] ?? DEFAULT_CONFIG.routesDir,
116
+ generatedDir: generatedDirMatch?.[1] ?? DEFAULT_CONFIG.generatedDir,
117
+ };
118
+ }
119
+ } catch {
120
+ // Ignore read errors and try next config file
121
+ }
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+
81
127
  /**
82
128
  * Gets navigation config for a project, with fallbacks
129
+ *
130
+ * Priority:
131
+ * 1. teardown.config.ts navigation section
132
+ * 2. metro.config.js withTeardownNavigation options
133
+ * 3. Default config if routes directory exists
83
134
  */
84
135
  export function getNavigationConfig(projectRoot: string): NavigationConfig | null {
85
- // Try to parse from metro.config.js first
86
- const parsedConfig = parseNavigationConfig(projectRoot);
87
- if (parsedConfig) {
88
- return parsedConfig;
136
+ // Try to parse from teardown.config.ts first (single source of truth)
137
+ const teardownConfig = parseTeardownConfig(projectRoot);
138
+ if (teardownConfig) {
139
+ return teardownConfig;
140
+ }
141
+
142
+ // Fallback: parse from metro.config.js (backwards compatibility)
143
+ const metroConfig = parseNavigationConfig(projectRoot);
144
+ if (metroConfig) {
145
+ return metroConfig;
89
146
  }
90
147
 
91
148
  // Fallback: check if default routes dir exists
@@ -94,14 +151,5 @@ export function getNavigationConfig(projectRoot: string): NavigationConfig | nul
94
151
  return DEFAULT_CONFIG;
95
152
  }
96
153
 
97
- // Try alternate common location
98
- const altRoutesPath = join(projectRoot, "src/_routes");
99
- if (existsSync(altRoutesPath)) {
100
- return {
101
- routesDir: "./src/_routes",
102
- generatedDir: "./.teardown",
103
- };
104
- }
105
-
106
154
  return null;
107
155
  }
@@ -11,12 +11,11 @@ const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
11
11
  * - Monorepo/workspace detection and watch folder configuration
12
12
  * - Bun-specific handling (.bun directory blocking)
13
13
  *
14
- *
15
14
  * withTeardownNavigation() automatically handles:
16
15
  * - Type-safe route generation from file-based routes
17
16
  * - Deep linking configuration generation
18
17
  * - Hot-reload support via file watching in development
19
- *
18
+ * - Reads routesDir/generatedDir from teardown.config.ts navigation section
20
19
  *
21
20
  * withUniwindConfig() enables:
22
21
  * - Tailwind CSS 4 support for React Native
@@ -29,11 +28,8 @@ const config = {};
29
28
 
30
29
  const teardownConfig = withTeardown(mergeConfig(getDefaultConfig(__dirname), config));
31
30
 
32
- const navigationConfig = withTeardownNavigation(teardownConfig, {
33
- routesDir: "./src/_routes",
34
- generatedDir: "./.teardown",
35
- verbose: false,
36
- });
31
+ // Navigation config is read from teardown.config.ts navigation section
32
+ const navigationConfig = withTeardownNavigation(teardownConfig);
37
33
 
38
34
  module.exports = withUniwindConfig(navigationConfig, {
39
35
  cssEntryFile: "./src/global.css",
@@ -51,7 +51,7 @@ function HomeScreen() {
51
51
  <Card.Title>Getting Started</Card.Title>
52
52
  <Card.Description>
53
53
  1. Explore the components in src/components/ui/{"\n"}
54
- 2. Add new routes in src/routes/{"\n"}
54
+ 2. Add new routes in src/_routes/{"\n"}
55
55
  3. Customize the theme in global.css{"\n"}
56
56
  4. Build your app!
57
57
  </Card.Description>
@@ -1,94 +0,0 @@
1
- /**
2
- * Home Screen
3
- *
4
- * The main landing screen of the app.
5
- */
6
-
7
- import { defineScreen } from "@teardown/navigation/primitives";
8
- import type React from "react";
9
- import { ScrollView, Text, View } from "react-native";
10
- import { Button } from "@/components/ui/button";
11
- import { Card } from "@/components/ui/card";
12
- import { APP_NAME } from "@/core/constants";
13
-
14
- function HomeScreen(): React.JSX.Element {
15
- return (
16
- <ScrollView className="flex-1 bg-background" contentContainerClassName="p-6 gap-6">
17
- {/* Hero Section */}
18
- <View className="items-center gap-4 py-8">
19
- <View className="h-20 w-20 items-center justify-center rounded-2xl bg-primary">
20
- <Text className="text-3xl font-bold text-primary-foreground">T</Text>
21
- </View>
22
- <View className="items-center gap-2">
23
- <Text className="text-2xl font-bold text-foreground text-center">Welcome to {APP_NAME}</Text>
24
- <Text className="text-base text-muted-foreground text-center">Your React Native app is ready to go</Text>
25
- </View>
26
- </View>
27
-
28
- {/* Quick Actions */}
29
- <Card>
30
- <Card.Body className="gap-3">
31
- <Card.Title>Quick Actions</Card.Title>
32
- <Button onPress={() => console.log("Primary action")} fullWidth>
33
- Get Started
34
- </Button>
35
- <Button variant="bordered" onPress={() => console.log("Secondary action")} fullWidth>
36
- Learn More
37
- </Button>
38
- </Card.Body>
39
- </Card>
40
-
41
- {/* Features */}
42
- <View className="gap-4">
43
- <Text className="text-lg font-semibold text-foreground">Features</Text>
44
- <FeatureCard icon="🔒" title="Type-Safe Navigation" description="Routes are fully typed with TypeScript" />
45
- <FeatureCard icon="📁" title="File-Based Routing" description="Routes generated from file structure" />
46
- <FeatureCard icon="🎨" title="HeroUI Components" description="Beautiful pre-built UI components" />
47
- </View>
48
-
49
- {/* Getting Started */}
50
- <Card className="bg-primary/5 border-primary/20">
51
- <Card.Body className="py-4">
52
- <Card.Title>Getting Started</Card.Title>
53
- <Card.Description>
54
- 1. Explore the components in src/components/ui/{"\n"}
55
- 2. Add new routes in src/routes/{"\n"}
56
- 3. Customize the theme in global.css{"\n"}
57
- 4. Build your app!
58
- </Card.Description>
59
- </Card.Body>
60
- </Card>
61
- </ScrollView>
62
- );
63
- }
64
-
65
- function FeatureCard({
66
- icon,
67
- title,
68
- description,
69
- }: {
70
- icon: string;
71
- title: string;
72
- description: string;
73
- }): React.JSX.Element {
74
- return (
75
- <Card>
76
- <Card.Body className="flex-row items-center gap-4">
77
- <View className="h-12 w-12 items-center justify-center rounded-xl bg-muted">
78
- <Text className="text-2xl">{icon}</Text>
79
- </View>
80
- <View className="flex-1">
81
- <Card.Title>{title}</Card.Title>
82
- <Card.Description>{description}</Card.Description>
83
- </View>
84
- </Card.Body>
85
- </Card>
86
- );
87
- }
88
-
89
- export default defineScreen({
90
- component: HomeScreen,
91
- options: {
92
- title: "Home",
93
- },
94
- });