@open-pencil/cli 0.7.0 → 0.9.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 +5 -3
- package/src/app-client.ts +56 -0
- package/src/commands/analyze/clusters.ts +23 -82
- package/src/commands/analyze/colors.ts +28 -152
- package/src/commands/analyze/spacing.ts +18 -49
- package/src/commands/analyze/typography.ts +23 -65
- package/src/commands/eval.ts +27 -13
- package/src/commands/export.ts +114 -56
- package/src/commands/find.ts +22 -39
- package/src/commands/info.ts +18 -30
- package/src/commands/node.ts +44 -54
- package/src/commands/pages.ts +16 -25
- package/src/commands/query.ts +90 -0
- package/src/commands/tree.ts +30 -34
- package/src/commands/variables.ts +19 -51
- package/src/format.ts +2 -2
- package/src/headless.ts +2 -2
- package/src/index.ts +3 -1
package/src/commands/node.ts
CHANGED
|
@@ -1,83 +1,73 @@
|
|
|
1
1
|
import { defineCommand } from 'citty'
|
|
2
2
|
|
|
3
3
|
import { loadDocument } from '../headless'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { isAppMode, requireFile, rpc } from '../app-client'
|
|
5
|
+
import { fmtNode, printError, formatType } from '../format'
|
|
6
|
+
import { executeRpcCommand, colorToHex } from '@open-pencil/core'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const details = nodeDetails(node)
|
|
8
|
+
import type { Color, NodeResult } from '@open-pencil/core'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
details.text = node.text.length > 80 ? node.text.slice(0, 80) + '…' : node.text
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (node.childIds.length > 0) details.children = node.childIds.length
|
|
18
|
-
|
|
19
|
-
for (const [field, varId] of Object.entries(node.boundVariables)) {
|
|
20
|
-
const variable = graph.variables.get(varId)
|
|
21
|
-
details[`var:${field}`] = variable?.name ?? varId
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return details
|
|
10
|
+
async function getData(file: string | undefined, id: string): Promise<NodeResult | { error: string }> {
|
|
11
|
+
if (isAppMode(file)) return rpc<NodeResult>('node', { id })
|
|
12
|
+
const graph = await loadDocument(requireFile(file))
|
|
13
|
+
return executeRpcCommand(graph, 'node', { id }) as NodeResult | { error: string }
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
export default defineCommand({
|
|
28
17
|
meta: { description: 'Show detailed node properties by ID' },
|
|
29
18
|
args: {
|
|
30
|
-
file: { type: 'positional', description: '.fig file path', required:
|
|
19
|
+
file: { type: 'positional', description: '.fig file path (omit to connect to running app)', required: false },
|
|
31
20
|
id: { type: 'string', description: 'Node ID', required: true },
|
|
32
21
|
json: { type: 'boolean', description: 'Output as JSON' }
|
|
33
22
|
},
|
|
34
23
|
async run({ args }) {
|
|
35
|
-
const
|
|
36
|
-
const node = graph.getNode(args.id)
|
|
24
|
+
const data = await getData(args.file, args.id)
|
|
37
25
|
|
|
38
|
-
if (
|
|
39
|
-
printError(
|
|
26
|
+
if ('error' in data) {
|
|
27
|
+
printError(data.error)
|
|
40
28
|
process.exit(1)
|
|
41
29
|
}
|
|
42
30
|
|
|
43
31
|
if (args.json) {
|
|
44
|
-
|
|
45
|
-
const children = childIds.length
|
|
46
|
-
const parent = parentId ? graph.getNode(parentId) : undefined
|
|
47
|
-
console.log(
|
|
48
|
-
JSON.stringify(
|
|
49
|
-
{
|
|
50
|
-
...rest,
|
|
51
|
-
parent: parent ? { id: parent.id, name: parent.name, type: parent.type } : null,
|
|
52
|
-
children
|
|
53
|
-
},
|
|
54
|
-
null,
|
|
55
|
-
2
|
|
56
|
-
)
|
|
57
|
-
)
|
|
32
|
+
console.log(JSON.stringify(data, null, 2))
|
|
58
33
|
return
|
|
59
34
|
}
|
|
60
35
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
header: `[${formatType(child.type)}] "${child.name}" (${child.id})`
|
|
71
|
-
}))
|
|
36
|
+
const nodeData = {
|
|
37
|
+
type: formatType(data.type),
|
|
38
|
+
name: data.name,
|
|
39
|
+
id: data.id,
|
|
40
|
+
width: data.width,
|
|
41
|
+
height: data.height,
|
|
42
|
+
x: data.x,
|
|
43
|
+
y: data.y
|
|
44
|
+
}
|
|
72
45
|
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
const details: Record<string, unknown> = {}
|
|
47
|
+
if (data.parent) details.parent = `${data.parent.name} (${data.parent.id})`
|
|
48
|
+
if (data.text) details.text = data.text
|
|
49
|
+
if (data.fills.length > 0) {
|
|
50
|
+
const solid = (data.fills as Array<{ type: string; visible: boolean; color: Color; opacity: number }>)
|
|
51
|
+
.find((f) => f.type === 'SOLID' && f.visible)
|
|
52
|
+
if (solid) {
|
|
53
|
+
const hex = colorToHex(solid.color)
|
|
54
|
+
details.fill = solid.opacity < 1 ? `${hex} ${Math.round(solid.opacity * 100)}%` : hex
|
|
75
55
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
56
|
+
}
|
|
57
|
+
if (data.cornerRadius) details.radius = `${data.cornerRadius}px`
|
|
58
|
+
if (data.rotation) details.rotate = `${Math.round(data.rotation)}°`
|
|
59
|
+
if (data.opacity < 1) details.opacity = data.opacity
|
|
60
|
+
if (!data.visible) details.visible = false
|
|
61
|
+
if (data.locked) details.locked = true
|
|
62
|
+
if (data.fontFamily) details.font = `${data.fontSize}px ${data.fontFamily}`
|
|
63
|
+
if (data.layoutMode !== 'NONE') details.layout = data.layoutMode.toLowerCase()
|
|
64
|
+
if (data.children > 0) details.children = data.children
|
|
65
|
+
for (const [field, name] of Object.entries(data.boundVariables)) {
|
|
66
|
+
details[`var:${field}`] = name
|
|
79
67
|
}
|
|
80
68
|
|
|
81
69
|
console.log('')
|
|
70
|
+
console.log(fmtNode(nodeData, details))
|
|
71
|
+
console.log('')
|
|
82
72
|
}
|
|
83
73
|
})
|
package/src/commands/pages.ts
CHANGED
|
@@ -1,38 +1,29 @@
|
|
|
1
1
|
import { defineCommand } from 'citty'
|
|
2
2
|
|
|
3
3
|
import { loadDocument } from '../headless'
|
|
4
|
-
import {
|
|
4
|
+
import { isAppMode, requireFile, rpc } from '../app-client'
|
|
5
|
+
import { bold, fmtList, entity } from '../format'
|
|
6
|
+
|
|
7
|
+
import type { PageItem } from '@open-pencil/core'
|
|
8
|
+
import { executeRpcCommand } from '@open-pencil/core'
|
|
9
|
+
|
|
10
|
+
async function getData(file?: string): Promise<PageItem[]> {
|
|
11
|
+
if (isAppMode(file)) return rpc<PageItem[]>('pages')
|
|
12
|
+
const graph = await loadDocument(requireFile(file))
|
|
13
|
+
return executeRpcCommand(graph, 'pages', undefined) as PageItem[]
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
export default defineCommand({
|
|
7
17
|
meta: { description: 'List pages in a .fig file' },
|
|
8
18
|
args: {
|
|
9
|
-
file: { type: 'positional', description: '.fig file path', required:
|
|
19
|
+
file: { type: 'positional', description: '.fig file path (omit to connect to running app)', required: false },
|
|
10
20
|
json: { type: 'boolean', description: 'Output as JSON' }
|
|
11
21
|
},
|
|
12
22
|
async run({ args }) {
|
|
13
|
-
const
|
|
14
|
-
const pages = graph.getPages()
|
|
15
|
-
|
|
16
|
-
const countNodes = (pageId: string): number => {
|
|
17
|
-
let count = 0
|
|
18
|
-
const walk = (id: string) => {
|
|
19
|
-
count++
|
|
20
|
-
const n = graph.getNode(id)
|
|
21
|
-
if (n) for (const cid of n.childIds) walk(cid)
|
|
22
|
-
}
|
|
23
|
-
const page = graph.getNode(pageId)
|
|
24
|
-
if (page) for (const cid of page.childIds) walk(cid)
|
|
25
|
-
return count
|
|
26
|
-
}
|
|
23
|
+
const pages = await getData(args.file)
|
|
27
24
|
|
|
28
25
|
if (args.json) {
|
|
29
|
-
console.log(
|
|
30
|
-
JSON.stringify(
|
|
31
|
-
pages.map((p) => ({ id: p.id, name: p.name, nodes: countNodes(p.id) })),
|
|
32
|
-
null,
|
|
33
|
-
2
|
|
34
|
-
)
|
|
35
|
-
)
|
|
26
|
+
console.log(JSON.stringify(pages, null, 2))
|
|
36
27
|
return
|
|
37
28
|
}
|
|
38
29
|
|
|
@@ -42,8 +33,8 @@ export default defineCommand({
|
|
|
42
33
|
console.log(
|
|
43
34
|
fmtList(
|
|
44
35
|
pages.map((page) => ({
|
|
45
|
-
header: entity(
|
|
46
|
-
details: { nodes:
|
|
36
|
+
header: entity('page', page.name, page.id),
|
|
37
|
+
details: { nodes: page.nodes }
|
|
47
38
|
})),
|
|
48
39
|
{ compact: true }
|
|
49
40
|
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { defineCommand } from 'citty'
|
|
2
|
+
|
|
3
|
+
import { loadDocument } from '../headless'
|
|
4
|
+
import { isAppMode, requireFile, rpc } from '../app-client'
|
|
5
|
+
import { fmtList, printError, bold, entity, formatType } from '../format'
|
|
6
|
+
import { executeRpcCommand } from '@open-pencil/core'
|
|
7
|
+
|
|
8
|
+
import type { QueryNodeResult } from '@open-pencil/core'
|
|
9
|
+
|
|
10
|
+
async function getData(
|
|
11
|
+
file: string | undefined,
|
|
12
|
+
args: { selector: string; page?: string; limit?: string }
|
|
13
|
+
): Promise<QueryNodeResult[] | { error: string }> {
|
|
14
|
+
const rpcArgs = {
|
|
15
|
+
selector: args.selector,
|
|
16
|
+
page: args.page,
|
|
17
|
+
limit: args.limit ? Number(args.limit) : undefined
|
|
18
|
+
}
|
|
19
|
+
if (isAppMode(file)) return rpc<QueryNodeResult[]>('query', rpcArgs)
|
|
20
|
+
const graph = await loadDocument(requireFile(file))
|
|
21
|
+
return await executeRpcCommand(graph, 'query', rpcArgs) as QueryNodeResult[] | { error: string }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default defineCommand({
|
|
25
|
+
meta: {
|
|
26
|
+
description: `Query nodes using XPath selectors
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
open-pencil query file.fig "//FRAME" # All frames
|
|
30
|
+
open-pencil query file.fig "//FRAME[@width < 300]" # Frames narrower than 300px
|
|
31
|
+
open-pencil query file.fig "//COMPONENT[starts-with(@name, 'Button')]" # Components starting with Button
|
|
32
|
+
open-pencil query file.fig "//SECTION/FRAME" # Direct frame children of sections
|
|
33
|
+
open-pencil query file.fig "//SECTION//TEXT" # All text inside sections
|
|
34
|
+
open-pencil query file.fig "//*[@cornerRadius > 0]" # Any node with corner radius`
|
|
35
|
+
},
|
|
36
|
+
args: {
|
|
37
|
+
file: {
|
|
38
|
+
type: 'positional',
|
|
39
|
+
description: '.fig file path (omit to connect to running app)',
|
|
40
|
+
required: false
|
|
41
|
+
},
|
|
42
|
+
selector: {
|
|
43
|
+
type: 'positional',
|
|
44
|
+
description:
|
|
45
|
+
'XPath selector (e.g., //FRAME[@width < 300], //TEXT[contains(@name, "Label")])',
|
|
46
|
+
required: true
|
|
47
|
+
},
|
|
48
|
+
page: { type: 'string', description: 'Page name (default: all pages)' },
|
|
49
|
+
limit: { type: 'string', description: 'Max results (default: 1000)', default: '1000' },
|
|
50
|
+
json: { type: 'boolean', description: 'Output as JSON' }
|
|
51
|
+
},
|
|
52
|
+
async run({ args }) {
|
|
53
|
+
const results = await getData(args.file, {
|
|
54
|
+
selector: args.selector,
|
|
55
|
+
page: args.page,
|
|
56
|
+
limit: args.limit
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if ('error' in results) {
|
|
60
|
+
printError(results.error)
|
|
61
|
+
process.exit(1)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (args.json) {
|
|
65
|
+
console.log(JSON.stringify(results, null, 2))
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (results.length === 0) {
|
|
70
|
+
console.log('No nodes found.')
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log('')
|
|
75
|
+
console.log(bold(` Found ${results.length} node${results.length > 1 ? 's' : ''}`))
|
|
76
|
+
console.log('')
|
|
77
|
+
console.log(
|
|
78
|
+
fmtList(
|
|
79
|
+
results.map((n) => ({
|
|
80
|
+
header: entity(
|
|
81
|
+
formatType(n.type),
|
|
82
|
+
`${n.name} ${n.width}×${n.height}`,
|
|
83
|
+
n.id
|
|
84
|
+
)
|
|
85
|
+
}))
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
console.log('')
|
|
89
|
+
}
|
|
90
|
+
})
|
package/src/commands/tree.ts
CHANGED
|
@@ -1,59 +1,55 @@
|
|
|
1
1
|
import { defineCommand } from 'citty'
|
|
2
2
|
|
|
3
3
|
import { loadDocument } from '../headless'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { isAppMode, requireFile, rpc } from '../app-client'
|
|
5
|
+
import { fmtTree, printError, entity, formatType } from '../format'
|
|
6
|
+
import { executeRpcCommand } from '@open-pencil/core'
|
|
7
|
+
|
|
8
|
+
import type { TreeResult, TreeNodeResult } from '@open-pencil/core'
|
|
9
|
+
import type { TreeNode } from 'agentfmt'
|
|
10
|
+
|
|
11
|
+
function toAgentfmtTree(node: TreeNodeResult, maxDepth: number, depth = 0): TreeNode {
|
|
12
|
+
const treeNode: TreeNode = {
|
|
13
|
+
header: entity(formatType(node.type), node.name, node.id)
|
|
14
|
+
}
|
|
15
|
+
if (node.children && depth < maxDepth) {
|
|
16
|
+
treeNode.children = node.children.map((c) => toAgentfmtTree(c, maxDepth, depth + 1))
|
|
17
|
+
}
|
|
18
|
+
return treeNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getData(file: string | undefined, args: { page?: string; depth?: string }): Promise<TreeResult | { error: string }> {
|
|
22
|
+
const rpcArgs = { page: args.page, depth: args.depth ? Number(args.depth) : undefined }
|
|
23
|
+
if (isAppMode(file)) return rpc<TreeResult>('tree', rpcArgs)
|
|
24
|
+
const graph = await loadDocument(requireFile(file))
|
|
25
|
+
return executeRpcCommand(graph, 'tree', rpcArgs) as TreeResult | { error: string }
|
|
26
|
+
}
|
|
6
27
|
|
|
7
28
|
export default defineCommand({
|
|
8
29
|
meta: { description: 'Print the node tree' },
|
|
9
30
|
args: {
|
|
10
|
-
file: { type: 'positional', description: '.fig file path', required:
|
|
31
|
+
file: { type: 'positional', description: '.fig file path (omit to connect to running app)', required: false },
|
|
11
32
|
page: { type: 'string', description: 'Page name (default: first page)' },
|
|
12
33
|
depth: { type: 'string', description: 'Max depth (default: unlimited)' },
|
|
13
34
|
json: { type: 'boolean', description: 'Output as JSON' }
|
|
14
35
|
},
|
|
15
36
|
async run({ args }) {
|
|
16
|
-
const
|
|
17
|
-
const pages = graph.getPages()
|
|
37
|
+
const data = await getData(args.file, args)
|
|
18
38
|
const maxDepth = args.depth ? Number(args.depth) : Infinity
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
: pages[0]
|
|
23
|
-
|
|
24
|
-
if (!page) {
|
|
25
|
-
printError(`Page "${args.page}" not found. Available: ${pages.map((p) => p.name).join(', ')}`)
|
|
40
|
+
if ('error' in data) {
|
|
41
|
+
printError(data.error)
|
|
26
42
|
process.exit(1)
|
|
27
43
|
}
|
|
28
44
|
|
|
29
45
|
if (args.json) {
|
|
30
|
-
|
|
31
|
-
const node = graph.getNode(id)
|
|
32
|
-
if (!node) return null
|
|
33
|
-
const result: Record<string, unknown> = {
|
|
34
|
-
id: node.id,
|
|
35
|
-
name: node.name,
|
|
36
|
-
type: node.type,
|
|
37
|
-
x: Math.round(node.x),
|
|
38
|
-
y: Math.round(node.y),
|
|
39
|
-
width: Math.round(node.width),
|
|
40
|
-
height: Math.round(node.height)
|
|
41
|
-
}
|
|
42
|
-
if (node.childIds.length > 0 && depth < maxDepth) {
|
|
43
|
-
result.children = node.childIds.map((cid) => buildJson(cid, depth + 1)).filter(Boolean)
|
|
44
|
-
}
|
|
45
|
-
return result
|
|
46
|
-
}
|
|
47
|
-
console.log(JSON.stringify(page.childIds.map((id) => buildJson(id, 0)), null, 2))
|
|
46
|
+
console.log(JSON.stringify(data.children, null, 2))
|
|
48
47
|
return
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
const root = {
|
|
52
|
-
header: entity(formatType(page.type), page.name, page.id),
|
|
53
|
-
children:
|
|
54
|
-
.map((id) => graph.getNode(id))
|
|
55
|
-
.filter((n): n is SceneNode => n !== undefined)
|
|
56
|
-
.map((child) => nodeToTreeNode(graph, child, maxDepth))
|
|
51
|
+
header: entity(formatType(data.page.type), data.page.name, data.page.id),
|
|
52
|
+
children: data.children.map((c) => toAgentfmtTree(c, maxDepth))
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
console.log('')
|
|
@@ -1,82 +1,50 @@
|
|
|
1
1
|
import { defineCommand } from 'citty'
|
|
2
2
|
|
|
3
3
|
import { loadDocument } from '../headless'
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { isAppMode, requireFile, rpc } from '../app-client'
|
|
5
|
+
import { bold, entity, fmtList, fmtSummary } from '../format'
|
|
6
|
+
import { executeRpcCommand } from '@open-pencil/core'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const modeId = graph.getActiveModeId(variable.collectionId)
|
|
9
|
-
const raw = variable.valuesByMode[modeId]
|
|
10
|
-
if (raw === undefined) return '–'
|
|
8
|
+
import type { VariablesResult } from '@open-pencil/core'
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (typeof raw === 'object' && 'r' in raw) {
|
|
18
|
-
const { r, g, b } = raw as { r: number; g: number; b: number }
|
|
19
|
-
return (
|
|
20
|
-
'#' +
|
|
21
|
-
[r, g, b]
|
|
22
|
-
.map((c) =>
|
|
23
|
-
Math.round(c * 255)
|
|
24
|
-
.toString(16)
|
|
25
|
-
.padStart(2, '0')
|
|
26
|
-
)
|
|
27
|
-
.join('')
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return String(raw)
|
|
10
|
+
async function getData(file: string | undefined, args: { collection?: string; type?: string }): Promise<VariablesResult> {
|
|
11
|
+
const rpcArgs = { collection: args.collection, type: args.type }
|
|
12
|
+
if (isAppMode(file)) return rpc<VariablesResult>('variables', rpcArgs)
|
|
13
|
+
const graph = await loadDocument(requireFile(file))
|
|
14
|
+
return executeRpcCommand(graph, 'variables', rpcArgs) as VariablesResult
|
|
32
15
|
}
|
|
33
16
|
|
|
34
17
|
export default defineCommand({
|
|
35
18
|
meta: { description: 'List design variables and collections' },
|
|
36
19
|
args: {
|
|
37
|
-
file: { type: 'positional', description: '.fig file path', required:
|
|
20
|
+
file: { type: 'positional', description: '.fig file path (omit to connect to running app)', required: false },
|
|
38
21
|
collection: { type: 'string', description: 'Filter by collection name' },
|
|
39
22
|
type: { type: 'string', description: 'Filter by type: COLOR, FLOAT, STRING, BOOLEAN' },
|
|
40
23
|
json: { type: 'boolean', description: 'Output as JSON' }
|
|
41
24
|
},
|
|
42
25
|
async run({ args }) {
|
|
43
|
-
const
|
|
26
|
+
const data = await getData(args.file, args)
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
const variables = [...graph.variables.values()]
|
|
47
|
-
|
|
48
|
-
if (variables.length === 0) {
|
|
28
|
+
if (data.totalVariables === 0) {
|
|
49
29
|
console.log('No variables found.')
|
|
50
30
|
return
|
|
51
31
|
}
|
|
52
32
|
|
|
53
33
|
if (args.json) {
|
|
54
|
-
console.log(JSON.stringify(
|
|
34
|
+
console.log(JSON.stringify(data, null, 2))
|
|
55
35
|
return
|
|
56
36
|
}
|
|
57
37
|
|
|
58
|
-
const typeFilter = args.type?.toUpperCase()
|
|
59
|
-
const collFilter = args.collection?.toLowerCase()
|
|
60
|
-
|
|
61
38
|
console.log('')
|
|
62
39
|
|
|
63
|
-
for (const coll of collections) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const collVars = graph
|
|
67
|
-
.getVariablesForCollection(coll.id)
|
|
68
|
-
.filter((v) => !typeFilter || v.type === typeFilter)
|
|
69
|
-
|
|
70
|
-
if (collVars.length === 0) continue
|
|
71
|
-
|
|
72
|
-
const modes = coll.modes.map((m) => m.name).join(', ')
|
|
73
|
-
console.log(bold(` ${coll.name}`) + ` (${modes})`)
|
|
40
|
+
for (const coll of data.collections) {
|
|
41
|
+
console.log(bold(entity(coll.name, coll.modes.join(', '))))
|
|
74
42
|
console.log('')
|
|
75
43
|
console.log(
|
|
76
44
|
fmtList(
|
|
77
|
-
|
|
45
|
+
coll.variables.map((v) => ({
|
|
78
46
|
header: v.name,
|
|
79
|
-
details: { value:
|
|
47
|
+
details: { value: v.value, type: v.type.toLowerCase() }
|
|
80
48
|
})),
|
|
81
49
|
{ compact: true }
|
|
82
50
|
)
|
|
@@ -86,8 +54,8 @@ export default defineCommand({
|
|
|
86
54
|
|
|
87
55
|
console.log(
|
|
88
56
|
fmtSummary({
|
|
89
|
-
variables:
|
|
90
|
-
collections:
|
|
57
|
+
variables: data.totalVariables,
|
|
58
|
+
collections: data.totalCollections
|
|
91
59
|
})
|
|
92
60
|
)
|
|
93
61
|
console.log('')
|
package/src/format.ts
CHANGED
|
@@ -48,7 +48,7 @@ export function formatBox(node: SceneNode): string {
|
|
|
48
48
|
function formatFill(node: SceneNode): string | null {
|
|
49
49
|
if (!node.fills.length) return null
|
|
50
50
|
const solid = node.fills.find((f) => f.type === 'SOLID' && f.visible)
|
|
51
|
-
if (!solid
|
|
51
|
+
if (!solid?.color) return null
|
|
52
52
|
const { r, g, b } = solid.color
|
|
53
53
|
const hex = '#' + [r, g, b].map((c) => Math.round(c * 255).toString(16).padStart(2, '0')).join('')
|
|
54
54
|
return solid.opacity < 1 ? `${hex} ${Math.round(solid.opacity * 100)}%` : hex
|
|
@@ -56,7 +56,7 @@ function formatFill(node: SceneNode): string | null {
|
|
|
56
56
|
|
|
57
57
|
function formatStroke(node: SceneNode): string | null {
|
|
58
58
|
if (!node.strokes.length) return null
|
|
59
|
-
const s = node.strokes[0]
|
|
59
|
+
const s = node.strokes[0]
|
|
60
60
|
const { r, g, b } = s.color
|
|
61
61
|
const hex = '#' + [r, g, b].map((c) => Math.round(c * 255).toString(16).padStart(2, '0')).join('')
|
|
62
62
|
return `${hex} ${s.weight}px`
|
package/src/headless.ts
CHANGED
|
@@ -2,14 +2,14 @@ import CanvasKitInit from 'canvaskit-wasm/full'
|
|
|
2
2
|
import type { CanvasKit } from 'canvaskit-wasm'
|
|
3
3
|
import {
|
|
4
4
|
parseFigFile,
|
|
5
|
-
SceneGraph,
|
|
5
|
+
type SceneGraph,
|
|
6
|
+
type ExportFormat,
|
|
6
7
|
SkiaRenderer,
|
|
7
8
|
computeAllLayouts,
|
|
8
9
|
loadFont,
|
|
9
10
|
renderNodesToImage,
|
|
10
11
|
renderThumbnail
|
|
11
12
|
} from '@open-pencil/core'
|
|
12
|
-
import type { ExportFormat } from '@open-pencil/core'
|
|
13
13
|
|
|
14
14
|
let ck: CanvasKit | null = null
|
|
15
15
|
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import evalCmd from './commands/eval'
|
|
|
6
6
|
import exportCmd from './commands/export'
|
|
7
7
|
import find from './commands/find'
|
|
8
8
|
import info from './commands/info'
|
|
9
|
+
import query from './commands/query'
|
|
9
10
|
import node from './commands/node'
|
|
10
11
|
import pages from './commands/pages'
|
|
11
12
|
import tree from './commands/tree'
|
|
@@ -25,6 +26,7 @@ const main = defineCommand({
|
|
|
25
26
|
export: exportCmd,
|
|
26
27
|
find,
|
|
27
28
|
info,
|
|
29
|
+
query,
|
|
28
30
|
node,
|
|
29
31
|
pages,
|
|
30
32
|
tree,
|
|
@@ -32,4 +34,4 @@ const main = defineCommand({
|
|
|
32
34
|
}
|
|
33
35
|
})
|
|
34
36
|
|
|
35
|
-
runMain(main)
|
|
37
|
+
void runMain(main)
|