Skip to content

Commit

Permalink
switch to yamler
Browse files Browse the repository at this point in the history
  • Loading branch information
kla committed Oct 28, 2024
1 parent f8be3a3 commit 77b3d29
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 651 deletions.
2 changes: 1 addition & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default class App {
static async setup(config: StaxConfig, options: SetupOptions = {}): Promise<App> {
options = { cache: true, ...options }
const staxfile = new Staxfile(config)
const composeFile = await staxfile.compile({ force: true, excludes: options.rebuild ? [ 'prompts' ] : [] })
const composeFile = await staxfile.compile({ force: true, excludes: options.rebuild ? [ 'prompt' ] : [] })

if (!composeFile)
return exit(1, { message: `👿 Couldn't setup a container for '${staxfile.source}'` })
Expand Down
75 changes: 75 additions & 0 deletions src/staxfile/evaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { dasherize, dig, resolve } from '~/utils'
import { ExpressionWarning } from '~/yamler'
import icons from '~/icons'
import Staxfile from '.'
import inquirer from 'inquirer'

export default class Evaluator {
public staxfile: Staxfile

constructor(staxfile: Staxfile) {
this.staxfile = staxfile
}

async evaluate(baseDir: string, attributes: Record<string, any>, path: string, name: string, args: string[]) {
args = args.map(arg => typeof arg === 'string' && arg.startsWith('stax.') ? this.fetch(attributes, arg) : arg)

// TODO: remove the stax. prefix
if (name === 'stax.ssh_auth_sock' || name === 'ssh_auth_sock') return '/run/host-services/ssh-auth.sock'

if (name.startsWith('stax.')) return this.fetch(attributes, name)
if (name === 'read') return this.read(args[0], args[1])
if (name === 'mount_workspace') return this.mountWorkspace()
if (name === 'mount_ssh_auth_sock') return this.mountSshAuthSock()
if (name === 'resolve' || name === 'resolve_relative') return resolve(baseDir, args[0])
if (name === 'user') return process.env.USER || ''
if (name === 'user_id') return process.getuid()
if (name === 'dasherize') return dasherize(args[0])
if (name === 'prompt') return await this.prompt(args[0], args[1])
if (name === 'requires?') return this.staxfile.config.requires.includes(args[0])

throw new ExpressionWarning(`Invalid template expression: ${name}`)
}

fetch(attributes: Record<string, any>, path: string): string {
return dig(attributes, path, { required: true })
}

platform(): string {
return process.platform
}

mountSshAuthSock(): string {
return this.platform() === 'darwin' ?
'/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock' :
`${process.env.STAX_HOST_SERVICES}:/run/host-services`
}

mountWorkspace(): string {
const src = this.staxfile.config.location.local ? this.staxfile.config.source : this.staxfile.config.workspace_volume
const dest = this.staxfile.config.workspace
return `${src}:${dest}`
}

read(file: string, defaultValue: string=''): string {
try {
return this.staxfile.location.readSync(file)?.trim() || defaultValue
} catch (e) {
const url = this.staxfile.config.location?.baseUrl
console.warn(`${icons.warning} Couldn't read ${file} from ${url}: ${e.code}... using default value of '${defaultValue}'`)
return defaultValue
}
}

async prompt(message: string, defaultValue: string): Promise<string> {
const response = await inquirer.prompt([
{
type: 'input',
name: 'result',
message,
default: defaultValue,
},
])
return response.result
}
}
116 changes: 0 additions & 116 deletions src/staxfile/expressions.ts

This file was deleted.

72 changes: 19 additions & 53 deletions src/staxfile/index.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import { writeFileSync, existsSync, mkdirSync, statSync } from 'fs'
import { cacheDir as _cacheDir, exit, flattenObject, deepMap, verifyFile, resolve } from '~/utils'
import { cacheDir as _cacheDir, exit, flattenObject, deepMap, verifyFile, resolve, deepMapWithKeys } from '~/utils'
import { StaxConfig, CompileOptions, DefaultCompileOptions } from '~/types'
import { renderTemplate, parseTemplateExpression } from './template'
import { dump, loadFile } from './yaml'
import YamlER, { dump } from '~/yamler'
import yaml from 'js-yaml'
import Config from './config'
import DockerfileCompiler from './dockerfile_compiler'
import Expressions from './expressions'
import Evaluator from './evaluator'
import Location from '~/location'
import icons from '~/icons'
import * as path from 'path'

export default class Staxfile {
public config: Config
public compose: Record<string, any>
public warnings: Set<string>
public cacheDir: string
private buildsCompiled: Record<string, string> = {}
private expressions: Expressions

constructor(config: StaxConfig, options: { cacheDir?: string } = {}) {
let source = config.source
Expand All @@ -27,8 +24,6 @@ export default class Staxfile {

this.config = new Config({ ...config, source: source })
this.cacheDir = options.cacheDir || this.systemCacheDir
this.warnings = new Set()
this.expressions = new Expressions(this)
}

get staxfile(): string { return this.config.staxfile }
Expand Down Expand Up @@ -84,65 +79,36 @@ export default class Staxfile {
}

private async load(options: CompileOptions = {}): Promise<void> {
const evaluator = new Evaluator(this)
const yamler = new YamlER(this.staxfile, { expressionCallback: evaluator.evaluate.bind(evaluator) })

options = { ...DefaultCompileOptions, ...options }

this.warnings = new Set<string>()
this.compose = loadFile(this.staxfile)
this.compose = yamler.compile()
yamler.attributes.stax.app = this.config.app

if (options.excludes.includes('prompts'))
this.keepExistingPromptValues()
// if (options.excludes?.includes('prompt'))
// this.compose = yamler.attributes = this.keepExistingPromptValues()

// render the stax section first since we need to update this.config with the values there
// exclude read on this first render since it can be dependent on stax.source
this.compose.stax = await this.renderCompose(this.compose.stax, { excludes: [ 'read' ] })
this.config = new Config({ ...this.config, ...this.compose.stax })

this.compose = await this.renderCompose(this.compose)
this.compose = await yamler.load()
this.config = new Config({ ...this.config, ...this.compose.stax })
this.updateServices()

// need to re-render after updating services since template expressions may have been added
this.compose = await this.renderCompose(this.compose)

if (this.generatedWarnings.length > 0)
return exit(1, { message: this.generatedWarnings.join('\n') })
if (yamler.warnings.length > 0)
return exit(1, { message: yamler.warnings.join('\n') })
}

// set all prompts to it's current config value or default if it is being excluded
private keepExistingPromptValues() {
deepMap(this.compose, (path, value) => {
if (typeof value === 'string' && value.includes('prompt')) {
const expression = parseTemplateExpression(value)
private keepExistingPromptValues(): Record<string, any> {
return deepMapWithKeys(this.compose, (path, key, value) => {
if (value && typeof(value) === 'string' && value.includes('prompt'))
return [ key, this.config.fetch(path) ]

if (expression.funcName === 'prompt')
return this.config.fetch(path) || expression.args[expression.args.length - 1]
}
return value
return [ key, value ]
})
}

private get generatedWarnings(): Array<string> {
return [...this.warnings]
}

private async renderCompose(attributes: Record<string, any>, options: CompileOptions = { excludes: [] }): Promise<Record<string, any>> {
const renderedYaml = await this.render(dump(attributes), options)
return yaml.load(renderedYaml)
}

private async render(text: string, options: CompileOptions = { excludes: [] }): Promise<string> {
let matches = 0

text = await renderTemplate(text, async (name, args, originalMatch) => {
if (options.excludes.includes(name))
return originalMatch

matches += 1
return await this.expressions.evaluate(name, args)
})

return matches > 0 ? await this.render(text, options) : text
}

private updateServices() {
const services = {}
let number = 0
Expand Down
Loading

0 comments on commit 77b3d29

Please sign in to comment.