create-objectstack 3.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/.turbo/turbo-build.log +13 -0
- package/LICENSE +202 -0
- package/README.md +100 -0
- package/bin/create-objectstack.js +2 -0
- package/dist/index.js +653 -0
- package/package.json +30 -0
- package/src/index.ts +720 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +10 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
|
|
9
|
+
// ─── Template Registry ──────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
type TemplateFiles = Record<string, (name: string) => string>;
|
|
12
|
+
|
|
13
|
+
interface Template {
|
|
14
|
+
description: string;
|
|
15
|
+
files: TemplateFiles;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const TEMPLATES: Record<string, Template> = {
|
|
19
|
+
'minimal-api': {
|
|
20
|
+
description: 'Server + memory driver + 1 object + REST API',
|
|
21
|
+
files: {
|
|
22
|
+
'objectstack.config.ts': (name) => `import { defineStack } from '@objectstack/spec';
|
|
23
|
+
import * as objects from './src/objects';
|
|
24
|
+
|
|
25
|
+
export default defineStack({
|
|
26
|
+
manifest: {
|
|
27
|
+
id: 'com.example.${name}',
|
|
28
|
+
namespace: '${name}',
|
|
29
|
+
version: '0.1.0',
|
|
30
|
+
type: 'app',
|
|
31
|
+
name: '${toTitleCase(name)}',
|
|
32
|
+
description: '${toTitleCase(name)} — built with ObjectStack',
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
objects: Object.values(objects),
|
|
36
|
+
|
|
37
|
+
api: {
|
|
38
|
+
rest: { enabled: true, basePath: '/api' },
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
`,
|
|
42
|
+
'package.json': (name) => JSON.stringify({
|
|
43
|
+
name,
|
|
44
|
+
version: '0.1.0',
|
|
45
|
+
private: true,
|
|
46
|
+
type: 'module',
|
|
47
|
+
scripts: {
|
|
48
|
+
dev: 'objectstack dev',
|
|
49
|
+
start: 'objectstack serve',
|
|
50
|
+
build: 'objectstack compile',
|
|
51
|
+
validate: 'objectstack validate',
|
|
52
|
+
typecheck: 'tsc --noEmit',
|
|
53
|
+
},
|
|
54
|
+
dependencies: {
|
|
55
|
+
'@objectstack/spec': '^3.0.0',
|
|
56
|
+
'@objectstack/runtime': '^3.0.0',
|
|
57
|
+
'@objectstack/driver-memory': '^3.0.0',
|
|
58
|
+
'@objectstack/plugin-hono-server': '^3.0.0',
|
|
59
|
+
},
|
|
60
|
+
devDependencies: {
|
|
61
|
+
'@objectstack/cli': '^3.0.0',
|
|
62
|
+
'typescript': '^5.3.0',
|
|
63
|
+
},
|
|
64
|
+
}, null, 2) + '\n',
|
|
65
|
+
'tsconfig.json': () => JSON.stringify({
|
|
66
|
+
compilerOptions: {
|
|
67
|
+
target: 'ES2022',
|
|
68
|
+
module: 'ESNext',
|
|
69
|
+
moduleResolution: 'bundler',
|
|
70
|
+
strict: true,
|
|
71
|
+
esModuleInterop: true,
|
|
72
|
+
skipLibCheck: true,
|
|
73
|
+
outDir: 'dist',
|
|
74
|
+
rootDir: '.',
|
|
75
|
+
declaration: true,
|
|
76
|
+
},
|
|
77
|
+
include: ['*.ts', 'src/**/*'],
|
|
78
|
+
exclude: ['dist', 'node_modules'],
|
|
79
|
+
}, null, 2) + '\n',
|
|
80
|
+
'src/objects/task.ts': () => `import { Data } from '@objectstack/spec';
|
|
81
|
+
|
|
82
|
+
const task: Data.Object = {
|
|
83
|
+
name: 'task',
|
|
84
|
+
label: 'Task',
|
|
85
|
+
ownership: 'own',
|
|
86
|
+
fields: {
|
|
87
|
+
title: {
|
|
88
|
+
type: 'text',
|
|
89
|
+
label: 'Title',
|
|
90
|
+
required: true,
|
|
91
|
+
},
|
|
92
|
+
description: {
|
|
93
|
+
type: 'textarea',
|
|
94
|
+
label: 'Description',
|
|
95
|
+
},
|
|
96
|
+
status: {
|
|
97
|
+
type: 'select',
|
|
98
|
+
label: 'Status',
|
|
99
|
+
options: [
|
|
100
|
+
{ label: 'Open', value: 'open' },
|
|
101
|
+
{ label: 'In Progress', value: 'in_progress' },
|
|
102
|
+
{ label: 'Done', value: 'done' },
|
|
103
|
+
],
|
|
104
|
+
defaultValue: 'open',
|
|
105
|
+
},
|
|
106
|
+
due_date: {
|
|
107
|
+
type: 'date',
|
|
108
|
+
label: 'Due Date',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default task;
|
|
114
|
+
`,
|
|
115
|
+
'src/objects/index.ts': () => `export { default as task } from './task';
|
|
116
|
+
`,
|
|
117
|
+
'.gitignore': () => `node_modules/
|
|
118
|
+
dist/
|
|
119
|
+
*.tsbuildinfo
|
|
120
|
+
`,
|
|
121
|
+
'README.md': (name) => `# ${toTitleCase(name)}
|
|
122
|
+
|
|
123
|
+
Built with [ObjectStack](https://objectstack.com).
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
\`\`\`bash
|
|
128
|
+
# Install dependencies
|
|
129
|
+
npm install
|
|
130
|
+
|
|
131
|
+
# Start development server
|
|
132
|
+
npm run dev
|
|
133
|
+
|
|
134
|
+
# Validate configuration
|
|
135
|
+
npm run validate
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
## Project Structure
|
|
139
|
+
|
|
140
|
+
- \`objectstack.config.ts\` — Stack definition (objects, API, settings)
|
|
141
|
+
- \`src/objects/\` — Object definitions
|
|
142
|
+
- \`dist/\` — Compiled output
|
|
143
|
+
|
|
144
|
+
## Learn More
|
|
145
|
+
|
|
146
|
+
- [ObjectStack Documentation](https://objectstack.com/docs)
|
|
147
|
+
`,
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
'full-stack': {
|
|
152
|
+
description: 'Server + UI + auth + 3 CRM objects',
|
|
153
|
+
files: {
|
|
154
|
+
'objectstack.config.ts': (name) => `import { defineStack } from '@objectstack/spec';
|
|
155
|
+
import * as objects from './src/objects';
|
|
156
|
+
import * as apps from './src/apps';
|
|
157
|
+
|
|
158
|
+
export default defineStack({
|
|
159
|
+
manifest: {
|
|
160
|
+
id: 'com.example.${name}',
|
|
161
|
+
namespace: '${name}',
|
|
162
|
+
version: '0.1.0',
|
|
163
|
+
type: 'app',
|
|
164
|
+
name: '${toTitleCase(name)}',
|
|
165
|
+
description: '${toTitleCase(name)} CRM — built with ObjectStack',
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
objects: Object.values(objects),
|
|
169
|
+
apps: Object.values(apps),
|
|
170
|
+
|
|
171
|
+
api: {
|
|
172
|
+
rest: { enabled: true, basePath: '/api' },
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
`,
|
|
176
|
+
'package.json': (name) => JSON.stringify({
|
|
177
|
+
name,
|
|
178
|
+
version: '0.1.0',
|
|
179
|
+
private: true,
|
|
180
|
+
type: 'module',
|
|
181
|
+
scripts: {
|
|
182
|
+
dev: 'objectstack dev',
|
|
183
|
+
start: 'objectstack serve',
|
|
184
|
+
build: 'objectstack compile',
|
|
185
|
+
validate: 'objectstack validate',
|
|
186
|
+
typecheck: 'tsc --noEmit',
|
|
187
|
+
},
|
|
188
|
+
dependencies: {
|
|
189
|
+
'@objectstack/spec': '^3.0.0',
|
|
190
|
+
'@objectstack/runtime': '^3.0.0',
|
|
191
|
+
'@objectstack/driver-memory': '^3.0.0',
|
|
192
|
+
'@objectstack/plugin-hono-server': '^3.0.0',
|
|
193
|
+
'@objectstack/plugin-auth': '^3.0.0',
|
|
194
|
+
},
|
|
195
|
+
devDependencies: {
|
|
196
|
+
'@objectstack/cli': '^3.0.0',
|
|
197
|
+
'typescript': '^5.3.0',
|
|
198
|
+
},
|
|
199
|
+
}, null, 2) + '\n',
|
|
200
|
+
'tsconfig.json': () => JSON.stringify({
|
|
201
|
+
compilerOptions: {
|
|
202
|
+
target: 'ES2022',
|
|
203
|
+
module: 'ESNext',
|
|
204
|
+
moduleResolution: 'bundler',
|
|
205
|
+
strict: true,
|
|
206
|
+
esModuleInterop: true,
|
|
207
|
+
skipLibCheck: true,
|
|
208
|
+
outDir: 'dist',
|
|
209
|
+
rootDir: '.',
|
|
210
|
+
declaration: true,
|
|
211
|
+
},
|
|
212
|
+
include: ['*.ts', 'src/**/*'],
|
|
213
|
+
exclude: ['dist', 'node_modules'],
|
|
214
|
+
}, null, 2) + '\n',
|
|
215
|
+
'src/objects/contact.ts': () => `import { Data } from '@objectstack/spec';
|
|
216
|
+
|
|
217
|
+
const contact: Data.Object = {
|
|
218
|
+
name: 'contact',
|
|
219
|
+
label: 'Contact',
|
|
220
|
+
ownership: 'own',
|
|
221
|
+
fields: {
|
|
222
|
+
first_name: {
|
|
223
|
+
type: 'text',
|
|
224
|
+
label: 'First Name',
|
|
225
|
+
required: true,
|
|
226
|
+
},
|
|
227
|
+
last_name: {
|
|
228
|
+
type: 'text',
|
|
229
|
+
label: 'Last Name',
|
|
230
|
+
required: true,
|
|
231
|
+
},
|
|
232
|
+
email: {
|
|
233
|
+
type: 'text',
|
|
234
|
+
label: 'Email',
|
|
235
|
+
},
|
|
236
|
+
phone: {
|
|
237
|
+
type: 'text',
|
|
238
|
+
label: 'Phone',
|
|
239
|
+
},
|
|
240
|
+
company: {
|
|
241
|
+
type: 'lookup',
|
|
242
|
+
label: 'Company',
|
|
243
|
+
reference: 'company',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export default contact;
|
|
249
|
+
`,
|
|
250
|
+
'src/objects/company.ts': () => `import { Data } from '@objectstack/spec';
|
|
251
|
+
|
|
252
|
+
const company: Data.Object = {
|
|
253
|
+
name: 'company',
|
|
254
|
+
label: 'Company',
|
|
255
|
+
ownership: 'own',
|
|
256
|
+
fields: {
|
|
257
|
+
name: {
|
|
258
|
+
type: 'text',
|
|
259
|
+
label: 'Company Name',
|
|
260
|
+
required: true,
|
|
261
|
+
},
|
|
262
|
+
website: {
|
|
263
|
+
type: 'text',
|
|
264
|
+
label: 'Website',
|
|
265
|
+
},
|
|
266
|
+
industry: {
|
|
267
|
+
type: 'select',
|
|
268
|
+
label: 'Industry',
|
|
269
|
+
options: [
|
|
270
|
+
{ label: 'Technology', value: 'technology' },
|
|
271
|
+
{ label: 'Finance', value: 'finance' },
|
|
272
|
+
{ label: 'Healthcare', value: 'healthcare' },
|
|
273
|
+
{ label: 'Other', value: 'other' },
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export default company;
|
|
280
|
+
`,
|
|
281
|
+
'src/objects/deal.ts': () => `import { Data } from '@objectstack/spec';
|
|
282
|
+
|
|
283
|
+
const deal: Data.Object = {
|
|
284
|
+
name: 'deal',
|
|
285
|
+
label: 'Deal',
|
|
286
|
+
ownership: 'own',
|
|
287
|
+
fields: {
|
|
288
|
+
name: {
|
|
289
|
+
type: 'text',
|
|
290
|
+
label: 'Deal Name',
|
|
291
|
+
required: true,
|
|
292
|
+
},
|
|
293
|
+
amount: {
|
|
294
|
+
type: 'number',
|
|
295
|
+
label: 'Amount',
|
|
296
|
+
},
|
|
297
|
+
stage: {
|
|
298
|
+
type: 'select',
|
|
299
|
+
label: 'Stage',
|
|
300
|
+
options: [
|
|
301
|
+
{ label: 'Prospecting', value: 'prospecting' },
|
|
302
|
+
{ label: 'Qualification', value: 'qualification' },
|
|
303
|
+
{ label: 'Proposal', value: 'proposal' },
|
|
304
|
+
{ label: 'Closed Won', value: 'closed_won' },
|
|
305
|
+
{ label: 'Closed Lost', value: 'closed_lost' },
|
|
306
|
+
],
|
|
307
|
+
defaultValue: 'prospecting',
|
|
308
|
+
},
|
|
309
|
+
contact: {
|
|
310
|
+
type: 'lookup',
|
|
311
|
+
label: 'Contact',
|
|
312
|
+
reference: 'contact',
|
|
313
|
+
},
|
|
314
|
+
company: {
|
|
315
|
+
type: 'lookup',
|
|
316
|
+
label: 'Company',
|
|
317
|
+
reference: 'company',
|
|
318
|
+
},
|
|
319
|
+
close_date: {
|
|
320
|
+
type: 'date',
|
|
321
|
+
label: 'Close Date',
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export default deal;
|
|
327
|
+
`,
|
|
328
|
+
'src/objects/index.ts': () => `export { default as contact } from './contact';
|
|
329
|
+
export { default as company } from './company';
|
|
330
|
+
export { default as deal } from './deal';
|
|
331
|
+
`,
|
|
332
|
+
'src/views/contact_list.ts': () => `import { UI } from '@objectstack/spec';
|
|
333
|
+
|
|
334
|
+
const contactList: UI.View = {
|
|
335
|
+
name: 'contact_list',
|
|
336
|
+
label: 'All Contacts',
|
|
337
|
+
object: 'contact',
|
|
338
|
+
type: 'list',
|
|
339
|
+
columns: ['first_name', 'last_name', 'email', 'phone', 'company'],
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
export default contactList;
|
|
343
|
+
`,
|
|
344
|
+
'src/views/company_list.ts': () => `import { UI } from '@objectstack/spec';
|
|
345
|
+
|
|
346
|
+
const companyList: UI.View = {
|
|
347
|
+
name: 'company_list',
|
|
348
|
+
label: 'All Companies',
|
|
349
|
+
object: 'company',
|
|
350
|
+
type: 'list',
|
|
351
|
+
columns: ['name', 'website', 'industry'],
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export default companyList;
|
|
355
|
+
`,
|
|
356
|
+
'src/views/deal_list.ts': () => `import { UI } from '@objectstack/spec';
|
|
357
|
+
|
|
358
|
+
const dealList: UI.View = {
|
|
359
|
+
name: 'deal_list',
|
|
360
|
+
label: 'All Deals',
|
|
361
|
+
object: 'deal',
|
|
362
|
+
type: 'list',
|
|
363
|
+
columns: ['name', 'amount', 'stage', 'contact', 'close_date'],
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export default dealList;
|
|
367
|
+
`,
|
|
368
|
+
'src/apps/crm.ts': () => `import { UI } from '@objectstack/spec';
|
|
369
|
+
|
|
370
|
+
const crm: UI.App = {
|
|
371
|
+
name: 'crm',
|
|
372
|
+
label: 'CRM',
|
|
373
|
+
description: 'Customer Relationship Management',
|
|
374
|
+
navigation: [
|
|
375
|
+
{ type: 'object', object: 'contact', label: 'Contacts' },
|
|
376
|
+
{ type: 'object', object: 'company', label: 'Companies' },
|
|
377
|
+
{ type: 'object', object: 'deal', label: 'Deals' },
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export default crm;
|
|
382
|
+
`,
|
|
383
|
+
'src/apps/index.ts': () => `export { default as crm } from './crm';
|
|
384
|
+
`,
|
|
385
|
+
'.gitignore': () => `node_modules/
|
|
386
|
+
dist/
|
|
387
|
+
*.tsbuildinfo
|
|
388
|
+
`,
|
|
389
|
+
'README.md': (name) => `# ${toTitleCase(name)}
|
|
390
|
+
|
|
391
|
+
A full-stack CRM application built with [ObjectStack](https://objectstack.com).
|
|
392
|
+
|
|
393
|
+
## Quick Start
|
|
394
|
+
|
|
395
|
+
\`\`\`bash
|
|
396
|
+
npm install
|
|
397
|
+
npm run dev
|
|
398
|
+
\`\`\`
|
|
399
|
+
|
|
400
|
+
## Project Structure
|
|
401
|
+
|
|
402
|
+
- \`objectstack.config.ts\` — Stack definition
|
|
403
|
+
- \`src/objects/\` — Data objects (Contact, Company, Deal)
|
|
404
|
+
- \`src/views/\` — List views
|
|
405
|
+
- \`src/apps/crm.ts\` — CRM app with navigation
|
|
406
|
+
|
|
407
|
+
## Learn More
|
|
408
|
+
|
|
409
|
+
- [ObjectStack Documentation](https://objectstack.com/docs)
|
|
410
|
+
`,
|
|
411
|
+
},
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
plugin: {
|
|
415
|
+
description: 'Plugin skeleton with test setup',
|
|
416
|
+
files: {
|
|
417
|
+
'objectstack.config.ts': (name) => `import { defineStack } from '@objectstack/spec';
|
|
418
|
+
import * as objects from './src/objects';
|
|
419
|
+
|
|
420
|
+
export default defineStack({
|
|
421
|
+
manifest: {
|
|
422
|
+
id: 'com.objectstack.plugin-${name}',
|
|
423
|
+
namespace: 'plugin_${name}',
|
|
424
|
+
version: '0.1.0',
|
|
425
|
+
type: 'plugin',
|
|
426
|
+
name: '${toTitleCase(name)} Plugin',
|
|
427
|
+
description: 'ObjectStack Plugin: ${toTitleCase(name)}',
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
objects: Object.values(objects),
|
|
431
|
+
});
|
|
432
|
+
`,
|
|
433
|
+
'package.json': (name) => JSON.stringify({
|
|
434
|
+
name: `@objectstack/plugin-${name}`,
|
|
435
|
+
version: '0.1.0',
|
|
436
|
+
description: `ObjectStack Plugin: ${toTitleCase(name)}`,
|
|
437
|
+
main: 'dist/index.js',
|
|
438
|
+
types: 'dist/index.d.ts',
|
|
439
|
+
type: 'module',
|
|
440
|
+
scripts: {
|
|
441
|
+
build: 'tsc',
|
|
442
|
+
dev: 'tsc --watch',
|
|
443
|
+
test: 'vitest run',
|
|
444
|
+
validate: 'objectstack validate',
|
|
445
|
+
typecheck: 'tsc --noEmit',
|
|
446
|
+
},
|
|
447
|
+
keywords: ['objectstack', 'plugin', name],
|
|
448
|
+
author: '',
|
|
449
|
+
license: 'MIT',
|
|
450
|
+
dependencies: {
|
|
451
|
+
'@objectstack/spec': '^3.0.0',
|
|
452
|
+
},
|
|
453
|
+
devDependencies: {
|
|
454
|
+
'@types/node': '^22.0.0',
|
|
455
|
+
'typescript': '^5.3.0',
|
|
456
|
+
'vitest': '^4.0.0',
|
|
457
|
+
},
|
|
458
|
+
}, null, 2) + '\n',
|
|
459
|
+
'tsconfig.json': () => JSON.stringify({
|
|
460
|
+
compilerOptions: {
|
|
461
|
+
target: 'ES2022',
|
|
462
|
+
module: 'ESNext',
|
|
463
|
+
moduleResolution: 'bundler',
|
|
464
|
+
strict: true,
|
|
465
|
+
esModuleInterop: true,
|
|
466
|
+
skipLibCheck: true,
|
|
467
|
+
outDir: 'dist',
|
|
468
|
+
rootDir: '.',
|
|
469
|
+
declaration: true,
|
|
470
|
+
},
|
|
471
|
+
include: ['*.ts', 'src/**/*'],
|
|
472
|
+
exclude: ['dist', 'node_modules'],
|
|
473
|
+
}, null, 2) + '\n',
|
|
474
|
+
'src/index.ts': (name) => `/**
|
|
475
|
+
* ${toTitleCase(name)} Plugin for ObjectStack
|
|
476
|
+
*
|
|
477
|
+
* Entry point — re-exports all plugin metadata.
|
|
478
|
+
*/
|
|
479
|
+
export * as objects from './objects';
|
|
480
|
+
`,
|
|
481
|
+
'src/objects/sample.ts': (name) => `import { Data } from '@objectstack/spec';
|
|
482
|
+
|
|
483
|
+
const sample: Data.Object = {
|
|
484
|
+
name: '${name}_sample',
|
|
485
|
+
label: '${toTitleCase(name)} Sample',
|
|
486
|
+
ownership: 'own',
|
|
487
|
+
fields: {
|
|
488
|
+
name: {
|
|
489
|
+
type: 'text',
|
|
490
|
+
label: 'Name',
|
|
491
|
+
required: true,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export default sample;
|
|
497
|
+
`,
|
|
498
|
+
'src/objects/index.ts': () => `export { default as sample } from './sample';
|
|
499
|
+
`,
|
|
500
|
+
'test/sample.test.ts': (name) => `import { describe, it, expect } from 'vitest';
|
|
501
|
+
import sample from '../src/objects/sample';
|
|
502
|
+
|
|
503
|
+
describe('${name} plugin', () => {
|
|
504
|
+
it('should export a valid sample object', () => {
|
|
505
|
+
expect(sample).toBeDefined();
|
|
506
|
+
expect(sample.name).toBe('${name}_sample');
|
|
507
|
+
expect(sample.fields).toHaveProperty('name');
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
`,
|
|
511
|
+
'.gitignore': () => `node_modules/
|
|
512
|
+
dist/
|
|
513
|
+
*.tsbuildinfo
|
|
514
|
+
`,
|
|
515
|
+
'README.md': (name) => `# @objectstack/plugin-${name}
|
|
516
|
+
|
|
517
|
+
ObjectStack Plugin: ${toTitleCase(name)}
|
|
518
|
+
|
|
519
|
+
## Installation
|
|
520
|
+
|
|
521
|
+
\`\`\`bash
|
|
522
|
+
npm install @objectstack/plugin-${name}
|
|
523
|
+
\`\`\`
|
|
524
|
+
|
|
525
|
+
## Usage
|
|
526
|
+
|
|
527
|
+
\`\`\`typescript
|
|
528
|
+
import { defineStack } from '@objectstack/spec';
|
|
529
|
+
|
|
530
|
+
export default defineStack({
|
|
531
|
+
plugins: [
|
|
532
|
+
'@objectstack/plugin-${name}',
|
|
533
|
+
],
|
|
534
|
+
});
|
|
535
|
+
\`\`\`
|
|
536
|
+
|
|
537
|
+
## Development
|
|
538
|
+
|
|
539
|
+
\`\`\`bash
|
|
540
|
+
# Run tests
|
|
541
|
+
npm test
|
|
542
|
+
|
|
543
|
+
# Build
|
|
544
|
+
npm run build
|
|
545
|
+
|
|
546
|
+
# Validate metadata
|
|
547
|
+
npm run validate
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
## License
|
|
551
|
+
|
|
552
|
+
MIT
|
|
553
|
+
`,
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
559
|
+
|
|
560
|
+
function toCamelCase(str: string): string {
|
|
561
|
+
return str.replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function toTitleCase(str: string): string {
|
|
565
|
+
return str.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ─── Formatting (matches @objectstack/cli style) ────────────────────
|
|
569
|
+
|
|
570
|
+
function printHeader(title: string) {
|
|
571
|
+
console.log(chalk.bold(`\n◆ ${title}`));
|
|
572
|
+
console.log(chalk.dim('─'.repeat(40)));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function printKV(key: string, value: string) {
|
|
576
|
+
console.log(` ${chalk.dim(key + ':')} ${chalk.white(value)}`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function printSuccess(msg: string) {
|
|
580
|
+
console.log(chalk.green(` ✓ ${msg}`));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function printError(msg: string) {
|
|
584
|
+
console.log(chalk.red(` ✗ ${msg}`));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function printStep(msg: string) {
|
|
588
|
+
console.log(chalk.yellow(` → ${msg}`));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function printWarning(msg: string) {
|
|
592
|
+
console.log(chalk.yellow(` ⚠ ${msg}`));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ─── CLI Program ────────────────────────────────────────────────────
|
|
596
|
+
|
|
597
|
+
const program = new Command()
|
|
598
|
+
.name('create-objectstack')
|
|
599
|
+
.description('Create a new ObjectStack project')
|
|
600
|
+
.version('3.0.0')
|
|
601
|
+
.argument('[name]', 'Project name (defaults to current directory name)')
|
|
602
|
+
.option(
|
|
603
|
+
'-t, --template <template>',
|
|
604
|
+
'Project template: minimal-api, full-stack, plugin',
|
|
605
|
+
'minimal-api',
|
|
606
|
+
)
|
|
607
|
+
.option('--skip-install', 'Skip dependency installation')
|
|
608
|
+
.action(async (name: string | undefined, options: { template: string; skipInstall?: boolean }) => {
|
|
609
|
+
// Banner
|
|
610
|
+
console.log('');
|
|
611
|
+
console.log(chalk.bold.cyan(' ╔═══════════════════════════════════╗'));
|
|
612
|
+
console.log(chalk.bold.cyan(' ║') + chalk.bold(' ◆ Create ObjectStack ') + chalk.dim('v3.0') + chalk.bold.cyan(' ║'));
|
|
613
|
+
console.log(chalk.bold.cyan(' ╚═══════════════════════════════════╝'));
|
|
614
|
+
|
|
615
|
+
printHeader('New Project');
|
|
616
|
+
|
|
617
|
+
// Resolve template
|
|
618
|
+
const template = TEMPLATES[options.template];
|
|
619
|
+
if (!template) {
|
|
620
|
+
printError(`Unknown template: ${options.template}`);
|
|
621
|
+
console.log(chalk.dim(` Available: ${Object.keys(TEMPLATES).join(', ')}`));
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Resolve project name and directory
|
|
626
|
+
const cwd = process.cwd();
|
|
627
|
+
const projectName = name || path.basename(cwd);
|
|
628
|
+
const targetDir = name ? path.resolve(cwd, name) : cwd;
|
|
629
|
+
const isCurrentDir = targetDir === cwd;
|
|
630
|
+
|
|
631
|
+
printKV('Project', projectName);
|
|
632
|
+
printKV('Template', `${options.template} — ${template.description}`);
|
|
633
|
+
printKV('Directory', targetDir);
|
|
634
|
+
console.log('');
|
|
635
|
+
|
|
636
|
+
// Guard: if creating in a sub-directory, check it doesn't already exist
|
|
637
|
+
if (!isCurrentDir && fs.existsSync(targetDir)) {
|
|
638
|
+
const existing = fs.readdirSync(targetDir);
|
|
639
|
+
if (existing.length > 0) {
|
|
640
|
+
printError(`Directory already exists and is not empty: ${targetDir}`);
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const createdFiles: string[] = [];
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
// Ensure target directory exists
|
|
649
|
+
if (!fs.existsSync(targetDir)) {
|
|
650
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Write every file defined by the template
|
|
654
|
+
for (const [filePath, contentFn] of Object.entries(template.files)) {
|
|
655
|
+
const fullPath = path.join(targetDir, filePath);
|
|
656
|
+
const dir = path.dirname(fullPath);
|
|
657
|
+
|
|
658
|
+
if (!fs.existsSync(dir)) {
|
|
659
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
fs.writeFileSync(fullPath, contentFn(projectName));
|
|
663
|
+
createdFiles.push(filePath);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Summary
|
|
667
|
+
console.log(chalk.bold(' Created files:'));
|
|
668
|
+
for (const f of createdFiles) {
|
|
669
|
+
console.log(chalk.green(` + ${f}`));
|
|
670
|
+
}
|
|
671
|
+
console.log('');
|
|
672
|
+
|
|
673
|
+
// Install dependencies
|
|
674
|
+
if (!options.skipInstall) {
|
|
675
|
+
printStep('Installing dependencies...');
|
|
676
|
+
try {
|
|
677
|
+
// Detect package manager — prefer pnpm, fall back to npm
|
|
678
|
+
const pm = detectPackageManager();
|
|
679
|
+
execSync(`${pm} install`, { stdio: 'inherit', cwd: targetDir });
|
|
680
|
+
console.log('');
|
|
681
|
+
} catch {
|
|
682
|
+
printWarning('Dependency installation failed. Run `npm install` manually.');
|
|
683
|
+
console.log('');
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
printSuccess('Project created!');
|
|
688
|
+
console.log('');
|
|
689
|
+
|
|
690
|
+
// Next steps
|
|
691
|
+
console.log(chalk.bold(' Next steps:'));
|
|
692
|
+
if (!isCurrentDir) {
|
|
693
|
+
console.log(chalk.dim(` cd ${name}`));
|
|
694
|
+
}
|
|
695
|
+
if (options.skipInstall) {
|
|
696
|
+
console.log(chalk.dim(' npm install'));
|
|
697
|
+
}
|
|
698
|
+
console.log(chalk.dim(' npm run dev # Start development server'));
|
|
699
|
+
console.log(chalk.dim(' npm run validate # Check configuration'));
|
|
700
|
+
console.log('');
|
|
701
|
+
|
|
702
|
+
} catch (error: any) {
|
|
703
|
+
printError(error.message || String(error));
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Detect available package manager (pnpm > npm).
|
|
710
|
+
*/
|
|
711
|
+
function detectPackageManager(): string {
|
|
712
|
+
try {
|
|
713
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
714
|
+
return 'pnpm';
|
|
715
|
+
} catch {
|
|
716
|
+
return 'npm';
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
program.parse();
|