mirror of
https://github.com/hanzoai/pack.git
synced 2026-09-30 23:43:13 +00:00
fix: hard fail on invalid railpack.json (#226)
* fix: hard fail if railpack.json cannot be parsed * test: add shouldFail test.json expression helpful if we expect the build to fail. There shouldn't be too many of these, but we'll need at least a couple to make sure cases like railpack.json syntax errors hard fail. * test: bad json syntax failure * test: config file abs path, relative, and DNE cases * refactor: move hujson helper into internal helpers * fix: support abs config file path, fail if config file path does not exist * fix: document relative config file path, no abs config file support * fix: invalid config json * test: snapshot update * test: remove abs file config test * test: remove custom java config I know basically nothing about Java and after I fixed the syntax error which was causing this config file to not be picked up, this started failing. The proper Zulu version check which is the main purpose of this is still working fine so I think it's fine to remove these custom commands. * fix: typo * test: snapshot update
This commit is contained in:
+1
-1
@@ -36,7 +36,7 @@ func commonPlanFlags() []cli.Flag {
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "config-file",
|
||||
Usage: "path to config file to use",
|
||||
Usage: "relative path to railpack config file (default: railpack.json)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "error-missing-start",
|
||||
|
||||
+3
-12
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"github.com/tailscale/hujson"
|
||||
"github.com/railwayapp/railpack/internal/utils"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ func (a *App) FindFilesWithContent(pattern string, regex *regexp.Regexp) []strin
|
||||
return matches
|
||||
}
|
||||
|
||||
// ReadFile reads the contents of a file
|
||||
// ReadFile reads the contents of a file within the application source directory
|
||||
func (a *App) ReadFile(name string) (string, error) {
|
||||
path := filepath.Join(a.Source, name)
|
||||
data, err := os.ReadFile(path)
|
||||
@@ -153,7 +153,7 @@ func (a *App) ReadJSON(name string, v interface{}) error {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonBytes, err := standardizeJSON([]byte(data))
|
||||
jsonBytes, err := utils.StandardizeJSON([]byte(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -216,12 +216,3 @@ func (a *App) stripSourcePath(absPath string) (string, error) {
|
||||
}
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
func standardizeJSON(b []byte) ([]byte, error) {
|
||||
ast, err := hujson.Parse(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
ast.Standardize()
|
||||
return ast.Pack(), nil
|
||||
}
|
||||
|
||||
+35
-5
@@ -1,7 +1,11 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -40,6 +44,26 @@ type BuildResult struct {
|
||||
Success bool `json:"success,omitempty"`
|
||||
}
|
||||
|
||||
func readConfigJSON(path string, v interface{}) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonBytes, err := utils.StandardizeJSON([]byte(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stringData := string(jsonBytes)
|
||||
|
||||
if err := json.Unmarshal([]byte(stringData), v); err != nil {
|
||||
return fmt.Errorf("error reading %s as JSON: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuildPlanOptions) *BuildResult {
|
||||
logger := logger.NewLogger()
|
||||
|
||||
@@ -56,7 +80,7 @@ func GenerateBuildPlan(app *app.App, env *app.Environment, options *GenerateBuil
|
||||
return &BuildResult{Success: false, Logs: logger.Logs}
|
||||
}
|
||||
|
||||
// Set the preivous versions
|
||||
// Set the previous versions
|
||||
if options.PreviousVersions != nil {
|
||||
for name, version := range options.PreviousVersions {
|
||||
ctx.Resolver.SetPreviousVersion(name, version)
|
||||
@@ -140,17 +164,23 @@ func GenerateConfigFromFile(app *app.App, env *app.Environment, options *Generat
|
||||
configFileName = envConfigFileName
|
||||
}
|
||||
|
||||
if !app.HasMatch(configFileName) {
|
||||
// always assume config file path is relative to the app source directory
|
||||
// https://github.com/railwayapp/railpack/pull/226
|
||||
absConfigFileName := filepath.Join(app.Source, configFileName)
|
||||
|
||||
if _, err := os.Stat(absConfigFileName); err != nil && os.IsNotExist(err) {
|
||||
// if a specific path was specified, we should indicate that it was not found and hard fail
|
||||
if configFileName != defaultConfigFileName {
|
||||
logger.LogWarn("Config file `%s` not found", configFileName)
|
||||
return nil, fmt.Errorf("config file %q not found", absConfigFileName)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if err := app.ReadJSON(configFileName, config); err != nil {
|
||||
// if a JSON file was provided, we should hard fail if we cannot parse it
|
||||
if err := readConfigJSON(absConfigFileName, config); err != nil {
|
||||
logger.LogWarn("Failed to read config file `%s`\nUse the following schema to validate your config file: %s\n", configFileName, c.SchemaUrl)
|
||||
return config, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.LogInfo("Using config file `%s`", configFileName)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/gkampitakis/go-snaps/snaps"
|
||||
"github.com/railwayapp/railpack/core/app"
|
||||
"github.com/railwayapp/railpack/core/logger"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -16,6 +17,7 @@ func TestMain(m *testing.M) {
|
||||
os.Exit(v)
|
||||
}
|
||||
|
||||
// generate snapshot plan JSON for each build example and assert against it
|
||||
func TestGenerateBuildPlanForExamples(t *testing.T) {
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
@@ -59,3 +61,34 @@ func TestGenerateBuildPlanForExamples(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateConfigFromFile_NotFound(t *testing.T) {
|
||||
// Use an existing example app directory so relative paths resolve
|
||||
appPath := "../examples/config-file"
|
||||
userApp, err := app.NewApp(appPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := app.NewEnvironment(nil)
|
||||
l := logger.NewLogger()
|
||||
|
||||
options := &GenerateBuildPlanOptions{ConfigFilePath: "does-not-exist.railpack.json"}
|
||||
cfg, genErr := GenerateConfigFromFile(userApp, env, options, l)
|
||||
|
||||
require.Error(t, genErr, "expected an error when explicit config file does not exist")
|
||||
require.Nil(t, cfg, "config should be nil on error")
|
||||
}
|
||||
|
||||
func TestGenerateConfigFromFile_Malformed(t *testing.T) {
|
||||
appPath := "../examples/config-file"
|
||||
userApp, err := app.NewApp(appPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := app.NewEnvironment(nil)
|
||||
l := logger.NewLogger()
|
||||
|
||||
options := &GenerateBuildPlanOptions{ConfigFilePath: "railpack.malformed.json"}
|
||||
cfg, genErr := GenerateConfigFromFile(userApp, env, options, l)
|
||||
|
||||
require.Error(t, genErr, "expected an error for malformed JSON config file")
|
||||
require.Nil(t, cfg, "config should be nil on error")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"steps": {
|
||||
"commands": ["npm install"]
|
||||
},
|
||||
"invalid": json syntax here
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
},
|
||||
{
|
||||
"expectedOutput": "Hello via Bun",
|
||||
"configFile": "railpack.invalid.json"
|
||||
"configFile": "railpack.invalid.json",
|
||||
"shouldFail": true
|
||||
},
|
||||
{
|
||||
"configFile": "railpack.malformed.json",
|
||||
"shouldFail": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,13 +3,5 @@
|
||||
"provider": "java",
|
||||
"packages": {
|
||||
"java": "zulu-8.86"
|
||||
},
|
||||
"steps": {
|
||||
"install": {
|
||||
"commands": [mvn clean compile", "mvn package"]
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"startCommand": "java -jar target/java-zulu-version-1.0-SNAPSHOT.jar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ type TestCase struct {
|
||||
Envs map[string]string `json:"envs"`
|
||||
ConfigFilePath string `json:"configFile"`
|
||||
JustBuild bool `json:"justBuild"`
|
||||
ShouldFail bool `json:"shouldFail"`
|
||||
}
|
||||
|
||||
func TestExamplesIntegration(t *testing.T) {
|
||||
@@ -78,6 +79,16 @@ func TestExamplesIntegration(t *testing.T) {
|
||||
buildResult := core.GenerateBuildPlan(userApp, env, &core.GenerateBuildPlanOptions{
|
||||
ConfigFilePath: testCase.ConfigFilePath,
|
||||
})
|
||||
|
||||
// Handle case where we expect the build to fail
|
||||
if testCase.ShouldFail {
|
||||
if buildResult.Success {
|
||||
t.Fatalf("expected build to fail, but it succeeded")
|
||||
}
|
||||
// Test passes - build failed as expected
|
||||
return
|
||||
}
|
||||
|
||||
if !buildResult.Success {
|
||||
t.Fatalf("failed to generate build plan: %v", buildResult.Logs)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tailscale/hujson"
|
||||
)
|
||||
|
||||
func RemoveDuplicates[T comparable](sliceList []T) []T {
|
||||
@@ -159,3 +161,13 @@ type Semver struct {
|
||||
Minor int
|
||||
Patch int
|
||||
}
|
||||
|
||||
// convert hujson/extended JSON into standardized json
|
||||
func StandardizeJSON(b []byte) ([]byte, error) {
|
||||
ast, err := hujson.Parse(b)
|
||||
if err != nil {
|
||||
return b, err
|
||||
}
|
||||
ast.Standardize()
|
||||
return ast.Pack(), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user