Install command
- General process of the command
- Changing .json files
- Changing .js files
- The state
- Adding options
- Applying the result state
- Changing the install command
General process of the command
The install command can be used to both set up a new cypress project as well as to update an existing project. This allows us to add features that the app devs can use more or less right away by passing in the right option.
Internally a state object is used that can be modified by the handlers of each option in any way it sees fit. By doing so, the handlers don't have to parse/write previously written files by other handlers as that's not happening anymore.
Instead the state object has a property for each file that might be touched by the install command and once the command is done modifying the state, it just needs to apply the state to the files.
Changing .json files
For json files this is done by parsing the existing file, then merging the additions with the existing data and finally overwrite the content of the file with the new, stringified, version. Currently it's a shallow merge as we don't add any nested properties to the json files.
Changing .js files
For javascript files codemods are being used to modify these files as that is more reliable than parsing the contents with regex. These will modify/add import/require statements and add call expressions (AST terminology for function calls)) to the appropriate places.
The state
The state currently has the following shape:
interface State {
support: String[]
plugins: String[]
cypressJson: Object
cypressEnvJson: Object
cypressCucumberPreprocessorJson: Object
}
Support
The items can either be function identifiers as string or a falsy value. The
codemod ensures that each function identifier will be imported from
@dhis2/cypress-commands
and adds a call expression to the support file.
Providing a falsy value can be done to ensure that the import statement is
there but nothing is imported (import '@dhis2/cypress-commands'
) as this will
register the commands provided by the library automatically.
Plugins
The plugins state works similarly to the support state with the exception that
falsy values will have no effect. Also the call expression to initialize the
imported plugins will be added to the function body of the default export
function and will always received both the on
and config
options.
JSON states
These are just (currently 1 dimensional) objects that will be JSON.stringified after being merged into the existing contents of these files.
Adding options
Options are organized in a config file:
packages/cli/src/commands/install/options.js
Each option must have the following properties:
interface Option {
name: String
group: String
handler: Function
}
Handlers
The handlers have to return the complete state. If the option is turned off (or other conditions inside the handler determine that the state should not be changed), these return the state that's been passed in.
This is the function signature:
type Handler = ({
state: State,
providedOptions?: { [optionName: String]: Bool },
paths: { [pathName]: String },
}) => State
Applying the result state
Before any state is applied, a check is performed whether there are any
changes. For the support and plugins state, this is done by checking if the
arrays have length. For the json states, this is done by checking if there are
any keys in the object (Object.keys(state[..]).length
).
Before any state is written, some functions are called that ensure that the
required files are in place. If they're missing, then the install command will
copy a template from packages/cli/template/
.
If the support or plugins states have length, the respective dependency will be installed (the user will be asked for the appropriate package manager, if there's a yarn.lock, then yarn will be the default option, if there's a package-lock.json, npm will be the default option. Otherwise 'skip' will be the default option).
Changing the install command
The files that need to be touched are mostly the options.js and the handlers you're working on. The apply process needs only to be touched if addition files or codemods need to be added.
To make it easier to work with the command during development, you can add the following code snippet after the option processing (state creation process):
console.log(JSON.stringify(state, null, 2))
process.exit()
This way you can inspect the final state's contents without adding/changing any file.