palmier 0.8.6 → 0.8.8

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.
@@ -133,6 +133,17 @@ function runLaunchctl(args, opts = {}) {
133
133
  console.error(`launchctl ${args[0]} failed: ${e.stderr || err}`);
134
134
  }
135
135
  }
136
+ /**
137
+ * Reload a LaunchAgent plist. The `enable` call is essential: after `bootout`
138
+ * macOS can leave the service in a *disabled* state (tracked in
139
+ * /var/db/com.apple.xpc.launchd/disabled.<uid>.plist). A subsequent bootstrap
140
+ * then fails with "Bootstrap failed: 5: Input/output error".
141
+ */
142
+ function reloadAgent(domain, label, plistPath) {
143
+ runLaunchctl(["bootout", `${domain}/${label}`], { ignoreFailure: true });
144
+ runLaunchctl(["enable", `${domain}/${label}`], { ignoreFailure: true });
145
+ runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
146
+ }
136
147
  export class MacOsPlatform {
137
148
  installDaemon(config) {
138
149
  fs.mkdirSync(AGENT_DIR, { recursive: true });
@@ -155,8 +166,7 @@ export class MacOsPlatform {
155
166
  fs.writeFileSync(plistPath, plist, "utf-8");
156
167
  console.log("LaunchAgent installed at:", plistPath);
157
168
  const domain = guiDomain();
158
- runLaunchctl(["bootout", `${domain}/${DAEMON_LABEL}`], { ignoreFailure: true });
159
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
169
+ reloadAgent(domain, DAEMON_LABEL, plistPath);
160
170
  runLaunchctl(["kickstart", "-k", `${domain}/${DAEMON_LABEL}`]);
161
171
  console.log("Palmier host LaunchAgent loaded and started.");
162
172
  console.log("Note: LaunchAgents only run while you are logged into the GUI session. " +
@@ -195,8 +205,7 @@ export class MacOsPlatform {
195
205
  const updated = content.replace(/(<key>PATH<\/key>\s*\n\s*<string>)[^<]*(<\/string>)/, `$1${escapeXml(userPath)}$2`);
196
206
  if (updated !== content) {
197
207
  fs.writeFileSync(plistPath, updated, "utf-8");
198
- runLaunchctl(["bootout", `${domain}/${DAEMON_LABEL}`], { ignoreFailure: true });
199
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
208
+ reloadAgent(domain, DAEMON_LABEL, plistPath);
200
209
  }
201
210
  }
202
211
  runLaunchctl(["kickstart", "-k", `${domain}/${DAEMON_LABEL}`]);
@@ -239,8 +248,7 @@ export class MacOsPlatform {
239
248
  }
240
249
  fs.writeFileSync(plistPath, buildPlist(dict), "utf-8");
241
250
  const domain = guiDomain();
242
- runLaunchctl(["bootout", `${domain}/${label}`], { ignoreFailure: true });
243
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
251
+ reloadAgent(domain, label, plistPath);
244
252
  }
245
253
  removeTaskTimer(taskId) {
246
254
  const domain = guiDomain();
@@ -249,10 +257,8 @@ export class MacOsPlatform {
249
257
  fs.unlinkSync(taskPlistPath(taskId));
250
258
  }
251
259
  catch { /* ignore */ }
252
- try {
253
- fs.unlinkSync(taskLogPath(taskId));
254
- }
255
- catch { /* ignore */ }
260
+ // Keep the log file — parity with journald retention on Linux, and
261
+ // needed to debug the last fire of one-shot specific_times tasks.
256
262
  }
257
263
  async startTask(taskId) {
258
264
  await execAsync(`launchctl kickstart ${guiDomain()}/${taskLabel(taskId)}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palmier",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
4
4
  "description": "Palmier host CLI - provisions, executes tasks, and serves NATS RPC",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hongxu Cai",
@@ -140,6 +140,18 @@ function runLaunchctl(args: string[], opts: { ignoreFailure?: boolean } = {}): v
140
140
  }
141
141
  }
142
142
 
143
+ /**
144
+ * Reload a LaunchAgent plist. The `enable` call is essential: after `bootout`
145
+ * macOS can leave the service in a *disabled* state (tracked in
146
+ * /var/db/com.apple.xpc.launchd/disabled.<uid>.plist). A subsequent bootstrap
147
+ * then fails with "Bootstrap failed: 5: Input/output error".
148
+ */
149
+ function reloadAgent(domain: string, label: string, plistPath: string): void {
150
+ runLaunchctl(["bootout", `${domain}/${label}`], { ignoreFailure: true });
151
+ runLaunchctl(["enable", `${domain}/${label}`], { ignoreFailure: true });
152
+ runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
153
+ }
154
+
143
155
  export class MacOsPlatform implements PlatformService {
144
156
  installDaemon(config: HostConfig): void {
145
157
  fs.mkdirSync(AGENT_DIR, { recursive: true });
@@ -166,8 +178,7 @@ export class MacOsPlatform implements PlatformService {
166
178
  console.log("LaunchAgent installed at:", plistPath);
167
179
 
168
180
  const domain = guiDomain();
169
- runLaunchctl(["bootout", `${domain}/${DAEMON_LABEL}`], { ignoreFailure: true });
170
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
181
+ reloadAgent(domain, DAEMON_LABEL, plistPath);
171
182
  runLaunchctl(["kickstart", "-k", `${domain}/${DAEMON_LABEL}`]);
172
183
 
173
184
  console.log("Palmier host LaunchAgent loaded and started.");
@@ -212,8 +223,7 @@ export class MacOsPlatform implements PlatformService {
212
223
  );
213
224
  if (updated !== content) {
214
225
  fs.writeFileSync(plistPath, updated, "utf-8");
215
- runLaunchctl(["bootout", `${domain}/${DAEMON_LABEL}`], { ignoreFailure: true });
216
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
226
+ reloadAgent(domain, DAEMON_LABEL, plistPath);
217
227
  }
218
228
  }
219
229
 
@@ -263,15 +273,15 @@ export class MacOsPlatform implements PlatformService {
263
273
  fs.writeFileSync(plistPath, buildPlist(dict), "utf-8");
264
274
 
265
275
  const domain = guiDomain();
266
- runLaunchctl(["bootout", `${domain}/${label}`], { ignoreFailure: true });
267
- runLaunchctl(["bootstrap", domain, `"${plistPath}"`]);
276
+ reloadAgent(domain, label, plistPath);
268
277
  }
269
278
 
270
279
  removeTaskTimer(taskId: string): void {
271
280
  const domain = guiDomain();
272
281
  runLaunchctl(["bootout", `${domain}/${taskLabel(taskId)}`], { ignoreFailure: true });
273
282
  try { fs.unlinkSync(taskPlistPath(taskId)); } catch { /* ignore */ }
274
- try { fs.unlinkSync(taskLogPath(taskId)); } catch { /* ignore */ }
283
+ // Keep the log file parity with journald retention on Linux, and
284
+ // needed to debug the last fire of one-shot specific_times tasks.
275
285
  }
276
286
 
277
287
  async startTask(taskId: string): Promise<void> {