Skip to content

Commit

Permalink
stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
kla committed Sep 26, 2024
1 parent 963bd8d commit efeeb23
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 84 deletions.
10 changes: 3 additions & 7 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFileSync, rmSync } from 'fs'
import { cacheDir, exit, fileExists, pp } from '~/utils'
import { cacheDir, exit, fileExists, pp, isEmpty } from '~/utils'
import { StaxConfig, SetupOptions, FindContainerOptions } from '~/types'
import { linkSshAuthSock } from './host_services'
import Staxfile from '~/staxfile'
Expand Down Expand Up @@ -83,10 +83,10 @@ export default class App {
const app = App.find(staxfile.context, staxfile.app)

if (options.duplicate || (!options.rebuild && !staxfile.config.location.local))
await app.primary.exec(`git clone ${staxfile.config.source} ${staxfile.compose.stax.workspace}`)
await app.primary.exec(`sh -c '[ -z "$(ls -A ${staxfile.compose.stax.workspace})" ] && git clone ${staxfile.config.source} ${staxfile.compose.stax.workspace} || echo "Directory not empty. Skipping git clone."'`)

if (options.duplicate || !options.rebuild)
await Promise.all(app.containers.map(container => container.runHook('after_setup')))
await app.primary.runHook('after_setup')

return app
}
Expand Down Expand Up @@ -162,10 +162,6 @@ export default class App {
return Promise.all(this.containers.map(container => container.restart()))
}

async runHooks() {
this.containers.forEach(async container => container.runHooks())
}

addAlias(alias: string) {
const aliases = settings.read('aliases') || {}

Expand Down
2 changes: 1 addition & 1 deletion src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export default class Container {
}

async runHook(type) {
let hook = this.labels[`stax.hooks.${type}`]
let hook = this.labels[`stax.${type}`]
if (!hook) return

if (existsSync(hook))
Expand Down
4 changes: 2 additions & 2 deletions src/location.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class Location {
}

static isGitUrl(url: string): boolean {
return url && (url.startsWith('git@') || url.startsWith('https://'))
return url && (url.endsWith('.git') || url.startsWith('https://'))
}

static from(context: string, app: string, location: string): Location {
Expand Down Expand Up @@ -111,6 +111,6 @@ class ContainerLocation extends GitLocation {
}

file = path.join(this.container.config.workspace, file)
return capture(`docker container exec ${this.container.containerName} cat "${file}"`)
return capture(`docker container exec ${this.container.containerName} sh -c 'cat "${file}" || true'`)
}
}
1 change: 1 addition & 0 deletions src/staxfile/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default class Config implements StaxConfig {
public workspace!: string
public workspace_volume!: string
public vars!: Record<string, string>
public after_setup!: string

constructor(config: StaxConfig | Record<string, string> | undefined = undefined) {
if (config)
Expand Down
10 changes: 9 additions & 1 deletion src/staxfile/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export default class Expressions {
this.staxfile = staxfile
}

clearCache() {
Expressions.cache = {}
}

async evaluate(name: string, args: any[]): Promise<string> {
const cacheKey = this.getCacheKey(name, args)

Expand Down Expand Up @@ -87,8 +91,12 @@ export default class Expressions {
return `${src}:${dest}`
}

private platform(): string {
return process.platform
}

private mountSshAuthSock(): string {
return process.platform === 'darwin' ?
return this.platform() === 'darwin' ?
'${{ stax.ssh_auth_sock }}:${{ stax.ssh_auth_sock }}' :
'${{ stax.host_services }}:/run/host-services'
}
Expand Down
21 changes: 9 additions & 12 deletions src/staxfile/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ export default class Staxfile {
this.warnings = new Set<string>()
this.compose = yaml.load(readFileSync(this.staxfile, 'utf-8'))

// render the stax section first since we need to update this.config with the values there
this.compose.stax = await this.renderCompose(this.compose.stax)
this.config = new Config({ ...this.config, ...this.compose.stax })

this.compose = await this.renderCompose(this.compose)
this.updateServices()
this.compose = await this.renderCompose(this.compose) // Re-render after updating services
// console.log(this.compose); console.log(this.config);process.exit()

// 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)
exit(1, this.generatedWarnings.join('\n'))
Expand All @@ -104,12 +106,7 @@ export default class Staxfile {

text = await renderTemplate(text, async (name, args) => {
matches += 1

const x = await this.expressions.evaluate(name, args)
if (name == 'prompt') {
console.log(name, args, x)
}
return x
return await this.expressions.evaluate(name, args)
})

return matches > 0 ? await this.render(text) : text
Expand Down Expand Up @@ -152,11 +149,11 @@ export default class Staxfile {
const hooks = [ 'after_setup' ]

for (const hook of hooks) {
if (labels[`stax.hooks.${hook}`]) {
if (existsSync(labels[`stax.hooks.${hook}`])) {
const file = path.resolve(labels[`stax.hooks.${hook}`])
if (labels[`stax.${hook}`]) {
if (existsSync(labels[`stax.${hook}`])) {
const file = path.resolve(labels[`stax.${hook}`])
verifyFile(file, `Hook file not found for '${hook}'`)
labels[`stax.hooks.${hook}`] = file
labels[`stax.${hook}`] = file
}
}
}
Expand Down
75 changes: 40 additions & 35 deletions tests/unit/staxfile/expressions.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, mock } from 'bun:test'
import { describe, it, expect, afterEach, beforeEach, mock } from 'bun:test'
import Expressions from '~/staxfile/expressions'
import Staxfile from '~/staxfile'
import os from 'os'
Expand All @@ -13,65 +13,70 @@ describe('Expressions', () => {
expression = new Expressions(staxfile)
})

it('evaluates undefined stax config values', () => {
const result = expression.evaluate('stax.app', [])
expect(result).toBe('tests')
afterEach(() => {
mock.restore()
expression.clearCache()
})

it('evaluates undefined stax config values', async () => {
expect(await expression.evaluate('stax.app', [])).toBe('tests')
})

// it('evaluates read function', () => {
// const result = expression.evaluate('read', [__filename, 'default'])
// expect(result).toContain('import { describe, it, expect, beforeEach } from \'bun:test\'')
// })

it('evaluates mount_workspace function', () => {
const result = expression.evaluate('mount_workspace', [])
it('evaluates mount_workspace function', async () => {
const result = await expression.evaluate('mount_workspace', [])
expect(result).toBe(`${path.resolve('./tests')}:/workspaces/tests`)
})

it('evaluates mount_ssh_auth_sock function', () => {
const result = expression.evaluate('mount_ssh_auth_sock', [])
expect(result).toBe('${{ stax.host_services }}:/run/host-services')
it('evaluates mount_ssh_auth_sock function for Darwin', async () => {
expression.platform = mock(() => 'darwin')
expect(await expression.evaluate('mount_ssh_auth_sock', [])).toBe('${{ stax.ssh_auth_sock }}:${{ stax.ssh_auth_sock }}')
})

it('evaluates mount_ssh_auth_sock function for Linux', async () => {
expression.platform = mock(() => 'linux')
expect(await expression.evaluate('mount_ssh_auth_sock', [])).toBe('${{ stax.host_services }}:/run/host-services')
})

it('evaluates resolve function', () => {
const result = expression.evaluate('resolve', ['/test/path'])
expect(result).toBe('/test/path')
it('evaluates resolve function', async () => {
expect(await expression.evaluate('resolve', ['/test/path'])).toBe('/test/path')
})

it('evaluates user function', () => {
const result = expression.evaluate('user', [])
expect(result).toBe(os.userInfo().username)
it('evaluates user function', async () => {
expect(await expression.evaluate('user', [])).toBe(os.userInfo().username)
})

it('evaluates user_id function', () => {
const result = expression.evaluate('user_id', [])
expect(result).toBe(process.getuid().toString())
it('evaluates user_id function', async () => {
expect(await expression.evaluate('user_id', [])).toBe(process.getuid().toString())
})

it('evaluates dasherize function', () => {
const result = expression.evaluate('dasherize', ['TestString'])
expect(result).toBe('test-string')
it('evaluates dasherize function', async () => {
expect(await expression.evaluate('dasherize', ['TestString'])).toBe('test-string')
})

it('evaluates dasherize function with stax.app', () => {
it('evaluates dasherize function with stax.app', async () => {
staxfile.config.set('app', 'MyTestApp')
const result = expression.evaluate('dasherize', ['stax.app'])
const result = await expression.evaluate('dasherize', ['stax.app'])
expect(result).toBe('my-test-app')
})

it('evaluates dasherize function with undefined stax config', () => {
const result = expression.evaluate('dasherize', ['stax.undefined_key'])
it('evaluates dasherize function with undefined stax config', async () => {
const result = await expression.evaluate('dasherize', ['stax.undefined_key'])
expect(result).toBeUndefined()
expect(staxfile.warnings).toContain("Undefined reference to 'stax.undefined_key'")
})

it('adds warning for invalid expression', () => {
expression.evaluate('invalid_expression', [])
it('adds warning for invalid expression', async () => {
await expression.evaluate('invalid_expression', [])
expect(staxfile.warnings).toContain('Invalid template expression: invalid_expression')
})

it('adds warning for undefined stax config', () => {
expression.evaluate('stax.undefined_key', [])
it('adds warning for undefined stax config', async () => {
await expression.evaluate('stax.undefined_key', [])
console.log(staxfile.warnings)
expect(staxfile.warnings).toContain("Undefined reference to 'stax.undefined_key'")
})
Expand All @@ -94,21 +99,21 @@ describe('Expressions', () => {
expressions = new Expressions(mockStaxfile)
})

it('returns true when pattern is found in file', () => {
it('returns true when pattern is found in file', async () => {
mockStaxfile.location.readSync.mockImplementation(() => 'Hello, world!')
const result = expressions.evaluate('test', ['test.txt', 'world'])
const result = await expressions.evaluate('test', ['test.txt', 'world'])
expect(result).toBe('true')
})

it('returns false when pattern is not found in file', () => {
it('returns false when pattern is not found in file', async () => {
mockStaxfile.location.readSync.mockImplementation(() => 'Hello, world!')
const result = expressions.evaluate('test', ['test.txt', 'foo'])
const result = await expressions.evaluate('test', ['test.txt', 'foo'])
expect(result).toBe('false')
})

it('uses default value when file cannot be read', () => {
it('uses default value when file cannot be read', async () => {
mockStaxfile.location.readSync.mockImplementation(() => { throw new Error('File not found') })
const result = expressions.evaluate('test', ['nonexistent.txt', 'pattern'])
const result = await expressions.evaluate('test', ['nonexistent.txt', 'pattern'])
expect(result).toBe('false')
})
})
Expand Down
52 changes: 26 additions & 26 deletions tests/unit/staxfile/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,81 @@ import { expect, it, describe } from 'bun:test'
import { renderTemplate } from '~/staxfile/template'

describe('renderTemplate', () => {
it('replaces tokens with callback results', () => {
it('replaces tokens with callback results', async () => {
const template = 'Hello, ${{ name John Doe }}! Your age is ${{ age 30 }}.'
const callback = (name, args) => {
const callback = async (name, args) => {
if (name === 'name') return args.join(' ')
if (name === 'age') return `${parseInt(args[0]) + 1}`
return ''
}

const result = renderTemplate(template, callback)
const result = await renderTemplate(template, callback)
expect(result).toBe('Hello, John Doe! Your age is 31.')
})

it('handles tokens without arguments', () => {
it('handles tokens without arguments', async () => {
const template = 'The current year is ${{ year }}.'
const callback = (name) => name === 'year' ? '2024' : ''
const callback = async (name) => name === 'year' ? '2024' : ''

const result = renderTemplate(template, callback)
const result = await renderTemplate(template, callback)
expect(result).toBe('The current year is 2024.')
})

it('ignores malformed tokens', () => {
it('ignores malformed tokens', async () => {
const template = 'This is a ${{ malformed token }} and this is correct ${{ correct token }}.'
const callback = (name, args) => name === 'correct' ? 'CORRECT' : 'IGNORED'
const callback = async (name, args) => name === 'correct' ? 'CORRECT' : 'IGNORED'

const result = renderTemplate(template, callback)
const result = await renderTemplate(template, callback)
expect(result).toBe('This is a IGNORED and this is correct CORRECT.')
})

it('handles multiple tokens in a single line', () => {
it('handles multiple tokens in a single line', async () => {
const template = '${{ greeting Hello }} ${{ name John }}! Today is ${{ day Monday }}.'
const callback = (name, args) => args.join(' ')
const callback = async (name, args) => args.join(' ')

const result = renderTemplate(template, callback)
const result = await renderTemplate(template, callback)
expect(result).toBe('Hello John! Today is Monday.')
})

it('handles simple arguments', () => {
it('handles simple arguments', async () => {
const input = "${{ test arg1 arg2 arg3 }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join(',')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join(',')}`)
expect(result).toBe("test:arg1,arg2,arg3")
})

it('handles single-quoted arguments', () => {
it('handles single-quoted arguments', async () => {
const input = "${{ test 'arg with spaces' normal_arg }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join(',')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join(',')}`)
expect(result).toBe("test:arg with spaces,normal_arg")
})

it('handles multi-part quoted arguments', () => {
it('handles multi-part quoted arguments', async () => {
const input = "${{ test 'arg with multiple parts' normal_arg }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join(',')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join(',')}`)
expect(result).toBe("test:arg with multiple parts,normal_arg")
})

it('handles mixed quoted and unquoted arguments', () => {
it('handles mixed quoted and unquoted arguments', async () => {
const input = "${{ test normal_arg 'quoted arg' another_normal 'multi part quoted arg' }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join(',')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join(',')}`)
expect(result).toBe("test:normal_arg,quoted arg,another_normal,multi part quoted arg")
})

it('handles empty arguments', () => {
it('handles empty arguments', async () => {
const input = "${{ test }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.length}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.length}`)
expect(result).toBe("test:0")
})

it('handles unclosed quotes', () => {
it('handles unclosed quotes', async () => {
const input = "${{ test 'unclosed quote argument }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join(',')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join(',')}`)
expect(result).toBe("test:unclosed quote argument")
})

it('handles multiple template expressions', () => {
it('handles multiple template expressions', async () => {
const input = "Hello ${{ test arg1 }} world ${{ test2 'arg2 with spaces' }}"
const result = renderTemplate(input, (name, args) => `${name}:${args.join('|')}`)
const result = await renderTemplate(input, async (name, args) => `${name}:${args.join('|')}`)
expect(result).toBe("Hello test:arg1 world test2:arg2 with spaces")
})
})

0 comments on commit efeeb23

Please sign in to comment.