nalloc 0.4.0 → 0.5.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.
Files changed (50) hide show
  1. package/README.md +300 -180
  2. package/build/codemod-cli.cjs +153 -0
  3. package/build/codemod-cli.cjs.map +1 -0
  4. package/build/codemod-cli.d.ts +2 -0
  5. package/build/codemod-cli.js +103 -0
  6. package/build/codemod-cli.js.map +1 -0
  7. package/build/codemod.cjs +652 -0
  8. package/build/codemod.cjs.map +1 -0
  9. package/build/codemod.d.ts +29 -0
  10. package/build/codemod.js +634 -0
  11. package/build/codemod.js.map +1 -0
  12. package/build/eslint.cjs +221 -0
  13. package/build/eslint.cjs.map +1 -0
  14. package/build/eslint.d.ts +36 -0
  15. package/build/eslint.js +198 -0
  16. package/build/eslint.js.map +1 -0
  17. package/build/http.cjs +31 -0
  18. package/build/http.cjs.map +1 -0
  19. package/build/http.d.ts +31 -0
  20. package/build/http.js +13 -0
  21. package/build/http.js.map +1 -0
  22. package/build/result.cjs +0 -11
  23. package/build/result.cjs.map +1 -1
  24. package/build/result.d.ts +0 -32
  25. package/build/result.js +0 -8
  26. package/build/result.js.map +1 -1
  27. package/build/safe.cjs +4 -0
  28. package/build/safe.cjs.map +1 -1
  29. package/build/safe.d.ts +1 -0
  30. package/build/safe.js +1 -0
  31. package/build/safe.js.map +1 -1
  32. package/build/schema.cjs +32 -0
  33. package/build/schema.cjs.map +1 -0
  34. package/build/schema.d.ts +44 -0
  35. package/build/schema.js +14 -0
  36. package/build/schema.js.map +1 -0
  37. package/package.json +54 -5
  38. package/src/__tests__/codemod.ts +211 -0
  39. package/src/__tests__/eslint.ts +99 -0
  40. package/src/__tests__/fixtures/tsconfig.json +10 -0
  41. package/src/__tests__/http.ts +64 -0
  42. package/src/__tests__/result.ts +74 -125
  43. package/src/__tests__/schema.ts +58 -0
  44. package/src/codemod-cli.ts +108 -0
  45. package/src/codemod.ts +623 -0
  46. package/src/eslint.ts +145 -0
  47. package/src/http.ts +42 -0
  48. package/src/result.ts +0 -37
  49. package/src/safe.ts +1 -0
  50. package/src/schema.ts +52 -0
@@ -49,11 +49,9 @@ import {
49
49
  safeTry,
50
50
  safeTryAsync,
51
51
  fromPromise,
52
- fromSchema,
53
52
  gen,
54
- genAsync
53
+ genAsync,
55
54
  } from '../result.js';
56
- import type { StandardSchema } from '../result.js';
57
55
  import { ok, err, isOk, isErr, optionOf as optOf, none } from '../types.js';
58
56
 
59
57
  describe('Result', () => {
@@ -86,7 +84,6 @@ describe('Result', () => {
86
84
  expect(isErr(failure)).toBe(true);
87
85
  expect((failure as any).error.message).toBe('failed');
88
86
  });
89
-
90
87
  });
91
88
 
92
89
  describe('type guards', () => {
@@ -127,7 +124,7 @@ describe('Result', () => {
127
124
 
128
125
  describe('map', () => {
129
126
  it('maps Ok value', () => {
130
- const result = map(ok(2), x => x * 3);
127
+ const result = map(ok(2), (x) => x * 3);
131
128
  expect(unwrap(result)).toBe(6);
132
129
  });
133
130
 
@@ -140,7 +137,7 @@ describe('Result', () => {
140
137
 
141
138
  describe('mapErr', () => {
142
139
  it('maps Err value', () => {
143
- const result = mapErr(err('error'), e => e.toUpperCase());
140
+ const result = mapErr(err('error'), (e) => e.toUpperCase());
144
141
  expect(isErr(result)).toBe(true);
145
142
  expect((result as any).error).toBe('ERROR');
146
143
  });
@@ -154,12 +151,12 @@ describe('Result', () => {
154
151
 
155
152
  describe('flatMap', () => {
156
153
  it('flatMaps Ok value', () => {
157
- const result = flatMap(ok(2), x => ok(x * 3));
154
+ const result = flatMap(ok(2), (x) => ok(x * 3));
158
155
  expect(unwrap(result)).toBe(6);
159
156
  });
160
157
 
161
158
  it('can return Err from mapper', () => {
162
- const result = flatMap(ok(2), x => err(`error: ${x}`));
159
+ const result = flatMap(ok(2), (x) => err(`error: ${x}`));
163
160
  expect(isErr(result)).toBe(true);
164
161
  expect((result as any).error).toBe('error: 2');
165
162
  });
@@ -173,7 +170,7 @@ describe('Result', () => {
173
170
 
174
171
  describe('andThen', () => {
175
172
  it('aliases flatMap for Ok', () => {
176
- const result = andThen(ok(2), x => ok(x * 4));
173
+ const result = andThen(ok(2), (x) => ok(x * 4));
177
174
  expect(unwrap(result)).toBe(8);
178
175
  });
179
176
 
@@ -188,7 +185,7 @@ describe('Result', () => {
188
185
  it('runs for Ok and returns original', () => {
189
186
  let seen = 0;
190
187
  const res = ok(7);
191
- const out = tap(res, value => {
188
+ const out = tap(res, (value) => {
192
189
  seen = value;
193
190
  });
194
191
  expect(out).toBe(res);
@@ -209,7 +206,7 @@ describe('Result', () => {
209
206
  it('runs for Err and returns original', () => {
210
207
  let seen = '';
211
208
  const res = err('nope');
212
- const out = tapErr(res, error => {
209
+ const out = tapErr(res, (error) => {
213
210
  seen = error;
214
211
  });
215
212
  expect(out).toBe(res);
@@ -228,12 +225,20 @@ describe('Result', () => {
228
225
 
229
226
  describe('bimap', () => {
230
227
  it('maps Ok value', () => {
231
- const result = bimap(ok(2), x => x * 3, e => e);
228
+ const result = bimap(
229
+ ok(2),
230
+ (x) => x * 3,
231
+ (e) => e,
232
+ );
232
233
  expect(unwrap(result)).toBe(6);
233
234
  });
234
235
 
235
236
  it('maps Err value', () => {
236
- const result = bimap(err('error'), (x: number) => x * 3, e => e.toUpperCase());
237
+ const result = bimap(
238
+ err('error'),
239
+ (x: number) => x * 3,
240
+ (e) => e.toUpperCase(),
241
+ );
237
242
  expect(isErr(result)).toBe(true);
238
243
  expect((result as any).error).toBe('ERROR');
239
244
  });
@@ -247,7 +252,11 @@ describe('Result', () => {
247
252
  it('throws error value for Err', () => {
248
253
  const e = err('error');
249
254
  let caught: unknown;
250
- try { unwrap(e); } catch (x) { caught = x; }
255
+ try {
256
+ unwrap(e);
257
+ } catch (x) {
258
+ caught = x;
259
+ }
251
260
  expect(caught).toBe('error');
252
261
  });
253
262
  });
@@ -279,7 +288,7 @@ describe('Result', () => {
279
288
 
280
289
  it('calls function for Err', () => {
281
290
  let called = false;
282
- const result = unwrapOrElse(err('error'), e => {
291
+ const result = unwrapOrElse(err('error'), (e) => {
283
292
  called = true;
284
293
  expect(e).toBe('error');
285
294
  return 99;
@@ -291,7 +300,7 @@ describe('Result', () => {
291
300
 
292
301
  describe('mapOr', () => {
293
302
  it('maps Ok value', () => {
294
- const result = mapOr(ok(2), 10, x => x * 3);
303
+ const result = mapOr(ok(2), 10, (x) => x * 3);
295
304
  expect(result).toBe(6);
296
305
  });
297
306
 
@@ -303,16 +312,24 @@ describe('Result', () => {
303
312
 
304
313
  describe('mapOrElse', () => {
305
314
  it('maps Ok value', () => {
306
- const result = mapOrElse(ok(2), () => 10, x => x * 3);
315
+ const result = mapOrElse(
316
+ ok(2),
317
+ () => 10,
318
+ (x) => x * 3,
319
+ );
307
320
  expect(result).toBe(6);
308
321
  });
309
322
 
310
323
  it('returns default for Err', () => {
311
324
  let called = false;
312
- const result = mapOrElse(err('error'), () => {
313
- called = true;
314
- return 10;
315
- }, (x: number) => x * 3);
325
+ const result = mapOrElse(
326
+ err('error'),
327
+ () => {
328
+ called = true;
329
+ return 10;
330
+ },
331
+ (x: number) => x * 3,
332
+ );
316
333
  expect(result).toBe(10);
317
334
  expect(called).toBe(true);
318
335
  });
@@ -383,7 +400,7 @@ describe('Result', () => {
383
400
 
384
401
  it('calls function if Err', () => {
385
402
  let called = false;
386
- const result = orElse(err('error'), e => {
403
+ const result = orElse(err('error'), (e) => {
387
404
  called = true;
388
405
  expect(e).toBe('error');
389
406
  return ok(2);
@@ -462,7 +479,6 @@ describe('Result', () => {
462
479
  expect(unwrap(result)).toBe(42);
463
480
  });
464
481
 
465
-
466
482
  it('returns outer Err', () => {
467
483
  const result = flatten(err('outer error'));
468
484
  expect(isErr(result)).toBe(true);
@@ -474,8 +490,8 @@ describe('Result', () => {
474
490
  it('calls ok branch for Ok', () => {
475
491
  const result = match(
476
492
  ok(42),
477
- x => `value: ${x}`,
478
- e => `error: ${e}`,
493
+ (x) => `value: ${x}`,
494
+ (e) => `error: ${e}`,
479
495
  );
480
496
  expect(result).toBe('value: 42');
481
497
  });
@@ -484,7 +500,7 @@ describe('Result', () => {
484
500
  const result = match(
485
501
  err('failed'),
486
502
  (x: number) => `value: ${x}`,
487
- e => `error: ${e}`,
503
+ (e) => `error: ${e}`,
488
504
  );
489
505
  expect(result).toBe('error: failed');
490
506
  });
@@ -566,11 +582,11 @@ describe('Result', () => {
566
582
 
567
583
  describe('isOkAnd', () => {
568
584
  it('returns true if Ok and predicate true', () => {
569
- expect(isOkAnd(ok(5), x => x > 3)).toBe(true);
585
+ expect(isOkAnd(ok(5), (x) => x > 3)).toBe(true);
570
586
  });
571
587
 
572
588
  it('returns false if Ok and predicate false', () => {
573
- expect(isOkAnd(ok(2), x => x > 3)).toBe(false);
589
+ expect(isOkAnd(ok(2), (x) => x > 3)).toBe(false);
574
590
  });
575
591
 
576
592
  it('returns false for Err', () => {
@@ -580,11 +596,11 @@ describe('Result', () => {
580
596
 
581
597
  describe('isErrAnd', () => {
582
598
  it('returns true if Err and predicate true', () => {
583
- expect(isErrAnd(err('error'), e => e.length > 3)).toBe(true);
599
+ expect(isErrAnd(err('error'), (e) => e.length > 3)).toBe(true);
584
600
  });
585
601
 
586
602
  it('returns false if Err and predicate false', () => {
587
- expect(isErrAnd(err('no'), e => e.length > 3)).toBe(false);
603
+ expect(isErrAnd(err('no'), (e) => e.length > 3)).toBe(false);
588
604
  });
589
605
 
590
606
  it('returns false for Ok', () => {
@@ -598,12 +614,11 @@ describe('Result', () => {
598
614
  () => {
599
615
  throw new Error('boom');
600
616
  },
601
- error => (error as Error).message,
617
+ (error) => (error as Error).message,
602
618
  );
603
619
  expect(isErr(result)).toBe(true);
604
620
  expect((result as any).error).toBe('boom');
605
621
  });
606
-
607
622
  });
608
623
 
609
624
  describe('wrap', () => {
@@ -643,7 +658,7 @@ describe('Result', () => {
643
658
  });
644
659
 
645
660
  it('throws Err error for failed Result', () => {
646
- const fn = (x: number) => x > 0 ? ok(x) : err('negative');
661
+ const fn = (x: number) => (x > 0 ? ok(x) : err('negative'));
647
662
  const throwing = toThrowable(fn);
648
663
  expect(() => throwing(-1)).toThrow('negative');
649
664
  });
@@ -715,22 +730,14 @@ describe('Result', () => {
715
730
  });
716
731
 
717
732
  it('partitionAsync separates async results', async () => {
718
- const promises = [
719
- Promise.resolve(ok(1)),
720
- Promise.resolve(err('a')),
721
- Promise.resolve(ok(2)),
722
- ];
733
+ const promises = [Promise.resolve(ok(1)), Promise.resolve(err('a')), Promise.resolve(ok(2))];
723
734
  const [oks, errs] = await partitionAsync(promises);
724
735
  expect(oks).toEqual([1, 2]);
725
736
  expect(errs).toEqual(['a']);
726
737
  });
727
738
 
728
739
  it('partitionAsync handles rejected promises as errors', async () => {
729
- const promises = [
730
- Promise.resolve(ok(1)),
731
- Promise.reject(new Error('rejected')),
732
- Promise.resolve(ok(2)),
733
- ];
740
+ const promises = [Promise.resolve(ok(1)), Promise.reject(new Error('rejected')), Promise.resolve(ok(2))];
734
741
  const [oks, errs] = await partitionAsync(promises);
735
742
  expect(oks).toEqual([1, 2]);
736
743
  expect(errs).toHaveLength(1);
@@ -779,8 +786,10 @@ describe('Result', () => {
779
786
 
780
787
  it('uses onError mapper for sync throw', () => {
781
788
  const result = tryCatchMaybePromise(
782
- () => { throw new Error('fail'); },
783
- (e) => `mapped: ${(e as Error).message}`
789
+ () => {
790
+ throw new Error('fail');
791
+ },
792
+ (e) => `mapped: ${(e as Error).message}`,
784
793
  );
785
794
  expect(isErr(result)).toBe(true);
786
795
  expect((result as any).error).toBe('mapped: fail');
@@ -789,7 +798,7 @@ describe('Result', () => {
789
798
  it('uses onError mapper for async rejection', async () => {
790
799
  const result = await tryCatchMaybePromise(
791
800
  () => Promise.reject(new Error('fail')),
792
- (e) => `mapped: ${(e as Error).message}`
801
+ (e) => `mapped: ${(e as Error).message}`,
793
802
  );
794
803
  expect(isErr(result)).toBe(true);
795
804
  expect((result as any).error).toBe('mapped: fail');
@@ -818,10 +827,7 @@ describe('Result', () => {
818
827
  });
819
828
 
820
829
  it('handles all async values', async () => {
821
- const result = await settleMaybePromise([
822
- Promise.resolve(1),
823
- Promise.resolve(2)
824
- ]);
830
+ const result = await settleMaybePromise([Promise.resolve(1), Promise.resolve(2)]);
825
831
  expect(result).toEqual([1, 2]);
826
832
  });
827
833
 
@@ -846,28 +852,20 @@ describe('Result', () => {
846
852
  });
847
853
 
848
854
  it('preserves input order with interleaved sync/async', async () => {
849
- const result = await partitionMaybePromise([
850
- Promise.resolve(ok(1)),
851
- ok(2),
852
- Promise.resolve(err('a')),
853
- err('b'),
855
+ const result = await partitionMaybePromise([Promise.resolve(ok(1)), ok(2), Promise.resolve(err('a')), err('b')]);
856
+ expect(result).toEqual([
857
+ [1, 2],
858
+ ['a', 'b'],
854
859
  ]);
855
- expect(result).toEqual([[1, 2], ['a', 'b']]);
856
860
  });
857
861
 
858
862
  it('handles all async values', async () => {
859
- const result = await partitionMaybePromise([
860
- Promise.resolve(ok(1)),
861
- Promise.resolve(err('a'))
862
- ]);
863
+ const result = await partitionMaybePromise([Promise.resolve(ok(1)), Promise.resolve(err('a'))]);
863
864
  expect(result).toEqual([[1], ['a']]);
864
865
  });
865
866
 
866
867
  it('handles rejected promises as errors', async () => {
867
- const result = await partitionMaybePromise([
868
- ok(1),
869
- Promise.reject(new Error('rejected'))
870
- ]);
868
+ const result = await partitionMaybePromise([ok(1), Promise.reject(new Error('rejected'))]);
871
869
  expect(result[0]).toEqual([1]);
872
870
  expect(result[1]).toHaveLength(1);
873
871
  expect((result[1][0] as Error).message).toBe('rejected');
@@ -938,7 +936,7 @@ describe('Result', () => {
938
936
  });
939
937
 
940
938
  it('propagates Err through chain', () => {
941
- const parse = (s: string) => s === 'bad' ? err('parse error') : ok(Number(s));
939
+ const parse = (s: string) => (s === 'bad' ? err('parse error') : ok(Number(s)));
942
940
  const result = safeTry(() => {
943
941
  const a = unwrap(parse('10'));
944
942
  const b = unwrap(parse('bad'));
@@ -995,10 +993,7 @@ describe('Result', () => {
995
993
  });
996
994
 
997
995
  it('uses onError mapper for rejected promise', async () => {
998
- const result = await fromPromise(
999
- Promise.reject(new Error('boom')),
1000
- (e) => `mapped: ${(e as Error).message}`,
1001
- );
996
+ const result = await fromPromise(Promise.reject(new Error('boom')), (e) => `mapped: ${(e as Error).message}`);
1002
997
  expect(isErr(result)).toBe(true);
1003
998
  expect((result as { error: string }).error).toBe('mapped: boom');
1004
999
  });
@@ -1016,53 +1011,9 @@ describe('Result', () => {
1016
1011
  });
1017
1012
  });
1018
1013
 
1019
- describe('fromSchema', () => {
1020
- const validSchema: StandardSchema<string> = {
1021
- '~standard': {
1022
- validate: (value) => typeof value === 'string'
1023
- ? { value }
1024
- : { issues: [{ message: 'Expected string' }] },
1025
- },
1026
- };
1027
-
1028
- const asyncSchema: StandardSchema<number> = {
1029
- '~standard': {
1030
- validate: (value) => Promise.resolve(
1031
- typeof value === 'number'
1032
- ? { value }
1033
- : { issues: [{ message: 'Expected number' }] },
1034
- ),
1035
- },
1036
- };
1037
-
1038
- it('returns Ok for valid sync schema', () => {
1039
- const result = fromSchema(validSchema, 'hello');
1040
- expect(isOk(result)).toBe(true);
1041
- expect(result).toBe('hello');
1042
- });
1043
-
1044
- it('returns Err with issues for invalid sync schema', () => {
1045
- const result = fromSchema(validSchema, 42);
1046
- expect(isErr(result)).toBe(true);
1047
- expect((result as any).error).toEqual([{ message: 'Expected string' }]);
1048
- });
1049
-
1050
- it('returns Ok for valid async schema', async () => {
1051
- const result = await fromSchema(asyncSchema, 42);
1052
- expect(isOk(result)).toBe(true);
1053
- expect(result).toBe(42);
1054
- });
1055
-
1056
- it('returns Err for invalid async schema', async () => {
1057
- const result = await fromSchema(asyncSchema, 'hello');
1058
- expect(isErr(result)).toBe(true);
1059
- expect((result as any).error).toEqual([{ message: 'Expected number' }]);
1060
- });
1061
- });
1062
-
1063
1014
  describe('gen', () => {
1064
1015
  it('returns Ok for successful execution', () => {
1065
- const result = gen(function*($) {
1016
+ const result = gen(function* ($) {
1066
1017
  const a = yield* $(ok(10));
1067
1018
  const b = yield* $(ok(5));
1068
1019
  return a + b;
@@ -1072,7 +1023,7 @@ describe('Result', () => {
1072
1023
  });
1073
1024
 
1074
1025
  it('short-circuits on first Err', () => {
1075
- const result = gen(function*($) {
1026
+ const result = gen(function* ($) {
1076
1027
  const a = yield* $(ok(10));
1077
1028
  const b = yield* $(err('fail') as Result<number, string>);
1078
1029
  const c = yield* $(ok(5));
@@ -1083,8 +1034,8 @@ describe('Result', () => {
1083
1034
  });
1084
1035
 
1085
1036
  it('propagates Err through chain', () => {
1086
- const parse = (s: string) => s === 'bad' ? err('parse error') : ok(Number(s));
1087
- const result = gen(function*($) {
1037
+ const parse = (s: string) => (s === 'bad' ? err('parse error') : ok(Number(s)));
1038
+ const result = gen(function* ($) {
1088
1039
  const a = yield* $(parse('10'));
1089
1040
  const b = yield* $(parse('bad'));
1090
1041
  const c = yield* $(parse('5'));
@@ -1095,7 +1046,7 @@ describe('Result', () => {
1095
1046
  });
1096
1047
 
1097
1048
  it('returns Ok for empty generator', () => {
1098
- const result = gen(function*() {
1049
+ const result = gen(function* () {
1099
1050
  return 42;
1100
1051
  });
1101
1052
  expect(isOk(result)).toBe(true);
@@ -1104,7 +1055,7 @@ describe('Result', () => {
1104
1055
 
1105
1056
  it('runs try/finally cleanup when short-circuiting on Err', () => {
1106
1057
  let cleanedUp = false;
1107
- const result = gen(function*($) {
1058
+ const result = gen(function* ($) {
1108
1059
  try {
1109
1060
  const a = yield* $(ok(10));
1110
1061
  const b = yield* $(err('fail') as Result<number, string>);
@@ -1121,7 +1072,7 @@ describe('Result', () => {
1121
1072
 
1122
1073
  describe('genAsync', () => {
1123
1074
  it('returns Ok for successful async execution', async () => {
1124
- const result = await genAsync(async function*($) {
1075
+ const result = await genAsync(async function* ($) {
1125
1076
  const a = yield* $(ok(10));
1126
1077
  const b = yield* $(await fromPromise(Promise.resolve(5)));
1127
1078
  return a + b;
@@ -1131,7 +1082,7 @@ describe('Result', () => {
1131
1082
  });
1132
1083
 
1133
1084
  it('short-circuits on first Err', async () => {
1134
- const result = await genAsync(async function*($) {
1085
+ const result = await genAsync(async function* ($) {
1135
1086
  const a = yield* $(ok(10));
1136
1087
  const b = yield* $(err('async fail') as Result<number, string>);
1137
1088
  return a + b;
@@ -1141,7 +1092,7 @@ describe('Result', () => {
1141
1092
  });
1142
1093
 
1143
1094
  it('handles rejected promises via fromPromise', async () => {
1144
- const result = await genAsync(async function*($) {
1095
+ const result = await genAsync(async function* ($) {
1145
1096
  const a = yield* $(await fromPromise(Promise.resolve(10)));
1146
1097
  const b = yield* $(await fromPromise(Promise.reject('boom')));
1147
1098
  return a + b;
@@ -1152,7 +1103,7 @@ describe('Result', () => {
1152
1103
 
1153
1104
  it('runs try/finally cleanup when short-circuiting on Err', async () => {
1154
1105
  let cleanedUp = false;
1155
- const result = await genAsync(async function*($) {
1106
+ const result = await genAsync(async function* ($) {
1156
1107
  try {
1157
1108
  const a = yield* $(ok(10));
1158
1109
  const b = yield* $(err('async fail') as Result<number, string>);
@@ -1166,6 +1117,4 @@ describe('Result', () => {
1166
1117
  expect(cleanedUp).toBe(true);
1167
1118
  });
1168
1119
  });
1169
-
1170
1120
  });
1171
-
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { fromSchema, wrapSchema } from '../schema.js';
3
+ import type { StandardSchema } from '../schema.js';
4
+ import { isOk, isErr } from '../types.js';
5
+
6
+ describe('Schema', () => {
7
+ const validSchema: StandardSchema<string> = {
8
+ '~standard': {
9
+ validate: (value) => (typeof value === 'string' ? { value } : { issues: [{ message: 'Expected string' }] }),
10
+ },
11
+ };
12
+
13
+ const asyncSchema: StandardSchema<number> = {
14
+ '~standard': {
15
+ validate: (value) => Promise.resolve(typeof value === 'number' ? { value } : { issues: [{ message: 'Expected number' }] }),
16
+ },
17
+ };
18
+
19
+ describe('fromSchema', () => {
20
+ it('returns Ok for valid sync schema', () => {
21
+ const result = fromSchema(validSchema, 'hello');
22
+ expect(isOk(result)).toBe(true);
23
+ expect(result).toBe('hello');
24
+ });
25
+
26
+ it('returns Err with issues for invalid sync schema', () => {
27
+ const result = fromSchema(validSchema, 42);
28
+ expect(isErr(result)).toBe(true);
29
+ expect((result as any).error).toEqual([{ message: 'Expected string' }]);
30
+ });
31
+
32
+ it('returns Ok for valid async schema', async () => {
33
+ const result = await fromSchema(asyncSchema, 42);
34
+ expect(isOk(result)).toBe(true);
35
+ expect(result).toBe(42);
36
+ });
37
+
38
+ it('returns Err for invalid async schema', async () => {
39
+ const result = await fromSchema(asyncSchema, 'hello');
40
+ expect(isErr(result)).toBe(true);
41
+ expect((result as any).error).toEqual([{ message: 'Expected number' }]);
42
+ });
43
+ });
44
+
45
+ describe('wrapSchema', () => {
46
+ it('creates a reusable sync validator', () => {
47
+ const parse = wrapSchema(validSchema);
48
+ expect(parse('hello')).toBe('hello');
49
+ expect(isErr(parse(42))).toBe(true);
50
+ });
51
+
52
+ it('creates a reusable async validator', async () => {
53
+ const parse = wrapSchema(asyncSchema);
54
+ expect(await parse(42)).toBe(42);
55
+ expect(isErr(await parse('hello'))).toBe(true);
56
+ });
57
+ });
58
+ });
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import process from 'node:process';
5
+
6
+ const HELP = `nalloc-codemod - migrate a project from neverthrow to nalloc
7
+
8
+ Usage:
9
+ nalloc-codemod <paths...> [--report <file>] [--dry]
10
+
11
+ Arguments:
12
+ paths Files or directories to migrate (.ts/.tsx/.mts/.cts, .d.ts and node_modules skipped)
13
+
14
+ Options:
15
+ --report <file> Write a markdown report of sites needing manual review
16
+ --dry Analyze and report without writing any files
17
+ --help Show this help
18
+
19
+ Migrate the whole project in one run: converted call sites assume their Result
20
+ values are nalloc values, which only holds once constructors are converted too.
21
+ `;
22
+
23
+ const SOURCE_EXT = /\.(ts|tsx|mts|cts)$/;
24
+
25
+ function collectFiles(path: string, files: string[]): void {
26
+ const stats = statSync(path);
27
+ if (stats.isDirectory()) {
28
+ for (const entry of readdirSync(path)) {
29
+ if (entry === 'node_modules' || entry.startsWith('.')) continue;
30
+ collectFiles(join(path, entry), files);
31
+ }
32
+ } else if (SOURCE_EXT.test(path) && !path.endsWith('.d.ts')) {
33
+ files.push(path);
34
+ }
35
+ }
36
+
37
+ const paths: string[] = [];
38
+ let reportPath: string | undefined;
39
+ let dry = false;
40
+ const args = process.argv.slice(2);
41
+ for (let i = 0; i < args.length; i++) {
42
+ const arg = args[i];
43
+ if (arg === '--help') {
44
+ console.log(HELP);
45
+ process.exit(0);
46
+ } else if (arg === '--dry') {
47
+ dry = true;
48
+ } else if (arg === '--report') {
49
+ reportPath = args[++i];
50
+ if (reportPath === undefined) {
51
+ console.error('--report requires a file path');
52
+ process.exit(1);
53
+ }
54
+ } else {
55
+ paths.push(arg);
56
+ }
57
+ }
58
+
59
+ if (paths.length === 0) {
60
+ console.log(HELP);
61
+ process.exit(1);
62
+ }
63
+
64
+ const codemod = await (async () => {
65
+ try {
66
+ return await import('./codemod.js');
67
+ } catch (error) {
68
+ if ((error as { code?: string }).code === 'ERR_MODULE_NOT_FOUND' && String(error).includes('oxc-parser')) {
69
+ console.error('nalloc-codemod requires the optional peer oxc-parser. Install it first:\n\n npm install -D oxc-parser\n');
70
+ process.exit(1);
71
+ }
72
+ throw error;
73
+ }
74
+ })();
75
+
76
+ const files: string[] = [];
77
+ for (const path of paths) {
78
+ collectFiles(path, files);
79
+ }
80
+
81
+ let filesChanged = 0;
82
+ let converted = 0;
83
+ const skipped = [];
84
+ for (const file of files) {
85
+ const source = readFileSync(file, 'utf8');
86
+ const result = codemod.migrateSource(source, file);
87
+ converted += result.converted;
88
+ skipped.push(...result.skipped);
89
+ if (result.changed) {
90
+ filesChanged++;
91
+ if (!dry) {
92
+ writeFileSync(file, result.output);
93
+ }
94
+ }
95
+ }
96
+
97
+ const report = { filesChanged, converted, skipped };
98
+ if (reportPath !== undefined) {
99
+ writeFileSync(reportPath, codemod.renderReport(report));
100
+ }
101
+ console.log(
102
+ `${dry ? '[dry run] ' : ''}${files.length} files scanned, ${filesChanged} changed, ${converted} sites converted, ${skipped.length} need manual review${reportPath !== undefined ? ` (see ${reportPath})` : ''}`,
103
+ );
104
+ if (skipped.length > 0 && reportPath === undefined) {
105
+ for (const site of skipped) {
106
+ console.log(` ${site.file}:${site.line} [${site.reason}] ${site.text}`);
107
+ }
108
+ }