@rpal/cli 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 paljs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { Pal } from '@paljs/core';
3
+ console.log(`
4
+ ╔═══════════════════════════════════════════╗
5
+ ║ ║
6
+ ║ ██████╗ ███████╗██╗ ██╗███████╗ ║
7
+ ║ ██╔══██╗██╔════╝██║ ██║██╔════╝ ║
8
+ ║ ██║ ██║█████╗ ██║ ██║█████╗ ║
9
+ ║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██╔══╝ ║
10
+ ║ ██████╔╝███████╗ ╚████╔╝ ███████╗ ║
11
+ ║ ╚═════╝ ╚══════╝ ╚═══╝ ╚══════╝ ║
12
+ ║ ║
13
+ ║ CLI Tool v${require('../package.json').version} ║
14
+ ║ ║
15
+ ╚═══════════════════════════════════════════╝
16
+ `);
17
+ const commands = process.argv.slice(2);
18
+ if (commands.length === 0) {
19
+ console.log(`
20
+ Usage: pal <command>
21
+
22
+ Commands:
23
+ init Initialize a new Pal project
24
+ generate Generate code from templates
25
+ serve Start development server
26
+ build Build for production
27
+ test Run tests
28
+
29
+ Examples:
30
+ pal init my-project
31
+ pal generate service User
32
+ pal serve
33
+ `);
34
+ process.exit(0);
35
+ }
36
+ const [command, ...args] = commands;
37
+ switch (command) {
38
+ case 'init':
39
+ console.log('📦 Initializing Pal project...');
40
+ console.log(' This would scaffold a new project');
41
+ break;
42
+ case 'generate':
43
+ console.log('🔧 Generating code...');
44
+ console.log(` Template: ${args[0] || 'unknown'}`);
45
+ console.log(` Name: ${args[1] || 'unnamed'}`);
46
+ break;
47
+ case 'serve':
48
+ console.log('🚀 Starting development server...');
49
+ const container = Pal.create();
50
+ console.log(' Container created:', container.constructor.name);
51
+ break;
52
+ default:
53
+ console.log(`❌ Unknown command: ${command}`);
54
+ process.exit(1);
55
+ }
@@ -0,0 +1,125 @@
1
+ import 'reflect-metadata';
2
+ import { Scope, DEFAULT_SCOPE } from './scope';
3
+ import { InjectionToken } from './token';
4
+ import { getInjectableMetadata, getConstructorParams } from './decorators';
5
+ export class PalCircularDependencyError extends Error {
6
+ constructor(path) {
7
+ super(`Circular dependency detected: ${path}`);
8
+ this.name = 'PalCircularDependencyError';
9
+ }
10
+ }
11
+ export class PalDependencyNotFoundError extends Error {
12
+ constructor(token, index, target) {
13
+ super(`Cannot resolve dependency "${getTokenName(token)}" at index ${index} in ${target.name || 'Unknown'} constructor`);
14
+ this.name = 'PalDependencyNotFoundError';
15
+ }
16
+ }
17
+ function getTokenName(token) {
18
+ if (token instanceof InjectionToken)
19
+ return token.name;
20
+ if (typeof token === 'string')
21
+ return token;
22
+ if (typeof token === 'symbol')
23
+ return String(token);
24
+ return token.name || 'Unknown';
25
+ }
26
+ export class PalContainer {
27
+ constructor(parent) {
28
+ this.registrations = new Map();
29
+ this.resolutionStack = [];
30
+ this.singletonCache = new Map();
31
+ this.parent = parent;
32
+ }
33
+ provide(token, provider) {
34
+ const reg = this.createRegistration(token, provider);
35
+ this.registrations.set(token, reg);
36
+ return this;
37
+ }
38
+ createRegistration(token, provider) {
39
+ const scope = provider.scope ?? DEFAULT_SCOPE;
40
+ if ('useValue' in provider) {
41
+ return { token, factory: () => provider.useValue, scope: Scope.Singleton, instance: provider.useValue };
42
+ }
43
+ if ('useClass' in provider) {
44
+ return { token, factory: (ctx) => this.resolveClass(provider.useClass, ctx), scope };
45
+ }
46
+ if ('useFactory' in provider) {
47
+ return { token, factory: (ctx) => provider.useFactory(ctx), scope };
48
+ }
49
+ throw new Error('Provider must have useValue, useClass, or useFactory');
50
+ }
51
+ get(token) {
52
+ if (this.resolutionStack.includes(token)) {
53
+ const path = [...this.resolutionStack, token].map(getTokenName).join(' -> ');
54
+ throw new PalCircularDependencyError(path);
55
+ }
56
+ const registration = this.findRegistration(token);
57
+ if (!registration) {
58
+ if (token instanceof Function && getInjectableMetadata(token)) {
59
+ return this.resolveClass(token, this);
60
+ }
61
+ throw new PalDependencyNotFoundError(token, 0, token);
62
+ }
63
+ return this.resolve(registration);
64
+ }
65
+ has(token) {
66
+ return this.findRegistration(token) !== undefined;
67
+ }
68
+ createChild() {
69
+ return new PalContainer(this);
70
+ }
71
+ override(token, provider) {
72
+ this.registrations.set(token, this.createRegistration(token, provider));
73
+ return this;
74
+ }
75
+ reset() {
76
+ this.registrations.clear();
77
+ this.singletonCache.clear();
78
+ }
79
+ findRegistration(token) {
80
+ return this.registrations.get(token) ?? this.parent?.findRegistration(token);
81
+ }
82
+ resolve(registration) {
83
+ if (registration.scope === Scope.Singleton && registration.instance !== undefined) {
84
+ return registration.instance;
85
+ }
86
+ if (registration.scope === Scope.Singleton) {
87
+ const instance = registration.factory(this);
88
+ registration.instance = instance;
89
+ return instance;
90
+ }
91
+ return registration.factory(this);
92
+ }
93
+ resolveClass(klass, ctx) {
94
+ this.resolutionStack.push(klass);
95
+ try {
96
+ const paramMeta = getConstructorParams(klass);
97
+ const args = paramMeta.map((param) => {
98
+ if (param.token && ctx.has(param.token)) {
99
+ return param.optional && !ctx.has(param.token) ? undefined : ctx.get(param.token);
100
+ }
101
+ if (ctx.has(param.type)) {
102
+ return ctx.get(param.type);
103
+ }
104
+ if (param.type === Object)
105
+ return undefined;
106
+ try {
107
+ return this.resolveClass(param.type, ctx);
108
+ }
109
+ catch {
110
+ return undefined;
111
+ }
112
+ });
113
+ return new klass(...args);
114
+ }
115
+ finally {
116
+ this.resolutionStack.pop();
117
+ }
118
+ }
119
+ }
120
+ export function createPal(parent) {
121
+ return new PalContainer(parent);
122
+ }
123
+ export const Pal = {
124
+ create: createPal,
125
+ };
@@ -0,0 +1,59 @@
1
+ import 'reflect-metadata';
2
+ import { DEFAULT_SCOPE } from './scope';
3
+ import { INJECTABLE_METADATA_KEY, INJECTABLE_CONSTRUCTOR_KEY } from './interface';
4
+ const paramsCache = new WeakMap();
5
+ export function Injectable(options) {
6
+ return (target) => {
7
+ Reflect.defineMetadata(INJECTABLE_METADATA_KEY, {
8
+ scope: options?.scope ?? DEFAULT_SCOPE,
9
+ token: options?.token,
10
+ }, target);
11
+ };
12
+ }
13
+ export function Inject(token, type, opts = {}) {
14
+ return (target, _key, index) => {
15
+ const inferredType = type || getParamType(target, index);
16
+ if (!inferredType) {
17
+ console.warn(`@Inject: Could not determine type for parameter ${index}. Provide it explicitly.`);
18
+ }
19
+ storeParamMeta(target, index, { type: inferredType, token, optional: opts.optional });
20
+ };
21
+ }
22
+ export function Optional() {
23
+ return (target, _key, index) => {
24
+ const type = getParamType(target, index);
25
+ const existing = getParamMeta(target, index);
26
+ storeParamMeta(target, index, { type, optional: true, token: existing?.token });
27
+ };
28
+ }
29
+ function getParamType(target, index) {
30
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
31
+ return paramTypes[index];
32
+ }
33
+ function getParamMeta(target, index) {
34
+ const params = getConstructorParams(target);
35
+ return params[index];
36
+ }
37
+ function storeParamMeta(target, index, meta) {
38
+ const params = Reflect.getOwnMetadata(INJECTABLE_CONSTRUCTOR_KEY, target) || [];
39
+ params[index] = meta;
40
+ Reflect.defineMetadata(INJECTABLE_CONSTRUCTOR_KEY, params, target);
41
+ }
42
+ export function getInjectableMetadata(target) {
43
+ return Reflect.getOwnMetadata(INJECTABLE_METADATA_KEY, target);
44
+ }
45
+ export function getConstructorParams(target) {
46
+ const cached = paramsCache.get(target);
47
+ if (cached)
48
+ return cached;
49
+ const params = Reflect.getOwnMetadata(INJECTABLE_CONSTRUCTOR_KEY, target) || [];
50
+ if (!params.length) {
51
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
52
+ return paramTypes.map((type, index) => ({ type }));
53
+ }
54
+ paramsCache.set(target, params);
55
+ return params;
56
+ }
57
+ export function getInjectionMetadata(target) {
58
+ return getConstructorParams(target).map(p => ({ token: p.token || p.type, optional: p.optional }));
59
+ }
@@ -0,0 +1,14 @@
1
+ export * from './scope';
2
+ export * from './token';
3
+ export * from './decorators';
4
+ export * from './interface';
5
+ export * from './container';
6
+ export * from './test-container';
7
+ import { Pal, PalContainer, createPal, PalCircularDependencyError, PalDependencyNotFoundError } from './container';
8
+ import { PalTestContainer } from './test-container';
9
+ export { Pal as default };
10
+ export const Container = PalContainer;
11
+ export const TestContainer = PalTestContainer;
12
+ export const createContainer = createPal;
13
+ export const CircularDependencyError = PalCircularDependencyError;
14
+ export const DependencyNotFoundError = PalDependencyNotFoundError;
@@ -0,0 +1,3 @@
1
+ export const INJECTABLE_METADATA_KEY = Symbol('injectable:metadata');
2
+ export const INJECT_METADATA_KEY = Symbol('inject:metadata');
3
+ export const INJECTABLE_CONSTRUCTOR_KEY = Symbol('injectable:constructor');
@@ -0,0 +1,6 @@
1
+ export const Scope = {
2
+ Singleton: 'singleton',
3
+ Transient: 'transient',
4
+ Scoped: 'scoped',
5
+ };
6
+ export const DEFAULT_SCOPE = Scope.Singleton;
@@ -0,0 +1,52 @@
1
+ import { PalContainer } from './container';
2
+ export class PalTestContainer extends PalContainer {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.snapshots = [];
6
+ }
7
+ static create() {
8
+ return new PalTestContainer();
9
+ }
10
+ snapshot() {
11
+ this.snapshots.push({
12
+ registrations: new Map(this.getRegistrations()),
13
+ singletons: new Map(this.getSingletons()),
14
+ });
15
+ }
16
+ restore() {
17
+ const snap = this.snapshots.pop();
18
+ if (!snap)
19
+ return;
20
+ this.restoreFrom(snap);
21
+ }
22
+ reset() {
23
+ super.reset();
24
+ this.snapshots = [];
25
+ }
26
+ clearSnapshots() {
27
+ this.snapshots = [];
28
+ }
29
+ mock(token, mockValue) {
30
+ const mock = new Proxy({}, {
31
+ get(_, prop) {
32
+ if (prop === 'then' || prop === 'catch')
33
+ return undefined;
34
+ if (typeof prop === 'symbol')
35
+ return undefined;
36
+ return mockValue?.[prop] ?? (() => mock);
37
+ },
38
+ });
39
+ this.override(token, { useValue: mock });
40
+ return mock;
41
+ }
42
+ getRegistrations() {
43
+ return this.registrations;
44
+ }
45
+ getSingletons() {
46
+ return this.singletonCache;
47
+ }
48
+ restoreFrom(snap) {
49
+ this.registrations = snap.registrations;
50
+ this.singletonCache = snap.singletons;
51
+ }
52
+ }
@@ -0,0 +1,6 @@
1
+ export class InjectionToken {
2
+ constructor(name) {
3
+ this.name = name;
4
+ this.toString = () => `InjectionToken(${name})`;
5
+ }
6
+ }
package/dist/index.js ADDED
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ const VERSION = '1.0.0';
5
+ console.log(`
6
+ ╔═══════════════════════════════════════════╗
7
+ ║ ║
8
+ ║ ██████╗ ███████╗██╗ ██╗███████╗ ║
9
+ ║ ██╔══██╗██╔════╝██║ ██║██╔════╝ ║
10
+ ║ ██║ ██║█████╗ ██║ ██║█████╗ ║
11
+ ║ ██║ ██║██╔══╝ ╚██╗ ██╔╝██╔══╝ ║
12
+ ║ ██████╔╝███████╗ ╚████╔╝ ███████╗ ║
13
+ ║ ╚═════╝ ╚══════╝ ╚═══╝ ╚══════╝ ║
14
+ ║ ║
15
+ ║ CLI Tool v${VERSION} ║
16
+ ║ ║
17
+ ╚═══════════════════════════════════════════╝
18
+ `);
19
+ const args = process.argv.slice(2);
20
+ const command = args[0];
21
+ if (!command || command === 'help') {
22
+ showHelp();
23
+ process.exit(0);
24
+ }
25
+ switch (command) {
26
+ case 'make:model':
27
+ makeModel(args.slice(1));
28
+ break;
29
+ case 'make:repository':
30
+ makeRepository(args.slice(1));
31
+ break;
32
+ case 'make:service':
33
+ makeService(args.slice(1));
34
+ break;
35
+ case 'make:migration':
36
+ makeMigration(args.slice(1));
37
+ break;
38
+ case 'make:controller':
39
+ makeController(args.slice(1));
40
+ break;
41
+ case 'make:factory':
42
+ makeFactory(args.slice(1));
43
+ break;
44
+ case 'init':
45
+ initProject(args.slice(1));
46
+ break;
47
+ case 'db:push':
48
+ dbPush();
49
+ break;
50
+ case 'db:seed':
51
+ dbSeed();
52
+ break;
53
+ default:
54
+ console.log(`❌ Unknown command: ${command}`);
55
+ console.log(` Run 'pal help' for available commands`);
56
+ process.exit(1);
57
+ }
58
+ function showHelp() {
59
+ console.log(`
60
+ Usage: pal <command> [options]
61
+
62
+ Make Commands:
63
+ make:model <name> Create a new model (entity + repository)
64
+ make:repository <name> Create a new repository
65
+ make:service <name> Create a new service
66
+ make:controller <name> Create a new controller
67
+ make:migration <name> Create a new migration
68
+ make:factory <name> Create a model factory
69
+
70
+ Database Commands:
71
+ db:push Push schema to database
72
+ db:seed Run database seeds
73
+
74
+ Project Commands:
75
+ init [name] Initialize a new Pal project
76
+
77
+ Options:
78
+ --resource Generate CRUD methods (for model/repository/controller)
79
+ --api Generate API-ready controller
80
+ --force Overwrite existing files
81
+
82
+ Examples:
83
+ pal make:model User
84
+ pal make:repository Post --resource
85
+ pal make:controller UserController --api
86
+ pal init my-api
87
+ `);
88
+ }
89
+ function makeModel(args) {
90
+ const name = args[0];
91
+ if (!name) {
92
+ console.log('❌ Please provide a model name');
93
+ console.log(' Example: pal make:model User');
94
+ process.exit(1);
95
+ }
96
+ const options = parseOptions(args);
97
+ const fileName = toKebabCase(name);
98
+ const dir = path.join(process.cwd(), 'src', 'models', fileName);
99
+ ensureDir(dir);
100
+ const entityCode = generateEntity(name, options);
101
+ const repositoryCode = generateRepository(name, options);
102
+ writeFile(path.join(dir, 'index.ts'), entityCode);
103
+ writeFile(path.join(dir, 'repository.ts'), repositoryCode);
104
+ console.log(`✅ Created model: src/models/${fileName}/`);
105
+ console.log(` - index.ts`);
106
+ console.log(` - repository.ts`);
107
+ }
108
+ function makeRepository(args) {
109
+ const name = args[0];
110
+ if (!name) {
111
+ console.log('❌ Please provide a repository name');
112
+ process.exit(1);
113
+ }
114
+ const options = parseOptions(args);
115
+ const fileName = toKebabCase(name);
116
+ const dir = path.join(process.cwd(), 'src', 'repositories');
117
+ ensureDir(dir);
118
+ const code = generateRepository(name, options);
119
+ writeFile(path.join(dir, `${fileName}.repository.ts`), code);
120
+ console.log(`✅ Created repository: src/repositories/${fileName}.repository.ts`);
121
+ }
122
+ function makeService(args) {
123
+ const name = args[0];
124
+ if (!name) {
125
+ console.log('❌ Please provide a service name');
126
+ process.exit(1);
127
+ }
128
+ const fileName = toKebabCase(name);
129
+ const dir = path.join(process.cwd(), 'src', 'services');
130
+ ensureDir(dir);
131
+ const code = generateService(name);
132
+ writeFile(path.join(dir, `${fileName}.service.ts`), code);
133
+ console.log(`✅ Created service: src/services/${fileName}.service.ts`);
134
+ }
135
+ function makeController(args) {
136
+ const name = args[0];
137
+ if (!name) {
138
+ console.log('❌ Please provide a controller name');
139
+ process.exit(1);
140
+ }
141
+ const options = parseOptions(args);
142
+ const fileName = toKebabCase(name);
143
+ const dir = path.join(process.cwd(), 'src', 'controllers');
144
+ ensureDir(dir);
145
+ const code = generateController(name, options);
146
+ writeFile(path.join(dir, `${fileName}.controller.ts`), code);
147
+ console.log(`✅ Created controller: src/controllers/${fileName}.controller.ts`);
148
+ }
149
+ function makeMigration(args) {
150
+ const name = args[0];
151
+ if (!name) {
152
+ console.log('❌ Please provide a migration name');
153
+ process.exit(1);
154
+ }
155
+ const timestamp = Date.now();
156
+ const fileName = `${timestamp}_${toKebabCase(name)}.ts`;
157
+ const dir = path.join(process.cwd(), 'database', 'migrations');
158
+ ensureDir(dir);
159
+ const code = generateMigration(name);
160
+ writeFile(path.join(dir, fileName), code);
161
+ console.log(`✅ Created migration: database/migrations/${fileName}`);
162
+ }
163
+ function makeFactory(args) {
164
+ const name = args[0];
165
+ if (!name) {
166
+ console.log('❌ Please provide a factory name');
167
+ process.exit(1);
168
+ }
169
+ const fileName = toKebabCase(name);
170
+ const dir = path.join(process.cwd(), 'database', 'factories');
171
+ ensureDir(dir);
172
+ const code = generateFactory(name);
173
+ writeFile(path.join(dir, `${fileName}.factory.ts`), code);
174
+ console.log(`✅ Created factory: database/factories/${fileName}.factory.ts`);
175
+ }
176
+ function initProject(args) {
177
+ const name = args[0] || 'my-pal-app';
178
+ console.log(`📦 Initializing Pal project: ${name}...`);
179
+ const dir = path.join(process.cwd(), name);
180
+ ensureDir(dir);
181
+ ensureDir(path.join(dir, 'src', 'models'));
182
+ ensureDir(path.join(dir, 'src', 'services'));
183
+ ensureDir(path.join(dir, 'src', 'repositories'));
184
+ ensureDir(path.join(dir, 'src', 'controllers'));
185
+ ensureDir(path.join(dir, 'database', 'migrations'));
186
+ ensureDir(path.join(dir, 'database', 'factories'));
187
+ ensureDir(path.join(dir, 'tests'));
188
+ const pkgJson = {
189
+ name: name,
190
+ version: '0.0.1',
191
+ type: 'module',
192
+ scripts: {
193
+ dev: 'pal serve',
194
+ build: 'pal build',
195
+ test: 'pal test',
196
+ 'db:push': 'pal db:push',
197
+ 'db:seed': 'pal db:seed',
198
+ },
199
+ dependencies: {
200
+ '@paljs/core': 'workspace:*',
201
+ '@paljs/orm': 'workspace:*',
202
+ '@paljs/http': 'workspace:*',
203
+ },
204
+ devDependencies: {
205
+ 'typescript': '^5.0.0',
206
+ '@types/node': '^20.0.0',
207
+ 'tsx': '^4.0.0',
208
+ },
209
+ };
210
+ const tsconfig = {
211
+ compilerOptions: {
212
+ target: 'ES2022',
213
+ module: 'ESNext',
214
+ moduleResolution: 'bundler',
215
+ experimentalDecorators: true,
216
+ emitDecoratorMetadata: true,
217
+ strict: true,
218
+ esModuleInterop: true,
219
+ skipLibCheck: true,
220
+ outDir: 'dist',
221
+ },
222
+ include: ['src/**/*'],
223
+ };
224
+ writeFile(path.join(dir, 'package.json'), JSON.stringify(pkgJson, null, 2));
225
+ writeFile(path.join(dir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
226
+ writeFile(path.join(dir, 'src', 'index.ts'), '// Welcome to Pal!\n\n');
227
+ console.log(`✅ Project created: ${name}/`);
228
+ console.log(` Run: cd ${name} && pnpm install && pnpm dev`);
229
+ }
230
+ function dbPush() {
231
+ console.log('🔄 Pushing schema to database...');
232
+ console.log(' (This would sync your models to the database)');
233
+ }
234
+ function dbSeed() {
235
+ console.log('🌱 Running database seeds...');
236
+ console.log(' (This would seed your database with initial data)');
237
+ }
238
+ function parseOptions(args) {
239
+ return args
240
+ .filter(a => a.startsWith('--'))
241
+ .reduce((acc, opt) => {
242
+ acc[opt.slice(2)] = true;
243
+ return acc;
244
+ }, {});
245
+ }
246
+ function toKebabCase(str) {
247
+ return str
248
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
249
+ .replace(/[\s_]+/g, '-')
250
+ .toLowerCase();
251
+ }
252
+ function toCamelCase(str) {
253
+ return str.charAt(0).toLowerCase() + str.slice(1);
254
+ }
255
+ function ensureDir(dir) {
256
+ if (!fs.existsSync(dir)) {
257
+ fs.mkdirSync(dir, { recursive: true });
258
+ }
259
+ }
260
+ function writeFile(filePath, content) {
261
+ if (fs.existsSync(filePath) && !process.argv.includes('--force')) {
262
+ console.log(`⚠️ File exists: ${filePath}`);
263
+ console.log(` Use --force to overwrite`);
264
+ return;
265
+ }
266
+ fs.writeFileSync(filePath, content);
267
+ }
268
+ function generateEntity(name, options) {
269
+ const camelName = toCamelCase(name);
270
+ const hasResource = options.resource;
271
+ return `import { BaseEntity } from '@paljs/orm';
272
+
273
+ export interface ${name} extends BaseEntity {
274
+ // Add your fields here
275
+ name: string;
276
+ ${hasResource ? `
277
+ // Common fields for CRUD:
278
+ // email: string;
279
+ // status: 'active' | 'inactive';
280
+ // description?: string;
281
+ // createdAt: Date;
282
+ // updatedAt: Date;` : ''}
283
+ }
284
+
285
+ export interface Create${name}DTO {
286
+ name: string;
287
+ ${hasResource ? `// email: string;
288
+ // status?: 'active' | 'inactive';
289
+ // description?: string;` : ''}
290
+ }
291
+
292
+ export interface Update${name}DTO {
293
+ name?: string;
294
+ ${hasResource ? `// email?: string;
295
+ // status?: 'active' | 'inactive';
296
+ // description?: string;` : ''}
297
+ }
298
+ `;
299
+ }
300
+ function generateRepository(name, options) {
301
+ const camelName = toCamelCase(name);
302
+ const hasResource = options.resource;
303
+ let extraMethods = '';
304
+ if (hasResource) {
305
+ extraMethods = `
306
+ async findByEmail(email: string): Promise<${name} | null> {
307
+ return this.findOne({ email });
308
+ }
309
+
310
+ async findActive(): Promise<${name}[] | null> {
311
+ return this.findAll({ status: 'active' });
312
+ }
313
+
314
+ async exists(email: string, excludeId?: string): Promise<boolean> {
315
+ const existing = await this.findByEmail(email);
316
+ if (!existing) return false;
317
+ if (excludeId && existing.id === excludeId) return false;
318
+ return true;
319
+ }`;
320
+ }
321
+ return `import { BaseRepository } from '@paljs/orm';
322
+ import { ${name}, Create${name}DTO, Update${name}DTO } from './index';
323
+
324
+ export class ${name}Repository extends BaseRepository<${name}, Create${name}DTO, Update${name}DTO> {
325
+ protected toEntity(data: Create${name}DTO): ${name} {
326
+ const now = new Date();
327
+ return {
328
+ id: this.generateId(),
329
+ name: data.name,
330
+ ${hasResource ? `// email: data.email,
331
+ // status: data.status || 'active',` : ''}
332
+ createdAt: now,
333
+ };
334
+ }
335
+
336
+ protected toUpdateData(data: Update${name}DTO): Partial<${name}> {
337
+ return {
338
+ name: data.name,
339
+ ${hasResource ? `// email: data.email,
340
+ // status: data.status,
341
+ // updatedAt: new Date(),` : ''}
342
+ };
343
+ }${extraMethods}
344
+ }
345
+ `;
346
+ }
347
+ function generateService(name) {
348
+ const camelName = toCamelCase(name);
349
+ const repoName = `${name}Repository`;
350
+ return `import { Injectable } from '@paljs/core';
351
+ import { ${repoName} } from '../repositories/${toKebabCase(name)}.repository';
352
+ import { Create${name}DTO, Update${name}DTO } from '../models/${toKebabCase(name)}';
353
+
354
+ @Injectable()
355
+ export class ${name}Service {
356
+ constructor(private ${camelName}Repo: ${repoName}) {}
357
+
358
+ async findAll() {
359
+ return this.${camelName}Repo.findAll();
360
+ }
361
+
362
+ async findOne(id: string) {
363
+ return this.${camelName}Repo.findById(id);
364
+ }
365
+
366
+ async create(data: Create${name}DTO) {
367
+ return this.${camelName}Repo.create(data);
368
+ }
369
+
370
+ async update(id: string, data: Update${name}DTO) {
371
+ return this.${camelName}Repo.update(id, data);
372
+ }
373
+
374
+ async delete(id: string) {
375
+ return this.${camelName}Repo.delete(id);
376
+ }
377
+ }
378
+ `;
379
+ }
380
+ function generateController(name, options) {
381
+ const camelName = toCamelCase(name);
382
+ const hasApi = options.api;
383
+ const hasResource = options.resource;
384
+ const kebabName = toKebabCase(name);
385
+ let crudMethods = '';
386
+ if (hasApi) {
387
+ crudMethods = `
388
+ async index() {
389
+ const ${camelName}s = await this.${camelName}Service.findAll();
390
+ return { data: ${camelName}s };
391
+ }
392
+
393
+ async show(id: string) {
394
+ const ${camelName} = await this.${camelName}Service.findOne(id);
395
+ if (!${camelName}) {
396
+ throw new Error('${name} not found');
397
+ }
398
+ return { data: ${camelName} };
399
+ }
400
+
401
+ async store(data: any) {
402
+ const ${camelName} = await this.${camelName}Service.create(data);
403
+ return { data: ${camelName}, status: 201 };
404
+ }
405
+
406
+ async update(id: string, data: any) {
407
+ const ${camelName} = await this.${camelName}Service.update(id, data);
408
+ return { data: ${camelName} };
409
+ }
410
+
411
+ async destroy(id: string) {
412
+ await this.${camelName}Service.delete(id);
413
+ return { status: 204 };
414
+ }`;
415
+ }
416
+ if (hasResource || hasApi) {
417
+ return `import { Injectable } from '@paljs/core';
418
+ import { ${name}Service } from '../services/${kebabName}.service';
419
+
420
+ @Injectable()
421
+ export class ${name}Controller {
422
+ constructor(private ${camelName}Service: ${name}Service) {} ${crudMethods}
423
+ }
424
+ `;
425
+ }
426
+ return `import { Injectable } from '@paljs/core';
427
+
428
+ @Injectable()
429
+ export class ${name}Controller {
430
+ async index() {
431
+ return { data: [] };
432
+ }
433
+ }
434
+ `;
435
+ }
436
+ function generateMigration(name) {
437
+ const tableName = toKebabCase(name) + 's';
438
+ return `import { Migration } from '@paljs/orm';
439
+
440
+ export class Create${name}Table implements Migration {
441
+ up() {
442
+ return \`
443
+ CREATE TABLE ${tableName} (
444
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
445
+ name VARCHAR(255) NOT NULL,
446
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
447
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
448
+ )
449
+ \`;
450
+ }
451
+
452
+ down() {
453
+ return \`DROP TABLE ${tableName}\`;
454
+ }
455
+ }
456
+ `;
457
+ }
458
+ function generateFactory(name) {
459
+ return `import { Factory } from '@paljs/test';
460
+ import { ${name} } from '../models/${toKebabCase(name)}';
461
+
462
+ export const ${name}Factory = Factory.define<${name}>(({ sequence }) => ({
463
+ id: \`seed-\${sequence}\`,
464
+ name: \`Test ${name} \${sequence}\`,
465
+ createdAt: new Date(),
466
+ }));
467
+ `;
468
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@rpal/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for PalJS",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "bin": {
10
+ "pal": "dist/index.js"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/paljs/paljs.git"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "@rpal/core": "^1.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.0.0",
24
+ "tsx": "^4.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.json",
28
+ "dev": "tsx src/index.ts"
29
+ }
30
+ }