@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 +2 -2
- package/src/cli/commands/dev.ts +10 -29
- package/src/cli/commands/run.ts +3 -0
- package/src/config/loader.ts +5 -0
- package/src/config/schema.ts +18 -0
- package/src/utils/bundler.ts +26 -8
- package/src/utils/metro-config.ts +64 -16
- package/templates/metro.config.js +3 -7
- package/templates/src/_routes/home.tsx +1 -1
- package/templates/src/navigation/router.tsx +1 -1
- package/templates/src/routes/home.tsx +0 -94
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teardown/cli",
|
|
3
|
-
"version": "2.0.
|
|
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.
|
|
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"
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
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;
|
package/src/cli/commands/run.ts
CHANGED
|
@@ -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
|
package/src/config/loader.ts
CHANGED
package/src/config/schema.ts
CHANGED
|
@@ -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>;
|
package/src/utils/bundler.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
return
|
|
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
|
-
|
|
33
|
-
|
|
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/
|
|
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
|
-
});
|