gencow 0.1.156 → 0.1.157
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/lib/deploy-command.mjs +2 -1
- package/lib/deploy-runtime.mjs +31 -1
- package/lib/readme-codegen.mjs +4 -4
- package/lib/static-command.mjs +80 -6
- package/package.json +1 -1
- package/server/index.js +167 -23
- package/server/index.js.map +4 -4
- package/templateFeature/ai/ai-facade.ts +2 -2
- package/templates/SECURITY.md +3 -3
- package/templates/ai-chat/README.md +2 -2
- package/templates/ai-chat/prompt.md +2 -2
package/lib/deploy-command.mjs
CHANGED
|
@@ -13,6 +13,7 @@ export function renderDeployHelp() {
|
|
|
13
13
|
log(` ${BOLD}Options:${RESET}`);
|
|
14
14
|
log(` ${DIM}--static [dir]${RESET} Deploy backend first, then static files`);
|
|
15
15
|
log(` ${DIM}--prod${RESET} Deploy to production (Pro+ only)`);
|
|
16
|
+
log(` ${DIM}--region <slug>${RESET} First production app placement region`);
|
|
16
17
|
log(` ${DIM}--rollback${RESET} Rollback to previous deployment`);
|
|
17
18
|
log(` ${DIM}--force, -f${RESET} Skip optional dependency scan`);
|
|
18
19
|
log(` ${DIM}--app, -a${RESET} Target specific app\n`);
|
|
@@ -21,7 +22,7 @@ export function renderDeployHelp() {
|
|
|
21
22
|
log(` gencow deploy status`);
|
|
22
23
|
log(` gencow deploy logs -n 100`);
|
|
23
24
|
log(` gencow deploy --static dist/`);
|
|
24
|
-
log(` gencow deploy --prod`);
|
|
25
|
+
log(` gencow deploy --prod --region kr`);
|
|
25
26
|
log(` gencow deploy --rollback\n`);
|
|
26
27
|
}
|
|
27
28
|
|
package/lib/deploy-runtime.mjs
CHANGED
|
@@ -15,6 +15,7 @@ export function parseDeployArgs(deployArgs) {
|
|
|
15
15
|
envTarget: "dev",
|
|
16
16
|
forceDeploy: false,
|
|
17
17
|
isRollback: false,
|
|
18
|
+
region: null,
|
|
18
19
|
staticDeploy: false,
|
|
19
20
|
staticDirArg: null,
|
|
20
21
|
unknownArg: null,
|
|
@@ -26,7 +27,21 @@ export function parseDeployArgs(deployArgs) {
|
|
|
26
27
|
else if (arg === "--force" || arg === "-f") parsed.forceDeploy = true;
|
|
27
28
|
else if (arg === "--rollback") parsed.isRollback = true;
|
|
28
29
|
else if (arg === "--app" || arg === "-a") parsed.appId = deployArgs[++index];
|
|
29
|
-
else if (arg === "--
|
|
30
|
+
else if (arg === "--region") {
|
|
31
|
+
const value = deployArgs[index + 1];
|
|
32
|
+
if (!value || value.startsWith("-")) {
|
|
33
|
+
parsed.unknownArg = arg;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
parsed.region = value;
|
|
37
|
+
index += 1;
|
|
38
|
+
} else if (arg.startsWith("--region=")) {
|
|
39
|
+
parsed.region = arg.slice("--region=".length) || null;
|
|
40
|
+
if (!parsed.region) {
|
|
41
|
+
parsed.unknownArg = arg;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
} else if (arg === "--static") {
|
|
30
45
|
parsed.staticDeploy = true;
|
|
31
46
|
const maybeDir = deployArgs[index + 1];
|
|
32
47
|
if (maybeDir && !maybeDir.startsWith("-")) {
|
|
@@ -87,6 +102,7 @@ function showUnknownDeployArgument(arg, { errorImpl = error, infoImpl = info, lo
|
|
|
87
102
|
infoImpl("Usage: gencow deploy [options]");
|
|
88
103
|
infoImpl(" gencow deploy Dev backend deploy");
|
|
89
104
|
infoImpl(" gencow deploy --prod Production backend deploy (Pro+)");
|
|
105
|
+
infoImpl(" gencow deploy --prod --region kr First production app placement");
|
|
90
106
|
infoImpl(" gencow deploy --rollback Rollback to previous version");
|
|
91
107
|
infoImpl(" gencow deploy logs View server logs");
|
|
92
108
|
infoImpl(" gencow deploy status Check app status");
|
|
@@ -134,6 +150,7 @@ async function ensureProductionDeployTarget({
|
|
|
134
150
|
writeFileSyncImpl = writeFileSync,
|
|
135
151
|
exitImpl = (code) => process.exit(code),
|
|
136
152
|
prodAppId = null,
|
|
153
|
+
region = null,
|
|
137
154
|
}) {
|
|
138
155
|
if (!appId || prodAppId) {
|
|
139
156
|
return prodAppId ?? appId;
|
|
@@ -154,6 +171,7 @@ async function ensureProductionDeployTarget({
|
|
|
154
171
|
const createProdRes = await platformFetchImpl(creds, `/platform/apps/${appId}/create-prod`, {
|
|
155
172
|
method: "POST",
|
|
156
173
|
headers: { "Content-Type": "application/json" },
|
|
174
|
+
body: JSON.stringify(region ? { region } : {}),
|
|
157
175
|
});
|
|
158
176
|
|
|
159
177
|
if (!createProdRes.ok) {
|
|
@@ -187,9 +205,17 @@ async function ensureProductionDeployTarget({
|
|
|
187
205
|
|
|
188
206
|
if (createProdData.alreadyExists) {
|
|
189
207
|
infoImpl(`Prod app verified: ${nextProdAppId}`);
|
|
208
|
+
if (region && createProdData.region && createProdData.region !== region) {
|
|
209
|
+
infoImpl(
|
|
210
|
+
`Region unchanged: existing prod app is in ${createProdData.region}; --region only applies to first prod creation.`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
190
213
|
} else {
|
|
191
214
|
successImpl(`Prod app created: ${nextProdAppId}`);
|
|
192
215
|
infoImpl(`URL: ${createProdData.url}`);
|
|
216
|
+
if (createProdData.region) {
|
|
217
|
+
infoImpl(`Region: ${createProdData.region} (${createProdData.regionSource || "selected"})`);
|
|
218
|
+
}
|
|
193
219
|
await new Promise((resolveDelay) => setTimeoutImpl(resolveDelay, 3000));
|
|
194
220
|
}
|
|
195
221
|
|
|
@@ -391,10 +417,14 @@ export function createDeployCommand({
|
|
|
391
417
|
writeFileSyncImpl,
|
|
392
418
|
exitImpl,
|
|
393
419
|
prodAppId,
|
|
420
|
+
region: parsed.region,
|
|
394
421
|
});
|
|
395
422
|
if (!prodTarget) return;
|
|
396
423
|
appId = prodTarget;
|
|
397
424
|
infoImpl(`Deploy target: ${CYAN}${appId}${RESET} (production)`);
|
|
425
|
+
if (prodAppId && parsed.region) {
|
|
426
|
+
infoImpl("Region option only applies when the production app is first created; no app move was requested.");
|
|
427
|
+
}
|
|
398
428
|
}
|
|
399
429
|
|
|
400
430
|
if (parsed.staticDeploy) {
|
package/lib/readme-codegen.mjs
CHANGED
|
@@ -298,7 +298,7 @@ export function buildAiPrompt(apiObj, namespaces) {
|
|
|
298
298
|
md += `- Existing generated components can use ai.chat(), ai.vision.extractText(), ai.image.generate(), and ai.generateObject() through the Gencow facade.\n`;
|
|
299
299
|
md += `- Do not import @ai-sdk/openai/createOpenAI directly, call provider APIs directly, or create wrapper files such as openai-direct.ts.\n`;
|
|
300
300
|
md += `- Do not log image base64, prompts, provider tokens, or raw private document snippets.\n`;
|
|
301
|
-
md += `- Recommended models: gpt-5.4-mini for most apps, gpt-5.5 for highest quality, gpt-5.4-nano for high-volume extraction.\n`;
|
|
301
|
+
md += `- Recommended chat models: gpt-5.4-mini for most apps, gpt-5.5 for Pro/Scale highest quality, gpt-5.4-nano for high-volume extraction.\n`;
|
|
302
302
|
md += `- Use ai.generateObject() with a Zod schema for structured output. Do not use ai.chat() + JSON.parse().\n`;
|
|
303
303
|
md += `- Install AI with gencow add AI, RAG with gencow add RAG, Memory with gencow add Memory, and durable agents with gencow add Agent.\n`;
|
|
304
304
|
md += `\`\`\`\n\n`;
|
|
@@ -307,7 +307,7 @@ export function buildAiPrompt(apiObj, namespaces) {
|
|
|
307
307
|
|
|
308
308
|
export function buildAiUsageSection() {
|
|
309
309
|
let md = `---\n\n## AI Usage\n\n`;
|
|
310
|
-
md += `Gencow supports
|
|
310
|
+
md += `Gencow supports a focused active chat catalog: gpt-5.5 on Pro/Scale, gpt-5.4-mini by default, gpt-5.4-nano for high volume, and temporary legacy gpt-4o-mini compatibility. Image and embedding models are also controlled by the platform \`model_pricing\` catalog.\n`;
|
|
311
311
|
md += `Local development uses \`OPENAI_API_KEY\`; cloud deployments automatically use the Gencow AI proxy and service credits.\n\n`;
|
|
312
312
|
md += `\`\`\`typescript\n`;
|
|
313
313
|
md += `import { generateImage, generateText } from "ai";\n`;
|
|
@@ -341,10 +341,10 @@ export function buildAiUsageSection() {
|
|
|
341
341
|
md += `\`\`\`\n\n`;
|
|
342
342
|
md += `| Purpose | Recommended model |\n`;
|
|
343
343
|
md += `|---|---|\n`;
|
|
344
|
-
md += `| Highest quality / complex reasoning | \`gpt-5.5\` |\n`;
|
|
344
|
+
md += `| Highest quality / complex reasoning | \`gpt-5.5\` (Pro/Scale only) |\n`;
|
|
345
345
|
md += `| Most production chat, coding, and agent tasks | \`gpt-5.4-mini\` |\n`;
|
|
346
346
|
md += `| High-volume classification and extraction | \`gpt-5.4-nano\` |\n`;
|
|
347
|
-
md += `|
|
|
347
|
+
md += `| Temporary legacy compatibility path | \`gpt-4o-mini\` |\n`;
|
|
348
348
|
md += `| Image-to-text / OCR-style extraction | \`gpt-5.4-mini\` or \`gpt-4o-mini\` |\n`;
|
|
349
349
|
md += `| Image generation | \`gpt-image-2\` or \`gpt-image-1-mini\` |\n\n`;
|
|
350
350
|
md += `Do not wire \`@ai-sdk/openai\` or provider keys directly. Use \`createGencowAI()\` or the \`ai.*\` facade from \`./ai\`.\n\n`;
|
package/lib/static-command.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
2
|
import { resolve } from "path";
|
|
3
3
|
|
|
4
4
|
import { readProjectDisplayName } from "./cli-project-runtime.mjs";
|
|
5
|
-
import { BOLD, CYAN, DIM, RESET, error, info, log } from "./output.mjs";
|
|
5
|
+
import { BOLD, CYAN, DIM, RESET, error, info, log, success } from "./output.mjs";
|
|
6
|
+
import { platformFetch } from "./platform-client.mjs";
|
|
6
7
|
import { detectStaticDeployBackend } from "./static-deploy-command.mjs";
|
|
7
8
|
|
|
8
9
|
export function parseStaticArgs(staticArgs) {
|
|
@@ -10,6 +11,7 @@ export function parseStaticArgs(staticArgs) {
|
|
|
10
11
|
appId: null,
|
|
11
12
|
forceDeploy: false,
|
|
12
13
|
isProd: false,
|
|
14
|
+
region: null,
|
|
13
15
|
staticDir: null,
|
|
14
16
|
};
|
|
15
17
|
|
|
@@ -17,6 +19,8 @@ export function parseStaticArgs(staticArgs) {
|
|
|
17
19
|
const arg = staticArgs[index];
|
|
18
20
|
if (arg === "--force" || arg === "-f") parsed.forceDeploy = true;
|
|
19
21
|
else if (arg === "--prod") parsed.isProd = true;
|
|
22
|
+
else if (arg === "--region") parsed.region = staticArgs[++index] || null;
|
|
23
|
+
else if (arg.startsWith("--region=")) parsed.region = arg.slice("--region=".length) || null;
|
|
20
24
|
else if (arg === "--app" || arg === "-a") parsed.appId = staticArgs[++index];
|
|
21
25
|
else if (!arg.startsWith("-")) parsed.staticDir = arg;
|
|
22
26
|
}
|
|
@@ -62,6 +66,57 @@ export function readStaticProjectContext(options = {}) {
|
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
async function ensureStaticProductionTarget({
|
|
70
|
+
appId,
|
|
71
|
+
creds,
|
|
72
|
+
gencowJsonPath,
|
|
73
|
+
platformFetchImpl = platformFetch,
|
|
74
|
+
readFileSyncImpl = readFileSync,
|
|
75
|
+
region = null,
|
|
76
|
+
setTimeoutImpl = setTimeout,
|
|
77
|
+
successImpl = success,
|
|
78
|
+
errorImpl = error,
|
|
79
|
+
infoImpl = info,
|
|
80
|
+
writeFileSyncImpl = writeFileSync,
|
|
81
|
+
}) {
|
|
82
|
+
if (!appId) {
|
|
83
|
+
errorImpl("App not found. Run from the project root with gencow.json.");
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
infoImpl("Creating prod app...");
|
|
88
|
+
const createProdRes = await platformFetchImpl(creds, `/platform/apps/${appId}/create-prod`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify(region ? { region } : {}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!createProdRes.ok) {
|
|
95
|
+
const errData = await createProdRes.json().catch(() => ({}));
|
|
96
|
+
errorImpl(`Prod app creation failed: ${errData.error || createProdRes.statusText}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const createProdData = await createProdRes.json();
|
|
101
|
+
const nextProdAppId = createProdData.prodApp;
|
|
102
|
+
const gencowJson = existsSync(gencowJsonPath) ? JSON.parse(readFileSyncImpl(gencowJsonPath, "utf8")) : {};
|
|
103
|
+
gencowJson.prodApp = nextProdAppId;
|
|
104
|
+
writeFileSyncImpl(gencowJsonPath, JSON.stringify(gencowJson, null, 2));
|
|
105
|
+
|
|
106
|
+
if (createProdData.alreadyExists) {
|
|
107
|
+
infoImpl(`Prod app verified: ${nextProdAppId}`);
|
|
108
|
+
} else {
|
|
109
|
+
successImpl(`Prod app created: ${nextProdAppId}`);
|
|
110
|
+
if (createProdData.url) infoImpl(`URL: ${createProdData.url}`);
|
|
111
|
+
if (createProdData.region) {
|
|
112
|
+
infoImpl(`Region: ${createProdData.region} (${createProdData.regionSource || "selected"})`);
|
|
113
|
+
}
|
|
114
|
+
await new Promise((resolveDelay) => setTimeoutImpl(resolveDelay, 3000));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return nextProdAppId;
|
|
118
|
+
}
|
|
119
|
+
|
|
65
120
|
export function createStaticCommand(options = {}) {
|
|
66
121
|
const {
|
|
67
122
|
BOLDValue = BOLD,
|
|
@@ -72,9 +127,14 @@ export function createStaticCommand(options = {}) {
|
|
|
72
127
|
errorImpl = error,
|
|
73
128
|
infoImpl = info,
|
|
74
129
|
logImpl = log,
|
|
130
|
+
platformFetchImpl = platformFetch,
|
|
131
|
+
readFileSyncImpl = readFileSync,
|
|
75
132
|
readStaticProjectContextImpl = readStaticProjectContext,
|
|
76
133
|
requireCredsImpl,
|
|
77
134
|
runStaticDeployRuntimeImpl,
|
|
135
|
+
setTimeoutImpl = setTimeout,
|
|
136
|
+
successImpl = success,
|
|
137
|
+
writeFileSyncImpl = writeFileSync,
|
|
78
138
|
} = options;
|
|
79
139
|
|
|
80
140
|
return async function runStaticCommand(...staticArgs) {
|
|
@@ -87,6 +147,7 @@ export function createStaticCommand(options = {}) {
|
|
|
87
147
|
);
|
|
88
148
|
logImpl(` ${BOLDValue}Options:${RESETValue}`);
|
|
89
149
|
logImpl(` ${DIMValue}--prod${RESETValue} Deploy to production app`);
|
|
150
|
+
logImpl(` ${DIMValue}--region <slug>${RESETValue} First production app placement region`);
|
|
90
151
|
logImpl(` ${DIMValue}--force, -f${RESETValue} Skip optional dependency scan`);
|
|
91
152
|
logImpl(` ${DIMValue}--app, -a${RESETValue} Target specific app\n`);
|
|
92
153
|
logImpl(` ${BOLDValue}Examples:${RESETValue}`);
|
|
@@ -104,13 +165,26 @@ export function createStaticCommand(options = {}) {
|
|
|
104
165
|
cwd: cwdImpl(),
|
|
105
166
|
explicitAppId: parsed.appId,
|
|
106
167
|
});
|
|
107
|
-
|
|
168
|
+
let deployTarget = parsed.isProd ? prodAppId || appId : appId;
|
|
108
169
|
const envTarget = parsed.isProd ? "prod" : "dev";
|
|
109
170
|
|
|
110
171
|
if (parsed.isProd && !prodAppId) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
172
|
+
deployTarget = await ensureStaticProductionTarget({
|
|
173
|
+
appId,
|
|
174
|
+
creds,
|
|
175
|
+
gencowJsonPath,
|
|
176
|
+
platformFetchImpl,
|
|
177
|
+
readFileSyncImpl,
|
|
178
|
+
region: parsed.region,
|
|
179
|
+
setTimeoutImpl,
|
|
180
|
+
successImpl,
|
|
181
|
+
errorImpl,
|
|
182
|
+
infoImpl,
|
|
183
|
+
writeFileSyncImpl,
|
|
184
|
+
});
|
|
185
|
+
if (!deployTarget) return;
|
|
186
|
+
} else if (parsed.isProd && parsed.region) {
|
|
187
|
+
infoImpl("Region option only applies when the production app is first created; no app move was requested.");
|
|
114
188
|
}
|
|
115
189
|
|
|
116
190
|
return runStaticDeployRuntimeImpl({
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -63151,7 +63151,7 @@ var require_html_writer = __commonJS({
|
|
|
63151
63151
|
}
|
|
63152
63152
|
function generateAttributeString(attributes) {
|
|
63153
63153
|
return _3.map(attributes, function(value, key) {
|
|
63154
|
-
return " " + key + '="' +
|
|
63154
|
+
return " " + key + '="' + escapeHtmlAttribute2(value) + '"';
|
|
63155
63155
|
}).join("");
|
|
63156
63156
|
}
|
|
63157
63157
|
function text2(value) {
|
|
@@ -63175,7 +63175,7 @@ var require_html_writer = __commonJS({
|
|
|
63175
63175
|
function escapeHtmlText(value) {
|
|
63176
63176
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
63177
63177
|
}
|
|
63178
|
-
function
|
|
63178
|
+
function escapeHtmlAttribute2(value) {
|
|
63179
63179
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
63180
63180
|
}
|
|
63181
63181
|
}
|
|
@@ -93267,7 +93267,9 @@ async function ensurePlatformSchemaUpgrade(params) {
|
|
|
93267
93267
|
await params.rawSql(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS status_reason TEXT`);
|
|
93268
93268
|
await params.rawSql(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS status_message TEXT`);
|
|
93269
93269
|
await params.rawSql(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS status_updated_at TIMESTAMP`);
|
|
93270
|
-
await params.rawSql(
|
|
93270
|
+
await params.rawSql(
|
|
93271
|
+
`ALTER TABLE apps ADD COLUMN IF NOT EXISTS memory_oom_count INTEGER NOT NULL DEFAULT 0`
|
|
93272
|
+
);
|
|
93271
93273
|
await params.rawSql(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS memory_oom_window_started_at TIMESTAMP`);
|
|
93272
93274
|
await params.rawSql(`ALTER TABLE apps ADD COLUMN IF NOT EXISTS memory_oom_last_at TIMESTAMP`);
|
|
93273
93275
|
await params.rawSql(`ALTER TABLE deployments ADD COLUMN IF NOT EXISTS env TEXT DEFAULT 'dev'`);
|
|
@@ -93555,20 +93557,20 @@ async function ensurePlatformSchemaUpgrade(params) {
|
|
|
93555
93557
|
await params.rawSql(`INSERT INTO model_pricing (model, type, provider, display_name, unit_label, input_credit_per_token, output_credit_per_token, active, sort_order)
|
|
93556
93558
|
VALUES
|
|
93557
93559
|
('gpt-5.5', 'chat', 'openai', 'GPT-5.5', '1K tok', 50, 300, true, 0),
|
|
93558
|
-
('gpt-5.4', 'chat', 'openai', 'GPT-5.4', '1K tok', 25, 150,
|
|
93560
|
+
('gpt-5.4', 'chat', 'openai', 'GPT-5.4', '1K tok', 25, 150, false, 10),
|
|
93559
93561
|
('gpt-5.4-mini', 'chat', 'openai', 'GPT-5.4 mini', '1K tok', 7.5, 45, true, 20),
|
|
93560
93562
|
('gpt-5.4-nano', 'chat', 'openai', 'GPT-5.4 nano', '1K tok', 2, 12.5, true, 30),
|
|
93561
|
-
('gpt-5.3-chat-latest', 'chat', 'openai', 'GPT-5.3 Chat', '1K tok', 17.5, 140,
|
|
93562
|
-
('gpt-5.2', 'chat', 'openai', 'GPT-5.2', '1K tok', 17.5, 140,
|
|
93563
|
-
('gpt-5.2-chat-latest', 'chat', 'openai', 'GPT-5.2 Chat', '1K tok', 17.5, 140,
|
|
93564
|
-
('gpt-5.1', 'chat', 'openai', 'GPT-5.1', '1K tok', 12.5, 100,
|
|
93565
|
-
('gpt-5', 'chat', 'openai', 'GPT-5', '1K tok', 12.5, 100,
|
|
93566
|
-
('gpt-5-mini', 'chat', 'openai', 'GPT-5 mini', '1K tok', 2.5, 20,
|
|
93567
|
-
('gpt-5-nano', 'chat', 'openai', 'GPT-5 nano', '1K tok', 0.5, 4,
|
|
93568
|
-
('gpt-4.1', 'chat', 'openai', 'GPT-4.1', '1K tok', 20, 80,
|
|
93569
|
-
('gpt-4.1-mini', 'chat', 'openai', 'GPT-4.1 mini', '1K tok', 4, 16,
|
|
93570
|
-
('gpt-4.1-nano', 'chat', 'openai', 'GPT-4.1 nano', '1K tok', 1, 4,
|
|
93571
|
-
('gpt-4o', 'chat', 'openai', 'GPT-4o', '1K tok', 25, 100,
|
|
93563
|
+
('gpt-5.3-chat-latest', 'chat', 'openai', 'GPT-5.3 Chat', '1K tok', 17.5, 140, false, 40),
|
|
93564
|
+
('gpt-5.2', 'chat', 'openai', 'GPT-5.2', '1K tok', 17.5, 140, false, 50),
|
|
93565
|
+
('gpt-5.2-chat-latest', 'chat', 'openai', 'GPT-5.2 Chat', '1K tok', 17.5, 140, false, 60),
|
|
93566
|
+
('gpt-5.1', 'chat', 'openai', 'GPT-5.1', '1K tok', 12.5, 100, false, 70),
|
|
93567
|
+
('gpt-5', 'chat', 'openai', 'GPT-5', '1K tok', 12.5, 100, false, 80),
|
|
93568
|
+
('gpt-5-mini', 'chat', 'openai', 'GPT-5 mini', '1K tok', 2.5, 20, false, 90),
|
|
93569
|
+
('gpt-5-nano', 'chat', 'openai', 'GPT-5 nano', '1K tok', 0.5, 4, false, 100),
|
|
93570
|
+
('gpt-4.1', 'chat', 'openai', 'GPT-4.1', '1K tok', 20, 80, false, 110),
|
|
93571
|
+
('gpt-4.1-mini', 'chat', 'openai', 'GPT-4.1 mini', '1K tok', 4, 16, false, 120),
|
|
93572
|
+
('gpt-4.1-nano', 'chat', 'openai', 'GPT-4.1 nano', '1K tok', 1, 4, false, 130),
|
|
93573
|
+
('gpt-4o', 'chat', 'openai', 'GPT-4o', '1K tok', 25, 100, false, 140),
|
|
93572
93574
|
('gpt-4o-mini', 'chat', 'openai', 'GPT-4o mini', '1K tok', 1.5, 6, true, 150),
|
|
93573
93575
|
('gpt-image-2', 'image', 'openai', 'GPT Image 2', '1K tok', 50, 300, true, 180),
|
|
93574
93576
|
('gpt-image-1.5', 'image', 'openai', 'GPT Image 1.5', '1K tok', 50, 320, true, 190),
|
|
@@ -94043,6 +94045,19 @@ var compressibleStaticExtensions = /* @__PURE__ */ new Set([
|
|
|
94043
94045
|
var encodedFileCache = /* @__PURE__ */ new Map();
|
|
94044
94046
|
var encodedFileCacheTtlMs = 10 * 60 * 1e3;
|
|
94045
94047
|
var encodedFileCacheMaxEntries = 240;
|
|
94048
|
+
function injectHtmlHeadTag(html2, tag, skipMarker) {
|
|
94049
|
+
if (!tag || html2.includes(skipMarker)) return html2;
|
|
94050
|
+
const headClose = /<\/head\s*>/i;
|
|
94051
|
+
if (headClose.test(html2)) return html2.replace(headClose, ` ${tag}
|
|
94052
|
+
</head>`);
|
|
94053
|
+
const htmlOpen = /<html(\s[^>]*)?>/i;
|
|
94054
|
+
if (htmlOpen.test(html2)) return html2.replace(htmlOpen, (match2) => `${match2}
|
|
94055
|
+
<head>
|
|
94056
|
+
${tag}
|
|
94057
|
+
</head>`);
|
|
94058
|
+
return `${tag}
|
|
94059
|
+
${html2}`;
|
|
94060
|
+
}
|
|
94046
94061
|
function acceptsEncoding(value, encoding) {
|
|
94047
94062
|
const tokens = value.toLowerCase().split(",").map((part) => part.trim());
|
|
94048
94063
|
return tokens.some((token) => {
|
|
@@ -94085,6 +94100,16 @@ async function encodedStaticFile(filePath, encoding, originalSize) {
|
|
|
94085
94100
|
encodedFileCache.set(cacheKey, { body: output, createdAt: Date.now() });
|
|
94086
94101
|
return output;
|
|
94087
94102
|
}
|
|
94103
|
+
function encodeStaticBuffer(input, encoding) {
|
|
94104
|
+
if (!encoding || input.byteLength <= 0) return { body: input, encoding: null };
|
|
94105
|
+
const output = encoding === "br" ? brotliCompressSync(input) : gzipSync(input);
|
|
94106
|
+
if (output.byteLength >= input.byteLength) return { body: input, encoding: null };
|
|
94107
|
+
return { body: output, encoding };
|
|
94108
|
+
}
|
|
94109
|
+
function bufferToBlob(buffer) {
|
|
94110
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
94111
|
+
return new Blob([arrayBuffer]);
|
|
94112
|
+
}
|
|
94088
94113
|
async function serveStaticFile(params) {
|
|
94089
94114
|
const staticDir = join2(params.dataDir, "static");
|
|
94090
94115
|
const resolution = await resolveStaticFileRequest({
|
|
@@ -94100,15 +94125,34 @@ async function serveStaticFile(params) {
|
|
|
94100
94125
|
const fileSize = file.size;
|
|
94101
94126
|
const requestMethod = String(params.c?.req?.method ?? "").toUpperCase();
|
|
94102
94127
|
const encoding = preferredStaticEncoding(requestHeader(params.c, "Accept-Encoding"), resolution.filePath);
|
|
94103
|
-
const
|
|
94104
|
-
const
|
|
94105
|
-
|
|
94128
|
+
const isHtml = [".html", ".htm"].includes(extname2(resolution.filePath).toLowerCase());
|
|
94129
|
+
const shouldInjectHtml = Boolean(params.htmlHeadInjection && isHtml);
|
|
94130
|
+
let responseBody;
|
|
94131
|
+
let responseBytes = fileSize;
|
|
94132
|
+
let responseEncoding = null;
|
|
94133
|
+
if (shouldInjectHtml) {
|
|
94134
|
+
const originalHtml = await file.text();
|
|
94135
|
+
const html2 = injectHtmlHeadTag(
|
|
94136
|
+
originalHtml,
|
|
94137
|
+
params.htmlHeadInjection ?? "",
|
|
94138
|
+
params.htmlHeadInjectionSkipMarker ?? params.htmlHeadInjection ?? ""
|
|
94139
|
+
);
|
|
94140
|
+
const encoded = encodeStaticBuffer(Buffer.from(html2), encoding);
|
|
94141
|
+
responseBody = bufferToBlob(encoded.body);
|
|
94142
|
+
responseBytes = encoded.body.byteLength;
|
|
94143
|
+
responseEncoding = encoded.encoding;
|
|
94144
|
+
} else {
|
|
94145
|
+
const encodedBody = encoding && Number.isSafeInteger(fileSize) && fileSize > 0 ? await encodedStaticFile(resolution.filePath, encoding, fileSize) : null;
|
|
94146
|
+
responseBody = encodedBody ? bufferToBlob(encodedBody) : file;
|
|
94147
|
+
responseBytes = encodedBody?.byteLength ?? fileSize;
|
|
94148
|
+
responseEncoding = encodedBody ? encoding : null;
|
|
94149
|
+
}
|
|
94106
94150
|
if (requestMethod !== "HEAD" && Number.isSafeInteger(responseBytes) && responseBytes > 0) {
|
|
94107
94151
|
params.recordBytes?.(responseBytes);
|
|
94108
94152
|
}
|
|
94109
94153
|
const headers = new Headers({ "Cache-Control": resolution.cacheControl });
|
|
94110
|
-
if (
|
|
94111
|
-
headers.set("Content-Encoding",
|
|
94154
|
+
if (responseEncoding) {
|
|
94155
|
+
headers.set("Content-Encoding", responseEncoding);
|
|
94112
94156
|
headers.set("Vary", "Accept-Encoding");
|
|
94113
94157
|
}
|
|
94114
94158
|
if (file.type) headers.set("Content-Type", file.type);
|
|
@@ -94478,6 +94522,15 @@ function createServerNodeProxyLoopbackRequest(params) {
|
|
|
94478
94522
|
|
|
94479
94523
|
// ../server/src/server-platform-app-proxy.ts
|
|
94480
94524
|
var INTERNAL_DOMAIN_VERIFY_PATH = "/internal/domain/verify";
|
|
94525
|
+
var HOSTED_ANALYTICS_SCRIPT_PATH = "/_gencow/analytics.js";
|
|
94526
|
+
function escapeHtmlAttribute(value) {
|
|
94527
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
94528
|
+
}
|
|
94529
|
+
function buildHostedAnalyticsScriptTag(appRow) {
|
|
94530
|
+
const deploy = appRow.currentDeployId === null || appRow.currentDeployId === void 0 ? "" : String(appRow.currentDeployId);
|
|
94531
|
+
const deployAttr = deploy ? ` data-deploy="${escapeHtmlAttribute(deploy)}"` : "";
|
|
94532
|
+
return `<script defer src="${HOSTED_ANALYTICS_SCRIPT_PATH}" data-site="app:${escapeHtmlAttribute(String(appRow.id))}"${deployAttr}></script>`;
|
|
94533
|
+
}
|
|
94481
94534
|
function buildRuntimeUnavailableBody(result2) {
|
|
94482
94535
|
return {
|
|
94483
94536
|
error: result2.error,
|
|
@@ -94590,6 +94643,7 @@ async function maybeServeHostedAnalytics(params) {
|
|
|
94590
94643
|
app: params.appRow,
|
|
94591
94644
|
host: params.host,
|
|
94592
94645
|
rawSql: params.rawSql,
|
|
94646
|
+
analyticsRawSql: params.analyticsRawSql ?? params.rawSql,
|
|
94593
94647
|
logger: params.logger
|
|
94594
94648
|
});
|
|
94595
94649
|
}
|
|
@@ -94736,6 +94790,7 @@ function registerPlatformAppProxy(params) {
|
|
|
94736
94790
|
c,
|
|
94737
94791
|
functionsPath: params.functionsPath,
|
|
94738
94792
|
rawSql: params.rawSql,
|
|
94793
|
+
analyticsRawSql: params.analyticsRawSql ?? params.rawSql,
|
|
94739
94794
|
logger: logger2
|
|
94740
94795
|
});
|
|
94741
94796
|
if (analyticsRes) {
|
|
@@ -94785,7 +94840,9 @@ function registerPlatformAppProxy(params) {
|
|
|
94785
94840
|
dataDir: appRow.dataDir,
|
|
94786
94841
|
pathname,
|
|
94787
94842
|
appName,
|
|
94788
|
-
recordBytes: (bytes) => params.addProxyMetric(appName, bytes)
|
|
94843
|
+
recordBytes: (bytes) => params.addProxyMetric(appName, bytes),
|
|
94844
|
+
htmlHeadInjection: buildHostedAnalyticsScriptTag(appRow),
|
|
94845
|
+
htmlHeadInjectionSkipMarker: HOSTED_ANALYTICS_SCRIPT_PATH
|
|
94789
94846
|
});
|
|
94790
94847
|
}
|
|
94791
94848
|
logger2.warn(
|
|
@@ -94877,7 +94934,9 @@ function registerPlatformAppProxy(params) {
|
|
|
94877
94934
|
dataDir: appRow.dataDir,
|
|
94878
94935
|
pathname,
|
|
94879
94936
|
appName,
|
|
94880
|
-
recordBytes: (bytes) => params.addProxyMetric(appName, bytes)
|
|
94937
|
+
recordBytes: (bytes) => params.addProxyMetric(appName, bytes),
|
|
94938
|
+
htmlHeadInjection: buildHostedAnalyticsScriptTag(appRow),
|
|
94939
|
+
htmlHeadInjectionSkipMarker: HOSTED_ANALYTICS_SCRIPT_PATH
|
|
94881
94940
|
});
|
|
94882
94941
|
}
|
|
94883
94942
|
if (hasBackend) {
|
|
@@ -98207,6 +98266,7 @@ async function bootstrapPlatformRuntime(params) {
|
|
|
98207
98266
|
},
|
|
98208
98267
|
generateInternalToken,
|
|
98209
98268
|
rawSql: params.rawSql,
|
|
98269
|
+
analyticsRawSql: params.analyticsRawSql ?? params.rawSql,
|
|
98210
98270
|
functionsPath: params.functionsPath,
|
|
98211
98271
|
addProxyMetric,
|
|
98212
98272
|
createProxyRequestReplayPlan,
|
|
@@ -103654,6 +103714,7 @@ function createRequestContextBuilder(params) {
|
|
|
103654
103714
|
db: createRlsDbImpl(params.db, refreshRls),
|
|
103655
103715
|
unsafeDb: params.db,
|
|
103656
103716
|
rawSql: params.rawSql,
|
|
103717
|
+
analyticsRawSql: params.analyticsRawSql ?? params.rawSql,
|
|
103657
103718
|
auth: refreshAuth,
|
|
103658
103719
|
storage: params.storage,
|
|
103659
103720
|
realtime: noopRealtime,
|
|
@@ -103721,6 +103782,7 @@ function createRequestContextBuilder(params) {
|
|
|
103721
103782
|
);
|
|
103722
103783
|
partialCtx.unsafeDb = params.db;
|
|
103723
103784
|
partialCtx.rawSql = params.rawSql;
|
|
103785
|
+
partialCtx.analyticsRawSql = params.analyticsRawSql ?? params.rawSql;
|
|
103724
103786
|
return partialCtx;
|
|
103725
103787
|
};
|
|
103726
103788
|
}
|
|
@@ -110081,6 +110143,7 @@ async function bootstrapServerMainRuntime(params) {
|
|
|
110081
110143
|
const buildCtx = createRequestContextBuilder({
|
|
110082
110144
|
db: params.db,
|
|
110083
110145
|
rawSql: params.rawSql,
|
|
110146
|
+
analyticsRawSql: params.analyticsRawSql ?? params.rawSql,
|
|
110084
110147
|
storage: params.storage,
|
|
110085
110148
|
scheduler,
|
|
110086
110149
|
isBaas: params.isBaas,
|
|
@@ -110405,6 +110468,76 @@ async function bootstrapServerMainRuntime(params) {
|
|
|
110405
110468
|
return server;
|
|
110406
110469
|
}
|
|
110407
110470
|
|
|
110471
|
+
// ../server/src/server-analytics-db.ts
|
|
110472
|
+
function isDisabledAnalyticsEnv(env2) {
|
|
110473
|
+
const enabled = env2.GENCOW_ANALYTICS_ENABLED?.trim().toLowerCase();
|
|
110474
|
+
const backend = env2.GENCOW_ANALYTICS_BACKEND?.trim().toLowerCase();
|
|
110475
|
+
return ["0", "false", "off", "disabled", "no"].includes(enabled ?? "") || backend === "disabled";
|
|
110476
|
+
}
|
|
110477
|
+
function redactPostgresDsn(value) {
|
|
110478
|
+
return value.replace(/postgres(?:ql)?:\/\/[^\s"']+/gi, "postgres://[redacted]");
|
|
110479
|
+
}
|
|
110480
|
+
async function connectPostgresAnalytics(url) {
|
|
110481
|
+
const mod6 = await import("postgres");
|
|
110482
|
+
return mod6.default(url, {
|
|
110483
|
+
max: Number(process.env.GENCOW_ANALYTICS_DB_POOL_MAX || 3),
|
|
110484
|
+
idle_timeout: Number(process.env.GENCOW_ANALYTICS_DB_IDLE_TIMEOUT_SEC || 20),
|
|
110485
|
+
connect_timeout: Number(process.env.GENCOW_ANALYTICS_DB_CONNECT_TIMEOUT_SEC || 5)
|
|
110486
|
+
});
|
|
110487
|
+
}
|
|
110488
|
+
function createUnavailableRawSql(reason) {
|
|
110489
|
+
return (async () => {
|
|
110490
|
+
throw new Error(`analytics database unavailable: ${redactPostgresDsn(reason).slice(0, 160)}`);
|
|
110491
|
+
});
|
|
110492
|
+
}
|
|
110493
|
+
async function createAnalyticsSqlRuntime(params) {
|
|
110494
|
+
const env2 = params.env ?? process.env;
|
|
110495
|
+
const logger2 = params.logger ?? console;
|
|
110496
|
+
const analyticsDatabaseUrl = env2.ANALYTICS_DATABASE_URL?.trim();
|
|
110497
|
+
if (!analyticsDatabaseUrl || isDisabledAnalyticsEnv(env2)) {
|
|
110498
|
+
return {
|
|
110499
|
+
rawSql: params.fallbackRawSql,
|
|
110500
|
+
source: "platform-fallback",
|
|
110501
|
+
close: async () => {
|
|
110502
|
+
}
|
|
110503
|
+
};
|
|
110504
|
+
}
|
|
110505
|
+
const connect = params.connect ?? connectPostgresAnalytics;
|
|
110506
|
+
const ensureSchema = params.ensureSchema ?? ensurePlatformAnalyticsSchema;
|
|
110507
|
+
let client = null;
|
|
110508
|
+
try {
|
|
110509
|
+
client = await connect(analyticsDatabaseUrl);
|
|
110510
|
+
const rawSql = (async (query2, sqlParams) => {
|
|
110511
|
+
const result2 = sqlParams && sqlParams.length > 0 ? await client.unsafe(query2, sqlParams) : await client.unsafe(query2);
|
|
110512
|
+
return Array.from(result2);
|
|
110513
|
+
});
|
|
110514
|
+
await ensureSchema({ rawSql });
|
|
110515
|
+
logger2.log("[analytics] using dedicated Postgres analytics store (ANALYTICS_DATABASE_URL configured)");
|
|
110516
|
+
return {
|
|
110517
|
+
rawSql,
|
|
110518
|
+
source: "dedicated-postgres",
|
|
110519
|
+
close: async () => {
|
|
110520
|
+
await client?.end?.();
|
|
110521
|
+
}
|
|
110522
|
+
};
|
|
110523
|
+
} catch (error) {
|
|
110524
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
110525
|
+
try {
|
|
110526
|
+
await client?.end?.();
|
|
110527
|
+
} catch {
|
|
110528
|
+
}
|
|
110529
|
+
logger2.warn(
|
|
110530
|
+
`[analytics] dedicated analytics DB unavailable; collector/provider will fail open: ${redactPostgresDsn(message).slice(0, 160)}`
|
|
110531
|
+
);
|
|
110532
|
+
return {
|
|
110533
|
+
rawSql: createUnavailableRawSql(message),
|
|
110534
|
+
source: "unavailable",
|
|
110535
|
+
close: async () => {
|
|
110536
|
+
}
|
|
110537
|
+
};
|
|
110538
|
+
}
|
|
110539
|
+
}
|
|
110540
|
+
|
|
110408
110541
|
// ../server/src/server-websocket-runtime.ts
|
|
110409
110542
|
function createServerWebsocketRuntime() {
|
|
110410
110543
|
const { upgradeWebSocket: upgradeWebSocket3, websocket: baseWebsocket } = createBunWebSocket();
|
|
@@ -113066,6 +113199,8 @@ function createSettingsRoutes(rawSql, options) {
|
|
|
113066
113199
|
var SYSTEM_ENV_KEYS = [
|
|
113067
113200
|
"DATABASE_URL",
|
|
113068
113201
|
// DB 비밀번호 포함 → 유출 시 다른 테넌트 접근 가능
|
|
113202
|
+
"ANALYTICS_DATABASE_URL",
|
|
113203
|
+
// Analytics 전용 DB 비밀번호 포함 → 서버 내부 store만 사용
|
|
113069
113204
|
"GENCOW_WAKE_SIGNAL_DATABASE_URL",
|
|
113070
113205
|
// LISTEN 전용 DB URL → DB 비밀번호 포함
|
|
113071
113206
|
"GENCOW_REALTIME_OUTBOX_POLL_INTERVAL_MS",
|
|
@@ -113697,6 +113832,10 @@ async function main() {
|
|
|
113697
113832
|
if (loadedCount > 0) {
|
|
113698
113833
|
console.log(`[settings] Loaded ${loadedCount} env var(s) from DB \u2713`);
|
|
113699
113834
|
}
|
|
113835
|
+
const analyticsSqlRuntime = await createAnalyticsSqlRuntime({
|
|
113836
|
+
fallbackRawSql: systemRawSql,
|
|
113837
|
+
logger: console
|
|
113838
|
+
});
|
|
113700
113839
|
const platformEmailEnv = {
|
|
113701
113840
|
RESEND_API_KEY: process.env.RESEND_API_KEY,
|
|
113702
113841
|
PLATFORM_EMAIL_FROM: process.env.PLATFORM_EMAIL_FROM,
|
|
@@ -113737,6 +113876,7 @@ async function main() {
|
|
|
113737
113876
|
app,
|
|
113738
113877
|
db: platformSystemDb,
|
|
113739
113878
|
rawSql: systemRawSql,
|
|
113879
|
+
analyticsRawSql: analyticsSqlRuntime.rawSql,
|
|
113740
113880
|
functionsPath,
|
|
113741
113881
|
platformDomain: PLATFORM_DOMAIN,
|
|
113742
113882
|
getBunServer: () => bunServer,
|
|
@@ -113760,7 +113900,11 @@ async function main() {
|
|
|
113760
113900
|
db,
|
|
113761
113901
|
platformSystemDb,
|
|
113762
113902
|
rawSql,
|
|
113763
|
-
|
|
113903
|
+
analyticsRawSql: analyticsSqlRuntime.rawSql,
|
|
113904
|
+
close: async () => {
|
|
113905
|
+
await analyticsSqlRuntime.close();
|
|
113906
|
+
await close();
|
|
113907
|
+
},
|
|
113764
113908
|
driver,
|
|
113765
113909
|
storage,
|
|
113766
113910
|
storageDir,
|