stellar-drive 1.0.21 → 1.1.1
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.
- package/README.md +9 -5
- package/dist/auth/deviceVerification.d.ts.map +1 -1
- package/dist/auth/deviceVerification.js +34 -8
- package/dist/auth/deviceVerification.js.map +1 -1
- package/dist/auth/singleUser.d.ts +4 -7
- package/dist/auth/singleUser.d.ts.map +1 -1
- package/dist/auth/singleUser.js +75 -11
- package/dist/auth/singleUser.js.map +1 -1
- package/dist/bin/install-pwa.d.ts.map +1 -1
- package/dist/bin/install-pwa.js +627 -42
- package/dist/bin/install-pwa.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -1
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/install-pwa.js
CHANGED
|
@@ -206,7 +206,7 @@ function generatePackageJson(opts) {
|
|
|
206
206
|
},
|
|
207
207
|
dependencies: {
|
|
208
208
|
postgres: '^3.4.0',
|
|
209
|
-
'stellar-drive': '^1.
|
|
209
|
+
'stellar-drive': '^1.1.1'
|
|
210
210
|
},
|
|
211
211
|
type: 'module'
|
|
212
212
|
}, null, 2) + '\n');
|
|
@@ -1104,20 +1104,161 @@ function generateSplashSvg(label) {
|
|
|
1104
1104
|
`;
|
|
1105
1105
|
}
|
|
1106
1106
|
// ---------------------------------------------------------------------------
|
|
1107
|
-
//
|
|
1107
|
+
// EMAIL TEMPLATE GENERATORS
|
|
1108
1108
|
// ---------------------------------------------------------------------------
|
|
1109
1109
|
/**
|
|
1110
|
-
* Generate
|
|
1110
|
+
* Generate the signup confirmation email HTML template.
|
|
1111
1111
|
*
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1112
|
+
* Uses `{{ .Data.app_name }}` and `{{ .Data.app_domain }}` (from Supabase
|
|
1113
|
+
* user_metadata) so the template works without any Supabase dashboard
|
|
1114
|
+
* configuration — important for self-hosted deployments.
|
|
1115
|
+
*
|
|
1116
|
+
* @returns The HTML source for the signup confirmation email.
|
|
1114
1117
|
*/
|
|
1115
|
-
function
|
|
1116
|
-
return
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
<
|
|
1120
|
-
<
|
|
1118
|
+
function generateSignupEmail() {
|
|
1119
|
+
return `<!DOCTYPE html>
|
|
1120
|
+
<html lang="en">
|
|
1121
|
+
<head>
|
|
1122
|
+
<meta charset="UTF-8">
|
|
1123
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1124
|
+
<title>Confirm Your Email</title>
|
|
1125
|
+
</head>
|
|
1126
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; background-color: #f4f4f5; color: #1a1a2e;">
|
|
1127
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f5;">
|
|
1128
|
+
<tr>
|
|
1129
|
+
<td align="center" style="padding: 40px 20px;">
|
|
1130
|
+
<table role="presentation" width="520" cellpadding="0" cellspacing="0" border="0" style="max-width: 520px; width: 100%;">
|
|
1131
|
+
<tr>
|
|
1132
|
+
<td align="center" style="padding-bottom: 24px; font-size: 24px; font-weight: 700; color: #1a1a2e;">
|
|
1133
|
+
{{ .Data.app_name }}
|
|
1134
|
+
</td>
|
|
1135
|
+
</tr>
|
|
1136
|
+
<tr>
|
|
1137
|
+
<td style="background-color: #ffffff; border: 1px solid #e0e0e6; border-radius: 12px; padding: 40px;">
|
|
1138
|
+
<h1 style="margin: 0 0 16px 0; font-size: 22px; font-weight: 600; color: #1a1a2e; text-align: center;">Confirm Your Email</h1>
|
|
1139
|
+
<p style="margin: 0 0 24px 0; font-size: 15px; line-height: 1.6; color: #4a4a68; text-align: center;">Welcome to {{ .Data.app_name }}! Click the button below to verify your email address and get started.</p>
|
|
1140
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
1141
|
+
<tr>
|
|
1142
|
+
<td align="center" style="padding-bottom: 24px;">
|
|
1143
|
+
<a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=signup" target="_blank" style="display: inline-block; padding: 14px 36px; font-size: 15px; font-weight: 600; color: #ffffff; background-color: #3b82f6; text-decoration: none; border-radius: 8px;">Verify Email Address</a>
|
|
1144
|
+
</td>
|
|
1145
|
+
</tr>
|
|
1146
|
+
</table>
|
|
1147
|
+
<hr style="border: none; border-top: 1px solid #e0e0e6; margin: 0 0 16px 0;">
|
|
1148
|
+
<p style="margin: 0 0 8px 0; font-size: 12px; color: #8888a0; text-align: center;">Or copy and paste this link into your browser:</p>
|
|
1149
|
+
<p style="margin: 0; font-size: 11px; word-break: break-all; text-align: center;"><a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=signup" style="color: #3b82f6;">{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=signup</a></p>
|
|
1150
|
+
</td>
|
|
1151
|
+
</tr>
|
|
1152
|
+
<tr>
|
|
1153
|
+
<td style="padding-top: 24px; text-align: center;">
|
|
1154
|
+
<p style="margin: 0 0 6px 0; font-size: 12px; color: #8888a0;">This link will expire in 24 hours.</p>
|
|
1155
|
+
<p style="margin: 0 0 12px 0; font-size: 12px; color: #8888a0;">If you didn't create an account, you can safely ignore this email.</p>
|
|
1156
|
+
<p style="margin: 0; font-size: 11px; color: #aaaabc;">© {{ .Data.app_name }}</p>
|
|
1157
|
+
</td>
|
|
1158
|
+
</tr>
|
|
1159
|
+
</table>
|
|
1160
|
+
</td>
|
|
1161
|
+
</tr>
|
|
1162
|
+
</table>
|
|
1163
|
+
</body>
|
|
1164
|
+
</html>
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
function generateChangeEmail() {
|
|
1168
|
+
return `<!DOCTYPE html>
|
|
1169
|
+
<html lang="en">
|
|
1170
|
+
<head>
|
|
1171
|
+
<meta charset="UTF-8">
|
|
1172
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1173
|
+
<title>Confirm Your New Email</title>
|
|
1174
|
+
</head>
|
|
1175
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; background-color: #f4f4f5; color: #1a1a2e;">
|
|
1176
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f5;">
|
|
1177
|
+
<tr>
|
|
1178
|
+
<td align="center" style="padding: 40px 20px;">
|
|
1179
|
+
<table role="presentation" width="520" cellpadding="0" cellspacing="0" border="0" style="max-width: 520px; width: 100%;">
|
|
1180
|
+
<tr>
|
|
1181
|
+
<td align="center" style="padding-bottom: 24px; font-size: 24px; font-weight: 700; color: #1a1a2e;">
|
|
1182
|
+
{{ .Data.app_name }}
|
|
1183
|
+
</td>
|
|
1184
|
+
</tr>
|
|
1185
|
+
<tr>
|
|
1186
|
+
<td style="background-color: #ffffff; border: 1px solid #e0e0e6; border-radius: 12px; padding: 40px;">
|
|
1187
|
+
<h1 style="margin: 0 0 16px 0; font-size: 22px; font-weight: 600; color: #1a1a2e; text-align: center;">Confirm Your New Email</h1>
|
|
1188
|
+
<p style="margin: 0 0 24px 0; font-size: 15px; line-height: 1.6; color: #4a4a68; text-align: center;">You requested to change your email address. Click the button below to confirm this new email for your {{ .Data.app_name }} account.</p>
|
|
1189
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
1190
|
+
<tr>
|
|
1191
|
+
<td align="center" style="padding-bottom: 24px;">
|
|
1192
|
+
<a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email_change" target="_blank" style="display: inline-block; padding: 14px 36px; font-size: 15px; font-weight: 600; color: #ffffff; background-color: #3b82f6; text-decoration: none; border-radius: 8px;">Confirm New Email</a>
|
|
1193
|
+
</td>
|
|
1194
|
+
</tr>
|
|
1195
|
+
</table>
|
|
1196
|
+
<hr style="border: none; border-top: 1px solid #e0e0e6; margin: 0 0 16px 0;">
|
|
1197
|
+
<p style="margin: 0 0 8px 0; font-size: 12px; color: #8888a0; text-align: center;">Or copy and paste this link into your browser:</p>
|
|
1198
|
+
<p style="margin: 0; font-size: 11px; word-break: break-all; text-align: center;"><a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email_change" style="color: #3b82f6;">{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email_change</a></p>
|
|
1199
|
+
</td>
|
|
1200
|
+
</tr>
|
|
1201
|
+
<tr>
|
|
1202
|
+
<td style="padding-top: 24px; text-align: center;">
|
|
1203
|
+
<p style="margin: 0 0 6px 0; font-size: 12px; color: #8888a0;">This link will expire in 24 hours.</p>
|
|
1204
|
+
<p style="margin: 0 0 12px 0; font-size: 12px; color: #8888a0;">If you didn't request this change, you can safely ignore this email.</p>
|
|
1205
|
+
<p style="margin: 0; font-size: 11px; color: #aaaabc;">© {{ .Data.app_name }}</p>
|
|
1206
|
+
</td>
|
|
1207
|
+
</tr>
|
|
1208
|
+
</table>
|
|
1209
|
+
</td>
|
|
1210
|
+
</tr>
|
|
1211
|
+
</table>
|
|
1212
|
+
</body>
|
|
1213
|
+
</html>
|
|
1214
|
+
`;
|
|
1215
|
+
}
|
|
1216
|
+
function generateDeviceVerificationEmail() {
|
|
1217
|
+
return `<!DOCTYPE html>
|
|
1218
|
+
<html lang="en">
|
|
1219
|
+
<head>
|
|
1220
|
+
<meta charset="UTF-8">
|
|
1221
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1222
|
+
<title>Verify Your Device</title>
|
|
1223
|
+
</head>
|
|
1224
|
+
<body style="margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; background-color: #f4f4f5; color: #1a1a2e;">
|
|
1225
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f4f4f5;">
|
|
1226
|
+
<tr>
|
|
1227
|
+
<td align="center" style="padding: 40px 20px;">
|
|
1228
|
+
<table role="presentation" width="520" cellpadding="0" cellspacing="0" border="0" style="max-width: 520px; width: 100%;">
|
|
1229
|
+
<tr>
|
|
1230
|
+
<td align="center" style="padding-bottom: 24px; font-size: 24px; font-weight: 700; color: #1a1a2e;">
|
|
1231
|
+
{{ .Data.app_name }}
|
|
1232
|
+
</td>
|
|
1233
|
+
</tr>
|
|
1234
|
+
<tr>
|
|
1235
|
+
<td style="background-color: #ffffff; border: 1px solid #e0e0e6; border-radius: 12px; padding: 40px;">
|
|
1236
|
+
<h1 style="margin: 0 0 16px 0; font-size: 22px; font-weight: 600; color: #1a1a2e; text-align: center;">Verify Your Device</h1>
|
|
1237
|
+
<p style="margin: 0 0 24px 0; font-size: 15px; line-height: 1.6; color: #4a4a68; text-align: center;">A sign-in attempt was made from a new device. Click the button below to verify this device and grant it access to your {{ .Data.app_name }} account.</p>
|
|
1238
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
|
|
1239
|
+
<tr>
|
|
1240
|
+
<td align="center" style="padding-bottom: 24px;">
|
|
1241
|
+
<a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email" target="_blank" style="display: inline-block; padding: 14px 36px; font-size: 15px; font-weight: 600; color: #ffffff; background-color: #22c55e; text-decoration: none; border-radius: 8px;">Verify This Device</a>
|
|
1242
|
+
</td>
|
|
1243
|
+
</tr>
|
|
1244
|
+
</table>
|
|
1245
|
+
<hr style="border: none; border-top: 1px solid #e0e0e6; margin: 0 0 16px 0;">
|
|
1246
|
+
<p style="margin: 0 0 8px 0; font-size: 12px; color: #8888a0; text-align: center;">Or copy and paste this link into your browser:</p>
|
|
1247
|
+
<p style="margin: 0; font-size: 11px; word-break: break-all; text-align: center;"><a href="{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email" style="color: #3b82f6;">{{ .Data.app_domain }}/confirm?token_hash={{ .TokenHash }}&type=email</a></p>
|
|
1248
|
+
</td>
|
|
1249
|
+
</tr>
|
|
1250
|
+
<tr>
|
|
1251
|
+
<td style="padding-top: 24px; text-align: center;">
|
|
1252
|
+
<p style="margin: 0 0 6px 0; font-size: 12px; color: #8888a0;">This link will expire in 24 hours.</p>
|
|
1253
|
+
<p style="margin: 0 0 12px 0; font-size: 12px; color: #8888a0;">If you didn't attempt to sign in, someone may have your code. Consider changing it immediately.</p>
|
|
1254
|
+
<p style="margin: 0; font-size: 11px; color: #aaaabc;">© {{ .Data.app_name }}</p>
|
|
1255
|
+
</td>
|
|
1256
|
+
</tr>
|
|
1257
|
+
</table>
|
|
1258
|
+
</td>
|
|
1259
|
+
</tr>
|
|
1260
|
+
</table>
|
|
1261
|
+
</body>
|
|
1121
1262
|
</html>
|
|
1122
1263
|
`;
|
|
1123
1264
|
}
|
|
@@ -1375,16 +1516,18 @@ export const load: LayoutLoad = async ({ url }): Promise<RootLayoutData> => {
|
|
|
1375
1516
|
if (browser) {
|
|
1376
1517
|
const result = await resolveRootLayout();
|
|
1377
1518
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (!result.serverConfigured) {
|
|
1519
|
+
if (result.authMode === 'none') {
|
|
1520
|
+
if (!result.serverConfigured && !url.pathname.startsWith('/setup')) {
|
|
1381
1521
|
redirect(307, '/setup');
|
|
1382
|
-
} else {
|
|
1383
|
-
const
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1522
|
+
} else if (result.serverConfigured) {
|
|
1523
|
+
const isPublicRoute = PUBLIC_ROUTES.some(r => url.pathname.startsWith(r));
|
|
1524
|
+
if (!isPublicRoute) {
|
|
1525
|
+
const returnUrl = url.pathname + url.search;
|
|
1526
|
+
const loginUrl = returnUrl && returnUrl !== '/'
|
|
1527
|
+
? \`/login?redirect=\${encodeURIComponent(returnUrl)}\`
|
|
1528
|
+
: '/login';
|
|
1529
|
+
redirect(307, loginUrl);
|
|
1530
|
+
}
|
|
1388
1531
|
}
|
|
1389
1532
|
}
|
|
1390
1533
|
|
|
@@ -1833,13 +1976,13 @@ export const load: PageLoad = async () => {
|
|
|
1833
1976
|
`;
|
|
1834
1977
|
}
|
|
1835
1978
|
/**
|
|
1836
|
-
* Generate the setup wizard page component with
|
|
1979
|
+
* Generate the setup wizard page component with a full 4-step UI.
|
|
1837
1980
|
*
|
|
1838
1981
|
* @returns The Svelte component source for `src/routes/setup/+page.svelte`.
|
|
1839
1982
|
*/
|
|
1840
1983
|
function generateSetupPageSvelte(opts) {
|
|
1841
1984
|
return `<!--
|
|
1842
|
-
@fileoverview
|
|
1985
|
+
@fileoverview Four-step Supabase configuration wizard.
|
|
1843
1986
|
|
|
1844
1987
|
Guides the user through entering Supabase credentials, validating them
|
|
1845
1988
|
against the server, optionally deploying environment variables to Vercel,
|
|
@@ -1849,21 +1992,20 @@ function generateSetupPageSvelte(opts) {
|
|
|
1849
1992
|
/**
|
|
1850
1993
|
* @fileoverview Setup wizard page — first-time Supabase configuration.
|
|
1851
1994
|
*
|
|
1852
|
-
* Guides the user through a
|
|
1995
|
+
* Guides the user through a four-step process to connect their own
|
|
1853
1996
|
* Supabase backend to ${opts.name}:
|
|
1854
1997
|
*
|
|
1855
1998
|
* 1. Create a Supabase project (instructions only).
|
|
1856
|
-
* 2.
|
|
1857
|
-
* 3.
|
|
1858
|
-
* 4.
|
|
1859
|
-
* 5. Persist configuration via Vercel API (set env vars + redeploy).
|
|
1999
|
+
* 2. Initialize the database (automatic — informational step).
|
|
2000
|
+
* 3. Enter and validate Supabase credentials (URL + publishable key).
|
|
2001
|
+
* 4. Persist configuration via Vercel API (set env vars + redeploy).
|
|
1860
2002
|
*
|
|
1861
2003
|
* After a successful deploy the page polls for a new service-worker
|
|
1862
2004
|
* version — once detected the user is prompted to refresh.
|
|
1863
2005
|
*
|
|
1864
|
-
* Access is controlled by the companion
|
|
1865
|
-
* - Unconfigured → anyone can reach this page (
|
|
1866
|
-
* - Configured → authenticated users only (
|
|
2006
|
+
* Access is controlled by the companion \\\`+page.ts\\\` load function:
|
|
2007
|
+
* - Unconfigured → anyone can reach this page (\\\`isFirstSetup: true\\\`).
|
|
2008
|
+
* - Configured → authenticated users only (\\\`isFirstSetup: false\\\`).
|
|
1867
2009
|
*/
|
|
1868
2010
|
|
|
1869
2011
|
import { page } from '$app/stores';
|
|
@@ -1871,6 +2013,13 @@ function generateSetupPageSvelte(opts) {
|
|
|
1871
2013
|
import { isOnline } from 'stellar-drive/stores';
|
|
1872
2014
|
import { pollForNewServiceWorker } from 'stellar-drive/kit';
|
|
1873
2015
|
|
|
2016
|
+
// =============================================================================
|
|
2017
|
+
// Wizard State
|
|
2018
|
+
// =============================================================================
|
|
2019
|
+
|
|
2020
|
+
/** Current step (1-4) */
|
|
2021
|
+
let currentStep = $state(1);
|
|
2022
|
+
|
|
1874
2023
|
// =============================================================================
|
|
1875
2024
|
// Form State — Supabase + Vercel credentials
|
|
1876
2025
|
// =============================================================================
|
|
@@ -1897,7 +2046,7 @@ function generateSetupPageSvelte(opts) {
|
|
|
1897
2046
|
/** Error from credential validation, if any */
|
|
1898
2047
|
let validateError = $state<string | null>(null);
|
|
1899
2048
|
|
|
1900
|
-
/**
|
|
2049
|
+
/** \\\`true\\\` after credentials have been successfully validated */
|
|
1901
2050
|
let validateSuccess = $state(false);
|
|
1902
2051
|
|
|
1903
2052
|
/** Error from the deployment step, if any */
|
|
@@ -1924,13 +2073,16 @@ function generateSetupPageSvelte(opts) {
|
|
|
1924
2073
|
let validatedKey = $state('');
|
|
1925
2074
|
|
|
1926
2075
|
/**
|
|
1927
|
-
*
|
|
2076
|
+
* \\\`true\\\` when the user changes credentials after a successful
|
|
1928
2077
|
* validation — the "Continue" button should be re-disabled.
|
|
1929
2078
|
*/
|
|
1930
2079
|
const credentialsChanged = $derived(
|
|
1931
2080
|
validateSuccess && (supabaseUrl !== validatedUrl || supabasePublishableKey !== validatedKey)
|
|
1932
2081
|
);
|
|
1933
2082
|
|
|
2083
|
+
/** Whether the Continue button on step 3 should be enabled */
|
|
2084
|
+
const canContinueStep3 = $derived(validateSuccess && !credentialsChanged);
|
|
2085
|
+
|
|
1934
2086
|
// =============================================================================
|
|
1935
2087
|
// Effects
|
|
1936
2088
|
// =============================================================================
|
|
@@ -1951,9 +2103,9 @@ function generateSetupPageSvelte(opts) {
|
|
|
1951
2103
|
// =============================================================================
|
|
1952
2104
|
|
|
1953
2105
|
/**
|
|
1954
|
-
* Send the entered Supabase credentials to
|
|
2106
|
+
* Send the entered Supabase credentials to \\\`/api/setup/validate\\\`
|
|
1955
2107
|
* and update UI state based on the result. On success, also
|
|
1956
|
-
* cache the config locally via
|
|
2108
|
+
* cache the config locally via \\\`setConfig\\\` so the app is usable
|
|
1957
2109
|
* immediately after the deployment finishes.
|
|
1958
2110
|
*/
|
|
1959
2111
|
async function handleValidate() {
|
|
@@ -1996,8 +2148,8 @@ function generateSetupPageSvelte(opts) {
|
|
|
1996
2148
|
|
|
1997
2149
|
/**
|
|
1998
2150
|
* Poll for a new service-worker version to detect when the Vercel
|
|
1999
|
-
* redeployment has finished. Uses the engine's
|
|
2000
|
-
* helper which checks
|
|
2151
|
+
* redeployment has finished. Uses the engine's \\\`pollForNewServiceWorker\\\`
|
|
2152
|
+
* helper which checks \\\`registration.update()\\\` at regular intervals.
|
|
2001
2153
|
*
|
|
2002
2154
|
* Resolves a Promise when a new SW is detected in the waiting state.
|
|
2003
2155
|
*/
|
|
@@ -2019,7 +2171,7 @@ function generateSetupPageSvelte(opts) {
|
|
|
2019
2171
|
// =============================================================================
|
|
2020
2172
|
|
|
2021
2173
|
/**
|
|
2022
|
-
* Send credentials and the Vercel token to
|
|
2174
|
+
* Send credentials and the Vercel token to \\\`/api/setup/deploy\\\`,
|
|
2023
2175
|
* which sets the environment variables on the Vercel project and
|
|
2024
2176
|
* triggers a fresh deployment. Then poll until the new build is live.
|
|
2025
2177
|
*/
|
|
@@ -2040,7 +2192,7 @@ function generateSetupPageSvelte(opts) {
|
|
|
2040
2192
|
if (data.success) {
|
|
2041
2193
|
deployStage = 'deploying';
|
|
2042
2194
|
_deploymentUrl = data.deploymentUrl || '';
|
|
2043
|
-
/* Poll for the new SW version → marks
|
|
2195
|
+
/* Poll for the new SW version → marks \\\`deployStage = 'ready'\\\` */
|
|
2044
2196
|
await pollForDeployment();
|
|
2045
2197
|
} else {
|
|
2046
2198
|
deployError = data.error || 'Deployment failed';
|
|
@@ -2059,7 +2211,436 @@ function generateSetupPageSvelte(opts) {
|
|
|
2059
2211
|
<title>Setup - ${opts.name}</title>
|
|
2060
2212
|
</svelte:head>
|
|
2061
2213
|
|
|
2062
|
-
|
|
2214
|
+
<div class="setup-page">
|
|
2215
|
+
<div class="setup-container">
|
|
2216
|
+
<!-- Header -->
|
|
2217
|
+
<h1>Set Up ${opts.name}</h1>
|
|
2218
|
+
<p class="subtitle">Configure ${opts.name} to connect to your own Supabase backend</p>
|
|
2219
|
+
|
|
2220
|
+
<!-- Step indicator -->
|
|
2221
|
+
<div class="step-indicator">
|
|
2222
|
+
{#each [1, 2, 3, 4] as step}
|
|
2223
|
+
{#if step > 1}
|
|
2224
|
+
<div class="step-line" class:completed={currentStep > step - 1}></div>
|
|
2225
|
+
{/if}
|
|
2226
|
+
<div
|
|
2227
|
+
class="step-dot"
|
|
2228
|
+
class:active={currentStep === step}
|
|
2229
|
+
class:completed={currentStep > step}
|
|
2230
|
+
>
|
|
2231
|
+
{#if currentStep > step}
|
|
2232
|
+
<span class="checkmark">✓</span>
|
|
2233
|
+
{:else}
|
|
2234
|
+
{step}
|
|
2235
|
+
{/if}
|
|
2236
|
+
</div>
|
|
2237
|
+
{/each}
|
|
2238
|
+
</div>
|
|
2239
|
+
|
|
2240
|
+
<!-- Offline warning -->
|
|
2241
|
+
{#if !$isOnline}
|
|
2242
|
+
<div class="message message-error">
|
|
2243
|
+
You are currently offline. An internet connection is required to complete setup.
|
|
2244
|
+
</div>
|
|
2245
|
+
{/if}
|
|
2246
|
+
|
|
2247
|
+
<!-- Step cards -->
|
|
2248
|
+
<div class="step-card">
|
|
2249
|
+
{#if currentStep === 1}
|
|
2250
|
+
<h2>Step 1: Create a Supabase Project</h2>
|
|
2251
|
+
<p>
|
|
2252
|
+
${opts.name} stores data in your own Supabase project.
|
|
2253
|
+
Create one if you don't have one already — the free tier is more than enough.
|
|
2254
|
+
</p>
|
|
2255
|
+
<ol>
|
|
2256
|
+
<li>Go to <a href="https://supabase.com/dashboard" target="_blank" rel="noopener noreferrer">supabase.com/dashboard</a></li>
|
|
2257
|
+
<li>Click <strong>New Project</strong>, choose a name and database password, then click <strong>Create new project</strong>.</li>
|
|
2258
|
+
<li>Wait for provisioning to finish (usually under a minute).</li>
|
|
2259
|
+
</ol>
|
|
2260
|
+
<p class="info-note">
|
|
2261
|
+
<strong>Note:</strong> Supabase's built-in SMTP works for development. For production
|
|
2262
|
+
you may want to configure a custom SMTP provider under Authentication > Settings.
|
|
2263
|
+
</p>
|
|
2264
|
+
|
|
2265
|
+
{:else if currentStep === 2}
|
|
2266
|
+
<h2>Step 2: Initialize the Database</h2>
|
|
2267
|
+
<p>
|
|
2268
|
+
The required tables and RLS policies are created automatically during the build process.
|
|
2269
|
+
When your app deploys to Vercel, the schema is pushed to your Supabase database — no
|
|
2270
|
+
manual SQL is needed.
|
|
2271
|
+
</p>
|
|
2272
|
+
|
|
2273
|
+
{:else if currentStep === 3}
|
|
2274
|
+
<h2>Step 3: Connect Your Supabase Project</h2>
|
|
2275
|
+
<p>Find these values in your Supabase dashboard under <strong>Settings > API</strong>.</p>
|
|
2276
|
+
|
|
2277
|
+
<div class="form-group">
|
|
2278
|
+
<label for="supabase-url">Project URL</label>
|
|
2279
|
+
<input
|
|
2280
|
+
id="supabase-url"
|
|
2281
|
+
type="url"
|
|
2282
|
+
placeholder="https://your-project.supabase.co"
|
|
2283
|
+
bind:value={supabaseUrl}
|
|
2284
|
+
/>
|
|
2285
|
+
</div>
|
|
2286
|
+
|
|
2287
|
+
<div class="form-group">
|
|
2288
|
+
<label for="supabase-key">Publishable Key (anon / public)</label>
|
|
2289
|
+
<input
|
|
2290
|
+
id="supabase-key"
|
|
2291
|
+
type="text"
|
|
2292
|
+
placeholder="eyJhbGciOiJIUzI1NiIs..."
|
|
2293
|
+
bind:value={supabasePublishableKey}
|
|
2294
|
+
/>
|
|
2295
|
+
<span class="hint">This is the \`anon\` key — safe to expose in the browser.</span>
|
|
2296
|
+
</div>
|
|
2297
|
+
|
|
2298
|
+
<button
|
|
2299
|
+
class="btn btn-secondary"
|
|
2300
|
+
onclick={handleValidate}
|
|
2301
|
+
disabled={validating || !supabaseUrl || !supabasePublishableKey}
|
|
2302
|
+
>
|
|
2303
|
+
{#if validating}Testing...{:else}Test Connection{/if}
|
|
2304
|
+
</button>
|
|
2305
|
+
|
|
2306
|
+
{#if validateError}
|
|
2307
|
+
<div class="message message-error">{validateError}</div>
|
|
2308
|
+
{/if}
|
|
2309
|
+
{#if validateSuccess}
|
|
2310
|
+
<div class="message message-success">Connection successful! Credentials are valid.</div>
|
|
2311
|
+
{/if}
|
|
2312
|
+
|
|
2313
|
+
{:else if currentStep === 4}
|
|
2314
|
+
<h2>Step 4: Deploy to Vercel</h2>
|
|
2315
|
+
<p>
|
|
2316
|
+
Provide a one-time Vercel API token so ${opts.name} can set the environment
|
|
2317
|
+
variables on your project and trigger a redeployment.
|
|
2318
|
+
</p>
|
|
2319
|
+
<div class="form-group">
|
|
2320
|
+
<label for="vercel-token">Vercel API Token</label>
|
|
2321
|
+
<input
|
|
2322
|
+
id="vercel-token"
|
|
2323
|
+
type="password"
|
|
2324
|
+
placeholder="Paste your Vercel token"
|
|
2325
|
+
bind:value={vercelToken}
|
|
2326
|
+
/>
|
|
2327
|
+
</div>
|
|
2328
|
+
|
|
2329
|
+
<button
|
|
2330
|
+
class="btn btn-primary"
|
|
2331
|
+
onclick={handleDeploy}
|
|
2332
|
+
disabled={deploying || !vercelToken}
|
|
2333
|
+
>
|
|
2334
|
+
{#if deploying}Deploying...{:else}Deploy{/if}
|
|
2335
|
+
</button>
|
|
2336
|
+
|
|
2337
|
+
{#if deployError}
|
|
2338
|
+
<div class="message message-error">{deployError}</div>
|
|
2339
|
+
{/if}
|
|
2340
|
+
|
|
2341
|
+
<!-- Deployment pipeline stages -->
|
|
2342
|
+
{#if deployStage !== 'idle'}
|
|
2343
|
+
<div class="deploy-stages">
|
|
2344
|
+
<div class="deploy-stage" class:active={deployStage === 'setting-env'} class:done={deployStage === 'deploying' || deployStage === 'ready'}>
|
|
2345
|
+
<span class="stage-icon">{#if deployStage === 'setting-env'}○{:else}✓{/if}</span>
|
|
2346
|
+
Setting environment variables
|
|
2347
|
+
</div>
|
|
2348
|
+
<div class="deploy-stage" class:active={deployStage === 'deploying'} class:done={deployStage === 'ready'}>
|
|
2349
|
+
<span class="stage-icon">{#if deployStage === 'deploying'}○{:else if deployStage === 'ready'}✓{:else}•{/if}</span>
|
|
2350
|
+
Deploying to Vercel
|
|
2351
|
+
</div>
|
|
2352
|
+
<div class="deploy-stage" class:active={deployStage === 'ready'}>
|
|
2353
|
+
<span class="stage-icon">{#if deployStage === 'ready'}✓{:else}•{/if}</span>
|
|
2354
|
+
Ready
|
|
2355
|
+
</div>
|
|
2356
|
+
</div>
|
|
2357
|
+
{/if}
|
|
2358
|
+
|
|
2359
|
+
{#if deployStage === 'ready'}
|
|
2360
|
+
<div class="message message-success">
|
|
2361
|
+
Deployment complete! <a href="/">Refresh to start using ${opts.name}</a>.
|
|
2362
|
+
</div>
|
|
2363
|
+
{/if}
|
|
2364
|
+
{/if}
|
|
2365
|
+
</div>
|
|
2366
|
+
|
|
2367
|
+
<!-- Step navigation -->
|
|
2368
|
+
<div class="step-nav">
|
|
2369
|
+
{#if currentStep > 1}
|
|
2370
|
+
<button class="btn btn-back" onclick={() => currentStep--}>Back</button>
|
|
2371
|
+
{:else}
|
|
2372
|
+
<div></div>
|
|
2373
|
+
{/if}
|
|
2374
|
+
|
|
2375
|
+
{#if currentStep < 3}
|
|
2376
|
+
<button class="btn btn-primary" onclick={() => currentStep++}>Continue</button>
|
|
2377
|
+
{:else if currentStep === 3}
|
|
2378
|
+
<button
|
|
2379
|
+
class="btn btn-primary"
|
|
2380
|
+
onclick={() => currentStep++}
|
|
2381
|
+
disabled={!canContinueStep3}
|
|
2382
|
+
>Continue</button>
|
|
2383
|
+
{/if}
|
|
2384
|
+
</div>
|
|
2385
|
+
|
|
2386
|
+
<!-- Security notice (first-time setup only) -->
|
|
2387
|
+
{#if isFirstSetup}
|
|
2388
|
+
<div class="security-notice">
|
|
2389
|
+
<strong>Security:</strong> Your Supabase credentials are stored as environment variables
|
|
2390
|
+
on Vercel and are never sent to any third-party service. The Vercel token is used once
|
|
2391
|
+
and is not persisted.
|
|
2392
|
+
</div>
|
|
2393
|
+
{/if}
|
|
2394
|
+
</div>
|
|
2395
|
+
</div>
|
|
2396
|
+
|
|
2397
|
+
<style>
|
|
2398
|
+
.setup-page {
|
|
2399
|
+
display: flex;
|
|
2400
|
+
justify-content: center;
|
|
2401
|
+
padding: 2rem 1rem;
|
|
2402
|
+
min-height: 100vh;
|
|
2403
|
+
background: var(--color-bg, #fafafa);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
.setup-container {
|
|
2407
|
+
max-width: 640px;
|
|
2408
|
+
width: 100%;
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
h1 {
|
|
2412
|
+
margin: 0 0 0.25rem;
|
|
2413
|
+
font-size: 1.75rem;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
.subtitle {
|
|
2417
|
+
margin: 0 0 1.5rem;
|
|
2418
|
+
color: var(--color-text-muted, #666);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
/* Step indicator */
|
|
2422
|
+
.step-indicator {
|
|
2423
|
+
display: flex;
|
|
2424
|
+
align-items: center;
|
|
2425
|
+
justify-content: center;
|
|
2426
|
+
gap: 0;
|
|
2427
|
+
margin-bottom: 2rem;
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
.step-dot {
|
|
2431
|
+
width: 32px;
|
|
2432
|
+
height: 32px;
|
|
2433
|
+
border-radius: 50%;
|
|
2434
|
+
border: 2px solid var(--color-border, #ccc);
|
|
2435
|
+
display: flex;
|
|
2436
|
+
align-items: center;
|
|
2437
|
+
justify-content: center;
|
|
2438
|
+
font-size: 0.875rem;
|
|
2439
|
+
font-weight: 600;
|
|
2440
|
+
color: var(--color-text-muted, #666);
|
|
2441
|
+
background: var(--color-bg, #fff);
|
|
2442
|
+
flex-shrink: 0;
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
.step-dot.active {
|
|
2446
|
+
border-color: var(--color-primary, #3b82f6);
|
|
2447
|
+
color: var(--color-primary, #3b82f6);
|
|
2448
|
+
background: var(--color-primary-light, #eff6ff);
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
.step-dot.completed {
|
|
2452
|
+
border-color: var(--color-success, #22c55e);
|
|
2453
|
+
background: var(--color-success, #22c55e);
|
|
2454
|
+
color: #fff;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
.checkmark {
|
|
2458
|
+
font-size: 0.875rem;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
.step-line {
|
|
2462
|
+
width: 40px;
|
|
2463
|
+
height: 2px;
|
|
2464
|
+
background: var(--color-border, #ccc);
|
|
2465
|
+
flex-shrink: 0;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
.step-line.completed {
|
|
2469
|
+
background: var(--color-success, #22c55e);
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
/* Step card */
|
|
2473
|
+
.step-card {
|
|
2474
|
+
padding: 1.5rem;
|
|
2475
|
+
border: 1px solid var(--color-border, #e2e2e2);
|
|
2476
|
+
border-radius: 8px;
|
|
2477
|
+
background: var(--color-surface, #fff);
|
|
2478
|
+
margin-bottom: 1.5rem;
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
.step-card h2 {
|
|
2482
|
+
margin: 0 0 0.75rem;
|
|
2483
|
+
font-size: 1.25rem;
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
.step-card p {
|
|
2487
|
+
margin: 0 0 1rem;
|
|
2488
|
+
line-height: 1.5;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
.step-card ol {
|
|
2492
|
+
margin: 0 0 1rem;
|
|
2493
|
+
padding-left: 1.25rem;
|
|
2494
|
+
line-height: 1.7;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
.info-note {
|
|
2498
|
+
padding: 0.75rem 1rem;
|
|
2499
|
+
background: var(--color-info-bg, #f0f9ff);
|
|
2500
|
+
border-radius: 6px;
|
|
2501
|
+
font-size: 0.875rem;
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
/* Form */
|
|
2505
|
+
.form-group {
|
|
2506
|
+
margin-bottom: 1rem;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
.form-group label {
|
|
2510
|
+
display: block;
|
|
2511
|
+
margin-bottom: 0.25rem;
|
|
2512
|
+
font-weight: 600;
|
|
2513
|
+
font-size: 0.875rem;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
.form-group input {
|
|
2517
|
+
width: 100%;
|
|
2518
|
+
padding: 0.5rem 0.75rem;
|
|
2519
|
+
border: 1px solid var(--color-border, #ccc);
|
|
2520
|
+
border-radius: 4px;
|
|
2521
|
+
font-size: 0.875rem;
|
|
2522
|
+
box-sizing: border-box;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
.hint {
|
|
2526
|
+
display: block;
|
|
2527
|
+
margin-top: 0.25rem;
|
|
2528
|
+
font-size: 0.75rem;
|
|
2529
|
+
color: var(--color-text-muted, #888);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
/* Buttons */
|
|
2533
|
+
.btn {
|
|
2534
|
+
padding: 0.5rem 1.25rem;
|
|
2535
|
+
border: none;
|
|
2536
|
+
border-radius: 4px;
|
|
2537
|
+
font-size: 0.875rem;
|
|
2538
|
+
cursor: pointer;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
.btn:disabled {
|
|
2542
|
+
opacity: 0.5;
|
|
2543
|
+
cursor: not-allowed;
|
|
2544
|
+
}
|
|
2545
|
+
|
|
2546
|
+
.btn-primary {
|
|
2547
|
+
background: var(--color-primary, #3b82f6);
|
|
2548
|
+
color: #fff;
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
.btn-secondary {
|
|
2552
|
+
background: var(--color-secondary, #6b7280);
|
|
2553
|
+
color: #fff;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
.btn-back {
|
|
2557
|
+
background: transparent;
|
|
2558
|
+
color: var(--color-text-muted, #666);
|
|
2559
|
+
border: 1px solid var(--color-border, #ccc);
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
/* Messages */
|
|
2563
|
+
.message {
|
|
2564
|
+
padding: 0.75rem 1rem;
|
|
2565
|
+
border-radius: 6px;
|
|
2566
|
+
margin-top: 0.75rem;
|
|
2567
|
+
font-size: 0.875rem;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.message-error {
|
|
2571
|
+
background: var(--color-error-bg, #fef2f2);
|
|
2572
|
+
color: var(--color-error, #dc2626);
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
.message-success {
|
|
2576
|
+
background: var(--color-success-bg, #f0fdf4);
|
|
2577
|
+
color: var(--color-success, #16a34a);
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
/* Deploy stages */
|
|
2581
|
+
.deploy-stages {
|
|
2582
|
+
display: flex;
|
|
2583
|
+
flex-direction: column;
|
|
2584
|
+
gap: 0.5rem;
|
|
2585
|
+
margin-top: 1rem;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
.deploy-stage {
|
|
2589
|
+
display: flex;
|
|
2590
|
+
align-items: center;
|
|
2591
|
+
gap: 0.5rem;
|
|
2592
|
+
font-size: 0.875rem;
|
|
2593
|
+
color: var(--color-text-muted, #999);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
.deploy-stage.active {
|
|
2597
|
+
color: var(--color-primary, #3b82f6);
|
|
2598
|
+
font-weight: 600;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
.deploy-stage.done {
|
|
2602
|
+
color: var(--color-success, #22c55e);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
.stage-icon {
|
|
2606
|
+
width: 1.25rem;
|
|
2607
|
+
text-align: center;
|
|
2608
|
+
}
|
|
2609
|
+
|
|
2610
|
+
/* Step navigation */
|
|
2611
|
+
.step-nav {
|
|
2612
|
+
display: flex;
|
|
2613
|
+
justify-content: space-between;
|
|
2614
|
+
align-items: center;
|
|
2615
|
+
margin-bottom: 1.5rem;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
/* Security notice */
|
|
2619
|
+
.security-notice {
|
|
2620
|
+
padding: 0.75rem 1rem;
|
|
2621
|
+
background: var(--color-info-bg, #f9fafb);
|
|
2622
|
+
border: 1px solid var(--color-border, #e5e7eb);
|
|
2623
|
+
border-radius: 6px;
|
|
2624
|
+
font-size: 0.8rem;
|
|
2625
|
+
color: var(--color-text-muted, #666);
|
|
2626
|
+
line-height: 1.5;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
/* Responsive */
|
|
2630
|
+
@media (max-width: 480px) {
|
|
2631
|
+
.setup-page {
|
|
2632
|
+
padding: 1rem 0.5rem;
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
.step-card {
|
|
2636
|
+
padding: 1rem;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
.step-line {
|
|
2640
|
+
width: 24px;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
</style>
|
|
2063
2644
|
`;
|
|
2064
2645
|
}
|
|
2065
2646
|
/**
|
|
@@ -4284,6 +4865,10 @@ function generateDemoPage(opts) {
|
|
|
4284
4865
|
width: 100%;
|
|
4285
4866
|
height: 1px;
|
|
4286
4867
|
}
|
|
4868
|
+
|
|
4869
|
+
.form-group input {
|
|
4870
|
+
font-size: 16px; /* Prevents iOS zoom on focus */
|
|
4871
|
+
}
|
|
4287
4872
|
}
|
|
4288
4873
|
|
|
4289
4874
|
@media (max-width: 380px) {
|
|
@@ -4656,9 +5241,9 @@ export async function run() {
|
|
|
4656
5241
|
['static/icons/monochrome.svg', generateMonochromeSvg(firstLetter)],
|
|
4657
5242
|
['static/icons/splash.svg', generateSplashSvg(opts.shortName)],
|
|
4658
5243
|
['static/icons/apple-touch.svg', generatePlaceholderSvg('#6c5ce7', firstLetter)],
|
|
4659
|
-
['static/change-email.html',
|
|
4660
|
-
['static/device-verification-email.html',
|
|
4661
|
-
['static/signup-email.html',
|
|
5244
|
+
['static/change-email.html', generateChangeEmail()],
|
|
5245
|
+
['static/device-verification-email.html', generateDeviceVerificationEmail()],
|
|
5246
|
+
['static/signup-email.html', generateSignupEmail()]
|
|
4662
5247
|
]
|
|
4663
5248
|
},
|
|
4664
5249
|
{
|