diff --git a/package.json b/package.json
index a594fec776bf1..d890a5cfae5e8 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"fast-glob": "^3.2.11",
"github-slugger": "^1.5.0",
"gray-matter": "^4.0.3",
+ "hast-util-from-html": "^1.0.0",
"hast-util-to-string": "^2.0.0",
"hastscript": "^7.0.2",
"html-escaper": "^3.0.3",
@@ -70,6 +71,7 @@
"prettier": "^2.8.1",
"prettier-plugin-astro": "^0.7.0",
"prompts": "^2.4.2",
+ "rehype": "^12.0.1",
"remark": "^14.0.2",
"remark-custom-heading-id": "^1.0.0",
"remark-directive": "^2.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c3aaa704da1b8..67c45da517671 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,7 @@ specifiers:
fast-glob: ^3.2.11
github-slugger: ^1.5.0
gray-matter: ^4.0.3
+ hast-util-from-html: ^1.0.0
hast-util-to-string: ^2.0.0
hastscript: ^7.0.2
html-escaper: ^3.0.3
@@ -51,6 +52,7 @@ specifiers:
prettier: ^2.8.1
prettier-plugin-astro: ^0.7.0
prompts: ^2.4.2
+ rehype: ^12.0.1
rehype-autolink-headings: ^6.1.1
rehype-slug: ^5.0.1
remark: ^14.0.2
@@ -109,6 +111,7 @@ devDependencies:
fast-glob: 3.2.11
github-slugger: 1.5.0
gray-matter: 4.0.3
+ hast-util-from-html: 1.0.0
hast-util-to-string: 2.0.0
hastscript: 7.0.2
html-escaper: 3.0.3
@@ -126,6 +129,7 @@ devDependencies:
prettier: 2.8.1
prettier-plugin-astro: 0.7.0
prompts: 2.4.2
+ rehype: 12.0.1
remark: 14.0.2
remark-custom-heading-id: 1.0.0
remark-directive: 2.0.1
@@ -2930,6 +2934,15 @@ packages:
web-namespaces: 2.0.1
dev: true
+ /hast-util-from-html/1.0.0:
+ resolution: {integrity: sha512-tXYPhk28aMtDjGb4xNDaxtGKqlyZAEPGjN12jPCjczWppdLSrG/0r604a3FMvrd+9nV1HclILQiVqMnuPxN0WQ==}
+ dependencies:
+ '@types/hast': 2.3.4
+ hast-util-from-parse5: 7.1.0
+ parse5: 7.1.2
+ vfile: 5.3.4
+ dev: true
+
/hast-util-from-parse5/7.1.0:
resolution: {integrity: sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==}
dependencies:
@@ -4216,6 +4229,12 @@ packages:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
dev: true
+ /parse5/7.1.2:
+ resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
+ dependencies:
+ entities: 4.4.0
+ dev: true
+
/path-browserify/1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
dev: true
diff --git a/src/components/FileTree.astro b/src/components/FileTree.astro
new file mode 100644
index 0000000000000..278e48b796490
--- /dev/null
+++ b/src/components/FileTree.astro
@@ -0,0 +1,135 @@
+---
+import { useTranslations } from '../i18n/util';
+import { fileTreeProcessor } from './internal/rehype-file-tree';
+
+const content = await Astro.slots.render('default');
+if (!/^\s*
/.test(content)) {
+ throw new Error(
+ ` component expects its content to be an unordered list but found HTML starting with “${content.slice(
+ 0,
+ 20
+ )}...”`
+ );
+}
+
+const t = useTranslations(Astro);
+
+const processedContent = await fileTreeProcessor.process({
+ value: content,
+ data: { directoryLabel: t('fileTree.directoryLabel') },
+});
+---
+
+
+
+
diff --git a/src/components/internal/file-tree-icons.ts b/src/components/internal/file-tree-icons.ts
new file mode 100644
index 0000000000000..d4da0197b67c2
--- /dev/null
+++ b/src/components/internal/file-tree-icons.ts
@@ -0,0 +1,756 @@
+/**
+ * Based on https://github.com/elviswolcott/seti-icons which
+ * is derived from https://github.com/jesseweed/seti-ui/
+ *
+ * Copyright (c) 2014 Jesse Weed
+ *
+ * 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.
+ */
+
+const rawDefinitions = {
+ files: {
+ COMMIT_EDITMSG: ['git', 'ignore'],
+ MERGE_MSG: ['git', 'ignore'],
+ 'karma.conf.js': ['karma', 'green'],
+ 'karma.conf.coffee': ['karma', 'green'],
+ 'README.md': ['info', 'blue'],
+ 'README.txt': ['info', 'blue'],
+ README: ['info', 'blue'],
+ 'CHANGELOG.md': ['clock', 'blue'],
+ 'CHANGELOG.txt': ['clock', 'blue'],
+ CHANGELOG: ['clock', 'blue'],
+ 'CHANGES.md': ['clock', 'blue'],
+ 'CHANGES.txt': ['clock', 'blue'],
+ CHANGES: ['clock', 'blue'],
+ 'VERSION.md': ['clock', 'blue'],
+ 'VERSION.txt': ['clock', 'blue'],
+ VERSION: ['clock', 'blue'],
+ mvnw: ['maven', 'red'],
+ 'tsconfig.json': ['tsconfig', 'blue'],
+ 'swagger.json': ['json', 'green'],
+ 'swagger.yml': ['json', 'green'],
+ 'swagger.yaml': ['json', 'green'],
+ 'mime.types': ['config', 'grey-light'],
+ Jenkinsfile: ['jenkins', 'red'],
+ 'babel.config.js': ['babel', 'yellow'],
+ 'babel.config.json': ['babel', 'yellow'],
+ 'babel.config.cjs': ['babel', 'yellow'],
+ BUILD: ['bazel', 'green'],
+ 'BUILD.bazel': ['bazel', 'green'],
+ WORKSPACE: ['bazel', 'green'],
+ 'WORKSPACE.bazel': ['bazel', 'green'],
+ 'bower.json': ['bower', 'orange'],
+ 'Bower.json': ['bower', 'orange'],
+ 'firebase.json': ['firebase', 'orange'],
+ geckodriver: ['firefox', 'orange'],
+ 'Gruntfile.js': ['grunt', 'orange'],
+ 'gruntfile.babel.js': ['grunt', 'orange'],
+ 'Gruntfile.babel.js': ['grunt', 'orange'],
+ 'gruntfile.js': ['grunt', 'orange'],
+ 'Gruntfile.coffee': ['grunt', 'orange'],
+ 'gruntfile.coffee': ['grunt', 'orange'],
+ 'ionic.config.json': ['ionic', 'blue'],
+ 'Ionic.config.json': ['ionic', 'blue'],
+ 'ionic.project': ['ionic', 'blue'],
+ 'Ionic.project': ['ionic', 'blue'],
+ 'platformio.ini': ['platformio', 'orange'],
+ 'rollup.config.js': ['rollup', 'red'],
+ 'sass-lint.yml': ['sass', 'pink'],
+ 'stylelint.config.js': ['stylelint', 'white'],
+ 'stylelint.config.cjs': ['stylelint', 'white'],
+ 'yarn.clean': ['yarn', 'blue'],
+ 'yarn.lock': ['yarn', 'blue'],
+ 'webpack.config.js': ['webpack', 'blue'],
+ 'webpack.config.cjs': ['webpack', 'blue'],
+ 'webpack.config.ts': ['webpack', 'blue'],
+ 'webpack.config.build.js': ['webpack', 'blue'],
+ 'webpack.config.build.cjs': ['webpack', 'blue'],
+ 'webpack.config.build.ts': ['webpack', 'blue'],
+ 'webpack.common.js': ['webpack', 'blue'],
+ 'webpack.common.cjs': ['webpack', 'blue'],
+ 'webpack.common.ts': ['webpack', 'blue'],
+ 'webpack.dev.js': ['webpack', 'blue'],
+ 'webpack.dev.cjs': ['webpack', 'blue'],
+ 'webpack.dev.ts': ['webpack', 'blue'],
+ 'webpack.prod.js': ['webpack', 'blue'],
+ 'webpack.prod.cjs': ['webpack', 'blue'],
+ 'webpack.prod.ts': ['webpack', 'blue'],
+ 'npm-debug.log': ['npm_ignored', 'ignore'],
+ },
+ extensions: {
+ '.astro': ['astro', 'red'],
+ '.bsl': ['bsl', 'red'],
+ '.mdo': ['mdo', 'red'],
+ '.cls': ['salesforce', 'blue'],
+ '.apex': ['salesforce', 'blue'],
+ '.asm': ['asm', 'red'],
+ '.s': ['asm', 'red'],
+ '.bicep': ['bicep', 'blue'],
+ '.bzl': ['bazel', 'green'],
+ '.bazel': ['bazel', 'green'],
+ '.BUILD': ['bazel', 'green'],
+ '.WORKSPACE': ['bazel', 'green'],
+ '.bazelignore': ['bazel', 'green'],
+ '.bazelversion': ['bazel', 'green'],
+ '.c': ['c', 'blue'],
+ '.h': ['c', 'purple'],
+ '.m': ['c', 'yellow'],
+ '.cs': ['c-sharp', 'blue'],
+ '.cshtml': ['html', 'blue'],
+ '.aspx': ['html', 'blue'],
+ '.ascx': ['html', 'green'],
+ '.asax': ['html', 'yellow'],
+ '.master': ['html', 'yellow'],
+ '.cc': ['cpp', 'blue'],
+ '.cpp': ['cpp', 'blue'],
+ '.cxx': ['cpp', 'blue'],
+ '.c++': ['cpp', 'blue'],
+ '.hh': ['cpp', 'purple'],
+ '.hpp': ['cpp', 'purple'],
+ '.hxx': ['cpp', 'purple'],
+ '.h++': ['cpp', 'purple'],
+ '.mm': ['cpp', 'yellow'],
+ '.clj': ['clojure', 'green'],
+ '.cljs': ['clojure', 'green'],
+ '.cljc': ['clojure', 'green'],
+ '.edn': ['clojure', 'blue'],
+ '.cfc': ['coldfusion', 'blue'],
+ '.cfm': ['coldfusion', 'blue'],
+ '.coffee': ['coffee', 'yellow'],
+ '.litcoffee': ['coffee', 'yellow'],
+ '.config': ['config', 'grey-light'],
+ '.cfg': ['config', 'grey-light'],
+ '.conf': ['config', 'grey-light'],
+ '.cr': ['crystal', 'white'],
+ '.ecr': ['crystal_embedded', 'white'],
+ '.slang': ['crystal_embedded', 'white'],
+ '.cson': ['json', 'yellow'],
+ '.css': ['css', 'blue'],
+ '.css.map': ['css', 'blue'],
+ '.sss': ['css', 'blue'],
+ '.csv': ['csv', 'green'],
+ '.xls': ['xls', 'green'],
+ '.xlsx': ['xls', 'green'],
+ '.cu': ['cu', 'green'],
+ '.cuh': ['cu', 'purple'],
+ '.hu': ['cu', 'purple'],
+ '.cake': ['cake', 'red'],
+ '.ctp': ['cake_php', 'red'],
+ '.d': ['d', 'red'],
+ '.doc': ['word', 'blue'],
+ '.docx': ['word', 'blue'],
+ '.ejs': ['ejs', 'yellow'],
+ '.ex': ['elixir', 'purple'],
+ '.exs': ['elixir_script', 'purple'],
+ '.elm': ['elm', 'blue'],
+ '.ico': ['favicon', 'yellow'],
+ '.fs': ['f-sharp', 'blue'],
+ '.fsx': ['f-sharp', 'blue'],
+ '.gitignore': ['git', 'ignore'],
+ '.gitconfig': ['git', 'ignore'],
+ '.gitkeep': ['git', 'ignore'],
+ '.gitattributes': ['git', 'ignore'],
+ '.gitmodules': ['git', 'ignore'],
+ '.go': ['go2', 'blue'],
+ '.slide': ['go', 'blue'],
+ '.article': ['go', 'blue'],
+ '.gd': ['godot', 'blue'],
+ '.godot': ['godot', 'red'],
+ '.tres': ['godot', 'yellow'],
+ '.tscn': ['godot', 'purple'],
+ '.gradle': ['gradle', 'blue'],
+ '.groovy': ['grails', 'green'],
+ '.gsp': ['grails', 'green'],
+ '.gql': ['graphql', 'pink'],
+ '.graphql': ['graphql', 'pink'],
+ '.graphqls': ['graphql', 'pink'],
+ '.hack': ['hacklang', 'orange'],
+ '.haml': ['haml', 'red'],
+ '.handlebars': ['mustache', 'orange'],
+ '.hbs': ['mustache', 'orange'],
+ '.hjs': ['mustache', 'orange'],
+ '.hs': ['haskell', 'purple'],
+ '.lhs': ['haskell', 'purple'],
+ '.hx': ['haxe', 'orange'],
+ '.hxs': ['haxe', 'yellow'],
+ '.hxp': ['haxe', 'blue'],
+ '.hxml': ['haxe', 'purple'],
+ '.html': ['html', 'orange'],
+ '.jade': ['jade', 'red'],
+ '.java': ['java', 'red'],
+ '.class': ['java', 'blue'],
+ '.classpath': ['java', 'red'],
+ '.properties': ['java', 'red'],
+ '.js': ['javascript', 'yellow'],
+ '.js.map': ['javascript', 'yellow'],
+ '.spec.js': ['javascript', 'orange'],
+ '.test.js': ['javascript', 'orange'],
+ '.es': ['javascript', 'yellow'],
+ '.es5': ['javascript', 'yellow'],
+ '.es6': ['javascript', 'yellow'],
+ '.es7': ['javascript', 'yellow'],
+ '.cjs': ['javascript', 'yellow'],
+ '.mjs': ['javascript', 'yellow'],
+ '.jinja': ['jinja', 'red'],
+ '.jinja2': ['jinja', 'red'],
+ '.json': ['json', 'yellow'],
+ '.jl': ['julia', 'purple'],
+ '.kt': ['kotlin', 'orange'],
+ '.kts': ['kotlin', 'orange'],
+ '.dart': ['dart', 'blue'],
+ '.less': ['less', 'blue'],
+ '.liquid': ['liquid', 'green'],
+ '.ls': ['livescript', 'blue'],
+ '.lua': ['lua', 'blue'],
+ '.markdown': ['markdown', 'blue'],
+ '.md': ['markdown', 'blue'],
+ '.mdx': ['markdown', 'blue'],
+ '.argdown': ['argdown', 'blue'],
+ '.ad': ['argdown', 'blue'],
+ '.mustache': ['mustache', 'orange'],
+ '.stache': ['mustache', 'orange'],
+ '.nim': ['nim', 'yellow'],
+ '.nims': ['nim', 'yellow'],
+ '.github-issues': ['github', 'white'],
+ '.ipynb': ['notebook', 'blue'],
+ '.njk': ['nunjucks', 'green'],
+ '.nunjucks': ['nunjucks', 'green'],
+ '.nunjs': ['nunjucks', 'green'],
+ '.nunj': ['nunjucks', 'green'],
+ '.njs': ['nunjucks', 'green'],
+ '.nj': ['nunjucks', 'green'],
+ '.npm-debug.log': ['npm', 'ignore'],
+ '.npmignore': ['npm', 'red'],
+ '.npmrc': ['npm', 'red'],
+ '.ml': ['ocaml', 'orange'],
+ '.mli': ['ocaml', 'orange'],
+ '.cmx': ['ocaml', 'orange'],
+ '.cmxa': ['ocaml', 'orange'],
+ '.odata': ['odata', 'orange'],
+ '.pl': ['perl', 'blue'],
+ '.php': ['php', 'purple'],
+ '.php.inc': ['php', 'purple'],
+ '.pipeline': ['pipeline', 'orange'],
+ '.pddl': ['pddl', 'purple'],
+ '.plan': ['plan', 'green'],
+ '.happenings': ['happenings', 'blue'],
+ '.ps1': ['powershell', 'blue'],
+ '.psd1': ['powershell', 'blue'],
+ '.psm1': ['powershell', 'blue'],
+ '.prisma': ['prisma', 'blue'],
+ '.pug': ['pug', 'red'],
+ '.pp': ['puppet', 'yellow'],
+ '.epp': ['puppet', 'yellow'],
+ '.purs': ['purescript', 'white'],
+ '.py': ['python', 'blue'],
+ '.jsx': ['react', 'blue'],
+ '.spec.jsx': ['react', 'orange'],
+ '.test.jsx': ['react', 'orange'],
+ '.cjsx': ['react', 'blue'],
+ '.spec.tsx': ['react', 'orange'],
+ '.test.tsx': ['react', 'orange'],
+ '.re': ['reasonml', 'red'],
+ '.res': ['rescript', 'red'],
+ '.resi': ['rescript', 'pink'],
+ '.R': ['R', 'blue'],
+ '.rmd': ['R', 'blue'],
+ '.rb': ['ruby', 'red'],
+ '.erb': ['html_erb', 'red'],
+ '.erb.html': ['html_erb', 'red'],
+ '.html.erb': ['html_erb', 'red'],
+ '.rs': ['rust', 'grey-light'],
+ '.sass': ['sass', 'pink'],
+ '.scss': ['sass', 'pink'],
+ '.springBeans': ['spring', 'green'],
+ '.slim': ['slim', 'orange'],
+ '.smarty.tpl': ['smarty', 'yellow'],
+ '.tpl': ['smarty', 'yellow'],
+ '.sbt': ['sbt', 'blue'],
+ '.scala': ['scala', 'red'],
+ '.sol': ['ethereum', 'blue'],
+ '.styl': ['stylus', 'green'],
+ '.svelte': ['svelte', 'red'],
+ '.swift': ['swift', 'orange'],
+ '.sql': ['db', 'pink'],
+ '.soql': ['db', 'blue'],
+ '.tf': ['terraform', 'purple'],
+ '.tf.json': ['terraform', 'purple'],
+ '.tfvars': ['terraform', 'purple'],
+ '.tfvars.json': ['terraform', 'purple'],
+ '.tex': ['tex', 'blue'],
+ '.sty': ['tex', 'yellow'],
+ '.dtx': ['tex', 'orange'],
+ '.ins': ['tex', 'white'],
+ '.txt': ['default', 'white'],
+ '.toml': ['config', 'grey-light'],
+ '.twig': ['twig', 'green'],
+ '.ts': ['typescript', 'blue'],
+ '.tsx': ['typescript', 'blue'],
+ '.spec.ts': ['typescript', 'orange'],
+ '.test.ts': ['typescript', 'orange'],
+ '.vala': ['vala', 'grey-light'],
+ '.vapi': ['vala', 'grey-light'],
+ '.component': ['html', 'orange'],
+ '.vue': ['vue', 'green'],
+ '.wasm': ['wasm', 'purple'],
+ '.wat': ['wat', 'purple'],
+ '.xml': ['xml', 'orange'],
+ '.yml': ['yml', 'purple'],
+ '.yaml': ['yml', 'purple'],
+ '.pro': ['prolog', 'orange'],
+ '.zig': ['zig', 'orange'],
+ '.jar': ['zip', 'red'],
+ '.zip': ['zip', 'grey-light'],
+ '.wgt': ['wgt', 'blue'],
+ '.ai': ['illustrator', 'yellow'],
+ '.psd': ['photoshop', 'blue'],
+ '.pdf': ['pdf', 'red'],
+ '.eot': ['font', 'red'],
+ '.ttf': ['font', 'red'],
+ '.woff': ['font', 'red'],
+ '.woff2': ['font', 'red'],
+ '.avif': ['image', 'purple'],
+ '.gif': ['image', 'purple'],
+ '.jpg': ['image', 'purple'],
+ '.jpeg': ['image', 'purple'],
+ '.png': ['image', 'purple'],
+ '.pxm': ['image', 'purple'],
+ '.svg': ['svg', 'purple'],
+ '.svgx': ['image', 'purple'],
+ '.tiff': ['image', 'purple'],
+ '.webp': ['image', 'purple'],
+ '.sublime-project': ['sublime', 'orange'],
+ '.sublime-workspace': ['sublime', 'orange'],
+ '.code-search': ['code-search', 'purple'],
+ '.sh': ['shell', 'green'],
+ '.zsh': ['shell', 'green'],
+ '.fish': ['shell', 'green'],
+ '.zshrc': ['shell', 'green'],
+ '.bashrc': ['shell', 'green'],
+ '.mov': ['video', 'pink'],
+ '.ogv': ['video', 'pink'],
+ '.webm': ['video', 'pink'],
+ '.avi': ['video', 'pink'],
+ '.mpg': ['video', 'pink'],
+ '.mp4': ['video', 'pink'],
+ '.mp3': ['audio', 'purple'],
+ '.ogg': ['audio', 'purple'],
+ '.wav': ['audio', 'purple'],
+ '.flac': ['audio', 'purple'],
+ '.3ds': ['svg', 'blue'],
+ '.3dm': ['svg', 'blue'],
+ '.stl': ['svg', 'blue'],
+ '.obj': ['svg', 'blue'],
+ '.dae': ['svg', 'blue'],
+ '.bat': ['windows', 'blue'],
+ '.cmd': ['windows', 'blue'],
+ '.babelrc': ['babel', 'yellow'],
+ '.babelrc.js': ['babel', 'yellow'],
+ '.babelrc.cjs': ['babel', 'yellow'],
+ '.bazelrc': ['bazel', 'grey'],
+ '.bowerrc': ['bower', 'orange'],
+ '.codeclimate.yml': ['code-climate', 'green'],
+ '.eslintrc': ['eslint', 'purple'],
+ '.eslintrc.js': ['eslint', 'purple'],
+ '.eslintrc.cjs': ['eslint', 'purple'],
+ '.eslintrc.yaml': ['eslint', 'purple'],
+ '.eslintrc.yml': ['eslint', 'purple'],
+ '.eslintrc.json': ['eslint', 'purple'],
+ '.eslintignore': ['eslint', 'grey'],
+ '.firebaserc': ['firebase', 'orange'],
+ '.gitlab-ci.yml': ['gitlab', 'orange'],
+ '.jshintrc': ['javascript', 'blue'],
+ '.jscsrc': ['javascript', 'blue'],
+ '.stylelintrc': ['stylelint', 'white'],
+ '.stylelintrc.json': ['stylelint', 'white'],
+ '.stylelintrc.yaml': ['stylelint', 'white'],
+ '.stylelintrc.yml': ['stylelint', 'white'],
+ '.stylelintrc.js': ['stylelint', 'white'],
+ '.stylelintignore': ['stylelint', 'grey'],
+ '.direnv': ['config', 'grey-light'],
+ '.env': ['config', 'grey-light'],
+ '.static': ['config', 'grey-light'],
+ '.editorconfig': ['config', 'grey-light'],
+ '.slugignore': ['config', 'grey-light'],
+ '.tmp': ['clock', 'grey-light'],
+ '.htaccess': ['config', 'grey-light'],
+ '.key': ['lock', 'green'],
+ '.cert': ['lock', 'green'],
+ '.cer': ['lock', 'green'],
+ '.crt': ['lock', 'green'],
+ '.pem': ['lock', 'green'],
+ '.DS_Store': ['ignored', 'ignore'],
+ },
+ partials: [
+ ['TODO.md', ['todo', 'blue']],
+ ['TODO.txt', ['todo', 'blue']],
+ ['TODO', ['todo', 'blue']],
+ ['Procfile', ['heroku', 'purple']],
+ ['cmakelists.txt', ['makefile', 'blue']],
+ ['CMakeLists.txt', ['makefile', 'blue']],
+ ['CMAKELISTS.txt', ['makefile', 'blue']],
+ ['CMAKELISTS.TXT', ['makefile', 'blue']],
+ ['omakefile', ['makefile', 'grey-light']],
+ ['OMakefile', ['makefile', 'grey-light']],
+ ['OMAKEFILE', ['makefile', 'grey-light']],
+ ['qmakefile', ['makefile', 'purple']],
+ ['QMakefile', ['makefile', 'purple']],
+ ['QMAKEFILE', ['makefile', 'purple']],
+ ['makefile', ['makefile', 'orange']],
+ ['Makefile', ['makefile', 'orange']],
+ ['MAKEFILE', ['makefile', 'orange']],
+ ['CONTRIBUTING.md', ['license', 'red']],
+ ['CONTRIBUTING.txt', ['license', 'red']],
+ ['CONTRIBUTING', ['license', 'red']],
+ ['COMPILING.md', ['license', 'orange']],
+ ['COMPILING.txt', ['license', 'orange']],
+ ['COMPILING', ['license', 'orange']],
+ ['COPYING.md', ['license', 'yellow']],
+ ['COPYING.txt', ['license', 'yellow']],
+ ['COPYING', ['license', 'yellow']],
+ ['LICENCE.md', ['license', 'yellow']],
+ ['LICENSE.md', ['license', 'yellow']],
+ ['LICENCE.txt', ['license', 'yellow']],
+ ['LICENSE.txt', ['license', 'yellow']],
+ ['LICENCE', ['license', 'yellow']],
+ ['LICENSE', ['license', 'yellow']],
+ ['gulpfile.js', ['gulp', 'red']],
+ ['gulpfile', ['gulp', 'red']],
+ ['Gulpfile', ['gulp', 'red']],
+ ['GULPFILE', ['gulp', 'red']],
+ ['docker-compose.override.yaml', ['docker', 'pink']],
+ ['docker-compose.override.yml', ['docker', 'pink']],
+ ['docker-compose.yaml', ['docker', 'pink']],
+ ['docker-compose.yml', ['docker', 'pink']],
+ ['docker-healthcheck', ['docker', 'green']],
+ ['.dockerignore', ['docker', 'grey']],
+ ['DOCKERFILE', ['docker', 'blue']],
+ ['Dockerfile', ['docker', 'blue']],
+ ['dockerfile', ['docker', 'blue']],
+ ['gemfile', ['ruby', 'red']],
+ ['Gemfile', ['ruby', 'red']],
+ ['mix', ['hex', 'red']],
+ ],
+ default: ['default', 'white'],
+};
+
+const rawIcons = {
+ astro:
+ '',
+ bsl: '',
+ mdo: '',
+ salesforce:
+ '',
+ asm: '',
+ bicep:
+ '',
+ bazel:
+ '',
+ c: '',
+ 'c-sharp':
+ '',
+ html: '',
+ cpp: '',
+ clojure:
+ '',
+ coldfusion:
+ '',
+ coffee:
+ '',
+ config:
+ '',
+ crystal:
+ '',
+ crystal_embedded:
+ '',
+ json: '',
+ css: '',
+ csv: '',
+ xls: '',
+ cu: '',
+ cake: '',
+ cake_php:
+ '',
+ d: '',
+ word: '',
+ ejs: '',
+ elixir:
+ '',
+ elixir_script:
+ '',
+ hex: '',
+ elm: '',
+ favicon:
+ '',
+ 'f-sharp':
+ '',
+ git: '',
+ go2: '',
+ go: '',
+ godot:
+ '',
+ gradle:
+ '',
+ grails:
+ '',
+ graphql:
+ '',
+ hacklang:
+ '',
+ haml: '',
+ mustache:
+ '',
+ haskell:
+ '',
+ haxe: '',
+ jade: '',
+ java: '',
+ javascript:
+ '',
+ jinja:
+ '',
+ julia:
+ '',
+ karma:
+ '',
+ kotlin:
+ '',
+ dart: '',
+ less: '',
+ liquid:
+ '',
+ livescript:
+ '',
+ lua: '',
+ markdown:
+ '',
+ argdown:
+ '',
+ info: '',
+ clock:
+ '',
+ maven:
+ '',
+ nim: '',
+ github:
+ '',
+ notebook:
+ '',
+ nunjucks:
+ '',
+ npm: '',
+ ocaml:
+ '',
+ odata:
+ '',
+ perl: '',
+ php: '',
+ pipeline:
+ '',
+ pddl: '',
+ plan: '',
+ happenings:
+ '',
+ powershell:
+ '',
+ prisma:
+ '',
+ pug: '',
+ puppet:
+ '',
+ purescript:
+ '',
+ python:
+ '',
+ react:
+ '',
+ reasonml:
+ '',
+ rescript:
+ '',
+ R: '',
+ ruby: '',
+ html_erb:
+ '',
+ rust: '',
+ sass: '',
+ spring:
+ '',
+ slim: '',
+ smarty:
+ '',
+ sbt: '',
+ scala:
+ '',
+ ethereum:
+ '',
+ stylus:
+ '',
+ svelte:
+ '',
+ swift:
+ '',
+ db: '',
+ terraform:
+ '',
+ tex: '',
+ default:
+ '',
+ twig: '',
+ typescript:
+ '',
+ tsconfig:
+ '',
+ vala: '',
+ vue: '',
+ wasm: '',
+ wat: '',
+ xml: '',
+ yml: '',
+ prolog:
+ '',
+ zig: '',
+ zip: '',
+ wgt: '',
+ illustrator:
+ '',
+ photoshop:
+ '',
+ pdf: '',
+ font: '',
+ image:
+ '',
+ svg: '',
+ sublime:
+ '',
+ 'code-search':
+ '',
+ shell:
+ '',
+ video:
+ '',
+ audio:
+ '',
+ windows:
+ '',
+ jenkins:
+ '',
+ babel:
+ '',
+ bower:
+ '',
+ docker:
+ '',
+ 'code-climate':
+ '',
+ eslint:
+ '',
+ firebase:
+ '',
+ firefox:
+ '',
+ gitlab:
+ '',
+ grunt:
+ '',
+ gulp: '',
+ ionic:
+ '',
+ platformio:
+ '',
+ rollup:
+ '',
+ stylelint:
+ '',
+ yarn: '',
+ webpack:
+ '',
+ lock: '',
+ license:
+ '',
+ makefile:
+ '',
+ heroku:
+ '',
+ todo: '',
+ npm_ignored:
+ '',
+ ignored:
+ '',
+};
+
+type IconDetails = [string, string];
+
+interface SetiTheme {
+ blue: string;
+ grey: string;
+ 'grey-light': string;
+ green: string;
+ orange: string;
+ pink: string;
+ purple: string;
+ red: string;
+ white: string;
+ yellow: string;
+ ignore: string;
+}
+
+type Color = keyof SetiTheme;
+
+interface Icon {
+ svg: string;
+ color: Color;
+}
+
+const definitions = rawDefinitions as unknown as {
+ default: IconDetails;
+ extensions: { [extension: string]: IconDetails };
+ files: { [file: string]: IconDetails };
+ partials: [string, IconDetails][];
+};
+const icons = rawIcons as unknown as {
+ [icon: string]: string;
+};
+
+const getDetails = (fileName: string): IconDetails => {
+ if (definitions.files[fileName]) {
+ return definitions.files[fileName];
+ }
+ let extension = fileName.slice(fileName.indexOf('.'));
+ while (extension !== '') {
+ if (definitions.extensions[extension]) {
+ return definitions.extensions[extension];
+ }
+ // look for next "."
+ extension = extension.slice(1);
+ extension = extension.slice(extension.indexOf('.'));
+ }
+ for (const partial of definitions.partials) {
+ if (fileName.indexOf(partial[0]) > -1) {
+ return partial[1];
+ }
+ }
+ return definitions.default;
+};
+
+export const getIcon = (fileName: string): Icon => {
+ const [icon, color] = getDetails(fileName);
+ return { svg: icons[icon], color } as Icon;
+};
diff --git a/src/components/internal/rehype-file-tree.ts b/src/components/internal/rehype-file-tree.ts
new file mode 100644
index 0000000000000..d9a6a381caf47
--- /dev/null
+++ b/src/components/internal/rehype-file-tree.ts
@@ -0,0 +1,117 @@
+import { rehype } from 'rehype';
+import { visit, CONTINUE, SKIP } from 'unist-util-visit';
+import { fromHtml } from 'hast-util-from-html';
+import { toString } from 'hast-util-to-string';
+import { h } from 'hastscript';
+import type { Element, HChild } from 'hastscript/lib/core';
+import { getIcon } from './file-tree-icons';
+
+/** Make a text node with the pass string as its contents. */
+const Text = (value = ''): { type: 'text'; value: string } => ({ type: 'text', value });
+
+/** Convert an HTML string containing an SVG into a HAST element node. */
+const makeSVGIcon = (svgString: string) => {
+ const root = fromHtml(svgString, { fragment: true });
+ const svg = root.children[0] as Element;
+ svg.properties = {
+ ...svg.properties,
+ width: 16,
+ height: 16,
+ class: 'tree-icon',
+ 'aria-hidden': 'true',
+ };
+ return svg;
+};
+
+const FileIcon = (filename: string) => {
+ const { svg } = getIcon(filename);
+ return makeSVGIcon(svg);
+};
+
+const FolderIcon = makeSVGIcon(
+ ''
+);
+
+export const fileTreeProcessor = rehype().use(function fileTree() {
+ return (tree, file) => {
+ const { directoryLabel } = file.data as { directoryLabel: string };
+ visit(tree, 'element', (node) => {
+ // Strip nodes that only contain newlines
+ node.children = node.children.filter(
+ (child) => child.type === 'comment' || child.type !== 'text' || !/^\n+$/.test(child.value)
+ );
+
+ if (node.tagName !== 'li') return CONTINUE;
+
+ // Ensure node has properties so we can assign classes later.
+ if (!node.properties) node.properties = {};
+
+ const [firstChild, ...otherChildren] = node.children;
+
+ const comment: HChild[] = [];
+ if (firstChild.type === 'text') {
+ const [filename, ...fragments] = firstChild.value.split(' ');
+ firstChild.value = filename;
+ comment.push(fragments.join(' '));
+ }
+ const subTreeIndex = otherChildren.findIndex(
+ (child) => child.type === 'element' && child.tagName === 'ul'
+ );
+ const commentNodes =
+ subTreeIndex > -1 ? otherChildren.slice(0, subTreeIndex) : [...otherChildren];
+ otherChildren.splice(0, subTreeIndex > -1 ? subTreeIndex : otherChildren.length);
+ comment.push(...commentNodes);
+
+ const firstChildTextContent = toString(firstChild);
+
+ // Decide a node is a directory if it ends in a `/` or contains another list.
+ const isDirectory =
+ /\/\s*$/.test(firstChildTextContent) ||
+ otherChildren.some((child) => child.type === 'element' && child.tagName === 'ul');
+ const isPlaceholder = /^\s*(\.{3}|…)\s*$/.test(firstChildTextContent);
+ const isHighlighted = firstChild.type === 'element' && firstChild.tagName === 'strong';
+ const hasContents = otherChildren.length > 0;
+
+ const fileExtension = isDirectory
+ ? 'dir'
+ : firstChildTextContent.trim().split('.').pop() || '';
+
+ const icon = h('span', isDirectory ? FolderIcon : FileIcon(firstChildTextContent));
+ if (!icon.properties) icon.properties = {};
+ if (isDirectory) {
+ icon.properties['aria-label'] = directoryLabel;
+ }
+
+ node.properties.class = isDirectory ? 'directory' : 'file';
+ if (isPlaceholder) node.properties.class += ' empty';
+ node.properties['data-filetype'] = fileExtension;
+
+ const treeEntry = h(
+ 'span',
+ { class: 'tree-entry' },
+ h('span', { class: isHighlighted ? 'highlight' : '' }, [
+ isPlaceholder ? null : icon,
+ firstChild,
+ ]),
+ Text(comment.length > 0 ? ' ' : ''),
+ comment.length > 0 ? h('span', { class: 'comment' }, ...comment) : Text()
+ );
+
+ if (isDirectory) {
+ node.children = [
+ h('details', { open: hasContents }, [
+ h('summary', treeEntry),
+ ...(hasContents ? otherChildren : [h('ul', h('li', '…'))]),
+ ]),
+ ];
+ // Continue down the tree.
+ return CONTINUE;
+ }
+
+ node.children = [treeEntry, ...otherChildren];
+
+ // Files can’t contain further files or directories, so skip iterating children.
+ return SKIP;
+ });
+ };
+});
diff --git a/src/i18n/en/ui.ts b/src/i18n/en/ui.ts
index 7438f2225cb78..82322c2787cee 100644
--- a/src/i18n/en/ui.ts
+++ b/src/i18n/en/ui.ts
@@ -84,4 +84,6 @@ export default {
'tutorial.getReady': 'Get ready to…',
// Feedback Fish widget
'feedback.button': 'Give us feedback',
+ // `` component
+ 'fileTree.directoryLabel': 'Directory',
};
diff --git a/src/pages/en/core-concepts/project-structure.md b/src/pages/en/core-concepts/project-structure.md
index 1c63dd24adf58..6591f3a1af474 100644
--- a/src/pages/en/core-concepts/project-structure.md
+++ b/src/pages/en/core-concepts/project-structure.md
@@ -3,6 +3,8 @@ layout: ~/layouts/MainLayout.astro
title: Project Structure
description: Learn how to structure a project with Astro.
i18nReady: true
+setup: |
+ import FileTree from '~/components/FileTree.astro'
---
Your new Astro project generated from the `create-astro` CLI wizard already includes some files and folders. Others, you will create yourself and add to Astro's existing file structure.
@@ -23,30 +25,29 @@ Astro leverages an opinionated folder layout for your project. Every Astro proje
A common Astro project directory might look like this:
-```
-├── src/
-│ ├── components/
-│ │ ├── Header.astro
-│ │ └-─ Button.jsx
-│ ├── layouts/
-│ │ └-─ PostLayout.astro
-│ └── pages/
-│ │ ├── posts/
-│ │ │ ├── post1.md
-│ │ │ ├── post2.md
-│ │ │ └── post3.md
-│ │ └── index.astro
-│ └── styles/
-│ └-─ global.css
-├── public/
-│ ├── robots.txt
-│ ├── favicon.svg
-│ └-─ social-image.png
-├── astro.config.mjs
-├── package.json
-└── tsconfig.json
-
-```
+
+- public/
+ - robots.txt
+ - favicon.svg
+ - social-image.png
+- src/
+ - components/
+ - Header.astro
+ - Button.jsx
+ - layouts/
+ - PostLayout.astro
+ - pages/
+ - posts/
+ - post1.md
+ - post2.md
+ - post3.md
+ - index.astro
+ - styles/
+ - global.css
+- astro.config.mjs
+- package.json
+- tsconfig.json
+
### `src/`
diff --git a/src/pages/en/core-concepts/routing.md b/src/pages/en/core-concepts/routing.md
index c66d53015a316..2dc6f7758e879 100644
--- a/src/pages/en/core-concepts/routing.md
+++ b/src/pages/en/core-concepts/routing.md
@@ -3,6 +3,8 @@ layout: ~/layouts/MainLayout.astro
title: Routing
description: An intro to routing with Astro.
i18nReady: true
+setup: |
+ import FileTree from '~/components/FileTree.astro'
---
Astro uses **file-based routing** to generate your build URLs based on the file layout of your project `src/pages/` directory. When a file is added to the `src/pages` directory of your project, it is automatically available as a route based on its filename.
@@ -221,14 +223,13 @@ const { title, text } = page;
It's possible for multiple routes to match the same URL path. For example each of these routes would match `/posts/create`:
-```
-└── pages/
-│ ├── posts/
-│ │ ├── create.astro
-│ │ ├── [pid].astro
-│ │ └── [...slug].astro
-
-```
+
+- src/pages/
+ - posts/
+ - create.astro
+ - [pid].astro
+ - [...slug].astro
+
Astro needs to know which route should be used to build the page. To do so, it sorts them according to the following rules:
@@ -381,16 +382,15 @@ This allows you to create private pages, and also to co-locate tests, utilities,
In this example, only `src/pages/index.astro` and `src/pages/posts/post1.md` will be built as page routes and HTML files.
-```md mark="post1.md" mark="index.astro"
-src/
-└── pages/
- ├── _hidden-directory/
- │ ├── page1.md
- │ └── page2.md
- ├── _hidden-page.astro
- ├── index.astro
- └── posts/
- ├── _SomeComponent.astro
- ├── _utils.js
- └── post1.md
-```
+
+- src/pages/
+ - _hidden-directory/
+ - page1.md
+ - page2.md
+ - _hidden-page.astro
+ - **index.astro**
+ - posts/
+ - _SomeComponent.astro
+ - _utils.js
+ - **post1.md**
+
diff --git a/src/pages/en/guides/cms/contentful.md b/src/pages/en/guides/cms/contentful.md
index c4abe21adb24c..ec87dd7a4c70f 100644
--- a/src/pages/en/guides/cms/contentful.md
+++ b/src/pages/en/guides/cms/contentful.md
@@ -2,6 +2,7 @@
title: Contentful & Astro
description: Add content to your Astro project using Contentful as a CMS
setup: |
+ import FileTree from '~/components/FileTree.astro'
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'
layout: ~/layouts/CMSLayout.astro
service: Contentful
@@ -54,13 +55,13 @@ Read more about [using environment variables](/en/guides/environment-variables/)
Your root directory should now include these new files:
-```ini title="Project Structure" ins={2-3}
-├── src/
-│ └── env.d.ts
-├── .env
-├── astro.config.mjs
-└── package.json
-```
+
+- src/
+ - **env.d.ts**
+- **.env**
+- astro.config.mjs
+- package.json
+
### Installing dependencies
@@ -111,15 +112,15 @@ At build time, your content will be fetched from the **Contentful delivery API**
Finally, your root directory should now include these new files:
-```ini ins={3-4}
-├── src/
-│ └── env.d.ts
-│ └── lib/
-│ └── contentful.ts
-├── .env
-├── astro.config.mjs
-└── package.json
-```
+
+- src/
+ - env.d.ts
+ - lib/
+ - **contentful.ts**
+- .env
+- astro.config.mjs
+- package.json
+
### Fetching data
diff --git a/src/pages/en/guides/cms/storyblok.md b/src/pages/en/guides/cms/storyblok.md
index 5af313613962c..63d680a6b938c 100644
--- a/src/pages/en/guides/cms/storyblok.md
+++ b/src/pages/en/guides/cms/storyblok.md
@@ -4,6 +4,7 @@ description: Add content to your Astro project using Storyblok as a CMS
layout: ~/layouts/CMSLayout.astro
service: Storyblok
setup: |
+ import FileTree from '~/components/FileTree.astro'
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'
stub: false
---
@@ -36,12 +37,12 @@ Now, you should be able to use these environment variables in your project.
Your root directory should now include this new file:
-```ini title="Project Structure" ins={2}
-├── src/
-├── .env
-├── astro.config.mjs
-└── package.json
-```
+
+- src/
+- **.env**
+- astro.config.mjs
+- package.json
+
### Installing dependencies
@@ -148,14 +149,14 @@ To render our content, the integration provides utility functions such as:
Your root directory should include this new file:
-```ini title="Project Structure" ins={3}
-├── src/
-│ └── storyblok/
-│ └── BlogPost.astro
-├── .env
-├── astro.config.mjs
-└── package.json
-```
+
+- src/
+ - storyblok/
+ - **BlogPost.astro**
+- .env
+- astro.config.mjs
+- package.json
+
Finally, to connect the `blogPost` Blok to the `BlogPost` component, add a new property to your components object in your Astro config file.
diff --git a/src/pages/en/guides/cms/wordpress.md b/src/pages/en/guides/cms/wordpress.md
index 482dd69bc853a..cb2b350511771 100644
--- a/src/pages/en/guides/cms/wordpress.md
+++ b/src/pages/en/guides/cms/wordpress.md
@@ -4,6 +4,7 @@ description: Add content to your Astro project using WordPress as a CMS
layout: ~/layouts/CMSLayout.astro
stub: false
service: WordPress
+setup: import FileTree from '~/components/FileTree.astro'
---
[WordPress](https://wordpress.org/) is a content management system that includes its own frontend, but can also be used as a headless CMS to provide content to your Astro project.
@@ -73,15 +74,16 @@ This example fetches data from a WordPress site whose content types have already
The page `src/pages/index.astro` lists each dinosaur, with a description and link to its own page.
-```ini title="Project Structure" {3}
-├── src/
-├── pages/
-│ └── index.astro
-│ └── dinos/
-│ └── [slug].astro
-├── astro.config.mjs
-└── package.json
-```
+
+- src/
+ - pages/
+ - **index.astro**
+ - dinos/
+ - [slug].astro
+- astro.config.mjs
+- package.json
+
+
Fetching via the API returns an object that includes the properties:
- `title.rendered` - Contains the HTML rendering of the title of the post.
diff --git a/src/pages/en/install/manual.md b/src/pages/en/install/manual.md
index 9dd3760385ed0..a603a32b41066 100644
--- a/src/pages/en/install/manual.md
+++ b/src/pages/en/install/manual.md
@@ -3,6 +3,7 @@ title: Install Astro manually
description: How to install Astro manually with NPM, PNPM, or Yarn.
layout: ~/layouts/MainLayout.astro
setup: |
+ import FileTree from '~/components/FileTree.astro';
import InstallGuideTabGroup from '~/components/TabGroup/InstallGuideTabGroup.astro';
import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'
i18nReady: true
@@ -172,19 +173,19 @@ Finally, create `src/env.d.ts` to let TypeScript know about ambient types availa
If you have followed the steps above, your project directory should now look like this:
-```
-├── node_modules/
-├── public/
-│ └── robots.txt
-├── src/
-│ ├── pages/
-│ │ └── index.astro
-│ └── env.d.ts
-├── astro.config.mjs
-├── package-lock.json (or: yarn.lock, pnpm-lock.yaml, etc.)
-├── package.json
-└── tsconfig.json
-```
+
+- node_modules/
+- public/
+ - robots.txt
+- src/
+ - pages/
+ - index.astro
+ - env.d.ts
+- astro.config.mjs
+- package-lock.json or `yarn.lock`, `pnpm-lock.yaml`, etc.
+- package.json
+- tsconfig.json
+
Congratulations, you're now set up to use Astro!
diff --git a/src/pages/en/reference/publish-to-npm.md b/src/pages/en/reference/publish-to-npm.md
index d0d7e149961a1..192a5b17bac1a 100644
--- a/src/pages/en/reference/publish-to-npm.md
+++ b/src/pages/en/reference/publish-to-npm.md
@@ -3,6 +3,8 @@ layout: ~/layouts/MainLayout.astro
title: Publish to NPM
description: Learn how to publish Astro components to NPM
i18nReady: true
+setup: |
+ import FileTree from '~/components/FileTree.astro'
---
Building a new Astro component? **Publish it to [npm!](https://npmjs.com/)**
@@ -41,17 +43,17 @@ Before diving in, it will help to have a basic understanding of:
To create a new package we strongly recommend configuring your development enviroment to use **workspaces** within your project. This will allow you to develop your component alongside a working copy of Astro.
-```
-my-new-component-directory/
-├─ demo/
-| └─ ... for testing and demonstration
-├─ package.json
-└─ packages/
- └─ my-component/
- ├─ index.js
- ├─ package.json
- └─ ... additional files used by the package
-```
+
+- my-new-component-directory/
+ - demo/
+ - ... for testing and demonstration
+ - package.json
+ - packages/
+ - my-component/
+ - index.js
+ - package.json
+ - ... additional files used by the package
+
In this example, named `my-project`, we create a project with a single package, named `my-component`, and a `demo/` directory for testing and demonstrating the component.
@@ -238,12 +240,12 @@ In the meantime, our current recommendation for testing is:
3. Each page should include some different component usage that you'd like to test.
4. Run `astro build` to build your fixtures, then compare the output of the `dist/__fixtures__/` directory to what you expected.
-```bash
-my-project/demo/src/pages/__fixtures__/
- ├─ test-name-01.astro
- ├─ test-name-02.astro
- └─ test-name-03.astro
-```
+
+- my-project/demo/src/pages/\_\_fixtures\_\_/
+ - test-name-01.astro
+ - test-name-02.astro
+ - test-name-03.astro
+
## Publishing your component