@simplysm/sd-claude 14.0.87 → 14.0.89
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/claude/references/sd-simplysm14/README.md +17 -18
- package/claude/references/sd-simplysm14/apis/angular/README.md +35 -0
- package/claude/references/sd-simplysm14/apis/angular/controls.md +51 -0
- package/claude/references/sd-simplysm14/apis/angular/crud.md +53 -0
- package/claude/references/sd-simplysm14/apis/angular/directives.md +34 -0
- package/claude/references/sd-simplysm14/apis/angular/features.md +40 -0
- package/claude/references/sd-simplysm14/apis/angular/infra.md +74 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +27 -0
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +103 -0
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +69 -0
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +28 -0
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +57 -0
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +73 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +78 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +66 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +71 -0
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +70 -0
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +62 -0
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +80 -0
- package/claude/references/sd-simplysm14/apis/core-common/README.md +262 -0
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +121 -0
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +128 -0
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +129 -0
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +91 -0
- package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +53 -0
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +117 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +17 -0
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +43 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +50 -0
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +41 -0
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +72 -0
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +39 -0
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +52 -0
- package/claude/references/sd-simplysm14/apis/excel/README.md +43 -0
- package/claude/references/sd-simplysm14/apis/excel/cell.md +54 -0
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +51 -0
- package/claude/references/sd-simplysm14/apis/excel/style.md +67 -0
- package/claude/references/sd-simplysm14/apis/excel/utils.md +35 -0
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +97 -0
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +83 -0
- package/claude/references/sd-simplysm14/apis/lint/README.md +49 -0
- package/claude/references/sd-simplysm14/apis/lint/rules.md +130 -0
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +13 -0
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +111 -0
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +128 -0
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +145 -0
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +147 -0
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +62 -0
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +90 -0
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +94 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +26 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +117 -0
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +291 -0
- package/claude/references/sd-simplysm14/apis/service-client/README.md +150 -0
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +48 -0
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +59 -0
- package/claude/references/sd-simplysm14/apis/service-common/README.md +84 -0
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +48 -0
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +72 -0
- package/claude/references/sd-simplysm14/apis/service-server/README.md +118 -0
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +71 -0
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +62 -0
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +39 -0
- package/claude/references/sd-simplysm14/apis/storage/README.md +120 -0
- package/claude/skills/sd-demo/SKILL.md +6 -0
- package/claude/skills/sd-impl/SKILL.md +4 -7
- package/claude/skills/sd-spec/SKILL.md +31 -858
- package/claude/skills/sd-spec/references/spec-authoring.md +519 -0
- package/claude/workflows/sd-docs.js +84 -0
- package/claude/{skills/sd-docs/references/subagent-prompt.md → workflows/sd-docs.rules.md} +25 -40
- package/package.json +1 -1
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/.specs/inventory/spec.md +0 -99
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/package.json +0 -12
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/index.ts +0 -3
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inbound/inbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/inventory/inventory-master.list.ts +0 -143
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/packages/demo-client/src/screens/outbound/outbound.list.ts +0 -150
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/pnpm-workspace.yaml +0 -2
- package/claude/skills/sd-demo/evals/fixtures/inventory-list/sd.config.ts +0 -12
- package/claude/skills/sd-demo/evals/golden.jsonl +0 -1
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/package.json +0 -8
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/src/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tests/.gitkeep +0 -0
- package/claude/skills/sd-dev/evals/fixtures/minimal-ts-pkg/tsconfig.json +0 -10
- package/claude/skills/sd-dev/evals/golden.jsonl +0 -1
- package/claude/skills/sd-docs/SKILL.md +0 -58
- package/claude/skills/sd-docs/evals/fixtures/new-write/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/new-write/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/README.md +0 -7
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/.claude/references/sd-simplysm14/apis/foo/README.md +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/bar/src/index.ts +0 -3
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/package.json +0 -6
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/baz/src/index.ts +0 -1
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/package.json +0 -5
- package/claude/skills/sd-docs/evals/fixtures/update-mixed/packages/foo/src/index.ts +0 -8
- package/claude/skills/sd-docs/evals/golden.jsonl +0 -2
- package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/packages/app/src/screens/box-register/box-register.view.ts +0 -46
- package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +0 -89
- package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +0 -101
- package/claude/skills/sd-impl/evals/golden.jsonl +0 -4
- package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +0 -25
- package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +0 -14
- package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +0 -37
- package/claude/skills/sd-manual/evals/golden.jsonl +0 -2
- package/claude/skills/sd-review/evals/fixtures/code-review/src/foo.ts +0 -7
- package/claude/skills/sd-review/evals/fixtures/doc-review/docs/foo.md +0 -4
- package/claude/skills/sd-review/evals/golden.jsonl +0 -2
- package/claude/skills/sd-skill/evals/fixtures/existing-skill/.claude/skills/todo-format/SKILL.md +0 -14
- package/claude/skills/sd-skill/evals/fixtures/new-skill/.gitkeep +0 -0
- package/claude/skills/sd-skill/evals/golden.jsonl +0 -2
- package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +0 -20
- package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +0 -95
- package/claude/skills/sd-spec/evals/golden.jsonl +0 -2
- package/claude/skills/sd-unpack/evals/fixtures/eml-with-text-attachment/meeting.eml +0 -21
- package/claude/skills/sd-unpack/evals/fixtures/simple-eml/meeting.eml +0 -10
- package/claude/skills/sd-unpack/evals/golden.jsonl +0 -2
- package/claude/skills/sd-use/evals/fixtures/empty/.gitkeep +0 -0
- package/claude/skills/sd-use/evals/golden.jsonl +0 -6
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @simplysm/core-node — pathx
|
|
2
|
+
|
|
3
|
+
`export * as pathx` 네임스페이스 (`packages/core-node/src/utils/path.ts`). 경로 문자열 가공·판정 유틸. OS 무관 비교를 위해 POSIX 슬래시(`/`)로 정규화하는 것이 핵심. `pathx.<fn>(...)` 형태로 호출.
|
|
4
|
+
|
|
5
|
+
## PosixPath (브랜드 타입)
|
|
6
|
+
|
|
7
|
+
- `type PosixPath = string & { [POSIX]: never }` — POSIX 슬래시 경로임을 나타내는 브랜드 타입. `posix()`/`posixResolve()` 로만 생성 가능. 일반 `string` 을 PosixPath 자리에 직접 넣을 수 없어, 정규화를 강제하는 타입 가드 역할.
|
|
8
|
+
|
|
9
|
+
## 정규화
|
|
10
|
+
|
|
11
|
+
- `posix(p: string): PosixPath` — 백슬래시 → 슬래시 치환만 수행. **결합·resolve 안 함**. 예: `posix("C:\\Users\\test")` → `"C:/Users/test"`. 이미 절대 경로인 문자열을 POSIX 표기로만 바꿀 때.
|
|
12
|
+
- `posixResolve(...args: string[]): PosixPath` — 인자들을 `path.resolve` 로 절대 경로 결합 후 슬래시로 변환. 예: `posixResolve("/base", "sub", "f.txt")` → `"/base/sub/f.txt"`, 상대경로 단독이면 cwd 기준 절대화. 경로 결합+정규화를 동시에 할 때.
|
|
13
|
+
|
|
14
|
+
## 경로 가공
|
|
15
|
+
|
|
16
|
+
- `changeFileDirectory(filePath, fromDirectory, toDirectory): string` — `filePath` 의 디렉토리 prefix 를 `fromDirectory` → `toDirectory` 로 치환(상대 위치 유지). 예: `("/a/b/c.txt", "/a", "/x")` → `"/x/b/c.txt"`. `filePath === fromDirectory` 면 `toDirectory` 반환. **filePath 가 fromDirectory 내부가 아니면 `ArgumentError` throw**. src→dist 같은 출력 경로 산출에 사용.
|
|
17
|
+
|
|
18
|
+
## 판정·필터링
|
|
19
|
+
|
|
20
|
+
- `isChildPath(childPath, parentPath): boolean` — `childPath` 가 `parentPath` 의 하위인지. 양쪽을 `posixResolve` 로 정규화 후 비교. **동일 경로면 false**(자기 자신은 하위 아님). 경계 오탐 방지를 위해 parent 끝에 `/` 를 붙여 비교.
|
|
21
|
+
- `filterByTargets(files, targets, cwd): string[]` — 파일 목록을 타겟 경로 하위만 남김.
|
|
22
|
+
- `files: string[]` — 필터링할 파일. **cwd 하위 절대 경로여야 함**(외부 경로는 `../` 상대경로로 변환되어 매칭 실패 가능).
|
|
23
|
+
- `targets: string[]` — 대상 경로(cwd 기준 상대, POSIX 권장). 각 파일의 cwd-상대경로가 target 과 같거나 `target + "/"` 로 시작하면 통과.
|
|
24
|
+
- `cwd: string` — 기준 작업 디렉토리(절대 경로).
|
|
25
|
+
- **`targets` 가 비면 `files` 를 그대로 반환**(필터 미적용). CLI 의 `-t <패키지>` 같은 부분 빌드 대상 한정에 사용.
|
|
26
|
+
|
|
27
|
+
## 파일명
|
|
28
|
+
|
|
29
|
+
- `basenameWithoutExt(filePath: string): string` — 확장자 1단계만 제거한 basename. 예: `"file.txt"` → `"file"`, `"a/file.spec.ts"` → `"file.spec"`(마지막 확장자만 제거).
|
|
30
|
+
|
|
31
|
+
## 사용 예
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { pathx } from "@simplysm/core-node";
|
|
35
|
+
const out = pathx.changeFileDirectory(srcFile, "/proj/src", "/proj/dist");
|
|
36
|
+
if (pathx.isChildPath(out, "/proj")) { /* ... */ }
|
|
37
|
+
const targeted = pathx.filterByTargets(allFiles, ["src", "tests"], process.cwd());
|
|
38
|
+
```
|
|
39
|
+
</content>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @simplysm/core-node — worker
|
|
2
|
+
|
|
3
|
+
`worker_threads` 를 타입 안전하게 쓰기 위한 래퍼. 워커 파일에서 `createWorker(methods)` 로 메서드 묶음을 export 하고, 메인에서 `Worker.create<typeof import("./worker")>(path)` 로 프록시를 만들어 `await worker.method(...)` 처럼 호출. 메시지 직렬화는 `@simplysm/core-common` 의 `transfer`(Date/특수타입·transferList 지원)를 사용한다. 개발(.ts)·프로덕션(.js) 양쪽을 자동 분기한다.
|
|
4
|
+
|
|
5
|
+
## createWorker (워커 스레드 측)
|
|
6
|
+
|
|
7
|
+
- `createWorker<TMethods, TEvents>(methods): { send; __methods; __events }` — 워커 스레드 진입 파일에서 호출하고 그 반환을 `export default`. `parentPort` 가 없으면(워커 컨텍스트 아님) `SdError` throw. 메서드 호출 메시지를 수신해 실행 후 결과/에러를 응답하고, 워커의 `process.stdout.write` 를 가로채 메인으로 로그를 전달한다.
|
|
8
|
+
- `methods: TMethods` (`Record<string, (...args) => unknown>`) — 워커가 제공할 메서드 맵. 동기/비동기 모두 가능(await 됨). 알 수 없는 메서드 호출 시 `SdError("알 수 없는 메서드: ...")` 응답.
|
|
9
|
+
- 제네릭 `TEvents extends Record<string, unknown>` — 워커가 보낼 이벤트명→데이터 타입 맵(메인의 `on` 타입 추론에 사용).
|
|
10
|
+
- 반환 `send<K extends keyof TEvents & string>(event, data?)` — 워커→메인 이벤트 전송. 진행률 등 메서드 반환과 별개 통지에 사용.
|
|
11
|
+
- 반환 `__methods`/`__events` — 타입 추론용 마커(런타임에 `Worker.create` 가 직접 쓰진 않음, 타입에서만 참조).
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// worker.ts
|
|
15
|
+
interface MyEvents { progress: number; }
|
|
16
|
+
const methods = {
|
|
17
|
+
calc: (x: number) => { sender.send("progress", 50); return x * 2; },
|
|
18
|
+
};
|
|
19
|
+
const sender = createWorker<typeof methods, MyEvents>(methods);
|
|
20
|
+
export default sender;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Worker.create (메인 측)
|
|
24
|
+
|
|
25
|
+
- `Worker.create<TModule extends WorkerModule>(filePath, opt?): WorkerProxy<TModule>` — 워커 스레드를 띄우고 메서드 프록시를 반환. 메서드 호출은 메시지로 전달되어 결과가 Promise 로 resolve/reject 된다. 워커 stdout/stderr 는 메인 프로세스로 파이프되며, 워커 비정상 종료(exit code≠0)·error 시 대기 중인 모든 호출이 reject 된다. 로거 태그 `sd-worker`.
|
|
26
|
+
- `filePath: string` — 워커 파일 경로. `file://` URL 또는 절대 경로. 확장자가 `.ts` 면 dev 모드로 `lib/worker-dev-proxy.js`(tsx 로 TS 동적 로드)를 통해 실행, `.js` 면 직접 실행.
|
|
27
|
+
- `opt?: Omit<WorkerRawOptions, "stdout" | "stderr">` — worker_threads 옵션(stdout/stderr 는 내부 고정이라 제외). `env` 는 `process.env` 와 병합 전달, `argv` 는 dev 모드에서 워커 경로 뒤에 이어 붙음.
|
|
28
|
+
- 반환 `WorkerProxy<TModule>` — 아래.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// main.ts
|
|
32
|
+
const worker = Worker.create<typeof import("./worker")>("./worker.ts");
|
|
33
|
+
worker.on("progress", (p) => console.log(p));
|
|
34
|
+
const result = await worker.calc(10); // 20
|
|
35
|
+
await worker.terminate();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## WorkerProxy
|
|
39
|
+
|
|
40
|
+
`Worker.create` 반환 프록시. 워커 메서드 + 예약 메서드 3종을 제공.
|
|
41
|
+
|
|
42
|
+
- 메서드 프록시: `TModule["default"]["__methods"]` 의 각 메서드가 `(...args) => Promise<Awaited<R>>` 로 노출(`PromisifyMethods`). 동기 메서드도 항상 Promise(postMessage 기반).
|
|
43
|
+
- `on<TEventName>(event, listener): void` — 워커 `send` 이벤트 구독. `event`/`listener` 타입은 `TEvents` 에서 추론.
|
|
44
|
+
- `off<TEventName>(event, listener): void` — 이벤트 구독 해제.
|
|
45
|
+
- `terminate(): Promise<void>` — 워커 종료. 대기 중 호출은 "워커가 종료되었습니다" 로 reject 후 스레드 종료.
|
|
46
|
+
|
|
47
|
+
## 타입
|
|
48
|
+
|
|
49
|
+
- `interface WorkerModule { default: { __methods: Record<string, (...args) => unknown>; __events: Record<string, unknown> } }` — `Worker.create` 의 제네릭 제약. `typeof import("./worker")` 가 이 구조를 만족(=`createWorker` 반환을 default export).
|
|
50
|
+
- `type PromisifyMethods<TMethods>` — 각 메서드 반환을 `Promise<Awaited<R>>` 로 바꾸는 매핑 타입.
|
|
51
|
+
- `type WorkerProxy<TModule>` — 위 프록시 타입.
|
|
52
|
+
- `interface WorkerRequest { id; method; params }` / `type WorkerResponse` — 내부 메시지 프로토콜 타입(`return`/`error`/`event`/`log`). 직접 다룰 일은 거의 없음.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @simplysm/excel
|
|
2
|
+
|
|
3
|
+
OOXML(.xlsx) 워크북을 ZIP 단위 lazy-load 로 읽고 쓰는 라이브러리. 대용량 파일에서도 접근한 셀에 필요한 XML 파트(SharedStrings/Styles 등)만 그때그때 로드한다(모든 셀 메서드가 `async` 인 이유). `ExcelWorkbook` 진입점에서 시트 추가·셀 값/수식·스타일·조건부 서식·이미지·행 복사를 다루며, Zod 스키마 기반 `ExcelWrapper` 로 레코드 배열 ↔ 엑셀 변환도 지원한다.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **ExcelWorkbook / ExcelWorksheet** — .xlsx 파일을 열거나 새로 만들고 시트를 다루며 바이트/Blob 로 내보낼 때. 시트 단위 데이터 테이블·매트릭스·행 복사·이미지·뷰(zoom/freeze/탭색) 처리 포함. 자세히: [workbook-worksheet.md](./workbook-worksheet.md)
|
|
8
|
+
- **ExcelCell / ExcelRow / ExcelCol** — 개별 셀의 값·수식·병합·스타일을 읽고 쓰거나 행/열 단위로 셀을 순회하고 열 너비를 줄 때. 자세히: [cell.md](./cell.md)
|
|
9
|
+
- **셀 스타일 (ExcelStyleOptions / ExcelFont)** — 셀(`cell.setStyle`) 또는 워크북 default(`wb.setDefaultStyle`)의 배경·테두리·정렬·숫자형식·폰트를 지정할 때. 자세히: [style.md](./style.md)
|
|
10
|
+
- **조건부 서식 (ExcelConditionalRule / ExcelConditionalRuleStyle)** — 셀/범위에 값 비교·텍스트 매칭·수식 기반 native CF 규칙을 추가할 때. 자세히: [conditional-format.md](./conditional-format.md)
|
|
11
|
+
- **ExcelWrapper** — Zod 스키마로 헤더 매핑·타입 변환·유효성 검사를 자동화해 레코드 배열 ↔ 엑셀을 변환할 때. 자세히: [wrapper.md](./wrapper.md)
|
|
12
|
+
- **ExcelUtils** — 셀 주소(A1 ↔ 좌표) 변환, 엑셀 날짜 시리얼 ↔ 타임스탬프 변환, 숫자형식 코드/ID/이름 상호 변환이 필요할 때. 자세히: [utils.md](./utils.md)
|
|
13
|
+
- **값/주소/형식 타입** — 셀 값 유니온·숫자형식 프리셋·셀 타입·주소 좌표·정렬/테두리 enum 을 시그니처에서 참조할 때. 아래 인라인 섹션 참조.
|
|
14
|
+
- **OOXML XML-shape 타입** — `ExcelXml*` / `Excel*Data` 류. 라이브러리 내부 XML 파서/직렬화기가 쓰는 OOXML 노드 구조 타입. 아래 인라인 섹션 참조.
|
|
15
|
+
|
|
16
|
+
## 값/주소/형식 타입
|
|
17
|
+
|
|
18
|
+
`./types` 가 노출하는 사용자 대면 타입. 셀 값을 다루거나 메서드 시그니처를 해석할 때 참조.
|
|
19
|
+
|
|
20
|
+
- `ExcelValueType` = `number | string | DateOnly | DateTime | Time | boolean | undefined` — 셀이 가질 수 있는 값 유니온. `getValue()` 반환·`setValue()` 인자 타입. `undefined` = "값 없음"(읽기 시 빈 셀, 쓰기 시 셀 삭제)이므로 결측을 끝까지 보존. `DateOnly`/`DateTime`/`Time` 은 `@simplysm/core-common` 타입.
|
|
21
|
+
- `ExcelNumberFormat` = `"number" | "string" | "DateOnly" | "DateTime" | "Time"` — 숫자형식 프리셋 이름. `"number"` = 일반 수치, `"string"` = 텍스트 형식, 나머지는 날짜/시간 시리얼 해석/표시에 사용. `ExcelStyleOptions.numberFormat` 와 `ExcelUtils` 변환의 공용 단위.
|
|
22
|
+
- `ExcelCellType` = `"s" | "b" | "str" | "n" | "inlineStr" | "e"` — OOXML 셀 `t` 속성. `"s"` = SharedString 인덱스 참조, `"b"` = boolean(`"1"`/`"0"`), `"str"` = 수식 결과 문자열, `"n"` = 숫자, `"inlineStr"` = 인라인 서식 텍스트, `"e"` = 에러(읽기 시 throw). 보통 직접 다루지 않고 `getValue`/`setValue` 가 자동 매핑.
|
|
23
|
+
- `ExcelAddressPoint` = `{ r: number; c: number }` — 0 기반 행(`r`)·열(`c`) 좌표. 셀 단일 위치 단위.
|
|
24
|
+
- `ExcelAddressRangePoint` = `{ s: ExcelAddressPoint; e: ExcelAddressPoint }` — 범위 좌표. `s` = 시작(좌상단), `e` = 끝(우하단). `getRange()` 반환 타입.
|
|
25
|
+
- `ExcelBorderPosition` = `"left" | "right" | "top" | "bottom"` — 테두리 적용 변. `ExcelStyleOptions.border` 배열 원소.
|
|
26
|
+
- `ExcelHorizontalAlign` = `"center" | "left" | "right"` — 가로 정렬 값.
|
|
27
|
+
- `ExcelVerticalAlign` = `"center" | "top" | "bottom"` — 세로 정렬 값.
|
|
28
|
+
- `ExcelFontUnderline` = `"single" | "double" | "singleAccounting" | "doubleAccounting"` — 밑줄 종류. OOXML `<u val="...">` 의 val 에 그대로 매핑.
|
|
29
|
+
|
|
30
|
+
## OOXML XML-shape 타입
|
|
31
|
+
|
|
32
|
+
`./types` 가 함께 export 하는 다음 인터페이스·타입은 라이브러리 내부 XML 파서/직렬화기가 OOXML 파트의 파싱 결과(`xml2js` 스타일의 `$`=속성, 자식 배열 래핑)를 표현하는 데이터 모델이다. 일반 사용 흐름(값/스타일/시트 API)에서는 직접 다루지 않으며, OOXML 노드를 직접 조작·검증할 때만 참조한다.
|
|
33
|
+
|
|
34
|
+
- `ExcelXmlContentTypeData` — `[Content_Types].xml` 의 `Types`(Default/Override 파트 등록) 구조.
|
|
35
|
+
- `ExcelXmlRelationshipData` / `ExcelRelationshipData` — `*.rels` 의 `Relationships` 및 개별 `Relationship`(Id/Target/Type) 구조.
|
|
36
|
+
- `ExcelXmlWorkbookData` — `xl/workbook.xml` 의 `workbook`(bookViews/sheets 등) 구조.
|
|
37
|
+
- `ExcelXmlWorksheetData` — `xl/worksheets/sheetN.xml` 의 `worksheet`(sheetPr/dimension/sheetViews/cols/sheetData/mergeCells/conditionalFormatting/drawing 등) 구조.
|
|
38
|
+
- `ExcelXmlConditionalFormattingData` / `ExcelXmlCfRuleData` — `<conditionalFormatting>` 블록과 `<cfRule>`(type/operator/priority/dxfId/formula) 구조. `ExcelXmlCfRuleData["$"]["type"|"operator"]` 는 규칙 spec 빌드 시 내부적으로 참조된다.
|
|
39
|
+
- `ExcelRowData` / `ExcelCellData` — `<row r="..">` 과 `<c r=".." s=".." t="..">`(v=값, f=수식, is=인라인 문자열) 구조.
|
|
40
|
+
- `ExcelXmlDrawingData` — `xl/drawings/drawingN.xml` 의 `wsDr`(twoCellAnchor/pic/blipFill/spPr 등 이미지 앵커) 구조.
|
|
41
|
+
- `ExcelXmlSharedStringData` / `ExcelXmlSharedStringDataSi` / `ExcelXmlSharedStringDataText` — `xl/sharedStrings.xml` 의 `sst` 와 `si` 항목(단순 `t` 또는 서식 run `r[]`), 텍스트 노드(`space="preserve"` 보존) 구조.
|
|
42
|
+
- `ExcelXmlStyleData` 및 하위(`...Font` / `...Fill` / `...Border` / `...Xf` / `...Dxf`) — `xl/styles.xml` 의 `styleSheet`(numFmts/fonts/fills/borders/cellXfs/dxfs) 와 각 자원 노드 구조.
|
|
43
|
+
- `ExcelXml` = `{ readonly data: unknown; cleanup(): void }` — 모든 XML 파트 래퍼가 따르는 공통 인터페이스. `data` = 파싱된 노드 트리, `cleanup()` = 직렬화 전 마무리 정리. 외부에서 구현할 일은 거의 없다.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @simplysm/excel — ExcelCell / ExcelRow / ExcelCol
|
|
2
|
+
|
|
3
|
+
개별 셀의 값·수식·병합·스타일을 읽고 쓰거나, 행/열 단위로 셀을 순회할 때 함께 읽는 묶음. 모든 셀 I/O 가 `async` 인 이유는 셀 타입별로 필요한 XML 파트(SharedStrings/Styles)만 lazy-load 하기 때문이다. 인스턴스는 `ws.cell(r,c)` / `ws.row(r)` / `ws.col(c)` 로 얻으며 좌표는 모두 0 기반.
|
|
4
|
+
|
|
5
|
+
## ExcelCell
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
readonly addr: ExcelAddressPoint // { r, c } 0 기반
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### 값/수식
|
|
12
|
+
|
|
13
|
+
- `setValue(val: ExcelValueType): Promise<void>` — 셀 값 설정. `val` 타입별 분기: string → SharedString 으로 등록, boolean → `"1"/"0"`, number → 숫자 셀, `DateOnly`/`DateTime`/`Time` → Excel 날짜 시리얼 + 해당 numFmt 스타일 자동 적용, `undefined`/`null` → 셀 삭제. 그 외 타입은 throw.
|
|
14
|
+
- `getValue(): Promise<ExcelValueType>` — 셀 값 반환. 빈 셀이면 `undefined`. 셀 타입·numFmt 를 보고 string/boolean/number/`DateOnly`/`DateTime`/`Time` 로 복원. 셀 타입이 `"e"`(에러)면 throw.
|
|
15
|
+
- `setFormula(val: string | undefined): Promise<void>` — 수식 설정. `undefined` 면 셀 삭제. 설정 시 셀 타입은 `str` 로.
|
|
16
|
+
- `getFormula(): Promise<string | undefined>` — 셀 수식 반환(없으면 `undefined`).
|
|
17
|
+
|
|
18
|
+
### 병합
|
|
19
|
+
|
|
20
|
+
- `merge(r, c): Promise<void>` — 현재 셀을 시작점으로 끝 좌표 `(r, c)`(0 기반)까지 병합. 예: A1 에서 `merge(2, 2)` → A1:C3.
|
|
21
|
+
|
|
22
|
+
### 스타일
|
|
23
|
+
|
|
24
|
+
- `setStyle(opts: ExcelStyleOptions): Promise<void>` — 배경·테두리·정렬·숫자형식·폰트 적용. 기존 스타일이 있으면 clone 후 병합. 자세히: [style.md](./style.md).
|
|
25
|
+
- `getStyleId(): Promise<string | undefined>` — 셀의 스타일 ID(없으면 `undefined`).
|
|
26
|
+
- `setStyleId(styleId: string | undefined): Promise<void>` — 스타일 ID 를 직접 지정/해제. 이미 만들어진 스타일을 재사용할 때.
|
|
27
|
+
|
|
28
|
+
### 사용 예
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
await ws.cell(0, 0).setValue("이름");
|
|
32
|
+
await ws.cell(1, 0).setValue(new DateOnly(2026, 6, 1)); // 날짜 numFmt 자동
|
|
33
|
+
await ws.cell(0, 0).merge(0, 2); // A1:C1 병합
|
|
34
|
+
const v = await ws.cell(1, 0).getValue(); // DateOnly 인스턴스
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## ExcelRow
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
cell(c: number): ExcelCell // 이 행의 c열 셀(0 기반)
|
|
41
|
+
getCells(): Promise<ExcelCell[]> // 데이터 범위 폭만큼 셀 배열(인덱스=열)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- `getCells` 는 시트 데이터 범위의 시작~끝 열까지 셀을 채우며, 배열 인덱스가 열 번호와 일치(앞쪽 빈 열은 sparse).
|
|
45
|
+
|
|
46
|
+
## ExcelCol
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
cell(r: number): ExcelCell // 이 열의 r행 셀(0 기반)
|
|
50
|
+
getCells(): Promise<ExcelCell[]> // 데이터 범위 높이만큼 셀 배열(인덱스=행)
|
|
51
|
+
setWidth(size: number): Promise<void> // 열 너비 설정
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `setWidth` 의 `size` — Excel 열 너비 단위(문자 폭 기준). 컬럼 폭을 데이터에 맞게 넓힐 때.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @simplysm/excel — 조건부 서식
|
|
2
|
+
|
|
3
|
+
셀/범위에 값 비교·텍스트 매칭·수식 기반의 native CF(Excel 조건부 서식) 규칙을 추가할 때 함께 읽는 묶음. `ws.addConditionalFormat` 에 `ExcelConditionalRule[]` 을 넘기며, 각 규칙은 `ExcelConditionalRuleStyle` 강조 스타일을 가진다.
|
|
4
|
+
|
|
5
|
+
## ws.addConditionalFormat
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
addConditionalFormat(opts: { ref: string; rules: ExcelConditionalRule[] }): Promise<void>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- `opts.ref: string` — 단일 셀(`"A1"`) 또는 범위(`"A1:B10"`) Excel 주소. 규칙 수식은 범위의 좌상단 셀 기준으로 생성된다.
|
|
12
|
+
- `opts.rules: ExcelConditionalRule[]` — 적용할 규칙 배열. 배열 순서가 priority(앞이 우선)이며, 호출 간에는 시트 전역 카운터로 이어붙는다. 빈 배열이면 no-op.
|
|
13
|
+
- 같은 시트에 여러 번 호출하면 호출마다 `<conditionalFormatting>` 블록이 누적된다. 정적 셀 스타일과의 합성은 Excel native CF 오버레이에 위임.
|
|
14
|
+
|
|
15
|
+
## ExcelConditionalRule
|
|
16
|
+
|
|
17
|
+
4개 변형의 유니온. 모든 변형이 `style: ExcelConditionalRuleStyle` 를 가진다.
|
|
18
|
+
|
|
19
|
+
- `{ type: "cellIs"; op: "<" | ">" | "<=" | ">=" | "=" | "<>"; value: number | string; style }` — 단일 값 비교. `op` = 비교 연산자, `value` = 비교 대상(숫자는 raw formula, 문자열은 따옴표 리터럴 formula 로 emit).
|
|
20
|
+
- `{ type: "cellIs"; op: "between" | "notBetween"; value: [number, number] | [string, string]; style }` — 구간 비교. `value` = `[a, b]` 튜플(양 끝 inclusive). `"between"` = 구간 안, `"notBetween"` = 구간 밖.
|
|
21
|
+
- `{ type: "text"; op: "contains" | "notContains" | "beginsWith" | "endsWith"; value: string; style }` — 텍스트 매칭. `value` = 비교 문자열. `"contains"`/`"notContains"` = 포함/미포함, `"beginsWith"`/`"endsWith"` = 시작/끝 일치. SEARCH 기반 대소문자 무시 고정.
|
|
22
|
+
- `{ type: "expression"; formula: string; style }` — 임의 수식 규칙. `formula` = Excel 수식 문자열(true 면 강조). 프리셋으로 표현 못하는 조건일 때.
|
|
23
|
+
|
|
24
|
+
## ExcelConditionalRuleStyle
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface ExcelConditionalRuleStyle {
|
|
28
|
+
background?: string;
|
|
29
|
+
fontColor?: string;
|
|
30
|
+
fontWeight?: "bold" | "normal";
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `background?: string` — 강조 배경색. ARGB 8자리(예: `"00FFFF00"`).
|
|
35
|
+
- `fontColor?: string` — 강조 글자색. ARGB 8자리.
|
|
36
|
+
- `fontWeight?: "bold" | "normal"` — 글자 굵기. `"bold"` = 굵게, `"normal"` = base 가 bold 라도 강제 normal.
|
|
37
|
+
|
|
38
|
+
미지정 필드는 base 셀 스타일을 그대로 두고, 지정 필드만 OOXML dxf 로 emit 되어 native CF 오버레이로 합성된다.
|
|
39
|
+
|
|
40
|
+
## 사용 예
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
await ws.addConditionalFormat({
|
|
44
|
+
ref: "B2:B100",
|
|
45
|
+
rules: [
|
|
46
|
+
{ type: "cellIs", op: "<", value: 1000, style: { background: "00FF0000" } },
|
|
47
|
+
{ type: "cellIs", op: "between", value: [1000, 4999], style: { background: "00FFFF00" } },
|
|
48
|
+
{ type: "text", op: "contains", value: "긴급", style: { fontColor: "00FF0000", fontWeight: "bold" } },
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @simplysm/excel — 셀 스타일
|
|
2
|
+
|
|
3
|
+
셀(`cell.setStyle`) 또는 워크북 default(`wb.setDefaultStyle`)의 배경·테두리·정렬·숫자형식·폰트를 지정할 때 함께 읽는 묶음. 두 API 모두 `ExcelStyleOptions` 를 받으며 `font` 는 `ExcelFont` 를 공유한다.
|
|
4
|
+
|
|
5
|
+
## ExcelStyleOptions
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface ExcelStyleOptions {
|
|
9
|
+
background?: string;
|
|
10
|
+
border?: ExcelBorderPosition[];
|
|
11
|
+
horizontalAlign?: ExcelHorizontalAlign;
|
|
12
|
+
verticalAlign?: ExcelVerticalAlign;
|
|
13
|
+
numberFormat?: ExcelNumberFormat;
|
|
14
|
+
numberFormatCode?: string;
|
|
15
|
+
font?: ExcelFont;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `background?: string` — 배경색. ARGB 8자리 16진수(예: `"00FF0000"` = 빨강). 셀 채우기 색이 필요할 때.
|
|
20
|
+
- `border?: ExcelBorderPosition[]` — 테두리를 그릴 변 배열. 원소 = `"left" | "right" | "top" | "bottom"`. 4변 모두면 `["left","right","top","bottom"]`.
|
|
21
|
+
- `horizontalAlign?: "center" | "left" | "right"` — 가로 정렬.
|
|
22
|
+
- `verticalAlign?: "center" | "top" | "bottom"` — 세로 정렬.
|
|
23
|
+
- `numberFormat?: "number" | "string" | "DateOnly" | "DateTime" | "Time"` — 숫자형식 프리셋. `"number"` = 일반 수치, `"string"` = 텍스트 형식, 나머지는 날짜/시간 표시 형식. 표준 형식 적용 시 사용.
|
|
24
|
+
- `numberFormatCode?: string` — 커스텀 Excel formatCode(예: `"0.000000"`, `"#,##0.00"`, `"0.00%"`). `numberFormat` 과 동시 지정 시 **이 필드가 우선**. 프리셋에 없는 세밀한 형식이 필요할 때.
|
|
25
|
+
- `font?: ExcelFont` — 폰트 묶음(아래). 미지정 속성은 워크북 default 폰트로 표시.
|
|
26
|
+
|
|
27
|
+
## ExcelFont
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface ExcelFont {
|
|
31
|
+
size?: number;
|
|
32
|
+
family?: string;
|
|
33
|
+
bold?: boolean;
|
|
34
|
+
italic?: boolean;
|
|
35
|
+
underline?: ExcelFontUnderline;
|
|
36
|
+
color?: string;
|
|
37
|
+
strike?: boolean;
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `size?: number` — 폰트 크기(pt).
|
|
42
|
+
- `family?: string` — 폰트명(예: `"맑은 고딕"`, `"Calibri"`).
|
|
43
|
+
- `bold?: boolean` — 굵게. `true` 면 굵게.
|
|
44
|
+
- `italic?: boolean` — 기울임. `true` 면 이탤릭.
|
|
45
|
+
- `underline?: "single" | "double" | "singleAccounting" | "doubleAccounting"` — 밑줄 종류. OOXML `<u val="...">` val 에 그대로 매핑.
|
|
46
|
+
- `color?: string` — 글자색. ARGB 8자리(예: `"00FF0000"`).
|
|
47
|
+
- `strike?: boolean` — 취소선. `true` 면 가운데줄.
|
|
48
|
+
|
|
49
|
+
미지정 폰트 속성은 OOXML `<font>` 자식으로 emit 되지 않고 Excel 기본값으로 표시된다.
|
|
50
|
+
|
|
51
|
+
## cell.setStyle vs wb.setDefaultStyle
|
|
52
|
+
|
|
53
|
+
- `cell.setStyle(opts)` — 해당 셀에만 스타일 적용. 기존 셀 스타일이 있으면 clone 후 옵션을 병합.
|
|
54
|
+
- `wb.setDefaultStyle(opts)` — `xl/styles.xml` 의 `fonts[0]`/`fills[0]`/`borders[0]`(OOXML default 슬롯) 자체를 덮어써, fontId/fillId/borderId 를 명시하지 않은 모든 셀에 전역 적용. `horizontalAlign`/`verticalAlign`/`numberFormat`/`numberFormatCode` 는 0번 슬롯 개념이 없어 `cellXfs[0]` 에 박힌다. 옵션이 없는 자원은 0번 슬롯이 빈 슬롯으로 reset 되며, 미호출 시 원본이 보존된다.
|
|
55
|
+
|
|
56
|
+
### 사용 예
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
await wb.setDefaultStyle({ font: { family: "맑은 고딕", size: 10 }, horizontalAlign: "center" });
|
|
60
|
+
|
|
61
|
+
await ws.cell(0, 0).setStyle({
|
|
62
|
+
background: "00FFFF00",
|
|
63
|
+
border: ["left", "right", "top", "bottom"],
|
|
64
|
+
font: { bold: true, color: "00FF0000" },
|
|
65
|
+
numberFormatCode: "#,##0",
|
|
66
|
+
});
|
|
67
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @simplysm/excel — ExcelUtils
|
|
2
|
+
|
|
3
|
+
셀 주소(A1 표기) ↔ 좌표 변환, Excel 날짜 시리얼 ↔ 타임스탬프 변환, 숫자형식 코드/ID/이름 상호 변환이 필요할 때 읽는다. 모든 메서드는 `static` 이라 인스턴스 없이 `ExcelUtils.xxx()` 로 호출.
|
|
4
|
+
|
|
5
|
+
## 주소 변환
|
|
6
|
+
|
|
7
|
+
- `stringifyAddr(point: ExcelAddressPoint): string` — 0 기반 좌표 `{r,c}` → `"A1"`. 로그·범위 ref 조립에 사용.
|
|
8
|
+
- `stringifyRowAddr(r: number): string` — 행 인덱스 → 행 문자열(0 → `"1"`).
|
|
9
|
+
- `stringifyColAddr(c: number): string` — 열 인덱스 → 열 문자(0 → `"A"`, 26 → `"AA"`). 0~16383 범위 밖이면 throw.
|
|
10
|
+
- `parseRowAddr(addr: string): number` — 주소 문자열에서 0 기반 행 인덱스(`"A3"` → 2). 파싱 실패 시 throw.
|
|
11
|
+
- `parseColAddr(addr: string): number` — 주소 문자열에서 0 기반 열 인덱스(`"B3"` → 1).
|
|
12
|
+
- `parseCellAddr(addr: string): ExcelAddressPoint` — 셀 주소 → 좌표(`"B3"` → `{r:2,c:1}`).
|
|
13
|
+
- `parseRangeAddr(rangeAddr: string): ExcelAddressRangePoint` — 범위 주소 → 좌표 범위(`"A1:C3"` → `{s:{r:0,c:0}, e:{r:2,c:2}}`). `:` 없으면 단일 셀을 `s===e` 로.
|
|
14
|
+
- `stringifyRangeAddr(point: ExcelAddressRangePoint): string` — 좌표 범위 → 문자열(`"A1:C3"`). `s===e` 면 단일 셀 문자열만 반환.
|
|
15
|
+
|
|
16
|
+
## 날짜 변환
|
|
17
|
+
|
|
18
|
+
Excel 은 1900-01-01 을 1 로 계산(1899-12-30 이 날짜 0). 변환 시 로컬 타임존을 보정한다.
|
|
19
|
+
|
|
20
|
+
- `convertTimeTickToNumber(tick: number): number` — JS 타임스탬프(ms) → Excel 날짜 시리얼 숫자.
|
|
21
|
+
- `convertNumberToTimeTick(value: number): number` — Excel 날짜 시리얼 숫자 → JS 타임스탬프(ms).
|
|
22
|
+
|
|
23
|
+
## 숫자형식 변환
|
|
24
|
+
|
|
25
|
+
- `convertNumFmtCodeToName(numFmtCode: string): ExcelNumberFormat` — formatCode 문자열을 분석해 `"number"`/`"DateOnly"`/`"DateTime"`/`"Time"`/`"string"` 판별. `"General"` 은 `"number"`. 시간 문맥의 `mm`(분)은 날짜 판별에서 제외. 어디에도 해당 안 되면 throw.
|
|
26
|
+
- `convertNumFmtIdToName(numFmtId: number): ExcelNumberFormat` — Excel 내장 numFmtId 를 형식명으로. 범위: 0~13·37~40·48 → `"number"`, 14~17·27~31·34~36·50~58 → `"DateOnly"`, 22 → `"DateTime"`, 18~21·32~33·45~47 → `"Time"`, 49 → `"string"`. 범위 밖이면 throw.
|
|
27
|
+
- `convertNumFmtNameToId(numFmtName: ExcelNumberFormat): number` — 역방향. `"number"` → 0, `"DateOnly"` → 14, `"DateTime"` → 22, `"Time"` → 18, `"string"` → 49.
|
|
28
|
+
|
|
29
|
+
## 사용 예
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
const { r, c } = ExcelUtils.parseCellAddr("C5"); // { r: 4, c: 2 }
|
|
33
|
+
const ref = ExcelUtils.stringifyRangeAddr({ s: { r: 0, c: 0 }, e: { r: 9, c: 1 } }); // "A1:B10"
|
|
34
|
+
const serial = ExcelUtils.convertTimeTickToNumber(Date.now()); // Excel 날짜 숫자
|
|
35
|
+
```
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @simplysm/excel — ExcelWorkbook / ExcelWorksheet
|
|
2
|
+
|
|
3
|
+
.xlsx 파일을 열거나 새로 만들고, 시트를 추가·조회하고, 시트 단위로 데이터 테이블/매트릭스/이미지/뷰를 다루고, 바이트/Blob 로 내보낼 때 함께 읽는 묶음. 워크북은 내부 ZIP 리소스를 lazy-load 하므로 사용 후 반드시 `close()` 해야 한다.
|
|
4
|
+
|
|
5
|
+
## ExcelWorkbook
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
new ExcelWorkbook(arg?: Blob | Bytes)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- 생성자 `arg` — 기존 .xlsx 데이터(`Blob` 또는 `Uint8Array`). 생략하면 빈 워크북(ContentTypes/rels/workbook 골격)을 새로 만든다. 기존 파일 편집이면 전달, 새 파일 생성이면 생략.
|
|
12
|
+
|
|
13
|
+
메서드:
|
|
14
|
+
|
|
15
|
+
- `getWorksheetNames(): Promise<string[]>` — 워크북의 모든 시트 이름을 정의 순서로 반환.
|
|
16
|
+
- `addWorksheet(name: string): Promise<ExcelWorksheet>` — 새 시트를 만들어 반환. ContentTypes·workbook rels 도 함께 갱신. `name` = 추가할 시트 이름.
|
|
17
|
+
- `getWorksheet(nameOrIndex: string | number): Promise<ExcelWorksheet>` — 이름(string) 또는 0 기반 인덱스(number)로 시트 조회. 같은 시트는 캐시돼 동일 인스턴스 반환. 없으면 throw.
|
|
18
|
+
- `setDefaultStyle(opts: ExcelStyleOptions): Promise<void>` — 워크북 전역 default 셀 스타일. fontId/fillId/borderId 를 명시하지 않은 모든 셀에 적용. 자세히: [style.md](./style.md).
|
|
19
|
+
- `toBytes(): Promise<Bytes>` — 워크북을 ZIP 직렬화해 바이트로 반환.
|
|
20
|
+
- `toBlob(): Promise<Blob>` — `toBytes()` 결과를 xlsx MIME 의 `Blob` 으로 래핑. 브라우저 다운로드용.
|
|
21
|
+
- `close(): Promise<void>` — ZIP 리더·내부 캐시 해제. 호출 후 워크북 사용 불가. 이미 닫혔으면 no-op(안전). 미호출 시 리소스 누수.
|
|
22
|
+
- `readonly zipCache: ZipCache` — 내부 ZIP 캐시. 일반 사용에서는 직접 다루지 않음.
|
|
23
|
+
|
|
24
|
+
`close()` 외 모든 메서드는 닫힌 워크북에서 호출 시 throw.
|
|
25
|
+
|
|
26
|
+
### 사용 예
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
const wb = new ExcelWorkbook(bytes); // 기존 파일 열기
|
|
30
|
+
try {
|
|
31
|
+
const ws = await wb.getWorksheet(0);
|
|
32
|
+
const table = await ws.getDataTable({ checkEndColIndex: 0 });
|
|
33
|
+
} finally {
|
|
34
|
+
await wb.close();
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## ExcelWorksheet
|
|
39
|
+
|
|
40
|
+
`wb.getWorksheet` / `wb.addWorksheet` 로 얻는다. 행/열/셀 접근, 복사, 데이터 변환, 뷰, 조건부 서식, 이미지를 제공.
|
|
41
|
+
|
|
42
|
+
### 이름
|
|
43
|
+
|
|
44
|
+
- `getName(): Promise<string>` — 시트 이름 반환(못 찾으면 throw).
|
|
45
|
+
- `setName(newName: string): Promise<void>` — 시트 이름 변경.
|
|
46
|
+
|
|
47
|
+
### 셀/행/열 접근 (모두 0 기반)
|
|
48
|
+
|
|
49
|
+
- `row(r): ExcelRow` — `r` 행 객체(캐시). 행 단위 순회·셀 접근. 자세히: [cell.md](./cell.md).
|
|
50
|
+
- `col(c): ExcelCol` — `c` 열 객체(캐시). 열 단위 순회·너비 설정.
|
|
51
|
+
- `cell(r, c): ExcelCell` — 단일 셀 객체(캐시). 값/수식/스타일/병합.
|
|
52
|
+
- `getRange(): Promise<ExcelAddressRangePoint>` — 시트의 데이터 범위(`{s, e}`).
|
|
53
|
+
- `getCells(): Promise<ExcelCell[][]>` — 데이터 범위 전체를 행 우선 2차원 셀 배열로.
|
|
54
|
+
|
|
55
|
+
### 복사
|
|
56
|
+
|
|
57
|
+
- `copyCellStyle(srcAddr, targetAddr): Promise<void>` — 셀 스타일 ID 만 복사(값 미복사). `srcAddr`/`targetAddr` = `ExcelAddressPoint`.
|
|
58
|
+
- `copyRowStyle(srcR, targetR): Promise<void>` — 데이터 범위 폭만큼 한 행의 셀 스타일을 다른 행에 복사.
|
|
59
|
+
- `copyCell(srcAddr, targetAddr): Promise<void>` — 셀 전체(값·수식·스타일) 복사.
|
|
60
|
+
- `copyRow(srcR, targetR): Promise<void>` — 한 행을 다른 행으로 복사(대상 덮어쓰기).
|
|
61
|
+
- `insertCopyRow(srcR, targetR): Promise<void>` — `srcR` 행을 `targetR` 위치에 삽입 복사. `targetR` 이하 기존 행은 한 칸 아래로 밀리고, 삽입 지점을 관통하는 다중행 병합은 1행 확장된다. 행 추가 삽입(기존 보존)이 필요할 때 `copyRow`(덮어쓰기) 대신 사용.
|
|
62
|
+
|
|
63
|
+
### 데이터 변환
|
|
64
|
+
|
|
65
|
+
- `getDataTable(opt?): Promise<Record<string, ExcelValueType>[]>` — 헤더 행을 키로, 이후 행을 레코드로 변환.
|
|
66
|
+
- `opt.headerRowIndex?: number` — 헤더로 쓸 행 인덱스. 미지정 시 데이터 범위 첫 행.
|
|
67
|
+
- `opt.checkEndColIndex?: number` — 데이터 끝 판정 열. 이 열이 비면 그 행에서 중단. 빈 행 뒤 잡음 데이터를 끊을 때 지정.
|
|
68
|
+
- `opt.usableHeaderNameFn?: (headerName: string) => boolean` — `true` 반환한 헤더만 컬럼으로 채택. 일부 컬럼만 읽을 때.
|
|
69
|
+
- 헤더 문자열이 중복되면 throw.
|
|
70
|
+
- `setDataMatrix(matrix: ExcelValueType[][]): Promise<void>` — 0행 0열부터 행 우선으로 2차원 배열을 그대로 기록.
|
|
71
|
+
- `setRecords(records: Record<string, ExcelValueType>[]): Promise<void>` — 0행에 헤더(전 레코드 키의 distinct, 빈 키 제외) 자동 생성 후 1행부터 값 기록.
|
|
72
|
+
|
|
73
|
+
### 뷰
|
|
74
|
+
|
|
75
|
+
- `setTabColor(color): Promise<void>` — 시트 탭 색. `color` = ARGB 8자리(예: `"00FF0000"`).
|
|
76
|
+
- `setZoom(percent): Promise<void>` — 확대/축소 비율(퍼센트).
|
|
77
|
+
- `freezeAt(point: { r?: number; c?: number }): Promise<void>` — 틀 고정. `r` = 이 행 위에서 고정, `c` = 이 열 왼쪽에서 고정. 둘 다/하나만 지정 가능.
|
|
78
|
+
|
|
79
|
+
### 조건부 서식 / 이미지
|
|
80
|
+
|
|
81
|
+
- `addConditionalFormat(opts): Promise<void>` — 셀/범위에 native CF 규칙 추가. 자세히: [conditional-format.md](./conditional-format.md).
|
|
82
|
+
- `addImage(opts): Promise<void>` — 이미지 삽입.
|
|
83
|
+
- `opts.bytes: Bytes` — 이미지 바이너리.
|
|
84
|
+
- `opts.ext: string` — 확장자(png/jpg 등). MIME 결정 불가 시 throw.
|
|
85
|
+
- `opts.from: { r, c, rOff?, cOff? }` — 시작 앵커(0 기반 행/열, `rOff`/`cOff` 는 EMU 오프셋).
|
|
86
|
+
- `opts.to?: { r, c, rOff?, cOff? }` — 끝 앵커. 생략 시 `from` 기준 1행·1열 크기로 배치.
|
|
87
|
+
- 같은 시트의 기존 drawing 이 있으면 재사용하고 없으면 새로 만든다.
|
|
88
|
+
|
|
89
|
+
### 사용 예
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const ws = await wb.addWorksheet("매출");
|
|
93
|
+
await ws.setRecords([{ 품목: "사과", 수량: 10 }]);
|
|
94
|
+
await ws.setZoom(85);
|
|
95
|
+
await ws.freezeAt({ r: 0 });
|
|
96
|
+
await ws.addImage({ bytes, ext: "png", from: { r: 0, c: 3 } });
|
|
97
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @simplysm/excel — ExcelWrapper
|
|
2
|
+
|
|
3
|
+
Zod 스키마 1개로 레코드 배열 ↔ Excel 파일을 타입 안전하게 매핑할 때 읽는다. 스키마 각 필드의 `.describe()` 가 Excel 헤더(표시명)가 되고, 필드 타입으로 읽기 시 값 변환·검증을 수행한다. 저수준 셀 조작 없이 "정형 데이터의 import/export" 용도일 때 사용.
|
|
4
|
+
|
|
5
|
+
## 시그니처
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
new ExcelWrapper<TSchema extends z.ZodObject<z.ZodRawShape>>(schema: TSchema)
|
|
9
|
+
|
|
10
|
+
read(
|
|
11
|
+
file: Bytes | Blob,
|
|
12
|
+
wsNameOrIndex: string | number = 0,
|
|
13
|
+
options?: { excludes?: (keyof z.infer<TSchema>)[] },
|
|
14
|
+
): Promise<z.infer<TSchema>[]>
|
|
15
|
+
|
|
16
|
+
write(
|
|
17
|
+
wsName: string,
|
|
18
|
+
records: Partial<z.infer<TSchema>>[],
|
|
19
|
+
options?: { excludes?: (keyof z.infer<TSchema>)[] },
|
|
20
|
+
): Promise<ExcelWorkbook>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- `schema: z.ZodObject` — 레코드 구조. 각 필드의 `.describe("표시명")` 이 Excel 헤더명. 미지정 시 필드 키를 헤더로 사용.
|
|
24
|
+
- `read.file: Bytes | Blob` — 읽을 .xlsx 데이터.
|
|
25
|
+
- `read.wsNameOrIndex: string | number` — 대상 시트(기본 0번). 이름 또는 0 기반 인덱스.
|
|
26
|
+
- `read.options.excludes?: (keyof ...)[]` — 매핑에서 제외할 필드 키 목록.
|
|
27
|
+
- `write.wsName: string` — 생성할 시트 이름.
|
|
28
|
+
- `write.records: Partial<...>[]` — 기록할 부분 레코드 배열. 누락 필드는 빈 셀.
|
|
29
|
+
- `write.options.excludes?: (keyof ...)[]` — 출력에서 제외할 필드 키 목록.
|
|
30
|
+
|
|
31
|
+
## read 동작
|
|
32
|
+
|
|
33
|
+
- 헤더명↔필드 역매핑 후 `ws.getDataTable({ usableHeaderNameFn })` 로 표시명에 일치하는 컬럼만 추출.
|
|
34
|
+
- 각 셀 값을 필드 타입별로 변환 후, 행마다 `schema.safeParse` 로 검증. 실패하면 그 행에서 throw(부분 반영 없음).
|
|
35
|
+
- 모든 필드가 null/`""` 인 행은 skip.
|
|
36
|
+
- 데이터가 한 건도 없으면 기대 헤더 목록을 담은 메시지로 throw.
|
|
37
|
+
- 내부에서 워크북을 열고 `finally` 로 `close()` 하므로 호출자 정리 불필요.
|
|
38
|
+
|
|
39
|
+
### 값 변환 규칙
|
|
40
|
+
|
|
41
|
+
- 빈값(null/`""`) → 스키마 기본값: `ZodDefault` 면 그 기본값, optional/nullable 이면 `undefined`, 필수 boolean 이면 `false`, 그 외 `undefined`.
|
|
42
|
+
- `ZodString` → 문자열(아니면 `String()` 캐스팅).
|
|
43
|
+
- `ZodNumber` → number(문자열은 `num.parseFloat`).
|
|
44
|
+
- `ZodBoolean` → `"1"`/`"true"` → `true`, `"0"`/`"false"` → `false`, 그 외 `Boolean()`.
|
|
45
|
+
- `DateOnly`/`DateTime`/`Time` 인스턴스는 그대로 통과.
|
|
46
|
+
|
|
47
|
+
## write 동작
|
|
48
|
+
|
|
49
|
+
- 0행에 헤더(제외 후 필드 순서), 1행부터 레코드 값 기록.
|
|
50
|
+
- 전 셀에 4변 테두리 적용.
|
|
51
|
+
- **필수**(optional/nullable/default 아님)이며 boolean 이 아닌 필드의 헤더 셀은 노란색(`00FFFF00`) 강조.
|
|
52
|
+
- zoom 85%, 0행 틀고정 적용.
|
|
53
|
+
- 반환된 `ExcelWorkbook` 의 리소스 관리는 **호출자 책임** — 사용 후 `close()` 필수. write 중 예외 발생 시 내부에서 close 후 rethrow.
|
|
54
|
+
|
|
55
|
+
## 사용 예
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
|
|
60
|
+
const schema = z.object({
|
|
61
|
+
name: z.string().describe("이름"),
|
|
62
|
+
age: z.number().optional().describe("나이"),
|
|
63
|
+
});
|
|
64
|
+
const wrapper = new ExcelWrapper(schema);
|
|
65
|
+
|
|
66
|
+
// 읽기 ("이름"/"나이" 헤더를 0번 시트에서 매칭)
|
|
67
|
+
const rows = await wrapper.read(bytes);
|
|
68
|
+
|
|
69
|
+
// 쓰기 (호출자가 close 책임)
|
|
70
|
+
const wb = await wrapper.write("회원", [{ name: "홍길동", age: 30 }]);
|
|
71
|
+
try {
|
|
72
|
+
const out = await wb.toBytes();
|
|
73
|
+
} finally {
|
|
74
|
+
await wb.close();
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 주의사항
|
|
79
|
+
|
|
80
|
+
- `.describe()` 표시명이 실제 Excel 헤더와 일치해야 `read` 가 컬럼을 인식. 일치 헤더가 전혀 없으면 데이터 0건으로 throw.
|
|
81
|
+
- 표시명 미지정 필드는 필드 키 그대로 헤더로 쓰이므로, 한글 헤더가 필요하면 반드시 `.describe()` 지정.
|
|
82
|
+
- `read` 행 검증은 전부-성공 전제: 한 행이라도 `safeParse` 실패 시 전체 throw.
|
|
83
|
+
- `write` 반환 워크북 미`close` 시 리소스 누수.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @simplysm/lint
|
|
2
|
+
|
|
3
|
+
simplysm 전용 ESLint 9 flat-config 프리셋과 커스텀 규칙 9종을 제공하는 ESLint 플러그인 패키지. `src/index.ts` 없음 — `package.json` 의 `exports`(`./eslint-plugin`, `./eslint-recommended`) 두 subpath 가 진입점. 패키지 루트 import 는 없음.
|
|
4
|
+
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
|
+
|
|
7
|
+
- **eslint-recommended** (`@simplysm/lint/eslint-recommended`) — 프로젝트 `eslint.config.ts` 에서 통째로 spread 해 쓰는 완성형 flat-config 배열. JS/TS/HTML 파일별 규칙·ignore·플러그인·Angular 통합이 모두 포함됨. 새 프로젝트 lint 설정을 잡을 때.
|
|
8
|
+
- **eslint-plugin** (`@simplysm/lint/eslint-plugin`) — 커스텀 규칙만 담은 ESLint Plugin 객체(`{ rules }`). recommended 를 쓰지 않고 규칙을 직접 골라 등록할 때, 또는 규칙 동작을 개별 검토할 때. 자세히: [rules.md](./rules.md)
|
|
9
|
+
|
|
10
|
+
## eslint-recommended
|
|
11
|
+
|
|
12
|
+
`@simplysm/lint/eslint-recommended` 의 default export. `typescript-eslint` 의 `tseslint.config(...)` 결과 = flat-config 객체 배열. 그대로 spread 해서 사용함.
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// eslint.config.ts
|
|
16
|
+
import recommended from "@simplysm/lint/eslint-recommended";
|
|
17
|
+
|
|
18
|
+
export default [...recommended];
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
배열을 구성하는 config 블록(순서대로):
|
|
22
|
+
|
|
23
|
+
- **globalIgnores** — `**/node_modules/**`, `**/dist/**`, `**/.*/**` 순회 자체를 건너뜀. 점(.)으로 시작하는 디렉토리 전체가 제외 대상.
|
|
24
|
+
- **공통 languageOptions** — `ecmaVersion: "latest"`, `sourceType: "module"`.
|
|
25
|
+
- **JS 블록** (`**/*.js`, `**/*.mjs`, `**/*.cjs`) — node 글로벌, `import`/`@simplysm`/`unused-imports` 플러그인 등록. 활성 규칙: `require-await`, `no-shadow`, `no-duplicate-imports`, `no-unused-expressions`, `no-undef`, unused-imports 2종, `import/no-extraneous-dependencies`(lib·`eslint.config`·`simplysm`·`vitest.config` 파일은 devDeps 허용), `@simplysm/no-subpath-imports-from-simplysm`, `@simplysm/no-hard-private`, node 빌트인 차단 규칙군, env 직접접근 차단 규칙군.
|
|
26
|
+
- **angular tsRecommended** — `angular.configs.tsRecommended` spread.
|
|
27
|
+
- **TS 블록** (`**/*.ts`) — `processor: angular.processInlineTemplates`(인라인 템플릿 추출 후 별도 lint), `parserOptions.project: true`(타입 정보 사용), `import/resolver` 로 typescript resolver(`alwaysTryTypes: true`) 지정. typescript-eslint 타입체크 규칙 다수 + `@simplysm` 커스텀 규칙 6종 활성. 커스텀 규칙 심각도: `ng-no-async-effect`/`no-hard-private`/`no-subpath-imports-from-simplysm`/`ts-no-unused-injects`/`ts-no-unused-protected-readonly` = `error`, `ts-no-throw-not-implemented-error` = `warn`. `@angular-eslint/no-output-native` 은 `off`.
|
|
28
|
+
- **HTML 블록** (`**/*.html`) — `angular.configs.templateRecommended` + `templateAccessibility` extends. `@simplysm` 템플릿 규칙 3종 활성: `ng-template-no-strict-null-check`=`error`, `ng-template-no-todo-comments`=`warn`, `ng-template-sd-require-binding-attrs`=`error`. `@angular-eslint/template/eqeqeq` 는 `allowNullOrUndefined: true`, `label-has-associated-control`=`off`, `no-any`=`error`.
|
|
29
|
+
- **테스트 오버라이드** (`**/tests/**/*.ts`) — 테스트 코드 완화: `no-console`=`off`, `import/no-extraneous-dependencies`=`off`, `@simplysm/ts-no-throw-not-implemented-error`=`off`.
|
|
30
|
+
- **vitest.config 오버라이드** (`**/vitest.config.ts`) — `no-restricted-properties`=`off` (설정 파일에서 `process.env` 접근 허용).
|
|
31
|
+
|
|
32
|
+
주의:
|
|
33
|
+
|
|
34
|
+
- TS 블록은 타입 정보(`parserOptions.project: true`)를 요구하므로, 대상 프로젝트에 유효한 `tsconfig` 가 있어야 함.
|
|
35
|
+
- node 빌트인 차단(`noNodeBuiltinsRules`): `Buffer` 글로벌 및 `buffer`/`events`/`eventemitter3` import 금지 — 각각 `Uint8Array`/`@simplysm/core-common`의 `BytesUtils`·`EventEmitter` 로 대체 유도. JS·TS 블록 모두에 적용.
|
|
36
|
+
- env 직접접근 차단(`noDirectEnvAccessRules`): `process.env`·`import.meta.env` 직접 접근, `env("NODE_ENV")` 호출, `=== undefined`/`!== undefined`(엄격 비교) 를 `no-restricted-properties`/`no-restricted-syntax` 로 막음(엄격 비교는 `== null`/`!= null` 로 유도). JS·TS 블록 모두에 적용.
|
|
37
|
+
|
|
38
|
+
## eslint-plugin
|
|
39
|
+
|
|
40
|
+
`@simplysm/lint/eslint-plugin` 의 default export. ESLint Plugin 형태 객체.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import plugin from "@simplysm/lint/eslint-plugin";
|
|
44
|
+
// plugin === { rules: { "ng-no-async-effect": ..., "no-hard-private": ..., ... } }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `rules` — 규칙 id → 규칙 객체 맵. 등록된 9개 id: `ng-no-async-effect`, `ng-template-no-strict-null-check`, `ng-template-no-todo-comments`, `ng-template-sd-require-binding-attrs`, `no-hard-private`, `no-subpath-imports-from-simplysm`, `ts-no-throw-not-implemented-error`, `ts-no-unused-injects`, `ts-no-unused-protected-readonly`.
|
|
48
|
+
|
|
49
|
+
flat-config 에서 직접 등록할 때는 `plugins: { "@simplysm": plugin }` 로 매핑 후 `"@simplysm/<id>"` 로 켬. 각 규칙의 검사 대상·옵션·autofix·메시지는 [rules.md](./rules.md) 참조.
|