@@ -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
153229func ensureGoModModulePath (path , want string ) error {
0 commit comments