@intelex-ia/sdk 0.2.0 → 0.4.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 +310 -310
- package/dist/autoria.test.d.ts +2 -0
- package/dist/autoria.test.d.ts.map +1 -0
- package/dist/autoria.test.js +113 -0
- package/dist/autoria.test.js.map +1 -0
- package/dist/react.d.ts +89 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +163 -1
- package/dist/react.js.map +1 -1
- package/package.json +64 -64
package/README.md
CHANGED
|
@@ -1,310 +1,310 @@
|
|
|
1
|
-
# `@intelex-ia/sdk`
|
|
2
|
-
|
|
3
|
-
SDK pra consumir a **Content API IntelexIA** em sites headless (Next.js, Astro etc.). Multiplataforma Fase 2b.
|
|
4
|
-
|
|
5
|
-
> **O que você ganha:** `getArticles` / `getArticleBySlug` tipados, `mergePosts()` pra unir conteúdo antigo + novo do painel, `<ArticleJsonLd>` pra injetar schema JSON-LD, e `articleToMetadata` pra plugar no `generateMetadata` do Next.js App Router. Zero dependências (usa `fetch` global).
|
|
6
|
-
|
|
7
|
-
## Instalação rápida (recomendado)
|
|
8
|
-
|
|
9
|
-
Se você está começando agora, use o CLI — ele detecta a estrutura do seu projeto e configura tudo automaticamente:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npx @intelex-ia/cli init
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Cria os arquivos no lugar certo (App Router ou Pages Router, com ou sem `src/`), configura `.env.local`, gera um `INTELEXIA.md` completo na raiz e instala o SDK pra você.
|
|
16
|
-
|
|
17
|
-
## Instalação manual
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npm install @intelex-ia/sdk
|
|
21
|
-
# ou pnpm add @intelex-ia/sdk / yarn add @intelex-ia/sdk
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Configuração
|
|
25
|
-
|
|
26
|
-
1. No painel da IntelexIA, **cadastre o site como "Headless (código)"** (vai pedir domain + URL e secret de revalidate).
|
|
27
|
-
2. Abra o site na lista → seção **"Tokens de API (Content API)"** → **Gerar token**. Você recebe `itx_…` (mostrado uma única vez).
|
|
28
|
-
3. Coloque em `.env.local`:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
INTELEXIA_API_TOKEN=itx_seu_token_aqui
|
|
32
|
-
INTELEXIA_API_URL=https://api.innovarticles.intelexia.com.br # opcional, é o default
|
|
33
|
-
INTELEXIA_REVALIDATE_SECRET=cole-o-mesmo-segredo-do-painel
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Três modos de integração
|
|
37
|
-
|
|
38
|
-
Cada cliente tem uma situação diferente. Escolha o modo que faz sentido:
|
|
39
|
-
|
|
40
|
-
| Modo | Quando usar | O que muda no `/blog` |
|
|
41
|
-
|---|---|---|
|
|
42
|
-
| **🔀 Additive (recomendado)** | Cliente já tem posts antigos e quer **adicionar** os do painel sem perder os antigos | `mergePosts()` une os dois feeds, dedupa por slug, ordena por data |
|
|
43
|
-
| **🔁 Replace** | Cliente migrou tudo pro painel — só os novos contam | Substitui `getAllPosts()` por `client.getArticles()` |
|
|
44
|
-
| **🆕 Isolated** | Cliente quer manter `/blog` antigo intacto e criar rota nova só pra IA | Cria `/blog/ia` separado, blog antigo zero alterado |
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
### Modo 1: Additive (cliente existente — caso mais comum)
|
|
49
|
-
|
|
50
|
-
```tsx
|
|
51
|
-
// app/blog/page.tsx
|
|
52
|
-
import { intelexia } from "@/lib/intelexia";
|
|
53
|
-
import { mergePosts } from "@intelex-ia/sdk";
|
|
54
|
-
import { posts as localPosts } from "@/lib/posts"; // seu array atual
|
|
55
|
-
import Link from "next/link";
|
|
56
|
-
|
|
57
|
-
export default async function BlogIndex() {
|
|
58
|
-
// 1) Busca os artigos do painel
|
|
59
|
-
const { items: remote } = await intelexia.getArticles({ limit: 50 });
|
|
60
|
-
|
|
61
|
-
// 2) Mistura com os seus posts antigos
|
|
62
|
-
const all = mergePosts({
|
|
63
|
-
local: localPosts,
|
|
64
|
-
remote,
|
|
65
|
-
mapLocal: (p) => ({
|
|
66
|
-
slug: p.slug,
|
|
67
|
-
title: p.title,
|
|
68
|
-
excerpt: p.excerpt,
|
|
69
|
-
publishedAt: p.date,
|
|
70
|
-
featuredImageUrl: null,
|
|
71
|
-
category: p.category,
|
|
72
|
-
}),
|
|
73
|
-
// Opcional — defaults sensatos:
|
|
74
|
-
// dedupe: "remote-wins" (painel substitui local se slug colide)
|
|
75
|
-
// sortBy: "date-desc" (mais recente primeiro)
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<ul>
|
|
80
|
-
{all.map((p) => (
|
|
81
|
-
<li key={p.slug}>
|
|
82
|
-
<Link href={`/blog/${p.slug}`}>{p.title}</Link>
|
|
83
|
-
<time>{new Date(p.publishedAt).toLocaleDateString("pt-BR")}</time>
|
|
84
|
-
{p.source === "remote" && <span className="badge-ia">✨ IA</span>}
|
|
85
|
-
</li>
|
|
86
|
-
))}
|
|
87
|
-
</ul>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Pro post individual, tenta o painel primeiro, cai pro local se 404:
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
// app/blog/[slug]/page.tsx
|
|
96
|
-
import { intelexia } from "@/lib/intelexia";
|
|
97
|
-
import { IntelexiaApiError } from "@intelex-ia/sdk";
|
|
98
|
-
import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
|
|
99
|
-
import { getPostBySlug } from "@/lib/posts";
|
|
100
|
-
import { notFound } from "next/navigation";
|
|
101
|
-
|
|
102
|
-
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
103
|
-
const { slug } = await params;
|
|
104
|
-
|
|
105
|
-
// 1) Tenta painel
|
|
106
|
-
try {
|
|
107
|
-
const { article } = await intelexia.getArticleBySlug(slug);
|
|
108
|
-
return (
|
|
109
|
-
<article>
|
|
110
|
-
<ArticleJsonLd article={article} />
|
|
111
|
-
<h1>{article.title}</h1>
|
|
112
|
-
<div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
|
|
113
|
-
</article>
|
|
114
|
-
);
|
|
115
|
-
} catch (err) {
|
|
116
|
-
if (!(err instanceof IntelexiaApiError) || err.status !== 404) throw err;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 2) Fallback: post antigo local
|
|
120
|
-
const local = getPostBySlug(slug);
|
|
121
|
-
if (!local) notFound();
|
|
122
|
-
return (
|
|
123
|
-
<article>
|
|
124
|
-
<h1>{local.title}</h1>
|
|
125
|
-
{/* renderiza o local do jeito que você já fazia */}
|
|
126
|
-
</article>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Modo 2: Replace (migração completa)
|
|
132
|
-
|
|
133
|
-
```tsx
|
|
134
|
-
// app/blog/page.tsx
|
|
135
|
-
import { intelexia } from "@/lib/intelexia";
|
|
136
|
-
|
|
137
|
-
export default async function BlogIndex() {
|
|
138
|
-
const { items } = await intelexia.getArticles({ limit: 20 });
|
|
139
|
-
return (
|
|
140
|
-
<ul>
|
|
141
|
-
{items.map((a) => (
|
|
142
|
-
<li key={a.id}>
|
|
143
|
-
<Link href={`/blog/${a.slug}`}>{a.title}</Link>
|
|
144
|
-
</li>
|
|
145
|
-
))}
|
|
146
|
-
</ul>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
Pro post individual, sem fallback:
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
import { intelexia } from "@/lib/intelexia";
|
|
155
|
-
import { IntelexiaApiError } from "@intelex-ia/sdk";
|
|
156
|
-
import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
|
|
157
|
-
import { notFound } from "next/navigation";
|
|
158
|
-
|
|
159
|
-
async function fetchArticle(slug: string) {
|
|
160
|
-
try {
|
|
161
|
-
return await intelexia.getArticleBySlug(slug);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
if (err instanceof IntelexiaApiError && err.status === 404) notFound();
|
|
164
|
-
throw err;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
169
|
-
const { slug } = await params;
|
|
170
|
-
const { article } = await fetchArticle(slug);
|
|
171
|
-
return articleToMetadata(article);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
175
|
-
const { slug } = await params;
|
|
176
|
-
const { article } = await fetchArticle(slug);
|
|
177
|
-
return (
|
|
178
|
-
<article>
|
|
179
|
-
<ArticleJsonLd article={article} />
|
|
180
|
-
<h1>{article.title}</h1>
|
|
181
|
-
<div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
|
|
182
|
-
</article>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Modo 3: Isolated (rota nova, blog antigo intacto)
|
|
188
|
-
|
|
189
|
-
Cria `app/blog/ia/page.tsx` e `app/blog/ia/[slug]/page.tsx` com o mesmo código do modo Replace, mas em rota separada (`/blog/ia` em vez de `/blog`). Zero alteração no blog existente.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Cliente — `lib/intelexia.ts`
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
import { createClient } from "@intelex-ia/sdk";
|
|
197
|
-
|
|
198
|
-
export const intelexia = createClient({
|
|
199
|
-
token: process.env.INTELEXIA_API_TOKEN!,
|
|
200
|
-
// ISR: revalida a cada 60s; o webhook revalidate invalida sob demanda
|
|
201
|
-
init: { next: { revalidate: 60, tags: ["intelexia-articles"] } },
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## Webhook de revalidate — `app/api/revalidate/route.ts`
|
|
206
|
-
|
|
207
|
-
Quando um artigo publica/edita no painel, nosso worker faz `POST` no seu endpoint. Implemente assim:
|
|
208
|
-
|
|
209
|
-
```ts
|
|
210
|
-
import { NextResponse } from "next/server";
|
|
211
|
-
import { revalidatePath, revalidateTag } from "next/cache";
|
|
212
|
-
|
|
213
|
-
export async function POST(req: Request) {
|
|
214
|
-
const headerSecret = req.headers.get("x-revalidate-secret");
|
|
215
|
-
const body = (await req.json().catch(() => ({}))) as {
|
|
216
|
-
secret?: string;
|
|
217
|
-
slug?: string | null;
|
|
218
|
-
domain?: string;
|
|
219
|
-
};
|
|
220
|
-
const got = headerSecret ?? body.secret ?? "";
|
|
221
|
-
if (!process.env.INTELEXIA_REVALIDATE_SECRET || got !== process.env.INTELEXIA_REVALIDATE_SECRET) {
|
|
222
|
-
return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (body.slug) revalidatePath(`/blog/${body.slug}`);
|
|
226
|
-
revalidatePath("/blog");
|
|
227
|
-
revalidateTag("intelexia-articles");
|
|
228
|
-
|
|
229
|
-
return NextResponse.json({ ok: true, revalidated: { slug: body.slug ?? null } });
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
> O worker envia o secret tanto no header `x-revalidate-secret` quanto no body, tem timeout de 15s e é **fire-and-forget** (se seu endpoint falhar, o artigo continua publicado e a ISR atualiza pelo `revalidate: 60`).
|
|
234
|
-
|
|
235
|
-
## API
|
|
236
|
-
|
|
237
|
-
### `createClient(config)`
|
|
238
|
-
|
|
239
|
-
| Opção | Tipo | Default |
|
|
240
|
-
| -------- | -------------------- | ------------------------------------------------- |
|
|
241
|
-
| `token` | `string` (obrig.) | — |
|
|
242
|
-
| `apiUrl` | `string` | `https://api.innovarticles.intelexia.com.br` |
|
|
243
|
-
| `fetch` | `typeof fetch` | `globalThis.fetch` |
|
|
244
|
-
| `init` | `RequestInit` | `undefined` (passe `{ next: { revalidate: N } }`) |
|
|
245
|
-
|
|
246
|
-
### `client.getArticles({ limit?, offset?, init? })`
|
|
247
|
-
|
|
248
|
-
Resposta: `{ ok, mode, lifecycle, count, items: ArticleListItem[] }`.
|
|
249
|
-
|
|
250
|
-
### `client.getArticleBySlug(slug, { init? })`
|
|
251
|
-
|
|
252
|
-
Resposta: `{ ok, mode, lifecycle, article: Article }`. **404 → joga `IntelexiaApiError`** (use `try/catch` + `notFound()`).
|
|
253
|
-
|
|
254
|
-
### `mergePosts({ local, remote, mapLocal, dedupe?, sortBy? })` 🆕 0.2.0
|
|
255
|
-
|
|
256
|
-
| Opção | Tipo | Default |
|
|
257
|
-
| ---------- | ------------------------------------------------- | ---------------- |
|
|
258
|
-
| `local` | `readonly TLocal[]` (qualquer shape) | — |
|
|
259
|
-
| `remote` | `readonly ArticleListItem[]` | — |
|
|
260
|
-
| `mapLocal` | `(post: TLocal) => Omit<UnifiedPost,"source"> \| null` | — |
|
|
261
|
-
| `dedupe` | `"remote-wins" \| "local-wins" \| "throw"` | `"remote-wins"` |
|
|
262
|
-
| `sortBy` | `"date-desc" \| "date-asc" \| "none"` | `"date-desc"` |
|
|
263
|
-
|
|
264
|
-
Retorna `UnifiedPost[]` com `source: "local" | "remote"`. `mapLocal` retornando `null` filtra o post (útil pra esconder rascunhos). Slugs duplicados resolvidos pela estratégia `dedupe`. Veja exemplo no Modo Additive acima.
|
|
265
|
-
|
|
266
|
-
### `mode: "full" | "plain"`
|
|
267
|
-
|
|
268
|
-
- **`full`** (sites `active`/`paused`): inclui schema JSON-LD, metaTitle/Description, keyword(s), interlinking interno no `contentHtml`.
|
|
269
|
-
- **`plain`** (site `cancelled`): só conteúdo cru — schema/meta/keywords vêm `null`, interlinking removido. O `ArticleJsonLd` e o `articleToMetadata` lidam graciosamente (omitem o que não tem).
|
|
270
|
-
|
|
271
|
-
A decisão é **server-side e não-negociável** — o cliente não escolhe o modo.
|
|
272
|
-
|
|
273
|
-
### Helpers `/react`
|
|
274
|
-
|
|
275
|
-
- `<ArticleJsonLd article={article} />` — renderiza `<script type="application/ld+json">` com o `schemaJson`. No-op se ausente.
|
|
276
|
-
- `articleToMetadata(article)` → objeto compatível com o `Metadata` do Next.js App Router (title, description, keywords, openGraph, twitter, customMetaTags como `other`).
|
|
277
|
-
|
|
278
|
-
## Erros
|
|
279
|
-
|
|
280
|
-
```ts
|
|
281
|
-
import { IntelexiaApiError, MergeConflictError } from "@intelex-ia/sdk";
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
await intelexia.getArticleBySlug("post-inexistente");
|
|
285
|
-
} catch (err) {
|
|
286
|
-
if (err instanceof IntelexiaApiError) {
|
|
287
|
-
console.error(err.status, err.message); // 404, "Artigo não encontrado."
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
mergePosts({ local, remote, mapLocal, dedupe: "throw" });
|
|
293
|
-
} catch (err) {
|
|
294
|
-
if (err instanceof MergeConflictError) {
|
|
295
|
-
console.error(`Conflito de slug: ${err.slug}`);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
## Compatibilidade
|
|
301
|
-
|
|
302
|
-
- **Node** 18+ (fetch nativo), Edge Runtime, Browser.
|
|
303
|
-
- **React** 18+ (peer dep opcional — só pros helpers `/react`).
|
|
304
|
-
- **TypeScript:** tipos publicados no `dist`.
|
|
305
|
-
- **Frameworks:** Next.js (App Router e Pages Router), Astro, Remix, SvelteKit, Vite, qualquer JS.
|
|
306
|
-
|
|
307
|
-
## Changelog
|
|
308
|
-
|
|
309
|
-
- **0.2.0** — adiciona `mergePosts()` + tipo `UnifiedPost` + `MergeConflictError` (3 modos de integração).
|
|
310
|
-
- **0.1.0** — release inicial: `createClient`, `getArticles`, `getArticleBySlug`, `<ArticleJsonLd>`, `articleToMetadata`.
|
|
1
|
+
# `@intelex-ia/sdk`
|
|
2
|
+
|
|
3
|
+
SDK pra consumir a **Content API IntelexIA** em sites headless (Next.js, Astro etc.). Multiplataforma Fase 2b.
|
|
4
|
+
|
|
5
|
+
> **O que você ganha:** `getArticles` / `getArticleBySlug` tipados, `mergePosts()` pra unir conteúdo antigo + novo do painel, `<ArticleJsonLd>` pra injetar schema JSON-LD, e `articleToMetadata` pra plugar no `generateMetadata` do Next.js App Router. Zero dependências (usa `fetch` global).
|
|
6
|
+
|
|
7
|
+
## Instalação rápida (recomendado)
|
|
8
|
+
|
|
9
|
+
Se você está começando agora, use o CLI — ele detecta a estrutura do seu projeto e configura tudo automaticamente:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @intelex-ia/cli init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Cria os arquivos no lugar certo (App Router ou Pages Router, com ou sem `src/`), configura `.env.local`, gera um `INTELEXIA.md` completo na raiz e instala o SDK pra você.
|
|
16
|
+
|
|
17
|
+
## Instalação manual
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @intelex-ia/sdk
|
|
21
|
+
# ou pnpm add @intelex-ia/sdk / yarn add @intelex-ia/sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Configuração
|
|
25
|
+
|
|
26
|
+
1. No painel da IntelexIA, **cadastre o site como "Headless (código)"** (vai pedir domain + URL e secret de revalidate).
|
|
27
|
+
2. Abra o site na lista → seção **"Tokens de API (Content API)"** → **Gerar token**. Você recebe `itx_…` (mostrado uma única vez).
|
|
28
|
+
3. Coloque em `.env.local`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
INTELEXIA_API_TOKEN=itx_seu_token_aqui
|
|
32
|
+
INTELEXIA_API_URL=https://api.innovarticles.intelexia.com.br # opcional, é o default
|
|
33
|
+
INTELEXIA_REVALIDATE_SECRET=cole-o-mesmo-segredo-do-painel
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Três modos de integração
|
|
37
|
+
|
|
38
|
+
Cada cliente tem uma situação diferente. Escolha o modo que faz sentido:
|
|
39
|
+
|
|
40
|
+
| Modo | Quando usar | O que muda no `/blog` |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| **🔀 Additive (recomendado)** | Cliente já tem posts antigos e quer **adicionar** os do painel sem perder os antigos | `mergePosts()` une os dois feeds, dedupa por slug, ordena por data |
|
|
43
|
+
| **🔁 Replace** | Cliente migrou tudo pro painel — só os novos contam | Substitui `getAllPosts()` por `client.getArticles()` |
|
|
44
|
+
| **🆕 Isolated** | Cliente quer manter `/blog` antigo intacto e criar rota nova só pra IA | Cria `/blog/ia` separado, blog antigo zero alterado |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Modo 1: Additive (cliente existente — caso mais comum)
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// app/blog/page.tsx
|
|
52
|
+
import { intelexia } from "@/lib/intelexia";
|
|
53
|
+
import { mergePosts } from "@intelex-ia/sdk";
|
|
54
|
+
import { posts as localPosts } from "@/lib/posts"; // seu array atual
|
|
55
|
+
import Link from "next/link";
|
|
56
|
+
|
|
57
|
+
export default async function BlogIndex() {
|
|
58
|
+
// 1) Busca os artigos do painel
|
|
59
|
+
const { items: remote } = await intelexia.getArticles({ limit: 50 });
|
|
60
|
+
|
|
61
|
+
// 2) Mistura com os seus posts antigos
|
|
62
|
+
const all = mergePosts({
|
|
63
|
+
local: localPosts,
|
|
64
|
+
remote,
|
|
65
|
+
mapLocal: (p) => ({
|
|
66
|
+
slug: p.slug,
|
|
67
|
+
title: p.title,
|
|
68
|
+
excerpt: p.excerpt,
|
|
69
|
+
publishedAt: p.date,
|
|
70
|
+
featuredImageUrl: null,
|
|
71
|
+
category: p.category,
|
|
72
|
+
}),
|
|
73
|
+
// Opcional — defaults sensatos:
|
|
74
|
+
// dedupe: "remote-wins" (painel substitui local se slug colide)
|
|
75
|
+
// sortBy: "date-desc" (mais recente primeiro)
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ul>
|
|
80
|
+
{all.map((p) => (
|
|
81
|
+
<li key={p.slug}>
|
|
82
|
+
<Link href={`/blog/${p.slug}`}>{p.title}</Link>
|
|
83
|
+
<time>{new Date(p.publishedAt).toLocaleDateString("pt-BR")}</time>
|
|
84
|
+
{p.source === "remote" && <span className="badge-ia">✨ IA</span>}
|
|
85
|
+
</li>
|
|
86
|
+
))}
|
|
87
|
+
</ul>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Pro post individual, tenta o painel primeiro, cai pro local se 404:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// app/blog/[slug]/page.tsx
|
|
96
|
+
import { intelexia } from "@/lib/intelexia";
|
|
97
|
+
import { IntelexiaApiError } from "@intelex-ia/sdk";
|
|
98
|
+
import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
|
|
99
|
+
import { getPostBySlug } from "@/lib/posts";
|
|
100
|
+
import { notFound } from "next/navigation";
|
|
101
|
+
|
|
102
|
+
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
103
|
+
const { slug } = await params;
|
|
104
|
+
|
|
105
|
+
// 1) Tenta painel
|
|
106
|
+
try {
|
|
107
|
+
const { article } = await intelexia.getArticleBySlug(slug);
|
|
108
|
+
return (
|
|
109
|
+
<article>
|
|
110
|
+
<ArticleJsonLd article={article} />
|
|
111
|
+
<h1>{article.title}</h1>
|
|
112
|
+
<div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
|
|
113
|
+
</article>
|
|
114
|
+
);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (!(err instanceof IntelexiaApiError) || err.status !== 404) throw err;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 2) Fallback: post antigo local
|
|
120
|
+
const local = getPostBySlug(slug);
|
|
121
|
+
if (!local) notFound();
|
|
122
|
+
return (
|
|
123
|
+
<article>
|
|
124
|
+
<h1>{local.title}</h1>
|
|
125
|
+
{/* renderiza o local do jeito que você já fazia */}
|
|
126
|
+
</article>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Modo 2: Replace (migração completa)
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
// app/blog/page.tsx
|
|
135
|
+
import { intelexia } from "@/lib/intelexia";
|
|
136
|
+
|
|
137
|
+
export default async function BlogIndex() {
|
|
138
|
+
const { items } = await intelexia.getArticles({ limit: 20 });
|
|
139
|
+
return (
|
|
140
|
+
<ul>
|
|
141
|
+
{items.map((a) => (
|
|
142
|
+
<li key={a.id}>
|
|
143
|
+
<Link href={`/blog/${a.slug}`}>{a.title}</Link>
|
|
144
|
+
</li>
|
|
145
|
+
))}
|
|
146
|
+
</ul>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Pro post individual, sem fallback:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import { intelexia } from "@/lib/intelexia";
|
|
155
|
+
import { IntelexiaApiError } from "@intelex-ia/sdk";
|
|
156
|
+
import { ArticleJsonLd, articleToMetadata } from "@intelex-ia/sdk/react";
|
|
157
|
+
import { notFound } from "next/navigation";
|
|
158
|
+
|
|
159
|
+
async function fetchArticle(slug: string) {
|
|
160
|
+
try {
|
|
161
|
+
return await intelexia.getArticleBySlug(slug);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err instanceof IntelexiaApiError && err.status === 404) notFound();
|
|
164
|
+
throw err;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
169
|
+
const { slug } = await params;
|
|
170
|
+
const { article } = await fetchArticle(slug);
|
|
171
|
+
return articleToMetadata(article);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
175
|
+
const { slug } = await params;
|
|
176
|
+
const { article } = await fetchArticle(slug);
|
|
177
|
+
return (
|
|
178
|
+
<article>
|
|
179
|
+
<ArticleJsonLd article={article} />
|
|
180
|
+
<h1>{article.title}</h1>
|
|
181
|
+
<div dangerouslySetInnerHTML={{ __html: article.contentHtml ?? "" }} />
|
|
182
|
+
</article>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Modo 3: Isolated (rota nova, blog antigo intacto)
|
|
188
|
+
|
|
189
|
+
Cria `app/blog/ia/page.tsx` e `app/blog/ia/[slug]/page.tsx` com o mesmo código do modo Replace, mas em rota separada (`/blog/ia` em vez de `/blog`). Zero alteração no blog existente.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Cliente — `lib/intelexia.ts`
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { createClient } from "@intelex-ia/sdk";
|
|
197
|
+
|
|
198
|
+
export const intelexia = createClient({
|
|
199
|
+
token: process.env.INTELEXIA_API_TOKEN!,
|
|
200
|
+
// ISR: revalida a cada 60s; o webhook revalidate invalida sob demanda
|
|
201
|
+
init: { next: { revalidate: 60, tags: ["intelexia-articles"] } },
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Webhook de revalidate — `app/api/revalidate/route.ts`
|
|
206
|
+
|
|
207
|
+
Quando um artigo publica/edita no painel, nosso worker faz `POST` no seu endpoint. Implemente assim:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { NextResponse } from "next/server";
|
|
211
|
+
import { revalidatePath, revalidateTag } from "next/cache";
|
|
212
|
+
|
|
213
|
+
export async function POST(req: Request) {
|
|
214
|
+
const headerSecret = req.headers.get("x-revalidate-secret");
|
|
215
|
+
const body = (await req.json().catch(() => ({}))) as {
|
|
216
|
+
secret?: string;
|
|
217
|
+
slug?: string | null;
|
|
218
|
+
domain?: string;
|
|
219
|
+
};
|
|
220
|
+
const got = headerSecret ?? body.secret ?? "";
|
|
221
|
+
if (!process.env.INTELEXIA_REVALIDATE_SECRET || got !== process.env.INTELEXIA_REVALIDATE_SECRET) {
|
|
222
|
+
return NextResponse.json({ ok: false, error: "unauthorized" }, { status: 401 });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (body.slug) revalidatePath(`/blog/${body.slug}`);
|
|
226
|
+
revalidatePath("/blog");
|
|
227
|
+
revalidateTag("intelexia-articles");
|
|
228
|
+
|
|
229
|
+
return NextResponse.json({ ok: true, revalidated: { slug: body.slug ?? null } });
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> O worker envia o secret tanto no header `x-revalidate-secret` quanto no body, tem timeout de 15s e é **fire-and-forget** (se seu endpoint falhar, o artigo continua publicado e a ISR atualiza pelo `revalidate: 60`).
|
|
234
|
+
|
|
235
|
+
## API
|
|
236
|
+
|
|
237
|
+
### `createClient(config)`
|
|
238
|
+
|
|
239
|
+
| Opção | Tipo | Default |
|
|
240
|
+
| -------- | -------------------- | ------------------------------------------------- |
|
|
241
|
+
| `token` | `string` (obrig.) | — |
|
|
242
|
+
| `apiUrl` | `string` | `https://api.innovarticles.intelexia.com.br` |
|
|
243
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` |
|
|
244
|
+
| `init` | `RequestInit` | `undefined` (passe `{ next: { revalidate: N } }`) |
|
|
245
|
+
|
|
246
|
+
### `client.getArticles({ limit?, offset?, init? })`
|
|
247
|
+
|
|
248
|
+
Resposta: `{ ok, mode, lifecycle, count, items: ArticleListItem[] }`.
|
|
249
|
+
|
|
250
|
+
### `client.getArticleBySlug(slug, { init? })`
|
|
251
|
+
|
|
252
|
+
Resposta: `{ ok, mode, lifecycle, article: Article }`. **404 → joga `IntelexiaApiError`** (use `try/catch` + `notFound()`).
|
|
253
|
+
|
|
254
|
+
### `mergePosts({ local, remote, mapLocal, dedupe?, sortBy? })` 🆕 0.2.0
|
|
255
|
+
|
|
256
|
+
| Opção | Tipo | Default |
|
|
257
|
+
| ---------- | ------------------------------------------------- | ---------------- |
|
|
258
|
+
| `local` | `readonly TLocal[]` (qualquer shape) | — |
|
|
259
|
+
| `remote` | `readonly ArticleListItem[]` | — |
|
|
260
|
+
| `mapLocal` | `(post: TLocal) => Omit<UnifiedPost,"source"> \| null` | — |
|
|
261
|
+
| `dedupe` | `"remote-wins" \| "local-wins" \| "throw"` | `"remote-wins"` |
|
|
262
|
+
| `sortBy` | `"date-desc" \| "date-asc" \| "none"` | `"date-desc"` |
|
|
263
|
+
|
|
264
|
+
Retorna `UnifiedPost[]` com `source: "local" | "remote"`. `mapLocal` retornando `null` filtra o post (útil pra esconder rascunhos). Slugs duplicados resolvidos pela estratégia `dedupe`. Veja exemplo no Modo Additive acima.
|
|
265
|
+
|
|
266
|
+
### `mode: "full" | "plain"`
|
|
267
|
+
|
|
268
|
+
- **`full`** (sites `active`/`paused`): inclui schema JSON-LD, metaTitle/Description, keyword(s), interlinking interno no `contentHtml`.
|
|
269
|
+
- **`plain`** (site `cancelled`): só conteúdo cru — schema/meta/keywords vêm `null`, interlinking removido. O `ArticleJsonLd` e o `articleToMetadata` lidam graciosamente (omitem o que não tem).
|
|
270
|
+
|
|
271
|
+
A decisão é **server-side e não-negociável** — o cliente não escolhe o modo.
|
|
272
|
+
|
|
273
|
+
### Helpers `/react`
|
|
274
|
+
|
|
275
|
+
- `<ArticleJsonLd article={article} />` — renderiza `<script type="application/ld+json">` com o `schemaJson`. No-op se ausente.
|
|
276
|
+
- `articleToMetadata(article)` → objeto compatível com o `Metadata` do Next.js App Router (title, description, keywords, openGraph, twitter, customMetaTags como `other`).
|
|
277
|
+
|
|
278
|
+
## Erros
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import { IntelexiaApiError, MergeConflictError } from "@intelex-ia/sdk";
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
await intelexia.getArticleBySlug("post-inexistente");
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (err instanceof IntelexiaApiError) {
|
|
287
|
+
console.error(err.status, err.message); // 404, "Artigo não encontrado."
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
try {
|
|
292
|
+
mergePosts({ local, remote, mapLocal, dedupe: "throw" });
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (err instanceof MergeConflictError) {
|
|
295
|
+
console.error(`Conflito de slug: ${err.slug}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Compatibilidade
|
|
301
|
+
|
|
302
|
+
- **Node** 18+ (fetch nativo), Edge Runtime, Browser.
|
|
303
|
+
- **React** 18+ (peer dep opcional — só pros helpers `/react`).
|
|
304
|
+
- **TypeScript:** tipos publicados no `dist`.
|
|
305
|
+
- **Frameworks:** Next.js (App Router e Pages Router), Astro, Remix, SvelteKit, Vite, qualquer JS.
|
|
306
|
+
|
|
307
|
+
## Changelog
|
|
308
|
+
|
|
309
|
+
- **0.2.0** — adiciona `mergePosts()` + tipo `UnifiedPost` + `MergeConflictError` (3 modos de integração).
|
|
310
|
+
- **0.1.0** — release inicial: `createClient`, `getArticles`, `getArticleBySlug`, `<ArticleJsonLd>`, `articleToMetadata`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoria.test.d.ts","sourceRoot":"","sources":["../src/autoria.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { autoriaDoArtigo, corpoDoArtigo, SELO_IA } from "./react.js";
|
|
3
|
+
/**
|
|
4
|
+
* A autoria do artigo tem duas regras duras, e as duas vêm de fora da
|
|
5
|
+
* engenharia:
|
|
6
|
+
*
|
|
7
|
+
* 1. `author` só vale quando é `Organization`. Texto produzido com apoio de IA
|
|
8
|
+
* e assinado por uma PESSOA é o que os Termos do Google proíbem desde
|
|
9
|
+
* 30/07/2026. O profissional aparece como REVISOR.
|
|
10
|
+
*
|
|
11
|
+
* 2. A autoria NUNCA some. Sem cadastro, assina a equipe da empresa — e o selo
|
|
12
|
+
* de apoio de IA sai do mesmo jeito. Artigo no ar sem dizer quem assina foi
|
|
13
|
+
* exatamente o defeito medido no siteIA em 21/10/2026.
|
|
14
|
+
*/
|
|
15
|
+
describe("autoriaDoArtigo", () => {
|
|
16
|
+
const comOrganization = {
|
|
17
|
+
schemaJson: {
|
|
18
|
+
"@graph": [
|
|
19
|
+
{
|
|
20
|
+
"@type": "BlogPosting",
|
|
21
|
+
author: { "@type": "Organization", name: "Equipe Editorial Parceria" },
|
|
22
|
+
contributor: {
|
|
23
|
+
"@type": "Person",
|
|
24
|
+
name: "Mauro Roger Pinto",
|
|
25
|
+
jobTitle: "Contador responsável técnico",
|
|
26
|
+
identifier: "CRC-SC 020357/O-4",
|
|
27
|
+
},
|
|
28
|
+
dateModified: "2026-10-21",
|
|
29
|
+
publisher: { "@type": "Organization", name: "Parceria Assessoria Contábil" },
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
it("usa a Organization como autor e o contributor como revisor", () => {
|
|
35
|
+
const a = autoriaDoArtigo(comOrganization);
|
|
36
|
+
expect(a.autor).toBe("Equipe Editorial Parceria");
|
|
37
|
+
expect(a.revisor).toBe("Mauro Roger Pinto, Contador responsável técnico, CRC-SC 020357/O-4");
|
|
38
|
+
expect(a.revisadoEm).toBe("2026-10-21");
|
|
39
|
+
expect(a.temCadastro).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it("IGNORA author do tipo Person — e cai para a equipe da empresa", () => {
|
|
42
|
+
const a = autoriaDoArtigo({
|
|
43
|
+
schemaJson: {
|
|
44
|
+
"@type": "BlogPosting",
|
|
45
|
+
author: { "@type": "Person", name: "Fulano de Tal" },
|
|
46
|
+
publisher: { "@type": "Organization", name: "Contabilidade Acme" },
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
expect(a.autor).not.toContain("Fulano");
|
|
50
|
+
expect(a.autor).toBe("Equipe Editorial Contabilidade Acme");
|
|
51
|
+
expect(a.temCadastro).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it("sem autoria no schema, assina a equipe tirada do publisher", () => {
|
|
54
|
+
const a = autoriaDoArtigo({
|
|
55
|
+
schemaJson: { "@type": "BlogPosting", publisher: { "@type": "Organization", name: "Acme" } },
|
|
56
|
+
});
|
|
57
|
+
expect(a.autor).toBe("Equipe Editorial Acme");
|
|
58
|
+
expect(a.revisor).toBeNull();
|
|
59
|
+
expect(a.temCadastro).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it("a prop `empresa` vence o publisher do schema", () => {
|
|
62
|
+
const a = autoriaDoArtigo({ schemaJson: { publisher: { "@type": "Organization", name: "Nome Velho" } } }, { empresa: "Nome Novo" });
|
|
63
|
+
expect(a.autor).toBe("Equipe Editorial Nome Novo");
|
|
64
|
+
});
|
|
65
|
+
it("sem schema nenhum ainda assina — nunca devolve vazio", () => {
|
|
66
|
+
for (const vazio of [null, undefined, "", 0, [], "texto solto"]) {
|
|
67
|
+
const a = autoriaDoArtigo({ schemaJson: vazio });
|
|
68
|
+
expect(a.autor.length).toBeGreaterThan(0);
|
|
69
|
+
expect(a.temCadastro).toBe(false);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
it("revisor sem cargo nem registro não gera pontuação solta", () => {
|
|
73
|
+
const a = autoriaDoArtigo({
|
|
74
|
+
schemaJson: {
|
|
75
|
+
author: { "@type": "Organization", name: "Equipe" },
|
|
76
|
+
contributor: { "@type": "Person", name: "Ana Souza" },
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
expect(a.revisor).toBe("Ana Souza");
|
|
80
|
+
});
|
|
81
|
+
it("o selo é o texto único do grupo", () => {
|
|
82
|
+
expect(SELO_IA).toBe("Apoio de IA · Revisão humana");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* ASSINATURA DUPLICADA — medida no siteIA em 21/10/2026. O corpo assinava
|
|
87
|
+
* "Dra. Ana Souza, CRC-SP 123456" (bloco `authority-signature` do motor) e o
|
|
88
|
+
* rodapé, "Equipe Editorial". Além de contraditório, o do corpo assina com uma
|
|
89
|
+
* PESSOA — a atribuição que `autoriaDoArtigo` recusa.
|
|
90
|
+
*/
|
|
91
|
+
describe("corpoDoArtigo", () => {
|
|
92
|
+
it("remove o bloco do motor, com os divs aninhados", () => {
|
|
93
|
+
const html = '<p>Texto.</p><div class="authority-signature" style="x"><div><b>Dra. Ana Souza</b></div><div>CRC-SP 123456</div></div><p>Fim.</p>';
|
|
94
|
+
const r = corpoDoArtigo({ contentHtml: html });
|
|
95
|
+
expect(r).not.toContain("authority-signature");
|
|
96
|
+
expect(r).not.toContain("Ana Souza");
|
|
97
|
+
expect(r).toContain("<p>Texto.</p>");
|
|
98
|
+
expect(r).toContain("<p>Fim.</p>");
|
|
99
|
+
});
|
|
100
|
+
it("corpo sem o bloco volta intacto", () => {
|
|
101
|
+
const html = "<h2>Um</h2><p>Dois</p>";
|
|
102
|
+
expect(corpoDoArtigo({ contentHtml: html })).toBe(html);
|
|
103
|
+
});
|
|
104
|
+
it("corpo vazio ou nulo não quebra", () => {
|
|
105
|
+
expect(corpoDoArtigo({ contentHtml: null })).toBe("");
|
|
106
|
+
expect(corpoDoArtigo({})).toBe("");
|
|
107
|
+
});
|
|
108
|
+
it("div sem fechamento corta do bloco em diante, sem deixar lixo", () => {
|
|
109
|
+
const r = corpoDoArtigo({ contentHtml: '<p>A</p><div class="authority-signature"><div>B' });
|
|
110
|
+
expect(r).toBe("<p>A</p>");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=autoria.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autoria.test.js","sourceRoot":"","sources":["../src/autoria.test.ts"],"names":[],"mappings":"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"}
|
package/dist/react.d.ts
CHANGED
|
@@ -88,4 +88,93 @@ export interface ArticleMetadata {
|
|
|
88
88
|
*/
|
|
89
89
|
export declare function articleToMetadata(article: ArticleMetadataInput): ArticleMetadata;
|
|
90
90
|
export type { Article, CustomMetaTag } from "./types.js";
|
|
91
|
+
/**
|
|
92
|
+
* Selo de transparência. Fecha a linha do revisor na ASSINATURA, no fim do
|
|
93
|
+
* artigo — nunca na barra do topo.
|
|
94
|
+
*/
|
|
95
|
+
export declare const SELO_IA = "Apoio de IA \u00B7 Revis\u00E3o humana";
|
|
96
|
+
export interface AutoriaDoArtigo {
|
|
97
|
+
/** Quem assina. SEMPRE preenchido: cai para a equipe da empresa. */
|
|
98
|
+
autor: string;
|
|
99
|
+
/** "Nome, cargo, registro" do revisor, ou `null` quando não há cadastro. */
|
|
100
|
+
revisor: string | null;
|
|
101
|
+
/** ISO da revisão, quando o schema traz `dateModified`. */
|
|
102
|
+
revisadoEm: string | null;
|
|
103
|
+
/** `false` quando a autoria veio da queda, e não do schema. */
|
|
104
|
+
temCadastro: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Lê a autoria do `schemaJson` do artigo.
|
|
108
|
+
*
|
|
109
|
+
* ⚠️ `author` SÓ vale quando é `Organization`. Texto produzido com apoio de IA
|
|
110
|
+
* e assinado por uma PESSOA é o que os Termos do Google proíbem desde
|
|
111
|
+
* 30/07/2026 — então, quando o schema traz uma Person em `author`, ela é
|
|
112
|
+
* ignorada aqui. O profissional aparece como REVISOR, via `contributor`.
|
|
113
|
+
*
|
|
114
|
+
* NUNCA devolve vazio: sem autoria no schema, assina a equipe da empresa
|
|
115
|
+
* (`empresa`, ou o `publisher.name` do próprio schema). Artigo no ar sem dizer
|
|
116
|
+
* quem assina e sem declarar o apoio de IA não é uma saída aceitável — foi o
|
|
117
|
+
* que esta função existe para impedir.
|
|
118
|
+
*/
|
|
119
|
+
export declare function autoriaDoArtigo(article: {
|
|
120
|
+
schemaJson?: unknown | null;
|
|
121
|
+
}, opts?: {
|
|
122
|
+
empresa?: string;
|
|
123
|
+
}): AutoriaDoArtigo;
|
|
124
|
+
export interface AuthorBarProps {
|
|
125
|
+
article: {
|
|
126
|
+
schemaJson?: unknown | null;
|
|
127
|
+
publishedAt?: string | null;
|
|
128
|
+
words?: number | null;
|
|
129
|
+
};
|
|
130
|
+
/** Nome do negócio, para a queda. Sem ele, usa o `publisher` do schema. */
|
|
131
|
+
empresa?: string;
|
|
132
|
+
/** Classe do contêiner. O estilo é do SITE — o SDK não traz CSS. */
|
|
133
|
+
className?: string;
|
|
134
|
+
/** Uma linha sobre a equipe, na assinatura. Omitida quando ausente. */
|
|
135
|
+
bioEquipe?: string;
|
|
136
|
+
/** Uma linha sobre o revisor, na assinatura. Omitida quando ausente. */
|
|
137
|
+
bioRevisor?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Barra de autoria, logo abaixo do `<h1>`.
|
|
141
|
+
*
|
|
142
|
+
* SEMPRE renderiza (ver `autoriaDoArtigo`). Não traz CSS: o SDK não pode impor
|
|
143
|
+
* visual a sites que têm cada um o seu. As classes (`ia-autor` por padrão) são
|
|
144
|
+
* ganchos para o site vestir.
|
|
145
|
+
*
|
|
146
|
+
* O selo de IA NÃO vem aqui — ele fecha a linha do revisor em
|
|
147
|
+
* `<AuthorSignature>`, no fim do artigo.
|
|
148
|
+
*/
|
|
149
|
+
export declare function AuthorBar({ article, empresa, className, }: AuthorBarProps): ReactElement;
|
|
150
|
+
/**
|
|
151
|
+
* Assinatura, no FIM do artigo. É aqui que o selo de IA fecha a linha do
|
|
152
|
+
* revisor — e aparece tanto com profissional cadastrado quanto sem.
|
|
153
|
+
*/
|
|
154
|
+
export declare function AuthorSignature({ article, empresa, className, bioEquipe, bioRevisor, }: AuthorBarProps): ReactElement;
|
|
155
|
+
/**
|
|
156
|
+
* O corpo do artigo SEM a assinatura que o motor embute.
|
|
157
|
+
*
|
|
158
|
+
* O `contentHtml` traz um `<div class="authority-signature">` com nome,
|
|
159
|
+
* cargo e registro de um PROFISSIONAL. Quando o site também renderiza
|
|
160
|
+
* `<AuthorSignature>`, o artigo sai com DUAS assinaturas que se contradizem —
|
|
161
|
+
* medido no siteIA em 21/10/2026: o corpo assinava "Dra. Ana Souza, CRC-SP
|
|
162
|
+
* 123456" e o rodapé, "Equipe Editorial".
|
|
163
|
+
*
|
|
164
|
+
* Pior que a duplicata: a do corpo assina com uma PESSOA, que é justamente a
|
|
165
|
+
* atribuição que `autoriaDoArtigo` recusa (Termos do Google, 30/07/2026). A
|
|
166
|
+
* regra não alcançava ali porque aquele bloco é HTML cru, não schema.
|
|
167
|
+
*
|
|
168
|
+
* Por que remover AQUI e não no servidor: tirar do `contentHtml` mudaria o
|
|
169
|
+
* corpo para TODO consumidor, inclusive sites que não renderizam assinatura
|
|
170
|
+
* própria e ficariam sem nenhuma. Removendo no render, quem usa
|
|
171
|
+
* `<AuthorSignature>` fica consistente e quem não usa continua como estava.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* <div dangerouslySetInnerHTML={{ __html: corpoDoArtigo(article) }} />
|
|
175
|
+
* <AuthorSignature article={article} empresa="Acme" />
|
|
176
|
+
*/
|
|
177
|
+
export declare function corpoDoArtigo(article: {
|
|
178
|
+
contentHtml?: string | null;
|
|
179
|
+
}): string;
|
|
91
180
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAW,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzD,iEAAiE;AACjE,UAAU,kBAAkB;IAC1B,UAAU,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,GACR,EAAE;IACD,OAAO,EAAE,kBAAkB,CAAC;CAC7B,GAAG,YAAY,GAAG,IAAI,CAYtB;AAED,mEAAmE;AACnE,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE;QACV,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE;YAAE,GAAG,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;KAC5B,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,EAAE,qBAAqB,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,eAAe,CAiCjB;AAGD,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
|
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Renderiza um `<script type="application/ld+json">` com o schema JSON-LD do
|
|
4
4
|
* artigo. Pode ser colocado em qualquer lugar do `<body>` — o Google lê tanto
|
|
@@ -81,4 +81,166 @@ export function articleToMetadata(article) {
|
|
|
81
81
|
...(Object.keys(other).length > 0 ? { other } : {}),
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
// AUTORIA
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
/**
|
|
88
|
+
* Selo de transparência. Fecha a linha do revisor na ASSINATURA, no fim do
|
|
89
|
+
* artigo — nunca na barra do topo.
|
|
90
|
+
*/
|
|
91
|
+
export const SELO_IA = "Apoio de IA · Revisão humana";
|
|
92
|
+
const ehNo = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
93
|
+
const texto = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
94
|
+
/**
|
|
95
|
+
* Lê a autoria do `schemaJson` do artigo.
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ `author` SÓ vale quando é `Organization`. Texto produzido com apoio de IA
|
|
98
|
+
* e assinado por uma PESSOA é o que os Termos do Google proíbem desde
|
|
99
|
+
* 30/07/2026 — então, quando o schema traz uma Person em `author`, ela é
|
|
100
|
+
* ignorada aqui. O profissional aparece como REVISOR, via `contributor`.
|
|
101
|
+
*
|
|
102
|
+
* NUNCA devolve vazio: sem autoria no schema, assina a equipe da empresa
|
|
103
|
+
* (`empresa`, ou o `publisher.name` do próprio schema). Artigo no ar sem dizer
|
|
104
|
+
* quem assina e sem declarar o apoio de IA não é uma saída aceitável — foi o
|
|
105
|
+
* que esta função existe para impedir.
|
|
106
|
+
*/
|
|
107
|
+
export function autoriaDoArtigo(article, opts) {
|
|
108
|
+
const schema = article.schemaJson;
|
|
109
|
+
const nos = [];
|
|
110
|
+
if (ehNo(schema)) {
|
|
111
|
+
const grafo = schema["@graph"];
|
|
112
|
+
if (Array.isArray(grafo))
|
|
113
|
+
nos.push(...grafo.filter(ehNo));
|
|
114
|
+
else
|
|
115
|
+
nos.push(schema);
|
|
116
|
+
}
|
|
117
|
+
// Nome da empresa para a queda: o que veio por prop vence; senão, o
|
|
118
|
+
// `publisher` do schema, que é justamente a organização do cliente.
|
|
119
|
+
let empresa = texto(opts?.empresa);
|
|
120
|
+
if (!empresa) {
|
|
121
|
+
for (const no of nos) {
|
|
122
|
+
const pub = no.publisher;
|
|
123
|
+
if (ehNo(pub) && texto(pub.name)) {
|
|
124
|
+
empresa = texto(pub.name);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const no of nos) {
|
|
130
|
+
const a = no.author;
|
|
131
|
+
if (!ehNo(a) || a["@type"] !== "Organization")
|
|
132
|
+
continue;
|
|
133
|
+
const autor = texto(a.name);
|
|
134
|
+
if (!autor)
|
|
135
|
+
continue;
|
|
136
|
+
const c = no.contributor;
|
|
137
|
+
const revisor = ehNo(c) && texto(c.name)
|
|
138
|
+
? [texto(c.name), texto(c.jobTitle), texto(c.identifier)]
|
|
139
|
+
.filter(Boolean)
|
|
140
|
+
.join(", ")
|
|
141
|
+
: null;
|
|
142
|
+
return { autor, revisor, revisadoEm: texto(no.dateModified), temCadastro: true };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
autor: empresa ? `Equipe Editorial ${empresa}` : "Equipe Editorial",
|
|
146
|
+
revisor: null,
|
|
147
|
+
revisadoEm: null,
|
|
148
|
+
temCadastro: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/** Texto da linha de revisão, igual nos dois blocos. */
|
|
152
|
+
function linhaDeRevisao(a, empresa) {
|
|
153
|
+
const quem = a.revisor
|
|
154
|
+
? `Revisado por ${a.revisor}`
|
|
155
|
+
: `Revisado pela equipe ${empresa?.trim() || a.autor.replace(/^Equipe Editorial\s*/, "") || "editorial"}`;
|
|
156
|
+
return `${quem} · ${SELO_IA}`;
|
|
157
|
+
}
|
|
158
|
+
/** "21/10/2026" no fuso de quem lê. Vazio quando a data não presta. */
|
|
159
|
+
function dataPtBr(iso) {
|
|
160
|
+
if (!iso)
|
|
161
|
+
return "";
|
|
162
|
+
const d = new Date(iso);
|
|
163
|
+
return Number.isNaN(d.getTime()) ? "" : d.toLocaleDateString("pt-BR");
|
|
164
|
+
}
|
|
165
|
+
/** ~200 palavras por minuto, o mesmo arredondamento do resto do grupo. */
|
|
166
|
+
function tempoDeLeitura(words) {
|
|
167
|
+
return typeof words === "number" && words > 0
|
|
168
|
+
? `${Math.max(1, Math.round(words / 200))} min de leitura`
|
|
169
|
+
: "";
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Barra de autoria, logo abaixo do `<h1>`.
|
|
173
|
+
*
|
|
174
|
+
* SEMPRE renderiza (ver `autoriaDoArtigo`). Não traz CSS: o SDK não pode impor
|
|
175
|
+
* visual a sites que têm cada um o seu. As classes (`ia-autor` por padrão) são
|
|
176
|
+
* ganchos para o site vestir.
|
|
177
|
+
*
|
|
178
|
+
* O selo de IA NÃO vem aqui — ele fecha a linha do revisor em
|
|
179
|
+
* `<AuthorSignature>`, no fim do artigo.
|
|
180
|
+
*/
|
|
181
|
+
export function AuthorBar({ article, empresa, className = "ia-autor", }) {
|
|
182
|
+
const a = autoriaDoArtigo(article, { empresa });
|
|
183
|
+
const meta = [dataPtBr(article.publishedAt), tempoDeLeitura(article.words)]
|
|
184
|
+
.filter(Boolean)
|
|
185
|
+
.join(" · ");
|
|
186
|
+
return (_jsxs("div", { className: className, children: [_jsxs("strong", { children: ["por ", a.autor] }), a.revisor ? _jsxs("span", { className: `${className}-revisao`, children: ["Revis\u00E3o: ", a.revisor] }) : null, meta ? _jsx("span", { className: `${className}-meta`, children: meta }) : null] }));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Assinatura, no FIM do artigo. É aqui que o selo de IA fecha a linha do
|
|
190
|
+
* revisor — e aparece tanto com profissional cadastrado quanto sem.
|
|
191
|
+
*/
|
|
192
|
+
export function AuthorSignature({ article, empresa, className = "ia-assinatura", bioEquipe, bioRevisor, }) {
|
|
193
|
+
const a = autoriaDoArtigo(article, { empresa });
|
|
194
|
+
const nomeRevisor = a.revisor ? a.revisor.split(",")[0].trim() : null;
|
|
195
|
+
// Dois blocos, como a referência do grupo: QUEM ESCREVEU e QUEM REVISOU.
|
|
196
|
+
// O segundo sai mesmo sem profissional cadastrado — é ele que carrega o
|
|
197
|
+
// selo de IA, e o selo é obrigatório.
|
|
198
|
+
return (_jsxs(_Fragment, { children: [_jsxs("section", { className: `${className} ${className}-equipe`, "aria-label": "Quem escreveu", children: [_jsxs("p", { className: `${className}-nome`, children: ["Escrito por ", a.autor] }), bioEquipe ? _jsx("p", { className: `${className}-bio`, children: bioEquipe }) : null] }), _jsxs("section", { className: `${className} ${className}-revisor`, "aria-label": "Quem revisou", children: [nomeRevisor ? _jsx("p", { className: `${className}-nome`, children: nomeRevisor }) : null, _jsx("p", { className: `${className}-cargo`, children: linhaDeRevisao(a, empresa) }), bioRevisor ? _jsx("p", { className: `${className}-bio`, children: bioRevisor }) : null] })] }));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* O corpo do artigo SEM a assinatura que o motor embute.
|
|
202
|
+
*
|
|
203
|
+
* O `contentHtml` traz um `<div class="authority-signature">` com nome,
|
|
204
|
+
* cargo e registro de um PROFISSIONAL. Quando o site também renderiza
|
|
205
|
+
* `<AuthorSignature>`, o artigo sai com DUAS assinaturas que se contradizem —
|
|
206
|
+
* medido no siteIA em 21/10/2026: o corpo assinava "Dra. Ana Souza, CRC-SP
|
|
207
|
+
* 123456" e o rodapé, "Equipe Editorial".
|
|
208
|
+
*
|
|
209
|
+
* Pior que a duplicata: a do corpo assina com uma PESSOA, que é justamente a
|
|
210
|
+
* atribuição que `autoriaDoArtigo` recusa (Termos do Google, 30/07/2026). A
|
|
211
|
+
* regra não alcançava ali porque aquele bloco é HTML cru, não schema.
|
|
212
|
+
*
|
|
213
|
+
* Por que remover AQUI e não no servidor: tirar do `contentHtml` mudaria o
|
|
214
|
+
* corpo para TODO consumidor, inclusive sites que não renderizam assinatura
|
|
215
|
+
* própria e ficariam sem nenhuma. Removendo no render, quem usa
|
|
216
|
+
* `<AuthorSignature>` fica consistente e quem não usa continua como estava.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* <div dangerouslySetInnerHTML={{ __html: corpoDoArtigo(article) }} />
|
|
220
|
+
* <AuthorSignature article={article} empresa="Acme" />
|
|
221
|
+
*/
|
|
222
|
+
export function corpoDoArtigo(article) {
|
|
223
|
+
const html = article.contentHtml ?? "";
|
|
224
|
+
if (!html)
|
|
225
|
+
return "";
|
|
226
|
+
// `<div class="authority-signature" …>` até o `</div>` que o fecha, contando
|
|
227
|
+
// os aninhados — o bloco tem divs dentro (avatar, textos).
|
|
228
|
+
const abre = /<div[^>]*class="[^"]*authority-signature[^"]*"[^>]*>/i;
|
|
229
|
+
const m = abre.exec(html);
|
|
230
|
+
if (!m)
|
|
231
|
+
return html;
|
|
232
|
+
let i = m.index + m[0].length;
|
|
233
|
+
let nivel = 1;
|
|
234
|
+
const tag = /<\/?div\b[^>]*>/gi;
|
|
235
|
+
tag.lastIndex = i;
|
|
236
|
+
let t;
|
|
237
|
+
while ((t = tag.exec(html)) !== null) {
|
|
238
|
+
nivel += t[0].startsWith("</") ? -1 : 1;
|
|
239
|
+
if (nivel === 0)
|
|
240
|
+
return (html.slice(0, m.index) + html.slice(t.index + t[0].length)).trim();
|
|
241
|
+
}
|
|
242
|
+
// Sem fechamento equilibrado, corta do bloco em diante: deixar pela metade
|
|
243
|
+
// seria pior que remover.
|
|
244
|
+
return html.slice(0, m.index).trim();
|
|
245
|
+
}
|
|
84
246
|
//# sourceMappingURL=react.js.map
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@intelex-ia/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "SDK pra consumir a Content API da IntelexIA em sites headless (Next.js, Astro, etc.). Multiplataforma Fase 2b.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"author": "intelex-ia",
|
|
7
|
-
"homepage": "https://github.com/intelex-ia/combo_automatico/tree/main/packages/sdk",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/intelex-ia/combo_automatico.git",
|
|
11
|
-
"directory": "packages/sdk"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/intelex-ia/combo_automatico/issues"
|
|
15
|
-
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"intelexia",
|
|
18
|
-
"content-api",
|
|
19
|
-
"headless",
|
|
20
|
-
"cms",
|
|
21
|
-
"next",
|
|
22
|
-
"nextjs",
|
|
23
|
-
"react",
|
|
24
|
-
"sdk"
|
|
25
|
-
],
|
|
26
|
-
"type": "module",
|
|
27
|
-
"main": "./dist/index.js",
|
|
28
|
-
"module": "./dist/index.js",
|
|
29
|
-
"types": "./dist/index.d.ts",
|
|
30
|
-
"exports": {
|
|
31
|
-
".": {
|
|
32
|
-
"types": "./dist/index.d.ts",
|
|
33
|
-
"default": "./dist/index.js"
|
|
34
|
-
},
|
|
35
|
-
"./react": {
|
|
36
|
-
"types": "./dist/react.d.ts",
|
|
37
|
-
"default": "./dist/react.js"
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"files": [
|
|
41
|
-
"dist",
|
|
42
|
-
"README.md"
|
|
43
|
-
],
|
|
44
|
-
"scripts": {
|
|
45
|
-
"build": "tsc",
|
|
46
|
-
"typecheck": "tsc --noEmit"
|
|
47
|
-
},
|
|
48
|
-
"peerDependencies": {
|
|
49
|
-
"react": ">=18"
|
|
50
|
-
},
|
|
51
|
-
"peerDependenciesMeta": {
|
|
52
|
-
"react": {
|
|
53
|
-
"optional": true
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"@types/react": "^18.3.12",
|
|
58
|
-
"typescript": "^5.6.3"
|
|
59
|
-
},
|
|
60
|
-
"publishConfig": {
|
|
61
|
-
"access": "public",
|
|
62
|
-
"provenance": true
|
|
63
|
-
}
|
|
64
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@intelex-ia/sdk",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "SDK pra consumir a Content API da IntelexIA em sites headless (Next.js, Astro, etc.). Multiplataforma Fase 2b.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "intelex-ia",
|
|
7
|
+
"homepage": "https://github.com/intelex-ia/combo_automatico/tree/main/packages/sdk",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/intelex-ia/combo_automatico.git",
|
|
11
|
+
"directory": "packages/sdk"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/intelex-ia/combo_automatico/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"intelexia",
|
|
18
|
+
"content-api",
|
|
19
|
+
"headless",
|
|
20
|
+
"cms",
|
|
21
|
+
"next",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"react",
|
|
24
|
+
"sdk"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"module": "./dist/index.js",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./react": {
|
|
36
|
+
"types": "./dist/react.d.ts",
|
|
37
|
+
"default": "./dist/react.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"peerDependenciesMeta": {
|
|
52
|
+
"react": {
|
|
53
|
+
"optional": true
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/react": "^18.3.12",
|
|
58
|
+
"typescript": "^5.6.3"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public",
|
|
62
|
+
"provenance": true
|
|
63
|
+
}
|
|
64
|
+
}
|