Merge chore/remove-legacy-launchers: remove the legacy Windows launcher, PORTABLE records and ZIP packaging scripts
This commit is contained in:
commit
bcec066bda
14 files changed
+10
-523
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
# Track B temporary Storybook LAN preview
|
# Track B temporary Storybook LAN preview
|
||||||
|
|
||||||
> Historical scope: the Track B session below was verified on 2026-10-07. Ordinary users should follow the [official source installation guide](../../docs/guidelines/publisher-distribution-guide.md) and use `npm run storybook` or `start-storybook.bat` at the repository root; the current default port is **6024**. LAN Preview is optional internal QA. The old 6007 package port and the temporary 6008 session are historical observations. No server binding, network profile or firewall configuration is changed by this documentation update.
|
> Historical scope: the Track B session below was verified on 2026-10-07. Ordinary users should follow the [official source installation guide](../../docs/guidelines/publisher-distribution-guide.md) and use the web Storybook or `npm run storybook` at the repository root; the current default port is **6024**. LAN Preview is optional internal QA. The old 6007 package port and the temporary 6008 session are historical observations. No server binding, network profile or firewall configuration is changed by this documentation update.
|
||||||
|
|
||||||
Purpose: **TEMPORARY INTERNAL LAN PREVIEW / LAN TEMP PREVIEW** for Storybook cleanup. This is a development server with HMR, not official static hosting or a permanent URL.
|
Purpose: **TEMPORARY INTERNAL LAN PREVIEW / LAN TEMP PREVIEW** for Storybook cleanup. This is a development server with HMR, not official static hosting or a permanent URL.
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
# HMDesignTest Portable r4 Verification
|
|
||||||
|
|
||||||
- Candidate: `HMDesignTest-Portable-Test-r4-final-2026-10-08.zip`
|
|
||||||
- 기준: `HMDesignTest-Portable-Test-r3.zip` 보존, r3 구조를 staging 기준으로 재사용
|
|
||||||
- Storybook 정적 빌드: PASS (`npm run build-storybook`)
|
|
||||||
- Dashboard Production export build: PASS (`npm run build:dashboard-export`)
|
|
||||||
- Dashboard 엔트리: PASS — `src/exports/dashboard/main.tsx`가 `DashboardScreen`을 사용하며 Storybook 모듈을 import하지 않음
|
|
||||||
- Portable HTTP server: PASS — 내장 Node HTTP 서버와 `/storybook-static` 정적 경로 확인
|
|
||||||
- Dashboard 별도 경로: NOT RUN — 현재 포터블 서버는 Dashboard export를 별도 URL로 서비스하지 않으며, 실행기 변경은 범위 밖
|
|
||||||
- Storybook 대표 Dashboard: PASS 대상 — `/` 및 Storybook story route로 제공
|
|
||||||
- 브라우저 실제 확인: NOT RUN (현재 환경에서 포터블 브라우저 검증을 수행하지 않음)
|
|
||||||
- H-2: Storybook과 분리된 `scripts/h2-preview.mjs` 및 관련 소스 유지
|
|
||||||
- 기존 r3 ZIP: 보존 확인
|
|
||||||
|
|
||||||
경고: Storybook 빌드에서 기존 CSS arbitrary-value 경고와 chunk-size 경고가 출력되었으나 빌드는 성공했다.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# HMDesignTest Portable r5 candidate verification
|
|
||||||
|
|
||||||
## Result
|
|
||||||
|
|
||||||
PORTABLE EXECUTION RESULT: PARTIAL
|
|
||||||
|
|
||||||
The portable server starts with the bundled Node runtime and prints separate Storybook and Dashboard URLs. The r5 candidate routes `/` to `storybook-static/` and `/dashboard/` to `dist-dashboard/` without changing UI source, Theme, Component, Pattern, or Storybook files.
|
|
||||||
|
|
||||||
## Candidate artifact
|
|
||||||
|
|
||||||
- Path: `HMDesignTest-Portable-Test-r5-candidate-2026-10-08.zip`
|
|
||||||
- Size: 108,362,960 bytes
|
|
||||||
- SHA-256: `2CD5BE30C6B6D7CCAC21D1081E9A0152CDA402D3D2F9218CCBCA6E253A0B9A38`
|
|
||||||
- ZIP entries: 2,065
|
|
||||||
- Duplicate archive paths: none
|
|
||||||
|
|
||||||
## Portable execution
|
|
||||||
|
|
||||||
- Bundled runtime: present at `HMDesignTest/runtime/node/node.exe`
|
|
||||||
- Server startup: PASS; bundled Node v22.23.3 started the candidate server.
|
|
||||||
- Example selected port during verification: 6009.
|
|
||||||
- Printed addresses: `http://127.0.0.1:6009/` and `http://127.0.0.1:6009/dashboard/`.
|
|
||||||
- Port collision fallback: retained; server increments from 6007 through 6023 and then fails cleanly if all are unavailable.
|
|
||||||
- Binding: retained at `127.0.0.1`.
|
|
||||||
- Path traversal and backslash rejection: retained.
|
|
||||||
|
|
||||||
## Static checks
|
|
||||||
|
|
||||||
- Storybook entry present: `HMDesignTest/storybook-static/index.html`
|
|
||||||
- Dashboard entry present: `HMDesignTest/dist-dashboard/index.html`
|
|
||||||
- Dashboard JS present: `HMDesignTest/dist-dashboard/assets/dashboard-export.js`
|
|
||||||
- Dashboard CSS present: `HMDesignTest/dist-dashboard/assets/style-BJ7gb3jH.css`
|
|
||||||
- Dashboard Noto Sans KR font present: `HMDesignTest/dist-dashboard/assets/NotoSansKR-variable-k7NQXPol.ttf`
|
|
||||||
- Dashboard source is the production export; no Storybook story import was added.
|
|
||||||
- r3/r4 preserved: PASS; no existing archive was overwritten.
|
|
||||||
|
|
||||||
## Browser verification
|
|
||||||
|
|
||||||
NOT RUN for the r5 candidate. The available browser could not connect to the locally spawned candidate server (`ERR_CONNECTION_TIMED_OUT`), and the shell HTTP client was blocked from accessing the local test socket by the execution sandbox. Therefore HTTP 200, rendered production Dashboard, filter/search/reset interaction, and console-error checks are not claimed as PASS.
|
|
||||||
|
|
||||||
## Inventory comparison
|
|
||||||
|
|
||||||
| Archive | Bytes | Entries | SHA-256 |
|
|
||||||
|---|---:|---:|---|
|
|
||||||
| r3 | 59,256,057 | 1,214 | `DA1F6DAD261D3F780BEE1E70F734F2464C5E6648A8BE94A75720B6F3D8F8FF08` |
|
|
||||||
| r4 | 108,362,799 | 2,065 | `3ED64BAD1E9BF062CC168B6F253C6905244F3645F75EE70CFA7FAAB63C9A1F31` |
|
|
||||||
| r5 candidate | 108,362,960 | 2,065 | `2CD5BE30C6B6D7CCAC21D1081E9A0152CDA402D3D2F9218CCBCA6E253A0B9A38` |
|
|
||||||
|
|
||||||
The r5 delta from r4 is limited to the launcher/server patch and ZIP metadata; no duplicate archive paths were found.
|
|
||||||
|
|
||||||
## User test status
|
|
||||||
|
|
||||||
Not yet fully user-testable as a verified release because browser-level HTTP/rendering and interaction checks remain NOT RUN. The candidate is suitable for a manual local test once the ZIP is extracted on a machine where localhost browser access is permitted.
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# HMDesignTest Portable r5 Verification
|
|
||||||
|
|
||||||
## Result
|
|
||||||
|
|
||||||
PORTABLE R5 AUTO OPEN RESULT: PARTIAL
|
|
||||||
|
|
||||||
## Artifact
|
|
||||||
|
|
||||||
- Path: `HMDesignTest-Portable-Test-r5-2026-10-08.zip`
|
|
||||||
- Size: `108,382,038` bytes
|
|
||||||
- SHA-256: `547FB98292406D5BF3FC8B8EF4FA229196115FB1C3111358B5F56EE85D6FC234`
|
|
||||||
- ZIP entries: `2,062`
|
|
||||||
- Inventory file count: `1,946`
|
|
||||||
- ZIP root: `HMDesignTest/`
|
|
||||||
|
|
||||||
## Applied change
|
|
||||||
|
|
||||||
- Reused the existing candidate/r4-derived portable contents.
|
|
||||||
- Replaced only `HMDesignTest/preview-server.cjs` with the current source launcher.
|
|
||||||
- Browser launch occurs from the server `listening` event, uses the selected `127.0.0.1` port, invokes the Windows default browser through `cmd /c start`, and is guarded to one launch per process.
|
|
||||||
- Browser launch errors are reported while the server remains running and the URL is printed.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
| Check | Result | Evidence |
|
|
||||||
|---|---|---|
|
|
||||||
| Bundled Node runtime | PASS | Node v22.23.3 (x64) started the staged server |
|
|
||||||
| Default port 6007 | PASS | Server printed `http://127.0.0.1:6007/` |
|
|
||||||
| 6007 collision fallback | PASS | Test occupant remained running; server selected port 6009 and printed its URL |
|
|
||||||
| Browser launch code path | PASS by execution path | `listening` invoked the launch function in both runs |
|
|
||||||
| Visual browser opened once | NOT RUN | Browser UI was not independently observable in this environment |
|
|
||||||
| BAT double-click | NOT RUN | Same limitation; equivalent bundled-node server invocation was run |
|
|
||||||
| Korean/space extraction path | NOT RUN | No separate user-style extraction/double-click run performed |
|
|
||||||
| Storybook render, Theme, Font, Asset | NOT RUN | No browser-level rendering verification claimed |
|
|
||||||
| Dashboard and H-2 | NOT RUN | Not changed or rebuilt |
|
|
||||||
|
|
||||||
## Source/archive note
|
|
||||||
|
|
||||||
The requested `HMDesignTest-Portable-Test-r4-final-2026-10-08.zip` was not present at the specified path. The existing `HMDesignTest-Portable-Test-r5-candidate-2026-10-08.zip`, together with `PORTABLE-R4-VERIFICATION.md`, was used as the preserved r4-derived staging source. The candidate launcher did not contain the browser patch; only the launcher was replaced.
|
|
||||||
|
|
||||||
Original r3/r4 archives were not overwritten. No Git commit/push, npm install/ci, production upload, component/pattern/theme change, or additional ZIP was performed.
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# HMDesignTest Portable r6 Verification
|
|
||||||
|
|
||||||
## Result
|
|
||||||
|
|
||||||
PORTABLE R6 DUAL LAUNCH RESULT: PARTIAL
|
|
||||||
|
|
||||||
## Artifact
|
|
||||||
|
|
||||||
- ZIP: `HMDesignTest-Portable-Test-r6-2026-10-08.zip`
|
|
||||||
- Size: `108,409,779` bytes
|
|
||||||
- SHA-256: `BCBFDFEE73CA3AE0CBDF8E810DAFAB6535578A655E3334CEB65B0E73F61C0ED0`
|
|
||||||
- Source candidate: `HMDesignTest-Portable-Test-r5-candidate-2026-10-08.zip`
|
|
||||||
- Source candidate SHA-256: `2CD5BE30C6B6D7CCAC21D1081E9A0152CDA402D3D2F9218CCBCA6E253A0B9A38`
|
|
||||||
|
|
||||||
## Packaging verification
|
|
||||||
|
|
||||||
- ZIP entries: `1,948` regular files; all paths begin with `HMDesignTest/`.
|
|
||||||
- Independent extraction: PASS.
|
|
||||||
- Extracted files: `1,948`.
|
|
||||||
- Inventory-managed files: `1,946`.
|
|
||||||
- Explicitly excluded: `FILE-INVENTORY-R6.json`, `PORTABLE-R6-VERIFICATION.md`.
|
|
||||||
- Inventory hash comparison: PASS; `0` mismatches.
|
|
||||||
- `node_modules/` and `.git/`: absent.
|
|
||||||
- Root layout includes `HMDesignTest/start-preview.bat`, `preview-server.cjs`, `runtime/node/`, `storybook-static/`, `dist-dashboard/`, `packages/`, `themes/`, `docs/`, `AGENTS.md`, `CLAUDE.md`, and `README.md`.
|
|
||||||
|
|
||||||
## Execution verification
|
|
||||||
|
|
||||||
- Bundled Node: PASS — `v22.23.3 (x64)`.
|
|
||||||
- Extracted BAT: PASS — `cmd.exe /c start-preview.bat` launched the bundled server.
|
|
||||||
- Default port: PASS — both URLs printed on `6007`.
|
|
||||||
- Port fallback: PASS — a second server selected `6009` when lower ports were occupied; both printed URLs used that port.
|
|
||||||
- Browser launch path: PASS by execution path — Dashboard and Storybook each have one guarded Windows default-browser launch call after `listening`.
|
|
||||||
- Korean/space path: PASS — extracted under `D:\디자인시스템\HMDesignSystem\r6 verify 한글 space\` and BAT started successfully.
|
|
||||||
|
|
||||||
## Not run
|
|
||||||
|
|
||||||
The shell and available browser could not access the locally spawned localhost socket (`access denied` / `ERR_CONNECTION_TIMED_OUT` or `ERR_CONNECTION_REFUSED`). Therefore HTTP 200, actual browser-rendered Dashboard/Storybook, Theme/font/asset loading in-browser, Dashboard filter/search/reset interaction, and console/runtime error checks are `NOT RUN`, not PASS.
|
|
||||||
|
|
||||||
## Scope preservation
|
|
||||||
|
|
||||||
Dashboard asset hashes match the current repository `dist-dashboard` outputs. No Component, Pattern, Theme, Storybook, H-2, source, existing ZIP, Git, npm install, full build, commit/push, or upload was performed.
|
|
||||||
@@ -56,7 +56,7 @@ npm run storybook
|
|||||||
|
|
||||||
브라우저에서 **http://localhost:6024/** 를 엽니다. Component는 Components, 업무/Layout Pattern은 Patterns, 브랜드 컬러·Typography는 Foundation에서 확인합니다. 내부 Theme 확인은 `?path=/story/theme-runtime-contract--comparison`, Designer는 `?path=/story/theme-designer-playground--editor`를 사용합니다. 확정 컬러로 검수하며 Designer 임시 편집은 해당 Story 안에서만 적용됩니다. [Designer 사용법](docs/guidelines/designer-theme-guide.md).
|
브라우저에서 **http://localhost:6024/** 를 엽니다. Component는 Components, 업무/Layout Pattern은 Patterns, 브랜드 컬러·Typography는 Foundation에서 확인합니다. 내부 Theme 확인은 `?path=/story/theme-runtime-contract--comparison`, Designer는 `?path=/story/theme-designer-playground--editor`를 사용합니다. 확정 컬러로 검수하며 Designer 임시 편집은 해당 Story 안에서만 적용됩니다. [Designer 사용법](docs/guidelines/designer-theme-guide.md).
|
||||||
|
|
||||||
Windows에서는 Clone한 루트의 `.\start-storybook.bat`를 실행하거나 파일을 더블 클릭할 수 있습니다(Node.js/npm 별도 설치 필요). **6024의 기존 서버가 HM Story index·preview 준비 조건을 만족하면 설치·빌드 검사 전에 해당 주소를 열고 종료합니다.** 다른 개발 폴더의 Storybook일 수 있으며, 신규 Clone의 의존성 설치·빌드·독립 실행 성공을 뜻하지 않습니다. 재사용 조건을 만족하지 않는 서버가 포트를 점유하면 오류를 표시하고, 빈 포트에서는 필요한 설치·package build 후 새 서버를 시작합니다. BAT에는 포트 변경 옵션이 없습니다. 일반 열람 방법, 정확한 재사용 조건과 기존 서버를 유지하는 별도 포트 QA는 [배포 안내](docs/guidelines/publisher-distribution-guide.md#storybook-bat-실행-분기)와 [신규-clone-독립-실행-qa](docs/guidelines/publisher-distribution-guide.md#신규-clone-독립-실행-qa)를 따릅니다. 새로 시작한 서버 종료는 실행 터미널의 Ctrl+C입니다.
|
일반 열람은 로컬 설치 없이 웹 Storybook(`https://design.hmac.kr/`, `main` 배포본)을 사용합니다. 위 `npm run storybook`은 소스를 수정·확인하는 로컬 개발용이며, 종료는 실행 터미널의 Ctrl+C입니다. 기존 서버를 유지하는 별도 포트 QA는 [신규 Clone 독립 실행 QA](docs/guidelines/publisher-distribution-guide.md#신규-clone-독립-실행-qa)를 따릅니다. 과거 Windows 실행기(`start-storybook.bat`)는 웹 Storybook 운영으로 제거했습니다.
|
||||||
|
|
||||||
정적 Storybook 빌드는 `npm run build-storybook`, 앱 빌드는 `npm run build`, 타입 확인은 `npm run typecheck`입니다. 명령은 루트 package.json의 현재 scripts를 기준으로 합니다.
|
정적 Storybook 빌드는 `npm run build-storybook`, 앱 빌드는 `npm run build`, 타입 확인은 `npm run typecheck`입니다. 명령은 루트 package.json의 현재 scripts를 기준으로 합니다.
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,20 @@
|
|||||||
|
|
||||||
## 설치 조건과 Storybook 열람
|
## 설치 조건과 Storybook 열람
|
||||||
|
|
||||||
Node.js를 별도로 설치하고 PATH에서 `node`와 `npm`을 사용할 수 있어야 합니다. 루트 `package.json`에는 `engines`/`packageManager` 버전 고정이 없으며, 현재 lockfile의 Vite Node 조건은 `^20.19.0 || >=22.12.0`입니다(Node 21 제외). BAT의 Node 검사도 이 조건에 맞습니다. 검증 환경은 Windows, **Node 20.20.1 / npm 10.8.2**이며 npm 10.8.2는 검증한 버전입니다. Git, Node/npm의 PATH와 버전을 확인합니다. 최초 Clone에는 의존성과 workspace 빌드 산출물이 없으므로 설치·빌드가 필요합니다. 설치에는 공식 Gitea·npm 레지스트리 접속이 필요합니다. HM Registry 설치·publish는 필요하지 않습니다.
|
Node.js를 별도로 설치하고 PATH에서 `node`와 `npm`을 사용할 수 있어야 합니다. 루트 `package.json`에는 `engines`/`packageManager` 버전 고정이 없으며, 현재 lockfile의 Vite Node 조건은 `^20.19.0 || >=22.12.0`입니다(Node 21 제외). 검증 환경은 Windows, **Node 20.20.1 / npm 10.8.2**이며 npm 10.8.2는 검증한 버전입니다. Git, Node/npm의 PATH와 버전을 확인합니다. 최초 Clone에는 의존성과 workspace 빌드 산출물이 없으므로 설치·빌드가 필요합니다. 설치에는 공식 Gitea·npm 레지스트리 접속이 필요합니다. HM Registry 설치·publish는 필요하지 않습니다.
|
||||||
|
|
||||||
일반 사용자는 아래처럼 Clone 후 Windows BAT를 실행하거나 `start-storybook.bat`를 더블 클릭합니다. 기존 서버 재사용 시에는 아래 [실행 분기](#storybook-bat-실행-분기)가 적용됩니다.
|
일반 열람은 로컬 설치 없이 웹 Storybook(`https://design.hmac.kr/`)을 사용합니다. 웹 Storybook은 `main` 배포본이며 `main`에 병합되지 않은 브랜치의 Story는 표시되지 않습니다. 과거 Windows 실행기(`start-storybook.bat`)는 웹 Storybook 운영으로 제거했습니다.
|
||||||
|
|
||||||
|
로컬 개발 Storybook이 필요하면 Clone 후 루트에서 다음을 실행합니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
git clone https://gitea.hmac.kr/b25027/HMDesign.git
|
git clone https://gitea.hmac.kr/b25027/HMDesign.git
|
||||||
cd HMDesign
|
cd HMDesign
|
||||||
node --version
|
node --version
|
||||||
npm --version
|
npm --version
|
||||||
.\start-storybook.bat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
수동 설치·실행이 필요하면 Clone한 루트에서 다음을 실행합니다. `npm ci`는 lockfile 기준으로 의존성과 로컬 workspace 연결을 준비하고, `build:packages`는 UI/CENTER package 산출물을 만듭니다. 기존 서버가 있는 환경의 독립 재현성 검사는 별도 [QA 절차](#신규-clone-독립-실행-qa)를 따릅니다.
|
이어서 Clone한 루트에서 다음을 실행합니다. `npm ci`는 lockfile 기준으로 의존성과 로컬 workspace 연결을 준비하고, `build:packages`는 UI/CENTER package 산출물을 만듭니다. 기존 서버가 있는 환경의 독립 재현성 검사는 별도 [QA 절차](#신규-clone-독립-실행-qa)를 따릅니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
npm ci
|
npm ci
|
||||||
@@ -30,18 +31,6 @@ Git Clone에는 `node_modules`, 사전 빌드 Storybook, 포터블 Node, 로컬
|
|||||||
|
|
||||||
과거 포터블 ZIP은 당시 포함된 Node·정적 Storybook·ZIP 전용 실행기를 사용한 별도 배포물입니다. 일반 Git Clone이나 소스 ZIP에는 그 런타임이 포함되지 않으므로 Node.js/npm 설치 조건을 생략할 수 없습니다. 과거 ZIP의 6007은 현재 개발 Storybook 기본 포트 6024와 다릅니다.
|
과거 포터블 ZIP은 당시 포함된 Node·정적 Storybook·ZIP 전용 실행기를 사용한 별도 배포물입니다. 일반 Git Clone이나 소스 ZIP에는 그 런타임이 포함되지 않으므로 Node.js/npm 설치 조건을 생략할 수 없습니다. 과거 ZIP의 6007은 현재 개발 Storybook 기본 포트 6024와 다릅니다.
|
||||||
|
|
||||||
### Storybook BAT 실행 분기
|
|
||||||
|
|
||||||
[start-storybook.bat](../../start-storybook.bat)는 BAT가 있는 폴더를 작업 경로로 설정하고 [Node 실행기](../../scripts/start-storybook.mjs)를 호출합니다. 실행기는 루트 [package.json](../../package.json)의 `scripts.storybook`에서 포트를 읽습니다. 현재 기본 포트는 **6024**이며 BAT 인수나 환경변수로 포트를 변경하는 기능은 없습니다.
|
|
||||||
|
|
||||||
1. **기존 서버가 준비됨:** `http://localhost:6024/index.json`의 HTTP 성공 응답과 `entries` 안의 `components-form-button--all`, `theme-designer-playground--editor` 두 ID 존재, `iframe.html`의 HTTP 성공 응답을 모두 확인하면 기존 서버를 재사용합니다. 각 요청 제한은 1.5초입니다. PowerShell `Start-Process`로 기본 브라우저에 `http://localhost:6024/`를 열고 BAT는 종료합니다. npm 실행 가능 여부, 의존성 설치와 package 산출물 검사를 모두 건너뛰므로 `npm ci`, `build:packages`, 새 서버 시작이 생략됩니다. Node 버전 검사는 재사용 전에도 수행합니다.
|
|
||||||
2. **준비 조건 불충족 + 포트 점유:** `127.0.0.1:6024` TCP 연결에 성공하거나 연결 확인이 시간 초과되면 점유로 판단하고 오류로 종료합니다. 다른 서버뿐 아니라 아직 준비되지 않은 HM Storybook도 이 분기에 해당할 수 있습니다. 기존 프로세스를 자동 종료하거나 다른 포트로 이동하지 않습니다.
|
|
||||||
3. **준비 조건 불충족 + 빈 포트:** `npm.cmd --version` 성공을 확인합니다. `node_modules/.bin/storybook.cmd`가 없을 때만 `npm.cmd ci`를 실행합니다. UI/CENTER package의 `exports`에서 문자열 경로 또는 `import` 경로 중 하나라도 없을 때만 `npm.cmd run build:packages`를 실행합니다. 모든 의존성의 정상 상태, 타입 산출물이나 기존 산출물의 최신 여부까지 검사하는 것은 아닙니다. 이어서 `npm.cmd run storybook -- --exact-port --ci`로 현재 Clone의 새 서버를 시작합니다. Story index/preview 준비를 최대 3분 기다리고 기본 브라우저를 연 뒤 서버 종료까지 터미널을 유지합니다. 종료는 이 터미널의 Ctrl+C입니다.
|
|
||||||
|
|
||||||
**기존 서버 재사용은 신규 Clone의 독립 실행 성공 증거가 아닙니다.** 준비 확인은 Story ID와 HTTP 응답만 검사하며 서버의 실행 프로젝트 경로·Git SHA를 확인하지 않습니다. 브라우저에 표시된 화면이 기존 개발 폴더의 Storybook일 수 있습니다. 새 Clone의 Node/npm 설치 조건과 새 소스의 실행 여부를 확인하려면 아래 QA를 사용합니다.
|
|
||||||
|
|
||||||
실행 폴더 접근 실패는 BAT의 폴더 오류 안내, Node/npm·설치·빌드·서버·브라우저 열기 실패는 상세 오류와 BAT의 실행 실패 안내로 표시됩니다. 포트 점유, 서버의 비정상 종료(종료 코드가 0이 아닌 경우)와 준비 시간 초과도 오류입니다. BAT는 오류 시 창을 유지하기 위해 `pause`하고 종료 코드 1을 반환합니다. 포트 점유 오류가 나면 기존 서버의 담당자·실행 위치를 확인하거나 아래 격리 실행을 사용합니다.
|
|
||||||
|
|
||||||
### 신규 Clone 독립 실행 QA
|
### 신규 Clone 독립 실행 QA
|
||||||
|
|
||||||
이 절차는 일반 Storybook 열람과 별개입니다. 실행 터미널에서 새 Clone 루트의 `Get-Location`, `git remote get-url origin`, `git rev-parse HEAD`를 기록하고, 최초 설치 전 `Test-Path node_modules`로 기존 의존성 유무도 구분합니다. 새 Clone에서 `npm ci`와 `npm run build:packages`의 성공 로그를 확인하고 다음 workspace 연결을 확인합니다.
|
이 절차는 일반 Storybook 열람과 별개입니다. 실행 터미널에서 새 Clone 루트의 `Get-Location`, `git remote get-url origin`, `git rev-parse HEAD`를 기록하고, 최초 설치 전 `Test-Path node_modules`로 기존 의존성 유무도 구분합니다. 새 Clone에서 `npm ci`와 `npm run build:packages`의 성공 로그를 확인하고 다음 workspace 연결을 확인합니다.
|
||||||
@@ -58,14 +47,14 @@ npm ls @hm-design/ui @hm-design/center-graphic --depth=0
|
|||||||
|
|
||||||
`@hm-design/ui -> packages/ui`, `@hm-design/center-graphic -> packages/center-graphic`처럼 해당 Clone의 workspace를 가리키는지 확인합니다. 실행 방법은 환경에 따라 다음 중 하나를 선택합니다.
|
`@hm-design/ui -> packages/ui`, `@hm-design/center-graphic -> packages/center-graphic`처럼 해당 Clone의 workspace를 가리키는지 확인합니다. 실행 방법은 환경에 따라 다음 중 하나를 선택합니다.
|
||||||
|
|
||||||
- **기존 6024 서버가 없는 환경:** 위 준비 후 `.\start-storybook.bat`를 실행합니다. 새 서버 시작 로그와 6024의 대표 Story 렌더를 확인합니다. 기존 서버를 열고 BAT가 즉시 종료한 경우는 독립 실행으로 판정하지 않습니다.
|
- **기존 6024 서버가 없는 환경:** 위 준비 후 `npm run storybook`을 실행합니다. 새 서버 시작 로그와 6024의 대표 Story 렌더를 확인합니다.
|
||||||
- **기존 서버를 유지하는 환경:** 사용하지 않는 별도 포트에서 설치된 Storybook CLI를 직접 실행합니다. 아래 6124는 이전 Acceptance Test의 격리 포트 예시이며 현재 공식 기본 포트 6024를 바꾸지 않습니다. 6124가 점유됐으면 명령과 열람 URL의 숫자를 사용 가능한 포트로 함께 바꿉니다.
|
- **기존 서버를 유지하는 환경:** 사용하지 않는 별도 포트에서 설치된 Storybook CLI를 직접 실행합니다. 아래 6124는 이전 Acceptance Test의 격리 포트 예시이며 현재 공식 기본 포트 6024를 바꾸지 않습니다. 6124가 점유됐으면 명령과 열람 URL의 숫자를 사용 가능한 포트로 함께 바꿉니다.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\node_modules\.bin\storybook.cmd dev -p 6124 --host 127.0.0.1 --no-open --disable-telemetry --exact-port --ci
|
.\node_modules\.bin\storybook.cmd dev -p 6124 --host 127.0.0.1 --no-open --disable-telemetry --exact-port --ci
|
||||||
```
|
```
|
||||||
|
|
||||||
이 명령은 BAT를 거치지 않으며 package.json이나 Storybook 설정을 수정하지 않습니다. 현재 설치된 Storybook CLI가 지원하는 `-p`, `--host`, `--exact-port`, `--ci`를 사용합니다. 포트 점유 시 다른 서버를 재사용하지 않고 실패합니다. 브라우저 자동 열기는 꺼져 있으므로 실행 성공 로그의 포트를 확인한 뒤 `http://127.0.0.1:6124/`를 직접 엽니다. index/iframe 응답뿐 아니라 Button·Input·DataTable·Dashboard·Theme Comparison 등 대표 Story를 렌더링하고, 실행 터미널의 프로젝트 경로·HEAD·포트와 연결해 기록합니다. 응답이나 화면만으로 Clone의 정체를 판정하지 않습니다. 종료할 때는 이 QA 서버의 터미널에서만 Ctrl+C를 사용합니다.
|
이 명령은 package.json이나 Storybook 설정을 수정하지 않습니다. 현재 설치된 Storybook CLI가 지원하는 `-p`, `--host`, `--exact-port`, `--ci`를 사용합니다. 포트 점유 시 다른 서버를 재사용하지 않고 실패합니다. 브라우저 자동 열기는 꺼져 있으므로 실행 성공 로그의 포트를 확인한 뒤 `http://127.0.0.1:6124/`를 직접 엽니다. index/iframe 응답뿐 아니라 Button·Input·DataTable·Dashboard·Theme Comparison 등 대표 Story를 렌더링하고, 실행 터미널의 프로젝트 경로·HEAD·포트와 연결해 기록합니다. 응답이나 화면만으로 Clone의 정체를 판정하지 않습니다. 종료할 때는 이 QA 서버의 터미널에서만 Ctrl+C를 사용합니다.
|
||||||
|
|
||||||
### 기존 Acceptance Test 근거와 판정 범위
|
### 기존 Acceptance Test 근거와 판정 범위
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ ChatGPT Work의 실제 저장소·사내 Gitea 접근, 터미널·브라우저
|
|||||||
|
|
||||||
- Node.js **`^20.19.0 || >=22.12.0`**, **Node 21 제외**. npm과 PATH, Gitea/npm 레지스트리 접속 필요. 검증 환경은 Node 20.20.1 / npm 10.8.2이며 npm의 별도 버전 고정 계약은 없다.
|
- Node.js **`^20.19.0 || >=22.12.0`**, **Node 21 제외**. npm과 PATH, Gitea/npm 레지스트리 접속 필요. 검증 환경은 Node 20.20.1 / npm 10.8.2이며 npm의 별도 버전 고정 계약은 없다.
|
||||||
- 최초 독립 설치: Clone 루트에서 `npm ci` → `npm run build:packages` → `npm run storybook`. 실제 [scripts](../../package.json)를 따른다. Git 소스에는 node_modules·dist·사전 빌드 Storybook·포터블 Node가 없다.
|
- 최초 독립 설치: Clone 루트에서 `npm ci` → `npm run build:packages` → `npm run storybook`. 실제 [scripts](../../package.json)를 따른다. Git 소스에는 node_modules·dist·사전 빌드 Storybook·포터블 Node가 없다.
|
||||||
- 기본 Storybook **6024**, Windows [start-storybook.bat](../../start-storybook.bat) 제공. BAT는 [Node 실행기](../../scripts/start-storybook.mjs)를 호출한다.
|
- 기본 Storybook **6024**, Windows `start-storybook.bat` 제공. BAT는 Node 실행기 `scripts/start-storybook.mjs`를 호출한다(두 파일은 2026-10-22 제거, 당시 기록).
|
||||||
- 준비된 6024의 index/대표 Story ID/iframe 조건을 만족하면 기존 서버를 열고 종료한다. Node 검사 후 npm 설치·package build를 생략할 수 있으며 **새 Clone 독립 실행의 증거가 아니다**.
|
- 준비된 6024의 index/대표 Story ID/iframe 조건을 만족하면 기존 서버를 열고 종료한다. Node 검사 후 npm 설치·package build를 생략할 수 있으며 **새 Clone 독립 실행의 증거가 아니다**.
|
||||||
- 준비되지 않은 점유 서버는 오류. 빈 포트는 필요한 의존성 설치·export 산출물 build 후 새 서버를 실행한다. BAT에 포트 변경 옵션은 없다. 전체 의존성 정상 상태·산출물 최신성을 자동 인증하지 않는다.
|
- 준비되지 않은 점유 서버는 오류. 빈 포트는 필요한 의존성 설치·export 산출물 build 후 새 서버를 실행한다. BAT에 포트 변경 옵션은 없다. 전체 의존성 정상 상태·산출물 최신성을 자동 인증하지 않는다.
|
||||||
- 독립 QA의 **6124**는 기존 서버 보존용 개발 포트 사례. 최신 Foundation의 **6322**는 당시 정적 build 열람 포트 사례. 과거 ZIP의 **6007**과 모두 기본 6024에서 구분한다. [독립 QA 절차](../guidelines/publisher-distribution-guide.md#신규-clone-독립-실행-qa)를 따른다.
|
- 독립 QA의 **6124**는 기존 서버 보존용 개발 포트 사례. 최신 Foundation의 **6322**는 당시 정적 build 열람 포트 사례. 과거 ZIP의 **6007**과 모두 기본 6024에서 구분한다. [독립 QA 절차](../guidelines/publisher-distribution-guide.md#신규-clone-독립-실행-qa)를 따른다.
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
import { execFileSync } from 'node:child_process';
|
|
||||||
|
|
||||||
const root = process.cwd();
|
|
||||||
const out = path.join(root, 'HMDesignSystem-AI-Reference-Test');
|
|
||||||
const zip = path.join(root, 'HMDesignSystem-AI-Reference-Test.zip');
|
|
||||||
|
|
||||||
// Recursive cleanup is restricted to this script's fixed output directory.
|
|
||||||
if (path.dirname(path.resolve(out)) !== root || path.basename(out) !== 'HMDesignSystem-AI-Reference-Test') throw new Error('Unsafe package output path');
|
|
||||||
fs.rmSync(out, { recursive: true, force: true });
|
|
||||||
fs.rmSync(zip, { force: true });
|
|
||||||
fs.mkdirSync(out, { recursive: true });
|
|
||||||
|
|
||||||
const copy = (from, to, options = {}) => {
|
|
||||||
const src = path.join(root, from);
|
|
||||||
if (!fs.existsSync(src)) return false;
|
|
||||||
const dest = path.join(out, to);
|
|
||||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
||||||
fs.cpSync(src, dest, { recursive: true, force: true, ...options });
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Preserve repository-relative paths for source references and imports.
|
|
||||||
copy('packages/ui/src', 'source/packages/ui/src');
|
|
||||||
copy('packages/ui/package.json', 'source/packages/ui/package.json');
|
|
||||||
copy('packages/ui/tsconfig.json', 'source/packages/ui/tsconfig.json');
|
|
||||||
copy('packages/ui/tsconfig.build.json', 'source/packages/ui/tsconfig.build.json');
|
|
||||||
copy('packages/ui/components.json', 'source/packages/ui/components.json');
|
|
||||||
copy('packages/center-graphic/src', 'source/packages/center-graphic/src');
|
|
||||||
copy('packages/center-graphic/package.json', 'source/packages/center-graphic/package.json');
|
|
||||||
copy('packages/center-graphic/tsconfig.json', 'source/packages/center-graphic/tsconfig.json');
|
|
||||||
copy('packages/center-graphic/tsconfig.build.json', 'source/packages/center-graphic/tsconfig.build.json');
|
|
||||||
|
|
||||||
copy('.storybook', 'storybook/config');
|
|
||||||
copy('src', 'storybook/source');
|
|
||||||
copy('storybook-static', 'storybook/static');
|
|
||||||
|
|
||||||
copy('dist', 'packages/app-build');
|
|
||||||
copy('dist-dashboard', 'packages/dashboard-export-build');
|
|
||||||
copy('packages/ui/dist', 'packages/ui-dist');
|
|
||||||
copy('packages/center-graphic/dist', 'packages/center-graphic-dist');
|
|
||||||
copy('theme-dist', 'theme/generated');
|
|
||||||
copy('themes', 'theme/source');
|
|
||||||
copy('packages/ui/src/theme.css', 'theme/source/packages-ui-theme.css');
|
|
||||||
copy('packages/ui/src/theme', 'theme/source/packages-ui-theme');
|
|
||||||
|
|
||||||
copy('docs', 'docs/source');
|
|
||||||
copy('AGENTS.md', 'docs/AGENTS.md');
|
|
||||||
copy('CLAUDE.md', 'docs/CLAUDE.md');
|
|
||||||
copy('README.md', 'docs/README.md');
|
|
||||||
copy('package.json', 'docs/root-package.json');
|
|
||||||
copy('package-lock.json', 'docs/package-lock.json');
|
|
||||||
|
|
||||||
// Executable Guard and its contract sources share the original workspace paths.
|
|
||||||
// Keep legacy docs locations for existing reference consumers.
|
|
||||||
copy('AGENTS.md', 'source/AGENTS.md');
|
|
||||||
copy('docs', 'source/docs');
|
|
||||||
copy('package.json', 'source/package.json');
|
|
||||||
copy('package-lock.json', 'source/package-lock.json');
|
|
||||||
copy('scripts/ai-generation-guard.mjs', 'source/scripts/ai-generation-guard.mjs');
|
|
||||||
copy('scripts/ai-generation-guard.test.mjs', 'source/scripts/ai-generation-guard.test.mjs');
|
|
||||||
copy('src', 'source/src');
|
|
||||||
copy('themes', 'source/themes');
|
|
||||||
|
|
||||||
copy('src/exports', 'examples/react-and-html/src/exports');
|
|
||||||
copy('src/exports/dashboard', 'examples/react-and-html/src/exports/dashboard');
|
|
||||||
copy('index.html', 'examples/react-and-html/index.html');
|
|
||||||
copy('vite.dashboard-export.config.ts', 'examples/react-and-html/vite.dashboard-export.config.ts');
|
|
||||||
copy('scripts/finalize-dashboard-export.mjs', 'examples/react-and-html/finalize-dashboard-export.mjs');
|
|
||||||
copy('public/fonts', 'assets/fonts');
|
|
||||||
copy('packages/center-graphic/src/assets', 'assets/center-graphic');
|
|
||||||
|
|
||||||
const files = (dir) => fs.existsSync(dir) ? fs.readdirSync(dir, { recursive: true }).filter((p) => fs.statSync(path.join(dir, p)).isFile()).length : 0;
|
|
||||||
const countFiles = (rel, suffix) => {
|
|
||||||
const dir = path.join(root, rel);
|
|
||||||
if (!fs.existsSync(dir)) return 0;
|
|
||||||
return fs.readdirSync(dir, { recursive: true }).filter((p) => String(p).endsWith(suffix)).length;
|
|
||||||
};
|
|
||||||
const publicComponentFiles = countFiles('packages/ui/src/components', '.tsx');
|
|
||||||
const patternFiles = countFiles('packages/ui/src/patterns', '.tsx');
|
|
||||||
const storyFiles = ['.stories.tsx', '.stories.ts', '.stories.jsx', '.stories.js'].reduce((n, s) => n + countFiles('src', s), 0);
|
|
||||||
const generatedAt = new Date().toISOString();
|
|
||||||
|
|
||||||
const startHere = `# HM Design System AI Reference
|
|
||||||
|
|
||||||
이 ZIP은 빌드 패키지만이 아니라 HM Design System의 실제 구현·Storybook·Theme·문서를 함께 참조하기 위한 테스트 배포본입니다.
|
|
||||||
|
|
||||||
## AI 참조 순서
|
|
||||||
|
|
||||||
1. \`source/AGENTS.md\`의 짧은 필수 규칙부터 읽습니다.
|
|
||||||
2. \`source/docs/guidelines/component-pattern-reference.md\`에서 필요한 항목만 찾습니다.
|
|
||||||
3. 선택한 Component/Pattern 문서·공개 API와 대표 Story만 확인합니다. HTML/CSS 모방이나 import만으로 재사용을 주장하지 않습니다.
|
|
||||||
4. \`storybook/source\`의 Story에서 상태·조합·레이아웃을 확인합니다.
|
|
||||||
5. \`theme/source\` 및 \`theme/generated\`의 Token/CSS Custom Properties를 사용합니다.
|
|
||||||
6. \`packages/ui-dist\`와 declarations는 소비자 Export 형태를 확인할 때 사용합니다.
|
|
||||||
|
|
||||||
Guard는 \`source/\`에서 \`node scripts/ai-generation-guard.mjs --target <앱> --scope FULL_SCREEN|PARTIAL\`을 1회 실행합니다. PARTIAL은 \`--files\`, 최종 React artifact는 \`--result\`를 전달합니다. parser 의존성이 없으면 NOT VERIFIED로 보고합니다. 개발자가 실행할 때만 기존 lockfile로 의존성을 복원하며, 포터블 Storybook 테스트 사용자에게 npm 설치를 요구하지 않습니다. PASS는 정적 계약만 의미하며 실제 React 브라우저 결과를 별도로 최소 1회 확인합니다.
|
|
||||||
|
|
||||||
임의의 색상·spacing·radius·Typography 값을 추가하거나 디자인을 재해석하지 않습니다. 신규 화면은 기존 공개 Component/Pattern과 Theme 계약을 우선 재사용합니다.
|
|
||||||
|
|
||||||
## 구조
|
|
||||||
|
|
||||||
- \`source/\`: 원본 공개 패키지 구현 및 내부 의존 코드
|
|
||||||
- \`storybook/\`: Storybook 설정, Story/Pattern/Foundation 소스, 정적 Storybook
|
|
||||||
- \`packages/\`: 기존 빌드 산출물 및 TypeScript declarations
|
|
||||||
- \`theme/\`: Token 원본, Generated CSS, Runtime Theme 자료
|
|
||||||
- \`docs/\`: 원본 사용 규칙, AI 공통 가이드, 컴포넌트·패턴 문서
|
|
||||||
- \`examples/\`: React/HTML export 관련 예제 및 계약
|
|
||||||
- \`assets/\`: Font와 Center Graphic asset
|
|
||||||
|
|
||||||
## 재현 범위
|
|
||||||
|
|
||||||
React는 기존 공개 패키지를 직접 import하는 방식을 우선합니다. HTML은 \`examples/react-and-html\`의 기존 export와 Runtime 계약을 먼저 확인합니다. 이 배포본 자체에서 무료 AI에 의한 신규 화면 생성이나 브라우저 시각 검증은 수행하지 않았습니다.
|
|
||||||
`;
|
|
||||||
fs.writeFileSync(path.join(out, 'AI_START_HERE.md'), startHere, 'utf8');
|
|
||||||
|
|
||||||
const manifest = {
|
|
||||||
package: 'HMDesignSystem-AI-Reference-Test', generatedAt,
|
|
||||||
source: { publicComponentSourceFiles: publicComponentFiles, centerGraphicSourceFiles: files(path.join(out, 'source/packages/center-graphic/src')) },
|
|
||||||
storybook: { storyFiles, sourceFiles: files(path.join(out, 'storybook/source')), staticIncluded: fs.existsSync(path.join(out, 'storybook/static')) },
|
|
||||||
patterns: { patternSourceFiles: patternFiles },
|
|
||||||
theme: { sourceIncluded: fs.existsSync(path.join(out, 'theme/source')), generatedIncluded: fs.existsSync(path.join(out, 'theme/generated')) },
|
|
||||||
assets: { fontFiles: files(path.join(out, 'assets/fonts')), centerGraphicAssetFiles: files(path.join(out, 'assets/center-graphic')) },
|
|
||||||
verification: { aiReproduction: 'NOT_RUN', browserVisualVerification: 'NOT_RUN', sourceMutation: 'NOT_RUN (packaging-only)' }
|
|
||||||
};
|
|
||||||
fs.writeFileSync(path.join(out, 'MANIFEST.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
||||||
|
|
||||||
execFileSync('tar', ['-a', '-c', '-f', zip, '-C', root, path.basename(out)], { stdio: 'inherit' });
|
|
||||||
console.log(JSON.stringify({ out, zip, manifest }, null, 2));
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import fs from 'node:fs'
|
|
||||||
import path from 'node:path'
|
|
||||||
import { execFileSync } from 'node:child_process'
|
|
||||||
|
|
||||||
// Includes only repository source and named built artifacts, never node_modules,
|
|
||||||
// credentials, backups, Git metadata or unrelated local validation output.
|
|
||||||
const root = process.cwd()
|
|
||||||
const output = path.resolve(root, '_local/publisher-release/HMDesign-Publisher-2026-10-10')
|
|
||||||
if (path.dirname(output) !== path.resolve(root, '_local/publisher-release')) throw Error('Unsafe output')
|
|
||||||
fs.rmSync(output, { recursive: true, force: true })
|
|
||||||
fs.mkdirSync(output, { recursive: true })
|
|
||||||
const sources = execFileSync('git', ['ls-files', '-co', '--exclude-standard', '-z'], { encoding: 'utf8' }).split('\0').filter(Boolean)
|
|
||||||
for (const file of sources) {
|
|
||||||
if (/^(?:\.claude|\.codex|\.agents|\.git)(?:\/|$)|(?:^|\/)\.env(?:\.|$)|\.(?:zip|log|tsbuildinfo)$/.test(file)) continue
|
|
||||||
const destination = path.join(output, file)
|
|
||||||
fs.mkdirSync(path.dirname(destination), { recursive: true })
|
|
||||||
fs.copyFileSync(path.join(root, file), destination)
|
|
||||||
}
|
|
||||||
for (const dir of ['storybook-static', 'dist', 'dist-dashboard', 'dist-recruit', 'packages/ui/dist', 'packages/center-graphic/dist', 'examples/publisher-list/dist']) {
|
|
||||||
if (!fs.existsSync(dir)) throw Error(`Build required: ${dir}`)
|
|
||||||
fs.cpSync(dir, path.join(output, dir), { recursive: true })
|
|
||||||
}
|
|
||||||
for (const file of ['start-preview.bat', 'preview-server.cjs']) fs.copyFileSync(`scripts/portable-preview/${file}`, path.join(output, file))
|
|
||||||
fs.writeFileSync(path.join(output, 'START-HERE.md'), '# HMDesign 퍼블리셔 배포\n\nNode.js 20.19+ 또는 22.12+를 설치한 Windows에서 `start-preview.bat`를 실행하세요. 정적 열람에는 npm 설치가 필요하지 않습니다. 출력된 주소의 `/example/`은 실제 React 신규 화면입니다.\n\n소스 수정·새 화면 실행은 [전체 사용자 안내](docs/guidelines/publisher-distribution-guide.md)를 따릅니다. 검증과 제한은 [추가 수정 보고서](docs/guidelines/publisher-additional-review-2026-10-10.md)를 확인하세요.\n')
|
|
||||||
const manifest = sources.filter(file => fs.existsSync(path.join(output, file)))
|
|
||||||
fs.writeFileSync(path.join(output, 'DISTRIBUTION.json'), JSON.stringify({ date: '2026-10-10', sourcePaths: manifest, nodeRequirement: 'Node.js 20.19+ or 22.12+', bundledNode: false, npmRequiredForStaticPreview: false }, null, 2))
|
|
||||||
console.log(output)
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
// Preview launcher: built-in modules only. Bundled Node when present, installed
|
|
||||||
// Node otherwise; no npm install/build is needed for the static preview.
|
|
||||||
const http = require('node:http');
|
|
||||||
const fs = require('node:fs');
|
|
||||||
const path = require('node:path');
|
|
||||||
const { spawn } = require('node:child_process');
|
|
||||||
const root = __dirname;
|
|
||||||
const types = { '.html':'text/html; charset=utf-8', '.js':'text/javascript; charset=utf-8', '.mjs':'text/javascript; charset=utf-8', '.css':'text/css; charset=utf-8', '.json':'application/json; charset=utf-8', '.svg':'image/svg+xml', '.png':'image/png', '.jpg':'image/jpeg', '.jpeg':'image/jpeg', '.gif':'image/gif', '.webp':'image/webp', '.ico':'image/x-icon', '.ttf':'font/ttf', '.woff':'font/woff', '.woff2':'font/woff2', '.txt':'text/plain; charset=utf-8' };
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
if (!['GET','HEAD'].includes(req.method)) { res.writeHead(405); res.end(); return; }
|
|
||||||
let pathname;
|
|
||||||
try { pathname = decodeURIComponent(new URL(req.url, 'http://127.0.0.1').pathname); }
|
|
||||||
catch { res.writeHead(400); res.end(); return; }
|
|
||||||
// Dashboard is a production export and must be served from its own URL root.
|
|
||||||
const example = pathname === '/example' || pathname.startsWith('/example/');
|
|
||||||
const dashboard = pathname === '/dashboard' || pathname.startsWith('/dashboard/');
|
|
||||||
const dashboardPath = dashboard ? (pathname === '/dashboard' ? '/dashboard/' : pathname) : pathname;
|
|
||||||
// Source Reference/static example paths keep their repository-relative imports.
|
|
||||||
const reference = ['/docs/','/packages/','/src/','/public/'].some(prefix => pathname.startsWith(prefix));
|
|
||||||
const base = example ? path.join(root, 'examples/publisher-list/dist') : dashboard ? path.join(root, 'dist-dashboard') : (reference ? root : path.join(root, 'storybook-static'));
|
|
||||||
const relativePath = example ? pathname.slice('/example'.length) || '/' : dashboard ? dashboardPath.slice('/dashboard'.length) || '/' : (pathname === '/favicon.ico' ? '/favicon.svg' : pathname);
|
|
||||||
let file = path.resolve(base, '.' + relativePath);
|
|
||||||
const relative = path.relative(base, file);
|
|
||||||
if (relative.startsWith('..') || path.isAbsolute(relative) || pathname.includes('\\')) { res.writeHead(403); res.end(); return; }
|
|
||||||
fs.stat(file, (error, stat) => {
|
|
||||||
if (!error && stat.isDirectory()) file = path.join(file, 'index.html');
|
|
||||||
fs.stat(file, (error, stat) => {
|
|
||||||
if (error || !stat.isFile()) { res.writeHead(404); res.end('Not found'); return; }
|
|
||||||
res.writeHead(200, { 'Content-Type': types[path.extname(file).toLowerCase()] || 'application/octet-stream', 'Content-Length':stat.size, 'Cache-Control':'no-store', 'X-Content-Type-Options':'nosniff' });
|
|
||||||
if (req.method === 'HEAD') { res.end(); return; }
|
|
||||||
const stream = fs.createReadStream(file);
|
|
||||||
stream.on('error', () => res.destroy());
|
|
||||||
stream.pipe(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let port = 6007;
|
|
||||||
let browserOpened = false;
|
|
||||||
function openDefaultBrowser(url) {
|
|
||||||
if (browserOpened) return;
|
|
||||||
browserOpened = true;
|
|
||||||
const command = process.env.ComSpec || 'cmd.exe';
|
|
||||||
const browser = spawn(command, ['/c', 'start', '', url], {
|
|
||||||
detached: true,
|
|
||||||
stdio: 'ignore',
|
|
||||||
windowsHide: true
|
|
||||||
});
|
|
||||||
browser.on('error', error => {
|
|
||||||
console.error(`Browser auto-open failed: ${error.message}`);
|
|
||||||
console.error(`Open this address manually: ${url}`);
|
|
||||||
});
|
|
||||||
browser.unref();
|
|
||||||
}
|
|
||||||
server.on('error', error => {
|
|
||||||
if (error.code === 'EADDRINUSE' || error.code === 'EACCES') {
|
|
||||||
if (port === 0) { console.error(error.message); process.exitCode = 1; return; }
|
|
||||||
port = port < 6023 ? port + 1 : 0;
|
|
||||||
server.listen(port, '127.0.0.1');
|
|
||||||
} else { console.error(error.message); process.exitCode = 1; }
|
|
||||||
});
|
|
||||||
server.on('listening', () => {
|
|
||||||
const url = `http://127.0.0.1:${server.address().port}/`;
|
|
||||||
console.log(`HM Design System Portable Test\nNode ${process.version} (${process.arch})\nStorybook: ${url}\nNew React screen: ${url}example/\nDashboard: ${url}dashboard/\nStatic status example: ${url}docs/patterns/dashboard-status-static.html\nPress Ctrl+C to stop this preview.\n`);
|
|
||||||
openDefaultBrowser(url);
|
|
||||||
});
|
|
||||||
process.on('SIGINT', () => server.close(() => process.exit(0)));
|
|
||||||
process.on('SIGTERM', () => server.close(() => process.exit(0)));
|
|
||||||
server.listen(port, '127.0.0.1');
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal
|
|
||||||
chcp 65001 >nul
|
|
||||||
title HM Design System - Portable Test Preview
|
|
||||||
echo The server prints the actual Storybook, Dashboard and Example addresses below.
|
|
||||||
if not exist "%~dp0runtime\node\node.exe" (
|
|
||||||
where node >nul 2>nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo ERROR: Install Node.js 20.19+ or 22.12+, then retry. No npm install is needed for preview.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
node "%~dp0preview-server.cjs"
|
|
||||||
) else (
|
|
||||||
"%~dp0runtime\node\node.exe" "%~dp0preview-server.cjs"
|
|
||||||
)
|
|
||||||
if errorlevel 1 pause
|
|
||||||
endlocal
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// Git source launcher. Uses the existing npm scripts; no portable runtime.
|
|
||||||
import { spawn } from 'node:child_process';
|
|
||||||
import { readFileSync, existsSync } from 'node:fs';
|
|
||||||
import { fileURLToPath } from 'node:url';
|
|
||||||
import net from 'node:net';
|
|
||||||
|
|
||||||
process.chdir(fileURLToPath(new URL('../', import.meta.url)));
|
|
||||||
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
||||||
const portMatch = pkg.scripts?.storybook?.match(/(?:-p|--port)\s+(\d+)/);
|
|
||||||
if (!portMatch) throw new Error('Cannot determine the port from package.json scripts.storybook.');
|
|
||||||
const port = Number(portMatch[1]);
|
|
||||||
const url = `http://localhost:${port}/`;
|
|
||||||
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
||||||
if (!((major === 20 && minor >= 19) || (major === 22 && minor >= 12) || major > 22)) {
|
|
||||||
throw new Error('Node.js 20.19+ (20.x) or 22.12+ is required by the lockfile.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(args, options = {}) {
|
|
||||||
return spawn(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', args], {
|
|
||||||
stdio: 'inherit', windowsHide: true, ...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function completed(child) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
child.once('error', reject);
|
|
||||||
child.once('exit', (code) => code === 0 ? resolve() : reject(new Error(`Command failed with exit code ${code}. See output above.`)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function ready() {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${url}index.json`, { signal: AbortSignal.timeout(1500) });
|
|
||||||
if (!response.ok) return false;
|
|
||||||
const index = await response.json();
|
|
||||||
if (!index.entries?.['components-form-button--all'] || !index.entries?.['theme-designer-playground--editor']) return false;
|
|
||||||
return (await fetch(`${url}iframe.html`, { signal: AbortSignal.timeout(1500) })).ok;
|
|
||||||
} catch { return false; }
|
|
||||||
}
|
|
||||||
function occupied() {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const socket = net.connect({ host: '127.0.0.1', port });
|
|
||||||
socket.setTimeout(1500);
|
|
||||||
socket.once('connect', () => { socket.destroy(); resolve(true); });
|
|
||||||
socket.once('error', () => { socket.destroy(); resolve(false); });
|
|
||||||
socket.once('timeout', () => { socket.destroy(); resolve(true); });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async function openBrowser() {
|
|
||||||
await completed(spawn('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `Start-Process '${url}'`], {
|
|
||||||
stdio: 'inherit', windowsHide: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (await ready()) {
|
|
||||||
await openBrowser();
|
|
||||||
} else {
|
|
||||||
if (await occupied()) throw new Error(`Port ${port} is occupied by a server that is not a ready HMDesign Storybook. Stop it and retry.`);
|
|
||||||
await completed(run('npm.cmd --version', { stdio: 'ignore' }));
|
|
||||||
if (!existsSync('node_modules/.bin/storybook.cmd')) await completed(run('npm.cmd ci'));
|
|
||||||
const missingDist = ['packages/ui', 'packages/center-graphic'].some(dir => {
|
|
||||||
const workspace = JSON.parse(readFileSync(`${dir}/package.json`, 'utf8'));
|
|
||||||
return Object.values(workspace.exports).some(value => {
|
|
||||||
const entry = typeof value === 'string' ? value : value.import;
|
|
||||||
return entry && !existsSync(`${dir}/${entry}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (missingDist) await completed(run('npm.cmd run build:packages'));
|
|
||||||
// Exact port prevents interactive port selection, including a startup race.
|
|
||||||
const server = run('npm.cmd run storybook -- --exact-port --ci');
|
|
||||||
let stopped = false;
|
|
||||||
const exit = completed(server).finally(() => { stopped = true; });
|
|
||||||
// Attach a handler immediately while readiness polling is in progress.
|
|
||||||
exit.catch(() => {});
|
|
||||||
const stop = () => completed(run(`taskkill /pid ${server.pid} /t /f`, { stdio: 'ignore' })).catch(() => {});
|
|
||||||
process.once('SIGINT', async () => { await stop(); process.exit(0); });
|
|
||||||
process.once('SIGTERM', async () => { await stop(); process.exit(0); });
|
|
||||||
try {
|
|
||||||
const deadline = Date.now() + 180000;
|
|
||||||
while (!stopped && Date.now() < deadline) {
|
|
||||||
if (await ready()) { await openBrowser(); await exit; break; }
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
}
|
|
||||||
if (stopped) await exit;
|
|
||||||
else throw new Error('Storybook readiness timed out after 3 minutes.');
|
|
||||||
} catch (error) { await stop(); throw error; }
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableExtensions DisableDelayedExpansion
|
|
||||||
chcp 65001 >nul
|
|
||||||
title HMDesign - Storybook
|
|
||||||
pushd "%~dp0"
|
|
||||||
if errorlevel 1 goto root_error
|
|
||||||
node scripts\start-storybook.mjs
|
|
||||||
if errorlevel 1 goto fail
|
|
||||||
popd
|
|
||||||
endlocal
|
|
||||||
exit /b 0
|
|
||||||
:fail
|
|
||||||
echo.
|
|
||||||
echo ERROR: Storybook could not start. Review the error above.
|
|
||||||
echo Check Node.js/npm, dependency installation, and port availability.
|
|
||||||
popd
|
|
||||||
pause
|
|
||||||
endlocal
|
|
||||||
exit /b 1
|
|
||||||
:root_error
|
|
||||||
echo ERROR: Cannot access the launcher folder.
|
|
||||||
pause
|
|
||||||
endlocal
|
|
||||||
exit /b 1
|
|
||||||
Reference in new issue
Block a user