@tollwyn/baukasten 0.0.2 → 1.2.3

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 (213) hide show
  1. package/README.md +254 -1
  2. package/dist/bin/check.d.ts +2 -0
  3. package/dist/bin/check.js +38 -0
  4. package/dist/bin/migrate.d.ts +2 -0
  5. package/dist/bin/migrate.js +111 -0
  6. package/dist/bin/stubs.d.ts +2 -0
  7. package/dist/bin/stubs.js +38 -0
  8. package/dist/bin/test-accounts.d.ts +2 -0
  9. package/dist/bin/test-accounts.js +62 -0
  10. package/dist/check.d.ts +20 -0
  11. package/dist/check.js +285 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.js +9 -0
  14. package/dist/product.d.ts +161 -0
  15. package/dist/product.js +152 -0
  16. package/dist/server/accounts.d.ts +128 -0
  17. package/dist/server/accounts.js +282 -0
  18. package/dist/server/adapter/db.d.ts +5 -0
  19. package/dist/server/adapter/db.js +63 -0
  20. package/dist/server/adapter/log.d.ts +3 -0
  21. package/dist/server/adapter/log.js +12 -0
  22. package/dist/server/adapter/mailer.d.ts +4 -0
  23. package/dist/server/adapter/mailer.js +56 -0
  24. package/dist/server/adapter/migrate.d.ts +21 -0
  25. package/dist/server/adapter/migrate.js +113 -0
  26. package/dist/server/adapter/secrets.d.ts +2 -0
  27. package/dist/server/adapter/secrets.js +40 -0
  28. package/dist/server/adapter/stripe.d.ts +11 -0
  29. package/dist/server/adapter/stripe.js +333 -0
  30. package/dist/server/api.d.ts +25 -0
  31. package/dist/server/api.js +98 -0
  32. package/dist/server/baukasten-migrations.d.ts +4 -0
  33. package/dist/server/baukasten-migrations.js +126 -0
  34. package/dist/server/billing.d.ts +94 -0
  35. package/dist/server/billing.js +349 -0
  36. package/dist/server/catalog.d.ts +39 -0
  37. package/dist/server/catalog.js +193 -0
  38. package/dist/server/config.d.ts +108 -0
  39. package/dist/server/config.js +244 -0
  40. package/dist/server/endpoints.d.ts +44 -0
  41. package/dist/server/endpoints.js +159 -0
  42. package/dist/server/fields.d.ts +7 -0
  43. package/dist/server/fields.js +18 -0
  44. package/dist/server/headers.d.ts +1 -0
  45. package/dist/server/headers.js +9 -0
  46. package/dist/server/health.d.ts +34 -0
  47. package/dist/server/health.js +116 -0
  48. package/dist/server/hook.d.ts +92 -0
  49. package/dist/server/hook.js +293 -0
  50. package/dist/server/index.d.ts +18 -0
  51. package/dist/server/index.js +11 -0
  52. package/dist/server/legal/de.d.ts +2 -0
  53. package/dist/server/legal/de.js +251 -0
  54. package/dist/server/legal/document.d.ts +37 -0
  55. package/dist/server/legal/document.js +30 -0
  56. package/dist/server/legal/en.d.ts +2 -0
  57. package/dist/server/legal/en.js +251 -0
  58. package/dist/server/legal/fleet.d.ts +114 -0
  59. package/dist/server/legal/fleet.js +89 -0
  60. package/dist/server/legal/index.d.ts +3 -0
  61. package/dist/server/legal/index.js +11 -0
  62. package/dist/server/limits.d.ts +58 -0
  63. package/dist/server/limits.js +76 -0
  64. package/dist/server/log.d.ts +26 -0
  65. package/dist/server/log.js +61 -0
  66. package/dist/server/machine.d.ts +4 -0
  67. package/dist/server/machine.js +57 -0
  68. package/dist/server/mail.d.ts +20 -0
  69. package/dist/server/mail.js +29 -0
  70. package/dist/server/messages.d.ts +105 -0
  71. package/dist/server/messages.js +232 -0
  72. package/dist/server/migrations.d.ts +28 -0
  73. package/dist/server/migrations.js +59 -0
  74. package/dist/server/openapi.d.ts +24 -0
  75. package/dist/server/openapi.js +173 -0
  76. package/dist/server/pages.d.ts +158 -0
  77. package/dist/server/pages.js +491 -0
  78. package/dist/server/ports.d.ts +273 -0
  79. package/dist/server/ports.js +1 -0
  80. package/dist/server/runs.d.ts +30 -0
  81. package/dist/server/runs.js +55 -0
  82. package/dist/server/sales.d.ts +38 -0
  83. package/dist/server/sales.js +132 -0
  84. package/dist/server/services.d.ts +57 -0
  85. package/dist/server/services.js +44 -0
  86. package/dist/server/start.d.ts +14 -0
  87. package/dist/server/start.js +65 -0
  88. package/dist/server/stripe.d.ts +33 -0
  89. package/dist/server/stripe.js +29 -0
  90. package/dist/server/terms.d.ts +7 -0
  91. package/dist/server/terms.js +9 -0
  92. package/dist/server/test-accounts.d.ts +28 -0
  93. package/dist/server/test-accounts.js +62 -0
  94. package/dist/server/via.d.ts +9 -0
  95. package/dist/server/via.js +15 -0
  96. package/dist/server/web.d.ts +48 -0
  97. package/dist/server/web.js +105 -0
  98. package/dist/shopify/adapter/shopify.d.ts +8 -0
  99. package/dist/shopify/adapter/shopify.js +232 -0
  100. package/dist/shopify/admin.d.ts +33 -0
  101. package/dist/shopify/admin.js +47 -0
  102. package/dist/shopify/app.d.ts +60 -0
  103. package/dist/shopify/app.js +67 -0
  104. package/dist/shopify/config.d.ts +53 -0
  105. package/dist/shopify/config.js +133 -0
  106. package/dist/shopify/constants.d.ts +27 -0
  107. package/dist/shopify/constants.js +27 -0
  108. package/dist/shopify/headers.d.ts +1 -0
  109. package/dist/shopify/headers.js +8 -0
  110. package/dist/shopify/hmac.d.ts +1 -0
  111. package/dist/shopify/hmac.js +9 -0
  112. package/dist/shopify/hook.d.ts +60 -0
  113. package/dist/shopify/hook.js +508 -0
  114. package/dist/shopify/id-token.d.ts +12 -0
  115. package/dist/shopify/id-token.js +66 -0
  116. package/dist/shopify/index.d.ts +19 -0
  117. package/dist/shopify/index.js +15 -0
  118. package/dist/shopify/legal/de.d.ts +3 -0
  119. package/dist/shopify/legal/de.js +123 -0
  120. package/dist/shopify/legal/en.d.ts +3 -0
  121. package/dist/shopify/legal/en.js +121 -0
  122. package/dist/shopify/legal/index.d.ts +19 -0
  123. package/dist/shopify/legal/index.js +8 -0
  124. package/dist/shopify/legal/shopify.d.ts +16 -0
  125. package/dist/shopify/legal/shopify.js +21 -0
  126. package/dist/shopify/migrations.d.ts +4 -0
  127. package/dist/shopify/migrations.js +67 -0
  128. package/dist/shopify/pages.d.ts +45 -0
  129. package/dist/shopify/pages.js +146 -0
  130. package/dist/shopify/ports.d.ts +77 -0
  131. package/dist/shopify/ports.js +1 -0
  132. package/dist/shopify/sales.d.ts +4 -0
  133. package/dist/shopify/sales.js +27 -0
  134. package/dist/shopify/scopes.d.ts +2 -0
  135. package/dist/shopify/scopes.js +13 -0
  136. package/dist/shopify/seal.d.ts +12 -0
  137. package/dist/shopify/seal.js +60 -0
  138. package/dist/shopify/shop.d.ts +4 -0
  139. package/dist/shopify/shop.js +15 -0
  140. package/dist/shopify/shops.d.ts +76 -0
  141. package/dist/shopify/shops.js +256 -0
  142. package/dist/shopify/sveltekit.d.ts +19 -0
  143. package/dist/shopify/sveltekit.js +18 -0
  144. package/dist/shopify/texts.d.ts +28 -0
  145. package/dist/shopify/texts.js +49 -0
  146. package/dist/shopify/toml.d.ts +22 -0
  147. package/dist/shopify/toml.js +125 -0
  148. package/dist/shopify/webhooks.d.ts +65 -0
  149. package/dist/shopify/webhooks.js +220 -0
  150. package/dist/stubs.d.ts +15 -0
  151. package/dist/stubs.js +287 -0
  152. package/dist/sveltekit.d.ts +20 -0
  153. package/dist/sveltekit.js +18 -0
  154. package/dist/testing/app.d.ts +36 -0
  155. package/dist/testing/app.js +155 -0
  156. package/dist/testing/browser.d.ts +21 -0
  157. package/dist/testing/browser.js +21 -0
  158. package/dist/testing/connection.d.ts +7 -0
  159. package/dist/testing/connection.js +303 -0
  160. package/dist/testing/database.d.ts +21 -0
  161. package/dist/testing/database.js +54 -0
  162. package/dist/testing/env-example.d.ts +4 -0
  163. package/dist/testing/env-example.js +33 -0
  164. package/dist/testing/index.d.ts +9 -0
  165. package/dist/testing/index.js +9 -0
  166. package/dist/testing/shopify-connection.d.ts +8 -0
  167. package/dist/testing/shopify-connection.js +313 -0
  168. package/dist/testing/shopify.d.ts +71 -0
  169. package/dist/testing/shopify.js +278 -0
  170. package/dist/testing/smtp.d.ts +38 -0
  171. package/dist/testing/smtp.js +165 -0
  172. package/dist/testing/stripe.d.ts +128 -0
  173. package/dist/testing/stripe.js +511 -0
  174. package/dist/texts.d.ts +139 -0
  175. package/dist/texts.js +265 -0
  176. package/dist/ui/AccountPage.svelte +143 -0
  177. package/dist/ui/AccountPage.svelte.d.ts +10 -0
  178. package/dist/ui/ConfirmEmailPage.svelte +21 -0
  179. package/dist/ui/ConfirmEmailPage.svelte.d.ts +10 -0
  180. package/dist/ui/ErrorPage.svelte +14 -0
  181. package/dist/ui/ErrorPage.svelte.d.ts +3 -0
  182. package/dist/ui/Field.svelte +12 -0
  183. package/dist/ui/Field.svelte.d.ts +10 -0
  184. package/dist/ui/ForgotPasswordPage.svelte +32 -0
  185. package/dist/ui/ForgotPasswordPage.svelte.d.ts +7 -0
  186. package/dist/ui/Form.svelte +30 -0
  187. package/dist/ui/Form.svelte.d.ts +11 -0
  188. package/dist/ui/Frame.svelte +84 -0
  189. package/dist/ui/Frame.svelte.d.ts +12 -0
  190. package/dist/ui/LegalPage.svelte +38 -0
  191. package/dist/ui/LegalPage.svelte.d.ts +9 -0
  192. package/dist/ui/Linked.svelte +5 -0
  193. package/dist/ui/Linked.svelte.d.ts +8 -0
  194. package/dist/ui/LoginPage.svelte +41 -0
  195. package/dist/ui/LoginPage.svelte.d.ts +10 -0
  196. package/dist/ui/LogoutPage.svelte +19 -0
  197. package/dist/ui/LogoutPage.svelte.d.ts +9 -0
  198. package/dist/ui/ResetPasswordPage.svelte +30 -0
  199. package/dist/ui/ResetPasswordPage.svelte.d.ts +10 -0
  200. package/dist/ui/SignupPage.svelte +52 -0
  201. package/dist/ui/SignupPage.svelte.d.ts +10 -0
  202. package/dist/ui/VerifyEmailPage.svelte +50 -0
  203. package/dist/ui/VerifyEmailPage.svelte.d.ts +10 -0
  204. package/dist/ui/base.css +287 -0
  205. package/dist/ui/data.d.ts +167 -0
  206. package/dist/ui/data.js +1 -0
  207. package/dist/ui/frame.d.ts +17 -0
  208. package/dist/ui/frame.js +27 -0
  209. package/dist/ui/index.d.ts +15 -0
  210. package/dist/ui/index.js +15 -0
  211. package/dist/variables.d.ts +20 -0
  212. package/dist/variables.js +62 -0
  213. package/package.json +88 -4
package/dist/stubs.js ADDED
@@ -0,0 +1,287 @@
1
+ import { APP_BRIDGE_URL, COMPLIANCE_TOPICS, POLARIS_URL, SHOPIFY_API_VERSION, SHOPIFY_AUTH_PATH, SHOPIFY_WEBHOOK_PATH } from './shopify/constants.js';
2
+ /** The alias of src/lib in the product: written out in an import here, svelte-package would resolve it. */
3
+ const LIB = '$lib';
4
+ const PAGES = [
5
+ {
6
+ route: 'account',
7
+ pages: 'account',
8
+ server: ['load', 'actions'],
9
+ component: 'AccountPage',
10
+ props: ['data', 'form']
11
+ },
12
+ {
13
+ route: 'confirm-email',
14
+ pages: 'confirmEmail',
15
+ server: ['load', 'actions'],
16
+ component: 'ConfirmEmailPage',
17
+ props: ['data', 'form']
18
+ },
19
+ {
20
+ route: 'forgot-password',
21
+ pages: 'forgotPassword',
22
+ server: ['actions'],
23
+ component: 'ForgotPasswordPage',
24
+ props: ['form']
25
+ },
26
+ { route: 'imprint', pages: 'imprint', server: ['load'], component: 'LegalPage', props: ['data'] },
27
+ {
28
+ route: 'login',
29
+ pages: 'login',
30
+ server: ['load', 'actions'],
31
+ component: 'LoginPage',
32
+ props: ['data', 'form']
33
+ },
34
+ {
35
+ route: 'logout',
36
+ pages: 'logout',
37
+ server: ['load', 'actions'],
38
+ component: 'LogoutPage',
39
+ props: ['data']
40
+ },
41
+ { route: 'privacy', pages: 'privacy', server: ['load'], component: 'LegalPage', props: ['data'] },
42
+ {
43
+ route: 'reset-password',
44
+ pages: 'resetPassword',
45
+ server: ['load', 'actions'],
46
+ component: 'ResetPasswordPage',
47
+ props: ['data', 'form']
48
+ },
49
+ {
50
+ route: 'signup',
51
+ pages: 'signup',
52
+ server: ['load', 'actions'],
53
+ component: 'SignupPage',
54
+ props: ['data', 'form']
55
+ },
56
+ { route: 'terms', pages: 'terms', server: ['load'], component: 'LegalPage', props: ['data'] },
57
+ {
58
+ route: 'verify-email',
59
+ pages: 'verifyEmail',
60
+ server: ['load', 'actions'],
61
+ component: 'VerifyEmailPage',
62
+ props: ['data', 'form']
63
+ }
64
+ ];
65
+ function serverPart(page) {
66
+ return `import { kit } from '${LIB}/server/baukasten';
67
+
68
+ export const { ${page.server.join(', ')} } = kit.pages.${page.pages};
69
+ `;
70
+ }
71
+ function component(page) {
72
+ return `<script lang="ts">
73
+ import { ${page.component} } from '@tollwyn/baukasten/ui';
74
+ import type { PageProps } from './$types';
75
+
76
+ let { ${page.props.join(', ')} }: PageProps = $props();
77
+ </script>
78
+
79
+ <${page.component} ${page.props.map((prop) => `{${prop}}`).join(' ')} />
80
+ `;
81
+ }
82
+ /** The stubs by their path in the product, with exactly their content. */
83
+ export const STUBS = {
84
+ 'server.js': `import { start } from '@tollwyn/baukasten/server';
85
+
86
+ await start(() => import('./build/handler.js'));
87
+ `,
88
+ 'src/app.html': `<!doctype html>
89
+ <html lang="%baukasten.lang%" %baukasten.theme%>
90
+ <head>
91
+ <meta charset="utf-8" />
92
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
93
+ %sveltekit.head%
94
+ </head>
95
+ <body>
96
+ <div style="display: contents">%sveltekit.body%</div>
97
+ </body>
98
+ </html>
99
+ `,
100
+ 'src/hooks.server.ts': `// The hooks of a product: the baukasten first, then the product's own handle.
101
+ import { building } from '$app/environment';
102
+ import { sequence } from '@sveltejs/kit/hooks';
103
+ import { kit } from '${LIB}/server/baukasten';
104
+ import { handle as own } from '${LIB}/server/handle';
105
+
106
+ export const init = kit.init(building);
107
+ export const handle = sequence(kit.handle, own);
108
+ export const handleError = kit.handleError;
109
+ `,
110
+ 'src/routes/+layout.server.ts': `import { kit } from '${LIB}/server/baukasten';
111
+
112
+ export const { load } = kit.pages.layout;
113
+ `,
114
+ 'src/routes/+layout.svelte': `<script lang="ts">
115
+ import { Frame } from '@tollwyn/baukasten/ui';
116
+ import '../app.css';
117
+ import type { LayoutProps } from './$types';
118
+
119
+ let { data, children }: LayoutProps = $props();
120
+ </script>
121
+
122
+ <Frame {data}>{@render children()}</Frame>
123
+ `,
124
+ 'src/routes/+error.svelte': `<script lang="ts">
125
+ import { ErrorPage } from '@tollwyn/baukasten/ui';
126
+ </script>
127
+
128
+ <ErrorPage />
129
+ `,
130
+ ...Object.fromEntries(PAGES.flatMap((page) => [
131
+ [`src/routes/${page.route}/+page.server.ts`, serverPart(page)],
132
+ [`src/routes/${page.route}/+page.svelte`, component(page)]
133
+ ])),
134
+ 'e2e/baukasten.spec.ts': `// The connection test of the baukasten, in the gate of every product.
135
+ import { expect, test } from '@playwright/test';
136
+ import { connectionTest } from '@tollwyn/baukasten/testing';
137
+ import product from '../baukasten.config';
138
+
139
+ connectionTest(product, { test, expect });
140
+ `
141
+ };
142
+ /**
143
+ * The files the product owns, by their path: the command writes each only where it is missing. The
144
+ * configuration fails loudly until it is filled in.
145
+ */
146
+ export const STARTING_POINTS = {
147
+ 'baukasten.config.ts': `// The configuration of the product, at the fixed place of every product (see the README of
148
+ // @tollwyn/baukasten): what the pages, mails and legal texts of the baukasten say of it.
149
+ import { defineProduct } from '@tollwyn/baukasten';
150
+
151
+ export default defineProduct({
152
+ language: 'en',
153
+ env: {},
154
+ navigation: [],
155
+ accounts: { home: '/', keyPrefix: '', signup: { heading: '', lead: '' } },
156
+ termsDate: '',
157
+ legal: {
158
+ service: '',
159
+ notGuaranteed: '',
160
+ use: '',
161
+ data: [],
162
+ recipients: [],
163
+ processor: { kind: 'no' }
164
+ },
165
+ catalog: { currency: 'CHF', products: [] }
166
+ });
167
+ `,
168
+ 'src/app.css': `/*
169
+ * The product's own styles, after the base styles of the baukasten and on every page, those of the
170
+ * baukasten too. Its colours go through the variables of the baukasten, like --bk-accent.
171
+ */
172
+ `,
173
+ 'src/lib/server/baukasten.ts': `// The baukasten in the product, with the hooks it calls in the product (Hooks of
174
+ // @tollwyn/baukasten/server): checks, start and stop, deleting an account, plans from a
175
+ // subscription, a paid purchase, numbers for the sales.
176
+ import { baukasten } from '@tollwyn/baukasten/server';
177
+ import product from '../../../baukasten.config';
178
+
179
+ export const kit = baukasten(product);
180
+ `,
181
+ 'src/lib/server/handle.ts': `// The product's own handle, after that of the baukasten (src/hooks.server.ts).
182
+ import type { Handle } from '@sveltejs/kit';
183
+
184
+ export const handle: Handle = ({ event, resolve }) => resolve(event);
185
+ `
186
+ };
187
+ /** The stubs of a Shopify app, by their path, with exactly their content. */
188
+ export const SHOPIFY_STUBS = {
189
+ 'server.js': `import { start } from '@tollwyn/baukasten/shopify';
190
+
191
+ await start(() => import('./build/handler.js'));
192
+ `,
193
+ // App Bridge comes first, from the CDN of Shopify, as the App Store wants; the handle fills in the
194
+ // client ID.
195
+ 'src/app.html': `<!doctype html>
196
+ <html lang="%baukasten.lang%">
197
+ <head>
198
+ <meta charset="utf-8" />
199
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
200
+ <meta name="shopify-api-key" content="%baukasten.clientId%" />
201
+ <script src="${APP_BRIDGE_URL}"></script>
202
+ <script src="${POLARIS_URL}"></script>
203
+ %sveltekit.head%
204
+ </head>
205
+ <body>
206
+ <div style="display: contents">%sveltekit.body%</div>
207
+ </body>
208
+ </html>
209
+ `,
210
+ 'src/hooks.server.ts': `// The hooks of a Shopify app: the baukasten first, then the app's own handle.
211
+ import { building } from '$app/environment';
212
+ import { sequence } from '@sveltejs/kit/hooks';
213
+ import { kit } from '${LIB}/server/baukasten';
214
+ import { handle as own } from '${LIB}/server/handle';
215
+
216
+ export const init = kit.init(building);
217
+ export const handle = sequence(kit.handle, own);
218
+ export const handleError = kit.handleError;
219
+ `,
220
+ 'e2e/baukasten.spec.ts': `// The connection test of the baukasten, in the gate of every Shopify app.
221
+ import { expect, test } from '@playwright/test';
222
+ import { shopifyConnectionTest } from '@tollwyn/baukasten/testing';
223
+ import app from '../baukasten.config';
224
+
225
+ shopifyConnectionTest(app, { test, expect });
226
+ `
227
+ };
228
+ /**
229
+ * The files a Shopify app owns, by their path, written only where they are missing. The
230
+ * configuration fails loudly until it is filled in; shopify.app.toml waits for the subdomain of the
231
+ * app, its name, handle and scopes, and for the client ID, which linking the app gives it.
232
+ */
233
+ export const SHOPIFY_STARTING_POINTS = {
234
+ 'baukasten.config.ts': `// The configuration of the Shopify app, at the fixed place of every product (see the README of
235
+ // @tollwyn/baukasten): its plans and what its privacy text says of it.
236
+ import { defineShopifyApp } from '@tollwyn/baukasten';
237
+
238
+ export default defineShopifyApp({
239
+ language: 'en',
240
+ env: {},
241
+ plans: [],
242
+ privacyDate: '',
243
+ privacy: { service: '', data: [], recipients: [] }
244
+ });
245
+ `,
246
+ 'shopify.app.toml': `# The configuration of the app at Shopify, which the Shopify CLI deploys; the client ID comes when
247
+ # the app is linked. What baukasten-check holds in it: see the README of the baukasten.
248
+ client_id = ""
249
+ name = ""
250
+ handle = ""
251
+ application_url = "https://SUBDOMAIN.tollwyn.com"
252
+ embedded = true
253
+
254
+ [access_scopes]
255
+ scopes = ""
256
+
257
+ [auth]
258
+ redirect_urls = ["https://SUBDOMAIN.tollwyn.com${SHOPIFY_AUTH_PATH}"]
259
+
260
+ [webhooks]
261
+ api_version = "${SHOPIFY_API_VERSION}"
262
+
263
+ [[webhooks.subscriptions]]
264
+ topics = ["app/uninstalled"]
265
+ uri = "${SHOPIFY_WEBHOOK_PATH}"
266
+
267
+ [[webhooks.subscriptions]]
268
+ compliance_topics = [${COMPLIANCE_TOPICS.map((topic) => `"${topic}"`).join(', ')}]
269
+ uri = "${SHOPIFY_WEBHOOK_PATH}"
270
+
271
+ [build]
272
+ automatically_update_urls_on_dev = false
273
+ `,
274
+ 'src/lib/server/baukasten.ts': `// The baukasten in the Shopify app, with the hooks it calls in the app (ShopifyHooks of
275
+ // @tollwyn/baukasten/shopify): checks, start and stop, its webhooks, and what it keeps of a
276
+ // customer of a shop.
277
+ import { shopifyBaukasten } from '@tollwyn/baukasten/shopify';
278
+ import app from '../../../baukasten.config';
279
+
280
+ export const kit = shopifyBaukasten(app);
281
+ `,
282
+ 'src/lib/server/handle.ts': `// The app's own handle, after that of the baukasten (src/hooks.server.ts).
283
+ import type { Handle } from '@sveltejs/kit';
284
+
285
+ export const handle: Handle = ({ event, resolve }) => resolve(event);
286
+ `
287
+ };
@@ -0,0 +1,20 @@
1
+ export declare const SVELTEKIT_OPTIONS: {
2
+ csrf: {
3
+ trustedOrigins: string[];
4
+ };
5
+ csp: {
6
+ mode: "auto";
7
+ directives: {
8
+ 'default-src': "self"[];
9
+ 'script-src': "self"[];
10
+ 'style-src': ("self" | "unsafe-inline")[];
11
+ 'img-src': ("data:" | "self")[];
12
+ 'font-src': ("data:" | "self")[];
13
+ 'connect-src': "self"[];
14
+ 'form-action': ("self" | "https://checkout.stripe.com" | "https://billing.stripe.com")[];
15
+ 'frame-ancestors': "none"[];
16
+ 'base-uri': "self"[];
17
+ 'object-src': "none"[];
18
+ };
19
+ };
20
+ };
@@ -0,0 +1,18 @@
1
+ export const SVELTEKIT_OPTIONS = {
2
+ csrf: { trustedOrigins: ['*'] },
3
+ csp: {
4
+ mode: 'auto',
5
+ directives: {
6
+ 'default-src': ['self'],
7
+ 'script-src': ['self'],
8
+ 'style-src': ['self', 'unsafe-inline'],
9
+ 'img-src': ['self', 'data:'],
10
+ 'font-src': ['self', 'data:'],
11
+ 'connect-src': ['self'],
12
+ 'form-action': ['self', 'https://checkout.stripe.com', 'https://billing.stripe.com'],
13
+ 'frame-ancestors': ['none'],
14
+ 'base-uri': ['self'],
15
+ 'object-src': ['none']
16
+ }
17
+ }
18
+ };
@@ -0,0 +1,36 @@
1
+ import type { SmtpDouble } from './smtp.js';
2
+ import type { StripeDouble } from './stripe.js';
3
+ export type LogLine = Record<string, unknown>;
4
+ export interface AppProcess {
5
+ logs: LogLine[];
6
+ stderr(): string;
7
+ exited: Promise<number | null>;
8
+ /** The first line that matches, as soon as it is written; fails when the process ends first. */
9
+ waitForLog(matches: (line: LogLine) => boolean, timeoutMs?: number): Promise<LogLine>;
10
+ /** Sends SIGTERM and resolves with the exit code. */
11
+ stop(): Promise<number | null>;
12
+ }
13
+ export interface RunningApp extends AppProcess {
14
+ /** Where it listens; PUBLIC_ORIGIN unless the test set another. */
15
+ url: string;
16
+ }
17
+ export declare function freePort(): Promise<number>;
18
+ /** Runs a script of the product with exactly `env` and PATH, as the delivery service would. */
19
+ export declare function spawnNode(args: string[], env: Record<string, string>, options?: {
20
+ cwd?: string;
21
+ }): AppProcess;
22
+ /**
23
+ * Starts the built product on a free port and waits until it listens. PUBLIC_ORIGIN points at it,
24
+ * and with `smtp` its mail goes to the SMTP double, unless `env` says otherwise. With `stripe` it
25
+ * talks to the Stripe double, which sends the events of its webhook to the app, as the webhook the
26
+ * delivery service sets up; the key is the one of `env`, or one of the test mode. freePort releases
27
+ * the port before the app takes it, so a test running alongside can take it in between: then the
28
+ * app ends with EADDRINUSE and starts again on another port. A port the test set itself stays.
29
+ */
30
+ export declare function startApp(options: {
31
+ env: Record<string, string>;
32
+ smtp?: SmtpDouble;
33
+ stripe?: StripeDouble;
34
+ /** The root of the product; the working directory without it. */
35
+ root?: string;
36
+ }): Promise<RunningApp>;
@@ -0,0 +1,155 @@
1
+ // Runs the built product as its image does, with exactly the variables given. Every line on stdout
2
+ // must be JSON and stderr must stay empty; the tests wait for lines of the log, not for time.
3
+ import { spawn } from 'node:child_process';
4
+ import { EventEmitter } from 'node:events';
5
+ import { createServer } from 'node:net';
6
+ import { WEBHOOK_PATH } from '../server/stripe.js';
7
+ export function freePort() {
8
+ return new Promise((resolve, reject) => {
9
+ const server = createServer();
10
+ server.once('error', reject);
11
+ server.listen(0, '127.0.0.1', () => {
12
+ const address = server.address();
13
+ server.close(() => {
14
+ if (address === null || typeof address === 'string')
15
+ reject(new Error('no port'));
16
+ else
17
+ resolve(address.port);
18
+ });
19
+ });
20
+ });
21
+ }
22
+ /** Runs a script of the product with exactly `env` and PATH, as the delivery service would. */
23
+ export function spawnNode(args, env, options = {}) {
24
+ const child = spawn(process.execPath, args, {
25
+ cwd: options.cwd,
26
+ env: { PATH: process.env.PATH ?? '', ...env },
27
+ stdio: ['ignore', 'pipe', 'pipe']
28
+ });
29
+ const logs = [];
30
+ const events = new EventEmitter();
31
+ let stderr = '';
32
+ let buffer = '';
33
+ let ended = false;
34
+ child.stderr.on('data', (chunk) => {
35
+ stderr += chunk.toString();
36
+ });
37
+ child.stdout.on('data', (chunk) => {
38
+ buffer += chunk.toString();
39
+ let end;
40
+ while ((end = buffer.indexOf('\n')) >= 0) {
41
+ const text = buffer.slice(0, end);
42
+ buffer = buffer.slice(end + 1);
43
+ let line;
44
+ try {
45
+ line = asLine(JSON.parse(text));
46
+ }
47
+ catch {
48
+ line = { notJson: text };
49
+ }
50
+ logs.push(line);
51
+ events.emit('line', line);
52
+ }
53
+ });
54
+ const exited = new Promise((resolve) => child.once('exit', (code) => {
55
+ ended = true;
56
+ events.emit('exit');
57
+ resolve(code);
58
+ }));
59
+ function waitForLog(matches, timeoutMs = 20_000) {
60
+ return new Promise((resolve, reject) => {
61
+ const done = () => {
62
+ clearTimeout(timer);
63
+ events.off('line', onLine);
64
+ events.off('exit', onExit);
65
+ };
66
+ const onLine = (line) => {
67
+ if (!matches(line))
68
+ return;
69
+ done();
70
+ resolve(line);
71
+ };
72
+ const onExit = () => {
73
+ done();
74
+ reject(new Error(`the app ended first: ${JSON.stringify(logs)}`));
75
+ };
76
+ const timer = setTimeout(() => {
77
+ done();
78
+ reject(new Error(`no such line in ${timeoutMs} ms: ${JSON.stringify(logs)}`));
79
+ }, timeoutMs);
80
+ const found = logs.find(matches);
81
+ if (found !== undefined) {
82
+ done();
83
+ resolve(found);
84
+ }
85
+ else if (ended)
86
+ onExit();
87
+ else {
88
+ events.on('line', onLine);
89
+ events.on('exit', onExit);
90
+ }
91
+ });
92
+ }
93
+ return {
94
+ logs,
95
+ stderr: () => stderr,
96
+ exited,
97
+ waitForLog,
98
+ stop: () => {
99
+ child.kill('SIGTERM');
100
+ return exited;
101
+ }
102
+ };
103
+ }
104
+ /** A log line is a JSON object; anything else is kept as text. */
105
+ function asLine(value) {
106
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
107
+ throw new Error('not an object');
108
+ }
109
+ return Object.fromEntries(Object.entries(value));
110
+ }
111
+ /** How often startApp picks another port when the one it picked was taken before the app listened. */
112
+ const PORT_TRIES = 5;
113
+ /** Whether the app ended because its port was taken, as the log of its uncaught error says. */
114
+ function portTaken(app) {
115
+ return app.logs.some(({ error }) => typeof error === 'object' && error !== null && 'code' in error && error.code === 'EADDRINUSE');
116
+ }
117
+ /**
118
+ * Starts the built product on a free port and waits until it listens. PUBLIC_ORIGIN points at it,
119
+ * and with `smtp` its mail goes to the SMTP double, unless `env` says otherwise. With `stripe` it
120
+ * talks to the Stripe double, which sends the events of its webhook to the app, as the webhook the
121
+ * delivery service sets up; the key is the one of `env`, or one of the test mode. freePort releases
122
+ * the port before the app takes it, so a test running alongside can take it in between: then the
123
+ * app ends with EADDRINUSE and starts again on another port. A port the test set itself stays.
124
+ */
125
+ export async function startApp(options) {
126
+ const { smtp, stripe } = options;
127
+ for (let attempt = 1;; attempt++) {
128
+ const port = await freePort();
129
+ const url = `http://127.0.0.1:${port}`;
130
+ const app = spawnNode(['server.js'], {
131
+ LOG_LEVEL: 'info',
132
+ PORT: String(port),
133
+ PUBLIC_ORIGIN: url,
134
+ ...(smtp === undefined ? {} : { SMTP_HOST: smtp.host, SMTP_PORT: String(smtp.port) }),
135
+ ...(stripe === undefined ? {} : { STRIPE_SECRET_KEY: 'rk_test_double' }),
136
+ ...options.env,
137
+ // The double signs with its own secret, and only it takes the key.
138
+ ...(stripe === undefined
139
+ ? {}
140
+ : { STRIPE_API_BASE: stripe.url, STRIPE_WEBHOOK_SECRET: stripe.webhookSecret })
141
+ }, { cwd: options.root });
142
+ try {
143
+ await app.waitForLog((line) => line.msg === 'listening');
144
+ // The endpoint is the address of the product; the events go where it listens.
145
+ const origin = options.env.PUBLIC_ORIGIN ?? url;
146
+ stripe?.connect(`${origin}${WEBHOOK_PATH}`, `${url}${WEBHOOK_PATH}`);
147
+ return { ...app, url };
148
+ }
149
+ catch (error) {
150
+ const again = attempt < PORT_TRIES && !('PORT' in options.env) && portTaken(app);
151
+ if (!again)
152
+ throw error;
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,21 @@
1
+ /** What the frame sets once the page is hydrated. */
2
+ export declare const HYDRATED = "html[data-hydrated]";
3
+ /** What these helpers need of a page of Playwright. */
4
+ export interface PageLike {
5
+ goto(url: string): Promise<unknown>;
6
+ waitForSelector(selector: string, options: {
7
+ state: 'attached';
8
+ }): Promise<unknown>;
9
+ }
10
+ /** Waits until the page is interactive: filled before, a field can lose what was typed. */
11
+ export declare function waitForHydration(page: Pick<PageLike, 'waitForSelector'>): Promise<void>;
12
+ export declare function open(page: PageLike, url: string): Promise<void>;
13
+ /**
14
+ * A client address no other caller of this process gets, so that the limits of one test do not
15
+ * reach into another: from 198.18.0.0/15, which is meant for tests, with the headers that carry it:
16
+ * CF-Connecting-IP in production, X-Forwarded-For locally.
17
+ */
18
+ export declare function newCaller(): {
19
+ address: string;
20
+ headers: Record<string, string>;
21
+ };
@@ -0,0 +1,21 @@
1
+ /** What the frame sets once the page is hydrated. */
2
+ export const HYDRATED = 'html[data-hydrated]';
3
+ /** Waits until the page is interactive: filled before, a field can lose what was typed. */
4
+ export async function waitForHydration(page) {
5
+ await page.waitForSelector(HYDRATED, { state: 'attached' });
6
+ }
7
+ export async function open(page, url) {
8
+ await page.goto(url);
9
+ await waitForHydration(page);
10
+ }
11
+ let callers = 0;
12
+ /**
13
+ * A client address no other caller of this process gets, so that the limits of one test do not
14
+ * reach into another: from 198.18.0.0/15, which is meant for tests, with the headers that carry it:
15
+ * CF-Connecting-IP in production, X-Forwarded-For locally.
16
+ */
17
+ export function newCaller() {
18
+ const n = callers++;
19
+ const address = `198.${18 + (Math.floor(n / 65_536) % 2)}.${Math.floor(n / 256) % 256}.${n % 256}`;
20
+ return { address, headers: { 'CF-Connecting-IP': address, 'X-Forwarded-For': address } };
21
+ }
@@ -0,0 +1,7 @@
1
+ import type { expect as Expect, test as Test } from '@playwright/test';
2
+ import type { Product, ProductEnv } from '../product.js';
3
+ /** Registers the connection test of `product` with the test and expect of the product's Playwright. */
4
+ export declare function connectionTest<E extends ProductEnv>(product: Product<E>, playwright: {
5
+ test: typeof Test;
6
+ expect: typeof Expect;
7
+ }): void;