├── .eslintignore ├── .eslintrc.json ├── .github ├── release-please.yml ├── release-trigger.yml └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .mocharc.json ├── .pre-commit-hooks.yaml ├── .prettierrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── js-green-licenses.json ├── package-lock.json ├── package.json ├── renovate.json ├── src ├── clean.ts ├── cli.ts ├── index.ts ├── init.ts └── util.ts ├── template └── index.ts ├── test ├── fixtures │ └── kitchen │ │ ├── package.json │ │ └── src │ │ ├── samples.js │ │ └── server.ts ├── kitchen.ts ├── test-clean.ts ├── test-init.ts └── test-util.ts ├── tsconfig-google.json └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test/fixtures/ 3 | template/ 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:n/recommended", "prettier"], 3 | "plugins": ["n", "prettier"], 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "block-scoped-var": "error", 7 | "eqeqeq": "error", 8 | "no-var": "error", 9 | "prefer-const": "error", 10 | "eol-last": "error", 11 | "prefer-arrow-callback": "error", 12 | "no-trailing-spaces": "error", 13 | "quotes": ["warn", "single", {"avoidEscape": true}], 14 | "no-restricted-properties": [ 15 | "error", 16 | { 17 | "object": "describe", 18 | "property": "only" 19 | }, 20 | { 21 | "object": "it", 22 | "property": "only" 23 | } 24 | ] 25 | }, 26 | "overrides": [ 27 | { 28 | "files": ["**/*.ts", "**/*.tsx"], 29 | "parser": "@typescript-eslint/parser", 30 | "extends": ["plugin:@typescript-eslint/recommended"], 31 | "rules": { 32 | "@typescript-eslint/ban-ts-comment": "warn", 33 | "@typescript-eslint/no-floating-promises": "error", 34 | "@typescript-eslint/no-non-null-assertion": "off", 35 | "@typescript-eslint/no-use-before-define": "off", 36 | "@typescript-eslint/no-warning-comments": "off", 37 | "@typescript-eslint/no-empty-function": "off", 38 | "@typescript-eslint/no-var-requires": "off", 39 | "@typescript-eslint/explicit-function-return-type": "off", 40 | "@typescript-eslint/explicit-module-boundary-types": "off", 41 | "@typescript-eslint/ban-types": "off", 42 | "@typescript-eslint/camelcase": "off", 43 | "n/no-missing-import": "off", 44 | "n/no-empty-function": "off", 45 | "n/no-unsupported-features/es-syntax": "off", 46 | "n/no-missing-require": "off", 47 | "n/shebang": "off", 48 | "no-dupe-class-members": "off", 49 | "require-atomic-updates": "off" 50 | }, 51 | "parserOptions": { 52 | "ecmaVersion": 2018, 53 | "project": "./tsconfig.json", 54 | "sourceType": "module" 55 | } 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | releaseType: node 2 | handleGHRelease: true 3 | primaryBranch: main 4 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [$default-branch] 4 | pull_request: 5 | name: ci 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | node: [18, 20, 22] 13 | steps: 14 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: npm i --production --engine-strict --ignore-scripts 19 | - run: node --version 20 | - run: npm ci 21 | - run: npm test 22 | - name: coverage 23 | uses: codecov/codecov-action@v4 24 | with: 25 | name: actions ${{ matrix.node }} 26 | windows: 27 | runs-on: windows-latest 28 | steps: 29 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 30 | - uses: actions/setup-node@v4 31 | with: 32 | node-version: 18 33 | - run: npm ci 34 | - run: npm run system-test 35 | system_test: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 39 | - uses: actions/setup-node@v4 40 | with: 41 | node-version: 18 42 | - run: npm ci 43 | - run: npm run system-test 44 | lint: 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 48 | - uses: actions/setup-node@v4 49 | with: 50 | node-version: 18 51 | - run: npm ci 52 | - run: npm run lint 53 | license_check: 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 57 | - uses: actions/setup-node@v4 58 | with: 59 | node-version: 18 60 | - run: npm ci 61 | - run: npm run license-check 62 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | name: release 5 | jobs: 6 | release-please: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 10 | - uses: actions/setup-node@v4 11 | with: 12 | node-version: 18 13 | registry-url: 'https://wombat-dressing-room.appspot.com' 14 | - run: npm ci 15 | - run: npm publish 16 | env: 17 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .vscode 4 | build 5 | coverage 6 | node_modules 7 | npm-debug.log 8 | yarn-error.log 9 | yarn.lock 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "enable-source-maps": true, 3 | "timeout": 240000, 4 | "throw-deprecation": true 5 | } 6 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: gts 2 | name: gts 3 | description: 'gts: TypeScript style guide, formatter, and linter.' 4 | entry: gts lint 5 | language: node 6 | language_version: system 7 | minimum_pre_commit_version: 2.9.2 8 | require_serial: true 9 | types_or: [javascript, jsx, ts, tsx] 10 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [6.0.2](https://github.com/google/gts/compare/v6.0.1...v6.0.2) (2024-10-25) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * TS `lib` Support for Node 18+ ([#913](https://github.com/google/gts/issues/913)) ([82aa8e3](https://github.com/google/gts/commit/82aa8e33869b9d91ac426afbe3027e6b6f7b90b3)) 9 | 10 | ## [6.0.1](https://github.com/google/gts/compare/v6.0.0...v6.0.1) (2024-10-22) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * Allow `typescript` v5+ as a peer dependency ([#909](https://github.com/google/gts/issues/909)) ([996aaec](https://github.com/google/gts/commit/996aaece999033e2686300477bdae21debf62368)) 16 | 17 | ## [6.0.0](https://github.com/google/gts/compare/v5.3.1...v6.0.0) (2024-10-10) 18 | 19 | 20 | ### ⚠ BREAKING CHANGES 21 | 22 | * Update '.prettierrc.json' to include `trailingComma: "all"` to match internal Google config ([#822](https://github.com/google/gts/issues/822)) 23 | * Update `typescript` and other dependencies ([#902](https://github.com/google/gts/issues/902)) 24 | * Set `no-floating-promises` to `error` ([#901](https://github.com/google/gts/issues/901)) 25 | * Set `composite: true` in `tsconfig-google.json` ([#899](https://github.com/google/gts/issues/899)) 26 | * Set `stripInternal` in `tsconfig-google.json` ([#900](https://github.com/google/gts/issues/900)) 27 | * Support Node 18+ ([#896](https://github.com/google/gts/issues/896)) 28 | 29 | ### Features 30 | 31 | * Set `composite: true` in `tsconfig-google.json` ([#899](https://github.com/google/gts/issues/899)) ([71972dc](https://github.com/google/gts/commit/71972dc0e5611fbbe8b3885eb18d19563545cd4b)) 32 | * Set `no-floating-promises` to `error` ([#901](https://github.com/google/gts/issues/901)) ([1d28f92](https://github.com/google/gts/commit/1d28f92df53b9efd3924e4dbf11dc71dd8091d36)) 33 | * Set `stripInternal` in `tsconfig-google.json` ([#900](https://github.com/google/gts/issues/900)) ([9b37243](https://github.com/google/gts/commit/9b37243804dcca9a69b00142706f60f36b89e405)) 34 | * Support Node 18+ ([#896](https://github.com/google/gts/issues/896)) ([f011fa3](https://github.com/google/gts/commit/f011fa3ae9628ed72eeec6095cf8b9c0813317e7)) 35 | * Update '.prettierrc.json' to include `trailingComma: "all"` to match internal Google config ([#822](https://github.com/google/gts/issues/822)) ([27d0d93](https://github.com/google/gts/commit/27d0d93cdeedd885fa494c875957edf6399202e0)) 36 | * Update `typescript` and other dependencies ([#902](https://github.com/google/gts/issues/902)) ([1c18b3a](https://github.com/google/gts/commit/1c18b3ae1a54bbcbb587518dede9bfaf8fd5e5dd)) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **deps:** update dependency eslint to v8.57.1 ([#903](https://github.com/google/gts/issues/903)) ([23da8ef](https://github.com/google/gts/commit/23da8ef208a8ba957c2fc5fead4c72b98448d35d)) 42 | * **deps:** update dependency eslint-plugin-prettier to v5.2.1 ([#894](https://github.com/google/gts/issues/894)) ([a6d5e6a](https://github.com/google/gts/commit/a6d5e6a24037e4ed3b2a56861ef7b7e1e20982e4)) 43 | * **deps:** update dependency prettier to v3.3.3 ([#883](https://github.com/google/gts/issues/883)) ([52dca7c](https://github.com/google/gts/commit/52dca7c40808eda19e12a840a058581097dd2c9f)) 44 | 45 | ## [5.3.1](https://github.com/google/gts/compare/v5.3.0...v5.3.1) (2024-04-10) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * **deps:** replace dependency eslint-plugin-node with eslint-plugin-n ([#865](https://github.com/google/gts/issues/865)) ([efbe3a8](https://github.com/google/gts/commit/efbe3a838f40959188190dc04b4f11f45dd1aa87)) 51 | * **deps:** update dependency eslint to v8.57.0 ([#833](https://github.com/google/gts/issues/833)) ([0c0a45c](https://github.com/google/gts/commit/0c0a45c83832034ed96c19c0f7956180587991ed)) 52 | * **deps:** update dependency prettier to v3.2.5 ([#846](https://github.com/google/gts/issues/846)) ([7e60e38](https://github.com/google/gts/commit/7e60e3878b877865b3536ea66dc3607d9243cee4)) 53 | 54 | 55 | ### Performance Improvements 56 | 57 | * Supercharge Performance & Efficiency: Leveraging `Promise.all` for Resource-Friendly Tasks 🚤 ([#838](https://github.com/google/gts/issues/838)) ([7424fe1](https://github.com/google/gts/commit/7424fe19f822dc152315c1b1eb5f874512a88b55)) 58 | 59 | ## [5.3.0](https://github.com/google/gts/compare/v5.2.0...v5.3.0) (2024-03-21) 60 | 61 | 62 | ### Features 63 | 64 | * adding pre-commit-hooks.yaml ([#858](https://github.com/google/gts/issues/858)) ([b17994d](https://github.com/google/gts/commit/b17994d2f26f0cacaf3e7956dc01bc644a32b5ae)) 65 | 66 | 67 | ### Bug Fixes 68 | 69 | * **deps:** update dependency eslint to v8.51.0 ([#812](https://github.com/google/gts/issues/812)) ([ae913c1](https://github.com/google/gts/commit/ae913c17ae4460d0f76aad16b96cb3e1f23a5b89)) 70 | * **deps:** update dependency eslint to v8.52.0 ([#821](https://github.com/google/gts/issues/821)) ([50b3ce5](https://github.com/google/gts/commit/50b3ce56190a5f785c52b00b8da56769255caaa1)) 71 | * **deps:** update dependency eslint to v8.53.0 ([#829](https://github.com/google/gts/issues/829)) ([7d9ffed](https://github.com/google/gts/commit/7d9ffed35d0ee076bda063e5189573eeba82ac0a)) 72 | * **deps:** update dependency eslint-config-prettier to v9.1.0 ([#836](https://github.com/google/gts/issues/836)) ([9105ebb](https://github.com/google/gts/commit/9105ebb83516746503f8714914fc62da0e7fe1a6)) 73 | * **deps:** update dependency eslint-plugin-prettier to v5.0.1 ([#817](https://github.com/google/gts/issues/817)) ([89b8955](https://github.com/google/gts/commit/89b8955576d1997d2bc587b79bf57d3c4d07cdda)) 74 | * **deps:** update dependency eslint-plugin-prettier to v5.1.2 ([#839](https://github.com/google/gts/issues/839)) ([b5ab5c4](https://github.com/google/gts/commit/b5ab5c495e6da286ac57dc285586806f612913b8)) 75 | * **deps:** update dependency eslint-plugin-prettier to v5.1.3 ([#845](https://github.com/google/gts/issues/845)) ([6e13e12](https://github.com/google/gts/commit/6e13e12b9d4f82fc43ef015980d5345fb47b9a41)) 76 | * **deps:** update dependency prettier to v3.1.0 ([#832](https://github.com/google/gts/issues/832)) ([faf6d7e](https://github.com/google/gts/commit/faf6d7e60e7a382077de8fd7c8c5a9ec065259a5)) 77 | * **deps:** update dependency prettier to v3.1.1 ([#837](https://github.com/google/gts/issues/837)) ([6de3e3b](https://github.com/google/gts/commit/6de3e3b9a741e6ff0e34996848a2ac1f8957bb32)) 78 | 79 | ## [5.2.0](https://github.com/google/gts/compare/v5.1.1...v5.2.0) (2023-10-04) 80 | 81 | 82 | ### Features 83 | 84 | * warn for ts-ignore comments for ESM ([#810](https://github.com/google/gts/issues/810)) ([350fbf0](https://github.com/google/gts/commit/350fbf0486c7470123eef19a8ab1816d1d05a6ee)) 85 | 86 | ## [5.1.1](https://github.com/google/gts/compare/v5.1.0...v5.1.1) (2023-10-04) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * revert feat: no-floating-promises ([44de7f7](https://github.com/google/gts/commit/44de7f705cea94d4781e4eb4b2d71a4ee4f0e89d)) 92 | 93 | ## [5.1.0](https://github.com/google/gts/compare/v5.0.1...v5.1.0) (2023-09-29) 94 | 95 | 96 | ### Features 97 | 98 | * no-floating-promises ([#756](https://github.com/google/gts/issues/756)) ([c93e733](https://github.com/google/gts/commit/c93e73316164137e29daa7bea8a48083f7d7c1da)) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * **deps:** update dependency eslint to v8.49.0 ([#784](https://github.com/google/gts/issues/784)) ([5e2a05c](https://github.com/google/gts/commit/5e2a05c12886dc63a60219e0c0830846de7c24b9)) 104 | * **deps:** update dependency eslint to v8.50.0 ([#802](https://github.com/google/gts/issues/802)) ([794abf3](https://github.com/google/gts/commit/794abf30e4ee1d4655436ad9efa11ee031027993)) 105 | * **deps:** update dependency eslint-config-prettier to v8.10.0 ([#785](https://github.com/google/gts/issues/785)) ([5391d89](https://github.com/google/gts/commit/5391d89de2b8af68b63954b01b90f88015258406)) 106 | * **deps:** update dependency eslint-config-prettier to v9 ([#777](https://github.com/google/gts/issues/777)) ([470977a](https://github.com/google/gts/commit/470977a2bc7b29db0e4abb36c362920ec16381c5)) 107 | * **deps:** update dependency prettier to v3.0.3 ([#782](https://github.com/google/gts/issues/782)) ([5a04e76](https://github.com/google/gts/commit/5a04e76c74cb4984cbf9c7f7fbee4e800b33ca52)) 108 | * run eslint from PATH ([#654](https://github.com/google/gts/issues/654)) ([5dc2a76](https://github.com/google/gts/commit/5dc2a76aae06e5e46b6b623447837c77b58cd757)) 109 | 110 | ## [5.0.1](https://github.com/google/gts/compare/v5.0.0...v5.0.1) (2023-08-21) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * line up linting with owlbot post-processing linting ([#778](https://github.com/google/gts/issues/778)) ([a731fe9](https://github.com/google/gts/commit/a731fe9aef6d0003fe229627522ab8250a9222d9)) 116 | 117 | ## [5.0.0](https://github.com/google/gts/compare/v4.0.1...v5.0.0) (2023-07-26) 118 | 119 | 120 | ### ⚠ BREAKING CHANGES 121 | 122 | * Upgrade to node 14 as the minimum version ([#771](https://github.com/google/gts/issues/771)) 123 | 124 | ### Bug Fixes 125 | 126 | * **deps:** update dependency prettier to ~2.8.0 ([#743](https://github.com/google/gts/issues/743)) ([7582516](https://github.com/google/gts/commit/75825165ea32bb9bcd5013223d5e5dff2efa731c)) 127 | * update eslint-prettier ([43d4a06](https://github.com/google/gts/commit/43d4a06a27565b7d3839432c6f8267d254f6a002)) 128 | 129 | 130 | ### Miscellaneous Chores 131 | 132 | * Upgrade to node 14 as the minimum version ([#771](https://github.com/google/gts/issues/771)) ([6301178](https://github.com/google/gts/commit/6301178c859361ddf8dfd678f94fc80ad5b7e38f)) 133 | 134 | ## [4.0.1](https://github.com/google/gts/compare/v4.0.0...v4.0.1) (2023-01-09) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * **deps:** bump json5 from 2.2.1 to 2.2.2 addressing CVE-2022-46175 ([2a1fd61](https://github.com/google/gts/commit/2a1fd614620f0dc26cc6f12c3b05a3088409b923)) 140 | 141 | ## [4.0.0](https://github.com/google/gts/compare/v3.1.0...v4.0.0) (2022-07-04) 142 | 143 | 144 | ### ⚠ BREAKING CHANGES 145 | 146 | * drop support for node.js 10.x (#686) 147 | 148 | ### Features 149 | 150 | * generate .editorconfig ([#500](https://github.com/google/gts/issues/500)) ([81397e0](https://github.com/google/gts/commit/81397e0fd9a7f141c00b52f47c3c5d9a921292ad)) 151 | 152 | 153 | ### Bug Fixes 154 | 155 | * **deps:** update dependency eslint-config-prettier to v8 ([#624](https://github.com/google/gts/issues/624)) ([23a4abb](https://github.com/google/gts/commit/23a4abb50a539a93a7d1043669b2e42a887415fa)) 156 | * **deps:** update dependency eslint-plugin-prettier to v4 ([#657](https://github.com/google/gts/issues/657)) ([5408bfe](https://github.com/google/gts/commit/5408bfeda4eb9cc22fb948442cda4fb6da631ed7)) 157 | * **deps:** update dependency prettier to ~2.5.0 ([#660](https://github.com/google/gts/issues/660)) ([8789fd4](https://github.com/google/gts/commit/8789fd42388aead5cb572a543ae218563b21ac94)) 158 | * **deps:** update dependency prettier to ~2.6.0 ([#670](https://github.com/google/gts/issues/670)) ([2feba2c](https://github.com/google/gts/commit/2feba2cdf8884420349256420c8b5ccc9cb858c8)) 159 | * **deps:** update dependency prettier to ~2.7.0 ([#696](https://github.com/google/gts/issues/696)) ([3c677fd](https://github.com/google/gts/commit/3c677fdbad4772aea0be25c1cdd8149a88b0d735)) 160 | * **deps:** update dependency write-file-atomic to v4 ([#687](https://github.com/google/gts/issues/687)) ([f16a3e1](https://github.com/google/gts/commit/f16a3e1a1101bc5dab90ac3ab1437dd7758adf4e)) 161 | * **deps:** update typescript-eslint monorepo to v5 ([#688](https://github.com/google/gts/issues/688)) ([ed2fd0c](https://github.com/google/gts/commit/ed2fd0ce1be4826239b97bc1c8fdae5c61c50e62)) 162 | * **deps:** upgrade to eslint 8.x ([#693](https://github.com/google/gts/issues/693)) ([7ae5c8b](https://github.com/google/gts/commit/7ae5c8b827abb41844ba6f533821bf3d0a7f302b)) 163 | * drop update notifier ([#706](https://github.com/google/gts/issues/706)) ([cab7704](https://github.com/google/gts/commit/cab7704389c2ba7e8e426da08397af47991d8596)) 164 | * pin prettier to 2.3.x ([#641](https://github.com/google/gts/issues/641)) ([323fb4a](https://github.com/google/gts/commit/323fb4acacc9bfc1fcba06b27135a77acc54b15a)) 165 | 166 | 167 | ### Build System 168 | 169 | * drop support for node.js 10.x ([#686](https://github.com/google/gts/issues/686)) ([12cd913](https://github.com/google/gts/commit/12cd913b6e9eb97e52b1cf3a275aadfa4517fdcb)) 170 | 171 | ## [3.1.0](https://www.github.com/google/gts/compare/v3.0.3...v3.1.0) (2021-01-11) 172 | 173 | 174 | ### Features 175 | 176 | * support comments in JSON ([#571](https://www.github.com/google/gts/issues/571)) ([cb6d2ca](https://www.github.com/google/gts/commit/cb6d2cacb5de7bcc9c8e82dd47e14fc5bf9596a3)) 177 | 178 | 179 | ### Bug Fixes 180 | 181 | * **deps:** update dependency eslint-config-prettier to v7 ([#601](https://www.github.com/google/gts/issues/601)) ([6e26681](https://www.github.com/google/gts/commit/6e266812da4b90b18e2abead9b2b5a1ca0c6654b)) 182 | * **deps:** upgrade to latest version of meow ([#616](https://www.github.com/google/gts/issues/616)) ([634bad9](https://www.github.com/google/gts/commit/634bad9bbbdb4d397bba101dc38ab14881172a30)) 183 | 184 | ### [3.0.3](https://www.github.com/google/gts/compare/v3.0.2...v3.0.3) (2020-12-03) 185 | 186 | 187 | ### Bug Fixes 188 | 189 | * **deps:** update dependency execa to v5 ([#600](https://www.github.com/google/gts/issues/600)) ([4e5f1e5](https://www.github.com/google/gts/commit/4e5f1e54facf53588bbb3b025b5240edbd7f3c8a)) 190 | * **deps:** update dependency meow to v8 ([#591](https://www.github.com/google/gts/issues/591)) ([c7e223e](https://www.github.com/google/gts/commit/c7e223e6a2ff605fabad2f8359a0385033f8de66)) 191 | 192 | ### [3.0.2](https://www.github.com/google/gts/compare/v3.0.1...v3.0.2) (2020-10-26) 193 | 194 | 195 | ### Bug Fixes 196 | 197 | * **deps:** loosen ts peer dependency ([#589](https://www.github.com/google/gts/issues/589)) ([8f1d381](https://www.github.com/google/gts/commit/8f1d381d7b166a510c42786c4a337e81b7222c84)) 198 | 199 | ### [3.0.1](https://www.github.com/google/gts/compare/v3.0.0...v3.0.1) (2020-10-12) 200 | 201 | 202 | ### Bug Fixes 203 | 204 | * **rule:** turn off @typescript-eslint/no-var-requires ([#578](https://www.github.com/google/gts/issues/578)) ([3b37229](https://www.github.com/google/gts/commit/3b37229c45969a3c53af123c69bb749578ee6b0b)) 205 | 206 | ## [3.0.0](https://www.github.com/google/gts/compare/v2.0.2...v3.0.0) (2020-10-08) 207 | 208 | 209 | ### ⚠ BREAKING CHANGES 210 | 211 | * change default `check` to `lint` (#570) 212 | * **deps:** require TypeScript 4.x (#565) 213 | 214 | ### Features 215 | 216 | * Add TypeScript v4 support ([#551](https://www.github.com/google/gts/issues/551)) ([0883956](https://www.github.com/google/gts/commit/08839565a1d2b4b39d532c9b0b596f01b18856fe)) 217 | * change default `check` to `lint` ([#570](https://www.github.com/google/gts/issues/570)) ([c527b66](https://www.github.com/google/gts/commit/c527b66be1ef6a78ea14b3d29225a8d7fb7097bd)) 218 | * generate .eslintignore when running init ([#521](https://www.github.com/google/gts/issues/521)) ([8bce036](https://www.github.com/google/gts/commit/8bce0368767f0c2ad7d0700deb839962bc928d16)) 219 | 220 | 221 | ### Bug Fixes 222 | 223 | * add build/.eslintrc.json to files field ([#553](https://www.github.com/google/gts/issues/553)) ([3b516ad](https://www.github.com/google/gts/commit/3b516ad5e9f0d58201dde469461db7c6ed1c1b78)) 224 | * **deps:** require TypeScript 4.x ([#565](https://www.github.com/google/gts/issues/565)) ([cbc5267](https://www.github.com/google/gts/commit/cbc5267579ef24e8c8ceaa2ef794df3ef54ea56a)) 225 | * **deps:** update dependency update-notifier to v5 ([#574](https://www.github.com/google/gts/issues/574)) ([9a882bf](https://www.github.com/google/gts/commit/9a882bf4ac30ad06e7b91a65ad5721d8e8b41c4b)) 226 | * **deps:** update typescript-eslint monorepo to v2.34.0 ([#509](https://www.github.com/google/gts/issues/509)) ([998a4ac](https://www.github.com/google/gts/commit/998a4ac9b75c97f04d8e5db37563f32d31652f23)) 227 | * **deps:** update typescript-eslint monorepo to v3 (major) ([#528](https://www.github.com/google/gts/issues/528)) ([e22e173](https://www.github.com/google/gts/commit/e22e17338db2ddb7eb829c821037c2f4e77ff869)) 228 | * **deps:** update typescript-eslint monorepo to v4 ([#556](https://www.github.com/google/gts/issues/556)) ([54148df](https://www.github.com/google/gts/commit/54148dfbd8b5f8b36a0f44f901c5db933393a661)) 229 | * better error message for broken tsconfig.json ([#501](https://www.github.com/google/gts/issues/501)) ([0c17a76](https://www.github.com/google/gts/commit/0c17a76c6650eee1d8abaff11a897a432eeaa65f)) 230 | * prohibit calls for it.only and describe.only ([#499](https://www.github.com/google/gts/issues/499)) ([071c33c](https://www.github.com/google/gts/commit/071c33ceef0e3765166aaebf6ed4698167ac0f98)) 231 | 232 | ### [2.0.2](https://www.github.com/google/gts/compare/v2.0.1...v2.0.2) (2020-05-11) 233 | 234 | 235 | ### Bug Fixes 236 | 237 | * Revert 'update dependency eslint to v7'" ([#507](https://www.github.com/google/gts/issues/507)) ([0f9950b](https://www.github.com/google/gts/commit/0f9950b273329dbcce5f3cc20864c3dcd076f08c)) 238 | * **deps:** pin release of eslint-typescript ([#508](https://www.github.com/google/gts/issues/508)) ([bd86b42](https://www.github.com/google/gts/commit/bd86b42e2bb904d3765dee82262e4691a11b9958)) 239 | * **deps:** update dependency eslint to v7 ([#504](https://www.github.com/google/gts/issues/504)) ([6aee159](https://www.github.com/google/gts/commit/6aee1595d0486ae2c7fd68d16b1b59c4c4015753)) 240 | 241 | ### [2.0.1](https://www.github.com/google/gts/compare/v2.0.0...v2.0.1) (2020-05-07) 242 | 243 | 244 | ### Bug Fixes 245 | 246 | * throw an error if running with an unsupported version of nodejs ([#493](https://www.github.com/google/gts/issues/493)) ([94fdf1e](https://www.github.com/google/gts/commit/94fdf1eaed634aa73c3e44c7a3d9f1325f773b07)) 247 | * **deps:** update dependency meow to v7 ([#502](https://www.github.com/google/gts/issues/502)) ([cf91cda](https://www.github.com/google/gts/commit/cf91cda1afab25759427511d3c97d0037d61c649)) 248 | 249 | ## [2.0.0](https://www.github.com/google/gts/compare/v1.1.2...v2.0.0) (2020-04-02) 250 | 251 | ### ⚠ BREAKING CHANGES ⚠ 252 | This is a major rewrite of the tool. Based on community guidance, we've switched from using [tslint](https://palantir.github.io/tslint/) to [eslint](https://eslint.org/). *Please read all of the steps below to upgrade*. 253 | 254 | #### Configuring `eslint` 255 | With the shift to `eslint`, `gts` now will format and lint JavaScript *as well* as TypeScript. Upgrading will require a number of manual steps. To format JavaScript and TypeScript, you can run: 256 | 257 | ``` 258 | $ npx gts fix 259 | ``` 260 | 261 | To specify only TypeScript: 262 | 263 | ``` 264 | $ npx gts fix '**/*.ts' 265 | ``` 266 | 267 | #### Delete `tslint.json` 268 | This file is no longer used, and can lead to confusion. 269 | 270 | #### Create a `.eslintrc.json` 271 | Now that we're using eslint, you need to extend the eslint configuration baked into the module. Create a new file named `.eslintrc.json`, and paste the following: 272 | ```js 273 | { 274 | "extends": "./node_modules/gts" 275 | } 276 | ``` 277 | 278 | #### Create a `.eslintignore` 279 | The `.eslintignore` file lets you ignore specific directories. This tool now lints and formats JavaScript, so it's _really_ important to ignore your build directory! Here is an example of a `.eslintignore` file: 280 | 281 | ``` 282 | **/node_modules 283 | build/ 284 | ``` 285 | 286 | #### Rule changes 287 | The underlying linter was changed, so naturally there are going to be a variety of rule changes along the way. To see the full list, check out [.eslintrc.json](https://github.com/google/gts/blob/main/.eslintrc.json). 288 | 289 | #### Require Node.js 10.x and up 290 | Node.js 8.x is now end of life - this module now requires Ndoe.js 10.x and up. 291 | 292 | ### Features 293 | 294 | * add the eol-last rule ([#425](https://www.github.com/google/gts/issues/425)) ([50ebd4d](https://www.github.com/google/gts/commit/50ebd4dbaf063615f4c025f567ca28076a734223)) 295 | * allow eslintrc to run over tsx files ([#469](https://www.github.com/google/gts/issues/469)) ([a21db94](https://www.github.com/google/gts/commit/a21db94601def563952d677cb0980a12b6730f4c)) 296 | * disable global rule for checking TODO comments ([#459](https://www.github.com/google/gts/issues/459)) ([96aa84a](https://www.github.com/google/gts/commit/96aa84a0a42181046daa248750cc8fef0c320619)) 297 | * override require-atomic-updates ([#468](https://www.github.com/google/gts/issues/468)) ([8105c93](https://www.github.com/google/gts/commit/8105c9334ee5104b05f6b1b2f150e51419637262)) 298 | * prefer single quotes if possible ([#475](https://www.github.com/google/gts/issues/475)) ([39a2705](https://www.github.com/google/gts/commit/39a2705e51b4b6329a70f91f8293a2d7a363bf5d)) 299 | * use eslint instead of tslint ([#400](https://www.github.com/google/gts/issues/400)) ([b3096fb](https://www.github.com/google/gts/commit/b3096fbd5076d302d93c2307bf627e12c423e726)) 300 | 301 | 302 | ### Bug Fixes 303 | 304 | * use .prettierrc.js ([#437](https://www.github.com/google/gts/issues/437)) ([06efa84](https://www.github.com/google/gts/commit/06efa8444cdf1064b64f3e8d61ebd04f45d90b4c)) 305 | * **deps:** update dependency chalk to v4 ([#477](https://www.github.com/google/gts/issues/477)) ([061d64e](https://www.github.com/google/gts/commit/061d64e29d37b93ce55228937cc100e05ddef352)) 306 | * **deps:** update dependency eslint-plugin-node to v11 ([#426](https://www.github.com/google/gts/issues/426)) ([a394b7c](https://www.github.com/google/gts/commit/a394b7c1f80437f25017ca5c500b968ebb789ece)) 307 | * **deps:** update dependency execa to v4 ([#427](https://www.github.com/google/gts/issues/427)) ([f42ef36](https://www.github.com/google/gts/commit/f42ef36709251553342e655e287e889df72ee3e3)) 308 | * **deps:** update dependency prettier to v2 ([#464](https://www.github.com/google/gts/issues/464)) ([20ef43d](https://www.github.com/google/gts/commit/20ef43d566df17d3c93949ef7db3b72ee9123ca3)) 309 | * disable no-use-before-define ([#431](https://www.github.com/google/gts/issues/431)) ([dea2c22](https://www.github.com/google/gts/commit/dea2c223d1d3a60a1786aa820eebb93be27016a7)) 310 | * **deps:** update dependency update-notifier to v4 ([#403](https://www.github.com/google/gts/issues/403)) ([57393b7](https://www.github.com/google/gts/commit/57393b74c6cf299e8ae09311f0382226b8baa3e3)) 311 | * **deps:** upgrade to meow 6.x ([#423](https://www.github.com/google/gts/issues/423)) ([8f93d00](https://www.github.com/google/gts/commit/8f93d0049337a832d9a22b6ae4e86fd41140ec56)) 312 | * align back to the google style guide ([#440](https://www.github.com/google/gts/issues/440)) ([8bd78c4](https://www.github.com/google/gts/commit/8bd78c4c78526a72400f618a95a987d2a7c1a8db)) 313 | * disable empty-function check ([#467](https://www.github.com/google/gts/issues/467)) ([6455d7a](https://www.github.com/google/gts/commit/6455d7a9d227320d3ffe1b00c9c739b846f339a8)) 314 | * drop support for node 8 ([#422](https://www.github.com/google/gts/issues/422)) ([888c686](https://www.github.com/google/gts/commit/888c68692079065f38ce66ec84472f1f3311a050)) 315 | * emit .prettierrc.js with init ([#462](https://www.github.com/google/gts/issues/462)) ([b114614](https://www.github.com/google/gts/commit/b114614d22ab5560d2d1dd5cb6695968cc80027b)) 316 | * enable trailing comma ([#470](https://www.github.com/google/gts/issues/470)) ([6518f58](https://www.github.com/google/gts/commit/6518f5843d3093e3beb7d3371b56d9aecedf3924)) 317 | * include *.tsx and *.jsx in default fix command ([#473](https://www.github.com/google/gts/issues/473)) ([0509780](https://www.github.com/google/gts/commit/050978005ad089d9b3b5d8895b25ea1175d75db2)) 318 | 319 | ### [1.1.2](https://www.github.com/google/gts/compare/v1.1.1...v1.1.2) (2019-11-20) 320 | 321 | 322 | ### Bug Fixes 323 | 324 | * **deps:** update to newest prettier (with support for optional chain) ([#396](https://www.github.com/google/gts/issues/396)) ([ce8ad06](https://www.github.com/google/gts/commit/ce8ad06c8489c44a9e2ed5292382637b3ebb7601)) 325 | 326 | ### [1.1.1](https://www.github.com/google/gts/compare/v1.1.0...v1.1.1) (2019-11-11) 327 | 328 | 329 | ### Bug Fixes 330 | 331 | * **deps:** update dependency chalk to v3 ([#389](https://www.github.com/google/gts/issues/389)) ([1ce0f45](https://www.github.com/google/gts/commit/1ce0f450677e143a27efc39def617d13c66503e8)) 332 | * **deps:** update dependency inquirer to v7 ([#377](https://www.github.com/google/gts/issues/377)) ([bf2c349](https://www.github.com/google/gts/commit/bf2c349b2208ac63e551542599ac9cd27b461338)) 333 | * **deps:** update dependency rimraf to v3 ([#374](https://www.github.com/google/gts/issues/374)) ([2058eaa](https://www.github.com/google/gts/commit/2058eaa682f4baae978b469fd708d1f866e7da74)) 334 | * **deps:** update dependency write-file-atomic to v3 ([#353](https://www.github.com/google/gts/issues/353)) ([59e6aa8](https://www.github.com/google/gts/commit/59e6aa8580a2f8e9457d2d2b6fa9e18e86347592)) 335 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, 4 | and in the interest of fostering an open and welcoming community, 5 | we pledge to respect all people who contribute through reporting issues, 6 | posting feature requests, updating documentation, 7 | submitting pull requests or patches, and other activities. 8 | 9 | We are committed to making participation in this project 10 | a harassment-free experience for everyone, 11 | regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, 13 | body size, race, ethnicity, age, religion, or nationality. 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | * The use of sexualized language or imagery 18 | * Personal attacks 19 | * Trolling or insulting/derogatory comments 20 | * Public or private harassment 21 | * Publishing other's private information, 22 | such as physical or electronic 23 | addresses, without explicit permission 24 | * Other unethical or unprofessional conduct. 25 | 26 | Project maintainers have the right and responsibility to remove, edit, or reject 27 | comments, commits, code, wiki edits, issues, and other contributions 28 | that are not aligned to this Code of Conduct. 29 | By adopting this Code of Conduct, 30 | project maintainers commit themselves to fairly and consistently 31 | applying these principles to every aspect of managing this project. 32 | Project maintainers who do not follow or enforce the Code of Conduct 33 | may be permanently removed from the project team. 34 | 35 | This code of conduct applies both within project spaces and in public spaces 36 | when an individual is representing the project or its community. 37 | 38 | Instances of abusive, harassing, or otherwise unacceptable behavior 39 | may be reported by opening an issue 40 | or contacting one or more of the project maintainers. 41 | 42 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, 43 | available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2013 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gts 2 | 3 | > Google TypeScript Style 4 | 5 | [![NPM Version][npm-image]][npm-url] 6 | [![GitHub Actions][github-image]][github-url] 7 | [![Known Vulnerabilities][snyk-image]][snyk-url] 8 | [![codecov][codecov-image]][codecov-url] 9 | [![TypeScript Style Guide][gts-image]][gts-url] 10 | 11 | [gts][npm-url] is Google's TypeScript style guide, and the configuration for our formatter, linter, and automatic code fixer. No lint rules to edit, no configuration to update, no more bike shedding over syntax. 12 | 13 | To borrow from [standardjs][standardjs-url]: 14 | 15 | - **No configuration**. The easiest way to enforce consistent style in your project. Just drop it in. 16 | - **Automatically format code**. Just run `gts fix` and say goodbye to messy or inconsistent code. 17 | - **Catch style issues & programmer errors early**. Save precious code review time by eliminating back-and-forth between reviewer & contributor. 18 | - **Opinionated, but not to a fault**. We recommend you use the default configuration, but if you _need_ to customize compiler or linter config, you can. 19 | 20 | Under the covers, we use [eslint][eslint-url] to enforce the style guide and provide automated fixes, and [prettier][prettier-url] to re-format code. 21 | 22 | ## Getting Started 23 | 24 | The easiest way to get started is to run: 25 | 26 | ```sh 27 | npx gts init 28 | ``` 29 | 30 | ## How it works 31 | 32 | When you run the `npx gts init` command, it's going to do a few things for you: 33 | 34 | - Adds an opinionated `tsconfig.json` file to your project that uses the Google TypeScript Style. 35 | - Adds the necessary devDependencies to your `package.json`. 36 | - Adds scripts to your `package.json`: 37 | - `lint`: Lints and checks for formatting problems. 38 | - `fix`: Automatically fixes formatting and linting problems (if possible). 39 | - `clean`: Removes output files. 40 | - `compile`: Compiles the source code using TypeScript compiler. 41 | - `pretest`, `posttest` and `prepare`: convenience integrations. 42 | - If a source folder is not already present it will add a default template project. 43 | 44 | ### Individual files 45 | 46 | The commands above will all run in the scope of the current folder. Some commands can be run on individual files: 47 | 48 | ```sh 49 | gts lint index.ts 50 | gts lint one.ts two.ts three.ts 51 | gts lint *.ts 52 | ``` 53 | 54 | ### Working with eslint 55 | 56 | Under the covers, we use [eslint][eslint-url] to enforce the style guide and provide automated fixes, and [prettier][prettier-url] to re-format code. To use the shared `eslint` configuration, create an `.eslintrc` in your project directory, and extend the shared config: 57 | 58 | ```yml 59 | --- 60 | extends: 61 | - './node_modules/gts' 62 | ``` 63 | 64 | If you don't want to use the `gts` CLI, you can drop down to using the module as a basic `eslint` config, and just use the `eslint` cli: 65 | 66 | ``` 67 | $ eslint --fix 68 | ``` 69 | 70 | This opens the ability to use the vast `eslint` ecosystem including custom rules, and tools like the VSCode plugin for eslint: 71 | 72 | - https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint 73 | 74 | ## Badge 75 | 76 | Show your love for `gts` and include a badge! 77 | 78 | [![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://github.com/google/gts) 79 | 80 | ```md 81 | [![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://github.com/google/gts) 82 | ``` 83 | 84 | ## Supported Node.js Versions 85 | 86 | Our client libraries follow the [Node.js release schedule](https://nodejs.org/en/about/releases/). Libraries are compatible with all current _active_ and _maintenance_ versions of Node.js. 87 | 88 | ## Can I use *gts* with the [pre-commit](https://pre-commit.com/) framework? 89 | 90 | Yes! You can put the following in your `.pre-commit-config.yaml` file: 91 | 92 | ```yaml 93 | repos: 94 | - repo: https://github.com/google/gts 95 | rev: '' # Use the sha / tag you want to point at 96 | hooks: 97 | - id: gts 98 | ``` 99 | 100 | ## License 101 | 102 | [Apache-2.0](LICENSE) 103 | 104 | --- 105 | 106 | Made with ❤️ by the Google Node.js team. 107 | 108 | > **_NOTE: This is not an official Google product._** 109 | 110 | [github-image]: https://github.com/google/gts/workflows/ci/badge.svg 111 | [github-url]: https://github.com/google/gts/actions 112 | [prettier-url]: https://prettier.io/ 113 | [codecov-image]: https://codecov.io/gh/google/gts/branch/main/graph/badge.svg 114 | [codecov-url]: https://codecov.io/gh/google/gts 115 | [david-image]: https://david-dm.org/google/gts.svg 116 | [david-url]: https://david-dm.org/google/gts 117 | [gts-image]: https://img.shields.io/badge/code%20style-google-blueviolet.svg 118 | [gts-url]: https://github.com/google/gts 119 | [npm-image]: https://img.shields.io/npm/v/gts.svg 120 | [npm-url]: https://npmjs.org/package/gts 121 | [snyk-image]: https://snyk.io/test/github/google/gts/badge.svg 122 | [snyk-url]: https://snyk.io/test/github/google/gts 123 | [standardjs-url]: https://www.npmjs.com/package/standard 124 | [eslint-url]: https://eslint.org/ 125 | -------------------------------------------------------------------------------- /js-green-licenses.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageAllowlist": ["argparse"] 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gts", 3 | "version": "6.0.2", 4 | "description": "Google TypeScript Style", 5 | "repository": "google/gts", 6 | "main": "build/src/index.js", 7 | "bin": { 8 | "gts": "build/src/cli.js" 9 | }, 10 | "files": [ 11 | "CHANGELOG.md", 12 | "build/src", 13 | "build/template", 14 | "build/.eslintrc.json", 15 | ".prettierrc.json", 16 | "tsconfig-google.json", 17 | "tsconfig.json", 18 | ".eslintrc.json" 19 | ], 20 | "scripts": { 21 | "build": "npm run compile", 22 | "clean": "rimraf ./build/", 23 | "compile": "tsc", 24 | "postcompile": "ncp template build/template", 25 | "lint": "eslint '**/*.ts'", 26 | "prepare": "npm run compile", 27 | "test": "c8 mocha build/test/test-*.js", 28 | "system-test": "c8 mocha build/test/kitchen.js", 29 | "pretest": "npm run compile", 30 | "presystem-test": "npm run compile", 31 | "license-check": "jsgl --local .", 32 | "fix": "eslint --fix '**/*.ts'" 33 | }, 34 | "engines": { 35 | "node": ">=18" 36 | }, 37 | "keywords": [ 38 | "typescript", 39 | "linter", 40 | "formatter", 41 | "google" 42 | ], 43 | "author": "Google Inc.", 44 | "license": "Apache-2.0", 45 | "dependencies": { 46 | "@typescript-eslint/eslint-plugin": "5.62.0", 47 | "@typescript-eslint/parser": "5.62.0", 48 | "chalk": "^4.1.2", 49 | "eslint": "8.57.1", 50 | "eslint-config-prettier": "9.1.0", 51 | "eslint-plugin-n": "15.7.0", 52 | "eslint-plugin-prettier": "5.2.1", 53 | "execa": "^5.0.0", 54 | "inquirer": "^7.3.3", 55 | "json5": "^2.1.3", 56 | "meow": "^9.0.0", 57 | "ncp": "^2.0.0", 58 | "prettier": "3.3.3", 59 | "rimraf": "3.0.2", 60 | "write-file-atomic": "^4.0.0" 61 | }, 62 | "devDependencies": { 63 | "@npm/types": "^2.0.0", 64 | "@types/cross-spawn": "^6.0.2", 65 | "@types/eslint": "^8.0.0", 66 | "@types/fs-extra": "^11.0.0", 67 | "@types/inquirer": "^8.0.0", 68 | "@types/json5": "2.2.0", 69 | "@types/mocha": "^10.0.0", 70 | "@types/ncp": "^2.0.4", 71 | "@types/node": "^22.7.5", 72 | "@types/rimraf": "^3.0.0", 73 | "@types/sinon": "^17.0.0", 74 | "@types/tmp": "^0.2.0", 75 | "@types/write-file-atomic": "^4.0.0", 76 | "c8": "^10.1.2", 77 | "cross-spawn": "^7.0.3", 78 | "fs-extra": "^11.0.0", 79 | "inline-fixtures": "^1.1.0", 80 | "js-green-licenses": "^4.0.0", 81 | "mocha": "^10.0.0", 82 | "sinon": "^19.0.0", 83 | "tmp": "0.2.3", 84 | "typescript": "^5.6.3" 85 | }, 86 | "peerDependencies": { 87 | "typescript": ">=5" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | ], 7 | "pinVersions": false, 8 | "rebaseStalePrs": true, 9 | "lockFileMaintenance": { 10 | "enabled": true, 11 | "recreateClosed": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/clean.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import chalk = require('chalk'); 17 | import * as ts from 'typescript'; 18 | 19 | import {Options} from './cli'; 20 | import {getTSConfig, rimrafp} from './util'; 21 | 22 | interface TSConfig { 23 | compilerOptions: ts.CompilerOptions; 24 | } 25 | 26 | /** 27 | * Remove files generated by the build. 28 | */ 29 | export async function clean(options: Options): Promise { 30 | const tsconfig = (await getTSConfig(options.targetRootDir)) as TSConfig; 31 | if (tsconfig.compilerOptions && tsconfig.compilerOptions.outDir) { 32 | const outDir = tsconfig.compilerOptions.outDir; 33 | if (outDir === '.') { 34 | options.logger.error( 35 | `${chalk.red('ERROR:')} ${chalk.gray('compilerOptions.outDir')} ` + 36 | 'cannot use the value ".". That would delete all of our sources.', 37 | ); 38 | return false; 39 | } 40 | const message = `${chalk.red('Removing')} ${outDir} ...`; 41 | options.logger.log(message); 42 | await rimrafp(outDir); 43 | return true; 44 | } else { 45 | options.logger.error( 46 | `${chalk.red('ERROR:')} The ${chalk.gray('clean')} command` + 47 | ` requires ${chalk.gray('compilerOptions.outDir')} to be defined in ` + 48 | 'tsconfig.json.', 49 | ); 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright 2017 Google Inc. All Rights Reserved. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | import * as path from 'path'; 20 | import * as meow from 'meow'; 21 | import {init} from './init'; 22 | import {clean} from './clean'; 23 | import {isYarnUsed} from './util'; 24 | import * as execa from 'execa'; 25 | 26 | export interface Logger { 27 | log: (...args: Array<{}>) => void; 28 | error: (...args: Array<{}>) => void; 29 | dir: (obj: {}, options?: {}) => void; 30 | } 31 | 32 | export interface Options { 33 | dryRun: boolean; 34 | gtsRootDir: string; 35 | targetRootDir: string; 36 | yes: boolean; 37 | no: boolean; 38 | logger: Logger; 39 | yarn?: boolean; 40 | } 41 | 42 | export type VerbFilesFunction = ( 43 | options: Options, 44 | files: string[], 45 | fix?: boolean, 46 | ) => Promise; 47 | 48 | const logger: Logger = console; 49 | 50 | const cli = meow({ 51 | help: ` 52 | Usage 53 | $ gts [...] [options] 54 | 55 | Verb can be: 56 | init Adds default npm scripts to your package.json. 57 | lint Checks code for formatting and lint issues. 58 | check Alias for lint. Kept for backward compatibility. 59 | fix Fixes formatting and linting issues (if possible). 60 | clean Removes all files generated by the build. 61 | 62 | Options 63 | --help Prints this help message. 64 | -y, --yes Assume a yes answer for every prompt. 65 | -n, --no Assume a no answer for every prompt. 66 | --dry-run Don't make any actual changes. 67 | --yarn Use yarn instead of npm. 68 | 69 | Examples 70 | $ gts init -y 71 | $ gts lint 72 | $ gts fix 73 | $ gts fix src/file1.ts src/file2.ts 74 | $ gts clean`, 75 | flags: { 76 | help: {type: 'boolean'}, 77 | yes: {type: 'boolean', alias: 'y'}, 78 | no: {type: 'boolean', alias: 'n'}, 79 | dryRun: {type: 'boolean'}, 80 | yarn: {type: 'boolean'}, 81 | }, 82 | }); 83 | 84 | /** 85 | * Get the current version of node.js being run. 86 | * Exported purely for stubbing purposes. 87 | * @private 88 | */ 89 | export function getNodeVersion() { 90 | return process.version; 91 | } 92 | 93 | function usage(msg?: string): void { 94 | if (msg) { 95 | logger.error(msg); 96 | } 97 | cli.showHelp(1); 98 | } 99 | 100 | export async function run(verb: string, files: string[]): Promise { 101 | // throw if running on an old version of nodejs 102 | const nodeMajorVersion = Number(getNodeVersion().slice(1).split('.')[0]); 103 | console.log(`version: ${nodeMajorVersion}`); 104 | if (nodeMajorVersion < 10) { 105 | throw new Error( 106 | `gts requires node.js 10.x or up. You are currently running 107 | ${process.version}, which is not supported. Please upgrade to 108 | a safe, secure version of nodejs!`, 109 | ); 110 | } 111 | 112 | const options = { 113 | dryRun: cli.flags.dryRun || false, 114 | // Paths are relative to the transpiled output files. 115 | gtsRootDir: path.resolve(__dirname, '../..'), 116 | targetRootDir: process.cwd(), 117 | yes: cli.flags.yes || cli.flags.y || false, 118 | no: cli.flags.no || cli.flags.n || false, 119 | logger, 120 | yarn: cli.flags.yarn || isYarnUsed(), 121 | } as Options; 122 | // Linting/formatting depend on typescript. We don't want to load the 123 | // typescript module during init, since it might not exist. 124 | // See: https://github.com/google/gts/issues/48 125 | if (verb === 'init') { 126 | return init(options); 127 | } 128 | 129 | const flags = Object.assign([], files); 130 | if (flags.length === 0) { 131 | flags.push( 132 | '**/*.ts', 133 | '**/*.js', 134 | '**/*.tsx', 135 | '**/*.jsx', 136 | '--no-error-on-unmatched-pattern', 137 | ); 138 | } 139 | 140 | switch (verb) { 141 | case 'lint': 142 | case 'check': { 143 | try { 144 | await execa('eslint', flags, {stdio: 'inherit'}); 145 | return true; 146 | } catch (e) { 147 | return false; 148 | } 149 | } 150 | case 'fix': { 151 | const fixFlag = options.dryRun ? '--fix-dry-run' : '--fix'; 152 | try { 153 | await execa('eslint', [fixFlag, ...flags], {stdio: 'inherit'}); 154 | return true; 155 | } catch (e) { 156 | console.error(e); 157 | return false; 158 | } 159 | } 160 | case 'clean': 161 | return clean(options); 162 | default: 163 | usage(`Unknown verb: ${verb}`); 164 | return false; 165 | } 166 | } 167 | 168 | if (cli.input.length < 1) { 169 | usage(); 170 | } 171 | 172 | run(cli.input[0], cli.input.slice(1)) 173 | .then(success => { 174 | if (!success) { 175 | // eslint-disable-next-line n/no-process-exit 176 | process.exit(1); 177 | } 178 | }) 179 | .catch(e => { 180 | console.error(e); 181 | // eslint-disable-next-line n/no-process-exit 182 | process.exit(1); 183 | }); 184 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as cfg from '../.eslintrc.json'; 18 | module.exports = cfg; 19 | -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import * as cp from 'child_process'; 17 | import * as fs from 'fs'; 18 | import * as inquirer from 'inquirer'; 19 | import * as path from 'path'; 20 | import {ncp} from 'ncp'; 21 | import * as util from 'util'; 22 | import * as writeFileAtomic from 'write-file-atomic'; 23 | 24 | import { 25 | getPkgManagerCommand, 26 | readFilep as read, 27 | readJsonp as readJson, 28 | Bag, 29 | DefaultPackage, 30 | } from './util'; 31 | 32 | import {Options} from './cli'; 33 | import {PackageJSON} from '@npm/types'; 34 | import chalk = require('chalk'); 35 | 36 | // eslint-disable-next-line @typescript-eslint/no-var-requires 37 | const pkg = require('../../package.json'); 38 | 39 | const ncpp = util.promisify(ncp); 40 | 41 | const DEFAULT_PACKAGE_JSON: PackageJSON = { 42 | name: '', 43 | version: '0.0.0', 44 | description: '', 45 | main: 'build/src/index.js', 46 | types: 'build/src/index.d.ts', 47 | files: ['build/src'], 48 | license: 'Apache-2.0', 49 | keywords: [], 50 | scripts: {test: 'echo "Error: no test specified" && exit 1'}, 51 | }; 52 | 53 | async function query( 54 | message: string, 55 | question: string, 56 | defaultVal: boolean, 57 | options: Options, 58 | ): Promise { 59 | if (options.yes) { 60 | return true; 61 | } else if (options.no) { 62 | return false; 63 | } 64 | 65 | if (message) { 66 | options.logger.log(message); 67 | } 68 | 69 | const answers: inquirer.Answers = await inquirer.prompt({ 70 | type: 'confirm', 71 | name: 'query', 72 | message: question, 73 | default: defaultVal, 74 | }); 75 | return answers.query; 76 | } 77 | 78 | export async function addScripts( 79 | packageJson: PackageJSON, 80 | options: Options, 81 | ): Promise { 82 | let edits = false; 83 | const pkgManager = getPkgManagerCommand(options.yarn); 84 | const scripts: Bag = { 85 | lint: 'gts lint', 86 | clean: 'gts clean', 87 | compile: 'tsc', 88 | fix: 'gts fix', 89 | prepare: `${pkgManager} run compile`, 90 | pretest: `${pkgManager} run compile`, 91 | posttest: `${pkgManager} run lint`, 92 | }; 93 | 94 | if (!packageJson.scripts) { 95 | packageJson.scripts = {}; 96 | } 97 | 98 | for (const script of Object.keys(scripts)) { 99 | let install = true; 100 | const existing = packageJson.scripts[script]; 101 | const target = scripts[script]; 102 | 103 | if (existing !== target) { 104 | if (existing) { 105 | const message = 106 | `package.json already has a script for ${chalk.bold(script)}:\n` + 107 | `-${chalk.red(existing)}\n+${chalk.green(target)}`; 108 | install = await query(message, 'Replace', false, options); 109 | } 110 | 111 | if (install) { 112 | // eslint-disable-next-line require-atomic-updates 113 | packageJson.scripts[script] = scripts[script]; 114 | edits = true; 115 | } 116 | } 117 | } 118 | return edits; 119 | } 120 | 121 | export async function addDependencies( 122 | packageJson: PackageJSON, 123 | options: Options, 124 | ): Promise { 125 | let edits = false; 126 | const deps: DefaultPackage = { 127 | gts: `^${pkg.version}`, 128 | typescript: pkg.devDependencies.typescript, 129 | '@types/node': pkg.devDependencies['@types/node'], 130 | }; 131 | 132 | if (!packageJson.devDependencies) { 133 | packageJson.devDependencies = {}; 134 | } 135 | 136 | for (const dep of Object.keys(deps)) { 137 | let install = true; 138 | const existing = packageJson.devDependencies[dep]; 139 | const target = deps[dep]; 140 | 141 | if (existing !== target) { 142 | if (existing) { 143 | const message = 144 | `Already have devDependency for ${chalk.bold(dep)}:\n` + 145 | `-${chalk.red(existing)}\n+${chalk.green(target)}`; 146 | install = await query(message, 'Overwrite', false, options); 147 | } 148 | 149 | if (install) { 150 | // eslint-disable-next-line require-atomic-updates 151 | packageJson.devDependencies[dep] = deps[dep]; 152 | edits = true; 153 | } 154 | } 155 | } 156 | 157 | return edits; 158 | } 159 | 160 | function formatJson(object: {}) { 161 | const json = JSON.stringify(object, null, ' '); 162 | return `${json}\n`; 163 | } 164 | 165 | async function writePackageJson( 166 | packageJson: PackageJSON, 167 | options: Options, 168 | ): Promise { 169 | options.logger.log('Writing package.json...'); 170 | if (!options.dryRun) { 171 | await writeFileAtomic('./package.json', formatJson(packageJson)); 172 | } 173 | const preview = { 174 | scripts: packageJson.scripts, 175 | devDependencies: packageJson.devDependencies, 176 | }; 177 | options.logger.dir(preview); 178 | } 179 | 180 | export const ESLINT_CONFIG = { 181 | extends: './node_modules/gts/', 182 | }; 183 | 184 | export const ESLINT_IGNORE = 'build/\n'; 185 | 186 | async function generateConfigFile( 187 | options: Options, 188 | filename: string, 189 | contents: string, 190 | ) { 191 | let existing; 192 | try { 193 | existing = await read(filename, 'utf8'); 194 | } catch (e) { 195 | const err = e as Error & {code?: string}; 196 | if (err.code === 'ENOENT') { 197 | /* not found, create it. */ 198 | } else { 199 | throw new Error(`Unknown error reading ${filename}: ${err.message}`); 200 | } 201 | } 202 | 203 | let writeFile = true; 204 | if (existing && existing === contents) { 205 | options.logger.log(`No edits needed in ${filename}`); 206 | return; 207 | } else if (existing) { 208 | writeFile = await query( 209 | `${chalk.bold(filename)} already exists`, 210 | 'Overwrite', 211 | false, 212 | options, 213 | ); 214 | } 215 | 216 | if (writeFile) { 217 | options.logger.log(`Writing ${filename}...`); 218 | if (!options.dryRun) { 219 | await writeFileAtomic(filename, contents); 220 | } 221 | options.logger.log(contents); 222 | } 223 | } 224 | 225 | async function generateESLintConfig(options: Options): Promise { 226 | return generateConfigFile( 227 | options, 228 | './.eslintrc.json', 229 | formatJson(ESLINT_CONFIG), 230 | ); 231 | } 232 | 233 | async function generateESLintIgnore(options: Options): Promise { 234 | return generateConfigFile(options, './.eslintignore', ESLINT_IGNORE); 235 | } 236 | 237 | async function generateTsConfig(options: Options): Promise { 238 | const config = formatJson({ 239 | extends: './node_modules/gts/tsconfig-google.json', 240 | compilerOptions: {rootDir: '.', outDir: 'build'}, 241 | include: ['src/**/*.ts', 'test/**/*.ts'], 242 | }); 243 | return generateConfigFile(options, './tsconfig.json', config); 244 | } 245 | 246 | async function generatePrettierConfig(options: Options): Promise { 247 | const style = `module.exports = { 248 | ...require('gts/.prettierrc.json') 249 | } 250 | `; 251 | return generateConfigFile(options, './.prettierrc.js', style); 252 | } 253 | 254 | async function generateEditorConfig(options: Options): Promise { 255 | const config = `root = true 256 | 257 | [*] 258 | indent_style = space 259 | indent_size = 2 260 | end_of_line = lf 261 | charset = utf-8 262 | insert_final_newline = true 263 | `; 264 | return generateConfigFile(options, './.editorconfig', config); 265 | } 266 | 267 | export async function installDefaultTemplate( 268 | options: Options, 269 | ): Promise { 270 | const cwd = process.cwd(); 271 | const sourceDirName = path.join(__dirname, '../template'); 272 | const targetDirName = path.join(cwd, 'src'); 273 | 274 | try { 275 | fs.mkdirSync(targetDirName); 276 | } catch (e) { 277 | const err = e as Error & {code?: string}; 278 | if (err.code !== 'EEXIST') { 279 | throw err; 280 | } 281 | // Else, continue and populate files into the existing directory. 282 | } 283 | 284 | // Only install the template if no ts files exist in target directory. 285 | const files = fs.readdirSync(targetDirName); 286 | const tsFiles = files.filter(file => file.toLowerCase().endsWith('.ts')); 287 | if (tsFiles.length !== 0) { 288 | options.logger.log( 289 | 'Target src directory already has ts files. ' + 290 | 'Template files not installed.', 291 | ); 292 | return false; 293 | } 294 | await ncpp(sourceDirName, targetDirName); 295 | options.logger.log('Default template installed.'); 296 | return true; 297 | } 298 | 299 | export async function init(options: Options): Promise { 300 | let generatedPackageJson = false; 301 | let packageJson; 302 | try { 303 | packageJson = await readJson('./package.json'); 304 | } catch (e) { 305 | const err = e as Error & {code?: string}; 306 | if (err.code !== 'ENOENT') { 307 | throw new Error(`Unable to open package.json file: ${err.message}`); 308 | } 309 | const generate = await query( 310 | `${chalk.bold('package.json')} does not exist.`, 311 | 'Generate', 312 | true, 313 | options, 314 | ); 315 | 316 | if (!generate) { 317 | options.logger.log('Please run from a directory with your package.json.'); 318 | return false; 319 | } 320 | 321 | packageJson = DEFAULT_PACKAGE_JSON; 322 | generatedPackageJson = true; 323 | } 324 | 325 | const [addedDeps, addedScripts] = await Promise.all([ 326 | addDependencies(packageJson, options), 327 | addScripts(packageJson, options), 328 | ]); 329 | if (generatedPackageJson || addedDeps || addedScripts) { 330 | await writePackageJson(packageJson, options); 331 | } else { 332 | options.logger.log('No edits needed in package.json.'); 333 | } 334 | await Promise.all([ 335 | generateTsConfig(options), 336 | generateESLintConfig(options), 337 | generateESLintIgnore(options), 338 | generatePrettierConfig(options), 339 | generateEditorConfig(options), 340 | ]); 341 | await installDefaultTemplate(options); 342 | 343 | // Run `npm install` after initial setup so `npm run lint` works right away. 344 | if (!options.dryRun) { 345 | // --ignore-scripts so that compilation doesn't happen because there's no 346 | // source files yet. 347 | 348 | cp.spawnSync( 349 | getPkgManagerCommand(options.yarn), 350 | ['install', '--ignore-scripts'], 351 | {stdio: 'inherit'}, 352 | ); 353 | } 354 | 355 | return true; 356 | } 357 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as fs from 'fs'; 18 | import * as path from 'path'; 19 | import * as rimraf from 'rimraf'; 20 | import {promisify} from 'util'; 21 | import * as ncp from 'ncp'; 22 | import * as JSON5 from 'json5'; 23 | 24 | export const readFilep = promisify(fs.readFile); 25 | export const rimrafp = promisify(rimraf); 26 | export const ncpp = promisify(ncp.ncp); 27 | 28 | export interface Bag { 29 | [script: string]: T; 30 | } 31 | 32 | export interface DefaultPackage extends Bag { 33 | gts: string; 34 | typescript: string; 35 | '@types/node': string; 36 | } 37 | 38 | export async function readJsonp(jsonPath: string) { 39 | const contents = await readFilep(jsonPath, {encoding: 'utf8'}); 40 | return JSON5.parse(contents); 41 | } 42 | 43 | export interface ReadFileP { 44 | (path: string, encoding: string): Promise; 45 | } 46 | 47 | export function nop() { 48 | /* empty */ 49 | } 50 | 51 | /** 52 | * Recursively iterate through the dependency chain until we reach the end of 53 | * the dependency chain or encounter a circular reference 54 | * @param filePath Filepath of file currently being read 55 | * @param customReadFilep The file reading function being used 56 | * @param readFiles an array of the previously read files so we can check for 57 | * circular references 58 | * returns a ConfigFile object containing the data from all the dependencies 59 | */ 60 | async function getBase( 61 | filePath: string, 62 | customReadFilep: ReadFileP, 63 | readFiles: Set, 64 | currentDir: string, 65 | ): Promise { 66 | customReadFilep = customReadFilep || readFilep; 67 | 68 | filePath = path.resolve(currentDir, filePath); 69 | 70 | // An error is thrown if there is a circular reference as specified by the 71 | // TypeScript doc 72 | if (readFiles.has(filePath)) { 73 | throw new Error(`Circular reference in ${filePath}`); 74 | } 75 | readFiles.add(filePath); 76 | try { 77 | const json = await customReadFilep(filePath, 'utf8'); 78 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 79 | let contents: any; 80 | try { 81 | contents = JSON5.parse(json); 82 | } catch (e) { 83 | const err = e as Error; 84 | err.message = `Unable to parse ${filePath}!\n${err.message}`; 85 | throw err; 86 | } 87 | 88 | if (contents.extends) { 89 | const nextFile = await getBase( 90 | contents.extends, 91 | customReadFilep, 92 | readFiles, 93 | path.dirname(filePath), 94 | ); 95 | contents = combineTSConfig(nextFile, contents); 96 | } 97 | 98 | return contents; 99 | } catch (e) { 100 | const err = e as Error; 101 | err.message = `Error: ${filePath}\n${err.message}`; 102 | throw err; 103 | } 104 | } 105 | 106 | /** 107 | * Takes in 2 config files 108 | * @param base is loaded first 109 | * @param inherited is then loaded and overwrites base 110 | */ 111 | function combineTSConfig(base: ConfigFile, inherited: ConfigFile): ConfigFile { 112 | const result: ConfigFile = {compilerOptions: {}}; 113 | 114 | Object.assign(result, base, inherited); 115 | Object.assign( 116 | result.compilerOptions!, 117 | base.compilerOptions!, 118 | inherited.compilerOptions!, 119 | ); 120 | delete result.extends; 121 | return result; 122 | } 123 | 124 | /** 125 | * An interface containing the top level data fields present in Config Files 126 | */ 127 | export interface ConfigFile { 128 | files?: string[]; 129 | compilerOptions?: {}; 130 | include?: string[]; 131 | exclude?: string[]; 132 | extends?: string[]; 133 | } 134 | 135 | /** 136 | * Automatically defines npm or yarn is going to be used: 137 | * - If only yarn.lock exists, use yarn 138 | * - If only package-lock.json or both exist, use npm 139 | */ 140 | export function isYarnUsed(existsSync = fs.existsSync): boolean { 141 | if (existsSync('package-lock.json')) { 142 | return false; 143 | } 144 | return existsSync('yarn.lock'); 145 | } 146 | 147 | export function getPkgManagerCommand(isYarnUsed?: boolean): string { 148 | return ( 149 | (isYarnUsed ? 'yarn' : 'npm') + (process.platform === 'win32' ? '.cmd' : '') 150 | ); 151 | } 152 | 153 | /** 154 | * Find the tsconfig.json, read it, and return parsed contents. 155 | * @param rootDir Directory where the tsconfig.json should be found. 156 | * If the tsconfig.json file has an "extends" field hop down the dependency tree 157 | * until it ends or a circular reference is found in which case an error will be 158 | * thrown 159 | */ 160 | export async function getTSConfig( 161 | rootDir: string, 162 | customReadFilep?: ReadFileP, 163 | ): Promise { 164 | customReadFilep = (customReadFilep || readFilep) as ReadFileP; 165 | const readArr = new Set(); 166 | return getBase('tsconfig.json', customReadFilep, readArr, rootDir); 167 | } 168 | -------------------------------------------------------------------------------- /template/index.ts: -------------------------------------------------------------------------------- 1 | console.log("Try npm run lint/fix!"); 2 | 3 | const longString = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ut aliquet diam.'; 4 | 5 | const trailing = 'Semicolon' 6 | 7 | const why={am:'I tabbed?'}; 8 | 9 | const iWish = "I didn't have a trailing space..."; 10 | 11 | const sicilian = true;; 12 | 13 | const vizzini = (!!sicilian) ? !!!sicilian : sicilian; 14 | 15 | const re = /foo bar/; 16 | 17 | export function doSomeStuff(withThis: string, andThat: string, andThose: string[]) { 18 | //function on one line 19 | if(!Boolean(andThose.length)) {return false;} 20 | console.log(withThis); 21 | console.log(andThat); 22 | console.dir(andThose); 23 | console.log(longString, trailing, why, iWish, vizzini, re); 24 | return; 25 | } 26 | // TODO: more examples 27 | -------------------------------------------------------------------------------- /test/fixtures/kitchen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kitchen", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "typescript": "^5.4.3", 6 | "gts": "file:../gts.tgz" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/kitchen/src/samples.js: -------------------------------------------------------------------------------- 1 | let isASample = true; 2 | console.log(isASample); 3 | -------------------------------------------------------------------------------- /test/fixtures/kitchen/src/server.ts: -------------------------------------------------------------------------------- 1 | const isThisTypeScript = true 2 | -------------------------------------------------------------------------------- /test/kitchen.ts: -------------------------------------------------------------------------------- 1 | import chalk = require('chalk'); 2 | import * as cp from 'child_process'; 3 | import * as fs from 'fs-extra'; 4 | import * as tmp from 'tmp'; 5 | import * as assert from 'assert'; 6 | import * as path from 'path'; 7 | import {describe, it, before, after} from 'mocha'; 8 | 9 | import spawn = require('cross-spawn'); 10 | import execa = require('execa'); 11 | // eslint-disable-next-line @typescript-eslint/no-var-requires 12 | const pkg = require('../../package.json'); 13 | const keep = !!process.env.GTS_KEEP_TEMPDIRS; 14 | const stagingDir = tmp.dirSync({keep, unsafeCleanup: true}); 15 | const stagingPath = stagingDir.name; 16 | const execOpts = { 17 | cwd: `${stagingPath}${path.sep}kitchen`, 18 | encoding: 'utf8' as BufferEncoding, 19 | }; 20 | 21 | const action = process.platform !== 'win32' ? describe : describe.skip; 22 | 23 | action('🚰 kitchen sink', () => { 24 | const fixturesPath = path.join('test', 'fixtures'); 25 | const gtsPath = path.join('node_modules', '.bin', 'gts'); 26 | const kitchenPath = path.join(stagingPath, 'kitchen'); 27 | 28 | // Create a staging directory with temp fixtures used to test on a fresh application. 29 | before(() => { 30 | console.log(`${chalk.blue(`${__filename} staging area: ${stagingPath}`)}`); 31 | cp.execSync('npm pack'); 32 | const tarball = `${pkg.name}-${pkg.version}.tgz`; 33 | fs.renameSync(tarball, 'gts.tgz'); 34 | const targetPath = path.resolve(stagingPath, 'gts.tgz'); 35 | console.log('moving packed tar to ', targetPath); 36 | fs.moveSync('gts.tgz', targetPath); 37 | fs.copySync(fixturesPath, path.join(stagingPath, path.sep)); 38 | }); 39 | // CLEAN UP - remove the staging directory when done. 40 | after('cleanup staging', () => { 41 | if (!keep) { 42 | stagingDir.removeCallback(); 43 | } 44 | }); 45 | 46 | it('it should run init', () => { 47 | const args = [ 48 | '-p', 49 | path.resolve(stagingPath, 'gts.tgz'), 50 | 'gts', 51 | 'init', 52 | // It's important to use `-n` here because we don't want to overwrite 53 | // the version of gts installed, as it will trigger the npm install. 54 | '-n', 55 | ]; 56 | 57 | const res = spawn.sync('npx', args, execOpts); 58 | console.log('out: ', res.stdout + ''); 59 | console.log('error: ', res.stderr + ''); 60 | 61 | // Ensure config files got generated. 62 | fs.accessSync(path.join(kitchenPath, 'tsconfig.json')); 63 | fs.accessSync(path.join(kitchenPath, '.eslintrc.json')); 64 | fs.accessSync(path.join(kitchenPath, '.eslintignore')); 65 | fs.accessSync(path.join(kitchenPath, '.prettierrc.js')); 66 | fs.accessSync(path.join(kitchenPath, '.editorconfig')); 67 | console.log('ensured config files existed'); 68 | 69 | // Compilation shouldn't have happened. Hence no `build` directory. 70 | const dirContents = fs.readdirSync(kitchenPath); 71 | console.log(`read dirContents length = ${dirContents.length}`); 72 | assert.strictEqual(dirContents.indexOf('build'), -1); 73 | }); 74 | 75 | it('should use as a non-locally installed module', () => { 76 | // Use from a directory different from where we have locally installed. This 77 | // simulates use as a globally installed module. 78 | const GTS = path.resolve(stagingPath, 'kitchen/node_modules/.bin/gts'); 79 | const tmpDir = tmp.dirSync({keep, unsafeCleanup: true}); 80 | const opts = {cwd: path.join(tmpDir.name, 'kitchen')}; 81 | 82 | // Copy test files. 83 | fs.copySync(fixturesPath, tmpDir.name); 84 | // Test package.json expects a gts tarball from ../gts.tgz. 85 | fs.copySync( 86 | path.join(stagingPath, 'gts.tgz'), 87 | path.join(tmpDir.name, 'gts.tgz'), 88 | ); 89 | // It's important to use `-n` here because we don't want to overwrite 90 | // the version of gts installed, as it will trigger the npm install. 91 | spawn.sync(GTS, ['init', '-n'], opts); 92 | 93 | // The `extends` field must use the local gts path. 94 | const tsconfigJson = fs.readFileSync( 95 | path.join(tmpDir.name, 'kitchen', 'tsconfig.json'), 96 | 'utf8', 97 | ); 98 | const tsconfig = JSON.parse(tsconfigJson); 99 | assert.deepStrictEqual( 100 | tsconfig.extends, 101 | './node_modules/gts/tsconfig-google.json', 102 | ); 103 | 104 | // server.ts has a lint error. Should error. 105 | assert.throws(() => cp.execSync(`${GTS} lint src/server.ts`, opts)); 106 | 107 | if (!keep) { 108 | tmpDir.removeCallback(); 109 | } 110 | }); 111 | 112 | it('should terminate generated files with newline', () => { 113 | const GTS = path.resolve(stagingPath, gtsPath); 114 | spawn.sync(GTS, ['init', '-y'], execOpts); 115 | assert.ok( 116 | fs 117 | .readFileSync(path.join(kitchenPath, 'package.json'), 'utf8') 118 | .endsWith('\n'), 119 | ); 120 | assert.ok( 121 | fs 122 | .readFileSync(path.join(kitchenPath, 'tsconfig.json'), 'utf8') 123 | .endsWith('\n'), 124 | ); 125 | assert.ok( 126 | fs 127 | .readFileSync(path.join(kitchenPath, '.eslintrc.json'), 'utf8') 128 | .endsWith('\n'), 129 | ); 130 | assert.ok( 131 | fs 132 | .readFileSync(path.join(kitchenPath, '.eslintignore'), 'utf8') 133 | .endsWith('\n'), 134 | ); 135 | assert.ok( 136 | fs 137 | .readFileSync(path.join(kitchenPath, '.prettierrc.js'), 'utf8') 138 | .endsWith('\n'), 139 | ); 140 | }); 141 | 142 | it('should lint before fix', async () => { 143 | const res = await execa( 144 | 'npm', 145 | ['run', 'lint'], 146 | Object.assign({}, {reject: false}, execOpts), 147 | ); 148 | assert.strictEqual(res.exitCode, 1); 149 | assert.ok(res.stdout.includes('assigned a value but')); 150 | }); 151 | 152 | it('should fix', () => { 153 | const preFix = fs 154 | .readFileSync(path.join(kitchenPath, 'src', 'server.ts'), 'utf8') 155 | .split(/[\n\r]+/); 156 | 157 | cp.execSync('npm run fix', execOpts); 158 | const postFix = fs 159 | .readFileSync(path.join(kitchenPath, 'src', 'server.ts'), 'utf8') 160 | .split(/[\n\r]+/); 161 | assert.strictEqual(preFix[0].trim() + ';', postFix[0]); // fix should have added a semi-colon 162 | }); 163 | 164 | it('should lint after fix', () => { 165 | cp.execSync('npm run lint', execOpts); 166 | }); 167 | 168 | it('should build', () => { 169 | cp.execSync('npm run compile', execOpts); 170 | fs.accessSync(path.join(kitchenPath, 'build', 'src', 'server.js')); 171 | fs.accessSync(path.join(kitchenPath, 'build', 'src', 'server.js.map')); 172 | fs.accessSync(path.join(kitchenPath, 'build', 'src', 'server.d.ts')); 173 | }); 174 | 175 | // Verify the `gts clean` command actually removes the output dir 176 | it('should clean', () => { 177 | cp.execSync('npm run clean', execOpts); 178 | assert.throws(() => fs.accessSync(path.join(kitchenPath, 'build'))); 179 | }); 180 | }); 181 | -------------------------------------------------------------------------------- /test/test-clean.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as assert from 'assert'; 18 | import * as fs from 'fs'; 19 | import * as path from 'path'; 20 | 21 | import {clean} from '../src/clean'; 22 | import {nop} from '../src/util'; 23 | 24 | import {withFixtures} from 'inline-fixtures'; 25 | import {describe, it} from 'mocha'; 26 | 27 | describe('clean', () => { 28 | const OPTIONS = { 29 | gtsRootDir: path.resolve(__dirname, '../..'), 30 | targetRootDir: './', 31 | dryRun: false, 32 | yes: false, 33 | no: false, 34 | logger: {log: nop, error: nop, dir: nop}, 35 | }; 36 | 37 | it('should gracefully error if tsconfig is missing', () => { 38 | return assert.rejects(() => 39 | withFixtures({}, async () => { 40 | await clean(OPTIONS); 41 | }), 42 | ); 43 | }); 44 | 45 | it('should gracefully error if tsconfig does not have valid outDir', () => { 46 | return withFixtures({'tsconfig.json': JSON.stringify({})}, async () => { 47 | const deleted = await clean(OPTIONS); 48 | assert.strictEqual(deleted, false); 49 | }); 50 | }); 51 | 52 | it('should gracefully handle JSON with comments', () => { 53 | const invalidJson = ` 54 | { 55 | // hah, comments in JSON, what a world 56 | compilerOptions: {outDir: '.'} 57 | }`; 58 | return withFixtures({'tsconfig.json': invalidJson}, async () => { 59 | await clean(OPTIONS); 60 | }); 61 | }); 62 | 63 | it('should gracefully error if tsconfig has invalid JSON', () => { 64 | const invalidJson = "silly bear, this isn't JSON!"; 65 | return withFixtures({'tsconfig.json': invalidJson}, async () => { 66 | await assert.rejects(clean(OPTIONS), /Unable to parse/); 67 | }); 68 | }); 69 | 70 | it('should avoid deleting .', () => { 71 | return withFixtures( 72 | {'tsconfig.json': JSON.stringify({compilerOptions: {outDir: '.'}})}, 73 | async () => { 74 | const deleted = await clean(OPTIONS); 75 | assert.strictEqual(deleted, false); 76 | }, 77 | ); 78 | }); 79 | 80 | it('should ensure that outDir is local to targetRoot', () => { 81 | return assert.rejects(() => 82 | withFixtures( 83 | { 84 | 'tsconfig.json': JSON.stringify({ 85 | compilerOptions: {outDir: '../out'}, 86 | }), 87 | }, 88 | async () => { 89 | const deleted = await clean(OPTIONS); 90 | assert.strictEqual(deleted, false); 91 | }, 92 | ), 93 | ); 94 | }); 95 | 96 | it('should remove outDir', () => { 97 | const OUT = 'outputDirectory'; 98 | return withFixtures( 99 | { 100 | 'tsconfig.json': JSON.stringify({compilerOptions: {outDir: OUT}}), 101 | [OUT]: {}, 102 | }, 103 | async dir => { 104 | const outputPath = path.join(dir, OUT); 105 | // make sure the output directory exists. 106 | fs.accessSync(outputPath); 107 | const deleted = await clean(OPTIONS); 108 | assert.strictEqual(deleted, true); 109 | // make sure the directory has been deleted. 110 | assert.throws(() => { 111 | fs.accessSync(outputPath); 112 | }); 113 | }, 114 | ); 115 | }); 116 | }); 117 | -------------------------------------------------------------------------------- /test/test-init.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Google LLC. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import * as sinon from 'sinon'; 18 | import * as cp from 'child_process'; 19 | import * as assert from 'assert'; 20 | import * as fs from 'fs'; 21 | import * as path from 'path'; 22 | import {accessSync} from 'fs'; 23 | import {PackageJSON} from '@npm/types'; 24 | import {withFixtures, Fixtures} from 'inline-fixtures'; 25 | import {describe, it, beforeEach, afterEach} from 'mocha'; 26 | 27 | import {nop, readJsonp as readJson, DefaultPackage} from '../src/util'; 28 | import {Options} from '../src/cli'; 29 | import * as init from '../src/init'; 30 | 31 | const OPTIONS: Options = { 32 | gtsRootDir: path.resolve(__dirname, '../..'), 33 | targetRootDir: './', 34 | dryRun: false, 35 | yes: false, 36 | no: false, 37 | logger: {log: nop, error: nop, dir: nop}, 38 | }; 39 | const OPTIONS_YES = Object.assign({}, OPTIONS, {yes: true}); 40 | const OPTIONS_NO = Object.assign({}, OPTIONS, {no: true}); 41 | const OPTIONS_YARN = Object.assign({}, OPTIONS_YES, {yarn: true}); 42 | const MINIMAL_PACKAGE_JSON = {name: 'name', version: 'v1.1.1'}; 43 | 44 | function hasExpectedScripts(packageJson: PackageJSON): boolean { 45 | return ( 46 | !!packageJson.scripts && 47 | ['lint', 'clean', 'compile', 'fix', 'prepare', 'pretest', 'posttest'].every( 48 | s => !!packageJson.scripts![s], 49 | ) 50 | ); 51 | } 52 | 53 | function hasExpectedDependencies(packageJson: PackageJSON): boolean { 54 | return ( 55 | !!packageJson.devDependencies && 56 | ['gts', 'typescript'].every(d => !!packageJson.devDependencies![d]) 57 | ); 58 | } 59 | 60 | describe('init', () => { 61 | const sandbox = sinon.createSandbox(); 62 | 63 | beforeEach(function () { 64 | this.spawnSyncStub = sandbox.stub(cp, 'spawnSync'); 65 | }); 66 | 67 | afterEach(function () { 68 | this.spawnSyncStub.restore(); 69 | }); 70 | 71 | it('addScripts should add a scripts section if none exists', async () => { 72 | const pkg: PackageJSON = {...MINIMAL_PACKAGE_JSON}; 73 | const result = await init.addScripts(pkg, OPTIONS); 74 | assert.strictEqual(result, true); // made edits. 75 | assert.ok(pkg.scripts); 76 | assert.strictEqual(hasExpectedScripts(pkg), true); 77 | }); 78 | 79 | it('addScripts should not edit existing scripts on no', async () => { 80 | const SCRIPTS = { 81 | lint: 'fake lint', 82 | clean: 'fake clean', 83 | compile: 'fake tsc', 84 | fix: 'fake fix', 85 | prepare: 'fake run compile', 86 | pretest: 'fake run compile', 87 | posttest: 'fake run lint', 88 | }; 89 | const pkg: PackageJSON = { 90 | ...MINIMAL_PACKAGE_JSON, 91 | scripts: {...SCRIPTS}, 92 | }; 93 | const result = await init.addScripts(pkg, OPTIONS_NO); 94 | assert.strictEqual(result, false); // no edits. 95 | assert.deepStrictEqual(pkg.scripts, SCRIPTS); 96 | }); 97 | 98 | it('addScripts should edit existing scripts on yes', async () => { 99 | const SCRIPTS = { 100 | lint: 'fake lint', 101 | clean: 'fake clean', 102 | compile: 'fake tsc', 103 | fix: 'fake fix', 104 | prepare: 'fake run compile', 105 | pretest: 'fake run compile', 106 | posttest: 'fake run lint', 107 | }; 108 | const pkg: PackageJSON = { 109 | ...MINIMAL_PACKAGE_JSON, 110 | scripts: {...SCRIPTS}, 111 | }; 112 | const result = await init.addScripts(pkg, OPTIONS_YES); 113 | assert.strictEqual(result, true); // made edits. 114 | assert.notDeepStrictEqual(pkg.scripts, SCRIPTS); 115 | }); 116 | 117 | it('addDependencies should add a deps section if none exists', async () => { 118 | const pkg: PackageJSON = {...MINIMAL_PACKAGE_JSON}; 119 | const result = await init.addDependencies(pkg, OPTIONS); 120 | assert.strictEqual(result, true); // made edits. 121 | assert.ok(pkg.devDependencies); 122 | }); 123 | 124 | it('addDependencies should not edit existing deps on no', async () => { 125 | const DEPS: DefaultPackage = { 126 | gts: 'something', 127 | typescript: 'or the other', 128 | '@types/node': 'or another', 129 | }; 130 | const pkg: PackageJSON = { 131 | ...MINIMAL_PACKAGE_JSON, 132 | devDependencies: {...DEPS}, 133 | }; 134 | const OPTIONS_NO = Object.assign({}, OPTIONS, {no: true}); 135 | const result = await init.addDependencies(pkg, OPTIONS_NO); 136 | assert.strictEqual(result, false); // no edits. 137 | assert.deepStrictEqual(pkg.devDependencies, DEPS); 138 | }); 139 | 140 | it('addDependencies should edit existing deps on yes', async () => { 141 | const DEPS = {gts: 'something', typescript: 'or the other'}; 142 | const pkg: PackageJSON = { 143 | ...MINIMAL_PACKAGE_JSON, 144 | devDependencies: {...DEPS}, 145 | }; 146 | const result = await init.addDependencies(pkg, OPTIONS_YES); 147 | assert.strictEqual(result, true); // made edits. 148 | assert.notDeepStrictEqual(pkg.devDependencies, DEPS); 149 | }); 150 | 151 | // init 152 | it('init should read local package.json', () => { 153 | const originalContents = {some: 'property'}; 154 | return withFixtures( 155 | {'package.json': JSON.stringify(originalContents)}, 156 | async () => { 157 | const result = await init.init(OPTIONS_YES); 158 | assert.strictEqual(result, true); 159 | const contents = await readJson('./package.json'); 160 | 161 | assert.notStrictEqual( 162 | contents, 163 | originalContents, 164 | 'the file should have been modified', 165 | ); 166 | assert.strictEqual( 167 | contents.some, 168 | originalContents.some, 169 | 'unrelated property should have preserved', 170 | ); 171 | }, 172 | ); 173 | }); 174 | 175 | it('init should handle missing package.json', () => { 176 | return withFixtures({}, async () => { 177 | const result = await init.init(OPTIONS_YES); 178 | assert.strictEqual(result, true); 179 | const contents = await readJson('./package.json'); 180 | assert.strictEqual(hasExpectedScripts(contents), true); 181 | assert.strictEqual(hasExpectedDependencies(contents), true); 182 | }); 183 | }); 184 | 185 | it('init should support yarn', () => { 186 | return withFixtures( 187 | { 188 | 'package.json': JSON.stringify({name: 'test'}), 189 | 'yarn.lock': '', 190 | }, 191 | async () => { 192 | const result = await init.init(OPTIONS_YARN); 193 | assert.strictEqual(result, true); 194 | 195 | const contents = await readJson('./package.json'); 196 | const cmd = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'; 197 | assert.strictEqual(contents.scripts.prepare, cmd + ' run compile'); 198 | }, 199 | ); 200 | }); 201 | 202 | it('should install a default template if the source directory do not exists', () => { 203 | return withFixtures({}, async dir => { 204 | const indexPath = path.join(dir, 'src', 'index.ts'); 205 | await init.init(OPTIONS_YES); 206 | assert.doesNotThrow(() => { 207 | accessSync(indexPath); 208 | }); 209 | }); 210 | }); 211 | 212 | it('should install template copy if src directory already exists and is empty', () => { 213 | const FIXTURES = { 214 | src: {}, 215 | }; 216 | return withFixtures(FIXTURES, async dir => { 217 | const dirPath = path.join(dir, 'src'); 218 | const created = await init.installDefaultTemplate(OPTIONS_YES); 219 | assert.strictEqual(created, true); 220 | assert.doesNotThrow(() => { 221 | accessSync(path.join(dirPath, 'index.ts')); 222 | }); 223 | }); 224 | }); 225 | 226 | it('should install template copy if src directory already exists and contains files other than ts', () => { 227 | const FIXTURES = { 228 | src: { 229 | 'README.md': '# Read this', 230 | }, 231 | }; 232 | return withFixtures(FIXTURES, async dir => { 233 | const dirPath = path.join(dir, 'src'); 234 | const created = await init.installDefaultTemplate(OPTIONS_YES); 235 | assert.strictEqual(created, true); 236 | assert.doesNotThrow(() => { 237 | // Both old and new files should exist. 238 | accessSync(path.join(dirPath, 'README.md')); 239 | accessSync(path.join(dirPath, 'index.ts')); 240 | }); 241 | }); 242 | }); 243 | 244 | it('should copy the template with correct contents', () => { 245 | const FIXTURES = { 246 | src: {}, 247 | }; 248 | return withFixtures(FIXTURES, async dir => { 249 | const destDir = path.join(dir, 'src'); 250 | const created = await init.installDefaultTemplate(OPTIONS_YES); 251 | assert.strictEqual(created, true); 252 | 253 | // make sure the target directory exists. 254 | accessSync(destDir); 255 | 256 | // make sure the copied file exists and has the same content. 257 | const srcFilename = path.join(__dirname, '../template/index.ts'); 258 | const destFilename = path.join(destDir, 'index.ts'); 259 | const content = fs.readFileSync(destFilename, 'utf8'); 260 | assert.strictEqual(content, fs.readFileSync(srcFilename, 'utf8')); 261 | }); 262 | }); 263 | 264 | it('should not install the default template if the source directory already exists and does contain ts files', () => { 265 | const EXISTING = 'src'; 266 | const FIXTURES: Fixtures = { 267 | [EXISTING]: { 268 | 'main.ts': '42;', 269 | }, 270 | }; 271 | return withFixtures(FIXTURES, async dir => { 272 | const newPath = path.join(dir, 'src'); 273 | const created = await init.installDefaultTemplate(OPTIONS_YES); 274 | assert.strictEqual(created, false); 275 | assert.doesNotThrow(() => { 276 | accessSync(newPath); 277 | }); 278 | }); 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /test/test-util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import * as assert from 'assert'; 17 | import * as path from 'path'; 18 | import {PathLike} from 'fs'; 19 | import {describe, it} from 'mocha'; 20 | import { 21 | ConfigFile, 22 | getTSConfig, 23 | isYarnUsed, 24 | getPkgManagerCommand, 25 | } from '../src/util'; 26 | 27 | /** 28 | * Creates a fake promisified readFile function from a map 29 | * @param myMap contains a filepath as the key and a ConfigFile object as the 30 | * value. 31 | * The returned function has the same interface as fs.readFile 32 | */ 33 | function createFakeReadFilep(myMap: Map) { 34 | return (configPath: string) => { 35 | const configFile = myMap.get(configPath); 36 | if (configFile) { 37 | return Promise.resolve(JSON.stringify(configFile)); 38 | } else { 39 | return Promise.reject(`${configPath} Not Found`); 40 | } 41 | }; 42 | } 43 | 44 | function makeFakeFsExistsSync( 45 | expected: PathLike[], 46 | ): (path: PathLike) => boolean { 47 | return (path: PathLike) => expected.some(item => item === path); 48 | } 49 | 50 | const FAKE_DIRECTORY = '/some/fake/directory'; 51 | const PATH_TO_TSCONFIG = path.resolve(FAKE_DIRECTORY, 'tsconfig.json'); 52 | const PATH_TO_CONFIG2 = path.resolve(FAKE_DIRECTORY, 'FAKE_CONFIG2'); 53 | const PATH_TO_CONFIG3 = path.resolve(FAKE_DIRECTORY, 'FAKE_CONFIG3'); 54 | 55 | describe('util', () => { 56 | it('get should parse the correct tsconfig file', async () => { 57 | const FAKE_CONFIG1 = {files: ['b']}; 58 | 59 | function fakeReadFilep( 60 | configPath: string, 61 | encoding: string, 62 | ): Promise { 63 | assert.strictEqual(configPath, PATH_TO_TSCONFIG); 64 | assert.strictEqual(encoding, 'utf8'); 65 | return Promise.resolve(JSON.stringify(FAKE_CONFIG1)); 66 | } 67 | const contents = await getTSConfig(FAKE_DIRECTORY, fakeReadFilep); 68 | 69 | assert.deepStrictEqual(contents, FAKE_CONFIG1); 70 | }); 71 | 72 | it('should throw an error if it finds a circular reference', () => { 73 | const FAKE_CONFIG1 = {files: ['b'], extends: 'FAKE_CONFIG2'}; 74 | const FAKE_CONFIG2 = {extends: 'FAKE_CONFIG3'}; 75 | const FAKE_CONFIG3 = {extends: 'tsconfig.json'}; 76 | const myMap = new Map(); 77 | myMap.set(PATH_TO_TSCONFIG, FAKE_CONFIG1); 78 | myMap.set(PATH_TO_CONFIG2, FAKE_CONFIG2); 79 | myMap.set(PATH_TO_CONFIG3, FAKE_CONFIG3); 80 | 81 | // eslint-disable-next-line n/no-unsupported-features/node-builtins 82 | return assert.rejects( 83 | () => getTSConfig(FAKE_DIRECTORY, createFakeReadFilep(myMap)), 84 | Error, 85 | 'Circular Reference Detected', 86 | ); 87 | }); 88 | 89 | it('should follow dependency chain caused by extends files', async () => { 90 | const FAKE_CONFIG1 = { 91 | compilerOptions: {a: 'n'}, 92 | files: ['b'], 93 | extends: 'FAKE_CONFIG2', 94 | }; 95 | const FAKE_CONFIG2 = {include: ['/stuff/*'], extends: 'FAKE_CONFIG3'}; 96 | const FAKE_CONFIG3 = {exclude: ['doesnt/look/like/anything/to/me']}; 97 | const combinedConfig = { 98 | compilerOptions: {a: 'n'}, 99 | files: ['b'], 100 | include: ['/stuff/*'], 101 | exclude: ['doesnt/look/like/anything/to/me'], 102 | }; 103 | 104 | const myMap = new Map(); 105 | myMap.set(PATH_TO_TSCONFIG, FAKE_CONFIG1); 106 | myMap.set(PATH_TO_CONFIG2, FAKE_CONFIG2); 107 | myMap.set(PATH_TO_CONFIG3, FAKE_CONFIG3); 108 | 109 | const contents = await getTSConfig( 110 | FAKE_DIRECTORY, 111 | createFakeReadFilep(myMap), 112 | ); 113 | assert.deepStrictEqual(contents, combinedConfig); 114 | }); 115 | 116 | it('when a file contains an extends field, the base file is loaded first then overridden by the inherited files', async () => { 117 | const FAKE_CONFIG1 = {files: ['b'], extends: 'FAKE_CONFIG2'}; 118 | const FAKE_CONFIG2 = {files: ['c'], extends: 'FAKE_CONFIG3'}; 119 | const FAKE_CONFIG3 = {files: ['d']}; 120 | const combinedConfig = {compilerOptions: {}, files: ['b']}; 121 | const myMap = new Map(); 122 | myMap.set(PATH_TO_TSCONFIG, FAKE_CONFIG1); 123 | myMap.set(PATH_TO_CONFIG2, FAKE_CONFIG2); 124 | myMap.set(PATH_TO_CONFIG3, FAKE_CONFIG3); 125 | 126 | const contents = await getTSConfig( 127 | FAKE_DIRECTORY, 128 | createFakeReadFilep(myMap), 129 | ); 130 | assert.deepStrictEqual(contents, combinedConfig); 131 | }); 132 | 133 | it('when reading a file, all filepaths should be relative to the config file currently being read', async () => { 134 | const FAKE_CONFIG1 = {files: ['b'], extends: './foo/FAKE_CONFIG2'}; 135 | const FAKE_CONFIG2 = {include: ['c'], extends: './bar/FAKE_CONFIG3'}; 136 | const FAKE_CONFIG3 = {exclude: ['d']}; 137 | const combinedConfig = { 138 | compilerOptions: {}, 139 | exclude: ['d'], 140 | files: ['b'], 141 | include: ['c'], 142 | }; 143 | const myMap = new Map(); 144 | myMap.set(PATH_TO_TSCONFIG, FAKE_CONFIG1); 145 | myMap.set(path.resolve(FAKE_DIRECTORY, './foo/FAKE_CONFIG2'), FAKE_CONFIG2); 146 | myMap.set( 147 | path.resolve(FAKE_DIRECTORY, './foo/bar/FAKE_CONFIG3'), 148 | FAKE_CONFIG3, 149 | ); 150 | 151 | const contents = await getTSConfig( 152 | FAKE_DIRECTORY, 153 | createFakeReadFilep(myMap), 154 | ); 155 | assert.deepStrictEqual(contents, combinedConfig); 156 | }); 157 | 158 | it('function throws an error when reading a file that does not exist', () => { 159 | const myMap = new Map(); 160 | 161 | // eslint-disable-next-line n/no-unsupported-features/node-builtins 162 | return assert.rejects( 163 | () => getTSConfig(FAKE_DIRECTORY, createFakeReadFilep(myMap)), 164 | Error, 165 | `${FAKE_DIRECTORY}/tsconfig.json Not Found`, 166 | ); 167 | }); 168 | 169 | it("isYarnUsed returns true if there's yarn.lock file only", () => { 170 | const existsSync = makeFakeFsExistsSync(['yarn.lock']); 171 | assert.strictEqual(isYarnUsed(existsSync), true); 172 | }); 173 | 174 | it("isYarnUsed returns false if there's package-lock.json file only", () => { 175 | const existsSync = makeFakeFsExistsSync(['package-lock.json']); 176 | assert.strictEqual(isYarnUsed(existsSync), false); 177 | }); 178 | 179 | it("isYarnUsed returns false if there're yarn.lock and package-lock.json files", () => { 180 | const existsSync = makeFakeFsExistsSync(['package-lock.json', 'yarn.lock']); 181 | assert.strictEqual(isYarnUsed(existsSync), false); 182 | }); 183 | 184 | const npmCmd = process.platform !== 'win32' ? 'npm' : 'npm.cmd'; 185 | const yarnCmd = process.platform !== 'win32' ? 'yarn' : 'yarn.cmd'; 186 | it('getPkgManagerCommand returns npm by default', () => { 187 | assert.strictEqual(getPkgManagerCommand(), npmCmd); 188 | assert.strictEqual(getPkgManagerCommand(), getPkgManagerCommand(false)); 189 | }); 190 | 191 | it('getPkgManagerCommand returns yarn', () => { 192 | assert.strictEqual(getPkgManagerCommand(true), yarnCmd); 193 | }); 194 | }); 195 | -------------------------------------------------------------------------------- /tsconfig-google.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "composite": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["ES2023"], 8 | "module": "commonjs", 9 | "noEmitOnError": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "pretty": true, 13 | "sourceMap": true, 14 | "stripInternal": true, 15 | "strict": true, 16 | "target": "ES2022" 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-google.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "build", 6 | "resolveJsonModule": true 7 | }, 8 | "include": [".eslintrc.json", "src/**/*.ts", "test/**/*.ts"], 9 | "exclude": ["test/fixtures/**/*.*", "template/**/*.*"] 10 | } 11 | --------------------------------------------------------------------------------