synthesisui 0.16.290 → 0.16.291

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,7 +1,8 @@
1
- import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { generateComponentFiles } from "../component-codegen.js";
4
4
  import { readProjectConfig, resolveRegistry } from "../config.js";
5
+ import { installedSlugs } from "../installed.js";
5
6
  import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
6
7
  import { postGenerate, RegistryError } from "../registry.js";
7
8
  /** PascalCase para o hint de import (course-card → CourseCard). */
@@ -20,17 +21,6 @@ async function readActiveVersion(root, slug) {
20
21
  }
21
22
  }
22
23
  /** Slugs materialized under `_synthesisui/ds/` in the project. */
23
- async function installedSlugs(root) {
24
- try {
25
- const entries = await readdir(join(root, "_synthesisui", "ds"), {
26
- withFileTypes: true,
27
- });
28
- return entries.filter((e) => e.isDirectory()).map((e) => e.name);
29
- }
30
- catch {
31
- return [];
32
- }
33
- }
34
24
  /**
35
25
  * Generates a token-only component recipe for the project's design system
36
26
  * (chat-gen PRO, hosted) and materializes it additively under
@@ -1,35 +1,13 @@
1
- import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { basename, join } from "node:path";
3
3
  import { generateComponentFiles } from "../component-codegen.js";
4
4
  import { readProjectConfig, resolveRegistry } from "../config.js";
5
+ import { installedSlugs } from "../installed.js";
5
6
  import { body, section, snippet } from "../output.js";
6
7
  import { reactMajorOf, readInstalledConvention, readInstalledScheme, } from "../project-facts.js";
7
8
  import { fetchComponent, postRefit, postSaveComponent, RegistryError, } from "../registry.js";
8
9
  /** Slugs INSTALLED under `_synthesisui/ds/` (a `.lock` marks a real install -
9
10
  * a folder holding only refit artifacts doesn't count). */
10
- async function installedSlugs(root) {
11
- try {
12
- const entries = await readdir(join(root, "_synthesisui", "ds"), {
13
- withFileTypes: true,
14
- });
15
- const slugs = [];
16
- for (const entry of entries) {
17
- if (!entry.isDirectory())
18
- continue;
19
- try {
20
- await access(join(root, "_synthesisui", "ds", entry.name, ".lock"));
21
- slugs.push(entry.name);
22
- }
23
- catch {
24
- // artifacts-only folder (e.g. a refit before `add`) - not installed
25
- }
26
- }
27
- return slugs;
28
- }
29
- catch {
30
- return [];
31
- }
32
- }
33
11
  /** True when the system is actually installed (tokens.css present). */
34
12
  async function isInstalled(root, slug) {
35
13
  try {
package/dist/index.js CHANGED
@@ -30,6 +30,7 @@ import { template } from "./commands/template.js";
30
30
  import { upgrade } from "./commands/upgrade.js";
31
31
  import { use } from "./commands/use.js";
32
32
  import { appendEvent } from "./doctor/ledger.js";
33
+ import { blueprintTarget, installedSlugs } from "./installed.js";
33
34
  import { RegistryError } from "./registry.js";
34
35
  /** Our own version, for pinning the hook and MCP commands we write into a
35
36
  * project. Read from the package we are running out of, so a pinned command
@@ -44,8 +45,10 @@ Usage - deterministic, FREE:
44
45
  synthesisui list [options] list the published design systems
45
46
  synthesisui list --mine your own systems, with their group
46
47
  synthesisui add <slug> [options] materialize a DS into _synthesisui/ds/<slug>/
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
48
+ synthesisui bp <name> bring one EXISTING blueprint in as YOUR <Pascal>.tsx
49
+ (the slug is optional - this repo's system answers for it)
50
+ synthesisui bp <slug> <name> the same, from another system
51
+ synthesisui blueprint | component the same command, spelled in full
49
52
  synthesisui template <slug> <name> materialize a whole page from a DS template
50
53
  (--as landing-home names the output - multi-page safe)
51
54
  synthesisui upgrade <slug> update an installed DS + regenerate your components + migration brief
@@ -468,14 +471,42 @@ async function main() {
468
471
  * que apelido nenhum.
469
472
  */
470
473
  case "bp":
474
+ case "blueprint":
471
475
  case "component": {
472
- const slug = args[0];
473
- const name = args[1];
474
- if (!slug || !name) {
475
- console.error("error: provide slug and component name - `synthesisui component <slug> <name>`");
476
+ /**
477
+ * O SLUG É OPCIONAL, e o repositório responde por ele.
478
+ *
479
+ * `synthesisui bp card` num repositório que tem UM sistema instalado é a forma que a pessoa
480
+ * escreve naturalmente - ela está dentro do projeto, o sistema dela é aquele, e repetir o nome
481
+ * dele é a plataforma pedindo uma informação que ela já tem no disco.
482
+ *
483
+ * COM DOIS ARGUMENTOS o primeiro é o slug, que é o comportamento de sempre e o que permite
484
+ * trazer um blueprint de OUTRO sistema para cá.
485
+ *
486
+ * E O CASO AMBÍGUO É RECUSADO EM VEZ DE ADIVINHADO: um argumento só que é exatamente o nome do
487
+ * sistema instalado - `synthesisui bp codelevel-ds` - é o erro que o comentário acima registra,
488
+ * e ele tem duas leituras opostas ("instale este sistema" / "materialize um blueprint chamado
489
+ * codelevel-ds"). Escolher uma em silêncio seria acertar metade das vezes.
490
+ */
491
+ const root = dir ?? process.cwd();
492
+ const installed = await installedSlugs(root);
493
+ const target = blueprintTarget({
494
+ args: [args[0], args[1]],
495
+ local: installed.length === 1 ? installed[0] : null,
496
+ installed,
497
+ });
498
+ if ("error" in target) {
499
+ console.error(target.error === "ambiguous"
500
+ ? `error: "${target.name}" is the system installed here, not a blueprint name - say which blueprint you want: \`synthesisui bp <name>\``
501
+ : target.error === "no-name"
502
+ ? "error: provide a blueprint name - `synthesisui bp <name>`, or `synthesisui bp <slug> <name>` for another system"
503
+ : target.error === "no-system"
504
+ ? "error: no design system installed here - run `synthesisui add <slug>` first, or name one: `synthesisui bp <slug> <name>`"
505
+ : `error: ${installed.length} systems installed here (${installed.join(", ")}) - say which: \`synthesisui bp <slug> <name>\``);
476
506
  process.exitCode = 1;
477
507
  return;
478
508
  }
509
+ const { slug, name } = target;
479
510
  let version;
480
511
  if (typeof flags.version === "string") {
481
512
  version = Number.parseInt(flags.version.replace(/^v/i, ""), 10);
@@ -0,0 +1,68 @@
1
+ import { access, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ /**
4
+ * QUAIS SISTEMAS ESTÃO INSTALADOS NESTE REPOSITÓRIO - um lugar só, e a resposta certa.
5
+ *
6
+ * ESTA FUNÇÃO EXISTIA DUAS VEZES, em `refit.ts` e `generate.ts`, e elas discordavam: a primeira
7
+ * exigia o `.lock` - o arquivo que prova que o `add` rodou - e a segunda listava a pasta. Uma pasta
8
+ * `ds/<slug>/` existe assim que qualquer comando escreve um artefato ali, então o `generate`
9
+ * enxergava como instalado um sistema que ainda não tinha nada materializado, e a mensagem que ele
10
+ * daria ao cliente seria sobre um sistema que ele não pode usar.
11
+ *
12
+ * O `.lock` é o portão porque ele é o que o `add` escreve por último: se ele está lá, os arquivos
13
+ * estão. Ver `RootLock` em `add.ts`.
14
+ *
15
+ * E COM ISTO O SLUG PASSA A SER OPCIONAL nos comandos que agem sobre um sistema. Quem está num
16
+ * repositório que tem um sistema instalado não deveria precisar repetir o nome dele - o repositório
17
+ * já sabe.
18
+ */
19
+ export async function installedSlugs(root) {
20
+ try {
21
+ const dsDir = join(root, "_synthesisui", "ds");
22
+ const entries = await readdir(dsDir, { withFileTypes: true });
23
+ const slugs = [];
24
+ for (const entry of entries) {
25
+ if (!entry.isDirectory())
26
+ continue;
27
+ try {
28
+ await access(join(dsDir, entry.name, ".lock"));
29
+ slugs.push(entry.name);
30
+ }
31
+ catch {
32
+ // pasta de artefato (um refit antes do `add`, por exemplo) - não é um install
33
+ }
34
+ }
35
+ return slugs.sort();
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ /**
42
+ * O SISTEMA DESTE REPOSITÓRIO, quando há exatamente um - e `null` quando a pergunta não tem resposta.
43
+ *
44
+ * Nenhum instalado e dois instalados são situações diferentes com a mesma consequência: a plataforma
45
+ * não pode escolher por ele. Escolher o primeiro em ordem alfabética seria a pior forma de errar -
46
+ * silenciosa, e correta na metade dos casos. Quem chama diz o que fazer com o `null`, e o que ele
47
+ * tem a dizer é diferente nos dois casos, então `installedSlugs` continua disponível para nomear os
48
+ * candidatos na mensagem.
49
+ */
50
+ export async function theInstalledSlug(root) {
51
+ const slugs = await installedSlugs(root);
52
+ return slugs.length === 1 ? slugs[0] : null;
53
+ }
54
+ export function blueprintTarget(input) {
55
+ const [first, second] = input.args;
56
+ if (first && second)
57
+ return { slug: first, name: second };
58
+ const name = first;
59
+ if (!name)
60
+ return { error: "no-name" };
61
+ if (input.local && name === input.local)
62
+ return { error: "ambiguous", name };
63
+ if (!input.local)
64
+ return input.installed.length > 1
65
+ ? { error: "many-systems" }
66
+ : { error: "no-system" };
67
+ return { slug: input.local, name };
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.290",
3
+ "version": "0.16.291",
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": {