Skip to content

Commit c7ce976

Browse files
feat: updated config with build metadata (#22)
1 parent 3f81603 commit c7ce976

File tree

6 files changed

+193
-96
lines changed

6 files changed

+193
-96
lines changed

src/lib/config.ts

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { deepmerge, dotenv, parse, path, ulid, ValidationError, z } from '../dep
22
import { CliOptions, RunrealConfig } from '../lib/types.ts'
33
import { execSync } from '../lib/utils.ts'
44
import { ConfigSchema, InternalSchema } from '../lib/schema.ts'
5-
import { Source } from './source.ts'
5+
import { Git, Perforce, Source } from './source.ts'
66
import { renderConfig } from './template.ts'
77

88
const env = (key: string) => Deno.env.get(key) || ''
@@ -16,15 +16,11 @@ export class Config {
1616
project: {
1717
name: '',
1818
path: '',
19+
buildPath: '',
1920
repoType: 'git',
2021
},
2122
build: {
22-
path: '',
2323
id: env('RUNREAL_BUILD_ID') || '',
24-
branch: '',
25-
branchSafe: '',
26-
commit: '',
27-
commitShort: '',
2824
},
2925
buildkite: {
3026
branch: env('BUILDKITE_BRANCH') || '',
@@ -34,7 +30,17 @@ export class Config {
3430
buildPipelineSlug: env('BUILDKITE_PIPELINE_SLUG') || '',
3531
},
3632
metadata: {
37-
test: env('RUNREAL_BUILD_ID') || '',
33+
safeRef: '',
34+
git: {
35+
branch: '',
36+
branchSafe: '',
37+
commit: '',
38+
commitShort: '',
39+
},
40+
perforce: {
41+
changelist: '',
42+
stream: '',
43+
},
3844
},
3945
workflows: [],
4046
}
@@ -43,7 +49,7 @@ export class Config {
4349
'branch': 'engine.branch',
4450
'cachePath': 'engine.cachePath',
4551
'projectPath': 'project.path',
46-
'buildPath': 'build.path',
52+
'buildPath': 'project.buildPath',
4753
'buildId': 'build.id',
4854
'gitDependenciesCachePath': 'git.dependenciesCachePath',
4955
'gitMirrors': 'git.mirrors',
@@ -82,17 +88,6 @@ export class Config {
8288
return this.config
8389
}
8490

85-
determineBuildId() {
86-
const build = this.config.build
87-
if (!build) return ulid()
88-
if (!this.config.project?.path) return ulid()
89-
if (!this.config.project?.repoType) return ulid()
90-
const source = Source(this.config.project?.path, this.config.project?.repoType)
91-
const safeRef = source.safeRef()
92-
if (!safeRef) return ulid()
93-
return safeRef
94-
}
95-
9691
private async searchForConfigFile(): Promise<string | null> {
9792
const cwd = Deno.cwd()
9893
const configPath = path.join(cwd, 'runreal.config.json')
@@ -143,8 +138,8 @@ export class Config {
143138
config.project.path = path.resolve(config.project.path)
144139
}
145140

146-
if (config.build && config.build.path) {
147-
config.build.path = path.resolve(config.build.path)
141+
if (config.project && config.project.buildPath) {
142+
config.project.buildPath = path.resolve(config.project.buildPath)
148143
}
149144

150145
if (config.git && config.git.dependenciesCachePath) {
@@ -158,31 +153,91 @@ export class Config {
158153
return config
159154
}
160155

161-
private populateBuild(): RunrealConfig['build'] | null {
156+
private getBuildMetadata(): RunrealConfig['metadata'] | null {
162157
const cwd = this.config.project?.path
163158
if (!cwd) return null
159+
if (this.config.project?.repoType === 'git') {
160+
const { safeRef, git } = this.getGitBuildMetadata(cwd)
161+
return {
162+
safeRef,
163+
git,
164+
}
165+
} else if (this.config.project?.repoType === 'perforce') {
166+
const { safeRef, perforce } = this.getPerforceBuildMetadata(cwd)
167+
return {
168+
safeRef,
169+
perforce,
170+
}
171+
}
172+
return null
173+
}
174+
175+
private getGitBuildMetadata(projectPath: string): RunrealConfig['metadata'] {
176+
const cwd = projectPath
164177
try {
165-
let branch: string
166-
// On Buildkite, use the BUILDKITE_BRANCH env var as we may be in a detached HEAD state
167-
if (Deno.env.get('BUILDKITE_BRANCH')) {
168-
branch = this.config.buildkite?.branch || ''
169-
} else {
170-
branch = execSync('git', ['branch', '--show-current'], { cwd, quiet: false }).output.trim()
178+
const source = new Git(cwd)
179+
const branch = source.branch()
180+
const branchSafe = source.branchSafe()
181+
const commit = source.commit()
182+
const commitShort = source.commitShort()
183+
const safeRef = source.safeRef()
184+
return {
185+
safeRef,
186+
git: {
187+
branch,
188+
branchSafe,
189+
commit,
190+
commitShort,
191+
},
192+
}
193+
} catch (e) {
194+
return {
195+
safeRef: '',
196+
git: {
197+
branch: '',
198+
branchSafe: '',
199+
commit: '',
200+
commitShort: '',
201+
},
171202
}
172-
const branchSafe = branch.replace(/[^a-z0-9]/gi, '-')
173-
const commit = execSync('git', ['rev-parse', 'HEAD'], { cwd, quiet: false }).output.trim()
174-
const commitShort = execSync('git', ['rev-parse', '--short', 'HEAD'], { quiet: false }).output.trim()
203+
}
204+
}
175205

206+
private getPerforceBuildMetadata(projectPath: string): RunrealConfig['metadata'] {
207+
const cwd = projectPath
208+
try {
209+
const source = new Perforce(cwd)
210+
const changelist = source.changelist()
211+
const stream = source.stream()
212+
const safeRef = source.safeRef()
176213
return {
177-
...this.config.build,
178-
path: this.config.build?.path || '',
179-
branch,
180-
branchSafe,
181-
commit,
182-
commitShort,
214+
safeRef,
215+
perforce: {
216+
changelist,
217+
stream,
218+
},
183219
}
184220
} catch (e) {
185-
return null
221+
return {
222+
safeRef: '',
223+
perforce: {
224+
changelist: '',
225+
stream: '',
226+
},
227+
}
228+
}
229+
}
230+
231+
getBuildId() {
232+
if (this.config.build?.id) return this.config.build.id
233+
if (!this.config.project?.path) return ulid()
234+
if (!this.config.project?.repoType) return ulid()
235+
try {
236+
const source = Source(this.config.project.path, this.config.project.repoType)
237+
const safeRef = source.safeRef()
238+
return safeRef
239+
} catch (e) {
240+
return ulid()
186241
}
187242
}
188243

@@ -192,10 +247,16 @@ export class Config {
192247
const Merged = ConfigSchema.and(InternalSchema)
193248
this.config = Merged.parse(this.config)
194249

195-
const bd = this.populateBuild()
196-
if (bd) this.config.build = bd
197-
if (!this.config.build?.id) {
198-
this.config.build!.id = this.determineBuildId()
250+
const metadata = this.getBuildMetadata()
251+
this.config.metadata = {
252+
...this.config.metadata,
253+
...metadata,
254+
}
255+
256+
const buildId = this.getBuildId()
257+
this.config.build = {
258+
...this.config.build,
259+
id: buildId,
199260
}
200261
} catch (e) {
201262
if (e instanceof z.ZodError) {

src/lib/schema.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,18 @@ export const InternalSchema = z.object({
99
buildPipelineSlug: z.string().describe('Buildkite pipeline slug').optional(),
1010
}).optional(),
1111
metadata: z.object({
12-
test: z.string().describe('Build id <RUNREAL_BUILD_ID>'),
13-
}).optional(),
12+
safeRef: z.string().describe('Safe reference for file outputs or build ids').optional(),
13+
git: z.object({
14+
branch: z.string().describe('Branch name'),
15+
branchSafe: z.string().describe('Safe branch name'),
16+
commit: z.string().describe('Commit hash'),
17+
commitShort: z.string().describe('Short commit hash'),
18+
}).optional(),
19+
perforce: z.object({
20+
stream: z.string().describe('Stream name'),
21+
changelist: z.string().describe('Changelist number'),
22+
}).optional(),
23+
}),
1424
})
1525

1626
export const ConfigSchema = z.object({
@@ -27,28 +37,20 @@ export const ConfigSchema = z.object({
2737
project: z.object({
2838
name: z.string().optional().describe('Project name'),
2939
path: z.string().describe('Path to the project folder <RUNREAL_PROJECT_PATH>'),
40+
buildPath: z.string().describe('Path to the build folder <RUNREAL_BUILD_PATH>'),
3041
repoType: z.string().describe('git or perforce'),
3142
}),
3243
build: z.object({
33-
path: z.string().describe('Path to the build folder <RUNREAL_BUILD_PATH>'),
3444
id: z.string().optional().describe('Build id <RUNREAL_BUILD_ID>'),
35-
branch: z.string().optional().describe('Branch name'),
36-
branchSafe: z
45+
}),
46+
git: z.object({
47+
dependenciesCachePath: z
3748
.string()
3849
.optional()
39-
.describe('Branch name safe for filenames'),
40-
commit: z.string().optional().describe('Commit hash'),
41-
commitShort: z.string().optional().describe('Short commit hash'),
42-
}),
43-
git: z
44-
.object({
45-
dependenciesCachePath: z
46-
.string()
47-
.optional()
48-
.describe('Path to git dependencies cache folder <RUNREAL_GIT_DEPENDENCIES_CACHE_PATH>'),
49-
mirrors: z.boolean().optional().describe('Use git mirrors'),
50-
mirrorsPath: z.string().optional().describe('Path to git mirrors folder <RUNREAL_GIT_MIRRORS_PATH>'),
51-
})
50+
.describe('Path to git dependencies cache folder <RUNREAL_GIT_DEPENDENCIES_CACHE_PATH>'),
51+
mirrors: z.boolean().optional().describe('Use git mirrors'),
52+
mirrorsPath: z.string().optional().describe('Path to git mirrors folder <RUNREAL_GIT_MIRRORS_PATH>'),
53+
})
5254
.optional(),
5355
workflows: z.array(
5456
z.object({

src/lib/source.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,24 @@ export class Perforce extends Base {
8686
export class Git extends Base {
8787
executable: string = 'git'
8888
branch(): string {
89+
// On Buildkite, use the BUILDKITE_BRANCH env var as we may be in a detached HEAD state
90+
if (Deno.env.get('BUILDKITE_BRANCH')) {
91+
return Deno.env.get('BUILDKITE_BRANCH') || ''
92+
}
8993
return execSync(this.executable, ['branch', '--show-current'], { cwd: this.cwd, quiet: true }).output.trim()
9094
}
95+
branchSafe(): string {
96+
return this.branch().replace(/[^a-z0-9]/gi, '-')
97+
}
9198
commit(): string {
9299
return execSync(this.executable, ['rev-parse', 'HEAD'], { cwd: this.cwd, quiet: true }).output.trim()
93100
}
101+
commitShort(): string {
102+
return execSync(this.executable, ['rev-parse', '--short', 'HEAD'], { cwd: this.cwd, quiet: true }).output.trim()
103+
}
94104
ref(): string {
95-
const branch = this.branch()
96-
const commit = this.commit()
105+
const branch = this.branchSafe()
106+
const commit = this.commitShort()
97107
const parts: string[] = []
98108
if (branch) {
99109
parts.push(branch)

src/lib/template.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,14 @@ export const getSubstitutions = (cfg: RunrealConfig): Record<string, string | un
99
'engine.path': cfg.engine?.path,
1010
'project.path': cfg.project?.path,
1111
'project.name': cfg.project?.name,
12+
'project.buildPath': cfg.project?.buildPath,
13+
'build.path': cfg.project?.buildPath,
1214
'build.id': cfg.build?.id,
13-
'build.path': cfg.build?.path,
14-
'build.branch': cfg.build?.branchSafe,
15-
'build.commit': cfg.build?.commitShort,
15+
'metadata.safeRef': cfg.metadata?.safeRef,
16+
'metadata.git.branch': cfg.metadata?.git?.branchSafe,
17+
'metadata.git.commit': cfg.metadata?.git?.commitShort,
18+
'metadata.perforce.stream': cfg.metadata?.perforce?.stream,
19+
'metadata.perforce.changelist': cfg.metadata?.perforce?.changelist,
1620
'buildkite.buildNumber': cfg.buildkite?.buildNumber,
1721
})
1822

tests/config.test.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { assert, assertEquals } from 'https://deno.land/std/assert/mod.ts'
22
import { Config } from '../src/lib/config.ts'
3-
import { ulid } from '../src/deps.ts'
3+
import { path, ulid } from '../src/deps.ts'
44
import { CliOptions } from '../src/lib/types.ts'
55

66
Deno.test('Config.create should initialize with default values', async () => {
77
const config = await Config.create()
88
const id = ulid()
9-
config.determineBuildId = () => id
9+
config.getBuildId = () => id
1010
const expected = {
1111
engine: {
1212
path: '',
@@ -15,15 +15,11 @@ Deno.test('Config.create should initialize with default values', async () => {
1515
project: {
1616
name: '',
1717
path: '',
18+
buildPath: '',
1819
repoType: 'git',
1920
},
2021
build: {
2122
id,
22-
path: '',
23-
branch: '',
24-
branchSafe: '',
25-
commit: '',
26-
commitShort: '',
2723
},
2824
buildkite: {
2925
branch: '',
@@ -33,7 +29,17 @@ Deno.test('Config.create should initialize with default values', async () => {
3329
buildPipelineSlug: '',
3430
},
3531
metadata: {
36-
test: '',
32+
safeRef: '',
33+
git: {
34+
branch: '',
35+
branchSafe: '',
36+
commit: '',
37+
commitShort: '',
38+
},
39+
perforce: {
40+
stream: '',
41+
changelist: '',
42+
},
3743
},
3844
workflows: [],
3945
}
@@ -50,12 +56,14 @@ Deno.test('Config.create should load environment variables', async () => {
5056
Deno.test('Config.get should apply CLI options', async () => {
5157
const config = await Config.create()
5258
const id = ulid()
53-
config.determineBuildId = () => id
59+
config.getBuildId = () => id
60+
const enginePath = path.normalize('/path/to/engine')
61+
const projectPath = path.normalize('/path/to/project')
5462
const cliOptions: CliOptions = {
55-
enginePath: '/path/to/engine' as any,
56-
projectPath: '/path/to/project' as any,
63+
enginePath: enginePath as any,
64+
projectPath: projectPath as any,
5765
}
5866
const result = config.get(cliOptions)
59-
assert(result.engine.path.includes('/path/to/engine'))
60-
assert(result.project.path.includes('/path/to/project'))
67+
assert(result.engine.path.includes(enginePath))
68+
assert(result.project.path.includes(projectPath))
6169
})

0 commit comments

Comments
 (0)