Skip to content
This repository has been archived by the owner on Dec 3, 2021. It is now read-only.

Commit

Permalink
Almost finished awtrix pack
Browse files Browse the repository at this point in the history
  • Loading branch information
Christopher Mühl committed Apr 14, 2021
1 parent 776226a commit 4b370a4
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 147 deletions.
18 changes: 14 additions & 4 deletions packages/awtrix/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/awtrix/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"hammerjs": "^2.0.8",
"lodash": "^4.17.20",
"lowdb": "^1.0.0",
"mkdirp": "^1.0.3",
"node-fetch": "^2.6.0",
"nodemon": "^2.0.7",
"on-change": "^2.1.4",
Expand All @@ -55,6 +54,7 @@
"socket.io": "^2.3.0",
"stylus": "^0.54.8",
"stylus-loader": "^3.0.2",
"tar": "^6.1.0",
"typed-vuex": "^0.1.22",
"vue": "^3.0.10",
"vue-flip-toolkit": "^1.6.0",
Expand All @@ -73,13 +73,13 @@
"@types/hammerjs": "^2.0.36",
"@types/lodash": "^4.14.159",
"@types/lowdb": "^1.0.9",
"@types/mkdirp": "^1.0.0",
"@types/node": "^13.9.1",
"@types/node-fetch": "^2.5.7",
"@types/puppeteer": "^3.0.1",
"@types/shortid": "0.0.29",
"@types/socket.io": "^2.1.11",
"@types/socket.io-client": "^1.4.34",
"@types/tar": "^4.0.4",
"@types/yargs": "^16.0.1",
"@types/yeoman-environment": "^2.10.2",
"@types/yeoman-generator": "^4.11.3",
Expand Down
3 changes: 1 addition & 2 deletions packages/awtrix/src/app/ApplicationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { exec } from 'child_process'
import axios from 'axios'
import fs from 'fs-extra'
import decompress from 'decompress'
import mkdirp from 'mkdirp'
import {
ApplicationIdentifier,
ApplicationConfig,
Expand Down Expand Up @@ -48,7 +47,7 @@ export default class ApplicationManager {
async download(app: ApplicationIdentifier) {
// First, make sure the app path exists
const appPath = this.path(app)
await mkdirp(appPath)
await fs.mkdirp(appPath)

// Fetch the download URL from npm
const url = await this.getDownloadUrl(app)
Expand Down
98 changes: 8 additions & 90 deletions packages/awtrix/src/cli/commands/pack.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
import { Arguments, CommandModule } from 'yargs'
import BuildService from '../services/buildService'
import PackService from '../services/packService'
import fs from 'fs'
import os from 'os'
import { ChildProcess, fork } from 'child_process'
import path from 'path'
// @ts-ignore
import copyTemplateDir from 'copy-template-dir'
import buildBackend from '../packer/buildBackend'
import buildFrontend from '../packer/buildFrontend'
import copyAssets from '../packer/copyAssets'
import copyPackageJson from '../packer/copyPackageJson'

process.on('unhandledRejection', (reason, promise) => {
console.log(promise)
})

interface DevCommandArguments extends Arguments {
home?: string,
Expand All @@ -28,88 +17,17 @@ export default {
},
handler: async (flags: DevCommandArguments) => {
// For testing
process.chdir('../example-app')
let target = path.join(process.cwd(), '../example-app')

const jsonPath = path.join(process.cwd(), 'package.json')
const jsonPath = path.join(target, 'package.json')
if (!fs.existsSync(jsonPath)) {
console.log('Could not find a "package.json" in your current directory.')
}

const service = new BuildService(jsonPath)
await service.run()
const build = new BuildService(jsonPath)
await build.run()

const pack = new PackService(jsonPath)
await pack.run()
},
} as CommandModule

class Start implements CommandModule {
childProcess?: ChildProcess

async handler() {
if (!fs.existsSync('package.json')) {
// return this.error('Could not find a "package.json" in your current directory.')
}

const packageJson = fs.readFileSync('package.json', 'utf-8')
let config = JSON.parse(packageJson)

try {
await this.createAwtrixHome(config)
await copyPackageJson(config)
await copyAssets(config, 'translations')

if (config.awtrix.frontend) await buildFrontend(config, null)
if (config.awtrix.backend) await buildBackend(config, null)
if (config.awtrix.assets) await copyAssets(config, 'assets')

this.startReloadingAwtrix(config)
} catch (error) {
// this.error(error)
}

// 1. Start watcher that compiles sources (extract from pack.ts)
// 3. Symlink watcher output (dist/) to the awtrix app directory
// 4. Create awtrix configuration that includes the current app
// 5. Start awtrix

// TODO: Find out if hot reloading for backend is possible
// TODO: Find out how to handle automatic reloading for assets
// TODO: Find out how to use hot reloading for compiled frontend component

// TODO: Automatically generate empty translations directory, if it doesn't
// already exist. Show warning to user so they know to add it.
// TODO: Don't raise if the assets directory is missing.
}

startReloadingAwtrix (config: any) {
this.restartAwtrixServer()

fs.watchFile(`.awtrix/apps/${config.name}/${config.version}/backend.js`, () => {
this.restartAwtrixServer()
})
}

async createAwtrixHome (config: any): Promise<void> {
return new Promise((resolve, reject) => {
const source = path.join(__dirname, '../../templates', 'development')
const variables = { name: config.name, version: config.version }

copyTemplateDir(source, '.awtrix', variables, (error: Error | null) => {
if (error) return reject(error)
resolve()
})
})
}

restartAwtrixServer () {
if (this.childProcess) {
this.childProcess.on('exit', () => {
this.childProcess = undefined
this.restartAwtrixServer()
})

this.childProcess.kill('SIGTERM')
return
}

this.childProcess = fork(path.join(__dirname, '../thread/awtrix.js'))
}
}
84 changes: 72 additions & 12 deletions packages/awtrix/src/cli/services/buildService.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import fs from 'fs'
import { dirname } from 'path'
import fs from 'fs-extra'
import path from 'path'
import { Types } from '@awtrix/common'
import { build as buildVue } from 'vite'
import vue from '@vitejs/plugin-vue'
import esbuild from 'esbuild'
import Service from './service'

export default class BuildService {
path: string
json: Types.Application.ApplicationConfig
interface BuildServiceConfig {
watch: boolean,
}

constructor (jsonPath: string) {
this.path = dirname(jsonPath)
this.json = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
export default class BuildService extends Service<BuildServiceConfig> {
defaultConfig = {
watch: false,
}

async run (config?: any) {
console.log(this.json, this.path)
async run (config?: Partial<BuildServiceConfig>) {
config = { ...this.defaultConfig, ...config }

const outDir = config.watch ? `.awtrix/apps/${this.json.name}/${this.json.version}` : 'dist'
await fs.mkdirp(path.join(this.path, outDir))

// It is important to run Vite first, because it will empty the outDir
if (this.json.build.frontend) await this.vite(config.watch)
if (this.json.build.backend) await this.backend(config.watch)
if (this.json.build.assets) await this.assets(config.watch)

await this.vite()
await this.packageJson()
await this.translations(config.watch)
}

async vite (watch: boolean = false) {
Expand All @@ -26,10 +37,11 @@ export default class BuildService {
vue(),
],
build: {
outDir: 'dist',
minify: false,
lib: {
entry: 'frontend.vue',
name: `AwtrixComponent.${this.json.name}`,
name: `AwtrixComponent.${this.json.name}@${this.json.version!.replace(/\./g, '-')}`,
formats: ['umd'],
},
rollupOptions: {
Expand All @@ -41,4 +53,52 @@ export default class BuildService {
},
})
}

async backend (watch: boolean = false) {
// We need to set the ESBUILD_BINARY_PATH so that `esbuild.build` can properly
// spawn the correct binary.
process.env.ESBUILD_BINARY_PATH = path.resolve(__dirname, '../node_modules/esbuild/bin/esbuild')

// TODO: Verify types with tsc

await esbuild.build({
entryPoints: [path.join(this.path, 'backend.ts')],
outfile: path.join(this.path, 'dist/backend.js'),
watch: false, // true for watch mode
})
}

async packageJson () {
const source = path.join(this.path, 'package.json')
const dest = path.join(this.path, 'dist/package.json')

// TODO: Figure out if we want to also copy the `package-lock.json`
await fs.copy(source, dest)
}

async assets (watch: boolean = false) {
const source = path.join(this.path, 'assets')
const dest = path.join(this.path, 'dist/assets')

await fs.copy(source, dest)

// // Watch for asset changes
// chokidar.watch(source).on('all', debounce(() => {
// fs.copySync(source, dest)
// }, 500))
}

async translations (watch: boolean = false) {
const source = path.join(this.path, 'translations')
const englishLocale = path.join(source, 'en.json')

// Create the translations if they don't already exist
if (!fs.pathExistsSync(source)) await fs.mkdirp(source)
if (!fs.pathExistsSync(englishLocale)) {
// TODO: Add log/info
await fs.writeFile(englishLocale, '{ }\n')
}

await fs.copy(source, path.join(this.path, 'dist/translations'))
}
}
27 changes: 27 additions & 0 deletions packages/awtrix/src/cli/services/packService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import tar from 'tar'
import fs from 'fs-extra'
import path from 'path'
import Service from './service'

interface PackServiceConfig {
sourceDir: string,
outFile: string
}

export default class PackService extends Service<PackServiceConfig> {
defaultConfig = {
sourceDir: path.join(this.path, 'dist'),
outFile: path.join(this.path, `${this.json.name}@${this.json.version}.tar.gz`),
}

async run (config?: Partial<PackServiceConfig>) {
config = { ...this.defaultConfig, ...config }

const dirContent = await fs.readdir(config.sourceDir!)
const archive = tar.create({
gzip: true, cwd: config.sourceDir!,
}, dirContent)

archive.pipe(fs.createWriteStream(config.outFile!))
}
}
16 changes: 16 additions & 0 deletions packages/awtrix/src/cli/services/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import fs from 'fs-extra'
import path from 'path'
import { Types } from '@awtrix/common'

export default abstract class Service<Config> {
defaultConfig!: Config
path: string
json: Types.Application.ApplicationConfig

constructor (jsonPath: string) {
this.path = path.dirname(jsonPath)
this.json = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'))
}

abstract run (config?: Partial<Config>): Promise<any>
}
1 change: 1 addition & 0 deletions packages/awtrix/templates/app/_.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
8 changes: 4 additions & 4 deletions packages/awtrix/templates/app/frontend.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<template>
<div>
{{name}}
{{ app.name }}@{{ app.version }}
</div>
</template>

<script lang="ts">
import { createFrontend } from '@awtrix/common'
import { defineComponent } from '@awtrix/common'
export default createFrontend({
export default defineComponent({
mounted () {
console.log(`${this.app.name} has been mounted.`)
},
})
</script>
1 change: 1 addition & 0 deletions packages/awtrix/templates/app/translations/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading

0 comments on commit 4b370a4

Please sign in to comment.