Repository navigation
Expand file tree
/
Copy pathcontext.ts
More file actions
118 lines (104 loc) · 4.08 KB
/
Copy pathcontext.ts
File metadata and controls
118 lines (104 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import { existsSync, readFileSync } from 'node:fs'
import path from 'node:path'
const SOURCE_PACKAGE_MARKERS = [
['apps/sim/package.json', '@sim/app'],
['apps/realtime/package.json', '@sim/realtime'],
['packages/db/package.json', '@sim/db'],
] as const
const SIM_COMPOSE_MARKER = 'ghcr.io/simstudioai/simstudio'
const COMPOSE_FILE = 'docker-compose.prod.yml'
export type SetupContext =
| { kind: 'source'; root: string }
| { kind: 'standalone'; root: string; existing: boolean }
function readPackageName(file: string): string | null {
if (!existsSync(file)) return null
const parsed: unknown = JSON.parse(readFileSync(file, 'utf8'))
if (typeof parsed !== 'object' || parsed === null || !('name' in parsed)) return null
return typeof parsed.name === 'string' ? parsed.name : null
}
function parentDirectories(start: string): string[] {
const directories: string[] = []
let current = path.resolve(start)
while (true) {
directories.push(current)
const parent = path.dirname(current)
if (parent === current) return directories
current = parent
}
}
function inspectSourceRoot(candidate: string): 'valid' | 'partial' | 'absent' {
const rootName = readPackageName(path.join(candidate, 'package.json'))
const markerNames = SOURCE_PACKAGE_MARKERS.map(([file]) =>
readPackageName(path.join(candidate, file))
)
const looksLikeSource = rootName === 'simstudio' || markerNames.some((name) => name !== null)
if (!looksLikeSource) return 'absent'
if (
rootName === 'simstudio' &&
SOURCE_PACKAGE_MARKERS.every(([, expected], index) => markerNames[index] === expected)
) {
return 'valid'
}
return 'partial'
}
function isStandaloneInstall(candidate: string): boolean {
const composeFile = path.join(candidate, COMPOSE_FILE)
if (!existsSync(composeFile)) return false
return readFileSync(composeFile, 'utf8').includes(SIM_COMPOSE_MARKER)
}
export function directoryOverride(args: readonly string[]): string | null {
const equalsArg = args.find((arg) => arg.startsWith('--dir='))
if (equalsArg) {
const value = equalsArg.slice('--dir='.length)
if (!value) throw new Error('--dir requires a directory path')
return value
}
const index = args.indexOf('--dir')
if (index === -1) return null
const value = args[index + 1]
if (!value || value.startsWith('-')) throw new Error('--dir requires a directory path')
return value
}
/** Classifies one exact filesystem root without walking through its ancestors. */
export function resolveSetupContextAtRoot(root: string): SetupContext {
const resolvedRoot = path.resolve(root)
const source = inspectSourceRoot(resolvedRoot)
if (source === 'valid') return { kind: 'source', root: resolvedRoot }
if (source === 'partial') {
throw new Error(
`Incomplete Sim source checkout at ${resolvedRoot}; expected package.json plus apps/sim, apps/realtime, and packages/db package manifests.`
)
}
return {
kind: 'standalone',
root: resolvedRoot,
existing: isStandaloneInstall(resolvedRoot),
}
}
/** Resolves the filesystem context before setup reads or writes any installation state. */
export function resolveSetupContext(
start: string = process.cwd(),
args: readonly string[] = process.argv.slice(2)
): SetupContext {
const override = directoryOverride(args)
const searchStart = path.resolve(start, override ?? '.')
if (override) {
return resolveSetupContextAtRoot(searchStart)
}
for (const candidate of parentDirectories(searchStart)) {
const source = inspectSourceRoot(candidate)
if (source === 'valid') return { kind: 'source', root: candidate }
if (source === 'partial') {
throw new Error(
`Incomplete Sim source checkout at ${candidate}; expected package.json plus apps/sim, apps/realtime, and packages/db package manifests.`
)
}
}
for (const candidate of parentDirectories(searchStart)) {
if (isStandaloneInstall(candidate)) {
return { kind: 'standalone', root: candidate, existing: true }
}
}
return resolveSetupContextAtRoot(path.join(path.resolve(start), 'sim'))
}
export const SETUP_CONTEXT = resolveSetupContext()