Skip to content

Commit 3eb349b

Browse files
committed
reorganize cli command, update doc. Needed for future work with dependency visualizer
1 parent 4c60718 commit 3eb349b

File tree

25 files changed

+448
-381
lines changed

25 files changed

+448
-381
lines changed

README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# napi - Next-Level Visual Tooling For API Codebases
44

5-
`napi` is a versatile tool built by NanoAPI and designed to automatically refactor large microlith codebases into smaller, more manageable microservices. With both a powerful CLI and an intuitive UI, `napi` is compatible with all major CI/CD platforms, allowing seamless integration into your development and deployment pipelines.
5+
`napi` is a versatile tool built by NanoAPI and designed to automatically refactor large monolitic codebases into smaller, more manageable microservices. With both a powerful CLI and an intuitive UI, `napi` is compatible with all major CI/CD platforms, allowing seamless integration into your development and deployment pipelines.
66

77
![NanoAPI UI Overview](/media/hero-app.png)
88

@@ -55,27 +55,25 @@ npm install -g @nanoapi.io/napi
5555

5656
```bash
5757
napi init
58-
napi ui
58+
napi split configure
59+
napi split
5960
```
6061

6162
This will initialize a new NanoAPI project and open the UI for inspecting and refactoring your codebase.
6263

63-
## CLI Usage
64+
**Note:** `napi` works off a simple system of annotation. These annotation allow `napi` to be integrated into any existing projects. Annotating a program is needed before using the split functionalities see [Refactoring with Annotations](#refactoring-with-annotations).
6465

65-
`napi` provides the following commands:
66+
## CLI Usage
6667

67-
```bash
68-
init Initialize the NanoAPI project
69-
ui [entrypoint] Open the UI to inspect and refactor the codebase
70-
split <entrypoint> Transform the codebase into microservices
71-
napi <platform> annotate <entrypoint> [--token=<key>] Automatically generate annotations for the codebase via OpenAI
72-
```
68+
`napi --help` will provides an overview off all commands
7369

7470
For more detailed information about the CLI and what each command does, refer to our [CLI guide](https://nanoapi.io/docs/cli/).
7571

7672
## Refactoring with Annotations
7773

78-
You can use annotations to specify how to split your code. Here’s an example:
74+
You can use annotations to specify how to split your code.
75+
Simply add them above blocks of code that is handling or registering an endpoint
76+
Here’s an example:
7977

8078
```typescript
8179
// src/api.js
@@ -91,17 +89,20 @@ app.post("/api/v1/orders", (req, res) => {
9189
});
9290
```
9391

94-
Running `napi split` will generate modular services based on these annotations. You'll have a `Users` service and an `Orders` service, each containing the respective endpoint.
92+
You can view more example in the [examples](/examples/)
93+
94+
Running `napi split` with the following annotations will generate modular services based on these annotations. You'll have a `Users` service and an `Orders` service, each containing the respective endpoint.
9595

9696
## Using the UI
9797

9898
The UI provides an interactive interface to:
9999

100100
- Group and organize endpoints.
101101
- Preview the generated microservices.
102-
- Export refactored code for deployment.
103102

104-
![NanoAPI UI Preview](/media/screenshots/app-ui.png)
103+
```
104+
napi split configure
105+
```
105106

106107
## CI/CD Integration
107108

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Each example is already setup and initialize to use napi.
55
### To view the project on the UI
66

77
```
8-
napi ui
8+
napi split configure
99
```
1010

1111
### Or to split the APIs from the command line directly

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app/src/main.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import "react-toastify/dist/ReactToastify.css";
77
import "@xyflow/react/dist/style.css";
88
import "@radix-ui/themes/styles.css";
99
import "./index.css";
10-
import Index from "./pages/index";
10+
import SplitConfigure from "./pages/splitConfigure";
1111
import { createHashRouter, RouterProvider } from "react-router-dom";
1212
import DefaultLayout from "./layout/default";
1313
import { ToastContainer } from "react-toastify";
1414
import { ThemeContext, ThemeProvider } from "./contexts/ThemeContext";
1515

1616
const router = createHashRouter([
1717
{
18-
path: "/",
18+
path: "/splitConfigure",
1919
element: (
2020
<DefaultLayout>
21-
<Index />
21+
<SplitConfigure />
2222
</DefaultLayout>
2323
),
2424
},

packages/app/src/pages/index.tsx renamed to packages/app/src/pages/splitConfigure.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { syncEndpoints } from "../service/api/sync";
88
import { Endpoint } from "../service/api/types";
99
import { getConfig } from "../service/api/config";
1010

11-
export default function App() {
11+
export default function SplitConfigure() {
1212
const initialized = useRef(false);
1313

1414
const [chartLoading, setChartLoading] = useState<boolean>(false);

packages/cli/src/api/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { json, Router } from "express";
22
import { z } from "zod";
3-
import { napiConfigSchema } from "../config";
3+
import { localConfigSchema } from "../config/localConfig";
44
import { scanSchema, splitSchema, syncSchema } from "./helpers/validation";
55
import { scan } from "./scan";
66
import { split } from "./split";
77
import { sync } from "./sync";
88
import { TelemetryEvents, trackEvent } from "../telemetry";
99

10-
export function getApi(napiConfig: z.infer<typeof napiConfigSchema>) {
10+
export function getApi(napiConfig: z.infer<typeof localConfigSchema>) {
1111
const api = Router();
1212

1313
api.use(json());

packages/cli/src/api/split.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from "path";
22
import { z } from "zod";
33
import DependencyTreeManager from "../dependencyManager/dependencyManager";
4-
import { cleanupOutputDir, createOutputDir } from "../helper/file";
4+
import { cleanupOutputDir, createOutputDir } from "../helpers/file";
55
import { runWithWorker, writeSplitsToDisk } from "../splitRunner/splitRunner";
66
import { splitSchema } from "./helpers/validation";
77

packages/cli/src/api/sync.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import fs from "fs";
44
import DependencyTreeManager from "../dependencyManager/dependencyManager";
55
import AnnotationManager from "../annotationManager";
66
import { getLanguagePlugin } from "../languagesPlugins";
7-
import { replaceIndexesFromSourceCode } from "../helper/file";
7+
import { replaceIndexesFromSourceCode } from "../helpers/file";
88

99
export function sync(payload: z.infer<typeof syncSchema>) {
1010
const dependencyTreeManager = new DependencyTreeManager(

packages/cli/src/cli/handlers/init.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import prompts from "prompts";
4+
import z from "zod";
5+
import {
6+
createConfig,
7+
getConfigFromWorkDir,
8+
localConfigSchema,
9+
} from "../../config/localConfig";
10+
import yargs from "yargs";
11+
import { globalOptions } from "../helpers/options";
12+
import { TelemetryEvents, trackEvent } from "../../telemetry";
13+
14+
async function promptForEntryPointPath(currentPath: string) {
15+
// Read the current directory's contents
16+
const items = fs.readdirSync(currentPath).map((item) => {
17+
const fullPath = path.join(currentPath, item);
18+
const isDir = fs.statSync(fullPath).isDirectory();
19+
return { title: item + (isDir ? "/" : ""), value: fullPath, isDir };
20+
});
21+
22+
// Add an option to go up a directory
23+
if (currentPath !== path.parse(currentPath).root) {
24+
items.unshift({
25+
title: "../",
26+
value: path.join(currentPath, ".."),
27+
isDir: true,
28+
});
29+
}
30+
31+
// Ask user to select a file or directory
32+
const response = await prompts({
33+
type: "select",
34+
name: "selectedPath",
35+
message: `Select the entrypoint file of your application\nNavigate through: ${currentPath}`,
36+
choices: items,
37+
});
38+
39+
// If the user selected a directory, navigate into it
40+
if (
41+
response.selectedPath &&
42+
fs.statSync(response.selectedPath).isDirectory()
43+
) {
44+
return promptForEntryPointPath(response.selectedPath);
45+
}
46+
47+
// Return the file path if a file is selected
48+
return response.selectedPath;
49+
}
50+
51+
async function handler(
52+
argv: yargs.ArgumentsCamelCase<
53+
yargs.InferredOptionTypes<typeof globalOptions>
54+
>,
55+
) {
56+
const startTime = Date.now();
57+
trackEvent(TelemetryEvents.INIT_COMMAND, {
58+
message: "Init command started",
59+
});
60+
try {
61+
if (getConfigFromWorkDir(argv.workdir)) {
62+
const response = await prompts({
63+
type: "confirm",
64+
name: "confirm",
65+
message: `A .napirc file already exists in the selected directory. Do you want to overwrite it?`,
66+
initial: false,
67+
});
68+
if (!response.confirm) {
69+
return;
70+
}
71+
}
72+
73+
const absoluteFilePath = await promptForEntryPointPath(argv.workdir);
74+
const relativeFilePath = path.relative(argv.workdir, absoluteFilePath);
75+
76+
const napiConfig: z.infer<typeof localConfigSchema> = {
77+
entrypoint: relativeFilePath,
78+
out: "napi_dist",
79+
};
80+
81+
createConfig(napiConfig, argv.workdir);
82+
83+
console.info("Successfully created .napirc");
84+
trackEvent(TelemetryEvents.INIT_COMMAND, {
85+
message: "Init command finished",
86+
duration: Date.now() - startTime,
87+
});
88+
} catch (error) {
89+
trackEvent(TelemetryEvents.INIT_COMMAND, {
90+
message: "Init command error",
91+
duration: Date.now() - startTime,
92+
error: error,
93+
});
94+
throw error;
95+
}
96+
}
97+
98+
export default {
99+
command: "init",
100+
describe: "initialize a NanoAPI project",
101+
builder: {},
102+
handler,
103+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import yargs from "yargs";
2+
import { globalOptions } from "../helpers/options";
3+
import { TelemetryEvents, trackEvent } from "../../telemetry";
4+
import { getConfigFromWorkDir } from "../../config/localConfig";
5+
import DependencyTreeManager from "../../dependencyManager/dependencyManager";
6+
import { cleanupOutputDir, createOutputDir } from "../../helpers/file";
7+
import {
8+
runWithWorker,
9+
writeSplitsToDisk,
10+
} from "../../splitRunner/splitRunner";
11+
12+
async function splitCommandHandler(
13+
entrypointPath: string, // Path to the entrypoint file
14+
outputDir: string, // Path to the output directory
15+
) {
16+
console.time("split command");
17+
const dependencyTreeManager = new DependencyTreeManager(entrypointPath);
18+
19+
cleanupOutputDir(outputDir);
20+
createOutputDir(outputDir);
21+
22+
// Get groups from the dependency tree
23+
const groups = dependencyTreeManager.getGroups();
24+
25+
// Process each group for splitting
26+
const splits = groups.map((group, index) =>
27+
runWithWorker(
28+
index,
29+
group,
30+
dependencyTreeManager.entryPointPath,
31+
dependencyTreeManager.getFiles(),
32+
),
33+
);
34+
35+
// Wait for all splits to be processed
36+
const processedSplits = await Promise.all(splits.map(async (split) => split));
37+
38+
writeSplitsToDisk(outputDir, entrypointPath, processedSplits);
39+
40+
console.timeEnd("split command");
41+
}
42+
43+
async function handler(
44+
argv: yargs.ArgumentsCamelCase<
45+
yargs.InferredOptionTypes<typeof globalOptions>
46+
>,
47+
) {
48+
const startTime = Date.now();
49+
trackEvent(TelemetryEvents.SPLIT_COMMAND, {
50+
message: "Split command started",
51+
});
52+
const napiConfig = getConfigFromWorkDir(argv.workdir);
53+
54+
if (!napiConfig) {
55+
console.error("Missing .napirc file in project. Run `napi init` first");
56+
trackEvent(TelemetryEvents.SPLIT_COMMAND, {
57+
message: "Split command failed, missing .napirc file",
58+
duration: Date.now() - startTime,
59+
});
60+
return;
61+
}
62+
63+
try {
64+
await splitCommandHandler(napiConfig.entrypoint, napiConfig.out);
65+
} catch (error) {
66+
trackEvent(TelemetryEvents.SPLIT_COMMAND, {
67+
message: "Split command error",
68+
duration: Date.now() - startTime,
69+
error: error,
70+
});
71+
}
72+
}
73+
74+
export default {
75+
command: "split",
76+
description: "Split a program into multiple ones",
77+
builder: {},
78+
handler,
79+
};

0 commit comments

Comments
 (0)