@sorb/seed 0.2.0 → 0.4.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/package.json +7 -3
- package/src/adapt/__fixtures__/Button.legacy.jsx +38 -0
- package/src/adapt/__fixtures__/Button.tokenized.tsx +28 -0
- package/src/adapt/__fixtures__/corpus/button-inline.case.json +10 -0
- package/src/adapt/__fixtures__/corpus/card-styled.case.json +9 -0
- package/src/adapt/__fixtures__/corpus/misc-unmapped.case.json +7 -0
- package/src/adapt/adaptCli.js +110 -0
- package/src/adapt/adaptCli.test.js +109 -0
- package/src/adapt/benchmark.js +113 -0
- package/src/adapt/benchmark.test.js +79 -0
- package/src/adapt/codemod.js +160 -0
- package/src/adapt/codemod.test.js +120 -0
- package/src/adapt/detectHardcoded.js +165 -0
- package/src/adapt/detectHardcoded.test.js +62 -0
- package/src/adapt/glob.js +87 -0
- package/src/adapt/mapToToken.js +94 -0
- package/src/adapt/mapToToken.test.js +103 -0
- package/src/adapt/report.js +53 -0
- package/src/adapt/report.test.js +66 -0
- package/src/adapt/runBenchmark.js +16 -0
- package/src/adapt/types.js +48 -0
- package/src/captureCli.js +38 -139
- package/src/cli.js +16 -1
- package/src/emit/sorbFormat.js +313 -0
- package/src/emit/sorbFormat.test.js +133 -0
- package/src/emit/sorbMantine.js +116 -0
- package/src/emit/sorbMantine.test.js +68 -0
- package/src/emit/sorbMatSys.js +159 -0
- package/src/emit/sorbMatSys.test.js +75 -0
- package/src/emit/sorbMui.js +165 -0
- package/src/emit/sorbMui.test.js +102 -0
- package/src/emit/sorbPrimevue.js +240 -0
- package/src/emit/sorbPrimevue.test.js +130 -0
- package/src/emit/sorbShadcn.js +186 -0
- package/src/emit/sorbShadcn.test.js +102 -0
- package/src/index.js +48 -0
- package/src/sources/figmaPlugin.js +83 -0
- package/src/sources/figmaPlugin.test.js +65 -0
- package/src/sources/storybookDom.js +188 -0
package/src/index.js
CHANGED
|
@@ -5,3 +5,51 @@
|
|
|
5
5
|
// so the old `resolveBindableTokens` export is gone. What remains useful as a
|
|
6
6
|
// library is the token-annotation layer used by `capture`.
|
|
7
7
|
export { buildTokenIndex, annotateTree, matchColor, matchDimension } from './annotateTokens.js'
|
|
8
|
+
|
|
9
|
+
// Sorb's custom Style Dictionary outputs (component-compat-roadmap P0, part
|
|
10
|
+
// 2) — promoted from sorb-demo's copy-local `sd/sorb-format.js` so target
|
|
11
|
+
// adapters import these formats instead of duplicating them. A consumer's
|
|
12
|
+
// `sd.config.js` registers these with `StyleDictionary.registerFormat`/
|
|
13
|
+
// `registerParser` the same way sorb-demo's does.
|
|
14
|
+
export {
|
|
15
|
+
tierOfFile,
|
|
16
|
+
SORB_RESOLVED,
|
|
17
|
+
SORB_THEME_NESTED,
|
|
18
|
+
SORB_ALIASES,
|
|
19
|
+
SORB_VERSIONS,
|
|
20
|
+
SORB_SET_META,
|
|
21
|
+
SORB_TAILWIND,
|
|
22
|
+
SORB_TAILWIND_V3,
|
|
23
|
+
SORB_TOKENSET,
|
|
24
|
+
sorbSetMeta,
|
|
25
|
+
sorbVersions,
|
|
26
|
+
sorbTokenSet,
|
|
27
|
+
sorbResolved,
|
|
28
|
+
sorbAliases,
|
|
29
|
+
sorbThemeNested,
|
|
30
|
+
tailwindThemeEntry,
|
|
31
|
+
sorbTailwind,
|
|
32
|
+
tailwindV3Slot,
|
|
33
|
+
sorbTailwindV3,
|
|
34
|
+
} from './emit/sorbFormat.js'
|
|
35
|
+
|
|
36
|
+
// Mantine v7 CSS-var override format (framework-targets-productization T2) —
|
|
37
|
+
// promoted from `sorb-demo-mantine/sd/mantine-format.js`.
|
|
38
|
+
export { SORB_MANTINE_VARS, MANTINE_VAR_MAP, sorbMantineVars } from './emit/sorbMantine.js'
|
|
39
|
+
|
|
40
|
+
// shadcn/ui theme format (framework-targets-productization T1) — promoted
|
|
41
|
+
// from `sorb-demo-tailwind`'s hand-authored `shadcn-map.css`+`shadcn-theme.css`.
|
|
42
|
+
export { SORB_SHADCN, sorbShadcn, shadcnRootLines, shadcnThemeInlineLines } from './emit/sorbShadcn.js'
|
|
43
|
+
|
|
44
|
+
// MUI v6 CSS-var override format (framework-targets-productization T3) —
|
|
45
|
+
// promoted from `sorb-demo-mui/sd.config.js:18-86`.
|
|
46
|
+
export { SORB_MUI_VARS, MUI_VAR_MAP, sorbMuiVars } from './emit/sorbMui.js'
|
|
47
|
+
|
|
48
|
+
// Angular Material 20 M3 system-variable override format
|
|
49
|
+
// (framework-targets-productization T5) — promoted from
|
|
50
|
+
// `sorb-demo-angular/sd.config.js:96-113`.
|
|
51
|
+
export { SORB_MAT_SYS_VARS, MAT_SYS_MAP, sorbMatSysVars } from './emit/sorbMatSys.js'
|
|
52
|
+
|
|
53
|
+
// PrimeVue v4 preset format (framework-targets-productization T4) — the first
|
|
54
|
+
// JS-emitting target format; generated from `sorb-demo-primevue/src/jjPreset.js`.
|
|
55
|
+
export { SORB_PRIMEVUE_PRESET, PRIMEVUE_ROLE_TREE, sorbPrimevuePreset } from './emit/sorbPrimevue.js'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// `figma-plugin` SourceConnector — the DEFAULT Figma tokens source (all
|
|
2
|
+
// tiers; no Enterprise gate). spec/sorb/figma-source-connector.md §1a.
|
|
3
|
+
//
|
|
4
|
+
// Unlike `storybook-dom` (a local Playwright/Node process this package drives
|
|
5
|
+
// directly), this connector's data does NOT come from a locally-callable API.
|
|
6
|
+
// It comes from the Figma plugin (`sorb-canopy`), which reads
|
|
7
|
+
// `figma.variables.*` **in-file** (via the shared token-mapping walk —
|
|
8
|
+
// `sorb-canopy/lib/token-mapping.js`), maps it to Sorb's resolved-map shape,
|
|
9
|
+
// and PUSHES it to the juice bridge (`POST /tokens/figma`, the plugin's
|
|
10
|
+
// "Export variables" action). So this connector is a BRIDGE CLIENT, not a
|
|
11
|
+
// Figma caller — spec's flagged riskiest coupling ("the plugin is the
|
|
12
|
+
// executor, the seed-side connector is a bridge client"). `readTokens()`
|
|
13
|
+
// reads the latest pushed artifact back via `GET /tokens/figma`.
|
|
14
|
+
//
|
|
15
|
+
// v1 scope is tokens-first (figma-source-connector.md §1a Fork F /
|
|
16
|
+
// §R "Cut"): `listUnits`/`captureGeometry` need a geometry-capable bridge
|
|
17
|
+
// endpoint the plugin doesn't push yet (component/node geometry, not just
|
|
18
|
+
// variables) — that's a deferred follow-on phase, so they throw a clear,
|
|
19
|
+
// actionable error instead of pretending to call Figma directly.
|
|
20
|
+
//
|
|
21
|
+
// No local token-mapping fork: this connector does no Figma-value mapping of
|
|
22
|
+
// its own — the resolved-map entries it reads back already match
|
|
23
|
+
// `@sorb/core`'s `ResolvedToken` shape because `sorb-canopy/lib/token-
|
|
24
|
+
// mapping.js` produced them before the push. There is nothing to share/
|
|
25
|
+
// extract on the seed side (the mapping stays owned by canopy, the one place
|
|
26
|
+
// that actually touches `figma.variables.*`).
|
|
27
|
+
|
|
28
|
+
import { registerSource } from '@sorb/core'
|
|
29
|
+
|
|
30
|
+
// sorb.config.json may set seed.bridgeOrigin (mirrors storybookUrlOf's
|
|
31
|
+
// seed.storybookUrl convention). Falls back to juice's default dev port.
|
|
32
|
+
export const bridgeOriginOf = (config) =>
|
|
33
|
+
((config && config.seed && config.seed.bridgeOrigin) || (config && config.bridgeOrigin) || 'http://localhost:7777').replace(
|
|
34
|
+
/\/$/,
|
|
35
|
+
'',
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const readTokens = async (config) => {
|
|
39
|
+
const origin = bridgeOriginOf(config)
|
|
40
|
+
const url = `${origin}/tokens/figma`
|
|
41
|
+
let res
|
|
42
|
+
try {
|
|
43
|
+
res = await fetch(url)
|
|
44
|
+
} catch (e) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`figma-plugin source: could not reach the bridge at ${origin} (${e.message}). ` +
|
|
47
|
+
'Run `sorb dev` first.',
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
if (res.status === 404) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'figma-plugin source: no Figma export yet — in the Sorb Figma plugin, run ' +
|
|
53
|
+
`"Export variables" (bridge: ${origin}).`,
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
throw new Error(`figma-plugin source: bridge GET /tokens/figma failed — HTTP ${res.status}`)
|
|
58
|
+
}
|
|
59
|
+
const artifact = await res.json()
|
|
60
|
+
return Array.isArray(artifact.tokens) ? artifact.tokens : []
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const GEOMETRY_NOT_YET_IMPLEMENTED =
|
|
64
|
+
'figma-plugin source: listUnits/captureGeometry are not implemented in v1 (tokens-first — ' +
|
|
65
|
+
'see spec/sorb/figma-source-connector.md §1a Fork F). Use readTokens() for the token half; ' +
|
|
66
|
+
'geometry capture from Figma is a deferred follow-on phase.'
|
|
67
|
+
|
|
68
|
+
const listUnits = async () => {
|
|
69
|
+
throw new Error(GEOMETRY_NOT_YET_IMPLEMENTED)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const captureGeometry = async () => {
|
|
73
|
+
throw new Error(GEOMETRY_NOT_YET_IMPLEMENTED)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const figmaPluginConnector = {
|
|
77
|
+
id: 'figma-plugin',
|
|
78
|
+
listUnits,
|
|
79
|
+
captureGeometry,
|
|
80
|
+
readTokens,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerSource(figmaPluginConnector)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Tests for the `figma-plugin` SourceConnector (figma-source-connector.md
|
|
2
|
+
// §1a). Mirrors the contract-registration style of sorb-core's
|
|
3
|
+
// connectors.test.js dummy-source test, plus exercises readTokens() as a
|
|
4
|
+
// bridge CLIENT against a mocked `fetch` (no real juice server needed —
|
|
5
|
+
// the contract is "GET /tokens/figma returns { tokens: ResolvedToken[] }").
|
|
6
|
+
import test from 'node:test'
|
|
7
|
+
import assert from 'node:assert/strict'
|
|
8
|
+
import { connectors, getSource } from '@sorb/core'
|
|
9
|
+
import { figmaPluginConnector, bridgeOriginOf } from './figmaPlugin.js'
|
|
10
|
+
|
|
11
|
+
test('registers under id "figma-plugin" and round-trips via getSource (contract dispatch)', () => {
|
|
12
|
+
assert.equal(getSource('figma-plugin'), figmaPluginConnector)
|
|
13
|
+
assert.equal(connectors.source.get('figma-plugin'), figmaPluginConnector)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test('bridgeOriginOf: defaults to the juice dev port, honors seed.bridgeOrigin, strips trailing slash', () => {
|
|
17
|
+
assert.equal(bridgeOriginOf({}), 'http://localhost:7777')
|
|
18
|
+
assert.equal(bridgeOriginOf(), 'http://localhost:7777')
|
|
19
|
+
assert.equal(bridgeOriginOf({ seed: { bridgeOrigin: 'http://localhost:9999/' } }), 'http://localhost:9999')
|
|
20
|
+
assert.equal(bridgeOriginOf({ bridgeOrigin: 'http://example.com/' }), 'http://example.com')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('readTokens: reads the resolved-map array back from GET /tokens/figma', async (t) => {
|
|
24
|
+
const tokens = [{ id: 'color.bg.primary', cssVar: '--color-bg-primary', value: '#ffffff', tier: 'primitive', type: 'color' }]
|
|
25
|
+
const calls = []
|
|
26
|
+
const realFetch = global.fetch
|
|
27
|
+
global.fetch = async (url) => {
|
|
28
|
+
calls.push(url)
|
|
29
|
+
return { ok: true, status: 200, json: async () => ({ fileKey: 'abc', exportedAt: '2026-08-29T00:00:00Z', tokens }) }
|
|
30
|
+
}
|
|
31
|
+
t.after(() => { global.fetch = realFetch })
|
|
32
|
+
|
|
33
|
+
const got = await figmaPluginConnector.readTokens({ seed: { bridgeOrigin: 'http://localhost:7777' } })
|
|
34
|
+
assert.deepEqual(got, tokens)
|
|
35
|
+
assert.equal(calls[0], 'http://localhost:7777/tokens/figma')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('readTokens: 404 (no export yet) throws an actionable "Export variables" error', async (t) => {
|
|
39
|
+
const realFetch = global.fetch
|
|
40
|
+
global.fetch = async () => ({ ok: false, status: 404, json: async () => ({ error: 'No Figma export yet.' }) })
|
|
41
|
+
t.after(() => { global.fetch = realFetch })
|
|
42
|
+
|
|
43
|
+
await assert.rejects(() => figmaPluginConnector.readTokens({}), /Export variables/)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('readTokens: non-2xx, non-404 → surfaces the HTTP status', async (t) => {
|
|
47
|
+
const realFetch = global.fetch
|
|
48
|
+
global.fetch = async () => ({ ok: false, status: 500, json: async () => ({}) })
|
|
49
|
+
t.after(() => { global.fetch = realFetch })
|
|
50
|
+
|
|
51
|
+
await assert.rejects(() => figmaPluginConnector.readTokens({}), /HTTP 500/)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('readTokens: network failure (bridge not running) → actionable error naming the origin', async (t) => {
|
|
55
|
+
const realFetch = global.fetch
|
|
56
|
+
global.fetch = async () => { throw new Error('ECONNREFUSED') }
|
|
57
|
+
t.after(() => { global.fetch = realFetch })
|
|
58
|
+
|
|
59
|
+
await assert.rejects(() => figmaPluginConnector.readTokens({}), /could not reach the bridge at http:\/\/localhost:7777/)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('listUnits/captureGeometry: v1 tokens-first scope — throw a clear not-yet-implemented error, not a silent no-op', async () => {
|
|
63
|
+
await assert.rejects(() => figmaPluginConnector.listUnits({}), /not implemented in v1/)
|
|
64
|
+
await assert.rejects(() => figmaPluginConnector.captureGeometry({ id: 'u1' }, {}), /not implemented in v1/)
|
|
65
|
+
})
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// `storybook-dom` SourceConnector — the default SOURCE connector (registered
|
|
2
|
+
// under `@sorb/core`'s DEFAULT_SOURCE_ID = 'storybook-dom'). Extracted from
|
|
3
|
+
// captureCli.js verbatim (spec/sorb/connectors-architecture.md §3.1, C1) — a
|
|
4
|
+
// pure refactor, no behavior change. Owns everything source-specific:
|
|
5
|
+
// discovering Storybook story entries (`listUnits`), running Playwright +
|
|
6
|
+
// the `capture.js` walker to capture one entry's raw geometry
|
|
7
|
+
// (`captureGeometry`), and reading the DTCG/Style-Dictionary resolved token
|
|
8
|
+
// map (`readTokens`). The generic pipeline (tightenRoot -> annotateTree ->
|
|
9
|
+
// hash -> write) stays in captureCli.js and is fed by this connector.
|
|
10
|
+
|
|
11
|
+
import { basename, extname, dirname, resolve } from 'path'
|
|
12
|
+
import { readFileSync, existsSync } from 'fs'
|
|
13
|
+
import { build } from 'esbuild'
|
|
14
|
+
import { registerSource } from '@sorb/core'
|
|
15
|
+
|
|
16
|
+
// Playwright is an OPTIONAL peer dep — only `capture` needs it, and it pulls a
|
|
17
|
+
// ~150 MB browser. Lazy-load it so plain installs and `resolve` stay lean.
|
|
18
|
+
const loadChromium = async () => {
|
|
19
|
+
try {
|
|
20
|
+
const { chromium } = await import('playwright')
|
|
21
|
+
return chromium
|
|
22
|
+
} catch {
|
|
23
|
+
console.error(
|
|
24
|
+
'✗ `sorb-seed capture` needs Playwright (it is an optional peer dep).\n' +
|
|
25
|
+
' Install it where you run capture:\n' +
|
|
26
|
+
' npm install playwright # its postinstall fetches Chromium\n' +
|
|
27
|
+
' (or: npm install playwright && npx playwright install chromium)',
|
|
28
|
+
)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The `playwright` PACKAGE can be installed while its Chromium BROWSER binary is
|
|
34
|
+
// not (that's a separate `npx playwright install chromium` step). Launching then
|
|
35
|
+
// throws a raw "Executable doesn't exist" error — turn it into the same
|
|
36
|
+
// actionable guidance the missing-package path already gives.
|
|
37
|
+
export const launchChromium = async (chromium) => {
|
|
38
|
+
try {
|
|
39
|
+
return await chromium.launch()
|
|
40
|
+
} catch (e) {
|
|
41
|
+
const msg = e && e.message ? e.message : String(e)
|
|
42
|
+
if (/Executable doesn't exist|playwright install|browserType\.launch/i.test(msg)) {
|
|
43
|
+
console.error(
|
|
44
|
+
'✗ `sorb-seed capture` found Playwright but its Chromium browser is not installed.\n' +
|
|
45
|
+
' Install the browser where you run capture:\n' +
|
|
46
|
+
' npx playwright install chromium',
|
|
47
|
+
)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
throw e
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Bundle the walker into a single IIFE string we can addInitScript() into
|
|
55
|
+
// every page. Playwright can't pass functions across the boundary directly,
|
|
56
|
+
// and our walker has cross-file imports → bundling is the clean answer.
|
|
57
|
+
const buildWalkerBundle = async () => {
|
|
58
|
+
const here = dirname(new URL(import.meta.url).pathname)
|
|
59
|
+
const out = await build({
|
|
60
|
+
entryPoints: [resolve(here, '..', 'capture.js')],
|
|
61
|
+
bundle: true,
|
|
62
|
+
format: 'iife',
|
|
63
|
+
platform: 'browser',
|
|
64
|
+
write: false,
|
|
65
|
+
logLevel: 'silent',
|
|
66
|
+
target: 'es2020',
|
|
67
|
+
})
|
|
68
|
+
return out.outputFiles[0].text
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const isStoryEntry = (e) =>
|
|
72
|
+
e && (e.type === 'story' || (e.type === undefined && e.importPath)) // SB7/8: type:'story'
|
|
73
|
+
|
|
74
|
+
// componentName: "Button" from "./src/.../Button.stories.jsx"
|
|
75
|
+
const componentNameFromImportPath = (importPath) => {
|
|
76
|
+
const file = basename(importPath, extname(importPath)) // "Button.stories"
|
|
77
|
+
return file.replace(/\.stories$/i, '')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// sorb.config.json may set seed.storybookUrl. Fall back to localhost.
|
|
81
|
+
export const storybookUrlOf = (config) =>
|
|
82
|
+
(config.seed && config.seed.storybookUrl) || 'http://localhost:6006'
|
|
83
|
+
|
|
84
|
+
// ─── Playwright session (browser + context + walker init script) ──────────
|
|
85
|
+
// Lazily launched on the first captureGeometry() call and reused across every
|
|
86
|
+
// unit in a run — exactly today's single-launch/many-pages lifecycle. Not
|
|
87
|
+
// part of the shared SourceConnector contract (that's just listUnits /
|
|
88
|
+
// captureGeometry / readTokens); captureCli.js calls closeSession() once
|
|
89
|
+
// after it has processed every unit, mirroring today's single browser.close().
|
|
90
|
+
let session = null // { browser, ctx, sbUrl }
|
|
91
|
+
|
|
92
|
+
const ensureSession = async (sbUrl) => {
|
|
93
|
+
if (session && session.sbUrl === sbUrl) return session
|
|
94
|
+
if (session) await closeSession()
|
|
95
|
+
const chromium = await loadChromium()
|
|
96
|
+
const walker = await buildWalkerBundle()
|
|
97
|
+
const browser = await launchChromium(chromium)
|
|
98
|
+
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } })
|
|
99
|
+
await ctx.addInitScript({ content: walker })
|
|
100
|
+
session = { browser, ctx, sbUrl }
|
|
101
|
+
return session
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const closeSession = async () => {
|
|
105
|
+
if (session) {
|
|
106
|
+
await session.browser.close()
|
|
107
|
+
session = null
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── SourceConnector implementation ────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
const listUnits = async (config) => {
|
|
114
|
+
const sbUrl = storybookUrlOf(config).replace(/\/$/, '')
|
|
115
|
+
console.log(`→ Storybook: ${sbUrl}`)
|
|
116
|
+
let sbIndex
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(`${sbUrl}/index.json`)
|
|
119
|
+
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
120
|
+
sbIndex = await res.json()
|
|
121
|
+
} catch (e) {
|
|
122
|
+
console.error('✗ Could not fetch Storybook index:', e.message)
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
const entries = Object.values(sbIndex.entries || sbIndex.stories || {}).filter(isStoryEntry)
|
|
126
|
+
return entries.map((e) => ({
|
|
127
|
+
id: e.id,
|
|
128
|
+
name: e.name,
|
|
129
|
+
title: e.title,
|
|
130
|
+
importPath: e.importPath,
|
|
131
|
+
}))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const captureGeometry = async (unit, config) => {
|
|
135
|
+
const sbUrl = storybookUrlOf(config).replace(/\/$/, '')
|
|
136
|
+
const { ctx } = await ensureSession(sbUrl)
|
|
137
|
+
const url = `${sbUrl}/iframe.html?id=${unit.id}&viewMode=story`
|
|
138
|
+
const page = await ctx.newPage()
|
|
139
|
+
try {
|
|
140
|
+
console.log(` · ${unit.id}`)
|
|
141
|
+
await page.goto(url, { waitUntil: 'load' })
|
|
142
|
+
// Wait for Storybook to actually render the story.
|
|
143
|
+
await page
|
|
144
|
+
.waitForFunction(
|
|
145
|
+
() => !!document.querySelector('#storybook-root *'),
|
|
146
|
+
{ timeout: 15000 },
|
|
147
|
+
)
|
|
148
|
+
.catch(() => {})
|
|
149
|
+
await page.evaluate(() => document.fonts && document.fonts.ready)
|
|
150
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {})
|
|
151
|
+
|
|
152
|
+
const rawTree = await page.evaluate(() => {
|
|
153
|
+
const root = document.querySelector('#storybook-root')
|
|
154
|
+
return root ? window.__sorbCapture(root) : null
|
|
155
|
+
})
|
|
156
|
+
if (!rawTree) {
|
|
157
|
+
console.warn(' ⚠ no #storybook-root content; skipped')
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
return rawTree
|
|
161
|
+
} finally {
|
|
162
|
+
await page.close()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Thin wrapper over the resolved bindable token map produced by `sorb-seed
|
|
167
|
+
// resolve` (Style Dictionary build against the DTCG sources). Capture's own
|
|
168
|
+
// generic pipeline still reads `.sorb/resolved.json` directly (unchanged) —
|
|
169
|
+
// this exists so the connector satisfies the SOURCE contract for other
|
|
170
|
+
// consumers. Does not move the SD internals (those stay owned by `resolve`).
|
|
171
|
+
const readTokens = async (config) => {
|
|
172
|
+
const cwd = process.cwd()
|
|
173
|
+
const p = resolve(cwd, '.sorb/resolved.json')
|
|
174
|
+
if (!existsSync(p)) {
|
|
175
|
+
throw new Error('No .sorb/resolved.json — run `sorb-seed resolve` first.')
|
|
176
|
+
}
|
|
177
|
+
const data = JSON.parse(readFileSync(p, 'utf-8'))
|
|
178
|
+
return Array.isArray(data) ? data : data.tokens
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const storybookDomConnector = {
|
|
182
|
+
id: 'storybook-dom',
|
|
183
|
+
listUnits,
|
|
184
|
+
captureGeometry,
|
|
185
|
+
readTokens,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
registerSource(storybookDomConnector)
|