diff --git a/.gitignore b/.gitignore index 1d675a13a..75c9cc260 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.log node_modules -lib -es6 +/dist dev coverage declaration/out/src diff --git a/package.json b/package.json index 9b7511ce2..ca1a93e80 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,6 @@ "name": "io-ts", "version": "2.2.9", "description": "TypeScript runtime type system for IO decoding/encoding", - "files": [ - "lib", - "es6" - ], "main": "lib/index.js", "module": "es6/index.js", "typings": "lib/index.d.ts", @@ -16,14 +12,18 @@ "prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --list-different \"{src,test}/**/*.ts\"", "fix-prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --write \"{src,test,examples,exercises}/**/*.ts\"", "test": "npm run prettier && npm run lint && npm run dtslint && npm run jest && npm run docs", - "clean": "rimraf lib/* es6/*", - "build": "npm run clean && tsc && tsc -p tsconfig.es6.json && npm run import-path-rewrite", - "prepublish": "npm run build", + "clean": "rm -rf ./dist", + "prebuild": "npm run clean", + "build": "tsc -p ./tsconfig.build.json && tsc -p ./tsconfig.build-es6.json && npm run import-path-rewrite && ts-node scripts/build", + "postbuild": "prettier --loglevel=silent --write \"./dist/**/*.ts\"", + "prepublishOnly": "ts-node scripts/pre-publish", "perf": "ts-node perf/index", "dtslint": "dtslint dtslint", "mocha": "TS_NODE_CACHE=false mocha -r ts-node/register test/*.ts", "doctoc": "doctoc README.md index.md Decoder.md Encoder.md Codec.md Eq.md Schema.md", "docs": "docs-ts", + "prerelease": "npm run build", + "release": "ts-node scripts/release", "import-path-rewrite": "import-path-rewrite" }, "repository": { diff --git a/scripts/FileSystem.ts b/scripts/FileSystem.ts new file mode 100644 index 000000000..9e2c2c468 --- /dev/null +++ b/scripts/FileSystem.ts @@ -0,0 +1,29 @@ +import * as TE from 'fp-ts/TaskEither' +import { flow } from 'fp-ts/function' +import * as fs from 'fs' +import G from 'glob' + +export interface FileSystem { + readonly readFile: (path: string) => TE.TaskEither + readonly writeFile: (path: string, content: string) => TE.TaskEither + readonly copyFile: (from: string, to: string) => TE.TaskEither + readonly glob: (pattern: string) => TE.TaskEither> + readonly mkdir: (path: string) => TE.TaskEither +} + +const readFile = TE.taskify(fs.readFile) +const writeFile = TE.taskify(fs.writeFile) +const copyFile = TE.taskify(fs.copyFile) +const glob = TE.taskify>(G) +const mkdirTE = TE.taskify(fs.mkdir) + +export const fileSystem: FileSystem = { + readFile: (path) => readFile(path, 'utf8'), + writeFile, + copyFile, + glob, + mkdir: flow( + mkdirTE, + TE.map(() => undefined) + ) +} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 000000000..ba6ec6f59 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,88 @@ +import * as path from 'path' +import * as E from 'fp-ts/Either' +import { pipe } from 'fp-ts/function' +import * as RTE from 'fp-ts/ReaderTaskEither' +import * as A from 'fp-ts/ReadonlyArray' +import * as TE from 'fp-ts/TaskEither' +import { FileSystem, fileSystem } from './FileSystem' +import { run } from './run' + +interface Build extends RTE.ReaderTaskEither {} + +const OUTPUT_FOLDER = 'dist' +const PKG = 'package.json' + +export const copyPackageJson: Build = (C) => + pipe( + C.readFile(PKG), + TE.chain((s) => TE.fromEither(E.parseJSON(s, E.toError))), + TE.map((v) => { + const clone = Object.assign({}, v as any) + + delete clone.scripts + delete clone.files + delete clone.devDependencies + + return clone + }), + TE.chain((json) => C.writeFile(path.join(OUTPUT_FOLDER, PKG), JSON.stringify(json, null, 2))) + ) + +export const FILES: ReadonlyArray = ['CHANGELOG.md', 'LICENSE', 'README.md'] + +export const copyFiles: Build> = (C) => + pipe( + FILES, + A.traverse(TE.taskEither)((from) => C.copyFile(from, path.resolve(OUTPUT_FOLDER, from))) + ) + +const traverse = A.traverse(TE.taskEither) + +export const makeModules: Build = (C) => + pipe( + C.glob(`${OUTPUT_FOLDER}/lib/*.js`), + TE.map(getModules), + TE.chain(traverse(makeSingleModule(C))), + TE.map(() => undefined) + ) + +function getModules(paths: ReadonlyArray): ReadonlyArray { + return paths.map((filePath) => path.basename(filePath, '.js')).filter((x) => x !== 'index') +} + +function makeSingleModule(C: FileSystem): (module: string) => TE.TaskEither { + return (m) => + pipe( + C.mkdir(path.join(OUTPUT_FOLDER, m)), + TE.chain(() => makePkgJson(m)), + TE.chain((data) => C.writeFile(path.join(OUTPUT_FOLDER, m, 'package.json'), data)) + ) +} + +function makePkgJson(module: string): TE.TaskEither { + return pipe( + JSON.stringify( + { + main: `../lib/${module}.js`, + module: `../es6/${module}.js`, + typings: `../lib/${module}.d.ts`, + sideEffects: false + }, + null, + 2 + ), + TE.right + ) +} + +const main: Build = pipe( + copyPackageJson, + RTE.chain(() => copyFiles), + RTE.chain(() => makeModules) +) + +run( + main({ + ...fileSystem + }) +) diff --git a/scripts/pre-publish.ts b/scripts/pre-publish.ts new file mode 100644 index 000000000..c326efd04 --- /dev/null +++ b/scripts/pre-publish.ts @@ -0,0 +1,7 @@ +import { left } from 'fp-ts/TaskEither' +import { run } from './run' + +const main = left(new Error('"npm publish" can not be run from root, run "npm run release" instead')) + +run(main) + diff --git a/scripts/release.ts b/scripts/release.ts new file mode 100644 index 000000000..750f3eddd --- /dev/null +++ b/scripts/release.ts @@ -0,0 +1,23 @@ +import { run } from './run' +import * as child_process from 'child_process' +import { left, right } from 'fp-ts/Either' +import * as TE from 'fp-ts/TaskEither' + +const DIST = 'dist' + +const exec = (cmd: string, args?: child_process.ExecOptions): TE.TaskEither => () => + new Promise((resolve) => { + child_process.exec(cmd, args, (err) => { + if (err !== null) { + return resolve(left(err)) + } + + return resolve(right(undefined)) + }) + }) + +export const main = exec('npm publish', { + cwd: DIST +}) + +run(main) diff --git a/scripts/run.ts b/scripts/run.ts new file mode 100644 index 000000000..7629fe11d --- /dev/null +++ b/scripts/run.ts @@ -0,0 +1,21 @@ +import { fold } from 'fp-ts/Either' +import { TaskEither } from 'fp-ts/TaskEither' + +export function run(eff: TaskEither): void { + eff() + .then( + fold( + (e) => { + throw e + }, + (_) => { + process.exitCode = 0 + } + ) + ) + .catch((e) => { + console.error(e) // tslint:disable-line no-console + + process.exitCode = 1 + }) +} diff --git a/tsconfig.build-es6.json b/tsconfig.build-es6.json new file mode 100644 index 000000000..6b5e341f1 --- /dev/null +++ b/tsconfig.build-es6.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist/es6", + "module": "es6" + } +} diff --git a/tsconfig.es6.json b/tsconfig.build.json similarity index 55% rename from tsconfig.es6.json rename to tsconfig.build.json index fa53d00ed..9d833c294 100644 --- a/tsconfig.es6.json +++ b/tsconfig.build.json @@ -1,7 +1,7 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "./es6", - "module": "es6" - } + "noEmit": false + }, + "include": ["./src"] } diff --git a/tsconfig.json b/tsconfig.json index 692989298..082d65dec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,13 +1,14 @@ { "compilerOptions": { - "outDir": "./lib", + "outDir": "./dist/lib", + "noEmit": true, "declaration": true, + "esModuleInterop": true, "module": "commonjs", "noImplicitReturns": false, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "noEmitOnError": false, "strict": true, "target": "es5", "moduleResolution": "node",