Full Code of MobileFirstLLC/extension-cli for AI

main d967f786a512 cached
70 files
161.6 KB
47.1k tokens
15 symbols
1 requests
Download .txt
Repository: MobileFirstLLC/extension-cli
Branch: main
Commit: d967f786a512
Files: 70
Total size: 161.6 KB

Directory structure:
gitextract_b9dw6q70/

├── .eslintrc
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── changelog.md
│   ├── code_of_conduct.md
│   ├── contributing.md
│   └── workflows/
│       ├── codeql-analysis.yml
│       └── publish.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── cli/
│   ├── gulpfile.js
│   ├── rootsuite.js
│   ├── texts.js
│   ├── utilities.js
│   ├── xt-build.js
│   ├── xt-clean.js
│   ├── xt-create.js
│   ├── xt-docs.js
│   ├── xt-sync.js
│   └── xt-test.js
├── config/
│   ├── actions.yml
│   ├── build.json
│   ├── docs.json
│   ├── gitlab.yml
│   ├── ignore
│   ├── init/
│   │   ├── background.js
│   │   ├── eslint.json
│   │   ├── intro.md
│   │   ├── manifest.json
│   │   ├── messages.json
│   │   ├── package.json
│   │   └── test.js
│   ├── readme.md
│   └── travis.yml
├── guide/
│   ├── 01-getting-started.md
│   ├── 02-configuration.md
│   ├── 03-xt-build-assets.md
│   ├── 03-xt-build-cmds.md
│   ├── 03-xt-build-copy.md
│   ├── 03-xt-build-locales.md
│   ├── 03-xt-build-manifest.md
│   ├── 03-xt-build-scripts.md
│   ├── 03-xt-build-styles.md
│   ├── 03-xt-build.md
│   ├── 04-xt-clean.md
│   ├── 05-xt-docs-templates.md
│   ├── 05-xt-docs.md
│   ├── 06-xt-sync.md
│   ├── 07-xt-test.md
│   ├── 08-xt-create.md
│   ├── 09-release-notes-0.md
│   ├── 09-release-notes.md
│   ├── 12-helpful.md
│   ├── 13-cli-development.md
│   ├── 13-dev-env.md
│   ├── 14-user-guide.md
│   ├── assets/
│   │   ├── custom.css
│   │   └── custom.js
│   ├── index.md
│   └── overrides/
│       └── main.html
├── mkdocs.yml
├── package.json
├── renovate.json
├── requirements.txt
└── test/
    ├── README.md
    ├── cli-create.test.js
    └── cli-utilities.test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc
================================================
{
    "env": {
      "browser": true,
      "es2021": true,
      "node": true
    },
    "globals": {
      "document": false,
      "escape": false,
      "navigator": false,
      "unescape": false,
      "window": false,
      "describe": true,
      "before": true,
      "it": true,
      "expect": true,
      "sinon": true,
      "afterEach": true,
      "after": true
    },
    "plugins": [],
    "rules": {
      "block-scoped-var": 2,
      "brace-style": [
        2,
        "1tbs",
        {
          "allowSingleLine": true
        }
      ],
      "camelcase": [
        0,
        {
          "properties": "always"
        }
      ],
      "comma-dangle": [
        2,
        "never"
      ],
      "comma-spacing": [
        2,
        {
          "before": false,
          "after": true
        }
      ],
      "comma-style": [
        2,
        "last"
      ],
      "complexity": 0,
      "consistent-return": 2,
      "consistent-this": 0,
      "curly": [
        2,
        "multi-line"
      ],
      "default-case": 0,
      "dot-location": [
        2,
        "property"
      ],
      "dot-notation": 0,
      "eol-last": 2,
      "eqeqeq": [
        2,
        "allow-null"
      ],
      "func-names": 0,
      "func-style": 0,
      "generator-star-spacing": [
        2,
        "both"
      ],
      "guard-for-in": 0,
      "handle-callback-err": [
        2,
        "^(err|error|anySpecificError)$"
      ],
      "key-spacing": [
        2,
        {
          "beforeColon": false,
          "afterColon": true
        }
      ],
      "keyword-spacing": [
        2,
        {
          "before": true,
          "after": true
        }
      ],
      "linebreak-style": 0,
      "max-depth": 0,
      "max-len": [
        2,
        120,
        4
      ],
      "max-nested-callbacks": 0,
      "max-params": 0,
      "max-statements": 0,
      "new-cap": [
        2,
        {
          "newIsCap": true,
          "capIsNew": false
        }
      ],
      "newline-after-var": [
        2,
        "always"
      ],
      "new-parens": 2,
      "no-alert": 0,
      "no-array-constructor": 2,
      "no-bitwise": 0,
      "no-caller": 2,
      "no-catch-shadow": 0,
      "no-cond-assign": 2,
      "no-console": 0,
      "no-constant-condition": 0,
      "no-continue": 0,
      "no-control-regex": 2,
      "no-debugger": 2,
      "no-delete-var": 2,
      "no-div-regex": 0,
      "no-dupe-args": 2,
      "no-dupe-keys": 2,
      "no-duplicate-case": 2,
      "no-else-return": 2,
      "no-empty": 0,
      "no-empty-character-class": 2,
      "no-eq-null": 0,
      "no-eval": 2,
      "no-ex-assign": 2,
      "no-extend-native": 2,
      "no-extra-bind": 2,
      "no-extra-boolean-cast": 2,
      "no-extra-parens": 0,
      "no-extra-semi": 0,
      "no-extra-strict": 0,
      "no-fallthrough": 2,
      "no-floating-decimal": 2,
      "no-func-assign": 2,
      "no-implied-eval": 2,
      "no-inline-comments": 0,
      "no-inner-declarations": [
        2,
        "functions"
      ],
      "no-invalid-regexp": 2,
      "no-irregular-whitespace": 2,
      "no-iterator": 2,
      "no-label-var": 2,
      "no-labels": 2,
      "no-lone-blocks": 0,
      "no-lonely-if": 0,
      "no-loop-func": 0,
      "no-mixed-requires": 0,
      "no-mixed-spaces-and-tabs": [
        2,
        false
      ],
      "no-multi-spaces": 2,
      "no-multi-str": 2,
      "no-multiple-empty-lines": [
        2,
        {
          "max": 1
        }
      ],
      "no-native-reassign": 2,
      "no-negated-in-lhs": 2,
      "no-nested-ternary": 0,
      "no-new": 2,
      "no-new-func": 2,
      "no-new-object": 2,
      "no-new-require": 2,
      "no-new-wrappers": 2,
      "no-obj-calls": 2,
      "no-octal": 2,
      "no-octal-escape": 2,
      "no-path-concat": 0,
      "no-plusplus": 0,
      "no-process-env": 0,
      "no-process-exit": 0,
      "no-proto": 2,
      "no-redeclare": 2,
      "no-regex-spaces": 2,
      "no-reserved-keys": 0,
      "no-restricted-modules": 0,
      "no-return-assign": 2,
      "no-script-url": 0,
      "no-self-compare": 2,
      "no-sequences": 2,
      "no-shadow": 0,
      "no-shadow-restricted-names": 2,
      "no-spaced-func": 2,
      "no-sparse-arrays": 2,
      "no-sync": 0,
      "no-ternary": 0,
      "no-throw-literal": 2,
      "no-trailing-spaces": 1,
      "no-undef": 2,
      "no-undef-init": 2,
      "no-undefined": 0,
      "no-underscore-dangle": 0,
      "no-unneeded-ternary": 2,
      "no-unreachable": 2,
      "no-unused-expressions": 0,
      "no-unused-vars": [
        2,
        {
          "vars": "all",
          "args": "none"
        }
      ],
      "no-use-before-define": 2,
      "no-var": 0,
      "no-void": 0,
      "no-warning-comments": 0,
      "no-with": 2,
      "one-var": 0,
      "operator-assignment": 0,
      "operator-linebreak": [
        2,
        "after"
      ],
      "padded-blocks": 0,
      "quote-props": 0,
      "quotes": [
        2,
        "single",
        "avoid-escape"
      ],
      "radix": 2,
      "semi": [
        2,
        "always"
      ],
      "semi-spacing": 0,
      "sort-vars": 0,
      "space-before-blocks": [
        2,
        "always"
      ],
      "space-before-function-paren": [
        2,
        {
          "anonymous": "always",
          "named": "never"
        }
      ],
      "space-in-brackets": 0,
      "space-in-parens": [
        2,
        "never"
      ],
      "space-infix-ops": 2,
      "space-unary-ops": [
        2,
        {
          "words": true,
          "nonwords": false
        }
      ],
      "spaced-comment": [
        2,
        "always"
      ],
      "strict": 0,
      "use-isnan": 2,
      "valid-jsdoc": 0,
      "valid-typeof": 2,
      "vars-on-top": 2,
      "wrap-iife": [
        2,
        "any"
      ],
      "wrap-regex": 0,
      "yoda": [
        2,
        "never"
      ]
    }
  }


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: [e.g. iOS]
 - Browser [e.g. chrome, safari]
 - Version [e.g. 22]
 - Node.js Version


**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/changelog.md
================================================
# Changelog

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### [1.2.5-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.4...v1.2.5-alpha.0) (2022-03-17)


### Bug Fixes

* **deps:** downgraded chalk to v4 ([fee1634](https://github.com/mobilefirstllc/extension-cli/commit/fee16346a9b30b6dc61d3c76d0f1deaf868d3988)), closes [#155](https://github.com/mobilefirstllc/extension-cli/issues/155)
* **deps:** update babel monorepo ([#157](https://github.com/mobilefirstllc/extension-cli/issues/157)) ([066f782](https://github.com/mobilefirstllc/extension-cli/commit/066f7820ce48e877cf5477ff77037c9d3a9d2fdd))
* **deps:** update babel monorepo to v7.16.0 ([945f8a0](https://github.com/mobilefirstllc/extension-cli/commit/945f8a0d9041e0c424f87351e2ecbd56b2cf4ce2))
* **deps:** update babel monorepo to v7.16.5 ([1145058](https://github.com/mobilefirstllc/extension-cli/commit/11450586884e27324b63b35d4c99209b623217bf))
* **deps:** update babel monorepo to v7.16.8 ([c4a44aa](https://github.com/mobilefirstllc/extension-cli/commit/c4a44aaf06abedeb4fcda8ec3abac55bef34f147))
* **deps:** update dependency @babel/preset-env to v7.16.4 ([502269b](https://github.com/mobilefirstllc/extension-cli/commit/502269bfa4b9ec02c0ca9a4a0292be2b1f62fb1e))
* **deps:** update dependency @babel/register to v7.16.9 ([206eafd](https://github.com/mobilefirstllc/extension-cli/commit/206eafdb7a1a3109f8da56691ebf36b291c0414b))
* **deps:** update dependency chalk to v5 ([#134](https://github.com/mobilefirstllc/extension-cli/issues/134)) ([e7c7af0](https://github.com/mobilefirstllc/extension-cli/commit/e7c7af0091bbab8db9be87a1d5ae36982a410d67))
* **deps:** update dependency eslint to v8.0.1 ([204a9c0](https://github.com/mobilefirstllc/extension-cli/commit/204a9c025abd57336dda507fe14c28f66afd5145))
* **deps:** update dependency eslint to v8.2.0 ([f208e0f](https://github.com/mobilefirstllc/extension-cli/commit/f208e0fe1d96ba95af10ab3ad5278024a05f26c4))
* **deps:** update dependency eslint to v8.3.0 ([d7bec9f](https://github.com/mobilefirstllc/extension-cli/commit/d7bec9f05496e8c73f4144885f2481583578b161))
* **deps:** update dependency eslint to v8.4.0 ([2489220](https://github.com/mobilefirstllc/extension-cli/commit/248922085384faa5fd8b568c51601d3f0ab23720))
* **deps:** update dependency eslint to v8.4.1 ([91d64e0](https://github.com/mobilefirstllc/extension-cli/commit/91d64e0c20c84b1c4759f8409bb26370affeb87d))
* **deps:** update dependency eslint to v8.5.0 ([c612311](https://github.com/mobilefirstllc/extension-cli/commit/c6123110367028cac4342f637fd345b37c2efa70))
* **deps:** update dependency eslint to v8.7.0 ([#149](https://github.com/mobilefirstllc/extension-cli/issues/149)) ([4684574](https://github.com/mobilefirstllc/extension-cli/commit/4684574c16afa04af4bc6b797d4837b5bfaae84d))
* **deps:** update dependency eslint to v8.8.0 ([#161](https://github.com/mobilefirstllc/extension-cli/issues/161)) ([87fb840](https://github.com/mobilefirstllc/extension-cli/commit/87fb8403339033106d10b13a50429841a8dd20ac))
* **deps:** update dependency jsdoc to v3.6.10 ([#158](https://github.com/mobilefirstllc/extension-cli/issues/158)) ([d0a09a3](https://github.com/mobilefirstllc/extension-cli/commit/d0a09a3540580ef1d69edaaf9aa264a4674198b5))
* **deps:** update dependency jsdom to v18.0.1 ([9daee71](https://github.com/mobilefirstllc/extension-cli/commit/9daee71d218cec2cc5cac39022ef7483792a600d))
* **deps:** update dependency jsdom to v18.1.0 ([db6641b](https://github.com/mobilefirstllc/extension-cli/commit/db6641bfaf6ab9bbb7ee66ababff1db5742747d7))
* **deps:** update dependency jsdom to v18.1.1 ([7e1e222](https://github.com/mobilefirstllc/extension-cli/commit/7e1e22282b95329a7c99d4069a36b0501475e6bb))
* **deps:** update dependency jsdom to v19 ([b0f290f](https://github.com/mobilefirstllc/extension-cli/commit/b0f290fa43f76fc4e67a95ae35ca4cefb7b9db6c))
* **deps:** update dependency mocha to v9.1.3 ([c5186f9](https://github.com/mobilefirstllc/extension-cli/commit/c5186f9cb3b0cf1c27998d519c9d470863074358))
* **deps:** update dependency mocha to v9.1.4 ([f90c528](https://github.com/mobilefirstllc/extension-cli/commit/f90c528f16182d652c305144fb247b5b375f38e6))
* **deps:** update dependency mocha to v9.2.1 ([534eef9](https://github.com/mobilefirstllc/extension-cli/commit/534eef9c897f36e999fecf475a4a8effb9e326c0))
* **deps:** update dependency sass to v1.43.4 ([e9674a8](https://github.com/mobilefirstllc/extension-cli/commit/e9674a8de69848da4a6557f7beef6387f2bf283e))
* **deps:** update dependency sass to v1.43.5 ([ce6e9ab](https://github.com/mobilefirstllc/extension-cli/commit/ce6e9abf46188690762dd45a15b6659b620a122f))
* **deps:** update dependency sass to v1.44.0 ([60842ce](https://github.com/mobilefirstllc/extension-cli/commit/60842cef73de48d2bdbfa2fd93955701981ce9d3))
* **deps:** update dependency sass to v1.45.0 ([524a5d7](https://github.com/mobilefirstllc/extension-cli/commit/524a5d770c49365f449fc4bda8cf293f9c80f969))
* **deps:** update dependency sass to v1.45.1 ([6539428](https://github.com/mobilefirstllc/extension-cli/commit/6539428590a26e61fcbc9edc02e7ecd6b7e2bd7f))
* **deps:** update dependency sass to v1.46.0 ([bb70958](https://github.com/mobilefirstllc/extension-cli/commit/bb70958be4edb8ed6faef497fb5ba6a6bfbe0694))
* **deps:** update dependency sass to v1.48.0 ([1cfb841](https://github.com/mobilefirstllc/extension-cli/commit/1cfb841a92df7d99bf29852ea6110897690f7334))
* **deps:** update dependency sass to v1.49.8 ([#156](https://github.com/mobilefirstllc/extension-cli/issues/156)) ([5a658c0](https://github.com/mobilefirstllc/extension-cli/commit/5a658c09db5e06934b6f53345d212ab72edeecb8))
* **deps:** update dependency sass to v1.49.9 ([aef9fd6](https://github.com/mobilefirstllc/extension-cli/commit/aef9fd6142879af3bf4610c80eaca1f97aff96a4))
* **deps:** update dependency sinon to v12 ([1c9807d](https://github.com/mobilefirstllc/extension-cli/commit/1c9807d4ab682a8d4e24ebcf591fdd693828c0a2))
* **deps:** update dependency sinon to v13 ([8282c0a](https://github.com/mobilefirstllc/extension-cli/commit/8282c0a05d2702e6ec92155d8cfaba2de93c9c53))
* **deps:** update dependency yargs to v17.3.0 ([9f86f05](https://github.com/mobilefirstllc/extension-cli/commit/9f86f05b858551c9cff0b237830e19223627264a))
* **deps:** update dependency yargs to v17.3.1 ([#145](https://github.com/mobilefirstllc/extension-cli/issues/145)) ([add18ee](https://github.com/mobilefirstllc/extension-cli/commit/add18ee15196d4356a74d0f76ded23d1fef1d085))

### [1.2.4](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.4-beta.0...v1.2.4) (2021-10-20)

### [1.2.4-beta.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.2...v1.2.4-beta.0) (2021-10-14)

* 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**

* **deps:** update dependency sass to v1.43.2 ([32eb148](https://github.com/mobilefirstllc/extension-cli/commit/32eb148d81318f115942df2682270ded3c061652))
* **deps:** update dependency @babel/preset-env to v7.15.8 ([a341965](https://github.com/mobilefirstllc/extension-cli/commit/a3419659b3ac2427f1134f8c6cfb2bb38c29f009))
* **deps:** update dependency commander to v8.2.0 ([5226669](https://github.com/mobilefirstllc/extension-cli/commit/52266695f15cace4cc422e229afe5555d30ff0e4))
* **deps:** update dependency eslint to v8 ([d5549a8](https://github.com/mobilefirstllc/extension-cli/commit/d5549a8730256f61edbd36ab7cabbac95db5000e))
* **deps:** update dependency jsdom to v18 ([681db6b](https://github.com/mobilefirstllc/extension-cli/commit/681db6bafeedda989471235ff6f14ad9edff1885))
* **deps:** update dependency mocha to v9.1.2 ([d7cecc6](https://github.com/mobilefirstllc/extension-cli/commit/d7cecc60a2aa918559bea17b2531b3e331500cce))
* **deps:** update dependency prompts to v2.4.2 ([f99cb60](https://github.com/mobilefirstllc/extension-cli/commit/f99cb608f43414ecbb8f9309ce2d32453b11b0d5))
* **deps:** update dependency webpack-stream to v7 ([#94](https://github.com/mobilefirstllc/extension-cli/issues/94)) ([a19b448](https://github.com/mobilefirstllc/extension-cli/commit/a19b4488cc7f9a31474904e58b2920bf67f0619a))
* **deps:** update dependency yargs to v17.2.1 ([9a06f44](https://github.com/mobilefirstllc/extension-cli/commit/9a06f44b878d178dbd15fbae490470082b99221a))

### [1.2.2](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0...v1.2.2) (2021-08-28)

## [1.2.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0-beta.1...v1.2.0) (2021-07-28)

## [1.2.0-beta.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0-beta.0...v1.2.0-beta.1) (2021-07-25)

## [1.2.0-beta.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0-alpha.1...v1.2.0-beta.0) (2021-07-24)

## [1.2.0-alpha.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0-alpha.0...v1.2.0-alpha.1) (2021-07-19)

## [1.2.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.1.0...v1.2.0-alpha.0) (2021-07-19)

* Useless regular-expression character escape ([935d0b3](https://github.com/mobilefirstllc/extension-cli/commit/935d0b3df52255c3c18526b4a1a3212c2c6b1b20))
* Useless regular-expression character escape ([f2ebae5](https://github.com/mobilefirstllc/extension-cli/commit/f2ebae55ca8bc5df2fe8872a58131219b6f81a88))

## [1.1.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.1.0-alpha.1...v1.1.0) (2021-06-12)

## [1.1.0-alpha.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.1.0-alpha.0...v1.1.0-alpha.1) (2021-06-12)

## [1.1.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3...v1.1.0-alpha.0) (2021-06-12)

* **xt-sync:** change command to prompt with options; update relevant docs ([7a65245](https://github.com/mobilefirstllc/extension-cli/commit/7a652455e834929c1d5e78d8dc5648a64f079aca))

### [1.0.3](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3-alpha.2...v1.0.3) (2021-04-27)

### [1.0.3-alpha.2](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3-alpha.1...v1.0.3-alpha.2) (2021-04-27)

### [1.0.3-alpha.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3-alpha.0...v1.0.3-alpha.1) (2021-04-16)

### [1.0.3-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.2...v1.0.3-alpha.0) (2021-04-15)

### [1.0.2](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.1...v1.0.2) (2021-04-12)

* default style bundle without file extension ([70c4aa6](https://github.com/mobilefirstllc/extension-cli/commit/70c4aa69f7c4bdaa440cd3fe61eabe5a4ff7327c))

### [1.0.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.0...v1.0.1) (2021-04-12)

## [1.0.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.0-alpha.0...v1.0.0) (2021-04-11)

## [1.0.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.9...v1.0.0-alpha.0) (2021-04-09)

### [0.11.9](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.9-alpha.0...v0.11.9) (2021-04-05)

### [0.11.9-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8...v0.11.9-alpha.0) (2021-04-04)

### [0.11.8](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8-alpha.2...v0.11.8) (2021-03-12)

### [0.11.8-alpha.2](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8-alpha.1...v0.11.8-alpha.2) (2021-03-11)

### [0.11.8-alpha.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8-alpha.0...v0.11.8-alpha.1) (2021-03-10)

### [0.11.8-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.7...v0.11.8-alpha.0) (2021-03-09)

### [0.11.7](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.7-alpha.0...v0.11.7) (2021-03-02)

* **xt-docs**: make watch recursive, display more output on errors

### [0.11.7-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.6...v0.11.7-alpha.0) (2021-03-02)

### [0.11.6](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.6-alpha.0...v0.11.6) (2021-02-26)

* **xt-docs** [#23](https://github.com/MobileFirstLLC/extension-cli/issues/23) add watch mode for docs

### [0.11.5](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.5-alpha.1...v0.11.5) (2021-02-24)

* **xt-test** [#26](https://github.com/mobilefirstllc/extension-cli/issues/26) unit test coverage reporting and reporting error code on test failure ([d3bba9d](https://github.com/mobilefirstllc/extension-cli/commit/d3bba9d08e04d574ab26468b522f33db3567fd9a))

### [0.11.3](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.3-alpha.0...v0.11.3) (2021-01-27)

### [0.11.3-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.2...v0.11.3-alpha.0) (2021-01-27)

### [0.11.2](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.1...v0.11.2) (2021-01-08)

* **xt-build:** command path fix ([c0c2e08](https://github.com/mobilefirstllc/extension-cli/commit/c0c2e08bcfbb348ce8bb8d9980d4db22e2713d37))

### [0.11.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.0...v0.11.1) (2021-01-06)

* **custom commands:** [#18](https://github.com/mobilefirstllc/extension-cli/issues/18) enable custom commands watch ([3cfe52b](https://github.com/mobilefirstllc/extension-cli/commit/3cfe52ba38779d1570943a74a0a2e45203159d54))
* [#18](https://github.com/mobilefirstllc/extension-cli/issues/18) add configuration option for custom commands before building release zip file ([f8126e6](https://github.com/mobilefirstllc/extension-cli/commit/f8126e6838faa043fe38844f8941cc465ec2425a))

* xt-create images fix (again)

### [0.10.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.4...v0.10.1) (2020-12-15)

* xt-create images ([b6ad50f](https://github.com/mobilefirstllc/extension-cli/commit/b6ad50f0367a20d45d2ea677591e3869c3f4fff3))
* [#16](https://github.com/mobilefirstllc/extension-cli/issues/16) update test configs ([7f57bf5](https://github.com/mobilefirstllc/extension-cli/commit/7f57bf527ab6fcccec53fd4544e2fc134b5083ee))
* [#17](https://github.com/mobilefirstllc/extension-cli/issues/17) check if gitignore exists ([b96edc9](https://github.com/mobilefirstllc/extension-cli/commit/b96edc9a01e0077b0d68f994782c2b5c840199c1))
* **xt-create:** change default icon to high contrast ([4895f43](https://github.com/mobilefirstllc/extension-cli/commit/4895f43bf16b63309bc9e3d14248843050bc164c))

### [0.9.4](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.3...v0.9.4) (2020-11-29)

### [0.9.3](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.2...v0.9.3) (2020-10-31)

### [0.9.2](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.1...v0.9.2) (2020-10-31)

* [#10](https://github.com/mobilefirstllc/extension-cli/issues/10) improve xt-clean command handling of files ([b14f311](https://github.com/mobilefirstllc/extension-cli/commit/b14f311077b713f749e352a889a5c7b843e5f89a))

### [0.9.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.0...v0.9.1) (2020-10-11)

* [#8](https://github.com/mobilefirstllc/extension-cli/issues/8) xt-docs fix config keys replace when value is an array ([98d72ca](https://github.com/mobilefirstllc/extension-cli/commit/98d72ca8f3df251e36bd1b2da3ecea9e2832496d))

## [0.9.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.8.16...v0.9.0) (2020-10-05)

* **xt-clean:** refactor command ([8acf9c8](https://github.com/mobilefirstllc/extension-cli/commit/8acf9c80757c44265c9bf605882d8a06fd97d7bb))
* **xt-create:** refactor command ([1c64f93](https://github.com/mobilefirstllc/extension-cli/commit/1c64f932966c405b3958ce6069323661ac5079d9))
* **xt-create:** refactor create command ([6ac7f95](https://github.com/mobilefirstllc/extension-cli/commit/6ac7f95f1e08ea278498293c4f4d3048b5bc8190))
* **xt-docs:** refactor docs command ([432fa79](https://github.com/mobilefirstllc/extension-cli/commit/432fa7979ca7dc686c31cdd6d92fa5911ad467f1))
* **xt-sync:** refactor sync ([c938eec](https://github.com/mobilefirstllc/extension-cli/commit/c938eec37a6ce3a4ad8bba1c5a22519a01242761))
* **xt-test:** add configurable test path ([876fb8a](https://github.com/mobilefirstllc/extension-cli/commit/876fb8a0afbaeab196df6aebd09fc92a977150de))

### [0.8.16](https://github.com/mobilefirstllc/extension-cli/compare/v0.8.15...v0.8.16) (2020-08-09)

### [0.8.15](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.14...v0.8.15) (2020-08-04)

* fix packages ([0cad024](https://github.com/MobileFirstLLC/extension-cli/commit/0cad024ba53ec61ae96db0bcf6a616f476acebcf))

### [0.8.14](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.13...v0.8.14) (2020-08-01)

* **xt-create:** update generated docs ([a7ebb95](https://github.com/MobileFirstLLC/extension-cli/commit/a7ebb952cfeb0beac89b71a680a24eaad7325d95))

### [0.8.13](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.12...v0.8.13) (2020-07-11)

### [0.8.12](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.11...v0.8.12) (2020-05-26)

* **xt-build:** undo webpack config change ([19a19ba](https://github.com/MobileFirstLLC/extension-cli/commit/19a19baf7a99787bf34c295fe580d646a70b8b2f))

### [0.8.11](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.10...v0.8.11) (2020-05-26)

* **xt-docs:** fix docs command related issue with init configs ([88c5f58](https://github.com/MobileFirstLLC/extension-cli/commit/88c5f58986b9b2c33c8b86d774e466c7a9f24e03))

### [0.8.10](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9...v0.8.10) (2020-05-25)

* **xt-build:** [#2](https://github.com/MobileFirstLLC/extension-cli/issues/2) Updating manifest while watching causes looping behavior ([935377a](https://github.com/MobileFirstLLC/extension-cli/commit/935377a2ee4ebbf68e1bd2165de6a6dd641397b9))
* **xt-build:** [#3](https://github.com/MobileFirstLLC/extension-cli/issues/3) make webpack options configurable to user ([74af14f](https://github.com/MobileFirstLLC/extension-cli/commit/74af14f3ed6eacf26f7534636412b20151b76909))
* **xt-build:** JS build on watch doesn't rebuild js files correctly [#4](https://github.com/MobileFirstLLC/extension-cli/issues/4) ([e80c2d6](https://github.com/MobileFirstLLC/extension-cli/commit/e80c2d635e15a27c6c5f0c53fa63e25ba50a6233))
* **xt-build:** make webpack options configurable to user [#3](https://github.com/MobileFirstLLC/extension-cli/issues/3) ([4148890](https://github.com/MobileFirstLLC/extension-cli/commit/4148890e1c7d925214974710c515746009033f91))

### [0.8.9](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9-alpha.2...v0.8.9) (2020-04-10)

### [0.8.9-alpha.2](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9-alpha.1...v0.8.9-alpha.2) (2020-04-10)

* **xt-create:** update docs regarding xt-create and use ([27b39bb](https://github.com/MobileFirstLLC/extension-cli/commit/27b39bb46c5f57336a0083fa35d395936605936a))

### [0.8.9-alpha.1](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9-alpha.0...v0.8.9-alpha.1) (2020-04-10)

* **xt-create:** minor fixes ([c3c7b31](https://github.com/MobileFirstLLC/extension-cli/commit/c3c7b31fc8ca8c92be0e546287f474e3744439e3))

### [0.8.9-alpha.0](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.8...v0.8.9-alpha.0) (2020-04-10)

* **xt-create:** implement create command ([c28b22b](https://github.com/MobileFirstLLC/extension-cli/commit/c28b22b8ae41b786e49222c3d7c8830f5a2e92b4))

### [0.8.8](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.7...v0.8.8) (2020-04-08)

* Upgraded NPM packages

### [0.8.7](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.6...v0.8.7) (2020-01-17)

* build scripts and update packages ([74f51d2](https://github.com/MobileFirstLLC/extension-cli/commit/74f51d2a1dfe1b4d1611ca4ef848f20dfc92b81e))

### [0.8.6](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.5...v0.8.6) (2019-12-21)

### [0.8.5](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.4...v0.8.5) (2019-12-21)

### [0.8.4](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.3...v0.8.4) (2019-12-20)

### [0.8.3](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.2...v0.8.3) (2019-12-20)

### [0.8.2](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.1...v0.8.2) (2019-12-20)

* cognitive complexity ([de4d2fb](https://github.com/MobileFirstLLC/extension-cli/commit/de4d2fbbb475fbc43787925a7b25ad28fbb13330))
* reduce complexity ([78bbb16](https://github.com/MobileFirstLLC/extension-cli/commit/78bbb163e6f6ec9396a4146147a52488ab1637c9))
* Similar blocks of code found in 2 locations. Consider refactoring. ([b3c4e3d](https://github.com/MobileFirstLLC/extension-cli/commit/b3c4e3d014a3f2ac7db301ee3f80e16dbd8b7b00))
* Similar blocks of code found in 3 locations. Consider refactoring. ([c6a7e30](https://github.com/MobileFirstLLC/extension-cli/commit/c6a7e3074d8c1305246b0b69fb03475a3d27ec9f))

### [0.8.1](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0...v0.8.1) (2019-12-20)

## [0.8.0](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0-alpha.3...v0.8.0) (2019-12-19)

## [0.8.0-alpha.3](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0-alpha.2...v0.8.0-alpha.3) (2019-12-18)

## [0.8.0-alpha.2](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0-alpha.1...v0.8.0-alpha.2) (2019-12-18)

* try fix ci publish ([23ae7b3](https://github.com/MobileFirstLLC/extension-cli/commit/23ae7b33e2fc361fffafec2e9ec79693d212d292))

## [0.8.0-alpha.1](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0-alpha.0...v0.8.0-alpha.1) (2019-12-18)

* xt-build -> use webpack with scripts ([063cecc](https://github.com/MobileFirstLLC/extension-cli/commit/063cecc585d5657c005af7342eeb04466930ea09))
* xt-build locales and manifest tasks ([f98872e](https://github.com/MobileFirstLLC/extension-cli/commit/f98872e0f39cd7095076e4f97897de6857fe3306))
* xt-build webpack scripts ([13e2297](https://github.com/MobileFirstLLC/extension-cli/commit/13e2297b912b067eaf1ad44e449e83585b3a22ab))
* xt-test command ([130986f](https://github.com/MobileFirstLLC/extension-cli/commit/130986f3bf58c01d46e14308d2bf080b1b88c6fc))

## 0.8.0-alpha.0 (2019-12-17)

* jsdoc config ([b021a0e](https://github.com/MobileFirstLLC/extension-cli/commit/b021a0e1424d4f5e226dea1d1940dee9af3fdbb8))
* migrate build script to gulp 4 ([237c44f](https://github.com/MobileFirstLLC/extension-cli/commit/237c44f4107a66c7ffc7fd9917e6343d28f90021))
* migrate xt-test; update docs ([2ae3e78](https://github.com/MobileFirstLLC/extension-cli/commit/2ae3e78128b5db0dad81b1f589fb3e6add732fe8))
* xt-clean, xt-docs; update packages; add build configuration, image assets, and source docs ([85eead2](https://github.com/MobileFirstLLC/extension-cli/commit/85eead28704cf684b520409f192fe7073623f2fa))
* xt-sync; update readme ([ddcc8aa](https://github.com/MobileFirstLLC/extension-cli/commit/ddcc8aaab252e7e184061576fb6683f3ed095343))

<a name="0.7.11"></a>
## 0.7.11 (2018-06-25)

<a name="0.7.10"></a>
## 0.7.10 (2018-06-25)

<a name="0.7.9"></a>
## 0.7.9 (2018-06-10)

<a name="0.7.8"></a>
## 0.7.8 (2018-06-06)

* **xt-docs:** update docs output path 
* **xt-sync:** update ci configs 

<a name="0.7.7"></a>
## 0.7.7 (2018-06-04)

* **xt-test:** revert 

<a name="0.7.5"></a>
## 0.7.5 (2018-05-29)

<a name="0.7.6"></a>
## 0.7.6 (2018-06-04)

* **xt-test:** expose jsdom 

<a name="0.7.5"></a>
## 0.7.5 (2018-05-29)

<a name="0.7.4"></a>
## 0.7.4 (2018-05-25)

* **xt-test:** added chai-as-promised

<a name="0.7.3"></a>
## 0.7.3 (2018-05-24)

<a name="0.7.2"></a>
## 0.7.2 (2018-05-22)

* **xt-test:** added move event simulators

<a name="0.7.1"></a>
## 0.7.1  (2018-05-22)

<a name="0.7.0"></a>
# 0.7.0 (2018-05-21)

* **xt-test:** add test runner and env setup 

<a name="0.6.3"></a>
## 0.6.3 (2018-05-21)

* **xt-build:** added spinner and logging on watch 

<a name="0.6.2"></a>
## 0.6.2 (2018-05-21)

* **xt-sync:** fix path 

<a name="0.6.1"></a>
## 0.6.1 (2018-05-21)

* **xt-sync:** update commands 

<a name="0.6.0"></a>
# 0.6.0 (2018-05-21)

* **xt-sync:** consolidate file updates into one command; remove xt-ci, xt-ignore, and xt-lint

<a name="0.5.0"></a>
# 0.5.0 (2018-05-21)

* **ci:** fix ci command
* **xt-clean:** added clean option 

<a name="0.4.0"></a>
# 0.4.0 (2018-05-20)

* **build:** remove initial build script console output  
* **build:** add option to have configs in package.json 
* **docs:** simplyfy docs command and enable config in package.json  
* **xt-cli:** update ci settings 

<a name="0.3.2"></a>
## 0.3.2 (2018-05-19)

* **build:** update build to pipe output, change default configs && path

<a name="0.3.1"></a>
## 0.3.1 (2018-05-18)

* **build:** fix done callback 

<a name="0.3.0"></a>
# 0.3.0 (2018-05-14)

* **xt-build:** added copyAsIs to build options

<a name="0.2.1"></a>
## 0.2.1 (2018-05-14)

* **config:** update docs default config 

<a name="0.2.0"></a>
# 0.2.0 (2018-05-14)

* **xt-lint:** create or update eslint settings 

<a name="0.1.0"></a>
# 0.1.0 (2018-05-14)

<a name="0.0.7"></a>
#### 0.0.7 (2018-05-14)

<a name="0.0.6"></a>
#### 0.0.6 (2018-05-14)

<a name="0.0.5"></a>
#### 0.0.5 (2018-05-14)

<a name="0.0.4"></a>
#### 0.0.4 (2018-05-14)

<a name="0.0.3"></a>
#### 0.0.3 (2018-05-14)

<a name="0.0.2"></a>
#### 0.0.2  (2018-05-14)

<a name="0.0.1"></a>
#### 0.0.1 (2018-05-14)


================================================
FILE: .github/code_of_conduct.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
 advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
 address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
 professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at hello@mobilefirst.me. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org

For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq


================================================
FILE: .github/contributing.md
================================================
Let's add guidelines here over time


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [ master ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ master ]
  schedule:
    # Run every day at 2200 EST
    - cron: '0 2 * * *'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'javascript' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
        # Learn more:
        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    # Initializes the CodeQL tools for scanning.
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v1
      with:
        languages: ${{ matrix.language }}
        # If you wish to specify custom queries, you can do so here or in a config file.
        # By default, queries listed here will override any specified in a config file.
        # Prefix the list here with "+" to use these queries and those in the config file.
        # queries: ./path/to/local/query, your-org/your-repo/queries@main

    # ℹ️ Command-line programs to run using the OS shell.
    # 📚 https://git.io/JvXDl

    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
    #    and modify them (or add more) to build your code if your project
    #    uses a compiled language

    #- run: |
    #   make bootstrap
    #   make release

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v1


================================================
FILE: .github/workflows/publish.yml
================================================
name: NPM Publish

on:
  push:
    tags:
      - '*'

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v3
        with:
          node-version: '14'

      - name: Tag name
        run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
      - run: npm ci
      - run: npm run test

      - name: NPM Publish
        uses: JS-DevTools/npm-publish@v1
        with:
          tag: ${{ fromJSON('["latest", "alpha"]')[contains(env.TAG, '-')] }}
          token: ${{ secrets.NPM_TOKEN }}

      - name: Github Release
        uses: ncipollo/release-action@v1
        if: "!contains(env.TAG, '-')"
        with:
          token: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
.idea/
node_modules/
site/
tutorial-env/
.nyc_output
env
icon.png
venv


================================================
FILE: .npmignore
================================================
.idea/
.gitignore
.docs.json
.travis.yml
.github/
requirements.txt
node_modules/
CHANGELOG.md
*.jpg
assets/
docs/
guide/
site/
tutorial-env/
.eslintrc
inch.json
mkdocs.yml
.npmrc
.nyc_output
env/
test/
examples/
venv/
icon.png


================================================
FILE: .travis.yml
================================================
language: bash

node_js:
  - "14.18.3"

python:
  - "3.9"

jobs:
  include:
    - language: node_js
      node_js: 14.18.3
      script:
        - npm install
        - npm run test:travis || travis_terminate 1

    - language: python
      python: "3.9"
      script:
        - pip install -r requirements.txt
        - mkdocs build
      deploy:
        - provider: pages
          skip_cleanup: true
          github_token: $GITHUB_TOKEN
          keep_history: true
          local_dir: site
          on:
            branch: main


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019-2021 Mobile First LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Extension CLI

[![npm](https://img.shields.io/npm/v/extension-cli?style=flat-square)](https://www.npmjs.com/package/extension-cli)
[![travis](https://img.shields.io/travis/mobilefirstllc/extension-cli?style=flat-square)](https://travis-ci.com/github/MobileFirstLLC/extension-cli)
[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/MobileFirstLLC/extension-cli?style=flat-square)](https://codeclimate.com/github/MobileFirstLLC/extension-cli/maintainability)
[![Last commit](https://img.shields.io/github/last-commit/mobilefirstllc/extension-cli?style=flat-square)](https://github.com/MobileFirstLLC/extension-cli/commits/master)
[![npm](https://img.shields.io/npm/dt/extension-cli?style=flat-square)](https://www.npmjs.com/package/extension-cli)
<!-- [![Coveralls github](https://img.shields.io/coveralls/github/MobileFirstLLC/extension-cli?style=flat-square)](https://coveralls.io/github/MobileFirstLLC/extension-cli) hide coverage because it represents only utilities and may signal incorrectly; add back when this includes all of CLI commands -->

**Extension CLI is a command-line application that facilitates chromium&#8727;-based web extension development by providing
a systematic way to build, test and document extension projects. It handles the project setup and builds and lets you focus 
on the extension you are creating.**

* * *

## Features

-  🖥️ &nbsp; **Javascript Bundling** — Compiles, bundles and minifies javascript files<br/>

-  🎨 &nbsp; **CSS Bundling** — Compiles, bundles, and minifies CSS and [SASS](https://sass-lang.com/guide) files <br/>

-  💄 &nbsp; **Linting** — lint JavaScript using [ESLint](https://eslint.org/) <br/>

-  📦 &nbsp; **ZIP Generation** — Generates a `.zip` file for publishing <br/>

-  📝 &nbsp; **Document Source Code** — Generates code documentation using [JSDoc](https://jsdoc.app/about-getting-started.html) <br/>

-  ⚗️ &nbsp; **Unit Testing** — Provides a unit test environment preloaded with [mocha](https://mochajs.org), [chai](https://www.chaijs.com/) and [sinon-chrome](https://github.com/acvetkov/sinon-chrome) <br/>

-  ⚔️ &nbsp; **Cross-Browser Compatibility** - develop extensions for Chrome, Edge, Firefox, Opera and Brave. <br/>

![feature image](https://raw.githubusercontent.com/MobileFirstLLC/extension-cli/master/.github/feature.png)

## Getting Started

**Note:** Using this CLI assumes you have Node.js installed. If you do not, you can [install it here](https://nodejs.org/en/download/).

##### Create new extension project

```text
npx extension-cli
```

##### Add to an existing project

```text
npm install extension-cli
```

### Commands Reference

Command | Description
--- | ---
**xt-build** | Run builds; env flags: `-e prod` and `-e dev`
**xt-test**| Run unit tests
**xt-docs**| Generate docs
**xt-sync**| Update project config files to match the latest defaults supplied by this CLI
**xt-clean** | Remove automatically generated files

* * *

## Read the Docs

<img align="left" width="64" src="https://raw.githubusercontent.com/MobileFirstLLC/extension-cli/master/guide/assets/images/guide.svg" alt="" /> &nbsp; 
<br/>&nbsp; &nbsp;<strong><a href="https://oss.mobilefirst.me/extension-cli/">User Guide →</a></strong><br/><br/>

### CLI Developer Guide

If you are interested in extending this project or forking **[see this guide &rarr;](https://oss.mobilefirst.me/extension-cli/13-cli-development/)**

* * *

## Motivation

After developing multiple browser extensions, it became clear that there are several steps in the development process that stay the same between every project. 

Instead of setting up these tasks individually for each project, it made more sense to combine everything in a utility tool that could be shared between projects. 

This approach helps with creating a common, consistent development approach between multiple projects, reduces time to get started, and makes it easier to update build tools and scripts across multiple projects as many npm packages inevitably need to be updated (frequently!).

* * *

**Issues & Feature Requests:** [Submit on Github](https://github.com/MobileFirstLLC/extension-cli/issues/new/choose)

**Maker:** made by <a href="https://github.com/MobileFirstLLC/extension-cli/graphs/contributors" target="_blank" rel="noreferrer noopener">developers</a> behind several popular extensions!

**License:** [MIT](https://github.com/MobileFirstLLC/extension-cli/blob/master/LICENSE)


================================================
FILE: cli/gulpfile.js
================================================
const gulp = require('gulp');
const del = require('del');
const chalk = require('chalk');
const gulpChange = require('gulp-change');
const paths = require('../config/build.json');
const plugins = require('gulp-load-plugins')();
const webpack = require('webpack-stream');
const sass = require('gulp-sass')(require('sass'));
const Utilities = require('./utilities').Utilities;
const argv = require('yargs').argv;
const {prod: isProd, firefox: isFirefox, pkg: pkgPath, config} = argv;

/** helper method to ensure array type */
const ensureArray = path => Array.isArray(path) ? path : [path];

/** read project package.json **/
const pkg = Utilities.readJSON(pkgPath);

/** read project's config file, if specified **/
let customPaths = null;

if (Utilities.fileExists(config)) {
    // if config is a file
    customPaths = Utilities.readJSON(config);
} else if (pkg.xtbuild !== undefined) {
    // otherwise config should be specified in package.json
    customPaths = pkg.xtbuild;
}

/** replace default configs with project-level configs **/
if (customPaths) {
    for (let key in customPaths) {
        if (customPaths.hasOwnProperty(key)) {
            paths[key] = customPaths[key];
        }
    }
}

const clean = () => del([paths.dist + '/*']);

const script = ({src, name, mode}, done = _ => true) => {

    const webpackOptions = {
        // use mode if specified explicitly; otherwise choose by --env
        mode: mode || (isProd ? 'production' : 'development'),
        // match sourcemap name with configured js file name
        output: {filename: `${name}.js`},
        // use source map with dev builds only
        devtool: isProd ? undefined : 'cheap-source-map'
    };

    return gulp.src(src)
        .pipe(webpack(webpackOptions))
        .on('error', (err) => {
            console.log(err.toString());
            this.emit('end');
        })
        .pipe(plugins.rename(path => {
            path.dirname = '';
            path.basename = name;
        }))
        .pipe(gulp.dest(paths.dist))
        .on('end', done);
};

const style = ({src, name}, done = _ => true) => {
    return gulp.src(src)
        // convert to css
        .pipe(sass().on('error', sass.logError))
        // concatenate multiple src files
        .pipe(plugins.concat(`${name}.css`))
        // minify
        .pipe(plugins.cleanCss())
        // rename to user-specified name
        .pipe(plugins.rename((path) => {
            path.dirname = '';
            path.basename = name;
        }))
        .pipe(gulp.dest(paths.dist))
        .on('end', done);
};

const copy = (src, done = _ => true) => {
    // nested copy specified using glob pattern
    if (src.endsWith('*')) {
        return gulp.src(src, {base: 'src'})
            .pipe(gulp.dest(paths.dist))
            .on('end', done);
    }

    // copy single file or directory
    return gulp.src(src)
        .pipe(plugins.rename(path => {
            path.dirname = '';
        }))
        .pipe(gulp.dest(paths.dist))
        .on('end', done);
};

const locale = (language, done = _ => true) => {
    return gulp.src(paths.locales_dir + language + '/**/*.json')
        .pipe(plugins.mergeJson({fileName: 'messages.json'}))
        .pipe(plugins.jsonminify())
        .pipe(gulp.dest(paths.dist + '/_locales/' + language))
        .on('end', done);
};

const copyManifest = done => {

    const {version} = pkg;

    const performChange = (content) => {
        let mft = JSON.parse(content);

        mft.version = version; // use version from package

        if (isFirefox && mft.firefox) mft = {...mft, ...mft.firefox};
        else if (!isFirefox && mft.chrome) mft = {...mft, ...mft.chrome};
        delete mft.chrome;
        delete mft.firefox;

        return JSON.stringify(mft);
    };

    return gulp.src(paths.manifest)
        .pipe(gulpChange(performChange))
        .pipe(plugins.jsonminify())
        .pipe(plugins.rename(path => {
            path.dirname = '';
            path.basename = 'manifest';
            path.extname = '.json';
        }))
        .pipe(gulp.dest(paths.dist))
        .on('end', done);
};

const copyAssets = done => {
    return gulp.src(paths.assets)
        .pipe(gulp.dest(paths.dist + '/assets'))
        .on('end', done);
};

const buildHtml = done => {
    return gulp.src(paths.html)
        .pipe(plugins.htmlmin({collapseWhitespace: true}))
        .pipe(plugins.rename(path => {
            path.dirname = '';
        }))
        .pipe(gulp.dest(paths.dist))
        .on('end', done);
};

const customCommands = done => {
    if (!paths.commands || !paths.commands.length) {
        return done();
    }

    return require('child_process')
        .exec(paths.commands, done);
};

const release = done => {
    if (!isProd) return done();

    return gulp.src(paths.dist + '/**/*')
        .pipe(plugins.zip(`${paths.release_name || 'release'}.zip`))
        .pipe(gulp.dest(paths.releases))
        .on('end', done);
};

const dynamicFunc = (action, name) => {
    const f = action;

    Object.defineProperty(f, 'name', {
        value: name,
        writable: false
    });
    return f;
};

const scripts = paths.js_bundles.map(obj =>
    dynamicFunc(_ => script(obj), `${obj.name}.js`));

const styles = paths.scss_bundles.map(obj =>
    dynamicFunc(_ => style(obj), `${obj.name}.css`));

const locales = paths.locales_list.map(lang =>
    dynamicFunc(_ => locale(lang), `locale ${lang}`));

const copies = ensureArray(paths.copyAsIs).map(obj =>
    dynamicFunc(_ => copy(obj), `copy ${obj}`));

const watch = () => {
    console.log(chalk.bold.yellow('watching...'));
    if (scripts.length) {
        gulp.watch(ensureArray(paths.js), gulp.parallel(...scripts));
    }
    if (styles.length) {
        gulp.watch(ensureArray(paths.scss), gulp.parallel(...styles));
    }
    if (copies.length) {
        gulp.watch(ensureArray(paths.copyAsIs), gulp.parallel(...copies));
    }
    if (paths.locales_list.length) {
        gulp.watch(paths.locales_dir + '**/*.json', gulp.parallel(...locales));
    }
    gulp.watch(paths.manifest, copyManifest);
    gulp.watch(ensureArray(paths.html), buildHtml);
    gulp.watch(ensureArray(paths.assets), copyAssets);
    // gulp.watch(paths.commands_watch_path || '', customCommands);
};

const build = gulp.series(
    clean,
    gulp.parallel(
        ...scripts,
        ...styles,
        ...copies,
        ...locales,
        copyManifest,
        copyAssets,
        buildHtml
    ),
    customCommands,
    release
);

/*
 * Define default task that can be called by just running `gulp` from cli
 */
exports.default = build;

/*
 * If watch flag is defined, run build and keep watching
 */
exports.watch = gulp.series(build, watch);


================================================
FILE: cli/rootsuite.js
================================================
/**
 * @description
 * This rootsuite sets up unit testing environment
 */

const sinon = require('sinon');
const chrome = require('sinon-chrome');
const chai = require('chai');
const argv = require('yargs').argv;
const texts = require('./texts').xtTest;
const enableWatch = argv.watch;

/**
 * Create sinon sandbox
 *
 * Sandboxes removes the need to keep track of
 * every fake created, which greatly simplifies cleanup.
 *
 * @see {@link https://sinonjs.org/releases/latest/sandbox/}
 */
const sandbox = sinon.createSandbox();

/**
 * Setup global DOM
 */
global.jsdom = require('jsdom-global')();

/**
 * Before running any tests -
 * setup the test environment
 */
before(function () {
    process.env.NODE_ENV = 'test';
    global.sinon = sinon;
    global.chai = chai;
    global.expect = chai.expect;
    global.sandbox = sandbox;
    window.sandbox = sandbox;
    global.chrome = chrome;
    window.chrome = chrome;

    // output list of namespaces that
    // are available in test environment
    console.log(texts.onRootSetup(
        'window,chrome,chai,expect,sandbox(sinon)'
            .split(',')));
});

/**
 * After each test -
 * reset chrome and sandbox
 */
afterEach(function () {
    chrome.flush();
    sandbox.restore();
});

/**
 * After all tests -
 * Clean up everything that was initially set up
 */
after(function () {
    // important! do not clean when running in watch mode
    if (enableWatch) return;

    delete global.jsdom;
    delete global.sinon;
    delete global.chrome;
    delete global.chai;
    delete global.expect;
    delete global.sandbox;
    delete window.sandbox;
    delete window.chrome;
    delete global.mouseEvent;
    delete global.dispatchEvent;
});

/**
 * Enable mouse events globally during unit testing
 * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent}
 *
 * @param {String} type - event type
 * @param {Object} props - optional properties
 *  @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent}
 * @return {Event} - the event
 */
global.mouseEvent = function (type, props) {
    return new MouseEvent(type, {...props});
};

/**
 * Enable dispatching an event on some DOM element during unit testing
 *
 * @param {EventTarget} target - element on which to dispatch event
 * @param {Event} event - the event to dispatch
 * @return {Event} - the event
 */
global.dispatchEvent = function (target, event) {
    if (target.dispatchEvent) {
        target.dispatchEvent(event);
    } else if (target.fireEvent) {
        target.fireEvent('on' + event.type, event);
    }
    return event;
};


================================================
FILE: cli/texts.js
================================================
/**
 * @description
 * This module specifies terminal/console output for all commands
 */

const chalk = require('chalk');

/**
 * xt-create outputs
 */
exports.xtCreate = {

    prompts: {
        name: {
            type: 'text',
            name: 'name',
            message: 'What do you want to call the extension?',
            validate: value =>
                // basic null check is sufficient
                !value || value.trim().length < 1 ?
                    'You must choose a name' : true
        },
        optional: [
            {
                type: 'text',
                name: 'description',
                message: 'What does it do?'
            }, {
                type: 'text',
                name: 'homepage',
                message: 'Homepage URL (leave blank if you do not have one yet)'
            }
        ]
    },

    dirError: (dirname) => (
        chalk.bold.red(`Cannot create directory: ${dirname}.\n` +
            'It already exists, is not empty, or is not writable.')
    ),

    start: (dirname, name) => (
        `Creating extension ${name} in directory ${chalk.bold.green(dirname)}.`
    ),

    install: 'Installing packages - this may take a while...',

    installError: (
        chalk.bold.yellow('ATTN! ') +
        'npm install did not complete successfully\n' +
        'You may have to run npm install again in project directory.'
    ),

    success: (dir) => (
        `${chalk.bold.green('DONE! ')}Your extension starter is ready.\n` +
        `${chalk.bold.green('What Next: ')} Open ${dir} in your favorite web IDE`
    )

};

/**
 * xt-sync outputs
 */
exports.xtSync = {

    argGitlab: 'Gitlab CI config',

    argTravis: 'Travis CI config',

    argLint: 'eslint config',

    argGitIgnore: 'gitignore',

    argActions: 'Github actions workflow config',

    instructions: 'choose the files you want to sync:',

    updateSuccess: (what) => chalk.bold.green(`✓ updated ${what}`)
};

/**
 * xt-docs outputs
 */
exports.xtDocs = {

    argWatch: 'enable watch',

    watching: 'watching...',

    success: chalk.bold.green('Docs done!'),

    failure: chalk.bold.red('Docs failed'),

    configArg: 'Path to config file; defaults to `.xtdocs.json` in project root, or `xtdocs` in package.json'
};

/**
 * xt-clean outputs
 */
exports.xtClean = {

    argModules: 'Clean node_modules directory',

    argIdea: 'Clean .idea/ directory',

    argVS: 'Clean .vscode/ directory',

    onConfigError: (path) => chalk.yellow(`File does not exist: ${path}`),

    onCleanFile: (path) => `- ${path}`,

    onCleanError: (e, line) => chalk.bold.red(e) + ' ' + line,

    result: count => chalk.bold[count === 0 ? 'yellow' : 'green'](`Done. Cleaned: ${count}`)
};

/**
 * xt-test outputs
 */
exports.xtTest = {

    argPattern: 'test file/directory match pattern',

    argCoverage: 'deprecated! see docs on how to report coverage: https://bit.ly/3j5Zrn2',

    argWatch: 'enable watch',

    onRootSetup: (envList) => (
        ['ENV: ',
            envList.map(entry => chalk.bold.green(` ${entry} `))
        ].join(' ') + '\n'
    )
};

/**
 * xt-build outputs
 */
exports.xtBuild = {

    envArg: 'Build environment',

    watchArg: 'Enable watch',

    platformArg: 'Platform',

    configFileArg: 'Path to configuration file, default: .xtbuild.json in root, or xtbuild in package.json)',

    onBuildSuccess: _ => chalk.bold.green('Build done!'),

    onBuildError: _ => chalk.bold.red('Build failed')
};


================================================
FILE: cli/utilities.js
================================================
const fs = require('fs');
const path = require('path');

/**
 * @class
 * @classdesc Utility class provides helper methods
 * for performing commonly recurring operations such
 * as IO; and reading, writing, merging objects.
 *
 * When performing these operations, it is preferable
 * to use these utility methods to ensure same behavior
 * for these operations everywhere, and to establish one
 * place of change, should these operations change in the
 * future.
 */
class Utilities {

    /**
     * Given some string value, generate another string
     * from it, such that the generated string can be
     * used as a directory name. This function will
     * normalize the input and remove special characters.
     *
     * @param name - suggested directory name
     * @param defaultName - value to return if
     *      no characters in name can be used
     * @return {string} directory name
     */
    generateDirectoryName(name, defaultName = 'extension-1') {
        return ((name || '').toLowerCase()
            .replace(/[\W_]+/g, ' ')
            .replace(/ /g, '-')
            .replace(/-$/, '')) || defaultName;
    };

    /**
     * Replace string interpolation expressions in
     * a content string.
     *
     * @param content - string with placeholder values,
     *  @example "sample ${key}"
     * @param vars - dictionary of <K,V> pairs
     *  @example { key : "value" }
     * @return {string} -
     *  @example "sample value"
     */
    replaceVars(content, vars) {
        let temp = content.toString();

        Object.keys(vars).map(key => {
            const re = new RegExp('\\${' + key + '}', 'gi');

            temp = temp.replace(re, vars[key]);
            return true;
        });
        return temp;
    };

    /**
     * a union of two objects, child and parent,
     * with child values overriding all shared keys.
     *
     * This operation happens in place and
     * result will be stored in parent object.
     *
     * @example
     * let child = {a:1, b:5, c:{x:1}}
     * let parent = {b:8, c:{y:9}}

     * // expected result (parent):
     * // {a:1, b:5, c:{x:1, y:9}}
     *
     * @param child - source object
     * @param parent - parent object
     */
    keyReplace(child, parent) {
        for (let key in child) {
            if (!child.hasOwnProperty(key)) continue;
            if (Array.isArray(child[key])) {
                parent[key] = child[key];
                continue;
            }
            if (typeof child[key] !== 'object') {
                parent[key] = child[key];
                continue;
            }
            if (!parent[key]) parent[key] = {};
            this.keyReplace(child[key], parent[key]);
        }
    }

    /**
     * Given defaultConfig and project-level config
     * replace default configs with project-specific
     * configuration.
     *
     * Any property that is specified at project level
     * but not in default config, will be added to
     * the result configuration.
     *
     * Any property that exists in default config that
     * is not overwritten at project level, will hold
     * default value in the result configuration.
     *
     * @param defaultConfig
     * @param projectConfig
     * @return {Object}
     */
    iterateConfigs(defaultConfig, projectConfig) {
        if (!projectConfig) return defaultConfig;
        let temp = Object.assign({}, defaultConfig);

        for (let k in projectConfig) {
            if (!projectConfig.hasOwnProperty(k)) continue;
            if (typeof projectConfig[k] === 'object') {
                if (!temp[k]) temp[k] = {};
                this.keyReplace(projectConfig[k], temp[k]);
            } else {
                temp[k] = projectConfig[k];
            }
        }
        return temp;
    }

    /**
     * Recursively copy a directory and all its files to a new location
     * @param from - path to current location
     * @param to - target location path
     */
    copyFolderSync(from, to) {
        try {
            fs.mkdirSync(to);
        } catch (e) {
        }
        fs.readdirSync(from).forEach((element) => {
            const stat = fs.lstatSync(path.join(from, element));

            if (stat.isFile()) {
                fs.copyFileSync(path.join(from, element), path.join(to, element));
            } else if (stat.isSymbolicLink()) {
                fs.symlinkSync(fs.readlinkSync(path.join(from, element)), path.join(to, element));
            } else if (stat.isDirectory()) {
                this.copyFolderSync(path.join(from, element), path.join(to, element));
            }
        });
    }

    /**
     * Copy single file from one location to another (synchronous).
     *
     * @param from - source file path
     * @param to - target file path
     */
    copyFile(from, to) {
        fs.createReadStream(from).pipe(fs.createWriteStream(to));
    }

    /**
     * Read utf-8 encoded file (synchronous)
     * @param filePath - path to file
     * @return {string} - file contents
     */
    readFile(filePath) {
        return fs.readFileSync(filePath, 'utf8');
    }

    /**
     * Write file to disk (syncronous)
     * @param filePath - path to file
     * @param content - file contents
     */
    writeFile(filePath, content) {
        fs.writeFileSync(filePath, content);
    }

    /**
     * Check if file exists
     * @param filePath - path to file
     * @return {boolean} - true/false
     */
    fileExists(filePath) {
        return fs.existsSync(filePath);
    }

    /**
     * Create empty directory.
     *
     * @param dirPath - path to directory
     * @return {boolean} - true if exists and empty (should be
     *   writable) and false otherwise
     */
    createDir(dirPath) {
        // doesn't exist
        if (!fs.existsSync(dirPath)) {
            fs.mkdirSync(dirPath, { recursive: true });
            return true;
        }
        // check if empty
        return !fs.readdirSync(dirPath).length;
    };

    /**
     * Read JSON file
     * @param filePath - path to file
     * @return {any} - Object
     */
    readJSON(filePath) {
        return JSON.parse(this.readFile(filePath));
    }

    /**
     * Reads text file then replaces all variable placeholders, e.g. ${var1}
     * @param path - path to file
     * @param vars - variables Object <K, V>
     * @return {string} - file contents with all matched variables replaced
     */
    readAndReplaceTextFile(path, vars) {
        return this.replaceVars(this.readFile(path), vars);
    }

    /**
     * Reads JSON file then replaces all variable placeholders, e.g. ${var1}
     * @param path - path to file
     * @param vars - variables Object <K, V>
     * @return {string} - file contents with all matched variables replaced
     */
    readAndReplaceJSONFile(path, vars) {
        return JSON.stringify(JSON.parse(this.readAndReplaceTextFile(path, vars)), null, 4);
    }
}

exports.Utilities = new Utilities();


================================================
FILE: cli/xt-build.js
================================================
#!/usr/bin/env node

/**
 * @name xt-build
 * @module
 * @public
 *
 * @description
 *
 * ```text
 * xt-build --env {prod|dev} --platform {chrome|firefox} [--config filename] [--watch]
 * ```
 *
 * Build command generates a dist/ directory that can be
 * debugged in the browser. When called with production env flag, `-e prod`,
 * this command will minify and compile a `release.zip` file that can be
 * uploaded to extension marketplace for distribution.
 */

const util = require('util');
const path = require('path');
const program = require('commander');
const Spinner = require('cli-spinner').Spinner;
const exec = require('child_process').exec;
const pkg = require('../package.json');
const env = {prod: 'prod', dev: 'dev'};
const platform = {chrome: 'chrome', firefox: 'firefox'};
const texts = require('./texts').xtBuild;
const gulpfile = path.resolve(__dirname, './gulpfile.js');

program
    .version(pkg.version)
    .option('-e --env <env>', texts.envArg, /^(dev|prod)$/i, env.prod)
    .option('-p --platform <platform>', texts.platformArg, /^(chrome|firefox)$/i, platform.chrome)
    .option('-c --config <config>', texts.configFileArg, /^(.*)$/i)
    .option('-w --watch', texts.watchArg)
    .parse(process.argv);

const {watch, env: programEnv, config, platform: platformEnv} = program.opts();
const spinner = new Spinner(' %s ');

spinner.start();

const proc = exec([

    // run either watch or default
    watch ? 'gulp watch' : 'gulp',

    // path to gulpfile (in current dir)
    util.format('--gulpfile "%s"', gulpfile),

    // path to build configuration file
    util.format('--config "%s"', path.resolve(process.cwd(), config || './.xtbuild.json')),

    // path to project's package.json
    util.format('--pkg', path.resolve(process.cwd(), './package.json')),

    // explicitly tell gulp to use cwd (necessary)
    util.format('--cwd', path.resolve(process.cwd())),

    // ENV is either "--dev" or "--prod"
    util.format('--%s', programEnv),

    // target platform is "--chrome" or "--firefox"
    util.format('--%s', platformEnv),

    // use colors in terminal output
    '--colors'

].join(' '));

proc.stdout.on('data', (data) => {
    if (data && data.indexOf('Using gulpfile') === 0) return;
    spinner.stop(true);
    process.stdout.write(data.toString());
});

proc.stderr.on('data', (data) => {
    spinner.stop(true);
    process.stdout.write(data.toString());
});

proc.on('exit', (err) => {
    spinner.stop(true);
    console.log(!err ?
        texts.onBuildSuccess() :
        texts.onBuildError());
});


================================================
FILE: cli/xt-clean.js
================================================
#!/usr/bin/env node

/**
 * @name xt-clean
 * @module
 * @public
 *
 * @description
 *
 *```text
 * xt-clean [--modules] [--idea] [--vscode]
 * ```
 *
 * 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`. To remove these
 * directories, you must explicitly pass a flag to delete each one of them.
 */

const fs = require('fs');
const del = require('del');
const path = require('path');
const readline = require('readline');
const program = require('commander');
const pkg = require('../package.json');
const ignore = path.join(process.cwd(), '.gitignore');
const Utilities = require('./utilities').Utilities;
const texts = require('./texts').xtClean;

let counter = 0;

program
    .version(pkg.version)
    .option('-m --modules', texts.argModules)
    .option('-i --idea', texts.argIdea)
    .option('-v --vscode', texts.argVS)
    .parse(process.argv);

if (!Utilities.fileExists(ignore)) {
    console.log(texts.onConfigError(ignore));
    process.exit(0);
}

const {modules, idea, vscode} = program.opts();

readline.createInterface({input: fs.createReadStream(ignore)})
    .on('line', function (line) {

        // never clean these
        if (line.trim().indexOf('#') === 0 ||
            line.trim().indexOf('.env') === 0 ||
            !(line || '').trim().length) {
            return false;
        }

        // clean these only if flagged
        if ((line.indexOf('.idea') > -1 && !idea) ||
            (line.indexOf('.vscode') > -1 && !vscode) ||
            (line.indexOf('node_modules') > -1 && !modules)) {
            return false;
        }

        // otherwise clean if exists
        const basePath = path.join(process.cwd(), line);

        if (fs.existsSync(basePath)) {
            try {
                if (fs.lstatSync(basePath).isDirectory()) {
                    del.sync(path.join(basePath, '/*'));
                }
                if (fs.existsSync(basePath)) {
                    del.sync(basePath);
                }
                console.log(texts.onCleanFile(line));
                counter++;
            } catch (e) {
                console.log(texts.onCleanError(e, line));
            }
        }
        return true;
    })
    .on('close', () => {
        console.log(texts.result(counter));
        process.exit(0);
    });


================================================
FILE: cli/xt-create.js
================================================
#!/usr/bin/env node

/**
 * @name extension-cli
 * @module
 * @public
 *
 * @description
 *
 *```text
 * npx extension-cli
 * ```
 *
 * This command will create a new extension project and initial code files.
 * Command takes no arguments; follow prompts on screen.
 */

const prompts = require('prompts');
const path = require('path');
const exec = require('child_process').exec;
const Spinner = require('cli-spinner').Spinner;
const spinner = new Spinner(' %s ');
const Utilities = require('./utilities').Utilities;
const texts = require('./texts').xtCreate;
const createPrompts = texts.prompts;
const defaultHomepage = 'http://chrome.google.com/webstore';
const initFilesPath = '../config/init/';

/**
 * Run the setup script
 * @private
 */
(async () => {

    const promptOptions = {onCancel: () => process.exit(0)};
    const response = await prompts(createPrompts.name, promptOptions);
    const name = response.name;
    const dirname = Utilities.generateDirectoryName(name);
    const dir = path.join(process.cwd(), `/${dirname}`);

    // create project directory
    const success = Utilities.createDir(dir);

    if (!success) {
        console.error(texts.dirError(dirname));
        return process.exit(0);
    }

    const {description, homepage} = await prompts(createPrompts.optional, promptOptions);
    const vars = {
        name, description, safeName: dirname,
        version: '0.0.1', homepage: homepage || defaultHomepage
    };
    const _file = fileName => path.resolve(__dirname, initFilesPath + fileName);
    const _readtext = path => Utilities.readAndReplaceTextFile(path, vars);
    const _readjson = path => Utilities.readAndReplaceJSONFile(path, vars);

    console.log(texts.start(dirname, name));
    spinner.start();

    // SETUP files structure and starter files
    // initialize extension image assets
    Utilities.createDir(dir + '/assets');
    Utilities.createDir(dir + '/assets/img');
    Utilities.copyFile(_file('icon.svg'), dir + '/assets/icon.svg');
    Utilities.copyFile(_file('16x16.png'), dir + '/assets/img/16x16.png');
    Utilities.copyFile(_file('24x24.png'), dir + '/assets/img/24x24.png');
    Utilities.copyFile(_file('32x32.png'), dir + '/assets/img/32x32.png');
    Utilities.copyFile(_file('128x128.png'), dir + '/assets/img/128x128.png');

    // setup locales
    Utilities.createDir(dir + '/assets/locales');
    Utilities.createDir(dir + '/assets/locales/en');
    Utilities.writeFile(dir + '/assets/locales/en/messages.json', _readjson(_file('messages.json')));

    // setup source code
    Utilities.createDir(dir + '/src');
    Utilities.writeFile(dir + '/src/manifest.json', _readjson(_file('manifest.json')));
    Utilities.copyFile(_file('background.js'), dir + '/src/index.js');

    // setup test files
    Utilities.createDir(dir + '/test');
    Utilities.copyFile(_file('test.js'), dir + '/test/sample.js');

    // create package.json
    Utilities.writeFile(dir + '/package.json', _readjson(_file('package.json')));

    // create readme
    Utilities.writeFile(dir + '/README.md', _readtext(_file('intro.md')));

    // add eslint config
    Utilities.writeFile(dir + '/.eslintrc.json', _readtext(_file('eslint.json')));

    // INSTALL packages
    spinner.stop(true);
    console.log(texts.install);
    spinner.start();

    exec('npm install', {cwd: dir})
        .on('exit', code => {
            spinner.stop(true);
            if (code !== 0) {
                console.log(texts.installError);
            }
            console.log(texts.success(dir));
            process.exit(0);
        });

    // this is just to make eslint happy
    return '';
})();


================================================
FILE: cli/xt-docs.js
================================================
#!/usr/bin/env node

/**
 * @name xt-docs
 * @module
 * @public
 *
 * @description
 *
 * ```text
 * xt-docs [--config filename] [--watch]
 * ```
 *
 * Docs command generates documentation for the project. This command uses
 * jsdocs syntax. See {@link https://jsdoc.app/index.html|About JSDoc} for more details,
 * including {@link https://jsdoc.app/about-configuring-jsdoc.html|configuration options here}.
 * The default template for the guide is JsDoc default template. You can override this template
 * in the project by changing `opts.template` in jsdoc config file.
 *
 * By default, this command will automatically look for configuration in the project `package.json`.
 * - use `"xtdocs"` key to define config options in `package.json,
 * - -or- add a separate configuration file `.xtdocs.json` in the project root,
 * - -or- explicitly provide a path to a config file.
 *
 * Use `-c` / `--config` flag to provide path and name of the configuration file.
 */

const fs = require('fs');
const del = require('del');
const util = require('util');
const path = require('path');
const program = require('commander');
const pkg = require('../package.json');
const exec = require('child_process').exec;
const Spinner = require('cli-spinner').Spinner;
const Utilities = require('./utilities').Utilities;
const texts = require('./texts').xtDocs;
const defaultConfig = require('../config/docs.json');
const spinner = new Spinner(' %s ');
const jsdoc = './node_modules/.bin/jsdoc';
const tmpFile = path.join(process.cwd(),
    './node_modules', pkg.name, 'tmpDocsConfig.json');

program
    .version(pkg.version)
    .option('-c --config <config>', texts.configArg, /^(.*)$/i)
    .option('-w --watch', texts.argWatch)
    .parse(process.argv);

const {config: configArg, watch} = program.opts();

const getConfig = (docFileName) => {
    const fe = Utilities.fileExists(docFileName);
    const temp = Utilities.readJSON(fe ?
        docFileName : './package.json');

    return Utilities.iterateConfigs(defaultConfig,
        fe ? temp : temp.xtdocs);
};

const buildDocs = (tmpFile, config, callback) => {
    spinner.start();
    Utilities.writeFile(tmpFile, config);

    const proc = exec(util.format('"%s" -c %s', jsdoc, tmpFile));

    proc.stdout.on('data', (data) => {
        process.stdout.write(data.toString());
    });
    proc.stderr.on('data', (data) => {
        process.stderr.write(data.toString());
    });
    proc.on('exit', err => {
        del.sync(tmpFile);
        spinner.stop(true);
        console.log(err ? texts.failure : texts.success);
        if (callback) callback();
    });
};

const startWatch = (tmpFile, watchPaths, configStr) => {
    watchPaths.map(fileOrDir => {
        fs.watch(path.join(process.cwd(), fileOrDir), {
                persistent: true, recursive: true
            },
            (curr, prev) => {
                // if spinning it is already running
                if (!spinner.isSpinning()) {
                    buildDocs(tmpFile, configStr, false);
                }
            });
    });
    console.log(texts.watching);
};

const config = getConfig(configArg || '.xtdocs.json');
const configString = JSON.stringify(config);
const watchPaths = config.source.include.concat(
    config.opts.tutorials ? [config.opts.tutorials] : []);

buildDocs(tmpFile, configString, _ => watch ?
    startWatch(tmpFile, watchPaths, configString) :
    process.exit(0));


================================================
FILE: cli/xt-sync.js
================================================
#!/usr/bin/env node

/**
 * @name xt-sync
 * @module
 * @public
 *
 * @description
 *
 * ```text
 * xt-sync
 * ```
 *
 * The purpose of this command is to upgrade configuration files of
 * a stale project to latest version, where this CLI tool will provide
 * updated project configuration files. If the config files have been
 * modified heavily for the project, it is not advisable to upgrade them
 * in this manner. Instead you should upgrade such configs manually.
 */

const path = require('path');
const prompts = require('prompts');
const program = require('commander');
const pkg = require('../package.json');
const texts = require('./texts').xtSync;
const Utilities = require('./utilities').Utilities;

// list available options
const files = {
    actions: {title: texts.argActions, path: '../config/actions.yml', out: 'build.yml', dir: '.github/workflows'},
    gitlab: {title: texts.argGitlab, path: '../config/gitlab.yml', out: '.gitlab-ci.yml'},
    travis: {title: texts.argTravis, path: '../config/travis.yml', out: '.travis.yml'},
    eslint: {title: texts.argLint, path: '../config/init/eslint.json', out: '.eslintrc.json'},
    gitignore: {title: texts.gitignore, path: '../config/ignore', out: '.gitignore'}
};

// generate the options to display to user
const options = [{
    type: 'multiselect',
    name: 'options',
    message: texts.instructions,
    choices: Object.entries(files).map(
        ([key, {title}]) => ({title, value: key}))
}];

program
    .name('xt-sync')
    .option('-a --all', 'deprecated: call xt-sync without flags')
    .version(pkg.version)
    .parse(process.argv);

(async () => {

    const onCancel = () => process.exit(0);
    // noinspection JSUnresolvedVariable
    const response = (await prompts(options, {onCancel})).options;

    // copy selected options from config -> project
    Object.entries(files).map(([key, value]) => {

        if (response.indexOf(key) > -1) {
            const relativePath = path.resolve(__dirname, value.path);
            const content = Utilities.readFile(relativePath);
            const outPath = path.join(process.cwd(),
                (value.dir ? path.join(value.dir, value.out) : value.out));

            if (value.dir) Utilities.createDir(path.join(process.cwd(), value.dir));
            Utilities.writeFile(outPath, content);
            console.log(texts.updateSuccess(value.out));
        }
    });
})();


================================================
FILE: cli/xt-test.js
================================================
#!/usr/bin/env node

/**
 * @name xt-test
 * @module
 * @public
 *
 * @description
 *
 * ```text
 * xt-test [--pattern] [--coverage] [--watch]
 * ```
 *
 * This command will run project unit tests located in `./test` directory.
 *
 * Command sets up extension unit testing environment with ES6 syntax support 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 APIs are also initialized: `window`, `document`, `chrome`.
 * 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).
 *
 * You may extend this test environment within a single project. This is simply the base setup
 * for running unit tests. Or create your own testing environment at project level if this is
 * not suitable.
 */

const util = require('util');
const path = require('path');
const program = require('commander');
const pkg = require('../package.json');
const exec = require('child_process').exec;
const texts = require('./texts').xtTest;

process.chdir(process.cwd());

program
    .version(pkg.version)
    .option('-p --pattern <string>', 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 &rarr;](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 
 &nbsp; **└── NNxNN.png** | extension icons
 &nbsp; **└── background.js** | background script starter
 &nbsp; **└── icon.svg** | vector icon
 &nbsp; **└── intro.md** | new extension readme
 &nbsp; **└── manifest.json** | manifest template
 &nbsp; **└── messages.json** | message dictionary template
 &nbsp; **└── package.json** | package.json template
 &nbsp; **└── 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 &mdash; 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
&nbsp; &nbsp; └─ img | Extension icons
&nbsp; &nbsp; └─ locales | Localized string resources
&nbsp; &nbsp; &nbsp; &nbsp; └─ en/messages.json | English dictionary
└ **src** | Source code: put js, scss, html, json files here
&nbsp; &nbsp; └─ 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

* * *

<p class='page-intro'>Specify how static assets will be handled during builds.</p>

* * *

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

* * *

<p class='page-intro'>Custom commands enables running any custom actions after build and before generating a release.</p>

* * *

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.

<!--
## Watching changes

For `dev` builds, you can specify a watch pattern, such that changes matching the 
pattern will re-run custom commands.

Specify watch path using `commands_watch_path` configuration key, for example:

```json
"xtbuild": {
  "commands_watch_path": "./src"
}
```

then run build in `dev` mode with `--watch` flag. 

Any changes under `./src` directory will cause custom commands to re-run.
-->


================================================
FILE: guide/03-xt-build-copy.md
================================================
# Copying Files

* * *

<p class='page-intro'>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.</p>

* * *

!!! 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

* * *

<p class='page-intro'>Localization enables translating extension to different languages.</p>

* * *

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
&nbsp; &nbsp; &nbsp; &nbsp; └─ `en`/messages.json |  English dictionary
&nbsp; &nbsp; &nbsp; &nbsp; └─ `fr`/myFile.json | French dictionary
&nbsp; &nbsp; &nbsp; &nbsp; └─ `pl/` | 
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └─ app.json | Polish dictionary, part 1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └─ options.json | Polish dictionary, part 2
&nbsp; &nbsp; &nbsp; &nbsp; └─ `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

* * *

<p class='page-intro'>Customize build behavior for extension manifest.</p>

* * *

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

* * *

<p class='page-intro'>Instructions for configuring javascript build outputs.</p>

* * *

`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

* * *

<p class='page-intro'>Instructions for configuring stylesheet build outputs.</p>

* * *

`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

* * *

<p class='page-intro'><code>xt-build</code> command builds an extension project.</p>

* * *

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)
<!-- `"commands_watch_path"` | Commands watch pattern during dev builds | [Guide](03-xt-build-cmds.md) -->


================================================
FILE: guide/04-xt-clean.md
================================================
# xt-clean


* * *

<p class='page-intro'><code>xt-clean</code> command removes all automatically generated files from the project directories.</p>

* * *

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

* * *

<p class='page-intro'>Use templates to customize the look and feel of 
source code documentation.</p>

* * *

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).

<a href="https://jsdoc.app/about-configuring-default-template.html" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/jsdoc-default.jpg" alt="default template"/>
</a>

## 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)

![GitHub last commit](https://img.shields.io/github/last-commit/braintree/jsdoc-template)

<a href="https://github.com/braintree/jsdoc-template" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/braintree.jpg" alt="braintree"/>
</a>

* * *

### clean-jsdoc-theme

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

![GitHub last commit](https://img.shields.io/github/last-commit/ankitskvmdam/clean-jsdoc-theme)

_Light mode_

<a href="https://github.com/ankitskvmdam/clean-jsdoc-theme" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/clean-jsdoc-light.jpg" alt="light mode"/>
</a>

_Dark mode_

<a href="https://github.com/ankitskvmdam/clean-jsdoc-theme" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/clean-jsdoc-dark.jpg" alt="dark mode"/>
</a>

* * *

### Foodoc

[Source and configuration](https://github.com/steveush/foodoc)

![GitHub last commit](https://img.shields.io/github/last-commit/steveush/foodoc)


<a href="https://github.com/steveush/foodoc" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/foodoc.jpg" alt="foodoc"/>
</a>

* * *

### JsDoc Template

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

![GitHub last commit](https://img.shields.io/github/last-commit/AlexisPuga/jsdoc-template)

<a href="https://github.com/AlexisPuga/jsdoc-template" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/jsdoc-template.jpg" alt="JsDoc Template"/>
</a>

* * *

### Tidy JsDoc

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

![GitHub last commit](https://img.shields.io/github/last-commit/julie-ng/tidy-jsdoc)

<a href="https://github.com/julie-ng/tidy-jsdoc" 
   class="preview" target="_blank" rel="noreferrer nofollow">
  <span class="bar">
      <span class="red"></span>
      <span class="yellow"></span>
      <span class="green"></span>
  </span><img src="/extension-cli/assets/images/tidy-jsdoc.jpg" alt="Tidy JsDoc"/>
</a>

* * *

<!-- style the preview views -->
<style>
article a.preview {
  display: block;
  margin: 2rem auto;
  width:1000px; max-width: calc(100% - 42px); 
  box-shadow: 0 12px 42px rgba(0,0,0,.22), 0 4px 6px rgba(0,0,0,0.4);
  border-radius: 4px;
  overflow: hidden;
  position: relative;
}
article a.preview img{
  width: 100%;
  background: #222;
  display: block;
  position: relative;
  margin:0;
}
article a.preview .bar{
  padding:10px 12px; width:100%;
  background: #e4e4e4;
  position: relative;
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  margin:0;
}
article a.preview .bar > span{
  height: 10px; width:10px;
  border-radius: 50%;
  background: #9997;
  margin-right: 8px;
}
article a.preview .bar .red{
    background:#FF5952;
}
article a.preview .bar .yellow{
    background:#E6C029;
}
article a.preview .bar .green{
    background:#54C22B;
}
</style>


================================================
FILE: guide/05-xt-docs.md
================================================
# xt-docs

* * *

<p class='page-intro'><code>xt-docs</code> command generates source
 code documentation for an extension project.</p>

* * *

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


* * *

<p class='page-intro'><code>xt-sync</code> command enables copying and updating 
configuration files.</p>

* * *

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


* * *

<p class='page-intro'><code>xt-test</code> command runs unit tests.</p>

* * *

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

* * *

<p class='page-intro'><code>extension-cli</code> command creates a new web extension project.</p>

* * *

## 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

* * *

<p class='page-intro'>Collection of generally helpful links for extension development.</p>

* * *

**Getting started guides**<br/>
[Chrome](https://developer.chrome.com/extensions/getstarted) &bull;
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions#get_started) &bull;
[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions) &bull;
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/) &bull;
[Opera](https://dev.opera.com/extensions/getting-started/)

**Extension manifest references**<br/>
[Chrome v3](https://developer.chrome.com/extensions/manifest) &bull;
[Chrome v2 ⚠️](https://developer.chrome.com/docs/extensions/mv2/manifest/) &bull;
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json) &bull; 
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/manifest-format) &bull;
[Opera](https://dev.opera.com/extensions/manifest/)

**Lists of browser APIs**<br/>
[Chrome](https://developer.chrome.com/extensions/api_index) &bull;
[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API#javascript_api_listing) &bull;
[Opera](https://dev.opera.com/extensions/apis/)
  
    
**Cross-browser compatibility charts**<br/>
[Manifest compatibility](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json) &bull;
[JavaScript API support](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)

**Internationalization**<br/>
[Supporting multiple languages](https://developer.chrome.com/extensions/i18n) &bull;
[Language locales list](https://developer.chrome.com/docs/webstore/i18n/#choosing-locales-to-support)

**Extension Publishing Guides**<br/>
[Chrome](https://developer.chrome.com/webstore/publish) &bull;
[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/create-dev-account) &bull;
[Firefox](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/) &bull;
[Opera](https://dev.opera.com/extensions/publishing-guidelines/) &bull;
[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/distributing_your_safari_web_extension)

**Marketplace Image Guidelines**<br/>
[Chrome](https://developer.chrome.com/webstore/images) &bull;
[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**<br/>
[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 &rarr;](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  
    ```
    
    <br/>

2. In the **driver project** terminal run this command:    
    
    ```bash
    npm link extension-cli
    ``` 
   
* * * 

**<center>Your dev environment should now be ready to use.</center>**
 
* * * 

## 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

<p class='page-intro'>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.</p>

* * *

## Features

🖥️ &nbsp; **Javascript Bundling** 
<br/>&nbsp;&nbsp; &nbsp; &nbsp; Compiles, bundles and minifies javascript files (supports ES6, ES2021 syntax) <br/>

🎨 &nbsp; **CSS Bundling**
<br/>&nbsp;&nbsp; &nbsp; &nbsp; Compiles, bundles, and minifies CSS and [SASS](https://sass-lang.com/guide) files <br/>

💄 &nbsp; **Linting**
<br/>&nbsp;&nbsp; &nbsp; &nbsp; lint JavaScript using [ESLint](https://eslint.org/) <br/>

📦 &nbsp; **ZIP Generation**
<br/>&nbsp;&nbsp; &nbsp; &nbsp; Generates a .zip file for uploading to extension marketplaces<br/>

📝 &nbsp; **Document Source Code**
<br/>&nbsp;&nbsp; &nbsp; &nbsp; Generates source code documentation using [JSDoc](https://jsdoc.app/about-getting-started.html) <br/>

⚗️ &nbsp; **Unit Testing** 
<br/>&nbsp;&nbsp; &nbsp; &nbsp; 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) <br/>

⚔️ &nbsp; **Cross-Browser Compatibility**
<br/>&nbsp;&nbsp; &nbsp; &nbsp; develop extensions for Chrome, Edge, Firefox, Opera and Brave. <br/>

* * *


Extension CLI is made and maintained free and voluntarily by
<a href="https://github.com/MobileFirstLLC/extension-cli/graphs/contributors" target="_blank" rel="noreferrer noopener">open source contributors</a> 
behind several popular extensions. If you find it helpful, please share, star, or contribute to its development.

<br/>

<a class="github-button" href="https://github.com/mobilefirstllc/extension-cli" data-icon="octicon-star" data-size="large" aria-label="Star mobilefirstllc/extension-cli on GitHub">Star</a> &nbsp; <a class="github-button" href="https://github.com/mobilefirstllc/extension-cli/fork" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork mobilefirstllc/extension-cli on GitHub">Fork</a> &nbsp; <a class="github-button" href="https://github.com/mobilefirstllc/extension-cli/issues" data-icon="octicon-issue-opened" data-size="large" aria-label="Issue mobilefirstllc/extension-cli on GitHub">Issue</a> &nbsp; <a class="github-button" href="https://github.com/mobilefirstllc/extension-cli/subscription" data-icon="octicon-eye" data-size="large" aria-label="Watch mobilefirstllc/extension-cli on GitHub">Watch</a>

* * *

## 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 &rarr;](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 &rarr;](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" %}
<h2 id="__comments">{{ lang.t("meta.comments") }}</h2>
<script src="https://utteranc.es/client.js"
        repo="MobileFirstLLC/extension-cli"
        issue-term="pathname"
        label="documentation"
        theme="github-dark"
        crossorigin="anonymous"
        async>
</script>

{% 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');
        });

    });
});
Download .txt
gitextract_b9dw6q70/

├── .eslintrc
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── changelog.md
│   ├── code_of_conduct.md
│   ├── contributing.md
│   └── workflows/
│       ├── codeql-analysis.yml
│       └── publish.yml
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── cli/
│   ├── gulpfile.js
│   ├── rootsuite.js
│   ├── texts.js
│   ├── utilities.js
│   ├── xt-build.js
│   ├── xt-clean.js
│   ├── xt-create.js
│   ├── xt-docs.js
│   ├── xt-sync.js
│   └── xt-test.js
├── config/
│   ├── actions.yml
│   ├── build.json
│   ├── docs.json
│   ├── gitlab.yml
│   ├── ignore
│   ├── init/
│   │   ├── background.js
│   │   ├── eslint.json
│   │   ├── intro.md
│   │   ├── manifest.json
│   │   ├── messages.json
│   │   ├── package.json
│   │   └── test.js
│   ├── readme.md
│   └── travis.yml
├── guide/
│   ├── 01-getting-started.md
│   ├── 02-configuration.md
│   ├── 03-xt-build-assets.md
│   ├── 03-xt-build-cmds.md
│   ├── 03-xt-build-copy.md
│   ├── 03-xt-build-locales.md
│   ├── 03-xt-build-manifest.md
│   ├── 03-xt-build-scripts.md
│   ├── 03-xt-build-styles.md
│   ├── 03-xt-build.md
│   ├── 04-xt-clean.md
│   ├── 05-xt-docs-templates.md
│   ├── 05-xt-docs.md
│   ├── 06-xt-sync.md
│   ├── 07-xt-test.md
│   ├── 08-xt-create.md
│   ├── 09-release-notes-0.md
│   ├── 09-release-notes.md
│   ├── 12-helpful.md
│   ├── 13-cli-development.md
│   ├── 13-dev-env.md
│   ├── 14-user-guide.md
│   ├── assets/
│   │   ├── custom.css
│   │   └── custom.js
│   ├── index.md
│   └── overrides/
│       └── main.html
├── mkdocs.yml
├── package.json
├── renovate.json
├── requirements.txt
└── test/
    ├── README.md
    ├── cli-create.test.js
    └── cli-utilities.test.js
Download .txt
SYMBOL INDEX (15 symbols across 2 files)

FILE: cli/utilities.js
  class Utilities (line 16) | class Utilities {
    method generateDirectoryName (line 29) | generateDirectoryName(name, defaultName = 'extension-1') {
    method replaceVars (line 47) | replaceVars(content, vars) {
    method keyReplace (line 76) | keyReplace(child, parent) {
    method iterateConfigs (line 109) | iterateConfigs(defaultConfig, projectConfig) {
    method copyFolderSync (line 130) | copyFolderSync(from, to) {
    method copyFile (line 154) | copyFile(from, to) {
    method readFile (line 163) | readFile(filePath) {
    method writeFile (line 172) | writeFile(filePath, content) {
    method fileExists (line 181) | fileExists(filePath) {
    method createDir (line 192) | createDir(dirPath) {
    method readJSON (line 207) | readJSON(filePath) {
    method readAndReplaceTextFile (line 217) | readAndReplaceTextFile(path, vars) {
    method readAndReplaceJSONFile (line 227) | readAndReplaceJSONFile(path, vars) {

FILE: guide/assets/custom.js
  function gtag (line 2) | function gtag() {
Condensed preview — 70 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (176K chars).
[
  {
    "path": ".eslintrc",
    "chars": 5941,
    "preview": "{\n    \"env\": {\n      \"browser\": true,\n      \"es2021\": true,\n      \"node\": true\n    },\n    \"globals\": {\n      \"document\":"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 685,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 595,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
  },
  {
    "path": ".github/changelog.md",
    "chars": 25511,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github."
  },
  {
    "path": ".github/code_of_conduct.md",
    "chars": 3352,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": ".github/contributing.md",
    "chars": 36,
    "preview": "Let's add guidelines here over time\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 2231,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 723,
    "preview": "name: NPM Publish\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - use"
  },
  {
    "path": ".gitignore",
    "chars": 71,
    "preview": ".idea/\nnode_modules/\nsite/\ntutorial-env/\n.nyc_output\nenv\nicon.png\nvenv\n"
  },
  {
    "path": ".npmignore",
    "chars": 227,
    "preview": ".idea/\n.gitignore\n.docs.json\n.travis.yml\n.github/\nrequirements.txt\nnode_modules/\nCHANGELOG.md\n*.jpg\nassets/\ndocs/\nguide/"
  },
  {
    "path": ".travis.yml",
    "chars": 535,
    "preview": "language: bash\n\nnode_js:\n  - \"14.18.3\"\n\npython:\n  - \"3.9\"\n\njobs:\n  include:\n    - language: node_js\n      node_js: 14.18"
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2019-2021 Mobile First LLC\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "README.md",
    "chars": 4451,
    "preview": "# Extension CLI\n\n[![npm](https://img.shields.io/npm/v/extension-cli?style=flat-square)](https://www.npmjs.com/package/ex"
  },
  {
    "path": "cli/gulpfile.js",
    "chars": 6713,
    "preview": "const gulp = require('gulp');\nconst del = require('del');\nconst chalk = require('chalk');\nconst gulpChange = require('gu"
  },
  {
    "path": "cli/rootsuite.js",
    "chars": 2610,
    "preview": "/**\n * @description\n * This rootsuite sets up unit testing environment\n */\n\nconst sinon = require('sinon');\nconst chrome"
  },
  {
    "path": "cli/texts.js",
    "chars": 3474,
    "preview": "/**\n * @description\n * This module specifies terminal/console output for all commands\n */\n\nconst chalk = require('chalk'"
  },
  {
    "path": "cli/utilities.js",
    "chars": 6946,
    "preview": "const fs = require('fs');\nconst path = require('path');\n\n/**\n * @class\n * @classdesc Utility class provides helper metho"
  },
  {
    "path": "cli/xt-build.js",
    "chars": 2556,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name xt-build\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-build --env {pro"
  },
  {
    "path": "cli/xt-clean.js",
    "chars": 2418,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name xt-clean\n * @module\n * @public\n *\n * @description\n *\n *```text\n * xt-clean [--modules]"
  },
  {
    "path": "cli/xt-create.js",
    "chars": 3646,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name extension-cli\n * @module\n * @public\n *\n * @description\n *\n *```text\n * npx extension-c"
  },
  {
    "path": "cli/xt-docs.js",
    "chars": 3417,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name xt-docs\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-docs [--config fi"
  },
  {
    "path": "cli/xt-sync.js",
    "chars": 2410,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name xt-sync\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-sync\n * ```\n *\n *"
  },
  {
    "path": "cli/xt-test.js",
    "chars": 2168,
    "preview": "#!/usr/bin/env node\n\n/**\n * @name xt-test\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-test [--pattern] "
  },
  {
    "path": "config/actions.yml",
    "chars": 1900,
    "preview": "name: Build\n\non:\n  push:\n    branches: [ main ]\n\n## to run workflow on pull requests:\n#on:\n#  pull_request:\n#    branche"
  },
  {
    "path": "config/build.json",
    "chars": 598,
    "preview": "{\n  \"dist\": \"./dist\",\n  \"source\": \"./src\",\n  \"releases\": \"./\",\n  \"release_name\": \"release\",\n  \"manifest\": \"./src/manifes"
  },
  {
    "path": "config/docs.json",
    "chars": 526,
    "preview": "{\n  \"tags\": {\n    \"allowUnknownTags\": true,\n    \"dictionaries\": [\n      \"jsdoc\"\n    ]\n  },\n  \"source\": {\n    \"include\": "
  },
  {
    "path": "config/gitlab.yml",
    "chars": 659,
    "preview": "image: node:latest\n\nstages:\n  - install\n  - pages\n  - test\n  - publish\n\ncache:\n  key: ${CI_COMMIT_REF_SLUG}\n  paths:\n   "
  },
  {
    "path": "config/ignore",
    "chars": 108,
    "preview": ".idea/\n.vscode/\nnode_modules/\n.nyc_output/\ncoverage/\ndist/\npublic/documentation/\nrelease.zip\nyarn-error.log\n"
  },
  {
    "path": "config/init/background.js",
    "chars": 61,
    "preview": "console.log('This is background service worker - edit me!');\n"
  },
  {
    "path": "config/init/eslint.json",
    "chars": 483,
    "preview": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\"\n  ],"
  },
  {
    "path": "config/init/intro.md",
    "chars": 1622,
    "preview": "# ${name}\n\n${description}\n\n## Development \n\nThis extension was created with [Extension CLI](https://oss.mobilefirst.me/e"
  },
  {
    "path": "config/init/manifest.json",
    "chars": 622,
    "preview": "{\n  \"name\": \"__MSG_appName__\",\n  \"short_name\": \"__MSG_appShortName__\",\n  \"description\": \"__MSG_appDescription__\",\n  \"hom"
  },
  {
    "path": "config/init/messages.json",
    "chars": 157,
    "preview": "{\n  \"appName\": {\n    \"message\": \"${name}\"\n  },\n  \"appShortName\": {\n    \"message\": \"${name}\"\n  },\n  \"appDescription\": {\n "
  },
  {
    "path": "config/init/package.json",
    "chars": 960,
    "preview": "{\n  \"name\": \"${safeName}\",\n  \"description\": \"${description}\",\n  \"version\": \"${version}\",\n  \"homepage\": \"${homepage}\",\n  "
  },
  {
    "path": "config/init/test.js",
    "chars": 120,
    "preview": "describe('Test extension', () => {\n\n    it('This is a dummy test', () => {\n        expect(true).to.be.true;\n    });\n});\n"
  },
  {
    "path": "config/readme.md",
    "chars": 1553,
    "preview": "# CLI configuration files\n\nThis directory contains various files that are used by the available CLI commands. Below is a"
  },
  {
    "path": "config/travis.yml",
    "chars": 682,
    "preview": "language: node_js\n\nnode_js:\n  - \"14.15.4\"\n\ncache:\n  directories:\n  - node_modules\n\nbefore_script:\n  - npm run test || tr"
  },
  {
    "path": "guide/01-getting-started.md",
    "chars": 1061,
    "preview": "# Installation\n\n### Prerequisites\n\nBefore using extension CLI, you must have the following:\n\n- [Node.js](https://nodejs."
  },
  {
    "path": "guide/02-configuration.md",
    "chars": 919,
    "preview": "# Configuration for Existing Applications\n\n!!! info\n    **If you created the extension with Extension CLI, this setup is"
  },
  {
    "path": "guide/03-xt-build-assets.md",
    "chars": 795,
    "preview": "# Static assets\n\n* * *\n\n<p class='page-intro'>Specify how static assets will be handled during builds.</p>\n\n* * *\n\nBy de"
  },
  {
    "path": "guide/03-xt-build-cmds.md",
    "chars": 1092,
    "preview": "# Custom commands\n\n* * *\n\n<p class='page-intro'>Custom commands enables running any custom actions after build and befor"
  },
  {
    "path": "guide/03-xt-build-copy.md",
    "chars": 2676,
    "preview": "# Copying Files\n\n* * *\n\n<p class='page-intro'>Copying enables including files in the output without modifying them durin"
  },
  {
    "path": "guide/03-xt-build-locales.md",
    "chars": 2712,
    "preview": "# Localization\n\n* * *\n\n<p class='page-intro'>Localization enables translating extension to different languages.</p>\n\n* *"
  },
  {
    "path": "guide/03-xt-build-manifest.md",
    "chars": 1864,
    "preview": "# Manifest\n\n* * *\n\n<p class='page-intro'>Customize build behavior for extension manifest.</p>\n\n* * *\n\nIn your build conf"
  },
  {
    "path": "guide/03-xt-build-scripts.md",
    "chars": 2148,
    "preview": "# Building Scripts\n\n* * *\n\n<p class='page-intro'>Instructions for configuring javascript build outputs.</p>\n\n* * *\n\n`js_"
  },
  {
    "path": "guide/03-xt-build-styles.md",
    "chars": 1736,
    "preview": "# Building Stylesheets\n\n* * *\n\n<p class='page-intro'>Instructions for configuring stylesheet build outputs.</p>\n\n* * *\n\n"
  },
  {
    "path": "guide/03-xt-build.md",
    "chars": 4234,
    "preview": "# xt-build\n\n* * *\n\n<p class='page-intro'><code>xt-build</code> command builds an extension project.</p>\n\n* * *\n\nBuild co"
  },
  {
    "path": "guide/04-xt-clean.md",
    "chars": 1520,
    "preview": "# xt-clean\n\n\n* * *\n\n<p class='page-intro'><code>xt-clean</code> command removes all automatically generated files from t"
  },
  {
    "path": "guide/05-xt-docs-templates.md",
    "chars": 5045,
    "preview": "# Documentation Templates\n\n* * *\n\n<p class='page-intro'>Use templates to customize the look and feel of \nsource code doc"
  },
  {
    "path": "guide/05-xt-docs.md",
    "chars": 2049,
    "preview": "# xt-docs\n\n* * *\n\n<p class='page-intro'><code>xt-docs</code> command generates source\n code documentation for an extensi"
  },
  {
    "path": "guide/06-xt-sync.md",
    "chars": 1215,
    "preview": "# xt-sync\n\n\n* * *\n\n<p class='page-intro'><code>xt-sync</code> command enables copying and updating \nconfiguration files."
  },
  {
    "path": "guide/07-xt-test.md",
    "chars": 2707,
    "preview": "# xt-test\n\n\n* * *\n\n<p class='page-intro'><code>xt-test</code> command runs unit tests.</p>\n\n* * *\n\nThis command will set"
  },
  {
    "path": "guide/08-xt-create.md",
    "chars": 403,
    "preview": "# extension-cli\n\n* * *\n\n<p class='page-intro'><code>extension-cli</code> command creates a new web extension project.</p"
  },
  {
    "path": "guide/09-release-notes-0.md",
    "chars": 3025,
    "preview": "---\ndisqus: \"False\"\n---\n\n\n### 0.11.9 (2021-04-04)\n\n* **xt-build**: enable customizing release filename [PR #37](https://"
  },
  {
    "path": "guide/09-release-notes.md",
    "chars": 5552,
    "preview": "---\ndisqus: \"False\"\n---\n\n### 1.2.5 (2021-03-16) (alpha)\n\n**Dependency upgrades**\n\n- @babel/preset-env: 7.16.11 ([066f782"
  },
  {
    "path": "guide/12-helpful.md",
    "chars": 2959,
    "preview": "# Helpful References\n\n* * *\n\n<p class='page-intro'>Collection of generally helpful links for extension development.</p>\n"
  },
  {
    "path": "guide/13-cli-development.md",
    "chars": 2783,
    "preview": "---\ndisqus: \"False\"\n---\n\n# Extension CLI Development\n\n- This CLI is built with Node.Js, written in JavaScript, and uses "
  },
  {
    "path": "guide/13-dev-env.md",
    "chars": 2060,
    "preview": "# Environment Setup\n\n\nTo build extension CLI locally you will need [Node.js](https://nodejs.org/en/download/)\nand any we"
  },
  {
    "path": "guide/14-user-guide.md",
    "chars": 1747,
    "preview": "# Editing User Guide\n\n\n!!! info\n    If you are interested in editing the content (and not layout) of this user guide, \n "
  },
  {
    "path": "guide/assets/custom.css",
    "chars": 1055,
    "preview": "/* add custom css here */\n.md-main__inner{padding-bottom: 3em}\n.page-intro{font-size:1.3em;line-height:1.7;}\n.highlightt"
  },
  {
    "path": "guide/assets/custom.js",
    "chars": 155,
    "preview": "window.dataLayer = window.dataLayer || [];\nfunction gtag() {\n  window.dataLayer.push(arguments);\n}\ngtag('js', new Date()"
  },
  {
    "path": "guide/index.md",
    "chars": 3404,
    "preview": "---\ndisqus: \"False\"\n---\n\n# Extension CLI\n\n<p class='page-intro'>is a command-line build tool for developing \nchromium br"
  },
  {
    "path": "guide/overrides/main.html",
    "chars": 403,
    "preview": "{% extends \"base.html\" %}\n\n{% block disqus %}\n{% if not page.meta.disqus == \"False\" %}\n<h2 id=\"__comments\">{{ lang.t(\"me"
  },
  {
    "path": "mkdocs.yml",
    "chars": 2337,
    "preview": "site_name: Extension CLI • User Guide\nsite_description: Command-line build tools for chromium extension development.\nsit"
  },
  {
    "path": "package.json",
    "chars": 3357,
    "preview": "{\n  \"name\": \"extension-cli\",\n  \"version\": \"1.2.5-alpha.0\",\n  \"description\": \"CLI tool for building browser extensions\",\n"
  },
  {
    "path": "renovate.json",
    "chars": 94,
    "preview": "{\n  \"labels\": [\"dependencies\"],\n  \"extends\": [\"config:base\", \":disableDependencyDashboard\"]\n}\n"
  },
  {
    "path": "requirements.txt",
    "chars": 23,
    "preview": "mkdocs\nmkdocs-material\n"
  },
  {
    "path": "test/README.md",
    "chars": 74,
    "preview": "# CLI Unit Tests\n\nAll unit tests for extension-CLI are in this directory.\n"
  },
  {
    "path": "test/cli-create.test.js",
    "chars": 352,
    "preview": "// TODO: #21: how to require this without actually running the command?\n// const BuildScript = require('../cli/xt-build'"
  },
  {
    "path": "test/cli-utilities.test.js",
    "chars": 12067,
    "preview": "const Utilities = require('../cli/utilities').Utilities;\nconst sinon = require('sinon');\nconst expect = require('chai')."
  }
]

About this extraction

This page contains the full source code of the MobileFirstLLC/extension-cli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 70 files (161.6 KB), approximately 47.1k tokens, and a symbol index with 15 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!