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.
@@ -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
+ }
@@ -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 `page` materializes pages, where components live, which framework to
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 page <slug> <template> materialize a full page");
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 page ${slug} ${layoutNames[0]} # Next .tsx + .css (default)
113
- synthesisui page ${slug} ${layoutNames[0]} --target general # single self-contained HTML
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 { page } from "./commands/page.js";
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 page <slug> <template> materialize a whole page from a DS template
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> page/init target: next | general (default: next)
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 page (default: <pagesDir>/<file>)
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 page halogen dashboard-sidebar
43
- synthesisui page halogen landing --out app/page.tsx
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
- case "page": {
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 template = args[1];
131
- if (!slug || !template) {
132
- console.error("error: provide slug and template - `synthesisui page <slug> <template>`");
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 page(slug, template, { registry, dir, out, target, version });
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 (`?page=&target=`). The
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 fetchPage(base, slug, template, target, version) {
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("page", template);
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.1.21",
3
+ "version": "0.1.24",
4
4
  "description": "Traz design systems do SynthesisUI para qualquer projeto (materializa em _local/ds/).",
5
5
  "type": "module",
6
6
  "bin": {