From b69bf1c253ae0a9e3275ea83107b84fc715fd715 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sat, 6 Jun 2026 23:46:34 +0200 Subject: [PATCH 1/5] Expose API error metadata --- src/index.ts | 2 + src/wrapper.ts | 92 ++++++++++++++++++++++++++- test-d/runtime-types.test-d.ts | 9 +++ test/node/runtime-compat.node.test.ts | 61 +++++++++++++++++- test/web/runtime-compat.web.test.ts | 45 ++++++++++++- 5 files changed, 205 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1250a9a..13075f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,8 @@ import ComercioExteriorCatalogs from './tools/comercioExteriorCatalogs'; export * from './enums'; export * from './types'; +export { FacturapiError } from './wrapper'; +export type { FacturapiErrorDetail } from './wrapper'; const VALID_API_VERSIONS = ['v1', 'v2']; diff --git a/src/wrapper.ts b/src/wrapper.ts index 34b8a84..7a6ec75 100644 --- a/src/wrapper.ts +++ b/src/wrapper.ts @@ -24,6 +24,81 @@ type FormDataLike = { export type UniversalFormData = FormData | FormDataLike; +export interface FacturapiErrorDetail { + code?: string; + message?: string; + path?: string; + location?: string; + source?: string; + [key: string]: unknown; +} + +export interface FacturapiErrorOptions { + message: string; + status: number; + code?: string; + path?: string; + location?: string; + errors?: FacturapiErrorDetail[]; + logId?: string; + headers?: Record; +} + +export class FacturapiError extends Error { + status: number; + code?: string; + path?: string; + location?: string; + errors?: FacturapiErrorDetail[]; + logId?: string; + headers: Record; + + constructor(options: FacturapiErrorOptions) { + super(options.message); + this.name = 'FacturapiError'; + this.status = options.status; + this.code = options.code; + this.path = options.path; + this.location = options.location; + this.errors = options.errors; + this.logId = options.logId; + this.headers = options.headers || {}; + } +} + +const responseHeadersToObject = (headers: Headers): Record => { + const result: Record = {}; + if (typeof headers.forEach === 'function') { + headers.forEach((value, key) => { + result[key.toLowerCase()] = value; + }); + return result; + } + for (const key of ['retry-after', 'x-facturapi-log-id']) { + const value = headers.get(key); + if (value) { + result[key] = value; + } + } + return result; +}; + +const stringFrom = (value: unknown): string | undefined => + typeof value === 'string' ? value : undefined; + +const statusFrom = (value: unknown, fallback: number): number => { + if (typeof value === 'number') { + return value; + } + if (typeof value === 'string') { + const parsed = Number.parseInt(value, 10); + if (!Number.isNaN(parsed)) { + return parsed; + } + } + return fallback; +}; + const responseInterceptor = async (response: Response) => { if (!response.ok) { const contentType = response.headers.get('content-type') || ''; @@ -34,10 +109,11 @@ const responseInterceptor = async (response: Response) => { bodyText = null; } + let errorData: Record | null = null; let jsonMessage: string | null = null; if (contentType.includes('application/json') && bodyText) { try { - const errorData = JSON.parse(bodyText) as { message?: unknown }; + errorData = JSON.parse(bodyText) as Record; if (typeof errorData.message === 'string' && errorData.message.trim()) { jsonMessage = errorData.message; } @@ -46,7 +122,19 @@ const responseInterceptor = async (response: Response) => { } } - throw new Error(jsonMessage || bodyText || response.statusText); + const headers = responseHeadersToObject(response.headers); + throw new FacturapiError({ + message: jsonMessage || bodyText || response.statusText, + status: statusFrom(errorData?.status, response.status), + code: stringFrom(errorData?.code), + path: stringFrom(errorData?.path), + location: stringFrom(errorData?.location), + errors: Array.isArray(errorData?.errors) + ? (errorData.errors as FacturapiErrorDetail[]) + : undefined, + logId: headers['x-facturapi-log-id'], + headers, + }); } const contentType = response.headers.get('content-type') || ''; const contentDisposition = diff --git a/test-d/runtime-types.test-d.ts b/test-d/runtime-types.test-d.ts index a4e8f8b..13ad533 100644 --- a/test-d/runtime-types.test-d.ts +++ b/test-d/runtime-types.test-d.ts @@ -1,6 +1,7 @@ import { expectAssignable, expectType, expectError } from 'tsd'; import Facturapi, { BinaryDownload, + FacturapiError, NodeLikeReadableStream, TaxFactor, } from '../dist'; @@ -29,3 +30,11 @@ if ('pipe' in binary && typeof binary.pipe === 'function') { } expectAssignable(TaxFactor.EXENTO); + +declare const apiError: FacturapiError; +expectType(apiError.status); +expectType(apiError.code); +expectType(apiError.path); +expectType(apiError.location); +expectType(apiError.logId); +expectType>(apiError.headers); diff --git a/test/node/runtime-compat.node.test.ts b/test/node/runtime-compat.node.test.ts index 38b5cd4..0e5f97f 100644 --- a/test/node/runtime-compat.node.test.ts +++ b/test/node/runtime-compat.node.test.ts @@ -2,7 +2,7 @@ import crypto from 'node:crypto' import { Writable } from 'node:stream' import { afterEach, describe, expect, it, vi } from 'vitest' -import Facturapi from '../../src' +import Facturapi, { FacturapiError } from '../../src' const originalFetch = globalThis.fetch @@ -223,6 +223,65 @@ describe('runtime compatibility (node)', () => { ) }) + it('surfaces structured API errors and response headers in Node', async () => { + const client = createClient() + + globalThis.fetch = vi.fn(async () => { + return new Response( + JSON.stringify({ + message: 'Se excedió el límite de solicitudes.', + status: 429, + code: 'RATE_LIMIT_EXCEEDED', + path: 'date', + location: 'query', + errors: [ + { + code: 'required', + message: '"date" is required', + path: 'date', + location: 'query', + }, + ], + }), + { + status: 429, + headers: { + 'content-type': 'application/json', + 'retry-after': '3', + 'x-facturapi-log-id': 'log_123', + }, + }, + ) + }) as typeof fetch + + try { + await client.invoices.retrieve('inv_123') + throw new Error('Expected request to fail') + } catch (error) { + expect(error).toBeInstanceOf(FacturapiError) + expect((error as FacturapiError).message).toBe( + 'Se excedió el límite de solicitudes.', + ) + expect((error as FacturapiError).status).toBe(429) + expect((error as FacturapiError).code).toBe('RATE_LIMIT_EXCEEDED') + expect((error as FacturapiError).path).toBe('date') + expect((error as FacturapiError).location).toBe('query') + expect((error as FacturapiError).logId).toBe('log_123') + expect((error as FacturapiError).errors).toEqual([ + { + code: 'required', + message: '"date" is required', + path: 'date', + location: 'query', + }, + ]) + expect((error as FacturapiError).headers['retry-after']).toBe('3') + expect((error as FacturapiError).headers['x-facturapi-log-id']).toBe( + 'log_123', + ) + } + }) + it('falls back to raw text when non-OK JSON body is malformed', async () => { const client = createClient() diff --git a/test/web/runtime-compat.web.test.ts b/test/web/runtime-compat.web.test.ts index 289d9b3..d855cc9 100644 --- a/test/web/runtime-compat.web.test.ts +++ b/test/web/runtime-compat.web.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import Facturapi from '../../src' +import Facturapi, { FacturapiError } from '../../src' const originalFetch = globalThis.fetch const originalBuffer = (globalThis as any).Buffer @@ -127,6 +127,49 @@ describe('runtime compatibility (web simulation)', () => { ) }) + it('surfaces structured API errors and exposed headers in web-like runtime', async () => { + const client = createClient() + + globalThis.fetch = vi.fn(async () => { + return { + ok: false, + status: 429, + statusText: 'Too Many Requests', + headers: { + get(name: string) { + const values: Record = { + 'content-type': 'application/json', + 'retry-after': '3', + 'x-facturapi-log-id': 'log_123', + } + return values[name.toLowerCase()] || null + }, + }, + async text() { + return JSON.stringify({ + message: 'Se excedió el límite de solicitudes.', + status: 429, + code: 'RATE_LIMIT_EXCEEDED', + }) + }, + } as unknown as Response + }) as typeof fetch + + try { + await client.invoices.retrieve('inv_123') + throw new Error('Expected request to fail') + } catch (error) { + expect(error).toBeInstanceOf(FacturapiError) + expect((error as FacturapiError).status).toBe(429) + expect((error as FacturapiError).code).toBe('RATE_LIMIT_EXCEEDED') + expect((error as FacturapiError).logId).toBe('log_123') + expect((error as FacturapiError).headers['retry-after']).toBe('3') + expect((error as FacturapiError).headers['x-facturapi-log-id']).toBe( + 'log_123', + ) + } + }) + it('posts multiple receipts to invoice payload in web-like runtime', async () => { const client = createClient() const payload = { From a0b265d761b1618955a993d08fede9df8d9761b1 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sat, 6 Jun 2026 23:50:02 +0200 Subject: [PATCH 2/5] Add npm publish Slack notifications --- .github/workflows/deploy.yml | 138 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 5 ++ package-lock.json | 6 +- package.json | 4 +- 4 files changed, 148 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1b30ec5..5908678 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,5 +39,143 @@ jobs: fi - name: Publish to npm + id: publish_step if: env.exists == 'false' run: npm publish --provenance --access public + + - name: Notify Slack (success) + if: env.exists == 'false' && steps.publish_step.outcome == 'success' + env: + SLACK_DEPLOY_WEBHOOK_URL: ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} + shell: bash + run: | + set -euo pipefail + if [ -z "${SLACK_DEPLOY_WEBHOOK_URL:-}" ]; then + echo "SLACK_DEPLOY_WEBHOOK_URL not set; skipping Slack notification" + exit 0 + fi + + python3 - <<'PY' + import json + import os + from pathlib import Path + + package = json.loads(Path("package.json").read_text(encoding="utf-8")) + version = package["version"] + package_name = package["name"] + ref_name = os.environ["GITHUB_REF_NAME"] + sha = os.environ["GITHUB_SHA"] + actor = os.environ["GITHUB_ACTOR"] + server_url = os.environ["GITHUB_SERVER_URL"] + repo = os.environ["GITHUB_REPOSITORY"] + run_id = os.environ["GITHUB_RUN_ID"] + + changelog = Path("CHANGELOG.md") + latest_changes = "See CHANGELOG for details." + section_header = f"## [{version}]" + if changelog.exists(): + lines = changelog.read_text(encoding="utf-8").splitlines() + in_target = False + bullets = [] + for line in lines: + if line.startswith(section_header): + in_target = True + continue + if in_target and line.startswith("## ["): + break + if in_target and line.startswith("- "): + bullets.append(line[2:].strip()) + if bullets: + latest_changes = "\n".join(f"• {bullet}" for bullet in bullets[:5]) + + payload = { + "text": f"Facturapi Node SDK {version} published to npm", + "blocks": [ + { + "type": "header", + "text": { + "type": "plain_text", + "text": f"Node SDK {version} published to npm" + } + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": f"*Package:* `{package_name}@{version}`"}, + {"type": "mrkdwn", "text": f"*Branch:* `{ref_name}`"}, + {"type": "mrkdwn", "text": f"*Commit:* `{sha}`"}, + {"type": "mrkdwn", "text": f"*Actor:* `{actor}`"} + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ( + "*Useful links*\n" + f"• npm: \n" + f"• Workflow run: <{server_url}/{repo}/actions/runs/{run_id}|Open run>\n" + f"• Changelog: <{server_url}/{repo}/blob/{sha}/CHANGELOG.md|Read changes>" + ) + } + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": f"*Latest changes*\n{latest_changes}" + } + } + ] + } + + Path("/tmp/slack_payload.json").write_text(json.dumps(payload), encoding="utf-8") + PY + + curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true + + - name: Notify Slack (failure) + if: failure() && env.exists == 'false' && steps.publish_step.outcome == 'failure' + env: + SLACK_DEPLOY_WEBHOOK_URL: ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} + shell: bash + run: | + set -euo pipefail + if [ -z "${SLACK_DEPLOY_WEBHOOK_URL:-}" ]; then + echo "SLACK_DEPLOY_WEBHOOK_URL not set; skipping Slack notification" + exit 0 + fi + + python3 - <<'PY' + import json + import os + from pathlib import Path + + package = json.loads(Path("package.json").read_text(encoding="utf-8")) + version = package["version"] + server_url = os.environ["GITHUB_SERVER_URL"] + repo = os.environ["GITHUB_REPOSITORY"] + run_id = os.environ["GITHUB_RUN_ID"] + sha = os.environ["GITHUB_SHA"] + + payload = { + "text": f"Facturapi Node SDK {version} publish failed", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": ( + f"*Node SDK {version} publish failed*\n" + f"• Workflow run: <{server_url}/{repo}/actions/runs/{run_id}|Open run>\n" + f"• Commit: `{sha}`" + ) + } + } + ] + } + + Path("/tmp/slack_payload_failure.json").write_text(json.dumps(payload), encoding="utf-8") + PY + + curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload_failure.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true diff --git a/CHANGELOG.md b/CHANGELOG.md index 068af7e..54a6067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.17.1] 2026-06-06 +### Added +- Expose structured API error metadata through `FacturapiError`, including `status`, `code`, `path`, `location`, `errors`, `logId`, and response `headers`. +- Notify Slack when npm publishing succeeds or fails, when `SLACK_DEPLOY_WEBHOOK_URL` is configured. + ## [4.17.0] 2026-04-27 ### Added - Add support for custom request headers through the `Facturapi` constructor options. diff --git a/package-lock.json b/package-lock.json index 97ef798..7067995 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "facturapi", - "version": "4.17.0", + "version": "4.17.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "facturapi", - "version": "4.17.0", + "version": "4.17.1", "license": "MIT", "devDependencies": { "@eslint/js": "^10.0.1", @@ -5077,4 +5077,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 5c916e3..bed25c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "facturapi", - "version": "4.17.0", + "version": "4.17.1", "description": "SDK oficial de Facturapi para Node.js y navegadores. Integra facturación electrónica en México (CFDI) de forma simple y obtén una perspectiva fiscal completa de tu operación, con búsquedas indexadas, envío de documentos y trazabilidad.", "main": "dist/index.cjs.js", "module": "dist/index.es.js", @@ -95,4 +95,4 @@ "vite": "^8.0.3", "vitest": "^4.1.2" } -} \ No newline at end of file +} From cb0e38341a3db85c7685084a6bceb430c827fb11 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sun, 7 Jun 2026 00:23:50 +0200 Subject: [PATCH 3/5] Use Node for Slack publish payloads --- .github/workflows/deploy.yml | 192 ++++++++++++++++------------------- 1 file changed, 87 insertions(+), 105 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5908678..382b53e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -55,82 +55,74 @@ jobs: exit 0 fi - python3 - <<'PY' - import json - import os - from pathlib import Path - - package = json.loads(Path("package.json").read_text(encoding="utf-8")) - version = package["version"] - package_name = package["name"] - ref_name = os.environ["GITHUB_REF_NAME"] - sha = os.environ["GITHUB_SHA"] - actor = os.environ["GITHUB_ACTOR"] - server_url = os.environ["GITHUB_SERVER_URL"] - repo = os.environ["GITHUB_REPOSITORY"] - run_id = os.environ["GITHUB_RUN_ID"] - - changelog = Path("CHANGELOG.md") - latest_changes = "See CHANGELOG for details." - section_header = f"## [{version}]" - if changelog.exists(): - lines = changelog.read_text(encoding="utf-8").splitlines() - in_target = False - bullets = [] - for line in lines: - if line.startswith(section_header): - in_target = True - continue - if in_target and line.startswith("## ["): - break - if in_target and line.startswith("- "): - bullets.append(line[2:].strip()) - if bullets: - latest_changes = "\n".join(f"• {bullet}" for bullet in bullets[:5]) - - payload = { - "text": f"Facturapi Node SDK {version} published to npm", - "blocks": [ - { - "type": "header", - "text": { - "type": "plain_text", - "text": f"Node SDK {version} published to npm" - } - }, - { - "type": "section", - "fields": [ - {"type": "mrkdwn", "text": f"*Package:* `{package_name}@{version}`"}, - {"type": "mrkdwn", "text": f"*Branch:* `{ref_name}`"}, - {"type": "mrkdwn", "text": f"*Commit:* `{sha}`"}, - {"type": "mrkdwn", "text": f"*Actor:* `{actor}`"} - ] - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ( - "*Useful links*\n" - f"• npm: \n" - f"• Workflow run: <{server_url}/{repo}/actions/runs/{run_id}|Open run>\n" - f"• Changelog: <{server_url}/{repo}/blob/{sha}/CHANGELOG.md|Read changes>" - ) - } - }, - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": f"*Latest changes*\n{latest_changes}" - } - } - ] + node <<'JS' + const fs = require('node:fs'); + + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); + const version = packageJson.version; + const packageName = packageJson.name; + let latestChanges = 'See CHANGELOG for details.'; + + if (fs.existsSync('CHANGELOG.md')) { + const lines = fs.readFileSync('CHANGELOG.md', 'utf8').split(/\r?\n/); + const bullets = []; + let inTargetSection = false; + for (const line of lines) { + if (line.startsWith(`## [${version}]`)) { + inTargetSection = true; + continue; + } + if (inTargetSection && line.startsWith('## [')) break; + if (inTargetSection && line.startsWith('- ')) { + bullets.push(line.slice(2).trim()); + } + } + if (bullets.length) { + latestChanges = bullets.slice(0, 5).map((bullet) => `• ${bullet}`).join('\n'); + } } - Path("/tmp/slack_payload.json").write_text(json.dumps(payload), encoding="utf-8") - PY + fs.writeFileSync('/tmp/slack_payload.json', JSON.stringify({ + text: `Facturapi Node SDK ${version} published to npm`, + blocks: [ + { + type: 'header', + text: { + type: 'plain_text', + text: `Node SDK ${version} published to npm`, + }, + }, + { + type: 'section', + fields: [ + { type: 'mrkdwn', text: `*Package:* \`${packageName}@${version}\`` }, + { type: 'mrkdwn', text: `*Branch:* \`${process.env.GITHUB_REF_NAME}\`` }, + { type: 'mrkdwn', text: `*Commit:* \`${process.env.GITHUB_SHA}\`` }, + { type: 'mrkdwn', text: `*Actor:* \`${process.env.GITHUB_ACTOR}\`` }, + ], + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: [ + '*Useful links*', + `• npm: `, + `• Workflow run: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}|Open run>`, + `• Changelog: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/blob/${process.env.GITHUB_SHA}/CHANGELOG.md|Read changes>`, + ].join('\n'), + }, + }, + { + type: 'section', + text: { + type: 'mrkdwn', + text: `*Latest changes*\n${latestChanges}`, + }, + }, + ], + })); + JS curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true @@ -146,36 +138,26 @@ jobs: exit 0 fi - python3 - <<'PY' - import json - import os - from pathlib import Path - - package = json.loads(Path("package.json").read_text(encoding="utf-8")) - version = package["version"] - server_url = os.environ["GITHUB_SERVER_URL"] - repo = os.environ["GITHUB_REPOSITORY"] - run_id = os.environ["GITHUB_RUN_ID"] - sha = os.environ["GITHUB_SHA"] - - payload = { - "text": f"Facturapi Node SDK {version} publish failed", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": ( - f"*Node SDK {version} publish failed*\n" - f"• Workflow run: <{server_url}/{repo}/actions/runs/{run_id}|Open run>\n" - f"• Commit: `{sha}`" - ) - } - } - ] - } - - Path("/tmp/slack_payload_failure.json").write_text(json.dumps(payload), encoding="utf-8") - PY + node <<'JS' + const fs = require('node:fs'); + const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); + + fs.writeFileSync('/tmp/slack_payload_failure.json', JSON.stringify({ + text: `Facturapi Node SDK ${packageJson.version} publish failed`, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: [ + `*Node SDK ${packageJson.version} publish failed*`, + `• Workflow run: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}|Open run>`, + `• Commit: \`${process.env.GITHUB_SHA}\``, + ].join('\n'), + }, + }, + ], + })); + JS curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload_failure.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true From 44d367db552ca0de2e35a85927a836a73d131875 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sun, 7 Jun 2026 00:28:16 +0200 Subject: [PATCH 4/5] Bump Node SDK to 4.18.0 --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54a6067..b4f4bc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [4.17.1] 2026-06-06 +## [4.18.0] 2026-06-06 ### Added - Expose structured API error metadata through `FacturapiError`, including `status`, `code`, `path`, `location`, `errors`, `logId`, and response `headers`. - Notify Slack when npm publishing succeeds or fails, when `SLACK_DEPLOY_WEBHOOK_URL` is configured. diff --git a/package-lock.json b/package-lock.json index 7067995..df9b27b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "facturapi", - "version": "4.17.1", + "version": "4.18.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "facturapi", - "version": "4.17.1", + "version": "4.18.0", "license": "MIT", "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/package.json b/package.json index bed25c5..1b9b2b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "facturapi", - "version": "4.17.1", + "version": "4.18.0", "description": "SDK oficial de Facturapi para Node.js y navegadores. Integra facturación electrónica en México (CFDI) de forma simple y obtén una perspectiva fiscal completa de tu operación, con búsquedas indexadas, envío de documentos y trazabilidad.", "main": "dist/index.cjs.js", "module": "dist/index.es.js", From 3494dca56ae9a6db44ab84875de280fc62818eb8 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sun, 7 Jun 2026 00:41:51 +0200 Subject: [PATCH 5/5] Keep internal Slack changes out of changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f4bc7..52628eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [4.18.0] 2026-06-06 ### Added - Expose structured API error metadata through `FacturapiError`, including `status`, `code`, `path`, `location`, `errors`, `logId`, and response `headers`. -- Notify Slack when npm publishing succeeds or fails, when `SLACK_DEPLOY_WEBHOOK_URL` is configured. ## [4.17.0] 2026-04-27 ### Added