Conversion to full YAML
This commit is contained in:
+237
-76
@@ -296,27 +296,18 @@ func runBuild(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runBuildModulePreflight(ctx, p, nil); err != nil {
|
||||
preflight, err := runBuildModulePreflight(ctx, p, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "build")
|
||||
|
||||
result, err := pipeline.Build(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "module: %s\n", result.ModulePath)
|
||||
fmt.Fprintf(ctx.stdout, "resources: %d\n", result.Resources)
|
||||
if result.TopPackageHAK != "" {
|
||||
fmt.Fprintf(ctx.stdout, "top package hak: %s\n", result.TopPackageHAK)
|
||||
fmt.Fprintf(ctx.stdout, "top package tlk: %s\n", result.TopPackageTLK)
|
||||
}
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
fmt.Fprintf(ctx.stdout, "hak assets: %d\n", result.HAKAssets)
|
||||
fmt.Fprintf(ctx.stdout, "hak manifest: %s\n", result.Manifest)
|
||||
}
|
||||
console.emitBuildResult(result, preflight)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -325,15 +316,16 @@ func runBuildModule(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "build-module")
|
||||
spin.configure(ctx.stderr, console.spinnerEnabled)
|
||||
spin.start("Build Module: starting")
|
||||
defer spin.stop()
|
||||
|
||||
progress := func(message string) {
|
||||
if ctx.logLevel == logLevelQuiet {
|
||||
return
|
||||
}
|
||||
spin.linebreak()
|
||||
fmt.Fprintf(ctx.stdout, "[build-module] %s\n", message)
|
||||
console.progress(message)
|
||||
}
|
||||
if err := runBuildModulePreflight(ctx, p, progress); err != nil {
|
||||
preflight, err := runBuildModulePreflight(ctx, p, progress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -344,26 +336,36 @@ func runBuildModule(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "module: %s\n", result.ModulePath)
|
||||
fmt.Fprintf(ctx.stdout, "resources: %d\n", result.Resources)
|
||||
console.emitBuildModuleResult(result, preflight)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runBuildModulePreflight(ctx context, p *project.Project, progress func(string)) error {
|
||||
type buildModulePreflightResult struct {
|
||||
ManifestStatus string
|
||||
ManifestPath string
|
||||
ScriptCompilationStatus string
|
||||
}
|
||||
|
||||
func runBuildModulePreflight(ctx context, p *project.Project, progress func(string)) (buildModulePreflightResult, error) {
|
||||
result := buildModulePreflightResult{}
|
||||
if progress == nil {
|
||||
progress = func(string) {}
|
||||
}
|
||||
if err := refreshBuildModuleManifest(ctx, p, progress); err != nil {
|
||||
return err
|
||||
manifestStatus, manifestPath, err := refreshBuildModuleManifest(ctx, p, progress)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if err := prepareBuildModuleCompiler(ctx, p, progress); err != nil {
|
||||
return err
|
||||
result.ManifestStatus = manifestStatus
|
||||
result.ManifestPath = manifestPath
|
||||
scriptStatus, err := prepareBuildModuleCompiler(ctx, p, progress)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
return nil
|
||||
result.ScriptCompilationStatus = scriptStatus
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func refreshBuildModuleManifest(ctx context, p *project.Project, progress func(string)) error {
|
||||
func refreshBuildModuleManifest(ctx context, p *project.Project, progress func(string)) (string, string, error) {
|
||||
manifestPath := p.HAKManifestPath()
|
||||
if override := strings.TrimSpace(os.Getenv("SOW_HAK_MANIFEST_PATH")); override != "" {
|
||||
manifestPath = override
|
||||
@@ -374,36 +376,36 @@ func refreshBuildModuleManifest(ctx context, p *project.Project, progress func(s
|
||||
|
||||
if envEnabled("SOW_MODULE_SKIP_HAK_MANIFEST_REFRESH") {
|
||||
if _, err := os.Stat(manifestPath); err != nil {
|
||||
return fmt.Errorf("SOW_MODULE_SKIP_HAK_MANIFEST_REFRESH is set, but %s does not exist", manifestPath)
|
||||
return "", "", fmt.Errorf("SOW_MODULE_SKIP_HAK_MANIFEST_REFRESH is set, but %s does not exist", manifestPath)
|
||||
}
|
||||
progress(fmt.Sprintf("Using existing hak manifest at %s; skipping published sow-assets refresh.", relPathFromRoot(p.Root, manifestPath)))
|
||||
return nil
|
||||
return "reused", manifestPath, nil
|
||||
}
|
||||
|
||||
progress("Refreshing hak list from the latest published sow-assets manifest...")
|
||||
if err := runProjectScript(ctx, p, []string{"scripts", "fetch-hak-manifest"}, manifestPath); err != nil {
|
||||
return err
|
||||
return "", "", err
|
||||
}
|
||||
if _, err := pipeline.ApplyHAKManifest(p, manifestPath); err != nil {
|
||||
return err
|
||||
return "", "", err
|
||||
}
|
||||
return nil
|
||||
return "refreshed", manifestPath, nil
|
||||
}
|
||||
|
||||
func prepareBuildModuleCompiler(ctx context, p *project.Project, progress func(string)) error {
|
||||
func prepareBuildModuleCompiler(ctx context, p *project.Project, progress func(string)) (string, error) {
|
||||
if envEnabled("SOW_MODULE_SKIP_NSS_COMPILATION") {
|
||||
progress("SOW_MODULE_SKIP_NSS_COMPILATION is set; skipping NWScript compiler resolution and NSS compilation for this run.")
|
||||
return nil
|
||||
return "skipped", nil
|
||||
}
|
||||
if !projectHasScriptSources(p) {
|
||||
return nil
|
||||
return "not-needed", nil
|
||||
}
|
||||
|
||||
compilerPathEnv := p.ScriptCompilerPathEnv()
|
||||
explicitCompiler := strings.TrimSpace(os.Getenv(compilerPathEnv))
|
||||
if explicitCompiler != "" {
|
||||
if scriptCompilerRunnable(explicitCompiler) {
|
||||
return nil
|
||||
return "enabled", nil
|
||||
}
|
||||
if fileExists(explicitCompiler) {
|
||||
return disableBuildModuleCompiler(compilerPathEnv, progress, fmt.Sprintf("Configured NWScript compiler is present but cannot start: %s", explicitCompiler))
|
||||
@@ -411,7 +413,7 @@ func prepareBuildModuleCompiler(ctx context, p *project.Project, progress func(s
|
||||
}
|
||||
|
||||
if _, ok := findUsableBuildModuleCompiler(p); ok {
|
||||
return nil
|
||||
return "enabled", nil
|
||||
}
|
||||
|
||||
installScript := projectScriptPath(p.Root, "scripts", "install-script-compiler")
|
||||
@@ -424,21 +426,21 @@ func prepareBuildModuleCompiler(ctx context, p *project.Project, progress func(s
|
||||
return disableBuildModuleCompiler(compilerPathEnv, progress, "Automatic NWScript compiler install failed.")
|
||||
}
|
||||
if _, ok := findUsableBuildModuleCompiler(p); ok {
|
||||
return nil
|
||||
return "installed", nil
|
||||
}
|
||||
|
||||
return disableBuildModuleCompiler(compilerPathEnv, progress, "Installed NWScript compiler but it still cannot start.")
|
||||
}
|
||||
|
||||
func disableBuildModuleCompiler(compilerPathEnv string, progress func(string), reason string) error {
|
||||
func disableBuildModuleCompiler(compilerPathEnv string, progress func(string), reason string) (string, error) {
|
||||
if err := os.Setenv("SOW_MODULE_SKIP_NSS_COMPILATION", "1"); err != nil {
|
||||
return fmt.Errorf("set SOW_MODULE_SKIP_NSS_COMPILATION: %w", err)
|
||||
return "", fmt.Errorf("set SOW_MODULE_SKIP_NSS_COMPILATION: %w", err)
|
||||
}
|
||||
progress("Skipping NWScript compilation for this build. " + reason)
|
||||
if compilerPathEnv != "" {
|
||||
_ = os.Unsetenv(compilerPathEnv)
|
||||
}
|
||||
return nil
|
||||
return "skipped", nil
|
||||
}
|
||||
|
||||
func projectHasScriptSources(p *project.Project) bool {
|
||||
@@ -585,6 +587,190 @@ const (
|
||||
logLevelDebug
|
||||
)
|
||||
|
||||
type projectConsole struct {
|
||||
stdout io.Writer
|
||||
projectRoot string
|
||||
projectName string
|
||||
commandName string
|
||||
commandLabel string
|
||||
level logLevel
|
||||
spinnerEnabled bool
|
||||
}
|
||||
|
||||
func newProjectConsole(ctx context, p *project.Project, commandName string) *projectConsole {
|
||||
return &projectConsole{
|
||||
stdout: ctx.stdout,
|
||||
projectRoot: p.Root,
|
||||
projectName: p.Config.Module.Name,
|
||||
commandName: commandName,
|
||||
commandLabel: projectCommandLabel(commandName),
|
||||
level: ctx.logLevel,
|
||||
spinnerEnabled: isInteractiveTTY(ctx.stderr) && strings.TrimSpace(os.Getenv("CI")) == "" && ctx.logLevel == logLevelNormal,
|
||||
}
|
||||
}
|
||||
|
||||
func projectCommandLabel(commandName string) string {
|
||||
switch commandName {
|
||||
case "build":
|
||||
return "Build"
|
||||
case "build-module":
|
||||
return "Build Module"
|
||||
case "extract":
|
||||
return "Extract"
|
||||
case "validate":
|
||||
return "Validate"
|
||||
case "compare":
|
||||
return "Compare"
|
||||
case "apply-hak-manifest":
|
||||
return "Apply HAK Manifest"
|
||||
default:
|
||||
return commandName
|
||||
}
|
||||
}
|
||||
|
||||
func (c *projectConsole) progress(message string) {
|
||||
if phase := c.phaseLabel(message); phase != "" {
|
||||
spin.update(c.commandLabel + ": " + phase)
|
||||
}
|
||||
if c.level != logLevelDebug {
|
||||
return
|
||||
}
|
||||
spin.linebreak()
|
||||
fmt.Fprintf(c.stdout, "[debug] %s\n", message)
|
||||
}
|
||||
|
||||
func (c *projectConsole) phaseLabel(message string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(message, "Refreshing hak list from the latest published sow-assets manifest"):
|
||||
return "refreshing HAK manifest"
|
||||
case strings.HasPrefix(message, "Using existing hak manifest at "):
|
||||
return "reusing HAK manifest"
|
||||
case strings.HasPrefix(message, "NWScript compiler not found locally. Installing it now"):
|
||||
return "installing script compiler"
|
||||
case strings.HasPrefix(message, "Skipping NWScript compilation for this build."):
|
||||
return "skipping script compilation"
|
||||
case strings.HasPrefix(message, "Validating project"):
|
||||
return "validating project"
|
||||
case strings.HasPrefix(message, "Resolving module HAK order"):
|
||||
return "resolving HAK order"
|
||||
case strings.HasPrefix(message, "Collecting module resources"):
|
||||
return "collecting module resources"
|
||||
case strings.HasPrefix(message, "Writing module archive"):
|
||||
return "writing module archive"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitBuildResult(result pipeline.BuildResult, preflight buildModulePreflightResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Build ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "module: %s\n", c.relPath(result.ModulePath))
|
||||
fmt.Fprintf(c.stdout, "resources: %d\n", result.Resources)
|
||||
if preflight.ManifestStatus != "" {
|
||||
fmt.Fprintf(c.stdout, "hak manifest: %s", preflight.ManifestStatus)
|
||||
if preflight.ManifestPath != "" {
|
||||
fmt.Fprintf(c.stdout, " (%s)", c.relPath(preflight.ManifestPath))
|
||||
}
|
||||
fmt.Fprintln(c.stdout)
|
||||
}
|
||||
if preflight.ScriptCompilationStatus != "" && preflight.ScriptCompilationStatus != "not-needed" {
|
||||
fmt.Fprintf(c.stdout, "script compilation: %s\n", preflight.ScriptCompilationStatus)
|
||||
}
|
||||
if result.TopPackageHAK != "" {
|
||||
fmt.Fprintf(c.stdout, "top package hak: %s\n", c.relPath(result.TopPackageHAK))
|
||||
fmt.Fprintf(c.stdout, "top package tlk: %s\n", c.relPath(result.TopPackageTLK))
|
||||
}
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
fmt.Fprintf(c.stdout, "hak assets: %d\n", result.HAKAssets)
|
||||
fmt.Fprintf(c.stdout, "hak manifest: %s\n", c.relPath(result.Manifest))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitBuildModuleResult(result pipeline.BuildResult, preflight buildModulePreflightResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Build Module ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "module: %s\n", c.relPath(result.ModulePath))
|
||||
fmt.Fprintf(c.stdout, "resources: %d\n", result.Resources)
|
||||
if preflight.ManifestStatus != "" {
|
||||
fmt.Fprintf(c.stdout, "hak manifest: %s", preflight.ManifestStatus)
|
||||
if preflight.ManifestPath != "" {
|
||||
fmt.Fprintf(c.stdout, " (%s)", c.relPath(preflight.ManifestPath))
|
||||
}
|
||||
fmt.Fprintln(c.stdout)
|
||||
}
|
||||
if preflight.ScriptCompilationStatus != "" && preflight.ScriptCompilationStatus != "not-needed" {
|
||||
fmt.Fprintf(c.stdout, "script compilation: %s\n", preflight.ScriptCompilationStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitExtractResult(result pipeline.ExtractResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Extract ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "module: %s\n", c.relPath(result.ModulePath))
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
}
|
||||
if len(result.DeletedArchivePaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "deleted archives: %d\n", len(result.DeletedArchivePaths))
|
||||
}
|
||||
fmt.Fprintf(c.stdout, "written: %d\n", result.Written)
|
||||
fmt.Fprintf(c.stdout, "overwritten: %d\n", result.Overwritten)
|
||||
fmt.Fprintf(c.stdout, "removed: %d\n", result.Removed)
|
||||
fmt.Fprintf(c.stdout, "skipped: %d\n", result.Skipped)
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitValidationResult(report validator.Report, root string, inventoryReport project.InventoryReport) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Validate ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "root: %s\n", c.relPath(root))
|
||||
fmt.Fprintf(c.stdout, "source files: %d\n", inventoryReport.SourceFiles)
|
||||
fmt.Fprintf(c.stdout, "script files: %d\n", inventoryReport.ScriptFiles)
|
||||
fmt.Fprintf(c.stdout, "asset files: %d\n", inventoryReport.AssetFiles)
|
||||
fmt.Fprintf(c.stdout, "module file types: %s\n", strings.Join(inventoryReport.Extensions, ", "))
|
||||
if warnings := report.WarningCount(); warnings > 0 {
|
||||
fmt.Fprintf(c.stdout, "warnings: %d\n", warnings)
|
||||
}
|
||||
fmt.Fprintln(c.stdout, "validation: ok")
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitCompareResult(result pipeline.CompareResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Compare ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "module: %s\n", c.relPath(result.ModulePath))
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
}
|
||||
fmt.Fprintf(c.stdout, "checked resources: %d\n", result.Checked)
|
||||
fmt.Fprintln(c.stdout, "compare: ok")
|
||||
}
|
||||
|
||||
func (c *projectConsole) emitApplyManifestResult(result pipeline.ApplyManifestResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Apply HAK Manifest ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "manifest: %s\n", c.relPath(result.ManifestPath))
|
||||
fmt.Fprintf(c.stdout, "module source: %s\n", c.relPath(result.ModuleSource))
|
||||
fmt.Fprintf(c.stdout, "hak entries: %d\n", result.HAKCount)
|
||||
}
|
||||
|
||||
func (c *projectConsole) relPath(path string) string {
|
||||
if path == "" {
|
||||
return path
|
||||
}
|
||||
rel, err := filepath.Rel(c.projectRoot, path)
|
||||
if err != nil {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
return filepath.ToSlash(rel)
|
||||
}
|
||||
|
||||
type buildHAKConsole struct {
|
||||
stdout io.Writer
|
||||
projectRoot string
|
||||
@@ -1230,6 +1416,7 @@ func runExtract(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "extract")
|
||||
|
||||
files := ctx.args[1:]
|
||||
result, err := pipeline.Extract(p, files...)
|
||||
@@ -1237,18 +1424,7 @@ func runExtract(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "module: %s\n", result.ModulePath)
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
}
|
||||
if len(result.DeletedArchivePaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "deleted archives: %d\n", len(result.DeletedArchivePaths))
|
||||
}
|
||||
fmt.Fprintf(ctx.stdout, "written: %d\n", result.Written)
|
||||
fmt.Fprintf(ctx.stdout, "overwritten: %d\n", result.Overwritten)
|
||||
fmt.Fprintf(ctx.stdout, "removed: %d\n", result.Removed)
|
||||
fmt.Fprintf(ctx.stdout, "skipped: %d\n", result.Skipped)
|
||||
console.emitExtractResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1257,6 +1433,7 @@ func runValidate(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "validate")
|
||||
|
||||
validationReport := validator.ValidateProject(p)
|
||||
emitValidatorReport(ctx.stderr, validationReport)
|
||||
@@ -1265,16 +1442,7 @@ func runValidate(ctx context) error {
|
||||
}
|
||||
|
||||
inventoryReport := p.Inventory.Report()
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "root: %s\n", p.Root)
|
||||
fmt.Fprintf(ctx.stdout, "source files: %d\n", inventoryReport.SourceFiles)
|
||||
fmt.Fprintf(ctx.stdout, "script files: %d\n", inventoryReport.ScriptFiles)
|
||||
fmt.Fprintf(ctx.stdout, "asset files: %d\n", inventoryReport.AssetFiles)
|
||||
fmt.Fprintf(ctx.stdout, "module file types: %s\n", strings.Join(inventoryReport.Extensions, ", "))
|
||||
if warnings := validationReport.WarningCount(); warnings > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "warnings: %d\n", warnings)
|
||||
}
|
||||
fmt.Fprintf(ctx.stdout, "validation: ok\n")
|
||||
console.emitValidationResult(validationReport, p.Root, inventoryReport)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1480,19 +1648,14 @@ func runCompare(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "compare")
|
||||
|
||||
result, err := pipeline.Compare(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "module: %s\n", result.ModulePath)
|
||||
if len(result.HAKPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
}
|
||||
fmt.Fprintf(ctx.stdout, "checked resources: %d\n", result.Checked)
|
||||
fmt.Fprintf(ctx.stdout, "compare: ok\n")
|
||||
console.emitCompareResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1501,6 +1664,7 @@ func runApplyHAKManifest(ctx context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
console := newProjectConsole(ctx, p, "apply-hak-manifest")
|
||||
|
||||
manifestPath := p.HAKManifestPath()
|
||||
if len(ctx.args) > 1 {
|
||||
@@ -1515,10 +1679,7 @@ func runApplyHAKManifest(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "manifest: %s\n", result.ManifestPath)
|
||||
fmt.Fprintf(ctx.stdout, "module source: %s\n", result.ModuleSource)
|
||||
fmt.Fprintf(ctx.stdout, "hak entries: %d\n", result.HAKCount)
|
||||
console.emitApplyManifestResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/pipeline"
|
||||
)
|
||||
|
||||
func TestRunBuildTopPackageUsesCachedCompiledOutputs(t *testing.T) {
|
||||
@@ -233,6 +235,47 @@ func TestTopdataConsoleDebugProgressAndRelativePaths(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectConsoleSuppressesBuildModuleProgressInNormalMode(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
console := &projectConsole{
|
||||
stdout: &stdout,
|
||||
projectRoot: "/workspace/project",
|
||||
projectName: "Test Module",
|
||||
commandName: "build-module",
|
||||
commandLabel: "Build Module",
|
||||
level: logLevelNormal,
|
||||
}
|
||||
|
||||
console.progress("Writing module archive...")
|
||||
|
||||
if stdout.String() != "" {
|
||||
t.Fatalf("expected no normal-mode progress output, got %q", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProjectConsoleEmitsRelativePaths(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
console := &projectConsole{
|
||||
stdout: &stdout,
|
||||
projectRoot: "/workspace/project",
|
||||
projectName: "Test Module",
|
||||
commandName: "compare",
|
||||
commandLabel: "Compare",
|
||||
level: logLevelNormal,
|
||||
}
|
||||
|
||||
console.emitCompareResult(pipeline.CompareResult{
|
||||
ModulePath: "/workspace/project/build/test.mod",
|
||||
HAKPaths: []string{"/workspace/project/build/core.hak"},
|
||||
Checked: 42,
|
||||
})
|
||||
|
||||
output := stdout.String()
|
||||
if !strings.Contains(output, "module: build/test.mod") {
|
||||
t.Fatalf("expected relative module path, got %q", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunBuildModuleToolkitPreflightRefreshesManifestAndSkipsBrokenCompiler(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mkdirAll(t, filepath.Join(root, "src", "module"))
|
||||
@@ -309,8 +352,11 @@ scripts:
|
||||
if _, err := os.Stat(filepath.Join(root, ".cache", "ncs", "use_thing.ncs")); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected broken explicit compiler to skip compilation, got err=%v", err)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "Skipping NWScript compilation for this build.") {
|
||||
t.Fatalf("expected skip-compilation progress in output, got %q", stdout.String())
|
||||
if !strings.Contains(stdout.String(), "script compilation: skipped") {
|
||||
t.Fatalf("expected skip-compilation summary in output, got %q", stdout.String())
|
||||
}
|
||||
if strings.Contains(stdout.String(), "[build-module]") {
|
||||
t.Fatalf("did not expect raw build-module progress in normal output, got %q", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,8 +431,11 @@ scripts:
|
||||
if _, err := os.Stat(compiledScript); err != nil {
|
||||
t.Fatalf("expected installed compiler to compile script: %v", err)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "NWScript compiler not found locally. Installing it now...") {
|
||||
t.Fatalf("expected install progress in output, got %q", stdout.String())
|
||||
if !strings.Contains(stdout.String(), "script compilation: installed") {
|
||||
t.Fatalf("expected install summary in output, got %q", stdout.String())
|
||||
}
|
||||
if strings.Contains(stdout.String(), "[build-module]") {
|
||||
t.Fatalf("did not expect raw build-module progress in normal output, got %q", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+29
-17
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"gitea.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type convertOptions struct {
|
||||
@@ -105,7 +106,7 @@ func printConvertUsage(stdout io.Writer) {
|
||||
_, _ = fmt.Fprintln(stdout, " json-to-2da Convert canonical JSON rows into a 2DA file")
|
||||
_, _ = fmt.Fprintln(stdout, "")
|
||||
_, _ = fmt.Fprintln(stdout, "2da-to-module notes:")
|
||||
_, _ = fmt.Fprintln(stdout, " - --namespace is optional when topdata/templates/config.json declares it for the input table")
|
||||
_, _ = fmt.Fprintln(stdout, " - --namespace is optional when topdata/templates/config.yaml declares it for the input table")
|
||||
_, _ = fmt.Fprintln(stdout, " - --name controls inferred output naming: add_<namespace>_<name>.json")
|
||||
_, _ = fmt.Fprintln(stdout, " - flags accept both --flag value and --flag=value forms")
|
||||
_, _ = fmt.Fprintln(stdout, " - unkeyed rows now fail conversion instead of being skipped")
|
||||
@@ -238,7 +239,7 @@ func parseConvertArgs(args []string, allowFormat bool, defaultCollision string)
|
||||
opts.KeyFields = append(opts.KeyFields, ctx.Template.KeyFields...)
|
||||
}
|
||||
if strings.TrimSpace(opts.Namespace) == "" {
|
||||
return opts, "", "", errors.New("2da-to-module requires --namespace or a topdata/templates/config.json entry for the input table")
|
||||
return opts, "", "", errors.New("2da-to-module requires --namespace or a topdata/templates/config.yaml entry for the input table")
|
||||
}
|
||||
switch opts.Type {
|
||||
case "entry":
|
||||
@@ -420,7 +421,7 @@ func convert2DAToModule(data parsed2DA, outputPath string, opts convertOptions)
|
||||
entries[key] = entry
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return nil, fmt.Errorf("unable to generate keys for row ids %s; declare --key-field or configure key_fields in topdata/templates/config.json", strings.Join(missing, ", "))
|
||||
return nil, fmt.Errorf("unable to generate keys for row ids %s; declare --key-field or configure key_fields in topdata/templates/config.yaml", strings.Join(missing, ", "))
|
||||
}
|
||||
return map[string]any{"entries": entries}, nil
|
||||
}
|
||||
@@ -544,12 +545,12 @@ func normalizeConvertedKeyText(text string) string {
|
||||
}
|
||||
|
||||
type convertTemplateTable struct {
|
||||
Namespace string `json:"namespace"`
|
||||
KeyFields []string `json:"key_fields"`
|
||||
Namespace string `json:"namespace" yaml:"namespace"`
|
||||
KeyFields []string `json:"key_fields" yaml:"key_fields"`
|
||||
}
|
||||
|
||||
type convertTemplateConfig struct {
|
||||
Tables map[string]convertTemplateTable `json:"tables"`
|
||||
Tables map[string]convertTemplateTable `json:"tables" yaml:"tables"`
|
||||
}
|
||||
|
||||
type convertContext struct {
|
||||
@@ -617,20 +618,31 @@ func loadTemplateTableConfig(p *project.Project, input string) (convertTemplateT
|
||||
}
|
||||
|
||||
func loadConvertTemplateConfig(p *project.Project) (convertTemplateConfig, error) {
|
||||
configPath := filepath.Join(p.TopDataSourceDir(), "templates", "config.json")
|
||||
raw, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return convertTemplateConfig{}, nil
|
||||
templatesDir := filepath.Join(p.TopDataSourceDir(), "templates")
|
||||
for _, candidate := range []string{"config.yaml", "config.yml", "config.json"} {
|
||||
configPath := filepath.Join(templatesDir, candidate)
|
||||
raw, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return convertTemplateConfig{}, fmt.Errorf("read %s: %w", configPath, err)
|
||||
}
|
||||
return convertTemplateConfig{}, fmt.Errorf("read %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
var config convertTemplateConfig
|
||||
if err := json.Unmarshal(raw, &config); err != nil {
|
||||
return convertTemplateConfig{}, fmt.Errorf("parse %s: %w", configPath, err)
|
||||
var config convertTemplateConfig
|
||||
switch filepath.Ext(configPath) {
|
||||
case ".yaml", ".yml":
|
||||
if err := yaml.Unmarshal(raw, &config); err != nil {
|
||||
return convertTemplateConfig{}, fmt.Errorf("parse %s: %w", configPath, err)
|
||||
}
|
||||
default:
|
||||
if err := json.Unmarshal(raw, &config); err != nil {
|
||||
return convertTemplateConfig{}, fmt.Errorf("parse %s: %w", configPath, err)
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
return config, nil
|
||||
return convertTemplateConfig{}, nil
|
||||
}
|
||||
|
||||
func resolveConvertOutputContext(p *project.Project, output string) (convertTemplateTable, error) {
|
||||
|
||||
@@ -11,21 +11,27 @@ import (
|
||||
|
||||
func TestRunConvertCommandInfersTemplateNamespaceAndOutput(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {"name": "Test", "resref": "test"},
|
||||
"paths": {"source": "src", "assets": "assets", "build": "build"},
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "appearance", "modules"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_appearance": {
|
||||
"namespace": "appearance",
|
||||
"key_fields": ["LABEL"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.yaml"), `
|
||||
tables:
|
||||
template_appearance:
|
||||
namespace: appearance
|
||||
key_fields:
|
||||
- LABEL
|
||||
`)
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "template_appearance.2da"), "2DA V2.0\n\nLABEL STRING_REF NAME\n960 \"Zombie, Ash, Hot\" **** Zombie_Ash_Hot\n961 \"Zombie, Ash, Done\" **** Zombie_Ash_Done\n")
|
||||
|
||||
oldCwd, err := os.Getwd()
|
||||
@@ -64,20 +70,26 @@ func TestRunConvertCommandInfersTemplateNamespaceAndOutput(t *testing.T) {
|
||||
|
||||
func TestRunConvertCommand2DAToJSONInfersTemplateKeys(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {"name": "Test", "resref": "test"},
|
||||
"paths": {"source": "src", "assets": "assets", "build": "build"},
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_appearance": {
|
||||
"namespace": "appearance",
|
||||
"key_fields": ["LABEL"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.yaml"), `
|
||||
tables:
|
||||
template_appearance:
|
||||
namespace: appearance
|
||||
key_fields:
|
||||
- LABEL
|
||||
`)
|
||||
inputPath := filepath.Join(root, "topdata", "templates", "template_appearance.2da")
|
||||
writeFile(t, inputPath, "2DA V2.0\n\nLABEL STRING_REF NAME\n960 \"Zombie, Ash, Hot\" **** Zombie_Ash_Hot\n961 \"Zombie, Ash, Done\" **** Zombie_Ash_Done\n")
|
||||
|
||||
@@ -121,21 +133,28 @@ func TestRunConvertCommand2DAToJSONInfersTemplateKeys(t *testing.T) {
|
||||
|
||||
func TestRunConvertCommand2DAToJSONInfersOutputDatasetKeysFromConfig(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {"name": "Test", "resref": "test"},
|
||||
"paths": {"source": "src", "assets": "assets", "build": "build"},
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "data", "soundset"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_soundset": {
|
||||
"namespace": "soundset",
|
||||
"key_fields": ["RESREF", "LABEL"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.yaml"), `
|
||||
tables:
|
||||
template_soundset:
|
||||
namespace: soundset
|
||||
key_fields:
|
||||
- RESREF
|
||||
- LABEL
|
||||
`)
|
||||
inputPath := filepath.Join(root, "scratch_soundset.2da")
|
||||
writeFile(t, inputPath, "2DA V2.0\n\nLABEL RESREF STRREF GENDER TYPE\n0 \"Bandit Male\" nw_bandit 100 1 4\n1 \"Bandit Female\" nw_bandit 101 2 4\n")
|
||||
|
||||
@@ -179,20 +198,26 @@ func TestRunConvertCommand2DAToJSONInfersOutputDatasetKeysFromConfig(t *testing.
|
||||
|
||||
func TestRunConvertCommand2DAToJSONInfersAmbientTemplateKeysFromResource(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {"name": "Test", "resref": "test"},
|
||||
"paths": {"source": "src", "assets": "assets", "build": "build"},
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_ambientmusic": {
|
||||
"namespace": "ambientmusic",
|
||||
"key_fields": ["Resource"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.yaml"), `
|
||||
tables:
|
||||
template_ambientmusic:
|
||||
namespace: ambientmusic
|
||||
key_fields:
|
||||
- Resource
|
||||
`)
|
||||
inputPath := filepath.Join(root, "topdata", "templates", "template_ambientmusic.2da")
|
||||
writeFile(t, inputPath, "2DA V2.0\n\nDescription DisplayName Resource Stinger1 Stinger2 Stinger3\n0 61901 **** **** **** **** ****\n1 61842 **** mus_ruralday1 **** **** ****\n2 61843 **** mus_ruralday2 **** **** ****\n")
|
||||
|
||||
@@ -239,11 +264,18 @@ func TestRunConvertCommand2DAToJSONInfersAmbientTemplateKeysFromResource(t *test
|
||||
|
||||
func TestRunConvertCommand2DAToJSONSuffixesDuplicateGeneratedKeys(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {"name": "Test", "resref": "test"},
|
||||
"paths": {"source": "src", "assets": "assets", "build": "build"},
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
inputPath := filepath.Join(root, "dup.2da")
|
||||
writeFile(t, inputPath, "2DA V2.0\n\nResource\n1 cmp_reserved\n2 cmp_reserved\n")
|
||||
|
||||
@@ -353,14 +385,13 @@ func TestRunConvertCommandFailsOnUnkeyedRows(t *testing.T) {
|
||||
"topdata": {"source": "topdata", "build": "build/topdata"}
|
||||
}`+"\n")
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_appearance": {
|
||||
"namespace": "appearance",
|
||||
"key_fields": ["STRING_REF"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.yaml"), `
|
||||
tables:
|
||||
template_appearance:
|
||||
namespace: appearance
|
||||
key_fields:
|
||||
- STRING_REF
|
||||
`)
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "template_appearance.2da"), "2DA V2.0\n\nLABEL STRING_REF NAME\n960 \"Zombie, Ash, Hot\" **** Zombie_Ash_Hot\n")
|
||||
|
||||
oldCwd, err := os.Getwd()
|
||||
@@ -384,6 +415,61 @@ func TestRunConvertCommandFailsOnUnkeyedRows(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunConvertCommandFallsBackToLegacyTemplateJSONConfig(t *testing.T) {
|
||||
root := testProjectRoot(t)
|
||||
writeFile(t, filepath.Join(root, "nwn-tool.yaml"), `
|
||||
module:
|
||||
name: Test
|
||||
resref: test
|
||||
paths:
|
||||
source: src
|
||||
assets: assets
|
||||
build: build
|
||||
topdata:
|
||||
source: topdata
|
||||
build: build/topdata
|
||||
`)
|
||||
mkdirAll(t, filepath.Join(root, "topdata", "templates"))
|
||||
writeFile(t, filepath.Join(root, "topdata", "templates", "config.json"), `{
|
||||
"tables": {
|
||||
"template_appearance": {
|
||||
"namespace": "appearance",
|
||||
"key_fields": ["LABEL"]
|
||||
}
|
||||
}
|
||||
}`+"\n")
|
||||
inputPath := filepath.Join(root, "topdata", "templates", "template_appearance.2da")
|
||||
writeFile(t, inputPath, "2DA V2.0\n\nLABEL\n960 Zombie_Ash_Hot\n")
|
||||
|
||||
oldCwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("getwd: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.Chdir(oldCwd)
|
||||
})
|
||||
if err := os.Chdir(root); err != nil {
|
||||
t.Fatalf("chdir: %v", err)
|
||||
}
|
||||
|
||||
outputPath := filepath.Join(root, "out", "appearance.json")
|
||||
if err := RunConvertCommand([]string{
|
||||
"2da-to-json",
|
||||
"topdata/templates/template_appearance.2da",
|
||||
outputPath,
|
||||
}, &bytes.Buffer{}); err != nil {
|
||||
t.Fatalf("RunConvertCommand failed: %v", err)
|
||||
}
|
||||
|
||||
raw, err := os.ReadFile(outputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read output: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"appearance:zombie_ash_hot"`) {
|
||||
t.Fatalf("expected legacy JSON template config fallback to be honored, got %s", string(raw))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvert2DAToModuleFailsOnDuplicateGeneratedKeysByDefault(t *testing.T) {
|
||||
_, err := convert2DAToModule(parsed2DA{
|
||||
Columns: []string{"LABEL"},
|
||||
|
||||
Reference in New Issue
Block a user