pinokiod 8.2.10 → 8.2.12

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.
@@ -7,7 +7,7 @@ const { spawnSync } = require('node:child_process')
7
7
 
8
8
  const ShellRunTemplate = require('../kernel/api/shell_run_template')
9
9
  const Shell = require('../kernel/shell')
10
- const { PIP_INSTALL_COMMAND } = require('../kernel/bin/conda-pins')
10
+ const { PIP_SEED_SCRIPT, PIP_WHEEL } = require('../kernel/bin/conda-pins')
11
11
 
12
12
  function createKernel(root = process.cwd()) {
13
13
  return {
@@ -399,11 +399,11 @@ test('redactEnvArgs summarizes protected argv env values', () => {
399
399
  })
400
400
 
401
401
 
402
- test('only new default venvs bootstrap pip across supported shells', async t => {
402
+ test('venv selection preserves legacy activation without a per-venv pip install', async t => {
403
403
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-venv-selection-'))
404
404
  t.after(() => fs.rm(root, { recursive: true, force: true }))
405
405
  const cases = [
406
- { venv: "default env $NAME O'Brien (test)" },
406
+ { venv: 'default-env' },
407
407
  { venv: { path: 'object-default-env' } },
408
408
  { venv: 'explicit-env', venv_python: '3.10', version: '3.10' },
409
409
  { venv: { path: 'object-env', python: '3.11' }, version: '3.11' },
@@ -413,26 +413,20 @@ test('only new default venvs bootstrap pip across supported shells', async t =>
413
413
  ]
414
414
  await fs.mkdir(path.join(root, 'existing-env'))
415
415
  for (const [platform, shellName] of [
416
- ['linux', 'bash'],
417
- ['darwin', 'bash'],
418
- ['win32', 'cmd.exe'],
419
- ['win32', 'powershell'],
420
- ['win32', 'bash'],
416
+ ['linux', 'bash'], ['darwin', 'bash'], ['win32', 'cmd.exe'],
417
+ ['win32', 'powershell'], ['win32', 'bash'],
421
418
  ]) {
422
419
  for (const scenario of cases) {
423
420
  const shell = createShell(createKernel(root))
424
421
  shell.platform = platform
425
422
  shell.shell = shellName
426
423
  const params = await shell.activate({
427
- path: root,
428
- conda: { skip: true },
429
- venv: scenario.venv,
430
- venv_python: scenario.venv_python,
431
- message: 'echo PINOKIO_REQUIREMENTS_RAN',
424
+ path: root, conda: { skip: true }, venv: scenario.venv,
425
+ venv_python: scenario.venv_python, message: 'echo PINOKIO_REQUIREMENTS_RAN',
432
426
  })
433
427
  const command = shell.build(params)
434
- assert.doesNotMatch(command, /--seed|--no-config|--python-preference|uv pip install/)
435
- assert.match(command, /echo PINOKIO_REQUIREMENTS_RAN$/)
428
+ assert.match(command, /echo PINOKIO_REQUIREMENTS_RAN/)
429
+ assert.doesNotMatch(command, /--seed|--no-config|--python-preference|uv pip install|-m pip|https:|sha256|repair_pip_seed|\bCALL\s|\bexit\s/)
436
430
  if (!scenario.venv || scenario.existing) {
437
431
  assert.doesNotMatch(command, /python -m venv|uv venv/)
438
432
  } else if (scenario.version) {
@@ -441,28 +435,17 @@ test('only new default venvs bootstrap pip across supported shells', async t =>
441
435
  } else {
442
436
  assert.match(command, /python -m venv /)
443
437
  }
444
- if (scenario.venv && !scenario.version && !scenario.existing) {
445
- assert.equal(command.split(PIP_INSTALL_COMMAND).length - 1, 1)
446
- assert.ok(command.indexOf(PIP_INSTALL_COMMAND) > command.lastIndexOf('activate'))
447
- const envPath = path.resolve(root, typeof scenario.venv === 'string' ? scenario.venv : scenario.venv.path)
448
- const shellPath = shell.isCmdShell() ? `"${envPath}"` : shell.quoteArgForShell(platform === 'win32' && /bash/i.test(shellName) ? require('../kernel/util').p2u(envPath) : envPath)
449
- for (const step of [`python -m venv ${shellPath}`, PIP_INSTALL_COMMAND]) {
450
- assert.ok(command.includes(shell.isPowerShell()
451
- ? `${step}; if ($LASTEXITCODE -ne 0) { exit 1 }`
452
- : `${step} || exit 1`))
453
- }
454
- } else {
455
- assert.doesNotMatch(command, /-m pip/)
456
- }
457
438
  if (scenario.venv) {
458
439
  const envPath = path.resolve(root, typeof scenario.venv === 'string' ? scenario.venv : scenario.venv.path)
459
- const script = path.resolve(envPath, platform === 'win32' ? 'Scripts' : 'bin', shell.isPowerShell() ? 'Activate.ps1' : 'activate')
460
- const scriptPath = shell.isCmdShell() ? `"${script}"` : shell.quoteArgForShell(platform === 'win32' && /bash/i.test(shellName) ? require('../kernel/util').p2u(script) : script)
461
- assert.ok(command.includes(shell.isPowerShell()
462
- ? `& ${scriptPath}`
463
- : shell.isCmdShell() ? scriptPath : `source ${scriptPath}`), command)
464
- assert.ok(command.includes(shell.isPowerShell() ? '; if (!$?) { exit 1 }' : ' || exit 1'), command)
465
- if (shell.isPowerShell()) assert.doesNotMatch(command, /Scripts[\\/]deactivate/)
440
+ const script = path.resolve(envPath, platform === 'win32' ? 'Scripts' : 'bin', 'activate')
441
+ const windowsBash = platform === 'win32' && /bash/i.test(shellName)
442
+ const scriptPath = windowsBash ? `"${require('../kernel/util').p2u(script)}"` : script
443
+ const target = windowsBash ? `"${require('../kernel/util').p2u(envPath)}"` : envPath
444
+ const activation = `${platform !== 'win32' || windowsBash ? 'source ' : ''}${scriptPath} ${target}`
445
+ assert.ok(command.includes(activation), command)
446
+ assert.equal(command.split(scriptPath).length - 1, scenario.version && !scenario.existing ? 2 : 1, command)
447
+ if (!scenario.version) assert.doesNotMatch(command, /\bdeactivate\b/i)
448
+ assert.equal(params.message[1], 'echo PINOKIO_REQUIREMENTS_RAN')
466
449
  }
467
450
  assert.equal(shell.env.UV_PYTHON_PREFERENCE, 'only-managed')
468
451
  }
@@ -470,128 +453,152 @@ test('only new default venvs bootstrap pip across supported shells', async t =>
470
453
  })
471
454
 
472
455
 
473
- test('failed venv creation or pip bootstrap stops an unconditional command chain', async t => {
474
- if (process.platform === 'win32') return t.skip('executes the POSIX shell failure guard')
475
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-pip-failure-'))
476
- t.after(() => fs.rm(root, { recursive: true, force: true }))
477
- const binDir = path.join(root, 'bin')
478
- await fs.mkdir(binDir)
479
- await fs.writeFile(path.join(binDir, 'python'), `#!/bin/sh
480
- if [ "$1" = "-m" ] && [ "$2" = "venv" ]; then
481
- [ "$PINOKIO_TEST_VENV_EXIT_CODE" = "0" ] || exit "$PINOKIO_TEST_VENV_EXIT_CODE"
482
- mkdir -p "$3/bin"
483
- echo 'deactivate() { :; }' > "$3/bin/activate"
484
- else
485
- echo PINOKIO_PIP_BOOTSTRAP_RAN
486
- exit "$PINOKIO_TEST_PIP_EXIT_CODE"
487
- fi
488
- `, { mode: 0o755 })
489
- const shell = createShell(createKernel(root))
490
- shell.shell = 'bash'
491
- const params = await shell.activate({
492
- path: root, conda: { skip: true }, venv: 'env',
493
- chain: '*', message: 'echo PINOKIO_APPLICATION_RAN',
494
- })
495
- const command = shell.build(params)
496
- for (const [venvExit, pipExit] of [[9, 0], [0, 7], [0, 0]]) {
497
- const result = spawnSync('/bin/bash', ['-c', command], {
498
- encoding: 'utf8',
499
- env: { ...process.env, PATH: `${binDir}${path.delimiter}${process.env.PATH}`, PINOKIO_TEST_VENV_EXIT_CODE: String(venvExit), PINOKIO_TEST_PIP_EXIT_CODE: String(pipExit) },
500
- })
501
- assert.equal(result.status, venvExit || pipExit ? 1 : 0, result.stderr)
502
- assert.equal(result.stdout.includes('PINOKIO_APPLICATION_RAN'), venvExit === 0 && pipExit === 0)
503
- assert.equal(result.stdout.includes('PINOKIO_PIP_BOOTSTRAP_RAN'), venvExit === 0)
504
- }
505
- })
506
456
 
507
457
 
508
- test('venv paths remain literal through creation and repeated activation', async t => {
509
- if (process.platform === 'win32') return t.skip('executes bash commands')
510
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-venv-paths-'))
458
+
459
+
460
+
461
+ test('only new stdlib venvs under custom Conda prefixes run the local seed repair', async t => {
462
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-custom-seed-'))
511
463
  t.after(() => fs.rm(root, { recursive: true, force: true }))
512
- const bin = path.join(root, 'bin')
513
- const project = path.join(root, "project & $NAME O'Brien (test)")
514
- await fs.mkdir(bin)
515
- await fs.mkdir(project)
516
- const fixture = path.join(root, 'activate')
517
- await fs.writeFile(fixture, 'export VIRTUAL_ENV="$PINOKIO_TEST_ENV"\ndeactivate() { unset VIRTUAL_ENV; }\n')
518
- const executable = `#!/bin/sh
519
- if [ "$1" = "-m" ] && [ "$2" = "venv" ]; then
520
- [ "$#" = "3" ] || exit 9
521
- target=$3
522
- elif [ "$1" = "venv" ]; then
523
- [ "$#" = "4" ] || exit 9
524
- target=$2
525
- else
526
- echo "PINOKIO_BOOTSTRAP_TARGET=$VIRTUAL_ENV"
527
- exit 0
528
- fi
529
- printf '%s' "$target" > "$PINOKIO_TEST_CREATED"
530
- mkdir -p "$target/bin"
531
- cp "$PINOKIO_TEST_ACTIVATE" "$target/bin/activate"
532
- `
533
- await fs.writeFile(path.join(bin, 'python'), executable, { mode: 0o755 })
534
- await fs.writeFile(path.join(bin, 'uv'), executable, { mode: 0o755 })
535
- for (const explicit of [false, true]) {
536
- const venv = explicit ? 'uv env $NAME' : 'default env $NAME'
537
- const envPath = path.join(project, venv)
538
- const created = path.join(root, 'created')
539
- for (const existing of [false, true]) {
540
- const shell = createShell(createKernel(root))
541
- shell.shell = 'bash'
542
- const params = await shell.activate({
543
- path: project, conda: { skip: true }, venv,
544
- ...(explicit ? { venv_python: '3.10' } : {}),
545
- chain: '*', message: 'printf "PINOKIO_APP_TARGET=%s\\n" "$VIRTUAL_ENV"',
546
- })
547
- const result = spawnSync('/bin/bash', ['-c', shell.build(params)], {
548
- encoding: 'utf8', env: {
549
- ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`,
550
- VIRTUAL_ENV: '', NAME: 'must-not-expand',
551
- PINOKIO_TEST_ENV: envPath, PINOKIO_TEST_CREATED: created, PINOKIO_TEST_ACTIVATE: fixture,
552
- },
553
- })
554
- assert.equal(result.status, 0, result.stdout + result.stderr)
555
- assert.equal(await fs.readFile(created, 'utf8'), envPath)
556
- assert.ok(result.stdout.includes(`PINOKIO_APP_TARGET=${envPath}\n`), result.stdout)
557
- assert.equal(result.stdout.includes(`PINOKIO_BOOTSTRAP_TARGET=${envPath}\n`), !explicit && !existing)
464
+ await fs.mkdir(path.join(root, 'existing-env'))
465
+ for (const [platform, shellName] of [['darwin', 'bash'], ['linux', 'bash'], ['win32', 'cmd.exe'], ['win32', 'powershell'], ['win32', 'bash']]) {
466
+ for (const [conda, expected] of [[undefined, false], [{ name: 'base' }, false],
467
+ ['bin/miniforge', false], [{ skip: true }, false], [{ name: 'custom' }, true],
468
+ [{ path: "custom O'Brien & (test)" }, true], ['custom', true],
469
+ [{ name: 'custom', activate: 'minimal' }, false], [{ name: 'custom', activate: ['custom activation'] }, false]]) {
470
+ for (const kind of ['new', 'existing', 'uv']) {
471
+ const kernel = createKernel(root)
472
+ kernel.bin = { path: (...parts) => path.join(root, 'bin', ...parts) }
473
+ const shell = createShell(kernel)
474
+ shell.platform = platform
475
+ shell.shell = shellName
476
+ const params = await shell.activate({ path: root, conda,
477
+ venv: kind === 'existing' ? 'existing-env' : 'env',
478
+ ...(kind === 'uv' ? { venv_python: '3.11' } : {}), message: 'echo PINOKIO_APPLICATION_RAN' })
479
+ const command = shell.build(params)
480
+ const repair = expected && kind === 'new'
481
+ assert.equal(command.includes('repair_pip_seed.py'), repair, command)
482
+ assert.doesNotMatch(command, /https:|sha256|-m pip/)
483
+ if (repair) {
484
+ assert.ok(command.indexOf('repair_pip_seed.py') < command.indexOf('python -m venv'), command)
485
+ assert.ok(command.includes(PIP_WHEEL.filename), command)
486
+ if (shell.isCmdShell()) assert.doesNotMatch(command.slice(command.indexOf('python -I -B')), /\^/)
487
+ assert.equal((command.match(/\bexit 1\b/g) || []).length, shell.isCmdShell() || shell.isPowerShell() ? 1 : 0, command)
488
+ }
489
+ }
558
490
  }
559
491
  }
560
492
  })
561
493
 
562
- test('failed activation stops before pip or application commands', async t => {
563
- if (process.platform === 'win32') return t.skip('executes bash commands')
564
- const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-venv-activation-'))
494
+ test('custom Conda repair failure is retried after the Conda prefix already exists', async t => {
495
+ if (process.platform === 'win32') return t.skip('executes bash command chains')
496
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-custom-seed-retry-'))
565
497
  t.after(() => fs.rm(root, { recursive: true, force: true }))
566
498
  const bin = path.join(root, 'bin')
567
499
  await fs.mkdir(bin)
500
+ const trace = path.join(root, 'trace')
501
+ const prefix = path.join(root, "custom O'Brien & (test)")
568
502
  await fs.writeFile(path.join(bin, 'python'), `#!/bin/sh
569
- if [ "$1" = "-m" ] && [ "$2" = "venv" ]; then
503
+ if [ "$1" = "-I" ]; then
504
+ [ "$#" = "5" ] && [ "$5" = "$PINOKIO_TEST_PREFIX" ] || exit 9
505
+ echo repair >> "$PINOKIO_TEST_TRACE"
506
+ exit "$PINOKIO_TEST_REPAIR_EXIT"
507
+ elif [ "$1" = "-m" ] && [ "$2" = "venv" ]; then
508
+ echo create >> "$PINOKIO_TEST_TRACE"
570
509
  mkdir -p "$3/bin"
571
- if [ "$PINOKIO_TEST_FAILURE" = "second" ]; then
572
- echo 'deactivate() { rm -f "$PINOKIO_TEST_ENV/bin/activate"; }' > "$3/bin/activate"
573
- fi
510
+ echo 'deactivate() { :; }' > "$3/bin/activate"
574
511
  else
575
- echo PINOKIO_BOOTSTRAP_RAN
512
+ exit 9
576
513
  fi
577
514
  `, { mode: 0o755 })
578
- for (const failure of ['first', 'second', 'existing']) {
579
- const venv = `${failure} env`
580
- if (failure === 'existing') await fs.mkdir(path.join(root, venv))
581
- const shell = createShell(createKernel(root))
515
+ for (const failure of [true, false]) {
516
+ const kernel = createKernel(root)
517
+ kernel.bin = { path: (...parts) => path.join(bin, ...parts) }
518
+ const shell = createShell(kernel)
582
519
  shell.shell = 'bash'
583
- const params = await shell.activate({
584
- path: root, conda: { skip: true }, venv,
585
- chain: '*', message: 'echo PINOKIO_APPLICATION_RAN',
520
+ shell.conda_hook = () => ''
521
+ const params = await shell.activate({ path: root, conda: { path: path.basename(prefix) }, venv: 'env',
522
+ chain: '*', message: 'echo PINOKIO_APPLICATION_RAN' })
523
+ assert.equal(shell.build(params).includes('conda create'), failure)
524
+ const result = spawnSync('/bin/bash', ['-c', `conda() { if [ "$1" = create ]; then mkdir -p "$4"; fi; }\n${shell.build(params)}`], {
525
+ encoding: 'utf8', env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`,
526
+ PINOKIO_TEST_REPAIR_EXIT: failure ? '7' : '0', PINOKIO_TEST_TRACE: trace, PINOKIO_TEST_PREFIX: prefix },
586
527
  })
587
- const result = spawnSync('/bin/bash', ['-c', shell.build(params)], {
588
- encoding: 'utf8', env: {
589
- ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`,
590
- PINOKIO_TEST_FAILURE: failure, PINOKIO_TEST_ENV: path.join(root, venv),
591
- },
528
+ assert.equal(result.status, failure ? 7 : 0, result.stdout + result.stderr)
529
+ assert.equal(result.stdout.includes('PINOKIO_APPLICATION_RAN'), !failure)
530
+ assert.equal(await fs.access(path.join(root, 'env')).then(() => true, () => false), !failure)
531
+ }
532
+ assert.equal(await fs.readFile(trace, 'utf8'), 'repair\nrepair\ncreate\n')
533
+ })
534
+
535
+
536
+
537
+
538
+
539
+ test('uv retains its existing creation and deactivation failure behavior', async t => {
540
+ if (process.platform === 'win32') return t.skip('executes bash commands')
541
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-setup-uv-'))
542
+ t.after(() => fs.rm(root, { recursive: true, force: true }))
543
+ await fs.mkdir(path.join(root, 'bin'))
544
+ await fs.writeFile(path.join(root, 'bin', 'uv'), `#!/bin/sh
545
+ mkdir -p "$2/bin"
546
+ echo 'deactivate() { return 7; }' > "$2/bin/activate"
547
+ exit 7
548
+ `, { mode: 0o755 })
549
+ const shell = createShell(createKernel(root))
550
+ shell.shell = 'bash'
551
+ const params = await shell.activate({ path: root, conda: { skip: true }, venv: 'env', venv_python: '3.11',
552
+ message: 'echo APP' })
553
+ const result = spawnSync('/bin/bash', ['-c', shell.build(params)], {
554
+ encoding: 'utf8', env: { ...process.env, PATH: `${path.join(root, 'bin')}${path.delimiter}${process.env.PATH}` },
555
+ })
556
+ assert.equal(result.status, 0, result.stderr)
557
+ assert.equal(result.stdout, 'APP\n')
558
+ })
559
+
560
+ test('failed custom seed repair stops venv creation and all app commands', async t => {
561
+ const powershell = process.env.PINOKIO_TEST_PWSH || (process.platform === 'win32' ? 'powershell.exe' : null)
562
+ for (const [name, executable] of [
563
+ ['bash', process.platform !== 'win32' ? '/bin/bash' : null],
564
+ ['CMD', process.platform === 'win32' ? 'cmd.exe' : null],
565
+ ['PowerShell', powershell],
566
+ ]) {
567
+ await t.test(name, { skip: !executable && `requires ${name}` }, async t => {
568
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'pinokio-seed-stop-'))
569
+ t.after(() => fs.rm(root, { recursive: true, force: true }))
570
+ const bin = path.join(root, 'bin')
571
+ const trace = path.join(root, 'trace')
572
+ await fs.mkdir(bin)
573
+ await fs.mkdir(path.join(root, 'custom'))
574
+ await fs.writeFile(path.join(bin, process.platform === 'win32' ? 'python.bat' : 'python'),
575
+ process.platform === 'win32'
576
+ ? '@echo %1>>"%PINOKIO_TEST_TRACE%"\r\n@exit /b 7\r\n'
577
+ : '#!/bin/sh\nprintf "%s\\n" "$1" >> "$PINOKIO_TEST_TRACE"\nexit 7\n', { mode: 0o755 })
578
+ if (name === 'CMD') await fs.writeFile(path.join(bin, 'conda.bat'), '@exit /b 0\r\n')
579
+ const chains = name === 'PowerShell' ? ['*'] : [undefined, '&', '|', '*']
580
+ for (const chain of chains) {
581
+ const kernel = createKernel(root)
582
+ kernel.bin = { path: (...parts) => path.join(bin, ...parts) }
583
+ const shell = createShell(kernel)
584
+ shell.shell = executable
585
+ shell.conda_hook = () => ''
586
+ const message = name === 'PowerShell' ? 'Write-Output FIRST; Write-Output SECOND'
587
+ : name === 'CMD' ? 'echo FIRST & echo SECOND' : 'echo FIRST; echo SECOND'
588
+ const params = await shell.activate({ path: root, conda: { path: 'custom' }, venv: 'env', chain, message })
589
+ const command = (name === 'bash' ? 'conda() { :; }\n' : name === 'PowerShell' ? 'function conda {}\n' : '') + shell.build(params)
590
+ const result = spawnSync(executable, name === 'CMD' ? ['/D', '/Q']
591
+ : name === 'PowerShell' ? ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', command] : ['-c', command], {
592
+ ...(name === 'CMD' ? { input: `${command}\r\nexit\r\n` } : {}),
593
+ cwd: root, encoding: 'utf8', timeout: 15000,
594
+ env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}`, PINOKIO_TEST_TRACE: trace },
595
+ })
596
+ assert.ifError(result.error)
597
+ assert.equal(result.status, name === 'bash' ? 7 : 1, result.stdout + result.stderr)
598
+ assert.doesNotMatch(result.stdout, /FIRST|SECOND/)
599
+ assert.equal(await fs.access(path.join(root, 'env')).then(() => true, () => false), false)
600
+ }
601
+ assert.deepEqual((await fs.readFile(trace, 'utf8')).trim().split(/\r?\n/), chains.map(() => '-I'))
592
602
  })
593
- assert.equal(result.status, 1, `${failure}: ${result.stdout}${result.stderr}`)
594
- assert.equal(result.stdout.includes('PINOKIO_BOOTSTRAP_RAN'), false, failure)
595
- assert.equal(result.stdout.includes('PINOKIO_APPLICATION_RAN'), false, failure)
596
603
  }
597
604
  })
@@ -0,0 +1,239 @@
1
+ const assert = require('node:assert/strict')
2
+ const test = require('node:test')
3
+ const http = require('node:http')
4
+ const fs = require('node:fs')
5
+ const os = require('node:os')
6
+ const path = require('node:path')
7
+ const vm = require('node:vm')
8
+ const { EventEmitter } = require('node:events')
9
+ const TailscaleCLI = require('../kernel/anywhere/cli')
10
+ const Anywhere = require('../kernel/anywhere')
11
+ const Tailscaled = require('../kernel/bin/tailscaled')
12
+
13
+ test('CLI login delivers each streamed update once and rejects output-handler failures', async () => {
14
+ const source = fs.readFileSync(require.resolve('../kernel/anywhere/cli'), 'utf8')
15
+ const cliRequire = require('node:module').createRequire(require.resolve('../kernel/anywhere/cli'))
16
+ for (const failure of [null, 'json', 'callback']) {
17
+ let finish, killed = false
18
+ const child = { stdout: new EventEmitter(), kill() { killed = true } }
19
+ const module = { exports: {} }
20
+ vm.runInNewContext(source, { module, require(name) {
21
+ if (name !== 'child_process') return cliRequire(name)
22
+ return { execFile(file, args, options, callback) { finish = callback; return child } }
23
+ } })
24
+ const cli = new module.exports({ binPath: 'fixture', socketPath: 'fixture' })
25
+ const seen = []
26
+ let updates = 0
27
+ const result = cli.up(value => {
28
+ updates++
29
+ if (failure === 'callback') throw new Error('handler failed')
30
+ seen.push(value.AuthURL)
31
+ })
32
+ const checked = failure ? assert.rejects(result, failure === 'json' ? { name: 'SyntaxError' } : /handler failed/) : result
33
+ const first = '{\n "AuthURL": "first"\n}\n'
34
+ const second = '{\n "AuthURL": "second"\n}\n'
35
+ assert.doesNotThrow(() => {
36
+ child.stdout.emit('data', failure === 'json' ? '{\nINVALID JSON\n}\n' : first)
37
+ if (!failure) {
38
+ child.stdout.emit('data', second.slice(0, 10))
39
+ child.stdout.emit('data', second.slice(10))
40
+ } else {
41
+ child.stdout.emit('data', second)
42
+ }
43
+ })
44
+ finish(null, '', '')
45
+ await checked
46
+ assert.equal(killed, !!failure)
47
+ assert.equal(cli.children.size, 0)
48
+ assert.equal(updates, failure === 'json' ? 0 : failure === 'callback' ? 1 : 2)
49
+ if (!failure) assert.deepEqual(seen, ['first', 'second'])
50
+ }
51
+ })
52
+
53
+ // Run against the actual pinned binary on each native platform. No system
54
+ // Tailscale endpoint, account or network settings are used by these tests.
55
+ const binPath = process.env.PINOKIO_TEST_TAILSCALE
56
+ test('pinned CLI: login, approvals, conditional Serve writes, failures and cancellation', { skip: !binPath, timeout: 30000 }, async () => {
57
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-cli-'))
58
+ const socketPath = process.platform === 'win32' ? `\\\\.\\pipe\\pinokio-test-${process.pid}` : path.join(tmp, 'api.sock')
59
+ const seen = []
60
+ let config = null
61
+ let revision = 1
62
+ let conflict = false
63
+ let failure = 0
64
+ let bus = null
65
+ let loginNumber = 0
66
+ let status = {
67
+ BackendState: 'Running', Version: '1.104.1', TailscaleIPs: ['100.100.100.100'],
68
+ Self: { DNSName: 'pinokio.tail1.ts.net.', TailscaleIPs: ['100.100.100.100'] },
69
+ CertDomains: ['pinokio.tail1.ts.net'], CurrentTailnet: { MagicDNSSuffix: 'tail1.ts.net' },
70
+ }
71
+ const server = http.createServer((req, res) => {
72
+ let body = ''
73
+ req.on('data', (chunk) => { body += chunk })
74
+ req.on('end', () => {
75
+ seen.push({ method: req.method, url: req.url, body, etag: req.headers['if-match'] })
76
+ res.setHeader('Content-Type', 'application/json')
77
+ res.setHeader('Tailscale-Version', '1.104.1')
78
+ if (req.url.startsWith('/localapi/v0/status')) {
79
+ if (failure) { res.statusCode = failure; return res.end('control failure') }
80
+ return res.end(JSON.stringify(status))
81
+ }
82
+ if (req.url === '/localapi/v0/serve-config') {
83
+ if (req.method === 'POST') {
84
+ if (conflict || req.headers['if-match'] !== `v${revision}`) { res.statusCode = 412; return res.end('changed concurrently') }
85
+ config = JSON.parse(body); revision += 1
86
+ return res.end('{}')
87
+ }
88
+ res.setHeader('Etag', `v${revision}`)
89
+ return res.end(JSON.stringify(config))
90
+ }
91
+ if (req.url === '/localapi/v0/query-feature?feature=serve') return res.end(JSON.stringify({ Complete: !!status.CertDomains, URL: 'https://login.tailscale.com/f/serve?node=1', Text: 'Approve HTTPS' }))
92
+ if (req.url === '/localapi/v0/prefs' || req.url === '/localapi/v0/check-prefs') return res.end('{}')
93
+ if (req.url.startsWith('/localapi/v0/watch-ipn-bus')) { bus = res; res.flushHeaders(); return }
94
+ if (req.url === '/localapi/v0/start' || req.url === '/localapi/v0/login-interactive') {
95
+ if (bus && req.url.endsWith('/login-interactive')) bus.write(JSON.stringify({ BrowseToURL: `https://login.tailscale.com/a/fresh${++loginNumber}` }) + '\n')
96
+ res.statusCode = 204; return res.end()
97
+ }
98
+ if (req.url === '/localapi/v0/logout') { status.BackendState = 'NeedsLogin'; res.statusCode = 204; return res.end() }
99
+ res.statusCode = 404; res.end(`unexpected ${req.url}`)
100
+ })
101
+ })
102
+ const connections = new Set()
103
+ server.on('connection', (socket) => { connections.add(socket); socket.on('close', () => connections.delete(socket)) })
104
+ const cli = new TailscaleCLI({ binPath, socketPath })
105
+ try {
106
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(socketPath, resolve) })
107
+ assert.equal((await cli.status()).Version, '1.104.1')
108
+ await cli.setPrefs('pinokio')
109
+ const prefs = JSON.parse(seen.find((r) => r.method === 'PATCH' && r.url.endsWith('/prefs')).body)
110
+ assert.equal(prefs.CorpDNS, false)
111
+ assert.equal(prefs.RouteAll, false)
112
+ assert.equal(prefs.Hostname, 'pinokio')
113
+ status.CertDomains = null
114
+ assert.equal((await cli.queryFeature()).URL, 'https://login.tailscale.com/f/serve?node=1')
115
+ status.CertDomains = ['pinokio.tail1.ts.net']
116
+
117
+ const kernel = {
118
+ platform: process.platform, homedir: tmp, server_port: 42000,
119
+ path: (...parts) => path.join(tmp, ...parts),
120
+ memory: { local: { [path.join(tmp, 'api/comfy/start.js')]: { url: 'http://127.0.0.1:7860' } } },
121
+ bin: { mod: { tailscaled: { installed: async () => true } } },
122
+ }
123
+ const anywhere = new Anywhere(kernel)
124
+ anywhere.readPid = () => 123 // The mock endpoint represents a verified owned daemon.
125
+ anywhere.api = cli
126
+ await anywhere.saveConfig({ enabled: true })
127
+ assert.equal((await anywhere.snapshot()).phase, 'ready', JSON.stringify({ config, seen }))
128
+ assert.equal(config.Web['pinokio.tail1.ts.net:443'].Handlers['/'].Proxy, 'http://127.0.0.1:42000')
129
+ assert.equal(config.Web['pinokio.tail1.ts.net:43000'].Handlers['/'].Proxy, 'http://127.0.0.1:7860')
130
+ assert.equal(config.AllowFunnel, undefined)
131
+ assert.equal(await anywhere.sync(), false, 'unchanged routes do not write')
132
+ const portMap = fs.readFileSync(anywhere.portMapPath(), 'utf8')
133
+ kernel.memory.local[path.join(tmp, 'api/comfy/start.js')].url = 'http://127.0.0.1:7861'
134
+ conflict = true
135
+ assert.equal((await anywhere.snapshot()).phase, 'error')
136
+ assert.equal(anywhere.rewriteHref('pinokio.tail1.ts.net', 'http://127.0.0.1:7861/'), 'http://127.0.0.1:7861/')
137
+ assert.equal(config.Web['pinokio.tail1.ts.net:43000'].Handlers['/'].Proxy, 'http://127.0.0.1:7860')
138
+ conflict = false
139
+ assert.equal((await anywhere.snapshot()).phase, 'ready')
140
+ assert.equal(fs.readFileSync(anywhere.portMapPath(), 'utf8'), portMap)
141
+ assert.equal(config.Web['pinokio.tail1.ts.net:43000'].Handlers['/'].Proxy, 'http://127.0.0.1:7861')
142
+ for (const [origin, expected] of [
143
+ ['https://127.0.0.1', 'https://127.0.0.1'],
144
+ ['https://localhost', 'https://localhost'],
145
+ ['http://[::1]', 'http://[::1]'],
146
+ ['http://0.0.0.0', 'http://127.0.0.1'],
147
+ ]) {
148
+ kernel.memory.local[path.join(tmp, 'api/comfy/start.js')].url = `${origin}:7861/ui/`
149
+ assert.equal((await anywhere.snapshot()).phase, 'ready')
150
+ assert.equal(config.Web['pinokio.tail1.ts.net:43000'].Handlers['/'].Proxy, `${expected}:7861`)
151
+ }
152
+ kernel.memory.local = {}
153
+ await anywhere.sync()
154
+ assert.deepEqual(Object.keys(config.TCP), ['443'])
155
+ assert.ok(seen.filter((r) => r.method === 'POST' && r.url.endsWith('/serve-config')).every((r) => /^v\d+$/.test(r.etag)))
156
+
157
+ for (const code of [401, 403, 503]) {
158
+ failure = code
159
+ await assert.rejects(() => cli.ping(), /control failure/, `HTTP ${code} is not absence`)
160
+ }
161
+ failure = 0
162
+ await cli.logout()
163
+ let gotLink
164
+ const link = new Promise((resolve) => { gotLink = resolve })
165
+ const pending = cli.up((update) => { if (update.AuthURL) gotLink(update.AuthURL) })
166
+ const completed = pending.catch((error) => error)
167
+ assert.equal(await link, 'https://login.tailscale.com/a/fresh1')
168
+ cli.cancel()
169
+ assert.equal((await completed).cancelled, true)
170
+ let gotFresh
171
+ const fresh = new Promise((resolve) => { gotFresh = resolve })
172
+ const retried = cli.up((update) => { if (update.AuthURL) gotFresh(update.AuthURL) }, true)
173
+ const retriedResult = retried.catch((error) => error)
174
+ assert.equal(await fresh, 'https://login.tailscale.com/a/fresh2')
175
+ status.BackendState = 'Running'
176
+ bus.write(JSON.stringify({ State: 6, SelfChange: { Key: 'nodekey:' + '01'.repeat(32) } }) + '\n')
177
+ assert.equal(typeof await retriedResult, 'string')
178
+ } finally {
179
+ cli.cancel()
180
+ for (const socket of connections) socket.destroy()
181
+ await new Promise((resolve) => server.close(resolve))
182
+ fs.rmSync(tmp, { recursive: true, force: true })
183
+ }
184
+ })
185
+
186
+ test('native daemon: setup, simultaneous start, adoption, stop and saved Off', {
187
+ skip: !binPath || !process.env.PINOKIO_TEST_TAILSCALED, timeout: 45000,
188
+ }, async () => {
189
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-native-'))
190
+ const binDir = path.join(tmp, 'managed tools')
191
+ fs.mkdirSync(binDir)
192
+ const cliBin = path.join(binDir, path.basename(binPath))
193
+ const daemonBin = path.join(binDir, path.basename(process.env.PINOKIO_TEST_TAILSCALED))
194
+ fs.copyFileSync(binPath, cliBin)
195
+ fs.copyFileSync(process.env.PINOKIO_TEST_TAILSCALED, daemonBin)
196
+ const mod = new Tailscaled()
197
+ const kernel = {
198
+ platform: process.platform, homedir: tmp, path: (...parts) => path.join(tmp, ...parts),
199
+ bin: { mod: { tailscaled: mod }, installed: { conda: new Set(['tailscaled', 'tailscale']),
200
+ conda_versions: { tailscaled: '1.104.1', tailscale: '1.104.1' } } },
201
+ }
202
+ mod.kernel = kernel
203
+ mod.binPath = () => daemonBin
204
+ mod.cliPath = () => cliBin
205
+ const first = new Anywhere(kernel)
206
+ if (process.platform === 'win32') first._socketPath = `\\\\.\\pipe\\pinokio-native-test-${process.pid}`
207
+ let adopted
208
+ try {
209
+ assert.equal(await mod.installed(), true, 'both actual executable versions match')
210
+ await first.saveConfig({ enabled: true })
211
+ await Promise.all([first.start(), first.start(), first.start()])
212
+ assert.ok(first.child && first.child.pid)
213
+ const pid = first.child.pid
214
+ const statePath = first.stateDir('tailscaled.state')
215
+ // A fresh daemon must show sign-in or the pending CLI flow, never ready.
216
+ const snap = await first.snapshot()
217
+ assert.ok(['starting', 'login'].includes(snap.phase), JSON.stringify(snap))
218
+ const deadline = Date.now() + 15000
219
+ while (!first.login.url && !first.error && Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 100))
220
+ assert.ok(first.login.url, first.error || 'native login did not produce a URL within 15 seconds')
221
+ first.client().cancel()
222
+ if (first.login) await first.login.promise
223
+ adopted = new Anywhere(kernel)
224
+ adopted._socketPath = first.socketPath()
225
+ await adopted.start()
226
+ assert.equal(adopted.child, null, 'a second controller uses the existing endpoint')
227
+ assert.equal(first.child.pid, pid, 'no duplicate daemon')
228
+ assert.ok(fs.existsSync(statePath))
229
+ await adopted.disable()
230
+ assert.equal((await adopted.snapshot()).phase, 'off')
231
+ assert.equal(await adopted.autostart(), false)
232
+ assert.ok(fs.existsSync(statePath), 'disable retains node state')
233
+ assert.equal(await new TailscaleCLI({ binPath: cliBin, socketPath: first.socketPath() }).ping(), false)
234
+ } finally {
235
+ try { first.killSync() } catch (_) { }
236
+ try { if (adopted) adopted.killSync() } catch (_) { }
237
+ fs.rmSync(tmp, { recursive: true, force: true })
238
+ }
239
+ })