', texts.argPattern)
.option('-c --coverage', texts.argCoverage)
.option('-w --watch', texts.argWatch)
.parse(process.argv);
const {pattern, watch} = program.opts();
const rootSuite = path.resolve(process.cwd(),
'node_modules', pkg.name, 'cli', 'rootsuite.js');
const proc = exec([
// use nyc && mocha
'nyc mocha',
// where to look for tests
pattern ? pattern : './test/**/*.js',
// setup test environment
util.format('--file "%s"', rootSuite),
// enable watch
watch ? '--watch' : '',
// babel
'--require @babel/register',
// output colors
'--colors'
].join(' '));
proc.stdout.on('data', data => {
process.stdout.write(data.toString());
});
proc.stderr.on('data', data => {
process.stdout.write(data.toString());
});
// exit parent process with the unit test result code
proc.on('exit', process.exit);
================================================
FILE: config/actions.yml
================================================
name: Build
on:
push:
branches: [ main ]
## to run workflow on pull requests:
#on:
# pull_request:
# branches: [ main ]
## to run workflow on tagged commits
#on:
# push:
# tags:
# - '*'
## to run workflow on schedule, e.g. nightly build
#on:
# schedule:
# - cron: '0 0 * * *'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# see https://github.com/marketplace/actions/setup-node-js-environment
- uses: actions/setup-node@v2
with:
node-version: '14'
# see https://github.com/marketplace/actions/cache
- name: Cache dependecies
uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
run: npm install
- name: Unit test
run: npm run test
- name: Build docs
run: npm run docs
- name: Build extension .zip file
run: npm run build
## Uncomment to deploy docs to GH pages
## see: https://github.com/marketplace/actions/deploy-to-github-pages
# - name: Deploy docs
# uses: JamesIves/github-pages-deploy-action@4.1.3
# with:
# branch: gh-pages
# folder: public/documentation
## Uncomment to upload generated zip file to web store
## see: https://github.com/MobileFirstLLC/cws-publish
# - name: Upload to Chrome Web Store
# run: >-
# npx cws-upload
# ${{ secrets.CLIENT }}
# ${{ secrets.SECRET }}
# ${{ secrets.TOKEN }}
# "release.zip"
# ${{ EXTENSION_ID }};
## Uncomment to make a Github release
## see: https://github.com/marketplace/actions/create-release
# - uses: ncipollo/release-action@v1
# with:
# artifacts: "release.zip"
# token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: config/build.json
================================================
{
"dist": "./dist",
"source": "./src",
"releases": "./",
"release_name": "release",
"manifest": "./src/manifest.json",
"js": "./src/**/*.js",
"js_bundles": [{
"src":"./src/**/*.js",
"name": "script"
}],
"html": "./src/**/*.html",
"scss": "./src/**/*.scss",
"scss_bundles": [{
"src":"./src/**/*.scss",
"name": "styles"
}],
"assets": [
"./assets/**/*",
"!./assets/locales",
"!./assets/locales/**/*"
],
"copyAsIs": [],
"locales_dir": "./assets/locales/",
"locales_list": [
"en"
],
"commands": null,
"commands_watch_path": null
}
================================================
FILE: config/docs.json
================================================
{
"tags": {
"allowUnknownTags": true,
"dictionaries": [
"jsdoc"
]
},
"source": {
"include": [
"src"
],
"includePattern": ".js$",
"excludePattern": "(node_modules/)"
},
"plugins": [
"plugins/markdown"
],
"templates": {
"default": {
"cleverLinks": true,
"monospaceLinks": false
}
},
"opts": {
"destination": "./public/documentation",
"encoding": "utf8",
"private": true,
"recurse": true,
"template": "templates/default"
}
}
================================================
FILE: config/gitlab.yml
================================================
image: node:latest
stages:
- install
- pages
- test
- publish
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
install_dependencies:
stage: install
script: npm install
artifacts:
paths:
- node_modules/
pages:
stage: pages
script:
- npm run docs
artifacts:
paths:
- public/
only:
- master
test:
stage: test
script:
- npm run test
store_publish:
stage: publish
script:
- npm run build
## see: https://github.com/MobileFirstLLC/cws-publish
# - npx cws-upload $client_id $secret $token $zip_path $extension_id;
artifacts:
paths:
- $zip
only:
- tags
================================================
FILE: config/ignore
================================================
.idea/
.vscode/
node_modules/
.nyc_output/
coverage/
dist/
public/documentation/
release.zip
yarn-error.log
================================================
FILE: config/init/background.js
================================================
console.log('This is background service worker - edit me!');
================================================
FILE: config/init/eslint.json
================================================
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended"
],
"globals": {
"document": false,
"escape": false,
"navigator": false,
"unescape": false,
"window": false,
"describe": true,
"before": true,
"it": true,
"expect": true,
"sinon": true,
"chrome": true
},
"plugins": [],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
}
}
================================================
FILE: config/init/intro.md
================================================
# ${name}
${description}
## Development
This extension was created with [Extension CLI](https://oss.mobilefirst.me/extension-cli/)!
If you find this software helpful [star](https://github.com/MobileFirstLLC/extension-cli/) or [sponsor](https://github.com/sponsors/MobileFirstLLC) this project.
### Available Commands
| Commands | Description |
| --- | --- |
| `npm run start` | build extension, watch file changes |
| `npm run build` | generate release version |
| `npm run docs` | generate source code docs |
| `npm run clean` | remove temporary files |
| `npm run test` | run unit tests |
| `npm run sync` | update config files |
For CLI instructions see [User Guide →](https://oss.mobilefirst.me/extension-cli/)
### Learn More
**Extension Developer guides**
- [Getting started with extension development](https://developer.chrome.com/extensions/getstarted)
- Manifest configuration: [version 2](https://developer.chrome.com/extensions/manifest) - [version 3](https://developer.chrome.com/docs/extensions/mv3/intro/)
- [Permissions reference](https://developer.chrome.com/extensions/declare_permissions)
- [Chrome API reference](https://developer.chrome.com/docs/extensions/reference/)
**Extension Publishing Guides**
- [Publishing for Chrome](https://developer.chrome.com/webstore/publish)
- [Publishing for Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension)
- [Publishing for Opera addons](https://dev.opera.com/extensions/publishing-guidelines/)
- [Publishing for Firefox](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/)
================================================
FILE: config/init/manifest.json
================================================
{
"name": "__MSG_appName__",
"short_name": "__MSG_appShortName__",
"description": "__MSG_appDescription__",
"homepage_url": "${homepage}",
"version": "${version}",
"version_name": "${version}",
"manifest_version": 3,
"default_locale": "en",
"minimum_chrome_version": "88",
"permissions": [],
"icons": {
"128": "assets/img/128x128.png"
},
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": {
"16": "assets/img/16x16.png",
"24": "assets/img/24x24.png",
"32": "assets/img/32x32.png"
},
"default_title": "__MSG_appName__"
}
}
================================================
FILE: config/init/messages.json
================================================
{
"appName": {
"message": "${name}"
},
"appShortName": {
"message": "${name}"
},
"appDescription": {
"message": "${description}"
}
}
================================================
FILE: config/init/package.json
================================================
{
"name": "${safeName}",
"description": "${description}",
"version": "${version}",
"homepage": "${homepage}",
"author": "ENTER YOUR NAME HERE",
"repository": {
"type": "git",
"url": "ENTER GIT REPO URL"
},
"scripts": {
"start": "xt-build -e dev -w",
"start:firefox": "xt-build -e dev -p firefox -w",
"build": "xt-build -e prod",
"build:firefox": "xt-build -e prod -p firefox",
"clean": "xt-clean",
"docs": "xt-docs",
"test": "xt-test",
"coverage": "nyc --reporter=lcov npm run test",
"sync": "xt-sync"
},
"babel": {
"presets": [
"@babel/preset-env"
]
},
"eslintIgnore": [
"test/**/*"
],
"devDependencies": {
"extension-cli": "latest"
},
"xtdocs": {
"source": {
"include": [
"README.md",
"src"
]
}
},
"xtbuild": {
"js_bundles": [
{
"name": "background",
"src": "./src/**/*.js"
}
]
}
}
================================================
FILE: config/init/test.js
================================================
describe('Test extension', () => {
it('This is a dummy test', () => {
expect(true).to.be.true;
});
});
================================================
FILE: config/readme.md
================================================
# CLI configuration files
This directory contains various files that are used by the available CLI commands. Below is a short summary of each to explain their purpose.
Path | Description
:--- | :---
**actions.yml** | Github actions starter configuration
**build.json** | default file paths used by the build script
**docs.json** | JSDoc documentation template
**eslint.json** | default eslint configuration
**gitlab.yml** | Gitlab CI starter configuration
**ignore** | gitignore starter
**travis.yml** | Travis CI starter configuration
**init/** | Files for bootstrapping a new extension project
**└── NNxNN.png** | extension icons
**└── background.js** | background script starter
**└── icon.svg** | vector icon
**└── intro.md** | new extension readme
**└── manifest.json** | manifest template
**└── messages.json** | message dictionary template
**└── package.json** | package.json template
**└── test.js** | unit test starter
**Notes**
- eslint, CI configuration files, (git)ignore can be pulled into a project through `xt-sync` command,
or a project can specify these files independently.
The idea is not having to start from scratch at project level unless it is by choice.
- All files in `init` directory are included in a new extension project
- files that should not be included in a newly generated project go in `/config` directory
- keep `init` directory flat on purpose to keep things simple — create command will generate
the necessary structure
================================================
FILE: config/travis.yml
================================================
language: node_js
node_js:
- "14.15.4"
cache:
directories:
- node_modules
before_script:
- npm run test || travis_terminate 1
script:
- npm run docs
- npm run build
deploy:
- provider: pages
skip_cleanup: true
github_token: $github_token
local_dir: public/documentation
on:
branch: master
- provider: releases
skip_cleanup: true
api_key: $github_token
file: $zip_path
on:
tags: true
#after_deploy:
## upload generated zip to chrome web store
## see: https://github.com/MobileFirstLLC/cws-publish
# - if [ ! -z "$TRAVIS_TAG" ]; then
# npx cws-upload $client_id $secret $token $zip_path $extension_id;
# fi
================================================
FILE: guide/01-getting-started.md
================================================
# Installation
### Prerequisites
Before using extension CLI, you must have the following:
- [Node.js](https://nodejs.org/en/download/)
- JavaScript IDE
- Terminal access
- Browser for debugging extensions
### Setup
Create a new extension project:
```bash
npx extension-cli
```
Add CLI to an existing project:
```bash
npm install extension-cli
```
### Default Project Organization
Before you start using the CLI, inspect your project file structure. You can override most of
these paths in configurations, but this organization matches the CLI defaults.
If you created a new extension using the command above, your file structure already looks like this.
Path | Description
--- | ---
└ **assets** | static assets
└─ img | Extension icons
└─ locales | Localized string resources
└─ en/messages.json | English dictionary
└ **src** | Source code: put js, scss, html, json files here
└─ manifest.json | Extension manifest
└ **test** | Unit tests
└ package.json | Application root
================================================
FILE: guide/02-configuration.md
================================================
# Configuration for Existing Applications
!!! info
**If you created the extension with Extension CLI, this setup is already done for you, and you may skip this step.**
Before using Extension CLI with an existing application, add these configuration options to project's `package.json`:
### Babel Presets
This is needed to compile projects written in modern JavaScript syntax.
```json
"babel": {
"presets": [
"@babel/preset-env"
]
}
```
### ESLint Ignore
Exclude test files from being linted. If your project includes compiled 3rd party libraries, you should exclude them also.
```json
"eslintIgnore": [
"test/**/*"
]
```
### Add Scripts
Add these to `package.json` `scripts` section:
```json
"scripts": {
"start": "xt-build -e dev -w",
"build": "xt-build -e prod",
"clean": "xt-clean",
"docs": "xt-docs",
"test": "xt-test",
"coverage": "nyc --reporter=lcov npm run test"
}
```
================================================
FILE: guide/03-xt-build-assets.md
================================================
# Static assets
* * *
Specify how static assets will be handled during builds.
* * *
By default, extension CLI will look for static assets matching this configuration:
```json
"assets": [
"./assets/**/*",
"!./assets/locales",
"!./assets/locales/**/*"
],
```
You may change this configuration if the project's static assets are located elsewhere or
if you want to include or exclude additional files/directories.
After the build step, all static assets will be located in the `/dist/assets` directory.
For example, to refer to images in extension manifest, would be as follows:
```json
"browser_action": {
"default_icon": {
"16": "assets/img/16x16.png",
"24": "assets/img/24x24.png",
"32": "assets/img/32x32.png"
}
}
```
================================================
FILE: guide/03-xt-build-cmds.md
================================================
# Custom commands
* * *
Custom commands enables running any custom actions after build and before generating a release.
* * *
Custom commands will be executed:
- _after_ script, styles, HTML and other bundles have been built, and
- _before_ a release `.zip` file is generated
Custom commands are run for both `dev` and `prod` builds.
To configure custom commands specify `commands` build configuration key. For example:
```json
"xtbuild": {
"commands": "python do_something.py",
}
```
This configuration would first build the extension, then run a custom Python script,
then for a production build, generate the extension zip file.
================================================
FILE: guide/03-xt-build-copy.md
================================================
# Copying Files
* * *
Copying enables including files in the output without modifying them during build.
This includes use case where you want to skip compilation and linting of scripts or stylesheets.
* * *
!!! info "Copying static assets"
By default, all static assets under `assets/` directory will be automatically
copied to output directory during builds.
`copyAsIs` allows you to specify an array of files and/or directories which should be included in build output
without modification. Files to copy can be located anywhere in your project. The directories to copy are expected to be inside `/src` directory.
The build command will copy:
- specified **files** without any modification and add them to the root of the output directory;
directory path for files will be flattened.
- specified **directories** and their contents without modification and without flattening the path
If the copy command fails to locate the specified file or directory, it will not
raise an issue, the copy will simply not occur.
## Example 1: File copy
Sample configuration for skipping compilation of pre-compiled files.
This configuration will copy material theme directly from `node_modules`
and include it in the `dist` directory. It will also copy a project level `special.js`
script into the output directory. No modification will occur to these files during the build step.
After the build `dist/` directory root will include `material.min.js` and `special.js`.
```json
"xtbuild": {
"copyAsIs": [
"./node_modules/material-design-lite/material.min.js",
"./some/path/special.js"
]
}
```
## Example 2: Directory copy
When copying directories, directory will maintain its structure. Directory to copy must be
inside `src` directory. When specifying a directory use a match pattern, either `*` or `**/*`:
This build configuration will perform following copy operations:
- `/src/directory/*` copies all files under `/src/directory/` to `dist/` root (excludes nested directories).
- `/src/nested/directory/**/*` recursively copies all files and nested directories to `dist/` root without flattening path.
```json
"xtbuild": {
"copyAsIs": [
"/src/directory/*",
"/src/nested/directory/**/*"
]
}
```
## Disable Linting
When including precompiled javascript files to an extension project, you should also
disable linting for those files to avoid unnecessary warnings. In `package.json`,
add the file paths to the list of ignored files to prevent them from being linted:
```json
{
"eslintIgnore": [
"test/**/*",
"./some/path/special.js"
]
}
```
================================================
FILE: guide/03-xt-build-locales.md
================================================
# Localization
* * *
Localization enables translating extension to different languages.
* * *
If the extension supports multiple languages, you can customize
extension localization by specifying two build keys: `locales_dir` and `locales_list`.
## Locales directory
`locales_dir` key specifies where in project directory to look for locales files.
The default `locales_dir` is `./assets/locales/`.
If you prefer a different directory structure, override this default value.
## Locales list
`locales_list` is an array that lists all supported languages, and such that
the values of this array correspond to subdirectories under `locales_dir`. Only
locales directories specified in this array will be included in the build, which
allows excluding incomplete translations from build until they are ready to be
included.
The default value of `locales_list` is `["en"]`.
Refer [to this list of language codes](https://developers.google.com/admin-sdk/directory/v1/languages)
when specifying value for this configuration.
You may split localization files into multiple `.json` files within the
language-specific directory to improve maintainability. During builds
all files within a language directory will be automatically combined into a single
`messages.json` which is expected from a browser extensions.
Recommended reading: [learn how to internationalize extensions](https://developer.chrome.com/extensions/i18n).
## Example
This configuration shows build configuration with custom path and multiple language
outputs.
Build configuration
```json
"xtbuild": {
"locales_list": ["en","fr","pl"],
"locales_dir": "./my/custom/locales/path/"
}
```
Corresponding project level file structure:
File Path | Description
--- | ---
└ **`/my/custom/locales/path/`** | locales directory
└─ `en`/messages.json | English dictionary
└─ `fr`/myFile.json | French dictionary
└─ `pl/` |
└─ app.json | Polish dictionary, part 1
└─ options.json | Polish dictionary, part 2
└─ `de`/messages.json | German dictionary
Build behavior:
- `myFile.json` will be renamed to `messages.json`
- `app.json` and `options.json` will me concatenated and renamed to `messages.json`
- extension will be available in 3 languages; `dist/` directory will contain:
- `_locales/en/messages.json`
- `_locales/fr/messages.json`
- `_locales/pl/messages.json`
- German dictionary is excluded from build output because it is not included in `locales_list`
================================================
FILE: guide/03-xt-build-manifest.md
================================================
# Manifest
* * *
Customize build behavior for extension manifest.
* * *
In your build configuration specify path to the manifest file:
```json
"xtbuild": {
"manifest": "./src/manifest.json",
}
```
The file will be renamed to `manifest.json` during build regardless of its original name.
## Customizing manifests for different target browsers
There are two strategies for customizing the manifest contents per target browser:
1. Specify browser-specific keys in single manifest file
2. Specify multiple build configurations, each with different manifest file.
### Browser specific keys in single manifest
Using this strategy, the project contains single manifest. In manifest.json:
```json
{
"name": "__MSG_appname__",
"description": "__MSG_description__",
"chrome":{
... chrome-specific manifest keys here
},
"firefox":{
... firefox-specific manifest keys here
}
}
```
Then run the build command specifying the target platform:
```
xt-build --platform chrome
xt-build --platform firefox
```
The build will then combine all common manifest elements with those
specified for the target platform.
When building cross-browser extensions, most browsers can reuse the
same manifest. Therefore, these two targets are typically sufficient to generate the desired
manifests for multiple target browsers. However, if this strategy
is insufficient, see the next option.
### Multiple build configurations
Create multiple build configuration files:
chrome-config.json:
```json
{
"manifest": "./manifests/chrome.json"
}
```
firefox-config.json:
```json
{
"manifest": "./manifests/firefox.json"
}
```
Using this strategy, run the build command specifying path to config file explicitly:
```
xt-build --config chrome-config.json
xt-build --config firefox-config.json
```
================================================
FILE: guide/03-xt-build-scripts.md
================================================
# Building Scripts
* * *
Instructions for configuring javascript build outputs.
* * *
`js_bundles` key is used to configure build settings for javascript bundles.
It allows you to specify:
1. name of each generated script file
2. what to included in each script
3. how many scripts will be generated by build command
By default `js_bundles` looks for .js files in source directory and generates
a single script.js as output.
## Configuration options
`js_bundles` value is an array of objects, where each object specifies the following keys:
| Key | Value |
--- | ---
| **`name`** | Output filename without file extension |
| **`src`** | Glob pattern for specifying which files to include in the bundle |
| **`mode`** (optional) | webpack build mode; by default same as `--env` flag |
!!! info
Internally JavaScript bundles are built using webpack where mode is determined
by build `--env` flag. If you want to override this behavior and always use a
specific webpack mode configuration, explicitly specify `"mode"` in the build
configuration.
### Specifying source files
For specifying value of `src` you can use any valid glob pattern:
- a string value for a single file, example: `"src/index.js"`
- array of files, example: `["src/index.js", "src/popup.js"]`
- a path with wildcard, for example: `"src/*.js"`
- You may also use `!` as a way to negate inclusion of file(s)
See ["Explaining Globs"](https://gulpjs.com/docs/en/getting-started/explaining-globs) for detailed reference.
## Example
Below is a sample build configuration that will generate two JavaScript files:
- First one contains exactly `src/background.js`
- Second one contains all `.js` files under directories `scr/app/dir1` and `scr/app/dir2`
After running build command `dist/` will contain `background.js` and `app.js`.
```json
"xtbuild": {
"js_bundles": [
{
"name": "background",
"src": "./src/background.js"
},
{
"name": "app",
"src": [
"./src/app/dir1/**/*.js",
"./src/app/dir2/**/*.js"
]
}
]
}
```
================================================
FILE: guide/03-xt-build-styles.md
================================================
# Building Stylesheets
* * *
Instructions for configuring stylesheet build outputs.
* * *
`scss_bundles` are used to configure build settings for CSS stylesheets. The expected value is an array with
zero or more objects where.
- `name` is the output bundle filename with file extension
- `src` specifies which files to include in each bundle; you can use
- a string value for a single file
- array of files, or
- a path with wildcard.
- prefix `!` as a way to negate the inclusion of a file
See [globs syntax guide](https://gulpjs.com/docs/en/api/src) for more details.
Dev build does not minify style files. The production build will minify style files.
By default, the stylesheets are assumed to be written using [Sass](https://sass-lang.com/guide). When naming stylesheet files, use `.scss` file extension because default configuration looks for style files with this file extension.
If you are not a friend of Sass, you can write style sheets using CSS. In the build configuration override the default configuration: `"scss": "./src/**/*.scss"` to treat other file extensions as style files, and use `"scss_bundles"` key to specify how to generate stylesheets, as shown in the example below.
**Example**
Sample project-level configuration with multiple style bundles. This configuration will generate two stylesheets in the output directory: styles.css and display.css.
```json
"xtbuild": {
"scss_bundles": [
{
"src": [
"./src/**/*.scss",
"!./src/app/styles/ui.scss"
],
"name": "styles"
},
{
"src": [
"./src/app/styles/ui.scss"
],
"name": "display"
}
]
}
```
================================================
FILE: guide/03-xt-build.md
================================================
# xt-build
* * *
xt-build command builds an extension project.
* * *
Build command can be used to create a debuggable version, or a production-ready .zip file that can be uploaded to an extension/add-on marketplace for distribution.
Successful build command always generates an extension in build output directory that can be debugged in the browser. The underlying build system uses gulp, babel and webpack (among other plugins) to compile the extension project.
### Dev Build Artifacts
When specifying`dev` build flag, the build will complete using development settings. Successful dev build generates extension source code in the specified build output directory, which can be debugged in a browser.
### Prod Build Artifacts
When specifying `prod` build flag, the build will run a production build. Successful production build generates extension source code in build output directory, which can be debugged in a browser. It also generates a .zip file in the project root. This zip file can be uploaded to extension/add-on marketplace such as Chrome Web Store or Firefox add-ons. When running a production build, all code files (js, css, HTML, json) will be optimized.
## Commands
Braces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.
**Run build with default options**
Default option runs production build targeting Chrome browser.
```bash
xt-build
```
**Run build with explicit environment flag `-e` or `--env`**
```bash
xt-build {-e|--env} dev
```
```bash
xt-build {-e|--env} prod
```
**Run build for specific target browser**
```bash
xt-build {-p|--platform} chrome
```
```bash
xt-build {-p|--platform} firefox
```
**Run build using custom configuration file path**
```bash
xt-build {-c|--config} "/path/to/config.json"
```
**Run development build and keep watching changes**
```bash
xt-build {-e|--env} dev {-w|--watch}
```
**Get help using this command**
```bash
xt-build --help
```
## Package.json scripts
After adding Extension CLI to your project, you can run these commands from a
terminal using syntax `npx xt-build`, or add the following to `packages.json` scripts section,
then execute the commands as `npm run start` or `npm run build`:
```json
"scripts":{
"start": "xt-build -e dev -w",
"build": "xt-build -e prod",
}
```
## Default Configuration
By default the CLI will look for build configuration in two different
places:
- in `package.json` using key `xtbuild`
- in a file named `.xtbuild.json` in project root
Alternatively you can provide a path to configuration file with `-c` or
`--config` flag, followed by a path to configuration file.
The CLI uses a default build configuration shown below. This tells
extension CLI where to look for input files, how to process them, and where
to output files. You can override any of these key-value pairs at project level.
Explanations for each of these keys is given below.
```json
--8<--
./config/build.json
--8<--
```
### Configuration Keys
Key | Description | Guide
--- | --- | ---
`"dist"` | Build output directory ||
`"source"` | Source code directory ||
`"releases"` | Directory for outputting release zip file ||
`"release_name"` | name of release zip file ||
`"manifest"` | extension manifest file path | [Guide](03-xt-build-manifest.md) |
`"js"` | Watch pattern for script changes during dev builds ||
`"js_bundles"` | Javascript bundles configuration | [Guide](03-xt-build-scripts.md)
`"html"` | location and watch pattern of HTML files ||
`"scss"` | Watch pattern for style changes during dev builds ||
`"scss_bundles"` | Stylesheets bundles configuration | [Guide](03-xt-build-styles.md)
`"assets"` | Static assets configuration match pattern | [Guide](03-xt-build-assets.md)
`"copyAsIs"` | Files and directories to copy to output directory without modification | [Guide](03-xt-build-copy.md)
`"locales_dir"` | Localizations directory | [Guide](03-xt-build-locales.md)
`"locales_list"` | List of locales | [Guide](03-xt-build-locales.md)
`"commands"` | Custom commands | [Guide](03-xt-build-cmds.md)
================================================
FILE: guide/04-xt-clean.md
================================================
# xt-clean
* * *
xt-clean command removes all automatically generated files from the project directories.
* * *
Clean operation iterates over files and directories listed in the project `.gitignore` file, and removes all ignored files and directories, except `node_modules/`, `.idea/`, and `.vscode/`. `.idea` is a collection of configuration files used by WebStorm IDE, and `.vscode` is the same for Visual Studio Code. The IDE will generate them automatically if they are absent. To remove these three directories, you must explicitly pass a flag to delete each directory respectively.
## Commands
Braces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.
**Remove ignored files (default)**
```bash
xt-clean
```
**Clear ignored files, including `node_modules`**
```bash
xt-clean {-m|--modules}
```
**Clear ignored files, including `.idea/` directory**
```bash
xt-clean {-i|--idea}
```
**Clear ignored files, including `.vscode/` directory**
```bash
xt-clean {-v|--vscode}
```
**Clear absolutely all ignored files**
```bash
xt-clean -v -i -m
```
**Get help using this command**
```bash
xt-clean --help
```
## Package.json scripts
After installing extension-cli, you can run these commands from a terminal using syntax `npx xt-clean`.
Or you can add an option to `packages.json` scripts section and then execute the command as `npm run clean` See example below.
```json
"scripts":{
"clean": "xt-clean"
}
```
================================================
FILE: guide/05-xt-docs-templates.md
================================================
# Documentation Templates
* * *
Use templates to customize the look and feel of
source code documentation.
* * *
Extension CLI uses [JsDoc](https://jsdoc.app) to document extension projects.
You can then apply templates to customize the look and feel of these docs.
## Customizing Default Template
If you are using the default template see: [Configuring JSDoc's default template](https://jsdoc.app/about-configuring-default-template.html).
## Alternative Templates
To use an alternative template:
1. Choose a suitable template and use npm to install it at project level
2. In the [documentation configuration](https://oss.mobilefirst.me/extension-cli/05-xt-docs/#default-configuration):
1. Specify `"opts.template"` to indicate which template to use
2. Customize the template options under `"templates"`
* * *
### Braintree JSDoc Template
[Source and configuration](https://github.com/braintree/jsdoc-template)

* * *
### clean-jsdoc-theme
[Source and configuration](https://github.com/ankitskvmdam/clean-jsdoc-theme)

_Light mode_
_Dark mode_
* * *
### Foodoc
[Source and configuration](https://github.com/steveush/foodoc)

* * *
### JsDoc Template
[Source and configuration](https://github.com/AlexisPuga/jsdoc-template)

* * *
### Tidy JsDoc
[Source and configuration](https://github.com/julie-ng/tidy-jsdoc)

* * *
================================================
FILE: guide/05-xt-docs.md
================================================
# xt-docs
* * *
xt-docs command generates source
code documentation for an extension project.
* * *
Extension CLI uses [JSDoc](https://jsdoc.app/index.html) specification to
generate documentation for javascript files in an extension project. JSDoc is
a flexible documentation generator that converts javascript code comments to
readable HTML/CSS files which you can be hosted for example with github pages.
## Commands
Braces `{ }` indicate that the user must choose one (and only one) of the
items inside the braces.
**Default command**
```bash
xt-docs
```
**Command using custom configuration file path**
```bash
xt-docs {-c|--config} "/path/to/config.json"
```
**Build docs and keep watching changes**
```bash
xt-docs {-w|--watch}
```
**Get help using this command**
```bash
xt-docs --help
```
## Package.json scripts
After installing extension-cli, you can run these commands from a terminal
using syntax `npx xt-docs`.
Or you can add an option to `packages.json` scripts section and then execute
the command as `npm run docs`. See example below.
```json
"scripts":{
"docs": "xt-docs"
}
```
## Configuration
By default the CLI will look for docs configuration in two different
places:
- in `package.json` using key `xtdocs`
- in a file named `.xtdocs.json` in project root
If these two locations cause a conflict, alternatively you can provide a path
to configuration file with `-c` (`--config`) flag, followed by path to file.
[See commands for an example](#commands).
You can use any compatible template of choice to style your docs. You can find
some [templating options here](05-xt-docs-templates.md).
### Default Configuration
The CLI uses a documentation configuration file shown below. You can override any of these key-value pairs at project level. You can also add key-value pairs that are not defined here so long as they follow to [JSDoc guidelines](https://jsdoc.app/about-configuring-jsdoc.html).
```json
--8<--
./config/docs.json
--8<--
```
================================================
FILE: guide/06-xt-sync.md
================================================
# xt-sync
* * *
xt-sync command enables copying and updating
configuration files.
* * *
When adding more features to an extension project, it is helpful
to \*not\* start from scratch. `xt-sync` command enables extension projects
to pull in starter configuration files for the purposes of linting,
setting up automated CI builds, and for setting up git VCS.
The configuration files are intended as a starting point. If you
end up modifying them heavily at a project level, you should continue
to maintain them manually instead of using this command.
If you do not modify these configuration files, you can sync the
latest version periodically, to update to the latest version supplied
by this CLI.
## Commands
**Sync configuration files**
This command will guide you through the available options.
```bash
xt-sync
```
## Package.json scripts
After installing extension-cli, you can run these commands from a terminal by calling
```bash
npx xt-sync
```
Alternatively you can add an option to `packages.json` scripts section as shown below
```json
"scripts" : {
"sync": "xt-sync"
}
```
and then execute the command by running
```bash
npm run sync
```
================================================
FILE: guide/07-xt-test.md
================================================
# xt-test
* * *
xt-test command runs unit tests.
* * *
This command will setup extension testing environment that is pre-initialized
with [mocha](https://mochajs.org/), [chai](https://www.chaijs.com/),
and expect. [nyc](https://www.npmjs.com/package/nyc) is used for computing code coverage.
The following browser features are initialized for convenience: `window`, `chrome`, `document`.
Window is setup using [jsdom-global](https://www.npmjs.com/package/jsdom-global) and
chrome using [sinon-chrome](https://www.npmjs.com/package/sinon-chrome).
By default this command looks for unit tests in `test/` directory, in any file ending with `.js`.
Mocha will execute with babel, meaning you can use this test environment with modern JavaScript
syntax.
You may extend this unit testing environment within an extension project.
This is simply a base setup for running unit tests for web extensions.
You may also create your very own test environment if this setup is not suitable for your project.
## Commands
Braces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.
**Run unit tests (default)**
```bash
xt-test
```
**Configure custom test directory path or match pattern**
Defaults to `./test/**/*.js` if not specified
```bash
xt-test {-p|--pattern} "./test/**/*.js"
```
**Execute tests and keep watching changes**
```bash
xt-test {-w|--watch}
```
**Get help using this command**
```bash
xt-test --help
```
## Package.json scripts
After installing extension-cli, you can run these commands from a terminal using syntax `npx xt-test`.
You may also add an option to `packages.json` scripts section as shown below, then
- run unit tests from terminal: `npm run test`
- run unit tests and save coverage to file: `npm run coverage`.
```json
"scripts":{
"test": "xt-test",
"coverage": "nyc --reporter=lcov npm run test"
}
```
## Reporting Coverage
### Coveralls
The general setup is:
1. Install [coveralls](https://www.npmjs.com/package/coveralls) as a dev dependency at project level:
```
npm install coveralls --save-dev
```
2. Run unit tests with coverage report during CI/CD workflow, then pipe the result to coveralls:
```
nyc --reporter=lcov npm run test | coveralls
```
If using Github actions, use [Coveralls Github action](https://github.com/marketplace/actions/coveralls-github-action) to report results. Example:
```yaml
- name: Execute unit tests w/ coverage
run: nyc --reporter=lcov npm run test # or: npm run coverage
- name: Report coverage
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
```
================================================
FILE: guide/08-xt-create.md
================================================
# extension-cli
* * *
extension-cli command creates a new web extension project.
* * *
## Commands
```bash
npx extension-cli
```
This command will prompt with necessary questions and does not take any arguments.
This command will generate initial files and directories for a new project.
Run it in the directory where you want to create the extension.
================================================
FILE: guide/09-release-notes-0.md
================================================
---
disqus: "False"
---
### 0.11.9 (2021-04-04)
* **xt-build**: enable customizing release filename [PR #37](https://github.com/MobileFirstLLC/extension-cli/pull/37)
* update dependencies [PR #39](https://github.com/MobileFirstLLC/extension-cli/pull/39)
* improve user guide organization and UI [PR #38](https://github.com/MobileFirstLLC/extension-cli/pull/38), [PR #40](https://github.com/MobileFirstLLC/extension-cli/pull/40)
### 0.11.8 (2021-03-12)
* **xt-build**: support copying directories as-is [#32](https://github.com/MobileFirstLLC/extension-cli/issues/32)
* **xt-build**: append '.css' at the end of name if not specified by the user [PR #36](https://github.com/MobileFirstLLC/extension-cli/pull/36)
### 0.11.7 (2021-03-02)
* **xt-docs**: make watch recursive on watched directories
* **xt-docs**: add tutorials directory to watch list (if exists)
* **xt-docs**: display error when docs command fails
### 0.11.6 (2021-02-25)
* **xt-docs:** add watch mode to docs command, see: [#23](https://github.com/mobilefirstllc/extension-cli/issues/23)
### 0.11.5 (2021-02-24)
* **xt-test:** unit code result reporting fix, see: [#26](https://github.com/mobilefirstllc/extension-cli/issues/26)
### 0.11.3 (2021-01-27)
* **xt-build:** file watch fix
### 0.11.2 (2021-01-08)
* **xt-build:** command path fix
### 0.11.1 (2021-01-06)
* **xt-build:** allow specifying custom build commands
* **xt-create:** fix image generation issue
* update packages
### 0.10.1 (2020-12-15)
* update test configs
* check if gitignore exists before xt-clean
* **xt-create:** change default icon to high contrast
* update packages
### 0.9.4 (2020-11-29)
* extension-cli: fix typo
* update packages
### 0.9.3 (2020-10-31)
* xt-clean: improve xt-clean command handling of files
* change icon
* update docs
### 0.9.1 (2020-10-11)
- fix: xt-docs config keys replace when value is an array
### 0.9.0 (2020-10-05)
- xt-test: add configurable test path
- xt-create: sanitize package name
- update packages
- xt-clean: refactor command
- xt-docs: refactor docs command
- xt-sync: refactor sync command
### 0.8.16 (2020-08-09)
- update packages
### 0.8.15 (2020-08-04)
- update packages
### 0.8.14 (2020-08-01)
- update xt-create
### 0.8.13 (2020-07-26)
- updated packages
### 0.8.12 (2020-05-26)
- update build command
### 0.8.11 (2020-05-25)
- fix issue with create command docs configs
- add new/missing docs dependency
### 0.8.10 (2020-05-25)
- `xt-build` bug fixes
- Made webpack options configurable, to enable adding loaders etc.
- Upgraded project dependencies
### 0.8.9 (2020-04-10)
- Implemented command to create new extension
- Updated docs to reflect this new command
### 0.8.8 (2020-04-08)
- Upgraded project dependencies
### 0.8.7 (2020-01-17)
- Upgraded project dependencies
- Fixed scripts build step (changed webpack options)
### 0.8.6 (2019-12-21)
- Initial release for this publisher
- Migrated project from older source code
- Upgraded all packages
- Migrated build to use Gulp v4
================================================
FILE: guide/09-release-notes.md
================================================
---
disqus: "False"
---
### 1.2.5 (2021-03-16) (alpha)
**Dependency upgrades**
- @babel/preset-env: 7.16.11 ([066f782](https://github.com/MobileFirstLLC/extension-cli/commit/066f7820ce48e877cf5477ff77037c9d3a9d2fdd))
- @babel/register: 7.17.7 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))
- chai: 4.3.6 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))
- commander: 9.0.0 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))
- eslint: 8.11.0 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))
- gulp-sass: 5.1.0 ([0ac28b2](https://github.com/MobileFirstLLC/extension-cli/commit/0ac28b2eb26c998e0958ed4f98691419323b1931))
- jsdoc: 3.6.10 ([d0a09a3](https://github.com/MobileFirstLLC/extension-cli/commit/d0a09a3540580ef1d69edaaf9aa264a4674198b5))
- jsdom: 19.0.0 ([b0f290f](https://github.com/MobileFirstLLC/extension-cli/commit/b0f290fa43f76fc4e67a95ae35ca4cefb7b9db6c))
- mocha: 9.2.2 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))
- sass: 1.49.9 ([aef9fd6](https://github.com/MobileFirstLLC/extension-cli/commit/aef9fd6142879af3bf4610c80eaca1f97aff96a4))
- sinon: 13.0.1 ([8282c0a](https://github.com/MobileFirstLLC/extension-cli/commit/8282c0a05d2702e6ec92155d8cfaba2de93c9c53))
- yargs: 17.3.1 ([add18ee](https://github.com/MobileFirstLLC/extension-cli/commit/add18ee15196d4356a74d0f76ded23d1fef1d085))
### 1.2.4 (2021-10-19)
- Update devtools sourcemap config [PR #119](https://github.com/MobileFirstLLC/extension-cli/pull/119)
- New extension now initialized with MV3 [#86](https://github.com/MobileFirstLLC/extension-cli/pull/111)
**Dependency updates**
- update @babel/preset-env to v7.15.8 ([a341965](https://github.com/mobilefirstllc/extension-cli/commit/a3419659b3ac2427f1134f8c6cfb2bb38c29f009))
- update commander to v8.2.0 ([5226669](https://github.com/mobilefirstllc/extension-cli/commit/52266695f15cace4cc422e229afe5555d30ff0e4))
- update eslint to v8 ([d5549a8](https://github.com/mobilefirstllc/extension-cli/commit/d5549a8730256f61edbd36ab7cabbac95db5000e))
- update jsdom to v18 ([681db6b](https://github.com/mobilefirstllc/extension-cli/commit/681db6bafeedda989471235ff6f14ad9edff1885))
- update mocha to v9.1.2 ([d7cecc6](https://github.com/mobilefirstllc/extension-cli/commit/d7cecc60a2aa918559bea17b2531b3e331500cce))
- update prompts to v2.4.2 ([f99cb60](https://github.com/mobilefirstllc/extension-cli/commit/f99cb608f43414ecbb8f9309ce2d32453b11b0d5))
- update sass to v1.43.2 ([32eb148](https://github.com/mobilefirstllc/extension-cli/commit/32eb148d81318f115942df2682270ded3c061652))
- update webpack-stream to v7 ([#94](https://github.com/mobilefirstllc/extension-cli/issues/94)) ([a19b448](https://github.com/mobilefirstllc/extension-cli/commit/a19b4488cc7f9a31474904e58b2920bf67f0619a))
- update yargs to v17.2.1 ([9a06f44](https://github.com/mobilefirstllc/extension-cli/commit/9a06f44b878d178dbd15fbae490470082b99221a))
### 1.2.2 (2021-07-28)
- update dependencies
### 1.2.0 (2021-07-28)
**Changes to build**
- enable using custom filenames for manifests pre-build [PR #66](https://github.com/MobileFirstLLC/extension-cli/pull/66)
- run build tasks in parallel [PR #70](https://github.com/MobileFirstLLC/extension-cli/pull/70)
- make sourcemap basename match js file name [PR #70](https://github.com/MobileFirstLLC/extension-cli/pull/70)
- dynamically determine project path; remove build config key [PR #71](https://github.com/MobileFirstLLC/extension-cli/pull/71)
**Other changes**
- docs: make JsDoc default template the default documentation template for CLI [#62](https://github.com/MobileFirstLLC/extension-cli/issues/62)
- sync: add CI configuration starter for Github actions [#65](https://github.com/MobileFirstLLC/extension-cli/issues/65)
- sync: eslint config file will now have file extension `.json` [PR #78](https://github.com/MobileFirstLLC/extension-cli/pull/78)
- update dependencies
### 1.1.0 (2021-06-12)
- sync: changed command to prompt with options [PR #57](https://github.com/MobileFirstLLC/extension-cli/pull/57), [#59](https://github.com/MobileFirstLLC/extension-cli/pull/59)
- updated dependencies
### 1.0.3 (2021-04-27)
**Changes to build**
- Make webpack mode configurable [#51](https://github.com/MobileFirstLLC/extension-cli/issues/51), [PR #55](https://github.com/MobileFirstLLC/extension-cli/pull/55)
- use `cheap-source-map` [PR #49](https://github.com/MobileFirstLLC/extension-cli/pull/49)
- remove devtool in prod config [PR #50](https://github.com/MobileFirstLLC/extension-cli/pull/50)
### 1.0.2 (2021-04-11)
**Changes to build**
- Custom folders for scss bundles and always minify css [PR #47](https://github.com/MobileFirstLLC/extension-cli/pull/47)
- Default style bundle name without extension [PR #48](https://github.com/MobileFirstLLC/extension-cli/pull/48)
### 1.0.0 (2021-04-11)
**Changes to build**
- automatically copy from `assets/` to output directory `assets/` [PR #43](https://github.com/MobileFirstLLC/extension-cli/pull/43)
- add target platform for manifests: `chrome/firefox` [PR #43](https://github.com/MobileFirstLLC/extension-cli/pull/43)
- improved build outputs [PR #42](https://github.com/MobileFirstLLC/extension-cli/pull/42)
**Other changes**
- Updated dependencies [PR #44](https://github.com/MobileFirstLLC/extension-cli/pull/44)
================================================
FILE: guide/12-helpful.md
================================================
# Helpful References
* * *
Collection of generally helpful links for extension development.
* * *
**Getting started guides**
[Chrome](https://developer.chrome.com/extensions/getstarted) •
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions#get_started) •
[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions) •
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/) •
[Opera](https://dev.opera.com/extensions/getting-started/)
**Extension manifest references**
[Chrome v3](https://developer.chrome.com/extensions/manifest) •
[Chrome v2 ⚠️](https://developer.chrome.com/docs/extensions/mv2/manifest/) •
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json) •
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/manifest-format) •
[Opera](https://dev.opera.com/extensions/manifest/)
**Lists of browser APIs**
[Chrome](https://developer.chrome.com/extensions/api_index) •
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API#javascript_api_listing) •
[Opera](https://dev.opera.com/extensions/apis/)
**Cross-browser compatibility charts**
[Manifest compatibility](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json) •
[JavaScript API support](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)
**Internationalization**
[Supporting multiple languages](https://developer.chrome.com/extensions/i18n) •
[Language locales list](https://developer.chrome.com/docs/webstore/i18n/#choosing-locales-to-support)
**Extension Publishing Guides**
[Chrome](https://developer.chrome.com/webstore/publish) •
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/create-dev-account) •
[Firefox](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/) •
[Opera](https://dev.opera.com/extensions/publishing-guidelines/) •
[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/distributing_your_safari_web_extension)
**Marketplace Image Guidelines**
[Chrome](https://developer.chrome.com/webstore/images) •
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension#step-5-add-store-listing-details-for-your-extension)
**Marketplace APIs**
[Chrome Web Store](https://developer.chrome.com/webstore/api_index)
**Other Resources**
- [Chrome Extension Samples](https://github.com/GoogleChrome/chrome-extensions-samples) - examples of feature implementations
- [Awesome WebExtensions](https://github.com/fregante/Awesome-WebExtensions) - curated list of resources and tools
================================================
FILE: guide/13-cli-development.md
================================================
---
disqus: "False"
---
# Extension CLI Development
- This CLI is built with Node.Js, written in JavaScript, and uses numerous packages listed below.
- The source code is available on [Github](https://github.com/MobileFirstLLC/extension-cli).
- Releases are published on [NPM](https://www.npmjs.com/package/extension-cli).
- This user guide is built with [MkDocs](https://www.mkdocs.org/) and [MkDocs material theme](https://squidfunk.github.io/mkdocs-material/).
- CI/CD by [Travis CI](https://travis-ci.org/MobileFirstLLC/extension-cli) and documentation served by [Github Pages](https://pages.github.com/).
## Project Organization
Path | Description
--- | ---
└ **.github** | Github config files and markdown
└ **cli** | all available commands are defined here
└ **config** | Resources and config files used by the commands in `cli`
└ **guide** | User guide
└ **test** | CLI unit tests
└ `/*` | Application root; various project config files
* * *
To setup a local dev environment and develop the CLI application, see
[Environment Setup →](13-dev-env.md)
* * *
## Dependencies
Extension CLI is built with the following dependencies:
| # | Package name | Purpose |
| --- | --- | --- |
| 1. | `@babel/preset-env` | for modern JavaScript syntax |
| 2. | `@babel/register` | for unit testing |
| 3. | `chai` | BDD/TDD assertion library for unit testing |
| 4. | `chalk` | Add color to terminal output |
| 5. | `cli-spinner` | Terminal spinner to indicated progress |
| 6. | `commander` | handle CLI input arguments |
| 7. | `del` | for clearing generated files |
| 8. | `eslint` | for linting JavaScript |
| 9. | `gulp` | for running build script |
| 10. | `gulp-change` | JSON file content manipulations |
| 11. | `gulp-clean-css` | Minify CSS |
| 12. | `gulp-concat` | Concatenates files (used for CSS) |
| 13. | `gulp-htmlmin` | Removes whitespace from HTML |
| 14. | `gulp-jsonminify` | minify JSON files (manifest, locales) |
| 15. | `gulp-load-plugins` | to load various gulp plugins |
| 16. | `gulp-merge-json` | merge locales files |
| 17. | `gulp-rename` | rename files during builds |
| 18. | `gulp-sass` | process SASS files during builds |
| 19. | `gulp-zip` | generate zip files |
| 20. | `jsdoc` | generate docs |
| 21. | `jsdom` | mock DOM in Node.js env |
| 22. | `jsdom-global` | adds window, document to unit testing env |
| 23. | `mocha` | unit testing framework |
| 24. | `nyc` | unit testing code coverage tool |
| 25. | `prompts` | create CLI prompts with interactive selectors |
| 26. | `sass` | compile SASS files during builds |
| 27. | `sinon` | JavaScript test spies, stubs and mocks |
| 28. | `sinon-chrome` | unit testing for extensions |
| 29. | `webpack-stream` | build javascript files |
| 30. | `yargs` | parse keyword args |
================================================
FILE: guide/13-dev-env.md
================================================
# Environment Setup
To build extension CLI locally you will need [Node.js](https://nodejs.org/en/download/)
and any web IDE of your choice.
Developing the CLI requires two projects open at the same time:
1. the CLI source code, which you are developing
2. a driver project that is used to execute the CLI commands
The following instructions explain how to set up this environment.
## Instructions
### 1. Setup the CLI
1. [Fork the extension-CLI repo](https://github.com/MobileFirstLLC/extension-cli/fork)
2. Clone the forked repo and then open it in your favorite web IDE
3. Run the following command in terminal:
```bash
npm install
```
### 2. Setup driver project
Next you will need a project to drive the CLI to be able to execute its commands.
You can use any existing extension project that is using extension-cli.
If you do not have an existing project, create a new project. In the directory where you want to create the driver project run:
```bash
npx extension-cli
```
then follow the on-screen instructions. Once you have the project ready, open it in a web IDE.
At this point you should have two IDE windows open.
### 3. Link driver and CLI
1. In **CLI project** terminal run this command (use `sudo npm link` if necessary):
```bash
npm link
```
2. In the **driver project** terminal run this command:
```bash
npm link extension-cli
```
* * *
**Your dev environment should now be ready to use.**
* * *
## Clean up
Unlink CLI and driver project to remove all local links.
In the **driver project** terminal run:
```bash
npm unlink --no-save extension-cli
```
to unlink project from the local CLI version. Note that this may remove
extension-cli from the project completely, and you may need to run `install extension-cli`
to add back the version from NPM registry. This is relevant only if you used
an existing project as a driver.
In **CLI project** terminal run:
```bash
npm r extension-cli -g
```
to remove the CLI symlink.
================================================
FILE: guide/14-user-guide.md
================================================
# Editing User Guide
!!! info
If you are interested in editing the content (and not layout) of this user guide,
simply edit the markdown directly in any markdown editor or on Github.
There is a pencil icon linking to the markdown source on each page of these docs,
which takes you directly to the source document.
## Developing User Guide
When you want to edit the layout, organization and/or theme of these docs, you
will need to run these project docs locally. This user guide is built with Python.
You will need Python 3.x before proceeding.
1. If you are not a maintainer, [fork the repo](https://github.com/MobileFirstLLC/extension-cli/fork)
2. Clone the forked repo and launch your favorite markdown editor and terminal.
3. Setup Python development env as follows:
- Create virtual env for Python packages:
```
python3 -m venv env
```
- Activate virtual env:
```
source env/bin/activate # macOS/Linux
env\Scripts\activate.bat # Windows
```
- Install requirements:
```
pip install -r requirements.txt
```
- Run and debug the docs:
```
mkdocs serve
```
4. Relevant files:
- all written documents are under `guide` directory
- `mkdocs.yml` at project root is a configuration file for Mkdocs
- `guide/assets` includes static assets for these docs
- `guide/overrides` includes customized template files that override default mkdocs-material templates
5. After editing the docs, commit your changes and open a PR as
necessary. Travis CI is used to compile and publish the docs automatically
after each merge to master branch.
================================================
FILE: guide/assets/custom.css
================================================
/* add custom css here */
.md-main__inner{padding-bottom: 3em}
.page-intro{font-size:1.3em;line-height:1.7;}
.highlighttable td pre {line-height: 1.9;}
.md-tabs__item{font-weight: bold}
table {padding: 0;}
table tr {margin: 0;padding: 0; }
table tr th {font-weight: bold;}
table tr th :first-child, table tr td :first-child {margin-top: 0; }
table tr th :last-child, table tr td :last-child {margin-bottom: 0; }
.md-typeset table:not([class]){
font-size: .68rem;
box-shadow: none;
background: var(--md-code-bg-color);
}
table tr th,table tr td,
.md-typeset table:not([class]) th,
.md-typeset table:not([class]) td{
margin: 0; padding: .65em .95em;
}
.md-typeset code{
padding: 3px .35em;
}
.md-typeset table:not([class]) code {
font-size: .65rem;
white-space: nowrap;
}
.md-typeset table:not([class]) tr:hover{
transition: none;
background-color: transparent;
box-shadow: none;
}
.admonition > p{
font-size: 115%;
}
.admonition > p:not(first-child){
line-height: 1.8;
}
p code{
white-space: nowrap;
}
================================================
FILE: guide/assets/custom.js
================================================
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-6XB4XDVPX3');
================================================
FILE: guide/index.md
================================================
---
disqus: "False"
---
# Extension CLI
is a command-line build tool for developing
chromium browser extensions fast and in a standardized way. It provides a systematic way
to organize, build, test and document extension projects.
* * *
## Features
🖥️ **Javascript Bundling**
Compiles, bundles and minifies javascript files (supports ES6, ES2021 syntax)
🎨 **CSS Bundling**
Compiles, bundles, and minifies CSS and [SASS](https://sass-lang.com/guide) files
💄 **Linting**
lint JavaScript using [ESLint](https://eslint.org/)
📦 **ZIP Generation**
Generates a .zip file for uploading to extension marketplaces
📝 **Document Source Code**
Generates source code documentation using [JSDoc](https://jsdoc.app/about-getting-started.html)
⚗️ **Unit Testing**
Sets up a unit testing environment with [mocha](https://mochajs.org), [chai](https://www.chaijs.com/), [sinon-chrome](https://github.com/acvetkov/sinon-chrome) and [js-dom](https://github.com/rstacruz/jsdom-global)
⚔️ **Cross-Browser Compatibility**
develop extensions for Chrome, Edge, Firefox, Opera and Brave.
* * *
Extension CLI is made and maintained free and voluntarily by
open source contributors
behind several popular extensions. If you find it helpful, please share, star, or contribute to its development.
Star Fork Issue Watch
* * *
## Getting Started
**Note:** Using this CLI assumes you have Node.js installed (or [install it here](https://nodejs.org/en/download/)).
Create a new extension project:
```bash
npx extension-cli
```
Add CLI to an existing project:
```bash
npm install extension-cli
```
More detailed [getting started guide here →](https://oss.mobilefirst.me/extension-cli/01-getting-started/)
## Command Reference
Command | Description
--- | ---
**`xt-build`** | Run builds; env flags: `-e prod` or `-e dev`
**`xt-test`**| Run unit tests
**`xt-docs`**| Generate docs
**`xt-clean`** | Remove generated files
**`xt-sync`**| Update project config files to latest versions supplied by this CLI
More detailed [command instructions and configuration options here →](https://oss.mobilefirst.me/extension-cli/03-xt-build/)
================================================
FILE: guide/overrides/main.html
================================================
{% extends "base.html" %}
{% block disqus %}
{% if not page.meta.disqus == "False" %}
{% endif %}
{% endblock %}
================================================
FILE: mkdocs.yml
================================================
site_name: Extension CLI • User Guide
site_description: Command-line build tools for chromium extension development.
site_author: '@mobilefirstllc'
docs_dir: ./guide
site_url: https://oss.mobilefirst.me/extension-cli/
repo_url: https://github.com/MobileFirstLLC/extension-cli
repo_name: "extension-cli"
edit_uri: blob/master/guide/
use_directory_urls: true
nav:
- "Getting Started":
- Intro: index.md
- Installation: 01-getting-started.md
- Configuration: 02-configuration.md
- "Commands":
- 'extension-cli': 08-xt-create.md
- 'xt-build':
- Overview: 03-xt-build.md
- Manifest: 03-xt-build-manifest.md
- Building scripts: 03-xt-build-scripts.md
- Building styles: 03-xt-build-styles.md
- Copying files: 03-xt-build-copy.md
- Localization: 03-xt-build-locales.md
- Static assets: 03-xt-build-assets.md
- Commands: 03-xt-build-cmds.md
- 'xt-clean': 04-xt-clean.md
- 'xt-docs':
- Configuration: 05-xt-docs.md
- Templates: 05-xt-docs-templates.md
- 'xt-sync': 06-xt-sync.md
- 'xt-test': 07-xt-test.md
- "Releases":
- "Version 1.x (latest)": 09-release-notes.md
- "Version 0.x": 09-release-notes-0.md
- "Developer Resources":
- Helpful References: 12-helpful.md
- CLI Development:
- Overview: 13-cli-development.md
- Environment Setup: 13-dev-env.md
- Editing User Guide: 14-user-guide.md
- "Source Code":
- Github: https://github.com/MobileFirstLLC/extension-cli
extra_css:
- assets/custom.css
extra_javascript:
- "https://buttons.github.io/buttons.js"
- "https://www.googletagmanager.com/gtag/js?id=G-6XB4XDVPX3"
- assets/custom.js
theme:
name: material
custom_dir: ./guide/overrides
logo: '/extension-cli/assets/images/guide_icon.svg'
favicon: '/extension-cli/assets/images/favicon.png'
features:
- navigation.tabs
- navigation.tabs.sticky
- navigation.expand
palette:
scheme: slate
primary: amber
accent: amber
font:
text: Inter
extra:
disqus: xyz # enable comments
markdown_extensions:
- admonition
- pymdownx.inlinehilite
- pymdownx.superfences
- pymdownx.snippets
- pymdownx.magiclink
- pymdownx.snippets
- pymdownx.highlight:
use_pygments: true
linenums: true
linenums_style: pymdownx.inline
- meta
================================================
FILE: package.json
================================================
{
"name": "extension-cli",
"version": "1.2.5-alpha.0",
"description": "CLI tool for building browser extensions",
"homepage": "https://oss.mobilefirst.me/extension-cli",
"repository": {
"type": "git",
"url": "https://github.com/mobilefirstllc/extension-cli.git"
},
"bugs": {
"url": "https://github.com/mobilefirstllc/extension-cli.git"
},
"author": {
"name": "Mobile First",
"email": "hello@mobilefirst.me",
"url": "https://mobilefirst.me"
},
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/MobileFirstLLC"
}
],
"engines": {
"node": ">=12.0.0"
},
"bin": {
"extension-cli": "cli/xt-create.js",
"xt-build": "cli/xt-build.js",
"xt-docs": "cli/xt-docs.js",
"xt-test": "cli/xt-test.js",
"xt-clean": "cli/xt-clean.js",
"xt-sync": "cli/xt-sync.js"
},
"keywords": [
"chrome extensions",
"web extensions",
"browser extensions",
"command line",
"developer tools",
"utility",
"web",
"extensions",
"firefox",
"mozilla",
"add-ons",
"google",
"chrome",
"opera",
"edge",
"brave"
],
"scripts": {
"test": "nyc mocha ./test/*.test.js --colors",
"test:report": "npm run test -- nyc report",
"test:travis": "npm run test && nyc report --reporter=text-lcov | coveralls",
"alpha:test": "npx standard-version --dry-run --prerelease alpha",
"patch:test": "npx standard-version --dry-run --release-as patch",
"minor:test": "npx standard-version --dry-run --release-as minor",
"minor:beta:test": "npx standard-version --dry-run --release-as minor --prerelease beta",
"minor:alpha:test": "npx standard-version --dry-run --release-as minor --prerelease alpha",
"major:alpha:test": "npx standard-version --dry-run --release-as major --prerelease alpha",
"major:test": "npx standard-version --dry-run --release-as major",
"alpha": "npx standard-version --prerelease alpha",
"patch": "npx standard-version --release-as patch",
"minor": "npx standard-version --release-as minor",
"patch:alpha": "npx standard-version --release-as patch --prerelease alpha",
"minor:alpha": "npx standard-version --release-as minor --prerelease alpha",
"major:alpha": "npx standard-version --release-as major --prerelease alpha",
"major": "npx standard-version --release-as major"
},
"license": "MIT",
"standard-version": {
"infile": ".github/changelog.md"
},
"dependencies": {
"@babel/preset-env": "7.16.11",
"@babel/register": "7.17.7",
"chai": "4.3.6",
"chalk": "4.1.2",
"cli-spinner": "0.2.10",
"commander": "9.0.0",
"del": "6.0.0",
"eslint": "8.11.0",
"gulp": "4.0.2",
"gulp-change": "1.0.2",
"gulp-clean-css": "4.3.0",
"gulp-concat": "2.6.1",
"gulp-htmlmin": "5.0.1",
"gulp-jsonminify": "1.1.0",
"gulp-load-plugins": "2.0.7",
"gulp-merge-json": "2.1.1",
"gulp-rename": "2.0.0",
"gulp-sass": "5.1.0",
"gulp-zip": "5.1.0",
"jsdoc": "3.6.10",
"jsdom": "19.0.0",
"jsdom-global": "3.0.2",
"mocha": "9.2.2",
"nyc": "15.1.0",
"prompts": "2.4.2",
"sass": "1.49.9",
"sinon": "13.0.1",
"sinon-chrome": "3.0.1",
"webpack-stream": "7.0.0",
"yargs": "17.3.1"
},
"devDependencies": {
"coveralls": "3.1.1"
}
}
================================================
FILE: renovate.json
================================================
{
"labels": ["dependencies"],
"extends": ["config:base", ":disableDependencyDashboard"]
}
================================================
FILE: requirements.txt
================================================
mkdocs
mkdocs-material
================================================
FILE: test/README.md
================================================
# CLI Unit Tests
All unit tests for extension-CLI are in this directory.
================================================
FILE: test/cli-create.test.js
================================================
// TODO: #21: how to require this without actually running the command?
// const BuildScript = require('../cli/xt-build');
// const expect = require('chai').expect;
describe('create command', () => {
// placeholder; replace this with actual test
// it('...(dummy test)', async () => {
// expect(true).to.equal(true);
// });
});
================================================
FILE: test/cli-utilities.test.js
================================================
const Utilities = require('../cli/utilities').Utilities;
const sinon = require('sinon');
const expect = require('chai').expect;
const fs = require('fs');
describe('Test utility functions', () => {
/**
* Stub Node.js IO methods
*/
// eslint-disable-next-line no-undef
beforeEach(() => {
sinon.stub(fs, 'readFileSync');
sinon.stub(fs, 'existsSync');
sinon.stub(fs, 'mkdirSync');
sinon.stub(fs, 'readdirSync');
sinon.stub(fs, 'writeFileSync');
sinon.stub(fs, 'createReadStream');
sinon.stub(fs, 'createWriteStream');
sinon.stub(fs, 'lstatSync');
sinon.stub(fs, 'copyFileSync');
sinon.stub(fs, 'symlinkSync');
sinon.stub(fs, 'readlinkSync');
fs.createReadStream.returns({
pipe: () => true
});
});
/**
* Restore stubbed IO methods
*/
// eslint-disable-next-line no-undef
afterEach(() => {
fs.existsSync.restore();
fs.mkdirSync.restore();
fs.readdirSync.restore();
fs.readFileSync.restore();
fs.writeFileSync.restore();
fs.createReadStream.restore();
fs.createWriteStream.restore();
fs.lstatSync.restore();
fs.copyFileSync.restore();
fs.symlinkSync.restore();
fs.readlinkSync.restore();
});
describe('generateDirectoryName...', () => {
it('...returns lowercase name', () => {
expect(Utilities.generateDirectoryName('HELLO WORLD'))
.to.equal('hello-world');
expect(Utilities.generateDirectoryName('my app name'))
.to.equal('my-app-name');
expect(Utilities.generateDirectoryName('APP-APP'))
.to.equal('app-app');
expect(Utilities.generateDirectoryName('test789'))
.to.equal('test789');
});
it('...replaces special characters with hyphen', () => {
expect(Utilities.generateDirectoryName('MyAwesome#@Thing'))
.to.equal('myawesome-thing');
expect(Utilities.generateDirectoryName('````', 'xyz'))
.to.equal('xyz');
});
it('...removes trailing hyphen', () => {
expect(Utilities.generateDirectoryName('Hello World!!!'))
.to.equal('hello-world');
expect(Utilities.generateDirectoryName('awesom-o app#$%%'))
.to.equal('awesom-o-app');
});
it('...returns default name instead of empty string', () => {
expect(Utilities.generateDirectoryName('', 'foobar'))
.to.equal('foobar');
expect(Utilities.generateDirectoryName(null).length)
.to.be.greaterThan(0);
});
});
describe('replaceVars', () => {
it('...replaces one variable', () => {
expect(Utilities.replaceVars(
'your ${myVar}?', {myVar: 'name'}))
.to.equal('your name?');
});
it('...replace two variables', () => {
expect(Utilities.replaceVars(
'test ${x} ${y}', {x: '1', y: 'z'}))
.to.equal('test 1 z');
});
it('...ignores non-matching keys', () => {
expect(Utilities.replaceVars(
'no ${match} for this', {}))
.to.equal('no ${match} for this');
});
it('...returns input if it contains no variables', () => {
expect(Utilities.replaceVars(
'return me', {me: 'test'}))
.to.equal('return me');
});
it('...interpolation syntax must match', () => {
expect(Utilities.replaceVars(
'return {me}', {me: 'test'}))
.to.equal('return {me}');
expect(Utilities.replaceVars(
'return $me2', {me2: 'test'}))
.to.equal('return $me2');
});
});
describe('keyReplace', () => {
it('...simple override', () => {
let b = {x: 10};
Utilities.keyReplace({x: 8}, b);
expect(b.x).to.equal(8);
});
it('...performs union', () => {
let b = {y: 10};
Utilities.keyReplace({x: 8}, b);
expect(b).to.have.keys(['x', 'y']);
});
it('...replaces array', () => {
let b = {arr: [1, 2, 3]};
Utilities.keyReplace({arr: [4, 5]}, b);
expect(b.arr).to.have.length(2)
.and.to.contain(4).and.to.contain(5);
});
it('...replaces nested properties', () => {
let b = {c: {d: 8, e: 10}};
Utilities.keyReplace({c: {e: 11}}, b);
expect(b.c.d).to.equal(8);
expect(b.c.e).to.equal(11);
});
it('...nested replace with addition', () => {
let b = {b: 8, c: {y: 9}};
Utilities.keyReplace({a: 1, b: 5, c: {x: 1}}, b);
expect(b.a).to.equal(1);
expect(b.b).to.equal(5);
expect(b.c.x).to.equal(1);
expect(b.c.y).to.equal(9);
});
});
describe('iterateConfigs', () => {
const defaultConfig = {name: 'my app', version: '0.0.1'};
it('...returns default if project config is undefined', () => {
const result = Utilities.iterateConfigs(defaultConfig, undefined);
expect(result.name).to.equal(defaultConfig.name);
expect(result.version).to.equal(defaultConfig.version);
expect(Object.keys(result)).to.have.length(2);
});
it('...overrides defaults when override is specified', () => {
let projectConfig = {name: 'my awesome app', version: '1.0.0'};
const result = Utilities.iterateConfigs(defaultConfig, projectConfig);
expect(result.name).to.equal(projectConfig.name);
expect(result.version).to.equal(projectConfig.version);
expect(Object.keys(result)).to.have.length(2);
});
it('...appends new keys when not specified in default', () => {
let projectConfig = {special: {value: 5}};
const result = Utilities.iterateConfigs(defaultConfig, projectConfig);
expect(result.special.value).to.equal(projectConfig.special.value);
expect(Object.keys(result)).to.have.length(3);
});
});
describe('copyFolderSync', () => {
it('...copies directory with files to new location', () => {
fs.readdirSync.returns(['file1.txt', 'file2.txt']);
fs.lstatSync.returns({isFile: () => true});
Utilities.copyFolderSync('./test_dir', './test_dir_2');
expect(fs.mkdirSync.calledOnce).to.equal(true);
expect(fs.copyFileSync.calledTwice).to.equal(true);
});
it('...iterates nested directories recursively', () => {
fs.readdirSync.returns(['test']);
fs.lstatSync.onCall(0).returns({
isFile: () => false,
isSymbolicLink: () => false,
isDirectory: () => true
});
fs.lstatSync.onCall(1).returns({
isFile: () => false,
isSymbolicLink: () => true
});
Utilities.copyFolderSync('./test_dir', './test_dir2');
expect(fs.readdirSync.callCount).to.equal(2);
});
it('...does nothing when not file/dir/symlink', () => {
sinon.spy(Utilities, 'copyFolderSync');
fs.readdirSync.returns(['invalid']);
fs.lstatSync.onCall(0).returns({
isFile: () => false,
isSymbolicLink: () => false,
isDirectory: () => false
});
Utilities.copyFolderSync('a', 'b');
expect(fs.copyFileSync.callCount).to.equal(0);
expect(fs.symlinkSync.callCount).to.equal(0);
expect(Utilities.copyFolderSync.callCount).to.equal(1);
});
});
describe('copyFile', () => {
it('...copies file from old location to new location', () => {
Utilities.copyFile('./test1', './test2');
expect(fs.createReadStream.calledOnce).to.equal(true);
expect(fs.createWriteStream.calledOnce).to.equal(true);
});
});
describe('readFile', () => {
it('...calls read file', () => {
Utilities.readFile('xyz');
expect(fs.readFileSync.calledOnce).to.equal(true);
});
});
describe('writeFile', () => {
it('...calls write file', () => {
Utilities.writeFile('xyz', 'text content...');
expect(fs.writeFileSync.calledOnce).to.equal(true);
});
});
describe('fileExists', () => {
it('...returns true for existing file', () => {
fs.existsSync.returns(true);
expect(Utilities.fileExists('im_here')).to.equal(true);
});
it('...returns false when file does not exist', () => {
fs.existsSync.returns(false);
expect(Utilities.fileExists('nope')).to.equal(false);
});
});
describe('createDir', () => {
it('...will create a directory when it doesn\'t exist', () => {
fs.existsSync.returns(false);
const result = Utilities.createDir('my_dir');
expect(fs.mkdirSync.calledOnce).to.equal(true);
expect(result).to.equal(true);
});
it('...returns true for empty folder', () => {
fs.existsSync.returns(true);
fs.readdirSync.returns({length: 0});
const result = Utilities.createDir('empty_dir');
expect(fs.mkdirSync.notCalled).to.equal(true);
expect(result).to.equal(true);
});
it('...returns false for non-empty folder', () => {
fs.existsSync.returns(true);
fs.readdirSync.returns({length: 1});
expect(Utilities.createDir('non_empty_dir')).to.equal(false);
});
});
describe('readJSON', () => {
it('...returns a parsed JSON object', () => {
fs.readFileSync.returns('{ "title" : "test" }');
const obj = Utilities.readJSON('xyz');
expect(obj.title).to.equal('test');
});
it('...throws error for non-JSON format file content', () => {
fs.readFileSync.returns('this is some plain text');
expect(() => Utilities.readJSON('xyz')).to
.throw('Unexpected token');
});
});
describe('readAndReplaceTextFile', () => {
it('...replaces single variable', () => {
fs.readFileSync.returns('Text with ${variable} in the middle!');
const variables = {variable: 'find me'};
const result = Utilities.readAndReplaceTextFile('some_file', variables);
expect(result).to.contain(variables.variable);
});
it('...replaces multiple variables', () => {
fs.readFileSync.returns('Some math ${a} + ${b} = ${c}');
const math = {a: 1, b: 2, c: 3};
const result = Utilities.readAndReplaceTextFile('my_file', math);
expect(result).to.contain('1 + 2 = 3');
});
});
describe('readAndReplaceJSONFile', () => {
it('...replaces variables in an object', () => {
fs.readFileSync.returns('{ "name":"${name}", "version" : "v-${version}" }');
const values = {name: 'my_app', version: '1.0.0'};
const jsonString = Utilities.readAndReplaceJSONFile('manifest', values);
const obj = JSON.parse(jsonString);
expect(obj.name).to.equal(values.name);
expect(obj.version).to.equal('v-1.0.0');
});
it('...replaces nested variables', () => {
fs.readFileSync.returns('{ "config": { "count" : "${n}" }}');
const values = {n: 10};
const result = Utilities.readAndReplaceJSONFile('manifest', values);
expect(result).to.contain('10');
});
});
});