@weft-js/cli 0.0.0-stage → 0.1.1
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/LICENSE +16 -0
- package/README.md +74 -2
- package/bin/weft.mjs +8 -0
- package/docs/async.md +40 -0
- package/docs/browser.md +39 -0
- package/docs/config.md +93 -0
- package/docs/deploy.md +83 -0
- package/docs/design.md +97 -0
- package/docs/fonts.md +70 -0
- package/docs/icons.md +102 -0
- package/docs/lists.md +23 -0
- package/docs/meta.md +82 -0
- package/docs/plugins.md +143 -0
- package/docs/props.md +77 -0
- package/docs/rules.md +32 -0
- package/docs/rust.md +223 -0
- package/docs/start-short.md +8 -0
- package/docs/start.md +422 -0
- package/docs/state.md +96 -0
- package/docs/style.md +59 -0
- package/docs/surface.md +111 -0
- package/docs/testing.md +71 -0
- package/docs/ui-state.md +92 -0
- package/docs/verify.md +48 -0
- package/package.json +39 -4
- package/src/add-rust.mjs +123 -0
- package/src/add.mjs +358 -0
- package/src/agents-md.mjs +134 -0
- package/src/alias.mjs +59 -0
- package/src/audit.mjs +366 -0
- package/src/browser/cli.mjs +34 -0
- package/src/browser/client.mjs +132 -0
- package/src/browser/commands.mjs +143 -0
- package/src/catalog.mjs +496 -0
- package/src/check.mjs +330 -0
- package/src/cli.mjs +364 -0
- package/src/config.d.ts +1 -0
- package/src/config.mjs +5 -0
- package/src/contributions.mjs +69 -0
- package/src/db-layer.mjs +33 -0
- package/src/deploy/cdn.mjs +64 -0
- package/src/deploy/files.mjs +187 -0
- package/src/deploy/fit.mjs +62 -0
- package/src/deploy/hosts.mjs +56 -0
- package/src/deploy/index.mjs +273 -0
- package/src/deploy/needs.mjs +92 -0
- package/src/design-checks.mjs +439 -0
- package/src/diag.mjs +74 -0
- package/src/docs.mjs +132 -0
- package/src/doctor.mjs +30 -0
- package/src/files.mjs +47 -0
- package/src/fix.mjs +40 -0
- package/src/gate.mjs +276 -0
- package/src/index.mjs +9 -0
- package/src/infer/engine.cjs +525 -0
- package/src/infer/facts.cjs +252 -0
- package/src/infer/hosts.cjs +114 -0
- package/src/infer/lsp.mjs +131 -0
- package/src/infer/session.cjs +105 -0
- package/src/infer/shadow.cjs +46 -0
- package/src/infer/worker.cjs +97 -0
- package/src/jsx-scan.mjs +86 -0
- package/src/load-config.mjs +303 -0
- package/src/map.mjs +110 -0
- package/src/mcp.mjs +195 -0
- package/src/meta-table.mjs +28 -0
- package/src/mutate.mjs +140 -0
- package/src/plugin-checks.mjs +89 -0
- package/src/plugins.mjs +172 -0
- package/src/project.mjs +219 -0
- package/src/props-checks.mjs +46 -0
- package/src/props-listener.mjs +38 -0
- package/src/react-checks.mjs +36 -0
- package/src/share-checks.mjs +74 -0
- package/src/site.mjs +19 -0
- package/src/slots.mjs +96 -0
- package/src/start.mjs +296 -0
- package/src/state-checks.mjs +213 -0
- package/src/style.mjs +320 -0
- package/src/surface-checks.mjs +218 -0
- package/src/tailwind.mjs +22 -0
- package/src/tasks.mjs +99 -0
- package/src/testdiff.mjs +157 -0
- package/src/tests.mjs +572 -0
- package/src/text-checks.mjs +172 -0
- package/src/tokens.mjs +24 -0
- package/src/trust.mjs +282 -0
- package/src/weft.mjs +66 -0
- package/src/workspace.mjs +105 -0
- package/ts-plugin/index.js +137 -0
- package/ts-plugin/package.json +1 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// `weft browser` commands, shared by the CLI and the `weft mcp` browser tools. Each takes structured params and
|
|
2
|
+
// returns JSON; the page side is @weft-js/core/agent (packages/weft/src/agent.ts).
|
|
3
|
+
|
|
4
|
+
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { dirname, isAbsolute, join } from 'node:path'
|
|
6
|
+
import { call, close, launch, screenshot, spill, tabs, waitForTab } from './client.mjs'
|
|
7
|
+
|
|
8
|
+
export const actions = ['click', 'dblclick', 'hover', 'focus', 'clear', 'fill', 'type', 'press', 'select', 'check', 'uncheck']
|
|
9
|
+
export const expectations = ['visible', 'absent', 'text', 'exactText', 'value', 'checked', 'unchecked', 'disabled', 'enabled', 'focused', 'count']
|
|
10
|
+
|
|
11
|
+
const sleep = ms => new Promise(r => { setTimeout(r, ms) })
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} root project root
|
|
15
|
+
* @param {string} cmd
|
|
16
|
+
* @param {Record<string, any>} p params
|
|
17
|
+
* @param {{ url?: string, tab?: string }} [o] dev server URL and tab
|
|
18
|
+
*/
|
|
19
|
+
export async function browser(root, cmd, p = {}, o = {}) {
|
|
20
|
+
const page = (name, args, extra) => call(root, name, args, { ...o, ...extra })
|
|
21
|
+
switch (cmd) {
|
|
22
|
+
case 'tabs': return tabs(root, o)
|
|
23
|
+
case 'launch': return launch(root, p.route ?? '/', { url: o.url, headed: p.headed })
|
|
24
|
+
case 'close': return close(root)
|
|
25
|
+
case 'open': {
|
|
26
|
+
const before = await page('info', {})
|
|
27
|
+
await page('navigate', { to: p.route ?? '/' })
|
|
28
|
+
// Same tab id after the load (@weft-js/core/agent keeps it in sessionStorage); wait for the new document to say hello.
|
|
29
|
+
const until = Date.now() + 15_000
|
|
30
|
+
do {
|
|
31
|
+
await sleep(150)
|
|
32
|
+
try {
|
|
33
|
+
const info = await page('info', {}, { timeout: 2000 })
|
|
34
|
+
if (info.url !== before.url || p.route === new URL(before.url).pathname) return info
|
|
35
|
+
} catch {}
|
|
36
|
+
} while (Date.now() <= until)
|
|
37
|
+
return waitForTab(root, o)
|
|
38
|
+
}
|
|
39
|
+
case 'snapshot': {
|
|
40
|
+
const out = await page('snapshot', { interactive: p.interactive, scope: p.scope, delta: p.delta, graph: p.graph })
|
|
41
|
+
if (!p.full) {
|
|
42
|
+
if (out.tree) out.tree = spill(root, 'snapshot', out.tree)
|
|
43
|
+
if (out.graph) out.graph = spill(root, 'graph', out.graph)
|
|
44
|
+
}
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
case 'act': return page('act', { target: p.target, action: p.action, value: p.value })
|
|
48
|
+
case 'assert': return page('assert', { target: p.target, expect: p.expect, timeout: p.timeout })
|
|
49
|
+
case 'graph': {
|
|
50
|
+
const out = await page('graph', { filter: p.filter })
|
|
51
|
+
return { graph: p.full ? out.graph : spill(root, 'graph', out.graph), nodes: out.nodes.length }
|
|
52
|
+
}
|
|
53
|
+
case 'set': return page('set', { name: p.name, value: p.value })
|
|
54
|
+
case 'time': return page('time', { op: p.op, ms: p.ms, reload: p.reload })
|
|
55
|
+
case 'net': return page('net', { op: p.op, ms: p.ms })
|
|
56
|
+
case 'record': {
|
|
57
|
+
const out = await page('record', { op: p.op, text: p.text, component: p.component, from: p.from, props: p.props, imports: p.imports, name: p.name })
|
|
58
|
+
if (p.op === 'code' && p.out) {
|
|
59
|
+
const file = isAbsolute(p.out) ? p.out : join(root, p.out)
|
|
60
|
+
mkdirSync(dirname(file), { recursive: true })
|
|
61
|
+
writeFileSync(file, out.code)
|
|
62
|
+
return { ...out, written: file }
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
case 'logs': return page('logs', {})
|
|
67
|
+
case 'overlay': return page('overlay', { show: p.show })
|
|
68
|
+
case 'info': return page('info', {})
|
|
69
|
+
case 'reload': return page('reload', {})
|
|
70
|
+
case 'screenshot': return screenshot(root, p.out && (isAbsolute(p.out) ? p.out : join(root, p.out)))
|
|
71
|
+
default:
|
|
72
|
+
throw Object.assign(new Error(`unknown browser command ${cmd}`), { code: 'E_USAGE' })
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `button "Add Banana"`, `label Coupon`, `text Cart`, `@e5` → a target, and how many words it took. */
|
|
77
|
+
export function parseTarget(words) {
|
|
78
|
+
const [a, b] = words
|
|
79
|
+
if (!a) throw Object.assign(new Error('missing target: <role> <name>, label <text>, text <text>, or @ref'), { code: 'E_USAGE' })
|
|
80
|
+
if (a.startsWith('@')) return [a, 1]
|
|
81
|
+
if (a === 'label') return [{ label: b }, 2]
|
|
82
|
+
if (a === 'text') return [{ text: b }, 2]
|
|
83
|
+
return [b === undefined || b === '-' ? { role: a } : { role: a, name: b }, b === undefined ? 1 : 2]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** CLI words → params. */
|
|
87
|
+
export function cliParams(cmd, words, flags) {
|
|
88
|
+
const num = v => (v === undefined ? undefined : Number(v))
|
|
89
|
+
switch (cmd) {
|
|
90
|
+
case 'launch': case 'open': return { route: words[0] ?? '/', headed: !!flags.headed }
|
|
91
|
+
case 'snapshot': return { interactive: !!flags.interactive, delta: !!flags.delta, graph: flags['graph-only'] ? 'only' : flags['no-graph'] ? false : flags.graph ?? true, scope: flags.scope ? parseScope(flags.scope) : undefined, full: !!flags.full }
|
|
92
|
+
case 'act': {
|
|
93
|
+
const [target, n] = parseTarget(words)
|
|
94
|
+
const action = words[n] ?? 'click'
|
|
95
|
+
if (!actions.includes(action)) throw Object.assign(new Error(`unknown action ${action}: ${actions.join(', ')}`), { code: 'E_USAGE' })
|
|
96
|
+
return { target, action, value: words[n + 1] }
|
|
97
|
+
}
|
|
98
|
+
case 'assert': {
|
|
99
|
+
const [target, n] = parseTarget(words)
|
|
100
|
+
const kind = words[n] ?? 'visible'
|
|
101
|
+
if (!expectations.includes(kind)) throw Object.assign(new Error(`unknown expectation ${kind}: ${expectations.join(', ')}`), { code: 'E_USAGE' })
|
|
102
|
+
const v = words[n + 1]
|
|
103
|
+
return { target, expect: { [kind]: kind === 'count' ? Number(v) : ['text', 'exactText', 'value'].includes(kind) ? v ?? '' : true }, timeout: num(flags.timeout) }
|
|
104
|
+
}
|
|
105
|
+
case 'graph': return { filter: words[0], full: !!flags.full }
|
|
106
|
+
case 'set': {
|
|
107
|
+
let value = words[1]
|
|
108
|
+
try { value = JSON.parse(words[1]) } catch {}
|
|
109
|
+
return { name: words[0], value }
|
|
110
|
+
}
|
|
111
|
+
case 'time': return { op: words[0] ?? 'status', ms: num(words[1]), reload: !!flags.reload }
|
|
112
|
+
case 'net': return { op: words[0] ?? 'status', ms: num(words[1]) }
|
|
113
|
+
case 'record': return { op: words[0] ?? 'code', text: words.slice(1).join(' '), component: flags.component, from: flags.from, props: flags.props, name: flags.name, out: flags.out }
|
|
114
|
+
case 'overlay': return { show: words[0] === undefined ? undefined : words[0] !== 'off' }
|
|
115
|
+
case 'screenshot': return { out: flags.out }
|
|
116
|
+
default: return {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const parseScope = s => (s.startsWith('@') ? s : (() => { const i = s.indexOf(':'); return i < 0 ? { role: s } : { role: s.slice(0, i), name: s.slice(i + 1) } })())
|
|
121
|
+
|
|
122
|
+
export const browserUsage = `usage: weft browser <command> [--json] [--url <dev server>] [--tab <id>]
|
|
123
|
+
|
|
124
|
+
The page is the app open in any browser (yours, an agent's, or \`launch\`); the dev server relays to it.
|
|
125
|
+
In a browser that runs JS, the same commands are \`await __weft__.agent.run('<command>', args)\`.
|
|
126
|
+
|
|
127
|
+
tabs tabs connected to the dev server
|
|
128
|
+
launch [route] [--headed] start a headless Chrome on the app (when you have no browser)
|
|
129
|
+
open <route> navigate the tab
|
|
130
|
+
snapshot [--interactive] [--delta] [--scope role:name|@ref] [--graph-only|--no-graph] [--full]
|
|
131
|
+
roles, names and refs, plus the reactive graph (Cart.total = 42.5 ← …)
|
|
132
|
+
act <role> <name> <action> [value] click dblclick hover focus clear fill type press select check uncheck
|
|
133
|
+
act @e5 <action> [value] (or: act label <text> …, act text <text> …)
|
|
134
|
+
assert <target> <expectation> [v] visible absent text exactText value checked unchecked disabled enabled focused count
|
|
135
|
+
graph [filter] the named reactive graph
|
|
136
|
+
set <Name.box> <json> write state by its graph name (dev only)
|
|
137
|
+
time pause [--reload]|advance <ms>|resume|status
|
|
138
|
+
net offline|online|latency <ms>|drop|reset|status
|
|
139
|
+
record start|stop|note <text>|code [--component App --from ./App --props '{…}' --out file.test.tsx]
|
|
140
|
+
logs console errors/warnings and uncaught errors since the last call
|
|
141
|
+
overlay [on|off] the graph panel in the page (Alt+Shift+G)
|
|
142
|
+
screenshot [--out file.png] launched Chrome only; use your own browser's screenshot otherwise
|
|
143
|
+
close stop the launched Chrome`
|