Skip to content

Commit

Permalink
Add resource vendoring
Browse files Browse the repository at this point in the history
Fixes #13309
  • Loading branch information
bep committed Jan 28, 2025
1 parent e08d9af commit ee06931
Show file tree
Hide file tree
Showing 24 changed files with 492 additions and 123 deletions.
6 changes: 5 additions & 1 deletion commands/commandeer.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,11 @@ func (r *rootCommand) Name() string {
}

func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
b := newHugoBuilder(r, nil)
var vendor bool
if vendorCmd, ok := cd.Command.(vendoredCommand); ok {
vendor = vendorCmd.IsVendorCommand()
}
b := newHugoBuilder(r, nil, vendor)

if !r.buildWatch {
defer b.postBuild("Total", time.Now())
Expand Down
23 changes: 19 additions & 4 deletions commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
func newExec() (*simplecobra.Exec, error) {
rootCmd := &rootCommand{
commands: []simplecobra.Commander{
newHugoBuildCmd(),
newHugoBuildCmd(false),
newHugoBuildCmd(true),
newVersionCmd(),
newEnvCommand(),
newServerCommand(),
Expand All @@ -42,26 +43,40 @@ func newExec() (*simplecobra.Exec, error) {
return simplecobra.New(rootCmd)
}

func newHugoBuildCmd() simplecobra.Commander {
return &hugoBuildCommand{}
func newHugoBuildCmd(vendor bool) simplecobra.Commander {
return &hugoBuildCommand{
vendor: vendor,
}
}

// hugoBuildCommand just delegates to the rootCommand.
type hugoBuildCommand struct {
rootCmd *rootCommand
vendor bool
}

func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
return nil
}

func (c *hugoBuildCommand) Name() string {
if c.vendor {
return "vendor"
}
return "build"
}

type vendoredCommand interface {
IsVendorCommand() bool
}

func (c *hugoBuildCommand) IsVendorCommand() bool {
return c.vendor
}

func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
c.rootCmd = cd.Root.Command.(*rootCommand)
return c.rootCmd.initRootCommand("build", cd)
return c.rootCmd.initRootCommand(c.Name(), cd)
}

func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
Expand Down
4 changes: 3 additions & 1 deletion commands/hugobuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ type hugoBuilder struct {
showErrorInBrowser bool

errState hugoBuilderErrState

vendor bool
}

var errConfigNotSet = errors.New("config not set")
Expand Down Expand Up @@ -1046,11 +1048,11 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
}
}
cfg.Set("environment", c.r.environment)

cfg.Set("internal", maps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
"vendor": c.vendor,
"fastRenderMode": c.fastRenderMode,
})

Expand Down
4 changes: 3 additions & 1 deletion commands/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,15 @@ const (
configChangeGoWork = "go work file"
)

func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
func newHugoBuilder(r *rootCommand, s *serverCommand, vendor bool, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
var visitedURLs *types.EvictingQueue[string]
if s != nil && !s.disableFastRender {
visitedURLs = types.NewEvictingQueue[string](20)
}
return &hugoBuilder{
r: r,
s: s,
vendor: vendor,
visitedURLs: visitedURLs,
fullRebuildSem: semaphore.NewWeighted(1),
debounce: debounce.New(4 * time.Second),
Expand Down Expand Up @@ -563,6 +564,7 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
c.hugoBuilder = newHugoBuilder(
c.r,
c,
false,
func(reloaded bool) error {
if !reloaded {
if err := c.createServerPorts(cd); err != nil {
Expand Down
19 changes: 12 additions & 7 deletions common/hugio/writers.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,25 @@ func NewMultiWriteCloser(writeClosers ...io.WriteCloser) io.WriteCloser {
return multiWriteCloser{Writer: io.MultiWriter(writers...), closers: writeClosers}
}

// NewWriteCloser creates a new io.WriteCloser with the given writer and closer.
func NewWriteCloser(w io.Writer, closer io.Closer) io.WriteCloser {
return struct {
io.Writer
io.Closer
}{
w,
closer,
}
}

// ToWriteCloser creates an io.WriteCloser from the given io.Writer.
// If it's not already, one will be created with a Close method that does nothing.
func ToWriteCloser(w io.Writer) io.WriteCloser {
if rw, ok := w.(io.WriteCloser); ok {
return rw
}

return struct {
io.Writer
io.Closer
}{
w,
io.NopCloser(nil),
}
return NewWriteCloser(w, io.NopCloser(nil))
}

// ToReadCloser creates an io.ReadCloser from the given io.Reader.
Expand Down
6 changes: 6 additions & 0 deletions common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ func (i HugoInfo) IsExtended() bool {
return IsExtended
}

// IsVendor returns whether we're running as `hugo vendor`.
func (i HugoInfo) IsVendor() bool {
return i.conf.Vendor()
}

// WorkingDir returns the project working directory.
func (i HugoInfo) WorkingDir() string {
return i.conf.WorkingDir()
Expand Down Expand Up @@ -166,6 +171,7 @@ type ConfigProvider interface {
WorkingDir() string
IsMultihost() bool
IsMultilingual() bool
Vendor() bool
}

// NewInfo creates a new Hugo Info object.
Expand Down
1 change: 1 addition & 0 deletions config/allconfig/allconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type InternalConfig struct {
Watch bool
FastRenderMode bool
LiveReloadPort int
Vendor bool
}

// All non-params config keys for language.
Expand Down
4 changes: 4 additions & 0 deletions config/allconfig/configlanguage.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (c ConfigLanguage) Watching() bool {
return c.m.Base.Internal.Watch
}

func (c ConfigLanguage) Vendor() bool {
return c.m.Base.Internal.Vendor
}

func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
if !c.Watching() {
return identity.NopManager
Expand Down
1 change: 1 addition & 0 deletions config/commonConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (b BuildConfig) clone() BuildConfig {
return b
}

// TODO1 remove, but first add a deprecation warning somewhere.
func (b BuildConfig) UseResourceCache(err error) bool {
if b.UseResourceCacheWhen == "never" {
return false
Expand Down
1 change: 1 addition & 0 deletions config/configProvider.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type AllProvider interface {
BuildDrafts() bool
Running() bool
Watching() bool
Vendor() bool
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
FastRenderMode() bool
PrintUnusedTemplates() bool
Expand Down
4 changes: 3 additions & 1 deletion hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ const (
ComponentFolderAssets = "assets"
ComponentFolderI18n = "i18n"

FolderResources = "resources"
FolderVendor = "_vendor"

FolderResources = "resources" // TODO1 remove.
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.

NameContentData = "_content"
Expand Down
6 changes: 5 additions & 1 deletion hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,16 @@ type SourceFilesystems struct {
Archetypes *SourceFilesystem
Assets *SourceFilesystem

// Note that this can not be mounted. It's currently fixed at the project root.
Vendor *SourceFilesystem

AssetsWithDuplicatesPreserved *SourceFilesystem

RootFss []*hugofs.RootMappingFs

// Writable filesystem on top the project's resources directory,
// with any sub module's resource fs layered below.
ResourcesCache afero.Fs
ResourcesCache afero.Fs // TODO1 remove this.

// The work folder (may be a composite of project and theme components).
Work afero.Fs
Expand Down Expand Up @@ -575,6 +578,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
overlayMountsPreserveDupes := b.theBigFs.overlayMounts.WithDirsMerger(hugofs.AppendDirsMerger)
b.result.Data = createView(files.ComponentFolderData, overlayMountsPreserveDupes)
b.result.I18n = createView(files.ComponentFolderI18n, overlayMountsPreserveDupes)
b.result.Vendor = createView(files.FolderVendor, overlayMountsPreserveDupes)
b.result.AssetsWithDuplicatesPreserved = createView(files.ComponentFolderAssets, overlayMountsPreserveDupes)

contentFs := hugofs.NewComponentFs(
Expand Down
23 changes: 23 additions & 0 deletions hugolib/hugo_sites_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
if err := h.postProcess(infol); err != nil {
h.SendError(fmt.Errorf("postProcess: %w", err))
}

if err := h.writeVendor(infol); err != nil {
h.SendError(fmt.Errorf("writeVendor: %w", err))
}
}

if h.Metrics != nil {
Expand Down Expand Up @@ -693,6 +697,25 @@ func (h *HugoSites) postProcess(l logg.LevelLogger) error {
return g.Wait()
}

func (h *HugoSites) writeVendor(l logg.LevelLogger) error {
if !h.Conf.Vendor() {
return nil
}
l = l.WithField("step", "writeVendor")
defer loggers.TimeTrackf(l, time.Now(), nil, "")

v := h.ResourceSpec.Vendorer
if err := v.Finalize(); err != nil {
return err
}

for _, vf := range v.VendoredResources {
fmt.Println("=== ==", vf.Path, vf.Hash)
}

return nil
}

func (h *HugoSites) writeBuildStats() error {
if h.ResourceSpec == nil {
panic("h.ResourceSpec is nil")
Expand Down
25 changes: 15 additions & 10 deletions hugolib/integrationtest_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,14 +350,18 @@ func (s *IntegrationTestBuilder) AssertNoRenderShortcodesArtifacts() {
}
}

func (s *IntegrationTestBuilder) AssertWorkingDir(root string, matches ...string) {
s.AssertFs(s.fs.WorkingDirReadOnly, root, matches...)
}

func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
s.AssertFs(s.fs.PublishDir, matches...)
s.AssertFs(s.fs.PublishDir, "", matches...)
}

func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) {
func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, root string, matches ...string) {
s.Helper()
var buff bytes.Buffer
s.Assert(s.printAndCheckFs(fs, "", &buff), qt.IsNil)
s.Assert(s.printAndCheckFs(fs, root, &buff), qt.IsNil)
printFsLines := strings.Split(buff.String(), "\n")
sort.Strings(printFsLines)
content := strings.TrimSpace((strings.Join(printFsLines, "\n")))
Expand Down Expand Up @@ -665,17 +669,18 @@ func (s *IntegrationTestBuilder) initBuilder() error {
flags = config.New()
}

internal := make(maps.Params)

if s.Cfg.Running {
flags.Set("internal", maps.Params{
"running": s.Cfg.Running,
"watch": s.Cfg.Running,
})
internal["running"] = true
internal["watch"] = true

} else if s.Cfg.Watching {
flags.Set("internal", maps.Params{
"watch": s.Cfg.Watching,
})
internal["watch"] = true
}

flags.Set("internal", internal)

if s.Cfg.WorkingDir != "" {
flags.Set("workingDir", s.Cfg.WorkingDir)
}
Expand Down
16 changes: 8 additions & 8 deletions internal/js/esbuild/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ import (
// NewBuildClient creates a new BuildClient.
func NewBuildClient(fs *filesystems.SourceFilesystem, rs *resources.Spec) *BuildClient {
return &BuildClient{
rs: rs,
Rs: rs,
sfs: fs,
}
}

// BuildClient is a client for building JavaScript resources using esbuild.
type BuildClient struct {
rs *resources.Spec
Rs *resources.Spec
sfs *filesystems.SourceFilesystem
}

Expand All @@ -52,11 +52,11 @@ func (c *BuildClient) Build(opts Options) (api.BuildResult, error) {
dependencyManager = identity.NopManager
}

opts.OutDir = c.rs.AbsPublishDir
opts.ResolveDir = c.rs.Cfg.BaseConfig().WorkingDir // where node_modules gets resolved
opts.OutDir = c.Rs.AbsPublishDir
opts.ResolveDir = c.Rs.Cfg.BaseConfig().WorkingDir // where node_modules gets resolved
opts.AbsWorkingDir = opts.ResolveDir
opts.TsConfig = c.rs.ResolveJSConfigFile("tsconfig.json")
assetsResolver := newFSResolver(c.rs.Assets.Fs)
opts.TsConfig = c.Rs.ResolveJSConfigFile("tsconfig.json")
assetsResolver := newFSResolver(c.Rs.Assets.Fs)

if err := opts.validate(); err != nil {
return api.BuildResult{}, err
Expand All @@ -67,7 +67,7 @@ func (c *BuildClient) Build(opts Options) (api.BuildResult, error) {
}

var err error
opts.compiled.Plugins, err = createBuildPlugins(c.rs, assetsResolver, dependencyManager, opts)
opts.compiled.Plugins, err = createBuildPlugins(c.Rs, assetsResolver, dependencyManager, opts)
if err != nil {
return api.BuildResult{}, err
}
Expand Down Expand Up @@ -175,7 +175,7 @@ func (c *BuildClient) Build(opts Options) (api.BuildResult, error) {
// Return 1, log the rest.
for i, err := range errors {
if i > 0 {
c.rs.Logger.Errorf("js.Build failed: %s", err)
c.Rs.Logger.Errorf("js.Build failed: %s", err)
}
}

Expand Down
Loading

0 comments on commit ee06931

Please sign in to comment.