Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/flat-tables-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@minima-global/create-minima-app": patch
"@minima-global/minima-cli": patch
---

improvements

- fixed issue where configuring debugg setting would error due to
pupeter not finding "Click anywhere to continue"
- added env support for port in minima-cli
2 changes: 1 addition & 1 deletion packages/create-minima-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
import { Command } from "commander"
import packageJson from "../package.json"
import packageJson from "../package.json" with { type: "json" }
import { init } from "./commands/init.js"

process.on("SIGINT", () => process.exit(0))
Expand Down
280 changes: 216 additions & 64 deletions packages/create-minima-app/src/utils/setup-debug-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DappLink, MDSObj } from "@minima-global/mds"
import chalk from "chalk"
import fs from "fs/promises"
import path from "path"
import puppeteer from "puppeteer"
import puppeteer, { Browser, Page } from "puppeteer"
import { z } from "zod"
import { getRunCommand, type PackageManager } from "./get-package-manager.js"
import { logger } from "./logger.js"
Expand All @@ -23,101 +23,253 @@ const DEBUGGING_SCHEMA = z.object({
template: z.string(),
})

export async function setupDebugConfig(
options: z.infer<typeof DEBUGGING_SCHEMA>
) {
type DebugOptions = z.infer<typeof DEBUGGING_SCHEMA>

/**
* Sets up debug configuration for a Minima app
*/
export async function setupDebugConfig(options: DebugOptions) {
const debuggingSpinner = spinner("Configuring debug settings...").start()

const values = DEBUGGING_SCHEMA.parse(options)
try {
const values = DEBUGGING_SCHEMA.parse(options)

// Launch browser and navigate to Minima node
const browser = await launchBrowser()
const page = await setupBrowserPage(browser, values.host, values.port)

// Login to Minima node
await loginToMinimaNode(page, values.password, debuggingSpinner)

// Handle fresh node if needed
await handleFreshNodeIfNeeded(page, debuggingSpinner)

// Get session ID from MDS
const sessionID = await getSessionIDFromMDS(page)

// Close browser
await browser.close()

// Update configuration files
await updateConfigFiles(values, sessionID)

// Display success message
displaySuccessMessage(values, debuggingSpinner)

const browser = await puppeteer.launch({
process.exit(0)
} catch (error: unknown) {
const err = error instanceof Error ? error : new Error(String(error))
debuggingSpinner.fail("Debug configuration failed")
logger.error(`Error: ${err.message}`)
process.exit(1)
}
}

/**
* Launches a headless browser
*/
async function launchBrowser(): Promise<Browser> {
return await puppeteer.launch({
headless: true,
acceptInsecureCerts: true,
ignoreDefaultArgs: ["--disable-extensions", "--ignore-certificate-errors"],
})
}

/**
* Sets up the browser page and navigates to the Minima node
*/
async function setupBrowserPage(
browser: Browser,
host: string,
port: number
): Promise<Page> {
const page = await browser.newPage()
await page.setViewport({ width: 1280, height: 1024 })
await page.goto(`https://${values.host}:${values.port}`)
await page.goto(`https://${host}:${port}`)
await page.waitForNetworkIdle()
await page.type("#password", values.password).catch(() => {
logger.error("Invalid password")
return page
}

/**
* Logs in to the Minima node
*/
async function loginToMinimaNode(
page: Page,
password: string,
debuggingSpinner: any
): Promise<void> {
try {
// Enter password
await page.type("#password", password)

// Click submit and wait for navigation
await Promise.all([
page.waitForNavigation({ waitUntil: "networkidle0" }),
page.click("[type='submit']"),
])

// Wait for page to stabilize
await new Promise((resolve) => setTimeout(resolve, 2000))
} catch (err) {
debuggingSpinner.fail("Login failed")
if (err instanceof Error) {
logger.error(`Failed to login: ${err.message}`)
}
logger.info("Please check your password, host, port and try again.")
process.exit(1)
})
await page.click("[type='submit']")
await page
.waitForFunction(
() => document.body.innerText.includes("Click anywhere to continue"),
{ timeout: 5000 }
)
.catch(() => {
debuggingSpinner.fail("Something went wrong!")
logger.info("Please check your password, host, port and try again.")
logger.info(
"If you need further help or guidance, visit https://docs.minima.global\n"
}
}

/**
* Handles the case of a fresh node that requires clicking to continue
*/
async function handleFreshNodeIfNeeded(
page: Page,
debuggingSpinner: any
): Promise<void> {
try {
// Check if this is a fresh node by looking for the text
const isFreshNode = await page
.evaluate(() =>
document.body.innerText.includes("Click anywhere to continue")
)
process.exit(1)
})
await page.click("body")
.catch(() => false)

const data = await page.evaluate(() => {
return new Promise<DappLink>((resolve) => {
window.MDS.dapplink("Health", function (data) {
resolve(data)
if (isFreshNode) {
// Click to continue for fresh nodes
await page.click("body").catch((err) => {
debuggingSpinner.fail("Something went wrong!")
logger.error(`Failed to click body: ${err.message}`)
logger.info("Please check your password, host, port and try again.")
process.exit(1)
})

// Wait for the click to take effect
await new Promise((resolve) => setTimeout(resolve, 2000))
}
} catch (err) {
// If there's an error in the detection process, log it but continue
logger.info(`Error checking for fresh node: ${err}. Proceeding anyway.`)
}
}

/**
* Gets the session ID from MDS
*/
async function getSessionIDFromMDS(page: Page): Promise<string> {
// First check if MDS is available on the page
const hasMDS = await page
.evaluate(() => typeof window.MDS !== "undefined" && window.MDS !== null)
.catch(() => false)

if (!hasMDS) {
throw new Error(
"MDS object not found on the page. This may indicate that you're not properly logged in or the Minima node is not running correctly."
)
}

// Get session ID using MDS.dapplink
const data = await page.evaluate(() => {
return new Promise<DappLink>((resolve, reject) => {
if (!window.MDS) {
reject(new Error("MDS is not defined in the window object"))
return
}

try {
window.MDS.dapplink("Health", function (data) {
if (!data || data.status === false) {
reject(new Error(`MDS.dapplink failed: ${JSON.stringify(data)}`))
return
}
resolve(data)
})
} catch (e) {
reject(new Error(`Exception in MDS.dapplink: ${e}`))
}
})
})

const sessionID = data.sessionid
debuggingSpinner.text = "Writing environment variables..."
if (!data || !data.sessionid) {
throw new Error(
`Invalid data returned from MDS.dapplink: ${JSON.stringify(data)}`
)
}

await browser.close()
return data.sessionid
}

/**
* Updates configuration files based on the template
*/
async function updateConfigFiles(
values: DebugOptions,
sessionID: string
): Promise<void> {
if (values.template === "vanilla-js") {
// Update the mds.js file for vanilla-js template
const mdsPath = path.join(process.cwd(), "mds.js")
const mdsContent = await fs.readFile(mdsPath, "utf-8")
await updateVanillaJsConfig(values, sessionID)
} else {
await updateOtherTemplatesConfig(values, sessionID)
}
}

const updatedContent = mdsContent
.replace(/DEBUG_HOST: null,/, `DEBUG_HOST: "${values.host}",`)
.replace(/DEBUG_PORT: -1,/, `DEBUG_PORT: ${values.port},`)
.replace(/DEBUG_MINIDAPPID: "0x00",/, `DEBUG_MINIDAPPID: "${sessionID}",`)
/**
* Updates configuration for vanilla-js template
*/
async function updateVanillaJsConfig(
values: DebugOptions,
sessionID: string
): Promise<void> {
// Update the mds.js file for vanilla-js template
const mdsPath = path.join(process.cwd(), "mds.js")
const mdsContent = await fs.readFile(mdsPath, "utf-8")

await fs.writeFile(mdsPath, updatedContent, "utf-8")
} else {
// Write .env file for other templates
const updatedContent = mdsContent
.replace(/DEBUG_HOST: null,/, `DEBUG_HOST: "${values.host}",`)
.replace(/DEBUG_PORT: -1,/, `DEBUG_PORT: ${values.port},`)
.replace(/DEBUG_MINIDAPPID: "0x00",/, `DEBUG_MINIDAPPID: "${sessionID}",`)

await writeEnvFile({
VITE_DEBUG_MDS_PORT: values.port,
VITE_DEBUG_SESSION_ID: sessionID,
VITE_DEBUG: "true",
VITE_DEBUG_HOST: values.host,
})
}
await fs.writeFile(mdsPath, updatedContent, "utf-8")
}

if (values.logs) {
debuggingSpinner.succeed(
chalk.green(`Debug settings configured successfully!\n`)
)
/**
* Updates configuration for other templates
*/
async function updateOtherTemplatesConfig(
values: DebugOptions,
sessionID: string
): Promise<void> {
// Write .env file for other templates
await writeEnvFile({
VITE_DEBUG_MDS_PORT: values.port,
VITE_DEBUG_SESSION_ID: sessionID,
VITE_DEBUG: "true",
VITE_DEBUG_HOST: values.host,
})
}

/**
* Displays success message
*/
function displaySuccessMessage(
values: DebugOptions,
debuggingSpinner: any
): void {
debuggingSpinner.succeed(
chalk.green("Debug settings configured successfully!\n")
)

if (values.logs) {
logger.secondary("You can navigate to your project directory with:\n")
logger.secondary(`cd ${values.appName}`)

if (values.template === "react-ts") {
logger.secondary(`${getRunCommand(values.packageManager, "dev")}`)
}

logger.secondary(
"If you need further help or guidance, visit https://docs.minima.global\n"
)
} else {
debuggingSpinner.succeed(
chalk.green("Debug settings configured successfully!\n")
)
logger.secondary(
"If you need further help or guidance, visit https://docs.minima.global\n"
)
}

process.exit(0)
logger.secondary(
"If you need further help or guidance, visit https://docs.minima.global\n"
)
}
Loading