synthesisui 0.16.250 → 0.16.251

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,79 @@
1
+ /**
2
+ * A LINHA DE COMANDO, DESMONTADA - e o único lugar que sabe fazer isso.
3
+ *
4
+ * Este parser vivia dentro do `index.ts`, privado, e por isso nunca foi
5
+ * exercido por um teste. O custo apareceu inteiro num defeito: `login --force`
6
+ * era lido de `args` - a lista de POSICIONAIS, de onde este parser já tirou
7
+ * toda `--flag` - então a flag anunciada no help nunca chegou ao comando, e a
8
+ * resposta era sugerir o comando que a pessoa acabou de rodar.
9
+ *
10
+ * Separado, ele é testável, e `boolFlag` fecha a porta: quem lê uma flag
11
+ * booleana pede pelo NOME dela a quem tem a resposta, e não procura a string
12
+ * numa lista que por construção não a contém.
13
+ */
14
+ /**
15
+ * A REPEATED FLAG ACCUMULATES rather than overwriting.
16
+ *
17
+ * `--usage apps/web --usage apps/admin` is the natural way to say two, and keeping
18
+ * only the last would drop one silently - which is the failure mode this codebase
19
+ * refuses everywhere else. Every existing flag is passed once and still arrives as
20
+ * a string, so nothing that reads `typeof flags.x === "string"` changes.
21
+ */
22
+ export function parseFlags(argv) {
23
+ const positionals = [];
24
+ const flags = {};
25
+ const put = (key, value) => {
26
+ const had = flags[key];
27
+ if (had === undefined) {
28
+ flags[key] = value;
29
+ return;
30
+ }
31
+ if (typeof value !== "string")
32
+ return;
33
+ flags[key] = [
34
+ ...(Array.isArray(had) ? had : typeof had === "string" ? [had] : []),
35
+ value,
36
+ ];
37
+ };
38
+ for (let i = 0; i < argv.length; i++) {
39
+ const arg = argv[i];
40
+ if (arg === "-h" || arg === "--help") {
41
+ flags.help = true;
42
+ }
43
+ else if (arg.startsWith("--")) {
44
+ const body = arg.slice(2);
45
+ const eq = body.indexOf("=");
46
+ if (eq !== -1) {
47
+ // `--key=value` form (e.g. --out=templates/page.tsx)
48
+ put(body.slice(0, eq), body.slice(eq + 1));
49
+ }
50
+ else {
51
+ // `--key value` form
52
+ const next = argv[i + 1];
53
+ if (next !== undefined && !next.startsWith("--")) {
54
+ put(body, next);
55
+ i++;
56
+ }
57
+ else {
58
+ put(body, true);
59
+ }
60
+ }
61
+ }
62
+ else {
63
+ positionals.push(arg);
64
+ }
65
+ }
66
+ return { positionals, flags };
67
+ }
68
+ /**
69
+ * UMA FLAG BOOLEANA, LIDA PELO NOME - `true` só quando ela está mesmo lá.
70
+ *
71
+ * `--yes` sozinha chega como `true`; `--yes` seguida de um posicional chega
72
+ * como a string daquele posicional, porque o parser não sabe quais flags levam
73
+ * valor. Nos dois casos a pessoa DISSE a flag, e é isso que um booleano
74
+ * responde - tratar `--yes algo` como não-dito seria obedecer pela metade.
75
+ */
76
+ export function boolFlag(flags, name) {
77
+ const v = flags[name];
78
+ return v === true || typeof v === "string" || Array.isArray(v);
79
+ }
@@ -53,7 +53,8 @@ export async function login(opts) {
53
53
  if (existing && sameRegistry(existing.registry, base)) {
54
54
  console.log("");
55
55
  console.log(`✓ Already signed in to ${base} on this machine.`);
56
- console.log(" Run `synthesisui login --force` to sign in as someone else.");
56
+ console.log(" Run `synthesisui login --force` to sign in as someone else,");
57
+ console.log(" or `synthesisui logout` to sign out of this machine.");
57
58
  return;
58
59
  }
59
60
  }
@@ -0,0 +1,26 @@
1
+ import { clearCredentials, credentialsPath, readCredentials, } from "../config.js";
2
+ /**
3
+ * SAIR DA CONTA NESTA MÁQUINA - o comando que faltava do par.
4
+ *
5
+ * O CLI sabia entrar e não sabia sair. Quem precisava trocar de conta era
6
+ * mandado apagar `~/.synthesisui/credentials.json` na mão - e uma instrução que
7
+ * termina em `rm` num diretório de configuração é a que faz alguém apagar a
8
+ * pasta errada.
9
+ *
10
+ * Ele diz de QUAL host saiu, porque a credencial guarda o registry que a
11
+ * emitiu: sair de um localhost e achar que saiu da produção é o mesmo mal
12
+ * entendido que fez o token de dev ser enviado para o site em 31/07.
13
+ */
14
+ export async function logout() {
15
+ const existing = await readCredentials();
16
+ const had = await clearCredentials();
17
+ console.log("");
18
+ if (!had) {
19
+ console.log("✓ You are not signed in on this machine - nothing to do.");
20
+ console.log(" Run `synthesisui login` to connect the CLI to an account.");
21
+ return;
22
+ }
23
+ console.log(`✓ Signed out${existing?.registry ? ` of ${existing.registry}` : ""} on this machine.`);
24
+ console.log(` Removed ${credentialsPath}`);
25
+ console.log(" Run `synthesisui login` to sign in as someone else.");
26
+ }
package/dist/config.js CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  /**
@@ -76,6 +76,30 @@ export async function writeToken(token, registry) {
76
76
  mode: 0o600,
77
77
  });
78
78
  }
79
+ /**
80
+ * SAIR DA MÁQUINA - o outro lado da porta que o `login` abre.
81
+ *
82
+ * Existir era o mínimo: até aqui a única forma de encerrar a sessão era apagar
83
+ * `~/.synthesisui/credentials.json` à mão, e um produto que só sabe entrar
84
+ * obriga a pessoa a mexer no disco para trocar de conta.
85
+ *
86
+ * O arquivo inteiro vai embora, e não só o campo `token`: um arquivo sem token
87
+ * é indistinguível de nenhum arquivo para quem lê (`readCredentials` devolve
88
+ * `null` nos dois casos) e ainda deixaria o host da sessão antiga gravado.
89
+ *
90
+ * Devolve `false` quando não havia nada - a diferença entre "saí" e "você já
91
+ * estava fora" é a única coisa que o comando tem para dizer.
92
+ */
93
+ export async function clearCredentials() {
94
+ const existing = await readCredentials();
95
+ try {
96
+ await rm(credentialsPath, { force: true });
97
+ }
98
+ catch {
99
+ // já não existe, ou o disco recusou - o estado final é o mesmo
100
+ }
101
+ return existing !== null;
102
+ }
79
103
  /** Project-level config (committed): `<root>/_synthesisui/config.json`. */
80
104
  export const DEFAULT_CONFIG = {
81
105
  target: "next",
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
+ import { boolFlag, parseFlags } from "./cli-flags.js";
4
5
  import { absorb } from "./commands/absorb.js";
5
6
  import { add } from "./commands/add.js";
6
7
  import { adopt } from "./commands/adopt.js";
@@ -18,6 +19,7 @@ import { runImport } from "./commands/import.js";
18
19
  import { init } from "./commands/init.js";
19
20
  import { list } from "./commands/list.js";
20
21
  import { login } from "./commands/login.js";
22
+ import { logout } from "./commands/logout.js";
21
23
  import { mcp } from "./commands/mcp.js";
22
24
  import { refit } from "./commands/refit.js";
23
25
  import { request } from "./commands/request.js";
@@ -37,6 +39,7 @@ const HELP = `synthesisui - bring SynthesisUI design systems into your project
37
39
 
38
40
  Usage - deterministic, FREE:
39
41
  synthesisui login [options] connect the CLI to your account (device-flow; --force to switch accounts)
42
+ synthesisui logout sign out of this machine (removes the saved token)
40
43
  synthesisui init [options] write _synthesisui/config.json (target, dirs); --ds to bring one in
41
44
  synthesisui list [options] list the published design systems
42
45
  synthesisui list --mine your own systems, with their group
@@ -135,61 +138,6 @@ Examples:
135
138
  synthesisui advise "habit-building app for tracking personal finances"
136
139
  synthesisui generate "an upgrade banner with a title, message and a primary CTA"
137
140
  `;
138
- /** Extracts simple `--flag value` pairs and the remaining positionals. */
139
- /**
140
- * A REPEATED FLAG ACCUMULATES rather than overwriting.
141
- *
142
- * `--usage apps/web --usage apps/admin` is the natural way to say two, and keeping
143
- * only the last would drop one silently - which is the failure mode this codebase
144
- * refuses everywhere else. Every existing flag is passed once and still arrives as
145
- * a string, so nothing that reads `typeof flags.x === "string"` changes.
146
- */
147
- function parseFlags(argv) {
148
- const positionals = [];
149
- const flags = {};
150
- const put = (key, value) => {
151
- const had = flags[key];
152
- if (had === undefined) {
153
- flags[key] = value;
154
- return;
155
- }
156
- if (typeof value !== "string")
157
- return;
158
- flags[key] = [
159
- ...(Array.isArray(had) ? had : typeof had === "string" ? [had] : []),
160
- value,
161
- ];
162
- };
163
- for (let i = 0; i < argv.length; i++) {
164
- const arg = argv[i];
165
- if (arg === "-h" || arg === "--help") {
166
- flags.help = true;
167
- }
168
- else if (arg.startsWith("--")) {
169
- const body = arg.slice(2);
170
- const eq = body.indexOf("=");
171
- if (eq !== -1) {
172
- // `--key=value` form (e.g. --out=templates/page.tsx)
173
- put(body.slice(0, eq), body.slice(eq + 1));
174
- }
175
- else {
176
- // `--key value` form
177
- const next = argv[i + 1];
178
- if (next !== undefined && !next.startsWith("--")) {
179
- put(body, next);
180
- i++;
181
- }
182
- else {
183
- put(body, true);
184
- }
185
- }
186
- }
187
- else {
188
- positionals.push(arg);
189
- }
190
- }
191
- return { positionals, flags };
192
- }
193
141
  async function main() {
194
142
  const { positionals, flags } = parseFlags(process.argv.slice(2));
195
143
  const [command, ...args] = positionals;
@@ -397,11 +345,19 @@ async function main() {
397
345
  await add(slug, { registry, dir, version, cli: CLI_VERSION });
398
346
  break;
399
347
  }
348
+ case "logout":
349
+ /**
350
+ * O outro lado do `login`. Sem ele a única saída era apagar
351
+ * `~/.synthesisui/credentials.json` à mão - e trocar de conta é o
352
+ * caminho normal de quem testa a plataforma em mais de um repositório.
353
+ */
354
+ await logout();
355
+ break;
400
356
  case "login":
401
357
  // `--force` porque o comando agora sai cedo quando esta máquina já tem
402
358
  // sessão para este host - trocar de conta continua possível, e passa a
403
359
  // ser dito em vez de ser o comportamento padrão.
404
- await login({ registry, force: args.includes("--force") });
360
+ await login({ registry, force: boolFlag(flags, "force") });
405
361
  break;
406
362
  case "init": {
407
363
  const target = typeof flags.target === "string" ? flags.target : undefined;
@@ -562,10 +518,10 @@ async function main() {
562
518
  dir,
563
519
  registry,
564
520
  cli: CLI_VERSION,
565
- recordOnly: args.includes("--record-only"),
566
- yes: args.includes("--yes"),
521
+ recordOnly: boolFlag(flags, "record-only"),
522
+ yes: boolFlag(flags, "yes"),
567
523
  /** `--full`: o relatório inteiro do leitor. Por padrão a re-medição conta o que MUDOU. */
568
- full: args.includes("--full") || flags.full === true,
524
+ full: boolFlag(flags, "full"),
569
525
  });
570
526
  break;
571
527
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synthesisui",
3
- "version": "0.16.250",
3
+ "version": "0.16.251",
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": {