Skip to content

Commit 687e714

Browse files
committedAug 2, 2024
Feat(supabase): Improve init script
1 parent d55c437 commit 687e714

8 files changed

+2187
-1858
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ yarn-error.log*
2424
supabase/.branches
2525
supabase/.temp
2626

27+
dist/
2728
.env

‎.nvmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v20

‎makefile

+8-5
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ install: package.json ## install dependencies
99
start-supabase: ## start supabase locally
1010
npx supabase start
1111

12-
start-functions:
13-
npx supabase functions serve
14-
1512
start-app: ## start the app locally
1613
npm run dev
1714

18-
start: start-supabase start-app start-functions## start the stack locally
15+
start: start-supabase start-app## start the stack locally
1916

2017
stop-supabase: ## stop local supabase
2118
npx supabase stop
2219

2320
stop: stop-supabase ## stop the stack locally
2421

2522
build: ## build the app
26-
npm run build
23+
npm run build
24+
25+
start-prod: build
26+
open http://127.0.0.1:3000 && npx serve -l tcp://127.0.0.1:3000 dist
27+
28+
supabase-remote-init:
29+
npm run supabase:remote:init

‎package-lock.json

+1,980-1,612
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@nivo/bar": "^0.87.0",
1111
"@nivo/core": "^0.87.0",
1212
"@supabase/supabase-js": "^2.39.3",
13+
"@types/jsonexport": "^3.0.5",
1314
"clsx": "^2.1.0",
1415
"date-fns": "^3.3.1",
1516
"dotenv": "^16.4.1",
@@ -23,15 +24,18 @@
2324
"react-dom": "^18.2.0",
2425
"react-error-boundary": "^4.0.12",
2526
"react-router": "^6.21.3",
26-
"react-router-dom": "^6.21.3"
27+
"react-router-dom": "^6.21.3",
28+
"serve": "^14.2.3"
2729
},
2830
"devDependencies": {
2931
"@inquirer/prompts": "^5.3.2",
3032
"@testing-library/jest-dom": "^6.3.0",
3133
"@testing-library/react": "^16.0.0",
3234
"@testing-library/user-event": "^14.5.2",
3335
"@types/jsonexport": "^3.0.5",
36+
"@types/jest": "^29.5.11",
3437
"@types/lodash": "^4.17.7",
38+
"@types/papaparse": "^5.3.14",
3539
"@types/pg": "^8.11.0",
3640
"@types/react": "^18.2.48",
3741
"@types/react-dom": "^18.2.18",
@@ -57,8 +61,8 @@
5761
"dev": "vite --force",
5862
"build": "tsc && vite build",
5963
"preview": "vite preview",
60-
"deploy": "node ./scripts/deploy.mjs",
6164
"lint": "eslint --ext .js,.ts,.tsx \"./src/**/*.{js,ts,tsx}\"",
62-
"prettier": "prettier --config ./.prettierrc.mjs --write --list-different \"src/**/*.{js,json,ts,tsx,css,md}\""
65+
"prettier": "prettier --config ./.prettierrc.mjs --write --list-different \"src/**/*.{js,json,ts,tsx,css,md}\"",
66+
"supabase:remote:init": "node ./scripts/supabase-remote-init.mjs"
6367
}
6468
}

‎scripts/deploy.mjs

-237
This file was deleted.

‎scripts/supabase-remote-init.mjs

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { input } from '@inquirer/prompts';
2+
import { execa } from 'execa';
3+
import fs from 'node:fs';
4+
5+
(async () => {
6+
await loginToSupabase();
7+
const projectName = await input({
8+
message: 'Enter the name of the project:',
9+
default: 'Atomic CRM',
10+
});
11+
const databasePassword = await input({
12+
message: 'Enter a database password:',
13+
default: generatePassword(16),
14+
});
15+
16+
const projectRef = await createProject({ projectName, databasePassword });
17+
18+
// This also ensures the project is ready
19+
const { anonKey } = await fetchApiKeys({
20+
projectRef,
21+
});
22+
23+
await linkProject({
24+
projectRef,
25+
databasePassword,
26+
});
27+
28+
await setupDatabase({
29+
databasePassword,
30+
});
31+
32+
await persistSupabaseEnv({
33+
projectRef,
34+
anonKey,
35+
});
36+
})();
37+
38+
async function loginToSupabase() {
39+
await execa('npx', ['supabase', 'login'], { stdio: 'inherit' });
40+
}
41+
42+
async function createProject({ projectName, databasePassword }) {
43+
const { stdout } = await execa(
44+
'npx',
45+
[
46+
'supabase',
47+
'projects',
48+
'create',
49+
'--interactive',
50+
'--db-password',
51+
databasePassword,
52+
projectName,
53+
],
54+
{
55+
stdin: 'inherit',
56+
stdout: ['inherit', 'pipe'],
57+
}
58+
);
59+
60+
const projectRef = stdout.match(
61+
/https:\/\/supabase.com\/dashboard\/project\/([a-zA-Z0-9_-]*)/
62+
)[1];
63+
64+
return projectRef;
65+
}
66+
67+
let retry = 0;
68+
async function linkProject({ projectRef, databasePassword }) {
69+
await execa(
70+
'npx',
71+
[
72+
'supabase',
73+
'link',
74+
'--project-ref',
75+
projectRef,
76+
'--password',
77+
databasePassword,
78+
],
79+
{
80+
stdout: 'inherit',
81+
stderr: 'ignore',
82+
}
83+
).catch(() => {
84+
retry++;
85+
if (retry === 1) {
86+
console.log('Waiting for project to be ready...');
87+
}
88+
return sleep(1000).then(() =>
89+
linkProject({ projectRef, databasePassword })
90+
);
91+
});
92+
}
93+
94+
async function setupDatabase({ databasePassword }) {
95+
await execa(
96+
'npx',
97+
[
98+
'supabase',
99+
'db',
100+
'push',
101+
'--linked',
102+
'--include-roles',
103+
'--password',
104+
databasePassword,
105+
],
106+
{
107+
stdio: 'inherit',
108+
}
109+
);
110+
}
111+
112+
async function fetchApiKeys({ projectRef }) {
113+
let anonKey = '';
114+
115+
try {
116+
const { stdout } = await execa(
117+
'npx',
118+
['supabase', 'projects', 'api-keys', '--project-ref', projectRef],
119+
{
120+
stderr: 'ignore',
121+
}
122+
);
123+
124+
const keys = stdout
125+
.trim()
126+
.match(/[ ]+([a-z_]*)[ ]+[ ]+([a-zA-Z0-9._-]+)/gm);
127+
128+
for (const key of keys) {
129+
const [name, value] = key.split('│').map(s => s.trim());
130+
if (name === 'anon') {
131+
anonKey = value;
132+
}
133+
}
134+
} catch (e) {}
135+
136+
if (anonKey === '') {
137+
await sleep(1000);
138+
return fetchApiKeys({ projectRef });
139+
}
140+
141+
return { anonKey };
142+
}
143+
144+
async function persistSupabaseEnv({ projectRef, anonKey }) {
145+
let envFileStream = fs.createWriteStream(
146+
`${process.cwd()}/.env.production.local`,
147+
{ flags: 'a' }
148+
);
149+
await execa(
150+
'echo',
151+
[`VITE_SUPABASE_URL=https://${projectRef}.supabase.co`],
152+
{
153+
stdout: [envFileStream, 'pipe'],
154+
}
155+
);
156+
envFileStream = fs.createWriteStream(
157+
`${process.cwd()}/.env.production.local`,
158+
{ flags: 'a' }
159+
);
160+
await execa('echo', [`VITE_SUPABASE_ANON_KEY=${anonKey}`], {
161+
stdout: [envFileStream, 'pipe'],
162+
});
163+
}
164+
165+
function generatePassword(length) {
166+
const password = crypto
167+
// eslint-disable-next-line no-undef
168+
.getRandomValues(new BigUint64Array(4))
169+
.reduce(
170+
(prev, curr, index) =>
171+
(!index ? prev : prev.toString(36)) +
172+
(index % 2
173+
? curr.toString(36).toUpperCase()
174+
: curr.toString(36))
175+
)
176+
.split('')
177+
.sort(() => 128 - crypto.getRandomValues(new Uint8Array(1))[0])
178+
.join('');
179+
180+
if (length) {
181+
return password.slice(0, length);
182+
}
183+
184+
return password;
185+
}
186+
187+
function sleep(ms) {
188+
return new Promise(resolve => setTimeout(resolve, ms));
189+
}

‎tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "ES2015",
3+
"target": "ES2019",
44
"lib": ["dom", "dom.iterable", "esnext"],
55
"allowJs": true,
66
"skipLibCheck": true,

0 commit comments

Comments
 (0)
Please sign in to comment.