@bacano/sdk 1.1.9 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ # Gestión de tipos de Content para agentes
2
+
3
+ `@bacano/sdk/content-management` permite que una herramienta administrativa o
4
+ un agente autorizado consulte, cree y mantenga **tipos y componentes** de
5
+ Bacano Content. No permite administrar entradas, productos, ventas, clientes ni
6
+ ningún otro dato de la empresa.
7
+
8
+ Este contrato es independiente del cliente público del storefront. Debe usarse
9
+ solo desde un proceso local o servidor confiable; nunca desde el navegador ni
10
+ dentro del bundle del sitio web.
11
+
12
+ ## Activar un acceso temporal
13
+
14
+ 1. En Bacano, abre **Sitios web → Contenido → Tipos** o **Componentes**.
15
+ 2. Selecciona **Conectar agente**.
16
+ 3. Elige 15, 30 o 60 minutos y genera el acceso.
17
+ 4. Copia el token al entorno local del agente.
18
+
19
+ El token está firmado, pertenece a una sola empresa y solo contiene el alcance
20
+ `content_types:manage`. Cada petición vuelve a comprobar que el usuario que lo
21
+ generó continúa vinculado a la empresa y conserva `websites.manage`. Al vencer
22
+ o perder ese permiso deja de funcionar.
23
+
24
+ No guardes el token en Git, Figma, Content, logs, variables públicas ni archivos
25
+ que se empaqueten con el storefront.
26
+
27
+ ## Crear el cliente
28
+
29
+ ```ts
30
+ import { createContentManagementClient } from "@bacano/sdk/content-management";
31
+
32
+ const content = createContentManagementClient({
33
+ apiUrl: process.env.BACANO_API_URL!,
34
+ accessToken: process.env.BACANO_CONTENT_ACCESS_TOKEN!,
35
+ });
36
+
37
+ const context = await content.getContext();
38
+ console.log(context.companyId, context.expiresAt);
39
+ ```
40
+
41
+ El cliente ofrece:
42
+
43
+ - `getContext()` para confirmar empresa, alcance y vencimiento.
44
+ - `getReferences()` para consultar categorías, subcategorías, listas de opciones
45
+ y unidades de medida válidas.
46
+ - `listTypes()` y `getType(idOrKey)`.
47
+ - `createType(input)` y `updateType(idOrKey, input)`.
48
+ - `inspectLifecycle(idOrKey, operation)`.
49
+ - `applyLifecycle(idOrKey, operation)`.
50
+
51
+ ## Flujo seguro para un agente
52
+
53
+ 1. Ejecutar `getContext()` y confirmar la empresa con el usuario.
54
+ 2. Ejecutar `listTypes()` y `getReferences()` antes de proponer cambios.
55
+ 3. Reutilizar tipos, componentes, listas y keys existentes cuando correspondan.
56
+ 4. Crear primero los componentes que serán referenciados por otros tipos.
57
+ 5. Crear o actualizar en `draft`.
58
+ 6. Volver a consultar el tipo y presentar un resumen del resultado.
59
+ 7. Activarlo únicamente cuando el usuario haya aprobado la estructura.
60
+ 8. Para archivar, restaurar o eliminar, ejecutar primero
61
+ `inspectLifecycle()` y mostrar dependencias y bloqueos.
62
+ 9. Ejecutar `applyLifecycle()` solo después de una confirmación explícita.
63
+
64
+ `updateType()` reemplaza la definición completa. Antes de usarlo debes obtener
65
+ el tipo con `getType()`, conservar los IDs de campos existentes y enviar todos
66
+ los campos y secciones que deban permanecer. La omisión de un campo se interpreta
67
+ como una eliminación estructural y puede requerir
68
+ `confirmDestructiveChanges: true`.
69
+
70
+ ## Ejemplo: componente y tipo editorial
71
+
72
+ ```ts
73
+ await content.createType({
74
+ key: "cta_button",
75
+ name: "Botón de llamada a la acción",
76
+ kind: "component",
77
+ status: "draft",
78
+ fields: [
79
+ {
80
+ key: "label",
81
+ label: "Texto",
82
+ type: "text",
83
+ required: true,
84
+ sortOrder: 1,
85
+ settings: { isVisible: true, maxLength: 80 },
86
+ },
87
+ {
88
+ key: "url",
89
+ label: "Enlace",
90
+ type: "link",
91
+ required: true,
92
+ sortOrder: 2,
93
+ settings: { isVisible: true },
94
+ },
95
+ ],
96
+ });
97
+
98
+ await content.createType({
99
+ key: "home_hero",
100
+ name: "Hero del inicio",
101
+ kind: "entry",
102
+ status: "draft",
103
+ usages: ["website_content"],
104
+ sections: [{ key: "content", title: "Contenido", sortOrder: 1 }],
105
+ fields: [
106
+ {
107
+ key: "title",
108
+ label: "Título",
109
+ type: "text",
110
+ required: true,
111
+ sortOrder: 1,
112
+ sectionKey: "content",
113
+ settings: { isVisible: true, maxLength: 120 },
114
+ },
115
+ {
116
+ key: "actions",
117
+ label: "Acciones",
118
+ type: "repeater",
119
+ required: false,
120
+ sortOrder: 2,
121
+ sectionKey: "content",
122
+ settings: { isVisible: true, minRows: 0, maxRows: 3 },
123
+ children: [
124
+ {
125
+ key: "button",
126
+ label: "Botón",
127
+ type: "component",
128
+ required: true,
129
+ sortOrder: 1,
130
+ componentKey: "cta_button",
131
+ settings: { isVisible: true },
132
+ },
133
+ ],
134
+ },
135
+ ],
136
+ });
137
+ ```
138
+
139
+ ## Ajustes de campos
140
+
141
+ Todos los campos aceptan `helpText`, `defaultValue`, `isPrivate` e
142
+ `isVisible`. Usa además los ajustes correspondientes al tipo:
143
+
144
+ | Tipo | Ajustes principales |
145
+ | --- | --- |
146
+ | `text`, `textarea`, `slug` | `minLength`, `maxLength`, `pattern` |
147
+ | `rich_text`, `rich_text_full` | `richTextMode` |
148
+ | `media` | `mediaMultiple`, `acceptedMediaKinds` |
149
+ | `date` | `dateMode`: `date` o `datetime` |
150
+ | `number` | `min`, `max`, `step`, `precision` |
151
+ | `measure` | `measureDimensionCode`, `measureAllowedUnitCodes`, `measurePublicUnitCode` |
152
+ | `select` | `selectSource`, `selectOptions`, `selectSourceKey`, `selectMultiple` |
153
+ | `reference` | `referenceType`, `referenceContentTypeKey`, `referenceMultiple` |
154
+ | `repeater` | `minRows`, `maxRows` y `children` |
155
+ | `component` | `componentKey` |
156
+
157
+ Para una lista administrable usa `selectSource: "option_list"` y una key
158
+ devuelta por `getReferences().optionLists`. Para medidas usa exclusivamente los
159
+ códigos devueltos por `getReferences().measureDimensions`; no inventes unidades.
160
+
161
+ Una ficha técnica usa `usages: ["product_specification"]` y al menos un alcance
162
+ en `specificationScopes`. Sus campos técnicos pueden declarar
163
+ `isFilterable: true` únicamente cuando las reglas de Bacano lo permitan. Los
164
+ otros usos editoriales son `website_content` y `product_additional`.
165
+
166
+ ## Ciclo de vida
167
+
168
+ ```ts
169
+ const preview = await content.inspectLifecycle("home_hero", "delete");
170
+
171
+ if (preview.allowed && userConfirmed) {
172
+ await content.applyLifecycle("home_hero", "delete");
173
+ }
174
+ ```
175
+
176
+ Nunca ignores `dependencies`, `allowed` o `errors`. Archivar es preferible a
177
+ eliminar cuando existe contenido histórico. El backend aplica las mismas reglas
178
+ de dependencias, keys, componentes, secciones y cambios destructivos que el
179
+ backoffice; el SDK no crea un camino alterno.
180
+
181
+ ## Límites de esta versión
182
+
183
+ - No crea ni modifica entradas de Content.
184
+ - No carga archivos ni media.
185
+ - No crea listas de opciones.
186
+ - No funciona desde el cliente público del storefront.
187
+ - No acepta GraphQL directo ni credenciales administrativas permanentes.
188
+
189
+ El resultado esperado del flujo Figma es: el agente propone el blueprint, el
190
+ usuario habilita el acceso temporal, el agente crea o ajusta los tipos y
191
+ componentes aprobados, y el equipo editorial completa las entradas desde
192
+ Bacano.
package/README.md CHANGED
@@ -21,6 +21,21 @@ React hooks are available from `@bacano/sdk/react` and require `react >= 18`
21
21
  as a peer dependency. The core client has no runtime dependencies and works
22
22
  without React.
23
23
 
24
+ ### Gestión de tipos de Content para agentes
25
+
26
+ Un agente autorizado puede consultar, crear y mantener tipos y componentes con
27
+ el entrypoint administrativo `@bacano/sdk/content-management`. El acceso se
28
+ genera temporalmente desde Bacano, está limitado a una empresa y nunca debe
29
+ incluirse en el storefront:
30
+
31
+ ```ts
32
+ import { createContentManagementClient } from "@bacano/sdk/content-management";
33
+ ```
34
+
35
+ Lee [`CONTENT_TYPE_MANAGEMENT.md`](./CONTENT_TYPE_MANAGEMENT.md) antes de usarlo.
36
+ La guía contiene el flujo de autorización, contratos de campos, referencias,
37
+ cambios destructivos y límites de seguridad.
38
+
24
39
  ### Auditoría del storefront
25
40
 
26
41
  El SDK incluye la skill oficial `bacano-web`. Instalarla al iniciar un
@@ -44,6 +59,44 @@ descubrimiento. `check` confirma que está vigente. Ninguno ejecuta la auditorí
44
59
  modifica el storefront ni despliega; esa revisión comienza únicamente cuando
45
60
  el usuario o agente invoca la skill.
46
61
 
62
+ ### Implementación desde Figma
63
+
64
+ El SDK también incluye `bacano-figma-web`, la skill oficial para convertir un
65
+ diseño Figma en una web informativa o tienda que conserve el cascarón, Static
66
+ First y contratos públicos de Bacano:
67
+
68
+ ```bash
69
+ pnpm exec bacano-figma-web install --agent all
70
+ pnpm exec bacano-figma-web check
71
+ pnpm exec bacano-figma-web doctor
72
+ ```
73
+
74
+ Después invocarla como `$bacano-figma-web` en Codex, `/bacano-figma-web` en
75
+ Claude Code o solicitar “usar Bacano Figma Web” en Antigravity. La skill guía la
76
+ conexión al MCP remoto oficial, MCP local oficial de Figma Desktop, Desktop
77
+ Bridge comunitario o REST autorizado. Prefiere siempre el MCP oficial y nunca
78
+ guarda ni imprime tokens.
79
+
80
+ `doctor` comprueba instalación, endpoint local, puertos del bridge y solamente
81
+ la presencia del token REST. El OAuth del MCP remoto debe confirmarse dentro del
82
+ agente con las herramientas de Figma. La extracción se conserva como snapshot
83
+ normalizado. Cuando el equipo de Content y desarrollo trabajan en tiempos
84
+ distintos, la skill genera un blueprint de entrega y permite fixtures locales
85
+ con el mismo contrato de los componentes. Las entradas reales continúan
86
+ creándose desde Bacano. Con autorización temporal, la skill puede crear o
87
+ ajustar tipos y componentes mediante el entrypoint administrativo oficial; un
88
+ fixture activo bloquea la aprobación de producción.
89
+
90
+ El snapshot puede validarse con:
91
+
92
+ ```bash
93
+ pnpm exec bacano-figma-web validate-snapshot .bacano/designs/<proyecto>/manifest.json
94
+ ```
95
+
96
+ Las instrucciones completas viajan dentro de
97
+ `skills/bacano-figma-web/references/`; no es necesario copiar documentación ni
98
+ crear integraciones particulares en cada storefront.
99
+
47
100
  Every storefront must follow [`STOREFRONT_MARKETING.md`](./STOREFRONT_MARKETING.md)
48
101
  when instrumenting analytics, advertising signals, contact actions, ecommerce
49
102
  events, attribution, or preview behavior. It is the canonical implementation
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // libs/sdk/src/content-management.ts
21
+ var content_management_exports = {};
22
+ __export(content_management_exports, {
23
+ ContentManagementError: () => ContentManagementError,
24
+ createContentManagementClient: () => createContentManagementClient
25
+ });
26
+ module.exports = __toCommonJS(content_management_exports);
27
+ var ContentManagementError = class extends Error {
28
+ code;
29
+ status;
30
+ details;
31
+ constructor(input) {
32
+ super(input.message);
33
+ this.name = "ContentManagementError";
34
+ this.code = input.code ?? "CONTENT_MANAGEMENT_ERROR";
35
+ this.status = input.status;
36
+ this.details = input.details;
37
+ }
38
+ };
39
+ function normalizeApiUrl(apiUrl) {
40
+ const normalized = apiUrl.trim().replace(/\/+$/, "");
41
+ if (!normalized) {
42
+ throw new ContentManagementError({
43
+ message: "apiUrl is required.",
44
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
45
+ status: 0
46
+ });
47
+ }
48
+ return normalized;
49
+ }
50
+ function encodeIdentifier(identifier) {
51
+ const normalized = identifier.trim();
52
+ if (!normalized) {
53
+ throw new ContentManagementError({
54
+ message: "A content type id or key is required.",
55
+ code: "CONTENT_TYPE_IDENTIFIER_INVALID",
56
+ status: 0
57
+ });
58
+ }
59
+ return encodeURIComponent(normalized);
60
+ }
61
+ function createContentManagementClient(config) {
62
+ const apiUrl = normalizeApiUrl(config.apiUrl);
63
+ const fetchImplementation = config.fetch ?? globalThis.fetch;
64
+ if (!fetchImplementation) {
65
+ throw new ContentManagementError({
66
+ message: "A fetch implementation is required.",
67
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
68
+ status: 0
69
+ });
70
+ }
71
+ const getAccessToken = async () => {
72
+ const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
73
+ const normalized = token.trim();
74
+ if (!normalized) {
75
+ throw new ContentManagementError({
76
+ message: "A temporary content management access token is required.",
77
+ code: "CONTENT_MANAGEMENT_ACCESS_REQUIRED",
78
+ status: 0
79
+ });
80
+ }
81
+ return normalized;
82
+ };
83
+ const request = async (path, init) => {
84
+ const response = await fetchImplementation(
85
+ `${apiUrl}/api/v1/content-management${path}`,
86
+ {
87
+ ...init,
88
+ headers: {
89
+ Accept: "application/json",
90
+ Authorization: `Bearer ${await getAccessToken()}`,
91
+ ...init?.body ? { "Content-Type": "application/json" } : {},
92
+ ...init?.headers
93
+ }
94
+ }
95
+ );
96
+ let payload = null;
97
+ try {
98
+ payload = await response.json();
99
+ } catch {
100
+ }
101
+ if (!response.ok || !payload || payload.success !== true) {
102
+ const error = payload && "error" in payload ? payload.error : null;
103
+ throw new ContentManagementError({
104
+ message: error?.message ?? `Content management request failed with HTTP ${response.status}.`,
105
+ code: error?.code,
106
+ status: response.status,
107
+ details: error?.details
108
+ });
109
+ }
110
+ return payload.data;
111
+ };
112
+ const lifecycle = (identifier, operation, dryRun) => request(
113
+ `/types/${encodeIdentifier(identifier)}/lifecycle`,
114
+ {
115
+ method: "POST",
116
+ body: JSON.stringify({ operation, dryRun })
117
+ }
118
+ );
119
+ return {
120
+ getContext: () => request("/context"),
121
+ getReferences: () => request("/references"),
122
+ listTypes: () => request("/types"),
123
+ getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
124
+ createType: (input) => request("/types", {
125
+ method: "POST",
126
+ body: JSON.stringify(input)
127
+ }),
128
+ updateType: (identifier, input) => request(`/types/${encodeIdentifier(identifier)}`, {
129
+ method: "PUT",
130
+ body: JSON.stringify(input)
131
+ }),
132
+ inspectLifecycle: (identifier, operation) => lifecycle(identifier, operation, true),
133
+ applyLifecycle: (identifier, operation) => lifecycle(identifier, operation, false)
134
+ };
135
+ }
@@ -0,0 +1,144 @@
1
+ export type ContentManagementTypeKind = "entry" | "component";
2
+ export type ContentManagementTypeStatus = "draft" | "active" | "archived";
3
+ export type ContentManagementTypeUsage = "website_content" | "product_additional" | "product_specification";
4
+ export type ContentManagementFieldType = "boolean" | "component" | "date" | "link" | "measure" | "media" | "number" | "reference" | "repeater" | "rich_text" | "rich_text_full" | "select" | "slug" | "text" | "textarea";
5
+ export interface ContentManagementChildFieldInput {
6
+ id?: string | null;
7
+ key: string;
8
+ label: string;
9
+ type: ContentManagementFieldType;
10
+ required?: boolean;
11
+ sortOrder: number;
12
+ settings?: Record<string, unknown>;
13
+ componentKey?: string | null;
14
+ }
15
+ export interface ContentManagementFieldInput extends ContentManagementChildFieldInput {
16
+ sectionKey?: string | null;
17
+ children?: ContentManagementChildFieldInput[];
18
+ }
19
+ export interface ContentManagementTypeInput {
20
+ key: string;
21
+ name: string;
22
+ description?: string;
23
+ kind: ContentManagementTypeKind;
24
+ status?: "draft" | "active";
25
+ settings?: Record<string, unknown>;
26
+ usages?: ContentManagementTypeUsage[];
27
+ sections?: Array<{
28
+ key: string;
29
+ title: string;
30
+ description?: string;
31
+ sortOrder: number;
32
+ }>;
33
+ specificationScopes?: Array<{
34
+ category?: string | null;
35
+ subcategoryId?: string | null;
36
+ }>;
37
+ deletedPaths?: string[];
38
+ confirmDestructiveChanges?: boolean;
39
+ fields?: ContentManagementFieldInput[];
40
+ }
41
+ export interface ContentManagementType extends Omit<ContentManagementTypeInput, "status"> {
42
+ id: string;
43
+ companyId: string;
44
+ description: string;
45
+ status: ContentManagementTypeStatus;
46
+ settings: Record<string, unknown>;
47
+ usages: ContentManagementTypeUsage[];
48
+ sections: Array<{
49
+ id: string;
50
+ key: string;
51
+ title: string;
52
+ description: string;
53
+ sortOrder: number;
54
+ }>;
55
+ specificationScopes: Array<{
56
+ id: string;
57
+ category: string | null;
58
+ subcategoryId: string | null;
59
+ }>;
60
+ fields: ContentManagementFieldInput[];
61
+ createdAt: string;
62
+ updatedAt: string;
63
+ }
64
+ export interface ContentManagementContext {
65
+ companyId: string;
66
+ scope: "content_types:manage";
67
+ expiresAt: string;
68
+ }
69
+ export interface ContentManagementReferences {
70
+ categories: Array<{
71
+ code: string;
72
+ name: string;
73
+ subcategories: Array<{
74
+ id: string;
75
+ name: string;
76
+ }>;
77
+ }>;
78
+ optionLists: Array<{
79
+ key: string;
80
+ name: string;
81
+ isExtensible: boolean;
82
+ options: Array<{
83
+ value: string;
84
+ label: string;
85
+ }>;
86
+ }>;
87
+ measureDimensions: Array<{
88
+ code: string;
89
+ name: string;
90
+ canonicalUnitCode: string;
91
+ units: Array<{
92
+ code: string;
93
+ name: string;
94
+ symbol: string;
95
+ }>;
96
+ }>;
97
+ }
98
+ export type ContentManagementLifecycleOperation = "archive" | "restore" | "delete";
99
+ export interface ContentManagementLifecycleResult {
100
+ success: boolean;
101
+ allowed: boolean;
102
+ applied: boolean;
103
+ entity_id: string | null;
104
+ entity_type: "type" | "component" | null;
105
+ operation: ContentManagementLifecycleOperation | null;
106
+ name: string | null;
107
+ status: string | null;
108
+ dependencies: Array<{
109
+ code: string;
110
+ label: string;
111
+ count: number;
112
+ blocking: boolean;
113
+ }>;
114
+ message: string | null;
115
+ errors: string[];
116
+ }
117
+ export type ContentManagementAccessTokenProvider = () => string | Promise<string>;
118
+ export interface ContentManagementClientConfig {
119
+ apiUrl: string;
120
+ accessToken: string | ContentManagementAccessTokenProvider;
121
+ fetch?: typeof fetch;
122
+ }
123
+ export declare class ContentManagementError extends Error {
124
+ readonly code: string;
125
+ readonly status: number;
126
+ readonly details: unknown;
127
+ constructor(input: {
128
+ message: string;
129
+ code?: string;
130
+ status: number;
131
+ details?: unknown;
132
+ });
133
+ }
134
+ export interface ContentManagementClient {
135
+ getContext(): Promise<ContentManagementContext>;
136
+ getReferences(): Promise<ContentManagementReferences>;
137
+ listTypes(): Promise<ContentManagementType[]>;
138
+ getType(identifier: string): Promise<ContentManagementType>;
139
+ createType(input: ContentManagementTypeInput): Promise<ContentManagementType>;
140
+ updateType(identifier: string, input: ContentManagementTypeInput): Promise<ContentManagementType>;
141
+ inspectLifecycle(identifier: string, operation: ContentManagementLifecycleOperation): Promise<ContentManagementLifecycleResult>;
142
+ applyLifecycle(identifier: string, operation: ContentManagementLifecycleOperation): Promise<ContentManagementLifecycleResult>;
143
+ }
144
+ export declare function createContentManagementClient(config: ContentManagementClientConfig): ContentManagementClient;
@@ -0,0 +1,114 @@
1
+ // libs/sdk/src/content-management.ts
2
+ var ContentManagementError = class extends Error {
3
+ code;
4
+ status;
5
+ details;
6
+ constructor(input) {
7
+ super(input.message);
8
+ this.name = "ContentManagementError";
9
+ this.code = input.code ?? "CONTENT_MANAGEMENT_ERROR";
10
+ this.status = input.status;
11
+ this.details = input.details;
12
+ }
13
+ };
14
+ function normalizeApiUrl(apiUrl) {
15
+ const normalized = apiUrl.trim().replace(/\/+$/, "");
16
+ if (!normalized) {
17
+ throw new ContentManagementError({
18
+ message: "apiUrl is required.",
19
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
20
+ status: 0
21
+ });
22
+ }
23
+ return normalized;
24
+ }
25
+ function encodeIdentifier(identifier) {
26
+ const normalized = identifier.trim();
27
+ if (!normalized) {
28
+ throw new ContentManagementError({
29
+ message: "A content type id or key is required.",
30
+ code: "CONTENT_TYPE_IDENTIFIER_INVALID",
31
+ status: 0
32
+ });
33
+ }
34
+ return encodeURIComponent(normalized);
35
+ }
36
+ function createContentManagementClient(config) {
37
+ const apiUrl = normalizeApiUrl(config.apiUrl);
38
+ const fetchImplementation = config.fetch ?? globalThis.fetch;
39
+ if (!fetchImplementation) {
40
+ throw new ContentManagementError({
41
+ message: "A fetch implementation is required.",
42
+ code: "CONTENT_MANAGEMENT_CONFIG_INVALID",
43
+ status: 0
44
+ });
45
+ }
46
+ const getAccessToken = async () => {
47
+ const token = typeof config.accessToken === "function" ? await config.accessToken() : config.accessToken;
48
+ const normalized = token.trim();
49
+ if (!normalized) {
50
+ throw new ContentManagementError({
51
+ message: "A temporary content management access token is required.",
52
+ code: "CONTENT_MANAGEMENT_ACCESS_REQUIRED",
53
+ status: 0
54
+ });
55
+ }
56
+ return normalized;
57
+ };
58
+ const request = async (path, init) => {
59
+ const response = await fetchImplementation(
60
+ `${apiUrl}/api/v1/content-management${path}`,
61
+ {
62
+ ...init,
63
+ headers: {
64
+ Accept: "application/json",
65
+ Authorization: `Bearer ${await getAccessToken()}`,
66
+ ...init?.body ? { "Content-Type": "application/json" } : {},
67
+ ...init?.headers
68
+ }
69
+ }
70
+ );
71
+ let payload = null;
72
+ try {
73
+ payload = await response.json();
74
+ } catch {
75
+ }
76
+ if (!response.ok || !payload || payload.success !== true) {
77
+ const error = payload && "error" in payload ? payload.error : null;
78
+ throw new ContentManagementError({
79
+ message: error?.message ?? `Content management request failed with HTTP ${response.status}.`,
80
+ code: error?.code,
81
+ status: response.status,
82
+ details: error?.details
83
+ });
84
+ }
85
+ return payload.data;
86
+ };
87
+ const lifecycle = (identifier, operation, dryRun) => request(
88
+ `/types/${encodeIdentifier(identifier)}/lifecycle`,
89
+ {
90
+ method: "POST",
91
+ body: JSON.stringify({ operation, dryRun })
92
+ }
93
+ );
94
+ return {
95
+ getContext: () => request("/context"),
96
+ getReferences: () => request("/references"),
97
+ listTypes: () => request("/types"),
98
+ getType: (identifier) => request(`/types/${encodeIdentifier(identifier)}`),
99
+ createType: (input) => request("/types", {
100
+ method: "POST",
101
+ body: JSON.stringify(input)
102
+ }),
103
+ updateType: (identifier, input) => request(`/types/${encodeIdentifier(identifier)}`, {
104
+ method: "PUT",
105
+ body: JSON.stringify(input)
106
+ }),
107
+ inspectLifecycle: (identifier, operation) => lifecycle(identifier, operation, true),
108
+ applyLifecycle: (identifier, operation) => lifecycle(identifier, operation, false)
109
+ };
110
+ }
111
+ export {
112
+ ContentManagementError,
113
+ createContentManagementClient
114
+ };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.1.9",
3
+ "version": "1.1.12",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
7
7
  "types": "./index.d.ts",
8
8
  "bin": {
9
+ "bacano-figma-web": "./skills/bacano-figma-web/scripts/cli.mjs",
9
10
  "bacano-web": "./skills/bacano-web/scripts/cli.mjs"
10
11
  },
11
12
  "scripts": {
@@ -22,6 +23,11 @@
22
23
  "import": "./graphql.js",
23
24
  "require": "./graphql.cjs"
24
25
  },
26
+ "./content-management": {
27
+ "types": "./content-management.d.ts",
28
+ "import": "./content-management.js",
29
+ "require": "./content-management.cjs"
30
+ },
25
31
  "./react": {
26
32
  "types": "./react.d.ts",
27
33
  "import": "./react.js",