Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/public-adults-grab.md
Original file line number Diff line number Diff line change
@@ -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`.
35 changes: 26 additions & 9 deletions packages/gitignoreio-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/.

11 changes: 7 additions & 4 deletions packages/gitignoreio-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"publishConfig": {
"access": "public"
},
"dependencies": {
"neverthrow": "^8.2.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"@vitest/coverage-v8": "^3.2.4",
Expand All @@ -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"
}
2 changes: 1 addition & 1 deletion packages/gitignoreio-sdk/src/__tests__/data.ts
Original file line number Diff line number Diff line change
@@ -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<HttpClient>();
25 changes: 0 additions & 25 deletions packages/gitignoreio-sdk/src/__tests__/gitignore-sdk.test.ts

This file was deleted.

41 changes: 41 additions & 0 deletions packages/gitignoreio-sdk/src/adapters/__tests__/sdk.tests.ts
Original file line number Diff line number Diff line change
@@ -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',
),
);
});
});
});
2 changes: 1 addition & 1 deletion packages/gitignoreio-sdk/src/adapters/fetch/client.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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');
Comment on lines +41 to +42
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling discards the original error information. The match function receives an error parameter that should be used to provide more meaningful error messages or at least preserve the original error context.

Suggested change
() => {
throw new Error('There was an error fetching data from gitignore.io');
(error) => {
// Preserve original error information
if (error instanceof Error) {
throw new Error(`There was an error fetching data from gitignore.io: ${error.message}`, { cause: error });
} else {
throw new Error(`There was an error fetching data from gitignore.io: ${String(error)}`);
}

Copilot uses AI. Check for mistakes.
},
);
}
}
8 changes: 8 additions & 0 deletions packages/gitignoreio-sdk/src/domain/http-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ResultAsync } from 'neverthrow';

/**
* Interface for HTTP client dependency.
*/
export interface HttpClient {
get(url: URL): ResultAsync<string, Error>;
}
19 changes: 2 additions & 17 deletions packages/gitignoreio-sdk/src/domain/index.ts
Original file line number Diff line number Diff line change
@@ -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<GitIgnoreResult, Error>;
generate(technologies: Technologies): Promise<GitIgnoreResult>;
}

/**
Expand All @@ -18,10 +10,3 @@ export interface GitIgnoreIoSDK {
export interface GitIgnoreResult {
content: string;
}

/**
* Interface for HTTP client dependency.
*/
export interface HttpClient {
get(url: URL): ResultAsync<string, Error>;
}
Original file line number Diff line number Diff line change
@@ -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 =
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Technology type should be exported to allow consumers to use it for type checking and validation. Change type Technology = to export type Technology =

Suggested change
type Technology =
export type Technology =

Copilot uses AI. Check for mistakes.
| '1c-bitrix'
| '1c'
| 'a-frame'
Expand Down
2 changes: 1 addition & 1 deletion packages/gitignoreio-sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { GitIgnoreSDK } from './gitignore-sdk.js';
export { GitIgnoreSDK } from './adapters/sdk';