create-objectstack 6.1.1 → 6.3.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/dist/index.js CHANGED
@@ -3,554 +3,46 @@ import { Command } from "commander";
3
3
  import chalk from "chalk";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
+ import os from "os";
6
7
  import { execSync } from "child_process";
7
8
  import { fileURLToPath } from "url";
9
+ import { pipeline } from "stream/promises";
10
+ import { createGunzip } from "zlib";
11
+ import { createWriteStream, createReadStream } from "fs";
12
+ import { mkdtemp, rm } from "fs/promises";
13
+ import * as tar from "tar";
8
14
  var __filename = fileURLToPath(import.meta.url);
9
15
  var __dirname = path.dirname(__filename);
10
- var TEMPLATES_DIR = path.resolve(__dirname, "templates");
16
+ var BUNDLED_TEMPLATES_DIR = path.resolve(__dirname, "templates");
17
+ var REMOTE_REPO = "objectstack-ai/templates";
18
+ var REMOTE_BRANCH = "main";
19
+ var REMOTE_TARBALL_URL = `https://codeload.github.com/${REMOTE_REPO}/tar.gz/refs/heads/${REMOTE_BRANCH}`;
11
20
  var TEMPLATES = {
12
- "minimal-api": {
13
- description: "Server + memory driver + 1 object + REST API",
14
- files: {
15
- "objectstack.config.ts": (name) => `import { defineStack } from '@objectstack/spec';
16
- import * as objects from './src/objects';
17
-
18
- export default defineStack({
19
- manifest: {
20
- id: 'com.example.${name}',
21
- namespace: '${name}',
22
- version: '0.1.0',
23
- type: 'app',
24
- name: '${toTitleCase(name)}',
25
- description: '${toTitleCase(name)} \u2014 built with ObjectStack',
21
+ blank: {
22
+ description: "Minimal starter \u2014 one object, REST API, ready to extend",
23
+ source: { kind: "bundled", dir: "blank" }
26
24
  },
27
-
28
- objects: Object.values(objects),
29
-
30
- api: {
31
- rest: { enabled: true, basePath: '/api' },
25
+ todo: {
26
+ description: "Universal task & project management starter",
27
+ source: { kind: "remote", pkg: "todo" }
32
28
  },
33
- });
34
- `,
35
- "package.json": (name) => JSON.stringify({
36
- name,
37
- version: "0.1.0",
38
- private: true,
39
- type: "module",
40
- scripts: {
41
- dev: "objectstack dev",
42
- start: "objectstack serve",
43
- build: "objectstack compile",
44
- validate: "objectstack validate",
45
- typecheck: "tsc --noEmit"
46
- },
47
- dependencies: {
48
- "@objectstack/spec": "^3.0.0",
49
- "@objectstack/runtime": "^3.0.0",
50
- "@objectstack/driver-memory": "^3.0.0",
51
- "@objectstack/plugin-hono-server": "^3.0.0"
52
- },
53
- devDependencies: {
54
- "@objectstack/cli": "^3.0.0",
55
- "typescript": "^5.3.0"
56
- }
57
- }, null, 2) + "\n",
58
- "tsconfig.json": () => JSON.stringify({
59
- compilerOptions: {
60
- target: "ES2022",
61
- module: "ESNext",
62
- moduleResolution: "bundler",
63
- strict: true,
64
- esModuleInterop: true,
65
- skipLibCheck: true,
66
- outDir: "dist",
67
- rootDir: ".",
68
- declaration: true
69
- },
70
- include: ["*.ts", "src/**/*"],
71
- exclude: ["dist", "node_modules"]
72
- }, null, 2) + "\n",
73
- "src/objects/task.ts": () => `import * as Data from '@objectstack/spec/data';
74
-
75
- const task: Data.Object = {
76
- name: 'task',
77
- label: 'Task',
78
- ownership: 'own',
79
- fields: {
80
- title: {
81
- type: 'text',
82
- label: 'Title',
83
- required: true,
84
- },
85
- description: {
86
- type: 'textarea',
87
- label: 'Description',
88
- },
89
- status: {
90
- type: 'select',
91
- label: 'Status',
92
- options: [
93
- { label: 'Open', value: 'open' },
94
- { label: 'In Progress', value: 'in_progress' },
95
- { label: 'Done', value: 'done' },
96
- ],
97
- defaultValue: 'open',
98
- },
99
- due_date: {
100
- type: 'date',
101
- label: 'Due Date',
102
- },
29
+ compliance: {
30
+ description: "Compliance posture & evidence management (SOC2 / ISO27001)",
31
+ source: { kind: "remote", pkg: "compliance" }
103
32
  },
104
- };
105
-
106
- export default task;
107
- `,
108
- "src/objects/index.ts": () => `export { default as task } from './task';
109
- `,
110
- ".gitignore": () => `node_modules/
111
- dist/
112
- *.tsbuildinfo
113
- `,
114
- "README.md": (name) => `# ${toTitleCase(name)}
115
-
116
- Built with [ObjectStack](https://objectstack.com).
117
-
118
- ## Quick Start
119
-
120
- \`\`\`bash
121
- # Install dependencies
122
- npm install
123
-
124
- # Start development server
125
- npm run dev
126
-
127
- # Validate configuration
128
- npm run validate
129
- \`\`\`
130
-
131
- ## Project Structure
132
-
133
- - \`objectstack.config.ts\` \u2014 Stack definition (objects, API, settings)
134
- - \`src/objects/\` \u2014 Object definitions
135
- - \`dist/\` \u2014 Compiled output
136
-
137
- ## Learn More
138
-
139
- - [ObjectStack Documentation](https://objectstack.com/docs)
140
- `
141
- }
142
- },
143
- "full-stack": {
144
- description: "Server + UI + auth + 3 CRM objects",
145
- files: {
146
- "objectstack.config.ts": (name) => `import { defineStack } from '@objectstack/spec';
147
- import * as objects from './src/objects';
148
- import * as apps from './src/apps';
149
-
150
- export default defineStack({
151
- manifest: {
152
- id: 'com.example.${name}',
153
- namespace: '${name}',
154
- version: '0.1.0',
155
- type: 'app',
156
- name: '${toTitleCase(name)}',
157
- description: '${toTitleCase(name)} CRM \u2014 built with ObjectStack',
33
+ content: {
34
+ description: "Content marketing pipeline \u2014 editorial calendar & channel ROI",
35
+ source: { kind: "remote", pkg: "content" }
158
36
  },
159
-
160
- objects: Object.values(objects),
161
- apps: Object.values(apps),
162
-
163
- api: {
164
- rest: { enabled: true, basePath: '/api' },
37
+ contracts: {
38
+ description: "Post-signature CLM \u2014 approvals, obligations, renewals",
39
+ source: { kind: "remote", pkg: "contracts" }
165
40
  },
166
- });
167
- `,
168
- "package.json": (name) => JSON.stringify({
169
- name,
170
- version: "0.1.0",
171
- private: true,
172
- type: "module",
173
- scripts: {
174
- dev: "objectstack dev",
175
- start: "objectstack serve",
176
- build: "objectstack compile",
177
- validate: "objectstack validate",
178
- typecheck: "tsc --noEmit"
179
- },
180
- dependencies: {
181
- "@objectstack/spec": "^3.0.0",
182
- "@objectstack/runtime": "^3.0.0",
183
- "@objectstack/driver-memory": "^3.0.0",
184
- "@objectstack/plugin-hono-server": "^3.0.0",
185
- "@objectstack/plugin-auth": "^3.0.0"
186
- },
187
- devDependencies: {
188
- "@objectstack/cli": "^3.0.0",
189
- "typescript": "^5.3.0"
190
- }
191
- }, null, 2) + "\n",
192
- "tsconfig.json": () => JSON.stringify({
193
- compilerOptions: {
194
- target: "ES2022",
195
- module: "ESNext",
196
- moduleResolution: "bundler",
197
- strict: true,
198
- esModuleInterop: true,
199
- skipLibCheck: true,
200
- outDir: "dist",
201
- rootDir: ".",
202
- declaration: true
203
- },
204
- include: ["*.ts", "src/**/*"],
205
- exclude: ["dist", "node_modules"]
206
- }, null, 2) + "\n",
207
- "src/objects/contact.ts": () => `import * as Data from '@objectstack/spec/data';
208
-
209
- const contact: Data.Object = {
210
- name: 'contact',
211
- label: 'Contact',
212
- ownership: 'own',
213
- fields: {
214
- first_name: {
215
- type: 'text',
216
- label: 'First Name',
217
- required: true,
218
- },
219
- last_name: {
220
- type: 'text',
221
- label: 'Last Name',
222
- required: true,
223
- },
224
- email: {
225
- type: 'text',
226
- label: 'Email',
227
- },
228
- phone: {
229
- type: 'text',
230
- label: 'Phone',
231
- },
232
- company: {
233
- type: 'lookup',
234
- label: 'Company',
235
- reference: 'company',
236
- },
237
- },
238
- };
239
-
240
- export default contact;
241
- `,
242
- "src/objects/company.ts": () => `import * as Data from '@objectstack/spec/data';
243
-
244
- const company: Data.Object = {
245
- name: 'company',
246
- label: 'Company',
247
- ownership: 'own',
248
- fields: {
249
- name: {
250
- type: 'text',
251
- label: 'Company Name',
252
- required: true,
253
- },
254
- website: {
255
- type: 'text',
256
- label: 'Website',
257
- },
258
- industry: {
259
- type: 'select',
260
- label: 'Industry',
261
- options: [
262
- { label: 'Technology', value: 'technology' },
263
- { label: 'Finance', value: 'finance' },
264
- { label: 'Healthcare', value: 'healthcare' },
265
- { label: 'Other', value: 'other' },
266
- ],
267
- },
268
- },
269
- };
270
-
271
- export default company;
272
- `,
273
- "src/objects/deal.ts": () => `import * as Data from '@objectstack/spec/data';
274
-
275
- const deal: Data.Object = {
276
- name: 'deal',
277
- label: 'Deal',
278
- ownership: 'own',
279
- fields: {
280
- name: {
281
- type: 'text',
282
- label: 'Deal Name',
283
- required: true,
284
- },
285
- amount: {
286
- type: 'number',
287
- label: 'Amount',
288
- },
289
- stage: {
290
- type: 'select',
291
- label: 'Stage',
292
- options: [
293
- { label: 'Prospecting', value: 'prospecting' },
294
- { label: 'Qualification', value: 'qualification' },
295
- { label: 'Proposal', value: 'proposal' },
296
- { label: 'Closed Won', value: 'closed_won' },
297
- { label: 'Closed Lost', value: 'closed_lost' },
298
- ],
299
- defaultValue: 'prospecting',
300
- },
301
- contact: {
302
- type: 'lookup',
303
- label: 'Contact',
304
- reference: 'contact',
305
- },
306
- company: {
307
- type: 'lookup',
308
- label: 'Company',
309
- reference: 'company',
310
- },
311
- close_date: {
312
- type: 'date',
313
- label: 'Close Date',
314
- },
315
- },
316
- };
317
-
318
- export default deal;
319
- `,
320
- "src/objects/index.ts": () => `export { default as contact } from './contact';
321
- export { default as company } from './company';
322
- export { default as deal } from './deal';
323
- `,
324
- "src/views/contact_list.ts": () => `import * as UI from '@objectstack/spec/ui';
325
-
326
- const contactList: UI.View = {
327
- name: 'contact_list',
328
- label: 'All Contacts',
329
- object: 'contact',
330
- type: 'list',
331
- columns: ['first_name', 'last_name', 'email', 'phone', 'company'],
332
- };
333
-
334
- export default contactList;
335
- `,
336
- "src/views/company_list.ts": () => `import * as UI from '@objectstack/spec/ui';
337
-
338
- const companyList: UI.View = {
339
- name: 'company_list',
340
- label: 'All Companies',
341
- object: 'company',
342
- type: 'list',
343
- columns: ['name', 'website', 'industry'],
344
- };
345
-
346
- export default companyList;
347
- `,
348
- "src/views/deal_list.ts": () => `import * as UI from '@objectstack/spec/ui';
349
-
350
- const dealList: UI.View = {
351
- name: 'deal_list',
352
- label: 'All Deals',
353
- object: 'deal',
354
- type: 'list',
355
- columns: ['name', 'amount', 'stage', 'contact', 'close_date'],
356
- };
357
-
358
- export default dealList;
359
- `,
360
- "src/apps/crm.ts": () => `import * as UI from '@objectstack/spec/ui';
361
-
362
- const crm: UI.App = {
363
- name: 'crm',
364
- label: 'CRM',
365
- description: 'Customer Relationship Management',
366
- navigation: [
367
- { type: 'object', object: 'contact', label: 'Contacts' },
368
- { type: 'object', object: 'company', label: 'Companies' },
369
- { type: 'object', object: 'deal', label: 'Deals' },
370
- ],
371
- };
372
-
373
- export default crm;
374
- `,
375
- "src/apps/index.ts": () => `export { default as crm } from './crm';
376
- `,
377
- ".gitignore": () => `node_modules/
378
- dist/
379
- *.tsbuildinfo
380
- `,
381
- "README.md": (name) => `# ${toTitleCase(name)}
382
-
383
- A full-stack CRM application built with [ObjectStack](https://objectstack.com).
384
-
385
- ## Quick Start
386
-
387
- \`\`\`bash
388
- npm install
389
- npm run dev
390
- \`\`\`
391
-
392
- ## Project Structure
393
-
394
- - \`objectstack.config.ts\` \u2014 Stack definition
395
- - \`src/objects/\` \u2014 Data objects (Contact, Company, Deal)
396
- - \`src/views/\` \u2014 List views
397
- - \`src/apps/crm.ts\` \u2014 CRM app with navigation
398
-
399
- ## Learn More
400
-
401
- - [ObjectStack Documentation](https://objectstack.com/docs)
402
- `
403
- }
404
- },
405
- plugin: {
406
- description: "Plugin skeleton with test setup",
407
- files: {
408
- "objectstack.config.ts": (name) => `import { defineStack } from '@objectstack/spec';
409
- import * as objects from './src/objects';
410
-
411
- export default defineStack({
412
- manifest: {
413
- id: 'com.objectstack.plugin-${name}',
414
- namespace: 'plugin_${name}',
415
- version: '0.1.0',
416
- type: 'plugin',
417
- name: '${toTitleCase(name)} Plugin',
418
- description: 'ObjectStack Plugin: ${toTitleCase(name)}',
419
- },
420
-
421
- objects: Object.values(objects),
422
- });
423
- `,
424
- "package.json": (name) => JSON.stringify({
425
- name: `@objectstack/plugin-${name}`,
426
- version: "0.1.0",
427
- description: `ObjectStack Plugin: ${toTitleCase(name)}`,
428
- main: "dist/index.js",
429
- types: "dist/index.d.ts",
430
- type: "module",
431
- scripts: {
432
- build: "tsc",
433
- dev: "tsc --watch",
434
- test: "vitest run",
435
- validate: "objectstack validate",
436
- typecheck: "tsc --noEmit"
437
- },
438
- keywords: ["objectstack", "plugin", name],
439
- author: "",
440
- license: "MIT",
441
- dependencies: {
442
- "@objectstack/spec": "^3.0.0"
443
- },
444
- devDependencies: {
445
- "@types/node": "^22.0.0",
446
- "typescript": "^5.3.0",
447
- "vitest": "^4.0.0"
448
- }
449
- }, null, 2) + "\n",
450
- "tsconfig.json": () => JSON.stringify({
451
- compilerOptions: {
452
- target: "ES2022",
453
- module: "ESNext",
454
- moduleResolution: "bundler",
455
- strict: true,
456
- esModuleInterop: true,
457
- skipLibCheck: true,
458
- outDir: "dist",
459
- rootDir: ".",
460
- declaration: true
461
- },
462
- include: ["*.ts", "src/**/*"],
463
- exclude: ["dist", "node_modules"]
464
- }, null, 2) + "\n",
465
- "src/index.ts": (name) => `/**
466
- * ${toTitleCase(name)} Plugin for ObjectStack
467
- *
468
- * Entry point \u2014 re-exports all plugin metadata.
469
- */
470
- export * as objects from './objects';
471
- `,
472
- "src/objects/sample.ts": (name) => `import * as Data from '@objectstack/spec/data';
473
-
474
- const sample: Data.Object = {
475
- name: '${name}_sample',
476
- label: '${toTitleCase(name)} Sample',
477
- ownership: 'own',
478
- fields: {
479
- name: {
480
- type: 'text',
481
- label: 'Name',
482
- required: true,
483
- },
484
- },
485
- };
486
-
487
- export default sample;
488
- `,
489
- "src/objects/index.ts": () => `export { default as sample } from './sample';
490
- `,
491
- "test/sample.test.ts": (name) => `import { describe, it, expect } from 'vitest';
492
- import sample from '../src/objects/sample';
493
-
494
- describe('${name} plugin', () => {
495
- it('should export a valid sample object', () => {
496
- expect(sample).toBeDefined();
497
- expect(sample.name).toBe('${name}_sample');
498
- expect(sample.fields).toHaveProperty('name');
499
- });
500
- });
501
- `,
502
- ".gitignore": () => `node_modules/
503
- dist/
504
- *.tsbuildinfo
505
- `,
506
- "README.md": (name) => `# @objectstack/plugin-${name}
507
-
508
- ObjectStack Plugin: ${toTitleCase(name)}
509
-
510
- ## Installation
511
-
512
- \`\`\`bash
513
- npm install @objectstack/plugin-${name}
514
- \`\`\`
515
-
516
- ## Usage
517
-
518
- \`\`\`typescript
519
- import { defineStack } from '@objectstack/spec';
520
-
521
- export default defineStack({
522
- plugins: [
523
- '@objectstack/plugin-${name}',
524
- ],
525
- });
526
- \`\`\`
527
-
528
- ## Development
529
-
530
- \`\`\`bash
531
- # Run tests
532
- npm test
533
-
534
- # Build
535
- npm run build
536
-
537
- # Validate metadata
538
- npm run validate
539
- \`\`\`
540
-
541
- ## License
542
-
543
- MIT
544
- `
545
- }
41
+ procurement: {
42
+ description: "Source-to-pay \u2014 vendors, POs, receipts, invoice matching",
43
+ source: { kind: "remote", pkg: "procurement" }
546
44
  }
547
45
  };
548
- function readTemplate(filename) {
549
- return fs.readFileSync(path.join(TEMPLATES_DIR, filename), "utf-8");
550
- }
551
- var AI_CONFIG_FILES = {
552
- ".github/copilot-instructions.md": (name) => readTemplate("copilot-instructions.md").replaceAll("{{PROJECT_NAME}}", name).replaceAll("{{PROJECT_TITLE}}", toTitleCase(name))
553
- };
554
46
  function toTitleCase(str) {
555
47
  return str.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
556
48
  }
@@ -574,16 +66,132 @@ function printStep(msg) {
574
66
  function printWarning(msg) {
575
67
  console.log(chalk.yellow(` \u26A0 ${msg}`));
576
68
  }
577
- var program = new Command().name("create-objectstack").description("Create a new ObjectStack project").version("3.0.0").argument("[name]", "Project name (defaults to current directory name)").option(
69
+ function detectPackageManager() {
70
+ try {
71
+ execSync("pnpm --version", { stdio: "ignore" });
72
+ return "pnpm";
73
+ } catch {
74
+ return "npm";
75
+ }
76
+ }
77
+ function copyDir(src, dest, collected, rel = "") {
78
+ fs.mkdirSync(dest, { recursive: true });
79
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
80
+ const srcPath = path.join(src, entry.name);
81
+ const destPath = path.join(dest, entry.name);
82
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
83
+ if (entry.isDirectory()) {
84
+ copyDir(srcPath, destPath, collected, relPath);
85
+ } else if (entry.isFile()) {
86
+ fs.copyFileSync(srcPath, destPath);
87
+ collected.push(relPath);
88
+ }
89
+ }
90
+ }
91
+ function loadBundled(templateDir, targetDir) {
92
+ const src = path.join(BUNDLED_TEMPLATES_DIR, templateDir);
93
+ if (!fs.existsSync(src)) {
94
+ throw new Error(`Bundled template missing on disk: ${src}`);
95
+ }
96
+ const collected = [];
97
+ copyDir(src, targetDir, collected);
98
+ return collected;
99
+ }
100
+ async function downloadTarball(url, destFile) {
101
+ const res = await fetch(url, { redirect: "follow" });
102
+ if (!res.ok || !res.body) {
103
+ throw new Error(`Download failed: ${url} (${res.status})`);
104
+ }
105
+ const out = createWriteStream(destFile);
106
+ for await (const chunk of res.body) {
107
+ out.write(chunk);
108
+ }
109
+ await new Promise((resolve, reject) => {
110
+ out.end((err) => err ? reject(err) : resolve());
111
+ });
112
+ }
113
+ async function loadRemote(pkgName, targetDir) {
114
+ const tmp = await mkdtemp(path.join(os.tmpdir(), "create-objectstack-"));
115
+ try {
116
+ const tarball = path.join(tmp, "templates.tar.gz");
117
+ printStep(`Fetching template "${pkgName}" from ${REMOTE_REPO}@${REMOTE_BRANCH}\u2026`);
118
+ await downloadTarball(REMOTE_TARBALL_URL, tarball);
119
+ fs.mkdirSync(targetDir, { recursive: true });
120
+ const collected = [];
121
+ await pipeline(
122
+ createReadStream(tarball),
123
+ createGunzip(),
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ tar.extract({
126
+ cwd: targetDir,
127
+ strip: 3,
128
+ filter: (p) => {
129
+ const parts = p.split("/");
130
+ return parts[1] === "packages" && parts[2] === pkgName && parts.length > 3;
131
+ },
132
+ onentry: (entry) => {
133
+ if (entry.type === "File") {
134
+ const parts = entry.path.split("/").slice(3);
135
+ if (parts.length > 0) collected.push(parts.join("/"));
136
+ }
137
+ }
138
+ })
139
+ );
140
+ if (collected.length === 0) {
141
+ throw new Error(
142
+ `Template "${pkgName}" not found in ${REMOTE_REPO}@${REMOTE_BRANCH} (expected packages/${pkgName}/).`
143
+ );
144
+ }
145
+ return collected;
146
+ } finally {
147
+ await rm(tmp, { recursive: true, force: true });
148
+ }
149
+ }
150
+ function rewriteProjectIdentity(targetDir, projectName) {
151
+ const title = toTitleCase(projectName);
152
+ const pkgPath = path.join(targetDir, "package.json");
153
+ if (fs.existsSync(pkgPath)) {
154
+ try {
155
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
156
+ pkg.name = projectName;
157
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
158
+ } catch {
159
+ }
160
+ }
161
+ const manifestPath = path.join(targetDir, "objectstack.manifest.json");
162
+ if (fs.existsSync(manifestPath)) {
163
+ try {
164
+ const m = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
165
+ m.name = projectName;
166
+ m.displayName = title;
167
+ fs.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
168
+ } catch {
169
+ }
170
+ }
171
+ const configPath = path.join(targetDir, "objectstack.config.ts");
172
+ if (fs.existsSync(configPath)) {
173
+ let cfg = fs.readFileSync(configPath, "utf8");
174
+ cfg = cfg.replace(/(\bid:\s*)(['"`])[^'"`]*\2/, `$1$2${projectName}$2`);
175
+ cfg = cfg.replace(/(\bname:\s*)(['"`])[^'"`]*\2/, `$1$2${title}$2`);
176
+ fs.writeFileSync(configPath, cfg);
177
+ }
178
+ const readmePath = path.join(targetDir, "README.md");
179
+ if (fs.existsSync(readmePath)) {
180
+ let md = fs.readFileSync(readmePath, "utf8");
181
+ md = md.replace(/^#\s+.*$/m, `# ${title}`);
182
+ fs.writeFileSync(readmePath, md);
183
+ }
184
+ }
185
+ var program = new Command().name("create-objectstack").description("Create a new ObjectStack environment").version("6.2.0").argument("[name]", "Environment name (defaults to current directory name)").option(
578
186
  "-t, --template <template>",
579
- "Project template: minimal-api, full-stack, plugin",
580
- "minimal-api"
581
- ).option("--skip-install", "Skip dependency installation").action(async (name, options) => {
187
+ `Template: ${Object.keys(TEMPLATES).join(", ")}`,
188
+ "blank"
189
+ ).option("--skip-install", "Skip dependency installation").option("--skip-skills", "Skip installing ObjectStack AI skills").action(async (name, options) => {
582
190
  console.log("");
583
191
  console.log(chalk.bold.cyan(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
584
- console.log(chalk.bold.cyan(" \u2551") + chalk.bold(" \u25C6 Create ObjectStack ") + chalk.dim("v3.0") + chalk.bold.cyan(" \u2551"));
192
+ console.log(chalk.bold.cyan(" \u2551") + chalk.bold(" \u25C6 Create ObjectStack ") + chalk.dim("v6.x") + chalk.bold.cyan(" \u2551"));
585
193
  console.log(chalk.bold.cyan(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
586
- printHeader("New Project");
194
+ printHeader("New Environment");
587
195
  const template = TEMPLATES[options.template];
588
196
  if (!template) {
589
197
  printError(`Unknown template: ${options.template}`);
@@ -594,7 +202,7 @@ var program = new Command().name("create-objectstack").description("Create a new
594
202
  const projectName = name || path.basename(cwd);
595
203
  const targetDir = name ? path.resolve(cwd, name) : cwd;
596
204
  const isCurrentDir = targetDir === cwd;
597
- printKV("Project", projectName);
205
+ printKV("Environment", projectName);
598
206
  printKV("Template", `${options.template} \u2014 ${template.description}`);
599
207
  printKV("Directory", targetDir);
600
208
  console.log("");
@@ -605,25 +213,22 @@ var program = new Command().name("create-objectstack").description("Create a new
605
213
  process.exit(1);
606
214
  }
607
215
  }
608
- const createdFiles = [];
609
216
  try {
610
- if (!fs.existsSync(targetDir)) {
611
- fs.mkdirSync(targetDir, { recursive: true });
612
- }
613
- const allFiles = { ...template.files, ...AI_CONFIG_FILES };
614
- for (const [filePath, contentFn] of Object.entries(allFiles)) {
615
- const fullPath = path.join(targetDir, filePath);
616
- const dir = path.dirname(fullPath);
617
- if (!fs.existsSync(dir)) {
618
- fs.mkdirSync(dir, { recursive: true });
619
- }
620
- fs.writeFileSync(fullPath, contentFn(projectName));
621
- createdFiles.push(filePath);
217
+ fs.mkdirSync(targetDir, { recursive: true });
218
+ let createdFiles;
219
+ if (template.source.kind === "bundled") {
220
+ createdFiles = loadBundled(template.source.dir, targetDir);
221
+ } else {
222
+ createdFiles = await loadRemote(template.source.pkg, targetDir);
622
223
  }
224
+ rewriteProjectIdentity(targetDir, projectName);
623
225
  console.log(chalk.bold(" Created files:"));
624
- for (const f of createdFiles) {
226
+ for (const f of createdFiles.slice(0, 20)) {
625
227
  console.log(chalk.green(` + ${f}`));
626
228
  }
229
+ if (createdFiles.length > 20) {
230
+ console.log(chalk.dim(` \u2026 and ${createdFiles.length - 20} more`));
231
+ }
627
232
  console.log("");
628
233
  if (!options.skipInstall) {
629
234
  printStep("Installing dependencies...");
@@ -636,7 +241,7 @@ var program = new Command().name("create-objectstack").description("Create a new
636
241
  console.log("");
637
242
  }
638
243
  }
639
- if (!options.skipInstall) {
244
+ if (!options.skipInstall && !options.skipSkills) {
640
245
  printStep("Installing AI skills for your coding agent...");
641
246
  try {
642
247
  execSync("npx -y skills add objectstack-ai/framework --all", {
@@ -651,7 +256,7 @@ var program = new Command().name("create-objectstack").description("Create a new
651
256
  console.log("");
652
257
  }
653
258
  }
654
- printSuccess("Project created!");
259
+ printSuccess("Environment created!");
655
260
  console.log("");
656
261
  console.log(chalk.bold(" Next steps:"));
657
262
  if (!isCurrentDir) {
@@ -662,23 +267,16 @@ var program = new Command().name("create-objectstack").description("Create a new
662
267
  }
663
268
  console.log(chalk.dim(" npm run dev # Start development server"));
664
269
  console.log(chalk.dim(" npm run validate # Check configuration"));
665
- if (options.skipInstall) {
270
+ if (options.skipInstall || options.skipSkills) {
666
271
  console.log("");
667
272
  console.log(chalk.bold(" AI Skills (recommended):"));
668
273
  console.log(chalk.dim(" npx skills add objectstack-ai/framework"));
669
274
  }
670
275
  console.log("");
671
276
  } catch (error) {
672
- printError(error.message || String(error));
277
+ const msg = error instanceof Error ? error.message : String(error);
278
+ printError(msg);
673
279
  process.exit(1);
674
280
  }
675
281
  });
676
- function detectPackageManager() {
677
- try {
678
- execSync("pnpm --version", { stdio: "ignore" });
679
- return "pnpm";
680
- } catch {
681
- return "npm";
682
- }
683
- }
684
282
  program.parse();
@@ -0,0 +1,27 @@
1
+ # Blank Starter
2
+
3
+ Minimal ObjectStack environment — a clean slate for building.
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm dev
10
+ ```
11
+
12
+ The REST API is served at `http://localhost:3000/api`.
13
+
14
+ ## Layout
15
+
16
+ - `objectstack.config.ts` — environment manifest (objects, API, plugins)
17
+ - `src/objects/` — object definitions (one file per object)
18
+
19
+ ## Next steps
20
+
21
+ - Add an object: see the `objectstack-data` skill.
22
+ - Add a view or app: see `objectstack-ui`.
23
+ - Add a flow or automation: see `objectstack-automation`.
24
+ - Add an AI agent: see `objectstack-ai`.
25
+
26
+ Skills live in `skills/` in the ObjectStack framework repo and in the in-IDE
27
+ assistant catalog.
@@ -0,0 +1,19 @@
1
+ import { defineStack } from '@objectstack/spec';
2
+ import * as objects from './src/objects/index.js';
3
+
4
+ export default defineStack({
5
+ manifest: {
6
+ id: 'blank',
7
+ version: '0.1.0',
8
+ type: 'app',
9
+ name: 'Blank Starter',
10
+ description: 'Minimal ObjectStack environment — a clean slate for building.',
11
+ },
12
+ objects: Object.values(objects),
13
+ api: {
14
+ rest: {
15
+ enabled: true,
16
+ basePath: '/api',
17
+ },
18
+ },
19
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://schemas.objectstack.dev/template-manifest.json",
3
+ "name": "blank",
4
+ "specVersion": "^6.0.0",
5
+ "displayName": "Blank Starter",
6
+ "description": "Minimal ObjectStack environment with a single object — a clean slate for building.",
7
+ "category": "starter",
8
+ "isStarter": true,
9
+ "skills": ["objectstack-platform", "objectstack-data"],
10
+ "readmePath": "README.md",
11
+ "scaffold": {
12
+ "variables": {
13
+ "appName": { "type": "string", "prompt": "Application name", "default": "my-app" }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "objectstack-blank",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "objectstack serve --watch",
8
+ "start": "objectstack serve",
9
+ "build": "objectstack build",
10
+ "validate": "objectstack validate",
11
+ "typecheck": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@objectstack/spec": "^6.0.0",
15
+ "@objectstack/runtime": "^6.0.0",
16
+ "@objectstack/driver-memory": "^6.0.0",
17
+ "@objectstack/plugin-hono-server": "^6.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@objectstack/cli": "^6.0.0",
21
+ "typescript": "^5.3.0"
22
+ }
23
+ }
@@ -0,0 +1 @@
1
+ export { Note } from './note.object.js';
@@ -0,0 +1,28 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+
3
+ import { ObjectSchema, Field } from '@objectstack/spec/data';
4
+
5
+ export const Note = ObjectSchema.create({
6
+ name: 'note',
7
+ label: 'Note',
8
+ pluralLabel: 'Notes',
9
+ icon: 'sticky-note',
10
+ description: 'A short note — the starter object for a blank environment.',
11
+
12
+ fields: {
13
+ title: Field.text({
14
+ label: 'Title',
15
+ required: true,
16
+ searchable: true,
17
+ maxLength: 200,
18
+ }),
19
+ body: Field.longText({
20
+ label: 'Body',
21
+ }),
22
+ },
23
+
24
+ enable: {
25
+ apiEnabled: true,
26
+ searchable: true,
27
+ },
28
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "allowImportingTsExtensions": false,
11
+ "noEmit": true,
12
+ "isolatedModules": true
13
+ },
14
+ "include": ["objectstack.config.ts", "src/**/*"]
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-objectstack",
3
- "version": "6.1.1",
3
+ "version": "6.3.0",
4
4
  "description": "Create a new ObjectStack project — npx create-objectstack",
5
5
  "bin": {
6
6
  "create-objectstack": "./bin/create-objectstack.js"
@@ -16,10 +16,12 @@
16
16
  "license": "Apache-2.0",
17
17
  "dependencies": {
18
18
  "chalk": "^5.6.2",
19
- "commander": "^14.0.3"
19
+ "commander": "^14.0.3",
20
+ "tar": "^7.4.3"
20
21
  },
21
22
  "devDependencies": {
22
23
  "@types/node": "^25.9.1",
24
+ "@types/tar": "^6.1.13",
23
25
  "tsup": "^8.5.1",
24
26
  "typescript": "^6.0.3"
25
27
  },