├── .codeclimate.yml ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── coverage.yml │ └── publish.yml ├── .gitignore ├── .huskyrc ├── .lintstagedrc ├── .mocharc.json ├── .npmrc ├── .nycrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── api-extractor.json ├── docs ├── api.md ├── cli.md ├── examples │ ├── check-es5-js.md │ ├── check-js-ts.md │ ├── cli.md │ ├── create-es5-js.md │ ├── create-js-ts.md │ ├── createDirHash-es5-js.md │ ├── createDirHash-js-ts.md │ ├── createFileHash-es5-js.md │ ├── createFileHash-js-ts.md │ ├── createFilesHash-es5-js.md │ ├── createFilesHash-js-ts.md │ ├── getExclusionsFromIgnoreFile-es5-js.md │ ├── getExclusionsFromIgnoreFile-js-ts.md │ ├── getIntegrityOptionsFromConfig-es5-js.md │ ├── getIntegrityOptionsFromConfig-js-ts.md │ ├── getManifestIntegrity-es5-js.md │ ├── getManifestIntegrity-js-ts.md │ ├── persist-es5-js.md │ ├── persist-js-ts.md │ ├── updateManifestIntegrity-es5-js.md │ └── updateManifestIntegrity-js-ts.md └── importation.md ├── media ├── integrity_file.png ├── integrity_file.svg ├── nsri-logo.png └── nsri-logo.svg ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── abstractions │ └── baseLogger.ts ├── app │ ├── integrity.ts │ └── schemas │ │ └── v1 │ │ └── schema.json ├── cli.ts ├── cli │ └── index.ts ├── common │ ├── configExplorer.ts │ ├── constants.ts │ ├── enums.ts │ ├── fsAsync.ts │ ├── logger.ts │ ├── utils.ts │ └── yargsParser.ts ├── index.ts └── interfaces │ ├── arguments.ts │ ├── configOptions.ts │ ├── cryptoOptions.ts │ ├── hashObject.ts │ ├── integrityObject.ts │ ├── integrityOptions.ts │ ├── manifestInfo.ts │ ├── normalizedCryptoOptions.ts │ ├── normalizedIntegrityOptions.ts │ ├── parsedArgs.ts │ ├── spinner.ts │ └── verboseHashObject.ts ├── test ├── api │ ├── behavior.test.ts │ ├── check.test.ts │ ├── create.test.ts │ ├── createDirHash.test.ts │ ├── createFileHash.test.ts │ ├── createFilesHash.test.ts │ ├── getExclusionsFromIgnoreFile.test.ts │ ├── getIntegrityOptionsFromConfig.test.ts │ ├── getManifestIntegrity.test.ts │ ├── persist.test.ts │ └── updateManifestIntegrity.test.ts ├── cli │ └── index.test.ts ├── common │ ├── configExplorer.test.ts │ ├── logger.test.ts │ ├── utils.test.ts │ └── yargsParser.test.ts ├── cosmiconfig │ ├── configjs │ │ └── nsri.config.js │ ├── js │ │ └── .nsrirc.js │ ├── json │ │ └── .nsrirc.json │ ├── packagejson │ │ └── package.json │ ├── rc │ │ └── .nsrirc │ ├── yaml │ │ └── .nsrirc.yaml │ └── yml │ │ └── .nsrirc.yml ├── fixtures │ ├── .integrity.json │ ├── directory.1 │ │ ├── .integrity.json │ │ ├── anotherFileToHash.txt │ │ └── otherFileToHash.txt │ ├── directory │ │ ├── .integrity.json │ │ ├── anotherFileToHash.txt │ │ └── otherFileToHash.txt │ ├── fileToHash.txt │ ├── fixtures │ │ ├── .integrity.json │ │ ├── directory.1 │ │ │ ├── .integrity.json │ │ │ ├── anotherFileToHash.txt │ │ │ └── otherFileToHash.txt │ │ ├── directory │ │ │ ├── .integrity.json │ │ │ └── anotherFileToHash.txt │ │ └── fileToHash.txt │ └── sameContentWithFileToHash.txt ├── helper.test.ts ├── helper.ts ├── ignoreFile │ └── .nsriignore └── typings.d.ts ├── tsconfig.dev.json ├── tsconfig.json └── tsconfig.strict.dev.json /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | checks: 3 | file-lines: 4 | enabled: false 5 | method-complexity: 6 | enabled: false 7 | method-lines: 8 | enabled: false 9 | similar-code: 10 | enabled: false 11 | plugins: 12 | markdownlint: 13 | enabled: true 14 | checks: 15 | MD003: 16 | enabled: false 17 | MD013: 18 | enabled: false 19 | MD022: 20 | enabled: false 21 | MD033: 22 | enabled: false 23 | MD036: 24 | enabled: false 25 | nodesecurity: 26 | enabled: true 27 | tslint: 28 | enabled: false 29 | exclude_patterns: 30 | - "!src/**/*.ts" 31 | - "!test/**/*.ts" 32 | - "src/cli.ts" 33 | - "test/" 34 | - ".github/" 35 | - ".vscode/" 36 | - "*.json" 37 | - ".*.yml" 38 | - "*.lock" 39 | - ".editorconfig" 40 | - ".git*" 41 | - "**/*.d.ts" 42 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [.integrity.json] 12 | insert_final_newline = false 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/eslint-recommended", 9 | "plugin:@typescript-eslint/recommended", 10 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 11 | "plugin:import/errors", 12 | "plugin:import/warnings", 13 | "plugin:import/typescript" 14 | ], 15 | "ignorePatterns": [ 16 | "node_modules", 17 | "lib", 18 | "out", 19 | "coverage", 20 | "/.*", 21 | "/*.*", 22 | ".*", 23 | "*.d.ts" 24 | ], 25 | "parser": "@typescript-eslint/parser", 26 | "parserOptions": { 27 | "project": "tsconfig.dev.json", 28 | "sourceType": "module" 29 | }, 30 | "plugins": [ 31 | "@typescript-eslint", 32 | "import" 33 | ], 34 | "rules": { 35 | "@typescript-eslint/array-type": [ 36 | "error", 37 | { 38 | "default": "array-simple" 39 | } 40 | ], 41 | "@typescript-eslint/ban-ts-ignore": "off", 42 | "@typescript-eslint/ban-ts-comment": [ 43 | "error", 44 | { 45 | "ts-ignore": false 46 | } 47 | ], 48 | "@typescript-eslint/consistent-type-definitions": "error", 49 | "@typescript-eslint/explicit-member-accessibility": [ 50 | "error", 51 | { 52 | "accessibility": "explicit" 53 | } 54 | ], 55 | "@typescript-eslint/indent": [ 56 | "error", 57 | 2 58 | ], 59 | "@typescript-eslint/member-ordering": "error", 60 | "@typescript-eslint/naming-convention": "error", 61 | "@typescript-eslint/no-empty-function": "error", 62 | "@typescript-eslint/no-use-before-define": "error", 63 | "@typescript-eslint/parameter-properties": "error", 64 | "@typescript-eslint/prefer-for-of": "error", 65 | "@typescript-eslint/prefer-function-type": "error", 66 | "@typescript-eslint/quotes": [ 67 | "error", 68 | "single", 69 | { 70 | "allowTemplateLiterals": true 71 | } 72 | ], 73 | "@typescript-eslint/semi": "error", 74 | "@typescript-eslint/unified-signatures": "error", 75 | "import/no-unresolved": "error", 76 | "import/no-extraneous-dependencies": [ 77 | "error", 78 | { 79 | "devDependencies": [ 80 | "**/test/**" 81 | ] 82 | } 83 | ], 84 | "import/no-internal-modules": [ 85 | "error", 86 | { 87 | "allow": [ 88 | "**/src/**" 89 | ] 90 | } 91 | ], 92 | "arrow-body-style": "error", 93 | "arrow-parens": [ 94 | "error", 95 | "as-needed" 96 | ], 97 | "comma-dangle": [ 98 | "error", 99 | "always-multiline" 100 | ], 101 | "complexity": "off", 102 | "curly": "error", 103 | "dot-notation": "error", 104 | "eol-last": "error", 105 | "eqeqeq": [ 106 | "error", 107 | "smart" 108 | ], 109 | "guard-for-in": "error", 110 | "id-blacklist": [ 111 | "error", 112 | "any", 113 | "number", 114 | "string", 115 | "boolean" 116 | ], 117 | "id-match": "error", 118 | "max-classes-per-file": [ 119 | "error", 120 | 1 121 | ], 122 | "max-len": [ 123 | "error", 124 | { 125 | "code": 120 126 | } 127 | ], 128 | "new-parens": "error", 129 | "no-bitwise": "error", 130 | "no-caller": "error", 131 | "no-console": "error", 132 | "no-duplicate-imports": "error", 133 | "no-eval": "error", 134 | "no-extra-bind": "error", 135 | "no-invalid-this": "error", 136 | "no-multiple-empty-lines": "error", 137 | "no-new-func": "error", 138 | "no-new-wrappers": "error", 139 | "no-return-await": "error", 140 | "no-sequences": "error", 141 | "no-shadow": [ 142 | "error", 143 | { 144 | "hoist": "all", 145 | "allow": ["CryptoEncoding"] 146 | } 147 | ], 148 | "no-template-curly-in-string": "error", 149 | "no-throw-literal": "error", 150 | "no-trailing-spaces": "error", 151 | "no-undef-init": "error", 152 | "no-underscore-dangle": "error", 153 | "no-unused-expressions": "error", 154 | "no-nonoctal-decimal-escape": "error", 155 | "no-unsafe-optional-chaining": "error", 156 | "object-shorthand": "error", 157 | "one-var": [ 158 | "error", 159 | "never" 160 | ], 161 | "prefer-arrow-callback": "error", 162 | "prefer-object-spread": "error", 163 | "quote-props": [ 164 | "error", 165 | "as-needed" 166 | ], 167 | "radix": "error", 168 | "space-before-function-paren": [ 169 | "error", 170 | { 171 | "anonymous": "always", 172 | "named": "never", 173 | "asyncArrow": "always" 174 | } 175 | ], 176 | "space-in-parens": [ 177 | "error", 178 | "never" 179 | ], 180 | "spaced-comment": [ 181 | "error", 182 | "always", 183 | { 184 | "markers": [ 185 | "#region", 186 | "#endregion" 187 | ] 188 | } 189 | ] 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text eol=lf 5 | 6 | ############################################################################### 7 | # Denote all files that are truly binary and should not be modified. 8 | ############################################################################### 9 | *.psd binary 10 | *.ai binary 11 | *.png binary 12 | *.gif binary 13 | *.jpg binary -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jimikar@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | **Issue:** 4 | 5 | 1. Do a [search](https://github.com/JimiC/nsri/issues?q=is%3Aopen+is%3Aissue) before opening a new issue. If it exists, add a comment to it instead of opening a new one. 6 | 1. Fill in the provided issue template. 7 | 1. Include screenshots or GIFs whenever possible. 8 | 1. Don't be rude, describe your problem cool. 9 | 10 | **Pull Request:** 11 | 12 | 1. Fill in the provided pull request template. 13 | 1. Reference issues whenever possible. 14 | 15 | **Code Contribution:** 16 | 17 | 1. Keep your code clean. 18 | 1. Follow the existing coding style. 19 | 1. Obey the TSLint Rules. 20 | 1. Add test cases for every new functionality. 21 | 1. Follow commit message [guidelines](https://gist.github.com/robertpainsi/b632364184e70900af4ab688decf6f53). 22 | 1. Make sure that the project builts before submitting a PR. 23 | 24 | Thanks for helping out! :smile: 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | **Non-duplicate confirmation** 5 | 6 | - [ ] I'm sure this issue is not a _duplicate_. 7 | 8 | **Environment** 9 | 10 | - OS: 11 | - Version: 12 | - Package version: 13 | 14 | **Expected behavior** 15 | 16 | - A clear and concise description of what you expected to happen 17 | 18 | **Actual behavior** 19 | 20 | - A clear and concise description of what you actually happen 21 | 22 | **Screenshots** 23 | 24 | - If applicable, add screenshots to help explain your issue 25 | 26 | **Issue reproduction steps** 27 | 28 | - Do '...' 29 | - ... 30 | - See error 31 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | _**Fixes #IssueNumber**_ 4 | 5 | **Proposed changes:** 6 | 7 | - [ ] Add 8 | - [ ] Delete 9 | - [ ] Fix 10 | - [ ] Prepare 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "detect-indent" 9 | - dependency-name: "@types/node" 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | tags-ignore: 6 | - '*' 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | concurrency: 14 | group: ${{ github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | test: 19 | name: Node.js ${{ matrix.node-version }} | ${{ matrix.os }} | ${{ matrix.arch }} 20 | 21 | timeout-minutes: 5 22 | 23 | runs-on: ${{ matrix.os }} 24 | 25 | strategy: 26 | max-parallel: 3 27 | fail-fast: false 28 | matrix: 29 | # os: 30 | # - ubuntu-latest 31 | # - macOS-latest 32 | # - windows-latest 33 | # arch: 34 | # - x64 35 | # node-version: 36 | # - 18 37 | # experimental: 38 | # - false 39 | # send_coverage: 40 | # - false 41 | include: 42 | - os: ubuntu-latest 43 | arch: x64 44 | node-version: 20 # latest 45 | experimental: true 46 | node_minimum: false 47 | - os: macOS-latest 48 | arch: x64 49 | node-version: 20 # latest 50 | experimental: true 51 | send_coverage: false 52 | - os: windows-latest 53 | arch: x64 54 | node-version: 20 # latest 55 | experimental: true 56 | send_coverage: false 57 | 58 | - os: ubuntu-latest 59 | node-version: 18 # minimum 60 | arch: x64 61 | experimental: false 62 | send_coverage: true 63 | - os: macOS-latest 64 | arch: x64 65 | node-version: 18 # minimum 66 | experimental: false 67 | send_coverage: false 68 | - os: windows-latest 69 | arch: x64 70 | node-version: 18 # minimum 71 | experimental: false 72 | send_coverage: false 73 | 74 | steps: 75 | - name: Setting up Node.js ${{ matrix.node-version }} 76 | uses: actions/setup-node@v3 77 | with: 78 | node-version: ${{ matrix.node-version }} 79 | 80 | - name: Build info 81 | run: | 82 | node --version 83 | npm --version 84 | 85 | - name: Cloning repository 86 | uses: actions/checkout@v3 87 | with: 88 | fetch-depth: 5 89 | 90 | - name: Installing dependencies 91 | run: npm ci 92 | 93 | - name: Running tests 94 | continue-on-error: ${{ matrix.experimental }} 95 | run: npm test 96 | 97 | - name: Uploading code coverage report 98 | if: > 99 | ( 100 | success() && 101 | matrix.send_coverage && 102 | github.event_name == 'push' && 103 | github.repository_owner == 'JimiC' && 104 | github.actor != 'dependabot[bot]' 105 | ) 106 | uses: paambaati/codeclimate-action@v5 107 | env: 108 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 109 | 110 | - name: Uploading code coverage artifact 111 | if: > 112 | ( 113 | success() && 114 | matrix.send_coverage && 115 | github.event_name == 'pull_request' && 116 | github.actor != 'dependabot[bot]' 117 | ) 118 | uses: actions/upload-artifact@v3 119 | with: 120 | name: code-coverage-report 121 | path: coverage/lcov.info 122 | retention-days: 1 123 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: code coverage 2 | 3 | on: 4 | workflow_run: 5 | workflows: 6 | - build 7 | types: 8 | - completed 9 | 10 | jobs: 11 | code-climate: 12 | name: Send code coverage report to Code Climate 13 | runs-on: ubuntu-latest 14 | if: > 15 | ( 16 | github.event.workflow_run.conclusion == 'success' && 17 | github.event.workflow_run.event == 'pull_request' && 18 | github.repository_owner == 'JimiC' && 19 | github.actor != 'dependabot[bot]' 20 | ) 21 | steps: 22 | - name: Cloning repository 23 | uses: actions/checkout@v3 24 | with: 25 | ref: ${{ github.event.workflow_run.head_sha }} 26 | 27 | - name: Download artifact 28 | uses: dawidd6/action-download-artifact@v2 29 | with: 30 | run_id: ${{ github.event.workflow_run.id }} 31 | name: code-coverage-report 32 | 33 | - name: Uploading code coverage report 34 | uses: paambaati/codeclimate-action@v5 35 | with: 36 | coverageLocations: lcov.info:lcov 37 | env: 38 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | 12 | if: github.repository_owner == 'JimiC' 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Cloning repository 18 | uses: actions/checkout@v3 19 | with: 20 | fetch-depth: 5 21 | 22 | - name: "Setting up Node.js: 18" 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18 26 | 27 | - name: Build info 28 | run: | 29 | node --version 30 | npm --version 31 | 32 | - name: Installing dependencies 33 | run: npm ci 34 | 35 | - name: Publishing to NPM 36 | uses: JS-DevTools/npm-publish@v1 37 | with: 38 | token: ${{ secrets.NPM_AUTH_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | .DS_Store 3 | node_modules 4 | coverage 5 | out 6 | lib 7 | dist 8 | npm-debug.* 9 | *.lcov 10 | /.integrity.json 11 | -------------------------------------------------------------------------------- /.huskyrc: -------------------------------------------------------------------------------- 1 | { 2 | "skipCI": true, 3 | "hooks": { 4 | "pre-commit": "lint-staged" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": [ 3 | "eslint --ext .ts ." 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec": "./out/test", 3 | "ui": "bdd", 4 | "exit": true, 5 | "colors": true, 6 | "recursive": true, 7 | "retries": 2, 8 | "timeout": 15000 9 | } 10 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | fund=false 3 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["!test/**"], 3 | "exclude-after-remap": false 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "EditorConfig.EditorConfig", 5 | "DavidAnson.vscode-markdownlint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/out/src/cli.js", 12 | "args": [ 13 | "create", 14 | "-s", 15 | ".", 16 | "-x", 17 | "*", 18 | "*/", 19 | "!test/fixtures" 20 | ], 21 | "preLaunchTask": "npm: build - dev", 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ 25 | "${workspaceFolder}/out/**/*.js" 26 | ] 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Mocha Tests", 32 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 33 | "args": [ 34 | "${workspaceFolder}/out/test" 35 | ], 36 | "internalConsoleOptions": "openOnSessionStart", 37 | "preLaunchTask": "npm: build - dev", 38 | "stopOnEntry": false, 39 | "sourceMaps": true, 40 | "outFiles": [ 41 | "${workspaceFolder}/out/**/*.js" 42 | ] 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "files.associations": { 6 | ".huskyrc": "json", 7 | ".lintstagedrc": "json", 8 | ".nycrc": "json", 9 | "api-extractor.json": "jsonc", 10 | }, 11 | "typescript.tsdk": "node_modules/typescript/lib" 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // A task runner that calls a custom npm script that compiles the extension. 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | { 5 | "version": "2.0.0", 6 | "tasks": [ 7 | { 8 | "label": "npm: build - dev", 9 | // the command is a shell script 10 | "type": "shell", 11 | // we want to run npm 12 | "command": "npm", 13 | // show the output window only if unrecognized errors occur. 14 | "presentation": { 15 | "reveal": "silent" 16 | }, 17 | // we run the custom script "build" as defined in package.json 18 | "args": [ 19 | "run", 20 | "build:dev", 21 | "--loglevel", 22 | "silent" 23 | ], 24 | // The tsc compiler is running in the background 25 | "isBackground": true, 26 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 27 | "problemMatcher": "$tsc-watch" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Jimi (Dimitris) Charalampidis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # nsri (NodeJS Subresource Integrity) 3 | 4 | ### General Info 5 | 6 | [![License](https://img.shields.io/github/license/jimic/nsri)](https://raw.githubusercontent.com/jimic/nsri/main/LICENSE) 7 | ![Semantic Version](https://img.shields.io/badge/semver-2.0.0-green) 8 | ![npm type definitions](https://img.shields.io/npm/types/nsri) 9 | 10 | ### Release Info 11 | 12 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/jimic/nsri) 13 | ![node-current](https://img.shields.io/node/v/nsri?label=supported%20node%20versions) 14 | ![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/npm/nsri) 15 | 16 | ### Development Info 17 | 18 | ![GitHub package.json version](https://img.shields.io/github/package-json/v/jimic/nsri) 19 | ![node-dev](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=supported%20node%20versions&query=engines.node&url=https%3A%2F%2Fraw.githubusercontent.com%2FJimiC%2Fnsri%2Fmaster%2Fpackage.json) 20 | 21 | ![Build Status](https://github.com/JimiC/nsri/actions/workflows/build.yml/badge.svg) 22 | 23 | [![Maintainability](https://api.codeclimate.com/v1/badges/77bea27f9bd1906ac525/maintainability)](https://codeclimate.com/github/jimic/nsri/maintainability) 24 | [![Test Coverage](https://api.codeclimate.com/v1/badges/77bea27f9bd1906ac525/test_coverage)](https://codeclimate.com/github/jimic/nsri/test_coverage) 25 | 26 | [![Known Vulnerabilities](https://snyk.io/test/github/jimic/nsri/badge.svg?targetFile=package.json)](https://snyk.io/test/github/jimic/nsri?targetFile=package.json) 27 | 28 | --- 29 | 30 | A [Node.js](https://nodejs.org) utility tool that creates an integrity object containing the hash checksums of a file or a directory structure, that can be saved to an `.integrity.json` file [], or put inside the project's manifest file (`project.json`). 31 | 32 | The hashes are computed using, by default, the `sha1` algorithm for files and `sha512` algorithm for directories, with `base64` encoding, complying to [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity) spec, but other [Node.js crypto](https://nodejs.org/api/crypto.html) supported [algorithms](https://nodejs.org/api/crypto.html#cryptogethashes) and [encodings](https://nodejs.org/api/crypto.html#hashdigestencoding) can be used. 33 | 34 | ## Instalation 35 | 36 | To install as a dependency, simply type: 37 | 38 | ```sh 39 | npm i nsri --save 40 | ``` 41 | 42 | To install for global use, simply type: 43 | 44 | ```sh 45 | npm i nsri -g 46 | ``` 47 | 48 | ## Behavior 49 | 50 | **NOTE:** 51 | 52 | - The `.integrity.json` file itself is being excluded in all computations. 53 | - The `node_modules`, `.git*`, `.svn*`, `.hg*` directories are excluded by default. 54 | 55 | ### Files 56 | 57 | **Hashes are the same when:** 58 | 59 | - File names and contents are the same 60 | 61 | **Hashes are different when:** 62 | 63 | - File names are different and contents are the same 64 | - File contents are different and names are the same 65 | 66 | ### Directories 67 | 68 | Contents: The file names (and their data contents) and subdirectories names (with their contents) of the directory 69 | 70 | **Hashes are the same when:** 71 | 72 | - Directory names and contents are the same `(strict: true)` 73 | - Only root directory names are different and subdirectory names and all contents are the same `(strict: false)` 74 | 75 | **Hashes are different when:** 76 | 77 | - Directory names are different and contents are the same `(strict: true)` 78 | - Directory contents are different and names are the same 79 | 80 | ## Usage 81 | 82 | ### CLI 83 | 84 | `nsri` has a built-in command-line inteface. 85 | 86 | ```sh 87 | nsri [options] 88 | ``` 89 | 90 | To see the available `commands` type: 91 | 92 | ```sh 93 | nsri -h 94 | ``` 95 | 96 | and for available `command` options type: 97 | 98 | ```sh 99 | nsri -h 100 | ``` 101 | 102 | More info an be found at the [CLI](https://github.com/JimiC/nsri/blob/main/docs/cli.md) section. 103 | 104 | ### API 105 | 106 | `nsri` can also be used programatically ([TypeScript](https://www.typescriptlang.org/) types are included). 107 | 108 | More info can be found at the [API](https://github.com/JimiC/nsri/blob/main/docs/api.md) section. 109 | 110 | ### Configuration 111 | 112 | #### Config File 113 | 114 | `nsri` supports [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) configuration. 115 | 116 | Valid config filenames are: `.nsrirc`, `.nsrirc.js`, `.nsrirc.json`, `.nsrirc.yaml`, `.nsrirc.yml`, `.nsrirc.config.js`. In `package.json` the property name MUST be `nsri`. 117 | 118 | **NOTE:** Configurations set via `CLI` are overriding configurations set via `cosmiconfig`. To avoid confusion use one or the other. 119 | 120 | #### Ignore File 121 | 122 | Exclusions also can be set via an ignore file (`.nsriignore`), which supports the [gitignore](https://git-scm.com/docs/gitignore#_pattern_format) pattern format. 123 | 124 | **NOTE:** ExclusionsExclutionsExclutions set via `CLI` or `cosmiconfig` are getting merged with those in the ignore file and from those only unique entries are assigned. 125 | 126 | ### Integrity object schema 127 | 128 | ```json 129 | { 130 | "version": ... schema version, 131 | "hashes": ... verbosely or non-verbosely computed hashes 132 | } 133 | ``` 134 | 135 | More info on the used schema can be found [here](https://github.com/JimiC/nsri/blob/main/src/schemas). 136 | 137 | #### Verbosely hashes schema 138 | 139 | ```json 140 | { 141 | "directoryName": { 142 | "contents": { 143 | "aFileName": ... file computed hash string, 144 | "anotherFileName": ... file computed hash string 145 | }, 146 | "hash": ... directory computed hash string 147 | } 148 | } 149 | ``` 150 | 151 | Examples of a verbosely computed hash integrity file can be found [here](https://github.com/JimiC/nsri/blob/main/test/fixtures). 152 | 153 | #### Non-verbosely hashes schema 154 | 155 | ```json 156 | { 157 | "fileOrDirectoryName": ... file or directory computed hash string 158 | } 159 | ``` 160 | 161 | ### Examples 162 | 163 | Examples on how to use `nsri`, via `CLI` or `API`, can be found at the [examples](https://github.com/JimiC/nsri/blob/main/docs/examples) section. 164 | 165 | If you believe that the examples are incomplete or incorrect, please submit an issue or better yet a PR. 166 | 167 | ## Contributing 168 | 169 | If you like to contribute make sure to check-out the [Contribution Guidelines](https://github.com/JimiC/nsri/blob/main/.github/CONTRIBUTING.md) section. 170 | 171 | ## License 172 | 173 | This project is licensed under the [MIT](https://github.com/JimiC/nsri/blob/main/LICENSE) license. 174 | 175 | ## Versioning 176 | 177 | This project follows [Semantic Versioning 2.0.0](https://semver.org). 178 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | /** 2 | * Config file for API Extractor. For more info, please visit: https://api-extractor.com 3 | */ 4 | { 5 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 6 | 7 | /** 8 | * Optionally specifies another JSON config file that this file extends from. This provides a way for 9 | * standard settings to be shared across multiple projects. 10 | * 11 | * If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains 12 | * the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be 13 | * resolved using NodeJS require(). 14 | * 15 | * SUPPORTED TOKENS: none 16 | * DEFAULT VALUE: "" 17 | */ 18 | // "extends": "./shared/api-extractor-base.json" 19 | // "extends": "my-package/include/api-extractor-base.json" 20 | 21 | /** 22 | * Determines the "" token that can be used with other config file settings. The project folder 23 | * typically contains the tsconfig.json and package.json config files, but the path is user-defined. 24 | * 25 | * The path is resolved relative to the folder of the config file that contains the setting. 26 | * 27 | * The default value for "projectFolder" is the token "", which means the folder is determined by traversing 28 | * parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder 29 | * that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error 30 | * will be reported. 31 | * 32 | * SUPPORTED TOKENS: 33 | * DEFAULT VALUE: "" 34 | */ 35 | // "projectFolder": "..", 36 | 37 | /** 38 | * (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor 39 | * analyzes the symbols exported by this module. 40 | * 41 | * The file extension must be ".d.ts" and not ".ts". 42 | * 43 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 44 | * prepend a folder token such as "". 45 | * 46 | * SUPPORTED TOKENS: , , 47 | */ 48 | "mainEntryPointFilePath": "/dist/out/index.d.ts", 49 | 50 | /** 51 | * A list of NPM package names whose exports should be treated as part of this package. 52 | * 53 | * For example, suppose that Webpack is used to generate a distributed bundle for the project "library1", 54 | * and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part 55 | * of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly 56 | * imports library2. To avoid this, we can specify: 57 | * 58 | * "bundledPackages": [ "library2" ], 59 | * 60 | * This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been 61 | * local files for library1. 62 | */ 63 | "bundledPackages": [ ], 64 | 65 | /** 66 | * Determines how the TypeScript compiler engine will be invoked by API Extractor. 67 | */ 68 | "compiler": { 69 | /** 70 | * Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project. 71 | * 72 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 73 | * prepend a folder token such as "". 74 | * 75 | * Note: This setting will be ignored if "overrideTsconfig" is used. 76 | * 77 | * SUPPORTED TOKENS: , , 78 | * DEFAULT VALUE: "/tsconfig.json" 79 | */ 80 | // "tsconfigFilePath": "/tsconfig.json", 81 | 82 | /** 83 | * Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk. 84 | * The object must conform to the TypeScript tsconfig schema: 85 | * 86 | * http://json.schemastore.org/tsconfig 87 | * 88 | * If omitted, then the tsconfig.json file will be read from the "projectFolder". 89 | * 90 | * DEFAULT VALUE: no overrideTsconfig section 91 | */ 92 | // "overrideTsconfig": { 93 | // . . . 94 | // } 95 | 96 | /** 97 | * This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended 98 | * and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when 99 | * dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses 100 | * for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck. 101 | * 102 | * DEFAULT VALUE: false 103 | */ 104 | // "skipLibCheck": true, 105 | }, 106 | 107 | /** 108 | * Configures how the API report file (*.api.md) will be generated. 109 | */ 110 | "apiReport": { 111 | /** 112 | * (REQUIRED) Whether to generate an API report. 113 | */ 114 | "enabled": false 115 | 116 | /** 117 | * The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce 118 | * a full file path. 119 | * 120 | * The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/". 121 | * 122 | * SUPPORTED TOKENS: , 123 | * DEFAULT VALUE: ".api.md" 124 | */ 125 | // "reportFileName": ".api.md", 126 | 127 | /** 128 | * Specifies the folder where the API report file is written. The file name portion is determined by 129 | * the "reportFileName" setting. 130 | * 131 | * The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy, 132 | * e.g. for an API review. 133 | * 134 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 135 | * prepend a folder token such as "". 136 | * 137 | * SUPPORTED TOKENS: , , 138 | * DEFAULT VALUE: "/etc/" 139 | */ 140 | // "reportFolder": "/etc/", 141 | 142 | /** 143 | * Specifies the folder where the temporary report file is written. The file name portion is determined by 144 | * the "reportFileName" setting. 145 | * 146 | * After the temporary file is written to disk, it is compared with the file in the "reportFolder". 147 | * If they are different, a production build will fail. 148 | * 149 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 150 | * prepend a folder token such as "". 151 | * 152 | * SUPPORTED TOKENS: , , 153 | * DEFAULT VALUE: "/temp/" 154 | */ 155 | // "reportTempFolder": "/temp/" 156 | }, 157 | 158 | /** 159 | * Configures how the doc model file (*.api.json) will be generated. 160 | */ 161 | "docModel": { 162 | /** 163 | * (REQUIRED) Whether to generate a doc model file. 164 | */ 165 | "enabled": false 166 | 167 | /** 168 | * The output path for the doc model file. The file extension should be ".api.json". 169 | * 170 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 171 | * prepend a folder token such as "". 172 | * 173 | * SUPPORTED TOKENS: , , 174 | * DEFAULT VALUE: "/temp/.api.json" 175 | */ 176 | // "apiJsonFilePath": "/temp/.api.json" 177 | }, 178 | 179 | /** 180 | * Configures how the .d.ts rollup file will be generated. 181 | */ 182 | "dtsRollup": { 183 | /** 184 | * (REQUIRED) Whether to generate the .d.ts rollup file. 185 | */ 186 | "enabled": true, 187 | 188 | /** 189 | * Specifies the output path for a .d.ts rollup file to be generated without any trimming. 190 | * This file will include all declarations that are exported by the main entry point. 191 | * 192 | * If the path is an empty string, then this file will not be written. 193 | * 194 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 195 | * prepend a folder token such as "". 196 | * 197 | * SUPPORTED TOKENS: , , 198 | * DEFAULT VALUE: "/dist/.d.ts" 199 | */ 200 | "untrimmedFilePath": "/lib/.d.ts" 201 | 202 | /** 203 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release. 204 | * This file will include only declarations that are marked as "@public" or "@beta". 205 | * 206 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 207 | * prepend a folder token such as "". 208 | * 209 | * SUPPORTED TOKENS: , , 210 | * DEFAULT VALUE: "" 211 | */ 212 | // "betaTrimmedFilePath": "/dist/-beta.d.ts", 213 | 214 | 215 | /** 216 | * Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release. 217 | * This file will include only declarations that are marked as "@public". 218 | * 219 | * If the path is an empty string, then this file will not be written. 220 | * 221 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 222 | * prepend a folder token such as "". 223 | * 224 | * SUPPORTED TOKENS: , , 225 | * DEFAULT VALUE: "" 226 | */ 227 | // "publicTrimmedFilePath": "/dist/-public.d.ts", 228 | 229 | /** 230 | * When a declaration is trimmed, by default it will be replaced by a code comment such as 231 | * "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the 232 | * declaration completely. 233 | * 234 | * DEFAULT VALUE: false 235 | */ 236 | // "omitTrimmingComments": true 237 | }, 238 | 239 | /** 240 | * Configures how the tsdoc-metadata.json file will be generated. 241 | */ 242 | "tsdocMetadata": { 243 | /** 244 | * Whether to generate the tsdoc-metadata.json file. 245 | * 246 | * DEFAULT VALUE: true 247 | */ 248 | "enabled": false 249 | 250 | /** 251 | * Specifies where the TSDoc metadata file should be written. 252 | * 253 | * The path is resolved relative to the folder of the config file that contains the setting; to change this, 254 | * prepend a folder token such as "". 255 | * 256 | * The default value is "", which causes the path to be automatically inferred from the "tsdocMetadata", 257 | * "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup 258 | * falls back to "tsdoc-metadata.json" in the package folder. 259 | * 260 | * SUPPORTED TOKENS: , , 261 | * DEFAULT VALUE: "" 262 | */ 263 | // "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" 264 | }, 265 | 266 | /** 267 | * Specifies what type of newlines API Extractor should use when writing output files. By default, the output files 268 | * will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead. 269 | * To use the OS's default newline kind, specify "os". 270 | * 271 | * DEFAULT VALUE: "crlf" 272 | */ 273 | "newlineKind": "lf", 274 | 275 | /** 276 | * Configures how API Extractor reports error and warning messages produced during analysis. 277 | * 278 | * There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages. 279 | */ 280 | "messages": { 281 | /** 282 | * Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing 283 | * the input .d.ts files. 284 | * 285 | * TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551" 286 | * 287 | * DEFAULT VALUE: A single "default" entry with logLevel=warning. 288 | */ 289 | "compilerMessageReporting": { 290 | /** 291 | * Configures the default routing for messages that don't match an explicit rule in this table. 292 | */ 293 | "default": { 294 | /** 295 | * Specifies whether the message should be written to the the tool's output log. Note that 296 | * the "addToApiReportFile" property may supersede this option. 297 | * 298 | * Possible values: "error", "warning", "none" 299 | * 300 | * Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail 301 | * and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes 302 | * the "--local" option), the warning is displayed but the build will not fail. 303 | * 304 | * DEFAULT VALUE: "warning" 305 | */ 306 | "logLevel": "warning" 307 | 308 | /** 309 | * When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md), 310 | * then the message will be written inside that file; otherwise, the message is instead logged according to 311 | * the "logLevel" option. 312 | * 313 | * DEFAULT VALUE: false 314 | */ 315 | // "addToApiReportFile": false 316 | } 317 | 318 | // "TS2551": { 319 | // "logLevel": "warning", 320 | // "addToApiReportFile": true 321 | // }, 322 | // 323 | // . . . 324 | }, 325 | 326 | /** 327 | * Configures handling of messages reported by API Extractor during its analysis. 328 | * 329 | * API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag" 330 | * 331 | * DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings 332 | */ 333 | "extractorMessageReporting": { 334 | "default": { 335 | "logLevel": "warning" 336 | // "addToApiReportFile": false 337 | } 338 | 339 | // "ae-extra-release-tag": { 340 | // "logLevel": "warning", 341 | // "addToApiReportFile": true 342 | // }, 343 | // 344 | // . . . 345 | }, 346 | 347 | /** 348 | * Configures handling of messages reported by the TSDoc parser when analyzing code comments. 349 | * 350 | * TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text" 351 | * 352 | * DEFAULT VALUE: A single "default" entry with logLevel=warning. 353 | */ 354 | "tsdocMessageReporting": { 355 | "default": { 356 | "logLevel": "warning" 357 | // "addToApiReportFile": false 358 | } 359 | 360 | // "tsdoc-link-tag-unescaped-text": { 361 | // "logLevel": "warning", 362 | // "addToApiReportFile": true 363 | // }, 364 | // 365 | // . . . 366 | } 367 | } 368 | 369 | } 370 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | All `API` calls are `static` members of the `Integrity` class. 4 | 5 | --- 6 | 7 | >## CurrentSchemaVersion 8 | 9 | `Description`: Constant value of the current schema version used. 10 | 11 | `Return Type`: `string` 12 | 13 | --- 14 | 15 | >## check 16 | 17 | `Description`: Checks the integrity of a directory or a file. 18 | 19 | `Info`: The `fileOrDirPath` can be an absolute or relative path. 20 | 21 | `Return Type`: `Promise` 22 | 23 | `Parameters`: 24 | 25 | |Name|Type|Attribute|Default|Description| 26 | |:---:|:---:|:---:|:---:|:---:| 27 | |fileOrDirPath|string|||the path of the file or directory to check| 28 | |integrity|string|||the path of the directory containing the integrity file or the path to the integrity file or a stringified integrity JSON or a hash string, to check against| 29 | |options|IntegrityOptions|optional|see [options](#options) section|the `integrity` options to use| 30 | 31 | `Examples` 32 | 33 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/check-es5-js.md) 34 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/check-js-ts.md) 35 | 36 | --- 37 | 38 | >## create 39 | 40 | `Description`: Creates an integrity object of a directory or file. 41 | 42 | `Info`: `create` is a top-level helper function designed to internally determine whether to use `createDirHash` or `createFileHash`, saving you the coding hassle. Usually, `create` will be the function you are going to use, when you want to create an `integrity` object. The `fileOrDirPath` can be an absolute or relative path. 43 | 44 | `Return Type`: `Promise` 45 | 46 | `Parameters`: 47 | 48 | |Name|Type|Attribute|Default|Description| 49 | |:---:|:---:|:---:|:---:|:---:| 50 | |fileOrDirPath|string|||the path of the file or directory to hash| 51 | |options|IntegrityOptions|optional|see [options](#options) section|the `integrity` options to use| 52 | 53 | `Examples` 54 | 55 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/create-es5-js.md) 56 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/create-js-ts.md) 57 | 58 | --- 59 | 60 | >## createDirHash 61 | 62 | `Description`: Creates a hash object of a directory. 63 | 64 | `Info`: `createDirHash` is a function designed to create an `integrity` object for a directory. The `dirPath` can be an absolute or relative path. Creating a non-verbosely `integrity` object will compute all contents hashes combined. 65 | 66 | `Return Type`: `Promise` 67 | 68 | `Parameters`: 69 | 70 | |Name|Type|Attribute|Default|Description| 71 | |:---:|:---:|:---:|:---:|:---:| 72 | |dirPath|string|||the path of the directory to hash| 73 | |options|IntegrityOptions|optional|see [options](#options) section|the `integrity` options to use| 74 | 75 | `Examples` 76 | 77 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/check-es5-js.md) 78 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/check-js-ts.md) 79 | 80 | --- 81 | 82 | >## createFileHash 83 | 84 | `Description`: Creates a hash object of a file. 85 | 86 | `Info`: `createFileHash` is a function designed to create an `integrity` object for a file. The `filePath` can be an absolute or relative path. 87 | 88 | `Return Type`: `Promise` 89 | 90 | `Parameters`: 91 | 92 | |Name|Type|Attribute|Default|Description| 93 | |:---:|:---:|:---:|:---:|:---:| 94 | |filePath|string|||the path of the file to hash| 95 | |options|CryptoOptions|optional|see [options](#options) section|the `crypto` options to use| 96 | 97 | `Examples` 98 | 99 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/createFileHash-es5-js.md) 100 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/createFileHash-js-ts.md) 101 | 102 | --- 103 | 104 | >## createFilesHash 105 | 106 | `Description`: Creates a hash object of a list of files. 107 | 108 | `Info`: `createFilesHash` is a function designed to create an `integrity` object for a list of files. The `filenames` can be absolute or relative paths. 109 | 110 | `Return Type`: `Promise` 111 | 112 | `Parameters`: 113 | 114 | |Name|Type|Attribute|Default|Description| 115 | |:---:|:---:|:---:|:---:|:---:| 116 | |filenames|string[]|||the list of the file paths to hash| 117 | |options|CryptoOptions|optional|see [options](#options) section|the `crypto` options to use| 118 | 119 | `Examples` 120 | 121 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/createFilesHash-es5-js.md) 122 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/createFilesHash-js-ts.md) 123 | 124 | --- 125 | 126 | >## persist 127 | 128 | `Description`: Persists the integrity object on disk. 129 | 130 | `Info`: `persist` is a function designed to persist the created integrity object on disk. Usually, you will use `persist` whenever you create an `integrity` object. If the `dirPath` parameter is omitted, the `integrity` object will be persisted at the root directory, from where the function gets called. The `dirPath` can be an absolute or relative path. 131 | 132 | `Return Type`: `Promise` 133 | 134 | `Parameters`: 135 | 136 | |Name|Type|Attribute|Default|Description| 137 | |:---:|:---:|:---:|:---:|:---:| 138 | |intObj|IntegrityObject|||the integrity object to persist| 139 | |dirPath|string|optional|`./`|the path of the directory to persist the data to| 140 | |prettify|boolean|optional|false|data are formatted with indentation| 141 | 142 | `Examples` 143 | 144 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/persist-es5-js.md) 145 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/persist-js-ts.md) 146 | 147 | --- 148 | 149 | >## getManifestIntegrity 150 | 151 | `Description`: Gets the integrity object from the manifest file. 152 | 153 | `Info`: `getManifestIntegrity` is a function designed to retrieve a stringified version of the integrity object from the project's manifest file (`project.json`). 154 | 155 | `Return Type`: `Promise` 156 | 157 | `Parameters`: 158 | 159 | |Name|Type|Attribute|Default|Description| 160 | |:---:|:---:|:---:|:---:|:---:| 161 | |dirPath|string|optional|`./`|the path of the directory to the manifest file| 162 | 163 | `Examples` 164 | 165 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/getManifestIntegrity-es5-js.md) 166 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/getManifestIntegrity-js-ts.md) 167 | 168 | --- 169 | 170 | >## updateManifestIntegrity 171 | 172 | `Description`: Updates the manifest file (`project.json`) with the integrity object. 173 | 174 | `Info`: `updateManifestIntegrity` is a function designed to update the project's manifest file (`project.json`) with the integrity object. 175 | 176 | `Return Type`: `Promise` 177 | 178 | `Parameters`: 179 | 180 | |Name|Type|Attribute|Default|Description| 181 | |:---:|:---:|:---:|:---:|:---:| 182 | |intObj|IntegrityObject|||the integrity object to persist| 183 | |dirPath|string|optional|`./`|the path of the directory to the manifest file| 184 | 185 | `Examples` 186 | 187 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/updateManifestIntegrity-es5-js.md) 188 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/updateManifestIntegrity-js-ts.md) 189 | 190 | --- 191 | 192 | >## getIntegrityOptionsFromConfig 193 | 194 | `Description`: 195 | 196 | `Info`: `getIntegrityOptionsFromConfig` is a function designed to retrieve the integrity options from a `cosmiconfig` compatible configuration section. 197 | 198 | `Return Type`: `Promise` 199 | 200 | `Parameters`: None 201 | 202 | `Examples` 203 | 204 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/getIntegrityOptionsFromConfig-es5-js.md) 205 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/getIntegrityOptionsFromConfig-js-ts.md) 206 | 207 | --- 208 | 209 | >## getExclusionsFromIgnoreFile 210 | 211 | `Description`: 212 | 213 | `Info`: `getExclusionsFromIgnoreFile` is a function designed to retrieve the exclusions from a `.nsriignore` file. 214 | 215 | `Return Type`: `Promise` 216 | 217 | `Parameters`: 218 | 219 | |Name|Type|Attribute|Default|Description| 220 | |:---:|:---:|:---:|:---:|:---:| 221 | |dirPath|string|optional|`./`|the path of the directory to the ignore file| 222 | 223 | `Examples` 224 | 225 | - [ES5](https://github.com/JimiC/nsri/blob/main/docs/examples/getExclusionsFromIgnoreFile-es5-js.md) 226 | - [ES6+, Typescript](https://github.com/JimiC/nsri/blob/main/docs/examples/getExclusionsFromIgnoreFile-js-ts.md) 227 | 228 | --- 229 | 230 | ## Options 231 | 232 | ### IntegrityOptions 233 | 234 | |Name|Type|Attribute|Default|Description| 235 | |:---:|:---:|:---:|:---:|:---:| 236 | |cryptoOptions|CryptoOptions|optional|see `CryptoOptions` |the `crypto` options to use| 237 | |strict|boolean|optional|false|whether the computed hashes are strictly using the directory name| 238 | |verbose|boolean|optional|false|whether the computed hashes are returned in a verbosely or non-verbosely structure| 239 | |exclude|string[]|optional|[]|the paths to be excluded, supports also `glob` expressions (positive & negative)| 240 | 241 | ### CryptoOptions 242 | 243 | |Name|Type|Attribute|Default|Description| 244 | |:---:|:---:|:---:|:---:|:---:| 245 | |dirAlgorithm|string|optional|`sha512`|the `crypto` algorithm to use for directories| 246 | |encoding|BinaryToTextEncoding|optional|`base64`|the `crypto` encoding to use| 247 | |fileAlgorithm|string|optional|`sha1`|the `crypto` algorithm to use for files| 248 | 249 | --- 250 | 251 | ## Importation 252 | 253 | See [here](https://github.com/JimiC/nsri/blob/main/docs/importation.md) how to import the library. 254 | -------------------------------------------------------------------------------- /docs/cli.md: -------------------------------------------------------------------------------- 1 | # CLI 2 | 3 | ## Commands 4 | 5 | --- 6 | 7 | >## create 8 | 9 | `Description`: Creates integrity hash from the provided source. 10 | 11 | --- 12 | 13 | >## check 14 | 15 | `Description`: Checks integrity hash against the provided source. 16 | 17 | --- 18 | 19 | ## Options 20 | 21 | --- 22 | 23 | >## --diralgorithm 24 | 25 | `Alias`: -da 26 | 27 | `Description`: The algorithm to use for directory hashing. 28 | 29 | `Default`: sha512 30 | 31 | `Type`: string 32 | 33 | --- 34 | 35 | >## --encoding 36 | 37 | `Alias`: -e 38 | 39 | `Description`: The encoding to use for hashing. 40 | 41 | `Default`: base64 42 | 43 | `Type`: string 44 | 45 | --- 46 | 47 | >## --exclude 48 | 49 | `Alias`: -x 50 | 51 | `Description`: Files and/or directories paths to exclude. 52 | 53 | `Default`: [] 54 | 55 | `Type`: array 56 | 57 | --- 58 | 59 | >## --filealgorithm 60 | 61 | `Alias`: -fa 62 | 63 | `Description`: The algorithm to use for file hashing. 64 | 65 | `Default`: sha1 66 | 67 | `Type`: string 68 | 69 | --- 70 | 71 | >## --integrity 72 | 73 | `Alias`: -i 74 | 75 | `Description`: The integrity hash, JSON, file or directory path, to check against ([required] when 'manifest' option not specified). 76 | 77 | `Type`: string 78 | 79 | --- 80 | 81 | >## --manifest 82 | 83 | `Alias`: -m 84 | 85 | `Description`: The integrity hash gets persisted to, or read from, the project's manifest (package.json). 86 | 87 | `Default`: undefined 88 | 89 | `Type`: boolean 90 | 91 | --- 92 | 93 | >## --output 94 | 95 | `Alias`: -o 96 | 97 | `Description`: The directory path where to persist the created integrity file (ignored when 'manifest' option specified). 98 | 99 | `Type`: string 100 | 101 | --- 102 | 103 | >## --source 104 | 105 | `Alias`: -s 106 | 107 | `Description`: The path to the file or directory to hash. 108 | 109 | `Mandatory`: true 110 | 111 | `Type`: string 112 | 113 | --- 114 | 115 | >## --verbose 116 | 117 | `Alias`: -v 118 | 119 | `Description`: Verbosely create hashes of a directory. 120 | 121 | `Default`: false 122 | 123 | `Type`: boolean 124 | 125 | --- 126 | 127 | >## --strict 128 | 129 | `Alias`: -st 130 | 131 | `Description`: Strictly compares names and contents. 132 | 133 | `Default`: false 134 | 135 | `Type`: boolean 136 | 137 | --- 138 | 139 | >## --help 140 | 141 | `Alias`: -h 142 | 143 | `Description`: Show help. 144 | 145 | --- 146 | 147 | >## --version 148 | 149 | `Alias`: -V 150 | 151 | `Description`: Show version number. 152 | 153 | --- 154 | -------------------------------------------------------------------------------- /docs/examples/check-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.check` (ECMAScript 5) 4 | 5 | > Check the integrity of the root directory, using a root 'integrity' file 6 | 7 | ```js 8 | Integrity.check('./', './.integrity.json') 9 | .then(pass => console.info('Integrity check ' + pass ? 'passed': 'failed')) 10 | .catch(error => console.error(error)) 11 | ``` 12 | 13 | --- 14 | 15 | > Check the integrity of a subdirectory, using a subdirectory 'integrity' file. 16 | 17 | ```js 18 | Integrity.check('./sub', './sub/.integrity.json') 19 | .then(pass => console.info('Integrity check ' + pass ? 'passed': 'failed')) 20 | .catch(error => console.error(error)) 21 | ``` 22 | 23 | --- 24 | 25 | > Check the integrity of a root directory file, using a root 'integrity' file 26 | 27 | ```js 28 | Integrity.check('./fileToCheck.txt', './.integrity.json') 29 | .then(pass => console.info('Integrity check ' + pass ? 'passed': 'failed')) 30 | .catch(error => console.error(error)) 31 | ``` 32 | 33 | --- 34 | 35 | > Check the integrity of a subdirectory file, using a root 'integrity' file. 36 | 37 | ```js 38 | Integrity.check('./sub/fileToCheck.txt', './.integrity.json') 39 | .then(pass => console.info('Integrity check ' + pass ? 'passed': 'failed')) 40 | .catch(error => console.error(error)) 41 | ``` 42 | 43 | --- 44 | 45 | > Check the integrity of a subdirectory file, using a subdirectory 'integrity' file. 46 | 47 | ```js 48 | Integrity.check('./sub/fileToCheck.txt', './sub/.integrity.json') 49 | .then(pass => console.info('Integrity check ' + pass ? 'passed': 'failed')) 50 | .catch(error => console.error(error)) 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/examples/check-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.check` (ECMAScript 6+, TypeScript) 4 | 5 | > Check the integrity of the root directory, using a root 'integrity' file 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const pass = await Integrity.check('./', './.integrity.json') 11 | ``` 12 | 13 | `TypeScript` 14 | 15 | ```ts 16 | const pass: boolean = await Integrity.check('./', './.integrity.json'); 17 | ``` 18 | 19 | --- 20 | 21 | > Check the integrity of a subdirectory, using a subdirectory 'integrity' file. 22 | 23 | `ES6+` 24 | 25 | ```js 26 | const pass = await Integrity.check('./sub', './sub/.integrity.json') 27 | ``` 28 | 29 | `TypeScript` 30 | 31 | ```ts 32 | const pass: boolean = await Integrity.check('./sub', './sub/.integrity.json'); 33 | ``` 34 | 35 | --- 36 | 37 | > Check the integrity of a root directory file, using a root 'integrity' file 38 | 39 | `ES6+` 40 | 41 | ```js 42 | const pass = await Integrity.check('./fileToCheck.txt', './.integrity.json') 43 | ``` 44 | 45 | `TypeScript` 46 | 47 | ```ts 48 | const pass: boolean = await Integrity.check('./fileToCheck.txt', './.integrity.json'); 49 | ``` 50 | 51 | --- 52 | 53 | > Check the integrity of a subdirectory file, using a root 'integrity' file. 54 | 55 | `ES6+` 56 | 57 | ```js 58 | const pass = await Integrity.check('./sub/fileToCheck.txt', './.integrity.json') 59 | ``` 60 | 61 | `TypeScript` 62 | 63 | ```ts 64 | const pass: boolean = await Integrity.check('./sub/fileToCheck.txt', './.integrity.json'); 65 | ``` 66 | 67 | --- 68 | 69 | > Check the integrity of a subdirectory file, using a subdirectory 'integrity' file. 70 | 71 | `ES6+` 72 | 73 | ```js 74 | const pass = await Integrity.check('./sub/fileToCheck.txt', './sub/.integrity.json') 75 | ``` 76 | 77 | `TypeScript` 78 | 79 | ```ts 80 | const pass: boolean = await Integrity.check('./sub/fileToCheck.txt', './sub/.integrity.json'); 81 | ``` 82 | -------------------------------------------------------------------------------- /docs/examples/cli.md: -------------------------------------------------------------------------------- 1 | # CLI Examples 2 | 3 | ## `check` 4 | 5 | > Check the integrity of the root directory, using a root 'integrity' file 6 | 7 | ```sh 8 | nsri check -s ./ -i ./.integrity.json 9 | 10 | ``` 11 | 12 | > Check the integrity of the root directory, using the 'integrity' from a manifest file 13 | 14 | ```sh 15 | nsri check -m -s ./ 16 | ``` 17 | 18 | --- 19 | 20 | ## `create` 21 | 22 | > Create a non-verbosely integrity file of the root directory 23 | 24 | ```sh 25 | nsri create -s ./ 26 | ``` 27 | 28 | --- 29 | 30 | > Create a verbosely integrity file of the root directory 31 | 32 | ```sh 33 | nsri create -v -s ./ 34 | ``` 35 | 36 | --- 37 | 38 | > Create a non-verbosely integrity file of a sub directory and persist it on the subdirectory 39 | 40 | ```sh 41 | nsri create -s ./sub -o ./sub 42 | ``` 43 | 44 | --- 45 | 46 | > Create a non-verbosely integrity file of the root directory and persist it on the manifest file 47 | 48 | ```sh 49 | nsri create -m -s ./ 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/examples/create-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.create` (ECMAScript 5) 4 | 5 | >Create a verbosely integrity object of the root directory, using the default `sha1` and `sha512` algorithm and `hex` encoding 6 | 7 | ```js 8 | Integrity.create('./') 9 | .then(intObj => { 10 | // Do something with the integrity object here 11 | // It's advised not to modify them 12 | // as it will certainly lead to integrity check failure 13 | // when you'll try to check against them 14 | }) 15 | .catch(error => console.error(error)) 16 | ``` 17 | 18 | --- 19 | 20 | > Create a verbosely integrity object of the root directory, using the `sha256` algorithm and `base64` encoding 21 | 22 | ```js 23 | var options = { cryptoOptions: { dirAlgorithm: 'sha256', encoding: 'base64', fileAlgorithm: "sha256" } } 24 | Integrity.create('./', options) 25 | .then(intObj => { 26 | // Do something with the integrity object here 27 | // It's advised not to modify them 28 | // as it will certainly lead to integrity check failure 29 | // when you'll try to check against them 30 | }) 31 | .catch(error => console.error(error)) 32 | ``` 33 | 34 | --- 35 | 36 | > Create a verbosely integrity object of a subdirectory 37 | 38 | ```js 39 | Integrity.create('./sub') 40 | .then(intObj => { 41 | // Do something with the integrity object here 42 | // It's advised not to modify them 43 | // as it will certainly lead to integrity check failure 44 | // when you'll try to check against them 45 | }) 46 | .catch(error => console.error(error)) 47 | ``` 48 | 49 | --- 50 | 51 | > Create a non-verbosely integrity object of a file 52 | 53 | ```js 54 | var options = { verbose: false } 55 | Integrity.create('./fileToHash.txt', options) 56 | .then(intObj => { 57 | // Do something with the integrity object here 58 | // It's advised not to modify them 59 | // as it will certainly lead to integrity check failure 60 | // when you'll try to check against them 61 | }) 62 | .catch(error => console.error(error)) 63 | ``` 64 | 65 | --- 66 | 67 | > Create a verbosely integrity object of a subdirectory file 68 | 69 | ```js 70 | Integrity.create('./sub/fileToHash.txt') 71 | .then(intObj => { 72 | // Do something with the integrity object here 73 | // It's advised not to modify them 74 | // as it will certainly lead to integrity check failure 75 | // when you'll try to check against them 76 | }) 77 | .catch(error => console.error(error)) 78 | ``` 79 | 80 | --- 81 | 82 | > Create a verbosely integrity object of a directory excluding a file 83 | 84 | ```js 85 | var options = { exclude: ['fileToExclude.txt'] } 86 | Integrity.create('./dir', options) 87 | .then(intObj => { 88 | // Do something with the integrity object here 89 | // It's advised not to modify them 90 | // as it will certainly lead to integrity check failure 91 | // when you'll try to check against them 92 | }) 93 | .catch(error => console.error(error)) 94 | ``` 95 | 96 | --- 97 | 98 | > Create a verbosely integrity object of a directory excluding a subdirectory 99 | 100 | ```js 101 | var options = { exclude: ['sub'] } 102 | Integrity.create('./dir', options) 103 | .then(intObj => { 104 | // Do something with the integrity object here 105 | // It's advised not to modify them 106 | // as it will certainly lead to integrity check failure 107 | // when you'll try to check against them 108 | }) 109 | .catch(error => console.error(error)) 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/examples/create-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.create` (ECMAScript 6+, TypeScript) 4 | 5 | ## Using with `async/await` 6 | 7 | > Create a verbosely integrity object of the root directory, using the default `sha1` and `sha512` algorithm and `base64` encoding 8 | 9 | `ES6+` 10 | 11 | ```js 12 | const intObj = await Integrity.create('./') 13 | 14 | // Do something with the integrity object here 15 | // It's advised not to modify them 16 | // as it will certainly lead to integrity check failure 17 | // when you'll try to check against them 18 | ``` 19 | 20 | `TypeScript` 21 | 22 | ```ts 23 | const intObj: IntegrityObject = await Integrity.create('./'); 24 | 25 | // Do something with the integrity object here 26 | // It's advised not to modify them 27 | // as it will certainly lead to integrity check failure 28 | // when you'll try to check against them 29 | ``` 30 | 31 | --- 32 | 33 | > Create a verbosely integrity object of the root directory, using the `sha256` algorithm and `base64` encoding 34 | 35 | `ES6+` 36 | 37 | ```js 38 | const options = { cryptoOptions: { dirAlgorithm: 'sha256', encoding: 'base64', fileAlgorithm: 'sha256' } } 39 | const intObj = await Integrity.create('./', options) 40 | 41 | // Do something with the integrity object here 42 | // It's advised not to modify them 43 | // as it will certainly lead to integrity check failure 44 | // when you'll try to check against them 45 | ``` 46 | 47 | `TypeScript` 48 | 49 | ```ts 50 | const options: IntegrityOptions = { cryptoOptions: { dirAlgorithm: 'sha256', encoding: 'base64', fileAlgorithm: 'sha256' } }; 51 | const intObj: IntegrityObject = await Integrity.create('./', options); 52 | 53 | // Do something with the integrity object here 54 | // It's advised not to modify them 55 | // as it will certainly lead to integrity check failure 56 | // when you'll try to check against them 57 | ``` 58 | 59 | --- 60 | 61 | > Create a verbosely integrity object of a subdirectory 62 | 63 | `ES6+` 64 | 65 | ```ts 66 | const intObj = await Integrity.create('./sub') 67 | 68 | // Do something with the integrity object here 69 | // It's advised not to modify them 70 | // as it will certainly lead to integrity check failure 71 | // when you'll try to check against them 72 | ``` 73 | 74 | `TypeScript` 75 | 76 | ```ts 77 | const intObj: IntegrityObject = await Integrity.create('./sub'); 78 | 79 | // Do something with the integrity object here 80 | // It's advised not to modify them 81 | // as it will certainly lead to integrity check failure 82 | // when you'll try to check against them 83 | ``` 84 | 85 | --- 86 | 87 | > Create a non-verbosely integrity object of a file 88 | 89 | `ES6+` 90 | 91 | ```js 92 | const options = { verbose: false } 93 | const intObj = await Integrity.create('./fileToHash.txt', options) 94 | 95 | // Do something with the integrity object here 96 | // It's advised not to modify them 97 | // as it will certainly lead to integrity check failure 98 | // when you'll try to check against them 99 | ``` 100 | 101 | `TypeScript` 102 | 103 | ```ts 104 | const options: IntegrityOptions = { verbose: false }; 105 | const intObj: IntegrityObject = await Integrity.create('./fileToHash.txt', options); 106 | 107 | // Do something with the integrity object here 108 | // It's advised not to modify them 109 | // as it will certainly lead to integrity check failure 110 | // when you'll try to check against them 111 | ``` 112 | 113 | --- 114 | 115 | > Create a verbosely integrity object of a subdirectory file 116 | 117 | `ES6+` 118 | 119 | ```js 120 | const intObj = await Integrity.create('./sub/fileToHash.txt') 121 | 122 | // Do something with the integrity object here 123 | // It's advised not to modify them 124 | // as it will certainly lead to integrity check failure 125 | // when you'll try to check against them 126 | ``` 127 | 128 | `TypeScript` 129 | 130 | ```ts 131 | const intObj: IntegrityObject = await Integrity.create('./sub/fileToHash.txt'); 132 | 133 | // Do something with the integrity object here 134 | // It's advised not to modify them 135 | // as it will certainly lead to integrity check failure 136 | // when you'll try to check against them 137 | ``` 138 | 139 | --- 140 | 141 | > Create a verbosely integrity object of a directory excluding a file 142 | 143 | `ES6+` 144 | 145 | ```js 146 | const options = { exclude: ['fileToExclude.txt'] } 147 | const intObj = await Integrity.create('./dir', options) 148 | 149 | // Do something with the integrity object here 150 | // It's advised not to modify them 151 | // as it will certainly lead to integrity check failure 152 | // when you'll try to check against them 153 | ``` 154 | 155 | `TypeScript` 156 | 157 | ```ts 158 | const options: IntegrityOptions = { exclude: ['fileToExclude.txt'] }; 159 | const intObj: IntegrityObject = await Integrity.create('./dir', options); 160 | 161 | // Do something with the integrity object here 162 | // It's advised not to modify them 163 | // as it will certainly lead to integrity check failure 164 | // when you'll try to check against them 165 | ``` 166 | 167 | --- 168 | 169 | > Create a verbosely integrity object of a directory excluding a subdirectory 170 | 171 | `ES6+` 172 | 173 | ```js 174 | const options = { exclude: ['sub'] } 175 | const intObj = await Integrity.create('./dir', options) 176 | 177 | // Do something with the integrity object here 178 | // It's advised not to modify them 179 | // as it will certainly lead to integrity check failure 180 | // when you'll try to check against them 181 | ``` 182 | 183 | `TypeScript` 184 | 185 | ```ts 186 | const options: IntegrityOptions = { exclude: ['sub'] }; 187 | const intObj: IntegrityObject = await Integrity.create('./dir', options); 188 | 189 | // Do something with the integrity object here 190 | // It's advised not to modify them 191 | // as it will certainly lead to integrity check failure 192 | // when you'll try to check against them 193 | ``` 194 | 195 | --- 196 | 197 | ## Using with `then/catch` 198 | 199 | All above examples can be also used with the `then/catch` coding pattern. 200 | 201 | Here is how the first example will look like: 202 | 203 | >Create a verbosely integrity object of the root directory, using the default `sha1` and `sha512` algorithm and `base64` encoding 204 | 205 | `ES6+` 206 | 207 | ```js 208 | Integrity.create('./') 209 | .then(intObj => { 210 | // Do something with the integrity object here 211 | // It's advised not to modify them 212 | // as it will certainly lead to integrity check failure 213 | // when you'll try to check against them 214 | }) 215 | .catch(error => console.error(error)) 216 | ``` 217 | 218 | `TypeScript` 219 | 220 | ```ts 221 | Integrity.create('./') 222 | .then(intObj => { 223 | // Do something with the integrity object here 224 | // It's advised not to modify them 225 | // as it will certainly lead to integrity check failure 226 | // when you'll try to check against them 227 | }) 228 | .catch(error => console.error(error)); 229 | ``` 230 | -------------------------------------------------------------------------------- /docs/examples/createDirHash-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createDirHash` (ECMAScript 5) 4 | 5 | >Create a verbosely integrity object of the root directory, using the default `md5` algorithm and `hex` encoding 6 | 7 | ```js 8 | Integrity.createDirHash('./') 9 | .then(hashes => { 10 | // Do something with the hashes here 11 | // It's advised not to modify them 12 | // as it will certainly lead to integrity check failure 13 | // when you'll try to check against them 14 | }) 15 | .catch(error => console.error(error)) 16 | ``` 17 | 18 | --- 19 | 20 | > Create a verbosely integrity object of the root directory, using the `sha1` algorithm and `base64` encoding 21 | 22 | ```js 23 | var options = { cryptoOptions: { algorithm: 'sha1', encoding: 'base64' } } 24 | Integrity.createDirHash('./', options) 25 | .then(hashes => { 26 | // Do something with the hashes here 27 | // It's advised not to modify them 28 | // as it will certainly lead to integrity check failure 29 | // when you'll try to check against them 30 | }) 31 | .catch(error => console.error(error)) 32 | ``` 33 | 34 | --- 35 | 36 | > Create a verbosely integrity object of a subdirectory 37 | 38 | ```js 39 | Integrity.createDirHash('./sub') 40 | .then(hashes => { 41 | // Do something with the hashes here 42 | // It's advised not to modify them 43 | // as it will certainly lead to integrity check failure 44 | // when you'll try to check against them 45 | }) 46 | .catch(error => console.error(error)) 47 | ``` 48 | 49 | --- 50 | 51 | > Create a verbosely integrity object of a directory excluding a file 52 | 53 | ```js 54 | var options = { exclude: ['fileToExclude.txt'] } 55 | Integrity.createDirHash('./dir', options) 56 | .then(hashes => { 57 | // Do something with the hashes here 58 | // It's advised not to modify them 59 | // as it will certainly lead to integrity check failure 60 | // when you'll try to check against them 61 | }) 62 | .catch(error => console.error(error)) 63 | ``` 64 | 65 | --- 66 | 67 | > Create a verbosely integrity object of a directory excluding a subdirectory 68 | 69 | ```js 70 | var options = { exclude: ['sub'] } 71 | Integrity.createDirHash('./dir', options) 72 | .then(hashes => { 73 | // Do something with the hashes here 74 | // It's advised not to modify them 75 | // as it will certainly lead to integrity check failure 76 | // when you'll try to check against them 77 | }) 78 | .catch(error => console.error(error)) 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/examples/createDirHash-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createDirHash` (ECMAScript 6+, TypeScript) 4 | 5 | > Create a verbosely integrity object of the root directory, using the default `md5` algorithm and `hex` encoding 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const hashes = await Integrity.createDirHash('./') 11 | 12 | // Do something with the hashes here 13 | // It's advised not to modify them 14 | // as it will certainly lead to integrity check failure 15 | // when you'll try to check against them 16 | ``` 17 | 18 | `TypeScript` 19 | 20 | ```ts 21 | const hashes: IHashObject = await Integrity.createDirHash('./'); 22 | 23 | // Do something with the hashes here 24 | // It's advised not to modify them 25 | // as it will certainly lead to integrity check failure 26 | // when you'll try to check against them 27 | ``` 28 | 29 | --- 30 | 31 | > Create a verbosely integrity object of the root directory, using the `sha1` algorithm and `base64` encoding 32 | 33 | `ES6+` 34 | 35 | ```js 36 | const options = { cryptoOptions: { algorithm: 'sha1', encoding: 'base64' } } 37 | const hashes = await Integrity.createDirHash('./', options) 38 | 39 | // Do something with the hashes here 40 | // It's advised not to modify them 41 | // as it will certainly lead to integrity check failure 42 | // when you'll try to check against them 43 | ``` 44 | 45 | `TypeScript` 46 | 47 | ```ts 48 | const options: IntegrityOptions = { cryptoOptions: { algorithm: 'sha1', encoding: 'base64' } }; 49 | const hashes: IHashObject = await Integrity.createDirHash('./', options); 50 | 51 | // Do something with the hashes here 52 | // It's advised not to modify them 53 | // as it will certainly lead to integrity check failure 54 | // when you'll try to check against them 55 | ``` 56 | 57 | --- 58 | 59 | > Create a verbosely integrity object of a subdirectory 60 | 61 | `ES6+` 62 | 63 | ```ts 64 | const hashes = await Integrity.createDirHash('./sub') 65 | 66 | // Do something with the hashes here 67 | // It's advised not to modify them 68 | // as it will certainly lead to integrity check failure 69 | // when you'll try to check against them 70 | ``` 71 | 72 | `TypeScript` 73 | 74 | ```ts 75 | const hashes: IHashObject = await Integrity.createDirHash('./sub'); 76 | 77 | // Do something with the hashes here 78 | // It's advised not to modify them 79 | // as it will certainly lead to integrity check failure 80 | // when you'll try to check against them 81 | ``` 82 | 83 | --- 84 | 85 | > Create a verbosely integrity object of a directory excluding a file 86 | 87 | `ES6+` 88 | 89 | ```js 90 | const options = { exclude: ['fileToExclude.txt'] } 91 | const hashes = await Integrity.createDirHash('./dir', options) 92 | 93 | // Do something with the hashes here 94 | // It's advised not to modify them 95 | // as it will certainly lead to integrity check failure 96 | // when you'll try to check against them 97 | ``` 98 | 99 | `TypeScript` 100 | 101 | ```ts 102 | const options: IntegrityOptions = { exclude: ['fileToExclude.txt'] }; 103 | const hashes: IHashObject = await Integrity.createDirHash('./dir', options); 104 | 105 | // Do something with the hashes here 106 | // It's advised not to modify them 107 | // as it will certainly lead to integrity check failure 108 | // when you'll try to check against them 109 | ``` 110 | 111 | --- 112 | 113 | > Create a verbosely integrity object of a directory excluding a subdirectory 114 | 115 | `ES6+` 116 | 117 | ```js 118 | const options = { exclude: ['sub'] } 119 | const hashes = await Integrity.createDirHash('./dir', options) 120 | 121 | // Do something with the hashes here 122 | // It's advised not to modify them 123 | // as it will certainly lead to integrity check failure 124 | // when you'll try to check against them 125 | ``` 126 | 127 | `TypeScript` 128 | 129 | ```ts 130 | const options: IntegrityOptions = { exclude: ['sub'] }; 131 | const hashes: IHashObject = await Integrity.createDirHash('./dir', options); 132 | 133 | // Do something with the hashes here 134 | // It's advised not to modify them 135 | // as it will certainly lead to integrity check failure 136 | // when you'll try to check against them 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/examples/createFileHash-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createFileHash` (ECMAScript 5) 4 | 5 | > Create a verbosely integrity object of a file 6 | 7 | ```js 8 | Integrity.createFileHash('/path/to/fileToHash.txt') 9 | .then(hashes => { 10 | // Do something with the hashes here 11 | // It's advised not to modify them 12 | // as it will certainly lead to integrity check failure 13 | // when you'll try to check against them 14 | }) 15 | .catch(error => console.error(error)) 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/examples/createFileHash-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createFileHash` (ECMAScript 6+, TypeScript) 4 | 5 | > Create a verbosely integrity object of a file 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const hashes = await Integrity.createFileHash('/path/to/fileToHash.txt') 11 | 12 | // Do something with the hashes here 13 | // It's advised not to modify them 14 | // as it will certainly lead to integrity check failure 15 | // when you'll try to check against them 16 | ``` 17 | 18 | `TypeScript` 19 | 20 | ```ts 21 | const hashes: IHashObject = await Integrity.createFileHash('/path/to/fileToHash.txt'); 22 | 23 | // Do something with the hashes here 24 | // It's advised not to modify them 25 | // as it will certainly lead to integrity check failure 26 | // when you'll try to check against them 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/examples/createFilesHash-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createFilesHash` (ECMAScript 5) 4 | 5 | > Create a verbosely integrity object of a list of files 6 | 7 | ```js 8 | const listOfFiles = ['/path/to/file1.txt', './path/to/file2.txt'] 9 | Integrity.createFilesHash(listOfFiles) 10 | .then(hashes => { 11 | // Do something with the hashes here 12 | // It's advised not to modify them 13 | // as it will certainly lead to integrity check failure 14 | // when you'll try to check against them 15 | }) 16 | .catch(error => console.error(error)) 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/examples/createFilesHash-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.createFilesHash` (ECMAScript 6+, TypeScript) 4 | 5 | > Create a verbosely integrity object of a list of files 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const listOfFiles = ['/path/to/file1.txt', './path/to/file2.txt'] 11 | const hashes = await Integrity.createFilesHash(listOfFiles) 12 | 13 | // Do something with the hashes here 14 | // It's advised not to modify them 15 | // as it will certainly lead to integrity check failure 16 | // when you'll try to check against them 17 | ``` 18 | 19 | `TypeScript` 20 | 21 | ```ts 22 | const listOfFiles = ['/path/to/file1.txt', './path/to/file2.txt']; 23 | const hashes: IHashObject = await Integrity.createFilesHash(listOfFiles); 24 | 25 | // Do something with the hashes here 26 | // It's advised not to modify them 27 | // as it will certainly lead to integrity check failure 28 | // when you'll try to check against them 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/examples/getExclusionsFromIgnoreFile-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getExclusionsFromIgnoreFile` (ECMAScript 5) 4 | 5 | > Get the exclusions from a `.nsriignore` file 6 | 7 | ```js 8 | Integrity.getExclusionsFromIgnoreFile() 9 | .then((excl) => console.log(excl)) 10 | .catch(error => console.error(error)) 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/examples/getExclusionsFromIgnoreFile-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getExclusionsFromIgnoreFile` (ECMAScript 6+, TypeScript) 4 | 5 | > Get the exclusions from a `.nsriignore` file 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const exclusions = await Integrity.getExclusionsFromIgnoreFile() 11 | console.log(exclusions) 12 | ``` 13 | 14 | `TypeScript` 15 | 16 | ```ts 17 | const exclusions: string[] = await Integrity.getExclusionsFromIgnoreFile(); 18 | console.log(exclusions); 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/examples/getIntegrityOptionsFromConfig-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getIntegrityOptionsFromConfig` (ECMAScript 5) 4 | 5 | > Get the integrity options from a `cosmiconfig` compatible section 6 | 7 | ```js 8 | Integrity.getIntegrityOptionsFromConfig() 9 | .then((options) => console.log(options)) 10 | .catch(error => console.error(error)) 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/examples/getIntegrityOptionsFromConfig-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getIntegrityOptionsFromConfig` (ECMAScript 6+, TypeScript) 4 | 5 | > Get the integrity options from a `cosmiconfig` compatible section 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const options = await Integrity.getIntegrityOptionsFromConfig() 11 | console.log(options) 12 | ``` 13 | 14 | `TypeScript` 15 | 16 | ```ts 17 | const options: IntegrityOptions = await Integrity.getIntegrityOptionsFromConfig(); 18 | console.log(options); 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/examples/getManifestIntegrity-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getManifestIntegrity` (ECMAScript 5) 4 | 5 | > Get the integrity object from the manifest file 6 | 7 | ```js 8 | Integrity.getManifestIntegrity() 9 | .then((intObj) => console.log(intObj)) 10 | .catch(error => console.error(error)) 11 | ``` 12 | -------------------------------------------------------------------------------- /docs/examples/getManifestIntegrity-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.getManifestIntegrity` (ECMAScript 6+, TypeScript) 4 | 5 | > Get the integrity object from the manifest file 6 | 7 | `ES6+` 8 | 9 | ```js 10 | const integrity = await Integrity.getManifestIntegrity() 11 | console.log(integrity) 12 | ``` 13 | 14 | `TypeScript` 15 | 16 | ```ts 17 | const integrity: string = await Integrity.getManifestIntegrity(); 18 | console.log(integrity); 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/examples/persist-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.persist` (ECMAScript 5) 4 | 5 | > Persist the integrity object on the current working directory 6 | 7 | ```js 8 | // Assuming you have previously created an integrity object 9 | 10 | // Persist it on disk 11 | Integrity.persist(intObj) 12 | .then(() => console.log('Integrity file saved')) 13 | .catch(error => console.error(error)) 14 | ``` 15 | 16 | --- 17 | 18 | > Persist the integrity object on a specific directory (absolute path) 19 | 20 | ```js 21 | // Assuming you have previously created an integrity object 22 | 23 | // Persist it on disk 24 | Integrity.persist(intObj, '/dir/to/persist/the/integrity/object') 25 | .then(() => console.log('Integrity file saved')) 26 | .catch(error => console.error(error)) 27 | ``` 28 | 29 | --- 30 | 31 | > Persist the integrity object on a specific directory (relative path) 32 | 33 | ```js 34 | // Assuming you have previously created an integrity object 35 | 36 | // Persist it on disk 37 | Integrity.persist(intObj, './dir/to/persist/the/integrity/object') 38 | .then(() => console.log('Integrity file saved')) 39 | .catch(error => console.error(error)) 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/examples/persist-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.persist` (ECMAScript 6+, TypeScript) 4 | 5 | > Persist the integrity object on the current working directory 6 | 7 | `ES6+` 8 | 9 | ```js 10 | // Assuming you have previously created an integrity object 11 | 12 | // Persist it on disk 13 | await Integrity.persist(intObj) 14 | console.log('Integrity file saved') 15 | ``` 16 | 17 | `TypeScript` 18 | 19 | ```ts 20 | // Assuming you have previously created an integrity object 21 | 22 | // Persist it on disk 23 | await Integrity.persist(intObj); 24 | console.log('Integrity file saved'); 25 | ``` 26 | 27 | --- 28 | 29 | `ES6+` 30 | 31 | > Persist the integrity object on a specific directory (absolute path) 32 | 33 | ```js 34 | // Assuming you have previously created an integrity object 35 | 36 | // Persist it on disk 37 | await Integrity.persist(intObj, '/dir/to/persist/the/integrity/object') 38 | console.log('Integrity file saved') 39 | ``` 40 | 41 | `TypeScript` 42 | 43 | ```ts 44 | // Assuming you have previously created an integrity object 45 | 46 | // Persist it on disk 47 | await Integrity.persist(intObj, '/dir/to/persist/the/integrity/object'); 48 | console.log('Integrity file saved'); 49 | ``` 50 | 51 | --- 52 | 53 | > Persist the integrity object on a specific directory (relative path) 54 | 55 | `ES6+` 56 | 57 | ```js 58 | // Assuming you have previously created an integrity object 59 | 60 | // Persist it on disk 61 | await Integrity.persist(intObj, './dir/to/persist/the/integrity/object') 62 | console.log('Integrity file saved') 63 | ``` 64 | 65 | `TypeScript` 66 | 67 | ```ts 68 | // Assuming you have previously created an integrity object 69 | 70 | // Persist it on disk 71 | await Integrity.persist(intObj, './dir/to/persist/the/integrity/object'); 72 | console.log('Integrity file saved'); 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/examples/updateManifestIntegrity-es5-js.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.updateManifestIntegrity` (ECMAScript 5) 4 | 5 | > Update the integrity object on the manifest file 6 | 7 | ```js 8 | // Assuming you have previously created an integrity object 9 | 10 | // Persist it on the manifest file 11 | Integrity.updateManifestIntegrity(intObj) 12 | .then(() => console.log('Integrity hash created -> Manifest updated')) 13 | .catch(error => console.error(error)) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/examples/updateManifestIntegrity-js-ts.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## `.updateManifestIntegrity` (ECMAScript 6+, TypeScript) 4 | 5 | > Update the integrity object on the manifest file 6 | 7 | `ES6+` 8 | 9 | ```js 10 | // Assuming you have previously created an integrity object 11 | 12 | // Persist it on the manifest file 13 | await Integrity.updateManifestIntegrity(intObj) 14 | console.log('Integrity hash created -> Manifest updated') 15 | ``` 16 | 17 | `TypeScript` 18 | 19 | ```ts 20 | // Assuming you have previously created an integrity object 21 | 22 | // Persist it on the manifest file 23 | await Integrity.updateManifestIntegrity(intObj); 24 | console.log('Integrity hash created -> Manifest updated'); 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/importation.md: -------------------------------------------------------------------------------- 1 | # Importation 2 | 3 | ## ES5 4 | 5 | **Import as a `Class`** 6 | 7 | ```js 8 | var Integrity = require('nsri').Integrity; 9 | ``` 10 | 11 | **Import as a `Namespace`** 12 | 13 | ```js 14 | var nsri = require('nsri'); 15 | 16 | // and use as 17 | nsri.Integrity. ... 18 | ``` 19 | 20 | ## ES6+, Typescript 21 | 22 | **Import as a `Class`** 23 | 24 | All examples require to import the `Integrity` class before you will be able to use them. 25 | 26 | Additionally `CryptoOptions`, `IntegrityOptions`, `IntegrityObject`, `HashObject` and `VerboseHashObject` are also available types. 27 | 28 | ```ts 29 | import { Integrity } from 'nsri'; 30 | ``` 31 | 32 | **Import as a `Namespace`** 33 | 34 | You can also import it as a namespace. 35 | 36 | ```ts 37 | import * as nsri from 'nsri'; 38 | ``` 39 | 40 | In that case, all function calls should be modified to use `nsri` before the classes or types. 41 | 42 | ```ts 43 | nsri.Integrity. ... 44 | 45 | nsri.ICryptoOptions. ... 46 | 47 | nsri.IntegrityObject. ... 48 | 49 | nsri.IntegrityOptions. ... 50 | ``` 51 | -------------------------------------------------------------------------------- /media/integrity_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimiC/nsri/8df3c54a8bcc909d6e130eab95c131b75371c2e2/media/integrity_file.png -------------------------------------------------------------------------------- /media/integrity_file.svg: -------------------------------------------------------------------------------- 1 | integrity_file -------------------------------------------------------------------------------- /media/nsri-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimiC/nsri/8df3c54a8bcc909d6e130eab95c131b75371c2e2/media/nsri-logo.png -------------------------------------------------------------------------------- /media/nsri-logo.svg: -------------------------------------------------------------------------------- 1 | nsri -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nsri", 3 | "version": "8.0.0", 4 | "description": "Node.js utility tool for creating and checking subresource integrity", 5 | "license": "MIT", 6 | "author": { 7 | "email": "jimikar@gmail.com", 8 | "name": "Jimi (Dimitris) Charalampidis" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "JimiC/nsri" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/JimiC/nsri/issues" 16 | }, 17 | "homepage": "https://github.com/JimiC/nsri#readme", 18 | "engines": { 19 | "node": ">=18" 20 | }, 21 | "keywords": [ 22 | "node", 23 | "nodejs", 24 | "cli", 25 | "integrity", 26 | "checker", 27 | "check", 28 | "validation", 29 | "file", 30 | "files", 31 | "folder", 32 | "forders", 33 | "directory", 34 | "directories", 35 | "structure", 36 | "hash", 37 | "checksum", 38 | "checksums", 39 | "nsri" 40 | ], 41 | "main": "lib/index.js", 42 | "module": "lib/index.es.js", 43 | "bin": { 44 | "nsri": "lib/cli.js" 45 | }, 46 | "files": [ 47 | "lib" 48 | ], 49 | "types": "lib/nsri.d.ts", 50 | "scripts": { 51 | "reinstall": "rimraf ./package-lock.json ./node_modules && npm i", 52 | "start": "node ./out/src/cli.js", 53 | "lint": "eslint --ext .ts .", 54 | "prebuild:dev": "npm run lint", 55 | "build:dev": "npm run build -- -p tsconfig.dev.json", 56 | "prebuild": "npm run cleanup", 57 | "build": "tsc", 58 | "cleanup": "rimraf ./.nyc_output ./coverage ./out ./lib ./dist", 59 | "pretest": "npm run build:dev", 60 | "test": "nyc mocha", 61 | "posttest": "nyc report -r lcov", 62 | "prepublishOnly": "npm run cleanup && npx rollup -c --bundleConfigAsCjs" 63 | }, 64 | "dependencies": { 65 | "ajv": "^8.12.0", 66 | "cosmiconfig": "^8.2.0", 67 | "detect-indent": "^6.1.0", 68 | "minimatch": "^9.0.3", 69 | "yargs": "^17.7.2" 70 | }, 71 | "devDependencies": { 72 | "@microsoft/api-extractor": "^7.36.3", 73 | "@rollup/plugin-terser": "^0.4.3", 74 | "@rollup/plugin-typescript": "^11.1.2", 75 | "@types/chai": "^4.3.5", 76 | "@types/mocha": "^10.0.1", 77 | "@types/node": "18", 78 | "@types/sinon": "^10.0.16", 79 | "@types/yargs": "^17.0.24", 80 | "@typescript-eslint/eslint-plugin": "^6.2.1", 81 | "@typescript-eslint/parser": "^6.2.1", 82 | "builtin-modules": "^3.3.0", 83 | "chai": "^4.3.7", 84 | "eslint": "^8.46.0", 85 | "eslint-plugin-import": "^2.28.0", 86 | "husky": "^8.0.3", 87 | "lint-staged": "^13.2.3", 88 | "mocha": "^10.2.0", 89 | "nyc": "^15.1.0", 90 | "rimraf": "^5.0.1", 91 | "rollup": "^3.27.1", 92 | "rollup-plugin-api-extractor": "0.2.5", 93 | "rollup-plugin-copy": "^3.4.0", 94 | "rollup-plugin-preserve-shebangs": "^0.2.0", 95 | "sinon": "^15.2.0", 96 | "tslib": "^2.6.1", 97 | "typescript": "^5.1.6" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | import ts from '@rollup/plugin-typescript'; 3 | import builtins from 'builtin-modules'; 4 | import { apiExtractor } from "rollup-plugin-api-extractor"; 5 | import copy from 'rollup-plugin-copy'; 6 | import { preserveShebangs } from 'rollup-plugin-preserve-shebangs'; 7 | import typescript from 'typescript'; 8 | import pkg from './package.json'; 9 | 10 | export default [ 11 | { 12 | input: 'src/index.ts', 13 | output: [ 14 | { 15 | dir: 'dist', 16 | entryFileNames: pkg.main, 17 | format: 'cjs', 18 | }, 19 | { 20 | dir: 'dist', 21 | entryFileNames: pkg.module, 22 | format: 'es', 23 | }, 24 | ], 25 | external: [ 26 | ...Object.keys(pkg.dependencies || {}), 27 | ...Object.keys(pkg.peerDependencies || {}), 28 | ...builtins, 29 | ], 30 | plugins: [ 31 | ts({ 32 | typescript, 33 | }), 34 | terser(), 35 | copy({ 36 | targets: [ 37 | { src: 'src/app/schemas', dest: 'lib' }, 38 | { src: 'dist/lib', dest: '.' }, 39 | ], 40 | copyOnce: true, 41 | hook: 'writeBundle' 42 | }), 43 | apiExtractor({ 44 | configuration: { 45 | projectFolder: ".", 46 | }, 47 | configFile: "./api-extractor.json", 48 | cleanUpRollup: false 49 | }) 50 | ], 51 | }, 52 | { 53 | input: 'src/cli.ts', 54 | output: [ 55 | { 56 | dir: 'dist', 57 | entryFileNames: pkg.bin.nsri, 58 | format: 'cjs', 59 | } 60 | ], 61 | external: [ 62 | ...Object.keys(pkg.dependencies || {}), 63 | ...Object.keys(pkg.peerDependencies || {}), 64 | ...builtins, 65 | ], 66 | plugins: [ 67 | ts({ 68 | typescript, 69 | }), 70 | preserveShebangs(), 71 | terser(), 72 | copy({ 73 | targets: [ 74 | { src: 'src/app/schemas', dest: 'lib' }, 75 | { src: 'dist/lib', dest: '.' }, 76 | ], 77 | copyOnce: true, 78 | hook: 'writeBundle' 79 | }), 80 | apiExtractor({ 81 | configuration: { 82 | projectFolder: ".", 83 | }, 84 | configFile: "./api-extractor.json", 85 | cleanUpRollup: false 86 | }) 87 | ], 88 | } 89 | ] 90 | -------------------------------------------------------------------------------- /src/abstractions/baseLogger.ts: -------------------------------------------------------------------------------- 1 | import { Spinner } from '../interfaces/spinner'; 2 | 3 | /** @internal */ 4 | export abstract class BaseLogger { 5 | public abstract log(...args: unknown[]): void; 6 | 7 | public abstract error(...args: unknown[]): void; 8 | 9 | public abstract updateLog(...args: unknown[]): void; 10 | 11 | public abstract spinnerLogStart(...args: unknown[]): Spinner; 12 | 13 | public abstract spinnerLogStop(spinner: Spinner, ...args: unknown[]): void; 14 | } 15 | -------------------------------------------------------------------------------- /src/app/schemas/v1/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "title": "Integrity", 4 | "description": "Integrity object schema", 5 | "definitions": { 6 | "entity": { 7 | "title": "The contents or computed hash of the directory", 8 | "type": [ 9 | "object", 10 | "string" 11 | ], 12 | "properties": { 13 | "hash": { 14 | "$ref": "#/definitions/hash" 15 | }, 16 | "contents": { 17 | "$ref": "#/definitions/hashes" 18 | }, 19 | "additionalProperties": false 20 | }, 21 | "additionalProperties": false, 22 | "required": [ 23 | "hash", 24 | "contents" 25 | ] 26 | }, 27 | "hash": { 28 | "title": "The computed hash of the directory", 29 | "type": "string" 30 | }, 31 | "hashes": { 32 | "title": "The computed hashes", 33 | "type": "object", 34 | "patternProperties": { 35 | "^[\\w\\-.]+$": { 36 | "$ref": "#/definitions/entity" 37 | } 38 | }, 39 | "additionalProperties": false 40 | } 41 | }, 42 | "type": "object", 43 | "properties": { 44 | "version": { 45 | "title": "The schema version", 46 | "type": "string", 47 | "pattern": "^[0-9](?:\\.[0-9])?$" 48 | }, 49 | "hashes": { 50 | "$ref": "#/definitions/hashes" 51 | }, 52 | "additionalProperties": false 53 | }, 54 | "required": [ 55 | "version", 56 | "hashes" 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import cli from './cli/index'; 3 | 4 | void cli(); 5 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | import { BinaryToTextEncoding } from 'crypto'; 2 | import { Integrity, IntegrityObject, IntegrityOptions } from '../'; 3 | import { ConfigExplorer } from '../common/configExplorer'; 4 | import { Logger } from '../common/logger'; 5 | import { normalizeEntries, unique } from '../common/utils'; 6 | import { YargsParser } from '../common/yargsParser'; 7 | import { ParsedArgs } from '../interfaces/parsedArgs'; 8 | import { Spinner } from '../interfaces/spinner'; 9 | 10 | /** @internal */ 11 | export default (async (): Promise => { 12 | const id = 'nsri'; 13 | const logger = new Logger(); 14 | logger.eventEmitter.on('SIGINT', (): void => logger.handleForcedExit(!!logger)); 15 | let spinner: Spinner = { timer: setInterval((): void => void 0), line: 1 }; 16 | let command = ''; 17 | let message = ''; 18 | try { 19 | await new ConfigExplorer().assignArgs(); 20 | const parser = new YargsParser(); 21 | const pargs: ParsedArgs = await parser.parse(); 22 | let exclusions: string[] = await Integrity.getExclusionsFromIgnoreFile(); 23 | exclusions = unique([...exclusions, ...normalizeEntries(pargs.exclude)]); 24 | const options: IntegrityOptions = { 25 | cryptoOptions: { 26 | dirAlgorithm: pargs.dirAlgorithm, 27 | encoding: pargs.encoding as BinaryToTextEncoding, 28 | fileAlgorithm: pargs.fileAlgorithm, 29 | }, 30 | exclude: exclusions, 31 | strict: pargs.strict, 32 | verbose: pargs.verbose, 33 | }; 34 | command = pargs.command; 35 | if (command === 'create') { 36 | spinner = logger.spinnerLogStart('Creating integrity hash', id); 37 | const intObj: IntegrityObject = await Integrity.create(pargs.inPath, options); 38 | if (!pargs.manifest) { 39 | await Integrity.persist(intObj, pargs.outPath, pargs.pretty); 40 | message = 'Integrity hash file created'; 41 | } else { 42 | await Integrity.updateManifestIntegrity(intObj); 43 | message = 'Integrity hash created -> Manifest updated'; 44 | } 45 | } 46 | if (command === 'check') { 47 | spinner = logger.spinnerLogStart(`Checking integrity of: '${pargs.inPath}'`, id); 48 | const integrity: string = pargs.manifest ? await Integrity.getManifestIntegrity() : pargs.integrity; 49 | const passed: boolean = await Integrity.check(pargs.inPath, integrity, options); 50 | message = `Integrity ${passed ? 'validated' : 'check failed'}`; 51 | } 52 | logger.spinnerLogStop(spinner, message, id); 53 | } catch (error) { 54 | const err = error as Error; 55 | logger.spinnerLogStop(spinner, `Failed to ${command} integrity hash`, id); 56 | logger.updateLog(`Error: ${err.message || err.toString()}`); 57 | } finally { 58 | process.exit(); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /src/common/configExplorer.ts: -------------------------------------------------------------------------------- 1 | import { cosmiconfig } from 'cosmiconfig'; 2 | import { ConfigOptions } from '../interfaces/configOptions'; 3 | 4 | /** @internal */ 5 | type Explorer = ReturnType; 6 | 7 | /** @internal */ 8 | export class ConfigExplorer { 9 | private explorer: Explorer; 10 | public constructor() { 11 | this.explorer = cosmiconfig('nsri'); 12 | } 13 | 14 | public async assignArgs(): Promise { 15 | const config = await this.getConfig(); 16 | if (!Object.keys(config).length) { 17 | return Promise.resolve(); 18 | } 19 | if (!this.existsArg(['-m', 'manifest']) && config.manifest !== undefined) { 20 | process.argv.push('-m', config.manifest.toString()); 21 | } 22 | if (!this.existsArg(['-s', 'source']) && config.source) { 23 | process.argv.push('-s', config.source); 24 | } 25 | if (!this.existsArg(['-v', 'verbose']) && config.verbose !== undefined) { 26 | process.argv.push('-v', config.verbose.toString()); 27 | } 28 | if (!this.existsArg(['-st', 'strict']) && config.strict !== undefined) { 29 | process.argv.push('-st', config.strict.toString()); 30 | } 31 | if (!this.existsArg(['-da', 'diralgorithm']) && config.cryptoOptions?.dirAlgorithm) { 32 | process.argv.push('-da', config.cryptoOptions.dirAlgorithm); 33 | } 34 | if (!this.existsArg(['-fa', 'filealgorithm']) && config.cryptoOptions?.fileAlgorithm) { 35 | process.argv.push('-fa', config.cryptoOptions.fileAlgorithm); 36 | } 37 | if (!this.existsArg(['-e', 'encoding']) && config.cryptoOptions?.encoding) { 38 | process.argv.push('-e', config.cryptoOptions.encoding); 39 | } 40 | if (!this.existsArg(['-x', 'exclude']) && config.exclude) { 41 | process.argv.push('-x', ...config.exclude); 42 | } 43 | if (!this.existsArg(['-i', 'integrity']) && config.integrity) { 44 | process.argv.push('-i', config.integrity); 45 | } 46 | if (!this.existsArg(['-o', 'output']) && config.output) { 47 | process.argv.push('-o', config.output); 48 | } 49 | } 50 | 51 | public async getConfig(fromPath?: string): Promise { 52 | if (!this.explorer) { 53 | return Promise.reject(new Error('CosmiConfig not initialized')); 54 | } 55 | this.explorer.clearSearchCache(); 56 | const result = await this.explorer.search(fromPath); 57 | return (result ? result.config : {}) as ConfigOptions; 58 | } 59 | 60 | private existsArg(args: string[]): boolean { 61 | return args.some((arg: string): boolean => 62 | process.argv.some((argv: string): boolean => argv === arg)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | import { BinaryToTextEncoding } from 'crypto'; 2 | import { CryptoEncoding } from './enums'; 3 | 4 | /** @internal */ 5 | export const integrityFilename = '.integrity.json'; 6 | 7 | /** @internal */ 8 | export const manifestFile = 'package.json'; 9 | 10 | /** @internal */ 11 | export const defaultFileCryptoAlgorithm = 'sha1'; 12 | 13 | /** @internal */ 14 | export const defaultDirCryptoAlgorithm = 'sha512'; 15 | 16 | /** @internal */ 17 | export const defaultCryptoEncoding: BinaryToTextEncoding = CryptoEncoding.base64; 18 | 19 | /** @internal */ 20 | export const ignoreFile = '.nsriignore'; 21 | 22 | /** @internal */ 23 | export const defaultExclusions = [ 24 | `**/${integrityFilename}`, 25 | '**/.git*', 26 | '**/.hg*', 27 | '**/.svn*', 28 | '**/node_modules/**', 29 | ]; 30 | -------------------------------------------------------------------------------- /src/common/enums.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export enum CryptoEncoding { 3 | hex = 'hex', 4 | base64 = 'base64', 5 | base64url = 'base64url', 6 | } 7 | -------------------------------------------------------------------------------- /src/common/fsAsync.ts: -------------------------------------------------------------------------------- 1 | import { exists, lstat, readdir, readFile, writeFile } from 'fs'; 2 | import { promisify } from 'util'; 3 | 4 | /** @internal */ 5 | export const existsAsync = promisify(exists); 6 | 7 | /** @internal */ 8 | export const lstatAsync = promisify(lstat); 9 | 10 | /** @internal */ 11 | export const readdirAsync = promisify(readdir); 12 | 13 | /** @internal */ 14 | export const readFileAsync = promisify(readFile); 15 | 16 | /** @internal */ 17 | export const writeFileAsync = promisify(writeFile); 18 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import { clearLine, createInterface, cursorTo, moveCursor, ReadLine } from 'readline'; 2 | import { BaseLogger } from '../abstractions/baseLogger'; 3 | import { Spinner } from '../interfaces/spinner'; 4 | 5 | /** @internal */ 6 | export class Logger extends BaseLogger { 7 | 8 | public eventEmitter: ReadLine; 9 | public frames: string[]; 10 | public showSpinnerInFront: boolean; 11 | public spinnerInterval: number; 12 | 13 | private countLines: number; 14 | 15 | public constructor() { 16 | super(); 17 | this.eventEmitter = createInterface(process.stdin, process.stdout); 18 | this.frames = ['- ', '\\ ', '| ', '/ ']; 19 | this.showSpinnerInFront = true; 20 | this.spinnerInterval = 80; 21 | this.countLines = 1; 22 | } 23 | 24 | public log(message: string, groupId?: string): void { 25 | process.stdout.write(`${this.getHeader(groupId)}${message}\n`); 26 | this.countLines++; 27 | } 28 | 29 | public error(message: string, groupId?: string): void { 30 | process.stderr.write(`${this.getHeader(groupId)}${message}\n`); 31 | this.countLines++; 32 | } 33 | 34 | public updateLog(message: string, groupId?: string): void; 35 | public updateLog(message: string, line?: number, groupId?: string): void; 36 | public updateLog(message: string, lineOrGroupId?: number | string, groupId?: string): void { 37 | groupId = (typeof lineOrGroupId === 'string' && Number.isNaN(Number.parseInt(lineOrGroupId, 10))) 38 | ? lineOrGroupId 39 | : groupId; 40 | 41 | if (!process.stdout.isTTY) { 42 | process.stdout.write(`${this.getHeader(groupId)}${message}\n`); 43 | return; 44 | } 45 | 46 | const line = (typeof lineOrGroupId === 'number' && !Number.isNaN(lineOrGroupId)) 47 | ? lineOrGroupId 48 | : 1; 49 | this.moveCursorTo(-line); 50 | clearLine(process.stdout, 0); 51 | process.stdout.write(`${this.getHeader(groupId)}${message}`); 52 | this.moveCursorTo(line); 53 | } 54 | 55 | public spinnerLogStart(message: string, groupId?: string): Spinner { 56 | const line = this.countLines; 57 | this.log(message, groupId); 58 | return { timer: this.spin(message, groupId, line), line }; 59 | } 60 | 61 | public spinnerLogStop(spinner: Spinner, message: string, groupId?: string): void { 62 | clearInterval(spinner.timer); 63 | this.updateLog(message, this.countLines - spinner.line, groupId); 64 | if (!process.stdout.isTTY) { 65 | return; 66 | } 67 | this.cursorShow(); 68 | } 69 | 70 | public handleForcedExit(hasInfoLogging: boolean): void { 71 | if (!process.stdout.isTTY) { 72 | return process.exit(); 73 | } 74 | const moveAndClear = (): void => { 75 | this.moveCursorTo(-1); 76 | clearLine(process.stdout, 0); 77 | }; 78 | clearLine(process.stdout, 0); 79 | this.updateLog(''); 80 | if (hasInfoLogging) { 81 | this.updateLog('', 2); 82 | moveAndClear(); 83 | } 84 | moveAndClear(); 85 | this.cursorShow(); 86 | process.exit(); 87 | } 88 | 89 | public moveCursorTo(line: number): void { 90 | if (!process.stdout.isTTY) { 91 | return; 92 | } 93 | cursorTo(process.stdout, 0); 94 | moveCursor(process.stdout, 0, line); 95 | } 96 | 97 | private spin(message: string, groupId: string | undefined, line: number): NodeJS.Timer { 98 | if (!process.stdout.isTTY) { 99 | return setInterval((): void => void 0); 100 | } 101 | let index = 0; 102 | this.cursorHide(); 103 | const iteration = (): void => { 104 | const frame = this.frames[index = ++index % this.frames.length]; 105 | const msg = this.showSpinnerInFront 106 | ? `${this.getHeader(groupId)}${frame}${message}` 107 | : `${this.getHeader(groupId)}${message}${frame}`; 108 | this.updateLog(msg, this.countLines - line); 109 | }; 110 | iteration(); 111 | return setInterval(iteration, this.spinnerInterval); 112 | } 113 | 114 | private cursorShow(): void { 115 | process.stdout.write('\u001B[?25h'); 116 | } 117 | 118 | private cursorHide(): void { 119 | process.stdout.write('\u001B[?25l'); 120 | } 121 | 122 | private getHeader(groupId?: string): string { 123 | return groupId ? `[${groupId}]: ` : ''; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/common/utils.ts: -------------------------------------------------------------------------------- 1 | import { getHashes } from 'crypto'; 2 | import detectIndent from 'detect-indent'; 3 | 4 | /** @internal */ 5 | export const hexRegexPattern = /^(?:[a-f0-9])+$/; 6 | 7 | /** @internal */ 8 | export const base64RegexPattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; 9 | 10 | /** @internal */ 11 | export const base64urlRegexPattern = /^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}|[A-Za-z0-9-_]{3})?$/; 12 | 13 | /** @internal */ 14 | export function isSupportedHash(algorithm: string): boolean { 15 | return getHashes().some((hash: string): boolean => hash.toUpperCase() === algorithm.toUpperCase()); 16 | } 17 | 18 | /** @internal */ 19 | export function parseJSONSafe(data: string | Buffer): T { 20 | try { 21 | const text = Buffer.isBuffer(data) ? data.toString() : data; 22 | return JSON.parse(text) as T; 23 | } catch { 24 | return {} as T; 25 | } 26 | } 27 | 28 | /** @internal */ 29 | export function sortObject(obj: Record): Record { 30 | return Object.keys(obj) 31 | .sort() 32 | .reduce((p: Record, c: string): Record => { 33 | p[c] = obj[c]; 34 | return p; 35 | }, {}); 36 | } 37 | 38 | /** @internal */ 39 | export function getIndentation(text: string): detectIndent.Indent { 40 | return detectIndent(text); 41 | } 42 | 43 | /** @internal */ 44 | export function normalizeEntries(entries: string[]): string[] { 45 | return entries 46 | .map((entry: string): string => entry.trim()) 47 | .filter((entry: string): boolean => !!entry && !/^\s*#/.test(entry)); 48 | } 49 | 50 | /** @internal */ 51 | export function unique(entries: string[]): string[] { 52 | return entries.filter((entry: string, index: number, array: string[]): boolean => 53 | array.indexOf(entry) === index); 54 | } 55 | -------------------------------------------------------------------------------- /src/common/yargsParser.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, statSync } from 'fs'; 2 | import { dirname } from 'path'; 3 | import y, { Arguments } from 'yargs'; 4 | import { Arguments as Args } from '../interfaces/arguments'; 5 | import { ParsedArgs } from '../interfaces/parsedArgs'; 6 | import { sortObject } from './utils'; 7 | 8 | 9 | /** @internal */ 10 | export class YargsParser { 11 | private readonly commonOptions: { [key: string]: y.Options } = { 12 | diralgorithm: { 13 | alias: 'da', 14 | default: 'sha512', 15 | description: 'The algorithm to use for directory hashing', 16 | type: 'string', 17 | }, 18 | encoding: { 19 | alias: 'e', 20 | default: 'base64', 21 | description: 'The encoding to use for hashing', 22 | type: 'string', 23 | }, 24 | exclude: { 25 | alias: 'x', 26 | default: [], 27 | description: 'Files and/or directories paths to exclude', 28 | type: 'array', 29 | }, 30 | filealgorithm: { 31 | alias: 'fa', 32 | default: 'sha1', 33 | description: 'The algorithm to use for file hashing', 34 | type: 'string', 35 | }, 36 | manifest: { 37 | alias: 'm', 38 | default: undefined, 39 | description: `The integrity hash gets persisted to, or read from, the project's manifest (package.json)`, 40 | type: 'boolean', 41 | }, 42 | source: { 43 | alias: 's', 44 | demandOption: true, 45 | description: 'The path to the file or directory to hash', 46 | type: 'string', 47 | }, 48 | strict: { 49 | alias: 'st', 50 | default: false, 51 | description: 'Use directory name in root hash', 52 | type: 'boolean', 53 | }, 54 | verbose: { 55 | alias: 'v', 56 | default: false, 57 | description: 'Verbosely create hashes of a directory', 58 | type: 'boolean', 59 | }, 60 | }; 61 | 62 | private readonly createOptions: { [key: string]: y.Options } = { 63 | output: { 64 | alias: 'o', 65 | description: 'The directory path where to persist the created integrity file' + 66 | ` (ignored when 'manifest' option specified)`, 67 | type: 'string', 68 | }, 69 | pretty: { 70 | alias: 'p', 71 | default: false, 72 | description: 'Prettify the integrity object', 73 | type: 'boolean', 74 | }, 75 | }; 76 | 77 | private readonly checkOptions: { [key: string]: y.Options } = { 78 | integrity: { 79 | alias: 'i', 80 | conflicts: 'manifest', 81 | description: 'The integrity hash, JSON, file or directory path, to check against' + 82 | ` ([required] when 'manifest' option not specified)`, 83 | type: 'string', 84 | }, 85 | }; 86 | 87 | public constructor() { 88 | y 89 | .usage('Usage: $0 {command} [options]') 90 | .command('create [options]', 91 | `Creates integrity hash from the provided source (use '--help' for [options] details)`, 92 | sortObject({ ...this.createOptions, ...this.commonOptions }) as { [key: string]: y.Options }) 93 | .command('check [options]', 94 | `Checks integrity hash against the provided source (use '--help' for [options] details)`, 95 | sortObject({ ...this.checkOptions, ...this.commonOptions }) as { [key: string]: y.Options }) 96 | .demandCommand(1, 'Missing command') 97 | .recommendCommands() 98 | .version(false) 99 | .options({ 100 | help: { 101 | alias: 'h', 102 | description: 'Show help', 103 | global: true, 104 | }, 105 | version: { 106 | alias: 'V', 107 | description: 'Show version number', 108 | global: false, 109 | }, 110 | }) 111 | .check((argv: y.Arguments): boolean => this.validate(argv)) 112 | .strict(); 113 | } 114 | 115 | public async parse(): Promise { 116 | const pargs: Arguments = await y.parseAsync(process.argv.slice(2)); 117 | // Set 'output' dir same as 'source' when not provided 118 | if (!pargs.output) { 119 | const source = pargs.source as string; 120 | pargs.output = statSync(source).isFile() 121 | ? dirname(source) 122 | : pargs.source; 123 | } 124 | return { 125 | command: pargs._[0] as string, 126 | dirAlgorithm: pargs.diralgorithm as string, 127 | encoding: pargs.encoding as string, 128 | exclude: pargs.exclude as string[], 129 | fileAlgorithm: pargs.filealgorithm as string, 130 | inPath: pargs.source as string, 131 | integrity: pargs.integrity as string, 132 | manifest: pargs.manifest as boolean, 133 | outPath: pargs.output as string, 134 | pretty: pargs.pretty as boolean, 135 | strict: pargs.strict as boolean, 136 | verbose: pargs.verbose as boolean, 137 | }; 138 | } 139 | 140 | private validate(argv: y.Arguments): boolean { 141 | let errorMsg = ''; 142 | if (!existsSync(argv.source as string)) { 143 | errorMsg = `ENOENT: no such file or directory, '${argv.source as string}'`; 144 | } 145 | if (argv._[0] === 'check' && !argv.manifest && !argv.integrity) { 146 | errorMsg = 'Missing required argument: integrity'; 147 | } 148 | if (errorMsg) { 149 | throw new Error(errorMsg); 150 | } 151 | return true; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app/integrity'; 2 | export * from './interfaces/cryptoOptions'; 3 | export * from './interfaces/hashObject'; 4 | export * from './interfaces/integrityObject'; 5 | export * from './interfaces/integrityOptions'; 6 | export * from './interfaces/verboseHashObject'; 7 | -------------------------------------------------------------------------------- /src/interfaces/arguments.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export interface Arguments { 3 | _: Array<(string | number)>; 4 | $0: string; 5 | diralgorithm?: string; 6 | source?: string; 7 | encoding?: string; 8 | exclude?: string[]; 9 | filealgorithm?: string; 10 | integrity?: string; 11 | manifest?: boolean; 12 | output?: string; 13 | pretty?: boolean; 14 | strict?: boolean; 15 | verbose?: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /src/interfaces/configOptions.ts: -------------------------------------------------------------------------------- 1 | import { CryptoOptions } from './cryptoOptions'; 2 | 3 | /** @internal */ 4 | export interface ConfigOptions { 5 | manifest?: boolean; 6 | source?: string; 7 | verbose?: boolean; 8 | cryptoOptions? : CryptoOptions; 9 | exclude?: string[]; 10 | integrity?: string; 11 | output?: string; 12 | strict?: boolean; 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/cryptoOptions.ts: -------------------------------------------------------------------------------- 1 | import { BinaryToTextEncoding } from 'crypto'; 2 | 3 | /** @public */ 4 | export interface CryptoOptions { 5 | dirAlgorithm?: string; 6 | encoding?: BinaryToTextEncoding; 7 | fileAlgorithm?: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/hashObject.ts: -------------------------------------------------------------------------------- 1 | import { VerboseHashObject } from './verboseHashObject'; 2 | 3 | /** @public */ 4 | export interface HashObject { 5 | [key: string]: string | VerboseHashObject; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/integrityObject.ts: -------------------------------------------------------------------------------- 1 | import { HashObject } from './hashObject'; 2 | 3 | /** @public */ 4 | export interface IntegrityObject extends Record { 5 | version: string; 6 | hashes: HashObject; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/integrityOptions.ts: -------------------------------------------------------------------------------- 1 | import { CryptoOptions } from './cryptoOptions'; 2 | 3 | /** @public */ 4 | export interface IntegrityOptions { 5 | cryptoOptions?: CryptoOptions; 6 | verbose?: boolean; 7 | exclude?: string[]; 8 | strict?: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/interfaces/manifestInfo.ts: -------------------------------------------------------------------------------- 1 | import detectIndent from 'detect-indent'; 2 | 3 | /** @internal */ 4 | export interface ManifestInfo { 5 | indentation: detectIndent.Indent; 6 | manifest: Record ; 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/normalizedCryptoOptions.ts: -------------------------------------------------------------------------------- 1 | import { BinaryToTextEncoding } from 'crypto'; 2 | 3 | /** @internal */ 4 | export interface NormalizedCryptoOptions { 5 | dirAlgorithm: string; 6 | encoding: BinaryToTextEncoding; 7 | fileAlgorithm: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/normalizedIntegrityOptions.ts: -------------------------------------------------------------------------------- 1 | import { CryptoOptions } from './cryptoOptions'; 2 | 3 | /** @internal */ 4 | export interface NormalizedIntegrityOptions { 5 | cryptoOptions: CryptoOptions; 6 | verbose: boolean; 7 | strict: boolean; 8 | exclude: string[]; 9 | include: string[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/parsedArgs.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export interface ParsedArgs { 3 | command: string; 4 | dirAlgorithm: string; 5 | encoding: string; 6 | exclude: string[]; 7 | fileAlgorithm: string; 8 | inPath: string; 9 | integrity: string; 10 | manifest: boolean; 11 | outPath: string; 12 | pretty: boolean; 13 | strict: boolean; 14 | verbose: boolean; 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/spinner.ts: -------------------------------------------------------------------------------- 1 | /** @internal */ 2 | export interface Spinner { 3 | timer: NodeJS.Timer; 4 | line: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/interfaces/verboseHashObject.ts: -------------------------------------------------------------------------------- 1 | import { HashObject } from './hashObject'; 2 | 3 | /** @public */ 4 | export interface VerboseHashObject { 5 | contents: HashObject; 6 | hash: string; 7 | } 8 | -------------------------------------------------------------------------------- /test/api/behavior.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import path from 'path'; 4 | import { Integrity } from '../../src/app/integrity'; 5 | 6 | describe('Integrity: behavior tests', (): void => { 7 | 8 | context('expects', (): void => { 9 | 10 | let anotherFileToHashFilename: string; 11 | let fileToHashFilename: string; 12 | let directoryDirPath: string; 13 | let directoryOneDirPath: string; 14 | let fixturesDirPath: string; 15 | let anotherFileToHashFilePath: string; 16 | let fileToHashFilePath: string; 17 | 18 | before((): void => { 19 | anotherFileToHashFilename = 'anotherFileToHash.txt'; 20 | fileToHashFilename = 'fileToHash.txt'; 21 | }); 22 | 23 | beforeEach((): void => { 24 | fixturesDirPath = path.resolve(__dirname, '../../../test/fixtures'); 25 | directoryDirPath = path.resolve(fixturesDirPath, 'directory'); 26 | directoryOneDirPath = path.resolve(fixturesDirPath, 'directory.1'); 27 | anotherFileToHashFilePath = path.resolve(directoryDirPath, anotherFileToHashFilename); 28 | fileToHashFilePath = path.resolve(fixturesDirPath, fileToHashFilename); 29 | }); 30 | 31 | context('to pass integrity check when', (): void => { 32 | 33 | it('files have the same name and the same content', 34 | async (): Promise => { 35 | const sutFilePath = path.resolve(fixturesDirPath, './fixtures/directory/anotherFileToHash.txt'); 36 | const hash = await Integrity.create(sutFilePath); 37 | const sut = await Integrity.check(anotherFileToHashFilePath, JSON.stringify(hash)); 38 | expect(hash.hashes).to.haveOwnProperty(anotherFileToHashFilename); 39 | expect(sut).to.be.a('boolean').and.to.be.true; 40 | }); 41 | 42 | it('directories have the same name and the same content', 43 | async (): Promise => { 44 | const sutDirPath = path.resolve(fixturesDirPath, './fixtures/directory.1'); 45 | const hash = await Integrity.create(sutDirPath); 46 | const sut = await Integrity.check(directoryOneDirPath, JSON.stringify(hash)); 47 | expect(hash.hashes).to.haveOwnProperty('.'); 48 | expect(sut).to.be.a('boolean').and.to.be.true; 49 | }); 50 | 51 | it('directories have the same content but different names', 52 | async (): Promise => { 53 | const sutDirPath = path.resolve(fixturesDirPath, './directory.1'); 54 | const hash = await Integrity.create(sutDirPath, { strict: false }); 55 | const sut = await Integrity.check(directoryDirPath, JSON.stringify(hash)); 56 | expect(hash.hashes).to.haveOwnProperty('.'); 57 | expect(sut).to.be.a('boolean').and.to.be.true; 58 | }); 59 | 60 | }); 61 | 62 | context('to fail integrity check when', (): void => { 63 | 64 | it('files have the same content but different names', 65 | async (): Promise => { 66 | const sutFilePath = path.resolve(fixturesDirPath, './sameContentWithFileToHash.txt'); 67 | const hash = await Integrity.create(sutFilePath); 68 | const sut = await Integrity.check(fileToHashFilePath, JSON.stringify(hash)); 69 | expect(hash.hashes).to.haveOwnProperty('sameContentWithFileToHash.txt'); 70 | expect(sut).to.be.a('boolean').and.to.be.false; 71 | }); 72 | 73 | it('files have the same name but different content', 74 | async (): Promise => { 75 | const sutFilePath = path.resolve(fixturesDirPath, './fixtures/fileToHash.txt'); 76 | const hash = await Integrity.create(sutFilePath); 77 | const sut = await Integrity.check(fileToHashFilePath, JSON.stringify(hash)); 78 | expect(hash.hashes).to.haveOwnProperty(fileToHashFilename); 79 | expect(sut).to.be.a('boolean').and.to.be.false; 80 | }); 81 | 82 | it('directories have the same name but different content', 83 | async (): Promise => { 84 | const sutDirPath = path.resolve(fixturesDirPath, './fixtures/directory'); 85 | const hash = await Integrity.create(sutDirPath); 86 | const sut = await Integrity.check(directoryDirPath, JSON.stringify(hash)); 87 | expect(hash.hashes).to.haveOwnProperty('.'); 88 | expect(sut).to.be.a('boolean').and.to.be.false; 89 | }); 90 | 91 | it(`directories have the same content but different names in 'strict' mode`, 92 | async (): Promise => { 93 | const sutDirPath = path.resolve(fixturesDirPath, './directory.1'); 94 | const hash = await Integrity.create(sutDirPath, { strict: true }); 95 | const sut = await Integrity.check(directoryDirPath, JSON.stringify(hash)); 96 | expect(hash.hashes).to.haveOwnProperty('directory.1'); 97 | expect(sut).to.be.a('boolean').and.to.be.false; 98 | }); 99 | 100 | }); 101 | 102 | }); 103 | 104 | }); 105 | -------------------------------------------------------------------------------- /test/api/create.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import Ajv from 'ajv'; 3 | import { expect } from 'chai'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import sinon, { createSandbox } from 'sinon'; 7 | import { Integrity } from '../../src/app/integrity'; 8 | import * as fsAsync from '../../src/common/fsAsync'; 9 | import { IntegrityOptions } from '../../src/interfaces/integrityOptions'; 10 | import * as schema from '../../src/app/schemas/v1/schema.json'; 11 | 12 | describe(`Integrity: function 'create' tests`, (): void => { 13 | 14 | context('expects', (): void => { 15 | 16 | let fileToHashFilename: string; 17 | let fixturesDirPath: string; 18 | let fileToHashFilePath: string; 19 | let schemaValidator: Ajv; 20 | let sandbox: sinon.SinonSandbox; 21 | let fsStatsMock: sinon.SinonStubbedInstance; 22 | 23 | before((): void => { 24 | schemaValidator = new Ajv({ allowUnionTypes: true }); 25 | fileToHashFilename = 'fileToHash.txt'; 26 | }); 27 | 28 | let options: IntegrityOptions; 29 | 30 | beforeEach((): void => { 31 | sandbox = createSandbox(); 32 | fsStatsMock = sandbox.createStubInstance(fs.Stats); 33 | fixturesDirPath = path.resolve(__dirname, '../../../test/fixtures'); 34 | fileToHashFilePath = path.resolve(fixturesDirPath, fileToHashFilename); 35 | options = { 36 | cryptoOptions: undefined, 37 | exclude: undefined, 38 | verbose: undefined, 39 | }; 40 | }); 41 | 42 | afterEach((): void => { 43 | sandbox.restore(); 44 | }); 45 | 46 | it('to return an empty object when path is not a file or directory', 47 | async (): Promise => { 48 | fsStatsMock.isDirectory.returns(false); 49 | fsStatsMock.isFile.returns(false); 50 | const lstatStub = sandbox.stub(fsAsync, 'lstatAsync') 51 | .resolves(fsStatsMock); 52 | const sut = await Integrity.create(fixturesDirPath); 53 | lstatStub.restore(); 54 | expect(lstatStub.calledOnce).to.be.true; 55 | expect(sut).to.be.an('object'); 56 | expect(sut).to.haveOwnProperty('hashes').that.is.empty; 57 | expect(sut).to.haveOwnProperty('version').that.matches(/\d/); 58 | }); 59 | 60 | context('to produce a valid schema when hashing', (): void => { 61 | 62 | it('a directory non-verbosely', 63 | async (): Promise => { 64 | options.verbose = false; 65 | const sut = await Integrity.create(fixturesDirPath, options); 66 | expect(schemaValidator.validate(schema, sut)).to.be.true; 67 | expect(schemaValidator.errors).to.be.null; 68 | }); 69 | 70 | it('a directory verbosely', 71 | async (): Promise => { 72 | const sut = await Integrity.create(fixturesDirPath); 73 | expect(schemaValidator.validate(schema, sut)).to.be.true; 74 | expect(schemaValidator.errors).to.be.null; 75 | }); 76 | 77 | it('a file', 78 | async (): Promise => { 79 | const sut = await Integrity.create(fileToHashFilePath); 80 | expect(schemaValidator.validate(schema, sut)).to.be.true; 81 | expect(schemaValidator.errors).to.be.null; 82 | }); 83 | 84 | }); 85 | 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /test/api/createFileHash.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { BinaryToTextEncoding } from 'crypto'; 4 | import fs, { ReadStream } from 'fs'; 5 | import path from 'path'; 6 | import sinon, { createSandbox } from 'sinon'; 7 | import { Integrity } from '../../src/app/integrity'; 8 | import * as utils from '../../src/common/utils'; 9 | import { CryptoOptions } from '../../src/interfaces/cryptoOptions'; 10 | import { checker } from '../helper'; 11 | 12 | describe(`Integrity: function 'createFileHash' tests`, (): void => { 13 | 14 | context('expects', (): void => { 15 | 16 | let sandbox: sinon.SinonSandbox; 17 | let fileToHashFilename: string; 18 | let integrityTestFilename: string; 19 | let fixturesDirPath: string; 20 | let fileToHashFilePath: string; 21 | let integrityTestFilePath: string; 22 | let md5Length: number; 23 | let sha1Length: number; 24 | 25 | before((): void => { 26 | fileToHashFilename = 'fileToHash.txt'; 27 | integrityTestFilename = '.integrity.json'; 28 | 29 | md5Length = 32; 30 | sha1Length = 40; 31 | }); 32 | 33 | beforeEach((): void => { 34 | sandbox = createSandbox(); 35 | fixturesDirPath = path.resolve(__dirname, '../../../test/fixtures'); 36 | fileToHashFilePath = path.resolve(fixturesDirPath, fileToHashFilename); 37 | integrityTestFilePath = path.resolve(fixturesDirPath, integrityTestFilename); 38 | }); 39 | 40 | afterEach((): void => { 41 | sandbox.restore(); 42 | }); 43 | 44 | context('to throw an Error when', (): void => { 45 | 46 | it('the provided algorithm is not supported', 47 | async (): Promise => { 48 | const cryptoOptions = { fileAlgorithm: 'md1' }; 49 | try { 50 | await Integrity.createFileHash(fileToHashFilePath, cryptoOptions); 51 | } catch (error) { 52 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOSUP:/); 53 | } 54 | }); 55 | 56 | it('the provided encoding is not supported', 57 | async (): Promise => { 58 | const cryptoOptions: CryptoOptions = { encoding: 'ascii' as BinaryToTextEncoding }; 59 | try { 60 | await Integrity.createFileHash(fileToHashFilePath, cryptoOptions); 61 | } catch (error) { 62 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOSUP:/); 63 | } 64 | }); 65 | 66 | it('the provided path is not a file', 67 | async (): Promise => { 68 | try { 69 | await Integrity.createFileHash(fixturesDirPath); 70 | } catch (error) { 71 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOTFILE:/); 72 | } 73 | }); 74 | 75 | it('the provided path is not allowed', 76 | async (): Promise => { 77 | try { 78 | await Integrity.createFileHash(integrityTestFilePath); 79 | } catch (error) { 80 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOTALW:/); 81 | } 82 | }); 83 | 84 | it('the file can not be read', 85 | async (): Promise => { 86 | sandbox.stub(fs, 'createReadStream').returns({ 87 | pipe: sandbox.stub().returnsThis(), 88 | on: sandbox.stub().callsFake((_, cb: (err: Error) => void) => 89 | cb(new Error('Failed reading file'))), 90 | } as unknown as ReadStream); 91 | try { 92 | await Integrity.createFileHash(fileToHashFilePath); 93 | } catch (error) { 94 | expect(error).to.be.an.instanceof(Error).and.match(/Failed reading file/); 95 | } 96 | }); 97 | 98 | }); 99 | 100 | it(`to return by default an 'sha1' and 'base64' encoded hash string`, 101 | async (): Promise => { 102 | const sut = await Integrity.createFileHash(fileToHashFilePath); 103 | expect(sut).to.be.an('object') 104 | .and.to.haveOwnProperty(fileToHashFilename) 105 | .and.to.satisfy((hash: string): boolean => 106 | checker(hash, utils.base64RegexPattern, 'H58mYNjbMJTkiNvvNfj2YKl3ck0=')); 107 | }); 108 | 109 | it(`to return a 'sha1' and 'hex' encoded hash string`, 110 | async (): Promise => { 111 | const sut = await Integrity.createFileHash(fileToHashFilePath, { encoding: 'hex' }); 112 | expect(sut).to.be.an('object') 113 | .and.to.haveOwnProperty(fileToHashFilename) 114 | .and.to.satisfy((hash: string): boolean => 115 | checker(hash, utils.hexRegexPattern, 116 | '1f9f2660d8db3094e488dbef35f8f660a977724d', 117 | 'sha1', sha1Length)); 118 | }); 119 | 120 | it(`to return a 'sha1' and 'base64url' encoded hash string`, 121 | async (): Promise => { 122 | const sut = await Integrity.createFileHash(fileToHashFilePath, { encoding: 'base64url' }); 123 | expect(sut).to.be.an('object') 124 | .and.to.haveOwnProperty(fileToHashFilename) 125 | .and.to.satisfy((hash: string): boolean => 126 | checker(hash, utils.base64urlRegexPattern, 127 | 'H58mYNjbMJTkiNvvNfj2YKl3ck0', 128 | 'sha1')); 129 | }); 130 | 131 | it(`to return an 'md5' and 'base64' encoded hash string`, 132 | async (): Promise => { 133 | const sut = await Integrity.createFileHash(fileToHashFilePath, { fileAlgorithm: 'md5' }); 134 | expect(sut).to.be.an('object') 135 | .and.to.haveOwnProperty(fileToHashFilename) 136 | .and.to.satisfy((hash: string): boolean => 137 | checker(hash, utils.base64RegexPattern, 'ej1bR1vQeukEH6sqEz9AxA==', 'md5')); 138 | }); 139 | 140 | it(`to return an 'md5' and 'hex' encoded hash string`, 141 | async (): Promise => { 142 | const sut = await Integrity.createFileHash( 143 | fileToHashFilePath, 144 | { fileAlgorithm: 'md5', encoding: 'hex' }); 145 | expect(sut).to.be.an('object') 146 | .and.to.haveOwnProperty(fileToHashFilename) 147 | .and.to.satisfy((hash: string): boolean => 148 | checker(hash, utils.base64RegexPattern, '7a3d5b475bd07ae9041fab2a133f40c4', 'md5', md5Length)); 149 | }); 150 | 151 | it(`to support relative paths`, 152 | async (): Promise => { 153 | const sut = await Integrity.createFileHash('test/fixtures/fileToHash.txt'); 154 | 155 | expect(sut).to.be.an('object') 156 | .and.to.haveOwnProperty(fileToHashFilename) 157 | .and.to.satisfy((hash: string): boolean => 158 | checker(hash, utils.base64RegexPattern, 'H58mYNjbMJTkiNvvNfj2YKl3ck0=')); 159 | }); 160 | 161 | }); 162 | 163 | }); 164 | -------------------------------------------------------------------------------- /test/api/createFilesHash.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { BinaryToTextEncoding } from 'crypto'; 4 | import path from 'path'; 5 | import { Integrity } from '../../src/app/integrity'; 6 | import * as utils from '../../src/common/utils'; 7 | import { CryptoOptions } from '../../src/interfaces/cryptoOptions'; 8 | import { checker } from '../helper'; 9 | 10 | describe(`Integrity: function 'createFilesHash' tests`, (): void => { 11 | 12 | context('expects', (): void => { 13 | 14 | let anotherFileToHashFilename: string; 15 | let otherFileToHashFilename: string; 16 | let fileToHashFilename: string; 17 | let directoryDirPath: string; 18 | let fixturesDirPath: string; 19 | let anotherFileToHashFilePath: string; 20 | let otherFileToHashFilePath: string; 21 | let fileToHashFilePath: string; 22 | let md5Length: number; 23 | let sha1Length: number; 24 | 25 | before((): void => { 26 | anotherFileToHashFilename = 'anotherFileToHash.txt'; 27 | otherFileToHashFilename = 'otherFileToHash.txt'; 28 | fileToHashFilename = 'fileToHash.txt'; 29 | 30 | md5Length = 32; 31 | sha1Length = 40; 32 | }); 33 | 34 | beforeEach((): void => { 35 | fixturesDirPath = path.resolve(__dirname, '../../../test/fixtures'); 36 | directoryDirPath = path.resolve(fixturesDirPath, 'directory'); 37 | anotherFileToHashFilePath = path.resolve(directoryDirPath, anotherFileToHashFilename); 38 | otherFileToHashFilePath = path.resolve(directoryDirPath, otherFileToHashFilename); 39 | fileToHashFilePath = path.resolve(fixturesDirPath, fileToHashFilename); 40 | }); 41 | 42 | context('to throw an Error when', (): void => { 43 | 44 | it('the provided algorithm is not supported', 45 | async (): Promise => { 46 | const cryptoOptions = { fileAlgorithm: 'md1' }; 47 | try { 48 | await Integrity.createFilesHash([fileToHashFilePath], cryptoOptions); 49 | } catch (error) { 50 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOSUP:/); 51 | } 52 | }); 53 | 54 | it('the provided encoding is not supported', 55 | async (): Promise => { 56 | const cryptoOptions: CryptoOptions = { encoding: 'ascii' as BinaryToTextEncoding }; 57 | try { 58 | await Integrity.createFilesHash([fileToHashFilePath], cryptoOptions); 59 | } catch (error) { 60 | expect(error).to.be.an.instanceof(Error).that.matches(/ENOSUP:/); 61 | } 62 | }); 63 | 64 | }); 65 | 66 | it(`to return by default a 'sha1' and 'base64' encoded hash JSON`, 67 | async (): Promise => { 68 | const files = [anotherFileToHashFilePath, otherFileToHashFilePath]; 69 | const sut = await Integrity.createFilesHash(files); 70 | expect(sut).to.be.an('object'); 71 | expect(sut).to.haveOwnProperty(anotherFileToHashFilename) 72 | .and.to.satisfy((hash: string): boolean => 73 | checker(hash, utils.base64RegexPattern, 'EZ2w0rsSmXBOddIoz2IoOIuxGaQ=')); 74 | expect(sut).to.haveOwnProperty(otherFileToHashFilename) 75 | .and.to.satisfy((hash: string): boolean => 76 | checker(hash, utils.base64RegexPattern, 'B8FJ4uKgHESSgMvJUyrj3ix2uG8=')); 77 | }); 78 | 79 | it(`to return a 'sha1' and 'hex' encoded hash JSON`, 80 | async (): Promise => { 81 | const files = [anotherFileToHashFilePath, otherFileToHashFilePath]; 82 | const sut = await Integrity.createFilesHash(files, { encoding: 'hex' }); 83 | expect(sut).to.be.an('object'); 84 | expect(sut).to.haveOwnProperty(anotherFileToHashFilename) 85 | .and.to.satisfy((hash: string): boolean => 86 | checker(hash, utils.hexRegexPattern, 87 | '119db0d2bb1299704e75d228cf6228388bb119a4', 88 | 'sha1', sha1Length)); 89 | expect(sut).to.haveOwnProperty(otherFileToHashFilename) 90 | .and.to.satisfy((hash: string): boolean => 91 | checker(hash, utils.hexRegexPattern, 92 | '07c149e2e2a01c449280cbc9532ae3de2c76b86f', 93 | 'sha1', sha1Length)); 94 | }); 95 | 96 | it(`to return a 'sha1' and 'base64url' encoded hash JSON`, 97 | async (): Promise => { 98 | const files = [anotherFileToHashFilePath, otherFileToHashFilePath]; 99 | const sut = await Integrity.createFilesHash(files, { encoding: 'base64url' }); 100 | expect(sut).to.be.an('object'); 101 | expect(sut).to.haveOwnProperty(anotherFileToHashFilename) 102 | .and.to.satisfy((hash: string): boolean => 103 | checker(hash, utils.base64urlRegexPattern, 104 | 'EZ2w0rsSmXBOddIoz2IoOIuxGaQ', 105 | 'sha1')); 106 | expect(sut).to.haveOwnProperty(otherFileToHashFilename) 107 | .and.to.satisfy((hash: string): boolean => 108 | checker(hash, utils.base64urlRegexPattern, 109 | 'B8FJ4uKgHESSgMvJUyrj3ix2uG8', 110 | 'sha1')); 111 | }); 112 | 113 | it(`to return an 'md5' and 'base64' encoded hash JSON`, 114 | async (): Promise => { 115 | const files = [anotherFileToHashFilePath, otherFileToHashFilePath]; 116 | const sut = await Integrity.createFilesHash(files, { fileAlgorithm: 'md5' }); 117 | expect(sut).to.be.an('object'); 118 | expect(sut).to.haveOwnProperty(anotherFileToHashFilename) 119 | .and.to.satisfy((hash: string): boolean => 120 | checker(hash, utils.base64RegexPattern, '6FwJAV4629O2chl9ZbDgEQ==', 'md5')); 121 | expect(sut).to.haveOwnProperty(otherFileToHashFilename) 122 | .and.to.satisfy((hash: string): boolean => 123 | checker(hash, utils.base64RegexPattern, 'qrJbDxeJ/oiVXO5tI3Dntw==', 'md5')); 124 | }); 125 | 126 | it(`to return an 'md5' and 'hex' encoded hash JSON`, 127 | async (): Promise => { 128 | const files = [anotherFileToHashFilePath, otherFileToHashFilePath]; 129 | const sut = await Integrity.createFilesHash(files, { fileAlgorithm: 'md5', encoding: 'hex' }); 130 | expect(sut).to.be.an('object'); 131 | expect(sut).to.haveOwnProperty(anotherFileToHashFilename) 132 | .and.to.satisfy((hash: string): boolean => 133 | checker(hash, utils.hexRegexPattern, 'e85c09015e3adbd3b672197d65b0e011', 'md5', md5Length)); 134 | expect(sut).to.haveOwnProperty(otherFileToHashFilename) 135 | .and.to.satisfy((hash: string): boolean => 136 | checker(hash, utils.hexRegexPattern, 'aab25b0f1789fe88955cee6d2370e7b7', 'md5', md5Length)); 137 | }); 138 | 139 | }); 140 | 141 | }); 142 | -------------------------------------------------------------------------------- /test/api/getExclusionsFromIgnoreFile.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { resolve } from 'path'; 4 | import sinon, { createSandbox } from 'sinon'; 5 | import { Integrity } from '../../src/app/integrity'; 6 | 7 | describe(`Integrity: function 'getExclusionsFromIgnoreFile' tests`, (): void => { 8 | 9 | context('expects', (): void => { 10 | 11 | let sandbox: sinon.SinonSandbox; 12 | let baseIgnoreFilePath: string; 13 | 14 | beforeEach((): void => { 15 | sandbox = createSandbox(); 16 | baseIgnoreFilePath = resolve(__dirname, '../../../test/ignoreFile'); 17 | }); 18 | 19 | afterEach((): void => { 20 | sandbox.restore(); 21 | }); 22 | 23 | it('to return an empty array, when failing to find the ignore file', 24 | async (): Promise => { 25 | const exclusions = await Integrity.getExclusionsFromIgnoreFile(); 26 | expect(exclusions).to.be.an('array').and.to.be.empty; 27 | }); 28 | 29 | it('to return an array of exclution entries', 30 | async (): Promise => { 31 | const expectedEntries = ['*', '*/', '!dist']; 32 | const exclusions = await Integrity.getExclusionsFromIgnoreFile(baseIgnoreFilePath); 33 | expect(exclusions).to.be.an('array').and.to.eql(expectedEntries); 34 | }); 35 | 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /test/api/getIntegrityOptionsFromConfig.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import sinon, { createSandbox } from 'sinon'; 4 | import { Integrity } from '../../src/app/integrity'; 5 | import { ConfigExplorer } from '../../src/common/configExplorer'; 6 | import { ConfigOptions } from '../../src/interfaces/configOptions'; 7 | import { IntegrityOptions } from '../../src/interfaces/integrityOptions'; 8 | 9 | describe(`Integrity: function 'getIntegrityOptionsFromConfig' tests`, (): void => { 10 | 11 | context('expects', (): void => { 12 | 13 | let sandbox: sinon.SinonSandbox; 14 | let getConfigStub: sinon.SinonStub<[(string | undefined)?], Promise>; 15 | 16 | beforeEach((): void => { 17 | sandbox = createSandbox(); 18 | getConfigStub = sandbox.stub(ConfigExplorer.prototype, 'getConfig'); 19 | }); 20 | 21 | afterEach((): void => { 22 | sandbox.restore(); 23 | }); 24 | 25 | const expected = (options: ConfigOptions): IntegrityOptions => ( 26 | !Object.keys(options).length 27 | ? options 28 | : { 29 | cryptoOptions: options.cryptoOptions, 30 | exclude: options.exclude, 31 | verbose: options.verbose, 32 | strict: options.strict, 33 | } 34 | ); 35 | 36 | let rc: ConfigOptions = { 37 | cryptoOptions: { 38 | dirAlgorithm: 'mr', 39 | encoding: 'hex', 40 | fileAlgorithm: 'rm', 41 | }, 42 | exclude: ['dir', 'file'], 43 | source: '.', 44 | verbose: true, 45 | strict: true, 46 | }; 47 | 48 | it('to return an empty object, when failing to find a configuration', 49 | async (): Promise => { 50 | rc = {}; 51 | getConfigStub.resolves(rc); 52 | const config = await Integrity.getIntegrityOptionsFromConfig(); 53 | expect(config).to.eql(expected(rc)); 54 | }); 55 | 56 | context('to return the integrity options', (): void => { 57 | 58 | it('with all options', 59 | async (): Promise => { 60 | getConfigStub.resolves(rc); 61 | const config = await Integrity.getIntegrityOptionsFromConfig(); 62 | expect(config).to.eql(expected(rc)); 63 | }); 64 | 65 | it('without `cryptoOptions`', 66 | async (): Promise => { 67 | rc.cryptoOptions = undefined; 68 | getConfigStub.resolves(rc); 69 | const config = await Integrity.getIntegrityOptionsFromConfig(); 70 | expect(config).to.eql(expected(rc)); 71 | }); 72 | 73 | it('with `dirAlgorithm` `undefined`', 74 | async (): Promise => { 75 | rc.cryptoOptions = { 76 | dirAlgorithm: undefined, 77 | encoding: 'hex', 78 | fileAlgorithm: 'rm', 79 | }; 80 | getConfigStub.resolves(rc); 81 | const config = await Integrity.getIntegrityOptionsFromConfig(); 82 | expect(config).to.eql(expected(rc)); 83 | }); 84 | 85 | it('with `encoding` `undefined`', 86 | async (): Promise => { 87 | rc.cryptoOptions = { 88 | dirAlgorithm: 'mr', 89 | encoding: undefined, 90 | fileAlgorithm: 'rm', 91 | }; 92 | getConfigStub.resolves(rc); 93 | const config = await Integrity.getIntegrityOptionsFromConfig(); 94 | expect(config).to.eql(expected(rc)); 95 | }); 96 | 97 | it('with `fileAlgorithm` `undefined`', 98 | async (): Promise => { 99 | rc.cryptoOptions = { 100 | dirAlgorithm: 'mr', 101 | encoding: 'hex', 102 | fileAlgorithm: undefined, 103 | }; 104 | getConfigStub.resolves(rc); 105 | const config = await Integrity.getIntegrityOptionsFromConfig(); 106 | expect(config).to.eql(expected(rc)); 107 | }); 108 | 109 | }); 110 | 111 | }); 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /test/api/getManifestIntegrity.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { ObjectEncodingOptions, PathLike } from 'fs'; 4 | import sinon, { createSandbox } from 'sinon'; 5 | import { Integrity } from '../../src/app/integrity'; 6 | import * as fsAsync from '../../src/common/fsAsync'; 7 | 8 | describe(`Integrity: function 'getManifestIntegrity' tests`, (): void => { 9 | 10 | context('expects', (): void => { 11 | 12 | type ReadFileType = [PathLike | number, 13 | (ObjectEncodingOptions & { flag?: string | undefined } | BufferEncoding | null)?]; 14 | 15 | let sandbox: sinon.SinonSandbox; 16 | let existsAsyncStub: sinon.SinonStub<[PathLike], Promise>; 17 | let readFileAsyncStub: sinon.SinonStub>; 18 | 19 | beforeEach((): void => { 20 | sandbox = createSandbox(); 21 | existsAsyncStub = sandbox.stub(fsAsync, 'existsAsync'); 22 | readFileAsyncStub = sandbox.stub(fsAsync, 'readFileAsync'); 23 | }); 24 | 25 | afterEach((): void => { 26 | sandbox.restore(); 27 | }); 28 | 29 | context(`to return an 'Error' when`, (): void => { 30 | 31 | it('the manifest file is not found', 32 | async (): Promise => { 33 | try { 34 | await Integrity.getManifestIntegrity('../'); 35 | } catch (error) { 36 | expect(existsAsyncStub.calledOnce).to.be.true; 37 | expect(error).to.match(/Error: 'package\.json' not found/); 38 | } 39 | }); 40 | 41 | it(`the manifest is NOT valid`, 42 | async (): Promise => { 43 | existsAsyncStub.resolves(true); 44 | readFileAsyncStub.resolves(''); 45 | try { 46 | await Integrity.getManifestIntegrity(); 47 | } catch (error) { 48 | expect(existsAsyncStub.calledOnce).to.be.true; 49 | expect(readFileAsyncStub.calledOnce).to.be.true; 50 | expect(error).to.match(/Error: Manifest not found/); 51 | } 52 | }); 53 | 54 | it(`the manifest is an empty JSON`, 55 | async (): Promise => { 56 | existsAsyncStub.resolves(true); 57 | readFileAsyncStub.resolves('{\n }'); 58 | try { 59 | await Integrity.getManifestIntegrity(); 60 | } catch (error) { 61 | expect(existsAsyncStub.calledOnce).to.be.true; 62 | expect(readFileAsyncStub.calledOnce).to.be.true; 63 | expect(error).to.match(/Error: Manifest not found/); 64 | } 65 | }); 66 | 67 | }); 68 | 69 | context('to get the manifest integrity object', (): void => { 70 | let getManifestStub: sinon.SinonStub; 71 | 72 | beforeEach((): void => { 73 | // @ts-ignore 74 | getManifestStub = sandbox.stub(Integrity, 'getManifestInfo'); 75 | }); 76 | 77 | it(`when it's found`, 78 | async (): Promise => { 79 | getManifestStub.restore(); 80 | existsAsyncStub.resolves(true); 81 | readFileAsyncStub.resolves('{\n "integrity": {}\n}'); 82 | const sut = await Integrity.getManifestIntegrity(); 83 | expect(existsAsyncStub.calledOnce).to.be.true; 84 | expect(readFileAsyncStub.calledOnce).to.be.true; 85 | expect(sut).to.be.equal('{}'); 86 | }); 87 | 88 | it('using the indentation indent', 89 | async (): Promise => { 90 | getManifestStub.resolves({ manifest: { integrity: { hash: '' } }, indentation: { indent: ' ' } }); 91 | const sut = await Integrity.getManifestIntegrity(); 92 | getManifestStub.restore(); 93 | expect(getManifestStub.calledOnce).to.be.true; 94 | expect(sut).to.equal('{\n "hash": ""\n}'); 95 | }); 96 | 97 | it('using the indentation amount', 98 | async (): Promise => { 99 | // @ts-ignore 100 | getManifestStub.resolves({ manifest: { integrity: { hash: '' } }, indentation: { amount: 2 } }); 101 | const sut = await Integrity.getManifestIntegrity(); 102 | getManifestStub.restore(); 103 | expect(getManifestStub.calledOnce).to.be.true; 104 | expect(sut).to.equal('{\n "hash": ""\n}'); 105 | }); 106 | 107 | }); 108 | 109 | }); 110 | 111 | }); 112 | -------------------------------------------------------------------------------- /test/api/persist.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { PathLike, WriteFileOptions } from 'fs'; 4 | import path from 'path'; 5 | import sinon, { createSandbox } from 'sinon'; 6 | import { Integrity } from '../../src/app/integrity'; 7 | import * as fsAsync from '../../src/common/fsAsync'; 8 | import { IntegrityObject } from '../../src/interfaces/integrityObject'; 9 | 10 | describe(`Integrity: function 'persist' tests`, (): void => { 11 | 12 | context('expects', (): void => { 13 | 14 | type WriteFileType = [PathLike | number, string | NodeJS.ArrayBufferView, (WriteFileOptions)?]; 15 | 16 | let sandbox: sinon.SinonSandbox; 17 | let writeFileAsyncStub: sinon.SinonStub>; 18 | let integrityTestFilename: string; 19 | let integrityTestObject: IntegrityObject; 20 | let fixturesDirPath: string; 21 | 22 | before((): void => { 23 | integrityTestFilename = '.integrity.json'; 24 | }); 25 | 26 | beforeEach((): void => { 27 | sandbox = createSandbox(); 28 | writeFileAsyncStub = sandbox.stub(fsAsync, 'writeFileAsync'); 29 | fixturesDirPath = path.resolve(__dirname, '../../../test/fixtures'); 30 | integrityTestObject = { hashes: {}, version: '' }; 31 | }); 32 | 33 | afterEach((): void => { 34 | sandbox.restore(); 35 | }); 36 | 37 | context('to persist the created hash file', (): void => { 38 | 39 | it('on the provided path', 40 | async (): Promise => { 41 | const dirPath = path.resolve(fixturesDirPath, integrityTestFilename); 42 | const data = '{\n "hashes": {},\n "version": ""\n}'; 43 | await Integrity.persist(integrityTestObject, fixturesDirPath, true); 44 | expect(writeFileAsyncStub.calledOnceWithExactly(dirPath, data)).to.be.true; 45 | }); 46 | 47 | it('on the default path', 48 | async (): Promise => { 49 | const dirPath = path.resolve('./', integrityTestFilename); 50 | const data = '{\n "hashes": {},\n "version": ""\n}'; 51 | await Integrity.persist(integrityTestObject, undefined, true); 52 | expect(writeFileAsyncStub.calledOnceWithExactly(dirPath, data)).to.be.true; 53 | }); 54 | 55 | it('without prettifying the integrity object', 56 | async (): Promise => { 57 | const dirPath = path.resolve(fixturesDirPath, integrityTestFilename); 58 | const data = '{"hashes":{},"version":""}'; 59 | await Integrity.persist(integrityTestObject, fixturesDirPath); 60 | expect(writeFileAsyncStub.calledOnceWithExactly(dirPath, data)).to.be.true; 61 | }); 62 | }); 63 | 64 | }); 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /test/api/updateManifestIntegrity.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { PathLike, WriteFileOptions } from 'fs'; 4 | import sinon, { createSandbox, match } from 'sinon'; 5 | import { Integrity } from '../../src/app/integrity'; 6 | import * as fsAsync from '../../src/common/fsAsync'; 7 | import { IntegrityObject } from '../../src/interfaces/integrityObject'; 8 | 9 | describe(`Integrity: function 'updateManifestIntegrity' tests`, (): void => { 10 | 11 | context('expects', (): void => { 12 | 13 | type WriteFileType = [PathLike | number, string | NodeJS.ArrayBufferView, (WriteFileOptions)?]; 14 | 15 | context('to update the manifest with the integrity object', (): void => { 16 | 17 | let sandbox: sinon.SinonSandbox; 18 | let writeFileAsyncStub: sinon.SinonStub>; 19 | let getManifestStub: sinon.SinonStub; 20 | let integrityTestObject: IntegrityObject; 21 | 22 | beforeEach((): void => { 23 | sandbox = createSandbox(); 24 | writeFileAsyncStub = sandbox.stub(fsAsync, 'writeFileAsync'); 25 | // @ts-ignore 26 | getManifestStub = sandbox.stub(Integrity, 'getManifestInfo'); 27 | integrityTestObject = { hashes: {}, version: '' }; 28 | }); 29 | 30 | afterEach((): void => { 31 | sandbox.restore(); 32 | }); 33 | 34 | it('using the indentation indent', 35 | async (): Promise => { 36 | getManifestStub.resolves({ manifest: {}, indentation: { indent: ' ' } }); 37 | await Integrity.updateManifestIntegrity(integrityTestObject); 38 | expect(getManifestStub.calledOnce).to.be.true; 39 | expect(writeFileAsyncStub.calledOnceWithExactly(match(/package\.json/), 40 | '{\n "integrity": {\n "hashes": {},\n "version": ""\n }\n}')) 41 | .to.be.true; 42 | }); 43 | 44 | it('using the indentation amount', 45 | async (): Promise => { 46 | getManifestStub.resolves({ manifest: {}, indentation: { amount: 2 } }); 47 | await Integrity.updateManifestIntegrity(integrityTestObject); 48 | expect(getManifestStub.calledOnce).to.be.true; 49 | expect(writeFileAsyncStub.calledOnceWithExactly(match(/package\.json/), 50 | '{\n "integrity": {\n "hashes": {},\n "version": ""\n }\n}')) 51 | .to.be.true; 52 | }); 53 | 54 | it('replacing the existing manifest integrity property', 55 | async (): Promise => { 56 | getManifestStub.resolves({ manifest: { integrity: { hash: '' } }, indentation: { amount: 2 } }); 57 | await Integrity.updateManifestIntegrity(integrityTestObject); 58 | expect(getManifestStub.calledOnce).to.be.true; 59 | expect(writeFileAsyncStub.calledOnceWithExactly(match(/package\.json/), 60 | '{\n "integrity": {\n "hashes": {},\n "version": ""\n }\n}')) 61 | .to.be.true; 62 | }); 63 | }); 64 | 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /test/cli/index.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { EventEmitter } from 'events'; 4 | import readline, { ReadLine } from 'readline'; 5 | import sinon, { createSandbox } from 'sinon'; 6 | import { IntegrityOptions } from '../../src'; 7 | import { Integrity } from '../../src/app/integrity'; 8 | import nsri from '../../src/cli/index'; 9 | import { ConfigExplorer } from '../../src/common/configExplorer'; 10 | import { Logger } from '../../src/common/logger'; 11 | import { YargsParser } from '../../src/common/yargsParser'; 12 | import { ParsedArgs } from '../../src/interfaces//parsedArgs'; 13 | import { IntegrityObject } from '../../src/interfaces/integrityObject'; 14 | 15 | describe('CLI: tests', (): void => { 16 | 17 | let sandbox: sinon.SinonSandbox; 18 | let pargs: ParsedArgs; 19 | let configExplorerStub: sinon.SinonStub<[], Promise>; 20 | let ypParseStub: sinon.SinonStub<[], Promise>; 21 | let icCreateStub: sinon.SinonStub<[string, (IntegrityOptions | undefined)?], Promise>; 22 | let icCheckStub: sinon.SinonStub<[string, string, IntegrityOptions?], Promise>; 23 | let isTTY: boolean; 24 | 25 | beforeEach((): void => { 26 | pargs = { 27 | command: '', 28 | dirAlgorithm: 'md5', 29 | encoding: 'hex', 30 | exclude: [], 31 | fileAlgorithm: 'md5', 32 | inPath: '', 33 | integrity: '', 34 | manifest: false, 35 | outPath: './', 36 | pretty: false, 37 | strict: false, 38 | verbose: false, 39 | }; 40 | sandbox = createSandbox(); 41 | ypParseStub = sandbox.stub(YargsParser.prototype, 'parse').resolves(pargs); 42 | configExplorerStub = sandbox.stub(ConfigExplorer.prototype, 'assignArgs').resolves(); 43 | icCreateStub = sandbox.stub(Integrity, 'create'); 44 | icCheckStub = sandbox.stub(Integrity, 'check'); 45 | sandbox.stub(Integrity, 'persist'); 46 | sandbox.stub(Integrity, 'updateManifestIntegrity'); 47 | sandbox.stub(Integrity, 'getManifestIntegrity'); 48 | isTTY = process.stdout.isTTY; 49 | process.stdout.setMaxListeners(Infinity); 50 | process.stdin.setMaxListeners(Infinity); 51 | }); 52 | 53 | afterEach((): void => { 54 | process.stdout.isTTY = isTTY; 55 | sandbox.restore(); 56 | }); 57 | 58 | context('expects', (): void => { 59 | 60 | context('to log', (): void => { 61 | 62 | context('process messages', (): void => { 63 | 64 | it('when creating an integrity file', 65 | async (): Promise => { 66 | pargs.command = 'create'; 67 | pargs.manifest = false; 68 | const exitStub = sandbox.stub(process, 'exit'); 69 | const consoleLogStub = sandbox.stub(console, 'log'); 70 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 71 | const spinnerLogStartSpy = sandbox.spy(Logger.prototype, 'spinnerLogStart'); 72 | const spinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 73 | await nsri(); 74 | stdoutStub.restore(); 75 | consoleLogStub.restore(); 76 | exitStub.restore(); 77 | expect(spinnerLogStopSpy.calledOnce).to.be.true; 78 | expect(spinnerLogStartSpy.calledOnce).to.be.true; 79 | expect(spinnerLogStartSpy.calledBefore(spinnerLogStopSpy)).to.be.true; 80 | expect(spinnerLogStartSpy.calledWith('Creating integrity hash')).to.be.true; 81 | const returnValue = spinnerLogStartSpy.returnValues[0]; 82 | expect(spinnerLogStopSpy.calledWith(returnValue, 'Integrity hash file created')).to.be.true; 83 | }); 84 | 85 | it('when creating an integrity property in manifest', 86 | async (): Promise => { 87 | pargs.command = 'create'; 88 | pargs.manifest = true; 89 | const exitStub = sandbox.stub(process, 'exit'); 90 | const consoleLogStub = sandbox.stub(console, 'log'); 91 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 92 | const spinnerLogStartSpy = sandbox.spy(Logger.prototype, 'spinnerLogStart'); 93 | const spinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 94 | await nsri(); 95 | stdoutStub.restore(); 96 | consoleLogStub.restore(); 97 | exitStub.restore(); 98 | expect(spinnerLogStopSpy.calledOnce).to.be.true; 99 | expect(spinnerLogStartSpy.calledOnce).to.be.true; 100 | expect(spinnerLogStartSpy.calledBefore(spinnerLogStopSpy)).to.be.true; 101 | expect(spinnerLogStartSpy.calledWith('Creating integrity hash')).to.be.true; 102 | const returnValue = spinnerLogStartSpy.returnValues[0]; 103 | expect(spinnerLogStopSpy.calledWith(returnValue, 'Integrity hash created -> Manifest updated')).to.be.true; 104 | }); 105 | 106 | context('when integrity check passes against an integrity', (): void => { 107 | 108 | it('file', 109 | async (): Promise => { 110 | pargs.command = 'check'; 111 | pargs.manifest = false; 112 | icCheckStub.resolves(true); 113 | const exitStub = sandbox.stub(process, 'exit'); 114 | const consoleLogStub = sandbox.stub(console, 'log'); 115 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 116 | const spinnerLogStartSpy = sandbox.spy(Logger.prototype, 'spinnerLogStart'); 117 | const spinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 118 | await nsri(); 119 | stdoutStub.restore(); 120 | consoleLogStub.restore(); 121 | exitStub.restore(); 122 | expect(spinnerLogStopSpy.calledOnce).to.be.true; 123 | expect(spinnerLogStartSpy.calledOnce).to.be.true; 124 | expect(spinnerLogStartSpy.calledBefore(spinnerLogStopSpy)).to.be.true; 125 | expect(spinnerLogStartSpy.calledWith(`Checking integrity of: '${pargs.inPath}'`)).to.be.true; 126 | const returnValue = spinnerLogStartSpy.returnValues[0]; 127 | expect(spinnerLogStopSpy.calledWith(returnValue, 'Integrity validated')).to.be.true; 128 | }); 129 | 130 | it('in manifest', 131 | async (): Promise => { 132 | pargs.command = 'check'; 133 | pargs.manifest = true; 134 | icCheckStub.resolves(true); 135 | const exitStub = sandbox.stub(process, 'exit'); 136 | const consoleLogStub = sandbox.stub(console, 'log'); 137 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 138 | const spinnerLogStartSpy = sandbox.spy(Logger.prototype, 'spinnerLogStart'); 139 | const spinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 140 | await nsri(); 141 | stdoutStub.restore(); 142 | consoleLogStub.restore(); 143 | exitStub.restore(); 144 | expect(spinnerLogStopSpy.calledOnce).to.be.true; 145 | expect(spinnerLogStartSpy.calledOnce).to.be.true; 146 | expect(spinnerLogStartSpy.calledBefore(spinnerLogStopSpy)).to.be.true; 147 | expect(spinnerLogStartSpy.calledWith(`Checking integrity of: '${pargs.inPath}'`)).to.be.true; 148 | const returnValue = spinnerLogStartSpy.returnValues[0]; 149 | expect(spinnerLogStopSpy.calledWith(returnValue, 'Integrity validated')).to.be.true; 150 | }); 151 | 152 | }); 153 | 154 | it('when integrity check fails', 155 | async (): Promise => { 156 | pargs.command = 'check'; 157 | icCheckStub.resolves(false); 158 | const exitStub = sandbox.stub(process, 'exit'); 159 | const consoleLogStub = sandbox.stub(console, 'log'); 160 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 161 | const spinnerLogStartSpy = sandbox.spy(Logger.prototype, 'spinnerLogStart'); 162 | const spinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 163 | await nsri(); 164 | stdoutStub.restore(); 165 | consoleLogStub.restore(); 166 | exitStub.restore(); 167 | expect(spinnerLogStopSpy.calledOnce).to.be.true; 168 | expect(spinnerLogStartSpy.calledOnce).to.be.true; 169 | expect(spinnerLogStartSpy.calledBefore(spinnerLogStopSpy)).to.be.true; 170 | expect(spinnerLogStartSpy.calledWith(`Checking integrity of: '${pargs.inPath}'`)).to.be.true; 171 | const returnValue = spinnerLogStartSpy.returnValues[0]; 172 | expect(spinnerLogStopSpy.calledWith(returnValue, 'Integrity check failed')).to.be.true; 173 | }); 174 | 175 | }); 176 | 177 | it('informative messages', 178 | async (): Promise => { 179 | process.stdout.isTTY = true; 180 | pargs.command = 'check'; 181 | const exitStub = sandbox.stub(process, 'exit'); 182 | const consoleLogStub = sandbox.stub(console, 'log'); 183 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 184 | const loggerLogSpy = sandbox.spy(Logger.prototype, 'log'); 185 | const loggerUpdateLogSpy = sandbox.spy(Logger.prototype, 'updateLog'); 186 | await nsri(); 187 | stdoutStub.restore(); 188 | consoleLogStub.restore(); 189 | exitStub.restore(); 190 | expect(loggerLogSpy.called).to.be.true; 191 | expect(loggerUpdateLogSpy.callCount).to.equal(2); 192 | }); 193 | 194 | it('Error messages', 195 | async (): Promise => { 196 | process.stdout.isTTY = true; 197 | pargs.command = 'check'; 198 | const error = new Error('test'); 199 | icCheckStub.throws(error); 200 | const exitStub = sandbox.stub(process, 'exit'); 201 | const consoleLogStub = sandbox.stub(console, 'log'); 202 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 203 | const loggerSpinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 204 | const loggerUpdateLogSpy = sandbox.spy(Logger.prototype, 'updateLog'); 205 | await nsri(); 206 | stdoutStub.restore(); 207 | consoleLogStub.restore(); 208 | exitStub.restore(); 209 | expect(loggerSpinnerLogStopSpy.calledOnce).to.be.true; 210 | expect(loggerUpdateLogSpy.callCount).to.equal(3); 211 | expect(loggerUpdateLogSpy.thirdCall.calledWithMatch(error.message)).to.be.true; 212 | }); 213 | 214 | it('Error', 215 | async (): Promise => { 216 | process.stdout.isTTY = true; 217 | pargs.command = 'check'; 218 | const error = new Error(); 219 | icCheckStub.throws(error); 220 | const exitStub = sandbox.stub(process, 'exit'); 221 | const consoleLogStub = sandbox.stub(console, 'log'); 222 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 223 | const loggerSpinnerLogStopSpy = sandbox.spy(Logger.prototype, 'spinnerLogStop'); 224 | const loggerUpdateLogSpy = sandbox.spy(Logger.prototype, 'updateLog'); 225 | await nsri(); 226 | stdoutStub.restore(); 227 | consoleLogStub.restore(); 228 | exitStub.restore(); 229 | expect(loggerSpinnerLogStopSpy.calledOnce).to.be.true; 230 | expect(loggerUpdateLogSpy.callCount).to.equal(3); 231 | expect(loggerUpdateLogSpy.thirdCall.calledWithMatch(error.toString())).to.be.true; 232 | }); 233 | 234 | }); 235 | 236 | context('to call', (): void => { 237 | 238 | it(`the ConfigExplorer 'assignArgs' function`, 239 | async (): Promise => { 240 | pargs.command = 'check'; 241 | const exitStub = sandbox.stub(process, 'exit'); 242 | const consoleLogStub = sandbox.stub(console, 'log'); 243 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 244 | await nsri(); 245 | stdoutStub.restore(); 246 | consoleLogStub.restore(); 247 | exitStub.restore(); 248 | expect(configExplorerStub.calledOnce).to.be.true; 249 | }); 250 | 251 | it(`the YargsParser 'parse' function`, 252 | async (): Promise => { 253 | pargs.command = 'check'; 254 | const exitStub = sandbox.stub(process, 'exit'); 255 | const consoleLogStub = sandbox.stub(console, 'log'); 256 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 257 | await nsri(); 258 | stdoutStub.restore(); 259 | consoleLogStub.restore(); 260 | exitStub.restore(); 261 | expect(ypParseStub.calledOnce).to.be.true; 262 | }); 263 | 264 | it(`the Integrity 'create' function`, 265 | async (): Promise => { 266 | pargs.command = 'create'; 267 | const exitStub = sandbox.stub(process, 'exit'); 268 | const consoleLogStub = sandbox.stub(console, 'log'); 269 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 270 | await nsri(); 271 | stdoutStub.restore(); 272 | consoleLogStub.restore(); 273 | exitStub.restore(); 274 | expect(icCreateStub.calledOnce).to.be.true; 275 | }); 276 | 277 | it(`the Integrity 'check' function`, 278 | async (): Promise => { 279 | pargs.command = 'check'; 280 | const exitStub = sandbox.stub(process, 'exit'); 281 | const consoleLogStub = sandbox.stub(console, 'log'); 282 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 283 | await nsri(); 284 | stdoutStub.restore(); 285 | consoleLogStub.restore(); 286 | exitStub.restore(); 287 | expect(icCheckStub.calledOnce).to.be.true; 288 | }); 289 | 290 | }); 291 | 292 | context('when signaled to exit', (): void => { 293 | 294 | it(`to call 'handleForcedExit'`, 295 | (): Promise => { 296 | pargs.command = 'create'; 297 | const exitStub = sandbox.stub(process, 'exit'); 298 | const consoleLogStub = sandbox.stub(console, 'log'); 299 | const stdoutStub = sandbox.stub(process.stdout, 'write'); 300 | const handleForcedExitStub = sandbox.stub(Logger.prototype, 'handleForcedExit'); 301 | const emitter = new EventEmitter(); 302 | sandbox.stub(readline, 'createInterface').returns(emitter as ReadLine); 303 | const promise = nsri().then((): void => { 304 | consoleLogStub.restore(); 305 | stdoutStub.restore(); 306 | exitStub.restore(); 307 | expect(handleForcedExitStub.called).to.be.true; 308 | }); 309 | emitter.emit('SIGINT'); 310 | return promise; 311 | }); 312 | 313 | }); 314 | 315 | }); 316 | 317 | }); 318 | -------------------------------------------------------------------------------- /test/common/configExplorer.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { join, resolve } from 'path'; 4 | import sinon, { createSandbox } from 'sinon'; 5 | import { ConfigExplorer } from '../../src/common/configExplorer'; 6 | import { ConfigOptions } from '../../src/interfaces/configOptions'; 7 | 8 | describe('ConfigExplorer: tests', (): void => { 9 | 10 | context('expects', (): void => { 11 | 12 | let sandbox: sinon.SinonSandbox; 13 | let configExplorer: ConfigExplorer; 14 | let getConfigStub: sinon.SinonStub<[(string | undefined)?], Promise>; 15 | let baseConfigDirPath: string; 16 | 17 | beforeEach((): void => { 18 | sandbox = createSandbox(); 19 | configExplorer = new ConfigExplorer(); 20 | baseConfigDirPath = resolve(__dirname, '../../../test/cosmiconfig/'); 21 | }); 22 | 23 | afterEach((): void => { 24 | sandbox.restore(); 25 | }); 26 | 27 | context('when calling', (): void => { 28 | 29 | context(`function 'assignArgs'`, (): void => { 30 | beforeEach((): void => { 31 | getConfigStub = sandbox.stub(ConfigExplorer.prototype, 'getConfig'); 32 | sandbox.stub(process, 'argv').value([]); 33 | }); 34 | 35 | it('to simply return when no configuration section is found', 36 | async (): Promise => { 37 | getConfigStub.resolves({}); 38 | await configExplorer.assignArgs(); 39 | expect(process.argv).to.eql([]); 40 | }); 41 | 42 | context('to assign to the process arguments', (): void => { 43 | 44 | it(`the 'manifest' value`, 45 | async (): Promise => { 46 | getConfigStub.resolves({ manifest: true }); 47 | await configExplorer.assignArgs(); 48 | expect(process.argv).to.contain('-m').and.to.contain('true'); 49 | }); 50 | 51 | it(`the 'source' value`, 52 | async (): Promise => { 53 | getConfigStub.resolves({ source: '/some/path/' }); 54 | await configExplorer.assignArgs(); 55 | expect(process.argv).to.contain('-s').and.to.contain('/some/path/'); 56 | }); 57 | 58 | it(`the 'verbose' value`, 59 | async (): Promise => { 60 | getConfigStub.resolves({ verbose: true }); 61 | await configExplorer.assignArgs(); 62 | expect(process.argv).to.contain('-v').and.to.contain('true'); 63 | }); 64 | 65 | it(`the 'strict' value`, 66 | async (): Promise => { 67 | getConfigStub.resolves({ strict: true }); 68 | await configExplorer.assignArgs(); 69 | expect(process.argv).to.contain('-st').and.to.contain('true'); 70 | }); 71 | 72 | it(`the 'diralgorithm' value`, 73 | async (): Promise => { 74 | getConfigStub.resolves({ cryptoOptions: { dirAlgorithm: 'sha512' } }); 75 | await configExplorer.assignArgs(); 76 | expect(process.argv).to.contain('-da').and.to.contain('sha512'); 77 | }); 78 | 79 | it(`the 'filealgorithm' value`, 80 | async (): Promise => { 81 | getConfigStub.resolves({ cryptoOptions: { fileAlgorithm: 'sha1' } }); 82 | await configExplorer.assignArgs(); 83 | expect(process.argv).to.contain('-fa').and.to.contain('sha1'); 84 | }); 85 | 86 | it(`the 'encoding' value`, 87 | async (): Promise => { 88 | getConfigStub.resolves({ cryptoOptions: { encoding: 'base64' } }); 89 | await configExplorer.assignArgs(); 90 | expect(process.argv).to.contain('-e').and.to.contain('base64'); 91 | }); 92 | 93 | it(`the 'exclude' value`, 94 | async (): Promise => { 95 | getConfigStub.resolves({ exclude: ['dir', 'file'] }); 96 | await configExplorer.assignArgs(); 97 | expect(process.argv).to.contain('-x').and.to.contain('dir').and.to.contain('file'); 98 | }); 99 | 100 | it(`the 'integrity' value`, 101 | async (): Promise => { 102 | getConfigStub.resolves({ integrity: '/path/to/integrity/' }); 103 | await configExplorer.assignArgs(); 104 | expect(process.argv).to.contain('-i').and.to.contain('/path/to/integrity/'); 105 | }); 106 | 107 | it(`the 'output' value`, 108 | async (): Promise => { 109 | getConfigStub.resolves({ output: '/' }); 110 | await configExplorer.assignArgs(); 111 | expect(process.argv).to.contain('-o').and.to.contain('/'); 112 | }); 113 | 114 | }); 115 | 116 | }); 117 | 118 | context(`function 'getConfig'`, (): void => { 119 | 120 | it(`to throw an Error when 'explorer' is not initialized`, 121 | async (): Promise => { 122 | // @ts-ignore 123 | sandbox.stub(configExplorer, 'explorer').value(undefined); 124 | try { 125 | await configExplorer.getConfig(); 126 | } catch (error) { 127 | expect(error).to.be.an.instanceOf(Error).and.match(/CosmiConfig not initialized/); 128 | } 129 | }); 130 | 131 | it(`to return an empty object when 'explorer' returns 'null'`, 132 | async (): Promise => { 133 | // @ts-ignore 134 | sandbox.stub(configExplorer.explorer, 'search').resolves(null); 135 | const config = await configExplorer.getConfig(); 136 | expect(config).to.be.an('object').that.is.empty; 137 | }); 138 | 139 | context('to retrieve the configuration values from', (): void => { 140 | 141 | it(`a 'package.json' file`, 142 | async (): Promise => { 143 | const dirPath = join(baseConfigDirPath, 'packagejson'); 144 | const config = await configExplorer.getConfig(dirPath); 145 | expect(config).to.haveOwnProperty('source'); 146 | expect(config).to.haveOwnProperty('verbose'); 147 | expect(config).to.haveOwnProperty('exclude'); 148 | }); 149 | 150 | it(`an 'rc' file`, 151 | async (): Promise => { 152 | const dirPath = join(baseConfigDirPath, 'rc'); 153 | const config = await configExplorer.getConfig(dirPath); 154 | expect(config).to.haveOwnProperty('source'); 155 | expect(config).to.haveOwnProperty('verbose'); 156 | expect(config).to.haveOwnProperty('exclude'); 157 | }); 158 | 159 | it(`a '.config.js' file`, 160 | async (): Promise => { 161 | const dirPath = join(baseConfigDirPath, 'configjs'); 162 | const config = await configExplorer.getConfig(dirPath); 163 | expect(config).to.haveOwnProperty('source'); 164 | expect(config).to.haveOwnProperty('verbose'); 165 | expect(config).to.haveOwnProperty('exclude'); 166 | }); 167 | 168 | it(`a 'json' file`, 169 | async (): Promise => { 170 | const dirPath = join(baseConfigDirPath, 'json'); 171 | const config = await configExplorer.getConfig(dirPath); 172 | expect(config).to.haveOwnProperty('verbose'); 173 | expect(config).to.haveOwnProperty('exclude'); 174 | }); 175 | 176 | it(`a 'js' file`, 177 | async (): Promise => { 178 | const dirPath = join(baseConfigDirPath, 'js'); 179 | const config = await configExplorer.getConfig(dirPath); 180 | expect(config).to.haveOwnProperty('source'); 181 | expect(config).to.haveOwnProperty('verbose'); 182 | expect(config).to.haveOwnProperty('exclude'); 183 | }); 184 | 185 | it(`a 'yaml' file`, 186 | async (): Promise => { 187 | const dirPath = join(baseConfigDirPath, 'yaml'); 188 | const config = await configExplorer.getConfig(dirPath); 189 | expect(config).to.haveOwnProperty('source'); 190 | expect(config).to.haveOwnProperty('verbose'); 191 | expect(config).to.haveOwnProperty('exclude'); 192 | }); 193 | 194 | it(`a 'yml' file`, 195 | async (): Promise => { 196 | const dirPath = join(baseConfigDirPath, 'yml'); 197 | const config = await configExplorer.getConfig(dirPath); 198 | expect(config).to.haveOwnProperty('source'); 199 | expect(config).to.haveOwnProperty('verbose'); 200 | expect(config).to.haveOwnProperty('exclude'); 201 | }); 202 | 203 | }); 204 | 205 | }); 206 | 207 | }); 208 | 209 | }); 210 | 211 | }); 212 | -------------------------------------------------------------------------------- /test/common/utils.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import * as utils from '../../src/common/utils'; 4 | 5 | describe('Utils: tests', (): void => { 6 | 7 | context('expects', (): void => { 8 | 9 | context(`function 'parseJSONSafe'`, (): void => { 10 | 11 | context('to return a JSON when passed parameter is of type', (): void => { 12 | 13 | it('string', 14 | (): void => { 15 | const data = '{"some": "valid JSON"}'; 16 | expect(utils.parseJSONSafe(data)).to.eql({ some: 'valid JSON' }); 17 | }); 18 | 19 | it('Buffer', 20 | (): void => { 21 | const data = Buffer.from('{"some": "valid JSON"}'); 22 | expect(utils.parseJSONSafe(data)).to.eql({ some: 'valid JSON' }); 23 | }); 24 | 25 | }); 26 | 27 | it(`to return an empty JSON when provided text is not a valid JSON`, 28 | (): void => { 29 | const text = 'some invalid json'; 30 | expect(utils.parseJSONSafe(text)).to.be.empty; 31 | }); 32 | 33 | }); 34 | 35 | context(`function 'sortObject'`, (): void => { 36 | 37 | it('to sort the object properties', 38 | (): void => { 39 | const sut = { c: [], a: '', d: {}, b: 0 }; 40 | const expectedObj = { a: '', b: 0, c: [], d: {} }; 41 | expect(utils.sortObject(sut)).to.eql(expectedObj); 42 | }); 43 | 44 | }); 45 | 46 | context(`function 'getIndentation'`, (): void => { 47 | 48 | it('to return indent info', 49 | (): void => { 50 | const info = utils.getIndentation(' '); 51 | expect(info.amount).to.equal(2); 52 | expect(info.indent).to.equal(' '); 53 | expect(info.type).to.equal('space'); 54 | }); 55 | 56 | }); 57 | 58 | context(`function 'normalizeEntries'`, (): void => { 59 | 60 | it('to return the provided entries normalized', 61 | (): void => { 62 | const entries = [ 63 | [' *', '#comment', ' # another comment', '*/ ', ' !dist', ' ', ''], 64 | ['*', '*/', '!dist'], 65 | ]; 66 | const normalEntries = utils.normalizeEntries(entries[0]); 67 | expect(normalEntries).to.eql(entries[1]); 68 | }); 69 | 70 | context(`function 'unique'`, (): void => { 71 | 72 | it('to return unique entries', 73 | (): void => { 74 | const entries = [ 75 | ['one', 'two', 'one'], 76 | ['one', 'two'], 77 | ]; 78 | 79 | const uniqueEntries = utils.unique(entries[0]); 80 | expect(uniqueEntries).to.eql(entries[1]); 81 | }); 82 | 83 | }); 84 | 85 | }); 86 | 87 | }); 88 | 89 | }); 90 | -------------------------------------------------------------------------------- /test/common/yargsParser.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import sinon, { createSandbox } from 'sinon'; 6 | import { YargsParser } from '../../src/common/yargsParser'; 7 | 8 | describe('YargsParser: tests', (): void => { 9 | 10 | let sandbox: sinon.SinonSandbox; 11 | let argv: sinon.SinonStub; 12 | let parser: YargsParser; 13 | let args: string[]; 14 | let fsStatsMock: sinon.SinonStubbedInstance; 15 | 16 | beforeEach((): void => { 17 | sandbox = createSandbox(); 18 | argv = sandbox.stub(process, 'argv'); 19 | fsStatsMock = sandbox.createStubInstance(fs.Stats); 20 | parser = new YargsParser(); 21 | args = ['node', 'nsri', 'create', '-s', '.']; 22 | }); 23 | 24 | afterEach((): void => { 25 | sandbox.restore(); 26 | }); 27 | 28 | context('expects', (): void => { 29 | 30 | it('the returned parsed arguments object to have the correct properties', 31 | async (): Promise => { 32 | argv.value([...args]); 33 | const sut = await parser.parse(); 34 | const props = ['dirAlgorithm', 'fileAlgorithm', 'command', 'encoding', 35 | 'exclude', 'inPath', 'integrity', 'manifest', 'outPath', 'pretty', 36 | 'strict', 'verbose']; 37 | expect(sut).to.be.an('object'); 38 | props.forEach((prop: string): Chai.Assertion => expect(sut).to.be.haveOwnProperty(prop)); 39 | expect(Object.keys(sut)).with.length(props.length); 40 | }); 41 | 42 | it(`that the 'fileAlgorithm' option gets parsed correctly`, 43 | async (): Promise => { 44 | args = [...args, '--fa', 'sha']; 45 | argv.value(args); 46 | const pargs = await parser.parse(); 47 | expect(pargs).to.be.have.property('fileAlgorithm', args[6]); 48 | }); 49 | 50 | it(`that the 'dirAlgorithm' option gets parsed correctly`, 51 | async (): Promise => { 52 | args = [...args, '--da', 'sha']; 53 | argv.value(args); 54 | const pargs = await parser.parse(); 55 | expect(pargs).to.be.have.property('dirAlgorithm', args[6]); 56 | }); 57 | 58 | it(`that the 'command' gets parsed correctly`, 59 | async (): Promise => { 60 | argv.value([...args]); 61 | const pargs = await parser.parse(); 62 | expect(pargs).to.be.have.property('command', args[2]); 63 | }); 64 | 65 | it(`that the 'encoding' option gets parsed correctly`, 66 | async (): Promise => { 67 | args = [...args, '-e', 'base64']; 68 | argv.value(args); 69 | const pargs = await parser.parse(); 70 | expect(pargs).to.be.have.property('encoding', args[6]); 71 | }); 72 | 73 | it(`that the 'exclude' option gets parsed correctly`, 74 | async (): Promise => { 75 | args = [...args, '-x', 'some/path']; 76 | argv.value(args); 77 | const pargs = await parser.parse(); 78 | expect(pargs).to.be.have.property('exclude').with.members([args[6]]); 79 | }); 80 | 81 | it(`that the 'inPath' option gets parsed correctly`, 82 | async (): Promise => { 83 | args = [...args]; 84 | argv.value(args); 85 | const pargs = await parser.parse(); 86 | expect(pargs).to.be.have.property('inPath', args[4]); 87 | }); 88 | 89 | it(`that the 'integrity' option gets parsed correctly`, 90 | async (): Promise => { 91 | args.splice(2, 1, 'check'); 92 | args = [...args, '-i', '123456789']; 93 | argv.value(args); 94 | const pargs = await parser.parse(); 95 | expect(pargs).to.be.have.property('integrity', args[6]); 96 | }); 97 | 98 | it(`that the 'manifest' option gets parsed correctly`, 99 | async (): Promise => { 100 | args = [...args, '-m', 'true']; 101 | argv.value(args); 102 | const pargs = await parser.parse(); 103 | expect(pargs).to.be.have.property('manifest', true); 104 | }); 105 | 106 | it(`that the 'outPath' option gets parsed correctly`, 107 | async (): Promise => { 108 | args = [...args, '-o', './out']; 109 | argv.value(args); 110 | const pargs = await parser.parse(); 111 | expect(pargs).to.be.have.property('outPath', args[6]); 112 | }); 113 | 114 | it(`that the 'pretty' option gets parsed correctly`, 115 | async (): Promise => { 116 | args = [...args, '-p', 'true']; 117 | argv.value(args); 118 | const pargs = await parser.parse(); 119 | expect(pargs).to.be.have.property('pretty', true); 120 | }); 121 | 122 | it(`that the 'strict' option gets parsed correctly`, 123 | async (): Promise => { 124 | args = [...args, '--st', 'true']; 125 | argv.value(args); 126 | const pargs = await parser.parse(); 127 | expect(pargs).to.be.have.property('strict', true); 128 | }); 129 | 130 | it(`that the 'verbose' option gets parsed correctly`, 131 | async (): Promise => { 132 | args = [...args, '-v', 'true']; 133 | argv.value(args); 134 | const pargs = await parser.parse(); 135 | expect(pargs).to.be.have.property('verbose', true); 136 | }); 137 | 138 | it('to throw an Error on invalid file path', 139 | async (): Promise => { 140 | const consoleErrorStub = sandbox.stub(console, 'error'); 141 | const stderrStub = sandbox.stub(process.stderr, 'write'); 142 | const exitStub = sandbox.stub(process, 'exit'); 143 | fsStatsMock.isFile.returns(true); 144 | const statStub = sandbox.stub(fs, 'statSync').returns(fsStatsMock); 145 | args.pop(); 146 | args.push('file.io'); 147 | argv.value(args); 148 | await parser.parse(); 149 | expect(consoleErrorStub.called).to.be.true; 150 | expect(consoleErrorStub.thirdCall 151 | .calledWithExactly(`ENOENT: no such file or directory, 'file.io'`)) 152 | .to.be.true; 153 | expect(exitStub.called).to.be.true; 154 | statStub.restore(); 155 | stderrStub.restore(); 156 | consoleErrorStub.restore(); 157 | exitStub.restore(); 158 | }); 159 | 160 | it(`to throw an Error on invalid use of 'manifest' and 'integrity' options with the 'check' command`, 161 | async (): Promise => { 162 | const consoleErrorStub = sandbox.stub(console, 'error'); 163 | const stderrStub = sandbox.stub(process.stderr, 'write'); 164 | const exitStub = sandbox.stub(process, 'exit'); 165 | fsStatsMock.isFile.returns(true); 166 | const statStub = sandbox.stub(fs, 'statSync').returns(fsStatsMock); 167 | args.splice(2, 1, 'check'); 168 | args.push(...['-m', '-i', '.']); 169 | argv.value(args); 170 | await parser.parse(); 171 | expect(consoleErrorStub.called).to.be.true; 172 | expect(consoleErrorStub.thirdCall 173 | .calledWithExactly('Arguments integrity and manifest are mutually exclusive')) 174 | .to.be.true; 175 | expect(exitStub.called).to.be.true; 176 | statStub.restore(); 177 | stderrStub.restore(); 178 | consoleErrorStub.restore(); 179 | exitStub.restore(); 180 | }); 181 | 182 | it(`to throw an Error on invalid use of 'integrity' options with the 'check' command`, 183 | async (): Promise => { 184 | const consoleErrorStub = sandbox.stub(console, 'error'); 185 | const stderrStub = sandbox.stub(process.stderr, 'write'); 186 | const exitStub = sandbox.stub(process, 'exit'); 187 | fsStatsMock.isFile.returns(true); 188 | const statStub = sandbox.stub(fs, 'statSync').returns(fsStatsMock); 189 | args.splice(2, 1, 'check'); 190 | argv.value(args); 191 | await parser.parse(); 192 | expect(consoleErrorStub.called).to.be.true; 193 | expect(consoleErrorStub.thirdCall 194 | .calledWithExactly('Missing required argument: integrity')) 195 | .to.be.true; 196 | expect(exitStub.called).to.be.true; 197 | statStub.restore(); 198 | stderrStub.restore(); 199 | consoleErrorStub.restore(); 200 | exitStub.restore(); 201 | }); 202 | 203 | context(`that the 'outPath' gets assigned to`, (): void => { 204 | 205 | it(`the input directory when 'input' is a file`, 206 | async (): Promise => { 207 | const filePath = path.resolve(__dirname, '../../../test/fixtures/fileToHash.txt'); 208 | args.pop(); 209 | args.push(filePath); 210 | argv.value(args); 211 | const pargs = await parser.parse(); 212 | expect(pargs.outPath).to.equal(path.dirname(filePath)); 213 | }); 214 | 215 | it(`the input when 'input' is a directory`, 216 | async (): Promise => { 217 | const dirPath = path.resolve(__dirname, '../../../test/fixtures'); 218 | args.pop(); 219 | args.push(dirPath); 220 | argv.value(args); 221 | const pargs = await parser.parse(); 222 | expect(pargs.outPath).to.equal(dirPath); 223 | }); 224 | 225 | }); 226 | 227 | }); 228 | 229 | }); 230 | -------------------------------------------------------------------------------- /test/cosmiconfig/configjs/nsri.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | source: '', 3 | exclude: [], 4 | verbose: false 5 | }; 6 | -------------------------------------------------------------------------------- /test/cosmiconfig/js/.nsrirc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | source: '', 3 | exclude: [], 4 | verbose: false 5 | }; 6 | -------------------------------------------------------------------------------- /test/cosmiconfig/json/.nsrirc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "", 3 | "exclude": [], 4 | "verbose": false 5 | } 6 | -------------------------------------------------------------------------------- /test/cosmiconfig/packagejson/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "nsri": { 3 | "source": "", 4 | "exclude": [], 5 | "verbose": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/cosmiconfig/rc/.nsrirc: -------------------------------------------------------------------------------- 1 | { 2 | source: '', 3 | verbose: false, 4 | exclude: [] 5 | } 6 | -------------------------------------------------------------------------------- /test/cosmiconfig/yaml/.nsrirc.yaml: -------------------------------------------------------------------------------- 1 | source: '' 2 | verbose: true 3 | exclude: [] 4 | -------------------------------------------------------------------------------- /test/cosmiconfig/yml/.nsrirc.yml: -------------------------------------------------------------------------------- 1 | source: '' 2 | verbose: true 3 | exclude: [] 4 | -------------------------------------------------------------------------------- /test/fixtures/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "directory": { 7 | "contents": { 8 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 9 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 10 | }, 11 | "hash": "sha512-Ze62278vNFKc3izakn2FgyvHIZEbnsuqKogaZLA1ihM1zk95RKlz+z7qk1XEysMaoJlpDNqSWx4PoPp2cFNBPw==" 12 | }, 13 | "directory.1": { 14 | "contents": { 15 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 16 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 17 | }, 18 | "hash": "sha512-AwzUWMMnbbRf+pow1dBJlkRAxtQwgm59xDMLa2Gm1pcKDolXTPfPGjRc4a0muGENgeZiM4tf17HfIdEOIlS78Q==" 19 | }, 20 | "fileToHash.txt": "sha1-H58mYNjbMJTkiNvvNfj2YKl3ck0=", 21 | "fixtures": { 22 | "contents": { 23 | "directory": { 24 | "contents": { 25 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=" 26 | }, 27 | "hash": "sha512-a7F1/1x1ZVAWMdcx7X9Dnzd9M4TY9wU21vNCt3ALW0+0npq85MKn7uhz8yGqjDbSmAUDf14uxgqjk2tMtkjK9w==" 28 | }, 29 | "directory.1": { 30 | "contents": { 31 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 32 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 33 | }, 34 | "hash": "sha512-AwzUWMMnbbRf+pow1dBJlkRAxtQwgm59xDMLa2Gm1pcKDolXTPfPGjRc4a0muGENgeZiM4tf17HfIdEOIlS78Q==" 35 | }, 36 | "fileToHash.txt": "sha1-t56X7IQ267Hza0qjpSpqb9UPcfE=" 37 | }, 38 | "hash": "sha512-rDNKFYBCOuaCzpomiZEGyRLAmc3+IU/HoNj7NiKXqLG90rNko74LwpZ1DYKx+/aJptGTKCr/9mP8ggnl4QVNNw==" 39 | }, 40 | "sameContentWithFileToHash.txt": "sha1-l5sOr3meWkHyZWPi2Ln4GM7/lrg=" 41 | }, 42 | "hash": "sha512-WlFP+kAPdHyGd9E8SgkFfxuGvz9l/cqjt8gAhrHDdWLBIkkZGxgxxgpWZuARLVD7ACCxq8rVeNbwNL7NKyeWsA==" 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /test/fixtures/directory.1/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 7 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 8 | }, 9 | "hash": "sha512-AwzUWMMnbbRf+pow1dBJlkRAxtQwgm59xDMLa2Gm1pcKDolXTPfPGjRc4a0muGENgeZiM4tf17HfIdEOIlS78Q==" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/fixtures/directory.1/anotherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of another file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/directory.1/otherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of other file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/directory/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 7 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 8 | }, 9 | "hash": "sha512-Ze62278vNFKc3izakn2FgyvHIZEbnsuqKogaZLA1ihM1zk95RKlz+z7qk1XEysMaoJlpDNqSWx4PoPp2cFNBPw==" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/fixtures/directory/anotherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of another file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/directory/otherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of other file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/fileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/fixtures/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "directory": { 7 | "contents": { 8 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=" 9 | }, 10 | "hash": "sha512-a7F1/1x1ZVAWMdcx7X9Dnzd9M4TY9wU21vNCt3ALW0+0npq85MKn7uhz8yGqjDbSmAUDf14uxgqjk2tMtkjK9w==" 11 | }, 12 | "directory.1": { 13 | "contents": { 14 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 15 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 16 | }, 17 | "hash": "sha512-AwzUWMMnbbRf+pow1dBJlkRAxtQwgm59xDMLa2Gm1pcKDolXTPfPGjRc4a0muGENgeZiM4tf17HfIdEOIlS78Q==" 18 | }, 19 | "fileToHash.txt": "sha1-t56X7IQ267Hza0qjpSpqb9UPcfE=" 20 | }, 21 | "hash": "sha512-rDNKFYBCOuaCzpomiZEGyRLAmc3+IU/HoNj7NiKXqLG90rNko74LwpZ1DYKx+/aJptGTKCr/9mP8ggnl4QVNNw==" 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /test/fixtures/fixtures/directory.1/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=", 7 | "otherFileToHash.txt": "sha1-B8FJ4uKgHESSgMvJUyrj3ix2uG8=" 8 | }, 9 | "hash": "sha512-AwzUWMMnbbRf+pow1dBJlkRAxtQwgm59xDMLa2Gm1pcKDolXTPfPGjRc4a0muGENgeZiM4tf17HfIdEOIlS78Q==" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/fixtures/fixtures/directory.1/anotherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of another file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/fixtures/directory.1/otherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of other file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/fixtures/directory/.integrity.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "hashes": { 4 | ".": { 5 | "contents": { 6 | "anotherFileToHash.txt": "sha1-EZ2w0rsSmXBOddIoz2IoOIuxGaQ=" 7 | }, 8 | "hash": "sha512-a7F1/1x1ZVAWMdcx7X9Dnzd9M4TY9wU21vNCt3ALW0+0npq85MKn7uhz8yGqjDbSmAUDf14uxgqjk2tMtkjK9w==" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /test/fixtures/fixtures/directory/anotherFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of another file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/fixtures/fileToHash.txt: -------------------------------------------------------------------------------- 1 | Different content of file to hash 2 | -------------------------------------------------------------------------------- /test/fixtures/sameContentWithFileToHash.txt: -------------------------------------------------------------------------------- 1 | Content of file to hash 2 | -------------------------------------------------------------------------------- /test/helper.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | import { expect } from 'chai'; 3 | import { hexRegexPattern } from '../src/common/utils'; 4 | import { checker } from './helper'; 5 | 6 | describe(`Helper: function 'checker' tests`, (): void => { 7 | 8 | it('to fail when hash is not of SRI', 9 | (): void => { 10 | const sut = checker('1f9f2660d8db3094e488dbef35f8f660a977724d', hexRegexPattern, ''); 11 | expect(sut).to.be.false; 12 | }); 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /test/helper.ts: -------------------------------------------------------------------------------- 1 | export function checker( 2 | hash: string, encodingPattern: RegExp, hashToMatch: string, 3 | algorithmToMatch = 'sha1', lengthToMatch = 0, 4 | ): boolean { 5 | const members = /^([a-zA-Z0-9]*)-([\s\S]*)/.exec(hash); 6 | if (!members || !members.length) { 7 | return false; 8 | } 9 | const matchAlgorithm = members[1] === algorithmToMatch; 10 | const matchEncoding = encodingPattern.test(members[2]) && members[2] === hashToMatch; 11 | const matchLength = lengthToMatch > 1 ? members[2].length === lengthToMatch : true; 12 | return matchAlgorithm && matchEncoding && matchLength; 13 | } 14 | -------------------------------------------------------------------------------- /test/ignoreFile/.nsriignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !dist 4 | #comment 5 | # another comment 6 | -------------------------------------------------------------------------------- /test/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json'; 2 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "diagnostics": true, 4 | "esModuleInterop": true, 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "target": "es2020", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "newLine": "lf", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "outDir": "out", 15 | "pretty": true, 16 | "resolveJsonModule": true, 17 | "rootDir": ".", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "importHelpers": true, 22 | "forceConsistentCasingInFileNames": true 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "test/**/*.ts" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.dev.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "es5" 6 | ], 7 | "target": "es5", 8 | "declaration": true, 9 | "diagnostics": false, 10 | "sourceMap": false, 11 | "stripInternal": true, 12 | "removeComments": false, 13 | "outDir": "dist/out", 14 | "rootDir": "src" 15 | }, 16 | "exclude": ["test/**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.strict.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "diagnostics": true, 4 | "esModuleInterop": true, 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "target": "es2020", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "newLine": "lf", 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "outDir": "out", 15 | "pretty": true, 16 | "resolveJsonModule": true, 17 | "rootDir": ".", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strict": true, 21 | "importHelpers": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "allowUnusedLabels": false, 24 | "allowUnreachableCode": false, 25 | "exactOptionalPropertyTypes": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitOverride": true, 28 | "noImplicitReturns": true, 29 | "noPropertyAccessFromIndexSignature": true, 30 | "noUncheckedIndexedAccess": true, 31 | "importsNotUsedAsValues": "error", 32 | "checkJs": true 33 | }, 34 | "include": [ 35 | "src/**/*.ts", 36 | "test/**/*.ts" 37 | ] 38 | } 39 | --------------------------------------------------------------------------------