onboarding-kit 0.1.0 → 0.2.0
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/README.md +14 -1
- package/dist/brand.d.ts +38 -0
- package/dist/brand.js +319 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +11 -5
- package/dist/render/assets.d.ts +2 -2
- package/dist/render/assets.js +167 -36
- package/dist/render/copy.d.ts +8 -0
- package/dist/render/copy.js +60 -7
- package/dist/render/index.d.ts +7 -3
- package/dist/render/index.js +88 -25
- package/dist/template.d.ts +7 -2
- package/dist/template.js +10 -2
- package/dist/types.d.ts +13 -0
- package/dist/validate.js +13 -1
- package/package.json +10 -4
package/dist/render/index.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.escapeHtml = void 0;
|
|
|
4
4
|
exports.renderMissingPage = renderMissingPage;
|
|
5
5
|
exports.renderErrorPage = renderErrorPage;
|
|
6
6
|
exports.renderOnboarding = renderOnboarding;
|
|
7
|
+
const brand_1 = require("../brand");
|
|
7
8
|
const load_1 = require("../load");
|
|
8
9
|
const template_1 = require("../template");
|
|
9
10
|
const assets_1 = require("./assets");
|
|
@@ -14,39 +15,97 @@ var html_2 = require("./html");
|
|
|
14
15
|
Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return html_2.escapeHtml; } });
|
|
15
16
|
const ICON_GRID = '<svg viewBox="0 0 20 20" aria-hidden="true"><path fill="currentColor" d="M3 3h5.5v5.5H3zM11.5 3H17v5.5h-5.5zM3 11.5h5.5V17H3zM11.5 11.5H17V17h-5.5z" opacity=".9"/></svg>';
|
|
16
17
|
const ICON_THEME = '<svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M10 3a7 7 0 0 1 0 14z" fill="currentColor"/></svg>';
|
|
17
|
-
|
|
18
|
+
const HUE_OFFSETS = [0, 18, -18, 36, -36];
|
|
19
|
+
/**
|
|
20
|
+
* Tema a partir das cores da marca. Sem cores, mantém a paleta própria do kit.
|
|
21
|
+
* Com uma cor, os capítulos variam levemente em torno dela; com várias, se revezam entre elas.
|
|
22
|
+
* Marcas sem cor (preto/cinza) viram um tema monocromático.
|
|
23
|
+
*/
|
|
24
|
+
function themeFor(brand) {
|
|
25
|
+
const chromatic = brand.colors.filter((c) => !(0, brand_1.isAchromatic)(c)).map((c) => ({ ...c, h: Math.round(c.h) }));
|
|
26
|
+
const ref = chromatic[0] ?? (brand.colors[0] && { ...brand.colors[0], h: Math.round(brand.colors[0].h) });
|
|
27
|
+
if (!ref)
|
|
28
|
+
return { brand, hues: copy_1.CHAPTER_HUES, hue: copy_1.CHAPTER_HUES.welcome, css: "", mono: false };
|
|
29
|
+
const mono = chromatic.length === 0;
|
|
30
|
+
const hues = { ...copy_1.CHAPTER_HUES };
|
|
31
|
+
hues.welcome = hues.finale = ref.h;
|
|
32
|
+
slides_1.CHAPTERS.forEach(([id], i) => {
|
|
33
|
+
hues[id] = chromatic.length > 1 ? chromatic[i % chromatic.length].h : (ref.h + HUE_OFFSETS[i % HUE_OFFSETS.length] + 360) % 360;
|
|
34
|
+
});
|
|
35
|
+
const k = mono ? 0 : Math.round(Math.min(1, ref.s / 55) * 100) / 100;
|
|
36
|
+
const h2 = chromatic[1]?.h ?? (ref.h + 28) % 360;
|
|
37
|
+
const css = `:root{--brand-h:${ref.h};--h2:${h2};--k:${k};--brand-c:hsl(${ref.h} calc(var(--as) * ${k}) var(--al));--brand-on:var(--on-accent)}`;
|
|
38
|
+
return { brand, hues, hue: ref.h, css, mono };
|
|
39
|
+
}
|
|
40
|
+
function shell(lang, title, body, theme, opts = {}) {
|
|
41
|
+
const palette = theme.brand.colors.map(brand_1.toHex).join(" ");
|
|
18
42
|
return `<!doctype html>
|
|
19
|
-
<html lang="${(0, html_1.escapeHtml)(lang)}">
|
|
43
|
+
<html lang="${(0, html_1.escapeHtml)(lang)}"${theme.mono ? " data-mono" : ""}${palette ? ` data-palette="${palette}"` : ""}>
|
|
20
44
|
<head>
|
|
21
45
|
<meta charset="utf-8">
|
|
22
46
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
23
47
|
<meta name="robots" content="noindex">
|
|
24
48
|
<title>${(0, html_1.escapeHtml)(title)}</title>
|
|
49
|
+
${theme.brand.favicon ? `<link rel="icon" href="${(0, html_1.escapeHtml)(theme.brand.favicon)}">` : ""}
|
|
50
|
+
${theme.brand.colors[0] ? `<meta name="theme-color" content="${(0, brand_1.toHex)(theme.brand.colors[0])}">` : ""}
|
|
25
51
|
${assets_1.FONTS}
|
|
26
|
-
<style>${assets_1.CSS}</style>
|
|
52
|
+
<style>${assets_1.CSS}${theme.css}</style>
|
|
27
53
|
</head>
|
|
28
|
-
<body${opts.solo ? ' class="solo"' : ""}>
|
|
54
|
+
<body${opts.solo ? ' class="solo"' : ""}${opts.state ? ` data-ok-state="${opts.state}"` : ""}>
|
|
55
|
+
<div class="backdrop" id="ok-backdrop" style="--bh:${theme.hue}" aria-hidden="true"><i></i><i></i><i></i></div>
|
|
29
56
|
${body}
|
|
30
57
|
<script>${assets_1.JS}</script>
|
|
31
58
|
${opts.mermaid ? `<script src="${assets_1.MERMAID_URL}" onload="window.__okMermaid&&window.__okMermaid()"></script>` : ""}
|
|
32
59
|
</body>
|
|
33
60
|
</html>`;
|
|
34
61
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
62
|
+
function logoImg(theme, cls) {
|
|
63
|
+
return theme.brand.logo ? `<span class="${cls}"><img src="${(0, html_1.escapeHtml)(theme.brand.logo)}" alt=""></span>` : "";
|
|
64
|
+
}
|
|
65
|
+
function soloHead(theme, name, heading, body) {
|
|
66
|
+
const kicker = `<p class="kicker"><span class="num">👋</span>${(0, html_1.escapeHtml)(name ?? "onboarding-kit")}</p>`;
|
|
67
|
+
return `${logoImg(theme, "solo-logo")}${kicker}<h1>${(0, html_1.escapeHtml)(heading)}</h1>${body ? `<p>${(0, html_1.escapeHtml)(body)}</p>` : ""}`;
|
|
68
|
+
}
|
|
69
|
+
function promptBlock(t, prompt) {
|
|
70
|
+
return `<ol class="solo-steps">${t.pending.steps.map((step, i) => `<li><b>${i + 1}</b><span>${(0, html_1.escapeHtml)(step)}</span></li>`).join("")}</ol>
|
|
71
|
+
<div class="prompt-box"><pre class="prompt-text">${(0, html_1.escapeHtml)(prompt)}</pre><button type="button" class="cta copy-cta" data-copy="${(0, html_1.escapeHtml)(prompt)}" data-done="✓ ${(0, html_1.escapeHtml)(t.pending.copied)}">${(0, html_1.escapeHtml)(t.pending.copy)}</button></div>`;
|
|
40
72
|
}
|
|
41
|
-
function
|
|
73
|
+
function waiting(t) {
|
|
74
|
+
return `<p class="waiting" aria-live="polite"><span class="pulse" aria-hidden="true"></span>${(0, html_1.escapeHtml)(t.pending.waiting)}</p>`;
|
|
75
|
+
}
|
|
76
|
+
/** Seções do JSON acompanhadas na tela de espera enquanto a IA preenche. */
|
|
77
|
+
const PROGRESS_SECTIONS = ["project", "business", "features", "domain", "stack", "architecture", "gettingStarted", "conventions", "tours", "gotchas", "decisions", "firstTasks", "brand"];
|
|
78
|
+
function progressBlock(data, t) {
|
|
79
|
+
const items = PROGRESS_SECTIONS.map((key) => ({ key, done: !(0, load_1.isBlank)(data[key]) }));
|
|
80
|
+
const done = items.filter((i) => i.done).length;
|
|
81
|
+
return `<div class="progress" id="ok-progress"><p class="mini-label">${(0, html_1.escapeHtml)(t.pending.progress.replace("{done}", String(done)).replace("{total}", String(items.length)))}</p><div class="progress-bar"><span style="width:${Math.round((done / items.length) * 100)}%"></span></div><ul class="progress-list">${items
|
|
82
|
+
.map((i) => `<li data-key="${i.key}"${i.done ? ' class="is-done"' : ""}>${(0, html_1.escapeHtml)(t.pending.sections[i.key])}</li>`)
|
|
83
|
+
.join("")}</ul></div>`;
|
|
84
|
+
}
|
|
85
|
+
function soloPage(lang, title, inner, theme, state) {
|
|
86
|
+
return shell(lang, title, `<div class="solo-wrap" style="--h:${theme.hue}"><div class="solo-card">${inner}</div></div>`, theme, { solo: true, state });
|
|
87
|
+
}
|
|
88
|
+
function renderMissingPage(root) {
|
|
42
89
|
const t = (0, copy_1.copyFor)("pt-BR");
|
|
43
|
-
|
|
90
|
+
const theme = themeFor((0, brand_1.resolveBrand)(undefined, root));
|
|
91
|
+
const name = (0, brand_1.packageName)(root);
|
|
92
|
+
const inner = `${soloHead(theme, name, t.missing.title, t.missing.body)}${promptBlock(t, (0, template_1.aiPrompt)(undefined, { init: true }))}${waiting(t)}<p class="manual">${(0, html_1.escapeHtml)(t.missing.manual)} <code>npx onboarding-kit init</code></p>`;
|
|
93
|
+
return soloPage("pt-BR", name ? `${name} · Onboarding` : "Onboarding", inner, theme, "missing");
|
|
44
94
|
}
|
|
45
|
-
function renderErrorPage(message) {
|
|
95
|
+
function renderErrorPage(message, root) {
|
|
46
96
|
const t = (0, copy_1.copyFor)("pt-BR");
|
|
47
|
-
|
|
97
|
+
const theme = themeFor((0, brand_1.resolveBrand)(undefined, root));
|
|
98
|
+
const name = (0, brand_1.packageName)(root);
|
|
99
|
+
const inner = `${soloHead(theme, name, t.invalid.title, t.invalid.body)}<pre class="error-text">${(0, html_1.escapeHtml)(message)}</pre>${waiting(t)}`;
|
|
100
|
+
return soloPage("pt-BR", "Onboarding", inner, theme, "error");
|
|
101
|
+
}
|
|
102
|
+
function renderPendingPage(data, root, lang, t) {
|
|
103
|
+
const theme = themeFor((0, brand_1.resolveBrand)(data, root));
|
|
104
|
+
const name = data.project?.name?.trim() || (0, brand_1.packageName)(root);
|
|
105
|
+
const inner = `${soloHead(theme, name, t.pending.title, t.pending.body)}${promptBlock(t, (0, template_1.aiPrompt)())}${waiting(t)}${progressBlock(data, t)}`;
|
|
106
|
+
return soloPage(lang, name ? `${name} · Onboarding` : "Onboarding", inner, theme, "pending");
|
|
48
107
|
}
|
|
49
|
-
function welcomeSlide(data, t, chapters, minutes) {
|
|
108
|
+
function welcomeSlide(data, t, chapters, minutes, theme) {
|
|
50
109
|
const p = data.project;
|
|
51
110
|
const name = p?.name?.trim() || "Onboarding";
|
|
52
111
|
const generated = data.meta?.generatedAt ? new Date(data.meta.generatedAt) : undefined;
|
|
@@ -61,13 +120,14 @@ function welcomeSlide(data, t, chapters, minutes) {
|
|
|
61
120
|
].filter(Boolean);
|
|
62
121
|
const journey = chapters
|
|
63
122
|
.filter((c) => c.id !== "welcome" && c.id !== "finale")
|
|
64
|
-
.map((c) => `<li><button type="button" data-go="${c.start}" style="--ch:${
|
|
123
|
+
.map((c) => `<li><button type="button" data-go="${c.start}" style="--ch:${theme.hues[c.id]}"><span class="n">${c.num}</span>${(0, html_1.escapeHtml)(c.name)}</button></li>`)
|
|
65
124
|
.join("");
|
|
66
125
|
return {
|
|
67
126
|
chapter: "welcome",
|
|
68
127
|
kind: "welcome",
|
|
69
128
|
title: t.chapters.welcome,
|
|
70
129
|
html: `<div class="welcome">
|
|
130
|
+
${logoImg(theme, "brand-logo")}
|
|
71
131
|
<p class="hi"><span class="wave" aria-hidden="true">👋</span>${(0, html_1.escapeHtml)(t.welcome.hi)}</p>
|
|
72
132
|
<h1 class="title">${(0, html_1.escapeHtml)(t.welcome.title("")).trim()} <span class="grad">${(0, html_1.escapeHtml)(name)}</span></h1>
|
|
73
133
|
${p?.tagline ? `<p class="tagline">${(0, html_1.text)(p.tagline)}</p>` : ""}
|
|
@@ -78,10 +138,10 @@ ${journey ? `<div class="journey-wrap"><p class="mini-label">${(0, html_1.escape
|
|
|
78
138
|
</div>`,
|
|
79
139
|
};
|
|
80
140
|
}
|
|
81
|
-
function finaleSlide(t, chapters, hasTasks) {
|
|
141
|
+
function finaleSlide(t, chapters, hasTasks, theme) {
|
|
82
142
|
const recap = chapters
|
|
83
143
|
.filter((c) => c.id !== "welcome" && c.id !== "finale")
|
|
84
|
-
.map((c) => `<li style="--ch:${
|
|
144
|
+
.map((c) => `<li style="--ch:${theme.hues[c.id]}">${(0, html_1.escapeHtml)(c.name)}</li>`)
|
|
85
145
|
.join("");
|
|
86
146
|
return {
|
|
87
147
|
chapter: "finale",
|
|
@@ -97,13 +157,14 @@ ${hasTasks ? `<p class="task-count" data-task-count data-template="${(0, html_1.
|
|
|
97
157
|
</div>`,
|
|
98
158
|
};
|
|
99
159
|
}
|
|
100
|
-
function renderOnboarding(data) {
|
|
160
|
+
function renderOnboarding(data, options = {}) {
|
|
101
161
|
const lang = data.meta?.language || "pt-BR";
|
|
102
162
|
const t = (0, copy_1.copyFor)(lang);
|
|
103
163
|
const name = data.project?.name?.trim() || "Onboarding";
|
|
104
164
|
if (data.meta?.status !== "filled") {
|
|
105
|
-
return
|
|
165
|
+
return renderPendingPage(data, options.root, lang, t);
|
|
106
166
|
}
|
|
167
|
+
const theme = themeFor((0, brand_1.resolveBrand)(data, options.root));
|
|
107
168
|
// 1. capítulos do meio, numerados só quando têm conteúdo
|
|
108
169
|
const middle = [];
|
|
109
170
|
let num = 1;
|
|
@@ -126,12 +187,13 @@ function renderOnboarding(data) {
|
|
|
126
187
|
const total = cursor + 1;
|
|
127
188
|
const minutes = Math.max(3, Math.round(total * 0.4));
|
|
128
189
|
const hasTasks = middle.some((c) => c.id === "mission");
|
|
129
|
-
const slides = [welcomeSlide(data, t, chapters, minutes), ...middle.flatMap((c) => c.slides), finaleSlide(t, chapters, hasTasks)];
|
|
190
|
+
const slides = [welcomeSlide(data, t, chapters, minutes, theme), ...middle.flatMap((c) => c.slides), finaleSlide(t, chapters, hasTasks, theme)];
|
|
191
|
+
const chapterNum = (id) => (id === "welcome" ? "👋" : id === "finale" ? "🎉" : String(chapters.find((c) => c.id === id)?.num ?? ""));
|
|
130
192
|
const slideHtml = slides
|
|
131
|
-
.map((s, i) => `<section class="slide" data-kind="${(0, html_1.escapeHtml)(s.kind ?? s.chapter)}" data-chapter="${s.chapter}" data-chapter-name="${(0, html_1.escapeHtml)(t.chapters[s.chapter])}" style="--h:${
|
|
193
|
+
.map((s, i) => `<section class="slide" data-kind="${(0, html_1.escapeHtml)(s.kind ?? s.chapter)}" data-chapter="${s.chapter}" data-chapter-name="${(0, html_1.escapeHtml)(t.chapters[s.chapter])}" data-chapter-num="${chapterNum(s.chapter)}" style="--h:${theme.hues[s.chapter]}" aria-roledescription="slide" aria-label="${i + 1} / ${total}" aria-hidden="true"><div class="slide-inner">${s.html}</div></section>`)
|
|
132
194
|
.join("\n");
|
|
133
195
|
const segments = chapters
|
|
134
|
-
.map((c) => `<button type="button" class="seg" data-go="${c.start}" data-start="${c.start}" data-count="${c.count}" style="--h:${
|
|
196
|
+
.map((c) => `<button type="button" class="seg" data-go="${c.start}" data-start="${c.start}" data-count="${c.count}" style="--h:${theme.hues[c.id]};--w:${c.count}" title="${(0, html_1.escapeHtml)(c.name)}" aria-label="${(0, html_1.escapeHtml)(c.name)}"><span></span></button>`)
|
|
135
197
|
.join("");
|
|
136
198
|
let index = 0;
|
|
137
199
|
const overview = chapters
|
|
@@ -140,18 +202,19 @@ function renderOnboarding(data) {
|
|
|
140
202
|
.slice(c.start, c.start + c.count)
|
|
141
203
|
.map((s) => `<button type="button" data-go="${index++}">${(0, html_1.escapeHtml)(s.title)}</button>`)
|
|
142
204
|
.join("");
|
|
143
|
-
return `<div class="ov-chapter" style="--ch:${
|
|
205
|
+
return `<div class="ov-chapter" style="--ch:${theme.hues[c.id]}"><h3><span class="n">${chapterNum(c.id)}</span>${(0, html_1.escapeHtml)(c.name)}</h3>${items}</div>`;
|
|
144
206
|
})
|
|
145
207
|
.join("");
|
|
146
208
|
const body = `<div class="deck">
|
|
147
209
|
<header class="topbar">
|
|
148
|
-
<span class="brand">${(0, html_1.escapeHtml)(name)}</span>
|
|
210
|
+
<span class="brand">${logoImg(theme, "brand-mark")}<span class="brand-name">${(0, html_1.escapeHtml)(name)}</span></span>
|
|
149
211
|
<nav class="segments" aria-label="${(0, html_1.escapeHtml)(t.ui.chapters)}">${segments}</nav>
|
|
150
212
|
<button type="button" class="icon-btn" data-overview title="${(0, html_1.escapeHtml)(t.ui.overview)} (O)" aria-label="${(0, html_1.escapeHtml)(t.ui.overview)}">${ICON_GRID}</button>
|
|
151
213
|
<button type="button" class="icon-btn" data-theme-toggle title="${(0, html_1.escapeHtml)(t.ui.theme)}" aria-label="${(0, html_1.escapeHtml)(t.ui.theme)}">${ICON_THEME}</button>
|
|
152
214
|
</header>
|
|
153
215
|
<main class="stage" id="ok-stage">
|
|
154
216
|
${slideHtml}
|
|
217
|
+
<div class="wipe" id="ok-wipe" aria-hidden="true"><span></span><strong></strong></div>
|
|
155
218
|
</main>
|
|
156
219
|
<footer class="controls">
|
|
157
220
|
<button type="button" class="nav-btn" data-prev aria-label="${(0, html_1.escapeHtml)(t.ui.prev)}">← <span class="txt">${(0, html_1.escapeHtml)(t.ui.prev)}</span></button>
|
|
@@ -163,5 +226,5 @@ ${slideHtml}
|
|
|
163
226
|
<div class="overview-inner"><div class="overview-head"><h2>${(0, html_1.escapeHtml)(t.ui.overview)}</h2><button type="button" class="icon-btn" data-close aria-label="${(0, html_1.escapeHtml)(t.ui.close)}">✕</button></div><div class="ov-grid">${overview}</div></div>
|
|
164
227
|
</div>
|
|
165
228
|
<div class="toast" id="ok-toast" data-template="${(0, html_1.escapeHtml)(t.ui.resume)}" role="status"><span></span><button type="button" class="yes" data-resume="0">${(0, html_1.escapeHtml)(t.ui.resumeYes)}</button><button type="button" data-dismiss>${(0, html_1.escapeHtml)(t.ui.resumeNo)}</button></div>`;
|
|
166
|
-
return shell(lang, `${name} · Onboarding`, body, { mermaid: slides.some((s) => s.kind === "diagram") && !(0, load_1.isBlank)(data.architecture?.diagram) });
|
|
229
|
+
return shell(lang, `${name} · Onboarding`, body, theme, { mermaid: slides.some((s) => s.kind === "diagram") && !(0, load_1.isBlank)(data.architecture?.diagram) });
|
|
167
230
|
}
|
package/dist/template.d.ts
CHANGED
|
@@ -5,5 +5,10 @@ export interface TemplateOptions {
|
|
|
5
5
|
language?: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function createTemplate({ framework, language }: TemplateOptions): OnboardingData;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Frase para colar na IA local. Com `init`, pede para a IA criar o arquivo antes
|
|
10
|
+
* (usado quando o onboarding.json ainda não existe).
|
|
11
|
+
*/
|
|
12
|
+
export declare function aiPrompt(file?: string, options?: {
|
|
13
|
+
init?: boolean;
|
|
14
|
+
}): string;
|
package/dist/template.js
CHANGED
|
@@ -21,6 +21,7 @@ const SECTION_INSTRUCTIONS = {
|
|
|
21
21
|
tours: "1 a 3 tours guiados pelo código, ex.: 'Como uma requisição é atendida', 'Ciclo de vida de um pedido'. Cada step aponta um arquivo (com linha quando ajudar) e explica o que olhar ali. Ordene os steps na sequência de leitura.",
|
|
22
22
|
gotchas: "Armadilhas e cuidados: comportamentos não óbvios, partes frágeis, workarounds, TODOs/FIXMEs importantes, coisas que costumam quebrar. Só inclua o que tiver evidence.",
|
|
23
23
|
decisions: "Decisões de arquitetura perceptíveis no código ou na documentação (ex.: por que tal ORM, monorepo, cache, autenticação). context: o problema. decision: o que foi escolhido. Não invente motivos: se o motivo não está documentado, descreva só a decisão.",
|
|
24
|
+
brand: "Identidade visual, usada para a apresentação ter a cara da plataforma. primaryColor: a cor principal da marca em hex (#RRGGBB). Procure em tailwind.config (theme.extend.colors: brand, primary), no @theme do Tailwind v4 (--color-primary, --color-brand), em variáveis CSS de globals.css/index.css (--primary, --brand; converta HSL/OKLCH para hex), em temas de MUI/Chakra/styled-components (palette.primary.main) e no theme_color do manifest. palette: 1 a 4 outras cores da marca em hex (secundária, destaque). Ignore branco, preto e cinzas quando houver uma cor de verdade. logo: caminho do logo do projeto (procure em public/, assets/, src/assets/, app/icon.*); prefira SVG e a versão para fundo claro; ignore os arquivos de exemplo do framework (next.svg, vercel.svg, vite.svg, react.svg). favicon: caminho do favicon (public/favicon.ico, app/favicon.ico, app/icon.png). evidence: arquivos de onde tirou as cores. Deixe vazio o que não encontrar: o onboarding-kit tenta detectar sozinho.",
|
|
24
25
|
firstTasks: "3 a 5 tarefas pequenas e seguras para alguém novo começar (ex.: um teste faltando, um TODO simples, uma melhoria de mensagem de erro), com os arquivos envolvidos.",
|
|
25
26
|
};
|
|
26
27
|
function createTemplate({ framework, language = "pt-BR" }) {
|
|
@@ -80,9 +81,16 @@ function createTemplate({ framework, language = "pt-BR" }) {
|
|
|
80
81
|
gotchas: [{ title: "", description: "", evidence: [""] }],
|
|
81
82
|
decisions: [{ title: "", context: "", decision: "", evidence: [""] }],
|
|
82
83
|
firstTasks: [{ title: "", description: "", files: [""] }],
|
|
84
|
+
brand: { primaryColor: "", palette: [""], logo: "", favicon: "", evidence: [""] },
|
|
83
85
|
};
|
|
84
86
|
}
|
|
85
|
-
/**
|
|
86
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Frase para colar na IA local. Com `init`, pede para a IA criar o arquivo antes
|
|
89
|
+
* (usado quando o onboarding.json ainda não existe).
|
|
90
|
+
*/
|
|
91
|
+
function aiPrompt(file = "onboarding.json", options = {}) {
|
|
92
|
+
if (options.init) {
|
|
93
|
+
return `Rode "npx onboarding-kit init" na raiz deste projeto para criar o ${file}. Depois leia o arquivo e siga as instruções em "_instructions" para preenchê-lo com base neste repositório. No final, rode "npx onboarding-kit validate" e corrija o que ele apontar.`;
|
|
94
|
+
}
|
|
87
95
|
return `Leia o arquivo ${file} e siga as instruções em "_instructions" para preenchê-lo com base neste repositório. No final, rode "npx onboarding-kit validate" e corrija o que ele apontar.`;
|
|
88
96
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -121,6 +121,17 @@ export interface FirstTask {
|
|
|
121
121
|
description: string;
|
|
122
122
|
files: Evidence[];
|
|
123
123
|
}
|
|
124
|
+
export interface BrandInfo {
|
|
125
|
+
/** Cor principal da marca em hex (#RRGGBB). */
|
|
126
|
+
primaryColor: string;
|
|
127
|
+
/** Outras cores da paleta em hex. */
|
|
128
|
+
palette: string[];
|
|
129
|
+
/** Caminho relativo do logo (ex.: public/logo.svg). */
|
|
130
|
+
logo: string;
|
|
131
|
+
/** Caminho relativo do favicon (ex.: public/favicon.ico). */
|
|
132
|
+
favicon: string;
|
|
133
|
+
evidence: Evidence[];
|
|
134
|
+
}
|
|
124
135
|
export interface OnboardingData {
|
|
125
136
|
$schema?: string;
|
|
126
137
|
_instructions?: unknown;
|
|
@@ -141,6 +152,8 @@ export interface OnboardingData {
|
|
|
141
152
|
gotchas: Gotcha[];
|
|
142
153
|
decisions: Decision[];
|
|
143
154
|
firstTasks: FirstTask[];
|
|
155
|
+
/** Cores e logo do projeto, usados para dar a cara da marca à apresentação. Opcional. */
|
|
156
|
+
brand?: BrandInfo;
|
|
144
157
|
}
|
|
145
158
|
export interface OnboardingOptions {
|
|
146
159
|
/** Caminho do onboarding.json. Padrão: "<cwd>/onboarding.json". */
|
package/dist/validate.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.parseEvidence = parseEvidence;
|
|
|
4
4
|
exports.validateOnboarding = validateOnboarding;
|
|
5
5
|
const node_fs_1 = require("node:fs");
|
|
6
6
|
const node_path_1 = require("node:path");
|
|
7
|
+
const brand_1 = require("./brand");
|
|
7
8
|
const load_1 = require("./load");
|
|
8
9
|
/** Campo que identifica cada item e precisa estar preenchido. */
|
|
9
10
|
const ARRAY_SECTIONS = [
|
|
@@ -85,7 +86,7 @@ function walkEvidence(value, at, root, issues) {
|
|
|
85
86
|
if (EVIDENCE_KEYS.has(key) && Array.isArray(v)) {
|
|
86
87
|
v.forEach((ref, i) => checkEvidence(ref, `${here}[${i}]`, root, issues));
|
|
87
88
|
}
|
|
88
|
-
else if (key === "file" && typeof v === "string") {
|
|
89
|
+
else if ((key === "file" || key === "logo" || key === "favicon") && typeof v === "string") {
|
|
89
90
|
checkEvidence(v, here, root, issues);
|
|
90
91
|
}
|
|
91
92
|
else {
|
|
@@ -169,6 +170,17 @@ function validateOnboarding(data, options) {
|
|
|
169
170
|
});
|
|
170
171
|
}
|
|
171
172
|
}
|
|
173
|
+
const brand = data.brand;
|
|
174
|
+
if (brand !== undefined && !isObj(brand)) {
|
|
175
|
+
err("brand", "precisa ser um objeto");
|
|
176
|
+
}
|
|
177
|
+
else if (isObj(brand)) {
|
|
178
|
+
const colors = [["brand.primaryColor", brand.primaryColor], ...(Array.isArray(brand.palette) ? brand.palette.map((c, i) => [`brand.palette[${i}]`, c]) : [])];
|
|
179
|
+
for (const [path, value] of colors) {
|
|
180
|
+
if (typeof value === "string" && !(0, load_1.isBlank)(value) && !(0, brand_1.parseColor)(value))
|
|
181
|
+
warn(String(path), `cor não reconhecida: "${value}" (use hex, ex.: #4f46e5)`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
172
184
|
if (!isObj(data.gettingStarted))
|
|
173
185
|
err("gettingStarted", "seção ausente");
|
|
174
186
|
if (!isObj(data.domain))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "onboarding-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Onboarding de projetos gerado pela sua IA local: um JSON descreve o projeto e o onboarding-kit o apresenta em /onboarding.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,9 +29,15 @@
|
|
|
29
29
|
},
|
|
30
30
|
"typesVersions": {
|
|
31
31
|
"*": {
|
|
32
|
-
"node": [
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
"node": [
|
|
33
|
+
"./dist/adapters/node.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"next": [
|
|
36
|
+
"./dist/adapters/next.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"nest": [
|
|
39
|
+
"./dist/adapters/nest.d.ts"
|
|
40
|
+
]
|
|
35
41
|
}
|
|
36
42
|
},
|
|
37
43
|
"files": [
|