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.
Files changed (55) hide show
  1. package/.agents/AGENTS.md +35 -0
  2. package/.agents/skills/multi-runtime-management/SKILL.md +80 -0
  3. package/.agents/skills/platform-service-adapters/SKILL.md +108 -0
  4. package/.agents/skills/unitup-development-and-qa/SKILL.md +88 -0
  5. package/.agents/skills/zero-downtime-deployment/SKILL.md +135 -0
  6. package/CHANGELOG.md +5 -3
  7. package/README.md +89 -8
  8. package/biome.json +0 -1
  9. package/docs/index.html +188 -234
  10. package/index.d.ts +467 -440
  11. package/package.json +9 -3
  12. package/src/cli.js +561 -103
  13. package/src/core/drain-manager.js +70 -0
  14. package/src/core/generation-manager.js +414 -0
  15. package/src/core/index.js +8 -0
  16. package/src/core/ipc.js +236 -0
  17. package/src/core/lock.js +133 -0
  18. package/src/core/process-manager.js +313 -0
  19. package/src/core/readiness.js +243 -0
  20. package/src/core/router.js +390 -0
  21. package/src/core/supervisor.js +222 -0
  22. package/src/deploy/deployment-manager.js +407 -0
  23. package/src/deploy/index.js +9 -0
  24. package/src/deploy/rollback-manager.js +186 -0
  25. package/src/doctor.js +123 -56
  26. package/src/errors.js +131 -0
  27. package/src/index.js +186 -59
  28. package/src/logs.js +148 -0
  29. package/src/platform/base.js +211 -0
  30. package/src/platform/index.js +43 -0
  31. package/src/platform/linux.js +155 -0
  32. package/src/platform/macos.js +726 -0
  33. package/src/platform/windows-host.js +246 -0
  34. package/src/platform/windows.js +498 -0
  35. package/src/runtimes/bun.js +1 -1
  36. package/src/runtimes/common.js +24 -6
  37. package/src/runtimes/deno.js +1 -1
  38. package/src/runtimes/elixir.js +1 -1
  39. package/src/runtimes/go.js +1 -1
  40. package/src/runtimes/index.js +7 -7
  41. package/src/runtimes/native.js +2 -4
  42. package/src/runtimes/node.js +1 -1
  43. package/src/runtimes/php.js +1 -1
  44. package/src/runtimes/python.js +1 -1
  45. package/src/runtimes/ruby.js +1 -1
  46. package/src/runtimes/shell.js +1 -1
  47. package/src/schedule.js +31 -24
  48. package/src/service/config.js +63 -0
  49. package/src/service/manager.js +233 -0
  50. package/src/service/normalize.js +245 -0
  51. package/src/systemd.js +84 -38
  52. package/src/unit.js +17 -19
  53. package/src/utils.js +112 -18
  54. package/tsconfig.json +1 -1
  55. 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
- startService,
4
- stopService,
5
- restartService,
6
- removeService,
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
- runJournalctlLogs,
14
- setServiceLimits,
15
- executeJournalctlMaintenance,
48
+ inspectService,
16
49
  isLinux,
17
50
  isSystemctlAvailable,
18
51
  isSystemdPID1,
19
52
  isUserSystemdAvailable,
20
- checkUserLinger,
21
- checkNodeDiagnostics,
22
- findNodeExecutable,
23
- getServiceMemoryUsage,
24
- getAllServicesMemoryUsage,
53
+ listServices,
54
+ removeService,
55
+ resetCommandRunner,
56
+ restartService,
57
+ runCommand,
58
+ runJournalctlLogs,
25
59
  setCommandRunner,
26
- resetCommandRunner
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
- getUserUnitDir,
41
- getUnitPath,
65
+ generateScheduleServiceContent,
66
+ generateTimerContent,
42
67
  getTimerPath,
43
- unitFileExists,
44
- timerFileExists,
68
+ getUnitPath,
69
+ getUserUnitDir,
45
70
  parseUnitContent,
46
- generateTimerContent,
47
- generateScheduleServiceContent
71
+ timerFileExists,
72
+ unitFileExists
48
73
  } from './unit.js';
49
74
  import {
50
- sanitizeServiceName,
51
- getUnitFilename,
52
- getTimerFilename,
53
- getServiceNameFromUnit,
54
- readAppMetadata,
75
+ findProjectConfig,
76
+ formatDuration,
77
+ formatMemoryBytes,
55
78
  getAppMetadataPath,
56
- readScheduleMetadata,
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
- saveProjectConfig,
90
+ readScheduleMetadata,
64
91
  resolveEffectiveMemoryLimits,
65
- getSchedulesDir,
66
- getAppsDir,
67
- getUnitupDir,
68
- validateMemorySize,
92
+ sanitizeServiceName,
93
+ saveGlobalConfig,
94
+ saveProjectConfig,
69
95
  validateDuration,
70
- formatMemoryBytes
96
+ validateMemorySize
71
97
  } from './utils.js';
72
- import { detectRuntime, resolveRuntimeConfig } from './runtimes/index.js';
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
- // Main programmatic service management API
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 as 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
+ }