@jogak/core 0.1.0-alpha.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/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/actions.d.ts +22 -0
- package/dist/build/generate.d.ts +22 -0
- package/dist/build/index.d.ts +2 -0
- package/dist/build/index.js +72 -0
- package/dist/build/index.mjs +101 -0
- package/dist/extractor-client-CReBed7x.js +134 -0
- package/dist/extractor-client-CiWszHel.cjs +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +417 -0
- package/dist/meta/extract-core.d.ts +42 -0
- package/dist/meta/extract-props.d.ts +42 -0
- package/dist/meta/extractor-child.d.ts +1 -0
- package/dist/meta/extractor-child.js +1 -0
- package/dist/meta/extractor-child.mjs +305 -0
- package/dist/meta/extractor-client.d.ts +2 -0
- package/dist/registry.d.ts +86 -0
- package/dist/types.d.ts +142 -0
- package/dist/vite/cache-validate.d.ts +17 -0
- package/dist/vite/index.js +41 -0
- package/dist/vite/index.mjs +277 -0
- package/dist/vite/plugin.d.ts +3 -0
- package/dist/vite/virtual-ids.d.ts +19 -0
- package/package.json +82 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Jogak packages are documented here. The repository follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
Version numbers apply to all packages in the workspace (synchronized release).
|
|
7
|
+
|
|
8
|
+
## [0.1.0-alpha.0] — 2026-05-07
|
|
9
|
+
|
|
10
|
+
First public preview release. API is not yet stable.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Lazy virtual modules** (`@jogak/core`)
|
|
15
|
+
- `virtual:jogak` index module exposes only entry metadata; no user component imports.
|
|
16
|
+
- `virtual:jogak/entry/<slug>` per-entry virtual module dynamically imported on demand.
|
|
17
|
+
- `defaultRegistry` hydration state machine: `unknown → meta → pending → hydrated`.
|
|
18
|
+
- New API: `registerMeta`, `hydrateEntry`, `requestEntry`, `invalidateEntry`,
|
|
19
|
+
`getAllMeta`, `getMetaTree`, `getEntryState`, `setEntryLoader`, `subscribe`.
|
|
20
|
+
- `RegistryEntryMeta` type for sidebar metadata without component identity.
|
|
21
|
+
|
|
22
|
+
- **In-place HMR** (`@jogak/core` + `@jogak/react`)
|
|
23
|
+
- `*.jogak.tsx` edits trigger entry virtual module self-accept → automatic
|
|
24
|
+
rehydration. Sidebar metadata patched via `jogak:meta-update` ws custom event.
|
|
25
|
+
- `useEntry(id)` subscribes to registry mutations and re-renders on hydrate.
|
|
26
|
+
- meta-only vs structural change classification by `(title, jogakNamesKey)` signature.
|
|
27
|
+
|
|
28
|
+
- **child_process-isolated ts-morph extractor** (`@jogak/core`)
|
|
29
|
+
- Props extraction runs in a separate child process via IPC.
|
|
30
|
+
- Idle 5 s SIGTERM → OS reclaims memory immediately; no V8 isolate growth.
|
|
31
|
+
- Lazy spawn on first `extract()` call; pending requests rejected on child exit.
|
|
32
|
+
|
|
33
|
+
- **Vite cache auto-invalidation** (`@jogak/core/vite`)
|
|
34
|
+
- On dev boot, plugin compares jogak dist mtime vs `.vite/deps/_metadata.json`.
|
|
35
|
+
- Stale cache is purged automatically with an info log.
|
|
36
|
+
- Opt-out: `JogakPluginOptions.disableCacheValidation`.
|
|
37
|
+
|
|
38
|
+
- **`@jogak/react` hooks**
|
|
39
|
+
- `useEntry(id): UseEntryState` — `loading | ready | error | unknown` discriminated union.
|
|
40
|
+
- `useRegistryMeta(): UseRegistryMetaReturn` — backed by `useSyncExternalStore`,
|
|
41
|
+
referential identity guaranteed.
|
|
42
|
+
- `useRegistry()`, `JogakProvider`, `reactAdapter` (preserved signatures).
|
|
43
|
+
|
|
44
|
+
- **`@jogak/ui` library mode**
|
|
45
|
+
- `JogakApp`, `Sidebar`, `Preview`, `Controls`, `Actions` published as
|
|
46
|
+
pre-built ESM/CJS — no `transpilePackages` needed for Next.js.
|
|
47
|
+
- `JogakAppProps`: `entries` (eager) | `metas` (lazy) | both unset (defaultRegistry).
|
|
48
|
+
|
|
49
|
+
- **CLI** (`@jogak/cli`)
|
|
50
|
+
- `jogak dev` / `jogak build` / `jogak generate` commands.
|
|
51
|
+
- Auto-detects `<cwd>/tsconfig.json` for ts-morph; falls back to manual
|
|
52
|
+
`meta.argTypes` if absent.
|
|
53
|
+
|
|
54
|
+
- **Storybook benchmark suite** (`benchmarks/`)
|
|
55
|
+
- `bench:scale:full` — cold-start / build / bundle vs Storybook 8 (Vite builder).
|
|
56
|
+
- `bench:rss` — idle dev tree RSS vs Storybook.
|
|
57
|
+
- `bench:hmr` — Playwright-driven HMR latency.
|
|
58
|
+
|
|
59
|
+
### Numbers vs. Storybook 8 (Vite builder)
|
|
60
|
+
|
|
61
|
+
| Metric | size 100 | size 500 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| dev cold start | **1.7 s** vs 3.3 s | **2.9 s** vs 3.6 s |
|
|
64
|
+
| build time | **2.0 s** vs 2.9 s | **4.1 s** vs 7.6 s |
|
|
65
|
+
| bundle (gzip) | **108 KB** vs 716 KB | **156 KB** vs 1.09 MB |
|
|
66
|
+
| idle RSS (dev) | **321 MB** vs 403 MB | **345 MB** vs 489 MB |
|
|
67
|
+
| HMR (warm median) | **153 ms** | **199 ms** |
|
|
68
|
+
|
|
69
|
+
### Known Limitations
|
|
70
|
+
|
|
71
|
+
- HMR `jogak:meta-update` event only patches `defaultRegistry`. Custom registry
|
|
72
|
+
injections via `<JogakProvider registry={custom}>` fall back to full reload.
|
|
73
|
+
- Boot-time RSS spike (~700 MB) before settling at idle RSS — esbuild prebundle
|
|
74
|
+
+ ts-morph child spawn. Settles within 5 s.
|
|
75
|
+
- `@jogak/core` install pulls ts-morph (~17 MB); only `core/build` and the Vite
|
|
76
|
+
plugin actually use it. Splitting into `@jogak/extractor` is planned for 0.2.0.
|
|
77
|
+
|
|
78
|
+
### Compatibility
|
|
79
|
+
|
|
80
|
+
- Node ≥ 20.18 (`fetch` / `AbortSignal.timeout` stable)
|
|
81
|
+
- React ≥ 18 (peer)
|
|
82
|
+
- Vite ≥ 6 (peer, optional in `@jogak/core`)
|
|
83
|
+
- Next.js ≥ 14 (peer in `@jogak/next`)
|
|
84
|
+
- TypeScript ≥ 5.5 (build-time props extraction)
|
|
85
|
+
|
|
86
|
+
[0.1.0-alpha.0]: https://github.com/devclib/jogak/releases/tag/v0.1.0-alpha.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 devclib
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR DEALING IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @jogak/core
|
|
2
|
+
|
|
3
|
+
Core types, registry, and Vite plugin for [Jogak](https://github.com/devclib/jogak) — a lightweight Storybook alternative.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @jogak/core
|
|
9
|
+
# or: npm i @jogak/core / yarn add @jogak/core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Vite plugin (dev/build)
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// vite.config.ts
|
|
18
|
+
import { jogak } from '@jogak/core/vite'
|
|
19
|
+
|
|
20
|
+
export default defineConfig({
|
|
21
|
+
plugins: [react(), jogak()],
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Build-time codegen (host bundler embedding)
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { generateRegistryFile } from '@jogak/core/build'
|
|
29
|
+
|
|
30
|
+
await generateRegistryFile({
|
|
31
|
+
patterns: ['src/**/*.jogak.tsx'],
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
outFile: '.jogak/registry.ts',
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Registry / types
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { defaultRegistry } from '@jogak/core'
|
|
41
|
+
import type { JogakMeta, Jogak, RegistryEntry, ArgType } from '@jogak/core'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
See the [main README](https://github.com/devclib/jogak#readme) for architecture, CLI, host embedding, and Storybook benchmarks.
|
|
45
|
+
|
|
46
|
+
- Repository: https://github.com/devclib/jogak
|
|
47
|
+
- Issues: https://github.com/devclib/jogak/issues
|
|
48
|
+
- License: [MIT](./LICENSE)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ArgType } from './types.js';
|
|
2
|
+
export interface ActionLog {
|
|
3
|
+
readonly id: number;
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly args: readonly unknown[];
|
|
6
|
+
readonly timestamp: number;
|
|
7
|
+
}
|
|
8
|
+
export type ActionListener = (logs: readonly ActionLog[]) => void;
|
|
9
|
+
export declare class ActionChannel {
|
|
10
|
+
#private;
|
|
11
|
+
emit(name: string, args: readonly unknown[]): void;
|
|
12
|
+
subscribe(listener: ActionListener): () => void;
|
|
13
|
+
clear(): void;
|
|
14
|
+
getLogs(): readonly ActionLog[];
|
|
15
|
+
}
|
|
16
|
+
export declare const defaultActionChannel: ActionChannel;
|
|
17
|
+
export declare function action(name: string, channel?: ActionChannel): (...args: unknown[]) => void;
|
|
18
|
+
/**
|
|
19
|
+
* argTypes를 보고 함수/action prop 자리에 args에 정의된 값이 없으면 자동으로 spy를 주입한다.
|
|
20
|
+
* 어댑터(react, web-components 등)가 render 직전에 호출한다.
|
|
21
|
+
*/
|
|
22
|
+
export declare function injectActions(args: Readonly<Record<string, unknown>>, argTypes: Readonly<Record<string, ArgType>>, channel?: ActionChannel): Record<string, unknown>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GenerateOptions {
|
|
2
|
+
readonly patterns: readonly string[];
|
|
3
|
+
readonly cwd: string;
|
|
4
|
+
/** outFile은 cwd에 대한 상대 경로 또는 절대 경로 */
|
|
5
|
+
readonly outFile: string;
|
|
6
|
+
readonly tsConfigFilePath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GenerateResult {
|
|
9
|
+
readonly fileCount: number;
|
|
10
|
+
readonly outFile: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `*.jogak.tsx` 파일들을 스캔해 `entries`를 export하는 일반 TS 파일을 생성한다.
|
|
14
|
+
*
|
|
15
|
+
* Vite의 가상 모듈(`virtual:jogak`)과 동일한 형태의 데이터를 일반 import 가능한
|
|
16
|
+
* TS 파일로 출력하여 Next.js·Webpack·Turbopack·Rspack 등 어떤 호스트 번들러에서도
|
|
17
|
+
* 별도 플러그인 없이 마운트할 수 있게 한다.
|
|
18
|
+
*
|
|
19
|
+
* - props 자동 추출(ts-morph)은 빌드 타임에 1회 수행
|
|
20
|
+
* - source는 문자열로 inline (Vite의 `?raw` 같은 번들러별 문법 회피)
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateRegistryFile(options: GenerateOptions): Promise<GenerateResult>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const j=require("glob"),g=require("node:fs/promises"),n=require("node:path"),y=require("../extractor-client-CiWszHel.cjs");async function E(r){const c=n.resolve(r.cwd),a=n.resolve(c,r.outFile),u=n.dirname(a),s=(await j.glob(r.patterns,{cwd:c,absolute:!0})).sort(),l=r.tsConfigFilePath!==void 0?y.createPropsExtractor({tsConfigFilePath:r.tsConfigFilePath}):y.createPropsExtractor(),p=[],o=[];try{for(const e of s){p.push(await g.readFile(e,"utf8"));try{o.push(await l.extract(e))}catch{o.push({})}}}finally{l.releaseCache()}const d=s.map((e,t)=>{const i=n.relative(u,e).replace(/\\/g,"/").replace(/\.(tsx?|jsx?)$/u,""),m=i.startsWith(".")?i:`./${i}`;return`import _meta${t.toString()} from ${JSON.stringify(m)}
|
|
2
|
+
import * as _named${t.toString()} from ${JSON.stringify(m)}`}).join(`
|
|
3
|
+
`),f=p.map(e=>JSON.stringify(e)).join(`,
|
|
4
|
+
`),A=o.map(e=>JSON.stringify(e)).join(`,
|
|
5
|
+
`),T=s.map((e,t)=>`_buildEntry(_meta${t.toString()}, _named${t.toString()}, _sources[${t.toString()}], _autoArgTypes[${t.toString()}])`).join(`,
|
|
6
|
+
`),R=`// AUTO-GENERATED by @jogak/core/build — do not edit.
|
|
7
|
+
/* eslint-disable */
|
|
8
|
+
import type { RegistryEntry, RegistryEntryMeta, ArgType } from '@jogak/core'
|
|
9
|
+
${d}
|
|
10
|
+
|
|
11
|
+
const _sources: readonly string[] = [
|
|
12
|
+
${f}
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const _autoArgTypes: ReadonlyArray<Record<string, ArgType>> = [
|
|
16
|
+
${A}
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
function _buildEntry(
|
|
20
|
+
meta: any,
|
|
21
|
+
named: Record<string, unknown>,
|
|
22
|
+
source: string,
|
|
23
|
+
autoArgTypes: Record<string, ArgType>,
|
|
24
|
+
): RegistryEntry | null {
|
|
25
|
+
if (meta == null || typeof meta.title !== 'string') return null
|
|
26
|
+
const jogaks = Object.values(named).filter(
|
|
27
|
+
(v): v is { name: string } =>
|
|
28
|
+
v !== null && typeof v === 'object' && typeof (v as { name?: unknown }).name === 'string',
|
|
29
|
+
)
|
|
30
|
+
const userArgTypes: Record<string, ArgType> = meta.argTypes ?? {}
|
|
31
|
+
const mergedArgTypes: Record<string, ArgType> = { ...autoArgTypes }
|
|
32
|
+
for (const key of Object.keys(userArgTypes)) {
|
|
33
|
+
mergedArgTypes[key] = { ...mergedArgTypes[key], ...userArgTypes[key] }
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
id: meta.title,
|
|
37
|
+
title: meta.title,
|
|
38
|
+
jogaks: jogaks as RegistryEntry['jogaks'],
|
|
39
|
+
meta: { ...meta, argTypes: mergedArgTypes },
|
|
40
|
+
source,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _buildMeta(entry: RegistryEntry, autoArgTypes: Record<string, ArgType>): RegistryEntryMeta {
|
|
45
|
+
const userArgTypes = (entry.meta.argTypes ?? {}) as Record<string, ArgType>
|
|
46
|
+
return {
|
|
47
|
+
id: entry.id,
|
|
48
|
+
title: entry.title,
|
|
49
|
+
jogakNames: entry.jogaks.map((j) => j.name),
|
|
50
|
+
autoArgTypes,
|
|
51
|
+
userArgTypes,
|
|
52
|
+
source: entry.source ?? '',
|
|
53
|
+
filePath: entry.filePath ?? '',
|
|
54
|
+
metaExtras: {
|
|
55
|
+
...(entry.meta.tags !== undefined ? { tags: entry.meta.tags } : {}),
|
|
56
|
+
...(entry.meta.parameters !== undefined ? { parameters: entry.meta.parameters } : {}),
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const entries: readonly RegistryEntry[] = [
|
|
62
|
+
${T}
|
|
63
|
+
].filter((e): e is RegistryEntry => e !== null)
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 사이드바 메타 — \`<JogakApp metas={metas} />\` 패턴용.
|
|
67
|
+
* eager 모드에서는 entries로부터 합성 — ts-morph 추가 호출 없음.
|
|
68
|
+
*/
|
|
69
|
+
export const metas: readonly RegistryEntryMeta[] = entries.map((e, i) =>
|
|
70
|
+
_buildMeta(e, _autoArgTypes[i] ?? {}),
|
|
71
|
+
)
|
|
72
|
+
`;return await g.mkdir(u,{recursive:!0}),await g.writeFile(a,R,"utf8"),{fileCount:s.length,outFile:a}}exports.createPropsExtractor=y.createPropsExtractor;exports.generateRegistryFile=E;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { glob as R } from "glob";
|
|
2
|
+
import { readFile as j, mkdir as k, writeFile as E } from "node:fs/promises";
|
|
3
|
+
import { resolve as m, dirname as b, relative as h } from "node:path";
|
|
4
|
+
import { c as p } from "../extractor-client-CReBed7x.js";
|
|
5
|
+
async function S(r) {
|
|
6
|
+
const i = m(r.cwd), n = m(i, r.outFile), y = b(n), s = (await R(r.patterns, { cwd: i, absolute: !0 })).sort(), g = r.tsConfigFilePath !== void 0 ? p({ tsConfigFilePath: r.tsConfigFilePath }) : p(), c = [], a = [];
|
|
7
|
+
try {
|
|
8
|
+
for (const e of s) {
|
|
9
|
+
c.push(await j(e, "utf8"));
|
|
10
|
+
try {
|
|
11
|
+
a.push(await g.extract(e));
|
|
12
|
+
} catch {
|
|
13
|
+
a.push({});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
} finally {
|
|
17
|
+
g.releaseCache();
|
|
18
|
+
}
|
|
19
|
+
const l = s.map((e, t) => {
|
|
20
|
+
const o = h(y, e).replace(/\\/g, "/").replace(/\.(tsx?|jsx?)$/u, ""), u = o.startsWith(".") ? o : `./${o}`;
|
|
21
|
+
return `import _meta${t.toString()} from ${JSON.stringify(u)}
|
|
22
|
+
import * as _named${t.toString()} from ${JSON.stringify(u)}`;
|
|
23
|
+
}).join(`
|
|
24
|
+
`), d = c.map((e) => JSON.stringify(e)).join(`,
|
|
25
|
+
`), f = a.map((e) => JSON.stringify(e)).join(`,
|
|
26
|
+
`), A = s.map(
|
|
27
|
+
(e, t) => `_buildEntry(_meta${t.toString()}, _named${t.toString()}, _sources[${t.toString()}], _autoArgTypes[${t.toString()}])`
|
|
28
|
+
).join(`,
|
|
29
|
+
`), T = `// AUTO-GENERATED by @jogak/core/build — do not edit.
|
|
30
|
+
/* eslint-disable */
|
|
31
|
+
import type { RegistryEntry, RegistryEntryMeta, ArgType } from '@jogak/core'
|
|
32
|
+
${l}
|
|
33
|
+
|
|
34
|
+
const _sources: readonly string[] = [
|
|
35
|
+
${d}
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const _autoArgTypes: ReadonlyArray<Record<string, ArgType>> = [
|
|
39
|
+
${f}
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
function _buildEntry(
|
|
43
|
+
meta: any,
|
|
44
|
+
named: Record<string, unknown>,
|
|
45
|
+
source: string,
|
|
46
|
+
autoArgTypes: Record<string, ArgType>,
|
|
47
|
+
): RegistryEntry | null {
|
|
48
|
+
if (meta == null || typeof meta.title !== 'string') return null
|
|
49
|
+
const jogaks = Object.values(named).filter(
|
|
50
|
+
(v): v is { name: string } =>
|
|
51
|
+
v !== null && typeof v === 'object' && typeof (v as { name?: unknown }).name === 'string',
|
|
52
|
+
)
|
|
53
|
+
const userArgTypes: Record<string, ArgType> = meta.argTypes ?? {}
|
|
54
|
+
const mergedArgTypes: Record<string, ArgType> = { ...autoArgTypes }
|
|
55
|
+
for (const key of Object.keys(userArgTypes)) {
|
|
56
|
+
mergedArgTypes[key] = { ...mergedArgTypes[key], ...userArgTypes[key] }
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
id: meta.title,
|
|
60
|
+
title: meta.title,
|
|
61
|
+
jogaks: jogaks as RegistryEntry['jogaks'],
|
|
62
|
+
meta: { ...meta, argTypes: mergedArgTypes },
|
|
63
|
+
source,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function _buildMeta(entry: RegistryEntry, autoArgTypes: Record<string, ArgType>): RegistryEntryMeta {
|
|
68
|
+
const userArgTypes = (entry.meta.argTypes ?? {}) as Record<string, ArgType>
|
|
69
|
+
return {
|
|
70
|
+
id: entry.id,
|
|
71
|
+
title: entry.title,
|
|
72
|
+
jogakNames: entry.jogaks.map((j) => j.name),
|
|
73
|
+
autoArgTypes,
|
|
74
|
+
userArgTypes,
|
|
75
|
+
source: entry.source ?? '',
|
|
76
|
+
filePath: entry.filePath ?? '',
|
|
77
|
+
metaExtras: {
|
|
78
|
+
...(entry.meta.tags !== undefined ? { tags: entry.meta.tags } : {}),
|
|
79
|
+
...(entry.meta.parameters !== undefined ? { parameters: entry.meta.parameters } : {}),
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const entries: readonly RegistryEntry[] = [
|
|
85
|
+
${A}
|
|
86
|
+
].filter((e): e is RegistryEntry => e !== null)
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 사이드바 메타 — \`<JogakApp metas={metas} />\` 패턴용.
|
|
90
|
+
* eager 모드에서는 entries로부터 합성 — ts-morph 추가 호출 없음.
|
|
91
|
+
*/
|
|
92
|
+
export const metas: readonly RegistryEntryMeta[] = entries.map((e, i) =>
|
|
93
|
+
_buildMeta(e, _autoArgTypes[i] ?? {}),
|
|
94
|
+
)
|
|
95
|
+
`;
|
|
96
|
+
return await k(y, { recursive: !0 }), await E(n, T, "utf8"), { fileCount: s.length, outFile: n };
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
p as createPropsExtractor,
|
|
100
|
+
S as generateRegistryFile
|
|
101
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { fork as k } from "node:child_process";
|
|
2
|
+
import { existsSync as T } from "node:fs";
|
|
3
|
+
import { fileURLToPath as v } from "node:url";
|
|
4
|
+
import { dirname as $, resolve as a } from "node:path";
|
|
5
|
+
const C = 5e3, h = 3e4;
|
|
6
|
+
function S() {
|
|
7
|
+
const p = v(import.meta.url), e = $(p), s = [
|
|
8
|
+
a(e, "meta/extractor-child.mjs"),
|
|
9
|
+
a(e, "../meta/extractor-child.mjs"),
|
|
10
|
+
a(e, "../../meta/extractor-child.mjs"),
|
|
11
|
+
a(e, "extractor-child.mjs"),
|
|
12
|
+
// dev (tsc emit) fallback — 거의 안 쓰지만 안전망
|
|
13
|
+
a(e, "meta/extractor-child.js"),
|
|
14
|
+
a(e, "../meta/extractor-child.js")
|
|
15
|
+
];
|
|
16
|
+
for (const f of s)
|
|
17
|
+
if (T(f)) return f;
|
|
18
|
+
throw new Error(
|
|
19
|
+
`[jogak] extractor-child entry를 찾을 수 없습니다. 시도한 경로:
|
|
20
|
+
${s.join(`
|
|
21
|
+
`)}`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function R(p = {}) {
|
|
25
|
+
let e, s, f = 0;
|
|
26
|
+
const n = /* @__PURE__ */ new Map();
|
|
27
|
+
let g;
|
|
28
|
+
function j() {
|
|
29
|
+
return g === void 0 && (g = S()), g;
|
|
30
|
+
}
|
|
31
|
+
function m() {
|
|
32
|
+
s !== void 0 && (clearTimeout(s), s = void 0);
|
|
33
|
+
}
|
|
34
|
+
function u(i) {
|
|
35
|
+
for (const [, d] of n)
|
|
36
|
+
clearTimeout(d.timer), d.reject(i);
|
|
37
|
+
n.clear();
|
|
38
|
+
}
|
|
39
|
+
function x() {
|
|
40
|
+
if (m(), e === void 0) return;
|
|
41
|
+
const i = e;
|
|
42
|
+
e = void 0, u(new Error("[jogak] extractor child process disposed"));
|
|
43
|
+
try {
|
|
44
|
+
i.disconnect();
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
i.killed || i.kill("SIGTERM");
|
|
48
|
+
}
|
|
49
|
+
function y() {
|
|
50
|
+
m(), s = setTimeout(() => {
|
|
51
|
+
x();
|
|
52
|
+
}, C), typeof s.unref == "function" && s.unref();
|
|
53
|
+
}
|
|
54
|
+
function E() {
|
|
55
|
+
if (e !== void 0 && e.connected) return e;
|
|
56
|
+
e !== void 0 && (u(new Error("[jogak] extractor child process exited")), e = void 0);
|
|
57
|
+
const i = j(), d = p.tsConfigFilePath ?? "", o = k(i, [d], {
|
|
58
|
+
// detached:false → 부모 종료 시 자식도 종료
|
|
59
|
+
// serialization:'json' (기본) — ArgType은 plain JSON 호환
|
|
60
|
+
stdio: ["ignore", "inherit", "inherit", "ipc"]
|
|
61
|
+
});
|
|
62
|
+
return o.on("message", (r) => {
|
|
63
|
+
if (r.type !== "result" && r.type !== "metaResult" && r.type !== "error") return;
|
|
64
|
+
const t = n.get(r.id);
|
|
65
|
+
t !== void 0 && (n.delete(r.id), clearTimeout(t.timer), r.type === "result" ? t.kind === "argTypes" ? t.resolve(r.argTypes) : t.reject(new Error("[jogak] extractor child mismatched response: expected metaResult")) : r.type === "metaResult" ? t.kind === "meta" ? t.resolve(r.meta) : t.reject(new Error("[jogak] extractor child mismatched response: expected result")) : t.reject(new Error(`[jogak] extractor child error: ${r.message}`)));
|
|
66
|
+
}), o.on("exit", (r, t) => {
|
|
67
|
+
if (e === o) {
|
|
68
|
+
e = void 0;
|
|
69
|
+
const c = t !== null ? `signal=${t}` : `code=${(r ?? "null").toString()}`;
|
|
70
|
+
u(new Error(`[jogak] extractor child exited (${c})`));
|
|
71
|
+
}
|
|
72
|
+
}), o.on("error", (r) => {
|
|
73
|
+
e === o && (e = void 0, u(new Error(`[jogak] extractor child fork error: ${r.message}`)));
|
|
74
|
+
}), e = o, o;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
extract(i) {
|
|
78
|
+
m();
|
|
79
|
+
const d = E(), o = ++f;
|
|
80
|
+
return new Promise((r, t) => {
|
|
81
|
+
const c = setTimeout(() => {
|
|
82
|
+
n.delete(o) && t(
|
|
83
|
+
new Error(
|
|
84
|
+
`[jogak] extractor child timeout (${h}ms): ${i}`
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}, h);
|
|
88
|
+
typeof c.unref == "function" && c.unref(), n.set(o, { kind: "argTypes", resolve: r, reject: t, timer: c });
|
|
89
|
+
try {
|
|
90
|
+
d.send({ type: "extract", id: o, filePath: i });
|
|
91
|
+
} catch (l) {
|
|
92
|
+
n.delete(o), clearTimeout(c), t(
|
|
93
|
+
new Error(
|
|
94
|
+
`[jogak] extractor child IPC send 실패: ${l instanceof Error ? l.message : String(l)}`
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}).finally(() => {
|
|
99
|
+
n.size === 0 && y();
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
extractMeta(i) {
|
|
103
|
+
m();
|
|
104
|
+
const d = E(), o = ++f;
|
|
105
|
+
return new Promise((r, t) => {
|
|
106
|
+
const c = setTimeout(() => {
|
|
107
|
+
n.delete(o) && t(
|
|
108
|
+
new Error(
|
|
109
|
+
`[jogak] extractor child timeout (${h}ms): ${i}`
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}, h);
|
|
113
|
+
typeof c.unref == "function" && c.unref(), n.set(o, { kind: "meta", resolve: r, reject: t, timer: c });
|
|
114
|
+
try {
|
|
115
|
+
d.send({ type: "extractMeta", id: o, filePath: i });
|
|
116
|
+
} catch (l) {
|
|
117
|
+
n.delete(o), clearTimeout(c), t(
|
|
118
|
+
new Error(
|
|
119
|
+
`[jogak] extractor child IPC send 실패: ${l instanceof Error ? l.message : String(l)}`
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}).finally(() => {
|
|
124
|
+
n.size === 0 && y();
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
releaseCache() {
|
|
128
|
+
x();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
R as c
|
|
134
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";const k=require("node:child_process"),T=require("node:fs"),C=require("node:url"),a=require("node:path");var x=typeof document<"u"?document.currentScript:null;const S=5e3,h=3e4;function _(){const p=C.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:x&&x.tagName.toUpperCase()==="SCRIPT"&&x.src||new URL("extractor-client-CiWszHel.cjs",document.baseURI).href),e=a.dirname(p),s=[a.resolve(e,"meta/extractor-child.mjs"),a.resolve(e,"../meta/extractor-child.mjs"),a.resolve(e,"../../meta/extractor-child.mjs"),a.resolve(e,"extractor-child.mjs"),a.resolve(e,"meta/extractor-child.js"),a.resolve(e,"../meta/extractor-child.js")];for(const u of s)if(T.existsSync(u))return u;throw new Error(`[jogak] extractor-child entry를 찾을 수 없습니다. 시도한 경로:
|
|
2
|
+
${s.join(`
|
|
3
|
+
`)}`)}function $(p={}){let e,s,u=0;const n=new Map;let g;function v(){return g===void 0&&(g=_()),g}function f(){s!==void 0&&(clearTimeout(s),s=void 0)}function m(i){for(const[,d]of n)clearTimeout(d.timer),d.reject(i);n.clear()}function y(){if(f(),e===void 0)return;const i=e;e=void 0,m(new Error("[jogak] extractor child process disposed"));try{i.disconnect()}catch{}i.killed||i.kill("SIGTERM")}function E(){f(),s=setTimeout(()=>{y()},S),typeof s.unref=="function"&&s.unref()}function j(){if(e!==void 0&&e.connected)return e;e!==void 0&&(m(new Error("[jogak] extractor child process exited")),e=void 0);const i=v(),d=p.tsConfigFilePath??"",o=k.fork(i,[d],{stdio:["ignore","inherit","inherit","ipc"]});return o.on("message",r=>{if(r.type!=="result"&&r.type!=="metaResult"&&r.type!=="error")return;const t=n.get(r.id);t!==void 0&&(n.delete(r.id),clearTimeout(t.timer),r.type==="result"?t.kind==="argTypes"?t.resolve(r.argTypes):t.reject(new Error("[jogak] extractor child mismatched response: expected metaResult")):r.type==="metaResult"?t.kind==="meta"?t.resolve(r.meta):t.reject(new Error("[jogak] extractor child mismatched response: expected result")):t.reject(new Error(`[jogak] extractor child error: ${r.message}`)))}),o.on("exit",(r,t)=>{if(e===o){e=void 0;const c=t!==null?`signal=${t}`:`code=${(r??"null").toString()}`;m(new Error(`[jogak] extractor child exited (${c})`))}}),o.on("error",r=>{e===o&&(e=void 0,m(new Error(`[jogak] extractor child fork error: ${r.message}`)))}),e=o,o}return{extract(i){f();const d=j(),o=++u;return new Promise((r,t)=>{const c=setTimeout(()=>{n.delete(o)&&t(new Error(`[jogak] extractor child timeout (${h}ms): ${i}`))},h);typeof c.unref=="function"&&c.unref(),n.set(o,{kind:"argTypes",resolve:r,reject:t,timer:c});try{d.send({type:"extract",id:o,filePath:i})}catch(l){n.delete(o),clearTimeout(c),t(new Error(`[jogak] extractor child IPC send 실패: ${l instanceof Error?l.message:String(l)}`))}}).finally(()=>{n.size===0&&E()})},extractMeta(i){f();const d=j(),o=++u;return new Promise((r,t)=>{const c=setTimeout(()=>{n.delete(o)&&t(new Error(`[jogak] extractor child timeout (${h}ms): ${i}`))},h);typeof c.unref=="function"&&c.unref(),n.set(o,{kind:"meta",resolve:r,reject:t,timer:c});try{d.send({type:"extractMeta",id:o,filePath:i})}catch(l){n.delete(o),clearTimeout(c),t(new Error(`[jogak] extractor child IPC send 실패: ${l instanceof Error?l.message:String(l)}`))}}).finally(()=>{n.size===0&&E()})},releaseCache(){y()}}}exports.createPropsExtractor=$;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { ArgType, JogakMeta, Jogak, RegistryEntry, RegistryEntryMeta, CategoryTree, CategoryMetaTree, JogakAdapter, JogakPluginOptions, } from './types.js';
|
|
2
|
+
export { ComponentRegistry, defaultRegistry, UnknownEntryError } from './registry.js';
|
|
3
|
+
export { ActionChannel, defaultActionChannel, action, injectActions, type ActionLog, type ActionListener, } from './actions.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var q=Object.defineProperty;var z=s=>{throw TypeError(s)};var D=(s,t,e)=>t in s?q(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var F=(s,t,e)=>D(s,typeof t!="symbol"?t+"":t,e),x=(s,t,e)=>t.has(s)||z("Cannot "+e);var i=(s,t,e)=>(x(s,t,"read from private field"),e?e.call(s):t.get(s)),g=(s,t,e)=>t.has(s)?z("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(s):t.set(s,e),l=(s,t,e,n)=>(x(s,t,"write to private field"),n?n.call(s,e):t.set(s,e),e),u=(s,t,e)=>(x(s,t,"access private method"),e);var L=(s,t,e,n)=>({set _(o){l(s,t,o,e)},get _(){return i(s,t,n)}});Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class M extends Error{constructor(e){super(`[jogak] Unknown entry id: ${e}`);F(this,"id");this.name="UnknownEntryError",this.id=e}}var a,T,j,k,v,E,w,h,y,N;class U{constructor(){g(this,h);g(this,a,new Map);g(this,T);g(this,j,new Set);g(this,k);g(this,v);g(this,E,!1);g(this,w,!1)}register(t){const e=B(t);u(this,h,N).call(this,()=>{this.registerMeta(e),this.hydrateEntry(t.id,t.jogaks,t.meta.component)})}unregister(t){const e=i(this,a).get(t);e!==void 0&&(e.kind==="pending"&&e.reject(new M(t)),i(this,a).delete(t),u(this,h,y).call(this))}get(t){const e=i(this,a).get(t);return(e==null?void 0:e.kind)==="hydrated"?e.entry:void 0}getAll(){const t=[];for(const e of i(this,a).values())e.kind==="hydrated"&&t.push(e.entry);return t}search(t){const e=t.toLowerCase();return this.getAll().filter(n=>n.title.toLowerCase().includes(e))}getTree(){const t={};for(const e of this.getAll()){const n=e.title.split("/");let o=t;for(let d=0;d<n.length-1;d++){const c=n[d];if(c===void 0)continue;const f=o[c];(f===void 0||"id"in f)&&(o[c]={}),o=o[c]}const r=n[n.length-1];r!==void 0&&(o[r]=e)}return t}clear(){if(i(this,a).size!==0){for(const t of i(this,a).values())t.kind==="pending"&&t.reject(new Error("[jogak] registry cleared"));i(this,a).clear(),u(this,h,y).call(this)}}get size(){let t=0;for(const e of i(this,a).values())e.kind==="hydrated"&&t++;return t}registerMeta(t){const e=i(this,a).get(t.id);if(e===void 0){i(this,a).set(t.id,{kind:"meta",meta:t}),u(this,h,y).call(this);return}if(e.kind==="meta"){i(this,a).set(t.id,{kind:"meta",meta:t}),u(this,h,y).call(this);return}if(e.kind==="pending"){i(this,a).set(t.id,{kind:"pending",meta:t,promise:e.promise,resolve:e.resolve,reject:e.reject}),u(this,h,y).call(this);return}const n={...e.entry,title:t.title,filePath:t.filePath,source:t.source,meta:R(t,e.entry.meta.component)};i(this,a).set(t.id,{kind:"hydrated",meta:t,entry:n}),u(this,h,y).call(this)}hydrateEntry(t,e,n){const o=i(this,a).get(t);let r;o===void 0?(console.warn(`[jogak] hydrateEntry called for unknown id "${t}" — synthesizing minimal meta`),r={id:t,title:t,jogakNames:e.map(c=>c.name),autoArgTypes:{},userArgTypes:{},source:"",filePath:"",metaExtras:{}}):r=o.meta;const d={id:r.id,title:r.title,jogaks:e,meta:R(r,n),...r.filePath?{filePath:r.filePath}:{},...r.source?{source:r.source}:{}};if((o==null?void 0:o.kind)==="pending"){i(this,a).set(t,{kind:"hydrated",meta:r,entry:d}),u(this,h,y).call(this),o.resolve(d);return}i(this,a).set(t,{kind:"hydrated",meta:r,entry:d}),u(this,h,y).call(this)}invalidateEntry(t){const e=i(this,a).get(t);e===void 0||e.kind!=="hydrated"||(i(this,a).set(t,{kind:"meta",meta:e.meta}),u(this,h,y).call(this))}requestEntry(t){const e=i(this,a).get(t);if(e===void 0)return Promise.reject(new M(t));if(e.kind==="hydrated")return Promise.resolve(e.entry);if(e.kind==="pending")return e.promise;const n=i(this,T);if(n===void 0)return Promise.reject(new Error("[jogak] entry loader not set — virtual:jogak index module did not load"));let o,r;const d=new Promise((c,f)=>{o=c,r=f});return i(this,a).set(t,{kind:"pending",meta:e.meta,promise:d,resolve:o,reject:r}),n(t).then(()=>{const c=i(this,a).get(t);(c==null?void 0:c.kind)!=="hydrated"&&r(new Error(`[jogak] entry module loaded but did not hydrate: ${t}`))},c=>{const f=c instanceof Error?c:new Error(String(c)),p=i(this,a).get(t);(p==null?void 0:p.kind)==="pending"&&p.promise===d&&i(this,a).set(t,{kind:"meta",meta:e.meta}),r(f)}),d}getAllMeta(){if(i(this,k)!==void 0)return i(this,k);const t=[];for(const n of i(this,a).values())t.push(n.meta);const e=t;return l(this,k,e),e}getMetaTree(){if(i(this,v)!==void 0)return i(this,v);const t={};for(const e of i(this,a).values()){const n=e.meta,o=n.title.split("/");let r=t;for(let c=0;c<o.length-1;c++){const f=o[c];if(f===void 0)continue;const p=r[f];(p===void 0||"id"in p)&&(r[f]={}),r=r[f]}const d=o[o.length-1];d!==void 0&&(r[d]=n)}return l(this,v,t),t}getEntryState(t){const e=i(this,a).get(t);return e===void 0?"unknown":e.kind}setEntryLoader(t){l(this,T,t)}subscribe(t){i(this,j).add(t);let e=!0;return()=>{e&&(e=!1,i(this,j).delete(t))}}}a=new WeakMap,T=new WeakMap,j=new WeakMap,k=new WeakMap,v=new WeakMap,E=new WeakMap,w=new WeakMap,h=new WeakSet,y=function(){if(l(this,k,void 0),l(this,v,void 0),i(this,E)){l(this,w,!0);return}const t=Array.from(i(this,j));for(const e of t)try{e()}catch(n){console.error("[jogak] subscribe listener threw:",n)}},N=function(t){if(i(this,E)){t();return}l(this,E,!0),l(this,w,!1);try{t()}finally{l(this,E,!1),i(this,w)&&(l(this,w,!1),u(this,h,y).call(this))}};function B(s){const t=s.meta.argTypes??{};return{id:s.id,title:s.title,jogakNames:s.jogaks.map(e=>e.name),autoArgTypes:{},userArgTypes:t,source:s.source??"",filePath:s.filePath??"",metaExtras:{...s.meta.tags!==void 0?{tags:s.meta.tags}:{},...s.meta.parameters!==void 0?{parameters:s.meta.parameters}:{}}}}function R(s,t){const e={...s.autoArgTypes};for(const n of Object.keys(s.userArgTypes)){const o=s.userArgTypes[n];o!==void 0&&(e[n]={...e[n],...o})}return{title:s.title,component:t,argTypes:e,...s.metaExtras.tags!==void 0?{tags:s.metaExtras.tags}:{},...s.metaExtras.parameters!==void 0?{parameters:s.metaExtras.parameters}:{}}}const I=new U;var m,A,P,b,C;class O{constructor(){g(this,b);g(this,m,[]);g(this,A,new Set);g(this,P,1)}emit(t,e){const n={id:L(this,P)._++,name:t,args:e,timestamp:Date.now()};l(this,m,[...i(this,m),n]),u(this,b,C).call(this)}subscribe(t){return i(this,A).add(t),t(i(this,m)),()=>{i(this,A).delete(t)}}clear(){i(this,m).length!==0&&(l(this,m,[]),u(this,b,C).call(this))}getLogs(){return i(this,m)}}m=new WeakMap,A=new WeakMap,P=new WeakMap,b=new WeakSet,C=function(){for(const t of i(this,A))t(i(this,m))};const S=new O;function $(s,t=S){return(...e)=>{t.emit(s,e)}}function J(s,t,e=S){const n={...s};for(const o of Object.keys(t)){const r=t[o];if(r===void 0)continue;const d=r.action!==void 0&&r.action!==!1,c=r.type==="function";if(!d&&!c||typeof n[o]=="function")continue;const f=typeof r.action=="string"?r.action:o;n[o]=$(f,e)}return n}exports.ActionChannel=O;exports.ComponentRegistry=U;exports.UnknownEntryError=M;exports.action=$;exports.defaultActionChannel=S;exports.defaultRegistry=I;exports.injectActions=J;
|