feat: distribute as a global NPM package

BREAKING CHANGE: installation procedure is now completely modified
This commit is contained in:
Thomas Vaillant
2021-01-13 18:48:47 +01:00
committed by Thomas Vaillant
parent 079ee579db
commit 9551b689ff
53 changed files with 656 additions and 695 deletions

View File

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

View File

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

View File

@@ -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
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
export class Log4brainsConfigNotFound extends Error {
constructor() {
super("Cannot find .log4brains.yml");
}
}

View File

@@ -1,2 +1,4 @@
export * from "./AppConsole";
export * from "./ConsoleCapturer";
export * from "./FailureExit";
export * from "./Log4brainsConfigNotFound";

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
export { createCli } from "./cli";

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
const path = require("path");
module.exports = {
env: {
node: true
},
parserOptions: {
project: path.join(__dirname, "tsconfig.json")
},
extends: ["../../.eslintrc"]
};

View File

@@ -0,0 +1,5 @@
{
"watch": ["src"],
"ext": "ts",
"exec": "yarn build"
}

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

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

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

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "**/*.test.ts"]
}

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

View File

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

View File

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

View 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);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +1 @@
export * from "./FailureExit";
export * from "./InitCommand";

View File

@@ -0,0 +1 @@
export { createInitCli } from "./cli";

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env node
require = require("esm")(module, {
mainFields: ["module", "main"]
});
module.exports = require("./main");

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
#!/usr/bin/env node
require = require("esm")(module);
module.exports = require("./main");

View File

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

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

View File

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

View File

@@ -0,0 +1,2 @@
export * from "./preview";
export * from "./build";

View File

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

View File

@@ -1,2 +1 @@
export * from "./preview";
export * from "./build";
export * from "./cli";

View File

@@ -0,0 +1 @@
export { createWebCli } from "./cli";

View File

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

View File

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

View File

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