@teardown/cli 1.2.38 → 2.0.41

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 (182) hide show
  1. package/bin/teardown.js +11 -1
  2. package/package.json +77 -57
  3. package/src/cli/commands/init.ts +254 -0
  4. package/src/cli/commands/plugins.ts +93 -0
  5. package/src/cli/commands/prebuild.ts +168 -0
  6. package/src/cli/commands/run.ts +727 -0
  7. package/src/cli/commands/start.ts +87 -0
  8. package/src/cli/commands/validate.ts +62 -0
  9. package/src/cli/index.ts +59 -0
  10. package/src/config/index.ts +45 -0
  11. package/src/config/loader.ts +366 -0
  12. package/src/config/schema.ts +235 -0
  13. package/src/config/types.ts +322 -0
  14. package/src/index.ts +177 -0
  15. package/src/pipeline/cache.ts +179 -0
  16. package/src/pipeline/index.ts +10 -0
  17. package/src/pipeline/stages.ts +692 -0
  18. package/src/plugins/base.ts +370 -0
  19. package/src/plugins/capabilities/biometrics.ts +64 -0
  20. package/src/plugins/capabilities/bluetooth.ts +86 -0
  21. package/src/plugins/capabilities/calendar.ts +57 -0
  22. package/src/plugins/capabilities/camera.ts +77 -0
  23. package/src/plugins/capabilities/contacts.ts +57 -0
  24. package/src/plugins/capabilities/deep-linking.ts +124 -0
  25. package/src/plugins/capabilities/firebase.ts +138 -0
  26. package/src/plugins/capabilities/index.ts +96 -0
  27. package/src/plugins/capabilities/location.ts +87 -0
  28. package/src/plugins/capabilities/photo-library.ts +80 -0
  29. package/src/plugins/capabilities/push-notifications.ts +98 -0
  30. package/src/plugins/capabilities/sign-in-with-apple.ts +53 -0
  31. package/src/plugins/context.ts +220 -0
  32. package/src/plugins/index.ts +26 -0
  33. package/src/plugins/resolver.ts +321 -0
  34. package/src/templates/generator.ts +507 -0
  35. package/src/templates/index.ts +9 -0
  36. package/src/templates/paths.ts +25 -0
  37. package/src/transformers/android/gradle.ts +400 -0
  38. package/src/transformers/android/index.ts +19 -0
  39. package/src/transformers/android/manifest.ts +506 -0
  40. package/src/transformers/index.ts +39 -0
  41. package/src/transformers/ios/entitlements.ts +283 -0
  42. package/src/transformers/ios/index.ts +10 -0
  43. package/src/transformers/ios/pbxproj.ts +267 -0
  44. package/src/transformers/ios/plist.ts +198 -0
  45. package/src/utils/fs.ts +429 -0
  46. package/src/utils/index.ts +21 -0
  47. package/src/utils/logger.ts +203 -0
  48. package/templates/.gitignore +63 -0
  49. package/templates/Gemfile +3 -0
  50. package/templates/android/app/build.gradle.kts +97 -0
  51. package/templates/android/app/proguard-rules.pro +10 -0
  52. package/templates/android/app/src/main/AndroidManifest.xml +26 -0
  53. package/templates/android/app/src/main/java/com/appname/MainActivity.kt +22 -0
  54. package/templates/android/app/src/main/java/com/appname/MainApplication.kt +44 -0
  55. package/templates/android/app/src/main/res/values/strings.xml +3 -0
  56. package/templates/android/app/src/main/res/values/styles.xml +7 -0
  57. package/templates/android/build.gradle.kts +44 -0
  58. package/templates/android/gradle.properties +39 -0
  59. package/templates/android/settings.gradle.kts +12 -0
  60. package/templates/babel.config.js +15 -0
  61. package/templates/index.js +7 -0
  62. package/templates/ios/.xcode.env +11 -0
  63. package/templates/ios/AppName/AppDelegate.swift +25 -0
  64. package/templates/ios/AppName/AppName-Bridging-Header.h +4 -0
  65. package/templates/ios/AppName/AppName.entitlements +6 -0
  66. package/templates/ios/AppName/Images.xcassets/AppIcon.appiconset/Contents.json +35 -0
  67. package/templates/ios/AppName/Images.xcassets/Contents.json +6 -0
  68. package/templates/ios/AppName/Info.plist +49 -0
  69. package/templates/ios/AppName/LaunchScreen.storyboard +38 -0
  70. package/templates/ios/AppName.xcodeproj/project.pbxproj +402 -0
  71. package/templates/ios/AppName.xcodeproj/xcshareddata/xcschemes/AppName.xcscheme +78 -0
  72. package/templates/ios/Podfile +35 -0
  73. package/templates/metro.config.js +41 -0
  74. package/templates/package.json +57 -0
  75. package/templates/react-native.config.js +8 -0
  76. package/templates/src/app/index.tsx +34 -0
  77. package/templates/src/assets/fonts/.gitkeep +1 -0
  78. package/templates/src/assets/images/.gitkeep +1 -0
  79. package/templates/src/components/ui/accordion.tsx +114 -0
  80. package/templates/src/components/ui/avatar.tsx +75 -0
  81. package/templates/src/components/ui/button.tsx +93 -0
  82. package/templates/src/components/ui/card.tsx +120 -0
  83. package/templates/src/components/ui/checkbox.tsx +133 -0
  84. package/templates/src/components/ui/chip.tsx +95 -0
  85. package/templates/src/components/ui/dialog.tsx +134 -0
  86. package/templates/src/components/ui/divider.tsx +67 -0
  87. package/templates/src/components/ui/error-view.tsx +82 -0
  88. package/templates/src/components/ui/form-field.tsx +101 -0
  89. package/templates/src/components/ui/index.ts +100 -0
  90. package/templates/src/components/ui/popover.tsx +92 -0
  91. package/templates/src/components/ui/pressable-feedback.tsx +88 -0
  92. package/templates/src/components/ui/radio-group.tsx +153 -0
  93. package/templates/src/components/ui/scroll-shadow.tsx +108 -0
  94. package/templates/src/components/ui/select.tsx +165 -0
  95. package/templates/src/components/ui/skeleton-group.tsx +97 -0
  96. package/templates/src/components/ui/skeleton.tsx +87 -0
  97. package/templates/src/components/ui/spinner.tsx +87 -0
  98. package/templates/src/components/ui/surface.tsx +95 -0
  99. package/templates/src/components/ui/switch.tsx +124 -0
  100. package/templates/src/components/ui/tabs.tsx +154 -0
  101. package/templates/src/components/ui/text-field.tsx +106 -0
  102. package/templates/src/components/ui/toast.tsx +129 -0
  103. package/templates/src/contexts/.gitkeep +2 -0
  104. package/templates/src/core/clients/api/api.client.ts +113 -0
  105. package/templates/src/core/clients/api/index.ts +1 -0
  106. package/templates/src/core/clients/storage/index.ts +1 -0
  107. package/templates/src/core/clients/storage/storage.client.ts +121 -0
  108. package/templates/src/core/constants/index.ts +19 -0
  109. package/templates/src/core/core.ts +40 -0
  110. package/templates/src/core/index.ts +10 -0
  111. package/templates/src/global.css +87 -0
  112. package/templates/src/hooks/index.ts +6 -0
  113. package/templates/src/hooks/use-debounce.ts +23 -0
  114. package/templates/src/hooks/use-mounted.ts +21 -0
  115. package/templates/src/index.ts +28 -0
  116. package/templates/src/lib/index.ts +5 -0
  117. package/templates/src/lib/utils.ts +115 -0
  118. package/templates/src/modules/.gitkeep +6 -0
  119. package/templates/src/navigation/index.ts +8 -0
  120. package/templates/src/navigation/navigation-provider.tsx +36 -0
  121. package/templates/src/navigation/router.tsx +137 -0
  122. package/templates/src/providers/app.provider.tsx +29 -0
  123. package/templates/src/providers/index.ts +5 -0
  124. package/templates/src/routes/(tabs)/_layout.tsx +42 -0
  125. package/templates/src/routes/(tabs)/explore.tsx +161 -0
  126. package/templates/src/routes/(tabs)/home.tsx +138 -0
  127. package/templates/src/routes/(tabs)/profile.tsx +151 -0
  128. package/templates/src/routes/_layout.tsx +18 -0
  129. package/templates/src/routes/settings.tsx +194 -0
  130. package/templates/src/screens/auth/index.ts +6 -0
  131. package/templates/src/screens/auth/login.tsx +165 -0
  132. package/templates/src/screens/auth/register.tsx +203 -0
  133. package/templates/src/screens/home.tsx +204 -0
  134. package/templates/src/screens/index.ts +17 -0
  135. package/templates/src/screens/profile.tsx +210 -0
  136. package/templates/src/screens/settings.tsx +216 -0
  137. package/templates/src/screens/welcome.tsx +101 -0
  138. package/templates/src/styles/index.ts +103 -0
  139. package/templates/src/types/common.ts +71 -0
  140. package/templates/src/types/index.ts +5 -0
  141. package/templates/tsconfig.json +14 -0
  142. package/README.md +0 -15
  143. package/assets/favicon.ico +0 -0
  144. package/dist/commands/dev/dev.js +0 -55
  145. package/dist/commands/init/init-teardown.js +0 -26
  146. package/dist/index.js +0 -20
  147. package/dist/modules/dev/dev-menu/keyboard-handler.js +0 -138
  148. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.js +0 -105
  149. package/dist/modules/dev/dev-server/cdp/cdp.adapter.js +0 -12
  150. package/dist/modules/dev/dev-server/cdp/index.js +0 -18
  151. package/dist/modules/dev/dev-server/cdp/types.js +0 -2
  152. package/dist/modules/dev/dev-server/dev-server-checker.js +0 -72
  153. package/dist/modules/dev/dev-server/dev-server.js +0 -269
  154. package/dist/modules/dev/dev-server/inspector/device.event-reporter.js +0 -165
  155. package/dist/modules/dev/dev-server/inspector/device.js +0 -577
  156. package/dist/modules/dev/dev-server/inspector/inspector.js +0 -204
  157. package/dist/modules/dev/dev-server/inspector/types.js +0 -2
  158. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js +0 -61
  159. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js +0 -64
  160. package/dist/modules/dev/dev-server/plugins/devtools.plugin.js +0 -50
  161. package/dist/modules/dev/dev-server/plugins/favicon.plugin.js +0 -19
  162. package/dist/modules/dev/dev-server/plugins/multipart.plugin.js +0 -62
  163. package/dist/modules/dev/dev-server/plugins/systrace.plugin.js +0 -28
  164. package/dist/modules/dev/dev-server/plugins/types.js +0 -2
  165. package/dist/modules/dev/dev-server/plugins/wss/index.js +0 -19
  166. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.js +0 -66
  167. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.js +0 -128
  168. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.js +0 -75
  169. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.js +0 -198
  170. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.js +0 -120
  171. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.js +0 -357
  172. package/dist/modules/dev/dev-server/plugins/wss/types.js +0 -2
  173. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.js +0 -57
  174. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.js +0 -26
  175. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.js +0 -46
  176. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.js +0 -55
  177. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js +0 -36
  178. package/dist/modules/dev/dev-server/sybmolicate/types.js +0 -2
  179. package/dist/modules/dev/terminal/base.terminal.reporter.js +0 -78
  180. package/dist/modules/dev/terminal/terminal.reporter.js +0 -76
  181. package/dist/modules/dev/types.js +0 -2
  182. package/dist/modules/dev/utils/log.js +0 -73
package/bin/teardown.js CHANGED
@@ -1,3 +1,13 @@
1
1
  #!/usr/bin/env bun
2
+ /**
3
+ * Teardown Launchpad CLI
4
+ *
5
+ * This is the entry point for the teardown command.
6
+ */
2
7
 
3
- require("../dist/index.js");
8
+ import { run } from "../src/cli/index.ts";
9
+
10
+ run().catch((error) => {
11
+ console.error("Fatal error:", error);
12
+ process.exit(1);
13
+ });
package/package.json CHANGED
@@ -1,59 +1,79 @@
1
1
  {
2
- "name": "@teardown/cli",
3
- "version": "1.2.38",
4
- "description": "Teardown CLI",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "teardown": "./bin/teardown.js",
8
- "td": "./bin/teardown.js"
9
- },
10
- "files": [
11
- "bin/**/*",
12
- "README.md",
13
- "dist/**/*",
14
- "assets/**/*"
15
- ],
16
- "scripts": {
17
- "build": "tsc",
18
- "dev": "tsc --watch",
19
- "start": "bun run ./bin/teardown.js"
20
- },
21
- "dependencies": {
22
- "@babel/code-frame": "^7.26.2",
23
- "@fastify/compress": "^8.0.1",
24
- "@fastify/cors": "^10.0.1",
25
- "@fastify/middie": "^9.0.2",
26
- "@fastify/sensible": "^6.0.1",
27
- "@isaacs/ttlcache": "^1.4.1",
28
- "@react-native-community/cli-server-api": "^15.0.1",
29
- "@react-native/metro-config": "0.76.1",
30
- "@types/bun": "^1.1.13",
31
- "bun": "1.1.34",
32
- "chalk": "^5.3.0",
33
- "cli-progress": "^3.12.0",
34
- "commander": "^11.0.0",
35
- "compression": "^1.7.5",
36
- "connect": "^3.7.0",
37
- "debug": "^4.3.7",
38
- "devtools-protocol": "^0.0.1380148",
39
- "fastify": "^5.1.0",
40
- "fastify-favicon": "^5.0.0",
41
- "fastify-plugin": "^5.0.1",
42
- "log-update": "^6.1.0",
43
- "metro": "^0.81.0",
44
- "metro-config": "^0.81.0",
45
- "metro-core": "^0.81.0",
46
- "pretty-format": "^29.7.0",
47
- "prompts": "^2.4.2",
48
- "source-map": "^0.7.4",
49
- "timers": "^0.1.1"
50
- },
51
- "devDependencies": {
52
- "@types/babel__code-frame": "^7.0.6",
53
- "@types/metro": "^0.76.3",
54
- "@types/metro-config": "^0.76.3",
55
- "@types/prompts": "^2.4.9",
56
- "typescript": "^5.0.0",
57
- "copyfiles": "^2.4.1"
58
- }
2
+ "name": "@teardown/cli",
3
+ "version": "2.0.41",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "bin": {
9
+ "teardown": "./bin/teardown.js"
10
+ },
11
+ "files": [
12
+ "bin/**/*",
13
+ "src/**/*",
14
+ "templates/**/*",
15
+ "templates/.gitignore"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "types": "./src/index.ts",
20
+ "import": "./src/index.ts",
21
+ "default": "./src/index.ts"
22
+ },
23
+ "./config": {
24
+ "types": "./src/config/index.ts",
25
+ "import": "./src/config/index.ts",
26
+ "default": "./src/config/index.ts"
27
+ },
28
+ "./plugins": {
29
+ "types": "./src/plugins/index.ts",
30
+ "import": "./src/plugins/index.ts",
31
+ "default": "./src/plugins/index.ts"
32
+ },
33
+ "./plugins/*": {
34
+ "types": "./src/plugins/*/index.ts",
35
+ "import": "./src/plugins/*/index.ts",
36
+ "default": "./src/plugins/*/index.ts"
37
+ },
38
+ "./transformers": {
39
+ "types": "./src/transformers/index.ts",
40
+ "import": "./src/transformers/index.ts",
41
+ "default": "./src/transformers/index.ts"
42
+ },
43
+ "./cli": {
44
+ "types": "./src/cli/index.ts",
45
+ "import": "./src/cli/index.ts",
46
+ "default": "./src/cli/index.ts"
47
+ },
48
+ "./package.json": "./package.json"
49
+ },
50
+ "scripts": {
51
+ "typecheck": "bun x tsgo --noEmit --project ./tsconfig.json",
52
+ "build": "bun x tsgo --project ./tsconfig.json",
53
+ "dev": "bun x tsgo --watch --project ./tsconfig.json",
54
+ "lint": "bunx biome lint --write ./src",
55
+ "fmt": "bunx biome format --write ./src",
56
+ "check": "bunx biome check ./src",
57
+ "test": "bun test",
58
+ "test:e2e": "rm -rf ./example || true && bun test test/e2e",
59
+ "test:e2e:clean": "rm -rf example"
60
+ },
61
+ "dependencies": {
62
+ "@bacons/xcode": "^1.0.0-alpha.27",
63
+ "chalk": "^5.4.1",
64
+ "commander": "^13.1.0",
65
+ "ejs": "^3.1.10",
66
+ "fast-xml-parser": "^5.0.9",
67
+ "ora": "^8.2.0",
68
+ "simple-plist": "^1.4.0",
69
+ "ts-morph": "^25.0.1",
70
+ "zod": "4.2.1"
71
+ },
72
+ "devDependencies": {
73
+ "@biomejs/biome": "2.3.11",
74
+ "@teardown/tsconfig": "2.0.41",
75
+ "@types/bun": "1.3.5",
76
+ "@types/ejs": "^3.1.5",
77
+ "typescript": "5.9.3"
78
+ }
59
79
  }
@@ -0,0 +1,254 @@
1
+ import { spawn } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import chalk from "chalk";
6
+ import { Command } from "commander";
7
+ import ora from "ora";
8
+ import { findConfigFile, generateDefaultConfig } from "../../config";
9
+ import { TemplateGenerator } from "../../templates";
10
+
11
+ /**
12
+ * Get the CLI package version
13
+ */
14
+ function getCliVersion(): string {
15
+ try {
16
+ // Get the directory of this file
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ // Navigate up to package root and read package.json
20
+ const packageJsonPath = path.resolve(__dirname, "../../../package.json");
21
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
22
+ return `^${packageJson.version}`;
23
+ } catch {
24
+ // Fallback to a reasonable default
25
+ return "^0.1.0";
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Generate dev-client configuration file content
31
+ */
32
+ function generateDevClientConfig(): string {
33
+ return `import type { DevClientConfig } from "@teardown/dev-client";
34
+
35
+ /**
36
+ * Development client configuration
37
+ *
38
+ * This configuration controls the dev menu and splash screen
39
+ * behavior during development.
40
+ */
41
+ export const devClientConfig: DevClientConfig = {
42
+ // Enable shake gesture to open dev menu
43
+ enableShakeGesture: true,
44
+
45
+ // Enable 3-finger tap to open dev menu
46
+ enableThreeFingerTap: true,
47
+
48
+ // Splash screen configuration
49
+ splash: {
50
+ backgroundColor: "#ffffff",
51
+ minDisplayTime: 500,
52
+ fadeOutDuration: 300,
53
+ autoHide: true,
54
+ },
55
+
56
+ // Enable verbose logging in development
57
+ verbose: __DEV__,
58
+ };
59
+ `;
60
+ }
61
+
62
+ /**
63
+ * Create the init command
64
+ */
65
+ export function createInitCommand(): Command {
66
+ const command = new Command("init")
67
+ .description("Initialize a new Teardown React Native project")
68
+ .argument("<name>", "App name (required)")
69
+ .option("-f, --force", "Overwrite existing files", false)
70
+ .option("--skip-src", "Skip generating src folder", false)
71
+ .option("--skip-git", "Skip generating .gitignore", false)
72
+ .option("--skip-install", "Skip installing dependencies", false)
73
+ .action(async (name: string, options) => {
74
+ const spinner = ora("Initializing project...").start();
75
+
76
+ try {
77
+ const projectRoot = process.cwd();
78
+
79
+ // Check for existing config
80
+ const existingConfig = findConfigFile(projectRoot);
81
+ if (existingConfig && !options.force) {
82
+ spinner.fail("Configuration file already exists");
83
+ console.log(chalk.yellow(`\n Found: ${existingConfig}`));
84
+ console.log(chalk.gray(" Use --force to overwrite"));
85
+ process.exit(1);
86
+ }
87
+
88
+ // Use provided name as the app name
89
+ const appName = name;
90
+
91
+ // Generate slug from name
92
+ const slug = appName
93
+ .toLowerCase()
94
+ .replace(/[^a-z0-9]+/g, "-")
95
+ .replace(/^-|-$/g, "");
96
+
97
+ // Generate bundle identifier
98
+ const bundleId = `com.example.${slug.replace(/-/g, "")}`;
99
+
100
+ // Generate config content
101
+ const configContent = generateDefaultConfig({
102
+ name: appName,
103
+ slug,
104
+ bundleIdentifier: bundleId,
105
+ packageName: bundleId,
106
+ });
107
+
108
+ // Write config file
109
+ const configPath = path.join(projectRoot, "teardown.config.ts");
110
+ fs.writeFileSync(configPath, configContent, "utf-8");
111
+
112
+ spinner.succeed("Created teardown.config.ts");
113
+
114
+ // Create template generator
115
+ const generator = new TemplateGenerator({
116
+ projectRoot,
117
+ variables: {
118
+ slug,
119
+ appName,
120
+ bundleIdentifier: bundleId,
121
+ version: "1.0.0",
122
+ buildNumber: 1,
123
+ packageName: bundleId,
124
+ versionCode: 1,
125
+ versionName: "1.0.0",
126
+ cliVersion: getCliVersion(),
127
+ },
128
+ force: options.force,
129
+ });
130
+
131
+ // Generate src folder
132
+ if (!options.skipSrc) {
133
+ const srcSpinner = ora("Generating src folder...").start();
134
+ const srcExists = await generator.srcFolderExists();
135
+
136
+ if (!srcExists || options.force) {
137
+ const srcResult = await generator.generateSrcFolder();
138
+ if (srcResult.success) {
139
+ srcSpinner.succeed(`Generated src/ folder (${srcResult.filesCreated.length} files)`);
140
+ } else {
141
+ srcSpinner.warn(`src/ folder had errors: ${srcResult.errors.join(", ")}`);
142
+ }
143
+ } else {
144
+ srcSpinner.info("src/ folder already exists, skipping");
145
+ }
146
+ }
147
+
148
+ // Generate package.json if it doesn't exist
149
+ const packageJsonPath = path.join(projectRoot, "package.json");
150
+ if (!fs.existsSync(packageJsonPath) || options.force) {
151
+ const pkgSpinner = ora("Generating package.json...").start();
152
+ const pkgResult = await generator.generatePackageJson();
153
+ if (pkgResult.success && pkgResult.filesCreated.length > 0) {
154
+ pkgSpinner.succeed("Generated package.json");
155
+ } else if (pkgResult.filesSkipped.length > 0) {
156
+ pkgSpinner.info("package.json already exists, skipping");
157
+ } else {
158
+ pkgSpinner.warn(`package.json had errors: ${pkgResult.errors.join(", ")}`);
159
+ }
160
+ }
161
+
162
+ // Generate root config files
163
+ const configSpinner = ora("Generating config files...").start();
164
+ const configResult = await generator.generateRootConfigFiles();
165
+ if (configResult.filesCreated.length > 0) {
166
+ configSpinner.succeed(`Generated: ${configResult.filesCreated.map((f) => path.basename(f)).join(", ")}`);
167
+ } else if (configResult.filesSkipped.length > 0) {
168
+ configSpinner.info("Config files already exist, skipping");
169
+ }
170
+
171
+ // Generate .gitignore
172
+ if (!options.skipGit) {
173
+ const gitignorePath = path.join(projectRoot, ".gitignore");
174
+ if (!fs.existsSync(gitignorePath) || options.force) {
175
+ const gitSpinner = ora("Generating .gitignore...").start();
176
+ const gitResult = await generator.generateGitignore();
177
+ if (gitResult.success && gitResult.filesCreated.length > 0) {
178
+ gitSpinner.succeed("Generated .gitignore (ios/ and android/ excluded)");
179
+ } else {
180
+ gitSpinner.warn(`gitignore had errors: ${gitResult.errors.join(", ")}`);
181
+ }
182
+ }
183
+ }
184
+
185
+ // Create assets directory if it doesn't exist
186
+ const assetsDir = path.join(projectRoot, "assets");
187
+ if (!fs.existsSync(assetsDir)) {
188
+ fs.mkdirSync(assetsDir, { recursive: true });
189
+ }
190
+
191
+ // Generate dev-client configuration
192
+ const devClientConfigPath = path.join(projectRoot, "dev-client.config.ts");
193
+ if (!fs.existsSync(devClientConfigPath) || options.force) {
194
+ const devClientSpinner = ora("Setting up dev client...").start();
195
+ try {
196
+ fs.writeFileSync(devClientConfigPath, generateDevClientConfig(), "utf-8");
197
+ devClientSpinner.succeed("Created dev-client.config.ts");
198
+ } catch {
199
+ devClientSpinner.warn("Failed to create dev-client.config.ts");
200
+ }
201
+ }
202
+
203
+ // Run bun install (unless skipped)
204
+ if (!options.skipInstall) {
205
+ const installSpinner = ora("Installing dependencies...").start();
206
+ await new Promise<void>((resolve, reject) => {
207
+ const child = spawn("bun", ["install"], {
208
+ cwd: projectRoot,
209
+ stdio: ["ignore", "pipe", "pipe"],
210
+ });
211
+
212
+ let stderr = "";
213
+ child.stderr?.on("data", (data) => {
214
+ stderr += data.toString();
215
+ });
216
+
217
+ child.on("close", (code) => {
218
+ if (code === 0) {
219
+ installSpinner.succeed("Dependencies installed");
220
+ resolve();
221
+ } else {
222
+ installSpinner.fail("Failed to install dependencies");
223
+ console.error(chalk.red(stderr));
224
+ reject(new Error(`bun install exited with code ${code}`));
225
+ }
226
+ });
227
+
228
+ child.on("error", (err) => {
229
+ installSpinner.fail("Failed to run bun install");
230
+ reject(err);
231
+ });
232
+ });
233
+ }
234
+
235
+ console.log(chalk.green("\n✓ Project initialized successfully!"));
236
+ console.log(chalk.gray("\nProject structure:"));
237
+ console.log(chalk.gray(" teardown.config.ts - App configuration"));
238
+ console.log(chalk.gray(" dev-client.config.ts - Dev client settings"));
239
+ console.log(chalk.gray(" src/ - React Native source code"));
240
+ console.log(chalk.gray(" assets/ - App icons and images"));
241
+ console.log(chalk.gray(" .gitignore - Excludes ios/ and android/"));
242
+
243
+ console.log(chalk.gray("\nNext steps:"));
244
+ console.log(chalk.cyan(" 1. teardown prebuild") + chalk.gray(" - Generate native projects"));
245
+ console.log(chalk.cyan(" 2. teardown run ios") + chalk.gray(" or ") + chalk.cyan("teardown run android"));
246
+ } catch (error) {
247
+ spinner.fail("Initialization failed");
248
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
249
+ process.exit(1);
250
+ }
251
+ });
252
+
253
+ return command;
254
+ }
@@ -0,0 +1,93 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { CapabilityPlugins } from "../../plugins/capabilities";
4
+
5
+ /**
6
+ * Create the plugins command
7
+ */
8
+ export function createPluginsCommand(): Command {
9
+ const command = new Command("plugins").description("Manage Teardown plugins");
10
+
11
+ // List subcommand
12
+ command
13
+ .command("list")
14
+ .description("List all available plugins")
15
+ .action(() => {
16
+ console.log(chalk.cyan("\nAvailable Plugins:\n"));
17
+
18
+ const plugins = Object.entries(CapabilityPlugins).map(([_name, Plugin]) => {
19
+ const instance = new Plugin();
20
+ return {
21
+ name: instance.name,
22
+ version: instance.version,
23
+ description: instance.description,
24
+ platforms: instance.platforms,
25
+ provides: instance.provides,
26
+ };
27
+ });
28
+
29
+ // Group by capability
30
+ for (const plugin of plugins) {
31
+ const platformIcons = plugin.platforms.map((p) => (p === "ios" ? "" : "🤖")).join(" ");
32
+
33
+ console.log(`${chalk.bold(` ${plugin.name}`) + chalk.gray(` v${plugin.version}`)} ${platformIcons}`);
34
+ console.log(chalk.gray(` ${plugin.description}`));
35
+ console.log(chalk.gray(` Provides: ${plugin.provides.join(", ")}`));
36
+ console.log();
37
+ }
38
+
39
+ console.log(chalk.gray("Usage in teardown.config.ts:"));
40
+ console.log(
41
+ chalk.gray(`
42
+ import { CameraPlugin, LocationPlugin } from '@teardown/cli/plugins';
43
+
44
+ export default defineConfig({
45
+ // ...
46
+ plugins: [
47
+ [CameraPlugin, { cameraPermission: 'We need camera access' }],
48
+ [LocationPlugin, { whenInUsePermission: 'We need location' }],
49
+ ],
50
+ });
51
+ `)
52
+ );
53
+ });
54
+
55
+ // Info subcommand
56
+ command
57
+ .command("info <name>")
58
+ .description("Show detailed information about a plugin")
59
+ .action((name: string) => {
60
+ const PluginClass = Object.values(CapabilityPlugins).find((P) => {
61
+ const instance = new P();
62
+ return instance.name === name;
63
+ });
64
+
65
+ if (!PluginClass) {
66
+ console.error(chalk.red(`Plugin "${name}" not found`));
67
+ console.log(chalk.gray("\nRun 'teardown plugins list' to see available plugins"));
68
+ process.exit(1);
69
+ }
70
+
71
+ const plugin = new PluginClass();
72
+
73
+ console.log(chalk.cyan(`\n${plugin.name}`));
74
+ console.log(chalk.gray(` Version: ${plugin.version}`));
75
+ console.log(chalk.gray(` ${plugin.description}\n`));
76
+
77
+ console.log(chalk.bold("Platforms:"));
78
+ console.log(chalk.gray(` ${plugin.platforms.join(", ")}\n`));
79
+
80
+ console.log(chalk.bold("Provides:"));
81
+ console.log(chalk.gray(` ${plugin.provides.join(", ")}\n`));
82
+
83
+ if (plugin.dependencies.length > 0) {
84
+ console.log(chalk.bold("Dependencies:"));
85
+ console.log(chalk.gray(` ${plugin.dependencies.join(", ")}\n`));
86
+ }
87
+
88
+ console.log(chalk.bold("Configuration Schema:"));
89
+ console.log(chalk.gray(" See plugin documentation for schema details"));
90
+ });
91
+
92
+ return command;
93
+ }
@@ -0,0 +1,168 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import ora from "ora";
4
+ import { loadConfig } from "../../config";
5
+ import type { PrebuildOptions } from "../../config/types";
6
+ import { createPipeline } from "../../pipeline";
7
+ import type { BasePlugin } from "../../plugins/base";
8
+ import { getPluginByName } from "../../plugins/capabilities";
9
+ import { createLogger, formatDuration } from "../../utils";
10
+
11
+ /**
12
+ * Create the prebuild command
13
+ */
14
+ export function createPrebuildCommand(): Command {
15
+ const command = new Command("prebuild")
16
+ .description("Generate native iOS and Android projects from configuration")
17
+ .option("-p, --platform <platform>", "Target platform (ios, android, all)", "all")
18
+ .option("-c, --clean", "Clean native directories before prebuild", false)
19
+ .option("-d, --dry-run", "Preview changes without applying", false)
20
+ .option("-v, --verbose", "Enable verbose logging", false)
21
+ .option("--skip-validation", "Skip configuration validation", false)
22
+ .action(async (options) => {
23
+ const spinner = ora("Starting prebuild...").start();
24
+ const startTime = Date.now();
25
+
26
+ const logger = createLogger({
27
+ level: options.verbose ? "debug" : "info",
28
+ colors: true,
29
+ });
30
+
31
+ try {
32
+ const projectRoot = process.cwd();
33
+
34
+ // Load configuration
35
+ spinner.text = "Loading configuration...";
36
+ const configResult = await loadConfig(projectRoot);
37
+
38
+ if (!configResult.success || !configResult.config) {
39
+ spinner.fail("Failed to load configuration");
40
+ console.error(chalk.red("\nConfiguration errors:"));
41
+ for (const error of configResult.errors ?? []) {
42
+ console.error(chalk.red(` - ${error.path}: ${error.message}`));
43
+ if (error.suggestion) {
44
+ console.error(chalk.gray(` Suggestion: ${error.suggestion}`));
45
+ }
46
+ }
47
+ process.exit(1);
48
+ }
49
+
50
+ spinner.text = "Resolving plugins...";
51
+
52
+ // Create pipeline
53
+ const pipeline = createPipeline({ logger });
54
+
55
+ // Register plugins from configuration
56
+ const plugins = configResult.config.plugins ?? [];
57
+ for (const pluginEntry of plugins) {
58
+ if (typeof pluginEntry === "string") {
59
+ // String plugin name
60
+ const PluginClass = getPluginByName(pluginEntry);
61
+ if (PluginClass) {
62
+ const plugin = new (PluginClass as new () => BasePlugin)();
63
+ pipeline.registerPlugin(plugin);
64
+ } else {
65
+ logger.warn(`Unknown plugin: ${pluginEntry}`);
66
+ }
67
+ } else if (Array.isArray(pluginEntry)) {
68
+ // [PluginClass, config] tuple
69
+ const [PluginClass, pluginConfig] = pluginEntry;
70
+ if (typeof PluginClass === "function") {
71
+ const plugin = new PluginClass();
72
+ pipeline.registerPlugin(plugin, pluginConfig);
73
+ }
74
+ }
75
+ }
76
+
77
+ // Execute pipeline
78
+ spinner.text = "Running prebuild...";
79
+
80
+ const prebuildOptions: PrebuildOptions = {
81
+ platform: options.platform as "ios" | "android" | "all",
82
+ clean: options.clean,
83
+ dryRun: options.dryRun,
84
+ projectRoot,
85
+ skipValidation: options.skipValidation,
86
+ verbose: options.verbose,
87
+ };
88
+
89
+ const result = await pipeline.execute(prebuildOptions);
90
+
91
+ const duration = formatDuration(Date.now() - startTime);
92
+
93
+ if (!result.success) {
94
+ spinner.fail(`Prebuild failed (${duration})`);
95
+ console.error(chalk.red("\nErrors:"));
96
+ for (const error of result.errors) {
97
+ console.error(chalk.red(` - [${error.code}] ${error.message}`));
98
+ if (error.plugin) {
99
+ console.error(chalk.gray(` Plugin: ${error.plugin}`));
100
+ }
101
+ }
102
+ process.exit(1);
103
+ }
104
+
105
+ // Show warnings
106
+ if (result.warnings.length > 0) {
107
+ console.log(chalk.yellow("\nWarnings:"));
108
+ for (const warning of result.warnings) {
109
+ console.log(chalk.yellow(` - [${warning.code}] ${warning.message}`));
110
+ if (warning.suggestion) {
111
+ console.log(chalk.gray(` Suggestion: ${warning.suggestion}`));
112
+ }
113
+ }
114
+ }
115
+
116
+ // Show changes
117
+ if (options.dryRun && result.changes) {
118
+ spinner.succeed(`Dry run completed (${duration})`);
119
+ console.log(chalk.cyan("\nPending changes:"));
120
+
121
+ const iosChanges = result.changes.filter((c) => c.path.includes("/ios/"));
122
+ const androidChanges = result.changes.filter((c) => c.path.includes("/android/"));
123
+
124
+ if (iosChanges.length > 0) {
125
+ console.log(chalk.blue("\n iOS:"));
126
+ for (const change of iosChanges) {
127
+ const icon = change.operation === "create" ? "+" : change.operation === "delete" ? "-" : "~";
128
+ const color =
129
+ change.operation === "create" ? chalk.green : change.operation === "delete" ? chalk.red : chalk.yellow;
130
+ console.log(color(` ${icon} ${change.path.split("/ios/")[1]}`));
131
+ }
132
+ }
133
+
134
+ if (androidChanges.length > 0) {
135
+ console.log(chalk.green("\n Android:"));
136
+ for (const change of androidChanges) {
137
+ const icon = change.operation === "create" ? "+" : change.operation === "delete" ? "-" : "~";
138
+ const color =
139
+ change.operation === "create" ? chalk.green : change.operation === "delete" ? chalk.red : chalk.yellow;
140
+ console.log(color(` ${icon} ${change.path.split("/android/")[1]}`));
141
+ }
142
+ }
143
+
144
+ console.log(chalk.gray(`\n Run without --dry-run to apply changes.`));
145
+ } else {
146
+ spinner.succeed(`Prebuild completed (${duration})`);
147
+
148
+ if (result.changes && result.changes.length > 0) {
149
+ console.log(chalk.green(`\n ${result.changes.length} files modified`));
150
+ }
151
+
152
+ // Show iOS workspace hint if iOS was built
153
+ if (options.platform === "ios" || options.platform === "all") {
154
+ console.log(chalk.gray(`\n iOS: Use the .xcworkspace file (not .xcodeproj) to open in Xcode`));
155
+ }
156
+ }
157
+ } catch (error) {
158
+ spinner.fail("Prebuild failed");
159
+ console.error(chalk.red(error instanceof Error ? error.message : String(error)));
160
+ if (options.verbose && error instanceof Error && error.stack) {
161
+ console.error(chalk.gray(error.stack));
162
+ }
163
+ process.exit(1);
164
+ }
165
+ });
166
+
167
+ return command;
168
+ }