odoro 2.0.0 → 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.
- package/dist/build-XJIP4GNT.js +5 -0
- package/dist/{chunk-Q2KCMTB5.js → chunk-DF67LADH.js} +10 -16
- package/dist/{chunk-OMHEQVIW.js → chunk-HS7NGZLM.js} +17 -41
- package/dist/chunk-LIKVHWB4.js +231 -0
- package/dist/{chunk-WWTDEV4Y.js → chunk-RTWJ5GFB.js} +129 -4
- package/dist/{chunk-PRWVZ2SM.js → chunk-TONYIKCN.js} +6 -23
- package/dist/cli.js +8 -6
- package/dist/{commands-EUGVLQWL.js → commands-UOWN3IK4.js} +20 -83
- package/dist/{create-QX5LEWMS.js → create-B2TLV6XI.js} +45 -11
- package/dist/index.js +4 -4
- package/dist/{package-T4Z7OBOH.js → package-3ZJOEXM4.js} +2 -1
- package/dist/preview-3IRG7MHV.js +4 -0
- package/dist/{server-2WP562EV.js → server-ZB3P3EGS.js} +2 -2
- package/package.json +2 -1
- package/dist/build-LZAUDJST.js +0 -5
- package/dist/chunk-P3DJITWE.js +0 -64
- package/dist/preview-P4WY5GEY.js +0 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { extractEntries, cssProviderFor } from './chunk-
|
|
3
|
-
import { transformHtmlWith, sourcePlugin, esbuildPluginsFrom, applyAlias, isBareSpecifier, readSuffix, textModule, urlModule, workerModule } from './chunk-
|
|
2
|
+
import { extractEntries, cssProviderFor } from './chunk-HS7NGZLM.js';
|
|
3
|
+
import { transformHtmlWith, sourcePlugin, esbuildPluginsFrom, ASSET_EXTENSIONS, applyAlias, isBareSpecifier, readSuffix, textModule, urlModule, workerModule } from './chunk-RTWJ5GFB.js';
|
|
4
4
|
import { info, colors, size, success, duration } from './chunk-7ZB5MAM6.js';
|
|
5
5
|
import { existsSync } from 'fs';
|
|
6
6
|
import { mkdir, writeFile, readFile, rm, cp, stat } from 'fs/promises';
|
|
@@ -630,20 +630,14 @@ async function buildProject(config) {
|
|
|
630
630
|
"process.env.NODE_ENV": JSON.stringify("production"),
|
|
631
631
|
...config.define
|
|
632
632
|
},
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
".ico": "file",
|
|
642
|
-
".woff": "file",
|
|
643
|
-
".woff2": "file",
|
|
644
|
-
".mp4": "file",
|
|
645
|
-
".webm": "file"
|
|
646
|
-
},
|
|
633
|
+
/*
|
|
634
|
+
* La meme liste que celle du serveur de developpement, et non une copie.
|
|
635
|
+
*
|
|
636
|
+
* Les deux ont diverge : le `.ttf` manquait aux deux, et rien ne l aurait
|
|
637
|
+
* dit tant qu un gabarit n en embarquerait pas. Une seule liste ne peut
|
|
638
|
+
* plus se desaccorder d elle-meme.
|
|
639
|
+
*/
|
|
640
|
+
loader: Object.fromEntries(ASSET_EXTENSIONS.map((suffixe) => [suffixe, "file"])),
|
|
647
641
|
plugins: [
|
|
648
642
|
// The order is that of resolution: the suffixes first, because they
|
|
649
643
|
// change the target; the aliases next; the pass over the sources last, it
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { sourcePlugin, depFileName, SOURCE_EXTENSIONS, listen, openBrowser, INTERNAL_PREFIX, applyAlias, readSuffix, hasExtension, STYLE_EXTENSIONS, ASSET_EXTENSIONS, isBareSpecifier, DEPS_PREFIX, urlToFile, wantsStylesheet, wrapAsset, isAssetRequest, wrapJson, transformHtmlWith, textModule, urlModule, workerModule, fileToUrl, wrapStyle, transformModule } from './chunk-
|
|
2
|
+
import { sourcePlugin, depFileName, SOURCE_EXTENSIONS, listen, openBrowser, INTERNAL_PREFIX, applyAlias, readSuffix, hasExtension, STYLE_EXTENSIONS, ASSET_EXTENSIONS, isBareSpecifier, MIME, DEPS_PREFIX, urlToFile, wantsStylesheet, wrapAsset, isAssetRequest, wrapJson, sendFile, transformHtmlWith, textModule, urlModule, workerModule, fileToUrl, wrapStyle, transformModule } from './chunk-RTWJ5GFB.js';
|
|
3
3
|
import { info, warn, success, duration, colors, error } from './chunk-7ZB5MAM6.js';
|
|
4
|
-
import { existsSync, readFileSync, statSync, watch
|
|
4
|
+
import { existsSync, readFileSync, statSync, watch } from 'fs';
|
|
5
5
|
import { readFile, rm, mkdir, writeFile } from 'fs/promises';
|
|
6
6
|
import { createServer, request } from 'http';
|
|
7
7
|
import { createServer as createServer$1 } from 'https';
|
|
@@ -804,28 +804,6 @@ async function bundleRefreshRuntime() {
|
|
|
804
804
|
|
|
805
805
|
// src/dev/server.ts
|
|
806
806
|
var UTILITIES_PATH = `${INTERNAL_PREFIX}utilities.css`;
|
|
807
|
-
var MIME = {
|
|
808
|
-
".html": "text/html; charset=utf-8",
|
|
809
|
-
".js": "text/javascript; charset=utf-8",
|
|
810
|
-
".mjs": "text/javascript; charset=utf-8",
|
|
811
|
-
".json": "application/json; charset=utf-8",
|
|
812
|
-
".css": "text/css; charset=utf-8",
|
|
813
|
-
".svg": "image/svg+xml",
|
|
814
|
-
".png": "image/png",
|
|
815
|
-
".jpg": "image/jpeg",
|
|
816
|
-
".jpeg": "image/jpeg",
|
|
817
|
-
".gif": "image/gif",
|
|
818
|
-
".webp": "image/webp",
|
|
819
|
-
".avif": "image/avif",
|
|
820
|
-
".ico": "image/x-icon",
|
|
821
|
-
".woff": "font/woff",
|
|
822
|
-
".woff2": "font/woff2",
|
|
823
|
-
".mp4": "video/mp4",
|
|
824
|
-
".webm": "video/webm",
|
|
825
|
-
".txt": "text/plain; charset=utf-8",
|
|
826
|
-
".md": "text/markdown; charset=utf-8",
|
|
827
|
-
".markdown": "text/markdown; charset=utf-8"
|
|
828
|
-
};
|
|
829
807
|
var SCRIPT_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
830
808
|
function cleanUrl(url) {
|
|
831
809
|
return (url.split("?")[0] ?? url).split("#")[0] ?? url;
|
|
@@ -944,12 +922,12 @@ async function startDevServer(config) {
|
|
|
944
922
|
}
|
|
945
923
|
node.code = hotPreamble(url2) + body;
|
|
946
924
|
}
|
|
947
|
-
send(response, node.code, MIME[".js"]);
|
|
925
|
+
send(response, node.code, MIME[".js"] ?? "text/javascript");
|
|
948
926
|
};
|
|
949
927
|
const serveStyle = async (response, file, direct) => {
|
|
950
928
|
const { css, files } = await assembleStylesheet(file, config.root);
|
|
951
929
|
if (direct) {
|
|
952
|
-
send(response, css, MIME[".css"]);
|
|
930
|
+
send(response, css, MIME[".css"] ?? "text/css");
|
|
953
931
|
return;
|
|
954
932
|
}
|
|
955
933
|
const url2 = fileToUrl(file, config.root);
|
|
@@ -965,22 +943,22 @@ async function startDevServer(config) {
|
|
|
965
943
|
dependency.selfAccepting = false;
|
|
966
944
|
}
|
|
967
945
|
}
|
|
968
|
-
send(response, node.code, MIME[".js"]);
|
|
946
|
+
send(response, node.code, MIME[".js"] ?? "text/javascript");
|
|
969
947
|
};
|
|
970
948
|
const serveSuffixed = async (response, file, path, suffix) => {
|
|
971
949
|
if (suffix === "raw") {
|
|
972
950
|
send(
|
|
973
951
|
response,
|
|
974
952
|
textModule(await readFile(file, "utf8")),
|
|
975
|
-
MIME[".js"]
|
|
953
|
+
MIME[".js"] ?? "text/javascript"
|
|
976
954
|
);
|
|
977
955
|
return;
|
|
978
956
|
}
|
|
979
957
|
if (suffix === "url") {
|
|
980
|
-
send(response, urlModule(path), MIME[".js"]);
|
|
958
|
+
send(response, urlModule(path), MIME[".js"] ?? "text/javascript");
|
|
981
959
|
return;
|
|
982
960
|
}
|
|
983
|
-
send(response, workerModule(path), MIME[".js"]);
|
|
961
|
+
send(response, workerModule(path), MIME[".js"] ?? "text/javascript");
|
|
984
962
|
};
|
|
985
963
|
const directoryIndex = (directory) => {
|
|
986
964
|
if (!existsSync(directory) || !statSync(directory).isDirectory()) return void 0;
|
|
@@ -990,10 +968,8 @@ async function startDevServer(config) {
|
|
|
990
968
|
}
|
|
991
969
|
return void 0;
|
|
992
970
|
};
|
|
993
|
-
const serveFile = (response, file) => {
|
|
994
|
-
|
|
995
|
-
response.writeHead(200, { "Content-Type": type, "Cache-Control": "no-cache" });
|
|
996
|
-
createReadStream(file).pipe(response);
|
|
971
|
+
const serveFile = (response, file, incoming) => {
|
|
972
|
+
sendFile(response, file, { range: incoming?.headers.range });
|
|
997
973
|
};
|
|
998
974
|
const serveHtml = async (response, route = "/") => {
|
|
999
975
|
const html = await readFile(indexFile, "utf8");
|
|
@@ -1002,7 +978,7 @@ async function startDevServer(config) {
|
|
|
1002
978
|
injectClient(html, utilities !== void 0),
|
|
1003
979
|
{ dev: true, route }
|
|
1004
980
|
);
|
|
1005
|
-
send(response, rendered, MIME[".html"]);
|
|
981
|
+
send(response, rendered, MIME[".html"] ?? "text/html");
|
|
1006
982
|
};
|
|
1007
983
|
const forward = (incoming, response, target) => {
|
|
1008
984
|
const url2 = new URL(incoming.url ?? "/", target);
|
|
@@ -1072,7 +1048,7 @@ async function startDevServer(config) {
|
|
|
1072
1048
|
const name = last.endsWith(".js") ? last : depFileName(last);
|
|
1073
1049
|
const file2 = join(deps.directory, name);
|
|
1074
1050
|
if (existsSync(file2)) {
|
|
1075
|
-
serveFile(response, file2);
|
|
1051
|
+
serveFile(response, file2, incoming);
|
|
1076
1052
|
return;
|
|
1077
1053
|
}
|
|
1078
1054
|
send(
|
|
@@ -1107,7 +1083,7 @@ async function startDevServer(config) {
|
|
|
1107
1083
|
if (url2.includes("?import")) {
|
|
1108
1084
|
send(response, wrapAsset(path), MIME[".js"] ?? "text/javascript");
|
|
1109
1085
|
} else {
|
|
1110
|
-
serveFile(response, file);
|
|
1086
|
+
serveFile(response, file, incoming);
|
|
1111
1087
|
}
|
|
1112
1088
|
return;
|
|
1113
1089
|
}
|
|
@@ -1120,21 +1096,21 @@ async function startDevServer(config) {
|
|
|
1120
1096
|
const json = await readFile(file, "utf8");
|
|
1121
1097
|
send(response, wrapJson(json), MIME[".js"] ?? "text/javascript");
|
|
1122
1098
|
} else {
|
|
1123
|
-
serveFile(response, file);
|
|
1099
|
+
serveFile(response, file, incoming);
|
|
1124
1100
|
}
|
|
1125
1101
|
return;
|
|
1126
1102
|
}
|
|
1127
|
-
serveFile(response, file);
|
|
1103
|
+
serveFile(response, file, incoming);
|
|
1128
1104
|
return;
|
|
1129
1105
|
}
|
|
1130
1106
|
const publicFile = join(config.publicDir, path.replace(/^\//, ""));
|
|
1131
1107
|
if (existsSync(publicFile) && statSync(publicFile).isFile()) {
|
|
1132
|
-
serveFile(response, publicFile);
|
|
1108
|
+
serveFile(response, publicFile, incoming);
|
|
1133
1109
|
return;
|
|
1134
1110
|
}
|
|
1135
1111
|
const index = directoryIndex(publicFile);
|
|
1136
1112
|
if (index !== void 0) {
|
|
1137
|
-
serveFile(response, index);
|
|
1113
|
+
serveFile(response, index, incoming);
|
|
1138
1114
|
return;
|
|
1139
1115
|
}
|
|
1140
1116
|
if (!extname(path) && !isAssetRequest(incoming.headers)) {
|
|
@@ -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 };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, statSync, readdirSync } from 'fs';
|
|
2
|
+
import { statSync, createReadStream, existsSync, readdirSync } from 'fs';
|
|
4
3
|
import { resolve, dirname, extname, relative, join } from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
5
|
import { readFile } from 'fs/promises';
|
|
6
6
|
import { build } from 'esbuild';
|
|
7
7
|
|
|
@@ -76,6 +76,105 @@ async function listen(server, port, host, attempts = ATTEMPTS) {
|
|
|
76
76
|
`[odoro] No free port between ${String(port)} and ${String(port + attempts - 1)}.`
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
|
+
var MIME = {
|
|
80
|
+
".html": "text/html; charset=utf-8",
|
|
81
|
+
".js": "text/javascript; charset=utf-8",
|
|
82
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
83
|
+
".css": "text/css; charset=utf-8",
|
|
84
|
+
".json": "application/json; charset=utf-8",
|
|
85
|
+
".map": "application/json; charset=utf-8",
|
|
86
|
+
".txt": "text/plain; charset=utf-8",
|
|
87
|
+
".md": "text/markdown; charset=utf-8",
|
|
88
|
+
".markdown": "text/markdown; charset=utf-8",
|
|
89
|
+
".xml": "application/xml; charset=utf-8",
|
|
90
|
+
".webmanifest": "application/manifest+json",
|
|
91
|
+
".svg": "image/svg+xml",
|
|
92
|
+
".png": "image/png",
|
|
93
|
+
".jpg": "image/jpeg",
|
|
94
|
+
".jpeg": "image/jpeg",
|
|
95
|
+
".gif": "image/gif",
|
|
96
|
+
".webp": "image/webp",
|
|
97
|
+
".avif": "image/avif",
|
|
98
|
+
".ico": "image/x-icon",
|
|
99
|
+
".woff": "font/woff",
|
|
100
|
+
".woff2": "font/woff2",
|
|
101
|
+
".ttf": "font/ttf",
|
|
102
|
+
".otf": "font/otf",
|
|
103
|
+
".mp4": "video/mp4",
|
|
104
|
+
".m4v": "video/mp4",
|
|
105
|
+
".webm": "video/webm",
|
|
106
|
+
".mov": "video/quicktime",
|
|
107
|
+
".ogv": "video/ogg",
|
|
108
|
+
".mp3": "audio/mpeg",
|
|
109
|
+
".m4a": "audio/mp4",
|
|
110
|
+
".wav": "audio/wav",
|
|
111
|
+
".oga": "audio/ogg",
|
|
112
|
+
".glb": "model/gltf-binary",
|
|
113
|
+
".gltf": "model/gltf+json",
|
|
114
|
+
".pdf": "application/pdf",
|
|
115
|
+
".wasm": "application/wasm"
|
|
116
|
+
};
|
|
117
|
+
function mimeOf(file) {
|
|
118
|
+
return MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
119
|
+
}
|
|
120
|
+
function parseRange(header, size) {
|
|
121
|
+
if (header === void 0) return null;
|
|
122
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
|
|
123
|
+
if (match === null) return null;
|
|
124
|
+
const [, rawStart, rawEnd] = match;
|
|
125
|
+
if (rawStart === "" && rawEnd === "") return null;
|
|
126
|
+
if (rawStart === "") {
|
|
127
|
+
const length = Number(rawEnd);
|
|
128
|
+
if (!Number.isFinite(length) || length <= 0) return "unsatisfiable";
|
|
129
|
+
return { start: Math.max(0, size - length), end: size - 1 };
|
|
130
|
+
}
|
|
131
|
+
const start = Number(rawStart);
|
|
132
|
+
if (!Number.isFinite(start) || start >= size) return "unsatisfiable";
|
|
133
|
+
const end = rawEnd === "" ? size - 1 : Math.min(Number(rawEnd), size - 1);
|
|
134
|
+
if (!Number.isFinite(end) || end < start) return "unsatisfiable";
|
|
135
|
+
return { start, end };
|
|
136
|
+
}
|
|
137
|
+
function sendFile(response, file, options = {}) {
|
|
138
|
+
const size = statSync(file).size;
|
|
139
|
+
const type = mimeOf(file);
|
|
140
|
+
const cacheControl = options.cacheControl ?? "no-cache";
|
|
141
|
+
const range = parseRange(options.range, size);
|
|
142
|
+
if (range === "unsatisfiable") {
|
|
143
|
+
response.writeHead(416, {
|
|
144
|
+
"Content-Range": `bytes */${String(size)}`,
|
|
145
|
+
"Accept-Ranges": "bytes",
|
|
146
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
147
|
+
});
|
|
148
|
+
response.end("Range not satisfiable");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (range === null) {
|
|
152
|
+
response.writeHead(200, {
|
|
153
|
+
"Content-Type": type,
|
|
154
|
+
"Content-Length": String(size),
|
|
155
|
+
"Accept-Ranges": "bytes",
|
|
156
|
+
"Cache-Control": cacheControl
|
|
157
|
+
});
|
|
158
|
+
if (options.headOnly === true) {
|
|
159
|
+
response.end();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
createReadStream(file).pipe(response);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
response.writeHead(206, {
|
|
166
|
+
"Content-Type": type,
|
|
167
|
+
"Content-Length": String(range.end - range.start + 1),
|
|
168
|
+
"Content-Range": `bytes ${String(range.start)}-${String(range.end)}/${String(size)}`,
|
|
169
|
+
"Accept-Ranges": "bytes",
|
|
170
|
+
"Cache-Control": cacheControl
|
|
171
|
+
});
|
|
172
|
+
if (options.headOnly === true) {
|
|
173
|
+
response.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
createReadStream(file, { start: range.start, end: range.end }).pipe(response);
|
|
177
|
+
}
|
|
79
178
|
function openBrowser(url) {
|
|
80
179
|
const [command, ...arguments_] = process.platform === "win32" ? (
|
|
81
180
|
// `start` is a built-in command: it goes through the interpreter. The
|
|
@@ -347,6 +446,7 @@ var INTERNAL_PREFIX = "/@odoro/";
|
|
|
347
446
|
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
348
447
|
var STYLE_EXTENSIONS = [".css"];
|
|
349
448
|
var ASSET_EXTENSIONS = [
|
|
449
|
+
// Images
|
|
350
450
|
".svg",
|
|
351
451
|
".png",
|
|
352
452
|
".jpg",
|
|
@@ -355,10 +455,35 @@ var ASSET_EXTENSIONS = [
|
|
|
355
455
|
".webp",
|
|
356
456
|
".avif",
|
|
357
457
|
".ico",
|
|
458
|
+
/*
|
|
459
|
+
* Polices — les quatre formats, pas seulement le moderne.
|
|
460
|
+
*
|
|
461
|
+
* Un gabarit qui embarque ses coupes statiques les livre souvent en `.ttf` :
|
|
462
|
+
* la version variable est en `woff2`, les instances ne le sont pas toujours.
|
|
463
|
+
* Sans le `.ttf` ici, la construction s arrete sur « No loader is configured
|
|
464
|
+
* for ".ttf" files » — ce qui au moins se voit, contrairement a la plupart
|
|
465
|
+
* des lacunes de ce fichier.
|
|
466
|
+
*/
|
|
358
467
|
".woff",
|
|
359
468
|
".woff2",
|
|
469
|
+
".ttf",
|
|
470
|
+
".otf",
|
|
471
|
+
".eot",
|
|
472
|
+
// Video et audio
|
|
360
473
|
".mp4",
|
|
361
|
-
".
|
|
474
|
+
".m4v",
|
|
475
|
+
".webm",
|
|
476
|
+
".mov",
|
|
477
|
+
".ogv",
|
|
478
|
+
".mp3",
|
|
479
|
+
".m4a",
|
|
480
|
+
".wav",
|
|
481
|
+
".oga",
|
|
482
|
+
// Ce qu une scene demande
|
|
483
|
+
".glb",
|
|
484
|
+
".gltf",
|
|
485
|
+
".hdr",
|
|
486
|
+
".pdf"
|
|
362
487
|
];
|
|
363
488
|
function depFileName(specifier) {
|
|
364
489
|
return `${specifier.replace(/^@/, "").split("/").join("_")}.js`;
|
|
@@ -601,4 +726,4 @@ function wrapAsset(url) {
|
|
|
601
726
|
`;
|
|
602
727
|
}
|
|
603
728
|
|
|
604
|
-
export { ASSET_EXTENSIONS, DEPS_PREFIX, INTERNAL_PREFIX, SOURCE_EXTENSIONS, STYLE_EXTENSIONS, applyAlias, depFileName, esbuildPluginsFrom, fileToUrl, hasExtension, hasGlob, isAssetRequest, isBareSpecifier, listen, openBrowser, readSuffix, sourcePlugin, textModule, transformGlob, transformHtmlWith, transformModule, urlModule, urlToFile, wantsStylesheet, workerModule, wrapAsset, wrapJson, wrapStyle };
|
|
729
|
+
export { ASSET_EXTENSIONS, DEPS_PREFIX, INTERNAL_PREFIX, MIME, SOURCE_EXTENSIONS, STYLE_EXTENSIONS, applyAlias, depFileName, esbuildPluginsFrom, fileToUrl, hasExtension, hasGlob, isAssetRequest, isBareSpecifier, listen, openBrowser, readSuffix, sendFile, sourcePlugin, textModule, transformGlob, transformHtmlWith, transformModule, urlModule, urlToFile, wantsStylesheet, workerModule, wrapAsset, wrapJson, wrapStyle };
|
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { isAssetRequest, listen, openBrowser } from './chunk-
|
|
2
|
+
import { isAssetRequest, sendFile, listen, openBrowser } from './chunk-RTWJ5GFB.js';
|
|
3
3
|
import { warn, success, info, colors } from './chunk-7ZB5MAM6.js';
|
|
4
|
-
import { existsSync, statSync
|
|
4
|
+
import { existsSync, statSync } from 'fs';
|
|
5
5
|
import { createServer } from 'http';
|
|
6
6
|
import { normalize, join, extname } from 'path';
|
|
7
7
|
|
|
8
|
-
var MIME = {
|
|
9
|
-
".html": "text/html; charset=utf-8",
|
|
10
|
-
".js": "text/javascript; charset=utf-8",
|
|
11
|
-
".css": "text/css; charset=utf-8",
|
|
12
|
-
".json": "application/json; charset=utf-8",
|
|
13
|
-
".svg": "image/svg+xml",
|
|
14
|
-
".png": "image/png",
|
|
15
|
-
".jpg": "image/jpeg",
|
|
16
|
-
".webp": "image/webp",
|
|
17
|
-
".ico": "image/x-icon",
|
|
18
|
-
".woff2": "font/woff2",
|
|
19
|
-
".map": "application/json; charset=utf-8",
|
|
20
|
-
".txt": "text/plain; charset=utf-8",
|
|
21
|
-
".md": "text/markdown; charset=utf-8",
|
|
22
|
-
".markdown": "text/markdown; charset=utf-8"
|
|
23
|
-
};
|
|
24
8
|
async function startPreviewServer(config, port = config.server.port + 1) {
|
|
25
9
|
if (!existsSync(config.outDir)) {
|
|
26
10
|
throw new Error(`[odoro] Nothing to preview: run "odoro build" first.`);
|
|
@@ -42,13 +26,12 @@ async function startPreviewServer(config, port = config.server.port + 1) {
|
|
|
42
26
|
response.end("Forbidden");
|
|
43
27
|
return;
|
|
44
28
|
}
|
|
45
|
-
const type = MIME[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
46
29
|
const immutable = relativePath.startsWith("assets/");
|
|
47
|
-
response
|
|
48
|
-
"
|
|
49
|
-
|
|
30
|
+
sendFile(response, file, {
|
|
31
|
+
cacheControl: immutable ? "public, max-age=31536000, immutable" : "no-cache",
|
|
32
|
+
range: incoming.headers.range,
|
|
33
|
+
headOnly: incoming.method === "HEAD"
|
|
50
34
|
});
|
|
51
|
-
createReadStream(file).pipe(response);
|
|
52
35
|
});
|
|
53
36
|
const { port: obtained, requested } = await listen(
|
|
54
37
|
server,
|
package/dist/cli.js
CHANGED
|
@@ -89,6 +89,7 @@ ${colors.bold("create options")}
|
|
|
89
89
|
|
|
90
90
|
${colors.bold("Database options")}
|
|
91
91
|
--env <name> Target environment (production, staging, preview-42)
|
|
92
|
+
--region <name> Where the database lives; asked when absent
|
|
92
93
|
--from <env> Environment to branch from
|
|
93
94
|
--name <name> Name of the branch
|
|
94
95
|
--api <url> Root of the API, instead of the default one
|
|
@@ -148,7 +149,7 @@ function rootFrom(flags, positional) {
|
|
|
148
149
|
async function run(argv) {
|
|
149
150
|
const { command, positional, flags } = parseArgs(argv);
|
|
150
151
|
if (flags["version"] === true) {
|
|
151
|
-
const manifest = await import('./package-
|
|
152
|
+
const manifest = await import('./package-3ZJOEXM4.js');
|
|
152
153
|
console.log(manifest.default.version);
|
|
153
154
|
return 0;
|
|
154
155
|
}
|
|
@@ -159,7 +160,7 @@ async function run(argv) {
|
|
|
159
160
|
switch (command) {
|
|
160
161
|
case "create":
|
|
161
162
|
case "new": {
|
|
162
|
-
const { createCommand } = await import('./create-
|
|
163
|
+
const { createCommand } = await import('./create-B2TLV6XI.js');
|
|
163
164
|
const options = {};
|
|
164
165
|
if (positional[0] !== void 0) options.name = positional[0];
|
|
165
166
|
if (typeof flags["template"] === "string") options.template = flags["template"];
|
|
@@ -173,7 +174,7 @@ async function run(argv) {
|
|
|
173
174
|
return createCommand(options);
|
|
174
175
|
}
|
|
175
176
|
case "dev": {
|
|
176
|
-
const { startDevServer } = await import('./server-
|
|
177
|
+
const { startDevServer } = await import('./server-ZB3P3EGS.js');
|
|
177
178
|
const config = await loadConfig(
|
|
178
179
|
rootFrom(flags, positional),
|
|
179
180
|
overridesFrom(flags),
|
|
@@ -183,14 +184,14 @@ async function run(argv) {
|
|
|
183
184
|
return new Promise(() => void 0);
|
|
184
185
|
}
|
|
185
186
|
case "build": {
|
|
186
|
-
const { buildProject, reportBuild } = await import('./build-
|
|
187
|
+
const { buildProject, reportBuild } = await import('./build-XJIP4GNT.js');
|
|
187
188
|
const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
|
|
188
189
|
const output = await buildProject(config);
|
|
189
190
|
reportBuild(output, process.cwd());
|
|
190
191
|
return 0;
|
|
191
192
|
}
|
|
192
193
|
case "preview": {
|
|
193
|
-
const { startPreviewServer } = await import('./preview-
|
|
194
|
+
const { startPreviewServer } = await import('./preview-3IRG7MHV.js');
|
|
194
195
|
const config = await loadConfig(rootFrom(flags, positional), overridesFrom(flags));
|
|
195
196
|
const port = numberFlag(flags, "port");
|
|
196
197
|
await startPreviewServer(config, port);
|
|
@@ -217,11 +218,12 @@ async function run(argv) {
|
|
|
217
218
|
case "db:status":
|
|
218
219
|
case "db:create":
|
|
219
220
|
case "db:branch": {
|
|
220
|
-
const db = await import('./commands-
|
|
221
|
+
const db = await import('./commands-UOWN3IK4.js');
|
|
221
222
|
const options = {
|
|
222
223
|
root: typeof flags["root"] === "string" ? flags["root"] : process.cwd(),
|
|
223
224
|
...typeof flags["api"] === "string" ? { apiUrl: flags["api"] } : {},
|
|
224
225
|
...typeof flags["env"] === "string" ? { env: flags["env"] } : {},
|
|
226
|
+
...typeof flags["region"] === "string" ? { region: flags["region"] } : {},
|
|
225
227
|
yes: flags["yes"] === true
|
|
226
228
|
};
|
|
227
229
|
if (command === "db:login") return db.loginCommand(options);
|
|
@@ -1,83 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { writeDatabaseUrl, assertEnvIgnored } from './chunk-
|
|
2
|
+
import { storeToken, chooseEnvironment, cancelled, chooseRegion, writeDatabaseUrl, assertEnvIgnored, SDK_PACKAGE, loadSdk, findToken } from './chunk-LIKVHWB4.js';
|
|
3
3
|
import { info, success, colors, warn, error } from './chunk-7ZB5MAM6.js';
|
|
4
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
5
|
|
|
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-cli/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: `This command needs ${SDK_PACKAGE}, which is not installed.
|
|
71
|
-
|
|
72
|
-
npm install --save-dev ${SDK_PACKAGE}
|
|
73
|
-
|
|
74
|
-
It does not ship with odoro: this binary is downloaded on every
|
|
75
|
-
project creation, and most projects do not use the platform.`
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/db/commands.ts
|
|
81
6
|
var DEFAULT_API_URL = "https://db.odoro.dev";
|
|
82
7
|
function idempotencyKey() {
|
|
83
8
|
return randomBytes(24).toString("hex");
|
|
@@ -142,20 +67,31 @@ async function statusCommand(options) {
|
|
|
142
67
|
async function createCommand(options) {
|
|
143
68
|
const connection = await connect(options);
|
|
144
69
|
if (connection === void 0) return 1;
|
|
145
|
-
const
|
|
70
|
+
const prompts = await import('@clack/prompts');
|
|
71
|
+
let environmentId = options.env;
|
|
146
72
|
if (environmentId === void 0) {
|
|
147
|
-
|
|
148
|
-
return 1;
|
|
73
|
+
const chosen = await chooseEnvironment(prompts, connection.client);
|
|
74
|
+
if (cancelled(chosen) || chosen === void 0) return 1;
|
|
75
|
+
environmentId = chosen.id;
|
|
76
|
+
}
|
|
77
|
+
let region = options.region;
|
|
78
|
+
if (region === void 0) {
|
|
79
|
+
const chosen = await chooseRegion(prompts, connection.client);
|
|
80
|
+
if (cancelled(chosen)) return 1;
|
|
81
|
+
region = chosen;
|
|
149
82
|
}
|
|
150
|
-
const prompts = await import('@clack/prompts');
|
|
151
83
|
const spinner = prompts.spinner();
|
|
152
|
-
spinner.start(
|
|
84
|
+
spinner.start(`Provisioning in ${region ?? "the default region"}`);
|
|
153
85
|
const abort = new AbortController();
|
|
154
86
|
const onInterrupt = () => abort.abort();
|
|
155
87
|
process.once("SIGINT", onInterrupt);
|
|
156
88
|
try {
|
|
157
89
|
const finished = await connection.client.databases.createAndWait(
|
|
158
|
-
{
|
|
90
|
+
{
|
|
91
|
+
idempotencyKey: idempotencyKey(),
|
|
92
|
+
environmentId,
|
|
93
|
+
...region === void 0 ? {} : { region }
|
|
94
|
+
},
|
|
159
95
|
{ signal: abort.signal }
|
|
160
96
|
);
|
|
161
97
|
spinner.stop("Database provisioned");
|
|
@@ -235,7 +171,8 @@ function describe(cause, sdk) {
|
|
|
235
171
|
var DB_HELP = [
|
|
236
172
|
" db:login Record a platform token",
|
|
237
173
|
" db:status List the databases of the project",
|
|
238
|
-
" db:create
|
|
174
|
+
" db:create Provision a database and write .env",
|
|
175
|
+
" (--env and --region skip the questions)",
|
|
239
176
|
" db:branch --from <e> --name <n>",
|
|
240
177
|
" Create a per-branch preview",
|
|
241
178
|
` (requires ${SDK_PACKAGE})`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { writeDatabaseUrl, assertEnvIgnored, PROVIDER_PENDING, checkDatabaseUrl } from './chunk-
|
|
2
|
+
import { writeDatabaseUrl, assertEnvIgnored, PROVIDER_PENDING, checkDatabaseUrl, loadSdk, ensureToken, DEFAULT_API_URL, cancelled, chooseEnvironment, chooseRegion, provision } from './chunk-LIKVHWB4.js';
|
|
3
3
|
import { execSync, spawn } from 'child_process';
|
|
4
4
|
import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
|
|
5
5
|
import { resolve, basename, dirname, join } from 'path';
|
|
@@ -146,10 +146,10 @@ function availableTemplates(root = templatesRoot()) {
|
|
|
146
146
|
var FAMILY_VERSIONS = {
|
|
147
147
|
"@odoro-cli/engine": "2.0.0",
|
|
148
148
|
"@odoro-cli/icons": "2.0.0",
|
|
149
|
-
"@odoro-cli/libs": "2.
|
|
149
|
+
"@odoro-cli/libs": "2.1.0",
|
|
150
150
|
"@odoro-cli/server": "1.1.0",
|
|
151
|
-
"create-odoro": "1.0.
|
|
152
|
-
"odoro": "2.
|
|
151
|
+
"create-odoro": "1.0.11",
|
|
152
|
+
"odoro": "2.1.0"
|
|
153
153
|
};
|
|
154
154
|
|
|
155
155
|
// src/scaffold/scaffold.ts
|
|
@@ -279,29 +279,63 @@ var TEMPLATE_LABELS = {
|
|
|
279
279
|
"react-ts": "Single-page app \u2014 React, TypeScript, Odoro router and motion",
|
|
280
280
|
"react-ts-server": "Client and server \u2014 the same app, plus a modular @odoro-cli/server base and a Dockerfile"
|
|
281
281
|
};
|
|
282
|
-
async function
|
|
282
|
+
async function provisionOnPlatform(projectName) {
|
|
283
|
+
const load = await loadSdk();
|
|
284
|
+
if (!load.ok) {
|
|
285
|
+
prompts.log.warn(load.reason);
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
const token = await ensureToken(prompts, DEFAULT_API_URL);
|
|
289
|
+
if (cancelled(token)) return void 0;
|
|
290
|
+
const client = load.sdk.createClient({ baseUrl: DEFAULT_API_URL, token });
|
|
291
|
+
const environment = await chooseEnvironment(prompts, client);
|
|
292
|
+
if (cancelled(environment) || environment === void 0) return void 0;
|
|
293
|
+
const region = await chooseRegion(prompts, client);
|
|
294
|
+
if (cancelled(region)) return void 0;
|
|
295
|
+
const spinner2 = prompts.spinner();
|
|
296
|
+
spinner2.start(`Provisioning in ${region ?? "the default region"}`);
|
|
297
|
+
const result = await provision({
|
|
298
|
+
client,
|
|
299
|
+
environmentId: environment.id,
|
|
300
|
+
...region === void 0 ? {} : { region },
|
|
301
|
+
name: projectName
|
|
302
|
+
});
|
|
303
|
+
if (!result.ok) {
|
|
304
|
+
spinner2.stop("Provisioning did not finish");
|
|
305
|
+
prompts.log.warn(result.reason);
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
spinner2.stop(`Database ready in ${environment.label}`);
|
|
309
|
+
return {
|
|
310
|
+
choice: "provider",
|
|
311
|
+
url: result.connectionString,
|
|
312
|
+
note: `Database provisioned on ${environment.label}. DATABASE_URL is in .env \u2014 do not commit it.`
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async function askDatabase(projectName) {
|
|
283
316
|
const choice = ensure(
|
|
284
317
|
await prompts.select({
|
|
285
318
|
message: "Database",
|
|
286
|
-
initialValue: "
|
|
319
|
+
initialValue: "provider",
|
|
287
320
|
options: [
|
|
288
321
|
{
|
|
289
322
|
value: "provider",
|
|
290
323
|
label: "Odoro provider",
|
|
291
|
-
hint: "
|
|
324
|
+
hint: "provisioned now, PostgreSQL, region of your choosing"
|
|
292
325
|
},
|
|
293
326
|
{
|
|
294
327
|
value: "url",
|
|
295
328
|
label: "Existing PostgreSQL URL",
|
|
296
|
-
hint: "
|
|
329
|
+
hint: "bring your own"
|
|
297
330
|
},
|
|
298
331
|
{ value: "later", label: "Set up later", hint: ".env.example only" }
|
|
299
332
|
]
|
|
300
333
|
})
|
|
301
334
|
);
|
|
302
335
|
if (choice === "provider") {
|
|
303
|
-
|
|
304
|
-
|
|
336
|
+
const provisioned = await provisionOnPlatform(projectName);
|
|
337
|
+
if (provisioned !== void 0) return provisioned;
|
|
338
|
+
return { choice, note: PROVIDER_PENDING };
|
|
305
339
|
}
|
|
306
340
|
if (choice === "later") {
|
|
307
341
|
return {
|
|
@@ -485,7 +519,7 @@ async function createCommand(options) {
|
|
|
485
519
|
prompts.cancel(`Unknown package manager: "${manager}".`);
|
|
486
520
|
return 1;
|
|
487
521
|
}
|
|
488
|
-
const database = template === "react-ts-server" && options.yes !== true ? await askDatabase() : void 0;
|
|
522
|
+
const database = template === "react-ts-server" && options.yes !== true ? await askDatabase(packageName) : void 0;
|
|
489
523
|
const withGit = options.git ?? (options.yes === true ? true : ensure(await prompts.confirm({ message: "Initialize a git repository?" })));
|
|
490
524
|
const withInstall = options.install ?? (options.yes === true ? true : ensure(
|
|
491
525
|
await prompts.confirm({
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { startPreviewServer } from './chunk-
|
|
2
|
+
export { startPreviewServer } from './chunk-TONYIKCN.js';
|
|
3
3
|
export { clientEnv, defineConfig, loadConfig, loadEnv } from './chunk-QOVBLN7A.js';
|
|
4
4
|
import './chunk-T6RLHSCW.js';
|
|
5
|
-
export { buildManifest, buildProject, chunksFor, prerender, reportBuild } from './chunk-
|
|
6
|
-
export { ModuleGraph, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-
|
|
7
|
-
export { depFileName, hasGlob, transformGlob } from './chunk-
|
|
5
|
+
export { buildManifest, buildProject, chunksFor, prerender, reportBuild } from './chunk-DF67LADH.js';
|
|
6
|
+
export { ModuleGraph, detectSelfAccepting, optimizeDeps, scanDependencies, startDevServer } from './chunk-HS7NGZLM.js';
|
|
7
|
+
export { depFileName, hasGlob, transformGlob } from './chunk-RTWJ5GFB.js';
|
|
8
8
|
import './chunk-7ZB5MAM6.js';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "odoro",
|
|
5
|
-
version: "2.
|
|
5
|
+
version: "2.1.0",
|
|
6
6
|
type: "module",
|
|
7
7
|
license: "UNLICENSED",
|
|
8
8
|
author: "BouBouw",
|
|
@@ -47,6 +47,7 @@ var package_default = {
|
|
|
47
47
|
dependencies: {
|
|
48
48
|
"@babel/core": "^7.29.7",
|
|
49
49
|
"@clack/prompts": "^0.11.0",
|
|
50
|
+
"@odoro-cli/cloud-sdk": "^0.1.0",
|
|
50
51
|
esbuild: "^0.28.2",
|
|
51
52
|
picocolors: "^1.1.1",
|
|
52
53
|
"react-refresh": "^0.18.0"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { extractEntries, injectClient, startDevServer } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { extractEntries, injectClient, startDevServer } from './chunk-HS7NGZLM.js';
|
|
3
|
+
import './chunk-RTWJ5GFB.js';
|
|
4
4
|
import './chunk-7ZB5MAM6.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odoro",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "BouBouw",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@babel/core": "^7.29.7",
|
|
39
39
|
"@clack/prompts": "^0.11.0",
|
|
40
|
+
"@odoro-cli/cloud-sdk": "^0.1.0",
|
|
40
41
|
"esbuild": "^0.28.2",
|
|
41
42
|
"picocolors": "^1.1.1",
|
|
42
43
|
"react-refresh": "^0.18.0"
|
package/dist/build-LZAUDJST.js
DELETED
package/dist/chunk-P3DJITWE.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile, writeFile, appendFile } from 'fs/promises';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
|
|
6
|
-
function checkDatabaseUrl(value) {
|
|
7
|
-
const url = value.trim();
|
|
8
|
-
if (url.length === 0) return "The URL cannot be empty.";
|
|
9
|
-
if (!/^postgres(ql)?:\/\//i.test(url)) {
|
|
10
|
-
return "A PostgreSQL URL starts with postgres:// or postgresql://";
|
|
11
|
-
}
|
|
12
|
-
let parsed;
|
|
13
|
-
try {
|
|
14
|
-
parsed = new URL(url);
|
|
15
|
-
} catch {
|
|
16
|
-
return "This string is not a valid URL.";
|
|
17
|
-
}
|
|
18
|
-
if (parsed.hostname.length === 0) return "No host in this URL.";
|
|
19
|
-
if (parsed.pathname.replace(/^\//, "").length === 0) {
|
|
20
|
-
return "No database name: add it after the port, for example /my_project";
|
|
21
|
-
}
|
|
22
|
-
if (!/^(localhost|127\.0\.0\.1|\[::1\])$/i.test(parsed.hostname) && parsed.searchParams.get("sslmode") === "disable") {
|
|
23
|
-
return "sslmode=disable on a remote database: the traffic would go in the clear.";
|
|
24
|
-
}
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
async function writeDatabaseUrl(target, url) {
|
|
28
|
-
const path = join(target, ".env");
|
|
29
|
-
const line = `DATABASE_URL=${url}
|
|
30
|
-
`;
|
|
31
|
-
if (!existsSync(path)) {
|
|
32
|
-
const example = join(target, ".env.example");
|
|
33
|
-
const base = existsSync(example) ? await readFile(example, "utf8") : "";
|
|
34
|
-
await writeFile(path, base.replace(/^DATABASE_URL=.*$/m, line.trimEnd()), "utf8");
|
|
35
|
-
if (!base.includes("DATABASE_URL=")) await appendFile(path, line, "utf8");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const current = await readFile(path, "utf8");
|
|
39
|
-
await writeFile(
|
|
40
|
-
path,
|
|
41
|
-
current.includes("DATABASE_URL=") ? current.replace(/^DATABASE_URL=.*$/m, line.trimEnd()) : current + line,
|
|
42
|
-
"utf8"
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
async function assertEnvIgnored(target) {
|
|
46
|
-
const path = join(target, ".gitignore");
|
|
47
|
-
if (!existsSync(path)) {
|
|
48
|
-
return "No .gitignore: the .env file risks being versioned.";
|
|
49
|
-
}
|
|
50
|
-
const content = await readFile(path, "utf8");
|
|
51
|
-
const ignored = content.split("\n").map((line) => line.trim()).some((line) => line === ".env" || line === ".env*" || line === "*.env");
|
|
52
|
-
return ignored ? void 0 : ".env does not appear in the .gitignore: your credentials risk being versioned.";
|
|
53
|
-
}
|
|
54
|
-
var PROVIDER_PENDING = [
|
|
55
|
-
"Provisioning goes through @odoro-cli/cloud-sdk, installed separately:",
|
|
56
|
-
"",
|
|
57
|
-
" npm install --save-dev @odoro-cli/cloud-sdk",
|
|
58
|
-
" odoro db:login",
|
|
59
|
-
" odoro db:create --env production",
|
|
60
|
-
"",
|
|
61
|
-
"It does not ship with odoro: this binary is downloaded on every project creation, and most projects use their own database."
|
|
62
|
-
].join("\n");
|
|
63
|
-
|
|
64
|
-
export { PROVIDER_PENDING, assertEnvIgnored, checkDatabaseUrl, writeDatabaseUrl };
|
package/dist/preview-P4WY5GEY.js
DELETED