Skip to content

Commit a3dfaac

Browse files
Copilothotlong
andcommitted
feat: add comprehensive development tooling and workflow improvements
- Add development helper script (scripts/dev.sh) with commands for setup, dev, build, test, clean, doctor, create, link - Add enhanced CLI commands: dev, doctor, create (plugin/example templates) - Add template generator script (scripts/generate.sh) for schemas and tests - Add comprehensive DEVELOPMENT.md guide (bilingual English/Chinese) - Add convenience npm scripts (doctor, setup, test) - Update README.md with development guide reference - Include script documentation in scripts/README.md Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent a62b6e9 commit a3dfaac

File tree

10 files changed

+1629
-3
lines changed

10 files changed

+1629
-3
lines changed

DEVELOPMENT.md

Lines changed: 668 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,25 @@ pnpm install
5858
# 2. Build the Protocol (Generates Schemas & Docs)
5959
pnpm --filter @objectstack/spec build
6060

61-
# 3. Start Documentation Site
61+
# 3. Check environment health
62+
pnpm doctor
63+
# or
64+
./scripts/dev.sh doctor
65+
66+
# 4. Start Documentation Site
6267
pnpm docs:dev
6368
# Visit http://localhost:3000/docs
6469
```
6570

71+
### For Plugin/Package Development
72+
73+
See **[DEVELOPMENT.md](./DEVELOPMENT.md)** for comprehensive development guide including:
74+
- Development workflow and tooling
75+
- CLI commands reference
76+
- Debugging configurations
77+
- Testing strategies
78+
- Common tasks and troubleshooting
79+
6680
## 📦 Monorepo Structure
6781

6882
### Core Packages

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"scripts": {
77
"build": "pnpm --filter ./packages/spec build && pnpm -r --stream --filter '!./packages/spec' build",
88
"dev": "pnpm --filter @objectstack/client-react build && pnpm -r --filter @objectstack/example-msw-react-crud dev",
9+
"test": "pnpm --filter @objectstack/spec test",
910
"clean": "pnpm -r --parallel clean && rm -rf dist",
11+
"doctor": "pnpm --filter @objectstack/cli build && node packages/cli/bin/objectstack.js doctor",
12+
"setup": "pnpm install && pnpm --filter @objectstack/spec build",
1013
"version": "changeset version",
1114
"release": "pnpm run build && changeset publish",
1215
"docs:dev": "pnpm --filter @objectstack/docs dev",

packages/cli/src/bin.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { Command } from 'commander';
22
import { compileCommand } from './commands/compile.js';
3+
import { devCommand } from './commands/dev.js';
4+
import { doctorCommand } from './commands/doctor.js';
5+
import { createCommand } from './commands/create.js';
36

47
const program = new Command();
58

69
program
710
.name('objectstack')
8-
.description('CLI for ObjectStack Protocol')
9-
.version('0.1.0');
11+
.description('CLI for ObjectStack Protocol - Development Tools for Microkernel Architecture')
12+
.version('0.7.1');
1013

14+
// Add all commands
1115
program.addCommand(compileCommand);
16+
program.addCommand(devCommand);
17+
program.addCommand(doctorCommand);
18+
program.addCommand(createCommand);
1219

1320
program.parse(process.argv);
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import { Command } from 'commander';
2+
import chalk from 'chalk';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import { execSync } from 'child_process';
6+
7+
const templates = {
8+
plugin: {
9+
description: 'Create a new ObjectStack plugin',
10+
files: {
11+
'package.json': (name: string) => ({
12+
name: `@objectstack/plugin-${name}`,
13+
version: '0.1.0',
14+
description: `ObjectStack Plugin: ${name}`,
15+
main: 'dist/index.js',
16+
types: 'dist/index.d.ts',
17+
scripts: {
18+
build: 'tsc',
19+
dev: 'tsc --watch',
20+
test: 'vitest',
21+
},
22+
keywords: ['objectstack', 'plugin', name],
23+
author: '',
24+
license: 'MIT',
25+
dependencies: {
26+
'@objectstack/spec': 'workspace:*',
27+
zod: '^3.22.4',
28+
},
29+
devDependencies: {
30+
'@types/node': '^20.10.0',
31+
typescript: '^5.3.0',
32+
vitest: '^2.1.8',
33+
},
34+
}),
35+
'tsconfig.json': () => ({
36+
extends: '../../tsconfig.json',
37+
compilerOptions: {
38+
outDir: 'dist',
39+
rootDir: 'src',
40+
},
41+
include: ['src/**/*'],
42+
}),
43+
'src/index.ts': (name: string) => `import type { Plugin } from '@objectstack/spec';
44+
45+
/**
46+
* ${name} Plugin for ObjectStack
47+
*/
48+
export const ${toCamelCase(name)}Plugin: Plugin = {
49+
name: '${name}',
50+
version: '0.1.0',
51+
52+
async initialize(context) {
53+
console.log('Initializing ${name} plugin...');
54+
// Plugin initialization logic
55+
},
56+
57+
async destroy() {
58+
console.log('Destroying ${name} plugin...');
59+
// Plugin cleanup logic
60+
},
61+
};
62+
63+
export default ${toCamelCase(name)}Plugin;
64+
`,
65+
'README.md': (name: string) => `# @objectstack/plugin-${name}
66+
67+
ObjectStack Plugin: ${name}
68+
69+
## Installation
70+
71+
\`\`\`bash
72+
pnpm add @objectstack/plugin-${name}
73+
\`\`\`
74+
75+
## Usage
76+
77+
\`\`\`typescript
78+
import { ${toCamelCase(name)}Plugin } from '@objectstack/plugin-${name}';
79+
80+
// Use the plugin in your ObjectStack configuration
81+
export default {
82+
plugins: [
83+
${toCamelCase(name)}Plugin,
84+
],
85+
};
86+
\`\`\`
87+
88+
## License
89+
90+
MIT
91+
`,
92+
},
93+
},
94+
95+
example: {
96+
description: 'Create a new ObjectStack example application',
97+
files: {
98+
'package.json': (name: string) => ({
99+
name: `@objectstack/example-${name}`,
100+
version: '0.1.0',
101+
private: true,
102+
description: `ObjectStack Example: ${name}`,
103+
scripts: {
104+
build: 'objectstack compile',
105+
dev: 'tsx watch objectstack.config.ts',
106+
test: 'vitest',
107+
},
108+
dependencies: {
109+
'@objectstack/spec': 'workspace:*',
110+
'@objectstack/cli': 'workspace:*',
111+
zod: '^3.22.4',
112+
},
113+
devDependencies: {
114+
'@types/node': '^20.10.0',
115+
tsx: '^4.21.0',
116+
typescript: '^5.3.0',
117+
vitest: '^2.1.8',
118+
},
119+
}),
120+
'objectstack.config.ts': (name: string) => `import { defineStack } from '@objectstack/spec';
121+
122+
export default defineStack({
123+
metadata: {
124+
name: '${name}',
125+
version: '0.1.0',
126+
description: '${name} example application',
127+
},
128+
129+
objects: {
130+
// Define your data objects here
131+
},
132+
133+
ui: {
134+
apps: [],
135+
views: [],
136+
},
137+
});
138+
`,
139+
'README.md': (name: string) => `# ${name} Example
140+
141+
ObjectStack example application: ${name}
142+
143+
## Quick Start
144+
145+
\`\`\`bash
146+
# Build the configuration
147+
pnpm build
148+
149+
# Run in development mode
150+
pnpm dev
151+
\`\`\`
152+
153+
## Structure
154+
155+
- \`objectstack.config.ts\` - Main configuration file
156+
- \`dist/objectstack.json\` - Compiled artifact
157+
158+
## Learn More
159+
160+
- [ObjectStack Documentation](../../content/docs)
161+
- [Examples](../)
162+
`,
163+
'tsconfig.json': () => ({
164+
extends: '../../tsconfig.json',
165+
compilerOptions: {
166+
outDir: 'dist',
167+
rootDir: '.',
168+
},
169+
include: ['*.ts', 'src/**/*'],
170+
}),
171+
},
172+
},
173+
};
174+
175+
function toCamelCase(str: string): string {
176+
return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
177+
}
178+
179+
export const createCommand = new Command('create')
180+
.description('Create a new package, plugin, or example from template')
181+
.argument('<type>', 'Type of project to create (plugin, example)')
182+
.argument('[name]', 'Name of the project')
183+
.option('-d, --dir <directory>', 'Target directory')
184+
.action(async (type: string, name?: string, options?: { dir?: string }) => {
185+
console.log(chalk.bold(`\n📦 ObjectStack Project Creator`));
186+
console.log(chalk.dim(`-------------------------------`));
187+
188+
if (!templates[type as keyof typeof templates]) {
189+
console.error(chalk.red(`\n❌ Unknown type: ${type}`));
190+
console.log(chalk.dim('Available types: plugin, example'));
191+
process.exit(1);
192+
}
193+
194+
if (!name) {
195+
console.error(chalk.red('\n❌ Project name is required'));
196+
console.log(chalk.dim(`Usage: objectstack create ${type} <name>`));
197+
process.exit(1);
198+
}
199+
200+
const template = templates[type as keyof typeof templates];
201+
const cwd = process.cwd();
202+
203+
// Determine target directory
204+
let targetDir: string;
205+
if (options?.dir) {
206+
targetDir = path.resolve(cwd, options.dir);
207+
} else {
208+
const baseDir = type === 'plugin' ? 'packages/plugins' : 'examples';
209+
const projectName = type === 'plugin' ? `plugin-${name}` : name;
210+
targetDir = path.join(cwd, baseDir, projectName);
211+
}
212+
213+
// Check if directory already exists
214+
if (fs.existsSync(targetDir)) {
215+
console.error(chalk.red(`\n❌ Directory already exists: ${targetDir}`));
216+
process.exit(1);
217+
}
218+
219+
console.log(`📁 Creating ${type}: ${chalk.blue(name)}`);
220+
console.log(`📂 Location: ${chalk.dim(targetDir)}`);
221+
console.log('');
222+
223+
try {
224+
// Create directory
225+
fs.mkdirSync(targetDir, { recursive: true });
226+
227+
// Create files from template
228+
for (const [filePath, contentFn] of Object.entries(template.files)) {
229+
const fullPath = path.join(targetDir, filePath);
230+
const dir = path.dirname(fullPath);
231+
232+
if (!fs.existsSync(dir)) {
233+
fs.mkdirSync(dir, { recursive: true });
234+
}
235+
236+
const content = contentFn(name);
237+
const fileContent = typeof content === 'string'
238+
? content
239+
: JSON.stringify(content, null, 2);
240+
241+
fs.writeFileSync(fullPath, fileContent);
242+
console.log(chalk.green(`✓ Created ${filePath}`));
243+
}
244+
245+
console.log('');
246+
console.log(chalk.green('✅ Project created successfully!'));
247+
console.log('');
248+
console.log(chalk.bold('Next steps:'));
249+
console.log(chalk.dim(` cd ${path.relative(cwd, targetDir)}`));
250+
console.log(chalk.dim(' pnpm install'));
251+
console.log(chalk.dim(' pnpm build'));
252+
console.log('');
253+
254+
} catch (error: any) {
255+
console.error(chalk.red('\n❌ Failed to create project:'));
256+
console.error(error.message || error);
257+
258+
// Clean up on error
259+
if (fs.existsSync(targetDir)) {
260+
fs.rmSync(targetDir, { recursive: true });
261+
}
262+
263+
process.exit(1);
264+
}
265+
});

packages/cli/src/commands/dev.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Command } from 'commander';
2+
import chalk from 'chalk';
3+
import { execSync } from 'child_process';
4+
import os from 'os';
5+
import fs from 'fs';
6+
import path from 'path';
7+
8+
export const devCommand = new Command('dev')
9+
.description('Start development mode for a package')
10+
.argument('[package]', 'Package name (without @objectstack/ prefix)', 'all')
11+
.option('-w, --watch', 'Enable watch mode (default)', true)
12+
.option('-v, --verbose', 'Verbose output')
13+
.action(async (packageName, options) => {
14+
console.log(chalk.bold(`\n🚀 ObjectStack Development Mode`));
15+
console.log(chalk.dim(`-------------------------------`));
16+
17+
try {
18+
const cwd = process.cwd();
19+
const filter = packageName === 'all' ? '' : `--filter @objectstack/${packageName}`;
20+
21+
console.log(`📦 Package: ${chalk.blue(packageName === 'all' ? 'All packages' : `@objectstack/${packageName}`)}`);
22+
console.log(`🔄 Watch mode: ${chalk.green('enabled')}`);
23+
console.log('');
24+
25+
// Start dev mode
26+
const command = `pnpm ${filter} dev`.trim();
27+
console.log(chalk.dim(`$ ${command}`));
28+
console.log('');
29+
30+
execSync(command, {
31+
stdio: 'inherit',
32+
cwd
33+
});
34+
35+
} catch (error: any) {
36+
console.error(chalk.red(`\n❌ Development mode failed:`));
37+
console.error(error.message || error);
38+
process.exit(1);
39+
}
40+
});

0 commit comments

Comments
 (0)