elm-pages 3.0.12 → 3.0.14

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 (75) hide show
  1. package/README.md +2 -26
  2. package/codegen/elm-pages-codegen.cjs +10741 -10302
  3. package/generator/src/build.js +15 -5
  4. package/generator/src/cli.js +3 -5
  5. package/generator/src/compatibility-key.js +2 -2
  6. package/generator/src/dev-server.js +3 -0
  7. package/generator/src/render.js +681 -50
  8. package/generator/src/request-cache.js +13 -6
  9. package/generator/src/spinnies/index.js +200 -0
  10. package/generator/src/spinnies/utils.js +123 -0
  11. package/generator/src/validate-stream.js +25 -0
  12. package/generator/template/elm.json +4 -4
  13. package/generator/template/package.json +6 -6
  14. package/generator/template/script/elm.json +7 -8
  15. package/package.json +4 -3
  16. package/src/BackendTask/Custom.elm +38 -0
  17. package/src/BackendTask/Do.elm +233 -0
  18. package/src/BackendTask/File.elm +24 -9
  19. package/src/BackendTask/Glob.elm +208 -25
  20. package/src/BackendTask/Http.elm +32 -21
  21. package/src/BackendTask/Internal/Glob.elm +16 -4
  22. package/src/BackendTask/Stream.elm +1179 -0
  23. package/src/BackendTask.elm +214 -7
  24. package/src/Pages/Internal/Platform/CompatibilityKey.elm +1 -1
  25. package/src/Pages/Internal/Platform.elm +11 -2
  26. package/src/Pages/Script/Spinner.elm +505 -0
  27. package/src/Pages/Script.elm +199 -2
  28. package/src/Pages/StaticHttp/Request.elm +7 -0
  29. package/src/RequestsAndPending.elm +1 -1
  30. package/src/Scaffold/Form.elm +2 -3
  31. package/src/TerminalText.elm +8 -0
  32. package/src/Vendored/Result/Extra.elm +75 -0
  33. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
  34. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
  35. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmi +0 -0
  36. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
  37. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
  38. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
  39. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
  40. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
  41. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  42. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  43. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock +0 -0
  44. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  45. package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +0 -1
  46. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +0 -7900
  47. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +0 -28657
  48. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +0 -110
  49. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +0 -187
  50. package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/package.json +0 -1
  51. package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Reporter.elm +0 -26
  52. package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Runner.elm +0 -62
  53. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
  54. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
  55. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
  56. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
  57. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmi +0 -0
  58. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmo +0 -0
  59. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
  60. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
  61. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
  62. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
  63. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
  64. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
  65. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock +0 -0
  66. package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
  67. package/generator/review/elm-stuff/tests-0.19.1/elm.json +0 -1
  68. package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +0 -7900
  69. package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +0 -30511
  70. package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +0 -110
  71. package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +0 -187
  72. package/generator/review/elm-stuff/tests-0.19.1/js/package.json +0 -1
  73. package/generator/review/elm-stuff/tests-0.19.1/src/Reporter.elm +0 -26
  74. package/generator/review/elm-stuff/tests-0.19.1/src/Runner.elm +0 -62
  75. package/src/Result/Extra.elm +0 -26
@@ -13,6 +13,19 @@ import { compatibilityKey } from "./compatibility-key.js";
13
13
  import * as fs from "node:fs";
14
14
  import * as crypto from "node:crypto";
15
15
  import { restoreColorSafe } from "./error-formatter.js";
16
+ import { Spinnies } from "./spinnies/index.js";
17
+ import { default as which } from "which";
18
+ import * as readline from "readline";
19
+ import { spawn as spawnCallback } from "cross-spawn";
20
+ import * as consumers from "stream/consumers";
21
+ import * as zlib from "node:zlib";
22
+ import { Readable, Writable } from "node:stream";
23
+ import * as validateStream from "./validate-stream.js";
24
+ import { default as makeFetchHappenOriginal } from "make-fetch-happen";
25
+ import mergeStreams from "@sindresorhus/merge-streams";
26
+
27
+ let verbosity = 2;
28
+ const spinnies = new Spinnies();
16
29
 
17
30
  process.on("unhandledRejection", (error) => {
18
31
  console.error(error);
@@ -184,7 +197,8 @@ function runGeneratorAppHelp(
184
197
  mode,
185
198
  requestToPerform,
186
199
  hasFsAccess,
187
- patternsToWatch
200
+ patternsToWatch,
201
+ portsFile
188
202
  );
189
203
  } else {
190
204
  return runHttpJob(
@@ -203,6 +217,7 @@ function runGeneratorAppHelp(
203
217
  );
204
218
  } else if (fromElm.tag === "Errors") {
205
219
  foundErrors = true;
220
+ spinnies.stopAll();
206
221
  reject(fromElm.args[0].errorsJson);
207
222
  } else {
208
223
  console.log(fromElm);
@@ -321,7 +336,8 @@ function runElmApp(
321
336
  mode,
322
337
  requestToPerform,
323
338
  hasFsAccess,
324
- patternsToWatch
339
+ patternsToWatch,
340
+ portsFile
325
341
  );
326
342
  } else {
327
343
  return runHttpJob(
@@ -340,6 +356,7 @@ function runElmApp(
340
356
  );
341
357
  } else if (fromElm.tag === "Errors") {
342
358
  foundErrors = true;
359
+ spinnies.stopAll();
343
360
  reject(fromElm.args[0].errorsJson);
344
361
  } else {
345
362
  console.log(fromElm);
@@ -461,54 +478,85 @@ async function runInternalJob(
461
478
  mode,
462
479
  requestToPerform,
463
480
  hasFsAccess,
464
- patternsToWatch
481
+ patternsToWatch,
482
+ portsFile
465
483
  ) {
466
484
  try {
467
- if (requestToPerform.url === "elm-pages-internal://log") {
468
- return [requestHash, await runLogJob(requestToPerform)];
469
- } else if (requestToPerform.url === "elm-pages-internal://read-file") {
470
- return [
471
- requestHash,
472
- await readFileJobNew(requestToPerform, patternsToWatch),
473
- ];
474
- } else if (requestToPerform.url === "elm-pages-internal://glob") {
475
- return [requestHash, await runGlobNew(requestToPerform, patternsToWatch)];
476
- } else if (requestToPerform.url === "elm-pages-internal://randomSeed") {
477
- return [
478
- requestHash,
479
- jsonResponse(
480
- requestToPerform,
481
- crypto.getRandomValues(new Uint32Array(1))[0]
482
- ),
483
- ];
484
- } else if (requestToPerform.url === "elm-pages-internal://now") {
485
- return [requestHash, jsonResponse(requestToPerform, Date.now())];
486
- } else if (requestToPerform.url === "elm-pages-internal://env") {
487
- return [requestHash, await runEnvJob(requestToPerform, patternsToWatch)];
488
- } else if (requestToPerform.url === "elm-pages-internal://encrypt") {
489
- return [
490
- requestHash,
491
- await runEncryptJob(requestToPerform, patternsToWatch),
492
- ];
493
- } else if (requestToPerform.url === "elm-pages-internal://decrypt") {
494
- return [
495
- requestHash,
496
- await runDecryptJob(requestToPerform, patternsToWatch),
497
- ];
498
- } else if (requestToPerform.url === "elm-pages-internal://write-file") {
499
- return [requestHash, await runWriteFileJob(requestToPerform)];
500
- } else {
501
- throw `Unexpected internal BackendTask request format: ${kleur.yellow(
502
- JSON.stringify(2, null, requestToPerform)
503
- )}`;
485
+ const cwd = path.resolve(...requestToPerform.dir);
486
+ const quiet = requestToPerform.quiet;
487
+ const env = { ...process.env, ...requestToPerform.env };
488
+
489
+ const context = { cwd, quiet, env };
490
+ switch (requestToPerform.url) {
491
+ case "elm-pages-internal://log":
492
+ return [requestHash, await runLogJob(requestToPerform)];
493
+ case "elm-pages-internal://read-file":
494
+ return [
495
+ requestHash,
496
+ await readFileJobNew(requestToPerform, patternsToWatch, context),
497
+ ];
498
+ case "elm-pages-internal://glob":
499
+ return [
500
+ requestHash,
501
+ await runGlobNew(requestToPerform, patternsToWatch),
502
+ ];
503
+ case "elm-pages-internal://randomSeed":
504
+ return [
505
+ requestHash,
506
+ jsonResponse(
507
+ requestToPerform,
508
+ crypto.getRandomValues(new Uint32Array(1))[0]
509
+ ),
510
+ ];
511
+ case "elm-pages-internal://now":
512
+ return [requestHash, jsonResponse(requestToPerform, Date.now())];
513
+ case "elm-pages-internal://env":
514
+ return [
515
+ requestHash,
516
+ await runEnvJob(requestToPerform, patternsToWatch),
517
+ ];
518
+ case "elm-pages-internal://encrypt":
519
+ return [
520
+ requestHash,
521
+ await runEncryptJob(requestToPerform, patternsToWatch),
522
+ ];
523
+ case "elm-pages-internal://decrypt":
524
+ return [
525
+ requestHash,
526
+ await runDecryptJob(requestToPerform, patternsToWatch),
527
+ ];
528
+ case "elm-pages-internal://write-file":
529
+ return [requestHash, await runWriteFileJob(requestToPerform, context)];
530
+ case "elm-pages-internal://sleep":
531
+ return [requestHash, await runSleep(requestToPerform)];
532
+ case "elm-pages-internal://which":
533
+ return [requestHash, await runWhich(requestToPerform)];
534
+ case "elm-pages-internal://question":
535
+ return [requestHash, await runQuestion(requestToPerform)];
536
+ case "elm-pages-internal://shell":
537
+ return [requestHash, await runShell(requestToPerform)];
538
+ case "elm-pages-internal://stream":
539
+ return [
540
+ requestHash,
541
+ await runStream(requestToPerform, portsFile, context),
542
+ ];
543
+ case "elm-pages-internal://start-spinner":
544
+ return [requestHash, runStartSpinner(requestToPerform)];
545
+ case "elm-pages-internal://stop-spinner":
546
+ return [requestHash, runStopSpinner(requestToPerform)];
547
+ default:
548
+ throw `Unexpected internal BackendTask request format: ${kleur.yellow(
549
+ JSON.stringify(2, null, requestToPerform)
550
+ )}`;
504
551
  }
505
552
  } catch (error) {
506
553
  sendError(app, error);
507
554
  }
508
555
  }
509
556
 
510
- async function readFileJobNew(req, patternsToWatch) {
511
- const filePath = req.body.args[1];
557
+ async function readFileJobNew(req, patternsToWatch, { cwd }) {
558
+ // TODO use cwd
559
+ const filePath = path.resolve(cwd, req.body.args[1]);
512
560
  try {
513
561
  patternsToWatch.add(filePath);
514
562
 
@@ -527,36 +575,619 @@ async function readFileJobNew(req, patternsToWatch) {
527
575
  });
528
576
  }
529
577
  }
530
- async function runWriteFileJob(req) {
578
+
579
+ function runSleep(req) {
580
+ const { milliseconds } = req.body.args[0];
581
+ return new Promise((resolve) => {
582
+ setTimeout(() => {
583
+ resolve(jsonResponse(req, null));
584
+ }, milliseconds);
585
+ });
586
+ }
587
+
588
+ async function runWhich(req) {
589
+ const command = req.body.args[0];
590
+ try {
591
+ return jsonResponse(req, await which(command));
592
+ } catch (error) {
593
+ return jsonResponse(req, null);
594
+ }
595
+ }
596
+
597
+ async function runQuestion(req) {
598
+ return jsonResponse(req, await question(req.body.args[0]));
599
+ }
600
+
601
+ function runStream(req, portsFile, context) {
602
+ return new Promise(async (resolve) => {
603
+ let metadataResponse = null;
604
+ let lastStream = null;
605
+ try {
606
+ const kind = req.body.args[0].kind;
607
+ const parts = req.body.args[0].parts;
608
+ let index = 0;
609
+
610
+ for (const part of parts) {
611
+ let isLastProcess = index === parts.length - 1;
612
+ let thisStream;
613
+ const { stream, metadata } = await pipePartToStream(
614
+ lastStream,
615
+ part,
616
+ context,
617
+ portsFile,
618
+ (value) => resolve(jsonResponse(req, value)),
619
+ isLastProcess,
620
+ kind
621
+ );
622
+ metadataResponse = metadata;
623
+ thisStream = stream;
624
+
625
+ lastStream = thisStream;
626
+ index += 1;
627
+ }
628
+ if (kind === "json") {
629
+ resolve(
630
+ jsonResponse(req, {
631
+ body: await consumers.json(lastStream),
632
+ metadata: await tryCallingFunction(metadataResponse),
633
+ })
634
+ );
635
+ } else if (kind === "text") {
636
+ resolve(
637
+ jsonResponse(req, {
638
+ body: await consumers.text(lastStream),
639
+ metadata: await tryCallingFunction(metadataResponse),
640
+ })
641
+ );
642
+ } else if (kind === "none") {
643
+ if (!lastStream) {
644
+ // ensure all error handling gets a chance to fire before resolving successfully
645
+ await tryCallingFunction(metadataResponse);
646
+ resolve(jsonResponse(req, { body: null }));
647
+ } else {
648
+ let resolvedMeta = await tryCallingFunction(metadataResponse);
649
+ lastStream.once("finish", async () => {
650
+ resolve(
651
+ jsonResponse(req, {
652
+ body: null,
653
+ metadata: resolvedMeta,
654
+ })
655
+ );
656
+ });
657
+ lastStream.once("end", async () => {
658
+ resolve(
659
+ jsonResponse(req, {
660
+ body: null,
661
+ metadata: resolvedMeta,
662
+ })
663
+ );
664
+ });
665
+ }
666
+ } else if (kind === "command") {
667
+ // already handled in parts.forEach
668
+ }
669
+ /**
670
+ *
671
+ * @param {import('node:stream').Stream?} lastStream
672
+ * @param {{ name: string }} part
673
+ * @param {{cwd: string, quiet: boolean, env: object}} param2
674
+ * @returns {Promise<{stream: import('node:stream').Stream, metadata?: any}>}
675
+ */
676
+ async function pipePartToStream(
677
+ lastStream,
678
+ part,
679
+ { cwd, quiet, env },
680
+ portsFile,
681
+ resolve,
682
+ isLastProcess,
683
+ kind
684
+ ) {
685
+ if (verbosity > 1 && !quiet) {
686
+ }
687
+ if (part.name === "stdout") {
688
+ return { stream: pipeIfPossible(lastStream, stdout()) };
689
+ } else if (part.name === "stderr") {
690
+ return { stream: pipeIfPossible(lastStream, stderr()) };
691
+ } else if (part.name === "stdin") {
692
+ return { stream: process.stdin };
693
+ } else if (part.name === "fileRead") {
694
+ const newLocal = fs.createReadStream(path.resolve(cwd, part.path));
695
+ newLocal.once("error", (error) => {
696
+ newLocal.close();
697
+ resolve({ error: error.toString() });
698
+ });
699
+ return { stream: newLocal };
700
+ } else if (part.name === "customDuplex") {
701
+ const newLocal = await portsFile[part.portName](part.input, {
702
+ cwd,
703
+ quiet,
704
+ env,
705
+ });
706
+ if (validateStream.isDuplexStream(newLocal.stream)) {
707
+ pipeIfPossible(lastStream, newLocal.stream);
708
+ return newLocal;
709
+ } else {
710
+ throw `Expected '${part.portName}' to be a duplex stream!`;
711
+ }
712
+ } else if (part.name === "customRead") {
713
+ return {
714
+ metadata: null,
715
+ stream: await portsFile[part.portName](part.input, {
716
+ cwd,
717
+ quiet,
718
+ env,
719
+ }),
720
+ };
721
+ } else if (part.name === "customWrite") {
722
+ const newLocal = await portsFile[part.portName](part.input, {
723
+ cwd,
724
+ quiet,
725
+ env,
726
+ });
727
+ if (!validateStream.isWritableStream(newLocal.stream)) {
728
+ console.error("Expected a writable stream!");
729
+ resolve({ error: "Expected a writable stream!" });
730
+ } else {
731
+ pipeIfPossible(lastStream, newLocal.stream);
732
+ }
733
+ return newLocal;
734
+ } else if (part.name === "gzip") {
735
+ const gzip = zlib.createGzip();
736
+ if (!lastStream) {
737
+ gzip.end();
738
+ }
739
+ return {
740
+ metadata: null,
741
+ stream: pipeIfPossible(lastStream, gzip),
742
+ };
743
+ } else if (part.name === "unzip") {
744
+ return {
745
+ metadata: null,
746
+ stream: pipeIfPossible(lastStream, zlib.createUnzip()),
747
+ };
748
+ } else if (part.name === "fileWrite") {
749
+ const destinationPath = path.resolve(part.path);
750
+ try {
751
+ await fsPromises.mkdir(path.dirname(destinationPath), {
752
+ recursive: true,
753
+ });
754
+ } catch (error) {
755
+ resolve({ error: error.toString() });
756
+ }
757
+ const newLocal = fs.createWriteStream(destinationPath);
758
+ newLocal.once("error", (error) => {
759
+ newLocal.close();
760
+ newLocal.removeAllListeners();
761
+ resolve({ error: error.toString() });
762
+ });
763
+ return {
764
+ metadata: null,
765
+ stream: pipeIfPossible(lastStream, newLocal),
766
+ };
767
+ } else if (part.name === "httpWrite") {
768
+ const makeFetchHappen = makeFetchHappenOriginal.defaults({
769
+ // cache: mode === "build" ? "no-cache" : "default",
770
+ cache: "default",
771
+ });
772
+ const response = await makeFetchHappen(part.url, {
773
+ body: lastStream,
774
+ duplex: "half",
775
+ redirect: "follow",
776
+ method: part.method,
777
+ headers: part.headers,
778
+ retry: part.retries,
779
+ timeout: part.timeoutInMs,
780
+ });
781
+ if (!isLastProcess && !response.ok) {
782
+ resolve({
783
+ error: `HTTP request failed: ${response.status} ${response.statusText}`,
784
+ });
785
+ } else {
786
+ let metadata = () => {
787
+ return {
788
+ headers: Object.fromEntries(response.headers.entries()),
789
+ statusCode: response.status,
790
+ // bodyKind,
791
+ url: response.url,
792
+ statusText: response.statusText,
793
+ };
794
+ };
795
+ return { metadata, stream: response.body };
796
+ }
797
+ } else if (part.name === "command") {
798
+ const { command, args, allowNon0Status, output } = part;
799
+ /** @type {'ignore' | 'inherit'} } */
800
+ let letPrint = quiet ? "ignore" : "inherit";
801
+ let stderrKind = kind === "none" ? letPrint : "pipe";
802
+ if (output === "Ignore") {
803
+ stderrKind = "ignore";
804
+ } else if (output === "Print") {
805
+ stderrKind = letPrint;
806
+ }
807
+ /**
808
+ * @type {import('node:child_process').ChildProcess}
809
+ */
810
+ const newProcess = spawnCallback(command, args, {
811
+ stdio: [
812
+ "pipe",
813
+ // if we are capturing stderr instead of stdout, print out stdout with `inherit`
814
+ output === "InsteadOfStdout" || kind === "none"
815
+ ? letPrint
816
+ : "pipe",
817
+ stderrKind,
818
+ ],
819
+ cwd: cwd,
820
+ env: env,
821
+ });
822
+
823
+ pipeIfPossible(lastStream, newProcess.stdin);
824
+ let newStream;
825
+ if (output === "MergeWithStdout") {
826
+ newStream = mergeStreams([newProcess.stdout, newProcess.stderr]);
827
+ } else if (output === "InsteadOfStdout") {
828
+ newStream = newProcess.stderr;
829
+ } else {
830
+ newStream = newProcess.stdout;
831
+ }
832
+
833
+ newProcess.once("error", (error) => {
834
+ newStream && newStream.end();
835
+ newProcess.removeAllListeners();
836
+ resolve({ error: error.toString() });
837
+ });
838
+ if (isLastProcess) {
839
+ return {
840
+ stream: newStream,
841
+ metadata: new Promise((resoveMeta) => {
842
+ newProcess.once("exit", (code) => {
843
+ if (code !== 0 && !allowNon0Status) {
844
+ newStream && newStream.end();
845
+ resolve({
846
+ error: `Command ${command} exited with code ${code}`,
847
+ });
848
+ }
849
+
850
+ resoveMeta({
851
+ exitCode: code,
852
+ });
853
+ });
854
+ }),
855
+ };
856
+ } else {
857
+ return { metadata: null, stream: newStream };
858
+ }
859
+ } else if (part.name === "fromString") {
860
+ return { stream: Readable.from([part.string]), metadata: null };
861
+ } else {
862
+ // console.error(`Unknown stream part: ${part.name}!`);
863
+ // process.exit(1);
864
+ throw `Unknown stream part: ${part.name}!`;
865
+ }
866
+ }
867
+ } catch (error) {
868
+ if (lastStream) {
869
+ lastStream.destroy();
870
+ }
871
+
872
+ resolve(jsonResponse(req, { error: error.toString() }));
873
+ }
874
+ });
875
+ }
876
+
877
+ /**
878
+ * @param { import('stream').Stream? } input
879
+ * @param {import('stream').Writable | import('stream').Duplex} destination
880
+ */
881
+ function pipeIfPossible(input, destination) {
882
+ if (input) {
883
+ return input.pipe(destination);
884
+ } else {
885
+ return destination;
886
+ }
887
+ }
888
+
889
+ function stdout() {
890
+ return new Writable({
891
+ write(chunk, encoding, callback) {
892
+ process.stdout.write(chunk, callback);
893
+ },
894
+ });
895
+ }
896
+ function stderr() {
897
+ return new Writable({
898
+ write(chunk, encoding, callback) {
899
+ process.stderr.write(chunk, callback);
900
+ },
901
+ });
902
+ }
903
+
904
+ async function tryCallingFunction(func) {
905
+ if (func) {
906
+ // if is promise
907
+ if (func.then) {
908
+ return await func;
909
+ }
910
+ // if is function
911
+ else if (typeof func === "function") {
912
+ return await func();
913
+ }
914
+ } else {
915
+ return func;
916
+ }
917
+ }
918
+
919
+ async function runShell(req) {
920
+ const cwd = path.resolve(...req.dir);
921
+ const quiet = req.quiet;
922
+ const env = { ...process.env, ...req.env };
923
+ const captureOutput = req.body.args[0].captureOutput;
924
+ if (req.body.args[0].commands.length === 1) {
925
+ return jsonResponse(
926
+ req,
927
+ await shell({ cwd, quiet, env, captureOutput }, req.body.args[0])
928
+ );
929
+ } else {
930
+ return jsonResponse(
931
+ req,
932
+ await pipeShells({ cwd, quiet, env, captureOutput }, req.body.args[0])
933
+ );
934
+ }
935
+ }
936
+
937
+ function commandAndArgsToString(cwd, commandsAndArgs) {
938
+ return (
939
+ `$ ` +
940
+ commandsAndArgs.commands
941
+ .map((commandAndArgs) => {
942
+ return [commandAndArgs.command, ...commandAndArgs.args].join(" ");
943
+ })
944
+ .join(" | ")
945
+ );
946
+ }
947
+
948
+ export function shell({ cwd, quiet, env, captureOutput }, commandAndArgs) {
949
+ return new Promise((resolve, reject) => {
950
+ const command = commandAndArgs.commands[0].command;
951
+ const args = commandAndArgs.commands[0].args;
952
+ if (verbosity > 1 && !quiet) {
953
+ console.log(commandAndArgsToString(cwd, commandAndArgs));
954
+ }
955
+ if (!captureOutput && !quiet) {
956
+ const subprocess = spawnCallback(command, args, {
957
+ stdio: quiet
958
+ ? ["inherit", "ignore", "ignore"]
959
+ : ["inherit", "inherit", "inherit"],
960
+ cwd: cwd,
961
+ env: env,
962
+ });
963
+ subprocess.on("close", async (code) => {
964
+ resolve({
965
+ output: "",
966
+ errorCode: code,
967
+ stderrOutput: "",
968
+ stdoutOutput: "",
969
+ });
970
+ });
971
+ } else {
972
+ const subprocess = spawnCallback(command, args, {
973
+ stdio: ["pipe", "pipe", "pipe"],
974
+ cwd: cwd,
975
+ env: env,
976
+ });
977
+ let commandOutput = "";
978
+ let stderrOutput = "";
979
+ let stdoutOutput = "";
980
+
981
+ if (verbosity > 0 && !quiet) {
982
+ subprocess.stdout.pipe(process.stdout);
983
+ subprocess.stderr.pipe(process.stderr);
984
+ }
985
+ subprocess.stderr.on("data", function (data) {
986
+ commandOutput += data;
987
+ stderrOutput += data;
988
+ });
989
+ subprocess.stdout.on("data", function (data) {
990
+ commandOutput += data;
991
+ stdoutOutput += data;
992
+ });
993
+
994
+ subprocess.on("close", async (code) => {
995
+ resolve({
996
+ output: commandOutput,
997
+ errorCode: code,
998
+ stderrOutput,
999
+ stdoutOutput,
1000
+ });
1001
+ });
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ /**
1007
+ * @typedef {{ command: string, args: string[], timeout: number? }} ElmCommand
1008
+ */
1009
+
1010
+ /**
1011
+ * @param {{ commands: ElmCommand[] }} commandsAndArgs
1012
+ */
1013
+ export function pipeShells(
1014
+ { cwd, quiet, env, captureOutput },
1015
+ commandsAndArgs
1016
+ ) {
1017
+ return new Promise((resolve, reject) => {
1018
+ if (verbosity > 1 && !quiet) {
1019
+ console.log(commandAndArgsToString(cwd, commandsAndArgs));
1020
+ }
1021
+
1022
+ /**
1023
+ * @type {null | import('node:child_process').ChildProcess}
1024
+ */
1025
+ let previousProcess = null;
1026
+ let currentProcess = null;
1027
+
1028
+ commandsAndArgs.commands.forEach(({ command, args, timeout }, index) => {
1029
+ let isLastProcess = index === commandsAndArgs.commands.length - 1;
1030
+ /**
1031
+ * @type {import('node:child_process').ChildProcess}
1032
+ */
1033
+ if (previousProcess === null) {
1034
+ currentProcess = spawnCallback(command, args, {
1035
+ stdio: ["inherit", "pipe", "inherit"],
1036
+ timeout: timeout ? undefined : timeout,
1037
+ cwd: cwd,
1038
+ env: env,
1039
+ });
1040
+ } else {
1041
+ if (isLastProcess && !captureOutput && false) {
1042
+ currentProcess = spawnCallback(command, args, {
1043
+ stdio: quiet
1044
+ ? ["pipe", "ignore", "ignore"]
1045
+ : ["pipe", "inherit", "inherit"],
1046
+ timeout: timeout ? undefined : timeout,
1047
+ cwd: cwd,
1048
+ env: env,
1049
+ });
1050
+ } else {
1051
+ currentProcess = spawnCallback(command, args, {
1052
+ stdio: ["pipe", "pipe", "pipe"],
1053
+ timeout: timeout ? undefined : timeout,
1054
+ cwd: cwd,
1055
+ env: env,
1056
+ });
1057
+ }
1058
+ previousProcess.stdout.pipe(currentProcess.stdin);
1059
+ }
1060
+ previousProcess = currentProcess;
1061
+ });
1062
+
1063
+ if (currentProcess === null) {
1064
+ reject("");
1065
+ } else {
1066
+ let commandOutput = "";
1067
+ let stderrOutput = "";
1068
+ let stdoutOutput = "";
1069
+
1070
+ if (verbosity > 0 && !quiet) {
1071
+ currentProcess.stdout && currentProcess.stdout.pipe(process.stdout);
1072
+ currentProcess.stderr && currentProcess.stderr.pipe(process.stderr);
1073
+ }
1074
+
1075
+ currentProcess.stderr &&
1076
+ currentProcess.stderr.on("data", function (data) {
1077
+ commandOutput += data;
1078
+ stderrOutput += data;
1079
+ });
1080
+ currentProcess.stdout &&
1081
+ currentProcess.stdout.on("data", function (data) {
1082
+ commandOutput += data;
1083
+ stdoutOutput += data;
1084
+ });
1085
+
1086
+ currentProcess.on("close", async (code) => {
1087
+ resolve({
1088
+ output: commandOutput,
1089
+ errorCode: code,
1090
+ stderrOutput,
1091
+ stdoutOutput,
1092
+ });
1093
+ });
1094
+ }
1095
+ });
1096
+ }
1097
+
1098
+ export async function question({ prompt }) {
1099
+ return new Promise((resolve) => {
1100
+ const rl = readline.createInterface({
1101
+ input: process.stdin,
1102
+ output: process.stdout,
1103
+ });
1104
+
1105
+ return rl.question(prompt, (answer) => {
1106
+ rl.close();
1107
+ resolve(answer);
1108
+ });
1109
+ });
1110
+ }
1111
+
1112
+ async function runWriteFileJob(req, { cwd }) {
531
1113
  const data = req.body.args[0];
1114
+ const filePath = path.resolve(cwd, data.path);
532
1115
  try {
533
- const fullPathToWrite = path.join(process.cwd(), data.path);
534
- await fsPromises.mkdir(path.dirname(fullPathToWrite), { recursive: true });
535
- await fsPromises.writeFile(fullPathToWrite, data.body);
1116
+ await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
1117
+ await fsPromises.writeFile(filePath, data.body);
536
1118
  return jsonResponse(req, null);
537
1119
  } catch (error) {
538
1120
  console.trace(error);
539
1121
  throw {
540
1122
  title: "BackendTask Error",
541
1123
  message: `BackendTask.Generator.writeFile failed for file path: ${kleur.yellow(
542
- data.path
1124
+ filePath
543
1125
  )}\n${kleur.red(error.toString())}`,
544
1126
  };
545
1127
  }
546
1128
  }
547
1129
 
1130
+ function runStartSpinner(req) {
1131
+ const data = req.body.args[0];
1132
+ let spinnerId;
1133
+
1134
+ if (data.spinnerId) {
1135
+ spinnerId = data.spinnerId;
1136
+ // TODO use updateSpinnerState?
1137
+ spinnies.update(spinnerId, { text: data.text, status: "spinning" });
1138
+ } else {
1139
+ spinnerId = Math.random().toString(36);
1140
+ // spinnies.add(spinnerId, { text: data.text, status: data.immediateStart ? 'spinning' : 'stopped' });
1141
+ spinnies.add(spinnerId, { text: data.text, status: "spinning" });
1142
+ // }
1143
+ }
1144
+ return jsonResponse(req, spinnerId);
1145
+ }
1146
+
1147
+ function runStopSpinner(req) {
1148
+ const data = req.body.args[0];
1149
+ const { spinnerId, completionText, completionFn } = data;
1150
+ let completeFn;
1151
+ if (completionFn === "succeed") {
1152
+ spinnies.succeed(spinnerId, { text: completionText });
1153
+ } else if (completionFn === "fail") {
1154
+ spinnies.fail(spinnerId, { text: completionText });
1155
+ } else {
1156
+ console.log("Unexpected");
1157
+ }
1158
+ return jsonResponse(req, null);
1159
+ }
1160
+
548
1161
  async function runGlobNew(req, patternsToWatch) {
549
1162
  try {
550
1163
  const { pattern, options } = req.body.args[0];
551
- const matchedPaths = await globby.globby(pattern, options);
1164
+ const cwd = path.resolve(...req.dir);
1165
+ const matchedPaths = await globby.globby(pattern, {
1166
+ ...options,
1167
+ stats: true,
1168
+ cwd,
1169
+ });
552
1170
  patternsToWatch.add(pattern);
553
1171
 
554
1172
  return jsonResponse(
555
1173
  req,
556
1174
  matchedPaths.map((fullPath) => {
1175
+ const stats = fullPath.stats;
1176
+ if (!stats) {
1177
+ return null;
1178
+ }
557
1179
  return {
558
- fullPath,
559
- captures: mm.capture(pattern, fullPath),
1180
+ fullPath: fullPath.path,
1181
+ captures: mm.capture(pattern, fullPath.path),
1182
+ fileStats: {
1183
+ size: stats.size,
1184
+ atime: Math.round(stats.atime.getTime()),
1185
+ mtime: Math.round(stats.mtime.getTime()),
1186
+ ctime: Math.round(stats.ctime.getTime()),
1187
+ birthtime: Math.round(stats.birthtime.getTime()),
1188
+ fullPath: fullPath.path,
1189
+ isDirectory: stats.isDirectory(),
1190
+ },
560
1191
  };
561
1192
  })
562
1193
  );