arckode-ui 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/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/analyzer-Ctnj3WTI.js +424 -0
- package/dist/cli.js +581 -0
- package/dist/index.js +507 -0
- package/dist/router-DhUDyb8s.js +129 -0
- package/dist/vite.js +366 -0
- package/package.json +67 -0
- package/skills/analyzer/SKILL.md +128 -0
- package/skills/cli/SKILL.md +109 -0
- package/skills/compiler/SKILL.md +122 -0
- package/skills/components/SKILL.md +233 -0
- package/skills/runtime/SKILL.md +145 -0
- package/skills/testing/SKILL.md +169 -0
package/dist/vite.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
var y = Object.defineProperty;
|
|
2
|
+
var C = (r, t, e) => t in r ? y(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
|
|
3
|
+
var x = (r, t, e) => C(r, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import { transformWithEsbuild as b } from "vite";
|
|
5
|
+
import { a as N, f as v } from "./analyzer-Ctnj3WTI.js";
|
|
6
|
+
class h extends Error {
|
|
7
|
+
constructor(e, n, s) {
|
|
8
|
+
super(n);
|
|
9
|
+
x(this, "code");
|
|
10
|
+
x(this, "line");
|
|
11
|
+
this.name = "ArkParseError", this.code = e, this.line = s, Object.setPrototypeOf(this, h.prototype);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const k = /<(template|script|style)((?:\s+[^>]*)??)>([\s\S]*?)<\/\1>/g;
|
|
15
|
+
function O(r, t) {
|
|
16
|
+
let e = 1;
|
|
17
|
+
for (let n = 0; n < t; n++)
|
|
18
|
+
r[n] === `
|
|
19
|
+
` && e++;
|
|
20
|
+
return e;
|
|
21
|
+
}
|
|
22
|
+
function T(r, t) {
|
|
23
|
+
const n = new RegExp(`${t}=["']([^"']*)["']`).exec(r);
|
|
24
|
+
return n != null ? n[1] ?? null : null;
|
|
25
|
+
}
|
|
26
|
+
function R(r, t) {
|
|
27
|
+
return new RegExp(`(?:^|\\s)${t}(?:\\s|$|=)`).test(r);
|
|
28
|
+
}
|
|
29
|
+
function L(r) {
|
|
30
|
+
const t = [];
|
|
31
|
+
let e;
|
|
32
|
+
for (k.lastIndex = 0; (e = k.exec(r)) !== null; ) {
|
|
33
|
+
const i = e[1], u = e[2] ?? "", d = e[3] ?? "", m = O(r, e.index);
|
|
34
|
+
i !== void 0 && t.push({ tag: i, attrs: u, content: d, openLine: m });
|
|
35
|
+
}
|
|
36
|
+
const n = t.filter((i) => i.tag === "template"), s = t.filter((i) => i.tag === "script"), c = t.filter((i) => i.tag === "style");
|
|
37
|
+
if (n.length > 1 || s.length > 1) {
|
|
38
|
+
const i = n.length > 1 ? n[1] : s[1];
|
|
39
|
+
throw new h(
|
|
40
|
+
"DUPLICATE_SECTION",
|
|
41
|
+
`Duplicate <${i.tag}> section`,
|
|
42
|
+
i.openLine
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (n.length === 0)
|
|
46
|
+
throw new h(
|
|
47
|
+
"MISSING_TEMPLATE",
|
|
48
|
+
"Missing <template> section",
|
|
49
|
+
1
|
|
50
|
+
);
|
|
51
|
+
if (s.length === 0)
|
|
52
|
+
throw new h(
|
|
53
|
+
"MISSING_SCRIPT",
|
|
54
|
+
"Missing <script> section",
|
|
55
|
+
1
|
|
56
|
+
);
|
|
57
|
+
const p = n[0], o = s[0];
|
|
58
|
+
if (o.openLine < p.openLine)
|
|
59
|
+
throw new h(
|
|
60
|
+
"WRONG_ORDER",
|
|
61
|
+
"<script> must appear after <template>",
|
|
62
|
+
o.openLine
|
|
63
|
+
);
|
|
64
|
+
const l = T(o.attrs, "lang");
|
|
65
|
+
if (l !== "ts")
|
|
66
|
+
throw new h(
|
|
67
|
+
"MISSING_LANG_TS",
|
|
68
|
+
'<script> must have lang="ts"',
|
|
69
|
+
o.openLine
|
|
70
|
+
);
|
|
71
|
+
let f;
|
|
72
|
+
if (c.length > 0) {
|
|
73
|
+
const i = c[0], u = R(i.attrs, "scoped");
|
|
74
|
+
f = { content: i.content, scoped: u };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
template: {
|
|
78
|
+
content: p.content,
|
|
79
|
+
start: p.openLine
|
|
80
|
+
},
|
|
81
|
+
script: {
|
|
82
|
+
content: o.content,
|
|
83
|
+
lang: l
|
|
84
|
+
},
|
|
85
|
+
...f !== void 0 ? { style: f } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const E = /(?:(@[\w.]+|:[a-zA-Z][\w-]*|v-else-if|v-if|v-for|v-show|v-else|[\w-]+)(?:="([^"]*)")?)/g;
|
|
89
|
+
function j(r) {
|
|
90
|
+
const t = [];
|
|
91
|
+
E.lastIndex = 0;
|
|
92
|
+
let e;
|
|
93
|
+
for (; (e = E.exec(r)) !== null; )
|
|
94
|
+
t.push({ name: e[1] ?? "", value: e[2] ?? "", raw: e[0] });
|
|
95
|
+
return t;
|
|
96
|
+
}
|
|
97
|
+
const I = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)((?:"[^"]*"|[^>])*)(\/?)>|([^<]+)/g;
|
|
98
|
+
function W(r) {
|
|
99
|
+
const t = [], e = [];
|
|
100
|
+
I.lastIndex = 0;
|
|
101
|
+
let n;
|
|
102
|
+
const s = /* @__PURE__ */ new Set([
|
|
103
|
+
"area",
|
|
104
|
+
"base",
|
|
105
|
+
"br",
|
|
106
|
+
"col",
|
|
107
|
+
"embed",
|
|
108
|
+
"hr",
|
|
109
|
+
"img",
|
|
110
|
+
"input",
|
|
111
|
+
"link",
|
|
112
|
+
"meta",
|
|
113
|
+
"param",
|
|
114
|
+
"source",
|
|
115
|
+
"track",
|
|
116
|
+
"wbr"
|
|
117
|
+
]);
|
|
118
|
+
for (; (n = I.exec(r)) !== null; ) {
|
|
119
|
+
const c = n[1] === "/", p = n[2], o = (n[3] ?? "").trim(), l = o.endsWith("/"), f = l ? o.slice(0, -1).trim() : o, i = l || n[4] === "/" || p !== void 0 && s.has(p.toLowerCase()), u = n[5];
|
|
120
|
+
if (u !== void 0) {
|
|
121
|
+
const d = u;
|
|
122
|
+
d.length > 0 && (e.length > 0 ? e[e.length - 1].children : t).push({ kind: "text", text: d });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (p !== void 0)
|
|
126
|
+
if (c) {
|
|
127
|
+
if (e.length > 0) {
|
|
128
|
+
const d = e.pop();
|
|
129
|
+
(e.length > 0 ? e[e.length - 1].children : t).push({ kind: "element", el: d });
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
const d = {
|
|
133
|
+
tag: p,
|
|
134
|
+
attrs: j(f),
|
|
135
|
+
children: [],
|
|
136
|
+
selfClosing: i
|
|
137
|
+
};
|
|
138
|
+
i ? (e.length > 0 ? e[e.length - 1].children : t).push({ kind: "element", el: d }) : e.push(d);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (; e.length > 0; ) {
|
|
142
|
+
const c = e.pop();
|
|
143
|
+
(e.length > 0 ? e[e.length - 1].children : t).push({ kind: "element", el: c });
|
|
144
|
+
}
|
|
145
|
+
return t;
|
|
146
|
+
}
|
|
147
|
+
const $ = /\{\{\s*([\s\S]+?)\s*\}\}/g;
|
|
148
|
+
function z(r) {
|
|
149
|
+
return $.lastIndex = 0, $.test(r);
|
|
150
|
+
}
|
|
151
|
+
function M(r) {
|
|
152
|
+
$.lastIndex = 0;
|
|
153
|
+
const t = [];
|
|
154
|
+
let e = 0, n;
|
|
155
|
+
for ($.lastIndex = 0; (n = $.exec(r)) !== null; )
|
|
156
|
+
n.index > e && t.push({ kind: "text", value: r.slice(e, n.index) }), t.push({ kind: "expr", value: n[1] }), e = n.index + n[0].length;
|
|
157
|
+
return e < r.length && t.push({ kind: "text", value: r.slice(e) }), t.length === 0 ? JSON.stringify(r) : t.length === 1 && t[0].kind === "expr" ? t[0].value : "`" + t.map((c) => c.kind === "text" ? c.value : `\${${c.value}}`).join("") + "`";
|
|
158
|
+
}
|
|
159
|
+
const D = {
|
|
160
|
+
enter: "Enter",
|
|
161
|
+
escape: "Escape",
|
|
162
|
+
esc: "Escape",
|
|
163
|
+
tab: "Tab",
|
|
164
|
+
space: " ",
|
|
165
|
+
up: "ArrowUp",
|
|
166
|
+
down: "ArrowDown",
|
|
167
|
+
left: "ArrowLeft",
|
|
168
|
+
right: "ArrowRight",
|
|
169
|
+
delete: "Delete",
|
|
170
|
+
backspace: "Backspace"
|
|
171
|
+
};
|
|
172
|
+
function J(r, t) {
|
|
173
|
+
const e = r.slice(1), [n, ...s] = e.split("."), p = `on${n.charAt(0).toUpperCase() + n.slice(1)}`;
|
|
174
|
+
if (s.length === 0)
|
|
175
|
+
return `${p}: ${t}`;
|
|
176
|
+
const o = s[0].toLowerCase(), l = D[o] ?? o.charAt(0).toUpperCase() + o.slice(1);
|
|
177
|
+
return `${p}: (e) => e.key === '${l}' && ${t}(e)`;
|
|
178
|
+
}
|
|
179
|
+
function P(r) {
|
|
180
|
+
const { name: t, value: e } = r;
|
|
181
|
+
return t.startsWith("@") ? J(t, e) : t.startsWith(":") ? `${t.slice(1)}: ${e}` : ["v-if", "v-for", "v-else", "v-else-if"].includes(t) ? null : t === "v-show" ? `style: { display: ${e} ? '' : 'none' }` : e === "" ? `${t}: true` : `${t}: ${JSON.stringify(e)}`;
|
|
182
|
+
}
|
|
183
|
+
function V(r) {
|
|
184
|
+
const t = r;
|
|
185
|
+
return z(t) ? `h('span', null, ${M(t)})` : /^\s+$/.test(t) ? "" : JSON.stringify(t);
|
|
186
|
+
}
|
|
187
|
+
function w(r) {
|
|
188
|
+
const t = r.attrs.find((f) => f.name === "v-for");
|
|
189
|
+
if (r.tag === "slot")
|
|
190
|
+
return "(props.__slot_default ? props.__slot_default() : [])";
|
|
191
|
+
const e = r.attrs.map(P).filter((f) => f !== null && f !== ""), n = e.length === 0 ? "null" : `{ ${e.join(", ")} }`, s = A(r.children);
|
|
192
|
+
let l = `h(${[/^[A-Z]/.test(r.tag) ? r.tag : JSON.stringify(r.tag), n, ...s].join(", ")})`;
|
|
193
|
+
return t !== void 0 && (l = G(t.value, l)), l;
|
|
194
|
+
}
|
|
195
|
+
function A(r) {
|
|
196
|
+
const t = [];
|
|
197
|
+
let e = 0;
|
|
198
|
+
for (; e < r.length; ) {
|
|
199
|
+
const n = r[e];
|
|
200
|
+
if (n.kind === "text") {
|
|
201
|
+
const o = V(n.text);
|
|
202
|
+
o !== "" && t.push(o), e++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const s = n.el, c = s.attrs.find((o) => o.name === "v-if");
|
|
206
|
+
if (!c && s.attrs.some((o) => o.name === "v-else" || o.name === "v-else-if")) {
|
|
207
|
+
e++;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (c) {
|
|
211
|
+
let o = `${c.value} ? ${w(s)} : `;
|
|
212
|
+
for (e++; e < r.length; ) {
|
|
213
|
+
const l = r[e];
|
|
214
|
+
if (l.kind === "text" && /^\s+$/.test(l.text)) {
|
|
215
|
+
e++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (l.kind !== "element") break;
|
|
219
|
+
const f = l.el.attrs.find((u) => u.name === "v-else-if"), i = l.el.attrs.some((u) => u.name === "v-else");
|
|
220
|
+
if (f)
|
|
221
|
+
o += `${f.value} ? ${w(l.el)} : `, e++;
|
|
222
|
+
else if (i) {
|
|
223
|
+
o += w(l.el), e++;
|
|
224
|
+
break;
|
|
225
|
+
} else
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
o.endsWith(" : ") && (o += "null"), t.push(o);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
t.push(w(s)), e++;
|
|
232
|
+
}
|
|
233
|
+
return t;
|
|
234
|
+
}
|
|
235
|
+
function G(r, t) {
|
|
236
|
+
const e = /^\s*(?:\(([^)]+)\)|(\w+))\s+in\s+(.+?)\s*$/.exec(r);
|
|
237
|
+
if (!e)
|
|
238
|
+
return `(${r}).map((item) => ${t})`;
|
|
239
|
+
const n = e[1] ?? e[2] ?? "item";
|
|
240
|
+
return `${e[3].trim()}.map((${n}) => ${t})`;
|
|
241
|
+
}
|
|
242
|
+
function F(r) {
|
|
243
|
+
const t = W(r.trim()), e = A(t).filter((s) => s !== "");
|
|
244
|
+
let n;
|
|
245
|
+
return e.length === 0 ? n = "null" : e.length === 1 ? n = e[0] : n = `[${e.join(", ")}]`, `function h(tag, props, ...children) {
|
|
246
|
+
return { tag, props: props ?? {}, children: children.flat().filter(c => c != null) }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default function render(props, state, computed, actions) {
|
|
250
|
+
return ${n}
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
function U(r) {
|
|
255
|
+
let t = 5381;
|
|
256
|
+
for (let n = 0; n < r.length; n++)
|
|
257
|
+
t = (t << 5) + t ^ r.charCodeAt(n), t = t | 0;
|
|
258
|
+
return (t >>> 0).toString(36).padStart(6, "0").slice(-6);
|
|
259
|
+
}
|
|
260
|
+
function Z(r) {
|
|
261
|
+
const t = /export\s+default\s+(defineComponent\s*\([\s\S]*\))\s*;?\s*$/, e = r.trim(), n = t.exec(e);
|
|
262
|
+
return n ? {
|
|
263
|
+
preamble: e.slice(0, n.index).trim(),
|
|
264
|
+
defineComponentExpr: n[1]
|
|
265
|
+
} : {
|
|
266
|
+
preamble: e,
|
|
267
|
+
defineComponentExpr: null
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function K(r, t, e) {
|
|
271
|
+
const s = F(t).replace(/export default function render/, "function render").replace(/function h\(tag, props, \.\.\.children\) \{\n return [^\n]+\n\}\n+/, "").trim(), { preamble: c, defineComponentExpr: p } = Z(r);
|
|
272
|
+
return p ? [
|
|
273
|
+
"import { defineComponent, h } from 'arckode-ui'",
|
|
274
|
+
"",
|
|
275
|
+
c,
|
|
276
|
+
"",
|
|
277
|
+
"// [compiled render function — generated from <template>]",
|
|
278
|
+
s,
|
|
279
|
+
"",
|
|
280
|
+
`// [scope id: ${e}]`,
|
|
281
|
+
`const __scopeId = ${JSON.stringify(e)}`,
|
|
282
|
+
"",
|
|
283
|
+
`const __component = ${p}`,
|
|
284
|
+
"__component.__scopeId = __scopeId",
|
|
285
|
+
"__component.__render = render",
|
|
286
|
+
"",
|
|
287
|
+
"export default __component"
|
|
288
|
+
].filter((o) => o !== void 0).join(`
|
|
289
|
+
`) : [
|
|
290
|
+
"import { defineComponent } from 'arckode-ui'",
|
|
291
|
+
"",
|
|
292
|
+
c,
|
|
293
|
+
"",
|
|
294
|
+
"// [compiled render function — generated from <template>]",
|
|
295
|
+
s,
|
|
296
|
+
"",
|
|
297
|
+
`export default { __render: render, __scopeId: ${JSON.stringify(e)} }`
|
|
298
|
+
].join(`
|
|
299
|
+
`);
|
|
300
|
+
}
|
|
301
|
+
function H(r = {}) {
|
|
302
|
+
let t = "development";
|
|
303
|
+
return {
|
|
304
|
+
name: "arckode-ui",
|
|
305
|
+
enforce: "pre",
|
|
306
|
+
configResolved(e) {
|
|
307
|
+
t = e.mode;
|
|
308
|
+
},
|
|
309
|
+
transform(e, n) {
|
|
310
|
+
var S, _;
|
|
311
|
+
if (!n.endsWith(".ark")) return null;
|
|
312
|
+
let s;
|
|
313
|
+
try {
|
|
314
|
+
s = L(e);
|
|
315
|
+
} catch (a) {
|
|
316
|
+
if (a instanceof h) {
|
|
317
|
+
const g = [
|
|
318
|
+
`[arckode-ui] Parse error in ${n}`,
|
|
319
|
+
` ${a.code} (line ${a.line}): ${a.message}`,
|
|
320
|
+
"",
|
|
321
|
+
" Fix the .ark file structure before continuing."
|
|
322
|
+
].join(`
|
|
323
|
+
`);
|
|
324
|
+
throw new Error(g);
|
|
325
|
+
}
|
|
326
|
+
throw a;
|
|
327
|
+
}
|
|
328
|
+
const c = ((S = r.analyzer) == null ? void 0 : S.ignore) ?? [], p = ((_ = r.analyzer) == null ? void 0 : _.failOnWarnings) ?? !1, l = N(e).filter((a) => !c.includes(a.code)), f = l.filter((a) => a.severity === "error"), i = l.filter((a) => a.severity === "warning"), u = t === "production";
|
|
329
|
+
for (const a of i)
|
|
330
|
+
console.warn(v(a, n, e));
|
|
331
|
+
if (f.length > 0) {
|
|
332
|
+
const a = f.map((g) => v(g, n, e)).join(`
|
|
333
|
+
|
|
334
|
+
`);
|
|
335
|
+
if (u)
|
|
336
|
+
throw new Error(
|
|
337
|
+
`[arckode-ui] ${f.length} error(s) in ${n}:
|
|
338
|
+
|
|
339
|
+
${a}`
|
|
340
|
+
);
|
|
341
|
+
console.warn(`[arckode-ui] Analyzer errors in ${n} (not blocking in dev):
|
|
342
|
+
|
|
343
|
+
${a}`);
|
|
344
|
+
}
|
|
345
|
+
if (p && u && i.length > 0) {
|
|
346
|
+
const a = i.map((g) => v(g, n, e)).join(`
|
|
347
|
+
|
|
348
|
+
`);
|
|
349
|
+
throw new Error(
|
|
350
|
+
`[arckode-ui] ${i.length} warning(s) treated as errors (failOnWarnings=true) in ${n}:
|
|
351
|
+
|
|
352
|
+
${a}`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
const d = U(n), m = K(s.script.content, s.template.content, d);
|
|
356
|
+
return b(m, n + ".ts", {
|
|
357
|
+
loader: "ts",
|
|
358
|
+
target: "esnext"
|
|
359
|
+
}).then((a) => ({ code: a.code, map: a.map }));
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
export {
|
|
364
|
+
H as arkcodeUi,
|
|
365
|
+
U as generateScopeId
|
|
366
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arckode-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Frontend framework con .ark SFCs, signals, file-system router y analyzer estático con sugerencias concretas de fix. Diseñado para máxima predictibilidad de output de IA.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"framework",
|
|
8
|
+
"frontend",
|
|
9
|
+
"signals",
|
|
10
|
+
"reactivity",
|
|
11
|
+
"sfc",
|
|
12
|
+
"vite-plugin",
|
|
13
|
+
"ai-friendly",
|
|
14
|
+
"typescript",
|
|
15
|
+
"ark",
|
|
16
|
+
"arckode"
|
|
17
|
+
],
|
|
18
|
+
"author": "underworf",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"homepage": "https://gitlab.com/underworf/arckode-ui",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://gitlab.com/underworf/arckode-ui.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://gitlab.com/underworf/arckode-ui/-/issues"
|
|
27
|
+
},
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"import": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./vite": {
|
|
34
|
+
"import": "./dist/vite.js",
|
|
35
|
+
"types": "./dist/vite.d.ts"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"main": "./dist/index.js",
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"bin": {
|
|
41
|
+
"ark": "./dist/cli.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"skills",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "vite build && chmod +x dist/cli.js",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"type-check": "tsc --noEmit",
|
|
54
|
+
"prepublishOnly": "bun run build && bun run test && bun run type-check"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"typescript": ">=5.0.0",
|
|
58
|
+
"vite": ">=5.0.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/bun": "latest",
|
|
62
|
+
"happy-dom": "^20.9.0",
|
|
63
|
+
"typescript": "^5.4.0",
|
|
64
|
+
"vite": "^5.4.0",
|
|
65
|
+
"vitest": "^2.0.0"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# SKILL: Analyzer — reglas, fix suggestions, agregar nueva
|
|
2
|
+
|
|
3
|
+
> Cargá este skill cuando trabajés en `src/compiler/analyzer.ts` o cuando necesites agregar una nueva regla de validación estática.
|
|
4
|
+
|
|
5
|
+
## Interfaz Violation
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export interface Violation {
|
|
9
|
+
code: string // ej: 'HANDLER_NOT_IN_ACTIONS'
|
|
10
|
+
severity: 'error' | 'warning' // error bloquea build en prod
|
|
11
|
+
line: number // 1-based
|
|
12
|
+
message: string // descripción en español
|
|
13
|
+
fix?: string // sugerencia concreta (sino, undefined)
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Toda violation nueva DEBE incluir `fix` cuando el fix es determinístico.** La IA lee el fix y aplica directo, sin cruzar tablas externas.
|
|
18
|
+
|
|
19
|
+
## Las 16 reglas implementadas
|
|
20
|
+
|
|
21
|
+
| Código | Severidad | Detecta |
|
|
22
|
+
|--------|-----------|---------|
|
|
23
|
+
| MISSING_TEMPLATE | error | sin `<template>` |
|
|
24
|
+
| MISSING_SCRIPT | error | sin `<script>` |
|
|
25
|
+
| MISSING_LANG_TS | error | `<script>` sin `lang="ts"` |
|
|
26
|
+
| WRONG_TEMPLATE_ORDER | error | `<script>` antes que `<template>` |
|
|
27
|
+
| MISSING_COMPONENT_NAME | error | `defineComponent()` sin `name` |
|
|
28
|
+
| PROP_MISSING_TYPE | error | prop sin `type` |
|
|
29
|
+
| EMIT_CAMELCASE | error | evento en camelCase |
|
|
30
|
+
| REF_REACTIVE_USAGE | error | uso de `ref()` o `reactive()` |
|
|
31
|
+
| PROVIDE_INJECT_USAGE | error | uso de `provide()` o `inject()` |
|
|
32
|
+
| DIRECT_FETCH_IN_COMPONENT | error | `fetch()` en script de componente |
|
|
33
|
+
| LOGIC_IN_TEMPLATE | error | `++`, `--`, ternario en directiva |
|
|
34
|
+
| HANDLER_NOT_IN_ACTIONS | error | `@event` sin prefijo `actions.` |
|
|
35
|
+
| VFOR_NOT_NAMESPACED | error | colección de `v-for` sin `state.`/`computed.`/`props.` |
|
|
36
|
+
| SETUP_UNKNOWN_RETURN_KEY | error | llave extra en `return` de setup() |
|
|
37
|
+
| ARROW_FUNCTION_ACTION | warning | action como arrow function |
|
|
38
|
+
| VIF_NOT_NAMESPACED | warning | `v-if` con bare identifier (sin `.`) |
|
|
39
|
+
|
|
40
|
+
## formatViolation — output
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[arckode-ui] UserCard.ark:5
|
|
44
|
+
HANDLER_NOT_IN_ACTIONS: El handler "handleClick" no está namespaced.
|
|
45
|
+
|
|
46
|
+
> 5 | <button @click="handleClick">
|
|
47
|
+
Fix: Reemplazar "handleClick" por "actions.handleClick" y asegurarse...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
La línea `Fix: ...` aparece solo si `violation.fix !== undefined`.
|
|
51
|
+
|
|
52
|
+
## Cómo agregar una regla nueva
|
|
53
|
+
|
|
54
|
+
### Paso 1: definir el checker
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
function checkMyRule(source: string): Violation[] {
|
|
58
|
+
const violations: Violation[] = []
|
|
59
|
+
// Extraer template/script según corresponda
|
|
60
|
+
const template = extractTemplateContent(source)
|
|
61
|
+
if (!template) return violations
|
|
62
|
+
|
|
63
|
+
// ... detectar pattern, push violations con fix
|
|
64
|
+
|
|
65
|
+
return violations
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Paso 2: registrar en `analyze()`
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
if (!missing.template) {
|
|
73
|
+
violations.push(...checkMyRule(source))
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Paso 3: testear
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
describe('MY_RULE', () => {
|
|
81
|
+
test('detecta el problema', () => { ... })
|
|
82
|
+
test('no flaga código válido', () => { ... })
|
|
83
|
+
test('fix tiene la sugerencia esperada', () => {
|
|
84
|
+
const v = findViolation(violations, 'MY_RULE')
|
|
85
|
+
expect(v?.fix).toContain('lo que sugieres')
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Paso 4: documentar
|
|
91
|
+
|
|
92
|
+
- Skill principal (`~/.claude/skills/arckode-ui/SKILL.md`) → tabla de violations
|
|
93
|
+
- README del repo → tabla rápida
|
|
94
|
+
- Este skill → sumar a "Las 16 reglas implementadas"
|
|
95
|
+
|
|
96
|
+
## Helpers disponibles
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
extractTemplateContent(source) // { content, lineOffset } | null
|
|
100
|
+
extractBalancedBlock(source, startIdx) // { content, start, end } | null
|
|
101
|
+
getTopLevelObjectKeys(content) // string[]
|
|
102
|
+
getLineNumber(source, index) // number (1-based)
|
|
103
|
+
getLines(source) // string[]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Convenciones de mensaje y fix
|
|
107
|
+
|
|
108
|
+
**Message** — describe QUÉ está mal en lenguaje natural, con el valor problemático entre comillas:
|
|
109
|
+
```
|
|
110
|
+
'El handler "handleClick" no está namespaced.'
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Fix** — describe EXACTAMENTE qué cambiar, idealmente con el código de reemplazo:
|
|
114
|
+
```
|
|
115
|
+
'Reemplazar "handleClick" por "actions.handleClick" y asegurarse de que la función esté declarada en setup() y exportada en el return.actions.'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Para fixes ambiguos (varias opciones válidas), enumerar:
|
|
119
|
+
```
|
|
120
|
+
'v-for="item in state.items.value" // o computed.items.value / props.items'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Regla de retrocompatibilidad
|
|
124
|
+
|
|
125
|
+
- **NUNCA** cambiar el `code` de una regla existente
|
|
126
|
+
- **NUNCA** eliminar una regla (consumidores parsean output del analyzer en CI)
|
|
127
|
+
- **NUNCA** quitar el campo `fix` de una violation que ya lo tenía
|
|
128
|
+
- Solo agregar reglas nuevas; modificar mensaje OK; modificar fix para mejorarlo OK
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# SKILL: CLI — comandos `ark` y generators
|
|
2
|
+
|
|
3
|
+
> Cargá este skill cuando trabajés en `src/cli/` o cuando uses `ark new/generate/analyze/routes`.
|
|
4
|
+
|
|
5
|
+
## Comandos disponibles
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
ark new <nombre> # scaffold proyecto nuevo
|
|
9
|
+
ark generate <tipo> <nombre> # alias: ark g
|
|
10
|
+
ark analyze [--json] # analiza .ark files
|
|
11
|
+
ark routes # lista rutas detectadas en src/pages/
|
|
12
|
+
ark help | --help | -h # ayuda
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Tipos del generator
|
|
16
|
+
|
|
17
|
+
| Tipo | Alias | Naming | Output |
|
|
18
|
+
|------|-------|--------|--------|
|
|
19
|
+
| `component` | `c` | PascalCase | `src/components/features/<Name>.ark` |
|
|
20
|
+
| `page` | `p` | kebab/path | `src/pages/<path>.ark` |
|
|
21
|
+
| `store` | `s` | camelCase | `src/stores/<name>.store.ts` |
|
|
22
|
+
| `service` | `sv` | PascalCase | `src/services/<name>.service.ts` |
|
|
23
|
+
| `layout` | `l` | name | `src/pages/<name>/_layout.ark` |
|
|
24
|
+
|
|
25
|
+
Errores del generator:
|
|
26
|
+
- `INVALID_COMPONENT_NAME` — component no en PascalCase
|
|
27
|
+
- `INVALID_STORE_NAME` — store no en camelCase
|
|
28
|
+
- `INVALID_SERVICE_NAME` — service no en PascalCase
|
|
29
|
+
- `UNKNOWN_GENERATE_TYPE` — tipo no reconocido
|
|
30
|
+
- `FILE_ALREADY_EXISTS` — el destino ya existe
|
|
31
|
+
|
|
32
|
+
## Plantillas (`src/cli/templates/index.ts`)
|
|
33
|
+
|
|
34
|
+
Cada tipo tiene su función pura que retorna el contenido:
|
|
35
|
+
- `componentTemplate(name)` → `.ark` con shape estándar
|
|
36
|
+
- `pageTemplate(name)` → `.ark` para `src/pages/`
|
|
37
|
+
- `layoutTemplate(name)` → `.ark` con slot
|
|
38
|
+
- `storeTemplate(name)` → `.ts` con defineStore
|
|
39
|
+
- `serviceTemplate(name)` → `.ts` con createService
|
|
40
|
+
|
|
41
|
+
**Garantía**: el output de cada plantilla pasa el analyzer con 0 errors. Hay test que lo valida (`src/cli/__tests__/templates.test.ts`).
|
|
42
|
+
|
|
43
|
+
## Cómo agregar un tipo nuevo al generator
|
|
44
|
+
|
|
45
|
+
1. Crear función `xxxTemplate(name)` en `src/cli/templates/index.ts`
|
|
46
|
+
2. Agregar al `TYPE_ALIASES` en `src/cli/commands/generate.ts`
|
|
47
|
+
3. Agregar `buildXxxPath()` función
|
|
48
|
+
4. Agregar `case 'xxx':` al switch
|
|
49
|
+
5. Validación de naming si aplica
|
|
50
|
+
6. Test en `src/cli/__tests__/generate.test.ts` + `templates.test.ts`
|
|
51
|
+
|
|
52
|
+
## ark new — scaffold de proyecto
|
|
53
|
+
|
|
54
|
+
Crea estructura completa en `<projectName>/`:
|
|
55
|
+
- `src/pages/index.ark`, `src/pages/_layout.ark`
|
|
56
|
+
- `src/components/{ui,features}/` (con .gitkeep)
|
|
57
|
+
- `src/{stores,services,types,shared/{helpers,constants}}/`
|
|
58
|
+
- `src/styles/global.css` (Tailwind entry)
|
|
59
|
+
- `vite.config.ts` (con `arkcodeUi`)
|
|
60
|
+
- `tsconfig.json`, `tailwind.config.js`, `postcss.config.js`
|
|
61
|
+
- `package.json` con scripts dev/build/preview
|
|
62
|
+
|
|
63
|
+
**IMPORTANTE**: el `vite.config.ts` generado importa de `'arckode-ui/vite'` con el nombre real `arkcodeUi` (no `arckodePlugin`). Test cubre esto en `src/cli/__tests__/new.test.ts`.
|
|
64
|
+
|
|
65
|
+
Hay test que verifica que **ninguna** plantilla del scaffold use nombres rotos (`arckodePlugin`, `defineConfig from 'arckode-ui/vite'`).
|
|
66
|
+
|
|
67
|
+
## Anti-patterns
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ❌ generar templates con imports inexistentes
|
|
71
|
+
const TEMPLATE = `import { arckodePlugin } from 'arckode-ui/vite'`
|
|
72
|
+
// → 'arkcodeUi' (es el nombre real)
|
|
73
|
+
|
|
74
|
+
// ❌ olvidar el test del template
|
|
75
|
+
// si agregás un tipo nuevo, agregar test que corra analyze() sobre el output
|
|
76
|
+
|
|
77
|
+
// ❌ shebang en src/cli/index.ts
|
|
78
|
+
// esbuild lo rechaza. El shebang se inyecta vía rollupOptions.output.banner
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Cómo se construye el bin
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// vite.config.ts
|
|
85
|
+
build: {
|
|
86
|
+
lib: {
|
|
87
|
+
entry: {
|
|
88
|
+
index: 'src/index.ts',
|
|
89
|
+
vite: 'src/compiler/vite-plugin.ts',
|
|
90
|
+
cli: 'src/cli/index.ts',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
rollupOptions: {
|
|
94
|
+
output: {
|
|
95
|
+
banner: (chunk) => chunk.name === 'cli' ? '#!/usr/bin/env node' : '',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Y en package.json:
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"bin": { "ark": "./dist/cli.js" },
|
|
105
|
+
"scripts": { "build": "vite build && chmod +x dist/cli.js" }
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
El `chmod +x` es obligatorio porque el shebang solo se respeta si el archivo es ejecutable.
|