create-houdini 0.0.0-20231008055552

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/bin.js +416 -0
  4. package/fragments/localSchema/react/src/api/+schema.js +14 -0
  5. package/fragments/localSchema/react-typescript/src/api/+schema.ts +14 -0
  6. package/package.json +38 -0
  7. package/templates/react/.graphqlrc.yaml +8 -0
  8. package/templates/react/.meta.json +3 -0
  9. package/templates/react/houdini.config.js +9 -0
  10. package/templates/react/package.json +27 -0
  11. package/templates/react/src/+client.jsx +4 -0
  12. package/templates/react/src/+index.jsx +47 -0
  13. package/templates/react/src/routes/+page.jsx +14 -0
  14. package/templates/react/vite.config.ts +8 -0
  15. package/templates/react-typescript/.graphqlrc.yaml +8 -0
  16. package/templates/react-typescript/.meta.json +3 -0
  17. package/templates/react-typescript/houdini.config.js +9 -0
  18. package/templates/react-typescript/package.json +30 -0
  19. package/templates/react-typescript/src/+client.tsx +4 -0
  20. package/templates/react-typescript/src/+index.tsx +47 -0
  21. package/templates/react-typescript/src/routes/+page.tsx +14 -0
  22. package/templates/react-typescript/tsconfig.json +3 -0
  23. package/templates/react-typescript/vite.config.ts +8 -0
  24. package/templates/sveltekit-demo/.env.local +1 -0
  25. package/templates/sveltekit-demo/.eslintignore +13 -0
  26. package/templates/sveltekit-demo/.graphqlrc.yaml +9 -0
  27. package/templates/sveltekit-demo/.meta.json +5 -0
  28. package/templates/sveltekit-demo/.prettierignore +15 -0
  29. package/templates/sveltekit-demo/.prettierrc +9 -0
  30. package/templates/sveltekit-demo/README.md +14 -0
  31. package/templates/sveltekit-demo/houdini.config.js +13 -0
  32. package/templates/sveltekit-demo/package.json +38 -0
  33. package/templates/sveltekit-demo/playwright.config.ts +12 -0
  34. package/templates/sveltekit-demo/schema.graphql +23 -0
  35. package/templates/sveltekit-demo/src/app.d.ts +12 -0
  36. package/templates/sveltekit-demo/src/app.html +12 -0
  37. package/templates/sveltekit-demo/src/client.ts +16 -0
  38. package/templates/sveltekit-demo/src/index.test.ts +7 -0
  39. package/templates/sveltekit-demo/src/lib/index.ts +1 -0
  40. package/templates/sveltekit-demo/src/routes/+layout.gql +3 -0
  41. package/templates/sveltekit-demo/src/routes/+layout.svelte +23 -0
  42. package/templates/sveltekit-demo/src/routes/+page.svelte +26 -0
  43. package/templates/sveltekit-demo/src/routes/links/+page.gql +8 -0
  44. package/templates/sveltekit-demo/src/routes/links/+page.svelte +17 -0
  45. package/templates/sveltekit-demo/src/routes/links/Link.svelte +39 -0
  46. package/templates/sveltekit-demo/src/routes/sponsors/+page.gql +6 -0
  47. package/templates/sveltekit-demo/src/routes/sponsors/+page.svelte +47 -0
  48. package/templates/sveltekit-demo/src/routes/sponsors/Sponsors.svelte +34 -0
  49. package/templates/sveltekit-demo/static/favicon.png +0 -0
  50. package/templates/sveltekit-demo/svelte.config.js +18 -0
  51. package/templates/sveltekit-demo/tests/test.ts +6 -0
  52. package/templates/sveltekit-demo/tsconfig.json +14 -0
  53. package/templates/sveltekit-demo/vite.config.ts +7 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Alec Aivazis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # create-houdini
2
+
3
+ A CLI for creating new [Houdini](https://houdinigraphql.com/) projects.
4
+
5
+ ```bash
6
+ npm create houdini@latest hello-houdini
7
+ ```
8
+
package/bin.js ADDED
@@ -0,0 +1,416 @@
1
+ #!/usr/bin/env node
2
+ import * as p from '@clack/prompts'
3
+ import { program, Option, InvalidArgumentError } from 'commander'
4
+ import * as graphql from 'graphql'
5
+ import { bold, cyan, gray, grey, italic, white } from 'kleur/colors'
6
+ import fs, { readFileSync, writeFileSync } from 'node:fs'
7
+ import path from 'node:path'
8
+ import { exit } from 'node:process'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ // the first argument is the name of the project
12
+ let projectDir = process.argv[2]
13
+ let projectName = projectDir
14
+
15
+ // log the version of create-houdini that this was run with by looking at the packge's package.json
16
+ const { version } = JSON.parse(fs.readFileSync(new URL('package.json', import.meta.url), 'utf-8'))
17
+ console.log(`${grey(`create-houdini version ${version}`)}\n`)
18
+
19
+ // prepare options
20
+ const templatesDir = sourcePath(`./templates`)
21
+ const options = fs.readdirSync(templatesDir).map((templateDir) => {
22
+ // in .meta.json you can find:
23
+ /** @type {{label?: string, hint?: string, apiUrl?: string}} */
24
+ let data = {}
25
+ const metaPath = path.join(templatesDir, templateDir, '.meta.json')
26
+ if (fs.existsSync(metaPath)) {
27
+ data = JSON.parse(readFileSync(metaPath, 'utf-8'))
28
+ }
29
+ return { ...data, value: templateDir }
30
+ })
31
+
32
+ program.argument('[project_name]', 'optional project name')
33
+ program.addOption(
34
+ new Option('-t, --template <template>', 'template you want to use').choices(
35
+ options.map((c) => c.value)
36
+ )
37
+ )
38
+ program.addOption(
39
+ new Option('-s, --schema <schema>', '"local" or "http..."').argParser((value) => {
40
+ if (value === 'local' || value.startsWith('http')) {
41
+ return value
42
+ }
43
+ throw new InvalidArgumentError('Should be "local" or "http..." or do not set it!')
44
+ })
45
+ )
46
+
47
+ program.parse(process.argv)
48
+ const options_cli = program.opts()
49
+
50
+ p.intro('🎩 Welcome to Houdini!')
51
+
52
+ // if we weren't given a directory, then we should ask
53
+ if (!projectDir) {
54
+ const dir = await p.text({
55
+ message: `Where should we create your project?`,
56
+ placeholder: ' (press Enter to use the current directory)',
57
+ })
58
+
59
+ if (p.isCancel(dir)) {
60
+ process.exit(1)
61
+ }
62
+
63
+ if (dir) {
64
+ projectDir = dir
65
+ projectName = 'hello-houdini'
66
+ } else {
67
+ projectDir = '.'
68
+ }
69
+ }
70
+
71
+ // if we were told to use the current directory then we need
72
+ // a more appropriate name for the project
73
+ if (projectDir === '.') {
74
+ projectName = 'hello-houdini'
75
+ }
76
+ let dirToCreate = true
77
+
78
+ // project location emtpy?
79
+ if (fs.existsSync(projectDir)) {
80
+ if (fs.readdirSync(projectDir).length > 0) {
81
+ const force = await p.confirm({
82
+ message:
83
+ 'Target directory is not empty. Continue anyway? This might overwrite existing files.',
84
+ initialValue: false,
85
+ })
86
+ dirToCreate = false
87
+
88
+ // bail if `force` is `false` or the user cancelled with Ctrl-C
89
+ if (force !== true) {
90
+ process.exit(1)
91
+ }
92
+ }
93
+ }
94
+
95
+ // create the project directory if necessary
96
+ if (dirToCreate && !fs.existsSync(projectDir)) {
97
+ fs.mkdirSync(projectDir)
98
+ }
99
+
100
+ const template = options_cli.template
101
+ ? options_cli.template
102
+ : await p.select({
103
+ message: 'Which template do you want to use?',
104
+ initialValue: 'react-typescript',
105
+ options,
106
+ })
107
+ if (p.isCancel(template)) {
108
+ process.exit(1)
109
+ }
110
+ const templateDir = path.join(templatesDir, template)
111
+ const templateMeta = options.find((option) => option.value === template)
112
+ if (!templateMeta) {
113
+ // this will never happen, but it helps to types later
114
+ exit(1)
115
+ }
116
+
117
+ // ask if the schema is local or remote
118
+ const localSchema = templateMeta.apiUrl
119
+ ? false
120
+ : options_cli.schema === 'local'
121
+ ? true
122
+ : options_cli.schema?.startsWith('http')
123
+ ? false
124
+ : await p.confirm({
125
+ message: 'Is your api going to be defined in this project too?',
126
+ })
127
+
128
+ // if we have a remote schema then we need to introspect it and write the value
129
+ let apiUrl = options_cli.schema?.startsWith('http') ? options_cli.schema : templateMeta.apiUrl ?? ''
130
+ if (!localSchema) {
131
+ let pullSchema_content = ''
132
+ if (apiUrl === '') {
133
+ const { apiUrl: apiUrlCli, pullSchema_content: pullSchema_content_cli } =
134
+ await pullSchemaCli()
135
+ apiUrl = apiUrlCli
136
+ if (pullSchema_content_cli === null) {
137
+ pCancel('There was a problem pulling your shema. Please try again.')
138
+ } else {
139
+ pullSchema_content = pullSchema_content_cli
140
+ }
141
+ } else {
142
+ const pullSchema_content_local = await pullSchema(apiUrl, {})
143
+ if (pullSchema_content_local === null) {
144
+ pCancel('There was a problem pulling your shema. Please report this on Discord.')
145
+ } else {
146
+ pullSchema_content = pullSchema_content_local
147
+ }
148
+ }
149
+
150
+ writeFileSync(path.join(projectDir, 'schema.graphql'), pullSchema_content)
151
+ }
152
+
153
+ // the final client config depends on whether we have a local schema or not
154
+ const clientConfig = localSchema
155
+ ? ``
156
+ : `{
157
+ url: '${apiUrl}',
158
+ }`
159
+
160
+ const configFile = localSchema
161
+ ? ''
162
+ : `
163
+ watchSchema: {
164
+ url: '${apiUrl}',
165
+ },
166
+ `
167
+
168
+ copy(
169
+ sourcePath(path.join(templatesDir, template)),
170
+ projectDir,
171
+ {
172
+ API_URL: apiUrl,
173
+ PROJECT_NAME: projectName,
174
+ HOUDINI_VERSION: version,
175
+ ["'CLIENT_CONFIG'"]: clientConfig,
176
+ ["'CONFIG_FILE'"]: configFile,
177
+ },
178
+ ['.meta.json']
179
+ )
180
+
181
+ // if we have a local schema then we have more files to copy
182
+ if (localSchema) {
183
+ copy(sourcePath('./fragments/localSchema/' + template))
184
+ }
185
+
186
+ // If anything goes wrong, we don't want to block the user
187
+ let sponsor_msg = ''
188
+ try {
189
+ const selected = await getSponsors()
190
+ sponsor_msg = `🙏 Special thanks to the ${bold(white(selected))} for supporting Houdini!`
191
+ } catch (error) {}
192
+
193
+ p.outro(`🎉 Everything is ready!
194
+
195
+ 👉 Next Steps
196
+ 0️⃣ Go to your project : cd ${projectDir}
197
+ 1️⃣ Install dependencies : npm i | pnpm i | yarn
198
+ 2️⃣ Start your application : npm run dev | pnpm dev | yarn dev`)
199
+
200
+ console.log(
201
+ gray(
202
+ italic(
203
+ `${bold('❔ More help')} ` +
204
+ `at ${cyan('https://houdinigraphql.com')} ` +
205
+ `(📄 Docs, ⭐ Github, 📣 Discord, ...) ` +
206
+ `${sponsor_msg ? `\n${sponsor_msg}` : ``}\n`
207
+ )
208
+ )
209
+ )
210
+
211
+ // Function to copy files recursively
212
+ function copy(
213
+ /** @type {string} */ sourceDir,
214
+ /** @type {string} */ destDir = projectDir,
215
+ /** @type {Record<string, string>} */ transformMap = {},
216
+ /** @type {string[]} */ ignoreList = []
217
+ ) {
218
+ if (!fs.existsSync(destDir)) {
219
+ fs.mkdirSync(destDir)
220
+ }
221
+
222
+ const files = fs.readdirSync(sourceDir)
223
+ for (const file of files) {
224
+ const sourceFilePath = path.join(sourceDir, file)
225
+ const sourceRelative = path.relative(templateDir, sourceFilePath)
226
+ // skip the ignore list
227
+ if (!ignoreList.includes(sourceRelative)) {
228
+ const destFilePath = path.join(destDir, file)
229
+
230
+ const stats = fs.statSync(sourceFilePath)
231
+
232
+ // files need to be copied and potentially transformed
233
+ if (stats.isFile()) {
234
+ // read the source file
235
+ const source = fs.readFileSync(sourceFilePath)
236
+
237
+ // apply any transformations if necessary
238
+ const transformed = Object.entries(transformMap).reduce(
239
+ (prev, [pattern, value]) => {
240
+ return prev.replaceAll(pattern, value)
241
+ },
242
+ source.toString()
243
+ )
244
+
245
+ // write the result
246
+ fs.writeFileSync(destFilePath, transformed)
247
+ }
248
+ // if we run into a directory then we should keep going
249
+ else if (stats.isDirectory()) {
250
+ copy(sourceFilePath, destFilePath, transformMap, ignoreList)
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ function sourcePath(/** @type {string} */ path) {
257
+ return fileURLToPath(new URL(path, import.meta.url).href)
258
+ }
259
+
260
+ async function pullSchemaCli() {
261
+ let number_of_round = 0
262
+ let url_and_headers = ''
263
+ let apiUrl = ''
264
+ let pullSchema_content = null
265
+ while (pullSchema_content === null && number_of_round < 10) {
266
+ number_of_round++
267
+ const answer = await p.group(
268
+ {
269
+ url_and_headers: async () =>
270
+ p.text({
271
+ defaultValue: 'http://localhost:4000/graphql',
272
+ message: `What's the URL for your api? ${
273
+ number_of_round === 1 ? '' : `(attempt ${number_of_round}/10)`
274
+ }`,
275
+ placeholder: `http://localhost:4000/graphql ${
276
+ number_of_round === 1 ? '' : 'Authorization=Bearer MyToken'
277
+ }`,
278
+ // initialValue: url_and_headers,
279
+ validate: (value) => {
280
+ // If empty, let's assume the placeholder value
281
+ if (value === '') {
282
+ value = 'http://localhost:4000/graphql'
283
+ }
284
+
285
+ if (!value.startsWith('http')) {
286
+ return 'Please enter a valid URL'
287
+ }
288
+ },
289
+ }),
290
+ },
291
+ {
292
+ onCancel: () => pCancel(),
293
+ }
294
+ )
295
+
296
+ url_and_headers = answer.url_and_headers
297
+ const value_splited = url_and_headers.split(' ')
298
+ const apiUrl = value_splited[0]
299
+
300
+ const local_headers =
301
+ value_splited.length > 1
302
+ ? // remove the url and app all the headers
303
+ extractHeadersStr(value_splited.slice(1).join(' '))
304
+ : {}
305
+
306
+ pullSchema_content = await pullSchema(apiUrl, local_headers)
307
+
308
+ if (pullSchema_content === null) {
309
+ const msg = `If you need to pass headers, add them after the URL (eg: '${`${apiUrl} Authorization=Bearer MyToken`}')`
310
+ p.log.error(msg)
311
+ }
312
+ }
313
+
314
+ // if we are here... it means that we have tried x times to pull the schema and it failed
315
+ if (pullSchema_content === null) {
316
+ pCancel(
317
+ 'There was a problem pulling your schema. Please check your URL/headers and try again.'
318
+ )
319
+ }
320
+
321
+ return { apiUrl, pullSchema_content }
322
+ }
323
+
324
+ async function pullSchema(
325
+ /** @type {string} */ url,
326
+ /** @type {Record<string, string>} */ headers
327
+ ) {
328
+ let content = ''
329
+ const spinnerPullSchema = p.spinner()
330
+ spinnerPullSchema.start('Pulling your schema...')
331
+ let fileData = null
332
+ try {
333
+ // send the request
334
+ const resp = await fetch(url, {
335
+ method: 'POST',
336
+ body: JSON.stringify({
337
+ query: graphql.getIntrospectionQuery(),
338
+ }),
339
+ headers: { 'Content-Type': 'application/json', ...headers },
340
+ })
341
+ content = await resp.text()
342
+
343
+ const jsonSchema = JSON.parse(content).data
344
+ const schema = graphql.buildClientSchema(jsonSchema)
345
+ fileData = graphql.printSchema(graphql.lexicographicSortSchema(schema))
346
+ } catch (/** @type {any} */ e) {
347
+ if (content) {
348
+ console.warn(
349
+ `⚠️ Couldn't pull your schema.
350
+ ${' Reponse:'} ${content}
351
+ ${' Error :'} ${e.message}`
352
+ )
353
+ } else {
354
+ console.warn(`⚠️ Couldn't pull your schema: ${e.message}`)
355
+ }
356
+ }
357
+ spinnerPullSchema.stop(fileData ? 'Schema pulled 🪄' : 'Failed to pull schema!')
358
+ return fileData
359
+ }
360
+
361
+ function extractHeadersStr(/** @type {string} */ str) {
362
+ const regex = /(\w+)=("[^"]*"|[^ ]*)/g
363
+ const /** @type {Record<string, string>} */ obj = {}
364
+
365
+ let match
366
+ while ((match = regex.exec(str ?? '')) !== null) {
367
+ obj[match[1]] = match[2].replaceAll('"', '')
368
+ }
369
+
370
+ return obj
371
+ }
372
+
373
+ function pCancel(cancelText = 'Operation cancelled.') {
374
+ p.cancel(cancelText)
375
+ process.exit(1)
376
+ }
377
+
378
+ async function getSponsors() {
379
+ const res = await fetch(
380
+ 'https://raw.githubusercontent.com/HoudiniGraphql/sponsors/main/generated/sponsors.json'
381
+ )
382
+ const /**@type {any[]} */ jsonData = await res.json()
383
+
384
+ /** @returns {[number, string]} */
385
+ function getTier(/**@type {number}*/ value) {
386
+ if (value >= 1500) {
387
+ return [10, 'Wizard']
388
+ }
389
+ if (value >= 500) {
390
+ return [5, 'Mage']
391
+ }
392
+ if (value >= 25) {
393
+ return [2, "Magician's Apprentice"]
394
+ }
395
+ if (value >= 10) {
396
+ return [1, 'Supportive Muggle']
397
+ }
398
+ // don't display the past sponsors
399
+ return [0, 'Past Sponsors']
400
+ }
401
+
402
+ const list = jsonData.flatMap(
403
+ (/** @type {{sponsor: {name: string}, monthlyDollars: number}} */ c) => {
404
+ const [coef, title] = getTier(c.monthlyDollars)
405
+ const names = []
406
+ for (let i = 0; i < coef; i++) {
407
+ names.push(`${title}, ${c.sponsor.name}`)
408
+ }
409
+ return names
410
+ }
411
+ )
412
+
413
+ const selected_to_display = list[Math.floor(Math.random() * list.length)]
414
+
415
+ return selected_to_display
416
+ }
@@ -0,0 +1,14 @@
1
+ import { createSchema } from 'graphql-yoga'
2
+
3
+ export default createSchema({
4
+ typeDefs: /* GraphQL */ `
5
+ type Query {
6
+ hello: String
7
+ }
8
+ `,
9
+ resolvers: {
10
+ Query: {
11
+ hello: () => 'world',
12
+ },
13
+ },
14
+ })
@@ -0,0 +1,14 @@
1
+ import { createSchema } from 'graphql-yoga'
2
+
3
+ export default createSchema({
4
+ typeDefs: /* GraphQL */ `
5
+ type Query {
6
+ hello: String
7
+ }
8
+ `,
9
+ resolvers: {
10
+ Query: {
11
+ hello: () => 'world',
12
+ },
13
+ },
14
+ })
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "create-houdini",
3
+ "version": "0.0.0-20231008055552",
4
+ "description": "A CLI for creating new Houdini projects",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/HoudiniGraphQL/houdini",
8
+ "directory": "packages/create-houdini"
9
+ },
10
+ "license": "MIT",
11
+ "homepage": "https://houdinigraphql.com/",
12
+ "bin": "./bin.js",
13
+ "dependencies": {
14
+ "@clack/prompts": "^0.6.3",
15
+ "commander": "^9.4.0",
16
+ "graphql": "16.8.0",
17
+ "kleur": "^4.1.5"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^18.7.23",
21
+ "prettier": "^2.8.3"
22
+ },
23
+ "files": [
24
+ "fragments",
25
+ "templates",
26
+ "bin.js"
27
+ ],
28
+ "types": "types/index.d.ts",
29
+ "type": "module",
30
+ "scripts": {
31
+ "dev": "node bin.js",
32
+ "build:": "cd ../../ && ((pnpm run build && cd -) || (cd - && exit 1))",
33
+ "build:dev": "pnpm build: && pnpm dev",
34
+ "check": "tsc",
35
+ "lint": "prettier --check . --config ../../.prettierrc --ignore-path ../../.gitignore --ignore-path .gitignore --plugin prettier-plugin-svelte --plugin-search-dir=.",
36
+ "format": "pnpm lint --write"
37
+ }
38
+ }
@@ -0,0 +1,8 @@
1
+ projects:
2
+ default:
3
+ schema:
4
+ - ./$houdini/graphql/schema.graphql
5
+ documents:
6
+ - '**/*.gql'
7
+ - '**/*.jsx'
8
+ - ./$houdini/graphql/documents.gql
@@ -0,0 +1,3 @@
1
+ {
2
+ "label": "React"
3
+ }
@@ -0,0 +1,9 @@
1
+ /// <references types="houdini-react">
2
+ /** @type {import('houdini').ConfigFile} */
3
+ const config = {'CONFIG_FILE'
4
+ plugins: {
5
+ 'houdini-react': {},
6
+ },
7
+ }
8
+
9
+ export default config
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "PROJECT_NAME",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "vite build",
8
+ "dev": "vite",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "houdini": "^HOUDINI_VERSION",
13
+ "houdini-react": "^HOUDINI_VERSION",
14
+ "react": "^18.2.0",
15
+ "react-dom": "^18.2.0",
16
+ "graphql-yoga": "4.0.4",
17
+ "graphql": "15.8.0",
18
+ "@whatwg-node/server": "^0.9.14"
19
+ },
20
+ "devDependencies": {
21
+ "@vitejs/plugin-react": "^3.1.0",
22
+ "vite": "^4.1.0"
23
+ },
24
+ "resolutions": {
25
+ "graphql": "15.8.0"
26
+ }
27
+ }
@@ -0,0 +1,4 @@
1
+ import { HoudiniClient } from '$houdini'
2
+
3
+ // Export the Houdini client
4
+ export default new HoudiniClient('CLIENT_CONFIG')
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+
3
+ export default function App({ children }) {
4
+ return (
5
+ <html>
6
+ <head>
7
+ <meta charSet="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
9
+ <link
10
+ rel="icon"
11
+ type="image/png"
12
+ href="https://houdinigraphql.com/images/logo.png"
13
+ />
14
+ <link
15
+ rel="stylesheet"
16
+ href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css"
17
+ />
18
+ <title>Houdini • React</title>
19
+ </head>
20
+ <body>
21
+ <ErrorBoundary>{children}</ErrorBoundary>
22
+ </body>
23
+ </html>
24
+ );
25
+ }
26
+
27
+ class ErrorBoundary extends React.Component {
28
+ constructor(props) {
29
+ super(props);
30
+ this.state = { hasError: false };
31
+ }
32
+
33
+ static getDerivedStateFromError(error) {
34
+ return { hasError: true };
35
+ }
36
+
37
+ componentDidCatch(error, info) {
38
+ console.error("ErrorBoundary caught an error:", error, info);
39
+ }
40
+
41
+ render() {
42
+ if (this.state.hasError) {
43
+ return <h1>Something went wrong.</h1>;
44
+ }
45
+ return this.props.children;
46
+ }
47
+ }
@@ -0,0 +1,14 @@
1
+ // import type { PageProps } from "./$types";
2
+
3
+ // export default function ({}: PageProps) {
4
+ export default function () {
5
+ return (
6
+ <div>
7
+ <h2>Home</h2>
8
+
9
+ <p>
10
+ Visit <a href="https://houdinigraphql.com/">Houdini Graphql</a> to read the doc.
11
+ </p>
12
+ </div>
13
+ )
14
+ }
@@ -0,0 +1,8 @@
1
+ import react from '@vitejs/plugin-react'
2
+ import houdini from 'houdini/vite'
3
+ import { defineConfig } from 'vite'
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig({
7
+ plugins: [houdini(), react({ fastRefresh: false })],
8
+ })
@@ -0,0 +1,8 @@
1
+ projects:
2
+ default:
3
+ schema:
4
+ - ./$houdini/graphql/schema.graphql
5
+ documents:
6
+ - '**/*.gql'
7
+ - '**/*.tsx'
8
+ - ./$houdini/graphql/documents.gql
@@ -0,0 +1,3 @@
1
+ {
2
+ "label": "React w/ TypeScript"
3
+ }
@@ -0,0 +1,9 @@
1
+ /// <references types="houdini-react">
2
+ /** @type {import('houdini').ConfigFile} */
3
+ const config = {'CONFIG_FILE'
4
+ plugins: {
5
+ 'houdini-react': {},
6
+ },
7
+ }
8
+
9
+ export default config