@playgenx/components 0.2.0 → 0.2.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/bin/state-cli.mjs +196 -0
- package/package.json +6 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
/**
|
|
4
|
+
* playgenx-state — CLI for inspecting and operating on
|
|
5
|
+
* PlaygroundState snapshots.
|
|
6
|
+
*
|
|
7
|
+
* Pure ESM, no extra deps. Reads JSON from stdin (or argv),
|
|
8
|
+
* writes JSON to stdout.
|
|
9
|
+
*
|
|
10
|
+
* Subcommands:
|
|
11
|
+
*
|
|
12
|
+
* snapshot Pretty-print a snapshot in the v1 envelope format.
|
|
13
|
+
* Accepts a JSON object on stdin (the snapshot you
|
|
14
|
+
* captured from `state.snapshot()` or a DebugSurface).
|
|
15
|
+
*
|
|
16
|
+
* validate Validate one or more stateKey strings from stdin.
|
|
17
|
+
* Reads a JSON array of strings, returns the per-key
|
|
18
|
+
* verdict.
|
|
19
|
+
*
|
|
20
|
+
* diff Diff two snapshots. Reads `{prev, next}` from stdin
|
|
21
|
+
* and returns the structured {added, changed, removed}
|
|
22
|
+
* diff (or null when equal).
|
|
23
|
+
*
|
|
24
|
+
* keys List the sorted keys of a snapshot from stdin.
|
|
25
|
+
*
|
|
26
|
+
* count Count the keys in a snapshot from stdin.
|
|
27
|
+
*
|
|
28
|
+
* Usage examples (run from the components package root):
|
|
29
|
+
*
|
|
30
|
+
* echo '{"volume": 5}' | node bin/state-cli.mjs snapshot
|
|
31
|
+
* echo '["volume","user.name","bad key"]' | node bin/state-cli.mjs validate
|
|
32
|
+
* echo '{"prev":{"a":1},"next":{"a":2,"b":3}}' | node bin/state-cli.mjs diff
|
|
33
|
+
*
|
|
34
|
+
* Exit codes:
|
|
35
|
+
*
|
|
36
|
+
* 0 success
|
|
37
|
+
* 1 bad subcommand
|
|
38
|
+
* 2 bad input (not parseable as JSON, wrong shape, etc.)
|
|
39
|
+
* 3 validation failure (one or more keys rejected)
|
|
40
|
+
*
|
|
41
|
+
* @packageDocumentation
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
dumpState,
|
|
46
|
+
diffSnapshots,
|
|
47
|
+
validateStateKey,
|
|
48
|
+
} from '../dist/index.mjs';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Read all of stdin into a string. Node-only.
|
|
52
|
+
*/
|
|
53
|
+
async function readStdin() {
|
|
54
|
+
if (process.stdin.isTTY) {
|
|
55
|
+
// Interactive / no pipe — return empty; the caller will treat
|
|
56
|
+
// this as "no input" and print usage.
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
const chunks = [];
|
|
60
|
+
for await (const chunk of process.stdin) {
|
|
61
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
62
|
+
}
|
|
63
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function usage() {
|
|
67
|
+
return [
|
|
68
|
+
'Usage: playgenx-state <subcommand> [input]',
|
|
69
|
+
'',
|
|
70
|
+
'Subcommands:',
|
|
71
|
+
' snapshot Pretty-print a state snapshot (JSON on stdin)',
|
|
72
|
+
' validate Validate an array of stateKey strings (JSON array on stdin)',
|
|
73
|
+
' diff Diff two snapshots ({prev, next} on stdin)',
|
|
74
|
+
' keys List sorted keys of a snapshot (JSON on stdin)',
|
|
75
|
+
' count Count keys in a snapshot (JSON on stdin)',
|
|
76
|
+
'',
|
|
77
|
+
'Reads from stdin when no arg is given.',
|
|
78
|
+
].join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ok(payload) {
|
|
82
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function fail(code, msg) {
|
|
86
|
+
process.stderr.write(`error: ${msg}\n`);
|
|
87
|
+
process.exit(code);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Convert an arbitrary snapshot to the StateEnvelope shape. The
|
|
92
|
+
* input is whatever the caller passed; we expect a plain object.
|
|
93
|
+
*/
|
|
94
|
+
function envelope(input) {
|
|
95
|
+
const keys = Object.keys(input).sort();
|
|
96
|
+
return { version: 1, keys, values: { ...input } };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function main(argv) {
|
|
100
|
+
const sub = argv[0];
|
|
101
|
+
if (!sub || sub === '-h' || sub === '--help') {
|
|
102
|
+
process.stdout.write(usage() + '\n');
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
const stdinPayload = (await readStdin()).trim();
|
|
106
|
+
const argPayload = argv[1];
|
|
107
|
+
|
|
108
|
+
switch (sub) {
|
|
109
|
+
case 'snapshot': {
|
|
110
|
+
const input = parseObject(argPayload ?? stdinPayload);
|
|
111
|
+
// Re-use the library's envelope so the CLI output matches the
|
|
112
|
+
// canonical dumpState shape. (We don't have a real store to
|
|
113
|
+
// pass to dumpState; envelope() builds the same structure.)
|
|
114
|
+
const env = envelope(input);
|
|
115
|
+
process.stdout.write(JSON.stringify(env, null, 2) + '\n');
|
|
116
|
+
// Reference dumpState so it's not tree-shaken — the import
|
|
117
|
+
// is documented as the canonical envelope producer.
|
|
118
|
+
void dumpState;
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
case 'validate': {
|
|
123
|
+
const input = parseArray(argPayload ?? stdinPayload);
|
|
124
|
+
const results = input.map((k) => {
|
|
125
|
+
if (typeof k !== 'string') {
|
|
126
|
+
return { key: String(k), ok: false, error: 'must be a string' };
|
|
127
|
+
}
|
|
128
|
+
const err = validateStateKey(k);
|
|
129
|
+
return err === null
|
|
130
|
+
? { key: k, ok: true }
|
|
131
|
+
: { key: k, ok: false, error: err };
|
|
132
|
+
});
|
|
133
|
+
const anyFailed = results.some((r) => !r.ok);
|
|
134
|
+
ok(results);
|
|
135
|
+
return anyFailed ? 3 : 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case 'diff': {
|
|
139
|
+
const input = parseObject(argPayload ?? stdinPayload);
|
|
140
|
+
if (!('prev' in input) || !('next' in input)) {
|
|
141
|
+
fail(2, 'diff expects {prev, next}');
|
|
142
|
+
}
|
|
143
|
+
const prev = input.prev;
|
|
144
|
+
const next = input.next;
|
|
145
|
+
if (typeof prev !== 'object' || prev === null) fail(2, 'prev must be an object');
|
|
146
|
+
if (typeof next !== 'object' || next === null) fail(2, 'next must be an object');
|
|
147
|
+
const d = diffSnapshots(prev, next);
|
|
148
|
+
ok(d);
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
case 'keys': {
|
|
153
|
+
const input = parseObject(argPayload ?? stdinPayload);
|
|
154
|
+
ok(Object.keys(input).sort());
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
case 'count': {
|
|
159
|
+
const input = parseObject(argPayload ?? stdinPayload);
|
|
160
|
+
ok({ keys: Object.keys(input).length });
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
default:
|
|
165
|
+
process.stderr.write(usage() + '\n');
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseObject(raw) {
|
|
171
|
+
if (raw === '') fail(2, 'expected a JSON object on stdin or as argv[1]');
|
|
172
|
+
try {
|
|
173
|
+
const v = JSON.parse(raw);
|
|
174
|
+
if (typeof v !== 'object' || v === null || Array.isArray(v)) {
|
|
175
|
+
fail(2, 'expected a JSON object');
|
|
176
|
+
}
|
|
177
|
+
return v;
|
|
178
|
+
} catch (err) {
|
|
179
|
+
fail(2, `JSON parse error: ${err.message}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function parseArray(raw) {
|
|
184
|
+
if (raw === '') fail(2, 'expected a JSON array on stdin or as argv[1]');
|
|
185
|
+
try {
|
|
186
|
+
const v = JSON.parse(raw);
|
|
187
|
+
if (!Array.isArray(v)) fail(2, 'expected a JSON array');
|
|
188
|
+
return v;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
fail(2, `JSON parse error: ${err.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main(process.argv.slice(2))
|
|
195
|
+
.then((code) => process.exit(code))
|
|
196
|
+
.catch((err) => fail(2, err.message ?? String(err)));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playgenx/components",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Default React 19 implementations for every entry in DEFAULT_REGISTRY. Pair with @playgenx/registry and the parser/validator pipeline.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
"import": "./dist/index.mjs"
|
|
12
12
|
}
|
|
13
13
|
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"playgenx-state": "./bin/state-cli.mjs"
|
|
16
|
+
},
|
|
14
17
|
"files": [
|
|
15
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"bin"
|
|
16
20
|
],
|
|
17
21
|
"scripts": {
|
|
18
22
|
"build": "tsdown",
|