@compilr-dev/factory 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,8 @@ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev
7
7
  export function createListToolkitsTool(registry) {
8
8
  return defineTool({
9
9
  name: 'factory_list_toolkits',
10
- description: 'List all available factory toolkits. Each toolkit generates a different type of application (e.g. React+Node, Next.js, etc.).',
10
+ // Original: 'List all available factory toolkits. Each toolkit generates a different type of application (e.g. React+Node, Next.js, etc.).'
11
+ description: 'List all available factory toolkits.',
11
12
  inputSchema: {
12
13
  type: 'object',
13
14
  properties: {},
@@ -28,7 +28,9 @@ import { reactNodeToolkit } from '../toolkits/react-node/index.js';
28
28
  import { nextPrismaToolkit } from '../toolkits/next-prisma/index.js';
29
29
  import { staticLandingToolkit } from '../toolkits/static-landing/index.js';
30
30
  import { reactFastapiToolkit } from '../toolkits/react-fastapi/index.js';
31
+ import { reactGoToolkit } from '../toolkits/react-go/index.js';
31
32
  defaultRegistry.register(reactNodeToolkit);
32
33
  defaultRegistry.register(nextPrismaToolkit);
33
34
  defaultRegistry.register(staticLandingToolkit);
34
35
  defaultRegistry.register(reactFastapiToolkit);
36
+ defaultRegistry.register(reactGoToolkit);
@@ -11,13 +11,15 @@ import { writeFactoryFiles } from './file-writer.js';
11
11
  export function createScaffoldTool(config) {
12
12
  return defineTool({
13
13
  name: 'factory_scaffold',
14
- description: 'Generate a full application from the Application Model. Reads the model, validates it, runs the toolkit generators, and writes files to disk. Use dry_run: true to preview without writing.',
14
+ // Original: 'Generate a full application from the Application Model. Reads the model, validates it, runs the toolkit generators, and writes files to disk. Use dry_run: true to preview without writing.'
15
+ description: 'Generate a full application from the Application Model and write files to disk.',
15
16
  inputSchema: {
16
17
  type: 'object',
17
18
  properties: {
18
19
  dry_run: {
19
20
  type: 'boolean',
20
- description: 'Preview generated files without writing to disk. Default: false.',
21
+ description: 'Preview generated files without writing to disk.',
22
+ default: false,
21
23
  },
22
24
  output_dir: {
23
25
  type: 'string',
package/dist/index.d.ts CHANGED
@@ -22,4 +22,5 @@ export { reactNodeToolkit } from './toolkits/react-node/index.js';
22
22
  export { nextPrismaToolkit } from './toolkits/next-prisma/index.js';
23
23
  export { staticLandingToolkit } from './toolkits/static-landing/index.js';
24
24
  export { reactFastapiToolkit } from './toolkits/react-fastapi/index.js';
25
+ export { reactGoToolkit } from './toolkits/react-go/index.js';
25
26
  export { factoryScaffoldSkill, factorySkills } from './factory/skill.js';
package/dist/index.js CHANGED
@@ -27,5 +27,7 @@ export { nextPrismaToolkit } from './toolkits/next-prisma/index.js';
27
27
  export { staticLandingToolkit } from './toolkits/static-landing/index.js';
28
28
  // React + FastAPI Toolkit
29
29
  export { reactFastapiToolkit } from './toolkits/react-fastapi/index.js';
30
+ // React + Go Toolkit
31
+ export { reactGoToolkit } from './toolkits/react-go/index.js';
30
32
  // Factory skill (Phase 5)
31
33
  export { factoryScaffoldSkill, factorySkills } from './factory/skill.js';
@@ -45,7 +45,8 @@ function createSummary(model) {
45
45
  function createAppModelGetTool(config) {
46
46
  return defineTool({
47
47
  name: 'app_model_get',
48
- description: 'Read the Application Model for the current project. Supports scoped reads: no params = full model, scope="summary" for overview, scope="identity"/"features"/"layout"/"theme"/"techStack" for sections, entity="Name" for a single entity.',
48
+ // Original: 'Read the Application Model for the current project. Supports scoped reads: no params = full model, scope="summary" for overview, scope="identity"/"features"/"layout"/"theme"/"techStack" for sections, entity="Name" for a single entity.'
49
+ description: 'Read the Application Model. Supports scoped reads by section or entity name.',
49
50
  inputSchema: {
50
51
  type: 'object',
51
52
  properties: {
@@ -112,18 +113,8 @@ function createAppModelGetTool(config) {
112
113
  function createAppModelUpdateTool(config) {
113
114
  return defineTool({
114
115
  name: 'app_model_update',
115
- description: `Apply a semantic operation to the Application Model. If no model exists, one is created automatically.
116
-
117
- EXAMPLES:
118
- - updateIdentity: { "op": "updateIdentity", "updates": { "name": "My App", "description": "A task manager" } }
119
- - updateTechStack: { "op": "updateTechStack", "updates": { "toolkit": "react-node" } }
120
- - addEntity: { "op": "addEntity", "entity": { "name": "Task", "pluralName": "Tasks", "icon": "📋", "fields": [{ "name": "title", "label": "Title", "type": "string", "required": true }], "views": ["list", "detail", "card"], "relationships": [] } }
121
- - addField: { "op": "addField", "entity": "Task", "field": { "name": "dueDate", "label": "Due Date", "type": "date", "required": false } }
122
- - addRelationship: { "op": "addRelationship", "entity": "Task", "relationship": { "type": "belongsTo", "target": "Project" } }
123
- - updateFeatures: { "op": "updateFeatures", "updates": { "dashboard": true, "darkMode": true } }
124
- - updateTheme: { "op": "updateTheme", "updates": { "primaryColor": "#1976D2" } }
125
- - updateLayout: { "op": "updateLayout", "updates": { "shell": "sidebar-header" } }
126
- - removeEntity: { "op": "removeEntity", "entity": "Task", "force": true }`,
116
+ // Original: long description with 9 inline JSON examples (~1200 chars)
117
+ description: 'Apply a semantic operation to the Application Model. Auto-creates if none exists.',
127
118
  inputSchema: {
128
119
  type: 'object',
129
120
  properties: {
@@ -402,7 +393,8 @@ function buildOperation(input) {
402
393
  function createAppModelValidateTool(config) {
403
394
  return defineTool({
404
395
  name: 'app_model_validate',
405
- description: 'Validate the Application Model for the current project. Returns validation errors or confirms the model is valid.',
396
+ // Original: 'Validate the Application Model for the current project. Returns validation errors or confirms the model is valid.'
397
+ description: 'Validate the Application Model. Returns errors or confirms validity.',
406
398
  inputSchema: {
407
399
  type: 'object',
408
400
  properties: {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * React+Go Toolkit — API Generator
3
+ *
4
+ * Generates: server/main.go, server/models.go,
5
+ * server/data/{entity}.go, server/handlers/{entity}.go
6
+ *
7
+ * All files use `package main` so `go run ./server` compiles everything.
8
+ * Go 1.22+ net/http stdlib with method-based routing.
9
+ */
10
+ import type { ApplicationModel } from '../../model/types.js';
11
+ import type { FactoryFile } from '../types.js';
12
+ export declare function generateApiFiles(model: ApplicationModel): FactoryFile[];
@@ -0,0 +1,523 @@
1
+ /**
2
+ * React+Go Toolkit — API Generator
3
+ *
4
+ * Generates: server/main.go, server/models.go,
5
+ * server/data/{entity}.go, server/handlers/{entity}.go
6
+ *
7
+ * All files use `package main` so `go run ./server` compiles everything.
8
+ * Go 1.22+ net/http stdlib with method-based routing.
9
+ */
10
+ import { toCamelCase, toKebabCase, toPascalCase } from '../../model/naming.js';
11
+ import { goType, fkFieldName, belongsToRels, hasManyRels } from './helpers.js';
12
+ import { toSnakeCase } from './helpers.js';
13
+ import { generateSeedData } from './seed.js';
14
+ export function generateApiFiles(model) {
15
+ return [
16
+ generateServerMain(model),
17
+ generateModels(model),
18
+ ...model.entities.flatMap((entity) => [
19
+ generateDataStore(model, entity),
20
+ generateHandlers(model, entity),
21
+ ]),
22
+ ];
23
+ }
24
+ // =============================================================================
25
+ // Server Entry Point — server/main.go
26
+ // =============================================================================
27
+ function generateServerMain(model) {
28
+ const handlerRegistrations = model.entities
29
+ .map((e) => `\tRegister${e.name}Handlers(mux)`)
30
+ .join('\n');
31
+ return {
32
+ path: 'server/main.go',
33
+ content: `package main
34
+
35
+ import (
36
+ \t"fmt"
37
+ \t"log"
38
+ \t"net/http"
39
+ \t"os"
40
+ )
41
+
42
+ func corsMiddleware(next http.Handler) http.Handler {
43
+ \treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
44
+ \t\tw.Header().Set("Access-Control-Allow-Origin", "*")
45
+ \t\tw.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
46
+ \t\tw.Header().Set("Access-Control-Allow-Headers", "Content-Type")
47
+ \t\tif r.Method == http.MethodOptions {
48
+ \t\t\tw.WriteHeader(http.StatusNoContent)
49
+ \t\t\treturn
50
+ \t\t}
51
+ \t\tnext.ServeHTTP(w, r)
52
+ \t})
53
+ }
54
+
55
+ func main() {
56
+ \tmux := http.NewServeMux()
57
+
58
+ ${handlerRegistrations}
59
+
60
+ \tport := os.Getenv("PORT")
61
+ \tif port == "" {
62
+ \t\tport = "8080"
63
+ \t}
64
+
65
+ \tfmt.Printf("Server listening on :%s\\n", port)
66
+ \tlog.Fatal(http.ListenAndServe(":"+port, corsMiddleware(mux)))
67
+ }
68
+ `,
69
+ };
70
+ }
71
+ // =============================================================================
72
+ // Models — server/models.go
73
+ // =============================================================================
74
+ function generateModels(model) {
75
+ const modelBlocks = [];
76
+ for (const entity of model.entities) {
77
+ const btoRels = belongsToRels(entity);
78
+ // Main struct (with json tags)
79
+ const structFields = [];
80
+ structFields.push(`\tID int \`json:"id"\``);
81
+ for (const field of entity.fields) {
82
+ const goT = goType(field);
83
+ const jsonTag = field.name;
84
+ const goFieldName = toPascalCase(field.name);
85
+ structFields.push(`\t${goFieldName} ${goT} \`json:"${jsonTag}"\``);
86
+ }
87
+ for (const rel of btoRels) {
88
+ const fk = fkFieldName(rel);
89
+ const goFieldName = toPascalCase(fk);
90
+ structFields.push(`\t${goFieldName} int \`json:"${fk}"\``);
91
+ }
92
+ structFields.push(`\tCreatedAt string \`json:"createdAt"\``);
93
+ structFields.push(`\tUpdatedAt string \`json:"updatedAt"\``);
94
+ // Input struct (for create/update — pointer fields for optional update detection)
95
+ const inputFields = [];
96
+ for (const field of entity.fields) {
97
+ const goT = goType(field);
98
+ const jsonTag = field.name;
99
+ const goFieldName = toPascalCase(field.name);
100
+ inputFields.push(`\t${goFieldName} *${goT} \`json:"${jsonTag}"\``);
101
+ }
102
+ for (const rel of btoRels) {
103
+ const fk = fkFieldName(rel);
104
+ const goFieldName = toPascalCase(fk);
105
+ inputFields.push(`\t${goFieldName} *int \`json:"${fk}"\``);
106
+ }
107
+ modelBlocks.push(`type ${entity.name} struct {
108
+ ${structFields.join('\n')}
109
+ }
110
+
111
+ type ${entity.name}Input struct {
112
+ ${inputFields.join('\n')}
113
+ }`);
114
+ }
115
+ return {
116
+ path: 'server/models.go',
117
+ content: `package main
118
+ ${modelBlocks.length > 0 ? '\n' + modelBlocks.join('\n\n') + '\n' : ''}`,
119
+ };
120
+ }
121
+ // =============================================================================
122
+ // Data Store — server/data/{entity}.go
123
+ // =============================================================================
124
+ function generateDataStore(model, entity) {
125
+ const lowerName = toCamelCase(entity.pluralName);
126
+ const snakeName = toSnakeCase(entity.pluralName);
127
+ // Collect searchable fields (string + enum types)
128
+ const searchableFields = entity.fields
129
+ .filter((f) => f.type === 'string' || f.type === 'enum')
130
+ .map((f) => toPascalCase(f.name));
131
+ let searchLogic;
132
+ if (searchableFields.length > 0) {
133
+ const fieldChecks = searchableFields
134
+ .map((f) => `\t\t\t\tstrings.Contains(strings.ToLower(fmt.Sprintf("%v", item.${f})), q)`)
135
+ .join(' ||\n');
136
+ searchLogic = `\t\tq := strings.ToLower(search)
137
+ \t\tvar filtered []${entity.name}
138
+ \t\tfor _, item := range result {
139
+ \t\t\tif ${fieldChecks.trim()} {
140
+ \t\t\t\tfiltered = append(filtered, item)
141
+ \t\t\t}
142
+ \t\t}
143
+ \t\tresult = filtered`;
144
+ }
145
+ else {
146
+ searchLogic = `\t\tq := strings.ToLower(search)
147
+ \t\tvar filtered []${entity.name}
148
+ \t\tfor _, item := range result {
149
+ \t\t\tif strings.Contains(strings.ToLower(fmt.Sprintf("%v", item)), q) {
150
+ \t\t\t\tfiltered = append(filtered, item)
151
+ \t\t\t}
152
+ \t\t}
153
+ \t\tresult = filtered`;
154
+ }
155
+ // Filter logic — check field values via reflect-like approach (simple string comparison)
156
+ const filterableFields = entity.fields.map((f) => ({
157
+ jsonName: f.name,
158
+ goName: toPascalCase(f.name),
159
+ }));
160
+ const filterChecks = filterableFields
161
+ .map((f) => `\t\t\t\tcase "${f.jsonName}":\n\t\t\t\t\tif fmt.Sprintf("%v", item.${f.goName}) != value {\n\t\t\t\t\t\tmatch = false\n\t\t\t\t\t}`)
162
+ .join('\n');
163
+ const seedData = generateSeedData(model, entity);
164
+ return {
165
+ path: `server/data/${snakeName}.go`,
166
+ content: `package main
167
+
168
+ import (
169
+ \t"fmt"
170
+ \t"strings"
171
+ \t"sync"
172
+ \t"time"
173
+ )
174
+
175
+ ${seedData}
176
+
177
+ type ${entity.name}Store struct {
178
+ \tmu sync.Mutex
179
+ \titems []${entity.name}
180
+ \tnextID int
181
+ }
182
+
183
+ var ${lowerName}Store = &${entity.name}Store{
184
+ \titems: make([]${entity.name}, len(seedData)),
185
+ \tnextID: len(seedData) + 1,
186
+ }
187
+
188
+ func init() {
189
+ \tcopy(${lowerName}Store.items, seedData)
190
+ }
191
+
192
+ func (s *${entity.name}Store) GetAll() []${entity.name} {
193
+ \ts.mu.Lock()
194
+ \tdefer s.mu.Unlock()
195
+ \tresult := make([]${entity.name}, len(s.items))
196
+ \tcopy(result, s.items)
197
+ \treturn result
198
+ }
199
+
200
+ func (s *${entity.name}Store) GetByID(id int) (${entity.name}, bool) {
201
+ \ts.mu.Lock()
202
+ \tdefer s.mu.Unlock()
203
+ \tfor _, item := range s.items {
204
+ \t\tif item.ID == id {
205
+ \t\t\treturn item, true
206
+ \t\t}
207
+ \t}
208
+ \treturn ${entity.name}{}, false
209
+ }
210
+
211
+ func (s *${entity.name}Store) Query(search string, filters map[string]string) ([]${entity.name}, int) {
212
+ \ts.mu.Lock()
213
+ \tdefer s.mu.Unlock()
214
+ \tresult := make([]${entity.name}, len(s.items))
215
+ \tcopy(result, s.items)
216
+
217
+ \tif search != "" {
218
+ ${searchLogic}
219
+ \t}
220
+
221
+ \tif len(filters) > 0 {
222
+ \t\tvar filtered []${entity.name}
223
+ \t\tfor _, item := range result {
224
+ \t\t\tmatch := true
225
+ \t\t\tfor field, value := range filters {
226
+ \t\t\t\tswitch field {
227
+ ${filterChecks}
228
+ \t\t\t\t}
229
+ \t\t\t}
230
+ \t\t\tif match {
231
+ \t\t\t\tfiltered = append(filtered, item)
232
+ \t\t\t}
233
+ \t\t}
234
+ \t\tresult = filtered
235
+ \t}
236
+
237
+ \treturn result, len(result)
238
+ }
239
+
240
+ func (s *${entity.name}Store) Create(input ${entity.name}Input) ${entity.name} {
241
+ \ts.mu.Lock()
242
+ \tdefer s.mu.Unlock()
243
+ \tnow := time.Now().UTC().Format(time.RFC3339)
244
+ \titem := ${entity.name}{
245
+ \t\tID: s.nextID,
246
+ \t\tCreatedAt: now,
247
+ \t\tUpdatedAt: now,
248
+ \t}
249
+ \ts.nextID++
250
+ ${entity.fields.map((f) => `\tif input.${toPascalCase(f.name)} != nil {\n\t\titem.${toPascalCase(f.name)} = *input.${toPascalCase(f.name)}\n\t}`).join('\n')}
251
+ ${belongsToRels(entity)
252
+ .map((rel) => {
253
+ const fk = fkFieldName(rel);
254
+ const goField = toPascalCase(fk);
255
+ return `\tif input.${goField} != nil {\n\t\titem.${goField} = *input.${goField}\n\t}`;
256
+ })
257
+ .join('\n')}
258
+ \ts.items = append(s.items, item)
259
+ \treturn item
260
+ }
261
+
262
+ func (s *${entity.name}Store) Update(id int, input ${entity.name}Input) (${entity.name}, bool) {
263
+ \ts.mu.Lock()
264
+ \tdefer s.mu.Unlock()
265
+ \tfor i, item := range s.items {
266
+ \t\tif item.ID == id {
267
+ \t\t\tnow := time.Now().UTC().Format(time.RFC3339)
268
+ ${entity.fields.map((f) => `\t\t\tif input.${toPascalCase(f.name)} != nil {\n\t\t\t\ts.items[i].${toPascalCase(f.name)} = *input.${toPascalCase(f.name)}\n\t\t\t}`).join('\n')}
269
+ ${belongsToRels(entity)
270
+ .map((rel) => {
271
+ const fk = fkFieldName(rel);
272
+ const goField = toPascalCase(fk);
273
+ return `\t\t\tif input.${goField} != nil {\n\t\t\t\ts.items[i].${goField} = *input.${goField}\n\t\t\t}`;
274
+ })
275
+ .join('\n')}
276
+ \t\t\ts.items[i].UpdatedAt = now
277
+ \t\t\treturn s.items[i], true
278
+ \t\t}
279
+ \t}
280
+ \treturn ${entity.name}{}, false
281
+ }
282
+
283
+ func (s *${entity.name}Store) Remove(id int) bool {
284
+ \ts.mu.Lock()
285
+ \tdefer s.mu.Unlock()
286
+ \tfor i, item := range s.items {
287
+ \t\tif item.ID == id {
288
+ \t\t\ts.items = append(s.items[:i], s.items[i+1:]...)
289
+ \t\t\treturn true
290
+ \t\t}
291
+ \t}
292
+ \treturn false
293
+ }
294
+ `,
295
+ };
296
+ }
297
+ // =============================================================================
298
+ // Handlers — server/handlers/{entity}.go
299
+ // =============================================================================
300
+ function generateHandlers(model, entity) {
301
+ const snakeName = toSnakeCase(entity.pluralName);
302
+ const lowerName = toCamelCase(entity.pluralName);
303
+ const apiRoute = '/api/' + toKebabCase(entity.pluralName).toLowerCase();
304
+ const btoRels = belongsToRels(entity);
305
+ const hmRels = hasManyRels(entity);
306
+ // Populate function for belongsTo
307
+ let populateType = '';
308
+ let populateFunc = '';
309
+ if (btoRels.length > 0) {
310
+ const populateFields = [];
311
+ // Original entity fields
312
+ populateFields.push(`\t${entity.name}`);
313
+ for (const rel of btoRels) {
314
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
315
+ if (!targetEntity)
316
+ continue;
317
+ const targetVar = rel.target.charAt(0).toLowerCase() + rel.target.slice(1);
318
+ populateFields.push(`\t${toPascalCase(targetVar)} *${rel.target} \`json:"${targetVar}"\``);
319
+ }
320
+ populateType = `\ntype ${entity.name}WithRels struct {
321
+ ${populateFields.join('\n')}
322
+ }\n`;
323
+ const populateBody = btoRels
324
+ .map((rel) => {
325
+ const fk = fkFieldName(rel);
326
+ const goField = toPascalCase(fk);
327
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
328
+ if (!targetEntity)
329
+ return '';
330
+ const targetVar = rel.target.charAt(0).toLowerCase() + rel.target.slice(1);
331
+ const targetStoreName = toCamelCase(targetEntity.pluralName);
332
+ return `\tif item.${goField} != 0 {
333
+ \t\tif found, ok := ${targetStoreName}Store.GetByID(item.${goField}); ok {
334
+ \t\t\tresult.${toPascalCase(targetVar)} = &found
335
+ \t\t}
336
+ \t}`;
337
+ })
338
+ .filter(Boolean)
339
+ .join('\n');
340
+ populateFunc = `
341
+ func populate${entity.name}(item ${entity.name}) ${entity.name}WithRels {
342
+ \tresult := ${entity.name}WithRels{${entity.name}: item}
343
+ ${populateBody}
344
+ \treturn result
345
+ }
346
+ `;
347
+ }
348
+ // hasMany include logic
349
+ let includeLogic = '';
350
+ let includeType = '';
351
+ if (hmRels.length > 0) {
352
+ const includeFields = [];
353
+ if (btoRels.length > 0) {
354
+ includeFields.push(`\t${entity.name}WithRels`);
355
+ }
356
+ else {
357
+ includeFields.push(`\t${entity.name}`);
358
+ }
359
+ for (const rel of hmRels) {
360
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
361
+ if (!targetEntity)
362
+ continue;
363
+ const targetVar = toCamelCase(targetEntity.pluralName);
364
+ includeFields.push(`\t${toPascalCase(targetVar)} []${rel.target} \`json:"${targetVar},omitempty"\``);
365
+ }
366
+ includeType = `\ntype ${entity.name}WithIncludes struct {
367
+ ${includeFields.join('\n')}
368
+ }\n`;
369
+ const includeChecks = hmRels
370
+ .map((rel) => {
371
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
372
+ if (!targetEntity)
373
+ return '';
374
+ const targetVar = toCamelCase(targetEntity.pluralName);
375
+ const targetStoreName = toCamelCase(targetEntity.pluralName);
376
+ const fkRel = targetEntity.relationships.find((r) => r.type === 'belongsTo' && r.target === entity.name) ?? { type: 'belongsTo', target: entity.name };
377
+ const fk = fkFieldName(fkRel);
378
+ const goFk = toPascalCase(fk);
379
+ return `\t\tfor _, inc := range includes {
380
+ \t\t\tif inc == "${targetVar}" {
381
+ \t\t\t\tallItems := ${targetStoreName}Store.GetAll()
382
+ \t\t\t\tvar related []${rel.target}
383
+ \t\t\t\tfor _, r := range allItems {
384
+ \t\t\t\t\tif r.${goFk} == item.ID {
385
+ \t\t\t\t\t\trelated = append(related, r)
386
+ \t\t\t\t\t}
387
+ \t\t\t\t}
388
+ \t\t\t\tresult.${toPascalCase(targetVar)} = related
389
+ \t\t\t}
390
+ \t\t}`;
391
+ })
392
+ .filter(Boolean)
393
+ .join('\n');
394
+ includeLogic = includeChecks;
395
+ }
396
+ // GET list handler
397
+ const listReturnExpr = btoRels.length > 0
398
+ ? `\tpopulated := make([]${entity.name}WithRels, len(items))
399
+ \tfor i, item := range items {
400
+ \t\tpopulated[i] = populate${entity.name}(item)
401
+ \t}
402
+ \tjson.NewEncoder(w).Encode(map[string]any{"data": populated, "total": total})`
403
+ : `\tjson.NewEncoder(w).Encode(map[string]any{"data": items, "total": total})`;
404
+ // GET by ID handler
405
+ let getByIDBody;
406
+ if (hmRels.length > 0) {
407
+ const baseExpr = btoRels.length > 0 ? `populate${entity.name}(item)` : `item`;
408
+ const innerType = btoRels.length > 0 ? `${entity.name}WithRels` : entity.name;
409
+ getByIDBody = `\titem, ok := ${lowerName}Store.GetByID(id)
410
+ \tif !ok {
411
+ \t\thttp.Error(w, \`{"error":"not found"}\`, http.StatusNotFound)
412
+ \t\treturn
413
+ \t}
414
+ \tincludeParam := r.URL.Query().Get("include")
415
+ \tincludes := strings.Split(includeParam, ",")
416
+ \tresult := ${entity.name}WithIncludes{${innerType}: ${baseExpr}}
417
+ ${includeLogic}
418
+ \tw.Header().Set("Content-Type", "application/json")
419
+ \tjson.NewEncoder(w).Encode(result)`;
420
+ }
421
+ else if (btoRels.length > 0) {
422
+ getByIDBody = `\titem, ok := ${lowerName}Store.GetByID(id)
423
+ \tif !ok {
424
+ \t\thttp.Error(w, \`{"error":"not found"}\`, http.StatusNotFound)
425
+ \t\treturn
426
+ \t}
427
+ \tw.Header().Set("Content-Type", "application/json")
428
+ \tjson.NewEncoder(w).Encode(populate${entity.name}(item))`;
429
+ }
430
+ else {
431
+ getByIDBody = `\titem, ok := ${lowerName}Store.GetByID(id)
432
+ \tif !ok {
433
+ \t\thttp.Error(w, \`{"error":"not found"}\`, http.StatusNotFound)
434
+ \t\treturn
435
+ \t}
436
+ \tw.Header().Set("Content-Type", "application/json")
437
+ \tjson.NewEncoder(w).Encode(item)`;
438
+ }
439
+ // Determine imports needed
440
+ const needsStrings = hmRels.length > 0;
441
+ return {
442
+ path: `server/handlers/${snakeName}.go`,
443
+ content: `package main
444
+
445
+ import (
446
+ \t"encoding/json"
447
+ \t"net/http"
448
+ \t"strconv"${needsStrings ? '\n\t"strings"' : ''}
449
+ )
450
+ ${populateType}${includeType}${populateFunc}
451
+ func Register${entity.name}Handlers(mux *http.ServeMux) {
452
+ \tmux.HandleFunc("GET ${apiRoute}", func(w http.ResponseWriter, r *http.Request) {
453
+ \t\tsearch := r.URL.Query().Get("search")
454
+ \t\tfilters := make(map[string]string)
455
+ \t\tfor key, values := range r.URL.Query() {
456
+ \t\t\tif len(key) > 7 && key[:7] == "filter[" && key[len(key)-1] == ']' {
457
+ \t\t\t\tfield := key[7 : len(key)-1]
458
+ \t\t\t\tfilters[field] = values[0]
459
+ \t\t\t}
460
+ \t\t}
461
+ \t\titems, total := ${lowerName}Store.Query(search, filters)
462
+ \t\tw.Header().Set("Content-Type", "application/json")
463
+ ${listReturnExpr}
464
+ \t})
465
+
466
+ \tmux.HandleFunc("GET ${apiRoute}/{id}", func(w http.ResponseWriter, r *http.Request) {
467
+ \t\tid, err := strconv.Atoi(r.PathValue("id"))
468
+ \t\tif err != nil {
469
+ \t\t\thttp.Error(w, \`{"error":"invalid id"}\`, http.StatusBadRequest)
470
+ \t\t\treturn
471
+ \t\t}
472
+ ${getByIDBody}
473
+ \t})
474
+
475
+ \tmux.HandleFunc("POST ${apiRoute}", func(w http.ResponseWriter, r *http.Request) {
476
+ \t\tvar input ${entity.name}Input
477
+ \t\tif err := json.NewDecoder(r.Body).Decode(&input); err != nil {
478
+ \t\t\thttp.Error(w, \`{"error":"invalid json"}\`, http.StatusBadRequest)
479
+ \t\t\treturn
480
+ \t\t}
481
+ \t\titem := ${lowerName}Store.Create(input)
482
+ \t\tw.Header().Set("Content-Type", "application/json")
483
+ \t\tw.WriteHeader(http.StatusCreated)
484
+ \t\tjson.NewEncoder(w).Encode(item)
485
+ \t})
486
+
487
+ \tmux.HandleFunc("PUT ${apiRoute}/{id}", func(w http.ResponseWriter, r *http.Request) {
488
+ \t\tid, err := strconv.Atoi(r.PathValue("id"))
489
+ \t\tif err != nil {
490
+ \t\t\thttp.Error(w, \`{"error":"invalid id"}\`, http.StatusBadRequest)
491
+ \t\t\treturn
492
+ \t\t}
493
+ \t\tvar input ${entity.name}Input
494
+ \t\tif err := json.NewDecoder(r.Body).Decode(&input); err != nil {
495
+ \t\t\thttp.Error(w, \`{"error":"invalid json"}\`, http.StatusBadRequest)
496
+ \t\t\treturn
497
+ \t\t}
498
+ \t\titem, ok := ${lowerName}Store.Update(id, input)
499
+ \t\tif !ok {
500
+ \t\t\thttp.Error(w, \`{"error":"not found"}\`, http.StatusNotFound)
501
+ \t\t\treturn
502
+ \t\t}
503
+ \t\tw.Header().Set("Content-Type", "application/json")
504
+ \t\tjson.NewEncoder(w).Encode(item)
505
+ \t})
506
+
507
+ \tmux.HandleFunc("DELETE ${apiRoute}/{id}", func(w http.ResponseWriter, r *http.Request) {
508
+ \t\tid, err := strconv.Atoi(r.PathValue("id"))
509
+ \t\tif err != nil {
510
+ \t\t\thttp.Error(w, \`{"error":"invalid id"}\`, http.StatusBadRequest)
511
+ \t\t\treturn
512
+ \t\t}
513
+ \t\tok := ${lowerName}Store.Remove(id)
514
+ \t\tif !ok {
515
+ \t\t\thttp.Error(w, \`{"error":"not found"}\`, http.StatusNotFound)
516
+ \t\t\treturn
517
+ \t\t}
518
+ \t\tw.WriteHeader(http.StatusNoContent)
519
+ \t})
520
+ }
521
+ `,
522
+ };
523
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * React+Go Toolkit — Configuration Files Generator
3
+ *
4
+ * Generates: package.json, vite.config.ts, tailwind.config.js,
5
+ * tsconfig.json, postcss.config.js, go.mod
6
+ */
7
+ import type { ApplicationModel } from '../../model/types.js';
8
+ import type { FactoryFile } from '../types.js';
9
+ export declare function generateConfigFiles(model: ApplicationModel): FactoryFile[];
@@ -0,0 +1,129 @@
1
+ /**
2
+ * React+Go Toolkit — Configuration Files Generator
3
+ *
4
+ * Generates: package.json, vite.config.ts, tailwind.config.js,
5
+ * tsconfig.json, postcss.config.js, go.mod
6
+ */
7
+ import { toKebabCase } from '../../model/naming.js';
8
+ import { generateColorShades } from '../shared/color-utils.js';
9
+ export function generateConfigFiles(model) {
10
+ const appSlug = toKebabCase(model.identity.name);
11
+ return [
12
+ generatePackageJson(appSlug),
13
+ generateViteConfig(),
14
+ generateTailwindConfig(model),
15
+ generateTsConfig(),
16
+ generatePostCssConfig(),
17
+ generateGoMod(appSlug),
18
+ ];
19
+ }
20
+ function generatePackageJson(appSlug) {
21
+ const pkg = {
22
+ name: appSlug,
23
+ version: '0.1.0',
24
+ private: true,
25
+ type: 'module',
26
+ scripts: {
27
+ dev: 'concurrently "vite" "go run ./server"',
28
+ build: 'vite build',
29
+ preview: 'vite preview',
30
+ server: 'go run ./server',
31
+ },
32
+ dependencies: {
33
+ react: '^18.3.1',
34
+ 'react-dom': '^18.3.1',
35
+ 'react-router-dom': '^6.28.0',
36
+ },
37
+ devDependencies: {
38
+ '@types/react': '^18.3.12',
39
+ '@types/react-dom': '^18.3.1',
40
+ '@vitejs/plugin-react': '^4.3.4',
41
+ autoprefixer: '^10.4.20',
42
+ concurrently: '^9.1.0',
43
+ postcss: '^8.4.49',
44
+ '@tailwindcss/forms': '^0.5.9',
45
+ tailwindcss: '^3.4.15',
46
+ typescript: '^5.6.3',
47
+ vite: '^6.0.0',
48
+ },
49
+ };
50
+ return { path: 'package.json', content: JSON.stringify(pkg, null, 2) + '\n' };
51
+ }
52
+ function generateViteConfig() {
53
+ return {
54
+ path: 'vite.config.ts',
55
+ content: `import { defineConfig } from 'vite';
56
+ import react from '@vitejs/plugin-react';
57
+
58
+ export default defineConfig({
59
+ plugins: [react()],
60
+ server: {
61
+ proxy: {
62
+ '/api': 'http://localhost:8080',
63
+ },
64
+ },
65
+ });
66
+ `,
67
+ };
68
+ }
69
+ function generateTailwindConfig(model) {
70
+ return {
71
+ path: 'tailwind.config.js',
72
+ content: `import forms from '@tailwindcss/forms';
73
+
74
+ /** @type {import('tailwindcss').Config} */
75
+ export default {
76
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
77
+ darkMode: 'class',
78
+ theme: {
79
+ extend: {
80
+ colors: {
81
+ primary: ${generateColorShades(model.theme.primaryColor)},
82
+ },
83
+ },
84
+ },
85
+ plugins: [forms],
86
+ };
87
+ `,
88
+ };
89
+ }
90
+ function generateTsConfig() {
91
+ const config = {
92
+ compilerOptions: {
93
+ target: 'ES2022',
94
+ module: 'ESNext',
95
+ moduleResolution: 'bundler',
96
+ jsx: 'react-jsx',
97
+ strict: true,
98
+ esModuleInterop: true,
99
+ skipLibCheck: true,
100
+ forceConsistentCasingInFileNames: true,
101
+ resolveJsonModule: true,
102
+ isolatedModules: true,
103
+ noEmit: true,
104
+ },
105
+ include: ['src'],
106
+ };
107
+ return { path: 'tsconfig.json', content: JSON.stringify(config, null, 2) + '\n' };
108
+ }
109
+ function generatePostCssConfig() {
110
+ return {
111
+ path: 'postcss.config.js',
112
+ content: `export default {
113
+ plugins: {
114
+ tailwindcss: {},
115
+ autoprefixer: {},
116
+ },
117
+ };
118
+ `,
119
+ };
120
+ }
121
+ function generateGoMod(appSlug) {
122
+ return {
123
+ path: 'go.mod',
124
+ content: `module ${appSlug}
125
+
126
+ go 1.22
127
+ `,
128
+ };
129
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * React+Go Toolkit — Helpers
3
+ *
4
+ * Go-specific helpers plus re-exports from shared helpers.
5
+ */
6
+ import type { Field } from '../../model/types.js';
7
+ /** Map a model FieldType to a Go type string. */
8
+ export declare function goType(field: Field): string;
9
+ /** Generate a Go zero value string for a field. */
10
+ export declare function goZeroValue(field: Field): string;
11
+ /** Convert PascalCase or camelCase to snake_case. */
12
+ export declare function toSnakeCase(name: string): string;
13
+ export { indent, tsType, fkFieldName, belongsToRels, hasManyRels, routePath, apiPath, inputType, generateImports, } from '../shared/helpers.js';
@@ -0,0 +1,49 @@
1
+ /**
2
+ * React+Go Toolkit — Helpers
3
+ *
4
+ * Go-specific helpers plus re-exports from shared helpers.
5
+ */
6
+ /** Map a model FieldType to a Go type string. */
7
+ export function goType(field) {
8
+ const lowerName = field.name.toLowerCase();
9
+ switch (field.type) {
10
+ case 'string':
11
+ case 'enum':
12
+ case 'date':
13
+ return 'string';
14
+ case 'number':
15
+ if (lowerName.includes('price') ||
16
+ lowerName.includes('cost') ||
17
+ lowerName.includes('amount')) {
18
+ return 'float64';
19
+ }
20
+ return 'int';
21
+ case 'boolean':
22
+ return 'bool';
23
+ default:
24
+ return 'string';
25
+ }
26
+ }
27
+ /** Generate a Go zero value string for a field. */
28
+ export function goZeroValue(field) {
29
+ switch (field.type) {
30
+ case 'string':
31
+ case 'enum':
32
+ case 'date':
33
+ return '""';
34
+ case 'number':
35
+ return '0';
36
+ case 'boolean':
37
+ return 'false';
38
+ default:
39
+ return '""';
40
+ }
41
+ }
42
+ /** Convert PascalCase or camelCase to snake_case. */
43
+ export function toSnakeCase(name) {
44
+ return name
45
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
46
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1_$2')
47
+ .toLowerCase();
48
+ }
49
+ export { indent, tsType, fkFieldName, belongsToRels, hasManyRels, routePath, apiPath, inputType, generateImports, } from '../shared/helpers.js';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * React+Go Toolkit
3
+ *
4
+ * Generates a full-stack MVP: React 18 + Vite + Tailwind + Go (net/http).
5
+ * Deterministic: same ApplicationModel -> same output files.
6
+ *
7
+ * Frontend is identical to react-node (same React components, Vite config, Tailwind).
8
+ * Backend is Go net/http stdlib with in-memory data stores.
9
+ */
10
+ import type { FactoryToolkit } from '../types.js';
11
+ export declare const reactGoToolkit: FactoryToolkit;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * React+Go Toolkit
3
+ *
4
+ * Generates a full-stack MVP: React 18 + Vite + Tailwind + Go (net/http).
5
+ * Deterministic: same ApplicationModel -> same output files.
6
+ *
7
+ * Frontend is identical to react-node (same React components, Vite config, Tailwind).
8
+ * Backend is Go net/http stdlib with in-memory data stores.
9
+ */
10
+ import { generateConfigFiles } from './config.js';
11
+ import { generateStaticFiles } from './static.js';
12
+ import { generateShellFiles } from '../react-node/shell.js';
13
+ import { generateSharedComponents } from './shared.js';
14
+ import { generateDashboard } from '../react-node/dashboard.js';
15
+ import { generateEntityFiles } from '../react-node/entity.js';
16
+ import { generateRouter } from '../react-node/router.js';
17
+ import { generateApiFiles } from './api.js';
18
+ import { generateTypesFile } from '../react-node/types-gen.js';
19
+ export const reactGoToolkit = {
20
+ id: 'react-go',
21
+ name: 'React + Go',
22
+ description: 'React 18 + Vite + Tailwind CSS + Go — full-stack MVP with Go net/http backend',
23
+ requiredSections: ['identity', 'entities', 'layout', 'features', 'theme'],
24
+ generate(model) {
25
+ const warnings = [];
26
+ const allFiles = [];
27
+ // Collect files from all generators
28
+ const generators = [
29
+ generateConfigFiles(model),
30
+ generateStaticFiles(model),
31
+ generateTypesFile(model),
32
+ generateShellFiles(model),
33
+ generateSharedComponents(),
34
+ generateDashboard(model),
35
+ generateEntityFiles(model),
36
+ generateRouter(model),
37
+ generateApiFiles(model),
38
+ ];
39
+ for (const files of generators) {
40
+ allFiles.push(...files);
41
+ }
42
+ // Check for potential issues
43
+ if (model.entities.length === 0) {
44
+ warnings.push('No entities defined — generated app will have minimal functionality.');
45
+ }
46
+ if (model.entities.length > 10) {
47
+ warnings.push('Large number of entities — generated sidebar may be crowded.');
48
+ }
49
+ return {
50
+ files: allFiles,
51
+ toolkit: 'react-go',
52
+ warnings,
53
+ };
54
+ },
55
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * React+Go Toolkit — Seed Data Generator
3
+ *
4
+ * Generates realistic mock data in Go struct literal format.
5
+ * Uses shared generateFieldValue for deterministic values,
6
+ * wraps them in Go slice-of-structs format for in-memory data stores.
7
+ */
8
+ import type { ApplicationModel, Entity } from '../../model/types.js';
9
+ import { SEED_COUNT } from '../shared/seed-data.js';
10
+ /** Generate seed data items for a single entity (Go slice format). */
11
+ export declare function generateSeedData(model: ApplicationModel, entity: Entity): string;
12
+ export { SEED_COUNT };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * React+Go Toolkit — Seed Data Generator
3
+ *
4
+ * Generates realistic mock data in Go struct literal format.
5
+ * Uses shared generateFieldValue for deterministic values,
6
+ * wraps them in Go slice-of-structs format for in-memory data stores.
7
+ */
8
+ import { belongsToRels, fkFieldName } from './helpers.js';
9
+ import { toPascalCase } from '../../model/naming.js';
10
+ import { generateFieldValue, SEED_COUNT } from '../shared/seed-data.js';
11
+ /** Generate seed data items for a single entity (Go slice format). */
12
+ export function generateSeedData(model, entity) {
13
+ const rels = belongsToRels(entity);
14
+ const lines = [];
15
+ lines.push(`var seedData = []${entity.name}{`);
16
+ for (let i = 0; i < SEED_COUNT; i++) {
17
+ const fields = [];
18
+ fields.push(`ID: ${String(i + 1)}`);
19
+ for (const field of entity.fields) {
20
+ const jsValue = generateFieldValue(field, i, entity.name);
21
+ const goValue = jsToGoValue(jsValue, field.type);
22
+ fields.push(`${toPascalCase(field.name)}: ${goValue}`);
23
+ }
24
+ // FK fields from belongsTo relationships
25
+ for (const rel of rels) {
26
+ const fk = fkFieldName(rel);
27
+ const targetEntity = model.entities.find((e) => e.name === rel.target);
28
+ const targetCount = targetEntity ? SEED_COUNT : SEED_COUNT;
29
+ fields.push(`${toPascalCase(fk)}: ${String((i % targetCount) + 1)}`);
30
+ }
31
+ // Implicit timestamps (deterministic)
32
+ const createdDate = new Date('2025-06-01T10:00:00Z');
33
+ createdDate.setDate(createdDate.getDate() + i * 2);
34
+ const updatedDate = new Date(createdDate);
35
+ updatedDate.setDate(updatedDate.getDate() + i);
36
+ fields.push(`CreatedAt: "${createdDate.toISOString()}"`);
37
+ fields.push(`UpdatedAt: "${updatedDate.toISOString()}"`);
38
+ lines.push(`\t{${fields.join(', ')}},`);
39
+ }
40
+ lines.push('}');
41
+ return lines.join('\n');
42
+ }
43
+ /** Convert a JS seed value string to Go format. */
44
+ function jsToGoValue(jsValue, fieldType) {
45
+ if (fieldType === 'boolean') {
46
+ return jsValue; // true/false same in Go
47
+ }
48
+ // String/enum/date values come wrapped in single quotes — convert to double quotes
49
+ if (jsValue.startsWith("'") && jsValue.endsWith("'")) {
50
+ return `"${jsValue.slice(1, -1)}"`;
51
+ }
52
+ // Numbers pass through unchanged
53
+ return jsValue;
54
+ }
55
+ export { SEED_COUNT };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * React+Go Toolkit — Shared Components Generator
3
+ *
4
+ * Re-exports from the shared components module.
5
+ */
6
+ export { generateSharedComponents } from '../shared/components.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * React+Go Toolkit — Shared Components Generator
3
+ *
4
+ * Re-exports from the shared components module.
5
+ */
6
+ export { generateSharedComponents } from '../shared/components.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * React+Go Toolkit — Static Files Generator
3
+ *
4
+ * Generates: .gitignore, .env.example, README.md, index.html, src/main.tsx, src/index.css
5
+ */
6
+ import type { ApplicationModel } from '../../model/types.js';
7
+ import type { FactoryFile } from '../types.js';
8
+ export declare function generateStaticFiles(model: ApplicationModel): FactoryFile[];
@@ -0,0 +1,122 @@
1
+ /**
2
+ * React+Go Toolkit — Static Files Generator
3
+ *
4
+ * Generates: .gitignore, .env.example, README.md, index.html, src/main.tsx, src/index.css
5
+ */
6
+ export function generateStaticFiles(model) {
7
+ return [
8
+ generateGitignore(),
9
+ generateEnvExample(),
10
+ generateReadme(model),
11
+ generateIndexHtml(model),
12
+ generateMainTsx(),
13
+ generateIndexCss(),
14
+ ];
15
+ }
16
+ function generateGitignore() {
17
+ return {
18
+ path: '.gitignore',
19
+ content: `node_modules
20
+ dist
21
+ .env
22
+ *.local
23
+ bin/
24
+ *.exe
25
+ `,
26
+ };
27
+ }
28
+ function generateEnvExample() {
29
+ return {
30
+ path: '.env.example',
31
+ content: `PORT=8080
32
+ VITE_API_URL=http://localhost:8080
33
+ `,
34
+ };
35
+ }
36
+ function generateReadme(model) {
37
+ let entitiesSection = '';
38
+ if (model.entities.length > 0) {
39
+ const entityLines = model.entities
40
+ .map((e) => {
41
+ const relCount = e.relationships.length;
42
+ const parts = [`${String(e.fields.length)} fields`];
43
+ if (relCount > 0) {
44
+ parts.push(`${String(relCount)} relationship${relCount > 1 ? 's' : ''}`);
45
+ }
46
+ return `- **${e.name}** (${e.icon}) — ${parts.join(', ')}`;
47
+ })
48
+ .join('\n');
49
+ entitiesSection = `\n## Entities\n\n${entityLines}\n`;
50
+ }
51
+ return {
52
+ path: 'README.md',
53
+ content: `# ${model.identity.name}
54
+
55
+ ${model.identity.description}
56
+ ${entitiesSection}
57
+ ## Getting Started
58
+
59
+ \`\`\`bash
60
+ npm install
61
+ go run ./server
62
+ npm run dev
63
+ \`\`\`
64
+
65
+ This starts both the Vite dev server and the Go server.
66
+
67
+ - **Frontend:** http://localhost:5173
68
+ - **API:** http://localhost:8080
69
+
70
+ ## Tech Stack
71
+
72
+ - React 18 + TypeScript
73
+ - Vite
74
+ - Tailwind CSS
75
+ - Go (net/http)
76
+ - React Router v6
77
+ `,
78
+ };
79
+ }
80
+ function generateIndexHtml(model) {
81
+ return {
82
+ path: 'index.html',
83
+ content: `<!doctype html>
84
+ <html lang="en">
85
+ <head>
86
+ <meta charset="UTF-8" />
87
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
88
+ <title>${model.identity.name}</title>
89
+ </head>
90
+ <body>
91
+ <div id="root"></div>
92
+ <script type="module" src="/src/main.tsx"></script>
93
+ </body>
94
+ </html>
95
+ `,
96
+ };
97
+ }
98
+ function generateMainTsx() {
99
+ return {
100
+ path: 'src/main.tsx',
101
+ content: `import React from 'react';
102
+ import ReactDOM from 'react-dom/client';
103
+ import App from './App';
104
+ import './index.css';
105
+
106
+ ReactDOM.createRoot(document.getElementById('root')!).render(
107
+ <React.StrictMode>
108
+ <App />
109
+ </React.StrictMode>,
110
+ );
111
+ `,
112
+ };
113
+ }
114
+ function generateIndexCss() {
115
+ return {
116
+ path: 'src/index.css',
117
+ content: `@tailwind base;
118
+ @tailwind components;
119
+ @tailwind utilities;
120
+ `,
121
+ };
122
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/factory",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "AI-driven application scaffolder for the compilr-dev ecosystem",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",