Skip to main content

BNDY-NET

· 143 min read

Open in Notion

I'm code Porter, but I produce codes.

💯 Frequently Used


Frontend

Libraries

AmCharts

embed

embed

ECharts

embed

Step by Step to Guide (driver.js)

bookmark

import Driver from 'driver.js';
import 'driver.js/dist/driver.min.css';

const driver = new Driver({
className: 'scoped-class',
animate: true,
opacity: 0.75,
padding: 10,
allowClose: true,
overlayClickNext: false,
doneBtnText: 'Done',
closeBtnText: 'Close',
stageBackground: '#ffffff',
nextBtnText: 'Next',
prevBtnText: 'Previous',
showButtons: false,
keyboardControl: true,
scrollIntoViewOptions: {},
onHighlightStarted: (Element) => {},
onHighlighted: (Element) => {},
onDeselected: (Element) => {},
onReset: (Element) => {},
onNext: (Element) => {},
onPrevious: (Element) => {},
});

driver.defineSteps([
{
element: '#first-element-introduction',
popover: {
className: 'first-step-popover-class',
title: 'Title on Popover',
description: 'Body of the popover',
position: 'left'
}
},
{
element: '#second-element-introduction',
popover: {
title: 'Title on Popover',
description: 'Body of the popover',
position: 'top'
}
},
{
element: '#third-element-introduction',
popover: {
title: 'Title on Popover',
description: 'Body of the popover',
position: 'right'
}
},
]);

driver.start();

Just highlight some elements like below.

const driver = new Driver();
driver.highlight({
element: '#some-element',
popover: {
title: 'Title for the Popover',
description: 'Description for it',
position: 'left',
offset: 20,
}
});

React

Package your React Component for distribution via NPM

1. MAKE A PACKAGE NPM PUBLISHABLE

npm init

In the package.json, make sure these fields are populated: package.json

{
"name": "myUnflappableComponent",
"version": "0.0.29",
"main": "dist/index.js",
"publishConfig": {
"access": "restricted"
},
...
}

2. DON’T BUNDLE REACT. USE THE PARENT’S REACT AND REACT-DOM.

In package.json, add React and react-dom in the project’s peerDependencies (And remove it from dependencies, but add it to devDependencies for development)

...
"peerDependencies": {
"react": ">=15.0.1",
"react-dom": ">=15.0.1"
},
"devDependencies": {
"react": ">=15.0.1",
"react-dom": ">=15.0.1"
},
...

In your webpack configuration, create a UMD bundle

...
module.exports = {
...
output: {
path: path.join(__dirname, './dist'),
filename: 'myUnflappableComponent.js',
library: libraryName,
libraryTarget: 'umd',
publicPath: '/dist/',
umdNamedDefine: true
},
plugins: {...},
module: {...},
resolve: {...},
externals: {...}
}

And super-duper important, don’t bundle React

module.exports = {
output: {...},
plugins: {...},
module: {...},
resolve: {
alias: {
'react': path.resolve(__dirname, './node_modules/react'),
'react-dom': path.resolve(__dirname, './node_modules/react-dom'),
}
},
externals: {
// Don't bundle react or react-dom
react: {
commonjs: "react",
commonjs2: "react",
amd: "React",
root: "React"
},
"react-dom": {
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "ReactDOM",
root: "ReactDOM"
}
}
}

3. SET UP YOUR .NPMIGNORE FILE

If you don’t set up a .npmignore file, npm uses your .gitignore file and bad things will happen. An empty .npmignore file is allowed. This is what mine looks like:

webpack.local.config.js

webpack.production.config.js .eslintrc .gitignore

React and JSX Style Guide

https://github.com/airbnb/javascript/tree/master/react

Web Components in React

Events of Web Components in React

An improvement would be to extract the event listener callback function in order to remove the listener when the component unmounts.

import React from 'react';

import 'road-dropdown';

const Dropdown = ({ label, option, options, onChange }) => {
const ref = React.useRef();

React.useLayoutEffect(() => {
const handleChange = customEvent => onChange(customEvent.detail);

const { current } = ref;

current.addEventListener('onChange', handleChange);

// returning the callback in order to remove the listener when the component unmounts.
return () => current.removeEventListener('onChange', handleChange);
}, [ref]);

return (
<road-dropdown
ref={ref}
label={label}
option={option}
options={JSON.stringify(options)}
/>
);
};
That's it for adding an event listener for

React to Web Components

Below works with the useCustomElement React Hook which can be installed via npm install use-custom-element:

import React from 'react';

import 'road-dropdown';

import useCustomElement from 'use-custom-element';

const Dropdown = props => {
const [customElementProps, ref] = useCustomElement(props);

return <road-dropdown {...customElementProps} ref={ref} />;
};

Angular

CLI

ng generate component|directive|pipe|service|class|guard|interface|enum|module --module=app
ng g directive ./src/app/directives/your-directive --module=shared


ng test --include path/to/your-spec-file
ng test --include src/**/*.spec.ts
ng test your-module --code-coverage

Install Error

internal/modules/cjs/loader.js:638
throw err;
^

Error: Cannot find module '/usr/lib/node_modules/@angular/cli/bin/postinstall/script.js'
sudo npm install -g @angular/cli --unsafe-perm=true --allow-root

Cache for HttpClient

@Injectable()
class CacheInterceptor implements HttpInterceptor {
private cache: Map<HttpRequest, HttpResponse> = new Map()
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
if(req.method !== "GET") {
return next.handle(req)
}
if(req.headers.get("reset")) {
this.cache.delete(req)
}
const cachedResponse: HttpResponse = this.cache.get(req)
if(cachedResponse) {
return of(cachedResponse.clone())
}else {
return next.handle(req).pipe(
do(stateEvent => {
if(stateEvent instanceof HttpResponse) {
this.cache.set(req, stateEvent.clone())
}
})
).share()
}
}
}

According to above code, if you do not want to get the data from cache. you just pass a head parameter as below:

public fetchDogs(reset: boolean = false) {
return this.httpClient.get("api/dogs", new HttpHeaders({reset}))
}

And lastly, you must add the interceptor to module.

@NgModule({
...
providers: {
provide: HTTP_INTERCEPTORS,
useClass: CacheInterceptor,
multi: true
}
})
...

Events of Component

Outside Click

import { Directive, Input, Output, EventEmitter, ElementRef, HostListener } from '@angular/core';

@Directive({
selector: '[clickOutside]',
})
export class ClickOutsideDirective {

@Output() clickOutside = new EventEmitter<void>();

constructor(private elementRef: ElementRef) { }

@HostListener('document:click', ['$event.target'])
public onClick(target) {
const clickedInside = this.elementRef.nativeElement.contains(target);
if (!clickedInside) {
this.clickOutside.emit();
}
}
}
<div (clickOutside)="someHandler()"></div>

Keydown of Component

@HostListener('document: keydown', ['$event'])
public onEnter(event: KeyboardEvent): void {
if (this.elementRef.nativeElement.contains(event.target)) {
if (event.code === 'Enter') {
// TODO:
}
}
}

‼️Tips

If the element contains *ngIf, this.overlay.nativeElement.contains(event.target); always return false. Because of the angular directive *ngIf

<div #overlay>
<div *ngIf="show" class="item">
Click here
</div>
</div>
@ViewChild('overlay', { static: false }) overlay: ElementRef;

//...
this.overlay.nativeElement.contains(event.target); // always return false

How to solve it?

You can check the event.target

Object.keys(event.target.classList).includes('item');

Or use [hidden] instead.

<div [hidden]="!show" class="item">
Click here
</div>

Preloading

Lazy loading for modules

https://angular.io/guide/lazy-loading-ngmodules#preloading-modules

import { PreloadAllModules } from '@angular/router';
RouterModule.forRoot(
appRoutes,
{
preloadingStrategy: PreloadAllModules
}
)

Preloading component data

import { Resolve } from '@angular/router';

...

/* An interface that represents your data model */
export interface Crisis {
id: number;
name: string;
}

export class CrisisDetailResolverService implements Resolve {
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
// your logic goes here
}
}
import { CrisisDetailResolverService } from './crisis-detail-resolver.service';
{
path: '/your-path',
component: YourComponent,
resolve: {
crisis: CrisisDetailResolverService
}
}
import { ActivatedRoute } from '@angular/router';

@Component({ ... })
class YourComponent {
constructor(private route: ActivatedRoute) {}

ngOnInit() {
this.route.data
.subscribe(data => {
const crisis: Crisis = data.crisis;
// ...
});
}
}

Preloading Strategies

Available Preloading strategies

  • Build-in preloading strategies — NoPreloading (default) or PreloadAllModules.
  • Custom preloading strategies — Preload after some time, preload based on network quality, load required modules first, frequently used second, and others lazy load/last.

Preloading all the modules (PreloadAllModules)

Custom preloading strategies

app-routing.module.ts

import {NgModule} from '@angular/core';
import {Routes, RouterModule} from '@angular/router';
import {CustomPreloadingStrategyService} from './custom-preloading-strategy.service';
const routes: Routes = [
{path: 'about', data: {preload: true}, loadChildren: () => import('./about/about.module').then(m => m.AboutModule)},
{path: 'users', loadChildren: () => import('./users/users.module').then(m => m.UsersModule)},
{path: '', redirectTo: '', pathMatch: 'full'}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {preloadingStrategy: CustomPreloadingStrategyService})],
exports: [RouterModule]
})
export class AppRoutingModule {
}

custom-preloading-strategy.service.ts

import {Injectable} from '@angular/core';
import {PreloadingStrategy, Route} from '@angular/router';
import {Observable, of} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CustomPreloadingStrategyService implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
if (route.data && route.data.preload) {
return fn(); // Proceeds with preloading
}
return of(null); // Proceeds without preloading
}
}

Fix Circular Dependency issue

@Injectable({
providedIn: 'root',
})
export class AppService {

constructor(
private injector: Injector,
// public status: StatusService, // do not do it if this leads to circular dependency issue
) { }

// use below instead
getStatusSerice() {
return this.injector.get<StatusSerice>(StatusSerice);
}
}

AngularJS with TypeScript

Components

class HerosComponentController implements ng.IComponentController {
public static $inject = ['$log', '$scope', '$document', '$element'];
public title: string;
public heros: IHero[];
public onItemSelect: any;

constructor(
private $log: ng.ILogService,
private $scope: ng.IScope,
private $document: ng.IDocumentService,
private $element: ng.IRootElementService,
) { }

public $onInit () { }

public $onChanges(changes: angular.IOnChangesObject): void { }

public selectItem(item: IHero, event: any) {
if (this.onItemSelect && typeof this.onItemSelect === 'function') {
this.onItemSelect({
data: item,
});
}
}
}

class HerosComponent implements ng.IComponentOptions {

public controller: ng.Injectable<ng.IControllerConstructor>;
public controllerAs: string;
public template: string;
public bindings: any;

constructor() {
this.controller = HerosComponentController;
this.controllerAs = "$ctrl";
this.template = `
<ul>
<li>{{$ctrl.title}}</li>
<li ng-click="$ctrl.selectItem(hero, $event)" ng-repeat="hero in $ctrl.heros">{{ hero.name }}</li>
</ul>
`;
this.bindins = {
title: '@',
heros: '<',
onItemSelect: '&',
};
}
}

angular
.module("mySuperAwesomeApp", [])
.component("heros", new HerosComponent());

angular.element(document).ready(function() {
angular.bootstrap(document, ["mySuperAwesomeApp"]);
});
<heros title="Title" heros="$ctrl.heros" on-item-select="$ctrl.select(data)"></heros>

Unit Test

Http Mock

import {HttpTestingController} from '@angular/common/http/testing';

describe('DataService', () => {
let service: DataService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
providers: [DataService]
});
service = TestBed.get(DataService);
httpMock = TestBed.get(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});

it('be able to retrieve posts from the API bia GET', () => {
const dummyPosts: Post[] = [{
userId: '1',
id: 1,
body: 'Hello World',
title: 'testing Angular'
}, {
userId: '2',
id: 2,
body: 'Hello World2',
title: 'testing Angular2'
}];
service.getPost().subscribe(posts => {
expect(posts.length).toBe(2);
expect(posts).toEqual(dummyPosts);
});
const request = httpMock.expectOne( `${service.ROOT_URl}/posts`);
expect(request.request.method).toBe('GET');
request.flush(dummyPosts);
});
});

Injector in Console

// Get the injector
var injector = angular.element($0/*'[data-ng-app], [ng-app]'*/).injector();
// Get the service
var service = injector.get('Service');

NGRX

Effects_diagram.png

Variables in ng-container/ng-template

<div>
<ng-container *ngTemplateOutlet="viewTemplate; content: {$implicit: {name: 'Bing'}}"></ng-container>
</div>
@Component({
selector: 'sub',
})
export class SubComponent {
@Input() viewTemplate: TemplateRef<any>;
}

bookmark

How to use:

<sub [viewTemplate]="view"></sub>

<ng-template #view let-data>
Your name {{data.name}}
</ng-template>

bookmark

Global Error Handling

global-error-handler.ts

import { HttpErrorResponse } from '@angular/common/http';
import { ErrorHandler, Injectable, NgZone } from '@angular/core';

@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
constructor() {}

public handleError(error: any): void {
// Check if it's an error from an HTTP response
const isServerError = error instanceof HttpErrorResponse;

if (!isServerError) {
// TODO
}

console.error('Error from Global Error Handler', error);
}
}

app.module.ts

@NgModule({
declarations: [AppComponent],
imports: [
CommonModule,
HttpClientModule,
],
providers: [
{ provide: ErrorHandler, useClass: GlobalErrorHandler },
],
bootstrap: [AppComponent],
})
export class AppModule {}

Service Provided in a Lazy Loaded Module

https://angular.io/guide/ngmodule-faq#why-is-a-service-provided-in-a-lazy-loaded-module-visible-only-to-that-module

Unlike providers of the modules loaded at launch, providers of lazy-loaded modules are module-scoped.

When the Angular router lazy-loads a module, it creates a new execution context. That context has its own injector, which is a direct child of the application injector.

The router adds the lazy module's providers and the providers of its imported NgModules to this child injector.

These providers are insulated from changes to application providers with the same lookup token. When the router creates a component within the lazy-loaded context, Angular prefers service instances created from these providers to the service instances of the application root injector.

JavaScript

Use @semantic to release package to NPM and publish docs to gh-pages automatically

  1. Install requisite dependencies

    # run local scripts for executing ts file for deploying docs
    npm i -D @types/node
    npm i -D ts-node

    # semantic-release and plugin for attaching version number to package.json
    npm i -D semantic-release
    npm i -D @semantic-release/git

    # git hooks and validate commit messages
    npm i -D husky
    npm i -D @commitlint/cli
  2. Add content to package.json

    \{
    "scripts": \{
    "deploy-docs": "ts-node tools/gh-pages-publish.ts",
    "semantic-release": "semantic-release"
    \},
    "files": [
    "dist"
    ],
    // if package scoped like @bndynet/ui, by default private, so...
    "publishConfig": \{
    "access": "public"
    \},
    "husky": \{
    "hooks": \{
    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    \}
    \}
    \}
  3. New file .releaserc (Release Configurations)

    \{
    "branch": "master",
    "prepare": [
    "@semantic-release/npm",
    \{
    "path": "@semantic-release/git",
    "assets": [
    "package.json",
    "package-lock.json"
    ],
    "message": "chore(release): $\{nextRelease.version\} by CI\n\n$\{nextRelease.notes\}"
    \}
    ],
    "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/npm",
    "@semantic-release/github",
    "@semantic-release/git"
    ]
    \}
  4. New file .travis.yml (CI configurations for publishing docs and releasing package)

    language: node_js
    cache:
    directories:
    - ~/.npm
    notifications:
    email: false
    node_js:
    - '10'
    script:
    - npm run build
    after_success:
    - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run deploy-docs; fi
    - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run semantic-release; fi
    branches:
    except:
    - /^v\d+\.\d+\.\d+$/
  5. New file tools/gh-pages-publish.ts (Scripts to upload docs to gh-pages branch)

    import \{ cd, exec, echo, touch \} from "shelljs";
    import \{ readFileSync \} from "fs";
    import \{ parse \} from "url";

    let repoUrl
    let pkg = JSON.parse(readFileSync("package.json"))
    if (typeof pkg.repository === "object") \{
    if (!pkg.repository.hasOwnProperty("url")) \{
    throw new Error("URL does not exist in repository section")
    \}
    repoUrl = pkg.repository.url
    \} else \{
    repoUrl = pkg.repository
    \}

    let parsedUrl = parse(repoUrl)
    let repository = (parsedUrl.host || "") + (parsedUrl.path || "")
    let ghToken = process.env.GH_TOKEN

    echo("Deploying docs!!!")
    cd("docs")
    touch(".nojekyll")
    exec("git init")
    exec("git add .")
    exec('git config user.name "Bendy Zhang"')
    exec('git config user.email "zb@bndy.net"')
    exec('git commit -m "docs(docs): update gh-pages"')
    exec(
    `git push --force --quiet "https://${ghToken}@${repository}" master:gh-pages`
    )
    echo("Docs deployed!!")
  6. Add environment variables to CI (Travis CI)

    • GH_TOKEN or GITHUB_TOKEN: your token generated in GitHub and has repo scopes
    • NPM_TOKEN: your token generated in NPM

Download File via AJAX

$('#GetFile').on('click', function () {
$.ajax({
url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
method: 'GET',
xhrFields: {
responseType: 'blob'
},
success: function (response) {
var blob = response.data;
var a = document.createElement('a');
var fileUrl = window.URL.createObjectURL(blob);
var filename = 'myfile.pdf';
/* you can also use below to get filename from backend */
// var contentDisposition = response.headers('Content-Disposition');
// var matches = /filename=\\"(.+?)\\"/g.exec(contentDisposition);
// var filename = matches && matches.length > 1 ? matches\[1\] : '';
/* but need add backend code like below, otherwise you cannot get Content-Disposition header */
// + response.setHeader("Content-Disposition", "attachment; filename=\\"myfile.csv\\"");
// + response.setHeader("Content-type", "application/octet-stream;charset=utf-8");
// + response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
// work in IE
if (window.navigator && window.navigator.msSaveBlob) {
window.navigator.msSaveBlob(blob, filename);
return;
}
a.href = fileUrl;
a.download = filename;
a.dispatchEvent(new MouseEvent('click'));
setTimeout(function() {
a.remove();
window.URL.revokeObjectURL.bind(window.URL, fileUrl);
});
}
});
});

AMD、CMD、UMD、CommonJS

AMD(Asynchromous Module Definition)

Asynchronous Module Definition (AMD) has gained traction on the frontend, with RequireJS being the most popular implementation.

Here’s module foo with a single dependency on jquery:

// filename: foo.js
define(['jquery'], function ($) {
// methods
function myFunc(){};

// exposed public methods
return myFunc;
});

And a little more complicated example with multiple dependencies and multiple exposed methods:

// filename: foo.js
define(['jquery', 'underscore'], function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned

// exposed public methods
return {
b: b,
c: c
}
});

CMD(Common Module Definition)

Standard locates at https://github.com/seajs/seajs/issues/242. It keeps more compatibilities with CommonJS and Node.js Modules.

  • Published by Chinese people who is developing SeaJS.
  • It is like AMD.
define((require, exports, module) => {
module.exports = {
fun1: () => {
var $ = require('jquery');
return $('#test');
}
};
});

CommonJS

CommonJS is a style you may be familiar with if you’re written anything in Node (which uses a slight variant). It’s also been gaining traction on the frontend with Browserify.

Using the same format as before, here’s what our foo module looks like in CommonJS:

// filename: foo.js

// dependencies
var $ = require('jquery');

// methods
function myFunc(){};

// exposed public method (single)
module.exports = myFunc;

And our more complicate example, with multiple dependencies and multiple exposed methods:

// filename: foo.js
var $ = require('jquery');
var _ = require('underscore');

// methods
function a(){}; // private because it's omitted from module.exports (see below)
function b(){}; // public because it's defined in module.exports
function c(){}; // public because it's defined in module.exports

// exposed public methods
module.exports = {
b: b,
c: c
};

UMD(Universal Module Definition)

Since CommonJS and AMD styles have both been equally popular, it seems there’s yet no consensus. This has brought about the push for a “universal” pattern that supports both styles, which brings us to none other than the Universal Module Definition.

The pattern is admittedly ugly, but is both AMD and CommonJS compatible, as well as supporting the old-style “global” variable definition:

(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// methods
function myFunc(){};

// exposed public method
return myFunc;
}));

And keeping in the same pattern as the above examples, the more complicated case with multiple dependencies and multiple exposed methods:

(function (root, factory) {
if (typeof define === 'function' &amp;&amp; define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('jquery'), require('underscore'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// methods
function a(){}; // private because it's not returned (see below)
function b(){}; // public because it's returned
function c(){}; // public because it's returned

// exposed public methods
return {
b: b,
c: c
}
}));

Prototype in JavaScript

All JavaScript objects inherit properties and methods from a prototype.

  • Date objects inherit from Date.prototype
  • Array objects inherit from Array.prototype
  • Person objects inherit from Person.prototype

The Object.prototype is on the top of the prototype inheritance chain:

Date objects, Array objects, and Person objects inherit from Object.prototype

Example

function Person(first, last, age, eyecolor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
}

Person.prototype.nationality = "English";

Person.prototype.name = function() {
return this.firstName + " " + this.lastName;
};

var myFather = new Person("John", "Doe", 50, "blue");
var myMother = new Person("Sally", "Rally", 48, "green");

❗Please do not add a new property to an existing object constructor as below. That is a wrong way.

Person.nationality = "English";

Syntax Tips

?? and ||

a || b // equals: a ? a : b
a ?? b // equals: a != undefined && a != null ? a : b
!'' // output: true
0 ?? 'a' // output: 0
0 || 'a' // output: "a"
'' ?? 'a' // output: ""
'' || 'a' // output: "a"

call, apply, bind

These methods can change the this point.

function test(arg1, arg2) {};

test.call(null, a1, a2);
test.apply(null, [a1, a2]);

var t = test.bind(null);
t();

Marble testing for rxjs

Examples

'-' or '------': Equivalent to [NEVER](<https://rxjs.dev/api/index/const/NEVER>), or an observable that never emits or errors or completes.

|: Equivalent to [EMPTY](<https://rxjs.dev/api/index/const/EMPTY>), or an observable that never emits and completes immediately.

#: Equivalent to [throwError](<https://rxjs.dev/api/index/function/throwError>), or an observable that never emits and errors immediately.

'--a--': An observable that waits 2 "frames", emits value a on frame 2 and then never completes.

'--a--b--|': On frame 2 emit a, on frame 5 emit b, and on frame 8, complete.

'--a--b--#': On frame 2 emit a, on frame 5 emit b, and on frame 8, error.

'-a-^-b--|': In a hot observable, on frame -2 emit a, then on frame 2 emit b, and on frame 5, complete.

'--(abc)-|': on frame 2 emit ab, and c, then on frame 8, complete.

'-----(a|)': on frame 5 emit a and complete.

'a 9ms b 9s c|': on frame 0 emit a, on frame 10 emit b, on frame 9,011 emit c, then on frame 9,012 complete.

'--a 2.5m b': on frame 2 emit a, on frame 150,003 emit b and never complete.

import { TestScheduler } from 'rxjs/testing';
import { throttleTime } from 'rxjs';

const testScheduler = new TestScheduler((actual, expected) => {
// asserting the two objects are equal - required
// for TestScheduler assertions to work via your test framework
// e.g. using chai.
expect(actual).deep.equal(expected);
});

// This test runs synchronously.
it('generates the stream correctly', () => {
testScheduler.run((helpers) => {
const { cold, time, expectObservable, expectSubscriptions } = helpers;
const e1 = cold(' -a--b--c---|');
const e1subs = ' ^----------!';
const t = time(' ---| '); // t = 3
const expected = '-a-----c---|';

expectObservable(e1.pipe(throttleTime(t))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

testScheduler.run((helpers) => {
const { time, cold } = helpers;
const source = cold('---a--b--|');
const t = time(' --| ');
// --|
const expected = ' -----a--b|';
const result = source.pipe(delay(t));
expectObservable(result).toBe(expected);
});

testScheduler.run((helpers) => {
const { animate, cold } = helpers;
animate(' ---x---x---x---x');
const requests = cold('-r-------r------');
/* ... */
const expected = ' ---a-------b----';
});
});

Check Theme of Browser

Theme of the browser

const theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",

CSS, SCSS

Media Examples

/* Small (sm) */
@media (min-width: 640px) { /* ... */ }

/* Medium (md) */
@media (min-width: 768px) { /* ... */ }

/* Large (lg) */
@media (min-width: 1024px) { /* ... */ }

/* Extra Large (xl) */
@media (min-width: 1280px) { /* ... */ }

// SCSS
// A map of breakpoints.
$breakpoints: (
xs: 576px,
sm: 768px,
md: 992px,
lg: 1200px
);
@mixin respond-above($breakpoint) {
@if map-has-key($breakpoints, $breakpoint) {
$breakpoint-value: map-get($breakpoints, $breakpoint);
@media (min-width: $breakpoint-value) {
@content;
}
} @else {
@warn 'Invalid breakpoint: #{$breakpoint}.';
}
}

z-index

$zindex-dropdown: 1000 !default;
$zindex-sticky: 1020 !default;
$zindex-fixed: 1030 !default;
$zindex-modal-backdrop: 1040 !default;
$zindex-modal: 1050 !default;
$zindex-popover: 1060 !default;
$zindex-tooltip: 1070 !default;

Each Examples

$colorset: (
primary: #aaa,
accent: #bbb,
);
:root {
@each $key, $val in $colorset {
--#{$key}: #{$val};
}
}
button {
color: var(--#{$key})
}

Function Definitions

$colorKeys: 'primary', 'accent', 'warn';
@mixin each-colors {
@for $i from 1 through length($colorKeys) {
@content(nth($colorKeys, $i));
}
}
@include each-colors using ($colorKey) {
@if $color!= '' {
button.#{$colorKey} {
color: var(--#{$colorKey});
}
}
}

JS Operations

getComputedStyle(document.querySelector(":root")).getPropertyValue("--dark--primary");
document.querySelector('#id').classList.value.split(' ');

Web Components

HTMLSlotElement

The HTMLSlotElement interface of the Shadow DOM API enables access to the name and assigned nodes of an HTML <slot> element.

Life Cycle Callbacks

Special callback functions defined inside the custom element's class definition, which affect its behavior:

connectedcallback

Invoked when the custom element is first connected to the document's DOM.

disconnectedcallback

Invoked when the custom element is disconnected from the document's DOM.

adoptedcallback

Invoked when the custom element is moved to a new document.

attributechangedcallback

Invoked when one of the custom element's attributes is added, removed, or changed.

CSS pseudo-classes

:defined {}
:host {}
:host() {}
:host-context() {}

Access parent element

<wc-parent>
<wc-child>
<wc-subchild>
</wc-subchild>
</wc-child>
<wc-parent>
function findParent(startElement: Element | ShadowRoot, selector: string): Element | null {
let result: Element | null;
if (startElement instanceof HTMLElement) {
result = startElement.closest(selector);
if (result) {
return result;
}
}
const r = startElement.getRootNode();
if (r instanceof ShadowRoot) {
return findParent((r as ShadowRoot).host, selector);
}
return null;
}

// wc-subchild component
findParent(this.shadowRoot, 'wc-parent');

Testing

Install Xvfb on CentOS

Xvfb, or X virtual frame buffer is needed by selenium and chromedriver or gekodriver, so it can run via cron with your PC locked, or without your script taking focus from the user section.

Installing

yum install xorg-x11-server-Xvfb

Copy below to /etc/systemd/system/Xvfb.service

[Unit]
Description=X Virtual Frame Buffer Service
After=network.target

[Service]
ExecStart=/usr/bin/Xvfb :99 -screen 0 1024x768x24

[Install]
WantedBy=multi-user.target
chmod +x /etc/systemd/system/Xvfb.service
systemctl enable Xvfb.service
systemctl start Xvfb.service

Playwright - Automate Chromium, Firefox and WebKit with a single API

Playwright is a Node.js library to automate Chromium, Firefox and WebKit with a single API. Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast.

bookmark

Page screenshot

const playwright = require('playwright');

(async () => {
for (const browserType of ['chromium', 'firefox', 'webkit']) {
const browser = await playwright[browserType].launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('http://whatsmyuseragent.org/');
await page.screenshot({ path: `example-${browserType}.png` });
await browser.close();
}
})();

Mobile and geolocation

const { webkit, devices } = require('playwright');
const iPhone11 = devices['iPhone 11 Pro'];

(async () => {
const browser = await webkit.launch();
const context = await browser.newContext({
...iPhone11,
locale: 'en-US',
geolocation: { longitude: 12.492507, latitude: 41.889938 },
permissions: ['geolocation']
});
const page = await context.newPage();
await page.goto('https://maps.google.com');
await page.click('text="Your location"');
await page.waitForRequest(/.*preview\/pwa/);
await page.screenshot({ path: 'colosseum-iphone.png' });
await browser.close();
})();

Evaluate in browser context

const { firefox } = require('playwright');

(async () => {
const browser = await firefox.launch();
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://www.example.com/');
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
}
});
console.log(dimensions);

await browser.close();
})();

Intercept network requests

const { webkit } = require('playwright');

(async () => {
const browser = await webkit.launch();
const context = await browser.newContext();
const page = await context.newPage();

// Log and continue all network requests
page.route('**', route => {
console.log(route.request().url());
route.continue();
});

await page.goto('http://todomvc.com');
await browser.close();
})();

Jest - Issues

Configurations for DOM Support

Use document object and methods like document.querySelectorAll

_setup.ts

import "jsdom-global/register";

jest.config.js

module.exports = {
verbose: true,
transform: {
".(ts|tsx)": "<rootdir>/node_modules/ts-jest/preprocessor.js",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
},
testEnvironment: "node",
testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
setupTestFrameworkScriptFile: "./test/_setup.ts",
moduleFileExtensions: ["ts", "tsx", "js"],
coveragePathIgnorePatterns: ["/node_modules/", "/test/", "src/index.ts"],
coverageThreshold: {
global: {
branches: 0,
functions: 0,
lines: 0,
statements: 0,
},
},
collectCoverageFrom: ["src/**/*.{js,ts,tsx}"],
};

UT Example for Promise

// ut passed requires `done` called
it("should return a promise with callback and title", done => {
confirm("Promise confirm").then(() =&gt; {
done();
});
document.querySelectorAll<htmlelement>(".btn")[1].click();
});

// reject promise
it("test reject promise", async () => {
// do not use `Promise.reject`, because returns Promise immediately
const mockP = jest.fn(() => Promise.reject("err"));
await delay(1, mockP()).catch(() => {
// nothing
});
expect(mockP.mock.calls.length).toBe(1);
});

Jest - Async

Async Example

import * as user from '../user';

// The assertion for a promise must be returned.
it('works with promises', () => {
expect.assertions(1);
return user.getUserName(4).then(data => expect(data).toEqual('Mark'));
});

it('works with resolves', () => {
expect.assertions(1);
return expect(user.getUserName(5)).resolves.toEqual('Paul');
});

// async/await can be used.
it('works with async/await', async () => {
expect.assertions(1);
const data = await user.getUserName(4);
expect(data).toEqual('Mark');
});

// async/await can also be used with `.resolves`.
it('works with async/await and resolves', async () => {
expect.assertions(1);
await expect(user.getUserName(5)).resolves.toEqual('Paul');
});

Error handling

// Testing for async errors using Promise.catch.
it('tests error with promises', () => {
expect.assertions(1);
return user.getUserName(2).catch(e =>
expect(e).toEqual({
error: 'User with 2 not found.',
}),
);
});

// Or using async/await.
it('tests error with async/await', async () => {
expect.assertions(1);
try {
await user.getUserName(1);
} catch (e) {
expect(e).toEqual({
error: 'User with 1 not found.',
});
}
});

// Testing for async errors using `.rejects`.
it('tests error with rejects', () => {
expect.assertions(1);
return expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

// Or using async/await with `.rejects`.
it('tests error with async/await and rejects', async () => {
expect.assertions(1);
await expect(user.getUserName(3)).rejects.toEqual({
error: 'User with 3 not found.',
});
});

Jest - Timer Mocks

// timerGame.js
'use strict';

function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
// __tests__/timerGame-test.js
'use strict';

jest.useFakeTimers();

test('waits 1 second before ending the game', () => {
const timerGame = require('../timerGame');
timerGame();

expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);
});

While you can call jest.useFakeTimers() or jest.useRealTimers() from anywhere (top level, inside an it block, etc.), it is a global operation and will affect other tests within the same file. Additionally, you need to call jest.useFakeTimers() to reset internal counters before each test. If you plan to not use fake timers in all your tests, you will want to clean up manually, as otherwise the faked timers will leak across tests:

afterEach(() => {
jest.useRealTimers();
});

test('do something with fake timers', () => {
jest.useFakeTimers();
// ...
});

test('do something with real timers', () => {
// ...
});

Run All Timers

test('calls the callback after 1 second', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();

// Fast-forward until all timers have been executed
jest.runAllTimers();

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Run Pending Timers

// infiniteTimerGame.js
'use strict';

function infiniteTimerGame(callback) {
console.log('Ready....go!');

setTimeout(() => {
console.log("Time's up! 10 seconds before the next game starts...");
callback && callback();

// Schedule the next game in 10 seconds
setTimeout(() => {
infiniteTimerGame(callback);
}, 10000);
}, 1000);
}

module.exports = infiniteTimerGame;
// __tests__/infiniteTimerGame-test.js
'use strict';

jest.useFakeTimers();

describe('infiniteTimerGame', () => {
test('schedules a 10-second timer after 1 second', () => {
const infiniteTimerGame = require('../infiniteTimerGame');
const callback = jest.fn();

infiniteTimerGame(callback);

// At this point in time, there should have been a single call to
// setTimeout to schedule the end of the game in 1 second.
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000);

// Fast forward and exhaust only currently pending timers
// (but not any new timers that get created during that process)
jest.runOnlyPendingTimers();

// At this point, our 1-second timer should have fired it's callback
expect(callback).toBeCalled();

// And it should have created a new timer to start the game over in
// 10 seconds
expect(setTimeout).toHaveBeenCalledTimes(2);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 10000);
});
});

Advance Timers by Time

// timerGame.js
'use strict';

function timerGame(callback) {
console.log('Ready....go!');
setTimeout(() => {
console.log("Time's up -- stop!");
callback && callback();
}, 1000);
}

module.exports = timerGame;
it('calls the callback after 1 second via advanceTimersByTime', () => {
const timerGame = require('../timerGame');
const callback = jest.fn();

timerGame(callback);

// At this point in time, the callback should not have been called yet
expect(callback).not.toBeCalled();

// Fast-forward until all timers have been executed
jest.advanceTimersByTime(1000);

// Now our callback should have been called!
expect(callback).toBeCalled();
expect(callback).toHaveBeenCalledTimes(1);
});

Jest - For Angular

npm i --dev jest jest-preset-angular @types/jest

You need to add this entry to package.json

"jest": {
"preset": "jest-preset-angular",
"setupFilesAfterEnv": ["<rootDir>/src/setupJest.ts"]
}

You’re now ready to add this to your npm scripts:

"test": "jest",
"test:watch": "jest --watch",

Oh, one more thing. Forget about installing PhantomJS on your CI:

"test:ci": "jest --runInBand",

Backend

Databases

MySql

Docker

$ docker pull
$ docker run --name mysql-default -p 3306:3306 -e MYSQL_ROOT_HOST=% -e MYSQL_ROOT_PASSWORD=123456 -d mysql
$ docker exec -it mysql-default mysql -u root -p
ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';

Problem solving for remotely access

If you got the same problem like this while connect to MySQL server from another host (It depends on which version of MySQL you are using):

java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed

You should change your password of root user by using the native password hashing method to fix it:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '123456';

PostgreSQL

Installing

bookmark

# default
docker run --name my-postgres -e POSTGRES_PASSWORD=your-pass -d postgres

# advance
docker run -d --name my-postgres \
-v /my-dockers/my-postgres/my-postgres.conf:/etc/postgresql/postgresql.conf \
-v /my-dockers/my-postgres/data:/var/lib/postgresql/data \
-e PGDATA=/var/lib/postgresql/data/pgdata \
-e POSTGRES_PASSWORD=your-pass \
-e POSTGRES_USER=postgres \
-e POSTGRES_DB=my-db \
-c ssl=on \
-p 5432:5432 \
postgres -c 'config_file=/etc/postgresql/postgresql.conf'

via psql:

$ docker run -it --rm --network some-network postgres psql -h my-postgres -U postgres
psql (14.3)
Type "help" for help.

postgres=# SELECT 1;
?column?
----------
1
(1 row)

Generate SSL Certificates for PostgreSQL server

  1. Go to data folder

    cd /var/lib/postgresql/data/pgdata
  2. Generate a private key by entering a pass phrase:

    openssl genrsa -des3 -out server.key 2048
  3. Remove the pass phrase to automatically start up the server using the following command

    openssl rsa -in server.key -out server.key
  4. Run the following command to remove group and other’s permission from the private key file

    chmod og-rwx server.key
  5. Run the following command to create a self-signed certificate

    openssl req -new -key server.key -days 3650 -out server.crt -x509

    Note that: You will be asked to enter information that will be incorporated into your certificate request. For some fields, there will be a default value. If you enter ‘.’, the field will be left blank.

    Country Name (2 letter code) [XX]:IN
    State or Province Name (full name) []:.
    Locality Name (eg, city) [Default City]:CH
    Organization Name (eg, company) [Default Company Ltd]:francium tech Organizational Unit Name (eg, section) []:.
    Common Name (eg, your name or your server's hostname) []:.
    Email Address []:kumaresan@francium.tech
  6. For self-signed certificates, use the server certificate as the trusted root certificate:

    cp server.crt root.crt

Prepare PostgreSQL standalone for SSL authentication

  1. Edit the postgresql.conf file to activate SSL:

    cd /usr/local/var/postgres
    vi postgresql.conf
  2. Uncomment and change the following parameters:

    ssl = on
    ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
    ssl_prefer_server_ciphers = on
    ssl_cert_file = 'server.crt'
    ssl_key_file = 'server.key'
    ssl_ca_file = 'root.crt'
    ssl_crl_file = ''
  3. Add the following entry to the client machine in /var/lib/postgresql/data/pgdata/pg_hba.conf file:

    local all all trust
    hostssl all all 127.0.0.1/32 cert
    hostnossl all all 0.0.0.0/0 reject
    hostnossl all all ::/0 reject
  4. To verify if SSL is enabled on standalone Postgres, run the following command:

    psql 'host=\<hostname\> port=5432 dbname=\<name\> user=\<user\> sslmode=verify-full sslcert=/tmp/postgresql.crt sslkey=/tmp/postgresql.key sslrootcert=/tmp/root.crt'
  5. Once the database has restarted, login by specifying localhost to make sure the database is being connected over TCP/IP:

    psql -h localhost -U postgres

Java

Setup

Mirrors

Add mirror in conf/setting.xml file in Maven root folder:

<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>

Error Handling in Spring Boot

Custom Error

Disabling the Whitelabel Error Page

S1: In application.properties server.error.whitelabel.enabled=false

S2: Excluding the ErrorMvcAutoConfiguration bean

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration
#for Spring Boot 2.0
#spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration

Or by adding this annotation to the main class:

@EnableAutoConfiguration(exclude = {ErrorMvcAutoConfiguration.class})

Custom ErrorController

@Controller
public class MyErrorController implements ErrorController {
@RequestMapping("/error")
public String handleError() {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// get the errors
Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());

if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
} else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error-500";
}
}
return "error";
}

@Override
public String getErrorPath() {
return "/error";
}
}

在 Spring 中常见的全局异常处理,主要有三种:

(1)注解 ExceptionHandler

(2)继承 HandlerExceptionResolver 接口

(3)注解 ControllerAdvice

@Controller
@RequestMapping("c")
public class Controller1 {
// **/c/query -> response 400
@ResponseBody
@ResponseMapping(value = "/query", produces = "appliation/json;charset=UTF-8")
public String query(@RequestParam("id") Long id) {
return id;
}

// **/c/calc -> response 500
@ResponseBody
@ResponseMapping(value = "/calc", produces = "application/json;charset=UTF-8")
public String calc() {
int a = 2/0;
return "":
}
}

注解 ExceptionHandler

注解 ExceptionHandler 作用对象为方法,最简单的使用方法就是放在 controller 文件中,详细的注解定义不再介绍。如果项目中有多个 controller 文件,通常可以在 baseController 中实现 ExceptionHandler 的异常处理,而各个 contoller 继承 basecontroller 从而达到统一异常处理的目的。因为比较常见,简单代码如下:

@ExceptionHandler(Exception.class)
@ResponseBody
public String exception(Exception ex) {
return this.getClass().getSimpleName() + ": " + ex.getMessage();
}

优点:ExceptionHandler 简单易懂,并且对于异常处理没有限定方法格式; 缺点:由于 ExceptionHandler 仅作用于方法,对于多个 controller 的情况,仅为了一个方法,所有需要异常处理的 controller 都继承这个类,明明不相关的东西,强行给他们找个爹,不太好。

注解 ControllerAdvice

这里虽说是 ControllerAdvice 注解,其实是其与 ExceptionHandler 的组合使用。在上文中可以看到,单独使用 @ExceptionHandler 时,其必须在一个 Controller 中,然而当其与 ControllerAdvice 组合使用时就完全没有了这个限制。换句话说,二者的组合达到的全局的异常捕获处理。 这种方法将所有的异常处理整合到一处,去除了 Controller 中的继承关系,并且达到了全局捕获的效果,推荐使用此类方式; Controller 中单独 @ExceptionHandle 异常处理排在了首位,@ControllerAdvice 排在了第二位。

@ControllerAdvice
public class ExceptionHandlerAdvice {
@ExceptionHandler(Exception.class)
@ResponseBody
public String exceptionHandler(Exception ex) {
return "";
}
}

实现 HandlerExceptionResolver 接口

HandlerExceptionResolver 本身 SpringMVC 内部的接口,其内部只有 resolveException 一个方法,通过实现该接口我们可以达到全局异常处理的目的。

@Component
public class MyHandlerExcptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
PrintWriter writer = response.getWriter();
writer.write(ex.getMessage());
writer.flush();
writer.close();

return new ModelAndView();
}
}

可以看到 500 的异常处理已经生效了,但是 400 的异常处理却没有生效,并且根没有异常前的返回结果一样。这是怎么回事呢?不是说可以做到全局异常处理的么?

经过DefaultHandlerExceptionResolver异常处理源码分析,可以看到我们的自定义类 MyHandlerExceptionResolver 确实可以做到全局处理异常,只不过对于 query 请求的异常,中间被 DefaultHandlerExceptionResolver 插了一脚,所以就跳过了 MyHandlerExceptionResolver 类的处理,从而出现 400 的返回结果。

Enable https in Spring Boot

  1. Create Self Signed SSL Certificate

    keytool -genkeypair -alias filenameAlias -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore filename.p12 -validity 3650

    And use below code to view certificate details.

    keytool -list -keystore filename.p12
  2. Enabling HTTPS in Spring Boot application.propreties

    # The format used for the keystore. for JKS, set it as JKS
    server.ssl.key-store-type=PKCS12
    # The path to the keystore containing the certificate
    server.ssl.key-store=classpath:keystore/javadevjournal.p12
    # The password used to generate the certificate
    server.ssl.key-store-password=use the same password which we added during certificate creation
    # The alias mapped to the certificate
    server.ssl.key-alias=javadevjournal
    # Run Spring Boot on HTTPS only, default 443 like no https 80 port
    server.port=8443

Note that you need copy filename.p12 to src/main/resources/keystore folder

Build Spring Boot Starter Project

Two Java Files

@ConfigurationProperties(prefix = "spring.ftsi")
public class IndexServiceAutoConfigurationProperties {

}

@Configuration
@EnableConfigurationProperties(IndexServiceAutoConfigurationProperties.class)
@ConditionalOnClass(IndexService.class)
@ConditionalOnProperty(prefix = "spring.ftsi", name = "enabled", matchIfMissing = true)
public class IndexServiceAutoConfiguration {

@Autowired
private IndexServiceAutoConfigurationProperties properties;

@Bean
@ConditionalOnMissingBean(IndexService.class)
public IndexService indexService() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
IndexService service = new IndexService();
return service;
}
}

File: /src/main/resources/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.bndy.ftsi.starter.IndexServiceAutoConfiguration

Usage

application.yml

spring:
ftsi:
property1: ...
property2: ...

java

@SpringBootApplication
public class Application {

@Autowried
IndexService indexService;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

AOP – Aspect-oriented programming

It does so by adding additional behavior to existing code (an advice) without modifying the code itself, instead separately specifying which code is modified via a “pointcut” specification, such as “log all function calls when the function’s name begins with ‘set’”. This allows behaviors that are not central to the business logic (such as logging) to be added to a program without cluttering the code, core to the functionality. AOP forms a basis for aspect-oriented software development.

Define an Aspect by using @Aspect annotations – which is natively understood by Spring:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class AuditAspect {

private static Logger logger = LoggerFactory.getLogger(AuditAspect.class);

@Pointcut("execution(* net.bndy.service.*.*(..))")
public void serviceMethods(JoinPoint jp){ }

// Intercept all calls to methods annotated with @PerLog
// @Pointcut("execution(@net.bndy.annotations.PerfLog * *.*(..))")
public void performanceTargets(JoinPoint jp, PerLog perLog){ }
// Pass annotation to AOP advices
@Pointcut(value = "@annotation(perLog)", argNames = "perfLog")
public void performanceTargets(JoinPoint jp, PerLog perLog){ }

// Using annotations and parameters in AOP advices
@Before(value = "com.byteslounge.spring.aop.PointcutDefinition.serviceLayer() && args(account,..) && @annotation(auditable)")
public void audit(Account account, Auditable auditable) {
System.out.println("Audit account access: "
+ account.getAccountNumber() + ". Audit destination: "
+ auditable.value());
}

// If the first parameter is of the JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart type, you may leave out the name of the parameter from the value of the "argNames" attribute. For example, if you modify the preceding advice to receive the join point object, the "argNames" attribute need not include it:
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)", argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
AuditCode code = auditable.value();
// ... use code, bean, and jp
}

@Before("serviceMethods()")
public void beforeMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("before method:" + methodName);
}

@Around("serviceMethods()")
public Object aroundMethod(ProceedingJoinPoint jp) {
try {
long start = System.nanoTime();
// execute target method
Object result = jp.proceed();
long end = System.nanoTime();
logger.info(String.format("%s took %d ns", jp.getSignature(), (end - start)));
return result;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}

// Always run if method completed
@After("serviceMethods()")
public void afterMethod(JoinPoint jp) {
logger.info("after method");
}

// Run when the method returns normally.
@AfterReturning(value="execution(* net.bndy.service.*.*(..))",returning="result")
public void afterReturningMethod(JoinPoint jp, Object result){ }

// Run if an exception has benn thrown in method
@AfterThrowing(value="execution(* net.bndy.service.*.*(..))",throwing="e")
public void afterThorwingMethod(JoinPoint jp, NullPointerException e){ }
}

@Pointcut

// Any return type | package | class | method | any type and number of arguments
@Pointcut("execution(* net.bndy.service . *. *( .. ))")
@Pointcut("execution(* net.bndy.service.*.*(.. ))")

// examples
@Pointcut("within(*..*Test)") // support for sub packages is provided with ".."
@Pointcut("within(net.bndy.service..*Test)") // ends with Test inside the "net.bndy.service" package
@Pointcut("!within(net.bndy.service..*Test)" // expects that ends with Test inside the "net.bndy.service" package
@Pointcut("execution(void *..service.Service+.*(..))") // all methods in the Service class or a subtype of it

@Pointcut("!withincode(@org.junit.Test * *(..))") //Finds all statements that’s not inside a method marked with @Test

// other Pointcuts
//execution(void Point.setX(int))
//call(void Point.setX(int))
//handler(ArrayOutOfBoundsException)
//this(SomeType)
//target(SomeType)
//within(MyClass)
//cflow(call(void Test.main()))
//call(* *(..)) &amp;&amp; (within(Line) || within(Point))
//execution(!static * *(..))
//execution(public !static * *(..))

Why to use argNames

public class HelloApi {

public void aspectTest(String a,String b){
System.out.println("in aspectTest:" + "a:" + a + ",b:" + b);
}
}
@Pointcut(value="execution(* bean.HelloApi.aspectTest(..)) && args(a1,b2)",argNames="a1,b2")
public void pointcut1(String a1,String b2){}

@Before(value="pointcut1(a,b)",argNames="a,b")
public void beforecase1(String a,String b){
System.out.println("1 a:" + a +" b:" + b);
}

// NOTE: a and p locations
@Before(value="pointcut1(a,b)",argNames="b,a")
public void beforecase2(String a,String b){
System.out.println("2 a:" + a +" b:" + b);
}
HelloApi helloapi1 = beanFactory.getBean("helloapi1",HelloApi.class);
helloapi1.aspectTest("a", "b");

Output

1 a:a b:b
2 a:b b:a
in aspectTest:a:a,b:b

Publishing Repo to Maven Central

Create an account on oss.sonatype.org

Create an account and create an ISSUE about Repo. And waiting approved.

Generate and share a PGP signature

Installing GnuPG

gpg --version

Generating a Key Pair and Sharing Key

gpg --generate-key
gpg --list-keys --keyid-format short
gpg --list-secret-keys --keyid-format short
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys <pubkeyid>
// gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys <pubkeyid>

Delete Key or Sub Key

gpg2 –edit-key A6BAB25
gpg> 1
gpg> save
gpg> key 2
gpg> delkey

Local GPG

Add ~/.gradle/gradle.properties with following content:

signing.keyId=<pubkeyid>
signing.password=<passwordgpg>
// For new version GPG, secring.gpg does not be used.
// But you can still use `gpg --export-secret-keys > ~/.gnupg/secring.gpg` to generate
// On windows, you can use `C:/Users/<yourname>/.gnupg/secring.gpg` instead.
signing.secretKeyRingFile=/Users/<yourname>/.gnupg/secring.gpg

ossrhUsername=<name>
ossrhPassword=<password>

Project Configuration

build.gradle example:

group ‘net.bndy’
version ‘1.0’

apply plugin: ‘java’
apply plugin: ‘maven’
apply plugin: ‘signing’

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
compile group: ‘com.fasterxml.jackson.core’, name: ‘jackson-core’, version: ‘2.9.3’
compile group: ‘com.fasterxml.jackson.core’, name: ‘jackson-databind’, version: ‘2.9.3’
compile group: ‘commons-codec’, name: ‘commons-codec’, version: ‘1.11’
compile group: ‘javax.mail’, name: ‘javax.mail-api’, version: ‘1.6.0’
compile group: ‘org.apache.directory.studio’, name: ‘org.apache.commons.io’, version: ‘2.4’
compile group: ‘javax.servlet’, name: ‘servlet-api’, version: ‘2.5’
}

task javadocJar(type: Jar) {
classifier = ‘javadoc’
from javadoc
}

task sourcesJar(type: Jar) {
classifier = ‘sources’
from sourceSets.main.allSource
}

artifacts {
archives javadocJar, sourcesJar
}

signing {
sign configurations.archives
}

uploadArchives {
repositories {
mavenDeployer {
beforeDeployment {
MavenDeployment deployment -> signing.signPom(deployment)
}

repository(url: “https://oss.sonatype.org/service/local/staging/deploy/maven2/”) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}

snapshotRepository(url: “https://oss.sonatype.org/content/repositories/snapshots/”) {
authentication(userName: ossrhUsername, password: ossrhPassword)
}

pom.project {
name ‘Jlib’
packaging ‘jar’

// optionally artifactId can be defined here
description ‘A Java Library’
url ‘https://github.com/bndynet/Jlib’

scm {
url ‘https://github.com/bndynet/Jlib’
connection ‘scm:git:git://github.com/bndynet/Jlib.git’
developerConnection ‘scm:git:git@github.com:bndynet/Jlib.git’
}

licenses {
license {
name ‘The Apache License, Version 2.0’
url ‘http://www.apache.org/licenses/LICENSE-2.0.txt’
}
}

developers {
developer {
id ‘bndynet’
name ‘Bendy Zhang’
email ‘zb@bndy.net’
url ‘http://bndy.net’
}
}
}
}
}

Deployment

  1. gradle uploadArchives
  2. Go to https://oss.sonatype.org/#stagingRepositories to close your repo. If closed successfully, you can release it. Otherwise, drop it and regradle it again.

TimeZone in Spring Boot

@SpringBootApplication
public class Application {
@PostConstruct
public void init(){
// Setting Spring Boot SetTimeZone
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

In application.properties

spring.jackson.time-zone= # Time zone used when formatting dates. For instance, "America/Los_Angeles" or "GMT+10".

Java 8 Stream Tutorial

20210116091807985.jpg

List<String> myList =
Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);

// C1
// C2
Arrays.asList("a1", "a2", "a3")
.stream()
.findFirst()
.ifPresent(System.out::println); // a1

Stream.of("a1", "a2", "a3")
.findFirst()
.ifPresent(System.out::println); // a1

IntStream, LongStream, DoubleStream

IntStream.range(1, 4)
.forEach(System.out::println);
// 1
// 2
// 3

IntStream.range(1, 4)
.mapToObj(i -> "a" + i)
.forEach(System.out::println);
// a1
// a2
// a3

Processing Order

Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> {
System.out.println("filter: " + s);
return true;
})
.forEach(s -> System.out.println("forEach: " + s));

filter: d2
forEach: d2
filter: a2
forEach: a2
filter: b1
forEach: b1
filter: b3
forEach: b3
filter: c
forEach: c
Stream.of("d2", "a2", "b1", "b3", "c")
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.anyMatch(s -> {
System.out.println("anyMatch: " + s);
return s.startsWith("A");
});

// map: d2
// anyMatch: D2
// map: a2
// anyMatch: A2
Stream.of("d2", "a2", "b1", "b3", "c")
.sorted((s1, s2) -> {
System.out.printf("sort: %s; %s\n", s1, s2);
return s1.compareTo(s2);
})
.filter(s -> {
System.out.println("filter: " + s);
return s.startsWith("a");
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.forEach(s -> System.out.println("forEach: " + s));

parallelStream()

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();

summaryStatistics()

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);

IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Avg: " + stats.getAverage());

Monitor File Changes

public static void main(String[] args) throws Exception {

WatchService watchService = FileSystems.getDefault().newWatchService();

Path path = Paths.get("G:\\");

path.register(
watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY);

WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("Event:" + event.kind() + ", File:" + event.context());
}
key.reset();
}
}

WatchService added in Java7 for NIO solution.

Monitor Spring Boot Application

bookmark

Host Your Sentry

git clone https://github.com/getsentry/onpremise.git
cd onpremise
./install.sh

Once your Sentry is ready, you need to create your project with Spring Boot or Java platform in your Sentry.

Set up Spring Boot Application

Get Started

<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
sentry.dsn=https://key@host/id

Who send the exception?

You can use SentryUserProvider to build a Java Bean:

@Bean
public SentryUserProvider sentryUserProvider(){
return () -> {
// TODO: get your current user
User user = new User();
user.setId("userId");
user.setUsername("Bendy");
user.setEmail("zb@bndy.net");

return user;
};
}

Custom Tags

@Bean
public SentryOptions.BeforeSendCallback beforeSendCallback(){
return (event, hint) -> {
event.setTag("name","zhangsan");
return event;
};
}

Integration with Logback

<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-logback</artifactId>
<version>3.1.1</version>
</dependency>
@RestController
public class HelloController {
private static Logger logger = LoggerFactory.getLogger(HelloController.class);

@RequestMapping("/")
public void test(){
logger.error("Logback error!");
}
}

Spring Boot with Keycloak

bookmark

Dependencies

The Keycloak Spring Boot adapter capitalizes on Spring Boot’s auto-configuration, so all we need to do is add the Keycloak Spring Boot starter to our project.

Within the dependencies XML element, we need the following to run Keycloak with Spring Boot:

<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>

After the dependencies XML element, we need to specify dependencyManagement for Keycloak:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>11.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

The following embedded containers are supported now and don't require any extra dependencies if using Spring Boot Keycloak Starter:

  • Tomcat
  • Undertow
  • Jetty

Keycloak Configuration

Here's the basic, mandatory configuration:

keycloak.realm = <REALM_NAME>
keycloak.auth-server-url = http://127.0.0.1:8080/auth
keycloak.ssl-required = external
keycloak.resource = <CLIENT_ID>
keycloak.credentials.secret = <CLIENT_SECRET>
keycloak.use-resource-role-mappings = true
keycloak.bearer-only = true

You can disable the Keycloak Spring Boot Adapter (for example in tests) by setting keycloak.enabled = false.

To configure a Policy Enforcer, unlike keycloak.json, policy-enforcer-config must be used instead of just policy-enforcer.

You also need to specify the Java EE security config that would normally go in the web.xml. The Spring Boot Adapter will set the login-method to KEYCLOAK and configure the security-constraints at startup time. Here’s an example configuration:

keycloak.securityConstraints[0].authRoles[0] = admin
keycloak.securityConstraints[0].authRoles[1] = user
keycloak.securityConstraints[0].securityCollections[0].name = insecure stuff
keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /insecure

keycloak.securityConstraints[1].authRoles[0] = admin
keycloak.securityConstraints[1].securityCollections[0].name = admin stuff
keycloak.securityConstraints[1].securityCollections[0].patterns[0] = /admin

If you plan to deploy your Spring Application as a WAR then you should not use the Spring Boot Adapter and use the dedicated adapter for the application server or servlet container you are using. Your Spring Boot should also contain a web.xml file.

KeycloakSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.anyRequest()
.permitAll();
http.csrf().disable();
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}

@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}

@Bean
public KeycloakConfigResolver KeycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}

configureGlobal: Registers the KeycloakAuthenticationProvider with the authentication manager.

sessionAuthenticationStrategy: Defines the session authentication strategy.

KeycloakConfigResolver : By Default, the Spring Security Adapter looks for a keycloak.json configuration file. You can make sure it looks at the configuration provided by the Spring Boot Adapter by adding this bean

@EnableGlobalMethodSecurity: The jsr250Enabled property allows us to use the @RoleAllowed annotation. We’ll explore more about this annotation in the next section.

TestController.java

@RestController
@RequestMapping("/test")
public class TestController {

@RequestMapping(value = "/anonymous", method = RequestMethod.GET)
public ResponseEntity<String> getAnonymous() {
return ResponseEntity.ok("Hello Anonymous");
}

@RequestMapping(value = "/user", method = RequestMethod.GET)
public ResponseEntity<String> getUser() {
return ResponseEntity.ok("Hello User");
}

@RequestMapping(value = "/admin", method = RequestMethod.GET)
public ResponseEntity<String> getAdmin() {
return ResponseEntity.ok("Hello Admin");
}

@RequestMapping(value = "/all-user", method = RequestMethod.GET)
public ResponseEntity<String> getAllUser() {
return ResponseEntity.ok("Hello All User");
}
}

Run the Spring Boot Application. Make sure Maven is installed and configured.

mvn spring-boot:run

As defined in the TestController, let’s invoke the REST APIs with CURL one by one.

curl -X GET 'http://localhost:8000/test/anonymous'
curl -X GET 'http://localhost:8000/test/user'
curl -X GET 'http://localhost:8000/test/admin'
curl -X GET 'http://localhost:8000/test/all-user'

As you see all APIs don’t require any authentication or authorization. Now let’s try to secure these API endpoints.

Define Role-Based Access with @RolesAllowed Annotation

@RolesAllowed("user")
@RequestMapping(value = "/user", method = RequestMethod.GET)
public ResponseEntity<String> getUser(@RequestHeader String Authorization) {
return ResponseEntity.ok("Hello User");
}

@RolesAllowed("admin")
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public ResponseEntity<String> getAdmin(@RequestHeader String Authorization) {
return ResponseEntity.ok("Hello Admin");
}

@RolesAllowed({ "admin", "user" })
@RequestMapping(value = "/all-user", method = RequestMethod.GET)
public ResponseEntity<String> getAllUser(@RequestHeader String Authorization) {
return ResponseEntity.ok("Hello All User");
}

Define Role-Based Access with Security Configuration

Rather than using @RolesAllowed annotation, the same configuration can be made in KeycloakSecurityConfig class as below.

@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/test/anonymous").permitAll()
.antMatchers("/test/user").hasAnyRole("user")
.antMatchers("/test/admin").hasAnyRole("admin")
.antMatchers("/test/all-user").hasAnyRole("user","admin")
.anyRequest()
.permitAll();
http.csrf().disable();
}

Java Versions and JDK Versions

Versions and JDK

Java SE VersionJDK VersionClass VersionReleased Date
Java SE 6 (Mustang)1.650December 2006
Java SE 7 (Dolphin)1.751July 2011
Java SE 81.852March 2014
Java SE 9953September, 21st 2017
Java SE 101054March, 20th 2018
Java SE 111155September, 25th 2018

Install on CentOS

yum list *openjdk*
yum install java-<version here>-openjdk # e.g java-11-openjdk
update-alternatives --config java # choose the default java version
java -version

Install via sdkman

curl -s https://get.sdkman.io | bash
sdk list java
sdk install java <version> // for example `sdk install java 11.0.12-open`

sdk current
sdk current java

Spring Boot Logging

Logging Level

  • TRACE
  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL

🎯 Directory


Standard

API Design Guide

Use RESTful service URLs

Under REST principles, a URL identifies a resource. The following URL design patterns are considered REST best practices:

  • URLs should include nouns, not verbs.
  • Use plural nouns only for consistency (no singular nouns).
  • Use HTTP methods (HTTP/1.1) to operate on these resources:
  • Use HTTP response status codes to represent the outcome of operations on resources.

Response Http Status Code

  • 200 OK
  • 400 Bad Request
  • 500 Internal Server Error

Other commonly seen codes include:

  • 201 Created
  • 204 No Content
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found

jQuery Ajax Request

  • type: ‘GET’ —- dataType: ‘xml|json|script|text|html’ -> response http status code: 200 / 404
  • type: ‘POST’ —– dataType: ‘xml|json’ -> response http status code: 201 (created) / 405 (Method Not Allowed)
  • type: ‘PUT’ —— dataType: ‘xml|json’ -> response http status code: 200 / 404
  • type: ‘DELETE’ —— if return 204, dataType should not be ‘json’ -> response http status code: 200 / 404

Good RESTful URL examples

List of magazines:

GET /api/v1/magazines.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Filtering and sorting are server-side operations on resources:

GET /api/v1/magazines.json?year=2011&amp;sort=desc HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

A single magazine in JSON format:

GET /api/v1/magazines/1234.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

All articles in (or belonging to) this magazine:

GET /api/v1/magazines/1234/articles.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

All articles in this magazine in XML format:

GET /api/v1/magazines/1234/articles.xml HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Specify query parameters in a comma separated list:

GET /api/v1/magazines/1234.json?fields=title,subtitle,date HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Add a new article to a particular magazine:

POST /api/v1/magazines/1234/articles.json HTTP/1.1
Host: www.example.gov.au
Accept: application/json, text/javascript

Commit Message Format

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

type as below:

  • feat: a new feature
  • fix: a bug fix
  • docs: changes to documentation
  • style: formatting, missing semi colons, etc; no code change
  • refactor: refactoring production code
  • test: adding tests, refactoring test; no production code change
  • chore: updating build tasks, package manager configs, etc; no production code change
  • ci: about ci
  • perf: performance change
  • WIP: work in progress

OAuth2

Let’s see how this Oauth2 workflow looks like:

Untitled.png

Untitled.png

Languages

Ruby

Installing

bookmark

gem sources -l
gem sources --remove https://ruby.taobao.org/
gem sources --add https://gems.ruby-china.com/

Installing on M1 Apple for macOS 13 Ventura

brew install chruby ruby-install

Next, if you’re on an Apple Silicon Mac (M1 or M2), you need to check which version of the Apple Command Line Tools (CLT) or Xcode you have:

brew config

Look for the lines at the bottom that start with CLT:  and Xcode: . If either one of them starts with 14 , then you’ll need to install Ruby like this:

ruby-install ruby -- --enable-shared

Otherwise, don’t use any extra options:

ruby-install ruby

Resources

Symbols

★☆✦✤❅

➠➥

✓✗✔✘☓√

☑☐

●◔◕◓◒◐◑◉◎◌○❍

◜◝◞◟◠◡

Color Definitions

const colorScheme = {
background: '#1F2D3D',
primary: '#3498DB',
secondary: '#2ECC71',
highlight: '#E74C3C',
accent: '#F1C40F',
textPrimary: '#FFFFFF',
textSecondary: '#BDC3C7',
};

Retro Metro

[“#ea5545”, “#f46a9b”, “#ef9b20”, “#edbf33”, “#ede15b”, “#bdcf32”, “#87bc45”, “#27aeef”, “#b33dc6”]

metro.png

Dutch Field

[#e60049″, “#0bb4ff”, “#50e991, “#e6d800”, “#9b19f5”, “#ffa300”, “#dc0ab4”, “#b3d4ff”, “#00bfa0”]

dutch.png

River Nights

[“#b30000”, “#7c1158”, “#4421af”, “#1a53ff”, “#0d88e6”, “#00b7c7”, “#5ad45a”, “#8be04e”, “#ebdc78”]

river-1.png

Spring Pastels

[“#fd7f6f”, “#7eb0d5”, “#b2e061”, “#bd7ebe”, “#ffb55a”, “#ffee65”, “#beb9db”, “#fdcce5”, “#8bd3c7”]

spring.png

Sequential palettes

Blue to Yellow

blueyellow.png

[“#115f9a”, “#1984c5”, “#22a7f0”, “#48b5c4”, “#76c68f”, “#a6d75b”, “#c9e52f”, “#d0ee11”, “#f4f100”]

Grey to Red

greyred.png

[“#d7e1ee”, “#cbd6e4”, “#bfcbdb”, “#b3bfd1”, “#a4a2a8”, “#df8879”, “#c86558”, “#b04238”, “#991f17”]

Black to Pink

blackpink.png

[“#2e2b28”, “#3b3734”, “#474440”, “#54504c”, “#6b506b”, “#ab3da9”, “#de25da”, “#eb44e8”, “#ff80ff”]

Blues

blues.png

[“#0000b3”, “#0010d9”, “#0020ff”, “#0040ff”, “#0060ff”, “#0080ff”, “#009fff”, “#00bfff”, “#00ffff”]

More Colors

export const gray = {
gray1: "hsl(0, 0%, 99.0%)",
gray2: "hsl(0, 0%, 97.5%)",
gray3: "hsl(0, 0%, 94.6%)",
gray4: "hsl(0, 0%, 92.0%)",
gray5: "hsl(0, 0%, 89.5%)",
gray6: "hsl(0, 0%, 86.8%)",
gray7: "hsl(0, 0%, 83.0%)",
gray8: "hsl(0, 0%, 73.2%)",
gray9: "hsl(0, 0%, 55.2%)",
gray10: "hsl(0, 0%, 50.3%)",
gray11: "hsl(0, 0%, 39.3%)",
gray12: "hsl(0, 0%, 12.5%)",
};

export const mauve = {
mauve1: "hsl(300, 26.0%, 99.0%)",
mauve2: "hsl(270, 20.0%, 98.0%)",
mauve3: "hsl(267, 13.8%, 95.3%)",
mauve4: "hsl(265, 12.2%, 92.7%)",
mauve5: "hsl(263, 11.6%, 90.3%)",
mauve6: "hsl(261, 11.1%, 87.7%)",
mauve7: "hsl(257, 10.8%, 84.3%)",
mauve8: "hsl(249, 10.4%, 75.5%)",
mauve9: "hsl(252, 6.0%, 57.3%)",
mauve10: "hsl(250, 5.0%, 52.3%)",
mauve11: "hsl(252, 5.0%, 40.7%)",
mauve12: "hsl(260, 10.0%, 13.5%)",
};

export const slate = {
slate1: "hsl(240, 22.0%, 99.0%)",
slate2: "hsl(240, 20.0%, 98.0%)",
slate3: "hsl(239, 13.4%, 95.4%)",
slate4: "hsl(238, 11.8%, 92.9%)",
slate5: "hsl(237, 11.1%, 90.5%)",
slate6: "hsl(236, 10.6%, 87.9%)",
slate7: "hsl(234, 10.4%, 84.4%)",
slate8: "hsl(231, 10.2%, 75.1%)",
slate9: "hsl(230, 6.0%, 57.0%)",
slate10: "hsl(227, 5.2%, 51.8%)",
slate11: "hsl(220, 6.0%, 40.0%)",
slate12: "hsl(210, 12.0%, 12.5%)",
};

export const sage = {
sage1: "hsl(155, 30.0%, 98.8%)",
sage2: "hsl(150, 14.3%, 97.3%)",
sage3: "hsl(150, 8.0%, 94.5%)",
sage4: "hsl(150, 6.0%, 92.0%)",
sage5: "hsl(150, 4.9%, 89.5%)",
sage6: "hsl(150, 4.3%, 86.7%)",
sage7: "hsl(150, 3.7%, 82.8%)",
sage8: "hsl(150, 2.9%, 72.9%)",
sage9: "hsl(155, 3.5%, 54.2%)",
sage10: "hsl(158, 2.9%, 49.3%)",
sage11: "hsl(155, 3.0%, 38.5%)",
sage12: "hsl(155, 12.0%, 11.5%)",
};

export const olive = {
olive1: "hsl(110, 20.0%, 99.0%)",
olive2: "hsl(120, 16.7%, 97.6%)",
olive3: "hsl(118, 8.4%, 94.8%)",
olive4: "hsl(116, 6.0%, 92.1%)",
olive5: "hsl(115, 4.9%, 89.4%)",
olive6: "hsl(113, 4.2%, 86.5%)",
olive7: "hsl(111, 3.6%, 82.6%)",
olive8: "hsl(105, 2.9%, 72.9%)",
olive9: "hsl(110, 3.0%, 54.5%)",
olive10: "hsl(105, 2.7%, 49.4%)",
olive11: "hsl(110, 3.0%, 38.5%)",
olive12: "hsl(110, 8.0%, 12.0%)",
};

export const sand = {
sand1: "hsl(50, 20.0%, 99.0%)",
sand2: "hsl(60, 7.7%, 97.5%)",
sand3: "hsl(59, 5.8%, 94.5%)",
sand4: "hsl(58, 5.3%, 91.8%)",
sand5: "hsl(57, 5.1%, 89.0%)",
sand6: "hsl(56, 5.0%, 86.0%)",
sand7: "hsl(54, 4.9%, 81.8%)",
sand8: "hsl(51, 5.0%, 72.4%)",
sand9: "hsl(60, 3.0%, 53.9%)",
sand10: "hsl(60, 2.6%, 49.0%)",
sand11: "hsl(50, 2.5%, 38.0%)",
sand12: "hsl(50, 8.0%, 12.0%)",
};

export const tomato = {
tomato1: "hsl(10, 100%, 99.4%)",
tomato2: "hsl(8, 100%, 98.4%)",
tomato3: "hsl(8, 100%, 96.6%)",
tomato4: "hsl(8, 100%, 94.3%)",
tomato5: "hsl(8, 92.8%, 91.0%)",
tomato6: "hsl(9, 84.7%, 86.3%)",
tomato7: "hsl(10, 77.3%, 79.5%)",
tomato8: "hsl(10, 71.6%, 71.0%)",
tomato9: "hsl(10, 78.0%, 54.0%)",
tomato10: "hsl(10, 71.4%, 49.4%)",
tomato11: "hsl(10, 82.0%, 42.0%)",
tomato12: "hsl(8, 50.0%, 24.0%)",
};

export const red = {
red1: "hsl(359, 100%, 99.4%)",
red2: "hsl(0, 100%, 98.4%)",
red3: "hsl(360, 100%, 96.8%)",
red4: "hsl(360, 97.9%, 94.8%)",
red5: "hsl(360, 90.2%, 91.9%)",
red6: "hsl(360, 81.7%, 87.8%)",
red7: "hsl(359, 74.2%, 81.7%)",
red8: "hsl(359, 69.5%, 74.3%)",
red9: "hsl(358, 75.0%, 59.0%)",
red10: "hsl(358, 67.4%, 54.6%)",
red11: "hsl(358, 65.0%, 47.0%)",
red12: "hsl(350, 63.0%, 24.0%)",
};

export const ruby = {
ruby1: "hsl(348, 100%, 99.5%)",
ruby2: "hsl(345, 100%, 98.4%)",
ruby3: "hsl(345, 89.9%, 96.7%)",
ruby4: "hsl(346, 82.6%, 94.4%)",
ruby5: "hsl(346, 75.8%, 91.4%)",
ruby6: "hsl(347, 69.3%, 87.1%)",
ruby7: "hsl(348, 64.3%, 80.9%)",
ruby8: "hsl(348, 61.5%, 73.5%)",
ruby9: "hsl(348, 75.0%, 58.5%)",
ruby10: "hsl(347, 68.6%, 54.1%)",
ruby11: "hsl(345, 70.0%, 46.5%)",
ruby12: "hsl(344, 63.0%, 24.0%)",
};

export const crimson = {
crimson1: "hsl(332, 100%, 99.4%)",
crimson2: "hsl(330, 100%, 98.4%)",
crimson3: "hsl(331, 85.6%, 96.6%)",
crimson4: "hsl(331, 78.1%, 94.2%)",
crimson5: "hsl(332, 72.1%, 91.1%)",
crimson6: "hsl(333, 67.0%, 86.7%)",
crimson7: "hsl(335, 63.5%, 80.4%)",
crimson8: "hsl(336, 62.3%, 72.9%)",
crimson9: "hsl(336, 80.0%, 57.8%)",
crimson10: "hsl(336, 71.3%, 52.8%)",
crimson11: "hsl(336, 75.0%, 45.5%)",
crimson12: "hsl(332, 63.0%, 23.5%)",
};

export const pink = {
pink1: "hsl(322, 100%, 99.4%)",
pink2: "hsl(323, 100%, 98.4%)",
pink3: "hsl(323, 86.3%, 96.5%)",
pink4: "hsl(323, 78.7%, 94.2%)",
pink5: "hsl(323, 72.2%, 91.1%)",
pink6: "hsl(323, 66.3%, 86.6%)",
pink7: "hsl(323, 62.0%, 80.1%)",
pink8: "hsl(323, 60.3%, 72.4%)",
pink9: "hsl(322, 65.0%, 54.5%)",
pink10: "hsl(322, 62.0%, 49.6%)",
pink11: "hsl(322, 75.0%, 44.0%)",
pink12: "hsl(320, 70.0%, 23.2%)",
};

export const plum = {
plum1: "hsl(292, 90.0%, 99.4%)",
plum2: "hsl(300, 100%, 98.6%)",
plum3: "hsl(299, 71.2%, 96.4%)",
plum4: "hsl(299, 62.0%, 93.8%)",
plum5: "hsl(298, 56.1%, 90.5%)",
plum6: "hsl(296, 51.3%, 85.8%)",
plum7: "hsl(295, 48.2%, 78.9%)",
plum8: "hsl(292, 47.7%, 70.8%)",
plum9: "hsl(292, 45.0%, 51.0%)",
plum10: "hsl(292, 50.2%, 46.9%)",
plum11: "hsl(292, 60.0%, 42.5%)",
plum12: "hsl(291, 57.0%, 23.2%)",
};

export const purple = {
purple1: "hsl(280, 65.0%, 99.4%)",
purple2: "hsl(276, 100%, 99.0%)",
purple3: "hsl(276, 83.1%, 97.0%)",
purple4: "hsl(275, 76.4%, 94.7%)",
purple5: "hsl(275, 70.8%, 91.8%)",
purple6: "hsl(274, 65.4%, 87.8%)",
purple7: "hsl(273, 61.0%, 81.7%)",
purple8: "hsl(272, 60.0%, 73.5%)",
purple9: "hsl(272, 51.0%, 54.0%)",
purple10: "hsl(272, 46.8%, 50.3%)",
purple11: "hsl(272, 50.0%, 45.8%)",
purple12: "hsl(270, 50.0%, 25.0%)",
};

export const violet = {
violet1: "hsl(255, 65.0%, 99.4%)",
violet2: "hsl(252, 100%, 99.0%)",
violet3: "hsl(252, 96.9%, 97.4%)",
violet4: "hsl(252, 91.5%, 95.5%)",
violet5: "hsl(252, 85.1%, 93.0%)",
violet6: "hsl(252, 77.8%, 89.4%)",
violet7: "hsl(252, 71.0%, 83.7%)",
violet8: "hsl(252, 68.6%, 76.3%)",
violet9: "hsl(252, 56.0%, 57.5%)",
violet10: "hsl(251, 48.1%, 53.5%)",
violet11: "hsl(250, 43.0%, 48.0%)",
violet12: "hsl(250, 43.0%, 26.0%)",
};

export const iris = {
iris1: "hsl(243, 65.0%, 99.5%)",
iris2: "hsl(240, 100%, 99.0%)",
iris3: "hsl(240, 99.9%, 97.7%)",
iris4: "hsl(240, 93.5%, 95.9%)",
iris5: "hsl(240, 86.5%, 93.4%)",
iris6: "hsl(240, 80.2%, 89.8%)",
iris7: "hsl(239, 75.3%, 84.6%)",
iris8: "hsl(238, 73.9%, 77.5%)",
iris9: "hsl(240, 60.0%, 59.8%)",
iris10: "hsl(240, 55.7%, 56.7%)",
iris11: "hsl(240, 50.0%, 52.0%)",
iris12: "hsl(238, 43.0%, 26.8%)",
};

export const indigo = {
indigo1: "hsl(225, 60.0%, 99.4%)",
indigo2: "hsl(223, 100%, 98.6%)",
indigo3: "hsl(223, 98.4%, 97.1%)",
indigo4: "hsl(223, 92.9%, 95.0%)",
indigo5: "hsl(224, 87.1%, 92.0%)",
indigo6: "hsl(224, 81.9%, 87.8%)",
indigo7: "hsl(225, 77.4%, 82.1%)",
indigo8: "hsl(226, 75.4%, 74.5%)",
indigo9: "hsl(226, 70.0%, 55.5%)",
indigo10: "hsl(226, 58.6%, 51.3%)",
indigo11: "hsl(226, 55.0%, 45.0%)",
indigo12: "hsl(226, 50.0%, 24.0%)",
};

export const blue = {
blue1: "hsl(206, 100%, 99.2%)",
blue2: "hsl(210, 100%, 98.0%)",
blue3: "hsl(209, 100%, 96.5%)",
blue4: "hsl(210, 98.8%, 94.0%)",
blue5: "hsl(209, 95.0%, 90.1%)",
blue6: "hsl(209, 81.2%, 84.5%)",
blue7: "hsl(208, 77.5%, 76.9%)",
blue8: "hsl(206, 81.9%, 65.3%)",
blue9: "hsl(206, 100%, 50.0%)",
blue10: "hsl(208, 93.5%, 47.4%)",
blue11: "hsl(211, 90.0%, 42.0%)",
blue12: "hsl(216, 71.0%, 23.0%)",
};

export const cyan = {
cyan1: "hsl(185, 60.0%, 98.7%)",
cyan2: "hsl(185, 73.3%, 97.1%)",
cyan3: "hsl(186, 70.2%, 94.4%)",
cyan4: "hsl(186, 63.8%, 90.6%)",
cyan5: "hsl(187, 58.3%, 85.4%)",
cyan6: "hsl(188, 54.6%, 78.4%)",
cyan7: "hsl(189, 53.7%, 68.7%)",
cyan8: "hsl(189, 60.3%, 52.5%)",
cyan9: "hsl(190, 95.0%, 39.0%)",
cyan10: "hsl(191, 91.2%, 36.8%)",
cyan11: "hsl(192, 85.0%, 31.0%)",
cyan12: "hsl(192, 70.0%, 16.5%)",
};

export const teal = {
teal1: "hsl(165, 60.0%, 98.8%)",
teal2: "hsl(169, 64.7%, 96.7%)",
teal3: "hsl(169, 59.8%, 94.0%)",
teal4: "hsl(169, 53.1%, 90.2%)",
teal5: "hsl(170, 47.1%, 85.0%)",
teal6: "hsl(170, 42.6%, 77.9%)",
teal7: "hsl(170, 39.9%, 68.1%)",
teal8: "hsl(172, 42.1%, 52.5%)",
teal9: "hsl(173, 80.0%, 36.0%)",
teal10: "hsl(173, 83.4%, 32.5%)",
teal11: "hsl(174, 90.0%, 25.2%)",
teal12: "hsl(174, 65.0%, 14.5%)",
};

export const jade = {
jade1: "hsl(151, 60.0%, 99.0%)",
jade2: "hsl(150, 77.8%, 96.5%)",
jade3: "hsl(151, 65.9%, 93.7%)",
jade4: "hsl(152, 56.5%, 90.0%)",
jade5: "hsl(154, 49.1%, 85.0%)",
jade6: "hsl(156, 43.5%, 78.3%)",
jade7: "hsl(159, 40.2%, 68.7%)",
jade8: "hsl(164, 42.0%, 53.3%)",
jade9: "hsl(164, 60.0%, 40.0%)",
jade10: "hsl(164, 60.8%, 36.7%)",
jade11: "hsl(163, 65.0%, 28.9%)",
jade12: "hsl(160, 34.0%, 17.2%)",
};

export const green = {
green1: "hsl(136, 50.0%, 98.9%)",
green2: "hsl(138, 62.5%, 96.9%)",
green3: "hsl(139, 55.2%, 94.5%)",
green4: "hsl(140, 48.7%, 91.0%)",
green5: "hsl(141, 43.7%, 86.0%)",
green6: "hsl(143, 40.3%, 79.0%)",
green7: "hsl(146, 38.5%, 69.0%)",
green8: "hsl(151, 40.2%, 54.1%)",
green9: "hsl(151, 55.0%, 41.5%)",
green10: "hsl(152, 57.5%, 37.6%)",
green11: "hsl(153, 67.0%, 28.5%)",
green12: "hsl(155, 40.0%, 16.5%)",
};

export const grass = {
grass1: "hsl(116, 50.0%, 98.9%)",
grass2: "hsl(120, 60.0%, 97.1%)",
grass3: "hsl(120, 53.6%, 94.8%)",
grass4: "hsl(121, 47.5%, 91.4%)",
grass5: "hsl(122, 42.6%, 86.5%)",
grass6: "hsl(124, 39.0%, 79.7%)",
grass7: "hsl(126, 37.1%, 70.2%)",
grass8: "hsl(131, 38.1%, 56.3%)",
grass9: "hsl(131, 41.0%, 46.5%)",
grass10: "hsl(132, 43.1%, 42.2%)",
grass11: "hsl(133, 50.0%, 32.5%)",
grass12: "hsl(131, 30.0%, 18.0%)",
};

export const brown = {
brown1: "hsl(30, 40.0%, 99.1%)",
brown2: "hsl(30, 50.0%, 97.6%)",
brown3: "hsl(30, 52.5%, 94.6%)",
brown4: "hsl(30, 53.0%, 91.2%)",
brown5: "hsl(29, 52.9%, 86.8%)",
brown6: "hsl(29, 52.5%, 80.9%)",
brown7: "hsl(29, 51.5%, 72.8%)",
brown8: "hsl(28, 50.0%, 63.1%)",
brown9: "hsl(28, 34.0%, 51.0%)",
brown10: "hsl(26, 31.7%, 46.9%)",
brown11: "hsl(25, 30.0%, 39.0%)",
brown12: "hsl(20, 15.0%, 21.0%)",
};

export const bronze = {
bronze1: "hsl(15, 30.0%, 99.1%)",
bronze2: "hsl(17, 63.6%, 97.8%)",
bronze3: "hsl(17, 42.1%, 95.2%)",
bronze4: "hsl(17, 35.2%, 92.1%)",
bronze5: "hsl(17, 31.5%, 88.2%)",
bronze6: "hsl(17, 29.0%, 83.0%)",
bronze7: "hsl(17, 26.9%, 75.6%)",
bronze8: "hsl(17, 25.1%, 66.5%)",
bronze9: "hsl(17, 20.0%, 54.0%)",
bronze10: "hsl(17, 17.9%, 49.3%)",
bronze11: "hsl(15, 20.0%, 41.0%)",
bronze12: "hsl(12, 22.0%, 21.5%)",
};

export const gold = {
gold1: "hsl(50, 20.0%, 99.1%)",
gold2: "hsl(47, 52.9%, 96.7%)",
gold3: "hsl(46, 38.2%, 93.7%)",
gold4: "hsl(44, 32.7%, 90.1%)",
gold5: "hsl(43, 29.9%, 85.7%)",
gold6: "hsl(41, 28.3%, 79.8%)",
gold7: "hsl(39, 27.6%, 71.9%)",
gold8: "hsl(36, 27.2%, 61.8%)",
gold9: "hsl(36, 20.0%, 49.5%)",
gold10: "hsl(36, 19.9%, 44.9%)",
gold11: "hsl(36, 20.0%, 37.0%)",
gold12: "hsl(36, 16.0%, 20.0%)",
};

export const sky = {
sky1: "hsl(193, 100%, 98.8%)",
sky2: "hsl(193, 100%, 97.3%)",
sky3: "hsl(192, 100%, 94.3%)",
sky4: "hsl(193, 93.5%, 90.9%)",
sky5: "hsl(193, 79.3%, 85.9%)",
sky6: "hsl(194, 66.1%, 78.8%)",
sky7: "hsl(194, 60.0%, 69.4%)",
sky8: "hsl(193, 65.2%, 56.1%)",
sky9: "hsl(193, 98.0%, 74.0%)",
sky10: "hsl(193, 90.0%, 71.0%)",
sky11: "hsl(200, 60.0%, 36.0%)",
sky12: "hsl(195, 50.0%, 20.0%)",
};

export const mint = {
mint1: "hsl(165, 80.0%, 98.8%)",
mint2: "hsl(164, 88.2%, 96.7%)",
mint3: "hsl(164, 80.4%, 92.6%)",
mint4: "hsl(165, 71.6%, 88.4%)",
mint5: "hsl(165, 61.0%, 83.4%)",
mint6: "hsl(166, 50.2%, 76.7%)",
mint7: "hsl(166, 43.3%, 67.2%)",
mint8: "hsl(168, 45.0%, 52.9%)",
mint9: "hsl(167, 70.0%, 72.0%)",
mint10: "hsl(167, 62.0%, 69.0%)",
mint11: "hsl(172, 50.0%, 30.5%)",
mint12: "hsl(171, 50.0%, 17.5%)",
};

export const lime = {
lime1: "hsl(85, 50.0%, 98.7%)",
lime2: "hsl(85, 66.7%, 96.5%)",
lime3: "hsl(85, 76.8%, 91.7%)",
lime4: "hsl(84, 71.6%, 86.6%)",
lime5: "hsl(83, 62.5%, 80.8%)",
lime6: "hsl(81, 51.6%, 73.6%)",
lime7: "hsl(79, 41.5%, 64.4%)",
lime8: "hsl(77, 40.2%, 52.2%)",
lime9: "hsl(81, 80.0%, 66.0%)",
lime10: "hsl(81, 75.0%, 60.0%)",
lime11: "hsl(75, 40.0%, 29.0%)",
lime12: "hsl(75, 40.0%, 18.0%)",
};

export const yellow = {
yellow1: "hsl(60, 54.0%, 98.5%)",
yellow2: "hsl(52, 100%, 93.9%)",
yellow3: "hsl(53, 100%, 88.9%)",
yellow4: "hsl(53, 93.7%, 83.9%)",
yellow5: "hsl(52, 84.7%, 78.7%)",
yellow6: "hsl(51, 73.0%, 72.4%)",
yellow7: "hsl(48, 59.6%, 64.3%)",
yellow8: "hsl(46, 55.0%, 52.9%)",
yellow9: "hsl(53, 96.0%, 58.0%)",
yellow10: "hsl(52, 95.0%, 52.0%)",
yellow11: "hsl(42, 50.0%, 31.0%)",
yellow12: "hsl(42, 40.0%, 20.0%)",
};

export const amber = {
amber1: "hsl(39, 70.0%, 99.0%)",
amber2: "hsl(40, 100%, 96.5%)",
amber3: "hsl(45, 100%, 90.8%)",
amber4: "hsl(44, 100%, 85.8%)",
amber5: "hsl(40, 100%, 81.5%)",
amber6: "hsl(39, 83.6%, 75.4%)",
amber7: "hsl(37, 66.9%, 68.2%)",
amber8: "hsl(35, 59.8%, 60.0%)",
amber9: "hsl(42, 100%, 62.0%)",
amber10: "hsl(42, 100%, 55.0%)",
amber11: "hsl(25, 50.0%, 38.0%)",
amber12: "hsl(25, 40.0%, 22.0%)",
};

export const orange = {
orange1: "hsl(24, 70.0%, 99.0%)",
orange2: "hsl(22, 100%, 97.8%)",
orange3: "hsl(34, 100%, 91.7%)",
orange4: "hsl(33, 100%, 86.6%)",
orange5: "hsl(31, 100%, 82.2%)",
orange6: "hsl(27, 100%, 78.4%)",
orange7: "hsl(21, 100%, 74.5%)",
orange8: "hsl(19, 80.1%, 64.5%)",
orange9: "hsl(24, 94.0%, 50.0%)",
orange10: "hsl(24, 100%, 46.5%)",
orange11: "hsl(16, 45.0%, 41.5%)",
orange12: "hsl(16, 50.0%, 23.0%)",
};

export const grayA = {
grayA1: "hsla(0, 0%, 0%, 0.012)",
grayA2: "hsla(0, 0%, 0%, 0.024)",
grayA3: "hsla(0, 0%, 0%, 0.055)",
grayA4: "hsla(0, 0%, 0%, 0.078)",
grayA5: "hsla(0, 0%, 0%, 0.106)",
grayA6: "hsla(0, 0%, 0%, 0.133)",
grayA7: "hsla(0, 0%, 0%, 0.169)",
grayA8: "hsla(0, 0%, 0%, 0.267)",
grayA9: "hsla(0, 0%, 0%, 0.447)",
grayA10: "hsla(0, 0%, 0%, 0.498)",
grayA11: "hsla(0, 0%, 0%, 0.608)",
grayA12: "hsla(0, 0%, 0%, 0.875)",
};

export const mauveA = {
mauveA1: "hsla(300, 89.3%, 18.3%, 0.012)",
mauveA2: "hsla(270, 89.3%, 18.3%, 0.024)",
mauveA3: "hsla(270, 98.8%, 14.4%, 0.055)",
mauveA4: "hsla(264, 93.8%, 12.6%, 0.083)",
mauveA5: "hsla(270, 98.4%, 10.9%, 0.110)",
mauveA6: "hsla(266, 94.9%, 10.5%, 0.138)",
mauveA7: "hsla(255, 97.2%, 9.3%, 0.173)",
mauveA8: "hsla(249, 98.4%, 9.6%, 0.271)",
mauveA9: "hsla(249, 99.9%, 5.7%, 0.451)",
mauveA10: "hsla(250, 99.8%, 4.7%, 0.502)",
mauveA11: "hsla(252, 98.8%, 3.2%, 0.612)",
mauveA12: "hsla(257, 96.0%, 1.6%, 0.879)",
};

export const slateA = {
slateA1: "hsla(240, 89.3%, 18.3%, 0.012)",
slateA2: "hsla(240, 89.3%, 18.3%, 0.024)",
slateA3: "hsla(240, 99.7%, 11.6%, 0.051)",
slateA4: "hsla(240, 93.2%, 10.6%, 0.079)",
slateA5: "hsla(240, 98.8%, 9.4%, 0.106)",
slateA6: "hsla(240, 94.6%, 9.3%, 0.134)",
slateA7: "hsla(233, 97.2%, 9.3%, 0.173)",
slateA8: "hsla(231, 98.1%, 9.4%, 0.275)",
slateA9: "hsla(231, 99.6%, 5.6%, 0.455)",
slateA10: "hsla(230, 99.5%, 4.7%, 0.506)",
slateA11: "hsla(220, 98.0%, 3.8%, 0.624)",
slateA12: "hsla(210, 95.1%, 1.9%, 0.891)",
};

export const sageA = {
sageA1: "hsla(150, 92.6%, 26.5%, 0.016)",
sageA2: "hsla(150, 86.2%, 14.2%, 0.032)",
sageA3: "hsla(150, 95.7%, 6.9%, 0.059)",
sageA4: "hsla(160, 89.0%, 7.6%, 0.087)",
sageA5: "hsla(140, 96.8%, 5.5%, 0.110)",
sageA6: "hsla(140, 88.8%, 4.8%, 0.138)",
sageA7: "hsla(140, 91.7%, 3.6%, 0.177)",
sageA8: "hsla(150, 93.2%, 3.0%, 0.279)",
sageA9: "hsla(158, 97.0%, 3.4%, 0.475)",
sageA10: "hsla(163, 97.0%, 2.7%, 0.522)",
sageA11: "hsla(160, 95.5%, 2.0%, 0.628)",
sageA12: "hsla(154, 93.5%, 1.6%, 0.899)",
};

export const oliveA = {
oliveA1: "hsla(120, 89.3%, 18.3%, 0.012)",
oliveA2: "hsla(120, 87.7%, 16.0%, 0.028)",
oliveA3: "hsla(120, 97.6%, 7.3%, 0.055)",
oliveA4: "hsla(120, 85.8%, 5.5%, 0.083)",
oliveA5: "hsla(120, 95.2%, 3.7%, 0.110)",
oliveA6: "hsla(120, 87.7%, 4.7%, 0.142)",
oliveA7: "hsla(100, 90.6%, 3.6%, 0.181)",
oliveA8: "hsla(105, 93.2%, 3.0%, 0.279)",
oliveA9: "hsla(103, 97.1%, 3.0%, 0.471)",
oliveA10: "hsla(110, 97.1%, 2.3%, 0.518)",
oliveA11: "hsla(110, 95.5%, 2.0%, 0.628)",
oliveA12: "hsla(108, 92.4%, 1.2%, 0.891)",
};

export const sandA = {
sandA1: "hsla(60, 89.3%, 18.3%, 0.012)",
sandA2: "hsla(60, 78.1%, 9.0%, 0.028)",
sandA3: "hsla(60, 95.7%, 6.9%, 0.059)",
sandA4: "hsla(60, 84.4%, 5.3%, 0.087)",
sandA5: "hsla(60, 93.5%, 3.7%, 0.114)",
sandA6: "hsla(60, 86.7%, 4.6%, 0.146)",
sandA7: "hsla(48, 92.1%, 5.5%, 0.193)",
sandA8: "hsla(51, 94.5%, 5.0%, 0.291)",
sandA9: "hsla(60, 96.6%, 3.0%, 0.475)",
sandA10: "hsla(60, 96.5%, 2.3%, 0.522)",
sandA11: "hsla(60, 94.0%, 1.7%, 0.632)",
sandA12: "hsla(48, 92.4%, 1.2%, 0.891)",
};

export const tomatoA = {
tomatoA1: "hsla(0, 100%, 51.0%, 0.012)",
tomatoA2: "hsla(8, 100%, 51.0%, 0.032)",
tomatoA3: "hsla(7, 100%, 50.2%, 0.067)",
tomatoA4: "hsla(8, 100%, 50.1%, 0.114)",
tomatoA5: "hsla(7, 99.5%, 47.9%, 0.173)",
tomatoA6: "hsla(9, 99.9%, 46.2%, 0.255)",
tomatoA7: "hsla(10, 99.8%, 43.6%, 0.365)",
tomatoA8: "hsla(10, 99.5%, 41.8%, 0.499)",
tomatoA9: "hsla(10, 99.9%, 43.8%, 0.820)",
tomatoA10: "hsla(10, 100%, 41.1%, 0.859)",
tomatoA11: "hsla(10, 99.9%, 37.3%, 0.926)",
tomatoA12: "hsla(8, 99.5%, 13.7%, 0.879)",
};

export const redA = {
redA1: "hsla(0, 100%, 51.0%, 0.012)",
redA2: "hsla(0, 100%, 51.0%, 0.032)",
redA3: "hsla(0, 100%, 50.2%, 0.063)",
redA4: "hsla(0, 100%, 50.0%, 0.102)",
redA5: "hsla(0, 99.9%, 47.5%, 0.153)",
redA6: "hsla(0, 99.5%, 44.9%, 0.224)",
redA7: "hsla(359, 99.7%, 42.7%, 0.318)",
redA8: "hsla(359, 99.6%, 41.1%, 0.436)",
redA9: "hsla(358, 99.9%, 42.9%, 0.718)",
redA10: "hsla(358, 99.9%, 40.2%, 0.761)",
redA11: "hsla(358, 99.8%, 36.7%, 0.836)",
redA12: "hsla(351, 99.9%, 16.6%, 0.910)",
};

export const rubyA = {
rubyA1: "hsla(340, 100%, 51.0%, 0.012)",
rubyA2: "hsla(345, 100%, 51.0%, 0.032)",
rubyA3: "hsla(344, 99.1%, 47.1%, 0.063)",
rubyA4: "hsla(348, 99.9%, 46.2%, 0.102)",
rubyA5: "hsla(346, 99.9%, 43.6%, 0.153)",
rubyA6: "hsla(347, 99.6%, 41.2%, 0.220)",
rubyA7: "hsla(348, 99.8%, 39.4%, 0.314)",
rubyA8: "hsla(348, 99.7%, 38.2%, 0.428)",
rubyA9: "hsla(348, 99.8%, 43.0%, 0.726)",
rubyA10: "hsla(347, 99.9%, 40.6%, 0.773)",
rubyA11: "hsla(345, 99.9%, 37.9%, 0.859)",
rubyA12: "hsla(344, 99.9%, 16.6%, 0.910)",
};

export const crimsonA = {
crimsonA1: "hsla(340, 100%, 51.0%, 0.012)",
crimsonA2: "hsla(330, 100%, 51.0%, 0.032)",
crimsonA3: "hsla(332, 99.1%, 47.1%, 0.063)",
crimsonA4: "hsla(331, 99.9%, 44.3%, 0.102)",
crimsonA5: "hsla(333, 99.9%, 42.3%, 0.153)",
crimsonA6: "hsla(333, 99.5%, 40.5%, 0.224)",
crimsonA7: "hsla(335, 99.7%, 39.1%, 0.322)",
crimsonA8: "hsla(336, 99.5%, 38.5%, 0.440)",
crimsonA9: "hsla(336, 99.9%, 44.3%, 0.761)",
crimsonA10: "hsla(336, 100%, 41.5%, 0.808)",
crimsonA11: "hsla(336, 99.8%, 38.5%, 0.887)",
crimsonA12: "hsla(332, 99.8%, 16.3%, 0.914)",
};

export const pinkA = {
pinkA1: "hsla(320, 100%, 51.0%, 0.012)",
pinkA2: "hsla(323, 100%, 51.0%, 0.032)",
pinkA3: "hsla(323, 98.9%, 47.3%, 0.067)",
pinkA4: "hsla(323, 99.9%, 44.3%, 0.102)",
pinkA5: "hsla(324, 99.9%, 42.3%, 0.153)",
pinkA6: "hsla(323, 99.5%, 39.6%, 0.224)",
pinkA7: "hsla(323, 99.7%, 38.5%, 0.322)",
pinkA8: "hsla(323, 99.5%, 37.7%, 0.444)",
pinkA9: "hsla(322, 99.7%, 39.3%, 0.750)",
pinkA10: "hsla(322, 99.9%, 37.9%, 0.812)",
pinkA11: "hsla(322, 99.8%, 37.1%, 0.891)",
pinkA12: "hsla(320, 99.6%, 17.6%, 0.930)",
};

export const plumA = {
plumA1: "hsla(280, 100%, 51.0%, 0.012)",
plumA2: "hsla(300, 100%, 51.0%, 0.028)",
plumA3: "hsla(300, 99.0%, 40.9%, 0.063)",
plumA4: "hsla(300, 99.9%, 38.5%, 0.102)",
plumA5: "hsla(298, 98.2%, 35.9%, 0.150)",
plumA6: "hsla(297, 99.6%, 33.7%, 0.216)",
plumA7: "hsla(295, 99.7%, 32.6%, 0.314)",
plumA8: "hsla(292, 99.6%, 32.4%, 0.432)",
plumA9: "hsla(292, 99.9%, 31.0%, 0.710)",
plumA10: "hsla(292, 99.9%, 30.8%, 0.765)",
plumA11: "hsla(292, 99.8%, 30.7%, 0.832)",
plumA12: "hsla(291, 100%, 14.8%, 0.902)",
};

export const purpleA = {
purpleA1: "hsla(300, 94.3%, 34.6%, 0.012)",
purpleA2: "hsla(276, 100%, 51.0%, 0.020)",
purpleA3: "hsla(277, 99.6%, 46.5%, 0.055)",
purpleA4: "hsla(274, 97.9%, 44.3%, 0.095)",
purpleA5: "hsla(276, 98.6%, 42.0%, 0.142)",
purpleA6: "hsla(275, 100%, 39.2%, 0.200)",
purpleA7: "hsla(273, 99.2%, 38.2%, 0.295)",
purpleA8: "hsla(272, 99.7%, 37.6%, 0.424)",
purpleA9: "hsla(272, 99.6%, 34.0%, 0.695)",
purpleA10: "hsla(272, 99.7%, 32.0%, 0.730)",
purpleA11: "hsla(272, 99.8%, 29.7%, 0.773)",
purpleA12: "hsla(270, 99.6%, 14.4%, 0.875)",
};

export const violetA = {
violetA1: "hsla(270, 94.3%, 34.6%, 0.012)",
violetA2: "hsla(252, 100%, 51.0%, 0.020)",
violetA3: "hsla(254, 100%, 50.0%, 0.051)",
violetA4: "hsla(251, 98.3%, 48.2%, 0.087)",
violetA5: "hsla(252, 99.0%, 45.7%, 0.130)",
violetA6: "hsla(251, 99.1%, 44.0%, 0.189)",
violetA7: "hsla(252, 99.5%, 41.7%, 0.279)",
violetA8: "hsla(252, 100%, 40.7%, 0.400)",
violetA9: "hsla(252, 99.9%, 35.8%, 0.663)",
violetA10: "hsla(251, 99.6%, 32.5%, 0.691)",
violetA11: "hsla(250, 99.8%, 28.4%, 0.726)",
violetA12: "hsla(249, 100%, 13.1%, 0.851)",
};

export const irisA = {
irisA1: "hsla(240, 100%, 51.0%, 0.008)",
irisA2: "hsla(240, 100%, 51.0%, 0.020)",
irisA3: "hsla(240, 100%, 51.0%, 0.048)",
irisA4: "hsla(240, 98.5%, 47.9%, 0.079)",
irisA5: "hsla(240, 99.2%, 47.0%, 0.122)",
irisA6: "hsla(240, 99.2%, 44.9%, 0.185)",
irisA7: "hsla(239, 99.6%, 42.8%, 0.271)",
irisA8: "hsla(238, 99.5%, 42.6%, 0.393)",
irisA9: "hsla(240, 99.6%, 37.6%, 0.644)",
irisA10: "hsla(240, 99.8%, 35.8%, 0.675)",
irisA11: "hsla(240, 99.8%, 33.5%, 0.722)",
irisA12: "hsla(238, 99.2%, 13.8%, 0.848)",
};

export const indigoA = {
indigoA1: "hsla(240, 92.6%, 26.5%, 0.008)",
indigoA2: "hsla(223, 100%, 51.0%, 0.028)",
indigoA3: "hsla(224, 100%, 50.1%, 0.059)",
indigoA4: "hsla(223, 98.0%, 48.5%, 0.099)",
indigoA5: "hsla(225, 98.6%, 46.4%, 0.150)",
indigoA6: "hsla(224, 99.5%, 44.9%, 0.224)",
indigoA7: "hsla(225, 99.7%, 43.9%, 0.318)",
indigoA8: "hsla(226, 99.5%, 43.1%, 0.448)",
indigoA9: "hsla(226, 100%, 41.2%, 0.757)",
indigoA10: "hsla(226, 99.8%, 37.1%, 0.773)",
indigoA11: "hsla(226, 99.6%, 31.1%, 0.797)",
indigoA12: "hsla(226, 99.5%, 13.7%, 0.879)",
};

export const blueA = {
blueA1: "hsla(210, 100%, 51.0%, 0.016)",
blueA2: "hsla(210, 100%, 51.0%, 0.040)",
blueA3: "hsla(210, 100%, 50.3%, 0.071)",
blueA4: "hsla(210, 100%, 50.1%, 0.118)",
blueA5: "hsla(209, 99.1%, 49.2%, 0.193)",
blueA6: "hsla(209, 99.5%, 45.3%, 0.283)",
blueA7: "hsla(208, 99.9%, 43.8%, 0.412)",
blueA8: "hsla(206, 99.8%, 45.1%, 0.632)",
blueA9: "hsla(206, 100%, 50.0%, 1)",
blueA10: "hsla(208, 99.9%, 45.8%, 0.969)",
blueA11: "hsla(211, 100%, 39.4%, 0.957)",
blueA12: "hsla(216, 99.6%, 17.5%, 0.934)",
};

export const cyanA = {
cyanA1: "hsla(195, 95.2%, 41.2%, 0.020)",
cyanA2: "hsla(185, 99.9%, 42.3%, 0.051)",
cyanA3: "hsla(186, 97.8%, 42.2%, 0.095)",
cyanA4: "hsla(186, 99.9%, 38.5%, 0.153)",
cyanA5: "hsla(187, 99.3%, 36.6%, 0.232)",
cyanA6: "hsla(188, 99.4%, 35.4%, 0.334)",
cyanA7: "hsla(189, 99.6%, 35.0%, 0.483)",
cyanA8: "hsla(189, 99.9%, 37.6%, 0.761)",
cyanA9: "hsla(190, 99.8%, 37.8%, 0.981)",
cyanA10: "hsla(191, 99.9%, 34.6%, 0.969)",
cyanA11: "hsla(192, 100%, 27.6%, 0.953)",
cyanA12: "hsla(192, 99.2%, 12.3%, 0.950)",
};

export const tealA = {
tealA1: "hsla(165, 95.2%, 41.2%, 0.020)",
tealA2: "hsla(169, 99.5%, 39.4%, 0.055)",
tealA3: "hsla(167, 97.6%, 38.1%, 0.095)",
tealA4: "hsla(168, 98.1%, 34.6%, 0.150)",
tealA5: "hsla(170, 99.4%, 32.3%, 0.220)",
tealA6: "hsla(170, 99.7%, 30.1%, 0.314)",
tealA7: "hsla(170, 99.3%, 28.7%, 0.448)",
tealA8: "hsla(172, 99.8%, 29.7%, 0.675)",
tealA9: "hsla(173, 99.8%, 31.1%, 0.930)",
tealA10: "hsla(173, 99.7%, 28.7%, 0.946)",
tealA11: "hsla(174, 99.8%, 23.3%, 0.977)",
tealA12: "hsla(174, 99.0%, 10.0%, 0.950)",
};

export const jadeA = {
jadeA1: "hsla(160, 94.9%, 38.7%, 0.016)",
jadeA2: "hsla(150, 99.1%, 44.0%, 0.063)",
jadeA3: "hsla(150, 99.7%, 40.8%, 0.106)",
jadeA4: "hsla(151, 99.8%, 36.3%, 0.157)",
jadeA5: "hsla(153, 99.4%, 33.5%, 0.224)",
jadeA6: "hsla(155, 99.8%, 30.4%, 0.310)",
jadeA7: "hsla(159, 99.4%, 28.7%, 0.440)",
jadeA8: "hsla(164, 99.9%, 29.6%, 0.663)",
jadeA9: "hsla(164, 99.7%, 28.6%, 0.840)",
jadeA10: "hsla(164, 100%, 25.9%, 0.855)",
jadeA11: "hsla(163, 99.5%, 21.0%, 0.899)",
jadeA12: "hsla(160, 98.8%, 6.7%, 0.887)",
};

export const greenA = {
greenA1: "hsla(140, 94.9%, 38.7%, 0.016)",
greenA2: "hsla(138, 99.9%, 38.5%, 0.051)",
greenA3: "hsla(139, 97.7%, 36.9%, 0.087)",
greenA4: "hsla(139, 98.5%, 32.7%, 0.134)",
greenA5: "hsla(141, 100%, 30.4%, 0.200)",
greenA6: "hsla(142, 99.0%, 28.9%, 0.295)",
greenA7: "hsla(146, 99.5%, 27.6%, 0.428)",
greenA8: "hsla(151, 99.5%, 28.8%, 0.644)",
greenA9: "hsla(151, 99.9%, 28.0%, 0.812)",
greenA10: "hsla(152, 99.6%, 25.8%, 0.840)",
greenA11: "hsla(153, 99.9%, 21.0%, 0.906)",
greenA12: "hsla(155, 99.9%, 7.4%, 0.902)",
};

export const grassA = {
grassA1: "hsla(120, 94.9%, 38.7%, 0.016)",
grassA2: "hsla(120, 94.9%, 38.7%, 0.048)",
grassA3: "hsla(120, 98.0%, 35.5%, 0.079)",
grassA4: "hsla(120, 98.7%, 31.5%, 0.126)",
grassA5: "hsla(122, 98.5%, 29.9%, 0.193)",
grassA6: "hsla(125, 99.2%, 27.9%, 0.283)",
grassA7: "hsla(125, 99.9%, 27.0%, 0.408)",
grassA8: "hsla(131, 100%, 27.6%, 0.604)",
grassA9: "hsla(131, 99.7%, 26.3%, 0.726)",
grassA10: "hsla(132, 99.9%, 24.0%, 0.761)",
grassA11: "hsla(133, 99.5%, 19.5%, 0.840)",
grassA12: "hsla(131, 99.1%, 6.3%, 0.875)",
};

export const brownA = {
brownA1: "hsla(30, 94.3%, 34.6%, 0.012)",
brownA2: "hsla(30, 94.3%, 34.6%, 0.036)",
brownA3: "hsla(30, 97.7%, 33.9%, 0.083)",
brownA4: "hsla(31, 98.5%, 34.2%, 0.134)",
brownA5: "hsla(29, 100%, 34.3%, 0.200)",
brownA6: "hsla(28, 99.2%, 34.6%, 0.291)",
brownA7: "hsla(29, 99.8%, 33.8%, 0.412)",
brownA8: "hsla(28, 100%, 33.3%, 0.553)",
brownA9: "hsla(28, 99.9%, 25.5%, 0.655)",
brownA10: "hsla(26, 99.6%, 22.0%, 0.679)",
brownA11: "hsla(24, 99.6%, 16.0%, 0.726)",
brownA12: "hsla(19, 98.8%, 3.9%, 0.820)",
};

export const bronzeA = {
bronzeA1: "hsla(0, 89.3%, 18.3%, 0.012)",
bronzeA2: "hsla(17, 95.1%, 40.1%, 0.036)",
bronzeA3: "hsla(18, 98.3%, 29.8%, 0.067)",
bronzeA4: "hsla(17, 99.6%, 26.0%, 0.106)",
bronzeA5: "hsla(19, 99.6%, 23.8%, 0.157)",
bronzeA6: "hsla(17, 99.2%, 22.5%, 0.220)",
bronzeA7: "hsla(18, 99.7%, 21.6%, 0.310)",
bronzeA8: "hsla(17, 99.5%, 20.2%, 0.420)",
bronzeA9: "hsla(18, 99.9%, 16.7%, 0.553)",
bronzeA10: "hsla(17, 99.0%, 14.9%, 0.597)",
bronzeA11: "hsla(15, 99.5%, 12.0%, 0.671)",
bronzeA12: "hsla(12, 98.7%, 5.7%, 0.832)",
};

export const goldA = {
goldA1: "hsla(60, 89.3%, 18.3%, 0.012)",
goldA2: "hsla(47, 99.9%, 34.6%, 0.051)",
goldA3: "hsla(45, 97.0%, 27.9%, 0.087)",
goldA4: "hsla(46, 98.0%, 25.4%, 0.134)",
goldA5: "hsla(43, 98.4%, 22.6%, 0.185)",
goldA6: "hsla(41, 99.7%, 22.0%, 0.259)",
goldA7: "hsla(38, 99.8%, 21.5%, 0.357)",
goldA8: "hsla(36, 99.3%, 21.5%, 0.487)",
goldA9: "hsla(36, 99.9%, 16.2%, 0.604)",
goldA10: "hsla(36, 99.1%, 13.9%, 0.640)",
goldA11: "hsla(36, 99.8%, 10.6%, 0.706)",
goldA12: "hsla(38, 98.0%, 3.8%, 0.832)",
};

export const skyA = {
skyA1: "hsla(190, 100%, 51.0%, 0.024)",
skyA2: "hsla(193, 100%, 50.1%, 0.055)",
skyA3: "hsla(192, 100%, 50.1%, 0.114)",
skyA4: "hsla(193, 99.4%, 47.9%, 0.177)",
skyA5: "hsla(194, 100%, 44.5%, 0.251)",
skyA6: "hsla(194, 100%, 40.0%, 0.353)",
skyA7: "hsla(194, 99.6%, 37.7%, 0.491)",
skyA8: "hsla(193, 99.8%, 39.5%, 0.726)",
skyA9: "hsla(193, 99.9%, 49.6%, 0.514)",
skyA10: "hsla(193, 100%, 47.5%, 0.553)",
skyA11: "hsla(200, 100%, 25.2%, 0.855)",
skyA12: "hsla(195, 100%, 11.3%, 0.902)",
};

export const mintA = {
mintA1: "hsla(168, 95.4%, 42.8%, 0.024)",
mintA2: "hsla(164, 99.1%, 47.1%, 0.063)",
mintA3: "hsla(164, 98.9%, 44.4%, 0.134)",
mintA4: "hsla(165, 100%, 42.2%, 0.200)",
mintA5: "hsla(166, 99.7%, 37.6%, 0.267)",
mintA6: "hsla(166, 99.2%, 33.3%, 0.350)",
mintA7: "hsla(166, 99.7%, 30.5%, 0.471)",
mintA8: "hsla(168, 99.7%, 31.1%, 0.683)",
mintA9: "hsla(167, 99.8%, 41.4%, 0.475)",
mintA10: "hsla(167, 100%, 38.3%, 0.502)",
mintA11: "hsla(172, 99.4%, 18.1%, 0.848)",
mintA12: "hsla(171, 99.7%, 9.7%, 0.914)",
};

export const limeA = {
limeA1: "hsla(80, 93.8%, 31.4%, 0.020)",
limeA2: "hsla(85, 99.3%, 40.2%, 0.059)",
limeA3: "hsla(84, 98.6%, 43.6%, 0.146)",
limeA4: "hsla(83, 99.4%, 41.7%, 0.232)",
limeA5: "hsla(83, 99.8%, 38.8%, 0.314)",
limeA6: "hsla(81, 100%, 33.8%, 0.400)",
limeA7: "hsla(79, 100%, 29.3%, 0.502)",
limeA8: "hsla(77, 99.8%, 28.7%, 0.671)",
limeA9: "hsla(81, 99.9%, 44.6%, 0.612)",
limeA10: "hsla(81, 100%, 43.0%, 0.702)",
limeA11: "hsla(75, 99.5%, 14.3%, 0.828)",
limeA12: "hsla(75, 98.9%, 8.0%, 0.891)",
};

export const yellowA = {
yellowA1: "hsla(60, 94.3%, 34.6%, 0.024)",
yellowA2: "hsla(52, 100%, 50.2%, 0.122)",
yellowA3: "hsla(53, 100%, 50.1%, 0.224)",
yellowA4: "hsla(53, 99.8%, 48.2%, 0.314)",
yellowA5: "hsla(52, 99.5%, 46.1%, 0.393)",
yellowA6: "hsla(51, 99.7%, 42.3%, 0.479)",
yellowA7: "hsla(48, 99.8%, 37.3%, 0.569)",
yellowA8: "hsla(46, 99.8%, 35.5%, 0.730)",
yellowA9: "hsla(53, 99.9%, 49.1%, 0.824)",
yellowA10: "hsla(52, 99.8%, 48.8%, 0.938)",
yellowA11: "hsla(42, 99.4%, 18.5%, 0.844)",
yellowA12: "hsla(42, 99.3%, 9.0%, 0.879)",
};

export const amberA = {
amberA1: "hsla(40, 94.9%, 38.7%, 0.016)",
amberA2: "hsla(40, 100%, 50.3%, 0.071)",
amberA3: "hsla(45, 100%, 50.2%, 0.185)",
amberA4: "hsla(44, 100%, 50.1%, 0.283)",
amberA5: "hsla(40, 100%, 50.1%, 0.369)",
amberA6: "hsla(39, 100%, 45.7%, 0.451)",
amberA7: "hsla(37, 99.7%, 40.1%, 0.530)",
amberA8: "hsla(35, 99.7%, 37.5%, 0.640)",
amberA9: "hsla(42, 100%, 50.0%, 0.761)",
amberA10: "hsla(42, 100%, 50.1%, 0.899)",
amberA11: "hsla(25, 99.9%, 23.5%, 0.812)",
amberA12: "hsla(24, 99.6%, 10.2%, 0.867)",
};

export const orangeA = {
orangeA1: "hsla(20, 94.9%, 38.7%, 0.016)",
orangeA2: "hsla(22, 100%, 51.0%, 0.044)",
orangeA3: "hsla(34, 100%, 50.1%, 0.165)",
orangeA4: "hsla(33, 100%, 50.1%, 0.267)",
orangeA5: "hsla(31, 100%, 50.0%, 0.357)",
orangeA6: "hsla(27, 100%, 50.1%, 0.432)",
orangeA7: "hsla(21, 100%, 50.0%, 0.510)",
orangeA8: "hsla(19, 99.7%, 44.5%, 0.640)",
orangeA9: "hsla(24, 99.9%, 48.4%, 0.969)",
orangeA10: "hsla(24, 100%, 46.5%, 1)",
orangeA11: "hsla(16, 99.8%, 24.2%, 0.773)",
orangeA12: "hsla(16, 99.4%, 13.1%, 0.887)",
};

export const grayDark = {
gray1: "hsl(0, 0%, 9.5%)",
gray2: "hsl(0, 0%, 10.5%)",
gray3: "hsl(0, 0%, 15.8%)",
gray4: "hsl(0, 0%, 18.9%)",
gray5: "hsl(0, 0%, 21.7%)",
gray6: "hsl(0, 0%, 24.7%)",
gray7: "hsl(0, 0%, 29.1%)",
gray8: "hsl(0, 0%, 37.5%)",
gray9: "hsl(0, 0%, 43.0%)",
gray10: "hsl(0, 0%, 50.7%)",
gray11: "hsl(0, 0%, 69.5%)",
gray12: "hsl(0, 0%, 93.5%)",
};

export const mauveDark = {
mauve1: "hsl(300, 5.0%, 9.5%)",
mauve2: "hsl(300, 7.1%, 11.0%)",
mauve3: "hsl(290, 6.2%, 16.2%)",
mauve4: "hsl(285, 5.8%, 19.5%)",
mauve5: "hsl(280, 5.6%, 22.4%)",
mauve6: "hsl(276, 5.4%, 25.7%)",
mauve7: "hsl(269, 5.3%, 30.3%)",
mauve8: "hsl(258, 5.0%, 39.2%)",
mauve9: "hsl(250, 5.0%, 45.0%)",
mauve10: "hsl(252, 4.3%, 52.3%)",
mauve11: "hsl(250, 6.0%, 70.5%)",
mauve12: "hsl(240, 7.0%, 93.8%)",
};

export const slateDark = {
slate1: "hsl(240, 5.0%, 9.8%)",
slate2: "hsl(240, 6.9%, 11.4%)",
slate3: "hsl(227, 6.7%, 16.4%)",
slate4: "hsl(222, 6.6%, 19.4%)",
slate5: "hsl(218, 6.5%, 22.1%)",
slate6: "hsl(214, 6.4%, 25.1%)",
slate7: "hsl(209, 6.2%, 29.4%)",
slate8: "hsl(202, 5.8%, 37.5%)",
slate9: "hsl(220, 6.0%, 44.0%)",
slate10: "hsl(218, 5.3%, 51.5%)",
slate11: "hsl(220, 7.0%, 70.0%)",
slate12: "hsl(220, 7.0%, 93.5%)",
};

export const sageDark = {
sage1: "hsl(155, 7.0%, 9.2%)",
sage2: "hsl(150, 7.7%, 10.2%)",
sage3: "hsl(151, 5.5%, 15.2%)",
sage4: "hsl(152, 4.7%, 18.3%)",
sage5: "hsl(153, 4.2%, 21.1%)",
sage6: "hsl(153, 3.7%, 24.2%)",
sage7: "hsl(154, 3.3%, 28.7%)",
sage8: "hsl(156, 2.6%, 37.1%)",
sage9: "hsl(155, 6.0%, 41.5%)",
sage10: "hsl(157, 4.6%, 49.2%)",
sage11: "hsl(155, 5.0%, 68.3%)",
sage12: "hsl(155, 7.0%, 93.0%)",
};

export const oliveDark = {
olive1: "hsl(110, 5.0%, 9.2%)",
olive2: "hsl(120, 5.7%, 10.4%)",
olive3: "hsl(120, 4.3%, 15.4%)",
olive4: "hsl(120, 3.9%, 18.5%)",
olive5: "hsl(120, 3.6%, 21.2%)",
olive6: "hsl(120, 3.3%, 24.3%)",
olive7: "hsl(120, 3.0%, 28.7%)",
olive8: "hsl(120, 2.6%, 37.1%)",
olive9: "hsl(110, 6.0%, 41.5%)",
olive10: "hsl(106, 4.6%, 49.3%)",
olive11: "hsl(110, 5.0%, 68.8%)",
olive12: "hsl(110, 7.0%, 93.0%)",
};

export const sandDark = {
sand1: "hsl(60, 6.0%, 9.0%)",
sand2: "hsl(60, 1.9%, 10.4%)",
sand3: "hsl(56, 2.4%, 15.4%)",
sand4: "hsl(53, 2.7%, 18.4%)",
sand5: "hsl(51, 2.9%, 21.2%)",
sand6: "hsl(49, 3.1%, 24.3%)",
sand7: "hsl(47, 3.3%, 28.7%)",
sand8: "hsl(43, 3.7%, 37.1%)",
sand9: "hsl(50, 4.0%, 41.8%)",
sand10: "hsl(48, 3.5%, 49.5%)",
sand11: "hsl(50, 5.0%, 68.3%)",
sand12: "hsl(56, 7.0%, 93.0%)",
};

export const tomatoDark = {
tomato1: "hsl(10, 23.0%, 9.4%)",
tomato2: "hsl(10, 39.0%, 11.6%)",
tomato3: "hsl(9, 48.3%, 15.5%)",
tomato4: "hsl(9, 53.0%, 18.1%)",
tomato5: "hsl(9, 57.0%, 20.7%)",
tomato6: "hsl(9, 61.8%, 24.5%)",
tomato7: "hsl(9, 68.4%, 31.1%)",
tomato8: "hsl(10, 80.4%, 43.9%)",
tomato9: "hsl(10, 78.0%, 54.0%)",
tomato10: "hsl(10, 88.4%, 64.1%)",
tomato11: "hsl(10, 100%, 72.0%)",
tomato12: "hsl(10, 85.0%, 89.0%)",
};

export const redDark = {
red1: "hsl(353, 23.0%, 9.8%)",
red2: "hsl(354, 30.2%, 12.4%)",
red3: "hsl(353, 40.8%, 16.4%)",
red4: "hsl(353, 46.3%, 19.2%)",
red5: "hsl(353, 51.2%, 22.1%)",
red6: "hsl(353, 57.3%, 26.2%)",
red7: "hsl(354, 65.7%, 33.2%)",
red8: "hsl(358, 75.0%, 47.1%)",
red9: "hsl(358, 75.0%, 59.0%)",
red10: "hsl(359, 84.8%, 67.6%)",
red11: "hsl(358, 100%, 76.0%)",
red12: "hsl(350, 100%, 91.0%)",
};

export const rubyDark = {
ruby1: "hsl(343, 23.0%, 10.0%)",
ruby2: "hsl(349, 33.3%, 12.4%)",
ruby3: "hsl(348, 42.2%, 16.4%)",
ruby4: "hsl(348, 46.8%, 19.1%)",
ruby5: "hsl(348, 50.7%, 21.7%)",
ruby6: "hsl(348, 56.0%, 25.7%)",
ruby7: "hsl(348, 64.3%, 33.0%)",
ruby8: "hsl(348, 77.0%, 46.1%)",
ruby9: "hsl(348, 75.0%, 58.5%)",
ruby10: "hsl(349, 84.1%, 67.1%)",
ruby11: "hsl(348, 100%, 76.0%)",
ruby12: "hsl(340, 95.0%, 91.0%)",
};

export const crimsonDark = {
crimson1: "hsl(335, 20.0%, 9.6%)",
crimson2: "hsl(336, 32.3%, 12.2%)",
crimson3: "hsl(336, 40.2%, 16.0%)",
crimson4: "hsl(336, 44.4%, 18.5%)",
crimson5: "hsl(336, 48.4%, 21.3%)",
crimson6: "hsl(336, 53.9%, 25.5%)",
crimson7: "hsl(336, 62.7%, 32.8%)",
crimson8: "hsl(336, 80.0%, 45.1%)",
crimson9: "hsl(336, 80.0%, 57.8%)",
crimson10: "hsl(339, 87.0%, 67.2%)",
crimson11: "hsl(341, 100%, 76.0%)",
crimson12: "hsl(330, 90.0%, 91.0%)",
};

export const pinkDark = {
pink1: "hsl(318, 25.0%, 9.6%)",
pink2: "hsl(318, 32.3%, 12.2%)",
pink3: "hsl(318, 37.1%, 15.7%)",
pink4: "hsl(319, 39.8%, 18.1%)",
pink5: "hsl(319, 42.3%, 20.8%)",
pink6: "hsl(319, 45.8%, 24.9%)",
pink7: "hsl(320, 51.2%, 32.2%)",
pink8: "hsl(322, 60.0%, 46.1%)",
pink9: "hsl(322, 65.0%, 54.5%)",
pink10: "hsl(324, 71.5%, 63.2%)",
pink11: "hsl(325, 90.0%, 75.0%)",
pink12: "hsl(325, 90.0%, 90.5%)",
};

export const plumDark = {
plum1: "hsl(301, 20.0%, 9.4%)",
plum2: "hsl(300, 28.8%, 11.6%)",
plum3: "hsl(299, 31.8%, 15.5%)",
plum4: "hsl(298, 33.4%, 18.3%)",
plum5: "hsl(297, 34.6%, 21.0%)",
plum6: "hsl(296, 36.0%, 25.1%)",
plum7: "hsl(295, 37.9%, 32.7%)",
plum8: "hsl(292, 40.2%, 47.8%)",
plum9: "hsl(292, 45.0%, 51.0%)",
plum10: "hsl(291, 51.2%, 60.2%)",
plum11: "hsl(290, 70.0%, 74.0%)",
plum12: "hsl(300, 60.0%, 89.5%)",
};

export const purpleDark = {
purple1: "hsl(284, 20.0%, 9.6%)",
purple2: "hsl(284, 31.1%, 12.0%)",
purple3: "hsl(282, 35.1%, 16.8%)",
purple4: "hsl(281, 36.9%, 20.1%)",
purple5: "hsl(280, 38.1%, 23.0%)",
purple6: "hsl(278, 39.5%, 27.3%)",
purple7: "hsl(276, 41.1%, 35.1%)",
purple8: "hsl(272, 42.7%, 50.0%)",
purple9: "hsl(272, 51.0%, 54.0%)",
purple10: "hsl(271, 57.8%, 61.2%)",
purple11: "hsl(270, 90.0%, 78.0%)",
purple12: "hsl(275, 75.0%, 91.5%)",
};

export const violetDark = {
violet1: "hsl(250, 20.0%, 10.2%)",
violet2: "hsl(255, 30.3%, 12.9%)",
violet3: "hsl(254, 33.3%, 18.4%)",
violet4: "hsl(253, 34.6%, 22.1%)",
violet5: "hsl(253, 35.5%, 25.5%)",
violet6: "hsl(252, 36.6%, 30.5%)",
violet7: "hsl(251, 37.8%, 39.0%)",
violet8: "hsl(250, 46.2%, 54.1%)",
violet9: "hsl(252, 56.0%, 57.5%)",
violet10: "hsl(253, 63.2%, 64.1%)",
violet11: "hsl(255, 100%, 80.0%)",
violet12: "hsl(250, 93.0%, 93.0%)",
};

export const irisDark = {
iris1: "hsl(240, 22.0%, 10.5%)",
iris2: "hsl(243, 30.4%, 13.5%)",
iris3: "hsl(242, 32.6%, 18.9%)",
iris4: "hsl(241, 33.5%, 22.6%)",
iris5: "hsl(241, 34.2%, 26.1%)",
iris6: "hsl(240, 35.0%, 31.0%)",
iris7: "hsl(240, 36.0%, 39.4%)",
iris8: "hsl(240, 45.2%, 54.9%)",
iris9: "hsl(240, 60.0%, 59.8%)",
iris10: "hsl(241, 66.4%, 65.7%)",
iris11: "hsl(242, 100%, 81.0%)",
iris12: "hsl(242, 92.0%, 93.5%)",
};

export const indigoDark = {
indigo1: "hsl(229, 24.0%, 10.0%)",
indigo2: "hsl(230, 36.4%, 12.9%)",
indigo3: "hsl(228, 42.7%, 18.1%)",
indigo4: "hsl(227, 45.6%, 21.4%)",
indigo5: "hsl(227, 47.8%, 24.4%)",
indigo6: "hsl(226, 50.3%, 28.8%)",
indigo7: "hsl(226, 53.3%, 36.6%)",
indigo8: "hsl(226, 60.0%, 52.0%)",
indigo9: "hsl(226, 70.0%, 55.5%)",
indigo10: "hsl(230, 73.9%, 63.3%)",
indigo11: "hsl(235, 100%, 80.0%)",
indigo12: "hsl(235, 93.0%, 93.0%)",
};

export const blueDark = {
blue1: "hsl(212, 35.0%, 9.2%)",
blue2: "hsl(216, 50.0%, 11.8%)",
blue3: "hsl(214, 57.6%, 15.6%)",
blue4: "hsl(214, 62.3%, 18.4%)",
blue5: "hsl(213, 66.6%, 21.1%)",
blue6: "hsl(212, 72.6%, 25.2%)",
blue7: "hsl(211, 81.3%, 32.4%)",
blue8: "hsl(211, 85.2%, 47.8%)",
blue9: "hsl(206, 100%, 50.0%)",
blue10: "hsl(206, 100%, 61.8%)",
blue11: "hsl(205, 100%, 71.0%)",
blue12: "hsl(205, 100%, 88.0%)",
};

export const cyanDark = {
cyan1: "hsl(192, 60.0%, 7.2%)",
cyan2: "hsl(193, 51.1%, 8.8%)",
cyan3: "hsl(193, 51.9%, 11.9%)",
cyan4: "hsl(193, 52.6%, 14.1%)",
cyan5: "hsl(193, 53.4%, 16.5%)",
cyan6: "hsl(192, 54.6%, 20.1%)",
cyan7: "hsl(192, 56.6%, 26.5%)",
cyan8: "hsl(192, 59.8%, 39.0%)",
cyan9: "hsl(190, 95.0%, 39.0%)",
cyan10: "hsl(190, 84.0%, 46.0%)",
cyan11: "hsl(190, 90.0%, 54.0%)",
cyan12: "hsl(190, 80.0%, 84.0%)",
};

export const tealDark = {
teal1: "hsl(167, 50.0%, 6.7%)",
teal2: "hsl(166, 55.0%, 7.8%)",
teal3: "hsl(167, 52.9%, 10.6%)",
teal4: "hsl(168, 52.5%, 12.7%)",
teal5: "hsl(169, 52.6%, 14.8%)",
teal6: "hsl(169, 53.1%, 18.0%)",
teal7: "hsl(171, 54.9%, 23.8%)",
teal8: "hsl(173, 59.8%, 34.1%)",
teal9: "hsl(173, 80.0%, 36.0%)",
teal10: "hsl(172, 89.5%, 38.8%)",
teal11: "hsl(170, 90.0%, 44.5%)",
teal12: "hsl(163, 70.0%, 81.0%)",
};

export const jadeDark = {
jade1: "hsl(152, 53.0%, 6.5%)",
jade2: "hsl(153, 47.6%, 8.2%)",
jade3: "hsl(154, 46.7%, 11.0%)",
jade4: "hsl(155, 47.0%, 13.2%)",
jade5: "hsl(156, 47.7%, 15.3%)",
jade6: "hsl(158, 49.0%, 18.5%)",
jade7: "hsl(160, 52.0%, 24.2%)",
jade8: "hsl(164, 59.8%, 34.1%)",
jade9: "hsl(164, 60.0%, 40.0%)",
jade10: "hsl(164, 66.8%, 43.7%)",
jade11: "hsl(163, 75.0%, 48.4%)",
jade12: "hsl(155, 70.0%, 81.0%)",
};

export const greenDark = {
green1: "hsl(146, 30.0%, 7.4%)",
green2: "hsl(154, 31.8%, 8.6%)",
green3: "hsl(154, 37.6%, 11.5%)",
green4: "hsl(154, 41.0%, 13.6%)",
green5: "hsl(154, 43.7%, 15.7%)",
green6: "hsl(154, 46.7%, 18.7%)",
green7: "hsl(153, 50.3%, 24.4%)",
green8: "hsl(151, 52.2%, 36.1%)",
green9: "hsl(151, 55.0%, 41.5%)",
green10: "hsl(151, 55.2%, 46.7%)",
green11: "hsl(151, 65.0%, 54.0%)",
green12: "hsl(144, 70.0%, 82.0%)",
};

export const grassDark = {
grass1: "hsl(146, 30.0%, 7.4%)",
grass2: "hsl(138, 20.8%, 9.4%)",
grass3: "hsl(138, 25.8%, 12.5%)",
grass4: "hsl(138, 28.4%, 14.8%)",
grass5: "hsl(137, 30.3%, 17.1%)",
grass6: "hsl(137, 32.3%, 20.6%)",
grass7: "hsl(135, 34.4%, 27.1%)",
grass8: "hsl(131, 35.3%, 40.0%)",
grass9: "hsl(131, 41.0%, 46.5%)",
grass10: "hsl(131, 41.7%, 55.0%)",
grass11: "hsl(131, 50.0%, 63.0%)",
grass12: "hsl(120, 60.0%, 85.0%)",
};

export const brownDark = {
brown1: "hsl(22, 15.0%, 8.7%)",
brown2: "hsl(26, 13.2%, 10.4%)",
brown3: "hsl(26, 17.4%, 13.6%)",
brown4: "hsl(26, 19.6%, 16.2%)",
brown5: "hsl(27, 21.3%, 19.0%)",
brown6: "hsl(27, 23.2%, 23.0%)",
brown7: "hsl(27, 25.4%, 30.4%)",
brown8: "hsl(28, 27.8%, 45.1%)",
brown9: "hsl(28, 34.0%, 51.0%)",
brown10: "hsl(28, 38.0%, 58.3%)",
brown11: "hsl(28, 50.0%, 72.0%)",
brown12: "hsl(35, 60.0%, 87.0%)",
};

export const bronzeDark = {
bronze1: "hsl(17, 10.0%, 8.8%)",
bronze2: "hsl(15, 7.7%, 10.2%)",
bronze3: "hsl(15, 10.2%, 13.9%)",
bronze4: "hsl(16, 11.5%, 16.8%)",
bronze5: "hsl(16, 12.4%, 19.7%)",
bronze6: "hsl(16, 13.4%, 24.1%)",
bronze7: "hsl(17, 14.7%, 31.9%)",
bronze8: "hsl(18, 16.0%, 47.6%)",
bronze9: "hsl(17, 20.0%, 54.0%)",
bronze10: "hsl(18, 23.6%, 60.8%)",
bronze11: "hsl(18, 35.0%, 74.0%)",
bronze12: "hsl(22, 35.0%, 89.0%)",
};

export const goldDark = {
gold1: "hsl(44, 9.0%, 8.3%)",
gold2: "hsl(45, 8.0%, 9.8%)",
gold3: "hsl(44, 9.5%, 13.0%)",
gold4: "hsl(43, 10.5%, 15.6%)",
gold5: "hsl(42, 11.2%, 18.5%)",
gold6: "hsl(41, 12.1%, 22.6%)",
gold7: "hsl(39, 13.2%, 29.8%)",
gold8: "hsl(35, 14.8%, 45.1%)",
gold9: "hsl(36, 20.0%, 49.5%)",
gold10: "hsl(36, 21.9%, 56.8%)",
gold11: "hsl(35, 30.0%, 71.0%)",
gold12: "hsl(35, 25.0%, 88.0%)",
};

export const skyDark = {
sky1: "hsl(205, 45.0%, 8.6%)",
sky2: "hsl(202, 49.0%, 10.0%)",
sky3: "hsl(201, 50.8%, 13.2%)",
sky4: "hsl(201, 52.1%, 15.8%)",
sky5: "hsl(201, 53.3%, 18.4%)",
sky6: "hsl(200, 54.9%, 22.2%)",
sky7: "hsl(200, 57.2%, 29.3%)",
sky8: "hsl(200, 60.0%, 44.1%)",
sky9: "hsl(193, 98.0%, 74.0%)",
sky10: "hsl(192, 100%, 77.0%)",
sky11: "hsl(195, 100%, 66.0%)",
sky12: "hsl(192, 100%, 88.0%)",
};

export const mintDark = {
mint1: "hsl(173, 50.0%, 6.6%)",
mint2: "hsl(174, 51.2%, 8.0%)",
mint3: "hsl(174, 53.5%, 10.6%)",
mint4: "hsl(174, 55.1%, 12.6%)",
mint5: "hsl(173, 56.6%, 14.8%)",
mint6: "hsl(173, 58.3%, 17.9%)",
mint7: "hsl(172, 60.1%, 23.6%)",
mint8: "hsl(170, 59.8%, 35.1%)",
mint9: "hsl(167, 70.0%, 72.0%)",
mint10: "hsl(163, 80.0%, 77.0%)",
mint11: "hsl(167, 70.0%, 58.0%)",
mint12: "hsl(155, 70.0%, 86.5%)",
};

export const limeDark = {
lime1: "hsl(75, 55.0%, 6.0%)",
lime2: "hsl(78, 41.5%, 8.0%)",
lime3: "hsl(81, 38.1%, 11.0%)",
lime4: "hsl(81, 39.4%, 13.1%)",
lime5: "hsl(81, 40.8%, 15.3%)",
lime6: "hsl(80, 42.6%, 18.4%)",
lime7: "hsl(78, 45.4%, 24.0%)",
lime8: "hsl(75, 49.7%, 35.1%)",
lime9: "hsl(81, 80.0%, 66.0%)",
lime10: "hsl(75, 85.0%, 60.0%)",
lime11: "hsl(70, 70.0%, 50.0%)",
lime12: "hsl(80, 80.0%, 85.0%)",
};

export const yellowDark = {
yellow1: "hsl(45, 100%, 5.5%)",
yellow2: "hsl(44, 78.9%, 7.5%)",
yellow3: "hsl(44, 61.9%, 10.6%)",
yellow4: "hsl(44, 57.5%, 13.0%)",
yellow5: "hsl(45, 57.0%, 15.2%)",
yellow6: "hsl(46, 57.5%, 18.3%)",
yellow7: "hsl(47, 58.5%, 24.0%)",
yellow8: "hsl(50, 59.8%, 35.1%)",
yellow9: "hsl(53, 96.0%, 58.0%)",
yellow10: "hsl(53, 96.8%, 67.5%)",
yellow11: "hsl(55, 100%, 60.0%)",
yellow12: "hsl(53, 100%, 84.0%)",
};

export const amberDark = {
amber1: "hsl(36, 100%, 6.1%)",
amber2: "hsl(36, 80.5%, 8.0%)",
amber3: "hsl(35, 63.4%, 11.4%)",
amber4: "hsl(34, 58.8%, 14.1%)",
amber5: "hsl(34, 58.1%, 16.7%)",
amber6: "hsl(35, 58.3%, 20.5%)",
amber7: "hsl(35, 59.0%, 27.4%)",
amber8: "hsl(36, 60.2%, 41.4%)",
amber9: "hsl(42, 100%, 62.0%)",
amber10: "hsl(43, 100%, 64.0%)",
amber11: "hsl(43, 100%, 65.0%)",
amber12: "hsl(41, 100%, 85.0%)",
};

export const orangeDark = {
orange1: "hsl(30, 70.0%, 7.2%)",
orange2: "hsl(29, 81.4%, 8.4%)",
orange3: "hsl(26, 68.4%, 12.0%)",
orange4: "hsl(25, 64.6%, 14.9%)",
orange5: "hsl(25, 64.3%, 17.8%)",
orange6: "hsl(25, 65.7%, 21.9%)",
orange7: "hsl(25, 67.7%, 29.4%)",
orange8: "hsl(25, 70.3%, 44.9%)",
orange9: "hsl(24, 94.0%, 50.0%)",
orange10: "hsl(24, 100%, 58.5%)",
orange11: "hsl(24, 100%, 70.0%)",
orange12: "hsl(30, 100%, 88.0%)",
};

export const grayDarkA = {
grayA1: "hsla(0, 0%, 100%, 0)",
grayA2: "hsla(0, 0%, 100%, 0.013)",
grayA3: "hsla(0, 0%, 100%, 0.069)",
grayA4: "hsla(0, 0%, 100%, 0.104)",
grayA5: "hsla(0, 0%, 100%, 0.134)",
grayA6: "hsla(0, 0%, 100%, 0.169)",
grayA7: "hsla(0, 0%, 100%, 0.216)",
grayA8: "hsla(0, 0%, 100%, 0.312)",
grayA9: "hsla(0, 0%, 100%, 0.372)",
grayA10: "hsla(0, 0%, 100%, 0.455)",
grayA11: "hsla(0, 0%, 100%, 0.662)",
grayA12: "hsla(0, 0%, 100%, 0.926)",
};

export const mauveDarkA = {
mauveA1: "hsla(300, 100%, 43.9%, 0.005)",
mauveA2: "hsla(300, 99.7%, 69.7%, 0.026)",
mauveA3: "hsla(288, 96.3%, 88.3%, 0.087)",
mauveA4: "hsla(280, 96.6%, 90.3%, 0.126)",
mauveA5: "hsla(280, 98.8%, 92.4%, 0.156)",
mauveA6: "hsla(274, 98.7%, 92.9%, 0.195)",
mauveA7: "hsla(270, 98.6%, 93.6%, 0.247)",
mauveA8: "hsla(258, 98.4%, 94.3%, 0.351)",
mauveA9: "hsla(251, 98.3%, 94.7%, 0.416)",
mauveA10: "hsla(251, 99.3%, 95.6%, 0.498)",
mauveA11: "hsla(253, 98.2%, 97.4%, 0.693)",
mauveA12: "hsla(240, 82.2%, 99.5%, 0.936)",
};

export const slateDarkA = {
slateA1: "hsla(240, 92.7%, 53.0%, 0.009)",
slateA2: "hsla(240, 92.5%, 72.7%, 0.031)",
slateA3: "hsla(230, 99.3%, 87.0%, 0.091)",
slateA4: "hsla(223, 97.1%, 88.8%, 0.126)",
slateA5: "hsla(223, 99.0%, 91.1%, 0.156)",
slateA6: "hsla(218, 97.1%, 91.5%, 0.191)",
slateA7: "hsla(210, 97.4%, 91.7%, 0.243)",
slateA8: "hsla(202, 97.3%, 93.4%, 0.334)",
slateA9: "hsla(219, 97.6%, 93.2%, 0.412)",
slateA10: "hsla(217, 98.3%, 94.7%, 0.494)",
slateA11: "hsla(218, 98.5%, 96.8%, 0.693)",
slateA12: "hsla(220, 87.4%, 99.3%, 0.936)",
};

export const sageDarkA = {
sageA1: "hsla(126, 100%, 43.9%, 0.005)",
sageA2: "hsla(150, 92.7%, 53.0%, 0.018)",
sageA3: "hsla(150, 95.5%, 88.9%, 0.074)",
sageA4: "hsla(156, 93.3%, 90.4%, 0.109)",
sageA5: "hsla(150, 94.8%, 94.0%, 0.139)",
sageA6: "hsla(156, 92.8%, 93.9%, 0.174)",
sageA7: "hsla(156, 92.4%, 95.3%, 0.226)",
sageA8: "hsla(156, 91.7%, 96.6%, 0.317)",
sageA9: "hsla(157, 99.8%, 93.3%, 0.381)",
sageA10: "hsla(158, 96.8%, 95.2%, 0.464)",
sageA11: "hsla(158, 98.1%, 97.6%, 0.667)",
sageA12: "hsla(150, 88.0%, 99.5%, 0.927)",
};

export const oliveDarkA = {
oliveA1: "hsla(120, 100%, 43.9%, 0.005)",
oliveA2: "hsla(120, 90.5%, 63.9%, 0.018)",
oliveA3: "hsla(120, 94.1%, 91.6%, 0.074)",
oliveA4: "hsla(120, 91.8%, 92.2%, 0.109)",
oliveA5: "hsla(120, 94.8%, 94.0%, 0.139)",
oliveA6: "hsla(120, 91.2%, 95.1%, 0.174)",
oliveA7: "hsla(120, 97.5%, 96.4%, 0.221)",
oliveA8: "hsla(120, 91.7%, 96.6%, 0.317)",
oliveA9: "hsla(106, 99.8%, 93.3%, 0.381)",
oliveA10: "hsla(104, 96.8%, 95.2%, 0.464)",
oliveA11: "hsla(105, 100%, 97.7%, 0.671)",
oliveA12: "hsla(120, 88.0%, 99.5%, 0.927)",
};

export const sandDarkA = {
sandA1: "hsla(60, 98.4%, 4.7%, 0.084)",
sandA2: "hsla(60, 99.4%, 84.8%, 0.013)",
sandA3: "hsla(60, 85.5%, 93.4%, 0.070)",
sandA4: "hsla(60, 97.7%, 96.1%, 0.104)",
sandA5: "hsla(45, 94.8%, 94.0%, 0.139)",
sandA6: "hsla(45, 91.2%, 95.1%, 0.174)",
sandA7: "hsla(48, 92.4%, 95.3%, 0.226)",
sandA8: "hsla(43, 95.9%, 95.5%, 0.321)",
sandA9: "hsla(47, 98.1%, 95.2%, 0.377)",
sandA10: "hsla(47, 96.1%, 96.0%, 0.464)",
sandA11: "hsla(52, 98.1%, 97.6%, 0.667)",
sandA12: "hsla(60, 88.0%, 99.5%, 0.927)",
};

export const tomatoDarkA = {
tomatoA1: "hsla(0, 100%, 49.3%, 0.022)",
tomatoA2: "hsla(0, 100%, 49.8%, 0.074)",
tomatoA3: "hsla(9, 100%, 49.9%, 0.152)",
tomatoA4: "hsla(9, 99.5%, 52.7%, 0.204)",
tomatoA5: "hsla(9, 99.5%, 53.8%, 0.256)",
tomatoA6: "hsla(9, 99.6%, 54.6%, 0.334)",
tomatoA7: "hsla(9, 99.7%, 55.0%, 0.477)",
tomatoA8: "hsla(10, 99.9%, 54.2%, 0.771)",
tomatoA9: "hsla(10, 99.9%, 59.5%, 0.888)",
tomatoA10: "hsla(10, 99.8%, 66.8%, 0.953)",
tomatoA11: "hsla(10, 100%, 72.0%, 1)",
tomatoA12: "hsla(10, 99.7%, 90.4%, 0.983)",
};

export const redDarkA = {
redA1: "hsla(0, 100%, 49.0%, 0.031)",
redA2: "hsla(354, 100%, 49.8%, 0.074)",
redA3: "hsla(353, 99.3%, 55.9%, 0.152)",
redA4: "hsla(352, 99.8%, 56.5%, 0.208)",
redA5: "hsla(354, 99.3%, 57.5%, 0.265)",
redA6: "hsla(354, 99.8%, 57.5%, 0.351)",
redA7: "hsla(354, 99.7%, 56.6%, 0.503)",
redA8: "hsla(358, 99.8%, 56.1%, 0.806)",
redA9: "hsla(358, 99.8%, 65.3%, 0.888)",
redA10: "hsla(359, 99.9%, 70.9%, 0.944)",
redA11: "hsla(358, 100%, 76.1%, 1)",
redA12: "hsla(350, 100%, 91.0%, 1)",
};

export const rubyDarkA = {
rubyA1: "hsla(0, 100%, 49.0%, 0.031)",
rubyA2: "hsla(351, 100%, 50.0%, 0.078)",
rubyA3: "hsla(348, 99.4%, 54.6%, 0.152)",
rubyA4: "hsla(348, 99.5%, 56.5%, 0.204)",
rubyA5: "hsla(347, 99.5%, 56.9%, 0.256)",
rubyA6: "hsla(348, 99.8%, 57.6%, 0.338)",
rubyA7: "hsla(348, 99.8%, 57.0%, 0.494)",
rubyA8: "hsla(348, 99.9%, 55.4%, 0.797)",
rubyA9: "hsla(348, 99.8%, 64.8%, 0.888)",
rubyA10: "hsla(349, 99.9%, 70.7%, 0.944)",
rubyA11: "hsla(348, 100%, 76.1%, 1)",
rubyA12: "hsla(340, 99.7%, 91.3%, 0.996)",
};

export const crimsonDarkA = {
crimsonA1: "hsla(354, 100%, 49.3%, 0.022)",
crimsonA2: "hsla(338, 100%, 49.8%, 0.074)",
crimsonA3: "hsla(335, 99.8%, 54.7%, 0.143)",
crimsonA4: "hsla(336, 99.4%, 56.6%, 0.191)",
crimsonA5: "hsla(336, 99.8%, 57.8%, 0.247)",
crimsonA6: "hsla(336, 99.3%, 58.1%, 0.330)",
crimsonA7: "hsla(336, 99.9%, 57.5%, 0.485)",
crimsonA8: "hsla(336, 99.8%, 54.4%, 0.793)",
crimsonA9: "hsla(336, 99.9%, 62.7%, 0.905)",
crimsonA10: "hsla(339, 99.8%, 70.1%, 0.953)",
crimsonA11: "hsla(341, 100%, 76.1%, 1)",
crimsonA12: "hsla(330, 99.3%, 91.6%, 0.992)",
};

export const pinkDarkA = {
pinkA1: "hsla(331, 100%, 49.0%, 0.031)",
pinkA2: "hsla(319, 100%, 49.8%, 0.074)",
pinkA3: "hsla(318, 98.8%, 55.9%, 0.135)",
pinkA4: "hsla(319, 99.4%, 59.0%, 0.178)",
pinkA5: "hsla(319, 99.8%, 60.9%, 0.221)",
pinkA6: "hsla(319, 99.8%, 61.2%, 0.299)",
pinkA7: "hsla(320, 99.9%, 61.9%, 0.433)",
pinkA8: "hsla(322, 100%, 61.1%, 0.710)",
pinkA9: "hsla(322, 99.8%, 64.2%, 0.823)",
pinkA10: "hsla(324, 99.7%, 70.2%, 0.884)",
pinkA11: "hsla(325, 99.6%, 76.8%, 0.975)",
pinkA12: "hsla(326, 99.3%, 91.2%, 0.992)",
};

export const plumDarkA = {
plumA1: "hsla(300, 100%, 49.3%, 0.022)",
plumA2: "hsla(300, 100%, 49.7%, 0.061)",
plumA3: "hsla(300, 98.6%, 59.2%, 0.122)",
plumA4: "hsla(298, 99.3%, 62.9%, 0.165)",
plumA5: "hsla(297, 99.7%, 65.0%, 0.208)",
plumA6: "hsla(296, 99.7%, 66.9%, 0.273)",
plumA7: "hsla(295, 100%, 68.6%, 0.394)",
plumA8: "hsla(292, 99.7%, 69.7%, 0.637)",
plumA9: "hsla(292, 99.7%, 68.6%, 0.702)",
plumA10: "hsla(291, 99.8%, 74.2%, 0.784)",
plumA11: "hsla(290, 99.7%, 80.0%, 0.914)",
plumA12: "hsla(300, 99.1%, 93.4%, 0.953)",
};

export const purpleDarkA = {
purpleA1: "hsla(278, 100%, 49.3%, 0.022)",
purpleA2: "hsla(283, 100%, 49.5%, 0.070)",
purpleA3: "hsla(282, 98.8%, 59.8%, 0.148)",
purpleA4: "hsla(281, 99.0%, 62.4%, 0.200)",
purpleA5: "hsla(280, 99.7%, 64.2%, 0.247)",
purpleA6: "hsla(278, 99.2%, 65.7%, 0.317)",
purpleA7: "hsla(276, 99.7%, 67.5%, 0.442)",
purpleA8: "hsla(272, 100%, 68.8%, 0.684)",
purpleA9: "hsla(272, 99.7%, 68.7%, 0.754)",
purpleA10: "hsla(271, 99.7%, 72.6%, 0.819)",
purpleA11: "hsla(270, 99.6%, 79.6%, 0.975)",
purpleA12: "hsla(275, 99.1%, 93.3%, 0.979)",
};

export const violetDarkA = {
violetA1: "hsla(240, 100%, 49.0%, 0.031)",
violetA2: "hsla(255, 98.3%, 51.9%, 0.083)",
violetA3: "hsla(255, 99.8%, 62.8%, 0.169)",
violetA4: "hsla(252, 99.0%, 65.8%, 0.226)",
violetA5: "hsla(253, 99.1%, 67.2%, 0.278)",
violetA6: "hsla(253, 100%, 68.5%, 0.355)",
violetA7: "hsla(251, 99.5%, 69.8%, 0.490)",
violetA8: "hsla(250, 99.7%, 70.8%, 0.728)",
violetA9: "hsla(252, 99.7%, 70.0%, 0.793)",
violetA10: "hsla(253, 99.9%, 73.5%, 0.853)",
violetA11: "hsla(255, 100%, 80.0%, 1)",
violetA12: "hsla(249, 99.5%, 93.5%, 0.996)",
};

export const irisDarkA = {
irisA1: "hsla(240, 100%, 50.0%, 0.039)",
irisA2: "hsla(243, 99.8%, 54.7%, 0.091)",
irisA3: "hsla(244, 98.8%, 63.5%, 0.174)",
irisA4: "hsla(242, 99.3%, 66.5%, 0.230)",
irisA5: "hsla(241, 99.4%, 68.5%, 0.282)",
irisA6: "hsla(240, 99.4%, 69.3%, 0.360)",
irisA7: "hsla(240, 99.5%, 70.6%, 0.490)",
irisA8: "hsla(240, 99.7%, 71.9%, 0.728)",
irisA9: "hsla(240, 99.8%, 70.6%, 0.823)",
irisA10: "hsla(241, 99.8%, 73.7%, 0.875)",
irisA11: "hsla(242, 100%, 81.0%, 1)",
irisA12: "hsla(242, 99.5%, 93.9%, 0.996)",
};

export const indigoDarkA = {
indigoA1: "hsla(240, 100%, 49.5%, 0.035)",
indigoA2: "hsla(232, 100%, 50.0%, 0.091)",
indigoA3: "hsla(228, 99.8%, 56.8%, 0.182)",
indigoA4: "hsla(228, 99.2%, 59.5%, 0.239)",
indigoA5: "hsla(227, 99.5%, 59.9%, 0.295)",
indigoA6: "hsla(225, 99.6%, 60.9%, 0.373)",
indigoA7: "hsla(226, 99.6%, 62.2%, 0.516)",
indigoA8: "hsla(226, 100%, 63.4%, 0.788)",
indigoA9: "hsla(226, 99.9%, 63.4%, 0.853)",
indigoA10: "hsla(230, 99.7%, 69.5%, 0.897)",
indigoA11: "hsla(235, 100%, 80.0%, 1)",
indigoA12: "hsla(236, 99.5%, 93.5%, 0.996)",
};

export const blueDarkA = {
blueA1: "hsla(240, 100%, 49.5%, 0.035)",
blueA2: "hsla(227, 100%, 50.0%, 0.091)",
blueA3: "hsla(216, 100%, 50.0%, 0.169)",
blueA4: "hsla(214, 100%, 49.8%, 0.226)",
blueA5: "hsla(213, 99.8%, 50.5%, 0.286)",
blueA6: "hsla(212, 99.8%, 51.5%, 0.377)",
blueA7: "hsla(211, 99.8%, 51.4%, 0.546)",
blueA8: "hsla(211, 99.9%, 53.3%, 0.875)",
blueA9: "hsla(206, 100%, 50.0%, 1)",
blueA10: "hsla(206, 100%, 61.8%, 1)",
blueA11: "hsla(205, 100%, 71.0%, 1)",
blueA12: "hsla(206, 100%, 88.0%, 1)",
};

export const cyanDarkA = {
cyanA1: "hsla(223, 100%, 49.3%, 0.022)",
cyanA2: "hsla(207, 100%, 49.3%, 0.044)",
cyanA3: "hsla(197, 100%, 49.6%, 0.096)",
cyanA4: "hsla(194, 100%, 49.7%, 0.135)",
cyanA5: "hsla(193, 99.5%, 50.2%, 0.178)",
cyanA6: "hsla(192, 99.3%, 53.7%, 0.239)",
cyanA7: "hsla(192, 100%, 57.5%, 0.355)",
cyanA8: "hsla(192, 99.8%, 60.0%, 0.585)",
cyanA9: "hsla(190, 100%, 50.0%, 0.736)",
cyanA10: "hsla(190, 99.8%, 53.5%, 0.832)",
cyanA11: "hsla(190, 99.8%, 56.3%, 0.949)",
cyanA12: "hsla(190, 99.6%, 86.7%, 0.966)",
};

export const tealDarkA = {
tealA1: "hsla(120, 100%, 48.3%, 0.009)",
tealA2: "hsla(141, 100%, 49.0%, 0.031)",
tealA3: "hsla(161, 100%, 49.8%, 0.074)",
tealA4: "hsla(167, 100%, 49.7%, 0.109)",
tealA5: "hsla(167, 100%, 49.8%, 0.148)",
tealA6: "hsla(169, 99.2%, 52.5%, 0.200)",
tealA7: "hsla(171, 99.3%, 56.5%, 0.304)",
tealA8: "hsla(173, 99.9%, 59.0%, 0.498)",
tealA9: "hsla(173, 99.8%, 52.7%, 0.611)",
tealA10: "hsla(172, 99.9%, 50.8%, 0.706)",
tealA11: "hsla(170, 99.8%, 51.6%, 0.832)",
tealA12: "hsla(163, 99.4%, 85.9%, 0.936)",
};

export const jadeDarkA = {
jadeA1: "hsla(120, 100%, 43.9%, 0.005)",
jadeA2: "hsla(120, 100%, 49.0%, 0.031)",
jadeA3: "hsla(145, 100%, 49.8%, 0.074)",
jadeA4: "hsla(152, 100%, 49.7%, 0.109)",
jadeA5: "hsla(156, 100%, 49.8%, 0.148)",
jadeA6: "hsla(158, 99.1%, 54.5%, 0.200)",
jadeA7: "hsla(159, 99.3%, 58.4%, 0.304)",
jadeA8: "hsla(164, 99.9%, 59.0%, 0.498)",
jadeA9: "hsla(164, 99.9%, 60.2%, 0.602)",
jadeA10: "hsla(164, 99.8%, 58.3%, 0.702)",
jadeA11: "hsla(163, 99.8%, 56.3%, 0.832)",
jadeA12: "hsla(155, 99.4%, 85.9%, 0.936)",
};

export const greenDarkA = {
greenA1: "hsla(120, 100%, 43.9%, 0.005)",
greenA2: "hsla(120, 100%, 49.3%, 0.022)",
greenA3: "hsla(149, 100%, 49.5%, 0.070)",
greenA4: "hsla(154, 100%, 49.7%, 0.109)",
greenA5: "hsla(153, 98.9%, 53.1%, 0.148)",
greenA6: "hsla(155, 99.1%, 55.5%, 0.200)",
greenA7: "hsla(152, 99.3%, 59.1%, 0.304)",
greenA8: "hsla(151, 99.6%, 62.4%, 0.503)",
greenA9: "hsla(151, 99.6%, 62.4%, 0.607)",
greenA10: "hsla(151, 100%, 62.9%, 0.697)",
greenA11: "hsla(151, 99.9%, 63.5%, 0.823)",
greenA12: "hsla(144, 99.6%, 86.6%, 0.940)",
};

export const grassDarkA = {
grassA1: "hsla(120, 100%, 43.9%, 0.005)",
grassA2: "hsla(120, 100%, 49.3%, 0.022)",
grassA3: "hsla(139, 97.9%, 54.2%, 0.070)",
grassA4: "hsla(137, 99.8%, 60.3%, 0.104)",
grassA5: "hsla(138, 99.8%, 62.9%, 0.143)",
grassA6: "hsla(136, 99.7%, 66.7%, 0.195)",
grassA7: "hsla(135, 99.7%, 68.4%, 0.299)",
grassA8: "hsla(131, 99.7%, 71.3%, 0.494)",
grassA9: "hsla(131, 99.5%, 69.2%, 0.620)",
grassA10: "hsla(131, 100%, 73.5%, 0.710)",
grassA11: "hsla(131, 99.8%, 76.6%, 0.797)",
grassA12: "hsla(120, 99.1%, 90.3%, 0.936)",
};

export const brownDarkA = {
brownA1: "hsla(0, 100%, 48.3%, 0.009)",
brownA2: "hsla(24, 100%, 50.0%, 0.026)",
brownA3: "hsla(25, 98.5%, 67.7%, 0.074)",
brownA4: "hsla(26, 97.8%, 70.6%, 0.109)",
brownA5: "hsla(26, 98.9%, 72.6%, 0.152)",
brownA6: "hsla(27, 99.6%, 74.5%, 0.208)",
brownA7: "hsla(28, 98.8%, 75.6%, 0.317)",
brownA8: "hsla(28, 99.6%, 76.4%, 0.533)",
brownA9: "hsla(28, 99.5%, 74.1%, 0.646)",
brownA10: "hsla(28, 99.6%, 77.7%, 0.715)",
brownA11: "hsla(28, 99.5%, 83.4%, 0.845)",
brownA12: "hsla(34, 99.7%, 91.7%, 0.944)",
};

export const bronzeDarkA = {
bronzeA1: "hsla(0, 100%, 43.9%, 0.005)",
bronzeA2: "hsla(15, 92.7%, 53.0%, 0.018)",
bronzeA3: "hsla(17, 99.6%, 78.8%, 0.065)",
bronzeA4: "hsla(18, 99.5%, 81.1%, 0.104)",
bronzeA5: "hsla(15, 98.2%, 82.8%, 0.139)",
bronzeA6: "hsla(18, 97.7%, 82.9%, 0.200)",
bronzeA7: "hsla(17, 99.4%, 84.2%, 0.299)",
bronzeA8: "hsla(18, 99.4%, 84.8%, 0.507)",
bronzeA9: "hsla(18, 99.1%, 84.3%, 0.594)",
bronzeA10: "hsla(19, 100%, 86.0%, 0.671)",
bronzeA11: "hsla(18, 99.9%, 88.7%, 0.814)",
bronzeA12: "hsla(21, 97.9%, 95.7%, 0.923)",
};

export const goldDarkA = {
goldA1: "hsla(45, 99.2%, 3.8%, 0.209)",
goldA2: "hsla(42, 100%, 50.0%, 0.013)",
goldA3: "hsla(50, 99.6%, 77.3%, 0.052)",
goldA4: "hsla(45, 97.6%, 81.5%, 0.087)",
goldA5: "hsla(42, 96.5%, 83.3%, 0.122)",
goldA6: "hsla(39, 98.3%, 84.3%, 0.178)",
goldA7: "hsla(39, 98.6%, 85.2%, 0.269)",
goldA8: "hsla(35, 99.4%, 85.7%, 0.468)",
goldA9: "hsla(36, 99.8%, 82.1%, 0.550)",
goldA10: "hsla(36, 99.7%, 85.0%, 0.628)",
goldA11: "hsla(35, 99.9%, 88.9%, 0.775)",
goldA12: "hsla(36, 98.3%, 96.7%, 0.901)",
};

export const skyDarkA = {
skyA1: "hsla(234, 100%, 49.5%, 0.035)",
skyA2: "hsla(215, 100%, 49.7%, 0.061)",
skyA3: "hsla(204, 100%, 50.0%, 0.117)",
skyA4: "hsla(201, 100%, 49.8%, 0.161)",
skyA5: "hsla(202, 99.8%, 52.8%, 0.208)",
skyA6: "hsla(200, 99.3%, 56.0%, 0.278)",
skyA7: "hsla(200, 99.8%, 58.6%, 0.403)",
skyA8: "hsla(200, 99.8%, 60.8%, 0.676)",
skyA9: "hsla(193, 99.9%, 74.4%, 0.996)",
skyA10: "hsla(192, 100%, 77.1%, 1)",
skyA11: "hsla(195, 100%, 66.1%, 1)",
skyA12: "hsla(192, 100%, 88.0%, 1)",
};

export const mintDarkA = {
mintA1: "hsla(120, 100%, 43.9%, 0.005)",
mintA2: "hsla(165, 100%, 49.0%, 0.031)",
mintA3: "hsla(174, 100%, 49.8%, 0.074)",
mintA4: "hsla(172, 100%, 49.8%, 0.113)",
mintA5: "hsla(172, 100%, 49.9%, 0.152)",
mintA6: "hsla(173, 100%, 50.0%, 0.208)",
mintA7: "hsla(173, 99.8%, 54.7%, 0.312)",
mintA8: "hsla(170, 99.6%, 59.2%, 0.516)",
mintA9: "hsla(167, 99.6%, 78.4%, 0.910)",
mintA10: "hsla(163, 99.5%, 80.5%, 0.949)",
mintA11: "hsla(167, 99.8%, 65.8%, 0.862)",
mintA12: "hsla(156, 99.7%, 89.9%, 0.957)",
};

export const limeDarkA = {
limeA1: "hsla(74, 99.8%, 4.7%, 0.709)",
limeA2: "hsla(114, 100%, 49.3%, 0.022)",
limeA3: "hsla(89, 100%, 50.0%, 0.065)",
limeA4: "hsla(84, 100%, 49.8%, 0.100)",
limeA5: "hsla(81, 98.9%, 53.0%, 0.135)",
limeA6: "hsla(80, 99.0%, 57.6%, 0.187)",
limeA7: "hsla(78, 99.5%, 60.9%, 0.282)",
limeA8: "hsla(75, 99.6%, 63.3%, 0.477)",
limeA9: "hsla(81, 99.8%, 70.5%, 0.927)",
limeA10: "hsla(75, 99.8%, 63.5%, 0.936)",
limeA11: "hsla(70, 99.9%, 58.0%, 0.836)",
limeA12: "hsla(80, 99.5%, 87.6%, 0.966)",
};

export const yellowDarkA = {
yellowA1: "hsla(0, 100%, 48.3%, 0.018)",
yellowA2: "hsla(17, 100%, 49.3%, 0.044)",
yellowA3: "hsla(36, 100%, 49.8%, 0.087)",
yellowA4: "hsla(41, 100%, 49.7%, 0.122)",
yellowA5: "hsla(44, 100%, 49.8%, 0.161)",
yellowA6: "hsla(46, 99.2%, 50.8%, 0.213)",
yellowA7: "hsla(47, 99.4%, 55.2%, 0.317)",
yellowA8: "hsla(50, 99.6%, 59.2%, 0.516)",
yellowA9: "hsla(53, 99.9%, 58.9%, 0.983)",
yellowA10: "hsla(53, 99.7%, 68.2%, 0.988)",
yellowA11: "hsla(55, 100%, 60.0%, 1)",
yellowA12: "hsla(53, 100%, 83.9%, 1)",
};

export const amberDarkA = {
amberA1: "hsla(0, 100%, 49.0%, 0.031)",
amberA2: "hsla(6, 100%, 49.4%, 0.057)",
amberA3: "hsla(24, 100%, 50.0%, 0.104)",
amberA4: "hsla(30, 100%, 50.0%, 0.143)",
amberA5: "hsla(33, 100%, 49.8%, 0.187)",
amberA6: "hsla(34, 99.6%, 53.1%, 0.256)",
amberA7: "hsla(35, 99.8%, 57.3%, 0.377)",
amberA8: "hsla(36, 99.9%, 60.3%, 0.628)",
amberA9: "hsla(42, 100%, 62.0%, 1)",
amberA10: "hsla(43, 100%, 63.9%, 1)",
amberA11: "hsla(43, 100%, 65.1%, 1)",
amberA12: "hsla(41, 100%, 85.1%, 1)",
};

export const orangeDarkA = {
orangeA1: "hsla(0, 100%, 49.0%, 0.031)",
orangeA2: "hsla(0, 100%, 50.0%, 0.065)",
orangeA3: "hsla(13, 100%, 49.7%, 0.122)",
orangeA4: "hsla(20, 100%, 50.0%, 0.169)",
orangeA5: "hsla(24, 100%, 50.0%, 0.221)",
orangeA6: "hsla(25, 99.8%, 51.4%, 0.299)",
orangeA7: "hsla(25, 99.8%, 54.7%, 0.442)",
orangeA8: "hsla(25, 99.8%, 57.3%, 0.741)",
orangeA9: "hsla(24, 99.9%, 51.4%, 0.966)",
orangeA10: "hsla(24, 100%, 58.4%, 1)",
orangeA11: "hsla(24, 100%, 70.0%, 1)",
orangeA12: "hsla(30, 100%, 88.0%, 1)",
};
https://cdn.jsdelivr.net/npm/@bndynet/dator/dist/index.min.js

Tools

iTerm2 on MacOS

How to install

iTerm2

brew cask install iterm2

Or, if you do not have homebrew (you should ;)): Download and install iTerm2

iTerm2 has better color fidelity than the built in Terminal, so your themes will look better.

Get the iTerm color settings

Just save it somewhere and open the file(s). The color settings will be imported into iTerm2. Apply them in iTerm through iTerm → preferences → profiles → colors → load presets. You can create a different profile other than Default if you wish to do so.

Oh My Zsh

More info here: https://github.com/robbyrussell/oh-my-zsh

Install with curl

sh -c “$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)”

When the installation is done, edit ~/.zshrc and set ZSH_THEME="agnoster"

Powerlevel10k

Note that P9k had a substantial impact on CLI UX, and its legacy is now continued by P10k.

https://github.com/romkatv/powerlevel10k

Fonts with icon

Patched Fonts: https://github.com/ryanoasis/nerd-fonts

Set this font in iTerm2 (14px is my personal preference) (iTerm → Preferences → Profiles → Text → Change Font).

Restart iTerm2 for all changes to take effect.

The font SourceCodePro+Powerline+Awesome+Regular will be better to show the icons include git.

Further tweaking

Things like

  • auto suggestions
  • word jumping with arrow keys / natural text editing
  • shorter prompt style
  • syntax highlighting
  • visual studio code config

can be found in the section below.

Auto suggestions (for Oh My Zsh)

Just follow these steps: https://github.com/zsh-users/zsh-autosuggestions/blob/master/INSTALL.md#oh-my-zsh

If the auto suggestions do not appear to show, it could be a problem with your color scheme. Under “iTerm → Preferences → Profiles → Colors tab”, check the value of Black Bright, that is the color your auto suggestions will have. It will be displayed on top of the Background color. If there is not enough contrast between the two, you won’t see the suggestions even if they’re actually there..

Enable word jumps and word deletion, aka natural text selection

By default, word jumps (option + → or ←) and word deletions (option + backspace) do not work. To enable these, go to “iTerm → Preferences → Profiles → Keys → Load Preset… → Natural Text Editing → Boom! Head explodes”

Custom prompt styles

By default, your prompt will now show “user@hostname” in the prompt. This will make your prompt rather bloated. Optionally set DEFAULT_USER in ~/.zshrc to your regular username (these must match) to hide the “user@hostname” info when you’re logged in as yourself on your local machine. You can get your exact username value by executing whoami in the terminal.

For further customisation of your prompt, you can follow a great guide here: https://code.tutsplus.com/tutorials/how-to-customize-your-command-prompt–net-24083

Syntax highlighting

brew install zsh-syntax-highlighting

If you do not have or do not like homebrew, follow the installation instructions instead.

After installation through homebrew, add

source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

to the end of your .zshrc file. After that, it’s best to restart your terminal. Sourcing your ~/.zshrc does not seem to work well with this plugin.

Visual Studio Code config

Installing a patched font will mess up the integrated terminal in VS Code unless you use the proper settings. You’ll need to go to settings (CMD + ,) and add or edit the following values:

  • for Source Code Pro: "terminal.integrated.fontFamily": "Source Code Pro for Powerline"
  • for Meslo: "terminal.integrated.fontFamily": "Meslo LG M for Powerline"
  • for other fonts you’ll need to check the font name in Font Book.

You can also set the fontsize e.g.: "terminal.integrated.fontSize": 14

Font with icons

Copy patched fonts files to ~/.fonts and install all fonts, and set the installed font for iTerm2. Then add below code to first line of ~/.zshrc. Restart your iTerm2.

source ~/.fonts/*.sh POWERLEVEL9K_MODE="awesome-patched"

Microsoft Terminal

Useful Commands

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Set-ExecutionPolicy -Scope CurrentUser Bypass

oh-my-posh

Install from local

  1. Download the nupkg file from https://www.powershellgallery.com/packages?q=oh-my-posh

  2. Run below command to register repository and install

    Register-PSRepository -Name 'oh-my-posh' -SourceLocation 'your-nupkg-folder'
    Install-Module oh-my-posh -Scope CurrentUser -Repository oh-my-posh
    Import-Module oh-my-posh

Commands

Get-PoshThemes
Get-PoshThemes -list

Vim

Replace

**:s/foo/bar/g**Find each occurrence of 'foo' (in the current line only), and replace it with 'bar'.

**:%s/foo/bar/g**Find each occurrence of 'foo' (in all lines), and replace it with 'bar'.

**:%s/foo/bar/gc**Change each 'foo' to 'bar', but ask for confirmation first.

**:%s/\<foo\>/bar/gc**Change only whole words exactly matching 'foo' to 'bar'; ask for confirmation.

**:%s/foo/bar/gci**Change each 'foo' (case insensitive due to the i flag) to 'bar'; ask for confirmation.:%s/foo\c/bar/gc is the same because \c makes the search case insensitive.This may be wanted after using :set noignorecase to make searches case sensitive (the default).

**:%s/foo/bar/gcI**Change each 'foo' (case sensitive due to the I flag) to 'bar'; ask for confirmation.:%s/foo\C/bar/gc is the same because \C makes the search case sensitive.This may be wanted after using :set ignorecase to make searches case insensitive.

IntelliJ IDEA

Auto-build

  • Preferences → Build, Execute, Deployment → Compiler → [x] Build project automatically

  • Command + Shift + A (on MacOS) → Type "Registry" → [x] compiler.automake.allow.when.app.running

  • Add dependency to pom.xml

    \<dependency\>
    \<groupId\>org.springframework.boot\</groupId\>
    \<artifactId\>spring-boot-devtools\</artifactId\>
    \<version\>2.5.0\</version\>
    \<optional\>true\</optional\>
    \</dependency\>

Git

Download and Installation

https://git-scm.com/downloads

Commit

Commit with empty message

git config --global alias.nccommit "commit -a --allow-empty-message -m ''"
git nccommit

Change commit info

git commit --amend --author="Bendy Zhang <zbatbndy.net>" --no-edit

git commit --amend --date="Sun Aug 7 13:05 2022 +0800" --no-edit
GIT_COMMITTER_DATE="Sun Aug 7 14:10 2022 +0800" git commit --amend --no-edit

Branch

New branch from remote

git checkout -b localBranch origin/remote_branch

Push changes to new branch

git checkout -b newBranch
git push -u origin newBranch # -u is short for `-set-upstream`

Bind local branch with remote branch

git branch --set-upstream-to=origin/remote_branch your_branch

Remove multiple branches

git branch -d `git branch --list '3.2.*'`

Reset local branch to remote branch

git fetch origin && git reset --hard origin/master

Merge

  • git merge master
  • git merge master --squash // merge master branch to current branch and keep changes without commits
  • git rebase master
  • git rebase --interactive --root // squash all of your commits down to the root commit
  • git cherry-pick <commitid> // pick up specified commit to current branch, if conflicts, resolve it and git add . && git cherry-pick --continue
  • git cherry-pick A..B The commit A should be older than B which will not include A. If requires A, please use A^~B

Undoing Things

  • Remove from cache

    git rm -r --cached \<your-folder\>
  • Overwrite commit

    git commit -m 'initial commit'
    git add .
    git commit --amend
  • Unstaging a Staged File

    • git reset to unstage all files, they will be removed from the staging area back to your working directory.
    • git reset HEAD <file>...
    • git checkout -- <file>... to discard changes in working directory
    • git restore --staged <file>... to unstage into change list
  • Unmodifying a Modified File

    • git checkout -- <path>
    • git checkout -- . to discard your entire working directory, head back to the root of your project.
    • git reset --soft <commit> to unstage commits softly, git reset --soft HEAD~1 to unstage your last commit
  • View all logs git reflog

  • Remove untracked files & directories

    • Print out files and directories which will be removed (dry run), run git clean -n -d
    • To remove directories, run git clean -f -d or git clean -fd
    • To remove ignored files, run git clean -f -X or git clean -fX
    • To remove ignored and non-ignored files, run git clean -f -x or git clean -fx

Stash Usage

git stash // stash current changes
git stash pop // pop last stash and remove from history
git stash save 'message' // stash current changes with message
git stash list // show all stashes
git stash apply <id> // apply <id> stash

Tag

# create tagg
git tag -a <tagname> -m '<tagcomment>'

# show tags
git tag

# show specified tag
git show <tagname>

# push to remote
git push origin <tagname>

# push all tags to remote
git push origin --tags

# checkout tag
git checkout <tagname>

# delete tag
git tag -d <tagname>

# delete remote tag
git push origin <tagname>
git push --delete origin tagname

Clone Subfolder

By following ways, you will get the folder fastest that you want.

git clone -n --depth=1 --filter=blob:none git@github.com:bndynet/bndynet.git
cd bndynet
git sparse-checkout set "/_data/*" --no-cone
git checkout

Or

git init
git sparse-checkout init --no-cone
git sparse-checkout set "/_data/*" --no-cone
git remote add origin git@github.com:bndynet/bndynet.git
git config core.sparsecheckout true
echo "partialclonefilter = blob:none" >> .git/config
git pull --depth=1 origin master

Subtree

// with squash, this repo history commits will not be merged into parent repo
git subtree add --prefix=subfolder https://github.com/subrepo.git master --squash
git subtree push --prefix=dist https://github.com/subrepo.git gh-pages

Workflow

with Git: Fork, Branching, Commits, and Pull Request

  1. Fork a repo.

  2. Clone the forked project to your local machine:

    git clone git@github.com:USERNAME/<forked_repo>

  3. Configure remotes:

    git remote add upstream git://github.com/<origin_repo>

  4. Create a branch for new feature:

    git checkout -b my-new-branch

  5. Develop on my-new-branch branch only, but Do not merge my-new-branch branch to the your master (as it should stay equal to upstream master)!!

  6. Commit changes to my-new-branch branch:

    git add .
    git commit -m "commit message"
  7. Push branch to GitHub, to allow your mentor to review your code:

    $ git push origin my-new-branch

  8. Repeat steps 5-7 till development is complete.

  9. Fetch upstream changes that were done by other contributors:

    $ git fetch upstream

  10. Update local master:

git checkout master
git pull upstream master

ATTENTION: any time you lost of track of your code – launch “gitk —all” in source folder, UI application come up that will show all branches and history in pretty view, explanation.

  1. Rebase my-new-branch branch on top of the upstream master:
git checkout my-new-branch
git rebase master
  1. In the process of the rebase, it may discover conflicts. In that case it will stop and allow you to fix the conflicts. After fixing conflicts, use git add . to update the index with those contents, and then just run:
git rebase --continue
  1. Push branch to GitHub (with all your final changes and actual code):

We forcing changes to your issue branch(our sand box) is not common branch, and rebasing means recreation of commits so no way to push without force. NEVER force to common branch.

git push origin my-new-branch --force
  1. Created build for testing and send it to any mentor for testing.
  2. Only after all testing is done – Send a Pull Request.

Attention: Please recheck that in your pull request you send only your changes, and no other changes!! Check it by command: git diff my-new-branch upstream/master

Submodule

git submodule add submodule-repo path
git submodule update --init --recursive
git rm submodule-name
git rm submodule-name --cached
// update submodule to master
cd submodule_folder
git checkout master
cd ../
git add .
git commit -m ''

Common Alias

git config --global alias.s "status"
git config --global alias.a "\!git add . && git status"
git config --global alias.au "\!git add -u . && git status"
git config --global alias.aa "\!git add . && git add -u . && git status"
git config --global alias.b "branch"
git config --global alias.c "commit"
git config --global alias.cm "commit -m"
git config --global alias.ca "commit --amend"
git config --global alias.ac "\!git add . && git commit"
git config --global alias.acm "\!git add . && git commit -m"
git config --global alias.l "log --graph --all --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset'"
git config --global alias.ll "log --stat --abbrev-commit"
git config --global alias.lg "log --color --graph --pretty=format:'%C(bold white)%h%Creset -%C(bold green)%d%Creset %s %C(bold green)(%cr)%Creset %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative"
git config --global alias.llg "log --color --graph --pretty=format:'%C(bold white)%H %d%Creset%n%s%n%+b%C(bold blue)%an <%ae>%Creset %C(bold green)%cr (%ci)' --abbrev-commit"
git config --global alias.d "diff"
git config --global alias.master "checkout master"
git config --global alias.main "checkout main"
git config --global alias.alias "\!git config --list | grep 'alias\.' | sed 's/alias\.\([^=]*\)=\(.*\)/\1\ => \2/' | sort"
git config --global alias.hi "\!echo 'Hello World!'"
git config --global --unset alias.hi

Log

$ git log --follow --pretty=oneline things/text.txt view log of the renamed file
git log --author="zb@bndy.net" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -

Output:

added lines: 545140, removed lines: 1152979, total lines: -607839
git log --pretty=format:%h,%an,%ae,%ad,%s --name-only

Output:

acb533d,Bendy Zhang,zb@bndy.net,Sun Jul 12 16:17:14 2020 +0800,commit message
.gitignore
README.md
  • -since=<date>, --after=<date> Show commits more recent than a specific date.
  • -until=<date>, --before=<date> Show commits older than a specific date.
  • --author=<pattern>, --committer=<pattern> Limit the commits output to ones with author/committer header lines that match the specified pattern (regular expression). With more than one -author=<pattern>, commits whose author matches any of the given patterns are chosen (similarly for multiple -committer=<pattern>).

Pretty Formats

https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emHem

QA

.gitignore can not ignore a file.

Need run following command to remove all files from the repository and add them back.

git rm -rf --cached .
git add .

Tmux

Installing

bookmark

Script Examples

Start a main pane with other panes

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ +
+ +
+ main +
+ +
+ +
+ +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ a + b + c +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tmux new-session -d -s sessionName; \
tmux split-window -v; \
tmux send-keys "echo a" Enter; \
tmux split-window -v; \
tmux send-keys "echo b" Enter; \
tmux split-window -v; \
tmux send-keys "echo c" Enter; \
tmux select-layout main-horizontal; \
tmux select-pane -t 0; \
tmux a;

Start there same size panes

tmux new-session -d -s sessionName; \
tmux send-keys "echo a" Enter; \
tmux split-window -v; \
tmux send-keys "echo b" Enter; \
tmux split-window -v; \
tmux send-keys "echo c" Enter; \
tmux select-layout even-horizontal; \
tmux a;

Others

# Layouts
tmux select-layout main-vertical
tmux select-layout main-horizontal
tmux select-layout even-vertical
tmux select-layout even-horizontal

# Enable pane border labels
tmux set pane-border-status top

#Enable Mouse
tmux set mouse on

Oh my tmux

https://github.com/gpakosz/.tmux

Do not forget to source your config file via:

tmux source-file ~/.tmux.conf.local

ngrok

Installation

bookmark

Commands

Run an instance

# start in background and forward to https://localhost:3000
nohup ./ngrok http https://localhost:3000 &

Get Tunnels

curl \
-X GET \
-H "Authorization: Bearer {API_KEY}" \
-H "Ngrok-Version: 2" \
https://api.ngrok.com/tunnels

Response

{
"tunnels": [
{
"id": "tn_2NTVGqGpa5w2LExRzILEksu7FOa",
"public_url": "https://b8ad9cf4eff6.ngrok.paid",
"started_at": "2023-03-24T19:59:25Z",
"proto": "https",
"region": "us",
"tunnel_session": {
"id": "ts_2NTVGmVw5yMzr02ZZzlX4VC6b6L",
"uri": "https://api.ngrok.com/tunnel_sessions/ts_2NTVGmVw5yMzr02ZZzlX4VC6b6L"
},
"endpoint": {
"id": "ep_2NTVGqGpa5w2LExRzILEksu7FOa",
"uri": "https://api.ngrok.com/endpoints/ep_2NTVGqGpa5w2LExRzILEksu7FOa"
},
"forwards_to": "http://localhost:80"
},
{
"id": "tn_2NTVGiqsZQ8EGqJ7HgcysTvGPAN",
"public_url": "://:0",
"started_at": "2023-03-24T19:59:24Z",
"region": "us",
"tunnel_session": {
"id": "ts_2NTVGfpxBknN9h3rpLqPiV7NiAw",
"uri": "https://api.ngrok.com/tunnel_sessions/ts_2NTVGfpxBknN9h3rpLqPiV7NiAw"
},
"labels": {
"baz": "qux",
"foo": "bar"
},
"forwards_to": "http://localhost:80"
}
],
"uri": "https://api.ngrok.com/tunnels",
"next_page_uri": null
}

Get Tunnel

curl \
-X GET \
-H "Authorization: Bearer {API_KEY}" \
-H "Ngrok-Version: 2" \
https://api.ngrok.com/tunnels/tn_2NTVGqGpa5w2LExRzILEksu7FOa

Response

{
"id": "tn_2NTVGqGpa5w2LExRzILEksu7FOa",
"public_url": "https://b8ad9cf4eff6.ngrok.paid",
"started_at": "2023-03-24T19:59:25Z",
"proto": "https",
"region": "us",
"tunnel_session": {
"id": "ts_2NTVGmVw5yMzr02ZZzlX4VC6b6L",
"uri": "https://api.ngrok.com/tunnel_sessions/ts_2NTVGmVw5yMzr02ZZzlX4VC6b6L"
},
"endpoint": {
"id": "ep_2NTVGqGpa5w2LExRzILEksu7FOa",
"uri": "https://api.ngrok.com/endpoints/ep_2NTVGqGpa5w2LExRzILEksu7FOa"
},
"forwards_to": "http://localhost:80"
}

nodejs and npm

Installation on CentOS 7

curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash -
sudo yum install nodejs
node --version

Installation via NVM

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
nvm --version. # restart terminal
nvm install node

nvm install --lts
nvm install 8.12.0
nvm ls
nvm use 10.13.0
nvm alias default 10.13.0
nvm ls-remote

Mirrors

npm config set registry https://registry.npm.taobao.org # or
npm install -g cnpm --registry=https://registry.npm.taobao.org

or via alias:

alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"

# Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc

Install development tools

To be able to build native modules from npm we will need to install the development tools and libraries:

sudo yum install gcc-c++ make

NPM commands

npm start --prefix subapp # run `start` in subapp folder

# publish your package to npmjs
npm adduser
npm publish

Test package locally

Run the following commands to generate a package.

npm run build
npm pack --pack-destination ~ # it will pack all files under `files` section in package.json file

Above will output a file.

~/your-package-1.0.0.tgz

Install this package on your other project.

"dependencies": {
"your-package": "file:~/your-package-1.0.0.tgz"
}

and then install.

npm install

Alternatively, you can use the following way to debug in real-time.

cd ~/your-pkg-source
npm link // creates global link

cd ~/your-work
npm link your-pkg

You may also shortcut the two steps in one. For example, to do the above use-case in a shorter way:

cd ~/projects/your-project # go into the dir of your main project
npm link ../your-pkg-dir # link the dir of your dependency

Update all package versions

npm install -g npm-check-updates # global to install the tool
ncu # check updates
ncu -u # update the versions for all packages in package.json file
npm install

SSH

Log into server with SSH keys

Generate SSH keys

ssh-keygen -t rsa -b 4096

Your keys will be created at ~/.ssh/id_rsa.pub and ~/.ssh/id_rsa

Transfer Your Public Key to the Server

ssh-copy-id user@remote_server

If ssh-copy-id is unavailable, please

cat ~/.ssh/id_rsa.pub | ssh user@remote_server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Keycloak

Start

docker run -p 9900:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:13.0.1

New Client

  1. Add client: Client ID: foo, Access Type: confidential
  2. Go to Credentials tab, and get client secret.

OAuth2 Endpoints

Configurations for getting access token

OS

Linux

How to add swap on CentOS

Check the System for Swap Information

swapon -s
free -m

total used free shared buffers cached
Mem: 3953 315 3637 8 11 107
-/+ buffers/cache: 196 3756
Swap: 0 0 4095

Check Available Storage Space

df -h

Filesystem Size Used Avail Use% Mounted on
/dev/vda1 59G 1.5G 55G 3% /
devtmpfs 2.0G 0 2.0G 0% /dev
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 2.0G 8.3M 2.0G 1% /run
tmpfs 2.0G 0 2.0G 0% /sys/fs/cgroup

Create a Swap File

sudo dd if=/dev/zero of=/swapfile count=10240 bs=1MiB
ls -lh /swapfile

-rw-r--r-- 1 root root 4.0G Oct 30 11:00 /swapfile

Enable a Swap File

sudo chmod 600 /swapfile

ls -lh /swapfile
-rw------- 1 root root 4.0G Oct 30 11:00 /swapfile

sudo mkswap /swapfile
Setting up swapspace version 1, size = 4194300 KiB
no label, UUID=b99230bb-21af-47bc-8c37-de41129c39bf

sudo swapon /swapfile
swapon -s
Filename Type Size Used Priority
/swapfile file 4194300 0 -1

free -m
total used free shared buffers cached
Mem: 3953 315 3637 8 11 107
-/+ buffers/cache: 196 3756
Swap: 4095 0 4095

Make the Swap File Permanent

Edit the file with sudo privileges in your text editor:

sudo nano /etc/fstab

At the bottom of the file, you need to add a line that will tell the operating system to automatically use the swap file that you created:

/swapfile swap swap sw 0 0

User and sudo

How To create a new sudo user on CentOS

First create a new CentOS user account from the command line. For example, create the marlena user account, run:

adduser marlena

Set the password for marlena user by typing the following passwd command:

passwd marlena

A new user account was created. Verify it:

id marlena

In CentOS 8 Linux server all members of the wheel group have sudo access. So all you have to do is append user account to the wheel group using the usermod command command:

usermod -aG wheel marlena

User account marlena now have sudo privileges. Verify it by running the id command or grep command on /etc/passwd and /etc/group files:

id marlena
grep '^marlena' /etc/passwd
grep '^wheel' /etc/group

How to grant or add existing user account to sudo on CentOS

Below is to give sudo access to an existing user named vivek by adding the user to the wheel group:

usermod -aG wheel vivek
id vivek

Deleting a user account in CentOS 8

userdel -r {userName}

Service Operations on CentOS

Enable or disable service on OS start

$ systemctl disable httpd
rm '/etc/systemd/system/multi-user.target.wants/httpd.service'

$ systemctl status httpd
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service;
disabled
)
$ systemctl enable httpd
ln -s '/usr/lib/systemd/system/httpd.service' '/etc/systemd/system/multi-user.target.wants/httpd.service'

$ systemctl status httpd
httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service;
enabled
)

APT-GET

sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo vim /etc/apt/sources.list

Remove sources.list content using below instead.

deb http://mirrors.aliyun.com/ubuntu/ xenial main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main

deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main

deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates universe

deb http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security universe
sudo apt-get update
sudo apt-get -f install
sudo apt-get -y upgrade

Use aptitude instead of apt-get

sudo apt-get install aptitude

aptitude issues

W: Unable to read /etc/apt/preferences.d/ - DirectoryExists (2: No such file or directory)

To fix

mkdir /etc/apt/preferences.d
E: The method driver /usr/lib/apt/methods/https could not be found.

To fix

sudo wget http://archive.ubuntu.com/ubuntu/pool/universe/a/apt/apt-transport-https_1.6.1_all.debb
sudo dpkg -i apt-transport-https_1.6.1_all.deb

Commands for Terminal

Useful Aliases

Add following aliases in your ~/.bash_profile

alias ..='cd ..'
alias ...='cd ../../'
alias b='brew'
alias c='clear'
alias l='ls -lah'
alias su='sudo -i'
alias root='sudo -i'

How to parse JSON string in the terminal

Install jq via following link to parse the string.

bookmark

curl 'https://api.github.com/repos/jqlang/jq/commits?per_page=5' | jq '.[0]'

How to find the largest files or folders on a file system

To find the largest folders on a file system pass the -a option. This will change the behavior of du to write size counts for files as well as folders. Run the following as root to see the ten largest files or folders on a system. This can be useful if you are dealing with out-of-disk space issues on a system.

du -a / | sort -n -r | head -n 10
5351116 /
2462616 /usr
2153492 /home
2153472 /home/george
1571924 /usr/lib

How to sort by file or folder size

To sort by file size pass the output of du to sort and use the -n (numeric) and -r (reverse) options.

du ~/go | sort -n -r | less
170440 /home/george/go
132816 /home/george/go/src
74024 /home/george/go/src/github.com
57072 /home/george/go/src/golang.org

Open and close ports on RHEL 8 / CentOS 8

sudo firewall-cmd --zone=public --list-ports
sudo firewall-cmd --zone=public --permanent --add-port 8080/tcp
sudo firewall-cmd --reload

sudo firewall-cmd --zone=public --permanent --remove-port 8080/tcp

Compress Files

tar -xzf example.tgz # uncompress
tar --exclude='.DS_Store' --exclude='._*' -czf your-file.tgz folder/ # compress

Development on MacOS

Code Sign Invalid for Opening App

  • Option 1: Resign

    codesign --deep --force --sign "Developer ID Application: Your Name (Team ID)" /path/to/YourApp.app
  • Option 2: Use a temprary sign

    codesign --deep --force --sign - /path/to/YourApp.app

DevOps

Monitoring Tools

healthchecks

Running in Docker

docker run -d \
--name=hc \
-p 8010:8000 \
--restart unless-stopped \
-e DB=sqlite \
-e DB_NAME=/data/hc.sqlite \
-e DEBUG=False \
-e SITE_ROOT=http://10.224.59.43:8010 \
-v ~/healthchecks-data:/data \
healthchecks

docker exec -it hc /opt/healthchecks/manage.py createsuperuser

Uptime Kuma

Run in Docker

docker run -d \
--name uk \
--restart=unless-stopped \
-p 8010:3001 \
-v ~/uptime-kuma-data:/app/data \
louislam/uptime-kuma:1

CI and CD

Jenkins File Examples

Parameter Definitions

Scripted

properties([
parameters([
string(name: 'USERNAME', defaultValue: 'admin', description: 'Enter your username'),
booleanParam(name: 'ENABLE_FEATURE', defaultValue: false, description: 'Enable experimental feature'),
choice(name: 'DEPLOY_ENV', choices: 'dev\nqa\nprod', description: 'Choose deployment environment')
])
])

node {
echo "USERNAME: ${params.USERNAME}"
echo "ENABLE_FEATURE: ${params.ENABLE_FEATURE}"
echo "DEPLOY_ENV: ${params.DEPLOY_ENV}"
}

Declarative

pipeline {
agent any
parameters {
string(name: 'VERSION', defaultValue: '1.0.0', description: 'Version to deploy')
booleanParam(name: 'CONFIRM', defaultValue: true, description: 'Confirm the deployment')
choice(name: 'ENV', choices: ['staging', 'production'], description: 'Environment to deploy to')
}
stages {
stage('Print Params') {
steps {
echo "Version: ${params.VERSION}"
echo "Confirm: ${params.CONFIRM}"
echo "Environment: ${params.ENV}"
}
}
}
}

Parameter Usage

node {
print '✋' + params
print params.anything_to_undefined // output: null
if (params.MultiLines) {
params.MultiLines.split("\\r?\\n").each {
if (it.trim()) {
echo '✋-->' + it
}
}
}
// clean workspace
cleanWs()
try {
docker.image('node:20').inside('--network host') {
stage("Source code") {
checkout([
$class: 'GitSCM',
branches: [[name: '*/${Git_Branch}']],
userRemoteConfigs: [[
url: 'git@github.com:bndynet/typescript-lib-starter.git',
credentialsId: '${Cred_ID}'
]],
extensions: [
[$class: 'CloneOption', depth: 1, noTags: false, shallow: true],
[$class: 'CheckoutOption', timeout: 30]
]
])
}
stage('Prepare') {
sh """
echo "================================"
printenv | sort
echo "================================"
"""
sh 'npm i'
}
stage('Build') {
withEnv(['my_name=bing']) {
// TODO
sh 'Hi ${my_name}.'
}
}
stage('Test') {
if (params.Skip_Test != true) {
echo 'do test'
} else {
echo 'Skip test'
}
}
stage('Deploy') {
if (params.Skip_Deploy != true) {
// TODO
echo 'do deploy'
} else {
echo 'Skip deploy'
}
}
stage('Deploy to production') {
def toProdParams
def didTimeout = false
try {
timeout(time: 15, unit: 'SECONDS') {
// change to a convenient timeout for you
toProdParams = input(
id: 'deployToProd', message: 'Deploy to production?', parameters: [
choice(choices: ['prod-a.com/api', 'prod-b.com/api'], description: 'Backend endpoint to connect', name: 'Backend_Uri'),
booleanParam(defaultValue: false, description: 'Archive this package', name: 'Archive')
]
)
}
} catch (err) {
// timeout reached or input false
// def user = err.getCauses()[0].getUser() // required administrator
//if('SYSTEM' == user.toString()) { // SYSTEM means timeout.
didTimeout = true
//} else {
//toProd = false
//echo "Aborted by: [${user}]"
//}
}
echo '✋Your choice: ' + toProdParams // null if timeout
if (toProdParams) {
echo toProdParams.Backend_Uri
}
if (didTimeout) {
// do something on timeout
echo "no input was received before timeout"
} else if (toProdParams) {
// do something
echo "Deployed to production."
} else {
// do something else
echo "Skip deployment to production."
}
}
}
} catch (error) {
echo """❗Exception thrown:
${error.class}
${error.message}
"""
// error.printStackTrace() // required administrator
currentBuild.result = 'FAILURE'
} finally {
if (currentBuild.result == 'SUCCESS') {
// TODO
} else {
// TODO
}
echo currentBuild.result
}
}

Docker in Node

node ('your-server-node-label') {
stage('Test') {
docker.image('node:7-alpine').withRun() {
c -> sh 'node --version'
}
}
}

Maven and Deploy

pipeline {
agent any

tools {
maven 'Maven 3.5'
}

environment {
APP_NAME = 'wf'
DEPLOY_TO = '../../../../websites/demo/'
EMAIL_RECIPIENTS = 'zb@bndy.net'
HAS_DEPLOYMENT = 'false'
}

stages {
stage('Prepare') {
steps {
updateGithubStatus("PENDING", "Begin to build");
//sh 'printenv'
sh 'java -version'
sh 'mvn -v'
sh 'mkdir -p ../../email-templates/ && \\cp ./jenkins/my-groovy-html.template ../../email-templates/my-groovy-html.template'
}
}
stage('Build') {
steps {
echo 'Building...'
sh 'mvn -B -DskipTests clean package'
}
}
stage('Test') {
steps {
echo 'Skip tests'
}
}
stage('Deploy') {
when {
// following expression is just available for multi-branch project
branch 'master'
}
steps {
echo 'Deploying....'
sh 'rm -f ${DEPLOY_TO}/${APP_NAME}.war'
sh 'rm -Rf $DEPLOY_TO/${APP_NAME}'
sh 'mv ./target/wf-*.war ${DEPLOY_TO}/${APP_NAME}.war'
script {
HAS_DEPLOYMENT = 'true'
}
}
}
}

post {
success {
updateGithubStatus("SUCCESS", "Build complete");
sendEmail("SUCCESS");
}
unstable {
updateGithubStatus("UNSTABLE", "Build complete");
sendEmail("UNSTABLE");
}
failure {
updateGithubStatus("FAILURE", "Build complete");
sendEmail("FAILURE");
}
}
}

// get change log to be send over the mail
@NonCPS
def getChangeString() {
MAX_MSG_LEN = 100
def changeString = ""

echo "Gathering SCM changes"
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += "<li>${truncated_msg} [${entry.author}]</li>"
}
}

if (!changeString) {
changeString = "<li>No new changes</li>"
}
return changeString
}

def sendEmail(status) {
def subject = "[" + status + "] ${currentBuild.fullDisplayName}"
if (HAS_DEPLOYMENT == 'true') {
subject += " - deployed"
}

// Default Email Notification Plugin: email(...), but below supports html
emailext(
mimeType: "text/html",
to: "$EMAIL_RECIPIENTS",
subject: "${subject}",
body: '''${SCRIPT, template="my-groovy-html.template"}''',
//body: "Changes:<ul>" + getChangeString() + "</ul><br />Check console output at: ${BUILD_URL}console" + "<br />",
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']]
)
}

// set GitHub status
def getRepoURL() {
sh "git config --get remote.origin.url > .git/remote-url"
return readFile(".git/remote-url").trim()
}

def getCommitSha() {
sh "git rev-parse HEAD > .git/current-commit"
return readFile(".git/current-commit").trim()
}

def updateGithubStatus(status, message) {
// status: pending, success, failure or error
repoUrl = getRepoURL()
commitSha = getCommitSha()

step ([
$class: 'GitHubCommitStatusSetter',
reposSource: [$class: "ManuallyEnteredRepositorySource", url: repoUrl],
commitShaSource: [$class: "ManuallyEnteredShaSource", sha: commitSha],
errorHandlers: [[$class: 'ShallowAnyErrorHandler']],
statusResultSource: [
$class: 'ConditionalStatusResultSource',
results: [
[$class: 'AnyBuildResult', state: status, message: message]
]
]
])
}

Docker Usage

docker build -t <image-name> --build-arg YOUR_ARG="<arg-value>" .

# -d: as deamon
# --rm: exit and remove
docker run -d -p 80:80 -p 443:443 --name <container-name> -v /opt/websites/app:/usr/share/nginx/html <image-name>

# enter the terminal
docker exec -it <container-name> /bin/bash

docker image remove <image-name>

docker container rm <container-name>


##
docker container rename <container-name> <new-container-name>
docker rm -f <container-name>

# build from github
docker build -t <image-name> https://github.com/bndynet/docker.nginx.git[#<branch/tag>:<dir-for-context>]


# Copy & Move
## Export and import containers
docker export container-name | gzip > container-name.gz
zcat container-name.gz | docker import - container-name
## Container image migration - Using this method, the data volumes will not be migrated, but it preserves the data of the application created inside the container.
docker commit container-id image-name
## Save and load images
docker save image-name > image-name.tar
cat image-name.tar | docker load
## copy files from container into host which does not need a running container
docker cp your-file-path-in-container your-host-location

# Publish
docker build -t bndynet/image-name:1.0 .
docker push bndynet/image-name:1.0

Networking

bookmark

# bridge: The default network driver
# host: use the host’s networking directly
docker --network bridge|host|overlay|ipvlan

Change the port for the existing container

You can change the port mapping by directly editing the hostconfig.json file at /var/lib/docker/containers/[hash_of_the_container]/hostconfig.json or /var/snap/docker/common/var-lib-docker/containers/[hash_of_the_container]/hostconfig.json, I believe, if You installed Docker as a snap.

You can determine the [hash_of_the_container] via the docker inspect <container_name> command and the value of the "Id" field is the hash.

  1. Stop the container (docker stop <container_name>).
  2. Stop docker service (per Tacsiazuma's comment)
  3. Change the file.
  4. Restart your docker engine (to flush/clear config caches).
  5. Start the container (docker start <container_name>).

Clear

docker system df # show disk space about docker
docker image prune -f # clear all images which is not used
# print the log for container
docker logs -f <container-name> 1>/dev/null

Example:

IMAGE_NAME = "bndynet/nginx"
CONTAINER_NAME = "web"

docker rm -f $CONTAINER_NAME || true
docker image rm $IMAGE_NAME || true
docker build -t $IMAGE_NAME .

docker run -d -p 80:80 -p 443:443 --name $CONTAINER_NAME -v /opt/website:/usr/share/nginx/html -v /etc/ssl:/etc/ssl $IMAGE_NAME

Mirrors

{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}

Docker Resource

Node.js

docker pull node:14
docker pull node:<version>-alpine
docker pull node:<version>-slim

docker run --rm -v "$PWD":/home/node/app -w /home/node/app -it node:14-slim /bin/bash -c "npm build"

Docker Q&A

IPv4 forwarding is disabled. Networking will not work

Solution 1: This won’t work every time

// run on host server
sysctl -w net.ipv4.ip_forward=1
sudo systemctl restart network

Solution 2: Append net.ipv4.ip_forward=1 to /etc/sysctl.conf file.

Solution 3: Append parameter —net=host to docker run, that will use host ports and ignore the -p parameters for example:

docker run -it --network host –name mynginx nginx

/var/jenkins_home/copy_reference_file.log Permission denied

$ docker run -ti -p 8080:8080 -p 50000:50000 -v /opt/jenkins:/var/jenkins_home jenkins touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

If you meet the above error, please figure out your volume mapping permissions.

sudo chown -R 1000:1000 /opt/jenkins

Parameters and Variables in Jenkinsfile

import groovy.transform.Field

@Field def globalV = ""

def myFunc() {
echo "${globalV}" // The @Field is required for this variable in definition
}

node {
stage('Start') {
echo 'Testing..'
print params
if (params.b) {
echo 'has b'
}
if (b == 'true') {
echo 'b == true'
}
print 's:' + s
echo 's: ' + s

if (params.nn) {
echo 'has nn'
} else {
echo 'nn undefined'
}
}

stage('Build') {
echo 'Building..'
}

stage('Test') {
echo 'Testing..'
}

stage('Deploy') {
echo 'Deploying....'
}

stage ("Prompt for input") {
steps {
script {
env.USERNAME = input message: 'Please enter the username',
parameters: [string(defaultValue: '',
description: '',
name: 'Username')]
env.PASSWORD = input message: 'Please enter the password',
parameters: [password(defaultValue: '',
description: '',
name: 'Password')]
}
echo "Username: ${env.USERNAME}"
echo "Password: ${env.PASSWORD}"
}
}
}

Nginx

Run in Docker

sudo docker pull nginx
sudo docker run --name my-nginx -p 8080:80 -v /some/content:/usr/share/nginx/html:ro -d nginx
sudo chmod -R 755 /some/content

Install Nginx on CentOS 8

1. Update the System:

First, update your system to ensure all packages are up to date.

sudo dnf update -y

2. Add the Nginx Repository

CentOS 8 doesn't include the latest Nginx packages in its default repositories. To get the latest version, add the official Nginx repository.

Create a repository file for Nginx:

sudo nano /etc/yum.repos.d/nginx.repo

Add the following content to the file:

[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key

The nginx-stable repository is enabled by default, while the nginx-mainline repository is not. You can enable the nginx-mainline repository by setting enabled=1 if you prefer to use the mainline version of Nginx.

3. Install Nginx:

Install Nginx using the dnf package manager.

sudo dnf install nginx -y

4. Start and Enable Nginx:

Once the installation is complete, start the Nginx service and enable it to start on boot.

sudo systemctl start nginx
sudo systemctl enable nginx

5. Verify Nginx Installation:

You can verify that Nginx is running by checking its status.

sudo systemctl status nginx

6. Adjust Firewall Settings:

If you have a firewall enabled, you'll need to allow traffic on HTTP (port 80) and HTTPS (port 443) ports.

sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

Configuration for static website based on framework

server {
listen 80;
server_name yourdomain.com;

root /var/www/angular-app;
index index.html;

location / {
try_files $uri /index.html;
}

error_page 404 /index.html;

gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
}

Logs

sudo tail -f /var/log/nginx/error.log

Distribute Domains to Ports

server {
listen 80;
server_name domain1.com;

location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

server {
listen 80;
server_name domain2.com;

location / {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

Use map:

http {
map $host $backend {
default localhost:80;
domain1.com localhost:8080;
domain2.com localhost:8081;
}

server {
listen 80;

location / {
proxy_pass http://$backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

Certificate in Nginx

1. Obtain an SSL/TLS Certificate

You can obtain a certificate from a Certificate Authority (CA) like Let's Encrypt, which provides free SSL/TLS certificates. Using Certbot, you can automate the process of obtaining and renewing certificates

Install Certbot:

sudo dnf install certbot python3-certbot-nginx -y

Obtain a Certificate: Replace your_domain.com with your actual domain name.

sudo certbot --nginx -d your_domain.com

Follow the prompts to complete the certificate issuance process. Certbot will automatically configure Nginx for SSL.

2. Manually Configuring Nginx with an SSL/TLS Certificate

If you have an existing certificate or need to configure it manually, follow these steps:

Create a Configuration File for Your Site:

sudo nano /etc/nginx/conf.d/your_domain.conf

Add SSL Configuration: Replace your_domain.com with your actual domain name and specify the paths to your certificate and key files.

server {
listen 80;
server_name your_domain.com;
return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}

server {
listen 443 ssl;
server_name your_domain.com;

ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

location / {
proxy_pass http://backend_server; # Change to your backend application address
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

3. Configure Firewall

Ensure your firewall allows HTTPS traffic:

sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload

4. Test and Reload Nginx

Test Nginx Configuration:

sudo nginx -t

Reload Nginx to Apply Changes:

sudo systemctl reload nginx

Q&A

Error about socket 80

nginx: [emerg] socket() [::]:80 failed (97: Unknown error)

Nginx try to listen the IPV6 [::]:80. Comment below line from the configuration /etc/nginx/sites-available/default.

# listen [::]:80 default_server;

Permission denied

Let www-data user have the permission to read the folder.

sudo chown -R www-data:www-data /your/site
sudo chmod 755 /your/site

If you still have same error, maybe your path under some user home folder. You should change the home folder permission.

sudo chown -R username:www-data /home/username
sudo chmod -R 750 /home/username
sudo chmod -R 755 /home/username/public_html

GitHub Actions

Context in Workflow

More contexts at https://docs.github.com/en/actions/learn-github-actions/contexts

  • Url for repo, for example: https://github.com/bndynet/bndynet ${{ github.server_url }}/${{ github.repository }}
  • Url for pull request ${{ github.server_url }}/${{ github.repository }}/pull/${{ github.event.pull_request.number }}
  • Url for issue ${{ github.server_url }}/${{ github.repository }}/issues/${{ github.event.issue.number }}

About CRON

# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12)
# │ │ │ │ ┌───────────── day of the week (0 - 6)
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ │
# * * * * * <command to execute>

Examples

name: Test Build

on:
push:
pull_request:
schedule:
- cron: '00 1 * * 1' # At 01:00 on Mondays.
name: Sync Notion pages to posts

on:
schedule:
# Runs "At 20:00 on every day-of-week"
- cron: '0 20 * * *'

jobs:
# Build job
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Generate posts
uses: bndynet/github-action-notion@v1
with:
notion-token: ${{ secrets.NOTION_TOKEN}}
root-page-id: ${{ secrets.NOTION_ROOT_PAGE_ID }}

- name: Commit posts
uses: EndBug/add-and-commit@v9 # https://github.com/marketplace/actions/add-commit
with:
add: '_posts'
message: Sync Notion pages to posts by GitHub Actions
committer_name: Bendy Zhang
committer_email: email@your.com

Jenkins

Start Jenkins

Startup script to set the System Properties

Step 1:

  • Locate the Jenkins home directory. You can look into the existing system properties for ‘jenkins_home’ or execute ‘echo $JENKINS_HOME’ inside the jenkins server to get the Jenkins home directory.
  • Say if your Jenkins home directory is ‘/var/jenkins_home’. Create a new directory with name ‘init.groovy.d’.

Step 2:

  • Now change your working directory to ‘/var/jenkins_home/init.groovy.d
  • Within init.groovy.d directory create a new groovy script. Say ‘startup-properties.groovy’

Step 3:

  • Copy the below content to the ‘startup-properties.groovy’ file
import jenkins.model.Jenkins
import java.util.logging.LogManager

/* Jenkins home directory */
def jenkinsHome = Jenkins.instance.getRootDir().absolutePath
def logger = LogManager.getLogManager().getLogger("")

/* Replace the Key and value with the values you want to set.*/
/* System.setProperty(key, value)*/
System.setProperty("hudson.model.WorkspaceCleanupThread.disabled", "true")
logger.info("Jenkins Startup Script: Successfully updated the system properties. Script location: ${jenkinsHome}/init.groovy.d")

Visit https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html?is-external=true#setProperty(java.lang.String,java.lang.String) to see how to use setProperty.

Step 4:

Restart the Jenkins server, you can manually restart the server using :

<Jenkins-server-URL>/restart or <Jenkins-server-URL>/safeRestart

  • Use safeRestart when you have some job still running. It will wait till all the jobs are completed.

Step 5:

Check the logs after Jenkins restart: Manage Jenkins → System Log. You can see while server is starting, its executing the startup-properties.groovy script which is inside init.groovy.d directory.

1*kjzZCxTixWCYsQbIcfRkzA.png

For example, run the below scripts in Script Console to check the System Properties, you will see the required key and values.

println(System.getProperty("hudson.model.WorkspaceCleanupThread.disabled"))

Now every time server is restarted the script will run and set the system properties for you. You don’t need to worry for setting these system properties manually every time server restart.

https://wiki.jenkins-ci.org/display/JENKINS/Features-controlled-by-system-properties.html

Start docker container with system properties

link_preview

docker run --name my-jenkins -p 8080:8080 -p 50000:50000 -v /your/jenkins_home:/var/jenkins_home \
-d \
-v /var/run/docker.sock:/var/run/docker.sock \
--env JAVA_OPTS="-Dhudson.model.WorkspaceCleanupThread.disabled=\"true\"" \
--restart unless-stopped \
--user root \
bndynet/jenkins

Plugin: Configuration as Code (aka.JCasC )

Apply configuration from Git

https://<USERNAME>:<PERSON_ACCESS_TOKEN>@github.com/your-repo/jenkins-config.git

Add Nodes

Step 1. In node server, install java by https://sdkman.io/

curl -s "https://get.sdkman.io" | bash
sdk install java 17.0.14-tem

Step 2. Add java path for all users (/etc/environment)

JAVA_HOME=/home/<user>/.sdkman/candidates/java/current
PATH="/home/<user>/.sdkman/candidates/java/current/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"

Step 3. Generate ssh key in jenkins server, if you are running jenkins on container, you should enter the container to do that.

ssh-keygen -t rsa -b 4096 -f /var/jenkins_home/id_rsa

Step 4. Copy pub key to node server

ssh-copy-id -i /var/jenkins_home/.ssh/id_rsa.pub username@hostname

Step 5. Add Credentials in jenkins UI with the private key

Step 6. Add Node in jenkins UI with Launch agens via SSH, and choose above Credentials

Q: Could not copy remoting.jar into '/home/jenkins' on agent

If below error thrown:

Permission denied (SSH_FX_PERMISSION_DENIED: The user does not have sufficient permissions to perform the operation.)

Useful Scripts for Jenkins

Decrypt credentials defined in Jenkins and list values.

def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials(
com.cloudbees.plugins.credentials.common.StandardUsernameCredentials.class,
Jenkins.instance,
null,
null
);
for (c in creds) {
if (c.properties.privateKeySource) {
println("ID: " + c.id)
println("UserName: " + c.username)
println("Description: " + c.description)
println("Private Key: " + c.getPrivateKey() )
}
}

Decrypt the password if you got its HASH.

println(hudson.util.Secret.decrypt("{HASHxxxxx=}"))
# or /////
println(hudson.util.Secret.fromString("{HASHxxxxx=}").getPlainText())

Trigger build by url

# Run when the anonymous user has the build permission
curl http://127.0.0.1:8080/job/Trigger_Remote_Demo/build?token=My-token
# Send request with credentials
curl -u username:api_token http://127.0.0.1:8080/job/Trigger_Remote_Demo/build
# Build with Parameter
curl -u username:api_token http://127.0.0.1:8080/job/Trigger_Remote_Demo/buildWithParameters?para1=val1&para2=val2

Update Jenkins inside Docker Container

First identify your image.

$ docker ps --format "{{.ID}}: {{.Image}} {{.Names}}"3d2fb2ab2ca5: jenkins-docker jenkins-docker_1

Then login into the image as root.

$ docker container exec -u 0 -it jenkins-docker_1 /bin/bash

Now you are inside the container, download the jenkins.war file from the official site like.

# wget http://updates.jenkins-ci.org/download/war/2.176.1/jenkins.war

Replace the version with the one that fits to you.

The next step is to move that file and replace the oldest one.

# mv ./jenkins.war /usr/share/jenkins/

Then change permissions.

# chown jenkins:jenkins /usr/share/jenkins/jenkins.war

The last step is to logout from the container and restart it.

$ docker restart jenkins-docker_1

You can verify that update was successful by access to you Jenkins url.

Podman

The alternative of docker as docker is no longer free for enterprise users.

Installation

bookmark

Install from download

wget https://github.com/containers/podman/releases/latest/download/podman-remote-static-linux_amd64.tar.gz
tar -xvzf podman-remote-static-linux_amd64.tar.gz
sudo mv ./bin/podman-remote-static-linux_amd64 /usr/local/bin/podman
sudo chmod +x /usr/local/bin/podman

Examples

# If you want to keep your existing volumns, you should use --userns=keep-id,
# otherwise, it might throw "operation not permitted" when start a container
podman run -d --name my-container --userns=keep-id \
-v /your-path:/path
image-name

Configure sources

Open /etc/containers/registries.conf and uncomment below line, so that you can pull images from other sources.

unqualified-search-registries = ["registry.fedoraproject.org", "registry.access.redhat.com", "docker.io"]

Jenkins in Podman

Run podman in jenkins pipeline if jenkins is running in container

Mount the socket of podman into jenkins container.

docker run -d \
--name my-jenkins \
-p 8080:8080 \
-v /home/<host_user>/jenkins_home:/var/jenkins_home \
-v /run/user/1000/podman/podman.sock:/run/user/1000/podman/podman.sock \
bndynet/jenkins

Then run it in pipeline:

pipeline {
agent any
stages {
stage('Run Podman') {
steps {
sh 'export PODMAN_HOST=unix:///run/user/1000/podman/podman.sock && podman run --rm hello-world'
}
}
}
}

Let jenkins account run podman

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 jenkins

If the jenkins user does not exist, please create one:

sudo useradd -m -s /bin/bash jenkins
sudo passwd jenkins # set password(optional)

Restart jenkins

sudo systemctl restart jenkins
# or podman restart jenkins-container

Automatically Start

Podman provides generate systemd command, that can generate systemd service file to restart when server starts.

podman generate systemd --name my-container --restart-policy=always > /etc/systemd/system/my-container.service

Then enable and start service:

systemctl daemon-reload
systemctl enable my-container
systemctl start my-container

In this way, my-container will start automatically, it likes docker --restart unless-stopped.

Q&A

Can not “podman machine start”?

You need to remove the current machine via podman machine rm. and reinit via podman machine init.

link_to_page

PLC

Product Life Cycle

ELK

Installations & Usage

Refer https://elk-docker.readthedocs.io/#installation

$ sudo docker run -p 5601:5601 -p 9200:9200 -p 5044:5044 -it --name elk sebp/elk

Running the container using Docker Compose

elk:
image: sebp/elk
ports:
- "5601:5601"
- "9200:9200"
- "5044:5044"
$ sudo docker-compose up elk

Creating a dummy log entry

$ sudo docker exec -it <container-name> /bin/bash
# /opt/logstash/bin/logstash --path.data /tmp/logstash/data \
-e 'input { stdin { } } output { elasticsearch { hosts => ["localhost"] } }'

http://localhost:9200/_search?pretty&size=1000 You will see:

{
...
"hits": {
...
"hits": [ {
"_index": "logstash-...",
"_type": "logs",
...
"_source": { "message": "this is a dummy entry", "@version": "1", "@timestamp": ... }
} ]
}
}

Urls or Ports

Logstash

Commands

/opt/logstash/bin/logstash --debug
/opt/logstash/bin/logstash -f your-config-file
/opt/logstash/bin/logstash-plugin list

Configurations

input {
http {
host => "0.0.0.0"
port => 5044
type => http
response_headers => {
"Access-Control-Allow-Origin" => "*"
"Content-Type" => "text/plain"
"Access-Control-Allow-Headers" => "Origin, X-Requested-With, Content-Type,
Accept"
}
}
tcp {
host => "0.0.0.0"
port => 5045
codec => json_lines
type => logback
}
}

filter {
if [headers][request_method] == "OPTIONS" {
drop {}
}
}

output {
if [type]=="http" and [headers.request_method]!="OPTIONS" {
elasticsearch {
hosts => ["127.0.0.1:9200"]
manage_template => false
#index => "%{APP_NAME}-%{[@metadata][beat]}-%{+YYYY.MM.dd}"
index => "http-%{+YYYY.MM.dd}"
}
}
if [type]=="logback" {
elasticsearch {
hosts => ["127.0.0.1:9200"]
manage_template => false
#index => "%{APP_NAME}-%{[@metadata][beat]}-%{+YYYY.MM.dd}"
index => "logback-%{APP_NAME}-%{+YYYY.MM.dd}"
}
}
}

Zipkin

https://zipkin.io/

Zipkin is a distributed tracing system. It helps gather timing data needed to troubleshoot latency problems in service architectures. Features include both the collection and lookup of this data.

If you have a trace ID in a log file, you can jump directly to it. Otherwise, you can query based on attributes such as service, operation name, tags and duration. Some interesting data will be summarized for you, such as the percentage of time spent in a service, and whether or not operations failed.

web-screenshot.png

ELK Setup - Elasticsearch、Kibana、Logstash

摘要

ELK日志收集系统进阶使用,本文主要讲解如何打造一个线上环境真实可用的日志收集系统。有了它,你就可以和去服务器上捞日志说再见了!

ELK环境安装

ELK是指Elasticsearch、Kibana、Logstash这三种服务搭建的日志收集系统,具体搭建方式可以参考《SpringBoot应用整合ELK实现日志收集》。这里仅提供最新版本的docker-compose脚本和一些安装要点。

docker-compose脚本

version: '3'
services:
elasticsearch:
image: elasticsearch:6.4.0
container_name: elasticsearch
environment:
- "cluster.name=elasticsearch" #设置集群名称为elasticsearch
- "discovery.type=single-node" #以单一节点模式启动
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" #设置使用jvm内存大小
- TZ=Asia/Shanghai
volumes:
- /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins #插件文件挂载
- /mydata/elasticsearch/data:/usr/share/elasticsearch/data #数据文件挂载
ports:
- 9200:9200
- 9300:9300
kibana:
image: kibana:6.4.0
container_name: kibana
links:
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
depends_on:
- elasticsearch #kibana在elasticsearch启动之后再启动
environment:
- "elasticsearch.hosts=http://es:9200" #设置访问elasticsearch的地址
- TZ=Asia/Shanghai
ports:
- 5601:5601
logstash:
image: logstash:6.4.0
container_name: logstash
environment:
- TZ=Asia/Shanghai
volumes:
- /mydata/logstash/logstash.conf:/usr/share/logstash/pipeline/logstash.conf #挂载logstash的配置文件
depends_on:
- elasticsearch #kibana在elasticsearch启动之后再启动
links:
- elasticsearch:es #可以用es这个域名访问elasticsearch服务
ports:
- 4560:4560
- 4561:4561
- 4562:4562
- 4563:4563

安装要点

  • 使用docker-compose命令运行所有服务:
docker-compose up -d
  • 第一次启动可能会发现Elasticsearch无法启动,那是因为/usr/share/elasticsearch/data目录没有访问权限,只需要修改/mydata/elasticsearch/data目录的权限,再重新启动;
chmod 777 /mydata/elasticsearch/data/
  • Logstash需要安装json_lines插件。
logstash-plugin install logstash-codec-json_lines

分场景收集日志

这里为了方便我们查看日志,提出一个分场景收集日志的概念,把日志分为以下四种。

  • 调试日志:最全日志,包含了应用中所有DEBUG级别以上的日志,仅在开发、测试环境中开启收集;
  • 错误日志:只包含应用中所有ERROR级别的日志,所有环境只都开启收集;
  • 业务日志:在我们应用对应包下打印的日志,可用于查看我们自己在应用中打印的业务日志;
  • 记录日志:每个接口的访问记录,可以用来查看接口执行效率,获取接口访问参数。

Logback配置详解

要实现上面的分场景收集日志,主要通过Logback的配置来实现,我们先来了解下Logback的配置吧!

完全配置

在SpringBoot中,如果我们想要自定义Logback的配置,需要自行编写logback-spring.xml文件,下面是我们这次要使用的完全配置。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<!--引用默认日志配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--使用默认的控制台日志输出实现-->
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<!--应用名称-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="springBoot"/>
<!--日志文件保存路径-->
<property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
<!--LogStash访问host-->
<springProperty name="LOG_STASH_HOST" scope="context" source="logstash.host" defaultValue="localhost"/>

<!--DEBUG日志输出到文件-->
<appender name="FILE_DEBUG"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--输出DEBUG以上级别日志-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder>
<!--设置为默认的文件日志格式-->
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--设置文件命名格式-->
<fileNamePattern>${LOG_FILE_PATH}/debug/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--设置日志文件大小,超过就重新生成文件,默认10M-->
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<!--日志文件保留天数,默认30天-->
<maxHistory>${LOG_FILE_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>

<!--ERROR日志输出到文件-->
<appender name="FILE_ERROR"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--只输出ERROR级别的日志-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<!--设置为默认的文件日志格式-->
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--设置文件命名格式-->
<fileNamePattern>${LOG_FILE_PATH}/error/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--设置日志文件大小,超过就重新生成文件,默认10M-->
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<!--日志文件保留天数,默认30天-->
<maxHistory>${LOG_FILE_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>

<!--DEBUG日志输出到LogStash-->
<appender name="LOG_STASH_DEBUG" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<destination>${LOG_STASH_HOST}:4560</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定义日志输出格式-->
<pattern>
<pattern>
{
"project": "mall-tiny",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--当有多个LogStash服务时,设置访问策略为轮询-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>

<!--ERROR日志输出到LogStash-->
<appender name="LOG_STASH_ERROR" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<destination>${LOG_STASH_HOST}:4561</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定义日志输出格式-->
<pattern>
<pattern>
{
"project": "mall-tiny",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--当有多个LogStash服务时,设置访问策略为轮询-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>

<!--业务日志输出到LogStash-->
<appender name="LOG_STASH_BUSINESS" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOG_STASH_HOST}:4562</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定义日志输出格式-->
<pattern>
<pattern>
{
"project": "mall-tiny",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--当有多个LogStash服务时,设置访问策略为轮询-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>

<!--接口访问记录日志输出到LogStash-->
<appender name="LOG_STASH_RECORD" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOG_STASH_HOST}:4563</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定义日志输出格式-->
<pattern>
<pattern>
{
"project": "mall-tiny",
"level": "%level",
"service": "${APP_NAME:-}",
"class": "%logger",
"message": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--当有多个LogStash服务时,设置访问策略为轮询-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>

<!--控制框架输出日志-->
<logger name="org.slf4j" level="INFO"/>
<logger name="springfox" level="INFO"/>
<logger name="io.swagger" level="INFO"/>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate.validator" level="INFO"/>

<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<!--<appender-ref ref="FILE_DEBUG"/>-->
<!--<appender-ref ref="FILE_ERROR"/>-->
<appender-ref ref="LOG_STASH_DEBUG"/>
<appender-ref ref="LOG_STASH_ERROR"/>
</root>

<logger name="com.macro.mall.tiny.component" level="DEBUG">
<appender-ref ref="LOG_STASH_RECORD"/>
</logger>

<logger name="com.macro.mall" level="DEBUG">
<appender-ref ref="LOG_STASH_BUSINESS"/>
</logger>
</configuration>

配置要点解析

使用默认的日志配置

一般我们不需要自定义控制台输出,可以采用默认配置,具体配置参考console-appender.xml,该文件在spring-boot-${version}.jar下面。

<!--引用默认日志配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--使用默认的控制台日志输出实现-->
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

springProperty

该标签可以从SpringBoot的配置文件中获取配置属性,比如说在不同环境下我们的Logstash服务地址是不一样的,我们就可以把该地址定义在application.yml来使用。

例如在application-dev.yml中定义了这些属性:

logstash:
host: localhost

在logback-spring.xml中就可以直接这样使用:

<!--应用名称-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name" defaultValue="springBoot"/>
<!--LogStash访问host-->
<springProperty name="LOG_STASH_HOST" scope="context" source="logstash.host" defaultValue="localhost"/>

filter

在Logback中有两种不同的过滤器,用来过滤日志输出。

ThresholdFilter:临界值过滤器,过滤掉低于指定临界值的日志,比如下面的配置将过滤掉所有低于INFO级别的日志。

<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level></filter>

LevelFilter:级别过滤器,根据日志级别进行过滤,比如下面的配置将过滤掉所有非ERROR级别的日志。

<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level><onMatch>ACCEPT</onMatch><onMismatch>DENY</onMismatch></filter>

appender

Appender可以用来控制日志的输出形式,主要有下面三种。

  • ConsoleAppender:控制日志输出到控制台的形式,比如在console-appender.xml中定义的默认控制台输出。
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder></appender>
  • RollingFileAppender:控制日志输出到文件的形式,可以控制日志文件生成策略,比如文件名称格式、超过多大重新生成文件以及删除超过多少天的文件。
<!--ERROR日志输出到文件-->
<appender name="FILE_ERROR"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--设置文件命名格式-->
<fileNamePattern>${LOG_FILE_PATH}/error/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--设置日志文件大小,超过就重新生成文件,默认10M-->
<maxFileSize>${LOG_FILE_MAX_SIZE:-10MB}</maxFileSize>
<!--日志文件保留天数,默认30天-->
<maxHistory>${LOG_FILE_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
  • LogstashTcpSocketAppender:控制日志输出到Logstash的形式,可以用来配置Logstash的地址、访问策略以及日志的格式。
<!--ERROR日志输出到LogStash-->
<appender name="LOG_STASH_ERROR" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>${LOG_STASH_HOST}:4561</destination>
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp>
<timeZone>Asia/Shanghai</timeZone>
</timestamp>
<!--自定义日志输出格式-->
<pattern>
<pattern>
{
"project": "mall-tiny",
"level": "%level",
"service": "${APP_NAME:-}",
"pid": "${PID:-}",
"thread": "%thread",
"class": "%logger",
"message": "%message",
"stack_trace": "%exception{20}"
}
</pattern>
</pattern>
</providers>
</encoder>
<!--当有多个LogStash服务时,设置访问策略为轮询-->
<connectionStrategy>
<roundRobin>
<connectionTTL>5 minutes</connectionTTL>
</roundRobin>
</connectionStrategy>
</appender>

logger

只有配置到logger节点上的appender才会被使用,logger用于配置哪种条件下的日志被打印,root是一种特殊的appender,下面介绍下日志划分的条件。

  • 调试日志:所有的DEBUG级别以上日志;
  • 错误日志:所有的ERROR级别日志;
  • 业务日志:com.macro.mall包下的所有DEBUG级别以上日志;
  • 记录日志:com.macro.mall.tiny.component.WebLogAspect类下所有DEBUG级别以上日志,该类是统计接口访问信息的AOP切面类。

控制框架输出日志

还有一些使用框架内部的日志,DEBUG级别的日志对我们并没有啥用处,都可以设置为了INFO以上级别。

<!--控制框架输出日志-->
<logger name="org.slf4j" level="INFO"/>
<logger name="springfox" level="INFO"/>
<logger name="io.swagger" level="INFO"/>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate.validator" level="INFO"/>

Logstash配置详解

接下来我们需要配置下Logstash,让它可以分场景收集不同的日志,下面详细介绍下使用到的配置。

完全配置

input {
tcp {
mode => "server"
host => "0.0.0.0"
port => 4560
codec => json_lines
type => "debug"
}
tcp {
mode => "server"
host => "0.0.0.0"
port => 4561
codec => json_lines
type => "error"
}
tcp {
mode => "server"
host => "0.0.0.0"
port => 4562
codec => json_lines
type => "business"
}
tcp {
mode => "server"
host => "0.0.0.0"
port => 4563
codec => json_lines
type => "record"
}
}
filter{
if [type] == "record" {
mutate {
remove_field => "port"
remove_field => "host"
remove_field => "@version"
}
json {
source => "message"
remove_field => ["message"]
}
}
}
output {
elasticsearch {
hosts => ["es:9200"]
action => "index"
codec => json
index => "mall-tiny-%{type}-%{+YYYY.MM.dd}"
template_name => "mall-tiny"
}
}

配置要点

  • input:使用不同端口收集不同类型的日志,从4560~4563开启四个端口;
  • filter:对于记录类型的日志,直接将JSON格式的message转化到source中去,便于搜索查看;
  • output:按类型、时间自定义索引格式。

SpringBoot配置

在SpringBoot中的配置可以直接用来覆盖Logback中的配置,比如logging.level.root就可以覆盖<root>节点中的level配置。

  • 开发环境配置:application-dev.yml
logstash:
host: localhost
logging:
level:
root: debug
  • 测试环境配置:application-test.yml
logstash:
host: 192.168.3.101
logging:
level:
root: debug
  • 生产环境配置:application-prod.yml
logstash:
host: logstash-prod
logging:
level:
root: info

🌟 Tips & Tricks


AI

LangGraph

image.png