@ours.network/cli 0.5.0 → 1.0.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.
@@ -1,4 +1,5 @@
1
1
  export declare const DEFAULT_SYSTEMD_UNIT = "ours.service";
2
+ export declare const DEFAULT_LAUNCHD_LABEL = "solutions.adaptframework.ours";
2
3
  export interface InstanceResult {
3
4
  ok: boolean;
4
5
  /** '' means "the default, unnamed daemon" — the historical single-unit behaviour. */
@@ -7,6 +8,8 @@ export interface InstanceResult {
7
8
  }
8
9
  export declare function normalizeInstanceName(raw: unknown): InstanceResult;
9
10
  export declare function systemdUnitName(instance?: string): string;
11
+ export declare function launchdLabel(instance?: string): string;
10
12
  export declare function serviceDescription(instance?: string): string;
11
13
  export declare function instanceNameFromStateDir(stateDir: string): InstanceResult;
12
14
  export declare function unitNameForStateDir(stateDir: string): string;
15
+ export declare function labelForStateDir(stateDir: string): string;
@@ -1,14 +1,20 @@
1
1
  import {
2
+ DEFAULT_LAUNCHD_LABEL,
2
3
  DEFAULT_SYSTEMD_UNIT,
3
4
  instanceNameFromStateDir,
5
+ labelForStateDir,
6
+ launchdLabel,
4
7
  normalizeInstanceName,
5
8
  serviceDescription,
6
9
  systemdUnitName,
7
10
  unitNameForStateDir
8
- } from "./chunk-3RLAHOPS.js";
11
+ } from "./chunk-UZQWSOUB.js";
9
12
  export {
13
+ DEFAULT_LAUNCHD_LABEL,
10
14
  DEFAULT_SYSTEMD_UNIT,
11
15
  instanceNameFromStateDir,
16
+ labelForStateDir,
17
+ launchdLabel,
12
18
  normalizeInstanceName,
13
19
  serviceDescription,
14
20
  systemdUnitName,
package/dist/service.d.ts CHANGED
@@ -47,6 +47,12 @@ export interface ServiceContext {
47
47
  export interface ServiceDeps {
48
48
  platform: NodeJS.Platform;
49
49
  run(command: string, args: string[]): Promise<void>;
50
+ /**
51
+ * launchd only — the uid whose GUI domain the agent is bootstrapped into.
52
+ * Injected rather than read from `process.getuid()` so the domain a plan
53
+ * targets is assertable off macOS, where the rest of this file is developed.
54
+ */
55
+ uid?: number;
50
56
  }
51
57
  export interface ServiceAdapter {
52
58
  readonly name: string;
@@ -58,4 +64,28 @@ export interface ServiceAdapter {
58
64
  * unit written before this CLI baked one in.
59
65
  */
60
66
  export declare function bakedStateDir(unit: string): string | undefined;
67
+ /**
68
+ * What an already-present service definition says about itself, in the terms
69
+ * the guard needs: did this CLI write it, and which daemon is it for. Reading
70
+ * it is per-format; judging it is not, which is what keeps the systemd and
71
+ * launchd guards from drifting into two different sets of refusals.
72
+ */
73
+ export interface ExistingDefinition {
74
+ managed: boolean;
75
+ /** The baked state directory, or undefined for a definition that bakes none. */
76
+ owner?: string;
77
+ }
61
78
  export declare function createLinuxUserSystemdAdapter(deps?: ServiceDeps): ServiceAdapter;
79
+ /**
80
+ * The state directory a CLI-managed launchd agent was installed for. The plist
81
+ * counterpart of `bakedStateDir`, and the reason a label — which is not
82
+ * injective over state directories — is still enough to find its daemon.
83
+ */
84
+ export declare function bakedStateDirFromPlist(plist: string): string | undefined;
85
+ export declare function createMacosUserLaunchdAdapter(deps?: ServiceDeps): ServiceAdapter;
86
+ /**
87
+ * The adapter for the platform this CLI is running on. The two factories keep
88
+ * refusing every platform but their own — the selector is the only thing that
89
+ * is allowed to know which platform maps to which service manager.
90
+ */
91
+ export declare function createServiceAdapter(deps?: ServiceDeps): ServiceAdapter;
package/dist/service.js CHANGED
@@ -1,11 +1,17 @@
1
1
  import {
2
2
  SERVICE_NAME,
3
3
  bakedStateDir,
4
- createLinuxUserSystemdAdapter
5
- } from "./chunk-227DBTDK.js";
6
- import "./chunk-3RLAHOPS.js";
4
+ bakedStateDirFromPlist,
5
+ createLinuxUserSystemdAdapter,
6
+ createMacosUserLaunchdAdapter,
7
+ createServiceAdapter
8
+ } from "./chunk-TNNBVUKK.js";
9
+ import "./chunk-UZQWSOUB.js";
7
10
  export {
8
11
  SERVICE_NAME,
9
12
  bakedStateDir,
10
- createLinuxUserSystemdAdapter
13
+ bakedStateDirFromPlist,
14
+ createLinuxUserSystemdAdapter,
15
+ createMacosUserLaunchdAdapter,
16
+ createServiceAdapter
11
17
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/cli",
3
- "version": "0.5.0",
3
+ "version": "1.0.0",
4
4
  "description": "Transport-neutral operator CLI for the ours shared daemon",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
@@ -23,6 +23,8 @@
23
23
  "typecheck": "tsc -p tsconfig.json --noEmit"
24
24
  },
25
25
  "dependencies": {
26
- "@ours.network/sdk": "^1.0.2"
26
+ "@adapt-toolkit/sdk": "0.10.12",
27
+ "@adapt-toolkit/sdk-native": "0.10.12",
28
+ "@ours.network/sdk": "^2.0.0"
27
29
  }
28
30
  }
@@ -1,132 +0,0 @@
1
- import {
2
- instanceNameFromStateDir,
3
- serviceDescription,
4
- unitNameForStateDir
5
- } from "./chunk-3RLAHOPS.js";
6
-
7
- // src/service.ts
8
- import { execFile as nodeExecFile } from "node:child_process";
9
- import { promisify } from "node:util";
10
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
11
- import { homedir } from "node:os";
12
- import { dirname, join, resolve } from "node:path";
13
- var execFile = promisify(nodeExecFile);
14
- var MARKER = "# Managed by @ours.network/cli";
15
- var SERVICE_NAME = "ours.service";
16
- var defaultDeps = {
17
- platform: process.platform,
18
- run: async (command, args) => {
19
- await execFile(command, args, { windowsHide: true });
20
- }
21
- };
22
- function systemdEscapeArgument(value) {
23
- if (/[\n\r]/.test(value)) throw new Error("service arguments must not contain newlines");
24
- return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("%", "%%")}"`;
25
- }
26
- function systemdUnescapeArgument(value) {
27
- const trimmed = value.trim();
28
- if (!trimmed.startsWith('"') || !trimmed.endsWith('"') || trimmed.length < 2) return void 0;
29
- return trimmed.slice(1, -1).replaceAll("%%", "%").replaceAll('\\"', '"').replaceAll("\\\\", "\\");
30
- }
31
- function bakedStateDir(unit) {
32
- for (const line of unit.split("\n")) {
33
- if (!line.startsWith("Environment=OURS_STATE_DIR=")) continue;
34
- return systemdUnescapeArgument(line.slice("Environment=OURS_STATE_DIR=".length));
35
- }
36
- return void 0;
37
- }
38
- function describeConflict(previous, serviceFile, stateDir, verb = "overwrite") {
39
- if (!previous.startsWith(MARKER)) {
40
- return {
41
- kind: "unmanaged",
42
- serviceFile,
43
- message: verb === "remove" ? `${serviceFile} is not managed by @ours.network/cli; refusing to remove it` : `${serviceFile} is not managed by @ours.network/cli; refusing to overwrite it without --force`
44
- };
45
- }
46
- const owner = bakedStateDir(previous);
47
- if (owner !== void 0 && owner !== stateDir) {
48
- return {
49
- kind: "other-state-dir",
50
- serviceFile,
51
- owner,
52
- message: `${serviceFile} is the boot service for the daemon at ${owner}, not ${stateDir}; refusing to ${verb} it without --force`
53
- };
54
- }
55
- return void 0;
56
- }
57
- function resolveStateDir(context) {
58
- return resolve(context.stateDir ?? join(context.homeDir ?? homedir(), ".ours"));
59
- }
60
- function createLinuxUserSystemdAdapter(deps = defaultDeps) {
61
- if (deps.platform !== "linux") throw new Error(`service management is not supported on ${deps.platform}; use an external launcher for \`ours daemon serve\``);
62
- return {
63
- name: "systemd-user",
64
- async install(context) {
65
- const home = context.homeDir ?? homedir();
66
- const stateDir = resolveStateDir(context);
67
- const unitName = unitNameForStateDir(stateDir);
68
- const serviceFile = join(home, ".config", "systemd", "user", unitName);
69
- const unitConfig = context.configPath ? resolve(context.configPath) : void 0;
70
- const args = [process.execPath, resolve(context.cliPath), "daemon", "serve"];
71
- if (unitConfig) args.push("--config", unitConfig);
72
- const environment = `Environment=OURS_STATE_DIR=${systemdEscapeArgument(stateDir)}
73
- `;
74
- const unit = `${MARKER}
75
- [Unit]
76
- Description=${serviceDescription(instanceNameFromStateDir(stateDir).name)}
77
- After=network-online.target
78
-
79
- [Service]
80
- Type=simple
81
- ExecStart=${args.map(systemdEscapeArgument).join(" ")}
82
- ${environment}Restart=on-failure
83
- RestartSec=2
84
-
85
- [Install]
86
- WantedBy=default.target
87
- `;
88
- if (existsSync(serviceFile)) {
89
- const previous = readFileSync(serviceFile, "utf8");
90
- const conflict = describeConflict(previous, serviceFile, stateDir);
91
- if (conflict && !context.force) {
92
- if (!context.dryRun) throw new Error(conflict.message);
93
- return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands: [], changed: false, dryRun: true, conflict };
94
- }
95
- if (previous === unit) return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands: [], changed: false, dryRun: context.dryRun === true };
96
- }
97
- const commands = [["systemctl", "--user", "daemon-reload"], ["systemctl", "--user", "enable", "--now", unitName]];
98
- if (!context.dryRun) {
99
- mkdirSync(dirname(serviceFile), { recursive: true });
100
- writeFileSync(serviceFile, unit, { mode: 420 });
101
- for (const [command, ...args2] of commands) await deps.run(command, args2);
102
- }
103
- return { adapter: this.name, action: "install", serviceFile, unitName, stateDir, configPath: unitConfig, commands, changed: true, dryRun: context.dryRun === true };
104
- },
105
- async uninstall(context) {
106
- const home = context.homeDir ?? homedir();
107
- const stateDir = resolveStateDir(context);
108
- const unitName = unitNameForStateDir(stateDir);
109
- const serviceFile = join(home, ".config", "systemd", "user", unitName);
110
- if (!existsSync(serviceFile)) return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands: [], changed: false, dryRun: context.dryRun === true };
111
- const previous = readFileSync(serviceFile, "utf8");
112
- const conflict = describeConflict(previous, serviceFile, stateDir, "remove");
113
- if (conflict && !(context.force && previous.startsWith(MARKER))) {
114
- if (!context.dryRun) throw new Error(conflict.message);
115
- return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands: [], changed: false, dryRun: true, conflict };
116
- }
117
- const commands = [["systemctl", "--user", "disable", "--now", unitName], ["systemctl", "--user", "daemon-reload"]];
118
- if (!context.dryRun) {
119
- await deps.run(commands[0][0], commands[0].slice(1));
120
- unlinkSync(serviceFile);
121
- await deps.run(commands[1][0], commands[1].slice(1));
122
- }
123
- return { adapter: this.name, action: "uninstall", serviceFile, unitName, stateDir, commands, changed: true, dryRun: context.dryRun === true };
124
- }
125
- };
126
- }
127
-
128
- export {
129
- SERVICE_NAME,
130
- bakedStateDir,
131
- createLinuxUserSystemdAdapter
132
- };