@teardown/cli 2.0.74 → 2.0.76

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.76",
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.76",
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 { startBundlerBackground } from "../../utils/bundler";
10
10
  import { getNavigationConfig } from "../../utils/metro-config";
11
11
 
12
12
  /**
@@ -86,37 +86,18 @@ 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
+ // Start Metro with interactive mode enabled - inherits stdio directly
90
+ // so Metro receives the TTY and keyboard shortcuts (r, j, d) work
91
+ startBundlerBackground({
92
+ port: options.port,
93
+ resetCache: options.resetCache,
94
+ verbose: options.verbose,
102
95
  cwd: projectRoot,
103
- env: {
104
- ...process.env,
105
- // Ensure proper encoding for CocoaPods compatibility
106
- LANG: "en_US.UTF-8",
107
- },
96
+ interactive: true,
108
97
  });
109
98
 
110
- proc.on("error", (error) => {
111
- console.error(chalk.red(`Failed to start Metro: ${error.message}`));
112
- process.exit(1);
113
- });
114
-
115
- proc.on("close", (code) => {
116
- if (code !== 0 && code !== null) {
117
- process.exit(code);
118
- }
119
- });
99
+ // With inherited stdio, the process handles its own signals
100
+ // We just need to keep the parent process alive
120
101
  });
121
102
 
122
103
  return dev;
@@ -409,10 +409,13 @@ async function startMetroAndWait(port: string, _platform: "ios" | "android"): Pr
409
409
 
410
410
  console.log(chalk.blue(`\nStarting Metro bundler on port ${port}...`));
411
411
 
412
+ // Start bundler in non-interactive mode (CI=true suppresses warning)
413
+ // We'll attach to foreground later after build completes
412
414
  const metroProcess = startBundlerBackground({
413
415
  port,
414
416
  resetCache: true,
415
417
  cwd: process.cwd(),
418
+ interactive: false,
416
419
  });
417
420
 
418
421
  // Wait for bundler to be ready with progress updates
@@ -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>;
@@ -47,6 +47,8 @@ export interface StartBundlerOptions {
47
47
  cwd?: string;
48
48
  /** Whether to enable verbose logging */
49
49
  verbose?: boolean;
50
+ /** Whether to run in interactive mode (inherit TTY for keyboard shortcuts) */
51
+ interactive?: boolean;
50
52
  }
51
53
 
52
54
  /**
@@ -70,7 +72,13 @@ export interface StartBundlerOptions {
70
72
  * ```
71
73
  */
72
74
  export function startBundlerBackground(options: StartBundlerOptions = {}): ChildProcess {
73
- const { port = DEFAULT_BUNDLER_PORT, resetCache = false, cwd = process.cwd(), verbose = false } = options;
75
+ const {
76
+ port = DEFAULT_BUNDLER_PORT,
77
+ resetCache = false,
78
+ cwd = process.cwd(),
79
+ verbose = false,
80
+ interactive = false,
81
+ } = options;
74
82
 
75
83
  const args = ["react-native", "start", "--port", String(port)];
76
84
 
@@ -82,17 +90,27 @@ export function startBundlerBackground(options: StartBundlerOptions = {}): Child
82
90
  args.push("--verbose");
83
91
  }
84
92
 
93
+ // Build environment variables
94
+ const env: NodeJS.ProcessEnv = {
95
+ ...process.env,
96
+ LANG: "en_US.UTF-8",
97
+ // Force colors in Metro output
98
+ FORCE_COLOR: "1",
99
+ };
100
+
101
+ // When not interactive, set CI=true to suppress "Interactive mode is not supported" warning
102
+ if (!interactive) {
103
+ env.CI = "true";
104
+ }
105
+
85
106
  const proc = spawn("npx", args, {
86
107
  cwd,
87
108
  shell: true,
88
- stdio: ["pipe", "pipe", "pipe"],
109
+ // When interactive, inherit stdio to pass TTY directly to Metro for keyboard shortcuts
110
+ // When not interactive, use pipes so we can capture output and attach later
111
+ stdio: interactive ? "inherit" : ["pipe", "pipe", "pipe"],
89
112
  detached: false,
90
- env: {
91
- ...process.env,
92
- LANG: "en_US.UTF-8",
93
- // Force colors in Metro output
94
- FORCE_COLOR: "1",
95
- },
113
+ env,
96
114
  });
97
115
 
98
116
  // Handle process errors
@@ -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>
@@ -6,8 +6,8 @@
6
6
  */
7
7
 
8
8
  import { createTeardownRouter } from "@teardown/navigation";
9
+ import { defaultPrefixes, generatedLinkingConfig } from "../../.teardown/linking.generated";
9
10
  import { routeTree } from "../../.teardown/routeTree.generated";
10
- import { generatedLinkingConfig, defaultPrefixes } from "../../.teardown/linking.generated";
11
11
 
12
12
  /**
13
13
  * Theme colors for navigation styling
@@ -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
- });