feat: project, build, and bundle
This commit is contained in:
commit
8be80c3803
|
@ -0,0 +1,10 @@
|
|||
.awcache/
|
||||
.licenses/
|
||||
.nyc_output/
|
||||
node_modules/
|
||||
out/
|
||||
yarn-error.log
|
||||
|
||||
*.bak
|
||||
*.swp
|
||||
*.tmp
|
|
@ -0,0 +1,22 @@
|
|||
.github/
|
||||
|
||||
config/
|
||||
deploy/
|
||||
node_modules/
|
||||
out/*.html
|
||||
out/*.json
|
||||
scripts/
|
||||
src/
|
||||
test/
|
||||
|
||||
.codeclimate.yml
|
||||
.gitlab-ci.yml
|
||||
.gitmodules
|
||||
.mdlrc
|
||||
|
||||
Dockerfile
|
||||
licensed.yml
|
||||
Makefile
|
||||
renovate.json
|
||||
tsconfig.json
|
||||
yarn-*
|
|
@ -0,0 +1,123 @@
|
|||
# Git
|
||||
export GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
export GIT_COMMIT ?= $(shell git rev-parse HEAD)
|
||||
export GIT_REMOTES ?= $(shell git remote -v | awk '{ print $1; }' | sort | uniq)
|
||||
export GIT_OPTIONS ?=
|
||||
|
||||
# CI
|
||||
export CI_COMMIT_REF_SLUG ?= $(GIT_BRANCH)
|
||||
export CI_COMMIT_SHA ?= $(GIT_COMMIT)
|
||||
export CI_ENVIRONMENT_SLUG ?= local
|
||||
export CI_JOB_ID ?= 0
|
||||
export CI_RUNNER_DESCRIPTION ?= $(shell hostname)
|
||||
export CI_RUNNER_ID ?= $(shell hostname)
|
||||
export CI_RUNNER_VERSION ?= 0.0.0
|
||||
|
||||
# Debug
|
||||
export DEBUG_BIND ?= 127.0.0.1
|
||||
export DEBUG_PORT ?= 9229
|
||||
|
||||
# Paths
|
||||
# resolve the makefile's path and directory, from https://stackoverflow.com/a/18137056
|
||||
export MAKE_PATH ?= $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||
export ROOT_PATH ?= $(dir $(MAKE_PATH))
|
||||
export CONFIG_PATH ?= $(ROOT_PATH)/config
|
||||
export SCRIPT_PATH ?= $(ROOT_PATH)/scripts
|
||||
export SOURCE_PATH ?= $(ROOT_PATH)/src
|
||||
export TARGET_PATH ?= $(ROOT_PATH)/out
|
||||
export TEST_PATH ?= $(ROOT_PATH)/test
|
||||
|
||||
# Node options
|
||||
NODE_BIN := $(ROOT_PATH)/node_modules/.bin
|
||||
NODE_CMD ?= $(shell env node)
|
||||
NODE_DEBUG ?= --inspect-brk=$(DEBUG_BIND):$(DEBUG_PORT) --nolazy
|
||||
export NODE_OPTIONS ?= --max-old-space-size=5500
|
||||
|
||||
# Tool options
|
||||
DOCKER_IMAGE ?= ssube/salty:master
|
||||
DOCS_OPTS ?= --exclude "test.+" --tsconfig "$(CONFIG_PATH)/tsconfig.json" --out "$(TARGET_PATH)/docs"
|
||||
RELEASE_OPTS ?= --commit-all
|
||||
|
||||
# Versions
|
||||
export NODE_VERSION := $(shell node -v)
|
||||
export RUNNER_VERSION := $(CI_RUNNER_VERSION)
|
||||
|
||||
all: build run-terminal
|
||||
|
||||
clean: ## clean up the target directory
|
||||
rm -rf node_modules
|
||||
rm -rf $(TARGET_PATH)
|
||||
|
||||
configure: ## create the target directory and other files not in git
|
||||
mkdir -p $(TARGET_PATH)
|
||||
|
||||
node_modules: yarn-install
|
||||
|
||||
# from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
help: ## print this help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort \
|
||||
| sed 's/^.*\/\(.*\)/\1/' \
|
||||
| awk 'BEGIN {FS = ":[^:]*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
|
||||
todo:
|
||||
@echo "Remaining tasks:"
|
||||
@echo ""
|
||||
@grep -i "todo" -r docs/ src/ test/ || true
|
||||
@echo ""
|
||||
@echo "Pending tests:"
|
||||
@echo ""
|
||||
@grep "[[:space:]]xit" -r test/ || true
|
||||
@echo "Casts to any:"
|
||||
@echo ""
|
||||
@grep "as any" -r src/ test/ || true
|
||||
@echo ""
|
||||
|
||||
# build targets
|
||||
build: ## builds, bundles, and tests the application
|
||||
build: build-fast
|
||||
|
||||
build-cover: ## builds, bundles, and tests the application with code coverage
|
||||
build-cover: configure node_modules
|
||||
|
||||
build-fast: ## builds, bundles, and tests the application
|
||||
build-fast: configure node_modules
|
||||
|
||||
build-strict: ## builds, bundles, and tests the application with type checks and extra warnings (slow)
|
||||
build-strict: configure node_modules
|
||||
|
||||
bundle: node_modules
|
||||
$(NODE_BIN)/rollup --config $(CONFIG_PATH)/rollup.js
|
||||
|
||||
yarn-install: ## install dependencies from package and lock file
|
||||
yarn
|
||||
|
||||
yarn-update: ## check yarn for outdated packages
|
||||
yarn upgrade-interactive --latest
|
||||
|
||||
# release targets
|
||||
git-push: ## push to both gitlab and github (this assumes you have both remotes set up)
|
||||
git push $(GIT_OPTIONS) github $(GIT_BRANCH)
|
||||
git push $(GIT_OPTIONS) gitlab $(GIT_BRANCH)
|
||||
|
||||
# from https://gist.github.com/amitchhajer/4461043#gistcomment-2349917
|
||||
git-stats: ## print git contributor line counts (approx, for fun)
|
||||
git ls-files | while read f; do git blame -w -M -C -C --line-porcelain "$$f" |\
|
||||
grep -I '^author '; done | sort -f | uniq -ic | sort -n
|
||||
|
||||
license-check: ## check license status
|
||||
licensed cache
|
||||
licensed status
|
||||
|
||||
release: ## create a release
|
||||
$(NODE_BIN)/standard-version --sign $(RELEASE_OPTS)
|
||||
GIT_OPTIONS=--tags $(MAKE) git-push
|
||||
|
||||
release-dry: ## test creating a release
|
||||
$(NODE_BIN)/standard-version --sign $(RELEASE_OPTS) --dry-run
|
||||
|
||||
upload-climate:
|
||||
cc-test-reporter format-coverage -t lcov -o $(TARGET_PATH)/coverage/codeclimate.json -p $(ROOT_PATH) $(TARGET_PATH)/coverage/lcov.info
|
||||
cc-test-reporter upload-coverage --debug -i $(TARGET_PATH)/coverage/codeclimate.json -r "$(shell echo "${CODECLIMATE_SECRET}" | base64 -d)"
|
||||
|
||||
upload-codecov:
|
||||
codecov --disable=gcov --file=$(TARGET_PATH)/coverage/lcov.info --token=$(shell echo "${CODECOV_SECRET}" | base64 -d)
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
import resolve from 'rollup-plugin-node-resolve';
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
|
||||
// `npm run build` -> `production` is true
|
||||
// `npm run dev` -> `production` is false
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
export default {
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
file: 'out/bundle.js',
|
||||
format: 'cjs',
|
||||
sourcemap: true,
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
preferBuiltins: true,
|
||||
}),
|
||||
commonjs({
|
||||
namedExports: {
|
||||
'node_modules/lodash/lodash.js': ['isNil', 'isString'],
|
||||
'node_modules/noicejs/out/main-bundle.js': ['BaseError'],
|
||||
'node_modules/js-yaml/index.js': ['DEFAULT_SAFE_SCHEMA', 'SAFE_SCHEMA', 'safeLoad', 'Schema', 'Type'],
|
||||
'node_modules/yargs-parser/index.js': ['detailed'],
|
||||
},
|
||||
}),
|
||||
typescript({
|
||||
cacheRoot: 'out/cache/rts2',
|
||||
rollupCommonJSResolveHack: true,
|
||||
}),
|
||||
],
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": false,
|
||||
"baseUrl": "../",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"importHelpers": true,
|
||||
"jsx": "react",
|
||||
"lib": [
|
||||
"es2017",
|
||||
"esnext.asynciterable"
|
||||
],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"paths": {},
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"target": "es2017",
|
||||
"types": [
|
||||
"js-yaml"
|
||||
],
|
||||
"typeRoots": [
|
||||
"../node_modules/@types",
|
||||
"../node_modules",
|
||||
"../vendor"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"../src/**/*",
|
||||
"../test/**/*"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": ["./tslint.cc.json", "tslint-clean-code", "tslint-sonarts"],
|
||||
"rulesDirectory": ["../node_modules/tslint-microsoft-contrib"],
|
||||
"rules": {
|
||||
"mocha-unneeded-done": true,
|
||||
"no-banned-terms": true,
|
||||
"no-delete-expression": true,
|
||||
"no-for-in": true,
|
||||
"no-relative-imports": true,
|
||||
"no-small-switch": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "unknown",
|
||||
"version": "0.1.0",
|
||||
"description": "YAML linter, transformer, and validator",
|
||||
"main": "out/index.js",
|
||||
"directories": {
|
||||
"doc": "docs",
|
||||
"test": "test"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "out/harness.js"
|
||||
},
|
||||
"author": "ssube",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^6.10.0",
|
||||
"bunyan": "^1.8.12",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lodash": "^4.17.11",
|
||||
"noicejs": "^2.5.2",
|
||||
"yargs-parser": "^13.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/js-yaml": "^3.12.1",
|
||||
"@types/lodash": "^4.14.134",
|
||||
"@types/yargs-parser": "^13.0.0",
|
||||
"rollup": "^1.15.5",
|
||||
"rollup-plugin-commonjs": "^10.0.0",
|
||||
"rollup-plugin-node-resolve": "^5.0.2",
|
||||
"rollup-plugin-typescript2": "^0.21.1",
|
||||
"typescript": "^3.5.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import { readFile } from 'fs';
|
||||
import { DEFAULT_SAFE_SCHEMA, safeLoad, Schema } from 'js-yaml';
|
||||
import { isNil, isString } from 'lodash';
|
||||
import { join } from 'path';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { envType } from 'src/config/type/Env';
|
||||
import { includeSchema, includeType } from 'src/config/type/Include';
|
||||
import { regexpType } from 'src/config/type/Regexp';
|
||||
import { NotFoundError } from 'src/error/NotFoundError';
|
||||
|
||||
export const CONFIG_ENV = 'ISOLEX_HOME';
|
||||
export const CONFIG_SCHEMA = Schema.create([DEFAULT_SAFE_SCHEMA], [
|
||||
envType,
|
||||
includeType,
|
||||
regexpType,
|
||||
]);
|
||||
|
||||
includeSchema.schema = CONFIG_SCHEMA;
|
||||
|
||||
const readFileSync = promisify(readFile);
|
||||
|
||||
/**
|
||||
* With the given name, generate all potential config paths in their complete, absolute form.
|
||||
*
|
||||
* This will include the value of `ISOLEX_HOME`, `HOME`, the current working directory, and any extra paths
|
||||
* passed as the final arguments.
|
||||
*/
|
||||
export function completePaths(name: string, extras: Array<string>): Array<string> {
|
||||
const paths = [];
|
||||
|
||||
const env = process.env[CONFIG_ENV];
|
||||
if (isString(env)) {
|
||||
paths.push(join(env, name));
|
||||
}
|
||||
|
||||
const home = process.env.HOME;
|
||||
if (isString(home)) {
|
||||
paths.push(join(home, name));
|
||||
}
|
||||
|
||||
if (isString(__dirname)) {
|
||||
paths.push(join(__dirname, name));
|
||||
}
|
||||
|
||||
for (const e of extras) {
|
||||
paths.push(join(e, name));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export async function loadConfig(name: string, ...extras: Array<string>): Promise<any> {
|
||||
const paths = completePaths(name, extras);
|
||||
|
||||
for (const p of paths) {
|
||||
const data = await readConfig(p);
|
||||
if (!isNil(data)) {
|
||||
return safeLoad(data, {
|
||||
schema: CONFIG_SCHEMA,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFoundError('unable to load config');
|
||||
}
|
||||
|
||||
export async function readConfig(path: string): Promise<string | undefined> {
|
||||
try {
|
||||
// need to await this read to catch the error, need to catch the error to check the code
|
||||
// tslint:disable-next-line:prefer-immediate-return
|
||||
const data = await readFileSync(path, {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return data;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { Type as YamlType } from 'js-yaml';
|
||||
|
||||
import { NotFoundError } from 'src/error/NotFoundError';
|
||||
|
||||
export const envType = new YamlType('!env', {
|
||||
kind: 'scalar',
|
||||
resolve(name: string) {
|
||||
if (Reflect.has(process.env, name)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new NotFoundError(`environment variable not found: ${name}`);
|
||||
}
|
||||
},
|
||||
construct(name: string) {
|
||||
return Reflect.get(process.env, name);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
import { existsSync, readFileSync, realpathSync } from 'fs';
|
||||
import { SAFE_SCHEMA, safeLoad, Type as YamlType } from 'js-yaml';
|
||||
import { BaseError } from 'noicejs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { NotFoundError } from 'src/error/NotFoundError';
|
||||
|
||||
// work around the circular dependency by setting the schema later
|
||||
export const includeSchema = {
|
||||
schema: SAFE_SCHEMA,
|
||||
};
|
||||
|
||||
export const includeType = new YamlType('!include', {
|
||||
kind: 'scalar',
|
||||
resolve(path: string) {
|
||||
const canonical = resolvePath(path);
|
||||
if (existsSync(canonical)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new NotFoundError('included file does not exist');
|
||||
}
|
||||
},
|
||||
construct(path: string): unknown {
|
||||
try {
|
||||
return safeLoad(readFileSync(resolvePath(path), {
|
||||
encoding: 'utf-8',
|
||||
}), {
|
||||
schema: includeSchema.schema,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new BaseError('error including file', err);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export function resolvePath(path: string): string {
|
||||
if (path[0] === '.') {
|
||||
return realpathSync(join(__dirname, path));
|
||||
} else {
|
||||
return realpathSync(path);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Type as YamlType } from 'js-yaml';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
import { InvalidArgumentError } from 'src/error/InvalidArgumentError';
|
||||
|
||||
export const REGEXP_REGEXP = /\/(.*)\/([gimuy]*)/;
|
||||
|
||||
export const regexpType = new YamlType('!regexp', {
|
||||
kind: 'scalar',
|
||||
resolve(value: string) {
|
||||
return REGEXP_REGEXP.test(value);
|
||||
},
|
||||
construct(value: string): RegExp {
|
||||
const match = REGEXP_REGEXP.exec(value);
|
||||
if (isNil(match)) {
|
||||
throw new InvalidArgumentError('invalid regexp');
|
||||
}
|
||||
const [/* input */, expr, flags] = Array.from(match);
|
||||
return new RegExp(expr, flags);
|
||||
},
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseError } from 'noicejs/error/BaseError';
|
||||
|
||||
export class InvalidArgumentError extends BaseError {
|
||||
constructor(msg = 'invalid argument passed', ...nested: Array<Error>) {
|
||||
super(msg, ...nested);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { BaseError } from 'noicejs/error/BaseError';
|
||||
|
||||
export class NotFoundError extends BaseError {
|
||||
constructor(msg = 'value not found', ...nested: Array<Error>) {
|
||||
super(msg, ...nested);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { createLogger } from 'bunyan';
|
||||
import { detailed, Options } from 'yargs-parser';
|
||||
|
||||
import { loadConfig } from 'src/config';
|
||||
import { VERSION_INFO } from 'src/version';
|
||||
|
||||
const CONFIG_ARGS_NAME = 'config-name';
|
||||
const CONFIG_ARGS_PATH = 'config-path';
|
||||
|
||||
const MAIN_ARGS: Options = {
|
||||
array: [CONFIG_ARGS_PATH],
|
||||
count: ['v'],
|
||||
default: {
|
||||
[CONFIG_ARGS_NAME]: `.${VERSION_INFO.app.name}.yml`,
|
||||
[CONFIG_ARGS_PATH]: [],
|
||||
},
|
||||
envPrefix: VERSION_INFO.app.name,
|
||||
};
|
||||
|
||||
const STATUS_SUCCESS = 0;
|
||||
const STATUS_ERROR = 1;
|
||||
|
||||
export async function main(argv: Array<string>): Promise<number> {
|
||||
const args = detailed(argv, MAIN_ARGS);
|
||||
const config = await loadConfig(args.argv[CONFIG_ARGS_NAME], ...args.argv[CONFIG_ARGS_PATH]);
|
||||
|
||||
const logger = createLogger(config.data.logger);
|
||||
logger.info(VERSION_INFO, 'version info');
|
||||
logger.info({ args }, 'main arguments');
|
||||
|
||||
// const schema = new Schema();
|
||||
const result = { errors: [], valid: true }; // schema.match(config);
|
||||
if (!result.valid) {
|
||||
logger.error({ errors: result.errors }, 'config failed to validate');
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
if (args.argv.test) {
|
||||
logger.info('config is valid');
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
main(process.argv).then((status) => process.exit(status)).catch((err) => {
|
||||
/* tslint:disable-next-line:no-console */
|
||||
console.error('uncaught error during main:', err);
|
||||
process.exit(STATUS_ERROR);
|
||||
});
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// webpack environment defines
|
||||
declare const APP_NAME: string;
|
||||
declare const BUILD_JOB: string;
|
||||
declare const BUILD_RUNNER: string;
|
||||
declare const GIT_BRANCH: string;
|
||||
declare const GIT_COMMIT: string;
|
||||
declare const NODE_VERSION: string;
|
||||
declare const WEBPACK_VERSION: string;
|
||||
|
||||
export const VERSION_INFO = {
|
||||
app: {
|
||||
name: APP_NAME,
|
||||
},
|
||||
build: {
|
||||
job: BUILD_JOB,
|
||||
node: NODE_VERSION,
|
||||
runner: BUILD_RUNNER,
|
||||
webpack: WEBPACK_VERSION,
|
||||
},
|
||||
git: {
|
||||
branch: GIT_BRANCH,
|
||||
commit: GIT_COMMIT,
|
||||
},
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./config/tsconfig.json"
|
||||
}
|
Loading…
Reference in New Issue