@realtimex/realtimex-alchemy 1.0.74 → 1.0.76

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/dist/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.76] - 2026-01-29
9
+
10
+ ### Added
11
+ - **Localization**: Added full localization support for the new "Zero-Config" setup flow. All setup steps, including project selection and provisioning logs, are now available in supported languages.
12
+
13
+ ### Improved
14
+ - **Setup Wizard**: Replaced hardcoded strings in the setup wizard with translation keys to ensure a consistent localized experience.
15
+
16
+ ## [1.0.75] - 2026-01-29
17
+
18
+ ### Added
19
+ - **Onboarding**: Implemented "Zero-Config Cloud Provisioning". Users can now create and configure a brand new Supabase project directly from the Setup Wizard using just their Personal Access Token.
20
+ - **Setup Wizard**: Added a new "Quick Launch" workflow that automates project creation, API key retrieval, and database initialization.
21
+ - **API**: Added `/api/setup/auto-provision` endpoint that handles the full lifecycle of Supabase project creation (Create -> Poll -> Configure -> DNS Wait).
22
+
23
+ ### Improved
24
+ - **Migration**: Updated `migrate.sh` to support "Docker-less" Edge Function deployment via the Supabase Cloud API (`--use-api`), significantly reducing local dependency requirements.
25
+ - **Migration**: Added automatic `TOKEN_ENCRYPTION_KEY` generation during migration to ensure secure secret management out of the box.
26
+
8
27
  ## [1.0.74] - 2026-01-29
9
28
 
10
29
  ### Added
package/dist/api/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import express from 'express';
2
+ import axios from 'axios';
2
3
  import cors from 'cors';
4
+ import crypto from 'crypto';
3
5
  import path from 'path';
4
6
  import { fileURLToPath } from 'url';
5
7
  import { spawn } from 'child_process';
@@ -127,6 +129,166 @@ app.post('/api/migrate', (req, res) => {
127
129
  console.log('[Migrate] Client disconnected, but migration will continue');
128
130
  });
129
131
  });
132
+ // GET /api/setup/organizations - List Supabase organizations
133
+ app.get('/api/setup/organizations', async (req, res) => {
134
+ const authHeader = req.headers['authorization'];
135
+ if (!authHeader) {
136
+ return res.status(401).json({ error: 'Missing Authorization header' });
137
+ }
138
+ try {
139
+ const response = await axios.get('https://api.supabase.com/v1/organizations', {
140
+ headers: { 'Authorization': authHeader }
141
+ });
142
+ res.json(response.data);
143
+ }
144
+ catch (error) {
145
+ console.error('[Setup] Failed to fetch organizations:', error.response?.data || error.message);
146
+ res.status(error.response?.status || 500).json({
147
+ error: error.response?.data?.message || 'Failed to fetch organizations'
148
+ });
149
+ }
150
+ });
151
+ // POST /api/setup/auto-provision - Create project and poll for readiness (SSE)
152
+ app.post('/api/setup/auto-provision', async (req, res) => {
153
+ const { orgId, projectName: customProjectName, region: customRegion } = req.body;
154
+ const authHeader = req.headers['authorization'];
155
+ if (!orgId) {
156
+ return res.status(400).json({ error: 'Missing required parameter (orgId)' });
157
+ }
158
+ if (!authHeader) {
159
+ return res.status(401).json({ error: 'Missing Authorization header' });
160
+ }
161
+ res.setHeader('Content-Type', 'text/event-stream');
162
+ res.setHeader('Cache-Control', 'no-cache');
163
+ res.setHeader('Connection', 'keep-alive');
164
+ const sendEvent = (type, data) => {
165
+ res.write(`data: ${JSON.stringify({ type, data })}\n\n`);
166
+ };
167
+ try {
168
+ const projectName = customProjectName || `Alchemy-${crypto.randomBytes(2).toString('hex')}`;
169
+ const region = customRegion || 'us-east-1';
170
+ // Generate a secure DB password server-side
171
+ const dbPass = crypto.randomBytes(16).toString('base64')
172
+ .replace(/\+/g, 'a')
173
+ .replace(/\//g, 'b')
174
+ .replace(/=/g, 'c') + '1!Aa';
175
+ sendEvent('info', `🚀 Creating Supabase project: ${projectName} in ${region}...`);
176
+ // 1. Create Project
177
+ const createResponse = await axios.post('https://api.supabase.com/v1/projects', {
178
+ name: projectName,
179
+ organization_id: orgId,
180
+ region: region,
181
+ db_pass: dbPass
182
+ }, {
183
+ headers: { 'Authorization': authHeader },
184
+ timeout: 15000 // 15s timeout for creation
185
+ });
186
+ const project = createResponse.data;
187
+ const projectRef = project.id;
188
+ sendEvent('info', `📦 Project created! ID: ${projectRef}. Waiting for it to go live...`);
189
+ sendEvent('project_id', projectRef);
190
+ // 2. Poll for Readiness
191
+ let isReady = false;
192
+ let attempts = 0;
193
+ const maxAttempts = 60; // 5 minutes (5s interval)
194
+ while (!isReady && attempts < maxAttempts) {
195
+ attempts++;
196
+ await new Promise(resolve => setTimeout(resolve, 5000));
197
+ try {
198
+ const statusResponse = await axios.get(`https://api.supabase.com/v1/projects/${projectRef}`, {
199
+ headers: { 'Authorization': authHeader },
200
+ timeout: 10000 // 10s timeout for status check
201
+ });
202
+ const status = statusResponse.data.status;
203
+ sendEvent('info', `⏳ Status: ${status} (Attempt ${attempts}/${maxAttempts})`);
204
+ if (status === 'ACTIVE_HEALTHY' || status === 'ACTIVE') {
205
+ isReady = true;
206
+ }
207
+ }
208
+ catch (pollError) {
209
+ console.warn('[Setup] Polling error:', pollError.message);
210
+ }
211
+ }
212
+ if (!isReady) {
213
+ throw new Error('Project provision timed out after 5 minutes.');
214
+ }
215
+ // 3. Get API Keys
216
+ sendEvent('info', '🔑 Retrieving API keys...');
217
+ let anonKey = '';
218
+ let keyAttempts = 0;
219
+ const maxKeyAttempts = 10;
220
+ while (!anonKey && keyAttempts < maxKeyAttempts) {
221
+ keyAttempts++;
222
+ if (keyAttempts > 1) {
223
+ sendEvent('info', `⏳ API keys not ready yet. Retrying (Attempt ${keyAttempts}/${maxKeyAttempts})...`);
224
+ await new Promise(resolve => setTimeout(resolve, 3000));
225
+ }
226
+ try {
227
+ const keysResponse = await axios.get(`https://api.supabase.com/v1/projects/${projectRef}/api-keys`, {
228
+ headers: { 'Authorization': authHeader },
229
+ timeout: 10000 // 10s timeout for key retrieval
230
+ });
231
+ const keys = keysResponse.data;
232
+ if (Array.isArray(keys)) {
233
+ anonKey = keys.find((k) => k.name === 'anon')?.api_key;
234
+ if (anonKey) {
235
+ sendEvent('info', '✅ API keys retrieved successfully.');
236
+ }
237
+ }
238
+ }
239
+ catch (err) {
240
+ const errorMsg = err.response?.data?.message || err.message;
241
+ console.warn(`[Setup] Key retrieval attempt ${keyAttempts} failed:`, errorMsg);
242
+ sendEvent('info', `⚠️ Attempt ${keyAttempts} failed: ${errorMsg}`);
243
+ }
244
+ }
245
+ if (!anonKey) {
246
+ throw new Error('Could not find anonymous API key for the new project after several attempts.');
247
+ }
248
+ const supabaseUrl = `https://${projectRef}.supabase.co`;
249
+ // 4. Verification: Wait for DNS to propagate (Cloudflare/Supabase propagation)
250
+ sendEvent('info', '🌐 Waiting for DNS propagation (Supabase/Cloudflare)...');
251
+ let dnsReady = false;
252
+ let dnsAttempts = 0;
253
+ const maxDnsAttempts = 20;
254
+ while (!dnsReady && dnsAttempts < maxDnsAttempts) {
255
+ dnsAttempts++;
256
+ try {
257
+ // Try to ping the REST endpoint
258
+ const pingResponse = await axios.get(`${supabaseUrl}/rest/v1/`, {
259
+ timeout: 5000,
260
+ validateStatus: () => true // Accept any status (even 401/404) as DNS is resolved
261
+ });
262
+ if (pingResponse.status < 500) {
263
+ dnsReady = true;
264
+ sendEvent('info', '✨ DNS resolved! Project is fully accessible.');
265
+ }
266
+ }
267
+ catch (pingError) {
268
+ console.log(`[Setup] DNS poll ${dnsAttempts}:`, pingError.message);
269
+ if (dnsAttempts % 5 === 0) {
270
+ sendEvent('info', '⏳ DNS still propagating... standby.');
271
+ }
272
+ await new Promise(resolve => setTimeout(resolve, 3000));
273
+ }
274
+ }
275
+ sendEvent('success', {
276
+ url: supabaseUrl,
277
+ anonKey: anonKey,
278
+ projectId: projectRef,
279
+ dbPass: dbPass // Password generated on line 202
280
+ });
281
+ sendEvent('done', 'success');
282
+ }
283
+ catch (error) {
284
+ console.error('[Setup] Auto-provision failed:', error.response?.data || error.message);
285
+ sendEvent('error', error.response?.data?.message || error.message || 'Auto-provisioning failed');
286
+ sendEvent('done', 'failed');
287
+ }
288
+ finally {
289
+ res.end();
290
+ }
291
+ });
130
292
  // SSE Events
131
293
  app.get('/events', (req, res) => {
132
294
  res.setHeader('Content-Type', 'text/event-stream');