@revealui/setup 0.3.4 → 0.3.6
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/README.md +4 -4
- package/dist/bootstrap/index.d.ts +66 -0
- package/dist/bootstrap/index.js +147 -0
- package/dist/environment/index.js +8 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +506 -4
- package/dist/system-tune/index.d.ts +152 -0
- package/dist/system-tune/index.js +353 -0
- package/dist/utils/index.js +0 -1
- package/dist/validators/index.js +0 -1
- package/package.json +20 -8
- package/dist/environment/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/validators/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,148 @@
|
|
|
1
|
+
// src/bootstrap/index.ts
|
|
2
|
+
function richTextDoc(...nodes) {
|
|
3
|
+
return {
|
|
4
|
+
root: {
|
|
5
|
+
type: "root",
|
|
6
|
+
children: nodes,
|
|
7
|
+
direction: "ltr",
|
|
8
|
+
format: "",
|
|
9
|
+
indent: 0,
|
|
10
|
+
version: 1
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function heading(text, tag = "h2") {
|
|
15
|
+
return {
|
|
16
|
+
type: "heading",
|
|
17
|
+
children: [{ type: "text", detail: 0, format: 0, mode: "normal", style: "", text, version: 1 }],
|
|
18
|
+
direction: "ltr",
|
|
19
|
+
format: "",
|
|
20
|
+
indent: 0,
|
|
21
|
+
tag,
|
|
22
|
+
version: 1
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function paragraph(text) {
|
|
26
|
+
return {
|
|
27
|
+
type: "paragraph",
|
|
28
|
+
children: [{ type: "text", detail: 0, format: 0, mode: "normal", style: "", text, version: 1 }],
|
|
29
|
+
direction: "ltr",
|
|
30
|
+
format: "",
|
|
31
|
+
indent: 0,
|
|
32
|
+
textFormat: 0,
|
|
33
|
+
textStyle: "",
|
|
34
|
+
version: 1
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
var SEED_PAGES = [
|
|
38
|
+
{
|
|
39
|
+
title: "Home",
|
|
40
|
+
slug: "home",
|
|
41
|
+
path: "/",
|
|
42
|
+
layout: [
|
|
43
|
+
{
|
|
44
|
+
blockType: "content",
|
|
45
|
+
columns: [
|
|
46
|
+
{
|
|
47
|
+
size: "full",
|
|
48
|
+
richText: richTextDoc(
|
|
49
|
+
heading("Welcome to RevealUI"),
|
|
50
|
+
paragraph(
|
|
51
|
+
"Your agentic business runtime is ready. Visit /admin to manage content, configure collections, and build your application."
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
];
|
|
60
|
+
async function bootstrap(options) {
|
|
61
|
+
const { revealui, admin, seed = true } = options;
|
|
62
|
+
try {
|
|
63
|
+
const existing = await revealui.find({
|
|
64
|
+
collection: "users",
|
|
65
|
+
limit: 1,
|
|
66
|
+
depth: 0
|
|
67
|
+
});
|
|
68
|
+
if (existing.totalDocs > 0) {
|
|
69
|
+
return {
|
|
70
|
+
status: "locked",
|
|
71
|
+
message: "Setup already completed. Users exist."
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
return {
|
|
76
|
+
status: "error",
|
|
77
|
+
message: "Database connection failed. Check POSTGRES_URL or DATABASE_URL.",
|
|
78
|
+
error: err instanceof Error ? err.message : String(err)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (!(admin.email && admin.password)) {
|
|
82
|
+
return {
|
|
83
|
+
status: "error",
|
|
84
|
+
message: "Admin email and password are required."
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (admin.password.length < 12) {
|
|
88
|
+
return {
|
|
89
|
+
status: "error",
|
|
90
|
+
message: "Admin password must be at least 12 characters."
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await revealui.create({
|
|
95
|
+
collection: "users",
|
|
96
|
+
data: {
|
|
97
|
+
name: admin.name ?? "Admin",
|
|
98
|
+
email: admin.email,
|
|
99
|
+
password: admin.password,
|
|
100
|
+
role: "owner",
|
|
101
|
+
roles: ["super-admin"]
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const pgCode = err.code;
|
|
106
|
+
if (pgCode === "23505") {
|
|
107
|
+
return {
|
|
108
|
+
status: "error",
|
|
109
|
+
message: "A user with that email already exists."
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
status: "error",
|
|
114
|
+
message: "Failed to create admin user.",
|
|
115
|
+
error: err instanceof Error ? err.message : String(err)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
let seeded = false;
|
|
119
|
+
if (seed) {
|
|
120
|
+
try {
|
|
121
|
+
for (const page of SEED_PAGES) {
|
|
122
|
+
const existing = await revealui.find({
|
|
123
|
+
collection: "pages",
|
|
124
|
+
where: { slug: { equals: page.slug } },
|
|
125
|
+
limit: 1
|
|
126
|
+
});
|
|
127
|
+
if (existing.docs.length === 0) {
|
|
128
|
+
await revealui.create({
|
|
129
|
+
collection: "pages",
|
|
130
|
+
data: page
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
seeded = true;
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
status: "created",
|
|
140
|
+
message: `Admin user created${seeded ? " and content seeded" : ""}. Sign in at /admin.`,
|
|
141
|
+
user: { email: admin.email, role: "owner" },
|
|
142
|
+
seeded
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
1
146
|
// src/environment/generators.ts
|
|
2
147
|
import { randomBytes } from "crypto";
|
|
3
148
|
function generateSecret(length = 32) {
|
|
@@ -5,10 +150,15 @@ function generateSecret(length = 32) {
|
|
|
5
150
|
}
|
|
6
151
|
function generatePassword(length = 16) {
|
|
7
152
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
|
|
153
|
+
const maxValid = 256 - 256 % chars.length;
|
|
8
154
|
let password = "";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
155
|
+
while (password.length < length) {
|
|
156
|
+
const buf = randomBytes(length - password.length + 16);
|
|
157
|
+
for (let i = 0; i < buf.length && password.length < length; i++) {
|
|
158
|
+
if (buf[i] < maxValid) {
|
|
159
|
+
password += chars[buf[i] % chars.length];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
12
162
|
}
|
|
13
163
|
return password;
|
|
14
164
|
}
|
|
@@ -456,18 +606,370 @@ async function generateSecrets(envPath, logger2) {
|
|
|
456
606
|
await writeFile(envPath, content);
|
|
457
607
|
logger2.success("Generated REVEALUI_SECRET");
|
|
458
608
|
}
|
|
609
|
+
|
|
610
|
+
// src/system-tune/detect.ts
|
|
611
|
+
import { execSync } from "child_process";
|
|
612
|
+
import { existsSync, readFileSync } from "fs";
|
|
613
|
+
import { arch, cpus, freemem, homedir, platform, release, totalmem } from "os";
|
|
614
|
+
import { join as join2 } from "path";
|
|
615
|
+
function detectPlatformClass() {
|
|
616
|
+
const p = platform();
|
|
617
|
+
if (p === "linux") {
|
|
618
|
+
try {
|
|
619
|
+
const procVersion = readFileSync("/proc/version", "utf8").toLowerCase();
|
|
620
|
+
if (procVersion.includes("microsoft") || procVersion.includes("wsl")) {
|
|
621
|
+
return "wsl2";
|
|
622
|
+
}
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
if (existsSync("/.dockerenv")) return "docker";
|
|
627
|
+
const cgroup = readFileSync("/proc/1/cgroup", "utf8");
|
|
628
|
+
if (cgroup.includes("docker") || cgroup.includes("containerd")) return "docker";
|
|
629
|
+
} catch {
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
const dmiProduct = readFileSync("/sys/class/dmi/id/product_name", "utf8").trim().toLowerCase();
|
|
633
|
+
if (dmiProduct.includes("virtual") || dmiProduct.includes("kvm") || dmiProduct.includes("xen") || dmiProduct.includes("hvm")) {
|
|
634
|
+
return "cloud-vm";
|
|
635
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
return "linux";
|
|
639
|
+
}
|
|
640
|
+
if (p === "darwin") return "macos";
|
|
641
|
+
if (p === "win32") return "windows";
|
|
642
|
+
return "unknown";
|
|
643
|
+
}
|
|
644
|
+
function detectDistro() {
|
|
645
|
+
try {
|
|
646
|
+
const osRelease = readFileSync("/etc/os-release", "utf8");
|
|
647
|
+
const match = osRelease.match(/^PRETTY_NAME="?([^"\n]+)"?/m);
|
|
648
|
+
return match?.[1];
|
|
649
|
+
} catch {
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function detectMemory() {
|
|
654
|
+
const p = platform();
|
|
655
|
+
if (p === "linux") {
|
|
656
|
+
try {
|
|
657
|
+
const meminfo = readFileSync("/proc/meminfo", "utf8");
|
|
658
|
+
const parse = (key) => {
|
|
659
|
+
const match = meminfo.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
|
|
660
|
+
return match ? Number.parseInt(match[1], 10) * 1024 : 0;
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
totalBytes: parse("MemTotal"),
|
|
664
|
+
freeBytes: parse("MemAvailable") || parse("MemFree"),
|
|
665
|
+
swapTotalBytes: parse("SwapTotal"),
|
|
666
|
+
swapFreeBytes: parse("SwapFree")
|
|
667
|
+
};
|
|
668
|
+
} catch {
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
totalBytes: totalmem(),
|
|
673
|
+
freeBytes: freemem(),
|
|
674
|
+
swapTotalBytes: 0,
|
|
675
|
+
swapFreeBytes: 0
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function detectCpu() {
|
|
679
|
+
const cores = cpus();
|
|
680
|
+
const model = cores[0]?.model ?? "unknown";
|
|
681
|
+
const logicalCores = cores.length;
|
|
682
|
+
let physicalCores = logicalCores;
|
|
683
|
+
const p = platform();
|
|
684
|
+
if (p === "linux") {
|
|
685
|
+
try {
|
|
686
|
+
const output = execSync('grep "^cpu cores" /proc/cpuinfo | head -1', {
|
|
687
|
+
encoding: "utf8",
|
|
688
|
+
timeout: 2e3
|
|
689
|
+
});
|
|
690
|
+
const match = output.match(/:\s*(\d+)/);
|
|
691
|
+
if (match) physicalCores = Number.parseInt(match[1], 10);
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
} else if (p === "darwin") {
|
|
695
|
+
try {
|
|
696
|
+
const output = execSync("sysctl -n hw.physicalcpu", { encoding: "utf8", timeout: 2e3 });
|
|
697
|
+
physicalCores = Number.parseInt(output.trim(), 10) || logicalCores;
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return { model, physicalCores, logicalCores };
|
|
702
|
+
}
|
|
703
|
+
function detectDisk() {
|
|
704
|
+
const p = platform();
|
|
705
|
+
const target = process.cwd();
|
|
706
|
+
if (p === "linux" || p === "darwin") {
|
|
707
|
+
try {
|
|
708
|
+
const output = execSync(`df -B1 "${target}" | tail -1`, { encoding: "utf8", timeout: 2e3 });
|
|
709
|
+
const parts = output.trim().split(/\s+/);
|
|
710
|
+
if (parts.length >= 4) {
|
|
711
|
+
return {
|
|
712
|
+
totalBytes: Number.parseInt(parts[1], 10) || 0,
|
|
713
|
+
freeBytes: Number.parseInt(parts[3], 10) || 0
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return { totalBytes: 0, freeBytes: 0 };
|
|
720
|
+
}
|
|
721
|
+
function detectExistingConfigs() {
|
|
722
|
+
const home = homedir();
|
|
723
|
+
let wslconfig = false;
|
|
724
|
+
try {
|
|
725
|
+
const windowsUser = execSync('cmd.exe /c "echo %USERPROFILE%" 2>/dev/null', {
|
|
726
|
+
encoding: "utf8",
|
|
727
|
+
timeout: 3e3
|
|
728
|
+
}).trim();
|
|
729
|
+
const wslPath = windowsUser.replace(/\\/g, "/").replace(/^([A-Z]):/i, (_, d) => `/mnt/${d.toLowerCase()}`);
|
|
730
|
+
wslconfig = existsSync(join2(wslPath, ".wslconfig"));
|
|
731
|
+
} catch {
|
|
732
|
+
wslconfig = existsSync(join2(home, ".wslconfig"));
|
|
733
|
+
}
|
|
734
|
+
let earlyoom = false;
|
|
735
|
+
try {
|
|
736
|
+
const result = execSync("systemctl is-enabled earlyoom 2>/dev/null", {
|
|
737
|
+
encoding: "utf8",
|
|
738
|
+
timeout: 2e3
|
|
739
|
+
});
|
|
740
|
+
earlyoom = result.trim() === "enabled";
|
|
741
|
+
} catch {
|
|
742
|
+
earlyoom = false;
|
|
743
|
+
}
|
|
744
|
+
let dockerAutostart = false;
|
|
745
|
+
try {
|
|
746
|
+
const result = execSync("systemctl is-enabled docker 2>/dev/null", {
|
|
747
|
+
encoding: "utf8",
|
|
748
|
+
timeout: 2e3
|
|
749
|
+
});
|
|
750
|
+
dockerAutostart = result.trim() === "enabled";
|
|
751
|
+
} catch {
|
|
752
|
+
dockerAutostart = false;
|
|
753
|
+
}
|
|
754
|
+
const nodeOptions = process.env.NODE_OPTIONS ?? null;
|
|
755
|
+
return { wslconfig, earlyoom, dockerAutostart, nodeOptions };
|
|
756
|
+
}
|
|
757
|
+
function detectSystem() {
|
|
758
|
+
return {
|
|
759
|
+
os: {
|
|
760
|
+
platform: platform(),
|
|
761
|
+
release: release(),
|
|
762
|
+
arch: arch(),
|
|
763
|
+
distro: detectDistro()
|
|
764
|
+
},
|
|
765
|
+
platformClass: detectPlatformClass(),
|
|
766
|
+
memory: detectMemory(),
|
|
767
|
+
cpu: detectCpu(),
|
|
768
|
+
disk: detectDisk(),
|
|
769
|
+
existingConfigs: detectExistingConfigs()
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/system-tune/profiles/wsl-low-ram.json
|
|
774
|
+
var wsl_low_ram_default = {
|
|
775
|
+
$schema: "../profile-schema.json",
|
|
776
|
+
id: "wsl-low-ram",
|
|
777
|
+
name: "WSL2 Low-RAM Host",
|
|
778
|
+
description: "Safe defaults for WSL2 on hosts with \u22648 GB RAM. Prevents the VM from starving Windows by capping memory, disabling aggressive reclaim, and keeping Docker off by default.",
|
|
779
|
+
version: "0.1.0",
|
|
780
|
+
origin: "2026-04-13 crash postmortem: WSL on 7.3 GB host claimed ~6 GB, starving Windows. Hand-authored fix captured as seed profile.",
|
|
781
|
+
match: {
|
|
782
|
+
platform: "wsl2",
|
|
783
|
+
maxHostRamGb: 8
|
|
784
|
+
},
|
|
785
|
+
tune: {
|
|
786
|
+
wslconfig: {
|
|
787
|
+
memory: "4GB",
|
|
788
|
+
processors: 2,
|
|
789
|
+
swap: "2GB",
|
|
790
|
+
vmIdleTimeout: -1,
|
|
791
|
+
autoMemoryReclaim: "disabled",
|
|
792
|
+
networkingMode: "mirrored"
|
|
793
|
+
},
|
|
794
|
+
node: {
|
|
795
|
+
maxOldSpaceSize: 2048
|
|
796
|
+
},
|
|
797
|
+
pnpm: {
|
|
798
|
+
childConcurrency: 2
|
|
799
|
+
},
|
|
800
|
+
turbo: {
|
|
801
|
+
concurrency: 2
|
|
802
|
+
},
|
|
803
|
+
vitest: {
|
|
804
|
+
maxThreads: 2
|
|
805
|
+
},
|
|
806
|
+
earlyoom: {
|
|
807
|
+
enabled: true,
|
|
808
|
+
memThreshold: 5,
|
|
809
|
+
swapThreshold: 10,
|
|
810
|
+
prefer: "turbo|biome|vitest|tsc|esbuild"
|
|
811
|
+
},
|
|
812
|
+
docker: {
|
|
813
|
+
autostart: false
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
// src/system-tune/profiles.ts
|
|
819
|
+
var PROFILES = [wsl_low_ram_default];
|
|
820
|
+
|
|
821
|
+
// src/system-tune/plan.ts
|
|
822
|
+
function matchProfile(system) {
|
|
823
|
+
const ramGb = system.memory.totalBytes / (1024 * 1024 * 1024);
|
|
824
|
+
for (const profile of PROFILES) {
|
|
825
|
+
if (profile.match.platform !== system.platformClass) continue;
|
|
826
|
+
if (profile.match.maxHostRamGb !== void 0 && ramGb > profile.match.maxHostRamGb) continue;
|
|
827
|
+
if (profile.match.minHostRamGb !== void 0 && ramGb < profile.match.minHostRamGb) continue;
|
|
828
|
+
return profile;
|
|
829
|
+
}
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
function generateWslActions(profile, system) {
|
|
833
|
+
const actions = [];
|
|
834
|
+
const wsl = profile.tune.wslconfig;
|
|
835
|
+
if (!wsl) return actions;
|
|
836
|
+
const entries = [];
|
|
837
|
+
if (wsl.memory) entries.push(["memory", wsl.memory]);
|
|
838
|
+
if (wsl.processors !== void 0) entries.push(["processors", String(wsl.processors)]);
|
|
839
|
+
if (wsl.swap) entries.push(["swap", wsl.swap]);
|
|
840
|
+
if (wsl.vmIdleTimeout !== void 0) entries.push(["vmIdleTimeout", String(wsl.vmIdleTimeout)]);
|
|
841
|
+
if (wsl.autoMemoryReclaim) entries.push(["autoMemoryReclaim", wsl.autoMemoryReclaim]);
|
|
842
|
+
if (wsl.networkingMode) entries.push(["networkingMode", wsl.networkingMode]);
|
|
843
|
+
if (entries.length > 0) {
|
|
844
|
+
const desired = entries.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
845
|
+
actions.push({
|
|
846
|
+
target: ".wslconfig [wsl2]",
|
|
847
|
+
current: system.existingConfigs.wslconfig ? "(exists, values unknown)" : null,
|
|
848
|
+
desired,
|
|
849
|
+
privileged: false,
|
|
850
|
+
description: `Set WSL2 config: ${desired}`
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return actions;
|
|
854
|
+
}
|
|
855
|
+
function generateNodeActions(profile, system) {
|
|
856
|
+
const actions = [];
|
|
857
|
+
const node = profile.tune.node;
|
|
858
|
+
if (!node?.maxOldSpaceSize) return actions;
|
|
859
|
+
const desired = `--max-old-space-size=${node.maxOldSpaceSize}`;
|
|
860
|
+
const current = system.existingConfigs.nodeOptions;
|
|
861
|
+
const alreadySet = current?.includes("max-old-space-size") ?? false;
|
|
862
|
+
if (!alreadySet) {
|
|
863
|
+
actions.push({
|
|
864
|
+
target: "NODE_OPTIONS",
|
|
865
|
+
current,
|
|
866
|
+
desired: current ? `${current} ${desired}` : desired,
|
|
867
|
+
privileged: false,
|
|
868
|
+
description: `Add ${desired} to NODE_OPTIONS in shell profile`
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
return actions;
|
|
872
|
+
}
|
|
873
|
+
function generateEarlyoomActions(profile, system) {
|
|
874
|
+
const actions = [];
|
|
875
|
+
const oom = profile.tune.earlyoom;
|
|
876
|
+
if (!oom?.enabled) return actions;
|
|
877
|
+
if (!system.existingConfigs.earlyoom) {
|
|
878
|
+
const args = [`-m ${oom.memThreshold ?? 5}`, `-s ${oom.swapThreshold ?? 10}`];
|
|
879
|
+
if (oom.prefer) args.push(`--prefer '${oom.prefer}'`);
|
|
880
|
+
actions.push({
|
|
881
|
+
target: "/etc/default/earlyoom",
|
|
882
|
+
current: null,
|
|
883
|
+
desired: `EARLYOOM_ARGS="${args.join(" ")}"`,
|
|
884
|
+
privileged: true,
|
|
885
|
+
description: `Install and configure earlyoom (kill memory hogs before OOM killer)`
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return actions;
|
|
889
|
+
}
|
|
890
|
+
function generateDockerActions(profile, system) {
|
|
891
|
+
const actions = [];
|
|
892
|
+
const docker = profile.tune.docker;
|
|
893
|
+
if (docker === void 0) return actions;
|
|
894
|
+
if (!docker.autostart && system.existingConfigs.dockerAutostart) {
|
|
895
|
+
actions.push({
|
|
896
|
+
target: "systemctl docker",
|
|
897
|
+
current: "enabled (autostart)",
|
|
898
|
+
desired: "disabled (on-demand via sudo systemctl start docker)",
|
|
899
|
+
privileged: true,
|
|
900
|
+
description: "Disable Docker autostart to save ~200-400 MB RAM on boot"
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
return actions;
|
|
904
|
+
}
|
|
905
|
+
function generateConcurrencyActions(profile) {
|
|
906
|
+
const actions = [];
|
|
907
|
+
if (profile.tune.pnpm?.childConcurrency) {
|
|
908
|
+
actions.push({
|
|
909
|
+
target: ".npmrc or pnpm config",
|
|
910
|
+
current: null,
|
|
911
|
+
desired: `child-concurrency=${profile.tune.pnpm.childConcurrency}`,
|
|
912
|
+
privileged: false,
|
|
913
|
+
description: `Limit pnpm child concurrency to ${profile.tune.pnpm.childConcurrency}`
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
if (profile.tune.turbo?.concurrency) {
|
|
917
|
+
actions.push({
|
|
918
|
+
target: "turbo.json or TURBO_CONCURRENCY",
|
|
919
|
+
current: null,
|
|
920
|
+
desired: `concurrency=${profile.tune.turbo.concurrency}`,
|
|
921
|
+
privileged: false,
|
|
922
|
+
description: `Limit Turbo concurrency to ${profile.tune.turbo.concurrency}`
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
if (profile.tune.vitest?.maxThreads) {
|
|
926
|
+
actions.push({
|
|
927
|
+
target: "vitest.config poolOptions.threads.maxThreads",
|
|
928
|
+
current: null,
|
|
929
|
+
desired: `maxThreads=${profile.tune.vitest.maxThreads}`,
|
|
930
|
+
privileged: false,
|
|
931
|
+
description: `Limit Vitest threads to ${profile.tune.vitest.maxThreads}`
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
return actions;
|
|
935
|
+
}
|
|
936
|
+
function generatePlan(system, profile) {
|
|
937
|
+
const actions = [
|
|
938
|
+
...generateWslActions(profile, system),
|
|
939
|
+
...generateNodeActions(profile, system),
|
|
940
|
+
...generateEarlyoomActions(profile, system),
|
|
941
|
+
...generateDockerActions(profile, system),
|
|
942
|
+
...generateConcurrencyActions(profile)
|
|
943
|
+
];
|
|
944
|
+
return {
|
|
945
|
+
profileId: profile.id,
|
|
946
|
+
system,
|
|
947
|
+
actions,
|
|
948
|
+
isNoop: actions.length === 0
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
function generateAutoplan(system) {
|
|
952
|
+
const profile = matchProfile(system);
|
|
953
|
+
if (!profile) return null;
|
|
954
|
+
return generatePlan(system, profile);
|
|
955
|
+
}
|
|
459
956
|
export {
|
|
460
957
|
OPTIONAL_ENV_VARS,
|
|
958
|
+
PROFILES,
|
|
461
959
|
REQUIRED_ENV_VARS,
|
|
960
|
+
bootstrap,
|
|
462
961
|
createLogger,
|
|
962
|
+
detectSystem,
|
|
963
|
+
generateAutoplan,
|
|
463
964
|
generatePassword,
|
|
965
|
+
generatePlan,
|
|
464
966
|
generateSecret,
|
|
465
967
|
handleASTParseError,
|
|
466
968
|
logger,
|
|
969
|
+
matchProfile,
|
|
467
970
|
parseEnvContent,
|
|
468
971
|
setupEnvironment,
|
|
469
972
|
updateEnvValue,
|
|
470
973
|
validateEnv,
|
|
471
974
|
validators
|
|
472
975
|
};
|
|
473
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Tune Types
|
|
3
|
+
*
|
|
4
|
+
* Shared types for hardware detection, tuning profiles, and plan generation.
|
|
5
|
+
*/
|
|
6
|
+
type PlatformClass = 'wsl2' | 'linux' | 'macos' | 'windows' | 'docker' | 'cloud-vm' | 'unknown';
|
|
7
|
+
interface SystemInfo {
|
|
8
|
+
/** OS/distro/kernel identification */
|
|
9
|
+
os: {
|
|
10
|
+
platform: NodeJS.Platform;
|
|
11
|
+
release: string;
|
|
12
|
+
arch: string;
|
|
13
|
+
distro?: string;
|
|
14
|
+
};
|
|
15
|
+
/** Detected platform class */
|
|
16
|
+
platformClass: PlatformClass;
|
|
17
|
+
/** Memory in bytes */
|
|
18
|
+
memory: {
|
|
19
|
+
totalBytes: number;
|
|
20
|
+
freeBytes: number;
|
|
21
|
+
swapTotalBytes: number;
|
|
22
|
+
swapFreeBytes: number;
|
|
23
|
+
};
|
|
24
|
+
/** CPU info */
|
|
25
|
+
cpu: {
|
|
26
|
+
model: string;
|
|
27
|
+
physicalCores: number;
|
|
28
|
+
logicalCores: number;
|
|
29
|
+
};
|
|
30
|
+
/** Disk info for the working directory */
|
|
31
|
+
disk: {
|
|
32
|
+
totalBytes: number;
|
|
33
|
+
freeBytes: number;
|
|
34
|
+
};
|
|
35
|
+
/** Existing configs already present */
|
|
36
|
+
existingConfigs: {
|
|
37
|
+
wslconfig: boolean;
|
|
38
|
+
earlyoom: boolean;
|
|
39
|
+
dockerAutostart: boolean;
|
|
40
|
+
nodeOptions: string | null;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
interface WslConfigTune {
|
|
44
|
+
memory?: string;
|
|
45
|
+
processors?: number;
|
|
46
|
+
swap?: string;
|
|
47
|
+
vmIdleTimeout?: number;
|
|
48
|
+
autoMemoryReclaim?: string;
|
|
49
|
+
networkingMode?: string;
|
|
50
|
+
}
|
|
51
|
+
interface TuneValues {
|
|
52
|
+
wslconfig?: WslConfigTune;
|
|
53
|
+
node?: {
|
|
54
|
+
maxOldSpaceSize?: number;
|
|
55
|
+
};
|
|
56
|
+
pnpm?: {
|
|
57
|
+
childConcurrency?: number;
|
|
58
|
+
};
|
|
59
|
+
turbo?: {
|
|
60
|
+
concurrency?: number;
|
|
61
|
+
};
|
|
62
|
+
vitest?: {
|
|
63
|
+
maxThreads?: number;
|
|
64
|
+
};
|
|
65
|
+
earlyoom?: {
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
memThreshold?: number;
|
|
68
|
+
swapThreshold?: number;
|
|
69
|
+
prefer?: string;
|
|
70
|
+
};
|
|
71
|
+
docker?: {
|
|
72
|
+
autostart?: boolean;
|
|
73
|
+
};
|
|
74
|
+
macos?: {
|
|
75
|
+
maxFiles?: number;
|
|
76
|
+
spotlightExclusions?: string[];
|
|
77
|
+
};
|
|
78
|
+
windows?: {
|
|
79
|
+
defenderExclusions?: string[];
|
|
80
|
+
longPaths?: boolean;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
interface TuneProfile {
|
|
84
|
+
id: string;
|
|
85
|
+
name: string;
|
|
86
|
+
description: string;
|
|
87
|
+
version: string;
|
|
88
|
+
origin?: string;
|
|
89
|
+
match: {
|
|
90
|
+
platform: PlatformClass;
|
|
91
|
+
maxHostRamGb?: number;
|
|
92
|
+
minHostRamGb?: number;
|
|
93
|
+
};
|
|
94
|
+
tune: TuneValues;
|
|
95
|
+
}
|
|
96
|
+
interface PlanAction {
|
|
97
|
+
/** What file or setting is being changed */
|
|
98
|
+
target: string;
|
|
99
|
+
/** What the current value is (null if not set) */
|
|
100
|
+
current: string | null;
|
|
101
|
+
/** What the new value will be */
|
|
102
|
+
desired: string;
|
|
103
|
+
/** Whether this requires elevated privileges */
|
|
104
|
+
privileged: boolean;
|
|
105
|
+
/** Human-readable description */
|
|
106
|
+
description: string;
|
|
107
|
+
}
|
|
108
|
+
interface TunePlan {
|
|
109
|
+
/** Profile that generated this plan */
|
|
110
|
+
profileId: string;
|
|
111
|
+
/** Detected system info */
|
|
112
|
+
system: SystemInfo;
|
|
113
|
+
/** Actions to take */
|
|
114
|
+
actions: PlanAction[];
|
|
115
|
+
/** Whether all actions are no-ops (system already tuned) */
|
|
116
|
+
isNoop: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* System Detection Layer
|
|
121
|
+
*
|
|
122
|
+
* Scans the host's hardware and platform to produce a SystemInfo snapshot.
|
|
123
|
+
* This is the "read" side — no mutations, only observation.
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/** Scan the current host and return a SystemInfo snapshot. */
|
|
127
|
+
declare function detectSystem(): SystemInfo;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Plan Generator
|
|
131
|
+
*
|
|
132
|
+
* Pure function: (detected system state, profile) → plan of actions.
|
|
133
|
+
* No I/O — only computes the diff between current and desired state.
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/** Find the best matching profile for the detected system. */
|
|
137
|
+
declare function matchProfile(system: SystemInfo): TuneProfile | null;
|
|
138
|
+
/** Generate a tuning plan from detected system state and a profile. */
|
|
139
|
+
declare function generatePlan(system: SystemInfo, profile: TuneProfile): TunePlan;
|
|
140
|
+
/** Detect system, find matching profile, and generate plan. */
|
|
141
|
+
declare function generateAutoplan(system: SystemInfo): TunePlan | null;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Built-in Tuning Profiles
|
|
145
|
+
*
|
|
146
|
+
* Profiles are ordered by specificity — the first match wins.
|
|
147
|
+
* Add new profiles here, most specific first.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
declare const PROFILES: TuneProfile[];
|
|
151
|
+
|
|
152
|
+
export { PROFILES, type PlanAction, type PlatformClass, type SystemInfo, type TunePlan, type TuneProfile, type TuneValues, type WslConfigTune, detectSystem, generateAutoplan, generatePlan, matchProfile };
|