stackpatch 1.1.4 → 1.1.6

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 (37) hide show
  1. package/README.md +76 -69
  2. package/bin/stackpatch.js +79 -0
  3. package/bin/stackpatch.ts +2445 -3
  4. package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +124 -0
  5. package/boilerplate/auth/app/api/auth/signup/route.ts +45 -0
  6. package/boilerplate/auth/app/auth/login/page.tsx +24 -50
  7. package/boilerplate/auth/app/auth/signup/page.tsx +56 -69
  8. package/boilerplate/auth/app/dashboard/page.tsx +82 -0
  9. package/boilerplate/auth/app/login/page.tsx +136 -0
  10. package/boilerplate/auth/app/page.tsx +48 -0
  11. package/boilerplate/auth/components/auth-button.tsx +43 -0
  12. package/boilerplate/auth/components/auth-navbar.tsx +118 -0
  13. package/boilerplate/auth/components/protected-route.tsx +74 -0
  14. package/boilerplate/auth/components/session-provider.tsx +11 -0
  15. package/boilerplate/auth/middleware.ts +51 -0
  16. package/package.json +5 -6
  17. package/boilerplate/auth/app/stackpatch/page.tsx +0 -269
  18. package/boilerplate/auth/components/auth-wrapper.tsx +0 -61
  19. package/src/auth/generator.ts +0 -569
  20. package/src/auth/index.ts +0 -372
  21. package/src/auth/setup.ts +0 -293
  22. package/src/commands/add.ts +0 -112
  23. package/src/commands/create.ts +0 -128
  24. package/src/commands/revert.ts +0 -389
  25. package/src/config.ts +0 -52
  26. package/src/fileOps/copy.ts +0 -224
  27. package/src/fileOps/layout.ts +0 -304
  28. package/src/fileOps/protected.ts +0 -67
  29. package/src/index.ts +0 -215
  30. package/src/manifest.ts +0 -87
  31. package/src/ui/logo.ts +0 -24
  32. package/src/ui/progress.ts +0 -82
  33. package/src/utils/dependencies.ts +0 -114
  34. package/src/utils/deps-check.ts +0 -45
  35. package/src/utils/files.ts +0 -58
  36. package/src/utils/paths.ts +0 -217
  37. package/src/utils/scanner.ts +0 -109
package/src/index.ts DELETED
@@ -1,215 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- // Try to import dependencies and show helpful error if they're missing
4
- let chalk: typeof import("chalk").default;
5
- let inquirer: any;
6
-
7
- try {
8
- const chalkModule = await import("chalk");
9
- const inquirerModule = await import("inquirer");
10
- chalk = chalkModule.default;
11
- // Inquirer v13 uses default export
12
- inquirer = inquirerModule.default || inquirerModule;
13
- } catch (error: any) {
14
- if (error?.code === "ENOENT" || error?.message?.includes("Cannot find module")) {
15
- console.error("\n❌ Missing Dependencies\n");
16
- console.error("Required dependencies are not installed.");
17
- console.error("\nTo fix this, run:");
18
- console.error(" cd packages/cli");
19
- console.error(" pnpm install");
20
- console.error("\nOr from the project root:");
21
- console.error(" pnpm install");
22
- console.error();
23
- process.exit(1);
24
- }
25
- throw error;
26
- }
27
- import { PATCHES } from "./config.js";
28
- import { showLogo } from "./ui/logo.js";
29
- import { createProject } from "./commands/create.js";
30
- import { addPatch } from "./commands/add.js";
31
- import { revertPatch } from "./commands/revert.js";
32
-
33
- /**
34
- * Main CLI entry point
35
- */
36
- async function main(): Promise<void> {
37
- const args = process.argv.slice(2);
38
- const command = args[0];
39
- const projectName = args[1];
40
- const skipPrompts = args.includes("--yes") || args.includes("-y");
41
-
42
- // Show logo on startup
43
- showLogo();
44
-
45
- // Handle: bun create stackpatch@latest (no project name)
46
- // Show welcome and prompt for project name
47
- if (!command || command.startsWith("-")) {
48
- const { name } = await inquirer.prompt([
49
- {
50
- type: "input",
51
- name: "name",
52
- message: chalk.white("Enter your project name or path (relative to current directory)"),
53
- default: "my-stackpatch-app",
54
- validate: (input: string) => {
55
- if (!input.trim()) {
56
- return "Project name cannot be empty";
57
- }
58
- return true;
59
- },
60
- },
61
- ]);
62
- await createProject(name.trim(), false, skipPrompts); // Don't show welcome again
63
- return;
64
- }
65
-
66
- // Handle: npx stackpatch revert
67
- if (command === "revert") {
68
- await revertPatch();
69
- return;
70
- }
71
-
72
- // Handle: bun create stackpatch@latest my-app
73
- // When bun runs create, it passes project name as first arg (not "create")
74
- // Check if first arg looks like a project name (not a known command)
75
- // Always ask for project name first, even if provided
76
- if (
77
- command &&
78
- !["add", "create", "revert"].includes(command) &&
79
- !PATCHES[command] &&
80
- !command.startsWith("-")
81
- ) {
82
- // Likely called as: bun create stackpatch@latest my-app
83
- // But we'll ask for project name anyway to be consistent
84
- await showLogo();
85
- const { name } = await inquirer.prompt([
86
- {
87
- type: "input",
88
- name: "name",
89
- message: chalk.white("Enter your project name or path (relative to current directory)"),
90
- default: command || "my-stackpatch-app", // Use provided name as default
91
- validate: (input: string) => {
92
- if (!input.trim()) {
93
- return "Project name cannot be empty";
94
- }
95
- return true;
96
- },
97
- },
98
- ]);
99
- await createProject(name.trim(), false, skipPrompts); // Welcome already shown
100
- return;
101
- }
102
-
103
- // Handle: npx stackpatch create my-app
104
- if (command === "create") {
105
- if (!projectName) {
106
- showLogo();
107
- const { name } = await inquirer.prompt([
108
- {
109
- type: "input",
110
- name: "name",
111
- message: chalk.white("Enter your project name or path (relative to current directory)"),
112
- default: "my-stackpatch-app",
113
- validate: (input: string) => {
114
- if (!input.trim()) {
115
- return "Project name cannot be empty";
116
- }
117
- return true;
118
- },
119
- },
120
- ]);
121
- await createProject(name.trim(), false); // Logo already shown
122
- return;
123
- }
124
- await createProject(projectName, false, skipPrompts); // Logo already shown
125
- return;
126
- }
127
-
128
- // Handle: npx stackpatch add auth-ui
129
- const patchName = args[1];
130
- if (command === "add" && patchName) {
131
- await addPatch(patchName);
132
- return;
133
- }
134
-
135
- // If no command, show help or interactive mode
136
- if (!command) {
137
- await showLogo();
138
- console.log(chalk.yellow("Usage:"));
139
- console.log(chalk.white(" ") + chalk.cyan("npm create stackpatch@latest") + chalk.gray(" [project-name]"));
140
- console.log(chalk.white(" ") + chalk.cyan("npx create-stackpatch@latest") + chalk.gray(" [project-name]"));
141
- console.log(chalk.white(" ") + chalk.cyan("bunx create-stackpatch@latest") + chalk.gray(" [project-name]"));
142
- console.log(chalk.white(" ") + chalk.cyan("npx stackpatch create") + chalk.gray(" [project-name]"));
143
- console.log(chalk.white(" ") + chalk.cyan("npx stackpatch add") + chalk.white(" <patch-name>"));
144
- console.log(
145
- chalk.white(" ") + chalk.cyan("npx stackpatch revert") + chalk.gray(" - Revert a patch installation")
146
- );
147
- console.log(chalk.white("\nExamples:"));
148
- console.log(chalk.gray(" npm create stackpatch@latest my-app"));
149
- console.log(chalk.gray(" npx create-stackpatch@latest my-app"));
150
- console.log(chalk.gray(" bunx create-stackpatch@latest my-app"));
151
- console.log(chalk.gray(" npx stackpatch create my-app"));
152
- console.log(chalk.gray(" npx stackpatch add auth-ui"));
153
- console.log(chalk.gray("\n"));
154
- process.exit(0);
155
- }
156
-
157
- // Interactive mode (fallback)
158
- console.log(chalk.blue.bold("\n🚀 Welcome to StackPatch CLI\n"));
159
-
160
- let selectedPatch: string | null = null;
161
- let goBack = false;
162
-
163
- // 1️⃣ Select patch with back option
164
- do {
165
- const response = await inquirer.prompt([
166
- {
167
- type: "list",
168
- name: "patch",
169
- message: "Which patch do you want to add?",
170
- choices: [
171
- ...Object.keys(PATCHES)
172
- .filter((p) => p !== "auth-ui") // Don't show duplicate
173
- .map((p) => ({ name: p, value: p })),
174
- new inquirer.Separator(),
175
- {
176
- name: chalk.gray("← Go back / Cancel"),
177
- value: "back",
178
- },
179
- ],
180
- },
181
- ]);
182
-
183
- if (response.patch === "back") {
184
- goBack = true;
185
- break;
186
- }
187
-
188
- selectedPatch = response.patch;
189
- } while (!selectedPatch);
190
-
191
- if (goBack || !selectedPatch) {
192
- console.log(chalk.yellow("\n← Cancelled"));
193
- return;
194
- }
195
-
196
- const patch = selectedPatch;
197
-
198
- // 2️⃣ Enter target Next.js app folder
199
- const { target } = await inquirer.prompt([
200
- {
201
- type: "input",
202
- name: "target",
203
- message:
204
- "Enter the relative path to your Next.js app folder (e.g., ../../apps/stackpatch-frontend):",
205
- default: process.cwd(),
206
- },
207
- ]);
208
-
209
- await addPatch(patch, target);
210
- }
211
-
212
- main().catch((error) => {
213
- console.error(chalk.red("❌ Error:"), error);
214
- process.exit(1);
215
- });
package/src/manifest.ts DELETED
@@ -1,87 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import type { StackPatchManifest } from "./config.js";
4
-
5
- /**
6
- * Manifest management for tracking StackPatch installations
7
- */
8
-
9
- /**
10
- * Get manifest path for a target directory
11
- */
12
- export function getManifestPath(target: string): string {
13
- return path.join(target, ".stackpatch", "manifest.json");
14
- }
15
-
16
- /**
17
- * Read manifest if it exists
18
- */
19
- export function readManifest(target: string): StackPatchManifest | null {
20
- const manifestPath = getManifestPath(target);
21
- if (!fs.existsSync(manifestPath)) {
22
- return null;
23
- }
24
- try {
25
- const content = fs.readFileSync(manifestPath, "utf-8");
26
- return JSON.parse(content) as StackPatchManifest;
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- /**
33
- * Write manifest
34
- */
35
- export function writeManifest(target: string, manifest: StackPatchManifest): void {
36
- const manifestDir = path.join(target, ".stackpatch");
37
- const manifestPath = getManifestPath(target);
38
-
39
- if (!fs.existsSync(manifestDir)) {
40
- fs.mkdirSync(manifestDir, { recursive: true });
41
- }
42
-
43
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf-8");
44
- }
45
-
46
- /**
47
- * Backup a file before modifying it
48
- */
49
- export function backupFile(filePath: string, target: string): string | null {
50
- if (!fs.existsSync(filePath)) {
51
- return null;
52
- }
53
-
54
- const backupDir = path.join(target, ".stackpatch", "backups");
55
- if (!fs.existsSync(backupDir)) {
56
- fs.mkdirSync(backupDir, { recursive: true });
57
- }
58
-
59
- const relativePath = path.relative(target, filePath);
60
- const backupPath = path.join(backupDir, relativePath.replace(/\//g, "_").replace(/\\/g, "_"));
61
-
62
- // Create directory structure in backup
63
- const backupFileDir = path.dirname(backupPath);
64
- if (!fs.existsSync(backupFileDir)) {
65
- fs.mkdirSync(backupFileDir, { recursive: true });
66
- }
67
-
68
- fs.copyFileSync(filePath, backupPath);
69
- return backupPath;
70
- }
71
-
72
- /**
73
- * Restore a file from backup
74
- */
75
- export function restoreFile(backupPath: string, originalPath: string): boolean {
76
- if (!fs.existsSync(backupPath)) {
77
- return false;
78
- }
79
-
80
- const originalDir = path.dirname(originalPath);
81
- if (!fs.existsSync(originalDir)) {
82
- fs.mkdirSync(originalDir, { recursive: true });
83
- }
84
-
85
- fs.copyFileSync(backupPath, originalPath);
86
- return true;
87
- }
package/src/ui/logo.ts DELETED
@@ -1,24 +0,0 @@
1
- import chalk from "chalk";
2
-
3
- /**
4
- * Display StackPatch logo
5
- */
6
- export function showLogo(): void {
7
- console.log("\n");
8
-
9
- // StackPatch logo ASCII art
10
- const logo = [
11
- chalk.magentaBright(" _________ __ __ __________ __ .__"),
12
- chalk.magentaBright(" / _____// |______ ____ | | __ \\\\______ \\_____ _/ |_ ____ | |__"),
13
- chalk.magentaBright(" \\_____ \\\\ __\\__ \\ _/ ___\\| |/ / | ___/\\__ \\\\ __\\/ ___\\| | \\"),
14
- chalk.magentaBright(" / \\| | / __ \\\\ \\___| < | | / __ \\| | \\ \\___| Y \\"),
15
- chalk.magentaBright("/_______ /|__| (____ /\\___ >__|_ \\ |____| (____ /__| \\___ >___| /"),
16
- chalk.magentaBright(" \\/ \\/ \\/ \\/ \\/ \\/ \\/"),
17
- "",
18
- chalk.white(" Composable frontend features for modern React & Next.js"),
19
- chalk.gray(" Add authentication, UI components, and more with zero configuration"),
20
- "",
21
- ];
22
-
23
- logo.forEach((line) => console.log(line));
24
- }
@@ -1,82 +0,0 @@
1
- import chalk from "chalk";
2
-
3
- /**
4
- * Progress tracker with checkmarks
5
- */
6
- export class ProgressTracker {
7
- private steps: Array<{
8
- name: string;
9
- status: "pending" | "processing" | "completed" | "failed";
10
- interval?: NodeJS.Timeout;
11
- }> = [];
12
-
13
- addStep(name: string): void {
14
- this.steps.push({ name, status: "pending" });
15
- }
16
-
17
- startStep(index: number): void {
18
- if (index >= 0 && index < this.steps.length) {
19
- this.steps[index].status = "processing";
20
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
21
- let frameIndex = 0;
22
- const step = this.steps[index];
23
-
24
- const interval = setInterval(() => {
25
- process.stdout.write(`\r${chalk.yellow(frames[frameIndex])} ${step.name}`);
26
- frameIndex = (frameIndex + 1) % frames.length;
27
- }, 100);
28
-
29
- this.steps[index].interval = interval;
30
- }
31
- }
32
-
33
- completeStep(index: number): void {
34
- if (index >= 0 && index < this.steps.length) {
35
- if (this.steps[index].interval) {
36
- clearInterval(this.steps[index].interval);
37
- this.steps[index].interval = undefined;
38
- }
39
- process.stdout.write(`\r${chalk.green("✓")} ${this.steps[index].name}\n`);
40
- this.steps[index].status = "completed";
41
- }
42
- }
43
-
44
- failStep(index: number): void {
45
- if (index >= 0 && index < this.steps.length) {
46
- if (this.steps[index].interval) {
47
- clearInterval(this.steps[index].interval);
48
- this.steps[index].interval = undefined;
49
- }
50
- process.stdout.write(`\r${chalk.red("✗")} ${this.steps[index].name}\n`);
51
- this.steps[index].status = "failed";
52
- }
53
- }
54
-
55
- getSteps() {
56
- return this.steps;
57
- }
58
- }
59
-
60
- /**
61
- * Helper function with spinner and checkmark
62
- */
63
- export async function withSpinner<T>(text: string, fn: () => Promise<T> | T): Promise<T> {
64
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
65
- let frameIndex = 0;
66
-
67
- const interval = setInterval(() => {
68
- process.stdout.write(`\r${chalk.yellow(frames[frameIndex])} ${text}`);
69
- frameIndex = (frameIndex + 1) % frames.length;
70
- }, 100);
71
-
72
- try {
73
- const result = await fn();
74
- clearInterval(interval);
75
- process.stdout.write(`\r${chalk.green("✓")} ${text}\n`);
76
- return result;
77
- } catch (error) {
78
- clearInterval(interval);
79
- process.stdout.write(`\r${chalk.red("✗")} ${text}\n`);
80
- throw error;
81
- }
82
- }
@@ -1,114 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { spawnSync } from "child_process";
4
-
5
- /**
6
- * Utility functions for managing dependencies
7
- */
8
-
9
- /**
10
- * Check if dependency exists in package.json
11
- */
12
- export function hasDependency(target: string, depName: string): boolean {
13
- const packageJsonPath = path.join(target, "package.json");
14
- if (!fs.existsSync(packageJsonPath)) return false;
15
-
16
- try {
17
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
18
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
19
- return !!deps[depName];
20
- } catch {
21
- return false;
22
- }
23
- }
24
-
25
- /**
26
- * Install dependencies (only missing ones)
27
- */
28
- export function installDependencies(target: string, deps: string[]): void {
29
- if (deps.length === 0) return;
30
-
31
- // Check if target directory and package.json exist
32
- const packageJsonPath = path.join(target, "package.json");
33
- if (!fs.existsSync(target)) {
34
- throw new Error(`Target directory does not exist: ${target}`);
35
- }
36
- if (!fs.existsSync(packageJsonPath)) {
37
- throw new Error(`package.json not found in ${target}. Make sure you're in a valid project directory.`);
38
- }
39
-
40
- const missingDeps = deps.filter((dep) => !hasDependency(target, dep));
41
-
42
- if (missingDeps.length === 0) {
43
- return; // Already installed, spinner will show completion
44
- }
45
-
46
- // Use non-interactive flags to prevent credential prompts
47
- const result = spawnSync("pnpm", ["add", ...missingDeps], {
48
- cwd: target,
49
- stdio: "pipe",
50
- env: {
51
- ...process.env,
52
- // Prevent Git credential prompts
53
- GIT_TERMINAL_PROMPT: "0",
54
- GIT_ASKPASS: "",
55
- // Prevent npm/pnpm credential prompts
56
- NPM_CONFIG_PROGRESS: "false",
57
- },
58
- });
59
-
60
- if (result.status !== 0) {
61
- const errorOutput = result.stderr?.toString() || result.stdout?.toString() || "Unknown error";
62
- // Extract the actual error message if available
63
- const errorLines = errorOutput.split("\n").filter((line: string) =>
64
- line.trim() && (line.includes("error") || line.includes("Error") || line.includes("ERR"))
65
- );
66
- const errorMessage = errorLines.length > 0 ? errorLines.join("\n") : errorOutput;
67
- throw new Error(`Failed to install dependencies: ${missingDeps.join(", ")}\n${errorMessage}`);
68
- }
69
- }
70
-
71
- /**
72
- * Remove dependencies from package.json
73
- */
74
- export function removeDependencies(target: string, deps: string[]): boolean {
75
- if (deps.length === 0) return true;
76
-
77
- const packageJsonPath = path.join(target, "package.json");
78
- if (!fs.existsSync(packageJsonPath)) {
79
- return false;
80
- }
81
-
82
- try {
83
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
84
- let modified = false;
85
-
86
- // Remove from dependencies
87
- if (packageJson.dependencies) {
88
- for (const dep of deps) {
89
- if (packageJson.dependencies[dep]) {
90
- delete packageJson.dependencies[dep];
91
- modified = true;
92
- }
93
- }
94
- }
95
-
96
- // Remove from devDependencies
97
- if (packageJson.devDependencies) {
98
- for (const dep of deps) {
99
- if (packageJson.devDependencies[dep]) {
100
- delete packageJson.devDependencies[dep];
101
- modified = true;
102
- }
103
- }
104
- }
105
-
106
- if (modified) {
107
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
108
- }
109
-
110
- return modified;
111
- } catch {
112
- return false;
113
- }
114
- }
@@ -1,45 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- /**
5
- * Check if required dependencies are installed
6
- */
7
- export function checkDependencies(): { missing: string[]; allInstalled: boolean } {
8
- const requiredDeps = ["inquirer", "chalk", "fs-extra"];
9
- const missing: string[] = [];
10
-
11
- // Get the CLI directory
12
- // @ts-expect-error - Bun-specific API
13
- const CLI_DIR = import.meta.dir || path.dirname(new URL(import.meta.url).pathname);
14
- const nodeModulesPath = path.resolve(CLI_DIR, "../node_modules");
15
-
16
- for (const dep of requiredDeps) {
17
- const depPath = path.join(nodeModulesPath, dep);
18
- // Check if it exists (could be a symlink or directory)
19
- if (!fs.existsSync(depPath)) {
20
- missing.push(dep);
21
- }
22
- }
23
-
24
- return {
25
- missing,
26
- allInstalled: missing.length === 0,
27
- };
28
- }
29
-
30
- /**
31
- * Show helpful error message if dependencies are missing
32
- * Uses plain console.error to avoid importing chalk (which might not be installed)
33
- */
34
- export function showDependencyError(missing: string[]): void {
35
- console.error("\n❌ Missing Dependencies\n");
36
- console.error("The following dependencies are not installed:");
37
- missing.forEach((dep) => console.error(` - ${dep}`));
38
- console.error("\nTo fix this, run:");
39
- console.error(" cd packages/cli");
40
- console.error(" pnpm install");
41
- console.error("\nOr from the project root:");
42
- console.error(" pnpm install");
43
- console.error();
44
- process.exit(1);
45
- }
@@ -1,58 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- /**
5
- * Utility functions for file operations
6
- */
7
-
8
- /**
9
- * Remove empty directories recursively
10
- */
11
- export function removeEmptyDirectories(dirPath: string, rootPath: string): void {
12
- if (!fs.existsSync(dirPath)) return;
13
-
14
- // Don't remove the root directory or .stackpatch
15
- if (dirPath === rootPath || dirPath.includes(".stackpatch")) return;
16
-
17
- try {
18
- const entries = fs.readdirSync(dirPath);
19
-
20
- // Recursively remove empty subdirectories
21
- for (const entry of entries) {
22
- const fullPath = path.join(dirPath, entry);
23
- if (fs.statSync(fullPath).isDirectory()) {
24
- removeEmptyDirectories(fullPath, rootPath);
25
- }
26
- }
27
-
28
- // Check if directory is now empty (after removing subdirectories)
29
- const remainingEntries = fs.readdirSync(dirPath);
30
- if (remainingEntries.length === 0) {
31
- fs.rmdirSync(dirPath);
32
- }
33
- } catch {
34
- // Ignore errors when removing directories
35
- }
36
- }
37
-
38
- /**
39
- * Find all TypeScript/TSX files in a directory recursively
40
- */
41
- export function findTypeScriptFiles(dir: string, fileList: string[] = []): string[] {
42
- if (!fs.existsSync(dir)) return fileList;
43
-
44
- const files = fs.readdirSync(dir);
45
-
46
- for (const file of files) {
47
- const filePath = path.join(dir, file);
48
- const stat = fs.statSync(filePath);
49
-
50
- if (stat.isDirectory()) {
51
- findTypeScriptFiles(filePath, fileList);
52
- } else if (file.endsWith(".tsx") || file.endsWith(".ts")) {
53
- fileList.push(filePath);
54
- }
55
- }
56
-
57
- return fileList;
58
- }