Big commit initial code.
This commit is contained in:
parent
6db6345faf
commit
0da2dba96a
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"name": "blink",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"author": "Bauke <me@bauke.xyz>",
|
||||||
|
"scripts": {
|
||||||
|
"start": "vite",
|
||||||
|
"test": "xo && stylelint 'source/**/*.scss' && tsc",
|
||||||
|
"deploy": "vite build --emptyOutDir && pnpm deploy:netlify",
|
||||||
|
"deploy:netlify": "netlify deploy --prod -d 'public' -s 'blink.bauke.xyz'"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"htm": "^3.1.0",
|
||||||
|
"modern-normalize": "^1.1.0",
|
||||||
|
"preact": "^10.6.4",
|
||||||
|
"preact-router": "^3.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^17.0.5",
|
||||||
|
"postcss": "^8.4.5",
|
||||||
|
"sass": "^1.45.1",
|
||||||
|
"stylelint": "^14.2.0",
|
||||||
|
"stylelint-config-standard-scss": "^3.0.0",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"vite": "^2.7.10",
|
||||||
|
"xo": "^0.47.0"
|
||||||
|
},
|
||||||
|
"stylelint": {
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard-scss"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"string-quotes": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"prettier": true,
|
||||||
|
"space": true
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,5 @@
|
||||||
|
# Netlify redirects and rewrites configuration
|
||||||
|
# https://docs.netlify.com/routing/redirects/
|
||||||
|
|
||||||
|
# Redirect any non-existing files to /
|
||||||
|
/* / 200
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Blink</title>
|
||||||
|
<link rel="stylesheet" href="./scss/modern-normalize.scss">
|
||||||
|
<link rel="stylesheet" href="./scss/style.scss">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
This site doesn't work without JavaScript, sorry. :(
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<script type="module" src="./ts/single-page-application.ts"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,19 @@
|
||||||
|
html {
|
||||||
|
font-size: 62.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background-1);
|
||||||
|
color: var(--foreground-1);
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--foreground-1);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--foreground-1);
|
||||||
|
color: var(--background-1);
|
||||||
|
text-decoration-color: var(--foreground-1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
h1,
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
.shared-footer {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
@use '../../node_modules/modern-normalize/modern-normalize.css';
|
|
@ -0,0 +1,9 @@
|
||||||
|
.home-page {
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
h1::after {
|
||||||
|
content: 'WIP';
|
||||||
|
font-size: 1rem;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
.not-found-page {
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
.release {
|
||||||
|
background-color: var(--background-2);
|
||||||
|
box-shadow: 0 0 1rem #000;
|
||||||
|
margin: 2rem auto;
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-main {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-links {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.no-links {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.release-link {
|
||||||
|
a {
|
||||||
|
background-color: var(--background-1);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
display: block;
|
||||||
|
padding: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--foreground-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
@use 'reset';
|
||||||
|
@use 'common';
|
||||||
|
|
||||||
|
// Theme definitions
|
||||||
|
@use 'themes/love';
|
||||||
|
|
||||||
|
// Component styling
|
||||||
|
@use 'components/shared-footer';
|
||||||
|
|
||||||
|
// Page styling
|
||||||
|
@use 'pages/home';
|
||||||
|
@use 'pages/not-found';
|
||||||
|
@use 'pages/release';
|
|
@ -0,0 +1,9 @@
|
||||||
|
body,
|
||||||
|
.love-dark {
|
||||||
|
--border-radius: 8px;
|
||||||
|
--foreground-1: #f2efff;
|
||||||
|
--foreground-2: #e6deff;
|
||||||
|
--background-1: #1f1731;
|
||||||
|
--background-2: #2a2041;
|
||||||
|
--accent-1: #d2b83a;
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {html, Component} from 'htm/preact';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
extra?: Record<string, string>;
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ExternalAnchor extends Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {extra, text, url} = this.props;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<a href="${url}" target="_blank" rel="noopener noreferrer" ...${extra}>
|
||||||
|
${text}
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import {Component, html} from 'htm/preact';
|
||||||
|
|
||||||
|
import ExternalAnchor from './external-anchor.js';
|
||||||
|
|
||||||
|
export default class SharedFooter extends Component {
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<footer class="shared-footer">
|
||||||
|
<${ExternalAnchor} text="GitHub" url="https://github.com/Bauke/blink" />
|
||||||
|
<span> v${window.blinkVersion} (${window.blinkCommitHash})</span>
|
||||||
|
</footer>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {Component, html} from 'htm/preact';
|
||||||
|
|
||||||
|
import SharedFooter from '../components/shared-footer.js';
|
||||||
|
|
||||||
|
export default class HomePage extends Component {
|
||||||
|
render() {
|
||||||
|
document.title = 'Blink';
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="home-page">
|
||||||
|
<header>
|
||||||
|
<h1>Blink</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<${SharedFooter} />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import {Component, html} from 'htm/preact';
|
||||||
|
|
||||||
|
import SharedFooter from '../components/shared-footer.js';
|
||||||
|
|
||||||
|
export default class NotFoundPage extends Component {
|
||||||
|
render() {
|
||||||
|
document.title = 'Page Not Found';
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="not-found-page">
|
||||||
|
<main>
|
||||||
|
<h1>Page Not Found</h1>
|
||||||
|
<a href="/">← Home</a>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<${SharedFooter} />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
import {Component, html} from 'htm/preact';
|
||||||
|
|
||||||
|
import ExternalAnchor from '../components/external-anchor.js';
|
||||||
|
import Release from '../utilities/release.js';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
mbid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type State = {
|
||||||
|
loading: 'calling-api' | 'invalid-id' | 'unknown-release' | 'finished';
|
||||||
|
release: Release | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ReleasePage extends Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
props.mbid = encodeURIComponent(props.mbid);
|
||||||
|
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
loading: 'calling-api',
|
||||||
|
release: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
validateMbid(mbid: string): boolean {
|
||||||
|
return mbid.length === 36 && mbid.split('-').length === 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const {mbid} = this.props;
|
||||||
|
|
||||||
|
if (!this.validateMbid(mbid)) {
|
||||||
|
this.setState({loading: 'invalid-id'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.setState({
|
||||||
|
loading: 'finished',
|
||||||
|
release: await Release.fromMbid(mbid),
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error);
|
||||||
|
this.setState({loading: 'unknown-release'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {mbid} = this.props;
|
||||||
|
const {loading, release} = this.state;
|
||||||
|
|
||||||
|
if (loading === 'calling-api') {
|
||||||
|
return html`Loading MBID: ${mbid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading === 'invalid-id') {
|
||||||
|
return html`Invalid MBID: ${mbid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading === 'unknown-release') {
|
||||||
|
return html`No release found with MBID: ${mbid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading === 'finished' && release !== undefined) {
|
||||||
|
document.title = release.display();
|
||||||
|
|
||||||
|
const image =
|
||||||
|
release.image === undefined
|
||||||
|
? undefined
|
||||||
|
: html`<img class="cover-art" src="${release.image}" />`;
|
||||||
|
|
||||||
|
const urls = release.links.map(
|
||||||
|
(link) =>
|
||||||
|
html`
|
||||||
|
<li class="release-link">
|
||||||
|
<${ExternalAnchor} url="${link.original}" text="${link.text}" />
|
||||||
|
</li>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (urls.length === 0) {
|
||||||
|
const editUrl = `https://musicbrainz.org/release/${mbid}/edit`;
|
||||||
|
urls.push(
|
||||||
|
html`
|
||||||
|
<li class="no-links">
|
||||||
|
<p>
|
||||||
|
There are no links for this release yet, consider${' '}
|
||||||
|
<${ExternalAnchor} url="${editUrl}" text="adding some" />?
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="release">
|
||||||
|
<header class="release-header">
|
||||||
|
${image}
|
||||||
|
<h1>${release.artist}<br />${release.title}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="release-main">
|
||||||
|
<ul class="release-links">
|
||||||
|
${urls}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
// Uncomment when debugging and using Preact's DevTools WebExtension.
|
||||||
|
// import 'preact/debug';
|
||||||
|
|
||||||
|
import {html, render} from 'htm/preact';
|
||||||
|
import {Router} from 'preact-router';
|
||||||
|
|
||||||
|
import HomePage from './pages/home.js';
|
||||||
|
import NotFoundPage from './pages/not-found.js';
|
||||||
|
import ReleasePage from './pages/release.js';
|
||||||
|
|
||||||
|
render(
|
||||||
|
html`
|
||||||
|
<${Router}>
|
||||||
|
<${HomePage} path="/" />
|
||||||
|
<${ReleasePage} path="/release/:mbid" />
|
||||||
|
<${NotFoundPage} default />
|
||||||
|
<//>
|
||||||
|
`,
|
||||||
|
document.body,
|
||||||
|
);
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Fixes TypeScript thinking this isn't a correct module.
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
// These are created in `vite.config.ts` and defined by Vite at build time.
|
||||||
|
blinkCommitHash: string;
|
||||||
|
blinkVersion: string;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"regex": "amazon\\.com$",
|
||||||
|
"text": "Amazon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "(itunes|music)\\.apple\\.com$",
|
||||||
|
"text": "Apple Music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "bandcamp\\.com$",
|
||||||
|
"text": "Bandcamp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "beatport\\.com$",
|
||||||
|
"text": "Beatport"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "deezer\\.com$",
|
||||||
|
"text": "Deezer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "qobuz\\.com$",
|
||||||
|
"text": "Qobuz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "soundcloud\\.com$",
|
||||||
|
"text": "SoundCloud"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "spotify\\.com$",
|
||||||
|
"text": "Spotify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "tidal\\.com$",
|
||||||
|
"text": "Tidal"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Because the MusicBrainz API doesn't return a name or label for a link, like
|
||||||
|
// for example {"name": "Bandcamp", "url": "https://bandcamp.com/..."}, we have
|
||||||
|
// to figure out a name for each link. So all the known links are simply saved
|
||||||
|
// in a JSON file where we have a regular expression to test for and a
|
||||||
|
// replacement name to use instead. And whenever a link isn't matched to any we
|
||||||
|
// can just use the host name of the URL like "bandcamp.com".
|
||||||
|
|
||||||
|
import knownLinks from './known-links.json';
|
||||||
|
|
||||||
|
type KnownLink = {
|
||||||
|
regex: RegExp;
|
||||||
|
text: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const known: KnownLink[] = knownLinks.map((data: Record<string, string>) => ({
|
||||||
|
regex: new RegExp(data.regex),
|
||||||
|
text: data.text,
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default class RelationLink {
|
||||||
|
public readonly link: URL;
|
||||||
|
public readonly original: string;
|
||||||
|
public readonly text: string;
|
||||||
|
|
||||||
|
constructor(relationUrl: string) {
|
||||||
|
this.original = relationUrl;
|
||||||
|
this.link = new URL(relationUrl);
|
||||||
|
|
||||||
|
const knownLink = known.find(({regex}) => regex.test(this.link.host));
|
||||||
|
this.text = knownLink?.text ?? this.link.host;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
import RelationLink from './relation-link.js';
|
||||||
|
|
||||||
|
type ApiReleaseData = {
|
||||||
|
'artist-credit': Array<{
|
||||||
|
name: string;
|
||||||
|
joinphrase: string;
|
||||||
|
}>;
|
||||||
|
'cover-art-archive': {
|
||||||
|
front: boolean;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
relations: Array<{
|
||||||
|
url: {
|
||||||
|
resource: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
interface IRelease {
|
||||||
|
artist: string;
|
||||||
|
image: string | undefined;
|
||||||
|
links: RelationLink[];
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default interface Release extends IRelease {}
|
||||||
|
|
||||||
|
export default class Release {
|
||||||
|
public static async fromMbid(mbid: string): Promise<Release> {
|
||||||
|
const apiResponse = await window.fetch(this.apiUrl(mbid), {
|
||||||
|
headers: {
|
||||||
|
accept: 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
throw new Error(`No release found with MBID ${mbid}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await apiResponse.json()) as ApiReleaseData;
|
||||||
|
|
||||||
|
const artist = data['artist-credit']
|
||||||
|
.map(({name, joinphrase}) => `${name}${joinphrase}`)
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
const image = data['cover-art-archive'].front
|
||||||
|
? `https://coverartarchive.org/release/${mbid}/front-500`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const links = data.relations
|
||||||
|
.map(({url}) => new RelationLink(url.resource))
|
||||||
|
.sort((a, b) => a.text.localeCompare(b.text));
|
||||||
|
|
||||||
|
return new Release({
|
||||||
|
artist,
|
||||||
|
image,
|
||||||
|
links,
|
||||||
|
title: data.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static apiUrl(mbid: string): string {
|
||||||
|
const root = 'https://musicbrainz.org/ws/2';
|
||||||
|
return `${root}/release/${mbid}?inc=artists+url-rels`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(release: IRelease) {
|
||||||
|
Object.assign(this, release, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public display(): string {
|
||||||
|
return `${this.artist} - ${this.title}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"module": "es2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmit": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es2020"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"source/**/*.ts",
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import childProcess from 'node:child_process';
|
||||||
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import url from 'node:url';
|
||||||
|
import {defineConfig} from 'vite';
|
||||||
|
|
||||||
|
const currentDir = path.dirname(url.fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
const buildDir = path.join(currentDir, 'public');
|
||||||
|
const sourceDir = path.join(currentDir, 'source');
|
||||||
|
|
||||||
|
function gitRevParse(): string {
|
||||||
|
const revParse = childProcess.spawnSync(
|
||||||
|
'git',
|
||||||
|
['rev-parse', '--short', '--verify', 'main'],
|
||||||
|
{encoding: 'utf-8'},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (revParse.error) {
|
||||||
|
throw revParse.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(revParse.stdout.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: buildDir,
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
blinkVersion: JSON.stringify(process.env.npm_package_version),
|
||||||
|
blinkCommitHash: gitRevParse(),
|
||||||
|
},
|
||||||
|
publicDir: path.join(sourceDir, 'assets'),
|
||||||
|
root: sourceDir,
|
||||||
|
});
|
Loading…
Reference in New Issue