Skip to content

Commit ce2c3de

Browse files
committed
feat: introduce CreateTarget command to the CLI
1 parent 57b8653 commit ce2c3de

File tree

19 files changed

+379
-2
lines changed

19 files changed

+379
-2
lines changed

dist/esm/apps/Helper/src/i18n.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ export declare const I18n: {
22
en: {
33
err_existing_app: string;
44
err_existing_product: string;
5+
err_existing_target: string;
6+
err_target_generator_not_available: string;
57
err_unknown_app: string;
68
uc_CreateApp_desc: string;
79
uc_CreateApp_label: string;
810
uc_CreateProduct_desc: string;
911
uc_CreateProduct_label: string;
1012
uc_CreateProject_desc: string;
1113
uc_CreateProject_label: string;
14+
uc_CreateTarget_desc: string;
15+
uc_CreateTarget_label: string;
1216
uc_CreateUC_desc: string;
1317
uc_CreateUC_label: string;
1418
uc_DeleteGeneratedAppsTests_desc: string;

dist/esm/apps/Helper/src/i18n.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ export const I18n = {
22
en: {
33
err_existing_app: 'App "{{appPath}}" already exists',
44
err_existing_product: 'Product "{{productPath}}" already exists',
5+
err_existing_target: 'Target "{{targetPath}}" already exists',
6+
err_target_generator_not_available: 'This target has not been implemented yet via the generator. In the meantime, you can create it manually by following the examples.',
57
err_unknown_app: 'App "{{appPath}}" does not exist',
68
uc_CreateApp_desc: 'Create the basics of an app (index, i18n, manifest)',
79
uc_CreateApp_label: 'Create an app',
810
uc_CreateProduct_desc: 'Create the basics of a product (i18n, manifest)',
911
uc_CreateProduct_label: 'Create a product',
1012
uc_CreateProject_desc: 'Create a project by initializing a git repo, creating basic config files and installing the required dependencies',
1113
uc_CreateProject_label: 'Create a project',
14+
uc_CreateTarget_desc: 'Create a target based on the ones provided by the lib (see https://libmodulor.c100k.eu/docs/references/targets)',
15+
uc_CreateTarget_label: 'Create a target',
1216
uc_CreateUC_desc: 'Create a basic use case',
1317
uc_CreateUC_label: 'Create a use case',
1418
uc_DeleteGeneratedAppsTests_desc: 'Delete the automated test suite generated for each app',
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import type { TargetName } from '../../../../../target/index.js';
2+
import type { Files } from '../SrcFilesGenerator.js';
3+
export declare function files(name: TargetName): Files;
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { PRODUCT_I18N_FILE_NAME, PRODUCT_I18N_NAME, PRODUCT_MANIFEST_FILE_NAME, PRODUCT_MANIFEST_NAME, } from '../../../../../convention.js';
2+
import { LIB_NAME } from '../consts.js';
3+
import { fileImportName } from '../funcs.js';
4+
const COMMON_CONTAINER_IMPORTS = `import { ${PRODUCT_I18N_NAME} } from '../${fileImportName(PRODUCT_I18N_FILE_NAME)}';
5+
import { ${PRODUCT_MANIFEST_NAME} } from '../${fileImportName(PRODUCT_MANIFEST_FILE_NAME)}';
6+
import { type S, settings } from './settings.js';`;
7+
const NODE_CORE_CLI_CONTAINER_TS = `import { Container } from 'inversify';
8+
import {
9+
bindCommon,
10+
bindProduct,
11+
CONTAINER_OPTS,
12+
updateSettings,
13+
} from '${LIB_NAME}';
14+
import { bindNodeCLI, bindNodeCore } from '${LIB_NAME}/node';
15+
16+
${COMMON_CONTAINER_IMPORTS}
17+
18+
const container = new Container(CONTAINER_OPTS);
19+
20+
bindCommon(container);
21+
updateSettings<S>(container, settings);
22+
bindNodeCore(container);
23+
bindNodeCLI(container);
24+
bindProduct(container, Manifest, I18n);
25+
26+
export default container;
27+
`;
28+
const NODE_CORE_CLI_INDEX_TS = `import { NodeCoreCLIManager } from '${LIB_NAME}/node';
29+
30+
import container from './container.js';
31+
32+
await container.get(NodeCoreCLIManager).handleCommand({
33+
srcImporter: (path) => import(path),
34+
});
35+
`;
36+
const NODE_CORE_CLI_SETTINGS_TS = `import {
37+
type ServerClientManagerSettings,
38+
TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
39+
} from '${LIB_NAME}';
40+
41+
export type S = ServerClientManagerSettings;
42+
43+
export const settings: S = {
44+
...TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
45+
};
46+
`;
47+
const NODE_EXPRESS_SERVER_CONTAINER_TS = `import { Container } from 'inversify';
48+
import {
49+
bindCommon,
50+
bindProduct,
51+
CONTAINER_OPTS,
52+
type ServerManager,
53+
updateSettings,
54+
} from '${LIB_NAME}';
55+
import { bindNodeCore } from '${LIB_NAME}/node';
56+
import {
57+
bindServer,
58+
NodeExpressServerManager,
59+
} from '${LIB_NAME}/node-express';
60+
61+
${COMMON_CONTAINER_IMPORTS}
62+
63+
const container = new Container(CONTAINER_OPTS);
64+
65+
bindCommon(container);
66+
updateSettings<S>(container, settings);
67+
bindNodeCore(container);
68+
bindServer(container);
69+
bindProduct(container, ${PRODUCT_MANIFEST_NAME}, ${PRODUCT_I18N_NAME});
70+
71+
container.bind<ServerManager>('ServerManager').to(NodeExpressServerManager);
72+
73+
export default container;
74+
`;
75+
const NODE_HONO_SERVER_CONTAINER_TS = `import { Container } from 'inversify';
76+
import {
77+
bindCommon,
78+
bindProduct,
79+
CONTAINER_OPTS,
80+
type ServerManager,
81+
updateSettings,
82+
} from '${LIB_NAME}';
83+
import { bindNodeCore } from '${LIB_NAME}/node';
84+
import {
85+
bindServer,
86+
NodeHonoServerManager,
87+
} from '${LIB_NAME}/node-hono';
88+
89+
${COMMON_CONTAINER_IMPORTS}
90+
91+
const container = new Container(CONTAINER_OPTS);
92+
93+
bindCommon(container);
94+
updateSettings<S>(container, settings);
95+
bindNodeCore(container);
96+
bindServer(container);
97+
bindProduct(container, ${PRODUCT_MANIFEST_NAME}, ${PRODUCT_I18N_NAME});
98+
99+
container.bind<ServerManager>('ServerManager').to(NodeHonoServerManager);
100+
101+
export default container;
102+
`;
103+
const NODE_MCP_SERVER_CONTAINER_TS = `import { Container } from 'inversify';
104+
import {
105+
bindCommon,
106+
bindProduct,
107+
CONTAINER_OPTS,
108+
type ServerManager,
109+
updateSettings,
110+
} from '${LIB_NAME}';
111+
import { bindNodeCore } from '${LIB_NAME}/node';
112+
import { NodeLocalStdioMCPServerManager } from '${LIB_NAME}/node-mcp';
113+
114+
${COMMON_CONTAINER_IMPORTS}
115+
116+
const container = new Container(CONTAINER_OPTS);
117+
118+
bindCommon(container);
119+
updateSettings<S>(container, settings);
120+
bindNodeCore(container);
121+
bindProduct(container, Manifest, I18n);
122+
123+
container
124+
.bind<ServerManager>('ServerManager')
125+
.to(NodeLocalStdioMCPServerManager);
126+
127+
export default container;
128+
`;
129+
const NODE_MCP_SERVER_INDEX_TS = `import { MCPServerBooter } from '${LIB_NAME}/node-mcp';
130+
131+
import container from './container.js';
132+
133+
await container.get(MCPServerBooter).exec({
134+
srcImporter: (path) => import(path),
135+
});
136+
`;
137+
const NODE_MCP_SERVER_SETTINGS_TS = `import {
138+
type LoggerSettings,
139+
type ServerManagerSettings,
140+
TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
141+
} from '${LIB_NAME}';
142+
143+
export type S = LoggerSettings & ServerManagerSettings;
144+
145+
export const settings: S = {
146+
...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
147+
logger_level: 'error',
148+
};
149+
`;
150+
const SERVER_SETTINGS_TS = `import {
151+
type JWTManagerSettings,
152+
type ServerManagerSettings,
153+
STD_DEFAULT_JWT_MANAGER_SETTINGS,
154+
TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
155+
} from '${LIB_NAME}';
156+
157+
export type S = JWTManagerSettings & ServerManagerSettings;
158+
159+
export const settings: S = {
160+
...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
161+
...STD_DEFAULT_JWT_MANAGER_SETTINGS,
162+
};
163+
`;
164+
const SERVER_INDEX_TS = `import { ServerBooter } from '${LIB_NAME}';
165+
166+
import container from './container.js';
167+
168+
await container.get(ServerBooter).exec({
169+
srcImporter: (path) => import(path),
170+
});
171+
`;
172+
const MAPPING = {
173+
'edge-worker-hono-server': new Map(),
174+
'nextjs-server': new Map(),
175+
'node-core-cli': new Map([
176+
[['.', 'container.ts'], NODE_CORE_CLI_CONTAINER_TS],
177+
[['.', 'index.ts'], NODE_CORE_CLI_INDEX_TS],
178+
[['.', 'settings.ts'], NODE_CORE_CLI_SETTINGS_TS],
179+
]),
180+
'node-express-server': new Map([
181+
[['.', 'container.ts'], NODE_EXPRESS_SERVER_CONTAINER_TS],
182+
[['.', 'index.ts'], SERVER_INDEX_TS],
183+
[['.', 'settings.ts'], SERVER_SETTINGS_TS],
184+
]),
185+
'node-hono-server': new Map([
186+
[['.', 'container.ts'], NODE_HONO_SERVER_CONTAINER_TS],
187+
[['.', 'index.ts'], SERVER_INDEX_TS],
188+
[['.', 'settings.ts'], SERVER_SETTINGS_TS],
189+
]),
190+
'node-mcp-server': new Map([
191+
[['.', 'container.ts'], NODE_MCP_SERVER_CONTAINER_TS],
192+
[['.', 'index.ts'], NODE_MCP_SERVER_INDEX_TS],
193+
[['.', 'settings.ts'], NODE_MCP_SERVER_SETTINGS_TS],
194+
]),
195+
'node-stricli-cli': new Map(),
196+
'react-native-pure': new Map(),
197+
'react-web-pure': new Map(),
198+
};
199+
export function files(name) {
200+
return MAPPING[name];
201+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import type { FilePath } from '../../../../dt/index.js';
2-
export type Creatable = 'App' | 'Project' | 'Product' | 'Use case';
2+
export type Creatable = 'App' | 'Project' | 'Product' | 'Target' | 'Use case';
33
export type Files = Map<FilePath[], string>;

dist/esm/apps/Helper/src/manifest.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export declare const Manifest: {
1717
icon: string;
1818
name: "CreateProject";
1919
};
20+
CreateTarget: {
21+
action: "Create";
22+
icon: string;
23+
name: "CreateTarget";
24+
};
2025
CreateUC: {
2126
action: "Create";
2227
icon: string;

dist/esm/apps/Helper/src/manifest.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export const Manifest = {
1717
icon: 'plus',
1818
name: 'CreateProject',
1919
},
20+
CreateTarget: {
21+
action: 'Create',
22+
icon: 'plus',
23+
name: 'CreateTarget',
24+
},
2025
CreateUC: {
2126
action: 'Create',
2227
icon: 'plus',
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { ProductName } from '../../../../product/index.js';
2+
import { type TargetName } from '../../../../target/index.js';
3+
import { type UCDef, type UCInputFieldValue } from '../../../../uc/index.js';
4+
import { type ProductInput } from '../lib/io.js';
5+
export interface CreateTargetInput extends ProductInput {
6+
productName: UCInputFieldValue<ProductName>;
7+
targetName: UCInputFieldValue<TargetName>;
8+
}
9+
export declare const CreateTargetUCD: UCDef<CreateTargetInput>;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5+
return c > 3 && r && Object.defineProperty(target, key, r), r;
6+
};
7+
var __metadata = (this && this.__metadata) || function (k, v) {
8+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9+
};
10+
var __param = (this && this.__param) || function (paramIndex, decorator) {
11+
return function (target, key) { decorator(target, key, paramIndex); }
12+
};
13+
import { inject, injectable } from 'inversify';
14+
import { PRODUCT_NAME_PLACEHOLDER, TARGET_NAME_PLACEHOLDER, } from '../../../../convention.js';
15+
import { TString } from '../../../../dt/index.js';
16+
import { IllegalArgumentError } from '../../../../error/index.js';
17+
import { TARGETS } from '../../../../target/index.js';
18+
import { EverybodyUCPolicy, } from '../../../../uc/index.js';
19+
import { successMessage } from '../lib/funcs.js';
20+
import { ProductInputFieldsDef } from '../lib/io.js';
21+
import { files } from '../lib/layers/target.js';
22+
import { SrcFilesGenerator } from '../lib/SrcFilesGenerator.js';
23+
import { Manifest } from '../manifest.js';
24+
let CreateTargetClientMain = class CreateTargetClientMain {
25+
fsManager;
26+
i18nManager;
27+
logger;
28+
srcFilesGenerator;
29+
rootPath;
30+
constructor(fsManager, i18nManager, logger, srcFilesGenerator) {
31+
this.fsManager = fsManager;
32+
this.i18nManager = i18nManager;
33+
this.logger = logger;
34+
this.srcFilesGenerator = srcFilesGenerator;
35+
}
36+
async exec({ uc }) {
37+
const productsPath = uc.reqVal0('productsPath');
38+
const productName = uc.reqVal0('productName');
39+
const targetName = uc.reqVal0('targetName');
40+
this.rootPath = this.fsManager.path(productsPath, productName, targetName);
41+
// TODO : Rollback the whole thing in case of failure
42+
const filesToGenerate = files(targetName);
43+
if (filesToGenerate.size === 0) {
44+
throw new IllegalArgumentError(this.i18nManager.t('err_target_generator_not_available'));
45+
}
46+
await this.assertNotExisting();
47+
await this.createRootDir();
48+
await this.srcFilesGenerator.exec({
49+
files: files(targetName),
50+
rootPath: this.rootPath,
51+
});
52+
this.logger.info(successMessage('Target'));
53+
}
54+
async assertNotExisting() {
55+
if (!(await this.fsManager.exists(this.rootPath))) {
56+
return;
57+
}
58+
throw new IllegalArgumentError(this.i18nManager.t('err_existing_target', {
59+
vars: { targetPath: this.rootPath },
60+
}));
61+
}
62+
async createRootDir() {
63+
this.logger.info('Creating root dir : %s', this.rootPath);
64+
if (await this.fsManager.exists(this.rootPath)) {
65+
return;
66+
}
67+
await this.fsManager.mkdir(this.rootPath, { recursive: true });
68+
}
69+
};
70+
CreateTargetClientMain = __decorate([
71+
injectable(),
72+
__param(0, inject('FSManager')),
73+
__param(1, inject('I18nManager')),
74+
__param(2, inject('Logger')),
75+
__param(3, inject(SrcFilesGenerator)),
76+
__metadata("design:paramtypes", [Object, Object, Object, SrcFilesGenerator])
77+
], CreateTargetClientMain);
78+
export const CreateTargetUCD = {
79+
ext: {
80+
cmd: {
81+
mountAt: 'CreateTarget',
82+
},
83+
},
84+
io: {
85+
i: {
86+
fields: {
87+
...ProductInputFieldsDef,
88+
productName: {
89+
type: new TString().setExamples([PRODUCT_NAME_PLACEHOLDER]),
90+
},
91+
targetName: {
92+
type: new TString()
93+
.setOptions(Object.keys(TARGETS).map((t) => ({
94+
label: t,
95+
value: t,
96+
})))
97+
.setExamples([TARGET_NAME_PLACEHOLDER]),
98+
},
99+
},
100+
},
101+
},
102+
lifecycle: {
103+
client: {
104+
main: CreateTargetClientMain,
105+
policy: EverybodyUCPolicy,
106+
},
107+
},
108+
metadata: Manifest.ucReg.CreateTarget,
109+
};

0 commit comments

Comments
 (0)