@aponiajs/cli 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/bin/aponia.ts +6 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.mjs +122 -0
- package/package.json +39 -0
- package/src/arguments.ts +47 -0
- package/src/index.ts +55 -0
- package/src/project-generator.ts +104 -0
- package/templates/application/.env.example +1 -0
- package/templates/application/README.md +36 -0
- package/templates/application/_gitignore +6 -0
- package/templates/application/aponia.json +9 -0
- package/templates/application/package.json +26 -0
- package/templates/application/src/app.controller.spec.ts.tmpl +11 -0
- package/templates/application/src/app.controller.ts.tmpl +12 -0
- package/templates/application/src/app.module.ts.tmpl +9 -0
- package/templates/application/src/app.service.ts.tmpl +8 -0
- package/templates/application/src/main.ts.tmpl +10 -0
- package/templates/application/test/app.e2e-spec.ts.tmpl +12 -0
- package/templates/application/tsconfig.json +20 -0
- package/templates/application/vite.config.ts.tmpl +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @aponiajs/cli
|
|
2
|
+
|
|
3
|
+
The Bun-native Aponia command-line interface.
|
|
4
|
+
|
|
5
|
+
## Create a project
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
aponia new my-api
|
|
9
|
+
aponia n my-api --dry-run
|
|
10
|
+
aponia new my-api --skip-install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The initial CLI implements the standard application generator. Component and
|
|
14
|
+
resource schematics will be added behind the same command architecture after
|
|
15
|
+
their module-registration transforms are safe and testable.
|
package/bin/aponia.ts
ADDED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
//#region src/arguments.d.ts
|
|
2
|
+
interface NewCommandOptions {
|
|
3
|
+
readonly command: "new";
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly dryRun: boolean;
|
|
6
|
+
readonly skipInstall: boolean;
|
|
7
|
+
}
|
|
8
|
+
type CliCommand = NewCommandOptions | {
|
|
9
|
+
readonly command: "help";
|
|
10
|
+
} | {
|
|
11
|
+
readonly command: "version";
|
|
12
|
+
};
|
|
13
|
+
declare function parseArguments(arguments_: readonly string[]): CliCommand;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/project-generator.d.ts
|
|
16
|
+
interface GenerateProjectOptions {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly cwd?: string;
|
|
19
|
+
readonly dryRun?: boolean;
|
|
20
|
+
readonly skipInstall?: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface GenerateProjectResult {
|
|
23
|
+
readonly projectDirectory: string;
|
|
24
|
+
readonly files: readonly string[];
|
|
25
|
+
readonly installed: boolean;
|
|
26
|
+
readonly dryRun: boolean;
|
|
27
|
+
}
|
|
28
|
+
declare function generateProject(options: GenerateProjectOptions): Promise<GenerateProjectResult>;
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/index.d.ts
|
|
31
|
+
declare function runCli(arguments_: readonly string[]): Promise<number>;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { type CliCommand, type GenerateProjectOptions, type GenerateProjectResult, generateProject, parseArguments, runCli };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { mkdir, readdir } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/arguments.ts
|
|
5
|
+
function parseArguments(arguments_) {
|
|
6
|
+
const [command = "help", ...rest] = arguments_;
|
|
7
|
+
if (command === "help" || command === "--help" || command === "-h") return { command: "help" };
|
|
8
|
+
if (command === "version" || command === "--version" || command === "-v") return { command: "version" };
|
|
9
|
+
if (command !== "new" && command !== "n") throw new Error(`Unknown command "${command}".`);
|
|
10
|
+
const name = rest.find((argument) => !argument.startsWith("-"));
|
|
11
|
+
if (!name) throw new Error("Project name is required.");
|
|
12
|
+
const supportedOptions = /* @__PURE__ */ new Set([
|
|
13
|
+
"--dry-run",
|
|
14
|
+
"-d",
|
|
15
|
+
"--skip-install",
|
|
16
|
+
"-s"
|
|
17
|
+
]);
|
|
18
|
+
const unknownOption = rest.find((argument) => argument.startsWith("-") && !supportedOptions.has(argument));
|
|
19
|
+
if (unknownOption) throw new Error(`Unknown option "${unknownOption}".`);
|
|
20
|
+
return {
|
|
21
|
+
command: "new",
|
|
22
|
+
name,
|
|
23
|
+
dryRun: rest.includes("--dry-run") || rest.includes("-d"),
|
|
24
|
+
skipInstall: rest.includes("--skip-install") || rest.includes("-s")
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/project-generator.ts
|
|
29
|
+
const projectNamePattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
30
|
+
async function generateProject(options) {
|
|
31
|
+
validateProjectName(options.name);
|
|
32
|
+
const projectDirectory = join(options.cwd ?? process.cwd(), options.name);
|
|
33
|
+
const templateDirectory = fileURLToPath(new URL("../templates/application", import.meta.url));
|
|
34
|
+
const templateFiles = await listFiles(templateDirectory);
|
|
35
|
+
const relativeFiles = templateFiles.map((file) => outputPath(relative(templateDirectory, file)));
|
|
36
|
+
if (options.dryRun) return {
|
|
37
|
+
projectDirectory,
|
|
38
|
+
files: relativeFiles,
|
|
39
|
+
installed: false,
|
|
40
|
+
dryRun: true
|
|
41
|
+
};
|
|
42
|
+
if (await Bun.file(projectDirectory).exists()) throw new Error(`Target directory "${basename(projectDirectory)}" already exists.`);
|
|
43
|
+
await mkdir(projectDirectory, { recursive: false });
|
|
44
|
+
for (const templateFile of templateFiles) {
|
|
45
|
+
const outputFile = join(projectDirectory, outputPath(relative(templateDirectory, templateFile)));
|
|
46
|
+
const output = (await Bun.file(templateFile).text()).replaceAll("{{PROJECT_NAME}}", options.name);
|
|
47
|
+
await mkdir(dirname(outputFile), { recursive: true });
|
|
48
|
+
await Bun.write(outputFile, output);
|
|
49
|
+
}
|
|
50
|
+
const shouldInstall = !options.skipInstall;
|
|
51
|
+
if (shouldInstall) {
|
|
52
|
+
const exitCode = await Bun.spawn(["bun", "install"], {
|
|
53
|
+
cwd: projectDirectory,
|
|
54
|
+
stdin: "inherit",
|
|
55
|
+
stdout: "inherit",
|
|
56
|
+
stderr: "inherit"
|
|
57
|
+
}).exited;
|
|
58
|
+
if (exitCode !== 0) throw new Error(`Bun install failed with exit code ${exitCode}.`);
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
projectDirectory,
|
|
62
|
+
files: relativeFiles,
|
|
63
|
+
installed: shouldInstall,
|
|
64
|
+
dryRun: false
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function outputPath(templatePath) {
|
|
68
|
+
if (templatePath === "_gitignore") return ".gitignore";
|
|
69
|
+
return templatePath.endsWith(".tmpl") ? templatePath.slice(0, -5) : templatePath;
|
|
70
|
+
}
|
|
71
|
+
function validateProjectName(name) {
|
|
72
|
+
if (!projectNamePattern.test(name)) throw new Error("Project name must use lowercase kebab-case and start with a letter.");
|
|
73
|
+
}
|
|
74
|
+
async function listFiles(directory) {
|
|
75
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
76
|
+
return (await Promise.all(entries.toSorted((left, right) => left.name.localeCompare(right.name)).map(async (entry) => {
|
|
77
|
+
const path = join(directory, entry.name);
|
|
78
|
+
return entry.isDirectory() ? listFiles(path) : [path];
|
|
79
|
+
}))).flat();
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/index.ts
|
|
83
|
+
async function runCli(arguments_) {
|
|
84
|
+
try {
|
|
85
|
+
const command = parseArguments(arguments_);
|
|
86
|
+
if (command.command === "help") {
|
|
87
|
+
console.log(helpText);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
if (command.command === "version") {
|
|
91
|
+
console.log("0.0.0");
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
const result = await generateProject(command);
|
|
95
|
+
if (result.dryRun) {
|
|
96
|
+
console.log(`CREATE ${result.projectDirectory}`);
|
|
97
|
+
for (const file of result.files) console.log(` ${file}`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
console.log(`Created ${command.name}`);
|
|
101
|
+
console.log(`Next: cd ${command.name} && bun run dev`);
|
|
102
|
+
return 0;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
console.error(`Aponia CLI error: ${message}`);
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const helpText = `Aponia CLI
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
aponia new <name> [options]
|
|
113
|
+
aponia n <name> [options]
|
|
114
|
+
|
|
115
|
+
Options:
|
|
116
|
+
-d, --dry-run Report files without writing them
|
|
117
|
+
-s, --skip-install Generate without running bun install
|
|
118
|
+
-h, --help Show command help
|
|
119
|
+
-v, --version Show CLI version
|
|
120
|
+
`;
|
|
121
|
+
//#endregion
|
|
122
|
+
export { generateProject, parseArguments, runCli };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aponiajs/cli",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Bun-native project generator and command-line interface for Aponia.",
|
|
5
|
+
"homepage": "https://github.com/aponiajs/aponiajs#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/aponiajs/aponiajs/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"author": "AponiaJS contributors",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/aponiajs/aponiajs.git",
|
|
14
|
+
"directory": "packages/cli"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"aponia": "./bin/aponia.ts"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin",
|
|
21
|
+
"dist",
|
|
22
|
+
"src",
|
|
23
|
+
"templates"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./dist/index.mjs",
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "bunx --bun vp pack",
|
|
35
|
+
"test": "bun test",
|
|
36
|
+
"check": "bunx --bun vp check",
|
|
37
|
+
"prepublishOnly": "bun run build"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/arguments.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface NewCommandOptions {
|
|
2
|
+
readonly command: "new";
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly dryRun: boolean;
|
|
5
|
+
readonly skipInstall: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type CliCommand =
|
|
9
|
+
| NewCommandOptions
|
|
10
|
+
| { readonly command: "help" }
|
|
11
|
+
| { readonly command: "version" };
|
|
12
|
+
|
|
13
|
+
export function parseArguments(arguments_: readonly string[]): CliCommand {
|
|
14
|
+
const [command = "help", ...rest] = arguments_;
|
|
15
|
+
|
|
16
|
+
if (command === "help" || command === "--help" || command === "-h") {
|
|
17
|
+
return { command: "help" };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
21
|
+
return { command: "version" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (command !== "new" && command !== "n") {
|
|
25
|
+
throw new Error(`Unknown command "${command}".`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const name = rest.find((argument) => !argument.startsWith("-"));
|
|
29
|
+
if (!name) {
|
|
30
|
+
throw new Error("Project name is required.");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const supportedOptions = new Set(["--dry-run", "-d", "--skip-install", "-s"]);
|
|
34
|
+
const unknownOption = rest.find(
|
|
35
|
+
(argument) => argument.startsWith("-") && !supportedOptions.has(argument),
|
|
36
|
+
);
|
|
37
|
+
if (unknownOption) {
|
|
38
|
+
throw new Error(`Unknown option "${unknownOption}".`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
command: "new",
|
|
43
|
+
name,
|
|
44
|
+
dryRun: rest.includes("--dry-run") || rest.includes("-d"),
|
|
45
|
+
skipInstall: rest.includes("--skip-install") || rest.includes("-s"),
|
|
46
|
+
};
|
|
47
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { parseArguments } from "./arguments.ts";
|
|
2
|
+
import { generateProject } from "./project-generator.ts";
|
|
3
|
+
|
|
4
|
+
export { parseArguments, type CliCommand } from "./arguments.ts";
|
|
5
|
+
export {
|
|
6
|
+
generateProject,
|
|
7
|
+
type GenerateProjectOptions,
|
|
8
|
+
type GenerateProjectResult,
|
|
9
|
+
} from "./project-generator.ts";
|
|
10
|
+
|
|
11
|
+
export async function runCli(arguments_: readonly string[]): Promise<number> {
|
|
12
|
+
try {
|
|
13
|
+
const command = parseArguments(arguments_);
|
|
14
|
+
|
|
15
|
+
if (command.command === "help") {
|
|
16
|
+
console.log(helpText);
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (command.command === "version") {
|
|
21
|
+
console.log("0.0.0");
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const result = await generateProject(command);
|
|
26
|
+
if (result.dryRun) {
|
|
27
|
+
console.log(`CREATE ${result.projectDirectory}`);
|
|
28
|
+
for (const file of result.files) {
|
|
29
|
+
console.log(` ${file}`);
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`Created ${command.name}`);
|
|
35
|
+
console.log(`Next: cd ${command.name} && bun run dev`);
|
|
36
|
+
return 0;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
39
|
+
console.error(`Aponia CLI error: ${message}`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const helpText = `Aponia CLI
|
|
45
|
+
|
|
46
|
+
Usage:
|
|
47
|
+
aponia new <name> [options]
|
|
48
|
+
aponia n <name> [options]
|
|
49
|
+
|
|
50
|
+
Options:
|
|
51
|
+
-d, --dry-run Report files without writing them
|
|
52
|
+
-s, --skip-install Generate without running bun install
|
|
53
|
+
-h, --help Show command help
|
|
54
|
+
-v, --version Show CLI version
|
|
55
|
+
`;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { mkdir, readdir } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export interface GenerateProjectOptions {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly cwd?: string;
|
|
8
|
+
readonly dryRun?: boolean;
|
|
9
|
+
readonly skipInstall?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GenerateProjectResult {
|
|
13
|
+
readonly projectDirectory: string;
|
|
14
|
+
readonly files: readonly string[];
|
|
15
|
+
readonly installed: boolean;
|
|
16
|
+
readonly dryRun: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const projectNamePattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
20
|
+
|
|
21
|
+
export async function generateProject(
|
|
22
|
+
options: GenerateProjectOptions,
|
|
23
|
+
): Promise<GenerateProjectResult> {
|
|
24
|
+
validateProjectName(options.name);
|
|
25
|
+
|
|
26
|
+
const workingDirectory = options.cwd ?? process.cwd();
|
|
27
|
+
const projectDirectory = join(workingDirectory, options.name);
|
|
28
|
+
const templateDirectory = fileURLToPath(new URL("../templates/application", import.meta.url));
|
|
29
|
+
const templateFiles = await listFiles(templateDirectory);
|
|
30
|
+
const relativeFiles = templateFiles.map((file) => outputPath(relative(templateDirectory, file)));
|
|
31
|
+
|
|
32
|
+
if (options.dryRun) {
|
|
33
|
+
return {
|
|
34
|
+
projectDirectory,
|
|
35
|
+
files: relativeFiles,
|
|
36
|
+
installed: false,
|
|
37
|
+
dryRun: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (await Bun.file(projectDirectory).exists()) {
|
|
42
|
+
throw new Error(`Target directory "${basename(projectDirectory)}" already exists.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await mkdir(projectDirectory, { recursive: false });
|
|
46
|
+
for (const templateFile of templateFiles) {
|
|
47
|
+
const relativeFile = outputPath(relative(templateDirectory, templateFile));
|
|
48
|
+
const outputFile = join(projectDirectory, relativeFile);
|
|
49
|
+
const template = await Bun.file(templateFile).text();
|
|
50
|
+
const output = template.replaceAll("{{PROJECT_NAME}}", options.name);
|
|
51
|
+
|
|
52
|
+
await mkdir(dirname(outputFile), { recursive: true });
|
|
53
|
+
await Bun.write(outputFile, output);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const shouldInstall = !options.skipInstall;
|
|
57
|
+
if (shouldInstall) {
|
|
58
|
+
const process = Bun.spawn(["bun", "install"], {
|
|
59
|
+
cwd: projectDirectory,
|
|
60
|
+
stdin: "inherit",
|
|
61
|
+
stdout: "inherit",
|
|
62
|
+
stderr: "inherit",
|
|
63
|
+
});
|
|
64
|
+
const exitCode = await process.exited;
|
|
65
|
+
if (exitCode !== 0) {
|
|
66
|
+
throw new Error(`Bun install failed with exit code ${exitCode}.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
projectDirectory,
|
|
72
|
+
files: relativeFiles,
|
|
73
|
+
installed: shouldInstall,
|
|
74
|
+
dryRun: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function outputPath(templatePath: string): string {
|
|
79
|
+
if (templatePath === "_gitignore") {
|
|
80
|
+
return ".gitignore";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return templatePath.endsWith(".tmpl") ? templatePath.slice(0, -5) : templatePath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validateProjectName(name: string): void {
|
|
87
|
+
if (!projectNamePattern.test(name)) {
|
|
88
|
+
throw new Error("Project name must use lowercase kebab-case and start with a letter.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function listFiles(directory: string): Promise<string[]> {
|
|
93
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
94
|
+
const files = await Promise.all(
|
|
95
|
+
entries
|
|
96
|
+
.toSorted((left, right) => left.name.localeCompare(right.name))
|
|
97
|
+
.map(async (entry) => {
|
|
98
|
+
const path = join(directory, entry.name);
|
|
99
|
+
return entry.isDirectory() ? listFiles(path) : [path];
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return files.flat();
|
|
104
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PORT=3000
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Bun-first Aponia application generated by `@aponiajs/cli`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
bun run dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The generated request flow is:
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
main.ts
|
|
14
|
+
-> AponiaFactory
|
|
15
|
+
-> AppModule
|
|
16
|
+
-> AppController
|
|
17
|
+
-> AppService
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Open `http://localhost:3000/` after starting the application.
|
|
21
|
+
|
|
22
|
+
The starter follows Nest standard mode:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
src/
|
|
26
|
+
|-- app.controller.spec.ts
|
|
27
|
+
|-- app.controller.ts
|
|
28
|
+
|-- app.module.ts
|
|
29
|
+
|-- app.service.ts
|
|
30
|
+
`-- main.ts
|
|
31
|
+
test/
|
|
32
|
+
`-- app.e2e-spec.ts
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Add later features under `src/<feature>`, with the feature controller, module,
|
|
36
|
+
service, DTOs, entities, and unit tests kept together.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun --watch src/main.ts",
|
|
8
|
+
"start": "bun src/main.ts",
|
|
9
|
+
"build": "bun build ./src/main.ts --outdir ./dist --target bun",
|
|
10
|
+
"test": "bun test",
|
|
11
|
+
"test:e2e": "bun test ./test/app.e2e-spec.ts",
|
|
12
|
+
"check": "vp check"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@aponiajs/common": "latest",
|
|
16
|
+
"@aponiajs/core": "latest",
|
|
17
|
+
"@aponiajs/platform-elysia": "latest",
|
|
18
|
+
"elysia": "^1.4.29"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "^1.3.14",
|
|
22
|
+
"typescript": "^7.0.2",
|
|
23
|
+
"vite-plus": "^0.2.4"
|
|
24
|
+
},
|
|
25
|
+
"packageManager": "bun@1.3.14"
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { AppController } from "./app.controller.ts";
|
|
3
|
+
import { AppService } from "./app.service.ts";
|
|
4
|
+
|
|
5
|
+
describe("AppController", () => {
|
|
6
|
+
test("returns the application greeting", () => {
|
|
7
|
+
const controller = new AppController(new AppService());
|
|
8
|
+
|
|
9
|
+
expect(controller.getGreeting()).toBe("Hello from {{PROJECT_NAME}}!");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Controller, Get } from "@aponiajs/common";
|
|
2
|
+
import { AppService } from "./app.service.ts";
|
|
3
|
+
|
|
4
|
+
@Controller()
|
|
5
|
+
export class AppController {
|
|
6
|
+
constructor(private readonly appService: AppService) {}
|
|
7
|
+
|
|
8
|
+
@Get()
|
|
9
|
+
getGreeting(): string {
|
|
10
|
+
return this.appService.getGreeting();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AponiaFactory } from "@aponiajs/platform-elysia";
|
|
2
|
+
import { AppModule } from "./app.module.ts";
|
|
3
|
+
|
|
4
|
+
async function bootstrap(): Promise<void> {
|
|
5
|
+
const application = await AponiaFactory.create(AppModule);
|
|
6
|
+
const port = Number(Bun.env.PORT ?? 3000);
|
|
7
|
+
await application.listen(port);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
await bootstrap();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { AponiaFactory } from "@aponiajs/platform-elysia";
|
|
3
|
+
import { AppModule } from "../src/app.module.ts";
|
|
4
|
+
|
|
5
|
+
test("GET / routes through AppController and AppService", async () => {
|
|
6
|
+
const application = await AponiaFactory.create(AppModule, { logger: false });
|
|
7
|
+
const response = await application.handle(new Request("http://localhost/"));
|
|
8
|
+
|
|
9
|
+
expect(response.status).toBe(200);
|
|
10
|
+
expect(await response.text()).toBe("Hello from {{PROJECT_NAME}}!");
|
|
11
|
+
await application.close();
|
|
12
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"module": "Preserve",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"types": ["bun"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"experimentalDecorators": true,
|
|
11
|
+
"emitDecoratorMetadata": true,
|
|
12
|
+
"noUnusedLocals": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
"skipLibCheck": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src", "test", "vite.config.ts"]
|
|
20
|
+
}
|