-
Notifications
You must be signed in to change notification settings - Fork 0
fix(generated-output): publish artifacts transactionally #796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,11 @@ import ( | |
| "github.com/cssbruno/gowdk/internal/safeasset" | ||
| ) | ||
|
|
||
| type plannedFile struct { | ||
| path string | ||
| contents []byte | ||
| } | ||
|
|
||
| func validateDirectories(outputDir, appDir string) error { | ||
| info, err := os.Stat(outputDir) | ||
| if err != nil { | ||
|
|
@@ -38,7 +43,21 @@ func isSameOrWithin(parent, child string) bool { | |
| } | ||
|
|
||
| func copyOutputFiles(sourceRoot, targetRoot string) ([]string, error) { | ||
| files, planned, err := collectOutputFiles(sourceRoot, targetRoot) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, file := range planned { | ||
| if err := writeFileIfChanged(file.path, file.contents); err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
| return files, nil | ||
| } | ||
|
|
||
| func collectOutputFiles(sourceRoot, targetRoot string) ([]string, []plannedFile, error) { | ||
| var files []string | ||
| var planned []plannedFile | ||
| err := filepath.WalkDir(sourceRoot, func(sourcePath string, entry os.DirEntry, walkErr error) error { | ||
| if walkErr != nil { | ||
| return walkErr | ||
|
|
@@ -56,7 +75,7 @@ func copyOutputFiles(sourceRoot, targetRoot string) ([]string, error) { | |
| if unsafeEmbeddedDirectory(rel) { | ||
| return filepath.SkipDir | ||
| } | ||
| return os.MkdirAll(targetPath, 0o755) | ||
| return nil | ||
| } | ||
| info, err := entry.Info() | ||
| if err != nil { | ||
|
|
@@ -68,14 +87,16 @@ func copyOutputFiles(sourceRoot, targetRoot string) ([]string, error) { | |
| if !safeasset.EmbeddableGeneratedOutputFile(rel) { | ||
| return nil | ||
| } | ||
| if err := copyFile(sourcePath, targetPath); err != nil { | ||
| payload, err := os.ReadFile(sourcePath) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| planned = append(planned, plannedFile{path: targetPath, contents: payload}) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For generated app builds with many or large embeddable assets, this now retains the contents of every copied output file in Useful? React with 👍 / 👎. |
||
| files = append(files, rel) | ||
| return nil | ||
| }) | ||
| sort.Strings(files) | ||
| return files, err | ||
| return files, planned, err | ||
| } | ||
|
|
||
| func unsafeEmbeddedDirectory(rel string) bool { | ||
|
|
@@ -125,5 +146,31 @@ func writeFileIfChanged(filePath string, contents []byte) error { | |
| if err := os.MkdirAll(filepath.Dir(filePath), 0o755); err != nil { | ||
| return err | ||
| } | ||
| return os.WriteFile(filePath, contents, 0o644) | ||
| temp, err := os.CreateTemp(filepath.Dir(filePath), "."+filepath.Base(filePath)+".tmp-*") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| tempName := temp.Name() | ||
| cleanup := true | ||
| defer func() { | ||
| if cleanup { | ||
| _ = os.Remove(tempName) | ||
| } | ||
| }() | ||
| if _, err := temp.Write(contents); err != nil { | ||
| _ = temp.Close() | ||
| return err | ||
| } | ||
| if err := temp.Chmod(0o644); err != nil { | ||
| _ = temp.Close() | ||
| return err | ||
| } | ||
| if err := temp.Close(); err != nil { | ||
| return err | ||
| } | ||
| if err := os.Rename(tempName, filePath); err != nil { | ||
| return err | ||
| } | ||
| cleanup = false | ||
| return nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new rule says disk static output is planned before publication, but
gowdk devstill usesBuildIncrementalFromValidatedProgram(internal/gowdkcmd/dev_loop.go:131), whose incremental path writes CSS/runtime/page files as it goes before later cleanup and manifest/report steps. In incremental rebuilds, a late error can still partially update the served output, so the documented contract is broader than the implementation; either apply the planned-publish path to incremental builds or explicitly scope this guarantee to full static builds.Useful? React with 👍 / 👎.