odoro 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +12 -0
  2. package/client.d.ts +84 -0
  3. package/dist/build-SNTGJH2J.js +4 -0
  4. package/dist/chunk-2YDI5NKV.js +55 -0
  5. package/dist/chunk-G2WW7N4C.js +1872 -0
  6. package/dist/chunk-G5QXVBYT.js +1139 -0
  7. package/dist/chunk-JMEHF3KN.js +35 -0
  8. package/dist/chunk-PEMYUK2D.js +64 -0
  9. package/dist/chunk-T42X2NJN.js +98 -0
  10. package/dist/chunk-T6RLHSCW.js +123 -0
  11. package/dist/chunk-UGRSODHU.js +154 -0
  12. package/dist/cli.d.ts +40 -0
  13. package/dist/cli.js +236 -0
  14. package/dist/commands-FVAHVVVN.js +793 -0
  15. package/dist/commands-V44Y5G4F.js +245 -0
  16. package/dist/create-SH2M722Y.js +288 -0
  17. package/dist/index.d.ts +354 -0
  18. package/dist/index.js +7 -0
  19. package/dist/package-AUINPBEX.js +62 -0
  20. package/dist/preview-LOAO5Y6V.js +3 -0
  21. package/dist/registry/index.d.ts +316 -0
  22. package/dist/registry/index.js +2 -0
  23. package/dist/server-MZ76LPAG.js +3 -0
  24. package/package.json +57 -0
  25. package/templates/react-ts/README.md +52 -0
  26. package/templates/react-ts/_gitignore +8 -0
  27. package/templates/react-ts/index.html +13 -0
  28. package/templates/react-ts/odoro.config.ts +10 -0
  29. package/templates/react-ts/package.json +23 -0
  30. package/templates/react-ts/public/favicon.svg +4 -0
  31. package/templates/react-ts/src/App.tsx +66 -0
  32. package/templates/react-ts/src/main.tsx +18 -0
  33. package/templates/react-ts/src/odoro-env.d.ts +1 -0
  34. package/templates/react-ts/src/routes/About.tsx +19 -0
  35. package/templates/react-ts/src/routes/Home.tsx +80 -0
  36. package/templates/react-ts/src/routes/NotFound.tsx +14 -0
  37. package/templates/react-ts/src/styles.css +13 -0
  38. package/templates/react-ts/tsconfig.json +25 -0
  39. package/templates/react-ts-server/Dockerfile +51 -0
  40. package/templates/react-ts-server/README.md +87 -0
  41. package/templates/react-ts-server/_dockerignore +8 -0
  42. package/templates/react-ts-server/_env.example +78 -0
  43. package/templates/react-ts-server/_gitignore +8 -0
  44. package/templates/react-ts-server/client/index.html +13 -0
  45. package/templates/react-ts-server/client/public/favicon.svg +4 -0
  46. package/templates/react-ts-server/client/src/App.tsx +66 -0
  47. package/templates/react-ts-server/client/src/main.tsx +18 -0
  48. package/templates/react-ts-server/client/src/odoro-env.d.ts +1 -0
  49. package/templates/react-ts-server/client/src/routes/About.tsx +19 -0
  50. package/templates/react-ts-server/client/src/routes/Home.tsx +148 -0
  51. package/templates/react-ts-server/client/src/routes/NotFound.tsx +14 -0
  52. package/templates/react-ts-server/client/src/styles.css +13 -0
  53. package/templates/react-ts-server/odoro.config.ts +21 -0
  54. package/templates/react-ts-server/package.json +33 -0
  55. package/templates/react-ts-server/scripts/dev.mjs +74 -0
  56. package/templates/react-ts-server/server/src/main.ts +131 -0
  57. package/templates/react-ts-server/server/src/modules/health/index.ts +144 -0
  58. package/templates/react-ts-server/server/tsconfig.json +23 -0
  59. package/templates/react-ts-server/tsconfig.json +27 -0
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ import { writeDatabaseUrl, assertEnvIgnored } from './chunk-PEMYUK2D.js';
3
+ import { info, success, colors, warn, error } from './chunk-JMEHF3KN.js';
4
+ import { randomBytes } from 'crypto';
5
+ import { readFile, mkdir, writeFile, chmod } from 'fs/promises';
6
+ import { platform, homedir } from 'os';
7
+ import { dirname, join } from 'path';
8
+
9
+ function configPath(env = processEnv()) {
10
+ const xdg = env["XDG_CONFIG_HOME"];
11
+ if (xdg !== void 0 && xdg.length > 0) return join(xdg, "odoro", "config.json");
12
+ if (platform() === "win32") {
13
+ const appData = env["APPDATA"];
14
+ if (appData !== void 0 && appData.length > 0) {
15
+ return join(appData, "odoro", "config.json");
16
+ }
17
+ }
18
+ return join(homedir(), ".config", "odoro", "config.json");
19
+ }
20
+ function processEnv() {
21
+ return process.env;
22
+ }
23
+ async function readUserConfig(path = configPath()) {
24
+ try {
25
+ return JSON.parse(await readFile(path, "utf8"));
26
+ } catch {
27
+ return {};
28
+ }
29
+ }
30
+ async function writeUserConfig(config, path = configPath()) {
31
+ await mkdir(dirname(path), { recursive: true });
32
+ await writeFile(path, `${JSON.stringify(config, null, 2)}
33
+ `, "utf8");
34
+ if (platform() === "win32") return { path, restricted: false };
35
+ try {
36
+ await chmod(path, 384);
37
+ return { path, restricted: true };
38
+ } catch {
39
+ return { path, restricted: false };
40
+ }
41
+ }
42
+ async function storeToken(apiUrl, token, path = configPath()) {
43
+ const current = await readUserConfig(path);
44
+ return await writeUserConfig(
45
+ {
46
+ ...current,
47
+ tokens: { ...current.tokens, [apiUrl]: token },
48
+ defaultApiUrl: current.defaultApiUrl ?? apiUrl
49
+ },
50
+ path
51
+ );
52
+ }
53
+ async function findToken(apiUrl, path = configPath(), env = processEnv()) {
54
+ const fromEnv = env["ODORO_TOKEN"];
55
+ if (fromEnv !== void 0 && fromEnv.length > 0) return fromEnv;
56
+ const config = await readUserConfig(path);
57
+ return config.tokens?.[apiUrl];
58
+ }
59
+
60
+ // src/db/sdk.ts
61
+ var SDK_PACKAGE = "@odoro/cloud-sdk";
62
+ async function loadSdk() {
63
+ try {
64
+ const specifier = SDK_PACKAGE;
65
+ const sdk = await import(specifier);
66
+ return { ok: true, sdk };
67
+ } catch {
68
+ return {
69
+ ok: false,
70
+ reason: `Cette commande a besoin de ${SDK_PACKAGE}, qui n'est pas installe.
71
+
72
+ npm install --save-dev ${SDK_PACKAGE}
73
+
74
+ Il n'est pas fourni avec odoro : ce binaire est telecharge a chaque
75
+ creation de projet, et la plupart n'emploient pas la plateforme.`
76
+ };
77
+ }
78
+ }
79
+
80
+ // src/db/commands.ts
81
+ var DEFAULT_API_URL = "https://api.odoro.dev";
82
+ function idempotencyKey() {
83
+ return randomBytes(24).toString("hex");
84
+ }
85
+ async function connect(options) {
86
+ const apiUrl = options.apiUrl ?? DEFAULT_API_URL;
87
+ const load = await loadSdk();
88
+ if (!load.ok) {
89
+ error(load.reason);
90
+ return void 0;
91
+ }
92
+ const token = await findToken(apiUrl);
93
+ if (token === void 0) {
94
+ error(
95
+ `Aucun jeton pour ${apiUrl}.
96
+
97
+ odoro db:login
98
+
99
+ Ou fournissez-le par la variable ODORO_TOKEN, ce que fait une
100
+ integration continue.`
101
+ );
102
+ return void 0;
103
+ }
104
+ return { client: load.sdk.createClient({ baseUrl: apiUrl, token }), sdk: load.sdk };
105
+ }
106
+ async function loginCommand(options) {
107
+ const apiUrl = options.apiUrl ?? DEFAULT_API_URL;
108
+ const prompts = await import('@clack/prompts');
109
+ const value = await prompts.password({
110
+ message: `Jeton pour ${apiUrl}`,
111
+ validate: (input) => input.startsWith("odk_") ? void 0 : "Un jeton commence par odk_live_ ou odk_test_"
112
+ });
113
+ if (prompts.isCancel(value)) {
114
+ info("Annule.");
115
+ return 0;
116
+ }
117
+ const report = await storeToken(apiUrl, value);
118
+ success(`Jeton enregistre dans ${colors.dim(report.path)}`);
119
+ if (!report.restricted) {
120
+ warn(
121
+ "Les droits restrictifs du fichier n ont pas pu etre poses sur ce systeme : verifiez que ce dossier n est pas partage ni synchronise."
122
+ );
123
+ }
124
+ return 0;
125
+ }
126
+ async function statusCommand(options) {
127
+ const connection = await connect(options);
128
+ if (connection === void 0) return 1;
129
+ const { databases } = await connection.client.databases.list(
130
+ options.env === void 0 ? {} : { environmentId: options.env }
131
+ );
132
+ if (databases.length === 0) {
133
+ info("Aucune base. `odoro db:create` en provisionne une.");
134
+ return 0;
135
+ }
136
+ for (const base of databases) {
137
+ const etat = base.state === "ready" ? colors.green(base.state) : base.state === "failed" || base.state === "quarantined" ? colors.red(base.state) : colors.dim(base.state);
138
+ console.log(` ${base.id} ${etat} ${colors.dim(base.region)}`);
139
+ }
140
+ return 0;
141
+ }
142
+ async function createCommand(options) {
143
+ const connection = await connect(options);
144
+ if (connection === void 0) return 1;
145
+ const environmentId = options.env;
146
+ if (environmentId === void 0) {
147
+ error("Precisez l environnement : `odoro db:create --env production`.");
148
+ return 1;
149
+ }
150
+ const prompts = await import('@clack/prompts');
151
+ const spinner = prompts.spinner();
152
+ spinner.start("Provisionnement");
153
+ const abort = new AbortController();
154
+ const onInterrupt = () => abort.abort();
155
+ process.once("SIGINT", onInterrupt);
156
+ try {
157
+ const fini = await connection.client.databases.createAndWait(
158
+ { idempotencyKey: idempotencyKey(), environmentId, region: "eu-central-1" },
159
+ { signal: abort.signal }
160
+ );
161
+ spinner.stop("Base provisionnee");
162
+ const databaseId = fini.subject;
163
+ if (databaseId === void 0) {
164
+ warn(
165
+ "L operation a abouti sans nommer la base creee. `odoro db:status` la montrera."
166
+ );
167
+ return 0;
168
+ }
169
+ const { connectionString } = await connection.client.credentials.rotate({
170
+ databaseId
171
+ });
172
+ await writeDatabaseUrl(options.root, connectionString);
173
+ success("DATABASE_URL ecrite dans .env");
174
+ const risque = await assertEnvIgnored(options.root);
175
+ if (risque !== void 0) warn(risque);
176
+ return 0;
177
+ } catch (cause) {
178
+ spinner.stop("Provisionnement interrompu");
179
+ if (abort.signal.aborted) {
180
+ info(
181
+ "Attente abandonnee. Le provisionnement continue : `odoro db:status` en montrera l aboutissement."
182
+ );
183
+ return 0;
184
+ }
185
+ error(describe(cause, connection.sdk));
186
+ return 1;
187
+ } finally {
188
+ process.off("SIGINT", onInterrupt);
189
+ }
190
+ }
191
+ async function branchCommand(options) {
192
+ const connection = await connect(options);
193
+ if (connection === void 0) return 1;
194
+ if (options.from === void 0 || options.name === void 0) {
195
+ error("Usage : `odoro db:branch --from production --name preview-42`");
196
+ return 1;
197
+ }
198
+ const prompts = await import('@clack/prompts');
199
+ const spinner = prompts.spinner();
200
+ spinner.start(`Branche depuis ${options.from}`);
201
+ try {
202
+ const fini = await connection.client.databases.branchAndWait({
203
+ idempotencyKey: idempotencyKey(),
204
+ parentEnvironmentId: options.from,
205
+ name: options.name,
206
+ // Sans regle declaree, la plateforme refuse. On ne devine pas a sa
207
+ // place : une regle inventee ici anonymiserait la mauvaise colonne, ou
208
+ // aucune.
209
+ anonymization: []
210
+ });
211
+ spinner.stop("Branche creee");
212
+ info(`Base : ${fini.subject ?? "voir odoro db:status"}`);
213
+ return 0;
214
+ } catch (cause) {
215
+ spinner.stop("Branche refusee");
216
+ error(describe(cause, connection.sdk));
217
+ return 1;
218
+ }
219
+ }
220
+ function describe(cause, sdk) {
221
+ if (sdk.isApiError(cause, "VALIDATION")) {
222
+ const errors = cause.options?.errors;
223
+ return [
224
+ "La demande a ete refusee :",
225
+ ...(errors ?? []).map(({ field, message }) => ` ${field} \u2014 ${message}`)
226
+ ].join("\n");
227
+ }
228
+ if (sdk.isApiError(cause, "RATE_LIMIT")) {
229
+ return "Trop de demandes. Les routes qui provisionnent sont volontairement limitees : chaque creation coute.";
230
+ }
231
+ if (sdk.isApiError(cause, "UNAUTHORIZED")) {
232
+ return `Jeton refuse. \`odoro db:login\` en enregistre un autre.`;
233
+ }
234
+ return cause instanceof Error ? cause.message : String(cause);
235
+ }
236
+ var DB_HELP = [
237
+ " db:login Enregistre un jeton de plateforme",
238
+ " db:status Liste les bases du projet",
239
+ " db:create --env <e> Provisionne une base et ecrit .env",
240
+ " db:branch --from <e> --name <n>",
241
+ " Cree une previsualisation par branche",
242
+ ` (necessite ${SDK_PACKAGE})`
243
+ ].join("\n");
244
+
245
+ export { DB_HELP, branchCommand, createCommand, loginCommand, statusCommand };
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ import { writeDatabaseUrl, assertEnvIgnored, PROVIDER_PENDING, checkDatabaseUrl } from './chunk-PEMYUK2D.js';
3
+ import { execSync } from 'child_process';
4
+ import { existsSync, statSync, readdirSync } from 'fs';
5
+ import { resolve, basename, dirname, join } from 'path';
6
+ import * as prompts from '@clack/prompts';
7
+ import colors from 'picocolors';
8
+ import { readdir, rm, mkdir, copyFile, readFile, writeFile } from 'fs/promises';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ var PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
12
+ function detectPackageManager(userAgent = process.env["npm_config_user_agent"]) {
13
+ if (userAgent === void 0) return "npm";
14
+ const name = userAgent.split(" ")[0]?.split("/")[0];
15
+ return PACKAGE_MANAGERS.find((candidate) => candidate === name) ?? "npm";
16
+ }
17
+ function installCommand(manager) {
18
+ return manager === "yarn" ? "yarn" : `${manager} install`;
19
+ }
20
+ function runCommand(manager, script) {
21
+ return manager === "npm" ? `npm run ${script}` : `${manager} ${script}`;
22
+ }
23
+ function toPackageName(input) {
24
+ return input.trim().toLowerCase().replace(/^[._]+/, "").replace(/[^a-z0-9\-~]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 214) || "odoro-app";
25
+ }
26
+ function validatePackageName(name) {
27
+ if (name.trim() === "") return "Le nom du projet ne peut pas etre vide.";
28
+ if (name.length > 214) return "Le nom du projet ne peut pas depasser 214 caracteres.";
29
+ if (/^[._]/.test(name)) return 'Le nom du projet ne peut pas commencer par "." ou "_".';
30
+ if (!/^[a-z0-9\-~][a-z0-9\-._~]*$/.test(name)) {
31
+ return "Le nom doit etre en minuscules, sans espace ni caractere special.";
32
+ }
33
+ return void 0;
34
+ }
35
+ function inspectTarget(directory) {
36
+ if (!existsSync(directory)) return "absent";
37
+ const entries = readdirSync(directory).filter((entry) => entry !== ".git");
38
+ return entries.length === 0 ? "vide" : "occupe";
39
+ }
40
+ function targetFileName(name) {
41
+ return name.startsWith("_") ? `.${name.slice(1)}` : name;
42
+ }
43
+ function templatesRoot(from = fileURLToPath(import.meta.url)) {
44
+ let directory = dirname(from);
45
+ for (let depth = 0; depth < 6; depth += 1) {
46
+ const candidate = join(directory, "templates");
47
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) return candidate;
48
+ const parent = dirname(directory);
49
+ if (parent === directory) break;
50
+ directory = parent;
51
+ }
52
+ throw new Error("[odoro] Dossier des templates introuvable depuis " + from);
53
+ }
54
+ function availableTemplates(root = templatesRoot()) {
55
+ return readdirSync(root).filter((entry) => statSync(resolve(root, entry)).isDirectory()).sort();
56
+ }
57
+
58
+ // src/scaffold/scaffold.ts
59
+ async function copyDirectory(from, to, written, prefix = "") {
60
+ await mkdir(to, { recursive: true });
61
+ for (const entry of await readdir(from, { withFileTypes: true })) {
62
+ const source = join(from, entry.name);
63
+ const name = targetFileName(entry.name);
64
+ const destination = join(to, name);
65
+ const relativePath = prefix === "" ? name : `${prefix}/${name}`;
66
+ if (entry.isDirectory()) {
67
+ await copyDirectory(source, destination, written, relativePath);
68
+ continue;
69
+ }
70
+ await copyFile(source, destination);
71
+ written.push(relativePath);
72
+ }
73
+ }
74
+ async function renamePackage(target, packageName) {
75
+ const file = join(target, "package.json");
76
+ if (!existsSync(file)) return;
77
+ const manifest = JSON.parse(await readFile(file, "utf8"));
78
+ const renamed = { ...manifest, name: packageName };
79
+ await writeFile(file, `${JSON.stringify(renamed, null, 2)}
80
+ `, "utf8");
81
+ }
82
+ async function scaffold(options) {
83
+ const root = options.root ?? templatesRoot();
84
+ const source = join(root, options.template);
85
+ if (!existsSync(source)) {
86
+ throw new Error(`[odoro] Template inconnu : "${options.template}".`);
87
+ }
88
+ if (options.overwrite === "ecraser" && existsSync(options.target)) {
89
+ for (const entry of await readdir(options.target)) {
90
+ if (entry === ".git") continue;
91
+ await rm(join(options.target, entry), { recursive: true, force: true });
92
+ }
93
+ }
94
+ const files = [];
95
+ await copyDirectory(source, options.target, files);
96
+ await renamePackage(options.target, options.packageName);
97
+ return { files };
98
+ }
99
+
100
+ // src/commands/create.ts
101
+ var TEMPLATE_LABELS = {
102
+ "react-ts": "Application monopage \u2014 React, TypeScript, routeur et animations Odoro",
103
+ "react-ts-server": "Client et serveur \u2014 la meme application, plus un socle @odoro-cli/server modulaire et un Dockerfile"
104
+ };
105
+ async function askDatabase() {
106
+ const choice = ensure(
107
+ await prompts.select({
108
+ message: "Base de donnees",
109
+ initialValue: "url",
110
+ options: [
111
+ {
112
+ value: "provider",
113
+ label: "Fournisseur Odoro",
114
+ hint: "provisionnement automatique \u2014 bientot"
115
+ },
116
+ {
117
+ value: "url",
118
+ label: "URL PostgreSQL existante",
119
+ hint: "Neon, Supabase, RDS, la votre"
120
+ },
121
+ { value: "later", label: "Configurer plus tard", hint: ".env.example seul" }
122
+ ]
123
+ })
124
+ );
125
+ if (choice === "provider") {
126
+ prompts.log.warn(PROVIDER_PENDING);
127
+ return { choice, note: "Lancez `odoro db:create` des que la plateforme est la." };
128
+ }
129
+ if (choice === "later") {
130
+ return {
131
+ choice,
132
+ note: "Aucune base : le client demarrera, et /api/ready repondra 503 en disant ce qui manque."
133
+ };
134
+ }
135
+ const url = ensure(
136
+ await prompts.text({
137
+ message: "URL PostgreSQL",
138
+ placeholder: "postgres://utilisateur:motdepasse@hote:5432/base?sslmode=require",
139
+ validate: (value) => checkDatabaseUrl(value ?? "")
140
+ })
141
+ );
142
+ return {
143
+ choice,
144
+ url: url.trim(),
145
+ // Seule la forme a ete verifiee : le dire, plutot que de laisser croire
146
+ // que la connexion a ete etablie.
147
+ note: "Forme de l URL verifiee. La connexion sera etablie au premier demarrage."
148
+ };
149
+ }
150
+ function ensure(value) {
151
+ if (prompts.isCancel(value)) {
152
+ prompts.cancel("Creation annulee.");
153
+ process.exit(0);
154
+ }
155
+ return value;
156
+ }
157
+ async function createCommand(options) {
158
+ const root = templatesRoot();
159
+ const templates = availableTemplates(root);
160
+ const defaultTemplate = templates[0] ?? "react-ts";
161
+ prompts.intro(colors.bold(colors.magenta(" odoro ")));
162
+ const rawName = options.name ?? (options.yes === true ? "odoro-app" : ensure(
163
+ await prompts.text({
164
+ message: "Nom du projet",
165
+ placeholder: "mon-site",
166
+ defaultValue: "odoro-app",
167
+ validate: (value) => value === "" ? void 0 : validatePackageName(toPackageName(value))
168
+ })
169
+ ));
170
+ const target = resolve(process.cwd(), rawName);
171
+ const packageName = toPackageName(basename(target));
172
+ const invalid = validatePackageName(packageName);
173
+ if (invalid !== void 0) {
174
+ prompts.cancel(invalid);
175
+ return 1;
176
+ }
177
+ let overwrite = options.overwrite;
178
+ const state = inspectTarget(target);
179
+ if (state === "occupe" && overwrite === void 0) {
180
+ if (options.yes === true) {
181
+ prompts.cancel(
182
+ `Le dossier "${basename(target)}" n'est pas vide. Precisez --overwrite ou --merge.`
183
+ );
184
+ return 1;
185
+ }
186
+ const choice = ensure(
187
+ await prompts.select({
188
+ message: `Le dossier "${basename(target)}" n'est pas vide.`,
189
+ options: [
190
+ { value: "annuler", label: "Annuler" },
191
+ { value: "fusionner", label: "Fusionner \u2014 ecrase les fichiers de meme nom" },
192
+ { value: "ecraser", label: "Vider le dossier puis creer le projet" }
193
+ ]
194
+ })
195
+ );
196
+ if (choice === "annuler") {
197
+ prompts.cancel("Creation annulee.");
198
+ return 0;
199
+ }
200
+ overwrite = choice;
201
+ }
202
+ const template = options.template ?? (options.yes === true ? defaultTemplate : ensure(
203
+ await prompts.select({
204
+ message: "Template",
205
+ initialValue: defaultTemplate,
206
+ options: templates.map((name) => ({
207
+ value: name,
208
+ label: name,
209
+ hint: TEMPLATE_LABELS[name]
210
+ }))
211
+ })
212
+ ));
213
+ if (!templates.includes(template)) {
214
+ prompts.cancel(
215
+ `Template inconnu : "${template}". Disponibles : ${templates.join(", ")}.`
216
+ );
217
+ return 1;
218
+ }
219
+ const detected = detectPackageManager();
220
+ const manager = options.pm ?? (options.yes === true ? detected : ensure(
221
+ await prompts.select({
222
+ message: "Gestionnaire de paquets",
223
+ initialValue: detected,
224
+ options: PACKAGE_MANAGERS.map((name) => ({
225
+ value: name,
226
+ label: name,
227
+ hint: name === detected ? "detecte" : void 0
228
+ }))
229
+ })
230
+ ));
231
+ if (!PACKAGE_MANAGERS.includes(manager)) {
232
+ prompts.cancel(`Gestionnaire inconnu : "${manager}".`);
233
+ return 1;
234
+ }
235
+ const database = template === "react-ts-server" && options.yes !== true ? await askDatabase() : void 0;
236
+ const withGit = options.git ?? (options.yes === true ? true : ensure(await prompts.confirm({ message: "Initialiser un depot git ?" })));
237
+ const withInstall = options.install ?? (options.yes === true ? true : ensure(
238
+ await prompts.confirm({
239
+ message: `Installer les dependances avec ${manager} ?`
240
+ })
241
+ ));
242
+ const spinner2 = prompts.spinner();
243
+ spinner2.start("Creation du projet");
244
+ const scaffoldOptions = {
245
+ target,
246
+ template,
247
+ packageName,
248
+ root
249
+ };
250
+ if (overwrite !== void 0) scaffoldOptions.overwrite = overwrite;
251
+ const { files } = await scaffold(scaffoldOptions);
252
+ spinner2.stop(`${files.length} fichiers ecrits dans ${colors.cyan(basename(target))}`);
253
+ if (withGit && !existsSync(resolve(target, ".git"))) {
254
+ try {
255
+ execSync("git init -q", { cwd: target, stdio: "ignore" });
256
+ prompts.log.success("Depot git initialise.");
257
+ } catch {
258
+ prompts.log.warn("git est introuvable : depot non initialise.");
259
+ }
260
+ }
261
+ if (database?.url !== void 0) {
262
+ await writeDatabaseUrl(target, database.url);
263
+ prompts.log.success("URL de base ecrite dans .env");
264
+ const risque = await assertEnvIgnored(target);
265
+ if (risque !== void 0) prompts.log.warn(risque);
266
+ }
267
+ if (database !== void 0) prompts.log.info(database.note);
268
+ if (withInstall) {
269
+ const install = prompts.spinner();
270
+ install.start(`Installation avec ${manager}`);
271
+ try {
272
+ execSync(installCommand(manager), { cwd: target, stdio: "ignore" });
273
+ install.stop("Dependances installees.");
274
+ } catch {
275
+ install.stop("Installation echouee \u2014 a relancer a la main.");
276
+ }
277
+ }
278
+ const steps = [
279
+ `cd ${basename(target)}`,
280
+ ...withInstall ? [] : [installCommand(manager)],
281
+ runCommand(manager, "dev")
282
+ ];
283
+ prompts.note(steps.join("\n"), "Prochaines etapes");
284
+ prompts.outro(colors.green("Bon developpement."));
285
+ return 0;
286
+ }
287
+
288
+ export { createCommand };