@aakashwije/lanka-nic 1.0.0

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/dist/cli.cjs ADDED
@@ -0,0 +1,282 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // src/errors.ts
5
+ var NICError = class extends Error {
6
+ code;
7
+ input;
8
+ constructor(code, message, input) {
9
+ super(message);
10
+ this.name = "NICError";
11
+ this.code = code;
12
+ this.input = input;
13
+ }
14
+ };
15
+
16
+ // src/utils/constants.ts
17
+ var OLD_NIC_PATTERN = /^\d{9}[VvXx]$/;
18
+ var NEW_NIC_PATTERN = /^\d{12}$/;
19
+ var DAY_CODE_MIN = 1;
20
+ var DAY_CODE_MAX_MALE = 366;
21
+ var DAY_CODE_FEMALE_OFFSET = 500;
22
+ var DAY_CODE_MAX_FEMALE = 866;
23
+
24
+ // src/normalize.ts
25
+ var normalizeNIC = (nic) => {
26
+ if (typeof nic !== "string") {
27
+ return "";
28
+ }
29
+ const compact = nic.trim().replace(/\s+/g, "");
30
+ if (OLD_NIC_PATTERN.test(compact)) {
31
+ return compact.slice(0, 9) + compact.slice(9).toUpperCase();
32
+ }
33
+ return compact;
34
+ };
35
+
36
+ // src/utils/date.ts
37
+ var isLeapYear = (year) => {
38
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
39
+ };
40
+ var dayOfYearToUTCDate = (year, dayOfYear) => {
41
+ const maxDays = isLeapYear(year) ? 366 : 365;
42
+ if (dayOfYear < 1 || dayOfYear > maxDays) {
43
+ return null;
44
+ }
45
+ return new Date(Date.UTC(year, 0, dayOfYear));
46
+ };
47
+ var formatUTCDate = (date) => {
48
+ return date.toISOString().slice(0, 10);
49
+ };
50
+ var parseUTCBirthDate = (iso) => {
51
+ const year = Number(iso.slice(0, 4));
52
+ const month = Number(iso.slice(5, 7));
53
+ const day = Number(iso.slice(8, 10));
54
+ return new Date(Date.UTC(year, month - 1, day));
55
+ };
56
+
57
+ // src/parse.ts
58
+ var getUTCFullYearNow = () => (/* @__PURE__ */ new Date()).getUTCFullYear();
59
+ var fail = (code, message, input) => ({
60
+ success: false,
61
+ error: new NICError(code, message, input)
62
+ });
63
+ var resolveGenderAndDay = (dayCode) => {
64
+ if (dayCode >= DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_MALE) {
65
+ return { gender: "male", dayOfYear: dayCode };
66
+ }
67
+ if (dayCode >= DAY_CODE_FEMALE_OFFSET + DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_FEMALE) {
68
+ return { gender: "female", dayOfYear: dayCode - DAY_CODE_FEMALE_OFFSET };
69
+ }
70
+ return null;
71
+ };
72
+ var getNICType = (nic) => {
73
+ if (typeof nic !== "string") {
74
+ return "invalid";
75
+ }
76
+ const normalized = normalizeNIC(nic);
77
+ if (OLD_NIC_PATTERN.test(normalized)) {
78
+ return "old";
79
+ }
80
+ if (NEW_NIC_PATTERN.test(normalized)) {
81
+ return "new";
82
+ }
83
+ return "invalid";
84
+ };
85
+ var safeParse = (nic) => {
86
+ if (typeof nic !== "string") {
87
+ return fail("NON_STRING", "NIC must be a string", nic);
88
+ }
89
+ const normalized = normalizeNIC(nic);
90
+ if (!normalized) {
91
+ return fail("EMPTY", "NIC is empty", nic);
92
+ }
93
+ const type = getNICType(normalized);
94
+ if (type === "invalid") {
95
+ return fail(
96
+ "INVALID_FORMAT",
97
+ "NIC does not match old or new format",
98
+ nic
99
+ );
100
+ }
101
+ const birthYear = type === "old" ? 1900 + Number.parseInt(normalized.slice(0, 2), 10) : Number.parseInt(normalized.slice(0, 4), 10);
102
+ if (birthYear > getUTCFullYearNow()) {
103
+ return fail("FUTURE_YEAR", `birth year ${birthYear} is in the future`, nic);
104
+ }
105
+ const dayCode = type === "old" ? Number.parseInt(normalized.slice(2, 5), 10) : Number.parseInt(normalized.slice(4, 7), 10);
106
+ const genderAndDay = resolveGenderAndDay(dayCode);
107
+ if (!genderAndDay) {
108
+ return fail(
109
+ "INVALID_DAY_CODE",
110
+ `day code ${dayCode} is out of range`,
111
+ nic
112
+ );
113
+ }
114
+ const date = dayOfYearToUTCDate(birthYear, genderAndDay.dayOfYear);
115
+ if (!date) {
116
+ return fail(
117
+ "INVALID_DAY_FOR_YEAR",
118
+ `day ${genderAndDay.dayOfYear} does not exist in year ${birthYear}`,
119
+ nic
120
+ );
121
+ }
122
+ return {
123
+ success: true,
124
+ data: {
125
+ input: nic,
126
+ normalized,
127
+ type,
128
+ valid: true,
129
+ birthYear,
130
+ dayOfYear: genderAndDay.dayOfYear,
131
+ birthDate: formatUTCDate(date),
132
+ gender: genderAndDay.gender
133
+ }
134
+ };
135
+ };
136
+ var parseNIC = (nic) => {
137
+ const result = safeParse(nic);
138
+ return result.success ? result.data : null;
139
+ };
140
+
141
+ // src/age.ts
142
+ var toUTCDay = (date) => {
143
+ return new Date(
144
+ Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
145
+ );
146
+ };
147
+ var todayUTC = () => toUTCDay(/* @__PURE__ */ new Date());
148
+ var getAgeOn = (nic, date) => {
149
+ const parsed = parseNIC(nic);
150
+ if (!parsed) {
151
+ return null;
152
+ }
153
+ const birth = parseUTCBirthDate(parsed.birthDate);
154
+ const ref = toUTCDay(date);
155
+ if (ref.getTime() < birth.getTime()) {
156
+ return null;
157
+ }
158
+ let age = ref.getUTCFullYear() - birth.getUTCFullYear();
159
+ const monthDiff = ref.getUTCMonth() - birth.getUTCMonth();
160
+ if (monthDiff < 0 || monthDiff === 0 && ref.getUTCDate() < birth.getUTCDate()) {
161
+ age -= 1;
162
+ }
163
+ return age;
164
+ };
165
+ var getAge = (nic, opts) => {
166
+ const ref = todayUTC();
167
+ return getAgeOn(nic, ref);
168
+ };
169
+
170
+ // src/mask.ts
171
+ var maskNIC = (nic) => {
172
+ if (typeof nic !== "string") {
173
+ return "";
174
+ }
175
+ const normalized = normalizeNIC(nic);
176
+ const type = getNICType(normalized);
177
+ if (type === "old") {
178
+ return `${normalized.slice(0, 5)}****${normalized.slice(9)}`;
179
+ }
180
+ if (type === "new") {
181
+ return `${normalized.slice(0, 7)}*****`;
182
+ }
183
+ return normalized;
184
+ };
185
+
186
+ // src/validate.ts
187
+ var validateNIC = (nic) => {
188
+ return parseNIC(nic) !== null;
189
+ };
190
+
191
+ // src/cli-core.ts
192
+ var USAGE = `lanka-nic \u2014 Sri Lankan NIC utility
193
+
194
+ Usage:
195
+ lanka-nic validate <nic>
196
+ lanka-nic parse <nic>
197
+ lanka-nic mask <nic>
198
+ lanka-nic age <nic>
199
+
200
+ If <nic> is omitted, the value is read from stdin.
201
+ `;
202
+ var ok = (stdout) => ({ code: 0, stdout, stderr: "" });
203
+ var err = (code, stderr) => ({
204
+ code,
205
+ stdout: "",
206
+ stderr
207
+ });
208
+ var runCommand = (argv, stdinValue) => {
209
+ const cmd = argv[0];
210
+ const arg = argv[1];
211
+ if (!cmd || cmd === "--help" || cmd === "-h") {
212
+ return ok(USAGE);
213
+ }
214
+ const trimmedStdin = stdinValue?.trim() ?? "";
215
+ const nic = arg ?? (trimmedStdin || null);
216
+ if (nic === null) {
217
+ return err(2, USAGE);
218
+ }
219
+ switch (cmd) {
220
+ case "validate": {
221
+ const valid = validateNIC(nic);
222
+ return { code: valid ? 0 : 1, stdout: `${valid}
223
+ `, stderr: "" };
224
+ }
225
+ case "parse": {
226
+ const result = safeParse(nic);
227
+ if (result.success) {
228
+ return ok(`${JSON.stringify(result.data, null, 2)}
229
+ `);
230
+ }
231
+ const payload = JSON.stringify({
232
+ error: result.error.code,
233
+ message: result.error.message
234
+ });
235
+ return err(1, `${payload}
236
+ `);
237
+ }
238
+ case "mask": {
239
+ const masked = maskNIC(nic);
240
+ const valid = validateNIC(nic);
241
+ return { code: valid ? 0 : 1, stdout: `${masked}
242
+ `, stderr: "" };
243
+ }
244
+ case "age": {
245
+ const age = getAge(nic);
246
+ if (age === null) {
247
+ return err(1, "invalid nic\n");
248
+ }
249
+ return ok(`${age}
250
+ `);
251
+ }
252
+ default:
253
+ return err(2, `unknown command: ${cmd}
254
+ ${USAGE}`);
255
+ }
256
+ };
257
+
258
+ // src/cli.ts
259
+ var readStdin = async () => {
260
+ if (process.stdin.isTTY) {
261
+ return null;
262
+ }
263
+ let data = "";
264
+ for await (const chunk of process.stdin) {
265
+ data += String(chunk);
266
+ }
267
+ return data || null;
268
+ };
269
+ var main = async () => {
270
+ const stdin = await readStdin();
271
+ const { code, stdout, stderr } = runCommand(process.argv.slice(2), stdin);
272
+ if (stdout) {
273
+ process.stdout.write(stdout);
274
+ }
275
+ if (stderr) {
276
+ process.stderr.write(stderr);
277
+ }
278
+ process.exit(code);
279
+ };
280
+ void main();
281
+ //# sourceMappingURL=cli.cjs.map
282
+ //# sourceMappingURL=cli.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/utils/constants.ts","../src/normalize.ts","../src/utils/date.ts","../src/parse.ts","../src/age.ts","../src/mask.ts","../src/validate.ts","../src/cli-core.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.cjs","sourcesContent":["import type { NICErrorCode } from './types';\n\nexport class NICError extends Error {\n readonly code: NICErrorCode;\n readonly input: unknown;\n\n constructor(code: NICErrorCode, message: string, input: unknown) {\n super(message);\n this.name = 'NICError';\n this.code = code;\n this.input = input;\n }\n}\n","export const OLD_NIC_PATTERN = /^\\d{9}[VvXx]$/;\nexport const NEW_NIC_PATTERN = /^\\d{12}$/;\n\nexport const DAY_CODE_MIN = 1;\nexport const DAY_CODE_MAX_MALE = 366;\nexport const DAY_CODE_FEMALE_OFFSET = 500;\nexport const DAY_CODE_MAX_FEMALE = 866;\n","import { OLD_NIC_PATTERN } from './utils/constants';\n\nexport const normalizeNIC = (nic: string): string => {\n if (typeof nic !== 'string') {\n return '';\n }\n\n const compact = nic.trim().replace(/\\s+/g, '');\n if (OLD_NIC_PATTERN.test(compact)) {\n return compact.slice(0, 9) + compact.slice(9).toUpperCase();\n }\n\n return compact;\n};\n","export const isLeapYear = (year: number): boolean => {\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n};\n\nexport const dayOfYearToUTCDate = (year: number, dayOfYear: number): Date | null => {\n const maxDays = isLeapYear(year) ? 366 : 365;\n if (dayOfYear < 1 || dayOfYear > maxDays) {\n return null;\n }\n\n return new Date(Date.UTC(year, 0, dayOfYear));\n};\n\nexport const formatUTCDate = (date: Date): string => {\n return date.toISOString().slice(0, 10);\n};\n\nexport const parseUTCBirthDate = (iso: string): Date => {\n const year = Number(iso.slice(0, 4));\n const month = Number(iso.slice(5, 7));\n const day = Number(iso.slice(8, 10));\n return new Date(Date.UTC(year, month - 1, day));\n};\n","import { NICError } from './errors';\nimport { normalizeNIC } from './normalize';\nimport type {\n NICErrorCode,\n NICGender,\n NICType,\n ParsedNIC,\n SafeParseResult\n} from './types';\nimport {\n DAY_CODE_FEMALE_OFFSET,\n DAY_CODE_MAX_FEMALE,\n DAY_CODE_MAX_MALE,\n DAY_CODE_MIN,\n NEW_NIC_PATTERN,\n OLD_NIC_PATTERN\n} from './utils/constants';\nimport { dayOfYearToUTCDate, formatUTCDate } from './utils/date';\n\nconst getUTCFullYearNow = (): number => new Date().getUTCFullYear();\n\nconst fail = (\n code: NICErrorCode,\n message: string,\n input: unknown\n): SafeParseResult<ParsedNIC> => ({\n success: false,\n error: new NICError(code, message, input)\n});\n\nconst resolveGenderAndDay = (\n dayCode: number\n): { gender: NICGender; dayOfYear: number } | null => {\n if (dayCode >= DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_MALE) {\n return { gender: 'male', dayOfYear: dayCode };\n }\n\n if (\n dayCode >= DAY_CODE_FEMALE_OFFSET + DAY_CODE_MIN &&\n dayCode <= DAY_CODE_MAX_FEMALE\n ) {\n return { gender: 'female', dayOfYear: dayCode - DAY_CODE_FEMALE_OFFSET };\n }\n\n return null;\n};\n\nexport const getNICType = (nic: string): NICType => {\n if (typeof nic !== 'string') {\n return 'invalid';\n }\n\n const normalized = normalizeNIC(nic);\n\n if (OLD_NIC_PATTERN.test(normalized)) {\n return 'old';\n }\n\n if (NEW_NIC_PATTERN.test(normalized)) {\n return 'new';\n }\n\n return 'invalid';\n};\n\nexport const safeParse = (nic: unknown): SafeParseResult<ParsedNIC> => {\n if (typeof nic !== 'string') {\n return fail('NON_STRING', 'NIC must be a string', nic);\n }\n\n const normalized = normalizeNIC(nic);\n if (!normalized) {\n return fail('EMPTY', 'NIC is empty', nic);\n }\n\n const type = getNICType(normalized);\n if (type === 'invalid') {\n return fail(\n 'INVALID_FORMAT',\n 'NIC does not match old or new format',\n nic\n );\n }\n\n const birthYear =\n type === 'old'\n ? 1900 + Number.parseInt(normalized.slice(0, 2), 10)\n : Number.parseInt(normalized.slice(0, 4), 10);\n\n if (birthYear > getUTCFullYearNow()) {\n return fail('FUTURE_YEAR', `birth year ${birthYear} is in the future`, nic);\n }\n\n const dayCode =\n type === 'old'\n ? Number.parseInt(normalized.slice(2, 5), 10)\n : Number.parseInt(normalized.slice(4, 7), 10);\n\n const genderAndDay = resolveGenderAndDay(dayCode);\n if (!genderAndDay) {\n return fail(\n 'INVALID_DAY_CODE',\n `day code ${dayCode} is out of range`,\n nic\n );\n }\n\n const date = dayOfYearToUTCDate(birthYear, genderAndDay.dayOfYear);\n if (!date) {\n return fail(\n 'INVALID_DAY_FOR_YEAR',\n `day ${genderAndDay.dayOfYear} does not exist in year ${birthYear}`,\n nic\n );\n }\n\n return {\n success: true,\n data: {\n input: nic,\n normalized,\n type,\n valid: true,\n birthYear,\n dayOfYear: genderAndDay.dayOfYear,\n birthDate: formatUTCDate(date),\n gender: genderAndDay.gender\n }\n };\n};\n\nexport const parseNIC = (nic: string): ParsedNIC | null => {\n const result = safeParse(nic);\n return result.success ? result.data : null;\n};\n\nexport const parseNICOrThrow = (nic: string): ParsedNIC => {\n const result = safeParse(nic);\n if (result.success) {\n return result.data;\n }\n throw result.error;\n};\n\nexport const getBirthDate = (nic: string): string | null => {\n return parseNIC(nic)?.birthDate ?? null;\n};\n\nexport const getGender = (nic: string): NICGender | null => {\n return parseNIC(nic)?.gender ?? null;\n};\n","import { parseNIC } from './parse';\nimport { parseUTCBirthDate } from './utils/date';\n\nconst toUTCDay = (date: Date): Date => {\n return new Date(\n Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())\n );\n};\n\nconst todayUTC = (): Date => toUTCDay(new Date());\n\nexport const getAgeOn = (nic: string, date: Date): number | null => {\n const parsed = parseNIC(nic);\n if (!parsed) {\n return null;\n }\n\n const birth = parseUTCBirthDate(parsed.birthDate);\n const ref = toUTCDay(date);\n\n if (ref.getTime() < birth.getTime()) {\n return null;\n }\n\n let age = ref.getUTCFullYear() - birth.getUTCFullYear();\n const monthDiff = ref.getUTCMonth() - birth.getUTCMonth();\n if (\n monthDiff < 0 ||\n (monthDiff === 0 && ref.getUTCDate() < birth.getUTCDate())\n ) {\n age -= 1;\n }\n\n return age;\n};\n\nexport const getAge = (\n nic: string,\n opts?: { now?: Date }\n): number | null => {\n const ref = opts?.now ?? todayUTC();\n return getAgeOn(nic, ref);\n};\n","import { normalizeNIC } from './normalize';\nimport { getNICType } from './parse';\n\nexport const maskNIC = (nic: string): string => {\n if (typeof nic !== 'string') {\n return '';\n }\n\n const normalized = normalizeNIC(nic);\n const type = getNICType(normalized);\n\n if (type === 'old') {\n return `${normalized.slice(0, 5)}****${normalized.slice(9)}`;\n }\n\n if (type === 'new') {\n return `${normalized.slice(0, 7)}*****`;\n }\n\n return normalized;\n};\n","import { parseNIC } from './parse';\n\nexport const validateNIC = (nic: string): boolean => {\n return parseNIC(nic) !== null;\n};\n","import { getAge } from './age';\nimport { maskNIC } from './mask';\nimport { safeParse } from './parse';\nimport { validateNIC } from './validate';\n\nexport type CliResult = {\n code: number;\n stdout: string;\n stderr: string;\n};\n\nconst USAGE = `lanka-nic — Sri Lankan NIC utility\n\nUsage:\n lanka-nic validate <nic>\n lanka-nic parse <nic>\n lanka-nic mask <nic>\n lanka-nic age <nic>\n\nIf <nic> is omitted, the value is read from stdin.\n`;\n\nconst ok = (stdout: string): CliResult => ({ code: 0, stdout, stderr: '' });\nconst err = (code: number, stderr: string): CliResult => ({\n code,\n stdout: '',\n stderr\n});\n\nexport const runCommand = (\n argv: string[],\n stdinValue: string | null\n): CliResult => {\n const cmd = argv[0];\n const arg = argv[1];\n\n if (!cmd || cmd === '--help' || cmd === '-h') {\n return ok(USAGE);\n }\n\n const trimmedStdin = stdinValue?.trim() ?? '';\n const nic = arg ?? (trimmedStdin || null);\n if (nic === null) {\n return err(2, USAGE);\n }\n\n switch (cmd) {\n case 'validate': {\n const valid = validateNIC(nic);\n return { code: valid ? 0 : 1, stdout: `${valid}\\n`, stderr: '' };\n }\n case 'parse': {\n const result = safeParse(nic);\n if (result.success) {\n return ok(`${JSON.stringify(result.data, null, 2)}\\n`);\n }\n const payload = JSON.stringify({\n error: result.error.code,\n message: result.error.message\n });\n return err(1, `${payload}\\n`);\n }\n case 'mask': {\n const masked = maskNIC(nic);\n const valid = validateNIC(nic);\n return { code: valid ? 0 : 1, stdout: `${masked}\\n`, stderr: '' };\n }\n case 'age': {\n const age = getAge(nic);\n if (age === null) {\n return err(1, 'invalid nic\\n');\n }\n return ok(`${age}\\n`);\n }\n default:\n return err(2, `unknown command: ${cmd}\\n${USAGE}`);\n }\n};\n","#!/usr/bin/env node\nimport { runCommand } from './cli-core';\n\nconst readStdin = async (): Promise<string | null> => {\n if (process.stdin.isTTY) {\n return null;\n }\n\n let data = '';\n for await (const chunk of process.stdin) {\n data += String(chunk);\n }\n return data || null;\n};\n\nconst main = async (): Promise<void> => {\n const stdin = await readStdin();\n const { code, stdout, stderr } = runCommand(process.argv.slice(2), stdin);\n if (stdout) {\n process.stdout.write(stdout);\n }\n if (stderr) {\n process.stderr.write(stderr);\n }\n process.exit(code);\n};\n\nvoid main();\n"]}
package/dist/cli.js ADDED
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env node
2
+ // src/errors.ts
3
+ var NICError = class extends Error {
4
+ code;
5
+ input;
6
+ constructor(code, message, input) {
7
+ super(message);
8
+ this.name = "NICError";
9
+ this.code = code;
10
+ this.input = input;
11
+ }
12
+ };
13
+
14
+ // src/utils/constants.ts
15
+ var OLD_NIC_PATTERN = /^\d{9}[VvXx]$/;
16
+ var NEW_NIC_PATTERN = /^\d{12}$/;
17
+ var DAY_CODE_MIN = 1;
18
+ var DAY_CODE_MAX_MALE = 366;
19
+ var DAY_CODE_FEMALE_OFFSET = 500;
20
+ var DAY_CODE_MAX_FEMALE = 866;
21
+
22
+ // src/normalize.ts
23
+ var normalizeNIC = (nic) => {
24
+ if (typeof nic !== "string") {
25
+ return "";
26
+ }
27
+ const compact = nic.trim().replace(/\s+/g, "");
28
+ if (OLD_NIC_PATTERN.test(compact)) {
29
+ return compact.slice(0, 9) + compact.slice(9).toUpperCase();
30
+ }
31
+ return compact;
32
+ };
33
+
34
+ // src/utils/date.ts
35
+ var isLeapYear = (year) => {
36
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
37
+ };
38
+ var dayOfYearToUTCDate = (year, dayOfYear) => {
39
+ const maxDays = isLeapYear(year) ? 366 : 365;
40
+ if (dayOfYear < 1 || dayOfYear > maxDays) {
41
+ return null;
42
+ }
43
+ return new Date(Date.UTC(year, 0, dayOfYear));
44
+ };
45
+ var formatUTCDate = (date) => {
46
+ return date.toISOString().slice(0, 10);
47
+ };
48
+ var parseUTCBirthDate = (iso) => {
49
+ const year = Number(iso.slice(0, 4));
50
+ const month = Number(iso.slice(5, 7));
51
+ const day = Number(iso.slice(8, 10));
52
+ return new Date(Date.UTC(year, month - 1, day));
53
+ };
54
+
55
+ // src/parse.ts
56
+ var getUTCFullYearNow = () => (/* @__PURE__ */ new Date()).getUTCFullYear();
57
+ var fail = (code, message, input) => ({
58
+ success: false,
59
+ error: new NICError(code, message, input)
60
+ });
61
+ var resolveGenderAndDay = (dayCode) => {
62
+ if (dayCode >= DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_MALE) {
63
+ return { gender: "male", dayOfYear: dayCode };
64
+ }
65
+ if (dayCode >= DAY_CODE_FEMALE_OFFSET + DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_FEMALE) {
66
+ return { gender: "female", dayOfYear: dayCode - DAY_CODE_FEMALE_OFFSET };
67
+ }
68
+ return null;
69
+ };
70
+ var getNICType = (nic) => {
71
+ if (typeof nic !== "string") {
72
+ return "invalid";
73
+ }
74
+ const normalized = normalizeNIC(nic);
75
+ if (OLD_NIC_PATTERN.test(normalized)) {
76
+ return "old";
77
+ }
78
+ if (NEW_NIC_PATTERN.test(normalized)) {
79
+ return "new";
80
+ }
81
+ return "invalid";
82
+ };
83
+ var safeParse = (nic) => {
84
+ if (typeof nic !== "string") {
85
+ return fail("NON_STRING", "NIC must be a string", nic);
86
+ }
87
+ const normalized = normalizeNIC(nic);
88
+ if (!normalized) {
89
+ return fail("EMPTY", "NIC is empty", nic);
90
+ }
91
+ const type = getNICType(normalized);
92
+ if (type === "invalid") {
93
+ return fail(
94
+ "INVALID_FORMAT",
95
+ "NIC does not match old or new format",
96
+ nic
97
+ );
98
+ }
99
+ const birthYear = type === "old" ? 1900 + Number.parseInt(normalized.slice(0, 2), 10) : Number.parseInt(normalized.slice(0, 4), 10);
100
+ if (birthYear > getUTCFullYearNow()) {
101
+ return fail("FUTURE_YEAR", `birth year ${birthYear} is in the future`, nic);
102
+ }
103
+ const dayCode = type === "old" ? Number.parseInt(normalized.slice(2, 5), 10) : Number.parseInt(normalized.slice(4, 7), 10);
104
+ const genderAndDay = resolveGenderAndDay(dayCode);
105
+ if (!genderAndDay) {
106
+ return fail(
107
+ "INVALID_DAY_CODE",
108
+ `day code ${dayCode} is out of range`,
109
+ nic
110
+ );
111
+ }
112
+ const date = dayOfYearToUTCDate(birthYear, genderAndDay.dayOfYear);
113
+ if (!date) {
114
+ return fail(
115
+ "INVALID_DAY_FOR_YEAR",
116
+ `day ${genderAndDay.dayOfYear} does not exist in year ${birthYear}`,
117
+ nic
118
+ );
119
+ }
120
+ return {
121
+ success: true,
122
+ data: {
123
+ input: nic,
124
+ normalized,
125
+ type,
126
+ valid: true,
127
+ birthYear,
128
+ dayOfYear: genderAndDay.dayOfYear,
129
+ birthDate: formatUTCDate(date),
130
+ gender: genderAndDay.gender
131
+ }
132
+ };
133
+ };
134
+ var parseNIC = (nic) => {
135
+ const result = safeParse(nic);
136
+ return result.success ? result.data : null;
137
+ };
138
+
139
+ // src/age.ts
140
+ var toUTCDay = (date) => {
141
+ return new Date(
142
+ Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
143
+ );
144
+ };
145
+ var todayUTC = () => toUTCDay(/* @__PURE__ */ new Date());
146
+ var getAgeOn = (nic, date) => {
147
+ const parsed = parseNIC(nic);
148
+ if (!parsed) {
149
+ return null;
150
+ }
151
+ const birth = parseUTCBirthDate(parsed.birthDate);
152
+ const ref = toUTCDay(date);
153
+ if (ref.getTime() < birth.getTime()) {
154
+ return null;
155
+ }
156
+ let age = ref.getUTCFullYear() - birth.getUTCFullYear();
157
+ const monthDiff = ref.getUTCMonth() - birth.getUTCMonth();
158
+ if (monthDiff < 0 || monthDiff === 0 && ref.getUTCDate() < birth.getUTCDate()) {
159
+ age -= 1;
160
+ }
161
+ return age;
162
+ };
163
+ var getAge = (nic, opts) => {
164
+ const ref = todayUTC();
165
+ return getAgeOn(nic, ref);
166
+ };
167
+
168
+ // src/mask.ts
169
+ var maskNIC = (nic) => {
170
+ if (typeof nic !== "string") {
171
+ return "";
172
+ }
173
+ const normalized = normalizeNIC(nic);
174
+ const type = getNICType(normalized);
175
+ if (type === "old") {
176
+ return `${normalized.slice(0, 5)}****${normalized.slice(9)}`;
177
+ }
178
+ if (type === "new") {
179
+ return `${normalized.slice(0, 7)}*****`;
180
+ }
181
+ return normalized;
182
+ };
183
+
184
+ // src/validate.ts
185
+ var validateNIC = (nic) => {
186
+ return parseNIC(nic) !== null;
187
+ };
188
+
189
+ // src/cli-core.ts
190
+ var USAGE = `lanka-nic \u2014 Sri Lankan NIC utility
191
+
192
+ Usage:
193
+ lanka-nic validate <nic>
194
+ lanka-nic parse <nic>
195
+ lanka-nic mask <nic>
196
+ lanka-nic age <nic>
197
+
198
+ If <nic> is omitted, the value is read from stdin.
199
+ `;
200
+ var ok = (stdout) => ({ code: 0, stdout, stderr: "" });
201
+ var err = (code, stderr) => ({
202
+ code,
203
+ stdout: "",
204
+ stderr
205
+ });
206
+ var runCommand = (argv, stdinValue) => {
207
+ const cmd = argv[0];
208
+ const arg = argv[1];
209
+ if (!cmd || cmd === "--help" || cmd === "-h") {
210
+ return ok(USAGE);
211
+ }
212
+ const trimmedStdin = stdinValue?.trim() ?? "";
213
+ const nic = arg ?? (trimmedStdin || null);
214
+ if (nic === null) {
215
+ return err(2, USAGE);
216
+ }
217
+ switch (cmd) {
218
+ case "validate": {
219
+ const valid = validateNIC(nic);
220
+ return { code: valid ? 0 : 1, stdout: `${valid}
221
+ `, stderr: "" };
222
+ }
223
+ case "parse": {
224
+ const result = safeParse(nic);
225
+ if (result.success) {
226
+ return ok(`${JSON.stringify(result.data, null, 2)}
227
+ `);
228
+ }
229
+ const payload = JSON.stringify({
230
+ error: result.error.code,
231
+ message: result.error.message
232
+ });
233
+ return err(1, `${payload}
234
+ `);
235
+ }
236
+ case "mask": {
237
+ const masked = maskNIC(nic);
238
+ const valid = validateNIC(nic);
239
+ return { code: valid ? 0 : 1, stdout: `${masked}
240
+ `, stderr: "" };
241
+ }
242
+ case "age": {
243
+ const age = getAge(nic);
244
+ if (age === null) {
245
+ return err(1, "invalid nic\n");
246
+ }
247
+ return ok(`${age}
248
+ `);
249
+ }
250
+ default:
251
+ return err(2, `unknown command: ${cmd}
252
+ ${USAGE}`);
253
+ }
254
+ };
255
+
256
+ // src/cli.ts
257
+ var readStdin = async () => {
258
+ if (process.stdin.isTTY) {
259
+ return null;
260
+ }
261
+ let data = "";
262
+ for await (const chunk of process.stdin) {
263
+ data += String(chunk);
264
+ }
265
+ return data || null;
266
+ };
267
+ var main = async () => {
268
+ const stdin = await readStdin();
269
+ const { code, stdout, stderr } = runCommand(process.argv.slice(2), stdin);
270
+ if (stdout) {
271
+ process.stdout.write(stdout);
272
+ }
273
+ if (stderr) {
274
+ process.stderr.write(stderr);
275
+ }
276
+ process.exit(code);
277
+ };
278
+ void main();
279
+ //# sourceMappingURL=cli.js.map
280
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/utils/constants.ts","../src/normalize.ts","../src/utils/date.ts","../src/parse.ts","../src/age.ts","../src/mask.ts","../src/validate.ts","../src/cli-core.ts","../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["import type { NICErrorCode } from './types';\n\nexport class NICError extends Error {\n readonly code: NICErrorCode;\n readonly input: unknown;\n\n constructor(code: NICErrorCode, message: string, input: unknown) {\n super(message);\n this.name = 'NICError';\n this.code = code;\n this.input = input;\n }\n}\n","export const OLD_NIC_PATTERN = /^\\d{9}[VvXx]$/;\nexport const NEW_NIC_PATTERN = /^\\d{12}$/;\n\nexport const DAY_CODE_MIN = 1;\nexport const DAY_CODE_MAX_MALE = 366;\nexport const DAY_CODE_FEMALE_OFFSET = 500;\nexport const DAY_CODE_MAX_FEMALE = 866;\n","import { OLD_NIC_PATTERN } from './utils/constants';\n\nexport const normalizeNIC = (nic: string): string => {\n if (typeof nic !== 'string') {\n return '';\n }\n\n const compact = nic.trim().replace(/\\s+/g, '');\n if (OLD_NIC_PATTERN.test(compact)) {\n return compact.slice(0, 9) + compact.slice(9).toUpperCase();\n }\n\n return compact;\n};\n","export const isLeapYear = (year: number): boolean => {\n return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;\n};\n\nexport const dayOfYearToUTCDate = (year: number, dayOfYear: number): Date | null => {\n const maxDays = isLeapYear(year) ? 366 : 365;\n if (dayOfYear < 1 || dayOfYear > maxDays) {\n return null;\n }\n\n return new Date(Date.UTC(year, 0, dayOfYear));\n};\n\nexport const formatUTCDate = (date: Date): string => {\n return date.toISOString().slice(0, 10);\n};\n\nexport const parseUTCBirthDate = (iso: string): Date => {\n const year = Number(iso.slice(0, 4));\n const month = Number(iso.slice(5, 7));\n const day = Number(iso.slice(8, 10));\n return new Date(Date.UTC(year, month - 1, day));\n};\n","import { NICError } from './errors';\nimport { normalizeNIC } from './normalize';\nimport type {\n NICErrorCode,\n NICGender,\n NICType,\n ParsedNIC,\n SafeParseResult\n} from './types';\nimport {\n DAY_CODE_FEMALE_OFFSET,\n DAY_CODE_MAX_FEMALE,\n DAY_CODE_MAX_MALE,\n DAY_CODE_MIN,\n NEW_NIC_PATTERN,\n OLD_NIC_PATTERN\n} from './utils/constants';\nimport { dayOfYearToUTCDate, formatUTCDate } from './utils/date';\n\nconst getUTCFullYearNow = (): number => new Date().getUTCFullYear();\n\nconst fail = (\n code: NICErrorCode,\n message: string,\n input: unknown\n): SafeParseResult<ParsedNIC> => ({\n success: false,\n error: new NICError(code, message, input)\n});\n\nconst resolveGenderAndDay = (\n dayCode: number\n): { gender: NICGender; dayOfYear: number } | null => {\n if (dayCode >= DAY_CODE_MIN && dayCode <= DAY_CODE_MAX_MALE) {\n return { gender: 'male', dayOfYear: dayCode };\n }\n\n if (\n dayCode >= DAY_CODE_FEMALE_OFFSET + DAY_CODE_MIN &&\n dayCode <= DAY_CODE_MAX_FEMALE\n ) {\n return { gender: 'female', dayOfYear: dayCode - DAY_CODE_FEMALE_OFFSET };\n }\n\n return null;\n};\n\nexport const getNICType = (nic: string): NICType => {\n if (typeof nic !== 'string') {\n return 'invalid';\n }\n\n const normalized = normalizeNIC(nic);\n\n if (OLD_NIC_PATTERN.test(normalized)) {\n return 'old';\n }\n\n if (NEW_NIC_PATTERN.test(normalized)) {\n return 'new';\n }\n\n return 'invalid';\n};\n\nexport const safeParse = (nic: unknown): SafeParseResult<ParsedNIC> => {\n if (typeof nic !== 'string') {\n return fail('NON_STRING', 'NIC must be a string', nic);\n }\n\n const normalized = normalizeNIC(nic);\n if (!normalized) {\n return fail('EMPTY', 'NIC is empty', nic);\n }\n\n const type = getNICType(normalized);\n if (type === 'invalid') {\n return fail(\n 'INVALID_FORMAT',\n 'NIC does not match old or new format',\n nic\n );\n }\n\n const birthYear =\n type === 'old'\n ? 1900 + Number.parseInt(normalized.slice(0, 2), 10)\n : Number.parseInt(normalized.slice(0, 4), 10);\n\n if (birthYear > getUTCFullYearNow()) {\n return fail('FUTURE_YEAR', `birth year ${birthYear} is in the future`, nic);\n }\n\n const dayCode =\n type === 'old'\n ? Number.parseInt(normalized.slice(2, 5), 10)\n : Number.parseInt(normalized.slice(4, 7), 10);\n\n const genderAndDay = resolveGenderAndDay(dayCode);\n if (!genderAndDay) {\n return fail(\n 'INVALID_DAY_CODE',\n `day code ${dayCode} is out of range`,\n nic\n );\n }\n\n const date = dayOfYearToUTCDate(birthYear, genderAndDay.dayOfYear);\n if (!date) {\n return fail(\n 'INVALID_DAY_FOR_YEAR',\n `day ${genderAndDay.dayOfYear} does not exist in year ${birthYear}`,\n nic\n );\n }\n\n return {\n success: true,\n data: {\n input: nic,\n normalized,\n type,\n valid: true,\n birthYear,\n dayOfYear: genderAndDay.dayOfYear,\n birthDate: formatUTCDate(date),\n gender: genderAndDay.gender\n }\n };\n};\n\nexport const parseNIC = (nic: string): ParsedNIC | null => {\n const result = safeParse(nic);\n return result.success ? result.data : null;\n};\n\nexport const parseNICOrThrow = (nic: string): ParsedNIC => {\n const result = safeParse(nic);\n if (result.success) {\n return result.data;\n }\n throw result.error;\n};\n\nexport const getBirthDate = (nic: string): string | null => {\n return parseNIC(nic)?.birthDate ?? null;\n};\n\nexport const getGender = (nic: string): NICGender | null => {\n return parseNIC(nic)?.gender ?? null;\n};\n","import { parseNIC } from './parse';\nimport { parseUTCBirthDate } from './utils/date';\n\nconst toUTCDay = (date: Date): Date => {\n return new Date(\n Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())\n );\n};\n\nconst todayUTC = (): Date => toUTCDay(new Date());\n\nexport const getAgeOn = (nic: string, date: Date): number | null => {\n const parsed = parseNIC(nic);\n if (!parsed) {\n return null;\n }\n\n const birth = parseUTCBirthDate(parsed.birthDate);\n const ref = toUTCDay(date);\n\n if (ref.getTime() < birth.getTime()) {\n return null;\n }\n\n let age = ref.getUTCFullYear() - birth.getUTCFullYear();\n const monthDiff = ref.getUTCMonth() - birth.getUTCMonth();\n if (\n monthDiff < 0 ||\n (monthDiff === 0 && ref.getUTCDate() < birth.getUTCDate())\n ) {\n age -= 1;\n }\n\n return age;\n};\n\nexport const getAge = (\n nic: string,\n opts?: { now?: Date }\n): number | null => {\n const ref = opts?.now ?? todayUTC();\n return getAgeOn(nic, ref);\n};\n","import { normalizeNIC } from './normalize';\nimport { getNICType } from './parse';\n\nexport const maskNIC = (nic: string): string => {\n if (typeof nic !== 'string') {\n return '';\n }\n\n const normalized = normalizeNIC(nic);\n const type = getNICType(normalized);\n\n if (type === 'old') {\n return `${normalized.slice(0, 5)}****${normalized.slice(9)}`;\n }\n\n if (type === 'new') {\n return `${normalized.slice(0, 7)}*****`;\n }\n\n return normalized;\n};\n","import { parseNIC } from './parse';\n\nexport const validateNIC = (nic: string): boolean => {\n return parseNIC(nic) !== null;\n};\n","import { getAge } from './age';\nimport { maskNIC } from './mask';\nimport { safeParse } from './parse';\nimport { validateNIC } from './validate';\n\nexport type CliResult = {\n code: number;\n stdout: string;\n stderr: string;\n};\n\nconst USAGE = `lanka-nic — Sri Lankan NIC utility\n\nUsage:\n lanka-nic validate <nic>\n lanka-nic parse <nic>\n lanka-nic mask <nic>\n lanka-nic age <nic>\n\nIf <nic> is omitted, the value is read from stdin.\n`;\n\nconst ok = (stdout: string): CliResult => ({ code: 0, stdout, stderr: '' });\nconst err = (code: number, stderr: string): CliResult => ({\n code,\n stdout: '',\n stderr\n});\n\nexport const runCommand = (\n argv: string[],\n stdinValue: string | null\n): CliResult => {\n const cmd = argv[0];\n const arg = argv[1];\n\n if (!cmd || cmd === '--help' || cmd === '-h') {\n return ok(USAGE);\n }\n\n const trimmedStdin = stdinValue?.trim() ?? '';\n const nic = arg ?? (trimmedStdin || null);\n if (nic === null) {\n return err(2, USAGE);\n }\n\n switch (cmd) {\n case 'validate': {\n const valid = validateNIC(nic);\n return { code: valid ? 0 : 1, stdout: `${valid}\\n`, stderr: '' };\n }\n case 'parse': {\n const result = safeParse(nic);\n if (result.success) {\n return ok(`${JSON.stringify(result.data, null, 2)}\\n`);\n }\n const payload = JSON.stringify({\n error: result.error.code,\n message: result.error.message\n });\n return err(1, `${payload}\\n`);\n }\n case 'mask': {\n const masked = maskNIC(nic);\n const valid = validateNIC(nic);\n return { code: valid ? 0 : 1, stdout: `${masked}\\n`, stderr: '' };\n }\n case 'age': {\n const age = getAge(nic);\n if (age === null) {\n return err(1, 'invalid nic\\n');\n }\n return ok(`${age}\\n`);\n }\n default:\n return err(2, `unknown command: ${cmd}\\n${USAGE}`);\n }\n};\n","#!/usr/bin/env node\nimport { runCommand } from './cli-core';\n\nconst readStdin = async (): Promise<string | null> => {\n if (process.stdin.isTTY) {\n return null;\n }\n\n let data = '';\n for await (const chunk of process.stdin) {\n data += String(chunk);\n }\n return data || null;\n};\n\nconst main = async (): Promise<void> => {\n const stdin = await readStdin();\n const { code, stdout, stderr } = runCommand(process.argv.slice(2), stdin);\n if (stdout) {\n process.stdout.write(stdout);\n }\n if (stderr) {\n process.stderr.write(stderr);\n }\n process.exit(code);\n};\n\nvoid main();\n"]}