rojao-ds 0.1.0-next.10
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/LICENSE +32 -0
- package/README.md +86 -0
- package/dist/cli.js +611 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rojão Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Arquivos gerados
|
|
26
|
+
|
|
27
|
+
Os arquivos que esta ferramenta escreve no seu projeto (o tema CSS e o bloco
|
|
28
|
+
entre os marcadores <!-- rojao-ds:start --> e <!-- rojao-ds:end --> em
|
|
29
|
+
CLAUDE.md, AGENTS.md ou .cursorrules) são de quem usa a ferramenta. Você pode
|
|
30
|
+
usá-los, alterá-los e distribuí-los como quiser, sem obrigação de manter aviso
|
|
31
|
+
de licença ou atribuição. A licença MIT acima vale para o código da ferramenta,
|
|
32
|
+
não para o que ela gera.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# rojao-ds
|
|
2
|
+
|
|
3
|
+
A CLI do [Rojão DS](https://ds.rojao.ai): a sua marca vira o tema do design system e o arquivo de
|
|
4
|
+
regras que a IA (Claude Code, Cursor, outros agentes) lê para construir as telas.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx rojao-ds@next init
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Pergunta a cor da marca e escreve, na pasta atual:
|
|
11
|
+
|
|
12
|
+
- `rds-theme.css` — o tema da marca (claro, escuro e a placa de marca `.ds-plate`);
|
|
13
|
+
- `CLAUDE.md` — ou `.cursorrules` / `AGENTS.md`, o que já existir no projeto.
|
|
14
|
+
|
|
15
|
+
Depois:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @rojaostudio/ds@next
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```css
|
|
22
|
+
/* CSS raiz do app: o tema vem DEPOIS */
|
|
23
|
+
@import "@rojaostudio/ds/styles/rds.css";
|
|
24
|
+
@import "./rds-theme.css";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Antes de rodar
|
|
28
|
+
|
|
29
|
+
- Rode na raiz do projeto, com o trabalho commitado: assim o `git diff` mostra exatamente o que a CLI
|
|
30
|
+
escreveu, e desfazer é um `git checkout`.
|
|
31
|
+
- A CLI escreve só dois arquivos: o tema (`rds-theme.css`, ou o de `--out`) e o bloco entre os
|
|
32
|
+
marcadores no arquivo de regras. Nada mais é tocado.
|
|
33
|
+
- Ela não escreve através de link simbólico, nem fora da pasta do projeto (salvo `--allow-outside`,
|
|
34
|
+
e só para o tema). Se o arquivo de regras tiver um marcador órfão ou mais de um bloco, ela para sem
|
|
35
|
+
escrever e diz o que corrigir.
|
|
36
|
+
- `--recipe` e `--table` são lidos como entrada de terceiro: campos desconhecidos, `$version`/`$schema`
|
|
37
|
+
fora do formato e cores fora da lista branca (hex, `rgb()`, `rgba()`, `oklch()`) são recusados.
|
|
38
|
+
Use arquivos que você mesmo baixou de ds.rojao.ai ou exportou do Figma.
|
|
39
|
+
|
|
40
|
+
## De onde vem a marca
|
|
41
|
+
|
|
42
|
+
| Opção | Entrada |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `--color <hex>` | uma cor; o motor deriva o resto |
|
|
45
|
+
| `--recipe <arquivo>` | o `recipe.json` baixado em [ds.rojao.ai](https://ds.rojao.ai) |
|
|
46
|
+
| `--table <arquivo>` | a tabela da marca exportada do Figma [RDS] por `figma/export-brand.js` (vem no `@rojaostudio/ds-core`), formato `rds-brand-table/1` |
|
|
47
|
+
|
|
48
|
+
Sem nenhuma delas, a cor é perguntada no terminal.
|
|
49
|
+
|
|
50
|
+
## Opções
|
|
51
|
+
|
|
52
|
+
| Opção | |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `-n, --name <nome>` | nome da marca (padrão: o do recipe/tabela, senão o do `package.json`) |
|
|
55
|
+
| `--target <alvo>` | `claude`, `cursor` ou `agents` (padrão: o arquivo de regras que já existir, senão `claude`) |
|
|
56
|
+
| `-o, --out <arquivo>` | onde escrever o tema (padrão: `rds-theme.css`) |
|
|
57
|
+
| `-y, --yes` | sobrescreve o tema sem perguntar |
|
|
58
|
+
| `--allow-outside` | deixa escrever o tema fora da pasta do projeto |
|
|
59
|
+
| `-h, --help` | ajuda |
|
|
60
|
+
|
|
61
|
+
O arquivo de regras nunca é sobrescrito: a CLI escreve só o bloco entre `<!-- rojao-ds:start -->` e
|
|
62
|
+
`<!-- rojao-ds:end -->`. Se o arquivo já existe sem o bloco, ele é acrescentado no fim; se já tem, só
|
|
63
|
+
o bloco é trocado e o resto do arquivo fica como está. Rodar de novo não duplica nada.
|
|
64
|
+
|
|
65
|
+
O tema (`rds-theme.css`) é sobrescrito, então pede confirmação. Sem terminal (CI, script) e sem
|
|
66
|
+
`--yes`, a CLI recusa e não escreve nada.
|
|
67
|
+
|
|
68
|
+
## Sem dependência
|
|
69
|
+
|
|
70
|
+
Node 20+. A única dependência é o motor, [`@rojaostudio/ds-core`](https://www.npmjs.com/package/@rojaostudio/ds-core):
|
|
71
|
+
a CLI é uma casca fina sobre `generateRdsTheme`, `rdsThemeFromTable`, `emitRdsCss` e `emitClaudeMd`.
|
|
72
|
+
Quem prefere script usa o motor direto.
|
|
73
|
+
|
|
74
|
+
## Licença
|
|
75
|
+
|
|
76
|
+
MIT — veja [LICENSE](./LICENSE). Os arquivos que a CLI gera no seu projeto (o tema CSS e o bloco entre
|
|
77
|
+
os marcadores no CLAUDE.md, AGENTS.md ou .cursorrules) são seus: não precisam de aviso de licença nem de
|
|
78
|
+
atribuição.
|
|
79
|
+
|
|
80
|
+
O nome e a marca (Rojão, `rojao-ds`, `@rojaostudio/*`) não estão na licença: veja
|
|
81
|
+
[TRADEMARK.md](https://github.com/rojaostudio/ds/blob/main/TRADEMARK.md).
|
|
82
|
+
|
|
83
|
+
## Privacidade
|
|
84
|
+
|
|
85
|
+
Nada é coletado nem enviado. A CLI não tem telemetria e não acessa a rede: lê os arquivos que você
|
|
86
|
+
aponta e escreve na pasta do projeto.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
5
|
+
import { createInterface } from "readline/promises";
|
|
6
|
+
|
|
7
|
+
// src/init.ts
|
|
8
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
9
|
+
import { isAbsolute as isAbsolute2, join, relative as relative2, resolve as resolve2, sep } from "path";
|
|
10
|
+
import { parseArgs } from "util";
|
|
11
|
+
|
|
12
|
+
// src/prerelease.ts
|
|
13
|
+
import { readFileSync } from "fs";
|
|
14
|
+
function isPrerelease() {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
17
|
+
return Boolean(pkg.version?.includes("-"));
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var onTag = (name) => isPrerelease() ? `${name}@next` : name;
|
|
23
|
+
|
|
24
|
+
// src/recipe.ts
|
|
25
|
+
var BrandFileError = class extends Error {
|
|
26
|
+
};
|
|
27
|
+
var RECIPE_VERSIONS = ["1"];
|
|
28
|
+
var TABLE_SCHEMA = "rds-brand-table/1";
|
|
29
|
+
var HEX = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
30
|
+
var PALETTE_REF = /^[a-z][a-z0-9]*-\d{2,3}$/i;
|
|
31
|
+
var PALETTE_NAME = /^[a-z][a-z0-9]*$/i;
|
|
32
|
+
var PALETTE_REFS = /* @__PURE__ */ new Set([
|
|
33
|
+
"neutral",
|
|
34
|
+
"zinc",
|
|
35
|
+
"cream",
|
|
36
|
+
"coal",
|
|
37
|
+
"red",
|
|
38
|
+
"green",
|
|
39
|
+
"blue",
|
|
40
|
+
"teal",
|
|
41
|
+
"amber",
|
|
42
|
+
"purple",
|
|
43
|
+
"orange",
|
|
44
|
+
"cobalt",
|
|
45
|
+
"brick",
|
|
46
|
+
"moss",
|
|
47
|
+
"clay",
|
|
48
|
+
"flare",
|
|
49
|
+
"ash",
|
|
50
|
+
"ember",
|
|
51
|
+
"navy"
|
|
52
|
+
]);
|
|
53
|
+
var asHex = (v) => /^#[0-9a-f]{6}$/i.test(v) ? v : normalizeHex(v);
|
|
54
|
+
function normalizeHex(v) {
|
|
55
|
+
const m = v.trim().match(HEX);
|
|
56
|
+
if (!m) return null;
|
|
57
|
+
const h = m[1].length === 3 ? [...m[1]].map((c) => c + c).join("") : m[1];
|
|
58
|
+
return `#${h.toUpperCase()}`;
|
|
59
|
+
}
|
|
60
|
+
var isObject = (v) => !!v && typeof v === "object" && !Array.isArray(v);
|
|
61
|
+
var show = (v) => {
|
|
62
|
+
const s = (typeof v === "string" ? v : JSON.stringify(v) ?? String(v)).replace(/[\r\n\t]+/g, " ");
|
|
63
|
+
return JSON.stringify(s.length > 40 ? `${s.slice(0, 40)}\u2026` : s);
|
|
64
|
+
};
|
|
65
|
+
var RECIPE_FIELDS = /* @__PURE__ */ new Set([
|
|
66
|
+
"$version",
|
|
67
|
+
"name",
|
|
68
|
+
"description",
|
|
69
|
+
"archetype",
|
|
70
|
+
"scaleMode",
|
|
71
|
+
"allowLiteral",
|
|
72
|
+
"surfaceTint",
|
|
73
|
+
"surfaceGradient",
|
|
74
|
+
"darkenFill",
|
|
75
|
+
"brand",
|
|
76
|
+
"palettes",
|
|
77
|
+
"type",
|
|
78
|
+
"surface",
|
|
79
|
+
"text",
|
|
80
|
+
"fonts",
|
|
81
|
+
"display",
|
|
82
|
+
"radius",
|
|
83
|
+
"surfacePattern",
|
|
84
|
+
"card",
|
|
85
|
+
"hero",
|
|
86
|
+
"layout",
|
|
87
|
+
"dark",
|
|
88
|
+
"extras"
|
|
89
|
+
]);
|
|
90
|
+
var OTHER_TYPES = {
|
|
91
|
+
archetype: "string",
|
|
92
|
+
scaleMode: "string",
|
|
93
|
+
allowLiteral: "boolean",
|
|
94
|
+
surfaceTint: "number",
|
|
95
|
+
surfaceGradient: "number",
|
|
96
|
+
darkenFill: "boolean",
|
|
97
|
+
type: "object",
|
|
98
|
+
display: "object",
|
|
99
|
+
radius: "string|object",
|
|
100
|
+
surfacePattern: "string",
|
|
101
|
+
card: "object",
|
|
102
|
+
hero: "object",
|
|
103
|
+
layout: "string",
|
|
104
|
+
dark: "object",
|
|
105
|
+
extras: "object"
|
|
106
|
+
};
|
|
107
|
+
var BRAND_COLORS = /* @__PURE__ */ new Set([
|
|
108
|
+
"primary",
|
|
109
|
+
"hover",
|
|
110
|
+
"onPrimary",
|
|
111
|
+
"secondary",
|
|
112
|
+
"onSecondary",
|
|
113
|
+
"accent",
|
|
114
|
+
"accentLight",
|
|
115
|
+
"onAccent",
|
|
116
|
+
"borderFocus",
|
|
117
|
+
"heading",
|
|
118
|
+
"invert"
|
|
119
|
+
]);
|
|
120
|
+
var BRAND_USED = ["secondary", "accent", "heading", "invert"];
|
|
121
|
+
var typeOk = (v, t) => t.split("|").some((one) => one === "object" ? isObject(v) : typeof v === one);
|
|
122
|
+
function parseRecipe(raw, slug2) {
|
|
123
|
+
if (!isObject(raw)) throw new BrandFileError("recipe inv\xE1lido: esperava um objeto JSON.");
|
|
124
|
+
const problems = [];
|
|
125
|
+
const unknown = Object.keys(raw).filter((k) => !RECIPE_FIELDS.has(k));
|
|
126
|
+
if (unknown.length) problems.push(`campo desconhecido: ${unknown.map(show).join(", ")}`);
|
|
127
|
+
if (raw.$version === void 0) problems.push(`falta $version (esperava ${RECIPE_VERSIONS.map((v) => `"${v}"`).join(" ou ")})`);
|
|
128
|
+
else if (!RECIPE_VERSIONS.includes(raw.$version))
|
|
129
|
+
problems.push(`$version ${show(raw.$version)} n\xE3o \xE9 suportada (esperava ${RECIPE_VERSIONS.map((v) => `"${v}"`).join(" ou ")})`);
|
|
130
|
+
if (raw.name !== void 0 && typeof raw.name !== "string") problems.push("name: esperava texto");
|
|
131
|
+
if (raw.description !== void 0 && (typeof raw.description !== "string" || raw.description.length > 1e3))
|
|
132
|
+
problems.push("description: esperava texto de at\xE9 1000 caracteres");
|
|
133
|
+
for (const [field, t] of Object.entries(OTHER_TYPES))
|
|
134
|
+
if (raw[field] !== void 0 && !typeOk(raw[field], t)) problems.push(`${field}: tipo inv\xE1lido (esperava ${t.replace("|", " ou ")})`);
|
|
135
|
+
for (const axis of ["surface", "text"])
|
|
136
|
+
if (raw[axis] !== void 0 && !(typeof raw[axis] === "string" && PALETTE_REFS.has(raw[axis])))
|
|
137
|
+
problems.push(`${axis}: ${show(raw[axis])} n\xE3o \xE9 uma paleta do Roj\xE3o DS`);
|
|
138
|
+
let fonts = { body: "inter" };
|
|
139
|
+
if (raw.fonts !== void 0) {
|
|
140
|
+
if (!isObject(raw.fonts)) problems.push("fonts: esperava um objeto");
|
|
141
|
+
else {
|
|
142
|
+
const bad = Object.entries(raw.fonts).filter(
|
|
143
|
+
([k, v]) => !["body", "display", "editorial", "mono", "displayKind"].includes(k) || typeof v !== "string" || !/^[a-z0-9-]{1,40}$/i.test(v)
|
|
144
|
+
);
|
|
145
|
+
if (bad.length) problems.push(`fonts: ${bad.map(([k]) => show(k)).join(", ")} inv\xE1lido (nome de fonte: letras, d\xEDgitos e h\xEDfen)`);
|
|
146
|
+
else fonts = { body: "inter", ...raw.fonts };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const brand = { primary: "" };
|
|
150
|
+
if (!isObject(raw.brand)) problems.push("falta brand.primary (a cor da marca).");
|
|
151
|
+
else {
|
|
152
|
+
const extra = Object.keys(raw.brand).filter((k) => !BRAND_COLORS.has(k));
|
|
153
|
+
if (extra.length) problems.push(`brand: campo desconhecido ${extra.map(show).join(", ")}`);
|
|
154
|
+
const primary = typeof raw.brand.primary === "string" ? normalizeHex(raw.brand.primary) : null;
|
|
155
|
+
if (!primary) problems.push(`falta brand.primary (a cor da marca) em hex, ex.: #7C3AED${raw.brand.primary ? ` \u2014 veio ${show(raw.brand.primary)}` : ""}.`);
|
|
156
|
+
else brand.primary = primary;
|
|
157
|
+
for (const [k, v] of Object.entries(raw.brand)) {
|
|
158
|
+
if (k === "primary" || v === void 0) continue;
|
|
159
|
+
const ok = typeof v === "string" && (normalizeHex(v) || PALETTE_REF.test(v));
|
|
160
|
+
if (!ok) problems.push(`brand.${k}: ${show(v)} n\xE3o \xE9 uma cor (hex ou paleta-passo, ex.: teal-500)`);
|
|
161
|
+
else if (BRAND_USED.includes(k)) brand[k] = asHex(v) ?? v;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
let palettes;
|
|
165
|
+
if (raw.palettes !== void 0) {
|
|
166
|
+
if (!isObject(raw.palettes)) problems.push("palettes: esperava um objeto");
|
|
167
|
+
else {
|
|
168
|
+
palettes = {};
|
|
169
|
+
for (const [name, value] of Object.entries(raw.palettes)) {
|
|
170
|
+
if (!PALETTE_NAME.test(name)) problems.push(`palettes: nome ${show(name)} inv\xE1lido (letras e d\xEDgitos)`);
|
|
171
|
+
else if (typeof value === "string") {
|
|
172
|
+
const hex = asHex(value);
|
|
173
|
+
if (!hex) problems.push(`palettes.${name}: ${show(value)} n\xE3o \xE9 hex`);
|
|
174
|
+
else palettes[name] = hex;
|
|
175
|
+
} else if (isObject(value)) {
|
|
176
|
+
const bad = Object.entries(value).filter(([step, v]) => !/^(50|[1-9]00|950)$/.test(step) || typeof v !== "string" || !normalizeHex(v));
|
|
177
|
+
if (bad.length) problems.push(`palettes.${name}: passo ${bad.map(([s]) => show(s)).join(", ")} inv\xE1lido (50\u2013900, valor hex)`);
|
|
178
|
+
else palettes[name] = Object.fromEntries(Object.entries(value).map(([s, v]) => [s, asHex(v)]));
|
|
179
|
+
} else problems.push(`palettes.${name}: esperava hex ou escala 50\u2013900`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (problems.length) throw new BrandFileError(`recipe inv\xE1lido:
|
|
184
|
+
${problems.join("\n ")}`);
|
|
185
|
+
const own = typeof raw.name === "string" ? slug2(raw.name) : "";
|
|
186
|
+
return {
|
|
187
|
+
name: own || "marca",
|
|
188
|
+
...typeof raw.description === "string" ? { description: raw.description } : {},
|
|
189
|
+
brand,
|
|
190
|
+
...palettes ? { palettes } : {},
|
|
191
|
+
surface: raw.surface ?? "zinc",
|
|
192
|
+
text: raw.text ?? "zinc",
|
|
193
|
+
fonts
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
var TABLE_FIELDS = /* @__PURE__ */ new Set(["$schema", "name", "primitives", "modes", "vars"]);
|
|
197
|
+
var MODES = ["light", "dark", "brand"];
|
|
198
|
+
var stringMap = (v, field, problems) => {
|
|
199
|
+
if (!isObject(v)) {
|
|
200
|
+
problems.push(`${field}: esperava um objeto`);
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const bad = Object.entries(v).filter(([, x]) => typeof x !== "string");
|
|
204
|
+
if (bad.length) problems.push(`${field}: ${bad.slice(0, 5).map(([k]) => show(k)).join(", ")} n\xE3o \xE9 texto`);
|
|
205
|
+
return bad.length === 0;
|
|
206
|
+
};
|
|
207
|
+
function parseTable(raw) {
|
|
208
|
+
if (!isObject(raw) || !("modes" in raw) || !("primitives" in raw)) {
|
|
209
|
+
throw new BrandFileError(
|
|
210
|
+
`tabela inv\xE1lida: esperava o formato ${TABLE_SCHEMA} (com $schema, modes e primitives), exportado por figma/export-brand.js.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
const problems = [];
|
|
214
|
+
if (raw.$schema === void 0) problems.push(`falta $schema (esperava "${TABLE_SCHEMA}")`);
|
|
215
|
+
else if (raw.$schema !== TABLE_SCHEMA) problems.push(`formato desconhecido: ${show(raw.$schema)} (esperava ${TABLE_SCHEMA})`);
|
|
216
|
+
const unknown = Object.keys(raw).filter((k) => !TABLE_FIELDS.has(k));
|
|
217
|
+
if (unknown.length) problems.push(`campo desconhecido: ${unknown.map(show).join(", ")}`);
|
|
218
|
+
if (raw.name !== void 0 && typeof raw.name !== "string") problems.push("name: esperava texto");
|
|
219
|
+
stringMap(raw.primitives, "primitives", problems);
|
|
220
|
+
if (!isObject(raw.modes)) problems.push("modes: esperava um objeto");
|
|
221
|
+
else {
|
|
222
|
+
const extra = Object.keys(raw.modes).filter((m) => !MODES.includes(m));
|
|
223
|
+
if (extra.length) problems.push(`modes: modo desconhecido ${extra.map(show).join(", ")}`);
|
|
224
|
+
for (const m of MODES) if (raw.modes[m] !== void 0) stringMap(raw.modes[m], `modes.${m}`, problems);
|
|
225
|
+
}
|
|
226
|
+
if (raw.vars !== void 0) stringMap(raw.vars, "vars", problems);
|
|
227
|
+
if (problems.length) throw new BrandFileError(`tabela inv\xE1lida:
|
|
228
|
+
${problems.join("\n ")}`);
|
|
229
|
+
return raw;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/safe-write.ts
|
|
233
|
+
import { closeSync, constants, existsSync, ftruncateSync, lstatSync, mkdirSync, openSync, realpathSync, writeSync } from "fs";
|
|
234
|
+
import { dirname, isAbsolute, relative, resolve } from "path";
|
|
235
|
+
var UnsafePathError = class extends Error {
|
|
236
|
+
};
|
|
237
|
+
function inside(parent, child) {
|
|
238
|
+
const rel = relative(parent, child);
|
|
239
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
240
|
+
}
|
|
241
|
+
function nearestExisting(p) {
|
|
242
|
+
let cur = p;
|
|
243
|
+
while (!existsSync(cur)) {
|
|
244
|
+
const up = dirname(cur);
|
|
245
|
+
if (up === cur) break;
|
|
246
|
+
cur = up;
|
|
247
|
+
}
|
|
248
|
+
return cur;
|
|
249
|
+
}
|
|
250
|
+
var isLink = (p) => {
|
|
251
|
+
try {
|
|
252
|
+
return lstatSync(p).isSymbolicLink();
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
function checkDestination(cwd, abs, label, allowOutside = false) {
|
|
258
|
+
if (isLink(abs)) throw new UnsafePathError(`${label} \xE9 um link simb\xF3lico. Por seguran\xE7a, a CLI n\xE3o escreve atrav\xE9s de links: apague o link ou aponte outro caminho.`);
|
|
259
|
+
if (allowOutside) return;
|
|
260
|
+
const root = realpathSync(cwd);
|
|
261
|
+
const lexical = resolve(cwd, abs);
|
|
262
|
+
if (!inside(resolve(cwd), lexical) && !inside(root, lexical))
|
|
263
|
+
throw new UnsafePathError(`${label} fica fora da pasta do projeto (${cwd}). Use --allow-outside se \xE9 isso mesmo.`);
|
|
264
|
+
const real = realpathSync(nearestExisting(dirname(abs)));
|
|
265
|
+
if (!inside(root, real))
|
|
266
|
+
throw new UnsafePathError(`${label} cai fora da pasta do projeto atrav\xE9s de um link (${real}). Use --allow-outside se \xE9 isso mesmo.`);
|
|
267
|
+
}
|
|
268
|
+
function safeWrite(cwd, abs, content, label, allowOutside = false) {
|
|
269
|
+
checkDestination(cwd, abs, label, allowOutside);
|
|
270
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
271
|
+
checkDestination(cwd, abs, label, allowOutside);
|
|
272
|
+
let fd;
|
|
273
|
+
if (!existsSync(abs) && !isLink(abs)) {
|
|
274
|
+
fd = openSync(abs, "wx");
|
|
275
|
+
} else {
|
|
276
|
+
const noFollow = constants.O_NOFOLLOW;
|
|
277
|
+
fd = noFollow ? openSync(abs, constants.O_WRONLY | noFollow) : openSync(abs, "r+");
|
|
278
|
+
ftruncateSync(fd, 0);
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
writeSync(fd, content);
|
|
282
|
+
} finally {
|
|
283
|
+
closeSync(fd);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/init.ts
|
|
288
|
+
import {
|
|
289
|
+
emitClaudeMd,
|
|
290
|
+
emitRdsCss,
|
|
291
|
+
generateRdsTheme,
|
|
292
|
+
rdsThemeFromTable
|
|
293
|
+
} from "@rojaostudio/ds-core/generate";
|
|
294
|
+
var TARGETS = {
|
|
295
|
+
claude: "CLAUDE.md",
|
|
296
|
+
agents: "AGENTS.md",
|
|
297
|
+
cursor: ".cursorrules"
|
|
298
|
+
};
|
|
299
|
+
var DEFAULT_OUT = "rds-theme.css";
|
|
300
|
+
var HELP = `rojao-ds \u2014 o tema da sua marca no Roj\xE3o DS 2.0
|
|
301
|
+
|
|
302
|
+
Uso:
|
|
303
|
+
npx rojao-ds init [op\xE7\xF5es]
|
|
304
|
+
|
|
305
|
+
Gera, na pasta atual:
|
|
306
|
+
\u2022 o tema da marca (${DEFAULT_OUT}), para importar DEPOIS de @rojaostudio/ds/styles/rds.css;
|
|
307
|
+
\u2022 o arquivo de regras da IA (CLAUDE.md, .cursorrules ou AGENTS.md). Se ele j\xE1 existe, s\xF3 o
|
|
308
|
+
bloco entre <!-- rojao-ds:start --> e <!-- rojao-ds:end --> \xE9 escrito; o resto fica.
|
|
309
|
+
|
|
310
|
+
De onde vem a marca (escolha uma; sem nenhuma, a cor \xE9 perguntada):
|
|
311
|
+
-c, --color <hex> a cor da marca, ex.: #7C3AED
|
|
312
|
+
-r, --recipe <arquivo> o recipe.json baixado em ds.rojao.ai
|
|
313
|
+
-t, --table <arquivo> a tabela exportada do Figma (figma/export-brand.js, rds-brand-table/1)
|
|
314
|
+
|
|
315
|
+
Op\xE7\xF5es:
|
|
316
|
+
-n, --name <nome> nome da marca (padr\xE3o: o do recipe/tabela, sen\xE3o o do package.json)
|
|
317
|
+
--target <alvo> claude | cursor | agents (padr\xE3o: o arquivo que j\xE1 existir, sen\xE3o claude)
|
|
318
|
+
-o, --out <arquivo> onde escrever o tema (padr\xE3o: ${DEFAULT_OUT})
|
|
319
|
+
-y, --yes sobrescreve o tema sem perguntar
|
|
320
|
+
--allow-outside deixa escrever o tema fora da pasta do projeto
|
|
321
|
+
-h, --help mostra esta ajuda
|
|
322
|
+
-v, --version mostra a vers\xE3o
|
|
323
|
+
|
|
324
|
+
Exemplos:
|
|
325
|
+
npx rojao-ds init --color "#7C3AED"
|
|
326
|
+
npx rojao-ds init --recipe ./marca.recipe.json --target cursor
|
|
327
|
+
npx rojao-ds init --table ./brand/marca.rds.json --out src/styles/tema.css --yes
|
|
328
|
+
`;
|
|
329
|
+
var CliError = class extends Error {
|
|
330
|
+
};
|
|
331
|
+
function slug(v) {
|
|
332
|
+
return v.replace(/^@[^/]+\//, "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
333
|
+
}
|
|
334
|
+
var MAX_INPUT = 1024 * 1024;
|
|
335
|
+
function readJson(cwd, file, what) {
|
|
336
|
+
const path = resolve2(cwd, file);
|
|
337
|
+
if (!existsSync2(path)) throw new CliError(`${what} n\xE3o encontrado: ${file}`);
|
|
338
|
+
try {
|
|
339
|
+
const text = readFileSync2(path, "utf8");
|
|
340
|
+
if (text.length > MAX_INPUT) throw new CliError(`${what} grande demais (${file}): at\xE9 1 MB.`);
|
|
341
|
+
return JSON.parse(text);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
if (e instanceof CliError) throw e;
|
|
344
|
+
throw new CliError(`${what} n\xE3o \xE9 um JSON v\xE1lido (${file}): ${e.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function projectName(cwd) {
|
|
348
|
+
const pkg = join(cwd, "package.json");
|
|
349
|
+
if (!existsSync2(pkg)) return null;
|
|
350
|
+
try {
|
|
351
|
+
const name = JSON.parse(readFileSync2(pkg, "utf8")).name;
|
|
352
|
+
return typeof name === "string" && slug(name) ? slug(name) : null;
|
|
353
|
+
} catch {
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function fromColor(hex, name) {
|
|
358
|
+
return { name, brand: { primary: hex }, surface: "zinc", text: "zinc", fonts: { body: "inter" } };
|
|
359
|
+
}
|
|
360
|
+
var BLOCK_START = "<!-- rojao-ds:start -->";
|
|
361
|
+
var BLOCK_END = "<!-- rojao-ds:end -->";
|
|
362
|
+
function rulesBlock(md) {
|
|
363
|
+
return `${BLOCK_START}
|
|
364
|
+
${md.replace(/\s+$/, "")}
|
|
365
|
+
${BLOCK_END}`;
|
|
366
|
+
}
|
|
367
|
+
var RulesBlockError = class extends Error {
|
|
368
|
+
};
|
|
369
|
+
var count = (text, needle) => text.split(needle).length - 1;
|
|
370
|
+
function mergeRulesBlock(existing, block) {
|
|
371
|
+
const starts = count(existing, BLOCK_START);
|
|
372
|
+
const ends = count(existing, BLOCK_END);
|
|
373
|
+
const start = existing.indexOf(BLOCK_START);
|
|
374
|
+
const end = existing.indexOf(BLOCK_END);
|
|
375
|
+
if (starts > 1 || ends > 1)
|
|
376
|
+
throw new RulesBlockError(`h\xE1 mais de um bloco do Roj\xE3o DS (${starts} in\xEDcio, ${ends} fim). Deixe um s\xF3 par ${BLOCK_START} \u2026 ${BLOCK_END} e rode de novo.`);
|
|
377
|
+
if (starts !== ends || starts === 1 && end < start)
|
|
378
|
+
throw new RulesBlockError(`marcador \xF3rf\xE3o: ${starts ? BLOCK_START : BLOCK_END} sem o par na ordem certa. Corrija ou apague os marcadores e rode de novo.`);
|
|
379
|
+
if (starts === 1) {
|
|
380
|
+
return { text: existing.slice(0, start) + block + existing.slice(end + BLOCK_END.length), mode: "replaced" };
|
|
381
|
+
}
|
|
382
|
+
const head = existing.replace(/\s+$/, "");
|
|
383
|
+
return { text: head ? `${head}
|
|
384
|
+
|
|
385
|
+
${block}
|
|
386
|
+
` : `${block}
|
|
387
|
+
`, mode: "appended" };
|
|
388
|
+
}
|
|
389
|
+
async function askColor(io) {
|
|
390
|
+
for (let i = 0; i < 3; i++) {
|
|
391
|
+
const hex = normalizeHex(await io.ask("Cor da marca (hex, ex.: #7C3AED): "));
|
|
392
|
+
if (hex) return hex;
|
|
393
|
+
io.err(" N\xE3o \xE9 uma cor hex. Use 6 d\xEDgitos, como #7C3AED.");
|
|
394
|
+
}
|
|
395
|
+
throw new CliError("cor n\xE3o informada.");
|
|
396
|
+
}
|
|
397
|
+
async function resolveBrand(v, io) {
|
|
398
|
+
const modes = [v.color, v.recipe, v.table].filter((x) => x !== void 0);
|
|
399
|
+
if (modes.length > 1) throw new CliError("use s\xF3 uma fonte de marca: --color, --recipe ou --table.");
|
|
400
|
+
const name = v.name !== void 0 ? slug(v.name) || void 0 : void 0;
|
|
401
|
+
try {
|
|
402
|
+
if (v.table !== void 0) {
|
|
403
|
+
const table = parseTable(readJson(io.cwd, v.table, "tabela"));
|
|
404
|
+
const n = name ?? (slug(table.name ?? "") || "marca");
|
|
405
|
+
const theme = rdsThemeFromTable(table);
|
|
406
|
+
return { label: `tabela do Figma (${v.table})`, name: n, theme, source: { ...table, name: n } };
|
|
407
|
+
}
|
|
408
|
+
if (v.recipe !== void 0) {
|
|
409
|
+
const parsed = parseRecipe(readJson(io.cwd, v.recipe, "recipe"), slug);
|
|
410
|
+
const def2 = { ...parsed, name: name ?? parsed.name };
|
|
411
|
+
return { label: `recipe (${v.recipe})`, name: def2.name, theme: generateRdsTheme(def2), source: def2 };
|
|
412
|
+
}
|
|
413
|
+
let hex;
|
|
414
|
+
if (v.color !== void 0) {
|
|
415
|
+
const h = normalizeHex(v.color);
|
|
416
|
+
if (!h) throw new CliError(`cor inv\xE1lida: ${v.color} (use hex, ex.: #7C3AED).`);
|
|
417
|
+
hex = h;
|
|
418
|
+
} else if (io.interactive) {
|
|
419
|
+
hex = await askColor(io);
|
|
420
|
+
} else {
|
|
421
|
+
throw new CliError("informe a marca: --color <hex>, --recipe <arquivo> ou --table <arquivo>.");
|
|
422
|
+
}
|
|
423
|
+
const def = fromColor(hex, name ?? projectName(io.cwd) ?? "marca");
|
|
424
|
+
return { label: `cor ${hex}`, name: def.name, theme: generateRdsTheme(def), source: def };
|
|
425
|
+
} catch (e) {
|
|
426
|
+
if (e instanceof CliError) throw e;
|
|
427
|
+
if (e instanceof BrandFileError) throw new CliError(e.message);
|
|
428
|
+
throw new CliError(`n\xE3o foi poss\xEDvel gerar o tema: ${e.message}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function resolveTarget(v, io) {
|
|
432
|
+
if (v.target !== void 0) {
|
|
433
|
+
const t = v.target.toLowerCase();
|
|
434
|
+
const byFile = Object.keys(TARGETS).find((k) => TARGETS[k].toLowerCase() === t);
|
|
435
|
+
if (t in TARGETS) return t;
|
|
436
|
+
if (byFile) return byFile;
|
|
437
|
+
throw new CliError(`alvo inv\xE1lido: ${v.target} (use claude, cursor ou agents).`);
|
|
438
|
+
}
|
|
439
|
+
const found = Object.keys(TARGETS).filter((k) => existsSync2(join(io.cwd, TARGETS[k])));
|
|
440
|
+
if (found.length <= 1) return found[0] ?? "claude";
|
|
441
|
+
if (!io.interactive || v.yes) {
|
|
442
|
+
io.out(`Encontrei ${found.map((k) => TARGETS[k]).join(", ")}; uso ${TARGETS[found[0]]} (--target escolhe outro).`);
|
|
443
|
+
return found[0];
|
|
444
|
+
}
|
|
445
|
+
const lista = found.map((k, i2) => `${i2 + 1}) ${TARGETS[k]}`).join(" ");
|
|
446
|
+
const r = (await io.ask(`Encontrei mais de um arquivo de regras: ${lista}. Qual usar? [1] `)).trim();
|
|
447
|
+
const i = r === "" ? 0 : Number(r) - 1;
|
|
448
|
+
if (!Number.isInteger(i) || !found[i]) throw new CliError(`op\xE7\xE3o inv\xE1lida: ${r}`);
|
|
449
|
+
return found[i];
|
|
450
|
+
}
|
|
451
|
+
async function confirm(io, pergunta) {
|
|
452
|
+
const r = (await io.ask(`${pergunta} (s/N) `)).trim().toLowerCase();
|
|
453
|
+
return r === "s" || r === "sim" || r === "y" || r === "yes";
|
|
454
|
+
}
|
|
455
|
+
function importPath(cwd, abs) {
|
|
456
|
+
const rel = relative2(cwd, abs).split(sep).join("/");
|
|
457
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
458
|
+
}
|
|
459
|
+
function installCommand(cwd) {
|
|
460
|
+
const ds = onTag("@rojaostudio/ds");
|
|
461
|
+
if (existsSync2(join(cwd, "pnpm-lock.yaml"))) return `pnpm add ${ds}`;
|
|
462
|
+
if (existsSync2(join(cwd, "yarn.lock"))) return `yarn add ${ds}`;
|
|
463
|
+
if (existsSync2(join(cwd, "bun.lockb")) || existsSync2(join(cwd, "bun.lock"))) return `bun add ${ds}`;
|
|
464
|
+
if (existsSync2(join(cwd, "package-lock.json"))) return `npm install ${ds}`;
|
|
465
|
+
return `pnpm add ${ds}`;
|
|
466
|
+
}
|
|
467
|
+
var OPTIONS = {
|
|
468
|
+
color: { type: "string", short: "c" },
|
|
469
|
+
recipe: { type: "string", short: "r" },
|
|
470
|
+
table: { type: "string", short: "t" },
|
|
471
|
+
name: { type: "string", short: "n" },
|
|
472
|
+
target: { type: "string" },
|
|
473
|
+
out: { type: "string", short: "o" },
|
|
474
|
+
yes: { type: "boolean", short: "y" },
|
|
475
|
+
"allow-outside": { type: "boolean" },
|
|
476
|
+
help: { type: "boolean", short: "h" },
|
|
477
|
+
version: { type: "boolean", short: "v" }
|
|
478
|
+
};
|
|
479
|
+
async function run(argv, io, version2 = "0.0.0") {
|
|
480
|
+
let values;
|
|
481
|
+
let positionals;
|
|
482
|
+
try {
|
|
483
|
+
({ values, positionals } = parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: true }));
|
|
484
|
+
} catch (e) {
|
|
485
|
+
io.err(`\u2717 ${e.message.replace(/\. To specify.*$/s, "")}`);
|
|
486
|
+
io.err(" Veja: npx rojao-ds --help");
|
|
487
|
+
return 1;
|
|
488
|
+
}
|
|
489
|
+
if (values.version) {
|
|
490
|
+
io.out(version2);
|
|
491
|
+
return 0;
|
|
492
|
+
}
|
|
493
|
+
const [command, ...extra] = positionals;
|
|
494
|
+
if (values.help || command === void 0 || command === "help") {
|
|
495
|
+
io.out(HELP);
|
|
496
|
+
return command === void 0 && !values.help ? 1 : 0;
|
|
497
|
+
}
|
|
498
|
+
if (command !== "init") {
|
|
499
|
+
io.err(`\u2717 comando desconhecido: ${command}. O comando \xE9 init.`);
|
|
500
|
+
io.err(" Veja: npx rojao-ds --help");
|
|
501
|
+
return 1;
|
|
502
|
+
}
|
|
503
|
+
if (extra.length) {
|
|
504
|
+
io.err(`\u2717 argumento a mais: ${extra.join(" ")}`);
|
|
505
|
+
return 1;
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
return await init(values, io);
|
|
509
|
+
} catch (e) {
|
|
510
|
+
if (e instanceof CliError || e instanceof UnsafePathError || e instanceof RulesBlockError) {
|
|
511
|
+
io.err(`\u2717 ${e.message}`);
|
|
512
|
+
if (!(e instanceof CliError)) io.err(" Nada foi escrito.");
|
|
513
|
+
return 1;
|
|
514
|
+
}
|
|
515
|
+
throw e;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
async function init(v, io) {
|
|
519
|
+
const brand = await resolveBrand(v, io);
|
|
520
|
+
const target = await resolveTarget(v, io);
|
|
521
|
+
const allowOutside = Boolean(v["allow-outside"]);
|
|
522
|
+
const out = v.out ?? DEFAULT_OUT;
|
|
523
|
+
if (!out.endsWith(".css")) throw new CliError(`--out precisa terminar em .css: ${out}`);
|
|
524
|
+
const cssAbs = isAbsolute2(out) ? out : resolve2(io.cwd, out);
|
|
525
|
+
const rulesAbs = join(io.cwd, TARGETS[target]);
|
|
526
|
+
const cssImport = importPath(io.cwd, cssAbs);
|
|
527
|
+
const install = installCommand(io.cwd);
|
|
528
|
+
let css;
|
|
529
|
+
let block;
|
|
530
|
+
try {
|
|
531
|
+
css = { abs: cssAbs, label: importPath(io.cwd, cssAbs).replace(/^\.\//, ""), content: emitRdsCss(brand.theme) };
|
|
532
|
+
block = rulesBlock(
|
|
533
|
+
emitClaudeMd(brand.source, { theme: brand.theme, cssFile: cssImport.replace(/^\.\//, ""), target, install })
|
|
534
|
+
);
|
|
535
|
+
} catch (e) {
|
|
536
|
+
throw new CliError(`n\xE3o foi poss\xEDvel gerar os arquivos: ${e.message}`);
|
|
537
|
+
}
|
|
538
|
+
const rules = { abs: rulesAbs, label: TARGETS[target], block };
|
|
539
|
+
checkDestination(io.cwd, css.abs, css.label, allowOutside);
|
|
540
|
+
checkDestination(io.cwd, rules.abs, rules.label);
|
|
541
|
+
const rulesExists = existsSync2(rules.abs);
|
|
542
|
+
const before = rulesExists ? readFileSync2(rules.abs, "utf8") : "";
|
|
543
|
+
const merged = rulesExists ? mergeRulesBlock(before, rules.block) : null;
|
|
544
|
+
const cssExists = existsSync2(css.abs);
|
|
545
|
+
if (cssExists && !v.yes && !io.interactive) {
|
|
546
|
+
io.err(`\u2717 ${css.label} j\xE1 existe.`);
|
|
547
|
+
io.err(" Nada foi escrito. Rode de novo com --yes para sobrescrever.");
|
|
548
|
+
return 1;
|
|
549
|
+
}
|
|
550
|
+
io.out(`Marca: ${brand.name} \u2014 ${brand.label}`);
|
|
551
|
+
if (cssExists && !v.yes && !await confirm(io, `${css.label} j\xE1 existe. Sobrescrever?`)) {
|
|
552
|
+
io.out(`\u2013 ${css.label} mantido`);
|
|
553
|
+
} else {
|
|
554
|
+
safeWrite(io.cwd, css.abs, css.content, css.label, allowOutside);
|
|
555
|
+
io.out(`\u2713 ${css.label} ${cssExists ? "atualizado" : "criado"}`);
|
|
556
|
+
}
|
|
557
|
+
if (!merged) {
|
|
558
|
+
safeWrite(io.cwd, rules.abs, `${rules.block}
|
|
559
|
+
`, rules.label);
|
|
560
|
+
io.out(`\u2713 ${rules.label} criado`);
|
|
561
|
+
} else if (merged.text === before) io.out(`\u2713 ${rules.label} j\xE1 est\xE1 em dia`);
|
|
562
|
+
else {
|
|
563
|
+
safeWrite(io.cwd, rules.abs, merged.text, rules.label);
|
|
564
|
+
io.out(`\u2713 ${rules.label}: bloco do Roj\xE3o DS ${merged.mode === "replaced" ? "atualizado" : "acrescentado no fim"} (o resto do arquivo foi mantido)`);
|
|
565
|
+
}
|
|
566
|
+
io.out("");
|
|
567
|
+
io.out("Pr\xF3ximos passos:");
|
|
568
|
+
io.out(` 1. ${install}`);
|
|
569
|
+
io.out(" 2. No CSS raiz do app (o tema vem DEPOIS):");
|
|
570
|
+
io.out(' @import "@rojaostudio/ds/styles/rds.css";');
|
|
571
|
+
io.out(` @import "${cssImport}";`);
|
|
572
|
+
if (cssImport.slice(2).includes("/") || cssImport.startsWith("../")) {
|
|
573
|
+
io.out(" (caminho a partir da raiz do projeto: ajuste ao lugar do seu CSS)");
|
|
574
|
+
}
|
|
575
|
+
io.out(` 3. Pe\xE7a a tela para a IA: ela l\xEA o ${TARGETS[target]}.`);
|
|
576
|
+
return 0;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// src/cli.ts
|
|
580
|
+
function version() {
|
|
581
|
+
try {
|
|
582
|
+
const pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
|
|
583
|
+
return pkg.version ?? "0.0.0";
|
|
584
|
+
} catch {
|
|
585
|
+
return "0.0.0";
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
var prompt = {};
|
|
589
|
+
var interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
590
|
+
var code = await run(
|
|
591
|
+
process.argv.slice(2),
|
|
592
|
+
{
|
|
593
|
+
cwd: process.cwd(),
|
|
594
|
+
out: (l) => process.stdout.write(`${l}
|
|
595
|
+
`),
|
|
596
|
+
err: (l) => process.stderr.write(`${l}
|
|
597
|
+
`),
|
|
598
|
+
ask: (q) => {
|
|
599
|
+
prompt.rl ??= createInterface({ input: process.stdin, output: process.stdout });
|
|
600
|
+
return prompt.rl.question(q);
|
|
601
|
+
},
|
|
602
|
+
interactive
|
|
603
|
+
},
|
|
604
|
+
version()
|
|
605
|
+
).catch((e) => {
|
|
606
|
+
process.stderr.write(`\u2717 erro inesperado: ${e instanceof Error ? e.message : String(e)}
|
|
607
|
+
`);
|
|
608
|
+
return 1;
|
|
609
|
+
});
|
|
610
|
+
prompt.rl?.close();
|
|
611
|
+
process.exitCode = code;
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rojao-ds",
|
|
3
|
+
"version": "0.1.0-next.10",
|
|
4
|
+
"description": "CLI do Rojão DS — `npx rojao-ds init` gera o tema da sua marca e o arquivo de regras para a IA (CLAUDE.md, .cursorrules, AGENTS.md).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Rojão Studio (https://rojao.studio)",
|
|
7
|
+
"homepage": "https://ds.rojao.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rojaostudio/ds.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/rojaostudio/ds/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"design-system",
|
|
18
|
+
"theme-generator",
|
|
19
|
+
"cli",
|
|
20
|
+
"claude",
|
|
21
|
+
"cursor",
|
|
22
|
+
"agents",
|
|
23
|
+
"css-variables"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"rojao-ds": "./dist/cli.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"LICENSE",
|
|
31
|
+
"README.md",
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=20"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@rojaostudio/ds-core": "1.1.0-next.11"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.19.43",
|
|
42
|
+
"eslint": "^9.18.0",
|
|
43
|
+
"tsup": "^8.5.1",
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"vitest": "^4.1.7",
|
|
46
|
+
"@rojao/config": "0.0.0"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"registry": "https://registry.npmjs.org",
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint": "eslint ."
|
|
58
|
+
}
|
|
59
|
+
}
|