onboarding-kit 0.0.0-stage → 0.1.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 +89 -2
- package/dist/adapters/nest.d.ts +15 -0
- package/dist/adapters/nest.js +53 -0
- package/dist/adapters/next.d.ts +23 -0
- package/dist/adapters/next.js +41 -0
- package/dist/adapters/node.d.ts +10 -0
- package/dist/adapters/node.js +33 -0
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +272 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +57 -0
- package/dist/load.d.ts +19 -0
- package/dist/load.js +59 -0
- package/dist/render/assets.d.ts +4 -0
- package/dist/render/assets.js +381 -0
- package/dist/render/copy.d.ts +132 -0
- package/dist/render/copy.js +290 -0
- package/dist/render/html.d.ts +12 -0
- package/dist/render/html.js +74 -0
- package/dist/render/index.d.ts +5 -0
- package/dist/render/index.js +167 -0
- package/dist/render/slides.d.ts +19 -0
- package/dist/render/slides.js +232 -0
- package/dist/template.d.ts +9 -0
- package/dist/template.js +88 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.js +6 -0
- package/dist/validate.d.ts +17 -0
- package/dist/validate.js +224 -0
- package/package.json +69 -4
package/README.md
CHANGED
|
@@ -1,3 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# onboarding-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Onboarding de projetos gerado pela **sua IA local** e exibido em `/onboarding`, dentro do próprio app.
|
|
4
|
+
|
|
5
|
+
1. O `onboarding-kit` cria um `onboarding.json` com todos os campos em branco e instruções para a IA.
|
|
6
|
+
2. Você pede para a IA que já usa no projeto (Claude Code, Cursor, Copilot…) ler e preencher o arquivo.
|
|
7
|
+
3. O `onboarding-kit` valida o resultado e mostra uma apresentação do projeto em `/onboarding`.
|
|
8
|
+
|
|
9
|
+
Funciona com **Next.js**, **NestJS** e **Node.js** (Express, http nativo e similares). Não tem dependências em runtime.
|
|
10
|
+
|
|
11
|
+
## Começando
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm i -D onboarding-kit
|
|
15
|
+
npx onboarding-kit init
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
O `init` detecta o framework e:
|
|
19
|
+
|
|
20
|
+
- **Next.js (App Router):** cria `app/onboarding/route.ts` (ou `src/app/...`).
|
|
21
|
+
- **NestJS:** adiciona `OnboardingModule.register()` em `src/app.module.ts`.
|
|
22
|
+
- **Node.js:** mostra o trecho para registrar a rota no seu servidor.
|
|
23
|
+
|
|
24
|
+
Depois, envie isto para a sua IA:
|
|
25
|
+
|
|
26
|
+
> Leia o arquivo onboarding.json 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.
|
|
27
|
+
|
|
28
|
+
Para ver o resultado, abra `/onboarding` com o app rodando em dev, ou rode `npx onboarding-kit serve`.
|
|
29
|
+
|
|
30
|
+
## Comandos
|
|
31
|
+
|
|
32
|
+
| Comando | O que faz |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `init` | Cria o `onboarding.json` e liga a rota. Opções: `--framework`, `--lang`, `--no-route`, `--force` |
|
|
35
|
+
| `prompt` | Mostra o texto para enviar à IA |
|
|
36
|
+
| `validate` | Confere campos obrigatórios, itens-modelo esquecidos, se as evidências existem, se há segredos e se o JSON está atrás do commit atual. Sai com código 1 em caso de erro (útil na CI) |
|
|
37
|
+
| `serve` | Abre a apresentação em `http://localhost:4321` (`--port` para mudar) |
|
|
38
|
+
|
|
39
|
+
## A apresentação
|
|
40
|
+
|
|
41
|
+
A página `/onboarding` é um tour em slides, contado como uma história: boas-vindas → o porquê → o negócio → o produto → vocabulário (com cartões que viram e um quiz) → regras do jogo → stack → arquitetura → rodando na máquina → jeito da casa → tour pelo código → cuidados → decisões → primeira missão → final.
|
|
42
|
+
|
|
43
|
+
Navegação por ← →, espaço, swipe no celular ou pelos segmentos coloridos no topo. `O` abre o índice. O progresso e as tarefas marcadas ficam salvos no navegador. Tem tema claro e escuro. Capítulos sem conteúdo são pulados e listas longas viram vários slides.
|
|
44
|
+
|
|
45
|
+
## O que vai no JSON
|
|
46
|
+
|
|
47
|
+
`project`, `business` (modelo de negócio, valor entregue, perfis e jornadas), `gettingStarted`, `stack`, `architecture` (com diagrama Mermaid), `conventions`, `domain` (glossário e regras de negócio), `features`, `tours` pelo código, `gotchas`, `decisions` e `firstTasks`.
|
|
48
|
+
|
|
49
|
+
Toda afirmação aponta para arquivos reais (`evidence: ["src/orders/orders.service.ts:42"]`). Quando `project.repoUrl` está preenchido, essas referências viram links para o código.
|
|
50
|
+
|
|
51
|
+
As chaves que começam com `_` (como `_instructions`) são só para a IA: a página as ignora. Deixe-as no arquivo, porque a IA usa essas instruções para **atualizar** o onboarding depois (com `git diff` desde `meta.commit`).
|
|
52
|
+
|
|
53
|
+
## Uso manual dos adapters
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// Next.js — app/onboarding/route.ts
|
|
57
|
+
import { createOnboardingRoute } from "onboarding-kit/next";
|
|
58
|
+
export const runtime = "nodejs";
|
|
59
|
+
export const dynamic = "force-dynamic";
|
|
60
|
+
export const GET = createOnboardingRoute();
|
|
61
|
+
|
|
62
|
+
// Next.js Pages Router — pages/api/onboarding.ts (+ rewrite de /onboarding)
|
|
63
|
+
import { createOnboardingApiHandler } from "onboarding-kit/next";
|
|
64
|
+
export default createOnboardingApiHandler();
|
|
65
|
+
|
|
66
|
+
// NestJS — app.module.ts (com globalPrefix "api", a rota fica /api/onboarding)
|
|
67
|
+
import { OnboardingModule } from "onboarding-kit/nest";
|
|
68
|
+
@Module({ imports: [OnboardingModule.register({ path: "onboarding" })] })
|
|
69
|
+
|
|
70
|
+
// Express / Node
|
|
71
|
+
import { createOnboardingHandler } from "onboarding-kit/node";
|
|
72
|
+
app.get("/onboarding", createOnboardingHandler());
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Todos os adapters aceitam `{ file?: string; enabled?: boolean }`.
|
|
76
|
+
|
|
77
|
+
## Segurança
|
|
78
|
+
|
|
79
|
+
- A rota fica **desligada quando `NODE_ENV=production`**. Para ligar de propósito, passe `enabled: true`.
|
|
80
|
+
- A página envia `noindex` e não usa cache.
|
|
81
|
+
- O `validate` procura tokens, chaves privadas, JWTs e URLs com senha.
|
|
82
|
+
- Todo o conteúdo do JSON passa por escape antes de entrar no HTML.
|
|
83
|
+
|
|
84
|
+
## Desenvolvimento
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install
|
|
88
|
+
npm test # compila e roda os testes
|
|
89
|
+
node dist/cli/index.js serve # mostra o onboarding do próprio onboarding-kit
|
|
90
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { DynamicModule } from "@nestjs/common";
|
|
2
|
+
import type { OnboardingOptions } from "../types";
|
|
3
|
+
export interface OnboardingModuleOptions extends OnboardingOptions {
|
|
4
|
+
/** Caminho da rota. Padrão: "onboarding". O globalPrefix do app, se houver, é aplicado antes. */
|
|
5
|
+
path?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Módulo NestJS que serve a apresentação do projeto.
|
|
9
|
+
*
|
|
10
|
+
* app.module.ts:
|
|
11
|
+
* imports: [OnboardingModule.register()]
|
|
12
|
+
*/
|
|
13
|
+
export declare class OnboardingModule {
|
|
14
|
+
static register(options?: OnboardingModuleOptions): DynamicModule;
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var OnboardingModule_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.OnboardingModule = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const index_1 = require("../index");
|
|
16
|
+
const load_1 = require("../load");
|
|
17
|
+
/**
|
|
18
|
+
* Módulo NestJS que serve a apresentação do projeto.
|
|
19
|
+
*
|
|
20
|
+
* app.module.ts:
|
|
21
|
+
* imports: [OnboardingModule.register()]
|
|
22
|
+
*/
|
|
23
|
+
let OnboardingModule = OnboardingModule_1 = class OnboardingModule {
|
|
24
|
+
static register(options = {}) {
|
|
25
|
+
const { path = "onboarding", ...rest } = options;
|
|
26
|
+
let OnboardingController = class OnboardingController {
|
|
27
|
+
async show() {
|
|
28
|
+
if (!(0, load_1.resolveEnabled)(rest))
|
|
29
|
+
throw new common_1.NotFoundException();
|
|
30
|
+
// Páginas de erro (JSON inválido etc.) também são HTML legível, então sempre devolvemos o corpo.
|
|
31
|
+
const page = await (0, index_1.renderOnboardingPage)(rest.file);
|
|
32
|
+
return page.html;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
__decorate([
|
|
36
|
+
(0, common_1.Get)(),
|
|
37
|
+
(0, common_1.Header)("Content-Type", index_1.HTML_HEADERS["Content-Type"]),
|
|
38
|
+
(0, common_1.Header)("Cache-Control", index_1.HTML_HEADERS["Cache-Control"]),
|
|
39
|
+
(0, common_1.Header)("X-Robots-Tag", index_1.HTML_HEADERS["X-Robots-Tag"]),
|
|
40
|
+
__metadata("design:type", Function),
|
|
41
|
+
__metadata("design:paramtypes", []),
|
|
42
|
+
__metadata("design:returntype", Promise)
|
|
43
|
+
], OnboardingController.prototype, "show", null);
|
|
44
|
+
OnboardingController = __decorate([
|
|
45
|
+
(0, common_1.Controller)(path)
|
|
46
|
+
], OnboardingController);
|
|
47
|
+
return { module: OnboardingModule_1, controllers: [OnboardingController] };
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.OnboardingModule = OnboardingModule;
|
|
51
|
+
exports.OnboardingModule = OnboardingModule = OnboardingModule_1 = __decorate([
|
|
52
|
+
(0, common_1.Module)({})
|
|
53
|
+
], OnboardingModule);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { OnboardingOptions } from "../types";
|
|
2
|
+
export type { OnboardingOptions };
|
|
3
|
+
/**
|
|
4
|
+
* Route handler para o App Router do Next.js.
|
|
5
|
+
*
|
|
6
|
+
* app/onboarding/route.ts:
|
|
7
|
+
* import { createOnboardingRoute } from "onboarding-kit/next";
|
|
8
|
+
* export const runtime = "nodejs";
|
|
9
|
+
* export const dynamic = "force-dynamic";
|
|
10
|
+
* export const GET = createOnboardingRoute();
|
|
11
|
+
*/
|
|
12
|
+
export declare function createOnboardingRoute(options?: OnboardingOptions): () => Promise<Response>;
|
|
13
|
+
/**
|
|
14
|
+
* Handler para o Pages Router (pages/api/onboarding.ts).
|
|
15
|
+
* Para servir em /onboarding, adicione no next.config:
|
|
16
|
+
* async rewrites() { return [{ source: "/onboarding", destination: "/api/onboarding" }]; }
|
|
17
|
+
*/
|
|
18
|
+
export declare function createOnboardingApiHandler(options?: OnboardingOptions): (_req: unknown, res: {
|
|
19
|
+
status(code: number): {
|
|
20
|
+
send(body: string): void;
|
|
21
|
+
};
|
|
22
|
+
setHeader(name: string, value: string): void;
|
|
23
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOnboardingRoute = createOnboardingRoute;
|
|
4
|
+
exports.createOnboardingApiHandler = createOnboardingApiHandler;
|
|
5
|
+
const index_1 = require("../index");
|
|
6
|
+
const load_1 = require("../load");
|
|
7
|
+
/**
|
|
8
|
+
* Route handler para o App Router do Next.js.
|
|
9
|
+
*
|
|
10
|
+
* app/onboarding/route.ts:
|
|
11
|
+
* import { createOnboardingRoute } from "onboarding-kit/next";
|
|
12
|
+
* export const runtime = "nodejs";
|
|
13
|
+
* export const dynamic = "force-dynamic";
|
|
14
|
+
* export const GET = createOnboardingRoute();
|
|
15
|
+
*/
|
|
16
|
+
function createOnboardingRoute(options = {}) {
|
|
17
|
+
return async function GET() {
|
|
18
|
+
if (!(0, load_1.resolveEnabled)(options)) {
|
|
19
|
+
return new Response("Not Found", { status: 404 });
|
|
20
|
+
}
|
|
21
|
+
const page = await (0, index_1.renderOnboardingPage)(options.file);
|
|
22
|
+
return new Response(page.html, { status: page.status, headers: index_1.HTML_HEADERS });
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Handler para o Pages Router (pages/api/onboarding.ts).
|
|
27
|
+
* Para servir em /onboarding, adicione no next.config:
|
|
28
|
+
* async rewrites() { return [{ source: "/onboarding", destination: "/api/onboarding" }]; }
|
|
29
|
+
*/
|
|
30
|
+
function createOnboardingApiHandler(options = {}) {
|
|
31
|
+
return async function handler(_req, res) {
|
|
32
|
+
if (!(0, load_1.resolveEnabled)(options)) {
|
|
33
|
+
res.status(404).send("Not Found");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const page = await (0, index_1.renderOnboardingPage)(options.file);
|
|
37
|
+
for (const [name, value] of Object.entries(index_1.HTML_HEADERS))
|
|
38
|
+
res.setHeader(name, value);
|
|
39
|
+
res.status(page.status).send(page.html);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { OnboardingOptions } from "../types";
|
|
3
|
+
export type { OnboardingOptions };
|
|
4
|
+
/**
|
|
5
|
+
* Handler para Node.js puro, Express, Connect e similares.
|
|
6
|
+
*
|
|
7
|
+
* Express: app.get("/onboarding", createOnboardingHandler());
|
|
8
|
+
* Node: http.createServer((req, res) => req.url === "/onboarding" ? handler(req, res) : ...)
|
|
9
|
+
*/
|
|
10
|
+
export declare function createOnboardingHandler(options?: OnboardingOptions): (_req: IncomingMessage, res: ServerResponse, next?: (err?: unknown) => void) => Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOnboardingHandler = createOnboardingHandler;
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
const load_1 = require("../load");
|
|
6
|
+
/**
|
|
7
|
+
* Handler para Node.js puro, Express, Connect e similares.
|
|
8
|
+
*
|
|
9
|
+
* Express: app.get("/onboarding", createOnboardingHandler());
|
|
10
|
+
* Node: http.createServer((req, res) => req.url === "/onboarding" ? handler(req, res) : ...)
|
|
11
|
+
*/
|
|
12
|
+
function createOnboardingHandler(options = {}) {
|
|
13
|
+
return async function onboardingHandler(_req, res, next) {
|
|
14
|
+
if (!(0, load_1.resolveEnabled)(options)) {
|
|
15
|
+
if (next)
|
|
16
|
+
return next();
|
|
17
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
18
|
+
res.end("Not Found");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const page = await (0, index_1.renderOnboardingPage)(options.file);
|
|
23
|
+
res.writeHead(page.status, index_1.HTML_HEADERS);
|
|
24
|
+
res.end(page.html);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (next)
|
|
28
|
+
return next(err);
|
|
29
|
+
res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
|
|
30
|
+
res.end("Internal Server Error");
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import type { Framework } from "../types";
|
|
3
|
+
export declare function detectFramework(pkg: Record<string, any> | undefined): Framework;
|
|
4
|
+
/** Adiciona o OnboardingModule ao app.module.ts quando dá para fazer isso com segurança. */
|
|
5
|
+
export declare function patchNestAppModule(source: string): string | undefined;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.detectFramework = detectFramework;
|
|
5
|
+
exports.patchNestAppModule = patchNestAppModule;
|
|
6
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_http_1 = require("node:http");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_1 = require("../adapters/node");
|
|
11
|
+
const load_1 = require("../load");
|
|
12
|
+
const template_1 = require("../template");
|
|
13
|
+
const validate_1 = require("../validate");
|
|
14
|
+
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
15
|
+
const paint = (open, close) => (s) => (useColor ? `\x1b[${open}m${s}\x1b[${close}m` : s);
|
|
16
|
+
const c = {
|
|
17
|
+
bold: paint(1, 22),
|
|
18
|
+
dim: paint(2, 22),
|
|
19
|
+
green: paint(32, 39),
|
|
20
|
+
yellow: paint(33, 39),
|
|
21
|
+
red: paint(31, 39),
|
|
22
|
+
cyan: paint(36, 39),
|
|
23
|
+
};
|
|
24
|
+
const HELP = `
|
|
25
|
+
${c.bold("onboarding-kit")} — onboarding do projeto gerado pela sua IA local
|
|
26
|
+
|
|
27
|
+
${c.bold("Uso")}
|
|
28
|
+
npx onboarding-kit init Cria o onboarding.json em branco e a rota /onboarding
|
|
29
|
+
npx onboarding-kit prompt Mostra o texto para enviar à sua IA
|
|
30
|
+
npx onboarding-kit validate Confere o JSON preenchido (campos, evidências, segredos)
|
|
31
|
+
npx onboarding-kit serve Abre a apresentação em http://localhost:4321
|
|
32
|
+
|
|
33
|
+
${c.bold("Opções")}
|
|
34
|
+
--file <caminho> Arquivo de onboarding (padrão: onboarding.json)
|
|
35
|
+
--framework <next|nest|node> Força o framework em vez de detectar (init)
|
|
36
|
+
--lang <idioma> Idioma do conteúdo gerado (init, padrão: pt-BR)
|
|
37
|
+
--no-route Não cria/edita a rota no projeto (init)
|
|
38
|
+
--force Sobrescreve o onboarding.json existente (init)
|
|
39
|
+
--port <porta> Porta do serve (padrão: 4321)
|
|
40
|
+
`;
|
|
41
|
+
function parseArgs(argv) {
|
|
42
|
+
const flags = {};
|
|
43
|
+
let command;
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const arg = argv[i];
|
|
46
|
+
if (arg.startsWith("--")) {
|
|
47
|
+
const [key, inline] = arg.slice(2).split("=", 2);
|
|
48
|
+
if (inline !== undefined)
|
|
49
|
+
flags[key] = inline;
|
|
50
|
+
else if (argv[i + 1] && !argv[i + 1].startsWith("--"))
|
|
51
|
+
flags[key] = argv[++i];
|
|
52
|
+
else
|
|
53
|
+
flags[key] = true;
|
|
54
|
+
}
|
|
55
|
+
else if (arg === "-h") {
|
|
56
|
+
flags.help = true;
|
|
57
|
+
}
|
|
58
|
+
else if (!command) {
|
|
59
|
+
command = arg;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { command, flags };
|
|
63
|
+
}
|
|
64
|
+
function readPackageJson(root) {
|
|
65
|
+
const path = (0, node_path_1.join)(root, "package.json");
|
|
66
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function detectFramework(pkg) {
|
|
76
|
+
const deps = { ...(pkg?.dependencies ?? {}), ...(pkg?.devDependencies ?? {}) };
|
|
77
|
+
if (deps.next)
|
|
78
|
+
return "next";
|
|
79
|
+
if (deps["@nestjs/core"])
|
|
80
|
+
return "nest";
|
|
81
|
+
return "node";
|
|
82
|
+
}
|
|
83
|
+
function gitHead(cwd) {
|
|
84
|
+
try {
|
|
85
|
+
return (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], { cwd, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const NEXT_ROUTE = `import { createOnboardingRoute } from "onboarding-kit/next";
|
|
92
|
+
|
|
93
|
+
// Criado pelo onboarding-kit. Mostra a apresentação do projeto em /onboarding.
|
|
94
|
+
// Fica desligada em produção; para ligar: createOnboardingRoute({ enabled: true }).
|
|
95
|
+
export const runtime = "nodejs";
|
|
96
|
+
export const dynamic = "force-dynamic";
|
|
97
|
+
export const GET = createOnboardingRoute();
|
|
98
|
+
`;
|
|
99
|
+
const NEXT_PAGES_SNIPPET = `// pages/api/onboarding.ts
|
|
100
|
+
import { createOnboardingApiHandler } from "onboarding-kit/next";
|
|
101
|
+
export default createOnboardingApiHandler();
|
|
102
|
+
|
|
103
|
+
// next.config: para servir em /onboarding
|
|
104
|
+
async rewrites() { return [{ source: "/onboarding", destination: "/api/onboarding" }]; }`;
|
|
105
|
+
const NEST_SNIPPET = `// src/app.module.ts
|
|
106
|
+
import { OnboardingModule } from "onboarding-kit/nest";
|
|
107
|
+
|
|
108
|
+
@Module({
|
|
109
|
+
imports: [OnboardingModule.register(), /* ...seus módulos */],
|
|
110
|
+
})`;
|
|
111
|
+
const NODE_SNIPPET = `// Express
|
|
112
|
+
import { createOnboardingHandler } from "onboarding-kit/node";
|
|
113
|
+
app.get("/onboarding", createOnboardingHandler());
|
|
114
|
+
|
|
115
|
+
// http nativo
|
|
116
|
+
const onboarding = createOnboardingHandler();
|
|
117
|
+
if (req.url === "/onboarding") return onboarding(req, res);`;
|
|
118
|
+
function setupNext(root) {
|
|
119
|
+
const appDir = ["src/app", "app"].find((dir) => (0, node_fs_1.existsSync)((0, node_path_1.join)(root, dir)));
|
|
120
|
+
if (!appDir) {
|
|
121
|
+
return [`${c.yellow("!")} Não encontrei a pasta app/ (App Router). Para o Pages Router, adicione:`, "", c.dim(NEXT_PAGES_SNIPPET)];
|
|
122
|
+
}
|
|
123
|
+
const ext = (0, node_fs_1.existsSync)((0, node_path_1.join)(root, "tsconfig.json")) ? "ts" : "js";
|
|
124
|
+
const routeFile = (0, node_path_1.join)(root, appDir, "onboarding", `route.${ext}`);
|
|
125
|
+
const rel = (0, node_path_1.relative)(root, routeFile);
|
|
126
|
+
if ((0, node_fs_1.existsSync)(routeFile))
|
|
127
|
+
return [`${c.dim("•")} ${rel} já existe, mantive como está`];
|
|
128
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(routeFile), { recursive: true });
|
|
129
|
+
(0, node_fs_1.writeFileSync)(routeFile, NEXT_ROUTE);
|
|
130
|
+
return [`${c.green("✓")} Criei ${c.bold(rel)} (rota /onboarding)`];
|
|
131
|
+
}
|
|
132
|
+
/** Adiciona o OnboardingModule ao app.module.ts quando dá para fazer isso com segurança. */
|
|
133
|
+
function patchNestAppModule(source) {
|
|
134
|
+
if (source.includes("OnboardingModule"))
|
|
135
|
+
return source;
|
|
136
|
+
const matches = source.match(/imports\s*:\s*\[/g);
|
|
137
|
+
if (!matches || matches.length !== 1)
|
|
138
|
+
return undefined;
|
|
139
|
+
const withImport = source.replace(/imports\s*:\s*\[(\s*\])?/, (m, empty) => empty ? m.replace(/\[\s*\]$/, "[OnboardingModule.register()]") : `${m}OnboardingModule.register(), `);
|
|
140
|
+
return `import { OnboardingModule } from "onboarding-kit/nest";\n${withImport}`;
|
|
141
|
+
}
|
|
142
|
+
function setupNest(root) {
|
|
143
|
+
const file = (0, node_path_1.join)(root, "src", "app.module.ts");
|
|
144
|
+
if ((0, node_fs_1.existsSync)(file)) {
|
|
145
|
+
const source = (0, node_fs_1.readFileSync)(file, "utf8");
|
|
146
|
+
const patched = patchNestAppModule(source);
|
|
147
|
+
if (patched === source)
|
|
148
|
+
return [`${c.dim("•")} src/app.module.ts já importa o OnboardingModule`];
|
|
149
|
+
if (patched) {
|
|
150
|
+
(0, node_fs_1.writeFileSync)(file, patched);
|
|
151
|
+
return [`${c.green("✓")} Adicionei ${c.bold("OnboardingModule.register()")} em src/app.module.ts (rota /onboarding)`];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return [`${c.yellow("!")} Não consegui editar o app.module.ts sozinho. Adicione:`, "", c.dim(NEST_SNIPPET)];
|
|
155
|
+
}
|
|
156
|
+
function cmdInit(flags) {
|
|
157
|
+
const root = process.cwd();
|
|
158
|
+
const pkg = readPackageJson(root);
|
|
159
|
+
if (!pkg) {
|
|
160
|
+
console.error(c.red("✗ Não encontrei package.json. Rode o comando na raiz do projeto."));
|
|
161
|
+
return 1;
|
|
162
|
+
}
|
|
163
|
+
const forced = typeof flags.framework === "string" ? flags.framework : undefined;
|
|
164
|
+
if (forced && !["next", "nest", "node"].includes(forced)) {
|
|
165
|
+
console.error(c.red(`✗ Framework inválido: ${forced}. Use next, nest ou node.`));
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
const framework = forced ?? detectFramework(pkg);
|
|
169
|
+
const language = typeof flags.lang === "string" ? flags.lang : "pt-BR";
|
|
170
|
+
const file = (0, load_1.resolveFile)(typeof flags.file === "string" ? flags.file : undefined);
|
|
171
|
+
const relFile = (0, node_path_1.relative)(root, file);
|
|
172
|
+
const lines = [`Framework: ${c.bold(framework)}${forced ? "" : c.dim(" (detectado)")}`];
|
|
173
|
+
if ((0, node_fs_1.existsSync)(file) && !flags.force) {
|
|
174
|
+
lines.push(`${c.dim("•")} ${relFile} já existe, mantive como está ${c.dim("(use --force para recriar)")}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
(0, node_fs_1.writeFileSync)(file, JSON.stringify((0, template_1.createTemplate)({ framework, language }), null, 2) + "\n");
|
|
178
|
+
lines.push(`${c.green("✓")} Criei ${c.bold(relFile)} com os campos em branco e as instruções para a IA`);
|
|
179
|
+
}
|
|
180
|
+
if (flags.route !== false && !flags["no-route"]) {
|
|
181
|
+
if (framework === "next")
|
|
182
|
+
lines.push(...setupNext(root));
|
|
183
|
+
else if (framework === "nest")
|
|
184
|
+
lines.push(...setupNest(root));
|
|
185
|
+
else
|
|
186
|
+
lines.push(`${c.dim("•")} Para servir em /onboarding no seu servidor:`, "", c.dim(NODE_SNIPPET));
|
|
187
|
+
}
|
|
188
|
+
const prompt = (0, template_1.aiPrompt)(relFile);
|
|
189
|
+
console.log(`
|
|
190
|
+
${lines.join("\n")}
|
|
191
|
+
|
|
192
|
+
${c.bold("Próximo passo:")} envie para a IA que você usa neste projeto (Claude Code, Cursor, Copilot…):
|
|
193
|
+
|
|
194
|
+
${c.cyan(prompt)}
|
|
195
|
+
|
|
196
|
+
Depois rode ${c.bold("npx onboarding-kit validate")} e acesse ${c.bold("/onboarding")} no app em dev (ou ${c.bold("npx onboarding-kit serve")}).
|
|
197
|
+
`);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
async function cmdValidate(flags) {
|
|
201
|
+
const result = await (0, load_1.loadOnboarding)(typeof flags.file === "string" ? flags.file : undefined);
|
|
202
|
+
if (!result.ok) {
|
|
203
|
+
console.error(c.red(`✗ ${result.message}`));
|
|
204
|
+
if (result.reason === "missing")
|
|
205
|
+
console.error(` Rode ${c.bold("npx onboarding-kit init")} primeiro.`);
|
|
206
|
+
return 1;
|
|
207
|
+
}
|
|
208
|
+
const root = (0, node_path_1.dirname)(result.file);
|
|
209
|
+
const issues = (0, validate_1.validateOnboarding)(result.data, { root, headCommit: gitHead(root) });
|
|
210
|
+
const errors = issues.filter((i) => i.level === "error");
|
|
211
|
+
const warnings = issues.filter((i) => i.level === "warning");
|
|
212
|
+
for (const issue of [...errors, ...warnings]) {
|
|
213
|
+
const mark = issue.level === "error" ? c.red("✗") : c.yellow("!");
|
|
214
|
+
console.log(`${mark} ${c.bold(issue.path || "(raiz)")} ${issue.message}`);
|
|
215
|
+
}
|
|
216
|
+
if (errors.length) {
|
|
217
|
+
console.log(`\n${c.red(`${errors.length} erro(s)`)}, ${warnings.length} aviso(s). Peça à IA para corrigir e rode de novo.`);
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
console.log(`${issues.length ? "\n" : ""}${c.green("✓")} ${(0, node_path_1.relative)(process.cwd(), result.file)} válido${warnings.length ? ` com ${warnings.length} aviso(s)` : ""}.`);
|
|
221
|
+
return 0;
|
|
222
|
+
}
|
|
223
|
+
function cmdServe(flags) {
|
|
224
|
+
const port = Number(flags.port ?? 4321);
|
|
225
|
+
const handler = (0, node_1.createOnboardingHandler)({ file: typeof flags.file === "string" ? flags.file : undefined, enabled: true });
|
|
226
|
+
const server = (0, node_http_1.createServer)((req, res) => {
|
|
227
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
228
|
+
if (path === "/" || path === "/onboarding")
|
|
229
|
+
return void handler(req, res);
|
|
230
|
+
res.writeHead(302, { Location: "/" });
|
|
231
|
+
res.end();
|
|
232
|
+
});
|
|
233
|
+
return new Promise((resolvePromise) => {
|
|
234
|
+
server.on("error", (err) => {
|
|
235
|
+
console.error(c.red(`✗ ${err.code === "EADDRINUSE" ? `A porta ${port} está em uso. Use --port.` : err.message}`));
|
|
236
|
+
resolvePromise(1);
|
|
237
|
+
});
|
|
238
|
+
server.listen(port, "127.0.0.1", () => {
|
|
239
|
+
console.log(`${c.green("✓")} Onboarding em ${c.bold(`http://localhost:${port}`)} ${c.dim("(Ctrl+C para sair)")}`);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function main() {
|
|
244
|
+
const { command, flags } = parseArgs(process.argv.slice(2));
|
|
245
|
+
if (flags["no-route"])
|
|
246
|
+
flags.route = false;
|
|
247
|
+
if (!command || flags.help || command === "help") {
|
|
248
|
+
console.log(HELP);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
switch (command) {
|
|
252
|
+
case "init":
|
|
253
|
+
return cmdInit(flags);
|
|
254
|
+
case "validate":
|
|
255
|
+
return cmdValidate(flags);
|
|
256
|
+
case "serve":
|
|
257
|
+
return cmdServe(flags);
|
|
258
|
+
case "prompt":
|
|
259
|
+
console.log((0, template_1.aiPrompt)(typeof flags.file === "string" ? flags.file : load_1.DEFAULT_FILE));
|
|
260
|
+
return 0;
|
|
261
|
+
default:
|
|
262
|
+
console.error(c.red(`✗ Comando desconhecido: ${command}`));
|
|
263
|
+
console.log(HELP);
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (require.main === module) {
|
|
268
|
+
main().then((code) => {
|
|
269
|
+
if (code !== 0)
|
|
270
|
+
process.exitCode = code;
|
|
271
|
+
});
|
|
272
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export { createTemplate, aiPrompt, SCHEMA_VERSION } from "./template";
|
|
3
|
+
export { loadOnboarding, resolveEnabled, resolveFile, isBlank, DEFAULT_FILE } from "./load";
|
|
4
|
+
export { validateOnboarding, parseEvidence } from "./validate";
|
|
5
|
+
export type { Issue, ValidateOptions } from "./validate";
|
|
6
|
+
export { renderOnboarding, escapeHtml } from "./render";
|
|
7
|
+
export interface RenderedPage {
|
|
8
|
+
status: number;
|
|
9
|
+
html: string;
|
|
10
|
+
}
|
|
11
|
+
/** Lê o onboarding.json a cada chamada e devolve a página pronta (usado pelos adapters). */
|
|
12
|
+
export declare function renderOnboardingPage(file?: string): Promise<RenderedPage>;
|
|
13
|
+
export declare const HTML_HEADERS: {
|
|
14
|
+
readonly "Content-Type": "text/html; charset=utf-8";
|
|
15
|
+
readonly "Cache-Control": "no-store";
|
|
16
|
+
readonly "X-Robots-Tag": "noindex";
|
|
17
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.HTML_HEADERS = exports.escapeHtml = exports.renderOnboarding = exports.parseEvidence = exports.validateOnboarding = exports.DEFAULT_FILE = exports.isBlank = exports.resolveFile = exports.resolveEnabled = exports.loadOnboarding = exports.SCHEMA_VERSION = exports.aiPrompt = exports.createTemplate = void 0;
|
|
18
|
+
exports.renderOnboardingPage = renderOnboardingPage;
|
|
19
|
+
const load_1 = require("./load");
|
|
20
|
+
const render_1 = require("./render");
|
|
21
|
+
__exportStar(require("./types"), exports);
|
|
22
|
+
var template_1 = require("./template");
|
|
23
|
+
Object.defineProperty(exports, "createTemplate", { enumerable: true, get: function () { return template_1.createTemplate; } });
|
|
24
|
+
Object.defineProperty(exports, "aiPrompt", { enumerable: true, get: function () { return template_1.aiPrompt; } });
|
|
25
|
+
Object.defineProperty(exports, "SCHEMA_VERSION", { enumerable: true, get: function () { return template_1.SCHEMA_VERSION; } });
|
|
26
|
+
var load_2 = require("./load");
|
|
27
|
+
Object.defineProperty(exports, "loadOnboarding", { enumerable: true, get: function () { return load_2.loadOnboarding; } });
|
|
28
|
+
Object.defineProperty(exports, "resolveEnabled", { enumerable: true, get: function () { return load_2.resolveEnabled; } });
|
|
29
|
+
Object.defineProperty(exports, "resolveFile", { enumerable: true, get: function () { return load_2.resolveFile; } });
|
|
30
|
+
Object.defineProperty(exports, "isBlank", { enumerable: true, get: function () { return load_2.isBlank; } });
|
|
31
|
+
Object.defineProperty(exports, "DEFAULT_FILE", { enumerable: true, get: function () { return load_2.DEFAULT_FILE; } });
|
|
32
|
+
var validate_1 = require("./validate");
|
|
33
|
+
Object.defineProperty(exports, "validateOnboarding", { enumerable: true, get: function () { return validate_1.validateOnboarding; } });
|
|
34
|
+
Object.defineProperty(exports, "parseEvidence", { enumerable: true, get: function () { return validate_1.parseEvidence; } });
|
|
35
|
+
var render_2 = require("./render");
|
|
36
|
+
Object.defineProperty(exports, "renderOnboarding", { enumerable: true, get: function () { return render_2.renderOnboarding; } });
|
|
37
|
+
Object.defineProperty(exports, "escapeHtml", { enumerable: true, get: function () { return render_2.escapeHtml; } });
|
|
38
|
+
/** Lê o onboarding.json a cada chamada e devolve a página pronta (usado pelos adapters). */
|
|
39
|
+
async function renderOnboardingPage(file) {
|
|
40
|
+
const result = await (0, load_1.loadOnboarding)(file);
|
|
41
|
+
if (!result.ok) {
|
|
42
|
+
return result.reason === "missing"
|
|
43
|
+
? { status: 200, html: (0, render_1.renderMissingPage)() }
|
|
44
|
+
: { status: 500, html: (0, render_1.renderErrorPage)(result.message) };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return { status: 200, html: (0, render_1.renderOnboarding)(result.data) };
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return { status: 500, html: (0, render_1.renderErrorPage)(err.message) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.HTML_HEADERS = {
|
|
54
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
55
|
+
"Cache-Control": "no-store",
|
|
56
|
+
"X-Robots-Tag": "noindex",
|
|
57
|
+
};
|
package/dist/load.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OnboardingData, OnboardingOptions } from "./types";
|
|
2
|
+
export declare const DEFAULT_FILE = "onboarding.json";
|
|
3
|
+
export declare function resolveFile(file?: string): string;
|
|
4
|
+
export declare function resolveEnabled(options?: OnboardingOptions): boolean;
|
|
5
|
+
export type LoadResult = {
|
|
6
|
+
ok: true;
|
|
7
|
+
file: string;
|
|
8
|
+
data: OnboardingData;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
file: string;
|
|
12
|
+
reason: "missing" | "invalid-json";
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function loadOnboarding(file?: string): Promise<LoadResult>;
|
|
16
|
+
/** true quando o valor não tem nenhum texto preenchido (strings vazias, arrays/objetos vazios). */
|
|
17
|
+
export declare function isBlank(value: unknown): boolean;
|
|
18
|
+
/** Remove itens-modelo em branco de um array. */
|
|
19
|
+
export declare function filled<T>(items: T[] | undefined | null): T[];
|