mirror of
https://github.com/cloudflare/wrangler-action.git
synced 2024-11-21 17:43:23 +01:00
feat: rewrite Wrangler Action in TypeScript
* Removes dependencies such as Docker, decreasing spin-up time * Adds community-requested features, including bulk secrets API utilization from Wrangler * Fixes CI/CD * Adds testing * Improves command implementation * Begins using Node for the Action engine/runner * Openly discusses all changes with the community GitHub Discussions opened and Issues monitored BREAKING CHANGES: * Docker is no longer a dependency * Wrangler v1 is no longer supported Additional related Internal tickets: Major Version Default: https://jira.cfdata.org/browse/DEVX-632 Rewrite Project: DEVX-804,802,800,632
This commit is contained in:
parent
4c10c1822a
commit
edb2a58814
29 changed files with 5153 additions and 424 deletions
157
.github/workflows/deploy.yml
vendored
157
.github/workflows/deploy.yml
vendored
|
@ -1,91 +1,110 @@
|
||||||
on: push
|
name: Wrangler Action Self Testing
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
wrangler_action_self_testing:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Lint
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- name: Checkout Repo
|
||||||
- name: Lint shell script
|
uses: actions/checkout@v3
|
||||||
uses: azohra/shell-linter@v0.3.0
|
|
||||||
with:
|
- name: Setup Node.js
|
||||||
path: "entrypoint.sh"
|
uses: actions/setup-node@v3
|
||||||
build-only:
|
with:
|
||||||
runs-on: ubuntu-latest
|
node-version: "latest"
|
||||||
name: Only build the app
|
cache: "npm"
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
- name: Install Dependencies
|
||||||
- name: Build app
|
run: npm install
|
||||||
|
|
||||||
|
- name: Unit Tests
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
|
- name: Check Formatting
|
||||||
|
run: npm run check
|
||||||
|
|
||||||
|
- name: Build Action
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Only build app
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
|
workingDirectory: "./test/base"
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
workingDirectory: "test"
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
publish: false
|
command: deploy --dry-run
|
||||||
|
|
||||||
|
# START Setup and teardown of Worker Environment Tests
|
||||||
|
- name: Environment support
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
workingDirectory: "./test/environment"
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
environment: dev
|
||||||
secrets: |
|
secrets: |
|
||||||
SECRET1
|
SECRET1
|
||||||
SECRET2
|
SECRET2
|
||||||
preCommands: echo "*** pre commands ***"
|
|
||||||
postCommands: |
|
|
||||||
echo "*** post commands ***"
|
|
||||||
wrangler build
|
|
||||||
echo "******"
|
|
||||||
env:
|
env:
|
||||||
SECRET1: ${{ secrets.SECRET1 }}
|
SECRET1: ${{ secrets.SECRET1 }}
|
||||||
SECRET2: ${{ secrets.SECRET2 }}
|
SECRET2: ${{ secrets.SECRET2 }}
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
- name: Clean up Deployed Environment Worker
|
||||||
name: Publish app
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Publish app
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
environment: "production"
|
|
||||||
workingDirectory: 'test'
|
|
||||||
publish_legacy_credentials:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Publish app with legacy credentials
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Publish app
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
apiKey: ${{ secrets.CLOUDFLARE_API_KEY }}
|
|
||||||
email: ${{ secrets.CLOUDFLARE_EMAIL }}
|
|
||||||
environment: "production"
|
|
||||||
workingDirectory: 'test'
|
|
||||||
publish_hardcoded_wrangler_version:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Publish app with hardcoded Wrangler version
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Publish app
|
|
||||||
uses: ./
|
|
||||||
with:
|
|
||||||
apiKey: ${{ secrets.CLOUDFLARE_API_KEY }}
|
|
||||||
email: ${{ secrets.CLOUDFLARE_EMAIL }}
|
|
||||||
environment: "production"
|
|
||||||
wranglerVersion: '1.5.0'
|
|
||||||
workingDirectory: 'test'
|
|
||||||
publish_secrets:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Publish app with secrets
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Publish app
|
|
||||||
uses: ./
|
uses: ./
|
||||||
with:
|
with:
|
||||||
|
workingDirectory: "./test/base"
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
environment: "production"
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
workingDirectory: "test"
|
command: delete --name wrangler-action-dev-environment-test --force
|
||||||
|
|
||||||
|
# END Setup and teardown of Worker Environment Tests
|
||||||
|
# START Setup and teardown of Workers w/ Secrets Tests
|
||||||
|
- name: Deploy app secrets w/ hardcoded Wrangler v2
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
wranglerVersion: "2.20.0"
|
||||||
|
workingDirectory: "./test/base"
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
secrets: |
|
secrets: |
|
||||||
SECRET1
|
SECRET1
|
||||||
SECRET2
|
SECRET2
|
||||||
preCommands: echo "*** pre command ***"
|
|
||||||
postCommands: |
|
|
||||||
echo "*** post commands ***"
|
|
||||||
echo "******"
|
|
||||||
env:
|
env:
|
||||||
SECRET1: ${{ secrets.SECRET1 }}
|
SECRET1: ${{ secrets.SECRET1 }}
|
||||||
SECRET2: ${{ secrets.SECRET2 }}
|
SECRET2: ${{ secrets.SECRET2 }}
|
||||||
|
|
||||||
|
- name: Health Check Deployed Worker
|
||||||
|
run: node .github/workflows/workerHealthCheck.cjs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Deploy app secrets w/ default version
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
workingDirectory: "./test/base"
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
secrets: |
|
||||||
|
SECRET1
|
||||||
|
SECRET2
|
||||||
|
env:
|
||||||
|
SECRET1: ${{ secrets.SECRET1 }}
|
||||||
|
SECRET2: ${{ secrets.SECRET2 }}
|
||||||
|
|
||||||
|
- name: Health Check Deployed Worker
|
||||||
|
run: node .github/workflows/workerHealthCheck.cjs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Clean Up Deployed Workers
|
||||||
|
uses: ./
|
||||||
|
with:
|
||||||
|
workingDirectory: "./test/base"
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: delete --name wrangler-action-test --force
|
||||||
|
# END Setup and teardown of Workers w/ Secrets Tests
|
||||||
|
|
18
.github/workflows/workerHealthCheck.cjs
vendored
Normal file
18
.github/workflows/workerHealthCheck.cjs
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
|
function workerHealthCheck() {
|
||||||
|
const url =
|
||||||
|
"https://wrangler-action-test.devprod-testing7928.workers.dev/secret-health-check";
|
||||||
|
|
||||||
|
const buffer = execSync(`curl ${url}`);
|
||||||
|
|
||||||
|
const response = buffer.toString();
|
||||||
|
|
||||||
|
response.includes("OK")
|
||||||
|
? console.log(`Status: Worker is up! Secrets: ${response}`)
|
||||||
|
: console.log(`Worker is down!`);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
workerHealthCheck();
|
95
.gitignore
vendored
Normal file
95
.gitignore
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
dist
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
### macOS ###
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
node_modules
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# 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
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# 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/Release
|
||||||
|
|
||||||
|
|
||||||
|
# Moved from ./templates for ignoring all locks in templates
|
||||||
|
templates/**/*-lock.*
|
||||||
|
templates/**/*.lock
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"singleQuote": false,
|
||||||
|
"semi": true,
|
||||||
|
"useTabs": true
|
||||||
|
}
|
64
CHANGELOG.md
64
CHANGELOG.md
|
@ -1,23 +1,61 @@
|
||||||
# 2.0.0 (Breaking update)
|
# Changelog
|
||||||
|
|
||||||
## Additions
|
- [Version 3.0.0](#version-300)
|
||||||
|
- [Version 2.0.0](#version-200)
|
||||||
|
|
||||||
* New `command` input
|
## Version 3.0.0 (Breaking update)
|
||||||
* This allows you to specify the Wrangler command you would like to run.
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- **Rewritten Wrangler Action in TypeScript.**
|
||||||
|
- Bulk secrets API utilization from Wrangler.
|
||||||
|
- Added testing for improved reliability.
|
||||||
|
- Implemented multiline support for the `command` input to allow running multiple Wrangler commands.
|
||||||
|
- Now using Node for the Action engine/runner.
|
||||||
|
- Open discussions with the community on all changes through GitHub Discussions and monitored Issues.
|
||||||
|
|
||||||
|
### Removals
|
||||||
|
|
||||||
|
- Removed Docker as a dependency.
|
||||||
|
- Dropped support for Wrangler v1.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Fixed CI/CD issues.
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
- Wrangler v1 is no longer supported.
|
||||||
|
- Please update to the latest version of Wrangler.
|
||||||
|
- Updated default version of Wrangler to v3.4.0
|
||||||
|
|
||||||
|
### Additional Notes
|
||||||
|
|
||||||
|
- Major Version Default: [DEVX-632](https://jira.cfdata.org/browse/DEVX-632)
|
||||||
|
- Rewrite Project Tickets: [DEVX-804](https://jira.cfdata.org/browse/DEVX-804), [DEVX-802](https://jira.cfdata.org/browse/DEVX-802), [DEVX-800](https://jira.cfdata.org/browse/DEVX-800), [DEVX-632](https://jira.cfdata.org/browse/DEVX-632)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version 2.0.0 (Breaking update)
|
||||||
|
|
||||||
|
### Additions
|
||||||
|
|
||||||
|
- New `command` input
|
||||||
|
- This allows you to specify the Wrangler command you would like to run.
|
||||||
For example, if you want to publish the production version of your Worker you may run `publish --env=production`.
|
For example, if you want to publish the production version of your Worker you may run `publish --env=production`.
|
||||||
* This opens up other possibilities too like publishing a Pages project: `pages publish <directory> --project-name=<name>`.
|
- This opens up other possibilities too like publishing a Pages project: `pages publish <directory> --project-name=<name>`.
|
||||||
* New `accountId` input
|
- New `accountId` input
|
||||||
* This allows you to specify your account ID.
|
- This allows you to specify your account ID.
|
||||||
|
|
||||||
## Removals
|
### Removals
|
||||||
|
|
||||||
* Removed `publish` input (refer to [Breaking changes](#breaking-changes)).
|
- Removed `publish` input (refer to [Breaking changes](#breaking-changes)).
|
||||||
|
|
||||||
## Changes
|
### Changes
|
||||||
|
|
||||||
-- no changes --
|
-- no changes --
|
||||||
|
|
||||||
## __Breaking changes__
|
### Breaking changes
|
||||||
|
|
||||||
* `publish` has been removed.
|
- `publish` has been removed.
|
||||||
* You should instead do `command: publish`.
|
- You should instead do `command:
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
FROM node:16
|
|
||||||
ENV XDG_CONFIG_HOME /github/workspace
|
|
||||||
ENV WRANGLER_HOME /github/workspace
|
|
||||||
|
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
|
42
README.md
42
README.md
|
@ -23,7 +23,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
```
|
```
|
||||||
|
@ -39,7 +39,7 @@ jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy
|
name: Deploy
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
```
|
```
|
||||||
|
@ -51,7 +51,7 @@ jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy
|
name: Deploy
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiKey: ${{ secrets.CF_API_KEY }}
|
apiKey: ${{ secrets.CF_API_KEY }}
|
||||||
email: ${{ secrets.CF_EMAIL }}
|
email: ${{ secrets.CF_EMAIL }}
|
||||||
|
@ -65,10 +65,10 @@ If you need to install a specific version of Wrangler to use for deployment, you
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
wranglerVersion: '1.6.0'
|
wranglerVersion: "1.6.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
Optionally, you can also pass a `workingDirectory` key to the action. This will allow you to specify a subdirectory of the repo to run the Wrangler command from.
|
Optionally, you can also pass a `workingDirectory` key to the action. This will allow you to specify a subdirectory of the repo to run the Wrangler command from.
|
||||||
|
@ -77,24 +77,24 @@ Optionally, you can also pass a `workingDirectory` key to the action. This will
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
workingDirectory: 'subfoldername'
|
workingDirectory: "subfoldername"
|
||||||
```
|
```
|
||||||
|
|
||||||
[Worker secrets](https://developers.cloudflare.com/workers/tooling/wrangler/secrets/) can be optionally passed as a new line deliminated string of names in `secrets`. Each secret name must match an environment variable name specified in the `env` attribute. Creates or replaces the value for the Worker secret using the `wrangler secret put` command.
|
[Worker secrets](https://developers.cloudflare.com/workers/tooling/wrangler/secrets/) can optionally be passed in via `secrets` as a string of names separated by newlines. Each secret name must match the name of an environment variable specified in the `env` field. This creates or replaces the value for the Worker secret using the `wrangler secret put` command.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
secrets: |
|
secrets: |
|
||||||
SECRET1
|
SECRET1
|
||||||
SECRET2
|
SECRET2
|
||||||
env:
|
env:
|
||||||
SECRET1: ${{ secrets.SECRET1 }}
|
SECRET1: ${{ secrets.SECRET1 }}
|
||||||
SECRET2: ${{ secrets.SECRET2 }}
|
SECRET2: ${{ secrets.SECRET2 }}
|
||||||
|
@ -106,7 +106,7 @@ If you need to run additional shell commands before or after your command, you c
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
preCommands: echo "*** pre command ***"
|
preCommands: echo "*** pre command ***"
|
||||||
|
@ -122,7 +122,7 @@ You can use the `command` option to do specific actions such as running `wrangle
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
steps:
|
steps:
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
command: whoami
|
command: whoami
|
||||||
|
@ -147,7 +147,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
```
|
```
|
||||||
|
@ -168,7 +168,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Publish
|
- name: Publish
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
@ -182,7 +182,7 @@ If you would like to deploy your Workers application on a recurring basis – fo
|
||||||
```yaml
|
```yaml
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 * * * *'
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
@ -191,7 +191,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Publish app
|
- name: Publish app
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
```
|
```
|
||||||
|
@ -207,9 +207,9 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
environment:
|
environment:
|
||||||
description: 'Choose an environment to deploy to: <dev|staging|prod>'
|
description: "Choose an environment to deploy to: <dev|staging|prod>"
|
||||||
required: true
|
required: true
|
||||||
default: 'dev'
|
default: "dev"
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -217,7 +217,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Publish app
|
- name: Publish app
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
command: publish --env ${{ github.event.inputs.environment }}
|
command: publish --env ${{ github.event.inputs.environment }}
|
||||||
|
@ -245,7 +245,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Publish app
|
- name: Publish app
|
||||||
uses: cloudflare/wrangler-action@2.0.0
|
uses: cloudflare/wrangler-action@3.0.0
|
||||||
with:
|
with:
|
||||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||||
|
|
19
action.yml
19
action.yml
|
@ -2,17 +2,11 @@ name: "Deploy to Cloudflare Workers with Wrangler"
|
||||||
branding:
|
branding:
|
||||||
icon: "upload-cloud"
|
icon: "upload-cloud"
|
||||||
color: "orange"
|
color: "orange"
|
||||||
description: "Deploy your Cloudflare Workers and Pages projects from GitHub using Wrangler"
|
description: "Deploy your Cloudflare projects from GitHub using Wrangler"
|
||||||
runs:
|
runs:
|
||||||
using: "docker"
|
using: "node16"
|
||||||
image: "Dockerfile"
|
main: "dist/index.js"
|
||||||
inputs:
|
inputs:
|
||||||
apiKey:
|
|
||||||
description: "(Legacy) Your Cloudflare API Key"
|
|
||||||
required: false
|
|
||||||
email:
|
|
||||||
description: "(Legacy) Your Cloudflare Email"
|
|
||||||
required: false
|
|
||||||
apiToken:
|
apiToken:
|
||||||
description: "Your Cloudflare API Token"
|
description: "Your Cloudflare API Token"
|
||||||
required: false
|
required: false
|
||||||
|
@ -29,7 +23,7 @@ inputs:
|
||||||
description: "The version of Wrangler you'd like to use to publish your Workers project"
|
description: "The version of Wrangler you'd like to use to publish your Workers project"
|
||||||
required: false
|
required: false
|
||||||
secrets:
|
secrets:
|
||||||
description: "A new line deliminated string of environment variable names that should be configured as Worker secrets"
|
description: "A string of environment variable names, separated by newlines. These will be bound to your Worker as Secrets and must match the names of environment variables declared in `env` of this workflow."
|
||||||
required: false
|
required: false
|
||||||
preCommands:
|
preCommands:
|
||||||
description: "Commands to execute before publishing the Workers project"
|
description: "Commands to execute before publishing the Workers project"
|
||||||
|
@ -38,5 +32,8 @@ inputs:
|
||||||
description: "Commands to execute after publishing the Workers project"
|
description: "Commands to execute after publishing the Workers project"
|
||||||
required: false
|
required: false
|
||||||
command:
|
command:
|
||||||
description: "The Wrangler command you wish to run. For example: \"publish\" - this will publish your Worker"
|
description: 'The Wrangler command (along with any arguments) you wish to run. Multiple Wrangler commands can be run by separating each command with a newline. Defaults to `"deploy"`.'
|
||||||
|
required: false
|
||||||
|
vars:
|
||||||
|
description: "A string of environment variable names, separated by newlines. These will be bound to your Worker using the values of matching environment variables declared in `env` of this workflow."
|
||||||
required: false
|
required: false
|
161
entrypoint.sh
161
entrypoint.sh
|
@ -1,161 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
export HOME="/github/workspace"
|
|
||||||
export WRANGLER_HOME="/github/workspace"
|
|
||||||
|
|
||||||
mkdir -p "$HOME/.wrangler"
|
|
||||||
chmod -R 770 "$HOME/.wrangler"
|
|
||||||
|
|
||||||
export API_CREDENTIALS=""
|
|
||||||
|
|
||||||
# Used to execute any specified pre and post commands
|
|
||||||
execute_commands() {
|
|
||||||
echo "$ Running: $1"
|
|
||||||
COMMANDS=$1
|
|
||||||
while IFS= read -r COMMAND; do
|
|
||||||
CHUNKS=()
|
|
||||||
|
|
||||||
for CHUNK in $COMMAND; do
|
|
||||||
CHUNKS+=("$CHUNK")
|
|
||||||
done
|
|
||||||
|
|
||||||
eval "${CHUNKS[@]}"
|
|
||||||
|
|
||||||
CHUNKS=()
|
|
||||||
done <<< "$COMMANDS"
|
|
||||||
}
|
|
||||||
|
|
||||||
secret_not_found() {
|
|
||||||
echo "::error::Specified secret \"$1\" not found in environment variables."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
WRANGLER_VERSION=2
|
|
||||||
|
|
||||||
# If no Wrangler version is specified install v2.
|
|
||||||
if [ -z "$INPUT_WRANGLERVERSION" ]; then
|
|
||||||
npm i -g wrangler
|
|
||||||
|
|
||||||
# If Wrangler version starts with 1 then install wrangler v1
|
|
||||||
elif [[ "$INPUT_WRANGLERVERSION" == 1* ]]; then
|
|
||||||
npm i -g "@cloudflare/wrangler@$INPUT_WRANGLERVERSION"
|
|
||||||
WRANGLER_VERSION=1
|
|
||||||
|
|
||||||
# Else install Wrangler 2
|
|
||||||
else
|
|
||||||
npm i -g "wrangler@$INPUT_WRANGLERVERSION"
|
|
||||||
WRANGLER_VERSION=2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If an API token is detected as input
|
|
||||||
if [ -n "$INPUT_APITOKEN" ]; then
|
|
||||||
|
|
||||||
# Wrangler v1 uses CF_API_TOKEN but v2 uses CLOUDFLARE_API_TOKEN
|
|
||||||
if [ $WRANGLER_VERSION == 1 ]; then
|
|
||||||
export CF_API_TOKEN="$INPUT_APITOKEN"
|
|
||||||
else
|
|
||||||
export CLOUDFLARE_API_TOKEN="$INPUT_APITOKEN"
|
|
||||||
fi
|
|
||||||
|
|
||||||
export API_CREDENTIALS="API Token"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If an API key and email are detected as input
|
|
||||||
if [ -n "$INPUT_APIKEY" ] && [ -n "$INPUT_EMAIL" ]; then
|
|
||||||
|
|
||||||
# Wrangler v1 uses CF_ but v2 uses CLOUDFLARE_
|
|
||||||
if [ $WRANGLER_VERSION == 1 ]; then
|
|
||||||
export CF_EMAIL="$INPUT_EMAIL"
|
|
||||||
export CF_API_KEY="$INPUT_APIKEY"
|
|
||||||
else
|
|
||||||
echo "::error::Wrangler v2 does not support using the API Key. You should instead use an API token."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
export API_CREDENTIALS="Email and API Key"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$INPUT_ACCOUNTID" ]; then
|
|
||||||
|
|
||||||
if [ $WRANGLER_VERSION == 1 ]; then
|
|
||||||
export CF_ACCOUNT_ID="$INPUT_ACCOUNTID"
|
|
||||||
else
|
|
||||||
export CLOUDFLARE_ACCOUNT_ID="$INPUT_ACCOUNTID"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$INPUT_APIKEY" ] && [ -z "$INPUT_EMAIL" ]
|
|
||||||
then
|
|
||||||
echo "Provided an API key without an email for authentication. Please pass in 'apiKey' and 'email' to the action."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$INPUT_APIKEY" ] && [ -n "$INPUT_EMAIL" ]
|
|
||||||
then
|
|
||||||
echo "Provided an email without an API key for authentication. Please pass in 'apiKey' and 'email' to the action."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$API_CREDENTIALS" ]
|
|
||||||
then
|
|
||||||
>&2 echo "Unable to find authentication details. Please pass in an 'apiToken' as an input to the action, or a legacy 'apiKey' and 'email'."
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "Using $API_CREDENTIALS authentication"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If a working directory is detected as input
|
|
||||||
if [ -n "$INPUT_WORKINGDIRECTORY" ]
|
|
||||||
then
|
|
||||||
cd "$INPUT_WORKINGDIRECTORY"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If precommands is detected as input
|
|
||||||
if [ -n "$INPUT_PRECOMMANDS" ]
|
|
||||||
then
|
|
||||||
execute_commands "$INPUT_PRECOMMANDS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we have secrets, set them
|
|
||||||
for SECRET in $INPUT_SECRETS; do
|
|
||||||
VALUE=$(printenv "$SECRET") || secret_not_found "$SECRET"
|
|
||||||
|
|
||||||
if [ -z "$INPUT_ENVIRONMENT" ]; then
|
|
||||||
echo "$VALUE" | wrangler secret put "$SECRET"
|
|
||||||
else
|
|
||||||
echo "$VALUE" | wrangler secret put "$SECRET" --env "$INPUT_ENVIRONMENT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# If there's no input command then default to publish otherwise run it
|
|
||||||
if [ -z "$INPUT_COMMAND" ]; then
|
|
||||||
echo "::notice:: No command was provided, defaulting to 'publish'"
|
|
||||||
|
|
||||||
if [ -z "$INPUT_ENVIRONMENT" ]; then
|
|
||||||
wrangler publish
|
|
||||||
else
|
|
||||||
wrangler publish --env "$INPUT_ENVIRONMENT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
if [ -n "$INPUT_ENVIRONMENT" ]; then
|
|
||||||
echo "::notice::Since you have specified an environment you need to make sure to pass in '--env $INPUT_ENVIRONMENT' to your command."
|
|
||||||
fi
|
|
||||||
|
|
||||||
execute_commands "wrangler $INPUT_COMMAND"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If postcommands is detected as input
|
|
||||||
if [ -n "$INPUT_POSTCOMMANDS" ]
|
|
||||||
then
|
|
||||||
execute_commands "$INPUT_POSTCOMMANDS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If a working directory is detected as input, revert to the
|
|
||||||
# original directory before continuing with the workflow
|
|
||||||
if [ -n "$INPUT_WORKINGDIRECTORY" ]
|
|
||||||
then
|
|
||||||
cd $HOME
|
|
||||||
fi
|
|
4368
package-lock.json
generated
Normal file
4368
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
42
package.json
Normal file
42
package.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "wrangler-action",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"description": "GitHub Action to use [Wrangler](https://developers.cloudflare.com/workers/cli-wrangler/).",
|
||||||
|
"author": "wrangler@cloudflare.com",
|
||||||
|
"license": "MIT OR Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/cloudflare/wrangler-action/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/cloudflare/wrangler-action#readme",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/cloudflare/wrangler-action.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"script",
|
||||||
|
"cli",
|
||||||
|
"serverless",
|
||||||
|
"cloudflare",
|
||||||
|
"cloudflare-workers",
|
||||||
|
"typescript"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "vitest",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"check": "prettier --check ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": "^1.10.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/workers-types": "^4.20230710.1",
|
||||||
|
"@types/node": "^20.4.2",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
|
"vitest": "^0.33.0",
|
||||||
|
"@changesets/cli": "^2.26.2"
|
||||||
|
}
|
||||||
|
}
|
64
src/index.test.ts
Normal file
64
src/index.test.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { expect, test, describe } from "vitest";
|
||||||
|
import { checkWorkingDirectory, getNpxCmd, semverCompare } from "./index";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
WRANGLER_VERSION: "mockVersion",
|
||||||
|
secrets: ["mockSercret", "mockSecretAgain"],
|
||||||
|
workingDirectory: "./mockWorkingDirectory",
|
||||||
|
CLOUDFLARE_API_TOKEN: "mockAPIToken",
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: "mockAccountID",
|
||||||
|
ENVIRONMENT: undefined,
|
||||||
|
VARS: ["mockVar", "mockVarAgain"],
|
||||||
|
COMMANDS: ["mockCommand", "mockCommandAgain"],
|
||||||
|
};
|
||||||
|
|
||||||
|
test("getNpxCmd ", async () => {
|
||||||
|
process.env.RUNNER_OS = "Windows";
|
||||||
|
expect(getNpxCmd()).toBe("npx.cmd");
|
||||||
|
|
||||||
|
process.env.RUNNER_OS = "Mac";
|
||||||
|
expect(getNpxCmd()).toBe("npx");
|
||||||
|
|
||||||
|
process.env.RUNNER_OS = "Linux";
|
||||||
|
expect(getNpxCmd()).toBe("npx");
|
||||||
|
|
||||||
|
delete process.env.RUNNER_OS;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("semverCompare", () => {
|
||||||
|
test("should return false if the second argument is equal to the first argument", () => {
|
||||||
|
const isVersion1LessThanVersion2 = semverCompare("1.2.3", "1.2.3");
|
||||||
|
|
||||||
|
expect(isVersion1LessThanVersion2).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return true if the first argument is less than the second argument", () => {
|
||||||
|
const isVersion1LessThanVersion2 = semverCompare("1.2.2", "1.2.3");
|
||||||
|
|
||||||
|
expect(isVersion1LessThanVersion2).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checkWorkingDirectory", () => {
|
||||||
|
test("should return the normalized path if the directory exists", () => {
|
||||||
|
const normalizedPath = checkWorkingDirectory(".");
|
||||||
|
expect(normalizedPath).toBe(path.normalize("."));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should fail if the directory does not exist", () => {
|
||||||
|
try {
|
||||||
|
checkWorkingDirectory("/does/not/exist");
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatchInlineSnapshot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should fail if an error occurs while checking/creating the directory", () => {
|
||||||
|
try {
|
||||||
|
checkWorkingDirectory("/does/not/exist");
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.message).toMatchInlineSnapshot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
285
src/index.ts
Executable file
285
src/index.ts
Executable file
|
@ -0,0 +1,285 @@
|
||||||
|
import {
|
||||||
|
getInput,
|
||||||
|
getMultilineInput,
|
||||||
|
info,
|
||||||
|
setFailed,
|
||||||
|
endGroup,
|
||||||
|
startGroup,
|
||||||
|
} from "@actions/core";
|
||||||
|
import { execSync, exec } from "node:child_process";
|
||||||
|
import { existsSync } from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import * as util from "node:util";
|
||||||
|
const execAsync = util.promisify(exec);
|
||||||
|
|
||||||
|
const DEFAULT_WRANGLER_VERSION = "3.4.0";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A configuration object that contains all the inputs & immutable state for the action.
|
||||||
|
*/
|
||||||
|
const config = {
|
||||||
|
WRANGLER_VERSION: getInput("wranglerVersion") || DEFAULT_WRANGLER_VERSION,
|
||||||
|
secrets: getMultilineInput("secrets"),
|
||||||
|
workingDirectory: checkWorkingDirectory(getInput("workingDirectory")),
|
||||||
|
CLOUDFLARE_API_TOKEN: getInput("apiToken"),
|
||||||
|
CLOUDFLARE_ACCOUNT_ID: getInput("accountId"),
|
||||||
|
ENVIRONMENT: getInput("environment"),
|
||||||
|
VARS: getMultilineInput("vars"),
|
||||||
|
COMMANDS: getMultilineInput("command"),
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function getNpxCmd() {
|
||||||
|
return process.env.RUNNER_OS === "Windows" ? "npx.cmd" : "npx";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function to compare two semver versions. If the second arg is greater than the first arg, it returns true.
|
||||||
|
*/
|
||||||
|
function semverCompare(version1: string, version2: string) {
|
||||||
|
if (version2 === "latest") return true;
|
||||||
|
|
||||||
|
const version1Parts = version1.split(".");
|
||||||
|
const version2Parts = version2.split(".");
|
||||||
|
|
||||||
|
for (const version1Part of version1Parts) {
|
||||||
|
const version2Part = version2Parts.shift();
|
||||||
|
|
||||||
|
if (version1Part !== version2Part && version2Part) {
|
||||||
|
return version1Part < version2Part ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
installWrangler();
|
||||||
|
authenticationSetup();
|
||||||
|
await execCommands(getMultilineInput("preCommands"), "Pre");
|
||||||
|
await uploadSecrets();
|
||||||
|
await wranglerCommands();
|
||||||
|
await execCommands(getMultilineInput("postCommands"), "Post");
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWorkingDirectory(workingDirectory = ".") {
|
||||||
|
try {
|
||||||
|
const normalizedPath = path.normalize(workingDirectory);
|
||||||
|
if (existsSync(normalizedPath)) {
|
||||||
|
return normalizedPath;
|
||||||
|
} else {
|
||||||
|
setFailed(`🚨 Directory ${workingDirectory} does not exist.`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setFailed(
|
||||||
|
`🚨 While checking/creating directory ${workingDirectory} received ${error}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function installWrangler() {
|
||||||
|
if (config["WRANGLER_VERSION"].startsWith("1")) {
|
||||||
|
setFailed(
|
||||||
|
`🚨 Wrangler v1 is no longer supported by this action. Please use major version 2 or greater`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
startGroup("📥 Installing Wrangler");
|
||||||
|
const command = `npm install wrangler@${config["WRANGLER_VERSION"]}`;
|
||||||
|
info(`Running command: ${command}`);
|
||||||
|
execSync(command, { cwd: config["workingDirectory"], env: process.env });
|
||||||
|
info(`✅ Wrangler installed`);
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
function authenticationSetup() {
|
||||||
|
process.env.CLOUDFLARE_API_TOKEN = config["CLOUDFLARE_API_TOKEN"];
|
||||||
|
process.env.CLOUDFLARE_ACCOUNT_ID = config["CLOUDFLARE_ACCOUNT_ID"];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function execCommands(commands: string[], cmdType: string) {
|
||||||
|
if (!commands.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startGroup(`🚀 ${cmdType} Commands Group`);
|
||||||
|
|
||||||
|
const arrPromises = commands.map(async (command) => {
|
||||||
|
const cmd = command.startsWith("wrangler")
|
||||||
|
? `${getNpxCmd()} ${command}`
|
||||||
|
: command;
|
||||||
|
|
||||||
|
info(`🚀 Executing command: ${cmd}`);
|
||||||
|
|
||||||
|
return await execAsync(cmd, {
|
||||||
|
cwd: config["workingDirectory"],
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(arrPromises);
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function to get the secret from the environment variables.
|
||||||
|
*/
|
||||||
|
function getSecret(secret: string) {
|
||||||
|
if (!secret) {
|
||||||
|
setFailed("No secret provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = process.env[secret];
|
||||||
|
if (!value) {
|
||||||
|
setFailed(`Secret ${secret} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function legacyUploadSecrets(
|
||||||
|
secrets: string[],
|
||||||
|
environment?: string,
|
||||||
|
workingDirectory?: string,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const arrPromises = secrets
|
||||||
|
.map((secret) => {
|
||||||
|
const command = `echo ${getSecret(
|
||||||
|
secret,
|
||||||
|
)} | ${getNpxCmd()} wrangler secret put ${secret}`;
|
||||||
|
return environment ? command.concat(`--env ${environment}`) : command;
|
||||||
|
})
|
||||||
|
.map(
|
||||||
|
async (command) =>
|
||||||
|
await execAsync(command, {
|
||||||
|
cwd: workingDirectory,
|
||||||
|
env: process.env,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(arrPromises);
|
||||||
|
} catch {
|
||||||
|
setFailed(`🚨 Error uploading secrets`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadSecrets() {
|
||||||
|
const secrets: string[] = config["secrets"];
|
||||||
|
const environment = config["ENVIRONMENT"];
|
||||||
|
const workingDirectory = config["workingDirectory"];
|
||||||
|
|
||||||
|
if (!secrets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
startGroup("🔑 Uploading Secrets");
|
||||||
|
|
||||||
|
if (semverCompare(config["WRANGLER_VERSION"], "3.4.0"))
|
||||||
|
return legacyUploadSecrets(secrets, environment, workingDirectory);
|
||||||
|
|
||||||
|
const secretObj = secrets.reduce((acc: any, secret: string) => {
|
||||||
|
acc[secret] = getSecret(secret);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const environmentSuffix = !environment.length
|
||||||
|
? ""
|
||||||
|
: ` --env ${environment}`;
|
||||||
|
|
||||||
|
const secretCmd = `echo "${JSON.stringify(secretObj).replaceAll(
|
||||||
|
'"',
|
||||||
|
'\\"',
|
||||||
|
)}" | ${getNpxCmd()} wrangler secret:bulk ${environmentSuffix}`;
|
||||||
|
|
||||||
|
execSync(secretCmd, {
|
||||||
|
cwd: workingDirectory,
|
||||||
|
env: process.env,
|
||||||
|
stdio: "ignore",
|
||||||
|
});
|
||||||
|
info(`✅ Uploaded secrets`);
|
||||||
|
} catch {
|
||||||
|
setFailed(`🚨 Error uploading secrets`);
|
||||||
|
} finally {
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVarArgs() {
|
||||||
|
const vars = config["VARS"];
|
||||||
|
const envVarArray = vars.map((envVar: string) => {
|
||||||
|
if (process.env[envVar] && process.env[envVar]?.length !== 0) {
|
||||||
|
return `${envVar}:${process.env[envVar]!}`;
|
||||||
|
} else {
|
||||||
|
setFailed(`🚨 ${envVar} not found in variables.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return envVarArray.length > 0 ? `--var ${envVarArray.join(" ").trim()}` : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaultCommandBehavior() {
|
||||||
|
const environment = config["ENVIRONMENT"];
|
||||||
|
const wranglerVersion = config["WRANGLER_VERSION"];
|
||||||
|
const workingDirectory = config["workingDirectory"];
|
||||||
|
|
||||||
|
const deployCommand = semverCompare("2.20.0", wranglerVersion)
|
||||||
|
? "deploy"
|
||||||
|
: "publish";
|
||||||
|
|
||||||
|
info(`📌 No Wrangler commands were provided, executing default deployment.`);
|
||||||
|
|
||||||
|
if (environment.length === 0) {
|
||||||
|
execSync(
|
||||||
|
`${getNpxCmd()} wrangler ${deployCommand} ${getVarArgs()}`.trim(),
|
||||||
|
{
|
||||||
|
cwd: workingDirectory,
|
||||||
|
env: process.env,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
execSync(
|
||||||
|
`${getNpxCmd()} wrangler ${deployCommand} --env ${environment} ${getVarArgs()}`.trim(),
|
||||||
|
{ cwd: workingDirectory, env: process.env },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function wranglerCommands() {
|
||||||
|
const commands = config["COMMANDS"];
|
||||||
|
const environment = config["ENVIRONMENT"];
|
||||||
|
|
||||||
|
if (!commands.length) {
|
||||||
|
defaultCommandBehavior();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
startGroup("🚀 Executing Wrangler Commands");
|
||||||
|
|
||||||
|
const arrPromises = commands.map(async (command) => {
|
||||||
|
if (environment.length > 0 && !command.includes(`--env ${environment}`)) {
|
||||||
|
command.concat(`--env ${environment}`);
|
||||||
|
}
|
||||||
|
const result = await execAsync(
|
||||||
|
`${getNpxCmd()} wrangler ${command} ${getVarArgs()}`,
|
||||||
|
{
|
||||||
|
cwd: config["workingDirectory"],
|
||||||
|
env: process.env,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
info(result.stdout);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
await Promise.all(arrPromises);
|
||||||
|
endGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(() => setFailed("🚨 Action failed"));
|
||||||
|
|
||||||
|
export {
|
||||||
|
wranglerCommands,
|
||||||
|
execCommands,
|
||||||
|
uploadSecrets,
|
||||||
|
authenticationSetup,
|
||||||
|
installWrangler,
|
||||||
|
checkWorkingDirectory,
|
||||||
|
getNpxCmd,
|
||||||
|
semverCompare,
|
||||||
|
};
|
1
test/.gitignore
vendored
1
test/.gitignore
vendored
|
@ -1 +0,0 @@
|
||||||
dist
|
|
26
test/base/index.ts
Normal file
26
test/base/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
type Env = {
|
||||||
|
SECRET1?: string;
|
||||||
|
SECRET2?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetch(request: Request, env: Env) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
if (url.pathname === "/secret-health-check") {
|
||||||
|
const { SECRET1, SECRET2 } = env;
|
||||||
|
|
||||||
|
if (SECRET1 !== "SECRET_1_VALUE" || SECRET2 !== "SECRET_2_VALUE") {
|
||||||
|
throw new Error("SECRET1 or SECRET2 is not defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response("OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
return Response.json({
|
||||||
|
...request,
|
||||||
|
headers: Object.fromEntries(request.headers),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
3
test/base/package.json
Normal file
3
test/base/package.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"name": "wrangler-action-test"
|
||||||
|
}
|
4
test/base/public/index.html
Normal file
4
test/base/public/index.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<h1>My Static Site Test</h1>
|
||||||
|
<p>To Test Secrets go to /secret</p>
|
||||||
|
<a href="/secret">Secrets</a>
|
||||||
|
<footer>a footer</footer>
|
11
test/base/wrangler.toml
Normal file
11
test/base/wrangler.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
name = "wrangler-action-test"
|
||||||
|
main = "./index.ts"
|
||||||
|
compatibility_date = "2023-07-07"
|
||||||
|
workers_dev = true
|
||||||
|
|
||||||
|
[site]
|
||||||
|
bucket = "./public"
|
||||||
|
|
||||||
|
# [vars]
|
||||||
|
# SECRET1 = "value1"
|
||||||
|
# SECRET2 = "value2"
|
21
test/environment/index.ts
Normal file
21
test/environment/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
type Env = {
|
||||||
|
SECRET1?: string;
|
||||||
|
SECRET2?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fetch(request: Request, env: Env) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
if (url.pathname === "/secret") {
|
||||||
|
const { SECRET1 = "", SECRET2 = "" } = env;
|
||||||
|
return new Response(`${SECRET1} ${SECRET2}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
return Response.json({
|
||||||
|
...request,
|
||||||
|
headers: Object.fromEntries(request.headers),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
3
test/environment/package.json
Normal file
3
test/environment/package.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"name": "wrangler-action-environment-test"
|
||||||
|
}
|
4
test/environment/public/index.html
Normal file
4
test/environment/public/index.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<h1>My Static Site Test</h1>
|
||||||
|
<p>To Test Secrets go to /secret</p>
|
||||||
|
<a href="/secret">Secrets</a>
|
||||||
|
<footer>a footer</footer>
|
12
test/environment/wrangler.toml
Normal file
12
test/environment/wrangler.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
name = "wrangler-action-environment-test"
|
||||||
|
main = "./index.ts"
|
||||||
|
compatibility_date = "2023-07-07"
|
||||||
|
workers_dev = true
|
||||||
|
|
||||||
|
[site]
|
||||||
|
bucket = "./public"
|
||||||
|
|
||||||
|
[env.dev]
|
||||||
|
name = "wrangler-action-dev-environment-test"
|
||||||
|
[env.dev.vars]
|
||||||
|
DEV_VAR = "example_production_token"
|
|
@ -1,6 +0,0 @@
|
||||||
<h1>My Static Site Test</h1>
|
|
||||||
<p>This is the content of my site</p>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
And this is my footer
|
|
||||||
</footer>
|
|
|
@ -1,88 +0,0 @@
|
||||||
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The DEBUG flag will do two things that help during development:
|
|
||||||
* 1. we will skip caching on the edge, which makes it easier to
|
|
||||||
* debug.
|
|
||||||
* 2. we will return an error message on exception in your response rather
|
|
||||||
* than the default 404.html page.
|
|
||||||
*/
|
|
||||||
const DEBUG = false
|
|
||||||
|
|
||||||
addEventListener('fetch', event => {
|
|
||||||
try {
|
|
||||||
event.respondWith(handleEvent(event))
|
|
||||||
} catch (e) {
|
|
||||||
if (DEBUG) {
|
|
||||||
return event.respondWith(
|
|
||||||
new Response(e.message || e.toString(), {
|
|
||||||
status: 500,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
event.respondWith(new Response('Internal Error', { status: 500 }))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function handleEvent(event) {
|
|
||||||
const url = new URL(event.request.url)
|
|
||||||
let options = {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* You can add custom logic to how we fetch your assets
|
|
||||||
* by configuring the function `mapRequestToAsset`
|
|
||||||
*/
|
|
||||||
// options.mapRequestToAsset = handlePrefix(/^\/docs/)
|
|
||||||
|
|
||||||
// Path to test secrets passed through Wrangler Action. Create SECRET1 and SECRET2 secrets
|
|
||||||
// in the Action repo to something innocuous like "Hello" and "World!".
|
|
||||||
if (url.pathname === "/secret") {
|
|
||||||
let sec1 = (typeof SECRET1 !== 'undefined') ? SECRET1 : ""
|
|
||||||
let sec2 = (typeof SECRET2 !== 'undefined') ? SECRET2 : ""
|
|
||||||
return new Response(`${sec1} ${sec2}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (DEBUG) {
|
|
||||||
// customize caching
|
|
||||||
options.cacheControl = {
|
|
||||||
bypassCache: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await getAssetFromKV(event, options)
|
|
||||||
} catch (e) {
|
|
||||||
// if an error is thrown try to serve the asset at 404.html
|
|
||||||
if (!DEBUG) {
|
|
||||||
try {
|
|
||||||
let notFoundResponse = await getAssetFromKV(event, {
|
|
||||||
mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
|
|
||||||
})
|
|
||||||
|
|
||||||
return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(e.message || e.toString(), { status: 500 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Here's one example of how to modify a request to
|
|
||||||
* remove a specific prefix, in this case `/docs` from
|
|
||||||
* the url. This can be useful if you are deploying to a
|
|
||||||
* route on a zone, or if you only want your static content
|
|
||||||
* to exist at a specific path.
|
|
||||||
*/
|
|
||||||
function handlePrefix(prefix) {
|
|
||||||
return request => {
|
|
||||||
// compute the default (e.g. / -> index.html)
|
|
||||||
let defaultAssetKey = mapRequestToAsset(request)
|
|
||||||
let url = new URL(defaultAssetKey.url)
|
|
||||||
|
|
||||||
// strip the prefix from the path for lookup
|
|
||||||
url.pathname = url.pathname.replace(prefix, '/')
|
|
||||||
|
|
||||||
// inherit all other props from the default request
|
|
||||||
return new Request(url.toString(), defaultAssetKey)
|
|
||||||
}
|
|
||||||
}
|
|
21
test/workers-site/package-lock.json
generated
21
test/workers-site/package-lock.json
generated
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "worker",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@cloudflare/kv-asset-handler": {
|
|
||||||
"version": "0.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.0.5.tgz",
|
|
||||||
"integrity": "sha512-7yLMAUZD1XQNKzmktYCcUUPB+wXmQENv1MMi8QEMs0rzL01e0XEyCUUDauRXHzxi7dBbSUGA5RS23h890ncKog==",
|
|
||||||
"requires": {
|
|
||||||
"mime": "^2.4.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mime": {
|
|
||||||
"version": "2.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
|
|
||||||
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"private": true,
|
|
||||||
"name": "worker",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "A template for kick starting a Cloudflare Workers project",
|
|
||||||
"main": "index.js",
|
|
||||||
"author": "Ashley Lewis <ashleymichal@gmail.com>",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@cloudflare/kv-asset-handler": "^0.0.5"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
name = "static-test"
|
|
||||||
type = "webpack"
|
|
||||||
workers_dev = true
|
|
||||||
account_id = "dc56444c4c955a1653106ccf997c1067"
|
|
||||||
|
|
||||||
[env.production]
|
|
||||||
name = "static-test-prod"
|
|
||||||
|
|
||||||
[site]
|
|
||||||
bucket = "./public"
|
|
||||||
entry-point = "workers-site"
|
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"files": ["./src/index.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"composite": true,
|
||||||
|
"incremental": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"types": ["node", "@cloudflare/workers-types"]
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "**/*.test.ts"],
|
||||||
|
"include": ["src/index.ts"]
|
||||||
|
}
|
Loading…
Reference in a new issue