@mandujs/core 0.53.3 → 0.54.1
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.md +654 -654
- package/package.json +1 -1
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- 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/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- 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/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- 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 +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- 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/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 +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- 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 +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/filling/sse.ts
CHANGED
|
@@ -45,11 +45,11 @@ export class SSEConnection {
|
|
|
45
45
|
controller.enqueue(this.encoder.encode(chunk));
|
|
46
46
|
}
|
|
47
47
|
this.pending = [];
|
|
48
|
-
},
|
|
49
|
-
cancel: () => {
|
|
50
|
-
void this.close();
|
|
51
|
-
},
|
|
52
|
-
});
|
|
48
|
+
},
|
|
49
|
+
cancel: () => {
|
|
50
|
+
void this.close();
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
53
|
|
|
54
54
|
const headers = new Headers(DEFAULT_SSE_HEADERS);
|
|
55
55
|
if (options.headers) {
|
package/src/filling/ws.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mandu WebSocket Handler
|
|
3
|
-
* filling.ws({ open, message, close }) 패턴
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ========== Types ==========
|
|
7
|
-
|
|
8
|
-
export interface ManduWebSocket {
|
|
9
|
-
/** 고유 연결 ID */
|
|
10
|
-
readonly id: string;
|
|
11
|
-
/** 연결에 첨부된 데이터 */
|
|
12
|
-
readonly data: Record<string, unknown>;
|
|
13
|
-
|
|
14
|
-
/** 메시지 전송 */
|
|
15
|
-
send(data: string | ArrayBuffer | Uint8Array): void;
|
|
16
|
-
/** 토픽 구독 (pub/sub) */
|
|
17
|
-
subscribe(topic: string): void;
|
|
18
|
-
/** 토픽 구독 해제 */
|
|
19
|
-
unsubscribe(topic: string): void;
|
|
20
|
-
/** 토픽에 브로드캐스트 (자신 제외) */
|
|
21
|
-
publish(topic: string, data: string | ArrayBuffer | Uint8Array): void;
|
|
22
|
-
/** 연결 종료 */
|
|
23
|
-
close(code?: number, reason?: string): void;
|
|
24
|
-
/** JSON 전송 헬퍼 */
|
|
25
|
-
sendJSON(data: unknown): void;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface WSHandlers {
|
|
29
|
-
/** 연결 시 */
|
|
30
|
-
open?(ws: ManduWebSocket): void;
|
|
31
|
-
/** 메시지 수신 시 */
|
|
32
|
-
message?(ws: ManduWebSocket, message: string | ArrayBuffer): void;
|
|
33
|
-
/** 연결 종료 시 */
|
|
34
|
-
close?(ws: ManduWebSocket, code: number, reason: string): void;
|
|
35
|
-
/** 백프레셔 해소 시 */
|
|
36
|
-
drain?(ws: ManduWebSocket): void;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface WSUpgradeData {
|
|
40
|
-
routeId: string;
|
|
41
|
-
params: Record<string, string>;
|
|
42
|
-
id: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ========== Implementation ==========
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Bun WebSocket을 ManduWebSocket으로 래핑
|
|
49
|
-
*/
|
|
50
|
-
export function wrapBunWebSocket(
|
|
51
|
-
bunWs: { send: Function; subscribe: Function; unsubscribe: Function; publish: Function; close: Function; data: unknown }
|
|
52
|
-
): ManduWebSocket {
|
|
53
|
-
const wsData = bunWs.data as WSUpgradeData;
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
get id() { return wsData.id; },
|
|
57
|
-
get data() { return wsData as unknown as Record<string, unknown>; },
|
|
58
|
-
|
|
59
|
-
send(data: string | ArrayBuffer | Uint8Array) {
|
|
60
|
-
bunWs.send(data);
|
|
61
|
-
},
|
|
62
|
-
subscribe(topic: string) {
|
|
63
|
-
bunWs.subscribe(topic);
|
|
64
|
-
},
|
|
65
|
-
unsubscribe(topic: string) {
|
|
66
|
-
bunWs.unsubscribe(topic);
|
|
67
|
-
},
|
|
68
|
-
publish(topic: string, data: string | ArrayBuffer | Uint8Array) {
|
|
69
|
-
bunWs.publish(topic, data);
|
|
70
|
-
},
|
|
71
|
-
close(code?: number, reason?: string) {
|
|
72
|
-
bunWs.close(code, reason);
|
|
73
|
-
},
|
|
74
|
-
sendJSON(data: unknown) {
|
|
75
|
-
bunWs.send(JSON.stringify(data));
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Mandu WebSocket Handler
|
|
3
|
+
* filling.ws({ open, message, close }) 패턴
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ========== Types ==========
|
|
7
|
+
|
|
8
|
+
export interface ManduWebSocket {
|
|
9
|
+
/** 고유 연결 ID */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** 연결에 첨부된 데이터 */
|
|
12
|
+
readonly data: Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
/** 메시지 전송 */
|
|
15
|
+
send(data: string | ArrayBuffer | Uint8Array): void;
|
|
16
|
+
/** 토픽 구독 (pub/sub) */
|
|
17
|
+
subscribe(topic: string): void;
|
|
18
|
+
/** 토픽 구독 해제 */
|
|
19
|
+
unsubscribe(topic: string): void;
|
|
20
|
+
/** 토픽에 브로드캐스트 (자신 제외) */
|
|
21
|
+
publish(topic: string, data: string | ArrayBuffer | Uint8Array): void;
|
|
22
|
+
/** 연결 종료 */
|
|
23
|
+
close(code?: number, reason?: string): void;
|
|
24
|
+
/** JSON 전송 헬퍼 */
|
|
25
|
+
sendJSON(data: unknown): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface WSHandlers {
|
|
29
|
+
/** 연결 시 */
|
|
30
|
+
open?(ws: ManduWebSocket): void;
|
|
31
|
+
/** 메시지 수신 시 */
|
|
32
|
+
message?(ws: ManduWebSocket, message: string | ArrayBuffer): void;
|
|
33
|
+
/** 연결 종료 시 */
|
|
34
|
+
close?(ws: ManduWebSocket, code: number, reason: string): void;
|
|
35
|
+
/** 백프레셔 해소 시 */
|
|
36
|
+
drain?(ws: ManduWebSocket): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WSUpgradeData {
|
|
40
|
+
routeId: string;
|
|
41
|
+
params: Record<string, string>;
|
|
42
|
+
id: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ========== Implementation ==========
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Bun WebSocket을 ManduWebSocket으로 래핑
|
|
49
|
+
*/
|
|
50
|
+
export function wrapBunWebSocket(
|
|
51
|
+
bunWs: { send: Function; subscribe: Function; unsubscribe: Function; publish: Function; close: Function; data: unknown }
|
|
52
|
+
): ManduWebSocket {
|
|
53
|
+
const wsData = bunWs.data as WSUpgradeData;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
get id() { return wsData.id; },
|
|
57
|
+
get data() { return wsData as unknown as Record<string, unknown>; },
|
|
58
|
+
|
|
59
|
+
send(data: string | ArrayBuffer | Uint8Array) {
|
|
60
|
+
bunWs.send(data);
|
|
61
|
+
},
|
|
62
|
+
subscribe(topic: string) {
|
|
63
|
+
bunWs.subscribe(topic);
|
|
64
|
+
},
|
|
65
|
+
unsubscribe(topic: string) {
|
|
66
|
+
bunWs.unsubscribe(topic);
|
|
67
|
+
},
|
|
68
|
+
publish(topic: string, data: string | ArrayBuffer | Uint8Array) {
|
|
69
|
+
bunWs.publish(topic, data);
|
|
70
|
+
},
|
|
71
|
+
close(code?: number, reason?: string) {
|
|
72
|
+
bunWs.close(code, reason);
|
|
73
|
+
},
|
|
74
|
+
sendJSON(data: unknown) {
|
|
75
|
+
bunWs.send(JSON.stringify(data));
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
package/src/generator/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from "./generate";
|
|
2
|
-
export * from "./templates";
|
|
3
|
-
export * from "./contract-glue";
|
|
1
|
+
export * from "./generate";
|
|
2
|
+
export * from "./templates";
|
|
3
|
+
export * from "./contract-glue";
|
|
@@ -1,219 +1,219 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Guard `DESIGN_INLINE_CLASS` rule tests (Issue #245 M2).
|
|
3
|
-
*
|
|
4
|
-
* Cover the three forbid-list sources (explicit / DESIGN.md auto /
|
|
5
|
-
* combined), the exclude paths (canonical component dirs), and the
|
|
6
|
-
* regex matcher's variant-prefix handling.
|
|
7
|
-
*/
|
|
8
|
-
import { describe, it, expect } from "bun:test";
|
|
9
|
-
import fs from "node:fs/promises";
|
|
10
|
-
import os from "node:os";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
|
|
13
|
-
import { checkDesignInlineClasses } from "../design-inline-class";
|
|
14
|
-
|
|
15
|
-
async function makeRoot(prefix: string): Promise<string> {
|
|
16
|
-
return fs.mkdtemp(path.join(os.tmpdir(), `mandu-design-guard-${prefix}-`));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function writeFile(root: string, rel: string, content: string): Promise<void> {
|
|
20
|
-
const full = path.join(root, rel);
|
|
21
|
-
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
22
|
-
await fs.writeFile(full, content);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
describe("checkDesignInlineClasses", () => {
|
|
26
|
-
it("returns no violations when guard.design is undefined", async () => {
|
|
27
|
-
const root = await makeRoot("undef");
|
|
28
|
-
await writeFile(
|
|
29
|
-
root,
|
|
30
|
-
"src/client/page.tsx",
|
|
31
|
-
`export default () => <div className="btn-hard">x</div>;\n`,
|
|
32
|
-
);
|
|
33
|
-
const out = await checkDesignInlineClasses(root, undefined);
|
|
34
|
-
expect(out).toHaveLength(0);
|
|
35
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("flags an inline forbidden class with line number", async () => {
|
|
39
|
-
const root = await makeRoot("flag");
|
|
40
|
-
await writeFile(
|
|
41
|
-
root,
|
|
42
|
-
"src/client/page.tsx",
|
|
43
|
-
`import * as React from 'react';
|
|
44
|
-
|
|
45
|
-
export default () => (
|
|
46
|
-
<div className="px-4 py-2 btn-hard rounded-md">x</div>
|
|
47
|
-
);
|
|
48
|
-
`,
|
|
49
|
-
);
|
|
50
|
-
const out = await checkDesignInlineClasses(root, {
|
|
51
|
-
forbidInlineClasses: ["btn-hard"],
|
|
52
|
-
});
|
|
53
|
-
expect(out).toHaveLength(1);
|
|
54
|
-
expect(out[0]?.ruleId).toBe("DESIGN_INLINE_CLASS");
|
|
55
|
-
expect(out[0]?.file.replace(/\\/g, "/")).toBe("src/client/page.tsx");
|
|
56
|
-
expect(out[0]?.line).toBe(4);
|
|
57
|
-
expect(out[0]?.message).toContain("btn-hard");
|
|
58
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("surfaces the replacement component in the message + suggestion", async () => {
|
|
62
|
-
const root = await makeRoot("hint");
|
|
63
|
-
await writeFile(
|
|
64
|
-
root,
|
|
65
|
-
"src/client/page.tsx",
|
|
66
|
-
`<div className="btn-hard">x</div>`,
|
|
67
|
-
);
|
|
68
|
-
const out = await checkDesignInlineClasses(root, {
|
|
69
|
-
forbidInlineClasses: ["btn-hard"],
|
|
70
|
-
requireComponent: { "btn-hard": "@/client/shared/ui#MButton" },
|
|
71
|
-
});
|
|
72
|
-
expect(out[0]?.message).toContain("@/client/shared/ui#MButton");
|
|
73
|
-
expect(out[0]?.suggestion).toContain("@/client/shared/ui#MButton");
|
|
74
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("excludes the canonical component dirs by default", async () => {
|
|
78
|
-
const root = await makeRoot("exclude-default");
|
|
79
|
-
// The component definition itself uses btn-hard — that's where it
|
|
80
|
-
// legitimately lives. Default exclude must skip this file.
|
|
81
|
-
await writeFile(
|
|
82
|
-
root,
|
|
83
|
-
"src/client/shared/ui/m-button.tsx",
|
|
84
|
-
`<button className="btn-hard">x</button>`,
|
|
85
|
-
);
|
|
86
|
-
// A page that imports it must still be flagged.
|
|
87
|
-
await writeFile(
|
|
88
|
-
root,
|
|
89
|
-
"src/client/pages/home.tsx",
|
|
90
|
-
`<div className="btn-hard">x</div>`,
|
|
91
|
-
);
|
|
92
|
-
const out = await checkDesignInlineClasses(root, {
|
|
93
|
-
forbidInlineClasses: ["btn-hard"],
|
|
94
|
-
});
|
|
95
|
-
const files = out.map((v) => v.file.replace(/\\/g, "/"));
|
|
96
|
-
expect(files).toContain("src/client/pages/home.tsx");
|
|
97
|
-
expect(files).not.toContain("src/client/shared/ui/m-button.tsx");
|
|
98
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("strips Tailwind variant prefixes (hover:btn-hard matches btn-hard)", async () => {
|
|
102
|
-
const root = await makeRoot("variant");
|
|
103
|
-
await writeFile(
|
|
104
|
-
root,
|
|
105
|
-
"src/client/page.tsx",
|
|
106
|
-
`<div className="hover:btn-hard focus:btn-hard">x</div>`,
|
|
107
|
-
);
|
|
108
|
-
const out = await checkDesignInlineClasses(root, {
|
|
109
|
-
forbidInlineClasses: ["btn-hard"],
|
|
110
|
-
});
|
|
111
|
-
expect(out.length).toBeGreaterThanOrEqual(1);
|
|
112
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("auto-extracts forbid tokens from DESIGN.md §7 Don't section", async () => {
|
|
116
|
-
const root = await makeRoot("auto-design");
|
|
117
|
-
await writeFile(
|
|
118
|
-
root,
|
|
119
|
-
"DESIGN.md",
|
|
120
|
-
`# X
|
|
121
|
-
|
|
122
|
-
## Do's & Don'ts
|
|
123
|
-
|
|
124
|
-
### Do
|
|
125
|
-
- Use the \`MButton\` component.
|
|
126
|
-
|
|
127
|
-
### Don't
|
|
128
|
-
- Inline \`btn-hard\` directly in pages.
|
|
129
|
-
- Don't use \`shadow-hard\` outside ui/.
|
|
130
|
-
`,
|
|
131
|
-
);
|
|
132
|
-
await writeFile(
|
|
133
|
-
root,
|
|
134
|
-
"src/client/page.tsx",
|
|
135
|
-
`<div className="btn-hard shadow-hard">x</div>`,
|
|
136
|
-
);
|
|
137
|
-
const out = await checkDesignInlineClasses(root, {
|
|
138
|
-
autoFromDesignMd: true,
|
|
139
|
-
});
|
|
140
|
-
const tokens = out.map((v) => v.message.match(/"([\w-]+)"/)?.[1]);
|
|
141
|
-
expect(tokens).toContain("btn-hard");
|
|
142
|
-
expect(tokens).toContain("shadow-hard");
|
|
143
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("merges explicit forbid + DESIGN.md auto", async () => {
|
|
147
|
-
const root = await makeRoot("merge");
|
|
148
|
-
await writeFile(
|
|
149
|
-
root,
|
|
150
|
-
"DESIGN.md",
|
|
151
|
-
`## Do's & Don'ts
|
|
152
|
-
|
|
153
|
-
### Don't
|
|
154
|
-
- Avoid \`shadow-hard\`.
|
|
155
|
-
`,
|
|
156
|
-
);
|
|
157
|
-
await writeFile(
|
|
158
|
-
root,
|
|
159
|
-
"src/client/page.tsx",
|
|
160
|
-
`<div className="btn-hard shadow-hard">x</div>`,
|
|
161
|
-
);
|
|
162
|
-
const out = await checkDesignInlineClasses(root, {
|
|
163
|
-
forbidInlineClasses: ["btn-hard"],
|
|
164
|
-
autoFromDesignMd: true,
|
|
165
|
-
});
|
|
166
|
-
const tokens = out.map((v) => v.message.match(/"([\w-]+)"/)?.[1]);
|
|
167
|
-
expect(tokens).toContain("btn-hard");
|
|
168
|
-
expect(tokens).toContain("shadow-hard");
|
|
169
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("scans both src/ and app/ when present", async () => {
|
|
173
|
-
const root = await makeRoot("dual-roots");
|
|
174
|
-
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
175
|
-
await writeFile(root, "app/y.tsx", `<div className="btn-hard">y</div>`);
|
|
176
|
-
const out = await checkDesignInlineClasses(root, {
|
|
177
|
-
forbidInlineClasses: ["btn-hard"],
|
|
178
|
-
});
|
|
179
|
-
const files = out.map((v) => v.file.replace(/\\/g, "/")).sort();
|
|
180
|
-
expect(files).toEqual(["app/y.tsx", "src/x.tsx"]);
|
|
181
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("respects custom exclude patterns", async () => {
|
|
185
|
-
const root = await makeRoot("custom-exclude");
|
|
186
|
-
await writeFile(root, "src/legacy/old-page.tsx", `<div className="btn-hard">x</div>`);
|
|
187
|
-
const out = await checkDesignInlineClasses(root, {
|
|
188
|
-
forbidInlineClasses: ["btn-hard"],
|
|
189
|
-
exclude: ["src/legacy/**"],
|
|
190
|
-
});
|
|
191
|
-
expect(out).toHaveLength(0);
|
|
192
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("honours severity setting on emitted violations", async () => {
|
|
196
|
-
const root = await makeRoot("severity");
|
|
197
|
-
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
198
|
-
const errors = await checkDesignInlineClasses(root, {
|
|
199
|
-
forbidInlineClasses: ["btn-hard"],
|
|
200
|
-
severity: "error",
|
|
201
|
-
});
|
|
202
|
-
expect(errors[0]?.severity).toBe("error");
|
|
203
|
-
const warnings = await checkDesignInlineClasses(root, {
|
|
204
|
-
forbidInlineClasses: ["btn-hard"],
|
|
205
|
-
severity: "warning",
|
|
206
|
-
});
|
|
207
|
-
expect(warnings[0]?.severity).toBe("warning");
|
|
208
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("returns nothing when forbid list resolves to empty", async () => {
|
|
212
|
-
const root = await makeRoot("empty-forbid");
|
|
213
|
-
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
214
|
-
// No explicit list, autoFromDesignMd false → nothing to enforce.
|
|
215
|
-
const out = await checkDesignInlineClasses(root, {});
|
|
216
|
-
expect(out).toHaveLength(0);
|
|
217
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
218
|
-
});
|
|
219
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Guard `DESIGN_INLINE_CLASS` rule tests (Issue #245 M2).
|
|
3
|
+
*
|
|
4
|
+
* Cover the three forbid-list sources (explicit / DESIGN.md auto /
|
|
5
|
+
* combined), the exclude paths (canonical component dirs), and the
|
|
6
|
+
* regex matcher's variant-prefix handling.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from "bun:test";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import { checkDesignInlineClasses } from "../design-inline-class";
|
|
14
|
+
|
|
15
|
+
async function makeRoot(prefix: string): Promise<string> {
|
|
16
|
+
return fs.mkdtemp(path.join(os.tmpdir(), `mandu-design-guard-${prefix}-`));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function writeFile(root: string, rel: string, content: string): Promise<void> {
|
|
20
|
+
const full = path.join(root, rel);
|
|
21
|
+
await fs.mkdir(path.dirname(full), { recursive: true });
|
|
22
|
+
await fs.writeFile(full, content);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("checkDesignInlineClasses", () => {
|
|
26
|
+
it("returns no violations when guard.design is undefined", async () => {
|
|
27
|
+
const root = await makeRoot("undef");
|
|
28
|
+
await writeFile(
|
|
29
|
+
root,
|
|
30
|
+
"src/client/page.tsx",
|
|
31
|
+
`export default () => <div className="btn-hard">x</div>;\n`,
|
|
32
|
+
);
|
|
33
|
+
const out = await checkDesignInlineClasses(root, undefined);
|
|
34
|
+
expect(out).toHaveLength(0);
|
|
35
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("flags an inline forbidden class with line number", async () => {
|
|
39
|
+
const root = await makeRoot("flag");
|
|
40
|
+
await writeFile(
|
|
41
|
+
root,
|
|
42
|
+
"src/client/page.tsx",
|
|
43
|
+
`import * as React from 'react';
|
|
44
|
+
|
|
45
|
+
export default () => (
|
|
46
|
+
<div className="px-4 py-2 btn-hard rounded-md">x</div>
|
|
47
|
+
);
|
|
48
|
+
`,
|
|
49
|
+
);
|
|
50
|
+
const out = await checkDesignInlineClasses(root, {
|
|
51
|
+
forbidInlineClasses: ["btn-hard"],
|
|
52
|
+
});
|
|
53
|
+
expect(out).toHaveLength(1);
|
|
54
|
+
expect(out[0]?.ruleId).toBe("DESIGN_INLINE_CLASS");
|
|
55
|
+
expect(out[0]?.file.replace(/\\/g, "/")).toBe("src/client/page.tsx");
|
|
56
|
+
expect(out[0]?.line).toBe(4);
|
|
57
|
+
expect(out[0]?.message).toContain("btn-hard");
|
|
58
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("surfaces the replacement component in the message + suggestion", async () => {
|
|
62
|
+
const root = await makeRoot("hint");
|
|
63
|
+
await writeFile(
|
|
64
|
+
root,
|
|
65
|
+
"src/client/page.tsx",
|
|
66
|
+
`<div className="btn-hard">x</div>`,
|
|
67
|
+
);
|
|
68
|
+
const out = await checkDesignInlineClasses(root, {
|
|
69
|
+
forbidInlineClasses: ["btn-hard"],
|
|
70
|
+
requireComponent: { "btn-hard": "@/client/shared/ui#MButton" },
|
|
71
|
+
});
|
|
72
|
+
expect(out[0]?.message).toContain("@/client/shared/ui#MButton");
|
|
73
|
+
expect(out[0]?.suggestion).toContain("@/client/shared/ui#MButton");
|
|
74
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("excludes the canonical component dirs by default", async () => {
|
|
78
|
+
const root = await makeRoot("exclude-default");
|
|
79
|
+
// The component definition itself uses btn-hard — that's where it
|
|
80
|
+
// legitimately lives. Default exclude must skip this file.
|
|
81
|
+
await writeFile(
|
|
82
|
+
root,
|
|
83
|
+
"src/client/shared/ui/m-button.tsx",
|
|
84
|
+
`<button className="btn-hard">x</button>`,
|
|
85
|
+
);
|
|
86
|
+
// A page that imports it must still be flagged.
|
|
87
|
+
await writeFile(
|
|
88
|
+
root,
|
|
89
|
+
"src/client/pages/home.tsx",
|
|
90
|
+
`<div className="btn-hard">x</div>`,
|
|
91
|
+
);
|
|
92
|
+
const out = await checkDesignInlineClasses(root, {
|
|
93
|
+
forbidInlineClasses: ["btn-hard"],
|
|
94
|
+
});
|
|
95
|
+
const files = out.map((v) => v.file.replace(/\\/g, "/"));
|
|
96
|
+
expect(files).toContain("src/client/pages/home.tsx");
|
|
97
|
+
expect(files).not.toContain("src/client/shared/ui/m-button.tsx");
|
|
98
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("strips Tailwind variant prefixes (hover:btn-hard matches btn-hard)", async () => {
|
|
102
|
+
const root = await makeRoot("variant");
|
|
103
|
+
await writeFile(
|
|
104
|
+
root,
|
|
105
|
+
"src/client/page.tsx",
|
|
106
|
+
`<div className="hover:btn-hard focus:btn-hard">x</div>`,
|
|
107
|
+
);
|
|
108
|
+
const out = await checkDesignInlineClasses(root, {
|
|
109
|
+
forbidInlineClasses: ["btn-hard"],
|
|
110
|
+
});
|
|
111
|
+
expect(out.length).toBeGreaterThanOrEqual(1);
|
|
112
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("auto-extracts forbid tokens from DESIGN.md §7 Don't section", async () => {
|
|
116
|
+
const root = await makeRoot("auto-design");
|
|
117
|
+
await writeFile(
|
|
118
|
+
root,
|
|
119
|
+
"DESIGN.md",
|
|
120
|
+
`# X
|
|
121
|
+
|
|
122
|
+
## Do's & Don'ts
|
|
123
|
+
|
|
124
|
+
### Do
|
|
125
|
+
- Use the \`MButton\` component.
|
|
126
|
+
|
|
127
|
+
### Don't
|
|
128
|
+
- Inline \`btn-hard\` directly in pages.
|
|
129
|
+
- Don't use \`shadow-hard\` outside ui/.
|
|
130
|
+
`,
|
|
131
|
+
);
|
|
132
|
+
await writeFile(
|
|
133
|
+
root,
|
|
134
|
+
"src/client/page.tsx",
|
|
135
|
+
`<div className="btn-hard shadow-hard">x</div>`,
|
|
136
|
+
);
|
|
137
|
+
const out = await checkDesignInlineClasses(root, {
|
|
138
|
+
autoFromDesignMd: true,
|
|
139
|
+
});
|
|
140
|
+
const tokens = out.map((v) => v.message.match(/"([\w-]+)"/)?.[1]);
|
|
141
|
+
expect(tokens).toContain("btn-hard");
|
|
142
|
+
expect(tokens).toContain("shadow-hard");
|
|
143
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("merges explicit forbid + DESIGN.md auto", async () => {
|
|
147
|
+
const root = await makeRoot("merge");
|
|
148
|
+
await writeFile(
|
|
149
|
+
root,
|
|
150
|
+
"DESIGN.md",
|
|
151
|
+
`## Do's & Don'ts
|
|
152
|
+
|
|
153
|
+
### Don't
|
|
154
|
+
- Avoid \`shadow-hard\`.
|
|
155
|
+
`,
|
|
156
|
+
);
|
|
157
|
+
await writeFile(
|
|
158
|
+
root,
|
|
159
|
+
"src/client/page.tsx",
|
|
160
|
+
`<div className="btn-hard shadow-hard">x</div>`,
|
|
161
|
+
);
|
|
162
|
+
const out = await checkDesignInlineClasses(root, {
|
|
163
|
+
forbidInlineClasses: ["btn-hard"],
|
|
164
|
+
autoFromDesignMd: true,
|
|
165
|
+
});
|
|
166
|
+
const tokens = out.map((v) => v.message.match(/"([\w-]+)"/)?.[1]);
|
|
167
|
+
expect(tokens).toContain("btn-hard");
|
|
168
|
+
expect(tokens).toContain("shadow-hard");
|
|
169
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("scans both src/ and app/ when present", async () => {
|
|
173
|
+
const root = await makeRoot("dual-roots");
|
|
174
|
+
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
175
|
+
await writeFile(root, "app/y.tsx", `<div className="btn-hard">y</div>`);
|
|
176
|
+
const out = await checkDesignInlineClasses(root, {
|
|
177
|
+
forbidInlineClasses: ["btn-hard"],
|
|
178
|
+
});
|
|
179
|
+
const files = out.map((v) => v.file.replace(/\\/g, "/")).sort();
|
|
180
|
+
expect(files).toEqual(["app/y.tsx", "src/x.tsx"]);
|
|
181
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("respects custom exclude patterns", async () => {
|
|
185
|
+
const root = await makeRoot("custom-exclude");
|
|
186
|
+
await writeFile(root, "src/legacy/old-page.tsx", `<div className="btn-hard">x</div>`);
|
|
187
|
+
const out = await checkDesignInlineClasses(root, {
|
|
188
|
+
forbidInlineClasses: ["btn-hard"],
|
|
189
|
+
exclude: ["src/legacy/**"],
|
|
190
|
+
});
|
|
191
|
+
expect(out).toHaveLength(0);
|
|
192
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("honours severity setting on emitted violations", async () => {
|
|
196
|
+
const root = await makeRoot("severity");
|
|
197
|
+
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
198
|
+
const errors = await checkDesignInlineClasses(root, {
|
|
199
|
+
forbidInlineClasses: ["btn-hard"],
|
|
200
|
+
severity: "error",
|
|
201
|
+
});
|
|
202
|
+
expect(errors[0]?.severity).toBe("error");
|
|
203
|
+
const warnings = await checkDesignInlineClasses(root, {
|
|
204
|
+
forbidInlineClasses: ["btn-hard"],
|
|
205
|
+
severity: "warning",
|
|
206
|
+
});
|
|
207
|
+
expect(warnings[0]?.severity).toBe("warning");
|
|
208
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("returns nothing when forbid list resolves to empty", async () => {
|
|
212
|
+
const root = await makeRoot("empty-forbid");
|
|
213
|
+
await writeFile(root, "src/x.tsx", `<div className="btn-hard">x</div>`);
|
|
214
|
+
// No explicit list, autoFromDesignMd false → nothing to enforce.
|
|
215
|
+
const out = await checkDesignInlineClasses(root, {});
|
|
216
|
+
expect(out).toHaveLength(0);
|
|
217
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
218
|
+
});
|
|
219
|
+
});
|