├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc ├── .travis.yml ├── .yarnrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _create-specs.js ├── index.js ├── package-lock.json ├── package.json └── src ├── .editorconfig ├── .gitattributes ├── .gitignore ├── cli-utils ├── default-gitignore.js ├── download-project.js ├── fetch-from-crystallize.js ├── get-multilingual.js ├── get-payment-methods.js ├── init-gatsby.js ├── init-nextjs-conference.js ├── init-nextjs-content-commerce.js ├── init-nextjs-subscription-commerce.js ├── init-nextjs.js ├── init-nuxtjs.js ├── init-project.js ├── init-rn.js ├── init-service-api.js └── tips.js ├── cli.js ├── config.js ├── journeys ├── _shared │ ├── bootstrap-tenant.js │ ├── specs │ │ ├── conference-boilerplate.json │ │ ├── furniture.json │ │ ├── photofinder.json │ │ └── voyage.json │ ├── step-access-tokens.js │ └── step-bootstrap-tenant.js ├── bootstrap-tenant │ └── index.js └── download-boilerplate │ ├── index.js │ └── steps.js ├── shared.js ├── ui-modules ├── multi-select.js └── select.js └── ui.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | charset = utf-8 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | template 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "plugin:react-hooks/recommended", 6 | "prettier" 7 | ], 8 | "parserOptions": { 9 | "ecmaVersion": 2019 10 | }, 11 | "env": { 12 | "browser": true, 13 | "commonjs": true, 14 | "node": true, 15 | "es6": true 16 | }, 17 | "rules": { 18 | "react/prop-types": 0, 19 | "react/no-unescaped-entities": 0 20 | }, 21 | "settings": { 22 | "react": { 23 | "createClass": "createReactClass", // Regex for Component Factory to use, 24 | // default to "createReactClass" 25 | "pragma": "React", // Pragma to use, default to "React" 26 | "version": "detect" // React version. "detect" automatically picks the version you have installed. 27 | // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. 28 | // default to latest and warns if missing 29 | // It will default to "detect" in the future 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | commitlint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | with: 9 | fetch-depth: 0 10 | - uses: wagoid/commitlint-github-action@v2 11 | 12 | ci: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Begin CI... 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node 12 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: 16.x 23 | 24 | - name: Use cached node_modules 25 | uses: actions/cache@v2 26 | with: 27 | path: node_modules 28 | key: nodeModules-${{ hashFiles('**/package-lock.json') }} 29 | restore-keys: | 30 | nodeModules- 31 | - name: Install dependencies 32 | run: npm ci 33 | env: 34 | CI: true 35 | 36 | - name: Lint 37 | run: npm run lint 38 | env: 39 | CI: true 40 | 41 | - name: Release 42 | run: npx semantic-release 43 | env: 44 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 45 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 46 | CI: true 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # test projects 64 | crystallize-app* 65 | 66 | .DS_STORE 67 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "proseWrap": "always" 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | cache: 5 | yarn: true 6 | directories: 7 | - node_modules 8 | script: 9 | - npm run lint 10 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --install.no-lockfile true 2 | --install.check-files true 3 | --add.no-lockfile true 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.31.7](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.6...v3.31.7) (2022-05-13) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * corrected name output in bootstrapper ([7f7a155](https://github.com/CrystallizeAPI/crystallize-cli/commit/7f7a1558871f07cf78bf70d5a5917bf27ed9748b)) 7 | 8 | ## [3.31.6](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.5...v3.31.6) (2022-05-12) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * yet another ([6fa020c](https://github.com/CrystallizeAPI/crystallize-cli/commit/6fa020cd3d7175c4188aadbf6943e924d31981e5)) 14 | 15 | ## [3.31.5](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.4...v3.31.5) (2022-05-12) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * bump deps ([8bd5246](https://github.com/CrystallizeAPI/crystallize-cli/commit/8bd524635628b1c8e42de24cc08432dc131ce816)) 21 | 22 | ## [3.31.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.3...v3.31.4) (2022-05-12) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * bump deps ([7ff2016](https://github.com/CrystallizeAPI/crystallize-cli/commit/7ff2016fa6c03dea6a843a2c7d14da750e022559)) 28 | 29 | ## [3.31.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.2...v3.31.3) (2022-03-09) 30 | 31 | 32 | ### Bug Fixes 33 | 34 | * correct path to config for next.js ([1f4af08](https://github.com/CrystallizeAPI/crystallize-cli/commit/1f4af08cb8747b53af2869defa97027b4ea5a5da)) 35 | 36 | ## [3.31.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.1...v3.31.2) (2022-03-08) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * multilingual setup for next.js works properly again ([cd3de5d](https://github.com/CrystallizeAPI/crystallize-cli/commit/cd3de5d9385e7c48bebd34b91c817e52145315e1)) 42 | 43 | ## [3.31.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.31.0...v3.31.1) (2022-02-08) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * furniture grid missing items ([2a0b4c7](https://github.com/CrystallizeAPI/crystallize-cli/commit/2a0b4c72ab51b53610e6c3f9bad7dffb65514a80)) 49 | 50 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-02-03) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * app path for content and commerce setup ([8e3fe91](https://github.com/CrystallizeAPI/crystallize-cli/commit/8e3fe9170e8b84252adb5b7c9069caef33af10b6)) 56 | * conf spec ([b89dbce](https://github.com/CrystallizeAPI/crystallize-cli/commit/b89dbced7884f427066f283fb3c26d1d5af52c79)) 57 | * ignore .next files on conf boiler ([54deed3](https://github.com/CrystallizeAPI/crystallize-cli/commit/54deed31c4fbba275f5cea226b7dc548c642398a)) 58 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 59 | 60 | 61 | ### Features 62 | 63 | * update readme to force new minor ([b58f6b2](https://github.com/CrystallizeAPI/crystallize-cli/commit/b58f6b279f29fbc224f2df1d3375629dbe7c96f6)) 64 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 65 | * updated tenant specs ([526e54b](https://github.com/CrystallizeAPI/crystallize-cli/commit/526e54b9a1215380827a73ce9e058cfe10a1dac4)) 66 | 67 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-02-03) 68 | 69 | 70 | ### Bug Fixes 71 | 72 | * app path for content and commerce setup ([8e3fe91](https://github.com/CrystallizeAPI/crystallize-cli/commit/8e3fe9170e8b84252adb5b7c9069caef33af10b6)) 73 | * conf spec ([b89dbce](https://github.com/CrystallizeAPI/crystallize-cli/commit/b89dbced7884f427066f283fb3c26d1d5af52c79)) 74 | * ignore .next files on conf boiler ([54deed3](https://github.com/CrystallizeAPI/crystallize-cli/commit/54deed31c4fbba275f5cea226b7dc548c642398a)) 75 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 76 | 77 | 78 | ### Features 79 | 80 | * update readme to force new minor ([5e9e074](https://github.com/CrystallizeAPI/crystallize-cli/commit/5e9e074c9bc5ac1ccd74199bd8ede0e2a41e44b0)) 81 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 82 | 83 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-02-03) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * app path for content and commerce setup ([8e3fe91](https://github.com/CrystallizeAPI/crystallize-cli/commit/8e3fe9170e8b84252adb5b7c9069caef33af10b6)) 89 | * conf spec ([b89dbce](https://github.com/CrystallizeAPI/crystallize-cli/commit/b89dbced7884f427066f283fb3c26d1d5af52c79)) 90 | * ignore .next files on conf boiler ([54deed3](https://github.com/CrystallizeAPI/crystallize-cli/commit/54deed31c4fbba275f5cea226b7dc548c642398a)) 91 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 92 | 93 | 94 | ### Features 95 | 96 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 97 | 98 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-01-26) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * conf spec ([b89dbce](https://github.com/CrystallizeAPI/crystallize-cli/commit/b89dbced7884f427066f283fb3c26d1d5af52c79)) 104 | * ignore .next files on conf boiler ([54deed3](https://github.com/CrystallizeAPI/crystallize-cli/commit/54deed31c4fbba275f5cea226b7dc548c642398a)) 105 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 106 | 107 | 108 | ### Features 109 | 110 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 111 | 112 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-01-26) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * conf spec ([b89dbce](https://github.com/CrystallizeAPI/crystallize-cli/commit/b89dbced7884f427066f283fb3c26d1d5af52c79)) 118 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 119 | 120 | 121 | ### Features 122 | 123 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 124 | 125 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-01-26) 126 | 127 | 128 | ### Bug Fixes 129 | 130 | * updated readme ([bd672e9](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd672e9dbad1f8877c2a0bbffd98373601e983b3)) 131 | 132 | 133 | ### Features 134 | 135 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 136 | 137 | # [3.31.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.30.0...v3.31.0) (2022-01-26) 138 | 139 | 140 | ### Features 141 | 142 | * updated specs ([278220a](https://github.com/CrystallizeAPI/crystallize-cli/commit/278220ae5d608514f31abeabbbfa192556e5ed54)) 143 | 144 | # [3.30.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.29.0...v3.30.0) (2022-01-25) 145 | 146 | 147 | ### Features 148 | 149 | * new urls for conference boiler ([794d33f](https://github.com/CrystallizeAPI/crystallize-cli/commit/794d33f0798700fcbb101c042f2bb4e1721ac0e3)) 150 | 151 | # [3.29.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.28.1...v3.29.0) (2022-01-25) 152 | 153 | 154 | ### Bug Fixes 155 | 156 | * add .env to gitignore and remove unused variable ([101689c](https://github.com/CrystallizeAPI/crystallize-cli/commit/101689c00b334e59866f0f09fdd203e371dd14e1)) 157 | * spec not found error ([e021cf5](https://github.com/CrystallizeAPI/crystallize-cli/commit/e021cf579edb976e2f0c5937a1b0aa81d4cd2ddc)) 158 | 159 | 160 | ### Features 161 | 162 | * add conference boilerplate ([f1b6e2d](https://github.com/CrystallizeAPI/crystallize-cli/commit/f1b6e2d2bdadaada962c87d7b5444118e54c223e)) 163 | * add conference boilerplate ([#21](https://github.com/CrystallizeAPI/crystallize-cli/issues/21)) ([195b530](https://github.com/CrystallizeAPI/crystallize-cli/commit/195b530cd514f1157621300674bff2c106b7354e)) 164 | * add json spec for conference boilerplate ([7c65e02](https://github.com/CrystallizeAPI/crystallize-cli/commit/7c65e02ff23c14ac6d05e9299627e88a69768014)) 165 | 166 | # [3.29.0-beta.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.29.0-beta.2...v3.29.0-beta.3) (2022-01-24) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * spec not found error ([e021cf5](https://github.com/CrystallizeAPI/crystallize-cli/commit/e021cf579edb976e2f0c5937a1b0aa81d4cd2ddc)) 172 | 173 | # [3.29.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.29.0-beta.1...v3.29.0-beta.2) (2022-01-24) 174 | 175 | 176 | ### Features 177 | 178 | * add json spec for conference boilerplate ([7c65e02](https://github.com/CrystallizeAPI/crystallize-cli/commit/7c65e02ff23c14ac6d05e9299627e88a69768014)) 179 | 180 | # [3.29.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.28.1...v3.29.0-beta.1) (2022-01-21) 181 | 182 | 183 | ### Bug Fixes 184 | 185 | * add .env to gitignore and remove unused variable ([101689c](https://github.com/CrystallizeAPI/crystallize-cli/commit/101689c00b334e59866f0f09fdd203e371dd14e1)) 186 | 187 | 188 | ### Features 189 | 190 | * add conference boilerplate ([f1b6e2d](https://github.com/CrystallizeAPI/crystallize-cli/commit/f1b6e2d2bdadaada962c87d7b5444118e54c223e)) 191 | 192 | ## [3.28.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.28.0...v3.28.1) (2021-12-20) 193 | 194 | 195 | ### Bug Fixes 196 | 197 | * run install ([576119e](https://github.com/CrystallizeAPI/crystallize-cli/commit/576119ec07b0af7f298c5efbf57f9b00eb6ddd38)) 198 | 199 | # [3.28.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.27.2...v3.28.0) (2021-12-20) 200 | 201 | 202 | ### Features 203 | 204 | * bump import-utilities ([39d2e13](https://github.com/CrystallizeAPI/crystallize-cli/commit/39d2e13fcda98381d78bd02a5556036ffe077803)) 205 | 206 | ## [3.27.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.27.1...v3.27.2) (2021-12-07) 207 | 208 | 209 | ### Bug Fixes 210 | 211 | * use normal spacing ([97d3fb6](https://github.com/CrystallizeAPI/crystallize-cli/commit/97d3fb6bc92589da0e978e9852eb0019a847a14a)) 212 | 213 | ## [3.27.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.27.0...v3.27.1) (2021-12-07) 214 | 215 | 216 | ### Bug Fixes 217 | 218 | * remove trailing api path for sub. comm ([436c7a0](https://github.com/CrystallizeAPI/crystallize-cli/commit/436c7a064e04e7ee42a7e198d8718728a739b4b5)) 219 | 220 | # [3.27.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.26.0...v3.27.0) (2021-12-07) 221 | 222 | 223 | ### Features 224 | 225 | * automatic choose tenant based on boilerplate ([46dd545](https://github.com/CrystallizeAPI/crystallize-cli/commit/46dd545caa056366d004039fd58d45557009b693)) 226 | * use proper source for photofinder ([65d84e2](https://github.com/CrystallizeAPI/crystallize-cli/commit/65d84e2ec10f85668ebbce8e2cc2a587f4842614)) 227 | 228 | # [3.26.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.25.0...v3.26.0) (2021-12-07) 229 | 230 | 231 | ### Features 232 | 233 | * add subscription commerce boilerplate ([f36d67c](https://github.com/CrystallizeAPI/crystallize-cli/commit/f36d67ce291d1389ef33995b5e5cf03ba4ee1571)) 234 | 235 | # [3.25.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.24.1...v3.25.0) (2021-12-07) 236 | 237 | 238 | ### Features 239 | 240 | * add photofinder spec ([670c759](https://github.com/CrystallizeAPI/crystallize-cli/commit/670c759e516c4e08244ffd8afdb9901943ca2164)) 241 | 242 | # [3.25.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.24.1...v3.25.0-beta.1) (2021-12-02) 243 | 244 | 245 | ### Features 246 | 247 | * add photofinder spec ([670c759](https://github.com/CrystallizeAPI/crystallize-cli/commit/670c759e516c4e08244ffd8afdb9901943ca2164)) 248 | 249 | ## [3.24.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.24.0...v3.24.1) (2021-11-23) 250 | 251 | 252 | ### Bug Fixes 253 | 254 | * use ink-text-input module ([703cfc5](https://github.com/CrystallizeAPI/crystallize-cli/commit/703cfc56648d81b2a69cdfd1e73f5307ce1bd5a6)) 255 | 256 | # [3.24.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.23.1...v3.24.0) (2021-11-22) 257 | 258 | 259 | ### Features 260 | 261 | * new highlight color ([f3827ba](https://github.com/CrystallizeAPI/crystallize-cli/commit/f3827ba36094f95e298915979af589b653732365)) 262 | 263 | ## [3.23.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.23.0...v3.23.1) (2021-11-05) 264 | 265 | 266 | ### Bug Fixes 267 | 268 | * content&commerce boilerplate topbar removal ([ab4cb67](https://github.com/CrystallizeAPI/crystallize-cli/commit/ab4cb678485a5f34744fe429ef297ea9f87ad336)) 269 | 270 | # [3.23.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.22.0...v3.23.0) (2021-11-05) 271 | 272 | 273 | ### Features 274 | 275 | * remove boilerplate topbar for content and commerce ([92fb6d7](https://github.com/CrystallizeAPI/crystallize-cli/commit/92fb6d7cf55bd83f449d70d37e7a1f2e82a2c5e0)) 276 | 277 | # [3.22.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.21.0...v3.22.0) (2021-11-05) 278 | 279 | 280 | ### Features 281 | 282 | * remove boilerplate toolbar for nextjs ([97e0587](https://github.com/CrystallizeAPI/crystallize-cli/commit/97e058769b2e05f3a07fccb79112c8c7114b0f33)) 283 | 284 | # [3.21.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1...v3.21.0) (2021-10-20) 285 | 286 | 287 | ### Features 288 | 289 | * new welcome title ([8829368](https://github.com/CrystallizeAPI/crystallize-cli/commit/882936876771a3feab3e136537f348c5573fbc5a)) 290 | 291 | ## [3.20.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.0...v3.20.1) (2021-10-10) 292 | 293 | 294 | ### Bug Fixes 295 | 296 | * support for node 16 ([f47a134](https://github.com/CrystallizeAPI/crystallize-cli/commit/f47a134a0eb71337976a9037d76f4102ce781100)) 297 | 298 | ## [3.20.1-beta.10](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.9...v3.20.1-beta.10) (2021-10-10) 299 | 300 | 301 | ### Bug Fixes 302 | 303 | * use own module ([e215f34](https://github.com/CrystallizeAPI/crystallize-cli/commit/e215f34d0880f81b0a830e77fdbf55abe44ed494)) 304 | 305 | ## [3.20.1-beta.9](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.8...v3.20.1-beta.9) (2021-10-10) 306 | 307 | 308 | ### Bug Fixes 309 | 310 | * use gh package ([2fc9ccd](https://github.com/CrystallizeAPI/crystallize-cli/commit/2fc9ccd3b4340cae471130fd322406e33bab06db)) 311 | 312 | ## [3.20.1-beta.8](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.7...v3.20.1-beta.8) (2021-10-10) 313 | 314 | 315 | ### Bug Fixes 316 | 317 | * import ([36677ef](https://github.com/CrystallizeAPI/crystallize-cli/commit/36677ef63400fea93b9ef9e08d49ca9128045367)) 318 | 319 | ## [3.20.1-beta.7](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.6...v3.20.1-beta.7) (2021-10-10) 320 | 321 | 322 | ### Bug Fixes 323 | 324 | * import jsx ([9e5d8ee](https://github.com/CrystallizeAPI/crystallize-cli/commit/9e5d8eea57da595c9b26d45d6d8547eaf6c06c93)) 325 | 326 | ## [3.20.1-beta.6](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.5...v3.20.1-beta.6) (2021-10-10) 327 | 328 | 329 | ### Bug Fixes 330 | 331 | * pull in ink-text-input ([b18665d](https://github.com/CrystallizeAPI/crystallize-cli/commit/b18665df9e6b004b6009343d433f2f04925bf00e)) 332 | 333 | ## [3.20.1-beta.5](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.4...v3.20.1-beta.5) (2021-10-10) 334 | 335 | 336 | ### Bug Fixes 337 | 338 | * use forked ink-text-input ([01c5ad2](https://github.com/CrystallizeAPI/crystallize-cli/commit/01c5ad2ee4ba18cd3961607da804f8966d6d05b8)) 339 | 340 | ## [3.20.1-beta.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.3...v3.20.1-beta.4) (2021-10-08) 341 | 342 | 343 | ### Bug Fixes 344 | 345 | * node 16 in ci ([bc8a4c2](https://github.com/CrystallizeAPI/crystallize-cli/commit/bc8a4c2c0b8ddd8395998e36911982a50b9393a1)) 346 | 347 | ## [3.20.1-beta.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.2...v3.20.1-beta.3) (2021-10-08) 348 | 349 | 350 | ### Bug Fixes 351 | 352 | * use npm in ci ([8cf78c7](https://github.com/CrystallizeAPI/crystallize-cli/commit/8cf78c7614f314fa53a58d3d75b5285b16aee69f)) 353 | 354 | ## [3.20.1-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.1-beta.1...v3.20.1-beta.2) (2021-10-08) 355 | 356 | 357 | ### Bug Fixes 358 | 359 | * latest compatible react version ([e4709ff](https://github.com/CrystallizeAPI/crystallize-cli/commit/e4709ffd288aaf11f94b3ab36d2ddbb85a79d8cc)) 360 | 361 | ## [3.20.1-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.20.0...v3.20.1-beta.1) (2021-10-08) 362 | 363 | 364 | ### Bug Fixes 365 | 366 | * update deps ([ee5a5a4](https://github.com/CrystallizeAPI/crystallize-cli/commit/ee5a5a4266df6492be753bdc94e203c1d322b295)) 367 | 368 | # [3.20.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.19.1...v3.20.0) (2021-07-15) 369 | 370 | 371 | ### Features 372 | 373 | * updated specs ([bf5d5e5](https://github.com/CrystallizeAPI/crystallize-cli/commit/bf5d5e53301bf0da5f2ea663c8b92bbda85eb1b8)) 374 | 375 | ## [3.19.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.19.0...v3.19.1) (2021-07-14) 376 | 377 | 378 | ### Bug Fixes 379 | 380 | * typo and new furniture spec ([ac95700](https://github.com/CrystallizeAPI/crystallize-cli/commit/ac957008525a656b324ecc66e57a7963ed930abe)) 381 | 382 | # [3.19.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.1...v3.19.0) (2021-07-13) 383 | 384 | 385 | ### Features 386 | 387 | * show how to get help ([86e0b5f](https://github.com/CrystallizeAPI/crystallize-cli/commit/86e0b5fe33cca766f5a8d1560c2ca6680c061175)) 388 | 389 | # [3.19.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.1...v3.19.0-beta.1) (2021-07-13) 390 | 391 | 392 | ### Features 393 | 394 | * better feedback [skip release] ([1474b9f](https://github.com/CrystallizeAPI/crystallize-cli/commit/1474b9ffce0596edb0f042d4b3f2d954deb1ea30)) 395 | * proper feedback on bootstrapping ([9dee8d0](https://github.com/CrystallizeAPI/crystallize-cli/commit/9dee8d02e9ae87679da755b9b469985681ea09c6)) 396 | 397 | ## [3.18.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0...v3.18.1) (2021-07-08) 398 | 399 | 400 | ### Bug Fixes 401 | 402 | * typo ([3a52fc4](https://github.com/CrystallizeAPI/crystallize-cli/commit/3a52fc42cd67cb75f0dedf74c1943e8089a3fc8a)) 403 | 404 | # [3.18.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.17.0...v3.18.0) (2021-07-08) 405 | 406 | 407 | ### Features 408 | 409 | * show shortcut to bootstrap tenant ([a1422f9](https://github.com/CrystallizeAPI/crystallize-cli/commit/a1422f91cbedb8983bdc07377f1db938dcdd4fee)) 410 | 411 | # [3.18.0-beta.10](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.9...v3.18.0-beta.10) (2021-07-08) 412 | 413 | 414 | ### Bug Fixes 415 | 416 | * updated readme ([78abd8f](https://github.com/CrystallizeAPI/crystallize-cli/commit/78abd8f376e60c835a47de9557add6210e018c1f)) 417 | 418 | # [3.18.0-beta.9](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.8...v3.18.0-beta.9) (2021-07-08) 419 | 420 | 421 | ### Bug Fixes 422 | 423 | * mark bootstrapping as beta ([dec68f5](https://github.com/CrystallizeAPI/crystallize-cli/commit/dec68f519c36b7ce7fa2e3f47c79f68277df3692)) 424 | 425 | # [3.18.0-beta.8](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.7...v3.18.0-beta.8) (2021-07-08) 426 | 427 | 428 | ### Features 429 | 430 | * better warning for bootstrapping ([c29abd0](https://github.com/CrystallizeAPI/crystallize-cli/commit/c29abd0922bba9c66d403bcaaa70136a717c79d0)) 431 | 432 | # [3.18.0-beta.7](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.6...v3.18.0-beta.7) (2021-07-08) 433 | 434 | 435 | ### Bug Fixes 436 | 437 | * reorder feedback ([a8df13e](https://github.com/CrystallizeAPI/crystallize-cli/commit/a8df13ecf3e7587e6a97d9cfdac9942ae9c8fce4)) 438 | 439 | # [3.18.0-beta.6](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.5...v3.18.0-beta.6) (2021-07-08) 440 | 441 | 442 | ### Bug Fixes 443 | 444 | * cleanup ([ac96fe8](https://github.com/CrystallizeAPI/crystallize-cli/commit/ac96fe8ffffcaae6c3ad1457f1a8c5f781605b96)) 445 | 446 | # [3.18.0-beta.5](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.4...v3.18.0-beta.5) (2021-07-07) 447 | 448 | 449 | ### Bug Fixes 450 | 451 | * bugfixes ([7a51eb3](https://github.com/CrystallizeAPI/crystallize-cli/commit/7a51eb31e0469cd4467d9e0bab0a88ba4d9496b8)) 452 | 453 | # [3.18.0-beta.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.3...v3.18.0-beta.4) (2021-07-07) 454 | 455 | 456 | ### Features 457 | 458 | * more feedback ([a4cc51b](https://github.com/CrystallizeAPI/crystallize-cli/commit/a4cc51b5309f176820bb4bc8e61e5db8daa62257)) 459 | 460 | # [3.18.0-beta.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.2...v3.18.0-beta.3) (2021-07-07) 461 | 462 | 463 | ### Bug Fixes 464 | 465 | * bugfix ([9bef08f](https://github.com/CrystallizeAPI/crystallize-cli/commit/9bef08fd1ea811a1e1c0024b51b31b197564ce30)) 466 | 467 | # [3.18.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.18.0-beta.1...v3.18.0-beta.2) (2021-07-07) 468 | 469 | 470 | ### Bug Fixes 471 | 472 | * bugfixes ([249022c](https://github.com/CrystallizeAPI/crystallize-cli/commit/249022c24233c9fe37a38e7fa8cfb37b06bd7d97)) 473 | 474 | # [3.18.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.17.0...v3.18.0-beta.1) (2021-07-07) 475 | 476 | 477 | ### Features 478 | 479 | * first working version of bootstrapper ([b3f1cb4](https://github.com/CrystallizeAPI/crystallize-cli/commit/b3f1cb4aa5b24ea6ebadfad1b5435716e7316d50)) 480 | 481 | # [3.17.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.16.0...v3.17.0) (2021-04-26) 482 | 483 | 484 | ### Features 485 | 486 | * show overflow for select ([ee4e0c0](https://github.com/CrystallizeAPI/crystallize-cli/commit/ee4e0c0e89a468aa20d2d465f23f8e9c8124e9a4)) 487 | 488 | # [3.16.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.15.0...v3.16.0) (2021-04-21) 489 | 490 | 491 | ### Features 492 | 493 | * force new deploy ([522d308](https://github.com/CrystallizeAPI/crystallize-cli/commit/522d30804b157d4d64075466480722d8743845c0)) 494 | 495 | # Changelog 496 | 497 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 498 | 499 | ## [3.15.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.15.0-beta.1...v3.15.0) (2021-04-21) 500 | 501 | # [3.15.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.14.1...v3.15.0-beta.1) (2021-04-21) 502 | 503 | 504 | ### Features 505 | 506 | * service api for nuxt ([66315d1](https://github.com/CrystallizeAPI/crystallize-cli/commit/66315d183448661810356fc69024e4c91864adf8)) 507 | 508 | ## [3.14.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.14.0...v3.14.1) (2021-03-25) 509 | 510 | 511 | ### Bug Fixes 512 | 513 | * set .env for react native ([d380632](https://github.com/CrystallizeAPI/crystallize-cli/commit/d380632a29701a9b331233a920ca75fa15048972)) 514 | 515 | # [3.14.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.13.0...v3.14.0) (2021-03-25) 516 | 517 | 518 | ### Features 519 | 520 | * cli react native update ([c3dbfb1](https://github.com/CrystallizeAPI/crystallize-cli/commit/c3dbfb12b84a1bea27e3525d0d694327599a04a7)) 521 | 522 | # [3.14.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.13.0...v3.14.0-beta.1) (2021-03-24) 523 | 524 | 525 | ### Features 526 | 527 | * cli react native update ([c3dbfb1](https://github.com/CrystallizeAPI/crystallize-cli/commit/c3dbfb12b84a1bea27e3525d0d694327599a04a7)) 528 | 529 | # [3.13.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.12.1...v3.13.0) (2021-03-04) 530 | 531 | 532 | ### Features 533 | 534 | * service api question for gatsby ([90ec560](https://github.com/CrystallizeAPI/crystallize-cli/commit/90ec560ec16cc43030a36ed50825002c9dc7efd2)) 535 | 536 | ## [3.12.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.12.0...v3.12.1) (2021-03-03) 537 | 538 | 539 | ### Bug Fixes 540 | 541 | * remove isDefault from gatsby locale settings ([d1193b7](https://github.com/CrystallizeAPI/crystallize-cli/commit/d1193b7b6d41181c5f1f4b26de2a8dbde86e8852)) 542 | 543 | # [3.12.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.11.2...v3.12.0) (2021-02-19) 544 | 545 | 546 | ### Features 547 | 548 | * .env.local.example for service api ([9a27c61](https://github.com/CrystallizeAPI/crystallize-cli/commit/9a27c61ca1c3f31bfb7a5c7aad9124090d25fe0b)) 549 | 550 | ## [3.11.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.11.1...v3.11.2) (2021-02-15) 551 | 552 | 553 | ### Bug Fixes 554 | 555 | * no crystallize tokens for nextjs boiler ([6993c00](https://github.com/CrystallizeAPI/crystallize-cli/commit/6993c0022a0d76f9dec3cd42d360d4499b480aee)) 556 | 557 | ## [3.11.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.11.0...v3.11.1) (2021-02-12) 558 | 559 | 560 | ### Bug Fixes 561 | 562 | * typo on contributing.md ([a2880af](https://github.com/CrystallizeAPI/crystallize-cli/commit/a2880af519d757c53d8a148b97f8626e83de944b)) 563 | * update gatsby boilerplate demo link ([f1ee172](https://github.com/CrystallizeAPI/crystallize-cli/commit/f1ee172c7d8f0e5c2fbece07abd43afb193e1e55)) 564 | 565 | # [3.11.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.10.3...v3.11.0) (2021-02-12) 566 | 567 | 568 | ### Features 569 | 570 | * included boilerplat demo links ([ca91cd7](https://github.com/CrystallizeAPI/crystallize-cli/commit/ca91cd738345e90f25a2787656413931ed2f3d23)) 571 | 572 | ## [3.10.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.10.2...v3.10.3) (2021-02-10) 573 | 574 | 575 | ### Bug Fixes 576 | 577 | * correction of crystallize access token names ([c15b283](https://github.com/CrystallizeAPI/crystallize-cli/commit/c15b283d27934d2b6ebde50ee8a169acbc0ddddd)) 578 | 579 | ## [3.10.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.10.1...v3.10.2) (2021-02-10) 580 | 581 | 582 | ### Bug Fixes 583 | 584 | * correct name for crystallize access tokens ([71cc149](https://github.com/CrystallizeAPI/crystallize-cli/commit/71cc149d4652538b0df9b0370e8f8519d6acad55)) 585 | 586 | ## [3.10.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.10.0...v3.10.1) (2021-02-10) 587 | 588 | 589 | ### Bug Fixes 590 | 591 | * node 15 now works ([1801270](https://github.com/CrystallizeAPI/crystallize-cli/commit/1801270768e6d17bc1dab80442b7fdb6fb0192d3)) 592 | 593 | # [3.10.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.9.0...v3.10.0) (2021-02-10) 594 | 595 | 596 | ### Features 597 | 598 | * serverless aws for service api ([788c8c1](https://github.com/CrystallizeAPI/crystallize-cli/commit/788c8c1890824c846b135751313c0e15d40fdf1c)) 599 | * show service api choice ([dc5b76f](https://github.com/CrystallizeAPI/crystallize-cli/commit/dc5b76f63c9b6bf2125ee52db204e1d8941e2b6c)) 600 | 601 | # [3.9.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.1...v3.9.0) (2021-01-29) 602 | 603 | 604 | ### Features 605 | 606 | * ask for service api ([2d4b7d4](https://github.com/CrystallizeAPI/crystallize-cli/commit/2d4b7d48e3b7d64b77963d423888298637823578)) 607 | 608 | ## [3.8.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0...v3.8.1) (2021-01-28) 609 | 610 | 611 | ### Bug Fixes 612 | 613 | * try to get the latest project version ([601a4ee](https://github.com/CrystallizeAPI/crystallize-cli/commit/601a4eedcc37ae2f79b65edc4663bc3a8bca52f3)) 614 | 615 | # [3.8.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.4...v3.8.0) (2021-01-28) 616 | 617 | 618 | ### Features 619 | 620 | * updated readme ([74f311d](https://github.com/CrystallizeAPI/crystallize-cli/commit/74f311d3cf9895b8846484127111ad9ca49e4ea0)) 621 | 622 | # [3.8.0-beta.6](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2021-01-28) 623 | 624 | 625 | ### Bug Fixes 626 | 627 | * update immer and add .gitignore for service api ([8bd8a75](https://github.com/CrystallizeAPI/crystallize-cli/commit/8bd8a753579a848c2e8b67b832048e427dabbe00)) 628 | 629 | 630 | ### Features 631 | 632 | * target correct nextjs branch ([0c6d2c7](https://github.com/CrystallizeAPI/crystallize-cli/commit/0c6d2c78233b5388f9f6cfdb93731ea0fbc2303c)) 633 | 634 | # [3.8.0-beta.5](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2021-01-27) 635 | 636 | 637 | ### Bug Fixes 638 | 639 | * set service api url ([06be4c8](https://github.com/CrystallizeAPI/crystallize-cli/commit/06be4c8e158100a93a2c22b9e72c8d34b94ccdb7)) 640 | 641 | # [3.8.0-beta.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2021-01-27) 642 | 643 | 644 | ### Bug Fixes 645 | 646 | * no jwt token for nextjs ([1c3bd4a](https://github.com/CrystallizeAPI/crystallize-cli/commit/1c3bd4a4a1959ed004fe2a258adfbbf47157324b)) 647 | 648 | # [3.8.0-beta.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2021-01-27) 649 | 650 | 651 | ### Bug Fixes 652 | 653 | * nextjs init no longer has payment setup ([b30c1ef](https://github.com/CrystallizeAPI/crystallize-cli/commit/b30c1ef629c7f43e0822e4dc3b4a08c97a618b31)) 654 | 655 | # [3.8.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2021-01-27) 656 | 657 | 658 | ### Features 659 | 660 | * use beta nextjs boiler ([3b9f741](https://github.com/CrystallizeAPI/crystallize-cli/commit/3b9f7411da41ed56728f51c3b658a94da0537f4b)) 661 | 662 | # [3.8.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.4...v3.8.0-beta.1) (2021-01-27) 663 | 664 | 665 | ### Features 666 | 667 | * service api ([5c4d054](https://github.com/CrystallizeAPI/crystallize-cli/commit/5c4d05446771cef276bc9de10f26f8fe208c8c34)) 668 | 669 | ## [3.7.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.3...v3.7.4) (2021-01-06) 670 | 671 | 672 | ### Bug Fixes 673 | 674 | * proper check for node version ([1869613](https://github.com/CrystallizeAPI/crystallize-cli/commit/1869613892680c4f4cbb61333749e01ecd3f3931)) 675 | 676 | ## [3.7.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.2...v3.7.3) (2021-01-05) 677 | 678 | 679 | ### Bug Fixes 680 | 681 | * bump minimum node version to 12 ([601f26b](https://github.com/CrystallizeAPI/crystallize-cli/commit/601f26b157339734c5e1f1e6503c0c9bd64a7d5e)) 682 | 683 | ## [3.7.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.1...v3.7.2) (2020-12-28) 684 | 685 | 686 | ### Bug Fixes 687 | 688 | * runtime and strict mode for all files ([f5bae54](https://github.com/CrystallizeAPI/crystallize-cli/commit/f5bae54320e7ae58c746c0820e2c09e16b671329)) 689 | 690 | ## [3.7.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.0...v3.7.1) (2020-12-28) 691 | 692 | 693 | ### Bug Fixes 694 | 695 | * replace degit with gittar ([#13](https://github.com/CrystallizeAPI/crystallize-cli/issues/13)) ([4e1692c](https://github.com/CrystallizeAPI/crystallize-cli/commit/4e1692c72356d702f748b24c1a7556bf735a41c5)) 696 | 697 | ## [3.7.1-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.1-beta.1...v3.7.1-beta.2) (2020-12-28) 698 | 699 | 700 | ### Bug Fixes 701 | 702 | * highlighting for no payment choice ([f05b00b](https://github.com/CrystallizeAPI/crystallize-cli/commit/f05b00b6851bad549108ee0c55b9094753875e83)) 703 | 704 | ## [3.7.1-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.7.0...v3.7.1-beta.1) (2020-12-28) 705 | 706 | 707 | ### Bug Fixes 708 | 709 | * replace degit with gittar ([b958f39](https://github.com/CrystallizeAPI/crystallize-cli/commit/b958f397b6d233eb0f24e0da38caa275288be69e)) 710 | 711 | # [3.7.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.6.0...v3.7.0) (2020-12-09) 712 | 713 | 714 | ### Features 715 | 716 | * handle .gitignore ([4fb2870](https://github.com/CrystallizeAPI/crystallize-cli/commit/4fb287024677dd4d790ff9f68eb725ed3f1f793b)) 717 | 718 | # [3.6.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.5.1...v3.6.0) (2020-12-09) 719 | 720 | 721 | ### Bug Fixes 722 | 723 | * proper name for nuxt.js ([ffce78e](https://github.com/CrystallizeAPI/crystallize-cli/commit/ffce78ea942c2d428b56a9175a350e6f44e826f6)) 724 | 725 | 726 | ### Features 727 | 728 | * nuxt ([2ad2e5c](https://github.com/CrystallizeAPI/crystallize-cli/commit/2ad2e5c59f9c9fc7e2f78c56374dfb6ad3298ea3)) 729 | 730 | # [3.6.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.6.0-beta.1...v3.6.0-beta.2) (2020-12-04) 731 | 732 | 733 | ### Bug Fixes 734 | 735 | * proper name for nuxt.js ([ffce78e](https://github.com/CrystallizeAPI/crystallize-cli/commit/ffce78ea942c2d428b56a9175a350e6f44e826f6)) 736 | 737 | # [3.6.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.5.1...v3.6.0-beta.1) (2020-12-04) 738 | 739 | 740 | ### Features 741 | 742 | * nuxt ([2ad2e5c](https://github.com/CrystallizeAPI/crystallize-cli/commit/2ad2e5c59f9c9fc7e2f78c56374dfb6ad3298ea3)) 743 | 744 | ## [3.5.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.5.0...v3.5.1) (2020-11-16) 745 | 746 | 747 | ### Bug Fixes 748 | 749 | * alternative path for none-input env ([eed118b](https://github.com/CrystallizeAPI/crystallize-cli/commit/eed118b56e074512ddf362a372fa7a31b85f883b)) 750 | 751 | # [3.5.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.4.4...v3.5.0) (2020-11-16) 752 | 753 | 754 | ### Features 755 | 756 | * better wording and desc ([702294a](https://github.com/CrystallizeAPI/crystallize-cli/commit/702294abc7c76b98cb9aa1caffce39746e8a9dd4)) 757 | * updated deps ([555dee2](https://github.com/CrystallizeAPI/crystallize-cli/commit/555dee2f75e0105cb845b8b4c819a654c6d2baae)) 758 | 759 | ## [3.4.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.4.3...v3.4.4) (2020-11-09) 760 | 761 | 762 | ### Bug Fixes 763 | 764 | * gatsby now uses .env file ([adcba1a](https://github.com/CrystallizeAPI/crystallize-cli/commit/adcba1a30bbd69ba8c0518e35dadb8c17264cb7c)) 765 | 766 | ## [3.4.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.4.2...v3.4.3) (2020-11-05) 767 | 768 | 769 | ### Bug Fixes 770 | 771 | * use new prop name for priceVariant in Next.JS ([14e45b6](https://github.com/CrystallizeAPI/crystallize-cli/commit/14e45b6c0273965fba4026ce0d6827de4de53d34)) 772 | 773 | ## [3.4.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.4.1...v3.4.2) (2020-11-04) 774 | 775 | 776 | ### Bug Fixes 777 | 778 | * gatsby env vars fix ([c398470](https://github.com/CrystallizeAPI/crystallize-cli/commit/c39847011211def47a97363b17fb7f6fce12d5ec)) 779 | 780 | ## [3.4.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.4.0...v3.4.1) (2020-11-03) 781 | 782 | 783 | ### Bug Fixes 784 | 785 | * normalise project name ([685b2fd](https://github.com/CrystallizeAPI/crystallize-cli/commit/685b2fd851084e67a0bbd8a6ea1417dee689d56f)) 786 | 787 | # [3.4.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.3.1...v3.4.0) (2020-10-30) 788 | 789 | 790 | ### Features 791 | 792 | * new locale setup for NextJS ([905cd85](https://github.com/CrystallizeAPI/crystallize-cli/commit/905cd85898a2a96b193d581c86a0da50718a70cd)) 793 | 794 | ## [3.3.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.3.0...v3.3.1) (2020-10-21) 795 | 796 | 797 | ### Bug Fixes 798 | 799 | * app.config.json for the correct project ([7ffcbf8](https://github.com/CrystallizeAPI/crystallize-cli/commit/7ffcbf83af14996ba7452b7d9cd760767a9f0f37)) 800 | 801 | # [3.3.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.2.1...v3.3.0) (2020-10-21) 802 | 803 | 804 | ### Features 805 | 806 | * add app.config.json message ([c4e07eb](https://github.com/CrystallizeAPI/crystallize-cli/commit/c4e07ebed593d91e0424f27ec73ec8fcec85d1e6)) 807 | 808 | ## [3.2.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.2.0...v3.2.1) (2020-10-15) 809 | 810 | 811 | ### Bug Fixes 812 | 813 | * set priceVariant ([bd722e5](https://github.com/CrystallizeAPI/crystallize-cli/commit/bd722e50eb9541442445894ded979c72ef3b2cd2)) 814 | * wording ([64da2d1](https://github.com/CrystallizeAPI/crystallize-cli/commit/64da2d1315404d21349de5c682d2fb89c5336d09)) 815 | 816 | # [3.2.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.1.1...v3.2.0) (2020-10-13) 817 | 818 | 819 | ### Features 820 | 821 | * content commerce boilerplate ([3213380](https://github.com/CrystallizeAPI/crystallize-cli/commit/32133800aabf8bae7ae75a04fe3d4562dda3a330)) 822 | 823 | ## [3.1.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.1.0...v3.1.1) (2020-09-25) 824 | 825 | 826 | ### Bug Fixes 827 | 828 | # [3.1.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.0.2...v3.1.0) (2020-09-24) 829 | 830 | 831 | ### Features 832 | 833 | * highlight the install folder on end ([265c36b](https://github.com/CrystallizeAPI/crystallize-cli/commit/265c36b27a2a39ed301196ac99c3f627d5cf67ea)) 834 | 835 | ## [3.0.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.0.1...v3.0.2) (2020-09-23) 836 | 837 | 838 | ### Bug Fixes 839 | 840 | * updated readme ([9b6ad06](https://github.com/CrystallizeAPI/crystallize-cli/commit/9b6ad06a5aa3ffb3d9474e2d478197f24d2cba08)) 841 | 842 | ## [3.0.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v3.0.0...v3.0.1) (2020-09-23) 843 | 844 | 845 | ### Bug Fixes 846 | 847 | * set mollie keys if chosen ([685acbe](https://github.com/CrystallizeAPI/crystallize-cli/commit/685acbe043500910bee4e59f92ed24cda31bcd3c)) 848 | 849 | # [3.0.0](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.6.1...v3.0.0) (2020-09-23) 850 | 851 | 852 | * Revamped (#11) ([c45e750](https://github.com/CrystallizeAPI/crystallize-cli/commit/c45e750377454abe5acd1b389f59460a21def6bd)), closes [#11](https://github.com/CrystallizeAPI/crystallize-cli/issues/11) 853 | 854 | 855 | ### BREAKING CHANGES 856 | 857 | * blabla 858 | 859 | * chore(release): 3.0.0-beta.1 [skip ci] 860 | 861 | # [3.0.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.7.0-beta.4...v3.0.0-beta.1) (2020-09-23) 862 | 863 | ### chore 864 | 865 | * typing ([d6596a1](https://github.com/CrystallizeAPI/crystallize-cli/commit/d6596a1e3f9e006b4eaf28e82a27651fe26a6b45)) 866 | 867 | ### BREAKING CHANGES 868 | 869 | * blabla 870 | 871 | Co-authored-by: semantic-release-bot 872 | 873 | # [3.0.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.7.0-beta.4...v3.0.0-beta.1) (2020-09-23) 874 | 875 | 876 | ### chore 877 | 878 | * typing ([d6596a1](https://github.com/CrystallizeAPI/crystallize-cli/commit/d6596a1e3f9e006b4eaf28e82a27651fe26a6b45)) 879 | 880 | 881 | ### BREAKING CHANGES 882 | 883 | * blabla 884 | 885 | # [2.7.0-beta.4](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.7.0-beta.3...v2.7.0-beta.4) (2020-09-23) 886 | 887 | 888 | ### Features 889 | 890 | * whatever ([666888e](https://github.com/CrystallizeAPI/crystallize-cli/commit/666888e3a19a5bcd650b3f3a92c6a745b8720c2f)) 891 | 892 | # [2.7.0-beta.3](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.7.0-beta.2...v2.7.0-beta.3) (2020-09-23) 893 | 894 | 895 | ### Bug Fixes 896 | 897 | * require node 8 ([9f31ed6](https://github.com/CrystallizeAPI/crystallize-cli/commit/9f31ed6520cf5499681b109125754d7835d4efbc)) 898 | 899 | # [2.7.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.7.0-beta.1...v2.7.0-beta.2) (2020-09-23) 900 | 901 | 902 | ### Bug Fixes 903 | 904 | * remove uneeded files in package.json ([46e13b5](https://github.com/CrystallizeAPI/crystallize-cli/commit/46e13b54af076a34c6bc4b90e6755625633d9c01)) 905 | 906 | # [2.7.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v2.6.1...v2.7.0-beta.1) (2020-09-23) 907 | 908 | 909 | ### Features 910 | 911 | * initial ([36bed0f](https://github.com/CrystallizeAPI/crystallize-cli/commit/36bed0fe4f447a1cde04410469f699dc09320125)) 912 | 913 | # [1.0.0-beta.2](https://github.com/CrystallizeAPI/crystallize-cli/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2020-09-23) 914 | 915 | 916 | ### Features 917 | 918 | * feedback ([f6e2767](https://github.com/CrystallizeAPI/crystallize-cli/commit/f6e2767bd2d06be22280846df81ae7da6bfc384c)) 919 | * new feedback BREAKING CHANGE ([5555a5d](https://github.com/CrystallizeAPI/crystallize-cli/commit/5555a5dd741b7cb4c98f9a2dbc1f3a1aed1eaea7)) 920 | 921 | # [3.0.0-beta.1](https://github.com/CrystallizeAPI/crystallize-cli/compare/v1.0.0-beta.1...v3.0.0-beta.1) (2020-09-23) 922 | 923 | 924 | ### Features 925 | 926 | * new feedback BREAKING CHANGE ([5555a5d](https://github.com/CrystallizeAPI/crystallize-cli/commit/5555a5dd741b7cb4c98f9a2dbc1f3a1aed1eaea7)) 927 | 928 | # 1.0.0-beta.1 (2020-09-23) 929 | 930 | 931 | ### Features 932 | 933 | * removed lame feedback ([509252d](https://github.com/CrystallizeAPI/crystallize-cli/commit/509252d42a42ad8315c678424a29e78722427f7e)) 934 | 935 | # 1.0.0-beta.1 (2020-09-23) 936 | 937 | 938 | ### Features 939 | 940 | * inital ([d69f67f](https://github.com/CrystallizeAPI/crystallize-cli/commit/d69f67fb10a714a4fee0a53313743bcbdfce7dda)) 941 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Crystallize CLI 2 | 3 | ### Package Description 4 | 5 | #### [cli][0] 6 | 7 | This is the global CLI command, run either as `crystallize` when installed 8 | locally or `npx @crystallize/cli` when run with npx. 9 | 10 | The two main files in this are `lib/template.js` and `lib/boilerplate.js`. When 11 | the `--boilerplate, -b` flag is provided, the user will be taken through the 12 | `lib/boilerplate.js` code path. If omitted, they will be taken through 13 | `lib/template.js`. 14 | 15 | ## Local Development 16 | 17 | 1. Clone the repo with 18 | `git clone https://github.com/CrystallizeAPI/crystallize-cli.git`. 19 | 2. Run `yarn` to install any required dependencies. 20 | 3. Run `npm link`. Running `crystallize` in your terminal should now reference 21 | your local copy of the package. 22 | 23 | ## Deployment 24 | 25 | If you are a member of Crystallize you can deploy new versions of the pages by 26 | running `yarn lerna`. This will walk you through versioning any packages you've 27 | modified and publishing the packages to npm. 28 | 29 | If you are not a member of Crystallize please submit a pull request and we'll be 30 | happy to take a look 🙂. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Crystallize 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crystallize CLI 2 | 3 | [![Build Status](https://travis-ci.org/CrystallizeAPI/crystallize-cli.svg?branch=master)](https://travis-ci.org/CrystallizeAPI/crystallize-cli) 4 | 5 | Bootstrap an app or tenant running on the [headless ecommerce][1] and GraphQL 6 | based [Product Information Management][2] service [Crystallize][3]. 7 | 8 | ## Usage 9 | 10 | You'll need the following installed to use Crystallize CLI: 11 | 12 | - [Node.js][7] (>=12) 13 | 14 | To create a new app using Crystallize, simply run the following command: 15 | 16 | ```sh 17 | npx @crystallize/cli 18 | ``` 19 | 20 | This will walk you through creating a project, allowing you to choose which 21 | template and preferences you want to use. 22 | 23 | ### Show help 24 | 25 | ```sh 26 | npx @crystallize/cli --help 27 | ``` 28 | 29 | ### Bootstrapping a tenant 30 | 31 | ```sh 32 | npx @crystallize/cli --bootstrap-tenant 33 | ``` 34 | 35 | or 36 | 37 | ```sh 38 | npx @crystallize/cli -b 39 | ``` 40 | 41 | This will bootstrap an existing tenant with example data 42 | 43 | ### Templates 44 | 45 | The default mode of the Crystallize CLI is to use a template. Each template has 46 | different options that can be chosen to configure the initial project to suit 47 | your needs. 48 | 49 | Current templates include: 50 | 51 | - Complete Ecommerce (Next.js + React) 52 | - Content & Commerce (Next.js + React) 53 | - Gatsby (React) 54 | - Nuxt (Vue.js) 55 | - Service API (Node.js) 56 | - React Native (beta) 57 | 58 | ## Contributing 59 | 60 | If you'd like to help improve our CLI tool, check out the [contributing 61 | guidelines][9] for an overview of the codebase and structure. 62 | 63 | [1]: https://crystallize.com/product 64 | [2]: https://crystallize.com/product/product-information-management 65 | [3]: https://crystallize.com/ 66 | [4]: https://github.com/CrystallizeAPI/crystallize-nextjs-boilerplate 67 | [5]: https://github.com/CrystallizeAPI/crystallize-react-native-boilerplate 68 | [6]: https://github.com/CrystallizeAPI/crystallize-flutter-boilerplate 69 | [7]: https://nodejs.org 70 | [9]: 71 | https://github.com/CrystallizeAPI/crystallize-cli/blob/master/CONTRIBUTING.md 72 | -------------------------------------------------------------------------------- /_create-specs.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const { writeFileSync } = require('fs'); 4 | const { resolve } = require('path'); 5 | 6 | const { Bootstrapper } = require('@crystallize/import-utilities'); 7 | 8 | if ( 9 | !process.env.CRYSTALLIZE_ACCESS_TOKEN_ID || 10 | !process.env.CRYSTALLIZE_ACCESS_TOKEN_SECRET 11 | ) { 12 | throw new Error( 13 | 'CRYSTALLIZE_ACCESS_TOKEN_ID and CRYSTALLIZE_ACCESS_TOKEN_SECRET must be set' 14 | ); 15 | } 16 | 17 | function getFullSpec(tenantIdentifier) { 18 | const bootstrapper = new Bootstrapper(); 19 | 20 | bootstrapper.setAccessToken( 21 | process.env.CRYSTALLIZE_ACCESS_TOKEN_ID, 22 | process.env.CRYSTALLIZE_ACCESS_TOKEN_SECRET 23 | ); 24 | 25 | bootstrapper.setTenantIdentifier(tenantIdentifier); 26 | 27 | console.log('⏳ Getting the full spec for ' + tenantIdentifier); 28 | 29 | return bootstrapper.createSpec(); 30 | } 31 | 32 | function removeItemCataloguePath(item) { 33 | delete item.cataloguePath; 34 | 35 | if (item.children) { 36 | item.children.forEach(removeItemCataloguePath); 37 | } 38 | } 39 | 40 | async function furniture() { 41 | const spec = await getFullSpec('furniture'); 42 | 43 | const itemShop = spec.items.find((i) => i.cataloguePath === '/shop'); 44 | const itemStories = spec.items.find((i) => i.cataloguePath === '/stories'); 45 | const itemAbout = spec.items.find((i) => i.cataloguePath === '/about'); 46 | const itemAssets = spec.items.find((i) => i.cataloguePath === '/assets'); 47 | const itemFrontpage = spec.items.find( 48 | (i) => i.cataloguePath === '/frontpage-2021' 49 | ); 50 | 51 | // Only include a few shop sub folders 52 | itemShop.children = itemShop.children.filter((c) => 53 | ['/shop/plants', '/shop/chairs'].includes(c.cataloguePath) 54 | ); 55 | 56 | spec.items = [itemShop, itemStories, itemAbout, itemFrontpage, itemAssets]; 57 | 58 | // Remove references as we do not want to update existing items 59 | spec.items.forEach(removeItemCataloguePath); 60 | 61 | writeFileSync( 62 | resolve(__dirname, `./src/journeys/_shared/specs/furniture.json`), 63 | JSON.stringify(spec, null, 1), 64 | 'utf-8' 65 | ); 66 | 67 | console.log(`✔ furniture done`); 68 | } 69 | 70 | async function voyage() { 71 | const spec = await getFullSpec('voyage'); 72 | 73 | // Remove references as we do not want to update existing items 74 | spec.items.forEach(removeItemCataloguePath); 75 | 76 | writeFileSync( 77 | resolve(__dirname, `./src/journeys/_shared/specs/voyage.json`), 78 | JSON.stringify(spec, null, 1), 79 | 'utf-8' 80 | ); 81 | 82 | console.log(`✔ voyage done`); 83 | } 84 | 85 | async function photofinder() { 86 | const spec = await getFullSpec('photofinder'); 87 | 88 | // Remove references as we do not want to update existing items 89 | spec.items.forEach(removeItemCataloguePath); 90 | 91 | writeFileSync( 92 | resolve(__dirname, `./src/journeys/_shared/specs/photofinder.json`), 93 | JSON.stringify(spec, null, 1), 94 | 'utf-8' 95 | ); 96 | 97 | console.log(`✔ photofinder done`); 98 | } 99 | 100 | async function conference() { 101 | const spec = await getFullSpec('conference-boilerplate'); 102 | 103 | // Remove references as we do not want to update existing items 104 | spec.items.forEach(removeItemCataloguePath); 105 | 106 | writeFileSync( 107 | resolve(__dirname, `./src/journeys/_shared/specs/conference-boilerplate.json`), 108 | JSON.stringify(spec, null, 1), 109 | 'utf-8' 110 | ); 111 | 112 | console.log(`✔ conference done`); 113 | } 114 | 115 | (async function createSpecs() { 116 | await furniture(); 117 | await voyage(); 118 | await photofinder(); 119 | await conference(); 120 | process.exit(0); 121 | })(); 122 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var currentNodeVersion = process.versions.node; 5 | var semver = currentNodeVersion.split('.'); 6 | var major = semver[0]; 7 | 8 | const minVersion = 12; 9 | 10 | if (major < minVersion) { 11 | console.error(` 12 | You are running Node.js v${currentNodeVersion} 13 | Crystallize CLI requires Node ${minVersion} or higher. 14 | Please update your version of Node. 15 | `); 16 | process.exit(1); 17 | } 18 | 19 | require('./src/cli.js'); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@crystallize/cli", 3 | "version": "3.31.7", 4 | "license": "MIT", 5 | "engines": { 6 | "node": ">=12" 7 | }, 8 | "scripts": { 9 | "dev": "node index.js", 10 | "test": "xo && ava", 11 | "lint": "eslint src", 12 | "release": "standard-version" 13 | }, 14 | "main": "index.js", 15 | "bin": { 16 | "crystallize": "./index.js" 17 | }, 18 | "husky": { 19 | "hooks": { 20 | "pre-commit": "eslint src --fix", 21 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 22 | } 23 | }, 24 | "commitlint": { 25 | "extends": [ 26 | "@commitlint/config-conventional" 27 | ] 28 | }, 29 | "plugins": [ 30 | "@semantic-release/commit-analyzer", 31 | "@semantic-release/release-notes-generator", 32 | "@semantic-release/changelog", 33 | "@semantic-release/github", 34 | "@semantic-release/npm", 35 | "@semantic-release/git" 36 | ], 37 | "config": { 38 | "commitizen": { 39 | "path": "./node_modules/cz-conventional-changelog" 40 | } 41 | }, 42 | "release": { 43 | "prepare": [ 44 | "@semantic-release/changelog", 45 | "@semantic-release/npm", 46 | { 47 | "path": "@semantic-release/git", 48 | "assets": [ 49 | "package.json", 50 | "CHANGELOG.md" 51 | ], 52 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 53 | } 54 | ] 55 | }, 56 | "dependencies": { 57 | "@crystallize/import-utilities": "^0.94.1", 58 | "conf": "^10.0.1", 59 | "fs-extra": "^9.0.1", 60 | "gittar": "^0.1.1", 61 | "immer": "^9.0.6", 62 | "import-jsx": "^4.0.0", 63 | "ink": "^3.2.0", 64 | "ink-link": "^2.0.0", 65 | "ink-text-input": "^4.0.2", 66 | "meow": "^7.1.1", 67 | "node-fetch": "^2.6.7", 68 | "os": "^0.1.1", 69 | "react": "^17.0.2" 70 | }, 71 | "devDependencies": { 72 | "@ava/babel": "^1.0.1", 73 | "@babel/preset-env": "^7.15.8", 74 | "@babel/preset-react": "^7.14.5", 75 | "@babel/register": "^7.15.3", 76 | "@commitlint/cli": "^11.0.0", 77 | "@commitlint/config-conventional": "^11.0.0", 78 | "@semantic-release/changelog": "^5.0.1", 79 | "@semantic-release/commit-analyzer": "^8.0.1", 80 | "@semantic-release/git": "^9.0.0", 81 | "@semantic-release/npm": "^7.0.6", 82 | "@semantic-release/release-notes-generator": "^9.0.1", 83 | "ava": "^3.12.1", 84 | "chalk": "^4.1.2", 85 | "eslint": "^7.9.0", 86 | "eslint-config-xo-react": "^0.23.0", 87 | "eslint-plugin-react": "^7.20.6", 88 | "eslint-plugin-react-hooks": "^4.2.0", 89 | "husky": "^4.3.0", 90 | "ink-testing-library": "^2.0.1", 91 | "standard-version": "^9.0.0", 92 | "xo": "^0.33.1" 93 | }, 94 | "ava": { 95 | "babel": true, 96 | "require": [ 97 | "@babel/register" 98 | ] 99 | }, 100 | "babel": { 101 | "presets": [ 102 | "@babel/preset-env", 103 | "@babel/preset-react" 104 | ] 105 | }, 106 | "xo": { 107 | "extends": "xo-react", 108 | "rules": { 109 | "react/prop-types": "off" 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.yml] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /src/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /src/cli-utils/default-gitignore.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | module.exports = `# Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Dependency directories 12 | node_modules/ 13 | jspm_packages/ 14 | 15 | # Optional npm cache directory 16 | .npm 17 | 18 | # Optional eslint cache 19 | .eslintcache 20 | 21 | # Yarn Integrity file 22 | .yarn-integrity 23 | 24 | # local dotenv environment variables file 25 | .env*.local 26 | 27 | # OSX Finder folder settings 28 | .DS_STORE 29 | 30 | # Files containing sensitive variables 31 | .env.* 32 | 33 | # Vercel deployment settings 34 | .vercel`; 35 | -------------------------------------------------------------------------------- /src/cli-utils/download-project.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const gittar = require('gittar'); 6 | const { Text, Box } = require('ink'); 7 | 8 | const repos = { 9 | 'Next.js': 'crystallize-nextjs-boilerplate#main', 10 | 'Next.js - Content and commerce': 'content-commerce-boilerplate#main', 11 | Gatsby: 'crystallize-gatsby-boilerplate#main', 12 | 'Nuxt.js - Content and product listing': 13 | 'crystallize-nuxtjs-boilerplate#main', 14 | 'React Native': 'crystallize-react-native-boilerplate#main', 15 | 'Service API - Backend for any of the frontends': 16 | 'service-api-boilerplate#main', 17 | 'Next.js - Subscription Commerce': 'crystallize-saas-boilerplate#main', 18 | 'Next.js - Conference': 'conference-boilerplate#main' 19 | }; 20 | 21 | function DownloadProject({ 22 | answers, 23 | projectName, 24 | projectPath, 25 | resolveStep, 26 | flags, 27 | }) { 28 | React.useEffect(() => { 29 | if (projectName) { 30 | const repo = `CrystallizeAPI/${repos[answers.boilerplate]}`; 31 | 32 | gittar 33 | .fetch(repo, { 34 | force: true, 35 | }) 36 | .then(async (a) => { 37 | if (flags.info) { 38 | console.log(a); 39 | } 40 | 41 | await gittar.extract(repo, projectPath); 42 | 43 | // Why a timeout here? And why was it 2000ms? 44 | // setTimeout(resolveStep, 2000); 45 | resolveStep(); 46 | }); 47 | } 48 | }, [answers.boilerplate, flags.info, projectName, projectPath, resolveStep]); 49 | 50 | return ( 51 | 52 | Downloading the {answers.boilerplate} boilerplate... 53 | 54 | ); 55 | } 56 | 57 | module.exports = { 58 | DownloadProject, 59 | }; 60 | -------------------------------------------------------------------------------- /src/cli-utils/fetch-from-crystallize.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fetch = require('node-fetch'); 5 | 6 | function createAPICaller(uri, { headers } = {}) { 7 | return async function ({ query, variables }) { 8 | const body = JSON.stringify({ query, variables }); 9 | 10 | const response = await fetch(uri, { 11 | method: 'post', 12 | headers: { 13 | 'content-type': 'application/json', 14 | ...headers, 15 | }, 16 | body, 17 | }); 18 | 19 | if (!response.ok) { 20 | throw new Error(await response.text()); 21 | } 22 | 23 | return response.json(); 24 | }; 25 | } 26 | 27 | const callCrystallizeMarketingCatalogue = createAPICaller( 28 | 'https://api.crystallize.com/crystallize_marketing/catalogue' 29 | ); 30 | 31 | module.exports = { 32 | callCrystallizeMarketingCatalogue, 33 | createAPICaller, 34 | }; 35 | -------------------------------------------------------------------------------- /src/cli-utils/get-multilingual.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const importJsx = require('import-jsx'); 6 | const { Text, Box, Newline } = require('ink'); 7 | const { UncontrolledTextInput } = require('ink-text-input'); 8 | 9 | const Select = importJsx('../ui-modules/select'); 10 | 11 | function GetMultilingual({ onChange }) { 12 | const [add, setAdd] = React.useState(false); 13 | 14 | if (add) { 15 | return ( 16 | <> 17 | 18 | 19 | Enter the supported languages: 20 | 21 | onChange(query ? query.split(',') : ['en'])} 24 | /> 25 | 26 | 27 | 28 | The language codes needs to match the ones in Crystallize 29 | 30 | 31 | Read more about it here: 32 | https://crystallize.com/learn/user-guides/getting-started/configuring-languages 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | 40 | return ( 41 | <> 42 | Want to add enable multiple languages? 43 | { 64 | if (item.value === 'yes') { 65 | setAdd(true); 66 | } else { 67 | onChange([]); 68 | } 69 | }} 70 | /> 71 | 72 | ); 73 | } 74 | 75 | module.exports = GetPaymentMethods; 76 | -------------------------------------------------------------------------------- /src/cli-utils/init-gatsby.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | async function initGatsby({ answers, projectPath }) { 9 | const appConfig = { 10 | locales: [], 11 | }; 12 | 13 | // Set locales 14 | (answers.multilingual || ['en']).forEach(function addToConfig(lng) { 15 | appConfig.locales.push({ 16 | locale: lng, 17 | displayName: lng, 18 | urlPrefix: answers.multilingual ? lng : '', 19 | appLanguage: 'en-US', 20 | crystallizeCatalogueLanguage: lng, 21 | crystallizePriceVariant: 'default', 22 | }); 23 | }); 24 | 25 | // Update app.config.json file 26 | fs.writeFileSync( 27 | path.resolve(projectPath, 'app.config.json'), 28 | JSON.stringify(appConfig, null, 3) 29 | ); 30 | 31 | // Setup Crystallize config 32 | fs.writeFileSync( 33 | path.resolve(projectPath, '.env'), 34 | [ 35 | `GATSBY_CRYSTALLIZE_TENANT_ID=${answers.tenant}`, 36 | `GATSBY_PUBLIC_SERVICE_API_URL=${answers.serviceAPIURL}`, 37 | ].join(os.EOL), 38 | 'utf-8' 39 | ); 40 | 41 | // Add a sensible .gitignore 42 | fs.writeFileSync( 43 | path.resolve(projectPath, '.gitignore'), 44 | `${require('./default-gitignore')} 45 | 46 | # gatsby files 47 | .cache/ 48 | public` 49 | ); 50 | } 51 | 52 | module.exports = initGatsby; 53 | -------------------------------------------------------------------------------- /src/cli-utils/init-nextjs-conference.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | function initNextJSConference({ answers, projectPath }) { 9 | // Update website .env file 10 | const envVarsWebsite = { 11 | NEXT_PUBLIC_CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 12 | NEXT_PUBLIC_SERVICE_API_URL: 'https://api.conference.superfast.shop', 13 | }; 14 | fs.writeFileSync( 15 | path.resolve(projectPath, 'website/.env'), 16 | Object.keys(envVarsWebsite) 17 | .map((key) => `${key}=${envVarsWebsite[key]}`) 18 | .join(os.EOL) + os.EOL 19 | ); 20 | 21 | // Update Service API .env.local file 22 | const envVarsServiceAPI = { 23 | CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 24 | JWT_SECRET: 'some-secret-jwt-key-goes-here', 25 | EMAIL_FROM: '', 26 | CRYSTALLIZE_ACCESS_TOKEN_ID: '', 27 | CRYSTALLIZE_ACCESS_TOKEN_SECRET: '', 28 | STRIPE_SECRET_KEY: '', 29 | STRIPE_PUBLISHABLE_KEY: '', 30 | SENDGRID_API_KEY: '', 31 | SERVICE_CALLBACK_HOST: '', 32 | }; 33 | fs.writeFileSync( 34 | path.resolve(projectPath, 'service-api/.env.local'), 35 | Object.keys(envVarsServiceAPI) 36 | .map((key) => `${key}=${envVarsServiceAPI[key]}`) 37 | .join(os.EOL) + os.EOL 38 | ); 39 | 40 | // Add a sensible .gitignore 41 | const gitIgnore = `${require('./default-gitignore')} 42 | 43 | # Next build output 44 | .next`; 45 | fs.writeFileSync(path.resolve(projectPath, 'website/.gitignore'), gitIgnore); 46 | fs.writeFileSync( 47 | path.resolve(projectPath, 'service-api/.gitignore'), 48 | gitIgnore 49 | ); 50 | 51 | // Remove boilerplate toolbar 52 | const appPath = path.resolve(projectPath, 'website/src/pages/_app.tsx'); 53 | const _app = fs.readFileSync(appPath, 'utf-8'); 54 | const firstParts = _app.split('{/*crystallize-boilerplates-topbar-start*/}'); 55 | 56 | // Check for presence of the code 57 | if (firstParts.length > 1) { 58 | const secondParts = firstParts[1].split( 59 | '{/*crystallize-boilerplates-topbar-end*/}' 60 | ); 61 | fs.writeFileSync(appPath, firstParts[0] + secondParts[1]); 62 | } 63 | } 64 | 65 | module.exports = initNextJSConference; 66 | -------------------------------------------------------------------------------- /src/cli-utils/init-nextjs-content-commerce.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | function initNextJSContentCommerce({ answers, projectPath }) { 9 | const envVars = { 10 | NEXT_PUBLIC_CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 11 | }; 12 | 13 | // Update .env file 14 | fs.writeFileSync( 15 | path.resolve(projectPath, '.env'), 16 | Object.keys(envVars) 17 | .map((key) => `${key}=${envVars[key]}`) 18 | .join(os.EOL) + os.EOL 19 | ); 20 | 21 | // Add a sensible .gitignore 22 | fs.writeFileSync( 23 | path.resolve(projectPath, '.gitignore'), 24 | `${require('./default-gitignore')} 25 | 26 | # Next build output 27 | .next` 28 | ); 29 | 30 | // Remove boilerplate toolbar 31 | const appPath = path.resolve(projectPath, 'pages/index.js'); 32 | const _app = fs.readFileSync(appPath, 'utf-8'); 33 | const firstParts = _app.split('{/*crystallize-boilerplates-topbar-start*/}'); 34 | 35 | // Check for presence of the code 36 | if (firstParts.length > 1) { 37 | const secondParts = firstParts[1].split( 38 | '{/*crystallize-boilerplates-topbar-end*/}' 39 | ); 40 | fs.writeFileSync(appPath, firstParts[0] + secondParts[1]); 41 | } 42 | } 43 | 44 | module.exports = initNextJSContentCommerce; 45 | -------------------------------------------------------------------------------- /src/cli-utils/init-nextjs-subscription-commerce.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | function initNextJSSubscriptionCommerce({ answers, projectPath }) { 9 | // Update website .env file 10 | const envVarsWebsite = { 11 | NEXT_PUBLIC_CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 12 | NEXT_PUBLIC_SERVICE_API_URL: 'https://api.photofinder.superfast.shop', 13 | }; 14 | fs.writeFileSync( 15 | path.resolve(projectPath, 'website/.env'), 16 | Object.keys(envVarsWebsite) 17 | .map((key) => `${key}=${envVarsWebsite[key]}`) 18 | .join(os.EOL) + os.EOL 19 | ); 20 | 21 | // Update Service API .env.local file 22 | const envVarsServiceAPI = { 23 | CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 24 | JWT_SECRET: 'some-secret-jwt-key-goes-here', 25 | EMAIL_FROM: '', 26 | CRYSTALLIZE_ACCESS_TOKEN_ID: '', 27 | CRYSTALLIZE_ACCESS_TOKEN_SECRET: '', 28 | STRIPE_SECRET_KEY: '', 29 | STRIPE_PUBLISHABLE_KEY: '', 30 | SENDGRID_API_KEY: '', 31 | SERVICE_CALLBACK_HOST: '', 32 | TILLIT_API_KEY: '', 33 | }; 34 | fs.writeFileSync( 35 | path.resolve(projectPath, 'service-api/.env.local'), 36 | Object.keys(envVarsServiceAPI) 37 | .map((key) => `${key}=${envVarsServiceAPI[key]}`) 38 | .join(os.EOL) + os.EOL 39 | ); 40 | 41 | // Add a sensible .gitignore 42 | const gitIgnore = `${require('./default-gitignore')} 43 | 44 | # Next build output 45 | .next`; 46 | fs.writeFileSync(path.resolve(projectPath, 'website/.gitignore'), gitIgnore); 47 | fs.writeFileSync( 48 | path.resolve(projectPath, 'service-api/.gitignore'), 49 | gitIgnore 50 | ); 51 | 52 | // Remove boilerplate toolbar 53 | const appPath = path.resolve(projectPath, 'website/src/pages/_app.tsx'); 54 | const _app = fs.readFileSync(appPath, 'utf-8'); 55 | const firstParts = _app.split('{/*crystallize-boilerplates-topbar-start*/}'); 56 | 57 | // Check for presence of the code 58 | if (firstParts.length > 1) { 59 | const secondParts = firstParts[1].split( 60 | '{/*crystallize-boilerplates-topbar-end*/}' 61 | ); 62 | fs.writeFileSync(appPath, firstParts[0] + secondParts[1]); 63 | } 64 | } 65 | 66 | module.exports = initNextJSSubscriptionCommerce; 67 | -------------------------------------------------------------------------------- /src/cli-utils/init-nextjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | async function initNextJS({ answers, projectPath }) { 9 | const envVars = { 10 | NEXT_PUBLIC_CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 11 | NEXT_PUBLIC_SERVICE_API_URL: answers.serviceAPIURL, 12 | }; 13 | 14 | const appConfig = { 15 | locales: [], 16 | }; 17 | 18 | // Set locales 19 | const locales = answers.multilingual || ['en']; 20 | locales.forEach(function addToConfig(lng) { 21 | appConfig.locales.push({ 22 | locale: lng, 23 | displayName: lng, 24 | appLanguage: 'en-US', 25 | crystallizeCatalogueLanguage: lng, 26 | crystallizePriceVariant: 'default', 27 | }); 28 | }); 29 | 30 | let nextJsConfigContent = fs.readFileSync( 31 | path.resolve(projectPath, 'next-i18next.config.js'), 32 | 'utf-8' 33 | ); 34 | nextJsConfigContent = nextJsConfigContent.replace( 35 | `locales: ['en']`, 36 | `locales: ${JSON.stringify(locales)}` 37 | ); 38 | nextJsConfigContent = nextJsConfigContent.replace( 39 | `defaultLocale: 'en'`, 40 | `defaultLocale: '${locales[0]}'` 41 | ); 42 | fs.writeFileSync( 43 | path.resolve(projectPath, 'next-i18next.config.js'), 44 | nextJsConfigContent, 45 | 'utf-8' 46 | ); 47 | 48 | // Update app.config.json file 49 | fs.writeFileSync( 50 | path.resolve(projectPath, 'app.config.json'), 51 | JSON.stringify(appConfig, null, 3) 52 | ); 53 | 54 | // Update .env file 55 | fs.writeFileSync( 56 | path.resolve(projectPath, '.env'), 57 | Object.keys(envVars) 58 | .map((key) => `${key}=${envVars[key]}`) 59 | .join(os.EOL) + os.EOL 60 | ); 61 | 62 | // Add a sensible .gitignore 63 | fs.writeFileSync( 64 | path.resolve(projectPath, '.gitignore'), 65 | `${require('./default-gitignore')} 66 | 67 | # Next build output 68 | .next` 69 | ); 70 | 71 | // Remove boilerplate toolbar 72 | const appPath = path.resolve(projectPath, 'src/pages/_app.js'); 73 | const _app = fs.readFileSync(appPath, 'utf-8'); 74 | const firstParts = _app.split('{/*crystallize-boilerplates-topbar-start*/}'); 75 | 76 | // Check for presence of the code 77 | if (firstParts.length > 1) { 78 | const secondParts = firstParts[1].split( 79 | '{/*crystallize-boilerplates-topbar-end*/}' 80 | ); 81 | fs.writeFileSync(appPath, firstParts[0] + secondParts[1]); 82 | } 83 | } 84 | 85 | module.exports = initNextJS; 86 | -------------------------------------------------------------------------------- /src/cli-utils/init-nuxtjs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | async function initNuxtJS({ answers, projectPath }) { 9 | try { 10 | const envVars = { 11 | CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 12 | SERVICE_API_URL: answers.serviceAPIURL, 13 | SITE_URL: '', 14 | }; 15 | 16 | // Set locale 17 | const lng = answers.locale || 'en'; 18 | 19 | // Update app.config.json file. 20 | fs.writeFileSync( 21 | path.resolve(projectPath, 'app.config.json'), 22 | JSON.stringify( 23 | { 24 | locale: { 25 | locale: lng, 26 | displayName: lng, 27 | appLanguage: 'en-US', 28 | crystallizeCatalogueLanguage: lng, 29 | crystallizePriceVariant: 'default', 30 | }, 31 | }, 32 | null, 33 | 3 34 | ) 35 | ); 36 | 37 | // Update .env file 38 | fs.writeFileSync( 39 | path.resolve(projectPath, '.env'), 40 | Object.keys(envVars) 41 | .map((key) => `${key}=${envVars[key]}`) 42 | .join(os.EOL) + os.EOL 43 | ); 44 | 45 | // Add a sensible .gitignore 46 | fs.writeFileSync( 47 | path.resolve(projectPath, '.gitignore'), 48 | `${require('./default-gitignore')} 49 | 50 | # nuxt.js build output 51 | .nuxt 52 | 53 | # Nuxt generate 54 | dist 55 | 56 | # vuepress build output 57 | .vuepress/dist` 58 | ); 59 | } catch (error) { 60 | console.log(error); 61 | } 62 | } 63 | 64 | module.exports = initNuxtJS; 65 | -------------------------------------------------------------------------------- /src/cli-utils/init-project.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const fs = require('fs-extra'); 6 | const path = require('path'); 7 | const os = require('os'); 8 | const { Box, Text } = require('ink'); 9 | const exec = require('child_process').exec; 10 | 11 | let began = false; 12 | 13 | const feedbacksBase = [ 14 | 'Fetching the dependencies...', 15 | 'Still fetching...', 16 | 'Unpacking...', 17 | 'Preparing files for install...', 18 | 'Installing...', 19 | 'Still installing...', 20 | 'Daydreaming...', 21 | 'Growing that node_modules...', 22 | 'Looking for car keys...', 23 | 'Looking for the car...', 24 | ]; 25 | 26 | function InitProject(props) { 27 | const { 28 | answers, 29 | projectName, 30 | projectPath, 31 | onDone, 32 | shouldUseYarn, 33 | flags, 34 | } = props; 35 | const [feedbackIndex, setFeedbackIndex] = React.useState(0); 36 | 37 | const feedbacks = [...feedbacksBase]; 38 | 39 | if (answers.bootstrapTenant !== 'no') { 40 | feedbacks.push( 41 | 'Bootstrapping tenant...', 42 | 'Populating shapes...', 43 | 'Adding topics...', 44 | 'Creating grids...', 45 | 'Adding items...', 46 | 'Publishing...', 47 | 'Populating shapes...', 48 | 'Adding topics...', 49 | 'Creating grids...', 50 | 'Adding items...', 51 | 'Publishing...' 52 | ); 53 | } 54 | 55 | // Give different feedback messages 56 | React.useEffect(() => { 57 | let timeout; 58 | let ms = 10000; 59 | 60 | function changeFeedback() { 61 | setFeedbackIndex((f) => { 62 | let newI = f + 1; 63 | if (newI === feedbacks.length) { 64 | newI = 0; 65 | } 66 | 67 | return newI; 68 | }); 69 | 70 | ms *= 1.3; 71 | timeout = setTimeout(changeFeedback, ms); 72 | } 73 | 74 | timeout = setTimeout(changeFeedback, ms); 75 | 76 | return () => clearTimeout(timeout); 77 | }); 78 | 79 | // Install node deps 80 | React.useEffect(() => { 81 | async function go() { 82 | if (began) { 83 | return; 84 | } 85 | 86 | began = true; 87 | 88 | if (answers['service-api']) { 89 | await require('./init-service-api')(props); 90 | } 91 | 92 | // Navigate to the new folder 93 | process.chdir(projectPath); 94 | 95 | // Remove any lock files 96 | fs.removeSync(path.resolve('package-lock.json')); 97 | fs.removeSync(path.resolve('yarn.lock')); 98 | 99 | // Update the package.json with proper name 100 | if ( 101 | !answers['nextjs-conference'] && 102 | !answers['nextjs-subscription-commerce'] 103 | ) { 104 | const oldPackageJson = JSON.parse( 105 | fs.readFileSync(path.resolve('package.json'), 'utf-8') 106 | ); 107 | 108 | if (answers['service-api']) { 109 | delete oldPackageJson.scripts['dev:boilerplate']; 110 | } 111 | 112 | fs.writeFileSync( 113 | path.resolve(projectPath, 'package.json'), 114 | JSON.stringify( 115 | { 116 | ...oldPackageJson, 117 | name: projectName, 118 | version: '1.0.0', 119 | private: true, 120 | }, 121 | null, 122 | 2 123 | ) + os.EOL 124 | ); 125 | } 126 | 127 | if (answers.nextjs) { 128 | await require('./init-nextjs')(props); 129 | } else if (answers['nextjs-content-commerce']) { 130 | await require('./init-nextjs-content-commerce')(props); 131 | } else if (answers.gatsby) { 132 | await require('./init-gatsby')(props); 133 | } else if (answers.nuxtjs) { 134 | await require('./init-nuxtjs')(props); 135 | } else if (answers.rn) { 136 | await require('./init-rn')(props); 137 | } else if (answers['nextjs-subscription-commerce']) { 138 | await require('./init-nextjs-subscription-commerce')(props); 139 | } else if (answers['nextjs-conference']) { 140 | await require('./init-nextjs-conference')(props); 141 | } 142 | 143 | if ( 144 | answers['nextjs-conference'] || 145 | answers['nextjs-subscription-commerce'] 146 | ) { 147 | process.chdir(`${projectPath}/service-api`); 148 | await installDeps({ shouldUseYarn, flags }); 149 | process.chdir(`${projectPath}/website`); 150 | await installDeps({ shouldUseYarn, flags }); 151 | 152 | onDone(); 153 | } else { 154 | await installDeps({ shouldUseYarn, flags }); 155 | onDone(); 156 | } 157 | } 158 | go(); 159 | }); 160 | 161 | return ( 162 | 163 | {feedbacks[feedbackIndex]} 164 | 165 | ); 166 | } 167 | 168 | async function installDeps({ shouldUseYarn, flags }) { 169 | return new Promise((resolve, reject) => { 170 | exec( 171 | shouldUseYarn ? 'yarnpkg install' : 'npm install', 172 | { 173 | stdio: flags.info ? 'inherit' : 'ignore', 174 | }, 175 | async function (err) { 176 | if (err) { 177 | reject(err); 178 | process.exit(1); 179 | } 180 | 181 | resolve(); 182 | } 183 | ); 184 | }); 185 | } 186 | 187 | module.exports = { 188 | InitProject, 189 | }; 190 | -------------------------------------------------------------------------------- /src/cli-utils/init-rn.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | async function initReactNative({ answers, projectPath }) { 9 | const envVars = { 10 | CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 11 | SERVICE_API_URL: answers.serviceAPIURL, 12 | }; 13 | 14 | // Update .env file 15 | fs.writeFileSync( 16 | path.resolve(projectPath, '.env'), 17 | Object.keys(envVars) 18 | .map((key) => `${key}=${envVars[key]}`) 19 | .join(os.EOL) + os.EOL 20 | ); 21 | } 22 | 23 | module.exports = initReactNative; 24 | -------------------------------------------------------------------------------- /src/cli-utils/init-service-api.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const os = require('os'); 7 | 8 | async function initServiceAPI({ answers, projectPath }) { 9 | const envVars = { 10 | CRYSTALLIZE_TENANT_IDENTIFIER: answers.tenant, 11 | }; 12 | 13 | const envLocalVars = { 14 | JWT_SECRET: 'super-secret-jwt-token', 15 | EMAIL_FROM: 'example@crystallize.com', 16 | CRYSTALLIZE_ACCESS_TOKEN_ID: '', 17 | CRYSTALLIZE_ACCESS_TOKEN_SECRET: '', 18 | KLARNA_PASSWORD: '', 19 | KLARNA_USERNAME: '', 20 | STRIPE_SECRET_KEY: '', 21 | STRIPE_PUBLISHABLE_KEY: '', 22 | VIPPS_CLIENT_ID: '', 23 | VIPPS_CLIENT_SECRET: '', 24 | VIPPS_MERCHANT_SERIAL: '', 25 | VIPPS_SUB_KEY: '', 26 | SENDGRID_API_KEY: '', 27 | MOLLIE_API_KEY: '', 28 | SERVICE_CALLBACK_HOST: '', 29 | }; 30 | 31 | const platformPath = { 32 | vercel: 'platform-templates/vercel', 33 | 'serverless-aws': 'platform-templates/serverless-aws', 34 | }[answers.serviceAPIPlatform]; 35 | 36 | if (!platformPath) { 37 | throw new Error( 38 | `Can't determine platformPath for "${answers.serviceAPIPlatform}"` 39 | ); 40 | } 41 | 42 | // Copy the selected platform template up to root 43 | fs.copySync( 44 | path.resolve(projectPath, platformPath), 45 | path.resolve(projectPath, '.'), 46 | { overwrite: true, recursive: true } 47 | ); 48 | 49 | // Delete the platform template 50 | fs.removeSync(path.resolve(projectPath, 'platform-templates')); 51 | 52 | /* 53 | // Include Stripe credentials if stripe is selected 54 | if (answers.paymentMethods.includes('stripe')) { 55 | envLocalVars.STRIPE_SECRET_KEY = ''; 56 | envLocalVars.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = ''; 57 | } 58 | 59 | // Include Klarna credentials if klarna is selected 60 | if (answers.paymentMethods.includes('klarna')) { 61 | envLocalVars.KLARNA_USERNAME = ''; 62 | envLocalVars.KLARNA_PASSWORD = ''; 63 | } 64 | 65 | // Include Vipps credentials if selected 66 | if (answers.paymentMethods.includes('vipps')) { 67 | envLocalVars.VIPPS_CLIENT_ID = ''; 68 | envLocalVars.VIPPS_CLIENT_SECRET = ''; 69 | envLocalVars.VIPPS_MERCHANT_SERIAL = ''; 70 | envLocalVars.VIPPS_SUB_KEY = ''; 71 | } 72 | 73 | // Include Mollie credentials if selected 74 | if (answers.paymentMethods.includes('mollie')) { 75 | envLocalVars.MOLLIE_API_KEY = ''; 76 | } 77 | 78 | envLocalVars.SENDGRID_API_KEY = ''; 79 | */ 80 | 81 | // Update .env file 82 | fs.writeFileSync( 83 | path.resolve(projectPath, '.env'), 84 | Object.keys(envVars) 85 | .map((key) => `${key}=${envVars[key]}`) 86 | .join(os.EOL) + os.EOL 87 | ); 88 | 89 | // Update .env.local file 90 | fs.writeFileSync( 91 | path.resolve(projectPath, '.env.local'), 92 | Object.keys(envLocalVars) 93 | .map((key) => `${key}=${envLocalVars[key]}`) 94 | .join(os.EOL) + os.EOL 95 | ); 96 | 97 | // Example file of all config options 98 | fs.writeFileSync( 99 | path.resolve(projectPath, '.env.local.example'), 100 | `EMAIL_FROM= 101 | CRYSTALLIZE_SECRET_TOKEN_ID= 102 | CRYSTALLIZE_SECRET_TOKEN_SECRET= 103 | JWT_SECRET= 104 | KLARNA_PASSWORD= 105 | KLARNA_USERNAME= 106 | STRIPE_SECRET_KEY= 107 | STRIPE_PUBLISHABLE_KEY= 108 | VIPPS_CLIENT_ID= 109 | VIPPS_CLIENT_SECRET= 110 | VIPPS_MERCHANT_SERIAL= 111 | VIPPS_SUB_KEY= 112 | SENDGRID_API_KEY= 113 | MOLLIE_API_KEY= 114 | SERVICE_CALLBACK_HOST= 115 | ` 116 | ); 117 | 118 | // Add a sensible .gitignore 119 | fs.writeFileSync( 120 | path.resolve(projectPath, '.gitignore'), 121 | require('./default-gitignore') 122 | ); 123 | } 124 | 125 | module.exports = initServiceAPI; 126 | -------------------------------------------------------------------------------- /src/cli-utils/tips.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Text, Box, Newline } = require('ink'); 6 | 7 | const { 8 | callCrystallizeMarketingCatalogue, 9 | } = require('./fetch-from-crystallize'); 10 | 11 | const tips = [ 12 | () => ( 13 | 14 | Want to learn more about Crystallize? Check out 15 | https://crystallize.com/learn 16 | 17 | ), 18 | ]; 19 | 20 | // Get more tips 21 | (async function getTips() { 22 | try { 23 | const { 24 | data: { blog, comic }, 25 | } = await callCrystallizeMarketingCatalogue({ 26 | query: ` 27 | { 28 | blog: catalogue(path: "/blog") { 29 | ...s 30 | } 31 | comic: catalogue(path: "/comics") { 32 | ...s 33 | } 34 | } 35 | 36 | fragment s on Item { 37 | subtree(first: 1) { 38 | edges { 39 | node { 40 | name 41 | path 42 | } 43 | } 44 | } 45 | }`, 46 | }); 47 | 48 | const blogPost = blog.subtree.edges[0].node; 49 | const comicPost = comic.subtree.edges[0].node; 50 | 51 | if (blogPost) { 52 | tips.push(() => ( 53 | 54 | Catch up on our last blogpost "{blogPost.name}" 55 | 56 | https://crystallize.com{blogPost.path} 57 | 58 | )); 59 | } 60 | if (comicPost) { 61 | tips.push(() => ( 62 | 63 | Like comics? Check out our latest: "{comicPost.name}"{' '} 64 | (https://crystallize.com{comicPost.path}) 65 | 66 | )); 67 | } 68 | // eslint-disable-next-line no-empty 69 | } catch (err) {} 70 | })(); 71 | 72 | function Tips() { 73 | const [tipsIndex, setTipsIndex] = React.useState(0); 74 | 75 | // Give different tips messages 76 | React.useEffect(() => { 77 | const interval = setInterval(() => { 78 | setTipsIndex((f) => { 79 | let newI = f + 1; 80 | if (newI === tips.length) { 81 | newI = 0; 82 | } 83 | return newI; 84 | }); 85 | }, 8000); 86 | return () => clearInterval(interval); 87 | }); 88 | 89 | return ( 90 | 91 | {tips[tipsIndex]()} 92 | 93 | ); 94 | } 95 | 96 | module.exports = Tips; 97 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const React = require('react'); 7 | const importJsx = require('import-jsx'); 8 | const { render } = require('ink'); 9 | const meow = require('meow'); 10 | const execSync = require('child_process').execSync; 11 | 12 | const ui = importJsx('./ui'); 13 | 14 | const cli = meow( 15 | ` 16 | Usage 17 | $ @crystallize/cli 18 | 19 | Options 20 | --bootstrap-tenant, -b Bootstrap tenant 21 | 22 | Examples 23 | $ @crystallize/cli my-ecommerce 24 | $ @crystallize/cli --bootstrap-tenant 25 | `, 26 | { 27 | flags: { 28 | bootstrapTenant: { 29 | type: 'boolean', 30 | alias: 'b', 31 | }, 32 | }, 33 | } 34 | ); 35 | 36 | const desiredProjectName = (cli.input[0] || 'crystallize-app').replace( 37 | /\.?\/?/g, 38 | '' 39 | ); 40 | 41 | // Determine project name and path 42 | let projectName = desiredProjectName; 43 | let projectPath; 44 | let freeDirectory = false; 45 | let index = 0; 46 | do { 47 | try { 48 | projectPath = path.resolve(projectName); 49 | fs.statSync(projectPath); 50 | projectName = `${desiredProjectName}-${index++}`; 51 | } catch (e) { 52 | freeDirectory = true; 53 | } 54 | } while (!freeDirectory); 55 | 56 | /** 57 | * Determines whether yarn is installed. 58 | */ 59 | const shouldUseYarn = (function () { 60 | try { 61 | execSync('yarnpkg --version', { stdio: 'ignore' }); 62 | return true; 63 | } catch (e) { 64 | return false; 65 | } 66 | })(); 67 | 68 | // Determine the journey 69 | let journey; 70 | if (cli.flags.bootstrapTenant) { 71 | journey = importJsx('./journeys/bootstrap-tenant'); 72 | } else { 73 | journey = importJsx('./journeys/download-boilerplate'); 74 | } 75 | 76 | render( 77 | React.createElement(ui, { 78 | flags: cli.flags, 79 | projectName, 80 | projectPath, 81 | shouldUseYarn, 82 | journey, 83 | }) 84 | ); 85 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const Conf = require('conf'); 2 | 3 | const config = new Conf(); 4 | 5 | module.exports = { 6 | config, 7 | CONF_ACCESS_TOKENS: 'access.tokens', 8 | }; 9 | -------------------------------------------------------------------------------- /src/journeys/_shared/bootstrap-tenant.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const { Bootstrapper, EVENT_NAMES } = require('@crystallize/import-utilities'); 5 | 6 | function bootstrapTenant({ 7 | tenant, 8 | tenantSpec, 9 | id, 10 | secret, 11 | onUpdate = () => {}, 12 | }) { 13 | return new Promise((resolve) => { 14 | try { 15 | const spec = JSON.parse( 16 | fs.readFileSync( 17 | path.resolve(__dirname, `./specs/${tenantSpec}.json`), 18 | 'utf-8' 19 | ) 20 | ); 21 | if (spec) { 22 | const bootstrapper = new Bootstrapper(); 23 | 24 | bootstrapper.setAccessToken(id, secret); 25 | 26 | bootstrapper.setTenantIdentifier(tenant); 27 | 28 | bootstrapper.setSpec(spec); 29 | 30 | bootstrapper.start(); 31 | 32 | bootstrapper.on(EVENT_NAMES.STATUS_UPDATE, onUpdate); 33 | bootstrapper.on(EVENT_NAMES.DONE, resolve); 34 | } 35 | } catch (e) { 36 | console.log(e); 37 | resolve(); 38 | } 39 | }); 40 | } 41 | 42 | module.exports = { bootstrapTenant }; 43 | -------------------------------------------------------------------------------- /src/journeys/_shared/specs/conference-boilerplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "languages": [ 3 | { 4 | "code": "en", 5 | "name": "English", 6 | "isDefault": true 7 | } 8 | ], 9 | "vatTypes": [ 10 | { 11 | "name": "No Tax", 12 | "percent": 0 13 | } 14 | ], 15 | "subscriptionPlans": [], 16 | "priceVariants": [ 17 | { 18 | "identifier": "usd-sales", 19 | "name": "Early bird", 20 | "currency": "usd" 21 | }, 22 | { 23 | "identifier": "default", 24 | "name": "Retail", 25 | "currency": "usd" 26 | } 27 | ], 28 | "topicMaps": [], 29 | "shapes": [ 30 | { 31 | "name": "Collection", 32 | "id": "collection", 33 | "identifier": "collection", 34 | "type": "document", 35 | "components": [ 36 | { 37 | "id": "title", 38 | "name": "Title", 39 | "type": "singleLine", 40 | "description": null, 41 | "config": null 42 | }, 43 | { 44 | "id": "brief", 45 | "name": "Brief", 46 | "type": "richText", 47 | "description": null, 48 | "config": null 49 | }, 50 | { 51 | "id": "collection", 52 | "name": "Collection", 53 | "type": "componentChoice", 54 | "description": null, 55 | "config": { 56 | "choices": [ 57 | { 58 | "id": "images", 59 | "name": "Images", 60 | "type": "images", 61 | "description": "Image gallery", 62 | "config": null 63 | }, 64 | { 65 | "id": "grid", 66 | "name": "Grid", 67 | "type": "gridRelations", 68 | "description": "Can either be a collection of past sponsors or past conference videos", 69 | "config": null 70 | } 71 | ] 72 | } 73 | } 74 | ] 75 | }, 76 | { 77 | "name": "Article", 78 | "id": "default-document", 79 | "identifier": "default-document", 80 | "type": "document", 81 | "components": [ 82 | { 83 | "id": "title", 84 | "name": "Title", 85 | "type": "singleLine", 86 | "description": null, 87 | "config": null 88 | }, 89 | { 90 | "id": "image", 91 | "name": "Image", 92 | "type": "images", 93 | "description": null, 94 | "config": null 95 | }, 96 | { 97 | "id": "intro", 98 | "name": "Intro", 99 | "type": "richText", 100 | "description": null, 101 | "config": null 102 | }, 103 | { 104 | "id": "body", 105 | "name": "Body", 106 | "type": "paragraphCollection", 107 | "description": null, 108 | "config": null 109 | } 110 | ] 111 | }, 112 | { 113 | "name": "Folder", 114 | "id": "default-folder", 115 | "identifier": "default-folder", 116 | "type": "folder", 117 | "components": [ 118 | { 119 | "id": "title", 120 | "name": "Title", 121 | "type": "singleLine", 122 | "description": null, 123 | "config": null 124 | }, 125 | { 126 | "id": "brief", 127 | "name": "Brief", 128 | "type": "richText", 129 | "description": null, 130 | "config": null 131 | }, 132 | { 133 | "id": "illustration", 134 | "name": "Illustration", 135 | "type": "images", 136 | "description": null, 137 | "config": null 138 | }, 139 | { 140 | "id": "marketing-content", 141 | "name": "Marketing content", 142 | "type": "contentChunk", 143 | "description": null, 144 | "config": { 145 | "repeatable": false, 146 | "components": [ 147 | { 148 | "id": "title", 149 | "name": "Title", 150 | "type": "singleLine", 151 | "description": "", 152 | "config": null 153 | }, 154 | { 155 | "id": "description", 156 | "name": "Description", 157 | "type": "richText", 158 | "description": "", 159 | "config": null 160 | }, 161 | { 162 | "id": "image", 163 | "name": "Image", 164 | "type": "images", 165 | "description": "", 166 | "config": null 167 | } 168 | ] 169 | } 170 | } 171 | ] 172 | }, 173 | { 174 | "name": "Product", 175 | "id": "default-product", 176 | "identifier": "default-product", 177 | "type": "product", 178 | "components": [ 179 | { 180 | "id": "summary", 181 | "name": "Summary", 182 | "type": "richText", 183 | "description": null, 184 | "config": null 185 | }, 186 | { 187 | "id": "description", 188 | "name": "Description", 189 | "type": "paragraphCollection", 190 | "description": null, 191 | "config": null 192 | } 193 | ] 194 | }, 195 | { 196 | "name": "Frontpage", 197 | "id": "frontpage", 198 | "identifier": "frontpage", 199 | "type": "document", 200 | "components": [ 201 | { 202 | "id": "title", 203 | "name": "Title", 204 | "type": "singleLine", 205 | "description": null, 206 | "config": null 207 | }, 208 | { 209 | "id": "description", 210 | "name": "Description", 211 | "type": "richText", 212 | "description": null, 213 | "config": null 214 | }, 215 | { 216 | "id": "hero", 217 | "name": "Hero", 218 | "type": "images", 219 | "description": null, 220 | "config": null 221 | }, 222 | { 223 | "id": "cta", 224 | "name": "CTA", 225 | "type": "contentChunk", 226 | "description": null, 227 | "config": { 228 | "repeatable": true, 229 | "components": [ 230 | { 231 | "id": "name", 232 | "name": "Name", 233 | "type": "singleLine", 234 | "description": "", 235 | "config": null 236 | }, 237 | { 238 | "id": "url", 239 | "name": "URL", 240 | "type": "singleLine", 241 | "description": "", 242 | "config": null 243 | } 244 | ] 245 | } 246 | }, 247 | { 248 | "id": "main-sponsors", 249 | "name": "Main Sponsors", 250 | "type": "contentChunk", 251 | "description": null, 252 | "config": { 253 | "repeatable": false, 254 | "components": [ 255 | { 256 | "id": "title", 257 | "name": "Title", 258 | "type": "singleLine", 259 | "description": "", 260 | "config": null 261 | }, 262 | { 263 | "id": "sponsors", 264 | "name": "Sponsors", 265 | "type": "itemRelations", 266 | "description": "", 267 | "config": null 268 | } 269 | ] 270 | } 271 | }, 272 | { 273 | "id": "usp", 274 | "name": "USP", 275 | "type": "contentChunk", 276 | "description": null, 277 | "config": { 278 | "repeatable": true, 279 | "components": [ 280 | { 281 | "id": "title", 282 | "name": "Title", 283 | "type": "singleLine", 284 | "description": "", 285 | "config": null 286 | }, 287 | { 288 | "id": "description", 289 | "name": "Description", 290 | "type": "richText", 291 | "description": "", 292 | "config": null 293 | }, 294 | { 295 | "id": "image", 296 | "name": "Image", 297 | "type": "images", 298 | "description": "", 299 | "config": null 300 | }, 301 | { 302 | "id": "cta-name", 303 | "name": "CTA name", 304 | "type": "singleLine", 305 | "description": "", 306 | "config": null 307 | }, 308 | { 309 | "id": "cta-url", 310 | "name": "CTA url", 311 | "type": "singleLine", 312 | "description": "", 313 | "config": null 314 | } 315 | ] 316 | } 317 | }, 318 | { 319 | "id": "speakers", 320 | "name": "Speakers", 321 | "type": "contentChunk", 322 | "description": null, 323 | "config": { 324 | "repeatable": false, 325 | "components": [ 326 | { 327 | "id": "title", 328 | "name": "Title", 329 | "type": "singleLine", 330 | "description": "", 331 | "config": null 332 | }, 333 | { 334 | "id": "description", 335 | "name": "Description", 336 | "type": "richText", 337 | "description": "", 338 | "config": null 339 | }, 340 | { 341 | "id": "speaker", 342 | "name": "Speaker", 343 | "type": "itemRelations", 344 | "description": "", 345 | "config": null 346 | }, 347 | { 348 | "id": "cta-name", 349 | "name": "CTA name", 350 | "type": "singleLine", 351 | "description": "", 352 | "config": null 353 | }, 354 | { 355 | "id": "cta-url", 356 | "name": "CTA url", 357 | "type": "singleLine", 358 | "description": "", 359 | "config": null 360 | } 361 | ] 362 | } 363 | }, 364 | { 365 | "id": "schedule-header", 366 | "name": "Schedule header", 367 | "type": "singleLine", 368 | "description": null, 369 | "config": null 370 | }, 371 | { 372 | "id": "talk", 373 | "name": "Talk", 374 | "type": "contentChunk", 375 | "description": null, 376 | "config": { 377 | "repeatable": true, 378 | "components": [ 379 | { 380 | "id": "title", 381 | "name": "Title", 382 | "type": "singleLine", 383 | "description": "", 384 | "config": null 385 | }, 386 | { 387 | "id": "description", 388 | "name": "Description", 389 | "type": "richText", 390 | "description": "", 391 | "config": null 392 | }, 393 | { 394 | "id": "time", 395 | "name": "Time", 396 | "type": "singleLine", 397 | "description": "", 398 | "config": null 399 | }, 400 | { 401 | "id": "date", 402 | "name": "Date", 403 | "type": "datetime", 404 | "description": "", 405 | "config": null 406 | }, 407 | { 408 | "id": "speaker", 409 | "name": "Speaker", 410 | "type": "itemRelations", 411 | "description": "", 412 | "config": null 413 | }, 414 | { 415 | "id": "stream-url", 416 | "name": "Stream url", 417 | "type": "singleLine", 418 | "description": "", 419 | "config": null 420 | }, 421 | { 422 | "id": "is_premium", 423 | "name": "is_premium", 424 | "type": "boolean", 425 | "description": "", 426 | "config": null 427 | } 428 | ] 429 | } 430 | }, 431 | { 432 | "id": "job-board-header", 433 | "name": "Job board header", 434 | "type": "singleLine", 435 | "description": null, 436 | "config": null 437 | }, 438 | { 439 | "id": "jobs", 440 | "name": "Jobs", 441 | "type": "contentChunk", 442 | "description": null, 443 | "config": { 444 | "repeatable": true, 445 | "components": [ 446 | { 447 | "id": "title", 448 | "name": "Title", 449 | "type": "singleLine", 450 | "description": "", 451 | "config": null 452 | }, 453 | { 454 | "id": "location", 455 | "name": "Location", 456 | "type": "singleLine", 457 | "description": "", 458 | "config": null 459 | }, 460 | { 461 | "id": "url", 462 | "name": "Url", 463 | "type": "singleLine", 464 | "description": "", 465 | "config": null 466 | }, 467 | { 468 | "id": "sponsor", 469 | "name": "Sponsor", 470 | "type": "itemRelations", 471 | "description": "", 472 | "config": null 473 | } 474 | ] 475 | } 476 | }, 477 | { 478 | "id": "sponsors", 479 | "name": "Sponsors", 480 | "type": "contentChunk", 481 | "description": null, 482 | "config": { 483 | "repeatable": false, 484 | "components": [ 485 | { 486 | "id": "title", 487 | "name": "Title", 488 | "type": "singleLine", 489 | "description": "", 490 | "config": null 491 | }, 492 | { 493 | "id": "description", 494 | "name": "Description", 495 | "type": "richText", 496 | "description": "", 497 | "config": null 498 | }, 499 | { 500 | "id": "cta-name", 501 | "name": "CTA name", 502 | "type": "singleLine", 503 | "description": "", 504 | "config": null 505 | }, 506 | { 507 | "id": "cta-url", 508 | "name": "CTA url", 509 | "type": "singleLine", 510 | "description": "", 511 | "config": null 512 | }, 513 | { 514 | "id": "sponsor", 515 | "name": "Sponsor", 516 | "type": "itemRelations", 517 | "description": "", 518 | "config": null 519 | } 520 | ] 521 | } 522 | }, 523 | { 524 | "id": "partners", 525 | "name": "Partners", 526 | "type": "contentChunk", 527 | "description": null, 528 | "config": { 529 | "repeatable": false, 530 | "components": [ 531 | { 532 | "id": "title", 533 | "name": "Title", 534 | "type": "singleLine", 535 | "description": "", 536 | "config": null 537 | }, 538 | { 539 | "id": "description", 540 | "name": "Description", 541 | "type": "richText", 542 | "description": "", 543 | "config": null 544 | }, 545 | { 546 | "id": "cta-name", 547 | "name": "CTA name", 548 | "type": "singleLine", 549 | "description": "", 550 | "config": null 551 | }, 552 | { 553 | "id": "cta-url", 554 | "name": "CTA Url", 555 | "type": "singleLine", 556 | "description": "", 557 | "config": null 558 | }, 559 | { 560 | "id": "partner", 561 | "name": "Partner", 562 | "type": "itemRelations", 563 | "description": "", 564 | "config": null 565 | } 566 | ] 567 | } 568 | }, 569 | { 570 | "id": "gallery", 571 | "name": "Gallery", 572 | "type": "contentChunk", 573 | "description": null, 574 | "config": { 575 | "repeatable": false, 576 | "components": [ 577 | { 578 | "id": "header", 579 | "name": "Header", 580 | "type": "singleLine", 581 | "description": "", 582 | "config": null 583 | }, 584 | { 585 | "id": "images", 586 | "name": "Images", 587 | "type": "images", 588 | "description": "", 589 | "config": null 590 | } 591 | ] 592 | } 593 | }, 594 | { 595 | "id": "meta", 596 | "name": "Meta", 597 | "type": "contentChunk", 598 | "description": null, 599 | "config": { 600 | "repeatable": false, 601 | "components": [ 602 | { 603 | "id": "title", 604 | "name": "Title", 605 | "type": "singleLine", 606 | "description": "", 607 | "config": null 608 | }, 609 | { 610 | "id": "description", 611 | "name": "Description", 612 | "type": "richText", 613 | "description": "", 614 | "config": null 615 | }, 616 | { 617 | "id": "image", 618 | "name": "Image", 619 | "type": "images", 620 | "description": "", 621 | "config": null 622 | } 623 | ] 624 | } 625 | } 626 | ] 627 | }, 628 | { 629 | "name": "Organization", 630 | "id": "organization", 631 | "identifier": "organization", 632 | "type": "document", 633 | "components": [ 634 | { 635 | "id": "url", 636 | "name": "URL", 637 | "type": "singleLine", 638 | "description": null, 639 | "config": null 640 | }, 641 | { 642 | "id": "logo", 643 | "name": "Logo", 644 | "type": "images", 645 | "description": null, 646 | "config": null 647 | } 648 | ] 649 | }, 650 | { 651 | "name": "Speaker", 652 | "id": "speaker", 653 | "identifier": "speaker", 654 | "type": "document", 655 | "components": [ 656 | { 657 | "id": "name", 658 | "name": "Name", 659 | "type": "singleLine", 660 | "description": null, 661 | "config": null 662 | }, 663 | { 664 | "id": "company", 665 | "name": "Company", 666 | "type": "singleLine", 667 | "description": null, 668 | "config": null 669 | }, 670 | { 671 | "id": "job-title", 672 | "name": "Job title", 673 | "type": "singleLine", 674 | "description": null, 675 | "config": null 676 | }, 677 | { 678 | "id": "speaker-image", 679 | "name": "Speaker image", 680 | "type": "images", 681 | "description": null, 682 | "config": null 683 | }, 684 | { 685 | "id": "social", 686 | "name": "Social", 687 | "type": "contentChunk", 688 | "description": null, 689 | "config": { 690 | "repeatable": false, 691 | "components": [ 692 | { 693 | "id": "twitter-url", 694 | "name": "Twitter URL", 695 | "type": "singleLine", 696 | "description": "", 697 | "config": null 698 | }, 699 | { 700 | "id": "github-url", 701 | "name": "GitHub URL", 702 | "type": "singleLine", 703 | "description": "", 704 | "config": null 705 | }, 706 | { 707 | "id": "linkedin-url", 708 | "name": "LinkedIn URL", 709 | "type": "singleLine", 710 | "description": "", 711 | "config": null 712 | }, 713 | { 714 | "id": "facebook-url", 715 | "name": "Facebook URL", 716 | "type": "singleLine", 717 | "description": "", 718 | "config": null 719 | } 720 | ] 721 | } 722 | } 723 | ] 724 | } 725 | ], 726 | "grids": [], 727 | "items": [ 728 | { 729 | "name": "Speakers", 730 | "externalReference": null, 731 | "shape": "default-folder", 732 | "components": { 733 | "brief": { 734 | "json": [ 735 | { 736 | "kind": "block", 737 | "type": "paragraph", 738 | "metadata": {}, 739 | "children": [] 740 | } 741 | ] 742 | }, 743 | "marketing-content": [] 744 | }, 745 | "topics": null, 746 | "children": [ 747 | { 748 | "name": "Ivy Combs", 749 | "externalReference": null, 750 | "shape": "speaker", 751 | "components": { 752 | "name": "Ivy Combs", 753 | "company": "Crystallize", 754 | "job-title": "Head of Design", 755 | "speaker-image": [ 756 | { 757 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/1/character-illustration.svg", 758 | "altText": "Profile picture", 759 | "caption": { 760 | "json": [ 761 | { 762 | "kind": "block", 763 | "type": "paragraph", 764 | "metadata": {}, 765 | "children": [] 766 | } 767 | ] 768 | } 769 | } 770 | ], 771 | "social": [ 772 | { 773 | "twitter-url": "https://twitter.com/", 774 | "github-url": "https://github.com/", 775 | "linkedin-url": "https://linkedin.com/", 776 | "facebook-url": "https://www.facebook.com/" 777 | } 778 | ] 779 | }, 780 | "topics": null 781 | }, 782 | { 783 | "name": "Elizabeth Bennet", 784 | "externalReference": null, 785 | "shape": "speaker", 786 | "components": { 787 | "name": "Elizabeth Adams", 788 | "company": "Crystallize", 789 | "job-title": "Head of Engineering", 790 | "speaker-image": [ 791 | { 792 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/1/woman-jacket-illustration.svg", 793 | "altText": "Profile picture", 794 | "caption": { 795 | "json": [ 796 | { 797 | "kind": "block", 798 | "type": "paragraph", 799 | "metadata": {}, 800 | "children": [] 801 | } 802 | ] 803 | } 804 | } 805 | ], 806 | "social": [ 807 | { 808 | "twitter-url": "https://twitter.com/", 809 | "github-url": "https://github.com/", 810 | "linkedin-url": "https://linkedin.com/", 811 | "facebook-url": "https://www.facebook.com/" 812 | } 813 | ] 814 | }, 815 | "topics": null 816 | }, 817 | { 818 | "name": "Jude Francis", 819 | "externalReference": null, 820 | "shape": "speaker", 821 | "components": { 822 | "name": "Jude Francis", 823 | "company": "Crystallize", 824 | "job-title": "Backend Developer", 825 | "speaker-image": [ 826 | { 827 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/2/person-pink-shirt.svg", 828 | "altText": "Profile picture", 829 | "caption": { 830 | "json": [ 831 | { 832 | "kind": "block", 833 | "type": "paragraph", 834 | "metadata": {}, 835 | "children": [] 836 | } 837 | ] 838 | } 839 | } 840 | ], 841 | "social": [ 842 | { 843 | "twitter-url": "https://twitter.com/", 844 | "github-url": "https://github.com/", 845 | "linkedin-url": "https://linkedin.com/", 846 | "facebook-url": "https://www.facebook.com/" 847 | } 848 | ] 849 | }, 850 | "topics": null 851 | }, 852 | { 853 | "name": "Pedro Pascal", 854 | "externalReference": null, 855 | "shape": "speaker", 856 | "components": { 857 | "name": "Pedro Pascal", 858 | "company": "Crystallize", 859 | "job-title": "Frontend Developer", 860 | "speaker-image": [ 861 | { 862 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/2/person-with-airpods.svg", 863 | "altText": "Profile picture", 864 | "caption": { 865 | "json": [ 866 | { 867 | "kind": "block", 868 | "type": "paragraph", 869 | "metadata": {}, 870 | "children": [] 871 | } 872 | ] 873 | } 874 | } 875 | ], 876 | "social": [ 877 | { 878 | "twitter-url": "https://twitter.com/", 879 | "github-url": "https://github.com/", 880 | "linkedin-url": "https://linkedin.com/", 881 | "facebook-url": "https://www.facebook.com/" 882 | } 883 | ] 884 | }, 885 | "topics": null 886 | }, 887 | { 888 | "name": "Jane Warner", 889 | "externalReference": null, 890 | "shape": "speaker", 891 | "components": { 892 | "name": "Jane Warner", 893 | "company": "Crystallize", 894 | "job-title": "Intergalactic Expert", 895 | "speaker-image": [ 896 | { 897 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/3/woman-purple-shirt.svg", 898 | "altText": "Profile picture", 899 | "caption": { 900 | "json": [ 901 | { 902 | "kind": "block", 903 | "type": "paragraph", 904 | "metadata": {}, 905 | "children": [] 906 | } 907 | ] 908 | } 909 | } 910 | ], 911 | "social": [ 912 | { 913 | "twitter-url": "https://twitter.com/", 914 | "github-url": "https://github.com/", 915 | "linkedin-url": "https://linkedin.com/", 916 | "facebook-url": "https://www.facebook.com/" 917 | } 918 | ] 919 | }, 920 | "topics": null 921 | }, 922 | { 923 | "name": "Selena Jones", 924 | "externalReference": null, 925 | "shape": "speaker", 926 | "components": { 927 | "name": "Selena Jones", 928 | "company": "Crystallize", 929 | "job-title": "Frontend Engineer", 930 | "speaker-image": [ 931 | { 932 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/3/woman-blue-shirt.svg", 933 | "altText": "Profile picture", 934 | "caption": { 935 | "json": [ 936 | { 937 | "kind": "block", 938 | "type": "paragraph", 939 | "metadata": {}, 940 | "children": [] 941 | } 942 | ] 943 | } 944 | } 945 | ], 946 | "social": [ 947 | { 948 | "twitter-url": "https://twitter.com/", 949 | "github-url": "https://github.com/", 950 | "linkedin-url": "https://linkedin.com/", 951 | "facebook-url": "https://www.facebook.com/" 952 | } 953 | ] 954 | }, 955 | "topics": null 956 | } 957 | ] 958 | }, 959 | { 960 | "name": "Frontpage", 961 | "externalReference": null, 962 | "shape": "frontpage", 963 | "components": { 964 | "title": "Intergalactic Conference", 965 | "description": { 966 | "json": [ 967 | { 968 | "kind": "block", 969 | "type": "paragraph", 970 | "metadata": {}, 971 | "children": [ 972 | { 973 | "kind": "inline", 974 | "metadata": {}, 975 | "textContent": "Join the Intergalactic conference to up your game in all things. More content goes here. Also streamed live. Workshops have a small fee." 976 | } 977 | ] 978 | } 979 | ] 980 | }, 981 | "hero": [ 982 | { 983 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/2/woman-with-laptop.png", 984 | "altText": "Woman with laptop", 985 | "caption": null 986 | } 987 | ], 988 | "cta": [ 989 | { 990 | "name": "Workshop access", 991 | "url": "/" 992 | }, 993 | { 994 | "name": "Conference Tickets", 995 | "url": "/" 996 | } 997 | ], 998 | "main-sponsors": [ 999 | { 1000 | "title": "Our main sponsors", 1001 | "sponsors": [ 1002 | { 1003 | "cataloguePath": "/sponsors/cognite", 1004 | "externalReference": null 1005 | }, 1006 | { 1007 | "cataloguePath": "/sponsors/kelda-dynamics", 1008 | "externalReference": null 1009 | }, 1010 | { 1011 | "cataloguePath": "/sponsors/knowit", 1012 | "externalReference": null 1013 | }, 1014 | { 1015 | "cataloguePath": "/sponsors/formidable", 1016 | "externalReference": null 1017 | }, 1018 | { 1019 | "cataloguePath": "/sponsors/snowball", 1020 | "externalReference": null 1021 | }, 1022 | { 1023 | "cataloguePath": "/sponsors/nerds-and-company", 1024 | "externalReference": null 1025 | }, 1026 | { 1027 | "cataloguePath": "/sponsors/speed", 1028 | "externalReference": null 1029 | }, 1030 | { 1031 | "cataloguePath": "/sponsors/crystallize", 1032 | "externalReference": null 1033 | }, 1034 | { 1035 | "cataloguePath": "/sponsors/auth0", 1036 | "externalReference": null 1037 | } 1038 | ] 1039 | } 1040 | ], 1041 | "usp": [ 1042 | { 1043 | "title": "Level Up Your Knowledge", 1044 | "description": { 1045 | "json": [ 1046 | { 1047 | "kind": "block", 1048 | "metadata": {}, 1049 | "type": "unordered-list", 1050 | "children": [ 1051 | { 1052 | "kind": "block", 1053 | "type": "list-item", 1054 | "metadata": {}, 1055 | "children": [ 1056 | { 1057 | "kind": "inline", 1058 | "metadata": {}, 1059 | "textContent": "Lorem ipsum dolor sit amet, consectetur elit." 1060 | } 1061 | ] 1062 | }, 1063 | { 1064 | "kind": "block", 1065 | "type": "list-item", 1066 | "metadata": {}, 1067 | "children": [ 1068 | { 1069 | "kind": "inline", 1070 | "metadata": {}, 1071 | "textContent": "Sed cursus erat a mollis sagittis mi convallis." 1072 | } 1073 | ] 1074 | }, 1075 | { 1076 | "kind": "block", 1077 | "type": "list-item", 1078 | "metadata": {}, 1079 | "children": [ 1080 | { 1081 | "kind": "inline", 1082 | "metadata": {}, 1083 | "textContent": "Mauris ut tellus convallis, tempus ipsum et." 1084 | } 1085 | ] 1086 | } 1087 | ] 1088 | } 1089 | ] 1090 | }, 1091 | "image": [ 1092 | { 1093 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/3/woman-holding-textbox.png", 1094 | "altText": "Woman holding textbox", 1095 | "caption": null 1096 | } 1097 | ], 1098 | "cta-name": "Merch Store", 1099 | "cta-url": "/merch" 1100 | } 1101 | ], 1102 | "speakers": [ 1103 | { 1104 | "title": "Our Speakers", 1105 | "description": { 1106 | "json": [ 1107 | { 1108 | "kind": "block", 1109 | "type": "paragraph", 1110 | "metadata": {}, 1111 | "children": [ 1112 | { 1113 | "kind": "inline", 1114 | "metadata": {}, 1115 | "textContent": "Join the Intergalactic conference to up your game in all things. More content goes here. Also streamed live. Workshops have a small fee." 1116 | } 1117 | ] 1118 | } 1119 | ] 1120 | }, 1121 | "speaker": [ 1122 | { 1123 | "cataloguePath": "/speakers/ivy-combs", 1124 | "externalReference": null 1125 | }, 1126 | { 1127 | "cataloguePath": "/speakers/pedro-pascal", 1128 | "externalReference": null 1129 | }, 1130 | { 1131 | "cataloguePath": "/speakers/elizabeth-bennet", 1132 | "externalReference": null 1133 | }, 1134 | { 1135 | "cataloguePath": "/speakers/jude-francis", 1136 | "externalReference": null 1137 | }, 1138 | { 1139 | "cataloguePath": "/speakers/jane-warner", 1140 | "externalReference": null 1141 | }, 1142 | { 1143 | "cataloguePath": "/speakers/selena-jones", 1144 | "externalReference": null 1145 | } 1146 | ], 1147 | "cta-name": "Submit talk", 1148 | "cta-url": "/" 1149 | } 1150 | ], 1151 | "schedule-header": "Schedule", 1152 | "talk": [ 1153 | { 1154 | "title": "Latest Trends in Space Exploration", 1155 | "description": { 1156 | "json": [ 1157 | { 1158 | "kind": "block", 1159 | "type": "paragraph", 1160 | "metadata": {}, 1161 | "children": [ 1162 | { 1163 | "kind": "inline", 1164 | "metadata": {}, 1165 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1166 | } 1167 | ] 1168 | } 1169 | ] 1170 | }, 1171 | "time": "9:00 - 10:00 AM", 1172 | "date": "2022-07-18T10:00:00.000Z", 1173 | "speaker": [ 1174 | { 1175 | "cataloguePath": "/speakers/ivy-combs", 1176 | "externalReference": null 1177 | } 1178 | ], 1179 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1180 | "is_premium": false 1181 | }, 1182 | { 1183 | "title": "Latest Trends in Space Exploration", 1184 | "description": { 1185 | "json": [ 1186 | { 1187 | "kind": "block", 1188 | "type": "paragraph", 1189 | "metadata": {}, 1190 | "children": [ 1191 | { 1192 | "kind": "inline", 1193 | "metadata": {}, 1194 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1195 | } 1196 | ] 1197 | } 1198 | ] 1199 | }, 1200 | "time": "10:00 - 11:00 AM", 1201 | "date": "2022-07-18T10:00:00.000Z", 1202 | "speaker": [ 1203 | { 1204 | "cataloguePath": "/speakers/elizabeth-bennet", 1205 | "externalReference": null 1206 | } 1207 | ], 1208 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1209 | "is_premium": false 1210 | }, 1211 | { 1212 | "title": "Latest Trends in Space Exploration", 1213 | "description": { 1214 | "json": [ 1215 | { 1216 | "kind": "block", 1217 | "type": "paragraph", 1218 | "metadata": {}, 1219 | "children": [ 1220 | { 1221 | "kind": "inline", 1222 | "metadata": {}, 1223 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1224 | } 1225 | ] 1226 | } 1227 | ] 1228 | }, 1229 | "time": "12:00 - 01:00 PM", 1230 | "date": "2022-07-18T10:00:00.000Z", 1231 | "speaker": [ 1232 | { 1233 | "cataloguePath": "/speakers/pedro-pascal", 1234 | "externalReference": null 1235 | } 1236 | ], 1237 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1238 | "is_premium": false 1239 | }, 1240 | { 1241 | "title": "Latest Trends in Space Exploration", 1242 | "description": { 1243 | "json": [ 1244 | { 1245 | "kind": "block", 1246 | "type": "paragraph", 1247 | "metadata": {}, 1248 | "children": [ 1249 | { 1250 | "kind": "inline", 1251 | "metadata": {}, 1252 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1253 | } 1254 | ] 1255 | } 1256 | ] 1257 | }, 1258 | "time": "9:00 - 10:00 AM", 1259 | "date": "2022-07-19T10:00:00.000Z", 1260 | "speaker": [ 1261 | { 1262 | "cataloguePath": "/speakers/jude-francis", 1263 | "externalReference": null 1264 | } 1265 | ], 1266 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1267 | "is_premium": false 1268 | }, 1269 | { 1270 | "title": "Latest Trends in Space Exploration", 1271 | "description": { 1272 | "json": [ 1273 | { 1274 | "kind": "block", 1275 | "type": "paragraph", 1276 | "metadata": {}, 1277 | "children": [ 1278 | { 1279 | "kind": "inline", 1280 | "metadata": {}, 1281 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1282 | } 1283 | ] 1284 | } 1285 | ] 1286 | }, 1287 | "time": "10:00 - 11:00 AM", 1288 | "date": "2022-07-19T10:00:00.000Z", 1289 | "speaker": [ 1290 | { 1291 | "cataloguePath": "/speakers/selena-jones", 1292 | "externalReference": null 1293 | } 1294 | ], 1295 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1296 | "is_premium": false 1297 | }, 1298 | { 1299 | "title": "Latest Trends in Space Exploration", 1300 | "description": { 1301 | "json": [ 1302 | { 1303 | "kind": "block", 1304 | "type": "paragraph", 1305 | "metadata": {}, 1306 | "children": [ 1307 | { 1308 | "kind": "inline", 1309 | "metadata": {}, 1310 | "textContent": "Travel to the moon has not been top of the space exploration agenda for the past few decades, but that has changed as a number of strategic reasons to resume lunar landings have been identified in recent years. Space exploration will be conducted by autonomous landers and exploration vehicles." 1311 | } 1312 | ] 1313 | } 1314 | ] 1315 | }, 1316 | "time": "9:00 - 10:00 AM", 1317 | "date": "2022-07-20T10:00:00.000Z", 1318 | "speaker": [ 1319 | { 1320 | "cataloguePath": "/speakers/jane-warner", 1321 | "externalReference": null 1322 | } 1323 | ], 1324 | "stream-url": "https://d2evmbjjvznea6.cloudfront.net/videos/Didrik_Steen_Hegna_Crystallize_React_New_York/Didrik_Steen_Hegna_Crystallize_React_New_York.m3u8", 1325 | "is_premium": false 1326 | } 1327 | ], 1328 | "job-board-header": "Job Board", 1329 | "jobs": [ 1330 | { 1331 | "title": "Frontend Developer", 1332 | "location": "Remote / Anywhere in the world", 1333 | "url": "/", 1334 | "sponsor": [ 1335 | { 1336 | "cataloguePath": "/sponsors/crystallize", 1337 | "externalReference": null 1338 | } 1339 | ] 1340 | }, 1341 | { 1342 | "title": "Backend Developer", 1343 | "location": "Remote / Anywhere in the world", 1344 | "url": "/", 1345 | "sponsor": [ 1346 | { 1347 | "cataloguePath": "/sponsors/crystallize", 1348 | "externalReference": null 1349 | } 1350 | ] 1351 | }, 1352 | { 1353 | "title": "Intergalactic Expert", 1354 | "location": "Remote / Anywhere in the world", 1355 | "url": "/", 1356 | "sponsor": [ 1357 | { 1358 | "cataloguePath": "/sponsors/crystallize", 1359 | "externalReference": null 1360 | } 1361 | ] 1362 | } 1363 | ], 1364 | "sponsors": [ 1365 | { 1366 | "title": "Sponsors", 1367 | "description": { 1368 | "json": [ 1369 | { 1370 | "kind": "block", 1371 | "type": "paragraph", 1372 | "metadata": {}, 1373 | "children": [ 1374 | { 1375 | "kind": "inline", 1376 | "metadata": {}, 1377 | "textContent": "A list of all our sponsors." 1378 | } 1379 | ] 1380 | } 1381 | ] 1382 | }, 1383 | "cta-name": "Become a sponsor", 1384 | "cta-url": "/", 1385 | "sponsor": [ 1386 | { 1387 | "cataloguePath": "/sponsors/auth0", 1388 | "externalReference": null 1389 | }, 1390 | { 1391 | "cataloguePath": "/sponsors/formidable", 1392 | "externalReference": null 1393 | }, 1394 | { 1395 | "cataloguePath": "/sponsors/crystallize", 1396 | "externalReference": null 1397 | } 1398 | ] 1399 | } 1400 | ], 1401 | "partners": [ 1402 | { 1403 | "title": "Partners", 1404 | "description": { 1405 | "json": [ 1406 | { 1407 | "kind": "block", 1408 | "type": "paragraph", 1409 | "metadata": {}, 1410 | "children": [] 1411 | } 1412 | ] 1413 | }, 1414 | "partner": [ 1415 | { 1416 | "cataloguePath": "/sponsors/cognite", 1417 | "externalReference": null 1418 | }, 1419 | { 1420 | "cataloguePath": "/sponsors/snowball", 1421 | "externalReference": null 1422 | }, 1423 | { 1424 | "cataloguePath": "/sponsors/knowit", 1425 | "externalReference": null 1426 | } 1427 | ] 1428 | } 1429 | ], 1430 | "gallery": [ 1431 | { 1432 | "header": "Conference 2021", 1433 | "images": [ 1434 | { 1435 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/5/react_new_york_audience.jpg", 1436 | "altText": "Crowded hall", 1437 | "caption": { 1438 | "json": [ 1439 | { 1440 | "kind": "block", 1441 | "type": "paragraph", 1442 | "metadata": {}, 1443 | "children": [] 1444 | } 1445 | ] 1446 | } 1447 | }, 1448 | { 1449 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/5/react_new_york_bard_farstad_opening.jpg", 1450 | "altText": "Person speaking on stage", 1451 | "caption": { 1452 | "json": [ 1453 | { 1454 | "kind": "block", 1455 | "type": "paragraph", 1456 | "metadata": {}, 1457 | "children": [] 1458 | } 1459 | ] 1460 | } 1461 | }, 1462 | { 1463 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/6/react_new_york_bowling_1.jpg", 1464 | "altText": "Crowded room", 1465 | "caption": { 1466 | "json": [ 1467 | { 1468 | "kind": "block", 1469 | "type": "paragraph", 1470 | "metadata": {}, 1471 | "children": [] 1472 | } 1473 | ] 1474 | } 1475 | }, 1476 | { 1477 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/6/react_new_york_bowling.jpg", 1478 | "altText": "Crowded bowling alley", 1479 | "caption": { 1480 | "json": [ 1481 | { 1482 | "kind": "block", 1483 | "type": "paragraph", 1484 | "metadata": {}, 1485 | "children": [] 1486 | } 1487 | ] 1488 | } 1489 | }, 1490 | { 1491 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/7/react_new_york_crowd_2.jpg", 1492 | "altText": "Crowded room", 1493 | "caption": { 1494 | "json": [ 1495 | { 1496 | "kind": "block", 1497 | "type": "paragraph", 1498 | "metadata": {}, 1499 | "children": [] 1500 | } 1501 | ] 1502 | } 1503 | }, 1504 | { 1505 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/7/react_new_york_crowd.jpg", 1506 | "altText": "Crowded hall", 1507 | "caption": { 1508 | "json": [ 1509 | { 1510 | "kind": "block", 1511 | "type": "paragraph", 1512 | "metadata": {}, 1513 | "children": [] 1514 | } 1515 | ] 1516 | } 1517 | }, 1518 | { 1519 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/8/react_new_york_didrik_hegna_crystallize.jpg", 1520 | "altText": "Person speaking on stage", 1521 | "caption": { 1522 | "json": [ 1523 | { 1524 | "kind": "block", 1525 | "type": "paragraph", 1526 | "metadata": {}, 1527 | "children": [] 1528 | } 1529 | ] 1530 | } 1531 | }, 1532 | { 1533 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/8/react_new_york_registration1.jpg", 1534 | "altText": "People in queue", 1535 | "caption": { 1536 | "json": [ 1537 | { 1538 | "kind": "block", 1539 | "type": "paragraph", 1540 | "metadata": {}, 1541 | "children": [] 1542 | } 1543 | ] 1544 | } 1545 | } 1546 | ] 1547 | } 1548 | ], 1549 | "meta": [ 1550 | { 1551 | "title": "Intergalactic conference", 1552 | "description": { 1553 | "json": [ 1554 | { 1555 | "kind": "block", 1556 | "type": "paragraph", 1557 | "metadata": {}, 1558 | "children": [ 1559 | { 1560 | "kind": "inline", 1561 | "metadata": {}, 1562 | "textContent": "Crystallize conference boilerplate." 1563 | } 1564 | ] 1565 | } 1566 | ] 1567 | }, 1568 | "image": [ 1569 | { 1570 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/9/meta-image.png", 1571 | "altText": "Conference tickets", 1572 | "caption": { 1573 | "json": [ 1574 | { 1575 | "kind": "block", 1576 | "type": "paragraph", 1577 | "metadata": {}, 1578 | "children": [] 1579 | } 1580 | ] 1581 | } 1582 | } 1583 | ] 1584 | } 1585 | ] 1586 | }, 1587 | "topics": null 1588 | }, 1589 | { 1590 | "name": "Sponsors", 1591 | "externalReference": null, 1592 | "shape": "default-folder", 1593 | "components": { 1594 | "brief": { 1595 | "json": [ 1596 | { 1597 | "kind": "block", 1598 | "type": "paragraph", 1599 | "metadata": {}, 1600 | "children": [] 1601 | } 1602 | ] 1603 | }, 1604 | "marketing-content": [] 1605 | }, 1606 | "topics": null, 1607 | "children": [ 1608 | { 1609 | "name": "Kelda Dynamics", 1610 | "externalReference": null, 1611 | "shape": "organization", 1612 | "components": { 1613 | "url": "https://www.kelda.no/", 1614 | "logo": [ 1615 | { 1616 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/10/kelda-dynamics-logo.svg", 1617 | "altText": "Kelda dynamics logo", 1618 | "caption": { 1619 | "json": [ 1620 | { 1621 | "kind": "block", 1622 | "type": "paragraph", 1623 | "metadata": {}, 1624 | "children": [] 1625 | } 1626 | ] 1627 | } 1628 | } 1629 | ] 1630 | }, 1631 | "topics": null 1632 | }, 1633 | { 1634 | "name": "Snowball", 1635 | "externalReference": null, 1636 | "shape": "organization", 1637 | "components": { 1638 | "url": "https://snowball.digital/", 1639 | "logo": [ 1640 | { 1641 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/11/snowball-logo.svg", 1642 | "altText": "Snowball logo", 1643 | "caption": { 1644 | "json": [ 1645 | { 1646 | "kind": "block", 1647 | "type": "paragraph", 1648 | "metadata": {}, 1649 | "children": [] 1650 | } 1651 | ] 1652 | } 1653 | } 1654 | ] 1655 | }, 1656 | "topics": null 1657 | }, 1658 | { 1659 | "name": "Cognite", 1660 | "externalReference": null, 1661 | "shape": "organization", 1662 | "components": { 1663 | "url": "https://www.cognite.com/en/", 1664 | "logo": [ 1665 | { 1666 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/9/cognite-logo.svg", 1667 | "altText": "Cognite logo", 1668 | "caption": { 1669 | "json": [ 1670 | { 1671 | "kind": "block", 1672 | "type": "paragraph", 1673 | "metadata": {}, 1674 | "children": [] 1675 | } 1676 | ] 1677 | } 1678 | } 1679 | ] 1680 | }, 1681 | "topics": null 1682 | }, 1683 | { 1684 | "name": "Knowit", 1685 | "externalReference": null, 1686 | "shape": "organization", 1687 | "components": { 1688 | "url": "https://www.knowit.eu/", 1689 | "logo": [ 1690 | { 1691 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/11/knowit-logo.svg", 1692 | "altText": "Knowit logo", 1693 | "caption": { 1694 | "json": [ 1695 | { 1696 | "kind": "block", 1697 | "type": "paragraph", 1698 | "metadata": {}, 1699 | "children": [] 1700 | } 1701 | ] 1702 | } 1703 | } 1704 | ] 1705 | }, 1706 | "topics": null 1707 | }, 1708 | { 1709 | "name": "Auth0", 1710 | "externalReference": null, 1711 | "shape": "organization", 1712 | "components": { 1713 | "url": "https://auth0.com/", 1714 | "logo": [ 1715 | { 1716 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/10/auth0-logo.svg", 1717 | "altText": "Auth0 logo", 1718 | "caption": { 1719 | "json": [ 1720 | { 1721 | "kind": "block", 1722 | "type": "paragraph", 1723 | "metadata": {}, 1724 | "children": [] 1725 | } 1726 | ] 1727 | } 1728 | } 1729 | ] 1730 | }, 1731 | "topics": null 1732 | }, 1733 | { 1734 | "name": "Formidable", 1735 | "externalReference": null, 1736 | "shape": "organization", 1737 | "components": { 1738 | "url": "https://formidable.com/", 1739 | "logo": [ 1740 | { 1741 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/12/formidable-logo.svg", 1742 | "altText": "Formidable logo", 1743 | "caption": { 1744 | "json": [ 1745 | { 1746 | "kind": "block", 1747 | "type": "paragraph", 1748 | "metadata": {}, 1749 | "children": [] 1750 | } 1751 | ] 1752 | } 1753 | } 1754 | ] 1755 | }, 1756 | "topics": null 1757 | }, 1758 | { 1759 | "name": "Nerds & Company", 1760 | "externalReference": null, 1761 | "shape": "organization", 1762 | "components": { 1763 | "url": "https://nerds.company/", 1764 | "logo": [ 1765 | { 1766 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/12/nerds-company-logo.svg", 1767 | "altText": "Nerds & Company logo", 1768 | "caption": { 1769 | "json": [ 1770 | { 1771 | "kind": "block", 1772 | "type": "paragraph", 1773 | "metadata": {}, 1774 | "children": [] 1775 | } 1776 | ] 1777 | } 1778 | } 1779 | ] 1780 | }, 1781 | "topics": null 1782 | }, 1783 | { 1784 | "name": "Crystallize", 1785 | "externalReference": null, 1786 | "shape": "organization", 1787 | "components": { 1788 | "url": "https://crystallize.com/", 1789 | "logo": [ 1790 | { 1791 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/13/crystallize-logo.svg", 1792 | "altText": "Crystallize logo", 1793 | "caption": { 1794 | "json": [ 1795 | { 1796 | "kind": "block", 1797 | "type": "paragraph", 1798 | "metadata": {}, 1799 | "children": [] 1800 | } 1801 | ] 1802 | } 1803 | } 1804 | ] 1805 | }, 1806 | "topics": null 1807 | }, 1808 | { 1809 | "name": "Speed", 1810 | "externalReference": null, 1811 | "shape": "organization", 1812 | "components": { 1813 | "url": "https://speedcykkel.com/", 1814 | "logo": [ 1815 | { 1816 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/27/14/speed-logo.svg", 1817 | "altText": "Speed logo", 1818 | "caption": { 1819 | "json": [ 1820 | { 1821 | "kind": "block", 1822 | "type": "paragraph", 1823 | "metadata": {}, 1824 | "children": [] 1825 | } 1826 | ] 1827 | } 1828 | } 1829 | ] 1830 | }, 1831 | "topics": null 1832 | } 1833 | ] 1834 | }, 1835 | { 1836 | "name": "Merch", 1837 | "externalReference": null, 1838 | "shape": "default-folder", 1839 | "components": { 1840 | "title": "Super Conference Sale", 1841 | "brief": { 1842 | "json": [ 1843 | { 1844 | "kind": "block", 1845 | "type": "paragraph", 1846 | "metadata": {}, 1847 | "children": [ 1848 | { 1849 | "kind": "inline", 1850 | "metadata": {}, 1851 | "textContent": "All tickets and merch 30% off" 1852 | } 1853 | ] 1854 | } 1855 | ] 1856 | }, 1857 | "marketing-content": [] 1858 | }, 1859 | "topics": null, 1860 | "children": [ 1861 | { 1862 | "name": "Intergalactic Cap", 1863 | "externalReference": null, 1864 | "shape": "default-product", 1865 | "components": { 1866 | "summary": { 1867 | "json": [ 1868 | { 1869 | "kind": "block", 1870 | "type": "paragraph", 1871 | "metadata": {}, 1872 | "children": [ 1873 | { 1874 | "kind": "inline", 1875 | "metadata": {}, 1876 | "textContent": "Cool, sporty caps with six panels and rounded, snug fit. It truly is built to look great and last a very long time." 1877 | } 1878 | ] 1879 | } 1880 | ] 1881 | } 1882 | }, 1883 | "topics": null, 1884 | "vatType": "No Tax", 1885 | "variants": [ 1886 | { 1887 | "name": "Intergalactic Cap", 1888 | "sku": "intergalactic-cap-1641742621437", 1889 | "price": { 1890 | "default": 50 1891 | }, 1892 | "isDefault": true, 1893 | "attributes": {}, 1894 | "externalReference": null, 1895 | "stock": 300, 1896 | "images": [ 1897 | { 1898 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/10/intergalactic-cap.png", 1899 | "altText": "Purple cap", 1900 | "caption": null 1901 | } 1902 | ] 1903 | } 1904 | ] 1905 | }, 1906 | { 1907 | "name": "Intergalactic T-Shirt", 1908 | "externalReference": null, 1909 | "shape": "default-product", 1910 | "components": { 1911 | "summary": { 1912 | "json": [ 1913 | { 1914 | "kind": "block", 1915 | "type": "paragraph", 1916 | "metadata": {}, 1917 | "children": [ 1918 | { 1919 | "kind": "inline", 1920 | "metadata": {}, 1921 | "textContent": "Our intergalactic shirt is perfect for the summer. It truly is built to look great and last a very long time." 1922 | } 1923 | ] 1924 | } 1925 | ] 1926 | } 1927 | }, 1928 | "topics": null, 1929 | "vatType": "No Tax", 1930 | "variants": [ 1931 | { 1932 | "name": "Intergalactic T-Shirt", 1933 | "sku": "intergalactic-t-shirt-1641742575340", 1934 | "price": { 1935 | "default": 20 1936 | }, 1937 | "isDefault": true, 1938 | "attributes": {}, 1939 | "externalReference": null, 1940 | "stock": 300, 1941 | "images": [ 1942 | { 1943 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/9/intergalactic-tshirt.jpg", 1944 | "altText": "White t-shirt", 1945 | "caption": null 1946 | } 1947 | ] 1948 | } 1949 | ] 1950 | }, 1951 | { 1952 | "name": "Intergalactic Mug Set", 1953 | "externalReference": null, 1954 | "shape": "default-product", 1955 | "components": { 1956 | "summary": { 1957 | "json": [ 1958 | { 1959 | "kind": "block", 1960 | "type": "paragraph", 1961 | "metadata": {}, 1962 | "children": [ 1963 | { 1964 | "kind": "inline", 1965 | "metadata": {}, 1966 | "textContent": "Our 15 ounce ceramic coffee mug is perfect for all sorts of drinking activities. It truly is built to look great and last a very long time." 1967 | } 1968 | ] 1969 | } 1970 | ] 1971 | } 1972 | }, 1973 | "topics": null, 1974 | "vatType": "No Tax", 1975 | "variants": [ 1976 | { 1977 | "name": "Intergalactic Mug Set", 1978 | "sku": "intergalactic-mug-set-1641742535140", 1979 | "price": { 1980 | "default": 25 1981 | }, 1982 | "isDefault": true, 1983 | "attributes": {}, 1984 | "externalReference": null, 1985 | "stock": 300, 1986 | "images": [ 1987 | { 1988 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/8/intergalactic-mug.jpg", 1989 | "altText": "Two white mugs with a purple handle", 1990 | "caption": null 1991 | } 1992 | ] 1993 | } 1994 | ] 1995 | }, 1996 | { 1997 | "name": "Intergalactic Ticket", 1998 | "externalReference": null, 1999 | "shape": "default-product", 2000 | "components": { 2001 | "summary": { 2002 | "json": [ 2003 | { 2004 | "kind": "block", 2005 | "type": "paragraph", 2006 | "metadata": {}, 2007 | "children": [ 2008 | { 2009 | "kind": "inline", 2010 | "metadata": {}, 2011 | "textContent": "This was a year of big changes. Join an amazing list of experts to gain insights into the intergalactic world." 2012 | } 2013 | ] 2014 | } 2015 | ] 2016 | } 2017 | }, 2018 | "topics": null, 2019 | "vatType": "No Tax", 2020 | "variants": [ 2021 | { 2022 | "name": "Intergalactic Ticket", 2023 | "sku": "intergalactic-ticket-1641742735707", 2024 | "price": { 2025 | "default": 490, 2026 | "usd-sales": 345 2027 | }, 2028 | "isDefault": true, 2029 | "attributes": {}, 2030 | "externalReference": null, 2031 | "stock": 250, 2032 | "images": [ 2033 | { 2034 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/11/intergalactic-conference-tickets.jpg", 2035 | "altText": "Conference tickets", 2036 | "caption": null 2037 | } 2038 | ] 2039 | } 2040 | ] 2041 | }, 2042 | { 2043 | "name": "Lanyard", 2044 | "externalReference": null, 2045 | "shape": "default-product", 2046 | "components": { 2047 | "summary": { 2048 | "json": [ 2049 | { 2050 | "kind": "block", 2051 | "type": "paragraph", 2052 | "metadata": {}, 2053 | "children": [ 2054 | { 2055 | "kind": "inline", 2056 | "metadata": {}, 2057 | "textContent": "Cool, lanyard that can be used to display badges, tickets or ID cards. It truly is built to look great and last a very long time." 2058 | } 2059 | ] 2060 | } 2061 | ] 2062 | } 2063 | }, 2064 | "topics": null, 2065 | "vatType": "No Tax", 2066 | "variants": [ 2067 | { 2068 | "name": "Intergalactic Ticket", 2069 | "sku": "intergalactic-ticket-1641742452218", 2070 | "price": { 2071 | "default": 60 2072 | }, 2073 | "isDefault": true, 2074 | "attributes": {}, 2075 | "externalReference": null, 2076 | "stock": 300, 2077 | "images": [ 2078 | { 2079 | "src": "https://media.crystallize.com/conference-boilerplate/22/1/28/12/lanyard.png", 2080 | "altText": "Purple lanyard", 2081 | "caption": null 2082 | } 2083 | ] 2084 | } 2085 | ] 2086 | } 2087 | ] 2088 | } 2089 | ], 2090 | "stockLocations": [ 2091 | { 2092 | "identifier": "default", 2093 | "name": "Default", 2094 | "settings": null 2095 | } 2096 | ] 2097 | } -------------------------------------------------------------------------------- /src/journeys/_shared/step-access-tokens.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const importJsx = require('import-jsx'); 6 | const { Text, Newline, Box } = require('ink'); 7 | const { UncontrolledTextInput } = require('ink-text-input'); 8 | 9 | const Select = importJsx('../../ui-modules/select'); 10 | const { config, CONF_ACCESS_TOKENS } = require('../../config'); 11 | const { createAPICaller } = require('../../cli-utils/fetch-from-crystallize'); 12 | const { highlightColor } = require('../../shared'); 13 | 14 | async function verifyTokens({ id, secret }) { 15 | const callPIM = createAPICaller('https://pim.crystallize.com/graphql', { 16 | headers: { 17 | 'X-Crystallize-Access-Token-Id': id, 18 | 'X-Crystallize-Access-Token-Secret': secret, 19 | }, 20 | }); 21 | try { 22 | await callPIM({ 23 | query: ` 24 | { 25 | me { 26 | id 27 | } 28 | } 29 | `, 30 | }); 31 | 32 | return true; 33 | } catch (e) { 34 | return false; 35 | } 36 | } 37 | 38 | function GetAccessTokens({ onDone }) { 39 | const existingTokens = React.useRef(config.get(CONF_ACCESS_TOKENS)); 40 | const [verifiedExistingTokens, setVerifiedExistingTokens] = React.useState( 41 | false 42 | ); 43 | const [askForTokens, setAskForTokens] = React.useState(false); 44 | 45 | React.useEffect(() => { 46 | async function verifyExistingTokens() { 47 | const verified = await verifyTokens(existingTokens.current); 48 | if (!verified) { 49 | config.delete(CONF_ACCESS_TOKENS); 50 | existingTokens.current = null; 51 | } 52 | setVerifiedExistingTokens(true); 53 | } 54 | 55 | if (existingTokens.current && !verifiedExistingTokens) { 56 | verifyExistingTokens(); 57 | } 58 | }); 59 | 60 | if (existingTokens.current && !verifiedExistingTokens) { 61 | return ( 62 | 63 | Verifying existing Crystallize Access Tokens... 64 | 65 | ); 66 | } 67 | 68 | if (!askForTokens && existingTokens.current) { 69 | return ( 70 | 71 | Found existing Crystallize Access Tokens. Want to use it? 72 | { 140 | if (answer.value === 'yes') { 141 | config.set(CONF_ACCESS_TOKENS, { id, secret }); 142 | } else { 143 | config.delete(CONF_ACCESS_TOKENS); 144 | } 145 | onDone({ 146 | id, 147 | secret, 148 | }); 149 | }} 150 | options={[ 151 | { 152 | value: 'yes', 153 | render: Yes, please, 154 | }, 155 | { 156 | value: 'no', 157 | render: No thanks, 158 | }, 159 | ]} 160 | /> 161 | 162 | ); 163 | } 164 | 165 | return ( 166 | <> 167 | 168 | 169 | Please provide Access Tokens to bootstrap the tenant 170 | 171 | 172 | Learn about access tokens: 173 | https://crystallize.com/learn/developer-guides/access-tokens 174 | 175 | 176 | 177 | {invalidKeys && ( 178 | 179 | 180 | ⚠️ Invalid tokens supplied. Please try again ⚠️ 181 | 182 | 183 | )} 184 | 185 | {!id ? ( 186 | <> 187 | Access Token ID: 188 | setId(val)} 193 | /> 194 | 195 | ) : ( 196 | <> 197 | Access Token Secret: 198 | setSecret(val)} 203 | /> 204 | 205 | )} 206 | 207 | 208 | ); 209 | } 210 | 211 | module.exports = { GetAccessTokens }; 212 | -------------------------------------------------------------------------------- /src/journeys/_shared/step-bootstrap-tenant.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const importJsx = require('import-jsx'); 6 | const { Text, Newline, Box } = require('ink'); 7 | const { UncontrolledTextInput } = require('ink-text-input'); 8 | 9 | const { createAPICaller } = require('../../cli-utils/fetch-from-crystallize'); 10 | const Select = importJsx('../../ui-modules/select'); 11 | const { highlightColor } = require('../../shared'); 12 | const { bootstrapTenant } = require('../_shared/bootstrap-tenant'); 13 | const { GetAccessTokens } = importJsx('./step-access-tokens'); 14 | 15 | function progressText(progress) { 16 | const arr = []; 17 | arr.length = 20; 18 | arr.fill('-'); 19 | 20 | const filled = []; 21 | filled.length = parseInt(progress * 20, 10); 22 | filled.fill('='); 23 | arr.splice(0, filled.length, ...filled); 24 | 25 | return `[${arr.join('')}]`; 26 | } 27 | 28 | const areaIdToName = new Map(); 29 | areaIdToName.set('media', 'Media'); 30 | areaIdToName.set('shapes', 'Shapes'); 31 | areaIdToName.set('grids', 'Grids'); 32 | areaIdToName.set('items', 'Items'); 33 | areaIdToName.set('languages', 'Languages'); 34 | areaIdToName.set('priceVariants', 'Price variants'); 35 | areaIdToName.set('vatTypes', 'VAT types'); 36 | areaIdToName.set('topicMaps', 'Topic maps'); 37 | areaIdToName.set('customers', 'Customers'); 38 | areaIdToName.set('orders', 'Orders'); 39 | 40 | function AreaStatus({ id, progress, warnings }) { 41 | const name = areaIdToName.get(id) || id; 42 | 43 | return ( 44 | <> 45 | 46 | 47 | {progressText(progress)} |{' '} 48 | {parseInt(progress * 100) 49 | .toString() 50 | .padStart('3')} 51 | % | {name} 52 | 53 | 54 | {warnings 55 | ? warnings.map((warn, index) => ( 56 | 57 | 58 | ⚠ {warn.message} ({warn.code}) 59 | 60 | 61 | )) 62 | : null} 63 | 64 | ); 65 | } 66 | 67 | function BootstrapWarning() { 68 | return ( 69 | 76 | 77 | 78 | WARNING: this will 79 | 80 | 81 | 82 | - Alter your tenant 83 | 84 | - Use your bandwidth to upload data 85 | - Use the API count and bandwidth metrics for your tenant 86 | 87 | 88 | ); 89 | } 90 | 91 | function EnsureTenantAccess({ answers, onDone }) { 92 | const [checking, setChecking] = React.useState(true); 93 | const [tenant, setTenant] = React.useState(answers.tenant); 94 | 95 | const callPIM = React.useMemo( 96 | () => 97 | createAPICaller('https://pim.crystallize.com/graphql', { 98 | headers: { 99 | 'X-Crystallize-Access-Token-Id': answers.ACCESS_TOKEN_ID, 100 | 'X-Crystallize-Access-Token-Secret': answers.ACCESS_TOKEN_SECRET, 101 | }, 102 | }), 103 | [answers.ACCESS_TOKEN_ID, answers.ACCESS_TOKEN_SECRET] 104 | ); 105 | 106 | React.useEffect(() => { 107 | (async function check() { 108 | setChecking(true); 109 | 110 | const r = await callPIM({ 111 | query: ` 112 | { 113 | me { 114 | tenants { 115 | tenant { 116 | identifier 117 | } 118 | } 119 | } 120 | } 121 | `, 122 | }); 123 | 124 | const tenants = r.data.me.tenants.map((t) => t.tenant.identifier); 125 | if (tenants.includes(tenant)) { 126 | onDone(tenant); 127 | } else { 128 | setChecking(false); 129 | } 130 | })(); 131 | }, [tenant, callPIM, onDone]); 132 | 133 | if (checking) { 134 | return ( 135 | 136 | Checking access to tenant "{tenant}"... 137 | 138 | ); 139 | } 140 | 141 | return ( 142 | <> 143 | 144 | You have no access to tenant "{tenant}" 145 | 146 | 147 | 148 | Enter the identifier to a tenant that you have access to: 149 | 150 | setTenant(tenant)} 153 | /> 154 | 155 | 156 | ); 157 | } 158 | 159 | const askIfBootstrapTenant = { 160 | when({ answers }) { 161 | return answers.useOwnTenant; 162 | }, 163 | render({ resolveStep }) { 164 | return ( 165 | <> 166 | 167 | 168 | Would you like to bootstrap your tenant with example data? 169 | 170 | 171 | This would populate your tenant with shapes, items, topics and 172 | more 173 | 174 | 175 | 176 | resolveStep(answer.value)} 231 | options={[ 232 | { 233 | value: 'furniture', 234 | render: ( 235 | <> 236 | 237 | furniture 238 | 239 | Retail commerce, with different kinds of products. Some 240 | stories to go along with it in multiple languages. 241 | 242 | 243 | Example implementation: 244 | https://furniture.superfast.shop 245 | 246 | 247 | 248 | ), 249 | }, 250 | { 251 | value: 'voyage', 252 | render: ( 253 | <> 254 | 255 | voyage 256 | 257 | Content heavy with a story driven ecommerce 258 | 259 | 260 | Example implementation: https://voyage.superfast.shop 261 | 262 | 263 | 264 | ), 265 | }, 266 | { 267 | value: 'photofinder', 268 | render: ( 269 | <> 270 | 271 | photofinder 272 | 273 | Software As A Service (SAAS), giving access to digital 274 | photographs. Perfectly matched to work with the SAAS 275 | boilerplate. 276 | 277 | 278 | Example implementation: 279 | https://photofinder.superfast.shop 280 | 281 | 282 | 283 | ), 284 | }, 285 | { 286 | value: 'conference-boilerplate', 287 | render: ( 288 | <> 289 | 290 | Conference 291 | 292 | Conference boilerplate. 293 | 294 | 295 | Example implementation: 296 | https://conference.superfast.shop/ 297 | 298 | 299 | 300 | ), 301 | }, 302 | ]} 303 | /> 304 | 305 | 306 | ); 307 | }, 308 | answer({ answers, answer }) { 309 | answers.bootstrapTenant = answer; 310 | }, 311 | staticMessage({ answers }) { 312 | return ( 313 | 314 | Bootstrapped with example tenant{' '} 315 | {answers.bootstrapTenant} 316 | 317 | ); 318 | }, 319 | }, 320 | { 321 | when({ answers }) { 322 | return answers.bootstrapTenant !== 'no'; 323 | }, 324 | render({ resolveStep }) { 325 | return resolveStep(tokens)} />; 326 | }, 327 | answer({ answers, answer }) { 328 | answers.ACCESS_TOKEN_ID = answer.id; 329 | answers.ACCESS_TOKEN_SECRET = answer.secret; 330 | }, 331 | }, 332 | { 333 | when({ answers }) { 334 | return answers.bootstrapTenant !== 'no'; 335 | }, 336 | render({ answers, resolveStep }) { 337 | return ; 338 | }, 339 | answer({ answers, answer }) { 340 | answers.tenant = answer; 341 | }, 342 | }, 343 | ]; 344 | 345 | function RunBootstrapper({ answers, onDone }) { 346 | const [status, setStatus] = React.useState(null); 347 | 348 | React.useEffect(() => { 349 | (async function go() { 350 | const result = await bootstrapTenant({ 351 | tenant: answers.tenant, 352 | tenantSpec: answers.bootstrapTenant, 353 | id: answers.ACCESS_TOKEN_ID, 354 | secret: answers.ACCESS_TOKEN_SECRET, 355 | onUpdate(status) { 356 | setStatus(status); 357 | }, 358 | }); 359 | onDone(result); 360 | })(); 361 | // eslint-disable-next-line react-hooks/exhaustive-deps 362 | }, []); 363 | 364 | if (!status) { 365 | return null; 366 | } 367 | 368 | const keys = Object.keys(status); 369 | const firstAreas = keys.filter((k) => !['media', 'items'].includes(k)); 370 | 371 | return ( 372 | <> 373 | {firstAreas.map((area) => ( 374 | 375 | ))} 376 | 377 | 378 | 379 | ); 380 | } 381 | 382 | module.exports = { 383 | BootstrapWarning, 384 | RunBootstrapper, 385 | stepsBootstrapExampleTenant, 386 | stepBootstrapTenant: [askIfBootstrapTenant, ...stepsBootstrapExampleTenant], 387 | }; 388 | -------------------------------------------------------------------------------- /src/journeys/bootstrap-tenant/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Text, Newline, Box } = require('ink'); 6 | const importJsx = require('import-jsx'); 7 | const { UncontrolledTextInput } = require('ink-text-input'); 8 | 9 | const { highlightColor } = require('../../shared'); 10 | const Tips = importJsx('../../cli-utils/tips'); 11 | 12 | const { 13 | stepsBootstrapExampleTenant, 14 | BootstrapWarning, 15 | RunBootstrapper, 16 | } = importJsx('../_shared/step-bootstrap-tenant'); 17 | 18 | const steps = [ 19 | { 20 | render({ resolveStep }) { 21 | return ( 22 | 23 | Enter the tenant identifier you want to bootstrap 24 | 28 | 29 | ); 30 | }, 31 | answer({ answers, answer }) { 32 | answers.tenant = answer; 33 | }, 34 | staticMessage({ answers }) { 35 | return ( 36 | 37 | All right, using tenant{' '} 38 | {answers.tenant} 39 | 40 | ); 41 | }, 42 | }, 43 | ...stepsBootstrapExampleTenant, 44 | { 45 | render({ answers, resolveStep }) { 46 | return ( 47 | <> 48 | Please wait. This will take a few minutes... 49 | 50 | 51 | 52 | 53 | ); 54 | }, 55 | answer({ answers, answer }) { 56 | if (answer) { 57 | answers.bootstrapDuration = answer.duration; 58 | } 59 | }, 60 | }, 61 | { 62 | staticMessage({ answers }) { 63 | return ( 64 | 65 | 66 | ✨ {answers.tenant} is 67 | bootstrapped with {answers.bootstrapTenant} example data. 68 | {answers.bootstrapDuration && ( 69 | <> 70 | 71 | Duration: {answers.bootstrapDuration} 72 | 73 | )} 74 | 75 | 76 | ); 77 | }, 78 | }, 79 | ]; 80 | 81 | const baseAnswers = { 82 | bootstrapTenant: 'yes', 83 | }; 84 | 85 | const welcomeMessage = ( 86 | 87 | 88 | 89 | Crystallize - headless commerce for product storytellers 90 | 91 | 92 | Let's bootstrap a tenant 93 | 94 | 95 | 96 | ); 97 | 98 | module.exports = { 99 | baseAnswers, 100 | welcomeMessage, 101 | steps, 102 | }; 103 | -------------------------------------------------------------------------------- /src/journeys/download-boilerplate/index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Text, Newline } = require('ink'); 3 | const importJsx = require('import-jsx'); 4 | 5 | const steps = importJsx('./steps'); 6 | 7 | const baseAnswers = { 8 | defaultTenant: 'furniture', 9 | defaultServiceAPIURL: 'https://service-api-demo.superfast.shop/api/graphql', 10 | bootstrapTenant: 'no', 11 | }; 12 | 13 | const welcomeMessage = ( 14 | <> 15 | 16 | 17 | Crystallize - headless commerce for product storytellers 18 | 19 | 20 | Hi you, let's make something awesome! 21 | 22 | 23 | ); 24 | 25 | module.exports = { 26 | baseAnswers, 27 | welcomeMessage, 28 | steps, 29 | }; 30 | -------------------------------------------------------------------------------- /src/journeys/download-boilerplate/steps.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Text, Newline, Box } = require('ink'); 6 | const importJsx = require('import-jsx'); 7 | const { UncontrolledTextInput } = require('ink-text-input'); 8 | 9 | const { highlightColor } = require('../../shared'); 10 | 11 | const { DownloadProject } = importJsx('../../cli-utils/download-project'); 12 | const { InitProject } = importJsx('../../cli-utils/init-project'); 13 | // const GetPaymentMethods = importJsx('../../cli-utils/get-payment-methods'); 14 | const GetMultilingual = importJsx('../../cli-utils/get-multilingual'); 15 | const Tips = importJsx('../../cli-utils/tips'); 16 | const Select = importJsx('../../ui-modules/select'); 17 | const { stepBootstrapTenant, RunBootstrapper } = importJsx( 18 | '../_shared/step-bootstrap-tenant.js' 19 | ); 20 | 21 | function NodeStartCommand({ shouldUseYarn }) { 22 | return ( 23 | 24 | {shouldUseYarn ? 'yarn dev' : 'npm run dev'} 25 | 26 | ); 27 | } 28 | 29 | function InstallDepsAndBootstrap(props) { 30 | const [initDone, setInitDone] = React.useState(false); 31 | const [bootstrappingDone, setBootstrappingDone] = React.useState( 32 | props.answers.bootstrapTenant === 'no' 33 | ); 34 | 35 | React.useEffect(() => { 36 | if (initDone && bootstrappingDone) { 37 | props.resolveStep(); 38 | } 39 | }, [initDone, bootstrappingDone, props]); 40 | 41 | return ( 42 | <> 43 | {!initDone && setInitDone(true)} />} 44 | {props.answers.bootstrapTenant !== 'no' && ( 45 | 46 | 47 | Bootstrapping {props.answers.tenant} 48 | 49 | setBootstrappingDone(true)} 52 | /> 53 | 54 | )} 55 | 56 | 57 | ); 58 | } 59 | 60 | const steps = [ 61 | { 62 | render({ projectName, resolveStep }) { 63 | return ( 64 | <> 65 | 66 | Please select a boilerplate for{' '} 67 | {projectName} 68 | 69 | resolveStep(answer)} 244 | options={[ 245 | { 246 | value: 'serverless-aws', 247 | label: 'Serverless (AWS)', 248 | render: ( 249 | <> 250 | Serverless (AWS) 251 | 252 | Using the serverless framework 253 | 254 | https://www.serverless.com/ 255 | 256 | ), 257 | }, 258 | { 259 | value: 'vercel', 260 | label: 'Serverless (Vercel)', 261 | render: ( 262 | <> 263 | Serverless (Vercel) 264 | 265 | Using Vercel cloud functions 266 | 267 | 268 | https://vercel.com/docs/serverless-functions/introduction 269 | 270 | 271 | ), 272 | }, 273 | ]} 274 | /> 275 | 276 | ); 277 | }, 278 | when({ answers }) { 279 | return answers['service-api']; 280 | }, 281 | answer({ answers, answer }) { 282 | answers.serviceAPIPlatform = answer.value; 283 | answers.serviceAPIPlatformLabel = answer.label; 284 | }, 285 | staticMessage({ answers }) { 286 | return ( 287 | 288 | On{' '} 289 | {answers.serviceAPIPlatformLabel} 290 | 291 | ); 292 | }, 293 | }, 294 | { 295 | staticMessage({ projectName }) { 296 | return ( 297 | 298 | In folder ./{projectName} 299 | 300 | ); 301 | }, 302 | }, 303 | { 304 | render({ resolveStep, answers }) { 305 | return ( 306 | <> 307 | 308 | Please select a Crystallize tenant 309 | 310 | 311 | Don't have a tenant yet? Create one at 312 | https://crystallize.com/signup 313 | 314 | 315 | resolveStep(answer)} 452 | options={[ 453 | { 454 | value: answers.defaultServiceAPIURL, 455 | render: ( 456 | <> 457 | Our demo API ({answers.defaultServiceAPIURL}) 458 | 459 | 460 | User authentication, basket and checkout using the 461 | furniture tenant 462 | 463 | 464 | ), 465 | }, 466 | { 467 | value: '[use-own-service-api]', 468 | render: ( 469 | <> 470 | My own Service API 471 | 472 | ), 473 | }, 474 | ]} 475 | /> 476 | 477 | ); 478 | }, 479 | answer({ answers, answer }) { 480 | answers.serviceAPIURL = answer.value; 481 | }, 482 | when({ answers }) { 483 | return answers.nextjs || answers.gatsby || answers.rn || answers.nuxtjs; 484 | }, 485 | }, 486 | { 487 | render({ resolveStep, answers }) { 488 | return ( 489 | 490 | 491 | Enter the URI to your Service API: 492 | 493 | resolveStep(query)} 496 | /> 497 | 498 | ); 499 | }, 500 | answer({ answers, answer }) { 501 | answers.serviceAPIURL = answer; 502 | }, 503 | when({ answers }) { 504 | return answers.serviceAPIURL === '[use-own-service-api]'; 505 | }, 506 | }, 507 | { 508 | when({ answers }) { 509 | return answers.nextjs || answers.gatsby; 510 | }, 511 | staticMessage({ answers }) { 512 | if (answers.serviceAPIURL === answers.defaultServiceAPIURL) { 513 | return ( 514 | 515 | With the demo Service API 516 | 517 | ); 518 | } 519 | return ( 520 | 521 | With Service API URI:{' '} 522 | {answers.serviceAPIURL} 523 | 524 | ); 525 | }, 526 | }, 527 | { 528 | render(props) { 529 | return ( 530 | <> 531 | 532 | 533 | 534 | 535 | ); 536 | }, 537 | }, 538 | { 539 | render(props) { 540 | return ; 541 | }, 542 | }, 543 | { 544 | staticMessage({ projectName, projectPath, answers, shouldUseYarn }) { 545 | return ( 546 | 547 | 548 | 549 | ✨ "{projectName}" is ready ✨ 550 | 551 | {projectPath} 552 | 553 | 554 | {answers['service-api'] && ( 555 | <> 556 | 557 | 558 | Configure tokens and secrets in the .env.local file: 559 | 560 | 566 | 567 | CRYSTALLIZE_ACCESS_TOKEN_ID= 568 | 569 | CRYSTALLIZE_ACCESS_TOKEN_SECRET= 570 | 571 | ...and so on 572 | 573 | 574 | 575 | 576 | 577 | Configure the app language, price variant and such in{' '} 578 | app.config.json 579 | 580 | 581 | {answers.serviceAPIPlatform === 'vercel' && ( 582 | 583 | 584 | Read here on how to setup Vercel env values: 585 | 586 | https://vercel.com/blog/environment-variables-ui 587 | 588 | 589 | )} 590 | 591 | )} 592 | 593 | 594 | 595 | Now, navigate to the project and start creating magic. 596 | 597 | 598 | {answers['nextjs-subscription-commerce'] || answers['nextjs-conference'] ? ( 599 | <> 600 | 601 | Website 602 | 603 | 604 | cd ./{projectName}/website &&{' '} 605 | 606 | 607 | 608 | 609 | Service API 610 | 611 | 612 | cd ./{projectName}/service-api &&{' '} 613 | 614 | 615 | 616 | 617 | Psst: remember to setup keys in .env.local 618 | 619 | 620 | ) : ( 621 | <> 622 | cd ./{projectName} 623 | 624 | 625 | 626 | )} 627 | 628 | 629 | 630 | 631 | Go fast and prosper! 632 | 633 | The milliseconds are with you 634 | 635 | 636 | 637 | 638 | 639 | Want to get in touch with us? We would love to hear from you! 640 | 641 | 642 | Reach us at: https://crystallize.com/about#contact 643 | 644 | 645 | ); 646 | }, 647 | }, 648 | ]; 649 | 650 | module.exports = steps; 651 | -------------------------------------------------------------------------------- /src/shared.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | module.exports = { 5 | highlightColor: '#FFBD54', 6 | }; 7 | -------------------------------------------------------------------------------- /src/ui-modules/multi-select.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Text, useInput, Box } = require('ink'); 6 | 7 | const { highlightColor } = require('../shared'); 8 | 9 | function MultiSelect({ options, compact, onChange }) { 10 | const [selected, setSelected] = React.useState( 11 | options.reduce((acc, item, index) => { 12 | if (item.checked) { 13 | acc.push(index); 14 | } 15 | return acc; 16 | }, []) 17 | ); 18 | const [highlight, setHighlight] = React.useState(0); 19 | 20 | useInput( 21 | (input, key) => { 22 | if (key.return) { 23 | setSelected([]); 24 | setHighlight(0); 25 | onChange(selected.map((s) => options[s])); 26 | return; 27 | } 28 | 29 | let newHighlight = highlight; 30 | if (key.upArrow) { 31 | newHighlight--; 32 | } else if (key.downArrow) { 33 | newHighlight++; 34 | } 35 | if (newHighlight < 0) { 36 | newHighlight = options.length - 1; 37 | } else if (newHighlight > options.length - 1) { 38 | newHighlight = 0; 39 | } 40 | 41 | setHighlight(newHighlight); 42 | 43 | if (input === ' ') { 44 | const newSelected = [...selected]; 45 | const index = newSelected.indexOf(highlight); 46 | 47 | if (index === -1) { 48 | setSelected([...newSelected, highlight]); 49 | } else { 50 | newSelected.splice(index, 1); 51 | setSelected(newSelected); 52 | } 53 | } 54 | }, 55 | [selected, highlight, setSelected, setHighlight] 56 | ); 57 | 58 | return ( 59 | 60 | {options.map((o, i) => ( 61 | 62 | 63 | 64 | {selected.includes(i) ? '[✓]' : '[ ]'} 65 | 66 | 67 | 68 | 69 | {o.render || o.label} 70 | 71 | 72 | 73 | ))} 74 | 75 | ); 76 | } 77 | 78 | module.exports = MultiSelect; 79 | -------------------------------------------------------------------------------- /src/ui-modules/select.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Text, useInput, Box, Newline } = require('ink'); 6 | 7 | const { highlightColor } = require('../shared'); 8 | 9 | /** 10 | * Due to terminals not always being that large, we need 11 | * to cap the max amount of options to display to a low 12 | * number. 13 | */ 14 | const maxOptionsToDisplay = 3; 15 | 16 | function Select({ options, compact, onChange, initialSelected = 0 }) { 17 | const [selected, setSelected] = React.useState(initialSelected); 18 | 19 | useInput((input, key) => { 20 | if (key.return) { 21 | setSelected(0); 22 | onChange(options[selected]); 23 | return; 24 | } 25 | 26 | let newSel = selected; 27 | if (key.upArrow) { 28 | newSel--; 29 | } else if (key.downArrow) { 30 | newSel++; 31 | } 32 | if (newSel < 0) { 33 | newSel = options.length - 1; 34 | } else if (newSel > options.length - 1) { 35 | newSel = 0; 36 | } 37 | 38 | setSelected(newSel); 39 | }); 40 | 41 | let optionsToDisplay = options.slice( 42 | selected - 1, 43 | selected - 1 + maxOptionsToDisplay 44 | ); 45 | if (selected === 0) { 46 | optionsToDisplay = options.slice(0, maxOptionsToDisplay); 47 | } else if (selected === options.length - 1) { 48 | optionsToDisplay = options.slice(-maxOptionsToDisplay); 49 | } 50 | 51 | let overflowItem = null; 52 | let lastDisplayedIndex = options.findIndex( 53 | (o) => o === optionsToDisplay[optionsToDisplay.length - 1] 54 | ); 55 | if (lastDisplayedIndex < options.length - 1) { 56 | overflowItem = options[lastDisplayedIndex + 1]; 57 | } 58 | 59 | return ( 60 | 61 | {optionsToDisplay.map((o) => ( 62 | 63 | 64 | 65 | {options[selected].value === o.value ? '>' : ''} 66 | 67 | 68 | 69 | 70 | {o.render || o.label} 71 | 72 | 73 | 74 | ))} 75 | {overflowItem && ( 76 | 77 | 78 | {overflowItem.label} 79 | 80 | ... 81 | 82 | 83 | )} 84 | 85 | ); 86 | } 87 | 88 | module.exports = Select; 89 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const React = require('react'); 5 | const { Box, Text, useStdin } = require('ink'); 6 | const produce = require('immer').default; 7 | 8 | const { highlightColor } = require('./shared'); 9 | 10 | function App({ journey, ...globalOptions }) { 11 | const { isRawModeSupported } = useStdin(); 12 | const [stepIndex, setStepIndex] = React.useState(0); 13 | 14 | const [answers, setAnswers] = React.useState(journey.baseAnswers); 15 | const [staticMessages, setStaticMessages] = React.useState([ 16 | () => journey.welcomeMessage, 17 | ]); 18 | 19 | const { steps } = journey; 20 | let step = steps[stepIndex]; 21 | 22 | React.useEffect(() => { 23 | if (stepIndex === -1) { 24 | setTimeout(() => { 25 | process.exit(0); 26 | }, 1000); 27 | } 28 | }, [stepIndex]); 29 | 30 | /** 31 | * If we cannot receive user input, we will just defer to the 32 | * Next.JS boilerplate with standard settings 33 | */ 34 | if (!isRawModeSupported) { 35 | const newIndex = steps.findIndex((s) => s.name === 'download'); 36 | setStepIndex(newIndex); 37 | setAnswers({ 38 | nextjs: true, 39 | boilerplate: 'Next.js', 40 | tenant: answers.defaultTenant, 41 | multilingual: ['en'], 42 | }); 43 | setStaticMessages((messages) => [ 44 | ...messages, 45 | () => ( 46 | 47 | Using boilerplate Next.js 48 | 49 | ), 50 | () => ( 51 | 52 | In folder{' '} 53 | ./{globalOptions.projectName} 54 | 55 | ), 56 | ]); 57 | return null; 58 | } 59 | 60 | function resolveStep(answer) { 61 | const staticMessages = []; 62 | 63 | function getNextStepIndex() { 64 | const startAt = stepIndex + 1; 65 | for (let i = startAt; i < steps.length; i++) { 66 | const s = steps[i]; 67 | if (!s.when || s.when({ answers: nextAnswers })) { 68 | if (!s.render && s.staticMessage) { 69 | staticMessages.push(s.staticMessage); 70 | } else { 71 | return i; 72 | } 73 | } 74 | } 75 | return -1; 76 | } 77 | 78 | let nextAnswers; 79 | if (step.answer) { 80 | nextAnswers = produce({ answers, answer }, step.answer).answers; 81 | } 82 | 83 | if (nextAnswers) { 84 | setAnswers(nextAnswers); 85 | } 86 | 87 | // Collect any static message from the answer 88 | if (step.staticMessage) { 89 | staticMessages.push(step.staticMessage); 90 | } 91 | 92 | let nextStepIndex = getNextStepIndex(); 93 | if (staticMessages.length > 0) { 94 | setStaticMessages((messages) => [...messages, ...staticMessages]); 95 | } 96 | 97 | setStepIndex(nextStepIndex); 98 | } 99 | 100 | if (step) { 101 | const { render } = step; 102 | 103 | return ( 104 | 105 | 106 | {staticMessages.map((m, i) => ( 107 | 108 | {m({ ...globalOptions, answers })} 109 | 110 | ))} 111 | 112 | 113 | {render({ ...globalOptions, answers, resolveStep })} 114 | 115 | ); 116 | } 117 | 118 | return ( 119 | 120 | {staticMessages.map((m, i) => ( 121 | 122 | {m({ ...globalOptions, answers })} 123 | 124 | ))} 125 | 126 | ); 127 | } 128 | 129 | module.exports = App; 130 | --------------------------------------------------------------------------------