sh-ui-cli 0.63.1 → 0.64.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/data/changelog/versions.json +11 -0
- package/data/registry/react/components/input/index.module.tsx +3 -3
- package/data/registry/react/components/input/index.tailwind.tsx +3 -3
- package/data/registry/react/components/input/index.tsx +3 -3
- package/data/registry/react/components/input/index.vanilla-extract.tsx +3 -3
- package/package.json +1 -1
- package/src/create/generator.js +13 -1
- package/src/create/plugins/authJwt.js +12 -0
- package/src/create/plugins/nextIntl.js +28 -1
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$description": "sh-ui 릴리즈 노트 단일 소스. docs(React)와 showcase(Flutter)가 함께 읽는다. 새 릴리즈마다 맨 앞에 추가.",
|
|
4
4
|
"versions": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.64.0",
|
|
7
|
+
"date": "2026-05-08",
|
|
8
|
+
"title": "minor — proxy.ts 에 `HOME_REDIRECT` 자리 + input strict-indexing 회귀 fix",
|
|
9
|
+
"type": "minor",
|
|
10
|
+
"highlights": [
|
|
11
|
+
"**templates proxy.ts 에 `HOME_REDIRECT` 상수 추가** — `/` 진입 시 redirect 할 path 를 사용자가 한 줄로 지정. 빈 문자열이 기본이라 동작 변화 없음(= `app/page.tsx` 그대로 노출). `auth-jwt` 단독 / `next-intl` 단독 / `auth-jwt + next-intl` 합성 세 케이스 모두 동일 패턴. `/[locale]/page.tsx` 에 redirect RSC 를 따로 만들 필요 없이 미들웨어 한 곳에서 라우팅 게이트 처리.",
|
|
12
|
+
"**fix(input)**: `isValidBRN` 의 배열 인덱싱 4곳에 non-null assertion 추가 — `noUncheckedIndexedAccess: true` 인 사용자 프로젝트에서 `parseInt(d[i], 10)` 가 `string | undefined` 로 접혀 typecheck 실패하던 회귀. 직전 길이 가드(`d.length !== 10`) 로 모든 접근이 안전한 구간이라 런타임 동작 변경 없음. plain / tailwind / vanilla-extract / css-modules + `apps/docs` 듀얼카피본 동기화."
|
|
13
|
+
],
|
|
14
|
+
"url": "https://github.com/sanghyeonKim0201/sh-ui/releases/tag/v0.64.0"
|
|
15
|
+
},
|
|
5
16
|
{
|
|
6
17
|
"version": "0.63.1",
|
|
7
18
|
"date": "2026-05-08",
|
|
@@ -352,10 +352,10 @@ export function isValidBRN(digits: string): boolean {
|
|
|
352
352
|
if (d.length !== 10) return false;
|
|
353
353
|
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
354
354
|
let sum = 0;
|
|
355
|
-
for (let i = 0; i < 9; i++) sum += parseInt(d[i]
|
|
356
|
-
sum += Math.floor((parseInt(d[8]
|
|
355
|
+
for (let i = 0; i < 9; i++) sum += parseInt(d[i]!, 10) * w[i]!;
|
|
356
|
+
sum += Math.floor((parseInt(d[8]!, 10) * 5) / 10);
|
|
357
357
|
const check = (10 - (sum % 10)) % 10;
|
|
358
|
-
return check === parseInt(d[9]
|
|
358
|
+
return check === parseInt(d[9]!, 10);
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
export interface BusinessNumberInputProps
|
|
@@ -298,10 +298,10 @@ export function isValidBRN(digits: string): boolean {
|
|
|
298
298
|
if (d.length !== 10) return false;
|
|
299
299
|
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
300
300
|
let sum = 0;
|
|
301
|
-
for (let i = 0; i < 9; i++) sum += parseInt(d[i]
|
|
302
|
-
sum += Math.floor((parseInt(d[8]
|
|
301
|
+
for (let i = 0; i < 9; i++) sum += parseInt(d[i]!, 10) * w[i]!;
|
|
302
|
+
sum += Math.floor((parseInt(d[8]!, 10) * 5) / 10);
|
|
303
303
|
const check = (10 - (sum % 10)) % 10;
|
|
304
|
-
return check === parseInt(d[9]
|
|
304
|
+
return check === parseInt(d[9]!, 10);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
export interface BusinessNumberInputProps
|
|
@@ -377,10 +377,10 @@ export function isValidBRN(digits: string): boolean {
|
|
|
377
377
|
if (d.length !== 10) return false;
|
|
378
378
|
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
379
379
|
let sum = 0;
|
|
380
|
-
for (let i = 0; i < 9; i++) sum += parseInt(d[i]
|
|
381
|
-
sum += Math.floor((parseInt(d[8]
|
|
380
|
+
for (let i = 0; i < 9; i++) sum += parseInt(d[i]!, 10) * w[i]!;
|
|
381
|
+
sum += Math.floor((parseInt(d[8]!, 10) * 5) / 10);
|
|
382
382
|
const check = (10 - (sum % 10)) % 10;
|
|
383
|
-
return check === parseInt(d[9]
|
|
383
|
+
return check === parseInt(d[9]!, 10);
|
|
384
384
|
}
|
|
385
385
|
|
|
386
386
|
export interface BusinessNumberInputProps
|
|
@@ -307,10 +307,10 @@ export function isValidBRN(digits: string): boolean {
|
|
|
307
307
|
if (d.length !== 10) return false;
|
|
308
308
|
const w = [1, 3, 7, 1, 3, 7, 1, 3, 5];
|
|
309
309
|
let sum = 0;
|
|
310
|
-
for (let i = 0; i < 9; i++) sum += parseInt(d[i]
|
|
311
|
-
sum += Math.floor((parseInt(d[8]
|
|
310
|
+
for (let i = 0; i < 9; i++) sum += parseInt(d[i]!, 10) * w[i]!;
|
|
311
|
+
sum += Math.floor((parseInt(d[8]!, 10) * 5) / 10);
|
|
312
312
|
const check = (10 - (sum % 10)) % 10;
|
|
313
|
-
return check === parseInt(d[9]
|
|
313
|
+
return check === parseInt(d[9]!, 10);
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
export interface BusinessNumberInputProps
|
package/package.json
CHANGED
package/src/create/generator.js
CHANGED
|
@@ -1348,6 +1348,13 @@ import { routing } from '${configAlias}/i18n/routing';
|
|
|
1348
1348
|
|
|
1349
1349
|
const AUTH_ROUTES = ['/sign-in', '/sign-up'];
|
|
1350
1350
|
|
|
1351
|
+
/**
|
|
1352
|
+
* 홈(\`/\`, \`/{locale}\`) 진입 시 redirect 할 path. 빈 문자열이면
|
|
1353
|
+
* \`app/[locale]/page.tsx\` 가 그대로 노출. 예: '/dashboard', '/projects'.
|
|
1354
|
+
* 인증 가드 위에서 동작하므로 미인증이면 그대로 \`/sign-in\` 으로 빠진다.
|
|
1355
|
+
*/
|
|
1356
|
+
const HOME_REDIRECT = '';
|
|
1357
|
+
|
|
1351
1358
|
const intl = createIntlMiddleware(routing);
|
|
1352
1359
|
|
|
1353
1360
|
/**
|
|
@@ -1368,7 +1375,8 @@ const stripLocalePrefix = (pathname: string): string => {
|
|
|
1368
1375
|
* Next 16+ proxy.ts (구 middleware.ts).
|
|
1369
1376
|
* next-intl 라우팅 + auth-jwt 토큰 존재 체크 합성 버전.
|
|
1370
1377
|
*
|
|
1371
|
-
* -
|
|
1378
|
+
* - \`/\` + HOME_REDIRECT 설정 → 해당 경로로 리다이렉트 (인증 가드보다 먼저)
|
|
1379
|
+
* - intl 이 로케일 prefix 처리 + NEXT_LOCALE 쿠키 set
|
|
1372
1380
|
* - 그 위에 인증 가드 — 토큰 없고 인증 라우트도 아니면 /sign-in 으로 redirect
|
|
1373
1381
|
* - AT 만료 검사나 refresh 는 하지 않는다 (BFF 가 처리)
|
|
1374
1382
|
*/
|
|
@@ -1378,6 +1386,10 @@ export default function proxy(req: NextRequest) {
|
|
|
1378
1386
|
const hasToken = !!req.cookies.get('accessToken')?.value;
|
|
1379
1387
|
const isAuthRoute = AUTH_ROUTES.some((r) => pathname.startsWith(r));
|
|
1380
1388
|
|
|
1389
|
+
if (pathname === '/' && HOME_REDIRECT) {
|
|
1390
|
+
return NextResponse.redirect(new URL(HOME_REDIRECT, req.url));
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1381
1393
|
if (isAuthRoute) return intlRes;
|
|
1382
1394
|
if (!hasToken) return NextResponse.redirect(new URL('/sign-in', req.url));
|
|
1383
1395
|
|
|
@@ -87,10 +87,18 @@ export default function SignInPage() {
|
|
|
87
87
|
|
|
88
88
|
const AUTH_ROUTES = ['/sign-in', '/sign-up'];
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* 홈(\`/\`) 진입 시 redirect 할 path. 빈 문자열이면 \`app/page.tsx\` 가 그대로 노출.
|
|
92
|
+
* 예: '/dashboard', '/projects'. 인증 가드 위에서 동작하므로 미인증이면
|
|
93
|
+
* 그대로 \`/sign-in\` 으로 빠진다.
|
|
94
|
+
*/
|
|
95
|
+
const HOME_REDIRECT = '';
|
|
96
|
+
|
|
90
97
|
/**
|
|
91
98
|
* Next 16+ 의 proxy.ts (구 middleware.ts).
|
|
92
99
|
* 토큰 존재 여부만 검사한다 — 만료 검사나 refresh 는 하지 않는다.
|
|
93
100
|
*
|
|
101
|
+
* - \`/\` + HOME_REDIRECT 설정 → 해당 경로로 리다이렉트
|
|
94
102
|
* - AT 쿠키 없음 + 인증 라우트 아님 → /sign-in 으로 리다이렉트
|
|
95
103
|
* - AT 쿠키 있음 또는 인증 라우트 → 통과
|
|
96
104
|
*
|
|
@@ -102,6 +110,10 @@ export default function proxy(req: NextRequest) {
|
|
|
102
110
|
const hasToken = !!req.cookies.get('accessToken')?.value;
|
|
103
111
|
const isAuthRoute = AUTH_ROUTES.some((r) => pathname.startsWith(r));
|
|
104
112
|
|
|
113
|
+
if (pathname === '/' && HOME_REDIRECT) {
|
|
114
|
+
return NextResponse.redirect(new URL(HOME_REDIRECT, req.url));
|
|
115
|
+
}
|
|
116
|
+
|
|
105
117
|
if (isAuthRoute) return NextResponse.next();
|
|
106
118
|
if (!hasToken) return NextResponse.redirect(new URL('/sign-in', req.url));
|
|
107
119
|
|
|
@@ -325,11 +325,38 @@ export const { Link, redirect, usePathname, useRouter, getPathname } =
|
|
|
325
325
|
`,
|
|
326
326
|
|
|
327
327
|
'proxy.ts': `import createIntlMiddleware from 'next-intl/middleware';
|
|
328
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
329
|
+
|
|
328
330
|
import { routing } from '${arch.aliases.config}/i18n/routing';
|
|
329
331
|
|
|
332
|
+
/**
|
|
333
|
+
* 홈(\`/\`, \`/{locale}\`) 진입 시 redirect 할 path. 빈 문자열이면
|
|
334
|
+
* \`app/[locale]/page.tsx\` 가 그대로 노출. 예: '/dashboard', '/projects'.
|
|
335
|
+
*/
|
|
336
|
+
const HOME_REDIRECT = '';
|
|
337
|
+
|
|
330
338
|
const intl = createIntlMiddleware(routing);
|
|
331
339
|
|
|
332
|
-
|
|
340
|
+
/**
|
|
341
|
+
* 로케일 prefix (/ko, /en) 를 벗겨 홈 매칭 (\`/\`) 에 사용한다.
|
|
342
|
+
* 예: /ko → /, /ko/posts → /posts.
|
|
343
|
+
*/
|
|
344
|
+
const stripLocalePrefix = (pathname: string): string => {
|
|
345
|
+
const locales = routing.locales as readonly string[];
|
|
346
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
347
|
+
if (segments[0] && locales.includes(segments[0])) {
|
|
348
|
+
const rest = segments.slice(1).join('/');
|
|
349
|
+
return \`/\${rest}\`.replace(/\\/$/, '') || '/';
|
|
350
|
+
}
|
|
351
|
+
return pathname;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default function proxy(req: NextRequest) {
|
|
355
|
+
if (HOME_REDIRECT && stripLocalePrefix(req.nextUrl.pathname) === '/') {
|
|
356
|
+
return NextResponse.redirect(new URL(HOME_REDIRECT, req.url));
|
|
357
|
+
}
|
|
358
|
+
return intl(req);
|
|
359
|
+
}
|
|
333
360
|
|
|
334
361
|
export const config = {
|
|
335
362
|
matcher: '/((?!api|trpc|_next|_vercel|monitoring|.*\\\\..*).*)',
|