Skip to content

Commit 02c4748

Browse files
Allow partial configs in environment overrides (#1026)
* Allow partial configs in environment overrides Fix the TypeScript types so that you don't have to do a full set of config keys in environment overrides * Changelog * Fix utility file types * Work around the `default` exception * Accommodate CJS plugins * Work around TypeScript limitation
1 parent 13e6699 commit 02c4748

File tree

8 files changed

+96
-26
lines changed

8 files changed

+96
-26
lines changed

packages/create-gasket-app/lib/commands/create.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ createCommand.action = async function run(appname, options, command) {
118118

119119
const pluginGasket = makeGasket({
120120
...context.presetConfig,
121-
plugins: context.presets.concat(context.presetConfig.plugins)
121+
plugins: context.presetConfig.plugins.concat(context.presets)
122122
});
123123

124124
await promptHooks({ gasket: pluginGasket, context });

packages/gasket-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# `@gasket/core`
22

3+
- Fix configuration options so they allow partial environment overrides ([#1026])
4+
35
### 7.2.0
46

57
- Adjust types GasketTrace

packages/gasket-core/lib/gasket.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Gasket {
3838
// prune nullish and/or empty plugins
3939
config.plugins = config.plugins
4040
.filter(Boolean)
41-
.map(plugin => plugin.default || plugin) // quality of life for cjs apps
41+
.map(plugin => 'default' in plugin ? plugin.default : plugin) // quality of life for cjs apps
4242
.filter(plugin => Boolean(plugin.name) || Boolean(plugin.hooks));
4343

4444
// start the engine

packages/gasket-core/lib/index.d.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ declare module '@gasket/core' {
7070
env: string;
7171
}
7272

73+
export type PreNormalizedGasketConfig = Omit<GasketConfig, 'plugins'> & {
74+
plugins: Array<Plugin | { default: Plugin }>;
75+
}
76+
7377
export class GasketEngine {
7478
constructor(plugins: Array<Plugin>);
7579

@@ -133,11 +137,17 @@ declare module '@gasket/core' {
133137
? { [K in keyof T]?: PartialRecursive<T[K]> } | undefined
134138
: T | undefined;
135139

136-
export type GasketConfigDefinition = Omit<GasketConfig, 'root' | 'env' | 'command'> & {
140+
// Allow nested merging of most config
141+
type ConfigKeysRequiringFullEnvConfig = 'plugins';
142+
type GasketConfigOverrides =
143+
& PartialRecursive<Omit<GasketConfigDefinition, ConfigKeysRequiringFullEnvConfig>>
144+
& Partial<Pick<GasketConfigDefinition, ConfigKeysRequiringFullEnvConfig>>;
145+
146+
export type GasketConfigDefinition = Omit<PreNormalizedGasketConfig, 'root' | 'env' | 'command'> & {
137147
root?: string
138148
env?: string
139-
environments?: Record<string, Partial<GasketConfigDefinition>>
140-
commands?: Record<string, Partial<GasketConfigDefinition>>
149+
environments?: Record<string, GasketConfigOverrides>
150+
commands?: Record<string, GasketConfigOverrides>
141151
}
142152

143153
/**

packages/gasket-typescript-tests/test/engine.spec.ts renamed to packages/gasket-typescript-tests/test/core.spec.ts

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ declare module '@gasket/core' {
55
interface HookExecTypes {
66
example(str: string, num: number, bool: boolean): MaybeAsync<boolean>
77
}
8+
9+
interface GasketConfig {
10+
someConfigSection?: {
11+
foo: string;
12+
bar: number;
13+
}
14+
}
815
}
916

1017
const PluginExample = {
@@ -21,19 +28,35 @@ const PluginExample = {
2128
describe('@gasket/core', () => {
2229
it('exposes the constructor interface', () => {
2330
// eslint-disable-next-line no-new
24-
makeGasket({ plugins: [PluginExample] });
31+
makeGasket({
32+
plugins: [PluginExample],
33+
someConfigSection: {
34+
foo: 'foo',
35+
bar: 123
36+
}
37+
});
2538
});
2639

2740
it('checks constructor arguments', () => {
2841
// eslint-disable-next-line no-new
2942
makeGasket({
30-
plugins: [PluginExample]
43+
plugins: [PluginExample],
44+
someConfigSection: {
45+
foo: 'foo',
46+
bar: 123
47+
}
3148
// @ts-expect-error
3249
}, 'extra');
3350
});
3451

3552
it('should infer the types of lifecycle parameters', async function () {
36-
const gasket = makeGasket({ plugins: [PluginExample] });
53+
const gasket = makeGasket({
54+
plugins: [PluginExample],
55+
someConfigSection: {
56+
foo: 'foo',
57+
bar: 123
58+
}
59+
});
3760

3861
await gasket.execApply('example', async function (plugin, handler) {
3962
handler('a string', 123, true);
@@ -60,7 +83,13 @@ describe('@gasket/core', () => {
6083
});
6184

6285
it('type checks the hook method', () => {
63-
const gasket = makeGasket({ plugins: [PluginExample] });
86+
const gasket = makeGasket({
87+
plugins: [PluginExample],
88+
someConfigSection: {
89+
foo: 'foo',
90+
bar: 123
91+
}
92+
});
6493

6594
// Valid
6695
gasket.hook({
@@ -89,7 +118,13 @@ describe('@gasket/core', () => {
89118
});
90119

91120
it('exposes the running command on the Gasket interface', () => {
92-
const gasket = makeGasket({ plugins: [PluginExample] });
121+
const gasket = makeGasket({
122+
plugins: [PluginExample],
123+
someConfigSection: {
124+
foo: 'foo',
125+
bar: 123
126+
}
127+
});
93128

94129
// Valid
95130
gasket.hook({
@@ -116,4 +151,21 @@ describe('@gasket/core', () => {
116151
}
117152
};
118153
});
154+
155+
it('allows partial environment config overrides', () => {
156+
const config: GasketConfigDefinition = {
157+
plugins: [PluginExample],
158+
someConfigSection: {
159+
foo: 'foo',
160+
bar: 123
161+
},
162+
environments: {
163+
dev: {
164+
someConfigSection: {
165+
foo: 'dev-foo'
166+
}
167+
}
168+
}
169+
};
170+
});
119171
});

packages/gasket-typescript-tests/test/utils.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { applyConfigOverrides, runShellCommand } from '@gasket/utils';
2-
import { GasketConfig, GasketConfigDefinition } from '@gasket/core';
2+
import { GasketConfig } from '@gasket/core';
33

44
describe('@gasket/utils', function () {
55
const perform = false;
66

77
describe('applyConfigOverrides', function () {
8-
const config: GasketConfigDefinition = {
8+
const config: GasketConfig = {
99
plugins: [{ name: 'example', version: '', description: '', hooks: {} }],
1010
root: '/',
1111
env: 'debug'
1212
};
1313

1414
it('has expected API', function () {
1515
if (perform) {
16-
let result:GasketConfig;
16+
let result: GasketConfig;
1717
result = applyConfigOverrides(config, { env: 'test' });
1818
result = applyConfigOverrides(config, { env: 'test', commandId: 'test' });
1919

packages/gasket-utils/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# `@gasket/utils`
22

3+
- Fix type of environment overrides ([#1026])
4+
35
### 7.1.2
46

57
- Ensure non-plain objects are copied instead of merge ([#1002])

packages/gasket-utils/lib/config.d.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,37 @@ interface ConfigContext {
77
commandId?: string;
88
}
99

10-
interface ConfigDefinition extends Record<string, any> {
11-
environments?: Record<string, Partial<ConfigDefinition>>;
12-
commands?: Record<string, Partial<ConfigDefinition>>;
13-
[key: string]: any;
10+
// TODO: switch @gasket/core to re-exporting this type once this change is
11+
// published and we can update its dependency to this version
12+
export type PartialRecursive<T> = T extends Object
13+
? { [K in keyof T]?: PartialRecursive<T[K]> } | undefined
14+
: T | undefined;
15+
16+
type ConfigDefinition<T> = T & {
17+
environments?: Record<string, PartialRecursive<T>>;
18+
commands?: Record<string, PartialRecursive<T>>;
1419
}
1520

16-
type ConfigOutput = Omit<ConfigDefinition, 'environments' | 'commands'>;
17-
type ConfigPartial = Partial<ConfigDefinition> | undefined | void | unknown;
21+
type ConfigPartial<T extends ConfigDefinition> = PartialRecursive<T> | undefined | void | unknown;
1822

1923
export function getPotentialConfigs(
2024
config: ConfigDefinition,
2125
configContext: ConfigContext
2226
): Array<ConfigPartial>;
2327

2428
export function getCommandOverrides(
25-
commands: Record<string, Partial<ConfigDefinition>>,
29+
commands: Record<string, PartialRecursive<ConfigDefinition>>,
2630
commandId: string
2731
): Array<ConfigPartial>;
2832

2933
export function getSubEnvironmentOverrides(
3034
env: string,
31-
environments: Record<string, Partial<ConfigDefinition>>
35+
environments: Record<string, PartialRecursive<ConfigDefinition>>
3236
): Array<ConfigPartial>;
3337

3438
export function getDevOverrides(
3539
isLocalEnv: boolean,
36-
environments: Record<string, Partial<ConfigDefinition>>
40+
environments: Record<string, PartialRecursive<ConfigDefinition>>
3741
): Array<ConfigPartial>;
3842

3943
export async function getLatestVersion(
@@ -51,7 +55,7 @@ export function getLocalOverrides(
5155

5256
// Normalize the config by applying any overrides for environments, commands, or
5357
// local-only config file.
54-
export function applyConfigOverrides<
55-
Def extends ConfigDefinition,
56-
Out extends ConfigOutput
57-
>(config: Def, configContext: ConfigContext): Out;
58+
export function applyConfigOverrides<Config>(
59+
config: ConfigDefinition<Config>,
60+
configContext: ConfigContext
61+
): Config;

0 commit comments

Comments
 (0)