Skip to content

Commit

Permalink
chore: add more proper env file integration
Browse files Browse the repository at this point in the history
  • Loading branch information
achou11 committed Oct 31, 2024
1 parent dde4961 commit 8bc0059
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 43 deletions.
4 changes: 4 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ONLINE_STYLE_URL=<value> # URL pointing to an online map's StyleJSON.

# USER_DATA_PATH=<value> # Sets the user data directory for the app.
# ASAR=<value> # Determines if the ASAR format is used when packaging the app. Must literally be true or false.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ node_modules/

# dotenv environment variables file
.env
.env.test
.env.*

# Electron-Forge
out/
Expand Down
6 changes: 5 additions & 1 deletion docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ Make sure you have the desired Node version installed. For this project we encou

### Environment variables

Create a copy of the [`.env.template`](../.env.template) and call it `.env`. At the moment, we do not make use of this, but this will most likely change soon.
Create a copy of the [`.env.template`](../.env.template) and call it `.env` and update following variables:

- `ONLINE_STYLE_URL`: Full URL that points to a compatible map's StyleJSON

### Running the app

Expand Down Expand Up @@ -105,6 +107,8 @@ The [Electron Forge docs](https://www.electronforge.io/) are pretty informative

All commands place the built assets in the `out/` directory.

Our build process requires a `.env.production` file to exist at the project root in order for these commands to work. Usually this file will be generated as a prerequisite to packaging the app (e.g. in continuous deployment environments like GitHub Actions). If you are just debugging locally, you can create a copy of your `.env` file and call it `.env.production` in order to avoid errors at build time.

If you're running into an error with any of the Forge-related commands but not seeing any output in the console, you probably have to prefix the command with `DEBUG=electron-forge` e.g. `DEBUG=electron-forge npm run forge:package`.

By default, we package the app in the [ASAR](https://github.com/electron/asar) format. However, it can be helpful to avoid doing that for debugging purposes (e.g. building locally), in which case you can specify a `ASAR=true` environment variable when running the relevant Forge command e.g. `ASAR=true npm run forge:package`.
48 changes: 28 additions & 20 deletions forge.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { FusesPlugin } from '@electron-forge/plugin-fuses'
import { FuseV1Options, FuseVersion } from '@electron/fuses'
import { build, createServer } from 'vite'

import 'dotenv/config'

/**
* @import {ForgeConfig, ForgeHookFn} from '@electron-forge/shared-types'
* @import {ViteDevServer} from 'vite'
Expand All @@ -21,6 +23,10 @@ const RENDERER_VITE_CONFIG_PATH = fileURLToPath(
new URL('./src/renderer/vite.config.js', import.meta.url),
)

const PRODUCTION_ENV_FILE_PATH = fileURLToPath(
new URL('./.env.production', import.meta.url),
)

/**
* @extends {PluginBase<{}>}
*/
Expand Down Expand Up @@ -52,7 +58,6 @@ class CoMapeoDesktopForgePlugin extends PluginBase {
*/
getHooks() {
return {
readPackageJson: [this.#addAppEnvToPackageJson],
resolveForgeConfig: [this.#updatePackagerConfig],
postStart: [this.#hookViteDevServer],
prePackage: [this.#buildRender],
Expand Down Expand Up @@ -105,21 +110,6 @@ class CoMapeoDesktopForgePlugin extends PluginBase {
})
}

/**
* Kind of a lazy way of defining env-variable configuration for the app when
* packaged. Might re-consider and use a proper env file loader approach
* instead.
*
* @type {ForgeHookFn<'readPackageJson'>}
*/
async #addAppEnvToPackageJson(forgeConfig, packageJson) {
packageJson.appEnv = {
asar: forgeConfig.packagerConfig.asar,
}

return packageJson
}

/**
* Updates `packagerConfig.ignore` to exclude unnecessary files and
* directories from the final package output.
Expand All @@ -131,9 +121,15 @@ class CoMapeoDesktopForgePlugin extends PluginBase {

const ignoresToAppend = [
// Unnecessary directories
/^\/(messages|data|docs|\.husky|patches|\.github)/,
// Unecessary files
/^\/(\.env\.template|.eslintcache|\.gitignore|.*\.config\.js|\.prettier.*|\.nvmrc|\.tool-versions)/,
/^\/(\.github|\.husky|assets|data|docs|messages|patches)/,
// Unnecessary files
/^\/\.env/,
/^\/.*\.config\.js$/,
/^\/\.eslintcache$/,
/^\/\.gitignore$/,
/^\/\.nvmrc$/,
/^\/\.prettierignore$/,
/^\/\.tool-versions$/,
]

if (existingIgnores) {
Expand Down Expand Up @@ -184,8 +180,20 @@ class CoMapeoDesktopForgePlugin extends PluginBase {
// no-op if out path doesn't exist
}

await fs.mkdir(outPath)
await fs.mkdir(outPath, { recursive: true })
await fs.rename(path.join(buildPath, './dist/renderer'), outPath)

try {
await fs.copyFile(
PRODUCTION_ENV_FILE_PATH,
path.join(buildPath, '.env.production'),
)
} catch (err) {
throw new Error(
'Failed to copy over production env file. Confirm that it is present.',
{ cause: err },
)
}
}

#cleanUpVite = () => {
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"@formatjs/intl": "^2.10.11",
"@mapeo/default-config": "5.0.0",
"debug": "^4.3.7",
"dotenv": "^16.4.5",
"electron-is-dev": "^3.0.1",
"electron-squirrel-startup": "^1.0.1",
"electron-store": "^10.0.0",
Expand Down
11 changes: 7 additions & 4 deletions src/main/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const log = debug('comapeo:main:app')
* @import {UtilityProcess} from 'electron/main'
* @import {ProcessArgs as CoreProcessArgs, NewClientMessage} from '../services/core.js'
* @import {ConfigStore} from './config-store.js'
* @import {AppMode} from './utils.js'
* @import {AppEnv, AppMode} from './utils.js'
*/

/**
Expand Down Expand Up @@ -59,12 +59,13 @@ const APP_STATE = {

/**
* @param {Object} opts
* @param {AppEnv} opts.appEnv
* @param {AppMode} opts.appMode
* @param {ConfigStore} opts.configStore
*
* @returns {Promise<void>}
*/
export async function start({ appMode, configStore }) {
export async function start({ appEnv, appMode, configStore }) {
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
Expand All @@ -89,7 +90,7 @@ export async function start({ appMode, configStore }) {
await app.whenReady()

const rootKey = loadRootKey({ configStore })
const services = setupServices({ rootKey })
const services = setupServices({ appEnv, rootKey })

app.on('activate', () => {
log('App activated')
Expand Down Expand Up @@ -268,13 +269,15 @@ function loadRootKey({ configStore }) {

/**
* @param {Object} opts
* @param {AppEnv} opts.appEnv
* @param {string} opts.rootKey
*
* @returns {Services}
*/
function setupServices({ rootKey }) {
function setupServices({ appEnv, rootKey }) {
/** @satisfies {CoreProcessArgs} */
const coreArgs = {
onlineStyleUrl: appEnv.onlineStyleUrl,
rootKey,
storageDirectory: app.getPath('userData'),
}
Expand Down
22 changes: 12 additions & 10 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import { app } from 'electron/main'

import { start } from './app.js'
import { createConfigStore } from './config-store.js'
import { getAppMode } from './utils.js'
import { getAppEnv, getAppMode } from './utils.js'

const require = createRequire(import.meta.url)
import 'dotenv/config'

const packageJson = require('../../package.json')
const require = createRequire(import.meta.url)

const log = debug('comapeo:main:index')

// @ts-expect-error Not worth trying to make TS happy
if (packageJson.asar === false) {
const appEnv = getAppEnv()

if (appEnv.asar === false) {
process.noAsar = true
}

Expand All @@ -33,12 +34,12 @@ if (appMode === 'development') {
/** @type {string} */
let userDataPath

if (process.env.USER_DATA_PATH) {
userDataPath = path.isAbsolute(process.env.USER_DATA_PATH)
? path.resolve(process.env.USER_DATA_PATH)
: path.resolve(appPath, process.env.USER_DATA_PATH)
if (appEnv.userDataPath) {
userDataPath = path.isAbsolute(appEnv.userDataPath)
? path.resolve(appEnv.userDataPath)
: path.resolve(appPath, appEnv.userDataPath)
} else {
userDataPath = path.resolve(appPath, 'data')
userDataPath = path.join(appPath, 'data')
}

app.setPath('userData', userDataPath)
Expand All @@ -47,6 +48,7 @@ if (appMode === 'development') {
const configStore = createConfigStore()

start({
appEnv,
appMode,
configStore,
})
Expand Down
29 changes: 29 additions & 0 deletions src/main/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
import isDev from 'electron-is-dev'
import * as v from 'valibot'

const AppEnvSchema = v.object({
asar: v.optional(
v.pipe(
v.union([v.literal('true'), v.literal('false')]),
v.transform((value) => {
return value === 'true'
}),
),
),
onlineStyleUrl: v.pipe(v.string(), v.url()),
userDataPath: v.optional(v.string()),
})

/**
* @typedef {v.InferOutput<typeof AppEnvSchema>} AppEnv
*/

/**
* @returns {AppEnv}
*/
export function getAppEnv() {
return v.parse(AppEnvSchema, {
asar: process.env.ASAR,
onlineStyleUrl: process.env.ONLINE_STYLE_URL,
userDataPath: process.env.USER_DATA_PATH,
})
}

/**
* @typedef {'development' | 'production'} AppMode
Expand Down
14 changes: 7 additions & 7 deletions src/services/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,13 @@ const DEFAULT_CONFIG_PATH = fileURLToPath(
),
)

// TODO: Read from env or something
const MAP_ACCESS_TOKEN =
'pk.eyJ1IjoiZGlnaWRlbSIsImEiOiJjbHgzbTU5aDYweGVwMmtwdGV1bWgxMmJ2In0.dwyVZFnVvqrCqXicHsvE6Q'
const DEFAULT_ONLINE_MAP_STYLE_URL = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v11?access_token=${MAP_ACCESS_TOKEN}`

// Do not touch these!
const DB_DIR_NAME = 'sqlite-dbs'
const CORE_STORAGE_DIR_NAME = 'core-storage'
const CUSTOM_MAPS_DIR_NAME = 'maps'

const ProcessArgsSchema = v.object({
onlineStyleUrl: v.pipe(v.string(), v.url()),
rootKey: v.pipe(v.string(), v.hexadecimal()),
storageDirectory: v.string(),
})
Expand Down Expand Up @@ -103,6 +99,7 @@ let state = {
const { values } = parseArgs({
strict: true,
options: {
onlineStyleUrl: { type: 'string' },
rootKey: { type: 'string' },
storageDirectory: { type: 'string' },
},
Expand All @@ -115,6 +112,7 @@ const rootKey = Buffer.from(parsedProcessArgs.rootKey, 'hex')
assert(rootKey.byteLength === 16, 'Root key must be 16 bytes')

const { manager, fastifyController } = initializeCore({
onlineStyleUrl: parsedProcessArgs.onlineStyleUrl,
rootKey,
storageDirectory: parsedProcessArgs.storageDirectory,
})
Expand Down Expand Up @@ -153,6 +151,7 @@ process.parentPort.on('message', (event) => {
// Initialize core and set up the RPC connection
case 'idle': {
const { manager, fastifyController } = initializeCore({
onlineStyleUrl: parsedProcessArgs.onlineStyleUrl,
rootKey,
storageDirectory: parsedProcessArgs.storageDirectory,
})
Expand Down Expand Up @@ -202,10 +201,11 @@ process.parentPort.on('message', (event) => {

/**
* @param {Object} opts
* @param {string} opts.onlineStyleUrl
* @param {Buffer} opts.rootKey
* @param {string} opts.storageDirectory
*/
function initializeCore({ rootKey, storageDirectory }) {
function initializeCore({ onlineStyleUrl, rootKey, storageDirectory }) {
const databaseDirectory = path.join(storageDirectory, DB_DIR_NAME)
const coreStorageDirectory = path.join(
storageDirectory,
Expand All @@ -232,7 +232,7 @@ function initializeCore({ rootKey, storageDirectory }) {
),
fastify,
defaultConfigPath: DEFAULT_CONFIG_PATH,
defaultOnlineStyleUrl: DEFAULT_ONLINE_MAP_STYLE_URL,
defaultOnlineStyleUrl: onlineStyleUrl,
// TODO: Specify
// customMapPath: undefined
})
Expand Down

0 comments on commit 8bc0059

Please sign in to comment.