Skip to content

Commit e736832

Browse files
authored
Merge pull request #24 from TextureHQ/chore/add-biome-ci-lint
chore: add Biome linting and wire into CI
2 parents 0a7a9de + dc2b19d commit e736832

31 files changed

Lines changed: 365 additions & 244 deletions

.github/workflows/ci.yml

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,38 +24,19 @@ jobs:
2424
uses: actions/setup-node@v4
2525
with:
2626
node-version: '20'
27+
cache: 'yarn'
2728

2829
- name: Install dependencies
29-
run: npm install
30+
run: yarn install --frozen-lockfile
3031

31-
- name: Run Code Climate Test Reporter before-build
32-
env:
33-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
34-
run: |
35-
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
36-
chmod +x ./cc-test-reporter
37-
./cc-test-reporter before-build
32+
- name: Run lint
33+
run: yarn lint
3834

3935
- name: Run tests with coverage
40-
run: npm run coverage
36+
run: yarn coverage
4137

4238
- name: Upload coverage to CodeCov
4339
uses: codecov/codecov-action@v3
4440
with:
4541
token: ${{ secrets.CODECOV_TOKEN }}
4642
files: coverage/lcov.info
47-
48-
- name: Format coverage report for Code Climate
49-
env:
50-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
51-
run: |
52-
./cc-test-reporter format-coverage coverage/lcov.info \
53-
--input-type lcov \
54-
--output coverage/codeclimate.json
55-
56-
- name: Upload coverage to Code Climate
57-
env:
58-
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
59-
run: |
60-
./cc-test-reporter upload-coverage \
61-
--input coverage/codeclimate.json

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010
- `yarn build:cjs` / `yarn build:esm` generate a single module target when you need faster iteration.
1111
- `yarn dev` watches the compiler (`tsc-watch`) and re-runs `node dist/index.js` after successful builds for local experimentation.
1212
- `yarn test` runs the Jest suite once; `yarn test:watch` keeps Jest hot during development; `yarn coverage` enforces coverage expectations and refreshes the `coverage/` report.
13+
- `yarn lint` runs Biome across the repo; fix any reported issues or explicitly suppress with justification.
1314

1415
## Coding Style & Naming Conventions
1516
- Write modern, strict TypeScript (see `tsconfig.json`: `strict: true`, ES6 modules). Prefer named exports, keeping default exports limited to top-level entry points (`WeatherPlus`).
1617
- Use two-space indentation, camelCase for functions/variables, PascalCase for classes/types, and SCREAMING_CASE for constants. Match existing file casing (e.g., `providerRegistry.ts`, `weatherService.ts`) to stay consistent.
17-
- Keep provider adapters cohesive: map external API responses to internal interfaces inside the provider module, and surface errors via the shared `errors` utilities.
18+
- Keep provider adapters cohesive: map external API responses to internal interfaces inside the provider module, and surface errors via the shared `errors` utilities. Biome (`biome.json`) enforces formatting, import order, and lint defaults—run it before committing changes.
1819

1920
## Testing Guidelines
2021
- Jest powers unit and integration tests; colocate new specs as `*.test.ts` alongside the module under test.

biome.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
3+
"files": {
4+
"ignore": ["dist", "coverage", "node_modules"]
5+
},
6+
"formatter": {
7+
"enabled": true,
8+
"indentStyle": "space",
9+
"indentWidth": 2,
10+
"lineWidth": 100
11+
},
12+
"organizeImports": {
13+
"enabled": true
14+
},
15+
"linter": {
16+
"enabled": true,
17+
"rules": {
18+
"recommended": true,
19+
"style": {
20+
"useImportType": "off"
21+
}
22+
}
23+
}
24+
}

package.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
"author": "Victor Quinn <mail@victorquinn.com>",
99
"license": "MIT",
1010
"private": false,
11+
"packageManager": "yarn@1.22.22",
1112
"scripts": {
1213
"build:cjs": "tsc --project tsconfig.cjs.json",
1314
"build:esm": "tsc --project tsconfig.esm.json",
14-
"build": "npm run build:cjs && npm run build:esm",
15+
"build": "yarn build:cjs && yarn build:esm",
1516
"dev": "tsc-watch --onSuccess 'node dist/index.js'",
1617
"test": "jest",
1718
"test:watch": "jest --watch",
1819
"prebuild": "rimraf ./dist",
19-
"prepack": "npm run build",
20-
"coverage": "jest --coverage"
20+
"prepack": "yarn build",
21+
"coverage": "jest --coverage",
22+
"lint": "biome lint ."
2123
},
2224
"files": [
2325
"dist"
@@ -48,6 +50,7 @@
4850
"ts-jest": "^29.1.4",
4951
"ts-node": "^10.9.2",
5052
"tsc-watch": "^6.2.0",
51-
"typescript": "^5.4.5"
53+
"typescript": "^5.4.5",
54+
"@biomejs/biome": "^1.7.3"
5255
}
5356
}

src/cache.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ describe('Cache', () => {
3333
await cache.set(key, JSON.stringify(value), ttl);
3434
const result = await cache.get(key);
3535

36-
expect(JSON.parse(result!)).toEqual(value);
36+
expect(result).not.toBeNull();
37+
expect(JSON.parse(result as string)).toEqual(value);
3738
expect(mockRedisClient.set).toHaveBeenCalledWith(key, JSON.stringify(value), { EX: ttl });
3839
expect(mockRedisClient.get).toHaveBeenCalledWith(key);
3940
});
@@ -106,7 +107,8 @@ describe('Cache', () => {
106107
await cache.set(key, JSON.stringify(value), ttl);
107108
const result = await cache.get(key);
108109

109-
expect(JSON.parse(result!)).toEqual(value);
110+
expect(result).not.toBeNull();
111+
expect(JSON.parse(result as string)).toEqual(value);
110112
});
111113

112114
it('should return null for nonexistent keys in memory cache', async () => {
@@ -219,8 +221,9 @@ describe('Cache', () => {
219221

220222
// Assert
221223
expect(cachedData).not.toBeNull();
222-
expect(JSON.parse(cachedData!)).toEqual(weatherData);
223-
expect(JSON.parse(cachedData!).provider).toBe('nws'); // Verify provider name
224+
const parsed = JSON.parse(cachedData as string);
225+
expect(parsed).toEqual(weatherData);
226+
expect(parsed.provider).toBe('nws'); // Verify provider name
224227
});
225228
});
226-
});
229+
});

src/cache.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,24 @@ export class Cache {
2626
if (this.cacheType === 'redis' && this.redisClient) {
2727
const val = await this.redisClient.get(key);
2828
return val === undefined ? null : val;
29-
} else {
30-
const item = this.memoryCache.get(key);
31-
if (item && item.expiresAt > Date.now()) {
32-
return item.value;
33-
}
34-
this.memoryCache.delete(key);
35-
return null;
3629
}
30+
31+
const item = this.memoryCache.get(key);
32+
if (item && item.expiresAt > Date.now()) {
33+
return item.value;
34+
}
35+
this.memoryCache.delete(key);
36+
return null;
3737
}
3838

3939
async set(key: string, value: string, ttl?: number): Promise<void> {
4040
const expiresIn = ttl ?? this.defaultTTL;
4141
if (this.cacheType === 'redis' && this.redisClient) {
4242
await this.redisClient.set(key, value, { EX: expiresIn });
43-
} else {
44-
const expiresAt = Date.now() + expiresIn * 1000;
45-
this.memoryCache.set(key, { value, expiresAt });
43+
return;
4644
}
45+
46+
const expiresAt = Date.now() + expiresIn * 1000;
47+
this.memoryCache.set(key, { value, expiresAt });
4748
}
4849
}

src/index.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ jest.mock('redis', () => {
2121

2222
describe('WeatherPlus Library', () => {
2323
let mock: MockAdapter;
24-
let redisMock: any;
24+
let redisMock: { mGet: jest.Mock; mSet: jest.Mock; createClient: jest.Mock };
2525

2626
beforeAll(() => {
2727
// Use the same axios instance that your providers use
2828
mock = new MockAdapter(axios);
29-
redisMock = require('redis');
29+
redisMock = require('redis') as { mGet: jest.Mock; mSet: jest.Mock; createClient: jest.Mock };
3030
});
3131

3232
afterAll(() => {
@@ -41,15 +41,15 @@ describe('WeatherPlus Library', () => {
4141
it('should throw an error if an unsupported provider is specified', () => {
4242
// Use type assertion to bypass TypeScript error for testing purposes
4343
const initUnsupportedProvider = () => {
44-
new WeatherPlus({ providers: ['tomorrow.io' as any] });
44+
new WeatherPlus({ providers: ['tomorrow.io'] });
4545
};
4646
expect(initUnsupportedProvider).toThrow('Provider tomorrow.io is not supported yet');
4747
});
4848

4949
it('should throw an error if an unsupported provider is included in providers array', () => {
5050
// Use type assertion to bypass TypeScript error for testing purposes
5151
const initUnsupportedProvider = () => {
52-
new WeatherPlus({ providers: ['nws', 'weatherkit' as any] });
52+
new WeatherPlus({ providers: ['nws', 'weatherkit'] });
5353
};
5454
expect(initUnsupportedProvider).toThrow('Provider weatherkit is not supported yet');
5555
});
@@ -326,4 +326,4 @@ describe('WeatherPlus Library', () => {
326326
expect(error).toBeInstanceOf(Error);
327327
expect(error.message).toBe('Test error');
328328
});
329-
});
329+
});

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { InvalidProviderLocationError, ProviderNotSupportedError, WeatherProvide
33
import { RedisClientType } from 'redis';
44
import { GetWeatherOptions } from './weatherService';
55
import { IWeatherData } from './interfaces';
6+
import { ProviderId } from './providers/capabilities';
67

78
// Define the options interface for WeatherPlus
89
interface WeatherPlusOptions {
9-
providers?: Array<'nws' | 'openweather'>; // Ordered list of providers for fallback
10+
providers?: ProviderId[]; // Ordered list of providers for fallback
1011
apiKeys?: { [provider: string]: string }; // Mapping of provider names to their API keys
1112
redisClient?: RedisClientType; // Optional Redis client for caching
1213
geohashPrecision?: number; // Optional geohash precision for caching
@@ -35,4 +36,4 @@ class WeatherPlus {
3536

3637
export { WeatherService, GetWeatherOptions, InvalidProviderLocationError, ProviderNotSupportedError, WeatherProviderError };
3738
export * from './interfaces';
38-
export default WeatherPlus;
39+
export default WeatherPlus;

src/interfaces.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ export enum IWeatherKey {
1919
sunset = 'sunset',
2020
}
2121

22-
export interface IWeatherProviderWeatherData extends Record<IWeatherKey, IBaseWeatherProperty<any, any>> {
23-
dewPoint: IDewPoint;
24-
humidity: IRelativeHumidity;
25-
temperature: ITemperature;
26-
conditions: IConditions;
27-
cloudiness: ICloudiness;
28-
sunrise: ISunriseSunset;
29-
sunset: ISunriseSunset;
30-
}
22+
export type WeatherProviderPropertyMap = {
23+
[IWeatherKey.dewPoint]: IDewPoint;
24+
[IWeatherKey.humidity]: IRelativeHumidity;
25+
[IWeatherKey.temperature]: ITemperature;
26+
[IWeatherKey.conditions]: IConditions;
27+
[IWeatherKey.cloudiness]: ICloudiness;
28+
[IWeatherKey.sunrise]: ISunriseSunset;
29+
[IWeatherKey.sunset]: ISunriseSunset;
30+
};
31+
32+
export type IWeatherProviderWeatherData = WeatherProviderPropertyMap;
3133

3234
export interface IWeatherData extends Partial<IWeatherProviderWeatherData> {
3335
provider: string;
@@ -47,4 +49,4 @@ export type IConditions = IBaseWeatherProperty<string, IWeatherUnits.string> & {
4749
}
4850
export type ITemperature = IBaseWeatherProperty<number, IWeatherUnits.C | IWeatherUnits.F>;
4951
export type ICloudiness = IBaseWeatherProperty<number, IWeatherUnits.percent>;
50-
export type ISunriseSunset = IBaseWeatherProperty<string, IWeatherUnits.iso8601>;
52+
export type ISunriseSunset = IBaseWeatherProperty<string, IWeatherUnits.iso8601>;

0 commit comments

Comments
 (0)