unitup 0.0.9 → 0.0.11
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 +401 -181
- package/docs/index.html +544 -0
- package/index.d.ts +140 -153
- package/package.json +18 -4
- package/src/cli.js +259 -55
- package/src/doctor.js +73 -14
- package/src/index.js +16 -1
- package/src/runtimes/bun.js +46 -0
- package/src/runtimes/common.js +73 -0
- package/src/runtimes/deno.js +46 -0
- package/src/runtimes/elixir.js +46 -0
- package/src/runtimes/go.js +46 -0
- package/src/runtimes/index.js +155 -0
- package/src/runtimes/native.js +38 -0
- package/src/runtimes/node.js +57 -0
- package/src/runtimes/php.js +46 -0
- package/src/runtimes/python.js +46 -0
- package/src/runtimes/ruby.js +46 -0
- package/src/runtimes/shell.js +46 -0
- package/src/systemd.js +262 -30
- package/src/unit.js +59 -21
- package/src/utils.js +139 -4
- package/tsconfig.json +13 -0
package/src/utils.js
CHANGED
|
@@ -29,6 +29,101 @@ export function getAppMetadataPath(name) {
|
|
|
29
29
|
return path.join(getAppsDir(), `${safeName}.json`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Validates systemd memory limit sizes (128K, 256M, 1G, 2T, infinity, max, or raw bytes).
|
|
34
|
+
* @param {string|number} val
|
|
35
|
+
* @param {string} paramName
|
|
36
|
+
* @returns {string} Normalized memory limit string
|
|
37
|
+
*/
|
|
38
|
+
export function validateMemorySize(val, paramName = 'Memory limit') {
|
|
39
|
+
if (val === null || val === undefined || val === '') {
|
|
40
|
+
throw new Error(`${paramName} cannot be empty.`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const str = String(val).trim();
|
|
44
|
+
|
|
45
|
+
// Prevent shell injection and unsafe characters
|
|
46
|
+
if (/[;&|$`"'\n\r\t ]/.test(str)) {
|
|
47
|
+
throw new Error(`${paramName} contains invalid characters or shell injection attempt: "${str}".`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Reject negative numbers
|
|
51
|
+
if (str.startsWith('-')) {
|
|
52
|
+
throw new Error(`${paramName} cannot be negative: "${str}".`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const lower = str.toLowerCase();
|
|
56
|
+
if (lower === 'infinity' || lower === 'max') {
|
|
57
|
+
return 'infinity';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Matches numbers with optional K, M, G, T, P unit suffix (with optional 'b' or 'B')
|
|
61
|
+
const match = str.match(/^(\d+(?:\.\d+)?)\s*([kmgtp]?[bb]?)?$/i);
|
|
62
|
+
if (!match) {
|
|
63
|
+
throw new Error(`Invalid ${paramName} format: "${str}". Expected values like 128K, 256M, 1G, or infinity.`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const num = match[1];
|
|
67
|
+
const unit = (match[2] || '').toUpperCase().replace(/B$/, '');
|
|
68
|
+
|
|
69
|
+
return `${num}${unit}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Formats byte values or systemd memory limit markers into human readable strings.
|
|
74
|
+
* @param {number|string} bytes
|
|
75
|
+
* @returns {string} Formatted memory string e.g. "284 MB", "infinity", "unavailable"
|
|
76
|
+
*/
|
|
77
|
+
export function formatMemoryBytes(bytes) {
|
|
78
|
+
if (bytes === null || bytes === undefined || bytes === '' || bytes === 'unavailable' || bytes === '[unavailable]' || bytes === '-') {
|
|
79
|
+
return 'unavailable';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const str = String(bytes).trim().toLowerCase();
|
|
83
|
+
if (str === 'infinity' || str === 'max' || str === '18446744073709551615') {
|
|
84
|
+
return 'infinity';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const num = Number(bytes);
|
|
88
|
+
if (Number.isNaN(num) || num < 0) {
|
|
89
|
+
// If it's already a formatted string like "400M" or "400 MB"
|
|
90
|
+
if (/^\d+\s*[a-zA-Z]+$/.test(str)) {
|
|
91
|
+
const match = str.match(/^(\d+)\s*([a-zA-Z]+)$/);
|
|
92
|
+
if (match) {
|
|
93
|
+
const u = match[2].toUpperCase().replace(/B$/, '');
|
|
94
|
+
return `${match[1]} ${u === 'M' ? 'MB' : u === 'G' ? 'GB' : u === 'K' ? 'KB' : u}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return 'unavailable';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (num === 0) return '0 MB';
|
|
101
|
+
|
|
102
|
+
const kb = 1024;
|
|
103
|
+
const mb = kb * 1024;
|
|
104
|
+
const gb = mb * 1024;
|
|
105
|
+
const tb = gb * 1024;
|
|
106
|
+
|
|
107
|
+
if (num >= tb) {
|
|
108
|
+
const val = num / tb;
|
|
109
|
+
return `${Number.isInteger(val) ? val : val.toFixed(1)} TB`;
|
|
110
|
+
}
|
|
111
|
+
if (num >= gb) {
|
|
112
|
+
const val = num / gb;
|
|
113
|
+
return `${Number.isInteger(val) ? val : val.toFixed(1)} GB`;
|
|
114
|
+
}
|
|
115
|
+
if (num >= mb) {
|
|
116
|
+
const val = num / mb;
|
|
117
|
+
return `${Number.isInteger(val) ? val : Math.round(val)} MB`;
|
|
118
|
+
}
|
|
119
|
+
if (num >= kb) {
|
|
120
|
+
const val = num / kb;
|
|
121
|
+
return `${Number.isInteger(val) ? val : Math.round(val)} KB`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return `${num} B`;
|
|
125
|
+
}
|
|
126
|
+
|
|
32
127
|
/**
|
|
33
128
|
* Saves metadata JSON for a service.
|
|
34
129
|
* @param {object} meta
|
|
@@ -41,14 +136,32 @@ export function saveAppMetadata(meta) {
|
|
|
41
136
|
fs.mkdirSync(dir, { recursive: true });
|
|
42
137
|
}
|
|
43
138
|
const filePath = getAppMetadataPath(safeName);
|
|
44
|
-
|
|
139
|
+
|
|
140
|
+
const command = meta.command ? resolveAbsolutePath(meta.command) : (meta.node ? resolveAbsolutePath(meta.node) : process.execPath);
|
|
141
|
+
const rawArgs = Array.isArray(meta.args) ? meta.args : (meta.script ? [meta.script] : []);
|
|
142
|
+
const args = rawArgs.map(a => (a.startsWith('/') || a.startsWith('./') || a.startsWith('../') || a.startsWith('~/')) ? resolveAbsolutePath(a) : a);
|
|
143
|
+
const scriptPath = meta.script ? resolveAbsolutePath(meta.script) : (args[0] ? resolveAbsolutePath(args[0]) : command);
|
|
144
|
+
const cwd = meta.cwd ? resolveAbsolutePath(meta.cwd) : path.dirname(scriptPath || command);
|
|
145
|
+
|
|
146
|
+
// Extract memory resources if present
|
|
147
|
+
const resources = meta.resources || {};
|
|
148
|
+
if (meta.memoryHigh) resources.memoryHigh = validateMemorySize(meta.memoryHigh, 'MemoryHigh');
|
|
149
|
+
if (meta.memoryMax) resources.memoryMax = validateMemorySize(meta.memoryMax, 'MemoryMax');
|
|
150
|
+
if (meta.memorySwapMax) resources.memorySwapMax = validateMemorySize(meta.memorySwapMax, 'MemorySwapMax');
|
|
151
|
+
|
|
45
152
|
const payload = {
|
|
46
153
|
name: safeName,
|
|
47
154
|
unit: getUnitFilename(safeName),
|
|
155
|
+
runtime: meta.runtime || 'node',
|
|
156
|
+
command,
|
|
157
|
+
args,
|
|
158
|
+
cwd,
|
|
48
159
|
group: meta.group || 'default',
|
|
160
|
+
// Optional resources section
|
|
161
|
+
...(Object.keys(resources).length > 0 ? { resources } : {}),
|
|
162
|
+
// Backward compatibility fields
|
|
49
163
|
script: scriptPath,
|
|
50
|
-
|
|
51
|
-
node: meta.node ? resolveAbsolutePath(meta.node) : process.execPath
|
|
164
|
+
node: meta.node ? resolveAbsolutePath(meta.node) : command
|
|
52
165
|
};
|
|
53
166
|
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
|
54
167
|
return payload;
|
|
@@ -64,7 +177,29 @@ export function readAppMetadata(name) {
|
|
|
64
177
|
const filePath = getAppMetadataPath(name);
|
|
65
178
|
if (!fs.existsSync(filePath)) return null;
|
|
66
179
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
67
|
-
|
|
180
|
+
const data = JSON.parse(content);
|
|
181
|
+
if (!data) return null;
|
|
182
|
+
|
|
183
|
+
// Normalization for legacy metadata format
|
|
184
|
+
const command = data.command || data.node || process.execPath;
|
|
185
|
+
const args = data.args || (data.script ? [data.script] : []);
|
|
186
|
+
const runtime = data.runtime || 'node';
|
|
187
|
+
const script = data.script || args[0] || command;
|
|
188
|
+
const cwd = data.cwd || (script ? path.dirname(script) : process.cwd());
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...data,
|
|
192
|
+
name: data.name || sanitizeServiceName(name),
|
|
193
|
+
unit: data.unit || getUnitFilename(name),
|
|
194
|
+
runtime,
|
|
195
|
+
command,
|
|
196
|
+
args,
|
|
197
|
+
cwd,
|
|
198
|
+
group: data.group || 'default',
|
|
199
|
+
resources: data.resources || undefined,
|
|
200
|
+
script,
|
|
201
|
+
node: data.node || command
|
|
202
|
+
};
|
|
68
203
|
} catch {
|
|
69
204
|
return null;
|
|
70
205
|
}
|
package/tsconfig.json
ADDED