Skip to content

Commit

Permalink
Update to Node v20.11.1 (#2352)
Browse files Browse the repository at this point in the history
* Updates heroku addons for successful deployment

* Reverts changes

* Adds node update to package.json and .nvmrc file

* Edits dockerfile to new version update.

* Update node from v18.18 to v20.11.1

* node 20 updates / re (#2351)

* change mui import

* remove recompose

* remove recompose

* fix minor duplicate style

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* add comment

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove second onClick

* remove recompose and moved hook

* remove recompose

* remove recompose

* remove second variant call causing error

* remove recompose

* remove recompose

* remove recompose

* remove recompose

* remove recmpose

* remove recompose

* remove recompose

* remove recomopse

* remove recomopse

* remove recomopse

* remove recomopse

* remove recomopse

* remove recomopse

* remove recompose

* upate proper mui import

* formatting

* adjust export to fix error

* adjust export to match prev. version

* adjust export to match prev version

* adjust export to const

* redo withMutations; removing recomponse funcs

* fix texter-feedback loading error

* remove recompose import

* remove recompose funcs & import

* remove recompose import

* remove recompose dependency

* rowsMax => maxRows

* rowsMax => maxRows

* rowsMax => maxRows

* rowsMax => maxRows

* rowsMax => maxRows

* rowsMax => maxRows

* upgrade webpack-cli

* upgrade webpack-cli optionalDep

* yarn dump

* Ran npm audit and got a yarn.lock update

* npm audit fix yarn.lock update

* added @babel/plugin-syntax-flow@^7.14.5 for react-scripts > eslint-config-react-app > eslint-plugin-flowtype@8.0.3

* add unmet @babel/plugin-transform-react-jsx@^7.14.9 for react-scripts > eslint-config-react-app > eslint-plugin-flowtype@8.0.3

* yarn add eslint-plugin-react-hooks@^4.3.0 for unmet peer dep eslint-config-airbnb

* add comment explaining SQlite error

* add react-dnd peer dep

* update peerDep typescript

* Adds @babel/cli@7.23.4 to dependencies

* Adds @bable/core@7.24.0

* remove web-cli from optional dep (is in devDeps)

* Adds babel/traverse version 7.23.2 to package and yarn lock

* ran npm audit fix

* adds react-dnd@7.7.0

* Updates mobilecommons-signup to remove aws-sdk which is deprecated

* upgrade typscript from 2.7 to 2.8

* Adds babel/plugin-transform-class-properties to project dependencies

* ran yarn, new yarn.lock

* typescript from =2.8 to ^2.7

* typscript from ^2.8 tp ^3.2.1

* Adds @babel/plugin-transform-nullish-coalescing-operator to project dependencies

* Adds @babel/plugin-transform-optional-chaining into project dependencies

* Upgrade aws-sdk to version 3 standards

* Add @aws-sdk/client-sqs to deps

* Updates jobs.js and s3-pull/index.js to version 3 standards of aws - beginning of breaking change

* Add @aws-skd-/client-lamda to deps

* Adds aws-sdk/s3-request-predesigner to dependencies

* Adds @aws-sdk/client-s3 to project dependencies

* updates lambda-async index.js to use version 3 aws-sdk

* following ariques history, they reinstalled @aws-sdk/client-lambda, and got a new yarn.lock

* Updates server/telemetry.js to v3 of aws-sdk

* Adds aws-sdk/client-cloudwatch to project dependencies

* Adds @aws-sdk/client-cloudwatch-events to project dependencies

* Upgrades lambda.js to v3 of aws-sdk

* upgrade babel/cli from 7.23.9 to 7.24.1

* upgrade csv-s3-upload/index.js from v2 to v3 of aws-sdk

* upgrade enzyme-adapter-react-16 to 1.15.8 from 1.15.7

* upgrade eslint-plugin-import to 2.29.1 from 2.29.0

* upgrade @babel/core to 7.24.1 from 7.24.0

* upgrade jest-when to 3.6.0 from 3.5.2

* upgrade eslint to 8.56.0 from 8.26.0

* upgrade google-libphonenumber to 3.2.34 from 3.0.0

* remove deprecated @bandwidth/messaging for bandwidth-sdk

* adjust badnwidth/messaging.js to bandwidth-sdk

* update configs to support ESM

* upgrade isomorphic-fetch to 3.0.0 from 2.2.1

* remove unused isomophic-fetch import

* remove node-abort-controller in favor of node's native

* ajdust import to match node's native abort controller

* remove camelcase-keys in favore of humps

* remove camelcase keys in favor of humps in bulkSendMessages.js

* import camelizeKeys from humps to fix graphQL error

* upgrade dataloader to 2.2.2 from 1.4.2

* fixed string match that was missing a space

* upgrade twilio to 4.23.0 from 3.4.0, and url-join to 5.0.0 from 4.0.1

* add ESM support for filter-obj and url-join

* remove selenium-webdrive as it is no longer used

* upgrade terser-weback-pluging to 5.3.10 from 4

* removing unused functions

* remove url-join in favor of native options

* upgrade knex to 3.1.0 from 2.0.0

* upgrade pg-query-stream to 4.5.3 from 1.1.2

* upgrade redis to 4.6.13 from 3

* remove bluebird in favor for native support for promises

* upgrade query-string to 9.0.0 form 4.3.4

* update babel.config.js for esm support

* add esm support for decode-uri-component, split-on-first

* upgrade babel-loader to 9.1.3 from 9.1.0

* fix queryString import that caused error in dev mode

* updgrade @bable/eslint-parser to 7.24.1 from 7.19.1

* fix pageinfo limit in CampaignList test that threw errors when yarn test

* upgrade @babel/plugin-transform-runtime to 7.24.3 from 7.19.6

* upgrade rollbar to 2.26.4 from 2.4.4

* upgrade supertest to 6.3.4 from 6.2.3

* upgrade webpack to 5.90.1 from 5.74.0

* upgrade moment-timezone to 0.5.45 from 0.5.14

* upgrade @babel/preset-env to 7.23.9 from 7.20.2

* upgrade pg to 8.11.3 from 8.0.2

* add support for TextEncoder and TextDecoder for pg upgrade that dropped said support

* upgrade @babel/register to 7.23.7 from 7.18.9

* upgrade auth0-js to 9.24.1 from 9.14.3

* upgrade cookie-session to 2.1.0 from 2.0.0-alpha.1

* upgrade nodemailer to 6.9.9 from 6.4.16

* ran npm audit fix, got new yarn.lock

* node 20 updates (#2349)

* Update graphql + apollo packages and corresponding tests

* Replace deprecated @bandwidth/messaging with bandwidth-sdk

* Update docker image names

* Add yarn.lock updates

* fix tests and other tweaks related to node 20 (#2353)

* Update a test's expectation to align with a change in a Node exception

The failing test was testing a failure caused by trying to parse an
object as if it were a JSON string.

It’s failing because the exception thrown by a JSON parse error
changed between Node versions.

Node 16.18.0
============

```
==> node
Welcome to Node.js v16.18.0.
Type ".help" for more information.
> JSON.parse("{")
Uncaught SyntaxError: Unexpected end of JSON input
> JSON.parse({})
Uncaught SyntaxError: Unexpected token o in JSON at position 1
```

Node 20.11.1
============

```
==> nvm use 20.11.1
Now using node v20.11.1 (npm v10.2.4)
==> node
Welcome to Node.js v20.11.1.
Type ".help" for more information.
> JSON.parse({})
Uncaught SyntaxError: "[object Object]" is not valid JSON

```

* SQLite stores dates as an integer.

You can get the integer from a javascript date with `.getTime()`

* don't pin the version of  -- fix github actions not working with node 20 specified in package.json

* force CI

* try v4 instead of no version specified

* update cache to v4 to get node 20 support

* use node version 20

* Try to pin strip-ansi

* try without caching

* caching is not the problem; let's force strip-ansi to use a non ESM version

* make yarn.lock consistent with pinning strip-ansi

* update actions to work with node 20

* downgrade axios to the latest v0 release to allow cypress tests to run

* Add vm-browserify fallback

* Fixed bug where batchSize and responseWindow were passed as strings instead of Numbers

* Resolving cypress tests (#2356)

* Fix bug - trying to sort a read-only property of array

* Fix spacing

* Fix error where copied nested objects are immutable

* Define how fields should be merged (Apollo InMemoryCache error)

* Parse userId when fetching and updating account

* Linter fix

* Parse user id when getting todos data

* Clean up

* Add try/catch

* Parse buying phone number limit

* Target limit table cell more precisely for integration tests

* Clean up

* my local configs

* redis updates WIP

* updated redis port

* Replace HGET with HSET where it changed erroneously; HMSET deprecated

* update some dependencies

* put back the ports

* make people.test.js run

* Use core-js to bring back setImmediate, which the redis client uses

* Don't include UI tests in rediscache tests

* The excluded UI tests don't use server components

* We need `@jest-environment jsdom` for testing UI components where we
mount react components but it doesn't include setImmediate. The
Redis client needs setImmediate which is available when we use
`@jest-environment node` but then we can't mount react components.
One size does not fit all!

* Close redis after each test suite

* not sure why TextEncoder is not found, try this

* use quit to close the connection

* only import redis if we need it

* try another way to make the pg import not barf

* python on the brain

* only do the TextEncoder and TextDecoder replacement if we need to

* Update assignment.test.js

* Update containers/AssignmentTexterContact.test.js

* Update workers/jobs.test.js

* New node 20 branch texter side boxes (#2360)

* introduce GSIntegerField for fields that require numbers (due to stricter gql update) + add to take-conversations takeConversationsBatchSize field

* revert hard coded changes to force number from string, using GSIntegerField instead

* add redundency with DYNAMICASSIGNEMTN_BATCHES

* add clarifying comment and hyperlink dynamic-assignment docs

* add clarifying comment about vetted-takeconversations

* add clarifying text about selecting multiple batch strategies

* remove forgotton debug console.log

* fix typo in cypress test

* added clarifying comment

* revert changes

* update docs to better reflect DYNAMICASSIGNMNET_BATCHES

* remove sorting method on tag object that was causing loading issues

* more tests pass with redis

* make question-resonse.test pass with redis

* all tests pass locally now!

* increase timeout for redis tests

* refactor and hopefully optimize FLUSHDB

* All tests pass again. Maybe prevent test timeout.

* debug around redis.quit

* try bumping timeout

* maybe we don't care about open handles in tests; maybe this will make CI pass

* get rid of unused import

* update nock

* and update the lock file

* don't detect open handles for redis tests

* set some redis options

* maybe clean up nock preemptively?

* adjust jest.timeout from 15000ms to 25000ms

* a hail mary to try to get redis tests to succeed

* Try using host redis instead of 127.0.0.1

I can't leave it this way but let's see if it works

* restore package.json and thinky.js and add debug logging

* try localhost

* another try to get redis to work in GHA

* 🎉 REDIS TESTS PASS 🎉

* Documentation for ComposeV2 (#2367)

* docker-compose has been deprecated with docker compose now being used. Reflected in docs and devTools

* add clarifying documentattion about ComposeV2

* Fix Google Docs Integration (#2368)

* add experimental and supporting text

* nodeId is used to create the hierarchy of the script.
By just passed nodeId[0], it was sending the object { id: x }, where "x" is the id number.
nodeId only needs the id value, x, rather than the key value pair.

* fix typos in scrub mobile numbers component

* remove private key from docs (was only for example purposes), but got in the way of gh push protection. replaced with 'aVeryLongPrivateKey'

* change the steps to get Google Docs integration working as it has changed the last 4 years.

* Revert "nodeId is used to create the hierarchy of the script."

This reverts commit 1ac88e1.

* nodeId is used to create the hierarchy of the script.
By just passing nodeId[0], it was sending the object { id: x }, where "x" is the id number.
nodeId only needs the id value, x, rather than the key value pair.

* MoveOn > StateVoices :: reflecting repo transfer to SV

* remove MoveOn survey link

* change who to ping to SV staff

* MoveOn > StateVoices :: clarify release process

* remove merge party links :: add transfer to history

* preemptively link / state latest version

* MoveOn >> StateVoices :: change demo texts

* Moveon >> StateVoices :: reference

* adjust version to 14.0

* adjust heroku deployment to deploy on 14.0.0

* add v14 release notes

* Update v14 release date

* remove moveOn survey link

---------

Co-authored-by: Arique1104 <60521756+Arique1104@users.noreply.github.com>
Co-authored-by: Ruby Engelhart <engelhartrueben@gmail.com>
Co-authored-by: Larry Person (he/him) <lp10011@gmail.com>
  • Loading branch information
4 people authored Jul 2, 2024
1 parent 308daf8 commit 0dcdf4e
Show file tree
Hide file tree
Showing 191 changed files with 8,184 additions and 5,358 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ TWILIO_MESSAGE_VALIDITY_PERIOD=
DST_REFERENCE_TIMEZONE='US/Eastern'
PASSPORT_STRATEGY=local
TEXTER_SIDEBOXES=celebration-gif,default-dynamicassignment,default-releasecontacts,contact-reference,tag-contact,freshworks-widget,default-editinitial,take-conversations,hide-media,texter-feedback,contact-notes
DYNAMICASSIGNMENT_BATCHES=finished-replies-tz,vetted-texters,finished-replies
OWNER_CONFIGURABLE=ALL
NGP_VAN_API_KEY=
NGP_VAN_APP_NAME=
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/cypress-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Cypress run
uses: cypress-io/github-action@v4
env:
env:
DEBUG: '@cypress/github-action'
NODE_ENV: test
PORT: 3001
Expand Down
41 changes: 32 additions & 9 deletions .github/workflows/jest-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
timeout-minutes: 10
strategy:
matrix:
node-version: [14.x, 15.x, 16.x]
node-version: [14.x, 15.x, 16.x, 18.x, 20.x]
services:
redis:
image: redis
Expand All @@ -24,11 +24,14 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand All @@ -47,6 +50,11 @@ jobs:
image: redis
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: postgres:10
env:
Expand All @@ -57,11 +65,14 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand All @@ -80,6 +91,11 @@ jobs:
image: redis
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: postgres:10
env:
Expand All @@ -90,11 +106,14 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand All @@ -114,11 +133,15 @@ jobs:
ports:
- 6379:6379
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16.18.0
20.11.1
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ If you see someone who is making an extra effort to ensure our community is welc

Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated.

Any community member asked to stop unacceptable behavior is expected to comply immediately. If the member fails to comply immediately, the community organizers may take any action they deem appropriate, up to and including [blocking users from collaboration with MoveOn Github repositories](https://help.github.com/en/articles/blocking-a-user-from-your-organization) and/or temporarily banning or permanently expelling the non-complying member from the community without warning (and without refund in the case of a paid event).
Any community member asked to stop unacceptable behavior is expected to comply immediately. If the member fails to comply immediately, the community organizers may take any action they deem appropriate, up to and including [blocking users from collaboration with StateVoices Github repositories](https://help.github.com/en/articles/blocking-a-user-from-your-organization) and/or temporarily banning or permanently expelling the non-complying member from the community without warning (and without refund in the case of a paid event).

## Our Responsibilities

Expand Down
5 changes: 2 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ many important organizations!
- ProgCode has community guidelines
- We have a weekly(ish) working group at [Progressive HackNight](https://progressivehacknight.org) that organizes in the #wg-spoke_p2p_sms_tool channel in the slack
- Feel free to [create an issue or comment on an existing issue](https://github.com/StateVoicesNational/Spoke/issues) -- Every time we hear from the outside progressive developer community, we do a little dance.
- We also welcome reaching out on our [MoveOn Spoke interest form](https://act.moveon.org/survey/spoke-project/) with questions, etc.

In all forums we affirm the [Progressive Coder Community Guidelines](https://docs.google.com/document/d/1coMHvuGf6x6Qn_73SEhOXi_QaoRBM__3Zj6_5TyrmWs/edit#heading=h.ab96v3qhdgk9)

Expand Down Expand Up @@ -57,7 +56,7 @@ Welcome to the project! Once you've completed that first contribution, there are
- We use our [Area labels](docs/EXPLANATION-labels.md) to categorize issues into which code feature areas they belong in. Feel free to sort by an area you're interested in.
- We use our [Organization labels](docs/EXPLANATION-labels.md) to categorize issues by which organizations are prioritizing them. You can sort by your favorite org and help out there.
- More broadly, there are all sort of different [labels](https://github.com/StateVoicesNational/Spoke/labels) we use to filter the issues down and you can leverage that to help you find work.
- Lastly, you can always ping a project maintainer (@ibrand, and @schuyler1d) to get a read on what's on our radar right now.
- Lastly, you can always ping a project maintainer (@mau11 and @engelhartrueben) to get a read on what's on our radar right now.

### Claiming issues
- Like for your first issue, comment on the issue and tell us that you're working on it. Feel free to ask any clarifying questions that you have.
Expand All @@ -83,7 +82,7 @@ The actual process:
- We create a new stage-main branch at least twice a month:
- The stage-main branch includes the latest approved pull requests in one merged branch
- This ensures that PRs will not contain anything that breaks deployment and also will allow us to see if any PRs negatively interact with each other before they end up merged to main. Why bother with this step? It's helpful to have a separate "release candidate" on the stage-main branch because in earlier testing rounds people weren't sure what had been deployed to staging and having a separate branch makes this explicit and clear.
- After stage-main is created, we deploy it to MoveOn's staging instance. We have a small set of QA volunteers who then run through a list of [QA steps](https://github.com/StateVoicesNational/Spoke/blob/main/docs/HOWTO_QA_GUIDE.md) in order to find bugs and test new features.
- After stage-main is created, we deploy it to StateVoices staging instance. We have a small set of QA volunteers who then run through a list of [QA steps](https://github.com/StateVoicesNational/Spoke/blob/main/docs/HOWTO_QA_GUIDE.md) in order to find bugs and test new features.
- After QA is completed, and volunteers haven't identified any bugs, we deploy stage-main to production.
- We let stage-main run in production for at least a day, before merging stage-main into the main branch.
- We never roll code directly to prod without first testing on staging.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
ARG BUILDER_IMAGE=node:16.18
ARG RUNTIME_IMAGE=node:16.18-alpine
ARG BUILDER_IMAGE=node:20.11.1
ARG RUNTIME_IMAGE=node:20.11.1-alpine
ARG PHONE_NUMBER_COUNTRY=US

FROM ${BUILDER_IMAGE} as builder
Expand Down
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)

# StateVoices is the new community steward for Spoke!

On November 19th, the repo Spoke was transfered from MoveOn to StateVoices.

[Join us for the Merge Party for more information.](https://www.mobilize.us/statevoices/event/592881/)

## Spoke History

Spoke is an open source text-distribution tool for organizations to mobilize supporters and members into action. Spoke allows you to upload phone numbers, customize scripts and assign volunteers to communicate with supporters while allowing organizations to manage the process.

Spoke was created by Saikat Chakrabarti and Sheena Pakanati, and is now maintained by MoveOn.org.
Spoke was created by Saikat Chakrabarti and Sheena Pakanati.

The latest version is [13.1.0](https://github.com/StateVoicesNational/Spoke/tree/13.1.0) (see [release notes](https://github.com/StateVoicesNational/Spoke/blob/main/docs/RELEASE_NOTES.md#v1310))
On November 19th, 2023, the repo Spoke was transfered from MoveOn to StateVoices.

The latest version is [14.0.0](https://github.com/StateVoicesNational/Spoke/tree/14.0.0) (see [release notes](https://github.com/StateVoicesNational/Spoke/blob/main/docs/RELEASE_NOTES.md#v1310))


## Setting up Spoke
Expand All @@ -29,7 +25,7 @@ Want to know more?
### Quick Start with Heroku
This version of Spoke suitable for testing and, potentially, for small campaigns. This won't cost any money and will not support production(aka large-scale) usage. It's a great way to practice deploying Spoke or see it in action.

<a href="https://heroku.com/deploy?template=https://github.com/StateVoicesNational/Spoke/tree/13.1.0">
<a href="https://heroku.com/deploy?template=https://github.com/StateVoicesNational/Spoke/tree/14.0.0">

<img src="https://www.herokucdn.com/deploy/button.svg" alt="Deploy">
</a>
Expand All @@ -39,8 +35,6 @@ Follow up instructions located [here](/docs/HOWTO_HEROKU_DEPLOY.md).

**NOTE:** You can upgrade this deployment later for use in a production setting, but keep in mind you will need to migrate data from any prior campaigns. Thus it is best to upgrade before you start any live campaigns. This will cost ~$75 ($25 dyno + $50 postgres) a month and should be suitable for production level usage for most organizations. We recommend that if you plan to use Spoke at scale that you use [this link to deploy with a production infrastructure from the start!](https://heroku.com/deploy?template=https://github.com/StateVoicesNational/Spoke/tree/heroku-button-paid)

Please let us know if you deployed by filling out this form [here](https://act.moveon.org/survey/tech/)


### Other Options for Production Use

Expand Down
Loading

0 comments on commit 0dcdf4e

Please sign in to comment.