@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.
Files changed (91) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +74 -2
  3. package/bin/weft.mjs +8 -0
  4. package/docs/async.md +40 -0
  5. package/docs/browser.md +39 -0
  6. package/docs/config.md +93 -0
  7. package/docs/deploy.md +83 -0
  8. package/docs/design.md +97 -0
  9. package/docs/fonts.md +70 -0
  10. package/docs/icons.md +102 -0
  11. package/docs/lists.md +23 -0
  12. package/docs/meta.md +82 -0
  13. package/docs/plugins.md +143 -0
  14. package/docs/props.md +77 -0
  15. package/docs/rules.md +32 -0
  16. package/docs/rust.md +223 -0
  17. package/docs/start-short.md +8 -0
  18. package/docs/start.md +422 -0
  19. package/docs/state.md +96 -0
  20. package/docs/style.md +59 -0
  21. package/docs/surface.md +111 -0
  22. package/docs/testing.md +71 -0
  23. package/docs/ui-state.md +92 -0
  24. package/docs/verify.md +48 -0
  25. package/package.json +39 -4
  26. package/src/add-rust.mjs +123 -0
  27. package/src/add.mjs +358 -0
  28. package/src/agents-md.mjs +134 -0
  29. package/src/alias.mjs +59 -0
  30. package/src/audit.mjs +366 -0
  31. package/src/browser/cli.mjs +34 -0
  32. package/src/browser/client.mjs +132 -0
  33. package/src/browser/commands.mjs +143 -0
  34. package/src/catalog.mjs +496 -0
  35. package/src/check.mjs +330 -0
  36. package/src/cli.mjs +364 -0
  37. package/src/config.d.ts +1 -0
  38. package/src/config.mjs +5 -0
  39. package/src/contributions.mjs +69 -0
  40. package/src/db-layer.mjs +33 -0
  41. package/src/deploy/cdn.mjs +64 -0
  42. package/src/deploy/files.mjs +187 -0
  43. package/src/deploy/fit.mjs +62 -0
  44. package/src/deploy/hosts.mjs +56 -0
  45. package/src/deploy/index.mjs +273 -0
  46. package/src/deploy/needs.mjs +92 -0
  47. package/src/design-checks.mjs +439 -0
  48. package/src/diag.mjs +74 -0
  49. package/src/docs.mjs +132 -0
  50. package/src/doctor.mjs +30 -0
  51. package/src/files.mjs +47 -0
  52. package/src/fix.mjs +40 -0
  53. package/src/gate.mjs +276 -0
  54. package/src/index.mjs +9 -0
  55. package/src/infer/engine.cjs +525 -0
  56. package/src/infer/facts.cjs +252 -0
  57. package/src/infer/hosts.cjs +114 -0
  58. package/src/infer/lsp.mjs +131 -0
  59. package/src/infer/session.cjs +105 -0
  60. package/src/infer/shadow.cjs +46 -0
  61. package/src/infer/worker.cjs +97 -0
  62. package/src/jsx-scan.mjs +86 -0
  63. package/src/load-config.mjs +303 -0
  64. package/src/map.mjs +110 -0
  65. package/src/mcp.mjs +195 -0
  66. package/src/meta-table.mjs +28 -0
  67. package/src/mutate.mjs +140 -0
  68. package/src/plugin-checks.mjs +89 -0
  69. package/src/plugins.mjs +172 -0
  70. package/src/project.mjs +219 -0
  71. package/src/props-checks.mjs +46 -0
  72. package/src/props-listener.mjs +38 -0
  73. package/src/react-checks.mjs +36 -0
  74. package/src/share-checks.mjs +74 -0
  75. package/src/site.mjs +19 -0
  76. package/src/slots.mjs +96 -0
  77. package/src/start.mjs +296 -0
  78. package/src/state-checks.mjs +213 -0
  79. package/src/style.mjs +320 -0
  80. package/src/surface-checks.mjs +218 -0
  81. package/src/tailwind.mjs +22 -0
  82. package/src/tasks.mjs +99 -0
  83. package/src/testdiff.mjs +157 -0
  84. package/src/tests.mjs +572 -0
  85. package/src/text-checks.mjs +172 -0
  86. package/src/tokens.mjs +24 -0
  87. package/src/trust.mjs +282 -0
  88. package/src/weft.mjs +66 -0
  89. package/src/workspace.mjs +105 -0
  90. package/ts-plugin/index.js +137 -0
  91. 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`