joinweave 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.
Files changed (2) hide show
  1. package/package.json +4 -2
  2. package/src/installer.js +29 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joinweave",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "description": "One command to join the Weave agent-native web.",
6
6
  "license": "MIT OR Apache-2.0",
@@ -13,7 +13,9 @@
13
13
  "files": [
14
14
  "bin",
15
15
  "src",
16
- "README.md"
16
+ "README.md",
17
+ "LICENSE-APACHE",
18
+ "LICENSE-MIT"
17
19
  ],
18
20
  "dependencies": {
19
21
  "@useweave/cli": "0.1.0"
package/src/installer.js CHANGED
@@ -4,7 +4,10 @@ import { resolve, join, dirname, isAbsolute, parse } from 'node:path';
4
4
  import { createHash, randomUUID } from 'node:crypto';
5
5
  import { execFile } from 'node:child_process';
6
6
 
7
- export const VERSION = '0.1.0';
7
+ export const VERSION = '0.1.1';
8
+ const NATIVE_PACKAGE_VERSION = '0.1.0';
9
+ // Published native0.1.0 treats this as its service-ownership protocol version.
10
+ const RECEIPT_VERSION = '0.1.0';
8
11
  const PATH_BEGIN = '# >>> joinweave managed PATH >>>';
9
12
  const PATH_END = '# <<< joinweave managed PATH <<<' ;
10
13
  const MCP_BEGIN = '# >>> joinweave managed MCP >>>';
@@ -124,7 +127,7 @@ const systemdPath = (value) => value.replaceAll('%', '%%');
124
127
  const shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
125
128
 
126
129
  export class Installer {
127
- constructor({ home = homedir(), weaveHome = process.env.WEAVE_HOME || join(home, '.weave'), platform = process.platform, uid = process.getuid?.(), env = process.env, run = runCommand, resolveNative, confirm = async () => false, progress = () => {}, sleep = (ms) => new Promise((done) => setTimeout(done, ms)) } = {}) {
130
+ constructor({ home = homedir(), weaveHome = process.env.WEAVE_HOME || join(home, '.weave'), platform = process.platform, uid = process.getuid?.(), env = process.env, run = runCommand, resolveNative, confirm = async () => false, progress = () => {}, sleep = (ms) => new Promise((done) => setTimeout(done, ms)), now = Date.now } = {}) {
128
131
  this.home = resolve(home);
129
132
  this.weaveHome = resolve(weaveHome);
130
133
  if ([this.home, this.weaveHome].some((path) => /[\x00-\x1f\x7f]/.test(path))) fail('UNSAFE_PATH', 'Workspace and home paths cannot contain control characters.');
@@ -139,16 +142,17 @@ export class Installer {
139
142
  this.confirm = confirm;
140
143
  this.progress = progress;
141
144
  this.sleep = sleep;
145
+ this.now = now;
142
146
  this.binary = join(this.weaveHome, 'bin', 'weave');
143
147
  this.manifestPath = join(this.weaveHome, '.joinweave-install.json');
144
148
  this.lockPath = join(this.weaveHome, '.joinweave-install.lock');
145
- this.manifest = { schema: 1, installer: 'joinweave', version: VERSION, binary: null, service: null, path: null, mcp: {} };
149
+ this.manifest = { schema: 1, installer: 'joinweave', version: RECEIPT_VERSION, installerVersion: VERSION, binary: null, service: null, path: null, mcp: {} };
146
150
  this.warnings = [];
147
151
  this.manualMcp = [];
148
152
  }
149
153
 
150
154
  validatePlatform() {
151
- if (!['darwin', 'linux'].includes(this.platform)) fail('UNSUPPORTED_PLATFORM', 'joinweave 0.1.0 supports macOS and Linux. Windows service installation is not supported yet.');
155
+ if (!['darwin', 'linux'].includes(this.platform)) fail('UNSUPPORTED_PLATFORM', `joinweave ${VERSION} supports macOS and Linux. Windows service installation is not supported yet.`);
152
156
  }
153
157
 
154
158
  loadManifest() {
@@ -161,7 +165,8 @@ export class Installer {
161
165
  }
162
166
 
163
167
  saveManifest() {
164
- this.manifest.version = VERSION;
168
+ this.manifest.version = RECEIPT_VERSION;
169
+ this.manifest.installerVersion = VERSION;
165
170
  atomicWrite(this.manifestPath, Buffer.from(`${JSON.stringify(this.manifest, null, 2)}\n`), { previous: readFile(this.manifestPath) });
166
171
  }
167
172
 
@@ -213,7 +218,7 @@ export class Installer {
213
218
  if (old && (!this.manifest.binary || digest(old) !== this.manifest.binary.sha256)) fail('UNMANAGED_BINARY', `An unrelated or modified executable exists at ${this.binary}. It will not be replaced.`);
214
219
  if (!old || digest(old) !== sha256) atomicWrite(this.binary, bytes, { previous: old, mode: 0o755 });
215
220
  fs.chmodSync(this.binary, 0o755);
216
- this.manifest.binary = { sha256, version: VERSION };
221
+ this.manifest.binary = { sha256, version: NATIVE_PACKAGE_VERSION };
217
222
  this.saveManifest();
218
223
  this.progress('Installed Weave');
219
224
  }
@@ -236,20 +241,31 @@ export class Installer {
236
241
  return { path: join(this.home, '.config', 'systemd', 'user', 'weave-node.service'), bytes: Buffer.from(`[Unit]\nDescription=Weave peer node\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${systemdQuote(this.binary, true)} node run\n${entries}WorkingDirectory=${systemdPath(this.weaveHome)}\nRestart=on-failure\nRestartSec=5\nStandardOutput=append:${systemdPath(logPath)}\nStandardError=append:${systemdPath(logPath)}\n\n[Install]\nWantedBy=default.target\n`) };
237
242
  }
238
243
 
239
- async serviceCommand(args, required = true) {
244
+ async serviceCommand(args, required = true, timeout = 30_000) {
240
245
  const command = this.platform === 'darwin' ? 'launchctl' : 'systemctl';
241
- const result = await this.run(command, args, { env: this.env, timeout: 30_000 });
246
+ const result = await this.run(command, args, { env: this.env, timeout });
242
247
  if (required && result.code !== 0) fail('SERVICE_COMMAND_FAILED', `${command} ${args.join(' ')} failed. Confirm a logged-in user service session is available; use --no-service for a detached node.`);
243
248
  return result;
244
249
  }
245
250
 
246
251
  async stopService() {
247
252
  if (this.platform === 'darwin') {
248
- await this.serviceCommand(['bootout', `gui/${this.uid}/${serviceName}`], false);
249
- const status = await this.serviceCommand(['print', `gui/${this.uid}/${serviceName}`], false);
250
- if (status.code === 0) fail('SERVICE_STOP_FAILED', 'The launchd node remains registered. Its binary and configuration were preserved.');
251
- const missing = `Could not find service "${serviceName}" in domain for user gui: ${this.uid}`;
252
- if (status.code !== 113 || !status.stderr.includes(missing)) fail('SERVICE_STOP_UNKNOWN', 'Could not verify launchd shutdown. Its binary and configuration were preserved.');
253
+ const deadline = this.now() + 10_000;
254
+ const timeoutMessage = 'Timed out waiting for launchd deregistration. Its binary and configuration were preserved.';
255
+ await this.serviceCommand(['bootout', `gui/${this.uid}/${serviceName}`], false, 10_000);
256
+ for (;;) {
257
+ const remaining = deadline - this.now();
258
+ if (remaining <= 0) fail('SERVICE_STOP_FAILED', timeoutMessage);
259
+ const status = await this.serviceCommand(['print', `gui/${this.uid}/${serviceName}`], false, remaining);
260
+ const checkedAt = this.now();
261
+ if (checkedAt >= deadline) fail('SERVICE_STOP_FAILED', timeoutMessage);
262
+ if (status.code !== 0) {
263
+ const missing = `Could not find service "${serviceName}" in domain for user gui: ${this.uid}`;
264
+ if (status.code !== 113 || !status.stderr.includes(missing)) fail('SERVICE_STOP_UNKNOWN', 'Could not verify launchd shutdown. Its binary and configuration were preserved.');
265
+ break;
266
+ }
267
+ await this.sleep(Math.min(100, deadline - checkedAt));
268
+ }
253
269
  } else {
254
270
  await this.serviceCommand(['--user', 'disable', '--now', 'weave-node.service'], false);
255
271
  const status = await this.serviceCommand(['--user', 'is-active', '--quiet', 'weave-node.service'], false);