synthesisui 0.1.13 → 0.1.14
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/init.js +21 -0
- package/dist/commands/page.js +32 -0
- package/dist/config.js +28 -0
- package/dist/guide.js +21 -1
- package/dist/index.js +41 -6
- package/dist/registry.js +21 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG, writeProjectConfig } from "../config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Writes `_synthesisui/config.json` — where `synthesisui page` materializes
|
|
4
|
+
* pages and which framework to target. Committable; safe to re-run.
|
|
5
|
+
*/
|
|
6
|
+
export async function init(opts) {
|
|
7
|
+
const root = opts.dir ?? process.cwd();
|
|
8
|
+
const target = opts.target === "general" ? "general" : "next";
|
|
9
|
+
const config = {
|
|
10
|
+
target,
|
|
11
|
+
pagesDir: opts.pagesDir ?? (target === "next" ? "app" : DEFAULT_CONFIG.pagesDir),
|
|
12
|
+
};
|
|
13
|
+
await writeProjectConfig(root, config);
|
|
14
|
+
console.log("✓ wrote _synthesisui/config.json");
|
|
15
|
+
console.log(` target: ${config.target}`);
|
|
16
|
+
console.log(` pagesDir: ${config.pagesDir}`);
|
|
17
|
+
console.log("");
|
|
18
|
+
console.log("Next steps:");
|
|
19
|
+
console.log(" • synthesisui add <slug> bring a design system in");
|
|
20
|
+
console.log(" • synthesisui page <slug> <template> materialize a full page");
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { readProjectConfig, resolveRegistry } from "../config.js";
|
|
4
|
+
import { fetchPage } from "../registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Materializes a whole page from a DS template into the project (hybrid
|
|
7
|
+
* codegen-first): the server codegens a deterministic file, we write it, and
|
|
8
|
+
* the agent refines it in place. The page uses the DS's `.ds-*` classes +
|
|
9
|
+
* inline token vars, so it re-vests once `tokens.css` is imported.
|
|
10
|
+
*/
|
|
11
|
+
export async function page(slug, template, opts) {
|
|
12
|
+
const base = resolveRegistry(opts.registry);
|
|
13
|
+
const root = opts.dir ?? process.cwd();
|
|
14
|
+
const config = await readProjectConfig(root);
|
|
15
|
+
const target = opts.target === "general" || opts.target === "next"
|
|
16
|
+
? opts.target
|
|
17
|
+
: config.target;
|
|
18
|
+
console.log(`→ generating "${template}" from "${slug}" (${target}) …`);
|
|
19
|
+
const generated = await fetchPage(base, slug, template, target, opts.version);
|
|
20
|
+
// --out wins; otherwise <pagesDir>/<filename> (e.g. app/dashboard.tsx).
|
|
21
|
+
const relPath = opts.out ?? join(config.pagesDir, generated.filename);
|
|
22
|
+
const outPath = join(root, relPath);
|
|
23
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
24
|
+
await writeFile(outPath, generated.code, "utf8");
|
|
25
|
+
console.log(`✓ wrote ${relPath} (${slug} v${generated.version})`);
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log("Next steps:");
|
|
28
|
+
console.log(` • ensure the DS is installed: synthesisui add ${slug} (provides tokens.css)`);
|
|
29
|
+
console.log(` • @import "_synthesisui/ds/${slug}/tokens.css" in your global CSS`);
|
|
30
|
+
console.log(" • refine the file: wire real data, split into components, swap placeholders");
|
|
31
|
+
console.log(` • keep the data-ds="${slug}" wrapper and the ds-* classes (stays on-system)`);
|
|
32
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -35,3 +35,31 @@ export async function writeToken(token, registry) {
|
|
|
35
35
|
mode: 0o600,
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
|
+
/** Project-level config (committed): `<root>/_synthesisui/config.json`. */
|
|
39
|
+
export const DEFAULT_CONFIG = {
|
|
40
|
+
target: "next",
|
|
41
|
+
pagesDir: "app",
|
|
42
|
+
};
|
|
43
|
+
const projectConfigPath = (root) => join(root, "_synthesisui", "config.json");
|
|
44
|
+
/** Reads the project config, falling back to defaults when absent/invalid. */
|
|
45
|
+
export async function readProjectConfig(root) {
|
|
46
|
+
try {
|
|
47
|
+
const raw = await readFile(projectConfigPath(root), "utf8");
|
|
48
|
+
const parsed = JSON.parse(raw);
|
|
49
|
+
return {
|
|
50
|
+
target: parsed.target === "general" ? "general" : "next",
|
|
51
|
+
pagesDir: typeof parsed.pagesDir === "string" && parsed.pagesDir
|
|
52
|
+
? parsed.pagesDir
|
|
53
|
+
: DEFAULT_CONFIG.pagesDir,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return DEFAULT_CONFIG;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Writes the project config (committable, plain JSON). */
|
|
61
|
+
export async function writeProjectConfig(root, config) {
|
|
62
|
+
const path = projectConfigPath(root);
|
|
63
|
+
await mkdir(dirname(path), { recursive: true });
|
|
64
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
65
|
+
}
|
package/dist/guide.js
CHANGED
|
@@ -100,6 +100,26 @@ its typographic identity. Load them once (any one approach):
|
|
|
100
100
|
const artifactList = Object.keys(payload.artifacts)
|
|
101
101
|
.map((f) => `\`${f}\``)
|
|
102
102
|
.join(", ");
|
|
103
|
+
const layoutNames = Object.keys(doc.layouts ?? {});
|
|
104
|
+
const pagesSection = layoutNames.length > 0
|
|
105
|
+
? `
|
|
106
|
+
## Full pages (templates)
|
|
107
|
+
|
|
108
|
+
This system ships whole-page templates: ${layoutNames.map((n) => `\`${n}\``).join(", ")}.
|
|
109
|
+
|
|
110
|
+
Materialize one as a real file:
|
|
111
|
+
\`\`\`bash
|
|
112
|
+
synthesisui page ${slug} ${layoutNames[0]} # Next .tsx (default)
|
|
113
|
+
synthesisui page ${slug} ${layoutNames[0]} --target general # plain HTML
|
|
114
|
+
\`\`\`
|
|
115
|
+
It writes a **deterministic scaffold** using this DS's \`.ds-*\` classes + inline token vars. **Refine it
|
|
116
|
+
in place** — wire real data, split into components, swap the chart/icon/media placeholders — but keep the
|
|
117
|
+
\`data-ds="${slug}"\` wrapper and the \`.ds-*\` classes so it stays on-system. Run \`synthesisui init\` once
|
|
118
|
+
to set the target (next/general) and the output folder.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
`
|
|
122
|
+
: "";
|
|
103
123
|
const hasRules = (payload.rules?.length ?? 0) > 0;
|
|
104
124
|
const rulesNote = hasRules
|
|
105
125
|
? `
|
|
@@ -192,7 +212,7 @@ those, not the versioned ones. The pinned files for this version — ${artifactL
|
|
|
192
212
|
\`_synthesisui/ds/${slug}/v${version}/\`.
|
|
193
213
|
|
|
194
214
|
---
|
|
195
|
-
|
|
215
|
+
${pagesSection}
|
|
196
216
|
## Building with the system
|
|
197
217
|
|
|
198
218
|
**This system is for building real product UI** — pages, layouts, dashboards, whole flows.
|
package/dist/index.js
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
import { add } from "./commands/add.js";
|
|
3
3
|
import { advise } from "./commands/advise.js";
|
|
4
4
|
import { generate } from "./commands/generate.js";
|
|
5
|
+
import { init } from "./commands/init.js";
|
|
5
6
|
import { list } from "./commands/list.js";
|
|
6
7
|
import { login } from "./commands/login.js";
|
|
8
|
+
import { page } from "./commands/page.js";
|
|
7
9
|
import { RegistryError } from "./registry.js";
|
|
8
10
|
const HELP = `synthesisui — bring SynthesisUI design systems into your project
|
|
9
11
|
|
|
10
12
|
Usage:
|
|
11
|
-
synthesisui login [options]
|
|
12
|
-
synthesisui
|
|
13
|
-
synthesisui
|
|
14
|
-
synthesisui
|
|
15
|
-
synthesisui
|
|
13
|
+
synthesisui login [options] connect the CLI to your account (device-flow)
|
|
14
|
+
synthesisui init [options] write _synthesisui/config.json (target + pages dir)
|
|
15
|
+
synthesisui list [options] list the published design systems
|
|
16
|
+
synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
|
|
17
|
+
synthesisui page <slug> <template> materialize a whole page from a DS template
|
|
18
|
+
synthesisui advise "<value prop>" engagement-pattern proposals for this project (login required)
|
|
19
|
+
synthesisui generate "<desc>" generate a token-only component recipe for your DS (login required)
|
|
16
20
|
|
|
17
21
|
Options:
|
|
18
22
|
--registry <url> registry URL (or env SYNTHESISUI_REGISTRY_URL)
|
|
@@ -20,14 +24,18 @@ Options:
|
|
|
20
24
|
--version <n> install a specific version (default: latest)
|
|
21
25
|
--ds <slug> target design system for generate (default: the installed one)
|
|
22
26
|
--name <name> preferred component name for generate
|
|
27
|
+
--target <t> page/init target: next | general (default: next)
|
|
28
|
+
--out <path> output path for the generated page (default: <pagesDir>/<file>)
|
|
23
29
|
-h, --help this help
|
|
24
30
|
|
|
25
31
|
Examples:
|
|
26
32
|
synthesisui login
|
|
33
|
+
synthesisui init --target next
|
|
27
34
|
synthesisui list
|
|
28
35
|
synthesisui add halogen
|
|
29
36
|
synthesisui add halogen --version 3
|
|
30
|
-
synthesisui
|
|
37
|
+
synthesisui page halogen dashboard-sidebar
|
|
38
|
+
synthesisui page halogen landing --out app/page.tsx
|
|
31
39
|
synthesisui advise "habit-building app for tracking personal finances"
|
|
32
40
|
synthesisui generate "an upgrade banner with a title, message and a primary CTA"
|
|
33
41
|
`;
|
|
@@ -92,6 +100,33 @@ async function main() {
|
|
|
92
100
|
case "login":
|
|
93
101
|
await login({ registry });
|
|
94
102
|
break;
|
|
103
|
+
case "init": {
|
|
104
|
+
const target = typeof flags.target === "string" ? flags.target : undefined;
|
|
105
|
+
await init({ dir, target });
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case "page": {
|
|
109
|
+
const slug = args[0];
|
|
110
|
+
const template = args[1];
|
|
111
|
+
if (!slug || !template) {
|
|
112
|
+
console.error("error: provide slug and template — `synthesisui page <slug> <template>`");
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let version;
|
|
117
|
+
if (typeof flags.version === "string") {
|
|
118
|
+
version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
|
|
119
|
+
if (!Number.isInteger(version) || version < 1) {
|
|
120
|
+
console.error(`error: invalid --version "${flags.version}" — use an integer ≥ 1`);
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const target = typeof flags.target === "string" ? flags.target : undefined;
|
|
126
|
+
const out = typeof flags.out === "string" ? flags.out : undefined;
|
|
127
|
+
await page(slug, template, { registry, dir, out, target, version });
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
95
130
|
case "advise": {
|
|
96
131
|
const valueProp = args.join(" ").trim();
|
|
97
132
|
if (!valueProp) {
|
package/dist/registry.js
CHANGED
|
@@ -42,6 +42,27 @@ export async function fetchDesignSystem(base, slug, version) {
|
|
|
42
42
|
}
|
|
43
43
|
return (await res.json());
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Fetches a whole page generated from a DS template (`?page=&target=`). The
|
|
47
|
+
* server codegens it from `document.layouts[<template>]`; the CLI just writes it.
|
|
48
|
+
*/
|
|
49
|
+
export async function fetchPage(base, slug, template, target, version) {
|
|
50
|
+
const url = new URL(`${base}/api/registry/ds/${encodeURIComponent(slug)}`);
|
|
51
|
+
url.searchParams.set("page", template);
|
|
52
|
+
url.searchParams.set("target", target);
|
|
53
|
+
if (version != null)
|
|
54
|
+
url.searchParams.set("version", String(version));
|
|
55
|
+
const res = await request(url.toString());
|
|
56
|
+
if (res.status === 404) {
|
|
57
|
+
const body = (await res.json().catch(() => ({})));
|
|
58
|
+
throw new RegistryError(body.message ??
|
|
59
|
+
`No template "${template}" in "${slug}". Run \`synthesisui list\` and check the DS templates.`);
|
|
60
|
+
}
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
throw new RegistryError(`Registry responded ${res.status} while generating "${template}".`);
|
|
63
|
+
}
|
|
64
|
+
return (await res.json());
|
|
65
|
+
}
|
|
45
66
|
/**
|
|
46
67
|
* Calls the hosted advisor (`POST /api/ai/advisor`). Gated + metered server-side:
|
|
47
68
|
* 401 = not logged in, 429 = daily quota reached. Sends the Bearer token if present.
|