baasix 0.1.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 +355 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +2521 -0
- package/package.json +56 -0
- package/src/commands/extension.ts +447 -0
- package/src/commands/generate.ts +485 -0
- package/src/commands/init.ts +1409 -0
- package/src/commands/migrate.ts +573 -0
- package/src/index.ts +39 -0
- package/src/utils/api-client.ts +121 -0
- package/src/utils/get-config.ts +69 -0
- package/src/utils/get-package-info.ts +12 -0
- package/src/utils/package-manager.ts +62 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +16 -0
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "baasix",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for Baasix Backend-as-a-Service",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"main": "./dist/index.mjs",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/tspvivek/baasix.git",
|
|
11
|
+
"directory": "cli"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://baasix.com/docs/cli",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"start": "node ./dist/index.mjs",
|
|
17
|
+
"dev": "tsx ./src/index.ts",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"executableFiles": [
|
|
23
|
+
"./dist/index.mjs"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"keywords": [
|
|
28
|
+
"baasix",
|
|
29
|
+
"cli",
|
|
30
|
+
"backend",
|
|
31
|
+
"baas",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"exports": "./dist/index.mjs",
|
|
35
|
+
"bin": {
|
|
36
|
+
"baasix": "./dist/index.mjs"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.11.0",
|
|
40
|
+
"@types/prompts": "^2.4.9",
|
|
41
|
+
"tsup": "^8.0.1",
|
|
42
|
+
"tsx": "^4.20.6",
|
|
43
|
+
"typescript": "^5.3.3"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@clack/prompts": "^0.11.0",
|
|
47
|
+
"axios": "^1.6.0",
|
|
48
|
+
"chalk": "^5.3.0",
|
|
49
|
+
"commander": "^12.0.0",
|
|
50
|
+
"dotenv": "^16.3.1",
|
|
51
|
+
"ora": "^8.0.1",
|
|
52
|
+
"prettier": "^3.2.0",
|
|
53
|
+
"prompts": "^2.4.2",
|
|
54
|
+
"zod": "^3.22.0"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cancel,
|
|
6
|
+
confirm,
|
|
7
|
+
intro,
|
|
8
|
+
isCancel,
|
|
9
|
+
log,
|
|
10
|
+
outro,
|
|
11
|
+
select,
|
|
12
|
+
spinner,
|
|
13
|
+
text,
|
|
14
|
+
} from "@clack/prompts";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { Command } from "commander";
|
|
17
|
+
|
|
18
|
+
type ExtensionType = "hook" | "endpoint";
|
|
19
|
+
|
|
20
|
+
interface ExtensionOptions {
|
|
21
|
+
cwd: string;
|
|
22
|
+
type?: ExtensionType;
|
|
23
|
+
name?: string;
|
|
24
|
+
collection?: string;
|
|
25
|
+
typescript?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function extensionAction(opts: ExtensionOptions) {
|
|
29
|
+
const cwd = path.resolve(opts.cwd);
|
|
30
|
+
|
|
31
|
+
intro(chalk.bgMagenta.black(" Baasix Extension Generator "));
|
|
32
|
+
|
|
33
|
+
// Select extension type
|
|
34
|
+
let extensionType = opts.type;
|
|
35
|
+
if (!extensionType) {
|
|
36
|
+
const result = await select({
|
|
37
|
+
message: "What type of extension do you want to create?",
|
|
38
|
+
options: [
|
|
39
|
+
{
|
|
40
|
+
value: "hook",
|
|
41
|
+
label: "Hook",
|
|
42
|
+
hint: "Intercept and modify CRUD operations",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
value: "endpoint",
|
|
46
|
+
label: "Custom Endpoint",
|
|
47
|
+
hint: "Add new API routes",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (isCancel(result)) {
|
|
53
|
+
cancel("Operation cancelled");
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
extensionType = result as ExtensionType;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Get extension name
|
|
60
|
+
let extensionName = opts.name;
|
|
61
|
+
if (!extensionName) {
|
|
62
|
+
const result = await text({
|
|
63
|
+
message: "What is your extension name?",
|
|
64
|
+
placeholder: extensionType === "hook" ? "my-hook" : "my-endpoint",
|
|
65
|
+
validate: (value) => {
|
|
66
|
+
if (!value) return "Extension name is required";
|
|
67
|
+
if (!/^[a-z0-9-_]+$/i.test(value)) return "Name must be alphanumeric with dashes or underscores";
|
|
68
|
+
return undefined;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (isCancel(result)) {
|
|
73
|
+
cancel("Operation cancelled");
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
extensionName = result as string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// For hooks, ask for collection name
|
|
80
|
+
let collectionName = opts.collection;
|
|
81
|
+
if (extensionType === "hook" && !collectionName) {
|
|
82
|
+
const result = await text({
|
|
83
|
+
message: "Which collection should this hook apply to?",
|
|
84
|
+
placeholder: "posts",
|
|
85
|
+
validate: (value) => {
|
|
86
|
+
if (!value) return "Collection name is required";
|
|
87
|
+
return undefined;
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (isCancel(result)) {
|
|
92
|
+
cancel("Operation cancelled");
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
collectionName = result as string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Use TypeScript?
|
|
99
|
+
let useTypeScript = opts.typescript ?? false;
|
|
100
|
+
if (opts.typescript === undefined) {
|
|
101
|
+
const result = await confirm({
|
|
102
|
+
message: "Use TypeScript?",
|
|
103
|
+
initialValue: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (isCancel(result)) {
|
|
107
|
+
cancel("Operation cancelled");
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
useTypeScript = result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const s = spinner();
|
|
114
|
+
s.start("Creating extension...");
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Determine extensions directory
|
|
118
|
+
const extensionsDir = path.join(cwd, "extensions");
|
|
119
|
+
if (!existsSync(extensionsDir)) {
|
|
120
|
+
await fs.mkdir(extensionsDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const ext = useTypeScript ? "ts" : "js";
|
|
124
|
+
const extensionDir = path.join(extensionsDir, `baasix-${extensionType}-${extensionName}`);
|
|
125
|
+
|
|
126
|
+
// Check if extension already exists
|
|
127
|
+
if (existsSync(extensionDir)) {
|
|
128
|
+
s.stop("Extension already exists");
|
|
129
|
+
const overwrite = await confirm({
|
|
130
|
+
message: `Extension baasix-${extensionType}-${extensionName} already exists. Overwrite?`,
|
|
131
|
+
initialValue: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
135
|
+
cancel("Operation cancelled");
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await fs.mkdir(extensionDir, { recursive: true });
|
|
141
|
+
|
|
142
|
+
if (extensionType === "hook") {
|
|
143
|
+
await createHookExtension(extensionDir, extensionName, collectionName!, useTypeScript);
|
|
144
|
+
} else {
|
|
145
|
+
await createEndpointExtension(extensionDir, extensionName, useTypeScript);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
s.stop("Extension created");
|
|
149
|
+
|
|
150
|
+
outro(chalk.green(`✨ Extension created at extensions/baasix-${extensionType}-${extensionName}/`));
|
|
151
|
+
|
|
152
|
+
// Print next steps
|
|
153
|
+
console.log();
|
|
154
|
+
console.log(chalk.bold("Next steps:"));
|
|
155
|
+
console.log(` ${chalk.dim("1.")} Edit ${chalk.cyan(`extensions/baasix-${extensionType}-${extensionName}/index.${ext}`)}`);
|
|
156
|
+
console.log(` ${chalk.dim("2.")} Restart your Baasix server to load the extension`);
|
|
157
|
+
console.log();
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
s.stop("Failed to create extension");
|
|
161
|
+
log.error(error instanceof Error ? error.message : "Unknown error");
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function createHookExtension(
|
|
167
|
+
extensionDir: string,
|
|
168
|
+
name: string,
|
|
169
|
+
collection: string,
|
|
170
|
+
useTypeScript: boolean
|
|
171
|
+
) {
|
|
172
|
+
const ext = useTypeScript ? "ts" : "js";
|
|
173
|
+
|
|
174
|
+
const typeAnnotations = useTypeScript
|
|
175
|
+
? `
|
|
176
|
+
import type { HooksService } from "@tspvivek/baasix";
|
|
177
|
+
|
|
178
|
+
interface HookContext {
|
|
179
|
+
ItemsService: any;
|
|
180
|
+
schemaManager: any;
|
|
181
|
+
services: Record<string, any>;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface HookPayload {
|
|
185
|
+
data?: Record<string, any>;
|
|
186
|
+
query?: Record<string, any>;
|
|
187
|
+
id?: string | string[];
|
|
188
|
+
accountability: {
|
|
189
|
+
user: { id: string; email: string };
|
|
190
|
+
role: { id: string; name: string };
|
|
191
|
+
};
|
|
192
|
+
collection: string;
|
|
193
|
+
schema: any;
|
|
194
|
+
}
|
|
195
|
+
`
|
|
196
|
+
: "";
|
|
197
|
+
|
|
198
|
+
const hookContent = `${typeAnnotations}
|
|
199
|
+
/**
|
|
200
|
+
* Hook extension for ${collection} collection
|
|
201
|
+
*
|
|
202
|
+
* Available hooks:
|
|
203
|
+
* - items.create (before/after creating an item)
|
|
204
|
+
* - items.read (before/after reading items)
|
|
205
|
+
* - items.update (before/after updating an item)
|
|
206
|
+
* - items.delete (before/after deleting an item)
|
|
207
|
+
*/
|
|
208
|
+
export default (hooksService${useTypeScript ? ": HooksService" : ""}, context${useTypeScript ? ": HookContext" : ""}) => {
|
|
209
|
+
const { ItemsService } = context;
|
|
210
|
+
|
|
211
|
+
// Hook for creating items
|
|
212
|
+
hooksService.registerHook(
|
|
213
|
+
"${collection}",
|
|
214
|
+
"items.create",
|
|
215
|
+
async ({ data, accountability, collection, schema }${useTypeScript ? ": HookPayload" : ""}) => {
|
|
216
|
+
console.log(\`[${name}] Creating \${collection} item:\`, data);
|
|
217
|
+
|
|
218
|
+
// Example: Add created_by field
|
|
219
|
+
// data.created_by = accountability.user.id;
|
|
220
|
+
|
|
221
|
+
// Return modified data
|
|
222
|
+
return { data };
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Hook for reading items
|
|
227
|
+
hooksService.registerHook(
|
|
228
|
+
"${collection}",
|
|
229
|
+
"items.read",
|
|
230
|
+
async ({ query, data, accountability, collection, schema }${useTypeScript ? ": HookPayload" : ""}) => {
|
|
231
|
+
console.log(\`[${name}] Reading \${collection} with query:\`, query);
|
|
232
|
+
|
|
233
|
+
// Example: Filter results for non-admin users
|
|
234
|
+
// if (accountability.role.name !== "administrator") {
|
|
235
|
+
// query.filter = { ...query.filter, published: true };
|
|
236
|
+
// }
|
|
237
|
+
|
|
238
|
+
return { query };
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// Hook for updating items
|
|
243
|
+
hooksService.registerHook(
|
|
244
|
+
"${collection}",
|
|
245
|
+
"items.update",
|
|
246
|
+
async ({ id, data, accountability, schema }${useTypeScript ? ": HookPayload" : ""}) => {
|
|
247
|
+
console.log(\`[${name}] Updating item \${id}:\`, data);
|
|
248
|
+
|
|
249
|
+
// Example: Add updated_by field
|
|
250
|
+
// data.updated_by = accountability.user.id;
|
|
251
|
+
|
|
252
|
+
return { id, data };
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// Hook for deleting items
|
|
257
|
+
hooksService.registerHook(
|
|
258
|
+
"${collection}",
|
|
259
|
+
"items.delete",
|
|
260
|
+
async ({ id, accountability }${useTypeScript ? ": HookPayload" : ""}) => {
|
|
261
|
+
console.log(\`[${name}] Deleting item:\`, id);
|
|
262
|
+
|
|
263
|
+
// Example: Soft delete instead of hard delete
|
|
264
|
+
// const itemsService = new ItemsService("${collection}", { accountability, schema });
|
|
265
|
+
// await itemsService.update(id, { deletedAt: new Date() });
|
|
266
|
+
// return { skip: true }; // Skip the actual delete
|
|
267
|
+
|
|
268
|
+
return { id };
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
await fs.writeFile(path.join(extensionDir, `index.${ext}`), hookContent);
|
|
275
|
+
|
|
276
|
+
// Create README
|
|
277
|
+
const readme = `# baasix-hook-${name}
|
|
278
|
+
|
|
279
|
+
A Baasix hook extension for the \`${collection}\` collection.
|
|
280
|
+
|
|
281
|
+
## Available Hooks
|
|
282
|
+
|
|
283
|
+
- \`items.create\` - Before/after creating an item
|
|
284
|
+
- \`items.read\` - Before/after reading items
|
|
285
|
+
- \`items.update\` - Before/after updating an item
|
|
286
|
+
- \`items.delete\` - Before/after deleting an item
|
|
287
|
+
|
|
288
|
+
## Usage
|
|
289
|
+
|
|
290
|
+
This extension is automatically loaded when placed in the \`extensions/\` directory.
|
|
291
|
+
|
|
292
|
+
Edit \`index.${ext}\` to customize the hook behavior.
|
|
293
|
+
|
|
294
|
+
## Documentation
|
|
295
|
+
|
|
296
|
+
See [Hooks Documentation](https://baasix.com/docs/hooks) for more details.
|
|
297
|
+
`;
|
|
298
|
+
|
|
299
|
+
await fs.writeFile(path.join(extensionDir, "README.md"), readme);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function createEndpointExtension(
|
|
303
|
+
extensionDir: string,
|
|
304
|
+
name: string,
|
|
305
|
+
useTypeScript: boolean
|
|
306
|
+
) {
|
|
307
|
+
const ext = useTypeScript ? "ts" : "js";
|
|
308
|
+
|
|
309
|
+
const typeAnnotations = useTypeScript
|
|
310
|
+
? `
|
|
311
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
|
312
|
+
import { APIError } from "@tspvivek/baasix";
|
|
313
|
+
|
|
314
|
+
interface EndpointContext {
|
|
315
|
+
ItemsService: any;
|
|
316
|
+
schemaManager: any;
|
|
317
|
+
services: Record<string, any>;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
interface RequestWithAccountability extends FastifyRequest {
|
|
321
|
+
accountability?: {
|
|
322
|
+
user: { id: string; email: string };
|
|
323
|
+
role: { id: string; name: string };
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
`
|
|
327
|
+
: `import { APIError } from "@tspvivek/baasix";`;
|
|
328
|
+
|
|
329
|
+
const endpointContent = `${typeAnnotations}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Custom endpoint extension
|
|
333
|
+
*
|
|
334
|
+
* Register custom routes on the Fastify app instance.
|
|
335
|
+
*/
|
|
336
|
+
const registerEndpoint = (app${useTypeScript ? ": FastifyInstance" : ""}, context${useTypeScript ? ": EndpointContext" : ""}) => {
|
|
337
|
+
const { ItemsService } = context;
|
|
338
|
+
|
|
339
|
+
// GET endpoint example
|
|
340
|
+
app.get("/${name}", async (req${useTypeScript ? ": RequestWithAccountability" : ""}, res${useTypeScript ? ": FastifyReply" : ""}) => {
|
|
341
|
+
try {
|
|
342
|
+
// Check authentication (optional)
|
|
343
|
+
if (!req.accountability || !req.accountability.user) {
|
|
344
|
+
throw new APIError("Unauthorized", 401);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const { user, role } = req.accountability;
|
|
348
|
+
|
|
349
|
+
// Your custom logic here
|
|
350
|
+
const result = {
|
|
351
|
+
message: "Hello from ${name} endpoint!",
|
|
352
|
+
user: {
|
|
353
|
+
id: user.id,
|
|
354
|
+
email: user.email,
|
|
355
|
+
},
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
return res.send(result);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// POST endpoint example
|
|
366
|
+
app.post("/${name}", async (req${useTypeScript ? ": RequestWithAccountability" : ""}, res${useTypeScript ? ": FastifyReply" : ""}) => {
|
|
367
|
+
try {
|
|
368
|
+
if (!req.accountability || !req.accountability.user) {
|
|
369
|
+
throw new APIError("Unauthorized", 401);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const body = req.body${useTypeScript ? " as Record<string, any>" : ""};
|
|
373
|
+
|
|
374
|
+
// Example: Create an item using ItemsService
|
|
375
|
+
// const itemsService = new ItemsService("my_collection", {
|
|
376
|
+
// accountability: req.accountability,
|
|
377
|
+
// schema: context.schemaManager,
|
|
378
|
+
// });
|
|
379
|
+
// const itemId = await itemsService.createOne(body);
|
|
380
|
+
|
|
381
|
+
return res.status(201).send({
|
|
382
|
+
message: "Created successfully",
|
|
383
|
+
data: body,
|
|
384
|
+
});
|
|
385
|
+
} catch (error) {
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// Parameterized endpoint example
|
|
391
|
+
app.get("/${name}/:id", async (req${useTypeScript ? ": RequestWithAccountability" : ""}, res${useTypeScript ? ": FastifyReply" : ""}) => {
|
|
392
|
+
try {
|
|
393
|
+
const { id } = req.params${useTypeScript ? " as { id: string }" : ""};
|
|
394
|
+
|
|
395
|
+
return res.send({
|
|
396
|
+
message: \`Getting item \${id}\`,
|
|
397
|
+
id,
|
|
398
|
+
});
|
|
399
|
+
} catch (error) {
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export default {
|
|
406
|
+
id: "${name}",
|
|
407
|
+
handler: registerEndpoint,
|
|
408
|
+
};
|
|
409
|
+
`;
|
|
410
|
+
|
|
411
|
+
await fs.writeFile(path.join(extensionDir, `index.${ext}`), endpointContent);
|
|
412
|
+
|
|
413
|
+
// Create README
|
|
414
|
+
const readme = `# baasix-endpoint-${name}
|
|
415
|
+
|
|
416
|
+
A Baasix custom endpoint extension.
|
|
417
|
+
|
|
418
|
+
## Endpoints
|
|
419
|
+
|
|
420
|
+
- \`GET /${name}\` - Example GET endpoint
|
|
421
|
+
- \`POST /${name}\` - Example POST endpoint
|
|
422
|
+
- \`GET /${name}/:id\` - Example parameterized endpoint
|
|
423
|
+
|
|
424
|
+
## Usage
|
|
425
|
+
|
|
426
|
+
This extension is automatically loaded when placed in the \`extensions/\` directory.
|
|
427
|
+
|
|
428
|
+
Edit \`index.${ext}\` to customize the endpoints.
|
|
429
|
+
|
|
430
|
+
## Documentation
|
|
431
|
+
|
|
432
|
+
See [Custom Endpoints Documentation](https://baasix.com/docs/custom-endpoints) for more details.
|
|
433
|
+
`;
|
|
434
|
+
|
|
435
|
+
await fs.writeFile(path.join(extensionDir, "README.md"), readme);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export const extension = new Command("extension")
|
|
439
|
+
.alias("ext")
|
|
440
|
+
.description("Generate a new Baasix extension (hook or endpoint)")
|
|
441
|
+
.option("-c, --cwd <path>", "Working directory", process.cwd())
|
|
442
|
+
.option("-t, --type <type>", "Extension type (hook, endpoint)")
|
|
443
|
+
.option("-n, --name <name>", "Extension name")
|
|
444
|
+
.option("--collection <collection>", "Collection name (for hooks)")
|
|
445
|
+
.option("--typescript", "Use TypeScript")
|
|
446
|
+
.option("--no-typescript", "Use JavaScript")
|
|
447
|
+
.action(extensionAction);
|