@fenaura/sdk 0.2.0
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/LICENSE +21 -0
- package/README.md +124 -0
- package/bin/init.js +224 -0
- package/js/fenaura-client.d.ts +376 -0
- package/js/fenaura-client.js +1665 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 agah enoch wesoamo
|
|
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,124 @@
|
|
|
1
|
+
# @fenaura/sdk
|
|
2
|
+
|
|
3
|
+
Fenaura Client SDK — direct database access, auth, and storage from the browser. Zero dependencies. Works with any Fenaura BaaS backend.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @fenaura/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node >= 18 (browsers: modern evergreen with `fetch`).
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
First set up the same-domain proxy once per frontend (recommended for cookie auth):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx @fenaura/auth-init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then in your app:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import { createClient } from '@fenaura/sdk';
|
|
25
|
+
|
|
26
|
+
const fenaura = createClient('/fenaura', '<PROJECT_ID>');
|
|
27
|
+
|
|
28
|
+
await fenaura.auth.signIn({ email: 'jane@example.com', password: 'secret123' });
|
|
29
|
+
const { data, error } = await fenaura.from('todos').select('*').eq('done', false).limit(20);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Get your `<PROJECT_ID>` (project slug) from the Fenaura dashboard.
|
|
33
|
+
|
|
34
|
+
## Why the `/fenaura` proxy?
|
|
35
|
+
|
|
36
|
+
Fenaura auth uses `HttpOnly SameSite=Strict` session cookies. Calling the API cross-origin would drop cookies and hit CORS issues. The proxy maps same-origin `/fenaura/*` to your API, so auth just works in dev and prod with no code changes.
|
|
37
|
+
|
|
38
|
+
Direct (non-proxy) mode is for backends / Node scripts:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const fenaura = createClient('https://api.yours.com', '<PROJECT_ID>');
|
|
42
|
+
fenaura.setSession(token); // raw token from signIn(..., { browser: false })
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Database
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
// select
|
|
49
|
+
await fenaura.from('users').select('id,name').eq('role', 'admin').order('created_at').limit(10);
|
|
50
|
+
// insert
|
|
51
|
+
await fenaura.from('users').insert({ name: 'John' });
|
|
52
|
+
// update + delete are chained then awaited (Supabase-style)
|
|
53
|
+
await fenaura.from('users').update({ name: 'Jane' }).eq('id', 1).execute();
|
|
54
|
+
await fenaura.from('users').delete().eq('id', 1).execute();
|
|
55
|
+
// upsert takes a single object only
|
|
56
|
+
await fenaura.from('users').upsert({ id: 1, name: 'Jane' });
|
|
57
|
+
// single row
|
|
58
|
+
const user = await fenaura.from('users').select('*').eq('id', 1).single();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Filters: `eq, neq, gt, gte, lt, lte, like, ilike, in, is, contains, containedBy, overlaps, textSearch, not, or`. Table/column names are validated client-side.
|
|
62
|
+
|
|
63
|
+
## Auth
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
await fenaura.auth.signUp({ email, password });
|
|
67
|
+
await fenaura.auth.signIn({ email, password });
|
|
68
|
+
await fenaura.auth.signOut();
|
|
69
|
+
await fenaura.auth.getUser(); // current user via cookie or Bearer
|
|
70
|
+
await fenaura.auth.getSession();
|
|
71
|
+
|
|
72
|
+
// OAuth (12 providers: google, github, microsoft, apple, ...)
|
|
73
|
+
const { data } = await fenaura.auth.signInWithOAuth('google', { redirectTo: 'https://app.yours.com/auth/callback' });
|
|
74
|
+
// non-web / backend: { browser: false } returns { data: { url } }, then:
|
|
75
|
+
await fenaura.auth.exchangeCodeForSession(code);
|
|
76
|
+
|
|
77
|
+
// email verification / password reset / magic link
|
|
78
|
+
await fenaura.auth.sendVerificationCode({ email });
|
|
79
|
+
await fenaura.auth.submitVerificationCode({ email, code });
|
|
80
|
+
await fenaura.auth.sendPasswordReset({ email });
|
|
81
|
+
await fenaura.auth.confirmPasswordReset({ email, code, newPassword });
|
|
82
|
+
await fenaura.auth.sendMagicLink({ email });
|
|
83
|
+
await fenaura.auth.verifyMagicCode({ email, code });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Pass `{ browser: false }` to `signUp/signIn/submitVerificationCode/confirmPasswordReset/verifyMagicCode` in non-browser contexts to get the raw token in `data.token` instead of a cookie.
|
|
87
|
+
|
|
88
|
+
## Storage
|
|
89
|
+
|
|
90
|
+
Chunked (1 MiB), resumable per-chunk retry, with progress:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
const { data, error } = await fenaura.storage.from('avatars').upload('user1.jpg', file, {
|
|
94
|
+
compress: true, // on-device image resize / gzip before upload
|
|
95
|
+
onProgress: ({ sentBytes, totalBytes }) => console.log(sentBytes / totalBytes),
|
|
96
|
+
});
|
|
97
|
+
const dl = await fenaura.storage.from('avatars').download('user1.jpg');
|
|
98
|
+
const url = fenaura.storage.from('avatars').getPublicUrl('user1.jpg'); // session-gated, needs cookie
|
|
99
|
+
const signed = await fenaura.storage.from('avatars').createSignedUrl('user1.jpg', 3600); // public HMAC link
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`compressFile(file, opts)` is also exported standalone. Images use canvas, other files use `CompressionStream`.
|
|
103
|
+
|
|
104
|
+
## TypeScript
|
|
105
|
+
|
|
106
|
+
Types ship built-in:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createClient, type FenauraClient } from '@fenaura/sdk';
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Bins
|
|
113
|
+
|
|
114
|
+
This package also ships the proxy installer:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx -p @fenaura/sdk fenaura-init
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Prefer the standalone `npx @fenaura/auth-init`.
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT — see LICENSE.
|
package/bin/init.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npx @fenaura/auth-init — run from your frontend folder.
|
|
3
|
+
// Writes the /fenaura/* proxy files your host needs. Idempotent.
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const argv = process.argv.slice(2);
|
|
8
|
+
const apiFlag = argv.find((a) => a.startsWith('--api='));
|
|
9
|
+
const API = apiFlag ? apiFlag.split('=')[1] : (process.env.FENAURA_API || 'http://localhost:8080');
|
|
10
|
+
const RULE_LINE = `/fenaura/* ${API}/:splat 200`;
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
// Outside an app folder? Ask the user to confirm cwd is their frontend root.
|
|
14
|
+
async function confirmRoot() {
|
|
15
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) return;
|
|
16
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
const answer = await new Promise((resolve) => rl.question(`No package.json here. Is this your frontend root? (${cwd}) [Y/n] `, resolve));
|
|
18
|
+
rl.close();
|
|
19
|
+
if (!/^(y|yes)?\s*$/i.test(answer || '')) {
|
|
20
|
+
console.error('Aborted — cd into your frontend folder first, then re-run.');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
await confirmRoot();
|
|
27
|
+
const report = [];
|
|
28
|
+
const has = (f) => fs.existsSync(path.join(cwd, f));
|
|
29
|
+
const hasRule = (f) => has(f) && fs.readFileSync(path.join(cwd, f), 'utf8').includes('/fenaura');
|
|
30
|
+
|
|
31
|
+
// 1. public/_redirects (Netlify + Cloudflare Pages read this)
|
|
32
|
+
const pubDir = path.join(cwd, 'public');
|
|
33
|
+
const redirectsFile = path.join(pubDir, '_redirects');
|
|
34
|
+
if (has('public/_redirects')) {
|
|
35
|
+
const cur = fs.readFileSync(redirectsFile, 'utf8');
|
|
36
|
+
const lines = cur.split('\n');
|
|
37
|
+
const idx = lines.findIndex(l => l.includes('/fenaura'));
|
|
38
|
+
if (idx !== -1) {
|
|
39
|
+
if (lines[idx].trim() === RULE_LINE) {
|
|
40
|
+
report.push('kept public/_redirects (rule already present)');
|
|
41
|
+
} else {
|
|
42
|
+
lines[idx] = RULE_LINE;
|
|
43
|
+
fs.writeFileSync(redirectsFile, lines.join('\n'));
|
|
44
|
+
report.push('fixed public/_redirects (corrected tampered rule)');
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
fs.appendFileSync(redirectsFile, (cur.length && !cur.endsWith('\n') ? '\n' : '') + RULE_LINE + '\n');
|
|
48
|
+
report.push('wrote public/_redirects');
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
fs.mkdirSync(pubDir, { recursive: true });
|
|
52
|
+
fs.writeFileSync(redirectsFile, RULE_LINE + '\n');
|
|
53
|
+
report.push('wrote public/_redirects');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 2. vercel.json rewrites (Vercel only; ignored elsewhere)
|
|
57
|
+
const vercelPath = path.join(cwd, 'vercel.json');
|
|
58
|
+
const rewrite = { source: '/fenaura/:path*', destination: `${API}/:path*` };
|
|
59
|
+
let vercel = has('vercel.json') ? JSON.parse(fs.readFileSync(vercelPath, 'utf8')) : {};
|
|
60
|
+
vercel.rewrites = vercel.rewrites || [];
|
|
61
|
+
const existingIdx = vercel.rewrites.findIndex((r) => r.source && r.source.startsWith('/fenaura'));
|
|
62
|
+
if (existingIdx !== -1) {
|
|
63
|
+
const curDest = vercel.rewrites[existingIdx].destination;
|
|
64
|
+
if (curDest === rewrite.destination) {
|
|
65
|
+
report.push('kept vercel.json (rule already present)');
|
|
66
|
+
} else {
|
|
67
|
+
vercel.rewrites[existingIdx].destination = rewrite.destination;
|
|
68
|
+
fs.writeFileSync(vercelPath, JSON.stringify(vercel, null, 2) + '\n');
|
|
69
|
+
report.push('fixed vercel.json (corrected tampered destination)');
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
vercel.rewrites.push(rewrite);
|
|
73
|
+
fs.writeFileSync(vercelPath, JSON.stringify(vercel, null, 2) + '\n');
|
|
74
|
+
report.push('wrote vercel.json');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 3. Dev proxy: write it directly (Vite / Next / SvelteKit / Astro / Nuxt detected by config file)
|
|
78
|
+
const PROXY_BLOCK = `'/fenaura': { target: '${API}', changeOrigin: true, rewrite: (p) => p.replace(/^\\/fenaura/, '') }`;
|
|
79
|
+
const viteCfg = fs.readdirSync(cwd).find((f) => /^vite\.config\./.test(f));
|
|
80
|
+
const nextCfg = fs.readdirSync(cwd).find((f) => /^next\.config\./.test(f));
|
|
81
|
+
// Additional vite-based frameworks fallback: SvelteKit, Astro, Nuxt all use vite under the hood
|
|
82
|
+
const svelteCfg = !viteCfg && fs.existsSync(path.join(cwd, 'svelte.config.js')) ? 'svelte.config.js' : null;
|
|
83
|
+
const astroCfg = !viteCfg && fs.existsSync(path.join(cwd, 'astro.config.mjs')) ? 'astro.config.mjs' : null;
|
|
84
|
+
const nuxtCfg = !viteCfg && fs.existsSync(path.join(cwd, 'nuxt.config.ts')) ? 'nuxt.config.ts' : null;
|
|
85
|
+
const fallbackVite = svelteCfg || astroCfg || nuxtCfg;
|
|
86
|
+
if (viteCfg) {
|
|
87
|
+
const vp = path.join(cwd, viteCfg);
|
|
88
|
+
let src = fs.readFileSync(vp, 'utf8');
|
|
89
|
+
if (src.includes('/fenaura')) {
|
|
90
|
+
// already has proxy — correct it if API mismatched (tampered)
|
|
91
|
+
if (src.includes(API)) {
|
|
92
|
+
report.push(`kept ${viteCfg} (proxy already present)`);
|
|
93
|
+
} else {
|
|
94
|
+
// replace target URL inside the fenaura block
|
|
95
|
+
src = src.replace(/(['"]\/fenaura['"]\s*:\s*\{\s*target:\s*['"])[^'"]+(['"])/, `$1${API}$2`);
|
|
96
|
+
// also handle json-style source destination case (next) not here
|
|
97
|
+
fs.writeFileSync(vp, src);
|
|
98
|
+
report.push(`fixed ${viteCfg} (corrected tampered API target → ${API})`);
|
|
99
|
+
}
|
|
100
|
+
} else if (/server\s*:\s*\{[^}]*proxy\s*:/.test(src)) {
|
|
101
|
+
// server.proxy exists — append our entry inside it
|
|
102
|
+
src = src.replace(/(proxy\s*:\s*\{)/, `$1\n ${PROXY_BLOCK},`);
|
|
103
|
+
fs.writeFileSync(vp, src);
|
|
104
|
+
report.push(`wrote ${viteCfg} (appended to server.proxy)`);
|
|
105
|
+
} else if (/defineConfig\(\s*\{/.test(src)) {
|
|
106
|
+
// no server key — add one
|
|
107
|
+
src = src.replace(/(defineConfig\(\s*\{)/, `$1\n server: {\n proxy: {\n ${PROXY_BLOCK},\n },\n },`);
|
|
108
|
+
fs.writeFileSync(vp, src);
|
|
109
|
+
report.push(`wrote ${viteCfg} (added server.proxy)`);
|
|
110
|
+
} else {
|
|
111
|
+
report.push(`${viteCfg} has no defineConfig({...) — add manually:`);
|
|
112
|
+
report.push(` ${PROXY_BLOCK}`);
|
|
113
|
+
}
|
|
114
|
+
} else if (nextCfg) {
|
|
115
|
+
report.push('next detected — rewrites() needs an async function; add manually:');
|
|
116
|
+
report.push(` { source: '/fenaura/:path*', destination: '${API}/:path*' }`);
|
|
117
|
+
} else if (fallbackVite) {
|
|
118
|
+
// SvelteKit / Astro / Nuxt: they all proxy via vite underneath, but config shape varies
|
|
119
|
+
// For these, ensure public/_redirects covers prod and print the vite snippet for dev
|
|
120
|
+
report.push(`${fallbackVite} detected (vite-based) — add to vite server.proxy for dev:`);
|
|
121
|
+
report.push(` ${PROXY_BLOCK}`);
|
|
122
|
+
report.push(' (public/_redirects already covers prod)');
|
|
123
|
+
} else {
|
|
124
|
+
// No framework config (plain HTML/CSS/JS): wire VS Code Live Server proxy.
|
|
125
|
+
const vsDir = path.join(cwd, '.vscode');
|
|
126
|
+
const vsPath = path.join(vsDir, 'settings.json');
|
|
127
|
+
let settings = {};
|
|
128
|
+
if (fs.existsSync(vsPath)) {
|
|
129
|
+
try { settings = JSON.parse(fs.readFileSync(vsPath, 'utf8')); } catch { settings = {}; }
|
|
130
|
+
}
|
|
131
|
+
const proxy = (settings['liveServer.settings.proxy'] = settings['liveServer.settings.proxy'] || {});
|
|
132
|
+
const expectedUri = 'http://localhost:3000/fenaura';
|
|
133
|
+
if (proxy.baseUri === '/fenaura' && proxy.proxyUri === expectedUri) {
|
|
134
|
+
report.push('kept .vscode/settings.json (Live Server proxy already present)');
|
|
135
|
+
} else if (proxy.baseUri === '/fenaura') {
|
|
136
|
+
proxy.enable = true;
|
|
137
|
+
proxy.baseUri = '/fenaura';
|
|
138
|
+
proxy.proxyUri = expectedUri;
|
|
139
|
+
fs.mkdirSync(vsDir, { recursive: true });
|
|
140
|
+
fs.writeFileSync(vsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
141
|
+
report.push('fixed .vscode/settings.json (corrected tampered proxyUri)');
|
|
142
|
+
} else {
|
|
143
|
+
proxy.enable = true;
|
|
144
|
+
proxy.baseUri = '/fenaura';
|
|
145
|
+
// Via local nginx (:3000), which strips /fenaura — Live Server can't rewrite paths itself.
|
|
146
|
+
proxy.proxyUri = 'http://localhost:3000/fenaura';
|
|
147
|
+
fs.mkdirSync(vsDir, { recursive: true });
|
|
148
|
+
fs.writeFileSync(vsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
149
|
+
report.push('wrote .vscode/settings.json (Live Server proxies /fenaura to dev API)');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const wroteAny = report.some(r => r.startsWith('wrote') || r.startsWith('fixed'));
|
|
154
|
+
console.log('\n' + (wroteAny ? '✓ Fenaura auth proxy setup — done' : '✓ Fenaura auth proxy setup — already configured') + '\n' + report.map((r) => ' ' + r).join('\n'));
|
|
155
|
+
if (wroteAny) console.log('\n → You should see "wrote public/_redirects" and "wrote vercel.json" above (or "fixed …" if a tampered file was corrected). If you see "kept … (already present)" it was already configured — you are good to go.');
|
|
156
|
+
else console.log('\n → Nothing to write — proxy files were already present. You are good to go.');
|
|
157
|
+
console.log('\nFrontend calls (copy-paste, project ID from dashboard):');
|
|
158
|
+
console.log(" fetch('/fenaura/auth/dp/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ email, password, project_id: '<PROJECT_ID>' }) })");
|
|
159
|
+
|
|
160
|
+
// 4. Ensure JS intellisense for SDK in plain JS projects
|
|
161
|
+
const hasTsConfig = fs.existsSync(path.join(cwd, 'tsconfig.json'));
|
|
162
|
+
const hasJsConfig = fs.existsSync(path.join(cwd, 'jsconfig.json'));
|
|
163
|
+
if (!hasTsConfig && !hasJsConfig) {
|
|
164
|
+
const jsconfig = {
|
|
165
|
+
compilerOptions: {
|
|
166
|
+
target: "ES2020",
|
|
167
|
+
module: "ESNext",
|
|
168
|
+
moduleResolution: "bundler",
|
|
169
|
+
allowJs: true,
|
|
170
|
+
checkJs: false,
|
|
171
|
+
jsx: "react-jsx",
|
|
172
|
+
noImplicitAny: false
|
|
173
|
+
},
|
|
174
|
+
include: ["src"]
|
|
175
|
+
};
|
|
176
|
+
try {
|
|
177
|
+
fs.writeFileSync(path.join(cwd, 'jsconfig.json'), JSON.stringify(jsconfig, null, 2) + '\n');
|
|
178
|
+
report.push('wrote jsconfig.json (enables SDK intellisense for JS)');
|
|
179
|
+
} catch {}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 5. Ensure SDK is installed if run via npx (one-command experience)
|
|
183
|
+
try {
|
|
184
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
185
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
186
|
+
const hasSdk = deps['@fenaura/client'] || deps['@fenaura/sdk'] || deps['fenaura'];
|
|
187
|
+
if (!hasSdk) {
|
|
188
|
+
console.log('\n → SDK not in package.json — installing @fenaura/sdk ...');
|
|
189
|
+
const { spawnSync } = require('child_process');
|
|
190
|
+
const res = spawnSync('npm', ['install', '@fenaura/sdk', '--save'], { stdio: 'inherit', cwd });
|
|
191
|
+
if (res.status !== 0) console.log(' (auto-install failed — run: npm install @fenaura/sdk)');
|
|
192
|
+
else console.log(' ✓ @fenaura/client installed');
|
|
193
|
+
}
|
|
194
|
+
} catch { /* no package.json or parse fail — skip auto-install */ }
|
|
195
|
+
|
|
196
|
+
// 6. Detect how the dev server is running + print next steps.
|
|
197
|
+
const net = require('net');
|
|
198
|
+
function portOpen(port) {
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
const s = net.connect(port, '127.0.0.1');
|
|
201
|
+
s.on('connect', () => { s.end(); resolve(true); });
|
|
202
|
+
s.on('error', () => resolve(false));
|
|
203
|
+
setTimeout(() => { s.destroy(); resolve(false); }, 800);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
(async () => {
|
|
207
|
+
const stack = viteCfg ? 'vite' : nextCfg ? 'next' : 'live-server';
|
|
208
|
+
const startCmd = stack === 'vite' || stack === 'next' ? 'npm run dev' : 'Go Live (VS Code status bar)';
|
|
209
|
+
const defaults = stack === 'vite' ? [5173] : stack === 'next' ? [3000] : [5500];
|
|
210
|
+
const open = [];
|
|
211
|
+
for (const p of [...defaults, 5173, 5174, 5199, 5500, 5501, 3000, 8080]) {
|
|
212
|
+
if (await portOpen(p)) open.push(p);
|
|
213
|
+
}
|
|
214
|
+
console.log('\nNext steps:');
|
|
215
|
+
console.log(` 1. Start dev: ${startCmd} (detected stack: ${stack})`);
|
|
216
|
+
console.log(` 2. Open the page (local ports answering now: ${open.length ? open.join(', ') : 'none yet — start the server first'})`);
|
|
217
|
+
console.log(' 3. Click login — expect session_minted + a fenaura_eusid_* cookie on YOUR domain (devtools → Application → Cookies).');
|
|
218
|
+
console.log(' 4. Public URL test: forward the dev port in VS Code (Ports → Public, protocol http) and open the tunnel link.');
|
|
219
|
+
console.log(' 5. Deploy: the _redirects/vercel.json files proxy /fenaura/* in prod — no code changes.\n');
|
|
220
|
+
})();
|
|
221
|
+
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
main().catch((e) => { console.error(e.message); process.exit(1); });
|