Skip to content

Commit bef7069

Browse files
authored
Merge pull request #72 from 1012839419a-alt/feat/dev-toolchain
chore(dev): dev environment doctor + standardized dev commands + CI uv migration
2 parents fc01007 + 701ca8f commit bef7069

2 files changed

Lines changed: 81 additions & 15 deletions

File tree

‎scripts/dev-environment.mjs‎

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const root = process.cwd()
66
const args = process.argv.slice(2)
77
const envFileArg = args.find((arg) => arg.startsWith('--env-file='))
88
const profilesArg = args.find((arg) => arg.startsWith('--profiles='))
9+
const skipTools = args.includes('--skip-tools')
910
const envFile = path.resolve(root, envFileArg?.slice('--env-file='.length) || '.env')
1011

1112
function parseEnv(file) {
@@ -95,11 +96,24 @@ const rules = {
9596
for (const name of ['POSTGRES_DB', 'POSTGRES_USER', 'POSTGRES_PASSWORD']) requireValue(name, 'postgres')
9697
},
9798
agent() {
98-
requireValue('CENTRAL_API_URL', 'agent')
99-
requireValue('API_AUTH_TOKEN', 'agent')
100-
const registration = value('AGENT_REGISTER') || 'http'
101-
if (!['http', 'ws'].includes(registration)) errors.push('[agent] AGENT_REGISTER must be http or ws')
102-
if (registration === 'http') requireValue('AGENT_ADVERTISE_URL', 'agent')
99+
const registration = (value('AGENT_REGISTER') || 'http').toLowerCase()
100+
if (!['http', 'ws', 'off'].includes(registration)) {
101+
errors.push('[agent] AGENT_REGISTER must be http, ws, or off')
102+
return
103+
}
104+
if (registration === 'off') return
105+
106+
const agentToken = value('AGENT_API_TOKEN') || value('API_AUTH_TOKEN')
107+
if (!agentToken) errors.push('[agent] configure AGENT_API_TOKEN or API_AUTH_TOKEN')
108+
109+
const centralApiUrl = value('CENTRAL_API_URL')
110+
if (!centralApiUrl) {
111+
notes.push('[agent] CENTRAL_API_URL is empty; auto-registration is disabled')
112+
return
113+
}
114+
if (!value('AGENT_ADVERTISE_URL')) {
115+
notes.push('[agent] AGENT_ADVERTISE_URL is empty; the agent URL will be auto-detected')
116+
}
103117
},
104118
'embedded-chrome'() {
105119
requireExact('CHROME_SUFFIX', '-chrome', 'embedded-chrome')
@@ -133,9 +147,15 @@ const expectedNode = existsSync(path.join(root, '.nvmrc')) ? readFileSync(path.j
133147
if (expectedNode && nodeVersion.split('.')[0] !== expectedNode.split('.')[0]) {
134148
errors.push(`[tools] Node ${expectedNode}.x required; active version is ${nodeVersion}`)
135149
}
136-
checkCommand('uv', ['--version'], 'uv')
137-
checkCommand('uv', ['lock', '--check'], 'uv lock')
138-
checkCommand('docker', ['compose', '--env-file', envFile, '-f', 'docker-compose.yml', '-f', 'docker-compose.build.yml', 'config', '--quiet'], 'Docker Compose configuration')
150+
if (!skipTools) {
151+
checkCommand('uv', ['--version'], 'uv')
152+
checkCommand('uv', ['lock', '--check'], 'uv lock')
153+
checkCommand(
154+
'docker',
155+
['compose', '--env-file', envFile, '-f', 'docker-compose.yml', '-f', 'docker-compose.build.yml', 'config', '--quiet'],
156+
'Docker Compose configuration',
157+
)
158+
}
139159

140160
for (const note of notes) console.log(`NOTE ${note}`)
141161
if (errors.length) {

‎scripts/dev-environment.test.mjs‎

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,75 @@ function envFile(contents) {
1616
return file
1717
}
1818

19+
function doctorArgs(file, ...extraArgs) {
20+
return [script, `--env-file=${file}`, '--skip-tools', ...extraArgs]
21+
}
22+
1923
test('accepts the default core profile with an empty Chrome suffix', () => {
20-
const output = execFileSync(process.execPath, [script, `--env-file=${envFile(validCore)}`], {
24+
const output = execFileSync(process.execPath, doctorArgs(envFile(validCore)), {
2125
cwd: root,
2226
encoding: 'utf8',
2327
})
2428
assert.match(output, /Environment ready: core/)
2529
})
2630

2731
test('rejects embedded Chrome without the image suffix', () => {
28-
const result = spawnSync(process.execPath, [script, `--env-file=${envFile(validCore)}`, '--profiles=embedded-chrome'], {
32+
const result = spawnSync(
33+
process.execPath,
34+
doctorArgs(envFile(validCore), '--profiles=embedded-chrome'),
35+
{
36+
cwd: root,
37+
encoding: 'utf8',
38+
},
39+
)
40+
assert.notEqual(result.status, 0)
41+
assert.match(result.stderr, /CHROME_SUFFIX (?:is required|must be -chrome)/)
42+
})
43+
44+
test('accepts AGENT_REGISTER=off without a central URL', () => {
45+
const file = envFile(`${validCore}AGENT_REGISTER=off\n`)
46+
const output = execFileSync(process.execPath, doctorArgs(file, '--profiles=agent'), {
2947
cwd: root,
3048
encoding: 'utf8',
3149
})
32-
assert.notEqual(result.status, 0)
33-
assert.match(result.stderr, /CHROME_SUFFIX (?:is required|must be -chrome)/)
50+
assert.match(output, /Environment ready: core, agent/)
51+
})
52+
53+
test('allows active registration modes to skip auto-registration without a central URL', () => {
54+
for (const registration of ['http', 'ws']) {
55+
const file = envFile(`${validCore}AGENT_REGISTER=${registration}\n`)
56+
const output = execFileSync(process.execPath, doctorArgs(file, '--profiles=agent'), {
57+
cwd: root,
58+
encoding: 'utf8',
59+
})
60+
assert.match(output, /CENTRAL_API_URL is empty; auto-registration is disabled/)
61+
}
3462
})
3563

36-
test('rejects HTTP agent registration without an advertised URL', () => {
64+
test('accepts HTTP registration with an auto-detected advertised URL', () => {
3765
const file = envFile(`${validCore}CENTRAL_API_URL=http://center:8031\nAGENT_REGISTER=http\n`)
38-
const result = spawnSync(process.execPath, [script, `--env-file=${file}`, '--profiles=agent'], {
66+
const output = execFileSync(process.execPath, doctorArgs(file, '--profiles=agent'), {
67+
cwd: root,
68+
encoding: 'utf8',
69+
})
70+
assert.match(output, /AGENT_ADVERTISE_URL is empty; the agent URL will be auto-detected/)
71+
})
72+
73+
test('accepts WS registration with an auto-detected advertised URL', () => {
74+
const file = envFile(`${validCore}CENTRAL_API_URL=http://center:8031\nAGENT_REGISTER=ws\n`)
75+
const output = execFileSync(process.execPath, doctorArgs(file, '--profiles=agent'), {
76+
cwd: root,
77+
encoding: 'utf8',
78+
})
79+
assert.match(output, /Environment ready: core, agent/)
80+
})
81+
82+
test('rejects an unsupported agent registration mode', () => {
83+
const file = envFile(`${validCore}AGENT_REGISTER=udp\n`)
84+
const result = spawnSync(process.execPath, doctorArgs(file, '--profiles=agent'), {
3985
cwd: root,
4086
encoding: 'utf8',
4187
})
4288
assert.notEqual(result.status, 0)
43-
assert.match(result.stderr, /AGENT_ADVERTISE_URL is required/)
89+
assert.match(result.stderr, /AGENT_REGISTER must be http, ws, or off/)
4490
})

0 commit comments

Comments
 (0)