@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 CHANGED
@@ -2,187 +2,122 @@
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@nilejs/cli.svg)](https://www.npmjs.com/package/@nilejs/cli)
4
4
 
5
- CLI for scaffolding and generating [Nile](https://github.com/nile-js/nile/tree/main/packages/nile) backend projects.
5
+ Create [Nile Js](https://nile-js.github.io/nile/) projects and generate services, actions and schemas.
6
6
 
7
- ## Install
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
- ```bash
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
- Next steps:
19
+ The starter includes:
47
20
 
48
- cd my-app
49
- bun install
50
- cp .env.example .env
51
- bun run dev
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
- The scaffolded project includes:
27
+ ## Generate code
55
28
 
56
- - `src/index.ts`, server entry with PGLite and Drizzle
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
- ### `nile generate service <name>`
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
- Alias: `nile gen service <name>`
38
+ `generate` also has the alias `gen`, so `bunx @nilejs/cli gen service users` works outside the starter.
64
39
 
65
- Generate a new service directory under `src/services/` with a demo action and barrel export. Run this from the project root.
40
+ ### Services
66
41
 
67
42
  ```bash
68
- nile gen service users
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 # Demo action with Zod schema, handler, and createAction
76
- index.ts # Barrel export
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
- After creating the files, the CLI asks whether to auto-register the service in `src/services/services.config.ts`. If you accept, it adds the import and service entry. If you decline, it prints the snippet to add manually.
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
- ### `nile generate action <service-name> <action-name>`
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
- nile gen action users get-user
59
+ bun run gen action users get-user
89
60
  ```
90
61
 
91
- Creates `src/services/users/get-user.ts` with:
62
+ Creates `src/services/users/actions/get-user.ts`:
92
63
 
93
- ```typescript
94
- import { type Action, createAction } from "@nilejs/nile";
95
- import { Ok } from "slang-ts";
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
- // Define your validation schema here
70
+ // Describe the input this action accepts
100
71
  });
101
72
 
102
- const getUserHandler = async (data: Record<string, unknown>) => {
103
- // Implement your users.get-user logic here
104
- return Ok({ result: data });
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: Action = createAction({
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 are converted to camelCase for variables and PascalCase for types.
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
- Extract Zod validation schemas from your action definitions and generate TypeScript files with schema exports and inferred types.
90
+ ### Schemas and types
122
91
 
123
92
  ```bash
124
- nile gen schema
93
+ bun run gen schema
125
94
  ```
126
95
 
127
- The command auto-detects `src/services/services.config.ts`. If the file is not found, it prompts for the path. It spawns a `bun` subprocess to import your services config, extracts JSON Schema from each action's validation field, and converts them back to Zod code strings.
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
- Options:
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 (auto-detected by default) |
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 `validation` schema are skipped and listed in the CLI output.
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
- Requires [Bun](https://bun.sh) installed for the extraction subprocess.
108
+ ## Current starter
144
109
 
145
- ### Quick Reference
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
- | Command | Description |
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
- ## Generated Project Structure
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
- - Node.js 18+ or Bun
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 { type Action, createAction } from "@nilejs/nile";
91
- import { Ok } from "slang-ts";
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
- // Define your validation schema here
97
+ // Describe the input this action accepts
96
98
  });
97
99
 
98
- const ${camel}Handler = async (data: Record<string, unknown>) => {
99
- // Implement your ${serviceName}.${actionName} logic here
100
- return Ok({ result: data });
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: Action = createAction({
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 actionFile = resolve(serviceDir, `${actionName}.ts`);
119
- if (pathExists(actionFile)) {
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
- spinner.stop(`Action created at src/services/${serviceName}/${actionName}.ts`);
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("Register the action in your service config:");
130
- hint(` import { ${camel}Action } from "./${serviceName}/${actionName}";`);
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((resolve2) => {
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
- resolve2(defaultYes);
166
+ resolve(defaultYes);
152
167
  return;
153
168
  }
154
- resolve2(trimmed === "y" || trimmed === "yes");
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((resolve2) => {
178
+ return new Promise((resolve) => {
164
179
  rl.question(` ${question} `, (answer) => {
165
180
  rl.close();
166
- resolve2(answer.trim());
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 pascalAction = action.charAt(0).toUpperCase() + action.slice(1);
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 pascalAction = action.charAt(0).toUpperCase() + action.slice(1);
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.infer<typeof ${entry.exportName}>;`);
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((resolve3, reject) => {
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
- resolve3(result);
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 absolute2 = resolve3(projectRoot, entryFlag);
351
- if (!pathExists(absolute2)) {
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 absolute2;
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 { type Action, createAction } from "@nilejs/nile";
443
- import { Ok } from "slang-ts";
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
- const sample${pascal}Handler = async (data: Record<string, unknown>) => {
451
- return Ok({ ${camel}: { name: data.name } });
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: Action = createAction({
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
- return `export { sample${pascal}Action } from "./sample";
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 pascal = toPascalCase2(serviceName);
469
- const importLine = `import { sample${pascal}Action } from "./${serviceName}/sample";`;
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 pascal = toPascalCase2(serviceName);
484
- const importLine = `import { sample${pascal}Action } from "./${serviceName}/sample";`;
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
- name: "${serviceName}",
498
- description: "${pascal} service",
499
- actions: [sample${pascal}Action],
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
- content = content.slice(0, closingIndex) + serviceEntry + `
506
- ` + content.slice(closingIndex);
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("nile g service <name> Add a new service");
601
- hint("nile g action <service> <name> Add an action to a service");
602
- hint("nile g schema Generate Zod schemas & types");
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("0.0.1");
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": "0.0.7",
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
+ }
@@ -3,8 +3,7 @@ FROM oven/bun:1-alpine
3
3
  WORKDIR /app
4
4
 
5
5
  # Copy package files
6
- COPY package.json ./
7
- COPY bun.lockb ./
6
+ COPY package.json bun.lock ./
8
7
 
9
8
  # Install production dependencies only
10
9
  RUN bun install --frozen-lockfile --production