Files
upstream-atomic-crm/scripts/supabase-remote-init.mjs
upstream-mirror 93ecc1a6f8
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

341 lines
7.9 KiB
JavaScript

import { input, select } from "@inquirer/prompts";
import { execa } from "execa";
import fs from "node:fs";
(async () => {
await loginToSupabase();
const projectName = await input({
message: "Enter the name of the project:",
default: "Atomic CRM",
});
const databasePassword = await input({
message: "Enter a database password:",
default: generatePassword(16),
});
const organizationId = await selectOrganization();
const region = await selectRegion();
const projectRef = await createProject({
projectName,
databasePassword,
organizationId,
region,
});
await waitForProjectToBeReady({ projectRef });
// This also ensures the project is ready
const { publishableKey } = await fetchApiKeys({
projectRef,
});
await linkProject({
projectRef,
databasePassword,
});
await setupDatabase({
databasePassword,
});
await setupSupabaseSecrets({
projectRef,
publishableKey,
});
await persistSupabaseEnv({
projectRef,
publishableKey,
});
})();
async function loginToSupabase() {
await execa("npx", ["supabase", "login"], { stdio: "inherit" });
}
async function createProject({
projectName,
databasePassword,
organizationId,
region,
}) {
const { stdout } = await execa(
"npx",
[
"supabase",
"projects",
"create",
"--output",
"json",
"--db-password",
databasePassword,
"--region",
region,
...(organizationId ? ["--org-id", organizationId] : []),
projectName,
],
{
stdin: "pipe",
stdout: "pipe",
},
);
try {
const matchJSON = stdout.match(new RegExp("{.*}", "s"));
if (!matchJSON) {
throw new Error("Invalid JSON output");
}
const jsonOuput = JSON.parse(matchJSON[0]);
return jsonOuput.id;
} catch (e) {
console.error("Failed to create project");
console.error(e);
throw e;
}
}
async function selectOrganization() {
const { stdout: organizationsJson } = await execa(
"npx",
["supabase", "orgs", "list", "--output", "json"],
{
stdout: "pipe",
},
);
const organizations = JSON.parse(organizationsJson);
if (organizations.length === 0) {
return null;
}
if (organizations.length === 1) {
return organizations[0].id;
}
const selectedOrganizationId = await select({
message: "Select an organization to create the project in:",
choices: organizations.map((org) => ({
name: org.name,
value: org.id,
})),
});
return selectedOrganizationId;
}
const regions = [
{ value: "us-west-1", name: "West US (North California)" },
{ value: "us-west-2", name: "West US (Oregon)" },
{ value: "us-east-1", name: "East US (North Virginia)" },
{ value: "us-east-2", name: "East US (Ohio)" },
{ value: "ca-central-1", name: "Canada (Central)" },
{ value: "eu-west-1", name: "West EU (Ireland)" },
{ value: "eu-west-2", name: "West Europe (London)" },
{ value: "eu-west-3", name: "West EU (Paris)" },
{ value: "eu-central-1", name: "Central EU (Frankfurt)" },
{ value: "eu-central-2", name: "Central Europe (Zurich)" },
{ value: "eu-north-1", name: "North EU (Stockholm)" },
{ value: "ap-south-1", name: "South Asia (Mumbai)" },
{ value: "ap-southeast-1", name: "Southeast Asia (Singapore)" },
{ value: "ap-northeast-1", name: "Northeast Asia (Tokyo)" },
{ value: "ap-northeast-2", name: "Northeast Asia (Seoul)" },
{ value: "ap-southeast-2", name: "Oceania (Sydney)" },
{ value: "sa-east-1", name: "South America (São Paulo)" },
];
async function selectRegion() {
const selectedRegion = await select({
message:
"Select a region for the project (close to you for best performance):",
choices: regions.map((region) => ({
name: region.name,
value: region.value,
})),
});
return selectedRegion;
}
async function waitForProjectToBeReady({ projectRef }) {
// eslint-disable-next-line no-console
console.log("Waiting for project to be ready...");
const { stdout } = await execa(
"npx",
["supabase", "projects", "list", "--output", "json"],
{
stdout: "pipe",
},
);
try {
// The response is an Array of objects or null if there are no projects
const matchJSON =
stdout === null ? "[]" : stdout.match(new RegExp("\\[.*\\]", "s"));
if (!matchJSON) {
throw new Error("Invalid JSON output");
}
const jsonOuput = JSON.parse(matchJSON[0]);
const project = jsonOuput.find((project) => project.id === projectRef);
if (project?.status !== "ACTIVE_HEALTHY") {
await sleep(1000);
return waitForProjectToBeReady({ projectRef });
}
} catch (e) {
console.error("Failed to create project");
console.error(e);
throw e;
}
}
let retry = 0;
async function linkProject({ projectRef, databasePassword }) {
await execa(
"npx",
[
"supabase",
"link",
"--project-ref",
projectRef,
"--password",
databasePassword,
],
{
stdout: "ignore",
stderr: "ignore",
},
).catch(() => {
retry++;
if (retry === 1) {
// eslint-disable-next-line no-console
console.log("Waiting for project to be ready...");
}
return sleep(1000).then(() =>
linkProject({ projectRef, databasePassword }),
);
});
}
async function setupDatabase({ databasePassword }) {
await execa(
"npx",
[
"supabase",
"db",
"push",
"--linked",
"--include-roles",
"--include-seed",
"--password",
databasePassword,
],
{
stdio: "inherit",
},
);
}
async function fetchApiKeys({ projectRef }) {
let publishableKey = "";
try {
const { stdout, exitCode } = await execa(
"npx",
[
"supabase",
"projects",
"api-keys",
"--output",
"json",
"--project-ref",
projectRef,
],
{
stdout: "pipe",
stderr: "ignore",
},
);
// If the exitCode is not 0, the command failed most probably because the project is not ready
if (exitCode === 0) {
// The response is an Array of objects
const matchJSON = stdout.match(new RegExp("\\[.*\\]", "s"));
if (!matchJSON) {
throw new Error("Invalid JSON output");
}
const jsonOutput = JSON.parse(matchJSON[0]);
// Prioritize the default publishable key, but any publishable key will work.
publishableKey = jsonOutput.find(
(key) => key.type === "publishable" && key.name === "default",
)?.api_key;
if (!publishableKey) {
publishableKey = jsonOutput.find(
(key) => key.type === "publishable",
)?.api_key;
}
}
} catch (e) {
console.error("Failed to fetch API keys");
console.error(e);
throw e;
}
if (publishableKey === "") {
await sleep(1000);
return fetchApiKeys({ projectRef });
}
return { publishableKey };
}
async function setupSupabaseSecrets({ projectRef, publishableKey }) {
await execa(
"npx",
[
"supabase",
"secrets",
"set",
`SB_PUBLISHABLE_KEY=${publishableKey}`,
"--project-ref",
projectRef,
],
{
stdio: "inherit",
},
);
}
async function persistSupabaseEnv({ projectRef, publishableKey }) {
fs.writeFileSync(
`${process.cwd()}/.env.production.local`,
`
VITE_SUPABASE_URL=https://${projectRef}.supabase.co
VITE_SB_PUBLISHABLE_KEY=${publishableKey}`,
{ flag: "a" },
);
}
function generatePassword(length) {
const password = crypto
.getRandomValues(new BigUint64Array(4))
.reduce(
(prev, curr, index) =>
(!index ? prev : prev.toString(36)) +
(index % 2 ? curr.toString(36).toUpperCase() : curr.toString(36)),
)
.split("")
.sort(() => 128 - crypto.getRandomValues(new Uint8Array(1))[0])
.join("");
if (length) {
return password.slice(0, length);
}
return password;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}