@impakers/debug 1.4.5 → 1.4.7
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/next.d.mts +4 -0
- package/dist/next.d.ts +4 -0
- package/dist/next.js +127 -3
- package/dist/next.mjs +127 -3
- package/dist/react.js +246 -55
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +246 -55
- package/dist/react.mjs.map +1 -1
- package/package.json +1 -1
package/dist/next.d.mts
CHANGED
|
@@ -10,6 +10,10 @@ interface ImpakersDebugOptions {
|
|
|
10
10
|
* false로 설정하면 소스코드가 .map에 그대로 노출됨
|
|
11
11
|
*/
|
|
12
12
|
stripSourceContent?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Next App Router route -> file manifest 생성 여부 (기본: true)
|
|
15
|
+
*/
|
|
16
|
+
emitRouteManifest?: boolean;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Next.js config wrapper — 소스맵 설정 자동 구성
|
package/dist/next.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ interface ImpakersDebugOptions {
|
|
|
10
10
|
* false로 설정하면 소스코드가 .map에 그대로 노출됨
|
|
11
11
|
*/
|
|
12
12
|
stripSourceContent?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Next App Router route -> file manifest 생성 여부 (기본: true)
|
|
15
|
+
*/
|
|
16
|
+
emitRouteManifest?: boolean;
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
15
19
|
* Next.js config wrapper — 소스맵 설정 자동 구성
|
package/dist/next.js
CHANGED
|
@@ -89,8 +89,127 @@ var StripSourceContentPlugin = class {
|
|
|
89
89
|
);
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
|
+
var RouteManifestPlugin = class {
|
|
93
|
+
constructor(projectDir) {
|
|
94
|
+
this.projectDir = projectDir;
|
|
95
|
+
}
|
|
96
|
+
apply(compiler) {
|
|
97
|
+
compiler.hooks.afterEmit.tapAsync(
|
|
98
|
+
"ImpakersDebugRouteManifest",
|
|
99
|
+
(compilation, callback) => {
|
|
100
|
+
const fs = require("fs");
|
|
101
|
+
const path = require("path");
|
|
102
|
+
const outputPath = compilation.outputOptions?.path;
|
|
103
|
+
if (!outputPath) {
|
|
104
|
+
callback();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const appDir = findAppDirectory(fs, path, this.projectDir);
|
|
108
|
+
if (!appDir) {
|
|
109
|
+
callback();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const entries = scanRouteManifestEntries(fs, path, this.projectDir, appDir);
|
|
113
|
+
if (entries.length === 0) {
|
|
114
|
+
callback();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const manifestPath = path.resolve(
|
|
118
|
+
outputPath,
|
|
119
|
+
"../static/chunks/impakers-debug-route-manifest.json"
|
|
120
|
+
);
|
|
121
|
+
try {
|
|
122
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
123
|
+
fs.writeFileSync(
|
|
124
|
+
manifestPath,
|
|
125
|
+
JSON.stringify({ version: 1, entries }, null, 2),
|
|
126
|
+
"utf-8"
|
|
127
|
+
);
|
|
128
|
+
console.log(
|
|
129
|
+
`[@impakers/debug] Emitted route manifest with ${entries.length} route entries`
|
|
130
|
+
);
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
callback();
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function findAppDirectory(fs, path, projectDir) {
|
|
139
|
+
const candidates = [
|
|
140
|
+
path.join(projectDir, "src", "app"),
|
|
141
|
+
path.join(projectDir, "app")
|
|
142
|
+
];
|
|
143
|
+
for (const candidate of candidates) {
|
|
144
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
145
|
+
return candidate;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function toPosixPath(path) {
|
|
151
|
+
return path.replace(/\\/g, "/");
|
|
152
|
+
}
|
|
153
|
+
function findRouteFile(fs, path, dir, baseName) {
|
|
154
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
155
|
+
for (const ext of extensions) {
|
|
156
|
+
const file = path.join(dir, `${baseName}${ext}`);
|
|
157
|
+
if (fs.existsSync(file) && fs.statSync(file).isFile()) {
|
|
158
|
+
return file;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function getUrlSegment(segmentName) {
|
|
164
|
+
if (!segmentName) return null;
|
|
165
|
+
if (segmentName.startsWith("(") && segmentName.endsWith(")")) return null;
|
|
166
|
+
return segmentName;
|
|
167
|
+
}
|
|
168
|
+
function routeFromSegments(segments) {
|
|
169
|
+
if (segments.length === 0) return "/";
|
|
170
|
+
return `/${segments.join("/")}`;
|
|
171
|
+
}
|
|
172
|
+
function scanRouteManifestEntries(fs, path, projectDir, appDir) {
|
|
173
|
+
const entries = [];
|
|
174
|
+
const walk = (currentDir, routeSegments, layoutChain) => {
|
|
175
|
+
const currentLayout = findRouteFile(fs, path, currentDir, "layout");
|
|
176
|
+
const nextLayoutChain = currentLayout ? [
|
|
177
|
+
...layoutChain,
|
|
178
|
+
{
|
|
179
|
+
kind: "layout",
|
|
180
|
+
file: toPosixPath(path.relative(projectDir, currentLayout))
|
|
181
|
+
}
|
|
182
|
+
] : layoutChain;
|
|
183
|
+
const currentPage = findRouteFile(fs, path, currentDir, "page");
|
|
184
|
+
if (currentPage) {
|
|
185
|
+
entries.push({
|
|
186
|
+
route: routeFromSegments(routeSegments),
|
|
187
|
+
segments: [...routeSegments],
|
|
188
|
+
files: [
|
|
189
|
+
{
|
|
190
|
+
kind: "page",
|
|
191
|
+
file: toPosixPath(path.relative(projectDir, currentPage))
|
|
192
|
+
},
|
|
193
|
+
...nextLayoutChain
|
|
194
|
+
]
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const children = fs.readdirSync(currentDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
|
|
198
|
+
for (const child of children) {
|
|
199
|
+
if (child.name.startsWith("@")) continue;
|
|
200
|
+
const nextSegment = getUrlSegment(child.name);
|
|
201
|
+
walk(
|
|
202
|
+
path.join(currentDir, child.name),
|
|
203
|
+
nextSegment ? [...routeSegments, nextSegment] : routeSegments,
|
|
204
|
+
nextLayoutChain
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
walk(appDir, [], []);
|
|
209
|
+
return entries;
|
|
210
|
+
}
|
|
92
211
|
function withImpakersDebug(nextConfig, options = {}) {
|
|
93
|
-
const { stripSourceContent = true } = options;
|
|
212
|
+
const { stripSourceContent = true, emitRouteManifest = true } = options;
|
|
94
213
|
return {
|
|
95
214
|
...nextConfig,
|
|
96
215
|
// 소스맵 생성 활성화
|
|
@@ -99,9 +218,14 @@ function withImpakersDebug(nextConfig, options = {}) {
|
|
|
99
218
|
if (typeof nextConfig.webpack === "function") {
|
|
100
219
|
config = nextConfig.webpack(config, context);
|
|
101
220
|
}
|
|
102
|
-
if (!context.isServer
|
|
221
|
+
if (!context.isServer) {
|
|
103
222
|
config.plugins = config.plugins || [];
|
|
104
|
-
|
|
223
|
+
if (stripSourceContent) {
|
|
224
|
+
config.plugins.push(new StripSourceContentPlugin());
|
|
225
|
+
}
|
|
226
|
+
if (emitRouteManifest && context.dir) {
|
|
227
|
+
config.plugins.push(new RouteManifestPlugin(context.dir));
|
|
228
|
+
}
|
|
105
229
|
}
|
|
106
230
|
return config;
|
|
107
231
|
}
|
package/dist/next.mjs
CHANGED
|
@@ -72,8 +72,127 @@ var StripSourceContentPlugin = class {
|
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
74
|
};
|
|
75
|
+
var RouteManifestPlugin = class {
|
|
76
|
+
constructor(projectDir) {
|
|
77
|
+
this.projectDir = projectDir;
|
|
78
|
+
}
|
|
79
|
+
apply(compiler) {
|
|
80
|
+
compiler.hooks.afterEmit.tapAsync(
|
|
81
|
+
"ImpakersDebugRouteManifest",
|
|
82
|
+
(compilation, callback) => {
|
|
83
|
+
const fs = __require("fs");
|
|
84
|
+
const path = __require("path");
|
|
85
|
+
const outputPath = compilation.outputOptions?.path;
|
|
86
|
+
if (!outputPath) {
|
|
87
|
+
callback();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const appDir = findAppDirectory(fs, path, this.projectDir);
|
|
91
|
+
if (!appDir) {
|
|
92
|
+
callback();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const entries = scanRouteManifestEntries(fs, path, this.projectDir, appDir);
|
|
96
|
+
if (entries.length === 0) {
|
|
97
|
+
callback();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const manifestPath = path.resolve(
|
|
101
|
+
outputPath,
|
|
102
|
+
"../static/chunks/impakers-debug-route-manifest.json"
|
|
103
|
+
);
|
|
104
|
+
try {
|
|
105
|
+
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(
|
|
107
|
+
manifestPath,
|
|
108
|
+
JSON.stringify({ version: 1, entries }, null, 2),
|
|
109
|
+
"utf-8"
|
|
110
|
+
);
|
|
111
|
+
console.log(
|
|
112
|
+
`[@impakers/debug] Emitted route manifest with ${entries.length} route entries`
|
|
113
|
+
);
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
callback();
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
function findAppDirectory(fs, path, projectDir) {
|
|
122
|
+
const candidates = [
|
|
123
|
+
path.join(projectDir, "src", "app"),
|
|
124
|
+
path.join(projectDir, "app")
|
|
125
|
+
];
|
|
126
|
+
for (const candidate of candidates) {
|
|
127
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
|
128
|
+
return candidate;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
function toPosixPath(path) {
|
|
134
|
+
return path.replace(/\\/g, "/");
|
|
135
|
+
}
|
|
136
|
+
function findRouteFile(fs, path, dir, baseName) {
|
|
137
|
+
const extensions = [".tsx", ".ts", ".jsx", ".js"];
|
|
138
|
+
for (const ext of extensions) {
|
|
139
|
+
const file = path.join(dir, `${baseName}${ext}`);
|
|
140
|
+
if (fs.existsSync(file) && fs.statSync(file).isFile()) {
|
|
141
|
+
return file;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function getUrlSegment(segmentName) {
|
|
147
|
+
if (!segmentName) return null;
|
|
148
|
+
if (segmentName.startsWith("(") && segmentName.endsWith(")")) return null;
|
|
149
|
+
return segmentName;
|
|
150
|
+
}
|
|
151
|
+
function routeFromSegments(segments) {
|
|
152
|
+
if (segments.length === 0) return "/";
|
|
153
|
+
return `/${segments.join("/")}`;
|
|
154
|
+
}
|
|
155
|
+
function scanRouteManifestEntries(fs, path, projectDir, appDir) {
|
|
156
|
+
const entries = [];
|
|
157
|
+
const walk = (currentDir, routeSegments, layoutChain) => {
|
|
158
|
+
const currentLayout = findRouteFile(fs, path, currentDir, "layout");
|
|
159
|
+
const nextLayoutChain = currentLayout ? [
|
|
160
|
+
...layoutChain,
|
|
161
|
+
{
|
|
162
|
+
kind: "layout",
|
|
163
|
+
file: toPosixPath(path.relative(projectDir, currentLayout))
|
|
164
|
+
}
|
|
165
|
+
] : layoutChain;
|
|
166
|
+
const currentPage = findRouteFile(fs, path, currentDir, "page");
|
|
167
|
+
if (currentPage) {
|
|
168
|
+
entries.push({
|
|
169
|
+
route: routeFromSegments(routeSegments),
|
|
170
|
+
segments: [...routeSegments],
|
|
171
|
+
files: [
|
|
172
|
+
{
|
|
173
|
+
kind: "page",
|
|
174
|
+
file: toPosixPath(path.relative(projectDir, currentPage))
|
|
175
|
+
},
|
|
176
|
+
...nextLayoutChain
|
|
177
|
+
]
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const children = fs.readdirSync(currentDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
|
|
181
|
+
for (const child of children) {
|
|
182
|
+
if (child.name.startsWith("@")) continue;
|
|
183
|
+
const nextSegment = getUrlSegment(child.name);
|
|
184
|
+
walk(
|
|
185
|
+
path.join(currentDir, child.name),
|
|
186
|
+
nextSegment ? [...routeSegments, nextSegment] : routeSegments,
|
|
187
|
+
nextLayoutChain
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
walk(appDir, [], []);
|
|
192
|
+
return entries;
|
|
193
|
+
}
|
|
75
194
|
function withImpakersDebug(nextConfig, options = {}) {
|
|
76
|
-
const { stripSourceContent = true } = options;
|
|
195
|
+
const { stripSourceContent = true, emitRouteManifest = true } = options;
|
|
77
196
|
return {
|
|
78
197
|
...nextConfig,
|
|
79
198
|
// 소스맵 생성 활성화
|
|
@@ -82,9 +201,14 @@ function withImpakersDebug(nextConfig, options = {}) {
|
|
|
82
201
|
if (typeof nextConfig.webpack === "function") {
|
|
83
202
|
config = nextConfig.webpack(config, context);
|
|
84
203
|
}
|
|
85
|
-
if (!context.isServer
|
|
204
|
+
if (!context.isServer) {
|
|
86
205
|
config.plugins = config.plugins || [];
|
|
87
|
-
|
|
206
|
+
if (stripSourceContent) {
|
|
207
|
+
config.plugins.push(new StripSourceContentPlugin());
|
|
208
|
+
}
|
|
209
|
+
if (emitRouteManifest && context.dir) {
|
|
210
|
+
config.plugins.push(new RouteManifestPlugin(context.dir));
|
|
211
|
+
}
|
|
88
212
|
}
|
|
89
213
|
return config;
|
|
90
214
|
}
|
package/dist/react.js
CHANGED
|
@@ -1569,35 +1569,70 @@ function findNearestComponentSource(element, maxAncestors = 10) {
|
|
|
1569
1569
|
}
|
|
1570
1570
|
|
|
1571
1571
|
// src/utils/capture-element.ts
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const rect = el.getBoundingClientRect();
|
|
1575
|
-
const scale = Math.min(window.devicePixelRatio, maxScale);
|
|
1576
|
-
const canvas = document.createElement("canvas");
|
|
1577
|
-
canvas.width = rect.width * scale;
|
|
1578
|
-
canvas.height = rect.height * scale;
|
|
1579
|
-
const ctx = canvas.getContext("2d");
|
|
1580
|
-
ctx.scale(scale, scale);
|
|
1581
|
-
const clone = el.cloneNode(true);
|
|
1582
|
-
inlineComputedStyles(el, clone);
|
|
1583
|
-
clone.style.margin = "0";
|
|
1584
|
-
clone.style.position = "static";
|
|
1585
|
-
const serializer = new XMLSerializer();
|
|
1586
|
-
const html = serializer.serializeToString(clone);
|
|
1587
|
-
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${rect.width}" height="${rect.height}">
|
|
1588
|
-
<foreignObject width="100%" height="100%">
|
|
1589
|
-
<div xmlns="http://www.w3.org/1999/xhtml" style="width:${rect.width}px;height:${rect.height}px;overflow:hidden">${html}</div>
|
|
1590
|
-
</foreignObject>
|
|
1591
|
-
</svg>`;
|
|
1592
|
-
const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
|
|
1593
|
-
const url = URL.createObjectURL(blob);
|
|
1572
|
+
var UNSUPPORTED_RE = /oklab|oklch|color-mix/i;
|
|
1573
|
+
function sanitizeUnsupportedColors(clonedDoc) {
|
|
1594
1574
|
try {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1575
|
+
for (let i = 0; i < clonedDoc.styleSheets.length; i++) {
|
|
1576
|
+
try {
|
|
1577
|
+
const sheet = clonedDoc.styleSheets[i];
|
|
1578
|
+
const rules = sheet.cssRules || sheet.rules;
|
|
1579
|
+
if (!rules) continue;
|
|
1580
|
+
sanitizeCSSRules(sheet, rules);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
} catch {
|
|
1600
1585
|
}
|
|
1586
|
+
clonedDoc.querySelectorAll("style").forEach((styleEl) => {
|
|
1587
|
+
const text = styleEl.textContent || "";
|
|
1588
|
+
if (UNSUPPORTED_RE.test(text)) {
|
|
1589
|
+
styleEl.textContent = text.replace(
|
|
1590
|
+
/[^{};\n]+:\s*[^;{}]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;{}]*/gi,
|
|
1591
|
+
""
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
clonedDoc.querySelectorAll("*").forEach((el) => {
|
|
1596
|
+
const s = el.style;
|
|
1597
|
+
if (!s?.cssText) return;
|
|
1598
|
+
if (UNSUPPORTED_RE.test(s.cssText)) {
|
|
1599
|
+
s.cssText = s.cssText.replace(
|
|
1600
|
+
/[^;]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;]*/gi,
|
|
1601
|
+
""
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
function sanitizeCSSRules(sheet, rules) {
|
|
1607
|
+
for (let j = rules.length - 1; j >= 0; j--) {
|
|
1608
|
+
const rule = rules[j];
|
|
1609
|
+
if ("cssRules" in rule && rule.cssRules) {
|
|
1610
|
+
sanitizeCSSRules(sheet, rule.cssRules);
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (rule instanceof CSSStyleRule) {
|
|
1614
|
+
const style = rule.style;
|
|
1615
|
+
for (let k = style.length - 1; k >= 0; k--) {
|
|
1616
|
+
const prop = style[k];
|
|
1617
|
+
const val = style.getPropertyValue(prop);
|
|
1618
|
+
if (UNSUPPORTED_RE.test(val)) {
|
|
1619
|
+
style.setProperty(prop, "transparent");
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function captureElement(el, options = {}) {
|
|
1626
|
+
const { quality = 0.7, maxScale = 2 } = options;
|
|
1627
|
+
const html2canvas = (await import("html2canvas")).default;
|
|
1628
|
+
const canvas = await html2canvas(el, {
|
|
1629
|
+
useCORS: true,
|
|
1630
|
+
allowTaint: true,
|
|
1631
|
+
scale: Math.min(window.devicePixelRatio, maxScale),
|
|
1632
|
+
logging: false,
|
|
1633
|
+
onclone: (clonedDoc) => sanitizeUnsupportedColors(clonedDoc)
|
|
1634
|
+
});
|
|
1635
|
+
return canvas.toDataURL("image/jpeg", quality);
|
|
1601
1636
|
}
|
|
1602
1637
|
async function captureFullPage(options = {}) {
|
|
1603
1638
|
const { quality = 0.5, hideSelectors = [] } = options;
|
|
@@ -1614,17 +1649,7 @@ async function captureFullPage(options = {}) {
|
|
|
1614
1649
|
logging: false,
|
|
1615
1650
|
width: window.innerWidth,
|
|
1616
1651
|
height: window.innerHeight,
|
|
1617
|
-
onclone: (clonedDoc) =>
|
|
1618
|
-
const unsupported = /oklab|oklch|color-mix/i;
|
|
1619
|
-
clonedDoc.querySelectorAll("style").forEach((styleEl) => {
|
|
1620
|
-
if (unsupported.test(styleEl.textContent || "")) {
|
|
1621
|
-
styleEl.textContent = (styleEl.textContent || "").replace(
|
|
1622
|
-
/[^{};\n]+:\s*[^;{}]*(?:oklab|oklch|color-mix|lch|lab)\([^)]*(?:\([^)]*\))*[^)]*\)[^;{}]*/gi,
|
|
1623
|
-
""
|
|
1624
|
-
);
|
|
1625
|
-
}
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1652
|
+
onclone: (clonedDoc) => sanitizeUnsupportedColors(clonedDoc)
|
|
1628
1653
|
});
|
|
1629
1654
|
return canvas.toDataURL("image/jpeg", quality);
|
|
1630
1655
|
} catch {
|
|
@@ -1633,23 +1658,6 @@ async function captureFullPage(options = {}) {
|
|
|
1633
1658
|
hiddenEls.forEach((el) => el.style.visibility = "");
|
|
1634
1659
|
}
|
|
1635
1660
|
}
|
|
1636
|
-
function loadImage(src) {
|
|
1637
|
-
return new Promise((resolve, reject) => {
|
|
1638
|
-
const img = new Image();
|
|
1639
|
-
img.onload = () => resolve(img);
|
|
1640
|
-
img.onerror = () => reject(new Error("\uC774\uBBF8\uC9C0 \uB85C\uB4DC \uC2E4\uD328"));
|
|
1641
|
-
img.src = src;
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
function inlineComputedStyles(src, dst) {
|
|
1645
|
-
const cs = window.getComputedStyle(src);
|
|
1646
|
-
dst.style.cssText = cs.cssText;
|
|
1647
|
-
const srcChildren = src.children;
|
|
1648
|
-
const dstChildren = dst.children;
|
|
1649
|
-
for (let i = 0; i < srcChildren.length && i < dstChildren.length; i++) {
|
|
1650
|
-
inlineComputedStyles(srcChildren[i], dstChildren[i]);
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
1661
|
|
|
1654
1662
|
// src/core/collector.ts
|
|
1655
1663
|
var MAX_ERRORS = 20;
|
|
@@ -1866,6 +1874,151 @@ function collectMetadata(getUser) {
|
|
|
1866
1874
|
return metadata;
|
|
1867
1875
|
}
|
|
1868
1876
|
|
|
1877
|
+
// src/core/debug-targets.ts
|
|
1878
|
+
var NEXT_ROUTE_MANIFEST_URL = "/_next/static/chunks/impakers-debug-route-manifest.json";
|
|
1879
|
+
var manifestPromise = null;
|
|
1880
|
+
var manifestFailed = false;
|
|
1881
|
+
function normalizePathname(pathname) {
|
|
1882
|
+
if (!pathname || pathname === "/") return "/";
|
|
1883
|
+
return pathname.endsWith("/") ? pathname.slice(0, -1) || "/" : pathname;
|
|
1884
|
+
}
|
|
1885
|
+
function splitPathname(pathname) {
|
|
1886
|
+
const normalized = normalizePathname(pathname);
|
|
1887
|
+
if (normalized === "/") return [];
|
|
1888
|
+
return normalized.slice(1).split("/").map((segment) => decodeURIComponent(segment)).filter(Boolean);
|
|
1889
|
+
}
|
|
1890
|
+
function isDynamicSegment(segment) {
|
|
1891
|
+
return /^\[[^\]]+\]$/.test(segment);
|
|
1892
|
+
}
|
|
1893
|
+
function isCatchAllSegment(segment) {
|
|
1894
|
+
return /^\[\.\.\.[^\]]+\]$/.test(segment);
|
|
1895
|
+
}
|
|
1896
|
+
function isOptionalCatchAllSegment(segment) {
|
|
1897
|
+
return /^\[\[\.\.\.[^\]]+\]\]$/.test(segment);
|
|
1898
|
+
}
|
|
1899
|
+
function scoreRouteMatch(routeSegments, pathnameSegments) {
|
|
1900
|
+
let routeIndex = 0;
|
|
1901
|
+
let pathIndex = 0;
|
|
1902
|
+
let score = 0;
|
|
1903
|
+
while (routeIndex < routeSegments.length) {
|
|
1904
|
+
const routeSegment = routeSegments[routeIndex];
|
|
1905
|
+
if (isOptionalCatchAllSegment(routeSegment)) {
|
|
1906
|
+
score += 1;
|
|
1907
|
+
pathIndex = pathnameSegments.length;
|
|
1908
|
+
routeIndex += 1;
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
if (isCatchAllSegment(routeSegment)) {
|
|
1912
|
+
if (pathIndex >= pathnameSegments.length) return null;
|
|
1913
|
+
score += 2;
|
|
1914
|
+
pathIndex = pathnameSegments.length;
|
|
1915
|
+
routeIndex += 1;
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
const pathSegment = pathnameSegments[pathIndex];
|
|
1919
|
+
if (pathSegment === void 0) return null;
|
|
1920
|
+
if (isDynamicSegment(routeSegment)) {
|
|
1921
|
+
score += 5;
|
|
1922
|
+
routeIndex += 1;
|
|
1923
|
+
pathIndex += 1;
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
if (routeSegment !== pathSegment) return null;
|
|
1927
|
+
score += 20;
|
|
1928
|
+
routeIndex += 1;
|
|
1929
|
+
pathIndex += 1;
|
|
1930
|
+
}
|
|
1931
|
+
if (routeIndex !== routeSegments.length) return null;
|
|
1932
|
+
if (pathIndex !== pathnameSegments.length) return null;
|
|
1933
|
+
return score;
|
|
1934
|
+
}
|
|
1935
|
+
async function loadNextRouteManifest() {
|
|
1936
|
+
if (manifestFailed) return null;
|
|
1937
|
+
if (manifestPromise) return manifestPromise;
|
|
1938
|
+
manifestPromise = (async () => {
|
|
1939
|
+
try {
|
|
1940
|
+
const res = await fetch(NEXT_ROUTE_MANIFEST_URL, { cache: "force-cache" });
|
|
1941
|
+
if (!res.ok) {
|
|
1942
|
+
manifestFailed = true;
|
|
1943
|
+
return null;
|
|
1944
|
+
}
|
|
1945
|
+
const json = await res.json();
|
|
1946
|
+
if (!json || typeof json !== "object" || !Array.isArray(json.entries)) {
|
|
1947
|
+
manifestFailed = true;
|
|
1948
|
+
return null;
|
|
1949
|
+
}
|
|
1950
|
+
return json;
|
|
1951
|
+
} catch {
|
|
1952
|
+
manifestFailed = true;
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
})();
|
|
1956
|
+
return manifestPromise;
|
|
1957
|
+
}
|
|
1958
|
+
function buildRouteTargets(entry) {
|
|
1959
|
+
const pageTarget = entry.files.find((file) => file.kind === "page");
|
|
1960
|
+
const layoutTargets = entry.files.filter((file) => file.kind === "layout");
|
|
1961
|
+
const targets = [];
|
|
1962
|
+
if (pageTarget) {
|
|
1963
|
+
targets.push({
|
|
1964
|
+
kind: "route-page",
|
|
1965
|
+
file: pageTarget.file,
|
|
1966
|
+
label: "Current page route",
|
|
1967
|
+
confidence: 1,
|
|
1968
|
+
reason: "matched-current-url"
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
layoutTargets.forEach((layout, index) => {
|
|
1972
|
+
targets.push({
|
|
1973
|
+
kind: "route-layout",
|
|
1974
|
+
file: layout.file,
|
|
1975
|
+
label: index === layoutTargets.length - 1 ? "Nearest layout" : "Ancestor layout",
|
|
1976
|
+
confidence: Math.max(0.7, 0.95 - index * 0.08),
|
|
1977
|
+
reason: "matched-layout-chain"
|
|
1978
|
+
});
|
|
1979
|
+
});
|
|
1980
|
+
return targets;
|
|
1981
|
+
}
|
|
1982
|
+
async function getRouteDebugTargets(pathname) {
|
|
1983
|
+
if (typeof window === "undefined") return null;
|
|
1984
|
+
const currentPathname = pathname ?? window.location.pathname;
|
|
1985
|
+
const manifest = await loadNextRouteManifest();
|
|
1986
|
+
if (!manifest) return null;
|
|
1987
|
+
const pathnameSegments = splitPathname(currentPathname);
|
|
1988
|
+
let bestEntry = null;
|
|
1989
|
+
let bestScore = -1;
|
|
1990
|
+
for (const entry of manifest.entries) {
|
|
1991
|
+
const score = scoreRouteMatch(entry.segments, pathnameSegments);
|
|
1992
|
+
if (score === null) continue;
|
|
1993
|
+
if (score > bestScore) {
|
|
1994
|
+
bestScore = score;
|
|
1995
|
+
bestEntry = entry;
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (!bestEntry) return null;
|
|
1999
|
+
return {
|
|
2000
|
+
targets: buildRouteTargets(bestEntry),
|
|
2001
|
+
context: {
|
|
2002
|
+
pathname: normalizePathname(currentPathname),
|
|
2003
|
+
matchedRoute: bestEntry.route,
|
|
2004
|
+
source: "next-route-manifest"
|
|
2005
|
+
}
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
function mergeDebugTargets(...groups) {
|
|
2009
|
+
const merged = [];
|
|
2010
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2011
|
+
groups.forEach((group) => {
|
|
2012
|
+
group?.forEach((target) => {
|
|
2013
|
+
const key = `${target.kind}:${target.file}:${target.line ?? ""}:${target.column ?? ""}`;
|
|
2014
|
+
if (seen.has(key)) return;
|
|
2015
|
+
seen.add(key);
|
|
2016
|
+
merged.push(target);
|
|
2017
|
+
});
|
|
2018
|
+
});
|
|
2019
|
+
return merged.sort((a, b) => b.confidence - a.confidence);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1869
2022
|
// src/core/api.ts
|
|
1870
2023
|
function getToken() {
|
|
1871
2024
|
const token = loadToken();
|
|
@@ -3433,6 +3586,23 @@ function detectSourceFileCandidates(element) {
|
|
|
3433
3586
|
}
|
|
3434
3587
|
return [];
|
|
3435
3588
|
}
|
|
3589
|
+
function buildComponentDebugTarget(resolvedSource, componentName) {
|
|
3590
|
+
const match = resolvedSource.match(/^(.+):(\d+)(?::(\d+))?$/);
|
|
3591
|
+
if (!match) return null;
|
|
3592
|
+
const [, file, lineStr, columnStr] = match;
|
|
3593
|
+
const line = Number.parseInt(lineStr, 10);
|
|
3594
|
+
const column = columnStr ? Number.parseInt(columnStr, 10) : void 0;
|
|
3595
|
+
if (!file || Number.isNaN(line)) return null;
|
|
3596
|
+
return {
|
|
3597
|
+
kind: "component",
|
|
3598
|
+
file,
|
|
3599
|
+
line,
|
|
3600
|
+
column,
|
|
3601
|
+
label: componentName || "Resolved component source",
|
|
3602
|
+
confidence: 0.98,
|
|
3603
|
+
reason: "resolved-from-source-map"
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3436
3606
|
var STORAGE_PREFIX = "impakers-debug-markers-";
|
|
3437
3607
|
function getRouteKey() {
|
|
3438
3608
|
return STORAGE_PREFIX + window.location.pathname;
|
|
@@ -3754,7 +3924,28 @@ function DebugWidget({ endpoint, getUser, onHide }) {
|
|
|
3754
3924
|
} catch {
|
|
3755
3925
|
}
|
|
3756
3926
|
}
|
|
3927
|
+
const routeMatch = await getRouteDebugTargets(window.location.pathname);
|
|
3928
|
+
const componentTarget = resolvedSource && !resolvedSource.includes("/chunks/") ? buildComponentDebugTarget(resolvedSource, resolvedName) : null;
|
|
3929
|
+
const debugTargets = mergeDebugTargets(
|
|
3930
|
+
componentTarget ? [componentTarget] : void 0,
|
|
3931
|
+
routeMatch?.targets
|
|
3932
|
+
);
|
|
3933
|
+
if (debugTargets.length > 0) {
|
|
3934
|
+
metadata.debugTargets = debugTargets;
|
|
3935
|
+
}
|
|
3936
|
+
if (routeMatch?.context) {
|
|
3937
|
+
metadata.routeDebug = routeMatch.context;
|
|
3938
|
+
}
|
|
3757
3939
|
const descriptionParts = [comment];
|
|
3940
|
+
if (debugTargets.length > 0) {
|
|
3941
|
+
descriptionParts.push(
|
|
3942
|
+
"\n\n---\n**\uB514\uBC84\uAE45 \uD0C0\uAC9F \uD30C\uC77C**:",
|
|
3943
|
+
...debugTargets.map((target) => {
|
|
3944
|
+
const suffix = target.line ? `:${target.line}${typeof target.column === "number" ? `:${target.column}` : ""}` : "";
|
|
3945
|
+
return `- [${target.kind}] \`${target.file}${suffix}\` (${target.reason})`;
|
|
3946
|
+
})
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3758
3949
|
const elementInfo = [];
|
|
3759
3950
|
if (pendingAnnotation.element) {
|
|
3760
3951
|
elementInfo.push(`**\uC120\uD0DD\uB41C \uC694\uC18C**: ${pendingAnnotation.element}`);
|