libmodulor 0.5.0 → 0.6.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.
Files changed (49) hide show
  1. package/.github/dependabot.yml +19 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +10 -4
  4. package/dist/esm/apps/Helper/src/i18n.js +4 -0
  5. package/dist/esm/apps/Helper/src/lib/project.d.ts +2 -0
  6. package/dist/esm/apps/Helper/src/lib/project.js +120 -0
  7. package/dist/esm/apps/Helper/src/manifest.d.ts +5 -0
  8. package/dist/esm/apps/Helper/src/manifest.js +5 -0
  9. package/dist/esm/apps/Helper/src/ucds/CreateProjectUCD.d.ts +15 -0
  10. package/dist/esm/apps/Helper/src/ucds/CreateProjectUCD.js +118 -0
  11. package/dist/esm/dt/index.d.ts +1 -1
  12. package/dist/esm/index.node.d.ts +1 -0
  13. package/dist/esm/index.node.js +1 -0
  14. package/dist/esm/index.react.d.ts +1 -1
  15. package/dist/esm/products/Helper/container.js +4 -0
  16. package/dist/esm/products/Helper/index.js +2 -0
  17. package/dist/esm/std/ShellCommandExecutor.d.ts +19 -0
  18. package/dist/esm/std/ShellCommandExecutor.js +1 -0
  19. package/dist/esm/std/impl/NodeSpawnShellCommandExecutor.d.ts +4 -0
  20. package/dist/esm/std/impl/NodeSpawnShellCommandExecutor.js +41 -0
  21. package/dist/esm/std/index.d.ts +1 -0
  22. package/dist/esm/std/index.js +1 -0
  23. package/dist/esm/target/lib/react/form.d.ts +26 -2
  24. package/dist/esm/target/lib/react/form.js +18 -1
  25. package/dist/esm/target/react-native-pure/UCForm.js +2 -2
  26. package/dist/esm/target/react-native-pure/UCFormField.d.ts +2 -9
  27. package/dist/esm/target/react-native-pure/UCFormField.js +9 -20
  28. package/dist/esm/target/react-native-pure/UCFormFieldControl.d.ts +3 -10
  29. package/dist/esm/target/react-native-pure/UCFormFieldControl.js +8 -8
  30. package/dist/esm/target/react-native-pure/UCFormFieldDesc.d.ts +2 -5
  31. package/dist/esm/target/react-native-pure/UCFormFieldDesc.js +2 -2
  32. package/dist/esm/target/react-native-pure/UCFormFieldErr.d.ts +2 -6
  33. package/dist/esm/target/react-native-pure/UCFormFieldLabel.d.ts +2 -5
  34. package/dist/esm/target/react-native-pure/UCFormFieldLabel.js +2 -2
  35. package/dist/esm/target/react-native-pure/UCFormSubmitControl.d.ts +2 -7
  36. package/dist/esm/target/react-native-pure/UCFormSubmitControl.js +1 -1
  37. package/dist/esm/target/react-web-pure/UCForm.js +1 -1
  38. package/dist/esm/target/react-web-pure/UCFormField.d.ts +2 -9
  39. package/dist/esm/target/react-web-pure/UCFormField.js +9 -20
  40. package/dist/esm/target/react-web-pure/UCFormFieldControl.d.ts +3 -10
  41. package/dist/esm/target/react-web-pure/UCFormFieldControl.js +20 -5
  42. package/dist/esm/target/react-web-pure/UCFormFieldDesc.d.ts +2 -5
  43. package/dist/esm/target/react-web-pure/UCFormFieldDesc.js +2 -2
  44. package/dist/esm/target/react-web-pure/UCFormFieldErr.d.ts +2 -6
  45. package/dist/esm/target/react-web-pure/UCFormFieldLabel.d.ts +2 -5
  46. package/dist/esm/target/react-web-pure/UCFormFieldLabel.js +3 -3
  47. package/dist/esm/target/react-web-pure/UCFormSubmitControl.d.ts +2 -4
  48. package/dist/esm/target/react-web-pure/UCFormSubmitControl.js +1 -1
  49. package/package.json +8 -9
@@ -0,0 +1,19 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "npm"
9
+ directory: "/"
10
+ schedule:
11
+ interval: "weekly"
12
+ ignore:
13
+ - dependency-name: "inversify"
14
+ versions: ["7.x"]
15
+ groups:
16
+ dev:
17
+ dependency-type: "development"
18
+ prod:
19
+ dependency-type: "production"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.6.0 (2025-02-28)
4
+
5
+ **BREAKING**
6
+
7
+ - Extract common react elements on web and rn, renaming some of the props : check the new props names and the new `validateFormField` to simplify your overrides
8
+ - Remove `helper` from the exports map : it makes no sense to expose it as it is an executable (see `npx libmodulor` below)
9
+
10
+ **Added**
11
+
12
+ - Introduce the `npx libmodulor CreateProject` command
13
+ - Introduce `select` to target `react-web-pure` : it now renders an HTML `select` when the UC input field `hasOptions()`
14
+
15
+ **Misc**
16
+
17
+ - Bump `react` to `19.0.0`
18
+ - Add a new "Style the web Target" tutorial step showing how to use `tailwindcss` and `daisyUI` to provide custom components
19
+
3
20
  ## v0.5.0 (2025-02-24)
4
21
 
5
22
  **BREAKING**
package/README.md CHANGED
@@ -27,11 +27,17 @@ Applications created with `libmodulor` have **6 main properties** :
27
27
 
28
28
  ## 🚀 Getting Started
29
29
 
30
- To get started, we recommend reading the [📖 Introduction](https://github.com/c100k/libmodulor/blob/v0.5.0/docs/Introduction.md) first. It will give you an overview of what `libmodulor` is and how it works.
30
+ If you're discovering `libmodulor`, we recommend reading the [📖 Introduction](https://github.com/c100k/libmodulor/blob/v0.6.0/docs/Introduction.md) first. It will give you an overview of what `libmodulor` is and how it works.
31
31
 
32
- Then, you can follow the [🚀 Tutorial](https://github.com/c100k/libmodulor/blob/v0.5.0/docs/Tutorial.md) that will show you all the main notions by building something real. We'll build a small Trading app that will allow us to buy an asset from a web page, a Terminal, Claude Desktop, Android and iOS ! All within a single, simple codebase.
32
+ If you want to learn by doing, you can follow the [🚀 Tutorial](https://github.com/c100k/libmodulor/blob/v0.6.0/docs/Tutorial.md). It contains multiple steps showing you the basics of `libmodulor`. We'll build a small Trading app that will allow us to buy an asset from `curl`, a SPA, a CLI, Claude Desktop, an Android mobile app and an iOS mobile app !
33
33
 
34
- Finally, for more advanced usages, go to the [📜 Guides](https://github.com/c100k/libmodulor/blob/v0.5.0/docs/Guides.md).
34
+ If you're felling adventurous, create a project and start building your own application, with your own apps, use cases, products, targets :
35
+
36
+ ```sh
37
+ npx libmodulor CreateProject --outPath ~/Downloads --projectName libmodulor-projx
38
+ ```
39
+
40
+ Finally, for more advanced uses, check out the [📜 Guides](https://github.com/c100k/libmodulor/blob/v0.6.0/docs/Guides.md).
35
41
 
36
42
  ## 👨‍💻 Contribute
37
43
 
@@ -39,4 +45,4 @@ If you think you can help in any way, feel free to contact me (cf. `author` in `
39
45
 
40
46
  ## ⚖️ License
41
47
 
42
- [LGPL-3.0](./LICENSE)
48
+ [LGPL-3.0](https://github.com/c100k/libmodulor/blob/v0.6.0/LICENSE)
@@ -1,6 +1,8 @@
1
1
  export const I18n = {
2
2
  en: {
3
3
  err_unknown_app: 'Unknown app : {{appPath}}',
4
+ uc_CreateProject_desc: 'Create a project by initializing a git repo, creating basic config files and installing the required dependencies',
5
+ uc_CreateProject_label: 'Create a project',
4
6
  uc_DeleteGeneratedAppsTests_desc: 'Delete the automated test suite generated for each app',
5
7
  uc_DeleteGeneratedAppsTests_label: 'Delete generated apps tests',
6
8
  uc_GenerateAppsTests_desc: 'Generate an automated test suite for each app',
@@ -9,6 +11,8 @@ export const I18n = {
9
11
  uc_TestApp_label: 'Test app',
10
12
  ucif_appPath_desc: 'The path of the app',
11
13
  ucif_appsPath_desc: 'The path to the directory containing all the apps',
14
+ ucif_projectName_desc: "Name of the project conforming to the package.json's spec (i.e. a-z, 0-9, -)",
15
+ ucif_outPath_desc: 'Path to a directory where to create the project. Do not include the project name in it. It is created recursively if missing.',
12
16
  ucif_monkeyTestingTimeoutInMs_desc: 'These tests can take longer than the usual default of 5000ms because they try lots of possibilities',
13
17
  ucif_depsMapping_desc: 'The mapping of dependencies in case some of them need a specific pattern (e.g. one directory above the default)',
14
18
  ucif_serverPortRangeStart_desc: 'The port number to start with when generating the server to test (incremented by 1) for each app',
@@ -0,0 +1,2 @@
1
+ import type { FileName, Slug } from '../../../../dt/index.js';
2
+ export declare function projectFiles(name: Slug): Map<FileName, string>;
@@ -0,0 +1,120 @@
1
+ const GITIGNORE = `coverage
2
+ dist
3
+ node_modules
4
+ src/apps/**/test/reports
5
+ src/apps/**/test/*.test.ts
6
+ src/products/**/rn/.expo
7
+ .env
8
+ `;
9
+ const BIOME_JSON = `{
10
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
11
+ "files": {
12
+ "ignore": ["coverage", "dist", "node_modules"],
13
+ "ignoreUnknown": true
14
+ },
15
+ "formatter": {
16
+ "indentStyle": "space",
17
+ "indentWidth": 4
18
+ },
19
+ "javascript": {
20
+ "formatter": {
21
+ "quoteStyle": "single"
22
+ },
23
+ "parser": {
24
+ "unsafeParameterDecoratorsEnabled": true
25
+ }
26
+ }
27
+ }
28
+ `;
29
+ const PACKAGE_JSON = (name) => `{
30
+ "name": "${name}",
31
+ "version": "0.1.0",
32
+ "type": "module",
33
+ "private": true,
34
+ "scripts": {
35
+ "lint": "biome check --write .",
36
+ "test": "tsc && vitest run --passWithNoTests"
37
+ },
38
+ "dependencies": {
39
+ "inversify": "^6.2.2",
40
+ "libmodulor": "^0.5.0",
41
+ "reflect-metadata": "^0.2.2"
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^1.9.4",
45
+ "@types/node": "^22.13.5",
46
+ "@vitest/coverage-v8": "^3.0.7",
47
+ "buffer": "^6.0.3",
48
+ "cookie-parser": "^1.4.7",
49
+ "express": "^4.21.2",
50
+ "express-fileupload": "^1.5.1",
51
+ "fast-check": "^3.23.2",
52
+ "helmet": "^8.0.0",
53
+ "jose": "^6.0.8",
54
+ "typescript": "^5.7.3",
55
+ "vite": "^6.2.0",
56
+ "vitest": "^3.0.7"
57
+ }
58
+ }
59
+ `;
60
+ const README_MD = (name) => `# ${name}
61
+
62
+ 🚀🚀🚀
63
+ `;
64
+ const TSCONFIG_JSON = `{
65
+ "compilerOptions": {
66
+ "allowSyntheticDefaultImports": true,
67
+ "declaration": true,
68
+ "lib": ["dom", "esnext"],
69
+ "module": "NodeNext",
70
+ "moduleResolution": "NodeNext",
71
+ "noEmit": true,
72
+ "removeComments": true,
73
+ "skipLibCheck": true,
74
+ "sourceMap": true,
75
+ "target": "ESNext",
76
+
77
+ "strict": true,
78
+ "allowUnreachableCode": false,
79
+ "allowUnusedLabels": false,
80
+ "exactOptionalPropertyTypes": true,
81
+ "noFallthroughCasesInSwitch": true,
82
+ "noPropertyAccessFromIndexSignature": true,
83
+ "noImplicitOverride": true,
84
+ "noImplicitReturns": true,
85
+ "noUncheckedIndexedAccess": true,
86
+ "noUnusedLocals": true,
87
+ "noUnusedParameters": true,
88
+ "verbatimModuleSyntax": true,
89
+
90
+ "emitDecoratorMetadata": true,
91
+ "experimentalDecorators": true,
92
+
93
+ "jsx": "react"
94
+ }
95
+ }
96
+ `;
97
+ const VITEST_CONFIG_TS = `import { defineConfig } from 'vitest/config';
98
+
99
+ export default defineConfig({
100
+ test: {
101
+ coverage: {
102
+ enabled: true,
103
+ exclude: ['src/apps/**/test', 'src/**/*.test.ts'],
104
+ include: ['src'],
105
+ reporter: ['html', 'lcov', 'text'],
106
+ },
107
+ reporters: ['verbose'],
108
+ },
109
+ });
110
+ `;
111
+ export function projectFiles(name) {
112
+ return new Map([
113
+ ['.gitignore', GITIGNORE],
114
+ ['biome.json', BIOME_JSON],
115
+ ['package.json', PACKAGE_JSON(name)],
116
+ ['README.md', README_MD(name)],
117
+ ['tsconfig.json', TSCONFIG_JSON],
118
+ ['vitest.config.ts', VITEST_CONFIG_TS],
119
+ ]);
120
+ }
@@ -2,6 +2,11 @@ export declare const Manifest: {
2
2
  languageCodes: "en"[];
3
3
  name: "Helper";
4
4
  ucReg: {
5
+ CreateProject: {
6
+ action: "Create";
7
+ icon: string;
8
+ name: "CreateProject";
9
+ };
5
10
  DeleteGeneratedAppsTests: {
6
11
  action: "Delete";
7
12
  icon: string;
@@ -2,6 +2,11 @@ export const Manifest = {
2
2
  languageCodes: ['en'],
3
3
  name: 'Helper',
4
4
  ucReg: {
5
+ CreateProject: {
6
+ action: 'Create',
7
+ icon: 'plus',
8
+ name: 'CreateProject',
9
+ },
5
10
  DeleteGeneratedAppsTests: {
6
11
  action: 'Delete',
7
12
  icon: 'trash-can',
@@ -0,0 +1,15 @@
1
+ import { type DirPath, type Slug } from '../../../../dt/index.js';
2
+ import type { FSManager, Logger, ShellCommandExecutor } from '../../../../std/index.js';
3
+ import { type UCDef, type UCInput, type UCInputFieldValue, type UCMain, type UCMainInput } from '../../../../uc/index.js';
4
+ export interface CreateProjectInput extends UCInput {
5
+ outPath: UCInputFieldValue<DirPath>;
6
+ projectName: UCInputFieldValue<Slug>;
7
+ }
8
+ export declare class CreateProjectClientMain implements UCMain<CreateProjectInput> {
9
+ private fsManager;
10
+ private logger;
11
+ private shellCommandExecutor;
12
+ constructor(fsManager: FSManager, logger: Logger, shellCommandExecutor: ShellCommandExecutor);
13
+ exec({ uc }: UCMainInput<CreateProjectInput>): Promise<void>;
14
+ }
15
+ export declare const CreateProjectUCD: UCDef<CreateProjectInput>;
@@ -0,0 +1,118 @@
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 { APPS_ROOT_PATH, PRODUCTS_ROOT_PATH } from '../../../../convention.js';
15
+ import { TDirPath, TSlug, } from '../../../../dt/index.js';
16
+ import { EverybodyUCPolicy, } from '../../../../uc/index.js';
17
+ import { projectFiles } from '../lib/project.js';
18
+ import { Manifest } from '../manifest.js';
19
+ let CreateProjectClientMain = class CreateProjectClientMain {
20
+ fsManager;
21
+ logger;
22
+ shellCommandExecutor;
23
+ constructor(fsManager, logger, shellCommandExecutor) {
24
+ this.fsManager = fsManager;
25
+ this.logger = logger;
26
+ this.shellCommandExecutor = shellCommandExecutor;
27
+ }
28
+ async exec({ uc }) {
29
+ const outPath = uc.reqVal0('outPath');
30
+ const projectName = uc.reqVal0('projectName');
31
+ const cwd = this.fsManager.path(outPath, projectName);
32
+ this.logger.info('Creating root dir : %s', cwd);
33
+ await this.fsManager.mkdir(cwd, { recursive: true });
34
+ this.logger.info('Initializing git repository');
35
+ await this.shellCommandExecutor.exec({
36
+ bin: 'git',
37
+ opts: { args: ['init'], cwd },
38
+ });
39
+ this.logger.info('Creating config files');
40
+ const files = projectFiles(projectName);
41
+ for await (const [fileName, content] of files) {
42
+ const path = this.fsManager.path(cwd, fileName);
43
+ await this.fsManager.touch(path, content);
44
+ }
45
+ const dirs = [APPS_ROOT_PATH, PRODUCTS_ROOT_PATH];
46
+ this.logger.info('Creating apps and products directories');
47
+ for await (const dirPath of dirs) {
48
+ const path = this.fsManager.path(cwd, ...dirPath);
49
+ await this.fsManager.mkdir(path, { recursive: true });
50
+ await this.fsManager.touch(this.fsManager.path(path, '.gitkeep'), '');
51
+ }
52
+ this.logger.info('Installing dependencies');
53
+ await this.shellCommandExecutor.exec({
54
+ bin: 'yarn',
55
+ opts: { args: ['install'], cwd },
56
+ });
57
+ this.logger.info('Committing');
58
+ const cmdArgs = [
59
+ ['branch', '-M', 'master'],
60
+ ['add', '.'],
61
+ ['commit', '-am', '"chore: initial commit"'],
62
+ ];
63
+ for await (const args of cmdArgs) {
64
+ await this.shellCommandExecutor.exec({
65
+ bin: 'git',
66
+ opts: { args, cwd },
67
+ });
68
+ }
69
+ const devCmds = ['lint', 'test'];
70
+ for await (const cmd of devCmds) {
71
+ this.logger.info('Testing dev command : yarn %s', cmd);
72
+ await this.shellCommandExecutor.exec({
73
+ bin: 'yarn',
74
+ opts: { args: [cmd], cwd },
75
+ });
76
+ }
77
+ this.logger.info('Done ! Project ready ! ✅ 🚀');
78
+ }
79
+ };
80
+ CreateProjectClientMain = __decorate([
81
+ injectable(),
82
+ __param(0, inject('FSManager')),
83
+ __param(1, inject('Logger')),
84
+ __param(2, inject('ShellCommandExecutor')),
85
+ __metadata("design:paramtypes", [Object, Object, Object])
86
+ ], CreateProjectClientMain);
87
+ export { CreateProjectClientMain };
88
+ export const CreateProjectUCD = {
89
+ ext: {
90
+ cmd: {
91
+ mountAt: 'CreateProject',
92
+ },
93
+ },
94
+ io: {
95
+ i: {
96
+ fields: {
97
+ outPath: {
98
+ cardinality: {
99
+ min: 0,
100
+ },
101
+ type: new TDirPath()
102
+ .setDefaultValue('./')
103
+ .setExamples([['~', 'Desktop'].join('/')]),
104
+ },
105
+ projectName: {
106
+ type: new TSlug(),
107
+ },
108
+ },
109
+ },
110
+ },
111
+ lifecycle: {
112
+ client: {
113
+ main: CreateProjectClientMain,
114
+ policy: EverybodyUCPolicy,
115
+ },
116
+ },
117
+ metadata: Manifest.ucReg.CreateProject,
118
+ };
@@ -4,7 +4,7 @@ export { TInt, type TIntConstraints } from './base/TInt.js';
4
4
  export { TNumber, type TNumberConstraints } from './base/TNumber.js';
5
5
  export { TObject, type TObjectConstraints, TObjectShapeValidationStrategy, } from './base/TObject.js';
6
6
  export { TString, type TStringConstraints } from './base/TString.js';
7
- export { TUInt, type TUIntConstraints } from './base/TUInt.js';
7
+ export { TUInt, type TUIntConstraints, type UInt } from './base/TUInt.js';
8
8
  export { type Address, TAddress } from './final/TAddress.js';
9
9
  export { type Amount, TAmount } from './final/TAmount.js';
10
10
  export { type ApiKey, TApiKey } from './final/TApiKey.js';
@@ -6,6 +6,7 @@ export { NodeFormDataBuilder } from './std/impl/NodeFormDataBuilder.js';
6
6
  export { NodeFSManager } from './std/impl/NodeFSManager.js';
7
7
  export { NodeHTTPAPICallExecutorAgentBuilder } from './std/impl/NodeHTTPAPICallExecutorAgentBuilder.js';
8
8
  export { NodePromptManager } from './std/impl/NodePromptManager.js';
9
+ export { NodeSpawnShellCommandExecutor } from './std/impl/NodeSpawnShellCommandExecutor.js';
9
10
  export { NodeCoreCLIManager } from './target/node-core-cli/NodeCoreCLIManager.js';
10
11
  export { NodeExpressServerManager } from './target/node-express-server/NodeExpressServerManager.js';
11
12
  export { bindNodeCLI } from './utils/ioc/bindNodeCLI.js';
@@ -6,6 +6,7 @@ export { NodeFormDataBuilder } from './std/impl/NodeFormDataBuilder.js';
6
6
  export { NodeFSManager } from './std/impl/NodeFSManager.js';
7
7
  export { NodeHTTPAPICallExecutorAgentBuilder } from './std/impl/NodeHTTPAPICallExecutorAgentBuilder.js';
8
8
  export { NodePromptManager } from './std/impl/NodePromptManager.js';
9
+ export { NodeSpawnShellCommandExecutor } from './std/impl/NodeSpawnShellCommandExecutor.js';
9
10
  export { NodeCoreCLIManager } from './target/node-core-cli/NodeCoreCLIManager.js';
10
11
  export { NodeExpressServerManager } from './target/node-express-server/NodeExpressServerManager.js';
11
12
  export { bindNodeCLI } from './utils/ioc/bindNodeCLI.js';
@@ -1,5 +1,5 @@
1
1
  export { DIContext, useDIContext, DIContextProvider, type DIContextT, } from './target/lib/react/DIContextProvider.js';
2
- export type { UCFormFieldControlOnChange, UCFormProps, RenderUCForm, } from './target/lib/react/form.js';
2
+ export type { UCFormFieldControlOnChange, UCFormFieldControlProps, UCFormFieldDescProps, UCFormFieldElement, UCFormFieldErrProps, UCFormFieldLabelProps, UCFormFieldProps, UCFormProps, UCFormSubmitControlProps, UC_FORM_FIELD_ELEMENTS, RenderUCForm, validateFormField, } from './target/lib/react/form.js';
3
3
  export type { UCPanelCtx, UCPanelOnDone, UCPanelOnError, UCPanelOnInit, UCPanelOnStartSubmitting, UCPanelOnSubmit, UCPanelState, } from './target/lib/react/panel.js';
4
4
  export type { UCEntrypointTouchableProps, RenderUCEntrypointTouchable, RenderUCExecTouchable, UCExecTouchableProps, } from './target/lib/react/touchable.js';
5
5
  export { UCContainer } from './target/lib/react/UCContainer.js';
@@ -1,4 +1,5 @@
1
1
  import { Container } from 'inversify';
2
+ import { NodeSpawnShellCommandExecutor } from '../../std/impl/NodeSpawnShellCommandExecutor.js';
2
3
  import { VitestAppTestSuiteEmitter } from '../../testing/impl/VitestAppTestSuiteEmitter.js';
3
4
  import { VitestAppTestSuiteRunner } from '../../testing/impl/VitestAppTestSuiteRunner.js';
4
5
  import { CONTAINER_OPTS } from '../../utils/index.js';
@@ -19,4 +20,7 @@ container
19
20
  container
20
21
  .bind('AppTestSuiteRunner')
21
22
  .to(VitestAppTestSuiteRunner);
23
+ container
24
+ .bind('ShellCommandExecutor')
25
+ .to(NodeSpawnShellCommandExecutor);
22
26
  export default container;
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  import { APPS_ROOT_DIR_NAME } from '../../convention.js';
2
4
  import { NodeCoreCLIManager } from '../../target/node-core-cli/NodeCoreCLIManager.js';
3
5
  import container from './container.js';
@@ -0,0 +1,19 @@
1
+ import type { FilePath } from '../dt/index.js';
2
+ import type { Worker } from './Worker.js';
3
+ export type ShellCommandExecutorCommandArg = string;
4
+ export type ShellCommandExecutorCommandBin = 'docker' | 'file' | 'git' | 'open' | 'ssh-keygen' | 'ssh-keyscan' | 'unzip' | 'yarn' | 'zip' | (string & {});
5
+ export type ShellCommandExecutorEnv = Record<string, string>;
6
+ export type ShellCommandExecutorInstruction = string;
7
+ export type ShellCommandExecutorScript = string;
8
+ export type ShellCommandExecutorShebang = '#!/bin/bash';
9
+ export interface ShellCommandExecutorInput {
10
+ bin: ShellCommandExecutorCommandBin;
11
+ opts?: {
12
+ args?: ShellCommandExecutorCommandArg[];
13
+ cwd?: FilePath;
14
+ env?: ShellCommandExecutorEnv;
15
+ };
16
+ }
17
+ export type ShellCommandExecutorOutput = string;
18
+ export interface ShellCommandExecutor extends Worker<ShellCommandExecutorInput, Promise<ShellCommandExecutorOutput>> {
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { ShellCommandExecutor, ShellCommandExecutorInput, ShellCommandExecutorOutput } from '../ShellCommandExecutor.js';
2
+ export declare class NodeSpawnShellCommandExecutor implements ShellCommandExecutor {
3
+ exec({ bin, opts, }: ShellCommandExecutorInput): Promise<ShellCommandExecutorOutput>;
4
+ }
@@ -0,0 +1,41 @@
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
+ import { spawn } from 'node:child_process';
8
+ import { injectable } from 'inversify';
9
+ let NodeSpawnShellCommandExecutor = class NodeSpawnShellCommandExecutor {
10
+ async exec({ bin, opts, }) {
11
+ return new Promise((resolve, reject) => {
12
+ let stderr = '';
13
+ let stdout = '';
14
+ const proc = spawn(bin, opts?.args || [], {
15
+ cwd: opts?.cwd,
16
+ env: opts?.env,
17
+ });
18
+ proc.stderr.on('data', (chunk) => {
19
+ stderr += chunk;
20
+ });
21
+ proc.stdout.on('data', (chunk) => {
22
+ stdout += chunk;
23
+ });
24
+ proc.stdout.on('error', (err) => {
25
+ reject(err);
26
+ });
27
+ proc.on('close', (code, signal) => {
28
+ if (code === 0) {
29
+ resolve(stdout);
30
+ }
31
+ else {
32
+ reject(new Error(`Command failed with exit code (${code}), signal (${signal}), stderr (${stderr}) stdout (${stdout})`));
33
+ }
34
+ });
35
+ });
36
+ }
37
+ };
38
+ NodeSpawnShellCommandExecutor = __decorate([
39
+ injectable()
40
+ ], NodeSpawnShellCommandExecutor);
41
+ export { NodeSpawnShellCommandExecutor };
@@ -17,5 +17,6 @@ export * from './LLMManager.js';
17
17
  export * from './Logger.js';
18
18
  export * from './PromptManager.js';
19
19
  export * from './SettingsManager.js';
20
+ export * from './ShellCommandExecutor.js';
20
21
  export * from './Worker.js';
21
22
  export * from './XMLManager.js';
@@ -17,5 +17,6 @@ export * from './LLMManager.js';
17
17
  export * from './Logger.js';
18
18
  export * from './PromptManager.js';
19
19
  export * from './SettingsManager.js';
20
+ export * from './ShellCommandExecutor.js';
20
21
  export * from './Worker.js';
21
22
  export * from './XMLManager.js';
@@ -1,10 +1,34 @@
1
1
  import type { ReactElement } from 'react';
2
- import type { DataType } from '../../../dt/index.js';
2
+ import type { DataType, ErrorMessage } from '../../../dt/index.js';
3
+ import type { I18nManager } from '../../../std/index.js';
3
4
  import type { UCInput, UCInputField, UCInputFieldChangeOperator, UCInputFieldValue, UCOPIBase } from '../../../uc/index.js';
4
- import type { UCPanelCtx, UCPanelOnSubmit } from './panel.js';
5
+ import type { UCPanelCtx, UCPanelOnSubmit, UCPanelState } from './panel.js';
5
6
  export type UCFormFieldControlOnChange<T extends DataType = DataType> = (f: UCInputField<T>, op: UCInputFieldChangeOperator, v: UCInputFieldValue<T>) => void;
7
+ export type UCFormFieldControlProps<T extends DataType> = UCPanelState & {
8
+ errMsg?: ErrorMessage | null;
9
+ f: UCInputField<T>;
10
+ onChange: UCFormFieldControlOnChange<T>;
11
+ };
12
+ export type UCFormFieldDescProps<T extends DataType> = {
13
+ f: UCInputField<T>;
14
+ };
15
+ export type UCFormFieldErrProps = {
16
+ errMsg?: ErrorMessage | null;
17
+ };
18
+ export type UCFormFieldLabelProps<T extends DataType> = {
19
+ f: UCInputField<T>;
20
+ };
21
+ export declare const UC_FORM_FIELD_ELEMENTS: readonly ["control", "desc", "err", "label"];
22
+ export type UCFormFieldElement = (typeof UC_FORM_FIELD_ELEMENTS)[number];
23
+ export type UCFormFieldProps<T extends DataType> = UCFormFieldControlProps<T> & {
24
+ only?: UCFormFieldElement[];
25
+ };
6
26
  export type UCFormProps<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = UCPanelCtx<I, OPI0, OPI1> & {
7
27
  onChange: UCFormFieldControlOnChange;
8
28
  onSubmit: UCPanelOnSubmit;
9
29
  };
30
+ export type UCFormSubmitControlProps<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = UCPanelCtx<I, OPI0, OPI1> & {
31
+ onPress?: () => Promise<void>;
32
+ };
10
33
  export type RenderUCForm<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = (props: UCFormProps<I, OPI0, OPI1>) => ReactElement;
34
+ export declare function validateFormField<T extends DataType = DataType>(i18nManager: I18nManager, f: UCInputField<T>, v: UCInputFieldValue<T>): ErrorMessage | null;
@@ -1 +1,18 @@
1
- export {};
1
+ export const UC_FORM_FIELD_ELEMENTS = [
2
+ 'control',
3
+ 'desc',
4
+ 'err',
5
+ 'label',
6
+ ];
7
+ export function validateFormField(i18nManager, f, v) {
8
+ const vArr = Array.isArray(v) ? v : [v];
9
+ for (const vv of vArr) {
10
+ const validation = f.def.type.assign(vv).validate();
11
+ const violation = validation.get();
12
+ if (violation) {
13
+ const [key, expected] = violation;
14
+ return i18nManager.t(key, { vars: { expected } });
15
+ }
16
+ }
17
+ return null;
18
+ }
@@ -1,13 +1,13 @@
1
1
  import React, {} from 'react';
2
2
  import { View } from 'react-native';
3
3
  import { UCFormField } from './UCFormField.js';
4
- import { UCFormSubmitControl, } from './UCFormSubmitControl.js';
4
+ import { UCFormSubmitControl } from './UCFormSubmitControl.js';
5
5
  export function UCForm({ disabled, execState, onChange, onSubmit, uc, }) {
6
6
  const onPress = async () => {
7
7
  await onSubmit();
8
8
  };
9
9
  return (React.createElement(View, null,
10
10
  uc.inputFieldsForForm().map((f) => (React.createElement(View, { key: f.key },
11
- React.createElement(UCFormField, { disabled: disabled, execState: execState, field: f, onChange: onChange })))),
11
+ React.createElement(UCFormField, { disabled: disabled, execState: execState, f: f, onChange: onChange })))),
12
12
  React.createElement(UCFormSubmitControl, { execState: execState, disabled: disabled, onPress: onPress, uc: uc })));
13
13
  }
@@ -1,11 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import { type Props as FormFieldControlProps } from './UCFormFieldControl.js';
4
- import { type Props as FormFieldLabelProps } from './UCFormFieldLabel.js';
5
- declare const ELEMENTS: readonly ["control", "desc", "err", "label"];
6
- type Element = (typeof ELEMENTS)[number];
7
- type Props<T extends DataType> = FormFieldControlProps<T> & FormFieldLabelProps<T> & {
8
- only?: Element[];
9
- };
10
- export declare function UCFormField<T extends DataType>({ disabled, execState, field, onChange: onChangeBase, only, }: Props<T>): ReactElement;
11
- export {};
3
+ import { type UCFormFieldProps } from '../lib/react/form.js';
4
+ export declare function UCFormField<T extends DataType>({ disabled, execState, f, onChange: onChangeBase, only, }: UCFormFieldProps<T>): ReactElement;
@@ -1,32 +1,21 @@
1
1
  import React, { useState } from 'react';
2
2
  import { useDIContext } from '../lib/react/DIContextProvider.js';
3
- import { UCFormFieldControl, } from './UCFormFieldControl.js';
3
+ import { UC_FORM_FIELD_ELEMENTS, validateFormField, } from '../lib/react/form.js';
4
+ import { UCFormFieldControl } from './UCFormFieldControl.js';
4
5
  import { UCFormFieldDesc } from './UCFormFieldDesc.js';
5
6
  import { UCFormFieldErr } from './UCFormFieldErr.js';
6
- import { UCFormFieldLabel, } from './UCFormFieldLabel.js';
7
- const ELEMENTS = ['control', 'desc', 'err', 'label'];
8
- export function UCFormField({ disabled, execState, field, onChange: onChangeBase, only, }) {
7
+ import { UCFormFieldLabel } from './UCFormFieldLabel.js';
8
+ export function UCFormField({ disabled, execState, f, onChange: onChangeBase, only, }) {
9
9
  const { i18nManager } = useDIContext();
10
- const { type } = field.def;
11
10
  const [errMsg, setErrMsg] = useState(null);
12
- const elements = only ?? ELEMENTS;
13
11
  const onChange = (f, op, v) => {
14
- setErrMsg(null);
15
- const vArr = Array.isArray(v) ? v : [v];
16
- for (const vv of vArr) {
17
- const validation = type.assign(vv).validate();
18
- const violation = validation.get();
19
- if (violation) {
20
- const [key, expected] = violation;
21
- setErrMsg(i18nManager.t(key, { vars: { expected } }));
22
- break;
23
- }
24
- }
12
+ setErrMsg(validateFormField(i18nManager, f, v));
25
13
  onChangeBase(f, op, v);
26
14
  };
15
+ const elements = only ?? UC_FORM_FIELD_ELEMENTS;
27
16
  return (React.createElement(React.Fragment, null,
28
- elements.includes('label') && React.createElement(UCFormFieldLabel, { field: field }),
29
- elements.includes('control') && (React.createElement(UCFormFieldControl, { disabled: disabled, execState: execState, field: field, onChange: onChange })),
17
+ elements.includes('label') && React.createElement(UCFormFieldLabel, { f: f }),
18
+ elements.includes('control') && (React.createElement(UCFormFieldControl, { disabled: disabled, execState: execState, f: f, onChange: onChange })),
30
19
  elements.includes('err') && errMsg && (React.createElement(UCFormFieldErr, { errMsg: errMsg })),
31
- elements.includes('desc') && React.createElement(UCFormFieldDesc, { field: field })));
20
+ elements.includes('desc') && React.createElement(UCFormFieldDesc, { f: f })));
32
21
  }
@@ -1,11 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
- import type { DataType, ErrorMessage } from '../../dt/index.js';
3
- import { type UCInputField } from '../../uc/index.js';
4
- import type { UCFormFieldControlOnChange } from '../lib/react/form.js';
5
- import type { UCPanelState } from '../lib/react/panel.js';
6
- export type Props<T extends DataType> = UCPanelState & {
7
- errMsg?: ErrorMessage | null;
8
- field: UCInputField<T>;
9
- onChange: UCFormFieldControlOnChange<T>;
10
- };
11
- export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, field, onChange: onChangeBase, }: Props<T>): ReactElement;
2
+ import type { DataType } from '../../dt/index.js';
3
+ import type { UCFormFieldControlProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, f, onChange: onChangeBase, }: UCFormFieldControlProps<T>): ReactElement;
@@ -4,23 +4,23 @@ import { UCInputFieldChangeOperator, ucifRepeatability, } from '../../uc/index.j
4
4
  import { isBlank } from '../../utils/index.js';
5
5
  import { rnInputDef } from '../lib/rn/input.js';
6
6
  const MULTIPLE_VALUES_SEPARATOR = ',';
7
- export function UCFormFieldControl({ errMsg = null, execState, field, onChange: onChangeBase, }) {
8
- const [internalValue, setInternalValue] = useState(field.getValue());
7
+ export function UCFormFieldControl({ errMsg = null, execState, f, onChange: onChangeBase, }) {
8
+ const [internalValue, setInternalValue] = useState(f.getValue());
9
9
  // biome-ignore lint/correctness/useExhaustiveDependencies: false positive : It is actually necessary (only `field` does not trigger the effect)
10
10
  useEffect(() => {
11
- setInternalValue(field.getValue());
12
- }, [field.getValue()]);
13
- const attrs = rnInputDef(field, execState, errMsg);
11
+ setInternalValue(f.getValue());
12
+ }, [f.getValue()]);
13
+ const attrs = rnInputDef(f, execState, errMsg);
14
14
  const onChangeText = (value) => {
15
- const [isRepeatable] = ucifRepeatability(field.def);
15
+ const [isRepeatable] = ucifRepeatability(f.def);
16
16
  if (isRepeatable && typeof value === 'string') {
17
17
  const valueArr = value
18
18
  .split(MULTIPLE_VALUES_SEPARATOR)
19
19
  .map((v) => v.trim());
20
- onChangeBase(field, UCInputFieldChangeOperator.SET, valueArr);
20
+ onChangeBase(f, UCInputFieldChangeOperator.SET, valueArr);
21
21
  }
22
22
  else {
23
- onChangeBase(field, UCInputFieldChangeOperator.SET, value);
23
+ onChangeBase(f, UCInputFieldChangeOperator.SET, value);
24
24
  }
25
25
  setInternalValue(value);
26
26
  };
@@ -1,7 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import type { UCInputField } from '../../uc/index.js';
4
- export interface Props<T extends DataType> {
5
- field: UCInputField<T>;
6
- }
7
- export declare function UCFormFieldDesc<T extends DataType>({ field, }: Props<T>): ReactElement | null;
3
+ import type { UCFormFieldDescProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldDesc<T extends DataType>({ f, }: UCFormFieldDescProps<T>): ReactElement | null;
@@ -1,9 +1,9 @@
1
1
  import React, {} from 'react';
2
2
  import { Text } from 'react-native';
3
3
  import { useDIContext } from '../lib/react/DIContextProvider.js';
4
- export function UCFormFieldDesc({ field, }) {
4
+ export function UCFormFieldDesc({ f, }) {
5
5
  const { wordingManager } = useDIContext();
6
- const { desc } = wordingManager.ucif(field);
6
+ const { desc } = wordingManager.ucif(f);
7
7
  if (!desc) {
8
8
  return null;
9
9
  }
@@ -1,7 +1,3 @@
1
1
  import { type ReactElement } from 'react';
2
- import type { ErrorMessage } from '../../dt/index.js';
3
- interface Props {
4
- errMsg: ErrorMessage;
5
- }
6
- export declare function UCFormFieldErr({ errMsg }: Props): ReactElement;
7
- export {};
2
+ import type { UCFormFieldErrProps } from '../lib/react/form.js';
3
+ export declare function UCFormFieldErr({ errMsg }: UCFormFieldErrProps): ReactElement;
@@ -1,7 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import type { UCInputField } from '../../uc/index.js';
4
- export interface Props<T extends DataType> {
5
- field: UCInputField<T>;
6
- }
7
- export declare function UCFormFieldLabel<T extends DataType>({ field, }: Props<T>): ReactElement;
3
+ import type { UCFormFieldLabelProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldLabel<T extends DataType>({ f, }: UCFormFieldLabelProps<T>): ReactElement;
@@ -1,8 +1,8 @@
1
1
  import React, {} from 'react';
2
2
  import { Text } from 'react-native';
3
3
  import { useDIContext } from '../lib/react/DIContextProvider.js';
4
- export function UCFormFieldLabel({ field, }) {
4
+ export function UCFormFieldLabel({ f, }) {
5
5
  const { wordingManager } = useDIContext();
6
- const { label } = wordingManager.ucif(field);
6
+ const { label } = wordingManager.ucif(f);
7
7
  return React.createElement(Text, null, label);
8
8
  }
@@ -1,9 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { UCInput, UCOPIBase } from '../../uc/index.js';
3
- import type { UCPanelCtx } from '../lib/react/panel.js';
4
- export type UCFormSubmitOnPress = () => Promise<void>;
5
- type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = UCPanelCtx<I, OPI0, OPI1> & {
6
- onPress?: UCFormSubmitOnPress;
7
- };
8
- export declare function UCFormSubmitControl<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ execState, disabled, onPress, uc }: Props<I, OPI0, OPI1>): ReactElement;
9
- export {};
3
+ import type { UCFormSubmitControlProps } from '../lib/react/form.js';
4
+ export declare function UCFormSubmitControl<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ execState, disabled, onPress, uc, }: UCFormSubmitControlProps<I, OPI0, OPI1>): ReactElement;
@@ -1,7 +1,7 @@
1
1
  import React, {} from 'react';
2
2
  import { Pressable, Text } from 'react-native';
3
3
  import { useDIContext } from '../lib/react/DIContextProvider.js';
4
- export function UCFormSubmitControl({ execState, disabled, onPress, uc }) {
4
+ export function UCFormSubmitControl({ execState, disabled, onPress, uc, }) {
5
5
  const { wordingManager } = useDIContext();
6
6
  return (React.createElement(Pressable, { onPress: onPress, disabled: disabled },
7
7
  React.createElement(Text, null, wordingManager.ucISubmit(uc.def, execState))));
@@ -12,6 +12,6 @@ export function UCForm({ clearAfterExec, disabled, execState, onChange, onSubmit
12
12
  };
13
13
  return (React.createElement("form", { onSubmit: onSubmit, ref: formRef },
14
14
  uc.inputFieldsForForm().map((f) => (React.createElement("div", { key: f.key },
15
- React.createElement(UCFormField, { disabled: disabled, execState: execState, field: f, onChange: onChange })))),
15
+ React.createElement(UCFormField, { disabled: disabled, execState: execState, f: f, onChange: onChange })))),
16
16
  React.createElement(UCFormSubmitControl, { execState: execState, disabled: disabled, uc: uc })));
17
17
  }
@@ -1,11 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import { type Props as FormFieldControlProps } from './UCFormFieldControl.js';
4
- import { type Props as FormFieldLabelProps } from './UCFormFieldLabel.js';
5
- declare const ELEMENTS: readonly ["control", "desc", "err", "label"];
6
- type Element = (typeof ELEMENTS)[number];
7
- type Props<T extends DataType> = FormFieldControlProps<T> & FormFieldLabelProps<T> & {
8
- only?: Element[];
9
- };
10
- export declare function UCFormField<T extends DataType>({ disabled, execState, field, onChange: onChangeBase, only, }: Props<T>): ReactElement;
11
- export {};
3
+ import { type UCFormFieldProps } from '../lib/react/form.js';
4
+ export declare function UCFormField<T extends DataType>({ disabled, execState, f, onChange: onChangeBase, only, }: UCFormFieldProps<T>): ReactElement;
@@ -1,32 +1,21 @@
1
1
  import React, { useState } from 'react';
2
2
  import { useDIContext } from '../lib/react/DIContextProvider.js';
3
- import { UCFormFieldControl, } from './UCFormFieldControl.js';
3
+ import { UC_FORM_FIELD_ELEMENTS, validateFormField, } from '../lib/react/form.js';
4
+ import { UCFormFieldControl } from './UCFormFieldControl.js';
4
5
  import { UCFormFieldDesc } from './UCFormFieldDesc.js';
5
6
  import { UCFormFieldErr } from './UCFormFieldErr.js';
6
- import { UCFormFieldLabel, } from './UCFormFieldLabel.js';
7
- const ELEMENTS = ['control', 'desc', 'err', 'label'];
8
- export function UCFormField({ disabled, execState, field, onChange: onChangeBase, only, }) {
7
+ import { UCFormFieldLabel } from './UCFormFieldLabel.js';
8
+ export function UCFormField({ disabled, execState, f, onChange: onChangeBase, only, }) {
9
9
  const { i18nManager } = useDIContext();
10
- const { type } = field.def;
11
10
  const [errMsg, setErrMsg] = useState(null);
12
- const elements = only ?? ELEMENTS;
13
11
  const onChange = (f, op, v) => {
14
- setErrMsg(null);
15
- const vArr = Array.isArray(v) ? v : [v];
16
- for (const vv of vArr) {
17
- const validation = type.assign(vv).validate();
18
- const violation = validation.get();
19
- if (violation) {
20
- const [key, expected] = violation;
21
- setErrMsg(i18nManager.t(key, { vars: { expected } }));
22
- break;
23
- }
24
- }
12
+ setErrMsg(validateFormField(i18nManager, f, v));
25
13
  onChangeBase(f, op, v);
26
14
  };
15
+ const elements = only ?? UC_FORM_FIELD_ELEMENTS;
27
16
  return (React.createElement(React.Fragment, null,
28
- elements.includes('label') && React.createElement(UCFormFieldLabel, { field: field }),
29
- elements.includes('control') && (React.createElement(UCFormFieldControl, { disabled: disabled, execState: execState, field: field, onChange: onChange })),
17
+ elements.includes('label') && React.createElement(UCFormFieldLabel, { f: f }),
18
+ elements.includes('control') && (React.createElement(UCFormFieldControl, { disabled: disabled, execState: execState, f: f, onChange: onChange })),
30
19
  elements.includes('err') && errMsg && (React.createElement(UCFormFieldErr, { errMsg: errMsg })),
31
- elements.includes('desc') && React.createElement(UCFormFieldDesc, { field: field })));
20
+ elements.includes('desc') && React.createElement(UCFormFieldDesc, { f: f })));
32
21
  }
@@ -1,11 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
- import type { DataType, ErrorMessage } from '../../dt/index.js';
3
- import { type UCInputField } from '../../uc/index.js';
4
- import type { UCFormFieldControlOnChange } from '../lib/react/form.js';
5
- import type { UCPanelState } from '../lib/react/panel.js';
6
- export type Props<T extends DataType> = UCPanelState & {
7
- errMsg?: ErrorMessage | null;
8
- field: UCInputField<T>;
9
- onChange: UCFormFieldControlOnChange<T>;
10
- };
11
- export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, field, onChange: onChangeBase, }: Props<T>): ReactElement;
2
+ import type { DataType } from '../../dt/index.js';
3
+ import type { UCFormFieldControlProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, f, onChange: onChangeBase, }: UCFormFieldControlProps<T>): ReactElement;
@@ -4,31 +4,46 @@ import { htmlInputDef } from '../lib/web/input.js';
4
4
  const CHECKED_FIELD_TYPES = ['checkbox', 'radio'];
5
5
  const FILE_FIELD_TYPES = ['file'];
6
6
  const MULTIPLE_VALUES_SEPARATOR = ',';
7
- export function UCFormFieldControl({ errMsg = null, execState, field, onChange: onChangeBase, }) {
8
- const attrs = htmlInputDef(field, execState, errMsg);
7
+ export function UCFormFieldControl({ errMsg = null, execState, f, onChange: onChangeBase, }) {
8
+ const attrs = htmlInputDef(f, execState, errMsg);
9
9
  const onChange = (e) => {
10
10
  const target = e.currentTarget;
11
11
  const type = target.type;
12
12
  let value = target.value;
13
+ if (target.localName === 'select' && !value) {
14
+ // Prevent the value from being '' when we set/unset the select
15
+ // Otherwise it sets '' as value and prevents the form for being valid
16
+ onChangeBase(f, UCInputFieldChangeOperator.RESET, value);
17
+ return;
18
+ }
13
19
  if (CHECKED_FIELD_TYPES.includes(type) && 'checked' in target) {
14
20
  value = target.checked;
15
21
  }
16
22
  else if (FILE_FIELD_TYPES.includes(type) && 'files' in target) {
17
23
  value = target.files?.item(0);
18
24
  }
19
- const [isRepeatable] = ucifRepeatability(field.def);
25
+ const [isRepeatable] = ucifRepeatability(f.def);
20
26
  if (isRepeatable && typeof value === 'string') {
21
27
  const valueArr = value
22
28
  .split(MULTIPLE_VALUES_SEPARATOR)
23
29
  .map((v) => v.trim());
24
- onChangeBase(field, UCInputFieldChangeOperator.SET, valueArr);
30
+ onChangeBase(f, UCInputFieldChangeOperator.SET, valueArr);
25
31
  }
26
32
  else {
27
- onChangeBase(field, UCInputFieldChangeOperator.SET, value);
33
+ onChangeBase(f, UCInputFieldChangeOperator.SET, value);
28
34
  }
29
35
  };
30
36
  if (attrs.internal?.multiline) {
31
37
  return React.createElement("textarea", { ...attrs.spec, onChange: onChange });
32
38
  }
39
+ const { type } = f.def;
40
+ const options = type.getOptions();
41
+ if (options) {
42
+ // TODO : Handle type.hasStrictOptions() => display an input text alongside the select
43
+ // TODO : Consider using a radio and/or checkbox and/or selectable buttons when the options count < X
44
+ return (React.createElement("select", { ...attrs.spec, onChange: onChange },
45
+ React.createElement("option", null),
46
+ options.map((o) => (React.createElement("option", { key: o.value.toString(), value: o.value.toString() }, o.label)))));
47
+ }
33
48
  return React.createElement("input", { ...attrs.spec, onChange: onChange });
34
49
  }
@@ -1,7 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import type { UCInputField } from '../../uc/index.js';
4
- export interface Props<T extends DataType> {
5
- field: UCInputField<T>;
6
- }
7
- export declare function UCFormFieldDesc<T extends DataType>({ field, }: Props<T>): ReactElement | null;
3
+ import type { UCFormFieldDescProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldDesc<T extends DataType>({ f, }: UCFormFieldDescProps<T>): ReactElement | null;
@@ -1,8 +1,8 @@
1
1
  import React, {} from 'react';
2
2
  import { useDIContext } from '../lib/react/DIContextProvider.js';
3
- export function UCFormFieldDesc({ field, }) {
3
+ export function UCFormFieldDesc({ f, }) {
4
4
  const { wordingManager } = useDIContext();
5
- const { desc } = wordingManager.ucif(field);
5
+ const { desc } = wordingManager.ucif(f);
6
6
  if (!desc) {
7
7
  return null;
8
8
  }
@@ -1,7 +1,3 @@
1
1
  import { type ReactElement } from 'react';
2
- import type { ErrorMessage } from '../../dt/index.js';
3
- interface Props {
4
- errMsg: ErrorMessage;
5
- }
6
- export declare function UCFormFieldErr({ errMsg }: Props): ReactElement;
7
- export {};
2
+ import type { UCFormFieldErrProps } from '../lib/react/form.js';
3
+ export declare function UCFormFieldErr({ errMsg }: UCFormFieldErrProps): ReactElement;
@@ -1,7 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { DataType } from '../../dt/index.js';
3
- import { type UCInputField } from '../../uc/index.js';
4
- export interface Props<T extends DataType> {
5
- field: UCInputField<T>;
6
- }
7
- export declare function UCFormFieldLabel<T extends DataType>({ field, }: Props<T>): ReactElement;
3
+ import type { UCFormFieldLabelProps } from '../lib/react/form.js';
4
+ export declare function UCFormFieldLabel<T extends DataType>({ f, }: UCFormFieldLabelProps<T>): ReactElement;
@@ -1,8 +1,8 @@
1
1
  import React, {} from 'react';
2
2
  import { ucifId } from '../../uc/index.js';
3
3
  import { useDIContext } from '../lib/react/DIContextProvider.js';
4
- export function UCFormFieldLabel({ field, }) {
4
+ export function UCFormFieldLabel({ f, }) {
5
5
  const { wordingManager } = useDIContext();
6
- const { label } = wordingManager.ucif(field);
7
- return React.createElement("label", { htmlFor: ucifId(field.key) }, label);
6
+ const { label } = wordingManager.ucif(f);
7
+ return React.createElement("label", { htmlFor: ucifId(f.key) }, label);
8
8
  }
@@ -1,6 +1,4 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { UCInput, UCOPIBase } from '../../uc/index.js';
3
- import type { UCPanelCtx } from '../lib/react/panel.js';
4
- type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = UCPanelCtx<I, OPI0, OPI1>;
5
- export declare function UCFormSubmitControl<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ execState, disabled, uc }: Props<I, OPI0, OPI1>): ReactElement;
6
- export {};
3
+ import type { UCFormSubmitControlProps } from '../lib/react/form.js';
4
+ export declare function UCFormSubmitControl<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ execState, disabled, uc, }: UCFormSubmitControlProps<I, OPI0, OPI1>): ReactElement;
@@ -1,6 +1,6 @@
1
1
  import React, {} from 'react';
2
2
  import { useDIContext } from '../lib/react/DIContextProvider.js';
3
- export function UCFormSubmitControl({ execState, disabled, uc }) {
3
+ export function UCFormSubmitControl({ execState, disabled, uc, }) {
4
4
  const { wordingManager } = useDIContext();
5
5
  return (React.createElement("input", { disabled: disabled, type: "submit", value: wordingManager.ucISubmit(uc.def, execState) }));
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "libmodulor",
3
3
  "description": "An opinionated TypeScript library to create business oriented applications",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "Chafik H'nini <chafik.hnini@gmail.com>",
7
7
  "homepage": "https://github.com/c100k/libmodulor#readme",
@@ -13,9 +13,6 @@
13
13
  ".": {
14
14
  "import": "./dist/esm/index.js"
15
15
  },
16
- "./helper": {
17
- "import": "./dist/products/Helper/index.js"
18
- },
19
16
  "./locales/en": {
20
17
  "import": "./dist/esm/i18n/locales/en.js"
21
18
  },
@@ -56,9 +53,10 @@
56
53
  "import": "./dist/esm/index.web.js"
57
54
  }
58
55
  },
56
+ "bin": "./dist/esm/products/Helper/index.js",
59
57
  "peerDependencies": {
60
58
  "@hono/node-server": "^1.13.8",
61
- "@modelcontextprotocol/sdk": "^1.5.0",
59
+ "@modelcontextprotocol/sdk": "^1.6.0",
62
60
  "@stricli/core": "^1.1.1",
63
61
  "buffer": "^6.0.3",
64
62
  "cookie-parser": "^1.4.7",
@@ -68,16 +66,17 @@
68
66
  "helmet": "^8.0.0",
69
67
  "hono": "^4.7.2",
70
68
  "inversify": "^6.2.2",
71
- "jose": "^6.0.6",
69
+ "jose": "^6.0.8",
72
70
  "knex": "^3.1.0",
73
71
  "pg": "^8.13.3",
74
- "react": "^18.3.1",
75
- "react-dom": "^18.3.1",
72
+ "react": "^19.0.0",
73
+ "react-dom": "^19.0.0",
76
74
  "react-native": "^0.78.0",
77
75
  "reflect-metadata": "^0.2.2",
78
76
  "sqlite3": "^5.1.7",
79
77
  "typescript": "^5.7.3",
80
- "vite": "^6.1.1"
78
+ "vite": "^6.2.0",
79
+ "vitest": "^3.0.7"
81
80
  },
82
81
  "peerDependenciesMeta": {
83
82
  "@hono/node-server": {