@weft-js/cli 0.1.1 → 0.1.2

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 (171) hide show
  1. package/README.md +38 -14
  2. package/bin/weft.mjs +8 -1
  3. package/docs/async.md +16 -6
  4. package/docs/browser.md +22 -8
  5. package/docs/config.md +39 -9
  6. package/docs/core.md +30 -0
  7. package/docs/deploy.md +33 -8
  8. package/docs/design.md +12 -3
  9. package/docs/entrypoints.md +79 -0
  10. package/docs/fonts.md +12 -2
  11. package/docs/icons.md +13 -7
  12. package/docs/lists.md +3 -1
  13. package/docs/meta.md +13 -3
  14. package/docs/plugins.md +32 -9
  15. package/docs/props.md +13 -2
  16. package/docs/reactivity.md +26 -0
  17. package/docs/rules.md +19 -12
  18. package/docs/rust.md +47 -17
  19. package/docs/start-short.md +3 -2
  20. package/docs/start.md +226 -52
  21. package/docs/state.md +33 -7
  22. package/docs/style.md +31 -8
  23. package/docs/surface.md +61 -15
  24. package/docs/testing.md +15 -5
  25. package/docs/theme.md +182 -0
  26. package/docs/ui-state.md +19 -6
  27. package/docs/verify.md +13 -0
  28. package/package.json +13 -4
  29. package/src/add/config.mjs +93 -0
  30. package/src/add/errors.mjs +2 -0
  31. package/src/add/setup.mjs +88 -0
  32. package/src/add-rust.mjs +82 -27
  33. package/src/add.mjs +209 -181
  34. package/src/agents-md.mjs +144 -43
  35. package/src/alias.mjs +16 -5
  36. package/src/audit/page.mjs +313 -0
  37. package/src/audit/report.mjs +69 -0
  38. package/src/audit/utils.mjs +5 -0
  39. package/src/audit.mjs +159 -206
  40. package/src/browser/cli.mjs +24 -5
  41. package/src/browser/client.mjs +189 -25
  42. package/src/browser/commands.mjs +166 -51
  43. package/src/catalog/audit.mjs +129 -0
  44. package/src/catalog/auth.mjs +40 -0
  45. package/src/catalog/compiler.mjs +337 -0
  46. package/src/catalog/contracts.mjs +18 -0
  47. package/src/catalog/database.mjs +52 -0
  48. package/src/catalog/deploy.mjs +117 -0
  49. package/src/catalog/design.mjs +159 -0
  50. package/src/catalog/forms.mjs +25 -0
  51. package/src/catalog/hydration.mjs +29 -0
  52. package/src/catalog/icons.mjs +24 -0
  53. package/src/catalog/media.mjs +163 -0
  54. package/src/catalog/metadata.mjs +116 -0
  55. package/src/catalog/plugins.mjs +228 -0
  56. package/src/catalog/realtime.mjs +195 -0
  57. package/src/catalog/rust.mjs +75 -0
  58. package/src/catalog/server-functions.mjs +92 -0
  59. package/src/catalog/state.mjs +39 -0
  60. package/src/catalog/style-groups.mjs +54 -0
  61. package/src/catalog/tests.mjs +127 -0
  62. package/src/catalog/text.mjs +200 -0
  63. package/src/catalog/ui-state.mjs +42 -0
  64. package/src/catalog.mjs +67 -460
  65. package/src/check/runtime.mjs +280 -0
  66. package/src/check.mjs +198 -208
  67. package/src/cli/authoring.mjs +213 -0
  68. package/src/cli/project.mjs +302 -0
  69. package/src/cli.mjs +222 -250
  70. package/src/config.mjs +1 -1
  71. package/src/contributions.mjs +75 -25
  72. package/src/db-layer.mjs +13 -5
  73. package/src/deploy/cdn.mjs +94 -16
  74. package/src/deploy/files.mjs +100 -21
  75. package/src/deploy/fit.mjs +127 -23
  76. package/src/deploy/hosts.mjs +90 -20
  77. package/src/deploy/index/cli.mjs +20 -0
  78. package/src/deploy/index/environment.mjs +69 -0
  79. package/src/deploy/index/steps.mjs +87 -0
  80. package/src/deploy/index.mjs +228 -114
  81. package/src/deploy/needs.mjs +88 -31
  82. package/src/design-checks/accessible-names.mjs +62 -0
  83. package/src/design-checks/contrast.mjs +43 -0
  84. package/src/design-checks/csp.mjs +60 -0
  85. package/src/design-checks/files.mjs +59 -0
  86. package/src/design-checks/heading-links.mjs +61 -0
  87. package/src/design-checks/interaction.mjs +65 -0
  88. package/src/design-checks/jsx.mjs +67 -0
  89. package/src/design-checks/layout-values.mjs +68 -0
  90. package/src/design-checks/native-controls.mjs +48 -0
  91. package/src/design-checks/outline.mjs +39 -0
  92. package/src/design-checks/reporter.mjs +37 -0
  93. package/src/design-checks/rules.mjs +274 -0
  94. package/src/design-checks/values.mjs +262 -0
  95. package/src/design-checks.mjs +10 -400
  96. package/src/diag.mjs +32 -8
  97. package/src/docs.mjs +138 -43
  98. package/src/doctor.mjs +20 -8
  99. package/src/files.mjs +18 -5
  100. package/src/fix.mjs +10 -2
  101. package/src/gate/after.mjs +40 -0
  102. package/src/gate/before.mjs +54 -0
  103. package/src/gate/classify.mjs +72 -0
  104. package/src/gate/mutation.mjs +209 -0
  105. package/src/gate/resolve-cases.mjs +22 -0
  106. package/src/gate/result.mjs +63 -0
  107. package/src/gate/run.mjs +75 -0
  108. package/src/gate.mjs +104 -202
  109. package/src/infer/box-props.cjs +140 -0
  110. package/src/infer/core-boxes.cjs +46 -0
  111. package/src/infer/derived-writes.cjs +95 -0
  112. package/src/infer/engine/checks.cjs +193 -0
  113. package/src/infer/engine/flow.cjs +293 -0
  114. package/src/infer/engine/report.cjs +158 -0
  115. package/src/infer/engine/utils.cjs +15 -0
  116. package/src/infer/engine.cjs +108 -368
  117. package/src/infer/facts/sites.cjs +114 -0
  118. package/src/infer/facts.cjs +210 -117
  119. package/src/infer/hosts.cjs +69 -22
  120. package/src/infer/lsp.mjs +149 -40
  121. package/src/infer/session.cjs +73 -17
  122. package/src/infer/shadow.cjs +13 -5
  123. package/src/infer/worker.cjs +57 -9
  124. package/src/jsx-scan.mjs +63 -21
  125. package/src/load-config/schema.mjs +281 -0
  126. package/src/load-config.mjs +137 -138
  127. package/src/map.mjs +93 -26
  128. package/src/mcp/browser-tools.mjs +155 -0
  129. package/src/mcp.mjs +147 -69
  130. package/src/meta-table.mjs +43 -8
  131. package/src/mutate.mjs +120 -36
  132. package/src/plugin-checks.mjs +52 -23
  133. package/src/plugins.mjs +125 -39
  134. package/src/project.mjs +170 -79
  135. package/src/props-checks.mjs +24 -6
  136. package/src/props-listener.mjs +27 -9
  137. package/src/react-checks.mjs +35 -4
  138. package/src/server-state-checks.mjs +34 -0
  139. package/src/share-checks.mjs +121 -37
  140. package/src/site.mjs +33 -6
  141. package/src/slots.mjs +104 -19
  142. package/src/start/build.mjs +116 -0
  143. package/src/start/routes.mjs +38 -0
  144. package/src/start.mjs +193 -95
  145. package/src/state-checks/core-state.mjs +223 -0
  146. package/src/state-checks/modules.mjs +17 -0
  147. package/src/state-checks/scan.mjs +232 -0
  148. package/src/state-checks/server-graph.mjs +65 -0
  149. package/src/state-checks.mjs +109 -133
  150. package/src/style/find.mjs +121 -0
  151. package/src/style.mjs +133 -127
  152. package/src/surface-checks.mjs +212 -46
  153. package/src/tailwind.mjs +5 -4
  154. package/src/tasks.mjs +189 -27
  155. package/src/test-rules/context.mjs +109 -0
  156. package/src/test-rules/file-rules.mjs +279 -0
  157. package/src/test-rules/rule-helpers.mjs +244 -0
  158. package/src/test-rules/syntax.mjs +223 -0
  159. package/src/test-rules/test-rules.mjs +127 -0
  160. package/src/testdiff.mjs +94 -32
  161. package/src/tests.mjs +31 -483
  162. package/src/text-checks.mjs +229 -69
  163. package/src/tokens.mjs +12 -4
  164. package/src/trust/scan.mjs +95 -0
  165. package/src/trust.mjs +214 -102
  166. package/src/weft.mjs +41 -9
  167. package/src/workspace.mjs +96 -19
  168. package/ts-plugin/context.js +57 -0
  169. package/ts-plugin/index.js +4 -124
  170. package/ts-plugin/mapping.js +65 -0
  171. package/ts-plugin/proxy.js +144 -0
package/src/cli.mjs CHANGED
@@ -24,9 +24,55 @@ import { formatGate, gate } from './gate.mjs'
24
24
  import { browserCommand } from './browser/cli.mjs'
25
25
  import { metaTable } from './meta-table.mjs'
26
26
  import { audit } from './audit.mjs'
27
- import { buildCommand, dbCommand, devCommand, doctorCommand, routesCommand, startCommand, unsafeAllowed } from './start.mjs'
27
+ import {
28
+ buildCommand,
29
+ dbCommand,
30
+ devCommand,
31
+ doctorCommand,
32
+ routesCommand,
33
+ startCommand,
34
+ unsafeAllowed,
35
+ } from './start.mjs'
28
36
  import { deployCommand, formatHosts, hostsCommand, initCommand } from './deploy/index.mjs'
29
- import { explainTarget, findGroups, formatExplain, formatFind, styleMatrix, writeMatrixFile } from './style.mjs'
37
+ import {
38
+ explainTarget,
39
+ findGroups,
40
+ formatExplain,
41
+ formatFind,
42
+ styleMatrix,
43
+ writeMatrixFile,
44
+ } from './style.mjs'
45
+
46
+ import {
47
+ commandLsp,
48
+ commandTsc,
49
+ commandCheck,
50
+ commandDoctor,
51
+ commandTest,
52
+ commandBrowser,
53
+ commandDev,
54
+ commandBuild,
55
+ commandAudit,
56
+ commandDeploy,
57
+ commandStart,
58
+ commandDb,
59
+ commandRoutes,
60
+ commandSurfaces,
61
+ commandTasks,
62
+ commandRun,
63
+ commandDocs,
64
+ commandAgentsMd,
65
+ } from './cli/project.mjs'
66
+ import {
67
+ commandMap,
68
+ commandIcons,
69
+ commandAdd,
70
+ commandPlugins,
71
+ commandFix,
72
+ commandTokens,
73
+ commandStyle,
74
+ commandMcp,
75
+ } from './cli/authoring.mjs'
30
76
 
31
77
  const usage = `usage: weft <command> [--json] [--root <dir>]
32
78
 
@@ -43,7 +89,7 @@ const usage = `usage: weft <command> [--json] [--root <dir>]
43
89
  build [--runtime node|bun|deno] [--preset <p>] production build to .output, and .weft/contract.json; then the
44
90
  built pages are audited (L-codes) and an error fails the build
45
91
  [--unsafe-allow <codes>] go past these codes (R002, L003, a family like L, or all): forces a deploy
46
- audit [routes…] [--url <u>] [--all] [--max <n>] the built pages, or a running site, in a clean Chrome at a phone's
92
+ audit [routes…] [--url <u>] [--indexing public|private|auto] [--all] [--max <n>] the built pages, or a running site, in a clean Chrome at a phone's
47
93
  and a desktop's width: axe-core's WCAG rules, console, deprecations, head, sideways scroll, shift
48
94
  deploy [--prod] [--yes] [--target t] fit check, build for the target, then the host's own CLI; without --yes a dry
49
95
  run that prints what would run. Netlify, Vercel, Cloudflare, Fly, Render; the live URL at the end
@@ -55,11 +101,13 @@ const usage = `usage: weft <command> [--json] [--root <dir>]
55
101
  db live a migration with change triggers for the tables in \`changes\`
56
102
  db listen the change feed as its own process, for an app on functions
57
103
  routes [--meta] the route table; --meta: every route's merged metadata, tags, card mode and H-diagnostics
104
+ surfaces the canvas and WebGL surfaces this project already defines
58
105
  tasks commands and project tasks
59
106
  run <task> run a project task from weft.config.ts
60
107
  docs [topic|code] docs for agents (rules, props, lists, async, testing, config, diagnostics, commands, W014…)
61
108
  agents-md [--check] write AGENTS.md (--check: exit 1 when stale; --stdout: print it)
62
109
  map [--stdout] write .weft/map.md and .json: a matrix of what to call in this app
110
+ icons publish <dir> every icon and variant as static .svg files with a manifest.json, for a CDN
63
111
  add <feature> install a plugin and wire it into the config; also tailwind, auth, db [engine] [--layer drizzle|prisma|native], and rust [name] [--remote] [--dry-run] [--no-install]
64
112
  plugins [list] plugins, the slots they provide and bind, permissions, diagnostics
65
113
  plugins explain <slot> binding chain: config → plugin → module → .weft/slots/<id>.ts
@@ -70,16 +118,67 @@ const usage = `usage: weft <command> [--json] [--root <dir>]
70
118
  style explain <group|selector> [--<axis> <value>] [--states a,b] [--accent intent:token] [--part p]
71
119
  resolved CSS for one variation, and where each value came from
72
120
  style matrix write .weft/styles.json (every group: axes, parts, accents, resolved CSS, uses)
73
- mcp MCP server on stdio: docs, check, tasks, style_find, style_explain, test_gate, browser_*`
121
+ mcp MCP server on stdio: docs, check, audit, routes_meta, map, tasks, surfaces, style_find,
122
+ style_explain, test_gate, browser_*`
74
123
 
75
124
  /** Flags that take a value (`--base main` or `--base=main`), and boolean flags, beyond the global ones. */
76
- const valued = new Set(['layer', 'base', 'budget', 'max-mutants', 'concurrency', 'url', 'tab', 'out', 'component', 'from', 'props', 'name', 'scope', 'timeout', 'preset', 'port', 'runtime', 'unsafe-allow', 'max', 'target'])
125
+ const valued = new Set([
126
+ 'layer',
127
+ 'base',
128
+ 'budget',
129
+ 'max-mutants',
130
+ 'concurrency',
131
+ 'url',
132
+ 'indexing',
133
+ 'tab',
134
+ 'out',
135
+ 'component',
136
+ 'from',
137
+ 'props',
138
+ 'name',
139
+ 'scope',
140
+ 'timeout',
141
+ 'preset',
142
+ 'port',
143
+ 'runtime',
144
+ 'unsafe-allow',
145
+ 'max',
146
+ 'target',
147
+ ])
77
148
  /** `weft style explain` takes the group's own axes as flags (`--status late`); these are its fixed ones. */
78
149
  const styleFlags = new Set(['states', 'accent', 'part'])
79
- const booleans = new Set(['dry-run', 'no-install', 'remote', 'gate', 'refactor', 'no-mutation', 'interactive', 'delta', 'graph-only', 'no-graph', 'headed', 'full', 'reload', 'meta', 'all', 'verbose', 'prod', 'yes', 'force'])
150
+ const booleans = new Set([
151
+ 'dry-run',
152
+ 'no-install',
153
+ 'remote',
154
+ 'gate',
155
+ 'refactor',
156
+ 'no-mutation',
157
+ 'interactive',
158
+ 'delta',
159
+ 'graph-only',
160
+ 'no-graph',
161
+ 'headed',
162
+ 'full',
163
+ 'reload',
164
+ 'meta',
165
+ 'all',
166
+ 'verbose',
167
+ 'prod',
168
+ 'yes',
169
+ 'force',
170
+ ])
80
171
 
81
172
  export function parse(argv) {
82
- const out = { command: argv[0], positional: [], json: false, root: process.cwd(), check: false, stdout: false, flags: {} }
173
+ const out = {
174
+ command: argv[0],
175
+ positional: [],
176
+ json: false,
177
+ root: process.cwd(),
178
+ check: false,
179
+ stdout: false,
180
+ flags: {},
181
+ }
83
182
  for (let i = 1; i < argv.length; i++) {
84
183
  const a = argv[i]
85
184
  if (a === '--json') out.json = true
@@ -91,262 +190,134 @@ export function parse(argv) {
91
190
  const [name, inline] = a.slice(2).split(/=(.*)/s)
92
191
  if (valued.has(name)) out.flags[name] = inline ?? argv[++i]
93
192
  else if (booleans.has(name) && inline === undefined) out.flags[name] = true
94
- else if (out.command === 'style' && styleFlags.has(name)) out.flags[name] = inline ?? argv[++i]
95
- else if (out.command === 'style' && /^[a-z][\w-]*$/i.test(name) && (inline ?? argv[i + 1]) !== undefined) (out.flags.axes ??= {})[name] = inline ?? argv[++i]
193
+ else if (out.command === 'style' && styleFlags.has(name))
194
+ out.flags[name] = inline ?? argv[++i]
195
+ else if (
196
+ out.command === 'style' &&
197
+ /^[a-z][\w-]*$/i.test(name) &&
198
+ (inline ?? argv[i + 1]) !== undefined
199
+ )
200
+ (out.flags.axes ??= {})[name] = inline ?? argv[++i]
96
201
  else throw Object.assign(new Error(`unknown flag ${a}`), { code: 'E_USAGE' })
97
202
  } else out.positional.push(a)
98
203
  }
99
204
  return out
100
205
  }
101
206
 
207
+ const dependencies = {
208
+ fix,
209
+ writeTokens,
210
+ createRequire,
211
+ existsSync,
212
+ join,
213
+ resolve,
214
+ pathToFileURL,
215
+ addCommand,
216
+ writeAgentsMd,
217
+ check,
218
+ formatHuman,
219
+ sortDiagnostics,
220
+ docs,
221
+ compilerLine,
222
+ doctor,
223
+ formatDoctor,
224
+ formatMap,
225
+ writeMap,
226
+ serve,
227
+ explain,
228
+ list,
229
+ sync,
230
+ approveCommand,
231
+ auditCommand,
232
+ formatTrust,
233
+ loadConfig,
234
+ listSurfaces,
235
+ runTask,
236
+ tasks,
237
+ formatGate,
238
+ gate,
239
+ browserCommand,
240
+ metaTable,
241
+ audit,
242
+ buildCommand,
243
+ dbCommand,
244
+ devCommand,
245
+ doctorCommand,
246
+ routesCommand,
247
+ startCommand,
248
+ unsafeAllowed,
249
+ deployCommand,
250
+ formatHosts,
251
+ hostsCommand,
252
+ initCommand,
253
+ explainTarget,
254
+ findGroups,
255
+ formatExplain,
256
+ formatFind,
257
+ styleMatrix,
258
+ writeMatrixFile,
259
+ }
260
+
102
261
  export async function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
103
- const print = data => io.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
104
- const say = text => text && io.stderr.write(`${text}\n`)
262
+ const print = (data) => io.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
263
+ const say = (text) => text && io.stderr.write(`${text}\n`)
105
264
  let args
106
265
  try {
107
266
  args = parse(argv)
267
+ const context = { ...dependencies, args, print, say, io }
108
268
  switch (args.command) {
109
- case 'lsp': {
110
- const { lsp } = await import('./infer/lsp.mjs')
111
- const out = lsp(args.root)
112
- return await new Promise(ok => { out.server.on('exit', () => ok(0)) })
113
- }
114
- case 'tsc': {
115
- const { propTsc } = await import('./props-checks.mjs')
116
- const diagnostics = sortDiagnostics(await propTsc(args.root))
117
- if (args.json) print({ ok: !diagnostics.some(d => d.severity === 'error'), diagnostics })
118
- say(formatHuman(diagnostics))
119
- say(`weft tsc: ${diagnostics.filter(d => d.severity === 'error').length} errors`)
120
- return diagnostics.some(d => d.severity === 'error') ? 1 : 0
121
- }
122
- case 'check': {
123
- const result = await check(args.root, { files: args.positional, preset: args.flags.preset })
124
- if (args.json) print(result)
125
- say(formatHuman(result.diagnostics))
126
- if (args.flags.verbose) say(compilerLine(doctor(args.root).compiler))
127
- say(`weft check: ${result.summary.files} files, ${result.summary.errors} errors, ${result.summary.warnings} warnings (${result.ms} ms)`)
128
- return result.ok ? 0 : 1
129
- }
130
- case 'doctor': {
131
- // What this install compiles with, always; what live data does on the target, for an app that has any.
132
- const result = doctor(args.root)
133
- const plan = args.flags.preset || existsSync(join(args.root, 'src/realtime.server.ts')) ? await doctorCommand(args.root, { preset: args.flags.preset, runtime: args.flags.runtime }) : undefined
134
- const hosts = hostsCommand(args.root, (await loadConfig(args.root)).config)
135
- if (args.json) print({ ...result, live: plan, deploy: hosts })
136
- else say(`${formatDoctor(result)}\n\nweft doctor: deploy${hosts.target ? ` (target ${hosts.target})` : ''}\n${formatHosts(hosts).replace(/^/gm, ' ')}`)
137
- if (!plan) return 0
138
- say(`weft doctor: ${plan.preset} (${plan.host})\n live mode: ${plan.mode}\n ${plan.says}${plan.required.length ? `\n required: ${plan.required.join(', ')}` : ''}${plan.options.length ? `\n options:\n${plan.options.map(o => ` - ${o}`).join('\n')}` : ''}`)
139
- say(formatHuman(plan.diagnostics))
140
- return plan.diagnostics.some(d => d.severity === 'error') ? 1 : 0
141
- }
142
- case 'test': {
143
- if (!args.flags.gate) throw Object.assign(new Error('`weft test` runs the gate with --gate; run the suite itself with vitest'), { code: 'E_USAGE' })
144
- const num = name => (args.flags[name] === undefined ? undefined : Number(args.flags[name]))
145
- const result = await gate(args.root, {
146
- base: args.flags.base, budgetSec: num('budget'), maxMutants: num('max-mutants'), concurrency: num('concurrency'),
147
- refactor: !!args.flags.refactor, mutation: !args.flags['no-mutation'], log: line => say(line),
148
- })
149
- if (args.json) print(result)
150
- say(formatHuman(result.diagnostics))
151
- say(formatGate(result))
152
- say(`weft test --gate: ${result.ok ? 'pass' : 'fail'}, ${result.summary.changedTests} changed tests, ${result.summary.errors} errors, ${result.summary.warnings} warnings (${result.ms} ms)`)
153
- return result.ok ? 0 : 1
154
- }
269
+ case 'lsp':
270
+ return await commandLsp(context)
271
+ case 'tsc':
272
+ return await commandTsc(context)
273
+ case 'check':
274
+ return await commandCheck(context)
275
+ case 'doctor':
276
+ return await commandDoctor(context)
277
+ case 'test':
278
+ return await commandTest(context)
155
279
  case 'browser':
156
- return await browserCommand(args, { print, say, io })
157
- case 'dev': {
158
- const server = await devCommand(args.root, { port: args.flags.port && Number(args.flags.port) })
159
- say(`weft dev: ${server.resolvedUrls.local[0]}`)
160
- await new Promise(ok => { process.once('SIGINT', ok) })
161
- await server.close()
162
- return 0
163
- }
164
- case 'build': {
165
- const result = await buildCommand(args.root, { preset: args.flags.preset, runtime: args.flags.runtime, unsafeAllow: args.flags['unsafe-allow'] })
166
- if (args.json) print(result)
167
- say(formatHuman(result.diagnostics))
168
- say(`weft build: ${result.preset} → ${result.output}, ${result.functions} functions in the contract, ${result.audited} pages audited (${result.ms} ms)`)
169
- if (!result.ok) say('weft build: failed. The output is written, but fix the errors above before it ships, or `--unsafe-allow <code>` to force it.')
170
- return result.ok ? 0 : 1
171
- }
172
- case 'audit': {
173
- const result = await audit(args.root, { url: args.flags.url, routes: args.positional.length ? args.positional : undefined, all: !!args.flags.all, max: args.flags.max && Number(args.flags.max) })
174
- if (args.json) print(result)
175
- say(formatHuman(result.diagnostics))
176
- say(`weft audit: ${result.skipped ?? `${result.pages.length} pages at a phone's and a desktop's width, ${result.diagnostics.length} findings (${result.ms} ms)`}`)
177
- return result.ok ? 0 : 1
178
- }
179
- case 'deploy': {
180
- const loaded = await loadConfig(args.root)
181
- const sub = args.positional[0]
182
- if (sub === 'hosts') {
183
- const result = hostsCommand(args.root, loaded.config)
184
- if (args.json) print(result)
185
- else say(formatHosts(result))
186
- return 0
187
- }
188
- const unsafe = unsafeAllowed(args.flags['unsafe-allow'], loaded.config)
189
- if (sub === 'init') {
190
- const result = initCommand(args.root, loaded.config, { target: args.positional[1] ?? args.flags.target, dryRun: !!args.flags['dry-run'], force: !!args.flags.force, unsafe })
191
- if (args.json) print(result)
192
- say(formatHuman(result.findings))
193
- say([`weft deploy init ${result.target}${result.dryRun ? ' (dry run: nothing written)' : ''}:`, ...result.actions.map(a => ` ${a}`), '', 'then, once:', ...result.next.map(n => ` ${n}`)].join('\n'))
194
- return 0
195
- }
196
- if (sub !== undefined) throw Object.assign(new Error(`weft deploy ${sub}: use \`weft deploy\`, \`weft deploy hosts\` or \`weft deploy init [target]\``), { code: 'E_USAGE' })
197
- const result = await deployCommand(args.root, loaded.config, { target: args.flags.target, prod: !!args.flags.prod, yes: !!args.flags.yes, unsafe, build: (root, o) => buildCommand(root, { ...o, unsafeAllow: args.flags['unsafe-allow'] }) })
198
- if (args.json) print(result)
199
- say(formatHuman(result.diagnostics))
200
- if (result.dryRun) say([`weft deploy: a dry run for ${result.target}${result.prod ? ' (production)' : ' (a preview)'}. With --yes it runs:`, ...result.steps.map(s => ` ${s.what}${s.reads.length ? `\n reads ${s.reads.join(', ')} from the environment` : ''}`)].join('\n'))
201
- else say(`weft deploy: ${result.target}${result.prod ? ' (production)' : ''} → ${result.url ?? 'deployed; the host printed no URL'}`)
202
- return 0
203
- }
280
+ return await commandBrowser(context)
281
+ case 'dev':
282
+ return await commandDev(context)
283
+ case 'build':
284
+ return await commandBuild(context)
285
+ case 'audit':
286
+ return await commandAudit(context)
287
+ case 'deploy':
288
+ return await commandDeploy(context)
204
289
  case 'start':
205
- return await startCommand(args.root, { runtime: args.flags.runtime })
206
- case 'db': {
207
- const result = await dbCommand(args.root, args.positional[0], args.positional[1])
208
- if (args.json) print(result)
209
- say(result.output)
210
- return 0
211
- }
212
- case 'routes': {
213
- const result = await routesCommand(args.root, { meta: !!args.flags.meta, preset: args.flags.preset })
214
- if (args.json) print(result)
215
- else say(args.flags.meta ? metaTable(result) : result.routes.map(r => `${r.path.padEnd(24)} ${r.file}`).join('\n'))
216
- say(formatHuman(result.diagnostics))
217
- return result.ok ? 0 : 1
218
- }
219
- case 'surfaces': {
220
- const surfaces = listSurfaces(args.root)
221
- if (args.json) print({ surfaces })
222
- else say(surfaces.length ? surfaces.map(x => `${String(x.name).padEnd(20)} ${String(x.kind).padEnd(10)} ${x.file}:${x.line} props: ${x.props.join(', ') || '(none)'}`).join('\n') : 'No surfaces yet: `weft docs surface`.')
223
- return 0
224
- }
225
- case 'tasks': {
226
- const result = await tasks(args.root)
227
- if (args.json) print(result)
228
- else say(result.tasks.map(t => `${t.available ? ' ' : '·'} ${t.name.padEnd(10)} ${t.description ?? ''}`).join('\n'))
229
- say(formatHuman(result.diagnostics))
230
- return result.ok ? 0 : 1
231
- }
232
- case 'run': {
233
- if (!args.positional[0]) throw Object.assign(new Error('weft run <task>'), { code: 'E_USAGE' })
234
- return await runTask(args.root, args.positional[0])
235
- }
236
- case 'docs': {
237
- const page = await docs(args.root, args.positional[0])
238
- if (args.json) print(page)
239
- else io.stdout.write(`${page.text}\n`)
240
- return 0
241
- }
242
- case 'agents-md': {
243
- const out = await writeAgentsMd(args.root, { check: args.check || args.stdout })
244
- if (args.stdout) io.stdout.write(out.text)
245
- if (args.json) print({ path: out.path, bytes: out.bytes, limit: 8192, overLimit: out.overLimit, current: out.current, written: out.written })
246
- say(`AGENTS.md: ${out.bytes} bytes${out.overLimit ? ' (over the 8 KB limit)' : ''}, ${out.written ? 'written' : out.current ? 'current' : 'stale'}`)
247
- return (args.check && !out.current) || out.overLimit ? 1 : 0
248
- }
249
- case 'map': {
250
- const map = await writeMap(args.root)
251
- if (args.json) print(map)
252
- else if (args.stdout) process.stdout.write(formatMap(map))
253
- else say(`.weft/map.md: ${map.rows.length} rows`)
254
- return 0
255
- }
256
- case 'icons': {
257
- if (args.positional[0] !== 'publish' || !args.positional[1]) { say('usage: weft icons publish <dir>'); return 1 }
258
- const { publish } = await import(pathToFileURL(createRequire(join(args.root, 'package.json')).resolve('@weft-js/warp-icons/compile')).href)
259
- const out = await publish(resolve(args.root, args.positional[1]))
260
- if (args.json) print(out)
261
- else say(`${out.files} files for ${out.icons} icons in ${out.dir}. Nothing was uploaded: host that folder, then set icons({ source }).`)
262
- return 0
263
- }
264
- case 'add': {
265
- const result = await addCommand(args.root, args.positional[0], { dryRun: args.flags['dry-run'], install: !args.flags['no-install'], engine: args.positional[1], layer: args.flags.layer, remote: args.flags.remote })
266
- if (args.json) print(result)
267
- else say(result.already ? `${result.package} is already in ${result.config}` : result.actions.map(a => `${args.flags['dry-run'] ? 'would ' : ''}${a}`).join('\n'))
268
- return 0
269
- }
270
- case 'plugins': {
271
- const sub = args.positional[0] ?? 'list'
272
- if (sub === 'list') {
273
- const result = await list(args.root)
274
- if (args.json) print(result)
275
- else say(result.plugins.map(p => `${p.name}@${p.version ?? '?'} ${p.provides.map(s => `${s.slot}${s.key ? `[${s.key}]` : ''}${s.bound ? '' : ' (not bound)'}`).join(', ') || 'no slots'}${p.tokens ? ` tokens --${p.tokens}-*` : ''}`).join('\n') || 'no plugins')
276
- say(formatHuman(result.diagnostics))
277
- return result.ok ? 0 : 1
278
- }
279
- if (sub === 'explain') {
280
- const result = await explain(args.root, args.positional[1])
281
- if (args.json) print(result)
282
- else say(JSON.stringify(result.binding, null, 2))
283
- return 0
284
- }
285
- if (sub === 'sync') {
286
- const loaded = await loadConfig(args.root)
287
- const errors = loaded.diagnostics.filter(d => d.severity === 'error')
288
- const written = loaded.diagnostics.some(d => d.code === 'P007') ? [] : sync(args.root, loaded)
289
- if (args.json) print({ ok: errors.length === 0, written, diagnostics: loaded.diagnostics })
290
- say(formatHuman(loaded.diagnostics))
291
- say(`weft plugins sync: ${written.length} files written`)
292
- return errors.length ? 1 : 0
293
- }
294
- if (sub === 'audit') {
295
- const loaded = await loadConfig(args.root)
296
- const result = auditCommand(args.root, loaded)
297
- if (args.json) print(result)
298
- else say(result.plugins.map(formatTrust).join('\n') || 'no plugins')
299
- say(formatHuman(result.diagnostics))
300
- return result.ok ? 0 : 1
301
- }
302
- if (sub === 'approve') {
303
- const result = approveCommand(args.root, await loadConfig(args.root), { names: args.positional.slice(1), all: !!args.flags.all })
304
- if (args.json) print(result)
305
- else say([...(result.written ? result.approved : result.pending).map(formatTrust), result.written ? `${result.lock}: ${result.approved.length} approved` : result.pending.length ? 'nothing written: name a plugin, or pass --all' : 'nothing waiting', ...result.written ? result.pending.map(p => `still waiting: ${p.name}`) : [], ...(result.gitignore ? ['.gitignore: the lock is no longer ignored, so commit it'] : [])].join('\n'))
306
- return result.ok ? 0 : 1
307
- }
308
- throw Object.assign(new Error(`unknown plugins subcommand ${String(sub)}`), { code: 'E_USAGE' })
309
- }
310
- case 'fix': {
311
- const result = await fix(args.root, { files: args.positional })
312
- if (args.json) print(result)
313
- else say(result.applied.length ? result.applied.map(a => `${a.file}:${a.span[0]}:${a.span[1]} ${a.code}`).join('\n') : 'nothing to fix')
314
- return 0
315
- }
316
- case 'tokens': {
317
- const result = await writeTokens(args.root)
318
- if (args.json) print(result)
319
- else say(`${result.dir}: ${result.files.length} files`)
320
- return 0
321
- }
322
- case 'style': {
323
- const sub = args.positional[0]
324
- if (sub === 'find') {
325
- const result = findGroups((await styleMatrix(args.root)).matrix, args.positional.slice(1).join(' '))
326
- if (args.json) print(result)
327
- else say(formatFind(result))
328
- return 0
329
- }
330
- if (sub === 'explain') {
331
- const accent = args.flags.accent ? Object.fromEntries(args.flags.accent.split(/[\s,]+/).filter(Boolean).map(w => w.split(':'))) : undefined
332
- const states = args.flags.states?.split(',').map(s => s.trim()).filter(Boolean)
333
- const result = explainTarget(await styleMatrix(args.root), args.positional.slice(1).join(' '), { axes: args.flags.axes, states, accent, part: args.flags.part })
334
- if (args.json) print(result)
335
- else say(formatExplain(result))
336
- return 0
337
- }
338
- if (sub === 'matrix') {
339
- const { matrix } = await styleMatrix(args.root)
340
- const written = args.stdout ? false : writeMatrixFile(args.root, matrix)
341
- if (args.stdout || args.json) print(matrix)
342
- say(`.weft/styles.json: ${Object.keys(matrix.groups).length} groups, ${matrix.diagnostics.length} diagnostics, ${args.stdout ? 'not written' : written ? 'written' : 'current'}`)
343
- return 0
344
- }
345
- throw Object.assign(new Error(`weft style ${sub ?? ''}: use find <query>, explain <group|selector> or matrix`), { code: 'E_USAGE' })
346
- }
290
+ return await commandStart(context)
291
+ case 'db':
292
+ return await commandDb(context)
293
+ case 'routes':
294
+ return await commandRoutes(context)
295
+ case 'surfaces':
296
+ return commandSurfaces(context)
297
+ case 'tasks':
298
+ return await commandTasks(context)
299
+ case 'run':
300
+ return await commandRun(context)
301
+ case 'docs':
302
+ return await commandDocs(context)
303
+ case 'agents-md':
304
+ return await commandAgentsMd(context)
305
+ case 'map':
306
+ return await commandMap(context)
307
+ case 'icons':
308
+ return await commandIcons(context)
309
+ case 'add':
310
+ return await commandAdd(context)
311
+ case 'plugins':
312
+ return await commandPlugins(context)
313
+ case 'fix':
314
+ return await commandFix(context)
315
+ case 'tokens':
316
+ return await commandTokens(context)
317
+ case 'style':
318
+ return await commandStyle(context)
347
319
  case 'mcp':
348
- await serve(args.root)
349
- return 0
320
+ return await commandMcp(context)
350
321
  case undefined:
351
322
  case 'help':
352
323
  case '--help':
@@ -357,7 +328,8 @@ export async function main(argv, io = { stdout: process.stdout, stderr: process.
357
328
  }
358
329
  } catch (error) {
359
330
  const code = error.code ?? 'E_INTERNAL'
360
- if (args?.json || argv.includes('--json')) print({ ok: false, error: { code, message: error.message } })
331
+ if (args?.json || argv.includes('--json'))
332
+ print({ ok: false, error: { code, message: error.message } })
361
333
  say(`weft: ${error.message}${code === 'E_USAGE' ? `\n\n${usage}` : ''}`)
362
334
  return code === 'E_REFUSED' ? 1 : 2
363
335
  }
package/src/config.mjs CHANGED
@@ -2,4 +2,4 @@
2
2
  // Apps import it from `@weft-js/core/config`; the CLI's loader falls back here when a project's weft doesn't resolve.
3
3
 
4
4
  /** @type {import('@weft-js/core/config').defineConfig} */
5
- export const defineConfig = config => config
5
+ export const defineConfig = (config) => config