[
  {
    "path": ".eslintrc",
    "content": "{\n    \"env\": {\n      \"browser\": true,\n      \"es2021\": true,\n      \"node\": true\n    },\n    \"globals\": {\n      \"document\": false,\n      \"escape\": false,\n      \"navigator\": false,\n      \"unescape\": false,\n      \"window\": false,\n      \"describe\": true,\n      \"before\": true,\n      \"it\": true,\n      \"expect\": true,\n      \"sinon\": true,\n      \"afterEach\": true,\n      \"after\": true\n    },\n    \"plugins\": [],\n    \"rules\": {\n      \"block-scoped-var\": 2,\n      \"brace-style\": [\n        2,\n        \"1tbs\",\n        {\n          \"allowSingleLine\": true\n        }\n      ],\n      \"camelcase\": [\n        0,\n        {\n          \"properties\": \"always\"\n        }\n      ],\n      \"comma-dangle\": [\n        2,\n        \"never\"\n      ],\n      \"comma-spacing\": [\n        2,\n        {\n          \"before\": false,\n          \"after\": true\n        }\n      ],\n      \"comma-style\": [\n        2,\n        \"last\"\n      ],\n      \"complexity\": 0,\n      \"consistent-return\": 2,\n      \"consistent-this\": 0,\n      \"curly\": [\n        2,\n        \"multi-line\"\n      ],\n      \"default-case\": 0,\n      \"dot-location\": [\n        2,\n        \"property\"\n      ],\n      \"dot-notation\": 0,\n      \"eol-last\": 2,\n      \"eqeqeq\": [\n        2,\n        \"allow-null\"\n      ],\n      \"func-names\": 0,\n      \"func-style\": 0,\n      \"generator-star-spacing\": [\n        2,\n        \"both\"\n      ],\n      \"guard-for-in\": 0,\n      \"handle-callback-err\": [\n        2,\n        \"^(err|error|anySpecificError)$\"\n      ],\n      \"key-spacing\": [\n        2,\n        {\n          \"beforeColon\": false,\n          \"afterColon\": true\n        }\n      ],\n      \"keyword-spacing\": [\n        2,\n        {\n          \"before\": true,\n          \"after\": true\n        }\n      ],\n      \"linebreak-style\": 0,\n      \"max-depth\": 0,\n      \"max-len\": [\n        2,\n        120,\n        4\n      ],\n      \"max-nested-callbacks\": 0,\n      \"max-params\": 0,\n      \"max-statements\": 0,\n      \"new-cap\": [\n        2,\n        {\n          \"newIsCap\": true,\n          \"capIsNew\": false\n        }\n      ],\n      \"newline-after-var\": [\n        2,\n        \"always\"\n      ],\n      \"new-parens\": 2,\n      \"no-alert\": 0,\n      \"no-array-constructor\": 2,\n      \"no-bitwise\": 0,\n      \"no-caller\": 2,\n      \"no-catch-shadow\": 0,\n      \"no-cond-assign\": 2,\n      \"no-console\": 0,\n      \"no-constant-condition\": 0,\n      \"no-continue\": 0,\n      \"no-control-regex\": 2,\n      \"no-debugger\": 2,\n      \"no-delete-var\": 2,\n      \"no-div-regex\": 0,\n      \"no-dupe-args\": 2,\n      \"no-dupe-keys\": 2,\n      \"no-duplicate-case\": 2,\n      \"no-else-return\": 2,\n      \"no-empty\": 0,\n      \"no-empty-character-class\": 2,\n      \"no-eq-null\": 0,\n      \"no-eval\": 2,\n      \"no-ex-assign\": 2,\n      \"no-extend-native\": 2,\n      \"no-extra-bind\": 2,\n      \"no-extra-boolean-cast\": 2,\n      \"no-extra-parens\": 0,\n      \"no-extra-semi\": 0,\n      \"no-extra-strict\": 0,\n      \"no-fallthrough\": 2,\n      \"no-floating-decimal\": 2,\n      \"no-func-assign\": 2,\n      \"no-implied-eval\": 2,\n      \"no-inline-comments\": 0,\n      \"no-inner-declarations\": [\n        2,\n        \"functions\"\n      ],\n      \"no-invalid-regexp\": 2,\n      \"no-irregular-whitespace\": 2,\n      \"no-iterator\": 2,\n      \"no-label-var\": 2,\n      \"no-labels\": 2,\n      \"no-lone-blocks\": 0,\n      \"no-lonely-if\": 0,\n      \"no-loop-func\": 0,\n      \"no-mixed-requires\": 0,\n      \"no-mixed-spaces-and-tabs\": [\n        2,\n        false\n      ],\n      \"no-multi-spaces\": 2,\n      \"no-multi-str\": 2,\n      \"no-multiple-empty-lines\": [\n        2,\n        {\n          \"max\": 1\n        }\n      ],\n      \"no-native-reassign\": 2,\n      \"no-negated-in-lhs\": 2,\n      \"no-nested-ternary\": 0,\n      \"no-new\": 2,\n      \"no-new-func\": 2,\n      \"no-new-object\": 2,\n      \"no-new-require\": 2,\n      \"no-new-wrappers\": 2,\n      \"no-obj-calls\": 2,\n      \"no-octal\": 2,\n      \"no-octal-escape\": 2,\n      \"no-path-concat\": 0,\n      \"no-plusplus\": 0,\n      \"no-process-env\": 0,\n      \"no-process-exit\": 0,\n      \"no-proto\": 2,\n      \"no-redeclare\": 2,\n      \"no-regex-spaces\": 2,\n      \"no-reserved-keys\": 0,\n      \"no-restricted-modules\": 0,\n      \"no-return-assign\": 2,\n      \"no-script-url\": 0,\n      \"no-self-compare\": 2,\n      \"no-sequences\": 2,\n      \"no-shadow\": 0,\n      \"no-shadow-restricted-names\": 2,\n      \"no-spaced-func\": 2,\n      \"no-sparse-arrays\": 2,\n      \"no-sync\": 0,\n      \"no-ternary\": 0,\n      \"no-throw-literal\": 2,\n      \"no-trailing-spaces\": 1,\n      \"no-undef\": 2,\n      \"no-undef-init\": 2,\n      \"no-undefined\": 0,\n      \"no-underscore-dangle\": 0,\n      \"no-unneeded-ternary\": 2,\n      \"no-unreachable\": 2,\n      \"no-unused-expressions\": 0,\n      \"no-unused-vars\": [\n        2,\n        {\n          \"vars\": \"all\",\n          \"args\": \"none\"\n        }\n      ],\n      \"no-use-before-define\": 2,\n      \"no-var\": 0,\n      \"no-void\": 0,\n      \"no-warning-comments\": 0,\n      \"no-with\": 2,\n      \"one-var\": 0,\n      \"operator-assignment\": 0,\n      \"operator-linebreak\": [\n        2,\n        \"after\"\n      ],\n      \"padded-blocks\": 0,\n      \"quote-props\": 0,\n      \"quotes\": [\n        2,\n        \"single\",\n        \"avoid-escape\"\n      ],\n      \"radix\": 2,\n      \"semi\": [\n        2,\n        \"always\"\n      ],\n      \"semi-spacing\": 0,\n      \"sort-vars\": 0,\n      \"space-before-blocks\": [\n        2,\n        \"always\"\n      ],\n      \"space-before-function-paren\": [\n        2,\n        {\n          \"anonymous\": \"always\",\n          \"named\": \"never\"\n        }\n      ],\n      \"space-in-brackets\": 0,\n      \"space-in-parens\": [\n        2,\n        \"never\"\n      ],\n      \"space-infix-ops\": 2,\n      \"space-unary-ops\": [\n        2,\n        {\n          \"words\": true,\n          \"nonwords\": false\n        }\n      ],\n      \"spaced-comment\": [\n        2,\n        \"always\"\n      ],\n      \"strict\": 0,\n      \"use-isnan\": 2,\n      \"valid-jsdoc\": 0,\n      \"valid-typeof\": 2,\n      \"vars-on-top\": 2,\n      \"wrap-iife\": [\n        2,\n        \"any\"\n      ],\n      \"wrap-regex\": 0,\n      \"yoda\": [\n        2,\n        \"never\"\n      ]\n    }\n  }\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n - Node.js Version\n\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/changelog.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n\n### [1.2.5-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.4...v1.2.5-alpha.0) (2022-03-17)\n\n\n### Bug Fixes\n\n* **deps:** downgraded chalk to v4 ([fee1634](https://github.com/mobilefirstllc/extension-cli/commit/fee16346a9b30b6dc61d3c76d0f1deaf868d3988)), closes [#155](https://github.com/mobilefirstllc/extension-cli/issues/155)\n* **deps:** update babel monorepo ([#157](https://github.com/mobilefirstllc/extension-cli/issues/157)) ([066f782](https://github.com/mobilefirstllc/extension-cli/commit/066f7820ce48e877cf5477ff77037c9d3a9d2fdd))\n* **deps:** update babel monorepo to v7.16.0 ([945f8a0](https://github.com/mobilefirstllc/extension-cli/commit/945f8a0d9041e0c424f87351e2ecbd56b2cf4ce2))\n* **deps:** update babel monorepo to v7.16.5 ([1145058](https://github.com/mobilefirstllc/extension-cli/commit/11450586884e27324b63b35d4c99209b623217bf))\n* **deps:** update babel monorepo to v7.16.8 ([c4a44aa](https://github.com/mobilefirstllc/extension-cli/commit/c4a44aaf06abedeb4fcda8ec3abac55bef34f147))\n* **deps:** update dependency @babel/preset-env to v7.16.4 ([502269b](https://github.com/mobilefirstllc/extension-cli/commit/502269bfa4b9ec02c0ca9a4a0292be2b1f62fb1e))\n* **deps:** update dependency @babel/register to v7.16.9 ([206eafd](https://github.com/mobilefirstllc/extension-cli/commit/206eafdb7a1a3109f8da56691ebf36b291c0414b))\n* **deps:** update dependency chalk to v5 ([#134](https://github.com/mobilefirstllc/extension-cli/issues/134)) ([e7c7af0](https://github.com/mobilefirstllc/extension-cli/commit/e7c7af0091bbab8db9be87a1d5ae36982a410d67))\n* **deps:** update dependency eslint to v8.0.1 ([204a9c0](https://github.com/mobilefirstllc/extension-cli/commit/204a9c025abd57336dda507fe14c28f66afd5145))\n* **deps:** update dependency eslint to v8.2.0 ([f208e0f](https://github.com/mobilefirstllc/extension-cli/commit/f208e0fe1d96ba95af10ab3ad5278024a05f26c4))\n* **deps:** update dependency eslint to v8.3.0 ([d7bec9f](https://github.com/mobilefirstllc/extension-cli/commit/d7bec9f05496e8c73f4144885f2481583578b161))\n* **deps:** update dependency eslint to v8.4.0 ([2489220](https://github.com/mobilefirstllc/extension-cli/commit/248922085384faa5fd8b568c51601d3f0ab23720))\n* **deps:** update dependency eslint to v8.4.1 ([91d64e0](https://github.com/mobilefirstllc/extension-cli/commit/91d64e0c20c84b1c4759f8409bb26370affeb87d))\n* **deps:** update dependency eslint to v8.5.0 ([c612311](https://github.com/mobilefirstllc/extension-cli/commit/c6123110367028cac4342f637fd345b37c2efa70))\n* **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))\n* **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))\n* **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))\n* **deps:** update dependency jsdom to v18.0.1 ([9daee71](https://github.com/mobilefirstllc/extension-cli/commit/9daee71d218cec2cc5cac39022ef7483792a600d))\n* **deps:** update dependency jsdom to v18.1.0 ([db6641b](https://github.com/mobilefirstllc/extension-cli/commit/db6641bfaf6ab9bbb7ee66ababff1db5742747d7))\n* **deps:** update dependency jsdom to v18.1.1 ([7e1e222](https://github.com/mobilefirstllc/extension-cli/commit/7e1e22282b95329a7c99d4069a36b0501475e6bb))\n* **deps:** update dependency jsdom to v19 ([b0f290f](https://github.com/mobilefirstllc/extension-cli/commit/b0f290fa43f76fc4e67a95ae35ca4cefb7b9db6c))\n* **deps:** update dependency mocha to v9.1.3 ([c5186f9](https://github.com/mobilefirstllc/extension-cli/commit/c5186f9cb3b0cf1c27998d519c9d470863074358))\n* **deps:** update dependency mocha to v9.1.4 ([f90c528](https://github.com/mobilefirstllc/extension-cli/commit/f90c528f16182d652c305144fb247b5b375f38e6))\n* **deps:** update dependency mocha to v9.2.1 ([534eef9](https://github.com/mobilefirstllc/extension-cli/commit/534eef9c897f36e999fecf475a4a8effb9e326c0))\n* **deps:** update dependency sass to v1.43.4 ([e9674a8](https://github.com/mobilefirstllc/extension-cli/commit/e9674a8de69848da4a6557f7beef6387f2bf283e))\n* **deps:** update dependency sass to v1.43.5 ([ce6e9ab](https://github.com/mobilefirstllc/extension-cli/commit/ce6e9abf46188690762dd45a15b6659b620a122f))\n* **deps:** update dependency sass to v1.44.0 ([60842ce](https://github.com/mobilefirstllc/extension-cli/commit/60842cef73de48d2bdbfa2fd93955701981ce9d3))\n* **deps:** update dependency sass to v1.45.0 ([524a5d7](https://github.com/mobilefirstllc/extension-cli/commit/524a5d770c49365f449fc4bda8cf293f9c80f969))\n* **deps:** update dependency sass to v1.45.1 ([6539428](https://github.com/mobilefirstllc/extension-cli/commit/6539428590a26e61fcbc9edc02e7ecd6b7e2bd7f))\n* **deps:** update dependency sass to v1.46.0 ([bb70958](https://github.com/mobilefirstllc/extension-cli/commit/bb70958be4edb8ed6faef497fb5ba6a6bfbe0694))\n* **deps:** update dependency sass to v1.48.0 ([1cfb841](https://github.com/mobilefirstllc/extension-cli/commit/1cfb841a92df7d99bf29852ea6110897690f7334))\n* **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))\n* **deps:** update dependency sass to v1.49.9 ([aef9fd6](https://github.com/mobilefirstllc/extension-cli/commit/aef9fd6142879af3bf4610c80eaca1f97aff96a4))\n* **deps:** update dependency sinon to v12 ([1c9807d](https://github.com/mobilefirstllc/extension-cli/commit/1c9807d4ab682a8d4e24ebcf591fdd693828c0a2))\n* **deps:** update dependency sinon to v13 ([8282c0a](https://github.com/mobilefirstllc/extension-cli/commit/8282c0a05d2702e6ec92155d8cfaba2de93c9c53))\n* **deps:** update dependency yargs to v17.3.0 ([9f86f05](https://github.com/mobilefirstllc/extension-cli/commit/9f86f05b858551c9cff0b237830e19223627264a))\n* **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))\n\n### [1.2.4](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.4-beta.0...v1.2.4) (2021-10-20)\n\n### [1.2.4-beta.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.2...v1.2.4-beta.0) (2021-10-14)\n\n* Update devtools sourcemap config [PR #119](https://github.com/MobileFirstLLC/extension-cli/pull/119)\n* New extension now initialized with MV3 [#86](https://github.com/MobileFirstLLC/extension-cli/pull/111)\n\n**Dependency updates**\n\n* **deps:** update dependency sass to v1.43.2 ([32eb148](https://github.com/mobilefirstllc/extension-cli/commit/32eb148d81318f115942df2682270ded3c061652))\n* **deps:** update dependency @babel/preset-env to v7.15.8 ([a341965](https://github.com/mobilefirstllc/extension-cli/commit/a3419659b3ac2427f1134f8c6cfb2bb38c29f009))\n* **deps:** update dependency commander to v8.2.0 ([5226669](https://github.com/mobilefirstllc/extension-cli/commit/52266695f15cace4cc422e229afe5555d30ff0e4))\n* **deps:** update dependency eslint to v8 ([d5549a8](https://github.com/mobilefirstllc/extension-cli/commit/d5549a8730256f61edbd36ab7cabbac95db5000e))\n* **deps:** update dependency jsdom to v18 ([681db6b](https://github.com/mobilefirstllc/extension-cli/commit/681db6bafeedda989471235ff6f14ad9edff1885))\n* **deps:** update dependency mocha to v9.1.2 ([d7cecc6](https://github.com/mobilefirstllc/extension-cli/commit/d7cecc60a2aa918559bea17b2531b3e331500cce))\n* **deps:** update dependency prompts to v2.4.2 ([f99cb60](https://github.com/mobilefirstllc/extension-cli/commit/f99cb608f43414ecbb8f9309ce2d32453b11b0d5))\n* **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))\n* **deps:** update dependency yargs to v17.2.1 ([9a06f44](https://github.com/mobilefirstllc/extension-cli/commit/9a06f44b878d178dbd15fbae490470082b99221a))\n\n### [1.2.2](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0...v1.2.2) (2021-08-28)\n\n## [1.2.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.2.0-beta.1...v1.2.0) (2021-07-28)\n\n## [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)\n\n## [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)\n\n## [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)\n\n## [1.2.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.1.0...v1.2.0-alpha.0) (2021-07-19)\n\n* Useless regular-expression character escape ([935d0b3](https://github.com/mobilefirstllc/extension-cli/commit/935d0b3df52255c3c18526b4a1a3212c2c6b1b20))\n* Useless regular-expression character escape ([f2ebae5](https://github.com/mobilefirstllc/extension-cli/commit/f2ebae55ca8bc5df2fe8872a58131219b6f81a88))\n\n## [1.1.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.1.0-alpha.1...v1.1.0) (2021-06-12)\n\n## [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)\n\n## [1.1.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3...v1.1.0-alpha.0) (2021-06-12)\n\n* **xt-sync:** change command to prompt with options; update relevant docs ([7a65245](https://github.com/mobilefirstllc/extension-cli/commit/7a652455e834929c1d5e78d8dc5648a64f079aca))\n\n### [1.0.3](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.3-alpha.2...v1.0.3) (2021-04-27)\n\n### [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)\n\n### [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)\n\n### [1.0.3-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.2...v1.0.3-alpha.0) (2021-04-15)\n\n### [1.0.2](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.1...v1.0.2) (2021-04-12)\n\n* default style bundle without file extension ([70c4aa6](https://github.com/mobilefirstllc/extension-cli/commit/70c4aa69f7c4bdaa440cd3fe61eabe5a4ff7327c))\n\n### [1.0.1](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.0...v1.0.1) (2021-04-12)\n\n## [1.0.0](https://github.com/mobilefirstllc/extension-cli/compare/v1.0.0-alpha.0...v1.0.0) (2021-04-11)\n\n## [1.0.0-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.9...v1.0.0-alpha.0) (2021-04-09)\n\n### [0.11.9](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.9-alpha.0...v0.11.9) (2021-04-05)\n\n### [0.11.9-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8...v0.11.9-alpha.0) (2021-04-04)\n\n### [0.11.8](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.8-alpha.2...v0.11.8) (2021-03-12)\n\n### [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)\n\n### [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)\n\n### [0.11.8-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.7...v0.11.8-alpha.0) (2021-03-09)\n\n### [0.11.7](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.7-alpha.0...v0.11.7) (2021-03-02)\n\n* **xt-docs**: make watch recursive, display more output on errors\n\n### [0.11.7-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.6...v0.11.7-alpha.0) (2021-03-02)\n\n### [0.11.6](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.6-alpha.0...v0.11.6) (2021-02-26)\n\n* **xt-docs** [#23](https://github.com/MobileFirstLLC/extension-cli/issues/23) add watch mode for docs\n\n### [0.11.5](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.5-alpha.1...v0.11.5) (2021-02-24)\n\n* **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))\n\n### [0.11.3](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.3-alpha.0...v0.11.3) (2021-01-27)\n\n### [0.11.3-alpha.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.2...v0.11.3-alpha.0) (2021-01-27)\n\n### [0.11.2](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.1...v0.11.2) (2021-01-08)\n\n* **xt-build:** command path fix ([c0c2e08](https://github.com/mobilefirstllc/extension-cli/commit/c0c2e08bcfbb348ce8bb8d9980d4db22e2713d37))\n\n### [0.11.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.11.0...v0.11.1) (2021-01-06)\n\n* **custom commands:** [#18](https://github.com/mobilefirstllc/extension-cli/issues/18) enable custom commands watch ([3cfe52b](https://github.com/mobilefirstllc/extension-cli/commit/3cfe52ba38779d1570943a74a0a2e45203159d54))\n* [#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))\n\n* xt-create images fix (again)\n\n### [0.10.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.4...v0.10.1) (2020-12-15)\n\n* xt-create images ([b6ad50f](https://github.com/mobilefirstllc/extension-cli/commit/b6ad50f0367a20d45d2ea677591e3869c3f4fff3))\n* [#16](https://github.com/mobilefirstllc/extension-cli/issues/16) update test configs ([7f57bf5](https://github.com/mobilefirstllc/extension-cli/commit/7f57bf527ab6fcccec53fd4544e2fc134b5083ee))\n* [#17](https://github.com/mobilefirstllc/extension-cli/issues/17) check if gitignore exists ([b96edc9](https://github.com/mobilefirstllc/extension-cli/commit/b96edc9a01e0077b0d68f994782c2b5c840199c1))\n* **xt-create:** change default icon to high contrast ([4895f43](https://github.com/mobilefirstllc/extension-cli/commit/4895f43bf16b63309bc9e3d14248843050bc164c))\n\n### [0.9.4](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.3...v0.9.4) (2020-11-29)\n\n### [0.9.3](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.2...v0.9.3) (2020-10-31)\n\n### [0.9.2](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.1...v0.9.2) (2020-10-31)\n\n* [#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))\n\n### [0.9.1](https://github.com/mobilefirstllc/extension-cli/compare/v0.9.0...v0.9.1) (2020-10-11)\n\n* [#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))\n\n## [0.9.0](https://github.com/mobilefirstllc/extension-cli/compare/v0.8.16...v0.9.0) (2020-10-05)\n\n* **xt-clean:** refactor command ([8acf9c8](https://github.com/mobilefirstllc/extension-cli/commit/8acf9c80757c44265c9bf605882d8a06fd97d7bb))\n* **xt-create:** refactor command ([1c64f93](https://github.com/mobilefirstllc/extension-cli/commit/1c64f932966c405b3958ce6069323661ac5079d9))\n* **xt-create:** refactor create command ([6ac7f95](https://github.com/mobilefirstllc/extension-cli/commit/6ac7f95f1e08ea278498293c4f4d3048b5bc8190))\n* **xt-docs:** refactor docs command ([432fa79](https://github.com/mobilefirstllc/extension-cli/commit/432fa7979ca7dc686c31cdd6d92fa5911ad467f1))\n* **xt-sync:** refactor sync ([c938eec](https://github.com/mobilefirstllc/extension-cli/commit/c938eec37a6ce3a4ad8bba1c5a22519a01242761))\n* **xt-test:** add configurable test path ([876fb8a](https://github.com/mobilefirstllc/extension-cli/commit/876fb8a0afbaeab196df6aebd09fc92a977150de))\n\n### [0.8.16](https://github.com/mobilefirstllc/extension-cli/compare/v0.8.15...v0.8.16) (2020-08-09)\n\n### [0.8.15](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.14...v0.8.15) (2020-08-04)\n\n* fix packages ([0cad024](https://github.com/MobileFirstLLC/extension-cli/commit/0cad024ba53ec61ae96db0bcf6a616f476acebcf))\n\n### [0.8.14](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.13...v0.8.14) (2020-08-01)\n\n* **xt-create:** update generated docs ([a7ebb95](https://github.com/MobileFirstLLC/extension-cli/commit/a7ebb952cfeb0beac89b71a680a24eaad7325d95))\n\n### [0.8.13](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.12...v0.8.13) (2020-07-11)\n\n### [0.8.12](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.11...v0.8.12) (2020-05-26)\n\n* **xt-build:** undo webpack config change ([19a19ba](https://github.com/MobileFirstLLC/extension-cli/commit/19a19baf7a99787bf34c295fe580d646a70b8b2f))\n\n### [0.8.11](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.10...v0.8.11) (2020-05-26)\n\n* **xt-docs:** fix docs command related issue with init configs ([88c5f58](https://github.com/MobileFirstLLC/extension-cli/commit/88c5f58986b9b2c33c8b86d774e466c7a9f24e03))\n\n### [0.8.10](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9...v0.8.10) (2020-05-25)\n\n* **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))\n* **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))\n* **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))\n* **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))\n\n### [0.8.9](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.9-alpha.2...v0.8.9) (2020-04-10)\n\n### [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)\n\n* **xt-create:** update docs regarding xt-create and use ([27b39bb](https://github.com/MobileFirstLLC/extension-cli/commit/27b39bb46c5f57336a0083fa35d395936605936a))\n\n### [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)\n\n* **xt-create:** minor fixes ([c3c7b31](https://github.com/MobileFirstLLC/extension-cli/commit/c3c7b31fc8ca8c92be0e546287f474e3744439e3))\n\n### [0.8.9-alpha.0](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.8...v0.8.9-alpha.0) (2020-04-10)\n\n* **xt-create:** implement create command ([c28b22b](https://github.com/MobileFirstLLC/extension-cli/commit/c28b22b8ae41b786e49222c3d7c8830f5a2e92b4))\n\n### [0.8.8](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.7...v0.8.8) (2020-04-08)\n\n* Upgraded NPM packages\n\n### [0.8.7](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.6...v0.8.7) (2020-01-17)\n\n* build scripts and update packages ([74f51d2](https://github.com/MobileFirstLLC/extension-cli/commit/74f51d2a1dfe1b4d1611ca4ef848f20dfc92b81e))\n\n### [0.8.6](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.5...v0.8.6) (2019-12-21)\n\n### [0.8.5](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.4...v0.8.5) (2019-12-21)\n\n### [0.8.4](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.3...v0.8.4) (2019-12-20)\n\n### [0.8.3](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.2...v0.8.3) (2019-12-20)\n\n### [0.8.2](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.1...v0.8.2) (2019-12-20)\n\n* cognitive complexity ([de4d2fb](https://github.com/MobileFirstLLC/extension-cli/commit/de4d2fbbb475fbc43787925a7b25ad28fbb13330))\n* reduce complexity ([78bbb16](https://github.com/MobileFirstLLC/extension-cli/commit/78bbb163e6f6ec9396a4146147a52488ab1637c9))\n* Similar blocks of code found in 2 locations. Consider refactoring. ([b3c4e3d](https://github.com/MobileFirstLLC/extension-cli/commit/b3c4e3d014a3f2ac7db301ee3f80e16dbd8b7b00))\n* Similar blocks of code found in 3 locations. Consider refactoring. ([c6a7e30](https://github.com/MobileFirstLLC/extension-cli/commit/c6a7e3074d8c1305246b0b69fb03475a3d27ec9f))\n\n### [0.8.1](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0...v0.8.1) (2019-12-20)\n\n## [0.8.0](https://github.com/MobileFirstLLC/extension-cli/compare/v0.8.0-alpha.3...v0.8.0) (2019-12-19)\n\n## [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)\n\n## [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)\n\n* try fix ci publish ([23ae7b3](https://github.com/MobileFirstLLC/extension-cli/commit/23ae7b33e2fc361fffafec2e9ec79693d212d292))\n\n## [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)\n\n* xt-build -> use webpack with scripts ([063cecc](https://github.com/MobileFirstLLC/extension-cli/commit/063cecc585d5657c005af7342eeb04466930ea09))\n* xt-build locales and manifest tasks ([f98872e](https://github.com/MobileFirstLLC/extension-cli/commit/f98872e0f39cd7095076e4f97897de6857fe3306))\n* xt-build webpack scripts ([13e2297](https://github.com/MobileFirstLLC/extension-cli/commit/13e2297b912b067eaf1ad44e449e83585b3a22ab))\n* xt-test command ([130986f](https://github.com/MobileFirstLLC/extension-cli/commit/130986f3bf58c01d46e14308d2bf080b1b88c6fc))\n\n## 0.8.0-alpha.0 (2019-12-17)\n\n* jsdoc config ([b021a0e](https://github.com/MobileFirstLLC/extension-cli/commit/b021a0e1424d4f5e226dea1d1940dee9af3fdbb8))\n* migrate build script to gulp 4 ([237c44f](https://github.com/MobileFirstLLC/extension-cli/commit/237c44f4107a66c7ffc7fd9917e6343d28f90021))\n* migrate xt-test; update docs ([2ae3e78](https://github.com/MobileFirstLLC/extension-cli/commit/2ae3e78128b5db0dad81b1f589fb3e6add732fe8))\n* xt-clean, xt-docs; update packages; add build configuration, image assets, and source docs ([85eead2](https://github.com/MobileFirstLLC/extension-cli/commit/85eead28704cf684b520409f192fe7073623f2fa))\n* xt-sync; update readme ([ddcc8aa](https://github.com/MobileFirstLLC/extension-cli/commit/ddcc8aaab252e7e184061576fb6683f3ed095343))\n\n<a name=\"0.7.11\"></a>\n## 0.7.11 (2018-06-25)\n\n<a name=\"0.7.10\"></a>\n## 0.7.10 (2018-06-25)\n\n<a name=\"0.7.9\"></a>\n## 0.7.9 (2018-06-10)\n\n<a name=\"0.7.8\"></a>\n## 0.7.8 (2018-06-06)\n\n* **xt-docs:** update docs output path \n* **xt-sync:** update ci configs \n\n<a name=\"0.7.7\"></a>\n## 0.7.7 (2018-06-04)\n\n* **xt-test:** revert \n\n<a name=\"0.7.5\"></a>\n## 0.7.5 (2018-05-29)\n\n<a name=\"0.7.6\"></a>\n## 0.7.6 (2018-06-04)\n\n* **xt-test:** expose jsdom \n\n<a name=\"0.7.5\"></a>\n## 0.7.5 (2018-05-29)\n\n<a name=\"0.7.4\"></a>\n## 0.7.4 (2018-05-25)\n\n* **xt-test:** added chai-as-promised\n\n<a name=\"0.7.3\"></a>\n## 0.7.3 (2018-05-24)\n\n<a name=\"0.7.2\"></a>\n## 0.7.2 (2018-05-22)\n\n* **xt-test:** added move event simulators\n\n<a name=\"0.7.1\"></a>\n## 0.7.1  (2018-05-22)\n\n<a name=\"0.7.0\"></a>\n# 0.7.0 (2018-05-21)\n\n* **xt-test:** add test runner and env setup \n\n<a name=\"0.6.3\"></a>\n## 0.6.3 (2018-05-21)\n\n* **xt-build:** added spinner and logging on watch \n\n<a name=\"0.6.2\"></a>\n## 0.6.2 (2018-05-21)\n\n* **xt-sync:** fix path \n\n<a name=\"0.6.1\"></a>\n## 0.6.1 (2018-05-21)\n\n* **xt-sync:** update commands \n\n<a name=\"0.6.0\"></a>\n# 0.6.0 (2018-05-21)\n\n* **xt-sync:** consolidate file updates into one command; remove xt-ci, xt-ignore, and xt-lint\n\n<a name=\"0.5.0\"></a>\n# 0.5.0 (2018-05-21)\n\n* **ci:** fix ci command\n* **xt-clean:** added clean option \n\n<a name=\"0.4.0\"></a>\n# 0.4.0 (2018-05-20)\n\n* **build:** remove initial build script console output  \n* **build:** add option to have configs in package.json \n* **docs:** simplyfy docs command and enable config in package.json  \n* **xt-cli:** update ci settings \n\n<a name=\"0.3.2\"></a>\n## 0.3.2 (2018-05-19)\n\n* **build:** update build to pipe output, change default configs && path\n\n<a name=\"0.3.1\"></a>\n## 0.3.1 (2018-05-18)\n\n* **build:** fix done callback \n\n<a name=\"0.3.0\"></a>\n# 0.3.0 (2018-05-14)\n\n* **xt-build:** added copyAsIs to build options\n\n<a name=\"0.2.1\"></a>\n## 0.2.1 (2018-05-14)\n\n* **config:** update docs default config \n\n<a name=\"0.2.0\"></a>\n# 0.2.0 (2018-05-14)\n\n* **xt-lint:** create or update eslint settings \n\n<a name=\"0.1.0\"></a>\n# 0.1.0 (2018-05-14)\n\n<a name=\"0.0.7\"></a>\n#### 0.0.7 (2018-05-14)\n\n<a name=\"0.0.6\"></a>\n#### 0.0.6 (2018-05-14)\n\n<a name=\"0.0.5\"></a>\n#### 0.0.5 (2018-05-14)\n\n<a name=\"0.0.4\"></a>\n#### 0.0.4 (2018-05-14)\n\n<a name=\"0.0.3\"></a>\n#### 0.0.3 (2018-05-14)\n\n<a name=\"0.0.2\"></a>\n#### 0.0.2  (2018-05-14)\n\n<a name=\"0.0.1\"></a>\n#### 0.0.1 (2018-05-14)\n"
  },
  {
    "path": ".github/code_of_conduct.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at hello@mobilefirst.me. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": ".github/contributing.md",
    "content": "Let's add guidelines here over time\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ master ]\n  schedule:\n    # Run every day at 2200 EST\n    - cron: '0 2 * * *'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'javascript' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more:\n        # 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\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: NPM Publish\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v3\n        with:\n          node-version: '14'\n\n      - name: Tag name\n        run: echo \"TAG=${GITHUB_REF#refs/*/}\" >> $GITHUB_ENV\n      - run: npm ci\n      - run: npm run test\n\n      - name: NPM Publish\n        uses: JS-DevTools/npm-publish@v1\n        with:\n          tag: ${{ fromJSON('[\"latest\", \"alpha\"]')[contains(env.TAG, '-')] }}\n          token: ${{ secrets.NPM_TOKEN }}\n\n      - name: Github Release\n        uses: ncipollo/release-action@v1\n        if: \"!contains(env.TAG, '-')\"\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea/\nnode_modules/\nsite/\ntutorial-env/\n.nyc_output\nenv\nicon.png\nvenv\n"
  },
  {
    "path": ".npmignore",
    "content": ".idea/\n.gitignore\n.docs.json\n.travis.yml\n.github/\nrequirements.txt\nnode_modules/\nCHANGELOG.md\n*.jpg\nassets/\ndocs/\nguide/\nsite/\ntutorial-env/\n.eslintrc\ninch.json\nmkdocs.yml\n.npmrc\n.nyc_output\nenv/\ntest/\nexamples/\nvenv/\nicon.png\n"
  },
  {
    "path": ".travis.yml",
    "content": "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.3\n      script:\n        - npm install\n        - npm run test:travis || travis_terminate 1\n\n    - language: python\n      python: \"3.9\"\n      script:\n        - pip install -r requirements.txt\n        - mkdocs build\n      deploy:\n        - provider: pages\n          skip_cleanup: true\n          github_token: $GITHUB_TOKEN\n          keep_history: true\n          local_dir: site\n          on:\n            branch: main\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019-2021 Mobile First LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Extension CLI\n\n[![npm](https://img.shields.io/npm/v/extension-cli?style=flat-square)](https://www.npmjs.com/package/extension-cli)\n[![travis](https://img.shields.io/travis/mobilefirstllc/extension-cli?style=flat-square)](https://travis-ci.com/github/MobileFirstLLC/extension-cli)\n[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/MobileFirstLLC/extension-cli?style=flat-square)](https://codeclimate.com/github/MobileFirstLLC/extension-cli/maintainability)\n[![Last commit](https://img.shields.io/github/last-commit/mobilefirstllc/extension-cli?style=flat-square)](https://github.com/MobileFirstLLC/extension-cli/commits/master)\n[![npm](https://img.shields.io/npm/dt/extension-cli?style=flat-square)](https://www.npmjs.com/package/extension-cli)\n<!-- [![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 -->\n\n**Extension CLI is a command-line application that facilitates chromium&#8727;-based web extension development by providing\na systematic way to build, test and document extension projects. It handles the project setup and builds and lets you focus \non the extension you are creating.**\n\n* * *\n\n## Features\n\n-  🖥️ &nbsp; **Javascript Bundling** — Compiles, bundles and minifies javascript files<br/>\n\n-  🎨 &nbsp; **CSS Bundling** — Compiles, bundles, and minifies CSS and [SASS](https://sass-lang.com/guide) files <br/>\n\n-  💄 &nbsp; **Linting** — lint JavaScript using [ESLint](https://eslint.org/) <br/>\n\n-  📦 &nbsp; **ZIP Generation** — Generates a `.zip` file for publishing <br/>\n\n-  📝 &nbsp; **Document Source Code** — Generates code documentation using [JSDoc](https://jsdoc.app/about-getting-started.html) <br/>\n\n-  ⚗️ &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/>\n\n-  ⚔️ &nbsp; **Cross-Browser Compatibility** - develop extensions for Chrome, Edge, Firefox, Opera and Brave. <br/>\n\n![feature image](https://raw.githubusercontent.com/MobileFirstLLC/extension-cli/master/.github/feature.png)\n\n## Getting Started\n\n**Note:** Using this CLI assumes you have Node.js installed. If you do not, you can [install it here](https://nodejs.org/en/download/).\n\n##### Create new extension project\n\n```text\nnpx extension-cli\n```\n\n##### Add to an existing project\n\n```text\nnpm install extension-cli\n```\n\n### Commands Reference\n\nCommand | Description\n--- | ---\n**xt-build** | Run builds; env flags: `-e prod` and `-e dev`\n**xt-test**| Run unit tests\n**xt-docs**| Generate docs\n**xt-sync**| Update project config files to match the latest defaults supplied by this CLI\n**xt-clean** | Remove automatically generated files\n\n* * *\n\n## Read the Docs\n\n<img align=\"left\" width=\"64\" src=\"https://raw.githubusercontent.com/MobileFirstLLC/extension-cli/master/guide/assets/images/guide.svg\" alt=\"\" /> &nbsp; \n<br/>&nbsp; &nbsp;<strong><a href=\"https://oss.mobilefirst.me/extension-cli/\">User Guide →</a></strong><br/><br/>\n\n### CLI Developer Guide\n\nIf you are interested in extending this project or forking **[see this guide &rarr;](https://oss.mobilefirst.me/extension-cli/13-cli-development/)**\n\n* * *\n\n## Motivation\n\nAfter developing multiple browser extensions, it became clear that there are several steps in the development process that stay the same between every project. \n\nInstead 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. \n\nThis 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!).\n\n* * *\n\n**Issues & Feature Requests:** [Submit on Github](https://github.com/MobileFirstLLC/extension-cli/issues/new/choose)\n\n**Maker:** made by <a href=\"https://github.com/MobileFirstLLC/extension-cli/graphs/contributors\" target=\"_blank\" rel=\"noreferrer noopener\">developers</a> behind several popular extensions!\n\n**License:** [MIT](https://github.com/MobileFirstLLC/extension-cli/blob/master/LICENSE)\n"
  },
  {
    "path": "cli/gulpfile.js",
    "content": "const gulp = require('gulp');\nconst del = require('del');\nconst chalk = require('chalk');\nconst gulpChange = require('gulp-change');\nconst paths = require('../config/build.json');\nconst plugins = require('gulp-load-plugins')();\nconst webpack = require('webpack-stream');\nconst sass = require('gulp-sass')(require('sass'));\nconst Utilities = require('./utilities').Utilities;\nconst argv = require('yargs').argv;\nconst {prod: isProd, firefox: isFirefox, pkg: pkgPath, config} = argv;\n\n/** helper method to ensure array type */\nconst ensureArray = path => Array.isArray(path) ? path : [path];\n\n/** read project package.json **/\nconst pkg = Utilities.readJSON(pkgPath);\n\n/** read project's config file, if specified **/\nlet customPaths = null;\n\nif (Utilities.fileExists(config)) {\n    // if config is a file\n    customPaths = Utilities.readJSON(config);\n} else if (pkg.xtbuild !== undefined) {\n    // otherwise config should be specified in package.json\n    customPaths = pkg.xtbuild;\n}\n\n/** replace default configs with project-level configs **/\nif (customPaths) {\n    for (let key in customPaths) {\n        if (customPaths.hasOwnProperty(key)) {\n            paths[key] = customPaths[key];\n        }\n    }\n}\n\nconst clean = () => del([paths.dist + '/*']);\n\nconst script = ({src, name, mode}, done = _ => true) => {\n\n    const webpackOptions = {\n        // use mode if specified explicitly; otherwise choose by --env\n        mode: mode || (isProd ? 'production' : 'development'),\n        // match sourcemap name with configured js file name\n        output: {filename: `${name}.js`},\n        // use source map with dev builds only\n        devtool: isProd ? undefined : 'cheap-source-map'\n    };\n\n    return gulp.src(src)\n        .pipe(webpack(webpackOptions))\n        .on('error', (err) => {\n            console.log(err.toString());\n            this.emit('end');\n        })\n        .pipe(plugins.rename(path => {\n            path.dirname = '';\n            path.basename = name;\n        }))\n        .pipe(gulp.dest(paths.dist))\n        .on('end', done);\n};\n\nconst style = ({src, name}, done = _ => true) => {\n    return gulp.src(src)\n        // convert to css\n        .pipe(sass().on('error', sass.logError))\n        // concatenate multiple src files\n        .pipe(plugins.concat(`${name}.css`))\n        // minify\n        .pipe(plugins.cleanCss())\n        // rename to user-specified name\n        .pipe(plugins.rename((path) => {\n            path.dirname = '';\n            path.basename = name;\n        }))\n        .pipe(gulp.dest(paths.dist))\n        .on('end', done);\n};\n\nconst copy = (src, done = _ => true) => {\n    // nested copy specified using glob pattern\n    if (src.endsWith('*')) {\n        return gulp.src(src, {base: 'src'})\n            .pipe(gulp.dest(paths.dist))\n            .on('end', done);\n    }\n\n    // copy single file or directory\n    return gulp.src(src)\n        .pipe(plugins.rename(path => {\n            path.dirname = '';\n        }))\n        .pipe(gulp.dest(paths.dist))\n        .on('end', done);\n};\n\nconst locale = (language, done = _ => true) => {\n    return gulp.src(paths.locales_dir + language + '/**/*.json')\n        .pipe(plugins.mergeJson({fileName: 'messages.json'}))\n        .pipe(plugins.jsonminify())\n        .pipe(gulp.dest(paths.dist + '/_locales/' + language))\n        .on('end', done);\n};\n\nconst copyManifest = done => {\n\n    const {version} = pkg;\n\n    const performChange = (content) => {\n        let mft = JSON.parse(content);\n\n        mft.version = version; // use version from package\n\n        if (isFirefox && mft.firefox) mft = {...mft, ...mft.firefox};\n        else if (!isFirefox && mft.chrome) mft = {...mft, ...mft.chrome};\n        delete mft.chrome;\n        delete mft.firefox;\n\n        return JSON.stringify(mft);\n    };\n\n    return gulp.src(paths.manifest)\n        .pipe(gulpChange(performChange))\n        .pipe(plugins.jsonminify())\n        .pipe(plugins.rename(path => {\n            path.dirname = '';\n            path.basename = 'manifest';\n            path.extname = '.json';\n        }))\n        .pipe(gulp.dest(paths.dist))\n        .on('end', done);\n};\n\nconst copyAssets = done => {\n    return gulp.src(paths.assets)\n        .pipe(gulp.dest(paths.dist + '/assets'))\n        .on('end', done);\n};\n\nconst buildHtml = done => {\n    return gulp.src(paths.html)\n        .pipe(plugins.htmlmin({collapseWhitespace: true}))\n        .pipe(plugins.rename(path => {\n            path.dirname = '';\n        }))\n        .pipe(gulp.dest(paths.dist))\n        .on('end', done);\n};\n\nconst customCommands = done => {\n    if (!paths.commands || !paths.commands.length) {\n        return done();\n    }\n\n    return require('child_process')\n        .exec(paths.commands, done);\n};\n\nconst release = done => {\n    if (!isProd) return done();\n\n    return gulp.src(paths.dist + '/**/*')\n        .pipe(plugins.zip(`${paths.release_name || 'release'}.zip`))\n        .pipe(gulp.dest(paths.releases))\n        .on('end', done);\n};\n\nconst dynamicFunc = (action, name) => {\n    const f = action;\n\n    Object.defineProperty(f, 'name', {\n        value: name,\n        writable: false\n    });\n    return f;\n};\n\nconst scripts = paths.js_bundles.map(obj =>\n    dynamicFunc(_ => script(obj), `${obj.name}.js`));\n\nconst styles = paths.scss_bundles.map(obj =>\n    dynamicFunc(_ => style(obj), `${obj.name}.css`));\n\nconst locales = paths.locales_list.map(lang =>\n    dynamicFunc(_ => locale(lang), `locale ${lang}`));\n\nconst copies = ensureArray(paths.copyAsIs).map(obj =>\n    dynamicFunc(_ => copy(obj), `copy ${obj}`));\n\nconst watch = () => {\n    console.log(chalk.bold.yellow('watching...'));\n    if (scripts.length) {\n        gulp.watch(ensureArray(paths.js), gulp.parallel(...scripts));\n    }\n    if (styles.length) {\n        gulp.watch(ensureArray(paths.scss), gulp.parallel(...styles));\n    }\n    if (copies.length) {\n        gulp.watch(ensureArray(paths.copyAsIs), gulp.parallel(...copies));\n    }\n    if (paths.locales_list.length) {\n        gulp.watch(paths.locales_dir + '**/*.json', gulp.parallel(...locales));\n    }\n    gulp.watch(paths.manifest, copyManifest);\n    gulp.watch(ensureArray(paths.html), buildHtml);\n    gulp.watch(ensureArray(paths.assets), copyAssets);\n    // gulp.watch(paths.commands_watch_path || '', customCommands);\n};\n\nconst build = gulp.series(\n    clean,\n    gulp.parallel(\n        ...scripts,\n        ...styles,\n        ...copies,\n        ...locales,\n        copyManifest,\n        copyAssets,\n        buildHtml\n    ),\n    customCommands,\n    release\n);\n\n/*\n * Define default task that can be called by just running `gulp` from cli\n */\nexports.default = build;\n\n/*\n * If watch flag is defined, run build and keep watching\n */\nexports.watch = gulp.series(build, watch);\n"
  },
  {
    "path": "cli/rootsuite.js",
    "content": "/**\n * @description\n * This rootsuite sets up unit testing environment\n */\n\nconst sinon = require('sinon');\nconst chrome = require('sinon-chrome');\nconst chai = require('chai');\nconst argv = require('yargs').argv;\nconst texts = require('./texts').xtTest;\nconst enableWatch = argv.watch;\n\n/**\n * Create sinon sandbox\n *\n * Sandboxes removes the need to keep track of\n * every fake created, which greatly simplifies cleanup.\n *\n * @see {@link https://sinonjs.org/releases/latest/sandbox/}\n */\nconst sandbox = sinon.createSandbox();\n\n/**\n * Setup global DOM\n */\nglobal.jsdom = require('jsdom-global')();\n\n/**\n * Before running any tests -\n * setup the test environment\n */\nbefore(function () {\n    process.env.NODE_ENV = 'test';\n    global.sinon = sinon;\n    global.chai = chai;\n    global.expect = chai.expect;\n    global.sandbox = sandbox;\n    window.sandbox = sandbox;\n    global.chrome = chrome;\n    window.chrome = chrome;\n\n    // output list of namespaces that\n    // are available in test environment\n    console.log(texts.onRootSetup(\n        'window,chrome,chai,expect,sandbox(sinon)'\n            .split(',')));\n});\n\n/**\n * After each test -\n * reset chrome and sandbox\n */\nafterEach(function () {\n    chrome.flush();\n    sandbox.restore();\n});\n\n/**\n * After all tests -\n * Clean up everything that was initially set up\n */\nafter(function () {\n    // important! do not clean when running in watch mode\n    if (enableWatch) return;\n\n    delete global.jsdom;\n    delete global.sinon;\n    delete global.chrome;\n    delete global.chai;\n    delete global.expect;\n    delete global.sandbox;\n    delete window.sandbox;\n    delete window.chrome;\n    delete global.mouseEvent;\n    delete global.dispatchEvent;\n});\n\n/**\n * Enable mouse events globally during unit testing\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent}\n *\n * @param {String} type - event type\n * @param {Object} props - optional properties\n *  @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent}\n * @return {Event} - the event\n */\nglobal.mouseEvent = function (type, props) {\n    return new MouseEvent(type, {...props});\n};\n\n/**\n * Enable dispatching an event on some DOM element during unit testing\n *\n * @param {EventTarget} target - element on which to dispatch event\n * @param {Event} event - the event to dispatch\n * @return {Event} - the event\n */\nglobal.dispatchEvent = function (target, event) {\n    if (target.dispatchEvent) {\n        target.dispatchEvent(event);\n    } else if (target.fireEvent) {\n        target.fireEvent('on' + event.type, event);\n    }\n    return event;\n};\n"
  },
  {
    "path": "cli/texts.js",
    "content": "/**\n * @description\n * This module specifies terminal/console output for all commands\n */\n\nconst chalk = require('chalk');\n\n/**\n * xt-create outputs\n */\nexports.xtCreate = {\n\n    prompts: {\n        name: {\n            type: 'text',\n            name: 'name',\n            message: 'What do you want to call the extension?',\n            validate: value =>\n                // basic null check is sufficient\n                !value || value.trim().length < 1 ?\n                    'You must choose a name' : true\n        },\n        optional: [\n            {\n                type: 'text',\n                name: 'description',\n                message: 'What does it do?'\n            }, {\n                type: 'text',\n                name: 'homepage',\n                message: 'Homepage URL (leave blank if you do not have one yet)'\n            }\n        ]\n    },\n\n    dirError: (dirname) => (\n        chalk.bold.red(`Cannot create directory: ${dirname}.\\n` +\n            'It already exists, is not empty, or is not writable.')\n    ),\n\n    start: (dirname, name) => (\n        `Creating extension ${name} in directory ${chalk.bold.green(dirname)}.`\n    ),\n\n    install: 'Installing packages - this may take a while...',\n\n    installError: (\n        chalk.bold.yellow('ATTN! ') +\n        'npm install did not complete successfully\\n' +\n        'You may have to run npm install again in project directory.'\n    ),\n\n    success: (dir) => (\n        `${chalk.bold.green('DONE! ')}Your extension starter is ready.\\n` +\n        `${chalk.bold.green('What Next: ')} Open ${dir} in your favorite web IDE`\n    )\n\n};\n\n/**\n * xt-sync outputs\n */\nexports.xtSync = {\n\n    argGitlab: 'Gitlab CI config',\n\n    argTravis: 'Travis CI config',\n\n    argLint: 'eslint config',\n\n    argGitIgnore: 'gitignore',\n\n    argActions: 'Github actions workflow config',\n\n    instructions: 'choose the files you want to sync:',\n\n    updateSuccess: (what) => chalk.bold.green(`✓ updated ${what}`)\n};\n\n/**\n * xt-docs outputs\n */\nexports.xtDocs = {\n\n    argWatch: 'enable watch',\n\n    watching: 'watching...',\n\n    success: chalk.bold.green('Docs done!'),\n\n    failure: chalk.bold.red('Docs failed'),\n\n    configArg: 'Path to config file; defaults to `.xtdocs.json` in project root, or `xtdocs` in package.json'\n};\n\n/**\n * xt-clean outputs\n */\nexports.xtClean = {\n\n    argModules: 'Clean node_modules directory',\n\n    argIdea: 'Clean .idea/ directory',\n\n    argVS: 'Clean .vscode/ directory',\n\n    onConfigError: (path) => chalk.yellow(`File does not exist: ${path}`),\n\n    onCleanFile: (path) => `- ${path}`,\n\n    onCleanError: (e, line) => chalk.bold.red(e) + ' ' + line,\n\n    result: count => chalk.bold[count === 0 ? 'yellow' : 'green'](`Done. Cleaned: ${count}`)\n};\n\n/**\n * xt-test outputs\n */\nexports.xtTest = {\n\n    argPattern: 'test file/directory match pattern',\n\n    argCoverage: 'deprecated! see docs on how to report coverage: https://bit.ly/3j5Zrn2',\n\n    argWatch: 'enable watch',\n\n    onRootSetup: (envList) => (\n        ['ENV: ',\n            envList.map(entry => chalk.bold.green(` ${entry} `))\n        ].join(' ') + '\\n'\n    )\n};\n\n/**\n * xt-build outputs\n */\nexports.xtBuild = {\n\n    envArg: 'Build environment',\n\n    watchArg: 'Enable watch',\n\n    platformArg: 'Platform',\n\n    configFileArg: 'Path to configuration file, default: .xtbuild.json in root, or xtbuild in package.json)',\n\n    onBuildSuccess: _ => chalk.bold.green('Build done!'),\n\n    onBuildError: _ => chalk.bold.red('Build failed')\n};\n"
  },
  {
    "path": "cli/utilities.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\n/**\n * @class\n * @classdesc Utility class provides helper methods\n * for performing commonly recurring operations such\n * as IO; and reading, writing, merging objects.\n *\n * When performing these operations, it is preferable\n * to use these utility methods to ensure same behavior\n * for these operations everywhere, and to establish one\n * place of change, should these operations change in the\n * future.\n */\nclass Utilities {\n\n    /**\n     * Given some string value, generate another string\n     * from it, such that the generated string can be\n     * used as a directory name. This function will\n     * normalize the input and remove special characters.\n     *\n     * @param name - suggested directory name\n     * @param defaultName - value to return if\n     *      no characters in name can be used\n     * @return {string} directory name\n     */\n    generateDirectoryName(name, defaultName = 'extension-1') {\n        return ((name || '').toLowerCase()\n            .replace(/[\\W_]+/g, ' ')\n            .replace(/ /g, '-')\n            .replace(/-$/, '')) || defaultName;\n    };\n\n    /**\n     * Replace string interpolation expressions in\n     * a content string.\n     *\n     * @param content - string with placeholder values,\n     *  @example \"sample ${key}\"\n     * @param vars - dictionary of <K,V> pairs\n     *  @example { key : \"value\" }\n     * @return {string} -\n     *  @example \"sample value\"\n     */\n    replaceVars(content, vars) {\n        let temp = content.toString();\n\n        Object.keys(vars).map(key => {\n            const re = new RegExp('\\\\${' + key + '}', 'gi');\n\n            temp = temp.replace(re, vars[key]);\n            return true;\n        });\n        return temp;\n    };\n\n    /**\n     * a union of two objects, child and parent,\n     * with child values overriding all shared keys.\n     *\n     * This operation happens in place and\n     * result will be stored in parent object.\n     *\n     * @example\n     * let child = {a:1, b:5, c:{x:1}}\n     * let parent = {b:8, c:{y:9}}\n\n     * // expected result (parent):\n     * // {a:1, b:5, c:{x:1, y:9}}\n     *\n     * @param child - source object\n     * @param parent - parent object\n     */\n    keyReplace(child, parent) {\n        for (let key in child) {\n            if (!child.hasOwnProperty(key)) continue;\n            if (Array.isArray(child[key])) {\n                parent[key] = child[key];\n                continue;\n            }\n            if (typeof child[key] !== 'object') {\n                parent[key] = child[key];\n                continue;\n            }\n            if (!parent[key]) parent[key] = {};\n            this.keyReplace(child[key], parent[key]);\n        }\n    }\n\n    /**\n     * Given defaultConfig and project-level config\n     * replace default configs with project-specific\n     * configuration.\n     *\n     * Any property that is specified at project level\n     * but not in default config, will be added to\n     * the result configuration.\n     *\n     * Any property that exists in default config that\n     * is not overwritten at project level, will hold\n     * default value in the result configuration.\n     *\n     * @param defaultConfig\n     * @param projectConfig\n     * @return {Object}\n     */\n    iterateConfigs(defaultConfig, projectConfig) {\n        if (!projectConfig) return defaultConfig;\n        let temp = Object.assign({}, defaultConfig);\n\n        for (let k in projectConfig) {\n            if (!projectConfig.hasOwnProperty(k)) continue;\n            if (typeof projectConfig[k] === 'object') {\n                if (!temp[k]) temp[k] = {};\n                this.keyReplace(projectConfig[k], temp[k]);\n            } else {\n                temp[k] = projectConfig[k];\n            }\n        }\n        return temp;\n    }\n\n    /**\n     * Recursively copy a directory and all its files to a new location\n     * @param from - path to current location\n     * @param to - target location path\n     */\n    copyFolderSync(from, to) {\n        try {\n            fs.mkdirSync(to);\n        } catch (e) {\n        }\n        fs.readdirSync(from).forEach((element) => {\n            const stat = fs.lstatSync(path.join(from, element));\n\n            if (stat.isFile()) {\n                fs.copyFileSync(path.join(from, element), path.join(to, element));\n            } else if (stat.isSymbolicLink()) {\n                fs.symlinkSync(fs.readlinkSync(path.join(from, element)), path.join(to, element));\n            } else if (stat.isDirectory()) {\n                this.copyFolderSync(path.join(from, element), path.join(to, element));\n            }\n        });\n    }\n\n    /**\n     * Copy single file from one location to another (synchronous).\n     *\n     * @param from - source file path\n     * @param to - target file path\n     */\n    copyFile(from, to) {\n        fs.createReadStream(from).pipe(fs.createWriteStream(to));\n    }\n\n    /**\n     * Read utf-8 encoded file (synchronous)\n     * @param filePath - path to file\n     * @return {string} - file contents\n     */\n    readFile(filePath) {\n        return fs.readFileSync(filePath, 'utf8');\n    }\n\n    /**\n     * Write file to disk (syncronous)\n     * @param filePath - path to file\n     * @param content - file contents\n     */\n    writeFile(filePath, content) {\n        fs.writeFileSync(filePath, content);\n    }\n\n    /**\n     * Check if file exists\n     * @param filePath - path to file\n     * @return {boolean} - true/false\n     */\n    fileExists(filePath) {\n        return fs.existsSync(filePath);\n    }\n\n    /**\n     * Create empty directory.\n     *\n     * @param dirPath - path to directory\n     * @return {boolean} - true if exists and empty (should be\n     *   writable) and false otherwise\n     */\n    createDir(dirPath) {\n        // doesn't exist\n        if (!fs.existsSync(dirPath)) {\n            fs.mkdirSync(dirPath, { recursive: true });\n            return true;\n        }\n        // check if empty\n        return !fs.readdirSync(dirPath).length;\n    };\n\n    /**\n     * Read JSON file\n     * @param filePath - path to file\n     * @return {any} - Object\n     */\n    readJSON(filePath) {\n        return JSON.parse(this.readFile(filePath));\n    }\n\n    /**\n     * Reads text file then replaces all variable placeholders, e.g. ${var1}\n     * @param path - path to file\n     * @param vars - variables Object <K, V>\n     * @return {string} - file contents with all matched variables replaced\n     */\n    readAndReplaceTextFile(path, vars) {\n        return this.replaceVars(this.readFile(path), vars);\n    }\n\n    /**\n     * Reads JSON file then replaces all variable placeholders, e.g. ${var1}\n     * @param path - path to file\n     * @param vars - variables Object <K, V>\n     * @return {string} - file contents with all matched variables replaced\n     */\n    readAndReplaceJSONFile(path, vars) {\n        return JSON.stringify(JSON.parse(this.readAndReplaceTextFile(path, vars)), null, 4);\n    }\n}\n\nexports.Utilities = new Utilities();\n"
  },
  {
    "path": "cli/xt-build.js",
    "content": "#!/usr/bin/env node\n\n/**\n * @name xt-build\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-build --env {prod|dev} --platform {chrome|firefox} [--config filename] [--watch]\n * ```\n *\n * Build command generates a dist/ directory that can be\n * debugged in the browser. When called with production env flag, `-e prod`,\n * this command will minify and compile a `release.zip` file that can be\n * uploaded to extension marketplace for distribution.\n */\n\nconst util = require('util');\nconst path = require('path');\nconst program = require('commander');\nconst Spinner = require('cli-spinner').Spinner;\nconst exec = require('child_process').exec;\nconst pkg = require('../package.json');\nconst env = {prod: 'prod', dev: 'dev'};\nconst platform = {chrome: 'chrome', firefox: 'firefox'};\nconst texts = require('./texts').xtBuild;\nconst gulpfile = path.resolve(__dirname, './gulpfile.js');\n\nprogram\n    .version(pkg.version)\n    .option('-e --env <env>', texts.envArg, /^(dev|prod)$/i, env.prod)\n    .option('-p --platform <platform>', texts.platformArg, /^(chrome|firefox)$/i, platform.chrome)\n    .option('-c --config <config>', texts.configFileArg, /^(.*)$/i)\n    .option('-w --watch', texts.watchArg)\n    .parse(process.argv);\n\nconst {watch, env: programEnv, config, platform: platformEnv} = program.opts();\nconst spinner = new Spinner(' %s ');\n\nspinner.start();\n\nconst proc = exec([\n\n    // run either watch or default\n    watch ? 'gulp watch' : 'gulp',\n\n    // path to gulpfile (in current dir)\n    util.format('--gulpfile \"%s\"', gulpfile),\n\n    // path to build configuration file\n    util.format('--config \"%s\"', path.resolve(process.cwd(), config || './.xtbuild.json')),\n\n    // path to project's package.json\n    util.format('--pkg', path.resolve(process.cwd(), './package.json')),\n\n    // explicitly tell gulp to use cwd (necessary)\n    util.format('--cwd', path.resolve(process.cwd())),\n\n    // ENV is either \"--dev\" or \"--prod\"\n    util.format('--%s', programEnv),\n\n    // target platform is \"--chrome\" or \"--firefox\"\n    util.format('--%s', platformEnv),\n\n    // use colors in terminal output\n    '--colors'\n\n].join(' '));\n\nproc.stdout.on('data', (data) => {\n    if (data && data.indexOf('Using gulpfile') === 0) return;\n    spinner.stop(true);\n    process.stdout.write(data.toString());\n});\n\nproc.stderr.on('data', (data) => {\n    spinner.stop(true);\n    process.stdout.write(data.toString());\n});\n\nproc.on('exit', (err) => {\n    spinner.stop(true);\n    console.log(!err ?\n        texts.onBuildSuccess() :\n        texts.onBuildError());\n});\n"
  },
  {
    "path": "cli/xt-clean.js",
    "content": "#!/usr/bin/env node\n\n/**\n * @name xt-clean\n * @module\n * @public\n *\n * @description\n *\n *```text\n * xt-clean [--modules] [--idea] [--vscode]\n * ```\n *\n * Clean operation iterates over files and directories listed in the\n * project `.gitignore` file, and removes all ignored files and\n * directories, except `node_modules`, `.idea/`, and `.vscode`. To remove these\n * directories, you must explicitly pass a flag to delete each one of them.\n */\n\nconst fs = require('fs');\nconst del = require('del');\nconst path = require('path');\nconst readline = require('readline');\nconst program = require('commander');\nconst pkg = require('../package.json');\nconst ignore = path.join(process.cwd(), '.gitignore');\nconst Utilities = require('./utilities').Utilities;\nconst texts = require('./texts').xtClean;\n\nlet counter = 0;\n\nprogram\n    .version(pkg.version)\n    .option('-m --modules', texts.argModules)\n    .option('-i --idea', texts.argIdea)\n    .option('-v --vscode', texts.argVS)\n    .parse(process.argv);\n\nif (!Utilities.fileExists(ignore)) {\n    console.log(texts.onConfigError(ignore));\n    process.exit(0);\n}\n\nconst {modules, idea, vscode} = program.opts();\n\nreadline.createInterface({input: fs.createReadStream(ignore)})\n    .on('line', function (line) {\n\n        // never clean these\n        if (line.trim().indexOf('#') === 0 ||\n            line.trim().indexOf('.env') === 0 ||\n            !(line || '').trim().length) {\n            return false;\n        }\n\n        // clean these only if flagged\n        if ((line.indexOf('.idea') > -1 && !idea) ||\n            (line.indexOf('.vscode') > -1 && !vscode) ||\n            (line.indexOf('node_modules') > -1 && !modules)) {\n            return false;\n        }\n\n        // otherwise clean if exists\n        const basePath = path.join(process.cwd(), line);\n\n        if (fs.existsSync(basePath)) {\n            try {\n                if (fs.lstatSync(basePath).isDirectory()) {\n                    del.sync(path.join(basePath, '/*'));\n                }\n                if (fs.existsSync(basePath)) {\n                    del.sync(basePath);\n                }\n                console.log(texts.onCleanFile(line));\n                counter++;\n            } catch (e) {\n                console.log(texts.onCleanError(e, line));\n            }\n        }\n        return true;\n    })\n    .on('close', () => {\n        console.log(texts.result(counter));\n        process.exit(0);\n    });\n"
  },
  {
    "path": "cli/xt-create.js",
    "content": "#!/usr/bin/env node\n\n/**\n * @name extension-cli\n * @module\n * @public\n *\n * @description\n *\n *```text\n * npx extension-cli\n * ```\n *\n * This command will create a new extension project and initial code files.\n * Command takes no arguments; follow prompts on screen.\n */\n\nconst prompts = require('prompts');\nconst path = require('path');\nconst exec = require('child_process').exec;\nconst Spinner = require('cli-spinner').Spinner;\nconst spinner = new Spinner(' %s ');\nconst Utilities = require('./utilities').Utilities;\nconst texts = require('./texts').xtCreate;\nconst createPrompts = texts.prompts;\nconst defaultHomepage = 'http://chrome.google.com/webstore';\nconst initFilesPath = '../config/init/';\n\n/**\n * Run the setup script\n * @private\n */\n(async () => {\n\n    const promptOptions = {onCancel: () => process.exit(0)};\n    const response = await prompts(createPrompts.name, promptOptions);\n    const name = response.name;\n    const dirname = Utilities.generateDirectoryName(name);\n    const dir = path.join(process.cwd(), `/${dirname}`);\n\n    // create project directory\n    const success = Utilities.createDir(dir);\n\n    if (!success) {\n        console.error(texts.dirError(dirname));\n        return process.exit(0);\n    }\n\n    const {description, homepage} = await prompts(createPrompts.optional, promptOptions);\n    const vars = {\n        name, description, safeName: dirname,\n        version: '0.0.1', homepage: homepage || defaultHomepage\n    };\n    const _file = fileName => path.resolve(__dirname, initFilesPath + fileName);\n    const _readtext = path => Utilities.readAndReplaceTextFile(path, vars);\n    const _readjson = path => Utilities.readAndReplaceJSONFile(path, vars);\n\n    console.log(texts.start(dirname, name));\n    spinner.start();\n\n    // SETUP files structure and starter files\n    // initialize extension image assets\n    Utilities.createDir(dir + '/assets');\n    Utilities.createDir(dir + '/assets/img');\n    Utilities.copyFile(_file('icon.svg'), dir + '/assets/icon.svg');\n    Utilities.copyFile(_file('16x16.png'), dir + '/assets/img/16x16.png');\n    Utilities.copyFile(_file('24x24.png'), dir + '/assets/img/24x24.png');\n    Utilities.copyFile(_file('32x32.png'), dir + '/assets/img/32x32.png');\n    Utilities.copyFile(_file('128x128.png'), dir + '/assets/img/128x128.png');\n\n    // setup locales\n    Utilities.createDir(dir + '/assets/locales');\n    Utilities.createDir(dir + '/assets/locales/en');\n    Utilities.writeFile(dir + '/assets/locales/en/messages.json', _readjson(_file('messages.json')));\n\n    // setup source code\n    Utilities.createDir(dir + '/src');\n    Utilities.writeFile(dir + '/src/manifest.json', _readjson(_file('manifest.json')));\n    Utilities.copyFile(_file('background.js'), dir + '/src/index.js');\n\n    // setup test files\n    Utilities.createDir(dir + '/test');\n    Utilities.copyFile(_file('test.js'), dir + '/test/sample.js');\n\n    // create package.json\n    Utilities.writeFile(dir + '/package.json', _readjson(_file('package.json')));\n\n    // create readme\n    Utilities.writeFile(dir + '/README.md', _readtext(_file('intro.md')));\n\n    // add eslint config\n    Utilities.writeFile(dir + '/.eslintrc.json', _readtext(_file('eslint.json')));\n\n    // INSTALL packages\n    spinner.stop(true);\n    console.log(texts.install);\n    spinner.start();\n\n    exec('npm install', {cwd: dir})\n        .on('exit', code => {\n            spinner.stop(true);\n            if (code !== 0) {\n                console.log(texts.installError);\n            }\n            console.log(texts.success(dir));\n            process.exit(0);\n        });\n\n    // this is just to make eslint happy\n    return '';\n})();\n"
  },
  {
    "path": "cli/xt-docs.js",
    "content": "#!/usr/bin/env node\n\n/**\n * @name xt-docs\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-docs [--config filename] [--watch]\n * ```\n *\n * Docs command generates documentation for the project. This command uses\n * jsdocs syntax. See {@link https://jsdoc.app/index.html|About JSDoc} for more details,\n * including {@link https://jsdoc.app/about-configuring-jsdoc.html|configuration options here}.\n * The default template for the guide is JsDoc default template. You can override this template\n * in the project by changing `opts.template` in jsdoc config file.\n *\n * By default, this command will automatically look for configuration in the project `package.json`.\n * - use `\"xtdocs\"` key to define config options in `package.json,\n * - -or- add a separate configuration file `.xtdocs.json` in the project root,\n * - -or- explicitly provide a path to a config file.\n *\n * Use `-c` / `--config` flag to provide path and name of the configuration file.\n */\n\nconst fs = require('fs');\nconst del = require('del');\nconst util = require('util');\nconst path = require('path');\nconst program = require('commander');\nconst pkg = require('../package.json');\nconst exec = require('child_process').exec;\nconst Spinner = require('cli-spinner').Spinner;\nconst Utilities = require('./utilities').Utilities;\nconst texts = require('./texts').xtDocs;\nconst defaultConfig = require('../config/docs.json');\nconst spinner = new Spinner(' %s ');\nconst jsdoc = './node_modules/.bin/jsdoc';\nconst tmpFile = path.join(process.cwd(),\n    './node_modules', pkg.name, 'tmpDocsConfig.json');\n\nprogram\n    .version(pkg.version)\n    .option('-c --config <config>', texts.configArg, /^(.*)$/i)\n    .option('-w --watch', texts.argWatch)\n    .parse(process.argv);\n\nconst {config: configArg, watch} = program.opts();\n\nconst getConfig = (docFileName) => {\n    const fe = Utilities.fileExists(docFileName);\n    const temp = Utilities.readJSON(fe ?\n        docFileName : './package.json');\n\n    return Utilities.iterateConfigs(defaultConfig,\n        fe ? temp : temp.xtdocs);\n};\n\nconst buildDocs = (tmpFile, config, callback) => {\n    spinner.start();\n    Utilities.writeFile(tmpFile, config);\n\n    const proc = exec(util.format('\"%s\" -c %s', jsdoc, tmpFile));\n\n    proc.stdout.on('data', (data) => {\n        process.stdout.write(data.toString());\n    });\n    proc.stderr.on('data', (data) => {\n        process.stderr.write(data.toString());\n    });\n    proc.on('exit', err => {\n        del.sync(tmpFile);\n        spinner.stop(true);\n        console.log(err ? texts.failure : texts.success);\n        if (callback) callback();\n    });\n};\n\nconst startWatch = (tmpFile, watchPaths, configStr) => {\n    watchPaths.map(fileOrDir => {\n        fs.watch(path.join(process.cwd(), fileOrDir), {\n                persistent: true, recursive: true\n            },\n            (curr, prev) => {\n                // if spinning it is already running\n                if (!spinner.isSpinning()) {\n                    buildDocs(tmpFile, configStr, false);\n                }\n            });\n    });\n    console.log(texts.watching);\n};\n\nconst config = getConfig(configArg || '.xtdocs.json');\nconst configString = JSON.stringify(config);\nconst watchPaths = config.source.include.concat(\n    config.opts.tutorials ? [config.opts.tutorials] : []);\n\nbuildDocs(tmpFile, configString, _ => watch ?\n    startWatch(tmpFile, watchPaths, configString) :\n    process.exit(0));\n"
  },
  {
    "path": "cli/xt-sync.js",
    "content": "#!/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 * The purpose of this command is to upgrade configuration files of\n * a stale project to latest version, where this CLI tool will provide\n * updated project configuration files. If the config files have been\n * modified heavily for the project, it is not advisable to upgrade them\n * in this manner. Instead you should upgrade such configs manually.\n */\n\nconst path = require('path');\nconst prompts = require('prompts');\nconst program = require('commander');\nconst pkg = require('../package.json');\nconst texts = require('./texts').xtSync;\nconst Utilities = require('./utilities').Utilities;\n\n// list available options\nconst files = {\n    actions: {title: texts.argActions, path: '../config/actions.yml', out: 'build.yml', dir: '.github/workflows'},\n    gitlab: {title: texts.argGitlab, path: '../config/gitlab.yml', out: '.gitlab-ci.yml'},\n    travis: {title: texts.argTravis, path: '../config/travis.yml', out: '.travis.yml'},\n    eslint: {title: texts.argLint, path: '../config/init/eslint.json', out: '.eslintrc.json'},\n    gitignore: {title: texts.gitignore, path: '../config/ignore', out: '.gitignore'}\n};\n\n// generate the options to display to user\nconst options = [{\n    type: 'multiselect',\n    name: 'options',\n    message: texts.instructions,\n    choices: Object.entries(files).map(\n        ([key, {title}]) => ({title, value: key}))\n}];\n\nprogram\n    .name('xt-sync')\n    .option('-a --all', 'deprecated: call xt-sync without flags')\n    .version(pkg.version)\n    .parse(process.argv);\n\n(async () => {\n\n    const onCancel = () => process.exit(0);\n    // noinspection JSUnresolvedVariable\n    const response = (await prompts(options, {onCancel})).options;\n\n    // copy selected options from config -> project\n    Object.entries(files).map(([key, value]) => {\n\n        if (response.indexOf(key) > -1) {\n            const relativePath = path.resolve(__dirname, value.path);\n            const content = Utilities.readFile(relativePath);\n            const outPath = path.join(process.cwd(),\n                (value.dir ? path.join(value.dir, value.out) : value.out));\n\n            if (value.dir) Utilities.createDir(path.join(process.cwd(), value.dir));\n            Utilities.writeFile(outPath, content);\n            console.log(texts.updateSuccess(value.out));\n        }\n    });\n})();\n"
  },
  {
    "path": "cli/xt-test.js",
    "content": "#!/usr/bin/env node\n\n/**\n * @name xt-test\n * @module\n * @public\n *\n * @description\n *\n * ```text\n * xt-test [--pattern] [--coverage] [--watch]\n * ```\n *\n * This command will run project unit tests located in `./test` directory.\n *\n * Command sets up extension unit testing environment with ES6 syntax support that is pre-initialized\n * with [mocha](https://mochajs.org/), [chai](https://www.chaijs.com/) and expect.\n * [nyc](https://www.npmjs.com/package/nyc) is used for computing code coverage.\n * The following browser APIs are also initialized: `window`, `document`, `chrome`.\n * Window is setup using [jsdom-global](https://www.npmjs.com/package/jsdom-global) and\n * chrome using [sinon-chrome](https://www.npmjs.com/package/sinon-chrome).\n *\n * You may extend this test environment within a single project. This is simply the base setup\n * for running unit tests. Or create your own testing environment at project level if this is\n * not suitable.\n */\n\nconst util = require('util');\nconst path = require('path');\nconst program = require('commander');\nconst pkg = require('../package.json');\nconst exec = require('child_process').exec;\nconst texts = require('./texts').xtTest;\n\nprocess.chdir(process.cwd());\n\nprogram\n    .version(pkg.version)\n    .option('-p --pattern <string>', texts.argPattern)\n    .option('-c --coverage', texts.argCoverage)\n    .option('-w --watch', texts.argWatch)\n    .parse(process.argv);\n\nconst {pattern, watch} = program.opts();\nconst rootSuite = path.resolve(process.cwd(),\n    'node_modules', pkg.name, 'cli', 'rootsuite.js');\n\nconst proc = exec([\n\n    // use nyc && mocha\n    'nyc mocha',\n\n    // where to look for tests\n    pattern ? pattern : './test/**/*.js',\n\n    // setup test environment\n    util.format('--file \"%s\"', rootSuite),\n\n    // enable watch\n    watch ? '--watch' : '',\n\n    // babel\n    '--require @babel/register',\n\n    // output colors\n    '--colors'\n\n].join(' '));\n\nproc.stdout.on('data', data => {\n    process.stdout.write(data.toString());\n});\n\nproc.stderr.on('data', data => {\n    process.stdout.write(data.toString());\n});\n\n// exit parent process with the unit test result code\nproc.on('exit', process.exit);\n"
  },
  {
    "path": "config/actions.yml",
    "content": "name: Build\n\non:\n  push:\n    branches: [ main ]\n\n## to run workflow on pull requests:\n#on:\n#  pull_request:\n#    branches: [ main ]\n\n## to run workflow on tagged commits\n#on:\n#  push:\n#    tags:\n#      - '*'\n\n## to run workflow on schedule, e.g. nightly build\n#on:\n#  schedule:\n#    - cron:  '0 0 * * *'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n\n      # see https://github.com/marketplace/actions/setup-node-js-environment\n      - uses: actions/setup-node@v2\n        with:\n          node-version: '14'\n\n      # see https://github.com/marketplace/actions/cache\n      - name: Cache dependecies\n        uses: actions/cache@v2\n        with:\n          path: '**/node_modules'\n          key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Unit test\n        run: npm run test\n\n      - name: Build docs\n        run: npm run docs\n\n      - name: Build extension .zip file\n        run: npm run build\n\n## Uncomment to deploy docs to GH pages\n## see: https://github.com/marketplace/actions/deploy-to-github-pages\n#      - name: Deploy docs\n#        uses: JamesIves/github-pages-deploy-action@4.1.3\n#        with:\n#          branch: gh-pages\n#          folder: public/documentation\n\n## Uncomment to upload generated zip file to web store\n## see: https://github.com/MobileFirstLLC/cws-publish\n#      - name: Upload to Chrome Web Store\n#        run: >-\n#          npx cws-upload\n#          ${{ secrets.CLIENT }}\n#          ${{ secrets.SECRET }}\n#          ${{ secrets.TOKEN }}\n#          \"release.zip\"\n#          ${{ EXTENSION_ID }};\n\n## Uncomment to make a Github release\n## see: https://github.com/marketplace/actions/create-release\n#      - uses: ncipollo/release-action@v1\n#        with:\n#          artifacts: \"release.zip\"\n#          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": "config/build.json",
    "content": "{\n  \"dist\": \"./dist\",\n  \"source\": \"./src\",\n  \"releases\": \"./\",\n  \"release_name\": \"release\",\n  \"manifest\": \"./src/manifest.json\",\n  \"js\": \"./src/**/*.js\",\n  \"js_bundles\": [{\n    \"src\":\"./src/**/*.js\",\n    \"name\": \"script\"\n  }],\n  \"html\": \"./src/**/*.html\",\n  \"scss\": \"./src/**/*.scss\",\n  \"scss_bundles\": [{\n    \"src\":\"./src/**/*.scss\",\n    \"name\": \"styles\"\n  }],\n  \"assets\": [\n    \"./assets/**/*\",\n    \"!./assets/locales\",\n    \"!./assets/locales/**/*\"\n  ],\n  \"copyAsIs\": [],\n  \"locales_dir\": \"./assets/locales/\",\n  \"locales_list\": [\n    \"en\"\n  ],\n  \"commands\": null,\n  \"commands_watch_path\": null\n}\n"
  },
  {
    "path": "config/docs.json",
    "content": "{\n  \"tags\": {\n    \"allowUnknownTags\": true,\n    \"dictionaries\": [\n      \"jsdoc\"\n    ]\n  },\n  \"source\": {\n    \"include\": [\n      \"src\"\n    ],\n    \"includePattern\": \".js$\",\n    \"excludePattern\": \"(node_modules/)\"\n  },\n  \"plugins\": [\n    \"plugins/markdown\"\n  ],\n  \"templates\": {\n    \"default\": {\n      \"cleverLinks\": true,\n      \"monospaceLinks\": false\n    }\n  },\n  \"opts\": {\n    \"destination\": \"./public/documentation\",\n    \"encoding\": \"utf8\",\n    \"private\": true,\n    \"recurse\": true,\n    \"template\": \"templates/default\"\n  }\n}\n"
  },
  {
    "path": "config/gitlab.yml",
    "content": "image: node:latest\n\nstages:\n  - install\n  - pages\n  - test\n  - publish\n\ncache:\n  key: ${CI_COMMIT_REF_SLUG}\n  paths:\n    - node_modules/\n\ninstall_dependencies:\n  stage: install\n  script: npm install\n  artifacts:\n    paths:\n      - node_modules/\n\npages:\n  stage: pages\n  script:\n    - npm run docs\n  artifacts:\n    paths:\n      - public/\n  only:\n    - master\n\ntest:\n  stage: test\n  script:\n    - npm run test\n\nstore_publish:\n  stage: publish\n  script:\n    - npm run build\n    ## see: https://github.com/MobileFirstLLC/cws-publish\n    # - npx cws-upload $client_id $secret $token $zip_path $extension_id;\n  artifacts:\n    paths:\n      - $zip\n  only:\n    - tags\n"
  },
  {
    "path": "config/ignore",
    "content": ".idea/\n.vscode/\nnode_modules/\n.nyc_output/\ncoverage/\ndist/\npublic/documentation/\nrelease.zip\nyarn-error.log\n"
  },
  {
    "path": "config/init/background.js",
    "content": "console.log('This is background service worker - edit me!');\n"
  },
  {
    "path": "config/init/eslint.json",
    "content": "{\n  \"env\": {\n    \"browser\": true,\n    \"es2021\": true,\n    \"node\": true\n  },\n  \"extends\": [\n    \"eslint:recommended\"\n  ],\n  \"globals\": {\n    \"document\": false,\n    \"escape\": false,\n    \"navigator\": false,\n    \"unescape\": false,\n    \"window\": false,\n    \"describe\": true,\n    \"before\": true,\n    \"it\": true,\n    \"expect\": true,\n    \"sinon\": true,\n    \"chrome\": true\n  },\n  \"plugins\": [],\n  \"parserOptions\": {\n    \"ecmaVersion\": 2020,\n    \"sourceType\": \"module\"\n  },\n  \"rules\": {\n  }\n}\n"
  },
  {
    "path": "config/init/intro.md",
    "content": "# ${name}\n\n${description}\n\n## Development \n\nThis extension was created with [Extension CLI](https://oss.mobilefirst.me/extension-cli/)!\n\nIf you find this software helpful [star](https://github.com/MobileFirstLLC/extension-cli/) or [sponsor](https://github.com/sponsors/MobileFirstLLC) this project.\n\n\n### Available Commands\n\n| Commands | Description |\n| --- | --- |\n| `npm run start` | build extension, watch file changes |\n| `npm run build` | generate release version |\n| `npm run docs` | generate source code docs |\n| `npm run clean` | remove temporary files |\n| `npm run test` | run unit tests |\n| `npm run sync` | update config files |\n\nFor CLI instructions see [User Guide &rarr;](https://oss.mobilefirst.me/extension-cli/)\n\n### Learn More\n\n**Extension Developer guides**\n\n- [Getting started with extension development](https://developer.chrome.com/extensions/getstarted)\n- Manifest configuration: [version 2](https://developer.chrome.com/extensions/manifest) - [version 3](https://developer.chrome.com/docs/extensions/mv3/intro/)\n- [Permissions reference](https://developer.chrome.com/extensions/declare_permissions)\n- [Chrome API reference](https://developer.chrome.com/docs/extensions/reference/)\n\n**Extension Publishing Guides**\n\n- [Publishing for Chrome](https://developer.chrome.com/webstore/publish)\n- [Publishing for Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension)\n- [Publishing for Opera addons](https://dev.opera.com/extensions/publishing-guidelines/)\n- [Publishing for Firefox](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/)\n"
  },
  {
    "path": "config/init/manifest.json",
    "content": "{\n  \"name\": \"__MSG_appName__\",\n  \"short_name\": \"__MSG_appShortName__\",\n  \"description\": \"__MSG_appDescription__\",\n  \"homepage_url\": \"${homepage}\",\n  \"version\": \"${version}\",\n  \"version_name\": \"${version}\",\n  \"manifest_version\": 3,\n  \"default_locale\": \"en\",\n  \"minimum_chrome_version\": \"88\",\n  \"permissions\": [],\n  \"icons\": {\n    \"128\": \"assets/img/128x128.png\"\n  },\n  \"background\": {\n    \"service_worker\": \"background.js\"\n  },\n  \"action\": {\n    \"default_icon\": {\n      \"16\": \"assets/img/16x16.png\",\n      \"24\": \"assets/img/24x24.png\",\n      \"32\": \"assets/img/32x32.png\"\n    },\n    \"default_title\": \"__MSG_appName__\"\n  }\n}\n"
  },
  {
    "path": "config/init/messages.json",
    "content": "{\n  \"appName\": {\n    \"message\": \"${name}\"\n  },\n  \"appShortName\": {\n    \"message\": \"${name}\"\n  },\n  \"appDescription\": {\n    \"message\": \"${description}\"\n  }\n}\n"
  },
  {
    "path": "config/init/package.json",
    "content": "{\n  \"name\": \"${safeName}\",\n  \"description\": \"${description}\",\n  \"version\": \"${version}\",\n  \"homepage\": \"${homepage}\",\n  \"author\": \"ENTER YOUR NAME HERE\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"ENTER GIT REPO URL\"\n  },\n  \"scripts\": {\n    \"start\": \"xt-build -e dev -w\",\n    \"start:firefox\": \"xt-build -e dev -p firefox -w\",\n    \"build\": \"xt-build -e prod\",\n    \"build:firefox\": \"xt-build -e prod -p firefox\",\n    \"clean\": \"xt-clean\",\n    \"docs\": \"xt-docs\",\n    \"test\": \"xt-test\",\n    \"coverage\": \"nyc --reporter=lcov npm run test\",\n    \"sync\": \"xt-sync\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"@babel/preset-env\"\n    ]\n  },\n  \"eslintIgnore\": [\n    \"test/**/*\"\n  ],\n  \"devDependencies\": {\n    \"extension-cli\": \"latest\"\n  },\n  \"xtdocs\": {\n    \"source\": {\n      \"include\": [\n        \"README.md\",\n        \"src\"\n      ]\n    }\n  },\n  \"xtbuild\": {\n    \"js_bundles\": [\n      {\n        \"name\": \"background\",\n        \"src\": \"./src/**/*.js\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "config/init/test.js",
    "content": "describe('Test extension', () => {\n\n    it('This is a dummy test', () => {\n        expect(true).to.be.true;\n    });\n});\n"
  },
  {
    "path": "config/readme.md",
    "content": "# CLI configuration files\n\nThis directory contains various files that are used by the available CLI commands. Below is a short summary of each to explain their purpose.\n\nPath | Description\n:--- | :---\n**actions.yml** | Github actions starter configuration\n**build.json** |  default file paths used by the build script\n**docs.json** | JSDoc documentation template\n**eslint.json** | default eslint configuration\n**gitlab.yml** | Gitlab CI starter configuration\n**ignore** | gitignore starter\n**travis.yml** | Travis CI starter configuration\n**init/** | Files for bootstrapping a new extension project \n &nbsp; **└── NNxNN.png** | extension icons\n &nbsp; **└── background.js** | background script starter\n &nbsp; **└── icon.svg** | vector icon\n &nbsp; **└── intro.md** | new extension readme\n &nbsp; **└── manifest.json** | manifest template\n &nbsp; **└── messages.json** | message dictionary template\n &nbsp; **└── package.json** | package.json template\n &nbsp; **└── test.js** | unit test starter\n\n**Notes**\n\n- eslint, CI configuration files, (git)ignore can be pulled into a project through `xt-sync` command,\n  or a project can specify these files independently.\n  The idea is not having to start from scratch at project level unless it is by choice. \n- All files in `init` directory are included in a new extension project\n  - files that should not be included in a newly generated project go in `/config` directory\n- keep `init` directory flat on purpose to keep things simple &mdash; create command will generate\n  the necessary structure     \n    \n"
  },
  {
    "path": "config/travis.yml",
    "content": "language: node_js\n\nnode_js:\n  - \"14.15.4\"\n\ncache:\n  directories:\n  - node_modules\n\nbefore_script:\n  - npm run test || travis_terminate 1\n\nscript:\n  - npm run docs\n  - npm run build\n\ndeploy:\n  - provider: pages\n    skip_cleanup: true\n    github_token: $github_token\n    local_dir: public/documentation\n    on:\n      branch: master\n\n  - provider: releases\n    skip_cleanup: true\n    api_key: $github_token\n    file: $zip_path\n    on:\n      tags: true\n\n#after_deploy:\n## upload generated zip to chrome web store\n## see: https://github.com/MobileFirstLLC/cws-publish\n#  - if [ ! -z  \"$TRAVIS_TAG\" ]; then\n#      npx cws-upload $client_id $secret $token $zip_path $extension_id;\n#    fi\n"
  },
  {
    "path": "guide/01-getting-started.md",
    "content": "# Installation\n\n### Prerequisites\n\nBefore using extension CLI, you must have the following:\n\n- [Node.js](https://nodejs.org/en/download/)\n- JavaScript IDE\n- Terminal access\n- Browser for debugging extensions\n\n### Setup\n\nCreate a new extension project:\n\n```bash\nnpx extension-cli\n```\n\nAdd CLI to an existing project:\n\n```bash\nnpm install extension-cli\n```\n\n### Default Project Organization\n\nBefore you start using the CLI, inspect your project file structure. You can override most of \nthese paths in configurations, but this organization matches the CLI defaults.\n\nIf you created a new extension using the command above, your file structure already looks like this.\n\nPath | Description\n--- | ---\n└ **assets** |  static assets\n&nbsp; &nbsp; └─ img | Extension icons\n&nbsp; &nbsp; └─ locales | Localized string resources\n&nbsp; &nbsp; &nbsp; &nbsp; └─ en/messages.json | English dictionary\n└ **src** | Source code: put js, scss, html, json files here\n&nbsp; &nbsp; └─ manifest.json | Extension manifest \n└ **test** | Unit tests\n└ package.json | Application root\n\n"
  },
  {
    "path": "guide/02-configuration.md",
    "content": "# Configuration for Existing Applications\n\n!!! info\n    **If you created the extension with Extension CLI, this setup is already done for you, and you may skip this step.**\n \n\nBefore using Extension CLI with an existing application, add these configuration options to project's `package.json`:\n\n### Babel Presets\n\nThis is needed to compile projects written in modern JavaScript syntax.\n\n```json\n\"babel\": {\n  \"presets\": [\n    \"@babel/preset-env\"\n  ]\n}\n```\n\n### ESLint Ignore\n\nExclude test files from being linted. If your project includes compiled 3rd party libraries, you should exclude them also.\n\n```json\n\"eslintIgnore\": [\n    \"test/**/*\"\n]\n```\n\n### Add Scripts\n\nAdd these to `package.json` `scripts` section:\n\n```json\n\"scripts\": {\n  \"start\": \"xt-build -e dev -w\",\n  \"build\": \"xt-build -e prod\",\n  \"clean\": \"xt-clean\",\n  \"docs\": \"xt-docs\",\n  \"test\": \"xt-test\",\n  \"coverage\": \"nyc --reporter=lcov npm run test\"\n}\n```\n\n"
  },
  {
    "path": "guide/03-xt-build-assets.md",
    "content": "# Static assets\n\n* * *\n\n<p class='page-intro'>Specify how static assets will be handled during builds.</p>\n\n* * *\n\nBy default, extension CLI will look for static assets matching this configuration:\n\n```json\n\"assets\": [\n    \"./assets/**/*\",\n    \"!./assets/locales\",\n    \"!./assets/locales/**/*\"\n  ],\n```\n\nYou may change this configuration if the project's static assets are located elsewhere or\nif you want to include or exclude additional files/directories.\n\nAfter the build step, all static assets will be located in the `/dist/assets` directory.\n\nFor example, to refer to images in extension manifest, would be as follows:\n\n```json\n\"browser_action\": {\n    \"default_icon\": {\n      \"16\": \"assets/img/16x16.png\",\n      \"24\": \"assets/img/24x24.png\",\n      \"32\": \"assets/img/32x32.png\"\n    }\n}\n```\n"
  },
  {
    "path": "guide/03-xt-build-cmds.md",
    "content": "# Custom commands\n\n* * *\n\n<p class='page-intro'>Custom commands enables running any custom actions after build and before generating a release.</p>\n\n* * *\n\nCustom commands will be executed: \n\n- _after_ script, styles, HTML and other bundles have been built, and\n- _before_ a release `.zip` file is generated\n\nCustom commands are run for both `dev` and `prod` builds. \n\nTo configure custom commands specify `commands` build configuration key. For example:\n\n```json\n\"xtbuild\": {\n  \"commands\": \"python do_something.py\",\n} \n```\n\nThis configuration would first build the extension, then run a custom Python script, \nthen for a production build, generate the extension zip file.\n\n<!--\n## Watching changes\n\nFor `dev` builds, you can specify a watch pattern, such that changes matching the \npattern will re-run custom commands.\n\nSpecify watch path using `commands_watch_path` configuration key, for example:\n\n```json\n\"xtbuild\": {\n  \"commands_watch_path\": \"./src\"\n}\n```\n\nthen run build in `dev` mode with `--watch` flag. \n\nAny changes under `./src` directory will cause custom commands to re-run.\n-->\n"
  },
  {
    "path": "guide/03-xt-build-copy.md",
    "content": "# Copying Files\n\n* * *\n\n<p class='page-intro'>Copying enables including files in the output without modifying them during build.\nThis includes use case where you want to skip compilation and linting of scripts or stylesheets.</p>\n\n* * *\n\n!!! info \"Copying static assets\"\n    By default, all static assets under `assets/` directory will be automatically\n    copied to output directory during builds.\n \n\n`copyAsIs` allows you to specify an array of files and/or directories which should be included in build output\nwithout modification. Files to copy can be located anywhere in your project. The directories to copy are expected to be inside `/src` directory.\n  \nThe build command will copy: \n\n- specified **files** without any modification and add them to the root of the output directory;\n  directory path for files will be flattened.\n\n- specified **directories** and their contents without modification and without flattening the path\n\nIf the copy command fails to locate the specified file or directory, it will not\nraise an issue, the copy will simply not occur.\n\n## Example 1: File copy\n\nSample configuration for skipping compilation of pre-compiled files.\n\nThis configuration will copy material theme directly from `node_modules` \nand include it in the `dist` directory. It will also copy a project level `special.js` \nscript into the output directory. No modification will occur to these files during the build step.  \n\nAfter the build `dist/` directory root will include `material.min.js` and `special.js`.\n\n```json\n\"xtbuild\": {\n    \"copyAsIs\": [\n      \"./node_modules/material-design-lite/material.min.js\",\n      \"./some/path/special.js\"\n    ]\n}\n```\n\n\n## Example 2: Directory copy\n\nWhen copying directories, directory will maintain its structure. Directory to copy must be \ninside `src` directory. When specifying a directory use a match pattern, either `*` or `**/*`:\n\nThis build configuration will perform following copy operations:\n\n- `/src/directory/*` copies all files under `/src/directory/`  to `dist/` root (excludes nested directories).\n\n- `/src/nested/directory/**/*` recursively copies all files and nested directories to `dist/` root without flattening path.\n \n\n```json\n\"xtbuild\": {\n    \"copyAsIs\": [\n      \"/src/directory/*\",\n      \"/src/nested/directory/**/*\"\n    ]\n}\n```\n\n## Disable Linting\n\nWhen including precompiled javascript files to an extension project, you should also \ndisable linting for those files to avoid unnecessary warnings. In `package.json`, \nadd the file paths to the list of ignored files to prevent them from being linted:\n\n```json\n  {  \n      \"eslintIgnore\": [\n        \"test/**/*\",\n        \"./some/path/special.js\"\n      ]\n  }\n```\n"
  },
  {
    "path": "guide/03-xt-build-locales.md",
    "content": "# Localization\n\n* * *\n\n<p class='page-intro'>Localization enables translating extension to different languages.</p>\n\n* * *\n\nIf the extension supports multiple languages, you can customize \nextension localization by specifying two build keys: `locales_dir` and `locales_list`.\n\n## Locales directory\n\n`locales_dir` key specifies where in project directory to look for locales files.\nThe default `locales_dir` is `./assets/locales/`.\nIf you prefer a different directory structure, override this default value.\n\n\n## Locales list\n\n`locales_list` is an array that  lists all supported languages, and such that\n the values of this array correspond to subdirectories under `locales_dir`. Only\n locales directories specified in this array will be included in the build, which\n allows excluding incomplete translations from build until they are  ready to be\n included. \n \n The default value of `locales_list` is `[\"en\"]`. \n\n Refer [to this list of language codes](https://developers.google.com/admin-sdk/directory/v1/languages)\n when specifying value for this configuration.\n\nYou may split localization files into multiple `.json` files within the \nlanguage-specific directory to improve maintainability. During builds\nall files within a language directory will be automatically combined into a single \n`messages.json` which is expected from a browser extensions.\n\nRecommended reading: [learn how to internationalize extensions](https://developer.chrome.com/extensions/i18n).\n\n## Example\n\nThis configuration shows build configuration with custom path and multiple language\noutputs.\n\nBuild configuration\n\n```json\n\"xtbuild\": {\n  \"locales_list\": [\"en\",\"fr\",\"pl\"],\n  \"locales_dir\": \"./my/custom/locales/path/\"\n}\n```\n\nCorresponding project level file structure: \n\nFile Path | Description\n--- | ---\n└ **`/my/custom/locales/path/`** |  locales directory\n&nbsp; &nbsp; &nbsp; &nbsp; └─ `en`/messages.json |  English dictionary\n&nbsp; &nbsp; &nbsp; &nbsp; └─ `fr`/myFile.json | French dictionary\n&nbsp; &nbsp; &nbsp; &nbsp; └─ `pl/` | \n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └─ app.json | Polish dictionary, part 1\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; └─ options.json | Polish dictionary, part 2\n&nbsp; &nbsp; &nbsp; &nbsp; └─ `de`/messages.json | German dictionary\n\nBuild behavior:\n\n- `myFile.json` will be renamed to `messages.json` \n- `app.json` and `options.json` will me concatenated and renamed to `messages.json`\n- extension will be available in 3 languages; `dist/` directory will contain:\n    - `_locales/en/messages.json`\n    - `_locales/fr/messages.json`\n    - `_locales/pl/messages.json`\n- German dictionary is excluded from build output because it is not included in `locales_list`\n"
  },
  {
    "path": "guide/03-xt-build-manifest.md",
    "content": "# Manifest\n\n* * *\n\n<p class='page-intro'>Customize build behavior for extension manifest.</p>\n\n* * *\n\nIn your build configuration specify path to the manifest file:\n\n```json\n\"xtbuild\": {\n   \"manifest\": \"./src/manifest.json\",\n} \n```\n\nThe file will be renamed to `manifest.json` during build regardless of its original name.\n\n## Customizing manifests for different target browsers\n\nThere are two strategies for customizing the manifest contents per target browser:\n\n1. Specify browser-specific keys in single manifest file\n2. Specify multiple build configurations, each with different manifest file.\n\n### Browser specific keys in single manifest\n\nUsing this strategy, the project contains single manifest. In manifest.json:\n\n```json\n{\n  \"name\": \"__MSG_appname__\",\n  \"description\": \"__MSG_description__\",\n  \"chrome\":{\n       \n     ... chrome-specific manifest keys here\n      \n  },\n  \"firefox\":{\n\n     ... firefox-specific manifest keys here\n\n  }\n}\n```\n\nThen run the build command specifying the target platform:\n\n```\nxt-build --platform chrome  \nxt-build --platform firefox\n```\n\nThe build will then combine all common manifest elements with those\nspecified for the target platform. \n\nWhen building cross-browser extensions, most browsers can reuse the \nsame manifest. Therefore, these two targets are typically sufficient to generate the desired\n manifests for multiple target browsers. However, if this strategy\nis insufficient, see the next option.  \n\n\n### Multiple build configurations\n\nCreate multiple build configuration files:\n\nchrome-config.json:\n\n```json\n{\n  \"manifest\": \"./manifests/chrome.json\"\n} \n```\n\nfirefox-config.json:\n\n```json\n{\n  \"manifest\": \"./manifests/firefox.json\"\n} \n```\n\nUsing this strategy, run the build command specifying path to config file explicitly:\n\n```\nxt-build --config chrome-config.json  \nxt-build --config firefox-config.json\n```\n"
  },
  {
    "path": "guide/03-xt-build-scripts.md",
    "content": "# Building Scripts\n\n* * *\n\n<p class='page-intro'>Instructions for configuring javascript build outputs.</p>\n\n* * *\n\n`js_bundles` key is used to configure build settings for javascript bundles. \n\nIt allows you to specify: \n\n1. name of each generated script file\n2. what to included in each script\n3. how many scripts will be generated by build command\n\nBy default `js_bundles` looks for .js files in source directory and generates\na single script.js as output.\n\n## Configuration options\n\n`js_bundles` value is an array of objects, where each object specifies the following keys:\n\n| Key | Value |\n--- | ---\n| **`name`** | Output filename without file extension |\n| **`src`** | Glob pattern for specifying which files to include in the bundle | \n| **`mode`** (optional) | webpack build mode; by default same as `--env` flag |  \n\n!!! info\n    Internally JavaScript bundles are built using webpack where mode is determined\n    by build `--env` flag. If you want to override this behavior and always use a \n    specific webpack mode configuration, explicitly specify `\"mode\"` in the build \n    configuration. \n\n### Specifying source files\n\nFor specifying value of `src` you can use any valid glob pattern: \n\n- a string value for a single file, example: `\"src/index.js\"`\n- array of files, example: `[\"src/index.js\", \"src/popup.js\"]` \n- a path with wildcard, for example: `\"src/*.js\"`\n- You may also use `!` as a way to negate inclusion of file(s)\n\nSee [\"Explaining Globs\"](https://gulpjs.com/docs/en/getting-started/explaining-globs) for detailed reference. \n\n\n## Example\n\nBelow is a sample build configuration that will generate two JavaScript files: \n\n- First one contains exactly `src/background.js`  \n- Second one contains all `.js` files under directories `scr/app/dir1` and `scr/app/dir2`\n\nAfter running build command `dist/` will contain `background.js` and `app.js`.\n\n```json\n\"xtbuild\": {\n    \"js_bundles\": [\n      {\n        \"name\": \"background\",\n        \"src\": \"./src/background.js\"\n      },\n      {\n        \"name\": \"app\",\n        \"src\": [\n            \"./src/app/dir1/**/*.js\",\n            \"./src/app/dir2/**/*.js\"\n         ]\n      }\n    ]\n}\n```\n"
  },
  {
    "path": "guide/03-xt-build-styles.md",
    "content": "# Building Stylesheets\n\n* * *\n\n<p class='page-intro'>Instructions for configuring stylesheet build outputs.</p>\n\n* * *\n\n`scss_bundles` are used to configure build settings for CSS stylesheets. The expected value is an array with \nzero or more objects where.\n\n- `name` is the output bundle filename with file extension\n- `src` specifies which files to include in each bundle; you can use \n    - a string value for a single file\n    - array of files, or \n    - a path with wildcard. \n    - prefix `!` as a way to negate the inclusion of a file\n    \nSee [globs syntax guide](https://gulpjs.com/docs/en/api/src) for more details.\n\nDev build does not minify style files. The production build will minify style files.\n\nBy 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. \n\nIf 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.\n\n**Example**\n\nSample project-level configuration with multiple style bundles. This configuration will generate two stylesheets in the output directory: styles.css and display.css.\n\n```json\n\"xtbuild\": {\n    \"scss_bundles\": [\n      {\n        \"src\": [\n          \"./src/**/*.scss\",\n          \"!./src/app/styles/ui.scss\"\n        ],\n        \"name\": \"styles\"\n      },\n      {\n        \"src\": [\n          \"./src/app/styles/ui.scss\"\n        ],\n        \"name\": \"display\"\n      }\n    ]\n}\n```\n"
  },
  {
    "path": "guide/03-xt-build.md",
    "content": "# xt-build\n\n* * *\n\n<p class='page-intro'><code>xt-build</code> command builds an extension project.</p>\n\n* * *\n\nBuild 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.\n\nSuccessful 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.\n\n### Dev Build Artifacts\n\nWhen 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.\n\n### Prod Build Artifacts\n\nWhen 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.\n\n## Commands\n\nBraces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.\n\n\n**Run build with default options**\n\nDefault option runs production build targeting Chrome browser. \n\n```bash\nxt-build\n```\n\n**Run build with explicit environment flag `-e` or `--env`**\n\n```bash\nxt-build {-e|--env} dev\n```\n\n```bash\nxt-build {-e|--env} prod\n```\n\n**Run build for specific target browser**\n\n```bash\nxt-build {-p|--platform} chrome \n```\n\n```bash\nxt-build {-p|--platform} firefox\n```\n\n**Run build using custom configuration file path**\n\n```bash\nxt-build {-c|--config} \"/path/to/config.json\"\n```\n\n**Run development build and keep watching changes**\n\n```bash\nxt-build {-e|--env} dev {-w|--watch}\n```\n\n**Get help using this command**\n\n```bash\nxt-build --help\n``` \n\n## Package.json scripts\n\nAfter adding Extension CLI to your project, you can run these commands from a \nterminal using syntax `npx xt-build`, or add the following to `packages.json` scripts section, \nthen execute the commands as `npm run start` or `npm run build`:\n \n```json\n\"scripts\":{\n    \"start\": \"xt-build -e dev -w\",\n    \"build\": \"xt-build -e prod\",\n}\n```\n\n## Default Configuration\n\nBy default the CLI will look for build configuration in two different\nplaces:\n\n- in `package.json` using key `xtbuild`\n\n- in a file named `.xtbuild.json` in project root\n\nAlternatively you can provide a path  to configuration file with `-c` or \n`--config` flag, followed by a path to configuration file. \n\nThe CLI uses a default build configuration shown below. This tells \nextension CLI where to look for input files, how to process them, and where \nto output files. You can override any of these key-value pairs at project level. \n\nExplanations for each of these keys is given below.\n\n```json\n--8<--\n./config/build.json\n--8<--\n```\n\n### Configuration Keys\n\nKey | Description | Guide \n--- | --- | ---\n`\"dist\"` | Build output directory ||\n`\"source\"` | Source code directory ||\n`\"releases\"` | Directory for outputting release zip file ||\n`\"release_name\"` | name of release zip file ||\n`\"manifest\"` |  extension manifest file path | [Guide](03-xt-build-manifest.md) |\n`\"js\"` | Watch pattern for script changes during dev builds ||\n`\"js_bundles\"` | Javascript bundles configuration | [Guide](03-xt-build-scripts.md)\n`\"html\"` | location and watch pattern of HTML files ||\n`\"scss\"` | Watch pattern for style changes during dev builds ||\n`\"scss_bundles\"` | Stylesheets bundles configuration | [Guide](03-xt-build-styles.md)\n`\"assets\"` | Static assets configuration match pattern | [Guide](03-xt-build-assets.md) \n`\"copyAsIs\"` | Files and directories to copy to output directory without modification | [Guide](03-xt-build-copy.md)\n`\"locales_dir\"` | Localizations directory | [Guide](03-xt-build-locales.md) \n`\"locales_list\"` | List of locales | [Guide](03-xt-build-locales.md)\n`\"commands\"` | Custom commands | [Guide](03-xt-build-cmds.md)\n<!-- `\"commands_watch_path\"` | Commands watch pattern during dev builds | [Guide](03-xt-build-cmds.md) -->\n"
  },
  {
    "path": "guide/04-xt-clean.md",
    "content": "# xt-clean\n\n\n* * *\n\n<p class='page-intro'><code>xt-clean</code> command removes all automatically generated files from the project directories.</p>\n\n* * *\n\nClean 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.\n\n\n## Commands\n\nBraces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.\n\n**Remove ignored files (default)**\n\n```bash\nxt-clean\n```\n\n**Clear ignored files, including `node_modules`**\n\n```bash\nxt-clean {-m|--modules}\n```\n\n**Clear ignored files, including `.idea/` directory**\n\n```bash\nxt-clean {-i|--idea}\n```\n\n**Clear ignored files, including `.vscode/` directory**\n\n```bash\nxt-clean {-v|--vscode}\n```\n\n**Clear absolutely all ignored files**\n\n```bash\nxt-clean -v -i -m\n```\n\n**Get help using this command**\n\n```bash\nxt-clean --help\n``` \n\n## Package.json scripts\n\nAfter installing extension-cli, you can run these commands from a terminal using syntax `npx xt-clean`.\n \nOr you can add an option to `packages.json` scripts section and then execute the command as `npm run clean` See example below. \n \n```json\n\"scripts\":{\n  \"clean\": \"xt-clean\"\n}\n```\n\n\n"
  },
  {
    "path": "guide/05-xt-docs-templates.md",
    "content": "# Documentation Templates\n\n* * *\n\n<p class='page-intro'>Use templates to customize the look and feel of \nsource code documentation.</p>\n\n* * *\n\nExtension CLI uses [JsDoc](https://jsdoc.app) to document extension projects.\nYou can then apply templates to customize the look and feel of these docs.\n\n## Customizing Default Template\n\nIf you are using the default template see: [Configuring JSDoc's default template](https://jsdoc.app/about-configuring-default-template.html).\n\n<a href=\"https://jsdoc.app/about-configuring-default-template.html\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/jsdoc-default.jpg\" alt=\"default template\"/>\n</a>\n\n## Alternative Templates\n\nTo use an alternative template:\n\n1. Choose a suitable template and use npm to install it at project level\n2. In the [documentation configuration](https://oss.mobilefirst.me/extension-cli/05-xt-docs/#default-configuration):\n    1. Specify `\"opts.template\"` to indicate which template to use    \n    2. Customize the template options under `\"templates\"` \n\n* * *\n\n### Braintree JSDoc Template\n\n[Source and configuration](https://github.com/braintree/jsdoc-template)\n\n![GitHub last commit](https://img.shields.io/github/last-commit/braintree/jsdoc-template)\n\n<a href=\"https://github.com/braintree/jsdoc-template\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/braintree.jpg\" alt=\"braintree\"/>\n</a>\n\n* * *\n\n### clean-jsdoc-theme\n\n[Source and configuration](https://github.com/ankitskvmdam/clean-jsdoc-theme)\n\n![GitHub last commit](https://img.shields.io/github/last-commit/ankitskvmdam/clean-jsdoc-theme)\n\n_Light mode_\n\n<a href=\"https://github.com/ankitskvmdam/clean-jsdoc-theme\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/clean-jsdoc-light.jpg\" alt=\"light mode\"/>\n</a>\n\n_Dark mode_\n\n<a href=\"https://github.com/ankitskvmdam/clean-jsdoc-theme\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/clean-jsdoc-dark.jpg\" alt=\"dark mode\"/>\n</a>\n\n* * *\n\n### Foodoc\n\n[Source and configuration](https://github.com/steveush/foodoc)\n\n![GitHub last commit](https://img.shields.io/github/last-commit/steveush/foodoc)\n\n\n<a href=\"https://github.com/steveush/foodoc\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/foodoc.jpg\" alt=\"foodoc\"/>\n</a>\n\n* * *\n\n### JsDoc Template\n\n[Source and configuration](https://github.com/AlexisPuga/jsdoc-template)\n\n![GitHub last commit](https://img.shields.io/github/last-commit/AlexisPuga/jsdoc-template)\n\n<a href=\"https://github.com/AlexisPuga/jsdoc-template\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/jsdoc-template.jpg\" alt=\"JsDoc Template\"/>\n</a>\n\n* * *\n\n### Tidy JsDoc\n\n[Source and configuration](https://github.com/julie-ng/tidy-jsdoc)\n\n![GitHub last commit](https://img.shields.io/github/last-commit/julie-ng/tidy-jsdoc)\n\n<a href=\"https://github.com/julie-ng/tidy-jsdoc\" \n   class=\"preview\" target=\"_blank\" rel=\"noreferrer nofollow\">\n  <span class=\"bar\">\n      <span class=\"red\"></span>\n      <span class=\"yellow\"></span>\n      <span class=\"green\"></span>\n  </span><img src=\"/extension-cli/assets/images/tidy-jsdoc.jpg\" alt=\"Tidy JsDoc\"/>\n</a>\n\n* * *\n\n<!-- style the preview views -->\n<style>\narticle a.preview {\n  display: block;\n  margin: 2rem auto;\n  width:1000px; max-width: calc(100% - 42px); \n  box-shadow: 0 12px 42px rgba(0,0,0,.22), 0 4px 6px rgba(0,0,0,0.4);\n  border-radius: 4px;\n  overflow: hidden;\n  position: relative;\n}\narticle a.preview img{\n  width: 100%;\n  background: #222;\n  display: block;\n  position: relative;\n  margin:0;\n}\narticle a.preview .bar{\n  padding:10px 12px; width:100%;\n  background: #e4e4e4;\n  position: relative;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  margin:0;\n}\narticle a.preview .bar > span{\n  height: 10px; width:10px;\n  border-radius: 50%;\n  background: #9997;\n  margin-right: 8px;\n}\narticle a.preview .bar .red{\n    background:#FF5952;\n}\narticle a.preview .bar .yellow{\n    background:#E6C029;\n}\narticle a.preview .bar .green{\n    background:#54C22B;\n}\n</style>\n"
  },
  {
    "path": "guide/05-xt-docs.md",
    "content": "# xt-docs\n\n* * *\n\n<p class='page-intro'><code>xt-docs</code> command generates source\n code documentation for an extension project.</p>\n\n* * *\n\nExtension CLI uses [JSDoc](https://jsdoc.app/index.html) specification to \ngenerate documentation for javascript files in an extension project. JSDoc is \na flexible documentation generator that converts javascript code comments to \nreadable HTML/CSS files which you can be hosted for example with github pages.\n\n## Commands\n\nBraces `{ }` indicate that the user must choose one (and only one) of the \nitems inside the braces.\n\n**Default command**\n\n```bash\nxt-docs\n```\n \n**Command using custom configuration file path**\n\n```bash\nxt-docs {-c|--config} \"/path/to/config.json\"\n```\n\n**Build docs and keep watching changes**\n\n```bash\nxt-docs {-w|--watch}\n```\n\n**Get help using this command**\n\n```bash\nxt-docs --help\n``` \n\n## Package.json scripts\n\nAfter installing extension-cli, you can run these commands from a terminal \nusing syntax `npx xt-docs`.\n \n Or you can add an option to `packages.json` scripts section and then execute \n the command as `npm run docs`. See example below.\n \n```json\n\"scripts\":{\n  \"docs\": \"xt-docs\"\n}\n```\n  \n## Configuration\n \nBy default the CLI will look for docs configuration in two different\nplaces:\n\n- in `package.json` using key `xtdocs`\n\n- in a file named `.xtdocs.json` in project root\n\nIf these two locations cause a conflict, alternatively you can provide a path \nto configuration file with `-c` (`--config`) flag, followed by path to file. \n[See commands for an example](#commands).\n\nYou can use any compatible template of choice to style your docs. You can find \nsome [templating options here](05-xt-docs-templates.md).\n\n### Default Configuration\n\nThe 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).\n\n```json\n--8<--\n./config/docs.json\n--8<--\n```\n\n\n"
  },
  {
    "path": "guide/06-xt-sync.md",
    "content": "# xt-sync\n\n\n* * *\n\n<p class='page-intro'><code>xt-sync</code> command enables copying and updating \nconfiguration files.</p>\n\n* * *\n\nWhen adding more features to an extension project, it is helpful\nto \\*not\\* start from scratch. `xt-sync` command enables extension projects\nto pull in starter configuration files for the purposes of linting, \nsetting up automated CI builds, and for setting up git VCS.\n\nThe configuration files are intended as a starting point. If you\nend up modifying them heavily at a project level, you should continue\nto maintain them manually instead of using this command.\n\nIf you do not modify these configuration files, you can sync the \nlatest version periodically, to update to the latest version supplied\nby this CLI.\n\n## Commands\n\n**Sync configuration files**\n\nThis command will guide you through the available options. \n\n```bash\nxt-sync\n```\n\n\n## Package.json scripts\n\nAfter installing extension-cli, you can run these commands from a terminal by calling\n \n```bash\nnpx xt-sync\n```\n\nAlternatively you can add an option to `packages.json` scripts section as shown below\n \n```json\n\"scripts\" : {\n  \"sync\": \"xt-sync\"\n}\n```\n\nand then execute the command by running \n\n```bash\nnpm run sync\n```\n"
  },
  {
    "path": "guide/07-xt-test.md",
    "content": "# 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 setup extension testing environment that is pre-initialized\nwith [mocha](https://mochajs.org/), [chai](https://www.chaijs.com/),\nand expect. [nyc](https://www.npmjs.com/package/nyc) is used for computing code coverage. \nThe following browser features are initialized for convenience: `window`, `chrome`, `document`. \nWindow is setup using [jsdom-global](https://www.npmjs.com/package/jsdom-global) and\nchrome using [sinon-chrome](https://www.npmjs.com/package/sinon-chrome).\n\nBy default this command looks for unit tests in `test/` directory, in any file ending with `.js`. \nMocha will execute with babel, meaning you can use this test environment with modern JavaScript\n syntax.\n\nYou may extend this unit testing environment within an extension project. \nThis is simply a base setup for running unit tests for web extensions. \nYou may also create your very own test environment if this setup is not suitable for your project.\n\n## Commands\n\nBraces `{ }` indicate that the user must choose one (and only one) of the items inside the braces.\n\n\n**Run unit tests (default)**\n\n```bash\nxt-test\n```\n\n**Configure custom test directory path or match pattern**\n\nDefaults to `./test/**/*.js` if not specified\n\n```bash\nxt-test {-p|--pattern} \"./test/**/*.js\"\n```\n\n**Execute tests and keep watching changes**\n\n```bash\nxt-test {-w|--watch}\n```\n\n**Get help using this command**\n\n```bash\nxt-test --help\n``` \n\n## Package.json scripts\n\nAfter installing extension-cli, you can run these commands from a terminal using syntax `npx xt-test`.\n \nYou may also add an option to `packages.json` scripts section as shown below, then\n\n- run unit tests from terminal: `npm run test` \n- run unit tests and save coverage to file: `npm run coverage`.\n\n \n```json\n\"scripts\":{\n  \"test\": \"xt-test\",\n  \"coverage\": \"nyc --reporter=lcov npm run test\"\n}\n```\n\n## Reporting Coverage\n\n### Coveralls\n\nThe general setup is:\n\n1. Install [coveralls](https://www.npmjs.com/package/coveralls) as a dev dependency at project level:\n\n    ```\n    npm install coveralls --save-dev\n    ```\n\n2. Run unit tests with coverage report during CI/CD workflow, then pipe the result to coveralls:\n\n    ``` \n    nyc --reporter=lcov npm run test | coveralls\n    ```\n\n\nIf using Github actions, use [Coveralls Github action](https://github.com/marketplace/actions/coveralls-github-action) to report results. Example:\n\n```yaml\n- name: Execute unit tests w/ coverage\n  run: nyc --reporter=lcov npm run test  # or: npm run coverage\n\n- name: Report coverage\n  uses: coverallsapp/github-action@master\n  with:\n    github-token: ${{ secrets.GITHUB_TOKEN }}\n```\n\n\n\n"
  },
  {
    "path": "guide/08-xt-create.md",
    "content": "# extension-cli\n\n* * *\n\n<p class='page-intro'><code>extension-cli</code> command creates a new web extension project.</p>\n\n* * *\n\n## Commands\n\n```bash\nnpx extension-cli\n```\n \nThis command will prompt with necessary questions and does not take any arguments. \n \nThis command will generate initial files and directories for a new project.\n\nRun it in the directory where you want to create the extension.\n\n"
  },
  {
    "path": "guide/09-release-notes-0.md",
    "content": "---\ndisqus: \"False\"\n---\n\n\n### 0.11.9 (2021-04-04)\n\n* **xt-build**: enable customizing release filename [PR #37](https://github.com/MobileFirstLLC/extension-cli/pull/37)\n* update dependencies [PR #39](https://github.com/MobileFirstLLC/extension-cli/pull/39)\n* 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)\n\n### 0.11.8 (2021-03-12)\n\n* **xt-build**: support copying directories as-is [#32](https://github.com/MobileFirstLLC/extension-cli/issues/32)\n* **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)\n\n### 0.11.7 (2021-03-02)\n\n* **xt-docs**: make watch recursive on watched directories\n* **xt-docs**: add tutorials directory to watch list (if exists)\n* **xt-docs**: display error when docs command fails\n\n### 0.11.6 (2021-02-25)\n\n* **xt-docs:** add watch mode to docs command, see: [#23](https://github.com/mobilefirstllc/extension-cli/issues/23) \n\n### 0.11.5 (2021-02-24)\n\n* **xt-test:** unit code result reporting fix, see: [#26](https://github.com/mobilefirstllc/extension-cli/issues/26) \n\n### 0.11.3 (2021-01-27)\n\n* **xt-build:** file watch fix\n\n### 0.11.2 (2021-01-08)\n\n* **xt-build:** command path fix\n\n### 0.11.1 (2021-01-06)\n\n* **xt-build:** allow specifying custom build commands\n* **xt-create:** fix image generation issue\n* update packages\n\n\n### 0.10.1 (2020-12-15)\n\n* update test configs \n* check if gitignore exists before xt-clean\n* **xt-create:** change default icon to high contrast\n* update packages\n\n### 0.9.4 (2020-11-29)\n\n* extension-cli: fix typo\n* update packages\n\n### 0.9.3 (2020-10-31)\n\n* xt-clean: improve xt-clean command handling of files\n* change icon\n* update docs\n\n### 0.9.1 (2020-10-11)\n\n- fix: xt-docs config keys replace when value is an array\n\n### 0.9.0 (2020-10-05)\n\n- xt-test: add configurable test path\n- xt-create: sanitize package name\n- update packages\n- xt-clean: refactor command\n- xt-docs: refactor docs command\n- xt-sync: refactor sync command\n\n### 0.8.16 (2020-08-09)\n\n- update packages\n\n### 0.8.15  (2020-08-04)\n\n- update packages\n\n### 0.8.14 (2020-08-01)\n\n- update xt-create\n\n### 0.8.13 (2020-07-26)\n\n- updated packages\n\n### 0.8.12 (2020-05-26)\n\n- update build command\n\n### 0.8.11 (2020-05-25)\n\n- fix issue with create command docs configs\n- add new/missing docs dependency\n\n### 0.8.10 (2020-05-25)\n\n- `xt-build` bug fixes\n- Made webpack options configurable, to enable adding loaders etc.\n- Upgraded project dependencies\n\n### 0.8.9 (2020-04-10)\n\n- Implemented command to create new extension\n- Updated docs to reflect this new command\n\n### 0.8.8 (2020-04-08)\n\n- Upgraded project dependencies\n\n### 0.8.7 (2020-01-17)\n\n- Upgraded project dependencies\n- Fixed scripts build step (changed webpack options)\n\n### 0.8.6 (2019-12-21)\n\n- Initial release for this publisher\n- Migrated project from older source code\n- Upgraded all packages\n- Migrated build to use Gulp v4\n"
  },
  {
    "path": "guide/09-release-notes.md",
    "content": "---\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](https://github.com/MobileFirstLLC/extension-cli/commit/066f7820ce48e877cf5477ff77037c9d3a9d2fdd))\n- @babel/register: 7.17.7 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))\n- chai: 4.3.6 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))\n- commander: 9.0.0 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))\n- eslint: 8.11.0 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))\n- gulp-sass: 5.1.0 ([0ac28b2](https://github.com/MobileFirstLLC/extension-cli/commit/0ac28b2eb26c998e0958ed4f98691419323b1931))\n- jsdoc: 3.6.10 ([d0a09a3](https://github.com/MobileFirstLLC/extension-cli/commit/d0a09a3540580ef1d69edaaf9aa264a4674198b5))\n- jsdom: 19.0.0 ([b0f290f](https://github.com/MobileFirstLLC/extension-cli/commit/b0f290fa43f76fc4e67a95ae35ca4cefb7b9db6c))\n- mocha: 9.2.2 ([2084d42](https://github.com/MobileFirstLLC/extension-cli/commit/2084d42255e811c9f142ca77983b02aaa7dd71f0))\n- sass: 1.49.9 ([aef9fd6](https://github.com/MobileFirstLLC/extension-cli/commit/aef9fd6142879af3bf4610c80eaca1f97aff96a4))\n- sinon: 13.0.1 ([8282c0a](https://github.com/MobileFirstLLC/extension-cli/commit/8282c0a05d2702e6ec92155d8cfaba2de93c9c53))\n- yargs: 17.3.1 ([add18ee](https://github.com/MobileFirstLLC/extension-cli/commit/add18ee15196d4356a74d0f76ded23d1fef1d085))\n\n### 1.2.4 (2021-10-19)\n\n- Update devtools sourcemap config [PR #119](https://github.com/MobileFirstLLC/extension-cli/pull/119)\n- New extension now initialized with MV3 [#86](https://github.com/MobileFirstLLC/extension-cli/pull/111)\n\n**Dependency updates**\n\n- update @babel/preset-env to v7.15.8 ([a341965](https://github.com/mobilefirstllc/extension-cli/commit/a3419659b3ac2427f1134f8c6cfb2bb38c29f009))\n- update commander to v8.2.0 ([5226669](https://github.com/mobilefirstllc/extension-cli/commit/52266695f15cace4cc422e229afe5555d30ff0e4))\n- update eslint to v8 ([d5549a8](https://github.com/mobilefirstllc/extension-cli/commit/d5549a8730256f61edbd36ab7cabbac95db5000e))\n- update jsdom to v18 ([681db6b](https://github.com/mobilefirstllc/extension-cli/commit/681db6bafeedda989471235ff6f14ad9edff1885))\n- update mocha to v9.1.2 ([d7cecc6](https://github.com/mobilefirstllc/extension-cli/commit/d7cecc60a2aa918559bea17b2531b3e331500cce))\n- update prompts to v2.4.2 ([f99cb60](https://github.com/mobilefirstllc/extension-cli/commit/f99cb608f43414ecbb8f9309ce2d32453b11b0d5))\n- update sass to v1.43.2 ([32eb148](https://github.com/mobilefirstllc/extension-cli/commit/32eb148d81318f115942df2682270ded3c061652))\n- update webpack-stream to v7 ([#94](https://github.com/mobilefirstllc/extension-cli/issues/94)) ([a19b448](https://github.com/mobilefirstllc/extension-cli/commit/a19b4488cc7f9a31474904e58b2920bf67f0619a))\n- update yargs to v17.2.1 ([9a06f44](https://github.com/mobilefirstllc/extension-cli/commit/9a06f44b878d178dbd15fbae490470082b99221a))\n\n### 1.2.2 (2021-07-28)\n\n- update dependencies\n\n### 1.2.0 (2021-07-28)\n\n**Changes to build**\n\n- enable using custom filenames for manifests pre-build [PR #66](https://github.com/MobileFirstLLC/extension-cli/pull/66)\n- run build tasks in parallel [PR #70](https://github.com/MobileFirstLLC/extension-cli/pull/70)\n- make sourcemap basename match js file name [PR #70](https://github.com/MobileFirstLLC/extension-cli/pull/70)\n- dynamically determine project path; remove build config key [PR #71](https://github.com/MobileFirstLLC/extension-cli/pull/71)\n\n**Other changes**\n\n- docs: make JsDoc default template the default documentation template for CLI [#62](https://github.com/MobileFirstLLC/extension-cli/issues/62)\n- sync: add CI configuration starter for Github actions [#65](https://github.com/MobileFirstLLC/extension-cli/issues/65)\n- sync: eslint config file will now have file extension `.json` [PR #78](https://github.com/MobileFirstLLC/extension-cli/pull/78)\n- update dependencies\n\n### 1.1.0 (2021-06-12)\n\n- 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)\n- updated dependencies\n\n### 1.0.3 (2021-04-27)\n\n**Changes to build**\n\n- Make webpack mode configurable [#51](https://github.com/MobileFirstLLC/extension-cli/issues/51), [PR #55](https://github.com/MobileFirstLLC/extension-cli/pull/55)\n- use `cheap-source-map` [PR #49](https://github.com/MobileFirstLLC/extension-cli/pull/49)\n- remove devtool in prod config [PR #50](https://github.com/MobileFirstLLC/extension-cli/pull/50)\n\n### 1.0.2 (2021-04-11)\n\n**Changes to build**\n\n- Custom folders for scss bundles and always minify css [PR #47](https://github.com/MobileFirstLLC/extension-cli/pull/47)\n- Default style bundle name without extension [PR #48](https://github.com/MobileFirstLLC/extension-cli/pull/48)\n\n### 1.0.0 (2021-04-11)\n\n**Changes to build**\n\n- automatically copy from `assets/` to output directory `assets/` [PR #43](https://github.com/MobileFirstLLC/extension-cli/pull/43)\n- add target platform for manifests: `chrome/firefox` [PR #43](https://github.com/MobileFirstLLC/extension-cli/pull/43)\n- improved build outputs [PR #42](https://github.com/MobileFirstLLC/extension-cli/pull/42)\n\n**Other changes**\n\n- Updated dependencies [PR #44](https://github.com/MobileFirstLLC/extension-cli/pull/44)\n"
  },
  {
    "path": "guide/12-helpful.md",
    "content": "# Helpful References\n\n* * *\n\n<p class='page-intro'>Collection of generally helpful links for extension development.</p>\n\n* * *\n\n**Getting started guides**<br/>\n[Chrome](https://developer.chrome.com/extensions/getstarted) &bull;\n[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions#get_started) &bull;\n[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions) &bull;\n[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/) &bull;\n[Opera](https://dev.opera.com/extensions/getting-started/)\n\n**Extension manifest references**<br/>\n[Chrome v3](https://developer.chrome.com/extensions/manifest) &bull;\n[Chrome v2 ⚠️](https://developer.chrome.com/docs/extensions/mv2/manifest/) &bull;\n[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json) &bull; \n[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/getting-started/manifest-format) &bull;\n[Opera](https://dev.opera.com/extensions/manifest/)\n\n**Lists of browser APIs**<br/>\n[Chrome](https://developer.chrome.com/extensions/api_index) &bull;\n[Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API#javascript_api_listing) &bull;\n[Opera](https://dev.opera.com/extensions/apis/)\n  \n    \n**Cross-browser compatibility charts**<br/>\n[Manifest compatibility](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_compatibility_for_manifest.json) &bull;\n[JavaScript API support](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Browser_support_for_JavaScript_APIs)\n\n**Internationalization**<br/>\n[Supporting multiple languages](https://developer.chrome.com/extensions/i18n) &bull;\n[Language locales list](https://developer.chrome.com/docs/webstore/i18n/#choosing-locales-to-support)\n\n**Extension Publishing Guides**<br/>\n[Chrome](https://developer.chrome.com/webstore/publish) &bull;\n[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/create-dev-account) &bull;\n[Firefox](https://extensionworkshop.com/documentation/publish/submitting-an-add-on/) &bull;\n[Opera](https://dev.opera.com/extensions/publishing-guidelines/) &bull;\n[Safari](https://developer.apple.com/documentation/safariservices/safari_web_extensions/distributing_your_safari_web_extension)\n\n**Marketplace Image Guidelines**<br/>\n[Chrome](https://developer.chrome.com/webstore/images) &bull;\n[Edge](https://docs.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/publish-extension#step-5-add-store-listing-details-for-your-extension)\n  \n**Marketplace APIs**<br/>\n[Chrome Web Store](https://developer.chrome.com/webstore/api_index)\n\n\n**Other Resources**\n\n- [Chrome Extension Samples](https://github.com/GoogleChrome/chrome-extensions-samples) - examples of feature implementations\n- [Awesome WebExtensions](https://github.com/fregante/Awesome-WebExtensions) - curated list of resources and tools\n"
  },
  {
    "path": "guide/13-cli-development.md",
    "content": "---\ndisqus: \"False\"\n---\n\n# Extension CLI Development\n\n- This CLI is built with Node.Js, written in JavaScript, and uses numerous packages listed below. \n- The source code is available on [Github](https://github.com/MobileFirstLLC/extension-cli).\n- Releases are published on [NPM](https://www.npmjs.com/package/extension-cli).\n- This user guide is built with [MkDocs](https://www.mkdocs.org/) and  [MkDocs material theme](https://squidfunk.github.io/mkdocs-material/).\n- CI/CD by [Travis CI](https://travis-ci.org/MobileFirstLLC/extension-cli) and documentation served by [Github Pages](https://pages.github.com/).\n\n\n## Project Organization\n\nPath | Description\n--- | ---\n└ **.github** | Github config files and markdown\n└ **cli** |  all available commands are defined here\n└ **config** | Resources and config files used by the commands in `cli`\n└ **guide** | User guide\n└ **test** | CLI unit tests\n└ `/*` | Application root; various project config files\n\n* * *\n\nTo setup a local dev environment and develop the CLI application, see\n \n[Environment Setup &rarr;](13-dev-env.md)\n\n* * * \n\n## Dependencies\n\nExtension CLI is built with the following dependencies:\n\n| # | Package name | Purpose |\n| --- | --- | --- | \n| 1. | `@babel/preset-env` | for modern JavaScript syntax |\n| 2. | `@babel/register` | for unit testing |\n| 3. | `chai` | BDD/TDD assertion library for unit testing |\n| 4. | `chalk` | Add color to terminal output | \n| 5. | `cli-spinner` | Terminal spinner to indicated progress |\n| 6. | `commander` | handle CLI input arguments |\n| 7. | `del` | for clearing generated files |\n| 8. | `eslint` | for linting JavaScript |\n| 9. | `gulp` | for running build script |\n| 10. | `gulp-change` | JSON file content manipulations |\n| 11. | `gulp-clean-css` | Minify CSS |\n| 12. | `gulp-concat` | Concatenates files (used for CSS) |\n| 13. | `gulp-htmlmin` | Removes whitespace from HTML |\n| 14. | `gulp-jsonminify` | minify JSON files (manifest, locales) |\n| 15. | `gulp-load-plugins` | to load various gulp plugins |\n| 16. | `gulp-merge-json` | merge locales files |\n| 17. | `gulp-rename` | rename files during builds |\n| 18. | `gulp-sass` | process SASS files during builds |\n| 19. | `gulp-zip` | generate zip files |\n| 20. | `jsdoc` | generate docs |\n| 21. | `jsdom` | mock DOM in Node.js env |\n| 22. | `jsdom-global` | adds window, document to unit testing env |\n| 23. | `mocha` | unit testing framework |\n| 24. | `nyc` | unit testing code coverage tool  |\n| 25. | `prompts` | create CLI prompts with interactive selectors |\n| 26. | `sass` | compile SASS files during builds |\n| 27. | `sinon` | JavaScript test spies, stubs and mocks |\n| 28. | `sinon-chrome` | unit testing for extensions |\n| 29. | `webpack-stream` | build javascript files |\n| 30. | `yargs` | parse keyword args |\n"
  },
  {
    "path": "guide/13-dev-env.md",
    "content": "# Environment Setup\n\n\nTo build extension CLI locally you will need [Node.js](https://nodejs.org/en/download/)\nand any web IDE of your choice.\n\nDeveloping the CLI requires two projects open at the same time:\n\n1. the CLI source code, which you are developing\n2. a driver project that is used to execute the CLI commands\n\nThe following instructions explain how to set up this environment.\n\n## Instructions\n\n### 1. Setup the CLI\n\n1. [Fork the extension-CLI repo](https://github.com/MobileFirstLLC/extension-cli/fork)\n\n2. Clone the forked repo and then open it in your favorite web IDE\n\n3. Run the following command in terminal: \n\n```bash\nnpm install   \n```\n\n### 2. Setup driver project\n\nNext you will need a project to drive the CLI to be able to execute its commands.\nYou can use any existing extension project that is using extension-cli.\n\nIf you do not have an existing project, create a new project. In the directory where you want to create the driver project run:\n\n```bash\nnpx extension-cli\n```\n\nthen follow the on-screen instructions. Once you have the project ready, open it in a web IDE. \nAt this point you should have two IDE windows open.   \n\n### 3. Link driver and CLI\n\n\n1. In **CLI project** terminal run this command (use `sudo npm link` if necessary):\n\n    ```bash\n    npm link  \n    ```\n    \n    <br/>\n\n2. In the **driver project** terminal run this command:    \n    \n    ```bash\n    npm link extension-cli\n    ``` \n   \n* * * \n\n**<center>Your dev environment should now be ready to use.</center>**\n \n* * * \n\n## Clean up\n\nUnlink CLI and driver project to remove all local links.\n\nIn the **driver project** terminal run:    \n    \n```bash\nnpm unlink --no-save extension-cli\n``` \n\nto unlink project from the local CLI version. Note that this may remove\nextension-cli from the project completely, and you may need to run `install extension-cli`\nto add back the version from NPM registry. This is relevant only if you used\nan existing project as a driver.\n\nIn **CLI project** terminal run:\n\n```bash\nnpm r extension-cli -g\n```\n\nto remove the CLI symlink.\n"
  },
  {
    "path": "guide/14-user-guide.md",
    "content": "# Editing User Guide\n\n\n!!! info\n    If you are interested in editing the content (and not layout) of this user guide, \n    simply edit the markdown directly in any markdown editor or on Github.\n    There is a pencil icon linking to the markdown source on each page of these docs,\n    which takes you directly to the source document.\n\n## Developing User Guide\n\nWhen you want to edit the layout, organization and/or theme of these docs, you \nwill need to run these project docs locally. This user guide is built with Python. \nYou will need Python 3.x before proceeding.\n\n1. If you are not a maintainer, [fork the repo](https://github.com/MobileFirstLLC/extension-cli/fork)\n\n2. Clone the forked repo and launch your favorite markdown editor and terminal.\n\n3. Setup Python development env as follows: \n\n    - Create virtual env for Python packages:\n   \n        ```\n        python3 -m venv env         \n        ```\n   \n    - Activate virtual env:\n   \n        ``` \n        source env/bin/activate     # macOS/Linux\n        env\\Scripts\\activate.bat    # Windows\n        ```\n   \n    - Install requirements:\n    \n        ```\n        pip install -r requirements.txt\n        ```\n    \n    - Run and debug the docs:\n    \n        ```\n        mkdocs serve\n        ```\n\n4. Relevant files:\n\n    - all written documents are under `guide` directory\n    - `mkdocs.yml` at project root is a configuration file for Mkdocs\n    - `guide/assets` includes static assets for these docs\n    - `guide/overrides` includes customized template files that override default mkdocs-material templates\n\n5. After editing the docs, commit your changes and open a PR as\n   necessary. Travis CI is used to compile and publish the docs automatically\n   after each merge to master branch.\n"
  },
  {
    "path": "guide/assets/custom.css",
    "content": "/* add custom css here */\n.md-main__inner{padding-bottom: 3em}\n.page-intro{font-size:1.3em;line-height:1.7;}\n.highlighttable td pre {line-height: 1.9;}\n.md-tabs__item{font-weight: bold}\n\ntable {padding: 0;}\ntable tr {margin: 0;padding: 0; }\ntable tr th {font-weight: bold;}\ntable tr th :first-child, table tr td :first-child {margin-top: 0; }\ntable tr th :last-child, table tr td :last-child {margin-bottom: 0; }\n\n.md-typeset table:not([class]){\n    font-size: .68rem;\n    box-shadow: none;\n    background: var(--md-code-bg-color);\n}\ntable tr th,table tr td,\n.md-typeset table:not([class]) th,\n.md-typeset table:not([class]) td{\n    margin: 0; padding: .65em .95em;\n}\n.md-typeset code{\n    padding: 3px .35em;\n}\n.md-typeset table:not([class]) code {\n    font-size: .65rem;\n    white-space: nowrap;\n}\n.md-typeset table:not([class]) tr:hover{\n    transition: none;\n    background-color: transparent;\n    box-shadow: none;\n}\n.admonition > p{\n    font-size: 115%;\n}\n.admonition > p:not(first-child){\n    line-height: 1.8;\n}\np code{\n    white-space: nowrap;\n}\n"
  },
  {
    "path": "guide/assets/custom.js",
    "content": "window.dataLayer = window.dataLayer || [];\nfunction gtag() {\n  window.dataLayer.push(arguments);\n}\ngtag('js', new Date());\ngtag('config', 'G-6XB4XDVPX3');\n"
  },
  {
    "path": "guide/index.md",
    "content": "---\ndisqus: \"False\"\n---\n\n# Extension CLI\n\n<p class='page-intro'>is a command-line build tool for developing \nchromium browser extensions fast and in a standardized way. It provides a systematic way \nto organize, build, test and document extension projects.</p>\n\n* * *\n\n## Features\n\n🖥️ &nbsp; **Javascript Bundling** \n<br/>&nbsp;&nbsp; &nbsp; &nbsp; Compiles, bundles and minifies javascript files (supports ES6, ES2021 syntax) <br/>\n\n🎨 &nbsp; **CSS Bundling**\n<br/>&nbsp;&nbsp; &nbsp; &nbsp; Compiles, bundles, and minifies CSS and [SASS](https://sass-lang.com/guide) files <br/>\n\n💄 &nbsp; **Linting**\n<br/>&nbsp;&nbsp; &nbsp; &nbsp; lint JavaScript using [ESLint](https://eslint.org/) <br/>\n\n📦 &nbsp; **ZIP Generation**\n<br/>&nbsp;&nbsp; &nbsp; &nbsp; Generates a .zip file for uploading to extension marketplaces<br/>\n\n📝 &nbsp; **Document Source Code**\n<br/>&nbsp;&nbsp; &nbsp; &nbsp; Generates source code documentation using [JSDoc](https://jsdoc.app/about-getting-started.html) <br/>\n\n⚗️ &nbsp; **Unit Testing** \n<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/>\n\n⚔️ &nbsp; **Cross-Browser Compatibility**\n<br/>&nbsp;&nbsp; &nbsp; &nbsp; develop extensions for Chrome, Edge, Firefox, Opera and Brave. <br/>\n\n* * *\n\n\nExtension CLI is made and maintained free and voluntarily by\n<a href=\"https://github.com/MobileFirstLLC/extension-cli/graphs/contributors\" target=\"_blank\" rel=\"noreferrer noopener\">open source contributors</a> \nbehind several popular extensions. If you find it helpful, please share, star, or contribute to its development.\n\n<br/>\n\n<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>\n\n* * *\n\n## Getting Started\n\n**Note:** Using this CLI assumes you have Node.js installed (or [install it here](https://nodejs.org/en/download/)).\n\nCreate a new extension project:\n\n```bash\nnpx extension-cli\n```\n\nAdd CLI to an existing project:\n\n```bash\nnpm install extension-cli\n```\n\nMore detailed [getting started guide here &rarr;](https://oss.mobilefirst.me/extension-cli/01-getting-started/)\n \n\n## Command Reference\n\nCommand | Description\n--- | ---\n**`xt-build`** | Run builds; env flags: `-e prod` or `-e dev`\n**`xt-test`**| Run unit tests\n**`xt-docs`**| Generate docs\n**`xt-clean`** | Remove generated files\n**`xt-sync`**| Update project config files to latest versions supplied by this CLI\n\nMore detailed [command instructions and configuration options here &rarr;](https://oss.mobilefirst.me/extension-cli/03-xt-build/) \n\n"
  },
  {
    "path": "guide/overrides/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block disqus %}\n{% if not page.meta.disqus == \"False\" %}\n<h2 id=\"__comments\">{{ lang.t(\"meta.comments\") }}</h2>\n<script src=\"https://utteranc.es/client.js\"\n        repo=\"MobileFirstLLC/extension-cli\"\n        issue-term=\"pathname\"\n        label=\"documentation\"\n        theme=\"github-dark\"\n        crossorigin=\"anonymous\"\n        async>\n</script>\n\n{% endif %}\n{% endblock %}\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Extension CLI • User Guide\nsite_description: Command-line build tools for chromium extension development.\nsite_author: '@mobilefirstllc'\ndocs_dir: ./guide\nsite_url: https://oss.mobilefirst.me/extension-cli/\nrepo_url: https://github.com/MobileFirstLLC/extension-cli\nrepo_name: \"extension-cli\"\nedit_uri: blob/master/guide/\nuse_directory_urls: true\n\nnav:\n  - \"Getting Started\":\n    - Intro: index.md\n    - Installation: 01-getting-started.md\n    - Configuration: 02-configuration.md\n  - \"Commands\":\n    - 'extension-cli': 08-xt-create.md\n    - 'xt-build':\n      - Overview: 03-xt-build.md\n      - Manifest: 03-xt-build-manifest.md\n      - Building scripts: 03-xt-build-scripts.md\n      - Building styles: 03-xt-build-styles.md\n      - Copying files: 03-xt-build-copy.md\n      - Localization: 03-xt-build-locales.md\n      - Static assets: 03-xt-build-assets.md\n      - Commands: 03-xt-build-cmds.md\n    - 'xt-clean': 04-xt-clean.md\n    - 'xt-docs':\n      - Configuration: 05-xt-docs.md\n      - Templates: 05-xt-docs-templates.md\n    - 'xt-sync': 06-xt-sync.md\n    - 'xt-test': 07-xt-test.md\n  - \"Releases\":\n    - \"Version 1.x (latest)\": 09-release-notes.md\n    - \"Version 0.x\": 09-release-notes-0.md\n  - \"Developer Resources\":\n    - Helpful References: 12-helpful.md\n    - CLI Development:\n      - Overview: 13-cli-development.md\n      - Environment Setup: 13-dev-env.md\n    - Editing User Guide: 14-user-guide.md\n  - \"Source Code\":\n    - Github: https://github.com/MobileFirstLLC/extension-cli\n\nextra_css:\n - assets/custom.css\nextra_javascript:\n  - \"https://buttons.github.io/buttons.js\"\n  - \"https://www.googletagmanager.com/gtag/js?id=G-6XB4XDVPX3\"\n  - assets/custom.js\n\ntheme:\n  name: material\n  custom_dir: ./guide/overrides\n  logo: '/extension-cli/assets/images/guide_icon.svg'\n  favicon: '/extension-cli/assets/images/favicon.png'\n  features:\n    - navigation.tabs\n    - navigation.tabs.sticky\n    - navigation.expand\n  palette:\n    scheme: slate\n    primary: amber\n    accent: amber\n  font:\n    text: Inter\n  extra:\n    disqus: xyz # enable comments\n\nmarkdown_extensions:\n  - admonition\n  - pymdownx.inlinehilite\n  - pymdownx.superfences\n  - pymdownx.snippets\n  - pymdownx.magiclink\n  - pymdownx.snippets\n  - pymdownx.highlight:\n      use_pygments: true\n      linenums: true\n      linenums_style: pymdownx.inline\n  - meta\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"extension-cli\",\n  \"version\": \"1.2.5-alpha.0\",\n  \"description\": \"CLI tool for building browser extensions\",\n  \"homepage\": \"https://oss.mobilefirst.me/extension-cli\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/mobilefirstllc/extension-cli.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/mobilefirstllc/extension-cli.git\"\n  },\n  \"author\": {\n    \"name\": \"Mobile First\",\n    \"email\": \"hello@mobilefirst.me\",\n    \"url\": \"https://mobilefirst.me\"\n  },\n  \"funding\": [\n    {\n      \"type\": \"github\",\n      \"url\": \"https://github.com/sponsors/MobileFirstLLC\"\n    }\n  ],\n  \"engines\": {\n    \"node\": \">=12.0.0\"\n  },\n  \"bin\": {\n    \"extension-cli\": \"cli/xt-create.js\",\n    \"xt-build\": \"cli/xt-build.js\",\n    \"xt-docs\": \"cli/xt-docs.js\",\n    \"xt-test\": \"cli/xt-test.js\",\n    \"xt-clean\": \"cli/xt-clean.js\",\n    \"xt-sync\": \"cli/xt-sync.js\"\n  },\n  \"keywords\": [\n    \"chrome extensions\",\n    \"web extensions\",\n    \"browser extensions\",\n    \"command line\",\n    \"developer tools\",\n    \"utility\",\n    \"web\",\n    \"extensions\",\n    \"firefox\",\n    \"mozilla\",\n    \"add-ons\",\n    \"google\",\n    \"chrome\",\n    \"opera\",\n    \"edge\",\n    \"brave\"\n  ],\n  \"scripts\": {\n    \"test\": \"nyc mocha ./test/*.test.js --colors\",\n    \"test:report\": \"npm run test -- nyc report\",\n    \"test:travis\": \"npm run test && nyc report --reporter=text-lcov | coveralls\",\n    \"alpha:test\": \"npx standard-version --dry-run --prerelease alpha\",\n    \"patch:test\": \"npx standard-version --dry-run --release-as patch\",\n    \"minor:test\": \"npx standard-version --dry-run --release-as minor\",\n    \"minor:beta:test\": \"npx standard-version --dry-run --release-as minor --prerelease beta\",\n    \"minor:alpha:test\": \"npx standard-version --dry-run --release-as minor --prerelease alpha\",\n    \"major:alpha:test\": \"npx standard-version --dry-run --release-as major --prerelease alpha\",\n    \"major:test\": \"npx standard-version --dry-run --release-as major\",\n    \"alpha\": \"npx standard-version --prerelease alpha\",\n    \"patch\": \"npx standard-version --release-as patch\",\n    \"minor\": \"npx standard-version --release-as minor\",\n    \"patch:alpha\": \"npx standard-version --release-as patch --prerelease alpha\",\n    \"minor:alpha\": \"npx standard-version --release-as minor --prerelease alpha\",\n    \"major:alpha\": \"npx standard-version --release-as major --prerelease alpha\",\n    \"major\": \"npx standard-version --release-as major\"\n  },\n  \"license\": \"MIT\",\n  \"standard-version\": {\n    \"infile\": \".github/changelog.md\"\n  },\n  \"dependencies\": {\n    \"@babel/preset-env\": \"7.16.11\",\n    \"@babel/register\": \"7.17.7\",\n    \"chai\": \"4.3.6\",\n    \"chalk\": \"4.1.2\",\n    \"cli-spinner\": \"0.2.10\",\n    \"commander\": \"9.0.0\",\n    \"del\": \"6.0.0\",\n    \"eslint\": \"8.11.0\",\n    \"gulp\": \"4.0.2\",\n    \"gulp-change\": \"1.0.2\",\n    \"gulp-clean-css\": \"4.3.0\",\n    \"gulp-concat\": \"2.6.1\",\n    \"gulp-htmlmin\": \"5.0.1\",\n    \"gulp-jsonminify\": \"1.1.0\",\n    \"gulp-load-plugins\": \"2.0.7\",\n    \"gulp-merge-json\": \"2.1.1\",\n    \"gulp-rename\": \"2.0.0\",\n    \"gulp-sass\": \"5.1.0\",\n    \"gulp-zip\": \"5.1.0\",\n    \"jsdoc\": \"3.6.10\",\n    \"jsdom\": \"19.0.0\",\n    \"jsdom-global\": \"3.0.2\",\n    \"mocha\": \"9.2.2\",\n    \"nyc\": \"15.1.0\",\n    \"prompts\": \"2.4.2\",\n    \"sass\": \"1.49.9\",\n    \"sinon\": \"13.0.1\",\n    \"sinon-chrome\": \"3.0.1\",\n    \"webpack-stream\": \"7.0.0\",\n    \"yargs\": \"17.3.1\"\n  },\n  \"devDependencies\": {\n    \"coveralls\": \"3.1.1\"\n  }\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"labels\": [\"dependencies\"],\n  \"extends\": [\"config:base\", \":disableDependencyDashboard\"]\n}\n"
  },
  {
    "path": "requirements.txt",
    "content": "mkdocs\nmkdocs-material\n"
  },
  {
    "path": "test/README.md",
    "content": "# CLI Unit Tests\n\nAll unit tests for extension-CLI are in this directory.\n"
  },
  {
    "path": "test/cli-create.test.js",
    "content": "// TODO: #21: how to require this without actually running the command?\n// const BuildScript = require('../cli/xt-build');\n\n// const expect = require('chai').expect;\n\ndescribe('create command', () => {\n\n    // placeholder; replace this with actual test\n    // it('...(dummy test)', async () => {\n    //     expect(true).to.equal(true);\n    // });\n\n});\n"
  },
  {
    "path": "test/cli-utilities.test.js",
    "content": "const Utilities = require('../cli/utilities').Utilities;\nconst sinon = require('sinon');\nconst expect = require('chai').expect;\nconst fs = require('fs');\n\ndescribe('Test utility functions', () => {\n\n    /**\n     * Stub Node.js IO methods\n     */\n    // eslint-disable-next-line no-undef\n    beforeEach(() => {\n        sinon.stub(fs, 'readFileSync');\n        sinon.stub(fs, 'existsSync');\n        sinon.stub(fs, 'mkdirSync');\n        sinon.stub(fs, 'readdirSync');\n        sinon.stub(fs, 'writeFileSync');\n        sinon.stub(fs, 'createReadStream');\n        sinon.stub(fs, 'createWriteStream');\n        sinon.stub(fs, 'lstatSync');\n        sinon.stub(fs, 'copyFileSync');\n        sinon.stub(fs, 'symlinkSync');\n        sinon.stub(fs, 'readlinkSync');\n\n        fs.createReadStream.returns({\n            pipe: () => true\n        });\n    });\n\n    /**\n     * Restore stubbed IO methods\n     */\n    // eslint-disable-next-line no-undef\n    afterEach(() => {\n        fs.existsSync.restore();\n        fs.mkdirSync.restore();\n        fs.readdirSync.restore();\n        fs.readFileSync.restore();\n        fs.writeFileSync.restore();\n        fs.createReadStream.restore();\n        fs.createWriteStream.restore();\n        fs.lstatSync.restore();\n        fs.copyFileSync.restore();\n        fs.symlinkSync.restore();\n        fs.readlinkSync.restore();\n    });\n\n    describe('generateDirectoryName...', () => {\n\n        it('...returns lowercase name', () => {\n            expect(Utilities.generateDirectoryName('HELLO WORLD'))\n                .to.equal('hello-world');\n            expect(Utilities.generateDirectoryName('my app name'))\n                .to.equal('my-app-name');\n            expect(Utilities.generateDirectoryName('APP-APP'))\n                .to.equal('app-app');\n            expect(Utilities.generateDirectoryName('test789'))\n                .to.equal('test789');\n        });\n\n        it('...replaces special characters with hyphen', () => {\n            expect(Utilities.generateDirectoryName('MyAwesome#@Thing'))\n                .to.equal('myawesome-thing');\n            expect(Utilities.generateDirectoryName('````', 'xyz'))\n                .to.equal('xyz');\n        });\n\n        it('...removes trailing hyphen', () => {\n            expect(Utilities.generateDirectoryName('Hello World!!!'))\n                .to.equal('hello-world');\n            expect(Utilities.generateDirectoryName('awesom-o app#$%%'))\n                .to.equal('awesom-o-app');\n        });\n\n        it('...returns default name instead of empty string', () => {\n            expect(Utilities.generateDirectoryName('', 'foobar'))\n                .to.equal('foobar');\n            expect(Utilities.generateDirectoryName(null).length)\n                .to.be.greaterThan(0);\n        });\n    });\n\n    describe('replaceVars', () => {\n        it('...replaces one variable', () => {\n            expect(Utilities.replaceVars(\n                'your ${myVar}?', {myVar: 'name'}))\n                .to.equal('your name?');\n        });\n        it('...replace two variables', () => {\n            expect(Utilities.replaceVars(\n                'test ${x} ${y}', {x: '1', y: 'z'}))\n                .to.equal('test 1 z');\n        });\n        it('...ignores non-matching keys', () => {\n            expect(Utilities.replaceVars(\n                'no ${match} for this', {}))\n                .to.equal('no ${match} for this');\n        });\n        it('...returns input if it contains no variables', () => {\n            expect(Utilities.replaceVars(\n                'return me', {me: 'test'}))\n                .to.equal('return me');\n        });\n        it('...interpolation syntax must match', () => {\n            expect(Utilities.replaceVars(\n                'return {me}', {me: 'test'}))\n                .to.equal('return {me}');\n            expect(Utilities.replaceVars(\n                'return $me2', {me2: 'test'}))\n                .to.equal('return $me2');\n        });\n    });\n\n    describe('keyReplace', () => {\n        it('...simple override', () => {\n            let b = {x: 10};\n\n            Utilities.keyReplace({x: 8}, b);\n            expect(b.x).to.equal(8);\n        });\n        it('...performs union', () => {\n            let b = {y: 10};\n\n            Utilities.keyReplace({x: 8}, b);\n            expect(b).to.have.keys(['x', 'y']);\n        });\n        it('...replaces array', () => {\n            let b = {arr: [1, 2, 3]};\n\n            Utilities.keyReplace({arr: [4, 5]}, b);\n            expect(b.arr).to.have.length(2)\n                .and.to.contain(4).and.to.contain(5);\n        });\n        it('...replaces nested properties', () => {\n            let b = {c: {d: 8, e: 10}};\n\n            Utilities.keyReplace({c: {e: 11}}, b);\n            expect(b.c.d).to.equal(8);\n            expect(b.c.e).to.equal(11);\n        });\n\n        it('...nested replace with addition', () => {\n            let b = {b: 8, c: {y: 9}};\n\n            Utilities.keyReplace({a: 1, b: 5, c: {x: 1}}, b);\n            expect(b.a).to.equal(1);\n            expect(b.b).to.equal(5);\n            expect(b.c.x).to.equal(1);\n            expect(b.c.y).to.equal(9);\n        });\n    });\n\n    describe('iterateConfigs', () => {\n        const defaultConfig = {name: 'my app', version: '0.0.1'};\n\n        it('...returns default if project config is undefined', () => {\n            const result = Utilities.iterateConfigs(defaultConfig, undefined);\n\n            expect(result.name).to.equal(defaultConfig.name);\n            expect(result.version).to.equal(defaultConfig.version);\n            expect(Object.keys(result)).to.have.length(2);\n        });\n\n        it('...overrides defaults when override is specified', () => {\n            let projectConfig = {name: 'my awesome app', version: '1.0.0'};\n            const result = Utilities.iterateConfigs(defaultConfig, projectConfig);\n\n            expect(result.name).to.equal(projectConfig.name);\n            expect(result.version).to.equal(projectConfig.version);\n            expect(Object.keys(result)).to.have.length(2);\n        });\n\n        it('...appends new keys when not specified in default', () => {\n            let projectConfig = {special: {value: 5}};\n            const result = Utilities.iterateConfigs(defaultConfig, projectConfig);\n\n            expect(result.special.value).to.equal(projectConfig.special.value);\n            expect(Object.keys(result)).to.have.length(3);\n        });\n    });\n\n    describe('copyFolderSync', () => {\n\n        it('...copies directory with files to new location', () => {\n            fs.readdirSync.returns(['file1.txt', 'file2.txt']);\n            fs.lstatSync.returns({isFile: () => true});\n            Utilities.copyFolderSync('./test_dir', './test_dir_2');\n            expect(fs.mkdirSync.calledOnce).to.equal(true);\n            expect(fs.copyFileSync.calledTwice).to.equal(true);\n        });\n\n        it('...iterates nested directories recursively', () => {\n            fs.readdirSync.returns(['test']);\n            fs.lstatSync.onCall(0).returns({\n                isFile: () => false,\n                isSymbolicLink: () => false,\n                isDirectory: () => true\n            });\n            fs.lstatSync.onCall(1).returns({\n                isFile: () => false,\n                isSymbolicLink: () => true\n            });\n            Utilities.copyFolderSync('./test_dir', './test_dir2');\n            expect(fs.readdirSync.callCount).to.equal(2);\n        });\n\n        it('...does nothing when not file/dir/symlink', () => {\n            sinon.spy(Utilities, 'copyFolderSync');\n            fs.readdirSync.returns(['invalid']);\n            fs.lstatSync.onCall(0).returns({\n                isFile: () => false,\n                isSymbolicLink: () => false,\n                isDirectory: () => false\n            });\n            Utilities.copyFolderSync('a', 'b');\n            expect(fs.copyFileSync.callCount).to.equal(0);\n            expect(fs.symlinkSync.callCount).to.equal(0);\n            expect(Utilities.copyFolderSync.callCount).to.equal(1);\n        });\n\n    });\n\n    describe('copyFile', () => {\n\n        it('...copies file from old location to new location', () => {\n            Utilities.copyFile('./test1', './test2');\n            expect(fs.createReadStream.calledOnce).to.equal(true);\n            expect(fs.createWriteStream.calledOnce).to.equal(true);\n        });\n\n    });\n\n    describe('readFile', () => {\n\n        it('...calls read file', () => {\n            Utilities.readFile('xyz');\n            expect(fs.readFileSync.calledOnce).to.equal(true);\n        });\n    });\n\n    describe('writeFile', () => {\n\n        it('...calls write file', () => {\n            Utilities.writeFile('xyz', 'text content...');\n            expect(fs.writeFileSync.calledOnce).to.equal(true);\n        });\n\n    });\n\n    describe('fileExists', () => {\n\n        it('...returns true for existing file', () => {\n            fs.existsSync.returns(true);\n            expect(Utilities.fileExists('im_here')).to.equal(true);\n        });\n\n        it('...returns false when file does not exist', () => {\n            fs.existsSync.returns(false);\n            expect(Utilities.fileExists('nope')).to.equal(false);\n        });\n\n    });\n\n    describe('createDir', () => {\n\n        it('...will create a directory when it doesn\\'t exist', () => {\n            fs.existsSync.returns(false);\n            const result = Utilities.createDir('my_dir');\n\n            expect(fs.mkdirSync.calledOnce).to.equal(true);\n            expect(result).to.equal(true);\n        });\n\n        it('...returns true for empty folder', () => {\n            fs.existsSync.returns(true);\n            fs.readdirSync.returns({length: 0});\n            const result = Utilities.createDir('empty_dir');\n\n            expect(fs.mkdirSync.notCalled).to.equal(true);\n            expect(result).to.equal(true);\n        });\n\n        it('...returns false for non-empty folder', () => {\n            fs.existsSync.returns(true);\n            fs.readdirSync.returns({length: 1});\n            expect(Utilities.createDir('non_empty_dir')).to.equal(false);\n        });\n\n    });\n\n    describe('readJSON', () => {\n\n        it('...returns a parsed JSON object', () => {\n            fs.readFileSync.returns('{ \"title\" : \"test\" }');\n            const obj = Utilities.readJSON('xyz');\n\n            expect(obj.title).to.equal('test');\n        });\n\n        it('...throws error for non-JSON format file content', () => {\n            fs.readFileSync.returns('this is some plain text');\n            expect(() => Utilities.readJSON('xyz')).to\n                .throw('Unexpected token');\n        });\n\n    });\n\n    describe('readAndReplaceTextFile', () => {\n\n        it('...replaces single variable', () => {\n            fs.readFileSync.returns('Text with ${variable} in the middle!');\n            const variables = {variable: 'find me'};\n            const result = Utilities.readAndReplaceTextFile('some_file', variables);\n\n            expect(result).to.contain(variables.variable);\n        });\n\n        it('...replaces multiple variables', () => {\n            fs.readFileSync.returns('Some math ${a} + ${b} = ${c}');\n            const math = {a: 1, b: 2, c: 3};\n            const result = Utilities.readAndReplaceTextFile('my_file', math);\n\n            expect(result).to.contain('1 + 2 = 3');\n        });\n\n    });\n\n    describe('readAndReplaceJSONFile', () => {\n\n        it('...replaces variables in an object', () => {\n            fs.readFileSync.returns('{ \"name\":\"${name}\", \"version\" : \"v-${version}\" }');\n            const values = {name: 'my_app', version: '1.0.0'};\n            const jsonString = Utilities.readAndReplaceJSONFile('manifest', values);\n            const obj = JSON.parse(jsonString);\n\n            expect(obj.name).to.equal(values.name);\n            expect(obj.version).to.equal('v-1.0.0');\n        });\n\n        it('...replaces nested variables', () => {\n            fs.readFileSync.returns('{ \"config\": { \"count\" : \"${n}\" }}');\n            const values = {n: 10};\n            const result = Utilities.readAndReplaceJSONFile('manifest', values);\n\n            expect(result).to.contain('10');\n        });\n\n    });\n});\n"
  }
]