@teardown/cli 2.0.50 → 2.0.52
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/init.ts +87 -3
- package/src/cli/commands/run.ts +79 -5
- package/src/pipeline/stages.ts +69 -7
- package/templates/package.json +11 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teardown/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.52",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@biomejs/biome": "2.3.11",
|
|
74
|
-
"@teardown/tsconfig": "2.0.
|
|
74
|
+
"@teardown/tsconfig": "2.0.52",
|
|
75
75
|
"@types/bun": "1.3.5",
|
|
76
76
|
"@types/ejs": "^3.1.5",
|
|
77
77
|
"typescript": "5.9.3"
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -70,6 +70,7 @@ export function createInitCommand(): Command {
|
|
|
70
70
|
.option("--skip-src", "Skip generating src folder", false)
|
|
71
71
|
.option("--skip-git", "Skip generating .gitignore", false)
|
|
72
72
|
.option("--skip-install", "Skip installing dependencies", false)
|
|
73
|
+
.option("-v, --verbose", "Show detailed output", false)
|
|
73
74
|
.action(async (name: string, options) => {
|
|
74
75
|
const spinner = ora("Initializing project...").start();
|
|
75
76
|
|
|
@@ -203,6 +204,9 @@ export function createInitCommand(): Command {
|
|
|
203
204
|
// Run bun install (unless skipped)
|
|
204
205
|
if (!options.skipInstall) {
|
|
205
206
|
const installSpinner = ora("Installing dependencies...").start();
|
|
207
|
+
const startTime = Date.now();
|
|
208
|
+
let lastActivity = "";
|
|
209
|
+
|
|
206
210
|
await new Promise<void>((resolve, reject) => {
|
|
207
211
|
const child = spawn("bun", ["install"], {
|
|
208
212
|
cwd: projectRoot,
|
|
@@ -210,25 +214,105 @@ export function createInitCommand(): Command {
|
|
|
210
214
|
});
|
|
211
215
|
|
|
212
216
|
let stderr = "";
|
|
217
|
+
let stdout = "";
|
|
218
|
+
|
|
219
|
+
// Update spinner with elapsed time and last activity
|
|
220
|
+
const progressInterval = setInterval(() => {
|
|
221
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
222
|
+
const activity = lastActivity ? ` - ${lastActivity}` : "";
|
|
223
|
+
installSpinner.text = `Installing dependencies... (${elapsed}s)${activity}`;
|
|
224
|
+
|
|
225
|
+
// Warn if taking too long
|
|
226
|
+
if (elapsed === 60) {
|
|
227
|
+
installSpinner.text = `Installing dependencies... (${elapsed}s) - still working, this may take a while`;
|
|
228
|
+
} else if (elapsed === 120) {
|
|
229
|
+
installSpinner.text = `Installing dependencies... (${elapsed}s) - network may be slow`;
|
|
230
|
+
}
|
|
231
|
+
}, 1000);
|
|
232
|
+
|
|
233
|
+
// Parse bun output for status updates
|
|
234
|
+
child.stdout?.on("data", (data) => {
|
|
235
|
+
const chunk = data.toString();
|
|
236
|
+
stdout += chunk;
|
|
237
|
+
|
|
238
|
+
// Extract useful status from bun output
|
|
239
|
+
const lines = chunk.trim().split("\n");
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
// Bun outputs package resolution info
|
|
242
|
+
if (line.includes("Resolving")) {
|
|
243
|
+
lastActivity = "resolving packages";
|
|
244
|
+
} else if (line.includes("Downloading")) {
|
|
245
|
+
lastActivity = "downloading";
|
|
246
|
+
} else if (line.includes("Installing")) {
|
|
247
|
+
lastActivity = "installing";
|
|
248
|
+
} else if (line.includes("Linking")) {
|
|
249
|
+
lastActivity = "linking";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (options.verbose && line.trim()) {
|
|
253
|
+
console.log(chalk.gray(` [bun] ${line}`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
213
258
|
child.stderr?.on("data", (data) => {
|
|
214
|
-
|
|
259
|
+
const chunk = data.toString();
|
|
260
|
+
stderr += chunk;
|
|
261
|
+
|
|
262
|
+
// Bun sometimes outputs progress to stderr
|
|
263
|
+
if (chunk.includes("bun install")) {
|
|
264
|
+
lastActivity = "starting";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (options.verbose && chunk.trim()) {
|
|
268
|
+
console.log(chalk.yellow(` [bun:err] ${chunk.trim()}`));
|
|
269
|
+
}
|
|
215
270
|
});
|
|
216
271
|
|
|
217
272
|
child.on("close", (code) => {
|
|
273
|
+
clearInterval(progressInterval);
|
|
274
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
218
275
|
if (code === 0) {
|
|
219
|
-
installSpinner.succeed(
|
|
276
|
+
installSpinner.succeed(`Dependencies installed (${elapsed}s)`);
|
|
220
277
|
resolve();
|
|
221
278
|
} else {
|
|
222
|
-
installSpinner.fail(
|
|
279
|
+
installSpinner.fail(`Failed to install dependencies after ${elapsed}s`);
|
|
223
280
|
console.error(chalk.red(stderr));
|
|
281
|
+
if (stdout) {
|
|
282
|
+
console.log(chalk.gray("Last stdout:"), stdout.slice(-1000));
|
|
283
|
+
}
|
|
224
284
|
reject(new Error(`bun install exited with code ${code}`));
|
|
225
285
|
}
|
|
226
286
|
});
|
|
227
287
|
|
|
228
288
|
child.on("error", (err) => {
|
|
289
|
+
clearInterval(progressInterval);
|
|
229
290
|
installSpinner.fail("Failed to run bun install");
|
|
291
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
230
292
|
reject(err);
|
|
231
293
|
});
|
|
294
|
+
|
|
295
|
+
// Timeout after 5 minutes
|
|
296
|
+
const timeout = setTimeout(
|
|
297
|
+
() => {
|
|
298
|
+
clearInterval(progressInterval);
|
|
299
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
300
|
+
child.kill("SIGTERM");
|
|
301
|
+
installSpinner.fail(`bun install timed out after ${elapsed}s`);
|
|
302
|
+
console.log(chalk.yellow("\nThis usually means:"));
|
|
303
|
+
console.log(chalk.gray(" - Network connection issues"));
|
|
304
|
+
console.log(chalk.gray(" - Slow package registry response"));
|
|
305
|
+
console.log(chalk.gray(" - Large dependency tree"));
|
|
306
|
+
console.log(chalk.gray("\nTry running manually: bun install"));
|
|
307
|
+
if (stdout) {
|
|
308
|
+
console.log(chalk.gray("\nLast output:"), stdout.slice(-500));
|
|
309
|
+
}
|
|
310
|
+
reject(new Error("bun install timed out"));
|
|
311
|
+
},
|
|
312
|
+
5 * 60 * 1000
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
child.on("close", () => clearTimeout(timeout));
|
|
232
316
|
});
|
|
233
317
|
}
|
|
234
318
|
|
package/src/cli/commands/run.ts
CHANGED
|
@@ -92,6 +92,8 @@ function hasPodsInstalled(): boolean {
|
|
|
92
92
|
async function runPodInstall(): Promise<boolean> {
|
|
93
93
|
const spinner = ora("Installing CocoaPods dependencies...").start();
|
|
94
94
|
const iosDir = join(process.cwd(), "ios");
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
let lastActivity = "";
|
|
95
97
|
|
|
96
98
|
return new Promise((resolve) => {
|
|
97
99
|
const proc = spawn("pod", ["install", "--repo-update"], {
|
|
@@ -105,9 +107,52 @@ async function runPodInstall(): Promise<boolean> {
|
|
|
105
107
|
});
|
|
106
108
|
|
|
107
109
|
let stderr = "";
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
let stdout = "";
|
|
111
|
+
|
|
112
|
+
// Update spinner with elapsed time and activity
|
|
113
|
+
const progressInterval = setInterval(() => {
|
|
114
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
115
|
+
const activity = lastActivity ? ` - ${lastActivity}` : "";
|
|
116
|
+
spinner.text = `Installing CocoaPods dependencies... (${elapsed}s)${activity}`;
|
|
117
|
+
|
|
118
|
+
// Warn if taking too long
|
|
119
|
+
if (elapsed === 60) {
|
|
120
|
+
spinner.text = `Installing CocoaPods dependencies... (${elapsed}s) - still working, this can take a while`;
|
|
121
|
+
} else if (elapsed === 120) {
|
|
122
|
+
spinner.text = `Installing CocoaPods dependencies... (${elapsed}s) - updating pod repos`;
|
|
123
|
+
} else if (elapsed === 180) {
|
|
124
|
+
spinner.text = `Installing CocoaPods dependencies... (${elapsed}s) - this is taking longer than usual`;
|
|
125
|
+
}
|
|
126
|
+
}, 1000);
|
|
127
|
+
|
|
128
|
+
proc.stdout?.on("data", (data) => {
|
|
129
|
+
const chunk = data.toString();
|
|
130
|
+
stdout += chunk;
|
|
131
|
+
|
|
132
|
+
// Parse CocoaPods output for status
|
|
133
|
+
for (const line of chunk.split("\n")) {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (!trimmed) continue;
|
|
136
|
+
|
|
137
|
+
// Extract useful status from pod output
|
|
138
|
+
if (trimmed.startsWith("Analyzing dependencies")) {
|
|
139
|
+
lastActivity = "analyzing dependencies";
|
|
140
|
+
} else if (trimmed.startsWith("Downloading dependencies")) {
|
|
141
|
+
lastActivity = "downloading dependencies";
|
|
142
|
+
} else if (trimmed.startsWith("Installing")) {
|
|
143
|
+
// Extract pod name: "Installing PodName (1.0.0)"
|
|
144
|
+
const match = trimmed.match(/Installing (\S+)/);
|
|
145
|
+
if (match) {
|
|
146
|
+
lastActivity = `installing ${match[1]}`;
|
|
147
|
+
}
|
|
148
|
+
} else if (trimmed.startsWith("Generating Pods project")) {
|
|
149
|
+
lastActivity = "generating Pods project";
|
|
150
|
+
} else if (trimmed.startsWith("Integrating client project")) {
|
|
151
|
+
lastActivity = "integrating project";
|
|
152
|
+
} else if (trimmed.includes("Pod installation complete")) {
|
|
153
|
+
lastActivity = "completing";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
111
156
|
});
|
|
112
157
|
|
|
113
158
|
proc.stderr?.on("data", (data) => {
|
|
@@ -115,12 +160,18 @@ async function runPodInstall(): Promise<boolean> {
|
|
|
115
160
|
});
|
|
116
161
|
|
|
117
162
|
proc.on("close", (code) => {
|
|
163
|
+
clearInterval(progressInterval);
|
|
164
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
165
|
+
|
|
118
166
|
if (code === 0) {
|
|
119
|
-
spinner.succeed(
|
|
167
|
+
spinner.succeed(`CocoaPods dependencies installed (${elapsed}s)`);
|
|
120
168
|
resolve(true);
|
|
121
169
|
} else {
|
|
122
|
-
spinner.fail(
|
|
170
|
+
spinner.fail(`Failed to install CocoaPods dependencies after ${elapsed}s`);
|
|
123
171
|
console.error(chalk.red(stderr));
|
|
172
|
+
if (stdout) {
|
|
173
|
+
console.log(chalk.gray("Last output:"), stdout.slice(-500));
|
|
174
|
+
}
|
|
124
175
|
console.log(chalk.yellow("\nTry running manually:"));
|
|
125
176
|
console.log(chalk.gray(" cd ios && pod install --repo-update"));
|
|
126
177
|
resolve(false);
|
|
@@ -128,9 +179,32 @@ async function runPodInstall(): Promise<boolean> {
|
|
|
128
179
|
});
|
|
129
180
|
|
|
130
181
|
proc.on("error", (err) => {
|
|
182
|
+
clearInterval(progressInterval);
|
|
131
183
|
spinner.fail(`Failed to run pod install: ${err.message}`);
|
|
132
184
|
resolve(false);
|
|
133
185
|
});
|
|
186
|
+
|
|
187
|
+
// Timeout after 10 minutes (pod install can be slow)
|
|
188
|
+
const timeout = setTimeout(
|
|
189
|
+
() => {
|
|
190
|
+
clearInterval(progressInterval);
|
|
191
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
192
|
+
proc.kill("SIGTERM");
|
|
193
|
+
spinner.fail(`pod install timed out after ${elapsed}s`);
|
|
194
|
+
console.log(chalk.yellow("\nThis usually means:"));
|
|
195
|
+
console.log(chalk.gray(" - Network connection issues"));
|
|
196
|
+
console.log(chalk.gray(" - Slow CocoaPods CDN"));
|
|
197
|
+
console.log(chalk.gray(" - Large number of pods to install"));
|
|
198
|
+
console.log(chalk.gray("\nTry running manually: cd ios && pod install --repo-update"));
|
|
199
|
+
if (stdout) {
|
|
200
|
+
console.log(chalk.gray("\nLast output:"), stdout.slice(-500));
|
|
201
|
+
}
|
|
202
|
+
resolve(false);
|
|
203
|
+
},
|
|
204
|
+
10 * 60 * 1000
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
proc.on("close", () => clearTimeout(timeout));
|
|
134
208
|
});
|
|
135
209
|
}
|
|
136
210
|
|
package/src/pipeline/stages.ts
CHANGED
|
@@ -606,6 +606,9 @@ class IOSPostInstallStage implements PipelineStage {
|
|
|
606
606
|
* Run pod install in the iOS directory
|
|
607
607
|
*/
|
|
608
608
|
private async runPodInstall(iosDir: string, logger: Logger, repoUpdate = false): Promise<void> {
|
|
609
|
+
const startTime = Date.now();
|
|
610
|
+
let lastActivity = "";
|
|
611
|
+
|
|
609
612
|
return new Promise((resolve, reject) => {
|
|
610
613
|
// Use --repo-update for fresh installs to ensure specs are up to date
|
|
611
614
|
const args = repoUpdate ? ["install", "--repo-update"] : ["install"];
|
|
@@ -624,21 +627,53 @@ class IOSPostInstallStage implements PipelineStage {
|
|
|
624
627
|
let stdout = "";
|
|
625
628
|
let stderr = "";
|
|
626
629
|
|
|
630
|
+
// Log progress periodically
|
|
631
|
+
const progressInterval = setInterval(() => {
|
|
632
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
633
|
+
const activity = lastActivity ? ` - ${lastActivity}` : "";
|
|
634
|
+
logger.info(`pod install running... (${elapsed}s)${activity}`);
|
|
635
|
+
}, 30000); // Log every 30 seconds
|
|
636
|
+
|
|
627
637
|
child.stdout?.on("data", (data) => {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
638
|
+
const chunk = data.toString();
|
|
639
|
+
stdout += chunk;
|
|
640
|
+
|
|
641
|
+
// Parse CocoaPods output for status and log progress
|
|
642
|
+
const lines = chunk.split("\n");
|
|
631
643
|
for (const line of lines) {
|
|
632
|
-
|
|
633
|
-
|
|
644
|
+
const trimmed = line.trim();
|
|
645
|
+
if (!trimmed) continue;
|
|
646
|
+
|
|
647
|
+
logger.debug(`[pod] ${trimmed}`);
|
|
648
|
+
|
|
649
|
+
// Extract useful status from pod output
|
|
650
|
+
if (trimmed.startsWith("Analyzing dependencies")) {
|
|
651
|
+
lastActivity = "analyzing dependencies";
|
|
652
|
+
logger.info("Analyzing dependencies...");
|
|
653
|
+
} else if (trimmed.startsWith("Downloading dependencies")) {
|
|
654
|
+
lastActivity = "downloading dependencies";
|
|
655
|
+
logger.info("Downloading dependencies...");
|
|
656
|
+
} else if (trimmed.startsWith("Installing")) {
|
|
657
|
+
// Extract pod name: "Installing PodName (1.0.0)"
|
|
658
|
+
const match = trimmed.match(/Installing (\S+)/);
|
|
659
|
+
if (match) {
|
|
660
|
+
lastActivity = `installing ${match[1]}`;
|
|
661
|
+
}
|
|
662
|
+
} else if (trimmed.startsWith("Generating Pods project")) {
|
|
663
|
+
lastActivity = "generating Pods project";
|
|
664
|
+
logger.info("Generating Pods project...");
|
|
665
|
+
} else if (trimmed.startsWith("Integrating client project")) {
|
|
666
|
+
lastActivity = "integrating project";
|
|
667
|
+
logger.info("Integrating client project...");
|
|
634
668
|
}
|
|
635
669
|
}
|
|
636
670
|
});
|
|
637
671
|
|
|
638
672
|
child.stderr?.on("data", (data) => {
|
|
639
|
-
|
|
673
|
+
const chunk = data.toString();
|
|
674
|
+
stderr += chunk;
|
|
640
675
|
// Also log stderr for visibility
|
|
641
|
-
const lines =
|
|
676
|
+
const lines = chunk.split("\n");
|
|
642
677
|
for (const line of lines) {
|
|
643
678
|
if (line.trim()) {
|
|
644
679
|
logger.debug(`[pod:err] ${line.trim()}`);
|
|
@@ -647,16 +682,43 @@ class IOSPostInstallStage implements PipelineStage {
|
|
|
647
682
|
});
|
|
648
683
|
|
|
649
684
|
child.on("close", (code) => {
|
|
685
|
+
clearInterval(progressInterval);
|
|
686
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
687
|
+
|
|
650
688
|
if (code === 0) {
|
|
689
|
+
logger.info(`pod install completed in ${elapsed}s`);
|
|
651
690
|
resolve();
|
|
652
691
|
} else {
|
|
692
|
+
logger.error(`pod install failed after ${elapsed}s`);
|
|
693
|
+
if (stdout) {
|
|
694
|
+
logger.debug(`Last stdout: ${stdout.slice(-500)}`);
|
|
695
|
+
}
|
|
653
696
|
reject(new Error(`pod install exited with code ${code}: ${stderr || stdout}`));
|
|
654
697
|
}
|
|
655
698
|
});
|
|
656
699
|
|
|
657
700
|
child.on("error", (err) => {
|
|
701
|
+
clearInterval(progressInterval);
|
|
658
702
|
reject(err);
|
|
659
703
|
});
|
|
704
|
+
|
|
705
|
+
// Timeout after 10 minutes
|
|
706
|
+
const timeout = setTimeout(
|
|
707
|
+
() => {
|
|
708
|
+
clearInterval(progressInterval);
|
|
709
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
710
|
+
child.kill("SIGTERM");
|
|
711
|
+
logger.error(`pod install timed out after ${elapsed}s`);
|
|
712
|
+
logger.error("This usually means network issues or slow CocoaPods CDN");
|
|
713
|
+
if (stdout) {
|
|
714
|
+
logger.debug(`Last output: ${stdout.slice(-500)}`);
|
|
715
|
+
}
|
|
716
|
+
reject(new Error("pod install timed out"));
|
|
717
|
+
},
|
|
718
|
+
10 * 60 * 1000
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
child.on("close", () => clearTimeout(timeout));
|
|
660
722
|
});
|
|
661
723
|
}
|
|
662
724
|
}
|
package/templates/package.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"prebuild:clean": "teardown prebuild --clean"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
+
"@gorhom/bottom-sheet": "^5",
|
|
14
15
|
"@react-navigation/bottom-tabs": "^7.2.0",
|
|
15
16
|
"@react-navigation/native": "^7.0.14",
|
|
16
17
|
"@react-navigation/native-stack": "^7.2.0",
|
|
@@ -19,13 +20,17 @@
|
|
|
19
20
|
"heroui-native": "^1.0.0-beta.12",
|
|
20
21
|
"react": "18.3.1",
|
|
21
22
|
"react-native": "0.78.2",
|
|
23
|
+
"react-native-gesture-handler": "^2.28.0",
|
|
22
24
|
"react-native-mmkv": "^3.2.0",
|
|
23
|
-
"react-native-reanimated": "
|
|
24
|
-
"react-native-safe-area-context": "
|
|
25
|
-
"react-native-screens": "~4.
|
|
26
|
-
"react-native-svg": "
|
|
27
|
-
"
|
|
28
|
-
"
|
|
25
|
+
"react-native-reanimated": "~4.1.1",
|
|
26
|
+
"react-native-safe-area-context": "~5.6.0",
|
|
27
|
+
"react-native-screens": "~4.16.0",
|
|
28
|
+
"react-native-svg": "15.12.1",
|
|
29
|
+
"react-native-worklets": "0.5.1",
|
|
30
|
+
"tailwind-merge": "^3.4.0",
|
|
31
|
+
"tailwind-variants": "^3.2.2",
|
|
32
|
+
"tailwindcss": "^4.0.0",
|
|
33
|
+
"uniwind": "^1.2.0"
|
|
29
34
|
},
|
|
30
35
|
"devDependencies": {
|
|
31
36
|
"@babel/core": "^7.26.0",
|