superslate 0.9.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 +797 -531
- 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}
|
|
1069
|
+
`);
|
|
1070
|
+
}, Y);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
x = block({ output: n }), N && n.write(`${styleText2("gray", S_BAR)}
|
|
1104
1074
|
`);
|
|
1105
|
-
let
|
|
1106
|
-
|
|
1107
|
-
if (u &&
|
|
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
|
}
|
|
@@ -1266,20 +1246,31 @@ var LICENSE_KEY_VARIABLE2 = "SUPERSLATE_LICENSE_KEY";
|
|
|
1266
1246
|
var LICENSE_KEY_PREFIX = "SUPERSLATE-";
|
|
1267
1247
|
|
|
1268
1248
|
// src/core.ts
|
|
1249
|
+
var KEBAB_CASE_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1250
|
+
var WHITESPACE_CHAR_RE = /\s/;
|
|
1251
|
+
var NPM_SCOPE_RE = /^@[a-z0-9][a-z0-9._-]*$/;
|
|
1252
|
+
var NEWLINE_CHAR_RE = /\r|\n/;
|
|
1253
|
+
var BRAND_NAME_RE = /^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u;
|
|
1254
|
+
var REGEX_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\]/g;
|
|
1255
|
+
var ESCAPED_SOURCE_BRAND = SOURCE_BRAND_NAME.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
|
|
1256
|
+
var SOURCE_BRAND_RE = new RegExp(`\\b${ESCAPED_SOURCE_BRAND}\\b`, "gi");
|
|
1257
|
+
var ESCAPED_CLI_NAME = CLI_NAME2.replace(REGEX_SPECIAL_CHARS_RE, "\\$&");
|
|
1258
|
+
var SOURCE_DELIVERY_GUIDANCE_RE = new RegExp(`pnpm dlx ${ESCAPED_CLI_NAME}|bunx ${ESCAPED_CLI_NAME}|${ESCAPED_CLI_NAME} repository`, "i");
|
|
1259
|
+
var VENDOR_BOOTSTRAPPER_GUIDANCE_RE = new RegExp(`${ESCAPED_CLI_NAME}@\\d|public \`${ESCAPED_CLI_NAME}\` npm package`, "i");
|
|
1269
1260
|
function validateAppName(value) {
|
|
1270
1261
|
if (!value)
|
|
1271
1262
|
return "App name is required.";
|
|
1272
|
-
if (
|
|
1263
|
+
if (!KEBAB_CASE_RE.test(value)) {
|
|
1273
1264
|
return "App name must be kebab-case (lowercase letters, numbers, and single hyphens).";
|
|
1274
1265
|
}
|
|
1275
1266
|
return;
|
|
1276
1267
|
}
|
|
1277
1268
|
function normalizeScope(value) {
|
|
1278
1269
|
const scope = value.startsWith("@") ? value : `@${value}`;
|
|
1279
|
-
if (
|
|
1270
|
+
if (WHITESPACE_CHAR_RE.test(scope)) {
|
|
1280
1271
|
throw new Error("Use hyphens instead of spaces, for example test-package.");
|
|
1281
1272
|
}
|
|
1282
|
-
if (
|
|
1273
|
+
if (!NPM_SCOPE_RE.test(scope)) {
|
|
1283
1274
|
throw new Error("Use lowercase letters and numbers, with optional periods, underscores, or hyphens.");
|
|
1284
1275
|
}
|
|
1285
1276
|
return scope;
|
|
@@ -1287,13 +1278,13 @@ function normalizeScope(value) {
|
|
|
1287
1278
|
function validateBrandName(value) {
|
|
1288
1279
|
if (!value.trim())
|
|
1289
1280
|
return "Brand name cannot be empty.";
|
|
1290
|
-
if (
|
|
1281
|
+
if (NEWLINE_CHAR_RE.test(value))
|
|
1291
1282
|
return "Brand name must fit on one line.";
|
|
1292
1283
|
if (value !== value.trim())
|
|
1293
1284
|
return "Brand name cannot start or end with whitespace.";
|
|
1294
1285
|
if (value.length > 48)
|
|
1295
1286
|
return "Brand name must be 48 characters or less.";
|
|
1296
|
-
if (
|
|
1287
|
+
if (!BRAND_NAME_RE.test(value)) {
|
|
1297
1288
|
return "Brand name may contain letters, numbers, spaces, periods, underscores, and hyphens.";
|
|
1298
1289
|
}
|
|
1299
1290
|
return;
|
|
@@ -1303,12 +1294,43 @@ var SCOPE_EXTENSIONS = new Set([".json", ".jsonc", ".md", ".ts", ".tsx", ".yaml"
|
|
|
1303
1294
|
// src/core.ts
|
|
1304
1295
|
import { randomBytes } from "node:crypto";
|
|
1305
1296
|
import { extname } from "node:path";
|
|
1297
|
+
var KEBAB_CASE_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
1298
|
+
var WHITESPACE_CHAR_RE2 = /\s/;
|
|
1299
|
+
var NPM_SCOPE_RE2 = /^@[a-z0-9][a-z0-9._-]*$/;
|
|
1300
|
+
var NEWLINE_CHAR_RE2 = /\r|\n/;
|
|
1301
|
+
var BRAND_NAME_RE2 = /^[\p{L}\p{N}](?:[\p{L}\p{N} ._-]*[\p{L}\p{N}])?$/u;
|
|
1302
|
+
var REGEX_SPECIAL_CHARS_RE2 = /[.*+?^${}()|[\]\\]/g;
|
|
1303
|
+
var NON_ALPHANUMERIC_CHAR_RE = /[^a-z0-9]/g;
|
|
1304
|
+
var LEADING_DIGIT_RE = /^[0-9]/;
|
|
1305
|
+
var HTTP_PROTOCOL_RE = /^https?:\/\//;
|
|
1306
|
+
var APP_DOMAIN_RE = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/;
|
|
1307
|
+
var BUNDLE_IDENTIFIER_RE = /^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*){2,}$/;
|
|
1308
|
+
var MOBILE_APP_SCHEME_LINE_RE = /^MOBILE_APP_SCHEME=.*$/m;
|
|
1309
|
+
var MOBILE_DOCS_SOURCE_LINE_RE = /^\s*'mobile\.md',\n/m;
|
|
1310
|
+
var MOBILE_EDITION_SECTION_RE = /^## Mobile edition\n[\s\S]*?(?=^## |(?![\s\S]))/m;
|
|
1311
|
+
var MOBILE_TABLE_ROW_RE = /^\| `apps\/mobile` .*\n/gm;
|
|
1312
|
+
var MOBILE_PARAGRAPH_RE = /^`apps\/mobile`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm;
|
|
1313
|
+
var MOBILE_DOCS_LINK_RE = /\[([^\]]+)\]\(\.\/mobile\.md(?:#[^)]*)?\)/g;
|
|
1314
|
+
var NAMED_IMPORT_LINE_RE = /^(\s*)(import(?:\s+type)?\s+)\{([^}]+)\}(\s+from\s+.+)$/;
|
|
1315
|
+
var WHITESPACE_RE = /\s+/;
|
|
1316
|
+
var INTERNAL_AGENT_REFERENCE_RE = /internal\/|landing\/|seller-surface|studio-owned/i;
|
|
1317
|
+
var INTERNAL_CLAUDE_REFERENCE_RE = /internal\/|landing\/|studio-owned/i;
|
|
1318
|
+
var BETTER_AUTH_SECRET_LINE_RE = /^BETTER_AUTH_SECRET=.*$/m;
|
|
1319
|
+
var HEX_64_RE = /^[a-f0-9]{64}$/;
|
|
1320
|
+
var DATABASE_URL_LINE_RE = /^DATABASE_URL=.*$/m;
|
|
1321
|
+
var GHA_CREDS_FILE_RE = /^gha-creds-.*\.json$/;
|
|
1322
|
+
var ESCAPED_SOURCE_BRAND2 = SOURCE_BRAND_NAME.replace(REGEX_SPECIAL_CHARS_RE2, "\\$&");
|
|
1323
|
+
var SOURCE_BRAND_RE2 = new RegExp(`\\b${ESCAPED_SOURCE_BRAND2}\\b`, "gi");
|
|
1324
|
+
var ESCAPED_CLI_NAME2 = CLI_NAME2.replace(REGEX_SPECIAL_CHARS_RE2, "\\$&");
|
|
1325
|
+
var SOURCE_DELIVERY_GUIDANCE_RE2 = new RegExp(`pnpm dlx ${ESCAPED_CLI_NAME2}|bunx ${ESCAPED_CLI_NAME2}|${ESCAPED_CLI_NAME2} repository`, "i");
|
|
1326
|
+
var VENDOR_BOOTSTRAPPER_GUIDANCE_RE2 = new RegExp(`${ESCAPED_CLI_NAME2}@\\d|public \`${ESCAPED_CLI_NAME2}\` npm package`, "i");
|
|
1306
1327
|
var BRAND_MANIFEST = [
|
|
1307
1328
|
{ path: "apps/web/src/configs/config.ts", occurrences: 2 },
|
|
1308
1329
|
{ path: "apps/web/index.html", occurrences: 4 },
|
|
1309
1330
|
{ path: "apps/web/public/favicon/site.webmanifest", occurrences: 2 },
|
|
1310
1331
|
{ path: "apps/web/public/offline.html", occurrences: 6 },
|
|
1311
1332
|
{ path: "apps/docs/lib/shared.ts", occurrences: 1 },
|
|
1333
|
+
{ path: "apps/site/src/site.ts", occurrences: 1 },
|
|
1312
1334
|
{ path: "apps/docs/README.md", occurrences: 1 },
|
|
1313
1335
|
{ path: "README.md", occurrences: 1 },
|
|
1314
1336
|
{ path: "docs/meta.json", occurrences: 1 },
|
|
@@ -1351,17 +1373,17 @@ var UNIVERSAL_LINK_PATH_PREFIXES = ["/auth", "/invitations"];
|
|
|
1351
1373
|
function validateAppName2(value) {
|
|
1352
1374
|
if (!value)
|
|
1353
1375
|
return "App name is required.";
|
|
1354
|
-
if (
|
|
1376
|
+
if (!KEBAB_CASE_RE2.test(value)) {
|
|
1355
1377
|
return "App name must be kebab-case (lowercase letters, numbers, and single hyphens).";
|
|
1356
1378
|
}
|
|
1357
1379
|
return;
|
|
1358
1380
|
}
|
|
1359
1381
|
function normalizeScope2(value) {
|
|
1360
1382
|
const scope = value.startsWith("@") ? value : `@${value}`;
|
|
1361
|
-
if (
|
|
1383
|
+
if (WHITESPACE_CHAR_RE2.test(scope)) {
|
|
1362
1384
|
throw new Error("Use hyphens instead of spaces, for example test-package.");
|
|
1363
1385
|
}
|
|
1364
|
-
if (
|
|
1386
|
+
if (!NPM_SCOPE_RE2.test(scope)) {
|
|
1365
1387
|
throw new Error("Use lowercase letters and numbers, with optional periods, underscores, or hyphens.");
|
|
1366
1388
|
}
|
|
1367
1389
|
return scope;
|
|
@@ -1369,13 +1391,13 @@ function normalizeScope2(value) {
|
|
|
1369
1391
|
function validateBrandName2(value) {
|
|
1370
1392
|
if (!value.trim())
|
|
1371
1393
|
return "Brand name cannot be empty.";
|
|
1372
|
-
if (
|
|
1394
|
+
if (NEWLINE_CHAR_RE2.test(value))
|
|
1373
1395
|
return "Brand name must fit on one line.";
|
|
1374
1396
|
if (value !== value.trim())
|
|
1375
1397
|
return "Brand name cannot start or end with whitespace.";
|
|
1376
1398
|
if (value.length > 48)
|
|
1377
1399
|
return "Brand name must be 48 characters or less.";
|
|
1378
|
-
if (
|
|
1400
|
+
if (!BRAND_NAME_RE2.test(value)) {
|
|
1379
1401
|
return "Brand name may contain letters, numbers, spaces, periods, underscores, and hyphens.";
|
|
1380
1402
|
}
|
|
1381
1403
|
return;
|
|
@@ -1393,13 +1415,11 @@ function rewriteBrandContent(content, brandName) {
|
|
|
1393
1415
|
const error = validateBrandName2(brandName);
|
|
1394
1416
|
if (error)
|
|
1395
1417
|
throw new Error(error);
|
|
1396
|
-
const
|
|
1397
|
-
const sourceBrandPattern = new RegExp(`\\b${escapedSourceBrand}\\b`, "gi");
|
|
1398
|
-
const matches = content.match(sourceBrandPattern);
|
|
1418
|
+
const matches = content.match(SOURCE_BRAND_RE2);
|
|
1399
1419
|
if (brandName === SOURCE_BRAND_NAME || !matches)
|
|
1400
1420
|
return { content, replacements: 0 };
|
|
1401
1421
|
return {
|
|
1402
|
-
content: content.replace(
|
|
1422
|
+
content: content.replace(SOURCE_BRAND_RE2, () => brandName),
|
|
1403
1423
|
replacements: matches.length
|
|
1404
1424
|
};
|
|
1405
1425
|
}
|
|
@@ -1410,17 +1430,17 @@ function mobileSchemeFor(appName) {
|
|
|
1410
1430
|
return appName;
|
|
1411
1431
|
}
|
|
1412
1432
|
function androidPackageSegment(appName) {
|
|
1413
|
-
const segment = appName.replace(
|
|
1414
|
-
return
|
|
1433
|
+
const segment = appName.replace(NON_ALPHANUMERIC_CHAR_RE, "");
|
|
1434
|
+
return LEADING_DIGIT_RE.test(segment) || !segment ? `app${segment}` : segment;
|
|
1415
1435
|
}
|
|
1416
1436
|
function validateAppDomain(value) {
|
|
1417
1437
|
if (!value.trim())
|
|
1418
1438
|
return "Application domain cannot be empty.";
|
|
1419
|
-
if (
|
|
1439
|
+
if (HTTP_PROTOCOL_RE.test(value))
|
|
1420
1440
|
return "Use a bare host such as app.example.com, not a URL.";
|
|
1421
1441
|
if (value.includes("/"))
|
|
1422
1442
|
return "Use a bare host such as app.example.com, with no path.";
|
|
1423
|
-
if (
|
|
1443
|
+
if (!APP_DOMAIN_RE.test(value)) {
|
|
1424
1444
|
return "Use a lowercase hostname with at least two labels, for example app.example.com.";
|
|
1425
1445
|
}
|
|
1426
1446
|
return;
|
|
@@ -1428,7 +1448,7 @@ function validateAppDomain(value) {
|
|
|
1428
1448
|
function validateBundleIdentifier(value) {
|
|
1429
1449
|
if (!value.trim())
|
|
1430
1450
|
return "Bundle identifier cannot be empty.";
|
|
1431
|
-
if (
|
|
1451
|
+
if (!BUNDLE_IDENTIFIER_RE.test(value)) {
|
|
1432
1452
|
return "Use at least three lowercase reverse-DNS segments, for example com.example.app.";
|
|
1433
1453
|
}
|
|
1434
1454
|
return;
|
|
@@ -1505,11 +1525,11 @@ function rewriteMobileAppConfig(content, identity) {
|
|
|
1505
1525
|
`;
|
|
1506
1526
|
}
|
|
1507
1527
|
function rewriteMobileAppScheme(content, scheme) {
|
|
1508
|
-
if (
|
|
1528
|
+
if (!MOBILE_APP_SCHEME_LINE_RE.test(content)) {
|
|
1509
1529
|
throw new Error("Server .env.example must contain a MOBILE_APP_SCHEME entry.");
|
|
1510
1530
|
}
|
|
1511
1531
|
return {
|
|
1512
|
-
content: content.replace(
|
|
1532
|
+
content: content.replace(MOBILE_APP_SCHEME_LINE_RE, `MOBILE_APP_SCHEME=${scheme}`),
|
|
1513
1533
|
replacements: 1
|
|
1514
1534
|
};
|
|
1515
1535
|
}
|
|
@@ -1574,10 +1594,10 @@ function stripMobileDocsPage(content) {
|
|
|
1574
1594
|
`;
|
|
1575
1595
|
}
|
|
1576
1596
|
function stripMobileDocsSource(content) {
|
|
1577
|
-
return content.replace(
|
|
1597
|
+
return content.replace(MOBILE_DOCS_SOURCE_LINE_RE, "");
|
|
1578
1598
|
}
|
|
1579
1599
|
function stripMobileDocsReferences(content) {
|
|
1580
|
-
return content.replace(
|
|
1600
|
+
return content.replace(MOBILE_EDITION_SECTION_RE, "").replace(MOBILE_TABLE_ROW_RE, "").replace(MOBILE_PARAGRAPH_RE, "").replace(MOBILE_DOCS_LINK_RE, "$1");
|
|
1581
1601
|
}
|
|
1582
1602
|
function formatRewrittenPackageJson(content) {
|
|
1583
1603
|
const parsed = JSON.parse(content);
|
|
@@ -1600,7 +1620,7 @@ function wrapLongNamedImports(content, width = 100) {
|
|
|
1600
1620
|
`).map((line) => {
|
|
1601
1621
|
if (line.length <= width)
|
|
1602
1622
|
return line;
|
|
1603
|
-
const match = line.match(
|
|
1623
|
+
const match = line.match(NAMED_IMPORT_LINE_RE);
|
|
1604
1624
|
if (!match)
|
|
1605
1625
|
return line;
|
|
1606
1626
|
const [, indentation = "", prefix = "", names = "", suffix = ""] = match;
|
|
@@ -1644,7 +1664,7 @@ function wrapBrandedJsxText(content, brandName, width = 100) {
|
|
|
1644
1664
|
continue;
|
|
1645
1665
|
}
|
|
1646
1666
|
let current = indentation;
|
|
1647
|
-
for (const word of joined.split(
|
|
1667
|
+
for (const word of joined.split(WHITESPACE_RE)) {
|
|
1648
1668
|
const candidate = current === indentation ? `${indentation}${word}` : `${current} ${word}`;
|
|
1649
1669
|
if (candidate.length > width && current !== indentation) {
|
|
1650
1670
|
output.push(current);
|
|
@@ -1682,6 +1702,16 @@ function rewriteWorkerName(content, appName) {
|
|
|
1682
1702
|
return { content, replacements: 0 };
|
|
1683
1703
|
return { content: content.replace(WORKER_NAME_DECLARATION, replacement), replacements: 1 };
|
|
1684
1704
|
}
|
|
1705
|
+
var JSONC_WORKER_NAME_FIELD = /("name":\s*")[^"]*(")/;
|
|
1706
|
+
function rewriteSiteWorkerName(content, appName) {
|
|
1707
|
+
const error = validateAppName2(appName);
|
|
1708
|
+
if (error)
|
|
1709
|
+
throw new Error(error);
|
|
1710
|
+
if (!JSONC_WORKER_NAME_FIELD.test(content)) {
|
|
1711
|
+
throw new Error("The site Worker configuration is missing a name field.");
|
|
1712
|
+
}
|
|
1713
|
+
return content.replace(JSONC_WORKER_NAME_FIELD, `$1${appName}-site$2`);
|
|
1714
|
+
}
|
|
1685
1715
|
function replaceMarkdownSection(content, heading, replacement) {
|
|
1686
1716
|
const marker = `## ${heading}
|
|
1687
1717
|
`;
|
|
@@ -1711,7 +1741,7 @@ function removeParagraphContaining(content, fragment) {
|
|
|
1711
1741
|
`);
|
|
1712
1742
|
}
|
|
1713
1743
|
function rewriteBuyerAgentInstructions(content) {
|
|
1714
|
-
if (content.includes("Audit and remediate the complete buyer-owned SaaS application") &&
|
|
1744
|
+
if (content.includes("Audit and remediate the complete buyer-owned SaaS application") && !INTERNAL_AGENT_REFERENCE_RE.test(content)) {
|
|
1715
1745
|
return content;
|
|
1716
1746
|
}
|
|
1717
1747
|
let rewritten = removeParagraphContaining(content, "`internal/` holds every seller-only surface");
|
|
@@ -1729,7 +1759,7 @@ into this repository.
|
|
|
1729
1759
|
rewritten = rewritten.split(`
|
|
1730
1760
|
`).filter((line) => !line.includes("| `internal`")).join(`
|
|
1731
1761
|
`).replace("real env files, `internal/`, or\n `bun.lock`", "real env files, or `bun.lock`");
|
|
1732
|
-
if (
|
|
1762
|
+
if (INTERNAL_AGENT_REFERENCE_RE.test(rewritten)) {
|
|
1733
1763
|
throw new Error("Buyer agent instructions still contain seller-only guidance.");
|
|
1734
1764
|
}
|
|
1735
1765
|
return rewritten;
|
|
@@ -1793,10 +1823,10 @@ function stripSellerOnlyRootScripts(content) {
|
|
|
1793
1823
|
`;
|
|
1794
1824
|
}
|
|
1795
1825
|
function rewriteBuyerClaudeInstructions(content) {
|
|
1796
|
-
if (
|
|
1826
|
+
if (!INTERNAL_CLAUDE_REFERENCE_RE.test(content))
|
|
1797
1827
|
return content;
|
|
1798
1828
|
const rewritten = removeParagraphContaining(content, "`internal/landing/AGENTS.md`");
|
|
1799
|
-
if (
|
|
1829
|
+
if (INTERNAL_CLAUDE_REFERENCE_RE.test(rewritten)) {
|
|
1800
1830
|
throw new Error("Buyer Claude instructions still contain seller-only guidance.");
|
|
1801
1831
|
}
|
|
1802
1832
|
return rewritten;
|
|
@@ -1805,8 +1835,7 @@ function rewriteBuyerSetupInstructions(content) {
|
|
|
1805
1835
|
if (!content.includes("## Create a product repository"))
|
|
1806
1836
|
return content;
|
|
1807
1837
|
const rewritten = replaceMarkdownSection(content, "Create a product repository", "");
|
|
1808
|
-
|
|
1809
|
-
if (new RegExp(`pnpm dlx ${escapedCliName}|bunx ${escapedCliName}|${escapedCliName} repository`, "i").test(rewritten)) {
|
|
1838
|
+
if (SOURCE_DELIVERY_GUIDANCE_RE2.test(rewritten)) {
|
|
1810
1839
|
throw new Error("Buyer setup instructions still contain source-delivery guidance.");
|
|
1811
1840
|
}
|
|
1812
1841
|
return rewritten;
|
|
@@ -1815,30 +1844,29 @@ function rewriteBuyerReleaseInstructions(content) {
|
|
|
1815
1844
|
if (!content.includes("## npm bootstrapper"))
|
|
1816
1845
|
return content;
|
|
1817
1846
|
const rewritten = replaceMarkdownSection(content, "npm bootstrapper", "");
|
|
1818
|
-
|
|
1819
|
-
if (new RegExp(`${escapedCliName}@\\d|public \`${escapedCliName}\` npm package`, "i").test(rewritten)) {
|
|
1847
|
+
if (VENDOR_BOOTSTRAPPER_GUIDANCE_RE2.test(rewritten)) {
|
|
1820
1848
|
throw new Error("Buyer release instructions still contain vendor bootstrapper guidance.");
|
|
1821
1849
|
}
|
|
1822
1850
|
return rewritten;
|
|
1823
1851
|
}
|
|
1824
1852
|
function generateStartDevVars(example, secret = randomBytes(32).toString("base64")) {
|
|
1825
|
-
if (
|
|
1853
|
+
if (!BETTER_AUTH_SECRET_LINE_RE.test(example)) {
|
|
1826
1854
|
throw new Error("apps/start/.dev.vars.example must contain a BETTER_AUTH_SECRET entry.");
|
|
1827
1855
|
}
|
|
1828
|
-
return example.replace(
|
|
1856
|
+
return example.replace(BETTER_AUTH_SECRET_LINE_RE, `BETTER_AUTH_SECRET=${secret}`);
|
|
1829
1857
|
}
|
|
1830
1858
|
function generateServerEnv(example, secret = randomBytes(32).toString("hex"), databasePort = 5432) {
|
|
1831
|
-
if (
|
|
1859
|
+
if (!HEX_64_RE.test(secret)) {
|
|
1832
1860
|
throw new Error("Better Auth secret must be 32 bytes encoded as hex.");
|
|
1833
1861
|
}
|
|
1834
|
-
if (
|
|
1862
|
+
if (!DATABASE_URL_LINE_RE.test(example) || !BETTER_AUTH_SECRET_LINE_RE.test(example)) {
|
|
1835
1863
|
throw new Error("Server .env.example must contain DATABASE_URL and BETTER_AUTH_SECRET entries.");
|
|
1836
1864
|
}
|
|
1837
1865
|
if (!Number.isInteger(databasePort) || databasePort < 1 || databasePort > 65535) {
|
|
1838
1866
|
throw new Error("Database port must be an integer between 1 and 65535.");
|
|
1839
1867
|
}
|
|
1840
1868
|
const databaseUrl = `postgresql://app:app@localhost:${databasePort}/app?sslmode=disable`;
|
|
1841
|
-
return example.replace(
|
|
1869
|
+
return example.replace(DATABASE_URL_LINE_RE, `DATABASE_URL=${databaseUrl}`).replace(BETTER_AUTH_SECRET_LINE_RE, `BETTER_AUTH_SECRET=${secret}`);
|
|
1842
1870
|
}
|
|
1843
1871
|
var SCOPE_EXTENSIONS2 = new Set([".json", ".jsonc", ".md", ".ts", ".tsx", ".yaml", ".yml"]);
|
|
1844
1872
|
function isScopeRewriteFile(path) {
|
|
@@ -1871,7 +1899,7 @@ function isTemplatePathExcluded(relativePath) {
|
|
|
1871
1899
|
if ([".expo", "android", "ios", "credentials", "dist-verify"].includes(mobileEntry))
|
|
1872
1900
|
return true;
|
|
1873
1901
|
}
|
|
1874
|
-
if (parts.includes(".wrangler"))
|
|
1902
|
+
if (parts.includes(".wrangler") || parts.includes(".astro"))
|
|
1875
1903
|
return true;
|
|
1876
1904
|
if (parts.includes("dist") || parts.includes("out"))
|
|
1877
1905
|
return true;
|
|
@@ -1883,7 +1911,7 @@ function isTemplatePathExcluded(relativePath) {
|
|
|
1883
1911
|
return true;
|
|
1884
1912
|
if (basename.endsWith(".tfvars") || basename === "backend.hcl")
|
|
1885
1913
|
return true;
|
|
1886
|
-
if (basename.includes(".tfstate") ||
|
|
1914
|
+
if (basename.includes(".tfstate") || GHA_CREDS_FILE_RE.test(basename))
|
|
1887
1915
|
return true;
|
|
1888
1916
|
if (normalized === "PLAN.md" || normalized === "comments.md")
|
|
1889
1917
|
return true;
|
|
@@ -1907,6 +1935,13 @@ function isTemplatePathExcluded(relativePath) {
|
|
|
1907
1935
|
}
|
|
1908
1936
|
|
|
1909
1937
|
// src/editions.ts
|
|
1938
|
+
var NON_INDENTED_LINE_RE = /^\S/;
|
|
1939
|
+
var IMPORTER_KEY_LINE_RE = /^ {2}(\S[^:]*):\s*$/;
|
|
1940
|
+
var EXTRA_BLANK_LINES_RE = /\n{3,}/g;
|
|
1941
|
+
var START_TABLE_ROW_RE = /^\| `apps\/start` .*\n/gm;
|
|
1942
|
+
var START_PARAGRAPH_RE = /^`apps\/start`[\s\S]*?(?:\n\n|\n?(?![\s\S]))/gm;
|
|
1943
|
+
var WORKER_NAME_FIELD_RE = /("name":\s*")[^"]+(")/;
|
|
1944
|
+
var DATABASE_NAME_FIELD_RE = /("database_name":\s*")[^"]+(")/;
|
|
1910
1945
|
var EDITION_NAMES = ["web", "mobile", "start"];
|
|
1911
1946
|
var START_EDITION_PATHS = ["apps/start", ".github/workflows/deploy-start.yml"];
|
|
1912
1947
|
var START_EDITION_OVERLAY = "apps/start/edition";
|
|
@@ -1916,6 +1951,7 @@ var START_ROOT_CONTRACTS = {
|
|
|
1916
1951
|
};
|
|
1917
1952
|
var START_ONLY_KEPT_PATHS = [
|
|
1918
1953
|
"apps/start",
|
|
1954
|
+
"apps/site",
|
|
1919
1955
|
"packages/billing",
|
|
1920
1956
|
"packages/tsconfig",
|
|
1921
1957
|
".github/workflows/deploy-start.yml",
|
|
@@ -1953,14 +1989,14 @@ function pruneLockfileImporters(content, removedImporters) {
|
|
|
1953
1989
|
let inImporters = false;
|
|
1954
1990
|
let skipping = false;
|
|
1955
1991
|
for (const line of lines) {
|
|
1956
|
-
if (
|
|
1992
|
+
if (NON_INDENTED_LINE_RE.test(line)) {
|
|
1957
1993
|
inImporters = line === "importers:";
|
|
1958
1994
|
skipping = false;
|
|
1959
1995
|
output.push(line);
|
|
1960
1996
|
continue;
|
|
1961
1997
|
}
|
|
1962
1998
|
if (inImporters) {
|
|
1963
|
-
const importer = line.match(
|
|
1999
|
+
const importer = line.match(IMPORTER_KEY_LINE_RE)?.[1];
|
|
1964
2000
|
if (importer !== undefined)
|
|
1965
2001
|
skipping = removed.has(importer);
|
|
1966
2002
|
if (skipping)
|
|
@@ -1969,7 +2005,7 @@ function pruneLockfileImporters(content, removedImporters) {
|
|
|
1969
2005
|
output.push(line);
|
|
1970
2006
|
}
|
|
1971
2007
|
return output.join(`
|
|
1972
|
-
`).replace(
|
|
2008
|
+
`).replace(EXTRA_BLANK_LINES_RE, `
|
|
1973
2009
|
|
|
1974
2010
|
`);
|
|
1975
2011
|
}
|
|
@@ -1984,10 +2020,10 @@ function stripKnipWorkspaces(content, removedWorkspaces) {
|
|
|
1984
2020
|
`;
|
|
1985
2021
|
}
|
|
1986
2022
|
function stripStartReferences(content) {
|
|
1987
|
-
return content.replace(
|
|
2023
|
+
return content.replace(START_TABLE_ROW_RE, "").replace(START_PARAGRAPH_RE, "");
|
|
1988
2024
|
}
|
|
1989
2025
|
function rewriteStartWorkerIdentifiers(content, appName) {
|
|
1990
|
-
return content.replace(
|
|
2026
|
+
return content.replace(WORKER_NAME_FIELD_RE, `$1${appName}$2`).replace(DATABASE_NAME_FIELD_RE, `$1${appName}$2`);
|
|
1991
2027
|
}
|
|
1992
2028
|
function startOnlyRootPackage(rootPackage, overlayPackage) {
|
|
1993
2029
|
const root = JSON.parse(rootPackage);
|
|
@@ -1997,12 +2033,13 @@ function startOnlyRootPackage(rootPackage, overlayPackage) {
|
|
|
1997
2033
|
}
|
|
1998
2034
|
|
|
1999
2035
|
// src/delivery.ts
|
|
2036
|
+
var WHITESPACE_CHAR_RE3 = /\s/;
|
|
2000
2037
|
var RELEASE_TAG_PATTERN = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
|
|
2001
2038
|
function validateLicenseKey(value) {
|
|
2002
2039
|
const key = value.trim();
|
|
2003
2040
|
if (!key)
|
|
2004
2041
|
return "Enter the licence key from your purchase receipt.";
|
|
2005
|
-
if (
|
|
2042
|
+
if (WHITESPACE_CHAR_RE3.test(key))
|
|
2006
2043
|
return "A licence key does not contain spaces.";
|
|
2007
2044
|
if (!key.startsWith(LICENSE_KEY_PREFIX) || key.length === LICENSE_KEY_PREFIX.length) {
|
|
2008
2045
|
return `A ${CLI_NAME2} licence key starts with ${LICENSE_KEY_PREFIX}. Copy it from your purchase receipt or the customer portal.`;
|
|
@@ -2024,7 +2061,6 @@ function normalizeSourceVersion(value) {
|
|
|
2024
2061
|
// node_modules/@clack/prompts/dist/index.mjs
|
|
2025
2062
|
import { styleText as styleText3, stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
|
|
2026
2063
|
import process$12 from "node:process";
|
|
2027
|
-
var import_sisteransi3 = __toESM(require_src(), 1);
|
|
2028
2064
|
function isUnicodeSupported2() {
|
|
2029
2065
|
if (process$12.platform !== "win32") {
|
|
2030
2066
|
return process$12.env.TERM !== "linux";
|
|
@@ -2033,7 +2069,7 @@ function isUnicodeSupported2() {
|
|
|
2033
2069
|
}
|
|
2034
2070
|
var unicode2 = isUnicodeSupported2();
|
|
2035
2071
|
var isCI2 = () => process.env.CI === "true";
|
|
2036
|
-
var unicodeOr2 = (
|
|
2072
|
+
var unicodeOr2 = (e, o) => unicode2 ? e : o;
|
|
2037
2073
|
var S_STEP_ACTIVE2 = unicodeOr2("◆", "*");
|
|
2038
2074
|
var S_STEP_CANCEL2 = unicodeOr2("■", "x");
|
|
2039
2075
|
var S_STEP_ERROR2 = unicodeOr2("▲", "x");
|
|
@@ -2059,8 +2095,8 @@ var S_INFO2 = unicodeOr2("●", "•");
|
|
|
2059
2095
|
var S_SUCCESS2 = unicodeOr2("◆", "*");
|
|
2060
2096
|
var S_WARN2 = unicodeOr2("▲", "!");
|
|
2061
2097
|
var S_ERROR2 = unicodeOr2("■", "x");
|
|
2062
|
-
var symbol2 = (
|
|
2063
|
-
switch (
|
|
2098
|
+
var symbol2 = (e) => {
|
|
2099
|
+
switch (e) {
|
|
2064
2100
|
case "initial":
|
|
2065
2101
|
case "active":
|
|
2066
2102
|
return styleText3("cyan", S_STEP_ACTIVE2);
|
|
@@ -2074,8 +2110,8 @@ var symbol2 = (o) => {
|
|
|
2074
2110
|
return styleText3("dim", S_STEP_ACTIVE2);
|
|
2075
2111
|
}
|
|
2076
2112
|
};
|
|
2077
|
-
var symbolBar = (
|
|
2078
|
-
switch (
|
|
2113
|
+
var symbolBar = (e) => {
|
|
2114
|
+
switch (e) {
|
|
2079
2115
|
case "initial":
|
|
2080
2116
|
case "active":
|
|
2081
2117
|
return styleText3("cyan", S_BAR2);
|
|
@@ -2087,11 +2123,12 @@ var symbolBar = (o) => {
|
|
|
2087
2123
|
return styleText3("green", S_BAR2);
|
|
2088
2124
|
}
|
|
2089
2125
|
};
|
|
2090
|
-
function formatInstructionFooter(o,
|
|
2091
|
-
const
|
|
2092
|
-
|
|
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;
|
|
2093
2130
|
}
|
|
2094
|
-
var
|
|
2131
|
+
var E$1 = (l, e, w, p, b, C = false) => {
|
|
2095
2132
|
let r = e, O = 0;
|
|
2096
2133
|
if (C)
|
|
2097
2134
|
for (let i = p - 1;i >= w; i--) {
|
|
@@ -2120,12 +2157,12 @@ var limitOptions = ({
|
|
|
2120
2157
|
let f = 0;
|
|
2121
2158
|
l >= a - 3 && (f = Math.max(Math.min(l - a + 3, e.length - a), 0));
|
|
2122
2159
|
let d = a < e.length && f > 0, c = a < e.length && f + a < e.length;
|
|
2123
|
-
const
|
|
2160
|
+
const I = Math.min(f + a, e.length), s = [];
|
|
2124
2161
|
let g = 0;
|
|
2125
2162
|
d && g++, c && g++;
|
|
2126
|
-
const T = f + (d ? 1 : 0),
|
|
2127
|
-
for (let t = T;t <
|
|
2128
|
-
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, {
|
|
2129
2166
|
hard: true,
|
|
2130
2167
|
trim: false
|
|
2131
2168
|
}).split(`
|
|
@@ -2136,8 +2173,8 @@ var limitOptions = ({
|
|
|
2136
2173
|
let t = 0, n = 0, o = g;
|
|
2137
2174
|
const h = l - T;
|
|
2138
2175
|
let u = v;
|
|
2139
|
-
const L = () =>
|
|
2140
|
-
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));
|
|
2141
2178
|
}
|
|
2142
2179
|
const x = [];
|
|
2143
2180
|
d && x.push(M);
|
|
@@ -2186,82 +2223,82 @@ var MULTISELECT_INSTRUCTIONS2 = [
|
|
|
2186
2223
|
`${styleText3("dim", "Space:")} select`,
|
|
2187
2224
|
`${styleText3("dim", "Enter:")} confirm`
|
|
2188
2225
|
];
|
|
2189
|
-
var m = (
|
|
2226
|
+
var m = (n, u) => n.split(`
|
|
2190
2227
|
`).map((d) => u(d)).join(`
|
|
2191
2228
|
`);
|
|
2192
|
-
var multiselect = (
|
|
2229
|
+
var multiselect = (n) => {
|
|
2193
2230
|
const u = (t, a) => {
|
|
2194
2231
|
const r = t.label ?? String(t.value);
|
|
2195
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))}`;
|
|
2196
|
-
}, d =
|
|
2233
|
+
}, d = n.required ?? true, x = n.showInstructions ?? true;
|
|
2197
2234
|
return new a({
|
|
2198
|
-
options:
|
|
2199
|
-
signal:
|
|
2200
|
-
input:
|
|
2201
|
-
output:
|
|
2202
|
-
initialValues:
|
|
2235
|
+
options: n.options,
|
|
2236
|
+
signal: n.signal,
|
|
2237
|
+
input: n.input,
|
|
2238
|
+
output: n.output,
|
|
2239
|
+
initialValues: n.initialValues,
|
|
2203
2240
|
required: d,
|
|
2204
|
-
cursorAt:
|
|
2241
|
+
cursorAt: n.cursorAt,
|
|
2205
2242
|
validate(t) {
|
|
2206
2243
|
if (d && (t === undefined || t.length === 0))
|
|
2207
2244
|
return `Please select at least one option.
|
|
2208
2245
|
${styleText3("reset", styleText3("dim", `Press ${styleText3(["gray", "bgWhite", "inverse"], " space ")} to select, ${styleText3("gray", styleText3("bgWhite", styleText3("inverse", " enter ")))} to submit`))}`;
|
|
2209
2246
|
},
|
|
2210
2247
|
render() {
|
|
2211
|
-
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)}
|
|
2212
2249
|
` : ""}${a}
|
|
2213
|
-
`, o = this.value ?? [], g = (
|
|
2214
|
-
if (
|
|
2215
|
-
return u(
|
|
2216
|
-
const s = o.includes(
|
|
2217
|
-
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");
|
|
2218
2255
|
};
|
|
2219
2256
|
switch (this.state) {
|
|
2220
2257
|
case "submit": {
|
|
2221
|
-
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)} ` : "");
|
|
2222
2259
|
return `${r}${l}`;
|
|
2223
2260
|
}
|
|
2224
2261
|
case "cancel": {
|
|
2225
|
-
const
|
|
2226
|
-
if (
|
|
2262
|
+
const i = this.options.filter(({ value: s }) => o.includes(s)).map((s) => u(s, "cancelled")).join(styleText3("dim", ", "));
|
|
2263
|
+
if (i.trim() === "")
|
|
2227
2264
|
return `${r}${styleText3("gray", S_BAR2)}`;
|
|
2228
|
-
const l = wrapTextWithPrefix(
|
|
2265
|
+
const l = wrapTextWithPrefix(n.output, i, t ? `${styleText3("gray", S_BAR2)} ` : "");
|
|
2229
2266
|
return `${r}${l}${t ? `
|
|
2230
2267
|
${styleText3("gray", S_BAR2)}` : ""}`;
|
|
2231
2268
|
}
|
|
2232
2269
|
case "error": {
|
|
2233
|
-
const
|
|
2270
|
+
const i = t ? `${styleText3("yellow", S_BAR2)} ` : "", l = this.error.split(`
|
|
2234
2271
|
`).map(($, v) => v === 0 ? `${t ? `${styleText3("yellow", S_BAR_END2)} ` : ""}${styleText3("yellow", $)}` : ` ${$}`).join(`
|
|
2235
2272
|
`), s = r.split(`
|
|
2236
2273
|
`).length, h = l.split(`
|
|
2237
2274
|
`).length + 1;
|
|
2238
|
-
return `${r}${
|
|
2239
|
-
output:
|
|
2275
|
+
return `${r}${i}${limitOptions({
|
|
2276
|
+
output: n.output,
|
|
2240
2277
|
options: this.options,
|
|
2241
2278
|
cursor: this.cursor,
|
|
2242
|
-
maxItems:
|
|
2243
|
-
columnPadding:
|
|
2279
|
+
maxItems: n.maxItems,
|
|
2280
|
+
columnPadding: i.length,
|
|
2244
2281
|
rowPadding: s + h,
|
|
2245
2282
|
style: g
|
|
2246
2283
|
}).join(`
|
|
2247
|
-
${
|
|
2284
|
+
${i}`)}
|
|
2248
2285
|
${l}
|
|
2249
2286
|
`;
|
|
2250
2287
|
}
|
|
2251
2288
|
default: {
|
|
2252
|
-
const
|
|
2253
|
-
`).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(`
|
|
2254
2291
|
`), $ = s.length + 1;
|
|
2255
|
-
return `${r}${
|
|
2256
|
-
output:
|
|
2292
|
+
return `${r}${i}${limitOptions({
|
|
2293
|
+
output: n.output,
|
|
2257
2294
|
options: this.options,
|
|
2258
2295
|
cursor: this.cursor,
|
|
2259
|
-
maxItems:
|
|
2260
|
-
columnPadding:
|
|
2296
|
+
maxItems: n.maxItems,
|
|
2297
|
+
columnPadding: i.length,
|
|
2261
2298
|
rowPadding: l + $,
|
|
2262
2299
|
style: g
|
|
2263
2300
|
}).join(`
|
|
2264
|
-
${
|
|
2301
|
+
${i}`)}
|
|
2265
2302
|
${h}
|
|
2266
2303
|
`;
|
|
2267
2304
|
}
|
|
@@ -2329,85 +2366,103 @@ ${styleText3("gray", S_BAR_END2)} ` : "";
|
|
|
2329
2366
|
|
|
2330
2367
|
`);
|
|
2331
2368
|
};
|
|
2332
|
-
var
|
|
2369
|
+
var Y2 = 30000;
|
|
2370
|
+
var Z2 = (c) => styleText3("magenta", c);
|
|
2333
2371
|
var spinner2 = ({
|
|
2334
|
-
indicator:
|
|
2335
|
-
onCancel:
|
|
2372
|
+
indicator: c = "dots",
|
|
2373
|
+
onCancel: b,
|
|
2336
2374
|
output: n = process.stdout,
|
|
2337
|
-
cancelMessage:
|
|
2338
|
-
errorMessage:
|
|
2339
|
-
frames:
|
|
2340
|
-
delay:
|
|
2341
|
-
signal:
|
|
2342
|
-
...
|
|
2375
|
+
cancelMessage: _,
|
|
2376
|
+
errorMessage: C,
|
|
2377
|
+
frames: M = unicode2 ? ["◒", "◐", "◓", "◑"] : ["•", "o", "O", "0"],
|
|
2378
|
+
delay: O = unicode2 ? 80 : 120,
|
|
2379
|
+
signal: g,
|
|
2380
|
+
...E
|
|
2343
2381
|
} = {}) => {
|
|
2344
|
-
const u = isCI2();
|
|
2345
|
-
let
|
|
2346
|
-
const
|
|
2347
|
-
const
|
|
2348
|
-
|
|
2349
|
-
},
|
|
2350
|
-
process.on("uncaughtExceptionMonitor",
|
|
2351
|
-
},
|
|
2352
|
-
process.removeListener("uncaughtExceptionMonitor",
|
|
2353
|
-
},
|
|
2354
|
-
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)
|
|
2355
2393
|
return;
|
|
2356
2394
|
u && n.write(`
|
|
2357
2395
|
`);
|
|
2358
|
-
const
|
|
2396
|
+
const s = wrapAnsi(v, y, {
|
|
2359
2397
|
hard: true,
|
|
2360
2398
|
trim: false
|
|
2361
2399
|
}).split(`
|
|
2362
2400
|
`);
|
|
2363
|
-
|
|
2364
|
-
},
|
|
2365
|
-
const
|
|
2366
|
-
return
|
|
2367
|
-
}, N =
|
|
2368
|
-
|
|
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}
|
|
2369
2411
|
`);
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
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)
|
|
2373
2420
|
return;
|
|
2374
|
-
|
|
2375
|
-
const
|
|
2376
|
-
let
|
|
2421
|
+
R(), v = r;
|
|
2422
|
+
const t = F(M[s]);
|
|
2423
|
+
let o;
|
|
2377
2424
|
if (u)
|
|
2378
|
-
|
|
2379
|
-
else if (
|
|
2380
|
-
|
|
2425
|
+
o = `${t} ${r}...`;
|
|
2426
|
+
else if (c === "timer")
|
|
2427
|
+
o = `${t} ${r} ${T(w)}`;
|
|
2381
2428
|
else {
|
|
2382
|
-
const
|
|
2383
|
-
|
|
2429
|
+
const j = ".".repeat(Math.floor(i)).slice(0, 3);
|
|
2430
|
+
o = `${t} ${r}${j}`;
|
|
2384
2431
|
}
|
|
2385
|
-
const
|
|
2432
|
+
const D = wrapAnsi(o, y, {
|
|
2386
2433
|
hard: true,
|
|
2387
2434
|
trim: false
|
|
2388
2435
|
});
|
|
2389
|
-
n.write(
|
|
2390
|
-
},
|
|
2391
|
-
},
|
|
2392
|
-
if (
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
`) : n.write(`${o}
|
|
2398
|
-
`)
|
|
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
|
+
}
|
|
2399
2454
|
};
|
|
2400
2455
|
return {
|
|
2401
2456
|
start: P,
|
|
2402
|
-
stop: (e = "") =>
|
|
2457
|
+
stop: (e = "") => m(e, 0),
|
|
2403
2458
|
message: (e = "") => {
|
|
2404
|
-
|
|
2459
|
+
r = L(e ?? r);
|
|
2405
2460
|
},
|
|
2406
|
-
cancel: (e = "") =>
|
|
2407
|
-
error: (e = "") =>
|
|
2408
|
-
clear: () =>
|
|
2461
|
+
cancel: (e = "") => m(e, 1),
|
|
2462
|
+
error: (e = "") => m(e, 2),
|
|
2463
|
+
clear: () => m("", 0, true),
|
|
2409
2464
|
get isCancelled() {
|
|
2410
|
-
return
|
|
2465
|
+
return l;
|
|
2411
2466
|
}
|
|
2412
2467
|
};
|
|
2413
2468
|
};
|
|
@@ -2420,7 +2475,7 @@ var SELECT_INSTRUCTIONS2 = [
|
|
|
2420
2475
|
`${styleText3("dim", "↑/↓")} to navigate`,
|
|
2421
2476
|
`${styleText3("dim", "Enter:")} confirm`
|
|
2422
2477
|
];
|
|
2423
|
-
var
|
|
2478
|
+
var c2 = (t, o) => t.includes(`
|
|
2424
2479
|
`) ? t.split(`
|
|
2425
2480
|
`).map((d) => o(d)).join(`
|
|
2426
2481
|
`) : o(t);
|
|
@@ -2431,15 +2486,15 @@ var select = (t) => {
|
|
|
2431
2486
|
const s = n.label ?? String(n.value);
|
|
2432
2487
|
switch (m) {
|
|
2433
2488
|
case "disabled":
|
|
2434
|
-
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"})`)}` : ""}`;
|
|
2435
2490
|
case "selected":
|
|
2436
|
-
return `${
|
|
2491
|
+
return `${c2(s, (i) => styleText3("dim", i))}`;
|
|
2437
2492
|
case "active":
|
|
2438
2493
|
return `${styleText3("green", S_RADIO_ACTIVE2)} ${s}${n.hint ? ` ${styleText3("dim", `(${n.hint})`)}` : ""}`;
|
|
2439
2494
|
case "cancelled":
|
|
2440
|
-
return `${
|
|
2495
|
+
return `${c2(s, (i) => styleText3(["strikethrough", "dim"], i))}`;
|
|
2441
2496
|
default:
|
|
2442
|
-
return `${styleText3("dim", S_RADIO_INACTIVE2)} ${
|
|
2497
|
+
return `${styleText3("dim", S_RADIO_INACTIVE2)} ${c2(s, (i) => styleText3("dim", i))}`;
|
|
2443
2498
|
}
|
|
2444
2499
|
}, d = t.showInstructions ?? true;
|
|
2445
2500
|
return new n$1({
|
|
@@ -2464,7 +2519,7 @@ ${styleText3("gray", S_BAR2)}` : ""}`;
|
|
|
2464
2519
|
}
|
|
2465
2520
|
default: {
|
|
2466
2521
|
const r = n ? `${styleText3("cyan", S_BAR2)} ` : "", a = u.split(`
|
|
2467
|
-
`).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(`
|
|
2468
2523
|
`), b = p.length + 1;
|
|
2469
2524
|
return `${u}${r}${limitOptions({
|
|
2470
2525
|
output: t.output,
|
|
@@ -2588,6 +2643,8 @@ function isExecutable(path) {
|
|
|
2588
2643
|
}
|
|
2589
2644
|
|
|
2590
2645
|
// src/agent-setup.ts
|
|
2646
|
+
var CODEX_AGENT_DOCS_TABLE_RE = /^\[mcp_servers\.(?:agent-docs|agent_docs|"agent-docs")\]$/m;
|
|
2647
|
+
var LINE_BREAK_RE = /\r?\n/;
|
|
2591
2648
|
var CLIENTS = ["codex", "claude", "cursor"];
|
|
2592
2649
|
var SERVER_NAME = "agent-docs";
|
|
2593
2650
|
var CODEX_START = "# agent-context:start";
|
|
@@ -2681,7 +2738,7 @@ function mergeCodexConfig(existing, root) {
|
|
|
2681
2738
|
|
|
2682
2739
|
${managed}${existing.slice(after)}`.trimStart();
|
|
2683
2740
|
}
|
|
2684
|
-
if (
|
|
2741
|
+
if (CODEX_AGENT_DOCS_TABLE_RE.test(existing)) {
|
|
2685
2742
|
throw new Error("Codex already has an unmanaged agent-docs server; remove or rename it first.");
|
|
2686
2743
|
}
|
|
2687
2744
|
return `${existing.trimEnd()}${existing.trim() ? `
|
|
@@ -2732,14 +2789,14 @@ async function addGitInfoExcludes(root, paths) {
|
|
|
2732
2789
|
return false;
|
|
2733
2790
|
const excludePath = resolve(root, rawPath);
|
|
2734
2791
|
const existing = await readFile(excludePath, "utf8").catch(() => "");
|
|
2735
|
-
const missing = paths.filter((path) => !existing.split(
|
|
2792
|
+
const missing = paths.filter((path) => !existing.split(LINE_BREAK_RE).includes(path));
|
|
2736
2793
|
if (missing.length === 0)
|
|
2737
2794
|
return true;
|
|
2738
2795
|
const prefix = existing && !existing.endsWith(`
|
|
2739
2796
|
`) ? `
|
|
2740
2797
|
` : "";
|
|
2741
2798
|
const marker = "# Local agent configuration";
|
|
2742
|
-
const heading = existing.split(
|
|
2799
|
+
const heading = existing.split(LINE_BREAK_RE).includes(marker) ? "" : `${marker}
|
|
2743
2800
|
`;
|
|
2744
2801
|
await atomicWrite(excludePath, `${existing}${prefix}${heading}${missing.join(`
|
|
2745
2802
|
`)}
|
|
@@ -2889,24 +2946,40 @@ import { resolve as resolve2 } from "node:path";
|
|
|
2889
2946
|
// src/deployment.ts
|
|
2890
2947
|
import { mkdir as mkdir2, readFile as readFile2, rm, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
2891
2948
|
import { join as join3 } from "node:path";
|
|
2892
|
-
var
|
|
2893
|
-
var
|
|
2949
|
+
var DEPLOYMENT_ENVIRONMENT_RE = /^[a-z][a-z0-9-]{1,30}$/;
|
|
2950
|
+
var REGION_RE = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
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])?$/;
|
|
2952
|
+
var deploymentClouds = ["aws", "gcp", "railway"];
|
|
2953
|
+
var openTofuClouds = ["aws", "gcp"];
|
|
2954
|
+
var frontendTargets = ["native", "cloudflare"];
|
|
2894
2955
|
var storageProviders = ["r2", "s3", "gcs"];
|
|
2895
2956
|
var defaultRegions = {
|
|
2896
2957
|
aws: "us-east-1",
|
|
2897
|
-
gcp: "us-central1"
|
|
2898
|
-
azure: "eastus2"
|
|
2958
|
+
gcp: "us-central1"
|
|
2899
2959
|
};
|
|
2900
2960
|
var defaultStorageProviders = {
|
|
2901
2961
|
aws: "s3",
|
|
2902
2962
|
gcp: "gcs",
|
|
2903
|
-
|
|
2963
|
+
railway: "r2"
|
|
2904
2964
|
};
|
|
2905
2965
|
var storageProvidersByCloud = {
|
|
2906
2966
|
aws: ["s3", "r2"],
|
|
2907
2967
|
gcp: ["gcs", "r2"],
|
|
2908
|
-
|
|
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"
|
|
2909
2979
|
};
|
|
2980
|
+
function isOpenTofuCloud(cloud) {
|
|
2981
|
+
return openTofuClouds.some((candidate) => candidate === cloud);
|
|
2982
|
+
}
|
|
2910
2983
|
function isDeploymentCloud(value) {
|
|
2911
2984
|
return deploymentClouds.some((cloud) => cloud === value);
|
|
2912
2985
|
}
|
|
@@ -2918,12 +2991,12 @@ function isStorageProvider(value) {
|
|
|
2918
2991
|
}
|
|
2919
2992
|
function parseDeploymentCloud(value) {
|
|
2920
2993
|
if (!isDeploymentCloud(value))
|
|
2921
|
-
throw new Error("Cloud must be aws, gcp, or
|
|
2994
|
+
throw new Error("Cloud must be aws, gcp, or railway.");
|
|
2922
2995
|
return value;
|
|
2923
2996
|
}
|
|
2924
2997
|
function parseFrontendTarget(value) {
|
|
2925
2998
|
if (!isFrontendTarget(value)) {
|
|
2926
|
-
throw new Error("Frontend target must be native
|
|
2999
|
+
throw new Error("Frontend target must be native or cloudflare.");
|
|
2927
3000
|
}
|
|
2928
3001
|
return value;
|
|
2929
3002
|
}
|
|
@@ -2933,19 +3006,19 @@ function parseStorageProvider(value) {
|
|
|
2933
3006
|
return value;
|
|
2934
3007
|
}
|
|
2935
3008
|
function validateDeploymentEnvironment(value) {
|
|
2936
|
-
if (
|
|
3009
|
+
if (!DEPLOYMENT_ENVIRONMENT_RE.test(value)) {
|
|
2937
3010
|
return "Environment must use 2-31 lowercase letters, numbers, or hyphens.";
|
|
2938
3011
|
}
|
|
2939
3012
|
return;
|
|
2940
3013
|
}
|
|
2941
3014
|
function validateRegion(value) {
|
|
2942
|
-
if (
|
|
3015
|
+
if (!REGION_RE.test(value)) {
|
|
2943
3016
|
return "Region must use 2-32 lowercase letters, numbers, or hyphens.";
|
|
2944
3017
|
}
|
|
2945
3018
|
return;
|
|
2946
3019
|
}
|
|
2947
3020
|
function validateDomain(value) {
|
|
2948
|
-
if (value.length > 80 ||
|
|
3021
|
+
if (value.length > 80 || !DOMAIN_RE.test(value)) {
|
|
2949
3022
|
return "Enter a lowercase hostname such as api.example.com, without a protocol or path.";
|
|
2950
3023
|
}
|
|
2951
3024
|
return;
|
|
@@ -2954,12 +3027,24 @@ function validateDeploymentSelection(selection) {
|
|
|
2954
3027
|
if (!storageProvidersByCloud[selection.cloud].includes(selection.storageProvider)) {
|
|
2955
3028
|
throw new Error(`${selection.storageProvider} storage is not supported for ${selection.cloud} deployments.`);
|
|
2956
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
|
+
}
|
|
2957
3045
|
const environmentError = validateDeploymentEnvironment(selection.environment);
|
|
2958
3046
|
if (environmentError)
|
|
2959
3047
|
throw new Error(environmentError);
|
|
2960
|
-
const regionError = validateRegion(selection.region);
|
|
2961
|
-
if (regionError)
|
|
2962
|
-
throw new Error(regionError);
|
|
2963
3048
|
const apiError = validateDomain(selection.apiDomain);
|
|
2964
3049
|
if (apiError)
|
|
2965
3050
|
throw new Error(`API domain: ${apiError}`);
|
|
@@ -2991,20 +3076,86 @@ function createDeploymentConfig(projectName, selection) {
|
|
|
2991
3076
|
ha_enabled: selection.haEnabled
|
|
2992
3077
|
};
|
|
2993
3078
|
}
|
|
2994
|
-
function
|
|
2995
|
-
|
|
2996
|
-
|
|
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";
|
|
2997
3153
|
const storageSetup = config.storage_provider === "r2" ? `Create private staging and public Cloudflare R2 buckets, then store the required storage
|
|
2998
3154
|
variables in the runtime secret container.` : `${config.storage_provider.toUpperCase()} application buckets, lifecycle policy, CORS, public access, and workload permissions are
|
|
2999
3155
|
managed by the selected OpenTofu root.`;
|
|
3000
3156
|
return `# Deployment setup
|
|
3001
3157
|
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
- backend: **${config.cloud.toUpperCase()}**;
|
|
3005
|
-
- storage: **${config.storage_provider}**;
|
|
3006
|
-
- frontend: **${config.frontend_target}**;
|
|
3007
|
-
- environment: **${config.environment}**.
|
|
3158
|
+
${lockSummary(config)}
|
|
3008
3159
|
|
|
3009
3160
|
## Protected GitHub environment
|
|
3010
3161
|
|
|
@@ -3014,8 +3165,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
3014
3165
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
3015
3166
|
- a \`TFVARS_JSON\` secret containing only:
|
|
3016
3167
|
- ${providerInputs};
|
|
3017
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3018
|
-
|
|
3168
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a public
|
|
3169
|
+
marketing site.
|
|
3019
3170
|
|
|
3020
3171
|
## Cloud preparation
|
|
3021
3172
|
|
|
@@ -3036,9 +3187,33 @@ The workflow creates the registry first, pushes an immutable image, provisions t
|
|
|
3036
3187
|
traffic disabled, runs migrations, and admits traffic only after migration succeeds.
|
|
3037
3188
|
|
|
3038
3189
|
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3039
|
-
before changing
|
|
3190
|
+
before changing providers.
|
|
3040
3191
|
`;
|
|
3041
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
|
+
}
|
|
3042
3217
|
async function removeIfPresent(target) {
|
|
3043
3218
|
if (!await stat2(target).catch(() => {
|
|
3044
3219
|
return;
|
|
@@ -3047,6 +3222,20 @@ async function removeIfPresent(target) {
|
|
|
3047
3222
|
await rm(target, { recursive: true, force: true });
|
|
3048
3223
|
return true;
|
|
3049
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
|
+
}
|
|
3050
3239
|
async function applyDeploymentSelection(repositoryRoot, projectName, selection) {
|
|
3051
3240
|
const config = createDeploymentConfig(projectName, selection);
|
|
3052
3241
|
const configPath = join3(repositoryRoot, "deployment/config.json");
|
|
@@ -3060,40 +3249,12 @@ async function applyDeploymentSelection(repositoryRoot, projectName, selection)
|
|
|
3060
3249
|
`);
|
|
3061
3250
|
await writeFile2(join3(repositoryRoot, "deployment/README.md"), deploymentReadme(config));
|
|
3062
3251
|
const removed = [];
|
|
3063
|
-
for (const
|
|
3064
|
-
if (
|
|
3065
|
-
|
|
3066
|
-
for (const relativePath of [
|
|
3067
|
-
`infra/bootstrap/${cloud}`,
|
|
3068
|
-
`infra/modules/${cloud}`,
|
|
3069
|
-
`infra/roots/${cloud}`,
|
|
3070
|
-
`scripts/deploy/release-${cloud}.sh`,
|
|
3071
|
-
`scripts/deploy/frontend-${cloud}.sh`
|
|
3072
|
-
]) {
|
|
3073
|
-
if (await removeIfPresent(join3(repositoryRoot, relativePath)))
|
|
3074
|
-
removed.push(relativePath);
|
|
3075
|
-
}
|
|
3076
|
-
}
|
|
3077
|
-
const adapters = [
|
|
3078
|
-
{ target: "vercel", path: "apps/web/vercel.json" },
|
|
3079
|
-
{ target: "cloudflare", path: "apps/web/wrangler.toml" },
|
|
3080
|
-
{
|
|
3081
|
-
target: "azure-native",
|
|
3082
|
-
path: "apps/web/public/staticwebapp.config.json"
|
|
3083
|
-
}
|
|
3084
|
-
];
|
|
3085
|
-
const selectedAdapter = selection.frontendTarget === "native" ? selection.cloud === "azure" ? "azure-native" : undefined : selection.frontendTarget;
|
|
3086
|
-
for (const adapter of adapters) {
|
|
3087
|
-
if (adapter.target === selectedAdapter)
|
|
3088
|
-
continue;
|
|
3089
|
-
if (await removeIfPresent(join3(repositoryRoot, adapter.path)))
|
|
3090
|
-
removed.push(adapter.path);
|
|
3091
|
-
}
|
|
3092
|
-
if (selection.frontendTarget !== "native") {
|
|
3093
|
-
const nativeScript = `scripts/deploy/frontend-${selection.cloud}.sh`;
|
|
3094
|
-
if (await removeIfPresent(join3(repositoryRoot, nativeScript)))
|
|
3095
|
-
removed.push(nativeScript);
|
|
3252
|
+
for (const relativePath of unusedDeploymentPaths(selection)) {
|
|
3253
|
+
if (await removeIfPresent(join3(repositoryRoot, relativePath)))
|
|
3254
|
+
removed.push(relativePath);
|
|
3096
3255
|
}
|
|
3256
|
+
if (removed.includes("infra"))
|
|
3257
|
+
await removeInfrastructureScript(repositoryRoot);
|
|
3097
3258
|
return removed;
|
|
3098
3259
|
}
|
|
3099
3260
|
|
|
@@ -3112,37 +3273,31 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3112
3273
|
if (!defaults.cloud || !defaults.apiDomain || !defaults.frontendDomain) {
|
|
3113
3274
|
throw new Error("Non-interactive deployment setup requires --cloud, --api-domain, and --frontend-domain.");
|
|
3114
3275
|
}
|
|
3276
|
+
const cloud = defaults.cloud;
|
|
3115
3277
|
return {
|
|
3116
|
-
cloud
|
|
3117
|
-
storageProvider: defaults.storageProvider ?? defaultStorageProviders[
|
|
3118
|
-
frontendTarget: defaults.frontendTarget ?? "
|
|
3278
|
+
cloud,
|
|
3279
|
+
storageProvider: defaults.storageProvider ?? defaultStorageProviders[cloud],
|
|
3280
|
+
frontendTarget: defaults.frontendTarget ?? frontendTargetsByCloud[cloud][0] ?? "cloudflare",
|
|
3119
3281
|
environment: defaults.environment ?? "production",
|
|
3120
|
-
region: defaults.region ?? defaultRegions[defaults.
|
|
3282
|
+
region: isOpenTofuCloud(cloud) ? defaults.region ?? defaultRegions[cloud] : defaults.region ?? null,
|
|
3121
3283
|
apiDomain: defaults.apiDomain,
|
|
3122
3284
|
frontendDomain: defaults.frontendDomain,
|
|
3123
3285
|
haEnabled: defaults.haEnabled ?? false
|
|
3124
3286
|
};
|
|
3125
3287
|
}
|
|
3126
3288
|
const cloud = defaults.cloud ?? parseDeploymentCloud(await unwrap(await select({
|
|
3127
|
-
message: "Backend
|
|
3289
|
+
message: "Backend host",
|
|
3128
3290
|
options: [
|
|
3129
|
-
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL" },
|
|
3130
|
-
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL" },
|
|
3131
|
-
{ 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" }
|
|
3132
3294
|
]
|
|
3133
3295
|
})));
|
|
3134
|
-
const
|
|
3296
|
+
const frontendOptions = frontendTargetsByCloud[cloud];
|
|
3297
|
+
const frontendTarget = defaults.frontendTarget ?? (frontendOptions.length === 1 ? frontendOptions[0] ?? "cloudflare" : parseFrontendTarget(await unwrap(await select({
|
|
3135
3298
|
message: "Frontend host",
|
|
3136
|
-
options:
|
|
3137
|
-
|
|
3138
|
-
{ value: "vercel", label: "Vercel SPA", hint: "Static frontend only" },
|
|
3139
|
-
{
|
|
3140
|
-
value: "cloudflare",
|
|
3141
|
-
label: "Cloudflare static assets",
|
|
3142
|
-
hint: "No API Worker"
|
|
3143
|
-
}
|
|
3144
|
-
]
|
|
3145
|
-
})));
|
|
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
|
+
}))));
|
|
3146
3301
|
const storageProvider = defaults.storageProvider ?? (storageProvidersByCloud[cloud].length === 1 ? storageProvidersByCloud[cloud][0] ?? defaultStorageProviders[cloud] : parseStorageProvider(await unwrap(await select({
|
|
3147
3302
|
message: "Object storage",
|
|
3148
3303
|
options: storageProvidersByCloud[cloud].map((provider) => ({
|
|
@@ -3157,8 +3312,8 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3157
3312
|
defaultValue: "production",
|
|
3158
3313
|
validate: (value) => validateDeploymentEnvironment(value || "production")
|
|
3159
3314
|
}));
|
|
3160
|
-
const region = defaults.region ?? await unwrap(await text2({
|
|
3161
|
-
message: `${cloud
|
|
3315
|
+
const region = !isOpenTofuCloud(cloud) ? defaults.region ?? null : defaults.region ?? await unwrap(await text2({
|
|
3316
|
+
message: `${cloudLabels[cloud]} region`,
|
|
3162
3317
|
placeholder: defaultRegions[cloud],
|
|
3163
3318
|
defaultValue: defaultRegions[cloud],
|
|
3164
3319
|
validate: (value) => validateRegion(value || defaultRegions[cloud])
|
|
@@ -3173,7 +3328,7 @@ async function collectDeploymentSelection(defaults = {}, nonInteractive = false)
|
|
|
3173
3328
|
placeholder: "app.example.com",
|
|
3174
3329
|
validate: (value) => validateDomain(value ?? "")
|
|
3175
3330
|
}));
|
|
3176
|
-
const haEnabled = defaults.haEnabled ?? await unwrap(await confirm2({
|
|
3331
|
+
const haEnabled = !isOpenTofuCloud(cloud) ? defaults.haEnabled ?? false : defaults.haEnabled ?? await unwrap(await confirm2({
|
|
3177
3332
|
message: "Enable the higher-availability profile?",
|
|
3178
3333
|
initialValue: false
|
|
3179
3334
|
}));
|
|
@@ -3235,10 +3390,11 @@ async function runDeploymentConfigure(argv, productName, repositoryRoot = proces
|
|
|
3235
3390
|
try {
|
|
3236
3391
|
const selection = await collectDeploymentSelection(parsed, parsed.yes);
|
|
3237
3392
|
const removed = await applyDeploymentSelection(root, rootPackage.name, selection);
|
|
3238
|
-
log2.success(`${selection.cloud
|
|
3393
|
+
log2.success(`${cloudLabels[selection.cloud]} + ${selection.storageProvider} + ${selection.frontendTarget} is locked for ${selection.environment}.`);
|
|
3239
3394
|
log2.info(`${removed.length} unused deployment paths removed.`);
|
|
3240
3395
|
log2.step("Next");
|
|
3241
|
-
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
|
|
3242
3398
|
Configure the protected GitHub environment
|
|
3243
3399
|
Dispatch .github/workflows/deploy-cloud.yml`);
|
|
3244
3400
|
outro2("Deployment configuration is ready to commit.");
|
|
@@ -3329,37 +3485,31 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3329
3485
|
if (!defaults.cloud || !defaults.apiDomain || !defaults.frontendDomain) {
|
|
3330
3486
|
throw new Error("Non-interactive deployment setup requires --cloud, --api-domain, and --frontend-domain.");
|
|
3331
3487
|
}
|
|
3488
|
+
const cloud = defaults.cloud;
|
|
3332
3489
|
return {
|
|
3333
|
-
cloud
|
|
3334
|
-
storageProvider: defaults.storageProvider ?? defaultStorageProviders[
|
|
3335
|
-
frontendTarget: defaults.frontendTarget ?? "
|
|
3490
|
+
cloud,
|
|
3491
|
+
storageProvider: defaults.storageProvider ?? defaultStorageProviders[cloud],
|
|
3492
|
+
frontendTarget: defaults.frontendTarget ?? frontendTargetsByCloud[cloud][0] ?? "cloudflare",
|
|
3336
3493
|
environment: defaults.environment ?? "production",
|
|
3337
|
-
region: defaults.region ?? defaultRegions[defaults.
|
|
3494
|
+
region: isOpenTofuCloud(cloud) ? defaults.region ?? defaultRegions[cloud] : defaults.region ?? null,
|
|
3338
3495
|
apiDomain: defaults.apiDomain,
|
|
3339
3496
|
frontendDomain: defaults.frontendDomain,
|
|
3340
3497
|
haEnabled: defaults.haEnabled ?? false
|
|
3341
3498
|
};
|
|
3342
3499
|
}
|
|
3343
3500
|
const cloud = defaults.cloud ?? parseDeploymentCloud(await unwrap2(await select({
|
|
3344
|
-
message: "Backend
|
|
3501
|
+
message: "Backend host",
|
|
3345
3502
|
options: [
|
|
3346
|
-
{ value: "aws", label: "AWS", hint: "ECS Fargate + RDS PostgreSQL" },
|
|
3347
|
-
{ value: "gcp", label: "Google Cloud", hint: "Cloud Run + Cloud SQL" },
|
|
3348
|
-
{ 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" }
|
|
3349
3506
|
]
|
|
3350
3507
|
})));
|
|
3351
|
-
const
|
|
3508
|
+
const frontendOptions = frontendTargetsByCloud[cloud];
|
|
3509
|
+
const frontendTarget = defaults.frontendTarget ?? (frontendOptions.length === 1 ? frontendOptions[0] ?? "cloudflare" : parseFrontendTarget(await unwrap2(await select({
|
|
3352
3510
|
message: "Frontend host",
|
|
3353
|
-
options:
|
|
3354
|
-
|
|
3355
|
-
{ value: "vercel", label: "Vercel SPA", hint: "Static frontend only" },
|
|
3356
|
-
{
|
|
3357
|
-
value: "cloudflare",
|
|
3358
|
-
label: "Cloudflare static assets",
|
|
3359
|
-
hint: "No API Worker"
|
|
3360
|
-
}
|
|
3361
|
-
]
|
|
3362
|
-
})));
|
|
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
|
+
}))));
|
|
3363
3513
|
const storageProvider = defaults.storageProvider ?? (storageProvidersByCloud[cloud].length === 1 ? storageProvidersByCloud[cloud][0] ?? defaultStorageProviders[cloud] : parseStorageProvider(await unwrap2(await select({
|
|
3364
3514
|
message: "Object storage",
|
|
3365
3515
|
options: storageProvidersByCloud[cloud].map((provider) => ({
|
|
@@ -3374,8 +3524,8 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3374
3524
|
defaultValue: "production",
|
|
3375
3525
|
validate: (value) => validateDeploymentEnvironment(value || "production")
|
|
3376
3526
|
}));
|
|
3377
|
-
const region = defaults.region ?? await unwrap2(await text2({
|
|
3378
|
-
message: `${cloud
|
|
3527
|
+
const region = !isOpenTofuCloud(cloud) ? defaults.region ?? null : defaults.region ?? await unwrap2(await text2({
|
|
3528
|
+
message: `${cloudLabels[cloud]} region`,
|
|
3379
3529
|
placeholder: defaultRegions[cloud],
|
|
3380
3530
|
defaultValue: defaultRegions[cloud],
|
|
3381
3531
|
validate: (value) => validateRegion(value || defaultRegions[cloud])
|
|
@@ -3390,7 +3540,7 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3390
3540
|
placeholder: "app.example.com",
|
|
3391
3541
|
validate: (value) => validateDomain(value ?? "")
|
|
3392
3542
|
}));
|
|
3393
|
-
const haEnabled = defaults.haEnabled ?? await unwrap2(await confirm2({
|
|
3543
|
+
const haEnabled = !isOpenTofuCloud(cloud) ? defaults.haEnabled ?? false : defaults.haEnabled ?? await unwrap2(await confirm2({
|
|
3394
3544
|
message: "Enable the higher-availability profile?",
|
|
3395
3545
|
initialValue: false
|
|
3396
3546
|
}));
|
|
@@ -3409,14 +3559,31 @@ async function collectDeploymentSelection2(defaults = {}, nonInteractive = false
|
|
|
3409
3559
|
// src/deployment.ts
|
|
3410
3560
|
import { mkdir as mkdir3, readFile as readFile4, rm as rm2, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
3411
3561
|
import { join as join5 } from "node:path";
|
|
3412
|
-
var
|
|
3413
|
-
var
|
|
3562
|
+
var DEPLOYMENT_ENVIRONMENT_RE2 = /^[a-z][a-z0-9-]{1,30}$/;
|
|
3563
|
+
var REGION_RE2 = /^[a-z0-9][a-z0-9-]{1,31}$/;
|
|
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])?$/;
|
|
3565
|
+
var deploymentClouds2 = ["aws", "gcp", "railway"];
|
|
3566
|
+
var openTofuClouds2 = ["aws", "gcp"];
|
|
3567
|
+
var frontendTargets2 = ["native", "cloudflare"];
|
|
3414
3568
|
var storageProviders2 = ["r2", "s3", "gcs"];
|
|
3415
3569
|
var storageProvidersByCloud2 = {
|
|
3416
3570
|
aws: ["s3", "r2"],
|
|
3417
3571
|
gcp: ["gcs", "r2"],
|
|
3418
|
-
|
|
3572
|
+
railway: ["r2"]
|
|
3573
|
+
};
|
|
3574
|
+
var frontendTargetsByCloud2 = {
|
|
3575
|
+
aws: ["native", "cloudflare"],
|
|
3576
|
+
gcp: ["native", "cloudflare"],
|
|
3577
|
+
railway: ["cloudflare"]
|
|
3419
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
|
+
}
|
|
3420
3587
|
function isDeploymentCloud2(value) {
|
|
3421
3588
|
return deploymentClouds2.some((cloud) => cloud === value);
|
|
3422
3589
|
}
|
|
@@ -3428,12 +3595,12 @@ function isStorageProvider2(value) {
|
|
|
3428
3595
|
}
|
|
3429
3596
|
function parseDeploymentCloud2(value) {
|
|
3430
3597
|
if (!isDeploymentCloud2(value))
|
|
3431
|
-
throw new Error("Cloud must be aws, gcp, or
|
|
3598
|
+
throw new Error("Cloud must be aws, gcp, or railway.");
|
|
3432
3599
|
return value;
|
|
3433
3600
|
}
|
|
3434
3601
|
function parseFrontendTarget2(value) {
|
|
3435
3602
|
if (!isFrontendTarget2(value)) {
|
|
3436
|
-
throw new Error("Frontend target must be native
|
|
3603
|
+
throw new Error("Frontend target must be native or cloudflare.");
|
|
3437
3604
|
}
|
|
3438
3605
|
return value;
|
|
3439
3606
|
}
|
|
@@ -3443,19 +3610,19 @@ function parseStorageProvider2(value) {
|
|
|
3443
3610
|
return value;
|
|
3444
3611
|
}
|
|
3445
3612
|
function validateDeploymentEnvironment2(value) {
|
|
3446
|
-
if (
|
|
3613
|
+
if (!DEPLOYMENT_ENVIRONMENT_RE2.test(value)) {
|
|
3447
3614
|
return "Environment must use 2-31 lowercase letters, numbers, or hyphens.";
|
|
3448
3615
|
}
|
|
3449
3616
|
return;
|
|
3450
3617
|
}
|
|
3451
3618
|
function validateRegion2(value) {
|
|
3452
|
-
if (
|
|
3619
|
+
if (!REGION_RE2.test(value)) {
|
|
3453
3620
|
return "Region must use 2-32 lowercase letters, numbers, or hyphens.";
|
|
3454
3621
|
}
|
|
3455
3622
|
return;
|
|
3456
3623
|
}
|
|
3457
3624
|
function validateDomain2(value) {
|
|
3458
|
-
if (value.length > 80 ||
|
|
3625
|
+
if (value.length > 80 || !DOMAIN_RE2.test(value)) {
|
|
3459
3626
|
return "Enter a lowercase hostname such as api.example.com, without a protocol or path.";
|
|
3460
3627
|
}
|
|
3461
3628
|
return;
|
|
@@ -3464,12 +3631,24 @@ function validateDeploymentSelection2(selection) {
|
|
|
3464
3631
|
if (!storageProvidersByCloud2[selection.cloud].includes(selection.storageProvider)) {
|
|
3465
3632
|
throw new Error(`${selection.storageProvider} storage is not supported for ${selection.cloud} deployments.`);
|
|
3466
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
|
+
}
|
|
3467
3649
|
const environmentError = validateDeploymentEnvironment2(selection.environment);
|
|
3468
3650
|
if (environmentError)
|
|
3469
3651
|
throw new Error(environmentError);
|
|
3470
|
-
const regionError = validateRegion2(selection.region);
|
|
3471
|
-
if (regionError)
|
|
3472
|
-
throw new Error(regionError);
|
|
3473
3652
|
const apiError = validateDomain2(selection.apiDomain);
|
|
3474
3653
|
if (apiError)
|
|
3475
3654
|
throw new Error(`API domain: ${apiError}`);
|
|
@@ -3501,20 +3680,86 @@ function createDeploymentConfig2(projectName, selection) {
|
|
|
3501
3680
|
ha_enabled: selection.haEnabled
|
|
3502
3681
|
};
|
|
3503
3682
|
}
|
|
3504
|
-
function
|
|
3505
|
-
|
|
3506
|
-
|
|
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";
|
|
3507
3757
|
const storageSetup = config.storage_provider === "r2" ? `Create private staging and public Cloudflare R2 buckets, then store the required storage
|
|
3508
3758
|
variables in the runtime secret container.` : `${config.storage_provider.toUpperCase()} application buckets, lifecycle policy, CORS, public access, and workload permissions are
|
|
3509
3759
|
managed by the selected OpenTofu root.`;
|
|
3510
3760
|
return `# Deployment setup
|
|
3511
3761
|
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
- backend: **${config.cloud.toUpperCase()}**;
|
|
3515
|
-
- storage: **${config.storage_provider}**;
|
|
3516
|
-
- frontend: **${config.frontend_target}**;
|
|
3517
|
-
- environment: **${config.environment}**.
|
|
3762
|
+
${lockSummary2(config)}
|
|
3518
3763
|
|
|
3519
3764
|
## Protected GitHub environment
|
|
3520
3765
|
|
|
@@ -3524,8 +3769,8 @@ Create a GitHub environment named \`${config.environment}\` and configure:
|
|
|
3524
3769
|
- a \`BACKEND_HCL\` secret containing the selected remote-state backend configuration;
|
|
3525
3770
|
- a \`TFVARS_JSON\` secret containing only:
|
|
3526
3771
|
- ${providerInputs};
|
|
3527
|
-
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a
|
|
3528
|
-
|
|
3772
|
+
- \`VITE_TERMS_URL\` and \`VITE_PRIVACY_URL\`, plus \`VITE_MARKETING_URL\` when the product has a public
|
|
3773
|
+
marketing site.
|
|
3529
3774
|
|
|
3530
3775
|
## Cloud preparation
|
|
3531
3776
|
|
|
@@ -3546,9 +3791,33 @@ The workflow creates the registry first, pushes an immutable image, provisions t
|
|
|
3546
3791
|
traffic disabled, runs migrations, and admits traffic only after migration succeeds.
|
|
3547
3792
|
|
|
3548
3793
|
The provider lock is deliberate. Restore the deployment directories from a fresh licensed release
|
|
3549
|
-
before changing
|
|
3794
|
+
before changing providers.
|
|
3550
3795
|
`;
|
|
3551
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
|
+
}
|
|
3552
3821
|
async function removeIfPresent2(target) {
|
|
3553
3822
|
if (!await stat3(target).catch(() => {
|
|
3554
3823
|
return;
|
|
@@ -3557,6 +3826,20 @@ async function removeIfPresent2(target) {
|
|
|
3557
3826
|
await rm2(target, { recursive: true, force: true });
|
|
3558
3827
|
return true;
|
|
3559
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
|
+
}
|
|
3560
3843
|
async function applyDeploymentSelection2(repositoryRoot, projectName, selection) {
|
|
3561
3844
|
const config = createDeploymentConfig2(projectName, selection);
|
|
3562
3845
|
const configPath = join5(repositoryRoot, "deployment/config.json");
|
|
@@ -3570,40 +3853,12 @@ async function applyDeploymentSelection2(repositoryRoot, projectName, selection)
|
|
|
3570
3853
|
`);
|
|
3571
3854
|
await writeFile3(join5(repositoryRoot, "deployment/README.md"), deploymentReadme2(config));
|
|
3572
3855
|
const removed = [];
|
|
3573
|
-
for (const
|
|
3574
|
-
if (
|
|
3575
|
-
|
|
3576
|
-
for (const relativePath of [
|
|
3577
|
-
`infra/bootstrap/${cloud}`,
|
|
3578
|
-
`infra/modules/${cloud}`,
|
|
3579
|
-
`infra/roots/${cloud}`,
|
|
3580
|
-
`scripts/deploy/release-${cloud}.sh`,
|
|
3581
|
-
`scripts/deploy/frontend-${cloud}.sh`
|
|
3582
|
-
]) {
|
|
3583
|
-
if (await removeIfPresent2(join5(repositoryRoot, relativePath)))
|
|
3584
|
-
removed.push(relativePath);
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
const adapters = [
|
|
3588
|
-
{ target: "vercel", path: "apps/web/vercel.json" },
|
|
3589
|
-
{ target: "cloudflare", path: "apps/web/wrangler.toml" },
|
|
3590
|
-
{
|
|
3591
|
-
target: "azure-native",
|
|
3592
|
-
path: "apps/web/public/staticwebapp.config.json"
|
|
3593
|
-
}
|
|
3594
|
-
];
|
|
3595
|
-
const selectedAdapter = selection.frontendTarget === "native" ? selection.cloud === "azure" ? "azure-native" : undefined : selection.frontendTarget;
|
|
3596
|
-
for (const adapter of adapters) {
|
|
3597
|
-
if (adapter.target === selectedAdapter)
|
|
3598
|
-
continue;
|
|
3599
|
-
if (await removeIfPresent2(join5(repositoryRoot, adapter.path)))
|
|
3600
|
-
removed.push(adapter.path);
|
|
3601
|
-
}
|
|
3602
|
-
if (selection.frontendTarget !== "native") {
|
|
3603
|
-
const nativeScript = `scripts/deploy/frontend-${selection.cloud}.sh`;
|
|
3604
|
-
if (await removeIfPresent2(join5(repositoryRoot, nativeScript)))
|
|
3605
|
-
removed.push(nativeScript);
|
|
3856
|
+
for (const relativePath of unusedDeploymentPaths2(selection)) {
|
|
3857
|
+
if (await removeIfPresent2(join5(repositoryRoot, relativePath)))
|
|
3858
|
+
removed.push(relativePath);
|
|
3606
3859
|
}
|
|
3860
|
+
if (removed.includes("infra"))
|
|
3861
|
+
await removeInfrastructureScript2(repositoryRoot);
|
|
3607
3862
|
return removed;
|
|
3608
3863
|
}
|
|
3609
3864
|
|
|
@@ -3632,12 +3887,13 @@ import { rm as rm3 } from "node:fs/promises";
|
|
|
3632
3887
|
import { join as join6 } from "node:path";
|
|
3633
3888
|
import { Readable, Transform } from "node:stream";
|
|
3634
3889
|
import { pipeline } from "node:stream/promises";
|
|
3890
|
+
var TRAILING_SLASHES_RE = /\/+$/;
|
|
3635
3891
|
var RELEASE_TAG_PATTERN2 = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/;
|
|
3636
3892
|
var INDEX_TIMEOUT_MS = 15000;
|
|
3637
3893
|
var DOWNLOAD_TIMEOUT_MS = 300000;
|
|
3638
3894
|
function deliveryBaseUrl(environment = process.env) {
|
|
3639
3895
|
const configured = environment[DELIVERY_URL_VARIABLE]?.trim();
|
|
3640
|
-
return (configured || DELIVERY_BASE_URL).replace(
|
|
3896
|
+
return (configured || DELIVERY_BASE_URL).replace(TRAILING_SLASHES_RE, "");
|
|
3641
3897
|
}
|
|
3642
3898
|
function entitledEditions(index) {
|
|
3643
3899
|
if (!Array.isArray(index.editions))
|
|
@@ -3756,6 +4012,7 @@ async function downloadAsset(licenseKey, releaseTag, assetName, directory, baseU
|
|
|
3756
4012
|
}
|
|
3757
4013
|
|
|
3758
4014
|
// src/scaffold.ts
|
|
4015
|
+
var SHA256_CHECKSUM_LINE_RE = /^([a-f0-9]{64})\s+\*?([^\s]+)$/i;
|
|
3759
4016
|
var execFileAsync = promisify(execFile);
|
|
3760
4017
|
async function assertEmptyTarget(targetDirectory) {
|
|
3761
4018
|
try {
|
|
@@ -3825,7 +4082,7 @@ async function assertNoSymbolicLinks(directory, root = directory, isIncluded) {
|
|
|
3825
4082
|
}
|
|
3826
4083
|
}
|
|
3827
4084
|
function verifyReleaseMetadata(checksum, manifest, artifactName, digest, templateRef) {
|
|
3828
|
-
const checksumMatch = checksum.trim().match(
|
|
4085
|
+
const checksumMatch = checksum.trim().match(SHA256_CHECKSUM_LINE_RE);
|
|
3829
4086
|
if (!checksumMatch || checksumMatch[2] !== artifactName) {
|
|
3830
4087
|
throw new Error(`${CLI_NAME2} release checksum metadata is malformed.`);
|
|
3831
4088
|
}
|
|
@@ -4041,6 +4298,9 @@ async function assembleEditions(target, selection, { keepStartOverlay = false }
|
|
|
4041
4298
|
async function applyStartIdentity(target, appName) {
|
|
4042
4299
|
await rewriteIfPresent(join7(target, "apps/start/wrangler.jsonc"), (content) => rewriteStartWorkerIdentifiers(content, appName));
|
|
4043
4300
|
}
|
|
4301
|
+
async function renameSiteWorker(target, appName) {
|
|
4302
|
+
await rewriteIfPresent(join7(target, "apps/site/wrangler.jsonc"), (content) => rewriteSiteWorkerName(content, appName));
|
|
4303
|
+
}
|
|
4044
4304
|
async function renameFrontendWorker(target, appName) {
|
|
4045
4305
|
const workerPath = join7(target, "apps/web/wrangler.toml");
|
|
4046
4306
|
if (!await stat4(workerPath).catch(() => {
|
|
@@ -4152,6 +4412,7 @@ async function scaffold(options) {
|
|
|
4152
4412
|
const packagePath = join7(target, "package.json");
|
|
4153
4413
|
const packageReplacements = await rewriteFile(packagePath, (content) => rewriteRootPackageName(content, options.appName));
|
|
4154
4414
|
await renameFrontendWorker(target, options.appName);
|
|
4415
|
+
await renameSiteWorker(target, options.appName);
|
|
4155
4416
|
const scope = await applyScopeRewrite(target, options.scope);
|
|
4156
4417
|
if (selection.web) {
|
|
4157
4418
|
await neutralizeAgentInstructions(target);
|
|
@@ -4185,6 +4446,10 @@ async function scaffold(options) {
|
|
|
4185
4446
|
}
|
|
4186
4447
|
|
|
4187
4448
|
// src/cli.ts
|
|
4449
|
+
var COMBINING_MARK_RE = /\p{Mark}/gu;
|
|
4450
|
+
var NON_ALPHANUMERIC_RE = /[^a-z0-9]+/g;
|
|
4451
|
+
var OUTER_DASHES_RE = /^-+|-+$/g;
|
|
4452
|
+
|
|
4188
4453
|
class CliCancelled extends Error {
|
|
4189
4454
|
}
|
|
4190
4455
|
function usage() {
|
|
@@ -4215,15 +4480,15 @@ Options:
|
|
|
4215
4480
|
--no-install Skip dependency installation
|
|
4216
4481
|
--start Start Postgres and development servers after creation
|
|
4217
4482
|
--no-start Create the application without starting it
|
|
4218
|
-
--cloud <provider> Configure AWS, GCP, or
|
|
4483
|
+
--cloud <provider> Configure AWS, GCP, or Railway deployment during setup
|
|
4219
4484
|
--storage <provider> Select r2, s3, or gcs object storage
|
|
4220
|
-
--no-deploy Keep every
|
|
4221
|
-
--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)
|
|
4222
4487
|
--deploy-environment <name> Deployment environment (default: production)
|
|
4223
|
-
--region <name>
|
|
4488
|
+
--region <name> AWS or GCP region
|
|
4224
4489
|
--api-domain <host> Stable production API hostname
|
|
4225
4490
|
--frontend-domain <host> Stable production frontend hostname
|
|
4226
|
-
--ha / --no-ha Enable or disable the higher-availability profile
|
|
4491
|
+
--ha / --no-ha Enable or disable the AWS or GCP higher-availability profile
|
|
4227
4492
|
-v, --version Print the CLI version
|
|
4228
4493
|
-h, --help Show this help
|
|
4229
4494
|
|
|
@@ -4231,7 +4496,8 @@ Agent tooling:
|
|
|
4231
4496
|
${agentSetupUsage(CLI_NAME)}
|
|
4232
4497
|
|
|
4233
4498
|
Deployment:
|
|
4234
|
-
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.`;
|
|
4235
4501
|
}
|
|
4236
4502
|
function takeValue3(args, index, flag) {
|
|
4237
4503
|
const value = args[index + 1];
|
|
@@ -4334,7 +4600,7 @@ function suggestBrandName(appName) {
|
|
|
4334
4600
|
return appName.split("-").map((segment) => productAcronyms[segment] ?? `${segment.charAt(0).toUpperCase()}${segment.slice(1)}`).join(" ");
|
|
4335
4601
|
}
|
|
4336
4602
|
function suggestScope(brandName, fallbackAppName) {
|
|
4337
|
-
const namespace = brandName.normalize("NFKD").replace(
|
|
4603
|
+
const namespace = brandName.normalize("NFKD").replace(COMBINING_MARK_RE, "").toLowerCase().replace(NON_ALPHANUMERIC_RE, "-").replace(OUTER_DASHES_RE, "");
|
|
4338
4604
|
return namespace || fallbackAppName;
|
|
4339
4605
|
}
|
|
4340
4606
|
async function unwrapPrompt(value) {
|
|
@@ -4700,7 +4966,7 @@ ${completion.database}`);
|
|
|
4700
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.");
|
|
4701
4967
|
}
|
|
4702
4968
|
if (options.deployment) {
|
|
4703
|
-
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.`);
|
|
4704
4970
|
} else {
|
|
4705
4971
|
log.info("Deployment deferred. Run `pnpm deploy:configure` when you are ready.");
|
|
4706
4972
|
}
|