From a95974a81644a2c6cb98c0cc03978d2044bee861 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 23 Sep 2024 17:34:12 -0400 Subject: [PATCH] cli: error helper --- cmd/sst/main.go | 12 +++++ cmd/sst/mosaic/ui/ui.go | 4 ++ pkg/project/stack.go | 53 ++++++++++++++++++++- www/.gitignore | 2 + www/astro.config.mjs | 1 + www/generate.ts | 101 ++++++++++++++++++++++++++++++++++++++++ www/package.json | 4 +- 7 files changed, 174 insertions(+), 3 deletions(-) diff --git a/cmd/sst/main.go b/cmd/sst/main.go index e0c8f9f9b..04d6bb9c7 100644 --- a/cmd/sst/main.go +++ b/cmd/sst/main.go @@ -961,6 +961,18 @@ var root = &cli.Command{ return nil }, }, + { + Name: "common-errors", + Hidden: true, + Run: func(cli *cli.Cli) error { + data, err := json.MarshalIndent(project.CommonErrors, "", " ") + if err != nil { + return err + } + fmt.Println(string(data)) + return nil + }, + }, { Name: "refresh", Description: cli.Description{ diff --git a/cmd/sst/mosaic/ui/ui.go b/cmd/sst/mosaic/ui/ui.go index 14eda9ab3..755281c41 100644 --- a/cmd/sst/mosaic/ui/ui.go +++ b/cmd/sst/mosaic/ui/ui.go @@ -415,6 +415,10 @@ func (u *UI) Event(unknown interface{}) { for _, line := range parseError(status.Message) { u.println(TEXT_NORMAL.Render(" " + line)) } + for i, line := range status.Help { + if i == 0 { u.println() } + u.println(TEXT_NORMAL.Render(" " + line)) + } importDiffs, ok := evt.ImportDiffs[status.URN] if ok { isSSTComponent := strings.Contains(status.URN, "::sst") diff --git a/pkg/project/stack.go b/pkg/project/stack.go index 3b2baa763..cde798f06 100644 --- a/pkg/project/stack.go +++ b/pkg/project/stack.go @@ -123,10 +123,49 @@ type StackCommandEvent struct { } type Error struct { - Message string `json:"message"` - URN string `json:"urn"` + Message string `json:"message"` + URN string `json:"urn"` + Help []string `json:"help"` } +type CommonError struct { + Code string `json:"code"` + Message string `json:"message"` + Short []string `json:"short"` + Long []string `json:"long"` +} + +var CommonErrors = []CommonError{ + { + Code: "TooManyCacheBehaviors", + Message: "TooManyCacheBehaviors: Your request contains more CacheBehaviors than are allowed per distribution", + Short: []string{ + "There are too many top-level files and directories inside your app's public asset folder. Move some of them inside subdirectories.", + "Learn more about this https://sst.dev/docs/common-errors#toomanycachebehaviors", + }, + Long: []string{ + "This error usually happens to SvelteKit, SolidStart, Nuxt, and Analog apps.", + "CloudFront distributions have a limit of 25 cache behaviors per distribution. Each top-level file and directory inside your app's asset folder creates a cache behavior. For example, in the case of a SvelteKit app, the static assets are in the `static` folder. If you have two files in it, it creates 2 cache behaviors.", + "```", + "static/", + "├── favicon.png # Cache behavior for /favicon.png", + "└── logo.png # Cache behavior for /logo.png", + "```", + "If you have too many files, it creates too many cache behaviors and hits the limit.", + "The solution is to move some of the files into subdirectories. For example, by moving the files in the `images` folder, it will only create 1 cache behavior.", + "```", + "static/", + "└── images/ # Cache behavior for /images/*", + " ├── logo.png", + " └── logo.png", + "```", + "Learn more about CloudFront limits [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-web-distributions).", + "Alternatively, you can request a limit increase via the AWS Support.", + }, + }, +}; + + var ErrStackRunFailed = fmt.Errorf("stack run had errors") var ErrStageNotFound = fmt.Errorf("stage not found") var ErrPassphraseInvalid = fmt.Errorf("passphrase invalid") @@ -405,9 +444,19 @@ func (p *Project) Run(ctx context.Context, input *StackInput) error { if strings.Contains(event.DiagnosticEvent.Message, "failed to register new resource") { break } + + // check if the error is a common error + help := []string{} + for _, commonError := range CommonErrors { + if strings.Contains(event.DiagnosticEvent.Message, commonError.Message) { + help = append(help, commonError.Short...) + } + } + errors = append(errors, Error{ Message: event.DiagnosticEvent.Message, URN: event.DiagnosticEvent.URN, + Help: help, }) telemetry.Track("cli.resource.error", map[string]interface{}{ "error": event.DiagnosticEvent.Message, diff --git a/www/.gitignore b/www/.gitignore index 013332f2d..e38acb5df 100644 --- a/www/.gitignore +++ b/www/.gitignore @@ -21,12 +21,14 @@ pnpm-debug.log* .DS_Store # typedoc output +common-errors-doc.json components-doc.json examples-doc.json cli-doc.json sdk-doc.json # generated docs +src/content/docs/docs/common-errors.mdx src/content/docs/docs/component/ src/content/docs/docs/reference/ !src/content/docs/docs/reference/sdk.mdx diff --git a/www/astro.config.mjs b/www/astro.config.mjs index 7c7c5d348..24cd38f53 100644 --- a/www/astro.config.mjs +++ b/www/astro.config.mjs @@ -162,6 +162,7 @@ const sidebar = [ ], }, { label: "Examples", slug: "docs/examples" }, + { label: "Common Errors", slug: "docs/common-errors" }, { label: "Deprecated", collapsed: true, diff --git a/www/generate.ts b/www/generate.ts index 14f691aa8..400fe8a17 100644 --- a/www/generate.ts +++ b/www/generate.ts @@ -34,6 +34,12 @@ type CliCommand = { children: CliCommand[]; }; +type CommonError = { + code: string; + message: string; + long: string[]; +}; + const cmd = process.argv[2]; const linkHashes = new Map< TypeDoc.DeclarationReflection, @@ -80,6 +86,7 @@ if (!cmd || cmd === "components") { } } if (!cmd || cmd === "cli") await generateCliDoc(); +if (!cmd || cmd === "common-errors") await generateCommonErrorsDoc(); if (!cmd || cmd === "examples") await generateExamplesDocs(); restoreCode(); @@ -301,6 +308,100 @@ function generateCliDoc() { } } +function generateCommonErrorsDoc() { + const content = fs.readFileSync("common-errors-doc.json"); + const json = JSON.parse(content.toString()) as CommonError[]; + const outputFilePath = `src/content/docs/docs/common-errors.mdx`; + + fs.writeFileSync( + outputFilePath, + [ + renderHeader("Common Errors", "Common errors that SST can report."), + renderSourceMessage("cmd/sst/main.go"), + renderImports(outputFilePath), + renderBodyBegin(), + renderCommonErrorsAbout(), + renderCommonErrorsErrors(), + renderBodyEnd(), + ] + .flat() + .join("\n") + ); + + function renderCommonErrorsAbout() { + return [ + `Below are a collection of common errors you might encounter when using SST.`, + "", + ":::tip", + "This doc is best viewed through the site search or through the _AI_.", + ":::", + "", + "The descriptions for these errors are generated from the CLI.", + "", + ]; + } + + function renderCommonErrorsErrors() { + const lines: string[] = []; + + for (const error of json) { + console.debug(` - command ${error.code}`); + lines.push( + ``, + `---`, + ``, + `## ${error.code}`, + ``, + ``, + `
`, + `> ${error.message}`, + ``, + ...error.long, + `
`, + `
` + ); + } + return lines; + } + + function renderCliDescription(description: CliCommand["description"]) { + return description.long ?? description.short; + } + + function renderCliArgName(prop: CliCommand["args"][number]) { + return `${prop.name}${prop.required ? "" : "?"}`; + } + + function renderCliCommandUsage(command: CliCommand) { + const parts: string[] = []; + + parts.push(command.name); + command.args.forEach((arg) => + arg.required ? parts.push(`<${arg.name}>`) : parts.push(`[${arg.name}]`) + ); + return parts.join(" "); + } + + function renderCliFlagType(type: CliCommand["flags"][number]["type"]) { + if (type.startsWith("[") && type.endsWith("]")) { + return type + .substring(1, type.length - 1) + .split(",") + .map((t: string) => + [ + ``, + `${t}`, + ``, + ].join("") + ) + .join(` | `); + } + + if (type === "bool") return `boolean`; + return `${type}`; + } +} + async function generateExamplesDocs() { const modules = await buildExamples(); const outputFilePath = `src/content/docs/docs/examples.mdx`; diff --git a/www/package.json b/www/package.json index fe9dc548d..846bcc5bf 100644 --- a/www/package.json +++ b/www/package.json @@ -12,7 +12,9 @@ "generate-components": "tsx generate.ts components", "generate-examples": "tsx generate.ts examples", "generate-cli": "bun generate-cli-json && tsx generate.ts cli", - "generate-cli-json": "go run ../cmd/sst introspect > cli-doc.json" + "generate-cli-json": "go run ../cmd/sst introspect > cli-doc.json", + "generate-errors": "bun generate-errors-json && tsx generate.ts common-errors", + "generate-errors-json": "go run ../cmd/sst common-errors > common-errors-doc.json" }, "dependencies": { "@astrojs/check": "^0.9.2",