Skip to content

Commit

Permalink
feat(core): add fromBuildIdentifier helper for dynamic at-build-time …
Browse files Browse the repository at this point in the history
…config

* better tests
* proxy arrays as well as objects in proxify magic
  • Loading branch information
MarshallOfSound committed May 1, 2018
1 parent 8728baa commit dc6c9fc
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 9 deletions.
17 changes: 17 additions & 0 deletions packages/api/core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import _package, { PackageOptions } from './package';
import publish, { PublishOptions } from './publish';
import start, { StartOptions } from './start';

import { fromBuildIdentifier } from '../util/forge-config';

export class ForgeAPI {
/**
* Attempt to import a given module directory to the Electron Forge standard.
Expand Down Expand Up @@ -72,7 +74,21 @@ export class ForgeAPI {
}
}

export class ForgeUtils {
/**
* Helper for creating a dynamic config value that will get it's real value
* based on the "buildIdentifier" in your forge config.
*
* Usage:
* `fromBuildIdentifier({ stable: 'App', beta: 'App Beta' })`
*/
fromBuildIdentifier<T>(map: { [key: string]: T | undefined }) {
return fromBuildIdentifier(map);
}
}

const api = new ForgeAPI();
const utils = new ForgeUtils();

export {
ForgeMakeResult,
Expand All @@ -86,4 +102,5 @@ export {
PublishOptions,
StartOptions,
api,
utils,
};
32 changes: 24 additions & 8 deletions packages/api/core/src/util/forge-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,40 @@ import PluginInterface from './plugin-interface';

const underscoreCase = (str: string) => str.replace(/(.)([A-Z][a-z]+)/g, '$1_$2').replace(/([a-z0-9])([A-Z])/g, '$1_$2').toUpperCase();

const proxify = <T extends object>(object: T, envPrefix: string): T => {
const newObject: T = {} as any;
const proxify = <T extends object>(buildIdentifier: string | (() => string), object: T, envPrefix: string): T => {
let newObject: T = {} as any;
if (Array.isArray(object)) {
newObject = [] as any;
}

Object.keys(object).forEach((key) => {
if (typeof (object as any)[key] === 'object' && !Array.isArray((object as any)[key]) && key !== 'pluginInterface') {
(newObject as any)[key] = proxify((object as any)[key], `${envPrefix}_${underscoreCase(key)}`);
if (typeof (object as any)[key] === 'object' && key !== 'pluginInterface') {
(newObject as any)[key] = proxify(buildIdentifier, (object as any)[key], `${envPrefix}_${underscoreCase(key)}`);
} else {
(newObject as any)[key] = (object as any)[key];
}
});

return new Proxy<T>(newObject, {
get(target, name) {
get(target, name, receiver) {
// eslint-disable-next-line no-prototype-builtins
if (!target.hasOwnProperty(name) && typeof name === 'string') {
const envValue = process.env[`${envPrefix}_${underscoreCase(name)}`];
if (envValue) return envValue;
}
return (target as any)[name];
const value = Reflect.get(target, name, receiver);

if (value && typeof value === 'object' && value.__isMagicBuildIdentifierMap) {
const identifier = typeof buildIdentifier === 'function' ? buildIdentifier() : buildIdentifier;
return value.map[identifier];
}
return value;
},
getOwnPropertyDescriptor(target, name) {
const envValue = process.env[`${envPrefix}_${underscoreCase(name as string)}`];
// eslint-disable-next-line no-prototype-builtins
if (target.hasOwnProperty(name)) {
return Object.getOwnPropertyDescriptor(target, name);
return Reflect.getOwnPropertyDescriptor(target, name);
} else if (envValue) {
return { writable: true, enumerable: true, configurable: true, value: envValue };
}
Expand All @@ -51,6 +60,13 @@ export function setInitialForgeConfig(packageJSON: any) {
/* eslint-enable no-param-reassign */
}

export function fromBuildIdentifier<T>(map: { [key: string]: T | undefined }) {
return {
__isMagicBuildIdentifierMap: true,
map,
};
}

export default async (dir: string) => {
const packageJSON = await readPackageJSON(dir);
let forgeConfig: ForgeConfig | string = packageJSON.config.forge;
Expand Down Expand Up @@ -91,5 +107,5 @@ export default async (dir: string) => {

forgeConfig.pluginInterface = new PluginInterface(dir, forgeConfig);

return proxify<ForgeConfig>(forgeConfig, 'ELECTRON_FORGE');
return proxify<ForgeConfig>(forgeConfig.buildIdentifier || '', forgeConfig, 'ELECTRON_FORGE');
};
33 changes: 32 additions & 1 deletion packages/api/core/test/fast/forge-config_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const defaults = {
plugins: [],
};

describe('forge-config', () => {
describe.only('forge-config', () => {
it('should resolve the object in package.json with defaults if one exists', async () => {
const config = await findConfig(path.resolve(__dirname, '../fixture/dummy_app'));
delete config.pluginInterface;
Expand Down Expand Up @@ -54,7 +54,11 @@ describe('forge-config', () => {
it('should resolve the JS file exports in config.forge points to a JS file', async () => {
const config = JSON.parse(JSON.stringify(await findConfig(path.resolve(__dirname, '../fixture/dummy_js_conf'))));
delete config.pluginInterface;
delete config.sub;
delete config.topLevelProp;
delete config.topLevelUndef;
expect(config).to.be.deep.equal(Object.assign({}, defaults, {
buildIdentifier: 'beta',
packagerConfig: { foo: 'bar', baz: {} },
s3: {},
electronReleaseServer: {},
Expand All @@ -78,4 +82,31 @@ describe('forge-config', () => {
delete process.env.ELECTRON_FORGE_S3_SECRET_ACCESS_KEY;
delete process.env.ELECTRON_FORGE_ELECTRON_RELEASE_SERVER_BASE_URL;
});

it('should resolve values fromBuildIdentifier', async () => {
const conf: any = await findConfig(path.resolve(__dirname, '../fixture/dummy_js_conf'));
expect(conf.topLevelProp).to.equal('foo');
expect(conf.sub).to.deep.equal({
prop: {
deep: {
prop: 'bar'
},
inArray: [
'arr',
'natural',
'array'
]
},
});
});

it('should resolve undefined from fromBuildIdentifier if no value is provided', async () => {
const conf: any = await findConfig(path.resolve(__dirname, '../fixture/dummy_js_conf'));
expect(conf.topLevelUndef).to.equal(undefined);
});

it('should leave arrays intact', async () => {
const conf: any = await findConfig(path.resolve(__dirname, '../fixture/dummy_js_conf'));
expect(Array.isArray(conf.sub.prop.inArray)).to.equal(true, 'original array should be recognized as array');
});
});
17 changes: 17 additions & 0 deletions packages/api/core/test/fixture/dummy_js_conf/forge.config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
const { fromBuildIdentifier } = require('../../../src/api');

module.exports = {
buildIdentifier: 'beta',
makers: [],
publishers: [],
packagerConfig: { foo: 'bar', baz: {} },
s3: {},
electronReleaseServer: {},
magicFn: () => 'magic result',
topLevelProp: fromBuildIdentifier({ beta: 'foo' }),
topLevelUndef: fromBuildIdentifier({ stable: 'heya' }),
sub: {
prop: {
inArray: [
fromBuildIdentifier({ beta: 'arr' }),
'natural',
'array',
],
deep: {
prop: fromBuildIdentifier({ beta: 'bar' }),
},
},
},
};

0 comments on commit dc6c9fc

Please sign in to comment.