create-nolly-template 1.0.9 โ†’ 1.0.11

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/builder.js CHANGED
@@ -66,6 +66,13 @@ async function loadDirectoryIntoMap(root, baseDir, fileMap) {
66
66
  }
67
67
  }
68
68
  export async function buildProject(template, selectedFeatures, answers, outputDir) {
69
+ for (const feature of selectedFeatures) {
70
+ if (feature.group && feature.exclusive) {
71
+ const conflict = selectedFeatures.find(f => f.group === feature.group && f.key !== feature.key);
72
+ if (conflict)
73
+ throw new Error(`Cannot select "${feature.name}" because it is exclusive with "${conflict.name}" in group "${feature.group}"`);
74
+ }
75
+ }
69
76
  const fileMap = new Map();
70
77
  const baseRoot = path.join(TEMPLATES_ROOT, template.templateRoot);
71
78
  await loadDirectoryIntoMap(baseRoot, baseRoot, fileMap);
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import prompts from 'prompts';
2
+ import Enquirer from 'enquirer';
3
3
  import { registry } from './registry/index.js';
4
4
  import { buildProject } from './builder.js';
5
5
  import packageJSON from '../package.json' with { type: 'json' };
6
+ const { Select, MultiSelect, Input } = Enquirer;
6
7
  async function printHelp() {
7
8
  console.log(`
8
9
  ๐Ÿš€ create-nolly-template v${packageJSON.version}
@@ -69,57 +70,76 @@ async function main() {
69
70
  if (args.includes('--list') || args.includes('-l'))
70
71
  return printList();
71
72
  console.log('\n๐Ÿš€ Welcome to create-nolly-template!\n');
72
- const { category } = await prompts({
73
- type: 'select',
73
+ const category = await new Select({
74
74
  name: 'category',
75
75
  message: 'Main category',
76
- choices: registry.map(category => ({ title: category.name, value: category }))
77
- });
78
- const { subCategory } = await prompts({
79
- type: 'select',
76
+ choices: registry.map(c => c.name),
77
+ result(name) { return registry.find(c => c.name === name); }
78
+ }).run();
79
+ const subCategory = await new Select({
80
80
  name: 'subCategory',
81
81
  message: 'Sub category',
82
- choices: category.subCategories.map(subCategory => ({ title: subCategory.name, value: subCategory }))
83
- });
84
- if (!subCategory)
85
- process.exit(0);
86
- const { template } = await prompts({
87
- type: 'select',
82
+ choices: category.subCategories.map((s) => s.name),
83
+ result(name) { return category.subCategories.find((s) => s.name === name); }
84
+ }).run();
85
+ const template = await new Select({
88
86
  name: 'template',
89
87
  message: 'Base template',
90
- choices: subCategory.templates.map(template => ({ title: template.name, value: template }))
91
- });
92
- if (!template)
93
- process.exit(0);
88
+ choices: subCategory.templates.map((t) => t.name),
89
+ result(name) { return subCategory.templates.find((t) => t.name === name); }
90
+ }).run();
94
91
  let selectedFeatures = [];
95
92
  if (template.features && template.features.length > 0) {
96
- const { features } = await prompts({
97
- type: 'multiselect',
93
+ const prompt = new MultiSelect({
98
94
  name: 'features',
99
95
  message: 'Optional features (space to toggle)',
100
- choices: template.features.map((feature) => ({
101
- title: feature.name,
102
- value: feature,
103
- description: feature.description
96
+ hint: 'Space to select ยท Enter to submit',
97
+ choices: template.features.map((f) => ({
98
+ name: f.key,
99
+ message: f.name,
100
+ hint: f.description
104
101
  })),
105
- hint: '- Space to select. Return to submit'
102
+ result(names) { return names.map(name => template.features.find((f) => f.key === name)); }
106
103
  });
107
- selectedFeatures = features ?? [];
104
+ const selected = await prompt.run();
105
+ const groups = new Map();
106
+ for (const feat of selected) {
107
+ if (feat.group && feat.exclusive) {
108
+ if (groups.has(feat.group)) {
109
+ throw new Error(`Feature "${feat.name}" is exclusive with another selected feature in group "${feat.group}"`);
110
+ }
111
+ groups.set(feat.group, feat.key);
112
+ }
113
+ }
114
+ selectedFeatures = selected;
115
+ }
116
+ const answers = {};
117
+ for (const prompt of template.prompts) {
118
+ const value = await new Input({
119
+ name: prompt.name,
120
+ message: prompt.message,
121
+ initial: prompt.initial
122
+ }).run();
123
+ answers[prompt.name] = value;
108
124
  }
109
- const answers = await prompts(template.prompts);
110
- const { outputDir } = await prompts({
111
- type: 'text',
125
+ const outputDir = await new Input({
112
126
  name: 'outputDir',
113
127
  message: 'Output directory',
114
128
  initial: `./${answers.name || template.key}`
115
- });
129
+ }).run();
116
130
  if (!outputDir)
117
131
  process.exit(0);
118
132
  await buildProject(template, selectedFeatures, answers, outputDir);
119
133
  console.log('\nโœ… Project created at', outputDir);
120
134
  console.log(' Base:', template.name);
121
135
  if (selectedFeatures.length > 0)
122
- console.log(' Features:', selectedFeatures.map((feature) => feature.name).join(', '));
136
+ console.log(' Features:', selectedFeatures.map(f => f.name).join(', '));
137
+ for (const feature of selectedFeatures) {
138
+ if (feature.additionalMessages && feature.additionalMessages.length > 0) {
139
+ console.log(`\n๐Ÿ“Œ ${feature.name} - ${feature.description}`);
140
+ feature.additionalMessages.forEach((msg) => console.log(` โ€ข ${msg}`));
141
+ }
142
+ }
123
143
  console.log('\n cd', outputDir, '&& pnpm install\n');
124
144
  }
125
145
  main().catch(error => {
@@ -1,6 +1,7 @@
1
1
  import { swagger } from './swagger.js';
2
2
  import { websocket } from './websocket.js';
3
3
  import { mongodb } from './mongodb.js';
4
+ import { postgresql } from './postgresql.js';
4
5
  export const fastify = {
5
6
  key: 'fastify',
6
7
  name: 'Fastify TypeScript',
@@ -12,6 +13,7 @@ export const fastify = {
12
13
  features: [
13
14
  swagger,
14
15
  websocket,
15
- mongodb
16
+ mongodb,
17
+ postgresql
16
18
  ]
17
19
  };
@@ -10,6 +10,13 @@ export const mongodb = {
10
10
  '@types/node': '^18.0.0',
11
11
  'prisma': '6.19'
12
12
  },
13
+ group: 'database',
14
+ exclusive: true,
15
+ additionalMessages: [
16
+ 'This feature will set up Prisma with MongoDB as the database provider.',
17
+ 'It comes with a premade test model in the prisma schema as well as a plugin for Fastify',
18
+ 'Run `pnpx prisma generate` and `pnpx prisma db push` to set up your database after adding this feature.'
19
+ ],
13
20
  patches: [
14
21
  {
15
22
  targetPath: 'tsconfig.json',
@@ -0,0 +1,40 @@
1
+ export const postgresql = {
2
+ key: 'postgresql',
3
+ name: 'PostgreSQL support',
4
+ description: 'Adds PostgreSQL support via prisma directly to your Fastify application.',
5
+ templateRoot: 'web/backend/fastify/postgresql',
6
+ dependencies: {
7
+ '@prisma/adapter-pg': '^7.4.2',
8
+ '@prisma/client': '^7.4.2',
9
+ 'pg': '^8.20.0'
10
+ },
11
+ devDependencies: {
12
+ '@types/node': '^18.0.0',
13
+ '@types/pg': '^8.18.0',
14
+ 'prisma': '^7.4.2'
15
+ },
16
+ group: 'database',
17
+ exclusive: true,
18
+ additionalMessages: [
19
+ 'This feature will set up Prisma with PostgreSQL as the database provider.',
20
+ 'It comes with a premade test model in the prisma schema as well as a plugin for Fastify',
21
+ 'Run `pnpx prisma generate` and `pnpx prisma db push` to set up your database after adding this feature.'
22
+ ],
23
+ patches: [
24
+ {
25
+ targetPath: 'tsconfig.json',
26
+ operations: [
27
+ {
28
+ type: 'insertAfter',
29
+ pattern: "\"skipLibCheck\": true",
30
+ insert: ",\n\t\"types\": [\"node\"]"
31
+ },
32
+ {
33
+ type: 'insertAfter',
34
+ pattern: "\"src\"",
35
+ insert: ", \"prisma.config.ts\""
36
+ }
37
+ ]
38
+ }
39
+ ]
40
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nolly-template",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "All of my opiniated templates in one place. This is a CLI tool to create a new project based on one of my templates.",
5
5
  "license": "MIT",
6
6
  "author": "Nolly",
@@ -46,13 +46,12 @@
46
46
  "package.json"
47
47
  ],
48
48
  "dependencies": {
49
- "fs-extra": "^11.3.4",
50
- "prompts": "^2.4.2"
49
+ "enquirer": "^2.4.1",
50
+ "fs-extra": "^11.3.4"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/fs-extra": "^11.0.4",
54
54
  "@types/node": "^25.5.0",
55
- "@types/prompts": "^2.4.9",
56
55
  "tsx": "^4.21.0",
57
56
  "typescript": "^5.9.3"
58
57
  },
@@ -78,7 +77,7 @@
78
77
  }
79
78
  ],
80
79
  "scripts": {
81
- "build": "rm -rf dist && tsc --build tsconfig.cli.json",
80
+ "build": "rm -rf dist && tsc",
82
81
  "dev": "tsx src/index.ts",
83
82
  "start": "node dist/index.js"
84
83
  }
@@ -0,0 +1 @@
1
+ DATABASE_URL="mongodb://localhost:27017/mydatabase"
@@ -0,0 +1,24 @@
1
+ generator client {
2
+ provider = "prisma-client"
3
+ output = "../src/prisma"
4
+ }
5
+
6
+ datasource db {
7
+ provider = "postgresql"
8
+ }
9
+
10
+ model User {
11
+ id String @id @default(uuid())
12
+ email String @unique
13
+ name String?
14
+ posts Post[]
15
+ }
16
+
17
+ model Post {
18
+ id String @id @default(uuid())
19
+ title String
20
+ content String?
21
+ published Boolean @default(false)
22
+ author User @relation(fields: [authorId], references: [id])
23
+ authorId String
24
+ }
@@ -0,0 +1,12 @@
1
+ import 'dotenv/config'
2
+ import { defineConfig, env } from 'prisma/config'
3
+
4
+ export default defineConfig({
5
+ schema: 'prisma/schema.prisma',
6
+ migrations: {
7
+ path: 'prisma/migrations',
8
+ },
9
+ datasource: {
10
+ url: env['DATABASE_URL'],
11
+ },
12
+ })
@@ -0,0 +1,25 @@
1
+ import FastifyPlugin from 'fastify-plugin'
2
+ import { FastifyInstance } from 'fastify'
3
+ import { PrismaClient } from '../prisma/client'
4
+ import { PrismaPg } from '@prisma/adapter-pg'
5
+ import { env } from 'prisma/config'
6
+
7
+ declare module 'fastify' {
8
+ interface FastifyInstance {
9
+ database: PrismaClient
10
+ }
11
+ }
12
+
13
+ function hidePassword(): string {
14
+ const url = env('DATABASE_URL')
15
+ return url.replace(/\/\/(.*):(.*)@/, '//$1:****@')
16
+ }
17
+
18
+ export default FastifyPlugin(async (fastify: FastifyInstance) => {
19
+ const adapter = new PrismaPg({ connectionString: env('DATABASE_URL') })
20
+ const database = new PrismaClient({ adapter })
21
+ await database.connect()
22
+ fastify.log.info(`Database connected to ${hidePassword()}`)
23
+ fastify.decorate('database', database)
24
+ fastify.addHook('onClose', async (fastify) => await fastify.database.disconnect())
25
+ })