jskelet 0.6.3 → 0.6.4
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/AGENTS.md +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
package/src/migrate/babel.mjs
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Migrate (JSX/TSX → .jsk) için Babel — opsiyonel peer.
|
|
3
|
-
*
|
|
4
|
-
* Normal `build` / `start` / `dev` bunları indirmez. İlk parse anında paket
|
|
5
|
-
* yoksa kurulum komutunu gösteren net bir hata verir.
|
|
6
|
-
*/
|
|
7
|
-
import { createRequire } from "node:module";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import process from "node:process";
|
|
10
|
-
import { FRAMEWORK_ROOT } from "../config/index.js";
|
|
11
|
-
|
|
12
|
-
const MISSING =
|
|
13
|
-
"`jskelet migrate` requires optional peer dependencies `@babel/parser` and `@babel/types`.\n" +
|
|
14
|
-
"Install them in the project you are migrating:\n" +
|
|
15
|
-
" npm i -D @babel/parser @babel/types";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @typedef {{
|
|
19
|
-
* parse: typeof import('@babel/parser').parse,
|
|
20
|
-
* types: typeof import('@babel/types'),
|
|
21
|
-
* }} BabelApi
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** @type {BabelApi | null | undefined} */
|
|
25
|
-
let cached;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {string} [root] Hedef proje kökü (varsayılan: cwd).
|
|
29
|
-
* @returns {BabelApi}
|
|
30
|
-
*/
|
|
31
|
-
export function getBabel(root = process.cwd()) {
|
|
32
|
-
if (cached) return cached;
|
|
33
|
-
if (cached === null) throw new Error(MISSING);
|
|
34
|
-
|
|
35
|
-
for (const base of [root, FRAMEWORK_ROOT]) {
|
|
36
|
-
try {
|
|
37
|
-
const require = createRequire(path.join(base, "package.json"));
|
|
38
|
-
const parser = require("@babel/parser");
|
|
39
|
-
const types = require("@babel/types");
|
|
40
|
-
cached = {
|
|
41
|
-
parse: parser.parse.bind(parser),
|
|
42
|
-
types,
|
|
43
|
-
};
|
|
44
|
-
return cached;
|
|
45
|
-
} catch {
|
|
46
|
-
// Sonraki köke bak.
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
cached = null;
|
|
51
|
-
throw new Error(MISSING);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Migrate girişinde erken ve anlaşılır hata için.
|
|
56
|
-
*
|
|
57
|
-
* @param {string} [root]
|
|
58
|
-
*/
|
|
59
|
-
export function ensureBabel(root) {
|
|
60
|
-
getBabel(root);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* `@babel/types` — ilk erişimde peer'ları yükler.
|
|
65
|
-
* Metodlar doğru `this` ile bağlanır (`t.isIdentifier(node)`).
|
|
66
|
-
*
|
|
67
|
-
* @type {typeof import('@babel/types')}
|
|
68
|
-
*/
|
|
69
|
-
export const t = /** @type {typeof import('@babel/types')} */ (
|
|
70
|
-
new Proxy(/** @type {object} */ ({}), {
|
|
71
|
-
get(_target, prop) {
|
|
72
|
-
// Promise thenable tuzağına düşmesin.
|
|
73
|
-
if (prop === "then") return undefined;
|
|
74
|
-
const types = getBabel().types;
|
|
75
|
-
const value = Reflect.get(types, prop, types);
|
|
76
|
-
return typeof value === "function" ? value.bind(types) : value;
|
|
77
|
-
},
|
|
78
|
-
})
|
|
79
|
-
);
|
|
1
|
+
/**
|
|
2
|
+
* Migrate (JSX/TSX → .jsk) için Babel — opsiyonel peer.
|
|
3
|
+
*
|
|
4
|
+
* Normal `build` / `start` / `dev` bunları indirmez. İlk parse anında paket
|
|
5
|
+
* yoksa kurulum komutunu gösteren net bir hata verir.
|
|
6
|
+
*/
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import process from "node:process";
|
|
10
|
+
import { FRAMEWORK_ROOT } from "../config/index.js";
|
|
11
|
+
|
|
12
|
+
const MISSING =
|
|
13
|
+
"`jskelet migrate` requires optional peer dependencies `@babel/parser` and `@babel/types`.\n" +
|
|
14
|
+
"Install them in the project you are migrating:\n" +
|
|
15
|
+
" npm i -D @babel/parser @babel/types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {{
|
|
19
|
+
* parse: typeof import('@babel/parser').parse,
|
|
20
|
+
* types: typeof import('@babel/types'),
|
|
21
|
+
* }} BabelApi
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** @type {BabelApi | null | undefined} */
|
|
25
|
+
let cached;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} [root] Hedef proje kökü (varsayılan: cwd).
|
|
29
|
+
* @returns {BabelApi}
|
|
30
|
+
*/
|
|
31
|
+
export function getBabel(root = process.cwd()) {
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
if (cached === null) throw new Error(MISSING);
|
|
34
|
+
|
|
35
|
+
for (const base of [root, FRAMEWORK_ROOT]) {
|
|
36
|
+
try {
|
|
37
|
+
const require = createRequire(path.join(base, "package.json"));
|
|
38
|
+
const parser = require("@babel/parser");
|
|
39
|
+
const types = require("@babel/types");
|
|
40
|
+
cached = {
|
|
41
|
+
parse: parser.parse.bind(parser),
|
|
42
|
+
types,
|
|
43
|
+
};
|
|
44
|
+
return cached;
|
|
45
|
+
} catch {
|
|
46
|
+
// Sonraki köke bak.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
cached = null;
|
|
51
|
+
throw new Error(MISSING);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Migrate girişinde erken ve anlaşılır hata için.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} [root]
|
|
58
|
+
*/
|
|
59
|
+
export function ensureBabel(root) {
|
|
60
|
+
getBabel(root);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `@babel/types` — ilk erişimde peer'ları yükler.
|
|
65
|
+
* Metodlar doğru `this` ile bağlanır (`t.isIdentifier(node)`).
|
|
66
|
+
*
|
|
67
|
+
* @type {typeof import('@babel/types')}
|
|
68
|
+
*/
|
|
69
|
+
export const t = /** @type {typeof import('@babel/types')} */ (
|
|
70
|
+
new Proxy(/** @type {object} */ ({}), {
|
|
71
|
+
get(_target, prop) {
|
|
72
|
+
// Promise thenable tuzağına düşmesin.
|
|
73
|
+
if (prop === "then") return undefined;
|
|
74
|
+
const types = getBabel().types;
|
|
75
|
+
const value = Reflect.get(types, prop, types);
|
|
76
|
+
return typeof value === "function" ? value.bind(types) : value;
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
);
|
package/src/migrate/classify.mjs
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bir kaynak dosyasını page / layout / client / server-component olarak sınıflandırır.
|
|
3
|
-
*/
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { t } from "./babel.mjs";
|
|
6
|
-
import { parseSource } from "./parse.mjs";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {'page' | 'layout' | 'client' | 'server-component' | 'unknown'} FileKind
|
|
10
|
-
* @typedef {{
|
|
11
|
-
* kind: FileKind,
|
|
12
|
-
* isClient: boolean,
|
|
13
|
-
* hasHooks: boolean,
|
|
14
|
-
* hasServerAction: boolean,
|
|
15
|
-
* hasSuspense: boolean,
|
|
16
|
-
* revalidate: number | null,
|
|
17
|
-
* hasGenerateMetadata: boolean,
|
|
18
|
-
* hasGenerateStaticParams: boolean,
|
|
19
|
-
* reasons: string[],
|
|
20
|
-
* }} Classification
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @param {string} code
|
|
25
|
-
* @param {string} filename
|
|
26
|
-
* @returns {Classification}
|
|
27
|
-
*/
|
|
28
|
-
export function classifySource(code, filename) {
|
|
29
|
-
const base = path.basename(filename);
|
|
30
|
-
/** @type {Classification} */
|
|
31
|
-
const out = {
|
|
32
|
-
kind: "unknown",
|
|
33
|
-
isClient: false,
|
|
34
|
-
hasHooks: false,
|
|
35
|
-
hasServerAction: false,
|
|
36
|
-
hasSuspense: false,
|
|
37
|
-
revalidate: null,
|
|
38
|
-
hasGenerateMetadata: false,
|
|
39
|
-
hasGenerateStaticParams: false,
|
|
40
|
-
reasons: [],
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
if (/^["']use client["'];?/.test(code.trim()) || /\n["']use client["'];?/.test(code)) {
|
|
44
|
-
out.isClient = true;
|
|
45
|
-
}
|
|
46
|
-
if (code.includes('"use server"') || code.includes("'use server'")) {
|
|
47
|
-
out.hasServerAction = true;
|
|
48
|
-
out.reasons.push("contains \"use server\"");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let ast;
|
|
52
|
-
try {
|
|
53
|
-
ast = parseSource(code, filename);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
out.reasons.push(
|
|
56
|
-
`parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
57
|
-
);
|
|
58
|
-
if (out.isClient) out.kind = "client";
|
|
59
|
-
else if (/^page\./.test(base)) out.kind = "page";
|
|
60
|
-
else if (/^layout\./.test(base)) out.kind = "layout";
|
|
61
|
-
return out;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
for (const node of ast.program.directives ?? []) {
|
|
65
|
-
if (node.value?.value === "use client") out.isClient = true;
|
|
66
|
-
if (node.value?.value === "use server") {
|
|
67
|
-
out.hasServerAction = true;
|
|
68
|
-
out.reasons.push("directive \"use server\"");
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
for (const node of ast.program.body) {
|
|
73
|
-
if (t.isImportDeclaration(node)) {
|
|
74
|
-
const src = node.source.value;
|
|
75
|
-
if (src === "react" || src === "react/jsx-runtime") {
|
|
76
|
-
for (const spec of node.specifiers) {
|
|
77
|
-
const name =
|
|
78
|
-
(t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)
|
|
79
|
-
? spec.imported.name
|
|
80
|
-
: null) ||
|
|
81
|
-
(t.isImportDefaultSpecifier(spec) ? "default" : null);
|
|
82
|
-
if (name === "Suspense") out.hasSuspense = true;
|
|
83
|
-
if (
|
|
84
|
-
name &&
|
|
85
|
-
/^(use[A-Z]|useState|useEffect|useMemo|useCallback|useRef|useContext|useReducer|useLayoutEffect|useId|useTransition|useDeferredValue|useSyncExternalStore)/.test(
|
|
86
|
-
name,
|
|
87
|
-
)
|
|
88
|
-
) {
|
|
89
|
-
out.hasHooks = true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
t.isExportNamedDeclaration(node) &&
|
|
97
|
-
node.declaration &&
|
|
98
|
-
t.isVariableDeclaration(node.declaration)
|
|
99
|
-
) {
|
|
100
|
-
for (const decl of node.declaration.declarations) {
|
|
101
|
-
if (t.isIdentifier(decl.id) && decl.id.name === "revalidate") {
|
|
102
|
-
if (t.isNumericLiteral(decl.init)) out.revalidate = decl.init.value;
|
|
103
|
-
else if (t.isLiteral(decl.init) && typeof decl.init.value === "number") {
|
|
104
|
-
out.revalidate = /** @type {number} */ (decl.init.value);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (t.isExportNamedDeclaration(node)) {
|
|
111
|
-
const id = namedExportName(node);
|
|
112
|
-
if (id === "generateMetadata") out.hasGenerateMetadata = true;
|
|
113
|
-
if (id === "generateStaticParams") out.hasGenerateStaticParams = true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Hook çağrıları (import alias olmadan da)
|
|
118
|
-
if (/\buse(State|Effect|Memo|Callback|Ref|Context|Reducer)\s*\(/.test(code)) {
|
|
119
|
-
out.hasHooks = true;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (out.isClient || out.hasHooks) {
|
|
123
|
-
out.kind = "client";
|
|
124
|
-
} else if (/^page\./.test(base)) {
|
|
125
|
-
out.kind = "page";
|
|
126
|
-
} else if (/^layout\./.test(base)) {
|
|
127
|
-
out.kind = "layout";
|
|
128
|
-
} else {
|
|
129
|
-
out.kind = "server-component";
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return out;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @param {import('@babel/types').ExportNamedDeclaration} node
|
|
137
|
-
* @returns {string | null}
|
|
138
|
-
*/
|
|
139
|
-
function namedExportName(node) {
|
|
140
|
-
if (node.declaration) {
|
|
141
|
-
if (t.isFunctionDeclaration(node.declaration) && node.declaration.id) {
|
|
142
|
-
return node.declaration.id.name;
|
|
143
|
-
}
|
|
144
|
-
if (t.isVariableDeclaration(node.declaration)) {
|
|
145
|
-
const id = node.declaration.declarations[0]?.id;
|
|
146
|
-
if (t.isIdentifier(id)) return id.name;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
for (const spec of node.specifiers) {
|
|
150
|
-
if (t.isExportSpecifier(spec) && t.isIdentifier(spec.exported)) {
|
|
151
|
-
return spec.exported.name;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bir kaynak dosyasını page / layout / client / server-component olarak sınıflandırır.
|
|
3
|
+
*/
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { t } from "./babel.mjs";
|
|
6
|
+
import { parseSource } from "./parse.mjs";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {'page' | 'layout' | 'client' | 'server-component' | 'unknown'} FileKind
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* kind: FileKind,
|
|
12
|
+
* isClient: boolean,
|
|
13
|
+
* hasHooks: boolean,
|
|
14
|
+
* hasServerAction: boolean,
|
|
15
|
+
* hasSuspense: boolean,
|
|
16
|
+
* revalidate: number | null,
|
|
17
|
+
* hasGenerateMetadata: boolean,
|
|
18
|
+
* hasGenerateStaticParams: boolean,
|
|
19
|
+
* reasons: string[],
|
|
20
|
+
* }} Classification
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} code
|
|
25
|
+
* @param {string} filename
|
|
26
|
+
* @returns {Classification}
|
|
27
|
+
*/
|
|
28
|
+
export function classifySource(code, filename) {
|
|
29
|
+
const base = path.basename(filename);
|
|
30
|
+
/** @type {Classification} */
|
|
31
|
+
const out = {
|
|
32
|
+
kind: "unknown",
|
|
33
|
+
isClient: false,
|
|
34
|
+
hasHooks: false,
|
|
35
|
+
hasServerAction: false,
|
|
36
|
+
hasSuspense: false,
|
|
37
|
+
revalidate: null,
|
|
38
|
+
hasGenerateMetadata: false,
|
|
39
|
+
hasGenerateStaticParams: false,
|
|
40
|
+
reasons: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (/^["']use client["'];?/.test(code.trim()) || /\n["']use client["'];?/.test(code)) {
|
|
44
|
+
out.isClient = true;
|
|
45
|
+
}
|
|
46
|
+
if (code.includes('"use server"') || code.includes("'use server'")) {
|
|
47
|
+
out.hasServerAction = true;
|
|
48
|
+
out.reasons.push("contains \"use server\"");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let ast;
|
|
52
|
+
try {
|
|
53
|
+
ast = parseSource(code, filename);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
out.reasons.push(
|
|
56
|
+
`parse error: ${error instanceof Error ? error.message : String(error)}`,
|
|
57
|
+
);
|
|
58
|
+
if (out.isClient) out.kind = "client";
|
|
59
|
+
else if (/^page\./.test(base)) out.kind = "page";
|
|
60
|
+
else if (/^layout\./.test(base)) out.kind = "layout";
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const node of ast.program.directives ?? []) {
|
|
65
|
+
if (node.value?.value === "use client") out.isClient = true;
|
|
66
|
+
if (node.value?.value === "use server") {
|
|
67
|
+
out.hasServerAction = true;
|
|
68
|
+
out.reasons.push("directive \"use server\"");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
for (const node of ast.program.body) {
|
|
73
|
+
if (t.isImportDeclaration(node)) {
|
|
74
|
+
const src = node.source.value;
|
|
75
|
+
if (src === "react" || src === "react/jsx-runtime") {
|
|
76
|
+
for (const spec of node.specifiers) {
|
|
77
|
+
const name =
|
|
78
|
+
(t.isImportSpecifier(spec) && t.isIdentifier(spec.imported)
|
|
79
|
+
? spec.imported.name
|
|
80
|
+
: null) ||
|
|
81
|
+
(t.isImportDefaultSpecifier(spec) ? "default" : null);
|
|
82
|
+
if (name === "Suspense") out.hasSuspense = true;
|
|
83
|
+
if (
|
|
84
|
+
name &&
|
|
85
|
+
/^(use[A-Z]|useState|useEffect|useMemo|useCallback|useRef|useContext|useReducer|useLayoutEffect|useId|useTransition|useDeferredValue|useSyncExternalStore)/.test(
|
|
86
|
+
name,
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
out.hasHooks = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
t.isExportNamedDeclaration(node) &&
|
|
97
|
+
node.declaration &&
|
|
98
|
+
t.isVariableDeclaration(node.declaration)
|
|
99
|
+
) {
|
|
100
|
+
for (const decl of node.declaration.declarations) {
|
|
101
|
+
if (t.isIdentifier(decl.id) && decl.id.name === "revalidate") {
|
|
102
|
+
if (t.isNumericLiteral(decl.init)) out.revalidate = decl.init.value;
|
|
103
|
+
else if (t.isLiteral(decl.init) && typeof decl.init.value === "number") {
|
|
104
|
+
out.revalidate = /** @type {number} */ (decl.init.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (t.isExportNamedDeclaration(node)) {
|
|
111
|
+
const id = namedExportName(node);
|
|
112
|
+
if (id === "generateMetadata") out.hasGenerateMetadata = true;
|
|
113
|
+
if (id === "generateStaticParams") out.hasGenerateStaticParams = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Hook çağrıları (import alias olmadan da)
|
|
118
|
+
if (/\buse(State|Effect|Memo|Callback|Ref|Context|Reducer)\s*\(/.test(code)) {
|
|
119
|
+
out.hasHooks = true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (out.isClient || out.hasHooks) {
|
|
123
|
+
out.kind = "client";
|
|
124
|
+
} else if (/^page\./.test(base)) {
|
|
125
|
+
out.kind = "page";
|
|
126
|
+
} else if (/^layout\./.test(base)) {
|
|
127
|
+
out.kind = "layout";
|
|
128
|
+
} else {
|
|
129
|
+
out.kind = "server-component";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {import('@babel/types').ExportNamedDeclaration} node
|
|
137
|
+
* @returns {string | null}
|
|
138
|
+
*/
|
|
139
|
+
function namedExportName(node) {
|
|
140
|
+
if (node.declaration) {
|
|
141
|
+
if (t.isFunctionDeclaration(node.declaration) && node.declaration.id) {
|
|
142
|
+
return node.declaration.id.name;
|
|
143
|
+
}
|
|
144
|
+
if (t.isVariableDeclaration(node.declaration)) {
|
|
145
|
+
const id = node.declaration.declarations[0]?.id;
|
|
146
|
+
if (t.isIdentifier(id)) return id.name;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const spec of node.specifiers) {
|
|
150
|
+
if (t.isExportSpecifier(spec) && t.isIdentifier(spec.exported)) {
|
|
151
|
+
return spec.exported.name;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|