token-studio 4.8.0 → 4.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.8.1
4
+
5
+ - Fixed `npx token-studio demo/start` when npm hoists `vite` beside the package instead of inside `token-studio/node_modules`.
6
+ - Added runtime path tests for npm/npx hoisted dependency layout and local source checkout layout.
7
+
3
8
  ## 4.8.0
4
9
 
5
10
  - Renamed the npm package to `token-studio` with `npx token-studio demo` as the primary public entry.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-studio",
3
- "version": "4.8.0",
3
+ "version": "4.8.1",
4
4
  "description": "Local AI coding ROI studio for private token, output, and model strategy review.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -4,10 +4,10 @@ import { spawn } from 'node:child_process';
4
4
  import { createInterface } from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
6
  import { createServer } from 'node:net';
7
- import { existsSync } from 'node:fs';
8
7
  import { dirname, resolve } from 'node:path';
9
8
  import { hostname } from 'node:os';
10
9
  import { fileURLToPath } from 'node:url';
10
+ import { createRequire } from 'node:module';
11
11
  import { seedDemoDatabase } from './demo-seed.mjs';
12
12
  import { auditExperimentalCollectors, detectCollectors } from './collector-registry.mjs';
13
13
  import { CCUSAGE_CLI_REPORTS, ccusageInvocation, runCcusageCliImportPlan } from './ccusage-bridge.mjs';
@@ -17,12 +17,14 @@ import { formatPrivacyCheckReport, runPrivacyCheck } from './privacy-check.mjs';
17
17
  import { buildTerminalReport, formatTerminalReport } from './terminal-report.mjs';
18
18
  import { buildEmptyStatuslineSnapshot, buildStatuslineSnapshot, formatStatuslineText } from './statusline.mjs';
19
19
  import { buildModelPolicy, formatModelPolicy } from './model-policy.mjs';
20
+ import { resolveViteBin } from './runtime-paths.mjs';
20
21
 
21
22
  const command = process.argv[2] || 'help';
22
23
  const args = parseArgs(process.argv.slice(3));
23
24
  const SOURCE_DIR = dirname(fileURLToPath(import.meta.url));
24
25
  const PACKAGE_ROOT = resolve(SOURCE_DIR, '..');
25
26
  const USER_CWD = process.cwd();
27
+ const requireFromCli = createRequire(import.meta.url);
26
28
 
27
29
  try {
28
30
  if (command === 'start') {
@@ -81,10 +83,7 @@ async function startCommand({ demo = false, dbPath = null, route = '/', openBrow
81
83
  DB_PATH: dbPath || resolve(USER_CWD, args.db || process.env.DB_PATH || 'data/usage.sqlite'),
82
84
  TOKEN_STUDIO_DEMO_MODE: demo ? '1' : process.env.TOKEN_STUDIO_DEMO_MODE || ''
83
85
  };
84
- const viteBin = resolve(PACKAGE_ROOT, 'node_modules', 'vite', 'bin', 'vite.js');
85
- if (!existsSync(viteBin)) {
86
- throw new Error('Vite is not installed. Run npm install first, then retry token-studio start.');
87
- }
86
+ const viteBin = resolveViteBin({ packageRoot: PACKAGE_ROOT, requireLike: requireFromCli });
88
87
  const server = spawn(process.execPath, [resolve(SOURCE_DIR, 'server.mjs')], {
89
88
  cwd: PACKAGE_ROOT,
90
89
  env,
@@ -0,0 +1,25 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ export function resolveViteBin({ packageRoot, requireLike } = {}) {
5
+ const candidates = [];
6
+
7
+ try {
8
+ const vitePackageJson = requireLike?.resolve?.('vite/package.json');
9
+ if (vitePackageJson) {
10
+ candidates.push(resolve(dirname(vitePackageJson), 'bin', 'vite.js'));
11
+ }
12
+ } catch {
13
+ // Fall back to the direct local checkout layout below.
14
+ }
15
+
16
+ if (packageRoot) {
17
+ candidates.push(resolve(packageRoot, 'node_modules', 'vite', 'bin', 'vite.js'));
18
+ }
19
+
20
+ const found = candidates.find(candidate => existsSync(candidate));
21
+ if (!found) {
22
+ throw new Error('Vite is not installed. Reinstall token-studio or run npm install in the source checkout, then retry token-studio start.');
23
+ }
24
+ return found;
25
+ }
@@ -0,0 +1,54 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { resolveViteBin } from '../src/runtime-paths.mjs';
7
+
8
+ test('resolveViteBin handles npm npx hoisted dependency layout', () => {
9
+ const root = join(tmpdir(), `token-studio-runtime-${Date.now()}-${Math.random().toString(16).slice(2)}`);
10
+ const packageRoot = join(root, 'node_modules', 'token-studio');
11
+ const viteRoot = join(root, 'node_modules', 'vite');
12
+ const viteBin = join(viteRoot, 'bin', 'vite.js');
13
+ mkdirSync(join(packageRoot, 'src'), { recursive: true });
14
+ mkdirSync(join(viteRoot, 'bin'), { recursive: true });
15
+ writeFileSync(join(viteRoot, 'package.json'), '{"name":"vite"}\n');
16
+ writeFileSync(viteBin, '#!/usr/bin/env node\n');
17
+
18
+ try {
19
+ const resolved = resolveViteBin({
20
+ packageRoot,
21
+ requireLike: {
22
+ resolve(id) {
23
+ assert.equal(id, 'vite/package.json');
24
+ return join(viteRoot, 'package.json');
25
+ }
26
+ }
27
+ });
28
+ assert.equal(resolved, viteBin);
29
+ } finally {
30
+ rmSync(root, { recursive: true, force: true });
31
+ }
32
+ });
33
+
34
+ test('resolveViteBin keeps local source checkout fallback', () => {
35
+ const root = join(tmpdir(), `token-studio-runtime-${Date.now()}-${Math.random().toString(16).slice(2)}`);
36
+ const packageRoot = join(root, 'token-studio');
37
+ const viteBin = join(packageRoot, 'node_modules', 'vite', 'bin', 'vite.js');
38
+ mkdirSync(join(packageRoot, 'node_modules', 'vite', 'bin'), { recursive: true });
39
+ writeFileSync(viteBin, '#!/usr/bin/env node\n');
40
+
41
+ try {
42
+ const resolved = resolveViteBin({
43
+ packageRoot,
44
+ requireLike: {
45
+ resolve() {
46
+ throw new Error('not found');
47
+ }
48
+ }
49
+ });
50
+ assert.equal(resolved, viteBin);
51
+ } finally {
52
+ rmSync(root, { recursive: true, force: true });
53
+ }
54
+ });