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.
- package/README.md +76 -69
- package/bin/stackpatch.js +79 -0
- package/bin/stackpatch.ts +2445 -3
- package/boilerplate/auth/app/api/auth/[...nextauth]/route.ts +124 -0
- package/boilerplate/auth/app/api/auth/signup/route.ts +45 -0
- package/boilerplate/auth/app/auth/login/page.tsx +24 -50
- package/boilerplate/auth/app/auth/signup/page.tsx +56 -69
- package/boilerplate/auth/app/dashboard/page.tsx +82 -0
- package/boilerplate/auth/app/login/page.tsx +136 -0
- package/boilerplate/auth/app/page.tsx +48 -0
- package/boilerplate/auth/components/auth-button.tsx +43 -0
- package/boilerplate/auth/components/auth-navbar.tsx +118 -0
- package/boilerplate/auth/components/protected-route.tsx +74 -0
- package/boilerplate/auth/components/session-provider.tsx +11 -0
- package/boilerplate/auth/middleware.ts +51 -0
- package/package.json +5 -6
- package/boilerplate/auth/app/stackpatch/page.tsx +0 -269
- package/boilerplate/auth/components/auth-wrapper.tsx +0 -61
- package/src/auth/generator.ts +0 -569
- package/src/auth/index.ts +0 -372
- package/src/auth/setup.ts +0 -293
- package/src/commands/add.ts +0 -112
- package/src/commands/create.ts +0 -128
- package/src/commands/revert.ts +0 -389
- package/src/config.ts +0 -52
- package/src/fileOps/copy.ts +0 -224
- package/src/fileOps/layout.ts +0 -304
- package/src/fileOps/protected.ts +0 -67
- package/src/index.ts +0 -215
- package/src/manifest.ts +0 -87
- package/src/ui/logo.ts +0 -24
- package/src/ui/progress.ts +0 -82
- package/src/utils/dependencies.ts +0 -114
- package/src/utils/deps-check.ts +0 -45
- package/src/utils/files.ts +0 -58
- package/src/utils/paths.ts +0 -217
- package/src/utils/scanner.ts +0 -109
package/src/fileOps/copy.ts
DELETED
|
@@ -1,224 +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 {
|
|
7
|
-
detectAppDirectory,
|
|
8
|
-
detectComponentsDirectory,
|
|
9
|
-
generateComponentImportPath,
|
|
10
|
-
} from "../utils/paths.js";
|
|
11
|
-
import { findTypeScriptFiles } from "../utils/files.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* File copying operations
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Copy files from source to destination with smart directory detection
|
|
19
|
-
*/
|
|
20
|
-
export async function copyFiles(
|
|
21
|
-
src: string,
|
|
22
|
-
dest: string
|
|
23
|
-
): Promise<{ success: boolean; addedFiles: string[] }> {
|
|
24
|
-
const addedFiles: string[] = [];
|
|
25
|
-
|
|
26
|
-
if (!fs.existsSync(src)) {
|
|
27
|
-
console.log(chalk.red(`❌ Boilerplate folder not found: ${src}`));
|
|
28
|
-
return { success: false, addedFiles: [] };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await fse.ensureDir(dest);
|
|
32
|
-
|
|
33
|
-
// Detect app directory location in target
|
|
34
|
-
const appDir = detectAppDirectory(dest);
|
|
35
|
-
const appDirPath = path.join(dest, appDir);
|
|
36
|
-
const componentsDir = detectComponentsDirectory(dest);
|
|
37
|
-
const componentsDirPath = path.join(dest, componentsDir);
|
|
38
|
-
|
|
39
|
-
// Detect lib directory (src/lib or lib)
|
|
40
|
-
const hasSrcDir = fs.existsSync(path.join(dest, "src"));
|
|
41
|
-
const libDir = hasSrcDir ? "src/lib" : "lib";
|
|
42
|
-
const libDirPath = path.join(dest, libDir);
|
|
43
|
-
|
|
44
|
-
const conflicts: string[] = [];
|
|
45
|
-
|
|
46
|
-
// Check for conflicts before copying
|
|
47
|
-
const entries = fse.readdirSync(src, { withFileTypes: true });
|
|
48
|
-
for (const entry of entries) {
|
|
49
|
-
if (entry.name === "app") {
|
|
50
|
-
// For app directory, check conflicts in the detected app directory
|
|
51
|
-
if (fs.existsSync(appDirPath)) {
|
|
52
|
-
const appEntries = fse.readdirSync(path.join(src, "app"), { withFileTypes: true });
|
|
53
|
-
for (const appEntry of appEntries) {
|
|
54
|
-
const destAppPath = path.join(appDirPath, appEntry.name);
|
|
55
|
-
if (fs.existsSync(destAppPath)) {
|
|
56
|
-
conflicts.push(destAppPath);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
} else if (entry.name === "components") {
|
|
61
|
-
// For components directory, check conflicts in the detected components directory
|
|
62
|
-
if (fs.existsSync(componentsDirPath)) {
|
|
63
|
-
const componentEntries = fse.readdirSync(path.join(src, "components"), {
|
|
64
|
-
withFileTypes: true,
|
|
65
|
-
});
|
|
66
|
-
for (const componentEntry of componentEntries) {
|
|
67
|
-
const destComponentPath = path.join(componentsDirPath, componentEntry.name);
|
|
68
|
-
if (fs.existsSync(destComponentPath)) {
|
|
69
|
-
conflicts.push(destComponentPath);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
} else if (entry.name === "lib") {
|
|
74
|
-
// For lib directory, check conflicts in the detected lib directory
|
|
75
|
-
if (fs.existsSync(libDirPath)) {
|
|
76
|
-
const libEntries = fse.readdirSync(path.join(src, "lib"), {
|
|
77
|
-
withFileTypes: true,
|
|
78
|
-
});
|
|
79
|
-
for (const libEntry of libEntries) {
|
|
80
|
-
const destLibPath = path.join(libDirPath, libEntry.name);
|
|
81
|
-
if (fs.existsSync(destLibPath)) {
|
|
82
|
-
conflicts.push(destLibPath);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
// For other files/directories (middleware, etc.), check in root
|
|
88
|
-
const destPath = path.join(dest, entry.name);
|
|
89
|
-
if (fs.existsSync(destPath)) {
|
|
90
|
-
conflicts.push(destPath);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (conflicts.length) {
|
|
96
|
-
console.log(chalk.yellow("\n⚠️ The following files already exist:"));
|
|
97
|
-
conflicts.forEach((f) => console.log(` ${f}`));
|
|
98
|
-
|
|
99
|
-
const { overwrite } = await inquirer.prompt([
|
|
100
|
-
{
|
|
101
|
-
type: "list",
|
|
102
|
-
name: "overwrite",
|
|
103
|
-
message: "Do you want to overwrite them?",
|
|
104
|
-
choices: [
|
|
105
|
-
{ name: "Yes, overwrite", value: "yes" },
|
|
106
|
-
{ name: "No, skip", value: "no" },
|
|
107
|
-
],
|
|
108
|
-
default: "no",
|
|
109
|
-
},
|
|
110
|
-
]);
|
|
111
|
-
|
|
112
|
-
if (overwrite !== "yes") {
|
|
113
|
-
console.log(chalk.red("\nAborted! No files were copied."));
|
|
114
|
-
return { success: false, addedFiles: [] };
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Track files from SOURCE (boilerplate) before copying
|
|
119
|
-
// This ensures we only track files that are actually from StackPatch
|
|
120
|
-
function trackSourceFiles(srcDir: string, baseDir: string, targetBase: string): void {
|
|
121
|
-
if (!fs.existsSync(srcDir)) return;
|
|
122
|
-
|
|
123
|
-
const files = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
124
|
-
for (const file of files) {
|
|
125
|
-
const srcFilePath = path.join(srcDir, file.name);
|
|
126
|
-
if (file.isDirectory()) {
|
|
127
|
-
trackSourceFiles(srcFilePath, baseDir, targetBase);
|
|
128
|
-
} else {
|
|
129
|
-
const relativePath = path.relative(baseDir, srcFilePath);
|
|
130
|
-
const targetPath = targetBase
|
|
131
|
-
? path.join(targetBase, relativePath).replace(/\\/g, "/")
|
|
132
|
-
: relativePath.replace(/\\/g, "/");
|
|
133
|
-
addedFiles.push(targetPath);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Copy files with smart app directory handling
|
|
139
|
-
for (const entry of entries) {
|
|
140
|
-
const srcPath = path.join(src, entry.name);
|
|
141
|
-
|
|
142
|
-
if (entry.name === "app") {
|
|
143
|
-
// Track files from SOURCE boilerplate before copying
|
|
144
|
-
trackSourceFiles(srcPath, srcPath, appDir);
|
|
145
|
-
|
|
146
|
-
// Copy app directory contents to the detected app directory location
|
|
147
|
-
await fse.ensureDir(appDirPath);
|
|
148
|
-
await fse.copy(srcPath, appDirPath, { overwrite: true });
|
|
149
|
-
} else if (entry.name === "components") {
|
|
150
|
-
// Track files from SOURCE boilerplate before copying
|
|
151
|
-
trackSourceFiles(srcPath, srcPath, componentsDir);
|
|
152
|
-
|
|
153
|
-
// Copy components directory to the detected components directory location
|
|
154
|
-
await fse.ensureDir(componentsDirPath);
|
|
155
|
-
await fse.copy(srcPath, componentsDirPath, { overwrite: true });
|
|
156
|
-
} else if (entry.name === "lib") {
|
|
157
|
-
// Track files from SOURCE boilerplate before copying
|
|
158
|
-
trackSourceFiles(srcPath, srcPath, libDir);
|
|
159
|
-
|
|
160
|
-
// Copy lib directory to the detected lib directory location
|
|
161
|
-
await fse.ensureDir(libDirPath);
|
|
162
|
-
await fse.copy(srcPath, libDirPath, { overwrite: true });
|
|
163
|
-
} else {
|
|
164
|
-
// For root-level files/directories, track from source
|
|
165
|
-
const srcStat = fs.statSync(srcPath);
|
|
166
|
-
if (srcStat.isDirectory()) {
|
|
167
|
-
trackSourceFiles(srcPath, srcPath, "");
|
|
168
|
-
} else {
|
|
169
|
-
addedFiles.push(entry.name);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Copy other files/directories (middleware, etc.) to root
|
|
173
|
-
const destPath = path.join(dest, entry.name);
|
|
174
|
-
await fse.copy(srcPath, destPath, { overwrite: true });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Update imports in copied files to use correct paths
|
|
179
|
-
updateImportsInFiles(dest);
|
|
180
|
-
|
|
181
|
-
return { success: true, addedFiles };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Update imports in copied files to use correct paths
|
|
186
|
-
*/
|
|
187
|
-
function updateImportsInFiles(target: string): void {
|
|
188
|
-
const appDir = detectAppDirectory(target);
|
|
189
|
-
const appDirPath = path.join(target, appDir);
|
|
190
|
-
|
|
191
|
-
if (!fs.existsSync(appDirPath)) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const files = findTypeScriptFiles(appDirPath);
|
|
196
|
-
|
|
197
|
-
for (const filePath of files) {
|
|
198
|
-
try {
|
|
199
|
-
let content = fs.readFileSync(filePath, "utf-8");
|
|
200
|
-
let updated = false;
|
|
201
|
-
|
|
202
|
-
// Match imports like: from "@/components/component-name"
|
|
203
|
-
const importRegex = /from\s+["']@\/components\/([^"']+)["']/g;
|
|
204
|
-
const matches = Array.from(content.matchAll(importRegex));
|
|
205
|
-
|
|
206
|
-
for (const match of matches) {
|
|
207
|
-
const componentName = (match as RegExpMatchArray)[1];
|
|
208
|
-
const oldImport = (match as RegExpMatchArray)[0];
|
|
209
|
-
const newImportPath = generateComponentImportPath(target, componentName, filePath);
|
|
210
|
-
const newImport = oldImport.replace(/@\/components\/[^"']+/, newImportPath);
|
|
211
|
-
|
|
212
|
-
content = content.replace(oldImport, newImport);
|
|
213
|
-
updated = true;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (updated) {
|
|
217
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
218
|
-
}
|
|
219
|
-
} catch (error) {
|
|
220
|
-
// Silently skip files that can't be processed
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
package/src/fileOps/layout.ts
DELETED
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { detectAppDirectory } from "../utils/paths.js";
|
|
5
|
-
import { generateComponentImportPath } from "../utils/paths.js";
|
|
6
|
-
import { backupFile } from "../manifest.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Layout file modification operations
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export interface LayoutUpdateResult {
|
|
13
|
-
success: boolean;
|
|
14
|
-
modified: boolean;
|
|
15
|
-
filePath: string;
|
|
16
|
-
originalContent?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Update layout.tsx to include Toaster
|
|
21
|
-
*/
|
|
22
|
-
export function updateLayoutForToaster(
|
|
23
|
-
target: string
|
|
24
|
-
): LayoutUpdateResult {
|
|
25
|
-
const appDir = detectAppDirectory(target);
|
|
26
|
-
const layoutPath = path.join(target, appDir, "layout.tsx");
|
|
27
|
-
|
|
28
|
-
if (!fs.existsSync(layoutPath)) {
|
|
29
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const originalContent = fs.readFileSync(layoutPath, "utf-8");
|
|
34
|
-
let layoutContent = originalContent;
|
|
35
|
-
|
|
36
|
-
// Check if already has Toaster
|
|
37
|
-
if (layoutContent.includes("Toaster")) {
|
|
38
|
-
console.log(chalk.green("✅ Layout already has Toaster!"));
|
|
39
|
-
return { success: true, modified: false, filePath: layoutPath };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Generate the correct import path
|
|
43
|
-
const importPath = generateComponentImportPath(target, "toaster", layoutPath);
|
|
44
|
-
|
|
45
|
-
// Check if import already exists (check for various patterns)
|
|
46
|
-
const hasImport =
|
|
47
|
-
layoutContent.includes("toaster") &&
|
|
48
|
-
(layoutContent.includes("from") || layoutContent.includes("import"));
|
|
49
|
-
|
|
50
|
-
if (!hasImport) {
|
|
51
|
-
// Find the last import statement
|
|
52
|
-
const lines = layoutContent.split("\n");
|
|
53
|
-
let lastImportIndex = -1;
|
|
54
|
-
|
|
55
|
-
for (let i = 0; i < lines.length; i++) {
|
|
56
|
-
const trimmed = lines[i].trim();
|
|
57
|
-
if (trimmed.startsWith("import ") && trimmed.endsWith(";")) {
|
|
58
|
-
lastImportIndex = i;
|
|
59
|
-
} else if (trimmed && !trimmed.startsWith("//") && lastImportIndex >= 0) {
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (lastImportIndex >= 0) {
|
|
65
|
-
lines.splice(lastImportIndex + 1, 0, `import { Toaster } from "${importPath}";`);
|
|
66
|
-
layoutContent = lines.join("\n");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Add Toaster component
|
|
71
|
-
if (layoutContent.includes("{children}")) {
|
|
72
|
-
// Add Toaster after children
|
|
73
|
-
layoutContent = layoutContent.replace(/(\{children\})/, '$1\n <Toaster />');
|
|
74
|
-
} else {
|
|
75
|
-
// Try to find body tag and add Toaster before closing body
|
|
76
|
-
const bodyRegex = /(<body[^>]*>)([\s\S]*?)(<\/body>)/;
|
|
77
|
-
if (bodyRegex.test(layoutContent)) {
|
|
78
|
-
layoutContent = layoutContent.replace(bodyRegex, '$1$2\n <Toaster />\n $3');
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Backup before modifying
|
|
83
|
-
backupFile(layoutPath, target);
|
|
84
|
-
|
|
85
|
-
fs.writeFileSync(layoutPath, layoutContent, "utf-8");
|
|
86
|
-
console.log(chalk.green("✅ Updated layout.tsx with Toaster!"));
|
|
87
|
-
|
|
88
|
-
const relativePath = path.relative(target, layoutPath).replace(/\\/g, "/");
|
|
89
|
-
return { success: true, modified: true, filePath: relativePath, originalContent };
|
|
90
|
-
} catch (error: unknown) {
|
|
91
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
92
|
-
console.log(chalk.yellow(`⚠️ Failed to update layout with Toaster: ${errorMessage}`));
|
|
93
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Update layout.tsx to include AuthWrapper
|
|
99
|
-
* This wrapper provides client-side route protection
|
|
100
|
-
*/
|
|
101
|
-
export function updateLayoutForAuthWrapper(target: string): LayoutUpdateResult {
|
|
102
|
-
const appDir = detectAppDirectory(target);
|
|
103
|
-
const layoutPath = path.join(target, appDir, "layout.tsx");
|
|
104
|
-
|
|
105
|
-
if (!fs.existsSync(layoutPath)) {
|
|
106
|
-
console.log(chalk.yellow("⚠️ layout.tsx not found. Skipping layout update."));
|
|
107
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const originalContent = fs.readFileSync(layoutPath, "utf-8");
|
|
112
|
-
let layoutContent = originalContent;
|
|
113
|
-
|
|
114
|
-
// Check if already has AuthWrapper
|
|
115
|
-
if (layoutContent.includes("AuthWrapper")) {
|
|
116
|
-
console.log(chalk.green("✅ Layout already has AuthWrapper!"));
|
|
117
|
-
return { success: true, modified: false, filePath: layoutPath };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Generate the correct import path
|
|
121
|
-
const importPath = generateComponentImportPath(target, "auth-wrapper", layoutPath);
|
|
122
|
-
|
|
123
|
-
// Check if import already exists
|
|
124
|
-
const hasImport =
|
|
125
|
-
layoutContent.includes("auth-wrapper") &&
|
|
126
|
-
(layoutContent.includes("from") || layoutContent.includes("import"));
|
|
127
|
-
|
|
128
|
-
if (!hasImport) {
|
|
129
|
-
// Find the last import statement
|
|
130
|
-
const lines = layoutContent.split("\n");
|
|
131
|
-
let lastImportIndex = -1;
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < lines.length; i++) {
|
|
134
|
-
const trimmed = lines[i].trim();
|
|
135
|
-
if (trimmed.startsWith("import ") && trimmed.endsWith(";")) {
|
|
136
|
-
lastImportIndex = i;
|
|
137
|
-
} else if (trimmed && !trimmed.startsWith("//") && lastImportIndex >= 0) {
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (lastImportIndex >= 0) {
|
|
143
|
-
lines.splice(
|
|
144
|
-
lastImportIndex + 1,
|
|
145
|
-
0,
|
|
146
|
-
`import { AuthWrapper } from "${importPath}";`
|
|
147
|
-
);
|
|
148
|
-
layoutContent = lines.join("\n");
|
|
149
|
-
} else {
|
|
150
|
-
// No imports found, add after the first line
|
|
151
|
-
const firstNewline = layoutContent.indexOf("\n");
|
|
152
|
-
if (firstNewline > 0) {
|
|
153
|
-
layoutContent =
|
|
154
|
-
layoutContent.slice(0, firstNewline + 1) +
|
|
155
|
-
`import { AuthWrapper } from "${importPath}";\n` +
|
|
156
|
-
layoutContent.slice(firstNewline + 1);
|
|
157
|
-
} else {
|
|
158
|
-
layoutContent = `import { AuthWrapper } from "${importPath}";\n` + layoutContent;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Wrap children with AuthWrapper
|
|
164
|
-
// Look for {children} pattern
|
|
165
|
-
const childrenPattern = /(\s*)(\{children\})(\s*)/;
|
|
166
|
-
if (childrenPattern.test(layoutContent)) {
|
|
167
|
-
layoutContent = layoutContent.replace(
|
|
168
|
-
childrenPattern,
|
|
169
|
-
'$1<AuthWrapper>{children}</AuthWrapper>$3'
|
|
170
|
-
);
|
|
171
|
-
} else {
|
|
172
|
-
// Try to find body tag and wrap its content
|
|
173
|
-
const bodyRegex = /(<body[^>]*>)([\s\S]*?)(<\/body>)/;
|
|
174
|
-
const bodyMatch = layoutContent.match(bodyRegex);
|
|
175
|
-
if (bodyMatch) {
|
|
176
|
-
const bodyContent = bodyMatch[2].trim();
|
|
177
|
-
if (bodyContent && !bodyContent.includes("AuthWrapper")) {
|
|
178
|
-
layoutContent = layoutContent.replace(
|
|
179
|
-
bodyRegex,
|
|
180
|
-
`$1\n <AuthWrapper>${bodyContent}</AuthWrapper>\n $3`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Backup before modifying
|
|
187
|
-
backupFile(layoutPath, target);
|
|
188
|
-
|
|
189
|
-
fs.writeFileSync(layoutPath, layoutContent, "utf-8");
|
|
190
|
-
console.log(chalk.green("✅ Updated layout.tsx with AuthWrapper!"));
|
|
191
|
-
|
|
192
|
-
const relativePath = path.relative(target, layoutPath).replace(/\\/g, "/");
|
|
193
|
-
return { success: true, modified: true, filePath: relativePath, originalContent };
|
|
194
|
-
} catch (error: unknown) {
|
|
195
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
196
|
-
console.log(chalk.red(`❌ Failed to update layout.tsx: ${errorMessage}`));
|
|
197
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Update layout.tsx to include AuthSessionProvider
|
|
203
|
-
* Note: Better Auth doesn't require a session provider, but we add it for compatibility
|
|
204
|
-
* @deprecated Not used - middleware handles all route protection
|
|
205
|
-
*/
|
|
206
|
-
export function updateLayoutForAuth(target: string): LayoutUpdateResult {
|
|
207
|
-
const appDir = detectAppDirectory(target);
|
|
208
|
-
const layoutPath = path.join(target, appDir, "layout.tsx");
|
|
209
|
-
|
|
210
|
-
if (!fs.existsSync(layoutPath)) {
|
|
211
|
-
console.log(chalk.yellow("⚠️ layout.tsx not found. Skipping layout update."));
|
|
212
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const originalContent = fs.readFileSync(layoutPath, "utf-8");
|
|
217
|
-
let layoutContent = originalContent;
|
|
218
|
-
|
|
219
|
-
// Check if already has AuthSessionProvider
|
|
220
|
-
if (layoutContent.includes("AuthSessionProvider")) {
|
|
221
|
-
console.log(chalk.green("✅ Layout already has AuthSessionProvider!"));
|
|
222
|
-
return { success: true, modified: false, filePath: layoutPath };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Generate the correct import path
|
|
226
|
-
const importPath = generateComponentImportPath(target, "session-provider", layoutPath);
|
|
227
|
-
|
|
228
|
-
// Check if import already exists (check for various patterns)
|
|
229
|
-
const hasImport =
|
|
230
|
-
layoutContent.includes("session-provider") &&
|
|
231
|
-
(layoutContent.includes("from") || layoutContent.includes("import"));
|
|
232
|
-
|
|
233
|
-
if (!hasImport) {
|
|
234
|
-
// Find the last import statement (before the first non-import line)
|
|
235
|
-
const lines = layoutContent.split("\n");
|
|
236
|
-
let lastImportIndex = -1;
|
|
237
|
-
|
|
238
|
-
for (let i = 0; i < lines.length; i++) {
|
|
239
|
-
const trimmed = lines[i].trim();
|
|
240
|
-
if (trimmed.startsWith("import ") && trimmed.endsWith(";")) {
|
|
241
|
-
lastImportIndex = i;
|
|
242
|
-
} else if (trimmed && !trimmed.startsWith("//") && lastImportIndex >= 0) {
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (lastImportIndex >= 0) {
|
|
248
|
-
lines.splice(
|
|
249
|
-
lastImportIndex + 1,
|
|
250
|
-
0,
|
|
251
|
-
`import { AuthSessionProvider } from "${importPath}";`
|
|
252
|
-
);
|
|
253
|
-
layoutContent = lines.join("\n");
|
|
254
|
-
} else {
|
|
255
|
-
// No imports found, add after the first line
|
|
256
|
-
const firstNewline = layoutContent.indexOf("\n");
|
|
257
|
-
if (firstNewline > 0) {
|
|
258
|
-
layoutContent =
|
|
259
|
-
layoutContent.slice(0, firstNewline + 1) +
|
|
260
|
-
`import { AuthSessionProvider } from "${importPath}";\n` +
|
|
261
|
-
layoutContent.slice(firstNewline + 1);
|
|
262
|
-
} else {
|
|
263
|
-
layoutContent = `import { AuthSessionProvider } from "${importPath}";\n` + layoutContent;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Wrap children with AuthSessionProvider
|
|
269
|
-
// Look for {children} pattern in body tag
|
|
270
|
-
const childrenPattern = /(\s*)(\{children\})(\s*)/;
|
|
271
|
-
if (childrenPattern.test(layoutContent)) {
|
|
272
|
-
layoutContent = layoutContent.replace(
|
|
273
|
-
childrenPattern,
|
|
274
|
-
'$1<AuthSessionProvider>{children}</AuthSessionProvider>$3'
|
|
275
|
-
);
|
|
276
|
-
} else {
|
|
277
|
-
// Try to find body tag and wrap its content
|
|
278
|
-
const bodyRegex = /(<body[^>]*>)([\s\S]*?)(<\/body>)/;
|
|
279
|
-
const bodyMatch = layoutContent.match(bodyRegex);
|
|
280
|
-
if (bodyMatch) {
|
|
281
|
-
const bodyContent = bodyMatch[2].trim();
|
|
282
|
-
if (bodyContent && !bodyContent.includes("AuthSessionProvider")) {
|
|
283
|
-
layoutContent = layoutContent.replace(
|
|
284
|
-
bodyRegex,
|
|
285
|
-
`$1\n <AuthSessionProvider>${bodyContent}</AuthSessionProvider>\n $3`
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Backup before modifying
|
|
292
|
-
backupFile(layoutPath, target);
|
|
293
|
-
|
|
294
|
-
fs.writeFileSync(layoutPath, layoutContent, "utf-8");
|
|
295
|
-
console.log(chalk.green("✅ Updated layout.tsx with AuthSessionProvider!"));
|
|
296
|
-
|
|
297
|
-
const relativePath = path.relative(target, layoutPath).replace(/\\/g, "/");
|
|
298
|
-
return { success: true, modified: true, filePath: relativePath, originalContent };
|
|
299
|
-
} catch (error: unknown) {
|
|
300
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
301
|
-
console.log(chalk.red(`❌ Failed to update layout.tsx: ${errorMessage}`));
|
|
302
|
-
return { success: false, modified: false, filePath: layoutPath };
|
|
303
|
-
}
|
|
304
|
-
}
|
package/src/fileOps/protected.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { BOILERPLATE_ROOT } from "../config.js";
|
|
4
|
-
import { detectAppDirectory, detectComponentsDirectory } from "../utils/paths.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Copy protected route files
|
|
8
|
-
*/
|
|
9
|
-
export async function copyProtectedRouteFiles(target: string): Promise<void> {
|
|
10
|
-
const protectedRouteSrc = path.join(BOILERPLATE_ROOT, "auth/components/protected-route.tsx");
|
|
11
|
-
const middlewareSrc = path.join(BOILERPLATE_ROOT, "auth/middleware.ts");
|
|
12
|
-
|
|
13
|
-
const componentsDir = detectComponentsDirectory(target);
|
|
14
|
-
const componentsDirPath = path.join(target, componentsDir);
|
|
15
|
-
const protectedRouteDest = path.join(componentsDirPath, "protected-route.tsx");
|
|
16
|
-
const middlewareDest = path.join(target, "middleware.ts");
|
|
17
|
-
|
|
18
|
-
// Ensure components directory exists
|
|
19
|
-
if (!fs.existsSync(componentsDirPath)) {
|
|
20
|
-
fs.mkdirSync(componentsDirPath, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Copy protected route component
|
|
24
|
-
if (fs.existsSync(protectedRouteSrc)) {
|
|
25
|
-
fs.copyFileSync(protectedRouteSrc, protectedRouteDest);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Copy middleware (only if it doesn't exist)
|
|
29
|
-
if (fs.existsSync(middlewareSrc) && !fs.existsSync(middlewareDest)) {
|
|
30
|
-
fs.copyFileSync(middlewareSrc, middlewareDest);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Auth navbar is not copied by default - it's available in boilerplate if needed
|
|
34
|
-
// Users can manually copy it if they want to use it
|
|
35
|
-
|
|
36
|
-
// Copy example pages (only if they don't exist)
|
|
37
|
-
const appDir = detectAppDirectory(target);
|
|
38
|
-
const dashboardPageSrc = path.join(BOILERPLATE_ROOT, "auth/app/dashboard/page.tsx");
|
|
39
|
-
const landingPageSrc = path.join(BOILERPLATE_ROOT, "auth/app/page.tsx");
|
|
40
|
-
const dashboardPageDest = path.join(target, appDir, "dashboard/page.tsx");
|
|
41
|
-
const landingPageDest = path.join(target, appDir, "page.tsx");
|
|
42
|
-
|
|
43
|
-
// Create dashboard directory if needed
|
|
44
|
-
const dashboardDir = path.join(target, appDir, "dashboard");
|
|
45
|
-
if (!fs.existsSync(dashboardDir)) {
|
|
46
|
-
fs.mkdirSync(dashboardDir, { recursive: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Copy dashboard page (only if it doesn't exist)
|
|
50
|
-
if (fs.existsSync(dashboardPageSrc) && !fs.existsSync(dashboardPageDest)) {
|
|
51
|
-
fs.copyFileSync(dashboardPageSrc, dashboardPageDest);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Copy landing page (only if it doesn't exist or is default)
|
|
55
|
-
if (fs.existsSync(landingPageSrc)) {
|
|
56
|
-
// Check if current page is just a default Next.js page
|
|
57
|
-
if (fs.existsSync(landingPageDest)) {
|
|
58
|
-
const currentContent = fs.readFileSync(landingPageDest, "utf-8");
|
|
59
|
-
// Only replace if it's the default Next.js page
|
|
60
|
-
if (currentContent.includes("Get started by editing") || currentContent.length < 500) {
|
|
61
|
-
fs.copyFileSync(landingPageSrc, landingPageDest);
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
fs.copyFileSync(landingPageSrc, landingPageDest);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|