Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow partial configs in environment overrides #1026

Merged
merged 6 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/create-gasket-app/lib/commands/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ createCommand.action = async function run(appname, options, command) {

const pluginGasket = makeGasket({
...context.presetConfig,
plugins: context.presets.concat(context.presetConfig.plugins)
plugins: context.presetConfig.plugins.concat(context.presets)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just an annoying limitation of TypeScript. Because context.presets is of type Array<Plugin> it cannot concatenate in Array<Plugin | { default: Plugin }> because concat is defined such where the return value is supposed to be the same as the original array. However, it can do this in the reverse order. I assume that the order of plugins doesn't matter.

});

await promptHooks({ gasket: pluginGasket, context });
Expand Down
2 changes: 2 additions & 0 deletions packages/gasket-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `@gasket/core`

- Fix configuration options so they allow partial environment overrides ([#1026])

### 7.2.0

- Adjust types GasketTrace
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-core/lib/gasket.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class Gasket {
// prune nullish and/or empty plugins
config.plugins = config.plugins
.filter(Boolean)
.map(plugin => plugin.default || plugin) // quality of life for cjs apps
.map(plugin => 'default' in plugin ? plugin.default : plugin) // quality of life for cjs apps
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript is able to use an in check to narrow a type; seeing if a default property is falsy doesn't do the same, so the old expression wasn't able to be resolved as a Plugin object.

.filter(plugin => Boolean(plugin.name) || Boolean(plugin.hooks));

// start the engine
Expand Down
16 changes: 13 additions & 3 deletions packages/gasket-core/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ declare module '@gasket/core' {
env: string;
}

export type PreNormalizedGasketConfig = Omit<GasketConfig, 'plugins'> & {
plugins: Array<Plugin | { default: Plugin }>;
}

export class GasketEngine {
constructor(plugins: Array<Plugin>);

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

export type GasketConfigDefinition = Omit<GasketConfig, 'root' | 'env' | 'command'> & {
// Allow nested merging of most config
type ConfigKeysRequiringFullEnvConfig = 'plugins';
type GasketConfigOverrides =
& PartialRecursive<Omit<GasketConfigDefinition, ConfigKeysRequiringFullEnvConfig>>
& Partial<Pick<GasketConfigDefinition, ConfigKeysRequiringFullEnvConfig>>;

export type GasketConfigDefinition = Omit<PreNormalizedGasketConfig, 'root' | 'env' | 'command'> & {
root?: string
env?: string
environments?: Record<string, Partial<GasketConfigDefinition>>
commands?: Record<string, Partial<GasketConfigDefinition>>
environments?: Record<string, GasketConfigOverrides>
commands?: Record<string, GasketConfigOverrides>
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ declare module '@gasket/core' {
interface HookExecTypes {
example(str: string, num: number, bool: boolean): MaybeAsync<boolean>
}

interface GasketConfig {
someConfigSection?: {
foo: string;
bar: number;
}
}
}

const PluginExample = {
Expand All @@ -21,19 +28,35 @@ const PluginExample = {
describe('@gasket/core', () => {
it('exposes the constructor interface', () => {
// eslint-disable-next-line no-new
makeGasket({ plugins: [PluginExample] });
makeGasket({
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
}
});
});

it('checks constructor arguments', () => {
// eslint-disable-next-line no-new
makeGasket({
plugins: [PluginExample]
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
}
// @ts-expect-error
}, 'extra');
});

it('should infer the types of lifecycle parameters', async function () {
const gasket = makeGasket({ plugins: [PluginExample] });
const gasket = makeGasket({
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
}
});

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

it('type checks the hook method', () => {
const gasket = makeGasket({ plugins: [PluginExample] });
const gasket = makeGasket({
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
}
});

// Valid
gasket.hook({
Expand Down Expand Up @@ -89,7 +118,13 @@ describe('@gasket/core', () => {
});

it('exposes the running command on the Gasket interface', () => {
const gasket = makeGasket({ plugins: [PluginExample] });
const gasket = makeGasket({
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
}
});

// Valid
gasket.hook({
Expand All @@ -116,4 +151,21 @@ describe('@gasket/core', () => {
}
};
});

it('allows partial environment config overrides', () => {
const config: GasketConfigDefinition = {
plugins: [PluginExample],
someConfigSection: {
foo: 'foo',
bar: 123
},
environments: {
dev: {
someConfigSection: {
foo: 'dev-foo'
}
}
}
};
});
});
6 changes: 3 additions & 3 deletions packages/gasket-typescript-tests/test/utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { applyConfigOverrides, runShellCommand } from '@gasket/utils';
import { GasketConfig, GasketConfigDefinition } from '@gasket/core';
import { GasketConfig } from '@gasket/core';

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

describe('applyConfigOverrides', function () {
const config: GasketConfigDefinition = {
const config: GasketConfig = {
plugins: [{ name: 'example', version: '', description: '', hooks: {} }],
root: '/',
env: 'debug'
};

it('has expected API', function () {
if (perform) {
let result:GasketConfig;
let result: GasketConfig;
result = applyConfigOverrides(config, { env: 'test' });
result = applyConfigOverrides(config, { env: 'test', commandId: 'test' });

Expand Down
2 changes: 2 additions & 0 deletions packages/gasket-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# `@gasket/utils`

- Fix type of environment overrides ([#1026])

### 7.1.2

- Ensure non-plain objects are copied instead of merge ([#1002])
Expand Down
30 changes: 17 additions & 13 deletions packages/gasket-utils/lib/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,37 @@ interface ConfigContext {
commandId?: string;
}

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

type ConfigDefinition<T> = T & {
environments?: Record<string, PartialRecursive<T>>;
commands?: Record<string, PartialRecursive<T>>;
}

type ConfigOutput = Omit<ConfigDefinition, 'environments' | 'commands'>;
type ConfigPartial = Partial<ConfigDefinition> | undefined | void | unknown;
type ConfigPartial<T extends ConfigDefinition> = PartialRecursive<T> | undefined | void | unknown;

export function getPotentialConfigs(
config: ConfigDefinition,
configContext: ConfigContext
): Array<ConfigPartial>;

export function getCommandOverrides(
commands: Record<string, Partial<ConfigDefinition>>,
commands: Record<string, PartialRecursive<ConfigDefinition>>,
commandId: string
): Array<ConfigPartial>;

export function getSubEnvironmentOverrides(
env: string,
environments: Record<string, Partial<ConfigDefinition>>
environments: Record<string, PartialRecursive<ConfigDefinition>>
): Array<ConfigPartial>;

export function getDevOverrides(
isLocalEnv: boolean,
environments: Record<string, Partial<ConfigDefinition>>
environments: Record<string, PartialRecursive<ConfigDefinition>>
): Array<ConfigPartial>;

export async function getLatestVersion(
Expand All @@ -51,7 +55,7 @@ export function getLocalOverrides(

// Normalize the config by applying any overrides for environments, commands, or
// local-only config file.
export function applyConfigOverrides<
Def extends ConfigDefinition,
Out extends ConfigOutput
>(config: Def, configContext: ConfigContext): Out;
export function applyConfigOverrides<Config>(
config: ConfigDefinition<Config>,
configContext: ConfigContext
): Config;
Loading