1
0
Fork 0

feat: project, build, and bundle

This commit is contained in:
ssube 2019-06-15 15:20:04 -05:00
commit 8be80c3803
17 changed files with 1573 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
.awcache/
.licenses/
.nyc_output/
node_modules/
out/
yarn-error.log
*.bak
*.swp
*.tmp

22
.npmignore Normal file
View File

@ -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-*

123
Makefile Executable file
View File

@ -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)

34
config/rollup.js Normal file
View File

@ -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,
}),
],
};

44
config/tsconfig.json Executable file
View File

@ -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/**/*"
]
}

12
config/tslint.json Executable file
View File

@ -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
}
}

34
package.json Normal file
View File

@ -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"
}
}

82
src/config/index.ts Normal file
View File

@ -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;
}
}

17
src/config/type/Env.ts Normal file
View File

@ -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);
},
});

View File

@ -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);
}
}

21
src/config/type/Regexp.ts Normal file
View File

@ -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);
},
});

View File

@ -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);
}
}

View File

@ -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);
}
}

51
src/index.ts Normal file
View File

@ -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);
});

24
src/version.ts Normal file
View File

@ -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,
},
};

3
tsconfig.json Executable file
View File

@ -0,0 +1,3 @@
{
"extends": "./config/tsconfig.json"
}

1040
yarn.lock Normal file

File diff suppressed because it is too large Load Diff