diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index e6d184354d..058a4165bc 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -17,6 +17,10 @@ app todoApp { } ``` +### šŸž Bug fixes / šŸ”§ small improvements +- Changed the minimum number of machines that a server app is using when deployed to Fly.io from 0 to 1. This prevents the server app from shutting down when there are no requests to it. There might be some other work that the server is doing e.g. running periodic Jobs or sending e-mails, so we want to make sure that the server is always running. + + ## 0.11.7 ### šŸž Bug fixes / šŸ”§ small improvements diff --git a/waspc/packages/deploy/package-lock.json b/waspc/packages/deploy/package-lock.json index 05fdbf833e..74f3acee7f 100644 --- a/waspc/packages/deploy/package-lock.json +++ b/waspc/packages/deploy/package-lock.json @@ -21,6 +21,7 @@ "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint": "^8.31.0", + "prettier": "^2.8.8", "typescript": "^4.9.4" } }, @@ -1504,6 +1505,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -2964,6 +2980,12 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, "ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", diff --git a/waspc/packages/deploy/package.json b/waspc/packages/deploy/package.json index 120de8aeca..dca0007e94 100644 --- a/waspc/packages/deploy/package.json +++ b/waspc/packages/deploy/package.json @@ -20,6 +20,7 @@ "@typescript-eslint/eslint-plugin": "^5.48.0", "@typescript-eslint/parser": "^5.48.0", "eslint": "^8.31.0", + "prettier": "^2.8.8", "typescript": "^4.9.4" } } diff --git a/waspc/packages/deploy/src/providers/fly/helpers/helpers.ts b/waspc/packages/deploy/src/providers/fly/helpers/helpers.ts index fbec0ef911..bc9512b91b 100644 --- a/waspc/packages/deploy/src/providers/fly/helpers/helpers.ts +++ b/waspc/packages/deploy/src/providers/fly/helpers/helpers.ts @@ -9,12 +9,16 @@ export function isYes(str: string): boolean { } export function ensureWaspDirLooksRight(thisCommand: Command): void { - const dirContainsWasproot = fs.existsSync(path.join(thisCommand.opts().waspProjectDir, '.wasproot')); + const dirContainsWasproot = fs.existsSync( + path.join(thisCommand.opts().waspProjectDir, '.wasproot'), + ); if (dirContainsWasproot) { return; } - waspSays('The supplied Wasp directory does not appear to be a valid Wasp project.'); + waspSays( + 'The supplied Wasp directory does not appear to be a valid Wasp project.', + ); waspSays('Please double check your path.'); exit(1); } @@ -38,8 +42,11 @@ function getWaspBuildDir(waspProjectDir: string) { return path.join(waspProjectDir, '.wasp', 'build'); } -export function ensureDirsInCmdAreAbsoluteAndPresent(thisCommand: Command): void { - const waspProjectDirPath: string | undefined = thisCommand.opts().waspProjectDir; +export function ensureDirsInCmdAreAbsoluteAndPresent( + thisCommand: Command, +): void { + const waspProjectDirPath: string | undefined = + thisCommand.opts().waspProjectDir; if (waspProjectDirPath) { if (!path.isAbsolute(waspProjectDirPath)) { waspSays('The Wasp dir path must be absolute.'); @@ -88,6 +95,10 @@ export function waspSays(str: string): void { console.log('šŸš€ \x1b[33m ' + str + ' \x1b[0m'); } +export function boldText(str: string): string { + return '\x1b[1m' + str + '\x1b[0m'; +} + export function displayWaspRocketImage(): void { // Escaping backslashes makes it look weird here, but it works in console. const asciiArt = ` @@ -109,7 +120,10 @@ export function getCommandHelp(command: Command): string { } function trimUsage(usage: string): string { - return usage.split(/[\r\n]+/)[0].replace('Usage: ', '').replace(' [options]', ''); + return usage + .split(/[\r\n]+/)[0] + .replace('Usage: ', '') + .replace(' [options]', ''); } // There is a theoretical race condition here since we are modifying a global `$` @@ -118,7 +132,9 @@ function trimUsage(usage: string): string { // times concurrently could change the setting incorrectly. // However, our pattern of awaiting for both `$` and `silence` calls without any random // callbacks using either means this interleaving should not ever happen. -export async function silence(cmd: ($hh: Shell) => Promise): Promise { +export async function silence( + cmd: ($hh: Shell) => Promise, +): Promise { const verboseSetting = $.verbose; $.verbose = false; const proc = await cmd($); diff --git a/waspc/packages/deploy/src/providers/fly/helpers/tomlFileHelpers.ts b/waspc/packages/deploy/src/providers/fly/helpers/tomlFileHelpers.ts index ac97f0f6de..bb79c44c2e 100644 --- a/waspc/packages/deploy/src/providers/fly/helpers/tomlFileHelpers.ts +++ b/waspc/packages/deploy/src/providers/fly/helpers/tomlFileHelpers.ts @@ -4,8 +4,8 @@ import path from 'node:path'; import { CommonOptions } from '../CommonOptions.js'; export interface TomlFilePaths { - serverTomlPath: string; - clientTomlPath: string; + serverTomlPath: string; + clientTomlPath: string; } export function getTomlFilePaths(options: CommonOptions): TomlFilePaths { @@ -56,18 +56,30 @@ export function getAppNameFromToml(path: string): string { return data.app; } -export function getInferredBasenameFromServerToml(paths: TomlFilePaths): string { +export function getInferredBasenameFromServerToml( + paths: TomlFilePaths, +): string { const serverName = getAppNameFromToml(paths.serverTomlPath); return serverName.replace('-server', ''); } -export function getInferredBasenameFromClientToml(paths: TomlFilePaths): string { +export function getInferredBasenameFromClientToml( + paths: TomlFilePaths, +): string { const clientName = getAppNameFromToml(paths.clientTomlPath); return clientName.replace('-client', ''); } -export function replaceLineInLocalToml(searchValue: string | RegExp, replaceValue: string): void { +export function replaceLineInLocalToml( + searchValue: string | RegExp, + replaceValue: string, +): void { const content = fs.readFileSync('fly.toml', 'utf8'); const updatedContent = content.replace(searchValue, replaceValue); fs.writeFileSync('fly.toml', updatedContent); } + +export function doesLocalTomlContainLine(searchValue: string | RegExp): boolean { + const content = fs.readFileSync('fly.toml', 'utf8'); + return content.search(searchValue) !== -1; +} diff --git a/waspc/packages/deploy/src/providers/fly/setup/setup.ts b/waspc/packages/deploy/src/providers/fly/setup/setup.ts index 94f7344a0a..d229cf2d45 100644 --- a/waspc/packages/deploy/src/providers/fly/setup/setup.ts +++ b/waspc/packages/deploy/src/providers/fly/setup/setup.ts @@ -1,20 +1,32 @@ -import { $, cd } from 'zx'; +import { $, cd, question } from 'zx'; import crypto from 'crypto'; import { clientTomlExistsInProject, copyLocalClientTomlToProject, copyLocalServerTomlToProject, deleteLocalToml, + doesLocalTomlContainLine, getTomlFilePaths, replaceLineInLocalToml, serverTomlExistsInProject, } from '../helpers/tomlFileHelpers.js'; import { createDeploymentInfo, DeploymentInfo } from '../DeploymentInfo.js'; import { SetupOptions } from './SetupOptions.js'; -import { cdToClientBuildDir, cdToServerBuildDir, makeIdempotent, getCommandHelp, waspSays } from '../helpers/helpers.js'; +import { + cdToClientBuildDir, + cdToServerBuildDir, + makeIdempotent, + getCommandHelp, + waspSays, + boldText, +} from '../helpers/helpers.js'; import { createFlyDbCommand } from '../index.js'; -export async function setup(baseName: string, region: string, options: SetupOptions): Promise { +export async function setup( + baseName: string, + region: string, + options: SetupOptions, +): Promise { waspSays('Setting up your Wasp app with Fly.io!'); const buildWasp = makeIdempotent(async () => { @@ -24,7 +36,12 @@ export async function setup(baseName: string, region: string, options: SetupOpti }); const tomlFilePaths = getTomlFilePaths(options); - const deploymentInfo = createDeploymentInfo(baseName, region, options, tomlFilePaths); + const deploymentInfo = createDeploymentInfo( + baseName, + region, + options, + tomlFilePaths, + ); if (serverTomlExistsInProject(tomlFilePaths)) { waspSays(`${tomlFilePaths.serverTomlPath} exists. Skipping server setup.`); @@ -40,7 +57,11 @@ export async function setup(baseName: string, region: string, options: SetupOpti await setupClient(deploymentInfo); } - waspSays(`Don't forget to create your database by running "${getCommandHelp(createFlyDbCommand)}".`); + waspSays( + `Don't forget to create your database by running "${getCommandHelp( + createFlyDbCommand, + )}".`, + ); } async function setupServer(deploymentInfo: DeploymentInfo) { @@ -50,8 +71,10 @@ async function setupServer(deploymentInfo: DeploymentInfo) { deleteLocalToml(); const launchArgs = [ - '--name', deploymentInfo.serverName, - '--region', deploymentInfo.region, + '--name', + deploymentInfo.serverName, + '--region', + deploymentInfo.region, ]; if (deploymentInfo.options.org) { @@ -61,6 +84,31 @@ async function setupServer(deploymentInfo: DeploymentInfo) { // This creates the fly.toml file, but does not attempt to deploy. await $`flyctl launch --no-deploy ${launchArgs}`; + const minMachinesOptionRegex = /min_machines_running = 0/g; + + if (!doesLocalTomlContainLine(minMachinesOptionRegex)) { + await question(`\nāš ļø There was a possible issue setting up your server app. +We tried modifying your server fly.toml to set ${boldText( + 'min_machines_running = 1', + )}, but couldn't find the option ${boldText( + 'min_machines_running', +)} in the fly.toml. + +We advise that you additionaly check what is the value for "minimal number of machines running" on Fly +for this server app and confirm that it is set to the value you are OK with. + +Be aware that if it is set to 0, your server will shut down when there are no requests from the client, +which might be an issue for you if you have recurring Jobs or some other processes that need to keep +running on the server even without external input, in which case we advise keeping "minimal number +of machines running" setting at a number larger than zero. + +Contact the Wasp Team at our Discord server if you need help with this: https://discord.gg/rzdnErX + +Press any key to continue or Ctrl+C to cancel.`); + } else { + replaceLineInLocalToml(minMachinesOptionRegex, 'min_machines_running = 1'); + } + copyLocalServerTomlToProject(deploymentInfo.tomlFilePaths); const randomString = crypto.randomBytes(32).toString('hex'); @@ -74,7 +122,7 @@ async function setupServer(deploymentInfo: DeploymentInfo) { ]; if (deploymentInfo.options.serverSecret.length > 0) { - deploymentInfo.options.serverSecret.forEach(secret => { + deploymentInfo.options.serverSecret.forEach((secret) => { secretsArgs.push(secret); }); } @@ -92,8 +140,10 @@ async function setupClient(deploymentInfo: DeploymentInfo) { deleteLocalToml(); const launchArgs = [ - '--name', deploymentInfo.clientName, - '--region', deploymentInfo.region, + '--name', + deploymentInfo.clientName, + '--region', + deploymentInfo.region, ]; if (deploymentInfo.options.org) { @@ -103,9 +153,26 @@ async function setupClient(deploymentInfo: DeploymentInfo) { // This creates the fly.toml file, but does not attempt to deploy. await $`flyctl launch --no-deploy ${launchArgs}`; - // goStatic listens on port 8043 by default, but the default fly.toml - // assumes port 8080 (or 3000, depending on flyctl version). - replaceLineInLocalToml(/internal_port = \d+/g, 'internal_port = 8043'); + const internalPortOptionRegex = /internal_port = \d+/g; + + if (!doesLocalTomlContainLine(internalPortOptionRegex)) { + await question(`\nāš ļø There was an issue setting up your client app. +We tried modifying your client fly.toml to set ${boldText( + 'internal_port = 8043', + )}, but couldn't find the option ${boldText( + 'internal_port', +)} in the fly.toml. + +This means your client app might not be accessible. + +Contact the Wasp Team at our Discord server if you need help with this: https://discord.gg/rzdnErX + +Press any key to continue or Ctrl+C to cancel.`); + } else { + // goStatic listens on port 8043 by default, but the default fly.toml + // assumes port 8080 (or 3000, depending on flyctl version). + replaceLineInLocalToml(internalPortOptionRegex, 'internal_port = 8043'); + } copyLocalClientTomlToProject(deploymentInfo.tomlFilePaths); diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 49f8f74bd8..974aab340f 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -65,7 +65,6 @@ data-files: packages/studio/dist/**/*.js packages/studio/dist/**/*.html packages/studio/dist/**/*.css - packages/studio/dist/**/*.png packages/studio/package.json packages/studio/package-lock.json data-dir: data/ diff --git a/web/docs/advanced/deployment/cli.md b/web/docs/advanced/deployment/cli.md index b7a4cf2893..04075bb811 100644 --- a/web/docs/advanced/deployment/cli.md +++ b/web/docs/advanced/deployment/cli.md @@ -47,7 +47,11 @@ wasp deploy fly create-db mia wasp deploy fly deploy ``` -The commands above use the app basename `my-wasp-app` and deploy it to the _Miami, Florida (US) region_ (called `mia`). +The commands above use the app basename `my-wasp-app` and deploy it to the _Miami, Florida (US) region_ (called `mia`). Read more about Fly.io regions [here](#flyio-regions). + +:::caution Unique Name +Your app name must be unique across all of Fly or deployment will fail. +::: The basename is used to create all three app tiers, resulting in three separate apps in your Fly dashboard: @@ -55,11 +59,11 @@ The basename is used to create all three app tiers, resulting in three separate - `my-wasp-app-server` - `my-wasp-app-db` -:::caution Unique Name -Your app name must be unique across all of Fly or deployment will fail. -::: +You'll notice that Wasp creates two new files in your project root directory: +- `fly-server.toml` +- `fly-client.toml` -Read more about Fly.io regions [here](#flyio-regions). +You should include these files in your version control so that you can deploy your app with a single command in the future. ### Using a Custom Domain For Your App @@ -159,6 +163,8 @@ It accepts the following arguments: After running `setup`, Wasp creates two new files in your project root directory: `fly-server.toml` and `fly-client.toml`. You should include these files in your version control. +You **can edit the `fly-server.toml` and `fly-client.toml` files** to further configure your Fly deployments. Wasp will use the TOML files when you run `deploy`. + If you want to maintain multiple apps, you can add the `--fly-toml-dir ` option to point to different directories, like "dev" or "staging". :::caution Execute Only Once