Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support context file location in repository #567

Merged
merged 23 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aca9e25
feat: support context file location in repository
aeworxet May 10, 2023
ee17356
feat: support context file location in repository
aeworxet May 22, 2023
7911644
feat: support context file location in repository
aeworxet May 26, 2023
da91de3
feat: support context file location in repository
aeworxet Jun 16, 2023
5f6d08c
feat: support context file location in repository
aeworxet Jun 27, 2023
24f6af9
feat: support context file location in repository
aeworxet Jul 4, 2023
2a6cca1
feat: support context file location in repository
aeworxet Jul 4, 2023
b5faf8f
feat: support context file location in repository
aeworxet Jul 4, 2023
69f7df6
feat: support context file location in repository
aeworxet Jul 4, 2023
b6851a4
feat: support context file location in repository
aeworxet Jul 4, 2023
de85375
feat: support context file location in repository
aeworxet Jul 4, 2023
f8870fb
feat: support context file location in repository
aeworxet Jul 4, 2023
f3f40d0
feat: support context file location in repository
aeworxet Jul 4, 2023
fe20d89
feat: support context file location in repository
aeworxet Jul 4, 2023
c4f8156
feat: support context file location in repository
aeworxet Jul 4, 2023
0c9cd46
feat: support context file location in repository
aeworxet Jul 4, 2023
fb9bba5
feat: support context file location in repository
aeworxet Jul 4, 2023
ed9cc5e
feat: support context file location in repository
aeworxet Jul 4, 2023
a2eeb7a
feat: support context file location in repository
aeworxet Jul 4, 2023
6d89ea6
feat: support context file location in repository
aeworxet Jul 4, 2023
d51bc4e
feat: support context file location in repository
aeworxet Jul 4, 2023
4575a70
feat: support context file location in repository
aeworxet Jul 4, 2023
cc3b1c9
Merge branch 'master' into make-contexts-shareable-#38
derberg Aug 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions docs/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
title: 'Context concept'
weight: 50
---

## Overview

AsyncAPI CLI provides functionality called `context`. It's purpose is to help to work with AsyncAPI CLI in large projects where you do not have just one service exposing AsyncAPI document, but multiple.

Event driven architecture involves multiple actors, subscribers and publishers. One time you want to validate document **A** and the other time you want to generate models from document **B**. Every time you do it, you need to provide to AsyncAPI CLI the location of the AsyncAPI document, which might be time consuming. You can workaround it with aliases in bash profiles or with other solutions but it is better to use `context` feature, as you can then store it in your repository and share with other team members.

In short it means that for example instead of writing `asyncapi validate /some/folder/my-asyncapi.yml` you can create a context called `myasync` that will be an alias for and point to `/some/folder/my-asyncapi.yml`. This way next time you use the CLI you can do `asyncapi validate myasync`.

## Context File location

You can have a global context for your workstation, and a project specific context.

If your use case is that you work with multiple repositories, you might want to use a global context. The `.asyncapi-cli` context file is then located in your home directory. You can also store your custom `.asyncapi-cli` file in your project with custom configuration. This way when you run `asyncapi config context add` inside your project, the new context is added to the context file under your project.

## How to add context to a project

### Manually
- Create file `.asyncapi-cli` containing [minimal empty context file](#minimalEmptyContextFile) in:
- current directory
- root of current repository
- user's home directory

### Using CLI's `init` command

`asyncapi config context init [CONTEXT-FILE-PATH]`

Where `[CONTEXT-FILE-PATH]` instructs CLI what directory should the file `.asyncapi-cli` containing [minimal empty context file](#minimalEmptyContextFile) be created in:
- current directory: `asyncapi config context init .` (default)
- root of current repository: `asyncapi config context init ./`
- user's home directory: `asyncapi config context init ~`

(if `[CONTEXT-FILE-PATH]` is omitted, empty context file is created in current directory)

Make use of newly created `.asyncapi-cli` by executing command:

`asyncapi config context add [CONTEXT-NAME] [SPEC-FILE-PATH]`

### Setup example in a real project

Below you can see an example of context setup for [Event Driven Flight status notification service](https://github.com/amadeus4dev-examples/amadeus-async-flight-status/tree/ff433b6d320a3a6a2499976cbf0782353bc57c16) of the [Amadeus Airline Platform](https://amadeus.com/en/industries/airlines/airline-platform), with multiple microservices and their AsyncAPI documents.

```bash
# One-time initialization of '.asyncapi-cli' file
(main)$ asyncapi config context init
Initialized context /amadeus-async-flight-status/.asyncapi-cli

# Adding first context
(main)$ asyncapi config context add subscriber subscriber/asyncapi.yaml
Added context "subscriber".
You can set it as your current context: asyncapi config context use subscriber
You can use this context when needed by passing subscriber as a parameter: asyncapi validate subscriber

# Adding more contexts
(main)$ asyncapi config context add notifier notifier/asyncapi.yaml
Added context "notifier".
You can set it as your current context: asyncapi config context use notifier
You can use this context when needed by passing notifier as a parameter: asyncapi validate notifier

(main)$ asyncapi config context add monitor monitor/asyncapi.yaml
Added context "monitor".
You can set it as your current context: asyncapi config context use monitor
You can use this context when needed by passing monitor as a parameter: asyncapi validate monitor

# Setting monitor as default context
(main)$ asyncapi config context use monitor
monitor is set as current

# Now you do not even have to remember the context name, and default 'monitor/asyncapi.yaml' will be validated
(main)$ asyncapi validate
File monitor/asyncapi.yaml is valid but has (itself and/or referenced documents) governance issues.
monitor/asyncapi.yaml
1:1 warning asyncapi-defaultContentType AsyncAPI document should have "defaultContentType" field.
1:1 warning asyncapi-id AsyncAPI document should have "id" field.
1:1 warning asyncapi2-tags AsyncAPI object should have non-empty "tags" array.
1:11 information asyncapi-latest-version The latest version of AsyncAPi is not used. It is recommended update to the "2.6.0" version. asyncapi
2:6 warning asyncapi-info-contact Info object should have "contact" object. info
19:15 warning asyncapi2-operation-operationId Operation should have an "operationId" field defined. channels.flight/update.subscribe
26:13 warning asyncapi2-operation-operationId Operation should have an "operationId" field defined. channels.flight/queue.publish
✖ 7 problems (0 errors, 6 warnings, 1 info, 0 hints)

# You can now use context name when running AsyncAPI commands, no need to remember file location like 'notifier/asyncapi.yaml'
(main)$ asyncapi validate notifier
File notifier/asyncapi.yaml is valid but has (itself and/or referenced documents) governance issues.
notifier/asyncapi.yaml
1:1 warning asyncapi-defaultContentType AsyncAPI document should have "defaultContentType" field.
1:1 warning asyncapi-id AsyncAPI document should have "id" field.
1:1 warning asyncapi2-tags AsyncAPI object should have non-empty "tags" array.
1:11 information asyncapi-latest-version The latest version of AsyncAPi is not used. It is recommended update to the "2.6.0" version. asyncapi
2:6 warning asyncapi-info-contact Info object should have "contact" object. info
18:13 warning asyncapi2-operation-operationId Operation should have an "operationId" field defined. channels.flight/update.publish
✖ 6 problems (0 errors, 5 warnings, 1 info, 0 hints)

# Switch default context
(main)$ asyncapi config context use notifier
notifier is set as current

# List all contexts
(main)$ asyncapi config context list
monitor: monitor/asyncapi.yaml
notifier: notifier/asyncapi.yaml
subscriber: subscriber/asyncapi.yaml
```

## Context File structure

### Fixed Fields

Field Name | Type | Description
---|:---:|---
current | `string` | An optional string value representing one of context names, which is used as default in CLI. Default means you can run CLI commands without providing context name, like `asyncapi validate`, and it will run against the default - `current` - context.
store | [Store Object](#storeObject) | **REQUIRED**. Map of filesystem paths to target AsyncAPI documents.

### <a name="storeObject"></a>Store Object

Map of filesystem paths to target AsyncAPI documents.

**Patterned Fields**

Field Pattern | Type | Description
---|:---:|---
{contextName} | `string` | An optional string value representing filesystem path to the target AsyncAPI document.

### <a name="minimalEmptyContextFile"></a>Minimal Empty Context File
Raw JSON:
```
{
"store": {}
}
```
Stringified JSON:
```
{"store":{}}
```

### Context File Example

aeworxet marked this conversation as resolved.
Show resolved Hide resolved
Example of a context file for [Event Driven Flight status notification service](https://github.com/amadeus4dev-examples/amadeus-async-flight-status/tree/ff433b6d320a3a6a2499976cbf0782353bc57c16) of the [Amadeus Airline Platform](https://amadeus.com/en/industries/airlines/airline-platform), with multiple microservices and their AsyncAPI documents:
```
{
"current": "monitor",
"store": {
"monitor": "monitor/asyncapi.yaml",
"notifier": "notifier/asyncapi.yaml",
"subscriber": "subscriber/asyncapi.yaml"
}
}
```

## More context related CLI options

All commands for managing contexts are available under `asyncapi config context` [CLI commands group](usage#asyncapi-config-context).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
"release": "semantic-release",
"pretest": "npm run build",
"test": "npm run test:unit",
"test:unit": "cross-env NODE_ENV=development TEST=1 CONTEXT_FILENAME=\"./test.asyncapi\" CONTEXT_FILE_PATH=\"./\" jest --coverage -i",
"test:unit": "cross-env NODE_ENV=development TEST=1 CUSTOM_CONTEXT_FILENAME=\"test.asyncapi-cli\" CUSTOM_CONTEXT_FILE_LOCATION=\"\" jest --coverage -i",
"get-version": "echo $npm_package_version"
},
"types": "lib/index.d.ts"
Expand Down
40 changes: 32 additions & 8 deletions src/commands/config/context/add.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
import {Flags} from '@oclif/core';
import { Flags } from '@oclif/core';
import Command from '../../../base';
import { addContext } from '../../../models/Context';
import {
MissingContextFileError,
ContextFileWrongFormatError,
} from '../../../errors/context-error';

export default class ContextAdd extends Command {
static description='Add or modify a context in the store';
static description = 'Add a context to the store';
static flags = {
help: Flags.help({char: 'h'})
help: Flags.help({ char: 'h' }),
Souvikns marked this conversation as resolved.
Show resolved Hide resolved
};

static args = [
{name: 'context-name', description: 'context name', required: true},
{name: 'spec-file-path', description: 'file path of the spec file', required: true}
{ name: 'context-name', description: 'context name', required: true },
{
name: 'spec-file-path',
description: 'file path of the spec file',
required: true,
},
];

async run() {
const {args} = await this.parse(ContextAdd);
const { args } = await this.parse(ContextAdd);
const contextName = args['context-name'];
const specFilePath = args['spec-file-path'];

await addContext(contextName, specFilePath);
this.log(`Added context "${contextName}".\n\nYou can set it as your current context: asyncapi config context use ${contextName}\nYou can use this context when needed by passing ${contextName} as a parameter: asyncapi validate ${contextName}`);
try {
await addContext(contextName, specFilePath);
this.log(
`Added context "${contextName}".\n\nYou can set it as your current context: asyncapi config context use ${contextName}\nYou can use this context when needed by passing ${contextName} as a parameter: asyncapi validate ${contextName}`
);
} catch (e) {
if (
e instanceof (MissingContextFileError || ContextFileWrongFormatError)
) {
this.log(
'You have no context file configured. Run "asyncapi config context init" to initialize it.'
);
return;
}
{
throw e;
}
}
}
}
48 changes: 42 additions & 6 deletions src/commands/config/context/current.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,51 @@
import { Flags } from '@oclif/core';
import Command from '../../../base';
import { getCurrentContext } from '../../../models/Context';
import { getCurrentContext, CONTEXT_FILE_PATH } from '../../../models/Context';
import {
MissingContextFileError,
ContextFileWrongFormatError,
ContextFileEmptyError,
ContextNotFoundError,
} from '../../../errors/context-error';

export default class ContextCurrent extends Command {
static description='Shows the current context that is being used';
static flags={
help: Flags.help({char: 'h'})
static description = 'Shows the current context that is being used';
static flags = {
help: Flags.help({ char: 'h' }),
};

async run() {
const { current, context } = await getCurrentContext();
this.log(`${current}: ${context}`);
let fileContent;

try {
fileContent = await getCurrentContext();
} catch (e) {
if (
e instanceof (MissingContextFileError || ContextFileWrongFormatError)
) {
this.log(
'You have no context file configured. Run "asyncapi config context init" to initialize it.'
);
return;
} else if (e instanceof ContextFileEmptyError) {
this.log(`Context file "${CONTEXT_FILE_PATH}" is empty.`);
return;
} else if (
e instanceof ContextNotFoundError ||
(fileContent && !fileContent.current)
) {
this.log(
'No context is set as current. Run "asyncapi config context" to see all available options.'
);
return;
}
{
throw e;
}
}

if (fileContent) {
this.log(`${fileContent.current}: ${fileContent.context}`);
}
}
}
52 changes: 52 additions & 0 deletions src/commands/config/context/edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Flags } from '@oclif/core';
import Command from '../../../base';
import { editContext, CONTEXT_FILE_PATH } from '../../../models/Context';
import {
MissingContextFileError,
ContextFileWrongFormatError,
ContextFileEmptyError,
} from '../../../errors/context-error';

export default class ContextEdit extends Command {
static description = 'Edit a context in the store';
static flags = {
help: Flags.help({ char: 'h' }),
};

static args = [
{ name: 'context-name', description: 'context name', required: true },
{
name: 'new-spec-file-path',
description: 'new file path of the spec file',
required: true,
},
];

async run() {
const { args } = await this.parse(ContextEdit);
const contextName = args['context-name'];
const newSpecFilePath = args['new-spec-file-path'];

try {
await editContext(contextName, newSpecFilePath);
this.log(
`Edited context "${contextName}".\n\nYou can set it as your current context: asyncapi config context use ${contextName}\nYou can use this context when needed by passing ${contextName} as a parameter: asyncapi validate ${contextName}`
);
} catch (e) {
if (
e instanceof (MissingContextFileError || ContextFileWrongFormatError)
) {
this.log(
'You have no context file configured. Run "asyncapi config context init" to initialize it.'
);
return;
} else if (e instanceof ContextFileEmptyError) {
this.log(`Context file "${CONTEXT_FILE_PATH}" is empty.`);
return;
}
{
throw e;
}
}
}
}
3 changes: 3 additions & 0 deletions src/commands/config/context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { loadHelpClass } from '@oclif/core';
import Command from '../../../base';

export default class Context extends Command {
static description =
'Manage short aliases for full paths to AsyncAPI documents';

async run() {
const Help = await loadHelpClass(this.config);
const help = new Help(this.config);
Expand Down
31 changes: 31 additions & 0 deletions src/commands/config/context/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Flags } from '@oclif/core';
import Command from '../../../base';
import { initContext } from '../../../models/Context';

export default class ContextInit extends Command {
static description = 'Initialize context';
static flags = {
help: Flags.help({ char: 'h' }),
};

static contextFilePathMessage = `Specify directory in which context file should be created:
- current directory : asyncapi config context init . (default)
- root of current repository : asyncapi config context init ./
- user's home directory : asyncapi config context init ~`;

static args = [
{
name: 'context-file-path',
description: `${ContextInit.contextFilePathMessage}`,
required: false,
},
];

async run() {
const { args } = await this.parse(ContextInit);
const contextFilePath = args['context-file-path'];

const contextWritePath = await initContext(contextFilePath);
this.log(`Initialized context ${contextWritePath}`);
}
}
Loading