@weft-js/cli 0.1.0 → 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.
- package/README.md +38 -14
- package/bin/weft.mjs +8 -1
- package/docs/async.md +16 -6
- package/docs/browser.md +22 -8
- package/docs/config.md +39 -9
- package/docs/core.md +30 -0
- package/docs/deploy.md +108 -0
- package/docs/design.md +12 -3
- package/docs/entrypoints.md +79 -0
- package/docs/fonts.md +12 -2
- package/docs/icons.md +13 -7
- package/docs/lists.md +3 -1
- package/docs/meta.md +13 -3
- package/docs/plugins.md +32 -9
- package/docs/props.md +13 -2
- package/docs/reactivity.md +26 -0
- package/docs/rules.md +19 -12
- package/docs/rust.md +47 -17
- package/docs/start-short.md +3 -2
- package/docs/start.md +226 -52
- package/docs/state.md +33 -7
- package/docs/style.md +31 -8
- package/docs/surface.md +61 -15
- package/docs/testing.md +15 -5
- package/docs/theme.md +182 -0
- package/docs/ui-state.md +19 -6
- package/docs/verify.md +13 -0
- package/package.json +13 -4
- package/src/add/config.mjs +93 -0
- package/src/add/errors.mjs +2 -0
- package/src/add/setup.mjs +88 -0
- package/src/add-rust.mjs +82 -27
- package/src/add.mjs +209 -181
- package/src/agents-md.mjs +144 -43
- package/src/alias.mjs +16 -5
- package/src/audit/page.mjs +313 -0
- package/src/audit/report.mjs +69 -0
- package/src/audit/utils.mjs +5 -0
- package/src/audit.mjs +174 -188
- package/src/browser/cli.mjs +24 -5
- package/src/browser/client.mjs +189 -25
- package/src/browser/commands.mjs +166 -51
- package/src/catalog/audit.mjs +129 -0
- package/src/catalog/auth.mjs +40 -0
- package/src/catalog/compiler.mjs +337 -0
- package/src/catalog/contracts.mjs +18 -0
- package/src/catalog/database.mjs +52 -0
- package/src/catalog/deploy.mjs +117 -0
- package/src/catalog/design.mjs +159 -0
- package/src/catalog/forms.mjs +25 -0
- package/src/catalog/hydration.mjs +29 -0
- package/src/catalog/icons.mjs +24 -0
- package/src/catalog/media.mjs +163 -0
- package/src/catalog/metadata.mjs +116 -0
- package/src/catalog/plugins.mjs +228 -0
- package/src/catalog/realtime.mjs +195 -0
- package/src/catalog/rust.mjs +75 -0
- package/src/catalog/server-functions.mjs +92 -0
- package/src/catalog/state.mjs +39 -0
- package/src/catalog/style-groups.mjs +54 -0
- package/src/catalog/tests.mjs +127 -0
- package/src/catalog/text.mjs +200 -0
- package/src/catalog/ui-state.mjs +42 -0
- package/src/catalog.mjs +68 -444
- package/src/check/runtime.mjs +280 -0
- package/src/check.mjs +199 -207
- package/src/cli/authoring.mjs +213 -0
- package/src/cli/project.mjs +302 -0
- package/src/cli.mjs +228 -224
- package/src/config.mjs +1 -1
- package/src/contributions.mjs +75 -25
- package/src/db-layer.mjs +13 -5
- package/src/deploy/cdn.mjs +142 -0
- package/src/deploy/files.mjs +266 -0
- package/src/deploy/fit.mjs +166 -0
- package/src/deploy/hosts.mjs +126 -0
- package/src/deploy/index/cli.mjs +20 -0
- package/src/deploy/index/environment.mjs +69 -0
- package/src/deploy/index/steps.mjs +87 -0
- package/src/deploy/index.mjs +387 -0
- package/src/deploy/needs.mjs +149 -0
- package/src/design-checks/accessible-names.mjs +62 -0
- package/src/design-checks/contrast.mjs +43 -0
- package/src/design-checks/csp.mjs +60 -0
- package/src/design-checks/files.mjs +59 -0
- package/src/design-checks/heading-links.mjs +61 -0
- package/src/design-checks/interaction.mjs +65 -0
- package/src/design-checks/jsx.mjs +67 -0
- package/src/design-checks/layout-values.mjs +68 -0
- package/src/design-checks/native-controls.mjs +48 -0
- package/src/design-checks/outline.mjs +39 -0
- package/src/design-checks/reporter.mjs +37 -0
- package/src/design-checks/rules.mjs +274 -0
- package/src/design-checks/values.mjs +262 -0
- package/src/design-checks.mjs +10 -400
- package/src/diag.mjs +32 -8
- package/src/docs.mjs +138 -42
- package/src/doctor.mjs +20 -8
- package/src/files.mjs +18 -5
- package/src/fix.mjs +10 -2
- package/src/gate/after.mjs +40 -0
- package/src/gate/before.mjs +54 -0
- package/src/gate/classify.mjs +72 -0
- package/src/gate/mutation.mjs +209 -0
- package/src/gate/resolve-cases.mjs +22 -0
- package/src/gate/result.mjs +63 -0
- package/src/gate/run.mjs +75 -0
- package/src/gate.mjs +104 -202
- package/src/infer/box-props.cjs +140 -0
- package/src/infer/core-boxes.cjs +46 -0
- package/src/infer/derived-writes.cjs +95 -0
- package/src/infer/engine/checks.cjs +193 -0
- package/src/infer/engine/flow.cjs +293 -0
- package/src/infer/engine/report.cjs +158 -0
- package/src/infer/engine/utils.cjs +15 -0
- package/src/infer/engine.cjs +108 -368
- package/src/infer/facts/sites.cjs +114 -0
- package/src/infer/facts.cjs +210 -117
- package/src/infer/hosts.cjs +69 -22
- package/src/infer/lsp.mjs +149 -40
- package/src/infer/session.cjs +73 -17
- package/src/infer/shadow.cjs +13 -5
- package/src/infer/worker.cjs +57 -9
- package/src/jsx-scan.mjs +63 -21
- package/src/load-config/schema.mjs +281 -0
- package/src/load-config.mjs +144 -116
- package/src/map.mjs +93 -26
- package/src/mcp/browser-tools.mjs +155 -0
- package/src/mcp.mjs +147 -69
- package/src/meta-table.mjs +43 -8
- package/src/mutate.mjs +120 -36
- package/src/plugin-checks.mjs +52 -23
- package/src/plugins.mjs +125 -39
- package/src/project.mjs +170 -79
- package/src/props-checks.mjs +24 -6
- package/src/props-listener.mjs +27 -9
- package/src/react-checks.mjs +35 -4
- package/src/server-state-checks.mjs +34 -0
- package/src/share-checks.mjs +121 -37
- package/src/site.mjs +33 -6
- package/src/slots.mjs +104 -19
- package/src/start/build.mjs +116 -0
- package/src/start/routes.mjs +38 -0
- package/src/start.mjs +194 -92
- package/src/state-checks/core-state.mjs +223 -0
- package/src/state-checks/modules.mjs +17 -0
- package/src/state-checks/scan.mjs +232 -0
- package/src/state-checks/server-graph.mjs +65 -0
- package/src/state-checks.mjs +109 -133
- package/src/style/find.mjs +121 -0
- package/src/style.mjs +133 -127
- package/src/surface-checks.mjs +212 -46
- package/src/tailwind.mjs +5 -4
- package/src/tasks.mjs +189 -27
- package/src/test-rules/context.mjs +109 -0
- package/src/test-rules/file-rules.mjs +279 -0
- package/src/test-rules/rule-helpers.mjs +244 -0
- package/src/test-rules/syntax.mjs +223 -0
- package/src/test-rules/test-rules.mjs +127 -0
- package/src/testdiff.mjs +94 -32
- package/src/tests.mjs +31 -483
- package/src/text-checks.mjs +229 -69
- package/src/tokens.mjs +12 -4
- package/src/trust/scan.mjs +95 -0
- package/src/trust.mjs +214 -102
- package/src/weft.mjs +41 -9
- package/src/workspace.mjs +96 -19
- package/ts-plugin/context.js +57 -0
- package/ts-plugin/index.js +4 -124
- package/ts-plugin/mapping.js +65 -0
- package/ts-plugin/proxy.js +144 -0
package/src/cli.mjs
CHANGED
|
@@ -24,8 +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 {
|
|
28
|
-
|
|
27
|
+
import {
|
|
28
|
+
buildCommand,
|
|
29
|
+
dbCommand,
|
|
30
|
+
devCommand,
|
|
31
|
+
doctorCommand,
|
|
32
|
+
routesCommand,
|
|
33
|
+
startCommand,
|
|
34
|
+
unsafeAllowed,
|
|
35
|
+
} from './start.mjs'
|
|
36
|
+
import { deployCommand, formatHosts, hostsCommand, initCommand } from './deploy/index.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'
|
|
29
76
|
|
|
30
77
|
const usage = `usage: weft <command> [--json] [--root <dir>]
|
|
31
78
|
|
|
@@ -42,18 +89,25 @@ const usage = `usage: weft <command> [--json] [--root <dir>]
|
|
|
42
89
|
build [--runtime node|bun|deno] [--preset <p>] production build to .output, and .weft/contract.json; then the
|
|
43
90
|
built pages are audited (L-codes) and an error fails the build
|
|
44
91
|
[--unsafe-allow <codes>] go past these codes (R002, L003, a family like L, or all): forces a deploy
|
|
45
|
-
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
|
|
46
93
|
and a desktop's width: axe-core's WCAG rules, console, deprecations, head, sideways scroll, shift
|
|
94
|
+
deploy [--prod] [--yes] [--target t] fit check, build for the target, then the host's own CLI; without --yes a dry
|
|
95
|
+
run that prints what would run. Netlify, Vercel, Cloudflare, Fly, Render; the live URL at the end
|
|
96
|
+
deploy hosts which hosts can run this app, why not, what each costs, and the one weft suggests
|
|
97
|
+
deploy init [target] [--dry-run] [--force] the host's files (netlify.toml, fly.toml + Dockerfile…), a GitHub
|
|
98
|
+
workflow that checks and deploys on push, and \`deploy\` in the config
|
|
47
99
|
start [--runtime r] run .output/ on its runtime (the config's \`runtime\`, node by default)
|
|
48
100
|
db generate [name] a migration for the schema's changes (it applies on the next request)
|
|
49
101
|
db live a migration with change triggers for the tables in \`changes\`
|
|
50
102
|
db listen the change feed as its own process, for an app on functions
|
|
51
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
|
|
52
105
|
tasks commands and project tasks
|
|
53
106
|
run <task> run a project task from weft.config.ts
|
|
54
107
|
docs [topic|code] docs for agents (rules, props, lists, async, testing, config, diagnostics, commands, W014…)
|
|
55
108
|
agents-md [--check] write AGENTS.md (--check: exit 1 when stale; --stdout: print it)
|
|
56
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
|
|
57
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]
|
|
58
112
|
plugins [list] plugins, the slots they provide and bind, permissions, diagnostics
|
|
59
113
|
plugins explain <slot> binding chain: config → plugin → module → .weft/slots/<id>.ts
|
|
@@ -64,16 +118,67 @@ const usage = `usage: weft <command> [--json] [--root <dir>]
|
|
|
64
118
|
style explain <group|selector> [--<axis> <value>] [--states a,b] [--accent intent:token] [--part p]
|
|
65
119
|
resolved CSS for one variation, and where each value came from
|
|
66
120
|
style matrix write .weft/styles.json (every group: axes, parts, accents, resolved CSS, uses)
|
|
67
|
-
mcp MCP server on stdio: docs, check,
|
|
121
|
+
mcp MCP server on stdio: docs, check, audit, routes_meta, map, tasks, surfaces, style_find,
|
|
122
|
+
style_explain, test_gate, browser_*`
|
|
68
123
|
|
|
69
124
|
/** Flags that take a value (`--base main` or `--base=main`), and boolean flags, beyond the global ones. */
|
|
70
|
-
const valued = new Set([
|
|
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
|
+
])
|
|
71
148
|
/** `weft style explain` takes the group's own axes as flags (`--status late`); these are its fixed ones. */
|
|
72
149
|
const styleFlags = new Set(['states', 'accent', 'part'])
|
|
73
|
-
const booleans = new Set([
|
|
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
|
+
])
|
|
74
171
|
|
|
75
172
|
export function parse(argv) {
|
|
76
|
-
const out = {
|
|
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
|
+
}
|
|
77
182
|
for (let i = 1; i < argv.length; i++) {
|
|
78
183
|
const a = argv[i]
|
|
79
184
|
if (a === '--json') out.json = true
|
|
@@ -85,236 +190,134 @@ export function parse(argv) {
|
|
|
85
190
|
const [name, inline] = a.slice(2).split(/=(.*)/s)
|
|
86
191
|
if (valued.has(name)) out.flags[name] = inline ?? argv[++i]
|
|
87
192
|
else if (booleans.has(name) && inline === undefined) out.flags[name] = true
|
|
88
|
-
else if (out.command === 'style' && styleFlags.has(name))
|
|
89
|
-
|
|
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]
|
|
90
201
|
else throw Object.assign(new Error(`unknown flag ${a}`), { code: 'E_USAGE' })
|
|
91
202
|
} else out.positional.push(a)
|
|
92
203
|
}
|
|
93
204
|
return out
|
|
94
205
|
}
|
|
95
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
|
+
|
|
96
261
|
export async function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
97
|
-
const print = data => io.stdout.write(`${JSON.stringify(data, null, 2)}\n`)
|
|
98
|
-
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`)
|
|
99
264
|
let args
|
|
100
265
|
try {
|
|
101
266
|
args = parse(argv)
|
|
267
|
+
const context = { ...dependencies, args, print, say, io }
|
|
102
268
|
switch (args.command) {
|
|
103
|
-
case 'lsp':
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return await
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
say(`weft tsc: ${diagnostics.filter(d => d.severity === 'error').length} errors`)
|
|
114
|
-
return diagnostics.some(d => d.severity === 'error') ? 1 : 0
|
|
115
|
-
}
|
|
116
|
-
case 'check': {
|
|
117
|
-
const result = await check(args.root, { files: args.positional, preset: args.flags.preset })
|
|
118
|
-
if (args.json) print(result)
|
|
119
|
-
say(formatHuman(result.diagnostics))
|
|
120
|
-
if (args.flags.verbose) say(compilerLine(doctor(args.root).compiler))
|
|
121
|
-
say(`weft check: ${result.summary.files} files, ${result.summary.errors} errors, ${result.summary.warnings} warnings (${result.ms} ms)`)
|
|
122
|
-
return result.ok ? 0 : 1
|
|
123
|
-
}
|
|
124
|
-
case 'doctor': {
|
|
125
|
-
// What this install compiles with, always; what live data does on the target, for an app that has any.
|
|
126
|
-
const result = doctor(args.root)
|
|
127
|
-
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
|
|
128
|
-
if (args.json) print({ ...result, live: plan })
|
|
129
|
-
else say(formatDoctor(result))
|
|
130
|
-
if (!plan) return 0
|
|
131
|
-
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')}` : ''}`)
|
|
132
|
-
say(formatHuman(plan.diagnostics))
|
|
133
|
-
return plan.diagnostics.some(d => d.severity === 'error') ? 1 : 0
|
|
134
|
-
}
|
|
135
|
-
case 'test': {
|
|
136
|
-
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' })
|
|
137
|
-
const num = name => (args.flags[name] === undefined ? undefined : Number(args.flags[name]))
|
|
138
|
-
const result = await gate(args.root, {
|
|
139
|
-
base: args.flags.base, budgetSec: num('budget'), maxMutants: num('max-mutants'), concurrency: num('concurrency'),
|
|
140
|
-
refactor: !!args.flags.refactor, mutation: !args.flags['no-mutation'], log: line => say(line),
|
|
141
|
-
})
|
|
142
|
-
if (args.json) print(result)
|
|
143
|
-
say(formatHuman(result.diagnostics))
|
|
144
|
-
say(formatGate(result))
|
|
145
|
-
say(`weft test --gate: ${result.ok ? 'pass' : 'fail'}, ${result.summary.changedTests} changed tests, ${result.summary.errors} errors, ${result.summary.warnings} warnings (${result.ms} ms)`)
|
|
146
|
-
return result.ok ? 0 : 1
|
|
147
|
-
}
|
|
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)
|
|
148
279
|
case 'browser':
|
|
149
|
-
return await
|
|
150
|
-
case 'dev':
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const result = await buildCommand(args.root, { preset: args.flags.preset, runtime: args.flags.runtime, unsafeAllow: args.flags['unsafe-allow'] })
|
|
159
|
-
if (args.json) print(result)
|
|
160
|
-
say(formatHuman(result.diagnostics))
|
|
161
|
-
say(`weft build: ${result.preset} → ${result.output}, ${result.functions} functions in the contract, ${result.audited} pages audited (${result.ms} ms)`)
|
|
162
|
-
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.')
|
|
163
|
-
return result.ok ? 0 : 1
|
|
164
|
-
}
|
|
165
|
-
case 'audit': {
|
|
166
|
-
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) })
|
|
167
|
-
if (args.json) print(result)
|
|
168
|
-
say(formatHuman(result.diagnostics))
|
|
169
|
-
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)`}`)
|
|
170
|
-
return result.ok ? 0 : 1
|
|
171
|
-
}
|
|
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)
|
|
172
289
|
case 'start':
|
|
173
|
-
return await
|
|
174
|
-
case 'db':
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
case '
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
case '
|
|
201
|
-
|
|
202
|
-
return await runTask(args.root, args.positional[0])
|
|
203
|
-
}
|
|
204
|
-
case 'docs': {
|
|
205
|
-
const page = await docs(args.root, args.positional[0])
|
|
206
|
-
if (args.json) print(page)
|
|
207
|
-
else io.stdout.write(`${page.text}\n`)
|
|
208
|
-
return 0
|
|
209
|
-
}
|
|
210
|
-
case 'agents-md': {
|
|
211
|
-
const out = await writeAgentsMd(args.root, { check: args.check || args.stdout })
|
|
212
|
-
if (args.stdout) io.stdout.write(out.text)
|
|
213
|
-
if (args.json) print({ path: out.path, bytes: out.bytes, limit: 8192, overLimit: out.overLimit, current: out.current, written: out.written })
|
|
214
|
-
say(`AGENTS.md: ${out.bytes} bytes${out.overLimit ? ' (over the 8 KB limit)' : ''}, ${out.written ? 'written' : out.current ? 'current' : 'stale'}`)
|
|
215
|
-
return (args.check && !out.current) || out.overLimit ? 1 : 0
|
|
216
|
-
}
|
|
217
|
-
case 'map': {
|
|
218
|
-
const map = await writeMap(args.root)
|
|
219
|
-
if (args.json) print(map)
|
|
220
|
-
else if (args.stdout) process.stdout.write(formatMap(map))
|
|
221
|
-
else say(`.weft/map.md: ${map.rows.length} rows`)
|
|
222
|
-
return 0
|
|
223
|
-
}
|
|
224
|
-
case 'icons': {
|
|
225
|
-
if (args.positional[0] !== 'publish' || !args.positional[1]) { say('usage: weft icons publish <dir>'); return 1 }
|
|
226
|
-
const { publish } = await import(pathToFileURL(createRequire(join(args.root, 'package.json')).resolve('@weft-js/warp-icons/compile')).href)
|
|
227
|
-
const out = await publish(resolve(args.root, args.positional[1]))
|
|
228
|
-
if (args.json) print(out)
|
|
229
|
-
else say(`${out.files} files for ${out.icons} icons in ${out.dir}. Nothing was uploaded: host that folder, then set icons({ source }).`)
|
|
230
|
-
return 0
|
|
231
|
-
}
|
|
232
|
-
case 'add': {
|
|
233
|
-
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 })
|
|
234
|
-
if (args.json) print(result)
|
|
235
|
-
else say(result.already ? `${result.package} is already in ${result.config}` : result.actions.map(a => `${args.flags['dry-run'] ? 'would ' : ''}${a}`).join('\n'))
|
|
236
|
-
return 0
|
|
237
|
-
}
|
|
238
|
-
case 'plugins': {
|
|
239
|
-
const sub = args.positional[0] ?? 'list'
|
|
240
|
-
if (sub === 'list') {
|
|
241
|
-
const result = await list(args.root)
|
|
242
|
-
if (args.json) print(result)
|
|
243
|
-
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')
|
|
244
|
-
say(formatHuman(result.diagnostics))
|
|
245
|
-
return result.ok ? 0 : 1
|
|
246
|
-
}
|
|
247
|
-
if (sub === 'explain') {
|
|
248
|
-
const result = await explain(args.root, args.positional[1])
|
|
249
|
-
if (args.json) print(result)
|
|
250
|
-
else say(JSON.stringify(result.binding, null, 2))
|
|
251
|
-
return 0
|
|
252
|
-
}
|
|
253
|
-
if (sub === 'sync') {
|
|
254
|
-
const loaded = await loadConfig(args.root)
|
|
255
|
-
const errors = loaded.diagnostics.filter(d => d.severity === 'error')
|
|
256
|
-
const written = loaded.diagnostics.some(d => d.code === 'P007') ? [] : sync(args.root, loaded)
|
|
257
|
-
if (args.json) print({ ok: errors.length === 0, written, diagnostics: loaded.diagnostics })
|
|
258
|
-
say(formatHuman(loaded.diagnostics))
|
|
259
|
-
say(`weft plugins sync: ${written.length} files written`)
|
|
260
|
-
return errors.length ? 1 : 0
|
|
261
|
-
}
|
|
262
|
-
if (sub === 'audit') {
|
|
263
|
-
const loaded = await loadConfig(args.root)
|
|
264
|
-
const result = auditCommand(args.root, loaded)
|
|
265
|
-
if (args.json) print(result)
|
|
266
|
-
else say(result.plugins.map(formatTrust).join('\n') || 'no plugins')
|
|
267
|
-
say(formatHuman(result.diagnostics))
|
|
268
|
-
return result.ok ? 0 : 1
|
|
269
|
-
}
|
|
270
|
-
if (sub === 'approve') {
|
|
271
|
-
const result = approveCommand(args.root, await loadConfig(args.root), { names: args.positional.slice(1), all: !!args.flags.all })
|
|
272
|
-
if (args.json) print(result)
|
|
273
|
-
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'))
|
|
274
|
-
return result.ok ? 0 : 1
|
|
275
|
-
}
|
|
276
|
-
throw Object.assign(new Error(`unknown plugins subcommand ${String(sub)}`), { code: 'E_USAGE' })
|
|
277
|
-
}
|
|
278
|
-
case 'fix': {
|
|
279
|
-
const result = await fix(args.root, { files: args.positional })
|
|
280
|
-
if (args.json) print(result)
|
|
281
|
-
else say(result.applied.length ? result.applied.map(a => `${a.file}:${a.span[0]}:${a.span[1]} ${a.code}`).join('\n') : 'nothing to fix')
|
|
282
|
-
return 0
|
|
283
|
-
}
|
|
284
|
-
case 'tokens': {
|
|
285
|
-
const result = await writeTokens(args.root)
|
|
286
|
-
if (args.json) print(result)
|
|
287
|
-
else say(`${result.dir}: ${result.files.length} files`)
|
|
288
|
-
return 0
|
|
289
|
-
}
|
|
290
|
-
case 'style': {
|
|
291
|
-
const sub = args.positional[0]
|
|
292
|
-
if (sub === 'find') {
|
|
293
|
-
const result = findGroups((await styleMatrix(args.root)).matrix, args.positional.slice(1).join(' '))
|
|
294
|
-
if (args.json) print(result)
|
|
295
|
-
else say(formatFind(result))
|
|
296
|
-
return 0
|
|
297
|
-
}
|
|
298
|
-
if (sub === 'explain') {
|
|
299
|
-
const accent = args.flags.accent ? Object.fromEntries(args.flags.accent.split(/[\s,]+/).filter(Boolean).map(w => w.split(':'))) : undefined
|
|
300
|
-
const states = args.flags.states?.split(',').map(s => s.trim()).filter(Boolean)
|
|
301
|
-
const result = explainTarget(await styleMatrix(args.root), args.positional.slice(1).join(' '), { axes: args.flags.axes, states, accent, part: args.flags.part })
|
|
302
|
-
if (args.json) print(result)
|
|
303
|
-
else say(formatExplain(result))
|
|
304
|
-
return 0
|
|
305
|
-
}
|
|
306
|
-
if (sub === 'matrix') {
|
|
307
|
-
const { matrix } = await styleMatrix(args.root)
|
|
308
|
-
const written = args.stdout ? false : writeMatrixFile(args.root, matrix)
|
|
309
|
-
if (args.stdout || args.json) print(matrix)
|
|
310
|
-
say(`.weft/styles.json: ${Object.keys(matrix.groups).length} groups, ${matrix.diagnostics.length} diagnostics, ${args.stdout ? 'not written' : written ? 'written' : 'current'}`)
|
|
311
|
-
return 0
|
|
312
|
-
}
|
|
313
|
-
throw Object.assign(new Error(`weft style ${sub ?? ''}: use find <query>, explain <group|selector> or matrix`), { code: 'E_USAGE' })
|
|
314
|
-
}
|
|
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)
|
|
315
319
|
case 'mcp':
|
|
316
|
-
await
|
|
317
|
-
return 0
|
|
320
|
+
return await commandMcp(context)
|
|
318
321
|
case undefined:
|
|
319
322
|
case 'help':
|
|
320
323
|
case '--help':
|
|
@@ -325,7 +328,8 @@ export async function main(argv, io = { stdout: process.stdout, stderr: process.
|
|
|
325
328
|
}
|
|
326
329
|
} catch (error) {
|
|
327
330
|
const code = error.code ?? 'E_INTERNAL'
|
|
328
|
-
if (args?.json || argv.includes('--json'))
|
|
331
|
+
if (args?.json || argv.includes('--json'))
|
|
332
|
+
print({ ok: false, error: { code, message: error.message } })
|
|
329
333
|
say(`weft: ${error.message}${code === 'E_USAGE' ? `\n\n${usage}` : ''}`)
|
|
330
334
|
return code === 'E_REFUSED' ? 1 : 2
|
|
331
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
|
package/src/contributions.mjs
CHANGED
|
@@ -9,7 +9,12 @@ const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)
|
|
|
9
9
|
function stable(value) {
|
|
10
10
|
return JSON.stringify(value, (_, v) => {
|
|
11
11
|
if (typeof v === 'function') return `[function ${v.name}]`
|
|
12
|
-
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
12
|
+
if (v && typeof v === 'object' && !Array.isArray(v))
|
|
13
|
+
return Object.fromEntries(
|
|
14
|
+
Object.keys(v)
|
|
15
|
+
.sort()
|
|
16
|
+
.map((k) => [k, v[k]]),
|
|
17
|
+
)
|
|
13
18
|
return v
|
|
14
19
|
})
|
|
15
20
|
}
|
|
@@ -27,43 +32,88 @@ export function contributions(plugins, report, env) {
|
|
|
27
32
|
const catalogOwner = new Map()
|
|
28
33
|
for (const p of [...plugins].sort(byName)) {
|
|
29
34
|
if (typeof p.contribute !== 'function') continue
|
|
30
|
-
const run = () =>
|
|
35
|
+
const run = () =>
|
|
36
|
+
p.contribute({ command: env.command ?? 'check', mode: 'development', root: env.root })
|
|
31
37
|
let manifest
|
|
32
38
|
try {
|
|
33
39
|
manifest = run()
|
|
34
40
|
} catch (error) {
|
|
35
41
|
const code = typeof error?.code === 'string' ? error.code : 'P007'
|
|
36
|
-
report(code, `\`${p.name}\`: ${String(error?.message ?? error).split('\n')[0]}`, p.name, {
|
|
42
|
+
report(code, `\`${p.name}\`: ${String(error?.message ?? error).split('\n')[0]}`, p.name, {
|
|
43
|
+
owner: code === 'P007' ? 'core' : p.name,
|
|
44
|
+
help: error?.help,
|
|
45
|
+
})
|
|
37
46
|
continue
|
|
38
47
|
}
|
|
39
48
|
let again
|
|
40
49
|
try {
|
|
41
50
|
again = run()
|
|
42
51
|
} catch {}
|
|
43
|
-
if (!again || stable(again) !== stable(manifest))
|
|
52
|
+
if (!again || stable(again) !== stable(manifest))
|
|
53
|
+
report(
|
|
54
|
+
'P117',
|
|
55
|
+
`\`${p.name}\`'s contribute() returned a different manifest on a second call.`,
|
|
56
|
+
p.name,
|
|
57
|
+
)
|
|
44
58
|
|
|
45
|
-
const ns = p
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
if ((manifest.map?.length ?? 0) > mapLimit) report('P112', `\`${p.name}\` adds ${manifest.map.length} map rows (limit ${mapLimit}).`, p.name)
|
|
55
|
-
const tns = manifest.tokens?.namespace
|
|
56
|
-
if (tns) {
|
|
57
|
-
if (tokenOwner.has(tns)) report('P113', `Token namespace \`${tns}\` is contributed by both \`${tokenOwner.get(tns)}\` and \`${p.name}\`.`, p.name)
|
|
58
|
-
else tokenOwner.set(tns, p.name)
|
|
59
|
-
}
|
|
60
|
-
for (const c of manifest.catalog ?? []) {
|
|
61
|
-
if (catalogOwner.has(c.name)) report('P113', `Catalog entry \`${c.name}\` is contributed by both \`${catalogOwner.get(c.name)}\` and \`${p.name}\`.`, p.name)
|
|
62
|
-
else catalogOwner.set(c.name, p.name)
|
|
63
|
-
}
|
|
64
|
-
out.push({ name: p.name, version: p.version, namespace: ns, permissions: p.permissions ?? {}, manifest })
|
|
59
|
+
const ns = checkManifest(p, manifest, report, tokenOwner, catalogOwner)
|
|
60
|
+
out.push({
|
|
61
|
+
name: p.name,
|
|
62
|
+
version: p.version,
|
|
63
|
+
namespace: ns,
|
|
64
|
+
permissions: p.permissions ?? {},
|
|
65
|
+
manifest,
|
|
66
|
+
})
|
|
65
67
|
}
|
|
66
68
|
return out
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
function checkManifest(p, manifest, report, tokenOwner, catalogOwner) {
|
|
72
|
+
const ns = p.namespace ?? `${p.name.replace(/^@[^/]+\//, '').replace(/^plugin-/, '')}/`
|
|
73
|
+
for (const d of manifest.diagnostics ?? []) {
|
|
74
|
+
if (!d.code.startsWith(ns))
|
|
75
|
+
report(
|
|
76
|
+
'P111',
|
|
77
|
+
`\`${p.name}\` declares \`${d.code}\`, outside its namespace \`${ns}\`.`,
|
|
78
|
+
p.name,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
const agents = manifest.agents
|
|
82
|
+
if (agents) {
|
|
83
|
+
const bytes = Buffer.byteLength(agentsSection(agents))
|
|
84
|
+
if (bytes > agentsLimit)
|
|
85
|
+
report(
|
|
86
|
+
'P112',
|
|
87
|
+
`\`${p.name}\`'s AGENTS section is ${bytes} bytes (limit ${agentsLimit}).`,
|
|
88
|
+
p.name,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
if ((manifest.map?.length ?? 0) > mapLimit)
|
|
92
|
+
report(
|
|
93
|
+
'P112',
|
|
94
|
+
`\`${p.name}\` adds ${manifest.map.length} map rows (limit ${mapLimit}).`,
|
|
95
|
+
p.name,
|
|
96
|
+
)
|
|
97
|
+
const tns = manifest.tokens?.namespace
|
|
98
|
+
if (tns) {
|
|
99
|
+
if (tokenOwner.has(tns))
|
|
100
|
+
report(
|
|
101
|
+
'P113',
|
|
102
|
+
`Token namespace \`${tns}\` is contributed by both \`${tokenOwner.get(tns)}\` and \`${p.name}\`.`,
|
|
103
|
+
p.name,
|
|
104
|
+
)
|
|
105
|
+
else tokenOwner.set(tns, p.name)
|
|
106
|
+
}
|
|
107
|
+
for (const c of manifest.catalog ?? []) {
|
|
108
|
+
if (catalogOwner.has(c.name))
|
|
109
|
+
report(
|
|
110
|
+
'P113',
|
|
111
|
+
`Catalog entry \`${c.name}\` is contributed by both \`${catalogOwner.get(c.name)}\` and \`${p.name}\`.`,
|
|
112
|
+
p.name,
|
|
113
|
+
)
|
|
114
|
+
else catalogOwner.set(c.name, p.name)
|
|
115
|
+
}
|
|
116
|
+
return ns
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const agentsSection = (agents) => `### ${agents.title}\n\n${agents.text.trim()}\n`
|