jsrepo 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/index.js ADDED
@@ -0,0 +1,2179 @@
1
+ // src/index.ts
2
+ import fs11 from "node:fs";
3
+ import path9 from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { program as program5 } from "commander";
6
+
7
+ // src/commands/add.ts
8
+ import fs6 from "node:fs";
9
+ import path5 from "node:path";
10
+ import { cancel, confirm, isCancel, multiselect, outro, spinner as spinner2 } from "@clack/prompts";
11
+ import color4 from "chalk";
12
+ import { Command, program as program2 } from "commander";
13
+ import { execa } from "execa";
14
+ import { resolveCommand } from "package-manager-detector/commands";
15
+ import { detect } from "package-manager-detector/detect";
16
+ import * as v4 from "valibot";
17
+
18
+ // src/blocks/utilities/map-to-array.ts
19
+ var mapToArray = (map, fn) => {
20
+ const items = [];
21
+ for (const [key, value] of map) {
22
+ items.push(fn(key, value));
23
+ }
24
+ return items;
25
+ };
26
+
27
+ // src/config/index.ts
28
+ import fs from "node:fs";
29
+ import * as v from "valibot";
30
+
31
+ // src/blocks/types/result.ts
32
+ var Result = class {
33
+ _result;
34
+ constructor(result) {
35
+ this._result = result;
36
+ }
37
+ /** Allows you to run callbacks based on the result.
38
+ *
39
+ * @param success callback to be run when result is success
40
+ * @param failure callback to be run when result is failure
41
+ * @returns
42
+ *
43
+ * ## Usage
44
+ *
45
+ * ```ts
46
+ * result.match(
47
+ * (val) => val,
48
+ * () => {
49
+ * throw new Error('oops!')
50
+ * }
51
+ * );
52
+ * ```
53
+ *
54
+ * ## Examples
55
+ *
56
+ * ```ts
57
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello, World!");
58
+ *
59
+ * const result = functionThatMightFail();
60
+ *
61
+ * const val = result.match(
62
+ * (val) => val,
63
+ * () => {
64
+ * throw new Error('oops!')
65
+ * }
66
+ * );
67
+ *
68
+ * console.log(val); // "Hello, World!"
69
+ * ```
70
+ */
71
+ match(success, failure) {
72
+ if (!this._result.ok) {
73
+ return failure(this._result.err);
74
+ }
75
+ return success(this._result.val);
76
+ }
77
+ /** Maps `Result<T, E>` to `Result<A, E>` using the passed mapping function
78
+ *
79
+ * @param fn Mapping function
80
+ * @returns
81
+ *
82
+ * ## Usage
83
+ *
84
+ * ```ts
85
+ * result.map((val) => val.length);
86
+ * ```
87
+ *
88
+ * ## Examples
89
+ *
90
+ * ```ts
91
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello, World!");
92
+ *
93
+ * const result = functionThatMightFail();
94
+ *
95
+ * const hello = result.map((val) => val.slice(0, 5));
96
+ *
97
+ * console.log(hello.unwrap()); // "Hello"
98
+ * ```
99
+ */
100
+ map(fn) {
101
+ return this.match(
102
+ (val) => Ok(fn(val)),
103
+ (err) => Err(err)
104
+ );
105
+ }
106
+ /** In the `Ok` case returns the mapped value using the function else returns `defaultVal`
107
+ *
108
+ * @param defaultVal Value to be returned when `Err`
109
+ * @param fn Mapping function to map in case of `Ok`
110
+ * @returns
111
+ *
112
+ * ## Usage
113
+ *
114
+ * ```ts
115
+ * result.mapOr(1, (val) => val.length);
116
+ * ```
117
+ *
118
+ * ## Examples
119
+ *
120
+ * ### When `Ok`
121
+ *
122
+ * ```ts
123
+ * const functionThatMightFail = (): Result<string, string> => Ok("foo");
124
+ *
125
+ * const result = functionThatMightFail();
126
+ *
127
+ * const length = result.mapOr(1, (val) => val.length);
128
+ *
129
+ * console.log(length); // 3
130
+ * ```
131
+ *
132
+ * ### When `Err`
133
+ *
134
+ * ```ts
135
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
136
+ *
137
+ * const result = functionThatMightFail();
138
+ *
139
+ * const length = result.mapOr(1, (val) => val.length);
140
+ *
141
+ * console.log(length); // 1
142
+ * ```
143
+ */
144
+ mapOr(defaultVal, fn) {
145
+ return this.match(
146
+ (val) => fn(val),
147
+ (_) => defaultVal
148
+ );
149
+ }
150
+ /** In the `Ok` case returns the mapped value using `fn` else returns value of `def`
151
+ *
152
+ * @param def Mapping function called when `Err`
153
+ * @param fn Mapping function called when `Ok`
154
+ * @returns
155
+ *
156
+ * ## Usage
157
+ *
158
+ * ```ts
159
+ * result.mapOrElse(() => 1, (val) => val.length);
160
+ * ```
161
+ *
162
+ * ## Examples
163
+ *
164
+ * ### When `Ok`
165
+ *
166
+ * ```ts
167
+ * const functionThatMightFail = (): Result<string, string> => Ok("foo");
168
+ *
169
+ * const result = functionThatMightFail();
170
+ *
171
+ * const length = result.mapOrElse(() => 1, (val) => val.length);
172
+ *
173
+ * console.log(length); // 3
174
+ * ```
175
+ *
176
+ * ### When `Err`
177
+ *
178
+ * ```ts
179
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
180
+ *
181
+ * const result = functionThatMightFail();
182
+ *
183
+ * const length = result.mapOr(() => 1, (val) => val.length);
184
+ *
185
+ * console.log(length); // 1
186
+ * ```
187
+ */
188
+ mapOrElse(def, fn) {
189
+ return this.match(
190
+ (val) => fn(val),
191
+ (err) => def(err)
192
+ );
193
+ }
194
+ /** Maps `Result<T, E>` to `Result<T, A>` using the passed mapping function
195
+ *
196
+ * @param fn Mapping function
197
+ * @returns
198
+ *
199
+ * ## Usage
200
+ *
201
+ * ```ts
202
+ * result.mapErr((err) => getCodeMsg(err));
203
+ * ```
204
+ *
205
+ * ## Examples
206
+ *
207
+ * ```ts
208
+ * const functionThatMightFail = (): Result<string, string> => Err(10);
209
+ *
210
+ * const result = functionThatMightFail();
211
+ *
212
+ * const message = result.mapErr(() => "Error");
213
+ *
214
+ * console.log(message); // "Error"
215
+ * ```
216
+ */
217
+ mapErr(fn) {
218
+ return this.match(
219
+ (val) => Ok(val),
220
+ (err) => Err(fn(err))
221
+ );
222
+ }
223
+ /** In the `Err` case returns the mapped value using the function else returns `defaultVal`
224
+ *
225
+ * @param defaultVal Value to be returned when `Ok`
226
+ * @param fn Mapping function to map in case of `Err`
227
+ * @returns
228
+ *
229
+ * ## Usage
230
+ *
231
+ * ```ts
232
+ * result.mapErrOr("Should've been error", (err) => getCodeMsg(err));
233
+ * ```
234
+ *
235
+ * ## Examples
236
+ *
237
+ * ### When `Ok`
238
+ *
239
+ * ```ts
240
+ * const functionThatMightFail = (): Result<string, string> => Ok("foo");
241
+ *
242
+ * const result = functionThatMightFail();
243
+ *
244
+ * const message = result.mapErrOr("Should've been error", () => "Error");
245
+ *
246
+ * console.log(message); // "Should've been error"
247
+ * ```
248
+ *
249
+ * ### When `Err`
250
+ *
251
+ * ```ts
252
+ * const functionThatMightFail = (): Result<string, string> => Err(10);
253
+ *
254
+ * const result = functionThatMightFail();
255
+ *
256
+ * const message = result.mapErrOr("Should've been error", () => "Error");
257
+ *
258
+ * console.log(message); // "Error"
259
+ * ```
260
+ */
261
+ mapErrOr(defaultVal, fn) {
262
+ return this.match(
263
+ (_) => defaultVal,
264
+ (err) => fn(err)
265
+ );
266
+ }
267
+ /** In the `Err` case returns the mapped value using the function else returns value of `def`
268
+ *
269
+ * @param def Mapping function called when `Ok`
270
+ * @param fn Mapping function called when `Err`
271
+ * @returns
272
+ *
273
+ * ## Usage
274
+ *
275
+ * ```ts
276
+ * result.mapErrOrElse(() => "Value", (_) => "Error!");
277
+ * ```
278
+ *
279
+ * ## Examples
280
+ *
281
+ * ### When `Ok`
282
+ *
283
+ * ```ts
284
+ * const functionThatMightFail = (): Result<string, string> => Ok("foo");
285
+ *
286
+ * const result = functionThatMightFail();
287
+ *
288
+ * const length = result.mapErrOrElse(() => 1, (val) => val.length);
289
+ *
290
+ * console.log(length); // 1
291
+ * ```
292
+ *
293
+ * ### When `Err`
294
+ *
295
+ * ```ts
296
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
297
+ *
298
+ * const result = functionThatMightFail();
299
+ *
300
+ * const length = result.mapOr(() => 1, (val) => val.length);
301
+ *
302
+ * console.log(length); // 4
303
+ * ```
304
+ */
305
+ mapErrOrElse(def, fn) {
306
+ return this.match(
307
+ (val) => def(val),
308
+ (err) => fn(err)
309
+ );
310
+ }
311
+ /** Returns true if result is `Ok`
312
+ *
313
+ * @returns
314
+ *
315
+ * ## Usage
316
+ *
317
+ * ```ts
318
+ * result.isOk();
319
+ * ```
320
+ */
321
+ isOk() {
322
+ return this.match(
323
+ () => true,
324
+ () => false
325
+ );
326
+ }
327
+ /** Returns true if result is `Err`
328
+ *
329
+ * @returns
330
+ *
331
+ * ## Usage
332
+ *
333
+ * ```ts
334
+ * result.isErr();
335
+ * ```
336
+ */
337
+ isErr() {
338
+ return this.match(
339
+ () => false,
340
+ () => true
341
+ );
342
+ }
343
+ /** Tries to return value if value is `Err` throws generic error message.
344
+ *
345
+ * @returns
346
+ *
347
+ * ## Usage
348
+ *
349
+ * ```ts
350
+ * result.unwrap();
351
+ * ```
352
+ *
353
+ * ## Examples
354
+ *
355
+ * ### When `Ok`
356
+ *
357
+ * ```ts
358
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
359
+ *
360
+ * const result = functionThatMightFail();
361
+ *
362
+ * console.log(result.unwrap()); // "Hello!"
363
+ * ```
364
+ *
365
+ * ### When `Err`
366
+ *
367
+ * ```ts
368
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
369
+ *
370
+ * const result = functionThatMightFail();
371
+ *
372
+ * result.unwrap(); // Error: Attempted to call `.unwrap()` on a non `Ok` value.
373
+ * ```
374
+ */
375
+ unwrap() {
376
+ return this.match(
377
+ (val) => val,
378
+ () => {
379
+ throw new Error("Attempted to call `.unwrap()` on a non `Ok` value.");
380
+ }
381
+ );
382
+ }
383
+ /** Tries to return err if value is `Ok` throws generic error message.
384
+ *
385
+ * @returns
386
+ *
387
+ * ## Usage
388
+ *
389
+ * ```ts
390
+ * result.unwrapErr();
391
+ * ```
392
+ *
393
+ * ## Examples
394
+ *
395
+ * ### When `Ok`
396
+ *
397
+ * ```ts
398
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
399
+ *
400
+ * const result = functionThatMightFail();
401
+ *
402
+ * result.unwrapErr(); // Error: Attempted to call `.unwrapErr()` on a non `Err` value.
403
+ * ```
404
+ *
405
+ * ### When `Err`
406
+ *
407
+ * ```ts
408
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
409
+ *
410
+ * const result = functionThatMightFail();
411
+ *
412
+ * console.log(result.unwrapErr()); // "oops!"
413
+ * ```
414
+ */
415
+ unwrapErr() {
416
+ return this.match(
417
+ () => {
418
+ throw new Error("Attempted to call `.unwrapErr()` on a non `Err` value.");
419
+ },
420
+ (err) => err
421
+ );
422
+ }
423
+ /** Tries to unwrap the value if value is `Err` returns `defaultVal`
424
+ *
425
+ * @param defaultVal Value to be returned if `Err`
426
+ * @returns
427
+ *
428
+ * ## Usage
429
+ *
430
+ * ```ts
431
+ * result.unwrapOr(7);
432
+ * ```
433
+ *
434
+ * ## Examples
435
+ *
436
+ * ### When `Ok`
437
+ *
438
+ * ```ts
439
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
440
+ *
441
+ * const result = functionThatMightFail();
442
+ *
443
+ * console.log(result.unwrapOr("Yellow!")); // "Hello!"
444
+ * ```
445
+ *
446
+ * ### When `Err`
447
+ *
448
+ * ```ts
449
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
450
+ *
451
+ * const result = functionThatMightFail();
452
+ *
453
+ * console.log(result.unwrapOr("Yellow!")); // "Yellow!"
454
+ * ```
455
+ */
456
+ unwrapOr(defaultVal) {
457
+ return this.match(
458
+ (val) => val,
459
+ (_) => defaultVal
460
+ );
461
+ }
462
+ /** Tries to unwrap the error if vale is `Ok` returns `defaultVal`
463
+ *
464
+ * @param defaultVal
465
+ * @returns
466
+ *
467
+ * ## Usage
468
+ *
469
+ * ```ts
470
+ * result.unwrapErrOr("Error");
471
+ * ```
472
+ *
473
+ * ## Examples
474
+ *
475
+ * ### When `Ok`
476
+ *
477
+ * ```ts
478
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
479
+ *
480
+ * const result = functionThatMightFail();
481
+ *
482
+ * console.log(result.unwrapErrOr("Yellow!")); // "Yellow!"
483
+ * ```
484
+ *
485
+ * ### When `Err`
486
+ *
487
+ * ```ts
488
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
489
+ *
490
+ * const result = functionThatMightFail();
491
+ *
492
+ * console.log(result.unwrapErrOr("Yellow!")); // "oops!"
493
+ * ```
494
+ */
495
+ unwrapErrOr(defaultVal) {
496
+ return this.match(
497
+ () => defaultVal,
498
+ (err) => err
499
+ );
500
+ }
501
+ /** Tries to return the value if value is `Err` calls `fn`
502
+ *
503
+ * @param fn Function called if `Err`
504
+ *
505
+ * ## Usage
506
+ *
507
+ * ```ts
508
+ * result.unwrapOrElse(() => "Hello!");
509
+ * ```
510
+ *
511
+ * ## Examples
512
+ *
513
+ * ### When `Ok`
514
+ *
515
+ * ```ts
516
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
517
+ *
518
+ * const result = functionThatMightFail();
519
+ *
520
+ * console.log(result.unwrapOrElse(() => "oops!")); // "Hello!"
521
+ * ```
522
+ *
523
+ * ### When `Err`
524
+ *
525
+ * ```ts
526
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
527
+ *
528
+ * const result = functionThatMightFail();
529
+ *
530
+ * console.log(result.unwrapOrElse(() => "Hello!")); // "Hello!"
531
+ * ```
532
+ *
533
+ */
534
+ unwrapOrElse(fn) {
535
+ return this.match(
536
+ (val) => val,
537
+ (err) => fn(err)
538
+ );
539
+ }
540
+ /** Tries to return the error if value is `Ok` calls `fn`
541
+ *
542
+ * @param fn Function called if `Ok`
543
+ *
544
+ * ## Usage
545
+ *
546
+ * ```ts
547
+ * result.unwrapErrOrElse(() => "Error!");
548
+ * ```
549
+ *
550
+ * ## Examples
551
+ *
552
+ * ### When `Ok`
553
+ *
554
+ * ```ts
555
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
556
+ *
557
+ * const result = functionThatMightFail();
558
+ *
559
+ * console.log(result.unwrapErrOrElse(() => "oops!")); // "oops!"
560
+ * ```
561
+ *
562
+ * ### When `Err`
563
+ *
564
+ * ```ts
565
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
566
+ *
567
+ * const result = functionThatMightFail();
568
+ *
569
+ * console.log(result.unwrapErrOrElse(() => "Hello!")); // "oops!"
570
+ * ```
571
+ *
572
+ */
573
+ unwrapErrOrElse(fn) {
574
+ return this.match(
575
+ (val) => fn(val),
576
+ (err) => err
577
+ );
578
+ }
579
+ /** Tries to return value if value is `Err` throws custom error message.
580
+ *
581
+ * @param message Message to show when value is `Err`
582
+ * @returns
583
+ *
584
+ * ## Usage
585
+ *
586
+ * ```ts
587
+ * result.expect("Custom message");
588
+ * ```
589
+ *
590
+ * ## Examples
591
+ *
592
+ * ### When `Ok`
593
+ *
594
+ * ```ts
595
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
596
+ *
597
+ * const result = functionThatMightFail();
598
+ *
599
+ * console.log(result.expect("I failed!")); // "Hello!"
600
+ * ```
601
+ *
602
+ * ### When `Err`
603
+ *
604
+ * ```ts
605
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
606
+ *
607
+ * const result = functionThatMightFail();
608
+ *
609
+ * result.expect("I failed!"); // Error: I failed!
610
+ * ```
611
+ */
612
+ expect(message) {
613
+ return this.match(
614
+ (val) => val,
615
+ () => {
616
+ throw new Error(message);
617
+ }
618
+ );
619
+ }
620
+ /** Tries to return error value if value is `Ok` throws custom error message
621
+ *
622
+ * @param message
623
+ * @returns
624
+ *
625
+ * ## Usage
626
+ *
627
+ * ```ts
628
+ * result.expectErr("Custom message");
629
+ * ```
630
+ *
631
+ * ## Examples
632
+ *
633
+ * ### When `Ok`
634
+ *
635
+ * ```ts
636
+ * const functionThatMightFail = (): Result<string, string> => Ok("Hello!");
637
+ *
638
+ * const result = functionThatMightFail();
639
+ *
640
+ * console.log(result.expectErr("I failed!")); // Error: I failed!
641
+ * ```
642
+ *
643
+ * ### When `Err`
644
+ *
645
+ * ```ts
646
+ * const functionThatMightFail = (): Result<string, string> => Err("oops!");
647
+ *
648
+ * const result = functionThatMightFail();
649
+ *
650
+ * console.log(result.expectErr("I failed!")); // "oops!"
651
+ * ```
652
+ */
653
+ expectErr(message) {
654
+ return this.match(
655
+ () => {
656
+ throw new Error(message);
657
+ },
658
+ (err) => err
659
+ );
660
+ }
661
+ };
662
+ var Ok = (val) => {
663
+ return new Result({ ok: true, val });
664
+ };
665
+ var Err = (err) => {
666
+ return new Result({ ok: false, err });
667
+ };
668
+
669
+ // src/config/index.ts
670
+ var CONFIG_NAME = "blocks.json";
671
+ var schema = v.object({
672
+ $schema: v.string(),
673
+ repos: v.optional(v.array(v.string()), []),
674
+ includeTests: v.boolean(),
675
+ path: v.pipe(v.string(), v.minLength(1)),
676
+ watermark: v.optional(v.boolean(), true)
677
+ });
678
+ var getConfig = () => {
679
+ if (!fs.existsSync(CONFIG_NAME)) {
680
+ return Err("Could not find your configuration file! Please run `npx jsrepo init`.");
681
+ }
682
+ const config = v.safeParse(schema, JSON.parse(fs.readFileSync(CONFIG_NAME).toString()));
683
+ if (!config.success) {
684
+ return Err("There was an error reading your `blocks.json` file!");
685
+ }
686
+ return Ok(config.output);
687
+ };
688
+
689
+ // src/utils/build.ts
690
+ import fs4 from "node:fs";
691
+ import path3 from "node:path";
692
+ import color2 from "chalk";
693
+ import { program } from "commander";
694
+ import * as v2 from "valibot";
695
+
696
+ // src/utils/index.ts
697
+ import color from "chalk";
698
+ var OUTPUT_FILE = "blocks-manifest.json";
699
+ var WARN = color.bgRgb(245, 149, 66).white("WARN");
700
+ var INFO = color.bgBlueBright.white("INFO");
701
+
702
+ // src/utils/language-support.ts
703
+ import fs3 from "node:fs";
704
+ import { builtinModules } from "node:module";
705
+ import path2 from "node:path";
706
+ import { walk } from "estree-walker";
707
+ import * as sv from "svelte/compiler";
708
+ import { Project } from "ts-morph";
709
+ import validatePackageName from "validate-npm-package-name";
710
+
711
+ // src/utils/package.ts
712
+ import fs2 from "node:fs";
713
+ import path from "node:path";
714
+ var findNearestPackageJson = (startDir, until) => {
715
+ const packagePath = path.join(startDir, "package.json");
716
+ if (fs2.existsSync(packagePath)) return packagePath;
717
+ if (startDir === until) return void 0;
718
+ const segments = startDir.split(/[\/\\]/);
719
+ return findNearestPackageJson(segments.slice(0, segments.length - 1).join("/"), until);
720
+ };
721
+
722
+ // src/utils/language-support.ts
723
+ var typescript = {
724
+ matches: (fileName) => fileName.endsWith(".ts") || fileName.endsWith(".js") || fileName.endsWith(".tsx") || fileName.endsWith(".jsx"),
725
+ resolveDependencies: (filePath, category, isSubDir) => {
726
+ const project = new Project();
727
+ const blockFile = project.addSourceFileAtPath(filePath);
728
+ const imports = blockFile.getImportDeclarations();
729
+ const relativeImports = imports.filter(
730
+ (declaration) => declaration.getModuleSpecifierValue().startsWith(".")
731
+ );
732
+ const localDeps = /* @__PURE__ */ new Set();
733
+ for (const relativeImport of relativeImports) {
734
+ const mod = relativeImport.getModuleSpecifierValue();
735
+ const localDep = resolveLocalImport(mod, category, isSubDir);
736
+ if (localDep) localDeps.add(localDep);
737
+ }
738
+ const deps = imports.filter((declaration) => !declaration.getModuleSpecifierValue().startsWith(".")).map((declaration) => declaration.getModuleSpecifierValue());
739
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
740
+ return Ok({
741
+ local: Array.from(localDeps),
742
+ dependencies,
743
+ devDependencies
744
+ });
745
+ },
746
+ comment: (content) => `/*
747
+ ${content}
748
+ */`
749
+ };
750
+ var svelte = {
751
+ matches: (fileName) => fileName.endsWith(".svelte"),
752
+ resolveDependencies: (filePath, category, isSubDir) => {
753
+ const sourceCode = fs3.readFileSync(filePath).toString();
754
+ const root = sv.parse(sourceCode, { modern: true });
755
+ if (!root.instance) return Ok({ dependencies: [], devDependencies: [], local: [] });
756
+ const localDeps = /* @__PURE__ */ new Set();
757
+ const deps = /* @__PURE__ */ new Set();
758
+ walk(root.instance, {
759
+ enter: (node) => {
760
+ if (node.type === "ImportDeclaration") {
761
+ if (typeof node.source.value === "string") {
762
+ if (node.source.value.startsWith(".")) {
763
+ const localDep = resolveLocalImport(
764
+ node.source.value,
765
+ category,
766
+ isSubDir
767
+ );
768
+ if (localDep) localDeps.add(localDep);
769
+ } else {
770
+ deps.add(node.source.value);
771
+ }
772
+ }
773
+ }
774
+ }
775
+ });
776
+ const { devDependencies, dependencies } = resolveRemoteDeps(Array.from(deps), filePath);
777
+ return Ok({
778
+ dependencies,
779
+ devDependencies,
780
+ local: Array.from(localDeps)
781
+ });
782
+ },
783
+ comment: (content) => `<!--
784
+ ${content}
785
+ -->`
786
+ };
787
+ var resolveLocalImport = (mod, category, isSubDir) => {
788
+ if (isSubDir && mod.startsWith("./")) return void 0;
789
+ if (mod.startsWith("./")) {
790
+ return `${category}/${path2.parse(path2.basename(mod)).name}`;
791
+ }
792
+ if (isSubDir && mod.startsWith("../") && !mod.startsWith("../.")) {
793
+ return `${category}/${path2.parse(path2.basename(mod)).name}`;
794
+ }
795
+ const segments = mod.replaceAll("../", "").split("/");
796
+ if (segments.length !== 2) return void 0;
797
+ return `${segments[0]}/${segments[1]}`;
798
+ };
799
+ var resolveRemoteDeps = (deps, filePath) => {
800
+ const filteredDeps = deps.filter(
801
+ (dep) => !builtinModules.includes(dep) && !dep.startsWith("node:") && validatePackageName(dep).validForNewPackages
802
+ );
803
+ const pkgPath = findNearestPackageJson(path2.dirname(filePath), "");
804
+ const dependencies = /* @__PURE__ */ new Set();
805
+ const devDependencies = /* @__PURE__ */ new Set();
806
+ if (pkgPath) {
807
+ const { devDependencies: packageDevDependencies, dependencies: packageDependencies } = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
808
+ for (const dep of filteredDeps) {
809
+ let version2 = void 0;
810
+ if (packageDependencies !== void 0) {
811
+ version2 = packageDependencies[dep];
812
+ }
813
+ if (version2 !== void 0) {
814
+ dependencies.add(`${dep}@${version2}`);
815
+ continue;
816
+ }
817
+ if (packageDevDependencies !== void 0) {
818
+ version2 = packageDevDependencies[dep];
819
+ }
820
+ if (version2 !== void 0) {
821
+ devDependencies.add(`${dep}@${version2}`);
822
+ continue;
823
+ }
824
+ dependencies.add(dep);
825
+ }
826
+ }
827
+ return {
828
+ dependencies: Array.from(dependencies),
829
+ devDependencies: Array.from(devDependencies)
830
+ };
831
+ };
832
+ var languages = [typescript, svelte];
833
+
834
+ // src/utils/build.ts
835
+ var blockSchema = v2.object({
836
+ name: v2.string(),
837
+ category: v2.string(),
838
+ localDependencies: v2.array(v2.string()),
839
+ dependencies: v2.array(v2.string()),
840
+ devDependencies: v2.array(v2.string()),
841
+ tests: v2.boolean(),
842
+ /** Where to find the block relative to root */
843
+ directory: v2.string(),
844
+ subdirectory: v2.boolean(),
845
+ files: v2.array(v2.string())
846
+ });
847
+ var categorySchema = v2.object({
848
+ name: v2.string(),
849
+ blocks: v2.array(blockSchema)
850
+ });
851
+ var TEST_SUFFIXES = [".test.ts", "_test.ts", ".test.js", "_test.js"];
852
+ var isTestFile = (file) => TEST_SUFFIXES.find((suffix) => file.endsWith(suffix)) !== void 0;
853
+ var buildBlocksDirectory = (blocksPath, cwd) => {
854
+ let paths;
855
+ try {
856
+ paths = fs4.readdirSync(blocksPath);
857
+ } catch {
858
+ program.error(color2.red(`Couldn't read the ${color2.bold(blocksPath)} directory.`));
859
+ }
860
+ const categories = [];
861
+ for (const categoryPath of paths) {
862
+ const categoryDir = path3.join(blocksPath, categoryPath);
863
+ if (fs4.statSync(categoryDir).isFile()) continue;
864
+ const categoryName = path3.basename(categoryPath);
865
+ const category = {
866
+ name: categoryName,
867
+ blocks: []
868
+ };
869
+ const files = fs4.readdirSync(categoryDir);
870
+ for (const file of files) {
871
+ const blockDir = path3.join(categoryDir, file);
872
+ if (fs4.statSync(blockDir).isFile()) {
873
+ if (isTestFile(file)) continue;
874
+ const lang = languages.find((resolver) => resolver.matches(file));
875
+ if (!lang) {
876
+ console.warn(
877
+ `${WARN} Skipped \`${color2.bold(blockDir)}\` \`${color2.bold(
878
+ path3.parse(file).ext
879
+ )}\` files are not currently supported!`
880
+ );
881
+ continue;
882
+ }
883
+ const name2 = path3.parse(path3.basename(file)).name;
884
+ const testsPath = files.find(
885
+ (f) => TEST_SUFFIXES.find((suffix) => f === `${name2}${suffix}`)
886
+ );
887
+ const { dependencies, devDependencies, local } = lang.resolveDependencies(blockDir, categoryName, false).match(
888
+ (val) => val,
889
+ (err) => {
890
+ program.error(color2.red(err));
891
+ }
892
+ );
893
+ const block = {
894
+ name: name2,
895
+ directory: path3.relative(cwd, categoryDir),
896
+ category: categoryName,
897
+ tests: testsPath !== void 0,
898
+ subdirectory: false,
899
+ files: [file],
900
+ localDependencies: local,
901
+ dependencies,
902
+ devDependencies
903
+ };
904
+ if (testsPath !== void 0) {
905
+ block.files.push(testsPath);
906
+ }
907
+ category.blocks.push(block);
908
+ } else {
909
+ const blockName = file;
910
+ const blockFiles = fs4.readdirSync(blockDir);
911
+ const hasTests = blockFiles.findIndex((f) => isTestFile(f)) !== -1;
912
+ const localDepsSet = /* @__PURE__ */ new Set();
913
+ const depsSet = /* @__PURE__ */ new Set();
914
+ const devDepsSet = /* @__PURE__ */ new Set();
915
+ for (const f of blockFiles) {
916
+ if (isTestFile(f)) continue;
917
+ const lang = languages.find((resolver) => resolver.matches(f));
918
+ if (!lang) {
919
+ console.warn(
920
+ `${WARN} Skipped \`${color2.bold(path3.join(blockDir, f))}\` \`${color2.bold(
921
+ path3.parse(file).ext
922
+ )}\` files are not currently supported!`
923
+ );
924
+ continue;
925
+ }
926
+ const { local, dependencies, devDependencies } = lang.resolveDependencies(path3.join(blockDir, f), categoryName, true).match(
927
+ (val) => val,
928
+ (err) => {
929
+ program.error(color2.red(err));
930
+ }
931
+ );
932
+ for (const dep of local) {
933
+ localDepsSet.add(dep);
934
+ }
935
+ for (const dep of dependencies) {
936
+ depsSet.add(dep);
937
+ }
938
+ for (const dep of devDependencies) {
939
+ devDepsSet.add(dep);
940
+ }
941
+ }
942
+ const block = {
943
+ name: blockName,
944
+ directory: path3.relative(cwd, blockDir),
945
+ category: categoryName,
946
+ tests: hasTests,
947
+ subdirectory: true,
948
+ files: [...blockFiles],
949
+ localDependencies: Array.from(localDepsSet.keys()),
950
+ dependencies: Array.from(depsSet.keys()),
951
+ devDependencies: Array.from(devDepsSet.keys())
952
+ };
953
+ category.blocks.push(block);
954
+ }
955
+ }
956
+ categories.push(category);
957
+ }
958
+ return categories;
959
+ };
960
+
961
+ // src/utils/get-installed-blocks.ts
962
+ import fs5 from "node:fs";
963
+ import path4 from "node:path";
964
+ var getInstalledBlocks = (blocks, config) => {
965
+ const installedBlocks = [];
966
+ for (const [_, block] of blocks) {
967
+ const baseDir = path4.join(config.path, block.category);
968
+ let blockPath = path4.join(baseDir, block.files[0]);
969
+ if (block.subdirectory) {
970
+ blockPath = path4.join(baseDir, block.name);
971
+ }
972
+ if (fs5.existsSync(blockPath))
973
+ installedBlocks.push({
974
+ specifier: `${block.category}/${block.name}`,
975
+ path: blockPath
976
+ });
977
+ }
978
+ return installedBlocks;
979
+ };
980
+
981
+ // src/utils/get-watermark.ts
982
+ var getWatermark = (version2, repoUrl) => {
983
+ return ` jsrepo ${version2}
984
+ Installed from ${repoUrl}
985
+ ${(/* @__PURE__ */ new Date()).toLocaleDateString().replaceAll("/", "-")}`;
986
+ };
987
+
988
+ // src/utils/git-providers.ts
989
+ import { Octokit } from "octokit";
990
+ import * as v3 from "valibot";
991
+ var octokit = new Octokit({});
992
+ var github = {
993
+ name: () => "github",
994
+ resolveRaw: async (repoPath, resourcePath) => {
995
+ let info;
996
+ if (typeof repoPath === "string") {
997
+ info = await github.info(repoPath);
998
+ } else {
999
+ info = repoPath;
1000
+ }
1001
+ return new URL(
1002
+ resourcePath,
1003
+ `https://raw.githubusercontent.com/${info.owner}/${info.repoName}/refs/${info.refs}/${info.ref}/`
1004
+ );
1005
+ },
1006
+ info: async (repoPath) => {
1007
+ const repo = repoPath.replaceAll(/(https:\/\/github.com\/)|(github\/)/g, "");
1008
+ const [owner, repoName, ...rest] = repo.split("/");
1009
+ let ref = "main";
1010
+ if (rest[0] === "tree") {
1011
+ ref = rest[1];
1012
+ }
1013
+ let refs = "heads";
1014
+ if (ref !== "main") {
1015
+ try {
1016
+ const { data: tags } = await octokit.rest.git.listMatchingRefs({
1017
+ owner,
1018
+ repo: repoName,
1019
+ ref: "tags"
1020
+ });
1021
+ if (tags.some((tag) => tag.ref === `refs/tags/${ref}`)) {
1022
+ refs = "tags";
1023
+ }
1024
+ } catch {
1025
+ refs = "heads";
1026
+ }
1027
+ }
1028
+ return {
1029
+ refs,
1030
+ url: repoPath,
1031
+ name: github.name(),
1032
+ repoName,
1033
+ owner,
1034
+ ref,
1035
+ provider: github
1036
+ };
1037
+ },
1038
+ matches: (repoPath) => repoPath.toLowerCase().startsWith("https://github.com") || repoPath.toLowerCase().startsWith("github")
1039
+ };
1040
+ var getProviderInfo = async (repo) => {
1041
+ if (github.matches(repo)) {
1042
+ return Ok(await github.info(repo));
1043
+ }
1044
+ return Err("Only GitHub repositories are supported at this time!");
1045
+ };
1046
+ var getManifest = async (url) => {
1047
+ try {
1048
+ const response = await fetch(url);
1049
+ if (!response.ok) {
1050
+ return Err(
1051
+ `There was an error fetching the \`${OUTPUT_FILE}\` from the repository \`${url.href}\` make sure the target repository has a \`${OUTPUT_FILE}\` in its root?`
1052
+ );
1053
+ }
1054
+ const categories = v3.parse(v3.array(categorySchema), await response.json());
1055
+ return Ok(categories);
1056
+ } catch {
1057
+ return Err(
1058
+ `There was an error fetching the \`${OUTPUT_FILE}\` from the repository \`${url.href}\` make sure the target repository has a \`${OUTPUT_FILE}\` in its root?`
1059
+ );
1060
+ }
1061
+ };
1062
+
1063
+ // src/utils/prompts.ts
1064
+ import { intro, spinner } from "@clack/prompts";
1065
+ import color3 from "chalk";
1066
+
1067
+ // src/blocks/utilities/strip-ansi.ts
1068
+ import ansiRegex from "ansi-regex";
1069
+ var stripAsni = (str) => str.replace(ansiRegex(), "");
1070
+
1071
+ // src/blocks/utilities/pad.ts
1072
+ var leftPadMin = (str, length, padWith = " ") => {
1073
+ if (stripAsni(str).length > length)
1074
+ throw new Error("String length is greater than the length provided.");
1075
+ return padWith.repeat(length - stripAsni(str).length) + str;
1076
+ };
1077
+ var rightPad = (str, space, padWith = " ") => {
1078
+ return str + padWith.repeat(space);
1079
+ };
1080
+ var rightPadMin = (str, length, padWith = " ") => {
1081
+ if (stripAsni(str).length > length)
1082
+ throw new Error("String length is greater than the length provided.");
1083
+ return str + padWith.repeat(length - stripAsni(str).length);
1084
+ };
1085
+
1086
+ // src/utils/prompts.ts
1087
+ var VERTICAL_BORDER = color3.gray("\u2502");
1088
+ var HORIZONTAL_BORDER = color3.gray("\u2500");
1089
+ var TOP_RIGHT_CORNER = color3.gray("\u2510");
1090
+ var BOTTOM_RIGHT_CORNER = color3.gray("\u2518");
1091
+ var JUNCTION_RIGHT = color3.gray("\u251C");
1092
+ var runTasks = async (tasks, { verbose = false }) => {
1093
+ const loading = spinner();
1094
+ for (const task of tasks) {
1095
+ if (!verbose) loading.start(task.loadingMessage);
1096
+ try {
1097
+ await task.run();
1098
+ } catch (err) {
1099
+ console.error(err);
1100
+ }
1101
+ if (!verbose) loading.stop(task.completedMessage);
1102
+ }
1103
+ };
1104
+ var nextSteps = (steps) => {
1105
+ let max = 20;
1106
+ steps.map((val) => {
1107
+ const reset = rightPad(stripAsni(val), 4);
1108
+ if (reset.length > max) max = reset.length;
1109
+ });
1110
+ const NEXT_STEPS = "Next Steps";
1111
+ let result = `${VERTICAL_BORDER}
1112
+ `;
1113
+ result += `${JUNCTION_RIGHT} ${NEXT_STEPS} ${HORIZONTAL_BORDER.repeat(
1114
+ max - NEXT_STEPS.length - 1
1115
+ )}${TOP_RIGHT_CORNER}
1116
+ `;
1117
+ result += `${VERTICAL_BORDER} ${" ".repeat(max)} ${VERTICAL_BORDER}
1118
+ `;
1119
+ steps.map((step) => {
1120
+ result += `${VERTICAL_BORDER} ${rightPadMin(step, max - 1)} ${VERTICAL_BORDER}
1121
+ `;
1122
+ });
1123
+ result += `${VERTICAL_BORDER} ${" ".repeat(max)} ${VERTICAL_BORDER}
1124
+ `;
1125
+ result += `${JUNCTION_RIGHT}${HORIZONTAL_BORDER.repeat(max + 2)}${BOTTOM_RIGHT_CORNER}
1126
+ `;
1127
+ return result;
1128
+ };
1129
+ var _intro = (version2) => intro(`${color3.bgHex("#f7df1e").black(" jsrepo ")}${color3.gray(` v${version2} `)}`);
1130
+
1131
+ // src/commands/add.ts
1132
+ var schema2 = v4.object({
1133
+ yes: v4.boolean(),
1134
+ verbose: v4.boolean(),
1135
+ repo: v4.optional(v4.string()),
1136
+ allow: v4.boolean()
1137
+ });
1138
+ var add = new Command("add").argument("[blocks...]", "Whichever block you want to add to your project.").option("-y, --yes", "Add and install any required dependencies.", false).option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("--repo <repo>", "Repository to download the blocks from").option("--verbose", "Include debug logs.", false).action(async (blockNames, opts) => {
1139
+ const options = v4.parse(schema2, opts);
1140
+ await _add(blockNames, options);
1141
+ });
1142
+ var _add = async (blockNames, options) => {
1143
+ _intro(context.package.version);
1144
+ const verbose = (msg) => {
1145
+ if (options.verbose) {
1146
+ console.info(`${INFO} ${msg}`);
1147
+ }
1148
+ };
1149
+ verbose(`Attempting to add ${JSON.stringify(blockNames)}`);
1150
+ const loading = spinner2();
1151
+ const config = getConfig().match(
1152
+ (val) => val,
1153
+ (err) => program2.error(color4.red(err))
1154
+ );
1155
+ const blocksMap = /* @__PURE__ */ new Map();
1156
+ let repoPaths = config.repos;
1157
+ if (options.repo) repoPaths = [options.repo];
1158
+ if (!options.allow && options.repo) {
1159
+ const result = await confirm({
1160
+ message: `Allow ${color4.cyan("jsrepo")} to download and run code from ${color4.cyan(options.repo)}?`,
1161
+ initialValue: true
1162
+ });
1163
+ if (isCancel(result) || !result) {
1164
+ cancel("Canceled!");
1165
+ process.exit(0);
1166
+ }
1167
+ }
1168
+ verbose(`Fetching blocks from ${color4.cyan(repoPaths.join(", "))}`);
1169
+ if (!options.verbose) loading.start(`Fetching blocks from ${color4.cyan(repoPaths.join(", "))}`);
1170
+ for (const repo of repoPaths) {
1171
+ const providerInfo = (await getProviderInfo(repo)).match(
1172
+ (info) => info,
1173
+ (err) => {
1174
+ loading.stop(`Failed fetching blocks from ${color4.cyan(repo)}`);
1175
+ program2.error(color4.red(err));
1176
+ }
1177
+ );
1178
+ const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
1179
+ verbose(`Got info for provider ${color4.cyan(providerInfo.name)}`);
1180
+ const categories = (await getManifest(manifestUrl)).match(
1181
+ (val) => val,
1182
+ (err) => {
1183
+ loading.stop(`Failed fetching blocks from ${color4.cyan(repo)}`);
1184
+ program2.error(color4.red(err));
1185
+ }
1186
+ );
1187
+ for (const category of categories) {
1188
+ for (const block of category.blocks) {
1189
+ blocksMap.set(
1190
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
1191
+ {
1192
+ ...block,
1193
+ sourceRepo: providerInfo
1194
+ }
1195
+ );
1196
+ }
1197
+ }
1198
+ }
1199
+ verbose(`Retrieved blocks from ${color4.cyan(repoPaths.join(", "))}`);
1200
+ if (!options.verbose) loading.stop(`Retrieved blocks from ${color4.cyan(repoPaths.join(", "))}`);
1201
+ const installedBlocks = getInstalledBlocks(blocksMap, config).map((val) => val.specifier);
1202
+ let installingBlockNames = blockNames;
1203
+ if (installingBlockNames.length === 0) {
1204
+ const promptResult = await multiselect({
1205
+ message: "Select which blocks to add.",
1206
+ options: Array.from(blocksMap.entries()).map(([key, value]) => {
1207
+ const shortName = `${value.category}/${value.name}`;
1208
+ const blockExists = installedBlocks.findIndex((block) => block === shortName) !== -1;
1209
+ let label;
1210
+ if (repoPaths.length > 1) {
1211
+ label = `${color4.cyan(
1212
+ `${value.sourceRepo.name}/${value.sourceRepo.owner}/${value.sourceRepo.repoName}/${value.category}`
1213
+ )}/${value.name}`;
1214
+ } else {
1215
+ label = `${color4.cyan(value.category)}/${value.name}`;
1216
+ }
1217
+ return {
1218
+ label: blockExists ? color4.gray(label) : label,
1219
+ value: key,
1220
+ // show hint for `Installed` if block is already installed
1221
+ hint: blockExists ? "Installed" : void 0
1222
+ };
1223
+ }),
1224
+ required: true
1225
+ });
1226
+ if (isCancel(promptResult)) {
1227
+ cancel("Canceled!");
1228
+ process.exit(0);
1229
+ }
1230
+ installingBlockNames = promptResult;
1231
+ }
1232
+ verbose(`Installing blocks ${color4.cyan(installingBlockNames.join(", "))}`);
1233
+ if (options.verbose) console.log("Blocks map: ", blocksMap);
1234
+ const installingBlocks = await getBlocks(installingBlockNames, blocksMap, repoPaths);
1235
+ const pm = (await detect({ cwd: process.cwd() }))?.agent ?? "npm";
1236
+ const tasks = [];
1237
+ const devDeps = /* @__PURE__ */ new Set();
1238
+ const deps = /* @__PURE__ */ new Set();
1239
+ for (const { name: specifier, block } of installingBlocks) {
1240
+ const watermark = getWatermark(context.package.version, block.sourceRepo.url);
1241
+ const providerInfo = block.sourceRepo;
1242
+ verbose(`Attempting to add ${specifier}`);
1243
+ const directory = path5.join(config.path, block.category);
1244
+ verbose(`Creating directory ${color4.bold(directory)}`);
1245
+ const blockExists = !block.subdirectory && fs6.existsSync(path5.join(directory, block.files[0])) || block.subdirectory && fs6.existsSync(path5.join(directory, block.name));
1246
+ if (blockExists && !options.yes) {
1247
+ const result = await confirm({
1248
+ message: `${color4.bold(block.name)} already exists in your project would you like to overwrite it?`,
1249
+ initialValue: false
1250
+ });
1251
+ if (isCancel(result) || !result) {
1252
+ cancel("Canceled!");
1253
+ process.exit(0);
1254
+ }
1255
+ }
1256
+ tasks.push({
1257
+ loadingMessage: `Adding ${specifier}`,
1258
+ completedMessage: `Added ${specifier}`,
1259
+ run: async () => {
1260
+ fs6.mkdirSync(directory, { recursive: true });
1261
+ const files = [];
1262
+ const getSourceFile = async (filePath) => {
1263
+ const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, filePath);
1264
+ const response = await fetch(rawUrl);
1265
+ if (!response.ok) {
1266
+ loading.stop(color4.red(`Error fetching ${color4.bold(rawUrl.href)}`));
1267
+ program2.error(color4.red(`There was an error trying to get ${specifier}`));
1268
+ }
1269
+ return await response.text();
1270
+ };
1271
+ for (const sourceFile of block.files) {
1272
+ if (!config.includeTests && isTestFile(sourceFile)) continue;
1273
+ const sourcePath = path5.join(block.directory, sourceFile);
1274
+ let destPath;
1275
+ if (block.subdirectory) {
1276
+ destPath = path5.join(config.path, block.category, block.name, sourceFile);
1277
+ } else {
1278
+ destPath = path5.join(config.path, block.category, sourceFile);
1279
+ }
1280
+ const content = await getSourceFile(sourcePath);
1281
+ fs6.mkdirSync(destPath.slice(0, destPath.length - sourceFile.length), {
1282
+ recursive: true
1283
+ });
1284
+ files.push({ content, destPath });
1285
+ }
1286
+ for (const file of files) {
1287
+ let content = file.content;
1288
+ if (config.watermark) {
1289
+ const lang = languages.find((lang2) => lang2.matches(file.destPath));
1290
+ if (lang) {
1291
+ const comment = lang.comment(watermark);
1292
+ content = `${comment}
1293
+
1294
+ ${content}`;
1295
+ }
1296
+ }
1297
+ fs6.writeFileSync(file.destPath, content);
1298
+ }
1299
+ if (config.includeTests) {
1300
+ verbose("Trying to include tests");
1301
+ const { devDependencies } = JSON.parse(
1302
+ fs6.readFileSync("package.json").toString()
1303
+ );
1304
+ if (devDependencies.vitest === void 0) {
1305
+ devDeps.add("vitest");
1306
+ }
1307
+ }
1308
+ for (const dep of block.devDependencies) {
1309
+ devDeps.add(dep);
1310
+ }
1311
+ for (const dep of block.dependencies) {
1312
+ deps.add(dep);
1313
+ }
1314
+ }
1315
+ });
1316
+ }
1317
+ await runTasks(tasks, { verbose: options.verbose });
1318
+ const installDependencies = async (deps2, dev) => {
1319
+ if (!options.verbose) loading.start(`Installing dependencies with ${color4.cyan(pm)}`);
1320
+ let add2;
1321
+ if (dev) {
1322
+ add2 = resolveCommand(pm, "install", [...deps2, "-D"]);
1323
+ } else {
1324
+ add2 = resolveCommand(pm, "install", [...deps2]);
1325
+ }
1326
+ if (add2 == null) {
1327
+ program2.error(color4.red(`Could not resolve add command for '${pm}'.`));
1328
+ }
1329
+ try {
1330
+ await execa(add2.command, [...add2.args], { cwd: process.cwd() });
1331
+ } catch {
1332
+ program2.error(
1333
+ color4.red(
1334
+ `Failed to install ${color4.bold("vitest")}! Failed while running '${color4.bold(
1335
+ `${add2.command} ${add2.args.join(" ")}`
1336
+ )}'`
1337
+ )
1338
+ );
1339
+ }
1340
+ if (!options.verbose) loading.stop(`Installed ${color4.cyan(deps2.join(", "))}`);
1341
+ };
1342
+ const hasDependencies = deps.size > 0 || devDeps.size > 0;
1343
+ if (hasDependencies) {
1344
+ let install = options.yes;
1345
+ if (!options.yes) {
1346
+ const result = await confirm({
1347
+ message: "Would you like to install dependencies?",
1348
+ initialValue: true
1349
+ });
1350
+ if (isCancel(result)) {
1351
+ cancel("Canceled!");
1352
+ process.exit(0);
1353
+ }
1354
+ install = result;
1355
+ }
1356
+ if (install) {
1357
+ if (deps.size > 0) {
1358
+ await installDependencies(Array.from(deps), false);
1359
+ }
1360
+ if (devDeps.size > 0) {
1361
+ await installDependencies(Array.from(devDeps), true);
1362
+ }
1363
+ }
1364
+ let steps = [];
1365
+ if (!install) {
1366
+ if (deps.size > 0) {
1367
+ const cmd = resolveCommand(pm, "install", [...deps]);
1368
+ steps.push(
1369
+ `Install dependencies \`${color4.cyan(`${cmd?.command} ${cmd?.args.join(" ")}`)}\``
1370
+ );
1371
+ }
1372
+ if (devDeps.size > 0) {
1373
+ const cmd = resolveCommand(pm, "install", [...devDeps, "-D"]);
1374
+ steps.push(
1375
+ `Install dev dependencies \`${color4.cyan(`${cmd?.command} ${cmd?.args.join(" ")}`)}\``
1376
+ );
1377
+ }
1378
+ }
1379
+ steps = steps.map((step, i) => `${i + 1}. ${step}`);
1380
+ if (!install) {
1381
+ steps.push("");
1382
+ }
1383
+ steps.push(`Import the blocks from \`${color4.cyan(config.path)}\``);
1384
+ const next = nextSteps(steps);
1385
+ process.stdout.write(next);
1386
+ }
1387
+ outro(color4.green("All done!"));
1388
+ };
1389
+ var getBlocks = async (blockSpecifiers, blocksMap, repoPaths) => {
1390
+ const blocks = /* @__PURE__ */ new Map();
1391
+ for (const blockSpecifier of blockSpecifiers) {
1392
+ let block = void 0;
1393
+ if (!blockSpecifier.startsWith("github")) {
1394
+ if (repoPaths.length === 0) {
1395
+ program2.error(
1396
+ color4.red(
1397
+ `If your config doesn't repos then you must provide the repo in the block specifier ex: \`${color4.bold(
1398
+ `github/<owner>/<name>/${blockSpecifier}`
1399
+ )}\`!`
1400
+ )
1401
+ );
1402
+ }
1403
+ for (const repo of repoPaths) {
1404
+ const providerInfo = (await getProviderInfo(repo)).unwrap();
1405
+ const tempBlock = blocksMap.get(
1406
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${blockSpecifier}`
1407
+ );
1408
+ if (tempBlock === void 0) continue;
1409
+ block = tempBlock;
1410
+ break;
1411
+ }
1412
+ } else {
1413
+ if (repoPaths.length === 0) {
1414
+ const [providerName, owner, repoName, ...rest] = blockSpecifier.split("/");
1415
+ let repo;
1416
+ if (rest.length > 2) {
1417
+ repo = `${providerName}/${owner}/${repoName}/${rest.join("/")}`;
1418
+ } else {
1419
+ repo = `${providerName}/${owner}/${repoName}`;
1420
+ }
1421
+ const providerInfo = (await getProviderInfo(repo)).match(
1422
+ (val) => val,
1423
+ (err) => program2.error(color4.red(err))
1424
+ );
1425
+ const manifestUrl = await providerInfo.provider.resolveRaw(
1426
+ providerInfo,
1427
+ OUTPUT_FILE
1428
+ );
1429
+ const categories = (await getManifest(manifestUrl)).match(
1430
+ (val) => val,
1431
+ (err) => program2.error(color4.red(err))
1432
+ );
1433
+ for (const category of categories) {
1434
+ for (const block2 of category.blocks) {
1435
+ blocksMap.set(
1436
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block2.name}`,
1437
+ {
1438
+ ...block2,
1439
+ sourceRepo: providerInfo
1440
+ }
1441
+ );
1442
+ }
1443
+ }
1444
+ }
1445
+ block = blocksMap.get(blockSpecifier);
1446
+ }
1447
+ if (!block) {
1448
+ program2.error(
1449
+ color4.red(`Invalid block! ${color4.bold(blockSpecifier)} does not exist!`)
1450
+ );
1451
+ }
1452
+ blocks.set(blockSpecifier, { name: blockSpecifier, subDependency: false, block });
1453
+ if (block.localDependencies && block.localDependencies.length > 0) {
1454
+ const subDeps = await getBlocks(
1455
+ block.localDependencies.filter((dep) => blocks.has(dep)),
1456
+ blocksMap,
1457
+ repoPaths
1458
+ );
1459
+ for (const dep of subDeps) {
1460
+ blocks.set(dep.name, dep);
1461
+ }
1462
+ }
1463
+ }
1464
+ return mapToArray(blocks, (_, val) => val);
1465
+ };
1466
+
1467
+ // src/commands/build.ts
1468
+ import fs7 from "node:fs";
1469
+ import path6 from "node:path";
1470
+ import { outro as outro2, spinner as spinner3 } from "@clack/prompts";
1471
+ import color5 from "chalk";
1472
+ import { Command as Command2 } from "commander";
1473
+ import * as v5 from "valibot";
1474
+ var schema3 = v5.object({
1475
+ verbose: v5.boolean(),
1476
+ output: v5.boolean(),
1477
+ dirs: v5.array(v5.string()),
1478
+ cwd: v5.string()
1479
+ });
1480
+ var build = new Command2("build").description(`Builds the provided --dirs in the project root into a \`${OUTPUT_FILE}\` file.`).option("--dirs [dirs...]", "The directories containing the blocks.", ["./blocks"]).option("--no-output", `Do not output a \`${OUTPUT_FILE}\` file.`).option("--verbose", "Include debug logs.", false).option("--cwd <cwd>", "The current working directory", process.cwd()).action(async (opts) => {
1481
+ const options = v5.parse(schema3, opts);
1482
+ await _build(options);
1483
+ });
1484
+ var _build = async (options) => {
1485
+ _intro(context.package.version);
1486
+ const loading = spinner3();
1487
+ const categories = [];
1488
+ const outFile = path6.join(options.cwd, OUTPUT_FILE);
1489
+ for (const dir of options.dirs) {
1490
+ const dirPath = path6.join(options.cwd, dir);
1491
+ loading.start(`Building ${color5.cyan(dirPath)}`);
1492
+ if (options.output && fs7.existsSync(outFile)) fs7.rmSync(outFile);
1493
+ categories.push(...buildBlocksDirectory(dirPath, options.cwd));
1494
+ loading.stop(`Built ${color5.cyan(dirPath)}`);
1495
+ }
1496
+ const categoriesMap = /* @__PURE__ */ new Map();
1497
+ for (const category of categories) {
1498
+ const cat = categoriesMap.get(category.name);
1499
+ if (!cat) {
1500
+ categoriesMap.set(category.name, category);
1501
+ continue;
1502
+ }
1503
+ cat.blocks = [...cat.blocks, ...category.blocks];
1504
+ categoriesMap.set(cat.name, cat);
1505
+ }
1506
+ if (options.output) {
1507
+ fs7.writeFileSync(outFile, JSON.stringify(categories, null, " "));
1508
+ } else {
1509
+ loading.stop("Built successfully!");
1510
+ }
1511
+ outro2(color5.green("All done!"));
1512
+ };
1513
+
1514
+ // src/commands/diff.ts
1515
+ import fs8 from "node:fs";
1516
+ import path7 from "node:path";
1517
+ import { cancel as cancel2, confirm as confirm2, isCancel as isCancel2, outro as outro3, spinner as spinner4 } from "@clack/prompts";
1518
+ import color7 from "chalk";
1519
+ import { Command as Command3, program as program3 } from "commander";
1520
+ import { diffLines } from "diff";
1521
+ import * as v6 from "valibot";
1522
+
1523
+ // src/utils/diff.ts
1524
+ import color6 from "chalk";
1525
+ import { diffChars } from "diff";
1526
+
1527
+ // src/blocks/utilities/array-sum.ts
1528
+ var arraySum = (arr, fn) => {
1529
+ let total = 0;
1530
+ for (const item of arr) {
1531
+ total = total + fn(item);
1532
+ }
1533
+ return total;
1534
+ };
1535
+
1536
+ // src/blocks/utilities/lines.ts
1537
+ import os from "node:os";
1538
+ var NEW_LINE_REGEX = /\n|\r\n/g;
1539
+ var get = (str) => str.split(NEW_LINE_REGEX);
1540
+ var join = (lines, { lineNumbers = false, prefix } = {}) => {
1541
+ let transformed = lines;
1542
+ if (lineNumbers) {
1543
+ const length = lines.length.toString().length + 1;
1544
+ transformed = transformed.map((line, i) => `${leftPadMin(`${i + 1}`, length)} ${line}`);
1545
+ }
1546
+ if (prefix !== void 0) {
1547
+ transformed = transformed.map((line, i) => `${prefix(i, lines.length)}${line}`);
1548
+ }
1549
+ return transformed.join(os.EOL);
1550
+ };
1551
+
1552
+ // src/utils/diff.ts
1553
+ var formatDiff = ({
1554
+ from,
1555
+ to,
1556
+ changes,
1557
+ expand = false,
1558
+ maxUnchanged = 5,
1559
+ colorRemoved = color6.red,
1560
+ colorAdded = color6.green,
1561
+ prefix,
1562
+ onUnchanged,
1563
+ intro: intro2
1564
+ }) => {
1565
+ let result = "";
1566
+ const length = arraySum(changes, (change) => change.count ?? 0).toString().length + 1;
1567
+ let lineOffset = 0;
1568
+ if (changes.length === 1 && !changes[0].added && !changes[0].removed) {
1569
+ return onUnchanged({
1570
+ from,
1571
+ to,
1572
+ changes,
1573
+ expand,
1574
+ maxUnchanged,
1575
+ colorAdded,
1576
+ colorRemoved,
1577
+ prefix,
1578
+ onUnchanged,
1579
+ intro: intro2
1580
+ });
1581
+ }
1582
+ result += intro2({
1583
+ from,
1584
+ to,
1585
+ changes,
1586
+ expand,
1587
+ maxUnchanged,
1588
+ colorAdded,
1589
+ colorRemoved,
1590
+ prefix,
1591
+ onUnchanged,
1592
+ intro: intro2
1593
+ });
1594
+ const linePrefix = (line) => color6.gray(`${prefix?.() ?? ""}${leftPadMin(`${line + 1 + lineOffset} `, length)} `);
1595
+ for (let i = 0; i < changes.length; i++) {
1596
+ const change = changes[i];
1597
+ const hasPreviousChange = changes[i - 1]?.added || changes[i - 1]?.removed;
1598
+ const hasNextChange = changes[i + 1]?.added || changes[i + 1]?.removed;
1599
+ if (!change.added && !change.removed) {
1600
+ if (!expand && change.count !== void 0 && change.count > maxUnchanged) {
1601
+ const prevLineOffset = lineOffset;
1602
+ const ls = get(change.value.trimEnd());
1603
+ let shownLines = 0;
1604
+ if (hasNextChange) shownLines += maxUnchanged;
1605
+ if (hasPreviousChange) shownLines += maxUnchanged;
1606
+ if (shownLines >= ls.length) {
1607
+ result += `${join(ls, {
1608
+ prefix: linePrefix
1609
+ })}
1610
+ `;
1611
+ lineOffset += ls.length;
1612
+ continue;
1613
+ }
1614
+ if (hasPreviousChange) {
1615
+ result += `${join(ls.slice(0, maxUnchanged), {
1616
+ prefix: linePrefix
1617
+ })}
1618
+ `;
1619
+ }
1620
+ if (ls.length > shownLines) {
1621
+ const count = ls.length - shownLines;
1622
+ result += `${join(
1623
+ get(
1624
+ color6.gray(
1625
+ `+ ${count} more unchanged (${color6.italic("-E to expand")})`
1626
+ )
1627
+ ),
1628
+ {
1629
+ prefix: () => `${prefix?.() ?? ""}${leftPadMin(" ", length)} `
1630
+ }
1631
+ )}
1632
+ `;
1633
+ }
1634
+ if (hasNextChange) {
1635
+ lineOffset = lineOffset + ls.length - maxUnchanged;
1636
+ result += `${join(ls.slice(ls.length - maxUnchanged), {
1637
+ prefix: linePrefix
1638
+ })}
1639
+ `;
1640
+ }
1641
+ lineOffset = prevLineOffset + change.count;
1642
+ continue;
1643
+ }
1644
+ result += `${join(get(change.value.trimEnd()), {
1645
+ prefix: linePrefix
1646
+ })}
1647
+ `;
1648
+ lineOffset += change.count ?? 0;
1649
+ continue;
1650
+ }
1651
+ const colorChange = (change2) => {
1652
+ if (change2.added) {
1653
+ return colorAdded(change2.value.trimEnd());
1654
+ }
1655
+ if (change2.removed) {
1656
+ return colorRemoved(change2.value.trimEnd());
1657
+ }
1658
+ return change2.value;
1659
+ };
1660
+ if (change.removed && change.count === 1) {
1661
+ const diffedChars = diffChars(change.value, changes[i + 1].value);
1662
+ const sentence = diffedChars.map((chg) => colorChange(chg)).join("");
1663
+ result += `${linePrefix(0)}${sentence}`;
1664
+ lineOffset += 1;
1665
+ i++;
1666
+ } else {
1667
+ result += `${join(get(colorChange(change)), {
1668
+ prefix: linePrefix
1669
+ })}
1670
+ `;
1671
+ if (!change.removed) {
1672
+ lineOffset += change.count ?? 0;
1673
+ }
1674
+ }
1675
+ }
1676
+ return result;
1677
+ };
1678
+
1679
+ // src/commands/diff.ts
1680
+ var L = color7.gray("\u2502");
1681
+ var schema4 = v6.object({
1682
+ allow: v6.boolean(),
1683
+ expand: v6.boolean(),
1684
+ maxUnchanged: v6.number(),
1685
+ repo: v6.optional(v6.string())
1686
+ });
1687
+ var diff = new Command3("diff").description("Compares local blocks to the blocks in the provided repository.").option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("-E, --expand", "Expands the diff so you see everything.", false).option("--repo <repo>", "Repository to download the blocks from.").option(
1688
+ "--max-unchanged <number>",
1689
+ "Maximum unchanged lines that will show without being collapsed.",
1690
+ (val) => Number.parseInt(val),
1691
+ // this is such a dumb api thing
1692
+ 3
1693
+ ).action(async (opts) => {
1694
+ const options = v6.parse(schema4, opts);
1695
+ await _diff(options);
1696
+ });
1697
+ var _diff = async (options) => {
1698
+ _intro(context.package.version);
1699
+ const loading = spinner4();
1700
+ const config = getConfig().match(
1701
+ (val) => val,
1702
+ (err) => program3.error(color7.red(err))
1703
+ );
1704
+ const blocksMap = /* @__PURE__ */ new Map();
1705
+ let repoPaths = config.repos;
1706
+ if (options.repo) repoPaths = [options.repo];
1707
+ if (!options.allow && options.repo) {
1708
+ const result = await confirm2({
1709
+ message: `Allow ${color7.cyan("jsrepo")} to download and run code from ${color7.cyan(options.repo)}?`,
1710
+ initialValue: true
1711
+ });
1712
+ if (isCancel2(result) || !result) {
1713
+ cancel2("Canceled!");
1714
+ process.exit(0);
1715
+ }
1716
+ }
1717
+ loading.start(`Fetching blocks from ${color7.cyan(repoPaths.join(", "))}`);
1718
+ for (const repo of repoPaths) {
1719
+ const providerInfo = (await getProviderInfo(repo)).match(
1720
+ (info) => info,
1721
+ (err) => program3.error(color7.red(err))
1722
+ );
1723
+ const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
1724
+ const categories = (await getManifest(manifestUrl)).match(
1725
+ (val) => val,
1726
+ (err) => program3.error(color7.red(err))
1727
+ );
1728
+ for (const category of categories) {
1729
+ for (const block of category.blocks) {
1730
+ blocksMap.set(
1731
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
1732
+ {
1733
+ ...block,
1734
+ sourceRepo: providerInfo
1735
+ }
1736
+ );
1737
+ }
1738
+ }
1739
+ }
1740
+ loading.stop(`Retrieved blocks from ${color7.cyan(repoPaths.join(", "))}`);
1741
+ const installedBlocks = getInstalledBlocks(blocksMap, config);
1742
+ for (const blockSpecifier of installedBlocks) {
1743
+ let found = false;
1744
+ for (const repo of repoPaths) {
1745
+ const providerInfo = (await getProviderInfo(repo)).unwrap();
1746
+ const fullSpecifier = `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${blockSpecifier.specifier}`;
1747
+ const block = blocksMap.get(fullSpecifier);
1748
+ if (block === void 0) continue;
1749
+ found = true;
1750
+ process.stdout.write(`${L}
1751
+ `);
1752
+ process.stdout.write(`${L} ${fullSpecifier}
1753
+ `);
1754
+ fullSpecifier;
1755
+ for (const file of block.files) {
1756
+ if (!config.includeTests && isTestFile(file)) continue;
1757
+ process.stdout.write(`${L}
1758
+ `);
1759
+ const sourcePath = path7.join(block.directory, file);
1760
+ const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, sourcePath);
1761
+ const response = await fetch(rawUrl);
1762
+ if (!response.ok) {
1763
+ program3.error(color7.red(`There was an error trying to get ${fullSpecifier}`));
1764
+ }
1765
+ let remoteContent = await response.text();
1766
+ let localPath = path7.join(config.path, block.category, file);
1767
+ if (block.subdirectory) {
1768
+ localPath = path7.join(config.path, block.category, block.name, file);
1769
+ }
1770
+ let fileContent = "";
1771
+ if (fs8.existsSync(localPath)) {
1772
+ fileContent = fs8.readFileSync(localPath).toString();
1773
+ }
1774
+ if (config.watermark) {
1775
+ const lang = languages.find((lang2) => lang2.matches(sourcePath));
1776
+ if (lang) {
1777
+ const watermark = getWatermark(context.package.version, repo);
1778
+ const comment = lang.comment(watermark);
1779
+ remoteContent = `${comment}
1780
+
1781
+ ${remoteContent}`;
1782
+ }
1783
+ }
1784
+ const changes = diffLines(fileContent, remoteContent);
1785
+ const from = path7.join(
1786
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}`,
1787
+ sourcePath
1788
+ ).replaceAll("\\", "/");
1789
+ const to = localPath.replaceAll("\\", "/");
1790
+ const formattedDiff = formatDiff({
1791
+ from,
1792
+ to,
1793
+ changes,
1794
+ expand: options.expand,
1795
+ maxUnchanged: options.maxUnchanged,
1796
+ colorAdded: color7.greenBright,
1797
+ colorRemoved: color7.redBright,
1798
+ prefix: () => `${L} `,
1799
+ onUnchanged: ({ from: from2, to: to2, prefix }) => `${prefix?.() ?? ""}${color7.cyan(from2)} \u2192 ${color7.gray(to2)} ${color7.gray("(unchanged)")}
1800
+ `,
1801
+ intro: ({ from: from2, to: to2, changes: changes2, prefix }) => {
1802
+ const totalChanges = changes2.filter((a) => a.added).length;
1803
+ return `${prefix?.() ?? ""}${color7.cyan(from2)} \u2192 ${color7.gray(to2)} (${totalChanges} change${totalChanges === 1 ? "" : "s"})
1804
+ ${prefix?.() ?? ""}
1805
+ `;
1806
+ }
1807
+ });
1808
+ process.stdout.write(formattedDiff);
1809
+ }
1810
+ break;
1811
+ }
1812
+ if (!found) {
1813
+ program3.error(
1814
+ color7.red(`Invalid block! ${color7.bold(blockSpecifier)} does not exist!`)
1815
+ );
1816
+ }
1817
+ }
1818
+ outro3(color7.green("All done!"));
1819
+ };
1820
+
1821
+ // src/commands/init.ts
1822
+ import fs9 from "node:fs";
1823
+ import { cancel as cancel3, confirm as confirm3, isCancel as isCancel3, outro as outro4, spinner as spinner5, text } from "@clack/prompts";
1824
+ import color8 from "chalk";
1825
+ import { Command as Command4 } from "commander";
1826
+ import * as v7 from "valibot";
1827
+ var schema5 = v7.object({
1828
+ path: v7.optional(v7.string()),
1829
+ tests: v7.optional(v7.boolean()),
1830
+ repos: v7.optional(v7.array(v7.string())),
1831
+ watermark: v7.boolean()
1832
+ });
1833
+ var init = new Command4("init").description("Initializes the configuration file").option("--path <path>", "Path to install the blocks").option("--repos [repos...]", "Repository to install the blocks from").option(
1834
+ "--no-watermark",
1835
+ "Will not add a watermark to each file upon adding it to your project."
1836
+ ).option("--tests", "Will include tests along with the functions.").action(async (opts) => {
1837
+ const options = v7.parse(schema5, opts);
1838
+ await _init(options);
1839
+ });
1840
+ var _init = async (options) => {
1841
+ _intro(context.package.version);
1842
+ const initialConfig = getConfig();
1843
+ const loading = spinner5();
1844
+ if (!options.path) {
1845
+ const result = await text({
1846
+ message: "Where should we add the blocks?",
1847
+ validate(value) {
1848
+ if (value.trim() === "") return "Please provide a value";
1849
+ },
1850
+ initialValue: initialConfig.isOk() ? initialConfig.unwrap().path : "src/blocks"
1851
+ });
1852
+ if (isCancel3(result)) {
1853
+ cancel3("Canceled!");
1854
+ process.exit(0);
1855
+ }
1856
+ options.path = result;
1857
+ }
1858
+ if (!options.repos) {
1859
+ options.repos = initialConfig.isOk() ? initialConfig.unwrap().repos : [];
1860
+ while (true) {
1861
+ if (options.repos.length > 0) {
1862
+ const confirmResult = await confirm3({
1863
+ message: "Add another repo?",
1864
+ initialValue: false
1865
+ });
1866
+ if (isCancel3(confirmResult)) {
1867
+ cancel3("Canceled!");
1868
+ process.exit(0);
1869
+ }
1870
+ if (!confirmResult) break;
1871
+ }
1872
+ const result = await text({
1873
+ message: "Where should we download the blocks from?",
1874
+ placeholder: "github/ieedan/std",
1875
+ validate: (val) => {
1876
+ if (!val.startsWith("https://github.com") && !val.startsWith("github/")) {
1877
+ return `Must be a ${color8.bold("GitHub")} repository!`;
1878
+ }
1879
+ }
1880
+ });
1881
+ if (isCancel3(result)) {
1882
+ cancel3("Canceled!");
1883
+ process.exit(0);
1884
+ }
1885
+ options.repos.push(result);
1886
+ }
1887
+ }
1888
+ const config = {
1889
+ $schema: `https://unpkg.com/jsrepo@${context.package.version}/schema.json`,
1890
+ repos: options.repos,
1891
+ path: options.path,
1892
+ includeTests: initialConfig.isOk() && options.tests === void 0 ? initialConfig.unwrap().includeTests : options.tests ?? false,
1893
+ watermark: options.watermark
1894
+ };
1895
+ loading.start(`Writing config to \`${CONFIG_NAME}\``);
1896
+ fs9.writeFileSync(CONFIG_NAME, `${JSON.stringify(config, null, " ")}
1897
+ `);
1898
+ fs9.mkdirSync(config.path, { recursive: true });
1899
+ loading.stop(`Wrote config to \`${CONFIG_NAME}\`.`);
1900
+ outro4(color8.green("All done!"));
1901
+ };
1902
+
1903
+ // src/commands/test.ts
1904
+ import fs10 from "node:fs";
1905
+ import path8 from "node:path";
1906
+ import { cancel as cancel4, confirm as confirm4, isCancel as isCancel4, outro as outro5, spinner as spinner6 } from "@clack/prompts";
1907
+ import color9 from "chalk";
1908
+ import { Argument, Command as Command5, program as program4 } from "commander";
1909
+ import { execa as execa2 } from "execa";
1910
+ import { resolveCommand as resolveCommand2 } from "package-manager-detector/commands";
1911
+ import { detect as detect2 } from "package-manager-detector/detect";
1912
+ import { Project as Project2 } from "ts-morph";
1913
+ import * as v8 from "valibot";
1914
+ var schema6 = v8.object({
1915
+ debug: v8.boolean(),
1916
+ verbose: v8.boolean(),
1917
+ repo: v8.optional(v8.string()),
1918
+ allow: v8.boolean()
1919
+ });
1920
+ var test = new Command5("test").description("Tests blocks against most recent tests").addArgument(new Argument("[blocks...]", "Whichever blocks you want to test.").default([])).option("--verbose", "Include debug logs.", false).option("-A, --allow", "Allow jsrepo to download code from the provided repo.", false).option("--repo <repo>", "Repository to download the blocks from").option("--debug", "Leaves the temp test file around for debugging upon failure.", false).action(async (blockNames, opts) => {
1921
+ const options = v8.parse(schema6, opts);
1922
+ await _test(blockNames, options);
1923
+ });
1924
+ var _test = async (blockNames, options) => {
1925
+ _intro(context.package.version);
1926
+ const verbose = (msg) => {
1927
+ if (options.verbose) {
1928
+ console.info(`${INFO} ${msg}`);
1929
+ }
1930
+ };
1931
+ verbose(`Attempting to test ${JSON.stringify(blockNames)}`);
1932
+ const config = getConfig().match(
1933
+ (val) => val,
1934
+ (err) => program4.error(color9.red(err))
1935
+ );
1936
+ const loading = spinner6();
1937
+ const blocksMap = /* @__PURE__ */ new Map();
1938
+ let repoPaths = config.repos;
1939
+ if (options.repo) repoPaths = [options.repo];
1940
+ if (!options.allow && options.repo) {
1941
+ const result = await confirm4({
1942
+ message: `Allow ${color9.cyan("jsrepo")} to download and run code from ${color9.cyan(options.repo)}?`,
1943
+ initialValue: true
1944
+ });
1945
+ if (isCancel4(result) || !result) {
1946
+ cancel4("Canceled!");
1947
+ process.exit(0);
1948
+ }
1949
+ }
1950
+ verbose(`Fetching blocks from ${color9.cyan(repoPaths.join(", "))}`);
1951
+ if (!options.verbose) loading.start(`Fetching blocks from ${color9.cyan(repoPaths.join(", "))}`);
1952
+ for (const repo of repoPaths) {
1953
+ const providerInfo = (await getProviderInfo(repo)).match(
1954
+ (info) => info,
1955
+ (err) => program4.error(color9.red(err))
1956
+ );
1957
+ const manifestUrl = await providerInfo.provider.resolveRaw(providerInfo, OUTPUT_FILE);
1958
+ verbose(`Got info for provider ${color9.cyan(providerInfo.name)}`);
1959
+ const response = await fetch(manifestUrl);
1960
+ if (!response.ok) {
1961
+ if (!options.verbose) loading.stop(`Error fetching ${color9.cyan(manifestUrl.href)}`);
1962
+ program4.error(
1963
+ color9.red(
1964
+ `There was an error fetching the \`${OUTPUT_FILE}\` from the repository ${color9.cyan(
1965
+ repo
1966
+ )} make sure the target repository has a \`${OUTPUT_FILE}\` in its root?`
1967
+ )
1968
+ );
1969
+ }
1970
+ const categories = v8.parse(v8.array(categorySchema), await response.json());
1971
+ for (const category of categories) {
1972
+ for (const block of category.blocks) {
1973
+ blocksMap.set(
1974
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block.name}`,
1975
+ {
1976
+ ...block,
1977
+ sourceRepo: providerInfo
1978
+ }
1979
+ );
1980
+ }
1981
+ }
1982
+ }
1983
+ verbose(`Retrieved blocks from ${color9.cyan(repoPaths.join(", "))}`);
1984
+ if (!options.verbose) loading.stop(`Retrieved blocks from ${color9.cyan(repoPaths.join(", "))}`);
1985
+ const tempTestDirectory = `blocks-tests-temp-${Date.now()}`;
1986
+ verbose(`Trying to create the temp directory ${color9.bold(tempTestDirectory)}.`);
1987
+ fs10.mkdirSync(tempTestDirectory, { recursive: true });
1988
+ const cleanUp = () => {
1989
+ fs10.rmSync(tempTestDirectory, { recursive: true, force: true });
1990
+ };
1991
+ const installedBlocks = getInstalledBlocks(blocksMap, config).map((val) => val.specifier);
1992
+ let testingBlocks = blockNames;
1993
+ if (blockNames.length === 0) {
1994
+ testingBlocks = installedBlocks;
1995
+ }
1996
+ if (testingBlocks.length === 0) {
1997
+ cleanUp();
1998
+ program4.error(color9.red("There were no blocks found in your project!"));
1999
+ }
2000
+ const testingBlocksMapped = [];
2001
+ for (const blockSpecifier of testingBlocks) {
2002
+ let block = void 0;
2003
+ if (!blockSpecifier.startsWith("github")) {
2004
+ for (const repo of repoPaths) {
2005
+ const providerInfo = (await getProviderInfo(repo)).unwrap();
2006
+ const tempBlock = blocksMap.get(
2007
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${blockSpecifier}`
2008
+ );
2009
+ if (tempBlock === void 0) continue;
2010
+ block = tempBlock;
2011
+ break;
2012
+ }
2013
+ } else {
2014
+ if (repoPaths.length === 0) {
2015
+ const [providerName, owner, repoName, ...rest] = blockSpecifier.split("/");
2016
+ let repo;
2017
+ if (rest.length > 2) {
2018
+ repo = `${providerName}/${owner}/${repoName}/${rest.join("/")}`;
2019
+ } else {
2020
+ repo = `${providerName}/${owner}/${repoName}`;
2021
+ }
2022
+ const providerInfo = (await getProviderInfo(repo)).match(
2023
+ (val) => val,
2024
+ (err) => program4.error(color9.red(err))
2025
+ );
2026
+ const manifestUrl = await providerInfo.provider.resolveRaw(
2027
+ providerInfo,
2028
+ OUTPUT_FILE
2029
+ );
2030
+ const categories = (await getManifest(manifestUrl)).match(
2031
+ (val) => val,
2032
+ (err) => program4.error(color9.red(err))
2033
+ );
2034
+ for (const category of categories) {
2035
+ for (const block2 of category.blocks) {
2036
+ blocksMap.set(
2037
+ `${providerInfo.name}/${providerInfo.owner}/${providerInfo.repoName}/${category.name}/${block2.name}`,
2038
+ {
2039
+ ...block2,
2040
+ sourceRepo: providerInfo
2041
+ }
2042
+ );
2043
+ }
2044
+ }
2045
+ }
2046
+ block = blocksMap.get(blockSpecifier);
2047
+ }
2048
+ if (!block) {
2049
+ program4.error(
2050
+ color9.red(`Invalid block! ${color9.bold(blockSpecifier)} does not exist!`)
2051
+ );
2052
+ }
2053
+ testingBlocksMapped.push({ name: blockSpecifier, block });
2054
+ }
2055
+ for (const { name: specifier, block } of testingBlocksMapped) {
2056
+ const providerInfo = block.sourceRepo;
2057
+ if (!options.verbose) {
2058
+ loading.start(`Setting up test file for ${color9.cyan(specifier)}`);
2059
+ }
2060
+ if (!block.tests) {
2061
+ loading.stop(`No tests found for ${color9.cyan(specifier)}`);
2062
+ continue;
2063
+ }
2064
+ const getSourceFile = async (filePath) => {
2065
+ const rawUrl = await providerInfo.provider.resolveRaw(providerInfo, filePath);
2066
+ const response = await fetch(rawUrl);
2067
+ if (!response.ok) {
2068
+ loading.stop(color9.red(`Error fetching ${color9.bold(rawUrl.href)}`));
2069
+ program4.error(color9.red(`There was an error trying to get ${specifier}`));
2070
+ }
2071
+ return await response.text();
2072
+ };
2073
+ verbose(`Downloading and copying test files for ${specifier}`);
2074
+ const testFiles = [];
2075
+ for (const testFile of block.files.filter((file) => isTestFile(file))) {
2076
+ const content = await getSourceFile(path8.join(block.directory, testFile));
2077
+ const destPath = path8.join(tempTestDirectory, testFile);
2078
+ fs10.writeFileSync(destPath, content);
2079
+ testFiles.push(destPath);
2080
+ }
2081
+ const directory = path8.join(config.path, block.category);
2082
+ let blockFilePath = path8.join(directory, `${block.name}`);
2083
+ blockFilePath = blockFilePath.replaceAll("\\", "/");
2084
+ verbose(`${color9.bold(specifier)} file path is ${color9.bold(blockFilePath)}`);
2085
+ const project = new Project2();
2086
+ for (const file of testFiles) {
2087
+ verbose(`Opening test file ${file}`);
2088
+ const tempFile = project.addSourceFileAtPath(file);
2089
+ for (const importDeclaration of tempFile.getImportDeclarations()) {
2090
+ const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
2091
+ let newModuleSpecifier = void 0;
2092
+ if (moduleSpecifier.startsWith(".")) {
2093
+ if (block.subdirectory) {
2094
+ newModuleSpecifier = path8.join(
2095
+ "../",
2096
+ config.path,
2097
+ block.category,
2098
+ block.name,
2099
+ moduleSpecifier
2100
+ );
2101
+ } else {
2102
+ newModuleSpecifier = path8.join(
2103
+ "../",
2104
+ config.path,
2105
+ block.category,
2106
+ moduleSpecifier
2107
+ );
2108
+ }
2109
+ }
2110
+ if (newModuleSpecifier) {
2111
+ importDeclaration.setModuleSpecifier(newModuleSpecifier.replaceAll(/\\/g, "/"));
2112
+ }
2113
+ }
2114
+ }
2115
+ project.saveSync();
2116
+ verbose(`Completed ${color9.cyan.bold(specifier)} test file`);
2117
+ if (!options.verbose) {
2118
+ loading.stop(`Completed setup for ${color9.bold(specifier)}`);
2119
+ }
2120
+ }
2121
+ verbose("Beginning testing");
2122
+ const pm = await detect2({ cwd: process.cwd() });
2123
+ if (pm == null) {
2124
+ program4.error(color9.red("Could not detect package manager"));
2125
+ }
2126
+ const resolved = resolveCommand2(pm.agent, "execute", ["vitest", "run", tempTestDirectory]);
2127
+ if (resolved == null) {
2128
+ program4.error(color9.red(`Could not resolve add command for '${pm.agent}'.`));
2129
+ }
2130
+ const { command, args } = resolved;
2131
+ const testCommand = `${command} ${args.join(" ")}`;
2132
+ const testingProcess = execa2({
2133
+ cwd: process.cwd(),
2134
+ stdio: ["ignore", "pipe", "pipe"]
2135
+ })`${testCommand}`;
2136
+ const handler = (data) => console.info(data.toString());
2137
+ testingProcess.stdout.on("data", handler);
2138
+ testingProcess.stderr.on("data", handler);
2139
+ try {
2140
+ await testingProcess;
2141
+ cleanUp();
2142
+ outro5(color9.green("All done!"));
2143
+ } catch (err) {
2144
+ if (options.debug) {
2145
+ console.info(
2146
+ `${color9.bold("--debug")} flag provided. Skipping cleanup. Run '${color9.bold(
2147
+ testCommand
2148
+ )}' to retry tests.
2149
+ `
2150
+ );
2151
+ } else {
2152
+ cleanUp();
2153
+ }
2154
+ program4.error(color9.red(`Tests failed! Error ${err}`));
2155
+ }
2156
+ };
2157
+
2158
+ // src/index.ts
2159
+ var resolveRelativeToRoot = (p) => {
2160
+ const dirname = fileURLToPath(import.meta.url);
2161
+ return path9.join(dirname, "../..", p);
2162
+ };
2163
+ var { version, name, description, repository } = JSON.parse(
2164
+ fs11.readFileSync(resolveRelativeToRoot("package.json"), "utf-8")
2165
+ );
2166
+ var context = {
2167
+ package: {
2168
+ name,
2169
+ description,
2170
+ version,
2171
+ repository
2172
+ },
2173
+ resolveRelativeToRoot
2174
+ };
2175
+ program5.name(name).description(description).version(version).addCommand(add).addCommand(init).addCommand(test).addCommand(build).addCommand(diff);
2176
+ program5.parse();
2177
+ export {
2178
+ context
2179
+ };