···134134yarn siroc run ls --workspaces
135135```
136136137137-## Presets
138138-139139-### eslint
140140-141141-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).
142142-143143-1. Add the eslint config:
144144-145145- ```bash
146146- yarn add --dev @siroc/eslint-config
147147- ```
148148-149149-2. Add the following `.eslintrc.js` to your project:
150150-151151- ```js
152152- module.exports = {
153153- extends: ['@siroc'],
154154- // Your rules/plugins here
155155- }
156156- ```
157157-158158-### jest
159159-160160-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).
161161-162162-1. Add the jest preset:
163163-164164- ```bash
165165- yarn add --dev @siroc/jest-preset
166166- ```
167167-168168-2. Add the following `jest.config.js` to your project:
169169-170170- ```js
171171- module.exports = {
172172- preset: '@siroc/jest-preset',
173173- // Your customisations here
174174- }
175175- ```
176176-177137## Contributors
178138179139Contributions are very welcome.
···11-<h1 align="center">🌬️ siroc</h1>
22-<p align="center">Zero-config build tooling for Node</p>
33-44-<p align="center">
55-<a href="https://npmjs.com/package/siroc">
66- <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square">
77-</a>
88-<a href="https://npmjs.com/package/siroc">
99- <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square">
1010-</a>
1111-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1212- <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square">
1313-</a>
1414-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1515- <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square">
1616-</a>
1717-</p>
1818-1919-> `siroc` is a zero-config but extensible framework for developing Node applications and libraries
2020-2121-## Features
2222-2323-- 💯 **Zero-config required**: Intelligent support for your package
2424- - Supports running and compiling TypeScript and the latest JavaScript syntax
2525- - Autoconfigured `jest` and `eslint`
2626-- ⚒️ **Extensible**: Write your own commands and build hooks
2727-- 💪 **Typescript**: Fully typed and self-documenting
2828-2929-**`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.**
3030-3131-## Quick start
3232-3333-Just install `siroc`.
3434-3535-```bash
3636-# You can install siroc as a development dependency
3737-yarn add siroc --dev
3838-3939-# ... or install globally
4040-yarn global add siroc
4141-```
4242-4343-## Configuration
4444-4545-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`.
4646-4747-In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them.
4848-4949-### TypeScript
5050-5151-```ts
5252-import type { PackageOptions } from 'siroc'
5353-5454-const config: PackageOptions = {
5555- // fully typed options
5656-}
5757-5858-export default config
5959-```
6060-6161-### JavaScript
6262-6363-```js
6464-/**
6565- * @type {import('siroc').PackageOptions} config
6666- */
6767-const config = {
6868- // fully typed options
6969-}
7070-7171-export default config
7272-```
7373-7474-## Commands
7575-7676-### `siroc build`
7777-7878-`siroc` knows what to build based on your `package.json`.
7979-8080-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.
8181-8282-If you have specified additional binaries, `siroc` will look for input files matching their names.
8383-8484-Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files.
8585-8686-#### Monorepos
8787-8888-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.
8989-9090-```bash
9191-yarn siroc build @mypackage/cli
9292-```
9393-9494-#### Watch mode
9595-9696-You can build in watch mode, which will rebuild as necessary when source files change:
9797-9898-```
9999-yarn siroc build --watch
100100-```
101101-102102-#### Configuration
103103-104104-At the most basic level, your entrypoints are configured in your `package.json`:
105105-106106-- `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin))
107107-- `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main))
108108-109109-#### Build hooks
110110-111111-`siroc` makes available three hooks for customising your build, if you need it.
112112-113113-1. `build:extend`
114114-1. `build:extendRollup`
115115-1. `build:done`
116116-117117-### `siroc dev`
118118-119119-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.
120120-121121-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`.
122122-123123-### `siroc eslint`
124124-125125-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).
126126-127127-If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js`
128128-129129-```js
130130-module.exports = {
131131- extends: ['@siroc'],
132132- // Your rules/plugins here
133133-}
134134-```
135135-136136-### `siroc jest`
137137-138138-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`.
139139-140140-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`
141141-142142-```js
143143-module.exports = {
144144- preset: '@siroc/jest-preset',
145145- // Your customisations here
146146-}
147147-```
148148-149149-### `siroc run`
150150-151151-You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti).
152152-153153-For example:
154154-155155-```bash
156156-# You can run a node script written in TypeScript
157157-yarn siroc run myfile.ts
158158-159159-# You can run a command in all your workspaces
160160-yarn siroc run ls --workspaces
161161-```
162162-163163-## Contributors
164164-165165-Contributions are very welcome.
166166-167167-1. Clone this repo
168168-169169- ```bash
170170- git clone git@github.com:nuxt-contrib/siroc.git
171171- ```
172172-173173-2. Install dependencies and build project
174174-175175- ```bash
176176- yarn
177177-178178- # Stub modules for rapid development
179179- yarn siroc dev
180180-181181- # Test (on changes)
182182- yarn siroc jest
183183- ```
184184-185185-**Tip:** You can also run `yarn link` within a package directory to test the module locally with another project.
186186-187187-## License
188188-189189-[MIT License](./LICENCE) - Made with 💖
···11-<h1 align="center">🌬️ siroc</h1>
22-<p align="center">Zero-config build tooling for Node</p>
33-44-<p align="center">
55-<a href="https://npmjs.com/package/siroc">
66- <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square">
77-</a>
88-<a href="https://npmjs.com/package/siroc">
99- <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square">
1010-</a>
1111-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1212- <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square">
1313-</a>
1414-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1515- <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square">
1616-</a>
1717-</p>
1818-1919-> `siroc` is a zero-config but extensible framework for developing Node applications and libraries
2020-2121-## Features
2222-2323-- 💯 **Zero-config required**: Intelligent support for your package
2424- - Supports running and compiling TypeScript and the latest JavaScript syntax
2525- - Autoconfigured `jest` and `eslint`
2626-- ⚒️ **Extensible**: Write your own commands and build hooks
2727-- 💪 **Typescript**: Fully typed and self-documenting
2828-2929-**`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.**
3030-3131-## Quick start
3232-3333-Just install `siroc`.
3434-3535-```bash
3636-# You can install siroc as a development dependency
3737-yarn add siroc --dev
3838-3939-# ... or install globally
4040-yarn global add siroc
4141-```
4242-4343-## Configuration
4444-4545-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`.
4646-4747-In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them.
4848-4949-### TypeScript
5050-5151-```ts
5252-import type { PackageOptions } from 'siroc'
5353-5454-const config: PackageOptions = {
5555- // fully typed options
5656-}
5757-5858-export default config
5959-```
6060-6161-### JavaScript
6262-6363-```js
6464-/**
6565- * @type {import('siroc').PackageOptions} config
6666- */
6767-const config = {
6868- // fully typed options
6969-}
7070-7171-export default config
7272-```
7373-7474-## Commands
7575-7676-### `siroc build`
7777-7878-`siroc` knows what to build based on your `package.json`.
7979-8080-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.
8181-8282-If you have specified additional binaries, `siroc` will look for input files matching their names.
8383-8484-Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files.
8585-8686-#### Monorepos
8787-8888-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.
8989-9090-```bash
9191-yarn siroc build @mypackage/cli
9292-```
9393-9494-#### Watch mode
9595-9696-You can build in watch mode, which will rebuild as necessary when source files change:
9797-9898-```
9999-yarn siroc build --watch
100100-```
101101-102102-#### Configuration
103103-104104-At the most basic level, your entrypoints are configured in your `package.json`:
105105-106106-- `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin))
107107-- `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main))
108108-109109-#### Build hooks
110110-111111-`siroc` makes available three hooks for customising your build, if you need it.
112112-113113-1. `build:extend`
114114-1. `build:extendRollup`
115115-1. `build:done`
116116-117117-### `siroc dev`
118118-119119-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.
120120-121121-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`.
122122-123123-### `siroc eslint`
124124-125125-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).
126126-127127-If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js`
128128-129129-```js
130130-module.exports = {
131131- extends: ['@siroc'],
132132- // Your rules/plugins here
133133-}
134134-```
135135-136136-### `siroc jest`
137137-138138-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`.
139139-140140-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`
141141-142142-```js
143143-module.exports = {
144144- preset: '@siroc/jest-preset',
145145- // Your customisations here
146146-}
147147-```
148148-149149-### `siroc run`
150150-151151-You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti).
152152-153153-For example:
154154-155155-```bash
156156-# You can run a node script written in TypeScript
157157-yarn siroc run myfile.ts
158158-159159-# You can run a command in all your workspaces
160160-yarn siroc run ls --workspaces
161161-```
162162-163163-## Contributors
164164-165165-Contributions are very welcome.
166166-167167-1. Clone this repo
168168-169169- ```bash
170170- git clone git@github.com:nuxt-contrib/siroc.git
171171- ```
172172-173173-2. Install dependencies and build project
174174-175175- ```bash
176176- yarn
177177-178178- # Stub modules for rapid development
179179- yarn siroc dev
180180-181181- # Test (on changes)
182182- yarn siroc jest
183183- ```
184184-185185-**Tip:** You can also run `yarn link` within a package directory to test the module locally with another project.
186186-187187-## License
188188-189189-[MIT License](./LICENCE) - Made with 💖
···11-<h1 align="center">🌬️ siroc</h1>
22-<p align="center">Zero-config build tooling for Node</p>
33-44-<p align="center">
55-<a href="https://npmjs.com/package/siroc">
66- <img alt="" src="https://img.shields.io/npm/v/siroc/latest.svg?style=flat-square">
77-</a>
88-<a href="https://npmjs.com/package/siroc">
99- <img alt="" src="https://img.shields.io/npm/dt/siroc.svg?style=flat-square">
1010-</a>
1111-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1212- <img alt="" src="https://img.shields.io/lgtm/alerts/github/nuxt-contrib/siroc?style=flat-square">
1313-</a>
1414-<a href="https://lgtm.com/projects/g/nuxt-contrib/siroc">
1515- <img alt="" src="https://img.shields.io/lgtm/grade/javascript/github/nuxt-contrib/siroc?style=flat-square">
1616-</a>
1717-</p>
1818-1919-> `siroc` is a zero-config but extensible framework for developing Node applications and libraries
2020-2121-## Features
2222-2323-- 💯 **Zero-config required**: Intelligent support for your package
2424- - Supports running and compiling TypeScript and the latest JavaScript syntax
2525- - Autoconfigured `jest` and `eslint`
2626-- ⚒️ **Extensible**: Write your own commands and build hooks
2727-- 💪 **Typescript**: Fully typed and self-documenting
2828-2929-**`siroc` is still a work in progress. Feedback is welcome, and changes will be frequent.**
3030-3131-## Quick start
3232-3333-Just install `siroc`.
3434-3535-```bash
3636-# You can install siroc as a development dependency
3737-yarn add siroc --dev
3838-3939-# ... or install globally
4040-yarn global add siroc
4141-```
4242-4343-## Configuration
4444-4545-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`.
4646-4747-In a monorepo, any configuration options at the root level are inherited by your workspaces, though of course you can override them.
4848-4949-### TypeScript
5050-5151-```ts
5252-import type { PackageOptions } from 'siroc'
5353-5454-const config: PackageOptions = {
5555- // fully typed options
5656-}
5757-5858-export default config
5959-```
6060-6161-### JavaScript
6262-6363-```js
6464-/**
6565- * @type {import('siroc').PackageOptions} config
6666- */
6767-const config = {
6868- // fully typed options
6969-}
7070-7171-export default config
7272-```
7373-7474-## Commands
7575-7676-### `siroc build`
7777-7878-`siroc` knows what to build based on your `package.json`.
7979-8080-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.
8181-8282-If you have specified additional binaries, `siroc` will look for input files matching their names.
8383-8484-Under the hood, `siroc` uses `rollup` and `esbuild` to build and produce type definitions for your files.
8585-8686-#### Monorepos
8787-8888-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.
8989-9090-```bash
9191-yarn siroc build @mypackage/cli
9292-```
9393-9494-#### Watch mode
9595-9696-You can build in watch mode, which will rebuild as necessary when source files change:
9797-9898-```
9999-yarn siroc build --watch
100100-```
101101-102102-#### Configuration
103103-104104-At the most basic level, your entrypoints are configured in your `package.json`:
105105-106106-- `bin` (see [npm docs](https://docs.npmjs.com/files/package.json#bin))
107107-- `main` and `module` (see [npm docs](https://docs.npmjs.com/files/package.json#main))
108108-109109-#### Build hooks
110110-111111-`siroc` makes available three hooks for customising your build, if you need it.
112112-113113-1. `build:extend`
114114-1. `build:extendRollup`
115115-1. `build:done`
116116-117117-### `siroc dev`
118118-119119-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.
120120-121121-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`.
122122-123123-### `siroc eslint`
124124-125125-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).
126126-127127-If you would like to extend or modify the siroc base config you can do so with the following `.eslintrc.js`
128128-129129-```js
130130-module.exports = {
131131- extends: ['@siroc'],
132132- // Your rules/plugins here
133133-}
134134-```
135135-136136-### `siroc jest`
137137-138138-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`.
139139-140140-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`
141141-142142-```js
143143-module.exports = {
144144- preset: '@siroc/jest-preset',
145145- // Your customisations here
146146-}
147147-```
148148-149149-### `siroc run`
150150-151151-You can run arbitrary shell commands or node scripts using the power of [the `jiti` runtime](https://github.com/nuxt-contrib/jiti).
152152-153153-For example:
154154-155155-```bash
156156-# You can run a node script written in TypeScript
157157-yarn siroc run myfile.ts
158158-159159-# You can run a command in all your workspaces
160160-yarn siroc run ls --workspaces
161161-```
162162-163163-## Contributors
164164-165165-Contributions are very welcome.
166166-167167-1. Clone this repo
168168-169169- ```bash
170170- git clone git@github.com:nuxt-contrib/siroc.git
171171- ```
172172-173173-2. Install dependencies and build project
174174-175175- ```bash
176176- yarn
177177-178178- # Stub modules for rapid development
179179- yarn siroc dev
180180-181181- # Test (on changes)
182182- yarn siroc jest
183183- ```
184184-185185-**Tip:** You can also run `yarn link` within a package directory to test the module locally with another project.
186186-187187-## License
188188-189189-[MIT License](./LICENCE) - Made with 💖
···11+import { basename, dirname, relative, resolve } from 'path'
22+33+import { bold } from 'chalk'
44+import consola, { Consola } from 'consola'
55+import execa, { Options } from 'execa'
66+import {
77+ copy,
88+ existsSync,
99+ readJSONSync,
1010+ writeFile,
1111+ mkdirp,
1212+ chmod,
1313+} from 'fs-extra'
1414+import { RollupOptions } from 'rollup'
1515+import sortPackageJson from 'sort-package-json'
1616+1717+import type {
1818+ BuildConfigOptions,
1919+ PackageCommands,
2020+ PackageHookOptions,
2121+ PackageHooks,
2222+} from '../build'
2323+import {
2424+ glob,
2525+ runInParallel,
2626+ sortObjectKeys,
2727+ tryJSON,
2828+ tryRequire,
2929+ RequireProperties,
3030+} from '../utils'
3131+import type { PackageJson } from './types'
3232+3333+interface DefaultPackageOptions {
3434+ rootDir: string
3535+ build: boolean
3636+ suffix: string
3737+ hooks: PackageHooks
3838+ commands: PackageCommands
3939+ linkedDependencies?: string[]
4040+ pkg?: PackageJson
4141+ rollup?: BuildConfigOptions & RollupOptions
4242+ sortDependencies?: boolean
4343+}
4444+4545+export type PackageOptions = Partial<DefaultPackageOptions>
4646+4747+export interface BuildOptions {
4848+ dev?: boolean
4949+ watch?: boolean
5050+}
5151+5252+// 'package.js' is legacy and will go
5353+const configPaths = [
5454+ 'siroc.config.ts',
5555+ 'siroc.config.js',
5656+ 'siroc.config.json',
5757+ 'package.js',
5858+]
5959+6060+const DEFAULTS: DefaultPackageOptions = {
6161+ rootDir: process.cwd(),
6262+ build: true,
6363+ suffix: process.env.PACKAGE_SUFFIX ? `-${process.env.PACKAGE_SUFFIX}` : '',
6464+ hooks: {},
6565+ commands: {},
6666+}
6767+6868+export class Package {
6969+ options: DefaultPackageOptions
7070+ logger: Consola
7171+ pkg: RequireProperties<PackageJson, 'name' | 'version'>
7272+7373+ constructor(options: PackageOptions = {}) {
7474+ this.options = Object.assign({}, DEFAULTS, options)
7575+7676+ // Basic logger
7777+ this.logger = consola
7878+7979+ this.pkg = this.loadPackageJSON()
8080+8181+ // Use tagged logger
8282+ this.logger = consola.withTag(this.pkg.name)
8383+8484+ this.loadConfig()
8585+ }
8686+8787+ loadPackageJSON(): this['pkg'] {
8888+ try {
8989+ return readJSONSync(this.resolvePath('package.json'))
9090+ } catch {
9191+ if (this.options.rootDir === '/') {
9292+ this.logger.error(
9393+ `Could not locate a ${bold('package.json')} in ${bold(
9494+ DEFAULTS.rootDir
9595+ )} or its parent directories.`
9696+ )
9797+ throw new Error(
9898+ `Could not locate a package.json in ${DEFAULTS.rootDir} or its parent directories.`
9999+ )
100100+ }
101101+ this.options.rootDir = this.resolvePath('..')
102102+ return this.loadPackageJSON()
103103+ }
104104+ }
105105+106106+ /**
107107+ * Resolve path relative to this package
108108+ */
109109+ resolvePath(...pathSegments: string[]) {
110110+ return resolve(this.options.rootDir, ...pathSegments)
111111+ }
112112+113113+ /**
114114+ * Load options from the `siroc.config.js` in the package directory
115115+ */
116116+ loadConfig() {
117117+ configPaths.some(path => {
118118+ const configPath = this.resolvePath(path)
119119+120120+ const config = tryRequire<PackageOptions>(configPath)
121121+ if (!config) return false
122122+123123+ Object.assign(this.options, config)
124124+ return true
125125+ })
126126+ }
127127+128128+ /**
129129+ * Call hooks defined in config file
130130+ */
131131+ async callHook<H extends keyof PackageHookOptions>(
132132+ name: H,
133133+ options: PackageHookOptions[H]
134134+ ) {
135135+ const fns = this.options.hooks[name]
136136+137137+ if (!fns) return
138138+139139+ const fnArray = Array.isArray(fns) ? fns : [fns]
140140+ try {
141141+ await runInParallel(fnArray, async fn => fn(this, options))
142142+ } catch (e) {
143143+ this.logger.error(`Couldn't run hook for ${this.pkg.name}.`)
144144+ }
145145+ }
146146+147147+ /**
148148+ * Return a new package in a directory relative to the current package
149149+ */
150150+ load(relativePath: string, opts?: PackageOptions) {
151151+ return new Package(
152152+ Object.assign(
153153+ {
154154+ rootDir: this.resolvePath(relativePath),
155155+ },
156156+ opts
157157+ )
158158+ )
159159+ }
160160+161161+ /**
162162+ * Write updated `package.json`
163163+ */
164164+ async writePackage() {
165165+ const pkgPath = this.resolvePath('package.json')
166166+ this.logger.debug('Writing', pkgPath)
167167+ await writeFile(pkgPath, JSON.stringify(this.pkg, null, 2) + '\n')
168168+ }
169169+170170+ /**
171171+ * A version string unique to the current git commit and date
172172+ */
173173+ get version() {
174174+ const date = Math.round(Date.now() / (1000 * 60))
175175+ const gitCommit = this.shortCommit
176176+ const baseVersion = this.pkg.version.split('-')[0]
177177+ return `${baseVersion}-${date}.${gitCommit}`
178178+ }
179179+180180+ /**
181181+ * Add suffix to all dependencies and set new version
182182+ */
183183+ suffixAndVersion() {
184184+ this.logger.info(`Adding suffix ${this.options.suffix}`)
185185+186186+ const oldPkgName = this.pkg.name
187187+188188+ // Add suffix to the package name
189189+ if (!oldPkgName.includes(this.options.suffix)) {
190190+ this.pkg.name += this.options.suffix
191191+ }
192192+193193+ // Apply suffix to all linkedDependencies
194194+ if (this.pkg.dependencies) {
195195+ for (const oldName of this.options.linkedDependencies || []) {
196196+ const name = oldName + this.options.suffix
197197+ const version =
198198+ this.pkg.dependencies[oldName] || this.pkg.dependencies[name]
199199+200200+ delete this.pkg.dependencies[oldName]
201201+ this.pkg.dependencies[name] = version
202202+ }
203203+ }
204204+205205+ if (typeof this.pkg.bin === 'string') {
206206+ const { bin } = this.pkg
207207+ this.pkg.bin = {
208208+ [oldPkgName]: bin,
209209+ [this.pkg.name]: bin,
210210+ }
211211+ }
212212+213213+ this.pkg.version = this.version
214214+ }
215215+216216+ /**
217217+ * Synchronise version across all packages in monorepo
218218+ */
219219+ syncLinkedDependencies() {
220220+ // Apply suffix to all linkedDependencies
221221+ for (const _name of this.options.linkedDependencies || []) {
222222+ const name = _name + (this.options.suffix || '')
223223+224224+ // Try to read pkg
225225+ const pkg =
226226+ tryJSON<PackageJson>(`${name}/package.json`) ||
227227+ tryJSON<PackageJson>(`${_name}/package.json`)
228228+229229+ // Skip if pkg or dependency not found
230230+ if (
231231+ !pkg ||
232232+ !pkg.version ||
233233+ !this.pkg.dependencies ||
234234+ !this.pkg.dependencies[name]
235235+ ) {
236236+ continue
237237+ }
238238+239239+ // Current version
240240+ const currentVersion = this.pkg.dependencies[name]
241241+ const caret = currentVersion[0] === '^'
242242+243243+ // Sync version
244244+ this.pkg.dependencies[name] = caret ? `^${pkg.version}` : pkg.version
245245+ }
246246+ }
247247+248248+ publish(tag = 'latest') {
249249+ this.logger.info(
250250+ `publishing ${this.pkg.name}@${this.pkg.version} with tag ${tag}`
251251+ )
252252+ this.exec(`npm publish --tag ${tag}`)
253253+ }
254254+255255+ /**
256256+ * Synchronise fields from another package to this package
257257+ */
258258+ copyFieldsFrom(source: Package, fields: Array<keyof PackageJson> = []) {
259259+ for (const field of fields) {
260260+ ;(this.pkg[field] as any) = source.pkg[field] as any
261261+ }
262262+ }
263263+264264+ async setBinaryPermissions() {
265265+ await Promise.all(this.binaries.map(([binary]) => chmod(binary, 0o777)))
266266+ }
267267+268268+ async createBinaryStubs() {
269269+ await runInParallel(this.binaries, async ([binary, entrypoint]) => {
270270+ if (!entrypoint) return
271271+272272+ const outDir = dirname(binary)
273273+ if (!existsSync(outDir)) await mkdirp(outDir)
274274+ const absPath = entrypoint.replace(/(\.[jt]s)$/, '')
275275+ await writeFile(
276276+ binary,
277277+ `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('${absPath}')`
278278+ )
279279+ await this.setBinaryPermissions()
280280+ })
281281+ }
282282+283283+ async createStub(path: string | undefined) {
284284+ if (!path || !this.entrypoint || !this.options.build) return
285285+286286+ const outFile = this.resolvePath(path)
287287+ const outDir = dirname(outFile)
288288+ if (!existsSync(outDir)) await mkdirp(outDir)
289289+ const relativeEntrypoint = relative(outDir, this.entrypoint).replace(
290290+ /(\.[jt]s)$/,
291291+ ''
292292+ )
293293+ await writeFile(outFile, `export * from './${relativeEntrypoint}'`)
294294+ }
295295+296296+ async createStubs() {
297297+ return Promise.all([
298298+ this.createBinaryStubs(),
299299+ this.createStub(this.pkg.main),
300300+ this.createStub(this.pkg.module),
301301+ this.createStub(this.pkg.types),
302302+ ])
303303+ }
304304+305305+ /**
306306+ * Copy files from another package's directory
307307+ */
308308+ async copyFilesFrom(source: Package, files: string[]) {
309309+ for (const file of files || source.pkg.files || []) {
310310+ const src = resolve(source.options.rootDir, file)
311311+ const dst = resolve(this.options.rootDir, file)
312312+ await copy(src, dst)
313313+ }
314314+ }
315315+316316+ /**
317317+ * Sort `package.json` and sort package dependencies alphabetically (if enabled in options)
318318+ */
319319+ autoFix() {
320320+ this.pkg = sortPackageJson(this.pkg)
321321+ if (this.options.sortDependencies) this.sortDependencies()
322322+ }
323323+324324+ /**
325325+ * Sort package depndencies alphabetically by object key
326326+ */
327327+ sortDependencies() {
328328+ if (this.pkg.dependencies) {
329329+ this.pkg.dependencies = sortObjectKeys(this.pkg.dependencies)
330330+ }
331331+332332+ if (this.pkg.devDependencies) {
333333+ this.pkg.devDependencies = sortObjectKeys(this.pkg.devDependencies)
334334+ }
335335+ }
336336+337337+ execInteractive(command: string) {
338338+ const options: Options = {
339339+ cwd: this.options.rootDir,
340340+ env: process.env,
341341+ shell: true,
342342+ stdio: 'inherit',
343343+ }
344344+ return execa.command(command, options)
345345+ }
346346+347347+ /**
348348+ * Execute command in the package root directory
349349+ */
350350+ exec(command: string, { silent = false } = {}) {
351351+ const options = {
352352+ cwd: this.options.rootDir,
353353+ env: process.env,
354354+ }
355355+356356+ const r = execa.commandSync(command, options)
357357+358358+ if (!silent) {
359359+ if (r.failed) {
360360+ this.logger.error(command, r.stderr.trim())
361361+ } else {
362362+ this.logger.success(command, r.stdout.trim())
363363+ }
364364+ }
365365+366366+ return {
367367+ signal: r.signal,
368368+ stdout: String(r.stdout).trim(),
369369+ stderr: String(r.stderr).trim(),
370370+ }
371371+ }
372372+373373+ private resolveEntrypoint(path = this.pkg.main) {
374374+ if (!path) return undefined
375375+376376+ const basefile = basename(path).split('.').slice(0, -1).join()
377377+ let input!: string
378378+ const filenames = [basefile, `${basefile}/index`, 'index']
379379+ .map(name => [`${name}.ts`, `${name}.js`])
380380+ .reduce((names, arr) => {
381381+ arr.forEach(name => names.push(name))
382382+ return names
383383+ }, [] as string[])
384384+ filenames.some(filename => {
385385+ input = this.resolvePath('src', filename)
386386+ return existsSync(input)
387387+ })
388388+ return input
389389+ }
390390+391391+ parsePerson(person: string) {
392392+ /* eslint-disable no-unused-vars */
393393+ /* eslint-disable @typescript-eslint/no-unused-vars */
394394+ const [_matchedName, name] = person.match(/(^[^<(]*[^ <(])/) || []
395395+ const [_matchedEmail, email] = person.match(/<(.*)>/) || []
396396+ const [_matchedUrl, url] = person.match(/\((.*)\)/) || []
397397+ /* eslint-enable */
398398+ return { name, email, url }
399399+ }
400400+401401+ get contributors() {
402402+ if (!this.pkg.contributors) return []
403403+404404+ return this.pkg.contributors.map(person => {
405405+ if (typeof person === 'string') return this.parsePerson(person)
406406+ return person
407407+ })
408408+ }
409409+410410+ /**
411411+ * The main package entrypoint (source)
412412+ */
413413+ get entrypoint() {
414414+ return this.resolveEntrypoint()
415415+ }
416416+417417+ /**
418418+ * An array of built package binary paths and their entrypoints
419419+ * @returns an array of tuples of the binary and its corresponding entrypoint
420420+ */
421421+ get binaries() {
422422+ type Binary = string
423423+ type Entrypoint = string | undefined
424424+ const { bin } = this.pkg
425425+ const files = !bin
426426+ ? []
427427+ : typeof bin === 'string'
428428+ ? [bin]
429429+ : Object.values(bin)
430430+431431+ return Array.from(
432432+ new Set<[Binary, Entrypoint]>(
433433+ files.map(file => [
434434+ this.resolvePath(file),
435435+ this.resolveEntrypoint(file),
436436+ ])
437437+ )
438438+ )
439439+ }
440440+441441+ /**
442442+ * Return the child packages of this workspace (or, if there are no workspaces specified, just this package)
443443+ * @param packageNames If package names are provided, these will serve to limit the packages that are returned
444444+ */
445445+ async getWorkspacePackages(packageNames?: string[]) {
446446+ const dirs = new Set<string>()
447447+448448+ await runInParallel(this.pkg.workspaces || ['.'], async workspace => {
449449+ ;(await glob(workspace)).forEach(dir => dirs.add(dir))
450450+ })
451451+452452+ const packages = await runInParallel(dirs, dir => {
453453+ if (!existsSync(this.resolvePath(dir, 'package.json'))) {
454454+ throw new Error('Not a package directory.')
455455+ }
456456+ const pkg = new Package({
457457+ ...this.options,
458458+ rootDir: this.resolvePath(dir),
459459+ })
460460+ if (packageNames && !packageNames.includes(pkg.pkg.name)) {
461461+ throw new Error('Not a selected package.')
462462+ }
463463+ return pkg
464464+ })
465465+466466+ return packages
467467+ .filter(pkg => pkg.status === 'fulfilled')
468468+ .map(pkg => (pkg as PromiseFulfilledResult<Package>).value)
469469+ }
470470+471471+ get shortCommit() {
472472+ const { stdout } = this.exec('git rev-parse --short HEAD', {
473473+ silent: true,
474474+ })
475475+ return stdout
476476+ }
477477+478478+ get branch() {
479479+ const { stdout } = this.exec('git rev-parse --abbrev-ref HEAD', {
480480+ silent: true,
481481+ })
482482+ return stdout
483483+ }
484484+485485+ get lastGitTag() {
486486+ const { stdout } = this.exec('git --no-pager tag -l --sort=taggerdate', {
487487+ silent: true,
488488+ })
489489+ const r = stdout.split('\n')
490490+ return r[r.length - 1]
491491+ }
492492+}
493493+494494+export * from './types'
+122
src/core/package/types.ts
···11+/**
22+ * 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.
33+ */
44+export type PackageJsonPerson =
55+ | string
66+ | {
77+ name: string
88+ email?: string
99+ url?: string
1010+ }
1111+1212+export interface Repository {
1313+ type: string
1414+ url: string
1515+ /**
1616+ * 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:
1717+ */
1818+ directory?: string
1919+}
2020+export interface PackageJson {
2121+ /**
2222+ * The name is what your thing is called.
2323+ * Some rules:
2424+2525+ - The name must be less than or equal to 214 characters. This includes the scope for scoped packages.
2626+ - The name can’t start with a dot or an underscore.
2727+ - New packages must not have uppercase letters in the name.
2828+ - 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.
2929+3030+ */
3131+ name?: string
3232+ /**
3333+ * Version must be parseable by `node-semver`, which is bundled with npm as a dependency. (`npm install semver` to use it yourself.)
3434+ */
3535+ version?: string
3636+ /**
3737+ * Put a description in it. It’s a string. This helps people discover your package, as it’s listed in `npm search`.
3838+ */
3939+ description?: string
4040+ /**
4141+ * Put keywords in it. It’s an array of strings. This helps people discover your package as it’s listed in `npm search`.
4242+ */
4343+ keywords?: string[]
4444+ /**
4545+ * The url to the project homepage.
4646+ */
4747+ homepage?: string
4848+4949+ /**
5050+ * 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.
5151+ */
5252+ bugs?:
5353+ | string
5454+ | {
5555+ url?: string
5656+ email?: string
5757+ }
5858+ /**
5959+ * 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.
6060+ */
6161+ licence?: string
6262+ /**
6363+ * 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.
6464+ * For GitHub, GitHub gist, Bitbucket, or GitLab repositories you can use the same shortcut syntax you use for npm install:
6565+ */
6666+ repository?: string | Repository
6767+ /**
6868+ * If you set `"private": true` in your package.json, then npm will refuse to publish it.
6969+ */
7070+ private?: boolean
7171+ /**
7272+ * The “author” is one person.
7373+ */
7474+ author?: PackageJsonPerson
7575+ /**
7676+ * “contributors” is an array of people.
7777+ */
7878+ contributors?: PackageJsonPerson[]
7979+ /**
8080+ * 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.
8181+ */
8282+ files?: string[]
8383+ /**
8484+ * 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.
8585+ * This should be a module ID relative to the root of your package folder.
8686+ * For most modules, it makes the most sense to have a main script and often not much else.
8787+ */
8888+ main?: string
8989+ /**
9090+ * 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)
9191+ */
9292+ browser?: string
9393+ /**
9494+ * 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.
9595+ */
9696+ bin?: string | Record<string, string>
9797+ /**
9898+ * Specify either a single file or an array of filenames to put in place for the `man` program to find.
9999+ */
100100+ man?: string | string[]
101101+ /**
102102+ * 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.
103103+ */
104104+ dependencies?: Record<string, string>
105105+ /**
106106+ * 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.
107107+ * In this case, it’s best to map these additional items in a `devDependencies` object.
108108+ */
109109+ devDependencies?: Record<string, string>
110110+ /**
111111+ * 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.
112112+ */
113113+ optionalDependencies?: Record<string, string>
114114+ /**
115115+ * 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.
116116+ */
117117+ peerDependencies?: Record<string, string>
118118+119119+ types?: string
120120+ module?: string
121121+ workspaces?: string[]
122122+}
···11-import { basename, dirname, relative, resolve } from 'path'
22-33-import { bold } from 'chalk'
44-import consola, { Consola } from 'consola'
55-import execa, { Options } from 'execa'
66-import {
77- copy,
88- existsSync,
99- readJSONSync,
1010- writeFile,
1111- mkdirp,
1212- chmod,
1313-} from 'fs-extra'
1414-import { RollupOptions } from 'rollup'
1515-import sortPackageJson from 'sort-package-json'
1616-1717-import type {
1818- BuildConfigOptions,
1919- PackageCommands,
2020- PackageHookOptions,
2121- PackageHooks,
2222-} from '../build'
2323-import {
2424- glob,
2525- runInParallel,
2626- sortObjectKeys,
2727- tryJSON,
2828- tryRequire,
2929- RequireProperties,
3030-} from '../utils'
3131-import type { PackageJson } from './types'
3232-3333-interface DefaultPackageOptions {
3434- rootDir: string
3535- build: boolean
3636- suffix: string
3737- hooks: PackageHooks
3838- commands: PackageCommands
3939- linkedDependencies?: string[]
4040- pkg?: PackageJson
4141- rollup?: BuildConfigOptions & RollupOptions
4242- sortDependencies?: boolean
4343-}
4444-4545-export type PackageOptions = Partial<DefaultPackageOptions>
4646-4747-export interface BuildOptions {
4848- dev?: boolean
4949- watch?: boolean
5050-}
5151-5252-// 'package.js' is legacy and will go
5353-const configPaths = [
5454- 'siroc.config.ts',
5555- 'siroc.config.js',
5656- 'siroc.config.json',
5757- 'package.js',
5858-]
5959-6060-const DEFAULTS: DefaultPackageOptions = {
6161- rootDir: process.cwd(),
6262- build: true,
6363- suffix: process.env.PACKAGE_SUFFIX ? `-${process.env.PACKAGE_SUFFIX}` : '',
6464- hooks: {},
6565- commands: {},
6666-}
6767-6868-export class Package {
6969- options: DefaultPackageOptions
7070- logger: Consola
7171- pkg: RequireProperties<PackageJson, 'name' | 'version'>
7272-7373- constructor(options: PackageOptions = {}) {
7474- this.options = Object.assign({}, DEFAULTS, options)
7575-7676- // Basic logger
7777- this.logger = consola
7878-7979- this.pkg = this.loadPackageJSON()
8080-8181- // Use tagged logger
8282- this.logger = consola.withTag(this.pkg.name)
8383-8484- this.loadConfig()
8585- }
8686-8787- loadPackageJSON(): this['pkg'] {
8888- try {
8989- return readJSONSync(this.resolvePath('package.json'))
9090- } catch {
9191- if (this.options.rootDir === '/') {
9292- this.logger.error(
9393- `Could not locate a ${bold('package.json')} in ${bold(
9494- DEFAULTS.rootDir
9595- )} or its parent directories.`
9696- )
9797- throw new Error(
9898- `Could not locate a package.json in ${DEFAULTS.rootDir} or its parent directories.`
9999- )
100100- }
101101- this.options.rootDir = this.resolvePath('..')
102102- return this.loadPackageJSON()
103103- }
104104- }
105105-106106- /**
107107- * Resolve path relative to this package
108108- */
109109- resolvePath(...pathSegments: string[]) {
110110- return resolve(this.options.rootDir, ...pathSegments)
111111- }
112112-113113- /**
114114- * Load options from the `siroc.config.js` in the package directory
115115- */
116116- loadConfig() {
117117- configPaths.some(path => {
118118- const configPath = this.resolvePath(path)
119119-120120- const config = tryRequire<PackageOptions>(configPath)
121121- if (!config) return false
122122-123123- Object.assign(this.options, config)
124124- return true
125125- })
126126- }
127127-128128- /**
129129- * Call hooks defined in config file
130130- */
131131- async callHook<H extends keyof PackageHookOptions>(
132132- name: H,
133133- options: PackageHookOptions[H]
134134- ) {
135135- const fns = this.options.hooks[name]
136136-137137- if (!fns) return
138138-139139- const fnArray = Array.isArray(fns) ? fns : [fns]
140140- try {
141141- await runInParallel(fnArray, async fn => fn(this, options))
142142- } catch (e) {
143143- this.logger.error(`Couldn't run hook for ${this.pkg.name}.`)
144144- }
145145- }
146146-147147- /**
148148- * Return a new package in a directory relative to the current package
149149- */
150150- load(relativePath: string, opts?: PackageOptions) {
151151- return new Package(
152152- Object.assign(
153153- {
154154- rootDir: this.resolvePath(relativePath),
155155- },
156156- opts
157157- )
158158- )
159159- }
160160-161161- /**
162162- * Write updated `package.json`
163163- */
164164- async writePackage() {
165165- const pkgPath = this.resolvePath('package.json')
166166- this.logger.debug('Writing', pkgPath)
167167- await writeFile(pkgPath, JSON.stringify(this.pkg, null, 2) + '\n')
168168- }
169169-170170- /**
171171- * A version string unique to the current git commit and date
172172- */
173173- get version() {
174174- const date = Math.round(Date.now() / (1000 * 60))
175175- const gitCommit = this.shortCommit
176176- const baseVersion = this.pkg.version.split('-')[0]
177177- return `${baseVersion}-${date}.${gitCommit}`
178178- }
179179-180180- /**
181181- * Add suffix to all dependencies and set new version
182182- */
183183- suffixAndVersion() {
184184- this.logger.info(`Adding suffix ${this.options.suffix}`)
185185-186186- const oldPkgName = this.pkg.name
187187-188188- // Add suffix to the package name
189189- if (!oldPkgName.includes(this.options.suffix)) {
190190- this.pkg.name += this.options.suffix
191191- }
192192-193193- // Apply suffix to all linkedDependencies
194194- if (this.pkg.dependencies) {
195195- for (const oldName of this.options.linkedDependencies || []) {
196196- const name = oldName + this.options.suffix
197197- const version =
198198- this.pkg.dependencies[oldName] || this.pkg.dependencies[name]
199199-200200- delete this.pkg.dependencies[oldName]
201201- this.pkg.dependencies[name] = version
202202- }
203203- }
204204-205205- if (typeof this.pkg.bin === 'string') {
206206- const { bin } = this.pkg
207207- this.pkg.bin = {
208208- [oldPkgName]: bin,
209209- [this.pkg.name]: bin,
210210- }
211211- }
212212-213213- this.pkg.version = this.version
214214- }
215215-216216- /**
217217- * Synchronise version across all packages in monorepo
218218- */
219219- syncLinkedDependencies() {
220220- // Apply suffix to all linkedDependencies
221221- for (const _name of this.options.linkedDependencies || []) {
222222- const name = _name + (this.options.suffix || '')
223223-224224- // Try to read pkg
225225- const pkg =
226226- tryJSON<PackageJson>(`${name}/package.json`) ||
227227- tryJSON<PackageJson>(`${_name}/package.json`)
228228-229229- // Skip if pkg or dependency not found
230230- if (
231231- !pkg ||
232232- !pkg.version ||
233233- !this.pkg.dependencies ||
234234- !this.pkg.dependencies[name]
235235- ) {
236236- continue
237237- }
238238-239239- // Current version
240240- const currentVersion = this.pkg.dependencies[name]
241241- const caret = currentVersion[0] === '^'
242242-243243- // Sync version
244244- this.pkg.dependencies[name] = caret ? `^${pkg.version}` : pkg.version
245245- }
246246- }
247247-248248- publish(tag = 'latest') {
249249- this.logger.info(
250250- `publishing ${this.pkg.name}@${this.pkg.version} with tag ${tag}`
251251- )
252252- this.exec(`npm publish --tag ${tag}`)
253253- }
254254-255255- /**
256256- * Synchronise fields from another package to this package
257257- */
258258- copyFieldsFrom(source: Package, fields: Array<keyof PackageJson> = []) {
259259- for (const field of fields) {
260260- ;(this.pkg[field] as any) = source.pkg[field] as any
261261- }
262262- }
263263-264264- async setBinaryPermissions() {
265265- await Promise.all(this.binaries.map(([binary]) => chmod(binary, 0o777)))
266266- }
267267-268268- async createBinaryStubs() {
269269- await runInParallel(this.binaries, async ([binary, entrypoint]) => {
270270- if (!entrypoint) return
271271-272272- const outDir = dirname(binary)
273273- if (!existsSync(outDir)) await mkdirp(outDir)
274274- const absPath = entrypoint.replace(/(\.[jt]s)$/, '')
275275- await writeFile(
276276- binary,
277277- `#!/usr/bin/env node\nconst jiti = require('jiti')()\nmodule.exports = jiti('${absPath}')`
278278- )
279279- await this.setBinaryPermissions()
280280- })
281281- }
282282-283283- async createStub(path: string | undefined) {
284284- if (!path || !this.entrypoint || !this.options.build) return
285285-286286- const outFile = this.resolvePath(path)
287287- const outDir = dirname(outFile)
288288- if (!existsSync(outDir)) await mkdirp(outDir)
289289- const relativeEntrypoint = relative(outDir, this.entrypoint).replace(
290290- /(\.[jt]s)$/,
291291- ''
292292- )
293293- await writeFile(outFile, `export * from './${relativeEntrypoint}'`)
294294- }
295295-296296- async createStubs() {
297297- return Promise.all([
298298- this.createBinaryStubs(),
299299- this.createStub(this.pkg.main),
300300- this.createStub(this.pkg.module),
301301- this.createStub(this.pkg.types),
302302- ])
303303- }
304304-305305- /**
306306- * Copy files from another package's directory
307307- */
308308- async copyFilesFrom(source: Package, files: string[]) {
309309- for (const file of files || source.pkg.files || []) {
310310- const src = resolve(source.options.rootDir, file)
311311- const dst = resolve(this.options.rootDir, file)
312312- await copy(src, dst)
313313- }
314314- }
315315-316316- /**
317317- * Sort `package.json` and sort package dependencies alphabetically (if enabled in options)
318318- */
319319- autoFix() {
320320- this.pkg = sortPackageJson(this.pkg)
321321- if (this.options.sortDependencies) this.sortDependencies()
322322- }
323323-324324- /**
325325- * Sort package depndencies alphabetically by object key
326326- */
327327- sortDependencies() {
328328- if (this.pkg.dependencies) {
329329- this.pkg.dependencies = sortObjectKeys(this.pkg.dependencies)
330330- }
331331-332332- if (this.pkg.devDependencies) {
333333- this.pkg.devDependencies = sortObjectKeys(this.pkg.devDependencies)
334334- }
335335- }
336336-337337- execInteractive(command: string) {
338338- const options: Options = {
339339- cwd: this.options.rootDir,
340340- env: process.env,
341341- shell: true,
342342- stdio: 'inherit',
343343- }
344344- return execa.command(command, options)
345345- }
346346-347347- /**
348348- * Execute command in the package root directory
349349- */
350350- exec(command: string, { silent = false } = {}) {
351351- const options = {
352352- cwd: this.options.rootDir,
353353- env: process.env,
354354- }
355355-356356- const r = execa.commandSync(command, options)
357357-358358- if (!silent) {
359359- if (r.failed) {
360360- this.logger.error(command, r.stderr.trim())
361361- } else {
362362- this.logger.success(command, r.stdout.trim())
363363- }
364364- }
365365-366366- return {
367367- signal: r.signal,
368368- stdout: String(r.stdout).trim(),
369369- stderr: String(r.stderr).trim(),
370370- }
371371- }
372372-373373- private resolveEntrypoint(path = this.pkg.main) {
374374- if (!path) return undefined
375375-376376- const basefile = basename(path).split('.').slice(0, -1).join()
377377- let input!: string
378378- const filenames = [basefile, `${basefile}/index`, 'index']
379379- .map(name => [`${name}.ts`, `${name}.js`])
380380- .reduce((names, arr) => {
381381- arr.forEach(name => names.push(name))
382382- return names
383383- }, [] as string[])
384384- filenames.some(filename => {
385385- input = this.resolvePath('src', filename)
386386- return existsSync(input)
387387- })
388388- return input
389389- }
390390-391391- parsePerson(person: string) {
392392- /* eslint-disable no-unused-vars */
393393- /* eslint-disable @typescript-eslint/no-unused-vars */
394394- const [_matchedName, name] = person.match(/(^[^<(]*[^ <(])/) || []
395395- const [_matchedEmail, email] = person.match(/<(.*)>/) || []
396396- const [_matchedUrl, url] = person.match(/\((.*)\)/) || []
397397- /* eslint-enable */
398398- return { name, email, url }
399399- }
400400-401401- get contributors() {
402402- if (!this.pkg.contributors) return []
403403-404404- return this.pkg.contributors.map(person => {
405405- if (typeof person === 'string') return this.parsePerson(person)
406406- return person
407407- })
408408- }
409409-410410- /**
411411- * The main package entrypoint (source)
412412- */
413413- get entrypoint() {
414414- return this.resolveEntrypoint()
415415- }
416416-417417- /**
418418- * An array of built package binary paths and their entrypoints
419419- * @returns an array of tuples of the binary and its corresponding entrypoint
420420- */
421421- get binaries() {
422422- type Binary = string
423423- type Entrypoint = string | undefined
424424- const { bin } = this.pkg
425425- const files = !bin
426426- ? []
427427- : typeof bin === 'string'
428428- ? [bin]
429429- : Object.values(bin)
430430-431431- return Array.from(
432432- new Set<[Binary, Entrypoint]>(
433433- files.map(file => [
434434- this.resolvePath(file),
435435- this.resolveEntrypoint(file),
436436- ])
437437- )
438438- )
439439- }
440440-441441- /**
442442- * Return the child packages of this workspace (or, if there are no workspaces specified, just this package)
443443- * @param packageNames If package names are provided, these will serve to limit the packages that are returned
444444- */
445445- async getWorkspacePackages(packageNames?: string[]) {
446446- const dirs = new Set<string>()
447447-448448- await runInParallel(this.pkg.workspaces || ['.'], async workspace => {
449449- ;(await glob(workspace)).forEach(dir => dirs.add(dir))
450450- })
451451-452452- const packages = await runInParallel(dirs, dir => {
453453- if (!existsSync(this.resolvePath(dir, 'package.json'))) {
454454- throw new Error('Not a package directory.')
455455- }
456456- const pkg = new Package({
457457- ...this.options,
458458- rootDir: this.resolvePath(dir),
459459- })
460460- if (packageNames && !packageNames.includes(pkg.pkg.name)) {
461461- throw new Error('Not a selected package.')
462462- }
463463- return pkg
464464- })
465465-466466- return packages
467467- .filter(pkg => pkg.status === 'fulfilled')
468468- .map(pkg => (pkg as PromiseFulfilledResult<Package>).value)
469469- }
470470-471471- get shortCommit() {
472472- const { stdout } = this.exec('git rev-parse --short HEAD', {
473473- silent: true,
474474- })
475475- return stdout
476476- }
477477-478478- get branch() {
479479- const { stdout } = this.exec('git rev-parse --abbrev-ref HEAD', {
480480- silent: true,
481481- })
482482- return stdout
483483- }
484484-485485- get lastGitTag() {
486486- const { stdout } = this.exec('git --no-pager tag -l --sort=taggerdate', {
487487- silent: true,
488488- })
489489- const r = stdout.split('\n')
490490- return r[r.length - 1]
491491- }
492492-}
493493-494494-export * from './types'
-122
packages/core/src/package/types.ts
···11-/**
22- * 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.
33- */
44-export type PackageJsonPerson =
55- | string
66- | {
77- name: string
88- email?: string
99- url?: string
1010- }
1111-1212-export interface Repository {
1313- type: string
1414- url: string
1515- /**
1616- * 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:
1717- */
1818- directory?: string
1919-}
2020-export interface PackageJson {
2121- /**
2222- * The name is what your thing is called.
2323- * Some rules:
2424-2525- - The name must be less than or equal to 214 characters. This includes the scope for scoped packages.
2626- - The name can’t start with a dot or an underscore.
2727- - New packages must not have uppercase letters in the name.
2828- - 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.
2929-3030- */
3131- name?: string
3232- /**
3333- * Version must be parseable by `node-semver`, which is bundled with npm as a dependency. (`npm install semver` to use it yourself.)
3434- */
3535- version?: string
3636- /**
3737- * Put a description in it. It’s a string. This helps people discover your package, as it’s listed in `npm search`.
3838- */
3939- description?: string
4040- /**
4141- * Put keywords in it. It’s an array of strings. This helps people discover your package as it’s listed in `npm search`.
4242- */
4343- keywords?: string[]
4444- /**
4545- * The url to the project homepage.
4646- */
4747- homepage?: string
4848-4949- /**
5050- * 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.
5151- */
5252- bugs?:
5353- | string
5454- | {
5555- url?: string
5656- email?: string
5757- }
5858- /**
5959- * 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.
6060- */
6161- licence?: string
6262- /**
6363- * 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.
6464- * For GitHub, GitHub gist, Bitbucket, or GitLab repositories you can use the same shortcut syntax you use for npm install:
6565- */
6666- repository?: string | Repository
6767- /**
6868- * If you set `"private": true` in your package.json, then npm will refuse to publish it.
6969- */
7070- private?: boolean
7171- /**
7272- * The “author” is one person.
7373- */
7474- author?: PackageJsonPerson
7575- /**
7676- * “contributors” is an array of people.
7777- */
7878- contributors?: PackageJsonPerson[]
7979- /**
8080- * 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.
8181- */
8282- files?: string[]
8383- /**
8484- * 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.
8585- * This should be a module ID relative to the root of your package folder.
8686- * For most modules, it makes the most sense to have a main script and often not much else.
8787- */
8888- main?: string
8989- /**
9090- * 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)
9191- */
9292- browser?: string
9393- /**
9494- * 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.
9595- */
9696- bin?: string | Record<string, string>
9797- /**
9898- * Specify either a single file or an array of filenames to put in place for the `man` program to find.
9999- */
100100- man?: string | string[]
101101- /**
102102- * 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.
103103- */
104104- dependencies?: Record<string, string>
105105- /**
106106- * 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.
107107- * In this case, it’s best to map these additional items in a `devDependencies` object.
108108- */
109109- devDependencies?: Record<string, string>
110110- /**
111111- * 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.
112112- */
113113- optionalDependencies?: Record<string, string>
114114- /**
115115- * 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.
116116- */
117117- peerDependencies?: Record<string, string>
118118-119119- types?: string
120120- module?: string
121121- workspaces?: string[]
122122-}