@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.
Files changed (209) hide show
  1. package/.editorconfig +13 -0
  2. package/.gitattributes +22 -0
  3. package/.perf-baseline.json +12 -0
  4. package/.prettierignore +13 -0
  5. package/.prettierrc +10 -0
  6. package/.vscode/extensions.json +3 -0
  7. package/BRANCH-PROTECTION.md +25 -0
  8. package/CHANGELOG.md +292 -0
  9. package/LICENSE +21 -0
  10. package/README.md +11 -0
  11. package/commitlint.config.js +6 -0
  12. package/dist/packages/cli/CHANGELOG.md +92 -0
  13. package/dist/packages/cli/README.md +11 -0
  14. package/dist/packages/cli/package.json +16 -0
  15. package/dist/packages/cli/package.json.bak +16 -0
  16. package/dist/packages/cli/src/commands/generate/entity.d.ts +2 -0
  17. package/dist/packages/cli/src/commands/generate/entity.js +24 -0
  18. package/dist/packages/cli/src/commands/generate/entity.js.map +1 -0
  19. package/dist/packages/cli/src/commands/generate/index.d.ts +2 -0
  20. package/dist/packages/cli/src/commands/generate/index.js +30 -0
  21. package/dist/packages/cli/src/commands/generate/index.js.map +1 -0
  22. package/dist/packages/cli/src/commands/init.d.ts +5 -0
  23. package/dist/packages/cli/src/commands/init.js +27 -0
  24. package/dist/packages/cli/src/commands/init.js.map +1 -0
  25. package/dist/packages/cli/src/commands/new.d.ts +2 -0
  26. package/dist/packages/cli/src/commands/new.js +24 -0
  27. package/dist/packages/cli/src/commands/new.js.map +1 -0
  28. package/dist/packages/cli/src/commands/validate.d.ts +2 -0
  29. package/dist/packages/cli/src/commands/validate.js +50 -0
  30. package/dist/packages/cli/src/commands/validate.js.map +1 -0
  31. package/dist/packages/cli/src/index.d.ts +11 -0
  32. package/dist/packages/cli/src/index.js +15 -0
  33. package/dist/packages/cli/src/index.js.map +1 -0
  34. package/dist/packages/cli/src/lib/cli.d.ts +1 -0
  35. package/dist/packages/cli/src/lib/cli.js +7 -0
  36. package/dist/packages/cli/src/lib/cli.js.map +1 -0
  37. package/dist/packages/cli/src/lib/generator.d.ts +16 -0
  38. package/dist/packages/cli/src/lib/generator.js +165 -0
  39. package/dist/packages/cli/src/lib/generator.js.map +1 -0
  40. package/dist/packages/cli/src/lib/scaffolding.d.ts +5 -0
  41. package/dist/packages/cli/src/lib/scaffolding.js +87 -0
  42. package/dist/packages/cli/src/lib/scaffolding.js.map +1 -0
  43. package/dist/packages/cli/src/lib/validation.d.ts +4 -0
  44. package/dist/packages/cli/src/lib/validation.js +191 -0
  45. package/dist/packages/cli/src/lib/validation.js.map +1 -0
  46. package/dist/packages/cli/src/main.d.ts +4 -0
  47. package/dist/packages/cli/src/main.js +38 -0
  48. package/dist/packages/cli/src/main.js.map +1 -0
  49. package/dist/packages/cli/src/templates/entity.json +6 -0
  50. package/dist/packages/cli/src/templates/extension.json +8 -0
  51. package/dist/packages/cli/src/templates/index.d.ts +29 -0
  52. package/dist/packages/cli/src/templates/index.js +61 -0
  53. package/dist/packages/cli/src/templates/index.js.map +1 -0
  54. package/dist/packages/cli/src/templates/origo.json +16 -0
  55. package/dist/packages/cli/src/utils/errors.d.ts +15 -0
  56. package/dist/packages/cli/src/utils/errors.js +58 -0
  57. package/dist/packages/cli/src/utils/errors.js.map +1 -0
  58. package/dist/packages/core/CHANGELOG.md +275 -0
  59. package/dist/packages/core/README.md +11 -0
  60. package/dist/packages/core/package.json +21 -0
  61. package/dist/packages/core/package.json.bak +21 -0
  62. package/dist/packages/core/src/extension-api/extension-manager.d.ts +35 -0
  63. package/dist/packages/core/src/extension-api/extension-manager.js +300 -0
  64. package/dist/packages/core/src/extension-api/extension-manager.js.map +1 -0
  65. package/dist/packages/core/src/extension-api/index.d.ts +2 -0
  66. package/dist/packages/core/src/extension-api/index.js +6 -0
  67. package/dist/packages/core/src/extension-api/index.js.map +1 -0
  68. package/dist/packages/core/src/extension-api/types.d.ts +34 -0
  69. package/dist/packages/core/src/extension-api/types.js +3 -0
  70. package/dist/packages/core/src/extension-api/types.js.map +1 -0
  71. package/dist/packages/core/src/index.d.ts +3 -0
  72. package/dist/packages/core/src/index.js +7 -0
  73. package/dist/packages/core/src/index.js.map +1 -0
  74. package/dist/packages/core/src/schemas/capability.schema.json +104 -0
  75. package/dist/packages/core/src/schemas/contract.schema.json +52 -0
  76. package/dist/packages/core/src/schemas/domain.schema.json +47 -0
  77. package/dist/packages/core/src/schemas/entity.schema.json +72 -0
  78. package/dist/packages/core/src/schemas/extension.schema.json +39 -0
  79. package/dist/packages/core/src/schemas/permission.schema.json +21 -0
  80. package/dist/packages/core/src/types/ast.d.ts +17 -0
  81. package/dist/packages/core/src/types/ast.js +3 -0
  82. package/dist/packages/core/src/types/ast.js.map +1 -0
  83. package/dist/packages/core/src/types/domain.d.ts +101 -0
  84. package/dist/packages/core/src/types/domain.js +8 -0
  85. package/dist/packages/core/src/types/domain.js.map +1 -0
  86. package/dist/packages/core/src/types/validation.d.ts +12 -0
  87. package/dist/packages/core/src/types/validation.js +16 -0
  88. package/dist/packages/core/src/types/validation.js.map +1 -0
  89. package/dist/packages/core/src/validator/ast-validator.d.ts +11 -0
  90. package/dist/packages/core/src/validator/ast-validator.js +388 -0
  91. package/dist/packages/core/src/validator/ast-validator.js.map +1 -0
  92. package/dist/packages/core/src/validator/index.d.ts +23 -0
  93. package/dist/packages/core/src/validator/index.js +143 -0
  94. package/dist/packages/core/src/validator/index.js.map +1 -0
  95. package/dist/packages/core/src/validator/serializer.d.ts +11 -0
  96. package/dist/packages/core/src/validator/serializer.js +99 -0
  97. package/dist/packages/core/src/validator/serializer.js.map +1 -0
  98. package/eslint.config.js +114 -0
  99. package/generate_prompts.js +19 -0
  100. package/jest.config.ts +5 -0
  101. package/jest.preset.js +3 -0
  102. package/nx.json +169 -0
  103. package/package.json +115 -0
  104. package/packages/angular-renderer/CHANGELOG.md +274 -0
  105. package/packages/angular-renderer/README.md +11 -0
  106. package/packages/angular-renderer/package.json +20 -0
  107. package/packages/angular-renderer/playwright-ct.config.ts +23 -0
  108. package/packages/angular-renderer/src/adapters/web/adapter.ts +145 -0
  109. package/packages/angular-renderer/src/adapters/web/experience-adapter.service.ts +21 -0
  110. package/packages/angular-renderer/src/components/primitives/button/button.component.html +9 -0
  111. package/packages/angular-renderer/src/components/primitives/button/button.component.pw.ts +19 -0
  112. package/packages/angular-renderer/src/components/primitives/button/button.component.scss +31 -0
  113. package/packages/angular-renderer/src/components/primitives/button/button.component.ts +67 -0
  114. package/packages/angular-renderer/src/components/primitives/primitives.a11y.pw.ts +33 -0
  115. package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.html +10 -0
  116. package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.pw.ts +29 -0
  117. package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.scss +42 -0
  118. package/packages/angular-renderer/src/components/primitives/text-input/text-input.component.ts +84 -0
  119. package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.html +2 -0
  120. package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.pw.ts +19 -0
  121. package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.scss +5 -0
  122. package/packages/angular-renderer/src/components/primitives/vbox/vbox.component.ts +74 -0
  123. package/packages/angular-renderer/src/index.ts +7 -0
  124. package/packages/angular-renderer/src/lib/renderer.component.ts +118 -0
  125. package/packages/angular-renderer/src/lib/renderer.tokens.ts +9 -0
  126. package/packages/angular-renderer/src/lib/test-utils.ts +26 -0
  127. package/packages/angular-renderer/src/lib/theme.provider.ts +83 -0
  128. package/packages/angular-renderer/src/test-setup.ts +8 -0
  129. package/packages/cli/CHANGELOG.md +92 -0
  130. package/packages/cli/README.md +11 -0
  131. package/packages/cli/package.json +16 -0
  132. package/packages/cli/src/commands/generate/entity.ts +20 -0
  133. package/packages/cli/src/commands/generate/index.ts +28 -0
  134. package/packages/cli/src/commands/init.ts +28 -0
  135. package/packages/cli/src/commands/new.ts +22 -0
  136. package/packages/cli/src/commands/validate.ts +47 -0
  137. package/packages/cli/src/index.ts +11 -0
  138. package/packages/cli/src/lib/cli.ts +3 -0
  139. package/packages/cli/src/lib/generator.ts +181 -0
  140. package/packages/cli/src/lib/scaffolding.ts +104 -0
  141. package/packages/cli/src/lib/validation.ts +218 -0
  142. package/packages/cli/src/main.ts +38 -0
  143. package/packages/cli/src/templates/entity.json +6 -0
  144. package/packages/cli/src/templates/extension.json +8 -0
  145. package/packages/cli/src/templates/index.ts +94 -0
  146. package/packages/cli/src/templates/origo.json +16 -0
  147. package/packages/cli/src/utils/errors.ts +80 -0
  148. package/packages/core/CHANGELOG.md +275 -0
  149. package/packages/core/README.md +11 -0
  150. package/packages/core/package-lock.json +1035 -0
  151. package/packages/core/package.json +21 -0
  152. package/packages/core/scripts/generate-types.ts +23 -0
  153. package/packages/core/src/extension-api/extension-manager.ts +378 -0
  154. package/packages/core/src/extension-api/index.ts +2 -0
  155. package/packages/core/src/extension-api/types.ts +37 -0
  156. package/packages/core/src/index.ts +3 -0
  157. package/packages/core/src/schemas/__fixtures__/target-page.json +222 -0
  158. package/packages/core/src/schemas/capability.schema.json +104 -0
  159. package/packages/core/src/schemas/contract.schema.json +52 -0
  160. package/packages/core/src/schemas/domain.schema.json +47 -0
  161. package/packages/core/src/schemas/entity.schema.json +72 -0
  162. package/packages/core/src/schemas/extension.schema.json +39 -0
  163. package/packages/core/src/schemas/permission.schema.json +21 -0
  164. package/packages/core/src/types/ast.ts +20 -0
  165. package/packages/core/src/types/domain.ts +105 -0
  166. package/packages/core/src/types/validation.ts +29 -0
  167. package/packages/core/src/validator/ast-validator.ts +406 -0
  168. package/packages/core/src/validator/index.ts +207 -0
  169. package/packages/core/src/validator/serializer.ts +120 -0
  170. package/packages/core/test/fixtures/target-page.json +87 -0
  171. package/packages/design-tokens/CHANGELOG.md +273 -0
  172. package/packages/design-tokens/README.md +11 -0
  173. package/packages/design-tokens/package.json +25 -0
  174. package/packages/design-tokens/src/build.ts +97 -0
  175. package/packages/design-tokens/src/compile.ts +60 -0
  176. package/packages/design-tokens/src/index.ts +7 -0
  177. package/packages/design-tokens/src/runtime/index.ts +2 -0
  178. package/packages/design-tokens/src/runtime/theme-fetcher.ts +90 -0
  179. package/packages/design-tokens/src/runtime/theme-injector.ts +149 -0
  180. package/packages/design-tokens/src/schemas/base-tokens.schema.json +127 -0
  181. package/packages/design-tokens/src/schemas/semantic-tokens.schema.json +52 -0
  182. package/packages/design-tokens/src/tokens/base.json +46 -0
  183. package/packages/design-tokens/src/tokens/semantic.json +33 -0
  184. package/packages/playground/CHANGELOG.md +12 -0
  185. package/packages/playground/package.json +10 -0
  186. package/packages/playground/public/favicon.ico +0 -0
  187. package/packages/playground/src/app/app.component.html +1 -0
  188. package/packages/playground/src/app/app.component.scss +4 -0
  189. package/packages/playground/src/app/app.component.ts +13 -0
  190. package/packages/playground/src/app/app.config.ts +9 -0
  191. package/packages/playground/src/editor/badl-editor.component.html +1 -0
  192. package/packages/playground/src/editor/badl-editor.component.scss +10 -0
  193. package/packages/playground/src/editor/badl-editor.component.ts +57 -0
  194. package/packages/playground/src/editor/editor.worker.ts +1 -0
  195. package/packages/playground/src/editor/json.worker.ts +1 -0
  196. package/packages/playground/src/editor/monaco-environment.ts +11 -0
  197. package/packages/playground/src/editor/schema-registry.ts +30 -0
  198. package/packages/playground/src/index.html +13 -0
  199. package/packages/playground/src/main.ts +5 -0
  200. package/packages/playground/src/schemas/capability.schema.json +104 -0
  201. package/packages/playground/src/schemas/contract.schema.json +52 -0
  202. package/packages/playground/src/schemas/domain.schema.json +47 -0
  203. package/packages/playground/src/schemas/entity.schema.json +72 -0
  204. package/packages/playground/src/schemas/extension.schema.json +39 -0
  205. package/packages/playground/src/schemas/permission.schema.json +21 -0
  206. package/packages/playground/src/styles.scss +8 -0
  207. package/packages/playground/src/test-setup.ts +146 -0
  208. package/packages/playground/vite.config.ts +12 -0
  209. package/packages/playground/vitest.config.mjs +24 -0
@@ -0,0 +1,28 @@
1
+ import { Command } from 'commander';
2
+ import { entityCommand } from './entity';
3
+ import { ejectTemplates } from '../../lib/generator';
4
+ import { handleError } from '../../utils/errors';
5
+
6
+ export function generateCommand(): Command {
7
+ const cmd = new Command('generate').description('Generate Origo artifacts');
8
+
9
+ cmd.addCommand(entityCommand());
10
+
11
+ cmd
12
+ .option('--eject', 'Copy internal templates to .origo/templates/ for customization')
13
+ .option('--force', 'Overwrite existing templates if they exist')
14
+ .option('--json', 'Output machine-readable JSON format')
15
+ .action(async (options: { eject?: boolean; force?: boolean; json?: boolean }) => {
16
+ if (options.eject) {
17
+ try {
18
+ await ejectTemplates({ force: options.force, json: options.json });
19
+ } catch (error: unknown) {
20
+ handleError(error, { json: options.json });
21
+ }
22
+ } else {
23
+ cmd.outputHelp();
24
+ }
25
+ });
26
+
27
+ return cmd;
28
+ }
@@ -0,0 +1,28 @@
1
+ import { Command } from 'commander';
2
+ import { scaffoldProject } from '../lib/scaffolding';
3
+ import { handleError } from '../utils/errors';
4
+
5
+ export function initCommand(): Command {
6
+ const init = new Command('init')
7
+ .description('Initialize a new Origo project (alias for origo new)')
8
+ .argument('<project-name>', 'Name of the project to initialize')
9
+ .option('--json', 'Output machine-readable JSON format')
10
+ .action(async (projectName: string, options: { json?: boolean }) => {
11
+ try {
12
+ await initializeProject(projectName, process.cwd(), options);
13
+ } catch (error) {
14
+ handleError(error, { json: options.json });
15
+ }
16
+ });
17
+
18
+ return init;
19
+ }
20
+
21
+ export async function initializeProject(
22
+ projectName: string,
23
+ targetPath: string = process.cwd(),
24
+ options: { json?: boolean } = {}
25
+ ): Promise<void> {
26
+ // Delegate directly to scaffoldProject to maintain single source of truth for scaffolding
27
+ await scaffoldProject(projectName, { ...options, cwd: targetPath });
28
+ }
@@ -0,0 +1,22 @@
1
+ import { Command } from 'commander';
2
+ import { scaffoldProject } from '../lib/scaffolding';
3
+ import { handleError } from '../utils/errors';
4
+
5
+ export function newCommand(): Command {
6
+ const cmd = new Command('new');
7
+
8
+ cmd
9
+ .description('Scaffold a new Origo project')
10
+ .argument('<project-name>', 'Name of the project to scaffold')
11
+ .option('--json', 'Output machine-readable JSON format')
12
+ .action(async (projectName: string, options: { json?: boolean }) => {
13
+ try {
14
+ await scaffoldProject(projectName, { json: options.json });
15
+ } catch (error) {
16
+ handleError(error, { json: options.json });
17
+ process.exitCode = 1;
18
+ }
19
+ });
20
+
21
+ return cmd;
22
+ }
@@ -0,0 +1,47 @@
1
+ import { Command } from 'commander';
2
+ import { validateDirectory } from '../lib/validation';
3
+ import { handleError, CliError } from '../utils/errors';
4
+
5
+ export function validateCommand(): Command {
6
+ const cmd = new Command('validate');
7
+
8
+ cmd
9
+ .description('Validate BADL schemas in a directory or file')
10
+ .argument('[directory]', 'Directory or file containing BADL schemas to validate', './schemas')
11
+ .option('--json', 'Output machine-readable JSON format')
12
+ .action(async (directory: string, options: { json?: boolean }) => {
13
+ try {
14
+ const totalErrors = await validateDirectory(directory, { json: options.json });
15
+ if (totalErrors > 0) {
16
+ process.exitCode = 1;
17
+ }
18
+ } catch (error: unknown) {
19
+ if (options.json) {
20
+ const code = error instanceof CliError ? error.code : 'UNKNOWN_ERROR';
21
+ const message = error instanceof Error ? error.message : String(error);
22
+ const jsonOutput = {
23
+ status: 'error',
24
+ data: {
25
+ directory,
26
+ filesValidated: 0,
27
+ errorsFound: 1,
28
+ results: [
29
+ {
30
+ file: directory,
31
+ valid: false,
32
+ errors: [{ code, message }],
33
+ },
34
+ ],
35
+ },
36
+ };
37
+ console.log(JSON.stringify(jsonOutput, null, 2));
38
+ process.exitCode = 1;
39
+ } else {
40
+ handleError(error, { json: false });
41
+ process.exitCode = 1;
42
+ }
43
+ }
44
+ });
45
+
46
+ return cmd;
47
+ }
@@ -0,0 +1,11 @@
1
+ export * from './main';
2
+ export * from './commands/new';
3
+ export * from './commands/init';
4
+ export * from './commands/validate';
5
+ export * from './commands/generate';
6
+ export * from './commands/generate/entity';
7
+ export * from './lib/scaffolding';
8
+ export * from './lib/validation';
9
+ export * from './lib/generator';
10
+ export * from './templates';
11
+ export * from './utils/errors';
@@ -0,0 +1,3 @@
1
+ export function cli(): string {
2
+ return 'cli';
3
+ }
@@ -0,0 +1,181 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { CliError } from '../utils/errors';
4
+ import { generateEntityTemplate } from '../templates';
5
+
6
+ // FR-AI-005 Extensibility: Plugin registry for future AI integration
7
+ export interface GeneratorPlugin {
8
+ resolveTemplate?(entityName: string): Promise<string | null>;
9
+ postGenerate?(context: { name: string; targetPath: string }): Promise<void>;
10
+ }
11
+ const plugins: GeneratorPlugin[] = [];
12
+ export function registerGeneratorPlugin(plugin: GeneratorPlugin) {
13
+ plugins.push(plugin);
14
+ }
15
+
16
+ export async function generateEntity(
17
+ name: string,
18
+ options: { force?: boolean; json?: boolean }
19
+ ): Promise<void> {
20
+ if (!name || !name.trim()) {
21
+ throw new CliError({ code: 'ERR_INVALID_NAME', message: 'Entity name must not be empty.' });
22
+ }
23
+ if (/[/\\]/.test(name)) {
24
+ throw new CliError({
25
+ code: 'ERR_INVALID_NAME',
26
+ message: 'Entity name must not contain path separators.',
27
+ });
28
+ }
29
+
30
+ const targetDir = path.join(process.cwd(), 'schemas');
31
+ const targetPath = path.join(targetDir, name.toLowerCase() + '.json');
32
+ const userTemplatePath = path.join(process.cwd(), '.origo', 'templates', 'entity.json');
33
+ let content: string | null = null;
34
+
35
+ try {
36
+ await fs.access(targetPath);
37
+ if (!options.force) {
38
+ throw new CliError({
39
+ code: 'ERR_FILE_EXISTS',
40
+ message: `File already exists: ${targetPath}. Use --force to overwrite.`,
41
+ context: { path: targetPath },
42
+ });
43
+ }
44
+ } catch (error: unknown) {
45
+ if (error instanceof CliError) throw error;
46
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
47
+ }
48
+
49
+ // FR-AI-005 Extensibility: Check plugins first
50
+ for (const plugin of plugins) {
51
+ if (plugin.resolveTemplate) {
52
+ const resolved = await plugin.resolveTemplate(name);
53
+ if (resolved) {
54
+ content = resolved;
55
+ break;
56
+ }
57
+ }
58
+ }
59
+
60
+ if (!content) {
61
+ try {
62
+ const rawTemplate = await fs.readFile(userTemplatePath, 'utf-8');
63
+ content =
64
+ rawTemplate.replace(/\{\{name\}\}/g, name).replace(/\{\{id\}\}/g, name.toLowerCase()) +
65
+ '\n';
66
+ // Validate user template output
67
+ try {
68
+ JSON.parse(content);
69
+ } catch {
70
+ throw new CliError({
71
+ code: 'ERR_INVALID_TEMPLATE',
72
+ message: `The user template at ${userTemplatePath} produced invalid JSON when substituted.`,
73
+ });
74
+ }
75
+ } catch (err: unknown) {
76
+ if (err instanceof CliError) throw err;
77
+ const code = (err as NodeJS.ErrnoException).code;
78
+ if (code && code !== 'ENOENT') throw err;
79
+ content = generateEntityTemplate({ id: name.toLowerCase(), name });
80
+ }
81
+ }
82
+
83
+ try {
84
+ await fs.mkdir(targetDir, { recursive: true });
85
+ } catch (err: unknown) {
86
+ if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
87
+ }
88
+
89
+ if (content === null) {
90
+ throw new CliError({ code: 'ERR_UNEXPECTED', message: 'Content is unexpectedly null.' });
91
+ }
92
+
93
+ await fs.writeFile(targetPath, content, 'utf-8');
94
+
95
+ // FR-AI-005 Extensibility: Post-generate hooks
96
+ for (const plugin of plugins) {
97
+ if (plugin.postGenerate) {
98
+ await plugin.postGenerate({ name, targetPath });
99
+ }
100
+ }
101
+
102
+ if (options.json) {
103
+ console.log(
104
+ JSON.stringify(
105
+ {
106
+ status: 'success',
107
+ data: {
108
+ file: `./schemas/${name.toLowerCase()}.json`,
109
+ entityName: name,
110
+ },
111
+ },
112
+ null,
113
+ 2
114
+ )
115
+ );
116
+ } else {
117
+ console.log(`Successfully generated entity ${name} at ./schemas/${name.toLowerCase()}.json`);
118
+ }
119
+ }
120
+
121
+ export async function ejectTemplates(options: { force?: boolean; json?: boolean }): Promise<void> {
122
+ const destDir = path.join(process.cwd(), '.origo', 'templates');
123
+ const srcDir = path.resolve(__dirname, '../templates');
124
+
125
+ let files: string[];
126
+ try {
127
+ files = await fs.readdir(srcDir);
128
+ } catch {
129
+ throw new CliError({ code: 'ERR_NO_TEMPLATES', message: 'No template files found to eject.' });
130
+ }
131
+
132
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
133
+ if (jsonFiles.length === 0) {
134
+ throw new CliError({ code: 'ERR_NO_TEMPLATES', message: 'No template files found to eject.' });
135
+ }
136
+
137
+ if (!options.force) {
138
+ for (const file of jsonFiles) {
139
+ const destFile = path.join(destDir, file);
140
+ try {
141
+ await fs.access(destFile);
142
+ throw new CliError({
143
+ code: 'ERR_FILE_EXISTS',
144
+ message: `Template file already exists: ${destFile}. Use --force to overwrite.`,
145
+ context: { path: destFile },
146
+ });
147
+ } catch (err: unknown) {
148
+ if (err instanceof CliError) throw err;
149
+ if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
150
+ }
151
+ }
152
+ }
153
+
154
+ try {
155
+ await fs.mkdir(destDir, { recursive: true });
156
+ } catch (err: unknown) {
157
+ if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
158
+ }
159
+
160
+ await Promise.all(
161
+ jsonFiles.map(file => fs.copyFile(path.join(srcDir, file), path.join(destDir, file)))
162
+ );
163
+
164
+ if (options.json) {
165
+ console.log(
166
+ JSON.stringify(
167
+ {
168
+ status: 'success',
169
+ data: {
170
+ destination: './.origo/templates',
171
+ filesEjected: jsonFiles,
172
+ },
173
+ },
174
+ null,
175
+ 2
176
+ )
177
+ );
178
+ } else {
179
+ console.log(`Successfully ejected ${jsonFiles.length} templates to ./.origo/templates`);
180
+ }
181
+ }
@@ -0,0 +1,104 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { CliError } from '../utils/errors';
4
+ import { generateOrigoConfig } from '../templates';
5
+
6
+ export interface ScaffoldOptions {
7
+ json?: boolean;
8
+ cwd?: string;
9
+ }
10
+
11
+ export async function scaffoldProject(
12
+ projectName: string,
13
+ options: ScaffoldOptions = {}
14
+ ): Promise<void> {
15
+ // Guard against path traversal, empty input, or invalid directory names
16
+ if (
17
+ !projectName ||
18
+ path.basename(projectName) !== projectName ||
19
+ !/^[a-zA-Z0-9_.-]+$/.test(projectName)
20
+ ) {
21
+ throw new CliError({
22
+ code: 'ERR_INVALID_PROJECT_NAME',
23
+ message: `Invalid project name "${projectName}". Project name must be a single directory name without path traversal characters.`,
24
+ context: { projectName },
25
+ });
26
+ }
27
+
28
+ const baseDir = options.cwd ? path.resolve(options.cwd) : process.cwd();
29
+ const targetDir = path.resolve(baseDir, projectName);
30
+
31
+ // Check if directory already exists asynchronously
32
+ try {
33
+ await fs.access(targetDir);
34
+ throw new CliError({
35
+ code: 'EEXIST',
36
+ message: `Directory ${projectName} already exists.`,
37
+ context: { path: targetDir },
38
+ });
39
+ } catch (error) {
40
+ if (error instanceof CliError) {
41
+ throw error;
42
+ }
43
+ const isEnoent =
44
+ typeof error === 'object' &&
45
+ error !== null &&
46
+ 'code' in error &&
47
+ (error as { code?: string }).code === 'ENOENT';
48
+ if (!isEnoent) {
49
+ throw new CliError({
50
+ code: 'SCAFFOLD_ERROR',
51
+ message: `Failed to inspect target directory: ${error instanceof Error ? error.message : String(error)}`,
52
+ context: { targetDir },
53
+ cause: error,
54
+ });
55
+ }
56
+ }
57
+
58
+ try {
59
+ const schemasDir = path.join(targetDir, 'schemas');
60
+ const configFile = path.join(targetDir, 'origo.json');
61
+
62
+ await fs.mkdir(targetDir, { recursive: true });
63
+ await fs.mkdir(schemasDir, { recursive: true });
64
+
65
+ // Sourced directly from secure-by-default templates built in Story 5.5.5
66
+ const configContent = generateOrigoConfig({
67
+ version: '1.0',
68
+ build: { outDir: './dist' },
69
+ schemas: './schemas',
70
+ });
71
+
72
+ await fs.writeFile(configFile, configContent, 'utf-8');
73
+
74
+ if (options.json) {
75
+ console.log(
76
+ JSON.stringify(
77
+ {
78
+ status: 'success',
79
+ data: {
80
+ projectName,
81
+ targetDir,
82
+ configFile,
83
+ schemasDir,
84
+ },
85
+ },
86
+ null,
87
+ 2
88
+ )
89
+ );
90
+ } else {
91
+ console.log(`Successfully initialized Origo project in ${targetDir}`);
92
+ }
93
+ } catch (error) {
94
+ if (error instanceof CliError) {
95
+ throw error;
96
+ }
97
+ throw new CliError({
98
+ code: 'SCAFFOLD_ERROR',
99
+ message: `Failed to scaffold project: ${error instanceof Error ? error.message : String(error)}`,
100
+ context: { projectName, targetDir, error },
101
+ cause: error,
102
+ });
103
+ }
104
+ }
@@ -0,0 +1,218 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { BADLValidator, validateAST } from '@origo/core';
4
+ import { CliError } from '../utils/errors';
5
+
6
+ export interface ValidationOptions {
7
+ json?: boolean;
8
+ }
9
+
10
+ export async function validateDirectory(
11
+ directory = './schemas',
12
+ options: ValidationOptions = {}
13
+ ): Promise<number> {
14
+ if (!directory || directory.trim() === '') {
15
+ throw new CliError({
16
+ code: 'ERR_INVALID_DIRECTORY',
17
+ message: 'Directory argument must not be empty.',
18
+ });
19
+ }
20
+ const cwd = process.cwd();
21
+ const targetDir = path.resolve(cwd, directory);
22
+ const relativePath = path.relative(cwd, targetDir);
23
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
24
+ throw new CliError({
25
+ code: 'ERR_PATH_TRAVERSAL',
26
+ message: `Access denied: path "${directory}" resolves outside the working directory.`,
27
+ context: { directory, resolvedPath: targetDir, cwd },
28
+ });
29
+ }
30
+
31
+ let files: string[] = [];
32
+ let targetDirBase = targetDir;
33
+
34
+ try {
35
+ const stat = await fs.promises.stat(targetDir);
36
+ if (stat.isFile()) {
37
+ targetDirBase = path.dirname(targetDir);
38
+ files = [path.basename(targetDir)];
39
+ if (!files[0].endsWith('.json')) {
40
+ throw new CliError({
41
+ code: 'ERR_INVALID_FILE_TYPE',
42
+ message: 'Only .json files are supported for validation',
43
+ });
44
+ }
45
+ } else {
46
+ const dirents = await fs.promises.readdir(targetDir, {
47
+ recursive: true,
48
+ withFileTypes: true,
49
+ });
50
+ for (const d of dirents) {
51
+ if (d.isFile() && d.name.endsWith('.json')) {
52
+ const fullPath = path.join(d.parentPath || d.path, d.name);
53
+ files.push(path.relative(targetDirBase, fullPath));
54
+ }
55
+ }
56
+ }
57
+ } catch (error: unknown) {
58
+ if (error instanceof CliError) {
59
+ throw error;
60
+ }
61
+ const err = error as NodeJS.ErrnoException;
62
+ if (err.code === 'ENOENT') {
63
+ throw new CliError({
64
+ code: 'ERR_DIRECTORY_NOT_FOUND',
65
+ message: `Path not found: ${directory}`,
66
+ cause: error,
67
+ });
68
+ }
69
+ throw new CliError({
70
+ code: 'ERR_DIRECTORY_READ',
71
+ message: `Failed to read path: ${err.message}`,
72
+ cause: error,
73
+ });
74
+ }
75
+
76
+ if (!options.json) {
77
+ console.log(`Validating schemas in ${directory}...\n`);
78
+ }
79
+
80
+ if (files.length === 0) {
81
+ if (!options.json) {
82
+ console.log('No .json files found.');
83
+ }
84
+ return 0;
85
+ }
86
+
87
+ const results = [];
88
+ let totalErrors = 0;
89
+
90
+ let validator: BADLValidator;
91
+ try {
92
+ validator = new BADLValidator();
93
+ } catch (error: unknown) {
94
+ throw new CliError({
95
+ code: 'ERR_VALIDATOR_INIT',
96
+ message: `Failed to initialize BADLValidator: ${error instanceof Error ? error.message : String(error)}`,
97
+ cause: error,
98
+ });
99
+ }
100
+
101
+ for (const file of files) {
102
+ const filePath = path.join(targetDirBase, file);
103
+ let rawContent: string;
104
+ let combinedErrors: Array<{ code: string; message: string; line?: number; column?: number }> =
105
+ [];
106
+ let isValid = true;
107
+
108
+ try {
109
+ rawContent = await fs.promises.readFile(filePath, 'utf-8');
110
+ rawContent = rawContent.replace(/^\uFEFF/, '');
111
+
112
+ const parsed = JSON.parse(rawContent);
113
+ const schemaType = parsed.$schema || '';
114
+
115
+ let isSchemaValid = false;
116
+ const schemaErrorsList: Array<{
117
+ code?: string;
118
+ message?: string;
119
+ context?: { line?: number; column?: number };
120
+ }> = [];
121
+ let astErrorsList: Array<{
122
+ type?: string;
123
+ message?: string;
124
+ line?: number;
125
+ column?: number;
126
+ }> = [];
127
+
128
+ if (schemaType.includes('domain.schema.json')) {
129
+ isSchemaValid = validator.validateDomain(rawContent);
130
+ if (validator.errors) schemaErrorsList.push(...validator.errors);
131
+
132
+ if (isSchemaValid) {
133
+ astErrorsList = validateAST({ schemaVersion: '1.0', domains: [parsed] });
134
+ }
135
+ } else if (schemaType.includes('entity.schema.json')) {
136
+ isSchemaValid = validator.validateEntity(rawContent);
137
+ if (validator.errors) schemaErrorsList.push(...validator.errors);
138
+ } else {
139
+ schemaErrorsList.push({
140
+ code: 'UNSUPPORTED_SCHEMA',
141
+ message: `Schema type is unsupported or missing $schema. Cannot validate: ${schemaType}`,
142
+ });
143
+ }
144
+
145
+ combinedErrors = [
146
+ ...schemaErrorsList.map(e => ({
147
+ code: e.code || 'VALIDATION_ERROR',
148
+ message: e.message || 'Unknown error',
149
+ line: e.context?.line,
150
+ column: e.context?.column,
151
+ })),
152
+ ...astErrorsList.map(e => ({
153
+ code: e.type || 'VALIDATION_ERROR',
154
+ message: e.message || 'Unknown error',
155
+ line: e.line,
156
+ column: e.column,
157
+ })),
158
+ ];
159
+
160
+ isValid = combinedErrors.length === 0;
161
+ } catch (error: unknown) {
162
+ combinedErrors = [
163
+ {
164
+ code: 'ERR_JSON_PARSE',
165
+ message: `Failed to parse JSON or read file ${file}: ${error instanceof Error ? error.message : String(error)}`,
166
+ },
167
+ ];
168
+ isValid = false;
169
+ }
170
+
171
+ totalErrors += combinedErrors.length;
172
+
173
+ results.push({
174
+ file,
175
+ valid: isValid,
176
+ errors: combinedErrors,
177
+ });
178
+ }
179
+
180
+ if (options.json) {
181
+ const jsonOutput = {
182
+ status: totalErrors === 0 ? 'success' : 'error',
183
+ data: {
184
+ directory,
185
+ filesValidated: files.length,
186
+ errorsFound: totalErrors,
187
+ results,
188
+ },
189
+ };
190
+ console.log(JSON.stringify(jsonOutput, null, 2));
191
+ return totalErrors;
192
+ }
193
+
194
+ let validFilesCount = 0;
195
+ let invalidFilesCount = 0;
196
+
197
+ for (const result of results) {
198
+ if (result.valid) {
199
+ console.log(` \x1b[32m✓\x1b[0m ${result.file}`);
200
+ validFilesCount++;
201
+ } else {
202
+ console.log(` \x1b[31m✗\x1b[0m ${result.file}`);
203
+ invalidFilesCount++;
204
+ for (const error of result.errors) {
205
+ const line = error.line !== undefined ? error.line : '-';
206
+ const col = error.column !== undefined ? error.column : '-';
207
+ console.log(` ${line}:${col} [${error.code}] ${error.message}`);
208
+ }
209
+ }
210
+ }
211
+
212
+ const fileWord = (count: number) => (count === 1 ? 'file' : 'files');
213
+ console.log(
214
+ `\nFound ${totalErrors} errors in ${invalidFilesCount} ${fileWord(invalidFilesCount)} (${validFilesCount} ${fileWord(validFilesCount)} valid, ${invalidFilesCount} ${fileWord(invalidFilesCount)} invalid).`
215
+ );
216
+
217
+ return totalErrors;
218
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initCommand } from './commands/init';
4
+ import { newCommand } from './commands/new';
5
+ import { validateCommand } from './commands/validate';
6
+ import { generateCommand } from './commands/generate';
7
+ import { handleError } from './utils/errors';
8
+
9
+ export function createProgram(): Command {
10
+ const program = new Command();
11
+
12
+ program.name('origo').description('CLI for Origo Design').version('0.0.1');
13
+
14
+ program.addCommand(initCommand());
15
+ program.addCommand(newCommand());
16
+ program.addCommand(validateCommand());
17
+ program.addCommand(generateCommand());
18
+
19
+ return program;
20
+ }
21
+
22
+ export function main() {
23
+ const program = createProgram();
24
+
25
+ program.parseAsync(process.argv).catch(err => {
26
+ try {
27
+ handleError(err);
28
+ } catch (e) {
29
+ console.error('Fatal error in error handler:', e);
30
+ } finally {
31
+ process.exit(1);
32
+ }
33
+ });
34
+ }
35
+
36
+ if (require.main === module) {
37
+ main();
38
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "{{id}}",
3
+ "name": "{{name}}",
4
+ "implements": [],
5
+ "fields": []
6
+ }