@sorb/seed 0.2.0 → 0.3.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/index.js +27 -0
- package/src/sources/figmaPlugin.js +83 -0
- package/src/sources/figmaPlugin.test.js +65 -0
- package/src/sources/storybookDom.js +188 -0
|
@@ -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)
|