stackpatch 1.1.4 → 1.1.5

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 (36) hide show
  1. package/README.md +70 -67
  2. package/bin/stackpatch.ts +2441 -2
  3. package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +124 -0
  4. package/boilerplate/auth/app/api/auth/signup/route.ts +45 -0
  5. package/boilerplate/auth/app/auth/login/page.tsx +24 -50
  6. package/boilerplate/auth/app/auth/signup/page.tsx +56 -69
  7. package/boilerplate/auth/app/dashboard/page.tsx +82 -0
  8. package/boilerplate/auth/app/login/page.tsx +136 -0
  9. package/boilerplate/auth/app/page.tsx +48 -0
  10. package/boilerplate/auth/components/auth-button.tsx +43 -0
  11. package/boilerplate/auth/components/auth-navbar.tsx +118 -0
  12. package/boilerplate/auth/components/protected-route.tsx +74 -0
  13. package/boilerplate/auth/components/session-provider.tsx +11 -0
  14. package/boilerplate/auth/middleware.ts +51 -0
  15. package/package.json +2 -4
  16. package/boilerplate/auth/app/stackpatch/page.tsx +0 -269
  17. package/boilerplate/auth/components/auth-wrapper.tsx +0 -61
  18. package/src/auth/generator.ts +0 -569
  19. package/src/auth/index.ts +0 -372
  20. package/src/auth/setup.ts +0 -293
  21. package/src/commands/add.ts +0 -112
  22. package/src/commands/create.ts +0 -128
  23. package/src/commands/revert.ts +0 -389
  24. package/src/config.ts +0 -52
  25. package/src/fileOps/copy.ts +0 -224
  26. package/src/fileOps/layout.ts +0 -304
  27. package/src/fileOps/protected.ts +0 -67
  28. package/src/index.ts +0 -215
  29. package/src/manifest.ts +0 -87
  30. package/src/ui/logo.ts +0 -24
  31. package/src/ui/progress.ts +0 -82
  32. package/src/utils/dependencies.ts +0 -114
  33. package/src/utils/deps-check.ts +0 -45
  34. package/src/utils/files.ts +0 -58
  35. package/src/utils/paths.ts +0 -217
  36. package/src/utils/scanner.ts +0 -109
@@ -1,112 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
- import chalk from "chalk";
5
- import { BOILERPLATE_ROOT, PATCHES, MANIFEST_VERSION, type StackPatchManifest } from "../config.js";
6
- import { detectTargetDirectory } from "../utils/paths.js";
7
- import { installDependencies } from "../utils/dependencies.js";
8
- import { withSpinner } from "../ui/progress.js";
9
- import { showLogo } from "../ui/logo.js";
10
- import { copyFiles } from "../fileOps/copy.js";
11
- import { writeManifest } from "../manifest.js";
12
-
13
- /**
14
- * Add a patch to an existing project
15
- */
16
- export async function addPatch(patchName: string, targetDir?: string): Promise<void> {
17
- if (!PATCHES[patchName]) {
18
- console.log(chalk.red(`❌ Unknown patch: ${patchName}`));
19
- console.log(chalk.yellow(`Available patches: ${Object.keys(PATCHES).join(", ")}`));
20
- process.exit(1);
21
- }
22
-
23
- // Auto-detect target directory
24
- let target = targetDir ? path.resolve(targetDir) : detectTargetDirectory();
25
-
26
- // If still can't find, ask user
27
- const hasAppDir = fs.existsSync(path.join(target, "app")) || fs.existsSync(path.join(target, "src", "app"));
28
- const hasPagesDir = fs.existsSync(path.join(target, "pages")) || fs.existsSync(path.join(target, "src", "pages"));
29
-
30
- if (!hasAppDir && !hasPagesDir) {
31
- console.log(chalk.yellow("⚠️ Could not auto-detect Next.js app directory."));
32
- const { userTarget } = await inquirer.prompt([
33
- {
34
- type: "input",
35
- name: "userTarget",
36
- message: "Enter the path to your Next.js app folder:",
37
- default: target,
38
- },
39
- ]);
40
- target = path.resolve(userTarget);
41
- }
42
-
43
- // For auth patches, use new setup flow
44
- if (patchName === "auth" || patchName === "auth-ui") {
45
- showLogo();
46
-
47
- // Use new setup flow
48
- const { setupAuthNew } = await import("../auth/index.js");
49
- const success = await setupAuthNew(target);
50
-
51
- if (!success) {
52
- console.log(chalk.yellow("\n⚠️ Auth setup was cancelled or failed."));
53
- return;
54
- }
55
-
56
- // Manifest is already created in setupAuthNew
57
- return;
58
- }
59
-
60
- // For other patches, use old flow
61
- const src = path.join(BOILERPLATE_ROOT, PATCHES[patchName].path);
62
-
63
- console.log(chalk.blue.bold("\n🚀 StackPatch CLI\n"));
64
- console.log(chalk.blue(`Copying ${patchName} patch to ${target}...\n`));
65
-
66
- const copyResult = await copyFiles(src, target);
67
- if (!copyResult.success) process.exit(1);
68
-
69
- const addedFiles = copyResult.addedFiles;
70
- const modifiedFiles: Array<{ path: string; originalContent: string }> = [];
71
-
72
- // Install dependencies (only if missing)
73
- installDependencies(target, PATCHES[patchName].dependencies);
74
-
75
- // Create manifest for tracking
76
- const manifest: StackPatchManifest = {
77
- version: MANIFEST_VERSION,
78
- patchName,
79
- target,
80
- timestamp: new Date().toISOString(),
81
- files: {
82
- added: addedFiles,
83
- modified: modifiedFiles,
84
- backedUp: [],
85
- },
86
- dependencies: PATCHES[patchName].dependencies,
87
- oauthProviders: [],
88
- };
89
- writeManifest(target, manifest);
90
-
91
- // Final next steps
92
- console.log(chalk.blue("\n🎉 Patch setup complete!"));
93
- console.log(chalk.green("\n📝 Next Steps:"));
94
- console.log(chalk.white(" 1. Configure OAuth providers (see instructions above)"));
95
- console.log(chalk.white(" 2. Set up database for email/password auth (see comments in code)"));
96
- console.log(chalk.white(" 3. Visit the landing page at ") + chalk.cyan("/stackpatch"));
97
- console.log(chalk.white(" 4. Protect your routes (see README.md)"));
98
- console.log(chalk.white(" 5. Run your Next.js dev server: ") + chalk.cyan("pnpm dev"));
99
- console.log(chalk.white(" 6. Test authentication at: ") + chalk.cyan("http://localhost:3000/auth/login\n"));
100
-
101
- console.log(chalk.blue.bold("📚 Documentation:"));
102
- console.log(chalk.white(" - See ") + chalk.cyan("README.md") + chalk.white(" for complete setup guide\n"));
103
-
104
- console.log(chalk.yellow("⚠️ Important:"));
105
- console.log(chalk.white(" - Email/password auth is in DEMO mode"));
106
- console.log(chalk.white(" - Demo credentials: ") + chalk.gray("demo@example.com / demo123"));
107
- console.log(
108
- chalk.white(" - See code comments in ") +
109
- chalk.cyan("https://better-auth.com") +
110
- chalk.white(" for implementation details\n")
111
- );
112
- }
@@ -1,128 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import fse from "fs-extra";
4
- import inquirer from "inquirer";
5
- import chalk from "chalk";
6
- import { spawnSync } from "child_process";
7
- import { BOILERPLATE_ROOT, PATCHES, MANIFEST_VERSION, type StackPatchManifest } from "../config.js";
8
- import { detectAppDirectory } from "../utils/paths.js";
9
- import { installDependencies } from "../utils/dependencies.js";
10
- import { ProgressTracker, withSpinner } from "../ui/progress.js";
11
- import { showLogo } from "../ui/logo.js";
12
- import { writeManifest } from "../manifest.js";
13
-
14
- /**
15
- * Create a new project from template
16
- */
17
- export async function createProject(
18
- projectName: string,
19
- showWelcomeScreen: boolean = true,
20
- forceOverwrite: boolean = false
21
- ): Promise<void> {
22
- const templatePath = path.join(BOILERPLATE_ROOT, "template");
23
- const targetPath = path.resolve(process.cwd(), projectName);
24
-
25
- if (fs.existsSync(targetPath)) {
26
- if (!forceOverwrite) {
27
- console.log(chalk.yellow(`⚠️ Directory "${projectName}" already exists!`));
28
- const { overwrite } = await inquirer.prompt([
29
- {
30
- type: "list",
31
- name: "overwrite",
32
- message: chalk.white("Do you want to overwrite it? (This will delete existing files)"),
33
- choices: [
34
- { name: "Yes, overwrite", value: "yes" },
35
- { name: "No, cancel", value: "no" },
36
- ],
37
- default: "no",
38
- },
39
- ]);
40
-
41
- if (overwrite !== "yes") {
42
- console.log(chalk.gray("Cancelled. Choose a different name."));
43
- process.exit(0);
44
- }
45
- }
46
-
47
- // Remove existing directory if overwriting
48
- console.log(chalk.yellow(`Removing existing directory "${projectName}"...`));
49
- fs.rmSync(targetPath, { recursive: true, force: true });
50
- }
51
-
52
- if (showWelcomeScreen) {
53
- showLogo();
54
- }
55
- console.log(chalk.blue.bold(`🚀 Creating new StackPatch project: ${chalk.white(projectName)}\n`));
56
-
57
- const tracker = new ProgressTracker();
58
- tracker.addStep("Copying project template");
59
- tracker.addStep("Processing project files");
60
- tracker.addStep("Installing dependencies");
61
-
62
- // Step 1: Copy template
63
- tracker.startStep(0);
64
- await fse.copy(templatePath, targetPath);
65
- tracker.completeStep(0);
66
-
67
- // Step 2: Replace placeholders in files
68
- tracker.startStep(1);
69
- // Detect app directory for template processing
70
- const appDir = detectAppDirectory(targetPath);
71
- const filesToProcess = [
72
- "package.json",
73
- `${appDir}/layout.tsx`,
74
- `${appDir}/page.tsx`,
75
- "README.md",
76
- ];
77
-
78
- for (const file of filesToProcess) {
79
- const filePath = path.join(targetPath, file);
80
- if (fs.existsSync(filePath)) {
81
- let content = fs.readFileSync(filePath, "utf-8");
82
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
83
- fs.writeFileSync(filePath, content, "utf-8");
84
- }
85
- }
86
- tracker.completeStep(1);
87
-
88
- // Step 3: Install dependencies
89
- tracker.startStep(2);
90
- const installResult = spawnSync("pnpm", ["install"], {
91
- cwd: targetPath,
92
- stdio: "pipe",
93
- env: {
94
- ...process.env,
95
- // Prevent Git credential prompts
96
- GIT_TERMINAL_PROMPT: "0",
97
- GIT_ASKPASS: "",
98
- // Prevent npm/pnpm credential prompts
99
- NPM_CONFIG_PROGRESS: "false",
100
- },
101
- });
102
-
103
- if (installResult.status !== 0) {
104
- tracker.failStep(2);
105
- console.log(chalk.yellow("\n⚠️ Dependency installation had issues. You can run 'pnpm install' manually."));
106
- } else {
107
- tracker.completeStep(2);
108
- }
109
-
110
- console.log(chalk.green(`\n✅ Project "${projectName}" created successfully!`));
111
-
112
- // Automatically add auth-ui after creating the project
113
- console.log(chalk.blue.bold(`\n🔐 Adding authentication to your project...\n`));
114
-
115
- // Use new setup flow
116
- const { setupAuthNew } = await import("../auth/index.js");
117
- const success = await setupAuthNew(targetPath);
118
-
119
- if (!success) {
120
- console.log(chalk.yellow("\n⚠️ Authentication setup was cancelled. You can run 'npx stackpatch add auth' manually."));
121
- }
122
-
123
- console.log(chalk.blue("\n📦 Next steps:"));
124
- console.log(chalk.white(` ${chalk.cyan("cd")} ${chalk.yellow(projectName)}`));
125
- console.log(chalk.white(` ${chalk.cyan("pnpm")} ${chalk.yellow("dev")}`));
126
- console.log(chalk.white(` Test authentication at: ${chalk.cyan("http://localhost:3000/auth/login")}`));
127
- console.log(chalk.gray("\n📚 See README.md for OAuth setup and protected routes\n"));
128
- }
@@ -1,389 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import inquirer from "inquirer";
4
- import chalk from "chalk";
5
- import { BOILERPLATE_ROOT } from "../config.js";
6
- import { readManifest } from "../manifest.js";
7
- import { restoreFile } from "../manifest.js";
8
- import { detectAppDirectory, detectComponentsDirectory, getParentDirectories } from "../utils/paths.js";
9
- import { removeDependencies } from "../utils/dependencies.js";
10
- import { removeEmptyDirectories } from "../utils/files.js";
11
-
12
- /**
13
- * Revert a StackPatch installation
14
- */
15
- export async function revertPatch(targetDir?: string): Promise<void> {
16
- let target = targetDir ? path.resolve(targetDir) : process.cwd();
17
-
18
- // Auto-detect target directory
19
- const hasAppDir = fs.existsSync(path.join(target, "app")) || fs.existsSync(path.join(target, "src", "app"));
20
- const hasPagesDir = fs.existsSync(path.join(target, "pages")) || fs.existsSync(path.join(target, "src", "pages"));
21
-
22
- if (!hasAppDir && !hasPagesDir) {
23
- const parent = path.resolve(target, "..");
24
- if (
25
- fs.existsSync(path.join(parent, "app")) ||
26
- fs.existsSync(path.join(parent, "src", "app")) ||
27
- fs.existsSync(path.join(parent, "pages")) ||
28
- fs.existsSync(path.join(parent, "src", "pages"))
29
- ) {
30
- target = parent;
31
- }
32
- }
33
-
34
- const manifest = readManifest(target);
35
- if (!manifest) {
36
- console.log(chalk.red("❌ No StackPatch installation found to revert."));
37
- console.log(chalk.yellow(" Make sure you're in the correct directory where you ran 'stackpatch add'."));
38
- process.exit(1);
39
- }
40
-
41
- console.log(chalk.blue.bold("\n🔄 Reverting StackPatch installation\n"));
42
- console.log(chalk.white(` Patch: ${chalk.cyan(manifest.patchName)}`));
43
- console.log(chalk.white(` Installed: ${chalk.gray(new Date(manifest.timestamp).toLocaleString())}\n`));
44
-
45
- // Show what will be reverted
46
- console.log(chalk.white(" Files to remove: ") + chalk.cyan(`${manifest.files.added.length}`));
47
- console.log(chalk.white(" Files to restore: ") + chalk.cyan(`${manifest.files.modified.length}`));
48
- if (manifest.dependencies.length > 0) {
49
- console.log(chalk.white(" Dependencies to remove: ") + chalk.cyan(`${manifest.dependencies.join(", ")}`));
50
- }
51
- console.log();
52
-
53
- const { confirm } = await inquirer.prompt([
54
- {
55
- type: "list",
56
- name: "confirm",
57
- message:
58
- "Are you sure you want to revert this installation? This will remove all added files, restore modified files, and remove dependencies.",
59
- choices: [
60
- { name: "Yes, revert the installation", value: "yes" },
61
- { name: "No, cancel", value: "no" },
62
- ],
63
- default: "no",
64
- },
65
- ]);
66
-
67
- if (confirm !== "yes") {
68
- console.log(chalk.yellow("\n← Revert cancelled"));
69
- return;
70
- }
71
-
72
- console.log(chalk.blue("\n🔄 Starting revert process...\n"));
73
-
74
- let removedCount = 0;
75
- let restoredCount = 0;
76
- let failedRemovals: string[] = [];
77
- let failedRestorations: string[] = [];
78
- const directoriesToCheck: Set<string> = new Set();
79
-
80
- // Step 1: Get list of valid StackPatch files from boilerplate
81
- // This ensures we only remove files that are actually from StackPatch
82
- const boilerplatePath = path.join(
83
- BOILERPLATE_ROOT,
84
- manifest.patchName === "auth-ui" ? "auth" : manifest.patchName
85
- );
86
- const validStackPatchFiles = new Set<string>();
87
-
88
- function collectBoilerplateFiles(srcDir: string, baseDir: string, targetBase: string): void {
89
- if (!fs.existsSync(srcDir)) return;
90
-
91
- const files = fs.readdirSync(srcDir, { withFileTypes: true });
92
- for (const file of files) {
93
- const srcFilePath = path.join(srcDir, file.name);
94
- if (file.isDirectory()) {
95
- collectBoilerplateFiles(srcFilePath, baseDir, targetBase);
96
- } else {
97
- const relativePath = path.relative(baseDir, srcFilePath);
98
- const targetPath = targetBase
99
- ? path.join(targetBase, relativePath).replace(/\\/g, "/")
100
- : relativePath.replace(/\\/g, "/");
101
- validStackPatchFiles.add(targetPath);
102
- }
103
- }
104
- }
105
-
106
- // Collect files from boilerplate app directory
107
- const appDir = detectAppDirectory(target);
108
- const componentsDir = detectComponentsDirectory(target);
109
- const boilerplateAppPath = path.join(boilerplatePath, "app");
110
- const boilerplateComponentsPath = path.join(boilerplatePath, "components");
111
-
112
- if (fs.existsSync(boilerplateAppPath)) {
113
- collectBoilerplateFiles(boilerplateAppPath, boilerplateAppPath, appDir);
114
- }
115
- if (fs.existsSync(boilerplateComponentsPath)) {
116
- collectBoilerplateFiles(boilerplateComponentsPath, boilerplateComponentsPath, componentsDir);
117
- }
118
-
119
- // Collect root-level files
120
- if (fs.existsSync(boilerplatePath)) {
121
- const entries = fs.readdirSync(boilerplatePath, { withFileTypes: true });
122
- for (const entry of entries) {
123
- if (entry.name !== "app" && entry.name !== "components") {
124
- const srcPath = path.join(boilerplatePath, entry.name);
125
- if (entry.isDirectory()) {
126
- collectBoilerplateFiles(srcPath, srcPath, "");
127
- } else {
128
- validStackPatchFiles.add(entry.name);
129
- }
130
- }
131
- }
132
- }
133
-
134
- // Step 1: Remove added files (only if they're actually from StackPatch boilerplate)
135
- console.log(chalk.white("📁 Removing added files..."));
136
- const filesToRemove = new Set<string>(); // Track files we actually removed
137
-
138
- for (const filePath of manifest.files.added) {
139
- // Only remove if this file is actually in the boilerplate
140
- if (!validStackPatchFiles.has(filePath)) {
141
- console.log(chalk.gray(` ⊘ Skipped (not in boilerplate): ${filePath}`));
142
- continue;
143
- }
144
-
145
- const fullPath = path.join(target, filePath);
146
- if (fs.existsSync(fullPath)) {
147
- try {
148
- // Double-check: Only remove if file content matches boilerplate (safety check)
149
- const boilerplateFilePath = path.join(boilerplatePath, filePath.replace(appDir + "/", "app/").replace(componentsDir + "/", "components/"));
150
- if (fs.existsSync(boilerplateFilePath)) {
151
- fs.unlinkSync(fullPath);
152
- console.log(chalk.green(` ✓ Removed: ${filePath}`));
153
- removedCount++;
154
- filesToRemove.add(filePath);
155
-
156
- // Track parent directories for cleanup (only for files we actually removed)
157
- const parentDirs = getParentDirectories(fullPath, target);
158
- parentDirs.forEach((dir) => directoriesToCheck.add(dir));
159
- } else {
160
- console.log(chalk.yellow(` ⚠ Skipped (safety check failed): ${filePath}`));
161
- }
162
- } catch (error) {
163
- console.log(chalk.yellow(` ⚠ Could not remove: ${filePath}`));
164
- failedRemovals.push(filePath);
165
- }
166
- } else {
167
- console.log(chalk.gray(` ⊘ Already removed: ${filePath}`));
168
- }
169
- }
170
-
171
- // Step 2: Remove .env.local and .env.example if they were created by StackPatch
172
- console.log(chalk.white("\n🔐 Removing environment files..."));
173
- const envFilesToRemove = manifest.files.envFiles || [];
174
-
175
- // Fallback: check for common env files if not tracked in manifest (for older manifests)
176
- if (envFilesToRemove.length === 0) {
177
- const commonEnvFiles = [".env.local", ".env.example"];
178
- for (const envFile of commonEnvFiles) {
179
- const envPath = path.join(target, envFile);
180
- if (fs.existsSync(envPath)) {
181
- try {
182
- // Check if this file was created by StackPatch (contains BETTER_AUTH_SECRET)
183
- const content = fs.readFileSync(envPath, "utf-8");
184
- if (content.includes("BETTER_AUTH_SECRET") || content.includes("BETTER_AUTH_URL")) {
185
- envFilesToRemove.push(envFile);
186
- }
187
- } catch {
188
- // Ignore errors
189
- }
190
- }
191
- }
192
- }
193
-
194
- for (const envFile of envFilesToRemove) {
195
- const envPath = path.join(target, envFile);
196
- if (fs.existsSync(envPath)) {
197
- try {
198
- fs.unlinkSync(envPath);
199
- console.log(chalk.green(` ✓ Removed: ${envFile}`));
200
- removedCount++;
201
- } catch (error) {
202
- console.log(chalk.yellow(` ⚠ Could not remove: ${envFile}`));
203
- failedRemovals.push(envFile);
204
- }
205
- } else {
206
- console.log(chalk.gray(` ⊘ Already removed: ${envFile}`));
207
- }
208
- }
209
-
210
- // Step 3: Restore modified files from originalContent in manifest
211
- // The manifest contains the original content before StackPatch modifications
212
- console.log(chalk.white("\n📝 Restoring modified files..."));
213
-
214
- for (const modified of manifest.files.modified) {
215
- const originalPath = path.join(target, modified.path);
216
-
217
- if (!fs.existsSync(originalPath)) {
218
- console.log(chalk.gray(` ⊘ File not found (may have been deleted): ${modified.path}`));
219
- continue;
220
- }
221
-
222
- if (modified.originalContent !== undefined) {
223
- try {
224
- // Restore from originalContent in manifest (most reliable - this is the exact original content)
225
- const originalDir = path.dirname(originalPath);
226
- if (!fs.existsSync(originalDir)) {
227
- fs.mkdirSync(originalDir, { recursive: true });
228
- }
229
- fs.writeFileSync(originalPath, modified.originalContent, "utf-8");
230
- console.log(chalk.green(` ✓ Restored: ${modified.path}`));
231
- restoredCount++;
232
- } catch (error) {
233
- // Fallback to backup file if originalContent restore fails
234
- const backupPath = path.join(
235
- target,
236
- ".stackpatch",
237
- "backups",
238
- modified.path.replace(/\//g, "_").replace(/\\/g, "_")
239
- );
240
- if (fs.existsSync(backupPath)) {
241
- try {
242
- restoreFile(backupPath, originalPath);
243
- console.log(chalk.green(` ✓ Restored (from backup): ${modified.path}`));
244
- restoredCount++;
245
- } catch (backupError) {
246
- console.log(chalk.yellow(` ⚠ Could not restore: ${modified.path}`));
247
- failedRestorations.push(modified.path);
248
- }
249
- } else {
250
- console.log(chalk.yellow(` ⚠ Could not restore: ${modified.path} (no backup found)`));
251
- failedRestorations.push(modified.path);
252
- }
253
- }
254
- } else {
255
- // Fallback: try to restore from backup file
256
- const backupPath = path.join(
257
- target,
258
- ".stackpatch",
259
- "backups",
260
- modified.path.replace(/\//g, "_").replace(/\\/g, "_")
261
- );
262
- if (fs.existsSync(backupPath)) {
263
- try {
264
- restoreFile(backupPath, originalPath);
265
- console.log(chalk.green(` ✓ Restored (from backup): ${modified.path}`));
266
- restoredCount++;
267
- } catch (error) {
268
- console.log(chalk.yellow(` ⚠ Could not restore: ${modified.path}`));
269
- failedRestorations.push(modified.path);
270
- }
271
- } else {
272
- console.log(chalk.yellow(` ⚠ Backup not found and no originalContent: ${modified.path}`));
273
- failedRestorations.push(modified.path);
274
- }
275
- }
276
- }
277
-
278
- // Step 4: Remove dependencies from package.json
279
- if (manifest.dependencies.length > 0) {
280
- console.log(chalk.white("\n📦 Removing dependencies from package.json..."));
281
- const removed = removeDependencies(target, manifest.dependencies);
282
- if (removed) {
283
- console.log(chalk.green(` ✓ Removed dependencies: ${manifest.dependencies.join(", ")}`));
284
- console.log(chalk.yellow(" ⚠ Run 'pnpm install' to update node_modules"));
285
- } else {
286
- console.log(chalk.gray(" ⊘ Dependencies not found in package.json"));
287
- }
288
- }
289
-
290
- // Step 5: Clean up empty directories (only if they only contained StackPatch files)
291
- console.log(chalk.white("\n🧹 Cleaning up empty directories..."));
292
- const sortedDirs = Array.from(directoriesToCheck).sort((a, b) => b.length - a.length); // Sort by depth (deepest first)
293
- let removedDirCount = 0;
294
-
295
- // Only check directories that are known StackPatch directories
296
- const stackPatchDirs = new Set<string>();
297
- for (const filePath of filesToRemove) {
298
- const dir = path.dirname(path.join(target, filePath));
299
- let currentDir = dir;
300
- while (currentDir !== target && currentDir.length > target.length) {
301
- stackPatchDirs.add(currentDir);
302
- currentDir = path.dirname(currentDir);
303
- }
304
- }
305
-
306
- for (const dir of sortedDirs) {
307
- // Only remove directories that are in our StackPatch directories set
308
- if (!stackPatchDirs.has(dir)) {
309
- continue;
310
- }
311
-
312
- if (fs.existsSync(dir)) {
313
- try {
314
- const entries = fs.readdirSync(dir);
315
- if (entries.length === 0) {
316
- // Only remove if directory is empty AND it's a known StackPatch directory
317
- fs.rmdirSync(dir);
318
- removedDirCount++;
319
- console.log(chalk.green(` ✓ Removed empty directory: ${path.relative(target, dir)}`));
320
- } else {
321
- // Directory has other files - check if they're all StackPatch files
322
- const allStackPatchFiles = entries.every((entry) => {
323
- const entryPath = path.join(dir, entry);
324
- const relativePath = path.relative(target, entryPath).replace(/\\/g, "/");
325
- return filesToRemove.has(relativePath) || validStackPatchFiles.has(relativePath);
326
- });
327
-
328
- if (allStackPatchFiles && entries.length > 0) {
329
- // All files are StackPatch files, but directory isn't empty - this shouldn't happen
330
- // Skip removal to be safe
331
- console.log(chalk.gray(` ⊘ Skipped (has other files): ${path.relative(target, dir)}`));
332
- }
333
- }
334
- // If directory has other files, we don't remove it (silently skip)
335
- } catch {
336
- // Ignore errors
337
- }
338
- }
339
- }
340
-
341
- if (removedDirCount === 0) {
342
- console.log(chalk.gray(" ⊘ No empty directories to remove"));
343
- }
344
-
345
- // Step 6: Remove manifest and backups
346
- console.log(chalk.white("\n🗑️ Removing StackPatch tracking files..."));
347
- const stackpatchDir = path.join(target, ".stackpatch");
348
- if (fs.existsSync(stackpatchDir)) {
349
- try {
350
- fs.rmSync(stackpatchDir, { recursive: true, force: true });
351
- console.log(chalk.green(" ✓ Removed .stackpatch directory"));
352
- } catch (error) {
353
- console.log(chalk.yellow(" ⚠ Could not remove .stackpatch directory"));
354
- }
355
- }
356
-
357
- // Step 7: Verification
358
- console.log(chalk.white("\n✅ Verification..."));
359
- const remainingManifest = readManifest(target);
360
- if (remainingManifest) {
361
- console.log(chalk.red(" ❌ Warning: Manifest still exists. Revert may be incomplete."));
362
- } else {
363
- console.log(chalk.green(" ✓ Manifest removed successfully"));
364
- }
365
-
366
- // Summary
367
- console.log(chalk.blue.bold("\n📊 Revert Summary:"));
368
- console.log(chalk.white(` Files removed: ${chalk.green(removedCount)}`));
369
- console.log(chalk.white(` Files restored: ${chalk.green(restoredCount)}`));
370
- if (failedRemovals.length > 0) {
371
- console.log(chalk.yellow(` Failed removals: ${failedRemovals.length}`));
372
- failedRemovals.forEach((file) => console.log(chalk.gray(` - ${file}`)));
373
- }
374
- if (failedRestorations.length > 0) {
375
- console.log(chalk.yellow(` Failed restorations: ${failedRestorations.length}`));
376
- failedRestorations.forEach((file) => console.log(chalk.gray(` - ${file}`)));
377
- }
378
-
379
- if (failedRemovals.length === 0 && failedRestorations.length === 0 && !remainingManifest) {
380
- console.log(
381
- chalk.green("\n✅ Revert complete! Your project has been fully restored to its original state.")
382
- );
383
- if (manifest.dependencies.length > 0) {
384
- console.log(chalk.yellow("\n⚠️ Remember to run 'pnpm install' to update your node_modules."));
385
- }
386
- } else {
387
- console.log(chalk.yellow("\n⚠️ Revert completed with some warnings. Please review the output above."));
388
- }
389
- }
package/src/config.ts DELETED
@@ -1,52 +0,0 @@
1
- import path from "path";
2
-
3
- /**
4
- * Configuration constants and types for StackPatch CLI
5
- */
6
-
7
- // Get directory path - Bun supports import.meta.dir
8
- // @ts-expect-error - Bun-specific API, not in Node types
9
- const CLI_DIR = import.meta.dir || path.dirname(new URL(import.meta.url).pathname);
10
-
11
- // Resolve boilerplate path - use local boilerplate inside CLI package
12
- export const BOILERPLATE_ROOT = path.resolve(CLI_DIR, "../boilerplate");
13
-
14
- /**
15
- * Available patches and their configurations
16
- */
17
- export const PATCHES: Record<string, { path: string; dependencies: string[] }> = {
18
- auth: {
19
- path: "auth",
20
- dependencies: ["better-auth", "react-hot-toast"],
21
- },
22
- "auth-ui": {
23
- path: "auth",
24
- dependencies: ["better-auth", "react-hot-toast"],
25
- },
26
- // Example for future patches:
27
- // stripe: { path: "stripe", dependencies: ["stripe"] },
28
- // redux: { path: "redux", dependencies: ["@reduxjs/toolkit", "react-redux"] },
29
- };
30
-
31
- /**
32
- * Manifest structure for tracking changes
33
- */
34
- export interface StackPatchManifest {
35
- version: string;
36
- patchName: string;
37
- target: string;
38
- timestamp: string;
39
- files: {
40
- added: string[];
41
- modified: {
42
- path: string;
43
- originalContent: string;
44
- }[];
45
- backedUp: string[];
46
- envFiles?: string[]; // Track .env.local and .env.example if created
47
- };
48
- dependencies: string[];
49
- oauthProviders: string[];
50
- }
51
-
52
- export const MANIFEST_VERSION = "1.0.0";