mirror of
https://github.com/thomvaill/log4brains.git
synced 2022-05-07 18:36:09 +03:00
feat: distribute as a global NPM package
BREAKING CHANGE: installation procedure is now completely modified
This commit is contained in:
committed by
Thomas Vaillant
parent
079ee579db
commit
9551b689ff
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -104,7 +104,7 @@ jobs:
|
||||
# TODO: we should separate tests that require built packages of the others, to get a quicker feedback
|
||||
# Once it's done, we should add "yarn test" in each package's preVersion script
|
||||
- name: build
|
||||
run: yarn build && yarn links
|
||||
run: yarn build && yarn link-cli
|
||||
|
||||
- name: test
|
||||
run: yarn test
|
||||
@@ -156,12 +156,12 @@ jobs:
|
||||
# End of yarn setup
|
||||
|
||||
- name: build
|
||||
run: yarn build
|
||||
run: yarn build && yarn link-cli
|
||||
|
||||
- name: build self knowledge base
|
||||
env:
|
||||
HIDE_LOG4BRAINS_VERSION: "1" # TODO: use lerna to bump the version temporarily here so we don't have to hide it
|
||||
run: yarn log4brains-build --basePath /${GITHUB_REPOSITORY#*/}/adr
|
||||
run: log4brains build --basePath /${GITHUB_REPOSITORY#*/}/adr
|
||||
|
||||
- name: publish self knowledge base
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -119,7 +119,7 @@ jobs:
|
||||
# TODO: we should separate tests that require built packages of the others, to get a quicker feedback
|
||||
# Once it's done, we should add "yarn test" in each package's preVersion script
|
||||
- name: build
|
||||
run: yarn build && yarn links
|
||||
run: yarn build && yarn link-cli
|
||||
|
||||
- name: test changed packages
|
||||
if: ${{ steps.yarn-lock.outcome == 'success' }}
|
||||
|
||||
@@ -15,36 +15,35 @@ Thank you so much! :clap:
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm uninstall -g log4brains
|
||||
yarn install
|
||||
yarn links
|
||||
yarn link-cli
|
||||
yarn dev
|
||||
|
||||
# You can now develop
|
||||
# `yarn dev` re-builds the changed packages live
|
||||
|
||||
# You can test the different packages directly on the Log4brains project
|
||||
# All its scripts are linked to your local dev version
|
||||
yarn adr new
|
||||
yarn log4brains-build
|
||||
yarn serve # serves the build output (`.log4brains/out`) locally
|
||||
log4brains adr new
|
||||
log4brains preview
|
||||
log4brains build
|
||||
yarn serve-log4brains # serves the build output (`.log4brains/out`) locally
|
||||
|
||||
# For the Next.js app, you can enable the Fast Refresh feature just by setting NODE_ENV to `development`
|
||||
NODE_ENV=development yarn log4brains-preview
|
||||
# Or you can run this more convenient command on the root project:
|
||||
yarn log4brains-preview:dev
|
||||
NODE_ENV=development log4brains preview
|
||||
# Or if you want to debug only the Next.js app without the Log4brains custom part, you can run:
|
||||
cd packages/web && yarn next dev # (in this case `yarn dev` is not needed before running this command)
|
||||
|
||||
# To work on the UI, you probably would like to use the Storybook instead:
|
||||
cd packages/web && yarn storybook
|
||||
|
||||
# You can also test the different packages on an empty project
|
||||
# `npx init-log4brains` is linked to your local dev version and will install
|
||||
# the dev version of each Log4brains package if you set NODE_ENV to `development`
|
||||
cd $(mktemp -d -t l4b-test-XXXX) && npm init --yes && npm install
|
||||
NODE_ENV=development npx init-log4brains
|
||||
# To test the init CLI, create an empty project
|
||||
cd $(mktemp -d -t l4b-test-XXXX)
|
||||
log4brains init
|
||||
```
|
||||
|
||||
When you are done, run `yarn unlink-cli && npm install -g log4brains` to use the official version again.
|
||||
|
||||
## Checks to run before pushing
|
||||
|
||||
```bash
|
||||
|
||||
121
README.md
121
README.md
@@ -25,7 +25,15 @@
|
||||
</p>
|
||||
|
||||
Log4brains is a docs-as-code knowledge base for your development and infrastructure projects.
|
||||
It enables you to write and manage [Architecture Decision Records](https://adr.github.io/) (ADR) right from your IDE, and to publish them automatically as a static website.
|
||||
It enables you to log [Architecture Decision Records](https://adr.github.io/) (ADR) right from your IDE and to publish them automatically as a static website.
|
||||
|
||||
By logging your decisions with Log4brains, you will be able to:
|
||||
|
||||
- Understand past technical decisions and their context
|
||||
- Take new decisions with confidence
|
||||
- Always have an up-to-date technical documentation and training material available
|
||||
- Onboard new developers quicker
|
||||
- Set up a collaborative decision process thanks to pull requests
|
||||
|
||||
<details>
|
||||
<summary>Features</summary>
|
||||
@@ -66,66 +74,49 @@ It enables you to write and manage [Architecture Decision Records](https://adr.g
|
||||
|
||||
## Table of contents <!-- omit in toc -->
|
||||
|
||||
- [📣 Beta version: your feedback is welcome!](#-beta-version-your-feedback-is-welcome)
|
||||
- [🚀 Getting started](#-getting-started)
|
||||
- [🤔 What is an ADR and why should you use them](#-what-is-an-adr-and-why-should-you-use-them)
|
||||
- [💡 Why Log4brains](#-why-log4brains)
|
||||
- [📨 CI/CD configuration examples](#-cicd-configuration-examples)
|
||||
- [❓ FAQ](#-faq)
|
||||
- [What are the prerequisites?](#what-are-the-prerequisites)
|
||||
- [Is Log4brains only for JS projects?](#is-log4brains-only-for-js-projects)
|
||||
- [What about multi-package projects?](#what-about-multi-package-projects)
|
||||
- [What about non-JS projects?](#what-about-non-js-projects)
|
||||
- [How to configure `.log4brains.yml`?](#how-to-configure-log4brainsyml)
|
||||
- [📣 Beta version: your feedback is welcome!](#-beta-version-your-feedback-is-welcome)
|
||||
- [Contributing](#contributing)
|
||||
- [Acknowledgments](#acknowledgments)
|
||||
- [License](#license)
|
||||
|
||||
## 📣 Beta version: your feedback is welcome!
|
||||
|
||||
At this stage, Log4brains is just a few months old and was designed only based on my needs and my past experiences with ADRs.
|
||||
But I am convinced that this project can benefit a lot of teams.
|
||||
This is why it would be precious for me to get your feedback on this beta version in order to improve it.
|
||||
|
||||
To do so, you are very welcome to [create a new feedback in the Discussions](https://github.com/thomvaill/log4brains/discussions/new?category=Feedback) or to reach out to me at <thomvaill@bluebricks.dev>. Thanks a lot 🙏
|
||||
|
||||
Disclaimer: during the beta, some releases can introduce breaking changes without any warning. Therefore, we recommend you to pin exact versions of Log4brains in your `package.json` to be safe.
|
||||
|
||||
## 🚀 Getting started
|
||||
|
||||
According to the Log4brains philosophy, you should store your Architecture Decision Records (ADR) the closest to your code, which means ideally inside your project's git repository, for example in `<your project>/docs/adr`. In the case of a JS project, we recommend installing Log4brains as a dev dependency. To do so, run our interactive setup CLI inside your project's root directory:
|
||||
We recommend to store your Architecture Decision Records (ADR) next to the source code of your project,
|
||||
in the same git repository, to keep them in sync.
|
||||
|
||||
To get started, run these commands inside your git repository's root folder:
|
||||
|
||||
```bash
|
||||
npx init-log4brains
|
||||
npm install -g log4brains
|
||||
log4brains init
|
||||
```
|
||||
|
||||
... it will ask you several questions to get your knowledge base installed and configured properly. Click [here](#what-about-non-js-projects) for non-JS projects.
|
||||
|
||||
Of course, you can still choose to store your ADRs in a dedicated repository. In this case, just run `npm init --yes` before running `npx init-log4brains`.
|
||||
|
||||
Then, you can start the web UI in local preview mode:
|
||||
It will ask you several questions to get Log4brains properly configured for your project.
|
||||
Then, you can start the web UI to preview your architecture knowledge base locally:
|
||||
|
||||
```bash
|
||||
npm run log4brains-preview
|
||||
|
||||
# OR
|
||||
|
||||
yarn log4brains-preview
|
||||
log4brains preview
|
||||
```
|
||||
|
||||
In this mode, the Hot Reload feature is enabled: any change
|
||||
you make to a markdown file is applied live in the UI.
|
||||
you make to a markdown file from your IDE is applied live in the UI.
|
||||
|
||||
You can use this command to easily create a new ADR interactively:
|
||||
To create a new ADR from the template, run this command:
|
||||
|
||||
```bash
|
||||
npm run adr -- new
|
||||
|
||||
# OR
|
||||
|
||||
yarn adr new
|
||||
log4brains adr new
|
||||
```
|
||||
|
||||
Just add the `--help` option for more information on this command.
|
||||
Get all the available commands and options by running `log4brains --help`.
|
||||
|
||||
Finally, do not forget to [set up your CI/CD pipeline](#-cicd-configuration-examples) to automatically publish your knowledge base on a static website service like GitHub/GitLab Pages or S3.
|
||||
|
||||
@@ -203,17 +194,10 @@ jobs:
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "14"
|
||||
# NPM:
|
||||
# (unfortunately, we cannot use `npm ci` for now because of this bug: https://github.com/npm/cli/issues/558)
|
||||
- name: Install and Build Log4brains (NPM)
|
||||
- name: Install and Build Log4brains
|
||||
run: |
|
||||
npm install
|
||||
npm run log4brains-build -- --basePath /${GITHUB_REPOSITORY#*/}/log4brains
|
||||
# Yarn:
|
||||
# - name: Install and Build Log4brains (Yarn)
|
||||
# run: |
|
||||
# yarn install --frozen-lockfile
|
||||
# yarn log4brains-build --basePath /${GITHUB_REPOSITORY#*/}/log4brains
|
||||
npm install -g log4brains
|
||||
log4brains build --basePath /${GITHUB_REPOSITORY#*/}/log4brains
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@3.7.1
|
||||
with:
|
||||
@@ -260,12 +244,8 @@ pages:
|
||||
GIT_DEPTH: 0 # required by Log4brains to work correctly (needs the whole Git history)
|
||||
script:
|
||||
- mkdir -p public
|
||||
# NPM:
|
||||
- npm install # unfortunately we cannot use `npm ci` for now because of this bug: https://github.com/npm/cli/issues/558
|
||||
- npm run log4brains-build -- --basePath /$CI_PROJECT_NAME/log4brains --out public/log4brains
|
||||
# Yarn:
|
||||
# - yarn install --frozen-lockfile
|
||||
# - yarn log4brains-build --basePath /$CI_PROJECT_NAME/log4brains --out public/log4brains
|
||||
- npm install -g log4brains
|
||||
- log4brains build --basePath /$CI_PROJECT_NAME/log4brains --out public/log4brains
|
||||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
@@ -310,8 +290,8 @@ Then, configure your CI to run these commands:
|
||||
|
||||
- Install Node and the AWS CLI
|
||||
- Checkout your Git repository **with the full history**. Otherwise, Log4brains won't work correctly (see previous examples)
|
||||
- `npm install` or `yarn install --frozen-lockfile` to install the dev dependencies (unfortunately we cannot use `npm ci` for now because of this [bug](https://github.com/npm/cli/issues/558))
|
||||
- `npm run log4brains-build` or `yarn log4brains-build`
|
||||
- `npm install -g log4brains`
|
||||
- `log4brains build`
|
||||
- `aws s3 sync .log4brains/out s3://<YOUR BUCKET> --delete`
|
||||
|
||||
Your knowledge base will be available on `http://<YOUR BUCKET>.s3-website-<YOUR REGION>.amazonaws.com/`.
|
||||
@@ -337,11 +317,16 @@ Finally, you can add the ADR badge to your `README.md`!
|
||||
|
||||
- Node.js >= 10.23
|
||||
- NPM or Yarn
|
||||
- Your project versioned in Git ([not necessarily a JS project!](#what-about-non-js-projects))
|
||||
- Git
|
||||
|
||||
### Is Log4brains only for JS projects?
|
||||
|
||||
Of course not! Log4brains is developed with TypeScript and use NPM as a package manager.
|
||||
You need Node and NPM to be installed globally to run Log4brains, but it is designed to work for all kind of projects.
|
||||
|
||||
### What about multi-package projects?
|
||||
|
||||
Log4brains supports both mono and multi packages projects. The `npx init-log4brains` command will prompt you regarding this.
|
||||
Log4brains supports both mono and multi packages projects. The `log4brains init` command will prompt you regarding this.
|
||||
|
||||
In the case of a multi-package project, you have two options:
|
||||
|
||||
@@ -460,27 +445,9 @@ repo2
|
||||
</p>
|
||||
</details>
|
||||
|
||||
### What about non-JS projects?
|
||||
|
||||
Even if Log4brains is developed with TypeScript and is part of the NPM ecosystem, it can be used for any kind of project, in any language.
|
||||
|
||||
For projects that do not have a `package.json` file, you have to install Log4brains globally:
|
||||
|
||||
```bash
|
||||
npm install -g @log4brains/cli @log4brains/web
|
||||
```
|
||||
|
||||
Create a `.log4brains.yml` file at the root of your project and [configure it](#how-to-configure-log4brainsyml).
|
||||
|
||||
You can now use these global commands inside your project:
|
||||
|
||||
- Create a new ADR: `log4brains adr new`
|
||||
- Start the local web UI: `log4brains-web preview`
|
||||
- Build the static version: `log4brains-web build`
|
||||
|
||||
### How to configure `.log4brains.yml`?
|
||||
|
||||
This file is usually automatically created when you run `npx init-log4brains` (cf [getting started](#-getting-started)), but you may need to configure it manually.
|
||||
This file is automatically created when you run `log4brains init` (cf [getting started](#-getting-started)), but you may need to configure it manually.
|
||||
|
||||
Here is an example with just the required fields:
|
||||
|
||||
@@ -514,6 +481,16 @@ project:
|
||||
viewFileUriPattern: /blob/%branch/%path # Only required for `generic` providers
|
||||
```
|
||||
|
||||
## 📣 Beta version: your feedback is welcome!
|
||||
|
||||
At this stage, Log4brains is just a few months old and was designed only based on my needs and my past experiences with ADRs.
|
||||
But I am convinced that this project can benefit a lot of teams.
|
||||
This is why it would be precious for me to get your feedback on this beta version in order to improve it.
|
||||
|
||||
To do so, you are very welcome to [create a new feedback in the Discussions](https://github.com/thomvaill/log4brains/discussions/new?category=Feedback) or to reach out to me at <thomvaill@bluebricks.dev>. Thanks a lot 🙏
|
||||
|
||||
Disclaimer: during the beta, some releases can introduce breaking changes without any warning. Therefore, we recommend you to pin exact versions of Log4brains in your `package.json` to be safe.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull Requests are more than welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for more details. You can also [create a new issue](https://github.com/thomvaill/log4brains/issues/new/choose) or [give your feedback](https://github.com/thomvaill/log4brains/discussions/new?category=Feedback).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Distribute Log4brains as a global NPM package
|
||||
|
||||
- Status: accepted
|
||||
- Date: 2021-01-13
|
||||
|
||||
Technical Story: <https://github.com/thomvaill/log4brains/issues/14#issuecomment-750154907>
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Log4brains (`v1.0.0-beta.4`) installation procedure is currently optimized for JS projects and looks like this:
|
||||
|
||||
- Run `npx init-log4brains`
|
||||
- Which installs locally `@log4brains/cli` and `@log4brains/web`
|
||||
- And creates custom entries in `package.json`'s `scripts` section:
|
||||
- `"log4brains-preview": "log4brains-web preview"`
|
||||
- `"log4brains-build": "log4brains-web build"`
|
||||
- `"adr": "log4brains adr"`
|
||||
|
||||
For non-JS projects, you have to install manually the packages and the `npx init-log4brains` script does not work.
|
||||
|
||||
Since Log4brains is intented for all projects, not especially JS ones, we have to make the installation procedure simpler and language-agnostic.
|
||||
|
||||
## Decision Drivers <!-- optional -->
|
||||
|
||||
- Simplicity of the installation procedure
|
||||
- Language agnostic
|
||||
- Initialization script works on any kind of project
|
||||
- Faster "getting started"
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
The new installation procedure is now language agnostic and will be the following:
|
||||
|
||||
```bash
|
||||
npm install -g log4brains
|
||||
log4brains init
|
||||
```
|
||||
|
||||
Log4brains will be distributed as a global NPM package named `log4brains`, which provides a global `log4brains` command.
|
||||
|
||||
- This global package will require the existing `@log4brains/cli` and `@log4brains/web` packages
|
||||
- `init-log4brains` will be renamed to `@log4brains/init` and required as a dependency
|
||||
|
||||
### Consequences
|
||||
|
||||
For a JS project, it is now impossible to pin Log4brains to a specific version.
|
||||
We may implement a [xojs/xo](https://github.com/xojs/xo)-like behavior later: "the CLI will use your local install of XO when available, even when run globally."
|
||||
|
||||
## Links
|
||||
|
||||
- [Related GitHub issue](https://github.com/thomvaill/log4brains/issues/14#issuecomment-750154907)
|
||||
@@ -8,12 +8,16 @@ Please use this link to browse them.
|
||||
|
||||
## Development
|
||||
|
||||
If not already done, install Log4brains:
|
||||
|
||||
```bash
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
To preview the knowledge base locally, run:
|
||||
|
||||
```bash
|
||||
npm run log4brains-preview
|
||||
# OR
|
||||
yarn log4brains-preview
|
||||
log4brains preview
|
||||
```
|
||||
|
||||
In preview mode, the Hot Reload feature is enabled: any change you make to a markdown file is applied live in the UI.
|
||||
@@ -21,9 +25,7 @@ In preview mode, the Hot Reload feature is enabled: any change you make to a mar
|
||||
To create a new ADR interactively, run:
|
||||
|
||||
```bash
|
||||
npm run adr new
|
||||
# OR
|
||||
yarn adr new
|
||||
log4brains adr new
|
||||
```
|
||||
|
||||
## More information
|
||||
|
||||
@@ -10,10 +10,6 @@ const fsP = fs.promises;
|
||||
|
||||
process.env.NODE_ENV = "test";
|
||||
|
||||
const initBin = path.resolve(
|
||||
path.join(__dirname, "../packages/init/dist/log4brains-init")
|
||||
);
|
||||
|
||||
// Inspired by Next.js's test/integration/create-next-app/index.test.js. Thank you!
|
||||
async function usingTempDir(fn) {
|
||||
const folder = await fsP.mkdtemp(path.join(os.tmpdir(), "log4brains-e2e-"));
|
||||
@@ -37,21 +33,11 @@ async function run(file, arguments, cwd) {
|
||||
|
||||
(async () => {
|
||||
await usingTempDir(async (cwd) => {
|
||||
await run("npm", ["init", "--yes"], cwd);
|
||||
await run("npm", ["install"], cwd);
|
||||
await run(initBin, ["--defaults"], cwd);
|
||||
await run("log4brains", ["init", "--defaults"], cwd);
|
||||
|
||||
await run(
|
||||
"npm",
|
||||
["run", "adr", "--", "new", "--quiet", '"E2E test ADR"'],
|
||||
cwd
|
||||
);
|
||||
await run("log4brains", ["adr", "new", "--quiet", '"E2E test ADR"'], cwd);
|
||||
|
||||
const adrListRes = await run(
|
||||
"npm",
|
||||
["run", "adr", "--", "list", "--raw"],
|
||||
cwd
|
||||
);
|
||||
const adrListRes = await run("log4brains", ["adr", "list", "--raw"], cwd);
|
||||
expect(adrListRes.stdout).to.contain(
|
||||
"use-log4brains-to-manage-the-adrs",
|
||||
"Log4brains ADR was not created by init"
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
"format": "prettier-eslint \"$PWD/**/{.,}*.{js,jsx,ts,tsx,json,md}\" --list-different",
|
||||
"format:fix": "yarn format --write",
|
||||
"typedoc": "lerna run typedoc",
|
||||
"adr": "./packages/cli/dist/log4brains adr",
|
||||
"log4brains-preview": "./packages/web/dist/bin/log4brains-web preview",
|
||||
"log4brains-preview:dev": "cross-env NODE_ENV=development yarn log4brains-preview",
|
||||
"log4brains-build": "./packages/web/dist/bin/log4brains-web build",
|
||||
"serve": "serve .log4brains/out",
|
||||
"links": "lerna run --stream link",
|
||||
"serve-log4brains": "serve .log4brains/out",
|
||||
"link-cli": "lerna run --stream link",
|
||||
"unlink-cli": "lerna run --stream unlink",
|
||||
"e2e": "node e2e-tests/e2e-launcher.js",
|
||||
"lerna": "lerna"
|
||||
},
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# @log4brains/cli-common
|
||||
|
||||
This package provides common features for all [Log4brains](https://github.com/thomvaill/log4brains) CLI-based packages.
|
||||
It is not meant to be used directly in your project.
|
||||
It is not meant to be used directly.
|
||||
|
||||
## Installation
|
||||
Install [the main log4brains package](https://www.npmjs.com/package/log4brains) instead:
|
||||
|
||||
This package is not meant to be installed directly in your project. This is a common dependency of [@log4brains/cli](https://www.npmjs.com/package/@log4brains/cli), [@log4brains/init](https://www.npmjs.com/package/@log4brains/init) and [@log4brains/web](https://www.npmjs.com/package/@log4brains/web), which is installed automatically.
|
||||
```bash
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md)
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
yarn dev:test
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md)
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
"clean": "rimraf ./dist",
|
||||
"typescript": "tsc --noEmit",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"prepublishOnly": "yarn build",
|
||||
"link": "npm link && rm -f ./package-lock.json"
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/inquirer": "^7.3.1",
|
||||
|
||||
@@ -172,6 +172,7 @@ export class AppConsole {
|
||||
question: string,
|
||||
defaultValue: boolean
|
||||
): Promise<boolean> {
|
||||
console.log("");
|
||||
const answer = await inquirer.prompt<{ q: boolean }>([
|
||||
{ type: "confirm", name: "q", message: question, default: defaultValue }
|
||||
]);
|
||||
@@ -182,6 +183,7 @@ export class AppConsole {
|
||||
question: string,
|
||||
defaultValue?: string
|
||||
): Promise<string> {
|
||||
console.log("");
|
||||
const answer = await inquirer.prompt<{ q: string }>([
|
||||
{
|
||||
type: "input",
|
||||
@@ -193,11 +195,42 @@ export class AppConsole {
|
||||
return answer.q;
|
||||
}
|
||||
|
||||
async askInputQuestionAndValidate(
|
||||
question: string,
|
||||
validationCb: (answer: string) => boolean | string,
|
||||
defaultValue?: string
|
||||
): Promise<string> {
|
||||
let answer: string;
|
||||
let valid: boolean | string = true;
|
||||
let i = 0;
|
||||
do {
|
||||
if (valid !== true) {
|
||||
console.log(
|
||||
chalk.red(
|
||||
`✗ Please enter a correct value${
|
||||
valid === false ? "" : `: ${valid}`
|
||||
}`
|
||||
)
|
||||
);
|
||||
}
|
||||
if (i >= 10) {
|
||||
throw new Error("Reached maximum number of retries (10)");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
answer = await this.askInputQuestion(question, defaultValue);
|
||||
valid = validationCb(answer);
|
||||
i += 1;
|
||||
} while (valid !== true);
|
||||
return answer;
|
||||
}
|
||||
|
||||
async askListQuestion<V extends string>(
|
||||
question: string,
|
||||
choices: ChoiceDefinition<V>[],
|
||||
defaultValue?: V
|
||||
): Promise<V> {
|
||||
console.log("");
|
||||
const answer = await inquirer.prompt<{ q: V }>([
|
||||
{
|
||||
type: "list",
|
||||
|
||||
5
packages/cli-common/src/Log4brainsConfigNotFound.ts
Normal file
5
packages/cli-common/src/Log4brainsConfigNotFound.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export class Log4brainsConfigNotFound extends Error {
|
||||
constructor() {
|
||||
super("Cannot find .log4brains.yml");
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export * from "./AppConsole";
|
||||
export * from "./ConsoleCapturer";
|
||||
export * from "./FailureExit";
|
||||
export * from "./Log4brainsConfigNotFound";
|
||||
|
||||
@@ -1,47 +1,12 @@
|
||||
# @log4brains/cli
|
||||
|
||||
This package provides the CLI to use the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base in your project.
|
||||
This package provides the CLI features of the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base.
|
||||
It is not meant to be used directly.
|
||||
|
||||
## Installation
|
||||
|
||||
You should use `npx init-log4brains` as described in the [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md), which will install all the required dependencies in your project, including this one, and set up the right scripts in your `package.json`.
|
||||
|
||||
You can also install this package manually via npm or yarn:
|
||||
Install [the main log4brains package](https://www.npmjs.com/package/log4brains) instead:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @log4brains/cli
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn add --dev @log4brains/cli
|
||||
```
|
||||
|
||||
And add this script to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
[...]
|
||||
"scripts": {
|
||||
[...]
|
||||
"adr": "log4brains adr"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
See [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md), or run this command in your project:
|
||||
|
||||
```bash
|
||||
npm run adr -- --help
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn adr --help
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -23,28 +23,22 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"log4brains": "./dist/log4brains"
|
||||
},
|
||||
"source": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "tsc --build tsconfig.build.json && copyfiles -u 1 src/log4brains dist",
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"clean": "rimraf ./dist",
|
||||
"typescript": "tsc --noEmit",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"prepublishOnly": "yarn build",
|
||||
"link": "yarn link"
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@log4brains/cli-common": "^1.0.0-beta.4",
|
||||
"@log4brains/core": "^1.0.0-beta.4",
|
||||
"commander": "^6.1.0",
|
||||
"esm": "^3.2.25",
|
||||
"execa": "^5.0.0",
|
||||
"has-yarn": "^2.1.0",
|
||||
"terminal-link": "^2.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import commander from "commander";
|
||||
import { Log4brains } from "@log4brains/core";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import terminalLink from "terminal-link";
|
||||
import { Log4brains, Log4brainsError } from "@log4brains/core";
|
||||
import {
|
||||
AppConsole,
|
||||
FailureExit,
|
||||
Log4brainsConfigNotFound
|
||||
} from "@log4brains/cli-common";
|
||||
|
||||
import {
|
||||
ListCommand,
|
||||
ListCommandOpts,
|
||||
@@ -8,23 +16,61 @@ import {
|
||||
NewCommandOpts
|
||||
} from "./commands";
|
||||
|
||||
const templateExampleUrl =
|
||||
"https://raw.githubusercontent.com/thomvaill/log4brains/master/packages/init/assets/template.md";
|
||||
|
||||
function findRootFolder(cwd: string): string {
|
||||
if (fs.existsSync(path.join(cwd, ".log4brains.yml"))) {
|
||||
return cwd;
|
||||
}
|
||||
if (path.resolve(cwd) === "/") {
|
||||
throw new Log4brainsConfigNotFound();
|
||||
}
|
||||
return findRootFolder(path.join(cwd, ".."));
|
||||
}
|
||||
|
||||
let l4bInstance: Log4brains;
|
||||
function getL4bInstance(): Log4brains {
|
||||
if (!l4bInstance) {
|
||||
l4bInstance = Log4brains.create(
|
||||
findRootFolder(process.env.LOG4BRAINS_CWD || ".")
|
||||
);
|
||||
}
|
||||
return l4bInstance;
|
||||
}
|
||||
|
||||
function execWithErrorHandler<T>(
|
||||
promise: Promise<T>,
|
||||
appConsole: AppConsole
|
||||
): Promise<T> {
|
||||
promise.catch((err) => {
|
||||
if (
|
||||
err instanceof Log4brainsError &&
|
||||
err.name === "The template.md file does not exist"
|
||||
) {
|
||||
appConsole.error(err);
|
||||
appConsole.printlnErr(
|
||||
`You can use this ${terminalLink(
|
||||
"template",
|
||||
templateExampleUrl
|
||||
)} as an example`
|
||||
);
|
||||
throw new FailureExit();
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
type Deps = {
|
||||
l4bInstance: Log4brains;
|
||||
appConsole: AppConsole;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export function createCli({
|
||||
l4bInstance,
|
||||
appConsole,
|
||||
version
|
||||
}: Deps): commander.Command {
|
||||
export function createCli({ appConsole }: Deps): commander.Command {
|
||||
const program = new commander.Command();
|
||||
program.version(version);
|
||||
|
||||
const adr = program
|
||||
.command("adr")
|
||||
.description("Manage the Architecture Decision Records (ADR)");
|
||||
.description("Group of commands to manage your ADRs...");
|
||||
|
||||
adr
|
||||
.command("new [title]")
|
||||
@@ -42,7 +88,11 @@ export function createCli({
|
||||
)
|
||||
.action(
|
||||
(title: string | undefined, opts: NewCommandOpts): Promise<void> => {
|
||||
return new NewCommand({ l4bInstance, appConsole }).execute(opts, title);
|
||||
const cmd = new NewCommand({
|
||||
l4bInstance: getL4bInstance(),
|
||||
appConsole
|
||||
});
|
||||
return execWithErrorHandler(cmd.execute(opts, title), appConsole);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -65,7 +115,11 @@ export function createCli({
|
||||
.description("List ADRs")
|
||||
.action(
|
||||
(opts: ListCommandOpts): Promise<void> => {
|
||||
return new ListCommand({ l4bInstance, appConsole }).execute(opts);
|
||||
const cmd = new ListCommand({
|
||||
l4bInstance: getL4bInstance(),
|
||||
appConsole
|
||||
});
|
||||
return execWithErrorHandler(cmd.execute(opts), appConsole);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
1
packages/cli/src/index.ts
Normal file
1
packages/cli/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { createCli } from "./cli";
|
||||
@@ -1,55 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import terminalLink from "terminal-link";
|
||||
import { Log4brains, Log4brainsError } from "@log4brains/core";
|
||||
import { AppConsole } from "@log4brains/cli-common";
|
||||
import { createCli } from "./cli";
|
||||
|
||||
const templateExampleUrl =
|
||||
"https://raw.githubusercontent.com/thomvaill/log4brains/master/packages/init/assets/template.md";
|
||||
|
||||
function findRootFolder(cwd: string): string {
|
||||
if (fs.existsSync(path.join(cwd, ".log4brains.yml"))) {
|
||||
return cwd;
|
||||
}
|
||||
if (path.resolve(cwd) === "/") {
|
||||
throw new Error("Impossible to find a .log4brains.yml configuration file");
|
||||
}
|
||||
return findRootFolder(path.join(cwd, ".."));
|
||||
}
|
||||
|
||||
const debug = !!process.env.DEBUG;
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const pkgVersion = require("../package.json").version as string;
|
||||
|
||||
const l4bInstance = Log4brains.create(
|
||||
findRootFolder(process.env.LOG4BRAINS_CWD || ".")
|
||||
);
|
||||
|
||||
const cli = createCli({ version: pkgVersion, l4bInstance, appConsole });
|
||||
cli.parseAsync(process.argv).catch((err) => {
|
||||
appConsole.fatal(err);
|
||||
|
||||
if (
|
||||
err instanceof Log4brainsError &&
|
||||
err.name === "The template.md file does not exist"
|
||||
) {
|
||||
appConsole.printlnErr(
|
||||
`You can use this ${terminalLink(
|
||||
"template",
|
||||
templateExampleUrl
|
||||
)} as an example`
|
||||
);
|
||||
appConsole.printlnErr();
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
} catch (e) {
|
||||
appConsole.fatal(e);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
import hasYarn from "has-yarn";
|
||||
import execa from "execa";
|
||||
|
||||
export async function previewAdr(slug: string): Promise<void> {
|
||||
const subprocess = hasYarn()
|
||||
? execa("yarn", ["run", "log4brains-preview", slug], {
|
||||
stdio: "inherit"
|
||||
})
|
||||
: execa("npm", ["run", "--silent", "log4brains-preview", "--", slug], {
|
||||
stdio: "inherit"
|
||||
});
|
||||
const subprocess = execa("log4brains", ["preview", slug], {
|
||||
stdio: "inherit"
|
||||
});
|
||||
subprocess.stdout?.pipe(process.stdout);
|
||||
subprocess.stderr?.pipe(process.stderr);
|
||||
await subprocess;
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
# @log4brains/core
|
||||
|
||||
This package provides the core API of the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base.
|
||||
It is not meant to be used directly in your project.
|
||||
It is not meant to be used directly.
|
||||
|
||||
Install [the main log4brains package](https://www.npmjs.com/package/log4brains) instead:
|
||||
|
||||
```bash
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
This package is not meant to be installed directly in your project. This is a common dependency of [@log4brains/cli](https://www.npmjs.com/package/@log4brains/cli) and [@log4brains/web](https://www.npmjs.com/package/@log4brains/web), which is installed automatically.
|
||||
This package is not meant to be installed directly in your project.
|
||||
|
||||
However, if you want to create a package to extend [Log4brains](https://github.com/thomvaill/log4brains)' capabilities,
|
||||
you can include this package as a dependency of yours via npm or yarn:
|
||||
However, if you want to create a package that uses [Log4brains](https://github.com/thomvaill/log4brains)' API,
|
||||
you can include this package as a dependency:
|
||||
|
||||
```bash
|
||||
npm install --save @log4brains/core
|
||||
|
||||
11
packages/global-cli/.eslintrc.js
Normal file
11
packages/global-cli/.eslintrc.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
parserOptions: {
|
||||
project: path.join(__dirname, "tsconfig.json")
|
||||
},
|
||||
extends: ["../../.eslintrc"]
|
||||
};
|
||||
5
packages/global-cli/nodemon.json
Normal file
5
packages/global-cli/nodemon.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ext": "ts",
|
||||
"exec": "yarn build"
|
||||
}
|
||||
61
packages/global-cli/package.json
Normal file
61
packages/global-cli/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "log4brains",
|
||||
"version": "1.0.0-beta.4",
|
||||
"description": "Log and publish your architecture decisions (ADR) with Log4brains",
|
||||
"keywords": [
|
||||
"log4brains",
|
||||
"architecture decision records",
|
||||
"adr",
|
||||
"architecture",
|
||||
"knowledge base",
|
||||
"documentation",
|
||||
"docs-as-code",
|
||||
"markdown",
|
||||
"static site generator",
|
||||
"documentation generator",
|
||||
"tooling"
|
||||
],
|
||||
"author": "Thomas Vaillant <thomvaill@bluebricks.dev>",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"homepage": "https://github.com/thomvaill/log4brains",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thomvaill/log4brains",
|
||||
"directory": "packages/global-cli"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.23.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"log4brains": "./dist/log4brains"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "tsc --build tsconfig.build.json && copyfiles -u 1 src/log4brains dist",
|
||||
"clean": "rimraf ./dist",
|
||||
"typescript": "tsc --noEmit",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"prepublishOnly": "cp ../../README.md . && yarn build",
|
||||
"link": "yarn link",
|
||||
"unlink": "yarn unlink"
|
||||
},
|
||||
"dependencies": {
|
||||
"@log4brains/cli": "^1.0.0-beta.4",
|
||||
"@log4brains/cli-common": "^1.0.0-beta.4",
|
||||
"@log4brains/init": "^1.0.0-beta.4",
|
||||
"@log4brains/web": "^1.0.0-beta.4",
|
||||
"chalk": "^4.1.0",
|
||||
"commander": "^6.1.0",
|
||||
"esm": "^3.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copyfiles": "^2.4.0"
|
||||
}
|
||||
}
|
||||
35
packages/global-cli/src/cli.ts
Normal file
35
packages/global-cli/src/cli.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import commander from "commander";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import { createInitCli } from "@log4brains/init";
|
||||
import { createCli } from "@log4brains/cli";
|
||||
import { createWebCli } from "@log4brains/web";
|
||||
|
||||
type Deps = {
|
||||
appConsole: AppConsole;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export function createGlobalCli({
|
||||
appConsole,
|
||||
version
|
||||
}: Deps): commander.Command {
|
||||
const program = new commander.Command();
|
||||
program
|
||||
.version(version)
|
||||
.description(
|
||||
"Log4brains CLI to preview and build your architecture knowledge base.\n" +
|
||||
"You can also manage your ADRs from here (see `log4brains adr --help`).\n\n" +
|
||||
"All the commands should be run from your project's root folder.\n\n" +
|
||||
"Add the `--help` option to any command to see its detailed documentation."
|
||||
);
|
||||
|
||||
const initCli = createInitCli({ appConsole });
|
||||
const cli = createCli({ appConsole });
|
||||
const webCli = createWebCli({ appConsole });
|
||||
|
||||
[...initCli.commands, ...cli.commands, ...webCli.commands].forEach((cmd) => {
|
||||
program.addCommand(cmd);
|
||||
});
|
||||
|
||||
return program;
|
||||
}
|
||||
46
packages/global-cli/src/main.ts
Normal file
46
packages/global-cli/src/main.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import chalk from "chalk";
|
||||
import {
|
||||
AppConsole,
|
||||
FailureExit,
|
||||
Log4brainsConfigNotFound
|
||||
} from "@log4brains/cli-common";
|
||||
import { createGlobalCli } from "./cli";
|
||||
|
||||
const debug = !!process.env.DEBUG;
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
|
||||
function handleError(err: unknown): void {
|
||||
if (appConsole.isSpinning()) {
|
||||
appConsole.stopSpinner(true);
|
||||
}
|
||||
|
||||
if (err instanceof FailureExit) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (err instanceof Log4brainsConfigNotFound) {
|
||||
appConsole.fatal(`Cannot find ${chalk.bold(".log4brains.yml")}`);
|
||||
appConsole.printlnErr(
|
||||
chalk.red(
|
||||
`You are in the wrong directory or you need to run ${chalk.cyan(
|
||||
"log4brains init"
|
||||
)}`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
appConsole.fatal(err as Error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const pkgVersion = require("../package.json").version as string;
|
||||
|
||||
const cli = createGlobalCli({ version: pkgVersion, appConsole });
|
||||
cli.parseAsync(process.argv).catch(handleError);
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
4
packages/global-cli/tsconfig.build.json
Normal file
4
packages/global-cli/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
16
packages/global-cli/tsconfig.json
Normal file
16
packages/global-cli/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@src/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"ts-node": { "files": true }
|
||||
}
|
||||
@@ -1,24 +1,14 @@
|
||||
# init-log4brains
|
||||
# @log4brains/init
|
||||
|
||||
This interactive CLI lets you install and configure the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base in your project.
|
||||
This package provides the initialization script of the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base.
|
||||
It is not meant to be used directly.
|
||||
|
||||
## Usage
|
||||
|
||||
You should have a look at the main [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md) to get more context.
|
||||
|
||||
Start the interactive CLI by running this command in your project root directory:
|
||||
Install [the main log4brains package](https://www.npmjs.com/package/log4brains) instead:
|
||||
|
||||
```bash
|
||||
npx init-log4brains
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
It will:
|
||||
|
||||
- Install `@log4brains/cli` and `@log4brains/web` as development dependencies in your project (it detects automatically whether you use npm or yarn)
|
||||
- Add some entries in your `package.json`'s scripts: `adr`, `log4brains-preview`, `log4brains-build`
|
||||
- Prompt you some questions in order to create the `.log4brains.yml` config file for you
|
||||
- Import your existing Architecture Decision Records (ADR), or create a first one if you don't have any yet
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md)
|
||||
|
||||
@@ -8,12 +8,16 @@ Please use this link to browse them.
|
||||
|
||||
## Development
|
||||
|
||||
If not already done, install Log4brains:
|
||||
|
||||
```bash
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
To preview the knowledge base locally, run:
|
||||
|
||||
```bash
|
||||
npm run log4brains-preview
|
||||
# OR
|
||||
yarn log4brains-preview
|
||||
log4brains preview
|
||||
```
|
||||
|
||||
In preview mode, the Hot Reload feature is enabled: any change you make to a markdown file is applied live in the UI.
|
||||
@@ -21,9 +25,7 @@ In preview mode, the Hot Reload feature is enabled: any change you make to a mar
|
||||
To create a new ADR interactively, run:
|
||||
|
||||
```bash
|
||||
npm run adr new
|
||||
# OR
|
||||
yarn adr new
|
||||
log4brains adr new
|
||||
```
|
||||
|
||||
## More information
|
||||
|
||||
7
packages/init/integration-tests/fake-entrypoint.ts
Normal file
7
packages/init/integration-tests/fake-entrypoint.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { AppConsole } from "@log4brains/cli-common";
|
||||
import { createInitCli } from "../src/cli";
|
||||
|
||||
const cli = createInitCli({
|
||||
appConsole: new AppConsole({ debug: false, traces: false })
|
||||
});
|
||||
cli.parse(process.argv);
|
||||
@@ -4,16 +4,12 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-regexp-exec */
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
/* eslint-disable jest/no-try-expect */
|
||||
import execa, { ExecaError } from "execa";
|
||||
import execa from "execa";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
import fs, { promises as fsP } from "fs";
|
||||
import rimraf from "rimraf";
|
||||
|
||||
type PackageJson = Record<string, unknown> & {
|
||||
scripts: Record<string, string>;
|
||||
};
|
||||
|
||||
// Source: https://shift.infinite.red/integration-testing-interactive-clis-93af3cc0d56f. Thanks!
|
||||
const keys = {
|
||||
up: "\x1B\x5B\x41",
|
||||
@@ -22,10 +18,11 @@ const keys = {
|
||||
space: "\x20"
|
||||
};
|
||||
|
||||
// TODO: to rewrite completely. Because now we can just import the CLI and test it directly, not with execa (dirty!)
|
||||
// Inspired by Next.js's test/integration/create-next-app/index.test.js. Thank you!
|
||||
const cliPath = path.join(__dirname, "../src/main");
|
||||
const cliPath = path.join(__dirname, "fake-entrypoint.ts");
|
||||
const run = (cwd: string) =>
|
||||
execa("node", ["-r", "esm", "-r", "ts-node/register", cliPath, cwd]);
|
||||
execa("node", ["-r", "esm", "-r", "ts-node/register", cliPath, "init", cwd]);
|
||||
|
||||
jest.setTimeout(1000 * 60);
|
||||
|
||||
@@ -34,7 +31,7 @@ async function usingTempDir(fn: (cwd: string) => void | Promise<void>) {
|
||||
path.join(os.tmpdir(), "log4brains-init-tests-")
|
||||
);
|
||||
try {
|
||||
return await fn(folder);
|
||||
await fn(folder);
|
||||
} finally {
|
||||
rimraf.sync(folder);
|
||||
}
|
||||
@@ -67,8 +64,11 @@ function bindAnswers(
|
||||
throw new Error("CLI must have an stdin");
|
||||
}
|
||||
|
||||
if (!name && line.match(/What is the name of your project\?/)) {
|
||||
if (!name && line.match(/Continue\?/)) {
|
||||
cli.stdin.write("\n");
|
||||
}
|
||||
if (!name && line.match(/What is the name of your project\?/)) {
|
||||
cli.stdin.write("Test\n");
|
||||
name = true;
|
||||
}
|
||||
if (
|
||||
@@ -123,36 +123,11 @@ function bindAnswers(
|
||||
}
|
||||
|
||||
describe("Init", () => {
|
||||
test("empty directory", async () => {
|
||||
test("mono package project", async () => {
|
||||
await usingTempDir(async (cwd) => {
|
||||
expect.assertions(1);
|
||||
|
||||
try {
|
||||
await run(cwd);
|
||||
} catch (e) {
|
||||
expect((e as ExecaError).stderr).toMatch(
|
||||
/Impossible to find package\.json/
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("fresh NPM mono project", async () => {
|
||||
await usingTempDir(async (cwd) => {
|
||||
await execa("npm", ["init", "--yes"], { cwd });
|
||||
await execa("npm", ["install"], { cwd });
|
||||
|
||||
await execa("npm", ["init", "--yes"], { cwd }); // TODO: without this line, the test hangs! Find out why
|
||||
await bindAnswers(run(cwd));
|
||||
|
||||
const pkgJson = require(path.join(cwd, "package.json")) as PackageJson;
|
||||
expect(pkgJson.scripts.adr).toEqual("log4brains adr");
|
||||
expect(pkgJson.scripts["log4brains-preview"]).toEqual(
|
||||
"log4brains-web preview"
|
||||
);
|
||||
expect(pkgJson.scripts["log4brains-build"]).toEqual(
|
||||
"log4brains-web build"
|
||||
);
|
||||
|
||||
expect(fs.existsSync(path.join(cwd, ".log4brains.yml"))).toBeTruthy(); // TODO: test its content
|
||||
expect(fs.existsSync(path.join(cwd, "docs/adr"))).toBeTruthy();
|
||||
expect(
|
||||
@@ -163,28 +138,9 @@ describe("Init", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("fresh yarn mono project", async () => {
|
||||
test("multi packages project", async () => {
|
||||
await usingTempDir(async (cwd) => {
|
||||
await execa("npm", ["init", "--yes"], { cwd });
|
||||
await execa("yarn", ["install"], { cwd });
|
||||
|
||||
await bindAnswers(run(cwd));
|
||||
|
||||
const pkgJson = require(path.join(cwd, "package.json")) as PackageJson;
|
||||
expect(pkgJson.scripts.adr).toEqual("log4brains adr");
|
||||
expect(pkgJson.scripts["log4brains-preview"]).toEqual(
|
||||
"log4brains-web preview"
|
||||
);
|
||||
expect(pkgJson.scripts["log4brains-build"]).toEqual(
|
||||
"log4brains-web build"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("fresh NPM multi project", async () => {
|
||||
await usingTempDir(async (cwd) => {
|
||||
await execa("npm", ["init", "--yes"], { cwd });
|
||||
await execa("npm", ["install"], { cwd });
|
||||
await execa("npm", ["init", "--yes"], { cwd }); // TODO: without this line, the test hangs! Find out why
|
||||
await execa("mkdir", ["-p", "packages/package1"], { cwd });
|
||||
|
||||
await bindAnswers(run(cwd), {
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
{
|
||||
"name": "init-log4brains",
|
||||
"name": "@log4brains/init",
|
||||
"version": "1.0.0-beta.4",
|
||||
"description": "Install and configure the Log4brains architecture knowledge base in your project",
|
||||
"description": "Log4brains architecture knowledge base initialization CLI",
|
||||
"keywords": [
|
||||
"log4brains",
|
||||
"architecture decision records",
|
||||
"architecture",
|
||||
"knowledge base",
|
||||
"documentation",
|
||||
"docs-as-code",
|
||||
"markdown",
|
||||
"static site generator",
|
||||
"documentation generator",
|
||||
"tooling"
|
||||
"log4brains"
|
||||
],
|
||||
"author": "Thomas Vaillant <thomvaill@bluebricks.dev>",
|
||||
"license": "Apache-2.0",
|
||||
@@ -33,26 +24,24 @@
|
||||
"assets",
|
||||
"dist"
|
||||
],
|
||||
"bin": "./dist/log4brains-init",
|
||||
"source": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "tsc --build tsconfig.build.json && copyfiles -u 1 src/log4brains-init dist",
|
||||
"build": "tsc --build tsconfig.build.json",
|
||||
"clean": "rimraf ./dist",
|
||||
"typescript": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"test-watch": "jest --watch",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"prepublishOnly": "yarn build",
|
||||
"link": "npm link @log4brains/cli-common && npm link && rm -f ./package-lock.json"
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@log4brains/cli-common": "^1.0.0-beta.4",
|
||||
"chalk": "^4.1.0",
|
||||
"commander": "^6.1.0",
|
||||
"edit-json-file": "^1.5.0",
|
||||
"esm": "^3.2.25",
|
||||
"execa": "^4.1.0",
|
||||
"has-yarn": "^2.1.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"moment-timezone": "^0.5.32",
|
||||
"terminal-link": "^2.1.1",
|
||||
@@ -60,7 +49,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/edit-json-file": "^1.4.0",
|
||||
"copyfiles": "^2.4.0",
|
||||
"esm": "^3.2.25",
|
||||
"ts-node": "^9.0.0"
|
||||
}
|
||||
|
||||
@@ -4,19 +4,15 @@ import { InitCommand, InitCommandOpts } from "./commands";
|
||||
|
||||
type Deps = {
|
||||
appConsole: AppConsole;
|
||||
version: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function createCli({
|
||||
appConsole,
|
||||
name,
|
||||
version
|
||||
}: Deps): commander.Command {
|
||||
return new commander.Command(name)
|
||||
.version(version)
|
||||
export function createInitCli({ appConsole }: Deps): commander.Command {
|
||||
const program = new commander.Command();
|
||||
|
||||
program
|
||||
.command("init")
|
||||
.arguments("[path]")
|
||||
.description("Installs and configures Log4brains for your project", {
|
||||
.description("Configures Log4brains for your project", {
|
||||
path: "Path of your project. Default: current directory"
|
||||
})
|
||||
.option(
|
||||
@@ -29,4 +25,6 @@ export function createCli({
|
||||
return new InitCommand({ appConsole }).execute(options, path);
|
||||
}
|
||||
);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
@@ -3,20 +3,16 @@
|
||||
import fs, { promises as fsP } from "fs";
|
||||
import terminalLink from "terminal-link";
|
||||
import chalk from "chalk";
|
||||
import hasYarn from "has-yarn";
|
||||
import execa from "execa";
|
||||
import mkdirp from "mkdirp";
|
||||
import yaml from "yaml";
|
||||
import path from "path";
|
||||
import editJsonFile from "edit-json-file";
|
||||
import moment from "moment-timezone";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import { FailureExit } from "./FailureExit";
|
||||
import { FailureExit } from "@log4brains/cli-common";
|
||||
|
||||
const assetsPath = path.resolve(path.join(__dirname, "../../assets"));
|
||||
const docLink = "https://github.com/thomvaill/log4brains";
|
||||
const cliBinPath = "@log4brains/cli/dist/log4brains";
|
||||
const webBinPath = "@log4brains/web/dist/bin/log4brains-web";
|
||||
|
||||
function forceUnixPath(p: string): string {
|
||||
return p.replace(/\\/g, "/");
|
||||
@@ -47,76 +43,10 @@ type Deps = {
|
||||
export class InitCommand {
|
||||
private readonly console: AppConsole;
|
||||
|
||||
private hasYarnValue?: boolean;
|
||||
|
||||
constructor({ appConsole }: Deps) {
|
||||
this.console = appConsole;
|
||||
}
|
||||
|
||||
private hasYarn(): boolean {
|
||||
if (!this.hasYarnValue) {
|
||||
this.hasYarnValue = hasYarn();
|
||||
}
|
||||
return this.hasYarnValue;
|
||||
}
|
||||
|
||||
private isDev(): boolean {
|
||||
return (
|
||||
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test"
|
||||
);
|
||||
}
|
||||
|
||||
private async installNpmPackages(cwd: string): Promise<void> {
|
||||
const packages = ["@log4brains/cli", "@log4brains/web"];
|
||||
|
||||
if (this.isDev()) {
|
||||
await execa("yarn", ["link", ...packages], { cwd });
|
||||
|
||||
// ... but unfortunately `yarn link` does not create the bin symlinks (https://github.com/yarnpkg/yarn/issues/5713)
|
||||
// we have to do it ourselves:
|
||||
await mkdirp(path.join(cwd, "node_modules/.bin"));
|
||||
await execa(
|
||||
"ln",
|
||||
["-s", "--force", `../${cliBinPath}`, "node_modules/.bin/log4brains"],
|
||||
{ cwd }
|
||||
);
|
||||
await execa(
|
||||
"ln",
|
||||
[
|
||||
"-s",
|
||||
"--force",
|
||||
`../${webBinPath}`,
|
||||
"node_modules/.bin/log4brains-web"
|
||||
],
|
||||
{ cwd }
|
||||
);
|
||||
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
`${chalk.bgBlue.white.bold(" DEV ")} ${chalk.blue(
|
||||
"Local packages are linked!"
|
||||
)}`
|
||||
);
|
||||
this.console.println();
|
||||
} else if (this.hasYarn()) {
|
||||
await execa(
|
||||
"yarn",
|
||||
["add", "--dev", "--ignore-workspace-root-check", ...packages],
|
||||
{ cwd }
|
||||
);
|
||||
} else {
|
||||
await execa("npm", ["install", "--save-dev", ...packages], { cwd });
|
||||
}
|
||||
}
|
||||
|
||||
private setupPackageJsonScripts(packageJsonPath: string): void {
|
||||
const pkgJson = editJsonFile(packageJsonPath);
|
||||
pkgJson.set("scripts.adr", "log4brains adr");
|
||||
pkgJson.set("scripts.log4brains-preview", "log4brains-web preview");
|
||||
pkgJson.set("scripts.log4brains-build", "log4brains-web build");
|
||||
pkgJson.save();
|
||||
}
|
||||
|
||||
private guessMainAdrFolderPath(cwd: string): string | undefined {
|
||||
const usualPaths = [
|
||||
"./docs/adr",
|
||||
@@ -143,30 +73,52 @@ export class InitCommand {
|
||||
cwd: string,
|
||||
noInteraction: boolean
|
||||
): Promise<L4bYmlConfig> {
|
||||
this.console.println(
|
||||
`We will now help you to create your ${chalk.cyan(".log4brains.yml")}...`
|
||||
);
|
||||
this.console.println(chalk.bold("👋 Welcome to Log4brains!"));
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
"This interactive script will help you configure Log4brains for your project."
|
||||
);
|
||||
this.console.println(
|
||||
`It will create the ${chalk.cyan(".log4brains.yml")} config file,`
|
||||
);
|
||||
this.console.println(" copy the default ADR template,");
|
||||
this.console.println(" and create your first ADR for you!");
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
"Before going further, please check that you are running this command"
|
||||
);
|
||||
this.console.println(
|
||||
"from the root folder of your project's git repository:"
|
||||
);
|
||||
this.console.println(chalk.cyan(cwd));
|
||||
|
||||
// Continue?
|
||||
if (
|
||||
!noInteraction &&
|
||||
!(await this.console.askYesNoQuestion("Continue?", true))
|
||||
) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
"👍 We will now ask you several questions to get you started:"
|
||||
);
|
||||
|
||||
// Name
|
||||
let name;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,global-require,import/no-dynamic-require,@typescript-eslint/no-var-requires
|
||||
name = require(path.join(cwd, "package.json")).name as string;
|
||||
if (!name) {
|
||||
throw Error("Empty name");
|
||||
}
|
||||
} catch (e) {
|
||||
this.console.warn(
|
||||
`Impossible to get the project name from your ${chalk.cyan(
|
||||
"package.json"
|
||||
)}`
|
||||
);
|
||||
// ignore
|
||||
}
|
||||
|
||||
name = noInteraction
|
||||
? name || "untitled"
|
||||
: await this.console.askInputQuestion(
|
||||
: await this.console.askInputQuestionAndValidate(
|
||||
"What is the name of your project?",
|
||||
(answer) => !!answer.trim(),
|
||||
name
|
||||
);
|
||||
|
||||
@@ -193,24 +145,29 @@ export class InitCommand {
|
||||
// Main ADR folder location
|
||||
let adrFolder = this.guessMainAdrFolderPath(cwd);
|
||||
if (adrFolder) {
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
`We have detected a possible existing ADR folder: ${chalk.cyan(
|
||||
adrFolder
|
||||
)}`
|
||||
`${chalk.blue.bold(
|
||||
"i We have detected a folder with existing ADRs:"
|
||||
)} ${chalk.cyan(adrFolder)}`
|
||||
);
|
||||
adrFolder =
|
||||
noInteraction ||
|
||||
(await this.console.askYesNoQuestion("Do you confirm?", true))
|
||||
(await this.console.askYesNoQuestion(
|
||||
"Do you want to use it? (existing ADRs will be kept)",
|
||||
true
|
||||
))
|
||||
? adrFolder
|
||||
: undefined;
|
||||
}
|
||||
if (!adrFolder) {
|
||||
adrFolder = noInteraction
|
||||
? "./docs/adr"
|
||||
: await this.console.askInputQuestion(
|
||||
: await this.console.askInputQuestionAndValidate(
|
||||
`In which directory do you plan to store your ${
|
||||
type === "multi" ? "global " : ""
|
||||
}ADRs? (will be automatically created)`,
|
||||
(answer) => !!answer.trim(),
|
||||
"./docs/adr"
|
||||
);
|
||||
}
|
||||
@@ -230,16 +187,18 @@ export class InitCommand {
|
||||
this.console.println(
|
||||
` ${chalk.underline(`Package #${packageNumber}`)}:`
|
||||
);
|
||||
const pkgName = await this.console.askInputQuestion(
|
||||
"Name? (short, lowercase, without special characters, nor spaces)"
|
||||
const pkgName = await this.console.askInputQuestionAndValidate(
|
||||
"Name? (short, lowercase, without special characters, nor spaces)",
|
||||
(answer) => !!answer.trim()
|
||||
);
|
||||
const pkgCodeFolder = await this.askPathWhileNotFound(
|
||||
"Where is located the source code of this package?",
|
||||
cwd,
|
||||
`./packages/${pkgName}`
|
||||
);
|
||||
const pkgAdrFolder = await this.console.askInputQuestion(
|
||||
const pkgAdrFolder = await this.console.askInputQuestionAndValidate(
|
||||
`In which directory do you plan to store the ADRs of this package? (will be automatically created)`,
|
||||
(answer) => !!answer.trim(),
|
||||
`${pkgCodeFolder}/docs/adr`
|
||||
);
|
||||
await mkdirp(path.join(cwd, pkgAdrFolder));
|
||||
@@ -275,7 +234,7 @@ export class InitCommand {
|
||||
): Promise<string> {
|
||||
const slug = (
|
||||
await execa(
|
||||
path.join(cwd, `node_modules/${cliBinPath}`),
|
||||
"log4brains",
|
||||
[
|
||||
"adr",
|
||||
"new",
|
||||
@@ -329,18 +288,15 @@ export class InitCommand {
|
||||
}
|
||||
|
||||
private printSuccess(): void {
|
||||
const runCmd = this.hasYarn() ? "yarn" : "npm run";
|
||||
const l4bCliCmdName = "adr";
|
||||
|
||||
this.console.success("Log4brains is installed and configured! 🎉🎉🎉");
|
||||
this.console.success("Log4brains is configured! 🎉🎉🎉");
|
||||
this.console.println();
|
||||
this.console.println("You can now use the CLI to create a new ADR:");
|
||||
this.console.println(` ${chalk.cyan(`${runCmd} ${l4bCliCmdName} new`)}`);
|
||||
this.console.println(` ${chalk.cyan(`log4brains adr new`)}`);
|
||||
this.console.println("");
|
||||
this.console.println(
|
||||
"And start the web UI to preview your architecture knowledge base:"
|
||||
);
|
||||
this.console.println(` ${chalk.cyan(`${runCmd} log4brains-preview`)}`);
|
||||
this.console.println(` ${chalk.cyan(`log4brains preview`)}`);
|
||||
this.console.println();
|
||||
this.console.println(
|
||||
"Do not forget to set up your CI/CD to automatically publish your knowledge base"
|
||||
@@ -381,46 +337,11 @@ export class InitCommand {
|
||||
throw new FailureExit();
|
||||
}
|
||||
|
||||
// Check package.json existence
|
||||
const packageJsonPath = path.join(cwd, "package.json");
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
this.console.fatal(`Impossible to find ${chalk.cyan("package.json")}`);
|
||||
this.console.printlnErr(
|
||||
"Are you sure to execute the command inside your project's root directory?"
|
||||
);
|
||||
this.console.printlnErr(
|
||||
`Please refer to the ${terminalLink(
|
||||
"documentation",
|
||||
docLink
|
||||
)} if you want to use Log4brains in a non-JS project or globally`
|
||||
);
|
||||
throw new FailureExit();
|
||||
}
|
||||
|
||||
// Install NPM packages
|
||||
this.console.startSpinner("Install Log4brains packages...");
|
||||
await this.installNpmPackages(cwd);
|
||||
this.console.stopSpinner();
|
||||
|
||||
// Setup package.json scripts
|
||||
this.setupPackageJsonScripts(packageJsonPath);
|
||||
this.console.println(
|
||||
`We have added the following scripts to your ${chalk.cyan(
|
||||
"package.json"
|
||||
)}:`
|
||||
);
|
||||
this.console.println(" - adr");
|
||||
this.console.println(" - log4brains-preview");
|
||||
this.console.println(" - log4brains-init");
|
||||
this.console.println();
|
||||
|
||||
// Terminate now if already configured
|
||||
if (fs.existsSync(path.join(cwd, ".log4brains.yml"))) {
|
||||
this.console.warn(`${chalk.bold(".log4brains.yml")} already exists`);
|
||||
this.console.warn(
|
||||
`${chalk.bold(".log4brains.yml")} already exists. We won't override it`
|
||||
);
|
||||
this.console.warn(
|
||||
"Please remove it and execute this command again if you want to configure it interactively"
|
||||
"Please delete it and re-run this command if you want to configure it again"
|
||||
);
|
||||
this.console.println();
|
||||
this.printSuccess();
|
||||
@@ -433,7 +354,7 @@ export class InitCommand {
|
||||
noInteraction
|
||||
);
|
||||
|
||||
this.console.startSpinner("Write config file...");
|
||||
this.console.startSpinner("Writing config file...");
|
||||
const { adrFolder } = config.project;
|
||||
await fsP.writeFile(
|
||||
path.join(cwd, ".log4brains.yml"),
|
||||
@@ -442,7 +363,7 @@ export class InitCommand {
|
||||
);
|
||||
|
||||
// Copy template, index and README if not already created
|
||||
this.console.updateSpinner("Copy template files...");
|
||||
this.console.updateSpinner("Copying template files...");
|
||||
await this.copyFileIfAbsent(cwd, adrFolder, "template.md");
|
||||
await this.copyFileIfAbsent(cwd, adrFolder, "index.md", (content) =>
|
||||
content.replace(/{PROJECT_NAME}/g, config.project.name)
|
||||
@@ -450,12 +371,10 @@ export class InitCommand {
|
||||
await this.copyFileIfAbsent(cwd, adrFolder, "README.md");
|
||||
|
||||
// List existing ADRs
|
||||
this.console.updateSpinner("Create your first ADR...");
|
||||
const adrListRes = await execa(
|
||||
path.join(cwd, `node_modules/${cliBinPath}`),
|
||||
["adr", "list", "--raw"],
|
||||
{ cwd }
|
||||
);
|
||||
this.console.updateSpinner("Creating your first ADR...");
|
||||
const adrListRes = await execa("log4brains", ["adr", "list", "--raw"], {
|
||||
cwd
|
||||
});
|
||||
|
||||
// Create Log4brains ADR
|
||||
const l4bAdrSlug = await this.createAdr(
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./FailureExit";
|
||||
export * from "./InitCommand";
|
||||
|
||||
1
packages/init/src/index.ts
Normal file
1
packages/init/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { createInitCli } from "./cli";
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
require = require("esm")(module, {
|
||||
mainFields: ["module", "main"]
|
||||
});
|
||||
module.exports = require("./main");
|
||||
@@ -1,29 +0,0 @@
|
||||
import { AppConsole } from "@log4brains/cli-common";
|
||||
import { createCli } from "./cli";
|
||||
import { FailureExit } from "./commands";
|
||||
|
||||
const debug = !!process.env.DEBUG;
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line
|
||||
const { name, version } = require("../package.json") as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
|
||||
const cli = createCli({ name, version, appConsole });
|
||||
cli.parseAsync(process.argv).catch((err) => {
|
||||
if (!(err instanceof FailureExit)) {
|
||||
if (appConsole.isSpinning()) {
|
||||
appConsole.stopSpinner(true);
|
||||
}
|
||||
appConsole.fatal(err);
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
} catch (e) {
|
||||
appConsole.fatal(e);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,73 +1,14 @@
|
||||
# @log4brains/web
|
||||
|
||||
This package provides the web UI of the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base and its static site generation capabilities.
|
||||
This package provides the static site generation features of the [Log4brains](https://github.com/thomvaill/log4brains) architecture knowledge base.
|
||||
It is not meant to be used directly.
|
||||
|
||||
## Installation
|
||||
|
||||
You should use `npx init-log4brains` as described in the [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md), which will install all the required dependencies in your project, including this one, and set up the right scripts in your `package.json`.
|
||||
|
||||
You can also install this package manually via npm or yarn:
|
||||
Install [the main log4brains package](https://www.npmjs.com/package/log4brains) instead:
|
||||
|
||||
```bash
|
||||
npm install --save-dev @log4brains/web
|
||||
npm install -g log4brains
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn add --dev @log4brains/web
|
||||
```
|
||||
|
||||
And add these scripts to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
[...]
|
||||
"scripts": {
|
||||
[...]
|
||||
"log4brains-preview": "log4brains-web preview",
|
||||
"log4brains-build": "log4brains-web build",
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You should have a look at the main [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md) to get more context.
|
||||
|
||||
### Preview
|
||||
|
||||
This command will start the web UI in preview mode locally on <http://localhost:4004/>.
|
||||
You can define another port with the `-p` option.
|
||||
In this mode, the Hot Reload feature is enabled: any changes you make to the markdown files are applied live in the UI.
|
||||
|
||||
```bash
|
||||
npm run log4brains-preview
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn log4brains-preview
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
This command should be used in your CI/CD pipeline. It creates a static version of your knowledge base, ready to be deployed
|
||||
on a static website hosting service like GitHub or GitLab pages.
|
||||
|
||||
```bash
|
||||
npm run log4brains-build
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn log4brains-build
|
||||
```
|
||||
|
||||
The default output directory is `.log4brains/out`. You can change it with the `-o` option.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Log4brains README](https://github.com/thomvaill/log4brains/blob/master/README.md)
|
||||
|
||||
@@ -23,23 +23,22 @@
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"bin": {
|
||||
"log4brains-web": "./dist/bin/log4brains-web"
|
||||
},
|
||||
"source": "./src/index.ts",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "yarn build && nodemon",
|
||||
"dev-old": "cross-env NODE_ENV=development LOG4BRAINS_CWD=../.. ESM_DISABLE_CACHE=true node -r esm -r ts-node/register ./src/bin/main.ts",
|
||||
"next": "cross-env LOG4BRAINS_CWD=../.. next",
|
||||
"build:ts": "tsc --noEmit false",
|
||||
"build": "yarn clean && yarn build:ts && copyfiles --all next.config.js .babelrc \"public/**/*\" dist && copyfiles --all --up 1 src/bin/log4brains-web \"src/lib/core-api/noop/**/*\" src/components/Markdown/hljs.css dist && cross-env LOG4BRAINS_PHASE=initial-build next build dist",
|
||||
"build:ts": "tsc --noEmit false --declaration true && find dist/pages -name \"*.d.ts\" -type f -exec rm -f {} +",
|
||||
"build": "yarn clean && yarn build:ts && copyfiles --all next.config.js .babelrc \"public/**/*\" dist && copyfiles --all --up 1 \"src/lib/core-api/noop/**/*\" src/components/Markdown/hljs.css dist && cross-env LOG4BRAINS_PHASE=initial-build next build dist",
|
||||
"clean": "rimraf ./dist",
|
||||
"serve": "serve .log4brains/out",
|
||||
"typescript": "tsc",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --max-warnings=0",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"prepublishOnly": "yarn build",
|
||||
"link": "yarn link"
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@log4brains/cli-common": "^1.0.0-beta.4",
|
||||
@@ -55,7 +54,6 @@
|
||||
"clsx": "^1.1.1",
|
||||
"commander": "^6.1.0",
|
||||
"copy-text-to-clipboard": "^2.2.0",
|
||||
"esm": "^3.2.25",
|
||||
"highlight.js": "^10.4.0",
|
||||
"lunr": "^2.3.9",
|
||||
"markdown-to-jsx": "^7.0.1",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
require = require("esm")(module);
|
||||
module.exports = require("./main");
|
||||
@@ -1,58 +0,0 @@
|
||||
import commander from "commander";
|
||||
import { previewCommand, buildCommand } from "../cli";
|
||||
import { appConsole } from "../lib/console";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,global-require,@typescript-eslint/no-var-requires
|
||||
const pkgVersion = require("../../package.json").version as string;
|
||||
|
||||
type StartEditorCommandOpts = {
|
||||
port: string;
|
||||
open: boolean;
|
||||
};
|
||||
type BuildCommandOpts = {
|
||||
out: string;
|
||||
basePath: string;
|
||||
};
|
||||
|
||||
function createCli(version: string): commander.Command {
|
||||
const program = new commander.Command();
|
||||
program.version(version);
|
||||
|
||||
program
|
||||
.command("preview [adr]")
|
||||
.description("Start log4brains locally to preview your changes", {
|
||||
adr:
|
||||
"If provided, will automatically open your browser to this specific ADR"
|
||||
})
|
||||
.option("-p, --port <port>", "Port to listen on", "4004")
|
||||
.option("--no-open", "Do not open the browser automatically", false)
|
||||
.action(
|
||||
(adr: string, opts: StartEditorCommandOpts): Promise<void> => {
|
||||
return previewCommand(parseInt(opts.port, 10), opts.open, adr);
|
||||
}
|
||||
);
|
||||
|
||||
program
|
||||
.command("build")
|
||||
.description("Build the deployable static website")
|
||||
.option("-o, --out <path>", "Output path", ".log4brains/out")
|
||||
.option("--basePath <path>", "Custom base path", "")
|
||||
.action(
|
||||
(opts: BuildCommandOpts): Promise<void> => {
|
||||
return buildCommand(opts.out, opts.basePath);
|
||||
}
|
||||
);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
const cli = createCli(pkgVersion);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
cli.parseAsync(process.argv).catch((err) => {
|
||||
if (appConsole.isSpinning()) {
|
||||
appConsole.stopSpinner(true);
|
||||
}
|
||||
appConsole.fatal(err);
|
||||
process.exit(1);
|
||||
});
|
||||
52
packages/web/src/cli/cli.ts
Normal file
52
packages/web/src/cli/cli.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import commander from "commander";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import { previewCommand, buildCommand } from "./commands";
|
||||
|
||||
type StartEditorCommandOpts = {
|
||||
port: string;
|
||||
open: boolean;
|
||||
};
|
||||
type BuildCommandOpts = {
|
||||
out: string;
|
||||
basePath: string;
|
||||
};
|
||||
|
||||
type Deps = {
|
||||
appConsole: AppConsole;
|
||||
};
|
||||
|
||||
export function createWebCli({ appConsole }: Deps): commander.Command {
|
||||
const program = new commander.Command();
|
||||
|
||||
program
|
||||
.command("preview [adr]")
|
||||
.description("Start Log4brains locally to preview your changes", {
|
||||
adr:
|
||||
"If provided, will automatically open your browser to this specific ADR"
|
||||
})
|
||||
.option("-p, --port <port>", "Port to listen on", "4004")
|
||||
.option("--no-open", "Do not open the browser automatically", false)
|
||||
.action(
|
||||
(adr: string, opts: StartEditorCommandOpts): Promise<void> => {
|
||||
return previewCommand(
|
||||
{ appConsole },
|
||||
parseInt(opts.port, 10),
|
||||
opts.open,
|
||||
adr
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
program
|
||||
.command("build")
|
||||
.description("Build Log4brains as a deployable static website")
|
||||
.option("-o, --out <path>", "Output path", ".log4brains/out")
|
||||
.option("--basePath <path>", "Custom base path", "")
|
||||
.action(
|
||||
(opts: BuildCommandOpts): Promise<void> => {
|
||||
return buildCommand({ appConsole }, opts.out, opts.basePath);
|
||||
}
|
||||
);
|
||||
|
||||
return program;
|
||||
}
|
||||
@@ -6,13 +6,19 @@ import path from "path";
|
||||
import mkdirp from "mkdirp";
|
||||
import { makeBadge } from "badge-maker";
|
||||
import { promises as fsP } from "fs";
|
||||
import { getLog4brainsInstance } from "../lib/core-api";
|
||||
import { getNextJsDir } from "../lib/next";
|
||||
import { appConsole, execNext } from "../lib/console";
|
||||
import { Search } from "../lib/search";
|
||||
import { toAdrLight } from "../types";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import { getLog4brainsInstance } from "../../lib/core-api";
|
||||
import { getNextJsDir } from "../../lib/next";
|
||||
import { Search } from "../../lib/search";
|
||||
import { toAdrLight } from "../../types";
|
||||
import { execNext } from "../../lib/console";
|
||||
|
||||
type Deps = {
|
||||
appConsole: AppConsole;
|
||||
};
|
||||
|
||||
export async function buildCommand(
|
||||
{ appConsole }: Deps,
|
||||
outPath: string,
|
||||
basePath: string
|
||||
): Promise<void> {
|
||||
2
packages/web/src/cli/commands/index.ts
Normal file
2
packages/web/src/cli/commands/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./preview";
|
||||
export * from "./build";
|
||||
@@ -3,11 +3,17 @@ import { createServer } from "http";
|
||||
import SocketIO from "socket.io";
|
||||
import chalk from "chalk";
|
||||
import open from "open";
|
||||
import { getLog4brainsInstance } from "../lib/core-api";
|
||||
import { getNextJsDir } from "../lib/next";
|
||||
import { appConsole, execNext } from "../lib/console";
|
||||
import type { AppConsole } from "@log4brains/cli-common";
|
||||
import { getLog4brainsInstance } from "../../lib/core-api";
|
||||
import { getNextJsDir } from "../../lib/next";
|
||||
import { execNext } from "../../lib/console";
|
||||
|
||||
type Deps = {
|
||||
appConsole: AppConsole;
|
||||
};
|
||||
|
||||
export async function previewCommand(
|
||||
{ appConsole }: Deps,
|
||||
port: number,
|
||||
openBrowser: boolean,
|
||||
adrSlug?: string
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./preview";
|
||||
export * from "./build";
|
||||
export * from "./cli";
|
||||
|
||||
1
packages/web/src/index.ts
Normal file
1
packages/web/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { createWebCli } from "./cli";
|
||||
@@ -154,9 +154,7 @@ export function AdrMenu({ adrs, currentAdrSlug, className, ...props }: Props) {
|
||||
Run the following command in your terminal:
|
||||
</Typography>
|
||||
<pre>
|
||||
<code className="hljs bash">
|
||||
npm run adr -- new{"\n"}# OR{"\n"}yarn adr new
|
||||
</code>
|
||||
<code className="hljs bash">log4brains adr new</code>
|
||||
</pre>
|
||||
<Typography>
|
||||
This will create a new ADR from your template and will open it
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import chalk from "chalk";
|
||||
import { AppConsole, ConsoleCapturer } from "@log4brains/cli-common";
|
||||
|
||||
const debug = !!process.env.DEBUG;
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
|
||||
export const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
import { ConsoleCapturer } from "@log4brains/cli-common";
|
||||
|
||||
/**
|
||||
* #NEXTJS-HACK
|
||||
@@ -15,6 +10,8 @@ export const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
* @param fn The code which calls Next.js methods for which we want to capture the output
|
||||
*/
|
||||
export async function execNext(fn: () => Promise<void>): Promise<void> {
|
||||
const debug = !!process.env.DEBUG;
|
||||
|
||||
const capturer = new ConsoleCapturer();
|
||||
if (debug) {
|
||||
capturer.onLog = (method, args) => {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Log4brainsError } from "@log4brains/core";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { AppConsole } from "@log4brains/cli-common";
|
||||
import { getLog4brainsInstance } from "../../../lib/core-api";
|
||||
import { appConsole } from "../../../lib/console";
|
||||
|
||||
// TODO: get the global singleton of AppConsole instead of re-creating it
|
||||
const debug = !!process.env.DEBUG;
|
||||
const dev = process.env.NODE_ENV === "development";
|
||||
const appConsole = new AppConsole({ debug, traces: debug || dev });
|
||||
|
||||
export default async (
|
||||
req: NextApiRequest,
|
||||
|
||||
Reference in New Issue
Block a user