Skip to content

Commit

Permalink
chore: add linters and prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
sapachev committed Jan 24, 2023
1 parent c5effac commit b7c27cf
Show file tree
Hide file tree
Showing 28 changed files with 7,417 additions and 611 deletions.
17 changes: 17 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = {
env: {
node: true,
es2021: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
overrides: [],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
plugins: ["@typescript-eslint"],
rules: {
"no-empty": ["error", { allowEmptyCatch: true }],
},
};
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit ${1}
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx pretty-quick --staged
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
*.md
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Nginx config located in `/dist/<locale>/nginx-error-pages.conf` file and can be
# How to improve default pages

* *Extensibility* A new pages can be added by adding new json-files in `scr/<locale>` directory. The page name must follow to format `<HTTP-code>.json` (`<HTTP-code>` is Number, related to specific HTTP status code). You can put any additional data to json-files, that you want to display on a page. In case of common variables, you can use `common.json` file to define them.
* *Customization* By editing default theme you can add anything you want. In case if you want to have own page design, you can create a new theme and apply it by editing `config.json` file. All assets (images, fonts, etc) must be placed to `@assets` directory (note: the `@assets` name is used to avoid a naming collision with default assets directory name in common cases). By default [mustache.js](https://www.npmjs.com/package/mustache) is used as a template engine and [Tailwind](https://tailwindcss.com/) as a CSS framework. Entry point of Tailwind styles must be located in `themes/<name>/@assets/css/main.tcss` file. Custom Tailwind theme settings can be added to `theme.tailwind.config.js` file located in a root of theme directory. Also Tailwind can be disabled by editing `tailwind` option in `config.js`.
* *Customization* By editing default theme you can add anything you want. In case if you want to have own page design, you can create a new theme and apply it by editing `config.json` file. All assets (images, fonts, etc) must be placed to `@assets` directory (note: the `@assets` name is used to avoid a naming collision with default assets directory name in common cases). By default the [mustache.js](https://www.npmjs.com/package/mustache) is used as a template engine and [Tailwind](https://tailwindcss.com/) as a CSS framework. Entry point of Tailwind styles must be located in `themes/<name>/@assets/css/main.tcss` file. Custom Tailwind theme settings can be added to `theme.tailwind.config.js` file located in a root of theme directory. Also Tailwind can be disabled by editing `tailwind` option in `config.js`.
* *Localization* If you need to change default text messages, then you can simply edit existing files in`src/<locale>` directory according to your needs. If you want to create your own localization, just simply add new locale directory and create set of source files. After new locale adding, change `locale` property in `config.json` file, located in a root.


Expand Down
3 changes: 3 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["@commitlint/config-conventional"],
};
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"tailwind": true,
"theme": "minimalistic",
"locale": "en"
}
}
260 changes: 107 additions & 153 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,194 +1,148 @@
import { promisify } from 'util';
import { exec } from 'child_process';
import { access, cp, readFile, readdir, rm, mkdir, writeFile } from 'fs/promises';
import { render } from 'mustache';
import { promisify } from "util";
import { exec } from "child_process";
import { access, cp, readFile, readdir, rm, mkdir, writeFile } from "fs/promises";
import { render } from "mustache";

import { DEFAULTS, SRC_CODE_PATTERN, TAILWIND_STYLE } from './lib/constants';
import { DEFAULTS, SRC_CODE_PATTERN, TAILWIND_STYLE } from "./lib/constants";
import { Config, SatusCode, SnippetVariables, TemplateVariables } from "./lib/interfaces";

async function compile(config: Config) {
try {
let vars: TemplateVariables = {
locale: config.locale,
};

const template = await readFile(`${DEFAULTS.THEMES}/${config.theme}/template.html`).then(String);

const codesVars = new Map<SatusCode, TemplateVariables>();
await readdir(`${DEFAULTS.SRC}/${config.locale}/`)
.then(files => {
return Promise.all(
files.map(async (file) => {
const srcVars: TemplateVariables = await readJson(`${DEFAULTS.SRC}/${config.locale}/${file}`);
const match = file.match(SRC_CODE_PATTERN);
if (match) {
codesVars.set(Number(match[0]), srcVars);
}
else {
vars = {...vars, ...srcVars};
}
})
)
})

if (codesVars.size > 0) {
await Promise.all(
Array.from(codesVars).map(
([code, codeVars]) => renderPage(template, {...vars, ...codeVars, code})
)
).then(() => {
console.log(`INFO: ${codesVars.size} pages were successfully created`);
});

await readdir(`${DEFAULTS.SNIPPETS}/`)
.then(files => {
return Promise.all(
files.map(async (file) => {
const snippet = await readFile(`${DEFAULTS.SNIPPETS}/${file}`).then(String);
return renderSnippet(file, snippet, { locale: config.locale, codes: Array.from(codesVars.keys()) })
})
)
let vars: TemplateVariables = {
locale: config.locale,
};

const template = await readFile(`${DEFAULTS.THEMES}/${config.theme}/template.html`).then(String);

const codesVars = new Map<SatusCode, TemplateVariables>();
await readdir(`${DEFAULTS.SRC}/${config.locale}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const srcVars: TemplateVariables = await readJson(`${DEFAULTS.SRC}/${config.locale}/${file}`);
const match = file.match(SRC_CODE_PATTERN);
if (match) {
codesVars.set(Number(match[0]), srcVars);
} else {
vars = { ...vars, ...srcVars };
}
})
}
else {
throw new Error('No source data to render error pages');
}
} catch (err) {
throw err;
);
});

if (codesVars.size > 0) {
await Promise.all(Array.from(codesVars).map(([code, codeVars]) => renderPage(template, { ...vars, ...codeVars, code }))).then(() => {
console.log(`INFO: ${codesVars.size} pages were successfully created`);
});

await readdir(`${DEFAULTS.SNIPPETS}/`).then((files) => {
return Promise.all(
files.map(async (file) => {
const snippet = await readFile(`${DEFAULTS.SNIPPETS}/${file}`).then(String);
return renderSnippet(file, snippet, { locale: config.locale, codes: Array.from(codesVars.keys()) });
})
);
});
} else {
throw new Error("No source data to render error pages");
}
}

async function execTailwind(config: Config) {
try {
if (config.tailwind) {
const input = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY}`;
const output = `${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY.replace('.tcss', '.css')}`;
const cmd = `INPUT="${input}" OUTPUT="${output}" npm run build:tailwind`;

console.log(`INFO: build Tailwind CSS styles`);
console.log(`INFO: run '${cmd}'`);

await promisify(exec)(cmd).then(() => {
console.log(`INFO: Tailwind CSS styles were built`);
});
}
else {
console.log(`WARN: Tailwind CSS was disabled in config`);
}
} catch (err) {
throw err;
if (config.tailwind) {
const input = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY}`;
const output = `${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}/css/${DEFAULTS.TAILWIND_ENTRY.replace(".tcss", ".css")}`;
const cmd = `INPUT="${input}" OUTPUT="${output}" npm run build:tailwind`;

console.log(`INFO: build Tailwind CSS styles`);
console.log(`INFO: run '${cmd}'`);

await promisify(exec)(cmd).then(() => {
console.log(`INFO: Tailwind CSS styles were built`);
});
} else {
console.log(`WARN: Tailwind CSS was disabled in config`);
}
}

async function readJson(path: string) {
try {
return await readFile(path).then(String).then(JSON.parse);
} catch (err) {
throw err;
}
return await readFile(path).then(String).then(JSON.parse);
}

async function renderPage(template: string, vars: TemplateVariables) {
try {
if (!vars.code) {
throw new Error('No code variable to render page');
} else if (!vars.locale) {
throw new Error('No locale variable to render page');
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${vars.code}.html`;

console.log(`INFO: render '${path}' page`);
await writeFile(path, render(template, vars), { flag: 'w+' });
} catch (err) {
throw err;
if (!vars.code) {
throw new Error("No code variable to render page");
} else if (!vars.locale) {
throw new Error("No locale variable to render page");
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${vars.code}.html`;

console.log(`INFO: render '${path}' page`);
await writeFile(path, render(template, vars), { flag: "w+" });
}

async function renderSnippet(name: string, template: string, vars: SnippetVariables) {
try {
if (!vars.codes) {
throw new Error('No codes list to render config snippet');
} else if (!vars.locale) {
throw new Error('No locale variable to render config snippet');
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${name}`;

console.log(`INFO: render '${path}' config snippet`);
await writeFile(path, render(template, vars), { flag: 'w+' });
console.log(`INFO: config snippet '${name}' was successfully created`);
} catch (err) {
throw err;
if (!vars.codes) {
throw new Error("No codes list to render config snippet");
} else if (!vars.locale) {
throw new Error("No locale variable to render config snippet");
}

const path = `${DEFAULTS.DIST}/${vars.locale}/${name}`;

console.log(`INFO: render '${path}' config snippet`);
await writeFile(path, render(template, vars), { flag: "w+" });
console.log(`INFO: config snippet '${name}' was successfully created`);
}

async function readConfig(): Promise<Config> {
try {
const config = await readJson(DEFAULTS.CONFIG);

if (!config.theme) {
throw new Error(`Please set theme in your configuration: ${DEFAULTS.CONFIG}`);
} else if (!config.locale) {
throw new Error(`Please set locale in your configuration: ${DEFAULTS.CONFIG}`);
}

return config;
} catch (err) {
throw err;
const config = await readJson(DEFAULTS.CONFIG);

if (!config.theme) {
throw new Error(`Please set theme in your configuration: ${DEFAULTS.CONFIG}`);
} else if (!config.locale) {
throw new Error(`Please set locale in your configuration: ${DEFAULTS.CONFIG}`);
}

return config;
}

async function prepare(config: Config) {
try {
console.log(`INFO: prepare build directory '${DEFAULTS.DIST}'`);
console.log(`INFO: prepare build directory '${DEFAULTS.DIST}'`);

await rm(DEFAULTS.DIST, { force: true, recursive: true });
await mkdir(`${DEFAULTS.DIST}/${config.locale}/`, { recursive: true });
} catch (err) {
throw err;
}
await rm(DEFAULTS.DIST, { force: true, recursive: true });
await mkdir(`${DEFAULTS.DIST}/${config.locale}/`, { recursive: true });
}

async function copyAssets(config: Config) {
console.log(`INFO: copying assets to build directory '${DEFAULTS.DIST}'`);

const path = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}`;

let exists = false;
try {
console.log(`INFO: copying assets to build directory '${DEFAULTS.DIST}'`);

const path = `${DEFAULTS.THEMES}/${config.theme}/${DEFAULTS.ASSETS}`;

let exists = false;
try {
await access(path);
exists = true;
} catch (_) {}

if (exists) {
await cp(
path,
`${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}`,
{
recursive: true,
// Skip Tailwind styles to copy
filter: (src) => !TAILWIND_STYLE.test(src)
}
);
}
else {
console.log(`INFO: no assets in '${config.theme}' theme directory`);
}
} catch (err) {
throw err;
await access(path);
exists = true;
} catch (_) {}

if (exists) {
await cp(path, `${DEFAULTS.DIST}/${config.locale}/${DEFAULTS.ASSETS}`, {
recursive: true,
// Skip Tailwind styles to copy
filter: (src) => !TAILWIND_STYLE.test(src),
});
} else {
console.log(`INFO: no assets in '${config.theme}' theme directory`);
}
}

readConfig()
.then(async (config) => {
await prepare(config);
await compile(config);
await execTailwind(config);
await copyAssets(config);
await prepare(config);
await compile(config);
await execTailwind(config);
await copyAssets(config);
})
.catch(err => {
.catch((err) => {
console.error(`
An error happened during compile process. Please, check 'README.md' to get more details about calling this process.
Expand All @@ -198,4 +152,4 @@ ${err.message}
Error Stack:
${err.stack}
`);
});
});
16 changes: 8 additions & 8 deletions lib/_constants.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
exports.DefaultPaths = {
ASSETS: '@assets',
CONFIG: './config.json',
SRC: './src',
DIST: './dist',
SNIPPETS: './snippets',
THEMES: './themes',
TAILWIND_ENTRY: 'main.tcss'
}
ASSETS: "@assets",
CONFIG: "./config.json",
SRC: "./src",
DIST: "./dist",
SNIPPETS: "./snippets",
THEMES: "./themes",
TAILWIND_ENTRY: "main.tcss",
};
8 changes: 4 additions & 4 deletions lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Defaults } from './interfaces.js';
import { DefaultPaths } from './_constants.js';
import { Defaults } from "./interfaces.js";
import { DefaultPaths } from "./_constants.js";

export const DEFAULTS: Defaults = {
ASSETS: DefaultPaths.ASSETS,
Expand All @@ -9,7 +9,7 @@ export const DEFAULTS: Defaults = {
SNIPPETS: DefaultPaths.SNIPPETS,
THEMES: DefaultPaths.THEMES,
TAILWIND_ENTRY: DefaultPaths.TAILWIND_ENTRY,
}
};

export const SRC_CODE_PATTERN = /^[0-9]{3}(?=\.json$)/i;
export const TAILWIND_STYLE = /\.tcss$/i;
export const TAILWIND_STYLE = /\.tcss$/i;
Loading

0 comments on commit b7c27cf

Please sign in to comment.