@vevedh/create-feathers-app 0.1.0-rc.5 → 0.1.0-rc.6
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/README.md +7 -7
- package/bin/create-feathers-app.mjs +1 -0
- package/lib/compose.mjs +248 -0
- package/lib/scaffold.mjs +18 -13
- package/lib/services.mjs +1 -1
- package/package.json +1 -1
- package/templates/daisyui-ssg/package.json +6 -6
- package/templates/daisyui-ssr/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Générez des applications FeathersJS v5 avec Vue 3, Pinia, Vite, Quasar 2 ou Tailwind CSS + daisyUI.
|
|
4
4
|
|
|
5
|
-
Le CLI est indépendant du runtime [`@vevedh/vite-plugin-feathersjs`](https://www.npmjs.com/package/@vevedh/vite-plugin-feathersjs) (version 0.2.
|
|
5
|
+
Le CLI est indépendant du runtime [`@vevedh/vite-plugin-feathersjs`](https://www.npmjs.com/package/@vevedh/vite-plugin-feathersjs) (version 0.2.7). La version `0.1.0-rc.5` est la candidate présente dans les sources locales. Ne pas présumer de sa publication npm ; vérifier les versions du registre avant installation. La PWA reste optionnelle pour une SPA.
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
@@ -10,13 +10,13 @@ Le CLI est indépendant du runtime [`@vevedh/vite-plugin-feathersjs`](https://ww
|
|
|
10
10
|
# Préversion disponible sur npm : 0.1.0-rc.1
|
|
11
11
|
npm exec --yes --package=@vevedh/create-feathers-app@0.1.0-rc.1 -- create-feathers-app portail --ui daisyui --render spa
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# Candidate RC5, depuis les sources mises en stage :
|
|
14
14
|
node .release/create-feathers-app/bin/create-feathers-app.mjs portail-ssr --ui quasar2 --render ssr
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
| Interface | SPA | SSR | SSG |
|
|
18
18
|
| --- | --- | --- | --- |
|
|
19
|
-
| Quasar 2 | Générable | Générable, validation Windows
|
|
19
|
+
| Quasar 2 | Générable | Générable, validation Windows complète à vérifier | Bloqué |
|
|
20
20
|
| daisyUI 5 | Générable | Générable | Générable |
|
|
21
21
|
|
|
22
22
|
## PWA optionnelle (SPA uniquement)
|
|
@@ -40,7 +40,7 @@ Le générateur ajoute le module `api/services/tickets` et son inscription dans
|
|
|
40
40
|
|
|
41
41
|
## Profils d'identité et de bases
|
|
42
42
|
|
|
43
|
-
La création de projet accepte `--auth none|jwt|oauth2|oidc|keycloak`, `--schema typebox|json|zod` et `--db main:memory,audit:mongodb` avec alias nommés. **Les
|
|
43
|
+
La création de projet accepte `--auth none|jwt|oauth2|oidc|keycloak`, `--schema typebox|json|zod` et `--db main:memory,audit:mongodb` avec alias nommés. **Les profils JWT disposent de modules Feathers/CASL et les providers nommés de modules serveur ; le bootstrap HTTP sécurisé et les tests DB restent à certifier. Les autres fournisseurs externes sont des profils de configuration, pas des intégrations opérationnelles.** L'API échoue volontairement au démarrage jusqu'au raccordement et à la certification du fournisseur ou du SGBD.
|
|
44
44
|
|
|
45
45
|
Les projets sans authentification sont exclusivement des démonstrations locales. Ils ne doivent jamais être exposés tels quels sur Internet. Aucun secret, mot de passe ou jeton n'est inclus dans les templates.
|
|
46
46
|
|
|
@@ -57,10 +57,10 @@ SPA et daisyUI SSR/SSG utilisent `http://127.0.0.1:5173`. Quasar SSR utilise `ht
|
|
|
57
57
|
|
|
58
58
|
La documentation publique VitePress figure dans `docs/cli` du dépôt et sera déployée sur GitHub Pages après activation du workflow de publication.
|
|
59
59
|
|
|
60
|
-
## API Feathers intégrée à Vite en développement (
|
|
60
|
+
## API Feathers intégrée à Vite en développement (RC5)
|
|
61
61
|
|
|
62
62
|
Pour les applications SPA, `--dev-api vite` est désormais la valeur par défaut.
|
|
63
|
-
Le plugin public `@vevedh/vite-plugin-feathersjs@0.2.
|
|
63
|
+
Le plugin public `@vevedh/vite-plugin-feathersjs@0.2.7` est chargé depuis
|
|
64
64
|
`vite.config.ts` et monte les services sur `/api` pendant `vite dev`.
|
|
65
65
|
Il n'y a pas de second serveur REST ni de proxy REST à démarrer.
|
|
66
66
|
|
|
@@ -77,4 +77,4 @@ node .release/create-feathers-app/bin/create-feathers-app.mjs mon-spa --ui quasa
|
|
|
77
77
|
**Production** : le plugin Vite s'exécute uniquement en développement.
|
|
78
78
|
Le serveur `api/server.mjs` autonome reste disponible et obligatoire pour
|
|
79
79
|
le déploiement ; `vite preview` n'héberge pas l'API.
|
|
80
|
-
|
|
80
|
+
La version `0.2.7` du plugin doit être certifiée et publiée avant une installation depuis npm.
|
|
@@ -18,6 +18,7 @@ Usage: create-feathers-app <name> [options]
|
|
|
18
18
|
--adapter <koa|express> Default koa
|
|
19
19
|
--transport <rest|both> Default rest (both only for SPA)
|
|
20
20
|
--pwa <on|off> Optional offline SPA shell (default off, no API caching)
|
|
21
|
+
--compose <off|dev|prod|both> Generate Docker Compose for SPA (default off)
|
|
21
22
|
--dev-api <vite|separate> SPA default vite; SSR/SSG always separate
|
|
22
23
|
--dry-run Preview files without writing
|
|
23
24
|
--json Machine-readable output
|
package/lib/compose.mjs
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/** Opt-in, loopback-only Docker Compose profiles for Vite-first SPA applications.
|
|
2
|
+
* Authentication and database capability guards from scaffold.mjs stay active.
|
|
3
|
+
*/
|
|
4
|
+
const composeModes = new Set(['dev', 'prod', 'both'])
|
|
5
|
+
|
|
6
|
+
function requireExact(files, path, before, after) {
|
|
7
|
+
const source = files.get(path)
|
|
8
|
+
if (typeof source !== 'string' || source.split(before).length !== 2) {
|
|
9
|
+
throw new Error(`Unexpected ${path} template; refusing Docker generation`)
|
|
10
|
+
}
|
|
11
|
+
files.set(path, source.replace(before, after))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function composeReadme(options) {
|
|
15
|
+
const dev = options.compose === 'dev' || options.compose === 'both'
|
|
16
|
+
const prod = options.compose === 'prod' || options.compose === 'both'
|
|
17
|
+
return `# Docker Compose — ${options.name}
|
|
18
|
+
|
|
19
|
+
Ces fichiers déploient le squelette Vite-first, **pas une application déjà sécurisée pour Internet**.
|
|
20
|
+
Le service de démonstration \`notes\` est en mémoire et sans authentification. Les ports HTTP sont publiés sur \`127.0.0.1\` uniquement.
|
|
21
|
+
Aucun port de base de données n'est exposé. Le générateur **n'active pas** les profils JWT, OAuth ou bases externes : leurs garde-fous existants continuent de refuser le démarrage tant que leur intégration n'est pas certifiée.
|
|
22
|
+
|
|
23
|
+
## Prérequis
|
|
24
|
+
|
|
25
|
+
- Docker Desktop avec Docker Compose v2.
|
|
26
|
+
- Node.js compatible et Corepack/pnpm pour produire \`pnpm-lock.yaml\`.
|
|
27
|
+
- Vérifier les dépendances, leurs licences et vulnérabilités avant tout déploiement.
|
|
28
|
+
|
|
29
|
+
${dev ? `## Développement
|
|
30
|
+
|
|
31
|
+
\`docker compose -f compose.dev.yaml up --build\`
|
|
32
|
+
|
|
33
|
+
Ouvrir http://127.0.0.1:5173. Le serveur Vite monte Feathers via le plugin Vite, sans lancer un deuxième processus d'API. Aucun autre port n'est publié.
|
|
34
|
+
|
|
35
|
+
Arrêt : \`docker compose -f compose.dev.yaml down\` (le volume de dépendances est conservé).
|
|
36
|
+
|
|
37
|
+
` : ''}${prod ? `## Image de déploiement (prototype isolé)
|
|
38
|
+
|
|
39
|
+
Créer et réviser le lockfile à la racine du projet :
|
|
40
|
+
|
|
41
|
+
\`corepack pnpm install --lockfile-only\`
|
|
42
|
+
|
|
43
|
+
\`docker compose -f compose.prod.yaml up --build -d\`
|
|
44
|
+
|
|
45
|
+
Ouvrir http://127.0.0.1:8080. Nginx sert la SPA et relaie \`/api/\` à Feathers et ${options.transport === 'both' ? '\`/socket.io/\` au serveur temps réel.' : 'aucun Socket.IO (REST uniquement).'} Le backend n'a pas de port publié sur l'hôte.
|
|
46
|
+
|
|
47
|
+
Arrêt : \`docker compose -f compose.prod.yaml down\`.
|
|
48
|
+
|
|
49
|
+
**Avant une réelle mise en production :** remplacer le service notes non protégé, activer JWT/RBAC et une persistance durable, vérifier les migrations, isoler les secrets, mettre en place le reverse proxy HTTPS et la politique d'origines, vérifier les journaux et sauvegardes et exécuter les E2E REST/Socket.IO de la cible. Ne pas changer l'interface hôte de \`127.0.0.1\` vers \`0.0.0.0\` sans ces contrôles.
|
|
50
|
+
|
|
51
|
+
` : ''}## Particularités
|
|
52
|
+
|
|
53
|
+
- Choix générés : ${options.ui} SPA, ${options.adapter}, ${options.transport}, PWA ${options.pwa}.
|
|
54
|
+
- Le serveur API autonome peut écouter sur toutes les interfaces **du conteneur** uniquement via \`FEATHERS_BIND_HOST=0.0.0.0\` ; le réseau \`backend\` est interne et non publié.
|
|
55
|
+
- Le mode PWA, lorsqu'il est sélectionné, ne doit jamais mettre \`/api/\` ou \`/socket.io/\` en cache.
|
|
56
|
+
- Les images de production sont construites depuis un lockfile audité ; ne jamais copier de \`.env\` réel dans les images.
|
|
57
|
+
- Les ports peuvent être personnalisés uniquement côté loopback via \`P4_DEV_PORT\` et \`P4_HTTP_PORT\`.
|
|
58
|
+
`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function devCompose(options) {
|
|
62
|
+
return `name: ${options.name}-dev
|
|
63
|
+
services:
|
|
64
|
+
app:
|
|
65
|
+
image: node:24-bookworm-slim
|
|
66
|
+
working_dir: /workspace
|
|
67
|
+
command: ["sh", "-c", "corepack pnpm install --no-frozen-lockfile && corepack pnpm exec vite --host 0.0.0.0 --port 5173 --strictPort"]
|
|
68
|
+
environment:
|
|
69
|
+
NODE_ENV: development
|
|
70
|
+
VITE_FEATHERS_REALTIME: "${options.transport === 'both' ? '1' : '0'}"
|
|
71
|
+
P4_DEV_PORT: "\${P4_DEV_PORT:-5173}"
|
|
72
|
+
volumes:
|
|
73
|
+
- .:/workspace
|
|
74
|
+
- dependencies:/workspace/node_modules
|
|
75
|
+
ports:
|
|
76
|
+
- "127.0.0.1:\${P4_DEV_PORT:-5173}:5173"
|
|
77
|
+
security_opt:
|
|
78
|
+
- no-new-privileges:true
|
|
79
|
+
volumes:
|
|
80
|
+
dependencies:
|
|
81
|
+
`
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function prodCompose(options) {
|
|
85
|
+
return `name: ${options.name}-prod
|
|
86
|
+
services:
|
|
87
|
+
api:
|
|
88
|
+
build:
|
|
89
|
+
context: .
|
|
90
|
+
dockerfile: infra/compose/Dockerfile.api
|
|
91
|
+
environment:
|
|
92
|
+
NODE_ENV: production
|
|
93
|
+
FEATHERS_BIND_HOST: "0.0.0.0"
|
|
94
|
+
FEATHERS_ADAPTER: "${options.adapter}"
|
|
95
|
+
FEATHERS_SOCKETIO: "${options.transport === 'both' ? '1' : '0'}"
|
|
96
|
+
FEATHERS_API_PORT: "3030"
|
|
97
|
+
FEATHERS_SOCKET_PORT: "3031"
|
|
98
|
+
FEATHERS_PUBLIC_ORIGIN: "http://127.0.0.1:\${P4_HTTP_PORT:-8080}"
|
|
99
|
+
FEATHERS_PREVIEW_ORIGIN: "http://127.0.0.1:\${P4_HTTP_PORT:-8080}"
|
|
100
|
+
expose:
|
|
101
|
+
- "3030"
|
|
102
|
+
${options.transport === 'both' ? ' - "3031"\n' : ''} networks:
|
|
103
|
+
- backend
|
|
104
|
+
security_opt:
|
|
105
|
+
- no-new-privileges:true
|
|
106
|
+
read_only: true
|
|
107
|
+
tmpfs:
|
|
108
|
+
- /tmp
|
|
109
|
+
healthcheck:
|
|
110
|
+
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3030/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
|
111
|
+
interval: 10s
|
|
112
|
+
timeout: 5s
|
|
113
|
+
retries: 8
|
|
114
|
+
start_period: 20s
|
|
115
|
+
web:
|
|
116
|
+
build:
|
|
117
|
+
context: .
|
|
118
|
+
dockerfile: infra/compose/Dockerfile.web
|
|
119
|
+
depends_on:
|
|
120
|
+
api:
|
|
121
|
+
condition: service_healthy
|
|
122
|
+
ports:
|
|
123
|
+
- "127.0.0.1:\${P4_HTTP_PORT:-8080}:8080"
|
|
124
|
+
networks:
|
|
125
|
+
- frontend
|
|
126
|
+
- backend
|
|
127
|
+
security_opt:
|
|
128
|
+
- no-new-privileges:true
|
|
129
|
+
networks:
|
|
130
|
+
frontend:
|
|
131
|
+
backend:
|
|
132
|
+
internal: true
|
|
133
|
+
`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const apiDockerfile = `FROM node:24-bookworm-slim
|
|
137
|
+
WORKDIR /app
|
|
138
|
+
RUN corepack enable
|
|
139
|
+
COPY package.json pnpm-lock.yaml ./
|
|
140
|
+
RUN corepack pnpm install --frozen-lockfile --prod
|
|
141
|
+
COPY api ./api
|
|
142
|
+
USER node
|
|
143
|
+
EXPOSE 3030 3031
|
|
144
|
+
CMD ["node", "api/server.mjs"]
|
|
145
|
+
`
|
|
146
|
+
|
|
147
|
+
const webDockerfile = `FROM node:24-bookworm-slim AS builder
|
|
148
|
+
WORKDIR /app
|
|
149
|
+
RUN corepack enable
|
|
150
|
+
COPY package.json pnpm-lock.yaml ./
|
|
151
|
+
RUN corepack pnpm install --frozen-lockfile
|
|
152
|
+
COPY . .
|
|
153
|
+
RUN corepack pnpm run build
|
|
154
|
+
|
|
155
|
+
FROM nginx:stable-alpine
|
|
156
|
+
COPY infra/compose/nginx.conf /etc/nginx/conf.d/default.conf
|
|
157
|
+
COPY --from=builder /app/dist /usr/share/nginx/html
|
|
158
|
+
EXPOSE 8080
|
|
159
|
+
`
|
|
160
|
+
|
|
161
|
+
function nginx(options) {
|
|
162
|
+
return `server {
|
|
163
|
+
listen 8080;
|
|
164
|
+
server_name _;
|
|
165
|
+
root /usr/share/nginx/html;
|
|
166
|
+
index index.html;
|
|
167
|
+
autoindex off;
|
|
168
|
+
server_tokens off;
|
|
169
|
+
add_header X-Content-Type-Options nosniff always;
|
|
170
|
+
add_header Referrer-Policy no-referrer always;
|
|
171
|
+
add_header X-Frame-Options DENY always;
|
|
172
|
+
location = /api { return 404; }
|
|
173
|
+
location /api/ {
|
|
174
|
+
proxy_pass http://api:3030/api/;
|
|
175
|
+
proxy_http_version 1.1;
|
|
176
|
+
proxy_set_header Host $host;
|
|
177
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
178
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
179
|
+
proxy_read_timeout 30s;
|
|
180
|
+
}
|
|
181
|
+
${options.transport === 'both' ? ` location /socket.io/ {
|
|
182
|
+
proxy_pass http://api:3031;
|
|
183
|
+
proxy_http_version 1.1;
|
|
184
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
185
|
+
proxy_set_header Connection "upgrade";
|
|
186
|
+
proxy_set_header Host $host;
|
|
187
|
+
proxy_read_timeout 65s;
|
|
188
|
+
}
|
|
189
|
+
` : ` location /socket.io/ { return 404; }
|
|
190
|
+
`} location / {
|
|
191
|
+
try_files $uri $uri/ /index.html;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
`
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const dockerIgnore = `node_modules
|
|
198
|
+
.git
|
|
199
|
+
.agents
|
|
200
|
+
.ai
|
|
201
|
+
.codex
|
|
202
|
+
.cursor
|
|
203
|
+
.claude
|
|
204
|
+
.gemini
|
|
205
|
+
.windsurf
|
|
206
|
+
.mcp.json
|
|
207
|
+
.env
|
|
208
|
+
.env.*
|
|
209
|
+
!.env.example
|
|
210
|
+
database.env
|
|
211
|
+
*.zip
|
|
212
|
+
*.log
|
|
213
|
+
coverage
|
|
214
|
+
dist
|
|
215
|
+
.vite
|
|
216
|
+
.quasar
|
|
217
|
+
docs-private
|
|
218
|
+
test-results
|
|
219
|
+
playwright-report
|
|
220
|
+
`
|
|
221
|
+
|
|
222
|
+
export function enableCompose(files, options) {
|
|
223
|
+
if (!composeModes.has(options.compose) || options.render !== 'spa') {
|
|
224
|
+
throw new Error('Compose generation supports only SPA and compose dev|prod|both')
|
|
225
|
+
}
|
|
226
|
+
const dev = options.compose === 'dev' || options.compose === 'both'
|
|
227
|
+
const prod = options.compose === 'prod' || options.compose === 'both'
|
|
228
|
+
if (dev && options.devApi !== 'vite') {
|
|
229
|
+
throw new Error('Compose development requires --dev-api vite for a single Feathers/Vite listener')
|
|
230
|
+
}
|
|
231
|
+
const created = new Map([['COMPOSE.md', composeReadme(options)]])
|
|
232
|
+
if (dev) created.set('compose.dev.yaml', devCompose(options))
|
|
233
|
+
if (prod) {
|
|
234
|
+
created.set('compose.prod.yaml', prodCompose(options))
|
|
235
|
+
created.set('infra/compose/Dockerfile.api', apiDockerfile)
|
|
236
|
+
created.set('infra/compose/Dockerfile.web', webDockerfile)
|
|
237
|
+
created.set('infra/compose/nginx.conf', nginx(options))
|
|
238
|
+
// The only Docker interface bound to all container addresses is the private API listener.
|
|
239
|
+
requireExact(files, 'api/server.mjs', "const realtime = process.env.FEATHERS_SOCKETIO === '1'", "const bindHost = process.env.FEATHERS_BIND_HOST === '0.0.0.0' ? '0.0.0.0' : '127.0.0.1'\nconst realtime = process.env.FEATHERS_SOCKETIO === '1'")
|
|
240
|
+
requireExact(files, 'api/server.mjs', "host: '127.0.0.1' } : false", 'host: bindHost } : false')
|
|
241
|
+
requireExact(files, 'api/server.mjs', "httpServer.listen(port, '127.0.0.1', resolve)", 'httpServer.listen(port, bindHost, resolve)')
|
|
242
|
+
}
|
|
243
|
+
created.set('.dockerignore', dockerIgnore)
|
|
244
|
+
for (const [key, content] of created) {
|
|
245
|
+
if (files.has(key)) throw new Error(`Compose file conflicts with a template: ${key}`)
|
|
246
|
+
files.set(key, content)
|
|
247
|
+
}
|
|
248
|
+
}
|
package/lib/scaffold.mjs
CHANGED
|
@@ -2,8 +2,9 @@ import { mkdir, lstat, readdir, readFile, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from 'node:path'
|
|
4
4
|
import { buildProjectPlan } from './planner/plan.mjs'
|
|
5
|
+
import { enableCompose } from './compose.mjs'
|
|
5
6
|
|
|
6
|
-
const KEYS = new Set(['--name', '--dir', '--ui', '--render', '--adapter', '--transport', '--dry-run', '--json', '--schema', '--auth', '--db', '--pwa', '--dev-api'])
|
|
7
|
+
const KEYS = new Set(['--name', '--dir', '--ui', '--render', '--adapter', '--transport', '--dry-run', '--json', '--schema', '--auth', '--db', '--pwa', '--dev-api', '--compose'])
|
|
7
8
|
const CASES = Object.freeze({
|
|
8
9
|
'quasar2:spa': 'quasar-spa',
|
|
9
10
|
'quasar2:ssr': 'quasar-ssr',
|
|
@@ -43,8 +44,8 @@ function envChange(text, options) {
|
|
|
43
44
|
}
|
|
44
45
|
function spaManifest(o) {
|
|
45
46
|
const dependencies = {
|
|
46
|
-
'@vevedh/vite-plugin-feathersjs':'0.2.
|
|
47
|
-
'@feathersjs/koa':'
|
|
47
|
+
'@vevedh/vite-plugin-feathersjs':'0.2.7', '@feathersjs/feathers':'5.0.50', '@feathersjs/errors':'5.0.50',
|
|
48
|
+
'@feathersjs/koa':'5.0.51', '@feathersjs/express':'5.0.51', '@feathersjs/socketio':'5.0.51',
|
|
48
49
|
vue:'^3.5.44', pinia:'^3.0.4', 'socket.io-client':'^4.8.4',
|
|
49
50
|
}
|
|
50
51
|
const devDependencies = {
|
|
@@ -87,7 +88,7 @@ function enableSpaViteRuntime(files, options) {
|
|
|
87
88
|
}
|
|
88
89
|
const realtime = options.transport === 'both'
|
|
89
90
|
const socket = realtime
|
|
90
|
-
? "{ port: 3031, host: '127.0.0.1', origins: ['http://127.0.0.1:5173
|
|
91
|
+
? "{ port: 3031, host: '127.0.0.1', origins: ['http://127.0.0.1:' + String(Number(process.env.P4_DEV_PORT || 5173))] }"
|
|
91
92
|
: 'false'
|
|
92
93
|
const runtime = `feathers({
|
|
93
94
|
serviceFiles: 'api/services/*/*.service.mjs',
|
|
@@ -155,7 +156,7 @@ function enableSpaPwa(files, options) {
|
|
|
155
156
|
export function createGenerator({outputRoot, templatesRoot, examplesRoot = templatesRoot, reportRoot = outputRoot}) {
|
|
156
157
|
const output = resolve(outputRoot), templates = resolve(templatesRoot), report = resolve(reportRoot)
|
|
157
158
|
function parse(args) {
|
|
158
|
-
const o={ui:'daisyui',render:'spa',adapter:'koa',transport:'rest',schema:'typebox',auth:'none',db:'main:memory',pwa:'off',devApi:undefined,dryRun:false,json:false}
|
|
159
|
+
const o={ui:'daisyui',render:'spa',adapter:'koa',transport:'rest',schema:'typebox',auth:'none',db:'main:memory',pwa:'off',compose:'off',devApi:undefined,dryRun:false,json:false}
|
|
159
160
|
const seen=new Set()
|
|
160
161
|
if (args.length && !args[0].startsWith('-')) {o.name=args[0];args=args.slice(1);if(args.includes('--name'))throw new Error('Use either positional name or --name, not both')}
|
|
161
162
|
for(let i=0;i<args.length;i++) {
|
|
@@ -171,6 +172,9 @@ export function createGenerator({outputRoot, templatesRoot, examplesRoot = templ
|
|
|
171
172
|
if (o.devApi === 'vite' && o.render !== 'spa') throw new Error('--dev-api vite is supported for SPA only; SSR/SSG require separate API')
|
|
172
173
|
if(!['koa','express'].includes(o.adapter))throw new Error('Only koa|express adapters are supported')
|
|
173
174
|
if(!['rest','both'].includes(o.transport))throw new Error('Only rest|both are supported')
|
|
175
|
+
if (!['off','dev','prod','both'].includes(o.compose)) throw new Error('Invalid --compose: expected off|dev|prod|both')
|
|
176
|
+
if (o.compose !== 'off' && o.render !== 'spa') throw new Error('Docker Compose currently supports SPA only; SSR/SSG must be certified separately')
|
|
177
|
+
if (o.compose !== 'off' && o.devApi !== 'vite' && o.compose !== 'prod') throw new Error('Compose dev requires --dev-api vite')
|
|
174
178
|
if (!['on','off'].includes(o.pwa)) throw new Error('Invalid --pwa: expected on|off')
|
|
175
179
|
if (o.pwa==='on' && o.render!=='spa') throw new Error('PWA is supported for SPA only; SSR/SSG require separate certification')
|
|
176
180
|
if (!['typebox','json','zod'].includes(o.schema)) throw new Error('Invalid --schema: expected typebox|json|zod')
|
|
@@ -245,12 +249,12 @@ export function createGenerator({outputRoot, templatesRoot, examplesRoot = templ
|
|
|
245
249
|
files.set('api/config/profile.json',JSON.stringify({schema:o.schema,auth:o.auth,databases:o.databases,primary:o.databases[0].alias,operational:o.auth==='none' && o.databases.every(db=>db.adapter==='memory')},null,2)+'\n')
|
|
246
250
|
const depMap={typebox:['@feathersjs/typebox','ajv'],json:['ajv'],zod:['zod']}
|
|
247
251
|
// Dependencies for the service subcommand; project options alone create no arbitrary services.
|
|
248
|
-
for(const dep of depMap[o.schema])manifest.dependencies[dep] ||= dep==='ajv'?'^8.17.1':dep==='zod'?'^4.1.0':'
|
|
249
|
-
if(o.auth==='jwt')manifest.dependencies['@feathersjs/authentication']='
|
|
250
|
-
if(o.auth==='oauth2')manifest.dependencies['@feathersjs/authentication-oauth']='
|
|
252
|
+
for(const dep of depMap[o.schema])manifest.dependencies[dep] ||= dep==='ajv'?'^8.17.1':dep==='zod'?'^4.1.0':'5.0.51'
|
|
253
|
+
if(o.auth==='jwt')manifest.dependencies['@feathersjs/authentication']='5.0.50'
|
|
254
|
+
if(o.auth==='oauth2')manifest.dependencies['@feathersjs/authentication-oauth']='5.0.51'
|
|
251
255
|
for(const db of o.databases) {
|
|
252
256
|
const drivers={mongodb:['@feathersjs/mongodb','mongodb'],postgresql:['@feathersjs/knex','knex','pg'],mariadb:['@feathersjs/knex','knex','mysql2'],mysql:['@feathersjs/knex','knex','mysql2'],sqlite:['@feathersjs/knex','knex','better-sqlite3']}
|
|
253
|
-
for(const dep of drivers[db.adapter]||[])manifest.dependencies[dep] ||= dep === 'mongodb' ? '6.19.0' : dep === '@feathersjs/mongodb' ? '5.0.51' : '*'
|
|
257
|
+
for(const dep of drivers[db.adapter]||[])manifest.dependencies[dep] ||= dep === 'mongodb' ? '6.19.0' : dep === '@feathersjs/mongodb' ? '5.0.51' : dep === '@feathersjs/knex' ? '5.0.50' : '*'
|
|
254
258
|
}
|
|
255
259
|
files.set('package.json',JSON.stringify(manifest,null,2)+'\n')
|
|
256
260
|
if (o.auth === 'jwt' && o.databases.some(db => ['postgresql','mariadb','mongodb'].includes(db.adapter))) {
|
|
@@ -270,9 +274,10 @@ export function createGenerator({outputRoot, templatesRoot, examplesRoot = templ
|
|
|
270
274
|
const env = declarations.flatMap(db => [db.envPrefix + '_HOST=127.0.0.1', db.envPrefix + '_PORT=' + (db.adapter === 'postgresql' ? '5432' : db.adapter === 'mariadb' ? '3306' : '27017'), db.envPrefix + '_USER=', db.envPrefix + '_PASSWORD=', db.envPrefix + '_NAME='])
|
|
271
275
|
files.set('database.env.example', env.join('\n') + '\n')
|
|
272
276
|
}
|
|
277
|
+
if (o.compose !== 'off') enableCompose(files, o)
|
|
273
278
|
const list=[...files.keys()].sort()
|
|
274
279
|
const result={name:o.name,ui:o.ui,render:o.render,adapter:o.adapter,transport:o.transport,
|
|
275
|
-
destination:relative(report,o.destination).split(sep).join('/'),schema:o.schema,auth:o.auth,pwa:o.pwa,devApi:o.devApi,databases:o.databases,files:list}
|
|
280
|
+
destination:relative(report,o.destination).split(sep).join('/'),schema:o.schema,auth:o.auth,pwa:o.pwa,compose:o.compose,devApi:o.devApi,databases:o.databases,files:list}
|
|
276
281
|
if(o.dryRun)return result
|
|
277
282
|
// Do not follow symlinks and refuse any existing destination, including empty ones.
|
|
278
283
|
await mkdir(output,{recursive:true})
|
|
@@ -294,9 +299,9 @@ function quasarSSRManifest(o){
|
|
|
294
299
|
scripts:{'dev:ssr':'quasar dev -m ssr','build:ssr':'quasar build -m ssr',build:'quasar build -m ssr',
|
|
295
300
|
typecheck:'vue-tsc --noEmit', 'dev:api':'node --env-file=.env.example api/server.mjs',
|
|
296
301
|
'serve:production':'node dist/ssr/index.js',postinstall:'quasar prepare', 'dev:edge':'node scripts/edge-proxy.mjs'},
|
|
297
|
-
dependencies:{'@vevedh/vite-plugin-feathersjs':'0.2.
|
|
298
|
-
'@feathersjs/express':'
|
|
299
|
-
'@feathersjs/rest-client':'
|
|
302
|
+
dependencies:{'@vevedh/vite-plugin-feathersjs':'0.2.7', '@feathersjs/feathers':'5.0.50', '@feathersjs/koa':'5.0.51',
|
|
303
|
+
'@feathersjs/express':'5.0.51', '@feathersjs/errors':'5.0.50', '@feathersjs/socketio':'5.0.51',
|
|
304
|
+
'@feathersjs/rest-client':'5.0.50', '@feathersjs/socketio-client':'5.0.50',
|
|
300
305
|
'@quasar/extras':'^1.18.0',pinia:'^3.0.4',quasar:'^2.35.2','socket.io-client':'^4.8.4',
|
|
301
306
|
vue:'^3.5.44','vue-router':'^4.6.4',express:'^4.22.3','serialize-javascript':'^7.1.4'},
|
|
302
307
|
devDependencies:{'@quasar/app-vite':'2.6.2','@types/node':'^22.0.0',typescript:'^5.9.3','vue-tsc':'^3.3.14',
|
package/lib/services.mjs
CHANGED
|
@@ -175,7 +175,7 @@ export async function generateService(opts, cwd=process.cwd(), io={writeFile}) {
|
|
|
175
175
|
if(connection.adapter!=='memory')throw new Error('Database adapter not provisioned for service: '+connection.adapter+'. Refusing implicit in-memory fallback')
|
|
176
176
|
if(!tsReady)throw new Error('TypeScript services require a separate API runtime; Vite-embedded runtime not yet certified')
|
|
177
177
|
if(!ready)throw new Error('Authentication infrastructure not provisioned; refusing service generation')
|
|
178
|
-
const deps=schema==='zod'?{'zod':'^4.1.0'}:schema==='typebox'?{'@feathersjs/typebox':'
|
|
178
|
+
const deps=schema==='zod'?{'zod':'^4.1.0'}:schema==='typebox'?{'@feathersjs/typebox':'5.0.51','ajv':'^8.17.1'}:{'ajv':'^8.17.1'}
|
|
179
179
|
const dependencies={...manifest.dependencies}
|
|
180
180
|
for(const [name,version] of Object.entries(deps))dependencies[name]??=version
|
|
181
181
|
let manifestResult
|
package/package.json
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"serve:production": "node scripts/serve-static.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@vevedh/vite-plugin-feathersjs": "0.2.
|
|
19
|
-
"@feathersjs/feathers": "
|
|
20
|
-
"@feathersjs/errors": "
|
|
21
|
-
"@feathersjs/koa": "
|
|
22
|
-
"@feathersjs/express": "
|
|
23
|
-
"@feathersjs/socketio": "
|
|
18
|
+
"@vevedh/vite-plugin-feathersjs": "0.2.7",
|
|
19
|
+
"@feathersjs/feathers": "5.0.50",
|
|
20
|
+
"@feathersjs/errors": "5.0.50",
|
|
21
|
+
"@feathersjs/koa": "5.0.51",
|
|
22
|
+
"@feathersjs/express": "5.0.51",
|
|
23
|
+
"@feathersjs/socketio": "5.0.51",
|
|
24
24
|
"vue": "^3.5.44",
|
|
25
25
|
"pinia": "^3.0.4",
|
|
26
26
|
"socket.io-client": "^4.8.4",
|
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
"serve:production": "node scripts/serve-ssr.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@vevedh/vite-plugin-feathersjs": "0.2.
|
|
19
|
-
"@feathersjs/feathers": "
|
|
20
|
-
"@feathersjs/errors": "
|
|
21
|
-
"@feathersjs/koa": "
|
|
22
|
-
"@feathersjs/express": "
|
|
23
|
-
"@feathersjs/socketio": "
|
|
18
|
+
"@vevedh/vite-plugin-feathersjs": "0.2.7",
|
|
19
|
+
"@feathersjs/feathers": "5.0.50",
|
|
20
|
+
"@feathersjs/errors": "5.0.50",
|
|
21
|
+
"@feathersjs/koa": "5.0.51",
|
|
22
|
+
"@feathersjs/express": "5.0.51",
|
|
23
|
+
"@feathersjs/socketio": "5.0.51",
|
|
24
24
|
"vue": "^3.5.44",
|
|
25
25
|
"pinia": "^3.0.4",
|
|
26
26
|
"socket.io-client": "^4.8.4",
|