release: WRNexusJS 0.6.0
This commit is contained in:
@@ -1,20 +1,14 @@
|
||||
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { format, resolveConfig } from "prettier";
|
||||
|
||||
const uiRoot = join(import.meta.dirname, "..", "packages", "ui");
|
||||
const componentsDir = join(uiRoot, "components");
|
||||
const catalog = JSON.parse(readFileSync(join(uiRoot, "component-catalog.json"), "utf8"));
|
||||
const catalogPath = join(uiRoot, "component-catalog.json");
|
||||
const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
|
||||
const metadata = new Map(catalog.components.map((entry) => [entry.name, entry]));
|
||||
|
||||
async function writeFormatted(path, source, parser) {
|
||||
const config = (await resolveConfig(path)) ?? {};
|
||||
const formatted = await format(source, {
|
||||
...config,
|
||||
filepath: path,
|
||||
parser,
|
||||
});
|
||||
writeFileSync(path, formatted);
|
||||
async function writeFormatted(path, source) {
|
||||
writeFileSync(path, source);
|
||||
}
|
||||
|
||||
function block(source, keyword) {
|
||||
@@ -34,9 +28,9 @@ function propsOf(source) {
|
||||
for (const line of block(source, "props").split(/\r?\n/)) {
|
||||
const match = line
|
||||
.trim()
|
||||
.match(/^([A-Za-z][A-Za-z0-9_]*)(?:\s*:\s*([^=]+?))?\s*(?:=\s*(.+))?$/);
|
||||
.match(/^([A-Za-z][A-Za-z0-9_]*)(\?)?(?:\s*:\s*([^=]+?))?\s*(?:=\s*(.+))?$/);
|
||||
if (!match) continue;
|
||||
const [, name, annotation, rawDefault] = match;
|
||||
const [, name, optional, annotation, rawDefault] = match;
|
||||
const value = rawDefault?.trim();
|
||||
const type =
|
||||
annotation?.trim() ||
|
||||
@@ -45,11 +39,58 @@ function propsOf(source) {
|
||||
: /^-?\d+(?:\.\d+)?$/.test(value ?? "")
|
||||
? "number"
|
||||
: "string");
|
||||
props.push({ name, type, required: value === undefined, default: value ?? null });
|
||||
const options = [...type.matchAll(/["']([^"']+)["']/g)].map((entry) => entry[1]);
|
||||
props.push({
|
||||
name,
|
||||
type,
|
||||
required: optional !== "?" && value === undefined,
|
||||
default: value ?? null,
|
||||
options,
|
||||
});
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
function outputsOf(source) {
|
||||
const body = block(source, "outputs");
|
||||
const outputs = [];
|
||||
let index = 0;
|
||||
while (index < body.length) {
|
||||
while (index < body.length && /\s/.test(body[index])) index++;
|
||||
if (index >= body.length) break;
|
||||
const nameMatch = /^[A-Za-z][A-Za-z0-9_]*/.exec(body.slice(index));
|
||||
if (!nameMatch) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
const name = nameMatch[0];
|
||||
index += name.length;
|
||||
while (index < body.length && /\s/.test(body[index])) index++;
|
||||
if (body[index] !== "(") {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
const start = ++index;
|
||||
let depth = 1;
|
||||
let quote = null;
|
||||
for (; index < body.length && depth > 0; index++) {
|
||||
const char = body[index];
|
||||
if (quote) {
|
||||
if (char === "\\") index++;
|
||||
else if (char === quote) quote = null;
|
||||
continue;
|
||||
}
|
||||
if (char === '"' || char === "'" || char === "`") quote = char;
|
||||
else if (char === "(") depth++;
|
||||
else if (char === ")") depth--;
|
||||
}
|
||||
const parameters = body.slice(start, index - 1).trim();
|
||||
const payloadMatch = /^payload\??\s*:\s*([\s\S]+)$/.exec(parameters);
|
||||
outputs.push({ name, payloadType: payloadMatch?.[1]?.trim() ?? null });
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
function words(name) {
|
||||
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
||||
}
|
||||
@@ -84,13 +125,19 @@ const components = [...sourceByName.values()]
|
||||
const slots = [...source.matchAll(/<slot(?:\s+name="([^"]+)")?/g)].map(
|
||||
(match) => match[1] ?? "default",
|
||||
);
|
||||
const events = [
|
||||
const outputs = outputsOf(source);
|
||||
const legacyEvents = [
|
||||
...new Set(
|
||||
[...source.matchAll(/@event\s+([A-Za-z][A-Za-z0-9_]*)\s*=\s*function/g)].map(
|
||||
(match) => match[1],
|
||||
),
|
||||
),
|
||||
];
|
||||
for (const name of legacyEvents) {
|
||||
if (!outputs.some((output) => output.name === name)) {
|
||||
outputs.push({ name, payloadType: "unknown" });
|
||||
}
|
||||
}
|
||||
return {
|
||||
name,
|
||||
mount: name,
|
||||
@@ -98,12 +145,24 @@ const components = [...sourceByName.values()]
|
||||
purpose: entry?.purpose ?? `Reusable ${words(name)} component.`,
|
||||
props: propsOf(source),
|
||||
slots: [...new Set(slots)],
|
||||
events,
|
||||
outputs,
|
||||
events: outputs.map((output) => output.name),
|
||||
source: `components/${file}`,
|
||||
};
|
||||
})
|
||||
.sort((left, right) => left.name.localeCompare(right.name));
|
||||
|
||||
const catalogComponents = components.map((component) => ({
|
||||
name: component.name,
|
||||
category: component.category,
|
||||
purpose: component.purpose,
|
||||
}));
|
||||
await writeFormatted(
|
||||
catalogPath,
|
||||
JSON.stringify({ ...catalog, components: catalogComponents }, null, 2) + "\n",
|
||||
"json",
|
||||
);
|
||||
|
||||
const referencePath = join(uiRoot, "component-reference.json");
|
||||
await writeFormatted(
|
||||
referencePath,
|
||||
@@ -140,7 +199,7 @@ for (const category of [...new Set(components.map((component) => component.categ
|
||||
`- Mount: \`data-component="${component.mount}"\``,
|
||||
`- Props: ${props}`,
|
||||
`- Slots: ${component.slots.length ? component.slots.map((slot) => `\`${slot}\``).join(", ") : "None"}`,
|
||||
`- Events: ${component.events.length ? component.events.map((event) => `\`${event}\``).join(", ") : "None"}`,
|
||||
`- Outputs: ${component.outputs.length ? component.outputs.map((output) => `\`${output.name}(${output.payloadType ?? ""})\``).join(", ") : "None"}`,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user