-
-
Notifications
You must be signed in to change notification settings - Fork 85
Expand file tree
/
Copy pathutils.ts
More file actions
160 lines (143 loc) · 4.6 KB
/
utils.ts
File metadata and controls
160 lines (143 loc) · 4.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import * as fs from 'fs';
import * as path from 'path';
import { major, minVersion } from 'semver';
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
import { builders, parseModule } from 'magicast';
export function getNextJsVersionBucket(version: string | undefined) {
if (!version) {
return 'none';
}
try {
const minVer = minVersion(version);
if (!minVer) {
return 'invalid';
}
const majorVersion = major(minVer);
if (majorVersion >= 11) {
return `${majorVersion}.x`;
}
return '<11.0.0';
} catch {
return 'unknown';
}
}
/**
* Detects whether cacheComponents is enabled in the Next.js config.
* Returns true if cacheComponents is set to true, false otherwise.
*/
export function hasCacheComponentsEnabled(): boolean {
const nextConfigFiles = [
'next.config.js',
'next.config.mjs',
'next.config.ts',
'next.config.mts',
'next.config.cjs',
'next.config.cts',
];
for (const configFile of nextConfigFiles) {
const configPath = path.join(process.cwd(), configFile);
if (!fs.existsSync(configPath)) {
continue;
}
try {
const configContent = fs.readFileSync(configPath, 'utf8');
// First try a simple string check for common patterns
// This catches: cacheComponents: true, experimental: { cacheComponents: true }
if (
/cacheComponents\s*:\s*true/.test(configContent) ||
/experimental\s*:\s*\{\s*cacheComponents\s*:\s*true/.test(configContent)
) {
return true;
}
// Try parsing with magicast for more complex cases
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const mod = parseModule(configContent);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const nextConfig = mod.exports?.default?.$type
? // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
mod.exports.default
: // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
mod.exports;
// Check for cacheComponents at root level or in experimental
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (nextConfig?.cacheComponents === true) {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (nextConfig?.experimental?.cacheComponents === true) {
return true;
}
} catch {
// If magicast parsing fails, we already checked with regex above
}
} catch {
// If we can't read the file, continue to the next one
continue;
}
}
return false;
}
export function getMaybeAppDirLocation() {
const maybeAppDirPath = path.join(process.cwd(), 'app');
const maybeSrcAppDirPath = path.join(process.cwd(), 'src', 'app');
return fs.existsSync(maybeAppDirPath) &&
fs.lstatSync(maybeAppDirPath).isDirectory()
? ['app']
: fs.existsSync(maybeSrcAppDirPath) &&
fs.lstatSync(maybeSrcAppDirPath).isDirectory()
? ['src', 'app']
: undefined;
}
export function hasRootLayoutFile(appFolderPath: string) {
return ['jsx', 'tsx', 'js'].some((ext) =>
fs.existsSync(path.join(appFolderPath, `layout.${ext}`)),
);
}
/**
* Unwraps a withSentryConfig call expression using magicast.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function unwrapSentryConfigAst(astNode: unknown): any {
// Check if this is a CallExpression with withSentryConfig
if (
isAstNode(astNode) &&
astNode.type === 'CallExpression' &&
astNode.callee?.type === 'Identifier' &&
astNode.callee?.name === 'withSentryConfig'
) {
// Return the first argument (the config being wrapped)
return astNode.arguments?.[0] || astNode;
}
return astNode;
}
/**
* Wraps a magicast module export with withSentryConfig using magicast
*/
export function wrapWithSentryConfig(
moduleExport: unknown,
optionsTemplate: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any {
return builders.functionCall(
'withSentryConfig',
moduleExport,
builders.raw(optionsTemplate),
);
}
function isAstNode(astNode: unknown): astNode is {
type: string;
callee?: { type: string; name?: string };
arguments?: unknown[];
} {
return (
typeof astNode === 'object' &&
astNode !== null &&
'type' in astNode &&
'callee' in astNode &&
typeof astNode.callee === 'object' &&
astNode.callee !== null &&
'type' in astNode.callee &&
'name' in astNode.callee
);
}