@bacano/sdk 1.0.1 → 1.1.8

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.
Files changed (72) hide show
  1. package/README.md +439 -18
  2. package/STOREFRONT_CLARITY.md +140 -0
  3. package/STOREFRONT_CONSTRUCTION.md +105 -0
  4. package/STOREFRONT_LEADS.md +130 -0
  5. package/STOREFRONT_LEGAL.md +66 -0
  6. package/STOREFRONT_MARKETING.md +291 -0
  7. package/STOREFRONT_PERFORMANCE.md +304 -0
  8. package/STOREFRONT_PREVIEW.md +499 -0
  9. package/STOREFRONT_PRODUCT_SPECIFICATIONS.md +127 -0
  10. package/STOREFRONT_VOLUME_PRICING.md +131 -0
  11. package/client.d.ts +8 -1
  12. package/errors.d.ts +9 -1
  13. package/graphql.cjs +266 -3
  14. package/graphql.d.ts +2 -2
  15. package/graphql.js +266 -3
  16. package/index.cjs +2035 -255
  17. package/index.d.ts +8 -6
  18. package/index.js +2035 -255
  19. package/marketing/attribution.d.ts +53 -0
  20. package/marketing/clarity.d.ts +3 -0
  21. package/marketing/domain.d.ts +1 -0
  22. package/marketing/ga4.d.ts +2 -0
  23. package/marketing/index.d.ts +5 -0
  24. package/marketing/runtime.d.ts +2 -0
  25. package/marketing/sanitize.d.ts +2 -0
  26. package/marketing/types.d.ts +111 -0
  27. package/media.d.ts +45 -2
  28. package/modules/auth.d.ts +3 -2
  29. package/modules/availability.d.ts +2 -2
  30. package/modules/cart-errors.d.ts +2 -0
  31. package/modules/cart-session.d.ts +2 -2
  32. package/modules/cart.d.ts +4 -3
  33. package/modules/catalog.d.ts +15 -3
  34. package/modules/content.d.ts +2 -2
  35. package/modules/marketing.d.ts +5 -0
  36. package/modules/orders.d.ts +2 -2
  37. package/modules/profile.d.ts +1 -1
  38. package/modules/static-catalog.d.ts +13 -0
  39. package/modules/website.d.ts +7 -1
  40. package/package.json +14 -3
  41. package/react/context-registry.d.ts +9 -0
  42. package/react/context.d.ts +1 -1
  43. package/react/hooks.d.ts +24 -2
  44. package/react/marketing-context.d.ts +3 -0
  45. package/react/marketing-provider.d.ts +10 -0
  46. package/react/preview/cache.d.ts +23 -0
  47. package/react/preview/context.d.ts +5 -0
  48. package/react/preview/navigation.d.ts +9 -0
  49. package/react/preview/provider.d.ts +3 -0
  50. package/react/preview/shell.d.ts +3 -0
  51. package/react/preview/toolbar.d.ts +3 -0
  52. package/react/preview/types.d.ts +60 -0
  53. package/react/preview.cjs +742 -0
  54. package/react/preview.d.ts +14 -0
  55. package/react/preview.js +730 -0
  56. package/react/website-gate.d.ts +22 -0
  57. package/react.cjs +3893 -1795
  58. package/react.d.ts +8 -4
  59. package/react.js +3891 -1789
  60. package/skills/bacano-web/SKILL.md +116 -0
  61. package/skills/bacano-web/agents/openai.yaml +4 -0
  62. package/skills/bacano-web/references/architecture-runtime.md +109 -0
  63. package/skills/bacano-web/references/common.md +92 -0
  64. package/skills/bacano-web/references/informational.md +39 -0
  65. package/skills/bacano-web/references/report.md +70 -0
  66. package/skills/bacano-web/references/storefront.md +116 -0
  67. package/skills/bacano-web/scripts/audit-http.mjs +284 -0
  68. package/skills/bacano-web/scripts/cli.mjs +222 -0
  69. package/skills/bacano-web/scripts/postinstall.mjs +8 -0
  70. package/transport/documents.d.ts +23 -18
  71. package/transport/graphql.d.ts +13 -1
  72. package/types.d.ts +338 -0
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env node
2
+
3
+ const args = process.argv.slice(2);
4
+ const baseArg = args[0];
5
+
6
+ if (!baseArg || baseArg === "--help") {
7
+ console.log(
8
+ "Usage: node audit-http.mjs <origin-or-url> [--sitemap <url>] [--preview <path-or-url>] [--max <n>] [--delay <ms>]",
9
+ );
10
+ process.exit(baseArg ? 0 : 1);
11
+ }
12
+
13
+ function option(name, fallback) {
14
+ const index = args.indexOf(name);
15
+ return index >= 0 && args[index + 1] ? args[index + 1] : fallback;
16
+ }
17
+
18
+ const startUrl = new URL(baseArg);
19
+ const maxRoutes = Math.max(1, Number(option("--max", "500")) || 500);
20
+ const delayMs = Math.max(0, Number(option("--delay", "250")) || 0);
21
+ const explicitSitemap = option("--sitemap", null);
22
+ const explicitPreview = option("--preview", null);
23
+
24
+ function sleep(ms) {
25
+ return new Promise((resolve) => setTimeout(resolve, ms));
26
+ }
27
+
28
+ async function fetchResult(url) {
29
+ const startedAt = Date.now();
30
+ try {
31
+ const response = await fetch(url, {
32
+ redirect: "follow",
33
+ headers: { "user-agent": "bacano-web-readiness/1" },
34
+ });
35
+ return {
36
+ requestedUrl: url,
37
+ finalUrl: response.url,
38
+ status: response.status,
39
+ durationMs: Date.now() - startedAt,
40
+ headers: Object.fromEntries(response.headers.entries()),
41
+ body: await response.text(),
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ requestedUrl: url,
46
+ finalUrl: null,
47
+ status: null,
48
+ durationMs: Date.now() - startedAt,
49
+ headers: {},
50
+ body: "",
51
+ error: error instanceof Error ? error.message : String(error),
52
+ };
53
+ }
54
+ }
55
+
56
+ function sitemapUrls(xml) {
57
+ return [...xml.matchAll(/<loc>\s*([^<]+?)\s*<\/loc>/gi)].map((match) =>
58
+ match[1].replaceAll("&amp;", "&"),
59
+ );
60
+ }
61
+
62
+ function pageUrls(html, base) {
63
+ const urls = [];
64
+ for (const match of html.matchAll(
65
+ /<a\b[^>]*href\s*=\s*["']([^"']+)["'][^>]*>/gi,
66
+ )) {
67
+ try {
68
+ const url = new URL(match[1], base);
69
+ url.hash = "";
70
+ if (url.origin === startUrl.origin) urls.push(url.href);
71
+ } catch {
72
+ // Ignore malformed or unsupported links.
73
+ }
74
+ }
75
+ return urls;
76
+ }
77
+
78
+ function sameOrigin(url) {
79
+ try {
80
+ return new URL(url).origin === startUrl.origin;
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ function comparableRoute(url) {
87
+ const parsed = new URL(url, startUrl);
88
+ const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
89
+ return `${parsed.origin}${pathname}`;
90
+ }
91
+
92
+ function tagAttribute(tag, name) {
93
+ const match = tag.match(
94
+ new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"),
95
+ );
96
+ return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
97
+ }
98
+
99
+ function findMeta(html, key, value) {
100
+ const tags = html.match(/<meta\b[^>]*>/gi) ?? [];
101
+ const normalized = value.toLowerCase();
102
+ for (const tag of tags) {
103
+ if (tagAttribute(tag, key)?.toLowerCase() === normalized) {
104
+ return tagAttribute(tag, "content");
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+
110
+ function findLink(html, rel) {
111
+ const tags = html.match(/<link\b[^>]*>/gi) ?? [];
112
+ const normalized = rel.toLowerCase();
113
+ for (const tag of tags) {
114
+ const rels = tagAttribute(tag, "rel")?.toLowerCase().split(/\s+/) ?? [];
115
+ if (rels.includes(normalized)) return tagAttribute(tag, "href");
116
+ }
117
+ return null;
118
+ }
119
+
120
+ function stripMarkup(html) {
121
+ return html
122
+ .replace(/<script\b[\s\S]*?<\/script>/gi, " ")
123
+ .replace(/<style\b[\s\S]*?<\/style>/gi, " ")
124
+ .replace(/<[^>]+>/g, " ")
125
+ .replace(/\s+/g, " ")
126
+ .trim();
127
+ }
128
+
129
+ function auditHtml(html) {
130
+ const title =
131
+ html.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.trim() ?? null;
132
+ const visibleText = stripMarkup(html);
133
+ const placeholders = [
134
+ ...new Set(
135
+ visibleText.match(
136
+ /\[[A-ZÁÉÍÓÚÜÑ][A-ZÁÉÍÓÚÜÑ0-9 ._/-]{2,}\]|\b(?:TODO|FIXME|LOREM IPSUM)\b/gi,
137
+ ) ?? [],
138
+ ),
139
+ ].slice(0, 20);
140
+
141
+ return {
142
+ title,
143
+ description: findMeta(html, "name", "description"),
144
+ canonical: findLink(html, "canonical"),
145
+ robots: findMeta(html, "name", "robots"),
146
+ openGraphTitle: findMeta(html, "property", "og:title"),
147
+ openGraphImage: findMeta(html, "property", "og:image"),
148
+ twitterCard: findMeta(html, "name", "twitter:card"),
149
+ h1Count: (html.match(/<h1\b/gi) ?? []).length,
150
+ jsonLdCount: (
151
+ html.match(/<script\b[^>]*type=["']application\/ld\+json["'][^>]*>/gi) ??
152
+ []
153
+ ).length,
154
+ visibleTextLength: visibleText.length,
155
+ placeholders,
156
+ };
157
+ }
158
+
159
+ const home = await fetchResult(startUrl.href);
160
+ const sitemapCandidates = explicitSitemap
161
+ ? [new URL(explicitSitemap, startUrl).href]
162
+ : [new URL("/sitemap.xml", startUrl).href];
163
+
164
+ const routes = new Set([startUrl.href]);
165
+ const sitemapPageUrls = new Set();
166
+ const sitemapReports = [];
167
+
168
+ for (const url of pageUrls(home.body, startUrl)) {
169
+ if (routes.size < maxRoutes) routes.add(url);
170
+ }
171
+
172
+ const visitedSitemaps = new Set();
173
+ while (sitemapCandidates.length > 0) {
174
+ const candidate = sitemapCandidates.shift();
175
+ if (!candidate || visitedSitemaps.has(candidate)) continue;
176
+ visitedSitemaps.add(candidate);
177
+ const report = await fetchResult(candidate);
178
+ sitemapReports.push({ url: candidate, status: report.status });
179
+ if (report.status !== 200) continue;
180
+
181
+ const urls = sitemapUrls(report.body);
182
+ if (/<sitemapindex\b/i.test(report.body)) {
183
+ for (const url of urls) {
184
+ if (sameOrigin(url)) sitemapCandidates.push(url);
185
+ }
186
+ continue;
187
+ }
188
+
189
+ for (const url of urls) {
190
+ if (!sameOrigin(url)) continue;
191
+ sitemapPageUrls.add(url);
192
+ if (routes.size < maxRoutes) routes.add(url);
193
+ }
194
+ }
195
+
196
+ const pages = [];
197
+ for (const url of routes) {
198
+ if (url === startUrl.href) {
199
+ pages.push({
200
+ url,
201
+ finalUrl: home.finalUrl,
202
+ status: home.status,
203
+ durationMs: home.durationMs,
204
+ error: home.error,
205
+ html: auditHtml(home.body),
206
+ });
207
+ } else {
208
+ if (delayMs) await sleep(delayMs);
209
+ const report = await fetchResult(url);
210
+ pages.push({
211
+ url,
212
+ finalUrl: report.finalUrl,
213
+ status: report.status,
214
+ durationMs: report.durationMs,
215
+ error: report.error,
216
+ html: auditHtml(report.body),
217
+ });
218
+ }
219
+ }
220
+
221
+ const requiredHeaders = [
222
+ "content-security-policy",
223
+ "strict-transport-security",
224
+ "x-content-type-options",
225
+ "referrer-policy",
226
+ "permissions-policy",
227
+ ];
228
+
229
+ let previewAudit = null;
230
+ if (explicitPreview) {
231
+ const previewUrl = new URL(explicitPreview, startUrl).href;
232
+ if (!sameOrigin(previewUrl)) {
233
+ throw new Error("--preview must belong to the audited origin");
234
+ }
235
+ if (delayMs) await sleep(delayMs);
236
+ const preview = await fetchResult(previewUrl);
237
+ const html = auditHtml(preview.body);
238
+ const robots = html.robots?.toLowerCase() ?? "";
239
+ const sitemapRoutes = new Set(
240
+ [...sitemapPageUrls].map((url) => comparableRoute(url)),
241
+ );
242
+
243
+ previewAudit = {
244
+ url: previewUrl,
245
+ finalUrl: preview.finalUrl,
246
+ status: preview.status,
247
+ error: preview.error,
248
+ robots: html.robots,
249
+ noindex: /(?:^|,)\s*noindex(?:\s|,|$)/.test(robots),
250
+ nofollow: /(?:^|,)\s*nofollow(?:\s|,|$)/.test(robots),
251
+ inSitemap: sitemapRoutes.has(comparableRoute(previewUrl)),
252
+ };
253
+ }
254
+
255
+ const output = {
256
+ auditedAt: new Date().toISOString(),
257
+ origin: startUrl.origin,
258
+ discoveredRoutes: routes.size,
259
+ sitemapReports,
260
+ previewAudit,
261
+ summary: {
262
+ successful: pages.filter((page) => page.status === 200).length,
263
+ redirectsOrOther: pages.filter(
264
+ (page) => page.status && page.status !== 200 && page.status < 400,
265
+ ).length,
266
+ failed: pages.filter((page) => !page.status || page.status >= 400).length,
267
+ productRoutes: pages.filter((page) =>
268
+ new URL(page.url).pathname.startsWith("/producto/"),
269
+ ).length,
270
+ missingTitle: pages.filter((page) => !page.html.title).length,
271
+ missingDescription: pages.filter((page) => !page.html.description).length,
272
+ missingCanonical: pages.filter((page) => !page.html.canonical).length,
273
+ invalidH1Count: pages.filter((page) => page.html.h1Count !== 1).length,
274
+ pagesWithPlaceholders: pages.filter(
275
+ (page) => page.html.placeholders.length > 0,
276
+ ).length,
277
+ },
278
+ homeSecurityHeaders: Object.fromEntries(
279
+ requiredHeaders.map((header) => [header, home.headers[header] ?? null]),
280
+ ),
281
+ pages,
282
+ };
283
+
284
+ console.log(JSON.stringify(output, null, 2));
@@ -0,0 +1,222 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash } from "node:crypto";
4
+ import {
5
+ cpSync,
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ readlinkSync,
11
+ rmSync,
12
+ symlinkSync,
13
+ } from "node:fs";
14
+ import { dirname, relative, resolve } from "node:path";
15
+ import { createInterface } from "node:readline/promises";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
19
+ const sourceSkill = resolve(scriptDirectory, "..");
20
+ const sourceSkillFile = resolve(sourceSkill, "SKILL.md");
21
+ const projectRoot = process.cwd();
22
+ const canonicalSkill = resolve(projectRoot, ".agents", "skills", "bacano-web");
23
+
24
+ const adapterPaths = {
25
+ antigravity: canonicalSkill,
26
+ claude: resolve(projectRoot, ".claude", "skills", "bacano-web"),
27
+ codex: resolve(projectRoot, ".codex", "skills", "bacano-web"),
28
+ };
29
+
30
+ function usage() {
31
+ console.log(`Bacano Web readiness skill
32
+
33
+ Usage:
34
+ bacano-web install [--agent antigravity|claude|codex|all] [--force]
35
+ bacano-web check
36
+
37
+ The canonical project copy lives at .agents/skills/bacano-web.
38
+ Claude and Codex receive links to that same copy.`);
39
+ }
40
+
41
+ function option(name) {
42
+ const index = process.argv.indexOf(name);
43
+ return index >= 0 ? process.argv[index + 1] : null;
44
+ }
45
+
46
+ function hash(file) {
47
+ return createHash("sha256").update(readFileSync(file)).digest("hex");
48
+ }
49
+
50
+ function skillFile(directory) {
51
+ return resolve(directory, "SKILL.md");
52
+ }
53
+
54
+ function relativeDisplay(path) {
55
+ const value = relative(projectRoot, path);
56
+ return value || ".";
57
+ }
58
+
59
+ function removeExisting(path, force) {
60
+ if (!existsSync(path)) return;
61
+ if (!force) {
62
+ throw new Error(
63
+ `${relativeDisplay(path)} ya existe. Usa --force para reemplazarlo.`,
64
+ );
65
+ }
66
+ rmSync(path, { recursive: true, force: true });
67
+ }
68
+
69
+ function installCanonical(force) {
70
+ if (resolve(sourceSkill) === canonicalSkill) return;
71
+
72
+ if (existsSync(skillFile(canonicalSkill))) {
73
+ if (hash(skillFile(canonicalSkill)) === hash(sourceSkillFile)) return;
74
+ removeExisting(canonicalSkill, force);
75
+ } else if (existsSync(canonicalSkill)) {
76
+ removeExisting(canonicalSkill, force);
77
+ }
78
+
79
+ mkdirSync(dirname(canonicalSkill), { recursive: true });
80
+ cpSync(sourceSkill, canonicalSkill, { recursive: true });
81
+ }
82
+
83
+ function installAdapter(agent, force) {
84
+ const target = adapterPaths[agent];
85
+ if (!target || target === canonicalSkill) return;
86
+
87
+ if (existsSync(target)) {
88
+ const stat = lstatSync(target);
89
+ if (stat.isSymbolicLink()) {
90
+ const resolvedLink = resolve(dirname(target), readlinkSync(target));
91
+ if (resolvedLink === canonicalSkill) return;
92
+ }
93
+ removeExisting(target, force);
94
+ }
95
+
96
+ mkdirSync(dirname(target), { recursive: true });
97
+ const targetReference =
98
+ process.platform === "win32"
99
+ ? canonicalSkill
100
+ : relative(dirname(target), canonicalSkill);
101
+ symlinkSync(
102
+ targetReference,
103
+ target,
104
+ process.platform === "win32" ? "junction" : "dir",
105
+ );
106
+ }
107
+
108
+ async function chooseAgent() {
109
+ const explicit = option("--agent");
110
+ if (explicit) return explicit;
111
+
112
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
113
+ throw new Error(
114
+ "Indica --agent antigravity, --agent claude, --agent codex o --agent all.",
115
+ );
116
+ }
117
+
118
+ const prompt = createInterface({
119
+ input: process.stdin,
120
+ output: process.stdout,
121
+ });
122
+ const answer = await prompt.question(
123
+ "¿Qué agente usará la skill? [1] Antigravity [2] Claude Code [3] Codex [4] Todos: ",
124
+ );
125
+ prompt.close();
126
+
127
+ return (
128
+ { 1: "antigravity", 2: "claude", 3: "codex", 4: "all" }[answer.trim()] ??
129
+ answer.trim().toLowerCase()
130
+ );
131
+ }
132
+
133
+ function validateAgent(agent) {
134
+ if (!["antigravity", "claude", "codex", "all"].includes(agent)) {
135
+ throw new Error(`Agente no reconocido: ${agent || "(vacío)"}.`);
136
+ }
137
+ }
138
+
139
+ async function install() {
140
+ const agent = await chooseAgent();
141
+ validateAgent(agent);
142
+ const force = process.argv.includes("--force");
143
+
144
+ installCanonical(force);
145
+ if (agent === "all") {
146
+ installAdapter("claude", force);
147
+ installAdapter("codex", force);
148
+ } else {
149
+ installAdapter(agent, force);
150
+ }
151
+
152
+ console.log("Skill Bacano Web instalada.");
153
+ console.log(`Fuente canónica: ${relativeDisplay(canonicalSkill)}`);
154
+ if (agent === "claude" || agent === "all") {
155
+ console.log(`Claude Code: ${relativeDisplay(adapterPaths.claude)}`);
156
+ }
157
+ if (agent === "codex" || agent === "all") {
158
+ console.log(`Codex: ${relativeDisplay(adapterPaths.codex)}`);
159
+ }
160
+ console.log("Ejecuta bacano-web check para verificarla.");
161
+ }
162
+
163
+ function checkPath(name, path) {
164
+ if (!existsSync(path))
165
+ return { name, path: relativeDisplay(path), status: "missing" };
166
+ const file = skillFile(path);
167
+ if (!existsSync(file)) {
168
+ return { name, path: relativeDisplay(path), status: "invalid" };
169
+ }
170
+ return {
171
+ name,
172
+ path: relativeDisplay(path),
173
+ status: hash(file) === hash(sourceSkillFile) ? "current" : "outdated",
174
+ link: lstatSync(path).isSymbolicLink(),
175
+ };
176
+ }
177
+
178
+ function check() {
179
+ const results = [
180
+ checkPath("canonical", canonicalSkill),
181
+ checkPath("antigravity", adapterPaths.antigravity),
182
+ checkPath("claude", adapterPaths.claude),
183
+ checkPath("codex", adapterPaths.codex),
184
+ ];
185
+ const unique = results.filter(
186
+ (result, index) =>
187
+ results.findIndex((candidate) => candidate.path === result.path) ===
188
+ index,
189
+ );
190
+
191
+ console.table(unique);
192
+ const canonical = results[0];
193
+ if (canonical.status !== "current") {
194
+ console.error(
195
+ "Bacano Web no está instalada o está desactualizada. Ejecuta bacano-web install.",
196
+ );
197
+ process.exitCode = 1;
198
+ return;
199
+ }
200
+
201
+ const adapters = results
202
+ .slice(2)
203
+ .filter((result) => result.status !== "missing");
204
+ if (adapters.some((result) => !result.link)) {
205
+ console.warn(
206
+ "Se detectaron copias físicas en adaptadores. Reinstala con --force para evitar divergencias.",
207
+ );
208
+ }
209
+ if (unique.some((result) => result.status === "outdated"))
210
+ process.exitCode = 1;
211
+ }
212
+
213
+ const command = process.argv[2] ?? "help";
214
+
215
+ try {
216
+ if (command === "install") await install();
217
+ else if (command === "check") check();
218
+ else usage();
219
+ } catch (error) {
220
+ console.error(error instanceof Error ? error.message : String(error));
221
+ process.exitCode = 1;
222
+ }
@@ -0,0 +1,8 @@
1
+ if (!process.env.CI && process.env.BACANO_SDK_HIDE_SKILL_NOTICE !== "1") {
2
+ console.log(`
3
+ @bacano/sdk incluye la skill Bacano Web para evaluar sitios antes de producción.
4
+ Instálala una vez: pnpm exec bacano-web install --agent all
5
+ Verifica su estado: pnpm exec bacano-web check
6
+ Luego invócala desde tu agente para iniciar la auditoría.
7
+ `);
8
+ }