synthesisui 0.16.279 → 0.16.281

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.
@@ -1,4 +1,4 @@
1
- import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
1
+ import { access, mkdir, readdir, readFile, rm, writeFile, } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { syncClaudeMd } from "../claude-md.js";
4
4
  import { readProjectConfig, resolveRegistry } from "../config.js";
@@ -126,16 +126,55 @@ async function readRootLock(path) {
126
126
  }
127
127
  const exists = (path) => access(path).then(() => true, () => false);
128
128
  /**
129
- * Where the app's routes live: the configured pagesDir at the project root
130
- * (`app/`) or nested under `src/` (`src/app/` - create-next-app's other
131
- * layout). Null when neither exists (instructions-only mode).
129
+ * ONDE AS ROTAS DELE MORAM DE VERDADE - e num monorepo elas não moram na raiz.
130
+ *
131
+ * Olhava `app/` e `src/app/` na raiz. O `codelevel-monorepo` tem `apps/web/app` e
132
+ * `apps/landing/app`, então a busca voltava null, o `appDir` caía no default `"app"`, e TODO caminho
133
+ * impresso no setup apontava para uma pasta que não existe: `app/globals.css`, `app/layout.tsx`,
134
+ * `app/fonts.ts`. O cliente lê três instruções e nenhuma serve para o repositório dele.
135
+ *
136
+ * O que faz uma pasta ser raiz de app é o `layout` do App Router morar dentro dela - por isso o
137
+ * `apps/api` do Nest, que também é um workspace, não entra na lista.
138
+ *
139
+ * DEVOLVE TODAS, em ordem determinística: o setup é "once per app" e num monorepo isso é literal.
140
+ * Escolher uma calada seria acertar uma e errar a outra sem dizer qual.
132
141
  */
133
- async function detectAppDir(root, pagesDir) {
134
- if (await exists(join(root, pagesDir)))
135
- return pagesDir;
136
- if (await exists(join(root, "src", pagesDir)))
137
- return `src/${pagesDir}`;
138
- return null;
142
+ export async function detectAppDirs(root, pagesDir) {
143
+ const isAppRoot = async (dir) => {
144
+ if (!(await exists(join(root, dir))))
145
+ return false;
146
+ for (const ext of ["tsx", "jsx", "ts", "js"])
147
+ if (await exists(join(root, dir, `layout.${ext}`)))
148
+ return true;
149
+ return false;
150
+ };
151
+ const here = [pagesDir, `src/${pagesDir}`];
152
+ const nested = [];
153
+ for (const group of ["apps", "packages"]) {
154
+ let entries;
155
+ try {
156
+ entries = await readdir(join(root, group));
157
+ }
158
+ catch {
159
+ continue;
160
+ }
161
+ for (const entry of entries.sort())
162
+ nested.push(`${group}/${entry}/${pagesDir}`, `${group}/${entry}/src/${pagesDir}`);
163
+ }
164
+ const found = [];
165
+ for (const dir of [...here, ...nested])
166
+ if (await isAppRoot(dir))
167
+ found.push(dir);
168
+ /**
169
+ * A RAIZ QUE EXISTE MAS NÃO TEM LAYOUT ainda é o melhor palpite de um app avulso - é o caso de um
170
+ * `create-next-app` no meio de uma migração. Sem isto, um projeto de app único perderia a detecção
171
+ * que já funcionava.
172
+ */
173
+ if (found.length === 0)
174
+ for (const dir of here)
175
+ if (await exists(join(root, dir)))
176
+ return [dir];
177
+ return found;
139
178
  }
140
179
  /**
141
180
  * Materializes a published DS into `_synthesisui/ds/<slug>/v<version>/`, points
@@ -355,10 +394,19 @@ export async function add(slug, opts) {
355
394
  // Where the app actually lives (app/ vs src/app/) drives every printed
356
395
  // path: the @import depth, the layout example, and where fonts.ts lands.
357
396
  const projectConfig = await readProjectConfig(projectRoot);
358
- const appDir = (await detectAppDir(projectRoot, projectConfig.pagesDir)) ??
359
- projectConfig.pagesDir;
397
+ const appDirs = await detectAppDirs(projectRoot, projectConfig.pagesDir);
398
+ /** Nenhuma pasta encontrada: os caminhos viram exemplo, e o texto abaixo diz isso. */
399
+ const appDir = appDirs[0] ?? projectConfig.pagesDir;
400
+ const appDirFound = appDirs.length > 0;
360
401
  const importPrefix = "../".repeat(appDir.split("/").length);
361
402
  console.log(section("One-time setup (once per app)"));
403
+ /**
404
+ * "ONCE PER APP" É LITERAL NUM MONOREPO, e calar os outros apps entrega metade da fiação. Os
405
+ * caminhos abaixo são de um só; sem esta linha o cliente conclui que o repositório inteiro está
406
+ * ligado quando só o primeiro está.
407
+ */
408
+ if (appDirs.length > 1)
409
+ console.log(line(`(${appDirs.length} apps here: ${appDirs.join(", ")} - the paths below are for ${appDir}; repeat for the others.)`));
362
410
  console.log(line(`1. Import the system in your GLOBAL stylesheet, e.g. ${appDir}/globals.css`));
363
411
  console.log(line(` (the path is relative to that file - hence the leading ${importPrefix}):`));
364
412
  console.log("");
@@ -434,7 +482,14 @@ export async function add(slug, opts) {
434
482
  console.log(line(" Finish the wiring with two small edits:"));
435
483
  }
436
484
  else {
437
- console.log(line(`3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`));
485
+ console.log(line(appDirFound
486
+ ? `3. Load the type via next/font (${appDir}/fonts.ts already exists - left untouched; it should export:)`
487
+ : /**
488
+ * A FRASE DIZIA "already exists" PARA DOIS ESTADOS DIFERENTES, e um deles é o contrário
489
+ * disso: não achei a pasta do app. No monorepo dele o arquivo não existia e o terminal
490
+ * dizia que existia - o cliente não tem como saber que os caminhos acima são chute.
491
+ */
492
+ `3. Load the type via next/font (I could not find your app folder from here, so the paths above are examples - create ${appDir}/fonts.ts wherever your app router lives):`));
438
493
  console.log("");
439
494
  console.log(snippet(nextFonts.fontsFile));
440
495
  console.log("");
@@ -117,7 +117,7 @@ export async function sync(opts) {
117
117
  console.log(body(measured
118
118
  ? "This repo has a census but no installed system, so there is nowhere to send it. `import` created the system on the platform; bringing it back is what tells sync which one this repo feeds:"
119
119
  : "No installed system here, so there is nowhere to sync to. Install one first:"));
120
- console.log(body(paint.blue(" npx synthesisui@latest bp <slug> # or: add <slug>")));
120
+ console.log(body(paint.blue(" npx synthesisui@latest add <slug>")));
121
121
  console.log(body(paint.faint(" npx synthesisui@latest list --mine # the slugs you own")));
122
122
  return;
123
123
  }
package/dist/fonts.js CHANGED
@@ -12,6 +12,22 @@ const GENERIC_FAMILIES = new Set([
12
12
  "inherit",
13
13
  "initial",
14
14
  ]);
15
+ /**
16
+ * A PRIMEIRA FAMÍLIA DE UMA PILHA - `"Geist Mono", ui-monospace, monospace` é `Geist Mono`.
17
+ *
18
+ * O DOCUMENTO GUARDA A PILHA INTEIRA DE PROPÓSITO: é o que o código dele declara, e a lei 12 diz que
19
+ * o que ele declarou vence. O que não pode é a pilha virar NOME - e era o que acontecia aqui. O `add`
20
+ * do codelevel-ui imprimiu, em 22/08, um `fonts.ts` que não compila:
21
+ *
22
+ * export const mono = "Geist_Mono",_ui-monospace,_monospace({ // ← não é JavaScript
23
+ *
24
+ * e um href com `family="Geist+Mono",+ui-monospace,+monospace`, que o Google Fonts não resolve. Duas
25
+ * das quatro vagas do sistema dele saíam quebradas, e o cliente só descobriria colando o snippet.
26
+ *
27
+ * Um download pede UM nome de família; a pilha é o fallback do navegador e não viaja na URL. A mesma
28
+ * regra já existia em `read-unread.ts` na plataforma - existia num lugar e faltava neste.
29
+ */
30
+ const firstFamily = (value) => (value.split(",")[0] ?? "").trim().replace(/^["']|["']$/g, "");
15
31
  /**
16
32
  * TODA VAGA DE FONTE DO DOCUMENTO, dedup e sem os genéricos.
17
33
  *
@@ -36,7 +52,7 @@ export function customFontFamilies(families) {
36
52
  const seen = new Set();
37
53
  const names = [];
38
54
  for (const slot of familySlots(families)) {
39
- const name = families[slot]?.trim();
55
+ const name = firstFamily(families[slot] ?? "");
40
56
  if (!name)
41
57
  continue;
42
58
  const key = name.toLowerCase();
@@ -68,17 +84,17 @@ export function googleFontsHref(families) {
68
84
  export function nextFontSnippet(families, slug, appDir = "app") {
69
85
  /** Toda vaga que o documento tem, não só as nossas três - ver `customFontFamilies`. */
70
86
  const roles = familySlots(families).filter((role) => {
71
- const name = families[role]?.trim();
87
+ const name = firstFamily(families[role] ?? "");
72
88
  return name && !GENERIC_FAMILIES.has(name.toLowerCase());
73
89
  });
74
90
  if (roles.length === 0)
75
91
  return null;
76
- const importName = (name) => name.trim().replace(/ /g, "_");
92
+ const importName = (name) => firstFamily(name).replace(/ /g, "_");
77
93
  const seen = new Map(); // family name -> const name
78
94
  const importNames = [];
79
95
  const consts = [];
80
96
  for (const role of roles) {
81
- const name = (families[role] ?? "").trim();
97
+ const name = firstFamily(families[role] ?? "");
82
98
  if (!seen.has(name)) {
83
99
  seen.set(name, role);
84
100
  importNames.push(importName(name));
@@ -101,13 +117,13 @@ export function nextFontSnippet(families, slug, appDir = "app") {
101
117
  ...consts,
102
118
  ];
103
119
  const roleVar = (role) => {
104
- const name = (families[role] ?? "").trim();
120
+ const name = firstFamily(families[role] ?? "");
105
121
  return `--font-ds-${seen.get(name)}`;
106
122
  };
107
123
  const layout = [
108
124
  `// ${appDir}/layout.tsx`,
109
- `import { ${[...new Set(roles.map((r) => seen.get((families[r] ?? "").trim())))].join(", ")} } from "./fonts";`,
110
- `<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get((families[r] ?? "").trim())}.variable}`))].join(" ")}\`}>`,
125
+ `import { ${[...new Set(roles.map((r) => seen.get(firstFamily(families[r] ?? ""))))].join(", ")} } from "./fonts";`,
126
+ `<body data-ds="${slug}" className={\`${[...new Set(roles.map((r) => `\${${seen.get(firstFamily(families[r] ?? ""))}.variable}`))].join(" ")}\`}>`,
111
127
  ];
112
128
  const css = [
113
129
  `/* ${appDir}/globals.css - AFTER the tokens.css import */`,
package/dist/index.js CHANGED
@@ -44,8 +44,8 @@ Usage - deterministic, FREE:
44
44
  synthesisui list [options] list the published design systems
45
45
  synthesisui list --mine your own systems, with their group
46
46
  synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
47
- synthesisui bp <slug> [options] the same command, short for blueprint
48
47
  synthesisui component <slug> <name> bring one EXISTING component in as YOUR <Pascal>.tsx
48
+ synthesisui bp <slug> <name> the same command, short for blueprint
49
49
  synthesisui template <slug> <name> materialize a whole page from a DS template
50
50
  (--as landing-home names the output - multi-page safe)
51
51
  synthesisui upgrade <slug> update an installed DS + regenerate your components + migration brief
@@ -349,15 +349,6 @@ async function main() {
349
349
  */
350
350
  await list({ registry, mine: flags.mine === true });
351
351
  break;
352
- /**
353
- * `bp` É O MESMO COMANDO QUE `add`, e o apelido existe porque `blueprint` é o nome público do
354
- * que a plataforma produz.
355
- *
356
- * Apelido e não substituição: `add` está escrito em toda mensagem que o CLI já imprimiu, em
357
- * todo GUIDE.md já materializado e na cabeça de quem usa. Trocá-lo faria um comando que a pessoa
358
- * digitou ontem parar de existir hoje.
359
- */
360
- case "bp":
361
352
  case "add": {
362
353
  const slug = args[0];
363
354
  if (!slug) {
@@ -463,6 +454,20 @@ async function main() {
463
454
  });
464
455
  break;
465
456
  }
457
+ /**
458
+ * `bp` É O MESMO COMANDO QUE `component`, e é AQUI que ele pertence.
459
+ *
460
+ * O apelido nasceu em 22/08 pendurado no `add`, e estava errado: `add` instala o SISTEMA
461
+ * inteiro - tokens, receitas, GUIDE - e `blueprint` é um COMPONENTE. A tela do Studio lista
462
+ * `componentNames + blockNames` sob a frase "No blueprints yet", e a ferramenta MCP
463
+ * `add_blueprint` materializa exatamente um.
464
+ *
465
+ * O custo do erro apareceu na primeira vez que alguém leu: o dono viu
466
+ * `npx synthesisui bp codelevel-ui` e entendeu "gerar um componente novo", que é o que a
467
+ * palavra promete - e o comando instalava o sistema. Um apelido que promete outra coisa é pior
468
+ * que apelido nenhum.
469
+ */
470
+ case "bp":
466
471
  case "component": {
467
472
  const slug = args[0];
468
473
  const name = args[1];
@@ -128,7 +128,7 @@
128
128
  * é sempre o bump deste PR - nunca o número que o `package.json` já carrega, porque alguém pode
129
129
  * publicar no meio.
130
130
  */
131
- export const MATERIALISER_SINCE = "0.16.278";
131
+ export const MATERIALISER_SINCE = "0.16.281";
132
132
  /**
133
133
  * A ÚLTIMA VERSÃO EM QUE O QUE O HOOK RODA MUDOU.
134
134
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.279",
3
+ "version": "0.16.281",
4
4
  "description": "Bring SynthesisUI design systems into any project - tokens, typed components, whole pages and an agent-ready CLAUDE.md manifest.",
5
5
  "type": "module",
6
6
  "bin": {