feat(medusa-dev-cli): adds helpers to manage feature flags (#1770)

**Usage**

**Create a new feature flag**
```
$ medusa-dev ff create [name of flag] -d [description of what flag is for]
```
Will put a new file in `packages/medusa/src/loaders/feature-flags/[kebab-cased-flag-name].ts` and fill out the details.

**List feature flags**
```
$ medusa-dev ff list
```
Note: your Medusa repo must be built for the flags to show up

**Delete a feature flag**
```
$ medusa-dev ff delete [name of flag]
```
Will delete a file at `packages/medusa/src/loaders/feature-flags/[kebab-cased-flag-name].ts` if it exists.
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2022-07-05 07:33:46 +00:00
1 parent f9c3218aac
commit f8138afa36
4 files changed
+213 -9

No files matched your search

+1 -3
View File
@@ -29,9 +29,7 @@
"cross-env": "^7.0.3"
},
"homepage": "https://github.com/medusajs/medusa/tree/master/packages/medusa-dev-cli#readme",
"keywords": [
"medusa"
],
"keywords": ["medusa"],
"license": "MIT",
"main": "index.js",
"repository": {
@@ -0,0 +1,178 @@
import path from "path"
import glob from "glob"
import fs from "fs"
import Configstore from "configstore"
import { kebabCase, snakeCase } from "lodash"
import { featureFlagTemplate } from "./template"
import pkg from "../../package.json"
export const buildFFCli = (cli) => {
cli.command({
command: `ff`,
desc: "Manage Medusa feature flags",
builder: (yargs) => {
yargs
.command({
command: "create <name>",
desc: "Create a new feature flag",
builder: {
name: {
demandOption: true,
coerce: (name) => kebabCase(name),
description: "Name of the feature flag",
type: "string",
},
description: {
alias: "d",
demandOption: true,
description: "Description of the feature flag",
type: "string",
},
},
handler: async (argv) => {
const medusaLocation = getRepoRoot()
const featureFlagPath = buildPath(argv.name, medusaLocation)
if (fs.existsSync(featureFlagPath)) {
console.error(`Feature flag already exists: ${featureFlagPath}`)
return
}
const flagSettings = collectSettings(argv.name, argv.description)
writeFeatureFlag(flagSettings, featureFlagPath)
},
})
.command({
command: "list",
desc: "List available feature flags",
handler: async () => {
const medusaLocation = getRepoRoot()
const flagGlob = buildFlagsGlob(medusaLocation)
const featureFlags = glob.sync(flagGlob, {
ignore: ["**/index.*"],
})
const flagData = featureFlags.map((flag) => {
const flagSettings = readFeatureFlag(flag)
return {
...flagSettings,
file_name: path.basename(flag, ".js"),
}
})
console.table(flagData)
},
})
.command({
command: "delete <name>",
desc: "Delete a feature flag",
builder: {
name: {
demand: true,
coerce: (name) => kebabCase(name),
description: "Name of the feature flag",
type: "string",
},
},
handler: async (argv) => {
const medusaLocation = getRepoRoot()
const featureFlagPath = buildPath(argv.name, medusaLocation)
if (fs.existsSync(featureFlagPath)) {
fs.unlinkSync(featureFlagPath)
}
console.log(`Feature flag deleted: ${featureFlagPath}`)
},
})
.demandCommand(1, "Please specify an action")
},
})
}
const getRepoRoot = () => {
const conf = new Configstore(pkg.name)
const medusaLocation = conf.get(`medusa-location`)
if (!medusaLocation) {
console.error(
`
You haven't set the path yet to your cloned
version of medusa. Do so now by running:
medusa-dev --set-path-to-repo /path/to/my/cloned/version/medusa
`
)
process.exit()
}
return medusaLocation
}
const readFeatureFlag = (flagPath) => {
const flagSettings = require(flagPath).default
return flagSettings
}
const buildFlagsGlob = (repoRoot) => {
return path.join(
repoRoot,
"packages",
"medusa",
"dist",
"loaders",
"feature-flags",
`*.js`
)
}
const buildPath = (kebabCaseName, repoRoot) => {
return path.join(
repoRoot,
"packages",
"medusa",
"src",
"loaders",
"feature-flags",
`${kebabCaseName}.ts`
)
}
const collectSettings = (name, description) => {
const snakeCaseName = snakeCase(name)
return {
key: snakeCaseName,
description: description,
defaultValue: false,
envKey: `MEDUSA_FF_${snakeCaseName.toUpperCase()}`,
}
}
const writeFeatureFlag = (settings, featureFlagPath) => {
const featureFlag = featureFlagTemplate(settings)
fs.writeFileSync(featureFlagPath, featureFlag)
logFeatureFlagUsage(featureFlagPath, settings)
}
const logFeatureFlagUsage = (flagPath, flagSettings) => {
console.log(`Feature flag created: ${flagPath}`)
console.log(`
To use this feature flag, add the following to your medusa-config.js:
{
...,
featureFlags: {
${flagSettings.key}: true
}
}
or set the environment variable:
export ${flagSettings.envKey}=true
To add guarded code use the featureFlagRouter:
if (featureFlagRouter.isEnabled("${flagSettings.key}")) {
// do something
}
`)
}
@@ -0,0 +1,13 @@
export const featureFlagTemplate = ({
key,
description,
defaultValue,
envKey,
}) => {
return `export default {
key: "${key}",
description: "${description}",
default_value: ${defaultValue},
env_key: "${envKey}",
}`
}
+21 -6
View File
@@ -3,11 +3,21 @@
const Configstore = require(`configstore`)
const pkg = require(`../package.json`)
const _ = require(`lodash`)
const yargs = require(`yargs/yargs`)
const path = require(`path`)
const os = require(`os`)
const fs = require(`fs-extra`)
const watch = require(`./watch`)
const { getVersionInfo } = require(`./utils/version`)
const argv = require(`yargs`)
const { buildFFCli } = require("./feature-flags")
const cli = yargs()
cli.command({
command: `*`,
description: `Start the Medusa dev CLI`,
builder: (yargs) => {
yargs
.usage(`Usage: medusa-dev [options]`)
.alias(`q`, `quiet`)
.nargs(`q`, 0)
@@ -44,17 +54,16 @@ You typically only need to configure this once.`
.alias(`h`, `help`)
.nargs(`v`, 0)
.alias(`v`, `version`)
.describe(`v`, `Print the currently installed version of Medusa Dev CLI`).argv
.describe(`v`, `Print the currently installed version of Medusa Dev CLI`)
},
handler: async (argv) => {
const conf = new Configstore(pkg.name)
if (argv.version) {
console.log(getVersionInfo())
process.exit()
}
const conf = new Configstore(pkg.name)
const fs = require(`fs-extra`)
let pathToRepo = argv.setPathToRepo
if (pathToRepo) {
@@ -151,3 +160,9 @@ watch(medusaLocation, argv.packages, {
packageNameToPath,
externalRegistry: argv.externalRegistry,
})
},
})
buildFFCli(cli)
cli.parse(process.argv.slice(2))