Skip to content

Commit

Permalink
feat: feature flags (#853)
Browse files Browse the repository at this point in the history
Co-authored-by: Aenimus <daviditstutt@gmail.com>
Co-authored-by: Aenimus <47415099+Aenimus@users.noreply.github.com>
Co-authored-by: starptech <deusdustin@gmail.com>
Co-authored-by: Sergiy 🇺🇦 <818351+devsergiy@users.noreply.github.com>
Co-authored-by: hardworker-bot <bot@wundergraph.com>
Co-authored-by: Nithin Kumar B <nithinkumar5353@gmail.com>
  • Loading branch information
7 people authored Jul 3, 2024
1 parent 974ac21 commit 5461bb5
Show file tree
Hide file tree
Showing 192 changed files with 32,441 additions and 15,113 deletions.
2 changes: 1 addition & 1 deletion admission-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} node:lts as builder
FROM --platform=${BUILDPLATFORM} node:lts AS builder

WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion admission-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"devDependencies": {
"@types/node": "^20.11.17",
"tsx": "^4.7.2",
"tsx": "^4.16.0",
"typescript": "^5.3.2"
}
}
2 changes: 1 addition & 1 deletion cdn-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM --platform=${BUILDPLATFORM} node:lts as builder
FROM --platform=${BUILDPLATFORM} node:lts AS builder

WORKDIR /app

Expand Down
4 changes: 2 additions & 2 deletions cdn-server/cdn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"devDependencies": {
"eslint": "^8.53.0",
"eslint-config-unjs": "^0.2.1",
"typescript": "^5.3.2",
"vitest": "^1.5.0"
"typescript": "5.5.2",
"vitest": "^1.6.0"
}
}
4 changes: 2 additions & 2 deletions cdn-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@types/node": "^20.9.0",
"eslint": "^8.53.0",
"eslint-config-unjs": "^0.2.1",
"tsx": "^4.7.0",
"typescript": "^5.3.2"
"tsx": "^4.16.0",
"typescript": "5.5.2"
}
}
6 changes: 3 additions & 3 deletions cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@
"eslint-config-unjs": "^0.2.1",
"eslint-plugin-require-extensions": "^0.1.3",
"prettier": "^3.0.3",
"tsx": "^4.7.1",
"typescript": "^5.2.2",
"vitest": "^1.5.0"
"tsx": "^4.16.0",
"typescript": "5.5.2",
"vitest": "^1.6.0"
},
"gitHead": "c37aed755e1b19ed91d30f9b5f7041e15c56901a"
}
5 changes: 1 addition & 4 deletions cli/src/commands/contract/commands/update.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { readFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import Table from 'cli-table3';
import { Command, program } from 'commander';
import { resolve } from 'pathe';
import { Command } from 'commander';
import pc from 'picocolors';
import ora from 'ora';
import { getBaseHeaders } from '../../../core/config.js';
Expand Down
76 changes: 76 additions & 0 deletions cli/src/commands/feature-flag/commands/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { splitLabel } from '@wundergraph/cosmo-shared';
import { Command } from 'commander';
import ora from 'ora';
import pc from 'picocolors';
import { getBaseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { handleFeatureFlagResult } from '../../../handle-feature-flag-result.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('create');
command.description(
'Creates a feature flag on the control plane. A feature flag must contain one or more feature subgraphs.',
);
command.argument('<name>', 'The name of the feature flag to create.');
command.option('-n, --namespace [string]', 'The namespace of the feature flag.');
command.option(
'--label [labels...]',
'The labels to apply to the feature flag. The labels are passed in the format <key>=<value> <key>=<value>.',
);
command.requiredOption(
'--fs, --feature-subgraphs <featureSubgraphs...>',
'The names of the feature subgraphs that will form the feature flag.' +
' The feature subgraphs are passed in the format <featureSubgraph1> <featureSubgraph2> <featureSubgraph3>.' +
' The feature flag must have at least one feature subgraph.',
);
command.option(
'-e, --enabled',
'Flag that if included will enable the feature flag upon creation.' +
' A new feature flag is disabled by default to prevent accidental compositions.',
);
command.action(async (name, options) => {
const spinner = ora('The feature flag is being created...').start();
const resp = await opts.client.platform.createFeatureFlag(
{
name,
namespace: options.namespace,
labels: options.label ? options.label.map((label: string) => splitLabel(label)) : [],
featureSubgraphNames: options.featureSubgraphs,
isEnabled: !!options.enabled,
},
{
headers: getBaseHeaders(),
},
);

try {
handleFeatureFlagResult({
responseCode: resp.response?.code,
responseDetails: resp.response?.details,
compositionErrors: resp.compositionErrors,
deploymentErrors: resp.deploymentErrors,
spinner,
successMessage: `The feature flag "${name}" was created successfully. ${
options.enabled
? ''
: `To enable it, use the "wgc feature-flag enable" command or pass the "--enabled" flag when creating it.`
}`,
subgraphCompositionBaseErrorMessage: `The feature flag "${name}" was created but with composition errors.`,
subgraphCompositionDetailedErrorMessage:
`There were composition errors when composing at least one federated graph related to the` +
` creation of feature flag "${name}"` +
`.\nThe federated graphs will not be updated until the errors are fixed.` +
`\n${pc.bold('Please check the errors below:')}`,
deploymentErrorMessage:
`The feature flag "${name}" was created, but the updated composition could not be deployed.` +
`\nThis means the updated composition is not accessible to the router.` +
`\n${pc.bold('Please check the errors below:')}`,
defaultErrorMessage: `Failed to create the feature flag "${name}".`,
});
} catch {
process.exit(1);
}
});

return command;
};
66 changes: 66 additions & 0 deletions cli/src/commands/feature-flag/commands/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Command } from 'commander';
import pc from 'picocolors';
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import inquirer from 'inquirer';
import Table from 'cli-table3';
import ora from 'ora';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { getBaseHeaders } from '../../../core/config.js';
import { handleFeatureFlagResult } from '../../../handle-feature-flag-result.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('delete');
command.description('Deletes a feature flag from the control plane.');
command.argument('<name>', 'The name of the feature flag to delete.');
command.option('-n, --namespace [string]', 'The namespace of the feature flag.');
command.option('-f --force', 'Flag to force the deletion (skip confirmation).');
command.action(async (name, options) => {
if (!options.force) {
const deletionConfirmed = await inquirer.prompt({
name: 'confirmDeletion',
type: 'confirm',
message: `Are you sure you want to delete the feature flag "${name}"?`,
});
if (!deletionConfirmed.confirmDeletion) {
process.exit(1);
}
}

const spinner = ora(`The feature flag "${name}" is being deleted...`).start();

const resp = await opts.client.platform.deleteFeatureFlag(
{
name,
namespace: options.namespace,
},
{
headers: getBaseHeaders(),
},
);

try {
handleFeatureFlagResult({
responseCode: resp.response?.code,
responseDetails: resp.response?.details,
compositionErrors: resp.compositionErrors,
deploymentErrors: resp.deploymentErrors,
spinner,
successMessage: `The feature flag "${name}" was deleted successfully.`,
subgraphCompositionBaseErrorMessage: `The feature flag "${name}" was deleted but with composition errors.`,
subgraphCompositionDetailedErrorMessage:
`There were composition errors when composing at least one federated graph related to the` +
` deletion of feature flag "${name}".\nThe router will continue to work with the latest valid schema.` +
`\n${pc.bold('Please check the errors below:')}`,
deploymentErrorMessage:
`The feature flag "${name}" was deleted, but the updated composition could not be deployed.` +
`\nThis means the updated composition is not accessible to the router.` +
`\n${pc.bold('Please check the errors below:')}`,
defaultErrorMessage: `Failed to delete the feature flag "${name}".`,
});
} catch {
process.exit(1);
}
});

return command;
};
58 changes: 58 additions & 0 deletions cli/src/commands/feature-flag/commands/disable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { Command } from 'commander';
import ora from 'ora';
import pc from 'picocolors';
import Table from 'cli-table3';
import { getBaseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { handleFeatureFlagResult } from '../../../handle-feature-flag-result.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('disable');
command.description('Disables a feature flag on the control plane.');
command.argument('<name>', 'The name of the feature flag to disable.');
command.option('-n, --namespace [string]', 'The namespace of the feature flag.');

command.action(async (name, options) => {
const spinner = ora(`The feature flag "${name}" is being disabled...`).start();
const resp = await opts.client.platform.enableFeatureFlag(
{
name,
namespace: options.namespace,
enabled: false,
},
{
headers: getBaseHeaders(),
},
);

try {
handleFeatureFlagResult({
responseCode: resp.response?.code,
responseDetails: resp.response?.details,
compositionErrors: resp.compositionErrors,
deploymentErrors: resp.deploymentErrors,
spinner,
successMessage:
resp?.hasChanged === false
? `The feature flag "${name}" is already disabled.`
: `The feature flag "${name}" was disabled successfully.`,
subgraphCompositionBaseErrorMessage: `The feature flag "${name}" was disabled but with composition errors.`,
subgraphCompositionDetailedErrorMessage:
`There were composition errors when composing at least one federated graph related to the` +
` disabling of feature flag "${name}".` +
`.\nThe federated graphs will not be updated until the errors are fixed.` +
`\n${pc.bold('Please check the errors below:')}`,
deploymentErrorMessage:
`The feature flag "${name}" was disabled, but the updated composition could not be deployed.` +
`\nThis means the updated composition is not accessible to the router.` +
`\n${pc.bold('Please check the errors below:')}`,
defaultErrorMessage: `Failed to disable the feature flag "${name}".`,
});
} catch {
process.exit(1);
}
});

return command;
};
58 changes: 58 additions & 0 deletions cli/src/commands/feature-flag/commands/enable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
import { Command } from 'commander';
import ora from 'ora';
import pc from 'picocolors';
import Table from 'cli-table3';
import { getBaseHeaders } from '../../../core/config.js';
import { BaseCommandOptions } from '../../../core/types/types.js';
import { handleFeatureFlagResult } from '../../../handle-feature-flag-result.js';

export default (opts: BaseCommandOptions) => {
const command = new Command('enable');
command.description('Enables a feature flag on the control plane.');
command.argument('<name>', 'The name of the feature flag to enable.');
command.option('-n, --namespace [string]', 'The namespace of the feature flag.');

command.action(async (name, options) => {
const spinner = ora(`The feature flag "${name}" is being enabled...`).start();
const resp = await opts.client.platform.enableFeatureFlag(
{
name,
namespace: options.namespace,
enabled: true,
},
{
headers: getBaseHeaders(),
},
);

try {
handleFeatureFlagResult({
responseCode: resp.response?.code,
responseDetails: resp.response?.details,
compositionErrors: resp.compositionErrors,
deploymentErrors: resp.deploymentErrors,
spinner,
successMessage:
resp?.hasChanged === false
? `The feature flag "${name}" is already enabled.`
: `The feature flag "${name}" was enabled successfully.`,
subgraphCompositionBaseErrorMessage: `The feature flag "${name}" was enabled but with composition errors.`,
subgraphCompositionDetailedErrorMessage:
`There were composition errors when composing at least one federated graph related to the` +
` enabling of feature flag "${name}".` +
`.\nThe federated graphs will not be updated until the errors are fixed.` +
`\n${pc.bold('Please check the errors below:')}`,
deploymentErrorMessage:
`The feature flag "${name}" was enabled, but the updated composition could not be deployed.` +
`\nThis means the updated composition is not accessible to the router.` +
`\n${pc.bold('Please check the errors below:')}`,
defaultErrorMessage: `Failed to enable the feature flag "${name}".`,
});
} catch {
process.exit(1);
}
});

return command;
};
Loading

0 comments on commit 5461bb5

Please sign in to comment.