From 6a715b602a9c93b400995e1f0aca25b6aa1acb91 Mon Sep 17 00:00:00 2001 From: Andre Kutianski Date: Fri, 12 Jun 2026 22:32:34 -0300 Subject: [PATCH 1/2] fix(security): corrigir alertas do code scanning (CodeQL) - ci.yml: define permissions minimas (contents: read) para o GITHUB_TOKEN no nivel do workflow (alertas #1-3) - remove scripts/download-openapi.ts e o script download:spec: codigo morto (todos os endpoints retornam 404; specs sao mantidas manualmente em openapi/spec/) (alerta #6) - remove lib/ (codigo legado v2): nao importado, nao publicado no pacote (files exclui) e nao executavel (depende de 'when', nao instalada); v2 vive no historico git e no nfe-io@2.x do npm (alertas #5, #7, #8) - atualiza CLAUDE.md e README.md Refs #31 --- .github/workflows/ci.yml | 3 + CLAUDE.md | 9 +- README.md | 5 +- lib/BaseResource.Method.js | 66 --------- lib/BaseResource.js | 234 ------------------------------- lib/Error.js | 71 ---------- lib/nfe.js | 136 ------------------ lib/resources/Companies.js | 43 ------ lib/resources/LegalPeople.js | 39 ------ lib/resources/NaturalPeople.js | 39 ------ lib/resources/ServiceInvoices.js | 50 ------- lib/resources/Webhooks.js | 37 ----- lib/utils.js | 106 -------------- package.json | 1 - scripts/download-openapi.ts | 187 ------------------------ 15 files changed, 6 insertions(+), 1020 deletions(-) delete mode 100644 lib/BaseResource.Method.js delete mode 100644 lib/BaseResource.js delete mode 100644 lib/Error.js delete mode 100644 lib/nfe.js delete mode 100644 lib/resources/Companies.js delete mode 100644 lib/resources/LegalPeople.js delete mode 100644 lib/resources/NaturalPeople.js delete mode 100644 lib/resources/ServiceInvoices.js delete mode 100644 lib/resources/Webhooks.js delete mode 100644 lib/utils.js delete mode 100644 scripts/download-openapi.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fadd2f..72bec22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master ] +permissions: + contents: read + jobs: test: name: Test (Node ${{ matrix.node-version }}) diff --git a/CLAUDE.md b/CLAUDE.md index 6d7516d..d749bb9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Official NFE.io SDK for Node.js -- TypeScript native client for Brazilian electronic fiscal document APIs (NFS-e, NF-e, CT-e, CF-e). Version 3.x is a complete rewrite from the legacy v2 JavaScript/callback codebase that still lives in `lib/`. +Official NFE.io SDK for Node.js -- TypeScript native client for Brazilian electronic fiscal document APIs (NFS-e, NF-e, CT-e, CF-e). Version 3.x is a complete rewrite from the legacy v2 JavaScript/callback codebase (v2 lives in git history and is published as `nfe-io@2.x` on npm). ## Build & Development Commands @@ -32,12 +32,7 @@ Coverage thresholds: 80% for branches, functions, lines, and statements. Test se ## Architecture -### Dual Codebase (v2 + v3) - -- **v3 (active)**: `src/` -- TypeScript, async/await, Fetch API, zero runtime dependencies -- **v2 (legacy)**: `lib/` -- JavaScript, `when` promises, `BaseResource.extend()` pattern - -### v3 Source Structure +### Source Structure - `src/index.ts` -- Barrel export for all public API (`NfeClient`, types, errors) - `src/core/client.ts` -- Main `NfeClient` class with lazy-initialized resource getters and polling utilities diff --git a/README.md b/README.md index 677781d..08fe4fb 100644 --- a/README.md +++ b/README.md @@ -1050,12 +1050,9 @@ Veja [tests/integration/README.md](./tests/integration/README.md) para documenta ### Geração de Tipos OpenAPI -O SDK gera tipos TypeScript automaticamente a partir de especificações OpenAPI: +O SDK gera tipos TypeScript automaticamente a partir de especificações OpenAPI. As specs são mantidas manualmente em `openapi/spec/`: ```bash -# Baixar specs mais recentes da API (se disponível) -npm run download:spec - # Validar todas as specs OpenAPI npm run validate:spec diff --git a/lib/BaseResource.Method.js b/lib/BaseResource.Method.js deleted file mode 100644 index fca4f02..0000000 --- a/lib/BaseResource.Method.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -var path = require('path'); -var utils = require('./utils'); - -/** - * Create an API method from the declared spec. - * - * @param [spec.method='GET'] Request Method (POST, GET, DELETE, PUT) - * @param [spec.path=''] Path to be appended to the API BASE_PATH, joined with - * the instance's path (e.g. "charges" or "customers") - * @param [spec.required=[]] Array of required arguments in the order that they - * must be passed by the consumer of the API. Subsequent optional arguments are - * optionally passed through a hash (Object) as the penultimate argument - * (preceeding the also-optional callback argument - */ -module.exports = function nfeMethod(spec) { - - var commandPath = utils.makeURLInterpolator( spec.path || '' ); - var requestMethod = (spec.method || 'GET').toUpperCase(); - var urlParams = spec.urlParams || []; - - return function() { - - var self = this; - var args = [].slice.call(arguments); - - var callback = typeof args[args.length - 1] == 'function' && args.pop(); - var auth = args.length > urlParams.length && utils.isAuthKey(args[args.length - 1]) ? args.pop() : null; - var data = utils.isObject(args[args.length - 1]) ? args.pop() : {}; - var urlData = this.createUrlData(); - - var deferred = this.createDeferred(callback); - - for (var i = 0, l = urlParams.length; i < l; ++i) { - var arg = args[0]; - if (urlParams[i] && !arg) { - throw new Error('NFe.io: I require argument "' + urlParams[i] + '", but I got: ' + arg); - } - urlData[urlParams[i]] = args.shift(); - } - - if (args.length) { - throw new Error( - 'NFe.io: Unknown arguments (' + args + '). Did you mean to pass an options object? ' - ); - } - - var requestPath = this.createFullPath(commandPath, urlData); - - self._request(requestMethod, requestPath, data, auth, function(err, response) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve( - spec.transformResponseData ? - spec.transformResponseData(response) : - response - ); - } - }); - - return deferred.promise; - - }; -}; \ No newline at end of file diff --git a/lib/BaseResource.js b/lib/BaseResource.js deleted file mode 100644 index 1a9ab94..0000000 --- a/lib/BaseResource.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; - -var constants = require('constants'); -var http = require('http'); -var https = require('https'); -var path = require('path'); -var when = require('when'); - -var utils = require('./utils'); -var Error = require('./Error'); - -var hasOwn = {}.hasOwnProperty; - -// Provide extension mechanism for NFe.io Resource Sub-Classes -NfeResource.extend = utils.protoExtend; - -// Expose method-creator & prepared (basic) methods -NfeResource.method = require('./BaseResource.Method'); - -/** - * Encapsulates request logic for a NFe.io Resource - */ -function NfeResource(client, urlData) { - - this._client = client; - this._urlData = urlData || {}; - - this.basePath = utils.makeURLInterpolator(this._client.getApiField('basePath')); - this.path = utils.makeURLInterpolator(this.path); - - this.initialize.apply(this, arguments); - -} - -NfeResource.prototype = { - - path: '', - - initialize: function() {}, - - createFullPath: function(commandPath, urlData) { - return path.join( - this.basePath(urlData), - this.path(urlData), - typeof commandPath == 'function' ? - commandPath(urlData) : commandPath - ).replace(/\\/g, '/'); // ugly workaround for Windows - }, - - createUrlData: function() { - var urlData = {}; - // Merge in baseData - for (var i in this._urlData) { - if (hasOwn.call(this._urlData, i)) { - urlData[i] = this._urlData[i]; - } - } - return urlData; - }, - - createDeferred: function(callback) { - var deferred = when.defer(); - - if (callback) { - // Callback, if provided, is a simply translated to Promise'esque: - // (Ensure callback is called outside of promise stack) - deferred.promise.then(function(res) { - setTimeout(function(){ callback(null, res) }, 0); - }, function(err) { - setTimeout(function(){ callback(err, null); }, 0); - }); - } - - return deferred; - }, - - _timeoutHandler: function(timeout, req, callback) { - var self = this; - return function() { - var timeoutErr = new Error('ETIMEDOUT'); - timeoutErr.code = 'ETIMEDOUT'; - - req._isAborted = true; - req.abort(); - - callback.call( - self, - new Error.ConnectionError({ - message: 'Request aborted due to timeout being reached (' + timeout + 'ms)', - detail: timeoutErr - }), - null - ); - } - }, - - _responseHandler: function(res, callback) { - var self = this; - - return function(res) { - - if (res.statusCode === 201 || res.statusCode === 202) { - callback.call(self, null, { - code: res.statusCode, - location: res.headers.location - }); - } - - var data = ''; - res.setEncoding('utf8'); - res.on('data', function(chunk) { - data += chunk; - }); - res.on('end', function() { - try { - - var response = JSON.parse(data); - response.code = res.statusCode; - - if (response.code > 299) { - var err; - if (response.code === 401) { - err = new Error.AuthenticationError(response.message); - } else { - err = Error.ResourceError.generate(response); - } - return callback.call(self, err, null); - } - - } catch (e) { - return callback.call( - self, - new Error.APIError({ - message: 'Invalid JSON received from the NFe.io API', - response: response, - exception: e - }), - null - ); - } - callback.call(self, null, response); - }); - }; - }, - - _errorHandler: function(req, callback) { - var self = this; - return function(error) { - if (req._isAborted) return; // already handled - callback.call( - self, - new Error.ConnectionError({ - message: 'An error occurred with our connection to NFe.io', - detail: error - }), - null - ); - } - }, - - _request: function(method, path, data, auth, callback) { - - var requestData = JSON.stringify(data || {}); - var self = this; - - var apiVersion = this._client.getApiField('version'); - var headers = { - // Use specified auth token or use default from this stripe instance: - 'Authorization': auth ? - 'Basic ' + new Buffer(auth) : //'Basic ' + new Buffer(auth + ':').toString('base64') : - this._client.getApiField('auth'), - 'Accept': 'application/json', - 'User-Agent': 'Nfe-io/v1 NodeBindings/' + this._client.getConstant('PACKAGE_VERSION'), - }; - - if (method !== 'GET' && data.formData === undefined) - { - headers['Content-Type'] = 'application/json'; - //headers['Content-Length'] = Buffer.byteLength(requestData); - } - - if (apiVersion) { - headers['Nfe-Version'] = apiVersion; - } - - // Grab client-user-agent before making the request: - this._client.getClientUserAgent(function(cua) { - //headers['X-Nfe-Client-User-Agent'] = cua; - makeRequest(); - }); - - function makeRequest() { - - var timeout = self._client.getApiField('timeout'), - options = { - host: self._client.getApiField('host'), - port: self._client.getApiField('port'), - path: path, - method: method, - headers: headers, - agent: false, - rejectUnauthorized: false, - strictSSL: false - }; - - if (data.formData) { - options.formData = data; - requestData = undefined; - } - - var req = ( - self._client.getApiField('protocol') == 'http' ? http : https - ).request(options); - - req.setTimeout(timeout, self._timeoutHandler(timeout, req, callback)); - req.on('response', self._responseHandler(req, callback)); - req.on('error', self._errorHandler(req, callback)); - - req.on('socket', function(socket) { - socket.on('secureConnect', function() { - if (requestData) { - req.write(requestData); - } - req.end(); - }); - }); - - } - - } - -}; - -module.exports = NfeResource; \ No newline at end of file diff --git a/lib/Error.js b/lib/Error.js deleted file mode 100644 index a57244c..0000000 --- a/lib/Error.js +++ /dev/null @@ -1,71 +0,0 @@ -'use strict'; - -var utils = require('./utils'); - -module.exports = _Error; - -/** - * Generic Error class to wrap any errors returned by iugu-node - */ -function _Error(raw) { - this.populate.apply(this, arguments); -} - -// Extend Native Error -_Error.prototype = Object.create(Error.prototype); - -_Error.prototype.type = 'GenericError'; -_Error.prototype.populate = function(type, message) { - this.type = type; - this.message = message; -}; - -_Error.extend = utils.protoExtend; - -/** - * Create subclass of internal Error class - * (Specifically for errors returned from NFe.io REST API) - */ -var ResourceError = _Error.ResourceError = _Error.extend({ - type: 'ResourceError', - populate: function(raw) { - - // Move from prototype def (so it appears in stringified obj) - this.type = this.type; - - this.code = raw.code; - this.message = raw.message; - this.raw = raw; - } -}); - -/** - * Helper factory which takes raw iugu errors and outputs wrapping instances - */ -ResourceError.generate = function(raw) { - - switch (raw.code) { - case 400: - return new _Error.BadRequestError(raw); - break; - case 404: - return new _Error.NotFoundError(raw); - break; - case 409: - return new _Error.ConflictError(raw); - break; - default: - return new _Error.APIError(raw); - break; - } - - return new _Error('Generic', 'Unknown Error'); -}; - -// Specific Stripe Error types: -_Error.ConflictError = ResourceError.extend({ type: 'ConflictError' }); -_Error.BadRequestError = ResourceError.extend({ type: 'BadRequestError' }); -_Error.NotFoundError = ResourceError.extend({ type: 'NotFoundError' }); -_Error.APIError = ResourceError.extend({ type: 'APIError' }); -_Error.AuthenticationError = ResourceError.extend({ type: 'AuthenticationError' }); -_Error.ConnectionError = ResourceError.extend({ type: 'ConnectionError' }); diff --git a/lib/nfe.js b/lib/nfe.js deleted file mode 100644 index b97a0b3..0000000 --- a/lib/nfe.js +++ /dev/null @@ -1,136 +0,0 @@ -'use strict'; - -Nfe.DEFAULT_HOST = 'api.nfe.io'; -Nfe.DEFAULT_PORT = '443'; -Nfe.DEFAULT_PROTOCOL = Nfe.DEFAULT_PORT === '443' ? 'https' : 'http'; -Nfe.DEFAULT_BASE_PATH = '/v1/'; -Nfe.DEFAULT_API_VERSION = null; - -// Use node's default timeout: -Nfe.DEFAULT_TIMEOUT = require('http').createServer().timeout; - -Nfe.PACKAGE_VERSION = require('../package.json').version; - -Nfe.USER_AGENT = { - bindings_version: Nfe.PACKAGE_VERSION, - lang: 'node', - lang_version: process.version, - platform: process.platform, - publisher: 'nfe', - uname: null -}; - -Nfe.USER_AGENT_SERIALIZED = null; - -var exec = require('child_process').exec; - -var resources = { - - Companies: require('./resources/Companies'), - Webhooks: require('./resources/Webhooks'), - - LegalPeople: require('./resources/LegalPeople'), - NaturalPeople: require('./resources/NaturalPeople'), - - ServiceInvoices: require('./resources/ServiceInvoices') - -}; - -Nfe.baseResource = require('./BaseResource'); -Nfe.resources = resources; - -function Nfe(key, version) { - - if (!(this instanceof Nfe)) { - return new Nfe(key, version); - } - - this._api = { - auth: null, - host: Nfe.DEFAULT_HOST, - protocol: Nfe.DEFAULT_PROTOCOL, - port: Nfe.DEFAULT_PORT, - basePath: Nfe.DEFAULT_BASE_PATH, - version: Nfe.DEFAULT_API_VERSION, - timeout: Nfe.DEFAULT_TIMEOUT, - dev: false - }; - - this._prepResources(); - this.setApiKey(key); - this.setApiVersion(version); -} - -Nfe.prototype = { - - setHost: function(host, port, protocol) { - this._setApiField('host', host); - if (port) this.setPort(port); - if (protocol) this.setProtocol(protocol); - }, - - setProtocol: function(protocol) { - this._setApiField('protocol', protocol.toLowerCase()); - }, - - setPort: function(port) { - this._setApiField('port', port); - }, - - setApiVersion: function(version) { - if (version) { - this._setApiField('version', version); - } - }, - - setApiKey: function(key) { - if (key) { - this._setApiField( - 'auth', - 'Basic ' + new Buffer(key) - // 'Basic ' + new Buffer(key + ':').toString('base64') - ); - } - }, - - setTimeout: function(timeout) { - this._setApiField( - 'timeout', - timeout == null ? Nfe.DEFAULT_TIMEOUT : timeout - ); - }, - - _setApiField: function(key, value) { - this._api[key] = value; - }, - - getApiField: function(key) { - return this._api[key]; - }, - - getConstant: function(c) { - return Nfe[c]; - }, - - getClientUserAgent: function(cb) { - if (Nfe.USER_AGENT_SERIALIZED) { - return cb(Nfe.USER_AGENT_SERIALIZED); - } - exec('uname -a', function(err, uname) { - Nfe.USER_AGENT.uname = uname || 'UNKNOWN'; - Nfe.USER_AGENT_SERIALIZED = JSON.stringify(Nfe.USER_AGENT); - cb(Nfe.USER_AGENT_SERIALIZED); - }); - }, - - _prepResources: function() { - for (var name in resources) { - this[ - name[0].toLowerCase() + name.substring(1) - ] = new resources[name](this); - } - } - -}; - -module.exports = Nfe; diff --git a/lib/resources/Companies.js b/lib/resources/Companies.js deleted file mode 100644 index 03f2d72..0000000 --- a/lib/resources/Companies.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -var BaseResource = require('../BaseResource'); -var restMethod = BaseResource.method; - -module.exports = BaseResource.extend({ - - path: '/companies', - - create: restMethod({ - method: 'POST' - }), - - list: restMethod({ - method: 'GET' - }), - - retrieve: restMethod({ - method: 'GET', - path: '/{id}', - urlParams: ['id'] - }), - - update: restMethod({ - method: 'PUT', - path: '{id}', - urlParams: ['id'] - }), - - // Avoid 'delete' keyword in JS - remove: restMethod({ - method: 'DELETE', - path: '{id}', - urlParams: ['id'] - }), - - uploadCertificate: restMethod({ - method: 'POST', - path: '{id}/certificate', - urlParams: ['id'] - }) - -}); diff --git a/lib/resources/LegalPeople.js b/lib/resources/LegalPeople.js deleted file mode 100644 index 4e03057..0000000 --- a/lib/resources/LegalPeople.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var BaseResource = require('../BaseResource'); -var restMethod = BaseResource.method; - -module.exports = BaseResource.extend({ - - path: '/companies/{company_id}/legalpeople', - - create: restMethod({ - method: 'POST', - urlParams: ['company_id'] - }), - - list: restMethod({ - method: 'GET', - urlParams: ['company_id'] - }), - - retrieve: restMethod({ - method: 'GET', - path: '/{id}', - urlParams: ['company_id', 'id'] - }), - - update: restMethod({ - method: 'PUT', - path: '{id}', - urlParams: ['company_id', 'id'] - }), - - // Avoid 'delete' keyword in JS - remove: restMethod({ - method: 'DELETE', - path: '{id}', - urlParams: ['company_id', 'id'] - }) - -}); diff --git a/lib/resources/NaturalPeople.js b/lib/resources/NaturalPeople.js deleted file mode 100644 index 70d683c..0000000 --- a/lib/resources/NaturalPeople.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -var BaseResource = require('../BaseResource'); -var restMethod = BaseResource.method; - -module.exports = BaseResource.extend({ - - path: '/companies/{company_id}/naturalpeople', - - create: restMethod({ - method: 'POST', - urlParams: ['company_id'] - }), - - list: restMethod({ - method: 'GET', - urlParams: ['company_id'] - }), - - retrieve: restMethod({ - method: 'GET', - path: '/{id}', - urlParams: ['company_id', 'id'] - }), - - update: restMethod({ - method: 'PUT', - path: '{id}', - urlParams: ['company_id', 'id'] - }), - - // Avoid 'delete' keyword in JS - remove: restMethod({ - method: 'DELETE', - path: '{id}', - urlParams: ['company_id', 'id'] - }) - -}); diff --git a/lib/resources/ServiceInvoices.js b/lib/resources/ServiceInvoices.js deleted file mode 100644 index ca24a40..0000000 --- a/lib/resources/ServiceInvoices.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict'; - -var BaseResource = require('../BaseResource'); -var restMethod = BaseResource.method; - -module.exports = BaseResource.extend({ - - path: '/companies/{company_id}/serviceinvoices', - - create: restMethod({ - method: 'POST', - urlParams: ['company_id'] - }), - - list: restMethod({ - method: 'GET', - urlParams: ['company_id'] - }), - - retrieve: restMethod({ - method: 'GET', - path: '/{id}', - urlParams: ['company_id', 'id'] - }), - - cancel: restMethod({ - method: 'DELETE', - path: '/{id}', - urlParams: ['company_id', 'id'] - }), - - sendemail: restMethod({ - method: 'PUT', - path: '/{id}/sendemail', - urlParams: ['company_id', 'id'] - }), - - downloadPdf : restMethod({ - method: 'GET', - path: '/pdf', - urlParams: ['company_id'] - }), - - downloadXml : restMethod({ - method: 'GET', - path: '/xml', - urlParams: ['company_id'] - }) - -}); diff --git a/lib/resources/Webhooks.js b/lib/resources/Webhooks.js deleted file mode 100644 index 8da2f2a..0000000 --- a/lib/resources/Webhooks.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -var BaseResource = require('../BaseResource'); -var restMethod = BaseResource.method; - -module.exports = BaseResource.extend({ - - path: '/hooks', - - create: restMethod({ - method: 'POST' - }), - - list: restMethod({ - method: 'GET' - }), - - retrieve: restMethod({ - method: 'GET', - path: '/{id}', - urlParams: ['id'] - }), - - update: restMethod({ - method: 'PUT', - path: '{id}', - urlParams: ['id'] - }), - - // Avoid 'delete' keyword in JS - remove: restMethod({ - method: 'DELETE', - path: '{id}', - urlParams: ['id'] - }) - -}); diff --git a/lib/utils.js b/lib/utils.js deleted file mode 100644 index c2f05c7..0000000 --- a/lib/utils.js +++ /dev/null @@ -1,106 +0,0 @@ -'use strict'; - -var querystring = require('querystring'); - -var hasOwn = {}.hasOwnProperty; -var toString = {}.toString; - -var ARRAY_ - -var utils = module.exports = { - - isAuthKey: function (key) { - return typeof key == 'string' && /^(?:[a-z]{2}_)?[A-z0-9]{32}$/.test(key); - }, - - isObject: function(o) { - return toString.call(o) === '[object Object]'; - }, - - /** - * Stringifies an Object, accommodating a single-level of nested objects - * (forming the conventional key "parent[child]=value") - */ - stringifyRequestData: function(data) { - - var output = []; - - for (var i in data) { - if (hasOwn.call(data, i)) { - if (utils.isObject(data[i])) { - var hasProps = false; - for (var ii in data[i]) { - if (hasOwn.call(data[i], ii)) { - hasProps = true; - output.push(encode(i + '[' + ii + ']') + '=' + encode(data[i][ii])); - } - } - if (!hasProps) { - output.push(encode(i) + '=' + encode('')); - } - } else if (Array.isArray(data[i])) { - for (var a = 0, l = data[i].length; a < l; ++a) { - output.push(encode(i + '[]') + '=' + encode(data[i][a])); - } - } else { - output.push(encode(i) + '=' + encode(data[i])); - } - } - } - - return output.join('&'); - - function encode(v) { - return v == null ? '' : encodeURIComponent(v); - } - }, - - /** - * https://gist.github.com/padolsey/6008842 - * Outputs a new function with interpolated object property values. - * Use like so: - * var fn = makeURLInterpolator('some/url/{param1}/{param2}'); - * fn({ param1: 123, param2: 456 }); // => 'some/url/123/456' - */ - makeURLInterpolator: (function() { - var rc = { - '\n': '\\n', '\"': '\\\"', - '\u2028': '\\u2028', '\u2029': '\\u2029' - }; - return function makeURLInterpolator(str) { - return new Function( - 'o', - 'return "' + ( - str - .replace(/["\n\r\u2028\u2029]/g, function($0) { - return rc[$0]; - }) - .replace(/\{([\s\S]+?)\}/g, '" + encodeURIComponent(o["$1"]) + "') - ) + '";' - ); - }; - }()), - - /** - * Provide simple "Class" extension mechanism - */ - protoExtend: function(sub) { - var Super = this; - var Constructor = hasOwn.call(sub, 'constructor') ? sub.constructor : function() { - Super.apply(this, arguments); - }; - Constructor.prototype = Object.create(Super.prototype); - for (var i in sub) { - if (hasOwn.call(sub, i)) { - Constructor.prototype[i] = sub[i]; - } - } - for (i in Super) { - if (hasOwn.call(Super, i)) { - Constructor[i] = Super[i]; - } - } - return Constructor; - } - -}; diff --git a/package.json b/package.json index 5a25035..d0c53cb 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ ], "scripts": { "dev": "tsx watch src/index.ts", - "download:spec": "tsx scripts/download-openapi.ts", "generate": "tsx scripts/generate-types.ts", "generate:watch": "tsx watch scripts/generate-types.ts", "validate:spec": "tsx scripts/validate-spec.ts", diff --git a/scripts/download-openapi.ts b/scripts/download-openapi.ts deleted file mode 100644 index 268ef13..0000000 --- a/scripts/download-openapi.ts +++ /dev/null @@ -1,187 +0,0 @@ -#!/usr/bin/env tsx -/** - * Download OpenAPI specifications from NFE.io API - * - * This script attempts to download OpenAPI specs from known endpoints. - * Falls back gracefully if specs are not publicly available. - * - * Usage: - * npm run download:spec - * tsx scripts/download-openapi.ts - */ - -import { writeFileSync, mkdirSync, existsSync } from 'fs'; -import { join } from 'path'; - -interface SpecEndpoint { - name: string; - url: string; - outputFile: string; -} - -// Known spec endpoints (may not be publicly available) -const SPEC_ENDPOINTS: SpecEndpoint[] = [ - { - name: 'NF-e Serviço (Service Invoices)', - url: 'https://api.nfe.io/openapi/nf-servico-v1.json', - outputFile: 'nf-servico-v1.yaml', - }, - { - name: 'NF-e Produto', - url: 'https://api.nfe.io/openapi/nf-produto-v2.json', - outputFile: 'nf-produto-v2.yaml', - }, - { - name: 'NF-e Consumidor', - url: 'https://api.nfe.io/openapi/nf-consumidor-v2.json', - outputFile: 'nf-consumidor-v2.yaml', - }, - { - name: 'OpenAPI Main', - url: 'https://api.nfe.io/openapi.json', - outputFile: 'nfeio.yaml', - }, - { - name: 'OpenAPI v1', - url: 'https://api.nfe.io/v1/openapi.json', - outputFile: 'nfeio-v1.yaml', - }, -]; - -const OUTPUT_DIR = 'openapi/spec'; - -/** - * Convert JSON to YAML format (simple implementation) - */ -function jsonToYaml(json: any, indent = 0): string { - const spaces = ' '.repeat(indent); - let yaml = ''; - - if (Array.isArray(json)) { - json.forEach(item => { - if (typeof item === 'object' && item !== null) { - yaml += `${spaces}-\n${jsonToYaml(item, indent + 1)}`; - } else { - yaml += `${spaces}- ${item}\n`; - } - }); - } else if (typeof json === 'object' && json !== null) { - Object.entries(json).forEach(([key, value]) => { - if (Array.isArray(value)) { - yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`; - } else if (typeof value === 'object' && value !== null) { - yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`; - } else if (typeof value === 'string') { - const needsQuotes = value.includes(':') || value.includes('#') || value.includes('\n'); - yaml += `${spaces}${key}: ${needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value}\n`; - } else { - yaml += `${spaces}${key}: ${value}\n`; - } - }); - } - - return yaml; -} - -/** - * Download spec from URL - */ -async function downloadSpec(endpoint: SpecEndpoint): Promise { - try { - console.log(`📥 Downloading: ${endpoint.name}`); - console.log(` URL: ${endpoint.url}`); - - const response = await fetch(endpoint.url, { - headers: { - 'Accept': 'application/json, application/yaml, application/x-yaml, text/yaml', - 'User-Agent': 'NFE.io SDK OpenAPI Downloader', - }, - }); - - if (!response.ok) { - console.log(` ❌ HTTP ${response.status}: ${response.statusText}`); - return false; - } - - const contentType = response.headers.get('content-type') || ''; - let content: string; - - if (contentType.includes('json')) { - const json = await response.json(); - content = jsonToYaml(json); - } else { - content = await response.text(); - } - - // Ensure output directory exists - if (!existsSync(OUTPUT_DIR)) { - mkdirSync(OUTPUT_DIR, { recursive: true }); - } - - // Write to file - const outputPath = join(OUTPUT_DIR, endpoint.outputFile); - writeFileSync(outputPath, content, 'utf8'); - - console.log(` ✅ Saved to: ${outputPath}`); - console.log(` 📊 Size: ${(content.length / 1024).toFixed(2)} KB`); - return true; - - } catch (error) { - if (error instanceof Error) { - console.log(` ❌ Error: ${error.message}`); - } else { - console.log(` ❌ Unknown error`); - } - return false; - } -} - -/** - * Main download function - */ -async function main() { - console.log('🔍 NFE.io OpenAPI Spec Downloader'); - console.log('==================================\n'); - - let successCount = 0; - let failCount = 0; - - for (const endpoint of SPEC_ENDPOINTS) { - const success = await downloadSpec(endpoint); - if (success) { - successCount++; - } else { - failCount++; - } - console.log(''); // Blank line between downloads - } - - console.log('=================================='); - console.log(`✅ Downloaded: ${successCount}`); - console.log(`❌ Failed: ${failCount}`); - console.log(`📁 Output directory: ${OUTPUT_DIR}`); - - if (successCount === 0) { - console.log('\n⚠️ No specs downloaded.'); - console.log(' This is expected if NFE.io does not expose public OpenAPI endpoints.'); - console.log(' Manual spec creation may be required.\n'); - console.log(' See: https://github.com/nfe/client-nodejs/blob/main/CONTRIBUTING.md\n'); - } else { - console.log('\n✨ Success! Run `npm run validate:spec` to validate downloaded specs.\n'); - } - - // Exit with error code if all downloads failed - if (successCount === 0 && failCount > 0) { - process.exit(1); - } -} - -// Run if called directly -if (import.meta.url === `file://${process.argv[1]}`) { - main().catch((error) => { - console.error('Fatal error:', error); - process.exit(1); - }); -} - -export { downloadSpec, SPEC_ENDPOINTS }; From ebe0b5e686d0131bf24e9a172a6b8b2b6ae968b2 Mon Sep 17 00:00:00 2001 From: Andre Kutianski Date: Fri, 12 Jun 2026 22:34:00 -0300 Subject: [PATCH 2/2] fix(gitignore): add nfeio-docs to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d936937..1eb987e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ node_modules/ bower_components/ jspm_packages/ openspec/ - +nfeio-docs # ---------------------------------------------------------------------------- # Build Outputs # ----------------------------------------------------------------------------