Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe), packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier (CLI app), vendor/zapier-platform, and research/zapier vendor corpus. Gate 0 structure checks pass. Product code and research are not yet wired.
This commit is contained in:
commit
b4150c8250
1364 changed files with 6814366 additions and 0 deletions
8
vendor/zapier-platform/packages/cli/.gitignore
vendored
Normal file
8
vendor/zapier-platform/packages/cli/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
node_modules
|
||||
*.zip
|
||||
*.log
|
||||
.zapier-*
|
||||
scratch/*
|
||||
lib/*
|
||||
.DS_Store
|
||||
.vscode
|
||||
1
vendor/zapier-platform/packages/cli/.nvmrc
vendored
Normal file
1
vendor/zapier-platform/packages/cli/.nvmrc
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
v22
|
||||
36
vendor/zapier-platform/packages/cli/ARCHITECTURE.md
vendored
Normal file
36
vendor/zapier-platform/packages/cli/ARCHITECTURE.md
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# `zapier-platform-cli` Architecture
|
||||
|
||||
## Purpose
|
||||
|
||||
- This package, released as `zapier-platform-cli` publicly on [npm](https://www.npmjs.com/package/zapier-platform-cli), powers the Zapier CLI (invoked via the `zapier-platform` command).
|
||||
- Developers install and use this tool to complete operations required to develop an integration on the Zapier platform.
|
||||
- The `core`, `schema`, and `cli` packages are always released together under matching version numbers.
|
||||
|
||||
## Technical Organization
|
||||
|
||||
> `cli/...`, when used in a path to a file, is shorthand for `zapier-platform/packages/cli/...`
|
||||
|
||||
### Commands
|
||||
|
||||
- We use the [oclif](https://github.com/oclif/oclif) (created for the Heroku CLI) to power our CLI
|
||||
- Each command (`zapier-platform push`, `zapier-platform test`, etc) gets its own file in the `cli/src/oclif/commands` directory.
|
||||
- Some sub-commands live in sub-directories, such as `zapier-platform env:get` (which is found in `cli/src/oclif/commands/env/get.js`).
|
||||
- All commands inherit from `ZapierBaseCommand`, which provides convenient methods for parsing flags, printing structured data, and verifying authentication
|
||||
- Command code should be responsible for all user-facing behavior, including:
|
||||
- loading indicators
|
||||
- printing data
|
||||
- supplying and parsing args/flags
|
||||
- throwing descriptive error messages
|
||||
- Core functionality should mostly live in the `utils` folder (though we hope to organize it better eventually); see next section
|
||||
|
||||
### Everything Else
|
||||
|
||||
- All other functionality (especially as it relates to calling APIs) lives in the `cli/src/utils` folder); It's a bit of a kitchen sink
|
||||
- The most important file is probably `cli/src/utils/api.js`, which controls authentication and API calls against the Zapier monolith
|
||||
- The next most important file is probably `cli/src/utils/display.js`, which powers the loading spinner and printing tables. In a command you typically won't use these directly (prefer methods on `ZapierBaseCommand`).
|
||||
- While commands should be user-facing functionality, "business logic" should prefer to live in this folder. It may get a new name at some point
|
||||
|
||||
### Tests
|
||||
|
||||
- Most functions in `cli/src/utils` have unit test coverage (`cli/src/tests/utils`).
|
||||
- There are also "smoke tests", which simulate the full flow of the user journey (install, create an app, scaffold a trigger, run the tests), found in `cli/src/smoke-tests`
|
||||
3
vendor/zapier-platform/packages/cli/LICENSE
vendored
Normal file
3
vendor/zapier-platform/packages/cli/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Copyright (c) Zapier, Inc.
|
||||
|
||||
This repository is part of Zapier Platform. By downloading, installing, accessing, or using any part of the Zapier Platform, including this repository, you agree to the Zapier Platform Agreement, which can be found at: https://zapier.com/platform/tos. If you do not agree to the Zapier Platform Agreement, you may not download, install, access, or use any part of the Zapier Platform, including this repository.
|
||||
15
vendor/zapier-platform/packages/cli/README.md
vendored
Normal file
15
vendor/zapier-platform/packages/cli/README.md
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Zapier Platform CLI
|
||||
|
||||
This is the `zapier-platform` CLI, which you can use to build integrations with Zapier.
|
||||
|
||||
For documentation:
|
||||
|
||||
- [Platform Docs](https://docs.zapier.com/platform)
|
||||
- [CLI Reference](https://github.com/zapier/zapier-platform/blob/main/packages/cli/docs/cli.md)
|
||||
- [Schema Reference](https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md)
|
||||
|
||||
## Development
|
||||
|
||||
The published CLI exposes the **`zapier-platform`** command only (as of v19). The legacy **`zapier`** binary has been removed.
|
||||
|
||||
See [CONTRIBUTING.md](https://github.com/zapier/zapier-platform/blob/main/CONTRIBUTING.md) and [ARCHITECTURE.md](https://github.com/zapier/zapier-platform/blob/main/packages/cli/ARCHITECTURE.md) of this package in particular.
|
||||
1450
vendor/zapier-platform/packages/cli/docs/cli.html
vendored
Normal file
1450
vendor/zapier-platform/packages/cli/docs/cli.html
vendored
Normal file
File diff suppressed because one or more lines are too long
987
vendor/zapier-platform/packages/cli/docs/cli.md
vendored
Normal file
987
vendor/zapier-platform/packages/cli/docs/cli.md
vendored
Normal file
|
|
@ -0,0 +1,987 @@
|
|||
# Zapier CLI Command Reference
|
||||
|
||||
These are the generated docs for all Zapier platform CLI commands.
|
||||
|
||||
You can install the CLI with `npm install -g zapier-platform-cli`.
|
||||
|
||||
```bash
|
||||
$ npm install -g zapier-platform-cli
|
||||
```
|
||||
|
||||
# Commands
|
||||
|
||||
## analytics
|
||||
|
||||
> Show the status of the analytics that are collected. Also used to change what is collected.
|
||||
|
||||
**Usage**: `zapier-platform analytics`
|
||||
|
||||
**Flags**
|
||||
* `-m, --mode` | Choose how much information to share. Anonymous mode drops the OS type and Zapier user id, but keeps command info. Identifying information is used only for debugging purposes. One of `[enabled | anonymous | disabled]`.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform analytics --mode enabled`
|
||||
|
||||
|
||||
## build
|
||||
|
||||
> Build a pushable zip from the current directory.
|
||||
|
||||
**Usage**: `zapier-platform build`
|
||||
|
||||
This command does the following:
|
||||
|
||||
* Creates a temporary folder
|
||||
* Copies all code into the temporary folder
|
||||
* Adds an entry point: `zapierwrapper.js`
|
||||
* Generates and validates app definition.
|
||||
* Detects dependencies via esbuild (optional, on by default)
|
||||
* Zips up all needed `.js` files. If you want to include more files, add a "includeInBuild" property (array with strings of regexp paths) to your `.zapierapprc`.
|
||||
* Moves the zip to `build/build.zip` and `build/source.zip` and deletes the temp folder
|
||||
|
||||
This command is typically followed by `zapier-platform upload`.
|
||||
|
||||
**Flags**
|
||||
* `--disable-dependency-detection` | Disable "smart" file inclusion. By default, Zapier only includes files that are required by your entry point (`index.js` by default). If you (or your dependencies) require files dynamically (such as with `require(someVar)`), then you may see "Cannot find module" errors. Disabling this may make your `build.zip` too large. If that's the case, try using the `includeInBuild` option in your `.zapierapprc`. See the docs about `includeInBuild` for more info.
|
||||
* `--skip-dep-install` | [alias: --skip-npm-install]
|
||||
Skips installing a fresh copy of dependencies for shorter build time. Helpful for using yarn, pnpm, or local copies of dependencies.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## canary:create
|
||||
|
||||
> Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.
|
||||
|
||||
**Usage**: `zapier-platform canary:create VERSIONFROM VERSIONTO`
|
||||
|
||||
Only one canary can be active at the same time. You can run `zapier-platform canary:list` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using `zapier-platform canary:delete a.b.c x.y.z`.
|
||||
|
||||
To canary traffic for a specific user, use the --user flag.
|
||||
|
||||
To canary traffic for an entire account, use the --account-id. Note: this scenario is only permitted for Zapier staff.
|
||||
|
||||
To canary traffic for a specific user within a specific account, use both --user and --account-id flags.
|
||||
|
||||
Note: this is similar to `zapier-platform migrate` but different in that this is temporary and will "revert" the changes once the specified duration is expired.
|
||||
|
||||
**Only use this command to canary traffic between non-breaking versions!**
|
||||
|
||||
**Arguments**
|
||||
* (required) `versionFrom` | Version to route traffic from
|
||||
* (required) `versionTo` | Version to canary traffic to
|
||||
|
||||
**Flags**
|
||||
* (required) `-p, --percent` | Percent of traffic to route to new version
|
||||
* (required) `-d, --duration` | Duration of the canary in seconds
|
||||
* `-u, --user` | Canary this user (email) across all accounts, unless `account-id` is specified.
|
||||
* `-a, --account-id` | The account ID to target. If user is specified, only canary the user within this account. If user is not specified, then this argument is only permitted for Zapier staff.
|
||||
* `-f, --force-include-all` | Overrides any default filters the canary system imposes. This argument is only permitted for Zapier staff.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform canary:create 1.0.0 1.1.0 -p 10 -d 3600`
|
||||
* `zapier-platform canary:create 2.0.0 2.1.0 --percent 25 --duration 1800 --user user@example.com`
|
||||
* `zapier-platform canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345 -u user@example.com`
|
||||
* `zapier-platform canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345`
|
||||
|
||||
|
||||
## canary:delete
|
||||
|
||||
> Delete an active canary deployment
|
||||
|
||||
**Usage**: `zapier-platform canary:delete VERSIONFROM VERSIONTO`
|
||||
|
||||
**Arguments**
|
||||
* (required) `versionFrom` | Version to route traffic from
|
||||
* (required) `versionTo` | Version canary traffic is routed to
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform canary:delete 1.0.0 1.1.0`
|
||||
|
||||
|
||||
## canary:list
|
||||
|
||||
> List all active canary deployments
|
||||
|
||||
**Usage**: `zapier-platform canary:list`
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform canary:list`
|
||||
|
||||
|
||||
## convert
|
||||
|
||||
> Convert a Visual Builder integration to a CLI integration.
|
||||
|
||||
**Usage**: `zapier-platform convert PATH`
|
||||
|
||||
The resulting CLI integration will be identical to its Visual Builder version and ready to push and use immediately!
|
||||
|
||||
If you re-run this command on an existing directory it will leave existing files alone and not clobber them.
|
||||
|
||||
You'll need to do a `zapier-platform push` before the new version is visible in the editor, but otherwise you're good to go.
|
||||
|
||||
**Arguments**
|
||||
* (required) `path` | Relative to your current path - IE: `.` for current directory.
|
||||
|
||||
**Flags**
|
||||
* `-i, --integrationId` | To get the integration/app ID, go to "https://developer.zapier.com", click on an integration, and copy the number directly after "/app/" in the URL.
|
||||
* `-v, --version` | Convert a specific version. Required when converting a Visual Builder integration.
|
||||
* `-j, --json` | The JSON definition to use, as alternative for reading from a Visual Builder integration. Must be a JSON-encoded object. The data can be passed from the command directly like '{"key": "value"}', read from a file like @file.json, or read from stdin like @-.
|
||||
* `-t, --title` | The integration title, which will be snake-cased for the package.json name.
|
||||
* `-d, --description` | The integration description, which will be used for the package.json description.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## delete:integration
|
||||
|
||||
> Delete your integration (including all versions).
|
||||
|
||||
**Usage**: `zapier-platform delete:integration`
|
||||
|
||||
This only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the `zapier-platform delete:version` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Aliases**
|
||||
* `delete:app`
|
||||
|
||||
|
||||
## delete:version
|
||||
|
||||
> Delete a specific version of your integration.
|
||||
|
||||
**Usage**: `zapier-platform delete:version VERSION`
|
||||
|
||||
This only works if there are no users or Zaps on that version. You will probably need to have run `zapier-platform migrate` and `zapier-platform deprecate` before this command will work.
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | Specify the version to delete. It must have no users or Zaps.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## deprecate
|
||||
|
||||
> Mark a non-production version of your integration as deprecated, with removal by a certain date.
|
||||
|
||||
**Usage**: `zapier-platform deprecate VERSION DATE`
|
||||
|
||||
Use this when an integration version will not be supported or start breaking at a known date.
|
||||
|
||||
When deprecating a version, you must provide a reason for the deprecation. You can either specify the reason using the --reason flag or you will be prompted to select from the following options:
|
||||
- API endpoint deprecated
|
||||
- Security vulnerability
|
||||
- Critical bug
|
||||
- Legal requirement
|
||||
- Other
|
||||
|
||||
The deprecation date must be at least 3 weeks days in the future. Zapier will send emails warning users of the deprecation exactly 14 days before the configured deprecation date. This gives you 1 week to migrate users to a newer version, if possible, before we notify them that they need to do so themselves.
|
||||
|
||||
There are other side effects: they'll start seeing it as "Deprecated" in the UI, and once the deprecation date arrives, if the Zaps weren't updated, they'll be paused and the users will be emailed again explaining what happened.
|
||||
|
||||
Do not use deprecation if you only have non-breaking changes, such as:
|
||||
- Fixing help text
|
||||
- Adding new triggers/actions
|
||||
- Improving existing functionality
|
||||
- other bug fixes that don't break existing automations.
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version to deprecate.
|
||||
* (required) `date` | The date (YYYY-MM-DD) when Zapier will make the specified version unavailable.
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Skip confirmation prompt. Use with caution.
|
||||
* `-r, --reason` | Reason for deprecation. One of `[api endpoint deprecated | security vulnerability | critical bug | legal requirement | other]`.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform deprecate 1.2.3 2011-10-01`
|
||||
* `zapier-platform deprecate 1.2.3 2011-10-01 --reason=security_vulnerability`
|
||||
* `zapier-platform deprecate 1.2.3 2011-10-01 -r critical_bug`
|
||||
|
||||
|
||||
## describe
|
||||
|
||||
> Describe the current integration.
|
||||
|
||||
**Usage**: `zapier-platform describe`
|
||||
|
||||
This command prints a human readable enumeration of your integrations's
|
||||
triggers, searches, and creates as seen by Zapier. Useful to understand how your
|
||||
resources convert and relate to different actions.
|
||||
|
||||
* **Noun**: your action's noun
|
||||
* **Label**: your action's label
|
||||
* **Resource**: the resource (if any) this action is tied to
|
||||
* **Available Methods**: testable methods for this action
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
|
||||
## env:get
|
||||
|
||||
> Get environment variables for a version.
|
||||
|
||||
**Usage**: `zapier-platform env:get VERSION`
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version to get the environment for.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform env:get 1.2.3`
|
||||
|
||||
|
||||
## env:set
|
||||
|
||||
> Set environment variables for a version.
|
||||
|
||||
**Usage**: `zapier-platform env:set VERSION [KEY-VALUE PAIRS...]`
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version to set the environment for. Values are copied forward when a new version is created, but this command will only ever affect the specified version.
|
||||
* `key-value pairs...` | The key-value pairs to set. Keys are case-insensitive. Each pair should be space separated and pairs should be separated by an `=`. For example: `A=123 B=456`
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Force the update of environment variables regardless if the app version is production or not. Use with caution.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform env:set 1.2.3 SECRET=12345 OTHER=4321`
|
||||
|
||||
|
||||
## env:unset
|
||||
|
||||
> Unset environment variables for a version.
|
||||
|
||||
**Usage**: `zapier-platform env:unset VERSION [KEYS...]`
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version to set the environment for.
|
||||
* `keys...` | The keys to unset. Keys are case-insensitive.
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Force the update of environment variables regardless if the app version is production or not. Use with caution.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform env:unset 1.2.3 SECRET OTHER`
|
||||
|
||||
|
||||
## history
|
||||
|
||||
> Get the history of your integration.
|
||||
|
||||
**Usage**: `zapier-platform history`
|
||||
|
||||
History includes all the changes made over the lifetime of your integration. This includes everything from creation, updates, migrations, admins, and invitee changes, as well as who made the change and when.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
|
||||
## init
|
||||
|
||||
> Initialize a new Zapier integration with a project template.
|
||||
|
||||
**Usage**: `zapier-platform init PATH`
|
||||
|
||||
After running this, you'll have a new integration in the specified directory. If you re-run this command on an existing directory, it will prompt before overwriting any existing files.
|
||||
|
||||
This doesn't register or deploy the integration with Zapier - try the `zapier-platform register` and `zapier-platform push` commands for that!
|
||||
|
||||
**Arguments**
|
||||
* (required) `path` | Where to create the new integration. If the directory doesn't exist, it will be created. If the directory isn't empty, we'll ask for confirmation
|
||||
|
||||
**Flags**
|
||||
* `-t, --template` | The template to start your integration with. One of `[basic-auth | callback | custom-auth | digest-auth | dynamic-dropdown | files | line-items | minimal | oauth1-trello | oauth2 | openai | search-or-create | session-auth]`.
|
||||
* `-m, --module` | Choose module type: CommonJS or ES Modules. Only enabled for Typescript and Minimal templates. One of `[commonjs | esm]`.
|
||||
* `-l, --language` | Choose the language to use for your new integration. Defaults to JavaScript. One of `[javascript | typescript]`.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform init myapp`
|
||||
* `zapier-platform init ./path/myapp --template oauth2`
|
||||
* `zapier-platform init ./path/myapp --template minimal --module esm`
|
||||
* `zapier-platform init ./path/myapp --template oauth2 --language typescript`
|
||||
|
||||
|
||||
## integrations
|
||||
|
||||
> List integrations you have admin access to.
|
||||
|
||||
**Usage**: `zapier-platform integrations`
|
||||
|
||||
This command also checks the current directory for a linked integration.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Aliases**
|
||||
* `apps`
|
||||
|
||||
|
||||
## invoke
|
||||
|
||||
> Invoke an authentication method, a trigger, or a create/search action locally or remotely.
|
||||
|
||||
**Usage**: `zapier-platform invoke [ACTIONTYPE] [ACTIONKEY] [AUTHDATA]`
|
||||
|
||||
This command allows you to invoke your integration's authentication, triggers, and actions. With this tool, you can test and debug your integration code directly from your terminal without leaving your development environment and opening a browser.
|
||||
|
||||
Why use this command?
|
||||
|
||||
* Fast feedback loops: Verify your code changes instantly.
|
||||
* Step-by-step debugging: Use a debugger to step through your code locally.
|
||||
* Untruncated logs: View complete HTTP logs and errors in your terminal.
|
||||
|
||||
### Modes
|
||||
|
||||
The `invoke` command works in three modes:
|
||||
|
||||
1. Local mode (default): runs your code locally, and sends outgoing requests directly from your local machine.
|
||||
2. Relay mode (experimental): runs your code locally, but proxies all outgoing requests through Zapier using production authentication data.
|
||||
3. Remote mode: runs your code and sends outgoing requests entirely in/from Zapier production environment.
|
||||
|
||||
**Local mode** is the default mode. Without the `--remote` (or `-r`) flag or the `--authentication-id` (or `-a`) flag, the command runs in local mode. It's useful when you want to quickly test your integration code locally. You'll need to set up local auth data in the `.env` file using the `zapier-platform invoke auth start` command.
|
||||
|
||||
**Relay mode** is currently experimental. It's enabled when the `-a` flag is specified. It's useful when you want to test code locally but setting up local auth data is troublesome, such as when your OAuth2 server requires a non-localhost or HTTPS redirect URI. By specifying `-a <authentication-id>`, all outgoing requests will be proxied through Zapier's relay service using the production auth data with the given authentication ID. See the **Authentication** section below for more details.
|
||||
|
||||
Both local and relay mode **emulate** how your code would run in Zapier production environment, so the behavior might not be exactly the same. But we consider every inconsistency a bug or a limitation to be fixed. For 100% match with production behavior, use remote mode.
|
||||
|
||||
**Remote mode** is enabled when the `--remote` (or `-r`) flag is specified. It's useful when you want to verify how your code behaves in Zapier production environment. Note that remote mode requires deploying your integration first. If the `-a` flag is not specified, the command will prompt you to select one of your available authentications/connections in production. By default, the remote mode invokes the `version` set in your `package.json`. You can use the `--version` (or `-v`) flag to specify a different deployed version.
|
||||
|
||||
### Authentication
|
||||
|
||||
You can supply the authentcation data in two ways: Load from the local `.env` file or use the `--authentication-id` flag.
|
||||
|
||||
#### The local `.env` file
|
||||
|
||||
This command loads environment variables and `authData` from the `.env` file in the current directory. If you don't have a `.env` file yet, you can use the `zapier-platform invoke auth start` command to help you initialize it, or you can manually create it.
|
||||
|
||||
The `zapier-platform invoke auth start` subcommand will prompt you for the necessary auth fields and save them to the `.env` file. For OAuth2, it will start a local HTTP server, open the authorization URL in the browser, wait for the OAuth2 redirect, and get the access token.
|
||||
|
||||
Each line in the `.env` file should follow one of these formats:
|
||||
|
||||
* `VAR_NAME=VALUE` for environment variables
|
||||
* `authData_FIELD_KEY=VALUE` for auth data fields
|
||||
|
||||
For example, a `.env` file for an OAuth2 integration might look like this:
|
||||
|
||||
```
|
||||
CLIENT_ID='your_client_id'
|
||||
CLIENT_SECRET='your_client_secret'
|
||||
authData_access_token='1234567890'
|
||||
authData_refresh_token='abcdefg'
|
||||
authData_account_name='zapier'
|
||||
```
|
||||
|
||||
|
||||
#### The `--authentication-id` flag
|
||||
|
||||
Setting up local auth data can be troublesome. For instance, in OAuth2, you may have to configure your app server to allow localhost redirect URIs or use a port forwarding tool. This is sometimes not easy to get right.
|
||||
|
||||
The `--authentication-id` flag (`-a` for short) gives you an alternative (and perhaps easier) way to supply your auth data. You can use `-a` to specify an existing production authentication/connection. The available authentications can be found at https://zapier.com/app/assets/connections. Check https://zpr.io/z8SjFTdnTFZ2 for more instructions.
|
||||
|
||||
When `-a -` is specified, such as `zapier-platform invoke auth test -a -`, the command will interactively prompt you to select one of your available authentications.
|
||||
|
||||
If you know your authentication ID, you can specify it directly, such as `zapier-platform invoke auth test -a 123456`.
|
||||
|
||||
The `-a` flag also works in remote mode with the `-r` flag. In remote mode, if `-a` is not specified, such as `zapier-platform invoke -r`, the command will prompt you to select one of your available authentications.
|
||||
|
||||
#### Testing authentication
|
||||
|
||||
To test if the auth data is correct, run either one of these:
|
||||
|
||||
```
|
||||
zapier-platform invoke auth test # invokes authentication.test method
|
||||
zapier-platform invoke auth label # invokes authentication.test and renders connection label
|
||||
```
|
||||
|
||||
To refresh stale auth data for OAuth2 or session auth, run `zapier-platform invoke auth refresh`. Note that refreshing is only applicable for local auth data in the `.env` file.
|
||||
|
||||
### Invoking a trigger or an action
|
||||
|
||||
Once you have the correct auth data, you can test an trigger, a search, or a create action. For example, here's how you invoke a trigger with the key `new_recipe`:
|
||||
|
||||
```
|
||||
zapier-platform invoke trigger new_recipe # (local mode)
|
||||
zapier-platform invoke trigger new_recipe -r # (remote mode)
|
||||
```
|
||||
|
||||
To add input data, use the `--inputData` flag (`-i` for short). The input data can come from the command directly, a file, or stdin. See **EXAMPLES** below.
|
||||
|
||||
When you miss any command arguments, such as ACTIONTYPE or ACTIONKEY, the command will prompt you interactively. If you don't want to get interactive prompts, use the `--non-interactive` flag.
|
||||
|
||||
The `--debug` flag will show you the HTTP request logs and any console logs you have in your code.
|
||||
|
||||
### Limitations in local and relay mode
|
||||
|
||||
The following is a non-exhaustive list of current limitations in local and relay mode. We may support them in the future.
|
||||
|
||||
- Hook triggers, including REST hook subscribe/unsubscribe
|
||||
- Output hydration
|
||||
- File upload
|
||||
- Function-based connection label
|
||||
- Buffered create actions
|
||||
- Search-or-create actions
|
||||
- Search-powered fields
|
||||
- autoRefresh for OAuth2 and session auth
|
||||
|
||||
|
||||
**Arguments**
|
||||
* `actionType` | The action type you want to invoke.
|
||||
* `actionKey` | The trigger/action key you want to invoke. If ACTIONTYPE is "auth", this can be "label", "refresh", "start", or "test".
|
||||
* `authData` | Only used by `auth render`. JSON-encoded object with auth field values (e.g. `'{"access_token":"a_token"}'`). Values here take precedence over the .env file.
|
||||
|
||||
**Flags**
|
||||
* `-i, --inputData` | The input data to pass to the action. Must be a JSON-encoded object. The data can be passed from the command directly like '{"key": "value"}', read from a file like @file.json, or read from stdin like @-.
|
||||
* `--isFillingDynamicDropdown` | Set bundle.meta.isFillingDynamicDropdown to true. Only makes sense for a polling trigger. When true in production, this poll is being used to populate a dynamic dropdown.
|
||||
* `--isLoadingSample` | Set bundle.meta.isLoadingSample to true. When true in production, this run is initiated by the user in the Zap editor trying to pull a sample.
|
||||
* `--isPopulatingDedupe` | Set bundle.meta.isPopulatingDedupe to true. Only makes sense for a polling trigger. When true in production, the results of this poll will be used initialize the deduplication list rather than trigger a Zap. This happens when a user enables a Zap.
|
||||
* `--limit` | Set bundle.meta.limit. Only makes sense for a trigger. When used in production, this indicates the number of items you should fetch. -1 means no limit. Defaults to `-1`.
|
||||
* `-p, --page` | Set bundle.meta.page. Only makes sense for a trigger. When used in production, this indicates which page of items you should fetch. First page is 0.
|
||||
* `--non-interactive` | Do not show interactive prompts.
|
||||
* `-z, --timezone` | Set the default timezone for datetime field interpretation. If not set, defaults to America/Chicago, which matches Zapier production behavior. Find the list timezone names at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. Defaults to `America/Chicago`.
|
||||
* `--redirect-uri` | Only used by `auth start` subcommand. The redirect URI that will be passed to the OAuth2 authorization URL. Usually this should match the one configured in your server's OAuth2 application settings. A local HTTP server will be started to listen for the OAuth2 callback. If your server requires a non-localhost or HTTPS address for the redirect URI, you can set up port forwarding to route the non-localhost or HTTPS address to localhost. Defaults to `http://localhost:9000`.
|
||||
* `--local-port` | Only used by `auth start` subcommand. The local port that will be used to start the local HTTP server to listen for the OAuth2 callback. This port can be different from the one in the redirect URI if you have port forwarding set up. Defaults to `9000`.
|
||||
* `-r, --remote` | Run your trigger/action remotely on Zapier production servers instead of locally. This requires deploying your integration first. Because this (remote) mode uses the same set of API endpoints as the Zap editor and other Zapier products, it allows you to verify exactly how your code will behave in production. Note that `--authentication-id` is required and implied in remote mode, as a production authentication is necessary to invoke in production.
|
||||
* `-v, --version` | Only used when `--remote` is set. Specify a deployed version to invoke instead of the one currently set in your local package.json.
|
||||
* `-a, --authentication-id` | EXPERIMENTAL: Instead of using the local .env file, use the production authentication data with the given authentication ID (aka the "app connection" on Zapier). Find them at https://zapier.com/app/assets/connections (https://zpr.io/z8SjFTdnTFZ2 for instructions) or specify '-' to interactively select one from your available authentications. When specified, the code will still run locally, but all outgoing requests will be proxied through Zapier with the production auth data.
|
||||
* `--paging-token` | Set bundle.meta.paging_token. Used for search pagination or bulk reads. When used in production, this indicates which page of items you should fetch.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform invoke`
|
||||
* `zapier-platform invoke auth start`
|
||||
* `zapier-platform invoke auth refresh`
|
||||
* `zapier-platform invoke auth test`
|
||||
* `zapier-platform invoke auth label`
|
||||
* `zapier-platform invoke trigger new_recipe`
|
||||
* `zapier-platform invoke create add_recipe --inputData '{"title": "Pancakes"}'`
|
||||
* `zapier-platform invoke search find_recipe -i @file.json --non-interactive`
|
||||
* `cat file.json | zapier-platform invoke trigger new_recipe -i @-`
|
||||
* `zapier-platform invoke search find_ticket --authentication-id 12345`
|
||||
* `zapier-platform invoke create add_ticket -a -`
|
||||
* `zapier-platform invoke trigger new_recipe --remote`
|
||||
* `zapier-platform invoke trigger new_recipe -r -a 12345`
|
||||
* `zapier-platform invoke -r -v 2.0.0 -a -`
|
||||
* `zapier-platform invoke auth render '{"access_token":"a_token"}'`
|
||||
|
||||
|
||||
## jobs
|
||||
|
||||
> Lists ongoing migration or promotion jobs for the current integration.
|
||||
|
||||
**Usage**: `zapier-platform jobs`
|
||||
|
||||
A job represents a background process that will be queued up when users execute a "migrate" or "promote" command for the current integration.
|
||||
|
||||
Each job will be added to the end of a queue of "promote" and "migration" jobs where the "Job Stage" will then be initialized with "requested".
|
||||
|
||||
Job stages will then move to "estimating", "in_progress" and finally one of four "end" stages: "complete", "aborted", "errored" or "paused".
|
||||
|
||||
Job times will vary as it depends on the size of the queue and how many users your integration has.
|
||||
|
||||
Jobs are returned from oldest to newest.
|
||||
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform jobs`
|
||||
|
||||
|
||||
## legacy
|
||||
|
||||
> Mark a non-production version of your integration as legacy.
|
||||
|
||||
**Usage**: `zapier-platform legacy VERSION`
|
||||
|
||||
Use this when an integration version is no longer recommended for new users, but you don't want to block existing users from using it.
|
||||
|
||||
Reasons why you might want to mark a version as legacy:
|
||||
- this version may be discontinued in the future
|
||||
- this version has bugs
|
||||
- a newer version has been released and you want to encourage users to upgrade
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version to mark as legacy.
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Skip confirmation prompt. Use with caution.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform legacy 1.2.3`
|
||||
|
||||
|
||||
## link
|
||||
|
||||
> Link the current directory with an existing integration.
|
||||
|
||||
**Usage**: `zapier-platform link`
|
||||
|
||||
This command generates a `.zapierapprc` file in the directory in which it's ran. This file ties this code to an integration and is referenced frequently during `push` and `validate` operations. This file should be checked into source control.
|
||||
|
||||
If you're starting an integration from scratch, use `zapier-platform init` instead.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## login
|
||||
|
||||
> Configure your `~/.zapierrc` with a deploy key.
|
||||
|
||||
**Usage**: `zapier-platform login`
|
||||
|
||||
**Flags**
|
||||
* `-s, --sso` | Use this flag if you log into Zapier a Single Sign-On (SSO) button and don't have a Zapier password.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## logout
|
||||
|
||||
> Deactivate your active deploy key and reset `~/.zapierrc`.
|
||||
|
||||
**Usage**: `zapier-platform logout`
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## logs
|
||||
|
||||
> Print recent logs.
|
||||
|
||||
**Usage**: `zapier-platform logs`
|
||||
|
||||
Logs are created when your integration is run as part of a Zap. They come from explicit calls to `z.console.log()`, usage of `z.request()`, and any runtime errors.
|
||||
|
||||
This won't show logs from running locally with `zapier-platform test`, since those never hit our server.
|
||||
|
||||
**Flags**
|
||||
* `-v, --version` | Filter logs to the specified version.
|
||||
* `-s, --status` | Filter logs to only see errors or successes One of `[any | success | error]`. Defaults to `any`.
|
||||
* `-t, --type` | See logs of the specified type One of `[console | bundle | http]`. Defaults to `console`.
|
||||
* `--detailed` | See extra info, like request/response body and headers.
|
||||
* `-u, --user` | Only show logs for this user. Defaults to your account. Defaults to `me`.
|
||||
* `--limit` | Cap the number of logs returned. Max is 50 (also the default) Defaults to `50`.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
|
||||
## migrate
|
||||
|
||||
> Migrate a percentage of users or a single user from one version of your integration to another.
|
||||
|
||||
**Usage**: `zapier-platform migrate FROMVERSION TOVERSION [PERCENT]`
|
||||
|
||||
Start a migration to move users between different versions of your integration. You may also "revert" by simply swapping the from/to verion strings in the command line arguments (i.e. `zapier-platform migrate 1.0.1 1.0.0`).
|
||||
|
||||
**Only use this command to migrate users between non-breaking versions, use `zapier-platform deprecate` if you have breaking changes!**
|
||||
|
||||
Migration time varies based on the number of affected Zaps. Be patient and check `zapier-platform jobs` to track the status. Or use `zapier-platform history` if you want to see older jobs.
|
||||
|
||||
Since a migration is only for non-breaking changes, users are not emailed about the update/migration. It will be a transparent process for them.
|
||||
|
||||
We recommend migrating a small subset of users first, via the percent argument, then watching error logs of the new version for any sort of odd behavior. When you feel confident there are no bugs, go ahead and migrate everyone. If you see unexpected errors, you can revert.
|
||||
|
||||
You can migrate a specific user's Zaps by using `--user` (i.e. `zapier-platform migrate 1.0.0 1.0.1 --user=user@example.com`). This will migrate Zaps that are private for that user. Zaps that are
|
||||
|
||||
- [shared across the team](https://help.zapier.com/hc/en-us/articles/8496277647629),
|
||||
- [shared app connections](https://help.zapier.com/hc/en-us/articles/8496326497037-Share-app-connections-with-your-team), or
|
||||
- in a [team/company account](https://help.zapier.com/hc/en-us/articles/22330977078157-Collaborate-with-members-of-your-Team-or-Company-account)
|
||||
|
||||
will **not** be migrated.
|
||||
|
||||
Alternatively, you can pass the `--account` flag, (i.e. `zapier-platform migrate 1.0.0 1.0.1 --account=account@example.com`). This will migrate all Zaps owned by the user, Private & Shared, within all accounts for which the specified user is a member.
|
||||
|
||||
**The `--account` flag should be used cautiously as it can break shared Zaps for other users in Team or Enterprise accounts.**
|
||||
|
||||
You cannot pass both `PERCENT` and `--user` or `--account`.
|
||||
|
||||
You cannot pass both `--user` and `--account`.
|
||||
|
||||
**Arguments**
|
||||
* (required) `fromVersion` | The version FROM which to migrate users.
|
||||
* (required) `toVersion` | The version TO which to migrate users.
|
||||
* `percent` | Percentage (between 1 and 100) of users to migrate.
|
||||
|
||||
**Flags**
|
||||
* `--user` | Migrates a user's private Zaps under the user's individual account, excluding organization accounts
|
||||
* `--account` | Migrates a user's private and shared Zaps under the user's individual and organization accounts
|
||||
* `-y, --yes` | Automatically answer "yes" to any prompts. Useful if you want to avoid interactive prompts to run this command in CI.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform migrate 1.0.0 1.0.1`
|
||||
* `zapier-platform migrate 1.0.1 2.0.0 10`
|
||||
* `zapier-platform migrate 2.0.0 2.0.1 --user=user@example.com`
|
||||
* `zapier-platform migrate 2.0.0 2.0.1 --account=account@example.com`
|
||||
|
||||
|
||||
## promote
|
||||
|
||||
> Promote a specific version to public access.
|
||||
|
||||
**Usage**: `zapier-platform promote VERSION`
|
||||
|
||||
Promote an integration version into production (non-private) rotation, which means new users can use this integration version.
|
||||
|
||||
* This **does** mark the version as the official public version - all other versions & users are grandfathered.
|
||||
* This does **NOT** build/upload or deploy a version to Zapier - you should `zapier-platform push` first.
|
||||
* This does **NOT** move old users over to this version - `zapier-platform migrate 1.0.0 1.0.1` does that.
|
||||
* This does **NOT** recommend old users stop using this version - `zapier-platform deprecate 1.0.0 2017-01-01` does that.
|
||||
|
||||
Promotes are an inherently safe operation for all existing users of your integration.
|
||||
|
||||
After a promotion, go to your developer platform to [close issues that were resolved](https://platform.zapier.com/manage/user-feedback#3-close-resolved-issues) in the updated version.
|
||||
|
||||
If your integration is private and passes our integration checks, this will give you a URL to a form where you can fill in additional information for your integration to go public. After reviewing, the Zapier team will approve to make it public if there are no issues or decline with feedback.
|
||||
|
||||
Check `zapier-platform jobs` to track the status of the promotion. Or use `zapier-platform history` if you want to see older jobs.
|
||||
|
||||
**Arguments**
|
||||
* (required) `version` | The version you want to promote.
|
||||
|
||||
**Flags**
|
||||
* `-y, --yes` | Automatically answer "yes" to any prompts. Useful if you want to avoid interactive prompts to run this command in CI.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform promote 1.0.0`
|
||||
|
||||
|
||||
## pull
|
||||
|
||||
> Retrieve and update your local integration files with the promoted version (or latest version if not public).
|
||||
|
||||
**Usage**: `zapier-platform pull`
|
||||
|
||||
This command updates your local integration files with the promoted version (or latest version if not public). You will be prompted with a confirmation dialog before continuing if there any destructive file changes.
|
||||
|
||||
Zapier may release new versions of your integration with bug fixes or new features. In the event this occurs, you will be unable to do the following until your local files are updated by running `zapier-platform pull`:
|
||||
|
||||
* push to the promoted version
|
||||
* promote a new version
|
||||
* migrate users from one version to another
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## push
|
||||
|
||||
> Build and upload the current integration.
|
||||
|
||||
**Usage**: `zapier-platform push`
|
||||
|
||||
This command is the same as running `zapier-platform build` and `zapier-platform upload` in sequence. See those for more info.
|
||||
|
||||
**Flags**
|
||||
* `--disable-dependency-detection` | Disable "smart" file inclusion. By default, Zapier only includes files that are required by your entry point (`index.js` by default). If you (or your dependencies) require files dynamically (such as with `require(someVar)`), then you may see "Cannot find module" errors. Disabling this may make your `build.zip` too large. If that's the case, try using the `includeInBuild` option in your `.zapierapprc`. See the docs about `includeInBuild` for more info.
|
||||
* `--skip-dep-install` | [alias: --skip-npm-install]
|
||||
Skips installing a fresh copy of dependencies for shorter build time. Helpful for using yarn, pnpm, or local copies of dependencies.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `--snapshot` | Pass in a label to create a snapshot version of this integration for development and testing purposes. The version will be created as: 0.0.0-MY-LABEL
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform push`
|
||||
* `zapier-platform push --snapshot MY-LABEL`
|
||||
|
||||
|
||||
## register
|
||||
|
||||
> Register a new integration in your account, or update the existing one if a `.zapierapprc` file is found.
|
||||
|
||||
**Usage**: `zapier-platform register [TITLE]`
|
||||
|
||||
This command creates a new integration and links it in the `./.zapierapprc` file. If `.zapierapprc` already exists, it will ask you if you want to update the currently-linked integration, as opposed to creating a new one.
|
||||
|
||||
After registering a new integration, you can run `zapier-platform push` to build and upload your integration for use in the Zapier editor. This will change `.zapierapprc`, which identifies this directory as holding code for a specific integration.
|
||||
|
||||
**Arguments**
|
||||
* `title` | Your integration's public title. Asked interactively if not present.
|
||||
|
||||
**Flags**
|
||||
* `-D, --desc` | A sentence describing your app in 140 characters or less, e.g. "Trello is a team collaboration tool to organize tasks and keep projects on track."
|
||||
* `-u, --url` | The homepage URL of your app, e.g., https://example.com.
|
||||
* `-a, --audience` | Are you building a public or private integration?
|
||||
* `-r, --role` | What is your relationship with the app you're integrating with Zapier?
|
||||
* `-c, --category` | How would you categorize your app? Choose the most appropriate option for your app's core features.
|
||||
* `-s, --subscribe` | Get tips and recommendations about this integration along with our monthly newsletter that details the performance of your integration and the latest Zapier news.
|
||||
* `-y, --yes` | Assume yes for all yes/no prompts. This flag will also update an existing integration (as opposed to registering a new one) if a .zapierapprc file is found.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform register`
|
||||
* `zapier-platform register "My Cool Integration"`
|
||||
* `zapier-platform register "My Cool Integration" --desc "My Cool Integration helps you integrate your apps with the apps that you need." --no-subscribe`
|
||||
* `zapier-platform register "My Cool Integration" --url "https://www.zapier.com" --audience private --role employee --category marketing-automation`
|
||||
* `zapier-platform register --subscribe`
|
||||
|
||||
|
||||
## scaffold
|
||||
|
||||
> Add a starting trigger, create, search, or resource to your integration.
|
||||
|
||||
**Usage**: `zapier-platform scaffold ACTIONTYPE NOUN`
|
||||
|
||||
The first argument should be one of `trigger|search|create|resource` followed by the noun that this will act on (something like "contact" or "deal").
|
||||
|
||||
The scaffold command does two general things:
|
||||
|
||||
* Creates a new file (such as `triggers/contact.js`)
|
||||
* Imports and registers it inside your `index.js`
|
||||
|
||||
You can mix and match several options to customize the created scaffold for your project.
|
||||
|
||||
**Arguments**
|
||||
* (required) `actionType` | undefined
|
||||
* (required) `noun` | undefined
|
||||
|
||||
**Flags**
|
||||
* `-d, --dest` | Specify the new file's directory. Use this flag when you want to create a different folder structure such as `src/triggers` instead of the default `triggers`. Defaults to `[triggers|searches|creates]/{noun}`.
|
||||
* `--test-dest` | Specify the new test file's directory. Use this flag when you want to create a different folder structure such as `src/triggers` instead of the default `triggers`. Defaults to `test/[triggers|searches|creates]/{noun}`.
|
||||
* `-e, --entry` | Supply the path to your integration's entry point (`index.js` or `src/index.ts`). This will try to automatically detect the correct file if not provided.
|
||||
* `-f, --force` | Should we overwrite an existing trigger/search/create file?
|
||||
* `--no-help` | When scaffolding, should we skip adding helpful intro comments? Useful if this isn't your first rodeo.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform scaffold trigger contact`
|
||||
* `zapier-platform scaffold search contact --dest=my_src/searches`
|
||||
* `zapier-platform scaffold create contact --entry=src/index.js`
|
||||
* `zapier-platform scaffold resource contact --force`
|
||||
|
||||
|
||||
## team:add
|
||||
|
||||
> Add a team member to your integration.
|
||||
|
||||
**Usage**: `zapier-platform team:add EMAIL ROLE [MESSAGE]`
|
||||
|
||||
These users come in three levels:
|
||||
|
||||
* `admin`, who can edit everything about the integration
|
||||
* `collaborator`, who has read-only access for the app, and will receive periodic email updates. These updates include quarterly health scores and more.
|
||||
* `subscriber`, who can't directly access the app, but will receive periodic email updates. These updates include quarterly health scores and more.
|
||||
|
||||
Team members can be freely added and removed.
|
||||
|
||||
**Arguments**
|
||||
* (required) `email` | The user to be invited. If they don't have a Zapier account, they'll be prompted to create one.
|
||||
* (required) `role` | The level the invited team member should be at. Admins can edit everything and get email updates. Collaborators have read-access to the app and get email updates. Subscribers only get email updates.
|
||||
* `message` | A message sent in the email to your team member, if you need to provide context. Wrap the message in quotes to ensure spaces get saved.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform team:add bruce@wayne.com admin`
|
||||
* `zapier-platform team:add robin@wayne.com collaborator "Hey Robin, check out this app."`
|
||||
* `zapier-platform team:add alfred@wayne.com subscriber "Hey Alfred, check out this app."`
|
||||
|
||||
**Aliases**
|
||||
* `team:invite`
|
||||
|
||||
|
||||
## team:get
|
||||
|
||||
> Get team members involved with your integration.
|
||||
|
||||
**Usage**: `zapier-platform team:get`
|
||||
|
||||
These users come in three levels:
|
||||
|
||||
* `admin`, who can edit everything about the integration
|
||||
* `collaborator`, who has read-only access for the app, and will receive periodic email updates. These updates include quarterly health scores and more.
|
||||
* `subscriber`, who can't directly access the app, but will receive periodic email updates. These updates include quarterly health scores and more.
|
||||
|
||||
Use the `zapier-platform team:add` and `zapier-platform team:remove` commands to modify your team.
|
||||
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Aliases**
|
||||
* `team:list`
|
||||
|
||||
|
||||
## team:remove
|
||||
|
||||
> Remove a team member from all versions of your integration.
|
||||
|
||||
**Usage**: `zapier-platform team:remove`
|
||||
|
||||
Admins will immediately lose write access to the integration.
|
||||
Collaborators will immediately lose read access to the integration.
|
||||
Subscribers won't receive future email updates.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Aliases**
|
||||
* `team:delete`
|
||||
|
||||
|
||||
## test
|
||||
|
||||
> Test your integration via the "test" script in your "package.json".
|
||||
|
||||
**Usage**: `zapier-platform test`
|
||||
|
||||
This command is a wrapper around `npm test` that also validates the structure of your integration and sets up extra environment variables.
|
||||
|
||||
You can pass any args/flags after a `--`; they will get forwarded onto your test script.
|
||||
|
||||
**Flags**
|
||||
* `--skip-validate` | Forgo running `zapier-platform validate` before tests are run. This will speed up tests if you're modifying functionality of an existing integration rather than adding new actions.
|
||||
* `--yarn` | Use `yarn` instead of `npm`. This happens automatically if there's a `yarn.lock` file, but you can manually force `yarn` if you run tests from a sub-directory.
|
||||
* `--pnpm` | Use `pnpm` instead of `npm`. This happens automatically if there's a `pnpm-lock.yaml` file, but you can manually force `pnpm` if you run tests from a sub-directory.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform test`
|
||||
* `zapier-platform test --skip-validate -- -t 30000 --grep api`
|
||||
* `zapier-platform test -- -fo --testNamePattern "auth pass"`
|
||||
|
||||
|
||||
## upload
|
||||
|
||||
> Upload the latest build of your integration to Zapier.
|
||||
|
||||
**Usage**: `zapier-platform upload`
|
||||
|
||||
This command sends both build/build.zip and build/source.zip to Zapier for use.
|
||||
|
||||
Typically we recommend using `zapier-platform push`, which does a build and upload, rather than `upload` by itself.
|
||||
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
|
||||
## users:add
|
||||
|
||||
> Add a user to some or all versions of your integration.
|
||||
|
||||
**Usage**: `zapier-platform users:add EMAIL [VERSION]`
|
||||
|
||||
When this command is run, we'll send an email to the user inviting them to try your integration. You can track the status of that invite using the `zapier-platform users:get` command.
|
||||
|
||||
Invited users will be able to see your integration's name, logo, and description. They'll also be able to create Zaps using any available triggers and actions.
|
||||
|
||||
**Arguments**
|
||||
* (required) `email` | The user to be invited. If they don't have a Zapier account, they'll be prompted to create one.
|
||||
* `version` | A version string (like 1.2.3). Optional, used only if you want to invite a user to a specific version instead of all versions.
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Skip confirmation. Useful for running programatically.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform users:add bruce@wayne.com`
|
||||
* `zapier-platform users:add alfred@wayne.com 1.2.3`
|
||||
|
||||
**Aliases**
|
||||
* `users:invite`
|
||||
|
||||
|
||||
## users:get
|
||||
|
||||
> Get a list of users who have been invited to your integration.
|
||||
|
||||
**Usage**: `zapier-platform users:get`
|
||||
|
||||
Note that this list of users is NOT a comprehensive list of everyone who is using your integration. It only includes users who were invited directly by email (using the `zapier-platform users:add` command or the web UI). Users who joined by clicking links generated using the `zapier-platform user:links` command won't show up here.
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Aliases**
|
||||
* `users:list`
|
||||
|
||||
|
||||
## users:links
|
||||
|
||||
> Get a list of links that are used to invite users to your integration.
|
||||
|
||||
**Usage**: `zapier-platform users:links`
|
||||
|
||||
**Flags**
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
|
||||
## users:remove
|
||||
|
||||
> Remove a user from all versions of your integration.
|
||||
|
||||
**Usage**: `zapier-platform users:remove EMAIL`
|
||||
|
||||
When this command is run, their Zaps will immediately turn off. They won't be able to use your app again until they're re-invited or it has gone public. In practice, this command isn't run often as it's very disruptive to users.
|
||||
|
||||
**Arguments**
|
||||
* (required) `email` | The user to be removed.
|
||||
|
||||
**Flags**
|
||||
* `-f, --force` | Skips confirmation. Useful for running programatically.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
|
||||
**Aliases**
|
||||
* `users:delete`
|
||||
|
||||
|
||||
## validate
|
||||
|
||||
> Validate your integration.
|
||||
|
||||
**Usage**: `zapier-platform validate`
|
||||
|
||||
Run the standard validation routine powered by json-schema that checks your integration for any structural errors. This is the same routine that runs during `zapier-platform build`, `zapier-platform upload`, `zapier-platform push` or even as a test in `zapier-platform test`.
|
||||
|
||||
**Flags**
|
||||
* `--without-style` | Forgo pinging the Zapier server to run further checks.
|
||||
* `--skip-build` | Skip running the _zapier-build script before validation.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
|
||||
**Examples**
|
||||
* `zapier-platform validate`
|
||||
* `zapier-platform validate --without-style`
|
||||
* `zapier-platform validate --skip-build`
|
||||
* `zapier-platform validate --format json`
|
||||
|
||||
|
||||
## versions
|
||||
|
||||
> List the versions of your integration available for use in Zapier automations.
|
||||
|
||||
**Usage**: `zapier-platform versions`
|
||||
|
||||
**Flags**
|
||||
* `-a, --all` | List all versions, including deprecated versions.
|
||||
* `-d, --debug` | Show extra debugging output.
|
||||
* `-f, --format` | Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq. One of `[plain | json | raw | row | table]`. Defaults to `table`.
|
||||
143
vendor/zapier-platform/packages/cli/package.json
vendored
Normal file
143
vendor/zapier-platform/packages/cli/package.json
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
{
|
||||
"name": "zapier-platform-cli",
|
||||
"version": "19.1.0",
|
||||
"description": "The CLI for managing integrations in Zapier Developer Platform.",
|
||||
"repository": "zapier/zapier-platform",
|
||||
"homepage": "https://platform.zapier.com/",
|
||||
"author": "Zapier Engineering <contact@zapier.com>",
|
||||
"license": "SEE LICENSE IN LICENSE",
|
||||
"main": "./src/index.js",
|
||||
"files": [
|
||||
"/src/*.js",
|
||||
"/src/commands/",
|
||||
"/src/generators/",
|
||||
"/src/oclif/",
|
||||
"/src/bin/",
|
||||
"/src/utils/",
|
||||
"/scaffold/",
|
||||
"/oclif.manifest.json"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18.20"
|
||||
},
|
||||
"scripts": {
|
||||
"build-docs": "ZAPIER_BASE_ENDPOINT='' node scripts/docs.js",
|
||||
"preversion": "git pull && pnpm validate",
|
||||
"prepack": "oclif manifest",
|
||||
"postpack": "rimraf oclif.manifest.json",
|
||||
"precommit": "pnpm build-docs && git add docs",
|
||||
"version": "pnpm build-docs && git add docs/*",
|
||||
"postversion": "git push && git push --tags",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"test": "cross-env NODE_ENV=test mocha -t 200s --recursive src/tests --exit",
|
||||
"test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 200s --recursive src/tests --exit",
|
||||
"smoke-test": "cross-env NODE_ENV=test mocha -t 6m --recursive src/smoke-tests --exit",
|
||||
"smoke-test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 6m --recursive src/smoke-tests --exit",
|
||||
"validate-templates": "./scripts/validate-app-templates.js",
|
||||
"set-template-versions": "./scripts/set-app-template-versions.js",
|
||||
"validate": "pnpm test && pnpm smoke-test && pnpm lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oclif/core": "4.5.2",
|
||||
"@oclif/plugin-autocomplete": "3.2.34",
|
||||
"@oclif/plugin-help": "6.2.32",
|
||||
"@oclif/plugin-not-found": "3.2.62",
|
||||
"@oclif/plugin-version": "2.2.32",
|
||||
"adm-zip": "0.5.16",
|
||||
"archiver": "7.0.1",
|
||||
"chrono-node": "2.8.3",
|
||||
"cli-table3": "0.6.5",
|
||||
"colors": "1.4.0",
|
||||
"debug": "4.4.1",
|
||||
"decompress-unzip": "4.0.1",
|
||||
"dotenv": "17.2.1",
|
||||
"esbuild": "0.25.8",
|
||||
"fs-extra": "11.3.1",
|
||||
"gulp-filter": "7.0.0",
|
||||
"gulp-prettier": "5.0.0",
|
||||
"ignore": "7.0.5",
|
||||
"inquirer": "8.2.5",
|
||||
"jscodeshift": "^17.3.0",
|
||||
"lodash": "4.18.1",
|
||||
"luxon": "3.7.1",
|
||||
"marked": "15.0.12",
|
||||
"marked-terminal": "7.3.0",
|
||||
"open": "10.2.0",
|
||||
"ora": "5.4.0",
|
||||
"parse-gitignore": "0.5.1",
|
||||
"prettier": "3.6.2",
|
||||
"read": "4.1.0",
|
||||
"semver": "7.7.2",
|
||||
"string-length": "4.0.2",
|
||||
"through2": "4.0.2",
|
||||
"tmp": "0.2.5",
|
||||
"traverse": "0.6.11",
|
||||
"update-notifier": "7.3.1",
|
||||
"yeoman-environment": "4.4.3",
|
||||
"yeoman-generator": "7.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oclif/test": "^4.1.13",
|
||||
"@types/jscodeshift": "^0.12.0",
|
||||
"@types/mocha": "^10.0.9",
|
||||
"chai": "^4.3.7",
|
||||
"mock-fs": "^5.5.0",
|
||||
"nock": "^14.0.7",
|
||||
"oclif": "^4.22.5",
|
||||
"rimraf": "^5.0.10",
|
||||
"typescript": "^5.8.3",
|
||||
"yamljs": "0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"zapier-platform": "./src/bin/run"
|
||||
},
|
||||
"oclif": {
|
||||
"commands": {
|
||||
"strategy": "explicit",
|
||||
"target": "./src/oclif/oCommands",
|
||||
"identifier": "COMMANDS"
|
||||
},
|
||||
"additionalHelpFlags": [
|
||||
"-h"
|
||||
],
|
||||
"additionalVersionFlags": [
|
||||
"-v"
|
||||
],
|
||||
"bin": "zapier-platform",
|
||||
"dirname": "zapier-platform",
|
||||
"plugins": [
|
||||
"@oclif/plugin-autocomplete",
|
||||
"@oclif/plugin-help",
|
||||
"@oclif/plugin-not-found",
|
||||
"@oclif/plugin-version"
|
||||
],
|
||||
"hooks": {
|
||||
"init": [
|
||||
"./src/oclif/hooks/checkValidNodeVersion",
|
||||
"./src/oclif/hooks/deprecated",
|
||||
"./src/oclif/hooks/getAppRegistrationFieldChoices",
|
||||
"./src/oclif/hooks/renderMarkdownHelp",
|
||||
"./src/oclif/hooks/updateNotifier",
|
||||
"./src/oclif/hooks/versionInfo"
|
||||
]
|
||||
},
|
||||
"topics": {
|
||||
"cache": {
|
||||
"description": "Interact with your integration's cache data."
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete your entire integration or a specific integration version."
|
||||
},
|
||||
"env": {
|
||||
"description": "Interact with your integration's environment."
|
||||
},
|
||||
"team": {
|
||||
"description": "Add, remove, or get team members of your integration."
|
||||
},
|
||||
"users": {
|
||||
"description": "Add, remove, or get invited users of your integration."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
vendor/zapier-platform/packages/cli/scaffold/create.template.js
vendored
Normal file
62
vendor/zapier-platform/packages/cli/scaffold/create.template.js
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// create a particular <%= LOWER_NOUN %> by name
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
// if `body` is an object, it'll automatically get run through JSON.stringify
|
||||
// if you don't want to send JSON, pass a string in your chosen format here instead
|
||||
body: {
|
||||
name: bundle.inputData.name
|
||||
}
|
||||
});
|
||||
// this should return a single object
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#createschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'Create <%= NOUN %>',
|
||||
description: 'Creates a new <%= LOWER_NOUN %>, probably with input from previous steps.'
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional.',
|
||||
'// End-users will map data into these fields. In general, they should have any fields that the API can accept. Be sure to accurately mark which fields are required!'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: [
|
||||
{key: 'name', required: true},
|
||||
{key: 'fave_meal', label: 'Favorite Meal', required: false}
|
||||
],
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test'
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
]
|
||||
}
|
||||
};
|
||||
71
vendor/zapier-platform/packages/cli/scaffold/create.template.ts
vendored
Normal file
71
vendor/zapier-platform/packages/cli/scaffold/create.template.ts
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
defineInputFields,
|
||||
defineCreate,
|
||||
type CreatePerform,
|
||||
type InferInputData,
|
||||
} from 'zapier-platform-core';
|
||||
|
||||
const inputFields = defineInputFields([
|
||||
{ key: 'name', required: true },
|
||||
{ key: 'fave_meal', label: 'Favorite Meal', required: false },
|
||||
]);
|
||||
|
||||
// create a particular <%= LOWER_NOUN %> by name
|
||||
const perform = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
// if `body` is an object, it'll automatically get run through JSON.stringify
|
||||
// if you don't want to send JSON, pass a string in your chosen format here instead
|
||||
body: {
|
||||
name: bundle.inputData.name,
|
||||
},
|
||||
});
|
||||
// this should return a single object
|
||||
return response.data;
|
||||
}) satisfies CreatePerform<InferInputData<typeof inputFields>>;
|
||||
|
||||
export default defineCreate({
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#createschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'Create <%= NOUN %>',
|
||||
description: 'Creates a new <%= LOWER_NOUN %>, probably with input from previous steps.'
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional.',
|
||||
'// End-users will map data into these fields. In general, they should have any fields that the API can accept. Be sure to accurately mark which fields are required!'
|
||||
].join('\n ') : '' %>
|
||||
inputFields,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
],
|
||||
},
|
||||
});
|
||||
121
vendor/zapier-platform/packages/cli/scaffold/resource.template.js
vendored
Normal file
121
vendor/zapier-platform/packages/cli/scaffold/resource.template.js
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// get a list of <%= LOWER_NOUN %>s
|
||||
const performList = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
order_by: 'id desc'
|
||||
}
|
||||
});
|
||||
return response.data
|
||||
};
|
||||
|
||||
// find a particular <%= LOWER_NOUN %> by name (or other search criteria)
|
||||
const performSearch = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
name: bundle.inputData.name
|
||||
}
|
||||
});
|
||||
return response.data
|
||||
};
|
||||
|
||||
// creates a new <%= LOWER_NOUN %>
|
||||
const performCreate = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
body: {
|
||||
name: bundle.inputData.name // json by default
|
||||
}
|
||||
});
|
||||
return response.data
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If `get` is defined, it will be called after a `search` or `create`'
|
||||
].join('\n ') : '' %>
|
||||
// useful if your `searches` and `creates` return sparse objects
|
||||
// get: {
|
||||
// display: {
|
||||
// label: 'Get <%= NOUN %>',
|
||||
// description: 'Gets a <%= LOWER_NOUN %>.'
|
||||
// },
|
||||
// operation: {
|
||||
// inputFields: [
|
||||
// {key: 'id', required: true}
|
||||
// ],
|
||||
// perform: defineMe
|
||||
// }
|
||||
// },
|
||||
|
||||
list: {
|
||||
display: {
|
||||
label: 'New <%= NOUN %>',
|
||||
description: 'Lists the <%= LOWER_NOUN %>s.'
|
||||
},
|
||||
operation: {
|
||||
perform: performList,
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional on triggers, but required on searches and creates.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: []
|
||||
}
|
||||
},
|
||||
|
||||
search: {
|
||||
display: {
|
||||
label: 'Find <%= NOUN %>',
|
||||
description: 'Finds a <%= LOWER_NOUN %> give.'
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{key: 'name', required: true}
|
||||
],
|
||||
perform: performSearch
|
||||
},
|
||||
},
|
||||
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create <%= NOUN %>',
|
||||
description: 'Creates a new <%= LOWER_NOUN %>.'
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{key: 'name', required: true}
|
||||
],
|
||||
perform: performCreate
|
||||
},
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.',
|
||||
'// In this resource, the sample is reused across all methods'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test'
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields',
|
||||
'// In this resource, these output fields are reused across all resources'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
{key: 'id', label: 'ID'},
|
||||
{key: 'name', label: 'Name'}
|
||||
]
|
||||
};
|
||||
140
vendor/zapier-platform/packages/cli/scaffold/resource.template.ts
vendored
Normal file
140
vendor/zapier-platform/packages/cli/scaffold/resource.template.ts
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import {
|
||||
defineInputFields,
|
||||
type CreatePerform,
|
||||
type InferInputData,
|
||||
type PollingTriggerPerform,
|
||||
type Resource,
|
||||
type SearchPerform,
|
||||
} from 'zapier-platform-core';
|
||||
|
||||
// get a list of <%= LOWER_NOUN %>s
|
||||
const performList = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
order_by: 'id desc',
|
||||
tag: bundle.inputData.tagName,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
}) satisfies PollingTriggerPerform;
|
||||
|
||||
const searchInputFields = defineInputFields([
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
helpText: 'Find the <%= NOUN %> with this name.',
|
||||
},
|
||||
]);
|
||||
|
||||
// find a particular <%= LOWER_NOUN %> by name (or other search criteria)
|
||||
const performSearch = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
name: bundle.inputData.name,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
}) satisfies SearchPerform<InferInputData<typeof searchInputFields>>;
|
||||
|
||||
const createInputFields = defineInputFields([
|
||||
{ key: 'name', required: true },
|
||||
{ key: 'fave_meal', label: 'Favorite Meal', required: false },
|
||||
]);
|
||||
|
||||
// creates a new <%= LOWER_NOUN %>
|
||||
const performCreate = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
body: {
|
||||
name: bundle.inputData.name, // json by default
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
}) satisfies CreatePerform<InferInputData<typeof createInputFields>>;
|
||||
|
||||
export default {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#resourceschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If `get` is defined, it will be called after a `search` or `create`'
|
||||
].join('\n ') : '' %>
|
||||
// useful if your `searches` and `creates` return sparse objects
|
||||
// get: {
|
||||
// display: {
|
||||
// label: 'Get <%= NOUN %>',
|
||||
// description: 'Gets a <%= LOWER_NOUN %>.'
|
||||
// },
|
||||
// operation: {
|
||||
// inputFields: [
|
||||
// {key: 'id', required: true}
|
||||
// ],
|
||||
// perform: defineMe
|
||||
// }
|
||||
// },
|
||||
|
||||
list: {
|
||||
display: {
|
||||
label: 'New <%= NOUN %>',
|
||||
description: 'Lists the <%= LOWER_NOUN %>s.',
|
||||
},
|
||||
operation: {
|
||||
perform: performList,
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional on triggers, but required on searches and creates.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: [],
|
||||
},
|
||||
},
|
||||
|
||||
search: {
|
||||
display: {
|
||||
label: 'Find <%= NOUN %>',
|
||||
description: 'Finds a <%= LOWER_NOUN %> give.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: searchInputFields,
|
||||
perform: performSearch,
|
||||
},
|
||||
},
|
||||
|
||||
create: {
|
||||
display: {
|
||||
label: 'Create <%= NOUN %>',
|
||||
description: 'Creates a new <%= LOWER_NOUN %>.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: createInputFields,
|
||||
perform: performCreate,
|
||||
},
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.',
|
||||
'// In this resource, the sample is reused across all methods'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields',
|
||||
'// In this resource, these output fields are reused across all resources'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'Name' },
|
||||
],
|
||||
} satisfies Resource;
|
||||
57
vendor/zapier-platform/packages/cli/scaffold/search.template.js
vendored
Normal file
57
vendor/zapier-platform/packages/cli/scaffold/search.template.js
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// find a particular <%= LOWER_NOUN %> by name
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
name: bundle.inputData.name
|
||||
}
|
||||
});
|
||||
// this should return an array of objects (but only the first will be used)
|
||||
return response.data
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#searchschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'Find <%= NOUN %>',
|
||||
description: 'Finds a <%= LOWER_NOUN %> based on name.'
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. Searches need at least one `inputField`.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: [
|
||||
{key: 'name', required: true, helpText: 'Find the <%= NOUN %> with this name.'}
|
||||
],
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test'
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
]
|
||||
}
|
||||
};
|
||||
70
vendor/zapier-platform/packages/cli/scaffold/search.template.ts
vendored
Normal file
70
vendor/zapier-platform/packages/cli/scaffold/search.template.ts
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
defineInputFields,
|
||||
defineSearch,
|
||||
type SearchPerform,
|
||||
type InferInputData,
|
||||
} from 'zapier-platform-core';
|
||||
|
||||
const inputFields = defineInputFields([
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
helpText: 'Find the <%= NOUN %> with this name.',
|
||||
},
|
||||
]);
|
||||
|
||||
// find a particular <%= LOWER_NOUN %> by name
|
||||
const perform = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
name: bundle.inputData.name,
|
||||
},
|
||||
});
|
||||
// this should return an array of objects (but only the first will be used)
|
||||
return response.data;
|
||||
}) satisfies SearchPerform<InferInputData<typeof inputFields>>;
|
||||
|
||||
export default defineSearch({
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#searchschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'Find <%= NOUN %>',
|
||||
description: 'Finds a <%= LOWER_NOUN %> based on name.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. Searches need at least one `inputField`.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
],
|
||||
},
|
||||
});
|
||||
17
vendor/zapier-platform/packages/cli/scaffold/test.template.js
vendored
Normal file
17
vendor/zapier-platform/packages/cli/scaffold/test.template.js
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('<%= ACTION_PLURAL %>.<%= KEY %>', () => {
|
||||
it('should run', async () => {
|
||||
const bundle = { inputData: {} };
|
||||
|
||||
const results = await appTester(App.<%= ACTION_PLURAL %>['<%= KEY %>'].<%= MAYBE_RESOURCE %>operation.perform, bundle);
|
||||
expect(results).toBeDefined();
|
||||
// TODO: add more assertions
|
||||
});
|
||||
});
|
||||
18
vendor/zapier-platform/packages/cli/scaffold/test.template.ts
vendored
Normal file
18
vendor/zapier-platform/packages/cli/scaffold/test.template.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../../index';
|
||||
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('<%= ACTION_PLURAL %>.<%= KEY %>', () => {
|
||||
it('should run', async () => {
|
||||
const bundle = { inputData: {} };
|
||||
|
||||
const results = await appTester(App.<%= ACTION_PLURAL %>['<%= KEY %>'].<%= MAYBE_RESOURCE %>operation.perform, bundle);
|
||||
expect(results).toBeDefined();
|
||||
// TODO: add more assertions
|
||||
});
|
||||
});
|
||||
55
vendor/zapier-platform/packages/cli/scaffold/trigger.template.js
vendored
Normal file
55
vendor/zapier-platform/packages/cli/scaffold/trigger.template.js
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// triggers on a new <%= LOWER_NOUN %> with a certain tag
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
tag: bundle.inputData.tagName
|
||||
}
|
||||
});
|
||||
// this should return an array of objects
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema
|
||||
key: '<%= KEY %>',
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'New <%= NOUN %>',
|
||||
description: 'Triggers when a new <%= LOWER_NOUN %> is created.'
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: [],
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test'
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
]
|
||||
}
|
||||
};
|
||||
61
vendor/zapier-platform/packages/cli/scaffold/trigger.template.ts
vendored
Normal file
61
vendor/zapier-platform/packages/cli/scaffold/trigger.template.ts
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import {
|
||||
defineTrigger,
|
||||
type PollingTriggerPerform,
|
||||
} from 'zapier-platform-core';
|
||||
|
||||
// triggers on a new <%= LOWER_NOUN %> with a certain tag
|
||||
const perform = (async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://jsonplaceholder.typicode.com/posts',
|
||||
params: {
|
||||
tag: bundle.inputData.tagName,
|
||||
},
|
||||
});
|
||||
// this should return an array of objects
|
||||
return response.data;
|
||||
}) satisfies PollingTriggerPerform;
|
||||
|
||||
export default defineTrigger({
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/main/packages/schema/docs/build/schema.md#triggerschema
|
||||
key: '<%= KEY %>' as const,
|
||||
noun: '<%= NOUN %>',
|
||||
|
||||
display: {
|
||||
label: 'New <%= NOUN %>',
|
||||
description: 'Triggers when a new <%= LOWER_NOUN %> is created.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
type: 'polling',
|
||||
perform,
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// `inputFields` defines the fields a user could provide',
|
||||
'// Zapier will pass them in as `bundle.inputData` later. They\'re optional.'
|
||||
].join('\n ') : '' %>
|
||||
inputFields: [],
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example',
|
||||
'// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of',
|
||||
'// returned records, and have obvious placeholder values that we can show to any user.'
|
||||
].join('\n ') : '' %>
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
|
||||
<%= INCLUDE_INTRO_COMMENTS ? [
|
||||
'// If fields are custom to each user (like spreadsheet columns), `outputFields` can create human labels',
|
||||
'// For a more complete example of using dynamic fields see',
|
||||
'// https://github.com/zapier/zapier-platform/tree/main/packages/cli#customdynamic-fields',
|
||||
'// Alternatively, a static field definition can be provided, to specify labels for the fields'
|
||||
].join('\n ') : '' %>
|
||||
outputFields: [
|
||||
// these are placeholders to match the example `perform` above
|
||||
// {key: 'id', label: 'Person ID'},
|
||||
// {key: 'name', label: 'Person Name'}
|
||||
],
|
||||
},
|
||||
});
|
||||
49
vendor/zapier-platform/packages/cli/scripts/docs.js
vendored
Normal file
49
vendor/zapier-platform/packages/cli/scripts/docs.js
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
|
||||
const { COMMANDS } = require('../src/oclif/oCommands');
|
||||
|
||||
// Takes all the cmd.docs and puts them into a big md file.
|
||||
const generateCliMarkdown = () => {
|
||||
return (
|
||||
Object.keys(COMMANDS)
|
||||
.sort()
|
||||
// topics (such as `env` are listed as "true" in the commands so aliases play nice, but they don't need to be in docs
|
||||
.filter((name) => COMMANDS[name] !== true)
|
||||
.filter((name) => !COMMANDS[name].hide)
|
||||
.map((name) => {
|
||||
return COMMANDS[name].markdownHelp(name);
|
||||
})
|
||||
.join('\n\n\n')
|
||||
);
|
||||
};
|
||||
|
||||
// Writes out a big markdown file for the cli.
|
||||
const writeCliDocs = ({ markdownPath } = {}) => {
|
||||
const docs = generateCliMarkdown();
|
||||
|
||||
fs.writeFileSync(
|
||||
markdownPath,
|
||||
`\
|
||||
# Zapier CLI Command Reference
|
||||
|
||||
These are the generated docs for all Zapier platform CLI commands.
|
||||
|
||||
You can install the CLI with \`npm install -g zapier-platform-cli\`.
|
||||
|
||||
${'```'}bash
|
||||
$ npm install -g zapier-platform-cli
|
||||
${'```'}
|
||||
|
||||
# Commands
|
||||
|
||||
${docs}
|
||||
`,
|
||||
);
|
||||
};
|
||||
|
||||
writeCliDocs({
|
||||
markdownPath: './docs/cli.md',
|
||||
});
|
||||
160
vendor/zapier-platform/packages/cli/scripts/set-app-template-versions.js
vendored
Executable file
160
vendor/zapier-platform/packages/cli/scripts/set-app-template-versions.js
vendored
Executable file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const _ = require('lodash');
|
||||
const path = require('path');
|
||||
const tmp = require('tmp');
|
||||
const { promisifyAll } = require('../src/utils/promisify');
|
||||
const appTemplates = require('../src/app-templates');
|
||||
const versionStore = require('../src/version-store');
|
||||
|
||||
const fse = require('fs-extra');
|
||||
const semver = require('semver');
|
||||
const yaml = require('yamljs');
|
||||
const childProcess = promisifyAll(require('child_process'));
|
||||
|
||||
const CLONE_URL_PREFIX = 'git@github.com:zapier/zapier-platform-example-app';
|
||||
const PACKAGES_NAMES = 'node, npm, and zapier-platform-core';
|
||||
let packagesVersions;
|
||||
|
||||
const newCoreVersion = process.argv[2];
|
||||
if (!newCoreVersion) {
|
||||
console.error('Usage: npm run set-template-version [NEW_CORE_VERSION]');
|
||||
/* eslint no-process-exit: 0 */
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const newVersions = versionStore[semver.parse(newCoreVersion).major];
|
||||
newVersions.coreVersion = newCoreVersion;
|
||||
|
||||
const exec = (cmd, cwd) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
childProcess.exec(cmd, { cwd }, (err) => {
|
||||
if (err) {
|
||||
console.error('error:', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const hasCurrentVersions = (newVersion, travisNodeVersion, packageJson) => {
|
||||
return (
|
||||
packageJson.dependencies['zapier-platform-core'] ===
|
||||
newVersion.coreVersion &&
|
||||
packageJson.engines.node === newVersion.nodeVersion &&
|
||||
packageJson.engines.npm === newVersion.npmVersion &&
|
||||
travisNodeVersion === newVersion.nodeVersion
|
||||
);
|
||||
};
|
||||
|
||||
const setVersion = (template, rootTmpDir) => {
|
||||
const repoName = `zapier-platform-example-app-${template}`;
|
||||
const repoDir = path.resolve(rootTmpDir, repoName);
|
||||
const cloneUrl = `${CLONE_URL_PREFIX}-${template}`;
|
||||
var cmd = `git clone ${cloneUrl}`;
|
||||
packagesVersions = `${newVersions.nodeVersion}, ${newVersions.npmVersion}, and ${newVersions.coreVersion}`;
|
||||
|
||||
console.log(
|
||||
`Setting versions of ${PACKAGES_NAMES} to ${packagesVersions} respectively in ${template} app template.`,
|
||||
);
|
||||
console.log(`cloning ${cloneUrl}\n`);
|
||||
|
||||
return exec(cmd, rootTmpDir)
|
||||
.then(() => {
|
||||
const packageJsonFile = path.resolve(
|
||||
rootTmpDir,
|
||||
`${repoName}/package.json`,
|
||||
);
|
||||
const packageJson = require(packageJsonFile);
|
||||
|
||||
const travisYamlFile = path.resolve(
|
||||
rootTmpDir,
|
||||
`${repoName}/.travis.yml`,
|
||||
);
|
||||
const travisYaml = yaml.load(travisYamlFile);
|
||||
|
||||
if (hasCurrentVersions(newVersions, travisYaml.node_js[0], packageJson)) {
|
||||
return 'skip';
|
||||
}
|
||||
|
||||
packageJson.dependencies['zapier-platform-core'] =
|
||||
newVersions.coreVersion;
|
||||
packageJson.engines.node = newVersions.nodeVersion;
|
||||
packageJson.engines.npm = newVersions.npmVersion;
|
||||
const json = JSON.stringify(packageJson, null, 2);
|
||||
fse.writeFileSync(packageJsonFile, json);
|
||||
|
||||
travisYaml.node_js[0] = newVersions.nodeVersion;
|
||||
return fse.writeFile(travisYamlFile, yaml.stringify(travisYaml, null, 2));
|
||||
})
|
||||
.then((result) => {
|
||||
if (result === 'skip') {
|
||||
return result;
|
||||
}
|
||||
|
||||
cmd = `git commit package.json .travis.yml -m "update ${PACKAGES_NAMES} versions to ${packagesVersions} respectively."`;
|
||||
return exec(cmd, repoDir);
|
||||
})
|
||||
.then((result) => {
|
||||
if (result === 'skip') {
|
||||
return result;
|
||||
}
|
||||
|
||||
cmd = 'git push origin master';
|
||||
return exec(cmd, repoDir);
|
||||
})
|
||||
.then((result) => {
|
||||
if (result === 'skip') {
|
||||
console.log(
|
||||
`${template} is already set to ${packagesVersions} for ${PACKAGES_NAMES} respectively, skipping`,
|
||||
);
|
||||
return 'skip';
|
||||
}
|
||||
console.log(
|
||||
`Set ${PACKAGES_NAMES} versions to ${packagesVersions} respectively on app template ${template} successfully.`,
|
||||
);
|
||||
return null;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
`Error setting ${PACKAGES_NAMES} versions for app template ${template}:`,
|
||||
err,
|
||||
);
|
||||
return template;
|
||||
});
|
||||
};
|
||||
|
||||
const rootTmpDir = tmp.tmpNameSync();
|
||||
fse.removeSync(rootTmpDir);
|
||||
fse.ensureDirSync(rootTmpDir);
|
||||
|
||||
const tasks = _.map(appTemplates, (template) =>
|
||||
setVersion(template, rootTmpDir),
|
||||
);
|
||||
|
||||
Promise.all(tasks).then((results) => {
|
||||
const failures = _.filter(
|
||||
results,
|
||||
(result) => result !== null && result !== 'skip',
|
||||
);
|
||||
const skipped = _.filter(results, (result) => result === 'skip');
|
||||
const successCount = tasks.length - failures.length - skipped.length;
|
||||
|
||||
if (failures.length) {
|
||||
console.error(
|
||||
`failed to set ${PACKAGES_NAMES} versions on these templates:`,
|
||||
failures.join(', '),
|
||||
);
|
||||
}
|
||||
if (skipped.length) {
|
||||
console.log(
|
||||
`skipped ${skipped.length} templates because versions for ${PACKAGES_NAMES} were already set to ${packagesVersions} respectively`,
|
||||
);
|
||||
}
|
||||
if (successCount) {
|
||||
console.log(
|
||||
`Successfully updated versions in ${successCount} app templates`,
|
||||
);
|
||||
}
|
||||
});
|
||||
69
vendor/zapier-platform/packages/cli/scripts/validate-app-templates.js
vendored
Executable file
69
vendor/zapier-platform/packages/cli/scripts/validate-app-templates.js
vendored
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const _ = require('lodash');
|
||||
const path = require('path');
|
||||
const tmp = require('tmp');
|
||||
const { promisifyAll } = require('../src/utils/promisify');
|
||||
|
||||
const fse = require('fs-extra');
|
||||
const childProcess = promisifyAll(require('child_process'));
|
||||
|
||||
const appTemplates = require('../src/app-templates');
|
||||
|
||||
const validateAppTemplate = (template, rootTmpDir) => {
|
||||
// const appDir = path.resolve(rootTmpDir, template);
|
||||
const zapierCmd = path.resolve(__dirname, '../src/bin/run');
|
||||
|
||||
const logFile = path.resolve(__dirname, '..', `${template}.log`);
|
||||
const logStream = fse.createWriteStream(logFile);
|
||||
|
||||
console.log(
|
||||
`Validating ${template} app template, writing logs to ${logFile}`,
|
||||
);
|
||||
return fse
|
||||
.ensureFile(logFile)
|
||||
.then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cmd = `${zapierCmd} init ${template} --template=${template} --debug && cd ${template} && npm install && ${zapierCmd} validate && export CLIENT_ID=1234 CLIENT_SECRET=asdf && ${zapierCmd} test --timeout=5000`;
|
||||
const child = childProcess.exec(cmd, { cwd: rootTmpDir }, (err) => {
|
||||
if (err) {
|
||||
console.log('error starting child process:', err);
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
child.stdout.pipe(logStream);
|
||||
child.stderr.pipe(logStream);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`${template} template validated successfully`);
|
||||
return null;
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(`${template} template validation failed. See ${logFile}.`);
|
||||
return template;
|
||||
});
|
||||
};
|
||||
|
||||
global.argOpts = {};
|
||||
|
||||
const rootTmpDir = tmp.tmpNameSync();
|
||||
fse.removeSync(rootTmpDir);
|
||||
fse.ensureDirSync(rootTmpDir);
|
||||
|
||||
const tasks = _.map(appTemplates, (template) =>
|
||||
validateAppTemplate(template, rootTmpDir),
|
||||
);
|
||||
|
||||
Promise.all(tasks).then((results) => {
|
||||
const failures = _.filter(results, (result) => result !== null);
|
||||
if (failures.length) {
|
||||
console.error(
|
||||
'these app templates failed to validate:',
|
||||
failures.join(', '),
|
||||
);
|
||||
} else {
|
||||
console.log('app templates validated successfully');
|
||||
}
|
||||
});
|
||||
36
vendor/zapier-platform/packages/cli/src/app-templates.js
vendored
Normal file
36
vendor/zapier-platform/packages/cli/src/app-templates.js
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// list of template (example) apps
|
||||
// https://github.com/zapier?utf8=%E2%9C%93&q=zapier-platform-example-app-&type=&language=
|
||||
module.exports = [
|
||||
// basic
|
||||
'minimal',
|
||||
'trigger',
|
||||
'search',
|
||||
'create',
|
||||
|
||||
// auth types
|
||||
'basic-auth',
|
||||
'custom-auth',
|
||||
'digest-auth',
|
||||
'oauth2',
|
||||
'oauth1-trello',
|
||||
'oauth1-tumblr',
|
||||
'oauth1-twitter',
|
||||
'session-auth',
|
||||
|
||||
// features
|
||||
'dynamic-dropdown',
|
||||
'files',
|
||||
'line-items',
|
||||
'middleware',
|
||||
'resource',
|
||||
'rest-hooks',
|
||||
'search-or-create',
|
||||
|
||||
// transpilers
|
||||
'babel',
|
||||
'typescript',
|
||||
|
||||
// full examples
|
||||
'github',
|
||||
'onedrive',
|
||||
];
|
||||
6
vendor/zapier-platform/packages/cli/src/bin/run
vendored
Executable file
6
vendor/zapier-platform/packages/cli/src/bin/run
vendored
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
(async () => {
|
||||
const oclif = await import('@oclif/core');
|
||||
await oclif.execute({ development: false, dir: __dirname });
|
||||
})();
|
||||
109
vendor/zapier-platform/packages/cli/src/constants.js
vendored
Normal file
109
vendor/zapier-platform/packages/cli/src/constants.js
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const versionStore = require('./version-store');
|
||||
|
||||
const BASE_ENDPOINT = process.env.ZAPIER_BASE_ENDPOINT || 'https://zapier.com';
|
||||
const API_PATH = '/api/platform/cli';
|
||||
const ENDPOINT = process.env.ZAPIER_ENDPOINT || BASE_ENDPOINT + API_PATH;
|
||||
const STARTER_REPO =
|
||||
process.env.ZAPIER_STARTER_REPO || 'zapier/zapier-platform-example-app';
|
||||
const AUTH_LOCATION_RAW = '~/.zapierrc';
|
||||
const AUTH_LOCATION =
|
||||
process.env.ZAPIER_AUTH_LOCATION || path.resolve(os.homedir(), '.zapierrc');
|
||||
const CURRENT_APP_FILE = process.env.ZAPIER_CURRENT_APP_FILE || '.zapierapprc';
|
||||
const PLATFORM_PACKAGE = 'zapier-platform-core';
|
||||
const LEGACY_RUNNER_PACKAGE = 'zapier-platform-legacy-scripting-runner';
|
||||
const BUILD_DIR = 'build';
|
||||
const DEFINITION_PATH = `${BUILD_DIR}/definition.json`;
|
||||
const BUILD_PATH = `${BUILD_DIR}/build.zip`;
|
||||
const SOURCE_PATH = `${BUILD_DIR}/source.zip`;
|
||||
const NODE_VERSION = versionStore[versionStore.length - 1].nodeVersion;
|
||||
const LAMBDA_VERSION = `v${NODE_VERSION}`;
|
||||
const NODE_VERSION_CLI_REQUIRES = '>=22'; // should be the oldest non-ETL version
|
||||
const AUTH_KEY = 'deployKey';
|
||||
const ANALYTICS_KEY = 'analyticsMode';
|
||||
const ANALYTICS_MODES = {
|
||||
enabled: 'enabled',
|
||||
anonymous: 'anonymous',
|
||||
disabled: 'disabled',
|
||||
};
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
const PACKAGE_NAME = packageJson.name;
|
||||
const PACKAGE_VERSION = packageJson.version;
|
||||
|
||||
const UPDATE_NOTIFICATION_INTERVAL = 1000 * 60 * 60 * 24 * 7; // one week
|
||||
|
||||
const CHECK_REF_DOC_LINK =
|
||||
'https://docs.zapier.com/platform/publish/integration-checks-reference';
|
||||
|
||||
const ISSUES_URL =
|
||||
'https://github.com/zapier/zapier-platform/issues/new/choose';
|
||||
|
||||
// can't just read from argv because they could have lots of extra data, such as
|
||||
// [ '/Users/david/.nvm/versions/node/v10.13.0/bin/node',
|
||||
// '/Users/david/projects/zapier/platform/node_modules/.bin/mocha',
|
||||
// 'src/tests' ]
|
||||
const argvStr = process.argv.join(' ');
|
||||
const IS_TESTING =
|
||||
argvStr.includes('mocha') ||
|
||||
argvStr.includes('jest') ||
|
||||
(process.env.NODE_ENV || '').toLowerCase().startsWith('test');
|
||||
|
||||
const MIN_TITLE_LENGTH = 2;
|
||||
const MAX_DESCRIPTION_LENGTH = 140;
|
||||
|
||||
const EXAMPLE_CHANGELOG = `
|
||||
## 3.0.0
|
||||
|
||||
Made some changes that affect app actions
|
||||
|
||||
1. Update the trigger/pr_review action, as well as changes for #456
|
||||
2. Fix trigger/new_card #208
|
||||
3. New action! create/add_contact
|
||||
|
||||
However, we also addressed fixed open issues!
|
||||
|
||||
- Fix #123 and an issue with create/send_message
|
||||
|
||||
## 2.0.0
|
||||
|
||||
* Fix some bugs.
|
||||
* Major docs fixes.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
Initial release to public.
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
ANALYTICS_KEY,
|
||||
ANALYTICS_MODES,
|
||||
API_PATH,
|
||||
AUTH_KEY,
|
||||
AUTH_LOCATION,
|
||||
AUTH_LOCATION_RAW,
|
||||
BASE_ENDPOINT,
|
||||
BUILD_DIR,
|
||||
BUILD_PATH,
|
||||
CHECK_REF_DOC_LINK,
|
||||
CURRENT_APP_FILE,
|
||||
DEFINITION_PATH,
|
||||
ENDPOINT,
|
||||
IS_TESTING,
|
||||
ISSUES_URL,
|
||||
LAMBDA_VERSION,
|
||||
LEGACY_RUNNER_PACKAGE,
|
||||
MIN_TITLE_LENGTH,
|
||||
MAX_DESCRIPTION_LENGTH,
|
||||
NODE_VERSION,
|
||||
NODE_VERSION_CLI_REQUIRES,
|
||||
PACKAGE_NAME,
|
||||
PACKAGE_VERSION,
|
||||
PLATFORM_PACKAGE,
|
||||
SOURCE_PATH,
|
||||
STARTER_REPO,
|
||||
UPDATE_NOTIFICATION_INTERVAL,
|
||||
EXAMPLE_CHANGELOG,
|
||||
};
|
||||
346
vendor/zapier-platform/packages/cli/src/generators/index.js
vendored
Normal file
346
vendor/zapier-platform/packages/cli/src/generators/index.js
vendored
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
const path = require('path');
|
||||
|
||||
const { merge } = require('lodash');
|
||||
const filter = require('gulp-filter');
|
||||
const { createGeneratorClass } = require('../utils/esm-wrapper');
|
||||
const prettier = require('gulp-prettier');
|
||||
|
||||
const { PACKAGE_VERSION, PLATFORM_PACKAGE } = require('../constants');
|
||||
const authFilesCodegen = require('../utils/auth-files-codegen');
|
||||
const PullGeneratorPromise = require('./pull');
|
||||
|
||||
const writeGenericReadme = (gen) => {
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath('README.template.md'),
|
||||
gen.destinationPath('README.md'),
|
||||
{ name: gen.options.packageName },
|
||||
);
|
||||
};
|
||||
|
||||
const appendReadme = (gen) => {
|
||||
const content = gen.fs.read(
|
||||
gen.templatePath(gen.options.template, 'README.md'),
|
||||
{ defaults: '' },
|
||||
);
|
||||
if (content) {
|
||||
gen.fs.append(gen.destinationPath('README.md'), '\n' + content);
|
||||
}
|
||||
};
|
||||
|
||||
const writeGitignore = (gen) => {
|
||||
gen.fs.copy(gen.templatePath('gitignore'), gen.destinationPath('.gitignore'));
|
||||
};
|
||||
|
||||
const writeGenericPackageJson = (gen, packageJsonExtension) => {
|
||||
const moduleExtension =
|
||||
gen.options.module === 'esm'
|
||||
? {
|
||||
exports: './index.js',
|
||||
type: 'module',
|
||||
}
|
||||
: {
|
||||
main: 'index.js',
|
||||
};
|
||||
|
||||
const fullExtension = merge(moduleExtension, packageJsonExtension);
|
||||
|
||||
gen.fs.writeJSON(
|
||||
gen.destinationPath('package.json'),
|
||||
merge(
|
||||
{
|
||||
name: gen.options.packageName,
|
||||
version: '1.0.0',
|
||||
description: '',
|
||||
scripts: {
|
||||
test: 'jest --testTimeout 10000',
|
||||
},
|
||||
dependencies: {
|
||||
[PLATFORM_PACKAGE]: PACKAGE_VERSION,
|
||||
},
|
||||
devDependencies: {
|
||||
jest: '^29.6.0',
|
||||
},
|
||||
private: true,
|
||||
},
|
||||
fullExtension,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const writeGenericTypeScriptPackageJson = (gen, packageJsonExtension) => {
|
||||
gen.fs.writeJSON(
|
||||
gen.destinationPath('package.json'),
|
||||
merge(
|
||||
{
|
||||
name: gen.options.packageName,
|
||||
version: '1.0.0',
|
||||
description: '',
|
||||
scripts: {
|
||||
test: 'npm run build && vitest --run',
|
||||
clean: 'rimraf ./dist ./build',
|
||||
build: 'npm run clean && tsc',
|
||||
dev: 'npm run build -- --watch',
|
||||
'_zapier-build': 'npm run build',
|
||||
},
|
||||
dependencies: {
|
||||
[PLATFORM_PACKAGE]: PACKAGE_VERSION,
|
||||
},
|
||||
devDependencies: {
|
||||
rimraf: '^5.0.10',
|
||||
typescript: '5.6.2',
|
||||
vitest: '^2.1.2',
|
||||
},
|
||||
private: true,
|
||||
exports: './dist/index.js',
|
||||
type: 'module',
|
||||
},
|
||||
packageJsonExtension,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const writeGenericIndex = (gen, hasAuth) => {
|
||||
const templatePath =
|
||||
gen.options.module === 'esm'
|
||||
? 'index-esm.template.js'
|
||||
: 'index.template.js';
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath(templatePath),
|
||||
gen.destinationPath('index.js'),
|
||||
{ corePackageName: PLATFORM_PACKAGE, hasAuth },
|
||||
);
|
||||
};
|
||||
|
||||
const writeGenericTypescriptIndex = (gen) => {
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath('index.template.ts'),
|
||||
gen.destinationPath('src/index.ts'),
|
||||
{ corePackageName: PLATFORM_PACKAGE },
|
||||
);
|
||||
};
|
||||
|
||||
const authTypes = {
|
||||
'basic-auth': 'basic',
|
||||
'custom-auth': 'custom',
|
||||
'digest-auth': 'digest',
|
||||
'oauth1-trello': 'oauth1',
|
||||
oauth2: 'oauth2',
|
||||
'session-auth': 'session',
|
||||
};
|
||||
|
||||
const writeGenericAuth = (gen) => {
|
||||
const authType = authTypes[gen.options.template];
|
||||
const content = authFilesCodegen[authType](gen.options.language);
|
||||
const destPath = (key) =>
|
||||
gen.options.language === 'typescript' ? `src/${key}.ts` : `${key}.js`;
|
||||
|
||||
Object.entries(content).forEach(([key, value]) => {
|
||||
gen.fs.write(gen.destinationPath(destPath(key)), value);
|
||||
});
|
||||
};
|
||||
|
||||
const writeGenericAuthTest = (gen) => {
|
||||
const authType = authTypes[gen.options.template];
|
||||
const fileExtension = gen.options.language === 'typescript' ? 'ts' : 'js';
|
||||
const destPath = gen.options.language === 'typescript' ? 'src/test' : 'test';
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath(
|
||||
`authTests/${authType || 'generic'}.test.${fileExtension}`,
|
||||
),
|
||||
gen.destinationPath(`${destPath}/authentication.test.${fileExtension}`),
|
||||
);
|
||||
};
|
||||
|
||||
const writeGenericTest = (gen) => {
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath('authTests/generic.test.js'),
|
||||
gen.destinationPath('test/example.test.js'),
|
||||
);
|
||||
};
|
||||
|
||||
// Write files for templates that demonstrate an auth type
|
||||
const writeForAuthTemplate = (gen) => {
|
||||
writeGitignore(gen);
|
||||
writeGenericReadme(gen);
|
||||
if (gen.options.language === 'typescript') {
|
||||
writeGenericTypescriptIndex(gen);
|
||||
writeGenericTypeScriptPackageJson(gen);
|
||||
gen.fs.copyTpl(
|
||||
gen.templatePath('tsconfig.template.json'),
|
||||
gen.destinationPath('tsconfig.json'),
|
||||
);
|
||||
} else {
|
||||
writeGenericIndex(gen, true);
|
||||
writeGenericPackageJson(gen);
|
||||
}
|
||||
writeGenericAuth(gen);
|
||||
writeGenericAuthTest(gen);
|
||||
};
|
||||
|
||||
const writeForMinimalTemplate = (gen) => {
|
||||
writeGitignore(gen);
|
||||
writeGenericReadme(gen);
|
||||
writeGenericPackageJson(gen);
|
||||
writeGenericIndex(gen, false);
|
||||
writeGenericTest(gen);
|
||||
};
|
||||
|
||||
// Write files for "standalone" templates, which essentially just copies an
|
||||
// example directory
|
||||
const writeForStandaloneTemplate = (gen) => {
|
||||
writeGitignore(gen);
|
||||
writeGenericReadme(gen);
|
||||
appendReadme(gen);
|
||||
|
||||
const packageJsonExtension = {
|
||||
// Put template-specific package.json settings here, grouped by template
|
||||
// names. This is going to used to extend the generic package.json.
|
||||
files: {
|
||||
dependencies: {
|
||||
'form-data': '4.0.0',
|
||||
},
|
||||
},
|
||||
}[gen.options.template];
|
||||
|
||||
writeGenericPackageJson(gen, packageJsonExtension);
|
||||
|
||||
gen.fs.copy(
|
||||
gen.templatePath(gen.options.template, '**', '*.{js,json,ts}'),
|
||||
gen.destinationPath(),
|
||||
);
|
||||
};
|
||||
|
||||
const TEMPLATE_ROUTES = {
|
||||
'basic-auth': writeForAuthTemplate,
|
||||
callback: writeForStandaloneTemplate,
|
||||
'custom-auth': writeForAuthTemplate,
|
||||
'digest-auth': writeForAuthTemplate,
|
||||
'dynamic-dropdown': writeForStandaloneTemplate,
|
||||
files: writeForStandaloneTemplate,
|
||||
'line-items': writeForStandaloneTemplate,
|
||||
minimal: writeForMinimalTemplate,
|
||||
'oauth1-trello': writeForAuthTemplate,
|
||||
oauth2: writeForAuthTemplate,
|
||||
openai: writeForStandaloneTemplate,
|
||||
'search-or-create': writeForStandaloneTemplate,
|
||||
'session-auth': writeForAuthTemplate,
|
||||
};
|
||||
|
||||
const ESM_SUPPORTED_TEMPLATES = ['minimal'];
|
||||
|
||||
// Which templates can be used with the --language typescript flag
|
||||
const TS_SUPPORTED_TEMPLATES = [
|
||||
'basic-auth',
|
||||
'custom-auth',
|
||||
'digest-auth',
|
||||
'oauth1-trello',
|
||||
'oauth2',
|
||||
'session-auth',
|
||||
];
|
||||
|
||||
const TEMPLATE_CHOICES = Object.keys(TEMPLATE_ROUTES);
|
||||
|
||||
const ProjectGeneratorPromise = createGeneratorClass((Generator) => {
|
||||
return class ProjectGenerator extends Generator {
|
||||
initializing() {
|
||||
this.sourceRoot(path.resolve(__dirname, 'templates'));
|
||||
this.destinationRoot(path.resolve(this.options.path));
|
||||
|
||||
const jsFilter = filter(['*.js', '*.json', '*.ts'], { restore: true });
|
||||
this.queueTransformStream(
|
||||
{ disabled: true },
|
||||
jsFilter,
|
||||
prettier({ singleQuote: true }),
|
||||
jsFilter.restore,
|
||||
);
|
||||
}
|
||||
|
||||
async prompting() {
|
||||
if (!this.options.template) {
|
||||
// Filter template choices based on language and module type
|
||||
let templateChoices = TEMPLATE_CHOICES;
|
||||
let defaultTemplate = 'minimal';
|
||||
|
||||
// TypeScript filtering takes precedence over ESM filtering
|
||||
if (this.options.language === 'typescript') {
|
||||
templateChoices = TS_SUPPORTED_TEMPLATES;
|
||||
defaultTemplate = 'basic-auth';
|
||||
} else if (this.options.module === 'esm') {
|
||||
templateChoices = ESM_SUPPORTED_TEMPLATES;
|
||||
defaultTemplate = 'minimal'; // minimal is the only ESM template
|
||||
}
|
||||
|
||||
this.answers = await this.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'template',
|
||||
choices: templateChoices,
|
||||
message: 'Choose a project template to start with:',
|
||||
default: defaultTemplate,
|
||||
},
|
||||
]);
|
||||
this.options.template = this.answers.template;
|
||||
}
|
||||
|
||||
if (
|
||||
ESM_SUPPORTED_TEMPLATES.includes(this.options.template) &&
|
||||
!this.options.module
|
||||
) {
|
||||
this.answers = await this.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'module',
|
||||
choices: ['esm', 'commonjs'],
|
||||
message: 'Choose module type:',
|
||||
default: 'esm',
|
||||
},
|
||||
]);
|
||||
this.options.module = this.answers.module;
|
||||
}
|
||||
|
||||
if (this.options.language) {
|
||||
if (this.options.language === 'typescript') {
|
||||
// check if the template supports typescript
|
||||
if (!TS_SUPPORTED_TEMPLATES.includes(this.options.template)) {
|
||||
throw new Error(
|
||||
'Typescript is not supported for this template, please use a different template or set the language to javascript. Supported templates: ' +
|
||||
TS_SUPPORTED_TEMPLATES.join(', '),
|
||||
);
|
||||
}
|
||||
// if they try to combine typescript with commonjs, throw an error
|
||||
if (this.options.module === 'commonjs') {
|
||||
throw new Error('Typescript is not supported for commonjs');
|
||||
} // esm is supported for typescript templates
|
||||
}
|
||||
} else {
|
||||
// default to javascript for the language if it's not set
|
||||
this.options.language = 'javascript';
|
||||
}
|
||||
|
||||
if (
|
||||
!ESM_SUPPORTED_TEMPLATES.includes(this.options.template) &&
|
||||
this.options.module === 'esm' &&
|
||||
this.options.language === 'javascript'
|
||||
) {
|
||||
throw new Error(
|
||||
'ESM is not supported for this template, please use a different template, set the module to commonjs, or try setting the language to Typescript',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
writing() {
|
||||
this.options.packageName = path.basename(this.options.path);
|
||||
|
||||
const writeFunc = TEMPLATE_ROUTES[this.options.template];
|
||||
writeFunc(this);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
TEMPLATE_CHOICES,
|
||||
ESM_SUPPORTED_TEMPLATES,
|
||||
TS_SUPPORTED_TEMPLATES,
|
||||
PullGenerator: PullGeneratorPromise,
|
||||
ProjectGenerator: ProjectGeneratorPromise,
|
||||
};
|
||||
55
vendor/zapier-platform/packages/cli/src/generators/pull.js
vendored
Normal file
55
vendor/zapier-platform/packages/cli/src/generators/pull.js
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
const colors = require('colors/safe');
|
||||
const debug = require('debug')('zapier:pull');
|
||||
const inquirer = require('inquirer');
|
||||
const path = require('path');
|
||||
const { createGeneratorClass } = require('../utils/esm-wrapper');
|
||||
|
||||
const maybeOverwriteFiles = async (gen) => {
|
||||
const dstDir = gen.options.dstDir;
|
||||
const srcDir = gen.options.srcDir;
|
||||
for (const file of gen.options.sourceFiles) {
|
||||
gen.fs.copy(path.join(srcDir, file), path.join(dstDir, file), gen.options);
|
||||
}
|
||||
};
|
||||
|
||||
// Export a factory function that creates the PullGenerator class
|
||||
module.exports = createGeneratorClass((Generator) => {
|
||||
return class PullGenerator extends Generator {
|
||||
initializing() {
|
||||
debug('SRC', this.options.sourceFiles);
|
||||
}
|
||||
|
||||
prompting() {
|
||||
const prompts = [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: `Warning: You are about to overwrite existing files.
|
||||
|
||||
Before proceeding, please make sure you have saved your work. Consider creating a backup or saving your current state in a git branch.
|
||||
|
||||
If presented with a series of options ('ynarxdeiH'), you may
|
||||
press Enter to view more details about each option. For example, 'x' will abort the process.
|
||||
|
||||
Do you want to continue?`,
|
||||
default: false,
|
||||
},
|
||||
];
|
||||
|
||||
return inquirer.prompt(prompts).then((answers) => {
|
||||
if (!answers.confirm) {
|
||||
this.log(colors.green('zapier pull cancelled'));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
writing() {
|
||||
maybeOverwriteFiles(this);
|
||||
}
|
||||
|
||||
end() {
|
||||
this.log(colors.green('zapier pull completed successfully'));
|
||||
}
|
||||
};
|
||||
});
|
||||
34
vendor/zapier-platform/packages/cli/src/generators/templates/README.template.md
vendored
Normal file
34
vendor/zapier-platform/packages/cli/src/generators/templates/README.template.md
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# <%= name %>
|
||||
|
||||
This Zapier integration project is generated by the `zapier-platform init` CLI command.
|
||||
|
||||
These are what you normally do next:
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install --ignore-scripts # or you can use pnpm or yarn
|
||||
|
||||
# Run tests
|
||||
zapier-platform test
|
||||
|
||||
# Register the integration on Zapier if you haven't
|
||||
zapier-platform register "App Title"
|
||||
|
||||
# Or you can link to an existing integration on Zapier
|
||||
zapier-platform link
|
||||
|
||||
# Push it to Zapier
|
||||
zapier-platform push
|
||||
```
|
||||
|
||||
Then, to add more features, you can use the `zapier-platform scaffold` command, for example:
|
||||
|
||||
```bash
|
||||
# Add a trigger
|
||||
zapier-platform scaffold trigger contact
|
||||
|
||||
# Add an action
|
||||
zapier-platform scaffold create contact
|
||||
```
|
||||
|
||||
Find out more on the latest docs: https://docs.zapier.com/platform
|
||||
43
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/basic.test.js
vendored
Normal file
43
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/basic.test.js
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* globals describe, it, expect */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('basic auth', () => {
|
||||
it('automatically has Authorize Header add', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.request.headers.Authorization).toBe(
|
||||
'Basic dXNlcjpzZWNyZXQ='
|
||||
);
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'badpwd',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (err) {
|
||||
expect(err.message).toContain(
|
||||
'The username and/or password you supplied is incorrect'
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
42
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/basic.test.ts
vendored
Normal file
42
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/basic.test.ts
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('basic auth', () => {
|
||||
it('automatically has Authorize Header add', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.request.headers.Authorization).toBe(
|
||||
'Basic dXNlcjpzZWNyZXQ='
|
||||
);
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'badpwd',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (err) {
|
||||
expect(err.message).toContain(
|
||||
'The username and/or password you supplied is incorrect'
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
35
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/custom.test.js
vendored
Normal file
35
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/custom.test.js
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* globals describe, it, expect */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('custom auth', () => {
|
||||
it('passes authentication and returns json', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
apiKey: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
expect(response.data).toHaveProperty('username');
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
apiKey: 'bad',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('The API Key you supplied is incorrect');
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
34
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/custom.test.ts
vendored
Normal file
34
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/custom.test.ts
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('custom auth', () => {
|
||||
it('passes authentication and returns json', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
apiKey: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
expect(response.data).toHaveProperty('username');
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
apiKey: 'bad',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('The API Key you supplied is incorrect');
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
44
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/digest.test.js
vendored
Normal file
44
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/digest.test.js
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
/* globals describe, it, expect */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('digest auth', () => {
|
||||
it('correctly authenticates', async () => {
|
||||
// Try changing the values of username or password to see how the test method behaves
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'myuser',
|
||||
password: 'mypass',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data.authorized).toBe(true);
|
||||
expect(response.data.user).toBe('myuser');
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
// Try changing the values of username or password to see how the test method behaves
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'badpwd',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (err) {
|
||||
expect(err.message).toContain(
|
||||
'The username and/or password you supplied is incorrect'
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
43
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/digest.test.ts
vendored
Normal file
43
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/digest.test.ts
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('digest auth', () => {
|
||||
it('correctly authenticates', async () => {
|
||||
// Try changing the values of username or password to see how the test method behaves
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'myuser',
|
||||
password: 'mypass',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data.authorized).toBe(true);
|
||||
expect(response.data.user).toBe('myuser');
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
// Try changing the values of username or password to see how the test method behaves
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'user',
|
||||
password: 'badpwd',
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await appTester(App.authentication.test, bundle);
|
||||
} catch (err) {
|
||||
expect(err.message).toContain(
|
||||
'The username and/or password you supplied is incorrect'
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
7
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/generic.test.js
vendored
Normal file
7
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/generic.test.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/* globals describe, it, expect */
|
||||
|
||||
describe('addition ', () => {
|
||||
it('should work', () => {
|
||||
expect(1 + 1).toEqual(2);
|
||||
});
|
||||
});
|
||||
65
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth1.test.js
vendored
Normal file
65
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth1.test.js
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* globals describe, it, expect, beforeAll */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
zapier.tools.env.inject(); // read from the .env file
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
// You should create a `.env` file and populate it with your actual Trello
|
||||
// client ID and secret (called "API Key" and "API Secret" by Trello), which you
|
||||
// can find on https://trello.com/app-key.
|
||||
// The `.env` file should look like
|
||||
/*
|
||||
CLIENT_ID=<trello_api_key>
|
||||
CLIENT_SECRET=<trello_api_secret>
|
||||
*/
|
||||
// then you can delete the following 2 lines
|
||||
process.env.CLIENT_ID = process.env.CLIENT_ID || '<trello_api_key>';
|
||||
process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || '<trello_api_secret>';
|
||||
|
||||
describe('oauth1 app', () => {
|
||||
beforeAll(() => {
|
||||
// It's a good idea to store your Client ID and Secret in the environment rather than in code.
|
||||
if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
|
||||
throw new Error(
|
||||
`Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('fetch a request token', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
// You should add your redirect URI to https://trello.com/app-key
|
||||
redirect_uri: 'https://zapier.com',
|
||||
},
|
||||
};
|
||||
const tokens = await appTester(
|
||||
App.authentication.oauth1Config.getRequestToken,
|
||||
bundle
|
||||
);
|
||||
expect(tokens).toHaveProperty('oauth_token');
|
||||
expect(tokens).toHaveProperty('oauth_token_secret');
|
||||
});
|
||||
|
||||
it('generates an authorize URL', async () => {
|
||||
const bundle = {
|
||||
// In production, these will be generated by Zapier and set automatically
|
||||
inputData: {
|
||||
oauth_token: '4444',
|
||||
redirect_uri: 'https://zapier.com/',
|
||||
},
|
||||
};
|
||||
|
||||
const authorizeUrl = await appTester(
|
||||
App.authentication.oauth1Config.authorizeUrl,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(authorizeUrl).toBe(
|
||||
'https://trello.com/1/OAuthAuthorizeToken?oauth_token=4444&name=Zapier%2FTrello%20OAuth1%20Test'
|
||||
);
|
||||
});
|
||||
});
|
||||
63
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth1.test.ts
vendored
Normal file
63
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth1.test.ts
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { describe, expect, it, beforeAll } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
// Only defining the env vars here so the tests out of the box.
|
||||
// You should create a `.env` file and populate it with your actual Trello
|
||||
// client ID and secret (called "API Key" and "API Secret" by Trello), which you
|
||||
// can find on https://trello.com/app-key.
|
||||
// The `.env` file should look like
|
||||
/*
|
||||
CLIENT_ID=<trello_api_key>
|
||||
CLIENT_SECRET=<trello_api_secret>
|
||||
*/
|
||||
// then you can delete the following 2 lines
|
||||
process.env.CLIENT_ID = process.env.CLIENT_ID || '<trello_api_key>';
|
||||
process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || '<trello_api_secret>';
|
||||
|
||||
describe('oauth1 app', () => {
|
||||
beforeAll(() => {
|
||||
// It's a good idea to store your Client ID and Secret in the environment rather than in code.
|
||||
if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
|
||||
throw new Error(
|
||||
`Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('fetch a request token', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
// You should add your redirect URI to https://trello.com/app-key
|
||||
redirect_uri: 'https://zapier.com',
|
||||
},
|
||||
};
|
||||
const tokens = await appTester(
|
||||
App.authentication.oauth1Config.getRequestToken,
|
||||
bundle
|
||||
);
|
||||
expect(tokens).toHaveProperty('oauth_token');
|
||||
expect(tokens).toHaveProperty('oauth_token_secret');
|
||||
});
|
||||
|
||||
it('generates an authorize URL', async () => {
|
||||
const bundle = {
|
||||
// In production, these will be generated by Zapier and set automatically
|
||||
inputData: {
|
||||
oauth_token: '4444',
|
||||
redirect_uri: 'https://zapier.com/',
|
||||
},
|
||||
};
|
||||
|
||||
const authorizeUrl = await appTester(
|
||||
App.authentication.oauth1Config.authorizeUrl,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(authorizeUrl).toBe(
|
||||
'https://trello.com/1/OAuthAuthorizeToken?oauth_token=4444&name=Zapier%2FTrello%20OAuth1%20Test'
|
||||
);
|
||||
});
|
||||
});
|
||||
118
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth2.test.js
vendored
Normal file
118
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth2.test.js
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* globals describe, it, expect, beforeAll */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
zapier.tools.env.inject(); // read from the .env file
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
// Only here so the tests out of the box.
|
||||
// You should create a `.env` file and populate it with the necessarily configuration
|
||||
// it should look like:
|
||||
/*
|
||||
CLIENT_ID=1234
|
||||
CLIENT_SECRET=asdf
|
||||
*/
|
||||
// then you can delete the following 2 lines
|
||||
process.env.CLIENT_ID = process.env.CLIENT_ID || '1234';
|
||||
process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || 'asdf';
|
||||
|
||||
describe('oauth2 app', () => {
|
||||
beforeAll(() => {
|
||||
// It's a good idea to store your Client ID and Secret in the environment rather than in code.
|
||||
if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
|
||||
throw new Error(
|
||||
`Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('generates an authorize URL', async () => {
|
||||
const bundle = {
|
||||
// In production, these will be generated by Zapier and set automatically
|
||||
inputData: {
|
||||
state: '4444',
|
||||
redirect_uri: 'https://zapier.com/',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
};
|
||||
|
||||
const authorizeUrl = await appTester(
|
||||
App.authentication.oauth2Config.authorizeUrl,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(authorizeUrl).toBe(
|
||||
'https://auth-json-server.zapier-staging.com/oauth/authorize?client_id=1234&state=4444&redirect_uri=https%3A%2F%2Fzapier.com%2F&response_type=code'
|
||||
);
|
||||
});
|
||||
|
||||
it('can fetch an access token', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
// In production, Zapier passes along whatever code your API set in the query params when it redirects
|
||||
// the user's browser to the `redirect_uri`
|
||||
code: 'one_time_code',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
cleanedRequest: {
|
||||
querystring: {
|
||||
accountDomain: 'test-account',
|
||||
code: 'one_time_code',
|
||||
},
|
||||
},
|
||||
rawRequest: {
|
||||
querystring: '?accountDomain=test-account&code=one_time_code',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.authentication.oauth2Config.getAccessToken,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(result.access_token).toBe('a_token');
|
||||
expect(result.refresh_token).toBe('a_refresh_token');
|
||||
});
|
||||
|
||||
it('can refresh the access token', async () => {
|
||||
const bundle = {
|
||||
// In production, Zapier provides these. For testing, we have hard-coded them.
|
||||
// When writing tests for your own app, you should consider exporting them and doing process.env.MY_ACCESS_TOKEN
|
||||
authData: {
|
||||
access_token: 'a_token',
|
||||
refresh_token: 'a_refresh_token',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.authentication.oauth2Config.refreshAccessToken,
|
||||
bundle
|
||||
);
|
||||
expect(result.access_token).toBe('a_token');
|
||||
});
|
||||
|
||||
it('includes the access token in future requests', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: 'a_token',
|
||||
refresh_token: 'a_refresh_token',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
expect(response.data).toHaveProperty('username');
|
||||
expect(response.data.username).toBe('Bret');
|
||||
});
|
||||
});
|
||||
115
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth2.test.ts
vendored
Normal file
115
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/oauth2.test.ts
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { describe, expect, it, beforeAll } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
// Only defining the env vars here so the tests out of the box.
|
||||
// You should create a `.env` file and populate it with the necessarily configuration
|
||||
// it should look like:
|
||||
/*
|
||||
CLIENT_ID=1234
|
||||
CLIENT_SECRET=asdf
|
||||
*/
|
||||
// then you can delete the following 2 lines
|
||||
process.env.CLIENT_ID = process.env.CLIENT_ID || '1234';
|
||||
process.env.CLIENT_SECRET = process.env.CLIENT_SECRET || 'asdf';
|
||||
|
||||
describe('authentication', () => {
|
||||
beforeAll(() => {
|
||||
// It's a good idea to store your Client ID and Secret in the environment rather than in code.
|
||||
if (!(process.env.CLIENT_ID && process.env.CLIENT_SECRET)) {
|
||||
throw new Error(
|
||||
`Before running the tests, make sure CLIENT_ID and CLIENT_SECRET are available in the environment.`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('generates an authorize URL', async () => {
|
||||
const bundle = {
|
||||
// In production, these will be generated by Zapier and set automatically
|
||||
inputData: {
|
||||
state: '4444',
|
||||
redirect_uri: 'https://zapier.com/',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
};
|
||||
|
||||
const authorizeUrl = await appTester(
|
||||
App.authentication.oauth2Config.authorizeUrl,
|
||||
bundle,
|
||||
);
|
||||
|
||||
expect(authorizeUrl).toBe(
|
||||
'https://auth-json-server.zapier-staging.com/oauth/authorize?client_id=1234&state=4444&redirect_uri=https%3A%2F%2Fzapier.com%2F&response_type=code',
|
||||
);
|
||||
});
|
||||
|
||||
it('can fetch an access token', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
// In production, Zapier passes along whatever code your API set in the query params when it redirects
|
||||
// the user's browser to the `redirect_uri`
|
||||
code: 'one_time_code',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
cleanedRequest: {
|
||||
querystring: {
|
||||
accountDomain: 'test-account',
|
||||
code: 'one_time_code',
|
||||
},
|
||||
},
|
||||
rawRequest: {
|
||||
querystring: '?accountDomain=test-account&code=one_time_code',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.authentication.oauth2Config.getAccessToken,
|
||||
bundle,
|
||||
);
|
||||
|
||||
expect(result.access_token).toBe('a_token');
|
||||
expect(result.refresh_token).toBe('a_refresh_token');
|
||||
});
|
||||
|
||||
it('can refresh the access token', async () => {
|
||||
const bundle = {
|
||||
// In production, Zapier provides these. For testing, we have hard-coded them.
|
||||
// When writing tests for your own app, you should consider exporting them and doing process.env.MY_ACCESS_TOKEN
|
||||
authData: {
|
||||
access_token: 'a_token',
|
||||
refresh_token: 'a_refresh_token',
|
||||
},
|
||||
environment: {
|
||||
CLIENT_ID: process.env.CLIENT_ID,
|
||||
CLIENT_SECRET: process.env.CLIENT_SECRET,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.authentication.oauth2Config.refreshAccessToken,
|
||||
bundle,
|
||||
);
|
||||
expect(result.access_token).toBe('a_token');
|
||||
});
|
||||
|
||||
it('includes the access token in future requests', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
access_token: 'a_token',
|
||||
refresh_token: 'a_refresh_token',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
expect(response.data).toHaveProperty('username');
|
||||
expect(response.data.username).toBe('Bret');
|
||||
});
|
||||
});
|
||||
37
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/session.test.js
vendored
Normal file
37
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/session.test.js
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* globals describe, it, expect */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('session auth app', () => {
|
||||
it('has an exchange for username/password', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'bryan',
|
||||
password: 'hunter2',
|
||||
},
|
||||
};
|
||||
|
||||
const newAuthData = await appTester(
|
||||
App.authentication.sessionConfig.perform,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(newAuthData.sessionKey).toBe('secret');
|
||||
});
|
||||
|
||||
it('has auth details added to every request', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
sessionKey: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.request.headers['X-API-Key']).toBe('secret');
|
||||
});
|
||||
});
|
||||
36
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/session.test.ts
vendored
Normal file
36
vendor/zapier-platform/packages/cli/src/generators/templates/authTests/session.test.ts
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import zapier from 'zapier-platform-core';
|
||||
|
||||
import App from '../index.js';
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
describe('session auth app', () => {
|
||||
it('has an exchange for username/password', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
username: 'bryan',
|
||||
password: 'hunter2',
|
||||
},
|
||||
};
|
||||
|
||||
const newAuthData = await appTester(
|
||||
App.authentication.sessionConfig.perform,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(newAuthData.sessionKey).toBe('secret');
|
||||
});
|
||||
|
||||
it('has auth details added to every request', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
sessionKey: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
const response = await appTester(App.authentication.test, bundle);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.request.headers['X-API-Key']).toBe('secret');
|
||||
});
|
||||
});
|
||||
5
vendor/zapier-platform/packages/cli/src/generators/templates/callback/README.md
vendored
Normal file
5
vendor/zapier-platform/packages/cli/src/generators/templates/callback/README.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# The "Callback" Template
|
||||
|
||||
This example has a create showcasing the `performResume` callback function.
|
||||
|
||||
Find out more in the docs: https://github.com/zapier/zapier-platform/blob/main/packages/cli#zgeneratecallbackurl.
|
||||
73
vendor/zapier-platform/packages/cli/src/generators/templates/callback/creates/prediction.js
vendored
Normal file
73
vendor/zapier-platform/packages/cli/src/generators/templates/callback/creates/prediction.js
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// We recommend writing your creates separate like this and rolling them
|
||||
// into the App definition at the end.
|
||||
module.exports = {
|
||||
key: 'prediction',
|
||||
|
||||
// You'll want to provide some helpful display labels and descriptions
|
||||
// for users. Zapier will put them into the UX.
|
||||
noun: 'Prediction',
|
||||
display: {
|
||||
label: 'Create Prediction',
|
||||
description: 'Creates a new prediction.',
|
||||
},
|
||||
|
||||
// `operation` is where the business logic goes.
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'question',
|
||||
required: true,
|
||||
type: 'string',
|
||||
helpText: 'Provide a "Yes" or "No" question to ask the Magic 8-Ball.',
|
||||
},
|
||||
],
|
||||
perform: (z, bundle) => {
|
||||
const promise = z.request({
|
||||
url: 'https://auth-json-server.zapier-staging.com/magic',
|
||||
method: 'POST',
|
||||
body: {
|
||||
callbackUrl: z.generateCallbackUrl(),
|
||||
},
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
|
||||
// This is NOT how you normally do authentication. This is just to demo how to write a create here.
|
||||
// Refer to this doc to set up authentication:
|
||||
// https://docs.zapier.com/platform/reference/cli-docs#authentication
|
||||
'X-API-Key': 'secret',
|
||||
},
|
||||
});
|
||||
|
||||
return promise.then((response) => ({ ...response.data, extra: 'data' }));
|
||||
},
|
||||
|
||||
performResume: (z, bundle) => {
|
||||
// The original output from perform is available in bundle.outputData.
|
||||
// The data POSTed to the callbackUrl is in bundle.cleanedRequest.
|
||||
// The full request object corresponding to bundle.cleanedRequest can be found in bundle.rawRequest.
|
||||
const { extra, ...originalOutput } = bundle.outputData;
|
||||
// The following line will return an object containing the contents of the original API response to the
|
||||
// request from the perform function merged with the contents of the new request from the API.
|
||||
return { ...originalOutput, ...bundle.cleanedRequest };
|
||||
},
|
||||
|
||||
// In cases where Zapier needs to show an example record to the user, but we are unable to get a live example
|
||||
// from the API, Zapier will fallback to this hard-coded sample. It should reflect the data structure of
|
||||
// returned records, and have obviously dummy values that we can show to any user.
|
||||
sample: {
|
||||
callbackUrl: 'http://zapier.com/hooks/catch/-1234/abcdef/',
|
||||
status: 'success',
|
||||
result: 'Ask again later.',
|
||||
},
|
||||
|
||||
// If the resource can have fields that are custom on a per-user basis, define a function to fetch the custom
|
||||
// field definitions. The result will be used to augment the sample.
|
||||
// outputFields: () => { return []; }
|
||||
// Alternatively, a static field definition should be provided, to specify labels for the fields
|
||||
outputFields: [
|
||||
{ key: 'callbackUrl', label: 'Callback URL' },
|
||||
{ key: 'status', label: 'Status' },
|
||||
{ key: 'result', label: 'Predicted Result' },
|
||||
],
|
||||
},
|
||||
};
|
||||
17
vendor/zapier-platform/packages/cli/src/generators/templates/callback/index.js
vendored
Normal file
17
vendor/zapier-platform/packages/cli/src/generators/templates/callback/index.js
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const prediction = require('./creates/prediction');
|
||||
|
||||
// Now we can roll up all our behaviors in an App.
|
||||
const App = {
|
||||
// This is just shorthand to reference the installed dependencies you have. Zapier will
|
||||
// need to know these before we can upload
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
// If you want your creates to show up, you better include it here!
|
||||
creates: {
|
||||
[prediction.key]: prediction,
|
||||
},
|
||||
};
|
||||
|
||||
// Finally, export the app.
|
||||
module.exports = App;
|
||||
40
vendor/zapier-platform/packages/cli/src/generators/templates/callback/test/creates.test.js
vendored
Normal file
40
vendor/zapier-platform/packages/cli/src/generators/templates/callback/test/creates.test.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('creates', () => {
|
||||
test('perform function returns intermediate data', async () => {
|
||||
const bundle = { inputData: { question: 'Will this work?' } };
|
||||
const result = await appTester(App.creates.prediction.operation.perform, bundle);
|
||||
expect(result).toMatchObject({
|
||||
status: "...thinking...",
|
||||
callbackUrl: "https://auth-json-server.zapier-staging.com/echo",
|
||||
extra: "data",
|
||||
});
|
||||
});
|
||||
|
||||
test('performResume function returns "final" data', async () => {
|
||||
const bundle = {
|
||||
outputData: {
|
||||
callbackUrl: 'https://auth-json-server.zapier-staging.com/echo',
|
||||
status: '...thinking...',
|
||||
extra: 'data',
|
||||
},
|
||||
cleanedRequest: {
|
||||
status: 'success',
|
||||
result: 'Ask again later.',
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(App.creates.prediction.operation.performResume, bundle);
|
||||
expect(result).toMatchObject({
|
||||
status: 'success',
|
||||
result: 'Ask again later.',
|
||||
callbackUrl: 'https://auth-json-server.zapier-staging.com/echo'
|
||||
});
|
||||
});
|
||||
});
|
||||
107
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/README.md
vendored
Normal file
107
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/README.md
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# dynamic-dropdown
|
||||
|
||||
This example integration demonstrates how to create **dynamic dropdowns** (also known as dynamic choices) in Zapier integrations.
|
||||
|
||||
## Dynamic Dropdown Patterns
|
||||
|
||||
There are two ways to implement dynamic dropdowns:
|
||||
|
||||
### 1. Trigger-based (Legacy Pattern)
|
||||
|
||||
Uses a separate trigger to fetch choices. Reference it with the `dynamic` property:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'species_id',
|
||||
type: 'integer',
|
||||
label: 'Species',
|
||||
dynamic: 'species.id.name', // Format: "triggerKey.idField.labelField"
|
||||
}
|
||||
```
|
||||
|
||||
The trigger (`species`) fetches data, and Zapier uses `id` for the value and `name` for the display label.
|
||||
|
||||
### 2. Perform-based (New Pattern)
|
||||
|
||||
Uses a function to fetch choices directly. Define it with `choices.perform`:
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'planet_id',
|
||||
type: 'integer',
|
||||
label: 'Home Planet',
|
||||
resource: 'planet', // Explicit resource linking (see below)
|
||||
choices: {
|
||||
perform: getPlanetChoices,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### Resource Linking
|
||||
|
||||
The `resource` property explicitly links an input field to a resource. This is particularly important for perform-based dropdowns since they don't have a `dynamic` property to derive the resource from.
|
||||
|
||||
```javascript
|
||||
{
|
||||
key: 'spreadsheet_id',
|
||||
resource: 'spreadsheet',
|
||||
choices: { perform: getSpreadsheets },
|
||||
}
|
||||
```
|
||||
|
||||
The perform function must return:
|
||||
|
||||
```javascript
|
||||
{
|
||||
results: [
|
||||
{ id: '1', label: 'Tatooine' },
|
||||
{ id: '2', label: 'Alderaan' },
|
||||
],
|
||||
paging_token: 'https://api.example.com/planets?page=2', // or null if no more pages
|
||||
}
|
||||
```
|
||||
|
||||
#### Pagination Support
|
||||
|
||||
The perform function receives `bundle.meta.paging_token` for subsequent page requests:
|
||||
|
||||
```javascript
|
||||
const getPlanetChoices = async (z, bundle) => {
|
||||
// First request: paging_token is undefined
|
||||
// Subsequent requests: paging_token is the value you returned previously
|
||||
const url = bundle.meta.paging_token || 'https://api.example.com/planets';
|
||||
|
||||
const response = await z.request({ url });
|
||||
|
||||
return {
|
||||
results: response.data.results.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
})),
|
||||
// Return null when there are no more pages
|
||||
paging_token: response.data.next,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## This Example
|
||||
|
||||
This integration uses the [Star Wars API](https://swapi.dev/) to demonstrate:
|
||||
|
||||
- **Species dropdown** - Trigger-based pattern using the `species` trigger
|
||||
- **Planet dropdown** - Perform-based pattern with pagination and explicit `resource` linking
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run tests
|
||||
zapier-platform test
|
||||
|
||||
# Push to Zapier
|
||||
zapier-platform push
|
||||
```
|
||||
|
||||
Find out more on the latest docs: https://docs.zapier.com/platform
|
||||
12
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/index.js
vendored
Normal file
12
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/index.js
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const people = require('./triggers/people');
|
||||
const species = require('./triggers/species');
|
||||
|
||||
module.exports = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
triggers: {
|
||||
[people.key]: people,
|
||||
[species.key]: species,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('triggers', () => {
|
||||
test('species', async () => {
|
||||
const bundle = {
|
||||
inputData: {},
|
||||
meta: {},
|
||||
};
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers.species.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results.length).toBeGreaterThan(1);
|
||||
|
||||
const firstSpecies = results[0];
|
||||
expect(firstSpecies.id).toBe(1);
|
||||
expect(firstSpecies.name).toBe('Human');
|
||||
});
|
||||
|
||||
test('people', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
species: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers.people.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results.length).toBeGreaterThan(1);
|
||||
|
||||
const firstPerson = results[0];
|
||||
expect(firstPerson.id).toBe(1);
|
||||
expect(firstPerson.name).toBe('Luke Skywalker');
|
||||
});
|
||||
});
|
||||
120
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/triggers/people.js
vendored
Normal file
120
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/triggers/people.js
vendored
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
const { extractID } = require('../utils');
|
||||
|
||||
/**
|
||||
* PERFORM-BASED choices WITH PAGINATION (NEW pattern)
|
||||
* Fetches planets from the Star Wars API with pagination support.
|
||||
*
|
||||
* - bundle.meta.paging_token is a full URL from the previous response
|
||||
* - Return paging_token as the API's next page URL (or null if no more pages)
|
||||
*
|
||||
* MUST return: { results: [...], paging_token: string|null }
|
||||
*/
|
||||
const getPlanetChoices = async (z, bundle) => {
|
||||
// paging_token is a full URL to the next page (from SWAPI's "next" field)
|
||||
// First page: paging_token is undefined/null, use default URL
|
||||
const url = bundle.meta.paging_token || 'https://swapi.dev/api/planets/';
|
||||
|
||||
const response = await z.request({ url });
|
||||
const data = response.data;
|
||||
|
||||
// SWAPI returns: { results: [...], next: "url" or null }
|
||||
return {
|
||||
results: data.results.map((planet) => ({
|
||||
id: extractID(planet.url),
|
||||
label: planet.name,
|
||||
})),
|
||||
// Return SWAPI's next URL as our paging_token
|
||||
paging_token: data.next,
|
||||
};
|
||||
};
|
||||
|
||||
// Fetches a list of records from the endpoint
|
||||
const perform = async (z, bundle) => {
|
||||
// Ideally, we should poll through all the pages of results, but in this
|
||||
// example we're going to omit that part. Thus, this trigger only "see" the
|
||||
// people in their first page of results.
|
||||
const response = await z.request({ url: 'https://swapi.info/api/people/' });
|
||||
let peopleArray = response.data;
|
||||
|
||||
if (bundle.inputData.species_id) {
|
||||
// The Zap's setup has requested a specific species of person. Since the
|
||||
// API/endpoint can't perform the filtering, we'll perform it here, within
|
||||
// the integration, and return the matching objects/records back to Zapier.
|
||||
peopleArray = peopleArray.filter((person) => {
|
||||
let speciesID;
|
||||
if (!person.species || !person.species.length) {
|
||||
speciesID = 1; // Assume human if species is not provided
|
||||
} else {
|
||||
speciesID = extractID(person.species[0]);
|
||||
}
|
||||
return speciesID === bundle.inputData.species_id;
|
||||
});
|
||||
}
|
||||
|
||||
if (bundle.inputData.planet_id) {
|
||||
// The Zap's setup has requested a specific home planet. Filter people by
|
||||
// homeworld (SWAPI people have a homeworld URL).
|
||||
peopleArray = peopleArray.filter((person) => {
|
||||
if (!person.homeworld) return false;
|
||||
const homeworldID = extractID(person.homeworld);
|
||||
return homeworldID === bundle.inputData.planet_id;
|
||||
});
|
||||
}
|
||||
|
||||
return peopleArray.map((person) => {
|
||||
person.id = extractID(person.url);
|
||||
return person;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'people',
|
||||
noun: 'person',
|
||||
display: {
|
||||
label: 'New Person',
|
||||
description: 'Triggers when a new person is added.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
inputFields: [
|
||||
// TRIGGER-BASED dynamic dropdown (legacy pattern)
|
||||
// Uses a separate trigger to fetch choices
|
||||
{
|
||||
key: 'species_id',
|
||||
type: 'integer',
|
||||
label: 'Species (trigger-based)',
|
||||
helpText:
|
||||
'Filter by species. Uses trigger-based dynamic dropdown (dynamic: "species.id.name").',
|
||||
dynamic: 'species.id.name',
|
||||
altersDynamicFields: true,
|
||||
},
|
||||
// PERFORM-BASED dynamic dropdown WITH PAGINATION (new pattern)
|
||||
// Uses a function to fetch choices directly
|
||||
{
|
||||
key: 'planet_id',
|
||||
type: 'integer',
|
||||
label: 'Home Planet (perform-based)',
|
||||
helpText:
|
||||
'Filter by home planet. Uses perform-based dynamic dropdown with pagination support.',
|
||||
resource: 'planet', // Explicit resource linking for perform-based dropdowns
|
||||
choices: {
|
||||
perform: getPlanetChoices,
|
||||
},
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: '1',
|
||||
name: 'Luke Skywalker',
|
||||
birth_year: '19 BBY',
|
||||
eye_color: 'Blue',
|
||||
gender: 'Male',
|
||||
hair_color: 'Blond',
|
||||
height: '172',
|
||||
mass: '77',
|
||||
skin_color: 'Fair',
|
||||
created: '2014-12-09T13:50:51.644000Z',
|
||||
edited: '2014-12-10T13:52:43.172000Z',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
const { extractID } = require('../utils');
|
||||
|
||||
// Fetches a list of records from the endpoint
|
||||
const perform = async (z, bundle) => {
|
||||
const request = {
|
||||
url: 'https://swapi.info/api/species/',
|
||||
params: {},
|
||||
};
|
||||
|
||||
// This API returns things in "pages" of results
|
||||
if (bundle.meta.page) {
|
||||
request.params.page = 1 + bundle.meta.page;
|
||||
}
|
||||
|
||||
const response = await z.request(request);
|
||||
const speciesArray = response.data;
|
||||
return speciesArray.map((species) => {
|
||||
species.id = extractID(species.url);
|
||||
return species;
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'species',
|
||||
noun: 'Species',
|
||||
display: {
|
||||
label: 'List of Species',
|
||||
description:
|
||||
'This is a hidden trigger, and is used in a Dynamic Dropdown of another trigger.',
|
||||
hidden: true,
|
||||
},
|
||||
|
||||
operation: {
|
||||
// Since this is a "hidden" trigger, there aren't any inputFields needed
|
||||
perform,
|
||||
// The folowing is a "hint" to the Zap Editor that this trigger returns data
|
||||
// "in pages", and that the UI should display an option to "load more" to
|
||||
// the human.
|
||||
canPaginate: true,
|
||||
},
|
||||
};
|
||||
12
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/utils.js
vendored
Normal file
12
vendor/zapier-platform/packages/cli/src/generators/templates/dynamic-dropdown/utils.js
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Some handy stuff that's used in various places
|
||||
|
||||
// Extract the numeric ID from a URL like 'https://swapi.dev/api/people/1/'
|
||||
const extractID = (urlString) => {
|
||||
const match = urlString.match(/\/(\d+)\/?$/);
|
||||
if (match) {
|
||||
return parseInt(match[1]);
|
||||
}
|
||||
throw new Error(`ID not found in URL: ${urlString}`);
|
||||
};
|
||||
|
||||
module.exports = { extractID };
|
||||
5
vendor/zapier-platform/packages/cli/src/generators/templates/files/README.md
vendored
Normal file
5
vendor/zapier-platform/packages/cli/src/generators/templates/files/README.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# The "files" Template
|
||||
|
||||
This example has a trigger and a create showcasing file handling.
|
||||
|
||||
Find out more in the docs: https://github.com/zapier/zapier-platform/blob/main/packages/cli/README.md#stashing-files.
|
||||
61
vendor/zapier-platform/packages/cli/src/generators/templates/files/creates/uploadFile_v10.js
vendored
Normal file
61
vendor/zapier-platform/packages/cli/src/generators/templates/files/creates/uploadFile_v10.js
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
const http = require('https'); // require('http') if your URL is not https
|
||||
|
||||
const FormData = require('form-data');
|
||||
|
||||
// Getting a stream directly from http. This only works on core 10+. For core
|
||||
// 9.x compatible code, see uploadFile_v9.js.
|
||||
const makeDownloadStream = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
http.request(url, (res) => {
|
||||
// We can risk missing the first n bytes if we don't pause!
|
||||
res.pause();
|
||||
resolve(res);
|
||||
}).on('error', reject).end();
|
||||
});
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
// bundle.inputData.file will in fact be an URL where the file data can be
|
||||
// downloaded from which we do via a stream
|
||||
const stream = await makeDownloadStream(bundle.inputData.file, z);
|
||||
|
||||
const form = new FormData();
|
||||
form.append('filename', bundle.inputData.filename);
|
||||
form.append('file', stream);
|
||||
|
||||
// All set! Resume the stream
|
||||
stream.resume();
|
||||
|
||||
const response = await z.request({
|
||||
url: 'https://auth-json-server.zapier-staging.com/upload',
|
||||
method: 'POST',
|
||||
body: form,
|
||||
headers: {
|
||||
// DO NOT do auth like this! We do this here because this is a file
|
||||
// uploading example so the auth is not the point.
|
||||
'x-api-key': 'secret',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'uploadFile_v10',
|
||||
noun: 'File',
|
||||
display: {
|
||||
label: 'Upload File v10',
|
||||
description: 'Uploads a file. Only works on zapier-platform-core v10+.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'filename', required: true, type: 'string', label: 'Filename' },
|
||||
{ key: 'file', required: true, type: 'file', label: 'File' },
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: 1,
|
||||
filename: 'example.pdf',
|
||||
file: 'SAMPLE FILE',
|
||||
},
|
||||
},
|
||||
};
|
||||
80
vendor/zapier-platform/packages/cli/src/generators/templates/files/creates/uploadFile_v9.js
vendored
Normal file
80
vendor/zapier-platform/packages/cli/src/generators/templates/files/creates/uploadFile_v9.js
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
const { randomBytes } = require('crypto');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const FormData = require('form-data');
|
||||
|
||||
// Download the HTTP URL to a local temporary file, and make a readable stream
|
||||
// from it. This should work compatibly for all core versions. But if you're
|
||||
// using core v10+, we recommend to use the implementation of uploadFile_v10.js.
|
||||
const makeDownloadStream = async (url) => {
|
||||
// Create a temp file to store the downloaded file
|
||||
const filename = randomBytes(16).toString('hex');
|
||||
const tmpFilePath = path.join(os.tmpdir(), filename);
|
||||
const dest = fs.createWriteStream(tmpFilePath);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
// Download the file to the temp file. When finished, open a readable stream
|
||||
// from that temp file.
|
||||
return new Promise((resolve, reject) => {
|
||||
response.body
|
||||
.pipe(dest)
|
||||
.on('close', () => {
|
||||
const stream = fs.createReadStream(tmpFilePath).on('close', () => {
|
||||
// Delete the file once the stream is read
|
||||
fs.unlinkSync(tmpFilePath);
|
||||
});
|
||||
resolve(stream);
|
||||
})
|
||||
.on('error', reject);
|
||||
});
|
||||
};
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const form = new FormData();
|
||||
|
||||
form.append('filename', bundle.inputData.filename);
|
||||
|
||||
// bundle.inputData.file will in fact be an URL where the file data can be
|
||||
// downloaded from which we do via a stream
|
||||
const stream = await makeDownloadStream(bundle.inputData.file, z);
|
||||
form.append('file', stream);
|
||||
|
||||
const response = await z.request({
|
||||
url: 'https://auth-json-server.zapier-staging.com/upload',
|
||||
method: 'POST',
|
||||
body: form,
|
||||
headers: {
|
||||
// DO NOT do auth like this! We do this here because this is a file
|
||||
// uploading example so the auth is not the point.
|
||||
'x-api-key': 'secret',
|
||||
},
|
||||
});
|
||||
|
||||
return response.json;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'uploadFile_v9',
|
||||
noun: 'File',
|
||||
display: {
|
||||
label: 'Upload File v9',
|
||||
description:
|
||||
'Uploads a file. Compatible with all versions of zapier-platform-core.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'filename', required: true, type: 'string', label: 'Filename' },
|
||||
{ key: 'file', required: true, type: 'file', label: 'File' },
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: 1,
|
||||
filename: 'example.pdf',
|
||||
file: 'SAMPLE FILE',
|
||||
},
|
||||
},
|
||||
};
|
||||
16
vendor/zapier-platform/packages/cli/src/generators/templates/files/hydrators.js
vendored
Normal file
16
vendor/zapier-platform/packages/cli/src/generators/templates/files/hydrators.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
downloadFile: async (z, bundle) => {
|
||||
// Use standard auth to request the file
|
||||
const filePromise = z.request({
|
||||
url: bundle.inputData.url,
|
||||
raw: true,
|
||||
});
|
||||
|
||||
// When `raw` is true, the result of z.request() can be passed to
|
||||
// z.stashFile(). z.stashFile() will upload the file to a Zapier-owned S3
|
||||
// bucket and return a promise of an S3 URL that allows Zapier to get the
|
||||
// file without auth. If your file URL is permanently publicly available,
|
||||
// you may skip z.stashFile() and return that URL directly here.
|
||||
return z.stashFile(filePromise);
|
||||
},
|
||||
};
|
||||
25
vendor/zapier-platform/packages/cli/src/generators/templates/files/index.js
vendored
Normal file
25
vendor/zapier-platform/packages/cli/src/generators/templates/files/index.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const hydrators = require('./hydrators');
|
||||
const newFile = require('./triggers/newFile');
|
||||
const uploadFileV10 = require('./creates/uploadFile_v10');
|
||||
const uploadFileV9 = require('./creates/uploadFile_v9');
|
||||
|
||||
module.exports = {
|
||||
// This is just shorthand to reference the installed dependencies you have.
|
||||
// Zapier will need to know these before we can upload.
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
// Any hydrators go here
|
||||
hydrators,
|
||||
|
||||
// If you want your triggers to show up, you better include it here!
|
||||
triggers: {
|
||||
[newFile.key]: newFile,
|
||||
},
|
||||
|
||||
// If you want your creates to show up, you better include it here!
|
||||
creates: {
|
||||
[uploadFileV10.key]: uploadFileV10,
|
||||
[uploadFileV9.key]: uploadFileV9,
|
||||
},
|
||||
};
|
||||
60
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/creates.test.js
vendored
Normal file
60
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/creates.test.js
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
const CORE_VERSION = zapier.version.split('.').map((s) => parseInt(s));
|
||||
|
||||
const FILE_URL =
|
||||
'https://cdn.zapier.com/storage/files/f6679cf77afeaf6b8426de8d7b9642fc.pdf';
|
||||
|
||||
// This is what you get when doing `curl <FILE_URL> | sha1sum`
|
||||
const EXPECTED_SHA1 = '3cf58b42a0fb1b7cc58de8110096841ece967530';
|
||||
|
||||
describe('uploadFile', () => {
|
||||
test('upload file v10', async () => {
|
||||
if (CORE_VERSION[0] < 10) {
|
||||
console.warn(
|
||||
`skipped because this only works on core v10+ and you're on ${zapier.version}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = {
|
||||
inputData: {
|
||||
filename: 'sample.pdf',
|
||||
|
||||
// in production, this will be an hydration URL to the selected file's data
|
||||
file: FILE_URL,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.creates.uploadFile_v10.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(result.filename).toBe('sample.pdf');
|
||||
expect(result.file.sha1).toBe(EXPECTED_SHA1);
|
||||
});
|
||||
|
||||
test('upload file v9', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
filename: 'sample.pdf',
|
||||
|
||||
// in production, this will be an hydration URL to the selected file's data
|
||||
file: FILE_URL,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await appTester(
|
||||
App.creates.uploadFile_v9.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(result.filename).toBe('sample.pdf');
|
||||
expect(result.file.sha1).toBe(EXPECTED_SHA1);
|
||||
});
|
||||
});
|
||||
27
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/hydrators.test.js
vendored
Normal file
27
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/hydrators.test.js
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('downloadFile', () => {
|
||||
test('download file', async () => {
|
||||
if (!process.env.ZAPIER_DEPLOY_KEY) {
|
||||
console.warn('skipped as ZAPIER_DEPLOY_KEY is not defined');
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = {
|
||||
inputData: {
|
||||
url: 'https://httpbin.zapier-tooling.com/xml',
|
||||
},
|
||||
};
|
||||
|
||||
const url = await appTester(App.hydrators.downloadFile, bundle);
|
||||
expect(url).toContain(
|
||||
'https://zapier-dev-files.s3.amazonaws.com/cli-platform/'
|
||||
);
|
||||
});
|
||||
});
|
||||
26
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/triggers.test.js
vendored
Normal file
26
vendor/zapier-platform/packages/cli/src/generators/templates/files/test/triggers.test.js
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('newFile', () => {
|
||||
test('fetch files', async () => {
|
||||
const bundle = {};
|
||||
const results = await appTester(
|
||||
App.triggers.newFile.operation.perform,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
|
||||
// The 'hydrate|||' thing how Zapier represents dehydrated data
|
||||
const firstFile = results[0];
|
||||
expect(firstFile).toEqual({
|
||||
id: expect.stringMatching(/^https:/),
|
||||
file: expect.stringMatching(/^hydrate\|\|\|/),
|
||||
});
|
||||
});
|
||||
});
|
||||
46
vendor/zapier-platform/packages/cli/src/generators/templates/files/triggers/newFile.js
vendored
Normal file
46
vendor/zapier-platform/packages/cli/src/generators/templates/files/triggers/newFile.js
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const hydrators = require('../hydrators');
|
||||
|
||||
const perform = (z, bundle) => {
|
||||
// In reality you're more likely to get file info from a remote server. Here
|
||||
// we're hard coding some links just to demonstrate.
|
||||
const fileURLs = [
|
||||
'https://httpbin.zapier-tooling.com/image/png',
|
||||
'https://httpbin.zapier-tooling.com/image/jpeg',
|
||||
'https://httpbin.zapier-tooling.com/xml',
|
||||
];
|
||||
|
||||
return fileURLs.map((fileURL) => {
|
||||
const fileInfo = {
|
||||
id: fileURL,
|
||||
|
||||
// Make it possible to get the actual file contents if necessary. No need
|
||||
// to make the request to download files now when the trigger is run.
|
||||
file: z.dehydrateFile(hydrators.downloadFile, { url: fileURL }),
|
||||
};
|
||||
return fileInfo;
|
||||
});
|
||||
};
|
||||
|
||||
// We recommend writing your triggers separate like this and rolling them into
|
||||
// the App definition at the end.
|
||||
module.exports = {
|
||||
key: 'newFile',
|
||||
|
||||
// You'll want to provide some helpful display labels and descriptions
|
||||
// for users. Zapier will put them into the UX.
|
||||
noun: 'File',
|
||||
display: {
|
||||
label: 'New File',
|
||||
description: 'Triggers when a new file is added.',
|
||||
},
|
||||
|
||||
// `operation` is where the business logic goes.
|
||||
operation: {
|
||||
perform,
|
||||
|
||||
sample: {
|
||||
id: 'https://example.com/file.txt',
|
||||
file: 'content',
|
||||
},
|
||||
},
|
||||
};
|
||||
66
vendor/zapier-platform/packages/cli/src/generators/templates/gitignore
vendored
Normal file
66
vendor/zapier-platform/packages/cli/src/generators/templates/gitignore
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# environment variables file
|
||||
.env
|
||||
.environment
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
.pnpm-store/
|
||||
36
vendor/zapier-platform/packages/cli/src/generators/templates/index-esm.template.js
vendored
Normal file
36
vendor/zapier-platform/packages/cli/src/generators/templates/index-esm.template.js
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<% if (hasAuth) { %>
|
||||
import {
|
||||
config as authentication,
|
||||
befores = [],
|
||||
afters = [],
|
||||
} from './authentication';
|
||||
<% } %>
|
||||
|
||||
import packageJson from './package.json' with { type: 'json' };
|
||||
import zapier from '<%= corePackageName %>';
|
||||
|
||||
export default {
|
||||
// This is just shorthand to reference the installed dependencies you have.
|
||||
// Zapier will need to know these before we can upload.
|
||||
version: packageJson.version,
|
||||
platformVersion: zapier.version,
|
||||
|
||||
<% if (hasAuth) { %>
|
||||
authentication,
|
||||
|
||||
beforeRequest: [...befores],
|
||||
|
||||
afterResponse: [...afters],
|
||||
<% } %>
|
||||
|
||||
// If you want your trigger to show up, you better include it here!
|
||||
triggers: {},
|
||||
|
||||
// If you want your searches to show up, you better include it here!
|
||||
searches: {},
|
||||
|
||||
// If you want your creates to show up, you better include it here!
|
||||
creates: {},
|
||||
|
||||
resources: {},
|
||||
};
|
||||
30
vendor/zapier-platform/packages/cli/src/generators/templates/index.template.js
vendored
Normal file
30
vendor/zapier-platform/packages/cli/src/generators/templates/index.template.js
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<% if (hasAuth) { %>
|
||||
const authentication = require('./authentication');
|
||||
const { befores = [], afters = [] } = require('./middleware');
|
||||
<% } %>
|
||||
|
||||
module.exports = {
|
||||
// This is just shorthand to reference the installed dependencies you have.
|
||||
// Zapier will need to know these before we can upload.
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('<%= corePackageName %>').version,
|
||||
|
||||
<% if (hasAuth) { %>
|
||||
authentication,
|
||||
|
||||
beforeRequest: [...befores],
|
||||
|
||||
afterResponse: [...afters],
|
||||
<% } %>
|
||||
|
||||
// If you want your trigger to show up, you better include it here!
|
||||
triggers: {},
|
||||
|
||||
// If you want your searches to show up, you better include it here!
|
||||
searches: {},
|
||||
|
||||
// If you want your creates to show up, you better include it here!
|
||||
creates: {},
|
||||
|
||||
resources: {},
|
||||
};
|
||||
21
vendor/zapier-platform/packages/cli/src/generators/templates/index.template.ts
vendored
Normal file
21
vendor/zapier-platform/packages/cli/src/generators/templates/index.template.ts
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import zapier, { defineApp } from 'zapier-platform-core';
|
||||
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
|
||||
import authentication from './authentication.js';
|
||||
import { befores, afters } from './middleware.js';
|
||||
|
||||
export default defineApp({
|
||||
version: packageJson.version,
|
||||
platformVersion: zapier.version,
|
||||
|
||||
authentication,
|
||||
beforeRequest: [...befores],
|
||||
afterResponse: [...afters],
|
||||
|
||||
// Add your triggers here for them to show up!
|
||||
triggers: {},
|
||||
|
||||
// Add your creates here for them to show up!
|
||||
creates: {},
|
||||
});
|
||||
16
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/README.md
vendored
Normal file
16
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/README.md
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# line-items
|
||||
|
||||
An example integration demonstrating line item support. Line items are fields
|
||||
with a `children` property that represent structured, repeating data — like rows
|
||||
in a spreadsheet or items in an order.
|
||||
|
||||
## Testing with `zapier-platform invoke`
|
||||
|
||||
```bash
|
||||
# Non-interactive with JSON input
|
||||
zapier-platform invoke create order --non-interactive \
|
||||
-i '{"name": "My Order", "line_items": [{"product_name": "Pens", "quantity": "12", "price": "1.50"}]}'
|
||||
|
||||
# Interactive mode — use the line item editing UI
|
||||
zapier-platform invoke create order -i '{"name": "My Order"}'
|
||||
```
|
||||
67
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/creates/order.js
vendored
Normal file
67
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/creates/order.js
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://httpbin.zapier-tooling.com/post',
|
||||
method: 'POST',
|
||||
body: {
|
||||
name: bundle.inputData.name,
|
||||
line_items: bundle.inputData.line_items,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'order',
|
||||
noun: 'Order',
|
||||
display: {
|
||||
label: 'Create Order',
|
||||
description: 'Creates a new order with line items.',
|
||||
},
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'name', required: true, type: 'string', label: 'Order Name' },
|
||||
{
|
||||
key: 'line_items',
|
||||
label: 'Line Items',
|
||||
children: [
|
||||
{
|
||||
key: 'product_name',
|
||||
type: 'string',
|
||||
label: 'Product Name',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'quantity',
|
||||
type: 'integer',
|
||||
label: 'Quantity',
|
||||
required: true,
|
||||
},
|
||||
{ key: 'price', type: 'number', label: 'Unit Price' },
|
||||
],
|
||||
},
|
||||
],
|
||||
perform,
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Stationery Order',
|
||||
line_items: [
|
||||
{ product_name: 'Pens', quantity: 12, price: 1.5 },
|
||||
{ product_name: 'Notebooks', quantity: 3, price: 8.99 },
|
||||
],
|
||||
},
|
||||
outputFields: [
|
||||
{ key: 'id', label: 'ID' },
|
||||
{ key: 'name', label: 'Order Name' },
|
||||
{
|
||||
key: 'line_items',
|
||||
label: 'Line Items',
|
||||
children: [
|
||||
{ key: 'product_name', label: 'Product Name' },
|
||||
{ key: 'quantity', label: 'Quantity' },
|
||||
{ key: 'price', label: 'Unit Price' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
12
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/index.js
vendored
Normal file
12
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/index.js
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
const order = require('./creates/order');
|
||||
|
||||
const App = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
creates: {
|
||||
[order.key]: order,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = App;
|
||||
30
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/test/creates.test.js
vendored
Normal file
30
vendor/zapier-platform/packages/cli/src/generators/templates/line-items/test/creates.test.js
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('creates', () => {
|
||||
test('create order with line items', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
name: 'Test Order',
|
||||
line_items: [
|
||||
{ product_name: 'Pens', quantity: 12, price: 1.5 },
|
||||
{ product_name: 'Notebooks', quantity: 3, price: 8.99 },
|
||||
],
|
||||
},
|
||||
};
|
||||
const result = await appTester(
|
||||
App.creates.order.operation.perform,
|
||||
bundle,
|
||||
);
|
||||
const body = JSON.parse(result.data);
|
||||
expect(body.name).toBe('Test Order');
|
||||
expect(body.line_items).toHaveLength(2);
|
||||
expect(body.line_items[0].product_name).toBe('Pens');
|
||||
expect(body.line_items[1].quantity).toBe(3);
|
||||
});
|
||||
});
|
||||
3
vendor/zapier-platform/packages/cli/src/generators/templates/openai/README.md
vendored
Normal file
3
vendor/zapier-platform/packages/cli/src/generators/templates/openai/README.md
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# OpenAI
|
||||
|
||||
This Zapier integration project is generated by the `zapier-platform init` CLI command. This integration in particular is using the OpenAI API to generate responses to prompts from users. For this integration, there is a `constants.js` file that will allow you to swap out the base URL and version of the API to swap if you are using an OpenAI compatible API to get started.
|
||||
46
vendor/zapier-platform/packages/cli/src/generators/templates/openai/authentication.js
vendored
Normal file
46
vendor/zapier-platform/packages/cli/src/generators/templates/openai/authentication.js
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const { API_URL } = require('./constants');
|
||||
// You want to make a request to an endpoint that is either specifically designed
|
||||
// to test auth, or one that every user will have access to. eg: `/me`.
|
||||
// By returning the entire request object, you have access to the request and
|
||||
// response data for testing purposes. Your connection label can access any data
|
||||
// from the returned response using the `json.` prefix. eg: `{{json.username}}`.
|
||||
const test = (z, bundle) => z.request({ url: `${API_URL}/me` });
|
||||
|
||||
module.exports = {
|
||||
// "custom" is the catch-all auth type. The user supplies some info and Zapier can
|
||||
// make authenticated requests with it
|
||||
type: 'custom',
|
||||
|
||||
// Define any input app's auth requires here. The user will be prompted to enter
|
||||
// this info when they connect their account.
|
||||
fields: [
|
||||
{
|
||||
key: 'api_key',
|
||||
label: 'API Key',
|
||||
required: true,
|
||||
helpText:
|
||||
'Generate an API Key in your [Platform settings page](https://platform.openai.com/api-keys).',
|
||||
},
|
||||
// This field is optional and can be removed if not needed
|
||||
{
|
||||
key: 'organization_id',
|
||||
required: false,
|
||||
label: 'Organization ID',
|
||||
helpText:
|
||||
'**Optional** Only required if your OpenAI account belongs to multiple organizations. If not using OpenAI, this field will be disregarded. If your OpenAI account belongs to multiple organizations, optionally add the [Organization ID](https://platform.openai.com/account/org-settings) that this connection should use. If left blank, your [default organization](https://platform.openai.com/account/api-keys) will be used.',
|
||||
},
|
||||
],
|
||||
|
||||
// The test method allows Zapier to verify that the credentials a user provides
|
||||
// are valid. We'll execute this method whenever a user connects their account for
|
||||
// the first time.
|
||||
test,
|
||||
|
||||
// This template string can access all the data returned from the auth test. If
|
||||
// you return the test object, you'll access the returned data with a label like
|
||||
// `{{json.X}}`. If you return `response.data` from your test, then your label can
|
||||
// be `{{X}}`. This can also be a function that returns a label. That function has
|
||||
// the standard args `(z, bundle)` and data returned from the test can be accessed
|
||||
// in `bundle.inputData.X`.
|
||||
connectionLabel: '{{json.email}}',
|
||||
};
|
||||
10
vendor/zapier-platform/packages/cli/src/generators/templates/openai/constants.js
vendored
Normal file
10
vendor/zapier-platform/packages/cli/src/generators/templates/openai/constants.js
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const BASE_URL = 'https://api.openai.com';
|
||||
const VERSION = 'v1';
|
||||
const API_URL = `${BASE_URL}/${VERSION}`;
|
||||
|
||||
const DEFAULT_MODEL = 'gpt-4o-mini';
|
||||
|
||||
module.exports = {
|
||||
API_URL,
|
||||
DEFAULT_MODEL,
|
||||
};
|
||||
164
vendor/zapier-platform/packages/cli/src/generators/templates/openai/creates/chat_completion.js
vendored
Normal file
164
vendor/zapier-platform/packages/cli/src/generators/templates/openai/creates/chat_completion.js
vendored
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/* eslint-disable camelcase */
|
||||
const { API_URL, DEFAULT_MODEL } = require('../constants');
|
||||
|
||||
const sample = require('../samples/chat.json');
|
||||
|
||||
async function getAdvancedFields(_z, bundle) {
|
||||
if (bundle.inputData.show_advanced === true) {
|
||||
return [
|
||||
{
|
||||
key: 'info_advanced',
|
||||
type: 'copy',
|
||||
helpText:
|
||||
"The following fields are for advanced users and should be used with caution as they may affect performance. In most cases, the default options are sufficient. If you'd like to explore these options further, you can [learn more here](https://help.zapier.com/hc/en-us/articles/22497191078797).",
|
||||
},
|
||||
{
|
||||
key: 'developer_message',
|
||||
label: 'Developer/System Message',
|
||||
type: 'text',
|
||||
helpText:
|
||||
'Instructions to the model that are prioritized ahead of user messages, following [chain of command](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command).',
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
label: 'Temperature',
|
||||
type: 'number',
|
||||
helpText:
|
||||
'Higher values mean the model will take more risks. Try 0.9 for more creative applications, and 0 for ones with a well-defined answer.\n\nUse a decimal between 0 and 1.',
|
||||
},
|
||||
{
|
||||
key: 'max_completion_tokens',
|
||||
label: 'Maximum Length',
|
||||
type: 'integer',
|
||||
helpText: 'The maximum number of tokens for the completion.',
|
||||
},
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
async function perform(z, bundle) {
|
||||
const {
|
||||
user_message,
|
||||
model,
|
||||
files,
|
||||
developer_message,
|
||||
temperature,
|
||||
max_completion_tokens,
|
||||
} = bundle.inputData;
|
||||
|
||||
const developerMessage = {
|
||||
role: 'developer',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: developer_message || 'You are a helpful assistant.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const userMessage = {
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: user_message,
|
||||
},
|
||||
...(files
|
||||
? files.map((file) => ({
|
||||
type: 'image_url',
|
||||
image_url: {
|
||||
url: file,
|
||||
},
|
||||
}))
|
||||
: []),
|
||||
],
|
||||
};
|
||||
|
||||
const messages = [developerMessage, userMessage];
|
||||
|
||||
const response = await z.request({
|
||||
url: `${API_URL}/chat/completions`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
max_completion_tokens,
|
||||
}),
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
key: 'chat_completion',
|
||||
noun: 'Chat',
|
||||
display: {
|
||||
label: 'Chat Completion',
|
||||
description: 'Sends a Chat to OpenAI and generates a Completion.',
|
||||
},
|
||||
operation: {
|
||||
perform,
|
||||
inputFields: [
|
||||
{
|
||||
key: 'info_data_usage',
|
||||
type: 'copy',
|
||||
helpText:
|
||||
"Data sent to OpenAI through this Zap is via an API. Under OpenAI's [API data usage policy](https://openai.com/policies/api-data-usage-policies), OpenAI will not use API-submitted data to train or improve their models unless you explicitly decide to share your data with them for that purpose (such as by opting in). For more information, please review OpenAI's article about [when/how data may be used to improve model performance](https://help.openai.com/en/articles/5722486-how-your-data-is-used-to-improve-model-performance).",
|
||||
},
|
||||
{
|
||||
key: 'user_message',
|
||||
label: 'User Message',
|
||||
type: 'text',
|
||||
helpText:
|
||||
"Instructions that request some output from the model. Similar to messages you'd type in [ChatGPT](https://chatgpt.com) as an end user.",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: 'files',
|
||||
label: 'Images',
|
||||
type: 'file',
|
||||
helpText: 'Images to include along with your message.',
|
||||
list: true,
|
||||
},
|
||||
{
|
||||
key: 'model',
|
||||
label: 'Model',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: DEFAULT_MODEL, // Optional to default to a specific model for most users
|
||||
dynamic: 'list_models.id.name',
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'show_advanced',
|
||||
label: 'Show Advanced Options',
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
altersDynamicFields: true,
|
||||
},
|
||||
getAdvancedFields,
|
||||
],
|
||||
// Rename some of the output fields to be more descriptive for a user
|
||||
outputFields: [
|
||||
{ key: 'id', type: 'string', label: 'Completion ID' },
|
||||
{ key: 'model', type: 'string', label: 'Model' },
|
||||
{
|
||||
key: 'usage__prompt_tokens',
|
||||
type: 'number',
|
||||
label: 'Usage: Prompt Tokens',
|
||||
},
|
||||
{
|
||||
key: 'usage__completion_tokens',
|
||||
type: 'number',
|
||||
label: 'Usage: Completion Tokens',
|
||||
},
|
||||
{
|
||||
key: 'usage__total_tokens',
|
||||
type: 'number',
|
||||
label: 'Usage: Total Tokens',
|
||||
},
|
||||
],
|
||||
sample,
|
||||
},
|
||||
};
|
||||
7
vendor/zapier-platform/packages/cli/src/generators/templates/openai/creates/index.js
vendored
Normal file
7
vendor/zapier-platform/packages/cli/src/generators/templates/openai/creates/index.js
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable camelcase */
|
||||
const chat_completion = require('./chat_completion');
|
||||
|
||||
// If you add a new create, make sure it is exported here to display in the Zapier Editor
|
||||
module.exports = {
|
||||
[chat_completion.key]: chat_completion,
|
||||
};
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
/* eslint-disable camelcase */
|
||||
const list_models = require('./list_models.js');
|
||||
|
||||
// If you add a new Dynamic Dropdown, make sure it is exported here to display in the Zapier Editor
|
||||
module.exports = {
|
||||
[list_models.key]: list_models,
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
const { API_URL } = require('../constants');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({ url: `${API_URL}/models` });
|
||||
|
||||
const responseData = response.data;
|
||||
|
||||
return responseData.data.map((model) => ({
|
||||
id: model.id,
|
||||
name: model.id,
|
||||
}));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'list_models',
|
||||
noun: 'Model',
|
||||
display: {
|
||||
label: 'List of Models',
|
||||
description:
|
||||
'This is a hidden trigger, and is used in a Dynamic Dropdown of another trigger.',
|
||||
hidden: true,
|
||||
},
|
||||
operation: { perform },
|
||||
};
|
||||
33
vendor/zapier-platform/packages/cli/src/generators/templates/openai/index.js
vendored
Normal file
33
vendor/zapier-platform/packages/cli/src/generators/templates/openai/index.js
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* eslint-disable camelcase */
|
||||
const authentication = require('./authentication');
|
||||
const middleware = require('./middleware');
|
||||
const dynamic_dropdowns = require('./dynamic_dropdowns');
|
||||
const creates = require('./creates');
|
||||
|
||||
module.exports = {
|
||||
// This is just shorthand to reference the installed dependencies you have.
|
||||
// Zapier will need to know these before we can upload.
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
authentication,
|
||||
|
||||
beforeRequest: [...middleware.befores],
|
||||
|
||||
afterResponse: [...middleware.afters],
|
||||
|
||||
// If you want your trigger to show up, you better include it here!
|
||||
triggers: {
|
||||
...dynamic_dropdowns,
|
||||
},
|
||||
|
||||
// If you want your searches to show up, you better include it here!
|
||||
searches: {},
|
||||
|
||||
// If you want your creates to show up, you better include it here!
|
||||
creates: {
|
||||
...creates,
|
||||
},
|
||||
|
||||
resources: {},
|
||||
};
|
||||
50
vendor/zapier-platform/packages/cli/src/generators/templates/openai/middleware.js
vendored
Normal file
50
vendor/zapier-platform/packages/cli/src/generators/templates/openai/middleware.js
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* eslint-disable camelcase */
|
||||
// This function runs after every outbound request. You can use it to check for
|
||||
// errors or modify the response. You can have as many as you need. They'll need
|
||||
// to each be registered in your index.js file.
|
||||
const handleBadResponses = (response, z, bundle) => {
|
||||
if (response.data.error) {
|
||||
throw new z.errors.Error(
|
||||
response.data.error.message,
|
||||
response.data.error.code,
|
||||
response.status,
|
||||
);
|
||||
}
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const includeOrgId = (request, z, bundle) => {
|
||||
const { organization_id } = bundle.authData;
|
||||
if (organization_id) {
|
||||
request.headers['OpenAI-Organization'] = organization_id;
|
||||
}
|
||||
return request;
|
||||
};
|
||||
|
||||
// This function runs before every outbound request. You can have as many as you
|
||||
// need. They'll need to each be registered in your index.js file.
|
||||
const includeApiKey = (request, z, bundle) => {
|
||||
const { api_key } = bundle.authData;
|
||||
if (api_key) {
|
||||
// Use these lines to include the API key in the querystring
|
||||
// request.params = request.params || {};
|
||||
// request.params.api_key = api_key;
|
||||
|
||||
// If you want to include the API key in the header:
|
||||
request.headers.Authorization = `Bearer ${api_key}`;
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
const jsonHeaders = (request) => {
|
||||
request.headers['Content-Type'] = 'application/json';
|
||||
request.headers.Accept = 'application/json';
|
||||
return request;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
befores: [includeApiKey, includeOrgId, jsonHeaders],
|
||||
afters: [handleBadResponses],
|
||||
};
|
||||
29
vendor/zapier-platform/packages/cli/src/generators/templates/openai/samples/chat.json
vendored
Normal file
29
vendor/zapier-platform/packages/cli/src/generators/templates/openai/samples/chat.json
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"id": "chatcmpl-123",
|
||||
"object": "chat.completion",
|
||||
"created": 1677652288,
|
||||
"model": "gpt-4o-mini",
|
||||
"system_fingerprint": "fp_44709d6fcb",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "\n\nHello there, how may I assist you today?"
|
||||
},
|
||||
"logprobs": null,
|
||||
"finish_reason": "stop"
|
||||
}
|
||||
],
|
||||
"service_tier": "default",
|
||||
"usage": {
|
||||
"prompt_tokens": 9,
|
||||
"completion_tokens": 12,
|
||||
"total_tokens": 21,
|
||||
"completion_tokens_details": {
|
||||
"reasoning_tokens": 0,
|
||||
"accepted_prediction_tokens": 0,
|
||||
"rejected_prediction_tokens": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/authentication.test.js
vendored
Normal file
66
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/authentication.test.js
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/* globals describe, it, expect */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const App = require('../index');
|
||||
|
||||
describe('custom auth', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('passes authentication and returns json', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
api_key: 'secret',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock successful response
|
||||
const mockResponse = {
|
||||
status: 200,
|
||||
data: {
|
||||
email: 'test@example.com',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock the request client
|
||||
const mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||
const z = {
|
||||
request: mockRequest,
|
||||
};
|
||||
|
||||
const response = await App.authentication.test(z, bundle);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining('/me'),
|
||||
});
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.data).toHaveProperty('email', 'test@example.com');
|
||||
});
|
||||
|
||||
it('fails on bad auth', async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
api_key: 'bad',
|
||||
},
|
||||
};
|
||||
|
||||
// Mock failed response
|
||||
const mockRequest = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Incorrect API key provided'));
|
||||
const z = {
|
||||
request: mockRequest,
|
||||
};
|
||||
|
||||
try {
|
||||
await App.authentication.test(z, bundle);
|
||||
} catch (error) {
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(error.message).toContain('Incorrect API key provided');
|
||||
return;
|
||||
}
|
||||
throw new Error('appTester should have thrown');
|
||||
});
|
||||
});
|
||||
150
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/chat_completion.test.js
vendored
Normal file
150
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/chat_completion.test.js
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/* globals describe, it, expect */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const chatCompletion = require('../creates/chat_completion');
|
||||
const { DEFAULT_MODEL } = require('../constants');
|
||||
|
||||
describe('chat_completion', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('creates a basic chat completion', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
user_message: 'Hello, how are you?',
|
||||
model: DEFAULT_MODEL,
|
||||
},
|
||||
};
|
||||
|
||||
const mockResponse = {
|
||||
data: {
|
||||
id: 'chatcmpl-123',
|
||||
model: DEFAULT_MODEL,
|
||||
usage: {
|
||||
prompt_tokens: 20,
|
||||
completion_tokens: 15,
|
||||
total_tokens: 35,
|
||||
},
|
||||
choices: [
|
||||
{
|
||||
message: {
|
||||
content: 'I am doing well, thank you for asking!',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||
const z = { request: mockRequest };
|
||||
|
||||
const result = await chatCompletion.operation.perform(z, bundle);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining('/chat/completions'),
|
||||
method: 'POST',
|
||||
body: expect.stringContaining(bundle.inputData.user_message),
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
|
||||
it('creates a chat completion with advanced options', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
user_message: 'Write a story',
|
||||
model: DEFAULT_MODEL,
|
||||
developer_message: 'You are a creative writer',
|
||||
temperature: 0.9,
|
||||
max_completion_tokens: 100,
|
||||
},
|
||||
};
|
||||
|
||||
const mockResponse = {
|
||||
data: {
|
||||
id: 'chatcmpl-456',
|
||||
model: DEFAULT_MODEL,
|
||||
usage: {
|
||||
prompt_tokens: 25,
|
||||
completion_tokens: 50,
|
||||
total_tokens: 75,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||
const z = { request: mockRequest };
|
||||
|
||||
const result = await chatCompletion.operation.perform(z, bundle);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining('/chat/completions'),
|
||||
method: 'POST',
|
||||
body: expect.stringMatching(/temperature.*0.9/),
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
|
||||
it('creates a chat completion with image', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
user_message: 'Describe this image',
|
||||
model: DEFAULT_MODEL,
|
||||
files: ['https://example.com/image.jpg'],
|
||||
},
|
||||
};
|
||||
|
||||
const mockResponse = {
|
||||
data: {
|
||||
id: 'chatcmpl-789',
|
||||
model: DEFAULT_MODEL,
|
||||
usage: {
|
||||
prompt_tokens: 30,
|
||||
completion_tokens: 20,
|
||||
total_tokens: 50,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||
const z = { request: mockRequest };
|
||||
|
||||
const result = await chatCompletion.operation.perform(z, bundle);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining('/chat/completions'),
|
||||
method: 'POST',
|
||||
body: expect.stringMatching(/image_url.*example.com/),
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockResponse.data);
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
const bundle = {
|
||||
inputData: {
|
||||
user_message: 'Hello',
|
||||
model: DEFAULT_MODEL,
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Invalid request'));
|
||||
const z = { request: mockRequest };
|
||||
|
||||
try {
|
||||
await chatCompletion.operation.perform(z, bundle);
|
||||
} catch (error) {
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(error.message).toContain('Invalid request');
|
||||
return;
|
||||
}
|
||||
throw new Error('Should have thrown an error');
|
||||
});
|
||||
});
|
||||
51
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/list_models.test.js
vendored
Normal file
51
vendor/zapier-platform/packages/cli/src/generators/templates/openai/test/list_models.test.js
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* globals describe, it, expect */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const listModels = require('../dynamic_dropdowns/list_models');
|
||||
|
||||
describe('list_models', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns formatted list of models', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
data: [{ id: 'gpt-4' }, { id: 'gpt-3.5-turbo' }],
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest.fn().mockResolvedValue(mockResponse);
|
||||
const z = { request: mockRequest };
|
||||
const bundle = {};
|
||||
|
||||
const results = await listModels.operation.perform(z, bundle);
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(mockRequest).toHaveBeenCalledWith({
|
||||
url: expect.stringContaining('/models'),
|
||||
});
|
||||
|
||||
expect(results).toEqual([
|
||||
{ id: 'gpt-4', name: 'gpt-4' },
|
||||
{ id: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('handles API errors', async () => {
|
||||
const mockRequest = jest
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Failed to fetch models'));
|
||||
const z = { request: mockRequest };
|
||||
const bundle = {};
|
||||
|
||||
try {
|
||||
await listModels.operation.perform(z, bundle);
|
||||
} catch (error) {
|
||||
expect(mockRequest).toHaveBeenCalledTimes(1);
|
||||
expect(error.message).toContain('Failed to fetch models');
|
||||
return;
|
||||
}
|
||||
throw new Error('Should have thrown an error');
|
||||
});
|
||||
});
|
||||
6
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/.gitignore
vendored
Normal file
6
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
build
|
||||
docs
|
||||
node_modules
|
||||
*.log
|
||||
.environment
|
||||
lib.env
|
||||
5
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/README.md
vendored
Normal file
5
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/README.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# The "search-or-create" Template
|
||||
|
||||
An example showcasing a Search-or-Create.
|
||||
|
||||

|
||||
34
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/creates/recipe.js
vendored
Normal file
34
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/creates/recipe.js
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
method: 'POST',
|
||||
url: 'https://auth-json-server.zapier-staging.com/recipes',
|
||||
body: {
|
||||
name: bundle.inputData.name,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
|
||||
display: {
|
||||
label: 'Create Recipe',
|
||||
description: 'Creates a recipe.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
inputFields: [
|
||||
{ key: 'name', required: true },
|
||||
{ key: 'directions', required: false },
|
||||
{ key: 'style', required: false },
|
||||
],
|
||||
perform,
|
||||
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
},
|
||||
};
|
||||
35
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/index.js
vendored
Normal file
35
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/index.js
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const RecipeCreate = require('./creates/recipe');
|
||||
const RecipeSearch = require('./searches/recipe');
|
||||
|
||||
const addAuthHeader = (request, z, bundle) => {
|
||||
// Hard-coded auth header just for demo. DON'T do auth like this for your
|
||||
// production app!
|
||||
request.headers['X-Api-Key'] = 'secret';
|
||||
return request;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
|
||||
beforeRequest: [addAuthHeader],
|
||||
|
||||
searches: { [RecipeSearch.key]: RecipeSearch },
|
||||
|
||||
creates: { [RecipeCreate.key]: RecipeCreate },
|
||||
|
||||
searchOrCreates: {
|
||||
[RecipeSearch.key]: {
|
||||
// The key must match the search
|
||||
key: RecipeSearch.key, // same as above
|
||||
display: {
|
||||
// The label shows up when the search-or-create checkbox is checked.
|
||||
// See https://cdn.zappy.app/5fc31d104c6bd0050c44510557b3b98f.png
|
||||
label: 'Find or Create a Recipe',
|
||||
description: 'x', // this is ignored
|
||||
},
|
||||
search: RecipeSearch.key,
|
||||
create: RecipeCreate.key,
|
||||
},
|
||||
},
|
||||
};
|
||||
35
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/searches/recipe.js
vendored
Normal file
35
vendor/zapier-platform/packages/cli/src/generators/templates/search-or-create/searches/recipe.js
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: 'https://auth-json-server.zapier-staging.com/recipes',
|
||||
params: {
|
||||
name: bundle.inputData.name,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
key: 'recipe',
|
||||
noun: 'Recipe',
|
||||
|
||||
display: {
|
||||
label: 'Find Recipe',
|
||||
description: 'Finds a recipe.',
|
||||
},
|
||||
|
||||
operation: {
|
||||
inputFields: [
|
||||
{
|
||||
key: 'name',
|
||||
required: true,
|
||||
helpText: 'Find the Recipe with this name.',
|
||||
},
|
||||
],
|
||||
perform,
|
||||
|
||||
sample: {
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('recipe', () => {
|
||||
test('create with a name', async () => {
|
||||
const bundle = { inputData: { name: 'Pancake' } };
|
||||
const result = await appTester(
|
||||
App.creates.recipe.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(result.id).toBeTruthy();
|
||||
expect(result.name).toBe('Pancake');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('recipe', () => {
|
||||
test('search by name', async () => {
|
||||
const bundle = { inputData: { name: 'name 1' } };
|
||||
const results = await appTester(
|
||||
App.searches.recipe.operation.perform,
|
||||
bundle
|
||||
);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
|
||||
const firstRecipe = results[0];
|
||||
expect(firstRecipe).toMatchObject({
|
||||
id: '1',
|
||||
name: 'name 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
18
vendor/zapier-platform/packages/cli/src/generators/templates/tsconfig.template.json
vendored
Normal file
18
vendor/zapier-platform/packages/cli/src/generators/templates/tsconfig.template.json
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"isolatedModules": true,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"exclude": ["./**/*.test.ts"]
|
||||
}
|
||||
3
vendor/zapier-platform/packages/cli/src/index.js
vendored
Normal file
3
vendor/zapier-platform/packages/cli/src/index.js
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// for now, requiring this file has no real effect
|
||||
|
||||
module.exports = require('@oclif/core');
|
||||
352
vendor/zapier-platform/packages/cli/src/oclif/ZapierBaseCommand.js
vendored
Normal file
352
vendor/zapier-platform/packages/cli/src/oclif/ZapierBaseCommand.js
vendored
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
const { Command } = require('@oclif/core');
|
||||
const colors = require('colors/safe');
|
||||
|
||||
const { startSpinner, endSpinner, formatStyles } = require('../utils/display');
|
||||
const { isValidAppInstall } = require('../utils/misc');
|
||||
const { recordAnalytics } = require('../utils/analytics');
|
||||
|
||||
const { getWritableApp } = require('../utils/api');
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
const { throwForInvalidVersion } = require('../utils/version');
|
||||
|
||||
const DATA_FORMATS = ['json', 'raw'];
|
||||
|
||||
class ZapierBaseCommand extends Command {
|
||||
async run() {
|
||||
this._initPromptModules();
|
||||
await this._parseCommand();
|
||||
|
||||
if (this.flags.debug) {
|
||||
this.debug.enabled = true; // enables this.debug on the command
|
||||
require('debug').enable('zapier:*,oclif:zapier:*'); // enables all further spawned functions, like API
|
||||
}
|
||||
|
||||
this.debug('argv is', this.argv);
|
||||
this.debug('args are', this.args);
|
||||
this.debug('flags are', this.flags);
|
||||
this.debug('------------');
|
||||
|
||||
this.throwForInvalidAppInstall();
|
||||
|
||||
// the following comments are pre-merge, might be out of date:
|
||||
|
||||
// If the `perform` errors out, then we never see the analytics response. We also run the risk of not having the chance to fire them off at all
|
||||
// would need to catch errors in the perform so that they're not thrown until the whole chain finishes
|
||||
// also, would be nice to plug into something a little more base-level so we catch invalid flags. Not super important
|
||||
|
||||
return Promise.all([
|
||||
this._recordAnalytics(),
|
||||
|
||||
this.perform().catch((e) => {
|
||||
this.stopSpinner({ success: false });
|
||||
const errTextLines = [e.message];
|
||||
|
||||
this.debug(e.stack);
|
||||
|
||||
if (!this.flags.debug && !this.flags.invokedFromAnotherCommand) {
|
||||
errTextLines.push(
|
||||
colors.gray('re-run this command with `--debug` for more info'),
|
||||
);
|
||||
}
|
||||
|
||||
this.error(errTextLines.join('\n\n'));
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
get _staticClassReference() {
|
||||
return Object.getPrototypeOf(this).constructor;
|
||||
}
|
||||
|
||||
_initPromptModules() {
|
||||
this._stdoutPrompt = inquirer.prompt;
|
||||
this._stderrPrompt = inquirer.createPromptModule({
|
||||
output: process.stderr,
|
||||
});
|
||||
}
|
||||
|
||||
async _parseCommand() {
|
||||
const { flags, args, argv } = await this.parse(this._staticClassReference);
|
||||
|
||||
this.flags = flags;
|
||||
this.args = args;
|
||||
this.argv = argv;
|
||||
}
|
||||
|
||||
perform() {
|
||||
this.error(`subclass the "perform" method in the "${this.id}" command`);
|
||||
}
|
||||
|
||||
// put ina method so we can disable it easily in tests
|
||||
throwForInvalidAppInstall() {
|
||||
if (this._staticClassReference.skipValidInstallCheck) {
|
||||
return;
|
||||
}
|
||||
const { valid, reason } = isValidAppInstall();
|
||||
if (!valid) {
|
||||
this.error(reason);
|
||||
}
|
||||
}
|
||||
|
||||
// validate that user input looks like a semver version
|
||||
throwForInvalidVersion(version) {
|
||||
return throwForInvalidVersion(version);
|
||||
}
|
||||
|
||||
async getWritableApp() {
|
||||
this.startSpinner('Checking authentication & permissions');
|
||||
const app = await getWritableApp();
|
||||
this.stopSpinner();
|
||||
return app;
|
||||
}
|
||||
|
||||
// UTILS
|
||||
/**
|
||||
* Helps us not have helpful UI messages when the whole output should only be JSON.
|
||||
* @param {...any} message the joined string to print out
|
||||
*/
|
||||
log(...message) {
|
||||
if (this._shouldPrintData()) {
|
||||
super.log(...message);
|
||||
}
|
||||
}
|
||||
|
||||
logJSON(o) {
|
||||
if (typeof o === 'string') {
|
||||
console.log(o);
|
||||
} else {
|
||||
console.log(JSON.stringify(o, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* log data in table form. Headers are `[header, key]`
|
||||
* @param {Object} opts
|
||||
* @param {any[]} opts.rows The data to display
|
||||
* @param {string[][]} opts.headers Array of pairs of the column header and the key in the row that that header applies to
|
||||
* @param {string} opts.emptyMessage a message to print if there's no data. Printed in grey
|
||||
* @param {boolean} opts.formatOverride override format and use this instead
|
||||
*/
|
||||
logTable({
|
||||
rows = [],
|
||||
headers = [],
|
||||
emptyMessage = '',
|
||||
formatOverride = '',
|
||||
hasBorder = true,
|
||||
showHeaders = true,
|
||||
style = undefined,
|
||||
} = {}) {
|
||||
const formatter = formatOverride
|
||||
? formatStyles[formatOverride]
|
||||
: formatStyles[this.flags.format];
|
||||
if (!formatter) {
|
||||
// throwing this error ensures that all commands that call this function take a format flag, since that provides the default
|
||||
this.error(`invalid table format: ${this.flags.format}`);
|
||||
}
|
||||
if (!rows.length && this._shouldPrintData()) {
|
||||
this.log(colors.gray(emptyMessage));
|
||||
} else {
|
||||
// data comes out of the formatter ready to be printed (and it's always in the type to match the format) so we don't need to do anything special with it
|
||||
console.log(formatter(rows, headers, showHeaders, hasBorder, style));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Object} opts options object (as expected for this.prompt())
|
||||
* @returns {string|boolean} Boolean if validation passes, string w/ error message if it doesn't
|
||||
*/
|
||||
_getCustomValidatation(opts) {
|
||||
return (input) => {
|
||||
const validators = {
|
||||
required: (input) =>
|
||||
input.trim() === '' ? 'This field is required.' : true,
|
||||
charLimit: (input, charLimit) =>
|
||||
input.length > charLimit
|
||||
? `Please provide a value ${charLimit} characters or less.`
|
||||
: true,
|
||||
charMinimum: (input, charMinimum) =>
|
||||
input.length < charMinimum
|
||||
? `Please provide a value ${charMinimum} characters or more.`
|
||||
: true,
|
||||
};
|
||||
let aggregateResult = true;
|
||||
|
||||
for (const key in opts) {
|
||||
if (typeof validators[key] === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let individualResult;
|
||||
if (validators[key].length > 1) {
|
||||
individualResult = validators[key](input, opts[key]);
|
||||
} else {
|
||||
individualResult = validators[key](input);
|
||||
}
|
||||
|
||||
if (individualResult !== true) {
|
||||
aggregateResult = individualResult;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return aggregateResult;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* get user input
|
||||
* @param {string} question the question to ask the user
|
||||
* @param {object} opts `inquierer.js` opts ([read more](https://github.com/SBoudrias/Inquirer.js/#question))
|
||||
*/
|
||||
async prompt(question, opts = {}) {
|
||||
if (Object.keys(opts).length) {
|
||||
opts.validate = this._getCustomValidatation(opts);
|
||||
}
|
||||
const prompt = opts.useStderr ? this._stderrPrompt : this._stdoutPrompt;
|
||||
const { ans } = await prompt({
|
||||
type: 'string',
|
||||
...opts,
|
||||
name: 'ans',
|
||||
message: question,
|
||||
});
|
||||
return ans;
|
||||
}
|
||||
|
||||
promptHidden(question, useStderr = false) {
|
||||
return this.prompt(question, {
|
||||
type: 'password',
|
||||
mask: true,
|
||||
useStderr,
|
||||
});
|
||||
}
|
||||
|
||||
confirm(message, defaultAns = false, showCtrlC = false, useStderr = false) {
|
||||
if (showCtrlC) {
|
||||
message += ' (Ctrl-C to cancel)';
|
||||
}
|
||||
return this.prompt(message, {
|
||||
default: defaultAns,
|
||||
type: 'confirm',
|
||||
useStderr,
|
||||
});
|
||||
}
|
||||
|
||||
// see here for options for choices: https://github.com/SBoudrias/Inquirer.js/#question
|
||||
promptWithList(question, choices, additionalOpts) {
|
||||
return this.prompt(question, { type: 'list', choices, ...additionalOpts });
|
||||
}
|
||||
|
||||
/**
|
||||
* should only print to stdout when in a non-data mode
|
||||
*/
|
||||
_shouldPrintData() {
|
||||
return !this.flags.format || !DATA_FORMATS.includes(this.flags.format);
|
||||
}
|
||||
|
||||
startSpinner(message) {
|
||||
startSpinner(message);
|
||||
}
|
||||
|
||||
stopSpinner({ success = true, message = undefined } = {}) {
|
||||
endSpinner(success, message);
|
||||
}
|
||||
|
||||
// pulled from https://github.com/oclif/plugin-help/blob/73bfd5a861e65844a1d6c3a0a9638ee49d16fee8/src/command.ts
|
||||
// renamed to avoid naming collision
|
||||
static zUsage(name) {
|
||||
const formatArg = (arg) => {
|
||||
const argName = arg.name.toUpperCase();
|
||||
return arg.required ? argName : `[${argName}]`;
|
||||
};
|
||||
|
||||
const argv = Object.entries(this.args ?? {}).map(([argName, argValue]) => ({
|
||||
name: argName,
|
||||
...argValue,
|
||||
}));
|
||||
const visibleArgv = argv.filter((arg) => !arg.hidden);
|
||||
|
||||
return ['zapier-platform', name, ...visibleArgv.map(formatArg)].join(' ');
|
||||
}
|
||||
|
||||
// this is fine for now but we'll want to hack into https://github.com/oclif/plugin-help/blob/master/src/command.ts at some point
|
||||
// the presentation is wrapped into the formatting, so it's a little tough to pull out
|
||||
static markdownHelp(name) {
|
||||
const getFormattedArgs = () =>
|
||||
Object.keys(this.args ?? {}).map((argName) => {
|
||||
const arg = this.args[argName];
|
||||
return arg.hidden
|
||||
? null
|
||||
: `* ${arg.required ? '(required) ' : ''}\`${argName}\` | ${
|
||||
arg.description
|
||||
}`;
|
||||
});
|
||||
const getFormattedFlags = () =>
|
||||
Object.entries(this.flags)
|
||||
.map(([flagName, flagValue]) =>
|
||||
flagValue.hidden
|
||||
? null
|
||||
: `* ${flagValue.required ? '(required) ' : ''}\`${
|
||||
flagValue.char ? `-${flagValue.char}, ` : ''
|
||||
}--${flagName}\` |${
|
||||
flagValue.description ? ` ${flagValue.description}` : ''
|
||||
} ${
|
||||
flagValue.options
|
||||
? `One of \`[${flagValue.options.join(' | ')}]\`.`
|
||||
: ''
|
||||
}${
|
||||
flagValue.default
|
||||
? ` Defaults to \`${flagValue.default}\`.`
|
||||
: ''
|
||||
}
|
||||
`.trim(),
|
||||
)
|
||||
.filter(Boolean);
|
||||
|
||||
const descriptionParts = this.description.split('\n\n').filter(Boolean);
|
||||
const blurb = descriptionParts[0];
|
||||
const lengthyDescription = colors.stripColors(
|
||||
descriptionParts.length > 1 ? descriptionParts.slice(1).join('\n\n') : '',
|
||||
);
|
||||
|
||||
return [
|
||||
`## ${name}`,
|
||||
'',
|
||||
`> ${blurb}`,
|
||||
'',
|
||||
`**Usage**: \`${this.zUsage(name)}\``,
|
||||
...(lengthyDescription ? ['', lengthyDescription] : []),
|
||||
...(Object.keys(this.args ?? {}).length
|
||||
? ['', '**Arguments**', ...getFormattedArgs()]
|
||||
: []),
|
||||
...(Object.keys(this.flags ?? {}).length
|
||||
? ['', '**Flags**', ...getFormattedFlags()]
|
||||
: []),
|
||||
...((this.examples ?? []).length
|
||||
? [
|
||||
'',
|
||||
'**Examples**',
|
||||
this.examples.map((e) => `* \`${e}\``).join('\n'),
|
||||
]
|
||||
: []),
|
||||
...((this.aliases ?? []).length
|
||||
? ['', '**Aliases**', this.aliases.map((e) => `* \`${e}\``).join('\n')]
|
||||
: []),
|
||||
]
|
||||
.join('\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
_recordAnalytics() {
|
||||
// if we got here, the command must be valid
|
||||
if (!this.args) {
|
||||
throw new Error('unable to record analytics until args are parsed');
|
||||
}
|
||||
return recordAnalytics(this.id, true, this.args, this.flags);
|
||||
}
|
||||
}
|
||||
|
||||
ZapierBaseCommand.skipValidInstallCheck = false;
|
||||
|
||||
module.exports = ZapierBaseCommand;
|
||||
43
vendor/zapier-platform/packages/cli/src/oclif/buildFlags.js
vendored
Normal file
43
vendor/zapier-platform/packages/cli/src/oclif/buildFlags.js
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
const { Flags } = require('@oclif/core');
|
||||
const { pickBy } = require('lodash');
|
||||
|
||||
const { formatStyles } = require('../utils/display');
|
||||
|
||||
const baseFlags = {
|
||||
debug: Flags.boolean({
|
||||
char: 'd',
|
||||
description: 'Show extra debugging output.',
|
||||
// pull from env?
|
||||
}),
|
||||
format: Flags.string({
|
||||
char: 'f',
|
||||
options: Object.keys(formatStyles),
|
||||
default: 'table',
|
||||
description:
|
||||
'Change the way structured data is presented. If "json" or "raw", you can pipe the output of the command into other tools, such as jq.',
|
||||
}),
|
||||
// Indicates we're calling a command from another command so we know when not
|
||||
// to print duplicate messages.
|
||||
invokedFromAnotherCommand: Flags.boolean({
|
||||
hidden: true,
|
||||
}),
|
||||
};
|
||||
|
||||
// didn't destruture these opts because I want them all on one object to be picked from
|
||||
const defaultOpts = {
|
||||
debug: true,
|
||||
format: false,
|
||||
invokedFromAnotherCommand: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* pass in flag objects, plus whether or not to include debug, format, and
|
||||
* invokedFormatAnotherCommand.
|
||||
*/
|
||||
const buildFlags = ({ commandFlags = {}, opts = {} } = {}) => {
|
||||
const options = { ...defaultOpts, ...opts };
|
||||
const pickedFlags = pickBy(baseFlags, (_v, k) => options[k]);
|
||||
return { ...commandFlags, ...pickedFlags };
|
||||
};
|
||||
|
||||
module.exports = { buildFlags };
|
||||
47
vendor/zapier-platform/packages/cli/src/oclif/commands/analytics.js
vendored
Normal file
47
vendor/zapier-platform/packages/cli/src/oclif/commands/analytics.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const BaseCommand = require('../ZapierBaseCommand');
|
||||
const { Flags } = require('@oclif/core');
|
||||
const { buildFlags } = require('../buildFlags');
|
||||
const {
|
||||
currentAnalyticsMode,
|
||||
modes,
|
||||
setAnalyticsMode,
|
||||
} = require('../../utils/analytics');
|
||||
const colors = require('colors/safe');
|
||||
|
||||
class AnalyticsCommand extends BaseCommand {
|
||||
async perform() {
|
||||
const currentMode = await currentAnalyticsMode();
|
||||
this.log(
|
||||
`The current analytics mode is ${colors.cyan(
|
||||
currentMode,
|
||||
)}. Analytics may be skipped anyway if you've got DISABLE_ZAPIER_ANALYTICS set to a truthy value.`,
|
||||
);
|
||||
|
||||
if (this.flags.mode) {
|
||||
this.log(`\nSetting analytics mode to ${colors.cyan(this.flags.mode)}.`);
|
||||
return setAnalyticsMode(this.flags.mode);
|
||||
} else {
|
||||
this.log(
|
||||
`You can see what data is sent by running \`${colors.yellow(
|
||||
'DEBUG=zapier:analytics zapier someCommand',
|
||||
)}\`.\n\nYou can change your analytics preferences by re-running this command with the \`--mode\` flag.\n\nThe data collected is as generic as we can make it while still getting useful input. No specific information about your filesystem is collected. Your Zapier user id is collected so that we can better debug issues. We will never use this data for any advertising puposes.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnalyticsCommand.flags = buildFlags({
|
||||
commandFlags: {
|
||||
mode: Flags.string({
|
||||
char: 'm',
|
||||
options: Object.keys(modes),
|
||||
description:
|
||||
'Choose how much information to share. Anonymous mode drops the OS type and Zapier user id, but keeps command info. Identifying information is used only for debugging purposes.',
|
||||
}),
|
||||
},
|
||||
});
|
||||
AnalyticsCommand.examples = ['zapier-platform analytics --mode enabled'];
|
||||
AnalyticsCommand.description = `Show the status of the analytics that are collected. Also used to change what is collected.`;
|
||||
AnalyticsCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = AnalyticsCommand;
|
||||
69
vendor/zapier-platform/packages/cli/src/oclif/commands/build.js
vendored
Normal file
69
vendor/zapier-platform/packages/cli/src/oclif/commands/build.js
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
const BaseCommand = require('../ZapierBaseCommand');
|
||||
const { Flags } = require('@oclif/core');
|
||||
const { buildFlags } = require('../buildFlags');
|
||||
const {
|
||||
BUILD_PATH,
|
||||
SOURCE_PATH,
|
||||
CURRENT_APP_FILE,
|
||||
} = require('../../constants');
|
||||
|
||||
const { buildAndOrUpload } = require('../../utils/build');
|
||||
const colors = require('colors/safe');
|
||||
|
||||
class BuildCommand extends BaseCommand {
|
||||
async perform() {
|
||||
const skipDepInstall = this.flags['skip-dep-install'];
|
||||
await buildAndOrUpload(
|
||||
{ build: true },
|
||||
{
|
||||
skipDepInstall,
|
||||
disableDependencyDetection: this.flags['disable-dependency-detection'],
|
||||
skipValidation: this.flags['skip-validation'],
|
||||
},
|
||||
);
|
||||
|
||||
this.log(
|
||||
`\nBuild complete! Created ${BUILD_PATH} and ${SOURCE_PATH}.\n` +
|
||||
`Now you can upload them with the ${colors.bold.underline('zapier-platform upload')} command.`,
|
||||
);
|
||||
|
||||
if (!skipDepInstall) {
|
||||
this.log(
|
||||
`\nTip: Try ${colors.bold.underline('zapier-platform build --skip-dep-install')} for faster builds.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BuildCommand.flags = buildFlags({
|
||||
commandFlags: {
|
||||
'disable-dependency-detection': Flags.boolean({
|
||||
description: `Disable "smart" file inclusion. By default, Zapier only includes files that are required by your entry point (\`index.js\` by default). If you (or your dependencies) require files dynamically (such as with \`require(someVar)\`), then you may see "Cannot find module" errors. Disabling this may make your \`build.zip\` too large. If that's the case, try using the \`includeInBuild\` option in your \`${CURRENT_APP_FILE}\`. See the docs about \`includeInBuild\` for more info.`,
|
||||
}),
|
||||
'skip-dep-install': Flags.boolean({
|
||||
aliases: ['skip-npm-install'],
|
||||
description:
|
||||
'[alias: --skip-npm-install]\nSkips installing a fresh copy of dependencies for shorter build time. Helpful for using yarn, pnpm, or local copies of dependencies.',
|
||||
}),
|
||||
'skip-validation': Flags.boolean({
|
||||
description:
|
||||
"Skips local pre-push validation checks, and remote validation check of the CLI app's schema and AppVersion integrity.",
|
||||
hidden: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
BuildCommand.description = `Build a pushable zip from the current directory.
|
||||
|
||||
This command does the following:
|
||||
|
||||
* Creates a temporary folder
|
||||
* Copies all code into the temporary folder
|
||||
* Adds an entry point: \`zapierwrapper.js\`
|
||||
* Generates and validates app definition.
|
||||
* Detects dependencies via esbuild (optional, on by default)
|
||||
* Zips up all needed \`.js\` files. If you want to include more files, add a "includeInBuild" property (array with strings of regexp paths) to your \`${CURRENT_APP_FILE}\`.
|
||||
* Moves the zip to \`${BUILD_PATH}\` and \`${SOURCE_PATH}\` and deletes the temp folder
|
||||
|
||||
This command is typically followed by \`zapier-platform upload\`.`;
|
||||
|
||||
module.exports = BuildCommand;
|
||||
116
vendor/zapier-platform/packages/cli/src/oclif/commands/cache/clear.js
vendored
Normal file
116
vendor/zapier-platform/packages/cli/src/oclif/commands/cache/clear.js
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
const BaseCommand = require('../../ZapierBaseCommand');
|
||||
const { Args } = require('@oclif/core');
|
||||
const { buildFlags } = require('../../buildFlags');
|
||||
const { listVersions, getWritableApp, callAPI } = require('../../../utils/api');
|
||||
const { cyan } = require('colors/safe');
|
||||
|
||||
class ClearCacheCommand extends BaseCommand {
|
||||
async perform() {
|
||||
this.startSpinner('Fetching versions...');
|
||||
const { versions } = await listVersions();
|
||||
this.stopSpinner();
|
||||
|
||||
const { majorVersion } = this.args;
|
||||
|
||||
let selectedMajorVersion = majorVersion ? Number(majorVersion) : null;
|
||||
if (Number.isNaN(selectedMajorVersion)) {
|
||||
throw new Error(
|
||||
`Invalid major version '${majorVersion}'. Must be a number.`,
|
||||
);
|
||||
}
|
||||
|
||||
const majorVersions = [
|
||||
...new Set(
|
||||
versions.map((appVersion) => Number(appVersion.version.split('.')[0])),
|
||||
),
|
||||
];
|
||||
// Finds the current version in package.json.
|
||||
const currentVersion = await require(`${process.cwd()}/package.json`)
|
||||
.version;
|
||||
|
||||
if (selectedMajorVersion === null) {
|
||||
selectedMajorVersion = await this._promptForMajorVersionSelection(
|
||||
majorVersions,
|
||||
currentVersion,
|
||||
);
|
||||
} else {
|
||||
if (!majorVersions.includes(selectedMajorVersion)) {
|
||||
throw new Error(
|
||||
`This integration does not have any versions on major version '${selectedMajorVersion}'. Valid versions are: ${majorVersions.join(
|
||||
', ',
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!(await this.confirm(
|
||||
`Are you sure you want to clear all cache data for major version '${cyan(
|
||||
selectedMajorVersion,
|
||||
)}'?`,
|
||||
true,
|
||||
))
|
||||
) {
|
||||
this.log('\ncancelled');
|
||||
return;
|
||||
}
|
||||
|
||||
this.startSpinner('Clearing cache');
|
||||
const { id: appId } = await getWritableApp();
|
||||
const url = `/apps/${appId}/major-versions/${selectedMajorVersion}/cache`;
|
||||
|
||||
await callAPI(url, { method: 'DELETE' });
|
||||
|
||||
this.stopSpinner();
|
||||
|
||||
this.log('Ok! Job is queued.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts user to select a major version from a list of major versions.
|
||||
* @returns { majorVersion: int}
|
||||
*/
|
||||
async _promptForMajorVersionSelection(majorVersions, currentVersion) {
|
||||
const currentMajorVersion = Number(currentVersion.split('.')[0]);
|
||||
|
||||
const majorVersionChoices = majorVersions.map((v) => {
|
||||
const isCurrentMajorVersion = currentMajorVersion === v;
|
||||
|
||||
return {
|
||||
name: `${v}${
|
||||
isCurrentMajorVersion ? ` (current version '${currentVersion}')` : ''
|
||||
}`,
|
||||
value: v,
|
||||
};
|
||||
});
|
||||
|
||||
return await this.promptWithList(
|
||||
"Which major version's cache data would you like to delete?",
|
||||
majorVersionChoices,
|
||||
{ default: currentMajorVersion },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ClearCacheCommand.args = {
|
||||
majorVersion: Args.string({
|
||||
description:
|
||||
'(Optional) The cache data will be deleted for this major version. If not provided, you must pick from a list of major versions for this integration.',
|
||||
required: false,
|
||||
}),
|
||||
};
|
||||
ClearCacheCommand.flags = buildFlags();
|
||||
ClearCacheCommand.description = `Clear the cache data for a major version.
|
||||
|
||||
This command clears the cache data for a major version of your integration.
|
||||
The job will be run in the background and may take some time to complete.
|
||||
You can check \`zapier-platform history\` to see the job status.
|
||||
`;
|
||||
ClearCacheCommand.examples = [
|
||||
`zapier-platform cache:clear`,
|
||||
`zapier-platform cache:clear 2`,
|
||||
];
|
||||
ClearCacheCommand.skipValidInstallCheck = true;
|
||||
ClearCacheCommand.hide = true;
|
||||
|
||||
module.exports = ClearCacheCommand;
|
||||
149
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/create.js
vendored
Normal file
149
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/create.js
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
const ZapierBaseCommand = require('../../ZapierBaseCommand');
|
||||
const { Args, Flags } = require('@oclif/core');
|
||||
const { createCanary, listCanaries } = require('../../../utils/api');
|
||||
const { buildFlags } = require('../../buildFlags');
|
||||
|
||||
class CanaryCreateCommand extends ZapierBaseCommand {
|
||||
async perform() {
|
||||
const { versionFrom, versionTo } = this.args;
|
||||
const percent = this.flags.percent;
|
||||
const duration = this.flags.duration;
|
||||
const user = this.flags.user;
|
||||
const accountId = this.flags['account-id'];
|
||||
const forceIncludeAll = this.flags['force-include-all'];
|
||||
|
||||
this.validateVersions(versionFrom, versionTo);
|
||||
this.validatePercent(percent);
|
||||
this.validateDuration(duration);
|
||||
|
||||
const activeCanaries = await listCanaries();
|
||||
if (activeCanaries.objects.length > 0) {
|
||||
const existingCanary = activeCanaries.objects[0];
|
||||
const secondsRemaining =
|
||||
existingCanary.until_timestamp - Math.floor(Date.now() / 1000);
|
||||
this
|
||||
.log(`A canary deployment already exists from version ${existingCanary.from_version} to version ${existingCanary.to_version}, there are ${secondsRemaining} seconds remaining.
|
||||
|
||||
If you would like to stop this canary now, run \`zapier-platform canary:delete ${existingCanary.from_version} ${existingCanary.to_version}\``);
|
||||
return;
|
||||
}
|
||||
|
||||
let createCanaryMessage = `Creating canary deployment
|
||||
- From version: ${versionFrom}
|
||||
- To version: ${versionTo}
|
||||
- Percentage: ${percent}%
|
||||
- Duration: ${duration} seconds`;
|
||||
|
||||
const body = {
|
||||
percent,
|
||||
duration,
|
||||
};
|
||||
|
||||
if (user) {
|
||||
body.user = user;
|
||||
createCanaryMessage += `\n - User: ${user}`;
|
||||
}
|
||||
|
||||
if (accountId) {
|
||||
body.account_id = parseInt(accountId);
|
||||
createCanaryMessage += `\n - Account ID: ${accountId}`;
|
||||
}
|
||||
|
||||
if (forceIncludeAll) {
|
||||
body.force_include_all = true;
|
||||
createCanaryMessage += `\n - Force Include All: true`;
|
||||
}
|
||||
|
||||
this.startSpinner(createCanaryMessage);
|
||||
await createCanary(versionFrom, versionTo, body);
|
||||
|
||||
this.stopSpinner();
|
||||
this.log('Canary deployment created successfully.');
|
||||
}
|
||||
|
||||
validateVersions(versionFrom, versionTo) {
|
||||
this.throwForInvalidVersion(versionFrom);
|
||||
this.throwForInvalidVersion(versionTo);
|
||||
|
||||
if (versionFrom === versionTo) {
|
||||
this.error('`VERSIONFROM` and `VERSIONTO` can not be the same');
|
||||
}
|
||||
}
|
||||
|
||||
validatePercent(percent) {
|
||||
if (isNaN(percent) || percent < 1 || percent > 100) {
|
||||
this.error('`--percent` must be a number between 1 and 100');
|
||||
}
|
||||
}
|
||||
|
||||
validateDuration(duration) {
|
||||
if (isNaN(duration) || duration < 30 || duration > 24 * 60 * 60) {
|
||||
this.error('`--duration` must be a positive number between 30 and 86400');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CanaryCreateCommand.flags = buildFlags({
|
||||
commandFlags: {
|
||||
percent: Flags.integer({
|
||||
char: 'p',
|
||||
description: 'Percent of traffic to route to new version',
|
||||
required: true,
|
||||
}),
|
||||
duration: Flags.integer({
|
||||
char: 'd',
|
||||
description: 'Duration of the canary in seconds',
|
||||
required: true,
|
||||
}),
|
||||
user: Flags.string({
|
||||
char: 'u',
|
||||
description:
|
||||
'Canary this user (email) across all accounts, unless `account-id` is specified.',
|
||||
}),
|
||||
'account-id': Flags.string({
|
||||
char: 'a',
|
||||
description:
|
||||
'The account ID to target. If user is specified, only canary the user within this account. If user is not specified, then this argument is only permitted for Zapier staff.',
|
||||
}),
|
||||
'force-include-all': Flags.boolean({
|
||||
char: 'f',
|
||||
description:
|
||||
'Overrides any default filters the canary system imposes. This argument is only permitted for Zapier staff.',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
CanaryCreateCommand.args = {
|
||||
versionFrom: Args.string({
|
||||
description: 'Version to route traffic from',
|
||||
required: true,
|
||||
}),
|
||||
versionTo: Args.string({
|
||||
description: 'Version to canary traffic to',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
CanaryCreateCommand.description = `Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.
|
||||
|
||||
Only one canary can be active at the same time. You can run \`zapier-platform canary:list\` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using \`zapier-platform canary:delete a.b.c x.y.z\`.
|
||||
|
||||
To canary traffic for a specific user, use the --user flag.
|
||||
|
||||
To canary traffic for an entire account, use the --account-id. Note: this scenario is only permitted for Zapier staff.
|
||||
|
||||
To canary traffic for a specific user within a specific account, use both --user and --account-id flags.
|
||||
|
||||
Note: this is similar to \`zapier-platform migrate\` but different in that this is temporary and will "revert" the changes once the specified duration is expired.
|
||||
|
||||
**Only use this command to canary traffic between non-breaking versions!**`;
|
||||
|
||||
CanaryCreateCommand.examples = [
|
||||
'zapier-platform canary:create 1.0.0 1.1.0 -p 10 -d 3600',
|
||||
'zapier-platform canary:create 2.0.0 2.1.0 --percent 25 --duration 1800 --user user@example.com',
|
||||
'zapier-platform canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345 -u user@example.com',
|
||||
'zapier-platform canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345',
|
||||
];
|
||||
CanaryCreateCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = CanaryCreateCommand;
|
||||
69
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/delete.js
vendored
Normal file
69
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/delete.js
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
const ZapierBaseCommand = require('../../ZapierBaseCommand');
|
||||
const { Args } = require('@oclif/core');
|
||||
const { deleteCanary, listCanaries } = require('../../../utils/api');
|
||||
|
||||
class CanaryDeleteCommand extends ZapierBaseCommand {
|
||||
async perform() {
|
||||
const { versionFrom, versionTo } = this.args;
|
||||
|
||||
this.validateVersions(versionFrom, versionTo);
|
||||
|
||||
const existingCanary = await this.findExistingCanary(
|
||||
versionFrom,
|
||||
versionTo,
|
||||
);
|
||||
if (!existingCanary) {
|
||||
this.log(
|
||||
`There is no active canary from version ${versionFrom} to version ${versionTo}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await this.confirm(
|
||||
`Are you sure you want to delete the canary from ${versionFrom} to ${versionTo}?`,
|
||||
);
|
||||
if (!confirmed) {
|
||||
this.log('Canary deletion cancelled.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.startSpinner(
|
||||
`Deleting active canary from ${versionFrom} to ${versionTo}`,
|
||||
);
|
||||
await deleteCanary(versionFrom, versionTo);
|
||||
this.stopSpinner();
|
||||
this.log('Canary deployment deleted successfully.');
|
||||
}
|
||||
|
||||
async findExistingCanary(versionFrom, versionTo) {
|
||||
const activeCanaries = await listCanaries();
|
||||
return activeCanaries.objects.find(
|
||||
(c) => c.from_version === versionFrom && c.to_version === versionTo,
|
||||
);
|
||||
}
|
||||
|
||||
validateVersions(versionFrom, versionTo) {
|
||||
this.throwForInvalidVersion(versionFrom);
|
||||
this.throwForInvalidVersion(versionTo);
|
||||
|
||||
if (versionFrom === versionTo) {
|
||||
this.error('Versions can not be the same');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CanaryDeleteCommand.args = {
|
||||
versionFrom: Args.string({
|
||||
description: 'Version to route traffic from',
|
||||
required: true,
|
||||
}),
|
||||
versionTo: Args.string({
|
||||
description: 'Version canary traffic is routed to',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
CanaryDeleteCommand.description = 'Delete an active canary deployment';
|
||||
CanaryDeleteCommand.examples = ['zapier-platform canary:delete 1.0.0 1.1.0'];
|
||||
CanaryDeleteCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = CanaryDeleteCommand;
|
||||
40
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/list.js
vendored
Normal file
40
vendor/zapier-platform/packages/cli/src/oclif/commands/canary/list.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const ZapierBaseCommand = require('../../ZapierBaseCommand');
|
||||
const { grey, bold } = require('colors/safe');
|
||||
const { listCanaries } = require('../../../utils/api');
|
||||
const { buildFlags } = require('../../buildFlags');
|
||||
|
||||
class CanaryListCommand extends ZapierBaseCommand {
|
||||
async perform() {
|
||||
const canaries = await listCanaries();
|
||||
|
||||
const formattedCanaries = canaries.objects.map((c) => ({
|
||||
from_version: c.from_version,
|
||||
to_version: c.to_version,
|
||||
percent: c.percent,
|
||||
seconds_remaining: c.until_timestamp - Math.floor(Date.now() / 1000),
|
||||
user: c.user,
|
||||
account_id: c.account_id,
|
||||
}));
|
||||
|
||||
this.log(bold('Active Canaries') + '\n');
|
||||
this.logTable({
|
||||
rows: formattedCanaries,
|
||||
headers: [
|
||||
['From Version', 'from_version'],
|
||||
['To Version', 'to_version'],
|
||||
['Traffic Amount', 'percent'],
|
||||
['Seconds Remaining', 'seconds_remaining'],
|
||||
['User', 'user'],
|
||||
['Account ID', 'account_id'],
|
||||
],
|
||||
emptyMessage: grey(`No active canary deployments found.`),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CanaryListCommand.flags = buildFlags({ opts: { format: true } });
|
||||
CanaryListCommand.description = 'List all active canary deployments';
|
||||
CanaryListCommand.examples = ['zapier-platform canary:list'];
|
||||
CanaryListCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = CanaryListCommand;
|
||||
163
vendor/zapier-platform/packages/cli/src/oclif/commands/convert.js
vendored
Normal file
163
vendor/zapier-platform/packages/cli/src/oclif/commands/convert.js
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
const fs = require('node:fs/promises');
|
||||
|
||||
const { Args, Flags } = require('@oclif/core');
|
||||
|
||||
const BaseCommand = require('../ZapierBaseCommand');
|
||||
const { buildFlags } = require('../buildFlags');
|
||||
|
||||
const { callAPI } = require('../../utils/api');
|
||||
const { convertApp } = require('../../utils/convert');
|
||||
const { isExistingEmptyDir } = require('../../utils/files');
|
||||
const { initApp } = require('../../utils/init');
|
||||
|
||||
const readStream = async (stream) => {
|
||||
const chunks = [];
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return chunks.join('');
|
||||
};
|
||||
|
||||
class ConvertCommand extends BaseCommand {
|
||||
generateCreateFunc(appId, version, json, title, description) {
|
||||
return async (tempAppDir) => {
|
||||
if (json) {
|
||||
const appInfo = {
|
||||
title,
|
||||
description,
|
||||
};
|
||||
|
||||
let parsedDefinition = json;
|
||||
if (parsedDefinition.startsWith('@')) {
|
||||
const filePath = parsedDefinition.substr(1);
|
||||
let definitionStream;
|
||||
if (filePath === '-') {
|
||||
definitionStream = process.stdin;
|
||||
} else {
|
||||
const fd = await fs.open(filePath);
|
||||
definitionStream = fd.createReadStream({ encoding: 'utf8' });
|
||||
}
|
||||
parsedDefinition = await readStream(definitionStream);
|
||||
}
|
||||
parsedDefinition = JSON.parse(parsedDefinition);
|
||||
|
||||
return convertApp(appInfo, parsedDefinition, tempAppDir);
|
||||
}
|
||||
|
||||
// has info about the app, such as title
|
||||
// has a CLI version of the actual app implementation
|
||||
this.throwForInvalidVersion(version);
|
||||
this.startSpinner('Downloading integration from Zapier');
|
||||
try {
|
||||
const [appInfo, versionInfo] = await Promise.all([
|
||||
callAPI(`/apps/${appId}`, undefined, true),
|
||||
callAPI(`/apps/${appId}/versions/${version}`, undefined, true),
|
||||
]);
|
||||
|
||||
if (!versionInfo.definition_override) {
|
||||
this.error(
|
||||
`Integration ${appId} @ ${version} is already a CLI integration and can't be converted. Instead, pick a version that was created using the Visual Builder.`,
|
||||
);
|
||||
}
|
||||
this.stopSpinner();
|
||||
|
||||
return convertApp(appInfo, versionInfo.definition_override, tempAppDir);
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
this.error(
|
||||
`Visual Builder integration ${appId} @ ${version} not found. Double check the integration id and version.`,
|
||||
);
|
||||
}
|
||||
this.error(e.json.errors[0]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async perform() {
|
||||
const { path } = this.args;
|
||||
const {
|
||||
integrationId: appId,
|
||||
version,
|
||||
json,
|
||||
title,
|
||||
description,
|
||||
} = this.flags;
|
||||
|
||||
if (
|
||||
(await isExistingEmptyDir(path)) &&
|
||||
!(await this.confirm(`Path "${path}" is not empty. Continue anyway?`))
|
||||
) {
|
||||
this.exit();
|
||||
}
|
||||
|
||||
if (!appId && !json) {
|
||||
this.error('You must provide either an integrationId or json.');
|
||||
}
|
||||
|
||||
await initApp(
|
||||
path,
|
||||
this.generateCreateFunc(appId, version, json, title, description),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ConvertCommand.args = {
|
||||
path: Args.string({
|
||||
description:
|
||||
'Relative to your current path - IE: `.` for current directory.',
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
|
||||
ConvertCommand.flags = buildFlags({
|
||||
commandFlags: {
|
||||
integrationId: Args.string({
|
||||
char: 'i',
|
||||
description: `To get the integration/app ID, go to "https://developer.zapier.com", click on an integration, and copy the number directly after "/app/" in the URL.`,
|
||||
required: false,
|
||||
dependsOn: ['version'],
|
||||
exclusive: ['definition'],
|
||||
parse: (input) => Number(input),
|
||||
}),
|
||||
version: Flags.string({
|
||||
char: 'v',
|
||||
description:
|
||||
'Convert a specific version. Required when converting a Visual Builder integration.',
|
||||
required: false,
|
||||
dependsOn: ['integrationId'],
|
||||
}),
|
||||
json: Flags.string({
|
||||
char: 'j',
|
||||
description:
|
||||
'The JSON definition to use, as alternative for reading from a Visual Builder integration. Must be a JSON-encoded object. The data can be passed from the command directly like \'{"key": "value"}\', read from a file like @file.json, or read from stdin like @-.',
|
||||
required: false,
|
||||
exclusive: ['integrationId'],
|
||||
}),
|
||||
title: Flags.string({
|
||||
char: 't',
|
||||
description:
|
||||
'The integration title, which will be snake-cased for the package.json name.',
|
||||
required: false,
|
||||
dependsOn: ['json'],
|
||||
}),
|
||||
description: Flags.string({
|
||||
char: 'd',
|
||||
description:
|
||||
'The integration description, which will be used for the package.json description.',
|
||||
required: false,
|
||||
dependsOn: ['json'],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
ConvertCommand.description = `Convert a Visual Builder integration to a CLI integration.
|
||||
|
||||
The resulting CLI integration will be identical to its Visual Builder version and ready to push and use immediately!
|
||||
|
||||
If you re-run this command on an existing directory it will leave existing files alone and not clobber them.
|
||||
|
||||
You'll need to do a \`zapier-platform push\` before the new version is visible in the editor, but otherwise you're good to go.`;
|
||||
|
||||
ConvertCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = ConvertCommand;
|
||||
24
vendor/zapier-platform/packages/cli/src/oclif/commands/delete/integration.js
vendored
Normal file
24
vendor/zapier-platform/packages/cli/src/oclif/commands/delete/integration.js
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const BaseCommand = require('../../ZapierBaseCommand');
|
||||
const { buildFlags } = require('../../buildFlags');
|
||||
const { callAPI } = require('../../../utils/api');
|
||||
|
||||
class DeleteAppCommand extends BaseCommand {
|
||||
async perform() {
|
||||
const { id, title } = await this.getWritableApp();
|
||||
|
||||
this.startSpinner(`Deleting "${title}"`);
|
||||
await callAPI(`/apps/${id}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
this.stopSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
DeleteAppCommand.flags = buildFlags();
|
||||
DeleteAppCommand.description = `Delete your integration (including all versions).
|
||||
|
||||
This only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the \`zapier-platform delete:version\` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.`;
|
||||
DeleteAppCommand.aliases = ['delete:app'];
|
||||
DeleteAppCommand.skipValidInstallCheck = true;
|
||||
|
||||
module.exports = DeleteAppCommand;
|
||||
32
vendor/zapier-platform/packages/cli/src/oclif/commands/delete/version.js
vendored
Normal file
32
vendor/zapier-platform/packages/cli/src/oclif/commands/delete/version.js
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
const BaseCommand = require('../../ZapierBaseCommand');
|
||||
const { Args } = require('@oclif/core');
|
||||
const { buildFlags } = require('../../buildFlags');
|
||||
const { callAPI } = require('../../../utils/api');
|
||||
|
||||
class DeleteVersionCommand extends BaseCommand {
|
||||
async perform() {
|
||||
const { version } = this.args;
|
||||
|
||||
const { id, title } = await this.getWritableApp();
|
||||
|
||||
this.startSpinner(`Deleting version ${version} of app "${title}"`);
|
||||
await callAPI(`/apps/${id}/versions/${version}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
this.stopSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
DeleteVersionCommand.args = {
|
||||
version: Args.string({
|
||||
description: `Specify the version to delete. It must have no users or Zaps.`,
|
||||
required: true,
|
||||
}),
|
||||
};
|
||||
DeleteVersionCommand.flags = buildFlags();
|
||||
DeleteVersionCommand.skipValidInstallCheck = true;
|
||||
DeleteVersionCommand.description = `Delete a specific version of your integration.
|
||||
|
||||
This only works if there are no users or Zaps on that version. You will probably need to have run \`zapier-platform migrate\` and \`zapier-platform deprecate\` before this command will work.`;
|
||||
|
||||
module.exports = DeleteVersionCommand;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue