@kargojs/cli 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/bin/kargo.js +7 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +48 -0
- package/dist/commands/make-panel.d.ts +7 -0
- package/dist/commands/make-panel.d.ts.map +1 -0
- package/dist/commands/make-panel.js +39 -0
- package/dist/commands/make-resource.d.ts +14 -0
- package/dist/commands/make-resource.d.ts.map +1 -0
- package/dist/commands/make-resource.js +101 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/templates/database.d.ts +13 -0
- package/dist/templates/database.d.ts.map +1 -0
- package/dist/templates/database.js +43 -0
- package/dist/templates/form.d.ts +3 -0
- package/dist/templates/form.d.ts.map +1 -0
- package/dist/templates/form.js +11 -0
- package/dist/templates/project.d.ts +7 -0
- package/dist/templates/project.d.ts.map +1 -0
- package/dist/templates/project.js +459 -0
- package/dist/templates/resource.d.ts +3 -0
- package/dist/templates/resource.d.ts.map +1 -0
- package/dist/templates/resource.js +15 -0
- package/dist/templates/service-in-memory.d.ts +3 -0
- package/dist/templates/service-in-memory.d.ts.map +1 -0
- package/dist/templates/service-in-memory.js +47 -0
- package/dist/templates/service.d.ts +3 -0
- package/dist/templates/service.d.ts.map +1 -0
- package/dist/templates/service.js +21 -0
- package/dist/templates/table.d.ts +3 -0
- package/dist/templates/table.d.ts.map +1 -0
- package/dist/templates/table.js +31 -0
- package/dist/utils/fs.d.ts +12 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +26 -0
- package/dist/utils/log.d.ts +11 -0
- package/dist/utils/log.d.ts.map +1 -0
- package/dist/utils/log.js +16 -0
- package/dist/utils/register-resource.d.ts +22 -0
- package/dist/utils/register-resource.d.ts.map +1 -0
- package/dist/utils/register-resource.js +71 -0
- package/dist/utils/strings.d.ts +31 -0
- package/dist/utils/strings.d.ts.map +1 -0
- package/dist/utils/strings.js +63 -0
- package/package.json +31 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export const log = {
|
|
3
|
+
info: (msg) => console.log(` ${pc.blue('ℹ')} ${msg}`),
|
|
4
|
+
success: (msg) => console.log(` ${pc.green('✔')} ${msg}`),
|
|
5
|
+
warn: (msg) => console.log(` ${pc.yellow('⚠')} ${msg}`),
|
|
6
|
+
error: (msg) => console.error(` ${pc.red('✖')} ${msg}`),
|
|
7
|
+
file: (path) => console.log(` ${pc.gray('+')} ${pc.cyan(path)}`),
|
|
8
|
+
step: (msg) => console.log(`\n ${pc.bold(msg)}`),
|
|
9
|
+
blank: () => console.log(),
|
|
10
|
+
};
|
|
11
|
+
export function banner(name, version) {
|
|
12
|
+
console.log();
|
|
13
|
+
console.log(` ${pc.bold(pc.blue('◆ Kargo'))} ${pc.gray(`v${version}`)}`);
|
|
14
|
+
console.log(` ${pc.gray(name)}`);
|
|
15
|
+
console.log();
|
|
16
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface RegisterResourceOptions {
|
|
2
|
+
appModulePath: string;
|
|
3
|
+
pascalName: string;
|
|
4
|
+
resourcePath: string;
|
|
5
|
+
servicePath: string;
|
|
6
|
+
}
|
|
7
|
+
export interface RegisterResourceResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Adds the two import statements and a `{ resource: XResource, service: XService }`
|
|
13
|
+
* entry to app.module.ts's `KargoModule.forRoot({ resources: [...] })` array using
|
|
14
|
+
* a real TS AST edit (via ts-morph), so it works regardless of the file's existing
|
|
15
|
+
* formatting/comments/multi-line layout.
|
|
16
|
+
*
|
|
17
|
+
* Returns { success: false, reason } — never throws or partially edits the file —
|
|
18
|
+
* when app.module.ts doesn't match the expected shape (e.g. heavily customized),
|
|
19
|
+
* so the caller can fall back to printing manual instructions instead.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerResource(opts: RegisterResourceOptions): RegisterResourceResult;
|
|
22
|
+
//# sourceMappingURL=register-resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register-resource.d.ts","sourceRoot":"","sources":["../../src/utils/register-resource.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,uBAAuB;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAQD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,uBAAuB,GAAG,sBAAsB,CA+DtF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Project, SyntaxKind, QuoteKind } from 'ts-morph';
|
|
2
|
+
import { relative, dirname } from 'node:path';
|
|
3
|
+
function toImportSpecifier(fromDir, toFileAbsPath) {
|
|
4
|
+
let rel = relative(fromDir, toFileAbsPath).replace(/\\/g, '/').replace(/\.ts$/, '.js');
|
|
5
|
+
if (!rel.startsWith('.'))
|
|
6
|
+
rel = `./${rel}`;
|
|
7
|
+
return rel;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Adds the two import statements and a `{ resource: XResource, service: XService }`
|
|
11
|
+
* entry to app.module.ts's `KargoModule.forRoot({ resources: [...] })` array using
|
|
12
|
+
* a real TS AST edit (via ts-morph), so it works regardless of the file's existing
|
|
13
|
+
* formatting/comments/multi-line layout.
|
|
14
|
+
*
|
|
15
|
+
* Returns { success: false, reason } — never throws or partially edits the file —
|
|
16
|
+
* when app.module.ts doesn't match the expected shape (e.g. heavily customized),
|
|
17
|
+
* so the caller can fall back to printing manual instructions instead.
|
|
18
|
+
*/
|
|
19
|
+
export function registerResource(opts) {
|
|
20
|
+
const project = new Project({
|
|
21
|
+
manipulationSettings: { quoteKind: QuoteKind.Single },
|
|
22
|
+
});
|
|
23
|
+
let sourceFile;
|
|
24
|
+
try {
|
|
25
|
+
sourceFile = project.addSourceFileAtPath(opts.appModulePath);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { success: false, reason: `Could not read ${opts.appModulePath}` };
|
|
29
|
+
}
|
|
30
|
+
const callExpr = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((c) => {
|
|
31
|
+
const expr = c.getExpression();
|
|
32
|
+
if (expr.getKind() !== SyntaxKind.PropertyAccessExpression)
|
|
33
|
+
return false;
|
|
34
|
+
const pae = expr.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
|
35
|
+
return pae.getName() === 'forRoot' && pae.getExpression().getText() === 'KargoModule';
|
|
36
|
+
});
|
|
37
|
+
if (!callExpr) {
|
|
38
|
+
return { success: false, reason: 'Could not find a KargoModule.forRoot(...) call' };
|
|
39
|
+
}
|
|
40
|
+
const objArg = callExpr.getArguments()[0];
|
|
41
|
+
if (!objArg || objArg.getKind() !== SyntaxKind.ObjectLiteralExpression) {
|
|
42
|
+
return { success: false, reason: 'KargoModule.forRoot(...) argument is not an object literal' };
|
|
43
|
+
}
|
|
44
|
+
const resourcesProp = objArg
|
|
45
|
+
.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
|
|
46
|
+
.getProperty('resources');
|
|
47
|
+
if (!resourcesProp || resourcesProp.getKind() !== SyntaxKind.PropertyAssignment) {
|
|
48
|
+
return { success: false, reason: 'Could not find a `resources: [...]` property' };
|
|
49
|
+
}
|
|
50
|
+
const arrayLiteral = resourcesProp
|
|
51
|
+
.asKindOrThrow(SyntaxKind.PropertyAssignment)
|
|
52
|
+
.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression);
|
|
53
|
+
if (!arrayLiteral) {
|
|
54
|
+
return { success: false, reason: '`resources` is not an array literal' };
|
|
55
|
+
}
|
|
56
|
+
const moduleDir = dirname(opts.appModulePath);
|
|
57
|
+
const resourceImport = toImportSpecifier(moduleDir, opts.resourcePath);
|
|
58
|
+
const serviceImport = toImportSpecifier(moduleDir, opts.servicePath);
|
|
59
|
+
sourceFile.addImportDeclaration({
|
|
60
|
+
namedImports: [`${opts.pascalName}Resource`],
|
|
61
|
+
moduleSpecifier: resourceImport,
|
|
62
|
+
});
|
|
63
|
+
sourceFile.addImportDeclaration({
|
|
64
|
+
namedImports: [`${opts.pascalName}Service`],
|
|
65
|
+
moduleSpecifier: serviceImport,
|
|
66
|
+
});
|
|
67
|
+
arrayLiteral.addElement(`{ resource: ${opts.pascalName}Resource, service: ${opts.pascalName}Service }`);
|
|
68
|
+
sourceFile.formatText({ indentSize: 2 });
|
|
69
|
+
sourceFile.saveSync();
|
|
70
|
+
return { success: true };
|
|
71
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare function toPascalCase(str: string): string;
|
|
2
|
+
export declare function toCamelCase(str: string): string;
|
|
3
|
+
export declare function toKebabCase(str: string): string;
|
|
4
|
+
export declare function toSnakeCase(str: string): string;
|
|
5
|
+
export declare function toTitleCase(str: string): string;
|
|
6
|
+
/** Very simple English pluralisation (covers the common cases). */
|
|
7
|
+
export declare function toPlural(str: string): string;
|
|
8
|
+
export declare function toSingular(str: string): string;
|
|
9
|
+
/** All name variants for a resource name (e.g. "BlogPost"). */
|
|
10
|
+
export interface ResourceNames {
|
|
11
|
+
/** BlogPost */
|
|
12
|
+
pascal: string;
|
|
13
|
+
/** blogPost */
|
|
14
|
+
camel: string;
|
|
15
|
+
/** blog-post */
|
|
16
|
+
kebab: string;
|
|
17
|
+
/** blog_post */
|
|
18
|
+
snake: string;
|
|
19
|
+
/** Blog Post */
|
|
20
|
+
title: string;
|
|
21
|
+
/** BlogPosts (plural pascal) */
|
|
22
|
+
pluralPascal: string;
|
|
23
|
+
/** blogPosts (plural camel) */
|
|
24
|
+
pluralCamel: string;
|
|
25
|
+
/** blog-posts (plural kebab) */
|
|
26
|
+
pluralKebab: string;
|
|
27
|
+
/** Blog Posts */
|
|
28
|
+
pluralTitle: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function resourceNames(input: string): ResourceNames;
|
|
31
|
+
//# sourceMappingURL=strings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/utils/strings.ts"],"names":[],"mappings":"AAWA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED,+DAA+D;AAC/D,MAAM,WAAW,aAAa;IAC5B,eAAe;IACf,MAAM,EAAE,MAAM,CAAC;IACf,eAAe;IACf,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAa1D"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Normalise any casing to an array of lowercase words. */
|
|
2
|
+
function words(str) {
|
|
3
|
+
return str
|
|
4
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase → camel Case
|
|
5
|
+
.replace(/[_\-\s]+/g, ' ') // separators → space
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.split(' ')
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
}
|
|
11
|
+
export function toPascalCase(str) {
|
|
12
|
+
return words(str)
|
|
13
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
14
|
+
.join('');
|
|
15
|
+
}
|
|
16
|
+
export function toCamelCase(str) {
|
|
17
|
+
const w = words(str);
|
|
18
|
+
return w
|
|
19
|
+
.map((word, i) => (i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
|
|
20
|
+
.join('');
|
|
21
|
+
}
|
|
22
|
+
export function toKebabCase(str) {
|
|
23
|
+
return words(str).join('-');
|
|
24
|
+
}
|
|
25
|
+
export function toSnakeCase(str) {
|
|
26
|
+
return words(str).join('_');
|
|
27
|
+
}
|
|
28
|
+
export function toTitleCase(str) {
|
|
29
|
+
return words(str)
|
|
30
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
31
|
+
.join(' ');
|
|
32
|
+
}
|
|
33
|
+
/** Very simple English pluralisation (covers the common cases). */
|
|
34
|
+
export function toPlural(str) {
|
|
35
|
+
if (/[^aeiou]y$/i.test(str))
|
|
36
|
+
return str.slice(0, -1) + 'ies';
|
|
37
|
+
if (/(s|x|z|ch|sh)$/i.test(str))
|
|
38
|
+
return str + 'es';
|
|
39
|
+
if (!str.endsWith('s'))
|
|
40
|
+
return str + 's';
|
|
41
|
+
return str;
|
|
42
|
+
}
|
|
43
|
+
export function toSingular(str) {
|
|
44
|
+
if (/ies$/i.test(str))
|
|
45
|
+
return str.slice(0, -3) + 'y';
|
|
46
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(str))
|
|
47
|
+
return str.slice(0, -2);
|
|
48
|
+
if (str.endsWith('s') && !str.endsWith('ss'))
|
|
49
|
+
return str.slice(0, -1);
|
|
50
|
+
return str;
|
|
51
|
+
}
|
|
52
|
+
export function resourceNames(input) {
|
|
53
|
+
const pascal = toPascalCase(toSingular(input));
|
|
54
|
+
const camel = toCamelCase(toSingular(input));
|
|
55
|
+
const kebab = toKebabCase(toSingular(input));
|
|
56
|
+
const snake = toSnakeCase(toSingular(input));
|
|
57
|
+
const title = toTitleCase(toSingular(input));
|
|
58
|
+
const pluralPascal = toPascalCase(toPlural(pascal));
|
|
59
|
+
const pluralCamel = toCamelCase(toPlural(pascal));
|
|
60
|
+
const pluralKebab = toKebabCase(toPlural(pascal));
|
|
61
|
+
const pluralTitle = toTitleCase(toPlural(pascal));
|
|
62
|
+
return { pascal, camel, kebab, snake, title, pluralPascal, pluralCamel, pluralKebab, pluralTitle };
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kargojs/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the Kargo admin framework — scaffold projects and resources",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"kargo": "./bin/kargo.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"files": ["dist", "bin"],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"check-types": "tsc --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^12.1.0",
|
|
23
|
+
"picocolors": "^1.1.1",
|
|
24
|
+
"ts-morph": "^28.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@repo/typescript-config": "*",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"typescript": "7.0.2"
|
|
30
|
+
}
|
|
31
|
+
}
|