├── .github
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── RELEASING.md
├── preview.gif
└── workflows
│ ├── format.yml
│ └── unit-test.yml
├── .gitignore
├── LICENSE
├── README.md
├── package.json
├── renovate.json
├── rollup.config.js
├── src
├── api.ts
├── compress.ts
├── compressor.ts
├── helpers
│ ├── fs.ts
│ ├── object.ts
│ └── process.ts
├── index.ts
├── log
│ ├── cli-report.ts
│ ├── helpers
│ │ ├── bytes.ts
│ │ ├── error.ts
│ │ ├── format.ts
│ │ ├── icons.ts
│ │ ├── output.ts
│ │ └── path.ts
│ ├── no-tty-report.ts
│ ├── report.ts
│ └── tty-report.ts
├── tsconfig.json
└── validation
│ ├── Condition.ts
│ ├── Config.ts
│ ├── File.ts
│ ├── Project.ts
│ └── Track.ts
└── test
├── config-validation
├── config-validation.test.ts
├── fixtures
│ ├── compression-missing
│ │ ├── index.js
│ │ └── package.json
│ ├── item-path-missing
│ │ └── package.json
│ ├── max-size-missing
│ │ ├── index.js
│ │ └── package.json
│ ├── missing-configuration
│ │ └── package.json
│ ├── missing-package-json
│ │ └── index.js
│ ├── standalone-config
│ │ ├── filesize.json
│ │ └── index.js
│ ├── track-standalone-format
│ │ ├── index.js
│ │ └── package.json
│ ├── track-standalone
│ │ ├── index.js
│ │ └── package.json
│ ├── track
│ │ ├── index.js
│ │ └── package.json
│ └── unparseable-package-json
│ │ └── package.json
└── track.test.ts
├── end-to-end
├── fixtures
│ ├── api-report
│ │ ├── filesize.json
│ │ ├── inferno.js
│ │ ├── package.json
│ │ ├── preact.js
│ │ └── react-dom.js
│ ├── item-too-large
│ │ ├── dist
│ │ │ ├── bar.js
│ │ │ ├── baz.js
│ │ │ ├── foo.js
│ │ │ └── index.js
│ │ ├── filesize.json
│ │ └── package.json
│ ├── missing-item
│ │ ├── filesize.json
│ │ └── package.json
│ └── successful
│ │ ├── filesize.json
│ │ ├── index.js
│ │ ├── package.json
│ │ └── replaced.js
├── large-config.test.ts
├── large-project.test.ts
├── successful-config.test.ts
├── successful-project.test.ts
└── throw-error.test.ts
├── project-validation
├── fixtures
│ ├── contains-package-json
│ │ ├── index.js
│ │ └── package.json
│ └── missing-package-json
│ │ └── index.js
└── project-validation.test.ts
└── tsconfig.json
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | See https://github.com/ampproject/meta/blob/master/CODE_OF_CONDUCT.md
2 |
--------------------------------------------------------------------------------
/.github/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.
24 |
25 | ## Community Guidelines
26 |
27 | The AMP Project strives for a positive and growing project community that provides a safe environment for everyone. All members, committers and volunteers in the community are required to act according to the [code of conduct](CODE_OF_CONDUCT.md).
--------------------------------------------------------------------------------
/.github/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | Most of the manual steps for releasing this library are (hopefully) abstracted away by
4 | leveraging `np` to automatically update `package.json` and generate an updated changelist
5 | based on commits since the last active tag.
6 |
7 | ### Release Pre-Requisites:
8 |
9 | 1. Enable [two-factor authentication for your NPM account](https://docs.npmjs.com/configuring-two-factor-authentication).
10 | 2. Ensure you are part of the 'Administration' Group for the NPM `@ampproject` organization.
11 |
12 | ### Release an Update:
13 |
14 | 1. Ensure all tests are passing `yarn test`. The release scripts will not proceed
15 | if any test fails.
16 | 2. Execute in your terminal: `yarn release`.
17 |
--------------------------------------------------------------------------------
/.github/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/.github/preview.gif
--------------------------------------------------------------------------------
/.github/workflows/format.yml:
--------------------------------------------------------------------------------
1 | name: Format
2 | on: pull_request
3 | jobs:
4 | format:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | matrix:
8 | node-version: [14.x]
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v2
12 | - name: Use Node.js ${{ matrix.node-version }}
13 | uses: actions/setup-node@v2
14 | with:
15 | node-version: ${{ matrix.node-version }}
16 | - name: Format
17 | run: yarn install; yarn format
18 | - name: Push changes
19 | uses: kristoferbaxter/github-push-action@master
20 | with:
21 | force: true
22 | github_token: ${{ secrets.GITHUB_TOKEN }}
23 | branch: ${{ github.head_ref }}
--------------------------------------------------------------------------------
/.github/workflows/unit-test.yml:
--------------------------------------------------------------------------------
1 | name: Unit Tests
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | node-version: [14.x]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Use Node.js ${{ matrix.node-version }}
14 | uses: actions/setup-node@v2
15 | with:
16 | node-version: ${{ matrix.node-version }}
17 | - run: yarn install; yarn test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | output/
2 | test/output/
3 | node_modules/
4 | .vscode/
5 | .DS_Store
6 | .nyc_output
7 | .rollup.cache
8 | coverage
9 | yarn-error.log
10 | yarn.lock
11 | package-lock.json
12 | dist/*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2019, Google Inc.
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Filesize
2 |
3 | **Purpose**: Monitor the size of files in your project specified within `package.json`.
4 |
5 | Uses native compression from Node Core, attempts to parallelize compression work across available CPUs, and requires Node >= `12`.
6 |
7 | 
8 |
9 | ## Installation
10 |
11 | ```bash
12 | yarn add @ampproject/filesize --dev
13 | ```
14 |
15 | ## Usage
16 |
17 | Specify an object of files you'd like to check the size for inside the `filesize` key of `package.json`.
18 |
19 | ```json
20 | {
21 | "filesize": {
22 | "./dist/index.js": {
23 | "brotli": "11.4 kB"
24 | }
25 | }
26 | }
27 | ```
28 |
29 | Each file (key in the filesize object) must include an object with key/value pairs:
30 | 1. The key is the `compression` type you would like to use on the file.
31 | 2. The value is the string representation of the files maximum allowed size.
32 |
33 | **After completing configuration**, invoke `filesize` via: `yarn filesize`.
34 |
35 | Optionally one can target a different project directory via the `p` parameter `yarn filesize -p={PATH}`, or a different configuration file via the `c` parameter `yarn filesize -c=${PATH/filesize.json}`.
36 |
37 | ### Track Resource Size
38 |
39 | This utility now also supports tracking filesizes without enforcing a max limit. To use this feature add a `track` key to the `filesize` entry.
40 |
41 | ```json
42 | {
43 | "filesize": {
44 | "track": ["./dist/**/*.mjs"],
45 | }
46 | }
47 | ```
48 |
49 | These values will be added to the output report for all comression types.
50 |
51 | ## Security disclosures
52 |
53 | The AMP Project accepts responsible security disclosures through the [Google Application Security program](https://www.google.com/about/appsecurity/).
54 |
55 | ## Code of conduct
56 |
57 | The AMP Project strives for a positive and growing project community that provides a safe environment for everyone. All members, committers and volunteers in the community are required to act according to the [code of conduct](.github/CODE_OF_CONDUCT.md).
58 |
59 | ## License
60 |
61 | filesize is licensed under the [Apache License, Version 2.0](LICENSE).
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ampproject/filesize",
3 | "version": "4.2.1",
4 | "description": "Audit the filesize of items specified in package.json.",
5 | "author": "Kristofer Baxter",
6 | "license": "Apache-2.0",
7 | "main": "dist/api.js",
8 | "module": "dist/api.mjs",
9 | "files": [
10 | "dist"
11 | ],
12 | "engines": {
13 | "node": ">=12"
14 | },
15 | "bin": {
16 | "filesize": "./dist/filesize"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/ampproject/filesize.git"
21 | },
22 | "scripts": {
23 | "clean": "rimraf dist output test/output",
24 | "format": "prettier --write '**/*.ts'",
25 | "build-tests": "tsc -p test/tsconfig.json",
26 | "postbuild-tests": "tsc-esm -p test/tsconfig.json",
27 | "pretest": "yarn npm-run-all build build-tests",
28 | "test": "NODE_DISABLE_COLORS=1 tap --no-coverage --no-esm test/output/**/*.test.mjs",
29 | "build": "rollup -c; chmod a+x dist/filesize",
30 | "try": "./dist/filesize",
31 | "release": "np --no-2fa --any-branch",
32 | "prepublishOnly": "yarn npm-run-all clean build"
33 | },
34 | "dependencies": {
35 | "@kristoferbaxter/async": "1.0.0",
36 | "@kristoferbaxter/kleur": "4.0.2",
37 | "@kristoferbaxter/bytes": "0.1.2",
38 | "fast-glob": "3.2.5",
39 | "mri": "1.1.6"
40 | },
41 | "devDependencies": {
42 | "@ampproject/rollup-plugin-closure-compiler": "0.26.0",
43 | "@rollup/plugin-commonjs": "17.1.0",
44 | "@rollup/plugin-node-resolve": "11.1.1",
45 | "@rollup/plugin-typescript": "8.1.1",
46 | "@types/mri": "1.1.0",
47 | "@types/node": "14.14.35",
48 | "@types/tap": "14.10.2",
49 | "np": "https://github.com/pixelastic/np/tarball/c3ab2e3b053c7da0ce40a572ca1616273ac080f8",
50 | "npm-run-all": "4.1.5",
51 | "prettier": "2.2.1",
52 | "rimraf": "3.0.2",
53 | "rollup": "2.38.5",
54 | "tap": "14.11.0",
55 | "tslib": "2.1.0",
56 | "typescript": "4.1.3",
57 | "typescript-esm": "2.0.0"
58 | },
59 | "volta": {
60 | "node": "14.16.0",
61 | "yarn": "1.22.10"
62 | },
63 | "filesize": {
64 | "track": [
65 | "./dist/**/*.mjs",
66 | "./dist/filesize"
67 | ],
68 | "./dist/filesize": {
69 | "brotli": "3 kB",
70 | "gzip": "3.8 kB",
71 | "none": "20 kB"
72 | },
73 | "./dist/api.js": {
74 | "brotli": "5 kB",
75 | "gzip": "6.5 kB"
76 | },
77 | "./dist/api.mjs": {
78 | "gzip": "10 kB"
79 | }
80 | },
81 | "publishConfig": {
82 | "access": "public"
83 | },
84 | "prettier": {
85 | "printWidth": 120,
86 | "trailingComma": "all",
87 | "parser": "typescript",
88 | "singleQuote": true
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 resolve from '@rollup/plugin-node-resolve';
18 | import typescript from '@rollup/plugin-typescript';
19 | import commonjs from '@rollup/plugin-commonjs';
20 | import compiler from '@ampproject/rollup-plugin-closure-compiler';
21 |
22 | const external = ['os', 'zlib', 'path', 'fs', 'stream', 'util', 'events', 'fast-glob', 'process'];
23 | const plugins = executable => [
24 | resolve({ preferBuiltins: true }),
25 | commonjs({ include: 'node_modules/**' }),
26 | typescript({ tsconfig: 'src/tsconfig.json', include: '**/*.ts', exclude: 'dist/**/*.ts' }),
27 | executable ? compiler() : null,
28 | ];
29 |
30 | export default [
31 | {
32 | input: 'src/index.ts',
33 | output: {
34 | file: 'dist/filesize',
35 | format: 'cjs',
36 | sourcemap: true,
37 | banner: '#!/usr/bin/env node',
38 | },
39 | external,
40 | plugins: plugins(true),
41 | },
42 | {
43 | input: 'src/api.ts',
44 | output: {
45 | file: 'dist/api.mjs',
46 | format: 'esm',
47 | sourcemap: true,
48 | },
49 | external,
50 | plugins: plugins(false),
51 | },
52 | {
53 | input: 'src/api.ts',
54 | output: {
55 | file: 'dist/api.js',
56 | format: 'cjs',
57 | sourcemap: true,
58 | },
59 | external,
60 | plugins: plugins(false),
61 | },
62 | ];
63 |
--------------------------------------------------------------------------------
/src/api.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 path from 'path';
18 | import Project from './validation/Project';
19 | import Config from './validation/Config';
20 | import { Context, FileModifier, SizeMap } from './validation/Condition';
21 | import compress, { CompressionItem, findItemsToCompress } from './compress';
22 | import { Report } from './log/report';
23 | export { Report } from './log/report';
24 | export { NoTTYReport } from './log/no-tty-report';
25 |
26 | export async function report(
27 | configOrProjectPath: string,
28 | fileModifier: FileModifier,
29 | report?: typeof Report,
30 | silent?: boolean,
31 | ): Promise {
32 | let projectPath = '';
33 | let packagePath = '';
34 | if (path.extname(configOrProjectPath) === '.json') {
35 | // The requested config or project path is a config.
36 | packagePath = configOrProjectPath;
37 | } else {
38 | projectPath = configOrProjectPath;
39 | }
40 |
41 | const conditions = [Project, Config];
42 | let context: Context = {
43 | projectPath,
44 | packagePath,
45 | packageContent: '',
46 | silent: silent ?? true,
47 | originalPaths: new Map(),
48 | // Stores the result of compression
49 | compressed: new Map(),
50 | // Stores the basis of comparison.
51 | comparison: new Map(),
52 | fileModifier,
53 | fileContents: new Map(),
54 | };
55 |
56 | for (const condition of conditions) {
57 | const message = await condition(context)();
58 | if (message !== null) {
59 | throw message;
60 | }
61 | }
62 |
63 | const toCompress: Array = await findItemsToCompress(context, true);
64 | await compress(context, toCompress, report || null);
65 | return context.compressed;
66 | }
67 |
--------------------------------------------------------------------------------
/src/compress.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 { Context, Compression, OrderedCompressionValues, maxSize } from './validation/Condition';
18 | import { readFile } from './helpers/fs';
19 | import { Report } from './log/report';
20 | import { compressor } from './compressor';
21 | import { pool } from '@kristoferbaxter/async';
22 |
23 | export interface CompressionItem {
24 | path: string;
25 | compression: Compression;
26 | maxSize: maxSize;
27 | }
28 |
29 | /**
30 | * Store the original content so it isn't retrieved from FileSystem for each compression.
31 | * @param context
32 | * @param path
33 | */
34 | async function storeOriginalFileContents(context: Context, path: string): Promise {
35 | if (!context.fileContents.has(path)) {
36 | let content = await readFile(path);
37 | if (context.fileModifier !== null && content !== null) {
38 | content = context.fileModifier(content);
39 | }
40 | context.fileContents.set(path, content || '');
41 | }
42 | }
43 |
44 | /**
45 | * Find all items to compress, and store them for future compression.
46 | * @param context
47 | * @param findDefaultSize
48 | */
49 | export async function findItemsToCompress(context: Context, findDefaultSize: boolean): Promise> {
50 | const toCompress: Array = [];
51 | await pool(Array.from(context.compressed), async ([path, sizeMapValue]) => {
52 | for (let iterator: number = 0; iterator < OrderedCompressionValues.length; iterator++) {
53 | const compression: Compression = OrderedCompressionValues[iterator] as Compression;
54 | const [size, maxSize] = sizeMapValue[iterator];
55 | await storeOriginalFileContents(context, path);
56 | if (findDefaultSize && compression === 'none') {
57 | await compressor(context, null, { path, compression, maxSize });
58 | }
59 | if (size !== undefined) {
60 | toCompress.push({
61 | path,
62 | compression,
63 | maxSize,
64 | });
65 | }
66 | }
67 | });
68 |
69 | return toCompress;
70 | }
71 |
72 | /**
73 | * Given a context, compress all Items within splitting work eagly per cpu core to achieve some concurrency.
74 | * @param context Finalized Valid Context from Configuration
75 | */
76 | export default async function compress(
77 | context: Context,
78 | toCompress: Array,
79 | report: typeof Report | null,
80 | ): Promise {
81 | if (toCompress.length === 0) {
82 | return true;
83 | }
84 |
85 | const reportInstance: Report | null = report ? new report(context) : null;
86 | const successful = await pool(toCompress, (item: CompressionItem) => compressor(context, reportInstance, item));
87 | reportInstance?.end();
88 | return successful.every((success) => success);
89 | }
90 |
--------------------------------------------------------------------------------
/src/compressor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { constants as brotliConstants, brotliCompress, gzip } from 'zlib';
18 | import { Report } from './log/report';
19 | import { Context, Compression, maxSize, OrderedCompressionValues } from './validation/Condition';
20 | import { LogError } from './log/helpers/error';
21 |
22 | type CompressionMethod = (buffer: Buffer, options: {}, callback: (error: Error | null, result: Buffer) => void) => void;
23 |
24 | interface CompressionItem {
25 | path: string;
26 | compression: Compression;
27 | maxSize: maxSize;
28 | }
29 |
30 | const SUPPORTED_COMPRESSION: Map = new Map([
31 | [
32 | 'brotli',
33 | [
34 | brotliCompress,
35 | {
36 | params: {
37 | [brotliConstants.BROTLI_PARAM_MODE]: brotliConstants.BROTLI_DEFAULT_MODE,
38 | [brotliConstants.BROTLI_PARAM_QUALITY]: brotliConstants.BROTLI_MAX_QUALITY,
39 | [brotliConstants.BROTLI_PARAM_SIZE_HINT]: 0,
40 | },
41 | },
42 | ],
43 | ],
44 | [
45 | 'gzip',
46 | [
47 | gzip,
48 | {
49 | level: 9,
50 | },
51 | ],
52 | ],
53 | ]);
54 |
55 | /**
56 | * Use the given configuration and actual size to report item filesize.
57 | * @param report Optional reporter to update with this value
58 | * @param item Configuration for an Item
59 | * @param error Error from compressing an Item
60 | * @param size actual size for this comparison
61 | */
62 | function store(
63 | report: Report | null,
64 | context: Context,
65 | item: CompressionItem,
66 | error: Error | null,
67 | size: number,
68 | ): boolean {
69 | if (error !== null) {
70 | LogError(`Could not compress '${item.path}' with '${item.compression}'.`);
71 | return false;
72 | }
73 |
74 | // Store the size of the item in the compression map.
75 | const sizeMap = context.compressed.get(item.path);
76 | if (sizeMap === undefined) {
77 | LogError(`Could not find item '${item.path}' with '${item.compression}' in compression map.`);
78 | return false;
79 | }
80 | sizeMap[OrderedCompressionValues.indexOf(item.compression)][0] = size;
81 |
82 | report?.update(context);
83 | if (item.maxSize === undefined) {
84 | return true;
85 | }
86 | return size < item.maxSize;
87 | }
88 |
89 | export function compressor(context: Context, report: Report | null, item: CompressionItem): Promise {
90 | const contents = context.fileContents.get(item.path);
91 | if (contents) {
92 | return new Promise((resolve) => {
93 | const buffer = Buffer.from(contents, 'utf8');
94 | const compression = SUPPORTED_COMPRESSION.get(item.compression);
95 | if (compression) {
96 | compression[0](buffer, compression[1], (error: Error | null, result: Buffer) =>
97 | resolve(store(report, context, item, error, result.byteLength)),
98 | );
99 | } else {
100 | resolve(store(report, context, item, null, buffer.byteLength));
101 | }
102 | });
103 | }
104 |
105 | return Promise.resolve(false);
106 | }
107 |
--------------------------------------------------------------------------------
/src/helpers/fs.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 { promises as fs } from 'fs';
18 |
19 | /**
20 | * Returns if a path is a directory.
21 | * @param path
22 | */
23 | export async function isDirectory(path: string): Promise {
24 | try {
25 | return (await fs.lstat(path)).isDirectory();
26 | } catch (e) {}
27 | return false;
28 | }
29 |
30 | /**
31 | * Returns if a path is a file.
32 | * @param path
33 | */
34 | export async function isFile(path: string): Promise {
35 | try {
36 | return (await fs.lstat(path)).isFile();
37 | } catch (e) {}
38 | return false;
39 | }
40 |
41 | /**
42 | * Returns contents of a path as a string
43 | * @param path
44 | */
45 | export async function readFile(path: string): Promise {
46 | try {
47 | return await fs.readFile(path, 'utf-8');
48 | } catch (e) {}
49 | return null;
50 | }
51 |
--------------------------------------------------------------------------------
/src/helpers/object.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | /**
18 | * Helper Method, quick check if an any from JSON is an Object.
19 | * @param item
20 | * @returns {boolean}
21 | */
22 | export function isObject(item: any) {
23 | return item === Object(item);
24 | }
25 |
--------------------------------------------------------------------------------
/src/helpers/process.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 { exit } from 'process';
18 | import { exhaust } from '../log/helpers/output';
19 |
20 | export function shutdown(code: number) {
21 | exhaust(() => {
22 | exit(code);
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 mri from 'mri';
18 | import { stdout } from 'process';
19 | import Project from './validation/Project';
20 | import Config from './validation/Config';
21 | import { Context } from './validation/Condition';
22 | import compress, { CompressionItem, findItemsToCompress } from './compress';
23 | import { LogError } from './log/helpers/error';
24 | import { shutdown } from './helpers/process';
25 | import { Report } from './log/report';
26 | import { TTYReport } from './log/tty-report';
27 | import { NoTTYReport } from './log/no-tty-report';
28 |
29 | const args = mri(process.argv.slice(2), {
30 | alias: { p: 'project', c: 'config' },
31 | default: { project: process.cwd(), config: '', silent: false },
32 | });
33 |
34 | /**
35 | * Read the configuration from the specified project, validate it, perform requested compression, and report the results.
36 | */
37 | (async function () {
38 | const { project: projectPath, silent, config: requestedConfig } = args;
39 | const conditions = [Project, Config];
40 | const context: Context = {
41 | projectPath,
42 | packagePath: requestedConfig,
43 | packageContent: '',
44 | silent,
45 | originalPaths: new Map(),
46 | // Stores the result of compression
47 | compressed: new Map(),
48 | // Stores the basis of comparison.
49 | comparison: new Map(),
50 | fileModifier: null,
51 | fileContents: new Map(),
52 | };
53 |
54 | for (const condition of conditions) {
55 | const message = await condition(context)();
56 | if (message !== null) {
57 | LogError(message);
58 | shutdown(5);
59 | return;
60 | }
61 | }
62 |
63 | const toCompress: Array = await findItemsToCompress(context, true);
64 | const report: typeof Report = stdout.isTTY && toCompress.length < 30 ? TTYReport : NoTTYReport;
65 | const successful = await compress(context, toCompress, report);
66 | if (!successful) {
67 | shutdown(6);
68 | }
69 | })();
70 |
--------------------------------------------------------------------------------
/src/log/cli-report.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 kleur from '@kristoferbaxter/kleur';
18 | import { Report } from './report';
19 | import { Context, brotliSize, gzipSize, noneSize } from '../validation/Condition';
20 | import { maxFormatDisplay, formats } from './helpers/format';
21 | import { maxPathDisplay } from './helpers/path';
22 | import { write } from './helpers/output';
23 | import { prettyBytes } from './helpers/bytes';
24 | import { ICONS } from './helpers/icons';
25 |
26 | export class CLIReport extends Report {
27 | protected maxPathDisplay: number;
28 | protected maxFormatDisplay: number;
29 | protected currentLine: string;
30 |
31 | constructor(context: Context) {
32 | super(context);
33 |
34 | this.silent = context.silent;
35 | this.maxPathDisplay = maxPathDisplay(context);
36 | this.maxFormatDisplay = maxFormatDisplay(context);
37 |
38 | this.start();
39 | }
40 |
41 | private start(): void {
42 | write(kleur.bold('\n Filesizes\n'));
43 | write(`${''.padEnd(this.maxPathDisplay + 5)} ${formats(this.maxFormatDisplay)}\n`);
44 | }
45 |
46 | protected displaySize([size, maxSize]: brotliSize | gzipSize | noneSize): {
47 | success: number;
48 | warning: number;
49 | failure: number;
50 | processing: number;
51 | } {
52 | const status = {
53 | success: 0,
54 | warning: 0,
55 | failure: 0,
56 | processing: 0,
57 | };
58 |
59 | if (size === null) {
60 | // Item is still processing.
61 | status.processing++;
62 | }
63 |
64 | if (size === undefined || size === null) {
65 | // Will not be calculated.
66 | this.currentLine += kleur.dim().grey('–'.padEnd(this.maxFormatDisplay));
67 | return status;
68 | }
69 |
70 | const outputBytes = prettyBytes(size);
71 | if (maxSize === undefined) {
72 | this.currentLine += kleur.dim().grey(outputBytes.padEnd(this.maxFormatDisplay));
73 | return status;
74 | }
75 | if (size < maxSize) {
76 | if (1 - size / maxSize < 0.05) {
77 | this.warning++;
78 | status.warning++;
79 | this.currentLine += kleur.yellow(outputBytes.padEnd(this.maxFormatDisplay));
80 | return status;
81 | }
82 |
83 | this.success++;
84 | status.success++;
85 | this.currentLine += kleur.dim().green(outputBytes.padEnd(this.maxFormatDisplay));
86 | return status;
87 | }
88 |
89 | this.failure++;
90 | status.failure++;
91 | this.currentLine += kleur.red(outputBytes.padEnd(this.maxFormatDisplay));
92 | return status;
93 | }
94 |
95 | public end(): void {
96 | super.end();
97 |
98 | const { success, failure, warning } = this;
99 | if (success > 0 || failure > 0 || warning > 0) {
100 | write(
101 | '\n ' +
102 | kleur.green(success + ` ${success === 1 ? 'check' : 'checks'} passed`) +
103 | (failure === 0 ? ` ${ICONS['tada']}` : ''),
104 | );
105 | if (warning > 0) {
106 | write(
107 | '\n ' +
108 | kleur.yellow(warning + ` ${warning === 1 ? 'check' : 'checks'} warned`) +
109 | kleur.grey(' (within 5% of allowed size)'),
110 | );
111 | }
112 | if (failure > 0) {
113 | write('\n ' + kleur.red(failure + ` ${failure === 1 ? 'check' : 'checks'} failed`));
114 | }
115 | write('\n\n');
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/log/helpers/bytes.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 bytes from '@kristoferbaxter/bytes';
18 |
19 | /**
20 | * Format size into more human readable string.
21 | * @param size
22 | */
23 | export function prettyBytes(size: number): string {
24 | return bytes(size, { unit: 'kb', fixedDecimals: true, unitSeparator: ' ' });
25 | }
26 |
--------------------------------------------------------------------------------
/src/log/helpers/error.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { write } from './output';
18 | import kleur from '@kristoferbaxter/kleur';
19 |
20 | /**
21 | * Format output as an error message.
22 | * @param output
23 | */
24 | export function MakeError(output: string): string {
25 | return `${kleur.red('error')} ${output}`;
26 | }
27 |
28 | /**
29 | * Display output as an error message on the console.
30 | * @param output
31 | */
32 | export function LogError(output: string): void {
33 | write(MakeError(output) + '\n');
34 | }
35 |
--------------------------------------------------------------------------------
/src/log/helpers/format.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { OrderedCompressionValues, Context } from '../../validation/Condition';
18 | import { prettyBytes } from './bytes';
19 |
20 | const COMPRESSED_NAMES_LENGTH = OrderedCompressionValues.map((compression) => compression.length);
21 | const SPACE_AFTER_COMPRESSION = 2;
22 |
23 | /**
24 | * Use the 'none' size of items to find the max width of the format.
25 | * @param context
26 | */
27 | export function maxFormatDisplay(context: Context): number {
28 | const itemsToConsider: Array = [...COMPRESSED_NAMES_LENGTH];
29 | const paths = Array.from(context.compressed.keys());
30 | for (const path of paths) {
31 | const sizeMapValue = context.compressed.get(path);
32 | if (!sizeMapValue) {
33 | continue;
34 | }
35 |
36 | const [size] = sizeMapValue[OrderedCompressionValues.indexOf('none')];
37 | if (size) {
38 | itemsToConsider.push(prettyBytes(size).length);
39 | }
40 | }
41 |
42 | return Math.max.apply(null, itemsToConsider) + SPACE_AFTER_COMPRESSION;
43 | }
44 |
45 | /**
46 | * Output all formats
47 | * @param maxFormatDisplay
48 | */
49 | export function formats(maxFormatDisplay: number): string {
50 | return OrderedCompressionValues.map((compression) => compression.padEnd(maxFormatDisplay)).join('');
51 | }
52 |
--------------------------------------------------------------------------------
/src/log/helpers/icons.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | // Plan to use this spinner while a file is being processed.
18 | const isWin32 = process.platform === 'win32';
19 | export const SPINNER = isWin32
20 | ? {
21 | interval: 130,
22 | frames: ['-', '\\', '|', '/'],
23 | }
24 | : {
25 | interval: 80,
26 | frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
27 | };
28 |
29 | export const ICONS = {
30 | tick: isWin32 ? '√' : '✔',
31 | cross: isWin32 ? '×' : '✖',
32 | tada: '🎉',
33 | };
34 |
--------------------------------------------------------------------------------
/src/log/helpers/output.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { stdout } from 'process';
18 |
19 | const ESC_SEQUENCE = '\u001B[';
20 | const ERASE_LINE = ESC_SEQUENCE + '2K';
21 | const CURSOR_LEFT = ESC_SEQUENCE + 'G';
22 | const CURSOR_UP = ESC_SEQUENCE + '1A';
23 |
24 | const outputQueue: Array = [];
25 |
26 | /**
27 | * Erase the number of lines from a TTY terminal
28 | * @param count
29 | */
30 | export function erase(count: number): void {
31 | if (count <= 0) {
32 | return;
33 | }
34 |
35 | let sequence = '';
36 | for (let i = 0; i < count; i++) {
37 | sequence += ERASE_LINE + (i < count - 1 ? CURSOR_UP : '');
38 | }
39 | if (count) {
40 | sequence += CURSOR_LEFT;
41 | }
42 |
43 | write(sequence);
44 | }
45 |
46 | /**
47 | * Exhause the outputQueue and call the callback method when finished.
48 | * @param callback
49 | */
50 | export function exhaust(callback: () => any = () => void 0) {
51 | const text: string | undefined = outputQueue.shift();
52 |
53 | if (text) {
54 | stdout.write(text, () => {
55 | if (outputQueue.length > 0) {
56 | exhaust(callback);
57 | } else {
58 | callback();
59 | }
60 | });
61 | } else {
62 | callback();
63 | }
64 | }
65 |
66 | export function write(content: string): void {
67 | outputQueue.push(content);
68 | exhaust();
69 | }
70 |
--------------------------------------------------------------------------------
/src/log/helpers/path.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { Context } from '../../validation/Condition';
18 |
19 | const SPACE_AFTER_PATH = 2;
20 | const MAX_ALLOWED_PATH_LENGTH = 50;
21 |
22 | function allowedPathLength(path: string): number {
23 | return Math.min(path.length, MAX_ALLOWED_PATH_LENGTH) + SPACE_AFTER_PATH;
24 | }
25 |
26 | export function maxPathDisplay(context: Context): number {
27 | return Math.max.apply(null, Array.from(context.originalPaths.values()).map(allowedPathLength));
28 | }
29 |
--------------------------------------------------------------------------------
/src/log/no-tty-report.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 kleur from '@kristoferbaxter/kleur';
18 | import { Context, OrderedCompressionValues, SizeMap } from '../validation/Condition';
19 | import { write } from './helpers/output';
20 | import { ICONS } from './helpers/icons';
21 | import { CLIReport } from './cli-report';
22 |
23 | export class NoTTYReport extends CLIReport {
24 | protected maxPathDisplay: number;
25 | protected maxFormatDisplay: number;
26 | protected currentLine: string;
27 |
28 | public update(context: Context): void {
29 | const completed: SizeMap = super.getUpdated(context);
30 | if (this.silent) {
31 | return;
32 | }
33 |
34 | for (const complete of completed) {
35 | const [path, sizeMapValue] = complete;
36 | const displayPath = context.originalPaths.get(path) || path;
37 | let failure = 0;
38 |
39 | this.currentLine = ` ${displayPath
40 | .substring(displayPath.length - this.maxPathDisplay)
41 | .padEnd(this.maxPathDisplay)} `;
42 | for (let i = 0; i < OrderedCompressionValues.length; i++) {
43 | failure += this.displaySize(sizeMapValue[i]).failure;
44 | }
45 |
46 | if (failure > 0) {
47 | this.currentLine = ` ${kleur.red(ICONS['cross'])}${this.currentLine}`;
48 | } else {
49 | this.currentLine = ` ${kleur.dim().green(ICONS['tick'])}${this.currentLine}`;
50 | }
51 |
52 | write(this.currentLine + '\n');
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/log/report.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { Context, SizeMap } from '../validation/Condition';
18 |
19 | export class Report {
20 | protected silent: boolean = false;
21 | protected paths: Set;
22 | protected warning: number = 0;
23 | protected success: number = 0;
24 | protected failure: number = 0;
25 |
26 | constructor(context: Context) {
27 | this.paths = new Set(context.compressed.keys());
28 | }
29 |
30 | public end(): void {}
31 | public update(context: Context): void {}
32 |
33 | public getUpdated(context: Context): SizeMap {
34 | const completed: SizeMap = new Map();
35 |
36 | iterate_paths: for (const path of this.paths) {
37 | const sizeMapValue = context.compressed.get(path);
38 | if (!sizeMapValue) {
39 | continue iterate_paths;
40 | }
41 |
42 | for (const value of sizeMapValue) {
43 | if (value[0] === null) {
44 | // A null value for the size indicates the resource is still processing.
45 | continue iterate_paths;
46 | }
47 | }
48 |
49 | completed.set(path, sizeMapValue);
50 | this.paths.delete(path);
51 | }
52 |
53 | return completed;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/log/tty-report.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 kleur from '@kristoferbaxter/kleur';
18 | import { Context, OrderedCompressionValues } from '../validation/Condition';
19 | import { write, erase } from './helpers/output';
20 | import { ICONS } from './helpers/icons';
21 | import { CLIReport } from './cli-report';
22 |
23 | export class TTYReport extends CLIReport {
24 | private outputLength: number = 0;
25 |
26 | private reset = (): number => {
27 | const previousOutputLength = this.outputLength;
28 | this.currentLine = '';
29 | this.success = 0;
30 | this.failure = 0;
31 | this.warning = 0;
32 | this.outputLength = 0;
33 | return previousOutputLength;
34 | };
35 |
36 | private status = () => {
37 | write(
38 | '\n ' +
39 | kleur.green(this.success + ` ${this.success === 1 ? 'check' : 'checks'} passed`) +
40 | (this.failure === 0 ? ` ${ICONS['tada']}` : ''),
41 | );
42 | this.outputLength++;
43 | if (this.warning > 0) {
44 | write(
45 | '\n ' +
46 | kleur.yellow(this.warning + ` ${this.warning === 1 ? 'check' : 'checks'} warned`) +
47 | kleur.grey(' (within 5% of allowed size)'),
48 | );
49 | this.outputLength++;
50 | }
51 | if (this.failure > 0) {
52 | write('\n ' + kleur.red(this.failure + ` ${this.failure === 1 ? 'check' : 'checks'} failed`));
53 | this.outputLength++;
54 | }
55 | write('\n\n');
56 | this.outputLength = this.outputLength + 3;
57 | };
58 |
59 | public end(): void {}
60 | public update(context: Context): void {
61 | if (this.silent) {
62 | return;
63 | }
64 | const previousOutputLength = this.reset();
65 |
66 | let output: string = '';
67 | for (const path of this.paths) {
68 | const sizeMapValue = context.compressed.get(path);
69 | if (!sizeMapValue) {
70 | continue;
71 | }
72 |
73 | const displayPath = context.originalPaths.get(path) || path;
74 | let failure = 0;
75 | let processing = 0;
76 |
77 | this.currentLine = ` ${displayPath
78 | .substring(displayPath.length - this.maxPathDisplay)
79 | .padEnd(this.maxPathDisplay)} `;
80 | for (let i = 0; i < OrderedCompressionValues.length; i++) {
81 | const status = super.displaySize(sizeMapValue[i]);
82 | failure += status.failure;
83 | processing += status.processing;
84 | }
85 |
86 | const icon =
87 | failure > 0
88 | ? kleur.red(ICONS['cross'])
89 | : processing > 0
90 | ? kleur.dim().grey('-')
91 | : kleur.dim().green(ICONS['tick']);
92 | output += ` ${icon}${this.currentLine}\n`;
93 | this.outputLength++;
94 | }
95 |
96 | erase(previousOutputLength);
97 | write(output);
98 | this.status();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": true,
3 | "compilerOptions": {
4 | "lib": [
5 | "esnext"
6 | ],
7 | "outDir": "output",
8 | "sourceMap": true,
9 | "moduleResolution": "node",
10 | "target": "ES2019",
11 | "module": "esnext",
12 | "allowJs": false,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "noImplicitAny": true,
16 | "noImplicitThis": true,
17 | "alwaysStrict": true,
18 | "esModuleInterop": true,
19 | "declaration": true,
20 | },
21 | "include": [
22 | "*.ts",
23 | "**/*.ts"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/validation/Condition.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 | // string indicates an error of the formatted return type.
18 | // null indicates success.
19 | export type ValidationResponse = string | null;
20 | export type ConditionFunction = (context: Context) => () => Promise;
21 |
22 | export type Compression = 'brotli' | 'gzip' | 'none';
23 | export const OrderedCompressionValues = ['brotli', 'gzip', 'none'];
24 |
25 | type path = string;
26 | export type OriginalPath = Map;
27 |
28 | /*
29 | - number = calculated
30 | - null = awaiting calculation
31 | - undefined = ignored
32 | */
33 | type size = number | null | undefined;
34 | /*
35 | - number = max allowed size
36 | - undefined = unrestricted size
37 | */
38 | export type maxSize = number | undefined;
39 | export type brotliSize = [size, maxSize];
40 | export type gzipSize = [size, maxSize];
41 | export type noneSize = [size, maxSize];
42 | export type SizeMapValue = [brotliSize, gzipSize, noneSize];
43 | export const SizeMapValueIndex = {
44 | brotli: 0,
45 | gzip: 1,
46 | none: 2,
47 | };
48 | export type SizeMap = Map;
49 |
50 | export type FileContentsMap = Map;
51 | export type FileModifier = ((contents: string) => string) | null;
52 |
53 | export interface Context {
54 | projectPath: string;
55 | packagePath: string;
56 | packageContent: string | null;
57 | silent: boolean;
58 | originalPaths: OriginalPath;
59 | // Stores the result of compression
60 | compressed: SizeMap;
61 | // Stores the basis of comparison.
62 | comparison: SizeMap;
63 | // Allows the API to specify a method that alters content before analysis.
64 | fileModifier: FileModifier;
65 | // Stores the contents of files, to avoid reading from disk per compression type.
66 | fileContents: FileContentsMap;
67 | }
68 |
--------------------------------------------------------------------------------
/src/validation/Config.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { readFile } from '../helpers/fs';
18 | import { Context, ConditionFunction, ValidationResponse } from './Condition';
19 | import { Track } from './Track';
20 | import ValidateFileConfig from './File';
21 | import { isObject } from '../helpers/object';
22 |
23 | /**
24 | * Parse the content of the 'filesize' items.
25 | * @param context
26 | * @param overallContext
27 | */
28 | async function parseEnforcedKeys(context: Context, enforcedKeys: any): Promise {
29 | for (const [path, item] of Object.entries(enforcedKeys)) {
30 | if (!isObject(item)) {
31 | continue;
32 | }
33 |
34 | const error = await ValidateFileConfig(path, item as any, context);
35 | if (error) {
36 | return error;
37 | }
38 | }
39 |
40 | return null;
41 | }
42 |
43 | const CONDITIONS = [
44 | /**
45 | * Read the contents of package.json.
46 | * @param context
47 | */
48 | async function readPackage(context: Context): Promise {
49 | const packageContent = await readFile(context.packagePath);
50 | if (packageContent === null) {
51 | return `Could not read the configuration in '${context.packagePath}'`;
52 | }
53 |
54 | context.packageContent = packageContent;
55 | return null;
56 | },
57 | /**
58 | * Parse the content of package.json, ensure data is usable.
59 | * @param context
60 | */
61 | async function parsePackage(context: Context): Promise {
62 | try {
63 | const { filesize: json } = JSON.parse(context.packageContent as string);
64 |
65 | if (typeof json === 'undefined') {
66 | return `There is no 'filesize' configuration in '${context.packagePath}'`;
67 | }
68 |
69 | const { track, trackFormat, ...keys } = json;
70 | if (Object.entries(keys).length === 0 && track === undefined) {
71 | return `There is no data inside the 'filesize' configuration in '${context.packagePath}'`;
72 | }
73 |
74 | if (!isObject(keys) && track === undefined) {
75 | return (
76 | `'filesize' configuration is not an object in '${context.packagePath}'` +
77 | '(See https://github.com/ampproject/filesize/#usage for details on the structure of the filesize object).'
78 | );
79 | }
80 |
81 | // Since we have tracked entries, we need to store them
82 | const parseTrackingResult = await Track(context, json);
83 | if (parseTrackingResult !== null) {
84 | return parseTrackingResult;
85 | }
86 |
87 | const parseKeysResults = await parseEnforcedKeys(context, keys);
88 | if (parseKeysResults !== null) {
89 | return parseKeysResults;
90 | }
91 | } catch (e) {
92 | return `Could not parse '${context.packagePath}'`;
93 | }
94 | return null;
95 | },
96 | ];
97 |
98 | const Config: ConditionFunction = (context: Context) =>
99 | async function () {
100 | for await (const condition of CONDITIONS) {
101 | const conditionResult = await condition(context);
102 | if (conditionResult !== null) {
103 | return conditionResult;
104 | }
105 | }
106 |
107 | return null;
108 | };
109 |
110 | export default Config;
111 |
--------------------------------------------------------------------------------
/src/validation/File.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { Compression, Context, SizeMapValue, SizeMapValueIndex } from './Condition';
18 | import { resolve, isAbsolute } from 'path';
19 | import { isFile } from '../helpers/fs';
20 | import bytes from '@kristoferbaxter/bytes';
21 |
22 | /**
23 | * Format input string to a known Compression Enum Value.
24 | * @param fsValue
25 | */
26 | export function validateCompressionName(fsValue: string): Compression | null {
27 | const lowerCaseValue = fsValue.toLowerCase();
28 | switch (fsValue.toLowerCase()) {
29 | case 'brotli':
30 | case 'gzip':
31 | case 'none':
32 | return lowerCaseValue as Compression;
33 | case '':
34 | return 'none';
35 | default:
36 | return null;
37 | }
38 | }
39 |
40 | export default async function validateFileConfig(
41 | originalPath: string,
42 | compressionConfig: { [key: string]: string },
43 | context: Context,
44 | ): Promise {
45 | const entries = Object.entries(compressionConfig);
46 | if (entries.length === 0) {
47 | return `Configuration for '${originalPath}' is invalid. (compression values unspecified)`;
48 | }
49 |
50 | let path: string;
51 | if (isAbsolute(originalPath)) {
52 | path = resolve(originalPath);
53 | } else {
54 | path = resolve(context.projectPath, originalPath);
55 | }
56 | if (!(await isFile(path))) {
57 | return `Configuration for '${originalPath}' is invalid. (path is not a valid file)`;
58 | }
59 |
60 | for (const [configKey, configValue] of entries) {
61 | const compression: Compression | null = validateCompressionName(configKey);
62 | if (compression === null) {
63 | return `Configuration for '${originalPath}' is invalid. (Invalid compression value '${configKey}')`;
64 | }
65 |
66 | const maxSize = bytes(configValue);
67 | if (maxSize === null || maxSize < 0) {
68 | return `Configuration for '${originalPath}' is invalid. (size unspecified)`;
69 | }
70 |
71 | let compressedItem: SizeMapValue | undefined = context.compressed.get(path);
72 | if (!compressedItem) {
73 | compressedItem = [
74 | [undefined, undefined],
75 | [undefined, undefined],
76 | [undefined, undefined],
77 | ];
78 | }
79 | compressedItem[SizeMapValueIndex[compression]] = [null, maxSize];
80 | context.compressed.set(path, compressedItem);
81 | context.originalPaths.set(path, originalPath);
82 | }
83 | return null;
84 | }
85 |
--------------------------------------------------------------------------------
/src/validation/Project.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 { ConditionFunction, Context } from './Condition';
18 | import { isDirectory, isFile } from '../helpers/fs';
19 | import { resolve, dirname } from 'path';
20 | import { MakeError } from '../log/helpers/error';
21 |
22 | /**
23 | * Ensure context contains a valid project directory and `package.json` inside.
24 | * @param context
25 | */
26 | const Project: ConditionFunction = (context: Context) => {
27 | return async function () {
28 | if (context.packagePath !== '') {
29 | // The package path was specified up front, its likely not a package.json
30 | if (!(await isFile(context.packagePath))) {
31 | return MakeError(`config specified '${context.packagePath}' doesn't exist, is this a valid project?`);
32 | }
33 | context.projectPath = dirname(context.packagePath);
34 | return null;
35 | }
36 |
37 | const projectPath: string = resolve(context.projectPath);
38 | if (!(await isDirectory(projectPath))) {
39 | return MakeError(`project specified '${context.projectPath}' doesn't exist, is this a valid project?`);
40 | }
41 |
42 | const packagePath = resolve(context.projectPath, 'package.json');
43 | if (!(await isFile(packagePath))) {
44 | return MakeError(`Missing '${packagePath}', is this a valid project?`);
45 | }
46 |
47 | context.projectPath = projectPath;
48 | context.packagePath = packagePath;
49 | return null;
50 | };
51 | };
52 |
53 | export default Project;
54 |
--------------------------------------------------------------------------------
/src/validation/Track.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 { resolve } from 'path';
18 | import glob from 'fast-glob';
19 | import { Context, ValidationResponse, SizeMapValue } from './Condition';
20 | import { validateCompressionName } from './File';
21 |
22 | /**
23 | * Given formats, format a SizeMapValue.
24 | * @param {Array} formats
25 | * @return {SizeMapValue}
26 | */
27 | function getTrackedFormats(formats: Array): SizeMapValue {
28 | return [
29 | [formats.includes('brotli') ? null : undefined, undefined],
30 | [formats.includes('gzip') ? null : undefined, undefined],
31 | [formats.includes('none') ? null : undefined, undefined],
32 | ];
33 | }
34 |
35 | /**
36 | * Use 'fast-glob' to find files requested to track from configuration.
37 | * @param {Context} context
38 | */
39 | export async function Track(context: Context, json: any): Promise {
40 | if ('track' in json && Array.isArray(json.track)) {
41 | const entries: Array = await glob(json.track);
42 | let formats = ['brotli', 'gzip', 'none'];
43 |
44 | if ('trackFormat' in json && Array.isArray(json.trackFormat)) {
45 | // `trackFormats` allows you to limit the compression types for tracking
46 | formats = json.trackFormat.map((format: any) => validateCompressionName(String(format))).filter(Boolean);
47 | }
48 |
49 | // glob ensures the results are valid files.
50 | for (const entry of entries) {
51 | const path = resolve(entry);
52 |
53 | context.compressed.set(path, getTrackedFormats(formats));
54 | context.originalPaths.set(path, entry);
55 | }
56 | }
57 |
58 | return null;
59 | }
60 |
--------------------------------------------------------------------------------
/test/config-validation/config-validation.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 tap from 'tap';
18 | import Config from '../../src/validation/Config';
19 | import { Context } from '../../src/validation/Condition';
20 |
21 | tap.test('missing package.json should fail', async (t) => {
22 | const context: Context = {
23 | packagePath: 'test/config-validation/fixtures/missing-package-json/package.json',
24 | projectPath: 'test/config-validation/fixtures/missing-package-json',
25 | packageContent: '',
26 | originalPaths: new Map(),
27 | compressed: new Map(),
28 | comparison: new Map(),
29 | silent: false,
30 | fileModifier: null,
31 | fileContents: new Map(),
32 | };
33 | const message = await Config(context)();
34 |
35 | t.is(message, `Could not read the configuration in '${context.packagePath}'`);
36 | });
37 |
38 | tap.test('unparseable package.json should fail', async (t) => {
39 | const context: Context = {
40 | packagePath: 'test/config-validation/fixtures/unparseable-package-json/package.json',
41 | projectPath: 'test/config-validation/fixtures/unparseable-package-json',
42 | packageContent: '',
43 | originalPaths: new Map(),
44 | compressed: new Map(),
45 | comparison: new Map(),
46 | silent: false,
47 | fileModifier: null,
48 | fileContents: new Map(),
49 | };
50 | const message = await Config(context)();
51 |
52 | t.is(message, `Could not parse '${context.packagePath}'`);
53 | });
54 |
55 | tap.test("missing 'filesize' key from package.json should fail", async (t) => {
56 | const context: Context = {
57 | packagePath: 'test/config-validation/fixtures/missing-configuration/package.json',
58 | projectPath: 'test/config-validation/fixtures/missing-configuration',
59 | packageContent: '',
60 | originalPaths: new Map(),
61 | compressed: new Map(),
62 | comparison: new Map(),
63 | silent: false,
64 | fileModifier: null,
65 | fileContents: new Map(),
66 | };
67 | const message = await Config(context)();
68 |
69 | t.is(message, `There is no 'filesize' configuration in '${context.packagePath}'`);
70 | });
71 |
72 | tap.test("missing path from item in 'filesize' should fail", async (t) => {
73 | const context: Context = {
74 | packagePath: 'test/config-validation/fixtures/item-path-missing/package.json',
75 | projectPath: 'test/config-validation/fixtures/item-path-missing',
76 | packageContent: '',
77 | originalPaths: new Map(),
78 | compressed: new Map(),
79 | comparison: new Map(),
80 | silent: false,
81 | fileModifier: null,
82 | fileContents: new Map(),
83 | };
84 | const message = await Config(context)();
85 |
86 | t.is(message, `There is no data inside the 'filesize' configuration in '${context.packagePath}'`);
87 | });
88 |
89 | tap.test("missing maxSize from item in 'filesize' should fail", async (t) => {
90 | const context: Context = {
91 | packagePath: 'test/config-validation/fixtures/max-size-missing/package.json',
92 | projectPath: 'test/config-validation/fixtures/max-size-missing',
93 | packageContent: '',
94 | originalPaths: new Map(),
95 | compressed: new Map(),
96 | comparison: new Map(),
97 | silent: false,
98 | fileModifier: null,
99 | fileContents: new Map(),
100 | };
101 | const message = await Config(context)();
102 |
103 | t.is(message, "Configuration for 'index.js' is invalid. (size unspecified)");
104 | });
105 |
106 | tap.test("missing compression from item in 'filesize' should fail", async (t) => {
107 | const context: Context = {
108 | packagePath: 'test/config-validation/fixtures/compression-missing/package.json',
109 | projectPath: 'test/config-validation/fixtures/compression-missing',
110 | packageContent: '',
111 | originalPaths: new Map(),
112 | compressed: new Map(),
113 | comparison: new Map(),
114 | silent: false,
115 | fileModifier: null,
116 | fileContents: new Map(),
117 | };
118 | const message = await Config(context)();
119 |
120 | t.is(message, "Configuration for 'index.js' is invalid. (compression values unspecified)");
121 | });
122 |
123 | tap.test('standalone configuration file when valid should pass', async (t) => {
124 | const context: Context = {
125 | packagePath: 'test/config-validation/fixtures/standalone-config/filesize.json',
126 | projectPath: '',
127 | packageContent: '',
128 | originalPaths: new Map(),
129 | compressed: new Map(),
130 | comparison: new Map(),
131 | silent: false,
132 | fileModifier: null,
133 | fileContents: new Map(),
134 | };
135 | const message = await Config(context)();
136 |
137 | t.is(message, null);
138 | });
139 |
140 | tap.test('standalone configuration file when path is invalid should fail', async (t) => {
141 | const context: Context = {
142 | packagePath: 'test/config-validation/fixtures/standalone-config/invalid.json',
143 | projectPath: '',
144 | packageContent: '',
145 | originalPaths: new Map(),
146 | compressed: new Map(),
147 | comparison: new Map(),
148 | silent: false,
149 | fileModifier: null,
150 | fileContents: new Map(),
151 | };
152 | const message = await Config(context)();
153 |
154 | t.is(message, `Could not read the configuration in '${context.packagePath}'`);
155 | });
156 |
--------------------------------------------------------------------------------
/test/config-validation/fixtures/compression-missing/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/test/config-validation/fixtures/compression-missing/index.js
--------------------------------------------------------------------------------
/test/config-validation/fixtures/compression-missing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {}
4 | }
5 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/item-path-missing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {}
3 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/max-size-missing/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/test/config-validation/fixtures/max-size-missing/index.js
--------------------------------------------------------------------------------
/test/config-validation/fixtures/max-size-missing/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {
4 | "brotli": null
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/missing-configuration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nofilesizekey"
3 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/missing-package-json/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | */
--------------------------------------------------------------------------------
/test/config-validation/fixtures/standalone-config/filesize.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "track": ["test/config-validation/fixtures/standalone-config/**/*.js"]
4 | }
5 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/standalone-config/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | */
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track-standalone-format/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/test/config-validation/fixtures/track-standalone-format/index.js
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track-standalone-format/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "track": ["test/config-validation/fixtures/track-standalone-format/**/*.js"],
4 | "trackFormat": ["brotli"]
5 | }
6 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track-standalone/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/test/config-validation/fixtures/track-standalone/index.js
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track-standalone/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "track": ["test/config-validation/fixtures/track-standalone/**/*.js"]
4 | }
5 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ampproject/filesize/868a74e43ce058caac0a08595664d4db99e14c48/test/config-validation/fixtures/track/index.js
--------------------------------------------------------------------------------
/test/config-validation/fixtures/track/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "track": ["test/config-validation/fixtures/track/*"],
4 | "index.js": {
5 | "brotli": "5 kB",
6 | "gzip": "8 kB",
7 | "none": "20 kB"
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/test/config-validation/fixtures/unparseable-package-json/package.json:
--------------------------------------------------------------------------------
1 | {
2 | // Intentionally broken package.json to test error case.
--------------------------------------------------------------------------------
/test/config-validation/track.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 tap from 'tap';
18 | import { resolve } from 'path';
19 | import { report } from '../../src/api';
20 | import Config from '../../src/validation/Config';
21 | import { Context, SizeMapValue, SizeMap } from '../../src/validation/Condition';
22 |
23 | tap.test('including trackable items should succeed', async (t) => {
24 | const context: Context = {
25 | packagePath: 'test/config-validation/fixtures/track/package.json',
26 | projectPath: 'test/config-validation/fixtures/track',
27 | packageContent: '',
28 | originalPaths: new Map(),
29 | compressed: new Map(),
30 | comparison: new Map(),
31 | silent: false,
32 | fileModifier: null,
33 | fileContents: new Map(),
34 | };
35 | const message = await Config(context)();
36 |
37 | t.is(message, null);
38 | });
39 |
40 | tap.test('trackable items uses glob to find files', async (t) => {
41 | const sizes: SizeMapValue = [
42 | [null, undefined], // brotli
43 | [null, undefined], // gzip
44 | [null, undefined], // none
45 | ];
46 | const expected: SizeMap = new Map();
47 | expected.set(resolve('test/config-validation/fixtures/track-standalone/index.js'), sizes);
48 |
49 | const results = await report('test/config-validation/fixtures/track-standalone', null);
50 | t.deepEqual(results, expected);
51 | });
52 |
53 | tap.test('trackable items uses trackFormats to restrict compression types', async (t) => {
54 | const sizes: SizeMapValue = [
55 | [null, undefined], // brotli
56 | [undefined, undefined], // gzip
57 | [undefined, undefined], // none
58 | ];
59 | const expected: SizeMap = new Map();
60 | expected.set(resolve('test/config-validation/fixtures/track-standalone-format/index.js'), sizes);
61 |
62 | const results = await report('test/config-validation/fixtures/track-standalone-format', null);
63 | t.deepEqual(results, expected);
64 | });
65 |
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/api-report/filesize.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "track": ["test/end-to-end/fixtures/api-report/**/*.js"],
4 | "trackFormat": ["brotli"]
5 | }
6 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/api-report/inferno.js:
--------------------------------------------------------------------------------
1 | !function(e,n){"object"===typeof exports&&"undefined"!==typeof module?n(exports):"function"===typeof define&&define.amd?define(["exports"],n):n((e=e||self).Inferno=e.Inferno||{})}(this,(function(e){"use strict";var n=Array.isArray;function t(e){var n=typeof e;return"string"===n||"number"===n}function r(e){return void 0===e||null===e}function o(e){return null===e||!1===e||!0===e||void 0===e}function i(e){return"function"===typeof e}function l(e){return"string"===typeof e}function a(e){return null===e}function u(e,n){var t={};if(e)for(var r in e)t[r]=e[r];if(n)for(var o in n)t[o]=n[o];return t}function c(e){return!a(e)&&"object"===typeof e}var f={};function s(e){return e.substr(2).toLowerCase()}function d(e,n){e.appendChild(n)}function p(e,n,t){a(t)?d(e,n):e.insertBefore(n,t)}function v(e,n){e.removeChild(n)}function h(e){for(var n=0;n0,v=a(d),h=l(d)&&"$"===d[0];p||v||h?(i=i||r.slice(0,f),(p||h)&&(s=I(s)),(v||h)&&(s.key="$"+f),i.push(s)):i&&i.push(s),s.flags|=65536}}u=0===(i=i||r).length?1:8}else(i=r).flags|=65536,81920&r.flags&&(i=I(r)),u=2;return e.children=i,e.childFlags=u,e}function D(e){if(o(e)||t(e))return S(e,null);if(n(e))return U(e,0,null);return 16384&e.flags?I(e):e}var E="http://www.w3.org/1999/xlink",_="http://www.w3.org/XML/1998/namespace",A={"xlink:actuate":E,"xlink:arcrole":E,"xlink:href":E,"xlink:role":E,"xlink:show":E,"xlink:title":E,"xlink:type":E,"xml:base":_,"xml:lang":_,"xml:space":_};function T(e){return{onClick:e,onDblClick:e,onFocusIn:e,onFocusOut:e,onKeyDown:e,onKeyPress:e,onKeyUp:e,onMouseDown:e,onMouseMove:e,onMouseUp:e,onTouchEnd:e,onTouchMove:e,onTouchStart:e}}var R=T(0),W=T(null),O=T(!0);function j(e,n){var t=n.$EV;return t||(t=n.$EV=T(null)),t[e]||1===++R[e]&&(W[e]=function(e){var n="onClick"===e||"onDblClick"===e?function(e){return function(n){if(0!==n.button)return void n.stopPropagation();Q(n,!0,e,q(n))}}(e):function(e){return function(n){Q(n,!1,e,q(n))}}(e);return document.addEventListener(s(e),n),n}(e)),t}function H(e,n){var t=n.$EV;t&&t[e]&&(0===--R[e]&&(document.removeEventListener(s(e),W[e]),W[e]=null),t[e]=null)}function Q(e,n,t,r){var o=function(e){return i(e.composedPath)?e.composedPath()[0]:e.target}(e);do{if(n&&o.disabled)return;var l=o.$EV;if(l){var u=l[t];if(u&&(r.dom=o,u.event?u.event(u.data,e):u(e),e.cancelBubble))return}o=o.parentNode}while(!a(o))}function X(){this.cancelBubble=!0,this.immediatePropagationStopped||this.stopImmediatePropagation()}function G(){return this.defaultPrevented}function K(){return this.cancelBubble}function q(e){var n={dom:document};return e.isDefaultPrevented=G,e.isPropagationStopped=K,e.stopPropagation=X,Object.defineProperty(e,"currentTarget",{configurable:!0,get:function(){return n.dom}}),n}function z(e,n,t){if(e[n]){var r=e[n];r.event?r.event(r.data,t):r(t)}else{var o=n.toLowerCase();e[o]&&e[o](t)}}function J(e,n){var t=function(t){var r=this.$V;if(!r)return;var o=r.props||f,a=r.dom;if(l(e))z(o,e,t);else for(var u=0;u-1&&t.options[a]&&(u=t.options[a].value),o&&r(u)&&(u=e.defaultValue),function e(t,o){if("option"===t.type)!function(e,t){var o=e.props||f,i=e.dom;i.value=o.value,o.value===t||n(t)&&-1!==t.indexOf(o.value)?i.selected=!0:r(t)&&r(o.selected)||(i.selected=o.selected||!1)}(t,o);else{var i=t.children,l=t.flags;if(4&l)e(i.$LI,o);else if(8&l)e(i,o);else if(2===t.childFlags)e(i,o);else if(12&t.childFlags)for(var a=0,u=i.length;a0;for(var a in l&&(i=de(t))&&function(e,n,t){64&e?function(e,n){Z(n.type)?(Y(e,"change",ne),Y(e,"click",te)):Y(e,"input",ee)}(n,t):256&e?function(e){Y(e,"change",oe)}(n):128&e&&function(e,n){Y(e,"input",ue),n.onChange&&Y(e,"change",ce)}(n,t)}(n,r,t),t)ke(a,null,t[a],r,o,i,null);l&&se(n,e,r,t,!0,i)}function Ce(e,n,t){var r=D(e.render(n,e.state,t)),o=t;return i(e.getChildContext)&&(o=u(t,e.getChildContext())),e.$CX=o,r}function we(e,n,t,r,o,l){var u=new n(t,r),c=u.$N=Boolean(n.getDerivedStateFromProps||u.getSnapshotBeforeUpdate);if(u.$SVG=o,u.$L=l,e.children=u,u.$BS=!1,u.context=r,u.props===f&&(u.props=t),c)u.state=k(u,t,u.state);else if(i(u.componentWillMount)){u.$BR=!0,u.componentWillMount();var s=u.$PS;if(!a(s)){var d=u.state;if(a(d))u.state=s;else for(var p in s)d[p]=s[p];u.$PS=null}u.$BR=!1}return u.$LI=Ce(u,t,r),u}function Pe(e,n,t,r,o,i){var l=e.flags|=16384;481&l?Ne(e,n,t,r,o,i):4&l?function(e,n,t,r,o,i){var l=we(e,e.type,e.props||f,t,r,i);Pe(l.$LI,n,l.$CX,r,o,i),Ve(e.ref,l,i)}(e,n,t,r,o,i):8&l?(function(e,n,t,r,o,i){Pe(e.children=D(function(e,n){return 32768&e.flags?e.type.render(e.props||f,e.ref,n):e.type(e.props||f,n)}(e,t)),n,t,r,o,i)}(e,n,t,r,o,i),Se(e,i)):512&l||16&l?Fe(e,n,o):8192&l?function(e,n,t,r,o,i){var l=e.children,a=e.childFlags;12&a&&0===l.length&&(a=e.childFlags=2,l=e.children=L()),2===a?Pe(l,t,o,r,o,i):xe(l,t,n,r,o,i)}(e,t,n,r,o,i):1024&l&&function(e,n,t,r,o){Pe(e.children,e.ref,n,!1,null,o);var i=L();Fe(i,t,r),e.dom=i.dom}(e,t,n,o,i)}function Fe(e,n,t){var r=e.dom=document.createTextNode(e.children);a(n)||p(n,r,t)}function Ne(e,n,t,o,i,l){var u=e.flags,c=e.props,f=e.className,s=e.children,d=e.childFlags,v=e.dom=function(e,n){if(n)return document.createElementNS("http://www.w3.org/2000/svg",e);return document.createElement(e)}(e.type,o=o||(32&u)>0);if(r(f)||""===f||(o?v.setAttribute("class",f):v.className=f),16===d)w(v,s);else if(1!==d){var h=o&&"foreignObject"!==e.type;2===d?(16384&s.flags&&(e.children=s=I(s)),Pe(s,v,t,h,null,l)):8!==d&&4!==d||xe(s,v,t,h,null,l)}a(n)||p(n,v,i),a(c)||be(e,u,c,v,o),ve(e.ref,v,l)}function xe(e,n,t,r,o,i){for(var l=0;l0,c!==s){var v=c||f;if((a=s||f)!==f)for(var h in(d=(448&i)>0)&&(p=de(a)),a){var g=v[h],m=a[h];g!==m&&ke(h,g,m,u,o,p,e)}if(v!==f)for(var y in v)r(a[y])&&!r(v[y])&&ke(y,v[y],null,u,o,p,e)}var $=n.children,k=n.className;e.className!==k&&(r(k)?u.removeAttribute("class"):o?u.setAttribute("class",k):u.className=k),4096&i?function(e,n){e.textContent!==n&&(e.textContent=n)}(u,$):Ie(e.childFlags,n.childFlags,e.children,$,u,t,o&&"foreignObject"!==n.type,null,e,l),d&&se(i,n,u,a,!1,p);var b=n.ref,C=e.ref;C!==b&&(pe(C),ve(b,u,l))}(e,n,l,c,h,p):4&h?function(e,n,t,r,o,l,c){var s=n.children=e.children;if(a(s))return;s.$L=c;var d=n.props||f,p=n.ref,v=e.ref,h=s.state;if(!s.$N){if(i(s.componentWillReceiveProps)){if(s.$BR=!0,s.componentWillReceiveProps(d,r),s.$UN)return;s.$BR=!1}a(s.$PS)||(h=u(h,s.$PS),s.$PS=null)}Le(s,h,d,t,r,o,!1,l,c),v!==p&&(pe(v),ve(p,s,c))}(e,n,t,l,c,s,p):8&h?function(e,n,t,o,l,a,u){var c=!0,s=n.props||f,d=n.ref,p=e.props,v=!r(d),h=e.children;if(v&&i(d.onComponentShouldUpdate)&&(c=d.onComponentShouldUpdate(p,s)),!1!==c){v&&i(d.onComponentWillUpdate)&&d.onComponentWillUpdate(p,s);var g=n.type,m=D(32768&n.flags?g.render(s,d,o):g(s,o));Ue(h,m,t,o,l,a,u),n.children=m,v&&i(d.onComponentDidUpdate)&&d.onComponentDidUpdate(p,s)}else n.children=h}(e,n,t,l,c,s,p):16&h?function(e,n){var t=n.children,r=n.dom=e.dom;t!==e.children&&(r.nodeValue=t)}(e,n):512&h?n.dom=e.dom:8192&h?function(e,n,t,r,o,i){var l=e.children,a=n.children,u=e.childFlags,c=n.childFlags,f=null;12&c&&0===a.length&&(c=n.childFlags=2,a=n.children=L());var s=0!==(2&c);if(12&u){var d=l.length;(8&u&&8&c||s||!s&&a.length>d)&&(f=m(l[d-1],!1).nextSibling)}Ie(u,c,l,a,t,r,o,f,e,i)}(e,n,t,l,c,p):function(e,n,t,r){var i=e.ref,l=n.ref,a=n.children;if(Ie(e.childFlags,n.childFlags,e.children,a,i,t,!1,null,e,r),n.dom=e.dom,i!==l&&!o(a)){var u=a.dom;v(i,u),d(l,u)}}(e,n,l,p)}function Ie(e,n,t,r,o,i,l,a,u,c){switch(e){case 2:switch(n){case 2:Ue(t,r,o,i,l,a,c);break;case 1:he(t,o);break;case 16:ge(t),w(o,r);break;default:!function(e,n,t,r,o,i){ge(e),xe(n,t,r,o,m(e,!0),i),y(e,t)}(t,r,o,i,l,c)}break;case 1:switch(n){case 2:Pe(r,o,i,l,a,c);break;case 1:break;case 16:w(o,r);break;default:xe(r,o,i,l,a,c)}break;case 16:switch(n){case 16:!function(e,n,t){e!==n&&(""!==e?t.firstChild.nodeValue=n:w(t,n))}(t,r,o);break;case 2:ye(o),Pe(r,o,i,l,a,c);break;case 1:ye(o);break;default:ye(o),xe(r,o,i,l,a,c)}break;default:switch(n){case 16:me(t),w(o,r);break;case 2:$e(o,u,t),Pe(r,o,i,l,a,c);break;case 1:$e(o,u,t);break;default:var f=0|t.length,s=0|r.length;0===f?s>0&&xe(r,o,i,l,a,c):0===s?$e(o,u,t):8===n&&8===e?function(e,n,t,r,o,i,l,a,u,c){var f,s,d=i-1,p=l-1,v=0,h=e[v],g=n[v];e:{for(;h.key===g.key;){if(16384&g.flags&&(n[v]=g=I(g)),Ue(h,g,t,r,o,a,c),e[v]=g,++v>d||v>p)break e;h=e[v],g=n[v]}for(h=e[d],g=n[p];h.key===g.key;){if(16384&g.flags&&(n[p]=g=I(g)),Ue(h,g,t,r,o,a,c),e[d]=g,p--,v>--d||v>p)break e;h=e[d],g=n[p]}}if(v>d){if(v<=p)for(s=(f=p+1)p)for(;v<=d;)he(e[v++],t);else!function(e,n,t,r,o,i,l,a,u,c,f,s,d){var p,v,h,g=0,y=a,k=a,b=i-a+1,C=l-a+1,w=new Int32Array(C+1),P=b===r,F=!1,N=0,x=0;if(o<4||(b|C)<32)for(g=y;g<=i;++g)if(p=e[g],xa?F=!0:N=a,16384&v.flags&&(n[a]=v=I(v)),Ue(p,v,u,t,c,f,d),++x;break}!P&&a>l&&he(p,u)}else P||he(p,u);else{var V={};for(g=k;g<=l;++g)V[n[g].key]=g;for(g=y;g<=i;++g)if(p=e[g],xy;)he(e[y++],u);w[a-k]=g+1,N>a?F=!0:N=a,16384&(v=n[a]).flags&&(n[a]=v=I(v)),Ue(p,v,u,t,c,f,d),++x}else P||he(p,u);else P||he(p,u)}if(P)$e(u,s,e),xe(n,u,t,c,f,d);else if(F){var S=function(e){var n=0,t=0,r=0,o=0,i=0,l=0,a=0,u=e.length;for(u>Me&&(Me=u,le=new Int32Array(u),ae=new Int32Array(u));t>1]]0&&(ae[t]=le[i-1]),le[i]=t)}i=o+1;var c=new Int32Array(i);for(l=le[i-1];i-- >0;)c[i]=l,l=ae[l],le[i]=0;return c}(w);for(a=S.length-1,g=C-1;g>=0;g--)0===w[g]?(16384&(v=n[N=g+k]).flags&&(n[N]=v=I(v)),Pe(v,u,t,c,(h=N+1)=0;g--)0===w[g]&&(16384&(v=n[N=g+k]).flags&&(n[N]=v=I(v)),Pe(v,u,t,c,(h=N+1)l?l:i,d=0;dl)for(d=s;d3)for(u=[u],t=3;t2&&(l.children=c.slice.call(arguments,2)),h(n.type,l,l.key||n.key,l.ref||n.ref,null)},exports.createContext=function(n){var l={},u={__c:"__cC"+f++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var t,i=this;return this.getChildContext||(t=[],this.getChildContext=function(){return l[u.__c]=i,l},this.shouldComponentUpdate=function(n){i.props.value!==n.value&&t.some(function(l){l.context=n.value,w(l)})},this.sub=function(n){t.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){t.splice(t.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Consumer.contextType=u,u},exports.toChildArray=_,exports._e=j,exports.options=n;
2 | //# sourceMappingURL=preact.js.map
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/item-too-large/filesize.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "dist/index.js": {
4 | "brotli": "15 kB",
5 | "gzip": "20 kB",
6 | "none": "500 kB"
7 | },
8 | "dist/foo.js": {
9 | "brotli": "15 kB",
10 | "gzip": "20 kB",
11 | "none": "500 kB"
12 | },
13 | "dist/bar.js": {
14 | "brotli": "15 kB",
15 | "gzip": "20 kB",
16 | "none": "500 kB"
17 | },
18 | "dist/baz.js": {
19 | "brotli": "15 kB",
20 | "gzip": "20 kB",
21 | "none": "500 kB"
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/item-too-large/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "dist/index.js": {
4 | "brotli": "15 kB",
5 | "gzip": "20 kB",
6 | "none": "500 kB"
7 | },
8 | "dist/foo.js": {
9 | "brotli": "15 kB",
10 | "gzip": "20 kB",
11 | "none": "500 kB"
12 | },
13 | "dist/bar.js": {
14 | "brotli": "15 kB",
15 | "gzip": "20 kB",
16 | "none": "500 kB"
17 | },
18 | "dist/baz.js": {
19 | "brotli": "15 kB",
20 | "gzip": "20 kB",
21 | "none": "500 kB"
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/missing-item/filesize.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {
4 | "none": "500 kB"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/missing-item/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {
4 | "none": "500 kB"
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/successful/filesize.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {
4 | "brotli": "3.5 kB",
5 | "gzip": "4 kB",
6 | "none": "10 kB"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/successful/index.js:
--------------------------------------------------------------------------------
1 | !function(n,l){"object"==typeof exports&&"undefined"!=typeof module?l(exports):"function"==typeof define&&define.amd?define(["exports"],l):l(n.preact={})}(this,function(n){var l,u,t,i,o,f,r,e={},c=[],s=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord/i;function a(n,l){for(var u in l)n[u]=l[u];return n}function h(n){var l=n.parentNode;l&&l.removeChild(n)}function p(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t2&&(l.children=c.slice.call(arguments,2)),v(n.type,l,l.key||n.key,l.ref||n.ref)},n.createContext=function(n){var l={},u={__c:"__cC"+r++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var t,i=this;return this.getChildContext||(t=[],this.getChildContext=function(){return l[u.__c]=i,l},this.shouldComponentUpdate=function(l){n.value!==l.value&&t.some(function(n){n.context=l.value,g(n)})},this.sub=function(n){t.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){t.splice(t.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Consumer.contextType=u,u},n.toChildArray=b,n._e=A,n.options=l});
2 | //# sourceMappingURL=preact.umd.js.map
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/successful/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {
3 | "index.js": {
4 | "brotli": "3.5 kB",
5 | "gzip": "4 kB",
6 | "none": "10 kB"
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/test/end-to-end/fixtures/successful/replaced.js:
--------------------------------------------------------------------------------
1 | !function(n,l){"object"==typeof exports&&"undefined"!=typeof module?l(exports):"function"==typeof define&&define.amd?define(["exports"],l):l(n.preact={})}(this,function(n){var l,u,t,i,o,f,r,e={},c=[],s=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord/i;function a(n,l){for(var u in l)n[u]=l[u];return n}function h(n){var l=n.parentNode;l&&l.removeChild(n)}function p(n,l,u){var t,i=arguments,o={};for(t in l)"key"!==t&&"ref"!==t&&(o[t]=l[t]);if(arguments.length>3)for(u=[u],t=3;t2&&(l.children=c.slice.call(arguments,2)),v(n.type,l,l.key||n.key,l.ref||n.ref)},n.createContext=function(n){var l={},u={__c:"__cC"+r++,__:n,Consumer:function(n,l){return n.children(l)},Provider:function(n){var t,i=this;return this.getChildContext||(t=[],this.getChildContext=function(){return l[u.__c]=i,l},this.shouldComponentUpdate=function(l){n.value!==l.value&&t.some(function(n){n.context=l.value,g(n)})},this.sub=function(n){t.push(n);var l=n.componentWillUnmount;n.componentWillUnmount=function(){t.splice(t.indexOf(n),1),l&&l.call(n)}}),n.children}};return u.Consumer.contextType=u,u},n.toChildArray=b,n._e=A,n.options=l});
2 | //# sourceMappingURL=FOO.map
--------------------------------------------------------------------------------
/test/end-to-end/large-config.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 tap from 'tap';
18 | import { exec } from 'child_process';
19 |
20 | tap.test('too large valid item fails with exit code 6, using configuration file', (t) => {
21 | const executeFailure = exec('./dist/filesize -c=test/end-to-end/fixtures/item-too-large/filesize.json');
22 |
23 | executeFailure.on('exit', (code) => {
24 | t.is(code, 6);
25 | t.end();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/end-to-end/large-project.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 tap from 'tap';
18 | import { exec } from 'child_process';
19 |
20 | tap.test('too large valid item fails with exit code 6', (t) => {
21 | const executeFailure = exec('./dist/filesize -p=test/end-to-end/fixtures/item-too-large');
22 |
23 | executeFailure.on('exit', (code) => {
24 | t.is(code, 6);
25 | t.end();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/test/end-to-end/successful-config.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 tap from 'tap';
18 | import { exec } from 'child_process';
19 | import { report, Report } from '../../src/api';
20 | import { SizeMapValue, SizeMap } from '../../src/validation/Condition';
21 | import { resolve, relative } from 'path';
22 |
23 | tap.test('standalone configuration file when valid should pass', (t) => {
24 | const executeSuccess = exec('./dist/filesize -c=test/end-to-end/fixtures/successful/filesize.json');
25 |
26 | executeSuccess.on('exit', (code) => {
27 | t.is(code, 0);
28 | t.end();
29 | });
30 | });
31 |
32 | tap.test('item under requested filesize limit passes from API, using configuration file', async (t) => {
33 | const sizes: SizeMapValue = [
34 | [3410, 3584], // brotli
35 | [3737, 4096], // gzip
36 | [9327, 10240], // none
37 | ];
38 | const expected: SizeMap = new Map();
39 | expected.set(resolve('test/end-to-end/fixtures/successful/index.js'), sizes);
40 |
41 | const results = await report('test/end-to-end/fixtures/successful/filesize.json', null);
42 | t.deepEqual(results, expected);
43 | });
44 |
45 | tap.test(
46 | 'item under requested filesize limit passes from API, using configuration file, with replacement',
47 | async (t) => {
48 | const sizes: SizeMapValue = [
49 | [3401, 3584], // brotli
50 | [3731, 4096], // gzip
51 | [9317, 10240], // none
52 | ];
53 | const expected: SizeMap = new Map();
54 | expected.set(resolve('test/end-to-end/fixtures/successful/index.js'), sizes);
55 |
56 | const results = await report('test/end-to-end/fixtures/successful/filesize.json', (content) =>
57 | content.replace(new RegExp('preact.umd.js.map', 'g'), 'FOO.map'),
58 | );
59 | t.deepEqual(results, expected);
60 | },
61 | );
62 |
63 | tap.test('api is interactive with custom reporter, using configuration file', async (t) => {
64 | const mapping = new Map([
65 | ['preact.js', 3477],
66 | ['inferno.js', 7297],
67 | ['react-dom.js', 28721],
68 | ]);
69 |
70 | await report(
71 | 'test/end-to-end/fixtures/api-report/filesize.json',
72 | (content) => content,
73 | class extends Report {
74 | update(context: any) {
75 | const completed = super.getUpdated(context);
76 | for (const complete of completed) {
77 | const [filePath, sizeMap] = complete;
78 | const relativePath = relative('test/end-to-end/fixtures/api-report', filePath);
79 | t.is(mapping.has(relativePath), true);
80 | t.is(mapping.get(relativePath), sizeMap[0][0]);
81 | }
82 | }
83 | },
84 | );
85 | });
86 |
--------------------------------------------------------------------------------
/test/end-to-end/successful-project.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 tap from 'tap';
18 | import { exec } from 'child_process';
19 | import { report, Report } from '../../src/api';
20 | import { SizeMapValue, SizeMap } from '../../src/validation/Condition';
21 | import { resolve, relative } from 'path';
22 |
23 | tap.test('item under requested filesize limit passes', (t) => {
24 | const executeSuccess = exec('./dist/filesize -p=test/end-to-end/fixtures/successful');
25 |
26 | executeSuccess.on('exit', (code) => {
27 | t.is(code, 0);
28 | t.end();
29 | });
30 | });
31 |
32 | tap.test('item under requested filesize limit passes from API', async (t) => {
33 | const sizes: SizeMapValue = [
34 | [3410, 3584], // brotli
35 | [3737, 4096], // gzip
36 | [9327, 10240], // none
37 | ];
38 | const expected: SizeMap = new Map();
39 | expected.set(resolve('test/end-to-end/fixtures/successful/index.js'), sizes);
40 |
41 | const results = await report('test/end-to-end/fixtures/successful', null);
42 | t.deepEqual(results, expected);
43 | });
44 |
45 | tap.test('item under requested filesize limit passes from API, with replacement', async (t) => {
46 | const sizes: SizeMapValue = [
47 | [3401, 3584], // brotli
48 | [3731, 4096], // gzip
49 | [9317, 10240], // none
50 | ];
51 | const expected: SizeMap = new Map();
52 | expected.set(resolve('test/end-to-end/fixtures/successful/index.js'), sizes);
53 |
54 | const results = await report('test/end-to-end/fixtures/successful', (content) =>
55 | content.replace(new RegExp('preact.umd.js.map', 'g'), 'FOO.map'),
56 | );
57 | t.deepEqual(results, expected);
58 | });
59 |
60 | tap.test('api is interactive with custom reporter', async (t) => {
61 | const mapping = new Map([
62 | ['preact.js', 3477],
63 | ['inferno.js', 7297],
64 | ['react-dom.js', 28721],
65 | ]);
66 |
67 | await report(
68 | 'test/end-to-end/fixtures/api-report',
69 | (content) => content,
70 | class extends Report {
71 | update(context: any) {
72 | const completed = super.getUpdated(context);
73 | for (const complete of completed) {
74 | const [filePath, sizeMap] = complete;
75 | const relativePath = relative('test/end-to-end/fixtures/api-report', filePath);
76 | t.is(mapping.has(relativePath), true);
77 | t.is(mapping.get(relativePath), sizeMap[0][0]);
78 | }
79 | }
80 | },
81 | );
82 | });
83 |
--------------------------------------------------------------------------------
/test/end-to-end/throw-error.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 tap from 'tap';
18 | import { report } from '../../src/api';
19 | import { exec } from 'child_process';
20 |
21 | tap.test('item with missing file fails with exit code 5', (t) => {
22 | const executeFailure = exec('./dist/filesize -p=test/end-to-end/fixtures/missing-item');
23 |
24 | executeFailure.on('exit', (code) => {
25 | t.is(code, 5);
26 | t.end();
27 | });
28 | });
29 |
30 | tap.test('item with missing file fails with exit code 5, using configuration file', (t) => {
31 | const executeFailure = exec('./dist/filesize -c=test/end-to-end/fixtures/missing-item/filesize.json');
32 |
33 | executeFailure.on('exit', (code) => {
34 | t.is(code, 5);
35 | t.end();
36 | });
37 | });
38 |
39 | tap.test('item with missing file throws exception from API', async (t) => {
40 | try {
41 | await report('test/end-to-end/fixtures/missing-item', null);
42 | } catch (e) {
43 | t.is(e, `Configuration for 'index.js' is invalid. (path is not a valid file)`);
44 | }
45 | });
46 |
47 | tap.test('item with missing file throws exception from API, using configuration file', async (t) => {
48 | try {
49 | await report('test/end-to-end/fixtures/missing-item/filesize.json', null);
50 | } catch (e) {
51 | t.is(e, `Configuration for 'index.js' is invalid. (path is not a valid file)`);
52 | }
53 | });
54 |
--------------------------------------------------------------------------------
/test/project-validation/fixtures/contains-package-json/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | */
--------------------------------------------------------------------------------
/test/project-validation/fixtures/contains-package-json/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "filesize": {}
3 | }
--------------------------------------------------------------------------------
/test/project-validation/fixtures/missing-package-json/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 The AMP HTML Authors. 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 | */
--------------------------------------------------------------------------------
/test/project-validation/project-validation.test.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2019 The AMP HTML Authors. 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 tap from 'tap';
18 | import Project from '../../src/validation/Project';
19 | import { resolve } from 'path';
20 | import { Context } from '../../src/validation/Condition';
21 |
22 | tap.test('valid directory should pass', async (t) => {
23 | const context: Context = {
24 | packagePath: '',
25 | projectPath: 'test/project-validation/fixtures/contains-package-json',
26 | packageContent: '',
27 | originalPaths: new Map(),
28 | compressed: new Map(),
29 | comparison: new Map(),
30 | silent: false,
31 | fileModifier: null,
32 | fileContents: new Map(),
33 | };
34 | const message = await Project(context)();
35 |
36 | t.is(message, null);
37 | });
38 |
39 | tap.test('invalid directory should fail', async (t) => {
40 | const context = {
41 | packagePath: '',
42 | projectPath: 'test/project-validation/fixtures-invalid',
43 | packageContent: '',
44 | originalPaths: new Map(),
45 | compressed: new Map(),
46 | comparison: new Map(),
47 | silent: false,
48 | fileModifier: null,
49 | fileContents: new Map(),
50 | };
51 | const message = await Project(context)();
52 |
53 | t.is(message, `error project specified '${context.projectPath}' doesn't exist, is this a valid project?`);
54 | });
55 |
56 | tap.test('directory missing package.json should fail', async (t) => {
57 | const context = {
58 | packagePath: '',
59 | projectPath: 'test/project-validation/fixtures/missing-package-json',
60 | packageContent: '',
61 | originalPaths: new Map(),
62 | compressed: new Map(),
63 | comparison: new Map(),
64 | silent: false,
65 | fileModifier: null,
66 | fileContents: new Map(),
67 | };
68 | const message = await Project(context)();
69 |
70 | t.is(message, `error Missing '${resolve(context.projectPath, 'package.json')}', is this a valid project?`);
71 | });
72 |
--------------------------------------------------------------------------------
/test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": true,
3 | "compilerOptions": {
4 | "lib": [
5 | "esnext"
6 | ],
7 | "outDir": "output",
8 | "sourceMap": false,
9 | "moduleResolution": "node",
10 | "target": "ES2019",
11 | "module": "esnext",
12 | "allowJs": false,
13 | "noUnusedLocals": true,
14 | "strictNullChecks": true,
15 | "noImplicitAny": true,
16 | "noImplicitThis": true,
17 | "alwaysStrict": true,
18 | "incremental": true,
19 | "allowSyntheticDefaultImports": true,
20 | "esModuleInterop": true
21 | },
22 | "include": [
23 | "*.ts",
24 | "**/*.ts"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------