synthesisui 0.1.21 → 0.1.24
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/dist/commands/component.js +27 -0
- package/dist/commands/init.js +2 -2
- package/dist/commands/template.js +42 -0
- package/dist/guide.js +29 -3
- package/dist/index.js +40 -11
- package/dist/registry.js +24 -3
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveRegistry } from "../config.js";
|
|
4
|
+
import { fetchComponent } from "../registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Brings ONE component from a design system into the project (granular "bring
|
|
7
|
+
* specific", INS-18 fatia 3) - its recipe + compiled CSS, written under
|
|
8
|
+
* `_synthesisui/ds/<slug>/components/`. Handy for a component you refit/created
|
|
9
|
+
* on the platform. The component's styles reference the DS tokens, so the system
|
|
10
|
+
* itself must be installed (`synthesisui add <slug>`) for `tokens.css` to resolve.
|
|
11
|
+
*/
|
|
12
|
+
export async function component(slug, name, opts) {
|
|
13
|
+
const base = resolveRegistry(opts.registry);
|
|
14
|
+
const root = opts.dir ?? process.cwd();
|
|
15
|
+
console.log(`→ fetching "${name}" from "${slug}" …`);
|
|
16
|
+
const res = await fetchComponent(base, slug, name, opts.version);
|
|
17
|
+
const dir = join(root, "_synthesisui", "ds", slug, "components");
|
|
18
|
+
await mkdir(dir, { recursive: true });
|
|
19
|
+
await writeFile(join(dir, `${res.name}.json`), `${JSON.stringify(res.recipe, null, 2)}\n`, "utf8");
|
|
20
|
+
await writeFile(join(dir, `${res.name}.css`), `${res.css}\n`, "utf8");
|
|
21
|
+
console.log(`✓ ${res.name} → _synthesisui/ds/${slug}/components/${res.name}.{json,css} (${slug} v${res.version})`);
|
|
22
|
+
console.log("");
|
|
23
|
+
console.log("Use it:");
|
|
24
|
+
console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
|
|
25
|
+
console.log(` • @import "_synthesisui/ds/${slug}/components/${res.name}.css" in your CSS`);
|
|
26
|
+
console.log(` • <div data-ds="${slug}"><div class="ds-${res.name}">…</div></div>`);
|
|
27
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
|
|
|
2
2
|
import { add } from "./add.js";
|
|
3
3
|
/**
|
|
4
4
|
* Bootstraps a project for SynthesisUI: writes `_synthesisui/config.json`
|
|
5
|
-
* (where `
|
|
5
|
+
* (where `template` materializes pages, where components live, which framework to
|
|
6
6
|
* target) and - with `--ds <slug>` - immediately brings that system in, so the
|
|
7
7
|
* project lands with tokens, philosophy, rules and a CLAUDE.md in one step.
|
|
8
8
|
* Committable; safe to re-run.
|
|
@@ -30,5 +30,5 @@ export async function init(opts) {
|
|
|
30
30
|
console.log("");
|
|
31
31
|
console.log("Next steps:");
|
|
32
32
|
console.log(" • synthesisui add <slug> bring a design system in");
|
|
33
|
-
console.log(" • synthesisui
|
|
33
|
+
console.log(" • synthesisui template <slug> <name> materialize a full page");
|
|
34
34
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
4
|
+
import { fetchTemplate } from "../registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Materializes a whole page from a DS template into the project (hybrid
|
|
7
|
+
* codegen-first): the server codegens deterministic files, we write them, and
|
|
8
|
+
* the agent refines them in place. The page uses the DS's `.ds-*` classes +
|
|
9
|
+
* path classes; the co-located CSS (Next target) carries the responsive media
|
|
10
|
+
* queries + the CSS-only hamburger, so it re-vests once `tokens.css` is in.
|
|
11
|
+
*/
|
|
12
|
+
export async function template(slug, name, opts) {
|
|
13
|
+
const base = resolveRegistry(opts.registry);
|
|
14
|
+
const root = opts.dir ?? process.cwd();
|
|
15
|
+
const config = await readProjectConfig(root);
|
|
16
|
+
const target = opts.target === "general" || opts.target === "next"
|
|
17
|
+
? opts.target
|
|
18
|
+
: config.target;
|
|
19
|
+
console.log(`→ generating "${name}" from "${slug}" (${target}) …`);
|
|
20
|
+
const generated = await fetchTemplate(base, slug, name, target, opts.version);
|
|
21
|
+
// --out targets the page (1st file); sibling files (e.g. the CSS) land in the
|
|
22
|
+
// same directory. Without --out, everything goes under <pagesDir>.
|
|
23
|
+
const [pageFile, ...siblings] = generated.files;
|
|
24
|
+
const pageRel = opts.out ?? join(config.pagesDir, pageFile.filename);
|
|
25
|
+
const pageDir = dirname(join(root, pageRel));
|
|
26
|
+
await mkdir(pageDir, { recursive: true });
|
|
27
|
+
await writeFile(join(root, pageRel), pageFile.code, "utf8");
|
|
28
|
+
console.log(`✓ wrote ${pageRel} (${slug} v${generated.version})`);
|
|
29
|
+
for (const f of siblings) {
|
|
30
|
+
const rel = opts.out
|
|
31
|
+
? join(dirname(pageRel), f.filename)
|
|
32
|
+
: join(config.pagesDir, f.filename);
|
|
33
|
+
await writeFile(join(root, rel), f.code, "utf8");
|
|
34
|
+
console.log(`✓ wrote ${rel}`);
|
|
35
|
+
}
|
|
36
|
+
console.log("");
|
|
37
|
+
console.log("Next steps:");
|
|
38
|
+
console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
|
|
39
|
+
console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
|
|
40
|
+
console.log(" • refine the file: wire real data, split into components, swap placeholders");
|
|
41
|
+
console.log(` • keep the data-ds="${slug}" wrapper and the ds-* / layout classes (stays on-system)`);
|
|
42
|
+
}
|
package/dist/guide.js
CHANGED
|
@@ -84,6 +84,32 @@ its typographic identity. Load them once (any one approach):
|
|
|
84
84
|
- **Next.js** (\`next/font/google\`), **Fontsource**, or self-hosted \`@font-face\` work too - just
|
|
85
85
|
register the families above. If a family isn't on Google Fonts, self-host it.
|
|
86
86
|
|
|
87
|
+
---
|
|
88
|
+
`
|
|
89
|
+
: "";
|
|
90
|
+
// Libraries the system references but doesn't bundle - the agent should
|
|
91
|
+
// install them when it actually renders icons/charts (INS-19).
|
|
92
|
+
const iconLibraries = doc.icons?.libraries ?? [];
|
|
93
|
+
const hasCharts = !!doc.charts && Object.keys(doc.charts).length > 0;
|
|
94
|
+
const depLines = [
|
|
95
|
+
iconLibraries.includes("lucide")
|
|
96
|
+
? "- **Icons** - components reference Lucide icon names. Run `npm i lucide-react` " +
|
|
97
|
+
"(or swap in your own icon set) to render them."
|
|
98
|
+
: "",
|
|
99
|
+
hasCharts
|
|
100
|
+
? "- **Charts** - charts are themed through your series tokens, but you bring the " +
|
|
101
|
+
"renderer. Run `npm i recharts` (the gallery uses Recharts)."
|
|
102
|
+
: "",
|
|
103
|
+
].filter(Boolean);
|
|
104
|
+
const depsSection = depLines.length
|
|
105
|
+
? `
|
|
106
|
+
## Dependencies
|
|
107
|
+
|
|
108
|
+
Beyond loading the fonts above, the system references libraries it doesn't bundle - install them
|
|
109
|
+
when you actually render that UI:
|
|
110
|
+
|
|
111
|
+
${depLines.join("\n")}
|
|
112
|
+
|
|
87
113
|
---
|
|
88
114
|
`
|
|
89
115
|
: "";
|
|
@@ -109,8 +135,8 @@ This system ships whole-page templates: ${layoutNames.map((n) => `\`${n}\``).joi
|
|
|
109
135
|
|
|
110
136
|
Materialize one as a real file:
|
|
111
137
|
\`\`\`bash
|
|
112
|
-
synthesisui
|
|
113
|
-
synthesisui
|
|
138
|
+
synthesisui template ${slug} ${layoutNames[0]} # Next .tsx + .css (default)
|
|
139
|
+
synthesisui template ${slug} ${layoutNames[0]} --target general # single self-contained HTML
|
|
114
140
|
\`\`\`
|
|
115
141
|
It writes a **deterministic scaffold**: the page uses this DS's \`.ds-*\` recipe classes + layout
|
|
116
142
|
path-classes, paired with a co-located scoped CSS (Next target) that carries the **responsive** media
|
|
@@ -186,7 +212,7 @@ ${hasAlt
|
|
|
186
212
|
root.toggleAttribute("data-scheme"); // present = ${altScheme}, absent = ${meta.scheme}
|
|
187
213
|
\`\`\`
|
|
188
214
|
`
|
|
189
|
-
: ""}${fontsSection}${hasTailwind
|
|
215
|
+
: ""}${fontsSection}${depsSection}${hasTailwind
|
|
190
216
|
? `
|
|
191
217
|
## Styling with Tailwind v4 (preferred in this project)
|
|
192
218
|
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { add } from "./commands/add.js";
|
|
3
3
|
import { advise } from "./commands/advise.js";
|
|
4
|
+
import { component } from "./commands/component.js";
|
|
4
5
|
import { generate } from "./commands/generate.js";
|
|
5
6
|
import { init } from "./commands/init.js";
|
|
6
7
|
import { list } from "./commands/list.js";
|
|
7
8
|
import { login } from "./commands/login.js";
|
|
8
|
-
import {
|
|
9
|
+
import { template } from "./commands/template.js";
|
|
9
10
|
import { use } from "./commands/use.js";
|
|
10
11
|
import { RegistryError } from "./registry.js";
|
|
11
12
|
const HELP = `synthesisui - bring SynthesisUI design systems into your project
|
|
@@ -15,7 +16,8 @@ Usage:
|
|
|
15
16
|
synthesisui init [options] write _synthesisui/config.json (target, dirs); --ds to bring one in
|
|
16
17
|
synthesisui list [options] list the published design systems
|
|
17
18
|
synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
|
|
18
|
-
synthesisui
|
|
19
|
+
synthesisui template <slug> <name> materialize a whole page from a DS template
|
|
20
|
+
synthesisui component <slug> <name> bring one component (recipe + css) into the project
|
|
19
21
|
synthesisui use <slug> "<intent>" print a ready-to-paste agent prompt to build/modify on-system
|
|
20
22
|
synthesisui advise "<value prop>" engagement-pattern proposals for this project (login required)
|
|
21
23
|
synthesisui generate "<desc>" generate a token-only component recipe for your DS (login required)
|
|
@@ -26,10 +28,10 @@ Options:
|
|
|
26
28
|
--version <n> install a specific version (default: latest)
|
|
27
29
|
--ds <slug> init: bring this DS in right away · generate: target DS (default: installed)
|
|
28
30
|
--name <name> preferred component name for generate
|
|
29
|
-
--target <t>
|
|
31
|
+
--target <t> template/init target: next | general (default: next)
|
|
30
32
|
--pages-dir <dir> init: folder for generated pages (default: app)
|
|
31
33
|
--components-dir <dir> init: folder where components live (default: components)
|
|
32
|
-
--out <path> output path for the generated
|
|
34
|
+
--out <path> output path for the generated template (default: <pagesDir>/<file>)
|
|
33
35
|
-h, --help this help
|
|
34
36
|
|
|
35
37
|
Examples:
|
|
@@ -39,8 +41,9 @@ Examples:
|
|
|
39
41
|
synthesisui list
|
|
40
42
|
synthesisui add halogen
|
|
41
43
|
synthesisui add halogen --version 3
|
|
42
|
-
synthesisui
|
|
43
|
-
synthesisui
|
|
44
|
+
synthesisui template halogen dashboard-sidebar
|
|
45
|
+
synthesisui template halogen landing --out app/page.tsx
|
|
46
|
+
synthesisui component halogen pricing-tier
|
|
44
47
|
synthesisui use halogen "a pricing section with three tiers and a highlighted plan"
|
|
45
48
|
synthesisui use halogen "make the card shadow softer in components/StatCard.tsx"
|
|
46
49
|
synthesisui advise "habit-building app for tracking personal finances"
|
|
@@ -125,11 +128,17 @@ async function main() {
|
|
|
125
128
|
await init({ dir, registry, target, pagesDir, componentsDir, ds });
|
|
126
129
|
break;
|
|
127
130
|
}
|
|
128
|
-
|
|
131
|
+
// `page` is the legacy alias (renamed to `template`); it still works so
|
|
132
|
+
// GUIDE.md files materialized before the rename don't break.
|
|
133
|
+
case "page":
|
|
134
|
+
case "template": {
|
|
135
|
+
if (command === "page") {
|
|
136
|
+
console.error("note: `synthesisui page` was renamed to `synthesisui template` - the old name still works for now.");
|
|
137
|
+
}
|
|
129
138
|
const slug = args[0];
|
|
130
|
-
const
|
|
131
|
-
if (!slug || !
|
|
132
|
-
console.error("error: provide slug and template - `synthesisui
|
|
139
|
+
const name = args[1];
|
|
140
|
+
if (!slug || !name) {
|
|
141
|
+
console.error("error: provide slug and template name - `synthesisui template <slug> <name>`");
|
|
133
142
|
process.exitCode = 1;
|
|
134
143
|
return;
|
|
135
144
|
}
|
|
@@ -144,7 +153,27 @@ async function main() {
|
|
|
144
153
|
}
|
|
145
154
|
const target = typeof flags.target === "string" ? flags.target : undefined;
|
|
146
155
|
const out = typeof flags.out === "string" ? flags.out : undefined;
|
|
147
|
-
await
|
|
156
|
+
await template(slug, name, { registry, dir, out, target, version });
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "component": {
|
|
160
|
+
const slug = args[0];
|
|
161
|
+
const name = args[1];
|
|
162
|
+
if (!slug || !name) {
|
|
163
|
+
console.error("error: provide slug and component name - `synthesisui component <slug> <name>`");
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let version;
|
|
168
|
+
if (typeof flags.version === "string") {
|
|
169
|
+
version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
|
|
170
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
171
|
+
console.error(`error: invalid --version "${flags.version}" - use an integer ≥ 1`);
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await component(slug, name, { registry, dir, version });
|
|
148
177
|
break;
|
|
149
178
|
}
|
|
150
179
|
case "use": {
|
package/dist/registry.js
CHANGED
|
@@ -43,12 +43,12 @@ export async function fetchDesignSystem(base, slug, version) {
|
|
|
43
43
|
return (await res.json());
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
* Fetches a whole page generated from a DS template (`?
|
|
46
|
+
* Fetches a whole page generated from a DS template (`?template=&target=`). The
|
|
47
47
|
* server codegens it from `document.layouts[<template>]`; the CLI just writes it.
|
|
48
48
|
*/
|
|
49
|
-
export async function
|
|
49
|
+
export async function fetchTemplate(base, slug, template, target, version) {
|
|
50
50
|
const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
|
|
51
|
-
url.searchParams.set("
|
|
51
|
+
url.searchParams.set("template", template);
|
|
52
52
|
url.searchParams.set("target", target);
|
|
53
53
|
if (version != null)
|
|
54
54
|
url.searchParams.set("version", String(version));
|
|
@@ -63,6 +63,27 @@ export async function fetchPage(base, slug, template, target, version) {
|
|
|
63
63
|
}
|
|
64
64
|
return (await res.json());
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Fetches ONE component from a DS (`?component=<name>`): its recipe + compiled
|
|
68
|
+
* CSS. Granular "bring specific" - the system must exist; works for public DS
|
|
69
|
+
* without login (private/owned needs the token).
|
|
70
|
+
*/
|
|
71
|
+
export async function fetchComponent(base, slug, name, version) {
|
|
72
|
+
const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
|
|
73
|
+
url.searchParams.set("component", name);
|
|
74
|
+
if (version != null)
|
|
75
|
+
url.searchParams.set("version", String(version));
|
|
76
|
+
const res = await request(url.toString());
|
|
77
|
+
if (res.status === 404) {
|
|
78
|
+
const body = (await res.json().catch(() => ({})));
|
|
79
|
+
throw new RegistryError(body.message ??
|
|
80
|
+
`No component "${name}" in "${slug}". Run \`synthesisui add ${slug}\` and check its components.`);
|
|
81
|
+
}
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
throw new RegistryError(`Registry responded ${res.status} while fetching "${name}".`);
|
|
84
|
+
}
|
|
85
|
+
return (await res.json());
|
|
86
|
+
}
|
|
66
87
|
/**
|
|
67
88
|
* Calls the hosted advisor (`POST /api/ai/advisor`). Gated + metered server-side:
|
|
68
89
|
* 401 = not logged in, 429 = daily quota reached. Sends the Bearer token if present.
|