@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@origo/core",
|
|
3
|
+
"version": "0.0.22",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"ajv": "^8.17.1",
|
|
10
|
+
"ajv-errors": "^3.0.0",
|
|
11
|
+
"ajv-formats": "^3.0.1",
|
|
12
|
+
"json-schema-to-typescript": "^14.1.0",
|
|
13
|
+
"json-source-map": "^0.6.1",
|
|
14
|
+
"semver": "^7.7.4",
|
|
15
|
+
"tslib": "^2.3.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/json-source-map": "^0.6.0",
|
|
19
|
+
"@types/semver": "^7.8.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { compileFromFile } from 'json-schema-to-typescript';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
async function generate() {
|
|
6
|
+
const schemasDir = path.join(__dirname, '../src/schemas');
|
|
7
|
+
const typesDir = path.join(__dirname, '../src/types');
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(typesDir)) {
|
|
10
|
+
fs.mkdirSync(typesDir, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const domainTs = await compileFromFile(path.join(schemasDir, 'domain.schema.json'), {
|
|
14
|
+
cwd: schemasDir,
|
|
15
|
+
declareExternallyReferenced: true,
|
|
16
|
+
});
|
|
17
|
+
fs.writeFileSync(path.join(typesDir, 'domain.ts'), domainTs);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
generate().catch(e => {
|
|
21
|
+
console.error(e);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import { ExtensionManifest, ExtensionLifecycle, CorePermission, ExtensionContext } from './types';
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
3
|
+
import * as semver from 'semver';
|
|
4
|
+
|
|
5
|
+
export class ExtensionError extends Error {
|
|
6
|
+
constructor(
|
|
7
|
+
message: string,
|
|
8
|
+
public readonly code: string
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ExtensionError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export enum ExtensionState {
|
|
16
|
+
REGISTERED = 'REGISTERED',
|
|
17
|
+
INITIALIZED = 'INITIALIZED',
|
|
18
|
+
CONFIGURED = 'CONFIGURED',
|
|
19
|
+
VALIDATED = 'VALIDATED',
|
|
20
|
+
ACTIVATED = 'ACTIVATED',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ExtensionManager {
|
|
24
|
+
private extensions: Map<
|
|
25
|
+
string,
|
|
26
|
+
{ manifest: ExtensionManifest; lifecycle: ExtensionLifecycle; state: ExtensionState }
|
|
27
|
+
> = new Map();
|
|
28
|
+
private capabilities: Set<string> = new Set();
|
|
29
|
+
private grantedPermissions: Map<string, Set<CorePermission>> = new Map();
|
|
30
|
+
|
|
31
|
+
public registerExtension(manifest: ExtensionManifest, lifecycle: ExtensionLifecycle) {
|
|
32
|
+
if (!manifest) throw new ExtensionError('Manifest is required', 'INVALID_MANIFEST');
|
|
33
|
+
if (!lifecycle) throw new ExtensionError('Lifecycle is required', 'INVALID_LIFECYCLE');
|
|
34
|
+
if (this.extensions.has(manifest.id)) {
|
|
35
|
+
throw new ExtensionError(
|
|
36
|
+
`Extension already registered: ${manifest.id}`,
|
|
37
|
+
'DUPLICATE_EXTENSION'
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
this.validateManifest(manifest);
|
|
41
|
+
this.grantedPermissions.set(manifest.id, new Set());
|
|
42
|
+
this.extensions.set(manifest.id, { manifest, lifecycle, state: ExtensionState.REGISTERED });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public registerCapability(capability: string) {
|
|
46
|
+
if (!/^[a-z0-9-]+:[a-z0-9-]+$/i.test(capability)) {
|
|
47
|
+
throw new ExtensionError(
|
|
48
|
+
`Invalid capability format: ${capability}. Must be namespace:name`,
|
|
49
|
+
'INVALID_CAPABILITY_NAMESPACE'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
this.capabilities.add(capability);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private validateManifest(manifest: ExtensionManifest) {
|
|
56
|
+
if (!manifest.id || !manifest.name || !manifest.version || !manifest.type) {
|
|
57
|
+
throw new ExtensionError('Invalid manifest: missing required fields', 'INVALID_MANIFEST');
|
|
58
|
+
}
|
|
59
|
+
if (!semver.valid(manifest.version)) {
|
|
60
|
+
throw new ExtensionError(`Invalid semver version: ${manifest.version}`, 'INVALID_VERSION');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (manifest.grammarRanges) {
|
|
64
|
+
if (typeof manifest.grammarRanges !== 'object' || Array.isArray(manifest.grammarRanges)) {
|
|
65
|
+
throw new ExtensionError('grammarRanges must be an object', 'INVALID_MANIFEST');
|
|
66
|
+
}
|
|
67
|
+
for (const [key, range] of Object.entries(manifest.grammarRanges)) {
|
|
68
|
+
if (typeof range !== 'string' || !semver.validRange(range)) {
|
|
69
|
+
throw new ExtensionError(
|
|
70
|
+
`Invalid semver range for grammar ${key}: ${range}`,
|
|
71
|
+
'INVALID_GRAMMAR_RANGE'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (manifest.apiRanges) {
|
|
78
|
+
if (typeof manifest.apiRanges !== 'object' || Array.isArray(manifest.apiRanges)) {
|
|
79
|
+
throw new ExtensionError('apiRanges must be an object', 'INVALID_MANIFEST');
|
|
80
|
+
}
|
|
81
|
+
for (const [key, range] of Object.entries(manifest.apiRanges)) {
|
|
82
|
+
if (typeof range !== 'string' || !semver.validRange(range)) {
|
|
83
|
+
throw new ExtensionError(
|
|
84
|
+
`Invalid semver range for API ${key}: ${range}`,
|
|
85
|
+
'INVALID_API_RANGE'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (manifest.dependencies) {
|
|
92
|
+
if (typeof manifest.dependencies !== 'object' || Array.isArray(manifest.dependencies)) {
|
|
93
|
+
throw new ExtensionError('dependencies must be an object', 'INVALID_MANIFEST');
|
|
94
|
+
}
|
|
95
|
+
for (const [key, range] of Object.entries(manifest.dependencies)) {
|
|
96
|
+
if (typeof range !== 'string' || !semver.validRange(range)) {
|
|
97
|
+
throw new ExtensionError(
|
|
98
|
+
`Invalid dependency range for ${key}: ${range}`,
|
|
99
|
+
'INVALID_DEPENDENCY_RANGE'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (manifest.permissions !== undefined) {
|
|
106
|
+
if (
|
|
107
|
+
!Array.isArray(manifest.permissions) ||
|
|
108
|
+
manifest.permissions.some(p => typeof p !== 'string' || !p.trim())
|
|
109
|
+
) {
|
|
110
|
+
throw new ExtensionError(
|
|
111
|
+
'permissions must be an array of non-empty strings',
|
|
112
|
+
'INVALID_MANIFEST'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private negotiateCapabilities(manifest: ExtensionManifest) {
|
|
119
|
+
if (manifest.capabilities) {
|
|
120
|
+
if (!Array.isArray(manifest.capabilities)) {
|
|
121
|
+
throw new ExtensionError('Capabilities must be an array', 'INVALID_MANIFEST');
|
|
122
|
+
}
|
|
123
|
+
for (const cap of manifest.capabilities) {
|
|
124
|
+
if (!this.capabilities.has(cap)) {
|
|
125
|
+
throw new ExtensionError(`Missing required capability: ${cap}`, 'MISSING_CAPABILITY');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public grantPermission(id: string, permission: CorePermission) {
|
|
132
|
+
const ext = this.extensions.get(id);
|
|
133
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
134
|
+
|
|
135
|
+
if (ext.state !== ExtensionState.REGISTERED) {
|
|
136
|
+
throw new ExtensionError(
|
|
137
|
+
`Cannot grant permissions after extension initialization (current state: ${ext.state})`,
|
|
138
|
+
'INVALID_LIFECYCLE'
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const declaredPermissions = ext.manifest.permissions || [];
|
|
143
|
+
if (!declaredPermissions.includes(permission)) {
|
|
144
|
+
throw new ExtensionError(
|
|
145
|
+
`Permission not declared in manifest: ${permission}`,
|
|
146
|
+
'UNDECLARED_PERMISSION'
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const granted = this.grantedPermissions.get(id)!;
|
|
151
|
+
granted.add(permission);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
public getDeclaredPermissions(id: string): CorePermission[] {
|
|
155
|
+
const ext = this.extensions.get(id);
|
|
156
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
157
|
+
return ext.manifest.permissions || [];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
public getGrantedPermissions(id: string): CorePermission[] {
|
|
161
|
+
const ext = this.extensions.get(id);
|
|
162
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
163
|
+
return Array.from(this.grantedPermissions.get(id)!);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public hasPermission(id: string, permission: CorePermission): boolean {
|
|
167
|
+
const ext = this.extensions.get(id);
|
|
168
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
169
|
+
return this.grantedPermissions.get(id)!.has(permission);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public checkPermission(id: string, permission: CorePermission) {
|
|
173
|
+
if (!this.hasPermission(id, permission)) {
|
|
174
|
+
throw new ExtensionError(
|
|
175
|
+
`Unauthorized access: Missing permission ${permission}`,
|
|
176
|
+
'UNAUTHORIZED_ACCESS'
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private createSandboxContext(id: string, baseContext?: ExtensionContext): ExtensionContext {
|
|
182
|
+
const context = baseContext || {};
|
|
183
|
+
return {
|
|
184
|
+
getEnv: context.getEnv
|
|
185
|
+
? (key: string) => {
|
|
186
|
+
this.checkPermission(id, 'env:read');
|
|
187
|
+
return context.getEnv!(key);
|
|
188
|
+
}
|
|
189
|
+
: undefined,
|
|
190
|
+
fetch: context.fetch
|
|
191
|
+
? (((...args: Parameters<typeof fetch>) => {
|
|
192
|
+
this.checkPermission(id, 'network:fetch');
|
|
193
|
+
return context.fetch!(...args);
|
|
194
|
+
}) as typeof fetch)
|
|
195
|
+
: undefined,
|
|
196
|
+
readFile: context.readFile
|
|
197
|
+
? (path: string) => {
|
|
198
|
+
this.checkPermission(id, 'fs:read');
|
|
199
|
+
return context.readFile!(path);
|
|
200
|
+
}
|
|
201
|
+
: undefined,
|
|
202
|
+
writeFile: context.writeFile
|
|
203
|
+
? (path: string, data: string) => {
|
|
204
|
+
this.checkPermission(id, 'fs:write');
|
|
205
|
+
return context.writeFile!(path, data);
|
|
206
|
+
}
|
|
207
|
+
: undefined,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private enforcePermissionsBeforeExecution(manifest: ExtensionManifest) {
|
|
212
|
+
const id = manifest.id;
|
|
213
|
+
const declared = manifest.permissions || [];
|
|
214
|
+
const granted = this.grantedPermissions.get(id)!;
|
|
215
|
+
|
|
216
|
+
for (const perm of declared) {
|
|
217
|
+
if (!granted.has(perm)) {
|
|
218
|
+
throw new ExtensionError(
|
|
219
|
+
`Cannot execute extension ${id}: Missing required permission ${perm}`,
|
|
220
|
+
'UNAUTHORIZED_ACCESS'
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public resolveDependencyGraph(): string[] {
|
|
227
|
+
const adjList = new Map<string, string[]>();
|
|
228
|
+
|
|
229
|
+
for (const [id, { manifest }] of this.extensions.entries()) {
|
|
230
|
+
const deps = manifest.dependencies ? Object.keys(manifest.dependencies) : [];
|
|
231
|
+
|
|
232
|
+
for (const [depId, range] of Object.entries(manifest.dependencies || {})) {
|
|
233
|
+
const target = this.extensions.get(depId);
|
|
234
|
+
if (!target) {
|
|
235
|
+
throw new ExtensionError(`Missing required dependency: ${depId}`, 'MISSING_DEPENDENCY');
|
|
236
|
+
}
|
|
237
|
+
if (!semver.satisfies(target.manifest.version, range)) {
|
|
238
|
+
throw new ExtensionError(
|
|
239
|
+
`Dependency version mismatch for ${depId}. Expected ${range}, got ${target.manifest.version}`,
|
|
240
|
+
'DEPENDENCY_VERSION_MISMATCH'
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
adjList.set(id, deps);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const visited = new Set<string>();
|
|
249
|
+
const visiting = new Set<string>();
|
|
250
|
+
const sorted: string[] = [];
|
|
251
|
+
|
|
252
|
+
const dfs = (node: string, path: string[]) => {
|
|
253
|
+
if (visiting.has(node)) {
|
|
254
|
+
const cycleStartIndex = path.indexOf(node);
|
|
255
|
+
const loop = cycleStartIndex >= 0 ? path.slice(cycleStartIndex) : path;
|
|
256
|
+
const cyclePath = [...loop, node].join(' -> ');
|
|
257
|
+
throw new ExtensionError(`Cyclic dependency detected: ${cyclePath}`, 'CYCLIC_DEPENDENCY');
|
|
258
|
+
}
|
|
259
|
+
if (visited.has(node)) return;
|
|
260
|
+
|
|
261
|
+
visiting.add(node);
|
|
262
|
+
path.push(node);
|
|
263
|
+
|
|
264
|
+
const deps = adjList.get(node) || [];
|
|
265
|
+
for (const dep of deps) {
|
|
266
|
+
dfs(dep, path);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
path.pop();
|
|
270
|
+
visiting.delete(node);
|
|
271
|
+
visited.add(node);
|
|
272
|
+
sorted.push(node); // post-order (dependencies added before the node that depends on them)
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
for (const id of this.extensions.keys()) {
|
|
276
|
+
if (!visited.has(id)) {
|
|
277
|
+
dfs(id, []);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return sorted;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public async initializeExtension(id: string, context?: ExtensionContext) {
|
|
285
|
+
const ext = this.extensions.get(id);
|
|
286
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
287
|
+
if (ext.state !== ExtensionState.REGISTERED) {
|
|
288
|
+
throw new ExtensionError(
|
|
289
|
+
`Cannot initialize extension in state ${ext.state}`,
|
|
290
|
+
'INVALID_LIFECYCLE'
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.enforcePermissionsBeforeExecution(ext.manifest);
|
|
295
|
+
this.negotiateCapabilities(ext.manifest);
|
|
296
|
+
|
|
297
|
+
const sandboxedContext = this.createSandboxContext(id, context);
|
|
298
|
+
await ext.lifecycle.initialize(sandboxedContext);
|
|
299
|
+
ext.state = ExtensionState.INITIALIZED;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public async configureExtension(id: string, config?: unknown) {
|
|
303
|
+
const ext = this.extensions.get(id);
|
|
304
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
305
|
+
if (ext.state !== ExtensionState.INITIALIZED) {
|
|
306
|
+
throw new ExtensionError(
|
|
307
|
+
`Cannot configure extension in state ${ext.state}`,
|
|
308
|
+
'INVALID_LIFECYCLE'
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
this.enforcePermissionsBeforeExecution(ext.manifest);
|
|
313
|
+
await ext.lifecycle.configure(config);
|
|
314
|
+
ext.state = ExtensionState.CONFIGURED;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public async validateExtension(id: string) {
|
|
318
|
+
const ext = this.extensions.get(id);
|
|
319
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
320
|
+
if (ext.state !== ExtensionState.CONFIGURED) {
|
|
321
|
+
throw new ExtensionError(
|
|
322
|
+
`Cannot validate extension in state ${ext.state}`,
|
|
323
|
+
'INVALID_LIFECYCLE'
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this.enforcePermissionsBeforeExecution(ext.manifest);
|
|
328
|
+
const isValid = await ext.lifecycle.validate();
|
|
329
|
+
if (!isValid) {
|
|
330
|
+
throw new ExtensionError(`Extension failed validation: ${id}`, 'VALIDATION_FAILED');
|
|
331
|
+
}
|
|
332
|
+
ext.state = ExtensionState.VALIDATED;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public async activateExtension(id: string) {
|
|
336
|
+
const ext = this.extensions.get(id);
|
|
337
|
+
if (!ext) throw new ExtensionError(`Extension not found: ${id}`, 'NOT_FOUND');
|
|
338
|
+
if (ext.state !== ExtensionState.VALIDATED) {
|
|
339
|
+
throw new ExtensionError(
|
|
340
|
+
`Cannot activate extension in state ${ext.state}`,
|
|
341
|
+
'INVALID_LIFECYCLE'
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.enforcePermissionsBeforeExecution(ext.manifest);
|
|
346
|
+
await ext.lifecycle.activate();
|
|
347
|
+
ext.state = ExtensionState.ACTIVATED;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
public async loadAll(context?: ExtensionContext, config?: unknown) {
|
|
351
|
+
const sortedIds = this.resolveDependencyGraph();
|
|
352
|
+
|
|
353
|
+
for (const id of sortedIds) {
|
|
354
|
+
if (this.getExtensionState(id) === ExtensionState.REGISTERED) {
|
|
355
|
+
await this.initializeExtension(id, context);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
for (const id of sortedIds) {
|
|
359
|
+
if (this.getExtensionState(id) === ExtensionState.INITIALIZED) {
|
|
360
|
+
await this.configureExtension(id, config);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
for (const id of sortedIds) {
|
|
364
|
+
if (this.getExtensionState(id) === ExtensionState.CONFIGURED) {
|
|
365
|
+
await this.validateExtension(id);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
for (const id of sortedIds) {
|
|
369
|
+
if (this.getExtensionState(id) === ExtensionState.VALIDATED) {
|
|
370
|
+
await this.activateExtension(id);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
public getExtensionState(id: string): ExtensionState | undefined {
|
|
376
|
+
return this.extensions.get(id)?.state;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type CorePermission = 'network:fetch' | 'fs:read' | 'fs:write' | 'env:read';
|
|
2
|
+
|
|
3
|
+
export interface ExtensionContext {
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
readFile?: (path: string) => Promise<string>;
|
|
6
|
+
writeFile?: (path: string, data: string) => Promise<void>;
|
|
7
|
+
getEnv?: (key: string) => string | undefined;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ExtensionManifest {
|
|
12
|
+
/** The unique identifier of the extension */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Name of the extension */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Semantic version of the extension */
|
|
17
|
+
version: string;
|
|
18
|
+
/** Type of the extension */
|
|
19
|
+
type: string;
|
|
20
|
+
/** Grammar ranges required */
|
|
21
|
+
grammarRanges?: Record<string, string>;
|
|
22
|
+
/** API ranges required */
|
|
23
|
+
apiRanges?: Record<string, string>;
|
|
24
|
+
/** Capabilities required by this extension */
|
|
25
|
+
capabilities?: string[];
|
|
26
|
+
/** Permissions required by this extension for sandbox access */
|
|
27
|
+
permissions?: CorePermission[];
|
|
28
|
+
/** Dependencies on other extensions (map of extension id to semver range) */
|
|
29
|
+
dependencies?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ExtensionLifecycle {
|
|
33
|
+
initialize(context?: ExtensionContext): Promise<void> | void;
|
|
34
|
+
configure(config?: unknown): Promise<void> | void;
|
|
35
|
+
validate(): Promise<boolean> | boolean;
|
|
36
|
+
activate(): Promise<void> | void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "page-user-management",
|
|
3
|
+
"name": "User Management Page",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"domain": "User Management",
|
|
6
|
+
"entities": [
|
|
7
|
+
{
|
|
8
|
+
"id": "entity-user",
|
|
9
|
+
"name": "User",
|
|
10
|
+
"fields": [
|
|
11
|
+
{
|
|
12
|
+
"id": "field-user-id",
|
|
13
|
+
"name": "id",
|
|
14
|
+
"type": "string",
|
|
15
|
+
"label": "User ID",
|
|
16
|
+
"validation": ["required", "uuid"],
|
|
17
|
+
"metadata_path": "User.id"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "field-user-username",
|
|
21
|
+
"name": "username",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"label": "Username",
|
|
24
|
+
"validation": ["required", "minLength:3", "maxLength:50"],
|
|
25
|
+
"metadata_path": "User.username"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "field-user-email",
|
|
29
|
+
"name": "email",
|
|
30
|
+
"type": "string",
|
|
31
|
+
"label": "Email Address",
|
|
32
|
+
"validation": ["required", "email"],
|
|
33
|
+
"metadata_path": "User.email"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "field-user-isActive",
|
|
37
|
+
"name": "isActive",
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"label": "Active Status",
|
|
40
|
+
"validation": ["required"],
|
|
41
|
+
"metadata_path": "User.isActive"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "field-user-createdAt",
|
|
45
|
+
"name": "createdAt",
|
|
46
|
+
"type": "date",
|
|
47
|
+
"label": "Creation Date",
|
|
48
|
+
"validation": ["required"],
|
|
49
|
+
"metadata_path": "User.createdAt"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "field-user-roleId",
|
|
53
|
+
"name": "roleId",
|
|
54
|
+
"type": "string",
|
|
55
|
+
"label": "Role ID",
|
|
56
|
+
"references": "entity-role",
|
|
57
|
+
"validation": ["required"],
|
|
58
|
+
"metadata_path": "User.roleId"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "field-user-departmentId",
|
|
62
|
+
"name": "departmentId",
|
|
63
|
+
"type": "string",
|
|
64
|
+
"label": "Department ID",
|
|
65
|
+
"references": "entity-department",
|
|
66
|
+
"validation": ["uuid"],
|
|
67
|
+
"metadata_path": "User.departmentId"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": "entity-role",
|
|
73
|
+
"name": "Role",
|
|
74
|
+
"fields": [
|
|
75
|
+
{
|
|
76
|
+
"id": "field-role-id",
|
|
77
|
+
"name": "id",
|
|
78
|
+
"type": "string",
|
|
79
|
+
"label": "Role ID",
|
|
80
|
+
"validation": ["required", "uuid"],
|
|
81
|
+
"metadata_path": "Role.id"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "field-role-name",
|
|
85
|
+
"name": "name",
|
|
86
|
+
"type": "string",
|
|
87
|
+
"label": "Role Name",
|
|
88
|
+
"validation": ["required"],
|
|
89
|
+
"metadata_path": "Role.name"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "field-role-permissions",
|
|
93
|
+
"name": "permissions",
|
|
94
|
+
"type": "array",
|
|
95
|
+
"itemType": "string",
|
|
96
|
+
"label": "Permissions",
|
|
97
|
+
"validation": [],
|
|
98
|
+
"metadata_path": "Role.permissions"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "entity-department",
|
|
104
|
+
"name": "Department",
|
|
105
|
+
"fields": [
|
|
106
|
+
{
|
|
107
|
+
"id": "field-department-id",
|
|
108
|
+
"name": "id",
|
|
109
|
+
"type": "string",
|
|
110
|
+
"label": "Department ID",
|
|
111
|
+
"validation": ["required", "uuid"],
|
|
112
|
+
"metadata_path": "Department.id"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"id": "field-department-name",
|
|
116
|
+
"name": "name",
|
|
117
|
+
"type": "string",
|
|
118
|
+
"label": "Department Name",
|
|
119
|
+
"validation": ["required"],
|
|
120
|
+
"metadata_path": "Department.name"
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
],
|
|
125
|
+
"capabilities": [
|
|
126
|
+
{
|
|
127
|
+
"id": "cap-user-create",
|
|
128
|
+
"name": "Create",
|
|
129
|
+
"description": "Create a User",
|
|
130
|
+
"type": "Command",
|
|
131
|
+
"entityId": "entity-user",
|
|
132
|
+
"outcome_ref": ["outcome-1"],
|
|
133
|
+
"preconditions": ["pre-1"],
|
|
134
|
+
"postconditions": ["post-1"],
|
|
135
|
+
"permissions": [{ "role": "perm-1" }],
|
|
136
|
+
"risk_level": "medium",
|
|
137
|
+
"async": false
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"id": "cap-user-read",
|
|
141
|
+
"name": "Read",
|
|
142
|
+
"description": "Read a User",
|
|
143
|
+
"type": "Query",
|
|
144
|
+
"entityId": "entity-user",
|
|
145
|
+
"outcome_ref": [],
|
|
146
|
+
"preconditions": [],
|
|
147
|
+
"postconditions": [],
|
|
148
|
+
"permissions": [{ "role": "admin" }],
|
|
149
|
+
"risk_level": "low",
|
|
150
|
+
"interaction_contract_ref": "ref",
|
|
151
|
+
"async": true
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "cap-user-update",
|
|
155
|
+
"name": "Update",
|
|
156
|
+
"description": "Update a User",
|
|
157
|
+
"type": "Command",
|
|
158
|
+
"entityId": "entity-user",
|
|
159
|
+
"outcome_ref": [],
|
|
160
|
+
"preconditions": [],
|
|
161
|
+
"postconditions": [],
|
|
162
|
+
"permissions": [{ "role": "admin" }],
|
|
163
|
+
"risk_level": "high",
|
|
164
|
+
"async": false
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "cap-user-delete",
|
|
168
|
+
"name": "Delete",
|
|
169
|
+
"description": "Delete a User",
|
|
170
|
+
"type": "Command",
|
|
171
|
+
"entityId": "entity-user",
|
|
172
|
+
"outcome_ref": [],
|
|
173
|
+
"preconditions": [],
|
|
174
|
+
"postconditions": [],
|
|
175
|
+
"permissions": [{ "role": "admin" }],
|
|
176
|
+
"risk_level": "critical",
|
|
177
|
+
"async": false
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"id": "cap-user-list",
|
|
181
|
+
"name": "List",
|
|
182
|
+
"description": "List Users",
|
|
183
|
+
"type": "Query",
|
|
184
|
+
"entityId": "entity-user",
|
|
185
|
+
"outcome_ref": [],
|
|
186
|
+
"preconditions": [],
|
|
187
|
+
"postconditions": [],
|
|
188
|
+
"permissions": [{ "role": "admin" }],
|
|
189
|
+
"risk_level": "low",
|
|
190
|
+
"interaction_contract_ref": "ref",
|
|
191
|
+
"async": false
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"id": "cap-role-list",
|
|
195
|
+
"name": "List",
|
|
196
|
+
"description": "List Roles",
|
|
197
|
+
"type": "Query",
|
|
198
|
+
"entityId": "entity-role",
|
|
199
|
+
"outcome_ref": [],
|
|
200
|
+
"preconditions": [],
|
|
201
|
+
"postconditions": [],
|
|
202
|
+
"permissions": [{ "role": "admin" }],
|
|
203
|
+
"risk_level": "low",
|
|
204
|
+
"interaction_contract_ref": "ref",
|
|
205
|
+
"async": false
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"id": "cap-department-list",
|
|
209
|
+
"name": "List",
|
|
210
|
+
"description": "List Departments",
|
|
211
|
+
"type": "Query",
|
|
212
|
+
"entityId": "entity-department",
|
|
213
|
+
"outcome_ref": [],
|
|
214
|
+
"preconditions": [],
|
|
215
|
+
"postconditions": [],
|
|
216
|
+
"permissions": [{ "role": "admin" }],
|
|
217
|
+
"risk_level": "low",
|
|
218
|
+
"interaction_contract_ref": "ref",
|
|
219
|
+
"async": false
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|