@mandujs/core 0.12.1 โ 0.13.0
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.ko.md +304 -304
- package/README.md +653 -653
- package/package.json +8 -8
- package/src/brain/architecture/analyzer.ts +28 -26
- package/src/brain/doctor/analyzer.ts +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/bundler/dev.ts +0 -1
- package/src/change/history.ts +3 -3
- package/src/change/snapshot.ts +10 -9
- package/src/change/transaction.ts +2 -2
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +94 -96
- package/src/config/validate.ts +213 -215
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/classifier.ts +2 -2
- package/src/error/domains.ts +265 -265
- package/src/error/formatter.ts +32 -32
- package/src/error/result.ts +46 -46
- package/src/error/stack-analyzer.ts +5 -0
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +569 -569
- package/src/filling/deps.ts +238 -238
- package/src/generator/contract-glue.ts +2 -1
- package/src/generator/generate.ts +12 -10
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.ts +80 -79
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/check.ts +128 -128
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/healing.ts +2 -0
- package/src/guard/index.ts +2 -0
- package/src/guard/negotiation.ts +430 -4
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/cqrs.test.ts +175 -0
- package/src/guard/presets/cqrs.ts +107 -0
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -288
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -352
- package/src/guard/types.ts +348 -347
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +1 -0
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/paths.test.ts +47 -0
- package/src/paths.ts +47 -0
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/build.ts +1 -1
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-routes.ts +344 -401
- package/src/router/fs-scanner.ts +497 -497
- package/src/router/fs-types.ts +270 -278
- package/src/router/index.ts +81 -81
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +24 -24
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +367 -367
- package/src/runtime/streaming-ssr.ts +1245 -1245
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/rules.ts +5 -5
package/src/utils/string-safe.ts
CHANGED
|
@@ -1,298 +1,298 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DNA-005: UTF-16 Safe String Utilities
|
|
3
|
-
*
|
|
4
|
-
* ์ด๋ชจ์ง, ํน์ ๋ฌธ์ ๋ฑ ์๋ก๊ฒ์ดํธ ์์ ์์ ํ๊ฒ ์ฒ๋ฆฌ
|
|
5
|
-
* - ๋ฌธ์์ด ์ฌ๋ผ์ด์ฑ ์ ๊นจ์ง ๋ฐฉ์ง
|
|
6
|
-
* - ์๋ฌ ๋ฉ์์ง ํธ๋ ์ผ์ด์
|
|
7
|
-
* - ๋ก๊ทธ ๋ฉ์์ง ์ ํ
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* High Surrogate ๋ฒ์ ์ฒดํฌ (U+D800 ~ U+DBFF)
|
|
12
|
-
*/
|
|
13
|
-
function isHighSurrogate(codeUnit: number): boolean {
|
|
14
|
-
return codeUnit >= 0xd800 && codeUnit <= 0xdbff;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Low Surrogate ๋ฒ์ ์ฒดํฌ (U+DC00 ~ U+DFFF)
|
|
19
|
-
*/
|
|
20
|
-
function isLowSurrogate(codeUnit: number): boolean {
|
|
21
|
-
return codeUnit >= 0xdc00 && codeUnit <= 0xdfff;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* UTF-16 ์์ ๋ฌธ์์ด ์ฌ๋ผ์ด์ฑ
|
|
26
|
-
*
|
|
27
|
-
* ์๋ก๊ฒ์ดํธ ์ ๊ฒฝ๊ณ์์ ์๋ฆฌ์ง ์๋๋ก ๋ณดํธ
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* // ์ด๋ชจ์ง ํฌํจ ๋ฌธ์์ด
|
|
32
|
-
* const text = "Hello ๐ World";
|
|
33
|
-
*
|
|
34
|
-
* // ์ผ๋ฐ slice๋ ์ด๋ชจ์ง๋ฅผ ๊นจ๋จ๋ฆด ์ ์์
|
|
35
|
-
* text.slice(0, 7); // "Hello ๏ฟฝ" (๊นจ์ง)
|
|
36
|
-
*
|
|
37
|
-
* // ์์ ํ ์ฌ๋ผ์ด์ฑ
|
|
38
|
-
* sliceUtf16Safe(text, 0, 7); // "Hello " (์ด๋ชจ์ง ์ ์ธ)
|
|
39
|
-
* sliceUtf16Safe(text, 0, 8); // "Hello ๐" (์ด๋ชจ์ง ํฌํจ)
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export function sliceUtf16Safe(
|
|
43
|
-
input: string,
|
|
44
|
-
start: number,
|
|
45
|
-
end?: number
|
|
46
|
-
): string {
|
|
47
|
-
const len = input.length;
|
|
48
|
-
let from = Math.max(0, start);
|
|
49
|
-
let to = end === undefined ? len : Math.min(len, end);
|
|
50
|
-
|
|
51
|
-
// ์์ ์์น๊ฐ Low Surrogate๋ฉด ๊ฑด๋๋ฐ๊ธฐ
|
|
52
|
-
if (from > 0 && from < len) {
|
|
53
|
-
const codeUnit = input.charCodeAt(from);
|
|
54
|
-
if (isLowSurrogate(codeUnit) && isHighSurrogate(input.charCodeAt(from - 1))) {
|
|
55
|
-
from += 1;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ๋ ์์น๊ฐ Low Surrogate๋ฉด ์ ์ธ
|
|
60
|
-
if (to > 0 && to < len) {
|
|
61
|
-
const codeUnit = input.charCodeAt(to);
|
|
62
|
-
if (isLowSurrogate(codeUnit) && isHighSurrogate(input.charCodeAt(to - 1))) {
|
|
63
|
-
to -= 1;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return input.slice(from, to);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* ํธ๋ ์ผ์ด์
์ต์
|
|
72
|
-
*/
|
|
73
|
-
export interface TruncateOptions {
|
|
74
|
-
/** ์ต๋ ๊ธธ์ด (๊ธฐ๋ณธ: 100) */
|
|
75
|
-
maxLength?: number;
|
|
76
|
-
/** ์๋ต ํ์ (๊ธฐ๋ณธ: "...") */
|
|
77
|
-
ellipsis?: string;
|
|
78
|
-
/** ๋จ์ด ๊ฒฝ๊ณ์์ ์๋ฅด๊ธฐ */
|
|
79
|
-
wordBoundary?: boolean;
|
|
80
|
-
/** ํธ๋ ์ผ์ด์
์์น */
|
|
81
|
-
position?: "end" | "middle" | "start";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* ๋ฌธ์์ด ์์ ํธ๋ ์ผ์ด์
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```ts
|
|
89
|
-
* truncateSafe("Hello World! ๐๐", { maxLength: 12 });
|
|
90
|
-
* // โ "Hello Wor..."
|
|
91
|
-
*
|
|
92
|
-
* truncateSafe("Hello World! ๐๐", { maxLength: 12, wordBoundary: true });
|
|
93
|
-
* // โ "Hello..."
|
|
94
|
-
*
|
|
95
|
-
* truncateSafe("Hello World!", { maxLength: 8, position: "middle" });
|
|
96
|
-
* // โ "Hel...d!"
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
export function truncateSafe(
|
|
100
|
-
input: string,
|
|
101
|
-
options: TruncateOptions = {}
|
|
102
|
-
): string {
|
|
103
|
-
const {
|
|
104
|
-
maxLength = 100,
|
|
105
|
-
ellipsis = "...",
|
|
106
|
-
wordBoundary = false,
|
|
107
|
-
position = "end",
|
|
108
|
-
} = options;
|
|
109
|
-
|
|
110
|
-
// ์ด๋ฏธ ์งง์ผ๋ฉด ๊ทธ๋๋ก ๋ฐํ
|
|
111
|
-
if (input.length <= maxLength) {
|
|
112
|
-
return input;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const ellipsisLen = ellipsis.length;
|
|
116
|
-
const availableLen = maxLength - ellipsisLen;
|
|
117
|
-
|
|
118
|
-
if (availableLen <= 0) {
|
|
119
|
-
return ellipsis.slice(0, maxLength);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
switch (position) {
|
|
123
|
-
case "start": {
|
|
124
|
-
const start = input.length - availableLen;
|
|
125
|
-
let result = sliceUtf16Safe(input, start);
|
|
126
|
-
return ellipsis + result;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
case "middle": {
|
|
130
|
-
const halfLen = Math.floor(availableLen / 2);
|
|
131
|
-
const firstHalf = sliceUtf16Safe(input, 0, halfLen);
|
|
132
|
-
const secondHalf = sliceUtf16Safe(input, input.length - halfLen);
|
|
133
|
-
return firstHalf + ellipsis + secondHalf;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
case "end":
|
|
137
|
-
default: {
|
|
138
|
-
let result = sliceUtf16Safe(input, 0, availableLen);
|
|
139
|
-
|
|
140
|
-
if (wordBoundary) {
|
|
141
|
-
// ๋ง์ง๋ง ๊ณต๋ฐฑ ์ฐพ๊ธฐ
|
|
142
|
-
const lastSpace = result.lastIndexOf(" ");
|
|
143
|
-
if (lastSpace > 0) {
|
|
144
|
-
result = result.slice(0, lastSpace);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return result + ellipsis;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* ๋ฌธ์์ด ๊ธธ์ด (์ฝ๋ ํฌ์ธํธ ๊ธฐ์ค)
|
|
155
|
-
*
|
|
156
|
-
* @example
|
|
157
|
-
* ```ts
|
|
158
|
-
* "๐".length; // 2 (UTF-16 ์ฝ๋ ์ ๋)
|
|
159
|
-
* lengthInCodePoints("๐"); // 1 (์ฝ๋ ํฌ์ธํธ)
|
|
160
|
-
*
|
|
161
|
-
* "๐จโ๐ฉโ๐งโ๐ฆ".length; // 11
|
|
162
|
-
* lengthInCodePoints("๐จโ๐ฉโ๐งโ๐ฆ"); // 7 (ZWJ ํฌํจ)
|
|
163
|
-
* ```
|
|
164
|
-
*/
|
|
165
|
-
export function lengthInCodePoints(input: string): number {
|
|
166
|
-
return [...input].length;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* ์ฝ๋ ํฌ์ธํธ ๊ธฐ์ค ์ฌ๋ผ์ด์ฑ
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* ```ts
|
|
174
|
-
* const emoji = "๐๐๐";
|
|
175
|
-
* sliceByCodePoints(emoji, 0, 2); // "๐๐"
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
|
-
export function sliceByCodePoints(
|
|
179
|
-
input: string,
|
|
180
|
-
start: number,
|
|
181
|
-
end?: number
|
|
182
|
-
): string {
|
|
183
|
-
const codePoints = [...input];
|
|
184
|
-
const sliced = codePoints.slice(start, end);
|
|
185
|
-
return sliced.join("");
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* ๋ฌธ์์ด์์ ์ด๋ชจ์ง ์ ๊ฑฐ
|
|
190
|
-
*
|
|
191
|
-
* @example
|
|
192
|
-
* ```ts
|
|
193
|
-
* stripEmoji("Hello ๐ World ๐"); // "Hello World "
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
|
-
export function stripEmoji(input: string): string {
|
|
197
|
-
// ์ด๋ชจ์ง ์ ๊ท์ (Unicode ์์ฑ ๊ธฐ๋ฐ)
|
|
198
|
-
return input.replace(
|
|
199
|
-
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu,
|
|
200
|
-
""
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* ๋ฌธ์์ด์์ ์๋ก๊ฒ์ดํธ ์ ๊ฒ์ถ
|
|
206
|
-
*
|
|
207
|
-
* @example
|
|
208
|
-
* ```ts
|
|
209
|
-
* hasSurrogates("Hello"); // false
|
|
210
|
-
* hasSurrogates("Hello ๐"); // true
|
|
211
|
-
* ```
|
|
212
|
-
*/
|
|
213
|
-
export function hasSurrogates(input: string): boolean {
|
|
214
|
-
for (let i = 0; i < input.length; i++) {
|
|
215
|
-
if (isHighSurrogate(input.charCodeAt(i))) {
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* ์๋ชป๋ ์๋ก๊ฒ์ดํธ ์ํ์ค ์์
|
|
224
|
-
*
|
|
225
|
-
* @example
|
|
226
|
-
* ```ts
|
|
227
|
-
* // ๊ณ ๋ฆฝ๋ ์๋ก๊ฒ์ดํธ๋ฅผ ๊ต์ฒด ๋ฌธ์๋ก ๋์ฒด
|
|
228
|
-
* sanitizeSurrogates("\uD800"); // "๏ฟฝ"
|
|
229
|
-
* ```
|
|
230
|
-
*/
|
|
231
|
-
export function sanitizeSurrogates(
|
|
232
|
-
input: string,
|
|
233
|
-
replacement: string = "\uFFFD"
|
|
234
|
-
): string {
|
|
235
|
-
let result = "";
|
|
236
|
-
|
|
237
|
-
for (let i = 0; i < input.length; i++) {
|
|
238
|
-
const codeUnit = input.charCodeAt(i);
|
|
239
|
-
|
|
240
|
-
if (isHighSurrogate(codeUnit)) {
|
|
241
|
-
// ๋ค์ ๋ฌธ์๊ฐ Low Surrogate์ธ์ง ํ์ธ
|
|
242
|
-
const nextCodeUnit = input.charCodeAt(i + 1);
|
|
243
|
-
if (isLowSurrogate(nextCodeUnit)) {
|
|
244
|
-
// ์ ํจํ ์
|
|
245
|
-
result += input[i] + input[i + 1];
|
|
246
|
-
i++; // ๋ค์ ๋ฌธ์ ๊ฑด๋๋ฐ๊ธฐ
|
|
247
|
-
} else {
|
|
248
|
-
// ๊ณ ๋ฆฝ๋ High Surrogate
|
|
249
|
-
result += replacement;
|
|
250
|
-
}
|
|
251
|
-
} else if (isLowSurrogate(codeUnit)) {
|
|
252
|
-
// ๊ณ ๋ฆฝ๋ Low Surrogate
|
|
253
|
-
result += replacement;
|
|
254
|
-
} else {
|
|
255
|
-
result += input[i];
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return result;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* ๋ฐ์ดํธ ์ ๊ธฐ์ค ํธ๋ ์ผ์ด์
(UTF-8)
|
|
264
|
-
*
|
|
265
|
-
* @example
|
|
266
|
-
* ```ts
|
|
267
|
-
* truncateByBytes("Hello ๐", 8); // "Hello "
|
|
268
|
-
* ```
|
|
269
|
-
*/
|
|
270
|
-
export function truncateByBytes(
|
|
271
|
-
input: string,
|
|
272
|
-
maxBytes: number,
|
|
273
|
-
ellipsis: string = ""
|
|
274
|
-
): string {
|
|
275
|
-
const encoder = new TextEncoder();
|
|
276
|
-
const ellipsisBytes = encoder.encode(ellipsis).length;
|
|
277
|
-
const targetBytes = maxBytes - ellipsisBytes;
|
|
278
|
-
|
|
279
|
-
if (targetBytes <= 0) {
|
|
280
|
-
return ellipsis.slice(0, maxBytes);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const bytes = encoder.encode(input);
|
|
284
|
-
if (bytes.length <= maxBytes) {
|
|
285
|
-
return input;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ๋ฐ์ดํธ ๋จ์๋ก ์๋ฅด๋ฉด์ ์ ํจํ UTF-8 ๊ฒฝ๊ณ ์ฐพ๊ธฐ
|
|
289
|
-
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
290
|
-
let result = decoder.decode(bytes.slice(0, targetBytes));
|
|
291
|
-
|
|
292
|
-
// ๋ง์ง๋ง ๋ฌธ์๊ฐ ๊นจ์ก์ผ๋ฉด ์ ๊ฑฐ
|
|
293
|
-
if (result.endsWith("\uFFFD")) {
|
|
294
|
-
result = result.slice(0, -1);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return result + ellipsis;
|
|
298
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DNA-005: UTF-16 Safe String Utilities
|
|
3
|
+
*
|
|
4
|
+
* ์ด๋ชจ์ง, ํน์ ๋ฌธ์ ๋ฑ ์๋ก๊ฒ์ดํธ ์์ ์์ ํ๊ฒ ์ฒ๋ฆฌ
|
|
5
|
+
* - ๋ฌธ์์ด ์ฌ๋ผ์ด์ฑ ์ ๊นจ์ง ๋ฐฉ์ง
|
|
6
|
+
* - ์๋ฌ ๋ฉ์์ง ํธ๋ ์ผ์ด์
|
|
7
|
+
* - ๋ก๊ทธ ๋ฉ์์ง ์ ํ
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* High Surrogate ๋ฒ์ ์ฒดํฌ (U+D800 ~ U+DBFF)
|
|
12
|
+
*/
|
|
13
|
+
function isHighSurrogate(codeUnit: number): boolean {
|
|
14
|
+
return codeUnit >= 0xd800 && codeUnit <= 0xdbff;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Low Surrogate ๋ฒ์ ์ฒดํฌ (U+DC00 ~ U+DFFF)
|
|
19
|
+
*/
|
|
20
|
+
function isLowSurrogate(codeUnit: number): boolean {
|
|
21
|
+
return codeUnit >= 0xdc00 && codeUnit <= 0xdfff;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* UTF-16 ์์ ๋ฌธ์์ด ์ฌ๋ผ์ด์ฑ
|
|
26
|
+
*
|
|
27
|
+
* ์๋ก๊ฒ์ดํธ ์ ๊ฒฝ๊ณ์์ ์๋ฆฌ์ง ์๋๋ก ๋ณดํธ
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // ์ด๋ชจ์ง ํฌํจ ๋ฌธ์์ด
|
|
32
|
+
* const text = "Hello ๐ World";
|
|
33
|
+
*
|
|
34
|
+
* // ์ผ๋ฐ slice๋ ์ด๋ชจ์ง๋ฅผ ๊นจ๋จ๋ฆด ์ ์์
|
|
35
|
+
* text.slice(0, 7); // "Hello ๏ฟฝ" (๊นจ์ง)
|
|
36
|
+
*
|
|
37
|
+
* // ์์ ํ ์ฌ๋ผ์ด์ฑ
|
|
38
|
+
* sliceUtf16Safe(text, 0, 7); // "Hello " (์ด๋ชจ์ง ์ ์ธ)
|
|
39
|
+
* sliceUtf16Safe(text, 0, 8); // "Hello ๐" (์ด๋ชจ์ง ํฌํจ)
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function sliceUtf16Safe(
|
|
43
|
+
input: string,
|
|
44
|
+
start: number,
|
|
45
|
+
end?: number
|
|
46
|
+
): string {
|
|
47
|
+
const len = input.length;
|
|
48
|
+
let from = Math.max(0, start);
|
|
49
|
+
let to = end === undefined ? len : Math.min(len, end);
|
|
50
|
+
|
|
51
|
+
// ์์ ์์น๊ฐ Low Surrogate๋ฉด ๊ฑด๋๋ฐ๊ธฐ
|
|
52
|
+
if (from > 0 && from < len) {
|
|
53
|
+
const codeUnit = input.charCodeAt(from);
|
|
54
|
+
if (isLowSurrogate(codeUnit) && isHighSurrogate(input.charCodeAt(from - 1))) {
|
|
55
|
+
from += 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ๋ ์์น๊ฐ Low Surrogate๋ฉด ์ ์ธ
|
|
60
|
+
if (to > 0 && to < len) {
|
|
61
|
+
const codeUnit = input.charCodeAt(to);
|
|
62
|
+
if (isLowSurrogate(codeUnit) && isHighSurrogate(input.charCodeAt(to - 1))) {
|
|
63
|
+
to -= 1;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return input.slice(from, to);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* ํธ๋ ์ผ์ด์
์ต์
|
|
72
|
+
*/
|
|
73
|
+
export interface TruncateOptions {
|
|
74
|
+
/** ์ต๋ ๊ธธ์ด (๊ธฐ๋ณธ: 100) */
|
|
75
|
+
maxLength?: number;
|
|
76
|
+
/** ์๋ต ํ์ (๊ธฐ๋ณธ: "...") */
|
|
77
|
+
ellipsis?: string;
|
|
78
|
+
/** ๋จ์ด ๊ฒฝ๊ณ์์ ์๋ฅด๊ธฐ */
|
|
79
|
+
wordBoundary?: boolean;
|
|
80
|
+
/** ํธ๋ ์ผ์ด์
์์น */
|
|
81
|
+
position?: "end" | "middle" | "start";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* ๋ฌธ์์ด ์์ ํธ๋ ์ผ์ด์
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* truncateSafe("Hello World! ๐๐", { maxLength: 12 });
|
|
90
|
+
* // โ "Hello Wor..."
|
|
91
|
+
*
|
|
92
|
+
* truncateSafe("Hello World! ๐๐", { maxLength: 12, wordBoundary: true });
|
|
93
|
+
* // โ "Hello..."
|
|
94
|
+
*
|
|
95
|
+
* truncateSafe("Hello World!", { maxLength: 8, position: "middle" });
|
|
96
|
+
* // โ "Hel...d!"
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export function truncateSafe(
|
|
100
|
+
input: string,
|
|
101
|
+
options: TruncateOptions = {}
|
|
102
|
+
): string {
|
|
103
|
+
const {
|
|
104
|
+
maxLength = 100,
|
|
105
|
+
ellipsis = "...",
|
|
106
|
+
wordBoundary = false,
|
|
107
|
+
position = "end",
|
|
108
|
+
} = options;
|
|
109
|
+
|
|
110
|
+
// ์ด๋ฏธ ์งง์ผ๋ฉด ๊ทธ๋๋ก ๋ฐํ
|
|
111
|
+
if (input.length <= maxLength) {
|
|
112
|
+
return input;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const ellipsisLen = ellipsis.length;
|
|
116
|
+
const availableLen = maxLength - ellipsisLen;
|
|
117
|
+
|
|
118
|
+
if (availableLen <= 0) {
|
|
119
|
+
return ellipsis.slice(0, maxLength);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
switch (position) {
|
|
123
|
+
case "start": {
|
|
124
|
+
const start = input.length - availableLen;
|
|
125
|
+
let result = sliceUtf16Safe(input, start);
|
|
126
|
+
return ellipsis + result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case "middle": {
|
|
130
|
+
const halfLen = Math.floor(availableLen / 2);
|
|
131
|
+
const firstHalf = sliceUtf16Safe(input, 0, halfLen);
|
|
132
|
+
const secondHalf = sliceUtf16Safe(input, input.length - halfLen);
|
|
133
|
+
return firstHalf + ellipsis + secondHalf;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
case "end":
|
|
137
|
+
default: {
|
|
138
|
+
let result = sliceUtf16Safe(input, 0, availableLen);
|
|
139
|
+
|
|
140
|
+
if (wordBoundary) {
|
|
141
|
+
// ๋ง์ง๋ง ๊ณต๋ฐฑ ์ฐพ๊ธฐ
|
|
142
|
+
const lastSpace = result.lastIndexOf(" ");
|
|
143
|
+
if (lastSpace > 0) {
|
|
144
|
+
result = result.slice(0, lastSpace);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result + ellipsis;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* ๋ฌธ์์ด ๊ธธ์ด (์ฝ๋ ํฌ์ธํธ ๊ธฐ์ค)
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```ts
|
|
158
|
+
* "๐".length; // 2 (UTF-16 ์ฝ๋ ์ ๋)
|
|
159
|
+
* lengthInCodePoints("๐"); // 1 (์ฝ๋ ํฌ์ธํธ)
|
|
160
|
+
*
|
|
161
|
+
* "๐จโ๐ฉโ๐งโ๐ฆ".length; // 11
|
|
162
|
+
* lengthInCodePoints("๐จโ๐ฉโ๐งโ๐ฆ"); // 7 (ZWJ ํฌํจ)
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
export function lengthInCodePoints(input: string): number {
|
|
166
|
+
return [...input].length;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* ์ฝ๋ ํฌ์ธํธ ๊ธฐ์ค ์ฌ๋ผ์ด์ฑ
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* const emoji = "๐๐๐";
|
|
175
|
+
* sliceByCodePoints(emoji, 0, 2); // "๐๐"
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
export function sliceByCodePoints(
|
|
179
|
+
input: string,
|
|
180
|
+
start: number,
|
|
181
|
+
end?: number
|
|
182
|
+
): string {
|
|
183
|
+
const codePoints = [...input];
|
|
184
|
+
const sliced = codePoints.slice(start, end);
|
|
185
|
+
return sliced.join("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* ๋ฌธ์์ด์์ ์ด๋ชจ์ง ์ ๊ฑฐ
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* stripEmoji("Hello ๐ World ๐"); // "Hello World "
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function stripEmoji(input: string): string {
|
|
197
|
+
// ์ด๋ชจ์ง ์ ๊ท์ (Unicode ์์ฑ ๊ธฐ๋ฐ)
|
|
198
|
+
return input.replace(
|
|
199
|
+
/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}]/gu,
|
|
200
|
+
""
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* ๋ฌธ์์ด์์ ์๋ก๊ฒ์ดํธ ์ ๊ฒ์ถ
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* hasSurrogates("Hello"); // false
|
|
210
|
+
* hasSurrogates("Hello ๐"); // true
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export function hasSurrogates(input: string): boolean {
|
|
214
|
+
for (let i = 0; i < input.length; i++) {
|
|
215
|
+
if (isHighSurrogate(input.charCodeAt(i))) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* ์๋ชป๋ ์๋ก๊ฒ์ดํธ ์ํ์ค ์์
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* ```ts
|
|
227
|
+
* // ๊ณ ๋ฆฝ๋ ์๋ก๊ฒ์ดํธ๋ฅผ ๊ต์ฒด ๋ฌธ์๋ก ๋์ฒด
|
|
228
|
+
* sanitizeSurrogates("\uD800"); // "๏ฟฝ"
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export function sanitizeSurrogates(
|
|
232
|
+
input: string,
|
|
233
|
+
replacement: string = "\uFFFD"
|
|
234
|
+
): string {
|
|
235
|
+
let result = "";
|
|
236
|
+
|
|
237
|
+
for (let i = 0; i < input.length; i++) {
|
|
238
|
+
const codeUnit = input.charCodeAt(i);
|
|
239
|
+
|
|
240
|
+
if (isHighSurrogate(codeUnit)) {
|
|
241
|
+
// ๋ค์ ๋ฌธ์๊ฐ Low Surrogate์ธ์ง ํ์ธ
|
|
242
|
+
const nextCodeUnit = input.charCodeAt(i + 1);
|
|
243
|
+
if (isLowSurrogate(nextCodeUnit)) {
|
|
244
|
+
// ์ ํจํ ์
|
|
245
|
+
result += input[i] + input[i + 1];
|
|
246
|
+
i++; // ๋ค์ ๋ฌธ์ ๊ฑด๋๋ฐ๊ธฐ
|
|
247
|
+
} else {
|
|
248
|
+
// ๊ณ ๋ฆฝ๋ High Surrogate
|
|
249
|
+
result += replacement;
|
|
250
|
+
}
|
|
251
|
+
} else if (isLowSurrogate(codeUnit)) {
|
|
252
|
+
// ๊ณ ๋ฆฝ๋ Low Surrogate
|
|
253
|
+
result += replacement;
|
|
254
|
+
} else {
|
|
255
|
+
result += input[i];
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* ๋ฐ์ดํธ ์ ๊ธฐ์ค ํธ๋ ์ผ์ด์
(UTF-8)
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* truncateByBytes("Hello ๐", 8); // "Hello "
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
export function truncateByBytes(
|
|
271
|
+
input: string,
|
|
272
|
+
maxBytes: number,
|
|
273
|
+
ellipsis: string = ""
|
|
274
|
+
): string {
|
|
275
|
+
const encoder = new TextEncoder();
|
|
276
|
+
const ellipsisBytes = encoder.encode(ellipsis).length;
|
|
277
|
+
const targetBytes = maxBytes - ellipsisBytes;
|
|
278
|
+
|
|
279
|
+
if (targetBytes <= 0) {
|
|
280
|
+
return ellipsis.slice(0, maxBytes);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const bytes = encoder.encode(input);
|
|
284
|
+
if (bytes.length <= maxBytes) {
|
|
285
|
+
return input;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ๋ฐ์ดํธ ๋จ์๋ก ์๋ฅด๋ฉด์ ์ ํจํ UTF-8 ๊ฒฝ๊ณ ์ฐพ๊ธฐ
|
|
289
|
+
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
290
|
+
let result = decoder.decode(bytes.slice(0, targetBytes));
|
|
291
|
+
|
|
292
|
+
// ๋ง์ง๋ง ๋ฌธ์๊ฐ ๊นจ์ก์ผ๋ฉด ์ ๊ฑฐ
|
|
293
|
+
if (result.endsWith("\uFFFD")) {
|
|
294
|
+
result = result.slice(0, -1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return result + ellipsis;
|
|
298
|
+
}
|
package/src/watcher/rules.ts
CHANGED
|
@@ -21,10 +21,10 @@ import fs from "fs/promises";
|
|
|
21
21
|
*/
|
|
22
22
|
export const MVP_RULES: ArchRule[] = [
|
|
23
23
|
{
|
|
24
|
-
id: "GENERATED_DIRECT_EDIT",
|
|
24
|
+
id: "GENERATED_DIRECT_EDIT",
|
|
25
25
|
name: "Generated Direct Edit",
|
|
26
26
|
description: "Generated ํ์ผ์ ์ง์ ์์ ํ๋ฉด ์ ๋ฉ๋๋ค",
|
|
27
|
-
pattern: "**/generated/**",
|
|
27
|
+
pattern: "**/generated/**",
|
|
28
28
|
action: "warn",
|
|
29
29
|
message: "Generated ํ์ผ์ด ์ง์ ์์ ๋์์ต๋๋ค. ์ด ํ์ผ์ `mandu generate`๋ก ์ฌ์์ฑ๋ฉ๋๋ค.",
|
|
30
30
|
agentAction: "regenerate",
|
|
@@ -64,10 +64,10 @@ export const MVP_RULES: ArchRule[] = [
|
|
|
64
64
|
agentCommand: "mandu_check_location",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
id: "FORBIDDEN_IMPORT",
|
|
67
|
+
id: "FORBIDDEN_IMPORT",
|
|
68
68
|
name: "Forbidden Import in Generated",
|
|
69
69
|
description: "Generated ํ์ผ์์ ๊ธ์ง๋ ๋ชจ๋ import",
|
|
70
|
-
pattern: "**/generated/**",
|
|
70
|
+
pattern: "**/generated/**",
|
|
71
71
|
action: "warn",
|
|
72
72
|
message: "Generated ํ์ผ์์ ๊ธ์ง๋ ๋ชจ๋์ด import๋์์ต๋๋ค.",
|
|
73
73
|
forbiddenImports: ["fs", "child_process", "cluster", "worker_threads"],
|
|
@@ -88,7 +88,7 @@ export const MVP_RULES: ArchRule[] = [
|
|
|
88
88
|
id: "ISLAND_FIRST_MODIFIED",
|
|
89
89
|
name: "Island-First ComponentModule Modified",
|
|
90
90
|
description: "Island-First ๋ฐฉ์์ผ๋ก ์์ฑ๋ componentModule์ด ์๋์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค",
|
|
91
|
-
pattern: "
|
|
91
|
+
pattern: ".mandu/generated/web/routes/**",
|
|
92
92
|
action: "warn",
|
|
93
93
|
message: "Island-First componentModule์ด ์๋ ์์ ๋์์ต๋๋ค. mandu generate๋ฅผ ์คํํ์ธ์.",
|
|
94
94
|
agentAction: "regenerate",
|