@nilejs/cli 0.0.7 → 1.0.1
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 +56 -121
- package/dist/index.js +87 -58
- package/package.json +8 -6
- package/template/Dockerfile +1 -2
- package/template/README.md +33 -21
- package/template/SKILL.md +343 -0
- package/template/docker-compose.dev.yml +0 -1
- package/template/docker-compose.yml +4 -24
- package/template/gitignore +16 -0
- package/template/package.json +9 -8
- package/template/src/db/models/tasks.ts +2 -2
- package/template/src/index.ts +40 -9
- package/template/src/services/services.config.ts +3 -20
- package/template/src/services/tasks/{create.ts → actions/create.ts} +12 -8
- package/template/src/services/tasks/actions/delete.ts +27 -0
- package/template/src/services/tasks/{get.ts → actions/get.ts} +10 -6
- package/template/src/services/tasks/{list.ts → actions/list.ts} +5 -3
- package/template/src/services/tasks/{update.ts → actions/update.ts} +15 -8
- package/template/src/services/tasks/index.ts +18 -0
- package/template/tsconfig.json +17 -5
- package/template/src/services/tasks/delete.ts +0 -23
package/README.md
CHANGED
|
@@ -2,187 +2,122 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nilejs/cli)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Create [Nile Js](https://nile-js.github.io/nile/) projects and generate services, actions and schemas.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
bun add -g @nilejs/cli
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
npm install -g @nilejs/cli
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Or use without installing:
|
|
7
|
+
## Create a project
|
|
18
8
|
|
|
19
9
|
```bash
|
|
20
10
|
bunx @nilejs/cli new my-app
|
|
11
|
+
cd my-app
|
|
12
|
+
bun install
|
|
13
|
+
cp .env.example .env
|
|
14
|
+
bun run dev
|
|
21
15
|
```
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
npx @nilejs/cli new my-app
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Commands
|
|
28
|
-
|
|
29
|
-
### `nile new <project-name>`
|
|
30
|
-
|
|
31
|
-
Scaffold a new Nile project. Copies the project template, replaces placeholders with your project name, and prints next steps.
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
nile new my-app
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Output:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
Creating project: my-app
|
|
41
|
-
|
|
42
|
-
Copying project files...
|
|
43
|
-
Configuring project...
|
|
44
|
-
Project "my-app" created.
|
|
17
|
+
The server starts on `http://localhost:8000`. The starter stores its data in an embedded PGlite database, so there is no database server to install. The [getting started guide](https://nile-js.github.io/nile/guide/start/getting-started/) walks through the starter step by step.
|
|
45
18
|
|
|
46
|
-
|
|
19
|
+
The starter includes:
|
|
47
20
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
21
|
+
- `src/index.ts`: server entry point on Bun's native HTTP server, with a logger and an `onBoot` schema push
|
|
22
|
+
- `src/db/`: PGlite and Drizzle client, the `tasks` table and a `taskModel` built with `createModel`
|
|
23
|
+
- `src/services/`: an explicit registry and domain entry point for a `tasks` service with create, list, get, update and delete actions
|
|
24
|
+
- `Dockerfile`, `docker-compose.yml` and `docker-compose.dev.yml` for container deployments
|
|
25
|
+
- `drizzle.config.ts`, `.env.example`, `tsconfig.json` and `package.json`
|
|
53
26
|
|
|
54
|
-
|
|
27
|
+
## Generate code
|
|
55
28
|
|
|
56
|
-
|
|
57
|
-
- `src/db/`, database client, schema, types, and a `tasks` model using `createModel`
|
|
58
|
-
- `src/services/`, a `tasks` service with five CRUD actions
|
|
59
|
-
- `drizzle.config.ts`, `.env.example`, `tsconfig.json`, `package.json`
|
|
29
|
+
Run the generators from the project root. The starter's `gen` script calls the CLI for you.
|
|
60
30
|
|
|
61
|
-
|
|
31
|
+
| Command | Description |
|
|
32
|
+
|---|---|
|
|
33
|
+
| `bun run gen service <name>` | Add a service with a sample action |
|
|
34
|
+
| `bun run gen action <service> <name>` | Add an action to an existing service |
|
|
35
|
+
| `bun run gen action <service> <name> --folder` | Add an action in its own folder, for private helper modules |
|
|
36
|
+
| `bun run gen schema` | Generate Zod schemas and TypeScript types from your actions |
|
|
62
37
|
|
|
63
|
-
|
|
38
|
+
`generate` also has the alias `gen`, so `bunx @nilejs/cli gen service users` works outside the starter.
|
|
64
39
|
|
|
65
|
-
|
|
40
|
+
### Services
|
|
66
41
|
|
|
67
42
|
```bash
|
|
68
|
-
|
|
43
|
+
bun run gen service users
|
|
69
44
|
```
|
|
70
45
|
|
|
71
46
|
Creates:
|
|
72
47
|
|
|
73
|
-
```
|
|
48
|
+
```text
|
|
74
49
|
src/services/users/
|
|
75
|
-
sample.ts
|
|
76
|
-
index.ts
|
|
50
|
+
actions/sample.ts Sample action with a schema, an input type and a named handler
|
|
51
|
+
index.ts Registers the service and its public actions
|
|
77
52
|
```
|
|
78
53
|
|
|
79
|
-
|
|
54
|
+
The CLI then asks whether to register the service in `src/services/services.config.ts`. If you decline, it prints the lines to add yourself.
|
|
80
55
|
|
|
81
|
-
###
|
|
82
|
-
|
|
83
|
-
Alias: `nile gen action <service-name> <action-name>`
|
|
84
|
-
|
|
85
|
-
Generate a new action file in an existing service directory.
|
|
56
|
+
### Actions
|
|
86
57
|
|
|
87
58
|
```bash
|
|
88
|
-
|
|
59
|
+
bun run gen action users get-user
|
|
89
60
|
```
|
|
90
61
|
|
|
91
|
-
Creates `src/services/users/get-user.ts
|
|
62
|
+
Creates `src/services/users/actions/get-user.ts`:
|
|
92
63
|
|
|
93
|
-
```
|
|
94
|
-
import {
|
|
95
|
-
import { Ok } from "
|
|
64
|
+
```ts
|
|
65
|
+
import { createAction } from "@nilejs/nile";
|
|
66
|
+
import { Ok } from "@nilejs/nile/fp";
|
|
96
67
|
import z from "zod";
|
|
97
68
|
|
|
98
69
|
const getUserSchema = z.object({
|
|
99
|
-
//
|
|
70
|
+
// Describe the input this action accepts
|
|
100
71
|
});
|
|
101
72
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
73
|
+
type GetUserInput = z.infer<typeof getUserSchema>;
|
|
74
|
+
|
|
75
|
+
const getUserHandler = async (input: GetUserInput) => {
|
|
76
|
+
// Implement users.get-user here
|
|
77
|
+
return Ok({ result: input });
|
|
105
78
|
};
|
|
106
79
|
|
|
107
|
-
export const getUserAction
|
|
80
|
+
export const getUserAction = createAction<GetUserInput>({
|
|
108
81
|
name: "get-user",
|
|
109
82
|
description: "GetUser action for users",
|
|
110
|
-
handler: getUserHandler,
|
|
111
83
|
validation: getUserSchema,
|
|
84
|
+
handler: getUserHandler,
|
|
112
85
|
});
|
|
113
86
|
```
|
|
114
87
|
|
|
115
|
-
Kebab-case names
|
|
116
|
-
|
|
117
|
-
### `nile generate schema`
|
|
118
|
-
|
|
119
|
-
Alias: `nile gen schema`
|
|
88
|
+
Kebab-case names become camelCase variables and PascalCase types. Register the action by importing it in `src/services/users/index.ts` and adding it to the service’s `actions` list. The root registry imports services, not individual actions.
|
|
120
89
|
|
|
121
|
-
|
|
90
|
+
### Schemas and types
|
|
122
91
|
|
|
123
92
|
```bash
|
|
124
|
-
|
|
93
|
+
bun run gen schema
|
|
125
94
|
```
|
|
126
95
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
Output (default `src/generated/`):
|
|
130
|
-
|
|
131
|
-
- `schemas.ts` — named Zod schema exports (`tasksCreateSchema`, `tasksUpdateSchema`, etc.)
|
|
132
|
-
- `types.ts` — TypeScript types via `z.infer` (`TasksCreatePayload`, `TasksUpdatePayload`, etc.)
|
|
96
|
+
Reads every action's validation schema from `src/services/services.config.ts` and writes:
|
|
133
97
|
|
|
134
|
-
|
|
98
|
+
- `src/generated/schemas.ts`: named Zod schemas, such as `tasksCreateSchema`
|
|
99
|
+
- `src/generated/types.ts`: input types, such as `TasksCreatePayload`
|
|
135
100
|
|
|
136
101
|
| Flag | Description |
|
|
137
102
|
|---|---|
|
|
138
|
-
| `-e, --entry <path>` | Path to services config (
|
|
103
|
+
| `-e, --entry <path>` | Path to the services config (detected by default) |
|
|
139
104
|
| `-o, --output <path>` | Output directory (default: `src/generated`) |
|
|
140
105
|
|
|
141
|
-
Actions without a
|
|
106
|
+
Actions without a validation schema are skipped and listed in the output. Share the generated files with a frontend to validate forms with the same rules as the server.
|
|
142
107
|
|
|
143
|
-
|
|
108
|
+
## Current starter
|
|
144
109
|
|
|
145
|
-
|
|
110
|
+
CLI 1.0.1 targets Nile `^1.0.4`, Zod 4, TypeScript 7 and the Drizzle model helpers. The generated layout passes Nile’s strict boot checks. Database setup returns a Result and prevents startup on failure; shutdown drains Nile’s work before closing PGlite. Helpers are available as `utils.createModel`, and Result helpers come from `@nilejs/nile/fp`.
|
|
146
111
|
|
|
147
|
-
|
|
148
|
-
|---|---|
|
|
149
|
-
| `nile new <name>` | Scaffold a new project |
|
|
150
|
-
| `nile gen service <name>` | Add a service with a demo action |
|
|
151
|
-
| `nile gen action <service> <name>` | Add an action to an existing service |
|
|
152
|
-
| `nile gen schema` | Generate Zod schemas and TypeScript types |
|
|
112
|
+
Existing apps are not rewritten when the CLI updates. For an older starter, add a service entry point at `src/services/<domain>/index.ts`, register its actions there, and import that service from `services.config.ts`. [Nile’s migration notes](https://github.com/nile-js/nile/blob/main/packages/nile/docs/release-migration.md) describe the current error and startup behavior.
|
|
153
113
|
|
|
154
|
-
##
|
|
114
|
+
## Coding assistants
|
|
155
115
|
|
|
156
|
-
|
|
157
|
-
my-app/
|
|
158
|
-
package.json
|
|
159
|
-
tsconfig.json
|
|
160
|
-
drizzle.config.ts
|
|
161
|
-
.env.example
|
|
162
|
-
src/
|
|
163
|
-
index.ts
|
|
164
|
-
db/
|
|
165
|
-
client.ts
|
|
166
|
-
schema.ts
|
|
167
|
-
types.ts
|
|
168
|
-
index.ts
|
|
169
|
-
models/
|
|
170
|
-
tasks.ts
|
|
171
|
-
index.ts
|
|
172
|
-
services/
|
|
173
|
-
services.config.ts
|
|
174
|
-
tasks/
|
|
175
|
-
create.ts
|
|
176
|
-
list.ts
|
|
177
|
-
get.ts
|
|
178
|
-
update.ts
|
|
179
|
-
delete.ts
|
|
180
|
-
```
|
|
116
|
+
The starter includes `SKILL.md` with the current Nile action pattern, service layout, Results, access policies and client calls. Ask your assistant to read it before changing the backend. After updating Nile, use the version shipped at `node_modules/@nilejs/nile/SKILL.md`.
|
|
181
117
|
|
|
182
118
|
## Requirements
|
|
183
119
|
|
|
184
|
-
-
|
|
185
|
-
- The scaffolded project uses Bun as its runtime (`Bun.serve`, `bun run`)
|
|
120
|
+
- [Bun](https://bun.sh) 1.4.2 or later. The starter runs on Bun, and `gen schema` imports your services with Bun.
|
|
186
121
|
|
|
187
122
|
## License
|
|
188
123
|
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { resolve as resolve6 } from "node:path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
+
// package.json
|
|
7
|
+
var version = "1.0.1";
|
|
6
8
|
|
|
7
9
|
// src/commands/generate-action.ts
|
|
8
10
|
import { resolve } from "node:path";
|
|
@@ -87,47 +89,60 @@ var toPascalCase = (str) => {
|
|
|
87
89
|
var generateActionContent = (actionName, serviceName) => {
|
|
88
90
|
const camel = toCamelCase(actionName);
|
|
89
91
|
const pascal = toPascalCase(actionName);
|
|
90
|
-
return `import {
|
|
91
|
-
import { Ok } from "
|
|
92
|
+
return `import { createAction } from "@nilejs/nile";
|
|
93
|
+
import { Ok } from "@nilejs/nile/fp";
|
|
92
94
|
import z from "zod";
|
|
93
95
|
|
|
94
96
|
const ${camel}Schema = z.object({
|
|
95
|
-
//
|
|
97
|
+
// Describe the input this action accepts
|
|
96
98
|
});
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
type ${pascal}Input = z.infer<typeof ${camel}Schema>;
|
|
101
|
+
|
|
102
|
+
const ${camel}Handler = async (input: ${pascal}Input) => {
|
|
103
|
+
// Implement ${serviceName}.${actionName} here
|
|
104
|
+
return Ok({ result: input });
|
|
101
105
|
};
|
|
102
106
|
|
|
103
|
-
export const ${camel}Action
|
|
107
|
+
export const ${camel}Action = createAction<${pascal}Input>({
|
|
104
108
|
name: "${actionName}",
|
|
105
109
|
description: "${pascal} action for ${serviceName}",
|
|
106
|
-
handler: ${camel}Handler,
|
|
107
110
|
validation: ${camel}Schema,
|
|
111
|
+
handler: ${camel}Handler,
|
|
108
112
|
});
|
|
109
113
|
`;
|
|
110
114
|
};
|
|
111
|
-
var generateActionCommand = async (serviceName, actionName) => {
|
|
115
|
+
var generateActionCommand = async (serviceName, actionName, options = {}) => {
|
|
112
116
|
const serviceDir = resolve(process.cwd(), "src/services", serviceName);
|
|
113
117
|
if (!pathExists(serviceDir)) {
|
|
114
118
|
error(`Service "${serviceName}" not found at src/services/${serviceName}/`);
|
|
115
119
|
hint(`Create the service first: nile generate service ${serviceName}`);
|
|
116
120
|
process.exit(1);
|
|
117
121
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
122
|
+
const relative = options.folder ? `actions/${actionName}/${actionName}.ts` : `actions/${actionName}.ts`;
|
|
123
|
+
const actionFile = resolve(serviceDir, relative);
|
|
124
|
+
const existingEntries = [
|
|
125
|
+
actionFile,
|
|
126
|
+
resolve(serviceDir, `${actionName}.ts`),
|
|
127
|
+
resolve(serviceDir, `actions/${actionName}.ts`),
|
|
128
|
+
resolve(serviceDir, `actions/${actionName}/${actionName}.ts`)
|
|
129
|
+
];
|
|
130
|
+
if (existingEntries.some(pathExists)) {
|
|
120
131
|
error(`Action file "${actionName}.ts" already exists in src/services/${serviceName}/`);
|
|
121
132
|
process.exit(1);
|
|
122
133
|
}
|
|
123
134
|
brand();
|
|
124
135
|
const spinner = createSpinner(`Creating action ${actionName}...`);
|
|
125
136
|
await writeFileSafe(actionFile, generateActionContent(actionName, serviceName));
|
|
126
|
-
|
|
137
|
+
if (options.folder) {
|
|
138
|
+
await writeFileSafe(resolve(serviceDir, `actions/${actionName}/index.ts`), `export { ${toCamelCase(actionName)}Action } from "./${actionName}";
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
spinner.stop(`Action created at src/services/${serviceName}/${relative}`);
|
|
127
142
|
const camel = toCamelCase(actionName);
|
|
128
143
|
console.log("");
|
|
129
|
-
hint("
|
|
130
|
-
hint(` import { ${camel}Action } from "
|
|
144
|
+
hint("Import the action in your service index.ts and add it to actions:");
|
|
145
|
+
hint(` import { ${camel}Action } from "./actions/${actionName}";`);
|
|
131
146
|
outro();
|
|
132
147
|
};
|
|
133
148
|
|
|
@@ -143,15 +158,15 @@ var confirmPrompt = (question, defaultYes = true) => {
|
|
|
143
158
|
input: process.stdin,
|
|
144
159
|
output: process.stdout
|
|
145
160
|
});
|
|
146
|
-
return new Promise((
|
|
161
|
+
return new Promise((resolve) => {
|
|
147
162
|
rl.question(` ${question} ${suffix} `, (answer) => {
|
|
148
163
|
rl.close();
|
|
149
164
|
const trimmed = answer.trim().toLowerCase();
|
|
150
165
|
if (trimmed === "") {
|
|
151
|
-
|
|
166
|
+
resolve(defaultYes);
|
|
152
167
|
return;
|
|
153
168
|
}
|
|
154
|
-
|
|
169
|
+
resolve(trimmed === "y" || trimmed === "yes");
|
|
155
170
|
});
|
|
156
171
|
});
|
|
157
172
|
};
|
|
@@ -160,10 +175,10 @@ var inputPrompt = (question) => {
|
|
|
160
175
|
input: process.stdin,
|
|
161
176
|
output: process.stdout
|
|
162
177
|
});
|
|
163
|
-
return new Promise((
|
|
178
|
+
return new Promise((resolve) => {
|
|
164
179
|
rl.question(` ${question} `, (answer) => {
|
|
165
180
|
rl.close();
|
|
166
|
-
|
|
181
|
+
resolve(answer.trim());
|
|
167
182
|
});
|
|
168
183
|
});
|
|
169
184
|
};
|
|
@@ -171,12 +186,14 @@ var inputPrompt = (question) => {
|
|
|
171
186
|
// src/utils/schema-codegen.ts
|
|
172
187
|
import { jsonSchemaToZod } from "json-schema-to-zod";
|
|
173
188
|
var toSchemaExportName = (service, action) => {
|
|
174
|
-
const
|
|
189
|
+
const camelAction = toCamelCase2(action);
|
|
190
|
+
const pascalAction = camelAction.charAt(0).toUpperCase() + camelAction.slice(1);
|
|
175
191
|
return `${toCamelCase2(service)}${pascalAction}Schema`;
|
|
176
192
|
};
|
|
177
193
|
var toTypeExportName = (service, action) => {
|
|
178
194
|
const pascalService = toCamelCase2(service).charAt(0).toUpperCase() + toCamelCase2(service).slice(1);
|
|
179
|
-
const
|
|
195
|
+
const camelAction = toCamelCase2(action);
|
|
196
|
+
const pascalAction = camelAction.charAt(0).toUpperCase() + camelAction.slice(1);
|
|
180
197
|
return `${pascalService}${pascalAction}Payload`;
|
|
181
198
|
};
|
|
182
199
|
var toCamelCase2 = (str) => str.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
@@ -243,7 +260,7 @@ var generateTypesFile = (extraction) => {
|
|
|
243
260
|
""
|
|
244
261
|
];
|
|
245
262
|
for (const entry of entries) {
|
|
246
|
-
lines.push(`export type ${entry.typeName} = z.
|
|
263
|
+
lines.push(`export type ${entry.typeName} = z.input<typeof ${entry.exportName}>;`);
|
|
247
264
|
}
|
|
248
265
|
lines.push("");
|
|
249
266
|
lines.push("/** Map of all services and their action payloads for @nilejs/client */");
|
|
@@ -290,7 +307,7 @@ for (const service of services) {
|
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
try {
|
|
293
|
-
const jsonSchema = z.toJSONSchema(action.validation, { unrepresentable: "any" });
|
|
310
|
+
const jsonSchema = z.toJSONSchema(action.validation, { unrepresentable: "any", io: "input" });
|
|
294
311
|
serviceSchemas[action.name] = jsonSchema;
|
|
295
312
|
} catch {
|
|
296
313
|
skipped.push(\`\${service.name}.\${action.name}\`);
|
|
@@ -313,7 +330,7 @@ var extractSchemas = (configPath, projectRoot) => {
|
|
|
313
330
|
const importPath = resolveImportPath(configPath, projectRoot);
|
|
314
331
|
const scriptContent = buildExtractionScript(importPath);
|
|
315
332
|
const tempScriptPath = resolve2(projectRoot, TEMP_SCRIPT_NAME);
|
|
316
|
-
return new Promise((
|
|
333
|
+
return new Promise((resolve, reject) => {
|
|
317
334
|
writeFile2(tempScriptPath, scriptContent, "utf-8").then(() => {
|
|
318
335
|
execFile("bun", ["run", TEMP_SCRIPT_NAME], { cwd: projectRoot, timeout: 30000 }, (err, stdout, stderr) => {
|
|
319
336
|
unlink(tempScriptPath).catch(() => {});
|
|
@@ -325,7 +342,7 @@ ${message}`));
|
|
|
325
342
|
}
|
|
326
343
|
try {
|
|
327
344
|
const result = JSON.parse(stdout.trim());
|
|
328
|
-
|
|
345
|
+
resolve(result);
|
|
329
346
|
} catch {
|
|
330
347
|
reject(new Error(`Failed to parse extraction output. Raw output:
|
|
331
348
|
${stdout.trim()}`));
|
|
@@ -347,12 +364,12 @@ var DEFAULT_OUTPUT_DIR = "src/generated";
|
|
|
347
364
|
var LEADING_SLASH2 = /^\//;
|
|
348
365
|
var resolveConfigPath = async (projectRoot, entryFlag) => {
|
|
349
366
|
if (entryFlag) {
|
|
350
|
-
const
|
|
351
|
-
if (!pathExists(
|
|
367
|
+
const absolute = resolve3(projectRoot, entryFlag);
|
|
368
|
+
if (!pathExists(absolute)) {
|
|
352
369
|
error(`Config file not found: ${entryFlag}`);
|
|
353
370
|
return null;
|
|
354
371
|
}
|
|
355
|
-
return
|
|
372
|
+
return absolute;
|
|
356
373
|
}
|
|
357
374
|
const detected = findServicesConfig(projectRoot);
|
|
358
375
|
if (detected) {
|
|
@@ -439,39 +456,45 @@ var toPascalCase2 = (str) => {
|
|
|
439
456
|
var generateActionContent2 = (serviceName) => {
|
|
440
457
|
const camel = toCamelCase3(serviceName);
|
|
441
458
|
const pascal = toPascalCase2(serviceName);
|
|
442
|
-
return `import {
|
|
443
|
-
import { Ok } from "
|
|
459
|
+
return `import { createAction } from "@nilejs/nile";
|
|
460
|
+
import { Ok } from "@nilejs/nile/fp";
|
|
444
461
|
import z from "zod";
|
|
445
462
|
|
|
446
463
|
const sample${pascal}Schema = z.object({
|
|
447
464
|
name: z.string().min(1, "Name is required"),
|
|
448
465
|
});
|
|
449
466
|
|
|
450
|
-
|
|
451
|
-
|
|
467
|
+
type Sample${pascal}Input = z.infer<typeof sample${pascal}Schema>;
|
|
468
|
+
|
|
469
|
+
const sample${pascal}Handler = async (input: Sample${pascal}Input) => {
|
|
470
|
+
return Ok({ ${camel}: { name: input.name } });
|
|
452
471
|
};
|
|
453
472
|
|
|
454
|
-
export const sample${pascal}Action
|
|
473
|
+
export const sample${pascal}Action = createAction<Sample${pascal}Input>({
|
|
455
474
|
name: "sample",
|
|
456
475
|
description: "Sample ${serviceName} action",
|
|
457
|
-
handler: sample${pascal}Handler,
|
|
458
476
|
validation: sample${pascal}Schema,
|
|
477
|
+
handler: sample${pascal}Handler,
|
|
459
478
|
});
|
|
460
479
|
`;
|
|
461
480
|
};
|
|
462
481
|
var generateBarrelContent = (serviceName) => {
|
|
463
482
|
const pascal = toPascalCase2(serviceName);
|
|
464
|
-
|
|
483
|
+
const camel = toCamelCase3(serviceName);
|
|
484
|
+
return `import { createService } from "@nilejs/nile";
|
|
485
|
+
import { sample${pascal}Action } from "./actions/sample";
|
|
486
|
+
|
|
487
|
+
export const ${camel}Service = createService({
|
|
488
|
+
name: "${serviceName}",
|
|
489
|
+
description: "${pascal} service",
|
|
490
|
+
actions: [sample${pascal}Action],
|
|
491
|
+
});
|
|
465
492
|
`;
|
|
466
493
|
};
|
|
467
494
|
var generateConfigSnippet = (serviceName) => {
|
|
468
|
-
const
|
|
469
|
-
const importLine = `import {
|
|
470
|
-
const serviceEntry = ` {
|
|
471
|
-
name: "${serviceName}",
|
|
472
|
-
description: "${pascal} service",
|
|
473
|
-
actions: [sample${pascal}Action],
|
|
474
|
-
},`;
|
|
495
|
+
const camel = toCamelCase3(serviceName);
|
|
496
|
+
const importLine = `import { ${camel}Service } from "./${serviceName}";`;
|
|
497
|
+
const serviceEntry = ` ${camel}Service,`;
|
|
475
498
|
return `${importLine}
|
|
476
499
|
|
|
477
500
|
// Add to services array:
|
|
@@ -480,8 +503,8 @@ ${serviceEntry}`;
|
|
|
480
503
|
var autoRegisterService = async (configPath, serviceName) => {
|
|
481
504
|
try {
|
|
482
505
|
let content = await readFileContent(configPath);
|
|
483
|
-
const
|
|
484
|
-
const importLine = `import {
|
|
506
|
+
const camel = toCamelCase3(serviceName);
|
|
507
|
+
const importLine = `import { ${camel}Service } from "./${serviceName}";`;
|
|
485
508
|
const importLines = content.split(`
|
|
486
509
|
`).filter((line) => line.startsWith("import "));
|
|
487
510
|
if (importLines.length === 0) {
|
|
@@ -493,17 +516,18 @@ var autoRegisterService = async (configPath, serviceName) => {
|
|
|
493
516
|
}
|
|
494
517
|
content = content.replace(lastImport, `${lastImport}
|
|
495
518
|
${importLine}`);
|
|
496
|
-
const serviceEntry = ` {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
},`;
|
|
501
|
-
const closingIndex = content.lastIndexOf("];");
|
|
519
|
+
const serviceEntry = ` ${camel}Service,`;
|
|
520
|
+
const wrappedClosingIndex = content.lastIndexOf("]);");
|
|
521
|
+
const plainClosingIndex = content.lastIndexOf("];");
|
|
522
|
+
const closingIndex = Math.max(wrappedClosingIndex, plainClosingIndex);
|
|
502
523
|
if (closingIndex === -1) {
|
|
503
524
|
return false;
|
|
504
525
|
}
|
|
505
|
-
|
|
506
|
-
|
|
526
|
+
const prefix = content.slice(0, closingIndex).trimEnd();
|
|
527
|
+
const separator = prefix.endsWith("[") || prefix.endsWith(",") ? "" : ",";
|
|
528
|
+
content = `${prefix}${separator}
|
|
529
|
+
${serviceEntry}
|
|
530
|
+
${content.slice(closingIndex)}`;
|
|
507
531
|
await writeFileSafe(configPath, content);
|
|
508
532
|
return true;
|
|
509
533
|
} catch {
|
|
@@ -525,7 +549,7 @@ var generateServiceCommand = async (serviceName) => {
|
|
|
525
549
|
brand();
|
|
526
550
|
const spinner = createSpinner(`Creating service ${serviceName}...`);
|
|
527
551
|
await ensureDir(serviceDir);
|
|
528
|
-
await writeFileSafe(resolve4(serviceDir, "sample.ts"), generateActionContent2(serviceName));
|
|
552
|
+
await writeFileSafe(resolve4(serviceDir, "actions/sample.ts"), generateActionContent2(serviceName));
|
|
529
553
|
await writeFileSafe(resolve4(serviceDir, "index.ts"), generateBarrelContent(serviceName));
|
|
530
554
|
spinner.stop(`Service created at src/services/${serviceName}/`);
|
|
531
555
|
const configPath = resolve4(servicesDir, "services.config.ts");
|
|
@@ -557,6 +581,7 @@ ${generateConfigSnippet(serviceName)}
|
|
|
557
581
|
};
|
|
558
582
|
|
|
559
583
|
// src/commands/new.ts
|
|
584
|
+
import { rename } from "node:fs/promises";
|
|
560
585
|
import { resolve as resolve5 } from "node:path";
|
|
561
586
|
import { fileURLToPath } from "node:url";
|
|
562
587
|
var __dirname2 = fileURLToPath(new URL(".", import.meta.url));
|
|
@@ -581,6 +606,10 @@ var newCommand = async (projectName) => {
|
|
|
581
606
|
const spinner = createSpinner(`Creating ${projectName}...`);
|
|
582
607
|
const templateDir = resolveTemplateDir();
|
|
583
608
|
await copyDir(templateDir, targetDir);
|
|
609
|
+
const shippedGitignore = resolve5(targetDir, "gitignore");
|
|
610
|
+
if (pathExists(shippedGitignore)) {
|
|
611
|
+
await rename(shippedGitignore, resolve5(targetDir, ".gitignore"));
|
|
612
|
+
}
|
|
584
613
|
const allFiles = await getFilesRecursive(targetDir);
|
|
585
614
|
const replacements = { "{{projectName}}": projectName };
|
|
586
615
|
for (const filePath of allFiles) {
|
|
@@ -597,9 +626,9 @@ var newCommand = async (projectName) => {
|
|
|
597
626
|
hint("bun run dev");
|
|
598
627
|
console.log("");
|
|
599
628
|
success("Generate with the CLI:");
|
|
600
|
-
hint("
|
|
601
|
-
hint("
|
|
602
|
-
hint("
|
|
629
|
+
hint("bun run gen service <name> Add a new service");
|
|
630
|
+
hint("bun run gen action <service> <name> Add an action to a service");
|
|
631
|
+
hint("bun run gen schema Generate Zod schemas and types");
|
|
603
632
|
outro();
|
|
604
633
|
};
|
|
605
634
|
|
|
@@ -613,10 +642,10 @@ var ensureDepsInstalled = () => {
|
|
|
613
642
|
}
|
|
614
643
|
};
|
|
615
644
|
var program = new Command;
|
|
616
|
-
program.name("nile").description("CLI for the Nile backend framework").version(
|
|
645
|
+
program.name("nile").description("CLI for the Nile backend framework").version(version);
|
|
617
646
|
program.command("new").argument("<project-name>", "Name of the project to create").description("Scaffold a new Nile project").action(newCommand);
|
|
618
647
|
var generate = program.command("generate").alias("gen").description("Generate services and actions").hook("preAction", ensureDepsInstalled);
|
|
619
648
|
generate.command("service").argument("<name>", "Service name (kebab-case recommended)").description("Generate a new service with a demo action").action(generateServiceCommand);
|
|
620
|
-
generate.command("action").argument("<service-name>", "Name of the existing service").argument("<action-name>", "Name of the action to create").description("Generate a new action in an existing service").action(generateActionCommand);
|
|
649
|
+
generate.command("action").argument("<service-name>", "Name of the existing service").argument("<action-name>", "Name of the action to create").description("Generate a new action in an existing service").option("--folder", "Create an action folder for private modules").action(generateActionCommand);
|
|
621
650
|
generate.command("schema").option("-e, --entry <path>", "Path to services config file").option("-o, --output <path>", "Output directory for generated files").description("Generate Zod schemas and TypeScript types from action validations").action(generateSchemaCommand);
|
|
622
651
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nilejs/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "CLI for scaffolding and generating Nile backend projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"dist",
|
|
11
11
|
"template"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "bun run scripts/build.ts",
|
|
15
|
-
"dev": "bun run src/index.ts"
|
|
16
|
-
},
|
|
17
13
|
"dependencies": {
|
|
18
14
|
"commander": "^13.1.0",
|
|
19
15
|
"json-schema-to-zod": "^2.7.0",
|
|
@@ -22,5 +18,11 @@
|
|
|
22
18
|
"devDependencies": {
|
|
23
19
|
"@types/node": "^22.8.1",
|
|
24
20
|
"typescript": "^5"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "bun run scripts/build.ts",
|
|
24
|
+
"dev": "bun run src/index.ts",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"export": "bun run typecheck && bun run build && node dist/index.js --version"
|
|
25
27
|
}
|
|
26
|
-
}
|
|
28
|
+
}
|
package/template/Dockerfile
CHANGED