Files
Oleg Isonen 82c3d83fd1 fix: optimize CLI import for large data amount (#5831)
## Summary

- Add CLI project import support from synced `.webstudio/data.json` into
another project via a destination share link.
- Move project bundle import/export contract into
`@webstudio-is/protocol`, including bundle versioning, staged upload
constants, schema validation, and fixture coverage.
- Upload project bundle data through staged TUS upload before calling
the import RPC to avoid request body limits.
- Upload referenced assets as binary files through the existing asset
upload route before importing asset rows.
- Add `--skip-assets` and `--ignore-version-check` import options.
- Add retry handling for transient missing imported asset files by
re-uploading only the reported missing assets.
- Share missing-asset import error formatting/parsing through protocol
to avoid duplicated string contracts.
- Preserve browser asset upload behavior while adding API upload
support.
- Normalize schema naming across the branch to lower-case names without
`Schema` suffix.
2026-06-23 18:50:01 +02:00

165 lines
4.2 KiB
TypeScript

import { aria } from "aria-query";
import { mkdir, writeFile } from "node:fs/promises";
import {
coreMetas,
createScope,
elementComponent,
type Prop,
type Instance,
type Instances,
type Props,
} from "@webstudio-is/sdk";
import { generateWebstudioComponent } from "@webstudio-is/react-sdk";
import {
findByTags,
getAttr,
getTextContent,
loadPage,
parseHtml,
} from "./crawler";
type Attribute = {
name: string;
description: string;
type: "string" | "boolean" | "number" | "select";
options?: string[];
};
const overrides: Record<string, Partial<Attribute>> = {
"aria-label": {
description:
"Provides the accessible name that describes an interactive element if no other accessible name exists, for example in a button that contains an image with no text.",
},
};
const html = await loadPage("aria1.3", "https://www.w3.org/TR/wai-aria-1.3");
const document = parseHtml(html);
const list = findByTags(document, "dl").find(
(table) => getAttr(table, "id")?.value === "index_state_prop"
);
const terms = findByTags(list, "dt");
const details = findByTags(list, "dd");
const descriptions = new Map<string, string>();
for (let index = 0; index < terms.length; index += 1) {
const term = getTextContent(terms[index]);
const detail = getTextContent(details[index]);
descriptions.set(term, detail);
}
const attributes: Attribute[] = [
{
name: "role",
description:
"Defines an explicit role for an element for use by assistive technologies.",
type: "string",
},
];
for (const [name, meta] of aria.entries()) {
const attribute: Attribute = {
name,
description: descriptions.get(name) ?? "",
type: "string",
...overrides[name],
};
if (meta.type === "string" || meta.type === "boolean") {
attribute.type = meta.type;
} else if (meta.type === "number" || meta.type === "integer") {
attribute.type = "number";
} else if (meta.type === "token" || meta.type === "tokenlist") {
attribute.type = "select";
attribute.options = meta.values?.map((item) => item.toString());
} else if (meta.type === "tristate") {
attribute.type = "select";
attribute.options = ["false", "mixed", "true"];
} else {
meta.type satisfies "id" | "idlist" | "tristate";
}
attributes.push(attribute);
}
const ariaContent = `type Attribute = {
name: string,
description: string,
required?: boolean,
type: 'string' | 'boolean' | 'number' | 'select' | 'url',
options?: string[]
}
export const ariaAttributes: Attribute[] = ${JSON.stringify(attributes, null, 2)};
`;
await mkdir("./src/__generated__", { recursive: true });
await writeFile("./src/__generated__/aria.ts", ariaContent);
// generate jsx for testing react types
const instances: Instances = new Map();
const props: Props = new Map();
let id = 0;
const getId = () => {
id += 1;
return id.toString();
};
const instance: Instance = {
type: "instance",
id: getId(),
component: elementComponent,
tag: "div",
children: [],
};
instances.set(instance.id, instance);
for (const { name, type, options } of attributes) {
const id = getId();
const instanceId = instance.id;
if (type === "string") {
const prop: Prop = { id, instanceId, type, name, value: "" };
props.set(prop.id, prop);
continue;
}
if (type === "boolean") {
const prop: Prop = { id, instanceId, type, name, value: true };
props.set(prop.id, prop);
continue;
}
if (type === "select") {
const prop: Prop = {
id,
instanceId,
type: "string",
name,
value: options?.[0] ?? "",
};
props.set(prop.id, prop);
continue;
}
if (type === "number") {
const prop: Prop = {
id,
instanceId,
type: "number",
name,
value: 0,
};
props.set(prop.id, prop);
continue;
}
throw Error(`Unknown attribute ${name} with type ${type}`);
}
await mkdir("./src/__generated__", { recursive: true });
await writeFile(
"./src/__generated__/aria-jsx-test.tsx",
generateWebstudioComponent({
name: "Page",
scope: createScope(),
metas: new Map(Object.entries(coreMetas)),
instances,
props,
dataSources: new Map(),
rootInstanceId: instance.id,
classesMap: new Map(),
parameters: [],
}) + "export { Page }"
);