[READ-ONLY] Mirror of https://github.com/danielroe/siroc. Zero-config build tooling for Node
bundle node package rollup typescript
0

Configure Feed

Select the types of activity you want to include in your feed.

refactor: merge `siroc`, `@siroc/core` and `@siroc/cli` into single package

Daniel Roe (Jul 27, 2020, 1:19 PM +0100) 0bcbbe98 9fd5e8a0

+2024 -2897
+20 -1
.eslintrc.js
··· 1 1 module.exports = { 2 2 root: true, 3 + env: { 4 + browser: false, 5 + es6: true, 6 + node: true, 7 + 'jest/globals': true, 8 + }, 9 + parser: '@typescript-eslint/parser', 10 + parserOptions: { 11 + sourceType: 'module', 12 + }, 13 + plugins: ['jest', '@typescript-eslint'], 14 + extends: [ 15 + 'plugin:promise/recommended', 16 + 'plugin:@typescript-eslint/recommended', 17 + 'eslint:recommended', 18 + 'plugin:prettier/recommended', 19 + 'prettier', 20 + 'prettier/@typescript-eslint', 21 + 'plugin:jest/recommended', 22 + ], 3 23 rules: { 4 24 'prettier/prettier': [1, require('./prettier.config.js')], 5 25 '@typescript-eslint/no-inferrable-types': 1, ··· 8 28 'jest/valid-describe': 0, 9 29 'no-dupe-class-members': 0, 10 30 }, 11 - extends: ['plugin:promise/recommended', '@siroc'], 12 31 }
+1 -3
.release-it.json
··· 5 5 "github": { 6 6 "release": true 7 7 }, 8 - "npm": false, 9 8 "plugins": { 10 9 "@release-it/conventional-changelog": { 11 10 "preset": "conventionalcommits", 12 11 "infile": "CHANGELOG.md" 13 - }, 14 - "release-it-yarn-workspaces": true 12 + } 15 13 } 16 14 }
-40
README.md
··· 134 134 yarn siroc run ls --workspaces 135 135 ``` 136 136 137 - ## Presets 138 - 139 - ### eslint 140 - 141 - Rather than configure `eslint`, you can extend `@siroc/eslint-config`, with zero-config support for TypeScript (and prettier, if you have it installed within your package dev dependencies). 142 - 143 - 1. Add the eslint config: 144 - 145 - ```bash 146 - yarn add --dev @siroc/eslint-config 147 - ``` 148 - 149 - 2. Add the following `.eslintrc.js` to your project: 150 - 151 - ```js 152 - module.exports = { 153 - extends: ['@siroc'], 154 - // Your rules/plugins here 155 - } 156 - ``` 157 - 158 - ### jest 159 - 160 - Rather than configure `jest`, you can extend `@siroc/jest-preset`, with zero-config support for TypeScript test and source files. By default it will also include any settings from a local `jest.config.js` (e.g. in a package directory). 161 - 162 - 1. Add the jest preset: 163 - 164 - ```bash 165 - yarn add --dev @siroc/jest-preset 166 - ``` 167 - 168 - 2. Add the following `jest.config.js` to your project: 169 - 170 - ```js 171 - module.exports = { 172 - preset: '@siroc/jest-preset', 173 - // Your customisations here 174 - } 175 - ``` 176 - 177 137 ## Contributors 178 138 179 139 Contributions are very welcome.
+20
jest.config.js
··· 1 + module.exports = { 2 + verbose: true, 3 + testEnvironment: 'node', 4 + collectCoverage: true, 5 + coveragePathIgnorePatterns: [ 6 + '[\\/]dist[\\/]', 7 + '.*\\.spec.ts', 8 + '.*\\.config.js', 9 + '.babelrc.js', 10 + ], 11 + transform: { 12 + '\\.(js|ts)$': [ 13 + 'babel-jest', 14 + { 15 + presets: ['@babel/preset-env', '@babel/preset-typescript'], 16 + plugins: ['@babel/plugin-transform-runtime'], 17 + }, 18 + ], 19 + }, 20 + }
+49 -8
package.json
··· 1 1 { 2 - "name": "siroc-base", 2 + "name": "siroc", 3 3 "version": "0.2.0", 4 - "private": true, 5 4 "description": "Zero-config build tooling for Node", 6 5 "keywords": [ 7 6 "node", ··· 14 13 ], 15 14 "repository": "nuxt-contrib/siroc", 16 15 "license": "MIT", 17 - "workspaces": [ 18 - "packages/*" 16 + "sideEffects": false, 17 + "main": "dist/index.js", 18 + "module": "dist/index.es.js", 19 + "types": "dist/index.d.ts", 20 + "bin": { 21 + "siroc": "bin/cli.js", 22 + "siroc-runner": "bin/runtime.js" 23 + }, 24 + "files": [ 25 + "dist/**/*", 26 + "dist/index.d.ts", 27 + "!**/*.map" 19 28 ], 20 29 "scripts": { 21 - "bootstrap": "rollup -c && chmod a+x packages/*/bin/* && yarn build", 22 - "build": "packages/cli/bin/cli.js build", 30 + "bootstrap": "rollup -c && chmod a+x bin/* && yarn build", 31 + "build": "bin/cli.js build", 23 32 "lint": "run-s lint:all:*", 24 33 "lint:all:eslint": "yarn lint:eslint --ext .js,.ts,.vue .", 25 34 "lint:all:prettier": "yarn lint:prettier \"**/*.{js,json,ts,vue}\"", ··· 29 38 "prepublishOnly": "yarn lint && yarn test", 30 39 "release": "release-it", 31 40 "test": "run-s test:*", 32 - "test:unit": "packages/cli/bin/cli.js jest", 41 + "test:unit": "jest", 33 42 "watch": "yarn build --watch" 34 43 }, 44 + "dependencies": { 45 + "@rollup/plugin-alias": "^3.1.1", 46 + "@rollup/plugin-commonjs": "^14.0.0", 47 + "@rollup/plugin-json": "^4.1.0", 48 + "@rollup/plugin-node-resolve": "^8.4.0", 49 + "@rollup/plugin-replace": "^2.3.3", 50 + "cac": "^6.6.1", 51 + "chalk": "^4.1.0", 52 + "consola": "^2.14.0", 53 + "defu": "^2.0.4", 54 + "execa": "^4.0.2", 55 + "fs-extra": "^9.0.1", 56 + "glob": "^7.1.6", 57 + "jiti": "^0.1.11", 58 + "rollup": "^2.23.0", 59 + "rollup-plugin-dts": "^1.4.9", 60 + "rollup-plugin-esbuild": "2.2.0", 61 + "sort-package-json": "^1.44.0", 62 + "typescript": "^3.9.7", 63 + "v8-compile-cache": "^2.1.1" 64 + }, 35 65 "devDependencies": { 66 + "@babel/plugin-transform-runtime": "^7.10.5", 67 + "@babel/preset-env": "^7.10.4", 68 + "@babel/preset-typescript": "^7.10.4", 36 69 "@release-it/conventional-changelog": "^1.1.4", 70 + "@types/execa": "^2.0.0", 71 + "@types/fs-extra": "^9.0.1", 37 72 "@types/jest": "^26.0.7", 73 + "@typescript-eslint/eslint-plugin": "^3.7.0", 74 + "@typescript-eslint/parser": "^3.7.0", 38 75 "babel-loader": "^8.1.0", 39 76 "codecov": "^3.7.2", 77 + "eslint": "^7.5.0", 78 + "eslint-config-prettier": "^6.11.0", 79 + "eslint-plugin-jest": "^23.18.2", 80 + "eslint-plugin-prettier": "^3.1.4", 81 + "eslint-plugin-promise": "^4.2.1", 40 82 "jest": "^26.1.0", 41 83 "lint-staged": "^10.2.11", 42 84 "npm-run-all": "^4.1.5", 43 85 "prettier": "^2.0.5", 44 86 "release-it": "13.6.6", 45 - "release-it-yarn-workspaces": "^1.4.0", 46 87 "tsd": "^0.13.1", 47 88 "typescript": "^3.9.7", 48 89 "yorkie": "^2.0.0"
+16 -23
rollup.config.js
··· 2 2 import dts from 'rollup-plugin-dts' 3 3 import esbuild from 'rollup-plugin-esbuild' 4 4 5 - import cli from './packages/cli/package.json' 6 - import core from './packages/core/package.json' 5 + import pkg from './package.json' 6 + const external = Object.keys(pkg.dependencies || {}) 7 + const esbuildPlugin = esbuild({ 8 + watch: process.argv.includes('--watch'), 9 + target: 'es2018', 10 + }) 7 11 8 12 export default [ 9 13 { 10 - input: 'packages/cli/src/index.ts', 14 + input: 'src/cli/index.ts', 11 15 output: { 12 - file: 'packages/cli/bin/cli.js', 16 + file: 'bin/cli.js', 13 17 format: 'cjs', 14 18 banner: '#!/usr/bin/env node\n', 15 19 }, 16 - external: Object.keys(cli.dependencies || {}), 17 - plugins: [ 18 - jsonPlugin(), 19 - esbuild({ 20 - watch: process.argv.includes('--watch'), 21 - target: 'es2018', 22 - }), 23 - ], 20 + external, 21 + plugins: [jsonPlugin(), esbuildPlugin], 24 22 }, 25 23 { 26 - input: 'packages/core/src/index.ts', 24 + input: 'src/core/index.ts', 27 25 output: { 28 - file: 'packages/core/dist/index.js', 26 + file: 'dist/index.js', 29 27 format: 'cjs', 30 28 }, 31 - external: [...Object.keys(core.dependencies || {})], 32 - plugins: [ 33 - esbuild({ 34 - watch: process.argv.includes('--watch'), 35 - target: 'es2018', 36 - }), 37 - ], 29 + external, 30 + plugins: [esbuildPlugin], 38 31 }, 39 32 { 40 - input: 'packages/core/src/index.ts', 41 - output: [{ file: 'packages/core/dist/index.d.ts', format: 'es' }], 33 + input: 'src/core/index.ts', 34 + output: [{ file: 'dist/index.d.ts', format: 'es' }], 42 35 plugins: [dts()], 43 36 }, 44 37 ]
+9 -44
yarn.lock
··· 1418 1418 dependencies: 1419 1419 "@types/node" "*" 1420 1420 1421 - "@types/minimatch@*", "@types/minimatch@^3.0.3": 1421 + "@types/minimatch@*": 1422 1422 version "3.0.3" 1423 1423 resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" 1424 1424 integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== ··· 2107 2107 integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== 2108 2108 2109 2109 caniuse-lite@^1.0.30001093: 2110 - version "1.0.30001106" 2111 - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001106.tgz#7e2132711295ef30ffe5ee45b71936354d105d8c" 2112 - integrity sha512-XqSQKt9Fd3Z9BoN0cpSaITcTInKhMNGkaWtQ4rDnyQU1BJzzWDWCUi3cJflaPWk2kbrkYkfMrMrjIFzb3kd6NQ== 2110 + version "1.0.30001107" 2111 + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001107.tgz#809360df7a5b3458f627aa46b0f6ed6d5239da9a" 2112 + integrity sha512-86rCH+G8onCmdN4VZzJet5uPELII59cUzDphko3thQFgAQG1RNa+sVLDoALIhRYmflo5iSIzWY3vu1XTWtNMQQ== 2113 2113 2114 2114 capture-exit@^2.0.0: 2115 2115 version "2.0.0" ··· 2787 2787 resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" 2788 2788 integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== 2789 2789 2790 - detect-newline@3.1.0, detect-newline@^3.0.0, detect-newline@^3.1.0: 2790 + detect-newline@3.1.0, detect-newline@^3.0.0: 2791 2791 version "3.1.0" 2792 2792 resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" 2793 2793 integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== ··· 2893 2893 integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== 2894 2894 dependencies: 2895 2895 ansi-colors "^4.1.1" 2896 - 2897 - ensure-posix-path@^1.1.0: 2898 - version "1.1.1" 2899 - resolved "https://registry.yarnpkg.com/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz#3c62bdb19fa4681544289edb2b382adc029179ce" 2900 - integrity sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw== 2901 2896 2902 2897 error-ex@^1.2.0, error-ex@^1.3.1: 2903 2898 version "1.3.2" ··· 5188 5183 dependencies: 5189 5184 object-visit "^1.0.0" 5190 5185 5191 - matcher-collection@^2.0.0: 5192 - version "2.0.1" 5193 - resolved "https://registry.yarnpkg.com/matcher-collection/-/matcher-collection-2.0.1.tgz#90be1a4cf58d6f2949864f65bb3b0f3e41303b29" 5194 - integrity sha512-daE62nS2ZQsDg9raM0IlZzLmI2u+7ZapXBwdoeBUKAYERPDDIc0qNqA8E0Rp2D+gspKR7BgIFP52GeujaGXWeQ== 5195 - dependencies: 5196 - "@types/minimatch" "^3.0.3" 5197 - minimatch "^3.0.2" 5198 - 5199 5186 memorystream@^0.3.1: 5200 5187 version "0.3.1" 5201 5188 resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" ··· 5320 5307 resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" 5321 5308 integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== 5322 5309 5323 - minimatch@^3.0.2, minimatch@^3.0.4: 5310 + minimatch@^3.0.4: 5324 5311 version "3.0.4" 5325 5312 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 5326 5313 integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== ··· 6257 6244 dependencies: 6258 6245 jsesc "~0.5.0" 6259 6246 6260 - release-it-yarn-workspaces@^1.4.0: 6261 - version "1.4.0" 6262 - resolved "https://registry.yarnpkg.com/release-it-yarn-workspaces/-/release-it-yarn-workspaces-1.4.0.tgz#ebc6723a59592cea5f7d31cb3c11c1757f65a7da" 6263 - integrity sha512-alXTftlKl3fhm+UKvVaB4urUecZskWe7g7kBdfgWqm5mqQ363gd/3f9sdM5r/KXKJAGE1xH9Us3igTliiG/SxQ== 6264 - dependencies: 6265 - detect-indent "^6.0.0" 6266 - detect-newline "^3.1.0" 6267 - release-it "^13.3.2" 6268 - semver "^7.1.3" 6269 - url-join "^4.0.1" 6270 - walk-sync "^2.0.2" 6271 - 6272 - release-it@13.6.6, release-it@^13.3.2, release-it@^13.5.6: 6247 + release-it@13.6.6, release-it@^13.5.6: 6273 6248 version "13.6.6" 6274 6249 resolved "https://registry.yarnpkg.com/release-it/-/release-it-13.6.6.tgz#c1f770449177e1906297aa5dfc15b15334eeb77a" 6275 6250 integrity sha512-tR0RkcMxc0lhtnPGWE2tBcjAG7Z+NXsQZTGiNr6M/XM5tOL4cSkWPrBo9+vEtVS2S7XlBiLtCZ85jCzcovBnBw== ··· 6584 6559 resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" 6585 6560 integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== 6586 6561 6587 - semver@7.3.2, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2: 6562 + semver@7.3.2, semver@^7.2.1, semver@^7.3.2: 6588 6563 version "7.3.2" 6589 6564 resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" 6590 6565 integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== ··· 7429 7404 resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" 7430 7405 integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= 7431 7406 7432 - url-join@4.0.1, url-join@^4.0.1: 7407 + url-join@4.0.1: 7433 7408 version "4.0.1" 7434 7409 resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" 7435 7410 integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== ··· 7510 7485 integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== 7511 7486 dependencies: 7512 7487 xml-name-validator "^3.0.0" 7513 - 7514 - walk-sync@^2.0.2: 7515 - version "2.2.0" 7516 - resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-2.2.0.tgz#80786b0657fcc8c0e1c0b1a042a09eae2966387a" 7517 - integrity sha512-IC8sL7aB4/ZgFcGI2T1LczZeFWZ06b3zoHH7jBPyHxOtIIz1jppWHjjEXkOFvFojBVAK9pV7g47xOZ4LW3QLfg== 7518 - dependencies: 7519 - "@types/minimatch" "^3.0.3" 7520 - ensure-posix-path "^1.1.0" 7521 - matcher-collection "^2.0.0" 7522 - minimatch "^3.0.4" 7523 7488 7524 7489 walker@^1.0.7, walker@~1.0.5: 7525 7490 version "1.0.7"
+1
src/index.ts
··· 1 + export * from './core'
-3
packages/cli/.eslintignore
··· 1 - dist 2 - bin 3 - coverage
-189
packages/cli/README.md
··· 1 - <h1 align="center">🌬️ siroc</h1> 2 - <p align="center">Zero-config build tooling for Node</p> 3 - 4 - <p align="center"> 5 - <a href="https://npmjs.com/package/siroc"> 6 - <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square"> 7 - </a> 8 - <a href="https://npmjs.com/package/siroc"> 9 - <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square"> 10 - </a> 11 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 12 - <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square"> 13 - </a> 14 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 15 - <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square"> 16 - </a> 17 - </p> 18 - 19 - > `siroc` is a zero-config but extensible framework for developing Node applications and libraries 20 - 21 - ## Features 22 - 23 - - 💯 **Zero-config required**: Intelligent support for your package 24 - - Supports running and compiling TypeScript and the latest JavaScript syntax 25 - - Autoconfigured `jest` and `eslint` 26 - - ⚒️ **Extensible**: Write your own commands and build hooks 27 - - 💪 **Typescript**: Fully typed and self-documenting 28 - 29 - **`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.** 30 - 31 - ## Quick start 32 - 33 - Just install `siroc`. 34 - 35 - ```bash 36 - # You can install siroc as a development dependency 37 - yarn add siroc --dev 38 - 39 - # ... or install globally 40 - yarn global add siroc 41 - ``` 42 - 43 - ## Configuration 44 - 45 - You can configure `siroc` by creating a `siroc.config.ts`, `siroc.config.js` or `siroc.config.json` file at the same level as your `package.json`. 46 - 47 - In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them. 48 - 49 - ### TypeScript 50 - 51 - ```ts 52 - import type { PackageOptions } from 'siroc' 53 - 54 - const config: PackageOptions = { 55 - // fully typed options 56 - } 57 - 58 - export default config 59 - ``` 60 - 61 - ### JavaScript 62 - 63 - ```js 64 - /** 65 - * @type {import('siroc').PackageOptions} config 66 - */ 67 - const config = { 68 - // fully typed options 69 - } 70 - 71 - export default config 72 - ``` 73 - 74 - ## Commands 75 - 76 - ### `siroc build` 77 - 78 - `siroc` knows what to build based on your `package.json`. 79 - 80 - By default, `siroc` will build your `src/index.js` or `src/index.ts` file into whatever output file is specified in your package.json's `main` field. 81 - 82 - If you have specified additional binaries, `siroc` will look for input files matching their names. 83 - 84 - Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files. 85 - 86 - #### Monorepos 87 - 88 - If you have enabled yarn workspaces, siroc will build each of your workspaces. You can choose to build only some of these by specifying what to build. 89 - 90 - ```bash 91 - yarn siroc build @mypackage/cli 92 - ``` 93 - 94 - #### Watch mode 95 - 96 - You can build in watch mode, which will rebuild as necessary when source files change: 97 - 98 - ``` 99 - yarn siroc build --watch 100 - ``` 101 - 102 - #### Configuration 103 - 104 - At the most basic level, your entrypoints are configured in your `package.json`: 105 - 106 - - `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin)) 107 - - `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main)) 108 - 109 - #### Build hooks 110 - 111 - `siroc` makes available three hooks for customising your build, if you need it. 112 - 113 - 1. `build:extend` 114 - 1. `build:extendRollup` 115 - 1. `build:done` 116 - 117 - ### `siroc dev` 118 - 119 - If you're working in a monorepo, it can be helpful to have accurate and up-to-date intellisense when importing from other libraries in a monorepo, without having to rebuild every time you make changes. 120 - 121 - Running `siroc dev` will replace your package entrypoints with stubs that point to your source files. Your binaries will run your source files directly using `jiti`. 122 - 123 - ### `siroc eslint` 124 - 125 - Rather than configure `eslint`, you can run it directly using `siroc eslint`, with support for TypeScript (and prettier, if you have it installed within your package dev dependencies). 126 - 127 - If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js` 128 - 129 - ```js 130 - module.exports = { 131 - extends: ['@siroc'], 132 - // Your rules/plugins here 133 - } 134 - ``` 135 - 136 - ### `siroc jest` 137 - 138 - Rather than configure `jest`, you can run it directly using `siroc jest`, with support for TypeScript test and source files. By default it will include any settings from a local `jest.config.js`. 139 - 140 - If you would like to extend or modify the siroc base config (for example, to run jest directly with `yarn jest`) you can do so with the following `jest.config.js` 141 - 142 - ```js 143 - module.exports = { 144 - preset: '@siroc/jest-preset', 145 - // Your customisations here 146 - } 147 - ``` 148 - 149 - ### `siroc run` 150 - 151 - You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti). 152 - 153 - For example: 154 - 155 - ```bash 156 - # You can run a node script written in TypeScript 157 - yarn siroc run myfile.ts 158 - 159 - # You can run a command in all your workspaces 160 - yarn siroc run ls --workspaces 161 - ``` 162 - 163 - ## Contributors 164 - 165 - Contributions are very welcome. 166 - 167 - 1. Clone this repo 168 - 169 - ```bash 170 - git clone git@github.com:nuxt-contrib/siroc.git 171 - ``` 172 - 173 - 2. Install dependencies and build project 174 - 175 - ```bash 176 - yarn 177 - 178 - # Stub modules for rapid development 179 - yarn siroc dev 180 - 181 - # Test (on changes) 182 - yarn siroc jest 183 - ``` 184 - 185 - **Tip:** You can also run `yarn link` within a package directory to test the module locally with another project. 186 - 187 - ## License 188 - 189 - [MIT License](./LICENCE) - Made with 💖
-48
packages/cli/package.json
··· 1 - { 2 - "name": "@siroc/cli", 3 - "version": "0.2.0", 4 - "description": "Zero-config build tooling for Node", 5 - "keywords": [ 6 - "node", 7 - "nodejs", 8 - "typescript", 9 - "javascript" 10 - ], 11 - "repository": "nuxt-contrib/siroc", 12 - "license": "MIT", 13 - "sideEffects": false, 14 - "bin": { 15 - "siroc": "bin/cli.js", 16 - "siroc-runner": "bin/runtime.js" 17 - }, 18 - "files": [ 19 - "dist/**/*", 20 - "!**/*.map" 21 - ], 22 - "scripts": { 23 - "build": "siroc build", 24 - "prepare": "yarn build", 25 - "prepublishOnly": "yarn test", 26 - "test": "run-s test:*", 27 - "test:unit": "siroc test", 28 - "watch": "yarn build -w" 29 - }, 30 - "dependencies": { 31 - "@siroc/core": "0.2.0", 32 - "cac": "^6.6.1", 33 - "chalk": "^4.1.0", 34 - "consola": "^2.14.0", 35 - "fs-extra": "^9.0.1", 36 - "jiti": "^0.1.11", 37 - "v8-compile-cache": "^2.1.1" 38 - }, 39 - "devDependencies": { 40 - "@types/fs-extra": "^9.0.1" 41 - }, 42 - "tsd": { 43 - "directory": "test/tsd", 44 - "compilerOptions": { 45 - "rootDir": "." 46 - } 47 - } 48 - }
-3
packages/core/.eslintignore
··· 1 - dist 2 - bin 3 - coverage
-189
packages/core/README.md
··· 1 - <h1 align="center">🌬️ siroc</h1> 2 - <p align="center">Zero-config build tooling for Node</p> 3 - 4 - <p align="center"> 5 - <a href="https://npmjs.com/package/siroc"> 6 - <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square"> 7 - </a> 8 - <a href="https://npmjs.com/package/siroc"> 9 - <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square"> 10 - </a> 11 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 12 - <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square"> 13 - </a> 14 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 15 - <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square"> 16 - </a> 17 - </p> 18 - 19 - > `siroc` is a zero-config but extensible framework for developing Node applications and libraries 20 - 21 - ## Features 22 - 23 - - 💯 **Zero-config required**: Intelligent support for your package 24 - - Supports running and compiling TypeScript and the latest JavaScript syntax 25 - - Autoconfigured `jest` and `eslint` 26 - - ⚒️ **Extensible**: Write your own commands and build hooks 27 - - 💪 **Typescript**: Fully typed and self-documenting 28 - 29 - **`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.** 30 - 31 - ## Quick start 32 - 33 - Just install `siroc`. 34 - 35 - ```bash 36 - # You can install siroc as a development dependency 37 - yarn add siroc --dev 38 - 39 - # ... or install globally 40 - yarn global add siroc 41 - ``` 42 - 43 - ## Configuration 44 - 45 - You can configure `siroc` by creating a `siroc.config.ts`, `siroc.config.js` or `siroc.config.json` file at the same level as your `package.json`. 46 - 47 - In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them. 48 - 49 - ### TypeScript 50 - 51 - ```ts 52 - import type { PackageOptions } from 'siroc' 53 - 54 - const config: PackageOptions = { 55 - // fully typed options 56 - } 57 - 58 - export default config 59 - ``` 60 - 61 - ### JavaScript 62 - 63 - ```js 64 - /** 65 - * @type {import('siroc').PackageOptions} config 66 - */ 67 - const config = { 68 - // fully typed options 69 - } 70 - 71 - export default config 72 - ``` 73 - 74 - ## Commands 75 - 76 - ### `siroc build` 77 - 78 - `siroc` knows what to build based on your `package.json`. 79 - 80 - By default, `siroc` will build your `src/index.js` or `src/index.ts` file into whatever output file is specified in your package.json's `main` field. 81 - 82 - If you have specified additional binaries, `siroc` will look for input files matching their names. 83 - 84 - Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files. 85 - 86 - #### Monorepos 87 - 88 - If you have enabled yarn workspaces, siroc will build each of your workspaces. You can choose to build only some of these by specifying what to build. 89 - 90 - ```bash 91 - yarn siroc build @mypackage/cli 92 - ``` 93 - 94 - #### Watch mode 95 - 96 - You can build in watch mode, which will rebuild as necessary when source files change: 97 - 98 - ``` 99 - yarn siroc build --watch 100 - ``` 101 - 102 - #### Configuration 103 - 104 - At the most basic level, your entrypoints are configured in your `package.json`: 105 - 106 - - `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin)) 107 - - `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main)) 108 - 109 - #### Build hooks 110 - 111 - `siroc` makes available three hooks for customising your build, if you need it. 112 - 113 - 1. `build:extend` 114 - 1. `build:extendRollup` 115 - 1. `build:done` 116 - 117 - ### `siroc dev` 118 - 119 - If you're working in a monorepo, it can be helpful to have accurate and up-to-date intellisense when importing from other libraries in a monorepo, without having to rebuild every time you make changes. 120 - 121 - Running `siroc dev` will replace your package entrypoints with stubs that point to your source files. Your binaries will run your source files directly using `jiti`. 122 - 123 - ### `siroc eslint` 124 - 125 - Rather than configure `eslint`, you can run it directly using `siroc eslint`, with support for TypeScript (and prettier, if you have it installed within your package dev dependencies). 126 - 127 - If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js` 128 - 129 - ```js 130 - module.exports = { 131 - extends: ['@siroc'], 132 - // Your rules/plugins here 133 - } 134 - ``` 135 - 136 - ### `siroc jest` 137 - 138 - Rather than configure `jest`, you can run it directly using `siroc jest`, with support for TypeScript test and source files. By default it will include any settings from a local `jest.config.js`. 139 - 140 - If you would like to extend or modify the siroc base config (for example, to run jest directly with `yarn jest`) you can do so with the following `jest.config.js` 141 - 142 - ```js 143 - module.exports = { 144 - preset: '@siroc/jest-preset', 145 - // Your customisations here 146 - } 147 - ``` 148 - 149 - ### `siroc run` 150 - 151 - You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti). 152 - 153 - For example: 154 - 155 - ```bash 156 - # You can run a node script written in TypeScript 157 - yarn siroc run myfile.ts 158 - 159 - # You can run a command in all your workspaces 160 - yarn siroc run ls --workspaces 161 - ``` 162 - 163 - ## Contributors 164 - 165 - Contributions are very welcome. 166 - 167 - 1. Clone this repo 168 - 169 - ```bash 170 - git clone git@github.com:nuxt-contrib/siroc.git 171 - ``` 172 - 173 - 2. Install dependencies and build project 174 - 175 - ```bash 176 - yarn 177 - 178 - # Stub modules for rapid development 179 - yarn siroc dev 180 - 181 - # Test (on changes) 182 - yarn siroc jest 183 - ``` 184 - 185 - **Tip:** You can also run `yarn link` within a package directory to test the module locally with another project. 186 - 187 - ## License 188 - 189 - [MIT License](./LICENCE) - Made with 💖
-59
packages/core/package.json
··· 1 - { 2 - "name": "@siroc/core", 3 - "version": "0.2.0", 4 - "description": "Zero-config build tooling for Node", 5 - "keywords": [ 6 - "node", 7 - "nodejs", 8 - "typescript", 9 - "javascript" 10 - ], 11 - "repository": "nuxt-contrib/siroc", 12 - "license": "MIT", 13 - "sideEffects": false, 14 - "main": "dist/index.js", 15 - "module": "dist/index.es.js", 16 - "types": "dist/index.d.ts", 17 - "files": [ 18 - "dist/**/*", 19 - "dist/index.d.ts", 20 - "!**/*.map" 21 - ], 22 - "scripts": { 23 - "build": "siroc build", 24 - "prepare": "yarn build", 25 - "prepublishOnly": "yarn test", 26 - "test": "run-s test:*", 27 - "test:types": "tsd", 28 - "test:unit": "siroc test", 29 - "watch": "yarn build -w" 30 - }, 31 - "dependencies": { 32 - "@rollup/plugin-alias": "^3.1.1", 33 - "@rollup/plugin-commonjs": "^14.0.0", 34 - "@rollup/plugin-json": "^4.1.0", 35 - "@rollup/plugin-node-resolve": "^8.4.0", 36 - "@rollup/plugin-replace": "^2.3.3", 37 - "chalk": "^4.1.0", 38 - "consola": "^2.14.0", 39 - "defu": "^2.0.4", 40 - "execa": "^4.0.2", 41 - "fs-extra": "^9.0.1", 42 - "glob": "^7.1.6", 43 - "jiti": "^0.1.11", 44 - "rollup": "^2.23.0", 45 - "rollup-plugin-dts": "^1.4.9", 46 - "rollup-plugin-esbuild": "2.2.0", 47 - "sort-package-json": "^1.44.0" 48 - }, 49 - "devDependencies": { 50 - "@types/execa": "^2.0.0", 51 - "@types/fs-extra": "^9.0.1" 52 - }, 53 - "tsd": { 54 - "directory": "test/tsd", 55 - "compilerOptions": { 56 - "rootDir": "." 57 - } 58 - } 59 - }
-14
packages/eslint-config/.eslintrc.js
··· 1 - // eslint-disable-next-line 2 - const { join } = require('path') 3 - 4 - let eslintConfig = {} 5 - const eslintConfigPath = join(process.cwd(), '.eslintrc.js') 6 - 7 - try { 8 - eslintConfig = require(eslintConfigPath) 9 - // eslint-disable-next-line 10 - } catch {} 11 - 12 - module.exports = eslintConfig || { 13 - extends: '@siroc', 14 - }
-31
packages/eslint-config/index.js
··· 1 - var prettier = false 2 - try { 3 - prettier = !!require('prettier') 4 - // eslint-disable-next-line 5 - } catch {} 6 - 7 - module.exports = { 8 - env: { 9 - browser: false, 10 - es6: true, 11 - node: true, 12 - 'jest/globals': true, 13 - }, 14 - parser: '@typescript-eslint/parser', 15 - parserOptions: { 16 - sourceType: 'module', 17 - }, 18 - plugins: ['jest', '@typescript-eslint'], 19 - extends: [ 20 - 'plugin:@typescript-eslint/recommended', 21 - 'eslint:recommended', 22 - ...(prettier 23 - ? [ 24 - 'plugin:prettier/recommended', 25 - 'prettier', 26 - 'prettier/@typescript-eslint', 27 - ] 28 - : []), 29 - 'plugin:jest/recommended', 30 - ], 31 - }
-30
packages/eslint-config/package.json
··· 1 - { 2 - "name": "@siroc/eslint-config", 3 - "version": "0.2.0", 4 - "description": "Zero-config build tooling for Node", 5 - "keywords": [ 6 - "eslint", 7 - "eslintconfig", 8 - "node", 9 - "nodejs", 10 - "typescript", 11 - "javascript" 12 - ], 13 - "repository": "nuxt-contrib/siroc", 14 - "license": "MIT", 15 - "sideEffects": false, 16 - "main": "index.js", 17 - "files": [ 18 - "index.js", 19 - ".eslintrc.js" 20 - ], 21 - "dependencies": { 22 - "@typescript-eslint/eslint-plugin": "^3.7.0", 23 - "@typescript-eslint/parser": "^3.7.0", 24 - "eslint": "^7.5.0", 25 - "eslint-config-prettier": "^6.11.0", 26 - "eslint-plugin-jest": "^23.18.2", 27 - "eslint-plugin-prettier": "^3.1.4", 28 - "eslint-plugin-promise": "^4.2.1" 29 - } 30 - }
-3
packages/eslint-config/siroc.config.js
··· 1 - export default { 2 - build: false, 3 - }
-6
packages/jest-preset/.eslintrc.js
··· 1 - module.exports = { 2 - rules: { 3 - '@typescript-eslint/no-var-requires': 0, 4 - 'no-empty': 0, 5 - }, 6 - }
-17
packages/jest-preset/jest-preset.js
··· 1 - module.exports = { 2 - coveragePathIgnorePatterns: [ 3 - '[\\/]dist[\\/]', 4 - '.*\\.spec.ts', 5 - '.*\\.config.js', 6 - '.babelrc.js', 7 - ], 8 - transform: { 9 - '\\.(js|ts)$': [ 10 - 'babel-jest', 11 - { 12 - presets: ['@babel/preset-env', '@babel/preset-typescript'], 13 - plugins: ['@babel/plugin-transform-runtime'], 14 - }, 15 - ], 16 - }, 17 - }
-20
packages/jest-preset/jest.config.js
··· 1 - // eslint-disable-next-line 2 - const { join } = require('path') 3 - 4 - let jestConfig = {} 5 - const jestConfigPath = join(process.cwd(), 'jest.config.js') 6 - 7 - try { 8 - // eslint-disable-next-line 9 - jestConfig = require(jestConfigPath) 10 - // eslint-disable-next-line 11 - } catch {} 12 - 13 - module.exports = { 14 - rootDir: process.cwd(), 15 - preset: '@siroc/jest-preset', 16 - verbose: true, 17 - testEnvironment: 'node', 18 - collectCoverage: true, 19 - ...jestConfig, 20 - }
-29
packages/jest-preset/package.json
··· 1 - { 2 - "name": "@siroc/jest-preset", 3 - "version": "0.2.0", 4 - "description": "Zero-config build tooling for Node", 5 - "keywords": [ 6 - "jest", 7 - "jest-preset", 8 - "node", 9 - "nodejs", 10 - "typescript", 11 - "javascript" 12 - ], 13 - "repository": "nuxt-contrib/siroc", 14 - "license": "MIT", 15 - "sideEffects": false, 16 - "main": "jest-preset.js", 17 - "files": [ 18 - "jest-preset.js", 19 - "jest.config.js" 20 - ], 21 - "dependencies": { 22 - "@babel/plugin-transform-runtime": "^7.10.5", 23 - "@babel/preset-env": "^7.10.4", 24 - "@babel/preset-typescript": "^7.10.4", 25 - "@types/jest": "^26.0.7", 26 - "babel-loader": "^8.1.0", 27 - "jest": "^26.1.0" 28 - } 29 - }
-3
packages/jest-preset/siroc.config.js
··· 1 - export default { 2 - build: false, 3 - }
-3
packages/siroc/.eslintignore
··· 1 - dist 2 - bin 3 - coverage
-189
packages/siroc/README.md
··· 1 - <h1 align="center">🌬️ siroc</h1> 2 - <p align="center">Zero-config build tooling for Node</p> 3 - 4 - <p align="center"> 5 - <a href="https://npmjs.com/package/siroc"> 6 - <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square"> 7 - </a> 8 - <a href="https://npmjs.com/package/siroc"> 9 - <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square"> 10 - </a> 11 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 12 - <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square"> 13 - </a> 14 - <a href="https://lgtm.com/projects/g/nuxt-contrib/siroc"> 15 - <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square"> 16 - </a> 17 - </p> 18 - 19 - > `siroc` is a zero-config but extensible framework for developing Node applications and libraries 20 - 21 - ## Features 22 - 23 - - 💯 **Zero-config required**: Intelligent support for your package 24 - - Supports running and compiling TypeScript and the latest JavaScript syntax 25 - - Autoconfigured `jest` and `eslint` 26 - - ⚒️ **Extensible**: Write your own commands and build hooks 27 - - 💪 **Typescript**: Fully typed and self-documenting 28 - 29 - **`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.** 30 - 31 - ## Quick start 32 - 33 - Just install `siroc`. 34 - 35 - ```bash 36 - # You can install siroc as a development dependency 37 - yarn add siroc --dev 38 - 39 - # ... or install globally 40 - yarn global add siroc 41 - ``` 42 - 43 - ## Configuration 44 - 45 - You can configure `siroc` by creating a `siroc.config.ts`, `siroc.config.js` or `siroc.config.json` file at the same level as your `package.json`. 46 - 47 - In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them. 48 - 49 - ### TypeScript 50 - 51 - ```ts 52 - import type { PackageOptions } from 'siroc' 53 - 54 - const config: PackageOptions = { 55 - // fully typed options 56 - } 57 - 58 - export default config 59 - ``` 60 - 61 - ### JavaScript 62 - 63 - ```js 64 - /** 65 - * @type {import('siroc').PackageOptions} config 66 - */ 67 - const config = { 68 - // fully typed options 69 - } 70 - 71 - export default config 72 - ``` 73 - 74 - ## Commands 75 - 76 - ### `siroc build` 77 - 78 - `siroc` knows what to build based on your `package.json`. 79 - 80 - By default, `siroc` will build your `src/index.js` or `src/index.ts` file into whatever output file is specified in your package.json's `main` field. 81 - 82 - If you have specified additional binaries, `siroc` will look for input files matching their names. 83 - 84 - Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files. 85 - 86 - #### Monorepos 87 - 88 - If you have enabled yarn workspaces, siroc will build each of your workspaces. You can choose to build only some of these by specifying what to build. 89 - 90 - ```bash 91 - yarn siroc build @mypackage/cli 92 - ``` 93 - 94 - #### Watch mode 95 - 96 - You can build in watch mode, which will rebuild as necessary when source files change: 97 - 98 - ``` 99 - yarn siroc build --watch 100 - ``` 101 - 102 - #### Configuration 103 - 104 - At the most basic level, your entrypoints are configured in your `package.json`: 105 - 106 - - `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin)) 107 - - `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main)) 108 - 109 - #### Build hooks 110 - 111 - `siroc` makes available three hooks for customising your build, if you need it. 112 - 113 - 1. `build:extend` 114 - 1. `build:extendRollup` 115 - 1. `build:done` 116 - 117 - ### `siroc dev` 118 - 119 - If you're working in a monorepo, it can be helpful to have accurate and up-to-date intellisense when importing from other libraries in a monorepo, without having to rebuild every time you make changes. 120 - 121 - Running `siroc dev` will replace your package entrypoints with stubs that point to your source files. Your binaries will run your source files directly using `jiti`. 122 - 123 - ### `siroc eslint` 124 - 125 - Rather than configure `eslint`, you can run it directly using `siroc eslint`, with support for TypeScript (and prettier, if you have it installed within your package dev dependencies). 126 - 127 - If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js` 128 - 129 - ```js 130 - module.exports = { 131 - extends: ['@siroc'], 132 - // Your rules/plugins here 133 - } 134 - ``` 135 - 136 - ### `siroc jest` 137 - 138 - Rather than configure `jest`, you can run it directly using `siroc jest`, with support for TypeScript test and source files. By default it will include any settings from a local `jest.config.js`. 139 - 140 - If you would like to extend or modify the siroc base config (for example, to run jest directly with `yarn jest`) you can do so with the following `jest.config.js` 141 - 142 - ```js 143 - module.exports = { 144 - preset: '@siroc/jest-preset', 145 - // Your customisations here 146 - } 147 - ``` 148 - 149 - ### `siroc run` 150 - 151 - You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti). 152 - 153 - For example: 154 - 155 - ```bash 156 - # You can run a node script written in TypeScript 157 - yarn siroc run myfile.ts 158 - 159 - # You can run a command in all your workspaces 160 - yarn siroc run ls --workspaces 161 - ``` 162 - 163 - ## Contributors 164 - 165 - Contributions are very welcome. 166 - 167 - 1. Clone this repo 168 - 169 - ```bash 170 - git clone git@github.com:nuxt-contrib/siroc.git 171 - ``` 172 - 173 - 2. Install dependencies and build project 174 - 175 - ```bash 176 - yarn 177 - 178 - # Stub modules for rapid development 179 - yarn siroc dev 180 - 181 - # Test (on changes) 182 - yarn siroc jest 183 - ``` 184 - 185 - **Tip:** You can also run `yarn link` within a package directory to test the module locally with another project. 186 - 187 - ## License 188 - 189 - [MIT License](./LICENCE) - Made with 💖
-35
packages/siroc/package.json
··· 1 - { 2 - "name": "siroc", 3 - "version": "0.2.0", 4 - "description": "Zero-config build tooling for Node", 5 - "keywords": [ 6 - "node", 7 - "nodejs", 8 - "typescript", 9 - "javascript" 10 - ], 11 - "repository": "nuxt-contrib/siroc", 12 - "license": "MIT", 13 - "sideEffects": false, 14 - "main": "dist/index.js", 15 - "module": "dist/index.es.js", 16 - "types": "dist/index.d.ts", 17 - "files": [ 18 - "dist/**/*", 19 - "dist/index.d.ts", 20 - "!**/*.map" 21 - ], 22 - "scripts": { 23 - "build": "siroc build", 24 - "prepare": "yarn build", 25 - "prepublishOnly": "yarn test", 26 - "test": "run-s test:*", 27 - "test:unit": "siroc test", 28 - "watch": "yarn build -w" 29 - }, 30 - "dependencies": { 31 - "@siroc/cli": "0.2.0", 32 - "@siroc/core": "0.2.0", 33 - "typescript": "^3.9.7" 34 - } 35 - }
-8
packages/siroc/siroc.config.js
··· 1 - // @ts-check 2 - /** 3 - * @type {import('@siroc/core').PackageOptions} config 4 - */ 5 - const config = { 6 - build: true, 7 - } 8 - export default config
+103
src/cli/index.ts
··· 1 + import 'v8-compile-cache' 2 + import { PerformanceObserver, performance } from 'perf_hooks' 3 + 4 + import { Package } from '../core' 5 + import cac from 'cac' 6 + import { bold } from 'chalk' 7 + import consola from 'consola' 8 + 9 + import { version } from '../../package.json' 10 + 11 + import { build, BuildCommandOptions } from './commands/build' 12 + import { dev, DevCommandOptions } from './commands/dev' 13 + import { run as runFile } from './commands/run' 14 + 15 + import { time, timeEnd, RemoveFirst } from './utils' 16 + 17 + time('load root package') 18 + let rootPackage: Package 19 + try { 20 + rootPackage = new Package() 21 + } catch (e) { 22 + throw new Error(`Couldn't load package: ${e}`) 23 + } 24 + timeEnd('load root package') 25 + 26 + const exampleProject = rootPackage.pkg.name || '@siroc/cli' 27 + 28 + const obs = new PerformanceObserver(items => { 29 + const { duration, name } = items.getEntries()[0] 30 + const seconds = (duration / 1000).toFixed(1) 31 + const time = duration > 1000 ? seconds + 's' : Math.round(duration) + 'ms' 32 + rootPackage.logger.success(`${name} in ${bold(time)}`) 33 + }) 34 + obs.observe({ entryTypes: ['measure'] }) 35 + 36 + time('load CLI') 37 + const cli = cac('siroc') 38 + 39 + const run = async < 40 + A extends (pkg: Package, ...args: any[]) => void | Promise<void> 41 + >( 42 + type: string, 43 + action: A, 44 + ...args: RemoveFirst<Parameters<A>> 45 + ) => { 46 + performance.mark(`Start ${type}`) 47 + await Promise.resolve(action(rootPackage, ...args)).catch(err => { 48 + rootPackage.logger.error(err) 49 + process.exit(1) 50 + }) 51 + performance.mark(`Stop ${type}`) 52 + performance.measure(`Finished ${type}`, `Start ${type}`, `Stop ${type}`) 53 + } 54 + 55 + cli 56 + .command('build [...packages]', 'Bundle input files') 57 + .option('-w, --watch', 'Watch files in bundle and rebuild on changes', { 58 + default: false, 59 + }) 60 + .option('--dev', 'Build development bundle (only CJS)', { 61 + default: false, 62 + }) 63 + .example(bin => ` ${bin} build`) 64 + .example(bin => ` ${bin} build ${exampleProject} -w`) 65 + .action((packages: string[], options: BuildCommandOptions) => 66 + run('building', build, { ...options, packages }) 67 + ) 68 + 69 + cli 70 + .command('dev [...packages]', 'Generate package stubs for quick development') 71 + .example(bin => ` ${bin} dev`) 72 + .example(bin => ` ${bin} dev ${exampleProject} -w`) 73 + .action((packages: string[], options: DevCommandOptions) => 74 + run('stubbing', dev, { ...options, packages }) 75 + ) 76 + 77 + cli 78 + .command('run <file> [...args]', 'Run Node script') 79 + .allowUnknownOptions() 80 + .option('-w, --workspaces', 'Run command in all yarn workspaces.') 81 + .option('-s, --sequential', 'Run sequentially rather than in paralle.') 82 + .example(bin => ` ${bin} src/test.ts`) 83 + .example(bin => ` ${bin} --workspaces ls`) 84 + .action((file, args, options) => 85 + run('running', runFile, { file, args, options }) 86 + ) 87 + 88 + Object.entries(rootPackage.options.commands).forEach(([command, action]) => { 89 + cli 90 + .command(`${command}`, `Custom command (${bold(rootPackage.pkg.name)})`) 91 + .action(() => run(command, action)) 92 + }) 93 + 94 + cli.version(version) 95 + cli.help() 96 + timeEnd('load CLI') 97 + 98 + cli.parse() 99 + 100 + process.on('unhandledRejection', err => { 101 + consola.error(err) 102 + process.exit(1) 103 + })
+5
src/cli/runtime.ts
··· 1 + import _jiti from 'jiti' 2 + 3 + const jiti = _jiti() 4 + 5 + jiti(process.argv[2])
+10
src/cli/utils.ts
··· 1 + export const time = (id: string) => { 2 + if (process.env.TIME) console.time(id) 3 + } 4 + export const timeEnd = (id: string) => { 5 + if (process.env.TIME) console.timeEnd(id) 6 + } 7 + 8 + export type RemoveFirst<T extends Array<any>> = T[1] extends undefined 9 + ? never[] 10 + : [T[1]]
+3
src/core/index.ts
··· 1 + export * from './build' 2 + export * from './package' 3 + export * from './utils'
-103
packages/cli/src/index.ts
··· 1 - import 'v8-compile-cache' 2 - import { PerformanceObserver, performance } from 'perf_hooks' 3 - 4 - import { Package } from '@siroc/core' 5 - import cac from 'cac' 6 - import { bold } from 'chalk' 7 - import consola from 'consola' 8 - 9 - import { version } from '../package.json' 10 - 11 - import { build, BuildCommandOptions } from './commands/build' 12 - import { dev, DevCommandOptions } from './commands/dev' 13 - import { run as runFile } from './commands/run' 14 - 15 - import { time, timeEnd, RemoveFirst } from './utils' 16 - 17 - time('load root package') 18 - let rootPackage: Package 19 - try { 20 - rootPackage = new Package() 21 - } catch (e) { 22 - throw new Error(`Couldn't load package: ${e}`) 23 - } 24 - timeEnd('load root package') 25 - 26 - const exampleProject = rootPackage.pkg.name || '@siroc/cli' 27 - 28 - const obs = new PerformanceObserver(items => { 29 - const { duration, name } = items.getEntries()[0] 30 - const seconds = (duration / 1000).toFixed(1) 31 - const time = duration > 1000 ? seconds + 's' : Math.round(duration) + 'ms' 32 - rootPackage.logger.success(`${name} in ${bold(time)}`) 33 - }) 34 - obs.observe({ entryTypes: ['measure'] }) 35 - 36 - time('load CLI') 37 - const cli = cac('siroc') 38 - 39 - const run = async < 40 - A extends (pkg: Package, ...args: any[]) => void | Promise<void> 41 - >( 42 - type: string, 43 - action: A, 44 - ...args: RemoveFirst<Parameters<A>> 45 - ) => { 46 - performance.mark(`Start ${type}`) 47 - await Promise.resolve(action(rootPackage, ...args)).catch(err => { 48 - rootPackage.logger.error(err) 49 - process.exit(1) 50 - }) 51 - performance.mark(`Stop ${type}`) 52 - performance.measure(`Finished ${type}`, `Start ${type}`, `Stop ${type}`) 53 - } 54 - 55 - cli 56 - .command('build [...packages]', 'Bundle input files') 57 - .option('-w, --watch', 'Watch files in bundle and rebuild on changes', { 58 - default: false, 59 - }) 60 - .option('--dev', 'Build development bundle (only CJS)', { 61 - default: false, 62 - }) 63 - .example(bin => ` ${bin} build`) 64 - .example(bin => ` ${bin} build ${exampleProject} -w`) 65 - .action((packages: string[], options: BuildCommandOptions) => 66 - run('building', build, { ...options, packages }) 67 - ) 68 - 69 - cli 70 - .command('dev [...packages]', 'Generate package stubs for quick development') 71 - .example(bin => ` ${bin} dev`) 72 - .example(bin => ` ${bin} dev ${exampleProject} -w`) 73 - .action((packages: string[], options: DevCommandOptions) => 74 - run('stubbing', dev, { ...options, packages }) 75 - ) 76 - 77 - cli 78 - .command('run <file> [...args]', 'Run Node script') 79 - .allowUnknownOptions() 80 - .option('-w, --workspaces', 'Run command in all yarn workspaces.') 81 - .option('-s, --sequential', 'Run sequentially rather than in paralle.') 82 - .example(bin => ` ${bin} src/test.ts`) 83 - .example(bin => ` ${bin} --workspaces ls`) 84 - .action((file, args, options) => 85 - run('running', runFile, { file, args, options }) 86 - ) 87 - 88 - Object.entries(rootPackage.options.commands).forEach(([command, action]) => { 89 - cli 90 - .command(`${command}`, `Custom command (${bold(rootPackage.pkg.name)})`) 91 - .action(() => run(command, action)) 92 - }) 93 - 94 - cli.version(version) 95 - cli.help() 96 - timeEnd('load CLI') 97 - 98 - cli.parse() 99 - 100 - process.on('unhandledRejection', err => { 101 - consola.error(err) 102 - process.exit(1) 103 - })
-5
packages/cli/src/runtime.ts
··· 1 - import _jiti from 'jiti' 2 - 3 - const jiti = _jiti() 4 - 5 - jiti(process.argv[2])
-10
packages/cli/src/utils.ts
··· 1 - export const time = (id: string) => { 2 - if (process.env.TIME) console.time(id) 3 - } 4 - export const timeEnd = (id: string) => { 5 - if (process.env.TIME) console.timeEnd(id) 6 - } 7 - 8 - export type RemoveFirst<T extends Array<any>> = T[1] extends undefined 9 - ? never[] 10 - : [T[1]]
-4
packages/core/src/index.ts
··· 1 - export * from './build' 2 - export * from './package' 3 - export * from './utils' 4 - export * from './version/changelog'
-1
packages/siroc/src/index.ts
··· 1 - export * from '@siroc/core'
+60
src/cli/commands/build.ts
··· 1 + import { 2 + build as buildPackage, 3 + Package, 4 + removeBuildFolders, 5 + runInParallel, 6 + BuildOptions, 7 + } from '../../core' 8 + 9 + export interface BuildCommandOptions extends BuildOptions { 10 + packages: string[] 11 + } 12 + 13 + export async function build( 14 + rootPackage: Package, 15 + { packages, ...options }: BuildCommandOptions 16 + ) { 17 + const workspacePackages = await rootPackage.getWorkspacePackages( 18 + packages.length ? packages : undefined 19 + ) 20 + 21 + const { watch } = options 22 + rootPackage.logger.info(`Beginning build${watch ? ' (watching)' : ''}`) 23 + 24 + // Universal linkedDependencies based on workspace 25 + const linkedDependencies = workspacePackages.map(p => 26 + p.pkg.name.replace(p.options.suffix, '') 27 + ) 28 + 29 + // Create package stubs so we can build in parallel 30 + await runInParallel(workspacePackages, async pkg => { 31 + await removeBuildFolders(pkg) 32 + await pkg.createStubs() 33 + }) 34 + 35 + await runInParallel(workspacePackages, async pkg => { 36 + pkg.options.linkedDependencies = ( 37 + pkg.options.linkedDependencies || [] 38 + ).concat(linkedDependencies) 39 + 40 + // Step 1: Apply suffixes 41 + if (pkg.options.suffix && pkg.options.suffix.length) { 42 + pkg.suffixAndVersion() 43 + await pkg.writePackage() 44 + } 45 + 46 + // Step 2: Build packages 47 + if (pkg.options.build) { 48 + if (watch) { 49 + buildPackage(pkg, { dev: true, ...options, watch }) 50 + } else { 51 + await buildPackage(pkg, options) 52 + } 53 + } 54 + 55 + // Step 3: Link dependencies and Fix packages 56 + pkg.syncLinkedDependencies() 57 + pkg.autoFix() 58 + await Promise.all([pkg.setBinaryPermissions(), pkg.writePackage()]) 59 + }) 60 + }
+16
src/cli/commands/dev.ts
··· 1 + import { Package, runInParallel } from '../../core' 2 + 3 + export interface DevCommandOptions { 4 + packages: string[] 5 + } 6 + 7 + export async function dev( 8 + rootPackage: Package, 9 + { packages }: DevCommandOptions 10 + ) { 11 + const workspacePackages = await rootPackage.getWorkspacePackages( 12 + packages.length ? packages : undefined 13 + ) 14 + 15 + await runInParallel(workspacePackages, async pkg => pkg.createStubs()) 16 + }
+23
src/cli/commands/release.ts
··· 1 + export const publish = ` 2 + #!/bin/bash 3 + set -e 4 + 5 + if [ ! "$1" ]; then 6 + echo "Usage $0 [version]" 7 + exit 1 8 + fi 9 + 10 + yarn build 11 + 12 + ./scripts/workspace-run npm publish -q 13 + 14 + git tag -a v$1 -m v$1 15 + git push --tags 16 + ` 17 + 18 + export const version = ` 19 + #!/bin/bash 20 + set -e 21 + 22 + yarn lerna version --no-changelog --no-git-tag-version --no-push --force-publish "*" 23 + `
+55
src/cli/commands/run.ts
··· 1 + import { resolve } from 'path' 2 + 3 + import { Package, runInParallel } from '../../core' 4 + import { bold, gray } from 'chalk' 5 + import { existsSync } from 'fs-extra' 6 + 7 + interface RunCommandOptions { 8 + file: string 9 + args: string[] 10 + options: { 11 + workspaces?: boolean 12 + sequential?: boolean 13 + } 14 + } 15 + 16 + export async function run( 17 + rootPackage: Package, 18 + { file, args, options: { workspaces, sequential } }: RunCommandOptions 19 + ) { 20 + const fullCommand = `${file} ${args.join()}`.trim() 21 + const filepath = resolve(process.cwd(), file) 22 + const isLocalFile = 23 + (file.endsWith('.js') || file.endsWith('.ts')) && existsSync(filepath) 24 + 25 + function runCommand(pkg: Package) { 26 + try { 27 + if (isLocalFile) 28 + return pkg.execInteractive( 29 + `yarn siroc-runner ${filepath} ${args.join()}` 30 + ) 31 + 32 + const { stdout } = pkg.exec(fullCommand, { 33 + silent: true, 34 + }) 35 + 36 + pkg.logger.success( 37 + `Ran ${bold(fullCommand)} in ${bold(pkg.pkg.name)}.`, 38 + stdout ? '\n' : '', 39 + stdout ? gray(stdout) : '' 40 + ) 41 + } catch (e) { 42 + pkg.logger.error(`Error running ${bold(fullCommand)}\n`, gray(e)) 43 + } 44 + } 45 + 46 + const packages = workspaces 47 + ? await rootPackage.getWorkspacePackages() 48 + : [rootPackage] 49 + 50 + if (!sequential) { 51 + runInParallel(packages, runCommand) 52 + } else { 53 + packages.forEach(runCommand) 54 + } 55 + }
+24
src/core/build/builtins.ts
··· 1 + /* 2 + ** Core logic from https://github.com/sindresorhus/builtin-modules 3 + ** Many thanks to @sindresorhus 4 + */ 5 + import Module from 'module' 6 + 7 + const blacklist = ['sys'] 8 + 9 + export const builtins = Module.builtinModules 10 + .filter( 11 + x => 12 + !/^_|^(internal|v8|node-inspect)\/|\//.test(x) && !blacklist.includes(x) 13 + ) 14 + .sort() 15 + 16 + let builtinsObj: Record<string, boolean> 17 + 18 + const convertToObj = () => 19 + builtins.reduce((obj, builtin) => { 20 + obj[builtin] = true 21 + return obj 22 + }, (builtinsObj = {} as Record<string, boolean>)) 23 + 24 + export const builtinsMap = () => builtinsObj || convertToObj()
+30
src/core/build/hooks.ts
··· 1 + import type { RollupBuild, RollupOptions } from 'rollup' 2 + 3 + import type { Package } from '../package' 4 + import type { RequireProperties } from '../utils' 5 + import type { BuildConfigOptions } from './rollup' 6 + 7 + export type Hook<T> = { 8 + (pkg: Package, options: T): Promise<void> | void 9 + } 10 + 11 + export type Hooks<T extends Record<string, any>> = { 12 + [P in keyof T]?: Hook<T[P]> | Array<Hook<T[P]>> 13 + } 14 + 15 + export type PackageHookOptions = { 16 + 'build:extend': { 17 + config: RequireProperties<BuildConfigOptions, 'alias' | 'replace'> 18 + } 19 + 'build:extendRollup': { 20 + rollupConfig: RollupOptions[] 21 + } 22 + 'build:done': { bundle: RollupBuild } 23 + } 24 + 25 + export type PackageHooks = Hooks<PackageHookOptions> 26 + 27 + export type PackageCommands = Record< 28 + string, 29 + (pkg: Package) => void | Promise<void> 30 + >
+161
src/core/build/index.ts
··· 1 + import { dirname, join } from 'path' 2 + 3 + import { bold, gray, green } from 'chalk' 4 + import { remove, stat } from 'fs-extra' 5 + import { rollup, watch, RollupError } from 'rollup' 6 + 7 + import type { BuildOptions, Package } from '../package' 8 + import { 9 + asArray, 10 + ensureUnique, 11 + includeIf, 12 + runInParallel, 13 + RequireProperties, 14 + } from '../utils' 15 + import { getRollupConfig, BuildConfigOptions } from './rollup' 16 + 17 + export * from './hooks' 18 + export * from './rollup' 19 + 20 + /** 21 + * Remove folders for build destinations 22 + */ 23 + export async function removeBuildFolders(pkg: Package) { 24 + const directories = ensureUnique( 25 + [ 26 + ...pkg.binaries.map(([bin]) => bin), 27 + ...includeIf(pkg.pkg.main, main => main), 28 + ] 29 + .map(file => dirname(file)) 30 + .filter(dir => !dir.includes('src')) 31 + ) 32 + 33 + await runInParallel(directories, remove) 34 + } 35 + 36 + export const build = async ( 37 + pkg: Package, 38 + { watch: shouldWatch = false, dev = shouldWatch }: BuildOptions = {} 39 + ) => { 40 + const { 41 + options: { suffix, linkedDependencies, rootDir, rollup: rollupOptions }, 42 + } = pkg 43 + // Prepare rollup config 44 + const config: RequireProperties<BuildConfigOptions, 'alias' | 'replace'> = { 45 + alias: {}, 46 + replace: {}, 47 + dev, 48 + shouldWatch, 49 + ...rollupOptions, 50 + } 51 + 52 + // Replace linkedDependencies with their suffixed version 53 + if (suffix) { 54 + for (const _name of linkedDependencies || []) { 55 + const name = _name + suffix 56 + config.replace[`'${_name}'`] = `'${name}'` 57 + config.alias[_name] = name 58 + } 59 + } 60 + 61 + // Allow extending config 62 + await pkg.callHook('build:extend', { config }) 63 + 64 + // Create rollup config 65 + const rollupConfig = getRollupConfig(config, pkg) 66 + 67 + // Allow extending rollup config 68 + await pkg.callHook('build:extendRollup', { 69 + rollupConfig, 70 + }) 71 + 72 + if (shouldWatch) { 73 + // Watch 74 + const watcher = watch(rollupConfig) 75 + watcher.on('event', event => { 76 + switch (event.code) { 77 + // The watcher is (re)starting 78 + case 'START': 79 + return pkg.logger.debug(`Watching ${pkg.pkg.name} for changes`) 80 + 81 + // Building an individual bundle 82 + case 'BUNDLE_START': 83 + return pkg.logger.debug(`Building ${pkg.pkg.name}`) 84 + 85 + // Finished building a bundle 86 + case 'BUNDLE_END': 87 + return 88 + 89 + // Finished building all bundles 90 + case 'END': 91 + return pkg.logger.success(`Built ${bold(pkg.pkg.name)}`) 92 + 93 + // Encountered an error while bundling 94 + case 'ERROR': 95 + formatError(rootDir, event.error) 96 + return pkg.logger.error(event.error) 97 + 98 + // Unknown event 99 + default: 100 + // If rollup adds more events, TypeScript will let us know 101 + // eslint-disable-next-line 102 + const _event: never = event 103 + return pkg.logger.info(JSON.stringify(_event)) 104 + } 105 + }) 106 + } else { 107 + // Build 108 + pkg.logger.debug(`Building ${pkg.pkg.name}`) 109 + await runInParallel(rollupConfig, async config => { 110 + try { 111 + const bundle = await rollup(config) 112 + await runInParallel(asArray(config.output), async outputConfig => { 113 + if (!outputConfig) { 114 + pkg.logger.error('No build defined in generated config.') 115 + return 116 + } 117 + 118 + const { output } = await bundle.write(outputConfig) 119 + const { fileName } = output[0] 120 + let size 121 + try { 122 + const filePath = outputConfig.dir 123 + ? join(outputConfig.dir, fileName) 124 + : outputConfig.file || fileName 125 + const { size: bytes } = await stat(filePath) 126 + if (bytes > 500) { 127 + size = green( 128 + ' ' + bold((bytes / 1024).toFixed(1).padStart(5)) + ' kB' 129 + ) 130 + } else { 131 + size = green(' ' + bold(String(bytes).padStart(5)) + ' B') 132 + } 133 + // eslint-disable-next-line 134 + } catch {} 135 + pkg.logger.success( 136 + `Built ${bold(pkg.pkg.name.padEnd(15))} ${gray( 137 + fileName.padStart(15) 138 + )}${size}` 139 + ) 140 + }) 141 + await pkg.callHook('build:done', { bundle }) 142 + } catch (err) { 143 + const formattedError = formatError(rootDir, err) 144 + throw pkg.logger.error(formattedError) 145 + } 146 + }) 147 + } 148 + } 149 + 150 + /** 151 + * Format rollup error 152 + */ 153 + const formatError = (rootDir: string, error: RollupError) => { 154 + let loc = rootDir 155 + if (error.loc) { 156 + const { file, column, line } = error.loc 157 + loc = `${file}:${line}:${column}` 158 + } 159 + error.message = `[${error.code}] ${error.message}\nat ${loc}` 160 + return error 161 + }
+70
src/core/build/rollup.spec.ts
··· 1 + import { resolve } from 'path' 2 + 3 + import { Package } from '../package' 4 + import { builtins, builtinsMap } from './builtins' 5 + import { getRollupConfig } from './rollup' 6 + import { getNameFunction } from './utils' 7 + 8 + const getFixturePath = (path: string) => 9 + resolve(__dirname, '../../../test/fixture', path) 10 + 11 + describe('rollupConfig', () => { 12 + let core: Package 13 + let cli: Package 14 + beforeEach(() => { 15 + core = new Package({ rootDir: getFixturePath('default') }) 16 + cli = new Package({ rootDir: getFixturePath('cli') }) 17 + }) 18 + it('should return an appropriate config', () => { 19 + const config = getRollupConfig({}, core) 20 + expect(Array.isArray(config)).toBeTruthy() 21 + expect(typeof config[0].input).toBe('string') 22 + expect(config.length).toBe(2) 23 + }) 24 + it('should generate builds for binaries', () => { 25 + const config = getRollupConfig({}, cli) 26 + expect(config.length).toBe(1) 27 + }) 28 + it('should return an empty config if there are no outputs', () => { 29 + const empty = new Package({ rootDir: getFixturePath('empty') }) 30 + const config = getRollupConfig({}, empty) 31 + expect(config).toEqual([]) 32 + }) 33 + it('should return a config for the current directory if no package is provided', () => { 34 + const config = getRollupConfig({ input: 'src/index.ts' }) 35 + expect(Array.isArray(config)).toBeTruthy() 36 + expect(typeof config[0].input).toBe('string') 37 + }) 38 + const base = getFixturePath('default') 39 + it('should generate appropriately named outputs', () => { 40 + const fn = getNameFunction(core.options.rootDir, 'pkg') 41 + const result = fn('customdist/fname') 42 + expect( 43 + JSON.parse(JSON.stringify(result).replace(RegExp(base, 'g'), '')) 44 + ).toMatchSnapshot() 45 + }) 46 + it('should generate outputs without defined paths', () => { 47 + const fn = getNameFunction(core.options.rootDir, 'pkg') 48 + const result = fn(undefined) 49 + expect( 50 + JSON.parse(JSON.stringify(result).replace(RegExp(base, 'g'), '')) 51 + ).toMatchSnapshot() 52 + const suffixedResult = fn(undefined, '-suffix') 53 + expect( 54 + JSON.parse(JSON.stringify(suffixedResult).replace(RegExp(base, 'g'), '')) 55 + ).toMatchSnapshot() 56 + }) 57 + }) 58 + 59 + describe('builtins', () => { 60 + it('should return an array of builtins', () => { 61 + expect(Array.isArray(builtins)).toBeTruthy() 62 + expect(typeof builtins[0]).toBe('string') 63 + }) 64 + it('should return a builtins map', () => { 65 + const map = builtinsMap() 66 + const newMap = builtinsMap() 67 + expect(map).toBe(newMap) 68 + expect(Object.values(map)[0]).toBe(true) 69 + }) 70 + })
+148
src/core/build/rollup.ts
··· 1 + import { resolve, basename } from 'path' 2 + 3 + import aliasPlugin from '@rollup/plugin-alias' 4 + import commonjsPlugin from '@rollup/plugin-commonjs' 5 + import jsonPlugin from '@rollup/plugin-json' 6 + import replacePlugin, { Replacement } from '@rollup/plugin-replace' 7 + import nodeResolvePlugin, { 8 + RollupNodeResolveOptions, 9 + } from '@rollup/plugin-node-resolve' 10 + import defu from 'defu' 11 + import type { RollupOptions } from 'rollup' 12 + import dts from 'rollup-plugin-dts' 13 + import esbuild from 'rollup-plugin-esbuild' 14 + 15 + import { Package } from '../package' 16 + import { includeDefinedProperties, includeIf } from '../utils' 17 + import { builtins } from './builtins' 18 + import { getNameFunction } from './utils' 19 + 20 + const __NODE_ENV__ = process.env.NODE_ENV 21 + 22 + export interface BuildConfigOptions extends RollupOptions { 23 + rootDir?: string 24 + replace?: Record<string, Replacement> 25 + alias?: { [find: string]: string } 26 + dev?: boolean 27 + shouldWatch?: boolean 28 + /** 29 + * Explicit externals 30 + */ 31 + externals?: (string | RegExp)[] 32 + resolve?: RollupNodeResolveOptions 33 + input?: string 34 + } 35 + 36 + export function getRollupConfig( 37 + { 38 + input, 39 + replace = {}, 40 + alias = {}, 41 + externals = [], 42 + dev = false, 43 + shouldWatch: watch = false, 44 + resolve: resolveOptions = { 45 + resolveOnly: [/^((?!node_modules).)*$/], 46 + preferBuiltins: true, 47 + }, 48 + plugins = [], 49 + ...options 50 + }: BuildConfigOptions, 51 + { 52 + binaries, 53 + entrypoint, 54 + pkg, 55 + options: { rootDir, suffix }, 56 + }: Package = new Package() 57 + ): RollupOptions[] { 58 + const resolvePath = (...path: string[]) => resolve(rootDir, ...path) 59 + input = input ? resolvePath(input) : entrypoint 60 + if (!input && !binaries.length) return [] 61 + 62 + const name = basename(pkg.name.replace(suffix, '')) 63 + const getFilenames = getNameFunction(rootDir, name) 64 + 65 + const external = [ 66 + // Dependencies that will be installed alongside the package 67 + ...Object.keys(pkg.dependencies || {}), 68 + ...Object.keys(pkg.optionalDependencies || {}), 69 + ...Object.keys(pkg.peerDependencies || {}), 70 + // Builtin node modules 71 + ...builtins, 72 + ...externals, 73 + ] 74 + 75 + const getPlugins = () => 76 + [ 77 + aliasPlugin({ 78 + entries: alias, 79 + }), 80 + replacePlugin({ 81 + exclude: 'node_modules/**', 82 + delimiters: ['', ''], 83 + values: { 84 + ...includeDefinedProperties({ __NODE_ENV__ }), 85 + ...replace, 86 + }, 87 + }), 88 + nodeResolvePlugin(resolveOptions), 89 + commonjsPlugin({ include: /node_modules/ }), 90 + esbuild({ 91 + watch, 92 + target: 'es2018', 93 + }), 94 + jsonPlugin(), 95 + ].concat(plugins) 96 + 97 + const defaultOutputs = [ 98 + { 99 + ...getFilenames(pkg.main), 100 + format: 'cjs', 101 + preferConst: true, 102 + }, 103 + ...includeIf(!dev && pkg.module, pkgModule => ({ 104 + ...getFilenames(pkgModule, '-es'), 105 + format: 'es', 106 + })), 107 + ] 108 + 109 + return [ 110 + ...binaries.map(([binary, input]) => { 111 + return defu({}, options, { 112 + input, 113 + output: { 114 + ...getFilenames(binary), 115 + format: 'cjs', 116 + preferConst: true, 117 + banner: '#!/usr/bin/env node\n', 118 + }, 119 + external, 120 + plugins: getPlugins(), 121 + }) 122 + }), 123 + ...includeIf(input, input => 124 + defu({}, options, { 125 + input, 126 + output: defaultOutputs, 127 + external, 128 + plugins: getPlugins(), 129 + }) 130 + ), 131 + ...includeIf(pkg.types && input, input => ({ 132 + input, 133 + output: { 134 + file: resolvePath(pkg.types || ''), 135 + format: 'es' as const, 136 + }, 137 + external, 138 + plugins: [ 139 + jsonPlugin(), 140 + dts({ 141 + compilerOptions: { 142 + allowJs: true, 143 + }, 144 + }), 145 + ], 146 + })), 147 + ] 148 + }
+18
src/core/build/utils.ts
··· 1 + import { resolve, dirname, basename } from 'path' 2 + 3 + export const getNameFunction = (rootDir: string, packageName: string) => ( 4 + filename: string | undefined, 5 + defaultSuffix = '' 6 + ) => { 7 + return { 8 + dir: filename 9 + ? resolve(rootDir, dirname(filename)) 10 + : resolve(rootDir, 'dist'), 11 + entryFileNames: filename 12 + ? basename(filename) 13 + : `${packageName}${defaultSuffix}.js`, 14 + chunkFileNames: filename 15 + ? `${basename(filename)}-[name].js` 16 + : `${packageName}-[name]${defaultSuffix}.js`, 17 + } as const 18 + }
+175
src/core/package/index.spec.ts
··· 1 + import { resolve } from 'path' 2 + 3 + import { existsSync, remove, readFileSync } from 'fs-extra' 4 + import { RollupBuild } from 'rollup' 5 + 6 + import { Package, PackageOptions } from '.' 7 + 8 + const getFixturePath = (path: string) => 9 + resolve(__dirname, '../../../test/fixture', path) 10 + 11 + const loadPackage = (options?: PackageOptions) => 12 + new Package({ 13 + rootDir: getFixturePath('default'), 14 + ...options, 15 + }) 16 + 17 + describe('package class', () => { 18 + let core: Package 19 + let cli: Package 20 + 21 + beforeEach(() => { 22 + core = loadPackage() 23 + cli = core.load('../cli') 24 + }) 25 + 26 + test('should read JSON', () => { 27 + expect(core.pkg.name).toBe('default') 28 + expect(cli.pkg.name).toBe('cli') 29 + }) 30 + 31 + test('should generate appropriate binary array', () => { 32 + expect(cli.binaries.length).toBe(1) 33 + }) 34 + 35 + test('should generate a package version', () => { 36 + const { version } = core.pkg 37 + const newVersion = core.version 38 + expect(version === newVersion).toBeFalsy() 39 + expect(newVersion.includes(version)).toBeTruthy() 40 + core.suffixAndVersion() 41 + const { version: newestVersion } = core.pkg 42 + expect(newestVersion).toBe(newVersion) 43 + }) 44 + 45 + test('should suffix package', () => { 46 + // TODO: more testing required here 47 + const core = loadPackage({ suffix: '-test' }) 48 + expect(core.pkg.name).toBe('default') 49 + core.suffixAndVersion() 50 + expect(core.pkg.name).toBe('default-test') 51 + }) 52 + 53 + test('should get git commit and branch', () => { 54 + const { shortCommit, branch } = core 55 + expect(typeof shortCommit).toBe('string') 56 + expect(typeof branch).toBe('string') 57 + }) 58 + 59 + // TODO: move to fixture 60 + test('should generate package stub', async () => { 61 + const defaultPath = getFixturePath('default') 62 + const files = [ 63 + resolve(defaultPath, 'dist/index.js'), 64 + resolve(defaultPath, 'dist/index.es.js'), 65 + resolve(defaultPath, 'dist/index.d.ts'), 66 + ] 67 + for (const file of files) { 68 + await remove(file) 69 + expect(existsSync(file)).toBeFalsy() 70 + } 71 + 72 + await core.createStubs() 73 + 74 + for (const file of files) { 75 + expect( 76 + readFileSync(file) 77 + .toString() 78 + .replace(/from '.*\/fixture/, "from '/fixture") 79 + ).toBe(`export * from './../src/index'`) 80 + } 81 + }) 82 + 83 + // TODO: move to fixture 84 + test('should create binary stub', async () => { 85 + const file = resolve(getFixturePath('cli'), 'bin/cli.js') 86 + 87 + await remove(file) 88 + expect(existsSync(file)).toBeFalsy() 89 + await cli.createStubs() 90 + expect( 91 + readFileSync(file) 92 + .toString() 93 + .replace(/jiti\('.*\/fixture/, "jiti('/fixture") 94 + ).toBe( 95 + `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('/fixture/cli/src/index')` 96 + ) 97 + }) 98 + 99 + test('should handle git data', () => { 100 + const { lastGitTag } = core 101 + expect(lastGitTag[0]).toBe('v') 102 + }) 103 + 104 + test('should parse contributors', () => { 105 + const result = [ 106 + core.parsePerson('Barney Rubble (http://barnyrubble.tumblr.com/)'), 107 + core.parsePerson( 108 + 'Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)' 109 + ), 110 + ] 111 + expect(result).toEqual([ 112 + { 113 + name: 'Barney Rubble', 114 + url: 'http://barnyrubble.tumblr.com/', 115 + }, 116 + { 117 + name: 'Barney Rubble', 118 + email: 'b@rubble.com', 119 + url: 'http://barnyrubble.tumblr.com/', 120 + }, 121 + ]) 122 + }) 123 + }) 124 + 125 + describe('package hooks', () => { 126 + test('should not error if no hooks are provided', async () => { 127 + const core = loadPackage() 128 + let errored 129 + try { 130 + await core.callHook('build:done', { bundle: {} as RollupBuild }) 131 + } catch { 132 + errored = true 133 + } 134 + expect(errored).toBeFalsy() 135 + }) 136 + 137 + test('should call hooks when provided', () => { 138 + let called = false 139 + const bundle = {} as RollupBuild 140 + const hookPkg = loadPackage({ 141 + hooks: { 142 + 'build:done'(pkg, { bundle: providedBundle }) { 143 + expect(pkg).toBe(hookPkg) 144 + expect(providedBundle).toBe(bundle) 145 + called = true 146 + }, 147 + }, 148 + }) 149 + expect(called).toBeFalsy() 150 + hookPkg.callHook('build:done', { bundle }) 151 + expect(called).toBeTruthy() 152 + }) 153 + 154 + test('should call multiple hooks', async () => { 155 + let called = 0 156 + const hookPkg = loadPackage({ 157 + hooks: { 158 + 'build:done': [ 159 + async () => { 160 + called = called + 1 161 + }, 162 + () => { 163 + throw new Error('did not work') 164 + }, 165 + async () => { 166 + called = called + 2 167 + }, 168 + ], 169 + }, 170 + }) 171 + expect(called).toBe(0) 172 + await hookPkg.callHook('build:done', { bundle: {} as RollupBuild }) 173 + expect(called).toBe(3) 174 + }) 175 + })
+494
src/core/package/index.ts
··· 1 + import { basename, dirname, relative, resolve } from 'path' 2 + 3 + import { bold } from 'chalk' 4 + import consola, { Consola } from 'consola' 5 + import execa, { Options } from 'execa' 6 + import { 7 + copy, 8 + existsSync, 9 + readJSONSync, 10 + writeFile, 11 + mkdirp, 12 + chmod, 13 + } from 'fs-extra' 14 + import { RollupOptions } from 'rollup' 15 + import sortPackageJson from 'sort-package-json' 16 + 17 + import type { 18 + BuildConfigOptions, 19 + PackageCommands, 20 + PackageHookOptions, 21 + PackageHooks, 22 + } from '../build' 23 + import { 24 + glob, 25 + runInParallel, 26 + sortObjectKeys, 27 + tryJSON, 28 + tryRequire, 29 + RequireProperties, 30 + } from '../utils' 31 + import type { PackageJson } from './types' 32 + 33 + interface DefaultPackageOptions { 34 + rootDir: string 35 + build: boolean 36 + suffix: string 37 + hooks: PackageHooks 38 + commands: PackageCommands 39 + linkedDependencies?: string[] 40 + pkg?: PackageJson 41 + rollup?: BuildConfigOptions & RollupOptions 42 + sortDependencies?: boolean 43 + } 44 + 45 + export type PackageOptions = Partial<DefaultPackageOptions> 46 + 47 + export interface BuildOptions { 48 + dev?: boolean 49 + watch?: boolean 50 + } 51 + 52 + // 'package.js' is legacy and will go 53 + const configPaths = [ 54 + 'siroc.config.ts', 55 + 'siroc.config.js', 56 + 'siroc.config.json', 57 + 'package.js', 58 + ] 59 + 60 + const DEFAULTS: DefaultPackageOptions = { 61 + rootDir: process.cwd(), 62 + build: true, 63 + suffix: process.env.PACKAGE_SUFFIX ? `-${process.env.PACKAGE_SUFFIX}` : '', 64 + hooks: {}, 65 + commands: {}, 66 + } 67 + 68 + export class Package { 69 + options: DefaultPackageOptions 70 + logger: Consola 71 + pkg: RequireProperties<PackageJson, 'name' | 'version'> 72 + 73 + constructor(options: PackageOptions = {}) { 74 + this.options = Object.assign({}, DEFAULTS, options) 75 + 76 + // Basic logger 77 + this.logger = consola 78 + 79 + this.pkg = this.loadPackageJSON() 80 + 81 + // Use tagged logger 82 + this.logger = consola.withTag(this.pkg.name) 83 + 84 + this.loadConfig() 85 + } 86 + 87 + loadPackageJSON(): this['pkg'] { 88 + try { 89 + return readJSONSync(this.resolvePath('package.json')) 90 + } catch { 91 + if (this.options.rootDir === '/') { 92 + this.logger.error( 93 + `Could not locate a ${bold('package.json')} in ${bold( 94 + DEFAULTS.rootDir 95 + )} or its parent directories.` 96 + ) 97 + throw new Error( 98 + `Could not locate a package.json in ${DEFAULTS.rootDir} or its parent directories.` 99 + ) 100 + } 101 + this.options.rootDir = this.resolvePath('..') 102 + return this.loadPackageJSON() 103 + } 104 + } 105 + 106 + /** 107 + * Resolve path relative to this package 108 + */ 109 + resolvePath(...pathSegments: string[]) { 110 + return resolve(this.options.rootDir, ...pathSegments) 111 + } 112 + 113 + /** 114 + * Load options from the `siroc.config.js` in the package directory 115 + */ 116 + loadConfig() { 117 + configPaths.some(path => { 118 + const configPath = this.resolvePath(path) 119 + 120 + const config = tryRequire<PackageOptions>(configPath) 121 + if (!config) return false 122 + 123 + Object.assign(this.options, config) 124 + return true 125 + }) 126 + } 127 + 128 + /** 129 + * Call hooks defined in config file 130 + */ 131 + async callHook<H extends keyof PackageHookOptions>( 132 + name: H, 133 + options: PackageHookOptions[H] 134 + ) { 135 + const fns = this.options.hooks[name] 136 + 137 + if (!fns) return 138 + 139 + const fnArray = Array.isArray(fns) ? fns : [fns] 140 + try { 141 + await runInParallel(fnArray, async fn => fn(this, options)) 142 + } catch (e) { 143 + this.logger.error(`Couldn't run hook for ${this.pkg.name}.`) 144 + } 145 + } 146 + 147 + /** 148 + * Return a new package in a directory relative to the current package 149 + */ 150 + load(relativePath: string, opts?: PackageOptions) { 151 + return new Package( 152 + Object.assign( 153 + { 154 + rootDir: this.resolvePath(relativePath), 155 + }, 156 + opts 157 + ) 158 + ) 159 + } 160 + 161 + /** 162 + * Write updated `package.json` 163 + */ 164 + async writePackage() { 165 + const pkgPath = this.resolvePath('package.json') 166 + this.logger.debug('Writing', pkgPath) 167 + await writeFile(pkgPath, JSON.stringify(this.pkg, null, 2) + '\n') 168 + } 169 + 170 + /** 171 + * A version string unique to the current git commit and date 172 + */ 173 + get version() { 174 + const date = Math.round(Date.now() / (1000 * 60)) 175 + const gitCommit = this.shortCommit 176 + const baseVersion = this.pkg.version.split('-')[0] 177 + return `${baseVersion}-${date}.${gitCommit}` 178 + } 179 + 180 + /** 181 + * Add suffix to all dependencies and set new version 182 + */ 183 + suffixAndVersion() { 184 + this.logger.info(`Adding suffix ${this.options.suffix}`) 185 + 186 + const oldPkgName = this.pkg.name 187 + 188 + // Add suffix to the package name 189 + if (!oldPkgName.includes(this.options.suffix)) { 190 + this.pkg.name += this.options.suffix 191 + } 192 + 193 + // Apply suffix to all linkedDependencies 194 + if (this.pkg.dependencies) { 195 + for (const oldName of this.options.linkedDependencies || []) { 196 + const name = oldName + this.options.suffix 197 + const version = 198 + this.pkg.dependencies[oldName] || this.pkg.dependencies[name] 199 + 200 + delete this.pkg.dependencies[oldName] 201 + this.pkg.dependencies[name] = version 202 + } 203 + } 204 + 205 + if (typeof this.pkg.bin === 'string') { 206 + const { bin } = this.pkg 207 + this.pkg.bin = { 208 + [oldPkgName]: bin, 209 + [this.pkg.name]: bin, 210 + } 211 + } 212 + 213 + this.pkg.version = this.version 214 + } 215 + 216 + /** 217 + * Synchronise version across all packages in monorepo 218 + */ 219 + syncLinkedDependencies() { 220 + // Apply suffix to all linkedDependencies 221 + for (const _name of this.options.linkedDependencies || []) { 222 + const name = _name + (this.options.suffix || '') 223 + 224 + // Try to read pkg 225 + const pkg = 226 + tryJSON<PackageJson>(`${name}/package.json`) || 227 + tryJSON<PackageJson>(`${_name}/package.json`) 228 + 229 + // Skip if pkg or dependency not found 230 + if ( 231 + !pkg || 232 + !pkg.version || 233 + !this.pkg.dependencies || 234 + !this.pkg.dependencies[name] 235 + ) { 236 + continue 237 + } 238 + 239 + // Current version 240 + const currentVersion = this.pkg.dependencies[name] 241 + const caret = currentVersion[0] === '^' 242 + 243 + // Sync version 244 + this.pkg.dependencies[name] = caret ? `^${pkg.version}` : pkg.version 245 + } 246 + } 247 + 248 + publish(tag = 'latest') { 249 + this.logger.info( 250 + `publishing ${this.pkg.name}@${this.pkg.version} with tag ${tag}` 251 + ) 252 + this.exec(`npm publish --tag ${tag}`) 253 + } 254 + 255 + /** 256 + * Synchronise fields from another package to this package 257 + */ 258 + copyFieldsFrom(source: Package, fields: Array<keyof PackageJson> = []) { 259 + for (const field of fields) { 260 + ;(this.pkg[field] as any) = source.pkg[field] as any 261 + } 262 + } 263 + 264 + async setBinaryPermissions() { 265 + await Promise.all(this.binaries.map(([binary]) => chmod(binary, 0o777))) 266 + } 267 + 268 + async createBinaryStubs() { 269 + await runInParallel(this.binaries, async ([binary, entrypoint]) => { 270 + if (!entrypoint) return 271 + 272 + const outDir = dirname(binary) 273 + if (!existsSync(outDir)) await mkdirp(outDir) 274 + const absPath = entrypoint.replace(/(\.[jt]s)$/, '') 275 + await writeFile( 276 + binary, 277 + `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('${absPath}')` 278 + ) 279 + await this.setBinaryPermissions() 280 + }) 281 + } 282 + 283 + async createStub(path: string | undefined) { 284 + if (!path || !this.entrypoint || !this.options.build) return 285 + 286 + const outFile = this.resolvePath(path) 287 + const outDir = dirname(outFile) 288 + if (!existsSync(outDir)) await mkdirp(outDir) 289 + const relativeEntrypoint = relative(outDir, this.entrypoint).replace( 290 + /(\.[jt]s)$/, 291 + '' 292 + ) 293 + await writeFile(outFile, `export * from './${relativeEntrypoint}'`) 294 + } 295 + 296 + async createStubs() { 297 + return Promise.all([ 298 + this.createBinaryStubs(), 299 + this.createStub(this.pkg.main), 300 + this.createStub(this.pkg.module), 301 + this.createStub(this.pkg.types), 302 + ]) 303 + } 304 + 305 + /** 306 + * Copy files from another package's directory 307 + */ 308 + async copyFilesFrom(source: Package, files: string[]) { 309 + for (const file of files || source.pkg.files || []) { 310 + const src = resolve(source.options.rootDir, file) 311 + const dst = resolve(this.options.rootDir, file) 312 + await copy(src, dst) 313 + } 314 + } 315 + 316 + /** 317 + * Sort `package.json` and sort package dependencies alphabetically (if enabled in options) 318 + */ 319 + autoFix() { 320 + this.pkg = sortPackageJson(this.pkg) 321 + if (this.options.sortDependencies) this.sortDependencies() 322 + } 323 + 324 + /** 325 + * Sort package depndencies alphabetically by object key 326 + */ 327 + sortDependencies() { 328 + if (this.pkg.dependencies) { 329 + this.pkg.dependencies = sortObjectKeys(this.pkg.dependencies) 330 + } 331 + 332 + if (this.pkg.devDependencies) { 333 + this.pkg.devDependencies = sortObjectKeys(this.pkg.devDependencies) 334 + } 335 + } 336 + 337 + execInteractive(command: string) { 338 + const options: Options = { 339 + cwd: this.options.rootDir, 340 + env: process.env, 341 + shell: true, 342 + stdio: 'inherit', 343 + } 344 + return execa.command(command, options) 345 + } 346 + 347 + /** 348 + * Execute command in the package root directory 349 + */ 350 + exec(command: string, { silent = false } = {}) { 351 + const options = { 352 + cwd: this.options.rootDir, 353 + env: process.env, 354 + } 355 + 356 + const r = execa.commandSync(command, options) 357 + 358 + if (!silent) { 359 + if (r.failed) { 360 + this.logger.error(command, r.stderr.trim()) 361 + } else { 362 + this.logger.success(command, r.stdout.trim()) 363 + } 364 + } 365 + 366 + return { 367 + signal: r.signal, 368 + stdout: String(r.stdout).trim(), 369 + stderr: String(r.stderr).trim(), 370 + } 371 + } 372 + 373 + private resolveEntrypoint(path = this.pkg.main) { 374 + if (!path) return undefined 375 + 376 + const basefile = basename(path).split('.').slice(0, -1).join() 377 + let input!: string 378 + const filenames = [basefile, `${basefile}/index`, 'index'] 379 + .map(name => [`${name}.ts`, `${name}.js`]) 380 + .reduce((names, arr) => { 381 + arr.forEach(name => names.push(name)) 382 + return names 383 + }, [] as string[]) 384 + filenames.some(filename => { 385 + input = this.resolvePath('src', filename) 386 + return existsSync(input) 387 + }) 388 + return input 389 + } 390 + 391 + parsePerson(person: string) { 392 + /* eslint-disable no-unused-vars */ 393 + /* eslint-disable @typescript-eslint/no-unused-vars */ 394 + const [_matchedName, name] = person.match(/(^[^<(]*[^ <(])/) || [] 395 + const [_matchedEmail, email] = person.match(/<(.*)>/) || [] 396 + const [_matchedUrl, url] = person.match(/\((.*)\)/) || [] 397 + /* eslint-enable */ 398 + return { name, email, url } 399 + } 400 + 401 + get contributors() { 402 + if (!this.pkg.contributors) return [] 403 + 404 + return this.pkg.contributors.map(person => { 405 + if (typeof person === 'string') return this.parsePerson(person) 406 + return person 407 + }) 408 + } 409 + 410 + /** 411 + * The main package entrypoint (source) 412 + */ 413 + get entrypoint() { 414 + return this.resolveEntrypoint() 415 + } 416 + 417 + /** 418 + * An array of built package binary paths and their entrypoints 419 + * @returns an array of tuples of the binary and its corresponding entrypoint 420 + */ 421 + get binaries() { 422 + type Binary = string 423 + type Entrypoint = string | undefined 424 + const { bin } = this.pkg 425 + const files = !bin 426 + ? [] 427 + : typeof bin === 'string' 428 + ? [bin] 429 + : Object.values(bin) 430 + 431 + return Array.from( 432 + new Set<[Binary, Entrypoint]>( 433 + files.map(file => [ 434 + this.resolvePath(file), 435 + this.resolveEntrypoint(file), 436 + ]) 437 + ) 438 + ) 439 + } 440 + 441 + /** 442 + * Return the child packages of this workspace (or, if there are no workspaces specified, just this package) 443 + * @param packageNames If package names are provided, these will serve to limit the packages that are returned 444 + */ 445 + async getWorkspacePackages(packageNames?: string[]) { 446 + const dirs = new Set<string>() 447 + 448 + await runInParallel(this.pkg.workspaces || ['.'], async workspace => { 449 + ;(await glob(workspace)).forEach(dir => dirs.add(dir)) 450 + }) 451 + 452 + const packages = await runInParallel(dirs, dir => { 453 + if (!existsSync(this.resolvePath(dir, 'package.json'))) { 454 + throw new Error('Not a package directory.') 455 + } 456 + const pkg = new Package({ 457 + ...this.options, 458 + rootDir: this.resolvePath(dir), 459 + }) 460 + if (packageNames && !packageNames.includes(pkg.pkg.name)) { 461 + throw new Error('Not a selected package.') 462 + } 463 + return pkg 464 + }) 465 + 466 + return packages 467 + .filter(pkg => pkg.status === 'fulfilled') 468 + .map(pkg => (pkg as PromiseFulfilledResult<Package>).value) 469 + } 470 + 471 + get shortCommit() { 472 + const { stdout } = this.exec('git rev-parse --short HEAD', { 473 + silent: true, 474 + }) 475 + return stdout 476 + } 477 + 478 + get branch() { 479 + const { stdout } = this.exec('git rev-parse --abbrev-ref HEAD', { 480 + silent: true, 481 + }) 482 + return stdout 483 + } 484 + 485 + get lastGitTag() { 486 + const { stdout } = this.exec('git --no-pager tag -l --sort=taggerdate', { 487 + silent: true, 488 + }) 489 + const r = stdout.split('\n') 490 + return r[r.length - 1] 491 + } 492 + } 493 + 494 + export * from './types'
+122
src/core/package/types.ts
··· 1 + /** 2 + * A “person” is an object with a “name” field and optionally “url” and “email”. Or you can shorten that all into a single string, and npm will parse it for you. 3 + */ 4 + export type PackageJsonPerson = 5 + | string 6 + | { 7 + name: string 8 + email?: string 9 + url?: string 10 + } 11 + 12 + export interface Repository { 13 + type: string 14 + url: string 15 + /** 16 + * If the `package.json` for your package is not in the root directory (for example if it is part of a monorepo), you can specify the directory in which it lives: 17 + */ 18 + directory?: string 19 + } 20 + export interface PackageJson { 21 + /** 22 + * The name is what your thing is called. 23 + * Some rules: 24 + 25 + - The name must be less than or equal to 214 characters. This includes the scope for scoped packages. 26 + - The name can’t start with a dot or an underscore. 27 + - New packages must not have uppercase letters in the name. 28 + - The name ends up being part of a URL, an argument on the command line, and a folder name. Therefore, the name can’t contain any non-URL-safe characters. 29 + 30 + */ 31 + name?: string 32 + /** 33 + * Version must be parseable by `node-semver`, which is bundled with npm as a dependency. (`npm install semver` to use it yourself.) 34 + */ 35 + version?: string 36 + /** 37 + * Put a description in it. It’s a string. This helps people discover your package, as it’s listed in `npm search`. 38 + */ 39 + description?: string 40 + /** 41 + * Put keywords in it. It’s an array of strings. This helps people discover your package as it’s listed in `npm search`. 42 + */ 43 + keywords?: string[] 44 + /** 45 + * The url to the project homepage. 46 + */ 47 + homepage?: string 48 + 49 + /** 50 + * The url to your project’s issue tracker and / or the email address to which issues should be reported. These are helpful for people who encounter issues with your package. 51 + */ 52 + bugs?: 53 + | string 54 + | { 55 + url?: string 56 + email?: string 57 + } 58 + /** 59 + * You should specify a license for your package so that people know how they are permitted to use it, and any restrictions you’re placing on it. 60 + */ 61 + licence?: string 62 + /** 63 + * Specify the place where your code lives. This is helpful for people who want to contribute. If the git repo is on GitHub, then the `npm docs` command will be able to find you. 64 + * For GitHub, GitHub gist, Bitbucket, or GitLab repositories you can use the same shortcut syntax you use for npm install: 65 + */ 66 + repository?: string | Repository 67 + /** 68 + * If you set `"private": true` in your package.json, then npm will refuse to publish it. 69 + */ 70 + private?: boolean 71 + /** 72 + * The “author” is one person. 73 + */ 74 + author?: PackageJsonPerson 75 + /** 76 + * “contributors” is an array of people. 77 + */ 78 + contributors?: PackageJsonPerson[] 79 + /** 80 + * The optional `files` field is an array of file patterns that describes the entries to be included when your package is installed as a dependency. File patterns follow a similar syntax to `.gitignore`, but reversed: including a file, directory, or glob pattern (`*`, `**\/*`, and such) will make it so that file is included in the tarball when it’s packed. Omitting the field will make it default to `["*"]`, which means it will include all files. 81 + */ 82 + files?: string[] 83 + /** 84 + * The main field is a module ID that is the primary entry point to your program. That is, if your package is named `foo`, and a user installs it, and then does `require("foo")`, then your main module’s exports object will be returned. 85 + * This should be a module ID relative to the root of your package folder. 86 + * For most modules, it makes the most sense to have a main script and often not much else. 87 + */ 88 + main?: string 89 + /** 90 + * If your module is meant to be used client-side the browser field should be used instead of the main field. This is helpful to hint users that it might rely on primitives that aren’t available in Node.js modules. (e.g. window) 91 + */ 92 + browser?: string 93 + /** 94 + * A map of command name to local file name. On install, npm will symlink that file into `prefix/bin` for global installs, or `./node_modules/.bin/` for local installs. 95 + */ 96 + bin?: string | Record<string, string> 97 + /** 98 + * Specify either a single file or an array of filenames to put in place for the `man` program to find. 99 + */ 100 + man?: string | string[] 101 + /** 102 + * Dependencies are specified in a simple object that maps a package name to a version range. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or git URL. 103 + */ 104 + dependencies?: Record<string, string> 105 + /** 106 + * If someone is planning on downloading and using your module in their program, then they probably don’t want or need to download and build the external test or documentation framework that you use. 107 + * In this case, it’s best to map these additional items in a `devDependencies` object. 108 + */ 109 + devDependencies?: Record<string, string> 110 + /** 111 + * If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install, then you may put it in the `optionalDependencies` object. This is a map of package name to version or url, just like the `dependencies` object. The difference is that build failures do not cause installation to fail. 112 + */ 113 + optionalDependencies?: Record<string, string> 114 + /** 115 + * In some cases, you want to express the compatibility of your package with a host tool or library, while not necessarily doing a `require` of this host. This is usually referred to as a plugin. Notably, your module may be exposing a specific interface, expected and specified by the host documentation. 116 + */ 117 + peerDependencies?: Record<string, string> 118 + 119 + types?: string 120 + module?: string 121 + workspaces?: string[] 122 + }
+102
src/core/utils/index.ts
··· 1 + import { extname } from 'path' 2 + import { readJSONSync } from 'fs-extra' 3 + import _glob from 'glob' 4 + import _jiti from 'jiti' 5 + 6 + import { loadAllSettled, loadFromEntries } from './polyfills' 7 + 8 + const jiti = _jiti() 9 + 10 + if (!Object.fromEntries) loadFromEntries() 11 + if (!Promise.allSettled) loadAllSettled() 12 + 13 + export const glob = (pattern: string) => 14 + new Promise<string[]>((resolve, reject) => 15 + _glob(pattern, (err, matches) => { 16 + if (err) return reject(err) 17 + resolve(matches) 18 + }) 19 + ) 20 + 21 + export const sortObjectKeys = <T>(obj: Record<string, T>) => 22 + Object.fromEntries( 23 + Object.entries(obj).sort(([key1], [key2]) => +key2 - +key1) 24 + ) 25 + 26 + export const tryJSON = <T = unknown>(id: string) => { 27 + try { 28 + return readJSONSync(id) as T 29 + } catch { 30 + return undefined 31 + } 32 + } 33 + 34 + export const tryRequire = <T = unknown>(id: string) => { 35 + try { 36 + if (extname(id) === 'json') return tryJSON(id) 37 + const contents = jiti(id) as T | { default: T } 38 + if ('default' in contents) return contents.default 39 + return contents 40 + } catch { 41 + return undefined 42 + } 43 + } 44 + 45 + export type RequireProperties<T, R extends keyof T> = Omit<T, R> & 46 + Required<Pick<T, R>> 47 + 48 + export const ensureUnique = <T>(items: T[]) => Array.from(new Set(items)) 49 + 50 + export const groupBy = <T extends Record<string, any>, K extends keyof T>( 51 + collection: T[], 52 + key: K 53 + ) => { 54 + const groups = {} as Record<T[K], T[]> 55 + collection.forEach(entry => { 56 + groups[entry[key]] = groups[entry[key]] || [] 57 + groups[entry[key]].push(entry) 58 + }) 59 + return groups 60 + } 61 + 62 + type ExcludeNullable<T extends Record<string, any>> = { 63 + [P in keyof T]: NonNullable<T[P]> 64 + } 65 + 66 + export const includeDefinedProperties = <T extends Record<string, any>>( 67 + options: T 68 + ) => 69 + Object.fromEntries( 70 + // eslint-disable-next-line 71 + Object.entries(options).filter(([_, value]) => value !== undefined) 72 + ) as ExcludeNullable<T> 73 + 74 + type NonFalsy<T> = T extends null | undefined | false ? never : T 75 + 76 + export const includeIf = <T, I>( 77 + test: T, 78 + itemFactory: (outcome: NonFalsy<T>) => I 79 + ) => (test ? [itemFactory(test as any)] : []) 80 + 81 + export const runInParallel = async <T, R extends any>( 82 + items: Iterable<T>, 83 + cb: (item: T, index: number) => Promise<R> | R 84 + ) => { 85 + if (Array.isArray(items)) 86 + return Promise.allSettled(items.map(async (item, index) => cb(item, index))) 87 + 88 + const promises: Array<Promise<R>> = [] 89 + let index = 0 90 + for (const item of items) { 91 + try { 92 + promises.push(Promise.resolve(cb(item, index))) 93 + } catch (e) { 94 + promises.push(Promise.reject(e)) 95 + } 96 + index++ 97 + } 98 + return Promise.allSettled(promises) 99 + } 100 + 101 + export const asArray = <T>(item: T | T[] | undefined): T[] => 102 + item !== undefined ? (Array.isArray(item) ? item : [item]) : []
+37
src/core/utils/polyfills.ts
··· 1 + export function loadAllSettled() { 2 + ;(Promise.allSettled as any) = function <T>( 3 + values: Iterable<T> 4 + ): Promise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]> { 5 + return Promise.all( 6 + Array.from(values).map(promise => { 7 + if (promise instanceof Promise) { 8 + return promise 9 + .then(value => ({ 10 + status: 'fulfilled' as const, 11 + value, 12 + })) 13 + .catch(reason => ({ 14 + status: 'rejected' as const, 15 + reason, 16 + })) 17 + } 18 + return { 19 + status: 'fulfilled' as const, 20 + value: promise, 21 + } 22 + }) 23 + ) 24 + } 25 + } 26 + 27 + export function loadFromEntries() { 28 + Object.fromEntries = function <T = any>( 29 + entries: Iterable<readonly [PropertyKey, T]> 30 + ): { [k: string]: T } { 31 + const newObject: Record<string, T> = {} 32 + for (const [key, value] of entries) { 33 + if (typeof key === 'string') newObject[key] = value 34 + } 35 + return newObject 36 + } 37 + }
+204
src/core/utils/utils.spec.ts
··· 1 + import path from 'path' 2 + 3 + import type { PackageJson } from '../package' 4 + import { loadAllSettled, loadFromEntries } from './polyfills' 5 + import { 6 + asArray, 7 + ensureUnique, 8 + glob, 9 + groupBy, 10 + includeDefinedProperties, 11 + includeIf, 12 + runInParallel, 13 + sortObjectKeys, 14 + tryJSON, 15 + tryRequire, 16 + } from '.' 17 + 18 + describe('asArray', () => { 19 + it('should return an array if passed an undefined value', () => { 20 + expect(asArray(undefined)).toEqual([]) 21 + expect((asArray as any)()).toEqual([]) 22 + }) 23 + it('should return an array of a single item', () => { 24 + expect(asArray(1)).toEqual([1]) 25 + }) 26 + 27 + it('should return an array unchanged', () => { 28 + const array = [1, 2] 29 + expect(asArray(array)).toBe(array) 30 + }) 31 + }) 32 + 33 + describe('ensureUnique', () => { 34 + it('should filter out duplicated items', () => { 35 + const array = [1, 2, 1, 5, 2] 36 + const result = ensureUnique(array) 37 + expect(result).toEqual([1, 2, 5]) 38 + }) 39 + }) 40 + 41 + describe('glob', () => { 42 + it('should resolve paths with a promise', async () => { 43 + const result = await glob('READ*') 44 + expect(result).toEqual(['README.md']) 45 + }) 46 + }) 47 + 48 + describe('groupBy', () => { 49 + const array = [ 50 + { 51 + key: 'key-1', 52 + item: 'item-1', 53 + }, 54 + { 55 + key: 'key-1', 56 + item: 'item-2', 57 + }, 58 + ] 59 + it('should return a grouped object', () => { 60 + const result = groupBy(array, 'key') 61 + expect(result).toEqual({ 62 + 'key-1': array, 63 + }) 64 + }) 65 + it('should separate items', () => { 66 + const result = groupBy(array, 'item') 67 + expect(result).toEqual({ 68 + 'item-1': [array[0]], 69 + 'item-2': [array[1]], 70 + }) 71 + }) 72 + }) 73 + 74 + const definedPropertiesTest = () => { 75 + it('should omit undefined properties', () => { 76 + const obj = { 77 + val: 3, 78 + another: false, 79 + further: undefined, 80 + } 81 + const result = includeDefinedProperties(obj) 82 + expect(result).toEqual({ 83 + val: 3, 84 + another: false, 85 + }) 86 + }) 87 + } 88 + 89 + describe('includeDefinedProperties', definedPropertiesTest) 90 + 91 + describe('includeIf', () => { 92 + it('should return an empty array when falsy', () => { 93 + const results = [ 94 + includeIf(false, () => 'test'), 95 + includeIf(null, () => 'test'), 96 + includeIf(undefined, () => 'test'), 97 + ] 98 + expect(results).toEqual([[], [], []]) 99 + }) 100 + it('should return the item in an array when true', () => { 101 + const result = includeIf(true, () => 'test') 102 + expect(result).toEqual(['test']) 103 + }) 104 + }) 105 + 106 + const runInParallelTest = () => { 107 + it('should run tasks in parallel', async () => { 108 + let i = 0 109 + await runInParallel([1, 2, 3], async num => (i = i + num)) 110 + expect(i).toBe(6) 111 + }) 112 + it('should not fail if one task does', async () => { 113 + const result = await runInParallel([1, 2, 3], async num => { 114 + if (num === 2) throw new Error('') 115 + }) 116 + expect(result.length).toBe(3) 117 + }) 118 + } 119 + 120 + describe('runInParallel', runInParallelTest) 121 + 122 + const sortObjectKeysTest = () => { 123 + const obj = { 124 + c: '', 125 + '@a': '', 126 + Z$: '', 127 + } 128 + it('should sort object keys alphabetically', () => { 129 + const result = sortObjectKeys(obj) 130 + expect(result).toEqual({ 131 + '@a': '', 132 + c: '', 133 + Z$: '', 134 + }) 135 + }) 136 + } 137 + describe('sortObjectKeys', sortObjectKeysTest) 138 + 139 + describe('tryJSON', () => { 140 + it('should not throw an error when passed nonsense', () => { 141 + const result = tryJSON('require this if you dare') 142 + expect(result).toBeFalsy() 143 + }) 144 + it('should resolve JSON', () => { 145 + const result = tryJSON<PackageJson>( 146 + path.resolve(__dirname, '../../../package.json') 147 + ) 148 + expect(result!.name).toBeDefined() 149 + }) 150 + }) 151 + 152 + describe('tryRequire', () => { 153 + it('should not throw an error when passed nonsense', () => { 154 + const result = tryRequire('require this if you dare') 155 + expect(result).toBeFalsy() 156 + }) 157 + it('should resolve JS', () => { 158 + const result = tryRequire<any>( 159 + path.resolve(__dirname, '../../../prettier.config.js') 160 + ) 161 + expect(result.semi).toBe(false) 162 + }) 163 + it('should resolve JSON', () => { 164 + const result = tryRequire<PackageJson>( 165 + path.resolve(__dirname, '../../../package.json') 166 + ) 167 + expect(result!.name).toBeDefined() 168 + }) 169 + }) 170 + 171 + describe('utils with polyfills', () => { 172 + let fromEntries: any 173 + let allSettled: any 174 + beforeAll(() => { 175 + fromEntries = Object.fromEntries 176 + allSettled = Promise.allSettled 177 + loadAllSettled() 178 + loadFromEntries() 179 + }) 180 + afterAll(() => { 181 + Object.fromEntries = fromEntries 182 + Promise.allSettled = allSettled 183 + }) 184 + // Object.fromEntries 185 + definedPropertiesTest() 186 + it('should not include non-string values', () => { 187 + const result = Object.fromEntries([ 188 + ['key', 'value'], 189 + [2, 'value'], 190 + ]) 191 + expect(result).toEqual({ key: 'value' }) 192 + }) 193 + // Promise.allSettled 194 + runInParallelTest() 195 + sortObjectKeysTest() 196 + it('should return bare value if not a promise', async () => { 197 + const fn = async () => 'another' 198 + const result = await Promise.allSettled(['test', fn()]) 199 + expect(result).toEqual([ 200 + { status: 'fulfilled', value: 'test' }, 201 + { status: 'fulfilled', value: 'another' }, 202 + ]) 203 + }) 204 + })
+7
test/fixture/cli/package.json
··· 1 + { 2 + "name": "cli", 3 + "bin": { 4 + "test": "bin/cli.js" 5 + }, 6 + "dependencies": {} 7 + }
+8
test/fixture/default/package.json
··· 1 + { 2 + "name": "default", 3 + "version": "0.5.3", 4 + "main": "dist/index.js", 5 + "module": "dist/index.es.js", 6 + "types": "dist/index.d.ts", 7 + "dependencies": {} 8 + }
+4
test/fixture/empty/package.json
··· 1 + { 2 + "name": "empty", 3 + "dependencies": {} 4 + }
-60
packages/cli/src/commands/build.ts
··· 1 - import { 2 - build as buildPackage, 3 - Package, 4 - removeBuildFolders, 5 - runInParallel, 6 - BuildOptions, 7 - } from '@siroc/core' 8 - 9 - export interface BuildCommandOptions extends BuildOptions { 10 - packages: string[] 11 - } 12 - 13 - export async function build( 14 - rootPackage: Package, 15 - { packages, ...options }: BuildCommandOptions 16 - ) { 17 - const workspacePackages = await rootPackage.getWorkspacePackages( 18 - packages.length ? packages : undefined 19 - ) 20 - 21 - const { watch } = options 22 - rootPackage.logger.info(`Beginning build${watch ? ' (watching)' : ''}`) 23 - 24 - // Universal linkedDependencies based on workspace 25 - const linkedDependencies = workspacePackages.map(p => 26 - p.pkg.name.replace(p.options.suffix, '') 27 - ) 28 - 29 - // Create package stubs so we can build in parallel 30 - await runInParallel(workspacePackages, async pkg => { 31 - await removeBuildFolders(pkg) 32 - await pkg.createStubs() 33 - }) 34 - 35 - await runInParallel(workspacePackages, async pkg => { 36 - pkg.options.linkedDependencies = ( 37 - pkg.options.linkedDependencies || [] 38 - ).concat(linkedDependencies) 39 - 40 - // Step 1: Apply suffixes 41 - if (pkg.options.suffix && pkg.options.suffix.length) { 42 - pkg.suffixAndVersion() 43 - await pkg.writePackage() 44 - } 45 - 46 - // Step 2: Build packages 47 - if (pkg.options.build) { 48 - if (watch) { 49 - buildPackage(pkg, { dev: true, ...options, watch }) 50 - } else { 51 - await buildPackage(pkg, options) 52 - } 53 - } 54 - 55 - // Step 3: Link dependencies and Fix packages 56 - pkg.syncLinkedDependencies() 57 - pkg.autoFix() 58 - await Promise.all([pkg.setBinaryPermissions(), pkg.writePackage()]) 59 - }) 60 - }
-16
packages/cli/src/commands/dev.ts
··· 1 - import { Package, runInParallel } from '@siroc/core' 2 - 3 - export interface DevCommandOptions { 4 - packages: string[] 5 - } 6 - 7 - export async function dev( 8 - rootPackage: Package, 9 - { packages }: DevCommandOptions 10 - ) { 11 - const workspacePackages = await rootPackage.getWorkspacePackages( 12 - packages.length ? packages : undefined 13 - ) 14 - 15 - await runInParallel(workspacePackages, async pkg => pkg.createStubs()) 16 - }
-23
packages/cli/src/commands/release.ts
··· 1 - export const publish = ` 2 - #!/bin/bash 3 - set -e 4 - 5 - if [ ! "$1" ]; then 6 - echo "Usage $0 [version]" 7 - exit 1 8 - fi 9 - 10 - yarn build 11 - 12 - ./scripts/workspace-run npm publish -q 13 - 14 - git tag -a v$1 -m v$1 15 - git push --tags 16 - ` 17 - 18 - export const version = ` 19 - #!/bin/bash 20 - set -e 21 - 22 - yarn lerna version --no-changelog --no-git-tag-version --no-push --force-publish "*" 23 - `
-55
packages/cli/src/commands/run.ts
··· 1 - import { resolve } from 'path' 2 - 3 - import { Package, runInParallel } from '@siroc/core' 4 - import { bold, gray } from 'chalk' 5 - import { existsSync } from 'fs-extra' 6 - 7 - interface RunCommandOptions { 8 - file: string 9 - args: string[] 10 - options: { 11 - workspaces?: boolean 12 - sequential?: boolean 13 - } 14 - } 15 - 16 - export async function run( 17 - rootPackage: Package, 18 - { file, args, options: { workspaces, sequential } }: RunCommandOptions 19 - ) { 20 - const fullCommand = `${file} ${args.join()}`.trim() 21 - const filepath = resolve(process.cwd(), file) 22 - const isLocalFile = 23 - (file.endsWith('.js') || file.endsWith('.ts')) && existsSync(filepath) 24 - 25 - function runCommand(pkg: Package) { 26 - try { 27 - if (isLocalFile) 28 - return pkg.execInteractive( 29 - `yarn siroc-runner ${filepath} ${args.join()}` 30 - ) 31 - 32 - const { stdout } = pkg.exec(fullCommand, { 33 - silent: true, 34 - }) 35 - 36 - pkg.logger.success( 37 - `Ran ${bold(fullCommand)} in ${bold(pkg.pkg.name)}.`, 38 - stdout ? '\n' : '', 39 - stdout ? gray(stdout) : '' 40 - ) 41 - } catch (e) { 42 - pkg.logger.error(`Error running ${bold(fullCommand)}\n`, gray(e)) 43 - } 44 - } 45 - 46 - const packages = workspaces 47 - ? await rootPackage.getWorkspacePackages() 48 - : [rootPackage] 49 - 50 - if (!sequential) { 51 - runInParallel(packages, runCommand) 52 - } else { 53 - packages.forEach(runCommand) 54 - } 55 - }
-4
packages/cli/test/tsd/index.test-d.ts
··· 1 - import { expectType } from 'tsd' 2 - 3 - // eslint-disable-next-line 4 - expectType<void>((() => {})())
-4
packages/cli/test/unit/index.spec.ts
··· 1 - describe('empty', () => { 2 - // eslint-disable-next-line 3 - test('test', async () => {}) 4 - })
-24
packages/core/src/build/builtins.ts
··· 1 - /* 2 - ** Core logic from https://github.com/sindresorhus/builtin-modules 3 - ** Many thanks to @sindresorhus 4 - */ 5 - import Module from 'module' 6 - 7 - const blacklist = ['sys'] 8 - 9 - export const builtins = Module.builtinModules 10 - .filter( 11 - x => 12 - !/^_|^(internal|v8|node-inspect)\/|\//.test(x) && !blacklist.includes(x) 13 - ) 14 - .sort() 15 - 16 - let builtinsObj: Record<string, boolean> 17 - 18 - const convertToObj = () => 19 - builtins.reduce((obj, builtin) => { 20 - obj[builtin] = true 21 - return obj 22 - }, (builtinsObj = {} as Record<string, boolean>)) 23 - 24 - export const builtinsMap = () => builtinsObj || convertToObj()
-30
packages/core/src/build/hooks.ts
··· 1 - import type { RollupBuild, RollupOptions } from 'rollup' 2 - 3 - import type { Package } from '../package' 4 - import type { RequireProperties } from '../utils' 5 - import type { BuildConfigOptions } from './rollup' 6 - 7 - export type Hook<T> = { 8 - (pkg: Package, options: T): Promise<void> | void 9 - } 10 - 11 - export type Hooks<T extends Record<string, any>> = { 12 - [P in keyof T]?: Hook<T[P]> | Array<Hook<T[P]>> 13 - } 14 - 15 - export type PackageHookOptions = { 16 - 'build:extend': { 17 - config: RequireProperties<BuildConfigOptions, 'alias' | 'replace'> 18 - } 19 - 'build:extendRollup': { 20 - rollupConfig: RollupOptions[] 21 - } 22 - 'build:done': { bundle: RollupBuild } 23 - } 24 - 25 - export type PackageHooks = Hooks<PackageHookOptions> 26 - 27 - export type PackageCommands = Record< 28 - string, 29 - (pkg: Package) => void | Promise<void> 30 - >
-161
packages/core/src/build/index.ts
··· 1 - import { dirname, join } from 'path' 2 - 3 - import { bold, gray, green } from 'chalk' 4 - import { remove, stat } from 'fs-extra' 5 - import { rollup, watch, RollupError } from 'rollup' 6 - 7 - import type { BuildOptions, Package } from '../package' 8 - import { 9 - asArray, 10 - ensureUnique, 11 - includeIf, 12 - runInParallel, 13 - RequireProperties, 14 - } from '../utils' 15 - import { getRollupConfig, BuildConfigOptions } from './rollup' 16 - 17 - export * from './hooks' 18 - export * from './rollup' 19 - 20 - /** 21 - * Remove folders for build destinations 22 - */ 23 - export async function removeBuildFolders(pkg: Package) { 24 - const directories = ensureUnique( 25 - [ 26 - ...pkg.binaries.map(([bin]) => bin), 27 - ...includeIf(pkg.pkg.main, main => main), 28 - ] 29 - .map(file => dirname(file)) 30 - .filter(dir => !dir.includes('src')) 31 - ) 32 - 33 - await runInParallel(directories, remove) 34 - } 35 - 36 - export const build = async ( 37 - pkg: Package, 38 - { watch: shouldWatch = false, dev = shouldWatch }: BuildOptions = {} 39 - ) => { 40 - const { 41 - options: { suffix, linkedDependencies, rootDir, rollup: rollupOptions }, 42 - } = pkg 43 - // Prepare rollup config 44 - const config: RequireProperties<BuildConfigOptions, 'alias' | 'replace'> = { 45 - alias: {}, 46 - replace: {}, 47 - dev, 48 - shouldWatch, 49 - ...rollupOptions, 50 - } 51 - 52 - // Replace linkedDependencies with their suffixed version 53 - if (suffix) { 54 - for (const _name of linkedDependencies || []) { 55 - const name = _name + suffix 56 - config.replace[`'${_name}'`] = `'${name}'` 57 - config.alias[_name] = name 58 - } 59 - } 60 - 61 - // Allow extending config 62 - await pkg.callHook('build:extend', { config }) 63 - 64 - // Create rollup config 65 - const rollupConfig = getRollupConfig(config, pkg) 66 - 67 - // Allow extending rollup config 68 - await pkg.callHook('build:extendRollup', { 69 - rollupConfig, 70 - }) 71 - 72 - if (shouldWatch) { 73 - // Watch 74 - const watcher = watch(rollupConfig) 75 - watcher.on('event', event => { 76 - switch (event.code) { 77 - // The watcher is (re)starting 78 - case 'START': 79 - return pkg.logger.debug(`Watching ${pkg.pkg.name} for changes`) 80 - 81 - // Building an individual bundle 82 - case 'BUNDLE_START': 83 - return pkg.logger.debug(`Building ${pkg.pkg.name}`) 84 - 85 - // Finished building a bundle 86 - case 'BUNDLE_END': 87 - return 88 - 89 - // Finished building all bundles 90 - case 'END': 91 - return pkg.logger.success(`Built ${bold(pkg.pkg.name)}`) 92 - 93 - // Encountered an error while bundling 94 - case 'ERROR': 95 - formatError(rootDir, event.error) 96 - return pkg.logger.error(event.error) 97 - 98 - // Unknown event 99 - default: 100 - // If rollup adds more events, TypeScript will let us know 101 - // eslint-disable-next-line 102 - const _event: never = event 103 - return pkg.logger.info(JSON.stringify(_event)) 104 - } 105 - }) 106 - } else { 107 - // Build 108 - pkg.logger.debug(`Building ${pkg.pkg.name}`) 109 - await runInParallel(rollupConfig, async config => { 110 - try { 111 - const bundle = await rollup(config) 112 - await runInParallel(asArray(config.output), async outputConfig => { 113 - if (!outputConfig) { 114 - pkg.logger.error('No build defined in generated config.') 115 - return 116 - } 117 - 118 - const { output } = await bundle.write(outputConfig) 119 - const { fileName } = output[0] 120 - let size 121 - try { 122 - const filePath = outputConfig.dir 123 - ? join(outputConfig.dir, fileName) 124 - : outputConfig.file || fileName 125 - const { size: bytes } = await stat(filePath) 126 - if (bytes > 500) { 127 - size = green( 128 - ' ' + bold((bytes / 1024).toFixed(1).padStart(5)) + ' kB' 129 - ) 130 - } else { 131 - size = green(' ' + bold(String(bytes).padStart(5)) + ' B') 132 - } 133 - // eslint-disable-next-line 134 - } catch {} 135 - pkg.logger.success( 136 - `Built ${bold(pkg.pkg.name.padEnd(15))} ${gray( 137 - fileName.padStart(15) 138 - )}${size}` 139 - ) 140 - }) 141 - await pkg.callHook('build:done', { bundle }) 142 - } catch (err) { 143 - const formattedError = formatError(rootDir, err) 144 - throw pkg.logger.error(formattedError) 145 - } 146 - }) 147 - } 148 - } 149 - 150 - /** 151 - * Format rollup error 152 - */ 153 - const formatError = (rootDir: string, error: RollupError) => { 154 - let loc = rootDir 155 - if (error.loc) { 156 - const { file, column, line } = error.loc 157 - loc = `${file}:${line}:${column}` 158 - } 159 - error.message = `[${error.code}] ${error.message}\nat ${loc}` 160 - return error 161 - }
-67
packages/core/src/build/rollup.spec.ts
··· 1 - import { resolve } from 'path' 2 - 3 - import { Package } from '../package' 4 - import { builtins, builtinsMap } from './builtins' 5 - import { getRollupConfig } from './rollup' 6 - import { getNameFunction } from './utils' 7 - 8 - describe('rollupConfig', () => { 9 - let core: Package 10 - let cli: Package 11 - beforeEach(() => { 12 - core = new Package({ rootDir: __dirname }) 13 - cli = core.load('../cli') 14 - }) 15 - it('should return an appropriate config', () => { 16 - const config = getRollupConfig({}, core) 17 - expect(Array.isArray(config)).toBeTruthy() 18 - expect(typeof config[0].input).toBe('string') 19 - expect(config.length).toBe(2) 20 - }) 21 - it('should return an empty config if there are no outputs', () => { 22 - const base = core.load('../..') 23 - const config = getRollupConfig({}, base) 24 - expect(config).toEqual([]) 25 - }) 26 - it('should return a config for the current directory if no package is provided', () => { 27 - const config = getRollupConfig({ input: 'src/index.ts' }) 28 - expect(Array.isArray(config)).toBeTruthy() 29 - expect(typeof config[0].input).toBe('string') 30 - }) 31 - it('should generate builds for binaries', () => { 32 - const config = getRollupConfig({}, cli) 33 - expect(config.length).toBe(2) 34 - }) 35 - const base = resolve(__dirname, '../..') 36 - it('should generate appropriately named outputs', () => { 37 - const fn = getNameFunction(core.options.rootDir, 'pkg') 38 - const result = fn('customdist/fname') 39 - expect( 40 - JSON.parse(JSON.stringify(result).replace(RegExp(base, 'g'), '')) 41 - ).toMatchSnapshot() 42 - }) 43 - it('should generate outputs without defined paths', () => { 44 - const fn = getNameFunction(core.options.rootDir, 'pkg') 45 - const result = fn(undefined) 46 - expect( 47 - JSON.parse(JSON.stringify(result).replace(RegExp(base, 'g'), '')) 48 - ).toMatchSnapshot() 49 - const suffixedResult = fn(undefined, '-suffix') 50 - expect( 51 - JSON.parse(JSON.stringify(suffixedResult).replace(RegExp(base, 'g'), '')) 52 - ).toMatchSnapshot() 53 - }) 54 - }) 55 - 56 - describe('builtins', () => { 57 - it('should return an array of builtins', () => { 58 - expect(Array.isArray(builtins)).toBeTruthy() 59 - expect(typeof builtins[0]).toBe('string') 60 - }) 61 - it('should return a builtins map', () => { 62 - const map = builtinsMap() 63 - const newMap = builtinsMap() 64 - expect(map).toBe(newMap) 65 - expect(Object.values(map)[0]).toBe(true) 66 - }) 67 - })
-148
packages/core/src/build/rollup.ts
··· 1 - import { resolve, basename } from 'path' 2 - 3 - import aliasPlugin from '@rollup/plugin-alias' 4 - import commonjsPlugin from '@rollup/plugin-commonjs' 5 - import jsonPlugin from '@rollup/plugin-json' 6 - import replacePlugin, { Replacement } from '@rollup/plugin-replace' 7 - import nodeResolvePlugin, { 8 - RollupNodeResolveOptions, 9 - } from '@rollup/plugin-node-resolve' 10 - import defu from 'defu' 11 - import type { RollupOptions } from 'rollup' 12 - import dts from 'rollup-plugin-dts' 13 - import esbuild from 'rollup-plugin-esbuild' 14 - 15 - import { Package } from '../package' 16 - import { includeDefinedProperties, includeIf } from '../utils' 17 - import { builtins } from './builtins' 18 - import { getNameFunction } from './utils' 19 - 20 - const __NODE_ENV__ = process.env.NODE_ENV 21 - 22 - export interface BuildConfigOptions extends RollupOptions { 23 - rootDir?: string 24 - replace?: Record<string, Replacement> 25 - alias?: { [find: string]: string } 26 - dev?: boolean 27 - shouldWatch?: boolean 28 - /** 29 - * Explicit externals 30 - */ 31 - externals?: (string | RegExp)[] 32 - resolve?: RollupNodeResolveOptions 33 - input?: string 34 - } 35 - 36 - export function getRollupConfig( 37 - { 38 - input, 39 - replace = {}, 40 - alias = {}, 41 - externals = [], 42 - dev = false, 43 - shouldWatch: watch = false, 44 - resolve: resolveOptions = { 45 - resolveOnly: [/^((?!node_modules).)*$/], 46 - preferBuiltins: true, 47 - }, 48 - plugins = [], 49 - ...options 50 - }: BuildConfigOptions, 51 - { 52 - binaries, 53 - entrypoint, 54 - pkg, 55 - options: { rootDir, suffix }, 56 - }: Package = new Package() 57 - ): RollupOptions[] { 58 - const resolvePath = (...path: string[]) => resolve(rootDir, ...path) 59 - input = input ? resolvePath(input) : entrypoint 60 - if (!input && !binaries.length) return [] 61 - 62 - const name = basename(pkg.name.replace(suffix, '')) 63 - const getFilenames = getNameFunction(rootDir, name) 64 - 65 - const external = [ 66 - // Dependencies that will be installed alongside the package 67 - ...Object.keys(pkg.dependencies || {}), 68 - ...Object.keys(pkg.optionalDependencies || {}), 69 - ...Object.keys(pkg.peerDependencies || {}), 70 - // Builtin node modules 71 - ...builtins, 72 - ...externals, 73 - ] 74 - 75 - const getPlugins = () => 76 - [ 77 - aliasPlugin({ 78 - entries: alias, 79 - }), 80 - replacePlugin({ 81 - exclude: 'node_modules/**', 82 - delimiters: ['', ''], 83 - values: { 84 - ...includeDefinedProperties({ __NODE_ENV__ }), 85 - ...replace, 86 - }, 87 - }), 88 - nodeResolvePlugin(resolveOptions), 89 - commonjsPlugin({ include: /node_modules/ }), 90 - esbuild({ 91 - watch, 92 - target: 'es2018', 93 - }), 94 - jsonPlugin(), 95 - ].concat(plugins) 96 - 97 - const defaultOutputs = [ 98 - { 99 - ...getFilenames(pkg.main), 100 - format: 'cjs', 101 - preferConst: true, 102 - }, 103 - ...includeIf(!dev && pkg.module, pkgModule => ({ 104 - ...getFilenames(pkgModule, '-es'), 105 - format: 'es', 106 - })), 107 - ] 108 - 109 - return [ 110 - ...binaries.map(([binary, input]) => { 111 - return defu({}, options, { 112 - input, 113 - output: { 114 - ...getFilenames(binary), 115 - format: 'cjs', 116 - preferConst: true, 117 - banner: '#!/usr/bin/env node\n', 118 - }, 119 - external, 120 - plugins: getPlugins(), 121 - }) 122 - }), 123 - ...includeIf(input, input => 124 - defu({}, options, { 125 - input, 126 - output: defaultOutputs, 127 - external, 128 - plugins: getPlugins(), 129 - }) 130 - ), 131 - ...includeIf(pkg.types && input, input => ({ 132 - input, 133 - output: { 134 - file: resolvePath(pkg.types || ''), 135 - format: 'es' as const, 136 - }, 137 - external, 138 - plugins: [ 139 - jsonPlugin(), 140 - dts({ 141 - compilerOptions: { 142 - allowJs: true, 143 - }, 144 - }), 145 - ], 146 - })), 147 - ] 148 - }
-18
packages/core/src/build/utils.ts
··· 1 - import { resolve, dirname, basename } from 'path' 2 - 3 - export const getNameFunction = (rootDir: string, packageName: string) => ( 4 - filename: string | undefined, 5 - defaultSuffix = '' 6 - ) => { 7 - return { 8 - dir: filename 9 - ? resolve(rootDir, dirname(filename)) 10 - : resolve(rootDir, 'dist'), 11 - entryFileNames: filename 12 - ? basename(filename) 13 - : `${packageName}${defaultSuffix}.js`, 14 - chunkFileNames: filename 15 - ? `${basename(filename)}-[name].js` 16 - : `${packageName}-[name]${defaultSuffix}.js`, 17 - } as const 18 - }
-170
packages/core/src/package/index.spec.ts
··· 1 - import { resolve } from 'path' 2 - 3 - import { existsSync, remove, readFileSync } from 'fs-extra' 4 - import { RollupBuild } from 'rollup' 5 - 6 - import { Package, PackageOptions } from '.' 7 - 8 - const loadPackage = (options?: PackageOptions) => 9 - new Package({ rootDir: __dirname, ...options }) 10 - 11 - describe('package class', () => { 12 - let core: Package 13 - let cli: Package 14 - let siroc: Package 15 - 16 - beforeEach(() => { 17 - core = loadPackage() 18 - cli = core.load('../cli') 19 - siroc = core.load('../siroc') 20 - }) 21 - 22 - test('should read JSON', () => { 23 - expect(core.pkg.name).toBe('@siroc/core') 24 - expect(siroc.pkg.name).toBe('siroc') 25 - }) 26 - 27 - test('should generate appropriate binary array', () => { 28 - expect(cli.binaries.length).toBe(2) 29 - }) 30 - 31 - test('should generate a package version', () => { 32 - const { version } = core.pkg 33 - const newVersion = core.version 34 - expect(version === newVersion).toBeFalsy() 35 - expect(newVersion.includes(version)).toBeTruthy() 36 - core.suffixAndVersion() 37 - const { version: newestVersion } = core.pkg 38 - expect(newestVersion).toBe(newVersion) 39 - }) 40 - 41 - test('should suffix package', () => { 42 - // TODO: more testing required here 43 - const core = loadPackage({ suffix: '-test' }) 44 - expect(core.pkg.name).toBe('@siroc/core') 45 - core.suffixAndVersion() 46 - expect(core.pkg.name).toBe('@siroc/core-test') 47 - }) 48 - 49 - test('should get git commit and branch', () => { 50 - const { shortCommit, branch } = core 51 - expect(typeof shortCommit).toBe('string') 52 - expect(typeof branch).toBe('string') 53 - }) 54 - 55 - // TODO: move to fixture 56 - test('should generate package stub', async () => { 57 - const files = [ 58 - resolve(__dirname, '../../dist/index.js'), 59 - resolve(__dirname, '../../dist/index.es.js'), 60 - resolve(__dirname, '../../dist/index.d.ts'), 61 - ] 62 - for (const file of files) { 63 - await remove(file) 64 - expect(existsSync(file)).toBeFalsy() 65 - } 66 - 67 - await core.createStubs() 68 - 69 - for (const file of files) { 70 - expect( 71 - readFileSync(file) 72 - .toString() 73 - .replace(/from '.*\/packages/, "from '/packages") 74 - ).toBe(`export * from './../src/index'`) 75 - } 76 - }) 77 - 78 - // TODO: move to fixture 79 - test('should create binary stub', async () => { 80 - const file = resolve(__dirname, '../../../cli/bin/cli.js') 81 - 82 - await remove(file) 83 - expect(existsSync(file)).toBeFalsy() 84 - await cli.createStubs() 85 - expect( 86 - readFileSync(file) 87 - .toString() 88 - .replace(/jiti\('.*\/packages/, "jiti('/packages") 89 - ).toBe( 90 - `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('/packages/cli/src/index')` 91 - ) 92 - }) 93 - 94 - test('should handle git data', () => { 95 - const { lastGitTag } = core 96 - expect(lastGitTag[0]).toBe('v') 97 - }) 98 - 99 - test('should parse contributors', () => { 100 - const result = [ 101 - core.parsePerson('Barney Rubble (http://barnyrubble.tumblr.com/)'), 102 - core.parsePerson( 103 - 'Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)' 104 - ), 105 - ] 106 - expect(result).toEqual([ 107 - { 108 - name: 'Barney Rubble', 109 - url: 'http://barnyrubble.tumblr.com/', 110 - }, 111 - { 112 - name: 'Barney Rubble', 113 - email: 'b@rubble.com', 114 - url: 'http://barnyrubble.tumblr.com/', 115 - }, 116 - ]) 117 - }) 118 - }) 119 - 120 - describe('package hooks', () => { 121 - test('should not error if no hooks are provided', async () => { 122 - const core = loadPackage() 123 - let errored 124 - try { 125 - await core.callHook('build:done', { bundle: {} as RollupBuild }) 126 - } catch { 127 - errored = true 128 - } 129 - expect(errored).toBeFalsy() 130 - }) 131 - 132 - test('should call hooks when provided', () => { 133 - let called = false 134 - const bundle = {} as RollupBuild 135 - const hookPkg = loadPackage({ 136 - hooks: { 137 - 'build:done'(pkg, { bundle: providedBundle }) { 138 - expect(pkg).toBe(hookPkg) 139 - expect(providedBundle).toBe(bundle) 140 - called = true 141 - }, 142 - }, 143 - }) 144 - expect(called).toBeFalsy() 145 - hookPkg.callHook('build:done', { bundle }) 146 - expect(called).toBeTruthy() 147 - }) 148 - 149 - test('should call multiple hooks', async () => { 150 - let called = 0 151 - const hookPkg = loadPackage({ 152 - hooks: { 153 - 'build:done': [ 154 - async () => { 155 - called = called + 1 156 - }, 157 - () => { 158 - throw new Error('did not work') 159 - }, 160 - async () => { 161 - called = called + 2 162 - }, 163 - ], 164 - }, 165 - }) 166 - expect(called).toBe(0) 167 - await hookPkg.callHook('build:done', { bundle: {} as RollupBuild }) 168 - expect(called).toBe(3) 169 - }) 170 - })
-494
packages/core/src/package/index.ts
··· 1 - import { basename, dirname, relative, resolve } from 'path' 2 - 3 - import { bold } from 'chalk' 4 - import consola, { Consola } from 'consola' 5 - import execa, { Options } from 'execa' 6 - import { 7 - copy, 8 - existsSync, 9 - readJSONSync, 10 - writeFile, 11 - mkdirp, 12 - chmod, 13 - } from 'fs-extra' 14 - import { RollupOptions } from 'rollup' 15 - import sortPackageJson from 'sort-package-json' 16 - 17 - import type { 18 - BuildConfigOptions, 19 - PackageCommands, 20 - PackageHookOptions, 21 - PackageHooks, 22 - } from '../build' 23 - import { 24 - glob, 25 - runInParallel, 26 - sortObjectKeys, 27 - tryJSON, 28 - tryRequire, 29 - RequireProperties, 30 - } from '../utils' 31 - import type { PackageJson } from './types' 32 - 33 - interface DefaultPackageOptions { 34 - rootDir: string 35 - build: boolean 36 - suffix: string 37 - hooks: PackageHooks 38 - commands: PackageCommands 39 - linkedDependencies?: string[] 40 - pkg?: PackageJson 41 - rollup?: BuildConfigOptions & RollupOptions 42 - sortDependencies?: boolean 43 - } 44 - 45 - export type PackageOptions = Partial<DefaultPackageOptions> 46 - 47 - export interface BuildOptions { 48 - dev?: boolean 49 - watch?: boolean 50 - } 51 - 52 - // 'package.js' is legacy and will go 53 - const configPaths = [ 54 - 'siroc.config.ts', 55 - 'siroc.config.js', 56 - 'siroc.config.json', 57 - 'package.js', 58 - ] 59 - 60 - const DEFAULTS: DefaultPackageOptions = { 61 - rootDir: process.cwd(), 62 - build: true, 63 - suffix: process.env.PACKAGE_SUFFIX ? `-${process.env.PACKAGE_SUFFIX}` : '', 64 - hooks: {}, 65 - commands: {}, 66 - } 67 - 68 - export class Package { 69 - options: DefaultPackageOptions 70 - logger: Consola 71 - pkg: RequireProperties<PackageJson, 'name' | 'version'> 72 - 73 - constructor(options: PackageOptions = {}) { 74 - this.options = Object.assign({}, DEFAULTS, options) 75 - 76 - // Basic logger 77 - this.logger = consola 78 - 79 - this.pkg = this.loadPackageJSON() 80 - 81 - // Use tagged logger 82 - this.logger = consola.withTag(this.pkg.name) 83 - 84 - this.loadConfig() 85 - } 86 - 87 - loadPackageJSON(): this['pkg'] { 88 - try { 89 - return readJSONSync(this.resolvePath('package.json')) 90 - } catch { 91 - if (this.options.rootDir === '/') { 92 - this.logger.error( 93 - `Could not locate a ${bold('package.json')} in ${bold( 94 - DEFAULTS.rootDir 95 - )} or its parent directories.` 96 - ) 97 - throw new Error( 98 - `Could not locate a package.json in ${DEFAULTS.rootDir} or its parent directories.` 99 - ) 100 - } 101 - this.options.rootDir = this.resolvePath('..') 102 - return this.loadPackageJSON() 103 - } 104 - } 105 - 106 - /** 107 - * Resolve path relative to this package 108 - */ 109 - resolvePath(...pathSegments: string[]) { 110 - return resolve(this.options.rootDir, ...pathSegments) 111 - } 112 - 113 - /** 114 - * Load options from the `siroc.config.js` in the package directory 115 - */ 116 - loadConfig() { 117 - configPaths.some(path => { 118 - const configPath = this.resolvePath(path) 119 - 120 - const config = tryRequire<PackageOptions>(configPath) 121 - if (!config) return false 122 - 123 - Object.assign(this.options, config) 124 - return true 125 - }) 126 - } 127 - 128 - /** 129 - * Call hooks defined in config file 130 - */ 131 - async callHook<H extends keyof PackageHookOptions>( 132 - name: H, 133 - options: PackageHookOptions[H] 134 - ) { 135 - const fns = this.options.hooks[name] 136 - 137 - if (!fns) return 138 - 139 - const fnArray = Array.isArray(fns) ? fns : [fns] 140 - try { 141 - await runInParallel(fnArray, async fn => fn(this, options)) 142 - } catch (e) { 143 - this.logger.error(`Couldn't run hook for ${this.pkg.name}.`) 144 - } 145 - } 146 - 147 - /** 148 - * Return a new package in a directory relative to the current package 149 - */ 150 - load(relativePath: string, opts?: PackageOptions) { 151 - return new Package( 152 - Object.assign( 153 - { 154 - rootDir: this.resolvePath(relativePath), 155 - }, 156 - opts 157 - ) 158 - ) 159 - } 160 - 161 - /** 162 - * Write updated `package.json` 163 - */ 164 - async writePackage() { 165 - const pkgPath = this.resolvePath('package.json') 166 - this.logger.debug('Writing', pkgPath) 167 - await writeFile(pkgPath, JSON.stringify(this.pkg, null, 2) + '\n') 168 - } 169 - 170 - /** 171 - * A version string unique to the current git commit and date 172 - */ 173 - get version() { 174 - const date = Math.round(Date.now() / (1000 * 60)) 175 - const gitCommit = this.shortCommit 176 - const baseVersion = this.pkg.version.split('-')[0] 177 - return `${baseVersion}-${date}.${gitCommit}` 178 - } 179 - 180 - /** 181 - * Add suffix to all dependencies and set new version 182 - */ 183 - suffixAndVersion() { 184 - this.logger.info(`Adding suffix ${this.options.suffix}`) 185 - 186 - const oldPkgName = this.pkg.name 187 - 188 - // Add suffix to the package name 189 - if (!oldPkgName.includes(this.options.suffix)) { 190 - this.pkg.name += this.options.suffix 191 - } 192 - 193 - // Apply suffix to all linkedDependencies 194 - if (this.pkg.dependencies) { 195 - for (const oldName of this.options.linkedDependencies || []) { 196 - const name = oldName + this.options.suffix 197 - const version = 198 - this.pkg.dependencies[oldName] || this.pkg.dependencies[name] 199 - 200 - delete this.pkg.dependencies[oldName] 201 - this.pkg.dependencies[name] = version 202 - } 203 - } 204 - 205 - if (typeof this.pkg.bin === 'string') { 206 - const { bin } = this.pkg 207 - this.pkg.bin = { 208 - [oldPkgName]: bin, 209 - [this.pkg.name]: bin, 210 - } 211 - } 212 - 213 - this.pkg.version = this.version 214 - } 215 - 216 - /** 217 - * Synchronise version across all packages in monorepo 218 - */ 219 - syncLinkedDependencies() { 220 - // Apply suffix to all linkedDependencies 221 - for (const _name of this.options.linkedDependencies || []) { 222 - const name = _name + (this.options.suffix || '') 223 - 224 - // Try to read pkg 225 - const pkg = 226 - tryJSON<PackageJson>(`${name}/package.json`) || 227 - tryJSON<PackageJson>(`${_name}/package.json`) 228 - 229 - // Skip if pkg or dependency not found 230 - if ( 231 - !pkg || 232 - !pkg.version || 233 - !this.pkg.dependencies || 234 - !this.pkg.dependencies[name] 235 - ) { 236 - continue 237 - } 238 - 239 - // Current version 240 - const currentVersion = this.pkg.dependencies[name] 241 - const caret = currentVersion[0] === '^' 242 - 243 - // Sync version 244 - this.pkg.dependencies[name] = caret ? `^${pkg.version}` : pkg.version 245 - } 246 - } 247 - 248 - publish(tag = 'latest') { 249 - this.logger.info( 250 - `publishing ${this.pkg.name}@${this.pkg.version} with tag ${tag}` 251 - ) 252 - this.exec(`npm publish --tag ${tag}`) 253 - } 254 - 255 - /** 256 - * Synchronise fields from another package to this package 257 - */ 258 - copyFieldsFrom(source: Package, fields: Array<keyof PackageJson> = []) { 259 - for (const field of fields) { 260 - ;(this.pkg[field] as any) = source.pkg[field] as any 261 - } 262 - } 263 - 264 - async setBinaryPermissions() { 265 - await Promise.all(this.binaries.map(([binary]) => chmod(binary, 0o777))) 266 - } 267 - 268 - async createBinaryStubs() { 269 - await runInParallel(this.binaries, async ([binary, entrypoint]) => { 270 - if (!entrypoint) return 271 - 272 - const outDir = dirname(binary) 273 - if (!existsSync(outDir)) await mkdirp(outDir) 274 - const absPath = entrypoint.replace(/(\.[jt]s)$/, '') 275 - await writeFile( 276 - binary, 277 - `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('${absPath}')` 278 - ) 279 - await this.setBinaryPermissions() 280 - }) 281 - } 282 - 283 - async createStub(path: string | undefined) { 284 - if (!path || !this.entrypoint || !this.options.build) return 285 - 286 - const outFile = this.resolvePath(path) 287 - const outDir = dirname(outFile) 288 - if (!existsSync(outDir)) await mkdirp(outDir) 289 - const relativeEntrypoint = relative(outDir, this.entrypoint).replace( 290 - /(\.[jt]s)$/, 291 - '' 292 - ) 293 - await writeFile(outFile, `export * from './${relativeEntrypoint}'`) 294 - } 295 - 296 - async createStubs() { 297 - return Promise.all([ 298 - this.createBinaryStubs(), 299 - this.createStub(this.pkg.main), 300 - this.createStub(this.pkg.module), 301 - this.createStub(this.pkg.types), 302 - ]) 303 - } 304 - 305 - /** 306 - * Copy files from another package's directory 307 - */ 308 - async copyFilesFrom(source: Package, files: string[]) { 309 - for (const file of files || source.pkg.files || []) { 310 - const src = resolve(source.options.rootDir, file) 311 - const dst = resolve(this.options.rootDir, file) 312 - await copy(src, dst) 313 - } 314 - } 315 - 316 - /** 317 - * Sort `package.json` and sort package dependencies alphabetically (if enabled in options) 318 - */ 319 - autoFix() { 320 - this.pkg = sortPackageJson(this.pkg) 321 - if (this.options.sortDependencies) this.sortDependencies() 322 - } 323 - 324 - /** 325 - * Sort package depndencies alphabetically by object key 326 - */ 327 - sortDependencies() { 328 - if (this.pkg.dependencies) { 329 - this.pkg.dependencies = sortObjectKeys(this.pkg.dependencies) 330 - } 331 - 332 - if (this.pkg.devDependencies) { 333 - this.pkg.devDependencies = sortObjectKeys(this.pkg.devDependencies) 334 - } 335 - } 336 - 337 - execInteractive(command: string) { 338 - const options: Options = { 339 - cwd: this.options.rootDir, 340 - env: process.env, 341 - shell: true, 342 - stdio: 'inherit', 343 - } 344 - return execa.command(command, options) 345 - } 346 - 347 - /** 348 - * Execute command in the package root directory 349 - */ 350 - exec(command: string, { silent = false } = {}) { 351 - const options = { 352 - cwd: this.options.rootDir, 353 - env: process.env, 354 - } 355 - 356 - const r = execa.commandSync(command, options) 357 - 358 - if (!silent) { 359 - if (r.failed) { 360 - this.logger.error(command, r.stderr.trim()) 361 - } else { 362 - this.logger.success(command, r.stdout.trim()) 363 - } 364 - } 365 - 366 - return { 367 - signal: r.signal, 368 - stdout: String(r.stdout).trim(), 369 - stderr: String(r.stderr).trim(), 370 - } 371 - } 372 - 373 - private resolveEntrypoint(path = this.pkg.main) { 374 - if (!path) return undefined 375 - 376 - const basefile = basename(path).split('.').slice(0, -1).join() 377 - let input!: string 378 - const filenames = [basefile, `${basefile}/index`, 'index'] 379 - .map(name => [`${name}.ts`, `${name}.js`]) 380 - .reduce((names, arr) => { 381 - arr.forEach(name => names.push(name)) 382 - return names 383 - }, [] as string[]) 384 - filenames.some(filename => { 385 - input = this.resolvePath('src', filename) 386 - return existsSync(input) 387 - }) 388 - return input 389 - } 390 - 391 - parsePerson(person: string) { 392 - /* eslint-disable no-unused-vars */ 393 - /* eslint-disable @typescript-eslint/no-unused-vars */ 394 - const [_matchedName, name] = person.match(/(^[^<(]*[^ <(])/) || [] 395 - const [_matchedEmail, email] = person.match(/<(.*)>/) || [] 396 - const [_matchedUrl, url] = person.match(/\((.*)\)/) || [] 397 - /* eslint-enable */ 398 - return { name, email, url } 399 - } 400 - 401 - get contributors() { 402 - if (!this.pkg.contributors) return [] 403 - 404 - return this.pkg.contributors.map(person => { 405 - if (typeof person === 'string') return this.parsePerson(person) 406 - return person 407 - }) 408 - } 409 - 410 - /** 411 - * The main package entrypoint (source) 412 - */ 413 - get entrypoint() { 414 - return this.resolveEntrypoint() 415 - } 416 - 417 - /** 418 - * An array of built package binary paths and their entrypoints 419 - * @returns an array of tuples of the binary and its corresponding entrypoint 420 - */ 421 - get binaries() { 422 - type Binary = string 423 - type Entrypoint = string | undefined 424 - const { bin } = this.pkg 425 - const files = !bin 426 - ? [] 427 - : typeof bin === 'string' 428 - ? [bin] 429 - : Object.values(bin) 430 - 431 - return Array.from( 432 - new Set<[Binary, Entrypoint]>( 433 - files.map(file => [ 434 - this.resolvePath(file), 435 - this.resolveEntrypoint(file), 436 - ]) 437 - ) 438 - ) 439 - } 440 - 441 - /** 442 - * Return the child packages of this workspace (or, if there are no workspaces specified, just this package) 443 - * @param packageNames If package names are provided, these will serve to limit the packages that are returned 444 - */ 445 - async getWorkspacePackages(packageNames?: string[]) { 446 - const dirs = new Set<string>() 447 - 448 - await runInParallel(this.pkg.workspaces || ['.'], async workspace => { 449 - ;(await glob(workspace)).forEach(dir => dirs.add(dir)) 450 - }) 451 - 452 - const packages = await runInParallel(dirs, dir => { 453 - if (!existsSync(this.resolvePath(dir, 'package.json'))) { 454 - throw new Error('Not a package directory.') 455 - } 456 - const pkg = new Package({ 457 - ...this.options, 458 - rootDir: this.resolvePath(dir), 459 - }) 460 - if (packageNames && !packageNames.includes(pkg.pkg.name)) { 461 - throw new Error('Not a selected package.') 462 - } 463 - return pkg 464 - }) 465 - 466 - return packages 467 - .filter(pkg => pkg.status === 'fulfilled') 468 - .map(pkg => (pkg as PromiseFulfilledResult<Package>).value) 469 - } 470 - 471 - get shortCommit() { 472 - const { stdout } = this.exec('git rev-parse --short HEAD', { 473 - silent: true, 474 - }) 475 - return stdout 476 - } 477 - 478 - get branch() { 479 - const { stdout } = this.exec('git rev-parse --abbrev-ref HEAD', { 480 - silent: true, 481 - }) 482 - return stdout 483 - } 484 - 485 - get lastGitTag() { 486 - const { stdout } = this.exec('git --no-pager tag -l --sort=taggerdate', { 487 - silent: true, 488 - }) 489 - const r = stdout.split('\n') 490 - return r[r.length - 1] 491 - } 492 - } 493 - 494 - export * from './types'
-122
packages/core/src/package/types.ts
··· 1 - /** 2 - * A “person” is an object with a “name” field and optionally “url” and “email”. Or you can shorten that all into a single string, and npm will parse it for you. 3 - */ 4 - export type PackageJsonPerson = 5 - | string 6 - | { 7 - name: string 8 - email?: string 9 - url?: string 10 - } 11 - 12 - export interface Repository { 13 - type: string 14 - url: string 15 - /** 16 - * If the `package.json` for your package is not in the root directory (for example if it is part of a monorepo), you can specify the directory in which it lives: 17 - */ 18 - directory?: string 19 - } 20 - export interface PackageJson { 21 - /** 22 - * The name is what your thing is called. 23 - * Some rules: 24 - 25 - - The name must be less than or equal to 214 characters. This includes the scope for scoped packages. 26 - - The name can’t start with a dot or an underscore. 27 - - New packages must not have uppercase letters in the name. 28 - - The name ends up being part of a URL, an argument on the command line, and a folder name. Therefore, the name can’t contain any non-URL-safe characters. 29 - 30 - */ 31 - name?: string 32 - /** 33 - * Version must be parseable by `node-semver`, which is bundled with npm as a dependency. (`npm install semver` to use it yourself.) 34 - */ 35 - version?: string 36 - /** 37 - * Put a description in it. It’s a string. This helps people discover your package, as it’s listed in `npm search`. 38 - */ 39 - description?: string 40 - /** 41 - * Put keywords in it. It’s an array of strings. This helps people discover your package as it’s listed in `npm search`. 42 - */ 43 - keywords?: string[] 44 - /** 45 - * The url to the project homepage. 46 - */ 47 - homepage?: string 48 - 49 - /** 50 - * The url to your project’s issue tracker and / or the email address to which issues should be reported. These are helpful for people who encounter issues with your package. 51 - */ 52 - bugs?: 53 - | string 54 - | { 55 - url?: string 56 - email?: string 57 - } 58 - /** 59 - * You should specify a license for your package so that people know how they are permitted to use it, and any restrictions you’re placing on it. 60 - */ 61 - licence?: string 62 - /** 63 - * Specify the place where your code lives. This is helpful for people who want to contribute. If the git repo is on GitHub, then the `npm docs` command will be able to find you. 64 - * For GitHub, GitHub gist, Bitbucket, or GitLab repositories you can use the same shortcut syntax you use for npm install: 65 - */ 66 - repository?: string | Repository 67 - /** 68 - * If you set `"private": true` in your package.json, then npm will refuse to publish it. 69 - */ 70 - private?: boolean 71 - /** 72 - * The “author” is one person. 73 - */ 74 - author?: PackageJsonPerson 75 - /** 76 - * “contributors” is an array of people. 77 - */ 78 - contributors?: PackageJsonPerson[] 79 - /** 80 - * The optional `files` field is an array of file patterns that describes the entries to be included when your package is installed as a dependency. File patterns follow a similar syntax to `.gitignore`, but reversed: including a file, directory, or glob pattern (`*`, `**\/*`, and such) will make it so that file is included in the tarball when it’s packed. Omitting the field will make it default to `["*"]`, which means it will include all files. 81 - */ 82 - files?: string[] 83 - /** 84 - * The main field is a module ID that is the primary entry point to your program. That is, if your package is named `foo`, and a user installs it, and then does `require("foo")`, then your main module’s exports object will be returned. 85 - * This should be a module ID relative to the root of your package folder. 86 - * For most modules, it makes the most sense to have a main script and often not much else. 87 - */ 88 - main?: string 89 - /** 90 - * If your module is meant to be used client-side the browser field should be used instead of the main field. This is helpful to hint users that it might rely on primitives that aren’t available in Node.js modules. (e.g. window) 91 - */ 92 - browser?: string 93 - /** 94 - * A map of command name to local file name. On install, npm will symlink that file into `prefix/bin` for global installs, or `./node_modules/.bin/` for local installs. 95 - */ 96 - bin?: string | Record<string, string> 97 - /** 98 - * Specify either a single file or an array of filenames to put in place for the `man` program to find. 99 - */ 100 - man?: string | string[] 101 - /** 102 - * Dependencies are specified in a simple object that maps a package name to a version range. The version range is a string which has one or more space-separated descriptors. Dependencies can also be identified with a tarball or git URL. 103 - */ 104 - dependencies?: Record<string, string> 105 - /** 106 - * If someone is planning on downloading and using your module in their program, then they probably don’t want or need to download and build the external test or documentation framework that you use. 107 - * In this case, it’s best to map these additional items in a `devDependencies` object. 108 - */ 109 - devDependencies?: Record<string, string> 110 - /** 111 - * If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install, then you may put it in the `optionalDependencies` object. This is a map of package name to version or url, just like the `dependencies` object. The difference is that build failures do not cause installation to fail. 112 - */ 113 - optionalDependencies?: Record<string, string> 114 - /** 115 - * In some cases, you want to express the compatibility of your package with a host tool or library, while not necessarily doing a `require` of this host. This is usually referred to as a plugin. Notably, your module may be exposing a specific interface, expected and specified by the host documentation. 116 - */ 117 - peerDependencies?: Record<string, string> 118 - 119 - types?: string 120 - module?: string 121 - workspaces?: string[] 122 - }
-102
packages/core/src/utils/index.ts
··· 1 - import { extname } from 'path' 2 - import { readJSONSync } from 'fs-extra' 3 - import _glob from 'glob' 4 - import _jiti from 'jiti' 5 - 6 - import { loadAllSettled, loadFromEntries } from './polyfills' 7 - 8 - const jiti = _jiti() 9 - 10 - if (!Object.fromEntries) loadFromEntries() 11 - if (!Promise.allSettled) loadAllSettled() 12 - 13 - export const glob = (pattern: string) => 14 - new Promise<string[]>((resolve, reject) => 15 - _glob(pattern, (err, matches) => { 16 - if (err) return reject(err) 17 - resolve(matches) 18 - }) 19 - ) 20 - 21 - export const sortObjectKeys = <T>(obj: Record<string, T>) => 22 - Object.fromEntries( 23 - Object.entries(obj).sort(([key1], [key2]) => +key2 - +key1) 24 - ) 25 - 26 - export const tryJSON = <T = unknown>(id: string) => { 27 - try { 28 - return readJSONSync(id) as T 29 - } catch { 30 - return undefined 31 - } 32 - } 33 - 34 - export const tryRequire = <T = unknown>(id: string) => { 35 - try { 36 - if (extname(id) === 'json') return tryJSON(id) 37 - const contents = jiti(id) as T | { default: T } 38 - if ('default' in contents) return contents.default 39 - return contents 40 - } catch { 41 - return undefined 42 - } 43 - } 44 - 45 - export type RequireProperties<T, R extends keyof T> = Omit<T, R> & 46 - Required<Pick<T, R>> 47 - 48 - export const ensureUnique = <T>(items: T[]) => Array.from(new Set(items)) 49 - 50 - export const groupBy = <T extends Record<string, any>, K extends keyof T>( 51 - collection: T[], 52 - key: K 53 - ) => { 54 - const groups = {} as Record<T[K], T[]> 55 - collection.forEach(entry => { 56 - groups[entry[key]] = groups[entry[key]] || [] 57 - groups[entry[key]].push(entry) 58 - }) 59 - return groups 60 - } 61 - 62 - type ExcludeNullable<T extends Record<string, any>> = { 63 - [P in keyof T]: NonNullable<T[P]> 64 - } 65 - 66 - export const includeDefinedProperties = <T extends Record<string, any>>( 67 - options: T 68 - ) => 69 - Object.fromEntries( 70 - // eslint-disable-next-line 71 - Object.entries(options).filter(([_, value]) => value !== undefined) 72 - ) as ExcludeNullable<T> 73 - 74 - type NonFalsy<T> = T extends null | undefined | false ? never : T 75 - 76 - export const includeIf = <T, I>( 77 - test: T, 78 - itemFactory: (outcome: NonFalsy<T>) => I 79 - ) => (test ? [itemFactory(test as any)] : []) 80 - 81 - export const runInParallel = async <T, R extends any>( 82 - items: Iterable<T>, 83 - cb: (item: T, index: number) => Promise<R> | R 84 - ) => { 85 - if (Array.isArray(items)) 86 - return Promise.allSettled(items.map(async (item, index) => cb(item, index))) 87 - 88 - const promises: Array<Promise<R>> = [] 89 - let index = 0 90 - for (const item of items) { 91 - try { 92 - promises.push(Promise.resolve(cb(item, index))) 93 - } catch (e) { 94 - promises.push(Promise.reject(e)) 95 - } 96 - index++ 97 - } 98 - return Promise.allSettled(promises) 99 - } 100 - 101 - export const asArray = <T>(item: T | T[] | undefined): T[] => 102 - item !== undefined ? (Array.isArray(item) ? item : [item]) : []
-37
packages/core/src/utils/polyfills.ts
··· 1 - export function loadAllSettled() { 2 - ;(Promise.allSettled as any) = function <T>( 3 - values: Iterable<T> 4 - ): Promise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]> { 5 - return Promise.all( 6 - Array.from(values).map(promise => { 7 - if (promise instanceof Promise) { 8 - return promise 9 - .then(value => ({ 10 - status: 'fulfilled' as const, 11 - value, 12 - })) 13 - .catch(reason => ({ 14 - status: 'rejected' as const, 15 - reason, 16 - })) 17 - } 18 - return { 19 - status: 'fulfilled' as const, 20 - value: promise, 21 - } 22 - }) 23 - ) 24 - } 25 - } 26 - 27 - export function loadFromEntries() { 28 - Object.fromEntries = function <T = any>( 29 - entries: Iterable<readonly [PropertyKey, T]> 30 - ): { [k: string]: T } { 31 - const newObject: Record<string, T> = {} 32 - for (const [key, value] of entries) { 33 - if (typeof key === 'string') newObject[key] = value 34 - } 35 - return newObject 36 - } 37 - }
-204
packages/core/src/utils/utils.spec.ts
··· 1 - import path from 'path' 2 - 3 - import type { PackageJson } from '../package' 4 - import { loadAllSettled, loadFromEntries } from './polyfills' 5 - import { 6 - asArray, 7 - ensureUnique, 8 - glob, 9 - groupBy, 10 - includeDefinedProperties, 11 - includeIf, 12 - runInParallel, 13 - sortObjectKeys, 14 - tryJSON, 15 - tryRequire, 16 - } from '.' 17 - 18 - describe('asArray', () => { 19 - it('should return an array if passed an undefined value', () => { 20 - expect(asArray(undefined)).toEqual([]) 21 - expect((asArray as any)()).toEqual([]) 22 - }) 23 - it('should return an array of a single item', () => { 24 - expect(asArray(1)).toEqual([1]) 25 - }) 26 - 27 - it('should return an array unchanged', () => { 28 - const array = [1, 2] 29 - expect(asArray(array)).toBe(array) 30 - }) 31 - }) 32 - 33 - describe('ensureUnique', () => { 34 - it('should filter out duplicated items', () => { 35 - const array = [1, 2, 1, 5, 2] 36 - const result = ensureUnique(array) 37 - expect(result).toEqual([1, 2, 5]) 38 - }) 39 - }) 40 - 41 - describe('glob', () => { 42 - it('should resolve paths with a promise', async () => { 43 - const result = await glob('READ*') 44 - expect(result).toEqual(['README.md']) 45 - }) 46 - }) 47 - 48 - describe('groupBy', () => { 49 - const array = [ 50 - { 51 - key: 'key-1', 52 - item: 'item-1', 53 - }, 54 - { 55 - key: 'key-1', 56 - item: 'item-2', 57 - }, 58 - ] 59 - it('should return a grouped object', () => { 60 - const result = groupBy(array, 'key') 61 - expect(result).toEqual({ 62 - 'key-1': array, 63 - }) 64 - }) 65 - it('should separate items', () => { 66 - const result = groupBy(array, 'item') 67 - expect(result).toEqual({ 68 - 'item-1': [array[0]], 69 - 'item-2': [array[1]], 70 - }) 71 - }) 72 - }) 73 - 74 - const definedPropertiesTest = () => { 75 - it('should omit undefined properties', () => { 76 - const obj = { 77 - val: 3, 78 - another: false, 79 - further: undefined, 80 - } 81 - const result = includeDefinedProperties(obj) 82 - expect(result).toEqual({ 83 - val: 3, 84 - another: false, 85 - }) 86 - }) 87 - } 88 - 89 - describe('includeDefinedProperties', definedPropertiesTest) 90 - 91 - describe('includeIf', () => { 92 - it('should return an empty array when falsy', () => { 93 - const results = [ 94 - includeIf(false, () => 'test'), 95 - includeIf(null, () => 'test'), 96 - includeIf(undefined, () => 'test'), 97 - ] 98 - expect(results).toEqual([[], [], []]) 99 - }) 100 - it('should return the item in an array when true', () => { 101 - const result = includeIf(true, () => 'test') 102 - expect(result).toEqual(['test']) 103 - }) 104 - }) 105 - 106 - const runInParallelTest = () => { 107 - it('should run tasks in parallel', async () => { 108 - let i = 0 109 - await runInParallel([1, 2, 3], async num => (i = i + num)) 110 - expect(i).toBe(6) 111 - }) 112 - it('should not fail if one task does', async () => { 113 - const result = await runInParallel([1, 2, 3], async num => { 114 - if (num === 2) throw new Error('') 115 - }) 116 - expect(result.length).toBe(3) 117 - }) 118 - } 119 - 120 - describe('runInParallel', runInParallelTest) 121 - 122 - const sortObjectKeysTest = () => { 123 - const obj = { 124 - c: '', 125 - '@a': '', 126 - Z$: '', 127 - } 128 - it('should sort object keys alphabetically', () => { 129 - const result = sortObjectKeys(obj) 130 - expect(result).toEqual({ 131 - '@a': '', 132 - c: '', 133 - Z$: '', 134 - }) 135 - }) 136 - } 137 - describe('sortObjectKeys', sortObjectKeysTest) 138 - 139 - describe('tryJSON', () => { 140 - it('should not throw an error when passed nonsense', () => { 141 - const result = tryJSON('require this if you dare') 142 - expect(result).toBeFalsy() 143 - }) 144 - it('should resolve JSON', () => { 145 - const result = tryJSON<PackageJson>( 146 - path.resolve(__dirname, '../../package.json') 147 - ) 148 - expect(result!.name).toBeDefined() 149 - }) 150 - }) 151 - 152 - describe('tryRequire', () => { 153 - it('should not throw an error when passed nonsense', () => { 154 - const result = tryRequire('require this if you dare') 155 - expect(result).toBeFalsy() 156 - }) 157 - it('should resolve JS', () => { 158 - const result = tryRequire<any>( 159 - path.resolve(__dirname, '../../../../prettier.config.js') 160 - ) 161 - expect(result.semi).toBe(false) 162 - }) 163 - it('should resolve JSON', () => { 164 - const result = tryRequire<PackageJson>( 165 - path.resolve(__dirname, '../../package.json') 166 - ) 167 - expect(result!.name).toBeDefined() 168 - }) 169 - }) 170 - 171 - describe('utils with polyfills', () => { 172 - let fromEntries: any 173 - let allSettled: any 174 - beforeAll(() => { 175 - fromEntries = Object.fromEntries 176 - allSettled = Promise.allSettled 177 - loadAllSettled() 178 - loadFromEntries() 179 - }) 180 - afterAll(() => { 181 - Object.fromEntries = fromEntries 182 - Promise.allSettled = allSettled 183 - }) 184 - // Object.fromEntries 185 - definedPropertiesTest() 186 - it('should not include non-string values', () => { 187 - const result = Object.fromEntries([ 188 - ['key', 'value'], 189 - [2, 'value'], 190 - ]) 191 - expect(result).toEqual({ key: 'value' }) 192 - }) 193 - // Promise.allSettled 194 - runInParallelTest() 195 - sortObjectKeysTest() 196 - it('should return bare value if not a promise', async () => { 197 - const fn = async () => 'another' 198 - const result = await Promise.allSettled(['test', fn()]) 199 - expect(result).toEqual([ 200 - { status: 'fulfilled', value: 'test' }, 201 - { status: 'fulfilled', value: 'another' }, 202 - ]) 203 - }) 204 - })
-4
packages/core/test/tsd/index.test-d.ts
··· 1 - import { expectType } from 'tsd' 2 - 3 - // eslint-disable-next-line 4 - expectType<void>((() => {})())
-4
packages/siroc/test/tsd/index.test-d.ts
··· 1 - import { expectType } from 'tsd' 2 - 3 - // eslint-disable-next-line 4 - expectType<void>((() => {})())
-4
packages/siroc/test/unit/index.spec.ts
··· 1 - describe('empty', () => { 2 - // eslint-disable-next-line 3 - test('test', async () => {}) 4 - })
+25
src/core/build/__snapshots__/rollup.spec.ts.snap
··· 1 + // Jest Snapshot v1, https://goo.gl/fbAQLP 2 + 3 + exports[`rollupConfig should generate appropriately named outputs 1`] = ` 4 + Object { 5 + "chunkFileNames": "fname-[name].js", 6 + "dir": "/customdist", 7 + "entryFileNames": "fname", 8 + } 9 + `; 10 + 11 + exports[`rollupConfig should generate outputs without defined paths 1`] = ` 12 + Object { 13 + "chunkFileNames": "pkg-[name].js", 14 + "dir": "/dist", 15 + "entryFileNames": "pkg.js", 16 + } 17 + `; 18 + 19 + exports[`rollupConfig should generate outputs without defined paths 2`] = ` 20 + Object { 21 + "chunkFileNames": "pkg-[name]-suffix.js", 22 + "dir": "/dist", 23 + "entryFileNames": "pkg-suffix.js", 24 + } 25 + `;
+3
test/fixture/cli/src/index.ts
··· 1 + export default { 2 + empty: '', 3 + }
+1
test/fixture/default/src/index.ts
··· 1 + export const findUltimateQuestion = () => 42
-25
packages/core/src/build/__snapshots__/rollup.spec.ts.snap
··· 1 - // Jest Snapshot v1, https://goo.gl/fbAQLP 2 - 3 - exports[`rollupConfig should generate appropriately named outputs 1`] = ` 4 - Object { 5 - "chunkFileNames": "fname-[name].js", 6 - "dir": "/customdist", 7 - "entryFileNames": "fname", 8 - } 9 - `; 10 - 11 - exports[`rollupConfig should generate outputs without defined paths 1`] = ` 12 - Object { 13 - "chunkFileNames": "pkg-[name].js", 14 - "dir": "/dist", 15 - "entryFileNames": "pkg.js", 16 - } 17 - `; 18 - 19 - exports[`rollupConfig should generate outputs without defined paths 2`] = ` 20 - Object { 21 - "chunkFileNames": "pkg-[name]-suffix.js", 22 - "dir": "/dist", 23 - "entryFileNames": "pkg-suffix.js", 24 - } 25 - `;