My monorepo
Working in a monorepo, sometimes with many teams, is a fanatastic experience. But, finding your way in the codebase can be a bit of a challenge. This is where this tool comes in. It will help you find the code you teams owns.
This first release of the extension is tailored to the needs of my company. But, I will try to make it more generic in the future. Feel free to open an issue if you have any suggestions.
Features
From the contents of the code-owners file, the extension will filter the files in the explorer and show only the files that are owned by the team you are currently working in. It will also show the team that owns the file in the status bar. If you click on the team name in the status bar, you will be able to change the team that is used as a filter. The extension will also show the team that owns the file in the hover tooltip.
Settings
- exclude: an array of files/folders to always exclude from the tree view
- teamnames: an array of team names that can be toggled between in the status bar
- ignore: an array of files/folders to ignore when parsing the structure of your monorepo (for efficiency reasons). The node_modules folders is a good candidate...
- enabled: enable/disable the filter. If disabled, all files will be shown, excluding the files listed in the exclude setting. If enabled, the filter will be applied using the selected team in the status bar.
- filename: the name of the code-owners file. Default is code-owners.json. This specifies the base name only; the file is expected to be in the root of the workspace.
Code-owners file
The schema of the code owners file that is supported is the following:
{
"$schema": "http://json-schema.org/schema",
"id": "/CodeOwnersFileSchema",
"title": "Code owners",
"description": "Schema for validating the code-owners file in the project",
"type": "object",
"properties": {
"teams": {
"type": "object",
"patternProperties": {
".+": {
"type": "object",
"properties": {
"productOwner": {
"description": "Rabobank email address of Product Owner of the team",
"type": "string",
"format": "email"
},
"functionalMailbox": {
"description": "Functional Mailbox email address of the team",
"type": "string",
"minLength": 8
},
"members": {
"description": "Rabobank email addresses for each member of the team",
"type": "array",
"items": {
"type": "string",
"format": "email"
},
"minItems": 2,
"uniqueItems": true
},
"paths": {
"description": "Paths to files or directories which are owned by the team.
"type": "array",
"items": {
"type": "string",
"format": "codeOwnersPath"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["productOwner", "functionalMailbox", "members", "paths"]
}
}
}
}
}
Show owner of a module
The extension can show the owner of an imported module by hovering the mouse of the module name. It can do so by examining the project.json files in the repository. To speed up this process it will use a cache file which records the owners of the various projects in the repository.
Run the command Update mymonorepo cache to create or update this cache file. It is not updated automatically, as this information typically doesn't change a lot.
External commands
You can now add a json file (mymonorepo.commands.json) to your .vscode folder, to list some external commands in a panel to quickly execute them. Below you find an example of a senses.commands.json file;
[
{
"label": "Wiremock",
"externalCommand": "curl",
"description": "Query wiremock server",
"themeIcon": "cloud-download",
"optionPrefix": "-",
"terminalName": "external commands",
"options": [
{
"label": "Uri",
"name": "uri",
"description": "The endpoint to query",
"type": "string",
"value": "http://localhost:1080/__admin/mappings"
},
{
"label": "Method",
"name": "method",
"type": "option",
"value": "GET",
"options": [
"GET",
"POST"
]
}
]
}
]
You can add any icon from the vscode icons as the themeIcon.
Commands can be executed from an event (see interface definition). Commands that are executed from an event are not listed in the commands panel.
This is the interface that we use to cast the commands to:
export interface ExternalCommand extends vscode.TreeItem {
/**
* The name of the terminal that will be used to run the command. Defaults to 'external commands'
* @type {string}
* @memberof ExternalCommand
* @default 'external commands'
*/
terminalName?: string;
/**
* The label of the command that will be presented in the view
* @type {string}
* @memberof ExternalCommand
* @example 'Install packages'
*/
label: string;
/**
* The command that will be executed
* @type {string}
* @memberof ExternalCommand
* @example 'npm install'
*/
externalCommand: string;
/**
* Any parameters that need to be passed to the command
* @type {Parameter[]}
* @memberof ExternalCommand
* @example [{name: 'package', type: 'string', value: 'vscode'}]
* @default []
* @see {@link Parameter}
* @description
* The parameters will be added to the command followed by the value. If the parameter is of type boolean, no value is added
*/
parameters?: Parameter[];
/**
* Any options for the command
* @type {Parameter[]}
* @memberof ExternalCommand
* @example [{name: 'save', type: 'boolean', value: false}]
* @default []
* @description
* The optionPrefix will be added to the option name, so if you want to use a single dash instead of a double dash, you can set the optionPrefix to '-'
*/
options?: Parameter[];
/**
* Any prefix for the options, defaults to '--'
* @type {string}
* @memberof ExternalCommand
* @default '--'
*/
optionPrefix?: string;
/**
* The icon to represent the command
* @type {string}
* @memberof ExternalCommand
* @example 'terminal'
* @see https://code.visualstudio.com/api/references/icons-in-labels#icon-listing
*/
themeIcon?: string;
/**
* If true, the command will be executed immediately. If false, the command will be added to the terminal, so you can add additional options before running it
* @type {boolean}
* @memberof ExternalCommand
* @default true
* @description
* If you set this to false, you can add additional options to the command before running it
*/
runImmediately?: boolean;
/**
* A description to show in the input form
* @type {string}
* @memberof ExternalCommand
* @example 'Enter the package name'
* @default ''
*/
description?: string;
/**
* Any events that should be triggered after the command is executed.
* Commands with events are not listed in the command panel
* @type {vscode.event[]}
* @memberof ExternalCommand
* @default []
* @description
* The callback will be called with the terminal as the argument
*/
events: Array<ExternalCommandSystemHook>;
}
export interface ExternalCommandSystemHook {
event: ExternalCommandEvents;
// Provide the relative folder to the workspace root to which the command should be executed
root?: string;
// Flag to (temporarily) enable/disable a hook quickly
enabled?: boolean;
}
export interface Parameter {
// Is the parameter required? For now, just adds an * to the label of the field, no validation yet
required: boolean;
// The name of the parameter as it will be used in the command
name: string;
// The label of the parameter for the input form
label: string;
// The type of the parameter
type: 'string' | 'boolean' | 'option';
// The options to present in the dropdown (for type is option)
options?: string[];
// The active value of the parameter. Define it in the monorepo.commands.json file to set the default
value?: any;
}
/**
* Supported events that can be triggered an external command
*/
export enum ExternalCommandEvents {
// Parameters for the command:
// the name of the file that was saved
onSave = 'onSave',
// Parameters for the command:
// the name of the file that was opened
onOpen = 'onOpen',
// Parameters for the command:
// the name of the file that was closed
onClose = 'onClose',
// Parameters for the command:
// the name of the file that was created
onDelete = 'onDelete',
// Parameters for the command:
// the new name of the file that was renamed
// the old name of the file that was renamed
onRename= 'onRename'
}
Templates
mymonorepo now also supports templates which you can use to create new files from. The templates should work for any language. By default the templates should be stored in the .vscode/templates folder, but the location (in the workspace folder) can be configured in the vscode settings.
Variables
You can add variables in the templates which will be replaced by the extension when creating the new file. For any file that holds a variable definition, you will see a command button in the title of the editor. That command shows an input for which you can use to provide the values of the variables. Submitting the form will replace all occurences of the variables in the file with the value provided. An example of a markdown template file, with variables is shown below.
# <%= topic %>
The documentation for this <%= topic %> is available here. It is only accessible to members of group <%= access group %>. If you want to provide access to somebody, ask support to add that user to group AAD_<%= access group %>.
Imported modules
If you open a module from your repo, the mymonorepo
tries to determine which modules are imported. This is a recursive process so theoretically you could end up in a infinitive loop (although that would probably mean your project doesn't compile).
Note: this feature is still experimental and might not work for all projects. It is also not evaluating import-expressions, so the list of shown modules might not be complete. Lastly, the list of module names is sorted; it is not in the order of importing modules.
You will need to have generated the cache file for this to work (command: mymonorepo.updatecache
)
Finally: it has some hardcoded settings, so it works in my environment, but it might not work in yours.
Known issues
If your monorepo is really large, and your teams owns a lot of different files and folders, the list of excluded files/folders from the file explorer is large, you might run into an issue in vscode with that.
There is an open issue that you can track here: https://github.com/microsoft/vscode/issues/869
It's been open a long time now :-| If any other extension is reporting an issue with an error message 'spawn ENAMETOOLONG', set the filter to All and reload the extension, or restart vscode. After that you can set the filter to the team you want to work with. This happens only when you have a lot of files/folders excluded from the explorer.