ozmoz 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.
@@ -0,0 +1,748 @@
1
+ //scripts/setup-lib.cjs
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const https = require("https");
5
+ const { execSync } = require("child_process");
6
+
7
+ // ANSI Escape Codes for Colors
8
+ const COLOR_BLUE = "\x1b[38;2;231;233;237m";
9
+ const COLOR_RED = "\x1b[31m";
10
+ const COLOR_RESET = "\x1b[0m";
11
+
12
+ function log(msg) {
13
+ console.log(` ${COLOR_BLUE}${msg}${COLOR_RESET}`);
14
+ }
15
+
16
+ function logError(msg) {
17
+ console.error(` ${COLOR_RED}${msg}${COLOR_RESET}`);
18
+ }
19
+
20
+ function parseArgs(args) {
21
+ const flags = {};
22
+ args.forEach((arg) => {
23
+ if (arg.startsWith("--")) {
24
+ const [key, value] = arg.slice(2).split("=");
25
+ if (key && value) {
26
+ flags[key] = value;
27
+ }
28
+ }
29
+ });
30
+ return flags;
31
+ }
32
+
33
+ function detectDependency(pkgPath, name) {
34
+ try {
35
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
36
+ return Boolean(
37
+ (pkg.dependencies && pkg.dependencies[name]) ||
38
+ (pkg.devDependencies && pkg.devDependencies[name])
39
+ );
40
+ } catch (e) {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ function isActuallyInstalled(projectRoot, name) {
46
+ try {
47
+ return fs.existsSync(path.join(projectRoot, "node_modules", name, "package.json"));
48
+ } catch (e) {
49
+ return false;
50
+ }
51
+ }
52
+
53
+ function detectPackageManager(projectRoot, pkgPath) {
54
+ if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
55
+ if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) return "yarn";
56
+ if (fs.existsSync(path.join(projectRoot, "bun.lockb"))) return "bun";
57
+ try {
58
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
59
+ if (typeof pkg.packageManager === "string") {
60
+ if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
61
+ if (pkg.packageManager.startsWith("yarn")) return "yarn";
62
+ if (pkg.packageManager.startsWith("bun")) return "bun";
63
+ }
64
+ } catch (e) {}
65
+ return "npm";
66
+ }
67
+
68
+ function installPackageIfMissing(projectRoot, pkgPath) {
69
+ if (!detectDependency(pkgPath, "ozmoz") || !isActuallyInstalled(projectRoot, "ozmoz")) {
70
+ const pm = detectPackageManager(projectRoot, pkgPath);
71
+ const installCmd = {
72
+ pnpm: "pnpm add ozmoz",
73
+ yarn: "yarn add ozmoz",
74
+ bun: "bun add ozmoz",
75
+ npm: "npm install ozmoz",
76
+ }[pm];
77
+
78
+ log(`โ†’ Installing ozmoz into the project (detected: ${pm})...`);
79
+ try {
80
+ execSync(installCmd, { cwd: projectRoot, stdio: "inherit" });
81
+ log("ozmoz installed successfully!");
82
+ } catch (e) {
83
+ logError(`โš ๏ธ Automatic installation failed: ${e.message}`);
84
+ logError(`๐Ÿ‘‰ Please run '${installCmd}' manually.`);
85
+ }
86
+ }
87
+ }
88
+
89
+ // Installs a dev dependency in the user's project when it is missing (same package manager as the project).
90
+ function ensureDevPackage(projectRoot, pkgPath, name, versionSpec) {
91
+ if (detectDependency(pkgPath, name) && isActuallyInstalled(projectRoot, name)) return;
92
+ const pm = detectPackageManager(projectRoot, pkgPath);
93
+ const spec = `"${name}@${versionSpec}"`;
94
+ const installCmd = {
95
+ pnpm: `pnpm add -D ${spec}`,
96
+ yarn: `yarn add -D ${spec}`,
97
+ bun: `bun add -d ${spec}`,
98
+ npm: `npm install -D ${spec}`,
99
+ }[pm];
100
+
101
+ log(`โ†’ Installing ${name} into the project (detected: ${pm})...`);
102
+ try {
103
+ execSync(installCmd, { cwd: projectRoot, stdio: "inherit" });
104
+ log(`${name} installed successfully!`);
105
+ } catch (e) {
106
+ logError(`โš ๏ธ Automatic installation failed: ${e.message}`);
107
+ logError(`๐Ÿ‘‰ Please run manually.`);
108
+ }
109
+ }
110
+
111
+ function ensureSchemaFile(projectRoot) {
112
+ const schemaPath = path.join(projectRoot, "svro.schema.json");
113
+ if (fs.existsSync(schemaPath)) return;
114
+ const now = new Date().toISOString();
115
+ const initial = {
116
+ createdAt: now,
117
+ patchedAt: now,
118
+ schemaVersion: 1,
119
+ collections: {},
120
+ };
121
+ fs.writeFileSync(schemaPath, JSON.stringify(initial, null, 2) + "\n");
122
+ log("โ†’ Created svro.schema.json at project root.");
123
+ }
124
+
125
+ function ensureEnvPrototype(projectRoot) {
126
+ const envPath = path.join(projectRoot, ".env");
127
+ const defaultVar = "\n# ozmoZ UI Sandbox Mode\n# true = Sandbox mode: simulates writes locally for fluid UI testing; no DB access and IDE alert.\n# false = Strict production mode: enforces schema contracts and persists to DB\nOZ_PROTOTYPE=true\n";
128
+
129
+ if (!fs.existsSync(envPath)) {
130
+ fs.writeFileSync(envPath, defaultVar.trimStart());
131
+ log("โ†’ Created .env with OZ_PROTOTYPE=true");
132
+ } else {
133
+ const content = fs.readFileSync(envPath, "utf8");
134
+ if (!content.includes("OZ_PROTOTYPE")) {
135
+ fs.appendFileSync(envPath, defaultVar);
136
+ log("โ†’ Injected OZ_PROTOTYPE=true into existing .env");
137
+ }
138
+ }
139
+ }
140
+
141
+ function ensureAutoImportsDts(projectRoot) {
142
+ const srcDir = path.join(projectRoot, "src");
143
+ if (!fs.existsSync(srcDir)) return;
144
+
145
+ const dtsPath = path.join(srcDir, "ozmoz.d.ts");
146
+ if (fs.existsSync(dtsPath)) return;
147
+
148
+ const stubContent = `/* eslint-disable */
149
+ /* prettier-ignore */
150
+ // @ts-nocheck
151
+ export {}
152
+
153
+ type OzOtpOptions = {
154
+ email: string;
155
+ appName?: string;
156
+ subject?: string;
157
+ message?: string;
158
+ logoUrl?: string;
159
+ };
160
+
161
+ type OzPhoneOtpOptions = {
162
+ phoneNumber: string;
163
+ appName?: string;
164
+ };
165
+
166
+ type OzFacebookAuthOptions = {
167
+ accessToken?: string;
168
+ scope?: string;
169
+ };
170
+
171
+ type OzProp = {
172
+ value?: any;
173
+ fallback?: string;
174
+ 'invalid-fallback'?: string;
175
+ currency?: string;
176
+ locale?: string;
177
+ allowed?: string;
178
+ alt?: string;
179
+ table?: any;
180
+ id?: string;
181
+ field?: string;
182
+ 'label-true'?: string;
183
+ 'label-false'?: string;
184
+ 'orphan-fallback'?: string;
185
+ 'loading-fallback'?: string;
186
+ 'malformed-fallback'?: string;
187
+ class?: string;
188
+ className?: string;
189
+ children?: any;
190
+ [key: string]: any;
191
+ };
192
+
193
+ declare global {
194
+ interface ImportMetaEnv {
195
+ readonly OZ_DB_KEY?: string;
196
+ readonly OZ_ENTERPRISE_ID?: string;
197
+ readonly OZ_KEY_APP?: string;
198
+ readonly OZ_PROTOTYPE?: string;
199
+ readonly OZ_FACEBOOK_APP_ID?: string;
200
+ readonly [key: string]: any;
201
+ }
202
+
203
+ interface ImportMeta {
204
+ readonly env: ImportMetaEnv;
205
+ }
206
+
207
+ type OzTables = string;
208
+ const oz: typeof import('ozmoz')['oz'];
209
+
210
+ interface Window {
211
+ oz: typeof oz;
212
+ FB?: any;
213
+ }
214
+
215
+ namespace JSX {
216
+ interface IntrinsicElements {
217
+ 'oz-text': OzProp;
218
+ 'oz-number': OzProp;
219
+ 'oz-toggle': OzProp;
220
+ 'oz-date': OzProp;
221
+ 'oz-enum': OzProp;
222
+ 'oz-media': OzProp;
223
+ 'oz-reference': OzProp;
224
+ }
225
+ }
226
+
227
+ namespace React {
228
+ namespace JSX {
229
+ interface IntrinsicElements {
230
+ 'oz-text': OzProp;
231
+ 'oz-number': OzProp;
232
+ 'oz-toggle': OzProp;
233
+ 'oz-date': OzProp;
234
+ 'oz-enum': OzProp;
235
+ 'oz-media': OzProp;
236
+ 'oz-reference': OzProp;
237
+ }
238
+ }
239
+ }
240
+ }
241
+ `;
242
+ try {
243
+ fs.writeFileSync(dtsPath, stubContent);
244
+ log("โ†’ Created src/ozmoz.d.ts stub with strict oz.* and <oz-*> typings.");
245
+ } catch (e) {
246
+ logError(`โš ๏ธ Unable to create src/ozmoz.d.ts: ${e.message}`);
247
+ }
248
+ }
249
+
250
+ function ensureDts(packageRoot) {
251
+ const dtsPath = path.join(packageRoot, "index.d.ts");
252
+ if (fs.existsSync(dtsPath)) return;
253
+ log("โ†’ Verified index.d.ts presence.");
254
+ }
255
+
256
+ function touchTsConfig(projectRoot) {
257
+ const candidates = ['tsconfig.json', 'jsconfig.json'];
258
+ for (const filename of candidates) {
259
+ const fullPath = path.join(projectRoot, filename);
260
+ if (fs.existsSync(fullPath)) {
261
+ const now = new Date();
262
+ fs.utimesSync(fullPath, now, now);
263
+ break;
264
+ }
265
+ }
266
+ }
267
+
268
+ function exchangeBootstrapToken(token) {
269
+ return new Promise((resolve, reject) => {
270
+ const postData = JSON.stringify({ token });
271
+ const options = {
272
+ hostname: "bootstrapproject-jqycakhlxa-uc.a.run.app",
273
+ path: "/",
274
+ method: "POST",
275
+ headers: {
276
+ "Content-Type": "application/json",
277
+ "Content-Length": Buffer.byteLength(postData),
278
+ },
279
+ };
280
+ const req = https.request(options, (res) => {
281
+ let data = "";
282
+ res.on("data", (chunk) => { data += chunk; });
283
+ res.on("end", () => {
284
+ if (res.statusCode >= 200 && res.statusCode < 300) {
285
+ try { resolve(JSON.parse(data)); }
286
+ catch (e) { reject(new Error("Invalid response received from ozmoZ server.")); }
287
+ } else {
288
+ try {
289
+ const parsed = JSON.parse(data);
290
+ reject(new Error(parsed.error || `Server error ${res.statusCode}`));
291
+ } catch (e) {
292
+ reject(new Error(`Server error ${res.statusCode}`));
293
+ }
294
+ }
295
+ });
296
+ });
297
+ req.on("error", (e) => reject(e));
298
+ req.write(postData);
299
+ req.end();
300
+ });
301
+ }
302
+
303
+ async function handleAuthAndEnv(projectRoot, args = []) {
304
+ const envPath = path.join(projectRoot, ".env");
305
+
306
+ if (fs.existsSync(envPath) && fs.readFileSync(envPath, "utf8").includes("OZ_DB_KEY")) {
307
+ log("โ†’ ozmoZ environment variables already present in .env โ€” skipping step.");
308
+ return;
309
+ }
310
+
311
+ const flags = parseArgs(args);
312
+ const keyApp = flags.keyApp;
313
+ const bootstrapToken = flags.token || flags.bootstrapToken;
314
+ const testerEmail = flags.email || "";
315
+
316
+ if (!keyApp || !bootstrapToken) {
317
+ logError("โš ๏ธ Missing required parameters.");
318
+ logError("๐Ÿ‘‰ Usage: npx ozmoz init --keyApp=YOUR_KEY --token=YOUR_TOKEN");
319
+ process.exit(1);
320
+ }
321
+
322
+ log("โ†’ Exchanging bootstrap token for project keys...");
323
+ let keys;
324
+ try {
325
+ keys = await exchangeBootstrapToken(bootstrapToken);
326
+ } catch (err) {
327
+ logError(`โš ๏ธ Token exchange failed: ${err.message}`);
328
+ logError("๐Ÿ‘‰ The token may have expired (15 min limit) or was already used. Please regenerate one from the ozmoZ dashboard.");
329
+ process.exit(1);
330
+ }
331
+
332
+ if (!keys.enterpriseId || !keys.fleetboDBKey) {
333
+ logError("โš ๏ธ Incomplete response from ozmoZ server.");
334
+ process.exit(1);
335
+ }
336
+
337
+ const fbAppId = keys.facebookAppId || flags.fbAppId || flags.facebookAppId || "11967268204758";
338
+
339
+ const envContent = `\nOZ_DB_KEY=${keys.fleetboDBKey}\nOZ_ENTERPRISE_ID=${keys.enterpriseId}\nOZ_KEY_APP=${keyApp}\n${testerEmail ? `OZ_TESTER_EMAIL=${testerEmail}\n` : ""}OZ_FACEBOOK_APP_ID=${fbAppId}\n`;
340
+
341
+ fs.appendFileSync(envPath, envContent);
342
+ log(".env file updated successfully with project keys!");
343
+ }
344
+
345
+ function cleanupLegacyRootDts(projectRoot) {
346
+ const legacyPath = path.join(projectRoot, "svro.d.ts");
347
+ if (fs.existsSync(legacyPath)) {
348
+ try {
349
+ fs.unlinkSync(legacyPath);
350
+ log("โ†’ Removed legacy svro.d.ts from project root.");
351
+ } catch (e) {}
352
+ }
353
+ }
354
+
355
+ function patchJsOrTsConfig(projectRoot, pkgPath) {
356
+ if (process.env.FLEETBO_SKIP_TYPECHECK_SETUP) return;
357
+
358
+ const tsconfigPath = path.join(projectRoot, "tsconfig.json");
359
+ const jsconfigPath = path.join(projectRoot, "jsconfig.json");
360
+ const targetPath = fs.existsSync(tsconfigPath) ? tsconfigPath : jsconfigPath;
361
+ const isNew = !fs.existsSync(targetPath);
362
+
363
+ let config = {};
364
+ if (!isNew) {
365
+ try {
366
+ config = JSON.parse(fs.readFileSync(targetPath, "utf8"));
367
+ } catch (e) {
368
+ logError(`โš ๏ธ Unable to read ${path.basename(targetPath)} (Invalid JSON).`);
369
+ return;
370
+ }
371
+ }
372
+
373
+ config.compilerOptions = config.compilerOptions || {};
374
+ let changed = false;
375
+
376
+ const isReact = detectDependency(pkgPath, "react");
377
+
378
+ if (isNew) {
379
+ config.compilerOptions.checkJs = true;
380
+ config.compilerOptions.allowJs = true;
381
+ config.compilerOptions.target = config.compilerOptions.target || "ES2020";
382
+ config.compilerOptions.module = config.compilerOptions.module || "ESNext";
383
+ config.compilerOptions.moduleResolution = config.compilerOptions.moduleResolution || "bundler";
384
+ config.include = ["src/**/*"];
385
+ changed = true;
386
+ } else {
387
+ if (config.compilerOptions.checkJs === undefined) {
388
+ config.compilerOptions.checkJs = true;
389
+ changed = true;
390
+ }
391
+ if (config.compilerOptions.allowJs === undefined) {
392
+ config.compilerOptions.allowJs = true;
393
+ changed = true;
394
+ }
395
+ }
396
+
397
+ if (isReact && config.compilerOptions.jsx === undefined) {
398
+ config.compilerOptions.jsx = "react-jsx";
399
+ changed = true;
400
+ }
401
+
402
+ if (Array.isArray(config.compilerOptions.types) && config.compilerOptions.types.includes("vite/client")) {
403
+ config.compilerOptions.types = config.compilerOptions.types.filter((t) => t !== "vite/client");
404
+ if (config.compilerOptions.types.length === 0) {
405
+ delete config.compilerOptions.types;
406
+ }
407
+ changed = true;
408
+ log(`โ†’ Removed legacy "vite/client" entry from ${path.basename(targetPath)}.`);
409
+ }
410
+
411
+ if (changed) {
412
+ fs.writeFileSync(targetPath, JSON.stringify(config, null, 2) + "\n");
413
+ log(`โ†’ Updated ${path.basename(targetPath)}.`);
414
+ }
415
+ }
416
+
417
+ function injectNextAppRouterRuntime(projectRoot) {
418
+ const candidates = [
419
+ 'src/app/layout.tsx', 'src/app/layout.jsx', 'src/app/layout.js',
420
+ 'app/layout.tsx', 'app/layout.jsx', 'app/layout.js'
421
+ ];
422
+ const rel = candidates.find((p) => fs.existsSync(path.join(projectRoot, p)));
423
+ if (!rel) return false;
424
+
425
+ const layoutPath = path.join(projectRoot, rel);
426
+ const dir = path.dirname(layoutPath);
427
+ const isTs = rel.endsWith('.tsx');
428
+ const ext = isTs ? 'tsx' : 'jsx';
429
+
430
+ const clientSrc = isTs
431
+ ? `'use client';\nimport 'ozmoz';\n\ntype OzConfig = { prototype: boolean; dbKey?: string; enterpriseId?: string };\n\n// Fichier gรฉnรฉrรฉ par ozmoZ : embarque le SDK dans le navigateur et lui transmet la config du .env.\nexport default function OzClient({ config }: { config: OzConfig }) {\n Object.assign(globalThis, { OZ_CONFIG: config });\n return null;\n}\n`
432
+ : `'use client';\nimport 'ozmoz';\n\n// Fichier gรฉnรฉrรฉ par ozmoZ : embarque le SDK dans le navigateur et lui transmet la config du .env.\nexport default function OzClient({ config }) {\n globalThis.OZ_CONFIG = config;\n return null;\n}\n`;
433
+ const runtimeSrc = `import OzClient from './ozmoz-client';\n\n// Fichier gรฉnรฉrรฉ par ozmoZ : lit le .env cรดtรฉ serveur (source de vรฉritรฉ) et le transmet au navigateur.\nexport default function OzRuntime() {\n const config = {\n prototype: process.env.OZ_PROTOTYPE === 'true',\n dbKey: process.env.OZ_DB_KEY,\n enterpriseId: process.env.OZ_ENTERPRISE_ID,\n };\n return <OzClient config={config} />;\n}\n`;
434
+
435
+ const clientPath = path.join(dir, `ozmoz-client.${ext}`);
436
+ const runtimePath = path.join(dir, `ozmoz-runtime.${ext}`);
437
+ if (!fs.existsSync(clientPath)) { fs.writeFileSync(clientPath, clientSrc); log(`โ†’ Created ${path.relative(projectRoot, clientPath)}.`); }
438
+ if (!fs.existsSync(runtimePath)) { fs.writeFileSync(runtimePath, runtimeSrc); log(`โ†’ Created ${path.relative(projectRoot, runtimePath)}.`); }
439
+
440
+ let content = fs.readFileSync(layoutPath, 'utf8');
441
+ if (content.includes('OzRuntime')) return true;
442
+
443
+ content = content.replace(/^import 'ozmoz';\r?\n/, '');
444
+
445
+ if (/^\s*['"]use client['"]/.test(content) || !/<body\b[^>]*>/.test(content)) {
446
+ logError(`โš ๏ธ Could not wire ${path.relative(projectRoot, layoutPath)} automatically.`);
447
+ logError("๐Ÿ‘‰ Add `import OzRuntime from './ozmoz-runtime';` and render <OzRuntime /> inside <body>.");
448
+ return true;
449
+ }
450
+ content = `import OzRuntime from './ozmoz-runtime';\n` + content;
451
+ content = content.replace(/(<body\b[^>]*>)/, '$1\n <OzRuntime />');
452
+ fs.writeFileSync(layoutPath, content);
453
+ log(`โ†’ Wired <OzRuntime /> into ${path.relative(projectRoot, layoutPath)}.`);
454
+ return true;
455
+ }
456
+
457
+ const BUNDLER_DEPS = [
458
+ 'vite', 'next', 'nuxt', 'astro', 'webpack', 'parcel', 'rollup', 'esbuild', 'snowpack', 'react-scripts',
459
+ '@sveltejs/kit', '@angular/cli', '@vue/cli-service', 'gatsby', 'remix', '@remix-run/dev'
460
+ ];
461
+ const BUNDLER_CONFIGS = [
462
+ 'vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'next.config.js', 'next.config.mjs', 'next.config.ts',
463
+ 'webpack.config.js', 'svelte.config.js', 'nuxt.config.ts', 'nuxt.config.js', 'astro.config.mjs', 'angular.json'
464
+ ];
465
+
466
+ function hasBundler(projectRoot) {
467
+ if (BUNDLER_CONFIGS.some((f) => fs.existsSync(path.join(projectRoot, f)))) return true;
468
+ try {
469
+ const pkg = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
470
+ const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
471
+ return BUNDLER_DEPS.some((d) => d in deps);
472
+ } catch (_) {
473
+ return false;
474
+ }
475
+ }
476
+
477
+ function parseDotEnv(envPath) {
478
+ const out = {};
479
+ if (!fs.existsSync(envPath)) return out;
480
+ for (const line of fs.readFileSync(envPath, 'utf8').split(/\r?\n/)) {
481
+ const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*?)\s*$/);
482
+ if (m) out[m[1]] = m[2].replace(/^['"]|['"]$/g, '');
483
+ }
484
+ return out;
485
+ }
486
+
487
+ function writeBrowserConfig(projectRoot) {
488
+ if (hasBundler(projectRoot)) return false;
489
+ const htmlDir = ['.', 'public'].find((d) => fs.existsSync(path.join(projectRoot, d, 'index.html')));
490
+ if (htmlDir === undefined) return false;
491
+
492
+ const env = parseDotEnv(path.join(projectRoot, '.env'));
493
+ const config = {
494
+ prototype: env.OZ_PROTOTYPE === 'true',
495
+ dbKey: env.OZ_DB_KEY || '',
496
+ enterpriseId: env.OZ_ENTERPRISE_ID || ''
497
+ };
498
+ const target = path.join(projectRoot, htmlDir, 'ozmoz.config.js');
499
+ const body =
500
+ '// Gรฉnรฉrรฉ par `npx ozmoz init` / `sync` depuis le .env : NE PAS MODIFIER ร€ LA MAIN.\n' +
501
+ '// ร€ charger AVANT le SDK : <script src="ozmoz.config.js"></script>\n' +
502
+ 'window.OZ_CONFIG = ' + JSON.stringify(config, null, 2) + ';\n';
503
+ if (fs.existsSync(target) && fs.readFileSync(target, 'utf8') === body) return true;
504
+ fs.writeFileSync(target, body);
505
+ log(`โ†’ Wrote ${path.join(htmlDir, 'ozmoz.config.js')} from .env (no bundler detected).`);
506
+ return true;
507
+ }
508
+
509
+ function injectUniversalRuntime(projectRoot) {
510
+ if (injectNextAppRouterRuntime(projectRoot)) return;
511
+
512
+ const entryFiles = [
513
+ 'src/app/layout.tsx', 'src/app/layout.jsx', 'app/layout.tsx', 'app/layout.jsx',
514
+ 'src/pages/_app.tsx', 'src/pages/_app.jsx', 'pages/_app.tsx', 'pages/_app.jsx',
515
+ 'src/main.tsx', 'src/main.jsx', 'src/main.ts', 'src/main.js',
516
+ 'src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js',
517
+ 'src/App.tsx', 'src/App.jsx', 'src/App.vue',
518
+ 'app.vue', 'src/routes/+layout.svelte', 'src/routes/+layout.ts',
519
+ 'src/main.singletons.ts'
520
+ ];
521
+
522
+ for (const relPath of entryFiles) {
523
+ const fullPath = path.join(projectRoot, relPath);
524
+ if (fs.existsSync(fullPath)) {
525
+ let content = fs.readFileSync(fullPath, 'utf8');
526
+ if (!content.includes('ozmoz')) {
527
+ if (content.startsWith('"use client"') || content.startsWith("'use client'")) {
528
+ content = content.replace(/(['"]use client['"];?\r?\n)/, `$1import 'ozmoz';\n`);
529
+ } else {
530
+ content = `import 'ozmoz';\n` + content;
531
+ }
532
+ fs.writeFileSync(fullPath, content);
533
+ }
534
+ return;
535
+ }
536
+ }
537
+ }
538
+
539
+ const SYNC_HANDLER_V2 = ` configureServer(server) {
540
+ // ozmoZ sync handler: la synchronisation ne bloque plus le dev server
541
+ let syncing = false;
542
+ let pending = false;
543
+ const runSync = async () => {
544
+ if (syncing) { pending = true; return; }
545
+ syncing = true;
546
+ try {
547
+ const fs = await import('node:fs');
548
+ const path = await import('node:path');
549
+ const { spawn } = await import('node:child_process');
550
+ const localCli = path.join(process.cwd(), 'node_modules', 'ozmoz', 'dist', 'cli.cjs');
551
+ const child = fs.existsSync(localCli)
552
+ ? spawn(process.execPath, [localCli, 'sync'], { stdio: 'ignore' })
553
+ : spawn('npx', ['--no-install', 'ozmoz', 'sync'], { stdio: 'ignore', shell: process.platform === 'win32' });
554
+ child.on('error', () => { syncing = false; });
555
+ child.on('exit', () => { syncing = false; if (pending) { pending = false; runSync(); } });
556
+ } catch (_) { syncing = false; }
557
+ };
558
+ server.middlewares.use('/__ozmoz_sync_schema', (req, res) => {
559
+ if (req.method !== 'POST') { res.writeHead(405).end(); return; }
560
+ let body = '';
561
+ req.on('data', (chunk) => { body += chunk; });
562
+ req.on('end', async () => {
563
+ try {
564
+ const { schema } = JSON.parse(body);
565
+ let queued = false;
566
+ if (schema) {
567
+ const fs = await import('node:fs');
568
+ const path = await import('node:path');
569
+ const schemaPath = path.join(process.cwd(), 'svro.schema.json');
570
+ let localVersion = 0;
571
+ try { localVersion = Number(JSON.parse(fs.readFileSync(schemaPath, 'utf8')).schemaVersion) || 0; } catch (_) {}
572
+ if ((Number(schema.schemaVersion) || 0) > localVersion) {
573
+ fs.writeFileSync(schemaPath, JSON.stringify(schema, null, 2) + '\\n');
574
+ queued = true;
575
+ runSync();
576
+ }
577
+ }
578
+ res.writeHead(200, { 'Content-Type': 'application/json' });
579
+ res.end(JSON.stringify({ success: true, queued }));
580
+ } catch (e) {
581
+ res.writeHead(500, { 'Content-Type': 'application/json' });
582
+ res.end(JSON.stringify({ success: false, error: e.message }));
583
+ }
584
+ });
585
+ });
586
+ }`;
587
+
588
+ function attemptViteAutoImportPatch(projectRoot, pkgPath) {
589
+ if (process.env.FLEETBO_SKIP_AUTOIMPORT_SETUP) return;
590
+
591
+ const candidates = ["vite.config.ts", "vite.config.js", "vite.config.mjs"];
592
+ let found = candidates.map(f => path.join(projectRoot, f)).find(p => fs.existsSync(p));
593
+ if (!found) {
594
+ // A plain `npm create vite` project has no vite.config file. Create a minimal one,
595
+ // otherwise envPrefix is never set and the OZ_* variables stay invisible in the browser.
596
+ if (!detectDependency(pkgPath, "vite") || detectDependency(pkgPath, "next")) return;
597
+ let isEsm = false;
598
+ try { isEsm = JSON.parse(fs.readFileSync(pkgPath, "utf8")).type === "module"; } catch (_) {}
599
+ found = path.join(projectRoot, isEsm ? "vite.config.js" : "vite.config.mjs");
600
+ fs.writeFileSync(found, "import { defineConfig } from 'vite';\n\nexport default defineConfig({\n plugins: [],\n});\n");
601
+ log("โ†’ Created " + path.basename(found) + " (none found) so the OZ_* variables reach the browser.");
602
+ }
603
+
604
+ let content;
605
+ try {
606
+ content = fs.readFileSync(found, "utf8");
607
+ } catch (e) {
608
+ return;
609
+ }
610
+
611
+ const importSource = "ozmoz";
612
+ let patched = content;
613
+
614
+ // Configuration de envPrefix pour rendre OZ_ accessible cรดtรฉ client dans import.meta.env
615
+ if (!patched.includes("envPrefix") && patched.includes("defineConfig({")) {
616
+ patched = patched.replace(
617
+ "defineConfig({",
618
+ "defineConfig({\n envPrefix: ['VITE_', 'OZ_'],"
619
+ );
620
+ }
621
+
622
+ const autoRuntimePlugin = `{
623
+ name: 'ozmoz-auto-runtime',
624
+ transform(code, id) {
625
+ if (!id.includes('node_modules') && !code.includes('ozmoz') && /(src[\\\\/](main|index|App|root))\\.(jsx?|tsx?)(?:$|\\?)/i.test(id)) {
626
+ return {
627
+ code: "import 'ozmoz';\\n" + code,
628
+ map: null
629
+ };
630
+ }
631
+ return null;
632
+ },
633
+ ${SYNC_HANDLER_V2}
634
+ },`;
635
+
636
+ if (!patched.includes("svro.schema.json")) {
637
+ if (patched.includes("server:")) {
638
+ if (patched.includes("watch:")) {
639
+ patched = patched.replace(
640
+ /watch\s*:\s*\{/,
641
+ `watch: {\n ignored: ['**/svro.schema.json', '**/src/auto-imports.d.ts'],`
642
+ );
643
+ } else {
644
+ patched = patched.replace(
645
+ /server\s*:\s*\{/,
646
+ `server: {\n watch: {\n ignored: ['**/svro.schema.json', '**/src/auto-imports.d.ts']\n },`
647
+ );
648
+ }
649
+ } else if (patched.includes("defineConfig({")) {
650
+ patched = patched.replace(
651
+ "defineConfig({",
652
+ `defineConfig({\n server: {\n watch: {\n ignored: ['**/svro.schema.json', '**/src/auto-imports.d.ts']\n }\n },`
653
+ );
654
+ }
655
+ }
656
+
657
+ if (!patched.includes("ozmoz-auto-runtime")) {
658
+ const pluginsArrayMatch = patched.match(/plugins\s*:\s*\[/);
659
+ if (pluginsArrayMatch) {
660
+ const insertIndex = pluginsArrayMatch.index + pluginsArrayMatch[0].length;
661
+ patched = patched.slice(0, insertIndex) + "\n " + autoRuntimePlugin + patched.slice(insertIndex);
662
+ }
663
+ }
664
+
665
+ patched = patched.replace('(jsx?|tsx?|vue|svelte)/i.test(id)', () => '(jsx?|tsx?)(?:$|\\?)/i.test(id)');
666
+
667
+ if (patched !== content) {
668
+ fs.writeFileSync(found, patched);
669
+ }
670
+
671
+ }
672
+
673
+ function ensureVscodeCyanHints(projectRoot) {
674
+ const vscodeDir = path.join(projectRoot, ".vscode");
675
+ const settingsPath = path.join(vscodeDir, "settings.json");
676
+
677
+ try {
678
+ if (!fs.existsSync(vscodeDir)) {
679
+ fs.mkdirSync(vscodeDir, { recursive: true });
680
+ }
681
+
682
+ let settings = {};
683
+ if (fs.existsSync(settingsPath)) {
684
+ try {
685
+ settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
686
+ } catch (_) {
687
+ settings = {};
688
+ }
689
+ }
690
+
691
+ settings["workbench.colorCustomizations"] = settings["workbench.colorCustomizations"] || {};
692
+ settings["workbench.colorCustomizations"]["editorInfo.foreground"] = "#00feae";
693
+ settings["workbench.colorCustomizations"]["editorHint.foreground"] = "#00feae";
694
+
695
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
696
+ log("โ†’ Configured ozmoZ Cyan visual hints in .vscode/settings.json.");
697
+ } catch (e) {}
698
+ }
699
+
700
+ function patchAllowScripts(projectRoot) {
701
+ const pkgPath = path.join(projectRoot, "package.json");
702
+ if (!fs.existsSync(pkgPath)) return;
703
+
704
+ try {
705
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
706
+ pkg.allowScripts = pkg.allowScripts || {};
707
+ if (pkg.allowScripts["fsevents"] === true) return;
708
+
709
+ pkg.allowScripts["fsevents"] = true;
710
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
711
+ log("โ†’ Silenced native build warnings (fsevents) in package.json.");
712
+ } catch (e) {}
713
+ }
714
+
715
+ function runFullSetup(projectRoot, pkgPath, options = {}) {
716
+ const { skipSelfInstall = false } = options;
717
+ const packageRoot = path.join(__dirname, "..");
718
+ if (!skipSelfInstall) {
719
+ installPackageIfMissing(projectRoot, pkgPath);
720
+ }
721
+ ensureSchemaFile(projectRoot);
722
+ ensureEnvPrototype(projectRoot);
723
+ ensureDts(packageRoot);
724
+ ensureAutoImportsDts(projectRoot);
725
+ ensureVscodeCyanHints(projectRoot);
726
+ cleanupLegacyRootDts(projectRoot);
727
+ patchJsOrTsConfig(projectRoot, pkgPath);
728
+ attemptViteAutoImportPatch(projectRoot, pkgPath);
729
+ injectUniversalRuntime(projectRoot);
730
+ writeBrowserConfig(projectRoot);
731
+ patchAllowScripts(projectRoot);
732
+ }
733
+
734
+ module.exports = {
735
+ touchTsConfig,
736
+ log,
737
+ logError,
738
+ handleAuthAndEnv,
739
+ detectDependency,
740
+ cleanupLegacyRootDts,
741
+ patchJsOrTsConfig,
742
+ attemptViteAutoImportPatch,
743
+ injectUniversalRuntime,
744
+ injectNextAppRouterRuntime,
745
+ writeBrowserConfig,
746
+ runFullSetup,
747
+ patchAllowScripts
748
+ };