@compilr-dev/factory 0.1.7 → 0.1.8
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/factory/registry.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/toolkits/next-prisma/api-routes.d.ts +10 -0
- package/dist/toolkits/next-prisma/api-routes.js +155 -0
- package/dist/toolkits/next-prisma/config.d.ts +9 -0
- package/dist/toolkits/next-prisma/config.js +139 -0
- package/dist/toolkits/next-prisma/dashboard.d.ts +9 -0
- package/dist/toolkits/next-prisma/dashboard.js +87 -0
- package/dist/toolkits/next-prisma/entity-components.d.ts +10 -0
- package/dist/toolkits/next-prisma/entity-components.js +217 -0
- package/dist/toolkits/next-prisma/entity-pages.d.ts +12 -0
- package/dist/toolkits/next-prisma/entity-pages.js +348 -0
- package/dist/toolkits/next-prisma/helpers.d.ts +13 -0
- package/dist/toolkits/next-prisma/helpers.js +37 -0
- package/dist/toolkits/next-prisma/index.d.ts +9 -0
- package/dist/toolkits/next-prisma/index.js +57 -0
- package/dist/toolkits/next-prisma/layout.d.ts +9 -0
- package/dist/toolkits/next-prisma/layout.js +157 -0
- package/dist/toolkits/next-prisma/prisma.d.ts +8 -0
- package/dist/toolkits/next-prisma/prisma.js +76 -0
- package/dist/toolkits/next-prisma/seed.d.ts +9 -0
- package/dist/toolkits/next-prisma/seed.js +100 -0
- package/dist/toolkits/next-prisma/static.d.ts +8 -0
- package/dist/toolkits/next-prisma/static.js +61 -0
- package/dist/toolkits/next-prisma/types-gen.d.ts +10 -0
- package/dist/toolkits/next-prisma/types-gen.js +62 -0
- package/dist/toolkits/react-node/config.d.ts +1 -4
- package/dist/toolkits/react-node/config.js +3 -84
- package/dist/toolkits/react-node/helpers.d.ts +2 -23
- package/dist/toolkits/react-node/helpers.js +2 -67
- package/dist/toolkits/react-node/seed.d.ts +4 -3
- package/dist/toolkits/react-node/seed.js +4 -111
- package/dist/toolkits/react-node/shared.d.ts +2 -3
- package/dist/toolkits/react-node/shared.js +2 -115
- package/dist/toolkits/shared/color-utils.d.ts +10 -0
- package/dist/toolkits/shared/color-utils.js +85 -0
- package/dist/toolkits/shared/components.d.ts +12 -0
- package/dist/toolkits/shared/components.js +121 -0
- package/dist/toolkits/shared/helpers.d.ts +28 -0
- package/dist/toolkits/shared/helpers.js +72 -0
- package/dist/toolkits/shared/index.d.ts +9 -0
- package/dist/toolkits/shared/index.js +9 -0
- package/dist/toolkits/shared/seed-data.d.ts +18 -0
- package/dist/toolkits/shared/seed-data.js +119 -0
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — Prisma Schema Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates: prisma/schema.prisma, lib/prisma.ts
|
|
5
|
+
*/
|
|
6
|
+
import { toCamelCase } from '../../model/naming.js';
|
|
7
|
+
import { prismaFieldType, prismaModelName, fkFieldName, belongsToRels, hasManyRels, } from './helpers.js';
|
|
8
|
+
export function generatePrismaFiles(model) {
|
|
9
|
+
return [generateSchema(model), generatePrismaClient()];
|
|
10
|
+
}
|
|
11
|
+
function generateSchema(model) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
lines.push(`generator client {`);
|
|
14
|
+
lines.push(` provider = "prisma-client-js"`);
|
|
15
|
+
lines.push(`}`);
|
|
16
|
+
lines.push('');
|
|
17
|
+
lines.push(`datasource db {`);
|
|
18
|
+
lines.push(` provider = "sqlite"`);
|
|
19
|
+
lines.push(` url = env("DATABASE_URL")`);
|
|
20
|
+
lines.push(`}`);
|
|
21
|
+
for (const entity of model.entities) {
|
|
22
|
+
lines.push('');
|
|
23
|
+
lines.push(generatePrismaModel(model, entity));
|
|
24
|
+
}
|
|
25
|
+
lines.push('');
|
|
26
|
+
return { path: 'prisma/schema.prisma', content: lines.join('\n') };
|
|
27
|
+
}
|
|
28
|
+
function generatePrismaModel(model, entity) {
|
|
29
|
+
const modelName = prismaModelName(entity);
|
|
30
|
+
const bto = belongsToRels(entity);
|
|
31
|
+
const hm = hasManyRels(entity);
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push(`model ${modelName} {`);
|
|
34
|
+
lines.push(` id Int @id @default(autoincrement())`);
|
|
35
|
+
// Regular fields
|
|
36
|
+
for (const field of entity.fields) {
|
|
37
|
+
const pType = prismaFieldType(field);
|
|
38
|
+
const optional = field.required ? '' : '?';
|
|
39
|
+
lines.push(` ${field.name.padEnd(9)} ${pType}${optional}`);
|
|
40
|
+
}
|
|
41
|
+
// BelongsTo relationships — FK field + relation
|
|
42
|
+
for (const rel of bto) {
|
|
43
|
+
const fk = fkFieldName(rel);
|
|
44
|
+
const targetModel = prismaModelName({ name: rel.target });
|
|
45
|
+
const relField = toCamelCase(rel.target);
|
|
46
|
+
lines.push(` ${fk.padEnd(9)} Int`);
|
|
47
|
+
lines.push(` ${relField.padEnd(9)} ${targetModel} @relation(fields: [${fk}], references: [id])`);
|
|
48
|
+
}
|
|
49
|
+
// HasMany relationships — reverse relation array
|
|
50
|
+
for (const rel of hm) {
|
|
51
|
+
const targetEntity = model.entities.find((e) => e.name === rel.target);
|
|
52
|
+
if (!targetEntity)
|
|
53
|
+
continue;
|
|
54
|
+
const targetModel = prismaModelName(targetEntity);
|
|
55
|
+
const relField = toCamelCase(targetEntity.pluralName);
|
|
56
|
+
lines.push(` ${relField.padEnd(9)} ${targetModel}[]`);
|
|
57
|
+
}
|
|
58
|
+
// Timestamps
|
|
59
|
+
lines.push(` createdAt DateTime @default(now())`);
|
|
60
|
+
lines.push(` updatedAt DateTime @updatedAt`);
|
|
61
|
+
lines.push(`}`);
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
function generatePrismaClient() {
|
|
65
|
+
return {
|
|
66
|
+
path: 'lib/prisma.ts',
|
|
67
|
+
content: `import { PrismaClient } from '@prisma/client';
|
|
68
|
+
|
|
69
|
+
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
|
|
70
|
+
|
|
71
|
+
export const prisma = globalForPrisma.prisma || new PrismaClient();
|
|
72
|
+
|
|
73
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
|
|
74
|
+
`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — Seed Data Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates prisma/seed.ts using Prisma's createMany for bulk inserts.
|
|
5
|
+
* Entities are ordered by dependency (parents before children).
|
|
6
|
+
*/
|
|
7
|
+
import type { ApplicationModel } from '../../model/types.js';
|
|
8
|
+
import type { FactoryFile } from '../types.js';
|
|
9
|
+
export declare function generateSeedFile(model: ApplicationModel): FactoryFile[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — Seed Data Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates prisma/seed.ts using Prisma's createMany for bulk inserts.
|
|
5
|
+
* Entities are ordered by dependency (parents before children).
|
|
6
|
+
*/
|
|
7
|
+
import { toCamelCase } from '../../model/naming.js';
|
|
8
|
+
import { fkFieldName, belongsToRels } from './helpers.js';
|
|
9
|
+
import { generateFieldValue, SEED_COUNT } from '../shared/seed-data.js';
|
|
10
|
+
export function generateSeedFile(model) {
|
|
11
|
+
const ordered = topologicalSort(model.entities);
|
|
12
|
+
const seedBlocks = ordered.map((entity) => generateEntitySeed(model, entity)).join('\n\n');
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
path: 'prisma/seed.ts',
|
|
16
|
+
content: `import { PrismaClient } from '@prisma/client';
|
|
17
|
+
|
|
18
|
+
const prisma = new PrismaClient();
|
|
19
|
+
|
|
20
|
+
async function main() {
|
|
21
|
+
${seedBlocks}
|
|
22
|
+
|
|
23
|
+
console.log('Seed data created successfully.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
main()
|
|
27
|
+
.then(() => prisma.$disconnect())
|
|
28
|
+
.catch((e) => {
|
|
29
|
+
console.error(e);
|
|
30
|
+
prisma.$disconnect();
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
`,
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
function generateEntitySeed(model, entity) {
|
|
38
|
+
const camel = toCamelCase(entity.name);
|
|
39
|
+
const rels = belongsToRels(entity);
|
|
40
|
+
const items = [];
|
|
41
|
+
for (let i = 0; i < SEED_COUNT; i++) {
|
|
42
|
+
const fields = [];
|
|
43
|
+
for (const field of entity.fields) {
|
|
44
|
+
const value = generateFieldValue(field, i, entity.name);
|
|
45
|
+
// For Prisma, booleans and numbers don't need quotes; strings already have them
|
|
46
|
+
if (field.type === 'date') {
|
|
47
|
+
// Prisma expects Date objects for DateTime fields
|
|
48
|
+
fields.push(` ${field.name}: new Date(${value}),`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
fields.push(` ${field.name}: ${value},`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// FK fields from belongsTo relationships
|
|
55
|
+
for (const rel of rels) {
|
|
56
|
+
const fk = fkFieldName(rel);
|
|
57
|
+
const targetCount = SEED_COUNT;
|
|
58
|
+
fields.push(` ${fk}: ${String((i % targetCount) + 1)},`);
|
|
59
|
+
}
|
|
60
|
+
items.push(` {\n${fields.join('\n')}\n },`);
|
|
61
|
+
}
|
|
62
|
+
return ` await prisma.${camel}.createMany({
|
|
63
|
+
data: [
|
|
64
|
+
${items.join('\n')}
|
|
65
|
+
],
|
|
66
|
+
});`;
|
|
67
|
+
}
|
|
68
|
+
/** Sort entities so that parents come before children (entities with belongsTo go after their targets). */
|
|
69
|
+
function topologicalSort(entities) {
|
|
70
|
+
const entityMap = new Map(entities.map((e) => [e.name, e]));
|
|
71
|
+
const sorted = [];
|
|
72
|
+
const visited = new Set();
|
|
73
|
+
const visiting = new Set();
|
|
74
|
+
function visit(entity) {
|
|
75
|
+
if (visited.has(entity.name))
|
|
76
|
+
return;
|
|
77
|
+
if (visiting.has(entity.name)) {
|
|
78
|
+
// Circular dependency — just add it
|
|
79
|
+
sorted.push(entity);
|
|
80
|
+
visited.add(entity.name);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
visiting.add(entity.name);
|
|
84
|
+
// Visit dependencies first
|
|
85
|
+
for (const rel of entity.relationships) {
|
|
86
|
+
if (rel.type === 'belongsTo') {
|
|
87
|
+
const target = entityMap.get(rel.target);
|
|
88
|
+
if (target)
|
|
89
|
+
visit(target);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
visiting.delete(entity.name);
|
|
93
|
+
visited.add(entity.name);
|
|
94
|
+
sorted.push(entity);
|
|
95
|
+
}
|
|
96
|
+
for (const entity of entities) {
|
|
97
|
+
visit(entity);
|
|
98
|
+
}
|
|
99
|
+
return sorted;
|
|
100
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — Static Files Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates: .gitignore, README.md
|
|
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,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — Static Files Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates: .gitignore, README.md
|
|
5
|
+
*/
|
|
6
|
+
export function generateStaticFiles(model) {
|
|
7
|
+
return [generateGitignore(), generateReadme(model)];
|
|
8
|
+
}
|
|
9
|
+
function generateGitignore() {
|
|
10
|
+
return {
|
|
11
|
+
path: '.gitignore',
|
|
12
|
+
content: `node_modules
|
|
13
|
+
.next
|
|
14
|
+
.env
|
|
15
|
+
prisma/dev.db
|
|
16
|
+
prisma/dev.db-journal
|
|
17
|
+
*.local
|
|
18
|
+
`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function generateReadme(model) {
|
|
22
|
+
let entitiesSection = '';
|
|
23
|
+
if (model.entities.length > 0) {
|
|
24
|
+
const entityLines = model.entities
|
|
25
|
+
.map((e) => {
|
|
26
|
+
const relCount = e.relationships.length;
|
|
27
|
+
const parts = [`${String(e.fields.length)} fields`];
|
|
28
|
+
if (relCount > 0) {
|
|
29
|
+
parts.push(`${String(relCount)} relationship${relCount > 1 ? 's' : ''}`);
|
|
30
|
+
}
|
|
31
|
+
return `- **${e.name}** (${e.icon}) — ${parts.join(', ')}`;
|
|
32
|
+
})
|
|
33
|
+
.join('\n');
|
|
34
|
+
entitiesSection = `\n## Entities\n\n${entityLines}\n`;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
path: 'README.md',
|
|
38
|
+
content: `# ${model.identity.name}
|
|
39
|
+
|
|
40
|
+
${model.identity.description}
|
|
41
|
+
${entitiesSection}
|
|
42
|
+
## Getting Started
|
|
43
|
+
|
|
44
|
+
\`\`\`bash
|
|
45
|
+
npm install
|
|
46
|
+
npx prisma db push
|
|
47
|
+
npx prisma db seed
|
|
48
|
+
npm run dev
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
- **App:** http://localhost:3000
|
|
52
|
+
|
|
53
|
+
## Tech Stack
|
|
54
|
+
|
|
55
|
+
- Next.js 14 (App Router)
|
|
56
|
+
- TypeScript
|
|
57
|
+
- Prisma ORM (SQLite)
|
|
58
|
+
- Tailwind CSS
|
|
59
|
+
`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — TypeScript Types Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates src/types/index.ts with interfaces for each entity.
|
|
5
|
+
* These serve as the frontend contract (Client Components use these types;
|
|
6
|
+
* Prisma generates its own types server-side).
|
|
7
|
+
*/
|
|
8
|
+
import type { ApplicationModel } from '../../model/types.js';
|
|
9
|
+
import type { FactoryFile } from '../types.js';
|
|
10
|
+
export declare function generateTypesFile(model: ApplicationModel): FactoryFile[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Next.js + Prisma Toolkit — TypeScript Types Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates src/types/index.ts with interfaces for each entity.
|
|
5
|
+
* These serve as the frontend contract (Client Components use these types;
|
|
6
|
+
* Prisma generates its own types server-side).
|
|
7
|
+
*/
|
|
8
|
+
import { tsType, fkFieldName, belongsToRels } from './helpers.js';
|
|
9
|
+
const SHARED_TYPES = `// --- Shared API Types ---
|
|
10
|
+
|
|
11
|
+
export interface PaginatedResponse<T> {
|
|
12
|
+
data: T[];
|
|
13
|
+
total: number;
|
|
14
|
+
page: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
totalPages: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ApiResponse<T> {
|
|
20
|
+
data: T;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ApiListResponse<T> {
|
|
25
|
+
data: T[];
|
|
26
|
+
total: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SortParams {
|
|
30
|
+
field: string;
|
|
31
|
+
direction: 'asc' | 'desc';
|
|
32
|
+
}`;
|
|
33
|
+
export function generateTypesFile(model) {
|
|
34
|
+
const lines = ['// Auto-generated TypeScript types from Application Model', ''];
|
|
35
|
+
lines.push(SHARED_TYPES);
|
|
36
|
+
lines.push('');
|
|
37
|
+
for (const entity of model.entities) {
|
|
38
|
+
lines.push(generateInterface(entity));
|
|
39
|
+
lines.push('');
|
|
40
|
+
}
|
|
41
|
+
return [{ path: 'src/types/index.ts', content: lines.join('\n') }];
|
|
42
|
+
}
|
|
43
|
+
function generateInterface(entity) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
lines.push(`export interface ${entity.name} {`);
|
|
46
|
+
lines.push(' id: number;');
|
|
47
|
+
for (const field of entity.fields) {
|
|
48
|
+
const optional = field.required ? '' : '?';
|
|
49
|
+
lines.push(` ${field.name}${optional}: ${tsType(field)};`);
|
|
50
|
+
}
|
|
51
|
+
// FK fields from belongsTo
|
|
52
|
+
for (const rel of belongsToRels(entity)) {
|
|
53
|
+
const fk = fkFieldName(rel);
|
|
54
|
+
lines.push(` ${fk}: number;`);
|
|
55
|
+
lines.push(` ${rel.target.charAt(0).toLowerCase() + rel.target.slice(1)}?: ${rel.target};`);
|
|
56
|
+
}
|
|
57
|
+
// Implicit timestamps
|
|
58
|
+
lines.push(' createdAt: string;');
|
|
59
|
+
lines.push(' updatedAt: string;');
|
|
60
|
+
lines.push('}');
|
|
61
|
+
return lines.join('\n');
|
|
62
|
+
}
|
|
@@ -7,7 +7,4 @@
|
|
|
7
7
|
import type { ApplicationModel } from '../../model/types.js';
|
|
8
8
|
import type { FactoryFile } from '../types.js';
|
|
9
9
|
export declare function generateConfigFiles(model: ApplicationModel): FactoryFile[];
|
|
10
|
-
|
|
11
|
-
declare function hslToHex(h: number, s: number, l: number): string;
|
|
12
|
-
declare function generateColorShades(hex: string): string;
|
|
13
|
-
export { hexToHsl, hslToHex, generateColorShades };
|
|
10
|
+
export { hexToHsl, hslToHex, generateColorShades } from '../shared/color-utils.js';
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* tsconfig.json, postcss.config.js
|
|
6
6
|
*/
|
|
7
7
|
import { toKebabCase } from '../../model/naming.js';
|
|
8
|
+
import { generateColorShades } from '../shared/color-utils.js';
|
|
8
9
|
export function generateConfigFiles(model) {
|
|
9
10
|
const appSlug = toKebabCase(model.identity.name);
|
|
10
11
|
return [
|
|
@@ -109,90 +110,8 @@ function generateTsConfig() {
|
|
|
109
110
|
};
|
|
110
111
|
return { path: 'tsconfig.json', content: JSON.stringify(config, null, 2) + '\n' };
|
|
111
112
|
}
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
// =============================================================================
|
|
115
|
-
const SHADE_LIGHTNESS = {
|
|
116
|
-
'50': 97,
|
|
117
|
-
'100': 94,
|
|
118
|
-
'200': 86,
|
|
119
|
-
'300': 77,
|
|
120
|
-
'400': 66,
|
|
121
|
-
'500': 55,
|
|
122
|
-
'600': 44,
|
|
123
|
-
'700': 36,
|
|
124
|
-
'800': 27,
|
|
125
|
-
'900': 20,
|
|
126
|
-
'950': 14,
|
|
127
|
-
};
|
|
128
|
-
function hexToHsl(hex) {
|
|
129
|
-
const raw = hex.replace('#', '');
|
|
130
|
-
const r = parseInt(raw.substring(0, 2), 16) / 255;
|
|
131
|
-
const g = parseInt(raw.substring(2, 4), 16) / 255;
|
|
132
|
-
const b = parseInt(raw.substring(4, 6), 16) / 255;
|
|
133
|
-
const max = Math.max(r, g, b);
|
|
134
|
-
const min = Math.min(r, g, b);
|
|
135
|
-
const l = (max + min) / 2;
|
|
136
|
-
if (max === min)
|
|
137
|
-
return [0, 0, Math.round(l * 100)];
|
|
138
|
-
const d = max - min;
|
|
139
|
-
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
140
|
-
let h = 0;
|
|
141
|
-
if (max === r)
|
|
142
|
-
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
143
|
-
else if (max === g)
|
|
144
|
-
h = ((b - r) / d + 2) / 6;
|
|
145
|
-
else
|
|
146
|
-
h = ((r - g) / d + 4) / 6;
|
|
147
|
-
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
148
|
-
}
|
|
149
|
-
function hslToHex(h, s, l) {
|
|
150
|
-
const sN = s / 100;
|
|
151
|
-
const lN = l / 100;
|
|
152
|
-
const c = (1 - Math.abs(2 * lN - 1)) * sN;
|
|
153
|
-
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
154
|
-
const m = lN - c / 2;
|
|
155
|
-
let r = 0, g = 0, b = 0;
|
|
156
|
-
if (h < 60)
|
|
157
|
-
[r, g, b] = [c, x, 0];
|
|
158
|
-
else if (h < 120)
|
|
159
|
-
[r, g, b] = [x, c, 0];
|
|
160
|
-
else if (h < 180)
|
|
161
|
-
[r, g, b] = [0, c, x];
|
|
162
|
-
else if (h < 240)
|
|
163
|
-
[r, g, b] = [0, x, c];
|
|
164
|
-
else if (h < 300)
|
|
165
|
-
[r, g, b] = [x, 0, c];
|
|
166
|
-
else
|
|
167
|
-
[r, g, b] = [c, 0, x];
|
|
168
|
-
const toHex = (v) => Math.round((v + m) * 255)
|
|
169
|
-
.toString(16)
|
|
170
|
-
.padStart(2, '0');
|
|
171
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
172
|
-
}
|
|
173
|
-
function generateColorShades(hex) {
|
|
174
|
-
const [h, s] = hexToHsl(hex);
|
|
175
|
-
const entries = [];
|
|
176
|
-
for (const [shade, lightness] of Object.entries(SHADE_LIGHTNESS)) {
|
|
177
|
-
entries.push(` ${shade}: '${hslToHex(h, s, lightness)}',`);
|
|
178
|
-
}
|
|
179
|
-
// Find the shade closest to the original for DEFAULT
|
|
180
|
-
const [, , originalL] = hexToHsl(hex);
|
|
181
|
-
let closestShade = '500';
|
|
182
|
-
let closestDiff = Infinity;
|
|
183
|
-
for (const [shade, lightness] of Object.entries(SHADE_LIGHTNESS)) {
|
|
184
|
-
const diff = Math.abs(lightness - originalL);
|
|
185
|
-
if (diff < closestDiff) {
|
|
186
|
-
closestDiff = diff;
|
|
187
|
-
closestShade = shade;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const defaultHex = hslToHex(h, s, SHADE_LIGHTNESS[closestShade]);
|
|
191
|
-
entries.push(` DEFAULT: '${defaultHex}',`);
|
|
192
|
-
return `{\n${entries.join('\n')}\n }`;
|
|
193
|
-
}
|
|
194
|
-
// Export for testing
|
|
195
|
-
export { hexToHsl, hslToHex, generateColorShades };
|
|
113
|
+
// Re-export for testing (from shared)
|
|
114
|
+
export { hexToHsl, hslToHex, generateColorShades } from '../shared/color-utils.js';
|
|
196
115
|
function generatePostCssConfig() {
|
|
197
116
|
return {
|
|
198
117
|
path: 'postcss.config.js',
|
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React+Node Toolkit — Shared Helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Re-exports from the shared helpers module.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
/** Indent every line of a multi-line string. */
|
|
8
|
-
export declare function indent(text: string, spaces: number): string;
|
|
9
|
-
/** Map a model FieldType to a TypeScript type string. */
|
|
10
|
-
export declare function tsType(field: Field): string;
|
|
11
|
-
/** Get the FK field name for a belongsTo relationship. */
|
|
12
|
-
export declare function fkFieldName(rel: Relationship): string;
|
|
13
|
-
/** Get belongsTo relationships for an entity. */
|
|
14
|
-
export declare function belongsToRels(entity: Entity): readonly Relationship[];
|
|
15
|
-
/** Get hasMany relationships for an entity. */
|
|
16
|
-
export declare function hasManyRels(entity: Entity): readonly Relationship[];
|
|
17
|
-
/** Generate a route path from entity plural name. */
|
|
18
|
-
export declare function routePath(entity: Entity): string;
|
|
19
|
-
/** Generate an API path from entity plural name. */
|
|
20
|
-
export declare function apiPath(entity: Entity): string;
|
|
21
|
-
/** Map a model FieldType to an HTML input type. */
|
|
22
|
-
export declare function inputType(field: Field): string;
|
|
23
|
-
/** Generate import statement lines. */
|
|
24
|
-
export declare function generateImports(imports: Array<{
|
|
25
|
-
from: string;
|
|
26
|
-
names: string[];
|
|
27
|
-
}>): string;
|
|
6
|
+
export { indent, tsType, fkFieldName, belongsToRels, hasManyRels, routePath, apiPath, inputType, generateImports, } from '../shared/helpers.js';
|
|
@@ -1,71 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* React+Node Toolkit — Shared Helpers
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Re-exports from the shared helpers module.
|
|
5
5
|
*/
|
|
6
|
-
|
|
7
|
-
/** Indent every line of a multi-line string. */
|
|
8
|
-
export function indent(text, spaces) {
|
|
9
|
-
const pad = ' '.repeat(spaces);
|
|
10
|
-
return text
|
|
11
|
-
.split('\n')
|
|
12
|
-
.map((line) => (line.trim() === '' ? '' : pad + line))
|
|
13
|
-
.join('\n');
|
|
14
|
-
}
|
|
15
|
-
/** Map a model FieldType to a TypeScript type string. */
|
|
16
|
-
export function tsType(field) {
|
|
17
|
-
switch (field.type) {
|
|
18
|
-
case 'string':
|
|
19
|
-
case 'enum':
|
|
20
|
-
return 'string';
|
|
21
|
-
case 'number':
|
|
22
|
-
return 'number';
|
|
23
|
-
case 'boolean':
|
|
24
|
-
return 'boolean';
|
|
25
|
-
case 'date':
|
|
26
|
-
return 'string'; // ISO date string
|
|
27
|
-
default:
|
|
28
|
-
return 'string';
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/** Get the FK field name for a belongsTo relationship. */
|
|
32
|
-
export function fkFieldName(rel) {
|
|
33
|
-
return rel.fieldName ?? toCamelCase(rel.target) + 'Id';
|
|
34
|
-
}
|
|
35
|
-
/** Get belongsTo relationships for an entity. */
|
|
36
|
-
export function belongsToRels(entity) {
|
|
37
|
-
return entity.relationships.filter((r) => r.type === 'belongsTo');
|
|
38
|
-
}
|
|
39
|
-
/** Get hasMany relationships for an entity. */
|
|
40
|
-
export function hasManyRels(entity) {
|
|
41
|
-
return entity.relationships.filter((r) => r.type === 'hasMany');
|
|
42
|
-
}
|
|
43
|
-
/** Generate a route path from entity plural name. */
|
|
44
|
-
export function routePath(entity) {
|
|
45
|
-
return '/' + toKebabCase(entity.pluralName).toLowerCase();
|
|
46
|
-
}
|
|
47
|
-
/** Generate an API path from entity plural name. */
|
|
48
|
-
export function apiPath(entity) {
|
|
49
|
-
return '/api/' + toKebabCase(entity.pluralName).toLowerCase();
|
|
50
|
-
}
|
|
51
|
-
/** Map a model FieldType to an HTML input type. */
|
|
52
|
-
export function inputType(field) {
|
|
53
|
-
switch (field.type) {
|
|
54
|
-
case 'string':
|
|
55
|
-
return 'text';
|
|
56
|
-
case 'number':
|
|
57
|
-
return 'number';
|
|
58
|
-
case 'boolean':
|
|
59
|
-
return 'checkbox';
|
|
60
|
-
case 'date':
|
|
61
|
-
return 'date';
|
|
62
|
-
case 'enum':
|
|
63
|
-
return 'select';
|
|
64
|
-
default:
|
|
65
|
-
return 'text';
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
/** Generate import statement lines. */
|
|
69
|
-
export function generateImports(imports) {
|
|
70
|
-
return imports.map((i) => `import { ${i.names.join(', ')} } from '${i.from}';`).join('\n');
|
|
71
|
-
}
|
|
6
|
+
export { indent, tsType, fkFieldName, belongsToRels, hasManyRels, routePath, apiPath, inputType, generateImports, } from '../shared/helpers.js';
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* React+Node Toolkit — Seed Data Generator
|
|
3
3
|
*
|
|
4
4
|
* Generates realistic mock data based on field semantics.
|
|
5
|
-
*
|
|
5
|
+
* Uses shared generateFieldValue for deterministic values,
|
|
6
|
+
* wraps them in JavaScript array format for in-memory data stores.
|
|
6
7
|
*/
|
|
7
8
|
import type { ApplicationModel, Entity } from '../../model/types.js';
|
|
8
|
-
|
|
9
|
-
/** Generate seed data items for a single entity. */
|
|
9
|
+
import { SEED_COUNT } from '../shared/seed-data.js';
|
|
10
|
+
/** Generate seed data items for a single entity (JS array format). */
|
|
10
11
|
export declare function generateSeedData(model: ApplicationModel, entity: Entity): string;
|
|
11
12
|
export { SEED_COUNT };
|