@fraza/start 0.0.0-stage → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/LICENSE +21 -0
  3. package/README.md +97 -2
  4. package/THIRD-PARTY-NOTICES.md +11 -0
  5. package/bin/agents-block.md +61 -0
  6. package/bin/cli.js +197 -0
  7. package/bin/project.js +281 -0
  8. package/bin/version.js +2 -0
  9. package/changelog.json +602 -0
  10. package/package.json +42 -4
  11. package/templates/app/(admin)/admin/[...admin]/layout.js +3 -0
  12. package/templates/app/(admin)/admin/[...admin]/loading.js +3 -0
  13. package/templates/app/(admin)/admin/[...admin]/page.js +1 -0
  14. package/templates/app/(admin)/admin/error.js +4 -0
  15. package/templates/app/(admin)/admin/layout.js +17 -0
  16. package/templates/app/(admin)/admin/not-found.js +2 -0
  17. package/templates/app/(admin)/admin/page.js +5 -0
  18. package/templates/app/(auth)/auth/[...auth]/page.js +1 -0
  19. package/templates/app/(auth)/auth/page.js +5 -0
  20. package/templates/app/(public)/[...slug]/page.js +29 -0
  21. package/templates/app/(public)/error.js +6 -0
  22. package/templates/app/(public)/layout.js +17 -0
  23. package/templates/app/(public)/not-found.js +20 -0
  24. package/templates/app/(public)/page.js +248 -0
  25. package/templates/app/.fraza/blocks.client.js +8 -0
  26. package/templates/app/.fraza/blocks.server.js +4 -0
  27. package/templates/app/.fraza/modules.client.js +6 -0
  28. package/templates/app/.fraza/modules.generated.js +3 -0
  29. package/templates/app/.fraza/public-pages.server.js +4 -0
  30. package/templates/app/.fraza/sources.css +2 -0
  31. package/templates/app/.fraza/templates.server.js +5 -0
  32. package/templates/app/global-error.js +5 -0
  33. package/templates/app/icon.svg +1 -0
  34. package/templates/app/layout.js +31 -0
  35. package/templates/app/za/[...path]/not-found.js +6 -0
  36. package/templates/app/za/[...path]/page.js +8 -0
  37. package/templates/app/za/api/[...path]/route.js +1 -0
  38. package/templates/app/za/media/[filename]/route.js +1 -0
  39. package/templates/components/fraza/blocks/README.md +207 -0
  40. package/templates/components/fraza/blocks/accordion/fields.js +19 -0
  41. package/templates/components/fraza/blocks/accordion/render.js +47 -0
  42. package/templates/components/fraza/blocks/accordion/template.js +28 -0
  43. package/templates/components/fraza/blocks/callout/fields.js +15 -0
  44. package/templates/components/fraza/blocks/callout/render.js +28 -0
  45. package/templates/components/fraza/blocks/callout/template.js +37 -0
  46. package/templates/components/fraza/blocks/icon-list/fields.js +20 -0
  47. package/templates/components/fraza/blocks/icon-list/render.js +44 -0
  48. package/templates/components/fraza/blocks/icon-list/template.js +89 -0
  49. package/templates/components/fraza/blocks/three-cards/fields.js +19 -0
  50. package/templates/components/fraza/blocks/three-cards/render.js +33 -0
  51. package/templates/components/fraza/blocks/three-cards/template.js +57 -0
  52. package/templates/components/fraza/templates/README.md +293 -0
  53. package/templates/components/fraza/templates/content.js +271 -0
  54. package/templates/components/fraza/templates/default.js +18 -0
  55. package/templates/components/fraza/templates/post-blogue.js +97 -0
  56. package/templates/instrumentation.js +21 -0
  57. package/templates/jsconfig.json +9 -0
  58. package/templates/styles/globals.css +25 -0
  59. package/templates/theme.js +25 -0
package/bin/project.js ADDED
@@ -0,0 +1,281 @@
1
+ // Étapes de génération d'un projet Fraza, sans effet de bord à l'import.
2
+ //
3
+ // `cli.js` les enchaîne ; les tests de `test/` les appellent une par une sur un
4
+ // dossier temporaire. Chaque fonction reçoit les chemins qu'elle touche : aucune
5
+ // ne lit le dossier courant d'elle-même.
6
+
7
+ import { randomBytes } from 'node:crypto';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ export const REGISTRY_HOST = 'registry.fraza.ca';
12
+ export const REGISTRY_URL = `https://${REGISTRY_HOST}/`;
13
+
14
+ /** Plancher de Node, le même que `engines.node` et que le core. */
15
+ export const MIN_NODE = [20, 12];
16
+
17
+ // Version de `create-next-app`, donc de Next, installée dans le projet généré.
18
+ // C'est le dernier correctif de la version mineure contre laquelle le core a
19
+ // été validé (`packages.next.tested` de son `supported.json`) : un correctif de
20
+ // Next ne change pas d'API, et il porte souvent une faille corrigée : 16.3.5,
21
+ // validée d'abord, est touchée par GHSA-vcvr-r3jv-pc5j, corrigée en 16.3.8,
22
+ // la version que le core déclare maintenant.
23
+ // `npm run check:contract` échoue si l'épingle quitte la version mineure
24
+ // validée ou passe sous la version validée (`pinFollowsTestedNext`).
25
+ export const CREATE_NEXT_APP_VERSION = '16.3.8';
26
+
27
+ // Entrées tolérées dans le dossier cible : métadonnées d'éditeur ou de gestion
28
+ // de versions, que ni `create-next-app` ni ce CLI n'écrivent. Toute autre entrée
29
+ // ferait écraser un fichier existant, ou échouer `create-next-app` après coup.
30
+ const ALLOWED_ENTRIES = new Set([
31
+ '.claude',
32
+ '.cursor',
33
+ '.DS_Store',
34
+ '.git',
35
+ '.gitattributes',
36
+ '.hg',
37
+ '.idea',
38
+ '.vscode',
39
+ '.zed',
40
+ 'LICENSE',
41
+ 'Thumbs.db',
42
+ ]);
43
+
44
+ // `create-next-app` écrit un AGENTS.md, et `next dev` le réécrit à chaque
45
+ // démarrage. Il ne remplace que ce qui se trouve entre ses propres marqueurs et
46
+ // laisse le reste du fichier intact : notre bloc, posé à la suite avec ses
47
+ // marqueurs à lui, survit donc aux régénérations. On le délimite pareillement
48
+ // pour pouvoir le mettre à jour sans le dupliquer.
49
+ export const AGENTS_BLOCK_START = '<!-- BEGIN:fraza-agent-rules -->';
50
+ export const AGENTS_BLOCK_END = '<!-- END:fraza-agent-rules -->';
51
+
52
+ /**
53
+ * Compare la version de Node au plancher.
54
+ * @param {string} version Par exemple `process.versions.node`.
55
+ * @returns {boolean}
56
+ */
57
+ export function isSupportedNode(version) {
58
+ const [major, minor] = String(version).split('.').map(Number);
59
+ if (major !== MIN_NODE[0]) return major > MIN_NODE[0];
60
+ return minor >= MIN_NODE[1];
61
+ }
62
+
63
+ /**
64
+ * Dit si l'épingle de `create-next-app` suit la version de Next validée par le
65
+ * core : même version majeure et mineure, et pas en dessous.
66
+ * @param {string} pin
67
+ * @param {string} tested
68
+ * @returns {boolean}
69
+ */
70
+ export function pinFollowsTestedNext(pin, tested) {
71
+ const [pinMajor, pinMinor, pinPatch] = String(pin).split('.').map(Number);
72
+ const [major, minor, patch] = String(tested).split('.').map(Number);
73
+ return pinMajor === major && pinMinor === minor && pinPatch >= patch;
74
+ }
75
+
76
+ /**
77
+ * Liste les entrées du dossier qui empêchent d'y générer un projet.
78
+ * @param {string} dir
79
+ * @returns {string[]}
80
+ */
81
+ export function findConflictingEntries(dir) {
82
+ return fs.readdirSync(dir).filter((name) => !ALLOWED_ENTRIES.has(name)).sort();
83
+ }
84
+
85
+ /**
86
+ * Ajoute, ou remplace, le bloc Fraza dans l'AGENTS.md du projet généré. Un agent
87
+ * qui ouvre le dépôt apprend ainsi d'où vient le code et où lire la doc livrée
88
+ * dans `node_modules/@fraza/core/dist/docs/`.
89
+ * @param {string} agentsPath
90
+ * @param {string} block
91
+ */
92
+ export function writeAgentsBlock(agentsPath, block) {
93
+ if (!fs.existsSync(agentsPath)) {
94
+ fs.writeFileSync(agentsPath, `${block}\n`);
95
+ return;
96
+ }
97
+
98
+ const existing = fs.readFileSync(agentsPath, 'utf-8');
99
+ const start = existing.indexOf(AGENTS_BLOCK_START);
100
+ const end = existing.indexOf(AGENTS_BLOCK_END, start);
101
+
102
+ if (start !== -1 && end !== -1) {
103
+ const before = existing.slice(0, start);
104
+ const after = existing.slice(end + AGENTS_BLOCK_END.length);
105
+ fs.writeFileSync(agentsPath, before + block + after);
106
+ return;
107
+ }
108
+
109
+ const separator = existing.length === 0 || existing.endsWith('\n') ? '\n' : '\n\n';
110
+ fs.writeFileSync(agentsPath, `${existing}${separator}${block}\n`);
111
+ }
112
+
113
+ /**
114
+ * Masque l'indicateur de développement de Next dans le `next.config.mjs` écrit
115
+ * par `create-next-app`. Le fichier est complété, jamais remplacé : avec
116
+ * Turbopack, `create-next-app` peut y brancher Tailwind (règle `turbopack.rules`
117
+ * vers `@tailwindcss/turbopack`, sans `postcss.config.mjs`). Un gabarit qui
118
+ * l'écraserait laisserait alors le projet sans aucun style.
119
+ * @param {string} configPath
120
+ * @returns {boolean} `false` si le fichier manque ou n'a pas la forme attendue.
121
+ */
122
+ export function hideDevIndicators(configPath) {
123
+ if (!fs.existsSync(configPath)) return false;
124
+
125
+ const existing = fs.readFileSync(configPath, 'utf-8');
126
+ if (existing.includes('devIndicators')) return true;
127
+
128
+ const pattern = /(const nextConfig = \{\n)/;
129
+ if (!pattern.test(existing)) return false;
130
+
131
+ fs.writeFileSync(configPath, existing.replace(pattern, '$1 devIndicators: false,\n'));
132
+ return true;
133
+ }
134
+
135
+ /**
136
+ * Complète le `package.json` écrit par `create-next-app` : `fraza-modules sync`
137
+ * avant `next dev` et `next build`, et à chaque installation.
138
+ * @param {object} pkg
139
+ * @param {Record<string, string>} siteOverrides
140
+ * @returns {object} Un nouvel objet, `pkg` n'est pas modifié.
141
+ */
142
+ export function withFrazaScripts(pkg, siteOverrides = {}) {
143
+ const next = { ...pkg, scripts: { ...pkg.scripts } };
144
+ next.scripts.postinstall = 'fraza-modules sync';
145
+ next.scripts.dev = 'fraza-modules sync && next dev';
146
+ next.scripts.build = 'fraza-modules sync && next build';
147
+ next.scripts.audit = 'npm audit --omit=dev';
148
+
149
+ const overrides = { ...siteOverrides, ...pkg.overrides };
150
+ if (Object.keys(overrides).length > 0) next.overrides = overrides;
151
+ return next;
152
+ }
153
+
154
+ /**
155
+ * Ajoute au `.gitignore` de `create-next-app` les deux règles de Fraza.
156
+ *
157
+ * Les règles `.env*` de `create-next-app` restent actives : les valeurs d'une
158
+ * machine (`.env.local`) et les secrets ne sont jamais suivis. Seul le modèle
159
+ * `.env.example`, sans aucune valeur secrète, est rendu à git. Le `.npmrc` n'est
160
+ * pas ignoré : il ne porte que les scopes du registre, dont la machine de build
161
+ * a besoin pour installer `@fraza/core`.
162
+ * @param {string} gitignore
163
+ * @returns {string}
164
+ */
165
+ export function withFrazaGitignore(gitignore) {
166
+ let next = gitignore === '' || gitignore.endsWith('\n') ? gitignore : `${gitignore}\n`;
167
+ const add = (lines) => {
168
+ next += `\n${lines.join('\n')}\n`;
169
+ };
170
+
171
+ // La négation n'agit que placée après la règle qui ignore : sans `.env*`
172
+ // plus haut, elle ne protégerait rien.
173
+ if (!/^\.env\*$/m.test(next)) {
174
+ add(['# Variables d\'environnement : jamais suivies par git.', '.env*']);
175
+ }
176
+ if (!/^!\.env\.example$/m.test(next)) {
177
+ add(['# Modèle des variables, sans aucune valeur secrète.', '!.env.example']);
178
+ }
179
+ if (!/^\/?app\/\.fraza\/$/m.test(next)) {
180
+ add(['# Généré par fraza-modules sync.', 'app/.fraza/']);
181
+ }
182
+ return next;
183
+ }
184
+
185
+ /**
186
+ * Écrit le `.npmrc` du projet : le scope `@fraza-pro` vers le registre, rien
187
+ * d'autre. `@fraza` n'y figure pas : le core et les modules gratuits viennent
188
+ * de npmjs, le registre par défaut. Aucun jeton n'y est jamais recopié :
189
+ * `npx fraza-key` ajoute le jeton le jour où un module Pro arrive. Sans
190
+ * secret, le fichier reste suivi par git, et la machine de build sait où
191
+ * chercher les modules Pro.
192
+ *
193
+ * Le fichier est écrit ici, et non par `fraza-key` du core, parce qu'il doit
194
+ * exister avant l'installation de `@fraza/core`. Il n'est pas non plus copié
195
+ * depuis les gabarits : npm exclut toujours les `.npmrc` d'un paquet publié.
196
+ * @param {string} npmrcPath
197
+ */
198
+ export function writeProjectNpmrc(npmrcPath) {
199
+ fs.writeFileSync(npmrcPath, `@fraza-pro:registry=${REGISTRY_URL}\n`);
200
+ }
201
+
202
+ /**
203
+ * En-tête de `.env.local`. Celui du modèle décrit `.env.example` : un fichier
204
+ * suivi par git, sans secret, lu par rien. Les trois sont faux pour
205
+ * `.env.local`, le seul fichier du projet qui ne doit jamais être commité.
206
+ */
207
+ export const LOCAL_ENV_HEADER = [
208
+ '# Valeurs de cette machine, ignorées par git, lues par Next.js et les CLI',
209
+ '# fraza-*. Ne jamais commiter ce fichier : il porte FRAZA_ENCRYPTION_KEY.',
210
+ '#',
211
+ '# En production, ces variables se saisissent dans la plateforme',
212
+ "# d'hébergement. FRAZA_ENCRYPTION_KEY s'y copie telle quelle : elle ne doit",
213
+ '# jamais changer.',
214
+ ].join('\n');
215
+
216
+ /**
217
+ * Remplace le bloc de commentaires qui ouvre le modèle par `LOCAL_ENV_HEADER`,
218
+ * ou l'ajoute quand le modèle n'en a pas.
219
+ * @param {string} template
220
+ * @returns {string}
221
+ */
222
+ function withLocalHeader(template) {
223
+ const lines = template.split('\n');
224
+ let start = 0;
225
+ while (start < lines.length && lines[start].startsWith('#')) start += 1;
226
+ const rest = lines.slice(start).join('\n');
227
+ return start > 0 ? `${LOCAL_ENV_HEADER}\n${rest}` : `${LOCAL_ENV_HEADER}\n\n${rest}`;
228
+ }
229
+
230
+ /**
231
+ * Remplit les valeurs propres à cette machine dans une copie du modèle :
232
+ * `FRAZA_ENCRYPTION_KEY` générée et `NEXT_PUBLIC_URL` locale, sous l'en-tête
233
+ * de `.env.local` (`LOCAL_ENV_HEADER`).
234
+ *
235
+ * La clé n'a rien à saisir à la main : c'est 32 octets aléatoires propres au
236
+ * projet, que `@fraza/core/crypto` attend en 64 caractères hexadécimaux. Une clé
237
+ * déjà présente n'est jamais remplacée : la changer rendrait illisible tout ce
238
+ * qui a été chiffré avec l'ancienne.
239
+ * @param {string} template Contenu du `.env.example` du core.
240
+ * @param {{ key?: string, publicUrl?: string }} [opts]
241
+ * @returns {string}
242
+ */
243
+ export function fillLocalEnv(template, { key = randomBytes(32).toString('hex'), publicUrl = 'http://localhost:3000' } = {}) {
244
+ const setEmpty = (contents, name, value) => {
245
+ const line = new RegExp(`^(${name}=)[ \\t]*$`, 'm');
246
+ if (line.test(contents)) return contents.replace(line, () => `${name}=${value}`);
247
+ if (new RegExp(`^${name}=`, 'm').test(contents)) return contents;
248
+ const separator = contents === '' || contents.endsWith('\n') ? '' : '\n';
249
+ return `${contents}${separator}${name}=${value}\n`;
250
+ };
251
+
252
+ let contents = setEmpty(withLocalHeader(template), 'FRAZA_ENCRYPTION_KEY', key);
253
+ contents = contents.replace(/^NEXT_PUBLIC_URL=.*$/m, () => `NEXT_PUBLIC_URL=${publicUrl}`);
254
+ return setEmpty(contents, 'NEXT_PUBLIC_URL', publicUrl);
255
+ }
256
+
257
+ /**
258
+ * Crée `.env.example` (copie du modèle du core, suivie par git) et `.env.local`
259
+ * (la même copie remplie pour cette machine, ignorée par git). Un fichier qui
260
+ * existe déjà n'est jamais remplacé.
261
+ * @param {string} templatePath `node_modules/@fraza/core/.env.example`.
262
+ * @param {string} projectDir
263
+ * @returns {{ example: boolean, local: boolean }} Les fichiers créés.
264
+ */
265
+ export function writeEnvFiles(templatePath, projectDir) {
266
+ const template = fs.readFileSync(templatePath, 'utf-8');
267
+ const examplePath = path.join(projectDir, '.env.example');
268
+ const localPath = path.join(projectDir, '.env.local');
269
+ const created = { example: false, local: false };
270
+
271
+ if (!fs.existsSync(examplePath)) {
272
+ fs.writeFileSync(examplePath, template);
273
+ created.example = true;
274
+ }
275
+ if (!fs.existsSync(localPath)) {
276
+ // 0600 : le fichier porte la clé de chiffrement du projet.
277
+ fs.writeFileSync(localPath, fillLocalEnv(template), { mode: 0o600 });
278
+ created.local = true;
279
+ }
280
+ return created;
281
+ }
package/bin/version.js ADDED
@@ -0,0 +1,2 @@
1
+ // AUTO-GÉNÉRÉ par `fraza-module sync-version`, ne pas modifier à la main.
2
+ export const STARTER_VERSION = '0.3.5';