Skip to content

Commit 9beb5a8

Browse files
committed
feat: implement ObjectStack compiler CLI for configuration validation and artifact generation
1 parent 209d2da commit 9beb5a8

File tree

8 files changed

+191
-93
lines changed

8 files changed

+191
-93
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"title": "Cli Protocol"
3+
}

examples/crm/objectstack.config.ts

Lines changed: 66 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { defineStack } from '@objectstack/spec';
12
import { App } from '@objectstack/spec/ui';
23
import { Account } from './src/domains/crm/account.object';
34
import { Contact } from './src/domains/crm/contact.object';
@@ -11,12 +12,14 @@ import { CrmActions } from './src/ui/actions';
1112
import { CrmDashboards } from './src/ui/dashboards';
1213
import { CrmReports } from './src/ui/reports';
1314

14-
export default App.create({
15-
name: 'crm_example',
16-
label: 'CRM App',
17-
description: 'Comprehensive CRM example demonstrating all ObjectStack Protocol features',
18-
version: '2.0.0',
19-
icon: 'briefcase',
15+
export default defineStack({
16+
manifest: {
17+
id: 'com.example.crm',
18+
version: '2.0.0',
19+
type: 'app',
20+
name: 'CRM App',
21+
description: 'Comprehensive CRM example demonstrating all ObjectStack Protocol features'
22+
},
2023

2124
// All objects in the app
2225
objects: [
@@ -33,50 +36,7 @@ export default App.create({
3336
PipelineStatsApi,
3437
LeadConvertApi
3538
],
36-
37-
// Navigation menu structure
38-
navigation: [
39-
{
40-
id: 'group_sales',
41-
type: 'group',
42-
label: 'Sales',
43-
children: [
44-
{ id: 'nav_lead', type: 'object', objectName: 'lead', label: 'Leads' },
45-
{ id: 'nav_account', type: 'object', objectName: 'account', label: 'Accounts' },
46-
{ id: 'nav_contact', type: 'object', objectName: 'contact', label: 'Contacts' },
47-
{ id: 'nav_opportunity', type: 'object', objectName: 'opportunity', label: 'Opportunities' },
48-
{ id: 'nav_sales_dashboard', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' },
49-
]
50-
},
51-
{
52-
id: 'group_service',
53-
type: 'group',
54-
label: 'Service',
55-
children: [
56-
{ id: 'nav_case', type: 'object', objectName: 'case', label: 'Cases' },
57-
{ id: 'nav_service_dashboard', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' },
58-
]
59-
},
60-
{
61-
id: 'group_activities',
62-
type: 'group',
63-
label: 'Activities',
64-
children: [
65-
{ id: 'nav_task', type: 'object', objectName: 'task', label: 'Tasks' },
66-
]
67-
},
68-
{
69-
id: 'group_analytics',
70-
type: 'group',
71-
label: 'Analytics',
72-
children: [
73-
{ id: 'nav_exec_dashboard', type: 'dashboard', dashboardName: 'executive_dashboard', label: 'Executive Dashboard' },
74-
{ id: 'nav_analytics_sales_db', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' },
75-
{ id: 'nav_analytics_service_db', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' },
76-
]
77-
}
78-
],
79-
39+
8040
// Actions available in the app
8141
actions: Object.values(CrmActions),
8242

@@ -86,10 +46,59 @@ export default App.create({
8646
// Reports
8747
reports: Object.values(CrmReports),
8848

89-
// App-level branding
90-
branding: {
91-
primaryColor: '#4169E1',
92-
logo: '/assets/crm-logo.png',
93-
favicon: '/assets/crm-favicon.ico',
94-
}
95-
});
49+
apps: [
50+
App.create({
51+
name: 'crm_example',
52+
label: 'CRM App',
53+
icon: 'briefcase',
54+
branding: {
55+
primaryColor: '#4169E1',
56+
logo: '/assets/crm-logo.png',
57+
favicon: '/assets/crm-favicon.ico',
58+
},
59+
60+
// Navigation menu structure
61+
navigation: [
62+
{
63+
id: 'group_sales',
64+
type: 'group',
65+
label: 'Sales',
66+
children: [
67+
{ id: 'nav_lead', type: 'object', objectName: 'lead', label: 'Leads' },
68+
{ id: 'nav_account', type: 'object', objectName: 'account', label: 'Accounts' },
69+
{ id: 'nav_contact', type: 'object', objectName: 'contact', label: 'Contacts' },
70+
{ id: 'nav_opportunity', type: 'object', objectName: 'opportunity', label: 'Opportunities' },
71+
{ id: 'nav_sales_dashboard', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' },
72+
]
73+
},
74+
{
75+
id: 'group_service',
76+
type: 'group',
77+
label: 'Service',
78+
children: [
79+
{ id: 'nav_case', type: 'object', objectName: 'case', label: 'Cases' },
80+
{ id: 'nav_service_dashboard', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' },
81+
]
82+
},
83+
{
84+
id: 'group_activities',
85+
type: 'group',
86+
label: 'Activities',
87+
children: [
88+
{ id: 'nav_task', type: 'object', objectName: 'task', label: 'Tasks' },
89+
]
90+
},
91+
{
92+
id: 'group_analytics',
93+
type: 'group',
94+
label: 'Analytics',
95+
children: [
96+
{ id: 'nav_exec_dashboard', type: 'dashboard', dashboardName: 'executive_dashboard', label: 'Executive Dashboard' },
97+
{ id: 'nav_analytics_sales_db', type: 'dashboard', dashboardName: 'sales_dashboard', label: 'Sales Dashboard' },
98+
{ id: 'nav_analytics_service_db', type: 'dashboard', dashboardName: 'service_dashboard', label: 'Service Dashboard' },
99+
]
100+
}
101+
]
102+
})
103+
]
104+
});

examples/crm/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Example CRM implementation using ObjectStack Protocol",
55
"private": true,
66
"scripts": {
7-
"build": "tsc",
7+
"build": "npx tsx ../../packages/spec/src/cli/compile.ts objectstack.config.ts dist/objectstack.json",
88
"test": "echo \"Error: no test specified\" && exit 1"
99
},
1010
"dependencies": {
Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,54 @@
1+
import { defineStack } from '@objectstack/spec';
12
import { App } from '@objectstack/spec/ui';
23
import { TodoTask } from './src/domains/todo/task.object';
34

4-
export default App.create({
5-
name: 'todo_app',
6-
label: 'Todo App',
7-
description: 'A simple Todo example demonstrating ObjectStack Protocol',
8-
version: '1.0.0',
9-
icon: 'check-square',
10-
branding: {
11-
primaryColor: '#10B981',
12-
logo: '/assets/todo-logo.png',
13-
},
5+
export default defineStack({
146
objects: [
157
TodoTask
168
],
17-
navigation: [
18-
{
19-
id: 'group_tasks',
20-
type: 'group',
21-
label: 'Tasks',
22-
children: [
23-
{
24-
id: 'nav_todo_task',
25-
type: 'object',
26-
objectName: 'todo_task',
27-
label: 'My Tasks'
9+
apps: [
10+
App.create({
11+
name: 'todo_app',
12+
label: 'Todo App',
13+
icon: 'check-square',
14+
branding: {
15+
primaryColor: '#10B981',
16+
logo: '/assets/todo-logo.png',
17+
},
18+
navigation: [
19+
{
20+
id: 'group_tasks',
21+
type: 'group',
22+
label: 'Tasks',
23+
children: [
24+
{
25+
id: 'nav_todo_task',
26+
type: 'object',
27+
objectName: 'todo_task',
28+
label: 'My Tasks'
29+
}
30+
]
2831
}
2932
]
30-
}
33+
})
3134
],
32-
data: [
33-
{
34-
object: 'todo_task',
35-
mode: 'upsert',
36-
records: [
37-
{ subject: 'Review PR #102', is_completed: true, priority: 3, due_date: new Date() },
38-
{ subject: 'Write Documentation', is_completed: false, priority: 2, due_date: new Date(Date.now() + 86400000) },
39-
{ subject: 'Fix specific Server bug', is_completed: false, priority: 1 }
40-
]
41-
}
42-
]
35+
manifest: {
36+
id: 'com.example.todo',
37+
version: '1.0.0',
38+
type: 'app',
39+
name: 'Todo App',
40+
description: 'A simple Todo example demonstrating ObjectStack Protocol',
41+
data: [
42+
{
43+
object: 'todo_task',
44+
mode: 'upsert',
45+
records: [
46+
{ subject: 'Review PR #102', is_completed: true, priority: 3, due_date: new Date() },
47+
{ subject: 'Write Documentation', is_completed: false, priority: 2, due_date: new Date(Date.now() + 86400000) },
48+
{ subject: 'Fix specific Server bug', is_completed: false, priority: 1 }
49+
]
50+
}
51+
]
52+
}
4353
});
54+

examples/todo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Example Todo App using ObjectStack Protocol",
55
"private": true,
66
"scripts": {
7-
"build": "tsc"
7+
"build": "npx tsx ../../packages/spec/src/cli/compile.ts objectstack.config.ts dist/objectstack.json"
88
},
99
"dependencies": {
1010
"@objectstack/spec": "workspace:*",

packages/spec/src/cli/compile.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
import { pathToFileURL } from 'url';
4+
import { ObjectStackDefinitionSchema } from '../stack.zod';
5+
6+
async function compile(configPath: string, outputPath: string) {
7+
const start = Date.now();
8+
console.log(`\n🔹 ObjectStack Compiler v0.1`);
9+
console.log(`------------------------------`);
10+
console.log(`📂 Source: ${configPath}`);
11+
12+
const absolutePath = path.resolve(process.cwd(), configPath);
13+
if (!fs.existsSync(absolutePath)) {
14+
throw new Error(`Config file not found: ${absolutePath}`);
15+
}
16+
17+
try {
18+
// 1. Load Configuration
19+
// We use dynamic import to load the TS/JS file.
20+
// This expects the environment (tsx/node) to handle TS compilation if needed.
21+
const imported = await import(pathToFileURL(absolutePath).href);
22+
const config = imported.default || imported;
23+
24+
if (!config) {
25+
throw new Error(`Default export not found in ${configPath}`);
26+
}
27+
28+
// 2. Validate against Protocol
29+
console.log(`🔍 Validating Protocol Compliance...`);
30+
const result = ObjectStackDefinitionSchema.safeParse(config);
31+
32+
if (!result.success) {
33+
console.error(`\n❌ Validation Failed!`);
34+
// Simpler error formatting
35+
const errors = result.error.errors.map(e => ` - [${e.path.join('.')}] ${e.message}`).join('\n');
36+
console.error(errors);
37+
process.exit(1);
38+
}
39+
40+
// 3. Generate Artifact
41+
const artifactPath = path.resolve(process.cwd(), outputPath);
42+
const artifactDir = path.dirname(artifactPath);
43+
44+
if (!fs.existsSync(artifactDir)) {
45+
fs.mkdirSync(artifactDir, { recursive: true });
46+
}
47+
48+
const jsonContent = JSON.stringify(result.data, null, 2);
49+
fs.writeFileSync(artifactPath, jsonContent);
50+
51+
const size = (jsonContent.length / 1024).toFixed(2);
52+
console.log(`✅ Build Success (${Date.now() - start}ms)`);
53+
console.log(`📦 Artifact: ${outputPath} (${size} KB)`);
54+
console.log(`✨ Ready for Deployment`);
55+
56+
} catch (error: any) {
57+
console.error(`\n❌ Compilation Error:`);
58+
console.error(error.message || error);
59+
process.exit(1);
60+
}
61+
}
62+
63+
// CLI Entrypoint
64+
const args = process.argv.slice(2);
65+
const configFile = args[0] || 'objectstack.config.ts';
66+
const outputFile = args[1] || 'dist/objectstack.json';
67+
68+
compile(configFile, outputFile);

packages/spec/src/stack.zod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ export type ObjectStackDefinition = z.infer<typeof ObjectStackDefinitionSchema>;
103103
export const ObjectStackSchema = ObjectStackDefinitionSchema;
104104
export type ObjectStack = ObjectStackDefinition;
105105

106+
/**
107+
* Type-safe helper to define a project configuration.
108+
*/
109+
export const defineStack = (config: ObjectStackDefinition) => config;
110+
106111

107112
/**
108113
* 2. RUNTIME CAPABILITIES PROTOCOL (Dynamic)

packages/spec/src/ui/dashboard.zod.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ export const ChartType = z.enum([
99
'metric', // KPI / Big Number
1010
'bar', // Bar / Column
1111
'line', // Line / Area
12-
'pie', // Pie / Donut
12+
'pie', // Pie
13+
'donut', // Donut
14+
'gauge', // Gauge / Speedometer
1315
'funnel', // Conversion Funnel
1416
'radar', // Spider / Radar
1517
'scatter', // Scatter Plot

0 commit comments

Comments
 (0)