Repository: gohugoio/hugo (language: go)

Issue:
Extend default theme templates
The default theme proposed by the command `hugo new theme` is kind of light to say the least. There is some content in `baseof.html` referencing a few other (empty) partials: I believe it would make sense to complete these files just enough so that it creates a crude but still functional skeleton.

I'm thinking in particular of:

1. `head.html` (which is already created but empty) could contain something like (and probably other meta such as `.Description`, `.Keywords`, ...):
```html
<head>
  <title>{{block "title" .}}{{with .Title}}{{.}} - {{end}}{{.Site.Title}}{{end}}</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
```
2. `baseof.html` could show how to make use of `.Site.LanguageCode` in the `<html>` tag (and maybe `GetLanguageDirection`?)
3. `header.html` (which is already created but empty) could contain a basic list for the menus (`.Site.Menus`)
4. `single.html` and `list.html` could simply have a `.Content`
5. `list.html` could make use of the pages thing

There is no need for style or anything fancy, but simply show how the information provided by the engine is supposed to be used. This would help theme creators make best use of the engine features as well as providing a barely functional out of the box experience.


Discussion / hints:
I agree. 

2. it needs to be `.Language.LanguageCode` and `.Language.LanguageDirection` I think.
4. single.html, list.html, index.html all needs to have a `main` define.

Ideally both `hugo new theme` and `hugo new site` should get something runnable, so maybe also add a couple of dummy pages.

On the technical level the `hugo new theme/site` needs to be reworked a little so they can read the "scheleton templates" from a Go embedded fs, which should make it much easier to edit/maintain.


Fix patch:
diff --git a/commands/helpers.go b/commands/helpers.go
index d6c5e43aced..3b0c501592b 100644
--- a/commands/helpers.go
+++ b/commands/helpers.go
@@ -14,7 +14,6 @@
 package commands
 
 import (
-	"bytes"
 	"errors"
 	"fmt"
 	"log"
@@ -24,8 +23,6 @@ import (
 
 	"github.com/bep/simplecobra"
 	"github.com/gohugoio/hugo/config"
-	"github.com/gohugoio/hugo/helpers"
-	"github.com/spf13/afero"
 	"github.com/spf13/pflag"
 )
 
@@ -123,11 +120,3 @@ func mkdir(x ...string) {
 		log.Fatal(err)
 	}
 }
-
-func touchFile(fs afero.Fs, filename string) {
-	mkdir(filepath.Dir(filename))
-	err := helpers.WriteToDisk(filename, bytes.NewReader([]byte{}), fs)
-	if err != nil {
-		log.Fatal(err)
-	}
-}
diff --git a/commands/new.go b/commands/new.go
index 42644d7f6f1..3f43a687f47 100644
--- a/commands/new.go
+++ b/commands/new.go
@@ -16,19 +16,13 @@ package commands
 import (
 	"bytes"
 	"context"
-	"errors"
-	"fmt"
 	"path/filepath"
 	"strings"
 
 	"github.com/bep/simplecobra"
-	"github.com/gohugoio/hugo/common/htime"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/create"
-	"github.com/gohugoio/hugo/helpers"
-	"github.com/gohugoio/hugo/parser"
-	"github.com/gohugoio/hugo/parser/metadecoders"
-	"github.com/spf13/afero"
+	"github.com/gohugoio/hugo/create/skeletons"
 	"github.com/spf13/cobra"
 )
 
@@ -99,52 +93,13 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
 					}
 					sourceFs := conf.fs.Source
 
-					archeTypePath := filepath.Join(createpath, "archetypes")
-					dirs := []string{
-						archeTypePath,
-						filepath.Join(createpath, "assets"),
-						filepath.Join(createpath, "content"),
-						filepath.Join(createpath, "data"),
-						filepath.Join(createpath, "layouts"),
-						filepath.Join(createpath, "static"),
-						filepath.Join(createpath, "themes"),
-					}
-
-					if exists, _ := helpers.Exists(createpath, sourceFs); exists {
-						if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
-							return errors.New(createpath + " already exists but not a directory")
-						}
-
-						isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
-
-						switch {
-						case !isEmpty && !force:
-							return errors.New(createpath + " already exists and is not empty. See --force.")
-
-						case !isEmpty && force:
-							all := append(dirs, filepath.Join(createpath, "hugo."+format))
-							for _, path := range all {
-								if exists, _ := helpers.Exists(path, sourceFs); exists {
-									return errors.New(path + " already exists")
-								}
-							}
-						}
-					}
-
-					for _, dir := range dirs {
-						if err := sourceFs.MkdirAll(dir, 0777); err != nil {
-							return fmt.Errorf("failed to create dir: %w", err)
-						}
+					err = skeletons.CreateSite(createpath, sourceFs, force, format)
+					if err != nil {
+						return err
 					}
 
-					c.newSiteCreateConfig(sourceFs, createpath, format)
-
-					// Create a default archetype file.
-					helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"),
-						strings.NewReader(create.DefaultArchetypeTemplateTemplate), sourceFs)
-
-					r.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", createpath)
-					r.Println(c.newSiteNextStepsText())
+					r.Printf("Congratulations! Your new Hugo site was created in %s.\n\n", createpath)
+					r.Println(c.newSiteNextStepsText(createpath, format))
 
 					return nil
 				},
@@ -173,83 +128,13 @@ according to your needs.`,
 					sourceFs := ps.Fs.Source
 					themesDir := h.Configs.LoadingInfo.BaseConfig.ThemesDir
 					createpath := ps.AbsPathify(filepath.Join(themesDir, args[0]))
-					r.Println("Creating theme at", createpath)
-
-					if x, _ := helpers.Exists(createpath, sourceFs); x {
-						return errors.New(createpath + " already exists")
-					}
-
-					for _, filename := range []string{
-						"index.html",
-						"404.html",
-						"_default/list.html",
-						"_default/single.html",
-						"partials/head.html",
-						"partials/header.html",
-						"partials/footer.html",
-					} {
-						touchFile(sourceFs, filepath.Join(createpath, "layouts", filename))
-					}
-
-					baseofDefault := []byte(`<!DOCTYPE html>
-<html>
-    {{- partial "head.html" . -}}
-    <body>
-        {{- partial "header.html" . -}}
-        <div id="content">
-        {{- block "main" . }}{{- end }}
-        </div>
-        {{- partial "footer.html" . -}}
-    </body>
-</html>
-`)
-
-					err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), sourceFs)
-					if err != nil {
-						return err
-					}
-
-					mkdir(createpath, "archetypes")
-
-					archDefault := []byte("+++\n+++\n")
-
-					err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), sourceFs)
-					if err != nil {
-						return err
-					}
-
-					mkdir(createpath, "static", "js")
-					mkdir(createpath, "static", "css")
-
-					by := []byte(`The MIT License (MIT)
-
-Copyright (c) ` + htime.Now().Format("2006") + ` YOUR_NAME_HERE
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
+					r.Println("Creating new theme in", createpath)
 
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-`)
-
-					err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), sourceFs)
+					err = skeletons.CreateTheme(createpath, sourceFs)
 					if err != nil {
 						return err
 					}
 
-					c.createThemeMD(ps.Fs.Source, createpath)
-
 					return nil
 				},
 			},
@@ -299,77 +184,25 @@ func (c *newCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
 	return nil
 }
 
-func (c *newCommand) newSiteCreateConfig(fs afero.Fs, inpath string, kind string) (err error) {
-	in := map[string]string{
-		"baseURL":      "http://example.org/",
-		"title":        "My New Hugo Site",
-		"languageCode": "en-us",
-	}
-
-	var buf bytes.Buffer
-	err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(kind), &buf)
-	if err != nil {
-		return err
-	}
-
-	return helpers.WriteToDisk(filepath.Join(inpath, "hugo."+kind), &buf, fs)
-}
-
-func (c *newCommand) newSiteNextStepsText() string {
+func (c *newCommand) newSiteNextStepsText(path string, format string) string {
+	format = strings.ToLower(format)
 	var nextStepsText bytes.Buffer
 
-	nextStepsText.WriteString(`Just a few more steps and you're ready to go:
+	nextStepsText.WriteString(`Just a few more steps...
 
-1. Download a theme into the same-named folder.
-   Choose a theme from https://themes.gohugo.io/ or
-   create your own with the "hugo new theme <THEMENAME>" command.
-2. Perhaps you want to add some content. You can add single files
-   with "hugo new `)
+1. Change the current directory to ` + path + `.
+2. Create or install a theme:
+   - Create a new theme with the command "hugo new theme <THEMENAME>"
+   - Install a theme from https://themes.gohugo.io/
+3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
+4. Create new content with the command "hugo new content `)
 
 	nextStepsText.WriteString(filepath.Join("<SECTIONNAME>", "<FILENAME>.<FORMAT>"))
 
 	nextStepsText.WriteString(`".
-3. Start the built-in live server via "hugo server".
+5. Start the embedded web server with the command "hugo server --buildDrafts".
 
-Visit https://gohugo.io/ for quickstart guide and full documentation.`)
+See documentation at https://gohugo.io/.`)
 
 	return nextStepsText.String()
 }
-
-func (c *newCommand) createThemeMD(fs afero.Fs, inpath string) (err error) {
-
-	by := []byte(`# theme.toml template for a Hugo theme
-# See https://github.com/gohugoio/hugoThemes#themetoml for an example
-
-name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
-license = "MIT"
-licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
-description = ""
-homepage = "http://example.com/"
-tags = []
-features = []
-min_version = "0.116.0"
-
-[author]
-  name = ""
-  homepage = ""
-
-# If porting an existing theme
-[original]
-  name = ""
-  homepage = ""
-  repo = ""
-`)
-
-	err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs)
-	if err != nil {
-		return
-	}
-
-	err = helpers.WriteToDisk(filepath.Join(inpath, "hugo.toml"), strings.NewReader("# Theme config.\n"), fs)
-	if err != nil {
-		return
-	}
-
-	return nil
-}
diff --git a/create/content.go b/create/content.go
index 55159c24c30..10442c3962c 100644
--- a/create/content.go
+++ b/create/content.go
@@ -42,7 +42,7 @@ const (
 	// DefaultArchetypeTemplateTemplate is the template used in 'hugo new site'
 	// and the template we use as a fall back.
 	DefaultArchetypeTemplateTemplate = `---
-title: "{{ replace .Name "-" " " | title }}"
+title: "{{ replace .File.ContentBaseName "-" " " | title }}"
 date: {{ .Date }}
 draft: true
 ---
diff --git a/create/skeletons/site/archetypes/default.md b/create/skeletons/site/archetypes/default.md
new file mode 100644
index 00000000000..c6f3fcef6e3
--- /dev/null
+++ b/create/skeletons/site/archetypes/default.md
@@ -0,0 +1,5 @@
++++
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+date = {{ .Date }}
+draft = true
++++
diff --git a/create/skeletons/site/assets/.gitkeep b/create/skeletons/site/assets/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/content/.gitkeep b/create/skeletons/site/content/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/data/.gitkeep b/create/skeletons/site/data/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/i18n/.gitkeep b/create/skeletons/site/i18n/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/layouts/.gitkeep b/create/skeletons/site/layouts/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/static/.gitkeep b/create/skeletons/site/static/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/site/themes/.gitkeep b/create/skeletons/site/themes/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/skeletons.go b/create/skeletons/skeletons.go
new file mode 100644
index 00000000000..7f7fb1bb780
--- /dev/null
+++ b/create/skeletons/skeletons.go
@@ -0,0 +1,111 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package skeletons
+
+import (
+	"bytes"
+	"embed"
+	"errors"
+	"io/fs"
+	"path/filepath"
+	"strings"
+
+	"github.com/gohugoio/hugo/helpers"
+	"github.com/gohugoio/hugo/parser"
+	"github.com/gohugoio/hugo/parser/metadecoders"
+	"github.com/spf13/afero"
+)
+
+//go:embed all:site/*
+var siteFs embed.FS
+
+//go:embed all:theme/*
+var themeFs embed.FS
+
+// CreateTheme creates a theme skeleton.
+func CreateTheme(createpath string, sourceFs afero.Fs) error {
+	if exists, _ := helpers.Exists(createpath, sourceFs); exists {
+		return errors.New(createpath + " already exists")
+	}
+	return copyFiles(createpath, sourceFs, themeFs)
+}
+
+// CreateSite creates a site skeleton.
+func CreateSite(createpath string, sourceFs afero.Fs, force bool, format string) error {
+	format = strings.ToLower(format)
+	if exists, _ := helpers.Exists(createpath, sourceFs); exists {
+		if isDir, _ := helpers.IsDir(createpath, sourceFs); !isDir {
+			return errors.New(createpath + " already exists but not a directory")
+		}
+
+		isEmpty, _ := helpers.IsEmpty(createpath, sourceFs)
+
+		switch {
+		case !isEmpty && !force:
+			return errors.New(createpath + " already exists and is not empty. See --force.")
+		case !isEmpty && force:
+			var all []string
+			fs.WalkDir(siteFs, ".", func(path string, d fs.DirEntry, err error) error {
+				if d.IsDir() && path != "." {
+					all = append(all, path)
+				}
+				return nil
+			})
+			all = append(all, filepath.Join(createpath, "hugo."+format))
+			for _, path := range all {
+				if exists, _ := helpers.Exists(path, sourceFs); exists {
+					return errors.New(path + " already exists")
+				}
+			}
+		}
+	}
+
+	err := newSiteCreateConfig(sourceFs, createpath, format)
+	if err != nil {
+		return err
+	}
+
+	return copyFiles(createpath, sourceFs, siteFs)
+}
+
+func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) error {
+	return fs.WalkDir(skeleton, ".", func(path string, d fs.DirEntry, err error) error {
+		_, slug, _ := strings.Cut(path, "/")
+		if d.IsDir() {
+			return sourceFs.MkdirAll(filepath.Join(createpath, slug), 0777)
+		} else {
+			if filepath.Base(path) != ".gitkeep" {
+				data, _ := fs.ReadFile(skeleton, path)
+				return helpers.WriteToDisk(filepath.Join(createpath, slug), bytes.NewReader(data), sourceFs)
+			}
+			return nil
+		}
+	})
+}
+
+func newSiteCreateConfig(fs afero.Fs, createpath string, format string) (err error) {
+	in := map[string]string{
+		"baseURL":      "https://example.org/",
+		"title":        "My New Hugo Site",
+		"languageCode": "en-us",
+	}
+
+	var buf bytes.Buffer
+	err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(format), &buf)
+	if err != nil {
+		return err
+	}
+
+	return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs)
+}
diff --git a/create/skeletons/theme/LICENSE b/create/skeletons/theme/LICENSE
new file mode 100644
index 00000000000..8aa26455d23
--- /dev/null
+++ b/create/skeletons/theme/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) [year] [fullname]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/create/skeletons/theme/README.md b/create/skeletons/theme/README.md
new file mode 100644
index 00000000000..7cec74e073e
--- /dev/null
+++ b/create/skeletons/theme/README.md
@@ -0,0 +1,7 @@
+# Theme Name
+
+## Features
+
+## Installation
+
+## Configuration
diff --git a/create/skeletons/theme/archetypes/default.md b/create/skeletons/theme/archetypes/default.md
new file mode 100644
index 00000000000..c6f3fcef6e3
--- /dev/null
+++ b/create/skeletons/theme/archetypes/default.md
@@ -0,0 +1,5 @@
++++
+title = '{{ replace .File.ContentBaseName "-" " " | title }}'
+date = {{ .Date }}
+draft = true
++++
diff --git a/create/skeletons/theme/assets/css/main.css b/create/skeletons/theme/assets/css/main.css
new file mode 100644
index 00000000000..166ade92452
--- /dev/null
+++ b/create/skeletons/theme/assets/css/main.css
@@ -0,0 +1,22 @@
+body {
+  color: #222;
+  font-family: sans-serif;
+  line-height: 1.5;
+  margin: 1rem;
+  max-width: 768px;
+}
+
+header {
+  border-bottom: 1px solid #222;
+  margin-bottom: 1rem;
+}
+
+footer {
+  border-top: 1px solid #222;
+  margin-top: 1rem;
+}
+
+a {
+  color: #00e;
+  text-decoration: none;
+}
diff --git a/create/skeletons/theme/assets/js/main.js b/create/skeletons/theme/assets/js/main.js
new file mode 100644
index 00000000000..e2aac527510
--- /dev/null
+++ b/create/skeletons/theme/assets/js/main.js
@@ -0,0 +1,1 @@
+console.log('This site was generated by Hugo.');
diff --git a/create/skeletons/theme/config/_default/hugo.toml b/create/skeletons/theme/config/_default/hugo.toml
new file mode 100644
index 00000000000..1237194114f
--- /dev/null
+++ b/create/skeletons/theme/config/_default/hugo.toml
@@ -0,0 +1,4 @@
+[module]
+  [module.hugoVersion]
+    extended = false
+    min = "0.116.0"
diff --git a/create/skeletons/theme/config/_default/menus.toml b/create/skeletons/theme/config/_default/menus.toml
new file mode 100644
index 00000000000..526645f2cab
--- /dev/null
+++ b/create/skeletons/theme/config/_default/menus.toml
@@ -0,0 +1,14 @@
+[[main]]
+name = 'Home'
+pageRef = '/'
+weight = 10
+
+[[main]]
+name = 'Posts'
+pageRef = '/posts'
+weight = 20
+
+[[main]]
+name = 'Tags'
+pageRef = '/tags'
+weight = 30
diff --git a/create/skeletons/theme/content/_index.md b/create/skeletons/theme/content/_index.md
new file mode 100644
index 00000000000..652623b5730
--- /dev/null
+++ b/create/skeletons/theme/content/_index.md
@@ -0,0 +1,9 @@
++++
+title = 'Home'
+date = 2023-01-01T08:00:00-07:00
+draft = false
++++
+
+Laborum voluptate pariatur ex culpa magna nostrud est incididunt fugiat
+pariatur do dolor ipsum enim. Consequat tempor do dolor eu. Non id id anim anim
+excepteur excepteur pariatur nostrud qui irure ullamco.
diff --git a/create/skeletons/theme/content/posts/_index.md b/create/skeletons/theme/content/posts/_index.md
new file mode 100644
index 00000000000..e7066c092ba
--- /dev/null
+++ b/create/skeletons/theme/content/posts/_index.md
@@ -0,0 +1,7 @@
++++
+title = 'Posts'
+date = 2023-01-01T08:30:00-07:00
+draft = false
++++
+
+Tempor est exercitation ad qui pariatur quis adipisicing aliquip nisi ea consequat ipsum occaecat. Nostrud consequat ullamco laboris fugiat esse esse adipisicing velit laborum ipsum incididunt ut enim. Dolor pariatur nulla quis fugiat dolore excepteur. Aliquip ad quis aliqua enim do consequat.
diff --git a/create/skeletons/theme/content/posts/post-1.md b/create/skeletons/theme/content/posts/post-1.md
new file mode 100644
index 00000000000..3e3fc6b2592
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-1.md
@@ -0,0 +1,10 @@
++++
+title = 'Post 1'
+date = 2023-01-15T09:00:00-07:00
+draft = false
+tags = ['red']
++++
+
+Tempor proident minim aliquip reprehenderit dolor et ad anim Lorem duis sint eiusmod. Labore ut ea duis dolor. Incididunt consectetur proident qui occaecat incididunt do nisi Lorem. Tempor do laborum elit laboris excepteur eiusmod do. Eiusmod nisi excepteur ut amet pariatur adipisicing Lorem.
+
+Occaecat nulla excepteur dolore excepteur duis eiusmod ullamco officia anim in voluptate ea occaecat officia. Cillum sint esse velit ea officia minim fugiat. Elit ea esse id aliquip pariatur cupidatat id duis minim incididunt ea ea. Anim ut duis sunt nisi. Culpa cillum sit voluptate voluptate eiusmod dolor. Enim nisi Lorem ipsum irure est excepteur voluptate eu in enim nisi. Nostrud ipsum Lorem anim sint labore consequat do.
diff --git a/create/skeletons/theme/content/posts/post-2.md b/create/skeletons/theme/content/posts/post-2.md
new file mode 100644
index 00000000000..22b8287699e
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-2.md
@@ -0,0 +1,10 @@
++++
+title = 'Post 2'
+date = 2023-02-15T10:00:00-07:00
+draft = false
+tags = ['red','green']
++++
+
+Anim eiusmod irure incididunt sint cupidatat. Incididunt irure irure irure nisi ipsum do ut quis fugiat consectetur proident cupidatat incididunt cillum. Dolore voluptate occaecat qui mollit laborum ullamco et. Ipsum laboris officia anim laboris culpa eiusmod ex magna ex cupidatat anim ipsum aute. Mollit aliquip occaecat qui sunt velit ut cupidatat reprehenderit enim sunt laborum. Velit veniam in officia nulla adipisicing ut duis officia.
+
+Exercitation voluptate irure in irure tempor mollit Lorem nostrud ad officia. Velit id fugiat occaecat do tempor. Sit officia Lorem aliquip eu deserunt consectetur. Aute proident deserunt in nulla aliquip dolore ipsum Lorem ut cupidatat consectetur sit sint laborum. Esse cupidatat sit sint sunt tempor exercitation deserunt. Labore dolor duis laborum est do nisi ut veniam dolor et nostrud nostrud.
diff --git a/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg b/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg
new file mode 100644
index 00000000000..9a923bea0d1
Binary files /dev/null and b/create/skeletons/theme/content/posts/post-3/bryce-canyon.jpg differ
diff --git a/create/skeletons/theme/content/posts/post-3/index.md b/create/skeletons/theme/content/posts/post-3/index.md
new file mode 100644
index 00000000000..ca42a664b0a
--- /dev/null
+++ b/create/skeletons/theme/content/posts/post-3/index.md
@@ -0,0 +1,12 @@
++++
+title = 'Post 3'
+date = 2023-03-15T11:00:00-07:00
+draft = false
+tags = ['red','green','blue']
++++
+
+Occaecat aliqua consequat laborum ut ex aute aliqua culpa quis irure esse magna dolore quis. Proident fugiat labore eu laboris officia Lorem enim. Ipsum occaecat cillum ut tempor id sint aliqua incididunt nisi incididunt reprehenderit. Voluptate ad minim sint est aute aliquip esse occaecat tempor officia qui sunt. Aute ex ipsum id ut in est velit est laborum incididunt. Aliqua qui id do esse sunt eiusmod id deserunt eu nostrud aute sit ipsum. Deserunt esse cillum Lorem non magna adipisicing mollit amet consequat.
+
+![Bryce Canyon National Park](bryce-canyon.jpg)
+
+Sit excepteur do velit veniam mollit in nostrud laboris incididunt ea. Amet eu cillum ut reprehenderit culpa aliquip labore laborum amet sit sit duis. Laborum id proident nostrud dolore laborum reprehenderit quis mollit nulla amet veniam officia id id. Aliquip in deserunt qui magna duis qui pariatur officia sunt deserunt.
diff --git a/create/skeletons/theme/data/.gitkeep b/create/skeletons/theme/data/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/theme/i18n/.gitkeep b/create/skeletons/theme/i18n/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/create/skeletons/theme/layouts/_default/baseof.html b/create/skeletons/theme/layouts/_default/baseof.html
new file mode 100644
index 00000000000..ce9613a2bd1
--- /dev/null
+++ b/create/skeletons/theme/layouts/_default/baseof.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
+<head>
+  {{ partial "head.html" . }}
+</head>
+<body>
+  <header>
+    {{ partial "header.html" . }}
+  </header>
+  <main>
+    {{ block "main" . }}{{ end }}
+  </main>
+  <footer>
+    {{ partial "footer.html" . }}
+  </footer>
+</body>
+</html>
diff --git a/create/skeletons/theme/layouts/_default/home.html b/create/skeletons/theme/layouts/_default/home.html
new file mode 100644
index 00000000000..0df65974276
--- /dev/null
+++ b/create/skeletons/theme/layouts/_default/home.html
@@ -0,0 +1,7 @@
+{{ define "main" }}
+  {{ .Content }}
+  {{ range site.RegularPages }}
+    <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
+    {{ .Summary }}
+  {{ end }}
+{{ end }}
diff --git a/create/skeletons/theme/layouts/_default/list.html b/create/skeletons/theme/layouts/_default/list.html
new file mode 100644
index 00000000000..50fc92d40df
--- /dev/null
+++ b/create/skeletons/theme/layouts/_default/list.html
@@ -0,0 +1,8 @@
+{{ define "main" }}
+  <h1>{{ .Title }}</h1>
+  {{ .Content }}
+  {{ range .Pages }}
+    <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
+    {{ .Summary }}
+  {{ end }}
+{{ end }}
diff --git a/create/skeletons/theme/layouts/_default/single.html b/create/skeletons/theme/layouts/_default/single.html
new file mode 100644
index 00000000000..7e286c80218
--- /dev/null
+++ b/create/skeletons/theme/layouts/_default/single.html
@@ -0,0 +1,10 @@
+{{ define "main" }}
+  <h1>{{ .Title }}</h1>
+
+  {{ $dateMachine := .Date | time.Format "2006-01-02T15:04:05-07:00" }}
+  {{ $dateHuman := .Date | time.Format ":date_long" }}
+  <time datetime="{{ $dateMachine }}">{{ $dateHuman }}</time>
+
+  {{ .Content }}
+  {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }}
+{{ end }}
diff --git a/create/skeletons/theme/layouts/partials/footer.html b/create/skeletons/theme/layouts/partials/footer.html
new file mode 100644
index 00000000000..a7cd916d054
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/footer.html
@@ -0,0 +1,1 @@
+<p>Copyright {{ now.Year }}. All rights reserved.</p>
diff --git a/create/skeletons/theme/layouts/partials/head.html b/create/skeletons/theme/layouts/partials/head.html
new file mode 100644
index 00000000000..c15b7b499a4
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/head.html
@@ -0,0 +1,5 @@
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0">
+<title>{{ if .IsHome }}{{ site.Title }}{{ else }}{{ printf "%s | %s" .Title site.Title }}{{ end }}</title>
+{{ partialCached "head/css.html" . }}
+{{ partialCached "head/js.html" . }}
diff --git a/create/skeletons/theme/layouts/partials/head/css.html b/create/skeletons/theme/layouts/partials/head/css.html
new file mode 100644
index 00000000000..91b928ded42
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/head/css.html
@@ -0,0 +1,9 @@
+{{- with resources.Get "css/main.css" }}
+  {{- if eq hugo.Environment "development" }}
+    <link rel="stylesheet" href="{{ .RelPermalink }}">
+  {{- else }}
+    {{- with . | minify | fingerprint }}
+      <link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
+    {{- end }}
+  {{- end }}
+{{- end }}
diff --git a/create/skeletons/theme/layouts/partials/head/js.html b/create/skeletons/theme/layouts/partials/head/js.html
new file mode 100644
index 00000000000..18fe84291ae
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/head/js.html
@@ -0,0 +1,12 @@
+{{- with resources.Get "js/main.js" }}
+  {{- if eq hugo.Environment "development" }}
+    {{- with . | js.Build }}
+      <script src="{{ .RelPermalink }}"></script>
+    {{- end }}
+  {{- else }}
+    {{- $opts := dict "minify" true }}
+    {{- with . | js.Build $opts | fingerprint }}
+      <script src="{{ .RelPermalink }}" integrity="{{- .Data.Integrity }}" crossorigin="anonymous"></script>
+    {{- end }}
+  {{- end }}
+{{- end }}
diff --git a/create/skeletons/theme/layouts/partials/header.html b/create/skeletons/theme/layouts/partials/header.html
new file mode 100644
index 00000000000..7980a00e1e2
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/header.html
@@ -0,0 +1,2 @@
+<h1>{{ site.Title }}</h1>
+{{ partial "menu.html" (dict "menuID" "main" "page" .) }}
diff --git a/create/skeletons/theme/layouts/partials/menu.html b/create/skeletons/theme/layouts/partials/menu.html
new file mode 100644
index 00000000000..8f72130d07b
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/menu.html
@@ -0,0 +1,45 @@
+{{- /*
+Renders a menu for the given menu ID.
+
+@context {page} page The current page.
+@context {string} menuID The menu ID.
+
+@example: {{ partial "menu.html" (dict "menuID" "main" "page" .) }}
+*/}}
+
+{{- $page := .page }}
+{{- $menuID := .menuID }}
+
+{{- with index site.Menus $menuID }}
+  <nav>
+    <ul>
+      {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
+    </ul>
+  </nav>
+{{- end }}
+
+{{- define "partials/inline/menu/walk.html" }}
+  {{- $page := .page }}
+  {{- range .menuEntries }}
+    {{- $attrs := dict "href" .URL }}
+    {{- if $page.IsMenuCurrent .Menu . }}
+      {{- $attrs = merge $attrs (dict "class" "active" "aria-current" "page") }}
+    {{- else if $page.HasMenuCurrent .Menu .}}
+      {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }}
+    {{- end }}
+    <li>
+      <a
+        {{- range $k, $v := $attrs }}
+          {{- with $v }}
+            {{- printf " %s=%q" $k $v | safeHTMLAttr }}
+          {{- end }}
+        {{- end -}}
+      >{{ or (T .Identifier) .Name | safeHTML }}</a>
+      {{- with .Children }}
+        <ul>
+          {{- partial "inline/menu/walk.html" (dict "page" $page "menuEntries" .) }}
+        </ul>
+      {{- end }}
+    </li>
+  {{- end }}
+{{- end }}
diff --git a/create/skeletons/theme/layouts/partials/terms.html b/create/skeletons/theme/layouts/partials/terms.html
new file mode 100644
index 00000000000..47cf6e41c74
--- /dev/null
+++ b/create/skeletons/theme/layouts/partials/terms.html
@@ -0,0 +1,23 @@
+{{- /*
+For a given taxonomy, renders a list of terms assigned to the page.
+
+@context {page} page The current page.
+@context {string} taxonomy The taxonony.
+
+@example: {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }}
+*/}}
+
+{{- $page := .page }}
+{{- $taxonomy := .taxonomy }}
+
+{{- with $page.GetTerms $taxonomy }}
+  {{- $label := (index . 0).Parent.LinkTitle }}
+  <div>
+    <div>{{ $label }}:</div>
+    <ul>
+      {{- range . }}
+        <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></li>
+      {{- end }}
+    </ul>
+  </div>
+{{- end }}
diff --git a/create/skeletons/theme/static/favicon.ico b/create/skeletons/theme/static/favicon.ico
new file mode 100644
index 00000000000..67f8b777851
Binary files /dev/null and b/create/skeletons/theme/static/favicon.ico differ
diff --git a/create/skeletons/theme/theme.toml b/create/skeletons/theme/theme.toml
new file mode 100644
index 00000000000..3ba31646337
--- /dev/null
+++ b/create/skeletons/theme/theme.toml
@@ -0,0 +1,31 @@
+name = 'Theme name'
+license = 'MIT'
+licenselink = 'https://github.com/owner/repo/LICENSE'
+description = 'Theme description'
+
+# The home page of the theme, where the source can be found
+homepage = 'https://github.com/owner/repo'
+
+# If you have a running demo of the theme
+demosite = 'https://owner.github.io/repo'
+
+# Taxonomy terms
+tags = ['blog', 'company']
+features = ['some', 'awesome', 'features']
+
+# If the theme has multiple authors
+authors = [
+  {name = 'Name of author', homepage = 'Website of author'},
+  {name = 'Name of author', homepage = 'Website of author'}
+]
+
+# If the theme has a single author
+[author]
+  name = 'Your name'
+  homepage = 'Your website'
+
+# If porting an existing theme
+[original]
+  author = 'Name of original author'
+  homepage = 'Website of original author'
+  repo = 'https://github.com/owner/repo'


Test patch:
diff --git a/testscripts/commands/new.txt b/testscripts/commands/new.txt
index 4c77104b2ce..955841fda84 100644
--- a/testscripts/commands/new.txt
+++ b/testscripts/commands/new.txt
@@ -5,20 +5,52 @@ stdout 'Create a new site in the provided directory'
 hugo new site my-yaml-site --format yml
 checkfile my-yaml-site/hugo.yml
 hugo new site mysite -f
-stdout 'Congratulations! Your new Hugo site is created in'
+stdout 'Congratulations! Your new Hugo site was created in'
 cd mysite
+checkfile archetypes/default.md
 checkfile hugo.toml
+exists assets
+exists content
+exists data
+exists i18n
+exists layouts
+exists static
+exists themes
 
 hugo new theme -h
 stdout 'Create a new theme \(skeleton\) called \[name\] in ./themes'
 hugo new theme mytheme
-stdout 'Creating theme'
+stdout 'Creating new theme'
 cd themes
 cd mytheme
+checkfile archetypes/default.md
+checkfile assets/css/main.css
+checkfile assets/js/main.js
+checkfile config/_default/hugo.toml
+checkfile config/_default/menus.toml
+checkfile content/_index.md
+checkfile content/posts/_index.md
+checkfile content/posts/post-1.md
+checkfile content/posts/post-2.md
+checkfile content/posts/post-3/bryce-canyon.jpg
+checkfile content/posts/post-3/index.md
+checkfile layouts/_default/baseof.html
+checkfile layouts/_default/home.html
+checkfile layouts/_default/list.html
+checkfile layouts/_default/single.html
+checkfile layouts/partials/footer.html
+checkfile layouts/partials/head.html
+checkfile layouts/partials/head/css.html
+checkfile layouts/partials/head/js.html
+checkfile layouts/partials/header.html
+checkfile layouts/partials/menu.html
+checkfile layouts/partials/terms.html
+checkfile static/favicon.ico
+checkfile LICENSE
+checkfile README.md
 checkfile theme.toml
-checkfile hugo.toml
-exists layouts/_default/list.html
-exists layouts/_default/single.html
+exists data
+exists i18n
 
 cd $WORK/mysite