Skip to content

Commit 73b28d6

Browse files
committed
Handle README.md
1 parent 14e8035 commit 73b28d6

3 files changed

Lines changed: 110 additions & 14 deletions

File tree

internal/lib/run.go

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,35 @@ func processDependency(cfg Config, dep Dependency) error {
9797
modulePath := path.Join(cfg.ModuleBase, relPath, majorVersionSuffix(requestedVersion))
9898
outDir := filepath.Join(cfg.BaseOutputDir, filepath.FromSlash(relPath))
9999

100-
// Idempotency: if npmpackage.json already records the requested version,
101-
// nothing on disk needs to change. We don't even hit the npm registry.
102-
if existing, ok := readNpmPackageVersion(outDir); ok && existing == requestedVersion {
103-
return nil
100+
// Idempotency only covers the npm-version-driven work (network fetch,
101+
// tarball unpack, npmpackage.json, hugo.toml). The go.mod module line and
102+
// the README's import block depend on cfg.ModuleBase, which can change
103+
// between runs even when the npm version doesn't — so those are always
104+
// reconciled at the end.
105+
existing, hasExisting := readNpmPackageVersion(outDir)
106+
if !hasExisting || existing != requestedVersion {
107+
if err := syncPackageFromNpm(dep, outDir); err != nil {
108+
return err
109+
}
104110
}
105111

112+
// `hugo mod init` refuses to overwrite an existing go.mod. If one already
113+
// exists, rewrite just the module statement if needed — that handles a
114+
// major-version bump (or a --module-base change) without touching any
115+
// require/replace lines the user may have added.
116+
goModPath := filepath.Join(outDir, "go.mod")
117+
if _, err := os.Stat(goModPath); err == nil {
118+
if err := ensureGoModModulePath(goModPath, modulePath); err != nil {
119+
return err
120+
}
121+
} else if err := runHugoModInit(outDir, modulePath); err != nil {
122+
return err
123+
}
124+
125+
return writeReadme(outDir, dep.Name, modulePath)
126+
}
127+
128+
func syncPackageFromNpm(dep Dependency, outDir string) error {
106129
npmv, err := FetchPackageVersion(dep.Name, stripRangePrefix(dep.VersionRange))
107130
if err != nil {
108131
return err
@@ -135,19 +158,72 @@ func processDependency(cfg Config, dep Dependency) error {
135158
// The Hugo mount target keeps the original npm name (including any "@"
136159
// scope prefix) so user code can write `import x from "@scope/pkg"`. Only
137160
// Go module paths need the "@" stripped; the virtual assets path doesn't.
138-
if err := writeHugoConfig(outDir, dep.Name); err != nil {
139-
return err
161+
return writeHugoConfig(outDir, dep.Name)
162+
}
163+
164+
const (
165+
readmeMarkerBegin = "<!-- npmtohugomod:imports:begin -->"
166+
readmeMarkerEnd = "<!-- npmtohugomod:imports:end -->"
167+
)
168+
169+
// writeReadme creates the package README on first run, or refreshes the
170+
// marker-delimited "Hugo import" block on subsequent runs. If the file
171+
// exists but the markers were stripped, it's left untouched.
172+
func writeReadme(outDir, npmName, modulePath string) error {
173+
readmePath := filepath.Join(outDir, "README.md")
174+
importsBlock := renderReadmeImportsBlock(modulePath)
175+
176+
existing, err := os.ReadFile(readmePath)
177+
if err != nil {
178+
if !os.IsNotExist(err) {
179+
return err
180+
}
181+
fresh := renderReadmeTemplate(npmName, importsBlock)
182+
return writeTextFile(readmePath, []byte(fresh))
140183
}
141184

142-
// `hugo mod init` refuses to overwrite an existing go.mod. If one already
143-
// exists, rewrite just the module statement if needed — that handles a
144-
// major-version bump that changes the "/vN" suffix without touching any
145-
// require/replace lines the user may have added.
146-
goModPath := filepath.Join(outDir, "go.mod")
147-
if _, err := os.Stat(goModPath); err == nil {
148-
return ensureGoModModulePath(goModPath, modulePath)
185+
updated, ok := replaceBetweenMarkers(string(existing), readmeMarkerBegin, readmeMarkerEnd, importsBlock)
186+
if !ok || updated == string(existing) {
187+
return nil
188+
}
189+
return writeTextFile(readmePath, []byte(updated))
190+
}
191+
192+
func renderReadmeImportsBlock(modulePath string) string {
193+
var b strings.Builder
194+
b.WriteString("\n## Hugo import\n\n")
195+
b.WriteString("Add this to your Hugo project's `hugo.toml`:\n\n")
196+
b.WriteString("```toml\n")
197+
b.WriteString("[module]\n")
198+
b.WriteString(" [[module.imports]]\n")
199+
fmt.Fprintf(&b, " path = %q\n", modulePath)
200+
b.WriteString("```\n")
201+
return b.String()
202+
}
203+
204+
func renderReadmeTemplate(npmName, importsBlock string) string {
205+
var b strings.Builder
206+
fmt.Fprintf(&b, "# %s\n\n", npmName)
207+
fmt.Fprintf(&b, "Hugo Module wrapper for the npm package [%s](https://www.npmjs.com/package/%s).\n\n", npmName, npmName)
208+
b.WriteString(readmeMarkerBegin)
209+
b.WriteString(importsBlock)
210+
b.WriteString(readmeMarkerEnd)
211+
b.WriteString("\n")
212+
return b.String()
213+
}
214+
215+
func replaceBetweenMarkers(content, begin, end, replacement string) (string, bool) {
216+
beginIdx := strings.Index(content, begin)
217+
if beginIdx == -1 {
218+
return content, false
219+
}
220+
afterBegin := beginIdx + len(begin)
221+
endRel := strings.Index(content[afterBegin:], end)
222+
if endRel == -1 {
223+
return content, false
149224
}
150-
return runHugoModInit(outDir, modulePath)
225+
endIdx := afterBegin + endRel
226+
return content[:afterBegin] + replacement + content[endIdx:], true
151227
}
152228

153229
func ensureGoModModulePath(path, want string) error {

testscripts/hugodocs.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ grep '^module example.com/test/alpinejs/persist/v3$' alpinejs/persist/go.mod
2525
grep '^module example.com/test/hotwired/turbo/v8$' hotwired/turbo/go.mod
2626
grep '^module example.com/test/alpinejs/v3$' alpinejs/go.mod
2727

28+
# READMEs are generated with the matching module path.
29+
grep 'path = "example.com/test/alpinejs/focus/v3"' alpinejs/focus/README.md
30+
grep 'path = "example.com/test/alpinejs/persist/v3"' alpinejs/persist/README.md
31+
grep 'path = "example.com/test/hotwired/turbo/v8"' hotwired/turbo/README.md
32+
grep 'path = "example.com/test/alpinejs/v3"' alpinejs/README.md
33+
2834
# Idempotency: re-running with the same package.json should be a no-op
2935
# (in particular it must not fail on `hugo mod init` complaining about an
3036
# existing go.mod).

testscripts/popper.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ cmp popperjs/core/hugo.toml golden/popperjs/core/hugo.toml
1212
# local Hugo install, so we only assert the module line.
1313
grep '^module example.com/test/popperjs/core/v2$' popperjs/core/go.mod
1414

15+
# README is generated with a marker-delimited Hugo import block.
16+
exists popperjs/core/README.md
17+
grep 'path = "example.com/test/popperjs/core/v2"' popperjs/core/README.md
18+
grep 'npmtohugomod:imports:begin' popperjs/core/README.md
19+
grep 'npmtohugomod:imports:end' popperjs/core/README.md
20+
21+
# Changing --module-base updates the import path in both README and go.mod
22+
# even though the npm version is unchanged.
23+
npmtohugomod --module-base other.example.com/foo
24+
! stderr .
25+
grep '^module other.example.com/foo/popperjs/core/v2$' popperjs/core/go.mod
26+
grep 'path = "other.example.com/foo/popperjs/core/v2"' popperjs/core/README.md
27+
! grep 'example.com/test' popperjs/core/README.md
28+
1529
# We read dependencies section only
1630
# We ignore any version range syntax, fetch specific versions only.
1731
-- package.json --

0 commit comments

Comments
 (0)