• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cameri / nostream / 25601018106

09 May 2026 12:22PM UTC coverage: 33.99% (-31.1%) from 65.107%
25601018106

Pull #615

github

web-flow
Merge 1ef509ec3 into 36e5af87e
Pull Request #615: test: add unit tests for remaining app workers (#489)

788 of 3170 branches covered (24.86%)

Branch coverage included in aggregate %.

0 of 8 new or added lines in 2 files covered. (0.0%)

1822 existing lines in 87 files now uncovered.

2352 of 6068 relevant lines covered (38.76%)

13.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/cli/commands/setup.ts
UNCOV
1
import fs from 'fs'
×
UNCOV
2
import { randomBytes } from 'crypto'
×
UNCOV
3
import { intro, outro, confirm, text, isCancel, cancel } from '@clack/prompts'
×
4

UNCOV
5
import { ensureConfigBootstrap } from '../utils/bootstrap'
×
UNCOV
6
import { getProjectPath } from '../utils/paths'
×
UNCOV
7
import { runStart } from './start'
×
8

9
type SetupOptions = {
10
  yes?: boolean
11
  start?: boolean
12
}
13

UNCOV
14
const SECRET_PLACEHOLDER = 'change_me_to_something_long_and_random'
×
15

UNCOV
16
export const setupPrompts = {
×
17
  intro,
18
  outro,
19
  confirm,
20
  text,
21
  isCancel,
22
  cancel,
23
}
24

25
class SetupCancelledError extends Error {
26
  constructor() {
27
    super('Setup cancelled')
×
28
    this.name = 'SetupCancelledError'
×
29
  }
30
}
31

UNCOV
32
const readEnvSecret = (content: string): string | undefined => {
×
33
  for (const line of content.split(/\r?\n/)) {
×
34
    const trimmed = line.trim()
×
35
    if (!trimmed || trimmed.startsWith('#') || !trimmed.startsWith('SECRET=')) {
×
36
      continue
×
37
    }
38

39
    const [rawValue] = trimmed.slice('SECRET='.length).split('#', 1)
×
40
    return rawValue.trim()
×
41
  }
42

43
  return undefined
×
44
}
45

UNCOV
46
const needsSecretReplacement = (secret: string | undefined): boolean => {
×
47
  return !secret || secret === SECRET_PLACEHOLDER
×
48
}
49

UNCOV
50
const resolveSecret = async (assumeYes: boolean): Promise<string> => {
×
51
  if (process.env.SECRET?.trim()) {
×
52
    return process.env.SECRET.trim()
×
53
  }
54

55
  if (!assumeYes && process.stdin.isTTY) {
×
56
    const value = await setupPrompts.text({
×
57
      message: 'SECRET env var value (hex recommended)',
58
      placeholder: 'openssl rand -hex 128',
59
      validate: (input) => (input.trim() ? undefined : 'SECRET is required'),
×
60
    })
61

62
    if (setupPrompts.isCancel(value)) {
×
63
      setupPrompts.cancel('Setup cancelled')
×
64
      throw new SetupCancelledError()
×
65
    }
66

67
    return value.trim()
×
68
  }
69

70
  return randomBytes(64).toString('hex')
×
71
}
72

UNCOV
73
const upsertSecret = (content: string, secret: string): string => {
×
74
  const normalized = content.length > 0 ? content : ''
×
75
  const lines = normalized.split(/\r?\n/)
×
76
  let replaced = false
×
77

78
  const nextLines = lines.map((line) => {
×
79
    if (replaced) {
×
80
      return line
×
81
    }
82

83
    const trimmed = line.trim()
×
84
    if (!trimmed.startsWith('SECRET=') || trimmed.startsWith('#')) {
×
85
      return line
×
86
    }
87

88
    replaced = true
×
89
    const commentIndex = line.indexOf('#')
×
90
    const commentSuffix = commentIndex >= 0 ? line.slice(commentIndex).trimEnd() : ''
×
91
    return commentSuffix ? `SECRET=${secret} ${commentSuffix}` : `SECRET=${secret}`
×
92
  })
93

94
  if (!replaced) {
×
95
    if (nextLines.length > 0 && nextLines[nextLines.length - 1] !== '') {
×
96
      nextLines.push(`SECRET=${secret}`)
×
97
    } else if (nextLines.length === 0) {
×
98
      nextLines.push(`SECRET=${secret}`)
×
99
    } else {
100
      nextLines[nextLines.length - 1] = `SECRET=${secret}`
×
101
      nextLines.push('')
×
102
    }
103
  }
104

105
  return nextLines.join('\n')
×
106
}
107

UNCOV
108
const ensureEnvFile = async (assumeYes: boolean): Promise<boolean> => {
×
109
  const envPath = getProjectPath('.env')
×
110
  const envExamplePath = getProjectPath('.env.example')
×
111

112
  if (!fs.existsSync(envPath)) {
×
113
    if (fs.existsSync(envExamplePath)) {
×
114
      fs.copyFileSync(envExamplePath, envPath)
×
115
    } else {
116
      fs.writeFileSync(envPath, '', 'utf-8')
×
117
    }
118
  }
119

120
  const current = fs.readFileSync(envPath, 'utf-8')
×
121

122
  if (!needsSecretReplacement(readEnvSecret(current))) {
×
123
    return true
×
124
  }
125

126
  let secret: string
127
  try {
×
128
    secret = await resolveSecret(assumeYes)
×
129
  } catch (error) {
130
    if (error instanceof SetupCancelledError) {
×
131
      return false
×
132
    }
133
    throw error
×
134
  }
135

136
  fs.writeFileSync(envPath, upsertSecret(current, secret), 'utf-8')
×
137
  return true
×
138
}
139

UNCOV
140
export const runSetup = async (options: SetupOptions): Promise<number> => {
×
141
  setupPrompts.intro('Nostream setup')
×
142

143
  ensureConfigBootstrap()
×
144
  const shouldContinue = await ensureEnvFile(Boolean(options.yes))
×
145
  if (!shouldContinue) {
×
146
    return 1
×
147
  }
148

149
  let shouldStart = Boolean(options.start)
×
150

151
  if (!options.yes && !options.start && process.stdin.isTTY) {
×
152
    const answer = await setupPrompts.confirm({ message: 'Start relay now?', initialValue: true })
×
153
    if (setupPrompts.isCancel(answer)) {
×
154
      setupPrompts.cancel('Setup cancelled')
×
155
      return 1
×
156
    }
157

158
    shouldStart = answer
×
159
  }
160

161
  if (shouldStart) {
×
162
    const code = await runStart({}, [])
×
163
    setupPrompts.outro(code === 0 ? 'Setup complete' : 'Setup finished with errors')
×
164
    return code
×
165
  }
166

167
  setupPrompts.outro('Setup complete')
×
168
  return 0
×
169
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc