superslate 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +664 -473
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,102 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
-
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
-
const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
-
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
24
|
-
for (let key of __getOwnPropNames(mod))
|
|
25
|
-
if (!__hasOwnProp.call(to, key))
|
|
26
|
-
__defProp(to, key, {
|
|
27
|
-
get: __accessProp.bind(mod, key),
|
|
28
|
-
enumerable: true
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if (canCache)
|
|
32
|
-
cache.set(mod, to);
|
|
33
|
-
return to;
|
|
34
|
-
};
|
|
35
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
36
|
-
|
|
37
|
-
// node_modules/sisteransi/src/index.js
|
|
38
|
-
var require_src = __commonJS(function(exports, module) {
|
|
39
|
-
var ESC = "\x1B";
|
|
40
|
-
var CSI = `${ESC}[`;
|
|
41
|
-
var beep = "\x07";
|
|
42
|
-
var cursor = {
|
|
43
|
-
to(x, y) {
|
|
44
|
-
if (!y)
|
|
45
|
-
return `${CSI}${x + 1}G`;
|
|
46
|
-
return `${CSI}${y + 1};${x + 1}H`;
|
|
47
|
-
},
|
|
48
|
-
move(x, y) {
|
|
49
|
-
let ret = "";
|
|
50
|
-
if (x < 0)
|
|
51
|
-
ret += `${CSI}${-x}D`;
|
|
52
|
-
else if (x > 0)
|
|
53
|
-
ret += `${CSI}${x}C`;
|
|
54
|
-
if (y < 0)
|
|
55
|
-
ret += `${CSI}${-y}A`;
|
|
56
|
-
else if (y > 0)
|
|
57
|
-
ret += `${CSI}${y}B`;
|
|
58
|
-
return ret;
|
|
59
|
-
},
|
|
60
|
-
up: (count = 1) => `${CSI}${count}A`,
|
|
61
|
-
down: (count = 1) => `${CSI}${count}B`,
|
|
62
|
-
forward: (count = 1) => `${CSI}${count}C`,
|
|
63
|
-
backward: (count = 1) => `${CSI}${count}D`,
|
|
64
|
-
nextLine: (count = 1) => `${CSI}E`.repeat(count),
|
|
65
|
-
prevLine: (count = 1) => `${CSI}F`.repeat(count),
|
|
66
|
-
left: `${CSI}G`,
|
|
67
|
-
hide: `${CSI}?25l`,
|
|
68
|
-
show: `${CSI}?25h`,
|
|
69
|
-
save: `${ESC}7`,
|
|
70
|
-
restore: `${ESC}8`
|
|
71
|
-
};
|
|
72
|
-
var scroll = {
|
|
73
|
-
up: (count = 1) => `${CSI}S`.repeat(count),
|
|
74
|
-
down: (count = 1) => `${CSI}T`.repeat(count)
|
|
75
|
-
};
|
|
76
|
-
var erase = {
|
|
77
|
-
screen: `${CSI}2J`,
|
|
78
|
-
up: (count = 1) => `${CSI}1J`.repeat(count),
|
|
79
|
-
down: (count = 1) => `${CSI}J`.repeat(count),
|
|
80
|
-
line: `${CSI}2K`,
|
|
81
|
-
lineEnd: `${CSI}K`,
|
|
82
|
-
lineStart: `${CSI}1K`,
|
|
83
|
-
lines(count) {
|
|
84
|
-
let clear = "";
|
|
85
|
-
for (let i = 0;i < count; i++)
|
|
86
|
-
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
87
|
-
if (count)
|
|
88
|
-
clear += cursor.left;
|
|
89
|
-
return clear;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
module.exports = { cursor, scroll, erase, beep };
|
|
93
|
-
});
|
|
94
3
|
|
|
95
4
|
// node_modules/@clack/core/dist/index.mjs
|
|
96
5
|
import { styleText } from "node:util";
|
|
97
6
|
import { stdout, stdin } from "node:process";
|
|
98
|
-
import * as
|
|
99
|
-
import
|
|
7
|
+
import * as f$1 from "node:readline";
|
|
8
|
+
import f__default from "node:readline";
|
|
100
9
|
|
|
101
10
|
// node_modules/fast-string-truncated-width/dist/utils.js
|
|
102
11
|
var getCodePointsLength = (() => {
|
|
@@ -439,14 +348,66 @@ function wrapAnsi(string, columns, options) {
|
|
|
439
348
|
`);
|
|
440
349
|
}
|
|
441
350
|
|
|
351
|
+
// node_modules/sisteransi/dist/index.js
|
|
352
|
+
var ESC2 = "\x1B";
|
|
353
|
+
var CSI2 = `${ESC2}[`;
|
|
354
|
+
var cursor = {
|
|
355
|
+
to(x, y) {
|
|
356
|
+
if (!y)
|
|
357
|
+
return `${CSI2}${x + 1}G`;
|
|
358
|
+
return `${CSI2}${y + 1};${x + 1}H`;
|
|
359
|
+
},
|
|
360
|
+
move(x, y) {
|
|
361
|
+
let ret = "";
|
|
362
|
+
if (x < 0)
|
|
363
|
+
ret += `${CSI2}${-x}D`;
|
|
364
|
+
else if (x > 0)
|
|
365
|
+
ret += `${CSI2}${x}C`;
|
|
366
|
+
if (y < 0)
|
|
367
|
+
ret += `${CSI2}${-y}A`;
|
|
368
|
+
else if (y > 0)
|
|
369
|
+
ret += `${CSI2}${y}B`;
|
|
370
|
+
return ret;
|
|
371
|
+
},
|
|
372
|
+
up: (count = 1) => `${CSI2}${count}A`,
|
|
373
|
+
down: (count = 1) => `${CSI2}${count}B`,
|
|
374
|
+
forward: (count = 1) => `${CSI2}${count}C`,
|
|
375
|
+
backward: (count = 1) => `${CSI2}${count}D`,
|
|
376
|
+
nextLine: (count = 1) => `${CSI2}E`.repeat(count),
|
|
377
|
+
prevLine: (count = 1) => `${CSI2}F`.repeat(count),
|
|
378
|
+
left: `${CSI2}G`,
|
|
379
|
+
hide: `${CSI2}?25l`,
|
|
380
|
+
show: `${CSI2}?25h`,
|
|
381
|
+
save: `${ESC2}7`,
|
|
382
|
+
restore: `${ESC2}8`
|
|
383
|
+
};
|
|
384
|
+
var erase = {
|
|
385
|
+
screen: `${CSI2}2J`,
|
|
386
|
+
up: (count = 1) => `${CSI2}1J`.repeat(count),
|
|
387
|
+
down: (count = 1) => `${CSI2}J`.repeat(count),
|
|
388
|
+
line: `${CSI2}2K`,
|
|
389
|
+
lineEnd: `${CSI2}K`,
|
|
390
|
+
lineStart: `${CSI2}1K`,
|
|
391
|
+
lines(count) {
|
|
392
|
+
let clear = "";
|
|
393
|
+
for (let i = 0;i < count; i++)
|
|
394
|
+
clear += this.line + (i < count - 1 ? cursor.up() : "");
|
|
395
|
+
if (count)
|
|
396
|
+
clear += cursor.left;
|
|
397
|
+
return clear;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var clear = {
|
|
401
|
+
screen: `${ESC2}c`
|
|
402
|
+
};
|
|
403
|
+
|
|
442
404
|
// node_modules/@clack/core/dist/index.mjs
|
|
443
|
-
var import_sisteransi = __toESM(require_src(), 1);
|
|
444
405
|
import { ReadStream } from "node:tty";
|
|
445
|
-
function findCursor(
|
|
406
|
+
function findCursor(o, s, l) {
|
|
446
407
|
if (!l.some((r) => !r.disabled))
|
|
447
|
-
return
|
|
448
|
-
const t =
|
|
449
|
-
return l[e]?.disabled ? findCursor(e,
|
|
408
|
+
return o;
|
|
409
|
+
const t = o + s, n = Math.max(l.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
|
|
410
|
+
return l[e]?.disabled ? findCursor(e, s < 0 ? -1 : 1, l) : e;
|
|
450
411
|
}
|
|
451
412
|
var a$1 = ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
452
413
|
var t = [
|
|
@@ -536,43 +497,43 @@ function block({
|
|
|
536
497
|
overwrite: o = true,
|
|
537
498
|
hideCursor: n = true
|
|
538
499
|
} = {}) {
|
|
539
|
-
const s =
|
|
500
|
+
const s = f$1.createInterface({
|
|
540
501
|
input: e,
|
|
541
502
|
output: r,
|
|
542
503
|
prompt: "",
|
|
543
504
|
tabSize: 1
|
|
544
505
|
});
|
|
545
|
-
|
|
546
|
-
const t = (
|
|
547
|
-
const
|
|
548
|
-
if (isActionKey([
|
|
549
|
-
n && r.write(
|
|
506
|
+
f$1.emitKeypressEvents(e, s), e instanceof ReadStream && e.isTTY && e.setRawMode(true);
|
|
507
|
+
const t = (l, { name: i, sequence: p }) => {
|
|
508
|
+
const w = String(l);
|
|
509
|
+
if (isActionKey([w, i, p], "cancel")) {
|
|
510
|
+
n && r.write(cursor.show), process.exit(0);
|
|
550
511
|
return;
|
|
551
512
|
}
|
|
552
513
|
if (!o)
|
|
553
514
|
return;
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
|
|
515
|
+
const c = i === "return" ? 0 : -1, a = i === "return" ? -1 : 0;
|
|
516
|
+
f$1.moveCursor(r, c, a, () => {
|
|
517
|
+
f$1.clearLine(r, 1, () => {
|
|
557
518
|
e.once("keypress", t);
|
|
558
519
|
});
|
|
559
520
|
});
|
|
560
521
|
};
|
|
561
|
-
return n && r.write(
|
|
562
|
-
e.off("keypress", t), n && r.write(
|
|
522
|
+
return n && r.write(cursor.hide), e.once("keypress", t), () => {
|
|
523
|
+
e.off("keypress", t), n && r.write(cursor.show), e instanceof ReadStream && e.isTTY && !R && e.setRawMode(false), s.terminal = false, s.close();
|
|
563
524
|
};
|
|
564
525
|
}
|
|
565
526
|
var getColumns = (e) => ("columns" in e) && typeof e.columns == "number" ? e.columns : 80;
|
|
566
527
|
var getRows = (e) => ("rows" in e) && typeof e.rows == "number" ? e.rows : 20;
|
|
567
528
|
function wrapTextWithPrefix(e, r, o, n = o, s = o, t) {
|
|
568
|
-
const
|
|
569
|
-
return wrapAnsi(r,
|
|
529
|
+
const l = getColumns(e ?? stdout), i = dist_default2(o);
|
|
530
|
+
return wrapAnsi(r, l - i, {
|
|
570
531
|
hard: true,
|
|
571
532
|
trim: false
|
|
572
533
|
}).split(`
|
|
573
|
-
`).map((c,
|
|
574
|
-
const
|
|
575
|
-
return
|
|
534
|
+
`).map((c, a, g) => {
|
|
535
|
+
const m = t ? t(c, a) : c;
|
|
536
|
+
return a === 0 ? `${n}${m}` : a === g.length - 1 ? `${s}${m}` : `${o}${m}`;
|
|
576
537
|
}).join(`
|
|
577
538
|
`);
|
|
578
539
|
}
|
|
@@ -583,8 +544,7 @@ function runValidation(e, a) {
|
|
|
583
544
|
}
|
|
584
545
|
return e(a);
|
|
585
546
|
}
|
|
586
|
-
|
|
587
|
-
class y {
|
|
547
|
+
var C$1 = class C {
|
|
588
548
|
input;
|
|
589
549
|
output;
|
|
590
550
|
_abortSignal;
|
|
@@ -635,16 +595,16 @@ class y {
|
|
|
635
595
|
this.state = "cancel", this.close();
|
|
636
596
|
}, { once: true });
|
|
637
597
|
}
|
|
638
|
-
this.rl =
|
|
598
|
+
this.rl = f__default.createInterface({
|
|
639
599
|
input: this.input,
|
|
640
600
|
tabSize: 2,
|
|
641
601
|
prompt: "",
|
|
642
602
|
escapeCodeTimeout: 50,
|
|
643
603
|
terminal: true
|
|
644
604
|
}), this.rl.prompt(), this.opts.initialUserInput !== undefined && this._setUserInput(this.opts.initialUserInput, true), this.input.on("keypress", this.onKeypress), setRawMode(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
|
|
645
|
-
this.output.write(
|
|
605
|
+
this.output.write(cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t(this.value);
|
|
646
606
|
}), this.once("cancel", () => {
|
|
647
|
-
this.output.write(
|
|
607
|
+
this.output.write(cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t(CANCEL_SYMBOL);
|
|
648
608
|
});
|
|
649
609
|
});
|
|
650
610
|
}
|
|
@@ -681,18 +641,18 @@ class y {
|
|
|
681
641
|
`), setRawMode(this.input, false), this.rl?.close(), this.rl = undefined, this.emit(`${this.state}`, this.value), this.unsubscribe();
|
|
682
642
|
}
|
|
683
643
|
restoreCursor() {
|
|
684
|
-
const t = wrapAnsi(this._prevFrame,
|
|
644
|
+
const t = wrapAnsi(this._prevFrame, getColumns(this.output), { hard: true, trim: false }).split(`
|
|
685
645
|
`).length - 1;
|
|
686
|
-
this.output.write(
|
|
646
|
+
this.output.write(cursor.move(-999, t * -1));
|
|
687
647
|
}
|
|
688
648
|
render() {
|
|
689
|
-
const t = wrapAnsi(this._render(this) ?? "",
|
|
649
|
+
const t = wrapAnsi(this._render(this) ?? "", getColumns(this.output), {
|
|
690
650
|
hard: true,
|
|
691
651
|
trim: false
|
|
692
652
|
});
|
|
693
653
|
if (t !== this._prevFrame) {
|
|
694
654
|
if (this.state === "initial")
|
|
695
|
-
this.output.write(
|
|
655
|
+
this.output.write(cursor.hide);
|
|
696
656
|
else {
|
|
697
657
|
const e = diffLines(this._prevFrame, t), i = getRows(this.output);
|
|
698
658
|
if (this.restoreCursor(), e) {
|
|
@@ -703,33 +663,33 @@ class y {
|
|
|
703
663
|
return;
|
|
704
664
|
}
|
|
705
665
|
if (e.lines.length === 1) {
|
|
706
|
-
this.output.write(
|
|
666
|
+
this.output.write(cursor.move(0, n - r)), this.output.write(erase.lines(1));
|
|
707
667
|
const o = t.split(`
|
|
708
668
|
`);
|
|
709
|
-
this.output.write(o[n]), this._prevFrame = t, this.output.write(
|
|
669
|
+
this.output.write(o[n]), this._prevFrame = t, this.output.write(cursor.move(0, o.length - n - 1));
|
|
710
670
|
return;
|
|
711
671
|
} else if (e.lines.length > 1) {
|
|
712
672
|
if (s < r)
|
|
713
673
|
n = s;
|
|
714
674
|
else {
|
|
715
675
|
const h = n - r;
|
|
716
|
-
h > 0 && this.output.write(
|
|
676
|
+
h > 0 && this.output.write(cursor.move(0, h));
|
|
717
677
|
}
|
|
718
|
-
this.output.write(
|
|
719
|
-
const
|
|
678
|
+
this.output.write(erase.down());
|
|
679
|
+
const m = t.split(`
|
|
720
680
|
`).slice(n);
|
|
721
|
-
this.output.write(
|
|
681
|
+
this.output.write(m.join(`
|
|
722
682
|
`)), this._prevFrame = t;
|
|
723
683
|
return;
|
|
724
684
|
}
|
|
725
685
|
}
|
|
726
|
-
this.output.write(
|
|
686
|
+
this.output.write(erase.down());
|
|
727
687
|
}
|
|
728
688
|
this.output.write(t), this.state === "initial" && (this.state = "active"), this._prevFrame = t;
|
|
729
689
|
}
|
|
730
690
|
}
|
|
731
|
-
}
|
|
732
|
-
class r extends
|
|
691
|
+
};
|
|
692
|
+
class r extends C$1 {
|
|
733
693
|
get cursor() {
|
|
734
694
|
return this.value ? 0 : 1;
|
|
735
695
|
}
|
|
@@ -740,13 +700,13 @@ class r extends y {
|
|
|
740
700
|
super(t, false), this.value = !!t.initialValue, this.on("userInput", () => {
|
|
741
701
|
this.value = this._value;
|
|
742
702
|
}), this.on("confirm", (i) => {
|
|
743
|
-
this.output.write(
|
|
703
|
+
this.output.write(cursor.move(0, -1)), this.value = i, this.state = "submit", this.close();
|
|
744
704
|
}), this.on("cursor", () => {
|
|
745
705
|
this.value = !this.value;
|
|
746
706
|
});
|
|
747
707
|
}
|
|
748
708
|
}
|
|
749
|
-
class a extends
|
|
709
|
+
class a extends C$1 {
|
|
750
710
|
options;
|
|
751
711
|
cursor = 0;
|
|
752
712
|
get _value() {
|
|
@@ -774,8 +734,8 @@ class a extends y {
|
|
|
774
734
|
constructor(e) {
|
|
775
735
|
super(e, false), this.options = e.options, this.value = [...e.initialValues ?? []];
|
|
776
736
|
const i = Math.max(this.options.findIndex(({ value: t }) => t === e.cursorAt), 0);
|
|
777
|
-
this.cursor = this.options[i]?.disabled ? findCursor(i, 1, this.options) : i, this.on("key", (t,
|
|
778
|
-
|
|
737
|
+
this.cursor = this.options[i]?.disabled ? findCursor(i, 1, this.options) : i, this.on("key", (t, s) => {
|
|
738
|
+
s.name === "a" && this.toggleAll(), s.name === "i" && this.toggleInvert(), s.name === undefined && t === " " && this.toggleValue();
|
|
779
739
|
}), this.on("cursor", (t) => {
|
|
780
740
|
switch (t) {
|
|
781
741
|
case "left":
|
|
@@ -793,7 +753,8 @@ class a extends y {
|
|
|
793
753
|
});
|
|
794
754
|
}
|
|
795
755
|
}
|
|
796
|
-
|
|
756
|
+
|
|
757
|
+
class u extends C$1 {
|
|
797
758
|
_mask = "•";
|
|
798
759
|
get cursor() {
|
|
799
760
|
return this._cursor;
|
|
@@ -820,8 +781,8 @@ var u$1 = class u extends y {
|
|
|
820
781
|
this.value === undefined && (this.value = "");
|
|
821
782
|
});
|
|
822
783
|
}
|
|
823
|
-
}
|
|
824
|
-
var n$1 = class n extends
|
|
784
|
+
}
|
|
785
|
+
var n$1 = class n extends C$1 {
|
|
825
786
|
options;
|
|
826
787
|
cursor = 0;
|
|
827
788
|
get _selectedValue() {
|
|
@@ -849,7 +810,7 @@ var n$1 = class n extends y {
|
|
|
849
810
|
});
|
|
850
811
|
}
|
|
851
812
|
};
|
|
852
|
-
class n extends
|
|
813
|
+
class n extends C$1 {
|
|
853
814
|
get userInputWithCursor() {
|
|
854
815
|
if (this.state === "submit")
|
|
855
816
|
return this.userInput;
|
|
@@ -877,7 +838,6 @@ class n extends y {
|
|
|
877
838
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
878
839
|
import { styleText as styleText2, stripVTControlCharacters } from "node:util";
|
|
879
840
|
import process$1 from "node:process";
|
|
880
|
-
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
881
841
|
function isUnicodeSupported() {
|
|
882
842
|
if (process$1.platform !== "win32") {
|
|
883
843
|
return process$1.env.TERM !== "linux";
|
|
@@ -886,8 +846,8 @@ function isUnicodeSupported() {
|
|
|
886
846
|
}
|
|
887
847
|
var unicode = isUnicodeSupported();
|
|
888
848
|
var isCI = () => process.env.CI === "true";
|
|
889
|
-
var isTTY = (
|
|
890
|
-
var unicodeOr = (
|
|
849
|
+
var isTTY = (e) => e.isTTY === true;
|
|
850
|
+
var unicodeOr = (e, o) => unicode ? e : o;
|
|
891
851
|
var S_STEP_ACTIVE = unicodeOr("◆", "*");
|
|
892
852
|
var S_STEP_CANCEL = unicodeOr("■", "x");
|
|
893
853
|
var S_STEP_ERROR = unicodeOr("▲", "x");
|
|
@@ -913,8 +873,8 @@ var S_INFO = unicodeOr("●", "•");
|
|
|
913
873
|
var S_SUCCESS = unicodeOr("◆", "*");
|
|
914
874
|
var S_WARN = unicodeOr("▲", "!");
|
|
915
875
|
var S_ERROR = unicodeOr("■", "x");
|
|
916
|
-
var symbol = (
|
|
917
|
-
switch (
|
|
876
|
+
var symbol = (e) => {
|
|
877
|
+
switch (e) {
|
|
918
878
|
case "initial":
|
|
919
879
|
case "active":
|
|
920
880
|
return styleText2("cyan", S_STEP_ACTIVE);
|
|
@@ -1028,7 +988,7 @@ ${styleText2("gray", S_BAR_END)} ` : "";
|
|
|
1028
988
|
|
|
1029
989
|
`);
|
|
1030
990
|
};
|
|
1031
|
-
var password = (e) => new u
|
|
991
|
+
var password = (e) => new u({
|
|
1032
992
|
validate: e.validate,
|
|
1033
993
|
mask: e.mask ?? S_PASSWORD_MASK,
|
|
1034
994
|
signal: e.signal,
|
|
@@ -1064,85 +1024,103 @@ ${n}
|
|
|
1064
1024
|
}
|
|
1065
1025
|
}
|
|
1066
1026
|
}).prompt();
|
|
1067
|
-
var
|
|
1027
|
+
var Y = 30000;
|
|
1028
|
+
var Z = (c) => styleText2("magenta", c);
|
|
1068
1029
|
var spinner = ({
|
|
1069
|
-
indicator:
|
|
1070
|
-
onCancel:
|
|
1030
|
+
indicator: c = "dots",
|
|
1031
|
+
onCancel: b,
|
|
1071
1032
|
output: n = process.stdout,
|
|
1072
|
-
cancelMessage:
|
|
1073
|
-
errorMessage:
|
|
1074
|
-
frames:
|
|
1075
|
-
delay:
|
|
1076
|
-
signal:
|
|
1077
|
-
...
|
|
1033
|
+
cancelMessage: _,
|
|
1034
|
+
errorMessage: C,
|
|
1035
|
+
frames: M = unicode ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"],
|
|
1036
|
+
delay: O = unicode ? 80 : 120,
|
|
1037
|
+
signal: g,
|
|
1038
|
+
...E
|
|
1078
1039
|
} = {}) => {
|
|
1079
|
-
const u = isCI();
|
|
1080
|
-
let
|
|
1081
|
-
const
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
},
|
|
1085
|
-
process.on("uncaughtExceptionMonitor",
|
|
1086
|
-
},
|
|
1087
|
-
process.removeListener("uncaughtExceptionMonitor",
|
|
1088
|
-
},
|
|
1089
|
-
if (
|
|
1040
|
+
const u = isCI(), h = isAccessible(E.accessible);
|
|
1041
|
+
let x, I, p = false, l = false, r = "", v, w = performance.now();
|
|
1042
|
+
const y = getColumns(n), F = E?.styleFrame ?? Z, S = (e) => {
|
|
1043
|
+
const s = e > 1 ? C ?? settings.messages.error : _ ?? settings.messages.cancel;
|
|
1044
|
+
l = e === 1, p && (m(s, e), l && typeof b == "function" && b());
|
|
1045
|
+
}, $ = () => S(2), a = () => S(1), H = () => {
|
|
1046
|
+
process.on("uncaughtExceptionMonitor", $), process.on("unhandledRejection", $), process.on("SIGINT", a), process.on("SIGTERM", a), process.on("exit", S), g && g.addEventListener("abort", a);
|
|
1047
|
+
}, B = () => {
|
|
1048
|
+
process.removeListener("uncaughtExceptionMonitor", $), process.removeListener("unhandledRejection", $), process.removeListener("SIGINT", a), process.removeListener("SIGTERM", a), process.removeListener("exit", S), g && g.removeEventListener("abort", a);
|
|
1049
|
+
}, R = () => {
|
|
1050
|
+
if (v === undefined)
|
|
1090
1051
|
return;
|
|
1091
1052
|
u && n.write(`
|
|
1092
1053
|
`);
|
|
1093
|
-
const
|
|
1054
|
+
const s = wrapAnsi(v, y, {
|
|
1094
1055
|
hard: true,
|
|
1095
1056
|
trim: false
|
|
1096
1057
|
}).split(`
|
|
1097
1058
|
`);
|
|
1098
|
-
|
|
1099
|
-
},
|
|
1100
|
-
const
|
|
1101
|
-
return
|
|
1102
|
-
}, N =
|
|
1103
|
-
|
|
1059
|
+
s.length > 1 && n.write(cursor.up(s.length - 1)), n.write(cursor.to(0)), n.write(erase.down());
|
|
1060
|
+
}, L = (e) => e.replace(/\.+$/, ""), T = (e) => {
|
|
1061
|
+
const s = (performance.now() - e) / 1000, i = Math.floor(s / 60), t = Math.floor(s % 60);
|
|
1062
|
+
return i > 0 ? `[${i}m ${t}s]` : `[${t}s]`;
|
|
1063
|
+
}, N = E.withGuide ?? settings.withGuide, P = (e = "") => {
|
|
1064
|
+
if (p = true, l = false, r = L(e), w = performance.now(), H(), h) {
|
|
1065
|
+
r !== "" && n.write(`${r}
|
|
1066
|
+
`), I = setInterval(() => {
|
|
1067
|
+
n.write(r === "" ? `still working
|
|
1068
|
+
` : `still working: ${r}
|
|
1104
1069
|
`);
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1070
|
+
}, Y);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
x = block({ output: n }), N && n.write(`${styleText2("gray", S_BAR)}
|
|
1074
|
+
`);
|
|
1075
|
+
let s = 0, i = 0;
|
|
1076
|
+
I = setInterval(() => {
|
|
1077
|
+
if (u && r === v)
|
|
1108
1078
|
return;
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
let
|
|
1079
|
+
R(), v = r;
|
|
1080
|
+
const t = F(M[s]);
|
|
1081
|
+
let o;
|
|
1112
1082
|
if (u)
|
|
1113
|
-
|
|
1114
|
-
else if (
|
|
1115
|
-
|
|
1083
|
+
o = `${t} ${r}...`;
|
|
1084
|
+
else if (c === "timer")
|
|
1085
|
+
o = `${t} ${r} ${T(w)}`;
|
|
1116
1086
|
else {
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1087
|
+
const j = ".".repeat(Math.floor(i)).slice(0, 3);
|
|
1088
|
+
o = `${t} ${r}${j}`;
|
|
1119
1089
|
}
|
|
1120
|
-
const
|
|
1090
|
+
const D = wrapAnsi(o, y, {
|
|
1121
1091
|
hard: true,
|
|
1122
1092
|
trim: false
|
|
1123
1093
|
});
|
|
1124
|
-
n.write(
|
|
1125
|
-
},
|
|
1126
|
-
},
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
`) : n.write(`${o}
|
|
1133
|
-
`)
|
|
1094
|
+
n.write(D), s = s + 1 < M.length ? s + 1 : 0, i = i < 4 ? i + 0.125 : 0;
|
|
1095
|
+
}, O);
|
|
1096
|
+
}, m = (e = "", s = 0, i = false) => {
|
|
1097
|
+
if (p) {
|
|
1098
|
+
if (p = false, l = s === 1, clearInterval(I), h || R(), r = e ?? r, !i)
|
|
1099
|
+
if (h) {
|
|
1100
|
+
const t = s === 1 ? _ ?? settings.messages.cancel : s === 2 ? C ?? settings.messages.error : "Done", o = r || t;
|
|
1101
|
+
c === "timer" ? n.write(`${o} ${T(w)}
|
|
1102
|
+
`) : n.write(`${o}
|
|
1103
|
+
`);
|
|
1104
|
+
} else {
|
|
1105
|
+
const t = s === 0 ? styleText2("green", S_STEP_SUBMIT) : s === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
|
|
1106
|
+
c === "timer" ? n.write(`${t} ${r} ${T(w)}
|
|
1107
|
+
`) : n.write(`${t} ${r}
|
|
1108
|
+
`);
|
|
1109
|
+
}
|
|
1110
|
+
B(), x?.();
|
|
1111
|
+
}
|
|
1134
1112
|
};
|
|
1135
1113
|
return {
|
|
1136
1114
|
start: P,
|
|
1137
|
-
stop: (e = "") =>
|
|
1115
|
+
stop: (e = "") => m(e, 0),
|
|
1138
1116
|
message: (e = "") => {
|
|
1139
|
-
|
|
1117
|
+
r = L(e ?? r);
|
|
1140
1118
|
},
|
|
1141
|
-
cancel: (e = "") =>
|
|
1142
|
-
error: (e = "") =>
|
|
1143
|
-
clear: () =>
|
|
1119
|
+
cancel: (e = "") => m(e, 1),
|
|
1120
|
+
error: (e = "") => m(e, 2),
|
|
1121
|
+
clear: () => m("", 0, true),
|
|
1144
1122
|
get isCancelled() {
|
|
1145
|
-
return
|
|
1123
|
+
return l;
|
|
1146
1124
|
}
|
|
1147
1125
|
};
|
|
1148
1126
|
};
|
|
@@ -1187,7 +1165,9 @@ function progress({
|
|
|
1187
1165
|
error: r.error,
|
|
1188
1166
|
clear: r.clear,
|
|
1189
1167
|
advance: g,
|
|
1190
|
-
isCancelled
|
|
1168
|
+
get isCancelled() {
|
|
1169
|
+
return r.isCancelled;
|
|
1170
|
+
},
|
|
1191
1171
|
message: (t) => g(0, t)
|
|
1192
1172
|
};
|
|
1193
1173
|
}
|
|
@@ -2081,7 +2061,6 @@ function normalizeSourceVersion(value) {
|
|
|
2081
2061
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
2082
2062
|
import { styleText as styleText3, stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
|
|
2083
2063
|
import process$12 from "node:process";
|
|
2084
|
-
var import_sisteransi3 = __toESM(require_src(), 1);
|
|
2085
2064
|
function isUnicodeSupported2() {
|
|
2086
2065
|
if (process$12.platform !== "win32") {
|
|
2087
2066
|
return process$12.env.TERM !== "linux";
|
|
@@ -2090,7 +2069,7 @@ function isUnicodeSupported2() {
|
|
|
2090
2069
|
}
|
|
2091
2070
|
var unicode2 = isUnicodeSupported2();
|
|
2092
2071
|
var isCI2 = () => process.env.CI === "true";
|
|
2093
|
-
var unicodeOr2 = (
|
|
2072
|
+
var unicodeOr2 = (e, o) => unicode2 ? e : o;
|
|
2094
2073
|
var S_STEP_ACTIVE2 = unicodeOr2("◆", "*");
|
|
2095
2074
|
var S_STEP_CANCEL2 = unicodeOr2("■", "x");
|
|
2096
2075
|
var S_STEP_ERROR2 = unicodeOr2("▲", "x");
|
|
@@ -2116,8 +2095,8 @@ var S_INFO2 = unicodeOr2("●", "•");
|
|
|
2116
2095
|
var S_SUCCESS2 = unicodeOr2("◆", "*");
|
|
2117
2096
|
var S_WARN2 = unicodeOr2("▲", "!");
|
|
2118
2097
|
var S_ERROR2 = unicodeOr2("■", "x");
|
|
2119
|
-
var symbol2 = (
|
|
2120
|
-
switch (
|
|
2098
|
+
var symbol2 = (e) => {
|
|
2099
|
+
switch (e) {
|
|
2121
2100
|
case "initial":
|
|
2122
2101
|
case "active":
|
|
2123
2102
|
return styleText3("cyan", S_STEP_ACTIVE2);
|
|
@@ -2131,8 +2110,8 @@ var symbol2 = (o) => {
|
|
|
2131
2110
|
return styleText3("dim", S_STEP_ACTIVE2);
|
|
2132
2111
|
}
|
|
2133
2112
|
};
|
|
2134
|
-
var symbolBar = (
|
|
2135
|
-
switch (
|
|
2113
|
+
var symbolBar = (e) => {
|
|
2114
|
+
switch (e) {
|
|
2136
2115
|
case "initial":
|
|
2137
2116
|
case "active":
|
|
2138
2117
|
return styleText3("cyan", S_BAR2);
|
|
@@ -2144,11 +2123,12 @@ var symbolBar = (o) => {
|
|
|
2144
2123
|
return styleText3("green", S_BAR2);
|
|
2145
2124
|
}
|
|
2146
2125
|
};
|
|
2147
|
-
function formatInstructionFooter(o,
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2126
|
+
function formatInstructionFooter(e, o, r) {
|
|
2127
|
+
const s = r ? `${styleText3("cyan", S_BAR2)} ` : "", n = wrapTextWithPrefix(e, o.join(" • "), s).split(`
|
|
2128
|
+
`);
|
|
2129
|
+
return r && n.push(styleText3("cyan", S_BAR_END2)), n;
|
|
2150
2130
|
}
|
|
2151
|
-
var
|
|
2131
|
+
var E$1 = (l, e, w, p, b, C = false) => {
|
|
2152
2132
|
let r = e, O = 0;
|
|
2153
2133
|
if (C)
|
|
2154
2134
|
for (let i = p - 1;i >= w; i--) {
|
|
@@ -2177,12 +2157,12 @@ var limitOptions = ({
|
|
|
2177
2157
|
let f = 0;
|
|
2178
2158
|
l >= a - 3 && (f = Math.max(Math.min(l - a + 3, e.length - a), 0));
|
|
2179
2159
|
let d = a < e.length && f > 0, c = a < e.length && f + a < e.length;
|
|
2180
|
-
const
|
|
2160
|
+
const I = Math.min(f + a, e.length), s = [];
|
|
2181
2161
|
let g = 0;
|
|
2182
2162
|
d && g++, c && g++;
|
|
2183
|
-
const T = f + (d ? 1 : 0),
|
|
2184
|
-
for (let t = T;t <
|
|
2185
|
-
const n = e[t], o = n ? w(n, t === l) : "", h = wrapAnsi(o, i, {
|
|
2163
|
+
const T = f + (d ? 1 : 0), W = I - (c ? 1 : 0);
|
|
2164
|
+
for (let t = T;t < W; t++) {
|
|
2165
|
+
const n = e[t], o = n !== undefined ? w(n, t === l) : "", h = wrapAnsi(o, i, {
|
|
2186
2166
|
hard: true,
|
|
2187
2167
|
trim: false
|
|
2188
2168
|
}).split(`
|
|
@@ -2193,8 +2173,8 @@ var limitOptions = ({
|
|
|
2193
2173
|
let t = 0, n = 0, o = g;
|
|
2194
2174
|
const h = l - T;
|
|
2195
2175
|
let u = v;
|
|
2196
|
-
const L = () =>
|
|
2197
|
-
d ? ({ lineCount: o, removals: t } = L(), o > u && (c || (u -= 1), { lineCount: o, removals: n } =
|
|
2176
|
+
const L = () => E$1(s, o, 0, h, u), y = () => E$1(s, o, h + 1, s.length, u, true);
|
|
2177
|
+
d ? ({ lineCount: o, removals: t } = L(), o > u && (c || (u -= 1), { lineCount: o, removals: n } = y())) : (c || (u -= 1), { lineCount: o, removals: n } = y(), o > u && (u -= 1, { lineCount: o, removals: t } = L())), t > 0 && (d = true, s.splice(0, t)), n > 0 && (c = true, s.splice(s.length - n, n));
|
|
2198
2178
|
}
|
|
2199
2179
|
const x = [];
|
|
2200
2180
|
d && x.push(M);
|
|
@@ -2243,82 +2223,82 @@ var MULTISELECT_INSTRUCTIONS2 = [
|
|
|
2243
2223
|
`${styleText3("dim", "Space:")} select`,
|
|
2244
2224
|
`${styleText3("dim", "Enter:")} confirm`
|
|
2245
2225
|
];
|
|
2246
|
-
var m = (
|
|
2226
|
+
var m = (n, u) => n.split(`
|
|
2247
2227
|
`).map((d) => u(d)).join(`
|
|
2248
2228
|
`);
|
|
2249
|
-
var multiselect = (
|
|
2229
|
+
var multiselect = (n) => {
|
|
2250
2230
|
const u = (t, a) => {
|
|
2251
2231
|
const r = t.label ?? String(t.value);
|
|
2252
2232
|
return a === "disabled" ? `${styleText3("gray", S_CHECKBOX_INACTIVE2)} ${m(r, (o) => styleText3(["strikethrough", "gray"], o))}${t.hint ? ` ${styleText3("dim", `(${t.hint ?? "disabled"})`)}` : ""}` : a === "active" ? `${styleText3("cyan", S_CHECKBOX_ACTIVE2)} ${r}${t.hint ? ` ${styleText3("dim", `(${t.hint})`)}` : ""}` : a === "selected" ? `${styleText3("green", S_CHECKBOX_SELECTED2)} ${m(r, (o) => styleText3("dim", o))}${t.hint ? ` ${styleText3("dim", `(${t.hint})`)}` : ""}` : a === "cancelled" ? `${m(r, (o) => styleText3(["strikethrough", "dim"], o))}` : a === "active-selected" ? `${styleText3("green", S_CHECKBOX_SELECTED2)} ${r}${t.hint ? ` ${styleText3("dim", `(${t.hint})`)}` : ""}` : a === "submitted" ? `${m(r, (o) => styleText3("dim", o))}` : `${styleText3("dim", S_CHECKBOX_INACTIVE2)} ${m(r, (o) => styleText3("dim", o))}`;
|
|
2253
|
-
}, d =
|
|
2233
|
+
}, d = n.required ?? true, x = n.showInstructions ?? true;
|
|
2254
2234
|
return new a({
|
|
2255
|
-
options:
|
|
2256
|
-
signal:
|
|
2257
|
-
input:
|
|
2258
|
-
output:
|
|
2259
|
-
initialValues:
|
|
2235
|
+
options: n.options,
|
|
2236
|
+
signal: n.signal,
|
|
2237
|
+
input: n.input,
|
|
2238
|
+
output: n.output,
|
|
2239
|
+
initialValues: n.initialValues,
|
|
2260
2240
|
required: d,
|
|
2261
|
-
cursorAt:
|
|
2241
|
+
cursorAt: n.cursorAt,
|
|
2262
2242
|
validate(t) {
|
|
2263
2243
|
if (d && (t === undefined || t.length === 0))
|
|
2264
2244
|
return `Please select at least one option.
|
|
2265
2245
|
${styleText3("reset", styleText3("dim", `Press ${styleText3(["gray", "bgWhite", "inverse"], " space ")} to select, ${styleText3("gray", styleText3("bgWhite", styleText3("inverse", " enter ")))} to submit`))}`;
|
|
2266
2246
|
},
|
|
2267
2247
|
render() {
|
|
2268
|
-
const t =
|
|
2248
|
+
const t = n.withGuide ?? settings.withGuide, a = wrapTextWithPrefix(n.output, n.message, t ? `${symbolBar(this.state)} ` : "", `${symbol2(this.state)} `), r = `${t ? `${styleText3("gray", S_BAR2)}
|
|
2269
2249
|
` : ""}${a}
|
|
2270
|
-
`, o = this.value ?? [], g = (
|
|
2271
|
-
if (
|
|
2272
|
-
return u(
|
|
2273
|
-
const s = o.includes(
|
|
2274
|
-
return l && s ? u(
|
|
2250
|
+
`, o = this.value ?? [], g = (i, l) => {
|
|
2251
|
+
if (i.disabled)
|
|
2252
|
+
return u(i, "disabled");
|
|
2253
|
+
const s = o.includes(i.value);
|
|
2254
|
+
return l && s ? u(i, "active-selected") : s ? u(i, "selected") : u(i, l ? "active" : "inactive");
|
|
2275
2255
|
};
|
|
2276
2256
|
switch (this.state) {
|
|
2277
2257
|
case "submit": {
|
|
2278
|
-
const
|
|
2258
|
+
const i = this.options.filter(({ value: s }) => o.includes(s)).map((s) => u(s, "submitted")).join(styleText3("dim", ", ")) || styleText3("dim", "none"), l = wrapTextWithPrefix(n.output, i, t ? `${styleText3("gray", S_BAR2)} ` : "");
|
|
2279
2259
|
return `${r}${l}`;
|
|
2280
2260
|
}
|
|
2281
2261
|
case "cancel": {
|
|
2282
|
-
const
|
|
2283
|
-
if (
|
|
2262
|
+
const i = this.options.filter(({ value: s }) => o.includes(s)).map((s) => u(s, "cancelled")).join(styleText3("dim", ", "));
|
|
2263
|
+
if (i.trim() === "")
|
|
2284
2264
|
return `${r}${styleText3("gray", S_BAR2)}`;
|
|
2285
|
-
const l = wrapTextWithPrefix(
|
|
2265
|
+
const l = wrapTextWithPrefix(n.output, i, t ? `${styleText3("gray", S_BAR2)} ` : "");
|
|
2286
2266
|
return `${r}${l}${t ? `
|
|
2287
2267
|
${styleText3("gray", S_BAR2)}` : ""}`;
|
|
2288
2268
|
}
|
|
2289
2269
|
case "error": {
|
|
2290
|
-
const
|
|
2270
|
+
const i = t ? `${styleText3("yellow", S_BAR2)} ` : "", l = this.error.split(`
|
|
2291
2271
|
`).map(($, v) => v === 0 ? `${t ? `${styleText3("yellow", S_BAR_END2)} ` : ""}${styleText3("yellow", $)}` : ` ${$}`).join(`
|
|
2292
2272
|
`), s = r.split(`
|
|
2293
2273
|
`).length, h = l.split(`
|
|
2294
2274
|
`).length + 1;
|
|
2295
|
-
return `${r}${
|
|
2296
|
-
output:
|
|
2275
|
+
return `${r}${i}${limitOptions({
|
|
2276
|
+
output: n.output,
|
|
2297
2277
|
options: this.options,
|
|
2298
2278
|
cursor: this.cursor,
|
|
2299
|
-
maxItems:
|
|
2300
|
-
columnPadding:
|
|
2279
|
+
maxItems: n.maxItems,
|
|
2280
|
+
columnPadding: i.length,
|
|
2301
2281
|
rowPadding: s + h,
|
|
2302
2282
|
style: g
|
|
2303
2283
|
}).join(`
|
|
2304
|
-
${
|
|
2284
|
+
${i}`)}
|
|
2305
2285
|
${l}
|
|
2306
2286
|
`;
|
|
2307
2287
|
}
|
|
2308
2288
|
default: {
|
|
2309
|
-
const
|
|
2310
|
-
`).length, s = x ? formatInstructionFooter(MULTISELECT_INSTRUCTIONS2, t) : t ? [styleText3("cyan", S_BAR_END2)] : [], h = s.join(`
|
|
2289
|
+
const i = t ? `${styleText3("cyan", S_BAR2)} ` : "", l = r.split(`
|
|
2290
|
+
`).length, s = x ? formatInstructionFooter(n.output, MULTISELECT_INSTRUCTIONS2, t) : t ? [styleText3("cyan", S_BAR_END2)] : [], h = s.join(`
|
|
2311
2291
|
`), $ = s.length + 1;
|
|
2312
|
-
return `${r}${
|
|
2313
|
-
output:
|
|
2292
|
+
return `${r}${i}${limitOptions({
|
|
2293
|
+
output: n.output,
|
|
2314
2294
|
options: this.options,
|
|
2315
2295
|
cursor: this.cursor,
|
|
2316
|
-
maxItems:
|
|
2317
|
-
columnPadding:
|
|
2296
|
+
maxItems: n.maxItems,
|
|
2297
|
+
columnPadding: i.length,
|
|
2318
2298
|
rowPadding: l + $,
|
|
2319
2299
|
style: g
|
|
2320
2300
|
}).join(`
|
|
2321
|
-
${
|
|
2301
|
+
${i}`)}
|
|
2322
2302
|
${h}
|
|
2323
2303
|
`;
|
|
2324
2304
|
}
|
|
@@ -2386,85 +2366,103 @@ ${styleText3("gray", S_BAR_END2)} ` : "";
|
|
|
2386
2366
|
|
|
2387
2367
|
`);
|
|
2388
2368
|
};
|
|
2389
|
-
var
|
|
2369
|
+
var Y2 = 30000;
|
|
2370
|
+
var Z2 = (c) => styleText3("magenta", c);
|
|
2390
2371
|
var spinner2 = ({
|
|
2391
|
-
indicator:
|
|
2392
|
-
onCancel:
|
|
2372
|
+
indicator: c = "dots",
|
|
2373
|
+
onCancel: b,
|
|
2393
2374
|
output: n = process.stdout,
|
|
2394
|
-
cancelMessage:
|
|
2395
|
-
errorMessage:
|
|
2396
|
-
frames:
|
|
2397
|
-
delay:
|
|
2398
|
-
signal:
|
|
2399
|
-
...
|
|
2375
|
+
cancelMessage: _,
|
|
2376
|
+
errorMessage: C,
|
|
2377
|
+
frames: M = unicode2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"],
|
|
2378
|
+
delay: O = unicode2 ? 80 : 120,
|
|
2379
|
+
signal: g,
|
|
2380
|
+
...E
|
|
2400
2381
|
} = {}) => {
|
|
2401
|
-
const u = isCI2();
|
|
2402
|
-
let
|
|
2403
|
-
const
|
|
2404
|
-
const
|
|
2405
|
-
|
|
2406
|
-
},
|
|
2407
|
-
process.on("uncaughtExceptionMonitor",
|
|
2408
|
-
},
|
|
2409
|
-
process.removeListener("uncaughtExceptionMonitor",
|
|
2410
|
-
},
|
|
2411
|
-
if (
|
|
2382
|
+
const u = isCI2(), h = isAccessible(E.accessible);
|
|
2383
|
+
let x, I, p = false, l = false, r = "", v, w = performance.now();
|
|
2384
|
+
const y = getColumns(n), F = E?.styleFrame ?? Z2, S = (e) => {
|
|
2385
|
+
const s = e > 1 ? C ?? settings.messages.error : _ ?? settings.messages.cancel;
|
|
2386
|
+
l = e === 1, p && (m(s, e), l && typeof b == "function" && b());
|
|
2387
|
+
}, $ = () => S(2), a = () => S(1), H = () => {
|
|
2388
|
+
process.on("uncaughtExceptionMonitor", $), process.on("unhandledRejection", $), process.on("SIGINT", a), process.on("SIGTERM", a), process.on("exit", S), g && g.addEventListener("abort", a);
|
|
2389
|
+
}, B = () => {
|
|
2390
|
+
process.removeListener("uncaughtExceptionMonitor", $), process.removeListener("unhandledRejection", $), process.removeListener("SIGINT", a), process.removeListener("SIGTERM", a), process.removeListener("exit", S), g && g.removeEventListener("abort", a);
|
|
2391
|
+
}, R = () => {
|
|
2392
|
+
if (v === undefined)
|
|
2412
2393
|
return;
|
|
2413
2394
|
u && n.write(`
|
|
2414
2395
|
`);
|
|
2415
|
-
const
|
|
2396
|
+
const s = wrapAnsi(v, y, {
|
|
2416
2397
|
hard: true,
|
|
2417
2398
|
trim: false
|
|
2418
2399
|
}).split(`
|
|
2419
2400
|
`);
|
|
2420
|
-
|
|
2421
|
-
},
|
|
2422
|
-
const
|
|
2423
|
-
return
|
|
2424
|
-
}, N =
|
|
2425
|
-
|
|
2401
|
+
s.length > 1 && n.write(cursor.up(s.length - 1)), n.write(cursor.to(0)), n.write(erase.down());
|
|
2402
|
+
}, L = (e) => e.replace(/\.+$/, ""), T = (e) => {
|
|
2403
|
+
const s = (performance.now() - e) / 1000, i = Math.floor(s / 60), t = Math.floor(s % 60);
|
|
2404
|
+
return i > 0 ? `[${i}m ${t}s]` : `[${t}s]`;
|
|
2405
|
+
}, N = E.withGuide ?? settings.withGuide, P = (e = "") => {
|
|
2406
|
+
if (p = true, l = false, r = L(e), w = performance.now(), H(), h) {
|
|
2407
|
+
r !== "" && n.write(`${r}
|
|
2408
|
+
`), I = setInterval(() => {
|
|
2409
|
+
n.write(r === "" ? `still working
|
|
2410
|
+
` : `still working: ${r}
|
|
2426
2411
|
`);
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2412
|
+
}, Y2);
|
|
2413
|
+
return;
|
|
2414
|
+
}
|
|
2415
|
+
x = block({ output: n }), N && n.write(`${styleText3("gray", S_BAR2)}
|
|
2416
|
+
`);
|
|
2417
|
+
let s = 0, i = 0;
|
|
2418
|
+
I = setInterval(() => {
|
|
2419
|
+
if (u && r === v)
|
|
2430
2420
|
return;
|
|
2431
|
-
|
|
2432
|
-
const
|
|
2433
|
-
let
|
|
2421
|
+
R(), v = r;
|
|
2422
|
+
const t = F(M[s]);
|
|
2423
|
+
let o;
|
|
2434
2424
|
if (u)
|
|
2435
|
-
|
|
2436
|
-
else if (
|
|
2437
|
-
|
|
2425
|
+
o = `${t} ${r}...`;
|
|
2426
|
+
else if (c === "timer")
|
|
2427
|
+
o = `${t} ${r} ${T(w)}`;
|
|
2438
2428
|
else {
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2429
|
+
const j = ".".repeat(Math.floor(i)).slice(0, 3);
|
|
2430
|
+
o = `${t} ${r}${j}`;
|
|
2441
2431
|
}
|
|
2442
|
-
const
|
|
2432
|
+
const D = wrapAnsi(o, y, {
|
|
2443
2433
|
hard: true,
|
|
2444
2434
|
trim: false
|
|
2445
2435
|
});
|
|
2446
|
-
n.write(
|
|
2447
|
-
},
|
|
2448
|
-
},
|
|
2449
|
-
if (
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
`) : n.write(`${o}
|
|
2455
|
-
`)
|
|
2436
|
+
n.write(D), s = s + 1 < M.length ? s + 1 : 0, i = i < 4 ? i + 0.125 : 0;
|
|
2437
|
+
}, O);
|
|
2438
|
+
}, m = (e = "", s = 0, i = false) => {
|
|
2439
|
+
if (p) {
|
|
2440
|
+
if (p = false, l = s === 1, clearInterval(I), h || R(), r = e ?? r, !i)
|
|
2441
|
+
if (h) {
|
|
2442
|
+
const t = s === 1 ? _ ?? settings.messages.cancel : s === 2 ? C ?? settings.messages.error : "Done", o = r || t;
|
|
2443
|
+
c === "timer" ? n.write(`${o} ${T(w)}
|
|
2444
|
+
`) : n.write(`${o}
|
|
2445
|
+
`);
|
|
2446
|
+
} else {
|
|
2447
|
+
const t = s === 0 ? styleText3("green", S_STEP_SUBMIT2) : s === 1 ? styleText3("red", S_STEP_CANCEL2) : styleText3("red", S_STEP_ERROR2);
|
|
2448
|
+
c === "timer" ? n.write(`${t} ${r} ${T(w)}
|
|
2449
|
+
`) : n.write(`${t} ${r}
|
|
2450
|
+
`);
|
|
2451
|
+
}
|
|
2452
|
+
B(), x?.();
|
|
2453
|
+
}
|
|
2456
2454
|
};
|
|
2457
2455
|
return {
|
|
2458
2456
|
start: P,
|
|
2459
|
-
stop: (e = "") =>
|
|
2457
|
+
stop: (e = "") => m(e, 0),
|
|
2460
2458
|
message: (e = "") => {
|
|
2461
|
-
|
|
2459
|
+
r = L(e ?? r);
|
|
2462
2460
|
},
|
|
2463
|
-
cancel: (e = "") =>
|
|
2464
|
-
error: (e = "") =>
|
|
2465
|
-
clear: () =>
|
|
2461
|
+
cancel: (e = "") => m(e, 1),
|
|
2462
|
+
error: (e = "") => m(e, 2),
|
|
2463
|
+
clear: () => m("", 0, true),
|
|
2466
2464
|
get isCancelled() {
|
|
2467
|
-
return
|
|
2465
|
+
return l;
|
|
2468
2466
|
}
|
|
2469
2467
|
};
|
|
2470
2468
|
};
|
|
@@ -2477,7 +2475,7 @@ var SELECT_INSTRUCTIONS2 = [
|
|
|
2477
2475
|
`${styleText3("dim", "↑/↓")} to navigate`,
|
|
2478
2476
|
`${styleText3("dim", "Enter:")} confirm`
|
|
2479
2477
|
];
|
|
2480
|
-
var
|
|
2478
|
+
var c2 = (t, o) => t.includes(`
|
|
2481
2479
|
`) ? t.split(`
|
|
2482
2480
|
`).map((d) => o(d)).join(`
|
|
2483
2481
|
`) : o(t);
|
|
@@ -2488,15 +2486,15 @@ var select = (t) => {
|
|
|
2488
2486
|
const s = n.label ?? String(n.value);
|
|
2489
2487
|
switch (m) {
|
|
2490
2488
|
case "disabled":
|
|
2491
|
-
return `${styleText3("gray", S_RADIO_INACTIVE2)} ${
|
|
2489
|
+
return `${styleText3("gray", S_RADIO_INACTIVE2)} ${c2(s, (i) => styleText3("gray", i))}${n.hint ? ` ${styleText3("dim", `(${n.hint ?? "disabled"})`)}` : ""}`;
|
|
2492
2490
|
case "selected":
|
|
2493
|
-
return `${
|
|
2491
|
+
return `${c2(s, (i) => styleText3("dim", i))}`;
|
|
2494
2492
|
case "active":
|
|
2495
2493
|
return `${styleText3("green", S_RADIO_ACTIVE2)} ${s}${n.hint ? ` ${styleText3("dim", `(${n.hint})`)}` : ""}`;
|
|
2496
2494
|
case "cancelled":
|
|
2497
|
-
return `${
|
|
2495
|
+
return `${c2(s, (i) => styleText3(["strikethrough", "dim"], i))}`;
|
|
2498
2496
|
default:
|
|
2499
|
-
return `${styleText3("dim", S_RADIO_INACTIVE2)} ${
|
|
2497
|
+
return `${styleText3("dim", S_RADIO_INACTIVE2)} ${c2(s, (i) => styleText3("dim", i))}`;
|
|
2500
2498
|
}
|
|
2501
2499
|
}, d = t.showInstructions ?? true;
|
|
2502
2500
|
return new n$1({
|
|
@@ -2521,7 +2519,7 @@ ${styleText3("gray", S_BAR2)}` : ""}`;
|
|
|
2521
2519
|
}
|
|
2522
2520
|
default: {
|
|
2523
2521
|
const r = n ? `${styleText3("cyan", S_BAR2)} ` : "", a = u.split(`
|
|
2524
|
-
`).length, p = d ? formatInstructionFooter(SELECT_INSTRUCTIONS2, n) : n ? [styleText3("cyan", S_BAR_END2)] : [], f = p.join(`
|
|
2522
|
+
`).length, p = d ? formatInstructionFooter(t.output, SELECT_INSTRUCTIONS2, n) : n ? [styleText3("cyan", S_BAR_END2)] : [], f = p.join(`
|
|
2525
2523
|
`), b = p.length + 1;
|
|
2526
2524
|
return `${u}${r}${limitOptions({
|
|
2527
2525
|
output: t.output,
|
|
@@ -2951,24 +2949,37 @@ import { join as join3 } from "node:path";
|
|
|
2951
2949
|
var DEPLOYMENT_ENVIRONMENT_RE = /^[a-z][a-z0-9-]{1,30}$/;
|
|
2952
2950
|
var REGION_RE = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
2953
2951
|
var DOMAIN_RE = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
2954
|
-
var deploymentClouds = ["aws", "gcp", "
|
|
2955
|
-
var
|
|
2952
|
+
var deploymentClouds = ["aws", "gcp", "railway"];
|
|
2953
|
+
var openTofuClouds = ["aws", "gcp"];
|
|
2954
|
+
var frontendTargets = ["native", "cloudflare"];
|
|
2956
2955
|
var storageProviders = ["r2", "s3", "gcs"];
|
|
2957
2956
|
var defaultRegions = {
|
|
2958
2957
|
aws: "us-east-1",
|
|
2959
|
-
gcp: "us-central1"
|
|
2960
|
-
azure: "eastus2"
|
|
2958
|
+
gcp: "us-central1"
|
|
2961
2959
|
};
|
|
2962
2960
|
var defaultStorageProviders = {
|
|
2963
2961
|
aws: "s3",
|
|
2964
2962
|
gcp: "gcs",
|
|
2965
|
-
|
|
2963
|
+
railway: "r2"
|
|
2966
2964
|
};
|
|
2967
2965
|
var storageProvidersByCloud = {
|
|
2968
2966
|
aws: ["s3", "r2"],
|
|
2969
2967
|
gcp: ["gcs", "r2"],
|
|
2970
|
-
|
|
2968
|
+
railway: ["r2"]
|
|
2969
|
+
};
|
|
2970
|
+
var frontendTargetsByCloud = {
|
|
2971
|
+
aws: ["native", "cloudflare"],
|
|
2972
|
+
gcp: ["native", "cloudflare"],
|
|
2973
|
+
railway: ["cloudflare"]
|
|
2974
|
+
};
|
|
2975
|
+
var cloudLabels = {
|
|
2976
|
+
aws: "AWS",
|
|
2977
|
+
gcp: "GCP",
|
|
2978
|
+
railway: "Railway"
|
|
2971
2979
|
};
|
|
2980
|
+
function isOpenTofuCloud(cloud) {
|
|
2981
|
+
return openTofuClouds.some((candidate) => candidate === cloud);
|
|
2982
|
+
}
|
|
2972
2983
|
function isDeploymentCloud(value) {
|
|
2973
2984
|
return deploymentClouds.some((cloud) => cloud === value);
|
|
2974
2985
|
}
|
|
@@ -2980,12 +2991,12 @@ function isStorageProvider(value) {
|
|
|
2980
2991
|
}
|
|
2981
2992
|
function parseDeploymentCloud(value) {
|
|
2982
2993
|
if (!isDeploymentCloud(value))
|
|
2983
|
-
throw new Error("Cloud must be aws, gcp, or
|
|
2994
|
+
throw new Error("Cloud must be aws, gcp, or railway.");
|
|
2984
2995
|
return value;
|
|
2985
2996
|
}
|
|
2986
2997
|
function parseFrontendTarget(value) {
|
|
2987
2998
|
if (!isFrontendTarget(value)) {
|
|
2988
|
-
throw new Error("Frontend target must be native
|
|
2999
|
+
throw new Error("Frontend target must be native or cloudflare.");
|
|
2989
3000
|
}
|
|
2990
3001
|
return value;
|
|
2991
3002
|
}
|
|
@@ -3016,12 +3027,24 @@ function validateDeploymentSelection(selection) {
|
|
|
3016
3027
|
if (!storageProvidersByCloud[selection.cloud].includes(selection.storageProvider)) {
|
|
3017
3028
|
throw new Error(`${selection.storageProvider} storage is not supported for ${selection.cloud} deployments.`);
|
|
3018
3029
|
}
|
|
3030
|
+
if (!frontendTargetsByCloud[selection.cloud].includes(selection.frontendTarget)) {
|
|
3031
|
+
throw new Error(`The ${selection.frontendTarget} frontend is not supported for ${selection.cloud} deployments.`);
|
|
3032
|
+
}
|
|
3033
|
+
if (isOpenTofuCloud(selection.cloud)) {
|
|
3034
|
+
const regionError = validateRegion(selection.region ?? "");
|
|
3035
|
+
if (regionError)
|
|
3036
|
+
throw new Error(regionError);
|
|
3037
|
+
} else {
|
|
3038
|
+
if (selection.region !== null) {
|
|
3039
|
+
throw new Error("Railway selects its region per service; omit the region.");
|
|
3040
|
+
}
|
|
3041
|
+
if (selection.haEnabled) {
|
|
3042
|
+
throw new Error("The higher-availability profile applies to AWS and GCP only.");
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3019
3045
|
const environmentError = validateDeploymentEnvironment(selection.environment);
|
|
3020
3046
|
if (environmentError)
|
|
3021
3047
|
throw new Error(environmentError);
|
|
3022
|
-
const regionError = validateRegion(selection.region);
|
|
3023
|
-
if (regionError)
|
|
3024
|
-
throw new Error(regionError);
|
|
3025
3048
|
const apiError = validateDomain(selection.apiDomain);
|
|
3026
3049
|
if (apiError)
|
|
3027
3050
|
throw new Error(`API domain: ${apiError}`);
|
|
@@ -3053,20 +3076,86 @@ function createDeploymentConfig(projectName, selection) {
|
|
|
3053
3076
|
ha_enabled: selection.haEnabled
|
|
3054
3077
|
};
|
|
3055
3078
|
}
|
|
3056
|
-
function
|
|
3057
|
-
|
|
3058
|
-
|
|
3079
|
+
function lockSummary(config) {
|
|
3080
|
+
return `The deployment target is locked:
|
|
3081
|
+
|
|
3082
|
+
- backend: **${cloudLabels[config.cloud]}**;
|
|
3083
|
+
- storage: **${config.storage_provider}**;
|
|
3084
|
+
- frontend: **${config.frontend_target}**;
|
|
3085
|
+
- environment: **${config.environment}**.`;
|
|
3086
|
+
}
|
|
3087
|
+
function railwayReadme(config) {
|
|
3088
|
+
return `# Deployment setup
|
|
3089
|
+
|
|
3090
|
+
${lockSummary(config)}
|
|
3091
|
+
|
|
3092
|
+
The production hosts are:
|
|
3093
|
+
|
|
3094
|
+
- API: \`${config.api_domain}\`;
|
|
3095
|
+
- frontend: \`${config.frontend_domain}\`.
|
|
3096
|
+
|
|
3097
|
+
## Railway API and PostgreSQL
|
|
3098
|
+
|
|
3099
|
+
1. Create a Railway project with a PostgreSQL service and an API service connected to this
|
|
3100
|
+
repository. Build from the repository root, because the image copies the whole workspace.
|
|
3101
|
+
2. Set the API service's config-as-code path to \`apps/server/railway.toml\`. It builds
|
|
3102
|
+
\`apps/server/Dockerfile\`, runs \`./server migrate\` before each deploy, starts \`./server serve\`,
|
|
3103
|
+
and admits traffic only after \`/ready\` succeeds.
|
|
3104
|
+
3. Set these API service variables in the \`${config.environment}\` Railway environment:
|
|
3105
|
+
|
|
3106
|
+
\`\`\`text
|
|
3107
|
+
DATABASE_URL=\${{Postgres.DATABASE_URL}}
|
|
3108
|
+
ENVIRONMENT=production
|
|
3109
|
+
TRUSTED_PROXY_PROFILE=railway
|
|
3110
|
+
DEPLOYMENT_CLOUD=railway
|
|
3111
|
+
FRONTEND_URL=https://${config.frontend_domain}
|
|
3112
|
+
SERVER_URL=https://${config.api_domain}
|
|
3113
|
+
\`\`\`
|
|
3114
|
+
|
|
3115
|
+
Add \`BETTER_AUTH_SECRET\`, \`RESEND_API_KEY\`, \`EMAIL_FROM\`, \`APP_DB_POOL_MAX\`, and
|
|
3116
|
+
\`AUTH_DB_POOL_MAX\`, then the Cloudflare R2 storage variables and every enabled billing,
|
|
3117
|
+
monitoring, and OAuth value. Railway injects \`PORT\`; leave it unset.
|
|
3118
|
+
|
|
3119
|
+
4. Add the API host as the service's custom domain and publish the DNS records Railway reports. A
|
|
3120
|
+
Cloudflare-managed zone keeps them \`DNS only\`.
|
|
3121
|
+
|
|
3122
|
+
## Cloudflare frontend
|
|
3123
|
+
|
|
3124
|
+
1. Create a Cloudflare Workers project rooted at \`apps/web\`. It uses \`apps/web/wrangler.toml\`, which
|
|
3125
|
+
serves \`dist\` as static assets with single-page-application fallback.
|
|
3126
|
+
2. Build with \`pnpm run build:prod\` and these variables:
|
|
3127
|
+
|
|
3128
|
+
\`\`\`text
|
|
3129
|
+
VITE_APP_ENV=production
|
|
3130
|
+
VITE_API_URL=https://${config.api_domain}
|
|
3131
|
+
\`\`\`
|
|
3132
|
+
|
|
3133
|
+
Add \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3134
|
+
public marketing site.
|
|
3135
|
+
|
|
3136
|
+
3. Attach the frontend host as the Worker's custom domain.
|
|
3137
|
+
|
|
3138
|
+
## Verify
|
|
3139
|
+
|
|
3140
|
+
\`\`\`sh
|
|
3141
|
+
API_ORIGIN=https://${config.api_domain} \\
|
|
3142
|
+
WEB_ORIGIN=https://${config.frontend_domain} \\
|
|
3143
|
+
bash scripts/verify-public-deployment.sh
|
|
3144
|
+
\`\`\`
|
|
3145
|
+
|
|
3146
|
+
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3147
|
+
before changing providers.
|
|
3148
|
+
`;
|
|
3149
|
+
}
|
|
3150
|
+
function openTofuReadme(config) {
|
|
3151
|
+
const identity = config.cloud === "aws" ? "`AWS_ROLE_ARN` secret" : "`GCP_WORKLOAD_IDENTITY_PROVIDER` and `GCP_SERVICE_ACCOUNT` secrets";
|
|
3152
|
+
const providerInputs = config.cloud === "aws" ? "Route 53 zone or certificate identifiers and runtime Secrets Manager ARNs" : "GCP project, Cloud DNS zone, and runtime Secret Manager names";
|
|
3059
3153
|
const storageSetup = config.storage_provider === "r2" ? `Create private staging and public Cloudflare R2 buckets, then store the required storage
|
|
3060
3154
|
variables in the runtime secret container.` : `${config.storage_provider.toUpperCase()} application buckets, lifecycle policy, CORS, public access, and workload permissions are
|
|
3061
3155
|
managed by the selected OpenTofu root.`;
|
|
3062
3156
|
return `# Deployment setup
|
|
3063
3157
|
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
- backend: **${config.cloud.toUpperCase()}**;
|
|
3067
|
-
- storage: **${config.storage_provider}**;
|
|
3068
|
-
- frontend: **${config.frontend_target}**;
|
|
3069
|
-
- environment: **${config.environment}**.
|
|
3158
|
+
${lockSummary(config)}
|
|
3070
3159
|
|
|
3071
3160
|
## Protected GitHub environment
|
|
3072
3161
|
|
|
@@ -3076,8 +3165,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
3076
3165
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
3077
3166
|
- a \`TFVARS_JSON\` secret containing only:
|
|
3078
3167
|
- ${providerInputs};
|
|
3079
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3080
|
-
|
|
3168
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a public
|
|
3169
|
+
marketing site.
|
|
3081
3170
|
|
|
3082
3171
|
## Cloud preparation
|
|
3083
3172
|
|
|
@@ -3098,9 +3187,33 @@ The workflow creates the registry first, pushes an immutable image, provisions t
|
|
|
3098
3187
|
traffic disabled, runs migrations, and admits traffic only after migration succeeds.
|
|
3099
3188
|
|
|
3100
3189
|
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3101
|
-
before changing
|
|
3190
|
+
before changing providers.
|
|
3102
3191
|
`;
|
|
3103
3192
|
}
|
|
3193
|
+
function deploymentReadme(config) {
|
|
3194
|
+
return config.cloud === "railway" ? railwayReadme(config) : openTofuReadme(config);
|
|
3195
|
+
}
|
|
3196
|
+
function openTofuPaths(cloud) {
|
|
3197
|
+
return [
|
|
3198
|
+
`infra/bootstrap/${cloud}`,
|
|
3199
|
+
`infra/modules/${cloud}`,
|
|
3200
|
+
`infra/roots/${cloud}`,
|
|
3201
|
+
`scripts/deploy/release-${cloud}.sh`,
|
|
3202
|
+
`scripts/deploy/frontend-${cloud}.sh`
|
|
3203
|
+
];
|
|
3204
|
+
}
|
|
3205
|
+
function unusedDeploymentPaths(selection) {
|
|
3206
|
+
const paths = selection.cloud === "railway" ? ["infra", "scripts/deploy", ".github/workflows/deploy-cloud.yml"] : [
|
|
3207
|
+
...openTofuClouds.filter((cloud) => cloud !== selection.cloud).flatMap(openTofuPaths),
|
|
3208
|
+
"apps/server/railway.toml"
|
|
3209
|
+
];
|
|
3210
|
+
if (selection.frontendTarget === "native")
|
|
3211
|
+
paths.push("apps/web/wrangler.toml");
|
|
3212
|
+
else if (selection.cloud !== "railway") {
|
|
3213
|
+
paths.push(`scripts/deploy/frontend-${selection.cloud}.sh`);
|
|
3214
|
+
}
|
|
3215
|
+
return paths;
|
|
3216
|
+
}
|
|
3104
3217
|
async function removeIfPresent(target) {
|
|
3105
3218
|
if (!await stat2(target).catch(() => {
|
|
3106
3219
|
return;
|
|
@@ -3109,6 +3222,20 @@ async function removeIfPresent(target) {
|
|
|
3109
3222
|
await rm(target, { recursive: true, force: true });
|
|
3110
3223
|
return true;
|
|
3111
3224
|
}
|
|
3225
|
+
async function removeInfrastructureScript(repositoryRoot) {
|
|
3226
|
+
const packagePath = join3(repositoryRoot, "package.json");
|
|
3227
|
+
const content = await readFile2(packagePath, "utf8").catch(() => {
|
|
3228
|
+
return;
|
|
3229
|
+
});
|
|
3230
|
+
if (!content)
|
|
3231
|
+
return;
|
|
3232
|
+
const rootPackage = JSON.parse(content);
|
|
3233
|
+
if (!rootPackage.scripts?.["verify:infra"])
|
|
3234
|
+
return;
|
|
3235
|
+
delete rootPackage.scripts["verify:infra"];
|
|
3236
|
+
await writeFile2(packagePath, `${JSON.stringify(rootPackage, null, 2)}
|
|
3237
|
+
`);
|
|
3238
|
+
}
|
|
3112
3239
|
async function applyDeploymentSelection(repositoryRoot, projectName, selection) {
|
|
3113
3240
|
const config = createDeploymentConfig(projectName, selection);
|
|
3114
3241
|
const configPath = join3(repositoryRoot, "deployment/config.json");
|
|
@@ -3122,40 +3249,12 @@ async function applyDeploymentSelection(repositoryRoot, projectName, selection)
|
|
|
3122
3249
|
`);
|
|
3123
3250
|
await writeFile2(join3(repositoryRoot, "deployment/README.md"), deploymentReadme(config));
|
|
3124
3251
|
const removed = [];
|
|
3125
|
-
for (const
|
|
3126
|
-
if (
|
|
3127
|
-
|
|
3128
|
-
for (const relativePath of [
|
|
3129
|
-
`infra/bootstrap/${cloud}`,
|
|
3130
|
-
`infra/modules/${cloud}`,
|
|
3131
|
-
`infra/roots/${cloud}`,
|
|
3132
|
-
`scripts/deploy/release-${cloud}.sh`,
|
|
3133
|
-
`scripts/deploy/frontend-${cloud}.sh`
|
|
3134
|
-
]) {
|
|
3135
|
-
if (await removeIfPresent(join3(repositoryRoot, relativePath)))
|
|
3136
|
-
removed.push(relativePath);
|
|
3137
|
-
}
|
|
3138
|
-
}
|
|
3139
|
-
const adapters = [
|
|
3140
|
-
{ target: "vercel", path: "apps/web/vercel.json" },
|
|
3141
|
-
{ target: "cloudflare", path: "apps/web/wrangler.toml" },
|
|
3142
|
-
{
|
|
3143
|
-
target: "azure-native",
|
|
3144
|
-
path: "apps/web/public/staticwebapp.config.json"
|
|
3145
|
-
}
|
|
3146
|
-
];
|
|
3147
|
-
const selectedAdapter = selection.frontendTarget === "native" ? selection.cloud === "azure" ? "azure-native" : undefined : selection.frontendTarget;
|
|
3148
|
-
for (const adapter of adapters) {
|
|
3149
|
-
if (adapter.target === selectedAdapter)
|
|
3150
|
-
continue;
|
|
3151
|
-
if (await removeIfPresent(join3(repositoryRoot, adapter.path)))
|
|
3152
|
-
removed.push(adapter.path);
|
|
3153
|
-
}
|
|
3154
|
-
if (selection.frontendTarget !== "native") {
|
|
3155
|
-
const nativeScript = `scripts/deploy/frontend-${selection.cloud}.sh`;
|
|
3156
|
-
if (await removeIfPresent(join3(repositoryRoot, nativeScript)))
|
|
3157
|
-
removed.push(nativeScript);
|
|
3252
|
+
for (const relativePath of unusedDeploymentPaths(selection)) {
|
|
3253
|
+
if (await removeIfPresent(join3(repositoryRoot, relativePath)))
|
|
3254
|
+
removed.push(relativePath);
|
|
3158
3255
|
}
|
|
3256
|
+
if (removed.includes("infra"))
|
|
3257
|
+
await removeInfrastructureScript(repositoryRoot);
|
|
3159
3258
|
return removed;
|
|
3160
3259
|
}
|
|
3161
3260
|
|
|
@@ -3174,37 +3273,31 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3174
3273
|
if (!defaults.cloud || !defaults.apiDomain || !defaults.frontendDomain) {
|
|
3175
3274
|
throw new Error("Non-interactive deployment setup requires --cloud, --api-domain, and --frontend-domain.");
|
|
3176
3275
|
}
|
|
3276
|
+
const cloud = defaults.cloud;
|
|
3177
3277
|
return {
|
|
3178
|
-
cloud
|
|
3179
|
-
storageProvider: defaults.storageProvider ?? defaultStorageProviders[
|
|
3180
|
-
frontendTarget: defaults.frontendTarget ?? "
|
|
3278
|
+
cloud,
|
|
3279
|
+
storageProvider: defaults.storageProvider ?? defaultStorageProviders[cloud],
|
|
3280
|
+
frontendTarget: defaults.frontendTarget ?? frontendTargetsByCloud[cloud][0] ?? "cloudflare",
|
|
3181
3281
|
environment: defaults.environment ?? "production",
|
|
3182
|
-
region: defaults.region ?? defaultRegions[defaults.
|
|
3282
|
+
region: isOpenTofuCloud(cloud) ? defaults.region ?? defaultRegions[cloud] : defaults.region ?? null,
|
|
3183
3283
|
apiDomain: defaults.apiDomain,
|
|
3184
3284
|
frontendDomain: defaults.frontendDomain,
|
|
3185
3285
|
haEnabled: defaults.haEnabled ?? false
|
|
3186
3286
|
};
|
|
3187
3287
|
}
|
|
3188
3288
|
const cloud = defaults.cloud ?? parseDeploymentCloud(await unwrap(await select({
|
|
3189
|
-
message: "Backend
|
|
3289
|
+
message: "Backend host",
|
|
3190
3290
|
options: [
|
|
3191
|
-
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL" },
|
|
3192
|
-
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL" },
|
|
3193
|
-
{ value: "
|
|
3291
|
+
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL, OpenTofu" },
|
|
3292
|
+
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL, OpenTofu" },
|
|
3293
|
+
{ value: "railway", label: "Railway", hint: "Managed container + PostgreSQL" }
|
|
3194
3294
|
]
|
|
3195
3295
|
})));
|
|
3196
|
-
const
|
|
3296
|
+
const frontendOptions = frontendTargetsByCloud[cloud];
|
|
3297
|
+
const frontendTarget = defaults.frontendTarget ?? (frontendOptions.length === 1 ? frontendOptions[0] ?? "cloudflare" : parseFrontendTarget(await unwrap(await select({
|
|
3197
3298
|
message: "Frontend host",
|
|
3198
|
-
options:
|
|
3199
|
-
|
|
3200
|
-
{ value: "vercel", label: "Vercel SPA", hint: "Static frontend only" },
|
|
3201
|
-
{
|
|
3202
|
-
value: "cloudflare",
|
|
3203
|
-
label: "Cloudflare static assets",
|
|
3204
|
-
hint: "No API Worker"
|
|
3205
|
-
}
|
|
3206
|
-
]
|
|
3207
|
-
})));
|
|
3299
|
+
options: frontendOptions.map((target) => target === "native" ? { value: target, label: "Cloud native", hint: "Managed with the backend" } : { value: target, label: "Cloudflare static assets", hint: "No API Worker" })
|
|
3300
|
+
}))));
|
|
3208
3301
|
const storageProvider = defaults.storageProvider ?? (storageProvidersByCloud[cloud].length === 1 ? storageProvidersByCloud[cloud][0] ?? defaultStorageProviders[cloud] : parseStorageProvider(await unwrap(await select({
|
|
3209
3302
|
message: "Object storage",
|
|
3210
3303
|
options: storageProvidersByCloud[cloud].map((provider) => ({
|
|
@@ -3219,8 +3312,8 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3219
3312
|
defaultValue: "production",
|
|
3220
3313
|
validate: (value) => validateDeploymentEnvironment(value || "production")
|
|
3221
3314
|
}));
|
|
3222
|
-
const region = defaults.region ?? await unwrap(await text2({
|
|
3223
|
-
message: `${cloud
|
|
3315
|
+
const region = !isOpenTofuCloud(cloud) ? defaults.region ?? null : defaults.region ?? await unwrap(await text2({
|
|
3316
|
+
message: `${cloudLabels[cloud]} region`,
|
|
3224
3317
|
placeholder: defaultRegions[cloud],
|
|
3225
3318
|
defaultValue: defaultRegions[cloud],
|
|
3226
3319
|
validate: (value) => validateRegion(value || defaultRegions[cloud])
|
|
@@ -3235,7 +3328,7 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3235
3328
|
placeholder: "app.example.com",
|
|
3236
3329
|
validate: (value) => validateDomain(value ?? "")
|
|
3237
3330
|
}));
|
|
3238
|
-
const haEnabled = defaults.haEnabled ?? await unwrap(await confirm2({
|
|
3331
|
+
const haEnabled = !isOpenTofuCloud(cloud) ? defaults.haEnabled ?? false : defaults.haEnabled ?? await unwrap(await confirm2({
|
|
3239
3332
|
message: "Enable the higher-availability profile?",
|
|
3240
3333
|
initialValue: false
|
|
3241
3334
|
}));
|
|
@@ -3297,10 +3390,11 @@ async function runDeploymentConfigure(argv, productName, repositoryRoot = proces
|
|
|
3297
3390
|
try {
|
|
3298
3391
|
const selection = await collectDeploymentSelection(parsed, parsed.yes);
|
|
3299
3392
|
const removed = await applyDeploymentSelection(root, rootPackage.name, selection);
|
|
3300
|
-
log2.success(`${selection.cloud
|
|
3393
|
+
log2.success(`${cloudLabels[selection.cloud]} + ${selection.storageProvider} + ${selection.frontendTarget} is locked for ${selection.environment}.`);
|
|
3301
3394
|
log2.info(`${removed.length} unused deployment paths removed.`);
|
|
3302
3395
|
log2.step("Next");
|
|
3303
|
-
log2.message(`Review deployment/config.json
|
|
3396
|
+
log2.message(selection.cloud === "railway" ? `Review deployment/config.json
|
|
3397
|
+
Follow deployment/README.md for the Railway services and Cloudflare frontend` : `Review deployment/config.json
|
|
3304
3398
|
Configure the protected GitHub environment
|
|
3305
3399
|
Dispatch .github/workflows/deploy-cloud.yml`);
|
|
3306
3400
|
outro2("Deployment configuration is ready to commit.");
|
|
@@ -3391,37 +3485,31 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3391
3485
|
if (!defaults.cloud || !defaults.apiDomain || !defaults.frontendDomain) {
|
|
3392
3486
|
throw new Error("Non-interactive deployment setup requires --cloud, --api-domain, and --frontend-domain.");
|
|
3393
3487
|
}
|
|
3488
|
+
const cloud = defaults.cloud;
|
|
3394
3489
|
return {
|
|
3395
|
-
cloud
|
|
3396
|
-
storageProvider: defaults.storageProvider ?? defaultStorageProviders[
|
|
3397
|
-
frontendTarget: defaults.frontendTarget ?? "
|
|
3490
|
+
cloud,
|
|
3491
|
+
storageProvider: defaults.storageProvider ?? defaultStorageProviders[cloud],
|
|
3492
|
+
frontendTarget: defaults.frontendTarget ?? frontendTargetsByCloud[cloud][0] ?? "cloudflare",
|
|
3398
3493
|
environment: defaults.environment ?? "production",
|
|
3399
|
-
region: defaults.region ?? defaultRegions[defaults.
|
|
3494
|
+
region: isOpenTofuCloud(cloud) ? defaults.region ?? defaultRegions[cloud] : defaults.region ?? null,
|
|
3400
3495
|
apiDomain: defaults.apiDomain,
|
|
3401
3496
|
frontendDomain: defaults.frontendDomain,
|
|
3402
3497
|
haEnabled: defaults.haEnabled ?? false
|
|
3403
3498
|
};
|
|
3404
3499
|
}
|
|
3405
3500
|
const cloud = defaults.cloud ?? parseDeploymentCloud(await unwrap2(await select({
|
|
3406
|
-
message: "Backend
|
|
3501
|
+
message: "Backend host",
|
|
3407
3502
|
options: [
|
|
3408
|
-
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL" },
|
|
3409
|
-
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL" },
|
|
3410
|
-
{ value: "
|
|
3503
|
+
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL, OpenTofu" },
|
|
3504
|
+
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL, OpenTofu" },
|
|
3505
|
+
{ value: "railway", label: "Railway", hint: "Managed container + PostgreSQL" }
|
|
3411
3506
|
]
|
|
3412
3507
|
})));
|
|
3413
|
-
const
|
|
3508
|
+
const frontendOptions = frontendTargetsByCloud[cloud];
|
|
3509
|
+
const frontendTarget = defaults.frontendTarget ?? (frontendOptions.length === 1 ? frontendOptions[0] ?? "cloudflare" : parseFrontendTarget(await unwrap2(await select({
|
|
3414
3510
|
message: "Frontend host",
|
|
3415
|
-
options:
|
|
3416
|
-
|
|
3417
|
-
{ value: "vercel", label: "Vercel SPA", hint: "Static frontend only" },
|
|
3418
|
-
{
|
|
3419
|
-
value: "cloudflare",
|
|
3420
|
-
label: "Cloudflare static assets",
|
|
3421
|
-
hint: "No API Worker"
|
|
3422
|
-
}
|
|
3423
|
-
]
|
|
3424
|
-
})));
|
|
3511
|
+
options: frontendOptions.map((target) => target === "native" ? { value: target, label: "Cloud native", hint: "Managed with the backend" } : { value: target, label: "Cloudflare static assets", hint: "No API Worker" })
|
|
3512
|
+
}))));
|
|
3425
3513
|
const storageProvider = defaults.storageProvider ?? (storageProvidersByCloud[cloud].length === 1 ? storageProvidersByCloud[cloud][0] ?? defaultStorageProviders[cloud] : parseStorageProvider(await unwrap2(await select({
|
|
3426
3514
|
message: "Object storage",
|
|
3427
3515
|
options: storageProvidersByCloud[cloud].map((provider) => ({
|
|
@@ -3436,8 +3524,8 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3436
3524
|
defaultValue: "production",
|
|
3437
3525
|
validate: (value) => validateDeploymentEnvironment(value || "production")
|
|
3438
3526
|
}));
|
|
3439
|
-
const region = defaults.region ?? await unwrap2(await text2({
|
|
3440
|
-
message: `${cloud
|
|
3527
|
+
const region = !isOpenTofuCloud(cloud) ? defaults.region ?? null : defaults.region ?? await unwrap2(await text2({
|
|
3528
|
+
message: `${cloudLabels[cloud]} region`,
|
|
3441
3529
|
placeholder: defaultRegions[cloud],
|
|
3442
3530
|
defaultValue: defaultRegions[cloud],
|
|
3443
3531
|
validate: (value) => validateRegion(value || defaultRegions[cloud])
|
|
@@ -3452,7 +3540,7 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3452
3540
|
placeholder: "app.example.com",
|
|
3453
3541
|
validate: (value) => validateDomain(value ?? "")
|
|
3454
3542
|
}));
|
|
3455
|
-
const haEnabled = defaults.haEnabled ?? await unwrap2(await confirm2({
|
|
3543
|
+
const haEnabled = !isOpenTofuCloud(cloud) ? defaults.haEnabled ?? false : defaults.haEnabled ?? await unwrap2(await confirm2({
|
|
3456
3544
|
message: "Enable the higher-availability profile?",
|
|
3457
3545
|
initialValue: false
|
|
3458
3546
|
}));
|
|
@@ -3474,14 +3562,28 @@ import { join as join5 } from "node:path";
|
|
|
3474
3562
|
var DEPLOYMENT_ENVIRONMENT_RE2 = /^[a-z][a-z0-9-]{1,30}$/;
|
|
3475
3563
|
var REGION_RE2 = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
3476
3564
|
var DOMAIN_RE2 = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
3477
|
-
var deploymentClouds2 = ["aws", "gcp", "
|
|
3478
|
-
var
|
|
3565
|
+
var deploymentClouds2 = ["aws", "gcp", "railway"];
|
|
3566
|
+
var openTofuClouds2 = ["aws", "gcp"];
|
|
3567
|
+
var frontendTargets2 = ["native", "cloudflare"];
|
|
3479
3568
|
var storageProviders2 = ["r2", "s3", "gcs"];
|
|
3480
3569
|
var storageProvidersByCloud2 = {
|
|
3481
3570
|
aws: ["s3", "r2"],
|
|
3482
3571
|
gcp: ["gcs", "r2"],
|
|
3483
|
-
|
|
3572
|
+
railway: ["r2"]
|
|
3573
|
+
};
|
|
3574
|
+
var frontendTargetsByCloud2 = {
|
|
3575
|
+
aws: ["native", "cloudflare"],
|
|
3576
|
+
gcp: ["native", "cloudflare"],
|
|
3577
|
+
railway: ["cloudflare"]
|
|
3484
3578
|
};
|
|
3579
|
+
var cloudLabels2 = {
|
|
3580
|
+
aws: "AWS",
|
|
3581
|
+
gcp: "GCP",
|
|
3582
|
+
railway: "Railway"
|
|
3583
|
+
};
|
|
3584
|
+
function isOpenTofuCloud2(cloud) {
|
|
3585
|
+
return openTofuClouds2.some((candidate) => candidate === cloud);
|
|
3586
|
+
}
|
|
3485
3587
|
function isDeploymentCloud2(value) {
|
|
3486
3588
|
return deploymentClouds2.some((cloud) => cloud === value);
|
|
3487
3589
|
}
|
|
@@ -3493,12 +3595,12 @@ function isStorageProvider2(value) {
|
|
|
3493
3595
|
}
|
|
3494
3596
|
function parseDeploymentCloud2(value) {
|
|
3495
3597
|
if (!isDeploymentCloud2(value))
|
|
3496
|
-
throw new Error("Cloud must be aws, gcp, or
|
|
3598
|
+
throw new Error("Cloud must be aws, gcp, or railway.");
|
|
3497
3599
|
return value;
|
|
3498
3600
|
}
|
|
3499
3601
|
function parseFrontendTarget2(value) {
|
|
3500
3602
|
if (!isFrontendTarget2(value)) {
|
|
3501
|
-
throw new Error("Frontend target must be native
|
|
3603
|
+
throw new Error("Frontend target must be native or cloudflare.");
|
|
3502
3604
|
}
|
|
3503
3605
|
return value;
|
|
3504
3606
|
}
|
|
@@ -3529,12 +3631,24 @@ function validateDeploymentSelection2(selection) {
|
|
|
3529
3631
|
if (!storageProvidersByCloud2[selection.cloud].includes(selection.storageProvider)) {
|
|
3530
3632
|
throw new Error(`${selection.storageProvider} storage is not supported for ${selection.cloud} deployments.`);
|
|
3531
3633
|
}
|
|
3634
|
+
if (!frontendTargetsByCloud2[selection.cloud].includes(selection.frontendTarget)) {
|
|
3635
|
+
throw new Error(`The ${selection.frontendTarget} frontend is not supported for ${selection.cloud} deployments.`);
|
|
3636
|
+
}
|
|
3637
|
+
if (isOpenTofuCloud2(selection.cloud)) {
|
|
3638
|
+
const regionError = validateRegion2(selection.region ?? "");
|
|
3639
|
+
if (regionError)
|
|
3640
|
+
throw new Error(regionError);
|
|
3641
|
+
} else {
|
|
3642
|
+
if (selection.region !== null) {
|
|
3643
|
+
throw new Error("Railway selects its region per service; omit the region.");
|
|
3644
|
+
}
|
|
3645
|
+
if (selection.haEnabled) {
|
|
3646
|
+
throw new Error("The higher-availability profile applies to AWS and GCP only.");
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3532
3649
|
const environmentError = validateDeploymentEnvironment2(selection.environment);
|
|
3533
3650
|
if (environmentError)
|
|
3534
3651
|
throw new Error(environmentError);
|
|
3535
|
-
const regionError = validateRegion2(selection.region);
|
|
3536
|
-
if (regionError)
|
|
3537
|
-
throw new Error(regionError);
|
|
3538
3652
|
const apiError = validateDomain2(selection.apiDomain);
|
|
3539
3653
|
if (apiError)
|
|
3540
3654
|
throw new Error(`API domain: ${apiError}`);
|
|
@@ -3566,20 +3680,86 @@ function createDeploymentConfig2(projectName, selection) {
|
|
|
3566
3680
|
ha_enabled: selection.haEnabled
|
|
3567
3681
|
};
|
|
3568
3682
|
}
|
|
3569
|
-
function
|
|
3570
|
-
|
|
3571
|
-
|
|
3683
|
+
function lockSummary2(config) {
|
|
3684
|
+
return `The deployment target is locked:
|
|
3685
|
+
|
|
3686
|
+
- backend: **${cloudLabels2[config.cloud]}**;
|
|
3687
|
+
- storage: **${config.storage_provider}**;
|
|
3688
|
+
- frontend: **${config.frontend_target}**;
|
|
3689
|
+
- environment: **${config.environment}**.`;
|
|
3690
|
+
}
|
|
3691
|
+
function railwayReadme2(config) {
|
|
3692
|
+
return `# Deployment setup
|
|
3693
|
+
|
|
3694
|
+
${lockSummary2(config)}
|
|
3695
|
+
|
|
3696
|
+
The production hosts are:
|
|
3697
|
+
|
|
3698
|
+
- API: \`${config.api_domain}\`;
|
|
3699
|
+
- frontend: \`${config.frontend_domain}\`.
|
|
3700
|
+
|
|
3701
|
+
## Railway API and PostgreSQL
|
|
3702
|
+
|
|
3703
|
+
1. Create a Railway project with a PostgreSQL service and an API service connected to this
|
|
3704
|
+
repository. Build from the repository root, because the image copies the whole workspace.
|
|
3705
|
+
2. Set the API service's config-as-code path to \`apps/server/railway.toml\`. It builds
|
|
3706
|
+
\`apps/server/Dockerfile\`, runs \`./server migrate\` before each deploy, starts \`./server serve\`,
|
|
3707
|
+
and admits traffic only after \`/ready\` succeeds.
|
|
3708
|
+
3. Set these API service variables in the \`${config.environment}\` Railway environment:
|
|
3709
|
+
|
|
3710
|
+
\`\`\`text
|
|
3711
|
+
DATABASE_URL=\${{Postgres.DATABASE_URL}}
|
|
3712
|
+
ENVIRONMENT=production
|
|
3713
|
+
TRUSTED_PROXY_PROFILE=railway
|
|
3714
|
+
DEPLOYMENT_CLOUD=railway
|
|
3715
|
+
FRONTEND_URL=https://${config.frontend_domain}
|
|
3716
|
+
SERVER_URL=https://${config.api_domain}
|
|
3717
|
+
\`\`\`
|
|
3718
|
+
|
|
3719
|
+
Add \`BETTER_AUTH_SECRET\`, \`RESEND_API_KEY\`, \`EMAIL_FROM\`, \`APP_DB_POOL_MAX\`, and
|
|
3720
|
+
\`AUTH_DB_POOL_MAX\`, then the Cloudflare R2 storage variables and every enabled billing,
|
|
3721
|
+
monitoring, and OAuth value. Railway injects \`PORT\`; leave it unset.
|
|
3722
|
+
|
|
3723
|
+
4. Add the API host as the service's custom domain and publish the DNS records Railway reports. A
|
|
3724
|
+
Cloudflare-managed zone keeps them \`DNS only\`.
|
|
3725
|
+
|
|
3726
|
+
## Cloudflare frontend
|
|
3727
|
+
|
|
3728
|
+
1. Create a Cloudflare Workers project rooted at \`apps/web\`. It uses \`apps/web/wrangler.toml\`, which
|
|
3729
|
+
serves \`dist\` as static assets with single-page-application fallback.
|
|
3730
|
+
2. Build with \`pnpm run build:prod\` and these variables:
|
|
3731
|
+
|
|
3732
|
+
\`\`\`text
|
|
3733
|
+
VITE_APP_ENV=production
|
|
3734
|
+
VITE_API_URL=https://${config.api_domain}
|
|
3735
|
+
\`\`\`
|
|
3736
|
+
|
|
3737
|
+
Add \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3738
|
+
public marketing site.
|
|
3739
|
+
|
|
3740
|
+
3. Attach the frontend host as the Worker's custom domain.
|
|
3741
|
+
|
|
3742
|
+
## Verify
|
|
3743
|
+
|
|
3744
|
+
\`\`\`sh
|
|
3745
|
+
API_ORIGIN=https://${config.api_domain} \\
|
|
3746
|
+
WEB_ORIGIN=https://${config.frontend_domain} \\
|
|
3747
|
+
bash scripts/verify-public-deployment.sh
|
|
3748
|
+
\`\`\`
|
|
3749
|
+
|
|
3750
|
+
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3751
|
+
before changing providers.
|
|
3752
|
+
`;
|
|
3753
|
+
}
|
|
3754
|
+
function openTofuReadme2(config) {
|
|
3755
|
+
const identity = config.cloud === "aws" ? "`AWS_ROLE_ARN` secret" : "`GCP_WORKLOAD_IDENTITY_PROVIDER` and `GCP_SERVICE_ACCOUNT` secrets";
|
|
3756
|
+
const providerInputs = config.cloud === "aws" ? "Route 53 zone or certificate identifiers and runtime Secrets Manager ARNs" : "GCP project, Cloud DNS zone, and runtime Secret Manager names";
|
|
3572
3757
|
const storageSetup = config.storage_provider === "r2" ? `Create private staging and public Cloudflare R2 buckets, then store the required storage
|
|
3573
3758
|
variables in the runtime secret container.` : `${config.storage_provider.toUpperCase()} application buckets, lifecycle policy, CORS, public access, and workload permissions are
|
|
3574
3759
|
managed by the selected OpenTofu root.`;
|
|
3575
3760
|
return `# Deployment setup
|
|
3576
3761
|
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
- backend: **${config.cloud.toUpperCase()}**;
|
|
3580
|
-
- storage: **${config.storage_provider}**;
|
|
3581
|
-
- frontend: **${config.frontend_target}**;
|
|
3582
|
-
- environment: **${config.environment}**.
|
|
3762
|
+
${lockSummary2(config)}
|
|
3583
3763
|
|
|
3584
3764
|
## Protected GitHub environment
|
|
3585
3765
|
|
|
@@ -3589,8 +3769,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
3589
3769
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
3590
3770
|
- a \`TFVARS_JSON\` secret containing only:
|
|
3591
3771
|
- ${providerInputs};
|
|
3592
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3593
|
-
|
|
3772
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a public
|
|
3773
|
+
marketing site.
|
|
3594
3774
|
|
|
3595
3775
|
## Cloud preparation
|
|
3596
3776
|
|
|
@@ -3611,9 +3791,33 @@ The workflow creates the registry first, pushes an immutable image, provisions t
|
|
|
3611
3791
|
traffic disabled, runs migrations, and admits traffic only after migration succeeds.
|
|
3612
3792
|
|
|
3613
3793
|
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3614
|
-
before changing
|
|
3794
|
+
before changing providers.
|
|
3615
3795
|
`;
|
|
3616
3796
|
}
|
|
3797
|
+
function deploymentReadme2(config) {
|
|
3798
|
+
return config.cloud === "railway" ? railwayReadme2(config) : openTofuReadme2(config);
|
|
3799
|
+
}
|
|
3800
|
+
function openTofuPaths2(cloud) {
|
|
3801
|
+
return [
|
|
3802
|
+
`infra/bootstrap/${cloud}`,
|
|
3803
|
+
`infra/modules/${cloud}`,
|
|
3804
|
+
`infra/roots/${cloud}`,
|
|
3805
|
+
`scripts/deploy/release-${cloud}.sh`,
|
|
3806
|
+
`scripts/deploy/frontend-${cloud}.sh`
|
|
3807
|
+
];
|
|
3808
|
+
}
|
|
3809
|
+
function unusedDeploymentPaths2(selection) {
|
|
3810
|
+
const paths = selection.cloud === "railway" ? ["infra", "scripts/deploy", ".github/workflows/deploy-cloud.yml"] : [
|
|
3811
|
+
...openTofuClouds2.filter((cloud) => cloud !== selection.cloud).flatMap(openTofuPaths2),
|
|
3812
|
+
"apps/server/railway.toml"
|
|
3813
|
+
];
|
|
3814
|
+
if (selection.frontendTarget === "native")
|
|
3815
|
+
paths.push("apps/web/wrangler.toml");
|
|
3816
|
+
else if (selection.cloud !== "railway") {
|
|
3817
|
+
paths.push(`scripts/deploy/frontend-${selection.cloud}.sh`);
|
|
3818
|
+
}
|
|
3819
|
+
return paths;
|
|
3820
|
+
}
|
|
3617
3821
|
async function removeIfPresent2(target) {
|
|
3618
3822
|
if (!await stat3(target).catch(() => {
|
|
3619
3823
|
return;
|
|
@@ -3622,6 +3826,20 @@ async function removeIfPresent2(target) {
|
|
|
3622
3826
|
await rm2(target, { recursive: true, force: true });
|
|
3623
3827
|
return true;
|
|
3624
3828
|
}
|
|
3829
|
+
async function removeInfrastructureScript2(repositoryRoot) {
|
|
3830
|
+
const packagePath = join5(repositoryRoot, "package.json");
|
|
3831
|
+
const content = await readFile4(packagePath, "utf8").catch(() => {
|
|
3832
|
+
return;
|
|
3833
|
+
});
|
|
3834
|
+
if (!content)
|
|
3835
|
+
return;
|
|
3836
|
+
const rootPackage = JSON.parse(content);
|
|
3837
|
+
if (!rootPackage.scripts?.["verify:infra"])
|
|
3838
|
+
return;
|
|
3839
|
+
delete rootPackage.scripts["verify:infra"];
|
|
3840
|
+
await writeFile3(packagePath, `${JSON.stringify(rootPackage, null, 2)}
|
|
3841
|
+
`);
|
|
3842
|
+
}
|
|
3625
3843
|
async function applyDeploymentSelection2(repositoryRoot, projectName, selection) {
|
|
3626
3844
|
const config = createDeploymentConfig2(projectName, selection);
|
|
3627
3845
|
const configPath = join5(repositoryRoot, "deployment/config.json");
|
|
@@ -3635,40 +3853,12 @@ async function applyDeploymentSelection2(repositoryRoot, projectName, selection)
|
|
|
3635
3853
|
`);
|
|
3636
3854
|
await writeFile3(join5(repositoryRoot, "deployment/README.md"), deploymentReadme2(config));
|
|
3637
3855
|
const removed = [];
|
|
3638
|
-
for (const
|
|
3639
|
-
if (
|
|
3640
|
-
|
|
3641
|
-
for (const relativePath of [
|
|
3642
|
-
`infra/bootstrap/${cloud}`,
|
|
3643
|
-
`infra/modules/${cloud}`,
|
|
3644
|
-
`infra/roots/${cloud}`,
|
|
3645
|
-
`scripts/deploy/release-${cloud}.sh`,
|
|
3646
|
-
`scripts/deploy/frontend-${cloud}.sh`
|
|
3647
|
-
]) {
|
|
3648
|
-
if (await removeIfPresent2(join5(repositoryRoot, relativePath)))
|
|
3649
|
-
removed.push(relativePath);
|
|
3650
|
-
}
|
|
3651
|
-
}
|
|
3652
|
-
const adapters = [
|
|
3653
|
-
{ target: "vercel", path: "apps/web/vercel.json" },
|
|
3654
|
-
{ target: "cloudflare", path: "apps/web/wrangler.toml" },
|
|
3655
|
-
{
|
|
3656
|
-
target: "azure-native",
|
|
3657
|
-
path: "apps/web/public/staticwebapp.config.json"
|
|
3658
|
-
}
|
|
3659
|
-
];
|
|
3660
|
-
const selectedAdapter = selection.frontendTarget === "native" ? selection.cloud === "azure" ? "azure-native" : undefined : selection.frontendTarget;
|
|
3661
|
-
for (const adapter of adapters) {
|
|
3662
|
-
if (adapter.target === selectedAdapter)
|
|
3663
|
-
continue;
|
|
3664
|
-
if (await removeIfPresent2(join5(repositoryRoot, adapter.path)))
|
|
3665
|
-
removed.push(adapter.path);
|
|
3666
|
-
}
|
|
3667
|
-
if (selection.frontendTarget !== "native") {
|
|
3668
|
-
const nativeScript = `scripts/deploy/frontend-${selection.cloud}.sh`;
|
|
3669
|
-
if (await removeIfPresent2(join5(repositoryRoot, nativeScript)))
|
|
3670
|
-
removed.push(nativeScript);
|
|
3856
|
+
for (const relativePath of unusedDeploymentPaths2(selection)) {
|
|
3857
|
+
if (await removeIfPresent2(join5(repositoryRoot, relativePath)))
|
|
3858
|
+
removed.push(relativePath);
|
|
3671
3859
|
}
|
|
3860
|
+
if (removed.includes("infra"))
|
|
3861
|
+
await removeInfrastructureScript2(repositoryRoot);
|
|
3672
3862
|
return removed;
|
|
3673
3863
|
}
|
|
3674
3864
|
|
|
@@ -4290,15 +4480,15 @@ Options:
|
|
|
4290
4480
|
--no-install Skip dependency installation
|
|
4291
4481
|
--start Start Postgres and development servers after creation
|
|
4292
4482
|
--no-start Create the application without starting it
|
|
4293
|
-
--cloud <provider> Configure AWS, GCP, or
|
|
4483
|
+
--cloud <provider> Configure AWS, GCP, or Railway deployment during setup
|
|
4294
4484
|
--storage <provider> Select r2, s3, or gcs object storage
|
|
4295
|
-
--no-deploy Keep every
|
|
4296
|
-
--frontend <target> Select native
|
|
4485
|
+
--no-deploy Keep every deployment option for later setup
|
|
4486
|
+
--frontend <target> Select native or cloudflare frontend hosting (Railway: cloudflare)
|
|
4297
4487
|
--deploy-environment <name> Deployment environment (default: production)
|
|
4298
|
-
--region <name>
|
|
4488
|
+
--region <name> AWS or GCP region
|
|
4299
4489
|
--api-domain <host> Stable production API hostname
|
|
4300
4490
|
--frontend-domain <host> Stable production frontend hostname
|
|
4301
|
-
--ha / --no-ha Enable or disable the higher-availability profile
|
|
4491
|
+
--ha / --no-ha Enable or disable the AWS or GCP higher-availability profile
|
|
4302
4492
|
-v, --version Print the CLI version
|
|
4303
4493
|
-h, --help Show this help
|
|
4304
4494
|
|
|
@@ -4306,7 +4496,8 @@ Agent tooling:
|
|
|
4306
4496
|
${agentSetupUsage(CLI_NAME)}
|
|
4307
4497
|
|
|
4308
4498
|
Deployment:
|
|
4309
|
-
Run pnpm deploy:configure inside a generated app to make a deferred
|
|
4499
|
+
Run pnpm deploy:configure inside a generated app to make a deferred deployment selection.
|
|
4500
|
+
The TanStack Start edition always deploys to Cloudflare Workers and D1.`;
|
|
4310
4501
|
}
|
|
4311
4502
|
function takeValue3(args, index, flag) {
|
|
4312
4503
|
const value = args[index + 1];
|
|
@@ -4775,7 +4966,7 @@ ${completion.database}`);
|
|
|
4775
4966
|
log.info("Mobile pnpm dev:mobile \u2014 run `npx eas init` inside apps/mobile to claim your own\n Expo project before the first build. See docs/mobile.md.");
|
|
4776
4967
|
}
|
|
4777
4968
|
if (options.deployment) {
|
|
4778
|
-
log.info(`Deployment ${options.deployment.cloud
|
|
4969
|
+
log.info(`Deployment ${cloudLabels2[options.deployment.cloud]} + ${options.deployment.storageProvider} + ${options.deployment.frontendTarget} locked. Review deployment/README.md.`);
|
|
4779
4970
|
} else {
|
|
4780
4971
|
log.info("Deployment deferred. Run `pnpm deploy:configure` when you are ready.");
|
|
4781
4972
|
}
|