@objectstack/cli 1.0.11 → 1.0.12

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.
@@ -0,0 +1,261 @@
1
+ import chalk from 'chalk';
2
+ import type { ZodError } from 'zod';
3
+
4
+ // ─── Constants ──────────────────────────────────────────────────────
5
+ export const CLI_NAME = 'objectstack';
6
+ export const CLI_ALIAS = 'os';
7
+
8
+ // ─── Banner ─────────────────────────────────────────────────────────
9
+
10
+ export function printBanner(version: string) {
11
+ console.log('');
12
+ console.log(chalk.bold.cyan(' ╔═══════════════════════════════════╗'));
13
+ console.log(chalk.bold.cyan(' ║') + chalk.bold(' ◆ ObjectStack CLI ') + chalk.dim(`v${version}`) + chalk.bold.cyan(' ║'));
14
+ console.log(chalk.bold.cyan(' ╚═══════════════════════════════════╝'));
15
+ console.log('');
16
+ }
17
+
18
+ // ─── Section Header ─────────────────────────────────────────────────
19
+
20
+ export function printHeader(title: string) {
21
+ console.log(chalk.bold(`\n◆ ${title}`));
22
+ console.log(chalk.dim('─'.repeat(40)));
23
+ }
24
+
25
+ // ─── Key-Value Line ─────────────────────────────────────────────────
26
+
27
+ export function printKV(key: string, value: string | number, icon?: string) {
28
+ const prefix = icon ? `${icon} ` : ' ';
29
+ console.log(`${prefix}${chalk.dim(key + ':')} ${chalk.white(String(value))}`);
30
+ }
31
+
32
+ // ─── Status Line ────────────────────────────────────────────────────
33
+
34
+ export function printSuccess(msg: string) {
35
+ console.log(chalk.green(` ✓ ${msg}`));
36
+ }
37
+
38
+ export function printWarning(msg: string) {
39
+ console.log(chalk.yellow(` ⚠ ${msg}`));
40
+ }
41
+
42
+ export function printError(msg: string) {
43
+ console.log(chalk.red(` ✗ ${msg}`));
44
+ }
45
+
46
+ export function printInfo(msg: string) {
47
+ console.log(chalk.blue(` ℹ ${msg}`));
48
+ }
49
+
50
+ export function printStep(msg: string) {
51
+ console.log(chalk.yellow(` → ${msg}`));
52
+ }
53
+
54
+ // ─── Timer ──────────────────────────────────────────────────────────
55
+
56
+ export function createTimer() {
57
+ const start = Date.now();
58
+ return {
59
+ elapsed: () => Date.now() - start,
60
+ display: () => `${Date.now() - start}ms`,
61
+ };
62
+ }
63
+
64
+ // ─── Zod Error Formatting ───────────────────────────────────────────
65
+
66
+ export function formatZodErrors(error: ZodError) {
67
+ const issues = error.issues || (error as any).errors || [];
68
+
69
+ if (issues.length === 0) {
70
+ console.log(chalk.red(' Unknown validation error'));
71
+ return;
72
+ }
73
+
74
+ // Group by top-level path
75
+ const grouped = new Map<string, typeof issues>();
76
+ for (const issue of issues) {
77
+ const topPath = (issue as any).path?.[0] || '_root';
78
+ if (!grouped.has(String(topPath))) {
79
+ grouped.set(String(topPath), []);
80
+ }
81
+ grouped.get(String(topPath))!.push(issue);
82
+ }
83
+
84
+ for (const [section, sectionIssues] of grouped) {
85
+ console.log(chalk.bold.red(`\n ${section}:`));
86
+ for (const issue of sectionIssues) {
87
+ const path = (issue as any).path?.join('.') || '';
88
+ const code = (issue as any).code || '';
89
+ const msg = (issue as any).message || '';
90
+
91
+ console.log(chalk.red(` ✗ ${path}`));
92
+ console.log(chalk.dim(` ${code}: ${msg}`));
93
+
94
+ // Show expected/received for type errors
95
+ if ((issue as any).expected) {
96
+ console.log(chalk.dim(` expected: ${chalk.green((issue as any).expected)}`));
97
+ }
98
+ if ((issue as any).received) {
99
+ console.log(chalk.dim(` received: ${chalk.red((issue as any).received)}`));
100
+ }
101
+ }
102
+ }
103
+
104
+ console.log('');
105
+ console.log(chalk.dim(` ${issues.length} validation error(s) total`));
106
+ }
107
+
108
+ // ─── Metadata Statistics ────────────────────────────────────────────
109
+
110
+ export interface MetadataStats {
111
+ objects: number;
112
+ objectExtensions: number;
113
+ fields: number;
114
+ views: number;
115
+ pages: number;
116
+ apps: number;
117
+ dashboards: number;
118
+ reports: number;
119
+ actions: number;
120
+ flows: number;
121
+ workflows: number;
122
+ approvals: number;
123
+ agents: number;
124
+ apis: number;
125
+ roles: number;
126
+ permissions: number;
127
+ themes: number;
128
+ datasources: number;
129
+ translations: number;
130
+ plugins: number;
131
+ devPlugins: number;
132
+ }
133
+
134
+ export function collectMetadataStats(config: any): MetadataStats {
135
+ const count = (arr: any) => (Array.isArray(arr) ? arr.length : 0);
136
+
137
+ // Count total fields across all objects
138
+ let fields = 0;
139
+ if (Array.isArray(config.objects)) {
140
+ for (const obj of config.objects) {
141
+ if (obj.fields && typeof obj.fields === 'object') {
142
+ fields += Object.keys(obj.fields).length;
143
+ }
144
+ }
145
+ }
146
+
147
+ return {
148
+ objects: count(config.objects),
149
+ objectExtensions: count(config.objectExtensions),
150
+ fields,
151
+ views: count(config.views),
152
+ pages: count(config.pages),
153
+ apps: count(config.apps),
154
+ dashboards: count(config.dashboards),
155
+ reports: count(config.reports),
156
+ actions: count(config.actions),
157
+ flows: count(config.flows),
158
+ workflows: count(config.workflows),
159
+ approvals: count(config.approvals),
160
+ agents: count(config.agents),
161
+ apis: count(config.apis),
162
+ roles: count(config.roles),
163
+ permissions: count(config.permissions),
164
+ themes: count(config.themes),
165
+ datasources: count(config.datasources),
166
+ translations: count(config.translations),
167
+ plugins: count(config.plugins),
168
+ devPlugins: count(config.devPlugins),
169
+ };
170
+ }
171
+
172
+ // ─── Server Ready Banner ────────────────────────────────────────────
173
+
174
+ export interface ServerReadyOptions {
175
+ port: number;
176
+ configFile: string;
177
+ isDev: boolean;
178
+ pluginCount: number;
179
+ pluginNames?: string[];
180
+ uiEnabled?: boolean;
181
+ studioPath?: string;
182
+ }
183
+
184
+ export function printServerReady(opts: ServerReadyOptions) {
185
+ const base = `http://localhost:${opts.port}`;
186
+ console.log('');
187
+ console.log(chalk.bold.green(' ✓ Server is ready'));
188
+ console.log('');
189
+ console.log(chalk.cyan(' ➜') + chalk.bold(' API: ') + chalk.cyan(base + '/'));
190
+ if (opts.uiEnabled && opts.studioPath) {
191
+ console.log(chalk.cyan(' ➜') + chalk.bold(' Console: ') + chalk.cyan(base + opts.studioPath + '/'));
192
+ }
193
+ console.log('');
194
+ console.log(chalk.dim(` Config: ${opts.configFile}`));
195
+ console.log(chalk.dim(` Mode: ${opts.isDev ? 'development' : 'production'}`));
196
+ console.log(chalk.dim(` Plugins: ${opts.pluginCount} loaded`));
197
+ if (opts.pluginNames && opts.pluginNames.length > 0) {
198
+ console.log(chalk.dim(` ${opts.pluginNames.join(', ')}`));
199
+ }
200
+ console.log('');
201
+ console.log(chalk.dim(' Press Ctrl+C to stop'));
202
+ console.log('');
203
+ }
204
+
205
+ export function printMetadataStats(stats: MetadataStats) {
206
+ const sections: Array<{ label: string; items: Array<[string, number]> }> = [
207
+ {
208
+ label: 'Data',
209
+ items: [
210
+ ['Objects', stats.objects],
211
+ ['Fields', stats.fields],
212
+ ['Extensions', stats.objectExtensions],
213
+ ['Datasources', stats.datasources],
214
+ ],
215
+ },
216
+ {
217
+ label: 'UI',
218
+ items: [
219
+ ['Apps', stats.apps],
220
+ ['Views', stats.views],
221
+ ['Pages', stats.pages],
222
+ ['Dashboards', stats.dashboards],
223
+ ['Reports', stats.reports],
224
+ ['Actions', stats.actions],
225
+ ['Themes', stats.themes],
226
+ ],
227
+ },
228
+ {
229
+ label: 'Logic',
230
+ items: [
231
+ ['Flows', stats.flows],
232
+ ['Workflows', stats.workflows],
233
+ ['Approvals', stats.approvals],
234
+ ['Agents', stats.agents],
235
+ ['APIs', stats.apis],
236
+ ],
237
+ },
238
+ {
239
+ label: 'Security',
240
+ items: [
241
+ ['Roles', stats.roles],
242
+ ['Permissions', stats.permissions],
243
+ ],
244
+ },
245
+ ];
246
+
247
+ for (const section of sections) {
248
+ const nonZero = section.items.filter(([, v]) => v > 0);
249
+ if (nonZero.length === 0) continue;
250
+
251
+ const line = nonZero.map(([k, v]) => `${chalk.white(v)} ${chalk.dim(k)}`).join(' ');
252
+ console.log(` ${chalk.bold(section.label + ':')} ${line}`);
253
+ }
254
+
255
+ if (stats.plugins > 0 || stats.devPlugins > 0) {
256
+ const parts: string[] = [];
257
+ if (stats.plugins > 0) parts.push(`${stats.plugins} plugins`);
258
+ if (stats.devPlugins > 0) parts.push(`${stats.devPlugins} devPlugins`);
259
+ console.log(` ${chalk.bold('Runtime:')} ${chalk.dim(parts.join(', '))}`);
260
+ }
261
+ }
@@ -5,6 +5,10 @@ import { devCommand } from '../src/commands/dev';
5
5
  import { doctorCommand } from '../src/commands/doctor';
6
6
  import { createCommand } from '../src/commands/create';
7
7
  import { testCommand } from '../src/commands/test';
8
+ import { validateCommand } from '../src/commands/validate';
9
+ import { initCommand } from '../src/commands/init';
10
+ import { infoCommand } from '../src/commands/info';
11
+ import { generateCommand } from '../src/commands/generate';
8
12
 
9
13
  describe('CLI Commands', () => {
10
14
  it('should have compile command', () => {
@@ -33,7 +37,28 @@ describe('CLI Commands', () => {
33
37
  });
34
38
 
35
39
  it('should have test command', () => {
36
- expect(testCommand.name()).toBe('test:run');
40
+ expect(testCommand.name()).toBe('test');
37
41
  expect(testCommand.description()).toContain('Quality Protocol');
38
42
  });
43
+
44
+ it('should have validate command', () => {
45
+ expect(validateCommand.name()).toBe('validate');
46
+ expect(validateCommand.description()).toContain('Validate');
47
+ });
48
+
49
+ it('should have init command', () => {
50
+ expect(initCommand.name()).toBe('init');
51
+ expect(initCommand.description()).toContain('Initialize');
52
+ });
53
+
54
+ it('should have info command', () => {
55
+ expect(infoCommand.name()).toBe('info');
56
+ expect(infoCommand.description()).toContain('summary');
57
+ });
58
+
59
+ it('should have generate command with alias', () => {
60
+ expect(generateCommand.name()).toBe('generate');
61
+ expect(generateCommand.alias()).toBe('g');
62
+ expect(generateCommand.description()).toContain('Generate');
63
+ });
39
64
  });
package/tsup.config.ts CHANGED
@@ -1,12 +1,21 @@
1
1
  import { defineConfig } from 'tsup';
2
2
 
3
- export default defineConfig({
4
- entry: ['src/index.ts', 'src/bin.ts'],
5
- format: ['esm'],
6
- dts: true,
7
- clean: true,
8
- shims: true,
9
- banner: {
10
- js: '#!/usr/bin/env node',
3
+ export default defineConfig([
4
+ // CLI binary entry needs shebang
5
+ {
6
+ entry: ['src/bin.ts'],
7
+ format: ['esm'],
8
+ clean: true,
9
+ shims: true,
10
+ banner: {
11
+ js: '#!/usr/bin/env node',
12
+ },
11
13
  },
12
- });
14
+ // Library entry — no shebang, with types
15
+ {
16
+ entry: ['src/index.ts'],
17
+ format: ['esm'],
18
+ dts: true,
19
+ shims: true,
20
+ },
21
+ ]);
package/dist/bin.d.ts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { }
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/commands/compile.ts
4
- import { Command } from "commander";
5
- import path from "path";
6
- import fs from "fs";
7
- import chalk from "chalk";
8
- import { bundleRequire } from "bundle-require";
9
- import { ObjectStackDefinitionSchema } from "@objectstack/spec";
10
- var compileCommand = new Command("compile").description("Compile ObjectStack configuration to JSON definition").argument("[source]", "Source configuration file", "objectstack.config.ts").argument("[output]", "Output JSON file", "dist/objectstack.json").action(async (source, output) => {
11
- const start = Date.now();
12
- console.log(chalk.bold(`
13
- \u{1F539} ObjectStack Compiler v0.1`));
14
- console.log(chalk.dim(`------------------------------`));
15
- console.log(`\u{1F4C2} Source: ${chalk.blue(source)}`);
16
- const absolutePath = path.resolve(process.cwd(), source);
17
- if (!fs.existsSync(absolutePath)) {
18
- console.error(chalk.red(`
19
- \u274C Config file not found: ${absolutePath}`));
20
- process.exit(1);
21
- }
22
- try {
23
- console.log(chalk.yellow(`\u{1F4E6} Bundling Configuration...`));
24
- const { mod } = await bundleRequire({
25
- filepath: absolutePath
26
- });
27
- const config = mod.default || mod;
28
- if (!config) {
29
- throw new Error(`Default export not found in ${source}`);
30
- }
31
- console.log(chalk.yellow(`\u{1F50D} Validating Protocol Compliance...`));
32
- const result = ObjectStackDefinitionSchema.safeParse(config);
33
- if (!result.success) {
34
- console.error(chalk.red(`
35
- \u274C Validation Failed!`));
36
- const error = result.error;
37
- error.issues.forEach((e) => {
38
- console.error(chalk.red(` - [${e.path.join(".")}] ${e.message}`));
39
- });
40
- process.exit(1);
41
- }
42
- const artifactPath = path.resolve(process.cwd(), output);
43
- const artifactDir = path.dirname(artifactPath);
44
- if (!fs.existsSync(artifactDir)) {
45
- fs.mkdirSync(artifactDir, { recursive: true });
46
- }
47
- const jsonContent = JSON.stringify(result.data, null, 2);
48
- fs.writeFileSync(artifactPath, jsonContent);
49
- const size = (jsonContent.length / 1024).toFixed(2);
50
- console.log(chalk.green(`
51
- \u2705 Build Success (${Date.now() - start}ms)`));
52
- console.log(`\u{1F4E6} Artifact: ${chalk.blue(output)} (${size} KB)`);
53
- console.log(chalk.magenta(`\u2728 Ready for Deployment`));
54
- } catch (error) {
55
- console.error(chalk.red(`
56
- \u274C Compilation Error:`));
57
- console.error(error.message || error);
58
- process.exit(1);
59
- }
60
- });
61
-
62
- export {
63
- compileCommand
64
- };