@objectifthunes/create-sandstone 0.1.0 → 1.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.
Files changed (91) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +31 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/add-adapter.d.ts +2 -0
  6. package/dist/commands/add-adapter.d.ts.map +1 -0
  7. package/dist/commands/add-adapter.js +683 -0
  8. package/dist/commands/add-adapter.js.map +1 -0
  9. package/dist/commands/generate-adapter.d.ts +2 -0
  10. package/dist/commands/generate-adapter.d.ts.map +1 -0
  11. package/dist/commands/generate-adapter.js +467 -0
  12. package/dist/commands/generate-adapter.js.map +1 -0
  13. package/dist/commands/generate-entity.d.ts +2 -0
  14. package/dist/commands/generate-entity.d.ts.map +1 -0
  15. package/dist/commands/generate-entity.js +210 -0
  16. package/dist/commands/generate-entity.js.map +1 -0
  17. package/dist/generator.d.ts +3 -0
  18. package/dist/generator.d.ts.map +1 -0
  19. package/dist/generator.js +85 -0
  20. package/dist/generator.js.map +1 -0
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +107 -61
  24. package/dist/index.js.map +1 -0
  25. package/dist/presets.d.ts +45 -0
  26. package/dist/presets.d.ts.map +1 -0
  27. package/dist/presets.js +105 -0
  28. package/dist/presets.js.map +1 -0
  29. package/dist/prompts.d.ts +3 -13
  30. package/dist/prompts.d.ts.map +1 -0
  31. package/dist/prompts.js +222 -49
  32. package/dist/prompts.js.map +1 -0
  33. package/dist/templates/claude-md.d.ts +3 -0
  34. package/dist/templates/claude-md.d.ts.map +1 -0
  35. package/dist/templates/claude-md.js +853 -0
  36. package/dist/templates/claude-md.js.map +1 -0
  37. package/dist/templates/docker-compose.d.ts +3 -0
  38. package/dist/templates/docker-compose.d.ts.map +1 -0
  39. package/dist/templates/docker-compose.js +75 -0
  40. package/dist/templates/docker-compose.js.map +1 -0
  41. package/dist/templates/env-example.d.ts +3 -0
  42. package/dist/templates/env-example.d.ts.map +1 -0
  43. package/dist/templates/env-example.js +137 -0
  44. package/dist/templates/env-example.js.map +1 -0
  45. package/dist/templates/gitignore.d.ts +2 -0
  46. package/dist/templates/gitignore.d.ts.map +1 -0
  47. package/dist/templates/gitignore.js +11 -0
  48. package/dist/templates/gitignore.js.map +1 -0
  49. package/dist/templates/infrastructure.d.ts +3 -0
  50. package/dist/templates/infrastructure.d.ts.map +1 -0
  51. package/dist/templates/infrastructure.js +647 -0
  52. package/dist/templates/infrastructure.js.map +1 -0
  53. package/dist/templates/main-express.d.ts +3 -0
  54. package/dist/templates/main-express.d.ts.map +1 -0
  55. package/dist/templates/main-express.js +80 -0
  56. package/dist/templates/main-express.js.map +1 -0
  57. package/dist/templates/main-fastify.d.ts +3 -0
  58. package/dist/templates/main-fastify.d.ts.map +1 -0
  59. package/dist/templates/main-fastify.js +73 -0
  60. package/dist/templates/main-fastify.js.map +1 -0
  61. package/dist/templates/main-hono.d.ts +3 -0
  62. package/dist/templates/main-hono.d.ts.map +1 -0
  63. package/dist/templates/main-hono.js +83 -0
  64. package/dist/templates/main-hono.js.map +1 -0
  65. package/dist/templates/migrate-script.d.ts +2 -0
  66. package/dist/templates/migrate-script.d.ts.map +1 -0
  67. package/dist/templates/migrate-script.js +18 -0
  68. package/dist/templates/migrate-script.js.map +1 -0
  69. package/dist/templates/migration.d.ts +2 -0
  70. package/dist/templates/migration.d.ts.map +1 -0
  71. package/dist/templates/migration.js +17 -0
  72. package/dist/templates/migration.js.map +1 -0
  73. package/dist/templates/package-json.d.ts +3 -0
  74. package/dist/templates/package-json.d.ts.map +1 -0
  75. package/dist/templates/package-json.js +149 -0
  76. package/dist/templates/package-json.js.map +1 -0
  77. package/dist/templates/schema.d.ts +3 -0
  78. package/dist/templates/schema.d.ts.map +1 -0
  79. package/dist/templates/schema.js +108 -0
  80. package/dist/templates/schema.js.map +1 -0
  81. package/dist/templates/test-setup.d.ts +3 -0
  82. package/dist/templates/test-setup.d.ts.map +1 -0
  83. package/dist/templates/test-setup.js +12 -0
  84. package/dist/templates/test-setup.js.map +1 -0
  85. package/dist/templates/tsconfig.d.ts +2 -0
  86. package/dist/templates/tsconfig.d.ts.map +1 -0
  87. package/dist/templates/tsconfig.js +21 -0
  88. package/dist/templates/tsconfig.js.map +1 -0
  89. package/package.json +16 -9
  90. package/dist/generators.d.ts +0 -7
  91. package/dist/generators.js +0 -328
@@ -0,0 +1,210 @@
1
+ import { writeFile, mkdir, readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ const TS_TO_SQL = {
5
+ string: 'TEXT',
6
+ number: 'INTEGER',
7
+ boolean: 'BOOLEAN',
8
+ Date: 'TIMESTAMPTZ',
9
+ uuid: 'UUID',
10
+ };
11
+ const TS_TO_GRAPHQL = {
12
+ string: 'GraphQLString',
13
+ number: 'GraphQLInt',
14
+ boolean: 'GraphQLBoolean',
15
+ Date: 'DateTimeScalar',
16
+ uuid: 'UUIDScalar',
17
+ };
18
+ function pascalToSnake(name) {
19
+ return name
20
+ .replace(/([A-Z])/g, (match, letter, offset) => offset > 0 ? `_${letter.toLowerCase()}` : letter.toLowerCase())
21
+ .replace(/_{2,}/g, '_');
22
+ }
23
+ const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
24
+ function assertSafeIdentifier(value, label) {
25
+ if (!SAFE_IDENTIFIER.test(value)) {
26
+ throw new Error(`Invalid ${label}: "${value}". Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/ (letters, digits, underscores only, cannot start with a digit).`);
27
+ }
28
+ }
29
+ function parseFields(fieldsStr) {
30
+ return fieldsStr
31
+ .split(',')
32
+ .map((f) => f.trim())
33
+ .filter((f) => f.length > 0)
34
+ .map((f) => {
35
+ const parts = f.split(':').map((s) => s.trim());
36
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
37
+ throw new Error(`Invalid field definition: "${f}". Expected format: "name:type" (exactly one colon).`);
38
+ }
39
+ const name = parts[0];
40
+ const rawType = parts[1];
41
+ assertSafeIdentifier(name, 'field name');
42
+ const nullable = rawType.endsWith('?');
43
+ const type = nullable ? rawType.slice(0, -1) : rawType;
44
+ return { name, type, nullable };
45
+ });
46
+ }
47
+ function parseArgs(args) {
48
+ let name = null;
49
+ let fieldsStr = null;
50
+ for (let i = 0; i < args.length; i++) {
51
+ const arg = args[i];
52
+ if (arg === '--fields' || arg === '-f') {
53
+ fieldsStr = args[++i] ?? null;
54
+ }
55
+ else if (arg.startsWith('--fields=')) {
56
+ fieldsStr = arg.split('=').slice(1).join('=');
57
+ }
58
+ else if (!arg.startsWith('-')) {
59
+ name = arg;
60
+ }
61
+ }
62
+ if (!name) {
63
+ console.error('Error: Entity name is required.');
64
+ console.error('Usage: sandstone generate entity <Name> --fields "field:type, ..."');
65
+ process.exit(1);
66
+ }
67
+ if (!fieldsStr) {
68
+ console.error('Error: --fields is required.');
69
+ console.error('Usage: sandstone generate entity <Name> --fields "title:string, published:boolean"');
70
+ process.exit(1);
71
+ }
72
+ return { name, fields: parseFields(fieldsStr) };
73
+ }
74
+ async function detectNextMigrationNumber(migrationsDir) {
75
+ if (!existsSync(migrationsDir)) {
76
+ return '001';
77
+ }
78
+ const entries = await readdir(migrationsDir);
79
+ const numbers = entries
80
+ .map((e) => {
81
+ const match = e.match(/^(\d+)/);
82
+ return match ? parseInt(match[1], 10) : 0;
83
+ })
84
+ .filter((n) => n > 0);
85
+ if (numbers.length === 0)
86
+ return '001';
87
+ const next = Math.max(...numbers) + 1;
88
+ return String(next).padStart(3, '0');
89
+ }
90
+ function generateUpMigration(tableName, fields) {
91
+ const columns = [
92
+ ' id UUID PRIMARY KEY DEFAULT gen_random_uuid()',
93
+ ...fields.map((f) => {
94
+ const sqlType = TS_TO_SQL[f.type] ?? 'TEXT';
95
+ const nullable = f.nullable ? '' : ' NOT NULL';
96
+ return ` ${f.name} ${sqlType}${nullable}`;
97
+ }),
98
+ ' created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()',
99
+ ' updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()',
100
+ ];
101
+ return `CREATE TABLE ${tableName} (\n${columns.join(',\n')}\n);\n`;
102
+ }
103
+ function generateDownMigration(tableName) {
104
+ return `DROP TABLE IF EXISTS ${tableName};\n`;
105
+ }
106
+ function generateTypeScriptModule(entityName, tableName, fields) {
107
+ const tsFields = [
108
+ ' id: string;',
109
+ ...fields.map((f) => {
110
+ const tsType = f.type === 'uuid' ? 'string' : f.type;
111
+ const optional = f.nullable ? '?' : '';
112
+ return ` ${f.name}${optional}: ${tsType};`;
113
+ }),
114
+ ' created_at: Date;',
115
+ ' updated_at: Date;',
116
+ ];
117
+ return `import { createRepository } from '@objectifthunes/sandstone-sdk';
118
+ import type { Repository } from '@objectifthunes/sandstone-sdk';
119
+
120
+ export interface ${entityName} {
121
+ ${tsFields.join('\n')}
122
+ }
123
+
124
+ export const ${entityName.charAt(0).toLowerCase() + entityName.slice(1)}Repo: Repository<${entityName}> = createRepository<${entityName}>('${tableName}', {
125
+ primaryKey: 'id',
126
+ softDelete: false,
127
+ });
128
+ `;
129
+ }
130
+ function generateGraphQLSnippet(entityName, fields) {
131
+ const graphqlImports = new Set();
132
+ graphqlImports.add('GraphQLObjectType');
133
+ graphqlImports.add('GraphQLNonNull');
134
+ graphqlImports.add('GraphQLString');
135
+ const fieldEntries = [
136
+ ` id: { type: new GraphQLNonNull(UUIDScalar) },`,
137
+ ];
138
+ for (const f of fields) {
139
+ const gqlType = TS_TO_GRAPHQL[f.type] ?? 'GraphQLString';
140
+ graphqlImports.add(gqlType);
141
+ const wrapped = f.nullable ? gqlType : `new GraphQLNonNull(${gqlType})`;
142
+ fieldEntries.push(` ${f.name}: { type: ${wrapped} },`);
143
+ }
144
+ graphqlImports.add('DateTimeScalar');
145
+ fieldEntries.push(` created_at: { type: new GraphQLNonNull(DateTimeScalar) },`);
146
+ fieldEntries.push(` updated_at: { type: new GraphQLNonNull(DateTimeScalar) },`);
147
+ // Separate standard GraphQL types from Sandstone scalars
148
+ const standardTypes = [
149
+ 'GraphQLObjectType',
150
+ 'GraphQLNonNull',
151
+ 'GraphQLString',
152
+ 'GraphQLInt',
153
+ 'GraphQLBoolean',
154
+ ].filter((t) => graphqlImports.has(t));
155
+ const sandstoneScalars = ['DateTimeScalar', 'UUIDScalar', 'EmailScalar'].filter((t) => graphqlImports.has(t));
156
+ const lines = [];
157
+ lines.push(`import { ${standardTypes.join(', ')} } from 'graphql';`);
158
+ if (sandstoneScalars.length > 0) {
159
+ lines.push(`import { ${sandstoneScalars.join(', ')} } from '@objectifthunes/sandstone-sdk';`);
160
+ }
161
+ lines.push('');
162
+ lines.push(`export const ${entityName}Type = new GraphQLObjectType({`);
163
+ lines.push(` name: '${entityName}',`);
164
+ lines.push(` fields: () => ({`);
165
+ lines.push(fieldEntries.join('\n'));
166
+ lines.push(` }),`);
167
+ lines.push(`});`);
168
+ return lines.join('\n');
169
+ }
170
+ export async function generateEntity(args) {
171
+ const { name: entityName, fields } = parseArgs(args);
172
+ const snakeName = pascalToSnake(entityName);
173
+ const tableName = `${snakeName}s`;
174
+ assertSafeIdentifier(tableName, 'table name');
175
+ const cwd = process.cwd();
176
+ const migrationsDir = join(cwd, 'migrations');
177
+ const srcDir = join(cwd, 'src');
178
+ // Detect next migration number
179
+ const migrationNum = await detectNextMigrationNumber(migrationsDir);
180
+ // File paths
181
+ const upPath = join(migrationsDir, `${migrationNum}_create_${tableName}.up.sql`);
182
+ const downPath = join(migrationsDir, `${migrationNum}_create_${tableName}.down.sql`);
183
+ const modulePath = join(srcDir, `${snakeName}.ts`);
184
+ // Generate content
185
+ const upSql = generateUpMigration(tableName, fields);
186
+ const downSql = generateDownMigration(tableName);
187
+ const tsModule = generateTypeScriptModule(entityName, tableName, fields);
188
+ const graphqlSnippet = generateGraphQLSnippet(entityName, fields);
189
+ // Write files
190
+ await mkdir(migrationsDir, { recursive: true });
191
+ await mkdir(srcDir, { recursive: true });
192
+ await writeFile(upPath, upSql, 'utf-8');
193
+ await writeFile(downPath, downSql, 'utf-8');
194
+ await writeFile(modulePath, tsModule, 'utf-8');
195
+ // Report
196
+ console.log(`\n Generated entity: ${entityName}\n`);
197
+ console.log(` Files created:`);
198
+ console.log(` migrations/${migrationNum}_create_${tableName}.up.sql`);
199
+ console.log(` migrations/${migrationNum}_create_${tableName}.down.sql`);
200
+ console.log(` src/${snakeName}.ts`);
201
+ console.log();
202
+ console.log(` GraphQL type snippet (paste into your schema):`);
203
+ console.log();
204
+ console.log(graphqlSnippet
205
+ .split('\n')
206
+ .map((line) => ` ${line}`)
207
+ .join('\n'));
208
+ console.log();
209
+ }
210
+ //# sourceMappingURL=generate-entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-entity.js","sourceRoot":"","sources":["../../src/commands/generate-entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig } from './presets.js';
2
+ export declare function generateProject(targetDir: string, config: ProjectConfig): Promise<string[]>;
3
+ //# sourceMappingURL=generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAqBlD,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,aAAa,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA0EnB"}
@@ -0,0 +1,85 @@
1
+ import { writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { packageJsonTemplate } from './templates/package-json.js';
4
+ import { tsconfigTemplate } from './templates/tsconfig.js';
5
+ import { gitignoreTemplate } from './templates/gitignore.js';
6
+ import { envExampleTemplate } from './templates/env-example.js';
7
+ import { dockerComposeTemplate } from './templates/docker-compose.js';
8
+ import { infrastructureTemplate } from './templates/infrastructure.js';
9
+ import { mainExpressTemplate } from './templates/main-express.js';
10
+ import { mainHonoTemplate } from './templates/main-hono.js';
11
+ import { mainFastifyTemplate } from './templates/main-fastify.js';
12
+ import { schemaTemplate } from './templates/schema.js';
13
+ import { testSetupTemplate } from './templates/test-setup.js';
14
+ import { migrationTemplate } from './templates/migration.js';
15
+ import { migrateScriptTemplate } from './templates/migrate-script.js';
16
+ import { claudeMdTemplate } from './templates/claude-md.js';
17
+ export async function generateProject(targetDir, config) {
18
+ const files = [];
19
+ // Root config files
20
+ files.push({ path: 'package.json', content: packageJsonTemplate(config) });
21
+ files.push({ path: 'tsconfig.json', content: tsconfigTemplate() });
22
+ files.push({ path: '.gitignore', content: gitignoreTemplate() });
23
+ files.push({ path: '.env.example', content: envExampleTemplate(config) });
24
+ if (!config.devMode) {
25
+ files.push({
26
+ path: 'docker-compose.yml',
27
+ content: dockerComposeTemplate(config),
28
+ });
29
+ }
30
+ files.push({ path: 'CLAUDE.md', content: claudeMdTemplate(config) });
31
+ // Source files
32
+ files.push({
33
+ path: 'src/infrastructure.ts',
34
+ content: infrastructureTemplate(config),
35
+ });
36
+ if (config.framework === 'express') {
37
+ files.push({
38
+ path: 'src/main.ts',
39
+ content: mainExpressTemplate(config),
40
+ });
41
+ }
42
+ else if (config.framework === 'fastify') {
43
+ files.push({
44
+ path: 'src/main.ts',
45
+ content: mainFastifyTemplate(config),
46
+ });
47
+ }
48
+ else {
49
+ files.push({
50
+ path: 'src/main.ts',
51
+ content: mainHonoTemplate(config),
52
+ });
53
+ }
54
+ if (config.graphql) {
55
+ files.push({ path: 'src/schema.ts', content: schemaTemplate(config) });
56
+ }
57
+ // Migrations
58
+ files.push({
59
+ path: 'migrations/001_notes.up.sql',
60
+ content: migrationTemplate(),
61
+ });
62
+ // Scripts
63
+ files.push({
64
+ path: 'scripts/migrate.ts',
65
+ content: migrateScriptTemplate(),
66
+ });
67
+ // Tests
68
+ if (config.tests) {
69
+ files.push({
70
+ path: 'tests/setup.ts',
71
+ content: testSetupTemplate(config),
72
+ });
73
+ }
74
+ // Ensure all directories exist, then write files
75
+ const createdFiles = [];
76
+ for (const file of files) {
77
+ const fullPath = join(targetDir, file.path);
78
+ const dir = join(fullPath, '..');
79
+ await mkdir(dir, { recursive: true });
80
+ await writeFile(fullPath, file.content, 'utf-8');
81
+ createdFiles.push(file.path);
82
+ }
83
+ return createdFiles;
84
+ }
85
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js CHANGED
@@ -1,70 +1,116 @@
1
1
  #!/usr/bin/env node
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
- import { join } from 'node:path';
2
+ import { resolve } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { mkdir } from 'node:fs/promises';
4
5
  import { runPrompts } from './prompts.js';
5
- import { generatePackageJson, generateTsConfig, generateEnvExample, generateGitignore, generateDockerCompose, generateMainTs, } from './generators.js';
6
- async function write(dir, name, content) {
7
- const path = join(dir, name);
8
- await writeFile(path, content, 'utf-8');
9
- console.log(` created ${name}`);
6
+ import { generateProject } from './generator.js';
7
+ import { PRESETS, isPresetName } from './presets.js';
8
+ function printHelp() {
9
+ console.log(`
10
+ Usage: npx @objectifthunes/create-sandstone [project-name] [options]
11
+
12
+ Options:
13
+ --yes, -y Non-interactive mode (use defaults or preset)
14
+ --template, -t Use a preset template (saas, api, minimal, prototype)
15
+ --help, -h Show this help message
16
+
17
+ Examples:
18
+ npx @objectifthunes/create-sandstone my-backend
19
+ npx @objectifthunes/create-sandstone my-api --yes --template api
20
+ npx @objectifthunes/create-sandstone my-saas -y -t saas
21
+ npx @objectifthunes/create-sandstone my-app -y -t prototype
22
+ `);
23
+ }
24
+ function parseArgs(args) {
25
+ let name = null;
26
+ let yes = false;
27
+ let template = null;
28
+ let help = false;
29
+ for (let i = 0; i < args.length; i++) {
30
+ const arg = args[i];
31
+ if (arg === '--yes' || arg === '-y') {
32
+ yes = true;
33
+ }
34
+ else if (arg === '--help' || arg === '-h') {
35
+ help = true;
36
+ }
37
+ else if (arg === '--template' || arg === '-t') {
38
+ template = args[++i] ?? null;
39
+ }
40
+ else if (arg.startsWith('--template=')) {
41
+ template = arg.split('=')[1] ?? null;
42
+ }
43
+ else if (!arg.startsWith('-')) {
44
+ name = arg;
45
+ }
46
+ }
47
+ return { name, yes, template, help };
10
48
  }
11
49
  async function main() {
12
- console.log('\n @objectifthunes/create-sandstone\n');
13
- const config = await runPrompts();
14
- const dir = join(process.cwd(), config.name);
15
- console.log(`\n Scaffolding ${config.name}...\n`);
16
- await mkdir(dir, { recursive: true });
17
- await mkdir(join(dir, 'src'), { recursive: true });
18
- await mkdir(join(dir, 'migrations'), { recursive: true });
19
- await mkdir(join(dir, 'tests', 'unit'), { recursive: true });
20
- await write(dir, 'package.json', generatePackageJson(config));
21
- await write(dir, 'tsconfig.json', generateTsConfig(config));
22
- await write(dir, '.env.example', generateEnvExample(config));
23
- await write(dir, '.gitignore', generateGitignore());
24
- await write(dir, 'src/main.ts', generateMainTs(config));
25
- if (config.docker) {
26
- await write(dir, 'docker-compose.yml', generateDockerCompose());
50
+ const args = parseArgs(process.argv.slice(2));
51
+ if (args.help) {
52
+ printHelp();
53
+ process.exit(0);
27
54
  }
28
- // Sample migration
29
- await write(dir, 'migrations/001_sample.sql', `CREATE TABLE IF NOT EXISTS items (
30
- id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
31
- user_id UUID NOT NULL REFERENCES auth_users(id) ON DELETE CASCADE,
32
- title TEXT NOT NULL,
33
- created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
34
- );
35
- `);
36
- // Sample test
37
- await write(dir, 'tests/unit/sample.test.ts', `import { describe, it, expect } from 'vitest';
38
- import { createInMemoryDatabase } from '@objectifthunes/sandstone-sdk/testing';
39
-
40
- describe('sample', () => {
41
- it('in-memory database works', async () => {
42
- const db = createInMemoryDatabase();
43
- await db.query('CREATE TABLE test (id TEXT)');
44
- await db.query('INSERT INTO "test" ("id") VALUES ($1) RETURNING *', ['1']);
45
- const result = await db.query('SELECT * FROM "test"');
46
- expect(result.rows.length).toBe(1);
47
- });
48
- });
49
- `);
50
- // Vitest config
51
- await write(dir, 'vitest.config.ts', `import { defineConfig } from 'vitest/config';
52
-
53
- export default defineConfig({
54
- test: {
55
- globals: true,
56
- include: ['tests/unit/**/*.test.ts'],
57
- },
58
- });
59
- `);
60
- console.log('\n Done! Next steps:\n');
61
- console.log(` cd ${config.name}`);
62
- console.log(' cp .env.example .env');
63
- console.log(' pnpm install');
64
- console.log(' pnpm test');
65
- console.log('');
55
+ const projectName = args.name ?? 'my-sandstone-app';
56
+ const targetDir = resolve(process.cwd(), projectName);
57
+ console.log();
58
+ console.log(` Creating a new Sandstone project in ${targetDir}`);
59
+ console.log();
60
+ let config;
61
+ if (args.yes) {
62
+ // Non-interactive mode
63
+ if (args.template) {
64
+ if (!isPresetName(args.template)) {
65
+ console.error(` Error: Unknown template "${args.template}". Available: saas, api, minimal`);
66
+ process.exit(1);
67
+ }
68
+ config = { name: projectName, ...PRESETS[args.template] };
69
+ console.log(` Using preset: ${args.template}`);
70
+ }
71
+ else {
72
+ config = { name: projectName, ...PRESETS.minimal };
73
+ console.log(` Using default preset: minimal`);
74
+ }
75
+ console.log();
76
+ }
77
+ else {
78
+ // Interactive mode
79
+ const result = await runPrompts(projectName);
80
+ if (!result) {
81
+ console.log(' Cancelled.');
82
+ process.exit(0);
83
+ }
84
+ config = result;
85
+ console.log();
86
+ }
87
+ // Create target directory
88
+ if (existsSync(targetDir)) {
89
+ console.error(` Error: Directory "${projectName}" already exists.`);
90
+ process.exit(1);
91
+ }
92
+ await mkdir(targetDir, { recursive: true });
93
+ // Generate project files
94
+ const files = await generateProject(targetDir, config);
95
+ // Print summary
96
+ console.log(` Scaffolded ${files.length} files:`);
97
+ console.log();
98
+ for (const file of files) {
99
+ console.log(` ${file}`);
100
+ }
101
+ console.log();
102
+ console.log(` Next steps:`);
103
+ console.log();
104
+ console.log(` cd ${projectName}`);
105
+ console.log(` cp .env.example .env`);
106
+ console.log(` npm install`);
107
+ console.log(` docker compose up -d`);
108
+ console.log(` npm run db:migrate`);
109
+ console.log(` npm run dev`);
110
+ console.log();
66
111
  }
67
112
  main().catch((err) => {
68
- console.error(err);
113
+ console.error('Error:', err instanceof Error ? err.message : String(err));
69
114
  process.exit(1);
70
115
  });
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export type Framework = 'express' | 'hono' | 'fastify';
2
+ export type Database = 'pg' | 'supabase';
3
+ export type AuthStrategy = 'otp' | 'password+otp' | 'oauth+otp' | 'all' | null;
4
+ export type OAuthProviderChoice = 'google' | 'github' | 'apple' | 'discord';
5
+ export type EmailProvider = 'resend' | 'nodemailer' | null;
6
+ export type PaymentProvider = 'stripe' | 'revenuecat' | 'paddle' | 'lemonsqueezy' | null;
7
+ export type StorageProvider = 's3' | 'r2' | 'local' | null;
8
+ export type CacheProvider = 'upstash' | 'redis' | 'memory' | null;
9
+ export type SmsProvider = 'twilio' | 'sns' | null;
10
+ export type PushProvider = 'fcm' | 'apns' | 'both' | null;
11
+ export type RealtimeProvider = 'socketio' | 'pusher' | 'ably' | null;
12
+ export type SearchProvider = 'meilisearch' | 'typesense' | 'pg-search' | null;
13
+ export type QueueProvider = 'bullmq' | 'pg-boss' | null;
14
+ export type FlagProvider = 'pg-flags' | 'env-flags' | 'launchdarkly' | null;
15
+ export type AuthzProvider = 'pg-authz' | 'casbin' | null;
16
+ export interface ProjectConfig {
17
+ name: string;
18
+ framework: Framework;
19
+ database: Database;
20
+ auth: AuthStrategy;
21
+ oauthProviders: OAuthProviderChoice[];
22
+ email: EmailProvider;
23
+ payments: PaymentProvider;
24
+ storage: StorageProvider;
25
+ cache: CacheProvider;
26
+ sms: SmsProvider;
27
+ push: PushProvider;
28
+ realtime: RealtimeProvider;
29
+ search: SearchProvider;
30
+ queue: QueueProvider;
31
+ scheduler: boolean;
32
+ flags: FlagProvider;
33
+ tracing: boolean;
34
+ audit: boolean;
35
+ authz: AuthzProvider;
36
+ graphql: boolean;
37
+ dashboard: boolean;
38
+ tests: boolean;
39
+ e2e: boolean;
40
+ devMode?: boolean;
41
+ }
42
+ export type PresetName = 'saas' | 'api' | 'minimal' | 'prototype';
43
+ export declare const PRESETS: Record<PresetName, Omit<ProjectConfig, 'name'>>;
44
+ export declare function isPresetName(value: string): value is PresetName;
45
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../src/presets.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,UAAU,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,cAAc,GAAG,WAAW,GAAG,KAAK,GAAG,IAAI,CAAC;AAC/E,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,YAAY,GAAG,IAAI,CAAC;AAC3D,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,YAAY,GAAG,QAAQ,GAAG,cAAc,GAAG,IAAI,CAAC;AACzF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC;AAC3D,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAC1D,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GAAG,IAAI,CAAC;AACrE,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,WAAW,GAAG,IAAI,CAAC;AAC9E,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;AACxD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,IAAI,CAAC;AAC5E,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,GAAG,IAAI,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,KAAK,EAAE,aAAa,CAAC;IACrB,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,aAAa,CAAC;IACrB,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,YAAY,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,WAAW,CAAC;AAElE,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,CAAC,CAoGnE,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D"}