odoro 1.0.9 → 2.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 (88) hide show
  1. package/README.md +94 -0
  2. package/client.d.ts +90 -16
  3. package/dist/build-XJIP4GNT.js +5 -0
  4. package/dist/{chunk-34RGOZFA.js → chunk-4D56Z7G3.js} +49 -50
  5. package/dist/{chunk-JMEHF3KN.js → chunk-7ZB5MAM6.js} +4 -4
  6. package/dist/chunk-DF67LADH.js +735 -0
  7. package/dist/{chunk-LHZGX5ML.js → chunk-HS7NGZLM.js} +307 -196
  8. package/dist/chunk-LIKVHWB4.js +231 -0
  9. package/dist/chunk-QOVBLN7A.js +241 -0
  10. package/dist/chunk-RTWJ5GFB.js +729 -0
  11. package/dist/chunk-TONYIKCN.js +57 -0
  12. package/dist/cli.d.ts +12 -13
  13. package/dist/cli.js +86 -63
  14. package/dist/{commands-AHMXBWQQ.js → commands-GYA7NTCX.js} +124 -124
  15. package/dist/commands-UOWN3IK4.js +181 -0
  16. package/dist/{create-CYVSVDAD.js → create-B2TLV6XI.js} +198 -160
  17. package/dist/index.d.ts +603 -151
  18. package/dist/index.js +6 -6
  19. package/dist/{package-EIDLKAA6.js → package-3ZJOEXM4.js} +6 -5
  20. package/dist/preview-3IRG7MHV.js +4 -0
  21. package/dist/registry/index.d.ts +75 -76
  22. package/dist/registry/index.js +1 -1
  23. package/dist/{server-4UGN3SFS.js → server-ZB3P3EGS.js} +3 -3
  24. package/package.json +6 -5
  25. package/templates/react-ts/README.md +71 -35
  26. package/templates/react-ts/_env.example +9 -0
  27. package/templates/react-ts/_variants/with-engine/src/background.tsx +118 -0
  28. package/templates/react-ts/_variants/without-libs/src/App.tsx +380 -0
  29. package/templates/react-ts/_variants/without-libs/src/background.tsx +24 -0
  30. package/templates/react-ts/_variants/without-libs/src/entry-server.tsx +39 -0
  31. package/templates/react-ts/_variants/without-libs/src/main.tsx +26 -0
  32. package/templates/{react-ts-server/_variantes/sans-libs/client → react-ts/_variants/without-libs}/src/styles.css +137 -137
  33. package/templates/react-ts/{_variantes/sans-routeur → _variants/without-router}/src/App.tsx +141 -141
  34. package/templates/react-ts/_variants/without-router/src/entry-server.tsx +39 -0
  35. package/templates/react-ts/index.html +4 -4
  36. package/templates/react-ts/odoro.config.ts +5 -0
  37. package/templates/react-ts/src/App.tsx +219 -209
  38. package/templates/react-ts/src/background.tsx +66 -0
  39. package/templates/react-ts/src/entry-server.tsx +85 -0
  40. package/templates/react-ts/src/main.tsx +13 -4
  41. package/templates/react-ts/src/router.tsx +65 -45
  42. package/templates/react-ts/src/styles.css +5 -5
  43. package/templates/react-ts-server/Dockerfile +9 -9
  44. package/templates/react-ts-server/README.md +97 -45
  45. package/templates/react-ts-server/_env.example +31 -33
  46. package/templates/react-ts-server/_variants/with-engine/client/src/background.tsx +118 -0
  47. package/templates/react-ts-server/_variants/without-libs/client/src/App.tsx +380 -0
  48. package/templates/react-ts-server/_variants/without-libs/client/src/background.tsx +24 -0
  49. package/templates/react-ts-server/_variants/without-libs/client/src/entry-server.tsx +39 -0
  50. package/templates/react-ts-server/_variants/without-libs/client/src/main.tsx +26 -0
  51. package/templates/{react-ts/_variantes/sans-libs → react-ts-server/_variants/without-libs/client}/src/styles.css +137 -137
  52. package/templates/react-ts-server/{_variantes/sans-routeur → _variants/without-router}/client/src/App.tsx +141 -141
  53. package/templates/react-ts-server/_variants/without-router/client/src/entry-server.tsx +39 -0
  54. package/templates/react-ts-server/client/index.html +4 -4
  55. package/templates/react-ts-server/client/src/App.tsx +228 -210
  56. package/templates/react-ts-server/client/src/account.tsx +267 -0
  57. package/templates/react-ts-server/client/src/auth.tsx +139 -0
  58. package/templates/react-ts-server/client/src/background.tsx +66 -0
  59. package/templates/react-ts-server/client/src/entry-server.tsx +92 -0
  60. package/templates/react-ts-server/client/src/main.tsx +17 -5
  61. package/templates/react-ts-server/client/src/router.tsx +77 -45
  62. package/templates/react-ts-server/client/src/styles.css +5 -5
  63. package/templates/react-ts-server/odoro.config.ts +7 -4
  64. package/templates/react-ts-server/package.json +2 -0
  65. package/templates/react-ts-server/scripts/dev.mjs +14 -14
  66. package/templates/react-ts-server/server/src/main.ts +119 -45
  67. package/templates/react-ts-server/server/src/modules/auth/index.ts +238 -0
  68. package/templates/react-ts-server/server/src/modules/auth/password.ts +122 -0
  69. package/templates/react-ts-server/server/src/modules/auth/store.ts +193 -0
  70. package/templates/react-ts-server/server/src/modules/health/index.ts +45 -46
  71. package/dist/build-JFQHODAT.js +0 -5
  72. package/dist/chunk-22KJTV2R.js +0 -380
  73. package/dist/chunk-3SZIN6VG.js +0 -64
  74. package/dist/chunk-DL3NPC4H.js +0 -113
  75. package/dist/chunk-TQUJ3MFS.js +0 -268
  76. package/dist/chunk-ZVL7EXJO.js +0 -66
  77. package/dist/commands-4JRBD55Z.js +0 -245
  78. package/dist/preview-7DKEAQOQ.js +0 -4
  79. package/templates/react-ts/_variantes/avec-moteur/src/fond.tsx +0 -118
  80. package/templates/react-ts/_variantes/sans-libs/src/App.tsx +0 -382
  81. package/templates/react-ts/_variantes/sans-libs/src/fond.tsx +0 -23
  82. package/templates/react-ts/_variantes/sans-libs/src/main.tsx +0 -17
  83. package/templates/react-ts/src/fond.tsx +0 -66
  84. package/templates/react-ts-server/_variantes/avec-moteur/client/src/fond.tsx +0 -118
  85. package/templates/react-ts-server/_variantes/sans-libs/client/src/App.tsx +0 -382
  86. package/templates/react-ts-server/_variantes/sans-libs/client/src/fond.tsx +0 -23
  87. package/templates/react-ts-server/_variantes/sans-libs/client/src/main.tsx +0 -17
  88. package/templates/react-ts-server/client/src/fond.tsx +0 -66
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile, appendFile, mkdir, chmod } from 'fs/promises';
3
+ import { existsSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { platform, homedir } from 'os';
6
+ import { randomUUID } from 'crypto';
7
+
8
+ function checkDatabaseUrl(value) {
9
+ const url = value.trim();
10
+ if (url.length === 0) return "The URL cannot be empty.";
11
+ if (!/^postgres(ql)?:\/\//i.test(url)) {
12
+ return "A PostgreSQL URL starts with postgres:// or postgresql://";
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = new URL(url);
17
+ } catch {
18
+ return "This string is not a valid URL.";
19
+ }
20
+ if (parsed.hostname.length === 0) return "No host in this URL.";
21
+ if (parsed.pathname.replace(/^\//, "").length === 0) {
22
+ return "No database name: add it after the port, for example /my_project";
23
+ }
24
+ if (!/^(localhost|127\.0\.0\.1|\[::1\])$/i.test(parsed.hostname) && parsed.searchParams.get("sslmode") === "disable") {
25
+ return "sslmode=disable on a remote database: the traffic would go in the clear.";
26
+ }
27
+ return void 0;
28
+ }
29
+ async function writeDatabaseUrl(target, url) {
30
+ const path = join(target, ".env");
31
+ const line = `DATABASE_URL=${url}
32
+ `;
33
+ if (!existsSync(path)) {
34
+ const example = join(target, ".env.example");
35
+ const base = existsSync(example) ? await readFile(example, "utf8") : "";
36
+ await writeFile(path, base.replace(/^DATABASE_URL=.*$/m, line.trimEnd()), "utf8");
37
+ if (!base.includes("DATABASE_URL=")) await appendFile(path, line, "utf8");
38
+ return;
39
+ }
40
+ const current = await readFile(path, "utf8");
41
+ await writeFile(
42
+ path,
43
+ current.includes("DATABASE_URL=") ? current.replace(/^DATABASE_URL=.*$/m, line.trimEnd()) : current + line,
44
+ "utf8"
45
+ );
46
+ }
47
+ async function assertEnvIgnored(target) {
48
+ const path = join(target, ".gitignore");
49
+ if (!existsSync(path)) {
50
+ return "No .gitignore: the .env file risks being versioned.";
51
+ }
52
+ const content = await readFile(path, "utf8");
53
+ const ignored = content.split("\n").map((line) => line.trim()).some((line) => line === ".env" || line === ".env*" || line === "*.env");
54
+ return ignored ? void 0 : ".env does not appear in the .gitignore: your credentials risk being versioned.";
55
+ }
56
+ var PROVIDER_PENDING = [
57
+ "Provisioning goes through @odoro-cli/cloud-sdk, installed separately:",
58
+ "",
59
+ " npm install --save-dev @odoro-cli/cloud-sdk",
60
+ " odoro db:login",
61
+ " odoro db:create --env production",
62
+ "",
63
+ "It does not ship with odoro: this binary is downloaded on every project creation, and most projects use their own database."
64
+ ].join("\n");
65
+
66
+ // src/db/sdk.ts
67
+ var SDK_PACKAGE = "@odoro-cli/cloud-sdk";
68
+ async function loadSdk() {
69
+ try {
70
+ const specifier = SDK_PACKAGE;
71
+ const sdk = await import(specifier);
72
+ return { ok: true, sdk };
73
+ } catch {
74
+ return {
75
+ ok: false,
76
+ reason: [
77
+ `${SDK_PACKAGE} could not be loaded.`,
78
+ "",
79
+ "It ships with odoro, so this is usually a partial install:",
80
+ "",
81
+ " npm install",
82
+ "",
83
+ "Until it loads, a project can still bring its own PostgreSQL URL."
84
+ ].join("\n")
85
+ };
86
+ }
87
+ }
88
+ function configPath(env = processEnv()) {
89
+ const xdg = env["XDG_CONFIG_HOME"];
90
+ if (xdg !== void 0 && xdg.length > 0) return join(xdg, "odoro", "config.json");
91
+ if (platform() === "win32") {
92
+ const appData = env["APPDATA"];
93
+ if (appData !== void 0 && appData.length > 0) {
94
+ return join(appData, "odoro", "config.json");
95
+ }
96
+ }
97
+ return join(homedir(), ".config", "odoro", "config.json");
98
+ }
99
+ function processEnv() {
100
+ return process.env;
101
+ }
102
+ async function readUserConfig(path = configPath()) {
103
+ try {
104
+ return JSON.parse(await readFile(path, "utf8"));
105
+ } catch {
106
+ return {};
107
+ }
108
+ }
109
+ async function writeUserConfig(config, path = configPath()) {
110
+ await mkdir(dirname(path), { recursive: true });
111
+ await writeFile(path, `${JSON.stringify(config, null, 2)}
112
+ `, "utf8");
113
+ if (platform() === "win32") return { path, restricted: false };
114
+ try {
115
+ await chmod(path, 384);
116
+ return { path, restricted: true };
117
+ } catch {
118
+ return { path, restricted: false };
119
+ }
120
+ }
121
+ async function storeToken(apiUrl, token, path = configPath()) {
122
+ const current = await readUserConfig(path);
123
+ return await writeUserConfig(
124
+ {
125
+ ...current,
126
+ tokens: { ...current.tokens, [apiUrl]: token },
127
+ defaultApiUrl: current.defaultApiUrl ?? apiUrl
128
+ },
129
+ path
130
+ );
131
+ }
132
+ async function findToken(apiUrl, path = configPath(), env = processEnv()) {
133
+ const fromEnv = env["ODORO_TOKEN"];
134
+ if (fromEnv !== void 0 && fromEnv.length > 0) return fromEnv;
135
+ const config = await readUserConfig(path);
136
+ return config.tokens?.[apiUrl];
137
+ }
138
+ var DEFAULT_API_URL = "https://db.odoro.dev";
139
+ function idempotencyKey() {
140
+ return randomUUID();
141
+ }
142
+ var CANCELLED = /* @__PURE__ */ Symbol("cancelled");
143
+ function cancelled(value) {
144
+ return value === CANCELLED;
145
+ }
146
+ async function ensureToken(prompts, apiUrl) {
147
+ const known = await findToken(apiUrl);
148
+ if (known !== void 0) return known;
149
+ prompts.log.info(`No token yet for ${apiUrl}. Create one at ${apiUrl}/tokens.`);
150
+ const value = await prompts.password({
151
+ message: "Platform token",
152
+ validate: (input) => input.startsWith("odk_") ? void 0 : "A token starts with odk_live_ or odk_test_"
153
+ });
154
+ if (prompts.isCancel(value)) return CANCELLED;
155
+ const report = await storeToken(apiUrl, value);
156
+ prompts.log.success(`Token recorded in ${report.path}`);
157
+ return value;
158
+ }
159
+ async function chooseEnvironment(prompts, client) {
160
+ const { projects } = await client.projects.list();
161
+ const all = projects.flatMap(
162
+ (project) => project.environments.map((environment) => ({
163
+ id: environment.id,
164
+ label: `${project.name} / ${environment.name}`
165
+ }))
166
+ );
167
+ if (all.length === 0) {
168
+ prompts.log.warn(
169
+ `No environment on this account. Create a project at ${DEFAULT_API_URL}, then run \`odoro db:create\`.`
170
+ );
171
+ return void 0;
172
+ }
173
+ const only = all[0];
174
+ if (all.length === 1 && only !== void 0) {
175
+ prompts.log.info(`Environment: ${only.label}`);
176
+ return only;
177
+ }
178
+ const picked = await prompts.select({
179
+ message: "Environment",
180
+ options: all.map((entry) => ({ value: entry.id, label: entry.label }))
181
+ });
182
+ if (prompts.isCancel(picked)) return CANCELLED;
183
+ return all.find((entry) => entry.id === picked);
184
+ }
185
+ async function chooseRegion(prompts, client) {
186
+ const { regions } = await client.regions.list();
187
+ if (regions.length === 0) return void 0;
188
+ const preferred = regions.includes("eu-central-1") ? "eu-central-1" : regions[0];
189
+ const picked = await prompts.select({
190
+ message: "Region",
191
+ initialValue: preferred,
192
+ options: regions.map((region) => ({ value: region, label: region }))
193
+ });
194
+ if (prompts.isCancel(picked)) return CANCELLED;
195
+ return picked;
196
+ }
197
+ async function provision(input) {
198
+ const abort = new AbortController();
199
+ const onInterrupt = () => abort.abort();
200
+ process.once("SIGINT", onInterrupt);
201
+ try {
202
+ const finished = await input.client.databases.createAndWait(
203
+ {
204
+ idempotencyKey: idempotencyKey(),
205
+ environmentId: input.environmentId,
206
+ ...input.name === void 0 ? {} : { name: input.name },
207
+ ...input.region === void 0 ? {} : { region: input.region }
208
+ },
209
+ { signal: abort.signal }
210
+ );
211
+ if (finished.state !== "succeeded") {
212
+ return {
213
+ ok: false,
214
+ reason: finished.error ?? `The operation ended in state "${finished.state}". \`odoro db:status\` will show where it stands.`
215
+ };
216
+ }
217
+ const databaseId = finished.subject;
218
+ if (databaseId === void 0) {
219
+ return {
220
+ ok: false,
221
+ reason: "The operation succeeded without naming the database it created. `odoro db:status` will show it."
222
+ };
223
+ }
224
+ const { connectionString } = await input.client.credentials.rotate({ databaseId });
225
+ return { ok: true, databaseId, connectionString };
226
+ } finally {
227
+ process.removeListener("SIGINT", onInterrupt);
228
+ }
229
+ }
230
+
231
+ export { DEFAULT_API_URL, PROVIDER_PENDING, SDK_PACKAGE, assertEnvIgnored, cancelled, checkDatabaseUrl, chooseEnvironment, chooseRegion, ensureToken, findToken, loadSdk, provision, storeToken, writeDatabaseUrl };
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ import { guessAliasPaths } from './chunk-T6RLHSCW.js';
3
+ import { existsSync } from 'fs';
4
+ import { readFile, mkdir, writeFile, rm } from 'fs/promises';
5
+ import { join, resolve } from 'path';
6
+ import { pathToFileURL } from 'url';
7
+ import { build } from 'esbuild';
8
+
9
+ var LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
10
+ var REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
11
+ function parseEnvDetailed(text) {
12
+ const values = {};
13
+ const literals = /* @__PURE__ */ new Set();
14
+ const lines = text.split(/\r?\n/);
15
+ for (let index = 0; index < lines.length; index += 1) {
16
+ const line = lines[index];
17
+ if (line === void 0) continue;
18
+ if (line.trim() === "" || line.trimStart().startsWith("#")) continue;
19
+ const found = LINE.exec(line);
20
+ const key = found?.[1];
21
+ if (key === void 0) continue;
22
+ const raw = (found?.[2] ?? "").trim();
23
+ const quote = raw[0];
24
+ if (quote !== '"' && quote !== "'" && quote !== "`") {
25
+ const comment = raw.search(/\s#/);
26
+ values[key] = comment === -1 ? raw : raw.slice(0, comment).trimEnd();
27
+ continue;
28
+ }
29
+ let body = raw.slice(1);
30
+ while (body.length === 0 || !body.endsWith(quote)) {
31
+ const next = lines[index + 1];
32
+ if (next === void 0) break;
33
+ index += 1;
34
+ body = `${body}
35
+ ${next}`;
36
+ }
37
+ const content = body.endsWith(quote) ? body.slice(0, -1) : body;
38
+ if (quote === "'") {
39
+ values[key] = content;
40
+ literals.add(key);
41
+ continue;
42
+ }
43
+ values[key] = content.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
44
+ }
45
+ return { values, literals };
46
+ }
47
+ function expand(values, environment, literals) {
48
+ const rendered = {};
49
+ for (const [key, value] of Object.entries(values)) {
50
+ if (literals.has(key)) {
51
+ rendered[key] = value;
52
+ continue;
53
+ }
54
+ rendered[key] = value.replace(REFERENCE, (whole, braced, bare) => {
55
+ const name = braced ?? bare;
56
+ if (name === void 0) return whole;
57
+ return rendered[name] ?? values[name] ?? environment[name] ?? "";
58
+ });
59
+ }
60
+ return rendered;
61
+ }
62
+ function envFilesFor(mode) {
63
+ return [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`];
64
+ }
65
+ async function loadEnv(directory, mode, prefix) {
66
+ const stacked = {};
67
+ const literals = /* @__PURE__ */ new Set();
68
+ const files = [];
69
+ for (const name of envFilesFor(mode)) {
70
+ const path = join(directory, name);
71
+ if (!existsSync(path)) continue;
72
+ files.push(name);
73
+ const read = parseEnvDetailed(await readFile(path, "utf8"));
74
+ Object.assign(stacked, read.values);
75
+ for (const key of Object.keys(read.values)) {
76
+ if (read.literals.has(key)) literals.add(key);
77
+ else literals.delete(key);
78
+ }
79
+ }
80
+ const all = expand(stacked, process.env, literals);
81
+ for (const [key, value] of Object.entries(process.env)) {
82
+ if (value !== void 0 && key in all) all[key] = value;
83
+ }
84
+ const client = {};
85
+ for (const [key, value] of Object.entries(all)) {
86
+ if (key.startsWith(prefix)) client[key] = value;
87
+ }
88
+ for (const [key, value] of Object.entries(process.env)) {
89
+ if (value !== void 0 && key.startsWith(prefix)) client[key] = value;
90
+ }
91
+ return { all, client, files };
92
+ }
93
+ function clientEnv(variables, mode, base) {
94
+ return {
95
+ MODE: mode,
96
+ // `DEV` and `PROD` cannot be deduced from the mode name alone: a `staging`
97
+ // mode builds as production and must behave like it.
98
+ DEV: mode === "development",
99
+ PROD: mode !== "development",
100
+ BASE_URL: base,
101
+ ...variables
102
+ };
103
+ }
104
+ function defineConfig(config) {
105
+ return config;
106
+ }
107
+ var CONFIG_FILES = ["odoro.config.ts", "odoro.config.js", "odoro.config.mjs"];
108
+ async function importConfigFile(file, root) {
109
+ const directory = join(root, "node_modules", ".odoro");
110
+ const output = join(directory, `config.${Date.now().toString(36)}.mjs`);
111
+ await mkdir(directory, { recursive: true });
112
+ try {
113
+ const result = await build({
114
+ entryPoints: [file],
115
+ bundle: true,
116
+ format: "esm",
117
+ platform: "node",
118
+ target: "node20",
119
+ write: false,
120
+ // Only the project code is inlined; its dependencies stay external,
121
+ // otherwise the whole node_modules would have to be resolved to read
122
+ // three lines.
123
+ packages: "external",
124
+ // `packages: 'external'` is not enough: a `baseUrl` in the
125
+ // `tsconfig.json` makes bare imports be looked up from the project root,
126
+ // where `odoro.json` — the registry file — is found before the package.
127
+ // The import then stops being a package import, the rule above no longer
128
+ // applies, and the build fails on a `defineConfig` missing from a
129
+ // configuration file.
130
+ //
131
+ // Naming it explicitly puts it out of reach of that resolution. The list
132
+ // matters for projects already created: they still carry a `baseUrl`, and
133
+ // their tsconfig cannot be rewritten.
134
+ external: ["odoro"]
135
+ });
136
+ const code = result.outputFiles[0]?.text;
137
+ if (code === void 0) {
138
+ throw new Error(`[odoro] Configuration "${file}" produced no code.`);
139
+ }
140
+ await writeFile(output, code, "utf8");
141
+ const module = await import(pathToFileURL(output).href);
142
+ if (module.default === void 0) {
143
+ throw new Error(`[odoro] Configuration "${file}" must have a default export.`);
144
+ }
145
+ return module.default;
146
+ } finally {
147
+ await rm(output, { force: true });
148
+ }
149
+ }
150
+ function resolvePrerender(requested, root) {
151
+ if (requested === void 0 || requested === false) return void 0;
152
+ const raw = requested === true ? {} : Array.isArray(requested) ? { routes: requested } : requested;
153
+ return {
154
+ routes: raw.routes ?? [],
155
+ entry: resolve(root, raw.entry ?? "src/entry-server.tsx")
156
+ };
157
+ }
158
+ async function loadConfig(root, overrides = {}, defaultMode = "production") {
159
+ const absoluteRoot = resolve(root);
160
+ let file;
161
+ let loaded = {};
162
+ for (const candidate of CONFIG_FILES) {
163
+ const path = join(absoluteRoot, candidate);
164
+ if (existsSync(path)) {
165
+ file = path;
166
+ loaded = await importConfigFile(path, absoluteRoot);
167
+ break;
168
+ }
169
+ }
170
+ const merged = {
171
+ ...loaded,
172
+ ...overrides,
173
+ server: { ...loaded.server, ...overrides.server },
174
+ build: { ...loaded.build, ...overrides.build },
175
+ alias: { ...loaded.alias, ...overrides.alias },
176
+ define: { ...loaded.define, ...overrides.define }
177
+ };
178
+ const base = merged.base ?? "/";
179
+ const projectRoot = merged.root === void 0 ? absoluteRoot : resolve(absoluteRoot, merged.root);
180
+ const mode = merged.mode ?? defaultMode;
181
+ const envPrefix = merged.envPrefix ?? "ODORO_";
182
+ const envDir = merged.envDir === void 0 ? projectRoot : resolve(projectRoot, merged.envDir);
183
+ const environment = await loadEnv(envDir, mode, envPrefix);
184
+ for (const [key, value] of Object.entries(environment.all)) {
185
+ process.env[key] ??= value;
186
+ }
187
+ const https = merged.server?.https;
188
+ return {
189
+ root: projectRoot,
190
+ base: base.endsWith("/") ? base : `${base}/`,
191
+ publicDir: resolve(projectRoot, merged.publicDir ?? "public"),
192
+ outDir: resolve(projectRoot, merged.build?.outDir ?? "dist"),
193
+ server: {
194
+ port: merged.server?.port ?? 5180,
195
+ host: merged.server?.host ?? "localhost",
196
+ proxy: merged.server?.proxy ?? {},
197
+ https: https === void 0 ? void 0 : { ...https },
198
+ strictPort: merged.server?.strictPort ?? false,
199
+ open: merged.server?.open ?? false
200
+ },
201
+ build: {
202
+ outDir: merged.build?.outDir ?? "dist",
203
+ minify: merged.build?.minify ?? true,
204
+ sourcemap: merged.build?.sourcemap ?? true,
205
+ target: merged.build?.target ?? "es2022",
206
+ // On by default: a stylesheet holding every possible class is a
207
+ // generation accident, not an intention.
208
+ prune: merged.build?.prune ?? true,
209
+ safelist: merged.build?.safelist ?? [],
210
+ // On by default: the manifest only costs a file of a few lines, and
211
+ // preloading removes a cascade of requests nobody sees on a local machine
212
+ // and everybody suffers elsewhere.
213
+ manifest: merged.build?.manifest ?? true,
214
+ preload: merged.build?.preload ?? true,
215
+ prerender: resolvePrerender(merged.build?.prerender, projectRoot)
216
+ },
217
+ // The aliases declared in `tsconfig.json` are picked up automatically.
218
+ // Without that, a project following `odoro init` — which infers its prefix
219
+ // from the tsconfig — would have to redeclare the same alias here for the
220
+ // server to resolve it. Two places for the same truth, and an error that
221
+ // only shows up on the first import.
222
+ //
223
+ // The configuration wins: it is what you write to fix a case the inference
224
+ // does not catch.
225
+ alias: { ...await guessAliasPaths(projectRoot), ...merged.alias },
226
+ define: merged.define ?? {},
227
+ envPrefix,
228
+ envDir,
229
+ mode,
230
+ env: environment.all,
231
+ envClient: clientEnv(
232
+ environment.client,
233
+ mode,
234
+ base.endsWith("/") ? base : `${base}/`
235
+ ),
236
+ plugins: merged.plugins ?? [],
237
+ configFile: file
238
+ };
239
+ }
240
+
241
+ export { clientEnv, defineConfig, loadConfig, loadEnv };