gencow 0.1.156 → 0.1.158
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 +206 -23
- package/server/index.js.map +4 -4
- package/templateFeature/ai/ai-facade.ts +8 -17
- package/templateFeature/ai/ai-provider.ts +29 -1
- 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({
|