@cheongfish/bouncer 0.1.10 → 0.1.13

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/README.md CHANGED
@@ -8,9 +8,12 @@ runtime과 같은 version이어야 합니다.
8
8
  선택하세요.
9
9
 
10
10
  ```bash
11
- npm install --global @cheongfish/bouncer@0.1.10
11
+ npm install --global @cheongfish/bouncer@0.1.13
12
12
  ```
13
13
 
14
+ 전역 설치는 npm prefix의 실행 파일 디렉터리를 사용자 PATH에 등록합니다. 새
15
+ 터미널을 열어 변경 사항을 적용하세요.
16
+
14
17
  프로젝트 local 설치도 지원합니다. 일반 터미널은 `node_modules/.bin`의 shim을
15
18
  PATH에 추가할 수 있지만, plugin hook이 호스트 PATH를 상속하지 않는 환경에서는
16
19
  `BOUNCER_BIN`에 `node_modules/.bin/bouncer`의 절대 경로를 지정하세요.
@@ -18,7 +21,7 @@ PATH에 추가할 수 있지만, plugin hook이 호스트 PATH를 상속하지
18
21
  runtime을 다시 설치해야 shim이 생성됩니다.
19
22
 
20
23
  ```bash
21
- npm install --save-dev @cheongfish/bouncer@0.1.10
24
+ npm install --save-dev @cheongfish/bouncer@0.1.13
22
25
  ```
23
26
 
24
27
  PowerShell은 local 설치의 `node_modules/.bin/bouncer.cmd`를, 전역 설치는 PATH에
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { spawnSync } = require('node:child_process');
8
+
9
+ const env = process.env;
10
+ const isGlobal = /^(1|true)$/i.test(env.npm_config_global || env.NPM_CONFIG_GLOBAL || '')
11
+ || /^(global)$/i.test(env.npm_config_location || env.NPM_CONFIG_LOCATION || '');
12
+ if (!isGlobal) process.exit(0);
13
+
14
+ const prefix = env.npm_config_prefix || env.NPM_CONFIG_PREFIX;
15
+ if (!prefix) process.exit(0);
16
+
17
+ const binDir = process.platform === 'win32' ? path.resolve(prefix) : path.resolve(prefix, 'bin');
18
+ const marker = '# bouncer npm global PATH';
19
+
20
+ function addBlock(file, block) {
21
+ let content = '';
22
+ try { content = fs.readFileSync(file, 'utf8'); } catch (error) {
23
+ if (error.code !== 'ENOENT') throw error;
24
+ }
25
+ if (content.includes(marker)) return false;
26
+ fs.mkdirSync(path.dirname(file), { recursive: true });
27
+ fs.appendFileSync(file, `${content && !content.endsWith('\n') ? '\n' : ''}${block}\n`, { mode: 0o600 });
28
+ return true;
29
+ }
30
+
31
+ function posix() {
32
+ const escaped = binDir.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
33
+ const line = `${marker}\nexport PATH="${escaped}:$PATH"`;
34
+ const home = os.homedir();
35
+ const files = [path.join(home, '.profile')];
36
+ const shell = path.basename(env.SHELL || '');
37
+ if (shell === 'bash') files.push(path.join(home, '.bashrc'));
38
+ if (shell === 'zsh') files.push(path.join(home, '.zshrc'));
39
+ if (shell === 'fish') {
40
+ const fishLine = `${marker}\nfish_add_path --global ${JSON.stringify(binDir)}`;
41
+ files.push(path.join(home, '.config', 'fish', 'conf.d', 'bouncer-path.fish'));
42
+ fs.mkdirSync(path.dirname(files.at(-1)), { recursive: true });
43
+ fs.writeFileSync(files.at(-1), `${fishLine}\n`, { mode: 0o600 });
44
+ files.pop();
45
+ }
46
+ let changed = 0;
47
+ for (const file of files) if (addBlock(file, line)) changed++;
48
+ return changed;
49
+ }
50
+
51
+ function windows() {
52
+ const ps = [
53
+ '$p = [Environment]::GetEnvironmentVariable("Path", "User")',
54
+ `$d = ${JSON.stringify(binDir)}`,
55
+ '$parts = @($p -split ";" | Where-Object { $_ })',
56
+ 'if (-not ($parts | Where-Object { $_.TrimEnd("\\") -ieq $d.TrimEnd("\\") })) {',
57
+ ' [Environment]::SetEnvironmentVariable("Path", (($parts + $d) -join ";"), "User")',
58
+ ' Write-Output "added"',
59
+ '}',
60
+ ].join('; ');
61
+ const result = spawnSync('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', ps], { encoding: 'utf8' });
62
+ if (result.status !== 0) throw new Error(result.stderr || 'Could not update the Windows user PATH');
63
+ return result.stdout.includes('added') ? 1 : 0;
64
+ }
65
+
66
+ try {
67
+ const changed = process.platform === 'win32' ? windows() : posix();
68
+ if (changed) process.stdout.write(`bouncer: 사용자 PATH에 ${binDir} 경로를 추가했습니다. 새 터미널에서 적용됩니다.\n`);
69
+ } catch (error) {
70
+ process.stderr.write(`bouncer: npm 전역 경로를 PATH에 자동 등록하지 못했습니다: ${error.message}\n`);
71
+ }
package/dist/.build-stamp CHANGED
@@ -1 +1 @@
1
- sha256:7abe62097bbb29562f470a8e2eb46654e2dbf1cba03f5a19b9e01199371e2826
1
+ sha256:21c4cc141801913f284cfe283152673350e5dec5c2b00201e499bfad00b4d402
@@ -145,7 +145,7 @@ function attachCard(result, readCard) {
145
145
  return {
146
146
  ...fail('coordinator-card-missing'),
147
147
  cause: error.message,
148
- next: 'Install the matching npm runtime version or repair BOUNCER_HOME to the matching plugin install.',
148
+ next: 'Install a compatible npm runtime version or repair BOUNCER_HOME to a compatible plugin install.',
149
149
  };
150
150
  }
151
151
  return fail('coordinator-card-missing');
@@ -59,7 +59,11 @@ function readCandidate(host, root) {
59
59
  const metadata = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(absolute, metadataFile), 'utf8'));
60
60
  const version = parseVersion(metadata.version);
61
61
  if ((metadata.name === 'bouncer' || metadata.name === '@cheongfish/bouncer') && version) {
62
- return { host, path: absolute, version };
62
+ return {
63
+ host, path: absolute, version,
64
+ ...(typeof metadata.runtimeCompatibility === 'string'
65
+ ? { runtimeCompatibility: metadata.runtimeCompatibility } : {}),
66
+ };
63
67
  }
64
68
  }
65
69
  catch {
@@ -169,13 +173,41 @@ function resolvePluginRoot(options = {}, host) {
169
173
  * @returns {void} 일치하면 반환하고, 다르면 식별 가능한 Error를 던진다
170
174
  */
171
175
  function assertPluginVersion(candidate, runtimeVersion = readRuntimeVersion()) {
172
- if (candidate.version === runtimeVersion)
176
+ const range = candidate.runtimeCompatibility;
177
+ const supported = range === undefined
178
+ ? candidate.version === runtimeVersion
179
+ : isRuntimeCompatible(runtimeVersion, range);
180
+ if (supported)
173
181
  return;
174
- const error = new Error(`Bouncer plugin version ${candidate.version} does not match runtime version ${runtimeVersion}; `
175
- + 'install the matching npm runtime version or repair BOUNCER_HOME to the matching plugin install');
182
+ const error = new Error(`Bouncer plugin version ${candidate.version} is incompatible with runtime version ${runtimeVersion}`
183
+ + `${range === undefined ? '' : ` (supported: ${range})`}; `
184
+ + 'install a compatible npm runtime version or repair BOUNCER_HOME to a compatible plugin install');
176
185
  error.code = 'BOUNCER_PLUGIN_VERSION_MISMATCH';
177
186
  throw error;
178
187
  }
188
+ function isRuntimeCompatible(version, range) {
189
+ // Deliberately accept only explicit comparator sets to keep manifest parsing predictable.
190
+ const clauses = range.trim().split(/\s+/);
191
+ if (clauses.length === 0 || clauses.some((clause) => !/^(>=|>|<=|<|=)\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(clause))) {
192
+ return false;
193
+ }
194
+ return clauses.every((clause) => {
195
+ const match = /^(>=|>|<=|<|=)(.+)$/.exec(clause);
196
+ if (!match || !parseVersion(match[2]))
197
+ return false;
198
+ const order = compareSemverDesc(version, match[2]);
199
+ // compareSemverDesc returns positive when version sorts before the bound.
200
+ if (match[1] === '>=')
201
+ return order <= 0;
202
+ if (match[1] === '>')
203
+ return order < 0;
204
+ if (match[1] === '<=')
205
+ return order >= 0;
206
+ if (match[1] === '<')
207
+ return order > 0;
208
+ return order === 0;
209
+ });
210
+ }
179
211
  /**
180
212
  * 설치된 CLI package.json의 버전을 읽는다. 자산 manifest와 runtime의 비교 기준은
181
213
  * 실제 실행 중인 npm package에 고정해 배포본 간 불일치를 선택 직후 드러낸다.
@@ -192,7 +224,8 @@ function readRuntimeVersion() {
192
224
  /**
193
225
  * 플러그인 자산(`agents/`, `references/coordinator-cards/`)을 읽을 검증된 루트를 돌려준다.
194
226
  * 순서는 `BOUNCER_HOME` → strict-semver가 가장 높은 host 후보이며, 선택된 설치의
195
- * version은 runtime package version과 일치해야 한다. runtime tree는 자산 설치 여부를
227
+ * `runtimeCompatibility`가 있으면 runtime version이 범위에 포함되어야 한다. 오래된
228
+ * manifest는 plugin version과 runtime version이 일치해야 한다. runtime tree는 자산 설치 여부를
196
229
  * 증명하지 않으므로 후보로 추론하지 않는다.
197
230
  *
198
231
  * @param {AssetRootOptions} [options] - `env`, `homeDir`, resolver 테스트용 `runtimeVersion`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheongfish/bouncer",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Deterministic runtime for the Bouncer plugin",
6
6
  "repository": {
@@ -28,6 +28,7 @@
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsc -p tsconfig.json && node bin/build-stamp.js --write",
31
+ "postinstall": "node bin/register-path.js",
31
32
  "prepack": "npm run build"
32
33
  }
33
34
  }