stackpatch 1.2.6 → 1.2.8
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/dist/stackpatch.js +182 -43
- package/package.json +2 -1
package/dist/stackpatch.js
CHANGED
|
@@ -841,55 +841,70 @@ function getParentDirectories(filePath, rootPath) {
|
|
|
841
841
|
}
|
|
842
842
|
return dirs;
|
|
843
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Check if a directory is a Next.js app
|
|
846
|
+
*/
|
|
847
|
+
function isNextJsApp(dir) {
|
|
848
|
+
return (fs.existsSync(path.join(dir, "app")) ||
|
|
849
|
+
fs.existsSync(path.join(dir, "src", "app")) ||
|
|
850
|
+
fs.existsSync(path.join(dir, "pages")) ||
|
|
851
|
+
fs.existsSync(path.join(dir, "src", "pages"))) && fs.existsSync(path.join(dir, "package.json"));
|
|
852
|
+
}
|
|
844
853
|
/**
|
|
845
854
|
* Auto-detect target directory for Next.js app
|
|
846
855
|
*/
|
|
847
856
|
function detectTargetDirectory(startDir = process.cwd()) {
|
|
848
857
|
let target = startDir;
|
|
849
|
-
// Check if we're in a Next.js app
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
if (entry.isDirectory()) {
|
|
876
|
-
const appPath = path.join(possiblePath, entry.name);
|
|
877
|
-
if (fs.existsSync(path.join(appPath, "app")) ||
|
|
878
|
-
fs.existsSync(path.join(appPath, "src", "app")) ||
|
|
879
|
-
fs.existsSync(path.join(appPath, "pages")) ||
|
|
880
|
-
fs.existsSync(path.join(appPath, "src", "pages"))) {
|
|
881
|
-
target = appPath;
|
|
882
|
-
foundApp = true;
|
|
883
|
-
break;
|
|
884
|
-
}
|
|
858
|
+
// Check if we're in a Next.js app
|
|
859
|
+
if (isNextJsApp(target)) {
|
|
860
|
+
return target;
|
|
861
|
+
}
|
|
862
|
+
// Try parent directory
|
|
863
|
+
const parent = path.resolve(target, "..");
|
|
864
|
+
if (isNextJsApp(parent)) {
|
|
865
|
+
return parent;
|
|
866
|
+
}
|
|
867
|
+
// Try common monorepo locations: apps/, packages/, or root
|
|
868
|
+
const possiblePaths = [
|
|
869
|
+
path.join(target, "apps"),
|
|
870
|
+
path.join(parent, "apps"),
|
|
871
|
+
path.join(target, "packages"),
|
|
872
|
+
path.join(parent, "packages"),
|
|
873
|
+
];
|
|
874
|
+
for (const possiblePath of possiblePaths) {
|
|
875
|
+
if (fs.existsSync(possiblePath)) {
|
|
876
|
+
// Look for Next.js apps in this directory
|
|
877
|
+
try {
|
|
878
|
+
const entries = fs.readdirSync(possiblePath, { withFileTypes: true });
|
|
879
|
+
for (const entry of entries) {
|
|
880
|
+
if (entry.isDirectory()) {
|
|
881
|
+
const appPath = path.join(possiblePath, entry.name);
|
|
882
|
+
if (isNextJsApp(appPath)) {
|
|
883
|
+
return appPath;
|
|
885
884
|
}
|
|
886
885
|
}
|
|
887
|
-
if (foundApp)
|
|
888
|
-
break;
|
|
889
886
|
}
|
|
890
887
|
}
|
|
888
|
+
catch {
|
|
889
|
+
// Ignore errors reading directory
|
|
890
|
+
}
|
|
891
891
|
}
|
|
892
892
|
}
|
|
893
|
+
// Try searching subdirectories (one level deep) in current directory
|
|
894
|
+
try {
|
|
895
|
+
const entries = fs.readdirSync(target, { withFileTypes: true });
|
|
896
|
+
for (const entry of entries) {
|
|
897
|
+
if (entry.isDirectory()) {
|
|
898
|
+
const subPath = path.join(target, entry.name);
|
|
899
|
+
if (isNextJsApp(subPath)) {
|
|
900
|
+
return subPath;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
// Ignore errors reading directory
|
|
907
|
+
}
|
|
893
908
|
return target;
|
|
894
909
|
}
|
|
895
910
|
|
|
@@ -1436,15 +1451,73 @@ async function addPatch(patchName, targetDir) {
|
|
|
1436
1451
|
const hasPagesDir = fs.existsSync(path.join(target, "pages")) || fs.existsSync(path.join(target, "src", "pages"));
|
|
1437
1452
|
if (!hasAppDir && !hasPagesDir) {
|
|
1438
1453
|
console.log(chalk$1.yellow("⚠️ Could not auto-detect Next.js app directory."));
|
|
1454
|
+
// Try to find Next.js apps in subdirectories
|
|
1455
|
+
let foundApps = [];
|
|
1456
|
+
try {
|
|
1457
|
+
const entries = fs.readdirSync(target, { withFileTypes: true });
|
|
1458
|
+
for (const entry of entries) {
|
|
1459
|
+
if (entry.isDirectory()) {
|
|
1460
|
+
const subPath = path.join(target, entry.name);
|
|
1461
|
+
const hasSubAppDir = fs.existsSync(path.join(subPath, "app")) || fs.existsSync(path.join(subPath, "src", "app"));
|
|
1462
|
+
const hasSubPagesDir = fs.existsSync(path.join(subPath, "pages")) || fs.existsSync(path.join(subPath, "src", "pages"));
|
|
1463
|
+
const hasPackageJson = fs.existsSync(path.join(subPath, "package.json"));
|
|
1464
|
+
if ((hasSubAppDir || hasSubPagesDir) && hasPackageJson) {
|
|
1465
|
+
foundApps.push(entry.name);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
catch {
|
|
1471
|
+
// Ignore errors
|
|
1472
|
+
}
|
|
1473
|
+
let defaultPath = target;
|
|
1474
|
+
if (foundApps.length === 1) {
|
|
1475
|
+
defaultPath = path.join(target, foundApps[0]);
|
|
1476
|
+
console.log(chalk$1.green(`💡 Found Next.js app in subdirectory: ${foundApps[0]}`));
|
|
1477
|
+
}
|
|
1478
|
+
else if (foundApps.length > 1) {
|
|
1479
|
+
console.log(chalk$1.cyan(`💡 Found multiple Next.js apps: ${foundApps.join(", ")}`));
|
|
1480
|
+
}
|
|
1439
1481
|
const { userTarget } = await inquirer$1.prompt([
|
|
1440
1482
|
{
|
|
1441
1483
|
type: "input",
|
|
1442
1484
|
name: "userTarget",
|
|
1443
1485
|
message: "Enter the path to your Next.js app folder:",
|
|
1444
|
-
default:
|
|
1486
|
+
default: defaultPath,
|
|
1445
1487
|
},
|
|
1446
1488
|
]);
|
|
1447
|
-
|
|
1489
|
+
// Resolve the path - handle both absolute and relative paths
|
|
1490
|
+
if (path.isAbsolute(userTarget)) {
|
|
1491
|
+
target = userTarget;
|
|
1492
|
+
}
|
|
1493
|
+
else {
|
|
1494
|
+
// If relative, resolve from current working directory
|
|
1495
|
+
target = path.resolve(process.cwd(), userTarget);
|
|
1496
|
+
}
|
|
1497
|
+
// Verify the target exists
|
|
1498
|
+
if (!fs.existsSync(target)) {
|
|
1499
|
+
console.log(chalk$1.red(`❌ Error: Directory does not exist: ${target}`));
|
|
1500
|
+
console.log(chalk$1.yellow(`💡 Tip: Make sure you're in the correct directory or provide the full absolute path.`));
|
|
1501
|
+
console.log(chalk$1.yellow(` Current directory: ${process.cwd()}`));
|
|
1502
|
+
if (foundApps.length > 0) {
|
|
1503
|
+
console.log(chalk$1.yellow(` Found Next.js apps in subdirectories: ${foundApps.join(", ")}`));
|
|
1504
|
+
}
|
|
1505
|
+
process.exit(1);
|
|
1506
|
+
}
|
|
1507
|
+
// Verify it's actually a Next.js app
|
|
1508
|
+
const finalHasAppDir = fs.existsSync(path.join(target, "app")) || fs.existsSync(path.join(target, "src", "app"));
|
|
1509
|
+
const finalHasPagesDir = fs.existsSync(path.join(target, "pages")) || fs.existsSync(path.join(target, "src", "pages"));
|
|
1510
|
+
const finalHasPackageJson = fs.existsSync(path.join(target, "package.json"));
|
|
1511
|
+
if (!finalHasAppDir && !finalHasPagesDir) {
|
|
1512
|
+
console.log(chalk$1.red(`❌ Error: ${target} does not appear to be a Next.js app directory.`));
|
|
1513
|
+
console.log(chalk$1.yellow(` Expected to find: app/, src/app/, pages/, or src/pages/ directory`));
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
if (!finalHasPackageJson) {
|
|
1517
|
+
console.log(chalk$1.red(`❌ Error: package.json not found in ${target}.`));
|
|
1518
|
+
console.log(chalk$1.yellow(` Make sure you're pointing to the root of your Next.js project.`));
|
|
1519
|
+
process.exit(1);
|
|
1520
|
+
}
|
|
1448
1521
|
}
|
|
1449
1522
|
// For auth patches, use new setup flow
|
|
1450
1523
|
if (patchName === "auth" || patchName === "auth-ui") {
|
|
@@ -2059,6 +2132,15 @@ function scanProject(target) {
|
|
|
2059
2132
|
};
|
|
2060
2133
|
if (deps.next || deps["next"]) {
|
|
2061
2134
|
scan.framework = "nextjs";
|
|
2135
|
+
// Extract Next.js version
|
|
2136
|
+
const nextVersion = deps.next || deps["next"];
|
|
2137
|
+
if (typeof nextVersion === "string") {
|
|
2138
|
+
// Extract version number (handle ranges like "^16.0.0" or "16.0.0")
|
|
2139
|
+
const versionMatch = nextVersion.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
2140
|
+
if (versionMatch) {
|
|
2141
|
+
scan.nextVersion = versionMatch[0];
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2062
2144
|
}
|
|
2063
2145
|
// Detect package manager from lock files
|
|
2064
2146
|
if (fs.existsSync(path.join(target, "pnpm-lock.yaml"))) {
|
|
@@ -2610,7 +2692,22 @@ function generateMiddleware(target, config, scan) {
|
|
|
2610
2692
|
if (config.protectedRoutes.length === 0) {
|
|
2611
2693
|
return null;
|
|
2612
2694
|
}
|
|
2613
|
-
|
|
2695
|
+
// Next.js 16+ uses proxy.ts instead of middleware.ts
|
|
2696
|
+
const isNext16Plus = scan.nextVersion &&
|
|
2697
|
+
(parseInt(scan.nextVersion.split(".")[0]) >= 16);
|
|
2698
|
+
const middlewareFileName = isNext16Plus ? "proxy.ts" : "middleware.ts";
|
|
2699
|
+
const middlewarePath = path.join(target, middlewareFileName);
|
|
2700
|
+
// Also check for the old filename if migrating
|
|
2701
|
+
const oldMiddlewarePath = path.join(target, isNext16Plus ? "middleware.ts" : "proxy.ts");
|
|
2702
|
+
if (fs.existsSync(oldMiddlewarePath) && oldMiddlewarePath !== middlewarePath) {
|
|
2703
|
+
// Remove old middleware file if it exists
|
|
2704
|
+
try {
|
|
2705
|
+
fs.unlinkSync(oldMiddlewarePath);
|
|
2706
|
+
}
|
|
2707
|
+
catch {
|
|
2708
|
+
// Ignore errors
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2614
2711
|
// Check if middleware already exists
|
|
2615
2712
|
if (fs.existsSync(middlewarePath)) {
|
|
2616
2713
|
// Try to update existing middleware
|
|
@@ -2638,7 +2735,46 @@ function generateMiddleware(target, config, scan) {
|
|
|
2638
2735
|
// Add auth pages to matcher so we can redirect authenticated users away
|
|
2639
2736
|
const authPages = ["/auth/login", "/auth/signup"];
|
|
2640
2737
|
const allMatcherPatterns = [...matcherPatterns, ...authPages];
|
|
2641
|
-
|
|
2738
|
+
// Next.js 16+ uses proxy.ts with default export
|
|
2739
|
+
const middlewareContent = isNext16Plus
|
|
2740
|
+
? `import { NextRequest, NextResponse } from "next/server";
|
|
2741
|
+
import { getSessionCookie } from "better-auth/cookies";
|
|
2742
|
+
|
|
2743
|
+
export default async function handler(request: NextRequest) {
|
|
2744
|
+
const pathname = request.nextUrl.pathname;
|
|
2745
|
+
|
|
2746
|
+
// Check if session cookie exists
|
|
2747
|
+
const sessionCookie = getSessionCookie(request);
|
|
2748
|
+
|
|
2749
|
+
// Handle auth pages (login/signup)
|
|
2750
|
+
if (pathname === "/auth/login" || pathname === "/auth/signup") {
|
|
2751
|
+
// If already authenticated, redirect away from auth pages
|
|
2752
|
+
if (sessionCookie) {
|
|
2753
|
+
const redirectParam = request.nextUrl.searchParams.get("redirect");
|
|
2754
|
+
const redirectTo = redirectParam || "/stackpatch";
|
|
2755
|
+
return NextResponse.redirect(new URL(redirectTo, request.url));
|
|
2756
|
+
}
|
|
2757
|
+
// Not authenticated - allow access to auth pages
|
|
2758
|
+
return NextResponse.next();
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
// Handle protected routes (only protected routes reach here thanks to matcher)
|
|
2762
|
+
if (!sessionCookie) {
|
|
2763
|
+
// Not authenticated - redirect to login with return URL
|
|
2764
|
+
const loginUrl = new URL("/auth/login", request.url);
|
|
2765
|
+
loginUrl.searchParams.set("redirect", pathname);
|
|
2766
|
+
return NextResponse.redirect(loginUrl);
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
// Authenticated and accessing protected route - allow access
|
|
2770
|
+
return NextResponse.next();
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
export const config = {
|
|
2774
|
+
matcher: ${JSON.stringify(allMatcherPatterns)}, // Protected routes + auth pages
|
|
2775
|
+
};
|
|
2776
|
+
`
|
|
2777
|
+
: `import { NextRequest, NextResponse } from "next/server";
|
|
2642
2778
|
import { getSessionCookie } from "better-auth/cookies";
|
|
2643
2779
|
|
|
2644
2780
|
export async function middleware(request: NextRequest) {
|
|
@@ -3208,7 +3344,10 @@ function showSuccessMessage(target, config, scan) {
|
|
|
3208
3344
|
console.log(chalk$1.white(` - ${libDir}/auth-client.ts`));
|
|
3209
3345
|
console.log(chalk$1.white(` - app/api/auth/[...all]/route.ts`));
|
|
3210
3346
|
if (config.protectedRoutes.length > 0) {
|
|
3211
|
-
|
|
3347
|
+
const middlewareFileName = scan.nextVersion && parseInt(scan.nextVersion.split(".")[0]) >= 16
|
|
3348
|
+
? "proxy.ts"
|
|
3349
|
+
: "middleware.ts";
|
|
3350
|
+
console.log(chalk$1.white(` - ${middlewareFileName}`));
|
|
3212
3351
|
}
|
|
3213
3352
|
if (config.protectedRoutes.length > 0) {
|
|
3214
3353
|
console.log(chalk$1.white(` - ${libDir}/protected-routes.ts`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackpatch",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"description": "Composable frontend features for modern React & Next.js apps - Add authentication, UI components, and more with zero configuration",
|
|
5
5
|
"main": "dist/stackpatch.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"test:watch": "vitest",
|
|
21
21
|
"test:coverage": "vitest run --coverage",
|
|
22
22
|
"test:cli": "node scripts/test-cli-execution.js",
|
|
23
|
+
"test:nextjs-version": "node scripts/test-nextjs-version.js",
|
|
23
24
|
"prepublishOnly": "npm run build && node scripts/prepare-publish.js && npm run test:cli",
|
|
24
25
|
"dev": "bun run bin/stackpatch.ts",
|
|
25
26
|
"create": "bun run bin/stackpatch.ts"
|