@kumikijs/cli 0.1.0
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 +24 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +2 -0
- package/dist/kumiki.d.ts +1 -0
- package/dist/kumiki.js +219 -0
- package/dist/smoke-CY4f_tnY.js +530 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# @kumikijs/cli
|
|
2
|
+
|
|
3
|
+
The `kumiki` command — build, check, and AI-edit (list/view/add/replace/remove/rename/fix) Kumiki sources. Part of [Kumiki](https://github.com/kage1020/Kumiki), an AI-first web framework language.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm i -g @kumikijs/cli
|
|
9
|
+
# or run without installing
|
|
10
|
+
npx @kumikijs/cli --help
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
kumiki build <input.kumiki> <outdir> # compile to a runnable app
|
|
17
|
+
kumiki check <input.kumiki> # type-check and report diagnostics
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Run `kumiki` with no arguments to see the full command list (list / view / add / replace / remove / rename / fix / smoke / run).
|
|
21
|
+
|
|
22
|
+
## License
|
|
23
|
+
|
|
24
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Def, KumikiError, Program } from "@kumikijs/compiler";
|
|
2
|
+
import { Scenario, ScenarioReport, SmokeReport } from "@kumikijs/runtime";
|
|
3
|
+
|
|
4
|
+
//#region src/store.d.ts
|
|
5
|
+
type DefRange = {
|
|
6
|
+
/** 1-based start line in the source file. */startLine: number; /** 1-based end line, inclusive. */
|
|
7
|
+
endLine: number;
|
|
8
|
+
};
|
|
9
|
+
type DefEntry = {
|
|
10
|
+
layer: string;
|
|
11
|
+
name: string;
|
|
12
|
+
def: Def;
|
|
13
|
+
range: DefRange;
|
|
14
|
+
};
|
|
15
|
+
type Store = {
|
|
16
|
+
source: string;
|
|
17
|
+
lines: string[];
|
|
18
|
+
program: Program;
|
|
19
|
+
defs: DefEntry[];
|
|
20
|
+
byQName: Map<string, DefEntry>;
|
|
21
|
+
};
|
|
22
|
+
declare function load(path: string): Store;
|
|
23
|
+
declare function viewDef(store: Store, qname: string): string | null;
|
|
24
|
+
declare function viewWithDeps(store: Store, qname: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Return qnames that the definition at `qname` references. The match is
|
|
27
|
+
* textual (identifier token in the source range) and intentionally over-
|
|
28
|
+
* inclusive: any identifier that names another definition is considered a
|
|
29
|
+
* dependency.
|
|
30
|
+
*/
|
|
31
|
+
declare function directDeps(store: Store, qname: string): string[];
|
|
32
|
+
type RefSite = {
|
|
33
|
+
qname: string;
|
|
34
|
+
layer: string;
|
|
35
|
+
name: string;
|
|
36
|
+
line: number;
|
|
37
|
+
};
|
|
38
|
+
declare function findReferences(store: Store, targetQname: string): RefSite[];
|
|
39
|
+
declare function listDefs(store: Store, layer?: string): DefEntry[];
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/fix.d.ts
|
|
42
|
+
type AutoPatch = {
|
|
43
|
+
code: string;
|
|
44
|
+
message: string; /** Free-form description of the fix to be applied. */
|
|
45
|
+
description: string;
|
|
46
|
+
apply: (text: string) => string;
|
|
47
|
+
};
|
|
48
|
+
declare function planFixes(store: Store, errors: KumikiError[]): AutoPatch[];
|
|
49
|
+
declare function fixCmd(path: string, apply: boolean, onlyCode?: string): void;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/mutate.d.ts
|
|
52
|
+
declare function addDef(path: string, layer: string, name: string, body: string): void;
|
|
53
|
+
declare function replaceDef(path: string, qname: string, body: string): void;
|
|
54
|
+
declare function removeDef(path: string, qname: string, cascade: boolean): void;
|
|
55
|
+
declare function renameDef(path: string, qname: string, newName: string): void;
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/smoke.d.ts
|
|
58
|
+
/** Compile + mount + exercise a Kumiki source string; return the smoke report. */
|
|
59
|
+
declare function smokeSource(source: string): Promise<SmokeReport>;
|
|
60
|
+
declare function smokeFile(path: string): Promise<SmokeReport>;
|
|
61
|
+
/** CLI entry: print a human-readable report and exit non-zero on failure. */
|
|
62
|
+
declare function smokeCmd(path: string): Promise<void>;
|
|
63
|
+
/** Compile + mount + drive a scenario; return the structured trace. */
|
|
64
|
+
declare function runScenarioSource(source: string, scenario: Scenario): Promise<ScenarioReport>;
|
|
65
|
+
/** CLI entry: run a scenario JSON file against a .kumiki file; print the trace. */
|
|
66
|
+
declare function runCmd(kumikiPath: string, scenarioPath: string): Promise<void>;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type AutoPatch, type Store, addDef, directDeps, findReferences, fixCmd, listDefs, load, planFixes, removeDef, renameDef, replaceDef, runCmd, runScenarioSource, smokeCmd, smokeFile, smokeSource, viewDef, viewWithDeps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { _ as viewWithDeps, a as smokeSource, c as renameDef, d as planFixes, f as directDeps, g as viewDef, h as load, i as smokeFile, l as replaceDef, m as listDefs, n as runScenarioSource, o as addDef, p as findReferences, r as smokeCmd, s as removeDef, t as runCmd, u as fixCmd } from "./smoke-CY4f_tnY.js";
|
|
2
|
+
export { addDef, directDeps, findReferences, fixCmd, listDefs, load, planFixes, removeDef, renameDef, replaceDef, runCmd, runScenarioSource, smokeCmd, smokeFile, smokeSource, viewDef, viewWithDeps };
|
package/dist/kumiki.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/kumiki.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { _ as viewWithDeps, c as renameDef, g as viewDef, h as load, l as replaceDef, m as listDefs, o as addDef, p as findReferences, r as smokeCmd, s as removeDef, t as runCmd, u as fixCmd } from "./smoke-CY4f_tnY.js";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { check, compile } from "@kumikijs/compiler";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
//#region src/kumiki.ts
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
function usage() {
|
|
10
|
+
console.error("Usage:");
|
|
11
|
+
console.error(" kumiki build <input.kumiki> <outdir>");
|
|
12
|
+
console.error(" kumiki list <input.kumiki> [layer]");
|
|
13
|
+
console.error(" kumiki view <input.kumiki> <qname> [--with-deps]");
|
|
14
|
+
console.error(" kumiki refs <input.kumiki> <qname>");
|
|
15
|
+
console.error(" kumiki check <input.kumiki> [--strict-a11y]");
|
|
16
|
+
console.error(" kumiki smoke <input.kumiki>");
|
|
17
|
+
console.error(" kumiki run <input.kumiki> <scenario.json>");
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
|
20
|
+
function buildCmd(inputArg, outdirArg) {
|
|
21
|
+
const inputPath = resolve(process.cwd(), inputArg);
|
|
22
|
+
const outdir = resolve(process.cwd(), outdirArg);
|
|
23
|
+
const result = compile(readFileSync(inputPath, "utf8"), { runtimeSpecifier: "./runtime.js" });
|
|
24
|
+
if (result.kind === "fail") {
|
|
25
|
+
for (const err of result.errors) console.error(`${err.code} ${err.kind} at ${err.pos.line}:${err.pos.col}: ${err.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
mkdirSync(outdir, { recursive: true });
|
|
29
|
+
writeFileSync(resolve(outdir, "app.js"), result.js);
|
|
30
|
+
writeFileSync(resolve(outdir, "runtime.js"), buildRuntimeBundle());
|
|
31
|
+
writeFileSync(resolve(outdir, "index.html"), buildHtml());
|
|
32
|
+
console.log(`Wrote ${outdir}/index.html, app.js, runtime.js`);
|
|
33
|
+
}
|
|
34
|
+
function listCmd(inputArg, layer) {
|
|
35
|
+
const entries = listDefs(load(resolve(process.cwd(), inputArg)), layer);
|
|
36
|
+
for (const e of entries) console.log(`${e.layer.padEnd(8)} ${e.name} (${e.range.startLine}-${e.range.endLine})`);
|
|
37
|
+
}
|
|
38
|
+
function viewCmd(inputArg, qname, withDeps) {
|
|
39
|
+
const store = load(resolve(process.cwd(), inputArg));
|
|
40
|
+
const out = withDeps ? viewWithDeps(store, qname) : viewDef(store, qname);
|
|
41
|
+
if (out === null) {
|
|
42
|
+
console.error(`Definition "${qname}" not found`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log(out);
|
|
46
|
+
}
|
|
47
|
+
function refsCmd(inputArg, qname) {
|
|
48
|
+
const refs = findReferences(load(resolve(process.cwd(), inputArg)), qname);
|
|
49
|
+
if (refs.length === 0) {
|
|
50
|
+
console.log(`(no references to ${qname})`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
for (const r of refs) console.log(`${r.qname} ${inputArg}:${r.line}`);
|
|
54
|
+
}
|
|
55
|
+
function checkCmd(inputArg, strictA11y) {
|
|
56
|
+
const errors = check(load(resolve(process.cwd(), inputArg)).program, { strictA11y });
|
|
57
|
+
if (errors.length === 0) {
|
|
58
|
+
console.log("ok");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const err of errors) console.error(`${err.code} ${err.kind} at ${err.pos.line}:${err.pos.col}: ${err.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
async function main(argv) {
|
|
65
|
+
const cmd = argv[2];
|
|
66
|
+
if (!cmd) usage();
|
|
67
|
+
switch (cmd) {
|
|
68
|
+
case "build": {
|
|
69
|
+
const input = argv[3];
|
|
70
|
+
const out = argv[4];
|
|
71
|
+
if (!input || !out) usage();
|
|
72
|
+
buildCmd(input, out);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
case "list": {
|
|
76
|
+
const input = argv[3];
|
|
77
|
+
if (!input) usage();
|
|
78
|
+
listCmd(input, argv[4]);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
case "view": {
|
|
82
|
+
const input = argv[3];
|
|
83
|
+
const qname = argv[4];
|
|
84
|
+
if (!input || !qname) usage();
|
|
85
|
+
viewCmd(input, qname, argv.includes("--with-deps"));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
case "refs": {
|
|
89
|
+
const input = argv[3];
|
|
90
|
+
const qname = argv[4];
|
|
91
|
+
if (!input || !qname) usage();
|
|
92
|
+
refsCmd(input, qname);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
case "check": {
|
|
96
|
+
const input = argv[3];
|
|
97
|
+
if (!input) usage();
|
|
98
|
+
checkCmd(input, argv.includes("--strict-a11y"));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
case "smoke": {
|
|
102
|
+
const input = argv[3];
|
|
103
|
+
if (!input) usage();
|
|
104
|
+
await smokeCmd(resolve(process.cwd(), input));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case "run": {
|
|
108
|
+
const input = argv[3];
|
|
109
|
+
const scenario = argv[4];
|
|
110
|
+
if (!input || !scenario) usage();
|
|
111
|
+
await runCmd(resolve(process.cwd(), input), resolve(process.cwd(), scenario));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
case "add": {
|
|
115
|
+
const [, , , file, layer, name, ...rest] = argv;
|
|
116
|
+
if (!file || !layer || !name || rest.length === 0) {
|
|
117
|
+
console.error("Usage: kumiki add <file> <layer> <name> <body>");
|
|
118
|
+
process.exit(2);
|
|
119
|
+
}
|
|
120
|
+
const body = rest.join(" ");
|
|
121
|
+
try {
|
|
122
|
+
addDef(resolve(process.cwd(), file), layer, name, body);
|
|
123
|
+
console.log(`added ${layer}.${name}`);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(String(e));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case "replace": {
|
|
131
|
+
const [, , , file, qname, ...rest] = argv;
|
|
132
|
+
if (!file || !qname || rest.length === 0) {
|
|
133
|
+
console.error("Usage: kumiki replace <file> <qname> <body>");
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
const body = rest.join(" ");
|
|
137
|
+
try {
|
|
138
|
+
replaceDef(resolve(process.cwd(), file), qname, body);
|
|
139
|
+
console.log(`replaced ${qname}`);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error(String(e));
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
case "remove": {
|
|
147
|
+
const [, , , file, qname] = argv;
|
|
148
|
+
if (!file || !qname) {
|
|
149
|
+
console.error("Usage: kumiki remove <file> <qname> [--cascade]");
|
|
150
|
+
process.exit(2);
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
removeDef(resolve(process.cwd(), file), qname, argv.includes("--cascade"));
|
|
154
|
+
console.log(`removed ${qname}`);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error(String(e));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
case "rename": {
|
|
162
|
+
const [, , , file, qname, newName] = argv;
|
|
163
|
+
if (!file || !qname || !newName) {
|
|
164
|
+
console.error("Usage: kumiki rename <file> <qname> <new-name>");
|
|
165
|
+
process.exit(2);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
renameDef(resolve(process.cwd(), file), qname, newName);
|
|
169
|
+
console.log(`renamed ${qname} -> ${newName}`);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
console.error(String(e));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
case "fix": {
|
|
177
|
+
const file = argv[3];
|
|
178
|
+
if (!file) {
|
|
179
|
+
console.error("Usage: kumiki fix <file> [--apply] [<code>]");
|
|
180
|
+
process.exit(2);
|
|
181
|
+
}
|
|
182
|
+
const apply = argv.includes("--apply");
|
|
183
|
+
const code = argv.find((a, i) => i > 3 && a !== "--apply");
|
|
184
|
+
fixCmd(resolve(process.cwd(), file), apply, code);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
default: usage();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function buildRuntimeBundle() {
|
|
191
|
+
return readFileSync(require.resolve("@kumikijs/runtime/bundle"), "utf8");
|
|
192
|
+
}
|
|
193
|
+
function buildHtml() {
|
|
194
|
+
return `<!doctype html>
|
|
195
|
+
<html lang="en">
|
|
196
|
+
<head>
|
|
197
|
+
<meta charset="utf-8" />
|
|
198
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
199
|
+
<title>Kumiki App</title>
|
|
200
|
+
<style>
|
|
201
|
+
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; background: #fafafa; color: #1a1a1a; }
|
|
202
|
+
button { padding: 6px 12px; font-size: 16px; cursor: pointer; }
|
|
203
|
+
h1 { margin: 0 0 12px; }
|
|
204
|
+
</style>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
<base href="/">
|
|
208
|
+
<div id="root"></div>
|
|
209
|
+
<script type="module" src="/app.js"><\/script>
|
|
210
|
+
</body>
|
|
211
|
+
</html>
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
main(process.argv).catch((e) => {
|
|
215
|
+
console.error(String(e));
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|
|
218
|
+
//#endregion
|
|
219
|
+
export {};
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
import { appendFileSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { check, compile, lex, parse } from "@kumikijs/compiler";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { nodeRuntimeBundleReader } from "@kumikijs/compiler/node";
|
|
7
|
+
import { runScenario, smoke } from "@kumikijs/runtime";
|
|
8
|
+
import { JSDOM } from "jsdom";
|
|
9
|
+
//#region src/store.ts
|
|
10
|
+
const LAYER_OF = {
|
|
11
|
+
TypeDef: "type",
|
|
12
|
+
SlotDef: "slot",
|
|
13
|
+
EffectDef: "effect",
|
|
14
|
+
ReducerDef: "reducer",
|
|
15
|
+
TileDef: "tile",
|
|
16
|
+
FnDef: "fn",
|
|
17
|
+
AppDef: "app",
|
|
18
|
+
ThemeDef: "theme"
|
|
19
|
+
};
|
|
20
|
+
function load(path) {
|
|
21
|
+
const source = readFileSync(path, "utf8");
|
|
22
|
+
const lines = source.split(/\r?\n/);
|
|
23
|
+
const tokens = lex(source);
|
|
24
|
+
const program = parse(tokens);
|
|
25
|
+
const defs = buildEntries(program, lines, tokens);
|
|
26
|
+
const byQName = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const e of defs) byQName.set(`${e.layer}.${e.name}`, e);
|
|
28
|
+
return {
|
|
29
|
+
source,
|
|
30
|
+
lines,
|
|
31
|
+
program,
|
|
32
|
+
defs,
|
|
33
|
+
byQName
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function buildEntries(program, lines, tokens) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let i = 0; i < program.defs.length; i++) {
|
|
39
|
+
const d = program.defs[i];
|
|
40
|
+
const layer = LAYER_OF[d.kind] ?? "?";
|
|
41
|
+
const name = "name" in d ? d.name : "_";
|
|
42
|
+
const start = d.pos?.line ?? 1;
|
|
43
|
+
const next = program.defs[i + 1];
|
|
44
|
+
const nextStart = next && next.pos?.line;
|
|
45
|
+
const endLine = nextStart ? nextStart - 1 : lines.length;
|
|
46
|
+
out.push({
|
|
47
|
+
layer,
|
|
48
|
+
name,
|
|
49
|
+
def: d,
|
|
50
|
+
range: {
|
|
51
|
+
startLine: start,
|
|
52
|
+
endLine
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
for (const e of out) {
|
|
57
|
+
let end = e.range.endLine;
|
|
58
|
+
while (end > e.range.startLine) {
|
|
59
|
+
const line = lines[end - 1] ?? "";
|
|
60
|
+
if (line.trim() === "" || line.trim().startsWith("#")) end--;
|
|
61
|
+
else break;
|
|
62
|
+
}
|
|
63
|
+
e.range.endLine = end;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
function viewDef(store, qname) {
|
|
68
|
+
const e = store.byQName.get(qname);
|
|
69
|
+
if (!e) return null;
|
|
70
|
+
return store.lines.slice(e.range.startLine - 1, e.range.endLine).join("\n");
|
|
71
|
+
}
|
|
72
|
+
function viewWithDeps(store, qname) {
|
|
73
|
+
const seen = /* @__PURE__ */ new Set();
|
|
74
|
+
const order = [];
|
|
75
|
+
const visit = (q) => {
|
|
76
|
+
if (seen.has(q)) return;
|
|
77
|
+
seen.add(q);
|
|
78
|
+
const refs = directDeps(store, q);
|
|
79
|
+
for (const r of refs) visit(r);
|
|
80
|
+
order.push(q);
|
|
81
|
+
};
|
|
82
|
+
visit(qname);
|
|
83
|
+
return order.map((q) => viewDef(store, q)).filter((s) => s !== null).join("\n\n");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Return qnames that the definition at `qname` references. The match is
|
|
87
|
+
* textual (identifier token in the source range) and intentionally over-
|
|
88
|
+
* inclusive: any identifier that names another definition is considered a
|
|
89
|
+
* dependency.
|
|
90
|
+
*/
|
|
91
|
+
function directDeps(store, qname) {
|
|
92
|
+
const e = store.byQName.get(qname);
|
|
93
|
+
if (!e) return [];
|
|
94
|
+
const body = store.lines.slice(e.range.startLine - 1, e.range.endLine).join("\n");
|
|
95
|
+
const refs = /* @__PURE__ */ new Set();
|
|
96
|
+
const idents = body.matchAll(/[a-zA-Z_][a-zA-Z0-9_-]*/g);
|
|
97
|
+
for (const m of idents) {
|
|
98
|
+
const tok = m[0];
|
|
99
|
+
if (!tok || tok === e.name) continue;
|
|
100
|
+
for (const other of store.defs) {
|
|
101
|
+
if (other === e) continue;
|
|
102
|
+
if (other.name === tok) refs.add(`${other.layer}.${other.name}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return Array.from(refs).sort();
|
|
106
|
+
}
|
|
107
|
+
function findReferences(store, targetQname) {
|
|
108
|
+
const target = store.byQName.get(targetQname);
|
|
109
|
+
if (!target) return [];
|
|
110
|
+
const out = [];
|
|
111
|
+
const targetName = target.name;
|
|
112
|
+
for (const e of store.defs) {
|
|
113
|
+
if (e === target) continue;
|
|
114
|
+
for (let ln = e.range.startLine; ln <= e.range.endLine; ln++) {
|
|
115
|
+
const cleaned = (store.lines[ln - 1] ?? "").replace(/"(?:[^"\\]|\\.)*"/g, "\"\"").replace(/#.*$/, "");
|
|
116
|
+
if (new RegExp(`(^|[^a-zA-Z0-9_-])${escapeRegExp$1(targetName)}(?![a-zA-Z0-9_-])`).test(cleaned)) out.push({
|
|
117
|
+
qname: `${e.layer}.${e.name}`,
|
|
118
|
+
layer: e.layer,
|
|
119
|
+
name: e.name,
|
|
120
|
+
line: ln
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
function escapeRegExp$1(s) {
|
|
127
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
128
|
+
}
|
|
129
|
+
function listDefs(store, layer) {
|
|
130
|
+
if (layer) return store.defs.filter((e) => e.layer === layer);
|
|
131
|
+
return store.defs;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/fix.ts
|
|
135
|
+
function levenshtein(a, b) {
|
|
136
|
+
const m = a.length;
|
|
137
|
+
const n = b.length;
|
|
138
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
139
|
+
for (let i = 1; i <= m; i++) {
|
|
140
|
+
const curr = [i, ...new Array(n).fill(0)];
|
|
141
|
+
for (let j = 1; j <= n; j++) {
|
|
142
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
143
|
+
curr[j] = Math.min((prev[j] ?? 0) + 1, (curr[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
144
|
+
}
|
|
145
|
+
prev = curr;
|
|
146
|
+
}
|
|
147
|
+
return prev[n] ?? 0;
|
|
148
|
+
}
|
|
149
|
+
function escapeRegex(s) {
|
|
150
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
151
|
+
}
|
|
152
|
+
function suggestName(store, missing) {
|
|
153
|
+
const all = listDefs(store).map((e) => e.name);
|
|
154
|
+
let best = null;
|
|
155
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
156
|
+
for (const cand of all) {
|
|
157
|
+
const d = levenshtein(missing, cand);
|
|
158
|
+
if (d < bestScore) {
|
|
159
|
+
bestScore = d;
|
|
160
|
+
best = cand;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (best === null) return null;
|
|
164
|
+
if (bestScore <= 2 || bestScore <= Math.ceil(missing.length * .25)) return best;
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function planFixes(store, errors) {
|
|
168
|
+
const patches = [];
|
|
169
|
+
for (const err of errors) {
|
|
170
|
+
if (err.code === "E0103" || err.code === "E0105" || err.code === "E0102" || err.code === "E0104") {
|
|
171
|
+
const match = /"([^"]+)"/.exec(err.message);
|
|
172
|
+
if (!match) continue;
|
|
173
|
+
const missing = match[1];
|
|
174
|
+
const suggested = suggestName(store, missing);
|
|
175
|
+
if (!suggested) continue;
|
|
176
|
+
patches.push({
|
|
177
|
+
code: err.code,
|
|
178
|
+
message: err.message,
|
|
179
|
+
description: `replace "${missing}" with "${suggested}" at ${err.pos.line}:${err.pos.col}`,
|
|
180
|
+
apply: (text) => {
|
|
181
|
+
const lines = text.split(/\r?\n/);
|
|
182
|
+
const idx = err.pos.line - 1;
|
|
183
|
+
const line = lines[idx] ?? "";
|
|
184
|
+
const re = new RegExp(`\\b${escapeRegex(missing)}\\b`);
|
|
185
|
+
lines[idx] = line.replace(re, suggested);
|
|
186
|
+
return lines.join("\n");
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (err.code === "E0001") patches.push({
|
|
191
|
+
code: err.code,
|
|
192
|
+
message: err.message,
|
|
193
|
+
description: `add "/404" -> NotFound to app.routes (you must define a NotFound tile)`,
|
|
194
|
+
apply: (text) => {
|
|
195
|
+
return `\ntile NotFound = page(heading("404"))\n` + text.replace(/(routes\s*=\s*\{)([^}]*)(\})/, (_m, open, body, close) => {
|
|
196
|
+
if (body.includes("\"/404\"")) return `${open}${body}${close}`;
|
|
197
|
+
const trimmed = body.trimEnd();
|
|
198
|
+
return `${open}${body}${trimmed.endsWith(",") || trimmed.endsWith("{") ? "" : ","} "/404" -> NotFound ${close}`;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return patches;
|
|
204
|
+
}
|
|
205
|
+
function fixCmd(path, apply, onlyCode) {
|
|
206
|
+
const store = load(path);
|
|
207
|
+
const errors = check(store.program);
|
|
208
|
+
if (errors.length === 0) {
|
|
209
|
+
console.log("no errors");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let patches = planFixes(store, errors);
|
|
213
|
+
if (onlyCode) patches = patches.filter((p) => p.code === onlyCode);
|
|
214
|
+
if (patches.length === 0) {
|
|
215
|
+
console.log("(no auto-patches available)");
|
|
216
|
+
for (const e of errors) console.error(`${e.code} ${e.message}`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (!apply) {
|
|
220
|
+
for (const p of patches) {
|
|
221
|
+
console.log(`${p.code} ${p.message}`);
|
|
222
|
+
console.log(` fix: ${p.description}`);
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let text = readFileSync(path, "utf8");
|
|
227
|
+
for (const p of patches) text = p.apply(text);
|
|
228
|
+
writeFileSync(path, text);
|
|
229
|
+
try {
|
|
230
|
+
const after = check(parse(lex(text)));
|
|
231
|
+
if (after.length === 0) console.log(`applied ${patches.length} fix(es) — file now clean`);
|
|
232
|
+
else console.log(`applied ${patches.length} fix(es) — ${after.length} error(s) remain`);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
console.error(`fixes broke the file: ${String(e)}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/mutate.ts
|
|
239
|
+
const ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
240
|
+
function ulid() {
|
|
241
|
+
let s = "op_";
|
|
242
|
+
for (let i = 0; i < 16; i++) s += ULID_ALPHABET[Math.floor(Math.random() * 32)];
|
|
243
|
+
return s;
|
|
244
|
+
}
|
|
245
|
+
function logOp(path, op) {
|
|
246
|
+
const enriched = {
|
|
247
|
+
...op,
|
|
248
|
+
opId: ulid(),
|
|
249
|
+
ts: Date.now()
|
|
250
|
+
};
|
|
251
|
+
appendFileSync(`${path}.kumiki-ops.jsonl`, `${JSON.stringify(enriched)}\n`);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Validate that after the write the file still parses and typechecks.
|
|
255
|
+
* Returns the error list (empty array = success).
|
|
256
|
+
*/
|
|
257
|
+
function validate(path) {
|
|
258
|
+
try {
|
|
259
|
+
const errors = check(parse(lex(readFileSync(path, "utf8"))));
|
|
260
|
+
if (errors.length > 0) return {
|
|
261
|
+
ok: false,
|
|
262
|
+
message: `Validation failed: ${errors.slice(0, 3).map((e) => `${e.code} ${e.message}`).join("; ")}`
|
|
263
|
+
};
|
|
264
|
+
return { ok: true };
|
|
265
|
+
} catch (e) {
|
|
266
|
+
return {
|
|
267
|
+
ok: false,
|
|
268
|
+
message: `Parse/lex failed: ${String(e)}`
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
function addDef(path, layer, name, body) {
|
|
273
|
+
const src = readFileSync(path, "utf8");
|
|
274
|
+
const inserted = assemble(layer, name, body);
|
|
275
|
+
writeFileSync(path, src.endsWith("\n") ? `${src}\n${inserted}\n` : `${src}\n\n${inserted}\n`);
|
|
276
|
+
const v = validate(path);
|
|
277
|
+
if (!v.ok) {
|
|
278
|
+
writeFileSync(path, src);
|
|
279
|
+
throw new Error(`add rejected: ${v.message}`);
|
|
280
|
+
}
|
|
281
|
+
logOp(path, {
|
|
282
|
+
op: "add",
|
|
283
|
+
layer,
|
|
284
|
+
name,
|
|
285
|
+
body
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function replaceDef(path, qname, body) {
|
|
289
|
+
const store = load(path);
|
|
290
|
+
const entry = store.byQName.get(qname);
|
|
291
|
+
if (!entry) throw new Error(`Definition "${qname}" not found`);
|
|
292
|
+
const before = store.lines.slice(0, entry.range.startLine - 1);
|
|
293
|
+
const after = store.lines.slice(entry.range.endLine);
|
|
294
|
+
const inserted = assemble(entry.layer, entry.name, body).split(/\r?\n/);
|
|
295
|
+
const next = [
|
|
296
|
+
...before,
|
|
297
|
+
...inserted,
|
|
298
|
+
...after
|
|
299
|
+
].join("\n");
|
|
300
|
+
const original = store.source;
|
|
301
|
+
writeFileSync(path, next);
|
|
302
|
+
const v = validate(path);
|
|
303
|
+
if (!v.ok) {
|
|
304
|
+
writeFileSync(path, original);
|
|
305
|
+
throw new Error(`replace rejected: ${v.message}`);
|
|
306
|
+
}
|
|
307
|
+
logOp(path, {
|
|
308
|
+
op: "replace",
|
|
309
|
+
layer: entry.layer,
|
|
310
|
+
name: entry.name,
|
|
311
|
+
body
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function removeDef(path, qname, cascade) {
|
|
315
|
+
const store = load(path);
|
|
316
|
+
const entry = store.byQName.get(qname);
|
|
317
|
+
if (!entry) throw new Error(`Definition "${qname}" not found`);
|
|
318
|
+
const refs = findReferences(store, qname);
|
|
319
|
+
if (refs.length > 0 && !cascade) {
|
|
320
|
+
const summary = refs.slice(0, 5).map((r) => `${r.qname}:${r.line}`).join(", ");
|
|
321
|
+
throw new Error(`Cannot remove ${qname}: ${refs.length} references (${summary}). Re-run with --cascade.`);
|
|
322
|
+
}
|
|
323
|
+
const toRemove = new Set([qname]);
|
|
324
|
+
if (cascade) {
|
|
325
|
+
let frontier = [qname];
|
|
326
|
+
while (frontier.length > 0) {
|
|
327
|
+
const next = [];
|
|
328
|
+
for (const q of frontier) for (const r of findReferences(store, q)) if (!toRemove.has(r.qname)) {
|
|
329
|
+
toRemove.add(r.qname);
|
|
330
|
+
next.push(r.qname);
|
|
331
|
+
}
|
|
332
|
+
frontier = next;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const removalEntries = [...toRemove].map((q) => store.byQName.get(q)).filter((e) => !!e).sort((a, b) => b.range.startLine - a.range.startLine);
|
|
336
|
+
let lines = store.lines.slice();
|
|
337
|
+
for (const e of removalEntries) lines = [...lines.slice(0, e.range.startLine - 1), ...lines.slice(e.range.endLine)];
|
|
338
|
+
const next = lines.join("\n");
|
|
339
|
+
const original = store.source;
|
|
340
|
+
writeFileSync(path, next);
|
|
341
|
+
const v = validate(path);
|
|
342
|
+
if (!v.ok) {
|
|
343
|
+
writeFileSync(path, original);
|
|
344
|
+
throw new Error(`remove rejected: ${v.message}`);
|
|
345
|
+
}
|
|
346
|
+
logOp(path, {
|
|
347
|
+
op: "remove",
|
|
348
|
+
layer: entry.layer,
|
|
349
|
+
name: entry.name,
|
|
350
|
+
cascade
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
function renameDef(path, qname, newName) {
|
|
354
|
+
const store = load(path);
|
|
355
|
+
const entry = store.byQName.get(qname);
|
|
356
|
+
if (!entry) throw new Error(`Definition "${qname}" not found`);
|
|
357
|
+
const old = entry.name;
|
|
358
|
+
const re = new RegExp(`\\b${escapeRegExp(old)}\\b`, "g");
|
|
359
|
+
const next = store.lines.map((line) => {
|
|
360
|
+
let result = "";
|
|
361
|
+
let i = 0;
|
|
362
|
+
while (i < line.length) {
|
|
363
|
+
const ch = line[i];
|
|
364
|
+
if (ch === "#") {
|
|
365
|
+
result += line.slice(i);
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
if (ch === "\"") {
|
|
369
|
+
const start = i;
|
|
370
|
+
i++;
|
|
371
|
+
while (i < line.length && line[i] !== "\"") {
|
|
372
|
+
if (line[i] === "\\") i++;
|
|
373
|
+
i++;
|
|
374
|
+
}
|
|
375
|
+
i++;
|
|
376
|
+
result += line.slice(start, i);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
result += ch;
|
|
380
|
+
i++;
|
|
381
|
+
}
|
|
382
|
+
const codePart = result;
|
|
383
|
+
const tail = line.slice(codePart.length);
|
|
384
|
+
return codePart.replace(re, newName) + tail;
|
|
385
|
+
}).join("\n");
|
|
386
|
+
const original = store.source;
|
|
387
|
+
writeFileSync(path, next);
|
|
388
|
+
const v = validate(path);
|
|
389
|
+
if (!v.ok) {
|
|
390
|
+
writeFileSync(path, original);
|
|
391
|
+
throw new Error(`rename rejected: ${v.message}`);
|
|
392
|
+
}
|
|
393
|
+
logOp(path, {
|
|
394
|
+
op: "rename",
|
|
395
|
+
layer: entry.layer,
|
|
396
|
+
name: old,
|
|
397
|
+
newName
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
function escapeRegExp(s) {
|
|
401
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
402
|
+
}
|
|
403
|
+
function assemble(layer, name, body) {
|
|
404
|
+
switch (layer) {
|
|
405
|
+
case "type": return `type ${name} = ${body}`;
|
|
406
|
+
case "slot": return `slot ${name} : ${body}`;
|
|
407
|
+
case "effect": return `effect ${name} ${body}`;
|
|
408
|
+
case "reducer": return `reducer ${name} ${body}`;
|
|
409
|
+
case "tile": return `tile ${name} = ${body}`;
|
|
410
|
+
case "fn": return `fn ${name}${body.startsWith("(") ? "" : " "}${body}`;
|
|
411
|
+
case "app": return `app ${name}\n${body}`;
|
|
412
|
+
case "theme": return `theme ${name} = ${body}`;
|
|
413
|
+
default: throw new Error(`Unknown layer "${layer}"`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/smoke.ts
|
|
418
|
+
let domReady = false;
|
|
419
|
+
function ensureDom() {
|
|
420
|
+
if (domReady) return;
|
|
421
|
+
const dom = new JSDOM("<!doctype html><html><body></body></html>", { url: "http://localhost/" });
|
|
422
|
+
const g = globalThis;
|
|
423
|
+
const w = dom.window;
|
|
424
|
+
for (const key of [
|
|
425
|
+
"window",
|
|
426
|
+
"document",
|
|
427
|
+
"navigator",
|
|
428
|
+
"location",
|
|
429
|
+
"history",
|
|
430
|
+
"localStorage",
|
|
431
|
+
"sessionStorage",
|
|
432
|
+
"HTMLElement",
|
|
433
|
+
"HTMLInputElement",
|
|
434
|
+
"HTMLSelectElement",
|
|
435
|
+
"HTMLTextAreaElement",
|
|
436
|
+
"Element",
|
|
437
|
+
"Node",
|
|
438
|
+
"Event",
|
|
439
|
+
"MouseEvent",
|
|
440
|
+
"CustomEvent",
|
|
441
|
+
"KeyboardEvent",
|
|
442
|
+
"CSS",
|
|
443
|
+
"getComputedStyle"
|
|
444
|
+
]) {
|
|
445
|
+
if (w[key] === void 0) continue;
|
|
446
|
+
try {
|
|
447
|
+
g[key] = w[key];
|
|
448
|
+
} catch {
|
|
449
|
+
Object.defineProperty(g, key, {
|
|
450
|
+
value: w[key],
|
|
451
|
+
configurable: true,
|
|
452
|
+
writable: true
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
domReady = true;
|
|
457
|
+
}
|
|
458
|
+
async function loadApp(source) {
|
|
459
|
+
const result = compile(source, {
|
|
460
|
+
runtimeSpecifier: "ignored",
|
|
461
|
+
bundle: true,
|
|
462
|
+
readRuntimeBundle: nodeRuntimeBundleReader
|
|
463
|
+
});
|
|
464
|
+
if (result.kind !== "ok") throw new Error(`compile failed:\n${result.errors.map((e) => `${e.code} ${e.message}`).join("\n")}`);
|
|
465
|
+
const patched = result.js.replace(/mount\(App, document\.getElementById\("root"\)\);?/, "");
|
|
466
|
+
const file = join(mkdtempSync(join(tmpdir(), "kumiki-smoke-")), "app.mjs");
|
|
467
|
+
writeFileSync(file, patched);
|
|
468
|
+
await import(pathToFileURL(file).href);
|
|
469
|
+
const app = globalThis.__kumikiApp;
|
|
470
|
+
if (!app) throw new Error("compiled module did not expose __kumikiApp");
|
|
471
|
+
return app;
|
|
472
|
+
}
|
|
473
|
+
/** Compile + mount + exercise a Kumiki source string; return the smoke report. */
|
|
474
|
+
async function smokeSource(source) {
|
|
475
|
+
ensureDom();
|
|
476
|
+
const app = await loadApp(source);
|
|
477
|
+
const doc = globalThis.document;
|
|
478
|
+
const root = doc.createElement("div");
|
|
479
|
+
doc.body.appendChild(root);
|
|
480
|
+
try {
|
|
481
|
+
return await smoke(app, root, { settleMs: 20 });
|
|
482
|
+
} finally {
|
|
483
|
+
root.remove();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async function smokeFile(path) {
|
|
487
|
+
return smokeSource(readFileSync(path, "utf8"));
|
|
488
|
+
}
|
|
489
|
+
/** CLI entry: print a human-readable report and exit non-zero on failure. */
|
|
490
|
+
async function smokeCmd(path) {
|
|
491
|
+
const report = await smokeFile(path);
|
|
492
|
+
if (report.ok) {
|
|
493
|
+
console.log(`ok — mounted, rendered, ${report.interactions} interaction(s), no runtime errors`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
console.error(`runtime smoke failed (mounted=${report.mounted}, rendered=${report.rendered}, interactions=${report.interactions}):`);
|
|
497
|
+
for (const i of report.issues) console.error(` [${i.phase}] ${i.message}${i.trigger ? ` (on ${i.trigger})` : ""}`);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
/** Compile + mount + drive a scenario; return the structured trace. */
|
|
501
|
+
async function runScenarioSource(source, scenario) {
|
|
502
|
+
ensureDom();
|
|
503
|
+
const app = await loadApp(source);
|
|
504
|
+
const doc = globalThis.document;
|
|
505
|
+
const root = doc.createElement("div");
|
|
506
|
+
doc.body.appendChild(root);
|
|
507
|
+
try {
|
|
508
|
+
return await runScenario(app, root, scenario, { settleMs: 20 });
|
|
509
|
+
} finally {
|
|
510
|
+
root.remove();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/** CLI entry: run a scenario JSON file against a .kumiki file; print the trace. */
|
|
514
|
+
async function runCmd(kumikiPath, scenarioPath) {
|
|
515
|
+
const scenario = JSON.parse(readFileSync(scenarioPath, "utf8"));
|
|
516
|
+
const report = await runScenarioSource(readFileSync(kumikiPath, "utf8"), scenario);
|
|
517
|
+
for (let i = 0; i < report.steps.length; i++) {
|
|
518
|
+
const s = report.steps[i];
|
|
519
|
+
if (!s) continue;
|
|
520
|
+
const head = `step ${i}${s.label ? ` (${s.label})` : ""}${s.action ? `: ${s.action}` : ""}`;
|
|
521
|
+
const status = s.errors.length === 0 && s.failures.length === 0 ? "ok" : "FAIL";
|
|
522
|
+
console.log(`[${status}] ${head}`);
|
|
523
|
+
for (const e of s.errors) console.log(` error: ${e}`);
|
|
524
|
+
for (const f of s.failures) console.log(` assert: ${f}`);
|
|
525
|
+
}
|
|
526
|
+
console.log(report.ok ? "\nscenario passed" : "\nscenario FAILED");
|
|
527
|
+
if (!report.ok) process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
//#endregion
|
|
530
|
+
export { viewWithDeps as _, smokeSource as a, renameDef as c, planFixes as d, directDeps as f, viewDef as g, load as h, smokeFile as i, replaceDef as l, listDefs as m, runScenarioSource as n, addDef as o, findReferences as p, smokeCmd as r, removeDef as s, runCmd as t, fixCmd as u };
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kumikijs/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Kumiki CLI — build, check, and AI-edit (list/view/add/replace/remove/rename/fix) Kumiki sources.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"author": "kage1020",
|
|
8
|
+
"homepage": "https://github.com/kage1020/Kumiki/tree/main/packages/cli#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/kage1020/Kumiki.git",
|
|
12
|
+
"directory": "packages/cli"
|
|
13
|
+
},
|
|
14
|
+
"bugs": "https://github.com/kage1020/Kumiki/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"kumiki",
|
|
17
|
+
"ai-first",
|
|
18
|
+
"cli",
|
|
19
|
+
"compiler",
|
|
20
|
+
"codegen"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"kumiki": "./dist/kumiki.js"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"default": "./src/index.ts"
|
|
32
|
+
},
|
|
33
|
+
"./kumiki": "./src/kumiki.ts"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"provenance": true,
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"import": "./dist/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./kumiki": "./dist/kumiki.js"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsdown",
|
|
51
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"lint": "biome check src test",
|
|
54
|
+
"kumiki": "tsx src/kumiki.ts"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@kumikijs/compiler": "workspace:*",
|
|
58
|
+
"@kumikijs/runtime": "workspace:*",
|
|
59
|
+
"jsdom": "catalog:"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/jsdom": "^28.0.3",
|
|
63
|
+
"@types/node": "catalog:",
|
|
64
|
+
"tsx": "catalog:",
|
|
65
|
+
"typescript": "catalog:",
|
|
66
|
+
"vitest": "catalog:"
|
|
67
|
+
}
|
|
68
|
+
}
|