@salifbiaye/create-boutique 0.1.1 → 0.1.3

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.
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import pc from "picocolors";
5
5
  import { compose, composeOutput, isRunning } from "./docker.js";
6
6
  import { composeYaml, readEnv, refreshEnv, writeCampaignFiles, writeReadme } from "./generate.js";
7
- import { copyDir, TEMPLATES, templateVersion } from "./paths.js";
7
+ import { copyTemplate, templateVersion } from "./paths.js";
8
8
  import { nextFreePort, reservedByOthers, reservePorts } from "./ports.js";
9
9
  import { currencies, effective, readProject, validateProject, writeJsonSchema, writeProject, ProjectError, SLUG } from "./project.js";
10
10
  import { listThemes } from "./themes.js";
@@ -166,7 +166,7 @@ export async function update(dir, opts) {
166
166
  const to = templateVersion();
167
167
  for (const part of ["storefront", "backend"]) {
168
168
  fs.rmSync(path.join(target, part), { recursive: true, force: true });
169
- copyDir(path.join(TEMPLATES, part), path.join(target, part));
169
+ copyTemplate(part, target);
170
170
  }
171
171
  p.templateVersion = to;
172
172
  writeProject(target, p);
@@ -5,7 +5,7 @@ import pc from "picocolors";
5
5
  import { z } from "zod";
6
6
  import { composeYaml, writeCampaignFiles, writeEnv, writeReadme } from "./generate.js";
7
7
  import { writeGuides } from "./guide.js";
8
- import { copyDir, TEMPLATES, templateVersion } from "./paths.js";
8
+ import { copyDir, copyTemplate, templateVersion } from "./paths.js";
9
9
  import { nextFreePort, reservedByOthers, reservePorts } from "./ports.js";
10
10
  import { validateProject, writeJsonSchema, writeProject, ProjectError } from "./project.js";
11
11
  import { listThemes, themeIds } from "./themes.js";
@@ -75,7 +75,14 @@ async function ask(a, dirName, yes) {
75
75
  }
76
76
  const extra = [...(a.boutiques ?? [])];
77
77
  if (!a.boutiques) {
78
- while (cancelled(await p.confirm({ message: "Ajouter une autre boutique ?", initialValue: false }))) {
78
+ const none = () => chosen.length === 0 && extra.length === 0;
79
+ const more = async () => {
80
+ if (!none())
81
+ return cancelled(await p.confirm({ message: "Ajouter une autre boutique ?", initialValue: false }));
82
+ p.log.warn("Aucun thème coché : ajoute au moins une boutique.");
83
+ return true;
84
+ };
85
+ while (await more()) {
79
86
  const slug = cancelled(await p.text({ message: "Nom (slug) de la boutique", validate: (v) => (/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v ?? "") ? undefined : "minuscules, chiffres et tirets") }));
80
87
  const theme = cancelled(await p.select({ message: "Thème", options: themes.map((t) => ({ value: t.id, label: t.id })) }));
81
88
  extra.push(`${slug}:${theme}`);
@@ -83,8 +90,16 @@ async function ask(a, dirName, yes) {
83
90
  }
84
91
  const stripePk = a.stripePk ?? (cancelled(await p.text({ message: "Clé Stripe publique (vide = paiement manuel)", placeholder: "pk_test_…", defaultValue: "" })) || undefined);
85
92
  const stripeSk = stripePk ? (a.stripeSk ?? (cancelled(await p.password({ message: "Clé Stripe secrète" })) || undefined)) : a.stripeSk;
86
- const products = a.products ?? (cancelled(await p.text({ message: "Fichier produits (CSV, optionnel)", defaultValue: "" })) || undefined);
87
- const images = a.images ?? (cancelled(await p.text({ message: "Dossier images (optionnel)", defaultValue: "" })) || undefined);
93
+ const exists = (kind) => (v) => {
94
+ if (!v?.trim())
95
+ return undefined;
96
+ const st = fs.statSync(path.resolve(v.trim()), { throwIfNoEntry: false });
97
+ if (!st)
98
+ return "introuvable (laisser vide pour passer)";
99
+ return (kind === "file" ? st.isFile() : st.isDirectory()) ? undefined : kind === "file" ? "ce n'est pas un fichier" : "ce n'est pas un dossier";
100
+ };
101
+ const products = a.products ?? (cancelled(await p.text({ message: "Fichier produits (CSV, optionnel)", defaultValue: "", validate: exists("file") })).trim() || undefined);
102
+ const images = a.images ?? (cancelled(await p.text({ message: "Dossier images (optionnel)", defaultValue: "", validate: exists("dir") })).trim() || undefined);
88
103
  return { ...a, name, email, currency, country, language, themes: chosen, boutiques: extra, stripePk, stripeSk, products, images };
89
104
  }
90
105
  export async function create(dir, input, opts) {
@@ -160,8 +175,8 @@ export async function create(dir, input, opts) {
160
175
  }
161
176
  }
162
177
  // Écriture du projet
163
- copyDir(path.join(TEMPLATES, "storefront"), path.join(target, "storefront"));
164
- copyDir(path.join(TEMPLATES, "backend"), path.join(target, "backend"));
178
+ copyTemplate("storefront", target);
179
+ copyTemplate("backend", target);
165
180
  writeProject(target, proj);
166
181
  writeJsonSchema(target);
167
182
  writeCampaignFiles(target, proj, a.language);
package/cli/dist/index.js CHANGED
@@ -147,7 +147,7 @@ async function main() {
147
147
  }
148
148
  main().catch((e) => {
149
149
  if (e instanceof ProjectError)
150
- console.error(pc.red("✖ config invalide, rien n'a été modifié :\n") + e.message);
150
+ console.error(pc.red("✖ rien n'a été créé ni modifié :\n") + e.message);
151
151
  else if (e instanceof z.ZodError)
152
152
  console.error(pc.red("✖ options invalides :\n") + z.prettifyError(e));
153
153
  else
package/cli/dist/paths.js CHANGED
@@ -27,3 +27,14 @@ export function copyDir(from, to) {
27
27
  fs.copyFileSync(src, dst);
28
28
  }
29
29
  }
30
+ /** npm retire toujours les `.npmrc` d'un package publié : on les réécrit ici (le Dockerfile les copie). */
31
+ const NPMRC = {
32
+ backend: "save-exact=true\nlegacy-peer-deps=true\n",
33
+ storefront: "save-exact=true\n",
34
+ };
35
+ /** Copie templates/<part> dans <target>/<part>, avec ses fichiers que npm ne publie pas. */
36
+ export function copyTemplate(part, target) {
37
+ const to = path.join(target, part);
38
+ copyDir(path.join(TEMPLATES, part), to);
39
+ fs.writeFileSync(path.join(to, ".npmrc"), NPMRC[part]);
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salifbiaye/create-boutique",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Usine à Boutiques : génère un projet complet (backend Medusa + une boutique Next.js par campagne) prêt pour docker compose",
5
5
  "type": "module",
6
6
  "repository": { "type": "git", "url": "git+https://github.com/salifbiaye/create-boutique.git" },
@@ -0,0 +1,3 @@
1
+ Polices auto-hébergées (sous-ensemble latin), sous licence SIL Open Font License 1.1 :
2
+ Inter, Plus Jakarta Sans, Oswald, Outfit, Sora, Space Grotesk (versions variables), Poppins.
3
+ Source : paquets Fontsource (@fontsource-variable/*, @fontsource/poppins). https://openfontlicense.org
@@ -1,16 +1,25 @@
1
- import { Inter, Oswald, Outfit, Plus_Jakarta_Sans, Poppins, Sora, Space_Grotesk } from "next/font/google"
1
+ import localFont from "next/font/local"
2
2
  import type { FontId, Theme } from "./schema"
3
3
 
4
- // Polices des presets (design/bundle.css). Servies par next/font (auto-hébergées) :
5
- // seules les 2 polices du thème de l'instance sont appliquées.
4
+ // Polices des presets (design/bundle.css), fichiers woff2 dans ./fonts (licence OFL, voir fonts/LICENSE.txt) :
5
+ // aucun accès à Google Fonts au build ni à l'exécution. Seules les 2 polices du thème de l'instance sont appliquées.
6
6
  // Chaque police expose --nf-<id>, repris par --font-<id> dans styles/ub/fonts.css.
7
- const inter = Inter({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-inter", preload: false })
8
- const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"], weight: ["500", "700", "800"], variable: "--nf-jakarta", preload: false })
9
- const oswald = Oswald({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-oswald", preload: false })
10
- const outfit = Outfit({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-outfit", preload: false })
11
- const poppins = Poppins({ subsets: ["latin"], weight: ["400", "500", "600", "700"], variable: "--nf-poppins", preload: false })
12
- const sora = Sora({ subsets: ["latin"], weight: ["400", "600", "700"], variable: "--nf-sora", preload: false })
13
- const spaceGrotesk = Space_Grotesk({ subsets: ["latin"], weight: ["500", "700"], variable: "--nf-space-grotesk", preload: false })
7
+ const inter = localFont({ src: "./fonts/inter.woff2", weight: "100 900", variable: "--nf-inter", preload: false })
8
+ const jakarta = localFont({ src: "./fonts/plus-jakarta-sans.woff2", weight: "200 800", variable: "--nf-jakarta", preload: false })
9
+ const oswald = localFont({ src: "./fonts/oswald.woff2", weight: "200 700", variable: "--nf-oswald", preload: false })
10
+ const outfit = localFont({ src: "./fonts/outfit.woff2", weight: "100 900", variable: "--nf-outfit", preload: false })
11
+ const poppins = localFont({
12
+ src: [
13
+ { path: "./fonts/poppins-400.woff2", weight: "400" },
14
+ { path: "./fonts/poppins-500.woff2", weight: "500" },
15
+ { path: "./fonts/poppins-600.woff2", weight: "600" },
16
+ { path: "./fonts/poppins-700.woff2", weight: "700" },
17
+ ],
18
+ variable: "--nf-poppins",
19
+ preload: false,
20
+ })
21
+ const sora = localFont({ src: "./fonts/sora.woff2", weight: "100 800", variable: "--nf-sora", preload: false })
22
+ const spaceGrotesk = localFont({ src: "./fonts/space-grotesk.woff2", weight: "300 700", variable: "--nf-space-grotesk", preload: false })
14
23
 
15
24
  const fonts: Partial<Record<FontId, { variable: string }>> = {
16
25
  inter,