├── .github
├── publish.yml
├── auto-approve.yml
├── auto-label.yaml
├── release-please.yml
├── release-trigger.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── processs_request.md
│ ├── questions.md
│ ├── feature_request.yml
│ ├── documentation_request.yml
│ └── bug_report.yml
├── CODEOWNERS
├── workflows
│ ├── issues-no-repro.yaml
│ ├── response.yaml
│ └── ci.yaml
├── sync-repo-settings.yaml
├── .OwlBot.yaml
├── .OwlBot.lock.yaml
├── generated-files-bot.yml
├── PULL_REQUEST_TEMPLATE.md
└── scripts
│ ├── remove-response-label.cjs
│ ├── close-invalid-link.cjs
│ ├── close-unresponsive.cjs
│ ├── fixtures
│ ├── invalidIssueBody.txt
│ ├── validIssueBody.txt
│ └── validIssueBodyDifferentLinkLocation.txt
│ └── tests
│ ├── close-invalid-link.test.cjs
│ └── close-or-remove-response-label.test.cjs
├── .kokoro
├── presubmit
│ ├── node18
│ │ ├── test.cfg
│ │ ├── system-test.cfg
│ │ ├── samples-test.cfg
│ │ └── common.cfg
│ └── windows
│ │ ├── common.cfg
│ │ └── test.cfg
├── continuous
│ └── node18
│ │ ├── test.cfg
│ │ ├── lint.cfg
│ │ ├── system-test.cfg
│ │ ├── samples-test.cfg
│ │ └── common.cfg
├── .gitattributes
├── release
│ ├── common.cfg
│ ├── docs.cfg
│ ├── docs-devsite.cfg
│ ├── docs-devsite.sh
│ ├── publish.cfg
│ └── docs.sh
├── common.cfg
├── docs.sh
├── lint.sh
├── test.bat
├── trampoline.sh
├── publish.sh
├── test.sh
├── system-test.sh
├── samples-test.sh
├── populate-secrets.sh
└── trampoline_v2.sh
├── samples
├── .eslintrc.yml
├── package.json
├── test
│ └── test.js
├── quickstart.js
├── README.md
└── streamify.js
├── .eslintrc.json
├── .prettierignore
├── .gitignore
├── .eslintignore
├── .gitattributes
├── linkinator.config.json
├── .compodocrc
├── tsconfig.json
├── SECURITY.md
├── .repo-metadata.json
├── renovate.json
├── .nycrc
├── system-test
└── system.ts
├── .prettierrc.js
├── owlbot.py
├── .mocharc.js
├── .jsdoc.js
├── .trampolinerc
├── package.json
├── CONTRIBUTING.md
├── src
├── resource-stream.ts
└── index.ts
├── CODE_OF_CONDUCT.md
├── README.md
├── test
├── resource-stream.ts
└── index.ts
├── LICENSE
└── CHANGELOG.md
/.github/publish.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.kokoro/presubmit/node18/test.cfg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.kokoro/continuous/node18/test.cfg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.kokoro/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-generated=true
2 |
--------------------------------------------------------------------------------
/.github/auto-approve.yml:
--------------------------------------------------------------------------------
1 | processes:
2 | - "NodeDependency"
--------------------------------------------------------------------------------
/.github/auto-label.yaml:
--------------------------------------------------------------------------------
1 | requestsize:
2 | enabled: true
3 |
--------------------------------------------------------------------------------
/samples/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | rules:
3 | no-console: off
4 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/gts"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/release-please.yml:
--------------------------------------------------------------------------------
1 | handleGHRelease: true
2 | releaseType: node
3 |
--------------------------------------------------------------------------------
/.github/release-trigger.yml:
--------------------------------------------------------------------------------
1 | enabled: true
2 | multiScmName: nodejs-paginator
--------------------------------------------------------------------------------
/.kokoro/presubmit/windows/common.cfg:
--------------------------------------------------------------------------------
1 | # Format: //devtools/kokoro/config/proto/build.proto
2 |
3 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/coverage
3 | test/fixtures
4 | build/
5 | docs/
6 | protos/
7 |
--------------------------------------------------------------------------------
/.kokoro/presubmit/windows/test.cfg:
--------------------------------------------------------------------------------
1 | # Use the test file directly
2 | build_file: "nodejs-paginator/.kokoro/test.bat"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .nyc_output
3 | build
4 | package-lock.json
5 | docs/
6 | .coverage
7 | __pycache__
8 | .DS_Store
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/coverage
3 | test/fixtures
4 | build/
5 | docs/
6 | protos/
7 | samples/generated/
8 | system-test/**/fixtures
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ts text eol=lf
2 | *.js text eol=lf
3 | protos/* linguist-generated
4 | **/api-extractor.json linguist-language=JSON-with-Comments
5 |
--------------------------------------------------------------------------------
/.kokoro/continuous/node18/lint.cfg:
--------------------------------------------------------------------------------
1 | env_vars: {
2 | key: "TRAMPOLINE_BUILD_FILE"
3 | value: "github/nodejs-paginator/.kokoro/lint.sh"
4 | }
5 |
--------------------------------------------------------------------------------
/.kokoro/release/common.cfg:
--------------------------------------------------------------------------------
1 | before_action {
2 | fetch_keystore {
3 | keystore_resource {
4 | keystore_config_id: 73713
5 | keyname: "yoshi-automation-github-key"
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | contact_links:
2 | - name: Google Cloud Support
3 | url: https://cloud.google.com/support/
4 | about: If you have a support contract with Google, please use the Google Cloud Support portal.
5 |
--------------------------------------------------------------------------------
/linkinator.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "recurse": true,
3 | "skip": [
4 | "https://codecov.io/gh/googleapis/",
5 | "www.googleapis.com",
6 | "img.shields.io"
7 | ],
8 | "silent": true,
9 | "concurrency": 10
10 | }
11 |
--------------------------------------------------------------------------------
/.compodocrc:
--------------------------------------------------------------------------------
1 | ---
2 | tsconfig: ./tsconfig.json
3 | output: ./docs
4 | theme: material
5 | hideGenerator: true
6 | disablePrivate: true
7 | disableProtected: true
8 | disableInternal: true
9 | disableCoverage: true
10 | disableGraph: true
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/processs_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Process Request
3 | about: Submit a process request to the library. Process requests are any requests related to library infrastructure, for example CI/CD, publishing, releasing, broken links.
4 | ---
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./node_modules/gts/tsconfig-google.json",
3 | "compilerOptions": {
4 | "lib": ["es2018", "dom"],
5 | "rootDir": ".",
6 | "outDir": "build"
7 | },
8 | "include": [
9 | "src/*.ts",
10 | "test/*.ts",
11 | "system-test/*.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/questions.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: If you have a question, please use Discussions
4 |
5 | ---
6 |
7 | If you have a general question that goes beyond the library itself, we encourage you to use [Discussions](https://github.com//discussions)
8 | to engage with fellow community members!
9 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
4 |
5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
6 |
7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.
8 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Code owners file.
2 | # This file controls who is tagged for review for any given pull request.
3 | #
4 | # For syntax help see:
5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
6 |
7 |
8 | # Unless specified, the jsteam is the default owner for nodejs repositories.
9 | * jsteam-handwritten-libraries @googleapis/jsteam
--------------------------------------------------------------------------------
/.kokoro/continuous/node18/system-test.cfg:
--------------------------------------------------------------------------------
1 | # Download resources for system tests (service account key, etc.)
2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
3 |
4 | env_vars: {
5 | key: "TRAMPOLINE_BUILD_FILE"
6 | value: "github/nodejs-paginator/.kokoro/system-test.sh"
7 | }
8 |
9 | env_vars: {
10 | key: "SECRET_MANAGER_KEYS"
11 | value: "long-door-651-kokoro-system-test-service-account"
12 | }
--------------------------------------------------------------------------------
/.kokoro/presubmit/node18/system-test.cfg:
--------------------------------------------------------------------------------
1 | # Download resources for system tests (service account key, etc.)
2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
3 |
4 | env_vars: {
5 | key: "TRAMPOLINE_BUILD_FILE"
6 | value: "github/nodejs-paginator/.kokoro/system-test.sh"
7 | }
8 |
9 | env_vars: {
10 | key: "SECRET_MANAGER_KEYS"
11 | value: "long-door-651-kokoro-system-test-service-account"
12 | }
--------------------------------------------------------------------------------
/.kokoro/continuous/node18/samples-test.cfg:
--------------------------------------------------------------------------------
1 | # Download resources for system tests (service account key, etc.)
2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
3 |
4 | env_vars: {
5 | key: "TRAMPOLINE_BUILD_FILE"
6 | value: "github/nodejs-paginator/.kokoro/samples-test.sh"
7 | }
8 |
9 | env_vars: {
10 | key: "SECRET_MANAGER_KEYS"
11 | value: "long-door-651-kokoro-system-test-service-account"
12 | }
--------------------------------------------------------------------------------
/.kokoro/presubmit/node18/samples-test.cfg:
--------------------------------------------------------------------------------
1 | # Download resources for system tests (service account key, etc.)
2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs"
3 |
4 | env_vars: {
5 | key: "TRAMPOLINE_BUILD_FILE"
6 | value: "github/nodejs-paginator/.kokoro/samples-test.sh"
7 | }
8 |
9 | env_vars: {
10 | key: "SECRET_MANAGER_KEYS"
11 | value: "long-door-651-kokoro-system-test-service-account"
12 | }
--------------------------------------------------------------------------------
/.repo-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "paginator",
3 | "name_pretty": "Google Cloud Common Paginator",
4 | "release_level": "stable",
5 | "language": "nodejs",
6 | "repo": "googleapis/nodejs-paginator",
7 | "distribution_name": "@google-cloud/paginator",
8 | "client_documentation": "https://cloud.google.com/nodejs/docs/reference/paginator/latest",
9 | "library_type": "OTHER",
10 | "codeowner_team": "jsteam-handwritten-libraries"
11 | }
12 |
--------------------------------------------------------------------------------
/samples/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "Samples for the nodejs-paginator npm module.",
3 | "license": "Apache-2.0",
4 | "author": "Google LLC",
5 | "engines": {
6 | "node": ">=18"
7 | },
8 | "repository": "googleapis/nodejs-paginator",
9 | "private": true,
10 | "scripts": {
11 | "test": "mocha"
12 | },
13 | "dependencies": {
14 | "@google-cloud/paginator": "^6.0.0"
15 | },
16 | "devDependencies": {
17 | "mocha": "^8.0.0"
18 | }
19 | }
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base",
4 | "docker:disable",
5 | ":disableDependencyDashboard"
6 | ],
7 | "constraintsFiltering": "strict",
8 | "pinVersions": false,
9 | "rebaseStalePrs": true,
10 | "schedule": [
11 | "after 9am and before 3pm"
12 | ],
13 | "gitAuthor": null,
14 | "packageRules": [
15 | {
16 | "extends": "packages:linters",
17 | "groupName": "linters"
18 | }
19 | ],
20 | "ignoreDeps": ["typescript"]
21 | }
22 |
--------------------------------------------------------------------------------
/.github/workflows/issues-no-repro.yaml:
--------------------------------------------------------------------------------
1 | name: invalid_link
2 | on:
3 | issues:
4 | types: [opened, reopened]
5 |
6 | jobs:
7 | close:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | issues: write
11 | pull-requests: write
12 | steps:
13 | - uses: actions/checkout@v4
14 | - uses: actions/github-script@v7
15 | with:
16 | script: |
17 | const script = require('./.github/scripts/close-invalid-link.cjs')
18 | await script({github, context})
19 |
--------------------------------------------------------------------------------
/.nycrc:
--------------------------------------------------------------------------------
1 | {
2 | "report-dir": "./.coverage",
3 | "reporter": ["text", "lcov"],
4 | "exclude": [
5 | "**/*-test",
6 | "**/.coverage",
7 | "**/apis",
8 | "**/benchmark",
9 | "**/conformance",
10 | "**/docs",
11 | "**/samples",
12 | "**/scripts",
13 | "**/protos",
14 | "**/test",
15 | "**/*.d.ts",
16 | ".jsdoc.js",
17 | "**/.jsdoc.js",
18 | "karma.conf.js",
19 | "webpack-tests.config.js",
20 | "webpack.config.js"
21 | ],
22 | "exclude-after-remap": false,
23 | "all": true
24 | }
25 |
--------------------------------------------------------------------------------
/.github/sync-repo-settings.yaml:
--------------------------------------------------------------------------------
1 | branchProtectionRules:
2 | - pattern: main
3 | isAdminEnforced: true
4 | requiredApprovingReviewCount: 1
5 | requiresCodeOwnerReviews: true
6 | requiresStrictStatusChecks: true
7 | requiredStatusCheckContexts:
8 | - "ci/kokoro: Samples test"
9 | - "ci/kokoro: System test"
10 | - lint
11 | - test (18)
12 | - test (20)
13 | - test (22)
14 | - cla/google
15 | - windows
16 | - OwlBot Post Processor
17 | permissionRules:
18 | - team: yoshi-admins
19 | permission: admin
20 | - team: jsteam-admins
21 | permission: admin
22 | - team: jsteam
23 | permission: push
24 |
--------------------------------------------------------------------------------
/samples/test/test.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | console.warn('no sample tests available 👻');
16 |
--------------------------------------------------------------------------------
/system-test/system.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | console.warn('no system tests available 👻');
16 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | module.exports = {
16 | ...require('gts/.prettierrc.json')
17 | }
18 |
--------------------------------------------------------------------------------
/.kokoro/common.cfg:
--------------------------------------------------------------------------------
1 | # Format: //devtools/kokoro/config/proto/build.proto
2 |
3 | # Build logs will be here
4 | action {
5 | define_artifacts {
6 | regex: "**/*sponge_log.xml"
7 | }
8 | }
9 |
10 | # Download trampoline resources.
11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
12 |
13 | # Use the trampoline script to run in docker.
14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
15 |
16 | # Configure the docker image for kokoro-trampoline.
17 | env_vars: {
18 | key: "TRAMPOLINE_IMAGE"
19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user"
20 | }
21 | env_vars: {
22 | key: "TRAMPOLINE_BUILD_FILE"
23 | value: "github/nodejs-paginator/.kokoro/test.sh"
24 | }
25 |
--------------------------------------------------------------------------------
/.kokoro/continuous/node18/common.cfg:
--------------------------------------------------------------------------------
1 | # Format: //devtools/kokoro/config/proto/build.proto
2 |
3 | # Build logs will be here
4 | action {
5 | define_artifacts {
6 | regex: "**/*sponge_log.xml"
7 | }
8 | }
9 |
10 | # Download trampoline resources.
11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
12 |
13 | # Use the trampoline script to run in docker.
14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
15 |
16 | # Configure the docker image for kokoro-trampoline.
17 | env_vars: {
18 | key: "TRAMPOLINE_IMAGE"
19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
20 | }
21 | env_vars: {
22 | key: "TRAMPOLINE_BUILD_FILE"
23 | value: "github/nodejs-paginator/.kokoro/test.sh"
24 | }
25 |
--------------------------------------------------------------------------------
/.kokoro/presubmit/node18/common.cfg:
--------------------------------------------------------------------------------
1 | # Format: //devtools/kokoro/config/proto/build.proto
2 |
3 | # Build logs will be here
4 | action {
5 | define_artifacts {
6 | regex: "**/*sponge_log.xml"
7 | }
8 | }
9 |
10 | # Download trampoline resources.
11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
12 |
13 | # Use the trampoline script to run in docker.
14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
15 |
16 | # Configure the docker image for kokoro-trampoline.
17 | env_vars: {
18 | key: "TRAMPOLINE_IMAGE"
19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
20 | }
21 | env_vars: {
22 | key: "TRAMPOLINE_BUILD_FILE"
23 | value: "github/nodejs-paginator/.kokoro/test.sh"
24 | }
25 |
--------------------------------------------------------------------------------
/.kokoro/release/docs.cfg:
--------------------------------------------------------------------------------
1 | # service account used to publish up-to-date docs.
2 | before_action {
3 | fetch_keystore {
4 | keystore_resource {
5 | keystore_config_id: 73713
6 | keyname: "docuploader_service_account"
7 | }
8 | }
9 | }
10 |
11 | # doc publications use a Python image.
12 | env_vars: {
13 | key: "TRAMPOLINE_IMAGE"
14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
15 | }
16 |
17 | # Download trampoline resources.
18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
19 |
20 | # Use the trampoline script to run in docker.
21 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
22 |
23 | env_vars: {
24 | key: "TRAMPOLINE_BUILD_FILE"
25 | value: "github/nodejs-paginator/.kokoro/release/docs.sh"
26 | }
27 |
--------------------------------------------------------------------------------
/.kokoro/release/docs-devsite.cfg:
--------------------------------------------------------------------------------
1 | # service account used to publish up-to-date docs.
2 | before_action {
3 | fetch_keystore {
4 | keystore_resource {
5 | keystore_config_id: 73713
6 | keyname: "docuploader_service_account"
7 | }
8 | }
9 | }
10 |
11 | # doc publications use a Python image.
12 | env_vars: {
13 | key: "TRAMPOLINE_IMAGE"
14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
15 | }
16 |
17 | # Download trampoline resources.
18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
19 |
20 | # Use the trampoline script to run in docker.
21 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
22 |
23 | env_vars: {
24 | key: "TRAMPOLINE_BUILD_FILE"
25 | value: "github/nodejs-paginator/.kokoro/release/docs-devsite.sh"
26 | }
27 |
--------------------------------------------------------------------------------
/.github/.OwlBot.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2021 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | docker:
15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest
16 |
17 |
18 | begin-after-commit-hash: 397c0bfd367a2427104f988d5329bc117caafd95
19 |
20 |
--------------------------------------------------------------------------------
/samples/quickstart.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // [START paginator_quickstart]
16 | const {paginator} = require('@google-cloud/paginator');
17 | console.log(paginator);
18 | // [END paginator_quickstart]
19 |
--------------------------------------------------------------------------------
/.kokoro/docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
20 |
21 | cd $(dirname $0)/..
22 |
23 | npm install
24 |
25 | npm run docs-test
26 |
--------------------------------------------------------------------------------
/.github/.OwlBot.lock.yaml:
--------------------------------------------------------------------------------
1 | # Copyright 2025 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | docker:
15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest
16 | digest: sha256:c7e4968cfc97a204a4b2381f3ecb55cabc40c4cccf88b1ef8bef0d976be87fee
17 | # created: 2025-04-08T17:33:08.498793944Z
18 |
--------------------------------------------------------------------------------
/owlbot.py:
--------------------------------------------------------------------------------
1 | # Copyright 2018 Google LLC.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | import synthtool as s
15 | import synthtool.gcp as gcp
16 |
17 | common_templates = gcp.CommonTemplates()
18 | templates = common_templates.node_library()
19 | s.copy(sources=templates, excludes=["LICENSE", "README.md", ".github/ISSUE_TEMPLATE", ".github/scripts", ".kokoro", ".github/workflows/issues-no-repro.yaml", ".jsdoc.js"])
20 |
--------------------------------------------------------------------------------
/.kokoro/lint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
20 |
21 | cd $(dirname $0)/..
22 |
23 | npm install
24 |
25 | # Install and link samples
26 | if [ -f samples/package.json ]; then
27 | cd samples/
28 | npm link ../
29 | npm install
30 | cd ..
31 | fi
32 |
33 | npm run lint
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest an idea for this library
3 | labels:
4 | - feature request
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: >
9 | **PLEASE READ**: If you have a support contract with Google, please
10 | create an issue in the [support
11 | console](https://cloud.google.com/support/) instead of filing on GitHub.
12 | This will ensure a timely response. Otherwise, please make sure to
13 | follow the steps below.
14 | - type: textarea
15 | attributes:
16 | label: >
17 | What would you like to see in the library?
18 | description: >
19 | Screenshots can be provided in the issue body below.
20 | placeholder: |
21 | 1. Set up authentication like so
22 | 2. Run the program like so
23 | 3. X would be nice to happen
24 | - type: textarea
25 | attributes:
26 | label: Describe alternatives you've considered
27 | - type: textarea
28 | attributes:
29 | label: Additional context/notes
30 |
--------------------------------------------------------------------------------
/.github/workflows/response.yaml:
--------------------------------------------------------------------------------
1 | name: no_response
2 | on:
3 | schedule:
4 | - cron: '30 1 * * *' # Run every day at 01:30
5 | workflow_dispatch:
6 | issue_comment:
7 |
8 | jobs:
9 | close:
10 | if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
11 | runs-on: ubuntu-latest
12 | permissions:
13 | issues: write
14 | pull-requests: write
15 | steps:
16 | - uses: actions/checkout@v4
17 | - uses: actions/github-script@v7
18 | with:
19 | script: |
20 | const script = require('./.github/scripts/close-unresponsive.cjs')
21 | await script({github, context})
22 |
23 | remove_label:
24 | if: github.event_name == 'issue_comment'
25 | runs-on: ubuntu-latest
26 | permissions:
27 | issues: write
28 | pull-requests: write
29 | steps:
30 | - uses: actions/checkout@v4
31 | - uses: actions/github-script@v7
32 | with:
33 | script: |
34 | const script = require('./.github/scripts/remove-response-label.cjs')
35 | await script({github, context})
36 |
--------------------------------------------------------------------------------
/.kokoro/test.bat:
--------------------------------------------------------------------------------
1 | @rem Copyright 2018 Google LLC. All rights reserved.
2 | @rem
3 | @rem Licensed under the Apache License, Version 2.0 (the "License");
4 | @rem you may not use this file except in compliance with the License.
5 | @rem You may obtain a copy of the License at
6 | @rem
7 | @rem http://www.apache.org/licenses/LICENSE-2.0
8 | @rem
9 | @rem Unless required by applicable law or agreed to in writing, software
10 | @rem distributed under the License is distributed on an "AS IS" BASIS,
11 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | @rem See the License for the specific language governing permissions and
13 | @rem limitations under the License.
14 |
15 | @echo "Starting Windows build"
16 |
17 | cd /d %~dp0
18 | cd ..
19 |
20 | @rem npm path is not currently set in our image, we should fix this next time
21 | @rem we upgrade Node.js in the image:
22 | SET PATH=%PATH%;/cygdrive/c/Program Files/nodejs/npm
23 |
24 | call nvm use v14.17.3
25 | call which node
26 |
27 | call npm install || goto :error
28 | call npm run test || goto :error
29 |
30 | goto :EOF
31 |
32 | :error
33 | exit /b 1
34 |
--------------------------------------------------------------------------------
/.kokoro/release/docs-devsite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | if [[ -z "$CREDENTIALS" ]]; then
20 | # if CREDENTIALS are explicitly set, assume we're testing locally
21 | # and don't set NPM_CONFIG_PREFIX.
22 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
23 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin"
24 | cd $(dirname $0)/../..
25 | fi
26 |
27 | npm install
28 | npm install --no-save @google-cloud/cloud-rad@^0.4.0
29 | # publish docs to devsite
30 | npx @google-cloud/cloud-rad . cloud-rad
31 |
--------------------------------------------------------------------------------
/.mocharc.js:
--------------------------------------------------------------------------------
1 | // Copyright 2020 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | const config = {
15 | "enable-source-maps": true,
16 | "throw-deprecation": true,
17 | "timeout": 10000,
18 | "recursive": true
19 | }
20 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') {
21 | delete config['throw-deprecation'];
22 | }
23 | if (process.env.MOCHA_REPORTER) {
24 | config.reporter = process.env.MOCHA_REPORTER;
25 | }
26 | if (process.env.MOCHA_REPORTER_OUTPUT) {
27 | config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`;
28 | }
29 | module.exports = config
30 |
--------------------------------------------------------------------------------
/.kokoro/trampoline.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2017 Google Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # This file is not used any more, but we keep this file for making it
17 | # easy to roll back.
18 | # TODO: Remove this file from the template.
19 |
20 | set -eo pipefail
21 |
22 | # Always run the cleanup script, regardless of the success of bouncing into
23 | # the container.
24 | function cleanup() {
25 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
26 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
27 | echo "cleanup";
28 | }
29 | trap cleanup EXIT
30 |
31 | $(dirname $0)/populate-secrets.sh # Secret Manager secrets.
32 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"
33 |
--------------------------------------------------------------------------------
/.github/generated-files-bot.yml:
--------------------------------------------------------------------------------
1 | generatedFiles:
2 | - path: '.kokoro/**'
3 | message: '`.kokoro` files are templated and should be updated in [`synthtool`](https://github.com/googleapis/synthtool)'
4 | - path: '.github/CODEOWNERS'
5 | message: 'CODEOWNERS should instead be modified via the `codeowner_team` property in .repo-metadata.json'
6 | - path: '.github/workflows/ci.yaml'
7 | message: '`.github/workflows/ci.yaml` (GitHub Actions) should be updated in [`synthtool`](https://github.com/googleapis/synthtool)'
8 | - path: '.github/generated-files-bot.+(yml|yaml)'
9 | message: '`.github/generated-files-bot.(yml|yaml)` should be updated in [`synthtool`](https://github.com/googleapis/synthtool)'
10 | - path: 'README.md'
11 | message: '`README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml'
12 | - path: 'samples/README.md'
13 | message: '`samples/README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml'
14 | ignoreAuthors:
15 | - 'gcf-owl-bot[bot]'
16 | - 'yoshi-automation'
17 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | > Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:
2 |
3 | ## Description
4 |
5 | > Please provide a detailed description for the change.
6 | > As much as possible, please try to keep changes separate by purpose. For example, try not to make a one-line bug fix in a feature request, or add an irrelevant README change to a bug fix.
7 |
8 | ## Impact
9 |
10 | > What's the impact of this change?
11 |
12 | ## Testing
13 |
14 | > Have you added unit and integration tests if necessary?
15 | > Were any tests changed? Are any breaking changes necessary?
16 |
17 | ## Additional Information
18 |
19 | > Any additional details that we should be aware of?
20 |
21 | ## Checklist
22 |
23 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/nodejs-paginator/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
24 | - [ ] Ensure the tests and linter pass
25 | - [ ] Code coverage does not decrease
26 | - [ ] Appropriate docs were updated
27 | - [ ] Appropriate comments were added, particularly in complex areas or places that require background
28 | - [ ] No new warnings or issues will be generated from this change
29 |
30 | Fixes #issue_number_goes_here 🦕
31 |
--------------------------------------------------------------------------------
/.github/scripts/remove-response-label.cjs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | module.exports = async ({ github, context }) => {
16 | const commenter = context.actor;
17 | const issue = await github.rest.issues.get({
18 | owner: context.repo.owner,
19 | repo: context.repo.repo,
20 | issue_number: context.issue.number,
21 | });
22 | const author = issue.data.user.login;
23 | const labels = issue.data.labels.map((e) => e.name);
24 |
25 | if (author === commenter && labels.includes('needs more info')) {
26 | await github.rest.issues.removeLabel({
27 | owner: context.repo.owner,
28 | repo: context.repo.repo,
29 | issue_number: context.issue.number,
30 | name: 'needs more info',
31 | });
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/.jsdoc.js:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | //
15 |
16 | 'use strict';
17 |
18 | module.exports = {
19 | opts: {
20 | readme: './README.md',
21 | package: './package.json',
22 | template: './node_modules/jsdoc-fresh',
23 | recurse: true,
24 | verbose: true,
25 | destination: './docs/'
26 | },
27 | plugins: [
28 | 'plugins/markdown',
29 | 'jsdoc-region-tag'
30 | ],
31 | source: {
32 | excludePattern: '(^|\\/|\\\\)[._]',
33 | include: [
34 | 'build/src',
35 | ],
36 | includePattern: '\\.js$'
37 | },
38 | templates: {
39 | copyright: 'Copyright 2019 Google, LLC.',
40 | includeDate: false,
41 | sourceFiles: false,
42 | systemName: '@google-cloud/paginator',
43 | theme: 'lumen',
44 | default: {
45 | outputSourceFiles: false
46 | }
47 | },
48 | markdown: {
49 | idInHeadings: true
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/.kokoro/release/publish.cfg:
--------------------------------------------------------------------------------
1 | before_action {
2 | fetch_keystore {
3 | keystore_resource {
4 | keystore_config_id: 73713
5 | keyname: "docuploader_service_account"
6 | }
7 | }
8 | }
9 |
10 | before_action {
11 | fetch_keystore {
12 | keystore_resource {
13 | keystore_config_id: 73713
14 | keyname: "google-cloud-npm-token-1"
15 | }
16 | }
17 | }
18 |
19 | env_vars: {
20 | key: "SECRET_MANAGER_KEYS"
21 | value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem"
22 | }
23 |
24 | # Download trampoline resources.
25 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
26 |
27 | # Use the trampoline script to run in docker.
28 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh"
29 |
30 | # Configure the docker image for kokoro-trampoline.
31 | env_vars: {
32 | key: "TRAMPOLINE_IMAGE"
33 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user"
34 | }
35 |
36 | env_vars: {
37 | key: "TRAMPOLINE_BUILD_FILE"
38 | value: "github/nodejs-paginator/.kokoro/publish.sh"
39 | }
40 |
41 | # Store the packages we uploaded to npmjs.org and their corresponding
42 | # package-lock.jsons in Placer. That way, we have a record of exactly
43 | # what we published, and which version of which tools we used to publish
44 | # it, which we can use to generate SBOMs and attestations.
45 | action {
46 | define_artifacts {
47 | regex: "github/**/*.tgz"
48 | regex: "github/**/package-lock.json"
49 | strip_prefix: "github"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/.trampolinerc:
--------------------------------------------------------------------------------
1 | # Copyright 2020 Google LLC
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | # Template for .trampolinerc
16 |
17 | # Add required env vars here.
18 | required_envvars+=(
19 | )
20 |
21 | # Add env vars which are passed down into the container here.
22 | pass_down_envvars+=(
23 | "AUTORELEASE_PR"
24 | "VERSION"
25 | )
26 |
27 | # Prevent unintentional override on the default image.
28 | if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \
29 | [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then
30 | echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image."
31 | exit 1
32 | fi
33 |
34 | # Define the default value if it makes sense.
35 | if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then
36 | TRAMPOLINE_IMAGE_UPLOAD=""
37 | fi
38 |
39 | if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then
40 | TRAMPOLINE_IMAGE=""
41 | fi
42 |
43 | if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then
44 | TRAMPOLINE_DOCKERFILE=""
45 | fi
46 |
47 | if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then
48 | TRAMPOLINE_BUILD_FILE=""
49 | fi
50 |
51 | # Secret Manager secrets.
52 | source ${PROJECT_ROOT}/.kokoro/populate-secrets.sh
53 |
--------------------------------------------------------------------------------
/.kokoro/publish.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
20 |
21 | # Start the releasetool reporter
22 | python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script
23 |
24 | cd $(dirname $0)/..
25 |
26 | NPM_TOKEN=$(cat $KOKORO_KEYSTORE_DIR/73713_google-cloud-npm-token-1)
27 | echo "//wombat-dressing-room.appspot.com/:_authToken=${NPM_TOKEN}" > ~/.npmrc
28 |
29 | npm install
30 | npm pack .
31 | # npm provides no way to specify, observe, or predict the name of the tarball
32 | # file it generates. We have to look in the current directory for the freshest
33 | # .tgz file.
34 | TARBALL=$(ls -1 -t *.tgz | head -1)
35 |
36 | npm publish --access=public --registry=https://wombat-dressing-room.appspot.com "$TARBALL"
37 |
38 | # Kokoro collects *.tgz and package-lock.json files and stores them in Placer
39 | # so we can generate SBOMs and attestations.
40 | # However, we *don't* want Kokoro to collect package-lock.json and *.tgz files
41 | # that happened to be installed with dependencies.
42 | find node_modules -name package-lock.json -o -name "*.tgz" | xargs rm -f
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@google-cloud/paginator",
3 | "version": "6.0.0",
4 | "description": "A result paging utility used by Google node.js modules",
5 | "main": "build/src/index.js",
6 | "types": "build/src/index.d.ts",
7 | "repository": "googleapis/nodejs-paginator",
8 | "scripts": {
9 | "test": "c8 mocha build/test",
10 | "compile": "tsc -p .",
11 | "fix": "gts fix",
12 | "prelint": "cd samples; npm link ../; npm install",
13 | "lint": "gts check",
14 | "prepare": "npm run compile",
15 | "pretest": "npm run compile",
16 | "docs": "jsdoc -c .jsdoc.js",
17 | "presystem-test": "npm run compile",
18 | "samples-test": "cd samples/ && npm link ../ && npm test && cd ../",
19 | "system-test": "mocha build/system-test",
20 | "docs-test": "linkinator docs",
21 | "predocs-test": "npm run docs",
22 | "clean": "gts clean",
23 | "precompile": "gts clean"
24 | },
25 | "keywords": [],
26 | "files": [
27 | "build/src",
28 | "!build/src/**/*.map"
29 | ],
30 | "author": "Google Inc.",
31 | "license": "Apache-2.0",
32 | "devDependencies": {
33 | "@types/extend": "^3.0.4",
34 | "@types/mocha": "^10.0.10",
35 | "@types/node": "^22.13.8",
36 | "@types/proxyquire": "^1.3.31",
37 | "@types/sinon": "^17.0.4",
38 | "@types/uuid": "^10.0.0",
39 | "c8": "^10.1.3",
40 | "codecov": "^3.8.3",
41 | "gts": "^6.0.2",
42 | "jsdoc": "^4.0.4",
43 | "jsdoc-fresh": "^3.0.0",
44 | "jsdoc-region-tag": "^3.0.0",
45 | "linkinator": "^6.1.2",
46 | "mocha": "^11.1.0",
47 | "path-to-regexp": "^8.2.0",
48 | "proxyquire": "^2.1.3",
49 | "sinon": "^19.0.2",
50 | "typescript": "^5.8.2",
51 | "uuid": "^11.1.0"
52 | },
53 | "dependencies": {
54 | "extend": "^3.0.2"
55 | },
56 | "engines": {
57 | "node": ">=18"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/.kokoro/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
20 |
21 | cd $(dirname $0)/..
22 |
23 | npm install
24 | # If tests are running against main branch, configure flakybot
25 | # to open issues on failures:
26 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then
27 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml
28 | export MOCHA_REPORTER=xunit
29 | cleanup() {
30 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot
31 | $KOKORO_GFILE_DIR/linux_amd64/flakybot
32 | }
33 | trap cleanup EXIT HUP
34 | fi
35 | # Unit tests exercise the entire API surface, which may include
36 | # deprecation warnings:
37 | export MOCHA_THROW_DEPRECATION=false
38 | npm test
39 |
40 | # codecov combines coverage across integration and unit tests. Include
41 | # the logic below for any environment you wish to collect coverage for:
42 | COVERAGE_NODE=14
43 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
44 | NYC_BIN=./node_modules/nyc/bin/nyc.js
45 | if [ -f "$NYC_BIN" ]; then
46 | $NYC_BIN report || true
47 | fi
48 | bash $KOKORO_GFILE_DIR/codecov.sh
49 | else
50 | echo "coverage is only reported for Node $COVERAGE_NODE"
51 | fi
52 |
--------------------------------------------------------------------------------
/.kokoro/release/docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2019 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | # build jsdocs (Python is installed on the Node 18 docker image).
20 | if [[ -z "$CREDENTIALS" ]]; then
21 | # if CREDENTIALS are explicitly set, assume we're testing locally
22 | # and don't set NPM_CONFIG_PREFIX.
23 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
24 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin"
25 | cd $(dirname $0)/../..
26 | fi
27 | npm install
28 | npm run docs
29 |
30 | # create docs.metadata, based on package.json and .repo-metadata.json.
31 | npm i json@9.0.6 -g
32 | python3 -m docuploader create-metadata \
33 | --name=$(cat .repo-metadata.json | json name) \
34 | --version=$(cat package.json | json version) \
35 | --language=$(cat .repo-metadata.json | json language) \
36 | --distribution-name=$(cat .repo-metadata.json | json distribution_name) \
37 | --product-page=$(cat .repo-metadata.json | json product_documentation) \
38 | --github-repository=$(cat .repo-metadata.json | json repo) \
39 | --issue-tracker=$(cat .repo-metadata.json | json issue_tracker)
40 | cp docs.metadata ./docs/docs.metadata
41 |
42 | # deploy the docs.
43 | if [[ -z "$CREDENTIALS" ]]; then
44 | CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account
45 | fi
46 | if [[ -z "$BUCKET" ]]; then
47 | BUCKET=docs-staging
48 | fi
49 | python3 -m docuploader upload ./docs --credentials $CREDENTIALS --staging-bucket $BUCKET
50 |
--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost."
2 | [//]: # "To regenerate it, use `python -m synthtool`."
3 |
4 |
5 | # [Google Cloud Common Paginator: Node.js Samples](https://github.com/googleapis/nodejs-paginator)
6 |
7 | [![Open in Cloud Shell][shell_img]][shell_link]
8 |
9 |
10 |
11 | ## Table of Contents
12 |
13 | * [Before you begin](#before-you-begin)
14 | * [Samples](#samples)
15 | * [Quickstart](#quickstart)
16 | * [Streamify](#streamify)
17 |
18 | ## Before you begin
19 |
20 | Before running the samples, make sure you've followed the steps outlined in
21 | [Using the client library](https://github.com/googleapis/nodejs-paginator#using-the-client-library).
22 |
23 | `cd samples`
24 |
25 | `npm install`
26 |
27 | `cd ..`
28 |
29 | ## Samples
30 |
31 |
32 |
33 | ### Quickstart
34 |
35 | View the [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/quickstart.js).
36 |
37 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/quickstart.js,samples/README.md)
38 |
39 | __Usage:__
40 |
41 |
42 | `node samples/quickstart.js`
43 |
44 |
45 | -----
46 |
47 |
48 |
49 |
50 | ### Streamify
51 |
52 | View the [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/streamify.js).
53 |
54 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/streamify.js,samples/README.md)
55 |
56 | __Usage:__
57 |
58 |
59 | `node samples/streamify.js`
60 |
61 |
62 |
63 |
64 |
65 |
66 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png
67 | [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/README.md
68 | [product-docs]:
69 |
--------------------------------------------------------------------------------
/.kokoro/system-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global
20 |
21 | # Setup service account credentials.
22 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account
23 | export GCLOUD_PROJECT=long-door-651
24 |
25 | cd $(dirname $0)/..
26 |
27 | # Run a pre-test hook, if a pre-system-test.sh is in the project
28 | if [ -f .kokoro/pre-system-test.sh ]; then
29 | set +x
30 | . .kokoro/pre-system-test.sh
31 | set -x
32 | fi
33 |
34 | npm install
35 |
36 | # If tests are running against main branch, configure flakybot
37 | # to open issues on failures:
38 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then
39 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml
40 | export MOCHA_REPORTER=xunit
41 | cleanup() {
42 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot
43 | $KOKORO_GFILE_DIR/linux_amd64/flakybot
44 | }
45 | trap cleanup EXIT HUP
46 | fi
47 |
48 | npm run system-test
49 |
50 | # codecov combines coverage across integration and unit tests. Include
51 | # the logic below for any environment you wish to collect coverage for:
52 | COVERAGE_NODE=14
53 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
54 | NYC_BIN=./node_modules/nyc/bin/nyc.js
55 | if [ -f "$NYC_BIN" ]; then
56 | $NYC_BIN report || true
57 | fi
58 | bash $KOKORO_GFILE_DIR/codecov.sh
59 | else
60 | echo "coverage is only reported for Node $COVERAGE_NODE"
61 | fi
62 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 | name: ci
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | node: [18, 20, 22]
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: actions/setup-node@v4
16 | with:
17 | node-version: ${{ matrix.node }}
18 | - run: node --version
19 | # The first installation step ensures that all of our production
20 | # dependencies work on the given Node.js version, this helps us find
21 | # dependencies that don't match our engines field:
22 | - run: npm install --production --engine-strict --ignore-scripts --no-package-lock
23 | # Clean up the production install, before installing dev/production:
24 | - run: rm -rf node_modules
25 | - run: npm install --engine-strict
26 | - run: npm test
27 | env:
28 | MOCHA_THROW_DEPRECATION: false
29 | test-script:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-node@v4
34 | with:
35 | node-version: 18
36 | - run: node --version
37 | - run: npm install --engine-strict
38 | working-directory: .github/scripts
39 | - run: npm test
40 | working-directory: .github/scripts
41 | env:
42 | MOCHA_THROW_DEPRECATION: false
43 | windows:
44 | runs-on: windows-latest
45 | steps:
46 | - uses: actions/checkout@v4
47 | - uses: actions/setup-node@v4
48 | with:
49 | node-version: 18
50 | - run: npm install --engine-strict
51 | - run: npm test
52 | env:
53 | MOCHA_THROW_DEPRECATION: false
54 | lint:
55 | runs-on: ubuntu-latest
56 | steps:
57 | - uses: actions/checkout@v4
58 | - uses: actions/setup-node@v4
59 | with:
60 | node-version: 18
61 | - run: npm install
62 | - run: npm run lint
63 | docs:
64 | runs-on: ubuntu-latest
65 | steps:
66 | - uses: actions/checkout@v4
67 | - uses: actions/setup-node@v4
68 | with:
69 | node-version: 18
70 | - run: npm install
71 | - run: npm run docs
72 | - uses: JustinBeckwith/linkinator-action@v1
73 | with:
74 | paths: docs/
75 |
--------------------------------------------------------------------------------
/.github/scripts/close-invalid-link.cjs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | async function closeIssue(github, owner, repo, number) {
16 | await github.rest.issues.createComment({
17 | owner: owner,
18 | repo: repo,
19 | issue_number: number,
20 | body: 'Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)'
21 | });
22 | await github.rest.issues.update({
23 | owner: owner,
24 | repo: repo,
25 | issue_number: number,
26 | state: 'closed'
27 | });
28 | }
29 | module.exports = async ({github, context}) => {
30 | const owner = context.repo.owner;
31 | const repo = context.repo.repo;
32 | const number = context.issue.number;
33 |
34 | const issue = await github.rest.issues.get({
35 | owner: owner,
36 | repo: repo,
37 | issue_number: number,
38 | });
39 |
40 | const isBugTemplate = issue.data.body.includes('Link to the code that reproduces this issue');
41 |
42 | if (isBugTemplate) {
43 | console.log(`Issue ${number} is a bug template`)
44 | try {
45 | const link = issue.data.body.split('\n')[18].match(/(https?:\/\/(gist\.)?github.com\/.*)/)[0];
46 | console.log(`Issue ${number} contains this link: ${link}`)
47 | const isValidLink = (await fetch(link)).ok;
48 | console.log(`Issue ${number} has a ${isValidLink ? 'valid' : 'invalid'} link`)
49 | if (!isValidLink) {
50 | await closeIssue(github, owner, repo, number);
51 | }
52 | } catch (err) {
53 | await closeIssue(github, owner, repo, number);
54 | }
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/samples/streamify.js:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // [START paginator_streamify]
16 | const {paginator} = require('@google-cloud/paginator');
17 |
18 | // Let's assume the ApiCaller class calls some real API.
19 | // The call() methods accepts a query object and a callback;
20 | // the callback is called with three parameters:
21 | // - error or null,
22 | // - a list of responses,
23 | // - next query to retrieve the next batch of responses.
24 | // This is similar to BigQuery API.
25 | class ApiCaller {
26 | constructor() {
27 | // We use `streamify` to convert the results to a stream.
28 | this.call = paginator.streamify('call_');
29 | }
30 |
31 | call_(query, callback) {
32 | console.log(`Called call_(${JSON.stringify(query)})`);
33 |
34 | const values = [];
35 | let nextQuery = null;
36 |
37 | if (query.query < 10) {
38 | // Generate ten values
39 | for (let idx = 0; idx < 10; ++idx) {
40 | values.push(query.query * 10 + idx);
41 | }
42 |
43 | // Query to get the next page
44 | nextQuery = {query: query.query + 1};
45 | }
46 |
47 | // Timeout of 500ms to pretend we're making a network request :)
48 | setTimeout(callback, 500, null, values, nextQuery);
49 | }
50 | }
51 |
52 | // In this sample, we show how the streamified method works.
53 | // We stop streaming when we receive the specific result,
54 | // you can see that it stops requesting new pages after that.
55 | function streamifySample() {
56 | const apiCaller = new ApiCaller();
57 | const stream = apiCaller.call({query: 0});
58 | stream.on('data', data => {
59 | console.log(data);
60 | if (data === 30) {
61 | stream.end();
62 | }
63 | });
64 | stream.on('end', () => {
65 | console.log('Streaming finished');
66 | });
67 | }
68 | // [END paginator_streamify]
69 |
70 | streamifySample();
71 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation_request.yml:
--------------------------------------------------------------------------------
1 | name: Documentation Requests
2 | description: Requests for more information
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: >
7 | Please use this issue type to log documentation requests against the library itself.
8 | These requests should involve documentation on Github (`.md` files), and should relate to the library
9 | itself. If you have questions or documentation requests for an API, please
10 | reach out to the API tracker itself.
11 |
12 | Please submit an issue to the API team, either by submitting an
13 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers), or by
14 | submitting an issue in its linked tracker in the .repo-metadata.json
15 | file in the API under packages/* ([example]()).
16 | You can also submit a request to documentation on cloud.google.com itself with the "Send Feedback"
17 | on the bottom of the page.
18 |
19 |
20 | Please note that documentation requests and questions for specific APIs
21 | will be closed.
22 | - type: checkboxes
23 | attributes:
24 | label: Please make sure you have searched for information in the following
25 | guides.
26 | options:
27 | - label: "Search the issues already opened:
28 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues"
29 | required: true
30 | - label: "Check our Troubleshooting guide:
31 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\
32 | es/troubleshooting"
33 | required: true
34 | - label: "Check our FAQ:
35 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\
36 | es/faq"
37 | required: true
38 | - label: "Check our libraries HOW-TO:
39 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\
40 | .md"
41 | required: true
42 | - label: "Check out our authentication guide:
43 | https://github.com/googleapis/google-auth-library-nodejs"
44 | required: true
45 | - label: "Check out handwritten samples for many of our APIs:
46 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples"
47 | required: true
48 | - type: textarea
49 | attributes:
50 | label: >
51 | Documentation Request
52 | validations:
53 | required: true
54 |
--------------------------------------------------------------------------------
/.github/scripts/close-unresponsive.cjs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | function labeledEvent(data) {
16 | return data.event === 'labeled' && data.label.name === 'needs more info';
17 | }
18 |
19 | const numberOfDaysLimit = 15;
20 | const close_message = `This has been closed since a request for information has \
21 | not been answered for ${numberOfDaysLimit} days. It can be reopened when the \
22 | requested information is provided.`;
23 |
24 | module.exports = async ({github, context}) => {
25 | const owner = context.repo.owner;
26 | const repo = context.repo.repo;
27 |
28 | const issues = await github.rest.issues.listForRepo({
29 | owner: owner,
30 | repo: repo,
31 | labels: 'needs more info',
32 | });
33 | const numbers = issues.data.map((e) => e.number);
34 |
35 | for (const number of numbers) {
36 | const events = await github.paginate(
37 | github.rest.issues.listEventsForTimeline,
38 | {
39 | owner: owner,
40 | repo: repo,
41 | issue_number: number,
42 | },
43 | (response) => response.data.filter(labeledEvent)
44 | );
45 |
46 | const latest_response_label = events[events.length - 1];
47 |
48 | const created_at = new Date(latest_response_label.created_at);
49 | const now = new Date();
50 | const diff = now - created_at;
51 | const diffDays = diff / (1000 * 60 * 60 * 24);
52 |
53 | if (diffDays > numberOfDaysLimit) {
54 | await github.rest.issues.update({
55 | owner: owner,
56 | repo: repo,
57 | issue_number: number,
58 | state: 'closed',
59 | });
60 |
61 | await github.rest.issues.createComment({
62 | owner: owner,
63 | repo: repo,
64 | issue_number: number,
65 | body: close_message,
66 | });
67 | }
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/.kokoro/samples-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2018 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | set -eo pipefail
18 |
19 | # Ensure the npm global directory is writable, otherwise rebuild `npm`
20 | mkdir -p $NPM_CONFIG_PREFIX
21 | npm config -g ls || npm i -g npm@`npm --version`
22 |
23 | # Setup service account credentials.
24 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account
25 | export GCLOUD_PROJECT=long-door-651
26 |
27 | cd $(dirname $0)/..
28 |
29 | # Run a pre-test hook, if a pre-samples-test.sh is in the project
30 | if [ -f .kokoro/pre-samples-test.sh ]; then
31 | set +x
32 | . .kokoro/pre-samples-test.sh
33 | set -x
34 | fi
35 |
36 | if [ -f samples/package.json ]; then
37 | npm install
38 |
39 | # Install and link samples
40 | cd samples/
41 | npm link ../
42 | npm install
43 | cd ..
44 | # If tests are running against main branch, configure flakybot
45 | # to open issues on failures:
46 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then
47 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml
48 | export MOCHA_REPORTER=xunit
49 | cleanup() {
50 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot
51 | $KOKORO_GFILE_DIR/linux_amd64/flakybot
52 | }
53 | trap cleanup EXIT HUP
54 | fi
55 |
56 | npm run samples-test
57 | fi
58 |
59 | # codecov combines coverage across integration and unit tests. Include
60 | # the logic below for any environment you wish to collect coverage for:
61 | COVERAGE_NODE=14
62 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then
63 | NYC_BIN=./node_modules/nyc/bin/nyc.js
64 | if [ -f "$NYC_BIN" ]; then
65 | $NYC_BIN report || true
66 | fi
67 | bash $KOKORO_GFILE_DIR/codecov.sh
68 | else
69 | echo "coverage is only reported for Node $COVERAGE_NODE"
70 | fi
71 |
--------------------------------------------------------------------------------
/.kokoro/populate-secrets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Copyright 2020 Google LLC.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # This file is called in the early stage of `trampoline_v2.sh` to
17 | # populate secrets needed for the CI builds.
18 |
19 | set -eo pipefail
20 |
21 | function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;}
22 | function msg { println "$*" >&2 ;}
23 | function println { printf '%s\n' "$(now) $*" ;}
24 |
25 | # Populates requested secrets set in SECRET_MANAGER_KEYS
26 |
27 | # In Kokoro CI builds, we use the service account attached to the
28 | # Kokoro VM. This means we need to setup auth on other CI systems.
29 | # For local run, we just use the gcloud command for retrieving the
30 | # secrets.
31 |
32 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
33 | GCLOUD_COMMANDS=(
34 | "docker"
35 | "run"
36 | "--entrypoint=gcloud"
37 | "--volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR}"
38 | "gcr.io/google.com/cloudsdktool/cloud-sdk"
39 | )
40 | if [[ "${TRAMPOLINE_CI:-}" == "kokoro" ]]; then
41 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager"
42 | else
43 | echo "Authentication for this CI system is not implemented yet."
44 | exit 2
45 | # TODO: Determine appropriate SECRET_LOCATION and the GCLOUD_COMMANDS.
46 | fi
47 | else
48 | # For local run, use /dev/shm or temporary directory for
49 | # KOKORO_GFILE_DIR.
50 | if [[ -d "/dev/shm" ]]; then
51 | export KOKORO_GFILE_DIR=/dev/shm
52 | else
53 | export KOKORO_GFILE_DIR=$(mktemp -d -t ci-XXXXXXXX)
54 | fi
55 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager"
56 | GCLOUD_COMMANDS=("gcloud")
57 | fi
58 |
59 | msg "Creating folder on disk for secrets: ${SECRET_LOCATION}"
60 | mkdir -p ${SECRET_LOCATION}
61 |
62 | for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g")
63 | do
64 | msg "Retrieving secret ${key}"
65 | "${GCLOUD_COMMANDS[@]}" \
66 | secrets versions access latest \
67 | --project cloud-devrel-kokoro-resources \
68 | --secret $key > \
69 | "$SECRET_LOCATION/$key"
70 | if [[ $? == 0 ]]; then
71 | msg "Secret written to ${SECRET_LOCATION}/${key}"
72 | else
73 | msg "Error retrieving secret ${key}"
74 | exit 2
75 | fi
76 | done
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | **Table of contents**
4 |
5 | * [Contributor License Agreements](#contributor-license-agreements)
6 | * [Contributing a patch](#contributing-a-patch)
7 | * [Running the tests](#running-the-tests)
8 | * [Releasing the library](#releasing-the-library)
9 |
10 | ## Contributor License Agreements
11 |
12 | We'd love to accept your sample apps and patches! Before we can take them, we
13 | have to jump a couple of legal hurdles.
14 |
15 | Please fill out either the individual or corporate Contributor License Agreement
16 | (CLA).
17 |
18 | * If you are an individual writing original source code and you're sure you
19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual).
20 | * If you work for a company that wants to allow you to contribute your work,
21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate).
22 |
23 | Follow either of the two links above to access the appropriate CLA and
24 | instructions for how to sign and return it. Once we receive it, we'll be able to
25 | accept your pull requests.
26 |
27 | ## Contributing A Patch
28 |
29 | 1. Submit an issue describing your proposed change to the repo in question.
30 | 1. The repo owner will respond to your issue promptly.
31 | 1. If your proposed change is accepted, and you haven't already done so, sign a
32 | Contributor License Agreement (see details above).
33 | 1. Fork the desired repo, develop and test your code changes.
34 | 1. Ensure that your code adheres to the existing style in the code to which
35 | you are contributing.
36 | 1. Ensure that your code has an appropriate set of tests which all pass.
37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling.
38 | 1. Submit a pull request.
39 |
40 | ### Before you begin
41 |
42 | 1. [Select or create a Cloud Platform project][projects].
43 | 1. [Set up authentication with a service account][auth] so you can access the
44 | API from your local workstation.
45 |
46 |
47 | ## Running the tests
48 |
49 | 1. [Prepare your environment for Node.js setup][setup].
50 |
51 | 1. Install dependencies:
52 |
53 | npm install
54 |
55 | 1. Run the tests:
56 |
57 | # Run unit tests.
58 | npm test
59 |
60 | # Run sample integration tests.
61 | npm run samples-test
62 |
63 | # Run all system tests.
64 | npm run system-test
65 |
66 | 1. Lint (and maybe fix) any changes:
67 |
68 | npm run fix
69 |
70 | [setup]: https://cloud.google.com/nodejs/docs/setup
71 | [projects]: https://console.cloud.google.com/project
72 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing
73 |
74 | [auth]: https://cloud.google.com/docs/authentication/getting-started
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: Create a report to help us improve
3 | labels:
4 | - bug
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: >
9 | **PLEASE READ**: If you have a support contract with Google, please
10 | create an issue in the [support
11 | console](https://cloud.google.com/support/) instead of filing on GitHub.
12 | This will ensure a timely response. Otherwise, please make sure to
13 | follow the steps below.
14 | - type: checkboxes
15 | attributes:
16 | label: Please make sure you have searched for information in the following
17 | guides.
18 | options:
19 | - label: "Search the issues already opened:
20 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues"
21 | required: true
22 | - label: "Search StackOverflow:
23 | http://stackoverflow.com/questions/tagged/google-cloud-platform+nod\
24 | e.js"
25 | required: true
26 | - label: "Check our Troubleshooting guide:
27 | https://github.com/googleapis/google-cloud-node/blob/main/docs/trou\
28 | bleshooting.md"
29 | required: true
30 | - label: "Check our FAQ:
31 | https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.\
32 | md"
33 | required: true
34 | - label: "Check our libraries HOW-TO:
35 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\
36 | .md"
37 | required: true
38 | - label: "Check out our authentication guide:
39 | https://github.com/googleapis/google-auth-library-nodejs"
40 | required: true
41 | - label: "Check out handwritten samples for many of our APIs:
42 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples"
43 | required: true
44 | - type: input
45 | attributes:
46 | label: >
47 | Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal
48 | reproduction.
49 | - type: textarea
50 | attributes:
51 | label: >
52 | A step-by-step description of how to reproduce the issue, based on
53 | the linked reproduction.
54 | description: >
55 | Screenshots can be provided in the issue body below.
56 | placeholder: |
57 | 1. Start the application in development (next dev)
58 | 2. Click X
59 | 3. Y will happen
60 | - type: textarea
61 | attributes:
62 | label: A clear and concise description of what the bug is, and what you
63 | expected to happen.
64 | placeholder: Following the steps from the previous section, I expected A to
65 | happen, but I observed B instead
66 |
67 | - type: textarea
68 | attributes:
69 | label: A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
70 | placeholder: 'Documentation here(link) states that B should happen instead of A'
71 |
--------------------------------------------------------------------------------
/.github/scripts/fixtures/invalidIssueBody.txt:
--------------------------------------------------------------------------------
1 | ### Please make sure you have searched for information in the following guides.
2 |
3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues
4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js
5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting
6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq
7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md
8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs
9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples
10 |
11 | ### A screenshot that you have tested with "Try this API".
12 |
13 |
14 | N/A
15 |
16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction.
17 |
18 | not-a-link
19 |
20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction.
21 |
22 |
23 | Change MY_PROJECT to your project name, add credentials if needed and run.
24 |
25 | ### A clear and concise description of what the bug is, and what you expected to happen.
26 |
27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling.
28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object
29 | at _write (node:internal/streams/writable:474:13)
30 | at Writable.write (node:internal/streams/writable:502:10)
31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22)
32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27)
36 | at Object.onceWrapper (node:events:633:26)
37 | at Pumpify.emit (node:events:518:28)
38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22)
39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
42 | at PassThrough.ondata (node:internal/streams/readable:1007:22)
43 | at PassThrough.emit (node:events:518:28)
44 | at addChunk (node:internal/streams/readable:559:12) {
45 | code: 'ERR_INVALID_ARG_TYPE'
46 |
47 |
48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
49 |
50 | No library should crash an application this way.
--------------------------------------------------------------------------------
/.github/scripts/fixtures/validIssueBody.txt:
--------------------------------------------------------------------------------
1 | ### Please make sure you have searched for information in the following guides.
2 |
3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues
4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js
5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting
6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq
7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md
8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs
9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples
10 |
11 | ### A screenshot that you have tested with "Try this API".
12 |
13 |
14 | N/A
15 |
16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction.
17 |
18 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9
19 |
20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction.
21 |
22 |
23 | Change MY_PROJECT to your project name, add credentials if needed and run.
24 |
25 | ### A clear and concise description of what the bug is, and what you expected to happen.
26 |
27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling.
28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object
29 | at _write (node:internal/streams/writable:474:13)
30 | at Writable.write (node:internal/streams/writable:502:10)
31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22)
32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27)
36 | at Object.onceWrapper (node:events:633:26)
37 | at Pumpify.emit (node:events:518:28)
38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22)
39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
42 | at PassThrough.ondata (node:internal/streams/readable:1007:22)
43 | at PassThrough.emit (node:events:518:28)
44 | at addChunk (node:internal/streams/readable:559:12) {
45 | code: 'ERR_INVALID_ARG_TYPE'
46 |
47 |
48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
49 |
50 | No library should crash an application this way.
--------------------------------------------------------------------------------
/.github/scripts/fixtures/validIssueBodyDifferentLinkLocation.txt:
--------------------------------------------------------------------------------
1 | ### Please make sure you have searched for information in the following guides.
2 |
3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues
4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js
5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting
6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq
7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md
8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs
9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples
10 |
11 | ### A screenshot that you have tested with "Try this API".
12 |
13 |
14 | N/A
15 |
16 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction.
17 |
18 |
19 | Change MY_PROJECT to your project name, add credentials if needed and run.
20 |
21 | ### A clear and concise description of what the bug is, and what you expected to happen.
22 |
23 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling.
24 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object
25 | at _write (node:internal/streams/writable:474:13)
26 | at Writable.write (node:internal/streams/writable:502:10)
27 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22)
28 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
29 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
30 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
31 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27)
32 | at Object.onceWrapper (node:events:633:26)
33 | at Pumpify.emit (node:events:518:28)
34 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22)
35 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139)
36 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5)
37 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11)
38 | at PassThrough.ondata (node:internal/streams/readable:1007:22)
39 | at PassThrough.emit (node:events:518:28)
40 | at addChunk (node:internal/streams/readable:559:12) {
41 | code: 'ERR_INVALID_ARG_TYPE'
42 |
43 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository with a minimal reproduction.
44 |
45 |
46 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9
47 |
48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
49 |
50 | No library should crash an application this way.
--------------------------------------------------------------------------------
/.github/scripts/tests/close-invalid-link.test.cjs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | const { describe, it } = require('mocha');
18 | const closeInvalidLink = require('../close-invalid-link.cjs');
19 | const fs = require('fs');
20 | const sinon = require('sinon');
21 |
22 | describe('close issues with invalid links', () => {
23 | let octokitStub;
24 | let issuesStub;
25 |
26 | beforeEach(() => {
27 | issuesStub = {
28 | get: sinon.stub(),
29 | createComment: sinon.stub(),
30 | update: sinon.stub(),
31 | };
32 | octokitStub = {
33 | rest: {
34 | issues: issuesStub,
35 | },
36 | };
37 | });
38 |
39 | afterEach(() => {
40 | sinon.restore();
41 | });
42 |
43 | it('does not do anything if it is not a bug', async () => {
44 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
45 | issuesStub.get.resolves({ data: { body: "I'm having a problem with this." } });
46 |
47 | await closeInvalidLink({ github: octokitStub, context });
48 |
49 | sinon.assert.calledOnce(issuesStub.get);
50 | sinon.assert.notCalled(issuesStub.createComment);
51 | sinon.assert.notCalled(issuesStub.update);
52 | });
53 |
54 | it('does not do anything if it is a bug with an appropriate link', async () => {
55 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
56 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBody.txt', 'utf-8') } });
57 |
58 | await closeInvalidLink({ github: octokitStub, context });
59 |
60 | sinon.assert.calledOnce(issuesStub.get);
61 | sinon.assert.notCalled(issuesStub.createComment);
62 | sinon.assert.notCalled(issuesStub.update);
63 | });
64 |
65 | it('does not do anything if it is a bug with an appropriate link and the template changes', async () => {
66 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
67 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBodyDifferentLinkLocation.txt', 'utf-8') } });
68 |
69 | await closeInvalidLink({ github: octokitStub, context });
70 |
71 | sinon.assert.calledOnce(issuesStub.get);
72 | sinon.assert.notCalled(issuesStub.createComment);
73 | sinon.assert.notCalled(issuesStub.update);
74 | });
75 |
76 | it('closes the issue if the link is invalid', async () => {
77 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
78 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/invalidIssueBody.txt', 'utf-8') } });
79 |
80 | await closeInvalidLink({ github: octokitStub, context });
81 |
82 | sinon.assert.calledOnce(issuesStub.get);
83 | sinon.assert.calledOnce(issuesStub.createComment);
84 | sinon.assert.calledOnce(issuesStub.update);
85 | });
86 | });
--------------------------------------------------------------------------------
/src/resource-stream.ts:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright 2019 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {Transform, Writable} from 'stream';
18 | import {ParsedArguments} from './';
19 |
20 | interface ResourceEvents {
21 | addListener(event: 'data', listener: (data: T) => void): this;
22 | emit(event: 'data', data: T): boolean;
23 | on(event: 'data', listener: (data: T) => void): this;
24 | once(event: 'data', listener: (data: T) => void): this;
25 | prependListener(event: 'data', listener: (data: T) => void): this;
26 | prependOnceListener(event: 'data', listener: (data: T) => void): this;
27 | removeListener(event: 'data', listener: (data: T) => void): this;
28 | }
29 |
30 | export class ResourceStream extends Transform implements ResourceEvents {
31 | _ended: boolean;
32 | _maxApiCalls: number;
33 | _nextQuery: {} | null;
34 | _otherArgs: unknown[];
35 | _reading: boolean;
36 | _requestFn: Function;
37 | _requestsMade: number;
38 | _resultsToSend: number;
39 | constructor(args: ParsedArguments, requestFn: Function) {
40 | const options = Object.assign({objectMode: true}, args.streamOptions);
41 | super(options);
42 |
43 | this._ended = false;
44 | this._maxApiCalls = args.maxApiCalls === -1 ? Infinity : args.maxApiCalls!;
45 | this._nextQuery = args.query!;
46 | this._reading = false;
47 | this._requestFn = requestFn;
48 | this._requestsMade = 0;
49 | this._resultsToSend = args.maxResults === -1 ? Infinity : args.maxResults!;
50 | this._otherArgs = [];
51 | }
52 | /* eslint-disable @typescript-eslint/no-explicit-any */
53 | end(
54 | ...args: any[]
55 | ): ReturnType extends Writable ? this : void {
56 | this._ended = true;
57 | return super.end(...args);
58 | }
59 | _read() {
60 | if (this._reading) {
61 | return;
62 | }
63 |
64 | this._reading = true;
65 |
66 | // Wrap in a try/catch to catch input linting errors, e.g.
67 | // an invalid BigQuery query. These errors are thrown in an
68 | // async fashion, which makes them un-catchable by the user.
69 | try {
70 | this._requestFn(
71 | this._nextQuery,
72 | (
73 | err: Error | null,
74 | results: T[],
75 | nextQuery: {} | null,
76 | ...otherArgs: []
77 | ) => {
78 | if (err) {
79 | this.destroy(err);
80 | return;
81 | }
82 |
83 | this._otherArgs = otherArgs;
84 | this._nextQuery = nextQuery;
85 |
86 | if (this._resultsToSend !== Infinity) {
87 | results = results.splice(0, this._resultsToSend);
88 | this._resultsToSend -= results.length;
89 | }
90 |
91 | let more = true;
92 |
93 | for (const result of results) {
94 | if (this._ended) {
95 | break;
96 | }
97 | more = this.push(result);
98 | }
99 |
100 | const isFinished = !this._nextQuery || this._resultsToSend < 1;
101 | const madeMaxCalls = ++this._requestsMade >= this._maxApiCalls;
102 |
103 | if (isFinished || madeMaxCalls) {
104 | this.end();
105 | }
106 |
107 | if (more && !this._ended) {
108 | setImmediate(() => this._read());
109 | }
110 |
111 | this._reading = false;
112 | },
113 | );
114 | } catch (e) {
115 | this.destroy(e as Error);
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/.github/scripts/tests/close-or-remove-response-label.test.cjs:
--------------------------------------------------------------------------------
1 | // Copyright 2024 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | 'use strict';
16 |
17 | const { describe, it, beforeEach, afterEach } = require('mocha');
18 | const removeResponseLabel = require('../remove-response-label.cjs');
19 | const closeUnresponsive = require('../close-unresponsive.cjs');
20 | const sinon = require('sinon');
21 |
22 | function getISODateDaysAgo(days) {
23 | const today = new Date();
24 | const daysAgo = new Date(today.setDate(today.getDate() - days));
25 | return daysAgo.toISOString();
26 | }
27 |
28 | describe('close issues or remove needs more info labels', () => {
29 | let octokitStub;
30 | let issuesStub;
31 | let paginateStub;
32 |
33 | beforeEach(() => {
34 | issuesStub = {
35 | listForRepo: sinon.stub(),
36 | update: sinon.stub(),
37 | createComment: sinon.stub(),
38 | get: sinon.stub(),
39 | removeLabel: sinon.stub(),
40 | };
41 | paginateStub = sinon.stub();
42 | octokitStub = {
43 | rest: {
44 | issues: issuesStub,
45 | },
46 | paginate: paginateStub,
47 | };
48 | });
49 |
50 | afterEach(() => {
51 | sinon.restore();
52 | });
53 |
54 | it('closes the issue if the OP has not responded within the allotted time and there is a needs-more-info label', async () => {
55 | const context = { owner: 'testOrg', repo: 'testRepo' };
56 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }];
57 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(16) }];
58 |
59 | issuesStub.listForRepo.resolves({ data: issuesInRepo });
60 | paginateStub.resolves(eventsInIssue);
61 |
62 | await closeUnresponsive({ github: octokitStub, context });
63 |
64 | sinon.assert.calledOnce(issuesStub.listForRepo);
65 | sinon.assert.calledOnce(paginateStub);
66 | sinon.assert.calledOnce(issuesStub.update);
67 | sinon.assert.calledOnce(issuesStub.createComment);
68 | });
69 |
70 | it('does nothing if not enough time has passed and there is a needs-more-info label', async () => {
71 | const context = { owner: 'testOrg', repo: 'testRepo' };
72 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }];
73 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(14) }];
74 |
75 | issuesStub.listForRepo.resolves({ data: issuesInRepo });
76 | paginateStub.resolves(eventsInIssue);
77 |
78 | await closeUnresponsive({ github: octokitStub, context });
79 |
80 | sinon.assert.calledOnce(issuesStub.listForRepo);
81 | sinon.assert.calledOnce(paginateStub);
82 | sinon.assert.notCalled(issuesStub.update);
83 | sinon.assert.notCalled(issuesStub.createComment);
84 | });
85 |
86 | it('removes the label if OP responded', async () => {
87 | const context = { actor: 'OP', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
88 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] };
89 |
90 | issuesStub.get.resolves({ data: issueContext });
91 |
92 | await removeResponseLabel({ github: octokitStub, context });
93 |
94 | sinon.assert.calledOnce(issuesStub.get);
95 | sinon.assert.calledOnce(issuesStub.removeLabel);
96 | });
97 |
98 | it('does not remove the label if author responded', async () => {
99 | const context = { actor: 'repo-maintainer', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } };
100 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] };
101 |
102 | issuesStub.get.resolves({ data: issueContext });
103 |
104 | await removeResponseLabel({ github: octokitStub, context });
105 |
106 | sinon.assert.calledOnce(issuesStub.get);
107 | sinon.assert.notCalled(issuesStub.removeLabel);
108 | });
109 | });
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | In the interest of fostering an open and welcoming environment, we as
7 | contributors and maintainers pledge to making participation in our project and
8 | our community a harassment-free experience for everyone, regardless of age, body
9 | size, disability, ethnicity, gender identity and expression, level of
10 | experience, education, socio-economic status, nationality, personal appearance,
11 | race, religion, or sexual identity and orientation.
12 |
13 | ## Our Standards
14 |
15 | Examples of behavior that contributes to creating a positive environment
16 | include:
17 |
18 | * Using welcoming and inclusive language
19 | * Being respectful of differing viewpoints and experiences
20 | * Gracefully accepting constructive criticism
21 | * Focusing on what is best for the community
22 | * Showing empathy towards other community members
23 |
24 | Examples of unacceptable behavior by participants include:
25 |
26 | * The use of sexualized language or imagery and unwelcome sexual attention or
27 | advances
28 | * Trolling, insulting/derogatory comments, and personal or political attacks
29 | * Public or private harassment
30 | * Publishing others' private information, such as a physical or electronic
31 | address, without explicit permission
32 | * Other conduct which could reasonably be considered inappropriate in a
33 | professional setting
34 |
35 | ## Our Responsibilities
36 |
37 | Project maintainers are responsible for clarifying the standards of acceptable
38 | behavior and are expected to take appropriate and fair corrective action in
39 | response to any instances of unacceptable behavior.
40 |
41 | Project maintainers have the right and responsibility to remove, edit, or reject
42 | comments, commits, code, wiki edits, issues, and other contributions that are
43 | not aligned to this Code of Conduct, or to ban temporarily or permanently any
44 | contributor for other behaviors that they deem inappropriate, threatening,
45 | offensive, or harmful.
46 |
47 | ## Scope
48 |
49 | This Code of Conduct applies both within project spaces and in public spaces
50 | when an individual is representing the project or its community. Examples of
51 | representing a project or community include using an official project e-mail
52 | address, posting via an official social media account, or acting as an appointed
53 | representative at an online or offline event. Representation of a project may be
54 | further defined and clarified by project maintainers.
55 |
56 | This Code of Conduct also applies outside the project spaces when the Project
57 | Steward has a reasonable belief that an individual's behavior may have a
58 | negative impact on the project or its community.
59 |
60 | ## Conflict Resolution
61 |
62 | We do not believe that all conflict is bad; healthy debate and disagreement
63 | often yield positive results. However, it is never okay to be disrespectful or
64 | to engage in behavior that violates the project’s code of conduct.
65 |
66 | If you see someone violating the code of conduct, you are encouraged to address
67 | the behavior directly with those involved. Many issues can be resolved quickly
68 | and easily, and this gives people more control over the outcome of their
69 | dispute. If you are unable to resolve the matter for any reason, or if the
70 | behavior is threatening or harassing, report it. We are dedicated to providing
71 | an environment where participants feel welcome and safe.
72 |
73 | Reports should be directed to *googleapis-stewards@google.com*, the
74 | Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to
75 | receive and address reported violations of the code of conduct. They will then
76 | work with a committee consisting of representatives from the Open Source
77 | Programs Office and the Google Open Source Strategy team. If for any reason you
78 | are uncomfortable reaching out to the Project Steward, please email
79 | opensource@google.com.
80 |
81 | We will investigate every complaint, but you may not receive a direct response.
82 | We will use our discretion in determining when and how to follow up on reported
83 | incidents, which may range from not taking action to permanent expulsion from
84 | the project and project-sponsored spaces. We will notify the accused of the
85 | report and provide them an opportunity to discuss it before any action is taken.
86 | The identity of the reporter will be omitted from the details of the report
87 | supplied to the accused. In potentially harmful situations, such as ongoing
88 | harassment or threats to anyone's safety, we may take action without notice.
89 |
90 | ## Attribution
91 |
92 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
93 | available at
94 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost."
2 | [//]: # "To regenerate it, use `python -m synthtool`."
3 |
4 |
5 | # [Google Cloud Common Paginator: Node.js Client](https://github.com/googleapis/nodejs-paginator)
6 |
7 | [](https://cloud.google.com/terms/launch-stages)
8 | [](https://www.npmjs.org/package/@google-cloud/paginator)
9 |
10 |
11 |
12 |
13 | A result paging utility used by Google node.js modules
14 |
15 |
16 | A comprehensive list of changes in each version may be found in
17 | [the CHANGELOG](https://github.com/googleapis/nodejs-paginator/blob/main/CHANGELOG.md).
18 |
19 | * [Google Cloud Common Paginator Node.js Client API Reference][client-docs]
20 |
21 | * [github.com/googleapis/nodejs-paginator](https://github.com/googleapis/nodejs-paginator)
22 |
23 | Read more about the client libraries for Cloud APIs, including the older
24 | Google APIs Client Libraries, in [Client Libraries Explained][explained].
25 |
26 | [explained]: https://cloud.google.com/apis/docs/client-libraries-explained
27 |
28 | **Table of contents:**
29 |
30 |
31 | * [Quickstart](#quickstart)
32 |
33 | * [Installing the client library](#installing-the-client-library)
34 | * [Using the client library](#using-the-client-library)
35 | * [Samples](#samples)
36 | * [Versioning](#versioning)
37 | * [Contributing](#contributing)
38 | * [License](#license)
39 |
40 | ## Quickstart
41 |
42 | ### Installing the client library
43 |
44 | ```bash
45 | npm install @google-cloud/paginator
46 | ```
47 |
48 |
49 | ### Using the client library
50 |
51 | ```javascript
52 | const {paginator} = require('@google-cloud/paginator');
53 | console.log(paginator);
54 |
55 | ```
56 |
57 |
58 |
59 | ## Samples
60 |
61 | Samples are in the [`samples/`](https://github.com/googleapis/nodejs-paginator/tree/main/samples) directory. Each sample's `README.md` has instructions for running its sample.
62 |
63 | | Sample | Source Code | Try it |
64 | | --------------------------- | --------------------------------- | ------ |
65 | | Quickstart | [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) |
66 | | Streamify | [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/streamify.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/streamify.js,samples/README.md) |
67 |
68 |
69 |
70 | The [Google Cloud Common Paginator Node.js Client API Reference][client-docs] documentation
71 | also contains samples.
72 |
73 | ## Supported Node.js Versions
74 |
75 | Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule).
76 | Libraries are compatible with all current _active_ and _maintenance_ versions of
77 | Node.js.
78 | If you are using an end-of-life version of Node.js, we recommend that you update
79 | as soon as possible to an actively supported LTS version.
80 |
81 | Google's client libraries support legacy versions of Node.js runtimes on a
82 | best-efforts basis with the following warnings:
83 |
84 | * Legacy versions are not tested in continuous integration.
85 | * Some security patches and features cannot be backported.
86 | * Dependencies cannot be kept up-to-date.
87 |
88 | Client libraries targeting some end-of-life versions of Node.js are available, and
89 | can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag).
90 | The dist-tags follow the naming convention `legacy-(version)`.
91 | For example, `npm install @google-cloud/paginator@legacy-8` installs client libraries
92 | for versions compatible with Node.js 8.
93 |
94 | ## Versioning
95 |
96 | This library follows [Semantic Versioning](http://semver.org/).
97 |
98 |
99 |
100 | This library is considered to be **stable**. The code surface will not change in backwards-incompatible ways
101 | unless absolutely necessary (e.g. because of critical security issues) or with
102 | an extensive deprecation period. Issues and requests against **stable** libraries
103 | are addressed with the highest priority.
104 |
105 |
106 |
107 |
108 |
109 |
110 | More Information: [Google Cloud Platform Launch Stages][launch_stages]
111 |
112 | [launch_stages]: https://cloud.google.com/terms/launch-stages
113 |
114 | ## Contributing
115 |
116 | Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/nodejs-paginator/blob/main/CONTRIBUTING.md).
117 |
118 | Please note that this `README.md`, the `samples/README.md`,
119 | and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`)
120 | are generated from a central template. To edit one of these files, make an edit
121 | to its templates in
122 | [directory](https://github.com/googleapis/synthtool).
123 |
124 | ## License
125 |
126 | Apache Version 2.0
127 |
128 | See [LICENSE](https://github.com/googleapis/nodejs-paginator/blob/main/LICENSE)
129 |
130 | [client-docs]: https://cloud.google.com/nodejs/docs/reference/paginator/latest
131 |
132 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png
133 | [projects]: https://console.cloud.google.com/project
134 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing
135 |
136 | [auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local
137 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | /*!
2 | * Copyright 2015 Google Inc. All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*!
18 | * @module common/paginator
19 | */
20 |
21 | import * as extend from 'extend';
22 | import {TransformOptions} from 'stream';
23 | import {ResourceStream} from './resource-stream';
24 |
25 | export interface ParsedArguments extends TransformOptions {
26 | /**
27 | * Query object. This is most commonly an object, but to make the API more
28 | * simple, it can also be a string in some places.
29 | */
30 | query?: ParsedArguments;
31 |
32 | /**
33 | * Callback function.
34 | */
35 | callback?: Function;
36 |
37 | /**
38 | * Auto-pagination enabled.
39 | */
40 | autoPaginate?: boolean;
41 |
42 | /**
43 | * Maximum API calls to make.
44 | */
45 | maxApiCalls?: number;
46 |
47 | /**
48 | * Maximum results to return.
49 | */
50 | maxResults?: number;
51 |
52 | pageSize?: number;
53 |
54 | streamOptions?: ParsedArguments;
55 | }
56 |
57 | /*! Developer Documentation
58 | *
59 | * paginator is used to auto-paginate `nextQuery` methods as well as
60 | * streamifying them.
61 | *
62 | * Before:
63 | *
64 | * search.query('done=true', function(err, results, nextQuery) {
65 | * search.query(nextQuery, function(err, results, nextQuery) {});
66 | * });
67 | *
68 | * After:
69 | *
70 | * search.query('done=true', function(err, results) {});
71 | *
72 | * Methods to extend should be written to accept callbacks and return a
73 | * `nextQuery`.
74 | */
75 |
76 | export class Paginator {
77 | /**
78 | * Cache the original method, then overwrite it on the Class's prototype.
79 | *
80 | * @param {function} Class - The parent class of the methods to extend.
81 | * @param {string|string[]} methodNames - Name(s) of the methods to extend.
82 | */
83 | // tslint:disable-next-line:variable-name
84 | extend(Class: Function, methodNames: string | string[]) {
85 | if (typeof methodNames === 'string') {
86 | methodNames = [methodNames];
87 | }
88 | methodNames.forEach(methodName => {
89 | const originalMethod = Class.prototype[methodName];
90 |
91 | // map the original method to a private member
92 | Class.prototype[methodName + '_'] = originalMethod;
93 |
94 | // overwrite the original to auto-paginate
95 | /* eslint-disable @typescript-eslint/no-explicit-any */
96 | Class.prototype[methodName] = function (...args: any[]) {
97 | const parsedArguments = paginator.parseArguments_(args);
98 | return paginator.run_(parsedArguments, originalMethod.bind(this));
99 | };
100 | });
101 | }
102 |
103 | /**
104 | * Wraps paginated API calls in a readable object stream.
105 | *
106 | * This method simply calls the nextQuery recursively, emitting results to a
107 | * stream. The stream ends when `nextQuery` is null.
108 | *
109 | * `maxResults` will act as a cap for how many results are fetched and emitted
110 | * to the stream.
111 | *
112 | * @param {string} methodName - Name of the method to streamify.
113 | * @return {function} - Wrapped function.
114 | */
115 | /* eslint-disable @typescript-eslint/no-explicit-any */
116 | streamify(methodName: string) {
117 | return function (
118 | // tslint:disable-next-line:no-any
119 | this: {[index: string]: Function},
120 | /* eslint-disable @typescript-eslint/no-explicit-any */
121 | ...args: any[]
122 | ): ResourceStream {
123 | const parsedArguments = paginator.parseArguments_(args);
124 | const originalMethod = this[methodName + '_'] || this[methodName];
125 | return paginator.runAsStream_(
126 | parsedArguments,
127 | originalMethod.bind(this),
128 | );
129 | };
130 | }
131 |
132 | /**
133 | * Parse a pseudo-array `arguments` for a query and callback.
134 | *
135 | * @param {array} args - The original `arguments` pseduo-array that the original
136 | * method received.
137 | */
138 | /* eslint-disable @typescript-eslint/no-explicit-any */
139 | parseArguments_(args: any[]) {
140 | let query: string | ParsedArguments | undefined;
141 | let autoPaginate = true;
142 | let maxApiCalls = -1;
143 | let maxResults = -1;
144 | let callback: Function | undefined;
145 |
146 | const firstArgument = args[0];
147 | const lastArgument = args[args.length - 1];
148 |
149 | if (typeof firstArgument === 'function') {
150 | callback = firstArgument;
151 | } else {
152 | query = firstArgument;
153 | }
154 |
155 | if (typeof lastArgument === 'function') {
156 | callback = lastArgument;
157 | }
158 |
159 | if (typeof query === 'object') {
160 | query = extend<{}, ParsedArguments>(true, {}, query) as ParsedArguments;
161 |
162 | // Check if the user only asked for a certain amount of results.
163 | if (query.maxResults && typeof query.maxResults === 'number') {
164 | // `maxResults` is used API-wide.
165 | maxResults = query.maxResults;
166 | } else if (typeof query.pageSize === 'number') {
167 | // `pageSize` is Pub/Sub's `maxResults`.
168 | maxResults = query.pageSize!;
169 | }
170 |
171 | if (query.maxApiCalls && typeof query.maxApiCalls === 'number') {
172 | maxApiCalls = query.maxApiCalls;
173 | delete query.maxApiCalls;
174 | }
175 |
176 | // maxResults is the user specified limit.
177 | if (maxResults !== -1 || query.autoPaginate === false) {
178 | autoPaginate = false;
179 | }
180 | }
181 |
182 | const parsedArguments = {
183 | query: query || {},
184 | autoPaginate,
185 | maxApiCalls,
186 | maxResults,
187 | callback,
188 | } as ParsedArguments;
189 |
190 | parsedArguments.streamOptions = extend<{}, ParsedArguments>(
191 | true,
192 | {},
193 | parsedArguments.query as ParsedArguments,
194 | );
195 | delete parsedArguments.streamOptions.autoPaginate;
196 | delete parsedArguments.streamOptions.maxResults;
197 | delete parsedArguments.streamOptions.pageSize;
198 |
199 | return parsedArguments;
200 | }
201 |
202 | /**
203 | * This simply checks to see if `autoPaginate` is set or not, if it's true
204 | * then we buffer all results, otherwise simply call the original method.
205 | *
206 | * @param {array} parsedArguments - Parsed arguments from the original method
207 | * call.
208 | * @param {object=|string=} parsedArguments.query - Query object. This is most
209 | * commonly an object, but to make the API more simple, it can also be a
210 | * string in some places.
211 | * @param {function=} parsedArguments.callback - Callback function.
212 | * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled.
213 | * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make.
214 | * @param {number} parsedArguments.maxResults - Maximum results to return.
215 | * @param {function} originalMethod - The cached method that accepts a callback
216 | * and returns `nextQuery` to receive more results.
217 | */
218 | run_(parsedArguments: ParsedArguments, originalMethod: Function) {
219 | const query = parsedArguments.query;
220 | const callback = parsedArguments.callback!;
221 | if (!parsedArguments.autoPaginate) {
222 | return originalMethod(query, callback);
223 | }
224 | const results = new Array<{}>();
225 | let otherArgs: unknown[] = [];
226 | const promise = new Promise((resolve, reject) => {
227 | const stream = paginator.runAsStream_(parsedArguments, originalMethod);
228 | stream
229 | .on('error', reject)
230 | .on('data', (data: {}) => results.push(data))
231 | .on('end', () => {
232 | otherArgs = stream._otherArgs || [];
233 | resolve(results);
234 | });
235 | });
236 | if (!callback) {
237 | return promise.then(results => [results, query, ...otherArgs]);
238 | }
239 | promise.then(
240 | results => callback(null, results, query, ...otherArgs),
241 | (err: Error) => callback(err),
242 | );
243 | }
244 |
245 | /**
246 | * This method simply calls the nextQuery recursively, emitting results to a
247 | * stream. The stream ends when `nextQuery` is null.
248 | *
249 | * `maxResults` will act as a cap for how many results are fetched and emitted
250 | * to the stream.
251 | *
252 | * @param {object=|string=} parsedArguments.query - Query object. This is most
253 | * commonly an object, but to make the API more simple, it can also be a
254 | * string in some places.
255 | * @param {function=} parsedArguments.callback - Callback function.
256 | * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled.
257 | * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make.
258 | * @param {number} parsedArguments.maxResults - Maximum results to return.
259 | * @param {function} originalMethod - The cached method that accepts a callback
260 | * and returns `nextQuery` to receive more results.
261 | * @return {stream} - Readable object stream.
262 | */
263 | /* eslint-disable @typescript-eslint/no-explicit-any */
264 | runAsStream_(
265 | parsedArguments: ParsedArguments,
266 | originalMethod: Function,
267 | ): ResourceStream {
268 | return new ResourceStream(parsedArguments, originalMethod);
269 | }
270 | }
271 |
272 | const paginator = new Paginator();
273 | export {paginator};
274 |
275 | export {ResourceStream};
276 |
--------------------------------------------------------------------------------
/test/resource-stream.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as assert from 'assert';
16 | import {describe, it, beforeEach, afterEach} from 'mocha';
17 | import * as sinon from 'sinon';
18 | import {Transform} from 'stream';
19 |
20 | import {ResourceStream} from '../src/resource-stream';
21 |
22 | describe('ResourceStream', () => {
23 | const sandbox = sinon.createSandbox();
24 |
25 | const config = {
26 | maxApiCalls: -1,
27 | maxResults: -1,
28 | query: {},
29 | };
30 |
31 | let requestSpy: sinon.SinonSpy;
32 | let stream: ResourceStream<{}>;
33 |
34 | beforeEach(() => {
35 | requestSpy = sandbox.spy();
36 | stream = new ResourceStream(config, requestSpy);
37 | });
38 |
39 | afterEach(() => {
40 | sandbox.restore();
41 | });
42 |
43 | describe('instantiation', () => {
44 | it('should pass the streamingOptions to the constructor', () => {
45 | const highWaterMark = 1;
46 | const options = {
47 | streamOptions: {highWaterMark},
48 | },
49 | stream = new ResourceStream(options, requestSpy);
50 | assert.strictEqual(stream.readableHighWaterMark, highWaterMark);
51 | });
52 |
53 | it('should set ended to false', () => {
54 | assert.strictEqual(stream._ended, false);
55 | });
56 |
57 | it('should set reading to false', () => {
58 | assert.strictEqual(stream._reading, false);
59 | });
60 |
61 | it('should set requestsMade to 0', () => {
62 | assert.strictEqual(stream._requestsMade, 0);
63 | });
64 |
65 | it('should localize the first query', () => {
66 | assert.strictEqual(stream._nextQuery, config.query);
67 | });
68 |
69 | it('should localize the request function', () => {
70 | assert.strictEqual(stream._requestFn, requestSpy);
71 | });
72 |
73 | describe('maxApiCalls', () => {
74 | it('should localize maxApiCalls', () => {
75 | const maxApiCalls = 100;
76 | stream = new ResourceStream({maxApiCalls}, requestSpy);
77 | assert.strictEqual(stream._maxApiCalls, maxApiCalls);
78 | });
79 |
80 | it('should set it to Infinity if not specified', () => {
81 | assert.strictEqual(stream._maxApiCalls, Infinity);
82 | });
83 | });
84 |
85 | describe('resultsToSend', () => {
86 | it('should localize maxResults as resultsToSend', () => {
87 | const maxResults = 100;
88 | stream = new ResourceStream({maxResults}, requestSpy);
89 | assert.strictEqual(stream._resultsToSend, maxResults);
90 | });
91 |
92 | it('should set it to Infinity if not specified', () => {
93 | assert.strictEqual(stream._resultsToSend, Infinity);
94 | });
95 | });
96 | });
97 |
98 | describe('end', () => {
99 | it('should set ended to true', () => {
100 | stream.end();
101 | assert.strictEqual(stream._ended, true);
102 | });
103 |
104 | it('should call through to super.end', () => {
105 | const stub = sandbox.stub(Transform.prototype, 'end');
106 |
107 | stream.end();
108 | assert.strictEqual(stub.callCount, 1);
109 | });
110 | });
111 |
112 | describe('_read', () => {
113 | it('should set reading to true', () => {
114 | stream._read();
115 | assert.strictEqual(stream._reading, true);
116 | });
117 |
118 | it('should noop if already reading', () => {
119 | stream._read();
120 | stream._read();
121 |
122 | assert.strictEqual(requestSpy.callCount, 1);
123 | });
124 |
125 | it('should pass in the query options', () => {
126 | stream._read();
127 |
128 | const query = requestSpy.lastCall.args[0];
129 | assert.strictEqual(query, config.query);
130 | });
131 |
132 | it('should destroy the stream if an error occurs', () => {
133 | const fakeError = new Error('err');
134 | const stub = sandbox.stub(stream, 'destroy').withArgs(fakeError);
135 |
136 | stream._read();
137 | const callback = requestSpy.lastCall.args[1];
138 | callback(fakeError);
139 |
140 | assert.strictEqual(stub.callCount, 1);
141 | });
142 |
143 | it('should cache the next query', () => {
144 | const fakeQuery = {};
145 |
146 | stream._read();
147 | const callback = requestSpy.lastCall.args[1];
148 | callback(null, [], fakeQuery);
149 |
150 | assert.strictEqual(stream._nextQuery, fakeQuery);
151 | });
152 |
153 | it('should cache the rest of the callback arguments', () => {
154 | const fakeRes = {status: 'OK'};
155 | const anotherArg = 10;
156 |
157 | stream._read();
158 | const callback = requestSpy.lastCall.args[1];
159 | callback(null, [], {}, fakeRes, anotherArg);
160 |
161 | assert.deepStrictEqual(stream._otherArgs, [fakeRes, anotherArg]);
162 | });
163 |
164 | it('should adjust the results to send counter', () => {
165 | const maxResults = 100;
166 | const results = [{}, {}];
167 | const expected = maxResults - results.length;
168 |
169 | stream = new ResourceStream({maxResults}, requestSpy);
170 | stream._read();
171 |
172 | const callback = requestSpy.lastCall.args[1];
173 | callback(null, results);
174 |
175 | assert.strictEqual(stream._resultsToSend, expected);
176 | });
177 |
178 | it('should push in all the results', () => {
179 | // tslint:disable-next-line ban
180 | const results = Array(20).fill({});
181 | const stub = sandbox.stub(stream, 'push');
182 |
183 | stream._read();
184 | const callback = requestSpy.lastCall.args[1];
185 | callback(null, results, {});
186 |
187 | assert.strictEqual(stub.callCount, results.length);
188 |
189 | results.forEach((result, i) => {
190 | const pushed = stub.getCall(i).args[0];
191 | assert.strictEqual(pushed, result);
192 | });
193 | });
194 |
195 | it('should stop pushing results if the stream is ended', () => {
196 | // tslint:disable-next-line ban
197 | const results = Array(20).fill({});
198 |
199 | stream.on('data', () => stream.end());
200 |
201 | stream._read();
202 | const callback = requestSpy.lastCall.args[1];
203 | callback(null, results, {});
204 |
205 | assert.strictEqual(requestSpy.callCount, 1);
206 | });
207 |
208 | it('should end the stream if there is no next query', () => {
209 | const stub = sandbox.stub(stream, 'end');
210 |
211 | stream._read();
212 | const callback = requestSpy.lastCall.args[1];
213 | callback(null, []);
214 |
215 | assert.strictEqual(stub.callCount, 1);
216 | });
217 |
218 | it('should end the stream if max results is hit', () => {
219 | const maxResults = 10;
220 | // tslint:disable-next-line ban
221 | const results = Array(maxResults).fill({});
222 | stream = new ResourceStream({maxResults}, requestSpy);
223 | const stub = sandbox.stub(stream, 'end');
224 |
225 | stream._read();
226 | const callback = requestSpy.lastCall.args[1];
227 | callback(null, results, {});
228 |
229 | assert.strictEqual(stub.callCount, 1);
230 | });
231 |
232 | it('should end the stream if max api calls is hit', () => {
233 | const maxApiCalls = 1;
234 | stream = new ResourceStream({maxApiCalls}, requestSpy);
235 | const stub = sandbox.stub(stream, 'end');
236 |
237 | stream._read();
238 | const callback = requestSpy.lastCall.args[1];
239 | callback(null, [], {});
240 |
241 | assert.strictEqual(stub.callCount, 1);
242 | });
243 |
244 | it('should stop reading if the buffer is full', () => {
245 | const clock = sandbox.useFakeTimers();
246 |
247 | // tslint:disable-next-line ban
248 | const results = Array(stream.readableHighWaterMark).fill({});
249 | stream._read();
250 | const callback = requestSpy.lastCall.args[1];
251 | callback(null, results, {});
252 |
253 | const stub = sandbox.stub(stream, '_read');
254 | clock.runAll();
255 |
256 | assert.strictEqual(stub.callCount, 0);
257 | });
258 |
259 | it('should stop reading if the stream ended', () => {
260 | const clock = sandbox.useFakeTimers();
261 |
262 | stream.on('data', () => stream.end());
263 |
264 | stream._read();
265 | const callback = requestSpy.lastCall.args[1];
266 | callback(null, [{}], {});
267 |
268 | const stub = sandbox.stub(stream, '_read');
269 | clock.runAll();
270 |
271 | assert.strictEqual(stub.callCount, 0);
272 | });
273 |
274 | it('should keep reading if not full/ended', () => {
275 | // sinon adds a timer to `nextTick` by default beginning in v19
276 | // manually specifying the timers like this replicates the behavior pre v19
277 | const clock = sandbox.useFakeTimers({
278 | toFake: [
279 | 'setTimeout',
280 | 'clearTimeout',
281 | 'setInterval',
282 | 'clearInterval',
283 | 'Date',
284 | 'setImmediate',
285 | 'clearImmediate',
286 | 'hrtime',
287 | 'performance',
288 | ],
289 | });
290 |
291 | stream._read();
292 | const callback = requestSpy.lastCall.args[1];
293 | callback(null, [{}], {});
294 |
295 | const stub = sandbox.stub(stream, '_read');
296 | clock.runAll();
297 |
298 | assert.strictEqual(stub.callCount, 1);
299 | });
300 |
301 | it('should set reading to false inbetween reads', () => {
302 | stream._read();
303 | const callback = requestSpy.lastCall.args[1];
304 | callback(null, [{}], {});
305 |
306 | assert.strictEqual(stream._reading, false);
307 | });
308 |
309 | it('should destroy the stream if the request method throws', done => {
310 | const error = new Error('Error.');
311 | stream._requestFn = () => {
312 | throw error;
313 | };
314 | stream.on('error', err => {
315 | assert.strictEqual(err, error);
316 | done();
317 | });
318 | stream._read();
319 | });
320 | });
321 | });
322 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | [npm history][1]
4 |
5 | [1]: https://www.npmjs.com/package/nodejs-paginator?activeTab=versions
6 |
7 | ## [6.0.0](https://github.com/googleapis/nodejs-paginator/compare/v5.0.2...v6.0.0) (2025-03-04)
8 |
9 |
10 | ### ⚠ BREAKING CHANGES
11 |
12 | * migrate to node 18 ([#386](https://github.com/googleapis/nodejs-paginator/issues/386))
13 |
14 | ### Miscellaneous Chores
15 |
16 | * Migrate to node 18 ([#386](https://github.com/googleapis/nodejs-paginator/issues/386)) ([1f4b49f](https://github.com/googleapis/nodejs-paginator/commit/1f4b49f1d0a0251a99b106adf6ce6a7e01f2fa27))
17 |
18 | ## [5.0.2](https://github.com/googleapis/nodejs-paginator/compare/v5.0.1...v5.0.2) (2024-05-23)
19 |
20 |
21 | ### Bug Fixes
22 |
23 | * Query should be on the list of extra args ([#365](https://github.com/googleapis/nodejs-paginator/issues/365)) ([50e40d0](https://github.com/googleapis/nodejs-paginator/commit/50e40d064aed1bd0d5f93a51ad54112343086644))
24 |
25 | ## [5.0.1](https://github.com/googleapis/nodejs-paginator/compare/v5.0.0...v5.0.1) (2024-05-22)
26 |
27 |
28 | ### Bug Fixes
29 |
30 | * Should pass extra callback arguments back to consumer ([#361](https://github.com/googleapis/nodejs-paginator/issues/361)) ([cc5c48b](https://github.com/googleapis/nodejs-paginator/commit/cc5c48b95b21e9c6a4e555ff98de267258657b6e))
31 |
32 | ## [5.0.0](https://github.com/googleapis/nodejs-paginator/compare/v4.0.1...v5.0.0) (2023-08-09)
33 |
34 |
35 | ### ⚠ BREAKING CHANGES
36 |
37 | * update to Node 14 ([#346](https://github.com/googleapis/nodejs-paginator/issues/346))
38 |
39 | ### Miscellaneous Chores
40 |
41 | * Update to Node 14 ([#346](https://github.com/googleapis/nodejs-paginator/issues/346)) ([262ad70](https://github.com/googleapis/nodejs-paginator/commit/262ad70d3cc5e1aa8a67ece54c04920b24ceea09))
42 |
43 | ## [4.0.1](https://github.com/googleapis/nodejs-paginator/compare/v4.0.0...v4.0.1) (2022-09-09)
44 |
45 |
46 | ### Bug Fixes
47 |
48 | * Remove pip install statements ([#1546](https://github.com/googleapis/nodejs-paginator/issues/1546)) ([#329](https://github.com/googleapis/nodejs-paginator/issues/329)) ([697567b](https://github.com/googleapis/nodejs-paginator/commit/697567bdd86226b740304734b9562a2f2241a96f))
49 |
50 | ## [4.0.0](https://github.com/googleapis/nodejs-paginator/compare/v3.0.7...v4.0.0) (2022-05-17)
51 |
52 |
53 | ### ⚠ BREAKING CHANGES
54 |
55 | * update library to use Node 12 (#325)
56 |
57 | ### Build System
58 |
59 | * update library to use Node 12 ([#325](https://github.com/googleapis/nodejs-paginator/issues/325)) ([02887ae](https://github.com/googleapis/nodejs-paginator/commit/02887ae2b370bff18cae7fe1d434ecdf663b5748))
60 |
61 | ### [3.0.7](https://github.com/googleapis/nodejs-paginator/compare/v3.0.6...v3.0.7) (2022-02-14)
62 |
63 |
64 | ### Bug Fixes
65 |
66 | * update signature of end to comply with update node types definition ([#311](https://github.com/googleapis/nodejs-paginator/issues/311)) ([79e6fbd](https://github.com/googleapis/nodejs-paginator/commit/79e6fbdae5008d874613d2919a6cf723708fc919))
67 |
68 | ### [3.0.6](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.5...v3.0.6) (2021-09-09)
69 |
70 |
71 | ### Bug Fixes
72 |
73 | * **build:** switch primary branch to main ([#287](https://www.github.com/googleapis/nodejs-paginator/issues/287)) ([1b796f3](https://www.github.com/googleapis/nodejs-paginator/commit/1b796f3377174354a62b7475d16f52213197f650))
74 |
75 | ### [3.0.5](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.4...v3.0.5) (2020-09-02)
76 |
77 |
78 | ### Bug Fixes
79 |
80 | * add configs by running synthtool ([#241](https://www.github.com/googleapis/nodejs-paginator/issues/241)) ([643593a](https://www.github.com/googleapis/nodejs-paginator/commit/643593ae9ffb8febff69a7bdae19239f5bcb1266))
81 |
82 | ### [3.0.4](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.3...v3.0.4) (2020-08-06)
83 |
84 |
85 | ### Bug Fixes
86 |
87 | * destroy ResourceStream with pre-flight error ([#236](https://www.github.com/googleapis/nodejs-paginator/issues/236)) ([d57beb4](https://www.github.com/googleapis/nodejs-paginator/commit/d57beb424d875a7bf502d458cc208f1bbe47a42a))
88 |
89 | ### [3.0.3](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.2...v3.0.3) (2020-07-24)
90 |
91 |
92 | ### Bug Fixes
93 |
94 | * move gitattributes files to node templates ([#234](https://www.github.com/googleapis/nodejs-paginator/issues/234)) ([30e881c](https://www.github.com/googleapis/nodejs-paginator/commit/30e881ce7415749b93b6b7e4e71745ea3fb248b6))
95 |
96 | ### [3.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.1...v3.0.2) (2020-07-06)
97 |
98 |
99 | ### Bug Fixes
100 |
101 | * update node issue template ([#221](https://www.github.com/googleapis/nodejs-paginator/issues/221)) ([088153c](https://www.github.com/googleapis/nodejs-paginator/commit/088153c4fca6d53e2e5ef4bb42365ce5493b913d))
102 |
103 | ### [3.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.0...v3.0.1) (2020-05-20)
104 |
105 |
106 | ### Bug Fixes
107 |
108 | * apache license URL ([#468](https://www.github.com/googleapis/nodejs-paginator/issues/468)) ([#211](https://www.github.com/googleapis/nodejs-paginator/issues/211)) ([f343b7f](https://www.github.com/googleapis/nodejs-paginator/commit/f343b7f7e184fd1b453f20ac1463d17520aac7ad))
109 |
110 | ## [3.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.3...v3.0.0) (2020-03-25)
111 |
112 |
113 | ### ⚠ BREAKING CHANGES
114 |
115 | * **dep:** upgrade gts 2.0.0 (#194)
116 | * **deps:** deprecated node 8 to 10; upgrade typescript
117 |
118 | ### Miscellaneous Chores
119 |
120 | * **dep:** upgrade gts 2.0.0 ([#194](https://www.github.com/googleapis/nodejs-paginator/issues/194)) ([4eaf9be](https://www.github.com/googleapis/nodejs-paginator/commit/4eaf9bed1fcfd0f10e877ff15c1d0e968e3356c8))
121 | * **deps:** deprecated node 8 to 10; upgrade typescript ([f6434ab](https://www.github.com/googleapis/nodejs-paginator/commit/f6434ab9cacb6ab804c070f19c38b6072ca326b5))
122 |
123 | ### [2.0.3](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.2...v2.0.3) (2019-12-05)
124 |
125 |
126 | ### Bug Fixes
127 |
128 | * **deps:** pin TypeScript below 3.7.0 ([e06e1b0](https://www.github.com/googleapis/nodejs-paginator/commit/e06e1b0a2e2bb1cf56fc806c1703b8b5e468b954))
129 |
130 | ### [2.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.1...v2.0.2) (2019-11-13)
131 |
132 |
133 | ### Bug Fixes
134 |
135 | * **docs:** add jsdoc-region-tag plugin ([#155](https://www.github.com/googleapis/nodejs-paginator/issues/155)) ([b983799](https://www.github.com/googleapis/nodejs-paginator/commit/b98379905848fd179c6268aff3e1cfaf2bf76663))
136 |
137 | ### [2.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.0...v2.0.1) (2019-08-25)
138 |
139 |
140 | ### Bug Fixes
141 |
142 | * **deps:** use the latest extend ([#141](https://www.github.com/googleapis/nodejs-paginator/issues/141)) ([61b383e](https://www.github.com/googleapis/nodejs-paginator/commit/61b383e))
143 |
144 | ## [2.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.2...v2.0.0) (2019-07-12)
145 |
146 |
147 | ### ⚠ BREAKING CHANGES
148 |
149 | * rewrite streaming logic (#136)
150 |
151 | ### Code Refactoring
152 |
153 | * rewrite streaming logic ([#136](https://www.github.com/googleapis/nodejs-paginator/issues/136)) ([641d82d](https://www.github.com/googleapis/nodejs-paginator/commit/641d82d))
154 |
155 | ### [1.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.1...v1.0.2) (2019-06-26)
156 |
157 |
158 | ### Bug Fixes
159 |
160 | * **docs:** link to reference docs section on googleapis.dev ([#132](https://www.github.com/googleapis/nodejs-paginator/issues/132)) ([be231be](https://www.github.com/googleapis/nodejs-paginator/commit/be231be))
161 |
162 | ### [1.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.0...v1.0.1) (2019-06-14)
163 |
164 |
165 | ### Bug Fixes
166 |
167 | * **docs:** move to new client docs URL ([#129](https://www.github.com/googleapis/nodejs-paginator/issues/129)) ([689f483](https://www.github.com/googleapis/nodejs-paginator/commit/689f483))
168 |
169 | ## [1.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v0.2.0...v1.0.0) (2019-05-03)
170 |
171 |
172 | ### Bug Fixes
173 |
174 | * **deps:** update dependency arrify to v2 ([#109](https://www.github.com/googleapis/nodejs-paginator/issues/109)) ([9f06c83](https://www.github.com/googleapis/nodejs-paginator/commit/9f06c83))
175 |
176 |
177 | ### Build System
178 |
179 | * upgrade engines field to >=8.10.0 ([#115](https://www.github.com/googleapis/nodejs-paginator/issues/115)) ([0921076](https://www.github.com/googleapis/nodejs-paginator/commit/0921076))
180 |
181 |
182 | ### BREAKING CHANGES
183 |
184 | * upgrade engines field to >=8.10.0 (#115)
185 |
186 | ## v0.2.0
187 |
188 | 03-08-2019 12:15 PST
189 |
190 | ### New Features
191 | - feat: handle promise based functions ([#91](https://github.com/googleapis/nodejs-paginator/pull/91))
192 | - refactor(ts): create generic for object streams ([#101](https://github.com/googleapis/nodejs-paginator/pull/101))
193 |
194 | ### Dependencies
195 | - chore(deps): update dependency through2 to v3 ([#53](https://github.com/googleapis/nodejs-paginator/pull/53))
196 | - chore(deps): update dependency @types/is to v0.0.21 ([#55](https://github.com/googleapis/nodejs-paginator/pull/55))
197 | - chore(deps): update dependency gts to ^0.9.0 ([#57](https://github.com/googleapis/nodejs-paginator/pull/57))
198 | - fix: Pin @types/sinon to last compatible version ([#61](https://github.com/googleapis/nodejs-paginator/pull/61))
199 | - refactor: trim a few dependencies ([#60](https://github.com/googleapis/nodejs-paginator/pull/60))
200 | - chore(deps): update dependency @types/sinon to v5.0.7 ([#62](https://github.com/googleapis/nodejs-paginator/pull/62))
201 | - chore(deps): update dependency @types/sinon to v7 ([#81](https://github.com/googleapis/nodejs-paginator/pull/81))
202 | - chore(deps): update dependency mocha to v6
203 |
204 | ### Documentation
205 | - docs: add lint/fix example to contributing guide ([#85](https://github.com/googleapis/nodejs-paginator/pull/85))
206 | - chore: move CONTRIBUTING.md to root ([#87](https://github.com/googleapis/nodejs-paginator/pull/87))
207 | - docs: update links in contrib guide ([#94](https://github.com/googleapis/nodejs-paginator/pull/94))
208 | - docs: update contributing path in README ([#88](https://github.com/googleapis/nodejs-paginator/pull/88))
209 |
210 | ### Internal / Testing Changes
211 | - chore: include build in eslintignore ([#49](https://github.com/googleapis/nodejs-paginator/pull/49))
212 | - chore: update CircleCI config ([#52](https://github.com/googleapis/nodejs-paginator/pull/52))
213 | - chore: use latest npm on Windows ([#54](https://github.com/googleapis/nodejs-paginator/pull/54))
214 | - chore: update eslintignore config ([#56](https://github.com/googleapis/nodejs-paginator/pull/56))
215 | - chore: add synth.metadata
216 | - fix(build): fix system key decryption ([#64](https://github.com/googleapis/nodejs-paginator/pull/64))
217 | - chore: update license file ([#68](https://github.com/googleapis/nodejs-paginator/pull/68))
218 | - chore(build): update prettier config ([#69](https://github.com/googleapis/nodejs-paginator/pull/69))
219 | - chore: nyc ignore build/test by default ([#71](https://github.com/googleapis/nodejs-paginator/pull/71))
220 | - chore: always nyc report before calling codecov ([#72](https://github.com/googleapis/nodejs-paginator/pull/72))
221 | - build: add Kokoro configs for autorelease ([#75](https://github.com/googleapis/nodejs-paginator/pull/75))
222 | - fix(build): fix Kokoro release script ([#76](https://github.com/googleapis/nodejs-paginator/pull/76))
223 | - chore: fix publish.sh permission +x ([#77](https://github.com/googleapis/nodejs-paginator/pull/77))
224 | - chore: update nyc and eslint configs ([#79](https://github.com/googleapis/nodejs-paginator/pull/79))
225 | - chore(build): inject yoshi automation key ([#80](https://github.com/googleapis/nodejs-paginator/pull/80))
226 | - build: check broken links in generated docs ([#82](https://github.com/googleapis/nodejs-paginator/pull/82))
227 | - build: ignore googleapis.com in doc link check ([#84](https://github.com/googleapis/nodejs-paginator/pull/84))
228 | - build: test using @grpc/grpc-js in CI ([#89](https://github.com/googleapis/nodejs-paginator/pull/89))
229 | - build: create docs test npm scripts ([#90](https://github.com/googleapis/nodejs-paginator/pull/90))
230 | - build: use linkinator for docs test ([#93](https://github.com/googleapis/nodejs-paginator/pull/93))
231 | - build: update release configuration
232 | - build: fix types for sinon ([#98](https://github.com/googleapis/nodejs-paginator/pull/98))
233 | - build: use node10 to run samples-test, system-test etc ([#97](https://github.com/googleapis/nodejs-paginator/pull/97))
234 | - build: Add docuploader credentials to node publish jobs ([#99](https://github.com/googleapis/nodejs-paginator/pull/99))
235 |
236 | ## v0.1.2
237 |
238 | ### Bug fixes
239 | - fix: call limiter.makeRequest() instead of original method ([#43](https://github.com/googleapis/nodejs-paginator/pull/43))
240 |
241 | ### Internal / Testing Changes
242 | - chore: update issue templates ([#42](https://github.com/googleapis/nodejs-paginator/pull/42))
243 | - chore: remove old issue template ([#40](https://github.com/googleapis/nodejs-paginator/pull/40))
244 | - build: run tests on node11 ([#39](https://github.com/googleapis/nodejs-paginator/pull/39))
245 | - chores(build): run codecov on continuous builds ([#36](https://github.com/googleapis/nodejs-paginator/pull/36))
246 | - chores(build): do not collect sponge.xml from windows builds ([#37](https://github.com/googleapis/nodejs-paginator/pull/37))
247 | - chore: update new issue template ([#35](https://github.com/googleapis/nodejs-paginator/pull/35))
248 | - chore(deps): update dependency sinon to v7 ([#31](https://github.com/googleapis/nodejs-paginator/pull/31))
249 | - build: fix codecov uploading on Kokoro ([#32](https://github.com/googleapis/nodejs-paginator/pull/32))
250 | - Update kokoro config ([#29](https://github.com/googleapis/nodejs-paginator/pull/29))
251 | - Update CI config ([#27](https://github.com/googleapis/nodejs-paginator/pull/27))
252 | - Don't publish sourcemaps ([#25](https://github.com/googleapis/nodejs-paginator/pull/25))
253 | - build: prevent system/sample-test from leaking credentials
254 | - Update kokoro config ([#23](https://github.com/googleapis/nodejs-paginator/pull/23))
255 | - test: remove appveyor config ([#22](https://github.com/googleapis/nodejs-paginator/pull/22))
256 | - Update CI config ([#21](https://github.com/googleapis/nodejs-paginator/pull/21))
257 | - Enable prefer-const in the eslint config ([#20](https://github.com/googleapis/nodejs-paginator/pull/20))
258 | - Enable no-var in eslint ([#19](https://github.com/googleapis/nodejs-paginator/pull/19))
259 | - Update CI config ([#18](https://github.com/googleapis/nodejs-paginator/pull/18))
260 |
261 | ## v0.1.1
262 |
263 | ### Internal / Testing Changes
264 | - Add synth script and update CI config (#14)
265 | - chore(deps): update dependency nyc to v13 (#12)
266 | - chore: ignore package-lock.json (#11)
267 | - chore(deps): lock file maintenance (#10)
268 | - chore: update renovate config (#9)
269 | - remove that whitespace (#8)
270 | - chore(deps): lock file maintenance (#7)
271 | - chore(deps): update dependency typescript to v3 (#6)
272 | - chore: assert.deelEqual => assert.deepStrictEqual (#5)
273 | - chore: move mocha options to mocha.opts (#4)
274 |
--------------------------------------------------------------------------------
/.kokoro/trampoline_v2.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # Copyright 2020 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | # trampoline_v2.sh
17 | #
18 | # If you want to make a change to this file, consider doing so at:
19 | # https://github.com/googlecloudplatform/docker-ci-helper
20 | #
21 | # This script is for running CI builds. For Kokoro builds, we
22 | # set this script to `build_file` field in the Kokoro configuration.
23 |
24 | # This script does 3 things.
25 | #
26 | # 1. Prepare the Docker image for the test
27 | # 2. Run the Docker with appropriate flags to run the test
28 | # 3. Upload the newly built Docker image
29 | #
30 | # in a way that is somewhat compatible with trampoline_v1.
31 | #
32 | # These environment variables are required:
33 | # TRAMPOLINE_IMAGE: The docker image to use.
34 | # TRAMPOLINE_DOCKERFILE: The location of the Dockerfile.
35 | #
36 | # You can optionally change these environment variables:
37 | # TRAMPOLINE_IMAGE_UPLOAD:
38 | # (true|false): Whether to upload the Docker image after the
39 | # successful builds.
40 | # TRAMPOLINE_BUILD_FILE: The script to run in the docker container.
41 | # TRAMPOLINE_WORKSPACE: The workspace path in the docker container.
42 | # Defaults to /workspace.
43 | # Potentially there are some repo specific envvars in .trampolinerc in
44 | # the project root.
45 | #
46 | # Here is an example for running this script.
47 | # TRAMPOLINE_IMAGE=gcr.io/cloud-devrel-kokoro-resources/node:10-user \
48 | # TRAMPOLINE_BUILD_FILE=.kokoro/system-test.sh \
49 | # .kokoro/trampoline_v2.sh
50 |
51 | set -euo pipefail
52 |
53 | TRAMPOLINE_VERSION="2.0.7"
54 |
55 | if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then
56 | readonly IO_COLOR_RED="$(tput setaf 1)"
57 | readonly IO_COLOR_GREEN="$(tput setaf 2)"
58 | readonly IO_COLOR_YELLOW="$(tput setaf 3)"
59 | readonly IO_COLOR_RESET="$(tput sgr0)"
60 | else
61 | readonly IO_COLOR_RED=""
62 | readonly IO_COLOR_GREEN=""
63 | readonly IO_COLOR_YELLOW=""
64 | readonly IO_COLOR_RESET=""
65 | fi
66 |
67 | function function_exists {
68 | [ $(LC_ALL=C type -t $1)"" == "function" ]
69 | }
70 |
71 | # Logs a message using the given color. The first argument must be one
72 | # of the IO_COLOR_* variables defined above, such as
73 | # "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the
74 | # given color. The log message will also have an RFC-3339 timestamp
75 | # prepended (in UTC). You can disable the color output by setting
76 | # TERM=vt100.
77 | function log_impl() {
78 | local color="$1"
79 | shift
80 | local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")"
81 | echo "================================================================"
82 | echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}"
83 | echo "================================================================"
84 | }
85 |
86 | # Logs the given message with normal coloring and a timestamp.
87 | function log() {
88 | log_impl "${IO_COLOR_RESET}" "$@"
89 | }
90 |
91 | # Logs the given message in green with a timestamp.
92 | function log_green() {
93 | log_impl "${IO_COLOR_GREEN}" "$@"
94 | }
95 |
96 | # Logs the given message in yellow with a timestamp.
97 | function log_yellow() {
98 | log_impl "${IO_COLOR_YELLOW}" "$@"
99 | }
100 |
101 | # Logs the given message in red with a timestamp.
102 | function log_red() {
103 | log_impl "${IO_COLOR_RED}" "$@"
104 | }
105 |
106 | readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX)
107 | readonly tmphome="${tmpdir}/h"
108 | mkdir -p "${tmphome}"
109 |
110 | function cleanup() {
111 | rm -rf "${tmpdir}"
112 | }
113 | trap cleanup EXIT
114 |
115 | RUNNING_IN_CI="${RUNNING_IN_CI:-false}"
116 |
117 | # The workspace in the container, defaults to /workspace.
118 | TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}"
119 |
120 | pass_down_envvars=(
121 | # TRAMPOLINE_V2 variables.
122 | # Tells scripts whether they are running as part of CI or not.
123 | "RUNNING_IN_CI"
124 | # Indicates which CI system we're in.
125 | "TRAMPOLINE_CI"
126 | # Indicates the version of the script.
127 | "TRAMPOLINE_VERSION"
128 | # Contains path to build artifacts being executed.
129 | "KOKORO_BUILD_ARTIFACTS_SUBDIR"
130 | )
131 |
132 | log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}"
133 |
134 | # Detect which CI systems we're in. If we're in any of the CI systems
135 | # we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be
136 | # the name of the CI system. Both envvars will be passing down to the
137 | # container for telling which CI system we're in.
138 | if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then
139 | # descriptive env var for indicating it's on CI.
140 | RUNNING_IN_CI="true"
141 | TRAMPOLINE_CI="kokoro"
142 | if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then
143 | if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then
144 | log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting."
145 | exit 1
146 | fi
147 | # This service account will be activated later.
148 | TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json"
149 | else
150 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
151 | gcloud auth list
152 | fi
153 | log_yellow "Configuring Container Registry access"
154 | gcloud auth configure-docker --quiet
155 | fi
156 | pass_down_envvars+=(
157 | # KOKORO dynamic variables.
158 | "KOKORO_BUILD_NUMBER"
159 | "KOKORO_BUILD_ID"
160 | "KOKORO_JOB_NAME"
161 | "KOKORO_GIT_COMMIT"
162 | "KOKORO_GITHUB_COMMIT"
163 | "KOKORO_GITHUB_PULL_REQUEST_NUMBER"
164 | "KOKORO_GITHUB_PULL_REQUEST_COMMIT"
165 | # For flakybot
166 | "KOKORO_GITHUB_COMMIT_URL"
167 | "KOKORO_GITHUB_PULL_REQUEST_URL"
168 | )
169 | elif [[ "${TRAVIS:-}" == "true" ]]; then
170 | RUNNING_IN_CI="true"
171 | TRAMPOLINE_CI="travis"
172 | pass_down_envvars+=(
173 | "TRAVIS_BRANCH"
174 | "TRAVIS_BUILD_ID"
175 | "TRAVIS_BUILD_NUMBER"
176 | "TRAVIS_BUILD_WEB_URL"
177 | "TRAVIS_COMMIT"
178 | "TRAVIS_COMMIT_MESSAGE"
179 | "TRAVIS_COMMIT_RANGE"
180 | "TRAVIS_JOB_NAME"
181 | "TRAVIS_JOB_NUMBER"
182 | "TRAVIS_JOB_WEB_URL"
183 | "TRAVIS_PULL_REQUEST"
184 | "TRAVIS_PULL_REQUEST_BRANCH"
185 | "TRAVIS_PULL_REQUEST_SHA"
186 | "TRAVIS_PULL_REQUEST_SLUG"
187 | "TRAVIS_REPO_SLUG"
188 | "TRAVIS_SECURE_ENV_VARS"
189 | "TRAVIS_TAG"
190 | )
191 | elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then
192 | RUNNING_IN_CI="true"
193 | TRAMPOLINE_CI="github-workflow"
194 | pass_down_envvars+=(
195 | "GITHUB_WORKFLOW"
196 | "GITHUB_RUN_ID"
197 | "GITHUB_RUN_NUMBER"
198 | "GITHUB_ACTION"
199 | "GITHUB_ACTIONS"
200 | "GITHUB_ACTOR"
201 | "GITHUB_REPOSITORY"
202 | "GITHUB_EVENT_NAME"
203 | "GITHUB_EVENT_PATH"
204 | "GITHUB_SHA"
205 | "GITHUB_REF"
206 | "GITHUB_HEAD_REF"
207 | "GITHUB_BASE_REF"
208 | )
209 | elif [[ "${CIRCLECI:-}" == "true" ]]; then
210 | RUNNING_IN_CI="true"
211 | TRAMPOLINE_CI="circleci"
212 | pass_down_envvars+=(
213 | "CIRCLE_BRANCH"
214 | "CIRCLE_BUILD_NUM"
215 | "CIRCLE_BUILD_URL"
216 | "CIRCLE_COMPARE_URL"
217 | "CIRCLE_JOB"
218 | "CIRCLE_NODE_INDEX"
219 | "CIRCLE_NODE_TOTAL"
220 | "CIRCLE_PREVIOUS_BUILD_NUM"
221 | "CIRCLE_PROJECT_REPONAME"
222 | "CIRCLE_PROJECT_USERNAME"
223 | "CIRCLE_REPOSITORY_URL"
224 | "CIRCLE_SHA1"
225 | "CIRCLE_STAGE"
226 | "CIRCLE_USERNAME"
227 | "CIRCLE_WORKFLOW_ID"
228 | "CIRCLE_WORKFLOW_JOB_ID"
229 | "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS"
230 | "CIRCLE_WORKFLOW_WORKSPACE_ID"
231 | )
232 | fi
233 |
234 | # Configure the service account for pulling the docker image.
235 | function repo_root() {
236 | local dir="$1"
237 | while [[ ! -d "${dir}/.git" ]]; do
238 | dir="$(dirname "$dir")"
239 | done
240 | echo "${dir}"
241 | }
242 |
243 | # Detect the project root. In CI builds, we assume the script is in
244 | # the git tree and traverse from there, otherwise, traverse from `pwd`
245 | # to find `.git` directory.
246 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
247 | PROGRAM_PATH="$(realpath "$0")"
248 | PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")"
249 | PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")"
250 | else
251 | PROJECT_ROOT="$(repo_root $(pwd))"
252 | fi
253 |
254 | log_yellow "Changing to the project root: ${PROJECT_ROOT}."
255 | cd "${PROJECT_ROOT}"
256 |
257 | # To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need
258 | # to use this environment variable in `PROJECT_ROOT`.
259 | if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then
260 |
261 | mkdir -p "${tmpdir}/gcloud"
262 | gcloud_config_dir="${tmpdir}/gcloud"
263 |
264 | log_yellow "Using isolated gcloud config: ${gcloud_config_dir}."
265 | export CLOUDSDK_CONFIG="${gcloud_config_dir}"
266 |
267 | log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication."
268 | gcloud auth activate-service-account \
269 | --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}"
270 | log_yellow "Configuring Container Registry access"
271 | gcloud auth configure-docker --quiet
272 | fi
273 |
274 | required_envvars=(
275 | # The basic trampoline configurations.
276 | "TRAMPOLINE_IMAGE"
277 | "TRAMPOLINE_BUILD_FILE"
278 | )
279 |
280 | if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then
281 | source "${PROJECT_ROOT}/.trampolinerc"
282 | fi
283 |
284 | log_yellow "Checking environment variables."
285 | for e in "${required_envvars[@]}"
286 | do
287 | if [[ -z "${!e:-}" ]]; then
288 | log "Missing ${e} env var. Aborting."
289 | exit 1
290 | fi
291 | done
292 |
293 | # We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1
294 | # script: e.g. "github/repo-name/.kokoro/run_tests.sh"
295 | TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}"
296 | log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}"
297 |
298 | # ignore error on docker operations and test execution
299 | set +e
300 |
301 | log_yellow "Preparing Docker image."
302 | # We only download the docker image in CI builds.
303 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
304 | # Download the docker image specified by `TRAMPOLINE_IMAGE`
305 |
306 | # We may want to add --max-concurrent-downloads flag.
307 |
308 | log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}."
309 | if docker pull "${TRAMPOLINE_IMAGE}"; then
310 | log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}."
311 | has_image="true"
312 | else
313 | log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}."
314 | has_image="false"
315 | fi
316 | else
317 | # For local run, check if we have the image.
318 | if docker images "${TRAMPOLINE_IMAGE}" | grep "${TRAMPOLINE_IMAGE%:*}"; then
319 | has_image="true"
320 | else
321 | has_image="false"
322 | fi
323 | fi
324 |
325 |
326 | # The default user for a Docker container has uid 0 (root). To avoid
327 | # creating root-owned files in the build directory we tell docker to
328 | # use the current user ID.
329 | user_uid="$(id -u)"
330 | user_gid="$(id -g)"
331 | user_name="$(id -un)"
332 |
333 | # To allow docker in docker, we add the user to the docker group in
334 | # the host os.
335 | docker_gid=$(cut -d: -f3 < <(getent group docker))
336 |
337 | update_cache="false"
338 | if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then
339 | # Build the Docker image from the source.
340 | context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}")
341 | docker_build_flags=(
342 | "-f" "${TRAMPOLINE_DOCKERFILE}"
343 | "-t" "${TRAMPOLINE_IMAGE}"
344 | "--build-arg" "UID=${user_uid}"
345 | "--build-arg" "USERNAME=${user_name}"
346 | )
347 | if [[ "${has_image}" == "true" ]]; then
348 | docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}")
349 | fi
350 |
351 | log_yellow "Start building the docker image."
352 | if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then
353 | echo "docker build" "${docker_build_flags[@]}" "${context_dir}"
354 | fi
355 |
356 | # ON CI systems, we want to suppress docker build logs, only
357 | # output the logs when it fails.
358 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then
359 | if docker build "${docker_build_flags[@]}" "${context_dir}" \
360 | > "${tmpdir}/docker_build.log" 2>&1; then
361 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
362 | cat "${tmpdir}/docker_build.log"
363 | fi
364 |
365 | log_green "Finished building the docker image."
366 | update_cache="true"
367 | else
368 | log_red "Failed to build the Docker image, aborting."
369 | log_yellow "Dumping the build logs:"
370 | cat "${tmpdir}/docker_build.log"
371 | exit 1
372 | fi
373 | else
374 | if docker build "${docker_build_flags[@]}" "${context_dir}"; then
375 | log_green "Finished building the docker image."
376 | update_cache="true"
377 | else
378 | log_red "Failed to build the Docker image, aborting."
379 | exit 1
380 | fi
381 | fi
382 | else
383 | if [[ "${has_image}" != "true" ]]; then
384 | log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting."
385 | exit 1
386 | fi
387 | fi
388 |
389 | # We use an array for the flags so they are easier to document.
390 | docker_flags=(
391 | # Remove the container after it exists.
392 | "--rm"
393 |
394 | # Use the host network.
395 | "--network=host"
396 |
397 | # Run in priviledged mode. We are not using docker for sandboxing or
398 | # isolation, just for packaging our dev tools.
399 | "--privileged"
400 |
401 | # Run the docker script with the user id. Because the docker image gets to
402 | # write in ${PWD} you typically want this to be your user id.
403 | # To allow docker in docker, we need to use docker gid on the host.
404 | "--user" "${user_uid}:${docker_gid}"
405 |
406 | # Pass down the USER.
407 | "--env" "USER=${user_name}"
408 |
409 | # Mount the project directory inside the Docker container.
410 | "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}"
411 | "--workdir" "${TRAMPOLINE_WORKSPACE}"
412 | "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}"
413 |
414 | # Mount the temporary home directory.
415 | "--volume" "${tmphome}:/h"
416 | "--env" "HOME=/h"
417 |
418 | # Allow docker in docker.
419 | "--volume" "/var/run/docker.sock:/var/run/docker.sock"
420 |
421 | # Mount the /tmp so that docker in docker can mount the files
422 | # there correctly.
423 | "--volume" "/tmp:/tmp"
424 | # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR
425 | # TODO(tmatsuo): This part is not portable.
426 | "--env" "TRAMPOLINE_SECRET_DIR=/secrets"
427 | "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile"
428 | "--env" "KOKORO_GFILE_DIR=/secrets/gfile"
429 | "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore"
430 | "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore"
431 | )
432 |
433 | # Add an option for nicer output if the build gets a tty.
434 | if [[ -t 0 ]]; then
435 | docker_flags+=("-it")
436 | fi
437 |
438 | # Passing down env vars
439 | for e in "${pass_down_envvars[@]}"
440 | do
441 | if [[ -n "${!e:-}" ]]; then
442 | docker_flags+=("--env" "${e}=${!e}")
443 | fi
444 | done
445 |
446 | # If arguments are given, all arguments will become the commands run
447 | # in the container, otherwise run TRAMPOLINE_BUILD_FILE.
448 | if [[ $# -ge 1 ]]; then
449 | log_yellow "Running the given commands '" "${@:1}" "' in the container."
450 | readonly commands=("${@:1}")
451 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
452 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
453 | fi
454 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}"
455 | else
456 | log_yellow "Running the tests in a Docker container."
457 | docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}")
458 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then
459 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
460 | fi
461 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}"
462 | fi
463 |
464 |
465 | test_retval=$?
466 |
467 | if [[ ${test_retval} -eq 0 ]]; then
468 | log_green "Build finished with ${test_retval}"
469 | else
470 | log_red "Build finished with ${test_retval}"
471 | fi
472 |
473 | # Only upload it when the test passes.
474 | if [[ "${update_cache}" == "true" ]] && \
475 | [[ $test_retval == 0 ]] && \
476 | [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then
477 | log_yellow "Uploading the Docker image."
478 | if docker push "${TRAMPOLINE_IMAGE}"; then
479 | log_green "Finished uploading the Docker image."
480 | else
481 | log_red "Failed uploading the Docker image."
482 | fi
483 | # Call trampoline_after_upload_hook if it's defined.
484 | if function_exists trampoline_after_upload_hook; then
485 | trampoline_after_upload_hook
486 | fi
487 |
488 | fi
489 |
490 | exit "${test_retval}"
491 |
--------------------------------------------------------------------------------
/test/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as assert from 'assert';
16 | import {describe, it, beforeEach, afterEach} from 'mocha';
17 | import * as proxyquire from 'proxyquire';
18 | import * as sinon from 'sinon';
19 | import {PassThrough, Transform} from 'stream';
20 | import * as uuid from 'uuid';
21 | import * as P from '../src';
22 | import {paginator, ParsedArguments} from '../src';
23 |
24 | const util = {
25 | noop: () => {
26 | // do nothing
27 | },
28 | };
29 |
30 | class FakeResourceStream extends Transform {
31 | calledWith: IArguments;
32 | constructor() {
33 | super({objectMode: true});
34 | /* eslint-disable-next-line prefer-rest-params */
35 | this.calledWith = arguments;
36 | }
37 | }
38 |
39 | const p = proxyquire('../src', {
40 | './resource-stream': {ResourceStream: FakeResourceStream},
41 | }) as typeof P;
42 |
43 | const sandbox = sinon.createSandbox();
44 |
45 | // eslint-disable-next-line no-undef
46 | afterEach(() => {
47 | sandbox.restore();
48 | });
49 |
50 | /* eslint-disable @typescript-eslint/no-explicit-any */
51 | function createFakeStream() {
52 | return new PassThrough({objectMode: true}) as P.ResourceStream;
53 | }
54 |
55 | describe('paginator', () => {
56 | const UUID = uuid.v1();
57 | function FakeClass() {
58 | // do nothing
59 | }
60 |
61 | beforeEach(() => {
62 | FakeClass.prototype.methodToExtend = () => {
63 | return UUID;
64 | };
65 | delete FakeClass.prototype.methodToExtend_;
66 | });
67 |
68 | describe('extend', () => {
69 | it('should overwrite a method on a class', () => {
70 | const originalMethod = FakeClass.prototype.methodToExtend;
71 | paginator.extend(FakeClass, 'methodToExtend');
72 | const overwrittenMethod = FakeClass.prototype.methodToExtend;
73 | assert.notStrictEqual(originalMethod, overwrittenMethod);
74 | });
75 |
76 | it('should store the original method as a private member', () => {
77 | const originalMethod = FakeClass.prototype.methodToExtend;
78 | paginator.extend(FakeClass, 'methodToExtend');
79 | assert.strictEqual(originalMethod, FakeClass.prototype.methodToExtend_);
80 | });
81 |
82 | it('should accept an array or string method names', () => {
83 | const originalMethod = FakeClass.prototype.methodToExtend;
84 | const anotherMethod = FakeClass.prototype.anotherMethodToExtend;
85 | const methodsToExtend = ['methodToExtend', 'anotherMethodToExtend'];
86 | paginator.extend(FakeClass, methodsToExtend);
87 | assert.notStrictEqual(originalMethod, FakeClass.prototype.methodToExtend);
88 | assert.notStrictEqual(
89 | anotherMethod,
90 | FakeClass.prototype.anotherMethodToExtend,
91 | );
92 | });
93 |
94 | it('should parse the arguments', done => {
95 | sandbox.stub(paginator, 'parseArguments_').callsFake(args => {
96 | assert.deepStrictEqual([].slice.call(args), [1, 2, 3]);
97 | done();
98 | return args as ParsedArguments;
99 | });
100 | sandbox.stub(paginator, 'run_').callsFake(util.noop);
101 | paginator.extend(FakeClass, 'methodToExtend');
102 | FakeClass.prototype.methodToExtend(1, 2, 3);
103 | });
104 |
105 | it('should call router when the original method is called', done => {
106 | const expectedReturnValue = FakeClass.prototype.methodToExtend();
107 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments;
108 |
109 | sandbox.stub(paginator, 'parseArguments_').returns(parsedArguments);
110 | sandbox.stub(paginator, 'run_').callsFake((args, originalMethod) => {
111 | assert.strictEqual(args, parsedArguments);
112 | assert.strictEqual(originalMethod(), expectedReturnValue);
113 | done();
114 | });
115 |
116 | paginator.extend(FakeClass, 'methodToExtend');
117 | FakeClass.prototype.methodToExtend();
118 | });
119 |
120 | it('should maintain `this` context', done => {
121 | FakeClass.prototype.methodToExtend = function () {
122 | return this.uuid;
123 | };
124 |
125 | /* eslint-disable @typescript-eslint/no-explicit-any */
126 | const cls = new (FakeClass as any)();
127 | cls.uuid = uuid.v1();
128 |
129 | sandbox.stub(paginator, 'run_').callsFake((_, originalMethod) => {
130 | assert.strictEqual(originalMethod(), cls.uuid);
131 | done();
132 | });
133 |
134 | paginator.extend(FakeClass, 'methodToExtend');
135 | cls.methodToExtend();
136 | });
137 |
138 | it('should return what the router returns', () => {
139 | const uniqueValue = 234;
140 | sandbox.stub(paginator, 'run_').callsFake(() => {
141 | return uniqueValue;
142 | });
143 | paginator.extend(FakeClass, 'methodToExtend');
144 | assert.strictEqual(FakeClass.prototype.methodToExtend(), uniqueValue);
145 | });
146 | });
147 |
148 | describe('streamify', () => {
149 | beforeEach(() => {
150 | FakeClass.prototype.streamMethod = paginator.streamify('methodToExtend');
151 | });
152 |
153 | it('should return a function', () => {
154 | const fakeStreamMethod = FakeClass.prototype.streamMethod;
155 | assert.strictEqual(typeof fakeStreamMethod, 'function');
156 | });
157 |
158 | it('should parse the arguments', done => {
159 | const fakeArgs = [1, 2, 3];
160 |
161 | sandbox.stub(paginator, 'parseArguments_').callsFake(args => {
162 | assert.deepStrictEqual(fakeArgs, [].slice.call(args));
163 | done();
164 | return args as ParsedArguments;
165 | });
166 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream);
167 | FakeClass.prototype.streamMethod(...fakeArgs);
168 | });
169 |
170 | it('should run the method as a stream', done => {
171 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments;
172 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => {
173 | return parsedArguments;
174 | });
175 | sandbox.stub(paginator, 'runAsStream_').callsFake((args, callback) => {
176 | assert.strictEqual(args, parsedArguments);
177 | assert.strictEqual(callback(), UUID);
178 | setImmediate(done);
179 | return createFakeStream();
180 | });
181 |
182 | FakeClass.prototype.streamMethod();
183 | });
184 |
185 | it('should apply the proper context', done => {
186 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments;
187 | FakeClass.prototype.methodToExtend = function () {
188 | return this;
189 | };
190 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => {
191 | return parsedArguments;
192 | });
193 | sandbox.stub(paginator, 'runAsStream_').callsFake((_, callback) => {
194 | assert.strictEqual(callback(), FakeClass.prototype);
195 | setImmediate(done);
196 | return createFakeStream();
197 | });
198 | FakeClass.prototype.streamMethod();
199 | });
200 |
201 | it('should check for a private member', done => {
202 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments;
203 | const fakeValue = 123;
204 |
205 | FakeClass.prototype.methodToExtend_ = () => {
206 | return fakeValue;
207 | };
208 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => {
209 | return parsedArguments;
210 | });
211 | sandbox.stub(paginator, 'runAsStream_').callsFake((_, callback) => {
212 | assert.strictEqual(callback(), fakeValue);
213 | setImmediate(done);
214 | return createFakeStream();
215 | });
216 | FakeClass.prototype.streamMethod();
217 | });
218 |
219 | it('should return a stream', () => {
220 | const fakeStream = createFakeStream();
221 | sandbox.stub(paginator, 'parseArguments_').returns({});
222 | sandbox.stub(paginator, 'runAsStream_').returns(fakeStream);
223 | const stream = FakeClass.prototype.streamMethod();
224 | assert.strictEqual(fakeStream, stream);
225 | });
226 | });
227 |
228 | describe('parseArguments_', () => {
229 | it('should set defaults', () => {
230 | const parsedArguments = paginator.parseArguments_([]);
231 |
232 | assert.strictEqual(Object.keys(parsedArguments.query!).length, 0);
233 | assert.strictEqual(parsedArguments.autoPaginate, true);
234 | assert.strictEqual(parsedArguments.maxApiCalls, -1);
235 | assert.strictEqual(parsedArguments.maxResults, -1);
236 | assert.strictEqual(parsedArguments.callback, undefined);
237 | });
238 |
239 | it('should detect a callback if first argument is a function', () => {
240 | const args = [util.noop];
241 | const parsedArguments = paginator.parseArguments_(args);
242 |
243 | assert.strictEqual(parsedArguments.callback, args[0]);
244 | });
245 |
246 | it('should use any other first argument as query', () => {
247 | const args = ['string'];
248 | const parsedArguments = paginator.parseArguments_(args);
249 |
250 | assert.strictEqual(parsedArguments.query, args[0]);
251 | });
252 |
253 | it('should not make an undefined value the query', () => {
254 | const args = [undefined, util.noop];
255 | const parsedArguments = paginator.parseArguments_(args);
256 |
257 | assert.deepStrictEqual(parsedArguments.query, {});
258 | });
259 |
260 | it('should detect a callback if last argument is a function', () => {
261 | const args = ['string', util.noop];
262 | const parsedArguments = paginator.parseArguments_(args);
263 |
264 | assert.strictEqual(parsedArguments.callback, args[1]);
265 | });
266 |
267 | it('should not assign a callback if a fn is not provided', () => {
268 | const args = ['string'];
269 | const parsedArguments = paginator.parseArguments_(args);
270 |
271 | assert.strictEqual(parsedArguments.callback, undefined);
272 | });
273 |
274 | it('should set maxApiCalls from query.maxApiCalls', () => {
275 | const args = [{maxApiCalls: 10}];
276 | const parsedArguments = paginator.parseArguments_(args);
277 |
278 | assert.strictEqual(parsedArguments.maxApiCalls, args[0].maxApiCalls);
279 | assert.strictEqual(parsedArguments.query!.maxApiCalls, undefined);
280 | });
281 |
282 | it('should set maxResults from query.maxResults', () => {
283 | const args = [{maxResults: 10}];
284 | const parsedArguments = paginator.parseArguments_(args);
285 |
286 | assert.strictEqual(parsedArguments.maxResults, args[0].maxResults);
287 | });
288 |
289 | it('should set maxResults from query.pageSize', () => {
290 | const args = [{pageSize: 10}];
291 | const parsedArguments = paginator.parseArguments_(args);
292 |
293 | assert.strictEqual(parsedArguments.maxResults, args[0].pageSize);
294 | });
295 |
296 | it('should set autoPaginate: false if there is a maxResults', () => {
297 | const args = [{maxResults: 10}, util.noop];
298 | const parsedArguments = paginator.parseArguments_(args);
299 |
300 | assert.strictEqual(parsedArguments.autoPaginate, false);
301 | });
302 |
303 | it('should set autoPaginate: false query.autoPaginate', () => {
304 | const args = [{autoPaginate: false}, util.noop];
305 | const parsedArguments = paginator.parseArguments_(args);
306 |
307 | assert.strictEqual(parsedArguments.autoPaginate, false);
308 | });
309 |
310 | it('should parse streamOptions', () => {
311 | const args = [{maxResults: 10, highWaterMark: 8}];
312 | const parsedArguments = paginator.parseArguments_(args);
313 |
314 | assert.strictEqual(parsedArguments.maxResults, 10);
315 | assert.deepStrictEqual(parsedArguments.streamOptions, {
316 | highWaterMark: 8,
317 | });
318 | });
319 | });
320 |
321 | describe('run_', () => {
322 | describe('autoPaginate', () => {
323 | describe('originalmethod is callback based', () => {
324 | it('should call runAsStream_ when autoPaginate:true', done => {
325 | const parsedArguments = {
326 | autoPaginate: true,
327 | callback: util.noop,
328 | };
329 |
330 | sandbox
331 | .stub(paginator, 'runAsStream_')
332 | .callsFake((args, originalMethod) => {
333 | assert.strictEqual(args, parsedArguments);
334 | originalMethod();
335 | return createFakeStream();
336 | });
337 |
338 | paginator.run_(parsedArguments, done);
339 | });
340 |
341 | it('should execute callback on error', done => {
342 | const error = new Error('Error.');
343 |
344 | const parsedArguments = {
345 | autoPaginate: true,
346 | callback(err: Error) {
347 | assert.strictEqual(err, error);
348 | done();
349 | },
350 | };
351 |
352 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
353 | const stream = createFakeStream();
354 | setImmediate(() => {
355 | stream.emit('error', error);
356 | });
357 | return stream;
358 | });
359 |
360 | paginator.run_(parsedArguments, util.noop);
361 | });
362 |
363 | it('should return all results on end', done => {
364 | const results = [{a: 1}, {b: 2}, {c: 3}];
365 |
366 | const parsedArguments = {
367 | autoPaginate: true,
368 | callback(err: Error, results_: {}) {
369 | assert.deepStrictEqual(results_, results);
370 | done();
371 | },
372 | };
373 |
374 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
375 | const stream = createFakeStream();
376 | setImmediate(() => {
377 | results.forEach(result => stream.push(result));
378 | stream.push(null);
379 | });
380 | return stream;
381 | });
382 |
383 | paginator.run_(parsedArguments, util.noop);
384 | });
385 |
386 | it('should return all results and extra args', done => {
387 | const results = [{a: 1}, {b: 2}, {c: 3}];
388 | const args: any[] = [{msg: 'OK'}, 10];
389 |
390 | const parsedArguments = {
391 | autoPaginate: true,
392 | callback(
393 | err: Error,
394 | results_: {},
395 | query: {},
396 | fakeRes: {},
397 | anotherArg: number,
398 | ) {
399 | assert.deepStrictEqual(results_, results);
400 | assert.deepStrictEqual(query, undefined);
401 | assert.deepStrictEqual(fakeRes, {msg: 'OK'});
402 | assert.deepStrictEqual(anotherArg, 10);
403 | done();
404 | },
405 | };
406 |
407 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
408 | const stream = createFakeStream();
409 | setImmediate(() => {
410 | results.forEach(result => stream.push(result));
411 | stream.push(null);
412 | stream._otherArgs = args;
413 | });
414 | return stream;
415 | });
416 |
417 | paginator.run_(parsedArguments, util.noop);
418 | });
419 | });
420 |
421 | describe('original method is promise based', () => {
422 | const parsedArguments = {
423 | autoPaginate: true,
424 | };
425 | it('should call runAsStream_ when autoPaginate:true', done => {
426 | sandbox
427 | .stub(paginator, 'runAsStream_')
428 | .callsFake((args, originalMethod) => {
429 | assert.strictEqual(args, parsedArguments);
430 | originalMethod();
431 | return createFakeStream();
432 | });
433 |
434 | paginator.run_(parsedArguments, done);
435 | });
436 |
437 | it('should reject a promise on error', () => {
438 | const error = new Error('Error.');
439 |
440 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
441 | const stream = createFakeStream();
442 | setImmediate(() => {
443 | stream.emit('error', error);
444 | });
445 | return stream;
446 | });
447 |
448 | paginator
449 | .run_(parsedArguments, util.noop)
450 | .then(util.noop, (err: Error) => assert.strictEqual(err, error));
451 | });
452 |
453 | it('should resolve with all results on end', () => {
454 | const results = [{a: 1}, {b: 2}, {c: 3}];
455 |
456 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
457 | const stream = createFakeStream();
458 | setImmediate(() => {
459 | results.forEach(result => stream.push(result));
460 | stream.push(null);
461 | });
462 | return stream;
463 | });
464 |
465 | paginator
466 | .run_(parsedArguments, util.noop)
467 | .then(([results_]: [1]) =>
468 | assert.deepStrictEqual(results_, results),
469 | );
470 | });
471 |
472 | it('should resolve with all results and extra args', done => {
473 | const results = [{a: 1}, {b: 2}, {c: 3}];
474 | const args: any[] = [{msg: 'OK'}, 10];
475 |
476 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => {
477 | const stream = createFakeStream();
478 | setImmediate(() => {
479 | results.forEach(result => stream.push(result));
480 | stream.push(null);
481 | stream._otherArgs = args;
482 | });
483 | return stream;
484 | });
485 |
486 | paginator
487 | .run_(parsedArguments, util.noop)
488 | .then(([results_, query_, fakeRes, anotherArg]: unknown[]) => {
489 | assert.deepStrictEqual(results_, results);
490 | assert.deepStrictEqual(query_, undefined);
491 | assert.deepEqual(fakeRes, {msg: 'OK'});
492 | assert.deepEqual(anotherArg, 10);
493 | done();
494 | });
495 | });
496 | });
497 | });
498 |
499 | describe('manual pagination', () => {
500 | describe('originalmethod is callback based', () => {
501 | it('should recognize autoPaginate: false', done => {
502 | const parsedArguments = {
503 | autoPaginate: false,
504 | query: {
505 | a: 'b',
506 | c: 'd',
507 | },
508 | callback: done,
509 | } as ParsedArguments;
510 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream);
511 | paginator.run_(parsedArguments, (query: {}, callback: () => void) => {
512 | assert.deepStrictEqual(query, parsedArguments.query);
513 | callback();
514 | });
515 | });
516 | });
517 |
518 | describe('original method is promise based', () => {
519 | it('should recognize autoPaginate: false', () => {
520 | const parsedArguments = {
521 | autoPaginate: false,
522 | query: {
523 | a: 'b',
524 | c: 'd',
525 | },
526 | } as ParsedArguments;
527 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream);
528 | paginator.run_(parsedArguments, (query: {}) => {
529 | assert.deepStrictEqual(query, parsedArguments.query);
530 | });
531 | });
532 | });
533 | });
534 |
535 | describe('runAsStream_', () => {
536 | it('should create a resource stream', () => {
537 | const fakeArgs = {};
538 | const fakeFn = sandbox.spy();
539 | const stream = p.paginator.runAsStream_(
540 | fakeArgs,
541 | fakeFn,
542 | ) as unknown as FakeResourceStream;
543 |
544 | assert(stream instanceof FakeResourceStream);
545 | const [args, requestFn] = stream.calledWith;
546 | assert.strictEqual(args, fakeArgs);
547 | assert.strictEqual(requestFn, fakeFn);
548 | });
549 | });
550 | });
551 | });
552 |
--------------------------------------------------------------------------------