Opt-In Wiki Generation

This commit is contained in:
2026-04-18 09:11:03 +02:00
parent 338b4abf3b
commit 6daf5bf7a7
6 changed files with 96 additions and 29 deletions
+3 -1
View File
@@ -508,8 +508,10 @@ func parseBuildTopDataArgs(commandName string, args []string) (topdata.BuildAndP
switch arg {
case "--force":
opts.Force = true
case "--wiki":
opts.BuildWiki = true
case "-h", "--help":
return opts, fmt.Errorf("usage: %s [--force]", commandName)
return opts, fmt.Errorf("usage: %s [--force] [--wiki]", commandName)
default:
return opts, fmt.Errorf("unknown %s argument %q", commandName, arg)
}
+24 -14
View File
@@ -135,7 +135,15 @@ func Build(p *project.Project, progress func(string)) (BuildResult, error) {
return BuildNative(p, progress)
}
type NativeBuildOptions struct {
BuildWiki bool
}
func BuildNative(p *project.Project, progress func(string)) (BuildResult, error) {
return BuildNativeWithOptions(p, NativeBuildOptions{BuildWiki: true}, progress)
}
func BuildNativeWithOptions(p *project.Project, opts NativeBuildOptions, progress func(string)) (BuildResult, error) {
if progress == nil {
progress = func(string) {}
}
@@ -152,26 +160,28 @@ func BuildNative(p *project.Project, progress func(string)) (BuildResult, error)
return BuildResult{}, fmt.Errorf("topdata validation failed with %d error(s)", report.ErrorCount())
}
var prebuiltWiki *wikiResult
if newestWikiSource, _, err := newestRelevantWikiSource(p.TopDataSourceDir()); err == nil && !newestWikiSource.IsZero() {
statePath := filepath.Join(p.TopDataSourceDir(), wikiRootDirName, wikiStateFileName)
outputDir := filepath.Join(p.TopDataSourceDir(), wikiRootDirName, wikiPagesDirName)
if stateInfo, err := os.Stat(statePath); err == nil && !newestWikiSource.After(stateInfo.ModTime()) {
if outputInfo, err := os.Stat(outputDir); err == nil && outputInfo.IsDir() {
if state, err := loadWikiState(statePath); err == nil {
prebuiltWiki = &wikiResult{
OutputDir: outputDir,
PageCount: state.Pages,
Status: wikiSkippedStatus,
Digest: state.Digest,
if opts.BuildWiki {
if newestWikiSource, _, err := newestRelevantWikiSource(p.TopDataSourceDir()); err == nil && !newestWikiSource.IsZero() {
statePath := filepath.Join(p.TopDataSourceDir(), wikiRootDirName, wikiStateFileName)
outputDir := filepath.Join(p.TopDataSourceDir(), wikiRootDirName, wikiPagesDirName)
if stateInfo, err := os.Stat(statePath); err == nil && !newestWikiSource.After(stateInfo.ModTime()) {
if outputInfo, err := os.Stat(outputDir); err == nil && outputInfo.IsDir() {
if state, err := loadWikiState(statePath); err == nil {
prebuiltWiki = &wikiResult{
OutputDir: outputDir,
PageCount: state.Pages,
Status: wikiSkippedStatus,
Digest: state.Digest,
}
}
}
}
}
}
return buildNativeUnchecked(p, progress, prebuiltWiki)
return buildNativeUnchecked(p, opts, progress, prebuiltWiki)
}
func buildNativeUnchecked(p *project.Project, progress func(string), prebuiltWiki *wikiResult) (BuildResult, error) {
func buildNativeUnchecked(p *project.Project, opts NativeBuildOptions, progress func(string), prebuiltWiki *wikiResult) (BuildResult, error) {
if progress == nil {
progress = func(string) {}
}
@@ -333,7 +343,7 @@ func buildNativeUnchecked(p *project.Project, progress func(string), prebuiltWik
wikiBuild := wikiResult{Status: wikiSkippedStatus}
if prebuiltWiki != nil {
wikiBuild = *prebuiltWiki
} else {
} else if opts.BuildWiki {
wikiBuild, err = buildWiki(p, BuildResult{
Mode: "native",
Output2DADir: output2DA,
+12 -8
View File
@@ -45,14 +45,6 @@ func resolveReleasedPartsManifest(p *project.Project, progress func(string)) (ma
if progress == nil {
progress = func(string) {}
}
spec, err := deriveSowAssetsRepoSpec(p.Root)
if err != nil {
return nil, err
}
manifestURL, err := resolvePartsManifestAssetURL(spec)
if err != nil {
return nil, err
}
cachePath := filepath.Join(p.Root, ".cache", "topdata", partsManifestCacheFileName)
if strings.TrimSpace(os.Getenv("SOW_PARTS_MANIFEST_REFRESH")) == "" {
manifest, fresh, err := readFreshPartsManifestCache(cachePath, partsManifestCacheMaxAge)
@@ -63,6 +55,14 @@ func resolveReleasedPartsManifest(p *project.Project, progress func(string)) (ma
return inventoryFromPartsManifest(manifest), nil
}
}
spec, err := deriveSowAssetsRepoSpec(p.Root)
if err != nil {
return nil, err
}
manifestURL, err := resolvePartsManifestAssetURL(spec)
if err != nil {
return nil, err
}
progress(fmt.Sprintf("Fetching released parts manifest from %s...", manifestURL))
manifest, err := fetchPartsManifest(manifestURL)
if err != nil {
@@ -220,6 +220,10 @@ func writePartsManifestCache(path string, manifest *partsManifest) error {
payload = append(payload, '\n')
current, err := os.ReadFile(path)
if err == nil && bytes.Equal(current, payload) {
now := time.Now()
if err := os.Chtimes(path, now, now); err != nil {
return fmt.Errorf("refresh parts manifest cache timestamp: %w", err)
}
return nil
}
if err != nil && !os.IsNotExist(err) {
+5 -4
View File
@@ -60,7 +60,8 @@ func BuildAndPackage(p *project.Project, progress func(string)) (PackageResult,
}
type BuildAndPackageOptions struct {
Force bool
Force bool
BuildWiki bool
}
func BuildAndPackageWithOptions(p *project.Project, opts BuildAndPackageOptions, progress func(string)) (PackageResult, error) {
@@ -87,11 +88,11 @@ func BuildAndPackageWithOptions(p *project.Project, opts BuildAndPackageOptions,
}
}
return buildAndPackageFull(p, progress)
return buildAndPackageFull(p, opts, progress)
}
func buildAndPackageFull(p *project.Project, progress func(string)) (PackageResult, error) {
nativeResult, err := BuildNative(p, progress)
func buildAndPackageFull(p *project.Project, opts BuildAndPackageOptions, progress func(string)) (PackageResult, error) {
nativeResult, err := BuildNativeWithOptions(p, NativeBuildOptions{BuildWiki: opts.BuildWiki}, progress)
if err != nil {
return PackageResult{}, err
}
+2 -2
View File
@@ -1554,7 +1554,7 @@ func validateNativeBuildability(p *project.Project, report *ValidationReport) {
return
}
defer restoreLockfiles(lockSnapshot)
if _, err := buildNativeUnchecked(&clone, nil, nil); err != nil {
if _, err := buildNativeUnchecked(&clone, NativeBuildOptions{}, nil, nil); err != nil {
report.Diagnostics = append(report.Diagnostics, Diagnostic{
Severity: SeverityError,
Path: p.TopDataSourceDir(),
@@ -1958,7 +1958,7 @@ func compareNativeSelfCheck(p *project.Project, actual2DA, actualTLK string, pro
nativeProject := *p
nativeProject.Config.TopData.Build = tempBuild
nativeResult, err := buildNativeUnchecked(&nativeProject, nil, nil)
nativeResult, err := buildNativeUnchecked(&nativeProject, NativeBuildOptions{}, nil, nil)
if err != nil {
return 0, 0, 0, len(projectOutputs), nil
}
+50
View File
@@ -86,6 +86,56 @@ func TestBuildNativeGeneratesAndSkipsWikiPages(t *testing.T) {
}
}
func TestBuildAndPackageBuildsWikiOnlyWhenRequested(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "skills"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "base.json"), `{
"output": "skills.2da",
"columns": ["Label", "Name", "Description", "HideFromLevelUp", "Untrained", "KeyAbility", "ArmorCheckPenalty", "Constant"],
"rows": [
{
"id": 0,
"key": "skills:athletics",
"Label": "Athletics",
"Name": {"tlk": {"key": "skills:athletics.name", "text": "Athletics"}},
"Description": {"tlk": {"key": "skills:athletics.description", "text": "Skill text."}},
"HideFromLevelUp": "0",
"Untrained": "1",
"KeyAbility": "STR",
"ArmorCheckPenalty": "1",
"Constant": "SKILL_ATHLETICS"
}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "skills", "lock.json"), `{"skills:athletics":0}`+"\n")
proj := testProject(root)
proj.Config.TopData.ReferenceBuilder = ""
result, err := BuildAndPackageWithOptions(proj, BuildAndPackageOptions{}, nil)
if err != nil {
t.Fatalf("BuildAndPackageWithOptions failed: %v", err)
}
if result.WikiOutputDir != "" || result.WikiPages != 0 {
t.Fatalf("expected wiki generation to be skipped by default, got dir=%q pages=%d", result.WikiOutputDir, result.WikiPages)
}
if _, err := os.Stat(filepath.Join(root, "topdata", ".wiki", "state.json")); !os.IsNotExist(err) {
t.Fatalf("expected no wiki state without explicit wiki build, got %v", err)
}
result, err = BuildAndPackageWithOptions(proj, BuildAndPackageOptions{Force: true, BuildWiki: true}, nil)
if err != nil {
t.Fatalf("BuildAndPackageWithOptions with wiki failed: %v", err)
}
if result.WikiPages != 1 {
t.Fatalf("expected one generated wiki page when requested, got %d", result.WikiPages)
}
if _, err := os.Stat(filepath.Join(root, "topdata", ".wiki", "state.json")); err != nil {
t.Fatalf("expected wiki state after explicit wiki build: %v", err)
}
}
func TestBuildNativeRegeneratesWikiWhenTLKTextChanges(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "skills"))