Skip to content

Commit

Permalink
feat(cli): dynamic cli options
Browse files Browse the repository at this point in the history
  • Loading branch information
crherman7 committed Dec 9, 2024
1 parent bd46273 commit 684fa2e
Show file tree
Hide file tree
Showing 14 changed files with 612 additions and 80 deletions.
22 changes: 22 additions & 0 deletions packages/app-env/.flagshipcoderc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"options": [
{
"flags": "--app-env-initial <value>",
"description": "Specifies the initial environment to be used when the application starts (e.g., 'development', 'staging', or 'production').",
"required": true,
"example": "--app-env-initial development"
},
{
"flags": "--app-env-dir <value>",
"description": "Defines the directory path where environment configuration files are stored (relative or absolute path).",
"required": true,
"example": "--app-env-dir ./config/environments"
},
{
"flags": "--app-env-hide <value>",
"description": "Specifies one or more environments to hide from selection or visibility (comma-separated list).",
"required": false,
"example": "--app-env-hide staging,test"
}
]
}
2 changes: 1 addition & 1 deletion packages/cli/__tests__/main-application-java.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @jest-environment-options {"requireTemplate": true}
* @jest-environment-options {"requireTemplate": true, "fixtures": "project-pbxproj_fixtures"}
*/

/// <reference types="@brandingbrand/code-jest-config" />
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/__tests__/project-pbxproj_fixtures/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"@brandingbrand/fsapp": "11.0.0"
}
}
4 changes: 4 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
"detect-package-manager": "^3.0.1",
"esbuild": "^0.20.0",
"execa": "^9.3.0",
"find-node-modules": "^2.1.3",
"fp-ts": "^2.16.2",
"glob": "^11.0.0",
"ink": "^4.4.1",
"ink-spinner": "^5.0.0",
"io-ts": "^2.2.21",
Expand All @@ -57,9 +59,11 @@
"@repo/typescript-config": "*",
"@types/ansi-align": "3.0.5",
"@types/eslint": "^8.56.1",
"@types/find-node-modules": "^2.1.2",
"@types/node": "^20.10.6",
"@types/npmcli__package-json": "^4.0.4",
"@types/react": "18.2.6",
"ajv": "^8.17.1",
"react": "^18.2.0",
"type-fest": "^4.10.2",
"typescript": "^5.3.3"
Expand Down
74 changes: 74 additions & 0 deletions packages/cli/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Flagship Code Commands Configuration",
"description": "Schema for defining custom commander options in flagship-code.commands.json",
"type": "object",
"properties": {
"options": {
"type": "array",
"description": "An array of custom options to be dynamically added to commander commands",
"items": {
"type": "object",
"properties": {
"flags": {
"type": "string",
"description": "The flag syntax for the option, following commander conventions (e.g., '--flag <value>').",
"pattern": "^--[a-zA-Z0-9\\-_]+(\\s<[a-zA-Z0-9\\-_]+>|\\s\\[[a-zA-Z0-9\\-_]+\\])?$",
"examples": [
"--custom-flag <value>",
"--enable-feature",
"--mode [type]"
],
"minLength": 2
},
"description": {
"type": "string",
"description": "A brief description of the option, including the expected value format or type.",
"minLength": 1,
"examples": [
"Specifies the initial environment to use, e.g., 'development', 'staging', or 'production'.",
"Defines the path to the environments directory.",
"A comma-separated list of environments to hide."
]
},
"example": {
"type": "string",
"description": "A concrete example showing how to use the option.",
"examples": [
"--app-env-initial development",
"--app-env-dir ./config/environments",
"--app-env-hide staging,test"
]
},
"defaultValue": {
"description": "The default value for the option if it is not specified by the user.",
"anyOf": [
{"type": "string"},
{"type": "number"},
{"type": "boolean"},
{"type": "null"}
]
},
"choices": {
"type": "array",
"description": "Restrict the option's possible values to a predefined set.",
"items": {
"type": "string"
},
"uniqueItems": true,
"examples": [["debug", "release", "test"]]
},
"required": {
"type": "boolean",
"description": "Indicates whether the option is mandatory.",
"default": false
}
},
"required": ["flags", "description"],
"additionalProperties": false
}
}
},
"required": ["options"],
"additionalProperties": false
}
38 changes: 36 additions & 2 deletions packages/cli/src/actions/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
getReactNativeVersion,
} from '@brandingbrand/code-cli-kit';

import {config, defineAction, isGenerateCommand} from '@/lib';
import {FSAPP_DEPENDENCY, config, defineAction, isGenerateCommand} from '@/lib';
import {hasDependency, matchesFilePatterns} from '@/lib/dependencies';

/**
* Define an action to initialize a project template.
Expand All @@ -29,6 +30,36 @@ export default defineAction(async () => {
`react-native-${reactNativeVersion}`,
);

/**
* Filters files during a copy operation to exclude specific native files if the `fsapp` dependency is not installed.
*
* The function checks if the current project does not have the `fsapp` dependency and matches the source file
* against a predefined set of patterns. If both conditions are true, the file is excluded.
*
* @param src - The source file path being processed.
* @returns `false` if the file should be excluded; otherwise, `true`.
*
* @example
* ```typescript
* const shouldCopy = filter('/path/to/ios/EnvSwitcher.java');
* console.log(shouldCopy); // true or false depending on the presence of 'fsapp' and file match
* ```
*/
const fileFilter = (src: string): boolean => {
if (
!hasDependency(process.cwd(), FSAPP_DEPENDENCY) &&
matchesFilePatterns(src, [
'EnvSwitcher.java',
'EnvSwitcher.m',
'NativeConstants.java',
'NativeConstants.m',
])
) {
return false;
}
return true;
};

// If the generate cli command was executed copy the plugin template only
// WARNING: Consider moving this in future.
if (isGenerateCommand()) {
Expand Down Expand Up @@ -73,7 +104,9 @@ export default defineAction(async () => {

// Copy over iOS template to ios directory
await fse
.copy(path.resolve(templatePath, 'ios'), path.project.resolve('ios'))
.copy(path.resolve(templatePath, 'ios'), path.project.resolve('ios'), {
filter: fileFilter,
})
.catch(e => {
throw Error(
`Error: unable to copy ios template to ios directory in project root ${path.project.resolve('ios')}, ${e.message}`,
Expand All @@ -97,6 +130,7 @@ export default defineAction(async () => {
.copy(
path.resolve(templatePath, 'android'),
path.project.resolve('android'),
{filter: fileFilter},
)
.catch(e => {
throw Error(
Expand Down
129 changes: 67 additions & 62 deletions packages/cli/src/cmds/prebuild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
FLAGSHIP_CODE_TITLE,
} from '@/lib';
import {Status} from '@/components/Status';
import {registerCustomOptions} from '@/lib/commands';

/**
* Defines a command for the "prebuild" operation using the "commander" library.
Expand All @@ -30,78 +31,82 @@ import {Status} from '@/components/Status';
*
* @see {@link https://www.npmjs.com/package/commander | commander} - Command-line framework for Node.js.
*/
program
.command('prebuild')
.description(
'Ephemeral native code generation for a specific build, environment, and platform.',
)
.requiredOption('-b, --build [build]', 'Build configuration.')
.requiredOption('-e, --env [env]', 'Initial environment.')
.addOption(
new Option(
'-p, --platform [platform]',
'ios, android, or native (ios & android) code generation.',
registerCustomOptions(
program
.command('prebuild')
.description(
'Ephemeral native code generation for a specific build, environment, and platform.',
)
.choices(['ios', 'android', 'native'])
.default('native'),
)
.addOption(
new Option(
'-l --log-level [logLevel]',
'debug, log, info, warn, error log levels.',
.requiredOption('-b, --build [build]', 'Build configuration.')
.option('-e, --env [env]', 'Initial environment.')
.addOption(
new Option(
'-p, --platform [platform]',
'ios, android, or native (ios & android) code generation.',
)
.choices(['ios', 'android', 'native'])
.default('native'),
)
.choices(['debug', 'log', 'info', 'warn', 'error'])
.default('info'),
)
.option('-r, --release', 'Bundle only specified environment.', false)
.option('--verbose', 'Show stdout.', false)
.action(async (options: PrebuildOptions) => {
/**
* Update the configuration options with the provided options and command type.
*/
config.options = {...options, command: 'prebuild'};

const {render} = await import('ink');

await new Promise(res => {
.addOption(
new Option(
'-l --log-level [logLevel]',
'debug, log, info, warn, error log levels.',
)
.choices(['debug', 'log', 'info', 'warn', 'error'])
.default('info'),
)
.option('-r, --release', 'Bundle only specified environment.', false)
.option('--verbose', 'Show stdout.', false)
.action(async (options: PrebuildOptions) => {
/**
* Render the Reporter component to display progress.
* Update the configuration options with the provided options and command type.
*/
const {unmount} = render(<Status res={res} />, {stdout: process.stderr});
config.options = {...options, command: 'prebuild'};

const {render} = await import('ink');

global.unmount = unmount;
});
await new Promise(res => {
/**
* Render the Reporter component to display progress.
*/
const {unmount} = render(<Status res={res} />, {
stdout: process.stderr,
});

logger.setLogLevel(logger.getLogLevelFromString(options.logLevel));
global.unmount = unmount;
});

if (!options.verbose) {
logger.pause();
}
logger.setLogLevel(logger.getLogLevelFromString(options.logLevel));

process.stdout.write(FLAGSHIP_CODE_LOGO + '\n\n');
process.stdout.write(
ansiAlign([FLAGSHIP_CODE_TITLE, FLAGSHIP_CODE_DESCRIPTION]).join('\n') +
'\n\n',
);
if (!options.verbose) {
logger.pause();
}

logger.printCmdOptions(options, 'prebuild');
process.stdout.write(FLAGSHIP_CODE_LOGO + '\n\n');
process.stdout.write(
ansiAlign([FLAGSHIP_CODE_TITLE, FLAGSHIP_CODE_DESCRIPTION]).join('\n') +
'\n\n',
);

FlagshipCodeManager.shared
.addAction(actions.info)
.addAction(actions.clean)
.addAction(actions.config)
.addAction(actions.template)
.addAction(actions.env)
.addAction(actions.transformers)
.addAction(actions.plugins)
.addAction(actions.packagers);
logger.printCmdOptions(options, 'prebuild');

await FlagshipCodeManager.shared.run();
FlagshipCodeManager.shared
.addAction(actions.info)
.addAction(actions.clean)
.addAction(actions.config)
.addAction(actions.template)
.addAction(actions.env)
.addAction(actions.transformers)
.addAction(actions.plugins)
.addAction(actions.packagers);

/**
* Resume logging with console.log and process.stdout
*/
logger.resume();
await FlagshipCodeManager.shared.run();

/**
* Resume logging with console.log and process.stdout
*/
logger.resume();

global.unmount?.();
});
global.unmount?.();
}),
);
Loading

0 comments on commit 684fa2e

Please sign in to comment.