@origostudio/origo-design 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -0
- package/.gitattributes +22 -0
- package/.perf-baseline.json +12 -0
- package/.prettierignore +13 -0
- package/.prettierrc +10 -0
- package/.vscode/extensions.json +3 -0
- package/BRANCH-PROTECTION.md +25 -0
- package/CHANGELOG.md +292 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/commitlint.config.js +6 -0
- package/dist/packages/cli/CHANGELOG.md +92 -0
- package/dist/packages/cli/README.md +11 -0
- package/dist/packages/cli/package.json +16 -0
- package/dist/packages/cli/package.json.bak +16 -0
- package/dist/packages/cli/src/commands/generate/entity.d.ts +2 -0
- package/dist/packages/cli/src/commands/generate/entity.js +24 -0
- package/dist/packages/cli/src/commands/generate/entity.js.map +1 -0
- package/dist/packages/cli/src/commands/generate/index.d.ts +2 -0
- package/dist/packages/cli/src/commands/generate/index.js +30 -0
- package/dist/packages/cli/src/commands/generate/index.js.map +1 -0
- package/dist/packages/cli/src/commands/init.d.ts +5 -0
- package/dist/packages/cli/src/commands/init.js +27 -0
- package/dist/packages/cli/src/commands/init.js.map +1 -0
- package/dist/packages/cli/src/commands/new.d.ts +2 -0
- package/dist/packages/cli/src/commands/new.js +24 -0
- package/dist/packages/cli/src/commands/new.js.map +1 -0
- package/dist/packages/cli/src/commands/validate.d.ts +2 -0
- package/dist/packages/cli/src/commands/validate.js +50 -0
- package/dist/packages/cli/src/commands/validate.js.map +1 -0
- package/dist/packages/cli/src/index.d.ts +11 -0
- package/dist/packages/cli/src/index.js +15 -0
- package/dist/packages/cli/src/index.js.map +1 -0
- package/dist/packages/cli/src/lib/cli.d.ts +1 -0
- package/dist/packages/cli/src/lib/cli.js +7 -0
- package/dist/packages/cli/src/lib/cli.js.map +1 -0
- package/dist/packages/cli/src/lib/generator.d.ts +16 -0
- package/dist/packages/cli/src/lib/generator.js +165 -0
- package/dist/packages/cli/src/lib/generator.js.map +1 -0
- package/dist/packages/cli/src/lib/scaffolding.d.ts +5 -0
- package/dist/packages/cli/src/lib/scaffolding.js +87 -0
- package/dist/packages/cli/src/lib/scaffolding.js.map +1 -0
- package/dist/packages/cli/src/lib/validation.d.ts +4 -0
- package/dist/packages/cli/src/lib/validation.js +191 -0
- package/dist/packages/cli/src/lib/validation.js.map +1 -0
- package/dist/packages/cli/src/main.d.ts +4 -0
- package/dist/packages/cli/src/main.js +38 -0
- package/dist/packages/cli/src/main.js.map +1 -0
- package/dist/packages/cli/src/templates/entity.json +6 -0
- package/dist/packages/cli/src/templates/extension.json +8 -0
- package/dist/packages/cli/src/templates/index.d.ts +29 -0
- package/dist/packages/cli/src/templates/index.js +61 -0
- package/dist/packages/cli/src/templates/index.js.map +1 -0
- package/dist/packages/cli/src/templates/origo.json +16 -0
- package/dist/packages/cli/src/utils/errors.d.ts +15 -0
- package/dist/packages/cli/src/utils/errors.js +58 -0
- package/dist/packages/cli/src/utils/errors.js.map +1 -0
- package/dist/packages/core/CHANGELOG.md +275 -0
- package/dist/packages/core/README.md +11 -0
- package/dist/packages/core/package.json +21 -0
- package/dist/packages/core/package.json.bak +21 -0
- package/dist/packages/core/src/extension-api/extension-manager.d.ts +35 -0
- package/dist/packages/core/src/extension-api/extension-manager.js +300 -0
- package/dist/packages/core/src/extension-api/extension-manager.js.map +1 -0
- package/dist/packages/core/src/extension-api/index.d.ts +2 -0
- package/dist/packages/core/src/extension-api/index.js +6 -0
- package/dist/packages/core/src/extension-api/index.js.map +1 -0
- package/dist/packages/core/src/extension-api/types.d.ts +34 -0
- package/dist/packages/core/src/extension-api/types.js +3 -0
- package/dist/packages/core/src/extension-api/types.js.map +1 -0
- package/dist/packages/core/src/index.d.ts +3 -0
- package/dist/packages/core/src/index.js +7 -0
- package/dist/packages/core/src/index.js.map +1 -0
- package/dist/packages/core/src/schemas/capability.schema.json +104 -0
- package/dist/packages/core/src/schemas/contract.schema.json +52 -0
- package/dist/packages/core/src/schemas/domain.schema.json +47 -0
- package/dist/packages/core/src/schemas/entity.schema.json +72 -0
- package/dist/packages/core/src/schemas/extension.schema.json +39 -0
- package/dist/packages/core/src/schemas/permission.schema.json +21 -0
- package/dist/packages/core/src/types/ast.d.ts +17 -0
- package/dist/packages/core/src/types/ast.js +3 -0
- package/dist/packages/core/src/types/ast.js.map +1 -0
- package/dist/packages/core/src/types/domain.d.ts +101 -0
- package/dist/packages/core/src/types/domain.js +8 -0
- package/dist/packages/core/src/types/domain.js.map +1 -0
- package/dist/packages/core/src/types/validation.d.ts +12 -0
- package/dist/packages/core/src/types/validation.js +16 -0
- package/dist/packages/core/src/types/validation.js.map +1 -0
- package/dist/packages/core/src/validator/ast-validator.d.ts +11 -0
- package/dist/packages/core/src/validator/ast-validator.js +388 -0
- package/dist/packages/core/src/validator/ast-validator.js.map +1 -0
- package/dist/packages/core/src/validator/index.d.ts +23 -0
- package/dist/packages/core/src/validator/index.js +143 -0
- package/dist/packages/core/src/validator/index.js.map +1 -0
- package/dist/packages/core/src/validator/serializer.d.ts +11 -0
- package/dist/packages/core/src/validator/serializer.js +99 -0
- package/dist/packages/core/src/validator/serializer.js.map +1 -0
- package/eslint.config.js +114 -0
- package/generate_prompts.js +19 -0
- package/jest.config.ts +5 -0
- package/jest.preset.js +3 -0
- package/nx.json +169 -0
- package/package.json +115 -0
- package/packages/angular-renderer/CHANGELOG.md +274 -0
- package/packages/angular-renderer/README.md +11 -0
- package/packages/angular-renderer/package.json +20 -0
- package/packages/angular-renderer/playwright-ct.config.ts +23 -0
- package/packages/angular-renderer/src/adapters/web/adapter.ts +145 -0
- package/packages/angular-renderer/src/adapters/web/experience-adapter.service.ts +21 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.html +9 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.pw.ts +19 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.scss +31 -0
- package/packages/angular-renderer/src/components/primitives/button/button.component.ts +67 -0
- package/packages/angular-renderer/src/components/primitives/primitives.a11y.pw.ts +33 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.html +10 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.pw.ts +29 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.scss +42 -0
- package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts +84 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.html +2 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.pw.ts +19 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.scss +5 -0
- package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.ts +74 -0
- package/packages/angular-renderer/src/index.ts +7 -0
- package/packages/angular-renderer/src/lib/renderer.component.ts +118 -0
- package/packages/angular-renderer/src/lib/renderer.tokens.ts +9 -0
- package/packages/angular-renderer/src/lib/test-utils.ts +26 -0
- package/packages/angular-renderer/src/lib/theme.provider.ts +83 -0
- package/packages/angular-renderer/src/test-setup.ts +8 -0
- package/packages/cli/CHANGELOG.md +92 -0
- package/packages/cli/README.md +11 -0
- package/packages/cli/package.json +16 -0
- package/packages/cli/src/commands/generate/entity.ts +20 -0
- package/packages/cli/src/commands/generate/index.ts +28 -0
- package/packages/cli/src/commands/init.ts +28 -0
- package/packages/cli/src/commands/new.ts +22 -0
- package/packages/cli/src/commands/validate.ts +47 -0
- package/packages/cli/src/index.ts +11 -0
- package/packages/cli/src/lib/cli.ts +3 -0
- package/packages/cli/src/lib/generator.ts +181 -0
- package/packages/cli/src/lib/scaffolding.ts +104 -0
- package/packages/cli/src/lib/validation.ts +218 -0
- package/packages/cli/src/main.ts +38 -0
- package/packages/cli/src/templates/entity.json +6 -0
- package/packages/cli/src/templates/extension.json +8 -0
- package/packages/cli/src/templates/index.ts +94 -0
- package/packages/cli/src/templates/origo.json +16 -0
- package/packages/cli/src/utils/errors.ts +80 -0
- package/packages/core/CHANGELOG.md +275 -0
- package/packages/core/README.md +11 -0
- package/packages/core/package-lock.json +1035 -0
- package/packages/core/package.json +21 -0
- package/packages/core/scripts/generate-types.ts +23 -0
- package/packages/core/src/extension-api/extension-manager.ts +378 -0
- package/packages/core/src/extension-api/index.ts +2 -0
- package/packages/core/src/extension-api/types.ts +37 -0
- package/packages/core/src/index.ts +3 -0
- package/packages/core/src/schemas/__fixtures__/target-page.json +222 -0
- package/packages/core/src/schemas/capability.schema.json +104 -0
- package/packages/core/src/schemas/contract.schema.json +52 -0
- package/packages/core/src/schemas/domain.schema.json +47 -0
- package/packages/core/src/schemas/entity.schema.json +72 -0
- package/packages/core/src/schemas/extension.schema.json +39 -0
- package/packages/core/src/schemas/permission.schema.json +21 -0
- package/packages/core/src/types/ast.ts +20 -0
- package/packages/core/src/types/domain.ts +105 -0
- package/packages/core/src/types/validation.ts +29 -0
- package/packages/core/src/validator/ast-validator.ts +406 -0
- package/packages/core/src/validator/index.ts +207 -0
- package/packages/core/src/validator/serializer.ts +120 -0
- package/packages/core/test/fixtures/target-page.json +87 -0
- package/packages/design-tokens/CHANGELOG.md +273 -0
- package/packages/design-tokens/README.md +11 -0
- package/packages/design-tokens/package.json +25 -0
- package/packages/design-tokens/src/build.ts +97 -0
- package/packages/design-tokens/src/compile.ts +60 -0
- package/packages/design-tokens/src/index.ts +7 -0
- package/packages/design-tokens/src/runtime/index.ts +2 -0
- package/packages/design-tokens/src/runtime/theme-fetcher.ts +90 -0
- package/packages/design-tokens/src/runtime/theme-injector.ts +149 -0
- package/packages/design-tokens/src/schemas/base-tokens.schema.json +127 -0
- package/packages/design-tokens/src/schemas/semantic-tokens.schema.json +52 -0
- package/packages/design-tokens/src/tokens/base.json +46 -0
- package/packages/design-tokens/src/tokens/semantic.json +33 -0
- package/packages/playground/CHANGELOG.md +12 -0
- package/packages/playground/package.json +10 -0
- package/packages/playground/public/favicon.ico +0 -0
- package/packages/playground/src/app/app.component.html +1 -0
- package/packages/playground/src/app/app.component.scss +4 -0
- package/packages/playground/src/app/app.component.ts +13 -0
- package/packages/playground/src/app/app.config.ts +9 -0
- package/packages/playground/src/editor/badl-editor.component.html +1 -0
- package/packages/playground/src/editor/badl-editor.component.scss +10 -0
- package/packages/playground/src/editor/badl-editor.component.ts +57 -0
- package/packages/playground/src/editor/editor.worker.ts +1 -0
- package/packages/playground/src/editor/json.worker.ts +1 -0
- package/packages/playground/src/editor/monaco-environment.ts +11 -0
- package/packages/playground/src/editor/schema-registry.ts +30 -0
- package/packages/playground/src/index.html +13 -0
- package/packages/playground/src/main.ts +5 -0
- package/packages/playground/src/schemas/capability.schema.json +104 -0
- package/packages/playground/src/schemas/contract.schema.json +52 -0
- package/packages/playground/src/schemas/domain.schema.json +47 -0
- package/packages/playground/src/schemas/entity.schema.json +72 -0
- package/packages/playground/src/schemas/extension.schema.json +39 -0
- package/packages/playground/src/schemas/permission.schema.json +21 -0
- package/packages/playground/src/styles.scss +8 -0
- package/packages/playground/src/test-setup.ts +146 -0
- package/packages/playground/vite.config.ts +12 -0
- package/packages/playground/vitest.config.mjs +24 -0
package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.pw.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/experimental-ct-angular';
|
|
2
|
+
import { TextInputComponent, TextInputProps } from './text-input.component';
|
|
3
|
+
import { InteractionContract } from '@origo/core';
|
|
4
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
5
|
+
|
|
6
|
+
test.describe('TextInputComponent Accessibility', () => {
|
|
7
|
+
test('should not have any automatically detectable accessibility issues', async ({
|
|
8
|
+
mount,
|
|
9
|
+
page,
|
|
10
|
+
}) => {
|
|
11
|
+
await mount(TextInputComponent, {
|
|
12
|
+
props: {
|
|
13
|
+
contract: {
|
|
14
|
+
id: '2',
|
|
15
|
+
type: 'textInput',
|
|
16
|
+
props: {
|
|
17
|
+
placeholder: 'Enter name',
|
|
18
|
+
value: 'Jane',
|
|
19
|
+
'aria-label': 'Name input',
|
|
20
|
+
'aria-describedby': 'name-hint',
|
|
21
|
+
},
|
|
22
|
+
} as InteractionContract<TextInputProps>,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
|
|
27
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|
package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.scss
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: inline-block;
|
|
3
|
+
}
|
|
4
|
+
input {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
width: 100%;
|
|
7
|
+
background-color: var(--origo-color-surface-background);
|
|
8
|
+
color: var(--origo-color-text-primary);
|
|
9
|
+
padding: var(--origo-spacing-container-padding);
|
|
10
|
+
border: var(--origo-border-width-default, 1px) solid var(--origo-color-border-default, #ccc);
|
|
11
|
+
border-radius: var(--origo-radius-sm, 4px);
|
|
12
|
+
box-shadow: var(--origo-shadow-sm, none);
|
|
13
|
+
font-family: var(--origo-typography-input-font-family, inherit);
|
|
14
|
+
font-size: var(--origo-typography-input-font-size, inherit);
|
|
15
|
+
font-weight: var(--origo-typography-input-font-weight, inherit);
|
|
16
|
+
line-height: var(--origo-typography-input-line-height, inherit);
|
|
17
|
+
transition: all 0.2s ease-in-out;
|
|
18
|
+
}
|
|
19
|
+
input::placeholder {
|
|
20
|
+
color: var(--origo-color-text-muted, #777);
|
|
21
|
+
}
|
|
22
|
+
input:hover:not(:disabled):not([readonly]) {
|
|
23
|
+
border-color: var(--origo-color-border-hover, #999);
|
|
24
|
+
}
|
|
25
|
+
input:active:not(:disabled):not([readonly]) {
|
|
26
|
+
border-color: var(--origo-color-border-active, #666);
|
|
27
|
+
}
|
|
28
|
+
input:focus,
|
|
29
|
+
input:focus-visible {
|
|
30
|
+
outline: 2px solid var(--origo-color-focus, #005fcc);
|
|
31
|
+
outline-offset: -1px;
|
|
32
|
+
border-color: var(--origo-color-focus, #005fcc);
|
|
33
|
+
}
|
|
34
|
+
input:disabled {
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
opacity: var(--origo-opacity-disabled, 0.5);
|
|
37
|
+
background-color: var(--origo-color-surface-disabled, #eee);
|
|
38
|
+
color: var(--origo-color-text-disabled, #999);
|
|
39
|
+
}
|
|
40
|
+
input[readonly] {
|
|
41
|
+
background-color: var(--origo-color-surface-readonly, #f9f9f9);
|
|
42
|
+
}
|
package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
model,
|
|
5
|
+
ChangeDetectionStrategy,
|
|
6
|
+
computed,
|
|
7
|
+
ViewEncapsulation,
|
|
8
|
+
inject,
|
|
9
|
+
effect,
|
|
10
|
+
untracked,
|
|
11
|
+
SecurityContext,
|
|
12
|
+
} from '@angular/core';
|
|
13
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
14
|
+
import { InteractionContract } from '@origo/core';
|
|
15
|
+
import { OrigoAdapter } from '../../../adapters/web/adapter';
|
|
16
|
+
import { WebExperienceAdapterService } from '../../../adapters/web/experience-adapter.service';
|
|
17
|
+
|
|
18
|
+
export interface TextInputProps {
|
|
19
|
+
value?: string;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
readonly?: boolean;
|
|
23
|
+
'aria-label'?: string;
|
|
24
|
+
'aria-describedby'?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Component({
|
|
28
|
+
selector: 'origo-text-input',
|
|
29
|
+
standalone: true,
|
|
30
|
+
templateUrl: './text-input.component.html',
|
|
31
|
+
styleUrls: ['./text-input.component.scss'],
|
|
32
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
33
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
34
|
+
host: {
|
|
35
|
+
'[class.origo-text-input]': 'true',
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
export class TextInputComponent implements OrigoAdapter<TextInputProps> {
|
|
39
|
+
static readonly contractSchema = {
|
|
40
|
+
value: 'string',
|
|
41
|
+
placeholder: 'string',
|
|
42
|
+
disabled: 'boolean',
|
|
43
|
+
readonly: 'boolean',
|
|
44
|
+
};
|
|
45
|
+
static readonly strictContract = false;
|
|
46
|
+
|
|
47
|
+
contract = input.required<InteractionContract<TextInputProps>>();
|
|
48
|
+
value = model<string>('');
|
|
49
|
+
|
|
50
|
+
computedPlaceholder = computed(() => this.contract().props?.placeholder ?? '');
|
|
51
|
+
computedDisabled = computed(() => !!this.contract().props?.disabled);
|
|
52
|
+
computedReadonly = computed(() => !!this.contract().props?.readonly);
|
|
53
|
+
computedAriaLabel = computed(() => this.contract().props?.['aria-label'] as string | undefined);
|
|
54
|
+
computedAriaDescribedBy = computed(
|
|
55
|
+
() => this.contract().props?.['aria-describedby'] as string | undefined
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
private experienceAdapter = inject(WebExperienceAdapterService);
|
|
59
|
+
private sanitizer = inject(DomSanitizer);
|
|
60
|
+
|
|
61
|
+
constructor() {
|
|
62
|
+
effect(() => {
|
|
63
|
+
const contractVal = this.contract().props?.value;
|
|
64
|
+
untracked(() =>
|
|
65
|
+
this.value.set(contractVal !== undefined && contractVal !== null ? String(contractVal) : '')
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onInput(event: Event) {
|
|
71
|
+
const target = event.target as HTMLInputElement | null;
|
|
72
|
+
if (!target) return;
|
|
73
|
+
|
|
74
|
+
const rawValue = target.value;
|
|
75
|
+
const sanitizedValue = this.sanitizer.sanitize(SecurityContext.HTML, rawValue) || '';
|
|
76
|
+
|
|
77
|
+
if (target.value !== sanitizedValue) {
|
|
78
|
+
target.value = sanitizedValue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.value.set(sanitizedValue);
|
|
82
|
+
this.experienceAdapter.updateState(this.contract().id, 'value', sanitizedValue);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/experimental-ct-angular';
|
|
2
|
+
import { VBoxComponent } from './vbox.component';
|
|
3
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
4
|
+
|
|
5
|
+
test.describe('VBoxComponent Accessibility', () => {
|
|
6
|
+
test('should not have any automatically detectable accessibility issues', async ({
|
|
7
|
+
mount,
|
|
8
|
+
page,
|
|
9
|
+
}) => {
|
|
10
|
+
await mount(VBoxComponent, {
|
|
11
|
+
props: {
|
|
12
|
+
contract: { id: '1', type: 'vbox', props: { gap: '10px' } } as never,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
|
|
17
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
viewChild,
|
|
5
|
+
ViewContainerRef,
|
|
6
|
+
ChangeDetectionStrategy,
|
|
7
|
+
computed,
|
|
8
|
+
ViewEncapsulation,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { InteractionContract } from '@origo/core';
|
|
11
|
+
import { OrigoAdapter, ContainerComponent } from '../../../adapters/web/adapter';
|
|
12
|
+
|
|
13
|
+
export interface VBoxProps {
|
|
14
|
+
gap?: number | string;
|
|
15
|
+
alignment?: 'start' | 'center' | 'end' | 'stretch';
|
|
16
|
+
padding?: number | string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'origo-vbox',
|
|
21
|
+
standalone: true,
|
|
22
|
+
templateUrl: './vbox.component.html',
|
|
23
|
+
styleUrls: ['./vbox.component.scss'],
|
|
24
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
25
|
+
encapsulation: ViewEncapsulation.ShadowDom,
|
|
26
|
+
host: {
|
|
27
|
+
'[class.origo-vbox]': 'true',
|
|
28
|
+
'[style.gap]': 'computedGap()',
|
|
29
|
+
'[style.align-items]': 'computedAlignment()',
|
|
30
|
+
'[style.padding]': 'computedPadding()',
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
export class VBoxComponent implements OrigoAdapter<VBoxProps>, ContainerComponent {
|
|
34
|
+
static readonly contractSchema = {
|
|
35
|
+
gap: 'string',
|
|
36
|
+
alignment: 'string',
|
|
37
|
+
padding: 'string',
|
|
38
|
+
};
|
|
39
|
+
static readonly strictContract = false;
|
|
40
|
+
|
|
41
|
+
contract = input.required<InteractionContract<VBoxProps>>();
|
|
42
|
+
|
|
43
|
+
vc = viewChild.required('vc', { read: ViewContainerRef });
|
|
44
|
+
|
|
45
|
+
computedGap = computed(() => {
|
|
46
|
+
const gap = this.contract().props?.gap;
|
|
47
|
+
if (gap === undefined || gap === null || gap === '') return undefined;
|
|
48
|
+
const num = Number(gap);
|
|
49
|
+
return !isNaN(num) ? `${num}px` : String(gap);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
computedAlignment = computed(() => {
|
|
53
|
+
const align = this.contract().props?.alignment;
|
|
54
|
+
switch (align) {
|
|
55
|
+
case 'start':
|
|
56
|
+
return 'flex-start';
|
|
57
|
+
case 'end':
|
|
58
|
+
return 'flex-end';
|
|
59
|
+
case 'center':
|
|
60
|
+
return 'center';
|
|
61
|
+
case 'stretch':
|
|
62
|
+
return 'stretch';
|
|
63
|
+
default:
|
|
64
|
+
return 'stretch';
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
computedPadding = computed(() => {
|
|
69
|
+
const padding = this.contract().props?.padding;
|
|
70
|
+
if (padding === undefined || padding === null || padding === '') return undefined;
|
|
71
|
+
const num = Number(padding);
|
|
72
|
+
return !isNaN(num) ? `${num}px` : String(padding);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './lib/theme.provider';
|
|
2
|
+
export * from './lib/renderer.component';
|
|
3
|
+
export * from './lib/renderer.tokens';
|
|
4
|
+
export * from './adapters/web/adapter';
|
|
5
|
+
export * from './components/primitives/vbox/vbox.component';
|
|
6
|
+
export * from './components/primitives/text-input/text-input.component';
|
|
7
|
+
export * from './components/primitives/button/button.component';
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
input,
|
|
4
|
+
inject,
|
|
5
|
+
ViewContainerRef,
|
|
6
|
+
effect,
|
|
7
|
+
viewChild,
|
|
8
|
+
DestroyRef,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import { ASTNode } from '@origo/core';
|
|
11
|
+
import { RENDERER_REGISTRY } from './renderer.tokens';
|
|
12
|
+
import { AdapterPipelineService, ContainerComponent } from '../adapters/web/adapter';
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'origo-renderer',
|
|
16
|
+
standalone: true,
|
|
17
|
+
template: `<ng-container #vc></ng-container>`,
|
|
18
|
+
})
|
|
19
|
+
export class OrigoRendererComponent {
|
|
20
|
+
node = input.required<ASTNode>();
|
|
21
|
+
private registry = inject(RENDERER_REGISTRY, { optional: true }) ?? new Map();
|
|
22
|
+
private vc = viewChild.required('vc', { read: ViewContainerRef });
|
|
23
|
+
private destroyRef = inject(DestroyRef);
|
|
24
|
+
private adapter = inject(AdapterPipelineService);
|
|
25
|
+
private renderId = 0;
|
|
26
|
+
private isDestroyed = false;
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
this.destroyRef.onDestroy(() => (this.isDestroyed = true));
|
|
30
|
+
effect(() => {
|
|
31
|
+
const n = this.node();
|
|
32
|
+
const container = this.vc();
|
|
33
|
+
const currentId = ++this.renderId;
|
|
34
|
+
container.clear();
|
|
35
|
+
if (n) {
|
|
36
|
+
this.renderTree(n, container, currentId);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async renderTree(rootNode: ASTNode, rootVc: ViewContainerRef, traversalId: number) {
|
|
42
|
+
const queue: { node: ASTNode; vc: ViewContainerRef }[] = [{ node: rootNode, vc: rootVc }];
|
|
43
|
+
const CHUNK_SIZE = 50;
|
|
44
|
+
const visited = new Set<string>();
|
|
45
|
+
|
|
46
|
+
while (queue.length > 0) {
|
|
47
|
+
if (this.isDestroyed || traversalId !== this.renderId) return;
|
|
48
|
+
let processed = 0;
|
|
49
|
+
|
|
50
|
+
while (queue.length > 0 && processed < CHUNK_SIZE) {
|
|
51
|
+
const item = queue.shift();
|
|
52
|
+
if (!item) continue;
|
|
53
|
+
processed++;
|
|
54
|
+
const { node, vc } = item;
|
|
55
|
+
|
|
56
|
+
if (visited.has(node.id)) continue;
|
|
57
|
+
visited.add(node.id);
|
|
58
|
+
|
|
59
|
+
const componentType = this.registry.get(node.type);
|
|
60
|
+
|
|
61
|
+
if (!componentType) {
|
|
62
|
+
console.warn(`No primitive found for node type: ${node.type}`);
|
|
63
|
+
if (node.children && node.children.length > 0) {
|
|
64
|
+
for (const child of node.children) {
|
|
65
|
+
if (child) queue.push({ node: child, vc });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const componentRef = vc.createComponent(componentType);
|
|
72
|
+
|
|
73
|
+
// Extract optional schema/strict mode if defined on the component class
|
|
74
|
+
const schema = (componentType as unknown as Record<string, unknown>)['contractSchema'] as
|
|
75
|
+
| Record<string, 'string' | 'number' | 'boolean' | 'array' | 'object'>
|
|
76
|
+
| undefined;
|
|
77
|
+
const strict =
|
|
78
|
+
(componentType as unknown as Record<string, unknown>)['strictContract'] === true;
|
|
79
|
+
|
|
80
|
+
const preparedNode = this.adapter.prepareNode(node, schema, strict);
|
|
81
|
+
|
|
82
|
+
if ('contract' in (componentRef.instance as Record<string, unknown>)) {
|
|
83
|
+
componentRef.setInput('contract', preparedNode);
|
|
84
|
+
} else {
|
|
85
|
+
console.warn(
|
|
86
|
+
`Component for ${node.type} does not implement OrigoAdapter (missing 'contract' input).`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (node.children && node.children.length > 0) {
|
|
91
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
92
|
+
const instance = componentRef.instance as ContainerComponent;
|
|
93
|
+
let childVc = instance.vc || instance.viewContainerRef;
|
|
94
|
+
|
|
95
|
+
if (typeof childVc === 'function') {
|
|
96
|
+
childVc = childVc();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (childVc) {
|
|
100
|
+
for (const child of node.children) {
|
|
101
|
+
if (child) queue.push({ node: child, vc: childVc });
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
console.warn(
|
|
105
|
+
`Component for ${node.type} has children but does not expose a ViewContainerRef as 'vc'`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
componentRef.changeDetectorRef.markForCheck();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (queue.length > 0) {
|
|
114
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Type } from '@angular/core';
|
|
2
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
3
|
+
|
|
4
|
+
export interface RenderPrimitiveOptions<T> {
|
|
5
|
+
component: Type<T>;
|
|
6
|
+
inputs?: Partial<T>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function renderPrimitive<T>(
|
|
10
|
+
options: RenderPrimitiveOptions<T>
|
|
11
|
+
): Promise<ComponentFixture<T>> {
|
|
12
|
+
await TestBed.configureTestingModule({
|
|
13
|
+
imports: [options.component],
|
|
14
|
+
}).compileComponents();
|
|
15
|
+
|
|
16
|
+
const fixture = TestBed.createComponent(options.component);
|
|
17
|
+
|
|
18
|
+
if (options.inputs) {
|
|
19
|
+
for (const [key, value] of Object.entries(options.inputs)) {
|
|
20
|
+
fixture.componentRef.setInput(key, value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fixture.detectChanges();
|
|
25
|
+
return fixture;
|
|
26
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { APP_INITIALIZER, Provider, PLATFORM_ID, Optional } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
|
|
3
|
+
|
|
4
|
+
import { loadAndInjectTheme, injectTheme } from '@origo/design-tokens/runtime';
|
|
5
|
+
|
|
6
|
+
export interface OrigoThemeOptions {
|
|
7
|
+
url?: string;
|
|
8
|
+
theme?: Record<string, unknown>;
|
|
9
|
+
targetElement?: HTMLElement | string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function themeInitializerFactory(
|
|
13
|
+
optionsOrUrl: string | OrigoThemeOptions,
|
|
14
|
+
platformId: object,
|
|
15
|
+
document: Document | null
|
|
16
|
+
) {
|
|
17
|
+
return () => {
|
|
18
|
+
if (isPlatformBrowser(platformId)) {
|
|
19
|
+
const isString = typeof optionsOrUrl === 'string';
|
|
20
|
+
const url = isString ? optionsOrUrl : optionsOrUrl.url;
|
|
21
|
+
const theme = isString ? undefined : optionsOrUrl.theme;
|
|
22
|
+
// Resolve target element
|
|
23
|
+
let target: HTMLElement | undefined;
|
|
24
|
+
if (!isString && typeof optionsOrUrl.targetElement === 'string') {
|
|
25
|
+
target = document?.querySelector(optionsOrUrl.targetElement) as HTMLElement | undefined;
|
|
26
|
+
if (!target) {
|
|
27
|
+
console.warn(
|
|
28
|
+
`[origo-design] targetElement selector '${optionsOrUrl.targetElement}' matched nothing. Falling back to documentElement.`
|
|
29
|
+
);
|
|
30
|
+
target = document ? document.documentElement : undefined;
|
|
31
|
+
}
|
|
32
|
+
} else if (!isString && optionsOrUrl.targetElement instanceof HTMLElement) {
|
|
33
|
+
target = optionsOrUrl.targetElement;
|
|
34
|
+
} else {
|
|
35
|
+
target = document ? document.documentElement : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (theme && url) {
|
|
39
|
+
const staticTeardown = injectTheme(theme, target);
|
|
40
|
+
return loadAndInjectTheme(url, target)
|
|
41
|
+
.then(fetchTeardown => {
|
|
42
|
+
return () => {
|
|
43
|
+
if (typeof staticTeardown === 'function') staticTeardown();
|
|
44
|
+
fetchTeardown();
|
|
45
|
+
};
|
|
46
|
+
})
|
|
47
|
+
.catch(e => {
|
|
48
|
+
console.error('[origo-design] theme init failed', e);
|
|
49
|
+
return typeof staticTeardown === 'function' ? staticTeardown : () => undefined;
|
|
50
|
+
});
|
|
51
|
+
} else if (theme) {
|
|
52
|
+
const teardown = injectTheme(theme, target);
|
|
53
|
+
return Promise.resolve(typeof teardown === 'function' ? teardown : () => undefined);
|
|
54
|
+
} else if (url) {
|
|
55
|
+
return loadAndInjectTheme(url, target).catch(e => {
|
|
56
|
+
console.error('[origo-design] theme init failed', e);
|
|
57
|
+
return () => undefined;
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
console.warn('[origo-design] options provided with neither theme nor url');
|
|
61
|
+
return Promise.resolve(() => undefined);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return Promise.resolve();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Provides the Origo Design theme initialization for the Angular Web Adapter.
|
|
70
|
+
*
|
|
71
|
+
* @param optionsOrUrl URL string to the theme.json file, or an options object configuring the theme.
|
|
72
|
+
*/
|
|
73
|
+
export function provideOrigoTheme(optionsOrUrl: string | OrigoThemeOptions): Provider[] {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
provide: APP_INITIALIZER,
|
|
77
|
+
useFactory: (platformId: object, document: Document | null) =>
|
|
78
|
+
themeInitializerFactory(optionsOrUrl, platformId, document),
|
|
79
|
+
deps: [PLATFORM_ID, [new Optional(), DOCUMENT]],
|
|
80
|
+
multi: true,
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import '@angular/compiler';
|
|
2
|
+
import { getTestBed } from '@angular/core/testing';
|
|
3
|
+
import {
|
|
4
|
+
BrowserDynamicTestingModule,
|
|
5
|
+
platformBrowserDynamicTesting,
|
|
6
|
+
} from '@angular/platform-browser-dynamic/testing';
|
|
7
|
+
|
|
8
|
+
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
## 0.0.22 (2026-09-04)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- scaffold playground application and e2e testing suite with initial editor component and schema registry ([f097894](https://github.com/OrigoStudio/origo-design/commit/f097894))
|
|
6
|
+
- add GitHub Actions workflow to automate library releases using Nx Release ([3d6651f](https://github.com/OrigoStudio/origo-design/commit/3d6651f))
|
|
7
|
+
|
|
8
|
+
### ❤️ Thank You
|
|
9
|
+
|
|
10
|
+
- Hardik Patel @hardikpatelse
|
|
11
|
+
|
|
12
|
+
## 0.0.21 (2026-09-02)
|
|
13
|
+
|
|
14
|
+
### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add centralized test registry tool with validation logic, CI integration, and documentation artifacts ([384c1c7](https://github.com/OrigoStudio/origo-design/commit/384c1c7))
|
|
17
|
+
- implement central test registry with validation tools and CI integration ([02cf656](https://github.com/OrigoStudio/origo-design/commit/02cf656))
|
|
18
|
+
- initialize negative testing story and update sprint status tracking ([bbe5fdc](https://github.com/OrigoStudio/origo-design/commit/bbe5fdc))
|
|
19
|
+
- implement CLI framework with validation, testing, and CI/CD workflows ([ff6ce55](https://github.com/OrigoStudio/origo-design/commit/ff6ce55))
|
|
20
|
+
- implement validate command and schema validation logic for BADL files ([4f9e41d](https://github.com/OrigoStudio/origo-design/commit/4f9e41d))
|
|
21
|
+
- implement path traversal guards, remove process termination from library logic, and enable error cause preservation in @origo/cli ([0854f78](https://github.com/OrigoStudio/origo-design/commit/0854f78))
|
|
22
|
+
- initialize web-based editor component and track security remediation tasks in implementation artifacts ([65716bd](https://github.com/OrigoStudio/origo-design/commit/65716bd))
|
|
23
|
+
- document e2e npm verification plan for cli package releases ([22dab4c](https://github.com/OrigoStudio/origo-design/commit/22dab4c))
|
|
24
|
+
- initialize sprint status tracking and retrospective verification story for npm packaging ([c326751](https://github.com/OrigoStudio/origo-design/commit/c326751))
|
|
25
|
+
- add generated bundle for epic7-worker-csp spike ([16ca492](https://github.com/OrigoStudio/origo-design/commit/16ca492))
|
|
26
|
+
- add web worker CSP spike and associated implementation documentation ([ebf5aa4](https://github.com/OrigoStudio/origo-design/commit/ebf5aa4))
|
|
27
|
+
- implement web worker CSP spike with supporting documentation and harness tools ([f57b1e5](https://github.com/OrigoStudio/origo-design/commit/f57b1e5))
|
|
28
|
+
- add technical spike story for Epic 7 web worker CSP validation and ADR planning ([c977487](https://github.com/OrigoStudio/origo-design/commit/c977487))
|
|
29
|
+
- initialize sprint status tracking and create Epic 7 discovery research story ([b37b967](https://github.com/OrigoStudio/origo-design/commit/b37b967))
|
|
30
|
+
- implement entity generator command with template customization and ejection support ([02a3193](https://github.com/OrigoStudio/origo-design/commit/02a3193))
|
|
31
|
+
- implement entity generator command and template ejection logic in CLI ([d7d519a](https://github.com/OrigoStudio/origo-design/commit/d7d519a))
|
|
32
|
+
- implementation of 6-2-local-schema-validation ([45e67f6](https://github.com/OrigoStudio/origo-design/commit/45e67f6))
|
|
33
|
+
|
|
34
|
+
### ❤️ Thank You
|
|
35
|
+
|
|
36
|
+
- Hardik Patel @hardikpatelse
|
|
37
|
+
|
|
38
|
+
## 0.0.20 (2026-08-25)
|
|
39
|
+
|
|
40
|
+
### 🚀 Features
|
|
41
|
+
|
|
42
|
+
- implementation of 6-1-cli-initialization-and-scaffolding ([f6aca9e](https://github.com/OrigoStudio/origo-design/commit/f6aca9e))
|
|
43
|
+
|
|
44
|
+
### ❤️ Thank You
|
|
45
|
+
|
|
46
|
+
- Hardik Patel @hardikpatelse
|
|
47
|
+
|
|
48
|
+
## 0.0.19 (2026-08-23)
|
|
49
|
+
|
|
50
|
+
### 🚀 Features
|
|
51
|
+
|
|
52
|
+
- 5.5-5-define-secure-by-default-boilerplate-templates ([0eb6dde](https://github.com/OrigoStudio/origo-design/commit/0eb6dde))
|
|
53
|
+
|
|
54
|
+
### ❤️ Thank You
|
|
55
|
+
|
|
56
|
+
- Hardik Patel @hardikpatelse
|
|
57
|
+
|
|
58
|
+
## 0.0.18 (2026-08-23)
|
|
59
|
+
|
|
60
|
+
### 🚀 Features
|
|
61
|
+
|
|
62
|
+
- 5.5-4-verify-core-compiler-source-map-preservation ([a3b0d9b](https://github.com/OrigoStudio/origo-design/commit/a3b0d9b))
|
|
63
|
+
|
|
64
|
+
### 🩹 Fixes
|
|
65
|
+
|
|
66
|
+
- pipeline issue ([c1bbe47](https://github.com/OrigoStudio/origo-design/commit/c1bbe47))
|
|
67
|
+
|
|
68
|
+
### ❤️ Thank You
|
|
69
|
+
|
|
70
|
+
- Hardik Patel @hardikpatelse
|
|
71
|
+
|
|
72
|
+
## 0.0.17 (2026-08-21)
|
|
73
|
+
|
|
74
|
+
### 🚀 Features
|
|
75
|
+
|
|
76
|
+
- 5.5-3-resolve-recursive-schema-resolution-tech-debt ([943e2e7](https://github.com/OrigoStudio/origo-design/commit/943e2e7))
|
|
77
|
+
- 5.5-2-establish-end-to-end-qa-protocols ([d8990ae](https://github.com/OrigoStudio/origo-design/commit/d8990ae))
|
|
78
|
+
- 5.5-2-establish-end-to-end-qa-protocols ([a31d97a](https://github.com/OrigoStudio/origo-design/commit/a31d97a))
|
|
79
|
+
|
|
80
|
+
### ❤️ Thank You
|
|
81
|
+
|
|
82
|
+
- Hardik Patel @hardikpatelse
|
|
83
|
+
|
|
84
|
+
## 0.0.16 (2026-08-21)
|
|
85
|
+
|
|
86
|
+
### 🚀 Features
|
|
87
|
+
|
|
88
|
+
- 5.5-1-create-usability-quickstart-guide ([e740a1a](https://github.com/OrigoStudio/origo-design/commit/e740a1a))
|
|
89
|
+
|
|
90
|
+
### ❤️ Thank You
|
|
91
|
+
|
|
92
|
+
- Hardik Patel @hardikpatelse
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@origo/cli",
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"origo": "./src/main.js"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@origo/core": "0.0.22",
|
|
13
|
+
"commander": "^15.0.0",
|
|
14
|
+
"tslib": "^2.3.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { generateEntity } from '../../lib/generator';
|
|
3
|
+
import { handleError } from '../../utils/errors';
|
|
4
|
+
|
|
5
|
+
export function entityCommand(): Command {
|
|
6
|
+
const cmd = new Command('entity');
|
|
7
|
+
cmd
|
|
8
|
+
.description('Generate a new entity BADL schema')
|
|
9
|
+
.argument('<name>', 'Entity name (PascalCase, e.g. User)')
|
|
10
|
+
.option('--force', 'Overwrite existing file if it exists')
|
|
11
|
+
.option('--json', 'Output machine-readable JSON format')
|
|
12
|
+
.action(async (name: string, options: { force?: boolean; json?: boolean }) => {
|
|
13
|
+
try {
|
|
14
|
+
await generateEntity(name, { force: options.force, json: options.json });
|
|
15
|
+
} catch (error: unknown) {
|
|
16
|
+
handleError(error, { json: options.json });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return cmd;
|
|
20
|
+
}
|