create-singlestoredb-app
Advanced tools
Comparing version 1.0.4 to 2.0.0
const axios = require("axios"); | ||
const BASE_URL = "https://api.singlestore.com"; | ||
const BASE_URL = "https://shell.singlestore.com/api"; | ||
const POLL_INTERVAL_SECONDS = 10; | ||
async function create(appName, key) { | ||
console.log("create workspace with key:", key); | ||
// const organization = await getCurrentOrganization(key); | ||
// console.log({ organization }); | ||
const regions = await getRegions(key); | ||
// console.log({ regions }); | ||
const workspaceGroups = await getWorkspaceGroups(key); | ||
// console.log({ workspaceGroups }); | ||
let workspaceGroupName = `${appName}-workspace-group`; | ||
if (workspaceGroups.length === 0) { | ||
workspaceGroupName += `-1`; | ||
} else { | ||
workspaceGroupName += `-${workspaceGroups.length + 1}`; | ||
} | ||
// create workspace group with random password | ||
const workspaceGroup = await createWorkspaceGroup({ | ||
key, | ||
name: workspaceGroupName, | ||
regionID: regions[0].regionID, | ||
}); | ||
// console.log({ workspaceGroup }); | ||
// get workspace group ID from the response | ||
const { workspaceGroupID, adminPassword } = workspaceGroup; | ||
// create workspace with the Workspace group ID | ||
let workspace = await createWorkspace({ | ||
key, | ||
name: `${appName}-workspace-1`, | ||
workspaceGroupID, | ||
}); | ||
// console.log({ workspace }); | ||
// get workspace ID | ||
const { workspaceID } = workspace; | ||
const startTime = new Date(); | ||
const workspaceActive = await new Promise((resolve) => { | ||
const workspacePollInterval = setInterval(async () => { | ||
const endTime = new Date(); | ||
const elapsedSeconds = Math.round((endTime - startTime) / 1000); | ||
console.log( | ||
`Waiting for the workspace to be active... ${elapsedSeconds} seconds` | ||
); | ||
workspace = await getWorkspace({ key, workspaceID }); | ||
// console.log({ workspace }); | ||
if (workspace.state === "ACTIVE") { | ||
clearInterval(workspacePollInterval); | ||
console.log("Your workspace is active!"); | ||
resolve(workspace); | ||
} | ||
}, POLL_INTERVAL_SECONDS * 1000); | ||
}); | ||
// get hostname | ||
return { | ||
endpoint: workspaceActive.endpoint, | ||
password: adminPassword, | ||
}; | ||
// populate connection with hostname and password | ||
} | ||
async function getRegions(key) { | ||
async function create() { | ||
try { | ||
const response = await axios({ | ||
method: "GET", | ||
url: "/v1/regions", | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
async function getCurrentOrganization(key) { | ||
try { | ||
const response = await axios({ | ||
method: "GET", | ||
url: "/v1/organizations/current", | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
async function getWorkspaceGroups(key) { | ||
try { | ||
const response = await axios({ | ||
method: "GET", | ||
url: "/v1/workspaceGroups", | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
throw error; | ||
} | ||
} | ||
async function createWorkspaceGroup({ key, name, regionID }) { | ||
try { | ||
const response = await axios({ | ||
method: "POST", | ||
url: "/v1/workspaceGroups", | ||
url: "/session", | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
data: { | ||
name, | ||
regionID, | ||
firewallRanges: ["0.0.0.0/0"], | ||
}, | ||
}); | ||
@@ -143,41 +20,4 @@ | ||
async function getWorkspace({ key, workspaceID }) { | ||
try { | ||
const response = await axios({ | ||
method: "GET", | ||
url: `/v1/workspaces/${workspaceID}`, | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
params: { | ||
fields: "name,size,state,workspaceGroupID,workspaceID,createdAt,endpoint", | ||
}, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
async function createWorkspace({ key, name, workspaceGroupID }) { | ||
try { | ||
const response = await axios({ | ||
method: "POST", | ||
url: "/v1/workspaces", | ||
baseURL: BASE_URL, | ||
headers: { Authorization: `Bearer ${key}` }, | ||
data: { | ||
name, | ||
workspaceGroupID, | ||
}, | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
module.exports = { | ||
create, | ||
}; |
236
bin/start.js
#! /usr/bin/env node | ||
const axios = require("axios"); | ||
var createWorkspace = require("./create-workspace"); | ||
const { execSync } = require("child_process"); | ||
const { Worker, isMainThread, parentPort, workerData } = require("worker_threads"); | ||
const { options } = require("./commander"); | ||
const { isValidTemplateName, handleTemplate } = require("./templates"); | ||
const prompts = require("prompts"); | ||
async function setupConnection(hostname, password) { | ||
function execCommand(cmd) { | ||
try { | ||
const response = await axios({ | ||
method: "POST", | ||
url: "/setup", | ||
baseURL: "http://localhost:3000", | ||
data: { | ||
hostname, | ||
password | ||
}, | ||
headers: { "Content-Type": "application/json" } | ||
execSync(cmd, { | ||
stdio: "inherit" | ||
}); | ||
return response.data; | ||
} catch (error) { | ||
console.error(JSON.stringify(error)); | ||
console.error(error); | ||
} | ||
} | ||
if (options.template) { | ||
if (!isValidTemplateName(options.template)) { | ||
console.error("Invalid template name"); | ||
process.exit(1); | ||
function execCommandInApp(cmd, appName) { | ||
try { | ||
execSync(cmd, { | ||
cwd: `./${appName}`, | ||
stdio: "inherit" | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
} | ||
introMessage(`Creating a SingleStore application with ${options.template} template`); | ||
return handleTemplate(options.template); | ||
function runEstoreApp({ appName, envFileCommand }) { | ||
// TODO: remove once MR is approved | ||
execCommand(`git clone https://github.com/singlestore-labs/estore.git --branch hackathon-summer-2024 --single-branch ${appName}`); | ||
execCommandInApp(envFileCommand, appName); | ||
execCommandInApp("npm i", appName); | ||
execCommandInApp("npm run start:data", appName); | ||
execCommandInApp("npm run dev", appName); | ||
console.log("Your app is now ready!"); | ||
} | ||
if (isMainThread) { | ||
const threads = new Set(); | ||
function createNextApp({ appName, envFileCommand }) { | ||
// TODO: remove once MR is approved | ||
execCommand(`npx --yes create-next-app@latest ${appName} --example https://github.com/singlestore-labs/elegance-sdk-template-next/tree/hackathon-summer-2024`); | ||
execCommandInApp(envFileCommand, appName); | ||
execCommandInApp("npm run dev", appName); | ||
} | ||
if (process.argv.length < 3) { | ||
console.error("Please enter the name of your new app"); | ||
process.exit(1); | ||
} | ||
function createRemixApp({ appName, envFileCommand }) { | ||
execCommand(`npx --yes create-remix@latest ${appName} --template singlestore-labs/elegance-sdk-template-remix`) | ||
execCommandInApp(envFileCommand, appName); | ||
execCommandInApp("npm run dev", appName); | ||
} | ||
const appName = process.argv.slice(2, 3)[0]; | ||
function createExpressApp({ appName, envFileCommand }) { | ||
// TODO: remove once MR is approved | ||
execCommand(`git clone https://github.com/singlestore-labs/elegance-sdk-template-express.git --branch hackathon-summer-2024-2 --single-branch ${appName}`); | ||
execCommandInApp("rm -rf .git", appName); | ||
execCommandInApp(envFileCommand, appName); | ||
execCommandInApp("npm i", appName); | ||
execCommandInApp("npm run dev", appName); | ||
} | ||
if (process.argv.length < 4) { | ||
console.error( | ||
"Please enter your key. More info on https://docs.singlestore.com/managed-service/en/reference/management-api.html#authorization" | ||
); | ||
process.exit(1); | ||
} | ||
async function startMainThread() { | ||
const { appName } = await prompts( | ||
{ | ||
type: "text", | ||
name: "appName", | ||
message: "What is your project named?", | ||
initial: "my-singlestore-app" | ||
}, | ||
{ onCancel: () => process.exit(1) } | ||
); | ||
const key = process.argv.slice(3, 4)[0]; | ||
threads.add(new Worker(__filename, { workerData: { type: "workspace", appName, key } })); | ||
threads.add(new Worker(__filename, { workerData: { type: "app", appName, key } })); | ||
const { flow } = await prompts( | ||
{ | ||
type: "select", | ||
name: "flow", | ||
message: "What would you like to create?", | ||
choices: [ | ||
{ title: "Full built demo!", value: "demo" }, | ||
{ title: "Build my own app", value: "app" } | ||
], | ||
initial: 0 | ||
}, | ||
{ onCancel: () => process.exit(1) } | ||
); | ||
for (let worker of threads) { | ||
worker.on("error", err => { | ||
throw err; | ||
}); | ||
worker.on("exit", () => { | ||
threads.delete(worker); | ||
}); | ||
worker.on("message", msg => { | ||
if (msg.type === "exit") { | ||
threads.delete(worker); | ||
process.exit(1); | ||
} | ||
console.log(msg); | ||
}); | ||
} | ||
} else { | ||
// This code is executed in the worker and not in the main thread. | ||
const { demo } = await prompts( | ||
{ | ||
type: _prev => flow === 'demo' ? 'select' : null, | ||
name: "demo", | ||
message: "What demo would you like to try?", | ||
choices: [ | ||
{ title: "Gen AI store", value: "store" } | ||
], | ||
initial: 0 | ||
}, | ||
{ | ||
onCancel: () => process.exit(1), | ||
if (workerData.type === "app") { | ||
introMessage(`starting *${workerData.appName}*: an awesome app powered by SingleStore!`); | ||
try { | ||
execSync(`git clone https://github.com/singlestore-labs/singlestore-app-boilerplate.git ${workerData.appName}`); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
); | ||
const { framework } = await prompts( | ||
{ | ||
type: _prev => flow === 'app' ? 'select' : null, | ||
name: "framework", | ||
message: "What framework would you like to use?", | ||
choices: [ | ||
{ title: "Next.js", value: "next" }, | ||
{ title: "Express", value: "express" }, | ||
{ title: "Remix", value: "remix" } | ||
], | ||
initial: 0 | ||
}, | ||
{ | ||
onCancel: () => process.exit(1), | ||
try { | ||
execSync(`npm install`, { | ||
cwd: `./${workerData.appName}`, | ||
stdio: "inherit" | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
); | ||
const { endpoint, user, password, databaseName } = await createWorkspace.create(); | ||
try { | ||
execSync(`npm run dev`, { | ||
cwd: `./${workerData.appName}`, | ||
stdio: "inherit" | ||
}); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
const envFileCommand = `echo " | ||
DB_HOST=${endpoint} | ||
DB_USER=${user} | ||
DB_PASSWORD=${password} | ||
DB_NAME=${databaseName} | ||
DB_PORT=3333 | ||
TIER=shared | ||
" > .env` | ||
parentPort.postMessage("Your app is now ready!"); | ||
} else if (workerData.type === "workspace") { | ||
(async () => { | ||
try { | ||
console.log("start creating your workspace..."); | ||
const { endpoint, password } = await createWorkspace.create(workerData.appName, workerData.key); | ||
await setupConnection(endpoint, password); | ||
parentPort.postMessage("Your workspace is ready!"); | ||
} catch (error) { | ||
console.error(`Error ${error.response.status}: ${error.response.data}`); | ||
parentPort.postMessage({ type: "exit" }); | ||
process.exit(1); | ||
} | ||
})(); | ||
if (flow === "demo" && demo === "store") { | ||
runEstoreApp({ appName, endpoint, envFileCommand }); | ||
} else if (flow === "app" && framework === "next") { | ||
createNextApp({ appName, endpoint, envFileCommand }); | ||
} else if (flow === "app" && framework === "express") { | ||
const expressEnvFileCommand = `echo " | ||
REACT_APP_DB_HOST=${endpoint} | ||
REACT_APP_DB_USER=${user} | ||
REACT_APP_DB_PASSWORD=${password} | ||
REACT_APP_DB_NAME=${databaseName} | ||
REACT_APP_DB_PORT=3333 | ||
REACT_APP_TIER=shared | ||
" > .env` | ||
createExpressApp({ appName, endpoint, envFileCommand: expressEnvFileCommand }); | ||
} else if (flow === "app" && framework === "remix") { | ||
createRemixApp({ appName, endpoint, envFileCommand }); | ||
} | ||
} | ||
function introMessage(message = "") { | ||
console.log(` | ||
oo | ||
ooOOOOOo oOo | ||
ooOOOOOOOOOOOOOOo oOOOo | ||
oOOOOOOOOOOOOOo oOOOOo oOOOOo | ||
oOOOOOOOOOo oOo oOOOOo | ||
oOOOOOOOOo o oOOOOo | ||
oOOOOOOOO oOOOOOo | ||
oOOOOOOOO oOOOOOo | ||
oOOOOOOOO oOOOOOo | ||
oOOOOOOOO oOOOOOOo | ||
oOOOOOOOOO oOOOOOOOOo | ||
oOOOOOOOOOo oOOOOOOOOo | ||
oOOOOOOOOOOo oOOOOOOOOOOo | ||
oOOOOOOOOOOOOOOoo oOOOOOOOOOOOOOoo | ||
oOOOOOOOOOOOOOOOOOOOOOOOOOOOo | ||
ooOOOOOOOOOOOOOOOOOOOoo | ||
ooooOOOOOOoooo | ||
${message} | ||
`); | ||
} | ||
startMainThread(); |
{ | ||
"name": "create-singlestoredb-app", | ||
"version": "1.0.4", | ||
"description": "A boilerplate to build a web application connected with SingleStoreDB.", | ||
"version": "2.0.0", | ||
"description": "Create boilerplate templates or full demos connected to SingleStore!", | ||
"main": "index.js", | ||
@@ -6,0 +6,0 @@ "scripts": { |
# create-singlestoredb-app | ||
**Attention**: The code in this repository is intended for experimental use only and is not fully tested, documented, or supported by SingleStore. Visit the [SingleStore Forums](https://www.singlestore.com/forum/) to ask questions about this repository. | ||
This package includes a tool to create a demo application powered by SingleStoreDB (https://www.singlestore.com/). | ||
@@ -7,29 +9,18 @@ | ||
1. Sign up for a free trial account at https://portal.singlestore.com | ||
2. Go to API Keys page (last section of the sidebar) and [create your API key](https://docs.singlestore.com/managed-service/en/reference/management-api.html#authorization). | ||
3. Open your terminal and run: | ||
1. Open your terminal and run: | ||
```sh | ||
npx create-singlestoredb-app <YOUR_APP_NAME> <YOUR_API_KEY> | ||
npx create-singlestoredb-app | ||
``` | ||
2. Answer the prompts and choose between full built demos or templates based on Next.js, Express, or Remix! | ||
4- Your app will be up and running on http://localhost:3000, powered by SingleStoreDB! | ||
3. Follow the instructions in your terminal and your app will be up and running in a few seconds! | ||
### How to start using the template? | ||
1. Sign up for a free trial account at https://portal.singlestore.com | ||
2. Open your terminal and run: | ||
### What will this tool do for me? | ||
This tool creates a starter workspace (free!) at SingleStoreDB Cloud to power your application or demo. The created application will be connected to your workspace through the `.env` file. You can also connect through mysql protocol: | ||
```sh | ||
npx create-singlestoredb-app --template <elegance-next|elegance-express> | ||
mysql -u <user> -h <hostnmae> -P 3333 --default-auth=mysql_native_password --password=<password> <db_name> | ||
``` | ||
3. Open the application in a code editor and follow the instructions in the readme file. | ||
### What will this tool do for me? | ||
This tool creates a [Workspace](https://docs.singlestore.com/managed-service/en/getting-started-with-singlestoredb-cloud/about-workspaces/what-is-a-workspace.html) at SingleStoreDB Cloud to power your application while it sets up a demo app for you. When your Workspace is ready to be used, it sets up a secured connection to your application and creates a database `shop` and two tables: `item` and `sales`, so your demo works right away. We provide the API calls to remove them or to create new ones. You can build up on its structure. | ||
### What app can I expect to be running? | ||
The app created by this tool is a React + TypeScript + Express + SingleStoreDB Demo app. The app is started with a [boilerplate](https://github.com/singlestore-labs/singlestore-app-boilerplate), and you can count with packages like MaterialUI, Formik, YUP, react-chartjs-2 and others. We have a REST API integrated with SingleStoreDB structure done for you. It has everything already configurated and integrated so you can start implementing your idea right away. Happy coding! |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 4 instances in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
0
0
2
6813
5
157
26