Compare commits

..

No commits in common. "e7b8dc721aa224ba55038bad30fd59cabd48a755" and "0f4f34cf7912eb7bdac116b1b3b22ec10af97e6b" have entirely different histories.

16 changed files with 257 additions and 439 deletions

View File

@ -1,27 +0,0 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm install -g pnpm && pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm exec playwright test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View File

@ -1,33 +0,0 @@
name: 'Publish to Netlify'
on:
push:
branches: [ prod, deploy ]
pull_request:
branches: [ prod, deploy ]
jobs:
publish:
timeout-minutes: 10
runs-on: debian-latest
steps:
- uses: actions/checkout@v3
name: Checkout
- name: Build
run: |
npm --version && node --version
npm ci --no-update-notifier
npm run build
- uses: https://github.com/nwtgck/actions-netlify@v2.0
name: Deploy
with:
publish-dir: './dist'
production-deploy: true
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deployed from Gitea Action"
enable-commit-comment: false
enable-pull-request-comment: false
overwrites-pull-request-comment: true
enable-github-deployment: false
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
timeout-minutes: 1

View File

@ -90,7 +90,6 @@
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.5.2", "@fortawesome/fontawesome-free": "^6.5.2",
"@sentry/sveltekit": "^7.112.2", "@sentry/sveltekit": "^7.112.2",
"@sveltejs/adapter-cloudflare": "^4.4.0",
"@yushijinhun/three-minifier-rollup": "^0.4.0", "@yushijinhun/three-minifier-rollup": "^0.4.0",
"vite-plugin-tailwind-purgecss": "^0.3.3", "vite-plugin-tailwind-purgecss": "^0.3.3",
"vitest": "^1.5.2" "vitest": "^1.5.2"

View File

@ -5,26 +5,14 @@
<meta name="generator" content="gh:importantimport/urara" /> <meta name="generator" content="gh:importantimport/urara" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/assets/apple-touch-icon.png" /> <link rel="apple-touch-icon" sizes="180x180" href="%sveltekit.assets%/assets/apple-touch-icon.png" />
<link rel="icon" href="%sveltekit.assets%/assets/maskable@192.png" /> <link rel="icon" href="%sveltekit.assets%/assets/favicon@192.png" />
<link rel="manifest" crossorigin="use-credentials" href="/manifest.webmanifest" /> <link rel="manifest" crossorigin="use-credentials" href="/manifest.webmanifest" />
<link rel="alternate" type="application/feed+json" href="/feed.json" /> <link rel="alternate" type="application/feed+json" href="/feed.json" />
<link rel="alternate" type="application/atom+xml" href="/atom.xml" /> <link rel="alternate" type="application/atom+xml" href="/atom.xml" />
<link rel="sitemap" type="application/xml" href="/sitemap.xml" /> <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
%sveltekit.head% %sveltekit.head%
</head> </head>
<body itemscope itemtype="https://schema.org/WebPage" data-sveltekit-prefetch> <body itemscope itemtype="https://schema.org/WebPage">
<script>
const themeLocalStorageKey = 'theme'
if (
!!localStorage.getItem(themeLocalStorageKey)
? localStorage.getItem(themeLocalStorageKey) === 'dark'
: window.matchMedia('(prefers-color-scheme: dark)').matches
) {
document.body.classList.add('dark')
} else {
document.body.classList.add('light')
}
</script>
<div style="display: contents">%sveltekit.body%</div> <div style="display: contents">%sveltekit.body%</div>
</body> </body>
</html> </html>

View File

@ -45,8 +45,8 @@ h6 {
@font-face { @font-face {
font-family: 'Orbitron-Variable'; font-family: 'Orbitron-Variable';
src: src:
url('/assets/fonts/Orbitron-VariableFont_wght.woff') format('woff'), url('assets/fonts/Orbitron-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/Orbitron-VariableFont_wght.ttf') format('truetype'); url('assets/fonts/Orbitron-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900; font-weight: 300 900;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
@ -55,8 +55,8 @@ h6 {
@font-face { @font-face {
font-family: 'RobotoMono-Italic-Variable'; font-family: 'RobotoMono-Italic-Variable';
src: src:
url('/assets/fonts/RobotoMono-Italic-VariableFont_wght.woff') format('woff'), url('assets/fonts/RobotoMono-Italic-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/RobotoMono-Italic-VariableFont_wght.ttf') format('truetype'); url('assets/fonts/RobotoMono-Italic-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900; font-weight: 300 900;
font-display: swap; font-display: swap;
font-style: normal; font-style: normal;
@ -65,8 +65,8 @@ h6 {
@font-face { @font-face {
font-family: 'RobotoMono-VariableFont'; font-family: 'RobotoMono-VariableFont';
src: src:
url('/assets/fonts/RobotoMono-VariableFont_wght.woff') format('woff'), url('assets/fonts/RobotoMono-VariableFont_wght.woff') format('woff'),
url('/assets/fonts/RobotoMono-VariableFont_wght.ttf') format('truetype'); url('assets/fonts/RobotoMono-VariableFont_wght.ttf') format('truetype');
font-weight: 300 900; font-weight: 300 900;
font-display: swap; font-display: swap;
font-style: italic; font-style: italic;

View File

@ -42,58 +42,3 @@ export const maskable: { [key: number]: Icon } = {
type: 'image/png' type: 'image/png'
} }
} }
// <link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
// <link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
// <link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
// <link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
// <link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
// <link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
// <link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
// <link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
// <link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
// <link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
// <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
// <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
// <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
// <link rel="manifest" href="/manifest.json">
// <meta name="msapplication-TileColor" content="#ffffff">
// <meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
// <meta name="theme-color" content="#ffffff">
// {
// "src": "\/android-icon-36x36.png",
// "sizes": "36x36",
// "type": "image\/png",
// "density": "0.75"
// },
// {
// "src": "\/android-icon-48x48.png",
// "sizes": "48x48",
// "type": "image\/png",
// "density": "1.0"
// },
// {
// "src": "\/android-icon-72x72.png",
// "sizes": "72x72",
// "type": "image\/png",
// "density": "1.5"
// },
// {
// "src": "\/android-icon-96x96.png",
// "sizes": "96x96",
// "type": "image\/png",
// "density": "2.0"
// },
// {
// "src": "\/android-icon-144x144.png",
// "sizes": "144x144",
// "type": "image\/png",
// "density": "3.0"
// },
// {
// "src": "\/android-icon-192x192.png",
// "sizes": "192x192",
// "type": "image\/png",
// "density": "4.0"
// }

View File

@ -9,7 +9,7 @@ export const post: PostConfig = {
sortBy: 'created', // sort by: created / updated sortBy: 'created', // sort by: created / updated
sortDir: 'down', // sort order: up / down sortDir: 'down', // sort order: up / down
form: true, // enable comments: true / false form: true, // enable comments: true / false
commentParade: false // enable anonymous comments: true / false commentParade: true // enable anonymous comments: true / false
} }
} }
} }

View File

@ -3,7 +3,7 @@
</script> </script>
{#if $page.status === 404} {#if $page.status === 404}
<div class="flex flex-col items-center m-4 py-8 space-y-8"> <div class="flex flex-col items-center m-4 py-8">
<div <div
class="relative mb-[3.33vh] flex h-24 w-24 md:h-64 md:w-64 items-center justify-center bg-surface-800" class="relative mb-[3.33vh] flex h-24 w-24 md:h-64 md:w-64 items-center justify-center bg-surface-800"
> >
@ -15,18 +15,13 @@
<h1 class="h1 absolute leading-[5rem] text-white">404</h1> <h1 class="h1 absolute leading-[5rem] text-white">404</h1>
</div> </div>
<h4 class="flex text-center justify-center w-1/2"> <h2 class="h4 mb-2">
? My God, what are you doing here, this page doesn't exist and so on and so on. ? My God, what are you doing here, this page doesn't exist and so on and so on.
</h4> </h2>
<p> <p>
This site either never existed or just suddenly committed disappearance from this This site either never existed or just suddenly committed disappearance from this
universe after I changed something. universe.
</p> </p>
<h5>Maybe try one of these links?</h5>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/projects">Projects</a></li>
</ul>
</div> </div>
{:else} {:else}
<h2>{$page.status}</h2> <h2>{$page.status}</h2>
@ -37,7 +32,8 @@
<p><strong>Sorry!</strong> A grave error has occured. Maybe try one of these links?</p> <p><strong>Sorry!</strong> A grave error has occured. Maybe try one of these links?</p>
<ul> <ul>
<li><a class="link link-accent" href="/">Home</a></li> <li><a href="/">Home</a></li>
<li><a class="link link-accent" href="/projects">Projects</a></li> <li><a href="/blog">Blog</a></li>
<li><a href="/projects">Projects</a></li>
</ul> </ul>
{/if} {/if}

View File

@ -1,5 +1,5 @@
import type { LayoutLoad } from './$types' import type { LayoutLoad } from './$types'
export const prerender = true export const prerender = false
export const trailingSlash = 'always' export const trailingSlash = 'always'
export const load: LayoutLoad = async ({ url, fetch }) => ({ export const load: LayoutLoad = async ({ url, fetch }) => ({
path: url.pathname, path: url.pathname,

View File

@ -21,23 +21,6 @@
storedTitle.set('') storedTitle.set('')
function handleTagClick(tag) {
if (tags.includes(tag)) {
tags = tags.filter(tagName => tagName !== tag);
} else {
tags = [...tags, tag];
}
scrollToRecentFeed();
}
function scrollToRecentFeed() {
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
}
$: storedPosts.subscribe(storedPosts => (allPosts = storedPosts.filter(post => !post.flags?.includes('unlisted')))) $: storedPosts.subscribe(storedPosts => (allPosts = storedPosts.filter(post => !post.flags?.includes('unlisted'))))
$: storedTags.subscribe(storedTags => (allTags = storedTags as string[])) $: storedTags.subscribe(storedTags => (allTags = storedTags as string[]))
@ -46,9 +29,13 @@
$: if (tags) { $: if (tags) {
posts = !tags ? allPosts : allPosts.filter(post => tags.every(tag => post.tags?.includes(tag))) posts = !tags ? allPosts : allPosts.filter(post => tags.every(tag => post.tags?.includes(tag)))
if (loaded && browser) { if (browser) {
const newUrl = `/?tags=${tags.toString()}`; const newUrl = tags.length > 0 ? `/?tags=${tags.toString()}#recent-feed` : '/#recent-feed';
history.pushState(null, '', newUrl); history.pushState(null, '', newUrl);
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
} }
} }
@ -92,7 +79,17 @@
{#each allTags as tag} {#each allTags as tag}
<button <button
id={tag} id={tag}
on:click={() => handleTagClick(tag)} on:click|preventDefault={() => {
if (tags.includes(tag)) {
tags = tags.filter(tagName => tagName != tag);
} else {
tags = [...tags, tag];
}
const recentFeedSection = document.getElementById('recent-feed');
if (recentFeedSection) {
recentFeedSection.scrollIntoView({ behavior: 'smooth' });
}
}}
class:!btn-secondary={tags.includes(tag)} class:!btn-secondary={tags.includes(tag)}
class:shadow-lg={tags.includes(tag)} class:shadow-lg={tags.includes(tag)}
class="btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 mt-4 mb-8 xl:m-0 whitespace-nowrap"> class="btn btn-sm btn-ghost normal-case border-dotted border-base-content/20 border-2 mt-4 mb-8 xl:m-0 whitespace-nowrap">

View File

@ -1 +0,0 @@
export const prerender = true

View File

@ -1,9 +1,6 @@
// svelte adapter // svelte adapter
import adapterAuto from '@sveltejs/adapter-auto'
import adapterNode from '@sveltejs/adapter-node' import adapterNode from '@sveltejs/adapter-node'
import adapterVercel from '@sveltejs/adapter-vercel'
import adapterNetlify from '@sveltejs/adapter-netlify'
import adapterCloudflare from '@sveltejs/adapter-cloudflare'
import adapterStatic from '@sveltejs/adapter-static' import adapterStatic from '@sveltejs/adapter-static'
// svelte preprocessor // svelte preprocessor
import { mdsvex } from 'mdsvex' import { mdsvex } from 'mdsvex'
@ -11,22 +8,14 @@ import mdsvexConfig from './mdsvex.config.js'
// import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' // import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import preprocess from 'svelte-preprocess' import preprocess from 'svelte-preprocess'
function getAdapter() { const adapter = {
if (Object.keys(process.env).some(key => key.includes('VERCEL'))) { auto: adapterAuto(),
return adapterVercel() node: adapterNode(),
} else if (Object.keys(process.env).some(key => key.includes('NETLIFY'))) { static: adapterStatic({
return adapterNetlify()
} else if (Object.keys(process.env).some(key => key.includes('CF_PAGES'))) {
return adapterCloudflare()
} else {
return process.env.ADAPTER === 'node'
? adapterNode({ out: 'build' })
: adapterStatic({
pages: 'build', pages: 'build',
assets: 'build', assets: 'build',
fallback: undefined fallback: undefined
}) })
}
} }
/** @type {import("@svletejs/kit".Config)} */ /** @type {import("@svletejs/kit".Config)} */
@ -37,7 +26,11 @@ export default {
inspector: true inspector: true
}, },
kit: { kit: {
adapter: getAdapter(), adapter: process.env.ADAPTER
? adapter[process.env.ADAPTER.toLowerCase()]
: Object.keys(process.env).some(key => ['VERCEL', 'NETLIFY'].includes(key))
? adapter['auto']
: adapter['static'],
alias: { alias: {
$lib: './src/lib', $lib: './src/lib',
$root: './', $root: './',
@ -51,18 +44,14 @@ export default {
prerender: { prerender: {
crawl: true, crawl: true,
handleMissingId: 'warn', handleMissingId: 'warn',
handleHttpError: ({ status, path, referrer, referenceType, message }) => { handleHttpError: details => {
// Handle blog trying to prerender relative links that it can't // Handle blog trying to prerender relative links that it can't
if ( if (details.status == 404 && details.path.startsWith('/blog' && '/projects') && details.referenceType == 'linked') {
(status == 404 && path.startsWith('/blog')) || console.warn(`PRERENDER ignored route ${details.path}`)
path.startsWith('/projects') ||
(path.startsWith('/') && referenceType == 'linked')
) {
console.warn(`PRERENDER ignored route ${path}`)
return return
} }
throw new Error(`${status} ${path} from ${referrer}, ~~~~~~~~~ message: ${message}~~~~~~~~~`) throw new Error(`${details.status} ${details.path} from ${details.referrer}`)
} }
} }
} }

View File

@ -1,28 +1,14 @@
{ {
"extends": "./.svelte-kit/tsconfig.json", "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "build",
"allowJs": true, "allowJs": true,
"checkJs": true, "checkJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": false,
"resolveJsonModule": true, "resolveJsonModule": true,
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"types": ["vite/client"] "types": ["vite/client", "vite-plugin-pwa/client"]
}, }
"include": [
"./types/**/$types.d.ts",
"./svelte.config.js",
"./vite.config.js",
"./vite.config.ts",
"./src/**/*.js",
"./src/**/*.ts",
"./src/**/*.svelte",
"./tests/**/*.js",
"./tests/**/*.ts",
"./tests/**/*.svelte"
],
"exclude": ["node_modules", "build/**/*", "src/lib/template/**/*", "src/generated/**/*"]
} }

View File

@ -9,11 +9,15 @@ import chokidar from 'chokidar'
import chalk from 'chalk' import chalk from 'chalk'
const config = { const config = {
extensions: ['md'], extensions: {
posts: ['md'],
images: ['jpg', 'png', 'webp', 'avif']
},
images: [''],
catch: ['ENOENT', 'EEXIST'] catch: ['ENOENT', 'EEXIST']
} }
const check = (ext: string) => (config.extensions.includes(ext) ? 'src/routes' : 'static') const check = (ext: string) => (config.extensions.posts.includes(ext) ? 'src/routes' : 'static')
const log = (color: string, msg: string, dest?: string | Error) => const log = (color: string, msg: string, dest?: string | Error) =>
console.log( console.log(
@ -37,13 +41,25 @@ const error = (err: { code: string; message: unknown }) => {
} }
const cpFile = (src: string, { stat = 'copy', dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) => const cpFile = (src: string, { stat = 'copy', dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) =>
fs config.extensions.images.includes(path.parse(src).ext.slice(1))
? fs
.copyFile(src, path.join('src/static', src.slice(6)))
.then(() => fs.copyFile(src, path.join('static', src.slice(6))))
.then(() => log('green', `${stat} file`, dest))
.catch(error)
: fs
.copyFile(src, dest) .copyFile(src, dest)
.then(() => log('green', `${stat} file`, dest)) .then(() => log('green', `${stat} file`, dest))
.catch(error) .catch(error)
const rmFile = (src: string, { dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) => const rmFile = (src: string, { dest = path.join(check(path.parse(src).ext.slice(1)), src.slice(6)) } = {}) =>
fs config.extensions.images.includes(path.parse(src).ext.slice(1))
? fs
.rm(path.join('src/static', src.slice(6)))
.then(() => fs.rm(path.join('static', src.slice(6))))
.then(() => log('yellow', 'remove file', dest))
.catch(error)
: fs
.rm(dest) .rm(dest)
.then(() => log('yellow', 'remove file', dest)) .then(() => log('yellow', 'remove file', dest))
.catch(error) .catch(error)
@ -63,7 +79,12 @@ const cpDir = (src: string) =>
}) })
) )
const mkDir = (src: string, { dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6))] } = {}) => { const mkDir = (
src: string,
{
dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6)), path.join('src/static', src.slice(6))]
} = {}
) => {
dest.forEach(path => dest.forEach(path =>
fs fs
.mkdir(path) .mkdir(path)
@ -72,7 +93,12 @@ const mkDir = (src: string, { dest = [path.join('src/routes', src.slice(6)), pat
) )
} }
const rmDir = (src: string, { dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6))] } = {}) => { const rmDir = (
src: string,
{
dest = [path.join('src/routes', src.slice(6)), path.join('static', src.slice(6)), path.join('src/static', src.slice(6))]
} = {}
) => {
dest.forEach(path => dest.forEach(path =>
fs fs
.rm(path, { force: true, recursive: true }) .rm(path, { force: true, recursive: true })
@ -91,12 +117,14 @@ const cleanDir = (src: string) =>
const build = () => { const build = () => {
mkDir('static', { dest: ['static'] }) mkDir('static', { dest: ['static'] })
mkDir('src/static', { dest: ['src/static'] })
cpDir('urara') cpDir('urara')
} }
const clean = () => { const clean = () => {
cleanDir('urara') cleanDir('urara')
rmDir('static', { dest: ['static'] }) rmDir('static', { dest: ['static'] })
rmDir('src/static', { dest: ['src/static'] })
} }
switch (process.argv[2]) { switch (process.argv[2]) {

View File

@ -1,49 +0,0 @@
#!/bin/bash
BASE_PATH="./user/blogs"
# Check if a directory name is provided
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <directory-name>"
exit 1
fi
# Get the current date in ISO 8601 format with timezone
CURRENT_DATE=$(date "+%Y-%m-%dT%H:%M:%S.000")
TIMEZONE_FORMATTED=$(date "+%z" | sed 's/\([0-9][0-9]\)\([0-9][0-9]\)/\1:\2/')
# Every time you enter pnpm run createblog YourDirName,
# a folder will be created for you based on the following configuration,
# along with a default content for index.md.
# You are free to edit the information as needed.
DIRECTORY="$BASE_PATH/$1"
MD_TEMPLATE="---
title:
description:
summary:
published: '$CURRENT_DATE$TIMEZONE_FORMATTED'
updated: '$CURRENT_DATE$TIMEZONE_FORMATTED'
cover: ./cover.jpg
coverCaption: Photo by
coverStyle: 'IN'
series_tag:
series_title:
tag:
- [svelte-QWER]
---"
# Ensure base path exists
if [ ! -d "$BASE_PATH" ]; then
mkdir -p "$BASE_PATH"
fi
# Check if directory exists
if [ -d "$DIRECTORY" ]; then
echo "Directory $DIRECTORY already exists!"
else
mkdir "$DIRECTORY"
echo "$MD_TEMPLATE" > "$DIRECTORY/index.md"
echo "Directory $DIRECTORY and index.md created successfully!"
fi

View File

@ -91,8 +91,8 @@ export default defineConfig({
manifest: false, manifest: false,
scope: '/', scope: '/',
workbox: { workbox: {
globPatterns: ['robots.txt', 'posts.json', '**/*.{js,css,html,svg,ico,png,webp,avif}', 'prerendered/**/*.html'], globPatterns: ['posts.json', '**/*.{js,css,html,svg,ico,png,webp,avif}'],
globIgnores: ['**/sw*', '**/workbox-*', '*.xml', 'feed.json', 'tags.json'] globIgnores: ['**/sw*', '**/workbox-*']
} }
}) })
] ]