@echecs/fen 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ ## [1.0.1] - 2026-03-19
4
+
5
+ ### Fixed
6
+
7
+ - Updated lockfile after removing `@echecs/position` dependency.
8
+
9
+ ## [1.0.0] - 2026-03-19
10
+
11
+ ### Added
12
+
13
+ - FEN syntax validation for halfmove clock (>= 0) and fullmove number (>= 1).
14
+ - Position warnings via `onWarning`: missing king, pawns on rank 1 or 8, more
15
+ than 8 pawns per side, more than 16 pieces per side.
16
+ - Accurate `offset`, `line`, and `column` fields on `ParseError` and
17
+ `ParseWarning`.
18
+ - Exported all core types: `CastlingRights`, `Color`, `File`, `Piece`,
19
+ `PieceType`, `Position`, `Rank`, `Square`.
20
+
21
+ ### Changed
22
+
23
+ - Invalid halfmove clock and fullmove number are now hard errors (previously
24
+ warnings with fallback defaults).
25
+ - Rank mismatch in placement fires `onError` exactly once (previously fired both
26
+ `onWarning` and `onError`).
27
+ - Removed `@echecs/position` runtime dependency; all types are defined locally.
28
+ - Package is ESM-only; removed `"main"` field from `package.json`.
29
+
30
+ ## [0.1.0] - 2026-03-19
31
+
32
+ ### Added
33
+
34
+ - `parse` default export: FEN string to `Position` object (returns `null` on
35
+ invalid input).
36
+ - `stringify` named export: `Position` object to FEN string.
37
+ - `STARTING_FEN` constant for the standard starting position.
38
+ - FEN syntax validation for piece types, castling availability, and en passant
39
+ target square.
40
+ - `onError` and `onWarning` callbacks via `ParseOptions`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Adrian de la Rosa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @echecs/fen
2
+
3
+ Parse and stringify
4
+ [FEN](https://www.chessprogramming.org/Forsyth-Edwards_Notation)
5
+ (Forsyth-Edwards Notation) chess positions. Strict TypeScript, no-throw API.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @echecs/fen
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ### Parsing
16
+
17
+ ```typescript
18
+ import parse from '@echecs/fen';
19
+
20
+ const position = parse(
21
+ 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
22
+ );
23
+ // => { board, turn, castlingRights, enPassantSquare, halfmoveClock, fullmoveNumber }
24
+
25
+ parse('invalid');
26
+ // => null
27
+ ```
28
+
29
+ `parse` never throws. It returns `null` when the input is not a valid FEN
30
+ string.
31
+
32
+ #### Error and warning callbacks
33
+
34
+ Errors indicate invalid FEN syntax — the string cannot be parsed. Warnings
35
+ indicate a successfully parsed position that is suspicious (e.g. missing king).
36
+
37
+ ```typescript
38
+ const position = parse(fen, {
39
+ onError(error) {
40
+ // FEN is malformed — parse returns null.
41
+ console.error(`[${error.offset}] ${error.message}`);
42
+ },
43
+ onWarning(warning) {
44
+ // FEN is valid but the position is suspicious.
45
+ console.warn(warning.message);
46
+ },
47
+ });
48
+ ```
49
+
50
+ Errors are reported for:
51
+
52
+ - Wrong number of fields
53
+ - Invalid piece placement (bad piece type, wrong rank length)
54
+ - Invalid active color
55
+ - Invalid castling availability
56
+ - Invalid en passant target square
57
+ - Invalid halfmove clock (non-numeric or negative)
58
+ - Invalid fullmove number (non-numeric or less than 1)
59
+
60
+ Warnings are reported for:
61
+
62
+ - Missing king for either side
63
+ - Pawn on rank 1 or 8
64
+ - More than 8 pawns per side
65
+ - More than 16 pieces per side
66
+
67
+ ### Stringifying
68
+
69
+ ```typescript
70
+ import { stringify } from '@echecs/fen';
71
+
72
+ stringify(position);
73
+ // => 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
74
+ ```
75
+
76
+ `stringify` always succeeds.
77
+
78
+ ### Constants
79
+
80
+ ```typescript
81
+ import { STARTING_FEN } from '@echecs/fen';
82
+
83
+ STARTING_FEN;
84
+ // => 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
85
+ ```
86
+
87
+ ## API
88
+
89
+ ### `parse(input: string, options?: ParseOptions): Position | null`
90
+
91
+ Parses a FEN string into a `Position` object. Returns `null` if the input is not
92
+ a valid FEN string.
93
+
94
+ ### `stringify(position: Position): string`
95
+
96
+ Serializes a `Position` object into a FEN string.
97
+
98
+ ### `STARTING_FEN`
99
+
100
+ The FEN string for the standard starting position.
101
+
102
+ ### Types
103
+
104
+ ```typescript
105
+ interface Position {
106
+ board: Map<Square, Piece>;
107
+ castlingRights: CastlingRights;
108
+ enPassantSquare: Square | undefined;
109
+ fullmoveNumber: number;
110
+ halfmoveClock: number;
111
+ turn: Color;
112
+ }
113
+
114
+ interface ParseError {
115
+ column: number; // 1-indexed column in the FEN string
116
+ line: number; // Always 1 (FEN is single-line)
117
+ message: string;
118
+ offset: number; // 0-indexed offset into the FEN string
119
+ }
120
+
121
+ interface ParseWarning {
122
+ column: number;
123
+ line: number;
124
+ message: string;
125
+ offset: number;
126
+ }
127
+
128
+ type Color = 'w' | 'b';
129
+ type Square = `${File}${Rank}`;
130
+ type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
131
+ type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
132
+ type PieceType = 'p' | 'n' | 'b' | 'r' | 'q' | 'k';
133
+
134
+ interface Piece {
135
+ color: Color;
136
+ type: PieceType;
137
+ }
138
+
139
+ interface CastlingRights {
140
+ wK: boolean;
141
+ wQ: boolean;
142
+ bK: boolean;
143
+ bQ: boolean;
144
+ }
145
+ ```
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,47 @@
1
+ //#region src/types.d.ts
2
+ type Color = 'b' | 'w';
3
+ type File = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h';
4
+ type PieceType = 'b' | 'k' | 'n' | 'p' | 'q' | 'r';
5
+ type Rank = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
6
+ type Square = `${File}${Rank}`;
7
+ interface CastlingRights {
8
+ bK: boolean;
9
+ bQ: boolean;
10
+ wK: boolean;
11
+ wQ: boolean;
12
+ }
13
+ interface Piece {
14
+ color: Color;
15
+ type: PieceType;
16
+ }
17
+ interface Position {
18
+ board: Map<Square, Piece>;
19
+ castlingRights: CastlingRights;
20
+ enPassantSquare: Square | undefined;
21
+ fullmoveNumber: number;
22
+ halfmoveClock: number;
23
+ turn: Color;
24
+ }
25
+ //#endregion
26
+ //#region src/index.d.ts
27
+ interface ParseError {
28
+ column: number;
29
+ line: number;
30
+ message: string;
31
+ offset: number;
32
+ }
33
+ interface ParseWarning {
34
+ column: number;
35
+ line: number;
36
+ message: string;
37
+ offset: number;
38
+ }
39
+ interface ParseOptions {
40
+ onError?: (error: ParseError) => void;
41
+ onWarning?: (warning: ParseWarning) => void;
42
+ }
43
+ declare const STARTING_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
44
+ declare function parse(input: string, options?: ParseOptions): Position | null;
45
+ declare function stringify(position: Position): string;
46
+ //#endregion
47
+ export { type CastlingRights, type Color, type File, type ParseError, type ParseOptions, type ParseWarning, type Piece, type PieceType, type Position, type Rank, STARTING_FEN, type Square, parse as default, stringify };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ const e=`rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1`,t=[`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`],n=new Set([`p`,`n`,`b`,`r`,`q`,`k`]),r=[`8`,`7`,`6`,`5`,`4`,`3`,`2`,`1`];function i(e,t=0){return{column:t+1,line:1,message:e,offset:t}}function a(e,t=0){return{column:t+1,line:1,message:e,offset:t}}function o(e,a){let o=new Map,s=e.split(`/`);if(s.length!==8)return null;for(let[e,c]of s.entries()){let s=r[e];if(s===void 0||c===void 0)return null;let l=0;for(let e of c){let r=Number.parseInt(e,10);if(Number.isNaN(r)){let r=e.toLowerCase();if(!n.has(r))return a?.(i(`Invalid piece type: "${e}"`)),null;let c=e===e.toUpperCase()?`w`:`b`,u=r,d=t[l];if(d===void 0)return null;o.set(`${d}${s}`,{color:c,type:u}),l+=1}else l+=r}if(l!==8)return a?.(i(`Invalid FEN rank "${c}": expected 8 files, got ${l}`)),null}return o}const s=/^(?:-|K?Q?k?q?)$/,c=/^[a-h][36]$/;function l(e){return!s.test(e)||e.length===0?null:{bK:e.includes(`k`),bQ:e.includes(`q`),wK:e.includes(`K`),wQ:e.includes(`Q`)}}function u(e){let n=[];for(let i of r){let r=``,a=0;for(let n of t){let t=e.get(`${n}${i}`);t===void 0?a+=1:(a>0&&(r+=String(a),a=0),r+=t.color===`w`?t.type.toUpperCase():t.type)}a>0&&(r+=String(a)),n.push(r)}return n.join(`/`)}function d(e){let t=``;return e.wK&&(t+=`K`),e.wQ&&(t+=`Q`),e.bK&&(t+=`k`),e.bQ&&(t+=`q`),t.length>0?t:`-`}function f(e,t){let n=e.replace(/^\uFEFF/,``).trim();if(n.length===0)return t?.onError?.(i(`Input is empty`)),null;let r=n.split(` `);if(r.length!==6)return t?.onError?.(i(`Expected 6 FEN fields, got ${r.length}`)),null;let[s,u,d,f,p,m]=r,h=[0];for(let e=0;e<r.length-1;e++){let t=h[e]??0,n=r[e]??``;h.push(t+n.length+1)}let g=o(s,t?.onError);if(g===null)return null;if(u!==`w`&&u!==`b`)return t?.onError?.(i(`Invalid active color: "${u}"`,h[1])),null;let _=l(d);if(_===null)return t?.onError?.(i(`Invalid castling availability: "${d}"`,h[2])),null;if(f!==`-`&&!c.test(f))return t?.onError?.(i(`Invalid en passant square: "${f}"`,h[3])),null;let v=f===`-`?void 0:f,y=Number.parseInt(p,10);if(Number.isNaN(y)||y<0)return t?.onError?.(i(`Invalid halfmove clock: "${p}"`,h[4])),null;let b=Number.parseInt(m,10);if(Number.isNaN(b)||b<1)return t?.onError?.(i(`Invalid fullmove number: "${m}"`,h[5])),null;if(t?.onWarning){let e=0,n=0,r=0,i=0,o=0,s=0,c=!1;for(let[t,a]of g)a.color===`w`?(o+=1,a.type===`k`&&(e+=1),a.type===`p`&&(r+=1,(t.endsWith(`1`)||t.endsWith(`8`))&&(c=!0))):(s+=1,a.type===`k`&&(n+=1),a.type===`p`&&(i+=1,(t.endsWith(`1`)||t.endsWith(`8`))&&(c=!0)));e===0&&t.onWarning(a(`White king is missing`)),n===0&&t.onWarning(a(`Black king is missing`)),c&&t.onWarning(a(`Pawn on rank 1 or 8`)),r>8&&t.onWarning(a(`White has ${r} pawns (maximum is 8)`)),i>8&&t.onWarning(a(`Black has ${i} pawns (maximum is 8)`)),o>16&&t.onWarning(a(`White has ${o} pieces (maximum is 16)`)),s>16&&t.onWarning(a(`Black has ${s} pieces (maximum is 16)`))}return{board:g,castlingRights:_,enPassantSquare:v,fullmoveNumber:b,halfmoveClock:y,turn:u}}function p(e){let t=u(e.board),n=d(e.castlingRights),r=e.enPassantSquare??`-`;return[t,e.turn,n,r,String(e.halfmoveClock),String(e.fullmoveNumber)].join(` `)}export{e as STARTING_FEN,f as default,p as stringify};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type {\n CastlingRights,\n Color,\n File,\n Piece,\n PieceType,\n Position,\n Rank,\n Square,\n} from './types.js';\n\ninterface ParseError {\n column: number;\n line: number;\n message: string;\n offset: number;\n}\n\ninterface ParseWarning {\n column: number;\n line: number;\n message: string;\n offset: number;\n}\n\ninterface ParseOptions {\n onError?: (error: ParseError) => void;\n onWarning?: (warning: ParseWarning) => void;\n}\n\nconst STARTING_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1';\n\nconst FILES: File[] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];\nconst PIECE_TYPES = new Set<string>(['p', 'n', 'b', 'r', 'q', 'k']);\nconst RANKS: Rank[] = ['8', '7', '6', '5', '4', '3', '2', '1'];\n\nfunction makeError(message: string, offset = 0): ParseError {\n return { column: offset + 1, line: 1, message, offset };\n}\n\nfunction makeWarning(message: string, offset = 0): ParseWarning {\n return { column: offset + 1, line: 1, message, offset };\n}\n\nfunction parsePlacement(\n placement: string,\n onError?: (error: ParseError) => void,\n): Map<Square, Piece> | null {\n const board = new Map<Square, Piece>();\n const ranks = placement.split('/');\n\n if (ranks.length !== 8) {\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n for (const [rankIndex, rankString] of ranks.entries()) {\n const rank = RANKS[rankIndex];\n if (rank === undefined || rankString === undefined) {\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n let fileIndex = 0;\n for (const char of rankString) {\n const emptyCount = Number.parseInt(char, 10);\n if (Number.isNaN(emptyCount)) {\n const lower = char.toLowerCase();\n if (!PIECE_TYPES.has(lower)) {\n onError?.(makeError(`Invalid piece type: \"${char}\"`));\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n const color: Color = char === char.toUpperCase() ? 'w' : 'b';\n const type = lower as PieceType;\n const file = FILES[fileIndex];\n if (file === undefined) {\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n board.set(`${file}${rank}` as Square, { color, type });\n fileIndex += 1;\n } else {\n fileIndex += emptyCount;\n }\n }\n\n if (fileIndex !== 8) {\n onError?.(\n makeError(\n `Invalid FEN rank \"${rankString}\": expected 8 files, got ${fileIndex}`,\n ),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n }\n\n return board;\n}\n\nconst CASTLING_PATTERN = /^(?:-|K?Q?k?q?)$/;\nconst EN_PASSANT_PATTERN = /^[a-h][36]$/;\n\nfunction parseCastling(castling: string): CastlingRights | null {\n if (!CASTLING_PATTERN.test(castling) || castling.length === 0) {\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n return {\n bK: castling.includes('k'),\n bQ: castling.includes('q'),\n wK: castling.includes('K'),\n wQ: castling.includes('Q'),\n };\n}\n\nfunction stringifyPlacement(board: Map<Square, Piece>): string {\n const rankStrings: string[] = [];\n\n for (const rank of RANKS) {\n let rankString = '';\n let emptyCount = 0;\n\n for (const file of FILES) {\n const p = board.get(`${file}${rank}` as Square);\n if (p === undefined) {\n emptyCount += 1;\n } else {\n if (emptyCount > 0) {\n rankString += String(emptyCount);\n emptyCount = 0;\n }\n rankString += p.color === 'w' ? p.type.toUpperCase() : p.type;\n }\n }\n\n if (emptyCount > 0) {\n rankString += String(emptyCount);\n }\n rankStrings.push(rankString);\n }\n\n return rankStrings.join('/');\n}\n\nfunction stringifyCastling(rights: CastlingRights): string {\n let result = '';\n if (rights.wK) {\n result += 'K';\n }\n if (rights.wQ) {\n result += 'Q';\n }\n if (rights.bK) {\n result += 'k';\n }\n if (rights.bQ) {\n result += 'q';\n }\n return result.length > 0 ? result : '-';\n}\n\nfunction parse(input: string, options?: ParseOptions): Position | null {\n const content = input.replace(/^\\uFEFF/, '').trim();\n\n if (content.length === 0) {\n options?.onError?.(makeError('Input is empty'));\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n const parts = content.split(' ');\n if (parts.length !== 6) {\n options?.onError?.(makeError(`Expected 6 FEN fields, got ${parts.length}`));\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n const [\n placement,\n turnString,\n castlingString,\n epString,\n halfString,\n fullString,\n ] = parts as [string, string, string, string, string, string];\n\n // Compute the start offset of each field within the content string.\n // Fields are separated by single spaces.\n const fieldOffsets: number[] = [0];\n for (let index = 0; index < parts.length - 1; index++) {\n const previous = fieldOffsets[index] ?? 0;\n const field = parts[index] ?? '';\n fieldOffsets.push(previous + field.length + 1);\n }\n\n const board = parsePlacement(placement, options?.onError);\n if (board === null) {\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n if (turnString !== 'w' && turnString !== 'b') {\n options?.onError?.(\n makeError(`Invalid active color: \"${turnString}\"`, fieldOffsets[1]),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n const castlingRights = parseCastling(castlingString);\n if (castlingRights === null) {\n options?.onError?.(\n makeError(\n `Invalid castling availability: \"${castlingString}\"`,\n fieldOffsets[2],\n ),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n if (epString !== '-' && !EN_PASSANT_PATTERN.test(epString)) {\n options?.onError?.(\n makeError(`Invalid en passant square: \"${epString}\"`, fieldOffsets[3]),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n const enPassantSquare: Square | undefined =\n epString === '-' ? undefined : (epString as Square);\n\n const halfmoveClock = Number.parseInt(halfString, 10);\n if (Number.isNaN(halfmoveClock) || halfmoveClock < 0) {\n options?.onError?.(\n makeError(`Invalid halfmove clock: \"${halfString}\"`, fieldOffsets[4]),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n const fullmoveNumber = Number.parseInt(fullString, 10);\n if (Number.isNaN(fullmoveNumber) || fullmoveNumber < 1) {\n options?.onError?.(\n makeError(`Invalid fullmove number: \"${fullString}\"`, fieldOffsets[5]),\n );\n // eslint-disable-next-line unicorn/no-null\n return null;\n }\n\n // Position warnings — syntactically valid FEN but suspicious position.\n if (options?.onWarning) {\n let whiteKings = 0;\n let blackKings = 0;\n let whitePawns = 0;\n let blackPawns = 0;\n let whitePieces = 0;\n let blackPieces = 0;\n let pawnOnBackRank = false;\n\n for (const [square, piece] of board) {\n if (piece.color === 'w') {\n whitePieces += 1;\n if (piece.type === 'k') {\n whiteKings += 1;\n }\n if (piece.type === 'p') {\n whitePawns += 1;\n if (square.endsWith('1') || square.endsWith('8')) {\n pawnOnBackRank = true;\n }\n }\n } else {\n blackPieces += 1;\n if (piece.type === 'k') {\n blackKings += 1;\n }\n if (piece.type === 'p') {\n blackPawns += 1;\n if (square.endsWith('1') || square.endsWith('8')) {\n pawnOnBackRank = true;\n }\n }\n }\n }\n\n if (whiteKings === 0) {\n options.onWarning(makeWarning('White king is missing'));\n }\n if (blackKings === 0) {\n options.onWarning(makeWarning('Black king is missing'));\n }\n if (pawnOnBackRank) {\n options.onWarning(makeWarning('Pawn on rank 1 or 8'));\n }\n if (whitePawns > 8) {\n options.onWarning(\n makeWarning(`White has ${whitePawns} pawns (maximum is 8)`),\n );\n }\n if (blackPawns > 8) {\n options.onWarning(\n makeWarning(`Black has ${blackPawns} pawns (maximum is 8)`),\n );\n }\n if (whitePieces > 16) {\n options.onWarning(\n makeWarning(`White has ${whitePieces} pieces (maximum is 16)`),\n );\n }\n if (blackPieces > 16) {\n options.onWarning(\n makeWarning(`Black has ${blackPieces} pieces (maximum is 16)`),\n );\n }\n }\n\n return {\n board,\n castlingRights,\n enPassantSquare,\n fullmoveNumber,\n halfmoveClock,\n turn: turnString,\n };\n}\n\nfunction stringify(position: Position): string {\n const placement = stringifyPlacement(position.board);\n const castling = stringifyCastling(position.castlingRights);\n const enPassant = position.enPassantSquare ?? '-';\n\n return [\n placement,\n position.turn,\n castling,\n enPassant,\n String(position.halfmoveClock),\n String(position.fullmoveNumber),\n ].join(' ');\n}\n\nexport type { ParseError, ParseOptions, ParseWarning };\nexport type {\n CastlingRights,\n Color,\n File,\n Piece,\n PieceType,\n Position,\n Rank,\n Square,\n} from './types.js';\nexport { STARTING_FEN, stringify };\nexport default parse;\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "author": "Adrian de la Rosa <adrian@delarosab.me>",
3
+ "bugs": {
4
+ "url": "https://github.com/mormubis/fen/issues"
5
+ },
6
+ "description": "Parse and stringify FEN (Forsyth–Edwards Notation) chess positions. Strict TypeScript, no-throw API.",
7
+ "devDependencies": {
8
+ "@eslint/js": "^10.0.1",
9
+ "@typescript-eslint/parser": "^8.57.0",
10
+ "@vitest/coverage-v8": "^4.1.0",
11
+ "@vitest/eslint-plugin": "^1.6.12",
12
+ "eslint": "^10.0.3",
13
+ "eslint-config-prettier": "^10.1.8",
14
+ "eslint-import-resolver-typescript": "^4.4.4",
15
+ "eslint-plugin-import-x": "^4.16.2",
16
+ "eslint-plugin-unicorn": "^63.0.0",
17
+ "husky": "^9.1.7",
18
+ "lint-staged": "^16.4.0",
19
+ "prettier": "^3.8.1",
20
+ "tsdown": "^0.21.4",
21
+ "typedoc": "^0.28.17",
22
+ "typescript": "^5.9.3",
23
+ "typescript-eslint": "^8.57.0",
24
+ "vitest": "^4.1.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "import": "./dist/index.js",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "files": [
36
+ "/dist/",
37
+ "LICENSE",
38
+ "README.md",
39
+ "CHANGELOG.md"
40
+ ],
41
+ "homepage": "https://github.com/mormubis/fen#readme",
42
+ "keywords": [
43
+ "chess",
44
+ "fen",
45
+ "forsyth-edwards",
46
+ "parser",
47
+ "position",
48
+ "typescript"
49
+ ],
50
+ "license": "MIT",
51
+ "name": "@echecs/fen",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "https://github.com/mormubis/fen.git"
55
+ },
56
+ "sideEffects": false,
57
+ "type": "module",
58
+ "version": "1.0.1",
59
+ "scripts": {
60
+ "build": "tsdown",
61
+ "docs": "typedoc",
62
+ "format": "pnpm run format:ci --write",
63
+ "format:ci": "prettier -l \"**/*.+(css|js|json|jsx|md|mjs|mts|ts|tsx|yml|yaml)\"",
64
+ "lint": "pnpm run lint:style && pnpm run lint:types",
65
+ "lint:ci": "pnpm run lint:style --max-warnings 0 && pnpm run lint:types",
66
+ "lint:style": "eslint \"src/**/*.{ts,tsx}\" \"*.mjs\" --fix",
67
+ "lint:types": "tsc --noEmit --project tsconfig.json",
68
+ "test": "vitest run",
69
+ "test:coverage": "pnpm run test --coverage",
70
+ "test:watch": "pnpm run test --watch"
71
+ }
72
+ }