diff --git a/.changeset/public-adults-grab.md b/.changeset/public-adults-grab.md new file mode 100644 index 0000000..83724c4 --- /dev/null +++ b/.changeset/public-adults-grab.md @@ -0,0 +1,12 @@ +--- +"gitignoreio-sdk": minor +--- + +Change the `generate` return type to a Promise. +With this change the user of the SDK do not have to know or use `neverthrow`. +It is enough to properly handle the Promise instead. + +> [!NOTE] +> Even though this would be a major update since the contract has changed, +> I decided to bump a minor instead, following the [SemVer specification](https://semver.org) +> which allows to change the contract when the major is under `1`. \ No newline at end of file diff --git a/packages/gitignoreio-sdk/README.md b/packages/gitignoreio-sdk/README.md index 879dc82..ca84739 100644 --- a/packages/gitignoreio-sdk/README.md +++ b/packages/gitignoreio-sdk/README.md @@ -7,29 +7,46 @@ A comprehensive gitignore.io SDK and tools ecosystem. ```bash npm install gitignoreio-sdk # or -yarn add gitignoreio-sdk +yarn add gitignoreio-sdk # or pnpm add gitignoreio-sdk ``` ## Quick Start -### Using the SDK class (advanced usage) +### Using the SDK class ```javascript -import { GitIgnoreSDK } from 'gitignoreio-sdk'; +import { GitIgnoreSDK } from "gitignoreio-sdk"; // Create SDK instance with default configuration const sdk = new GitIgnoreSDK(); // Generate gitignore content -const result = await sdk.generate(['typescript', 'vscode']); -if (result.isErr()) { - // Handle the error -} else { - // Do whatever you want with the content - console.log(result.value.content); +try { + const result = await sdk.generate(["typescript", "vscode"]); + console.log(result.content); +} catch (error) { + console.error("Failed to generate gitignore:", error.message); } ``` +or, using `Promise` methods: + +```javascript +sdk.generate(["node", "python"]) + .then((result) => console.log(result.content)) + .catch(() => console.error("Failed to generate gitignore")); +``` + +#### Using a custom HTTP Client + +The GitIgnoreSDK constructor accepts a custom HttpClient; if not passed, it will use a default one. +You can use a custom HTTP client, but it must implement the `HttpClient` interface provided. + +### Known limitations + +**Hardcoded Technology Values**: The technology values are currently hardcoded in the SDK. If you notice any misalignment between the available technologies in this SDK and the official gitignore.io portal, please [open an issue](https://github.com/kin0992/gitignoreio/issues) to report the discrepancy. + For a complete list of available templates, see https://www.toptal.com/developers/gitignore/api/list or visit https://www.toptal.com/developers/gitignore/. + diff --git a/packages/gitignoreio-sdk/package.json b/packages/gitignoreio-sdk/package.json index fe3dea0..17ae00a 100644 --- a/packages/gitignoreio-sdk/package.json +++ b/packages/gitignoreio-sdk/package.json @@ -29,6 +29,9 @@ "publishConfig": { "access": "public" }, + "dependencies": { + "neverthrow": "^8.2.0" + }, "devDependencies": { "@types/node": "^22.0.0", "@vitest/coverage-v8": "^3.2.4", @@ -44,9 +47,9 @@ "url": "git+https://github.com/kin0992/gitignoreio.git", "directory": "packages/gitignoreio-sdk" }, + "bugs": { + "url": "https://github.com/kin0992/gitignoreio/issues" + }, "author": "Marco Comi", - "license": "ISC", - "dependencies": { - "neverthrow": "^8.2.0" - } + "license": "ISC" } diff --git a/packages/gitignoreio-sdk/src/__tests__/data.ts b/packages/gitignoreio-sdk/src/__tests__/data.ts index 63594fd..7fd89df 100644 --- a/packages/gitignoreio-sdk/src/__tests__/data.ts +++ b/packages/gitignoreio-sdk/src/__tests__/data.ts @@ -1,5 +1,5 @@ import { mock } from 'vitest-mock-extended'; -import type { HttpClient } from '../domain'; +import type { HttpClient } from '../domain/http-client'; export const makeMockHttpClient = () => mock(); diff --git a/packages/gitignoreio-sdk/src/__tests__/gitignore-sdk.test.ts b/packages/gitignoreio-sdk/src/__tests__/gitignore-sdk.test.ts deleted file mode 100644 index 77848fd..0000000 --- a/packages/gitignoreio-sdk/src/__tests__/gitignore-sdk.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ok, okAsync } from 'neverthrow'; -import { describe, expect, it } from 'vitest'; - -import { GitIgnoreSDK } from '../gitignore-sdk.js'; -import { makeMockHttpClient } from './data.js'; - -describe('GitIgnoreSDK', () => { - describe('generate', () => { - it('should return the .gitignore content', async () => { - const mockContent = '# Node.js\nnode_modules/\nnpm-debug.log'; - const mockHttpClient = makeMockHttpClient(); - mockHttpClient.get.mockReturnValueOnce(okAsync(mockContent)); - const sdk = new GitIgnoreSDK(mockHttpClient); - - const result = await sdk.generate(['node', 'python']); - - expect(mockHttpClient.get).toHaveBeenCalledWith( - new URL( - 'https://www.toptal.com/developers/gitignore/api/node%2Cpython', - ), - ); - expect(result).toStrictEqual(ok({ content: mockContent })); - }); - }); -}); diff --git a/packages/gitignoreio-sdk/src/adapters/__tests__/sdk.tests.ts b/packages/gitignoreio-sdk/src/adapters/__tests__/sdk.tests.ts new file mode 100644 index 0000000..5b07135 --- /dev/null +++ b/packages/gitignoreio-sdk/src/adapters/__tests__/sdk.tests.ts @@ -0,0 +1,41 @@ +import { errAsync, okAsync } from 'neverthrow'; +import { describe, expect, it } from 'vitest'; + +import { makeMockHttpClient } from '../../__tests__/data.js'; +import { GitIgnoreSDK } from '../sdk'; + +describe('GitIgnoreSDK', () => { + describe('generate', () => { + it('should return the .gitignore content', async () => { + const mockContent = '# Node.js\nnode_modules/\nnpm-debug.log'; + const mockHttpClient = makeMockHttpClient(); + mockHttpClient.get.mockReturnValueOnce(okAsync(mockContent)); + const sdk = new GitIgnoreSDK(mockHttpClient); + + const result = await sdk.generate(['node', 'python']); + + expect(mockHttpClient.get).toHaveBeenCalledWith( + new URL( + 'https://www.toptal.com/developers/gitignore/api/node%2Cpython', + ), + ); + expect(result).toStrictEqual({ content: mockContent }); + }); + + it('should throw an error when HTTP client fails', async () => { + const mockHttpClient = makeMockHttpClient(); + mockHttpClient.get.mockReturnValueOnce(errAsync(new Error('anError'))); + const sdk = new GitIgnoreSDK(mockHttpClient); + + await expect(sdk.generate(['node', 'python'])).rejects.toStrictEqual( + new Error('There was an error fetching data from gitignore.io'), + ); + + expect(mockHttpClient.get).toHaveBeenCalledWith( + new URL( + 'https://www.toptal.com/developers/gitignore/api/node%2Cpython', + ), + ); + }); + }); +}); diff --git a/packages/gitignoreio-sdk/src/adapters/fetch/client.ts b/packages/gitignoreio-sdk/src/adapters/fetch/client.ts index fb38910..e5dfe90 100644 --- a/packages/gitignoreio-sdk/src/adapters/fetch/client.ts +++ b/packages/gitignoreio-sdk/src/adapters/fetch/client.ts @@ -1,6 +1,6 @@ import { errAsync, ResultAsync } from 'neverthrow'; -import type { HttpClient } from '../../domain'; +import type { HttpClient } from '../../domain/http-client'; export class DefaultHttpClient implements HttpClient { get(url: URL) { diff --git a/packages/gitignoreio-sdk/src/gitignore-sdk.ts b/packages/gitignoreio-sdk/src/adapters/sdk.ts similarity index 60% rename from packages/gitignoreio-sdk/src/gitignore-sdk.ts rename to packages/gitignoreio-sdk/src/adapters/sdk.ts index b610e1a..d2b4b20 100644 --- a/packages/gitignoreio-sdk/src/gitignore-sdk.ts +++ b/packages/gitignoreio-sdk/src/adapters/sdk.ts @@ -1,6 +1,8 @@ -import type { GitIgnoreInput, GitIgnoreIoSDK, HttpClient } from './domain'; +import type { GitIgnoreIoSDK } from '../domain'; +import type { Technologies } from '../domain/technology'; -import { DefaultHttpClient } from './adapters/fetch/client'; +import { DefaultHttpClient } from '../adapters/fetch/client'; +import { type HttpClient } from '../domain/http-client'; /** * GitIgnore SDK for generating .gitignore files @@ -25,12 +27,20 @@ export class GitIgnoreSDK implements GitIgnoreIoSDK { * Generates .gitignore content for the given technologies by calling the {@link https://docs.gitignore.io/use/api} API. * * @param technologies Array of technology identifiers to include in the .gitignore. - * @returns A ResultAsync, either containing the generated .gitignore content or an error. + * @returns A Promise that resolves to the generated .gitignore content or rejects with an error. */ - generate(technologies: GitIgnoreInput) { + async generate(technologies: Technologies) { const url = new URL( `${this.baseUrl}/${encodeURIComponent(technologies.join(','))}`, ); - return this.httpClient.get(url).map((content) => ({ content })); + const result = await this.httpClient.get(url); + return result + .map((content) => ({ content })) + .match( + (value) => value, + () => { + throw new Error('There was an error fetching data from gitignore.io'); + }, + ); } } diff --git a/packages/gitignoreio-sdk/src/domain/http-client.ts b/packages/gitignoreio-sdk/src/domain/http-client.ts new file mode 100644 index 0000000..bce3e2d --- /dev/null +++ b/packages/gitignoreio-sdk/src/domain/http-client.ts @@ -0,0 +1,8 @@ +import type { ResultAsync } from 'neverthrow'; + +/** + * Interface for HTTP client dependency. + */ +export interface HttpClient { + get(url: URL): ResultAsync; +} diff --git a/packages/gitignoreio-sdk/src/domain/index.ts b/packages/gitignoreio-sdk/src/domain/index.ts index 652bbba..fe9937f 100644 --- a/packages/gitignoreio-sdk/src/domain/index.ts +++ b/packages/gitignoreio-sdk/src/domain/index.ts @@ -1,15 +1,7 @@ -import type { ResultAsync } from 'neverthrow'; - -import type { GitIgnoreElement } from './gitignore-element'; - -/** - * Input for generating gitignore content. - * A non-empty array of technology names. - */ -export type GitIgnoreInput = [GitIgnoreElement, ...GitIgnoreElement[]]; +import type { Technologies } from './technology'; export interface GitIgnoreIoSDK { - generate(technologies: GitIgnoreInput): ResultAsync; + generate(technologies: Technologies): Promise; } /** @@ -18,10 +10,3 @@ export interface GitIgnoreIoSDK { export interface GitIgnoreResult { content: string; } - -/** - * Interface for HTTP client dependency. - */ -export interface HttpClient { - get(url: URL): ResultAsync; -} diff --git a/packages/gitignoreio-sdk/src/domain/gitignore-element.ts b/packages/gitignoreio-sdk/src/domain/technology.ts similarity index 98% rename from packages/gitignoreio-sdk/src/domain/gitignore-element.ts rename to packages/gitignoreio-sdk/src/domain/technology.ts index bc4cf3e..a926cf0 100644 --- a/packages/gitignoreio-sdk/src/domain/gitignore-element.ts +++ b/packages/gitignoreio-sdk/src/domain/technology.ts @@ -1,4 +1,10 @@ -export type GitIgnoreElement = +/** + * Input for generating gitignore content. + * A non-empty array of technology names. + */ +export type Technologies = [Technology, ...Technology[]]; + +type Technology = | '1c-bitrix' | '1c' | 'a-frame' diff --git a/packages/gitignoreio-sdk/src/index.ts b/packages/gitignoreio-sdk/src/index.ts index 6b909c0..fe0be15 100644 --- a/packages/gitignoreio-sdk/src/index.ts +++ b/packages/gitignoreio-sdk/src/index.ts @@ -1 +1 @@ -export { GitIgnoreSDK } from './gitignore-sdk.js'; +export { GitIgnoreSDK } from './adapters/sdk';