unitup 0.1.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unitup",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Minimal, zero-dependency systemd-native service manager for any runtime, script, or executable",
5
5
  "main": "src/index.js",
6
6
  "types": "./index.d.ts",
package/src/schedule.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import {
4
5
  sanitizeServiceName,
5
6
  getUnitFilename,
@@ -106,42 +107,29 @@ export async function createSchedule(opts = {}) {
106
107
 
107
108
  const safeName = sanitizeServiceName(rawName);
108
109
 
109
- let commandExec = '';
110
- let execArgs = [];
111
- let detectedRuntime = opts.runtime || 'node';
112
- let scriptPath = '';
113
-
114
- if (opts.command) {
115
- commandExec = resolveAbsolutePath(opts.command);
116
- execArgs = Array.isArray(opts.args) ? opts.args : [];
117
- if (opts.script) {
118
- scriptPath = resolveAbsolutePath(opts.script);
119
- }
120
- } else if (opts.script) {
121
- scriptPath = resolveAbsolutePath(opts.script);
122
- const runtimeInfo = detectRuntime(scriptPath, opts.runtime);
123
- detectedRuntime = runtimeInfo.runtime;
124
-
125
- const resolved = await resolveRuntimeConfig({
126
- runtime: detectedRuntime,
127
- script: scriptPath,
128
- command: opts.command,
129
- args: opts.args || [],
130
- runtimeArgs: opts.runtimeArgs || [],
131
- customNodePath: opts.node
132
- });
133
-
134
- commandExec = resolved.command;
135
- execArgs = resolved.args;
136
- } else {
110
+ if (!opts.script && !opts.command) {
137
111
  throw new Error('Either script or command must be provided to create schedule.');
138
112
  }
139
113
 
114
+ const scriptPath = opts.script ? resolveAbsolutePath(opts.script) : '';
115
+ const resolved = await resolveRuntimeConfig({
116
+ runtime: opts.runtime,
117
+ script: scriptPath,
118
+ command: opts.command,
119
+ args: opts.args || [],
120
+ runtimeArgs: opts.runtimeArgs || [],
121
+ customNodePath: opts.node
122
+ });
123
+
124
+ const commandExec = resolved.command;
125
+ const execArgs = resolved.args;
126
+ const detectedRuntime = resolved.runtime;
127
+
140
128
  const cwd = opts.cwd
141
129
  ? resolveAbsolutePath(opts.cwd)
142
130
  : scriptPath
143
131
  ? path.dirname(scriptPath)
144
- : path.dirname(commandExec);
132
+ : process.cwd();
145
133
 
146
134
  const envObj = {};
147
135
  if (opts.env) {
@@ -333,7 +321,7 @@ export async function getScheduleStatus(name) {
333
321
  '--user',
334
322
  'show',
335
323
  timerFilename,
336
- '--property=ActiveState,SubState,NextElapseUSecRealtime,NextElapseUSec,LastTriggerUSec,Unit,Triggers,UnitFileState'
324
+ '--property=ActiveState,SubState,NextElapseUSecRealtime,NextElapseUSecMonotonic,NextElapseUSec,LastTriggerUSec,Unit,Triggers,UnitFileState'
337
325
  ]);
338
326
 
339
327
  const showServiceRes = await runCommand('systemctl', [
@@ -374,18 +362,27 @@ export async function getScheduleStatus(name) {
374
362
 
375
363
  // Parse NextElapse timestamp
376
364
  let rawNext = timerProps.NextElapseUSecRealtime || timerProps.NextElapseUSec;
377
- if (!rawNext || rawNext === '0' || rawNext === '18446744073709551615') {
378
- rawNext = 'n/a';
379
- }
380
-
381
365
  let nextRun = 'n/a';
382
- if (rawNext !== 'n/a') {
366
+
367
+ if (rawNext && rawNext !== '0' && rawNext !== '18446744073709551615') {
383
368
  const num = Number(rawNext);
384
369
  if (!isNaN(num) && num > 0) {
385
370
  nextRun = formatFutureTime(Math.floor(num / 1000));
386
371
  } else {
387
372
  nextRun = formatFutureTime(rawNext);
388
373
  }
374
+ } else if (timerProps.NextElapseUSecMonotonic && timerProps.NextElapseUSecMonotonic !== '0' && timerProps.NextElapseUSecMonotonic !== '18446744073709551615') {
375
+ const monoNum = Number(timerProps.NextElapseUSecMonotonic);
376
+ if (!isNaN(monoNum) && monoNum > 0) {
377
+ const currentMonoUSec = os.uptime() * 1000000;
378
+ const diffUSec = monoNum - currentMonoUSec;
379
+ if (diffUSec > 0) {
380
+ const targetMs = Date.now() + Math.floor(diffUSec / 1000);
381
+ nextRun = formatFutureTime(targetMs);
382
+ } else {
383
+ nextRun = 'imminent';
384
+ }
385
+ }
389
386
  }
390
387
 
391
388
  // Parse LastTrigger timestamp
package/src/unit.js CHANGED
@@ -83,8 +83,11 @@ export function generateUnitContent(opts) {
83
83
  throw new Error('Either "command" or "script" must be provided to generate systemd unit file.');
84
84
  }
85
85
 
86
- const scriptOrCmdPath = opts.script ? resolveAbsolutePath(opts.script) : (execArgs[0] && (execArgs[0].startsWith('/') || execArgs[0].startsWith('./')) ? resolveAbsolutePath(execArgs[0]) : commandExec);
87
- const cwd = opts.cwd ? resolveAbsolutePath(opts.cwd) : path.dirname(scriptOrCmdPath || commandExec);
86
+ const cwd = opts.cwd
87
+ ? resolveAbsolutePath(opts.cwd)
88
+ : opts.script
89
+ ? path.dirname(resolveAbsolutePath(opts.script))
90
+ : process.cwd();
88
91
  const restartPolicy = opts.restart || 'on-failure';
89
92
 
90
93
  const execStartTokens = [commandExec, ...execArgs];
@@ -306,8 +309,11 @@ export function generateScheduleServiceContent(opts) {
306
309
  throw new Error('Either "command" or "script" must be provided to generate scheduled unit file.');
307
310
  }
308
311
 
309
- const scriptOrCmdPath = opts.script ? resolveAbsolutePath(opts.script) : (execArgs[0] && (execArgs[0].startsWith('/') || execArgs[0].startsWith('./')) ? resolveAbsolutePath(execArgs[0]) : commandExec);
310
- const cwd = opts.cwd ? resolveAbsolutePath(opts.cwd) : path.dirname(scriptOrCmdPath || commandExec);
312
+ const cwd = opts.cwd
313
+ ? resolveAbsolutePath(opts.cwd)
314
+ : opts.script
315
+ ? path.dirname(resolveAbsolutePath(opts.script))
316
+ : process.cwd();
311
317
 
312
318
  const execStartTokens = [commandExec, ...execArgs];
313
319
  const execStartLine = execStartTokens.map(escapeExecArg).join(' ');
@@ -440,7 +446,7 @@ export function writeScheduleUnitFiles(opts) {
440
446
  saveScheduleMetadata({
441
447
  name: safeName,
442
448
  group: opts.group || 'default',
443
- runtime: opts.runtime || 'node',
449
+ runtime: opts.runtime || (opts.command ? 'custom' : 'node'),
444
450
  command: opts.command,
445
451
  args: opts.args,
446
452
  cwd: opts.cwd,
package/src/utils.js CHANGED
@@ -141,7 +141,7 @@ export function saveAppMetadata(meta) {
141
141
  const rawArgs = Array.isArray(meta.args) ? meta.args : (meta.script ? [meta.script] : []);
142
142
  const args = rawArgs.map(a => (a.startsWith('/') || a.startsWith('./') || a.startsWith('../') || a.startsWith('~/')) ? resolveAbsolutePath(a) : a);
143
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);
144
+ const cwd = meta.cwd ? resolveAbsolutePath(meta.cwd) : (meta.script ? path.dirname(resolveAbsolutePath(meta.script)) : process.cwd());
145
145
 
146
146
  // Extract memory resources if present
147
147
  const resources = meta.resources || {};