synthesisui 0.16.385 → 0.16.386

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.
@@ -169,7 +169,29 @@ export async function component(slug, name, opts) {
169
169
  */
170
170
  const theirs = res.recipe?.api;
171
171
  const already = theirs?.file && existsSync(join(root, theirs.file)) ? theirs : null;
172
- if (already && !opts.force) {
172
+ /**
173
+ * UM NOME PRÓPRIO JÁ É A RESPOSTA PARA A COLISÃO - achado do dono em 06/09, e é a SEGUNDA volta
174
+ * deste mesmo defeito.
175
+ *
176
+ * O QUE ELE FEZ: `component codelevel button --as=ButtonSample`, no repositório de onde o
177
+ * sistema foi importado. A recusa disparou, disse *"writing another one would leave you with
178
+ * two"* e ofereceu `--force` - com um `--as` na linha de comando, ignorado.
179
+ *
180
+ * OS DOIS ERROS DE UMA VEZ. O primeiro é a decisão: este guard responde "você já tem" sem
181
+ * nunca ler `opts.as`, embora `localName` - a função que resolve o nome - fique 70 linhas
182
+ * abaixo. O segundo é a frase: com um nome próprio NÃO ficariam dois `Button`; ficaria o dele
183
+ * e um `ButtonSample`. E a única saída oferecida, `--force`, escreve com o nome ORIGINAL - a
184
+ * única opção que de fato causaria a duplicação que a mensagem existe para evitar.
185
+ *
186
+ * A PRIMEIRA VOLTA está registrada em `component-as.spec.ts`: a recusa IMPRIMIA `--as` e a
187
+ * flag não existia em `ComponentOptions`. A flag nasceu; o guard não passou a consultá-la.
188
+ *
189
+ * E A POPULAÇÃO É O CLIENTE-ALVO INTEIRO. Medido na base viva em 06/09: no `codelevel`, **64
190
+ * dos 66** componentes (97%) apontam para um arquivo que existe no repositório dele - porque o
191
+ * sistema NASCEU dali. Nos 25 sistemas gerados aqui, **0 de ~45**. O comando funcionava para
192
+ * quem não é o cliente e recusava para quem é.
193
+ */
194
+ if (already && !opts.force && !opts.as?.trim()) {
173
195
  console.log(section("You already have this component"));
174
196
  console.log(body(`\`${already.name}\` lives in ${already.file} - writing another one would leave you with two.`));
175
197
  if (already.from) {
@@ -180,14 +202,44 @@ export async function component(slug, name, opts) {
180
202
  console.log(body(`It is not re-exported from that folder's entry, so import it by path.`));
181
203
  }
182
204
  console.log("");
183
- console.log(body("The recipe and the compiled CSS are on disk either way - only the .tsx was not written. Pass --force to write it anyway."));
205
+ /**
206
+ * A SAÍDA QUE NÃO DUPLICA VEM PRIMEIRO, e ela é a que a pessoa quer em quase todo caso.
207
+ *
208
+ * `--force` continua existindo e continua last: ele escreve com o nome DELE, que é a única
209
+ * opção que realmente deixa dois. Oferecê-lo como única saída era mandar a pessoa fazer
210
+ * exatamente o que a frase acima diz para evitar.
211
+ */
212
+ console.log(body(`To see what this recipe produces without touching your file, give it a name of its own:`));
213
+ console.log("");
214
+ console.log(snippet([
215
+ `npx synthesisui@latest component ${slug} ${res.name} --as ${res.name}-sample`,
216
+ ]));
217
+ console.log("");
218
+ console.log(body("The recipe and the compiled CSS are on disk either way - only the .tsx was not written. Pass --force to overwrite yours instead."));
184
219
  return;
185
220
  }
186
- const clash = await findCollision(root, config.componentsDir, res.name, pascalName);
221
+ /**
222
+ * A COLISÃO SE MEDE PELO NOME QUE VAI SER ESCRITO, e não pelo do blueprint.
223
+ *
224
+ * A metade que o guard acima não resolvia sozinha: passado o primeiro guard com `--as`, este
225
+ * procurava por `button`/`Button` - o nome do BLUEPRINT - e achava o componente dele de novo.
226
+ * Consertar um e deixar o outro deixaria o `--as` aceito e barrado uma linha depois, o que é
227
+ * pior que recusar de cara: a pessoa faz o que a tela mandou e recebe outra recusa.
228
+ *
229
+ * `local` já era calculado, 70 linhas abaixo, para decidir a PASTA. Ele sobe para cá porque é
230
+ * a mesma pergunta - qual nome este comando vai ocupar - e duas respostas para ela é o defeito.
231
+ */
232
+ const local = localName(res.name, opts.as);
233
+ const localPascal = local
234
+ .split(/[^a-zA-Z0-9]+/)
235
+ .filter(Boolean)
236
+ .map((p) => p[0].toUpperCase() + p.slice(1))
237
+ .join("");
238
+ const clash = await findCollision(root, config.componentsDir, local, localPascal);
187
239
  if (!opts.force && (clash.exported || (clash.file && !clash.ours))) {
188
240
  console.log(section("This name is already taken in your project"));
189
241
  if (clash.exported) {
190
- console.log(body(`\`${pascalName}\` is already exported from ${clash.exported}.`));
242
+ console.log(body(`\`${localPascal}\` is already exported from ${clash.exported}.`));
191
243
  }
192
244
  if (clash.file && !clash.ours) {
193
245
  console.log(body(`${clash.file} exists and we did not write it.`));
@@ -211,7 +263,7 @@ export async function component(slug, name, opts) {
211
263
  * componente. O CSS não muda de nome: a receita é a do blueprint, e as classes dela são
212
264
  * `.ds-<blueprint>` - o componente novo veste o mesmo desenho, com o nome dele no arquivo.
213
265
  */
214
- const local = localName(res.name, opts.as);
266
+ /** Ver o cálculo acima: uma resposta só para "qual nome este comando ocupa". */
215
267
  const compDir = join(root, config.componentsDir, local);
216
268
  await mkdir(compDir, { recursive: true });
217
269
  let filenames;
@@ -307,7 +359,19 @@ export async function component(slug, name, opts) {
307
359
  console.log(body(`(If you haven't installed the system yet, run: synthesisui add ${slug})`));
308
360
  console.log(section("Use it"));
309
361
  if (!opts.artifactsOnly && config.target === "next") {
310
- const pascalName = res.name
362
+ /**
363
+ * O EXEMPLO APONTA PARA O QUE FOI ESCRITO - a terceira metade do defeito do `--as` (06/09).
364
+ *
365
+ * Este bloco derivava o nome e o caminho de `res.name`, o nome do BLUEPRINT. Com
366
+ * `--as button-sample`, o arquivo vai para `components/button-sample/` e o exemplo mandava
367
+ * importar de `components/button` - um caminho que não existe. Um comando que escreve certo e
368
+ * instrui errado é pior que um que recusa: a pessoa copia a linha e o build quebra.
369
+ *
370
+ * `localName` é a mesma função dos dois guards acima. Uma resposta só para o nome, do primeiro
371
+ * guard até a última linha impressa.
372
+ */
373
+ const usedName = localName(res.name, opts.as);
374
+ const pascalName = usedName
311
375
  .split(/[^a-zA-Z0-9]+/)
312
376
  .filter(Boolean)
313
377
  .map((p) => p[0].toUpperCase() + p.slice(1))
@@ -318,7 +382,7 @@ export async function component(slug, name, opts) {
318
382
  ? ` ${firstAxis[0]}="${Object.keys(firstAxis[1])[0]}"`
319
383
  : "";
320
384
  console.log(snippet([
321
- `import { ${pascalName} } from "@/${config.componentsDir}/${res.name}";`,
385
+ `import { ${pascalName} } from "@/${config.componentsDir}/${usedName}";`,
322
386
  "",
323
387
  `<${pascalName}${exampleProp} />`,
324
388
  ]));
@@ -327,7 +391,7 @@ export async function component(slug, name, opts) {
327
391
  console.log("");
328
392
  console.log(body("Or ask your agent:"));
329
393
  console.log(snippet([
330
- `"Use the ${pascalName} component from ${config.componentsDir}/${res.name} (SynthesisUI ${slug})."`,
394
+ `"Use the ${pascalName} component from ${config.componentsDir}/${usedName} (SynthesisUI ${slug})."`,
331
395
  ]));
332
396
  }
333
397
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.385",
3
+ "version": "0.16.386",
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": {