@mostajs/wagon-engine 0.8.0 → 0.9.0

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/CHANGELOG.md CHANGED
@@ -3,6 +3,31 @@
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
  Format : Keep a Changelog ; versions : SemVer.
5
5
 
6
+ ## [0.9.0] — 2026-10-29
7
+
8
+ ### Modifié — les clés du `.env` sans préfixe `WAGON_` (demande du fondateur)
9
+ Le préfixe `WAGON_` ne disait ni la fonction de la clé ni son sens métier, et se mêlait mal au profil `MOSTA_ENV`
10
+ (`PROD_SGBD_URI` n'était pas lu : la base des comptes retombait sur `data/wagon.db`). Les clés portent désormais le
11
+ nom de leur fonction ; le seul préfixe est celui du profil. **Les anciens noms ne sont plus lus** (pas de secours) :
12
+ le démarrage avertit pour chacun qui reste (`signalerClesRetirees`).
13
+
14
+ | retiré | remplacé par |
15
+ |---|---|
16
+ | `WAGON_SGBD`, `WAGON_DB_DIALECT` | `SGBD_URI`, `DB_DIALECT` |
17
+ | `WAGON_BASE_URL` | `BASE_URL` |
18
+ | `WAGON_ADMINS` | `ADMIN_EMAILS` |
19
+ | `WAGON_EMAIL_SERVER_HOST`/`_PORT`/`_USER`/`_PASSWORD`, `WAGON_EMAIL_FROM` | `EMAIL_SERVER_HOST`/`_PORT`/`_USER`/`_PASSWORD`, `EMAIL_FROM` |
20
+ | `WAGON_COURRIEL_FICHIER` | `EMAIL_FICHIER` |
21
+ | `WAGON_STRIPE_RESTRICTED_KEY`, `WAGON_STRIPE_SECRET_KEY`, `WAGON_STRIPE_WEBHOOK_SECRET` | `STRIPE_RESTRICTED_KEY`, `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET` |
22
+ | `WAGON_GREFFE` (suite de conformité) | `GREFFE` |
23
+
24
+ Toutes ces clés sont lues par `getEnv` de `@mostajs/config` (la suite de conformité aussi, qui lisait `process.env`).
25
+ Ajouté : `adminsDeclares(config)` (la liste des administrateurs déclarés, pour les scripts d'administration des
26
+ greffes), `signalerClesRetirees(journal)`, `CLES_RETIREES`.
27
+
28
+ Essai T-ENG-17 (cascade `MOSTA_ENV=PROD` → `PROD_SGBD_URI` ; `WAGON_SGBD` ignoré et signalé) ; 53/53 ; sabotage vérifié
29
+ (relire `WAGON_SGBD` → T-ENG-17 tombe).
30
+
6
31
  ## [0.8.0] — 2026-10-29
7
32
 
8
33
  ### Ajouté — les membres d'un compte (demandé par CaisseTrax : écart E-08 de SPEC-ECRANS-CAISSETRAX)
package/README.md CHANGED
@@ -34,17 +34,17 @@ await demarrer({ config, charge });
34
34
  | `.env` | rôle |
35
35
  |---|---|
36
36
  | `PORT`, `HOST` | écoute (4800, 127.0.0.1) |
37
- | `WAGON_BASE_URL` | l'adresse publique (liens des courriels, retours de paiement) |
38
- | `WAGON_DB_DIALECT`, `WAGON_SGBD` | la base : `sqlite` et un chemin, ou `postgres` et `postgresql://…` |
39
- | `WAGON_EMAIL_SERVER_HOST`, `…_PORT`, `…_USER`, `…_PASSWORD`, `WAGON_EMAIL_FROM` | le courriel (SMTP) |
40
- | `WAGON_STRIPE_SECRET_KEY`, `WAGON_STRIPE_WEBHOOK_SECRET` | Stripe |
37
+ | `BASE_URL` | l'adresse publique (liens des courriels, retours de paiement) |
38
+ | `DB_DIALECT`, `SGBD_URI` | la base : `sqlite` et un chemin, ou `postgres` et `postgresql://…` |
39
+ | `EMAIL_SERVER_HOST`, `…_PORT`, `…_USER`, `…_PASSWORD`, `EMAIL_FROM` | le courriel (SMTP) |
40
+ | `STRIPE_SECRET_KEY` (ou `STRIPE_RESTRICTED_KEY`), `STRIPE_WEBHOOK_SECRET` | Stripe |
41
41
  | `CHARGILY_SECRET_KEY` · `SATIM_MERCHANT_ID`, `SATIM_PASSWORD`, `SATIM_TERMINAL_ID` · `PAYPAL_…` | les banques prépayées proposées par `config.paiement.prepaye` |
42
42
  | `PAIEMENT_STRIPE=off`, `PAIEMENT_CHARGILY=off`, `PAIEMENT_SATIM=off`, `PAIEMENT_PAYPAL=off` | **coupe un moyen** sans toucher à ses clés |
43
- | `WAGON_ADMINS` | les courriels administrateurs (confirmés par un code reçu) |
43
+ | `ADMIN_EMAILS` | les courriels administrateurs (confirmés par un code reçu) |
44
44
  | `PAIEMENT_VENDEUR_DZD`, `PAIEMENT_VENDEUR_EUR`… | 0.6 — **le vendeur de chaque devise** (dénomination, forme, adresse, immatriculations) ; dès qu'une est déclarée, une devise sans vendeur n'est pas vendue, et `{{VENDEUR}}` des CGV les nomme |
45
45
  | `PAIEMENT_STRIPE_DEVISES` | 0.6 — la devise de Stripe (défaut `EUR`) |
46
46
 
47
- **Le `.env` d'abord.** Toutes les clés passent par `getEnv` de `@mostajs/config` : cascade `MOSTA_ENV` (`PROD_CLÉ` l'emporte sur `CLÉ`), valeur vide = absente, et `loadEnv` n'écrase jamais une variable déjà posée par l'orchestrateur. Si `wagon.config.mjs` lit des variables, il appelle `loadEnv` lui-même, en tête.
47
+ **Le `.env` d'abord.** Toutes les clés passent par `getEnv` de `@mostajs/config` : cascade `MOSTA_ENV` (`PROD_CLÉ` l'emporte sur `CLÉ`), valeur vide = absente, et `loadEnv` n'écrase jamais une variable déjà posée par l'orchestrateur. Une clé porte le nom de sa fonction ; le seul préfixe est celui du profil (`PROD_SGBD_URI`, `DEV_BASE_URL`). Depuis la 0.9, les anciens noms `WAGON_…` ne sont plus lus : le démarrage signale ceux qui restent. Si `wagon.config.mjs` lit des variables, il appelle `loadEnv` lui-même, en tête.
48
48
 
49
49
  **Un vendeur par devise (0.6).** Un SaaS qui vend en dinars par un auto-entrepreneur et en euros par une société pose `PAIEMENT_VENDEUR_DZD=…` et `PAIEMENT_VENDEUR_EUR=…`. Une devise déclarée vide (`PAIEMENT_VENDEUR_EUR=`, la société n'est pas encore immatriculée) n'est pas vendue : Stripe est écarté même avec sa clé. Sans aucune `PAIEMENT_VENDEUR_*`, rien ne change.
50
50
 
@@ -54,7 +54,7 @@ await demarrer({ config, charge });
54
54
 
55
55
  ## Juger une greffe
56
56
 
57
- La suite de conformité est publiée avec le moteur. Elle juge la greffe du dossier courant, ou celle que désigne `WAGON_GREFFE` :
57
+ La suite de conformité est publiée avec le moteur. Elle juge la greffe du dossier courant, ou celle que désigne `GREFFE` :
58
58
 
59
59
  ```bash
60
60
  npx mjs-unit node_modules/@mostajs/wagon-engine/src/conformite/conformite.test.mjs
@@ -13,7 +13,7 @@ import { demarrer } from '../src/index.js';
13
13
  const racine = mkdtempSync(join(tmpdir(), 'greffe-'));
14
14
  mkdirSync(join(racine, 'legal'));
15
15
  writeFileSync(join(racine, 'legal', 'cgu.fr.md'), '# Conditions\n\n{{SERVICE}} est édité par Exemple SARL.\n');
16
- writeFileSync(join(racine, '.env'), `PORT=0\nWAGON_SGBD=${join(racine, 'wagon.db')}\nWAGON_BASE_URL=http://127.0.0.1\nCHARGILY_SECRET_KEY=test_sk_exemple\nPAIEMENT_STRIPE=off\n`);
16
+ writeFileSync(join(racine, '.env'), `PORT=0\nSGBD_URI=${join(racine, 'wagon.db')}\nBASE_URL=http://127.0.0.1\nCHARGILY_SECRET_KEY=test_sk_exemple\nPAIEMENT_STRIPE=off\n`);
17
17
 
18
18
  const config = {
19
19
  marque: { nom: 'Échos', symbole: 'É', auteur: 'Exemple SARL' }, langues: ['fr', 'en', 'ar'], connexion: 'motdepasse',
package/llms.txt CHANGED
@@ -17,7 +17,7 @@ Une greffe se configure par SON `.env` (dossier courant de `demarrer`, ou `racin
17
17
  conteneur) prime sur le fichier.
18
18
  3. **Cascade** (première valeur NON VIDE) : `${MOSTA_ENV}_${CLÉ}` → `${CLÉ}` → défaut. Un seul .env, des profils
19
19
  `TEST_`/`DEV_`/`PROD_`. Toutes les clés ci-dessous, et celles de `@mostajs/payment`, passent par `getEnv`.
20
- 4. **Vide = absent** : `WAGON_STRIPE_SECRET_KEY=` ne configure rien. Exception voulue : `PAIEMENT_VENDEUR_<DEVISE>=`
20
+ 4. **Vide = absent** : `STRIPE_SECRET_KEY=` ne configure rien. Exception voulue : `PAIEMENT_VENDEUR_<DEVISE>=`
21
21
  vide DÉCLARE la devise sans la nommer (elle n'est pas vendue).
22
22
  5. Les secrets et les informations de l'entreprise sont posés par l'exploitant sur le serveur, jamais commités ;
23
23
  `.env.example` de la greffe liste chaque clé, sans valeur.
@@ -27,13 +27,13 @@ Une greffe se configure par SON `.env` (dossier courant de `demarrer`, ou `racin
27
27
  - creerWagon({ config, charge, sgbd: string|{dialect,uri}, baseUrl, stripe?, webhookSecret?, transport?, journal?,
28
28
  paiement?: { banques: { chargily:'DZD', … }, port?, deviseStripe? }, racine? }) → { moyens, handler, catalogue(), catalogueStripe(), fermer(), … }
29
29
  - schemasWagon() (sauvegarde) · sauvegarder({ cibles, driver, seau, recipients, garder }) · suivrePalette(…)
30
- - '@mostajs/wagon-engine/conformite' = src/conformite/conformite.test.mjs (suite mjs-unit, juge WAGON_GREFFE ou cwd)
30
+ - '@mostajs/wagon-engine/conformite' = src/conformite/conformite.test.mjs (suite mjs-unit, juge GREFFE ou cwd)
31
31
 
32
32
  ## .env lu par demarrer
33
- PORT, HOST · WAGON_BASE_URL · WAGON_DB_DIALECT (sqlite|postgres…), WAGON_SGBD · WAGON_EMAIL_SERVER_HOST/PORT/USER/PASSWORD,
34
- WAGON_EMAIL_FROM (ou WAGON_COURRIEL_FICHIER pour les essais) · WAGON_STRIPE_SECRET_KEY|RESTRICTED_KEY, WAGON_STRIPE_WEBHOOK_SECRET ·
33
+ PORT, HOST · BASE_URL · DB_DIALECT (sqlite|postgres…), SGBD_URI · EMAIL_SERVER_HOST/PORT/USER/PASSWORD,
34
+ EMAIL_FROM (ou EMAIL_FICHIER pour les essais) · STRIPE_SECRET_KEY|STRIPE_RESTRICTED_KEY, STRIPE_WEBHOOK_SECRET ·
35
35
  CHARGILY_SECRET_KEY|API_KEY · SATIM_MERCHANT_ID, SATIM_PASSWORD, SATIM_TERMINAL_ID · PAYPAL_CLIENT_ID, PAYPAL_SECRET,
36
- PAYPAL_WEBHOOK_ID · PAIEMENT_<STRIPE|CHARGILY|SATIM|PAYPAL>=off (coupe le moyen) · WAGON_ADMINS · WAGON_INDEXER.
36
+ PAYPAL_WEBHOOK_ID · PAIEMENT_<STRIPE|CHARGILY|SATIM|PAYPAL>=off (coupe le moyen) · ADMIN_EMAILS. Cascade MOSTA_ENV (PROD_SGBD_URI…). 0.9 : plus aucun nom WAGON_… n'est lu (signalé au démarrage).
37
37
  0.6 (@mostajs/payment ≥ 2.7) : PAIEMENT_VENDEUR_<DEVISE> (le vendeur de cette devise : dénomination, forme, adresse,
38
38
  immatriculations) · PAIEMENT_STRIPE_DEVISES (la devise de Stripe, défaut EUR).
39
39
  Moyen ACTIF = clés présentes ET commutateur ≠ off (ET, pour une banque prépayée, proposée par config.paiement.prepaye)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/wagon-engine",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Le wagon d'un SaaS, une fois pour toutes : comptes, connexion, paliers, quotas, paiement (Stripe, Chargily, SATIM), espace client, administration, pages légales, fr/en/ar. Une greffe n'apporte que wagon.config.mjs, charge/, legal/ et son .env ; la suite de conformité publiée juge la greffe.",
5
5
  "keywords": [
6
6
  "mostajs",
@@ -1,5 +1,5 @@
1
1
  // @mostajs/wagon-engine — LA SUITE DE CONFORMITÉ : elle dit si une greffe tient.
2
- // La greffe jugée est celle de WAGON_GREFFE, sinon le dossier courant : sa configuration (wagon.config.mjs), sa
2
+ // La greffe jugée est celle de GREFFE, sinon le dossier courant : sa configuration (wagon.config.mjs), sa
3
3
  // charge (charge/index.mjs) et ses textes légaux (legal/). Lancer depuis la greffe :
4
4
  // mjs-unit node_modules/@mostajs/wagon-engine/src/conformite/conformite.test.mjs
5
5
  // Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
@@ -20,10 +20,11 @@ import { join, resolve } from 'node:path';
20
20
  import { pathToFileURL } from 'node:url';
21
21
  import Stripe from 'stripe';
22
22
  import { creerWagon } from '../wagon.mjs';
23
+ import { getEnv } from '@mostajs/config';
23
24
 
24
- const GREFFE = resolve(process.env.WAGON_GREFFE || process.cwd());
25
+ const GREFFE = resolve(getEnv('GREFFE', process.cwd()));
25
26
  if (!existsSync(join(GREFFE, 'wagon.config.mjs')) || !existsSync(join(GREFFE, 'charge', 'index.mjs'))) {
26
- throw new Error(`conformité : greffe introuvable dans ${GREFFE} (wagon.config.mjs et charge/index.mjs attendus ; WAGON_GREFFE la désigne)`);
27
+ throw new Error(`conformité : greffe introuvable dans ${GREFFE} (wagon.config.mjs et charge/index.mjs attendus ; GREFFE la désigne)`);
27
28
  }
28
29
  const config = (await import(pathToFileURL(join(GREFFE, 'wagon.config.mjs')).href)).default;
29
30
  const charge = (await import(pathToFileURL(join(GREFFE, 'charge', 'index.mjs')).href)).default;
@@ -374,7 +375,7 @@ test('T-WAG-27 — `config.css` : la feuille de l’application est posée sur l
374
375
  });
375
376
 
376
377
  // 0.5 (audit du 09/10/2026) — la base du wagon n'est plus figée en sqlite : `sgbd: { dialect, uri }` (le .env :
377
- // WAGON_DB_DIALECT, WAGON_SGBD), comme la base de la charge. La chaîne seule reste un chemin sqlite.
378
+ // DB_DIALECT, SGBD_URI), comme la base de la charge. La chaîne seule reste un chemin sqlite.
378
379
  test('T-WAG-19 — la base du wagon se choisit par { dialect, uri } ; la chaîne reste un chemin sqlite', async () => {
379
380
  const W2 = await creerWagon({ config: { ...config, dossierLegal: LEGAL_COMPLET }, charge, sgbd: { dialect: 'sqlite', uri: join(atelier, `objet${Math.random()}.db`) }, baseUrl: 'http://127.0.0.1', racine: GREFFE });
380
381
  await W2.catalogue();
package/src/demarrer.mjs CHANGED
@@ -13,18 +13,19 @@ import { join } from 'node:path';
13
13
  import { loadEnv, getEnv } from '@mostajs/config';
14
14
  import { devisesDuMoyen } from '@mostajs/payment/server';
15
15
  import { creerWagon } from './wagon.mjs';
16
+ import { signalerClesRetirees } from './env.mjs';
16
17
 
17
18
  const coupe = (moyen) => getEnv(`PAIEMENT_${moyen.toUpperCase()}`, '') === 'off';
18
19
 
19
20
  /** Les courriels : SMTP (avec DKIM si la clé est publiée), ou un fichier pour les essais. */
20
21
  async function transport() {
21
- const fichier = getEnv('WAGON_COURRIEL_FICHIER', '');
22
+ const fichier = getEnv('EMAIL_FICHIER', '');
22
23
  if (fichier) return { envoyer: async (m) => appendFileSync(fichier, `${JSON.stringify(m)}\n`, { mode: 0o600 }) };
23
- const host = getEnv('WAGON_EMAIL_SERVER_HOST', '');
24
+ const host = getEnv('EMAIL_SERVER_HOST', '');
24
25
  if (!host) return null;
25
26
  const { createSmtpLiteDriver } = await import('@mostajs/mailer');
26
- const pilote = createSmtpLiteDriver({ host, port: Number(getEnv('WAGON_EMAIL_SERVER_PORT', '587')), user: getEnv('WAGON_EMAIL_SERVER_USER', ''), pass: getEnv('WAGON_EMAIL_SERVER_PASSWORD', '') });
27
- const de = getEnv('WAGON_EMAIL_FROM', getEnv('WAGON_EMAIL_SERVER_USER', ''));
27
+ const pilote = createSmtpLiteDriver({ host, port: Number(getEnv('EMAIL_SERVER_PORT', '587')), user: getEnv('EMAIL_SERVER_USER', ''), pass: getEnv('EMAIL_SERVER_PASSWORD', '') });
28
+ const de = getEnv('EMAIL_FROM', getEnv('EMAIL_SERVER_USER', ''));
28
29
  return { envoyer: (m) => pilote.send({ from: de, ...m }) };
29
30
  }
30
31
 
@@ -45,14 +46,15 @@ const PRETES = {
45
46
  */
46
47
  export async function demarrer({ config, charge, racine = process.cwd(), journal = console }) {
47
48
  loadEnv({ path: join(racine, '.env') });
48
- const cle = coupe('stripe') ? '' : (getEnv('WAGON_STRIPE_RESTRICTED_KEY', '') || getEnv('WAGON_STRIPE_SECRET_KEY', ''));
49
+ signalerClesRetirees(journal);
50
+ const cle = coupe('stripe') ? '' : (getEnv('STRIPE_RESTRICTED_KEY', '') || getEnv('STRIPE_SECRET_KEY', ''));
49
51
  const banques = Object.fromEntries(Object.entries(config.paiement?.prepaye ?? {}).filter(([b]) => PRETES[b]?.() && !coupe(b)));
50
52
  const wagon = await creerWagon({
51
53
  config, charge, racine, journal,
52
- sgbd: { dialect: getEnv('WAGON_DB_DIALECT', 'sqlite'), uri: getEnv('WAGON_SGBD', join(racine, 'data', 'wagon.db')) },
53
- baseUrl: getEnv('WAGON_BASE_URL', 'http://localhost:4800'),
54
+ sgbd: { dialect: getEnv('DB_DIALECT', 'sqlite'), uri: getEnv('SGBD_URI', join(racine, 'data', 'wagon.db')) },
55
+ baseUrl: getEnv('BASE_URL', 'http://localhost:4800'),
54
56
  stripe: cle ? new (await import('stripe')).default(cle) : null,
55
- webhookSecret: getEnv('WAGON_STRIPE_WEBHOOK_SECRET', ''),
57
+ webhookSecret: getEnv('STRIPE_WEBHOOK_SECRET', ''),
56
58
  transport: await transport(),
57
59
  // 0.6 — la devise de Stripe (PAIEMENT_STRIPE_DEVISES, défaut EUR) : creerWagon écarte une devise sans vendeur.
58
60
  paiement: { banques, deviseStripe: devisesDuMoyen('stripe', ['EUR'])[0] },
package/src/env.mjs ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @mostajs/wagon-engine — les clés du .env, toujours lues par getEnv de @mostajs/config (cascade MOSTA_ENV :
3
+ * `PROD_CLÉ` l'emporte sur `CLÉ`, valeur vide = absente).
4
+ * @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
5
+ *
6
+ * 0.9 — les clés portent le nom de leur fonction (SGBD_URI, BASE_URL, ADMIN_EMAILS…) ; le seul préfixe est celui du
7
+ * profil MOSTA_ENV. Les anciens noms `WAGON_…` ne sont PLUS lus : s'il en reste dans le .env, le démarrage le dit.
8
+ */
9
+ import { getEnv } from '@mostajs/config';
10
+
11
+ /** Les noms retirés en 0.9, et leur remplaçant. */
12
+ export const CLES_RETIREES = {
13
+ WAGON_SGBD: 'SGBD_URI',
14
+ WAGON_DB_DIALECT: 'DB_DIALECT',
15
+ WAGON_BASE_URL: 'BASE_URL',
16
+ WAGON_ADMINS: 'ADMIN_EMAILS',
17
+ WAGON_EMAIL_SERVER_HOST: 'EMAIL_SERVER_HOST',
18
+ WAGON_EMAIL_SERVER_PORT: 'EMAIL_SERVER_PORT',
19
+ WAGON_EMAIL_SERVER_USER: 'EMAIL_SERVER_USER',
20
+ WAGON_EMAIL_SERVER_PASSWORD: 'EMAIL_SERVER_PASSWORD',
21
+ WAGON_EMAIL_FROM: 'EMAIL_FROM',
22
+ WAGON_COURRIEL_FICHIER: 'EMAIL_FICHIER',
23
+ WAGON_STRIPE_RESTRICTED_KEY: 'STRIPE_RESTRICTED_KEY',
24
+ WAGON_STRIPE_SECRET_KEY: 'STRIPE_SECRET_KEY',
25
+ WAGON_STRIPE_WEBHOOK_SECRET: 'STRIPE_WEBHOOK_SECRET',
26
+ };
27
+
28
+ /** Signale les anciens noms encore posés : ils sont ignorés. Rend la liste. */
29
+ export function signalerClesRetirees(journal = console) {
30
+ const restes = Object.keys(CLES_RETIREES).filter((k) => getEnv(k, undefined) !== undefined);
31
+ for (const k of restes) journal.warn(`wagon : ${k} n'est plus lue — renommez-la ${CLES_RETIREES[k]} dans le .env (même préfixe MOSTA_ENV)`);
32
+ return restes;
33
+ }
34
+
35
+ /** Les courriels administrateurs déclarés (config.admins + ADMIN_EMAILS), en minuscules. */
36
+ export function adminsDeclares(config = {}) {
37
+ return [...new Set([...(config.admins ?? []), ...String(getEnv('ADMIN_EMAILS', '') ?? '').split(',')]
38
+ .map((x) => x.trim().toLowerCase()).filter(Boolean))];
39
+ }
package/src/index.js CHANGED
@@ -5,3 +5,4 @@ export { creerWagon, schemasWagon } from './wagon.mjs';
5
5
  export { demarrer } from './demarrer.mjs';
6
6
  export { sauvegarder } from './sauvegarde.mjs';
7
7
  export { suivrePalette } from './palette.mjs';
8
+ export { adminsDeclares, signalerClesRetirees, CLES_RETIREES } from './env.mjs';
package/src/wagon.mjs CHANGED
@@ -22,6 +22,7 @@ import { readFileSync, existsSync } from 'node:fs';
22
22
  import { createHash } from 'node:crypto';
23
23
  import { join } from 'node:path';
24
24
  import { openIsolatedDialect } from '@mostajs/data-plug';
25
+ import { adminsDeclares } from './env.mjs';
25
26
  import { creerWagonUi, esc } from '@mostajs/wagon-ui';
26
27
  import { creerComptes, schemasComptes } from '@mostajs/comptes';
27
28
  import { createBilling, createStripeSale, createPrepaidSale, billingSchemas, checkLimits, assertMonotonicPlans } from '@mostajs/subscriptions-plan/server';
@@ -34,7 +35,6 @@ import { emailCodeEmailHtml, emailCodeEmailText, emailCodeForm, emailLinkConfirm
34
35
  import { magasinSessions } from '@mostajs/auth-plug';
35
36
  import { requeteLegitime, enTetesSecurite, cookieStrict } from '@mostajs/garde-http';
36
37
  import { mdToHtml } from '@mostajs/markdown-html';
37
- import { getEnv } from '@mostajs/config';
38
38
  import { suivrePalette } from './palette.mjs';
39
39
 
40
40
  const COURRIEL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -134,7 +134,7 @@ const CredentialSchema = {
134
134
  relations: {}, indexes: [{ fields: { user: 'asc' }, unique: true }],
135
135
  };
136
136
  /**
137
- * 0.4 (audit du 09/10/2026) — les administrateurs CONFIRMÉS. Un courriel déclaré (config.admins, WAGON_ADMINS) ne
137
+ * 0.4 (audit du 09/10/2026) — les administrateurs CONFIRMÉS. Un courriel déclaré (config.admins, ADMIN_EMAILS) ne
138
138
  * donne l'administration qu'une fois sa détention prouvée : connexion par code ou par lien reçu à ce courriel, ou
139
139
  * confirmation faite sur le serveur (scripts/admin.mjs). Sans cela, quiconque inscrivait ce courriel AVANT son
140
140
  * titulaire prenait l'administration.
@@ -195,7 +195,7 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
195
195
  const https = /^https:/.test(origine);
196
196
  const S = Object.fromEntries(schemasWagon().map((s) => [s.name, s]));
197
197
  // 0.5 (audit du 09/10/2026) — `sgbd` : un chemin sqlite (comme avant), ou { dialect, uri } d'après le .env
198
- // (WAGON_DB_DIALECT, WAGON_SGBD) : la base des comptes n'est plus figée en sqlite.
198
+ // (DB_DIALECT, SGBD_URI) : la base des comptes n'est plus figée en sqlite.
199
199
  const base = typeof sgbd === 'string' ? { dialect: 'sqlite', uri: sgbd } : { dialect: sgbd?.dialect ?? 'sqlite', uri: sgbd?.uri };
200
200
  const d = await openIsolatedDialect({ ...base, schemaStrategy: 'update' }, schemasWagon());
201
201
  // 0.4 — la charge ouvre SA base au démarrage, pas au premier client (DEVRULES-SAAS-APP §5.2).
@@ -325,7 +325,7 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
325
325
  } catch (e) { journal.warn(`wagon : paiementRegle de la charge : ${e?.message ?? e}`); return { status: 500, paye: false, meta }; }
326
326
  return { status: 200, paye: r.status === 'paid', meta };
327
327
  }
328
- const admins = new Set([...(config.admins ?? []), ...String(getEnv('WAGON_ADMINS', '') ?? '').split(',')].map((x) => x.trim().toLowerCase()).filter(Boolean));
328
+ const admins = new Set(adminsDeclares(config));
329
329
  /** Un courriel est administrateur s'il est DÉCLARÉ et CONFIRMÉ. */
330
330
  const estAdministrateur = async (courriel) => {
331
331
  const c = String(courriel ?? '').toLowerCase();
@@ -749,7 +749,7 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
749
749
  if (v.allowed) anonymes.set(k, (anonymes.get(k) ?? 0) + 1);
750
750
  return v;
751
751
  };
752
- // 0.3.1 — `admin` : la session est celle d'un administrateur (config.admins + WAGON_ADMINS) — pour qu'une
752
+ // 0.3.1 — `admin` : la session est celle d'un administrateur (config.admins + ADMIN_EMAILS) — pour qu'une
753
753
  // charge réserve ses outils (ex. l'outil de l'agence de travel) sans recalculer la liste.
754
754
  /** 0.2 (wagon-engine) — un courriel de la charge, par le transport du wagon (SMTP du .env). Rend true s'il est parti,
755
755
  * false s'il n'y a pas de transport ou s'il a échoué (journalisé) : jamais une exception dans la route. */