@teardown/cli 2.0.57 → 2.0.58

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.57",
3
+ "version": "2.0.58",
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.57",
74
+ "@teardown/tsconfig": "2.0.58",
75
75
  "@types/bun": "1.3.5",
76
76
  "@types/ejs": "^3.1.5",
77
77
  "typescript": "5.9.3"
@@ -0,0 +1,264 @@
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 } from "../../config";
9
+ import { TemplateGenerator } from "../../templates";
10
+
11
+ /**
12
+ * Teardown packages that should be upgraded
13
+ */
14
+ const TEARDOWN_PACKAGES = [
15
+ "@teardown/cli",
16
+ "@teardown/dev-client",
17
+ "@teardown/navigation",
18
+ "@teardown/metro-config",
19
+ "@teardown/navigation-metro",
20
+ ];
21
+
22
+ /**
23
+ * Config files that can be upgraded from templates
24
+ */
25
+ const UPGRADABLE_CONFIG_FILES = ["metro.config.js", "babel.config.js", "react-native.config.js", "Gemfile"];
26
+
27
+ interface PackageJson {
28
+ name?: string;
29
+ version?: string;
30
+ dependencies?: Record<string, string>;
31
+ devDependencies?: Record<string, string>;
32
+ }
33
+
34
+ /**
35
+ * Fetch the latest version of a package from npm
36
+ */
37
+ async function fetchLatestVersion(packageName: string): Promise<string | null> {
38
+ return new Promise((resolve) => {
39
+ const child = spawn("npm", ["view", packageName, "version"], {
40
+ stdio: ["ignore", "pipe", "pipe"],
41
+ });
42
+
43
+ let stdout = "";
44
+ child.stdout?.on("data", (data) => {
45
+ stdout += data.toString();
46
+ });
47
+
48
+ child.on("close", (code) => {
49
+ if (code === 0) {
50
+ resolve(stdout.trim());
51
+ } else {
52
+ resolve(null);
53
+ }
54
+ });
55
+
56
+ child.on("error", () => {
57
+ resolve(null);
58
+ });
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Get the CLI package version
64
+ */
65
+ function getCliVersion(): string {
66
+ try {
67
+ const __filename = fileURLToPath(import.meta.url);
68
+ const __dirname = path.dirname(__filename);
69
+ const packageJsonPath = path.resolve(__dirname, "../../../package.json");
70
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
71
+ return packageJson.version;
72
+ } catch {
73
+ return "0.0.1";
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Create the upgrade command
79
+ */
80
+ export function createUpgradeCommand(): Command {
81
+ const command = new Command("upgrade")
82
+ .description("Upgrade Teardown packages and config files to the latest versions")
83
+ .option("-f, --force", "Force upgrade config files even if they exist", false)
84
+ .option("--skip-install", "Skip running bun install after upgrade", false)
85
+ .option("--skip-config", "Skip upgrading config files", false)
86
+ .option("--dry-run", "Show what would be upgraded without making changes", false)
87
+ .action(async (options) => {
88
+ const projectRoot = process.cwd();
89
+
90
+ // Check if this is a Teardown project
91
+ const configPath = findConfigFile(projectRoot);
92
+ if (!configPath) {
93
+ console.error(chalk.red("Error: This does not appear to be a Teardown project."));
94
+ console.log(chalk.gray("\nNo teardown.config.ts found."));
95
+ console.log(chalk.gray("Run 'teardown init <name>' to initialize a new project."));
96
+ process.exit(1);
97
+ }
98
+
99
+ console.log(chalk.cyan("\nTeardown Upgrade\n"));
100
+
101
+ // Check for package.json
102
+ const packageJsonPath = path.join(projectRoot, "package.json");
103
+ if (!fs.existsSync(packageJsonPath)) {
104
+ console.error(chalk.red("Error: No package.json found in project root."));
105
+ process.exit(1);
106
+ }
107
+
108
+ const packageJson: PackageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
109
+ const updates: { name: string; from: string; to: string }[] = [];
110
+
111
+ // Fetch latest versions and check for updates
112
+ const versionSpinner = ora("Checking for package updates...").start();
113
+
114
+ for (const pkg of TEARDOWN_PACKAGES) {
115
+ // Check if package is in dependencies or devDependencies
116
+ const currentVersion = packageJson.dependencies?.[pkg] || packageJson.devDependencies?.[pkg];
117
+
118
+ if (!currentVersion) {
119
+ continue;
120
+ }
121
+
122
+ const latestVersion = await fetchLatestVersion(pkg);
123
+ if (latestVersion) {
124
+ const cleanCurrentVersion = currentVersion.replace(/^\^|~/, "");
125
+ if (cleanCurrentVersion !== latestVersion) {
126
+ updates.push({
127
+ name: pkg,
128
+ from: currentVersion,
129
+ to: `^${latestVersion}`,
130
+ });
131
+ }
132
+ }
133
+ }
134
+
135
+ versionSpinner.succeed("Checked package versions");
136
+
137
+ // Display updates
138
+ if (updates.length > 0) {
139
+ console.log(chalk.yellow("\nPackages to update:"));
140
+ for (const update of updates) {
141
+ console.log(
142
+ chalk.gray(` ${update.name}: `) + chalk.red(update.from) + chalk.gray(" -> ") + chalk.green(update.to)
143
+ );
144
+ }
145
+ } else {
146
+ console.log(chalk.green("\nAll Teardown packages are up to date!"));
147
+ }
148
+
149
+ // Apply updates if not dry run
150
+ if (!options.dryRun && updates.length > 0) {
151
+ const updateSpinner = ora("Updating package.json...").start();
152
+
153
+ for (const update of updates) {
154
+ if (packageJson.dependencies?.[update.name]) {
155
+ packageJson.dependencies[update.name] = update.to;
156
+ }
157
+ if (packageJson.devDependencies?.[update.name]) {
158
+ packageJson.devDependencies[update.name] = update.to;
159
+ }
160
+ }
161
+
162
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, "\t") + "\n", "utf-8");
163
+ updateSpinner.succeed("Updated package.json");
164
+ }
165
+
166
+ // Upgrade config files
167
+ if (!options.skipConfig) {
168
+ console.log(chalk.yellow("\nConfig files:"));
169
+
170
+ // Read current config to get variables
171
+ const config = await import(configPath);
172
+ const configData = config.default || config;
173
+
174
+ const slug = configData.slug || configData.name?.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "app";
175
+ const appName = configData.name || "App";
176
+ const bundleIdentifier = configData.ios?.bundleIdentifier || `com.example.${slug.replace(/-/g, "")}`;
177
+ const packageName = configData.android?.packageName || bundleIdentifier;
178
+
179
+ const generator = new TemplateGenerator({
180
+ projectRoot,
181
+ variables: {
182
+ slug,
183
+ appName,
184
+ bundleIdentifier,
185
+ version: configData.version || "1.0.0",
186
+ buildNumber: configData.ios?.buildNumber || 1,
187
+ packageName,
188
+ versionCode: configData.android?.versionCode || 1,
189
+ versionName: configData.version || "1.0.0",
190
+ cliVersion: `^${getCliVersion()}`,
191
+ },
192
+ force: options.force,
193
+ dryRun: options.dryRun,
194
+ });
195
+
196
+ const configFilesUpdated: string[] = [];
197
+ const configFilesSkipped: string[] = [];
198
+
199
+ for (const file of UPGRADABLE_CONFIG_FILES) {
200
+ const filePath = path.join(projectRoot, file);
201
+ const exists = fs.existsSync(filePath);
202
+
203
+ if (exists && !options.force) {
204
+ configFilesSkipped.push(file);
205
+ console.log(chalk.gray(` ${file}: skipped (use --force to overwrite)`));
206
+ } else {
207
+ if (!options.dryRun) {
208
+ const result = await generator.generateRootConfigFiles();
209
+ if (result.filesCreated.some((f) => f.endsWith(file))) {
210
+ configFilesUpdated.push(file);
211
+ console.log(chalk.green(` ${file}: updated`));
212
+ }
213
+ } else {
214
+ console.log(chalk.cyan(` ${file}: would be updated`));
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ // Run bun install
221
+ if (!options.skipInstall && !options.dryRun && updates.length > 0) {
222
+ const installSpinner = ora("Installing updated dependencies...").start();
223
+ await new Promise<void>((resolve, reject) => {
224
+ const child = spawn("bun", ["install"], {
225
+ cwd: projectRoot,
226
+ stdio: ["ignore", "pipe", "pipe"],
227
+ });
228
+
229
+ let stderr = "";
230
+ child.stderr?.on("data", (data) => {
231
+ stderr += data.toString();
232
+ });
233
+
234
+ child.on("close", (code) => {
235
+ if (code === 0) {
236
+ installSpinner.succeed("Dependencies installed");
237
+ resolve();
238
+ } else {
239
+ installSpinner.fail("Failed to install dependencies");
240
+ console.error(chalk.red(stderr));
241
+ reject(new Error(`bun install exited with code ${code}`));
242
+ }
243
+ });
244
+
245
+ child.on("error", (err) => {
246
+ installSpinner.fail("Failed to run bun install");
247
+ reject(err);
248
+ });
249
+ });
250
+ }
251
+
252
+ // Summary
253
+ if (options.dryRun) {
254
+ console.log(chalk.cyan("\n[Dry run] No changes were made."));
255
+ } else if (updates.length > 0) {
256
+ console.log(chalk.green("\nUpgrade complete!"));
257
+ console.log(chalk.gray("\nNext steps:"));
258
+ console.log(chalk.cyan(" 1. teardown prebuild --clean") + chalk.gray(" - Regenerate native projects"));
259
+ console.log(chalk.cyan(" 2. teardown run ios") + chalk.gray(" or ") + chalk.cyan("teardown run android"));
260
+ }
261
+ });
262
+
263
+ return command;
264
+ }
package/src/cli/index.ts CHANGED
@@ -8,6 +8,7 @@ import { createInitCommand } from "./commands/init";
8
8
  import { createPluginsCommand } from "./commands/plugins";
9
9
  import { createPrebuildCommand } from "./commands/prebuild";
10
10
  import { createRunCommand } from "./commands/run";
11
+ import { createUpgradeCommand } from "./commands/upgrade";
11
12
  import { createValidateCommand } from "./commands/validate";
12
13
 
13
14
  /**
@@ -28,6 +29,7 @@ export function createProgram(): Command {
28
29
  program.addCommand(createPrebuildCommand());
29
30
  program.addCommand(createValidateCommand());
30
31
  program.addCommand(createInitCommand());
32
+ program.addCommand(createUpgradeCommand());
31
33
  program.addCommand(createPluginsCommand());
32
34
  program.addCommand(createRunCommand());
33
35
  program.addCommand(createDevCommand());
@@ -56,4 +58,5 @@ export { createPluginsCommand } from "./commands/plugins";
56
58
  // Export commands for testing
57
59
  export { createPrebuildCommand } from "./commands/prebuild";
58
60
  export { createRunCommand } from "./commands/run";
61
+ export { createUpgradeCommand } from "./commands/upgrade";
59
62
  export { createValidateCommand } from "./commands/validate";