unitup 0.1.5 → 0.3.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/.agents/AGENTS.md +35 -0
- package/.agents/skills/multi-runtime-management/SKILL.md +80 -0
- package/.agents/skills/platform-service-adapters/SKILL.md +108 -0
- package/.agents/skills/unitup-development-and-qa/SKILL.md +88 -0
- package/.agents/skills/zero-downtime-deployment/SKILL.md +135 -0
- package/CHANGELOG.md +5 -3
- package/README.md +89 -8
- package/biome.json +0 -1
- package/docs/index.html +188 -234
- package/index.d.ts +467 -440
- package/package.json +9 -3
- package/src/cli.js +561 -103
- package/src/core/drain-manager.js +70 -0
- package/src/core/generation-manager.js +414 -0
- package/src/core/index.js +8 -0
- package/src/core/ipc.js +236 -0
- package/src/core/lock.js +133 -0
- package/src/core/process-manager.js +313 -0
- package/src/core/readiness.js +243 -0
- package/src/core/router.js +390 -0
- package/src/core/supervisor.js +222 -0
- package/src/deploy/deployment-manager.js +407 -0
- package/src/deploy/index.js +9 -0
- package/src/deploy/rollback-manager.js +186 -0
- package/src/doctor.js +123 -56
- package/src/errors.js +131 -0
- package/src/index.js +186 -59
- package/src/logs.js +148 -0
- package/src/platform/base.js +211 -0
- package/src/platform/index.js +43 -0
- package/src/platform/linux.js +155 -0
- package/src/platform/macos.js +726 -0
- package/src/platform/windows-host.js +246 -0
- package/src/platform/windows.js +498 -0
- package/src/runtimes/bun.js +1 -1
- package/src/runtimes/common.js +24 -6
- package/src/runtimes/deno.js +1 -1
- package/src/runtimes/elixir.js +1 -1
- package/src/runtimes/go.js +1 -1
- package/src/runtimes/index.js +7 -7
- package/src/runtimes/native.js +2 -4
- package/src/runtimes/node.js +1 -1
- package/src/runtimes/php.js +1 -1
- package/src/runtimes/python.js +1 -1
- package/src/runtimes/ruby.js +1 -1
- package/src/runtimes/shell.js +1 -1
- package/src/schedule.js +31 -24
- package/src/service/config.js +63 -0
- package/src/service/manager.js +233 -0
- package/src/service/normalize.js +245 -0
- package/src/systemd.js +84 -38
- package/src/unit.js +17 -19
- package/src/utils.js +112 -18
- package/tsconfig.json +1 -1
- package/types-test/index.ts +158 -0
package/src/index.js
CHANGED
|
@@ -1,78 +1,206 @@
|
|
|
1
|
+
import { getDoctorInfo, runDoctor } from './doctor.js';
|
|
2
|
+
import {
|
|
3
|
+
ExecutableNotFoundError,
|
|
4
|
+
InvalidServiceConfigError,
|
|
5
|
+
PermissionRequiredError,
|
|
6
|
+
ServiceAlreadyExistsError,
|
|
7
|
+
ServiceNotFoundError,
|
|
8
|
+
ServiceStartError,
|
|
9
|
+
ServiceStopError,
|
|
10
|
+
UnitupError,
|
|
11
|
+
UnsupportedPlatformError
|
|
12
|
+
} from './errors.js';
|
|
13
|
+
import { readServiceLogs } from './logs.js';
|
|
14
|
+
import {
|
|
15
|
+
LinuxAdapter,
|
|
16
|
+
MacOSAdapter,
|
|
17
|
+
ServiceAdapter,
|
|
18
|
+
WindowsAdapter,
|
|
19
|
+
getAdapter,
|
|
20
|
+
getPlatformCapabilities
|
|
21
|
+
} from './platform/index.js';
|
|
22
|
+
import { WindowsServiceHost } from './platform/windows-host.js';
|
|
23
|
+
import { detectRuntime, resolveRuntimeConfig } from './runtimes/index.js';
|
|
24
|
+
import {
|
|
25
|
+
createSchedule,
|
|
26
|
+
disableSchedule,
|
|
27
|
+
enableSchedule,
|
|
28
|
+
getScheduleStatus,
|
|
29
|
+
listSchedules,
|
|
30
|
+
removeSchedule,
|
|
31
|
+
runSchedule,
|
|
32
|
+
validateCalendar
|
|
33
|
+
} from './schedule.js';
|
|
34
|
+
import { ServiceManager, defaultManager } from './service/manager.js';
|
|
35
|
+
import { normalizeServiceConfig, resolveExecutable } from './service/normalize.js';
|
|
1
36
|
import {
|
|
2
37
|
addService,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
38
|
+
checkNodeDiagnostics,
|
|
39
|
+
checkUserLinger,
|
|
40
|
+
executeJournalctlMaintenance,
|
|
41
|
+
findNodeExecutable,
|
|
42
|
+
getAllServicesMemoryUsage,
|
|
43
|
+
getServiceFailures,
|
|
44
|
+
getServiceMemoryUsage,
|
|
7
45
|
getServiceStatus,
|
|
8
46
|
getServiceStatusRaw,
|
|
9
|
-
listServices,
|
|
10
|
-
inspectService,
|
|
11
|
-
getServiceFailures,
|
|
12
47
|
getServicesByGroup,
|
|
13
|
-
|
|
14
|
-
setServiceLimits,
|
|
15
|
-
executeJournalctlMaintenance,
|
|
48
|
+
inspectService,
|
|
16
49
|
isLinux,
|
|
17
50
|
isSystemctlAvailable,
|
|
18
51
|
isSystemdPID1,
|
|
19
52
|
isUserSystemdAvailable,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
53
|
+
listServices,
|
|
54
|
+
removeService,
|
|
55
|
+
resetCommandRunner,
|
|
56
|
+
restartService,
|
|
57
|
+
runCommand,
|
|
58
|
+
runJournalctlLogs,
|
|
25
59
|
setCommandRunner,
|
|
26
|
-
|
|
60
|
+
setServiceLimits,
|
|
61
|
+
startService,
|
|
62
|
+
stopService
|
|
27
63
|
} from './systemd.js';
|
|
28
|
-
import { getDoctorInfo } from './doctor.js';
|
|
29
|
-
import {
|
|
30
|
-
createSchedule,
|
|
31
|
-
listSchedules,
|
|
32
|
-
getScheduleStatus,
|
|
33
|
-
runSchedule,
|
|
34
|
-
enableSchedule,
|
|
35
|
-
disableSchedule,
|
|
36
|
-
removeSchedule,
|
|
37
|
-
validateCalendar
|
|
38
|
-
} from './schedule.js';
|
|
39
64
|
import {
|
|
40
|
-
|
|
41
|
-
|
|
65
|
+
generateScheduleServiceContent,
|
|
66
|
+
generateTimerContent,
|
|
42
67
|
getTimerPath,
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
getUnitPath,
|
|
69
|
+
getUserUnitDir,
|
|
45
70
|
parseUnitContent,
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
timerFileExists,
|
|
72
|
+
unitFileExists
|
|
48
73
|
} from './unit.js';
|
|
49
74
|
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
getServiceNameFromUnit,
|
|
54
|
-
readAppMetadata,
|
|
75
|
+
findProjectConfig,
|
|
76
|
+
formatDuration,
|
|
77
|
+
formatMemoryBytes,
|
|
55
78
|
getAppMetadataPath,
|
|
56
|
-
|
|
79
|
+
getAppsDir,
|
|
80
|
+
getConfigFilepath,
|
|
57
81
|
getScheduleMetadataPath,
|
|
82
|
+
getSchedulesDir,
|
|
83
|
+
getServiceNameFromUnit,
|
|
84
|
+
getTimerFilename,
|
|
85
|
+
getUnitFilename,
|
|
86
|
+
getUnitupDir,
|
|
87
|
+
readAppMetadata,
|
|
58
88
|
readGlobalConfig,
|
|
59
|
-
saveGlobalConfig,
|
|
60
|
-
getConfigFilepath,
|
|
61
|
-
findProjectConfig,
|
|
62
89
|
readProjectConfig,
|
|
63
|
-
|
|
90
|
+
readScheduleMetadata,
|
|
64
91
|
resolveEffectiveMemoryLimits,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
validateMemorySize,
|
|
92
|
+
sanitizeServiceName,
|
|
93
|
+
saveGlobalConfig,
|
|
94
|
+
saveProjectConfig,
|
|
69
95
|
validateDuration,
|
|
70
|
-
|
|
96
|
+
validateMemorySize
|
|
71
97
|
} from './utils.js';
|
|
72
|
-
import {
|
|
98
|
+
import {
|
|
99
|
+
ProcessManager,
|
|
100
|
+
GenerationManager,
|
|
101
|
+
Router,
|
|
102
|
+
ReadinessChecker,
|
|
103
|
+
ReadinessTimeoutError,
|
|
104
|
+
ProcessStartupError,
|
|
105
|
+
PortMismatchError,
|
|
106
|
+
DrainManager,
|
|
107
|
+
DeploymentLock,
|
|
108
|
+
DeploymentLockError,
|
|
109
|
+
Supervisor,
|
|
110
|
+
IPCServer,
|
|
111
|
+
IPCClient,
|
|
112
|
+
findFreePort
|
|
113
|
+
} from './core/index.js';
|
|
114
|
+
import {
|
|
115
|
+
DeploymentManager,
|
|
116
|
+
DeploymentState,
|
|
117
|
+
defaultDeploymentManager,
|
|
118
|
+
RollbackManager,
|
|
119
|
+
defaultRollbackManager
|
|
120
|
+
} from './deploy/index.js';
|
|
121
|
+
|
|
122
|
+
// Aliases for unified cross-platform API
|
|
123
|
+
const install = (opts) => defaultManager.install(opts);
|
|
124
|
+
const uninstall = (name, opts) => defaultManager.uninstall(name, opts);
|
|
125
|
+
const start = (name, opts) => defaultManager.start(name, opts);
|
|
126
|
+
const stop = (name, opts) => defaultManager.stop(name, opts);
|
|
127
|
+
const restart = (name, opts) => defaultManager.restart(name, opts);
|
|
128
|
+
const deploy = (name, opts) => defaultDeploymentManager.deploy(name, {}, opts);
|
|
129
|
+
const promote = (name, opts) => defaultDeploymentManager.promote(name, {}, opts);
|
|
130
|
+
const rollback = (name, opts) => defaultRollbackManager.rollback(name, {}, opts);
|
|
131
|
+
const generations = (name, opts) => new GenerationManager(opts).listGenerations(name);
|
|
132
|
+
const status = (name, opts) => defaultManager.status(name, opts);
|
|
133
|
+
const list = (opts) => defaultManager.list(opts);
|
|
134
|
+
const inspect = (name, opts) => defaultManager.inspect(name, opts);
|
|
135
|
+
const logs = (name, opts) => defaultManager.logs(name, opts);
|
|
136
|
+
const enable = (name, opts) => defaultManager.enable(name, opts);
|
|
137
|
+
const disable = (name, opts) => defaultManager.disable(name, opts);
|
|
138
|
+
const platform = (targetPlatform) => getPlatformCapabilities(targetPlatform);
|
|
73
139
|
|
|
74
140
|
export {
|
|
75
|
-
//
|
|
141
|
+
// Unified cross-platform API
|
|
142
|
+
install,
|
|
143
|
+
uninstall,
|
|
144
|
+
start,
|
|
145
|
+
stop,
|
|
146
|
+
restart,
|
|
147
|
+
deploy,
|
|
148
|
+
promote,
|
|
149
|
+
rollback,
|
|
150
|
+
generations,
|
|
151
|
+
status,
|
|
152
|
+
list,
|
|
153
|
+
inspect,
|
|
154
|
+
logs,
|
|
155
|
+
enable,
|
|
156
|
+
disable,
|
|
157
|
+
platform,
|
|
158
|
+
ServiceManager,
|
|
159
|
+
defaultManager,
|
|
160
|
+
// Zero-Downtime Deployment & Orchestration
|
|
161
|
+
DeploymentManager,
|
|
162
|
+
DeploymentState,
|
|
163
|
+
defaultDeploymentManager,
|
|
164
|
+
RollbackManager,
|
|
165
|
+
defaultRollbackManager,
|
|
166
|
+
// Core Runtime Primitives
|
|
167
|
+
ProcessManager,
|
|
168
|
+
GenerationManager,
|
|
169
|
+
Router,
|
|
170
|
+
ReadinessChecker,
|
|
171
|
+
ReadinessTimeoutError,
|
|
172
|
+
ProcessStartupError,
|
|
173
|
+
PortMismatchError,
|
|
174
|
+
DrainManager,
|
|
175
|
+
DeploymentLock,
|
|
176
|
+
DeploymentLockError,
|
|
177
|
+
Supervisor,
|
|
178
|
+
IPCServer,
|
|
179
|
+
IPCClient,
|
|
180
|
+
findFreePort,
|
|
181
|
+
// Platform Adapters & Factory
|
|
182
|
+
ServiceAdapter,
|
|
183
|
+
LinuxAdapter,
|
|
184
|
+
MacOSAdapter,
|
|
185
|
+
WindowsAdapter,
|
|
186
|
+
WindowsServiceHost,
|
|
187
|
+
getAdapter,
|
|
188
|
+
getPlatformCapabilities,
|
|
189
|
+
// Normalization & Resolution
|
|
190
|
+
normalizeServiceConfig,
|
|
191
|
+
resolveExecutable,
|
|
192
|
+
readServiceLogs,
|
|
193
|
+
// Error hierarchy
|
|
194
|
+
UnitupError,
|
|
195
|
+
UnsupportedPlatformError,
|
|
196
|
+
ServiceNotFoundError,
|
|
197
|
+
ServiceAlreadyExistsError,
|
|
198
|
+
PermissionRequiredError,
|
|
199
|
+
ServiceStartError,
|
|
200
|
+
ServiceStopError,
|
|
201
|
+
InvalidServiceConfigError,
|
|
202
|
+
ExecutableNotFoundError,
|
|
203
|
+
// Main programmatic service management API (backward compatible)
|
|
76
204
|
addService as createService,
|
|
77
205
|
addService,
|
|
78
206
|
startService,
|
|
@@ -88,9 +216,9 @@ export {
|
|
|
88
216
|
setServiceLimits,
|
|
89
217
|
getServiceMemoryUsage,
|
|
90
218
|
getAllServicesMemoryUsage,
|
|
91
|
-
executeJournalctlMaintenance
|
|
219
|
+
executeJournalctlMaintenance,
|
|
92
220
|
runJournalctlLogs as getServiceLogs,
|
|
93
|
-
|
|
221
|
+
runJournalctlLogs,
|
|
94
222
|
// Programmatic schedule management API
|
|
95
223
|
createSchedule,
|
|
96
224
|
listSchedules,
|
|
@@ -100,16 +228,14 @@ export {
|
|
|
100
228
|
disableSchedule,
|
|
101
229
|
removeSchedule,
|
|
102
230
|
validateCalendar,
|
|
103
|
-
|
|
104
231
|
// Duration & Validation helpers
|
|
105
232
|
validateMemorySize,
|
|
106
233
|
validateDuration,
|
|
107
234
|
formatMemoryBytes,
|
|
108
|
-
|
|
235
|
+
formatDuration,
|
|
109
236
|
// Runtime detection & resolution
|
|
110
237
|
detectRuntime,
|
|
111
238
|
resolveRuntimeConfig,
|
|
112
|
-
|
|
113
239
|
// Metadata & Config helpers
|
|
114
240
|
readAppMetadata,
|
|
115
241
|
getAppMetadataPath,
|
|
@@ -125,7 +251,6 @@ export {
|
|
|
125
251
|
getSchedulesDir,
|
|
126
252
|
getAppsDir,
|
|
127
253
|
getUnitupDir,
|
|
128
|
-
|
|
129
254
|
// System & Doctor checks
|
|
130
255
|
isSystemctlAvailable as isSystemdAvailable,
|
|
131
256
|
isSystemctlAvailable,
|
|
@@ -136,7 +261,7 @@ export {
|
|
|
136
261
|
checkNodeDiagnostics,
|
|
137
262
|
findNodeExecutable,
|
|
138
263
|
getDoctorInfo,
|
|
139
|
-
|
|
264
|
+
runDoctor,
|
|
140
265
|
// Unit file & path helpers
|
|
141
266
|
getUserUnitDir,
|
|
142
267
|
getUnitPath,
|
|
@@ -150,8 +275,10 @@ export {
|
|
|
150
275
|
getUnitFilename,
|
|
151
276
|
getTimerFilename,
|
|
152
277
|
getServiceNameFromUnit,
|
|
153
|
-
|
|
154
278
|
// Runner override for testing
|
|
155
279
|
setCommandRunner,
|
|
156
|
-
resetCommandRunner
|
|
280
|
+
resetCommandRunner,
|
|
281
|
+
runCommand
|
|
157
282
|
};
|
|
283
|
+
|
|
284
|
+
export default defaultManager;
|
package/src/logs.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads lines from one or more log files with tailing, filtering, and follow support.
|
|
6
|
+
*
|
|
7
|
+
* @param {string|string[]} filePaths
|
|
8
|
+
* @param {object} [options]
|
|
9
|
+
* @param {number} [options.lines=100]
|
|
10
|
+
* @param {boolean} [options.follow=false]
|
|
11
|
+
* @param {string} [options.grep]
|
|
12
|
+
* @param {string} [options.since]
|
|
13
|
+
* @param {boolean} [options.json=false]
|
|
14
|
+
* @returns {Promise<string|EventEmitter>}
|
|
15
|
+
*/
|
|
16
|
+
export async function readServiceLogs(filePaths, options = {}) {
|
|
17
|
+
const allCandidatePaths = (Array.isArray(filePaths) ? filePaths : [filePaths]).filter(Boolean);
|
|
18
|
+
const existingPaths = allCandidatePaths.filter((p) => fs.existsSync(p));
|
|
19
|
+
existingPaths.sort((a, b) => {
|
|
20
|
+
try {
|
|
21
|
+
return fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs;
|
|
22
|
+
} catch {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const maxLines = typeof options.lines === 'number' && options.lines > 0 ? options.lines : 100;
|
|
27
|
+
const grepPattern = options.grep ? new RegExp(options.grep, 'i') : null;
|
|
28
|
+
|
|
29
|
+
// Collect all lines from currently existing files
|
|
30
|
+
const allEntries = [];
|
|
31
|
+
for (const filePath of existingPaths) {
|
|
32
|
+
try {
|
|
33
|
+
const stat = fs.statSync(filePath);
|
|
34
|
+
let content = '';
|
|
35
|
+
|
|
36
|
+
// If file is larger than 1MB, read only the tail chunk
|
|
37
|
+
if (stat.size > 1024 * 1024) {
|
|
38
|
+
const chunkSize = Math.min(stat.size, Math.max(1024 * 1024, maxLines * 2048));
|
|
39
|
+
const start = Math.max(0, stat.size - chunkSize);
|
|
40
|
+
const buffer = Buffer.alloc(stat.size - start);
|
|
41
|
+
const fd = fs.openSync(filePath, 'r');
|
|
42
|
+
fs.readSync(fd, buffer, 0, buffer.length, start);
|
|
43
|
+
fs.closeSync(fd);
|
|
44
|
+
content = buffer.toString('utf8');
|
|
45
|
+
} else {
|
|
46
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
if (!line && line.trim() === '') continue;
|
|
52
|
+
if (grepPattern && !grepPattern.test(line)) continue;
|
|
53
|
+
allEntries.push(line);
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Slice tail lines
|
|
61
|
+
const sliced = allEntries.slice(-maxLines);
|
|
62
|
+
|
|
63
|
+
if (options.follow) {
|
|
64
|
+
const emitter = new EventEmitter();
|
|
65
|
+
const filePositions = new Map();
|
|
66
|
+
|
|
67
|
+
for (const p of allCandidatePaths) {
|
|
68
|
+
try {
|
|
69
|
+
if (fs.existsSync(p)) {
|
|
70
|
+
const stat = fs.statSync(p);
|
|
71
|
+
filePositions.set(p, stat.size);
|
|
72
|
+
} else {
|
|
73
|
+
filePositions.set(p, 0);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
filePositions.set(p, 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Print initial existing lines
|
|
81
|
+
for (const l of sliced) {
|
|
82
|
+
console.log(l);
|
|
83
|
+
emitter.emit('line', l);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let isPolling = false;
|
|
87
|
+
const interval = setInterval(() => {
|
|
88
|
+
if (isPolling) return;
|
|
89
|
+
isPolling = true;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
for (const p of allCandidatePaths) {
|
|
93
|
+
try {
|
|
94
|
+
if (!fs.existsSync(p)) continue;
|
|
95
|
+
const stat = fs.statSync(p);
|
|
96
|
+
const lastPos = filePositions.get(p) || 0;
|
|
97
|
+
|
|
98
|
+
if (stat.size > lastPos) {
|
|
99
|
+
const stream = fs.createReadStream(p, { start: lastPos, end: stat.size, encoding: 'utf8' });
|
|
100
|
+
let buffer = '';
|
|
101
|
+
stream.on('data', (chunk) => {
|
|
102
|
+
buffer += chunk;
|
|
103
|
+
});
|
|
104
|
+
stream.on('end', () => {
|
|
105
|
+
filePositions.set(p, stat.size);
|
|
106
|
+
const newLines = buffer.split('\n');
|
|
107
|
+
for (const line of newLines) {
|
|
108
|
+
if (!line || !line.trim()) continue;
|
|
109
|
+
if (grepPattern && !grepPattern.test(line)) continue;
|
|
110
|
+
console.log(line);
|
|
111
|
+
emitter.emit('line', line);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
} else if (stat.size < lastPos) {
|
|
115
|
+
// File truncated or rotated
|
|
116
|
+
filePositions.set(p, 0);
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
// ignore
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
isPolling = false;
|
|
124
|
+
}
|
|
125
|
+
}, 250);
|
|
126
|
+
|
|
127
|
+
emitter.stop = () => {
|
|
128
|
+
clearInterval(interval);
|
|
129
|
+
emitter.emit('close');
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
return emitter;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (options.json) {
|
|
136
|
+
return JSON.stringify(
|
|
137
|
+
sliced.map((line) => ({ message: line })),
|
|
138
|
+
null,
|
|
139
|
+
2
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (sliced.length === 0) {
|
|
144
|
+
return 'No logs found.';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return sliced.join('\n');
|
|
148
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { UnsupportedPlatformError } from '../errors.js';
|
|
2
|
+
import { runCommand } from '../systemd.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base ServiceAdapter class defining the cross-platform contract for service management.
|
|
6
|
+
*/
|
|
7
|
+
export class ServiceAdapter {
|
|
8
|
+
/**
|
|
9
|
+
* @param {object} [options]
|
|
10
|
+
*/
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Platform identifier name.
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
get name() {
|
|
20
|
+
return 'base';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Platform capabilities metadata.
|
|
25
|
+
* @type {object}
|
|
26
|
+
*/
|
|
27
|
+
get capabilities() {
|
|
28
|
+
return {
|
|
29
|
+
serviceManager: 'none',
|
|
30
|
+
supports: {
|
|
31
|
+
install: false,
|
|
32
|
+
uninstall: false,
|
|
33
|
+
start: false,
|
|
34
|
+
stop: false,
|
|
35
|
+
restart: false,
|
|
36
|
+
enable: false,
|
|
37
|
+
disable: false,
|
|
38
|
+
status: false,
|
|
39
|
+
logs: false,
|
|
40
|
+
restartPolicy: false,
|
|
41
|
+
userServices: false,
|
|
42
|
+
systemServices: false,
|
|
43
|
+
memoryLimits: false,
|
|
44
|
+
schedule: false
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Executes a command using the configured command runner.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} cmd
|
|
53
|
+
* @param {string[]} args
|
|
54
|
+
* @param {object} [opts]
|
|
55
|
+
* @returns {Promise<{ stdout: string, stderr: string, code: number }>}
|
|
56
|
+
*/
|
|
57
|
+
async run(cmd, args, opts) {
|
|
58
|
+
return runCommand(cmd, args, opts);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Generates the service definition (unit file, plist, etc.) from normalized config.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} config - NormalizedServiceConfig
|
|
65
|
+
* @returns {string|object}
|
|
66
|
+
*/
|
|
67
|
+
generateService(config) {
|
|
68
|
+
throw new UnsupportedPlatformError(this.name);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Installs a service from normalized config.
|
|
73
|
+
*
|
|
74
|
+
* @param {object} config - NormalizedServiceConfig
|
|
75
|
+
* @param {object} [options]
|
|
76
|
+
* @returns {Promise<object>}
|
|
77
|
+
*/
|
|
78
|
+
async install(config, options = {}) {
|
|
79
|
+
throw new UnsupportedPlatformError(this.name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Uninstalls a service by name.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} name
|
|
86
|
+
* @param {object} [options]
|
|
87
|
+
* @returns {Promise<boolean>}
|
|
88
|
+
*/
|
|
89
|
+
async uninstall(name, options = {}) {
|
|
90
|
+
throw new UnsupportedPlatformError(this.name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Starts a service by name.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} name
|
|
97
|
+
* @param {object} [options]
|
|
98
|
+
* @returns {Promise<boolean>}
|
|
99
|
+
*/
|
|
100
|
+
async start(name, options = {}) {
|
|
101
|
+
throw new UnsupportedPlatformError(this.name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stops a service by name.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} name
|
|
108
|
+
* @param {object} [options]
|
|
109
|
+
* @returns {Promise<boolean>}
|
|
110
|
+
*/
|
|
111
|
+
async stop(name, options = {}) {
|
|
112
|
+
throw new UnsupportedPlatformError(this.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Restarts a service by name.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} name
|
|
119
|
+
* @param {object} [options]
|
|
120
|
+
* @returns {Promise<boolean>}
|
|
121
|
+
*/
|
|
122
|
+
async restart(name, options = {}) {
|
|
123
|
+
throw new UnsupportedPlatformError(this.name);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Enables automatic startup for a service.
|
|
128
|
+
*
|
|
129
|
+
* @param {string} name
|
|
130
|
+
* @param {object} [options]
|
|
131
|
+
* @returns {Promise<boolean>}
|
|
132
|
+
*/
|
|
133
|
+
async enable(name, options = {}) {
|
|
134
|
+
throw new UnsupportedPlatformError(this.name);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Disables automatic startup for a service.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} name
|
|
141
|
+
* @param {object} [options]
|
|
142
|
+
* @returns {Promise<boolean>}
|
|
143
|
+
*/
|
|
144
|
+
async disable(name, options = {}) {
|
|
145
|
+
throw new UnsupportedPlatformError(this.name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Gets normalized status for a service.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} name
|
|
152
|
+
* @param {object} [options]
|
|
153
|
+
* @returns {Promise<object>}
|
|
154
|
+
*/
|
|
155
|
+
async status(name, options = {}) {
|
|
156
|
+
throw new UnsupportedPlatformError(this.name);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Inspects detailed configuration and runtime info for a service.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} name
|
|
163
|
+
* @param {object} [options]
|
|
164
|
+
* @returns {Promise<object>}
|
|
165
|
+
*/
|
|
166
|
+
async inspect(name, options = {}) {
|
|
167
|
+
throw new UnsupportedPlatformError(this.name);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Retrieves or streams logs for a service.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} name
|
|
174
|
+
* @param {object} [options]
|
|
175
|
+
* @returns {Promise<any>}
|
|
176
|
+
*/
|
|
177
|
+
async logs(name, options = {}) {
|
|
178
|
+
throw new UnsupportedPlatformError(this.name);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Lists all services.
|
|
183
|
+
*
|
|
184
|
+
* @param {object} [options]
|
|
185
|
+
* @returns {Promise<Array<object>>}
|
|
186
|
+
*/
|
|
187
|
+
async list(options = {}) {
|
|
188
|
+
throw new UnsupportedPlatformError(this.name);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Checks if a service is installed.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} name
|
|
195
|
+
* @param {object} [options]
|
|
196
|
+
* @returns {Promise<boolean>|boolean}
|
|
197
|
+
*/
|
|
198
|
+
isInstalled(name, options = {}) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Lists failed services.
|
|
204
|
+
*
|
|
205
|
+
* @param {object} [options]
|
|
206
|
+
* @returns {Promise<Array<object>>}
|
|
207
|
+
*/
|
|
208
|
+
async failures(options = {}) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { UnsupportedPlatformError } from '../errors.js';
|
|
2
|
+
import { ServiceAdapter } from './base.js';
|
|
3
|
+
import { LinuxAdapter } from './linux.js';
|
|
4
|
+
import { MacOSAdapter } from './macos.js';
|
|
5
|
+
import { WindowsAdapter } from './windows.js';
|
|
6
|
+
|
|
7
|
+
export { ServiceAdapter, LinuxAdapter, MacOSAdapter, WindowsAdapter };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the appropriate platform service adapter for the given OS.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} [platform]
|
|
13
|
+
* @param {object} [options]
|
|
14
|
+
* @returns {ServiceAdapter}
|
|
15
|
+
*/
|
|
16
|
+
export function getAdapter(platform, options = {}) {
|
|
17
|
+
const targetPlatform =
|
|
18
|
+
platform || process.env.UNITUP_PLATFORM || (process.env.XDG_CONFIG_HOME ? 'linux' : process.platform);
|
|
19
|
+
|
|
20
|
+
switch (targetPlatform) {
|
|
21
|
+
case 'linux':
|
|
22
|
+
return new LinuxAdapter(options);
|
|
23
|
+
case 'darwin':
|
|
24
|
+
case 'macos':
|
|
25
|
+
return new MacOSAdapter(options);
|
|
26
|
+
case 'win32':
|
|
27
|
+
case 'windows':
|
|
28
|
+
return new WindowsAdapter(options);
|
|
29
|
+
default:
|
|
30
|
+
throw new UnsupportedPlatformError(targetPlatform);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns platform capabilities for the target or current OS.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} [platform]
|
|
38
|
+
* @returns {object}
|
|
39
|
+
*/
|
|
40
|
+
export function getPlatformCapabilities(platform) {
|
|
41
|
+
const adapter = getAdapter(platform);
|
|
42
|
+
return adapter.capabilities;
|
|
43
|
+
}
|