gencow 0.1.146 → 0.1.148
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/bin/gencow.mjs +17 -1
- package/core/index.js +35 -27
- package/lib/add-command.mjs +1 -1
- package/lib/cli-artifact-guard.mjs +70 -0
- package/lib/cli-dev-runtime.mjs +3 -3
- package/lib/cli-version-check.mjs +146 -0
- package/lib/codegen/index.mjs +35 -27
- package/lib/codegen-command.mjs +2 -2
- package/lib/cron-manifest.mjs +142 -0
- package/lib/deploy-dependency-compat.mjs +2 -5
- package/lib/deploy-package-runtime.mjs +13 -0
- package/lib/dev-cloud-bundle.mjs +5 -0
- package/lib/init-command.mjs +40 -16
- package/package.json +1 -1
- package/scripts/pre-publish-check.mjs +14 -15
- package/server/index.js +120 -57
- package/server/index.js.map +3 -3
- package/templates/ai-chat/ai.ts +1 -2
- package/templates/ai-chat/chat.ts +19 -5
- package/templates/ai.ts +1 -2
- package/templates/fullstack/ai.ts +1 -2
package/bin/gencow.mjs
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
updateEnvLocalUrl,
|
|
40
40
|
} from "../lib/cli-project-runtime.mjs";
|
|
41
41
|
import { createCodegenCommand } from "../lib/codegen-command.mjs";
|
|
42
|
+
import { maybeCheckCliVersion } from "../lib/cli-version-check.mjs";
|
|
42
43
|
import { updateComponentReadme } from "../lib/component-readme.mjs";
|
|
43
44
|
import { createConfigCommand } from "../lib/config-command.mjs";
|
|
44
45
|
import { createCorsCommand } from "../lib/cors-command.mjs";
|
|
@@ -81,6 +82,8 @@ import {
|
|
|
81
82
|
import { platformFetch, requireCreds, rpcMutation, rpcQuery } from "../lib/platform-client.mjs";
|
|
82
83
|
|
|
83
84
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
85
|
+
const CLI_PACKAGE_JSON = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf8"));
|
|
86
|
+
const CLI_VERSION = CLI_PACKAGE_JSON.version || "0.0.0";
|
|
84
87
|
const _drizzleKitCmd = (subcmd, options = {}) => buildDrizzleKitCommand(subcmd, options);
|
|
85
88
|
const generateApiTs = createApiCodegenRuntime({});
|
|
86
89
|
const runAddCommand = createAddCommand({
|
|
@@ -282,6 +285,7 @@ const commands = {
|
|
|
282
285
|
help() {
|
|
283
286
|
log(`
|
|
284
287
|
${BOLD}${CYAN}Gencow CLI${RESET} ${DIM}— AI Backend Engine${RESET}
|
|
288
|
+
${DIM}Version: ${CLI_VERSION}${RESET}
|
|
285
289
|
|
|
286
290
|
${BOLD}Usage:${RESET}
|
|
287
291
|
gencow <command>
|
|
@@ -382,6 +386,10 @@ ${BOLD}Examples:${RESET}
|
|
|
382
386
|
`);
|
|
383
387
|
},
|
|
384
388
|
|
|
389
|
+
version() {
|
|
390
|
+
log(CLI_VERSION);
|
|
391
|
+
},
|
|
392
|
+
|
|
385
393
|
// ── login/logout/whoami ───────────────────────────────
|
|
386
394
|
login: runLoginCommand,
|
|
387
395
|
logout: runLogoutCommand,
|
|
@@ -455,7 +463,15 @@ ${BOLD}Examples:${RESET}
|
|
|
455
463
|
|
|
456
464
|
// ─── updateReadme: gencow/ 폴더 스캔 → README에 컴포넌트 문서 자동 추가 ────
|
|
457
465
|
|
|
458
|
-
const [, ,
|
|
466
|
+
const [, , rawCmd = "help", ...args] = process.argv;
|
|
467
|
+
const cmd = rawCmd === "--help" || rawCmd === "-h" ? "help" : rawCmd === "--version" || rawCmd === "-v" ? "version" : rawCmd;
|
|
468
|
+
maybeCheckCliVersion({
|
|
469
|
+
command: cmd,
|
|
470
|
+
currentVersion: CLI_VERSION,
|
|
471
|
+
errorImpl: error,
|
|
472
|
+
processEnv: process.env,
|
|
473
|
+
warnImpl: warn,
|
|
474
|
+
});
|
|
459
475
|
|
|
460
476
|
if (commands[cmd]) {
|
|
461
477
|
Promise.resolve(commands[cmd](...args)).catch((e) => {
|
package/core/index.js
CHANGED
|
@@ -3258,49 +3258,57 @@ async function withRlsConnection(session, rls, reuseOuterConnection, fn) {
|
|
|
3258
3258
|
}
|
|
3259
3259
|
function wrapPreparedQuery(pq, session, rls, reuseOuterConnection) {
|
|
3260
3260
|
const origExecute = pq.execute.bind(pq);
|
|
3261
|
-
const origAll = pq.all.bind(pq);
|
|
3261
|
+
const origAll = typeof pq.all === "function" ? pq.all.bind(pq) : null;
|
|
3262
|
+
const swapClient = (client) => {
|
|
3263
|
+
const prevPq = pq.client;
|
|
3264
|
+
const prevSession = session.client;
|
|
3265
|
+
pq.client = client;
|
|
3266
|
+
session.client = client;
|
|
3267
|
+
return () => {
|
|
3268
|
+
pq.client = prevPq;
|
|
3269
|
+
session.client = prevSession;
|
|
3270
|
+
};
|
|
3271
|
+
};
|
|
3262
3272
|
pq.execute = async (placeholderValues) => {
|
|
3263
3273
|
const active = rlsExecClient.getStore();
|
|
3264
3274
|
if (active) {
|
|
3265
|
-
const
|
|
3266
|
-
pq.client = active;
|
|
3275
|
+
const restore = swapClient(active);
|
|
3267
3276
|
try {
|
|
3268
3277
|
return await origExecute(placeholderValues);
|
|
3269
3278
|
} finally {
|
|
3270
|
-
|
|
3279
|
+
restore();
|
|
3271
3280
|
}
|
|
3272
3281
|
}
|
|
3273
3282
|
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
3274
|
-
const
|
|
3275
|
-
pq.client = client;
|
|
3283
|
+
const restore = swapClient(client);
|
|
3276
3284
|
try {
|
|
3277
3285
|
return await origExecute(placeholderValues);
|
|
3278
3286
|
} finally {
|
|
3279
|
-
|
|
3287
|
+
restore();
|
|
3280
3288
|
}
|
|
3281
3289
|
});
|
|
3282
3290
|
};
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
}
|
|
3294
|
-
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
3295
|
-
const prev = pq.client;
|
|
3296
|
-
pq.client = client;
|
|
3297
|
-
try {
|
|
3298
|
-
return await origAll(placeholderValues);
|
|
3299
|
-
} finally {
|
|
3300
|
-
pq.client = prev;
|
|
3291
|
+
if (origAll) {
|
|
3292
|
+
pq.all = async (placeholderValues) => {
|
|
3293
|
+
const active = rlsExecClient.getStore();
|
|
3294
|
+
if (active) {
|
|
3295
|
+
const restore = swapClient(active);
|
|
3296
|
+
try {
|
|
3297
|
+
return await origAll(placeholderValues);
|
|
3298
|
+
} finally {
|
|
3299
|
+
restore();
|
|
3300
|
+
}
|
|
3301
3301
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3302
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
3303
|
+
const restore = swapClient(client);
|
|
3304
|
+
try {
|
|
3305
|
+
return await origAll(placeholderValues);
|
|
3306
|
+
} finally {
|
|
3307
|
+
restore();
|
|
3308
|
+
}
|
|
3309
|
+
});
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3304
3312
|
}
|
|
3305
3313
|
function wrapSession(session, rls, reuseOuterConnection) {
|
|
3306
3314
|
return new Proxy(session, {
|
package/lib/add-command.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, relative, resolve } from "path";
|
|
|
5
5
|
export const ADD_COMPONENTS = {
|
|
6
6
|
ai: {
|
|
7
7
|
label: "AI Engine",
|
|
8
|
-
deps: ["ai", "@ai-sdk/openai"],
|
|
8
|
+
deps: ["ai", "@ai-sdk/openai", "@ai-sdk/provider-utils"],
|
|
9
9
|
files: [{ src: "ai.ts", dest: "gencow/ai.ts" }],
|
|
10
10
|
env: { OPENAI_API_KEY: "" },
|
|
11
11
|
requires: [],
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const REQUIRED_CLI_TARBALL_FILES = Object.freeze([
|
|
2
|
+
"bin/gencow.mjs",
|
|
3
|
+
"bin/gencow-mcp.mjs",
|
|
4
|
+
"lib/readme-codegen.mjs",
|
|
5
|
+
"lib/deploy-auditor.mjs",
|
|
6
|
+
"lib/cron-manifest.mjs",
|
|
7
|
+
"lib/dev-cloud-bundle.mjs",
|
|
8
|
+
"lib/codegen/index.mjs",
|
|
9
|
+
"server/index.js",
|
|
10
|
+
"core/index.js",
|
|
11
|
+
"dashboard/index.html",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const FORBIDDEN_CODEGEN_CORE_IMPORTS = Object.freeze([
|
|
15
|
+
{
|
|
16
|
+
id: "server-only-procedure-route-collector",
|
|
17
|
+
pattern: /(?:import\s*{[^}]*\bcollectProcedureRouteMaps\b[^}]*}\s*from\s*["']@gencow\/core["']|collectProcedureRouteMaps[\s\S]{0,240}from\s*["']@gencow\/core["'])/,
|
|
18
|
+
message: "@gencow/core에서 server-only procedure route collector를 import합니다",
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export function validateCliTarballFiles(files, requiredFiles = REQUIRED_CLI_TARBALL_FILES) {
|
|
23
|
+
const fileSet = new Set(files);
|
|
24
|
+
const errors = [];
|
|
25
|
+
const forbiddenSourceFiles = files.filter((path) => path === "src" || path.startsWith("src/"));
|
|
26
|
+
|
|
27
|
+
for (const path of forbiddenSourceFiles) {
|
|
28
|
+
errors.push({
|
|
29
|
+
code: "forbidden-source-file",
|
|
30
|
+
path,
|
|
31
|
+
message: "packages/cli/src 는 npm tarball에 포함되면 안 됩니다",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const path of requiredFiles) {
|
|
36
|
+
if (!fileSet.has(path)) {
|
|
37
|
+
errors.push({
|
|
38
|
+
code: "missing-required-file",
|
|
39
|
+
path,
|
|
40
|
+
message: "필수 CLI artifact가 npm tarball에 없습니다",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
ok: errors.length === 0,
|
|
47
|
+
errors,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function validateGeneratedCodegenBundle(source) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
for (const rule of FORBIDDEN_CODEGEN_CORE_IMPORTS) {
|
|
54
|
+
if (rule.pattern.test(source)) {
|
|
55
|
+
errors.push({
|
|
56
|
+
code: rule.id,
|
|
57
|
+
path: "lib/codegen/index.mjs",
|
|
58
|
+
message: rule.message,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
ok: errors.length === 0,
|
|
64
|
+
errors,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatArtifactGuardErrors(errors) {
|
|
69
|
+
return errors.map((error) => ` ❌ ${error.path} — ${error.message}`).join("\n");
|
|
70
|
+
}
|
package/lib/cli-dev-runtime.mjs
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
resolveCodegenConfig,
|
|
9
9
|
resolveCodegenPublishDir,
|
|
10
10
|
} from "./codegen-command.mjs";
|
|
11
|
-
import { generateAuthSchema } from "./codegen/index.mjs";
|
|
12
11
|
import { updateComponentReadme } from "./component-readme.mjs";
|
|
13
12
|
import { buildReadmeMarkdown, extractComponentsBlock } from "./readme-codegen.mjs";
|
|
14
13
|
import { CYAN, DIM, RED, RESET, YELLOW, error, info, log, success, warn } from "./output.mjs";
|
|
@@ -146,7 +145,7 @@ export function createApiCodegenRuntime(options = {}) {
|
|
|
146
145
|
resolvePathImpl = resolve,
|
|
147
146
|
successImpl = success,
|
|
148
147
|
updateComponentReadmeImpl = updateComponentReadme,
|
|
149
|
-
generateAuthSchemaImpl
|
|
148
|
+
generateAuthSchemaImpl,
|
|
150
149
|
warnImpl = warn,
|
|
151
150
|
writeFileSyncImpl = writeFileSync,
|
|
152
151
|
} = options;
|
|
@@ -176,6 +175,7 @@ export function createApiCodegenRuntime(options = {}) {
|
|
|
176
175
|
if (typeof bundled.createNodeProcessRunner !== "function") {
|
|
177
176
|
throw new Error("Invalid codegen bundle: createNodeProcessRunner export not found");
|
|
178
177
|
}
|
|
178
|
+
const generateAuthSchema = generateAuthSchemaImpl ?? bundled.generateAuthSchema;
|
|
179
179
|
|
|
180
180
|
const result = await bundled.runCodegen({
|
|
181
181
|
projectRoot: cwd,
|
|
@@ -185,7 +185,7 @@ export function createApiCodegenRuntime(options = {}) {
|
|
|
185
185
|
finalOutDir: publishDir,
|
|
186
186
|
authSchema: {
|
|
187
187
|
enabled: resolveCodegenConfig(config).authSchema,
|
|
188
|
-
generate:
|
|
188
|
+
generate: generateAuthSchema,
|
|
189
189
|
},
|
|
190
190
|
});
|
|
191
191
|
if (result.authSchema?.changed) {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { dirname, resolve } from "path";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const DEFAULT_NPM_TIMEOUT_MS = 1500;
|
|
8
|
+
|
|
9
|
+
function isEnabled(value) {
|
|
10
|
+
return value === "1" || value === "true" || value === "yes";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseCliSemver(version) {
|
|
14
|
+
const match = String(version ?? "").trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
15
|
+
if (!match) return null;
|
|
16
|
+
return {
|
|
17
|
+
major: Number(match[1]),
|
|
18
|
+
minor: Number(match[2]),
|
|
19
|
+
patch: Number(match[3]),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function compareCliSemver(left, right) {
|
|
24
|
+
const a = typeof left === "string" ? parseCliSemver(left) : left;
|
|
25
|
+
const b = typeof right === "string" ? parseCliSemver(right) : right;
|
|
26
|
+
if (!a || !b) return 0;
|
|
27
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
28
|
+
if (a[key] > b[key]) return 1;
|
|
29
|
+
if (a[key] < b[key]) return -1;
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function shouldWarnForStaleCli({ currentVersion, latestVersion }) {
|
|
35
|
+
return compareCliSemver(latestVersion, currentVersion) > 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveVersionCheckCachePath({ homeDir = homedir(), processEnv = process.env } = {}) {
|
|
39
|
+
return processEnv.GENCOW_VERSION_CHECK_CACHE || resolve(homeDir, ".gencow", "version-check.json");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readCache(cachePath, { existsSyncImpl = existsSync, readFileSyncImpl = readFileSync } = {}) {
|
|
43
|
+
try {
|
|
44
|
+
if (!existsSyncImpl(cachePath)) return {};
|
|
45
|
+
return JSON.parse(readFileSyncImpl(cachePath, "utf8"));
|
|
46
|
+
} catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeCache(cachePath, cache, { mkdirSyncImpl = mkdirSync, writeFileSyncImpl = writeFileSync } = {}) {
|
|
52
|
+
try {
|
|
53
|
+
mkdirSyncImpl(dirname(cachePath), { recursive: true });
|
|
54
|
+
writeFileSyncImpl(cachePath, JSON.stringify(cache, null, 2) + "\n");
|
|
55
|
+
} catch {
|
|
56
|
+
/* version check cache must never break CLI commands */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isFresh(timestamp, now, maxAgeMs) {
|
|
61
|
+
return typeof timestamp === "number" && now - timestamp >= 0 && now - timestamp < maxAgeMs;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function fetchLatestVersion({ execFileSyncImpl = execFileSync, timeoutMs = DEFAULT_NPM_TIMEOUT_MS } = {}) {
|
|
65
|
+
return String(
|
|
66
|
+
execFileSyncImpl("npm", ["view", "gencow", "version", "--silent"], {
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
69
|
+
timeout: timeoutMs,
|
|
70
|
+
}),
|
|
71
|
+
).trim();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatStaleCliWarning({ currentVersion, latestVersion, command }) {
|
|
75
|
+
const suffix = command ? ` ${command}` : "";
|
|
76
|
+
return [
|
|
77
|
+
`Gencow CLI is older than npm latest (current ${currentVersion}, latest ${latestVersion}).`,
|
|
78
|
+
`Use project-local/latest CLI for verification: bunx gencow@latest${suffix}`,
|
|
79
|
+
"If this came from a global install, check: which gencow && gencow --version",
|
|
80
|
+
"Skip this check with GENCOW_SKIP_VERSION_CHECK=true.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function maybeCheckCliVersion({
|
|
85
|
+
cacheMaxAgeMs = DEFAULT_CACHE_MAX_AGE_MS,
|
|
86
|
+
command,
|
|
87
|
+
currentVersion,
|
|
88
|
+
errorImpl = console.error,
|
|
89
|
+
execFileSyncImpl = execFileSync,
|
|
90
|
+
exitImpl = (code) => process.exit(code),
|
|
91
|
+
existsSyncImpl = existsSync,
|
|
92
|
+
homeDirImpl = homedir,
|
|
93
|
+
mkdirSyncImpl = mkdirSync,
|
|
94
|
+
nowImpl = () => Date.now(),
|
|
95
|
+
processEnv = process.env,
|
|
96
|
+
readFileSyncImpl = readFileSync,
|
|
97
|
+
warnImpl = console.warn,
|
|
98
|
+
writeFileSyncImpl = writeFileSync,
|
|
99
|
+
} = {}) {
|
|
100
|
+
if (isEnabled(processEnv.GENCOW_SKIP_VERSION_CHECK)) {
|
|
101
|
+
return { status: "skipped" };
|
|
102
|
+
}
|
|
103
|
+
if (command === "help" || command === "version") {
|
|
104
|
+
return { status: "skipped" };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const requireLatest = isEnabled(processEnv.GENCOW_REQUIRE_LATEST_CLI);
|
|
108
|
+
const now = nowImpl();
|
|
109
|
+
const cachePath = resolveVersionCheckCachePath({ homeDir: homeDirImpl(), processEnv });
|
|
110
|
+
const cache = readCache(cachePath, { existsSyncImpl, readFileSyncImpl });
|
|
111
|
+
let latestVersion = cache.latestVersion;
|
|
112
|
+
|
|
113
|
+
if (!latestVersion || !isFresh(cache.checkedAt, now, cacheMaxAgeMs)) {
|
|
114
|
+
try {
|
|
115
|
+
latestVersion = fetchLatestVersion({ execFileSyncImpl });
|
|
116
|
+
cache.latestVersion = latestVersion;
|
|
117
|
+
cache.checkedAt = now;
|
|
118
|
+
writeCache(cachePath, cache, { mkdirSyncImpl, writeFileSyncImpl });
|
|
119
|
+
} catch (caught) {
|
|
120
|
+
return { status: "unverified", error: caught };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!shouldWarnForStaleCli({ currentVersion, latestVersion })) {
|
|
125
|
+
return { status: "fresh", latestVersion };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const message = formatStaleCliWarning({ currentVersion, latestVersion, command });
|
|
129
|
+
if (requireLatest) {
|
|
130
|
+
errorImpl(message);
|
|
131
|
+
exitImpl(1);
|
|
132
|
+
return { status: "blocked", latestVersion };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const warnedRecently =
|
|
136
|
+
cache.warnedForVersion === latestVersion && isFresh(cache.warnedAt, now, cacheMaxAgeMs);
|
|
137
|
+
if (!warnedRecently) {
|
|
138
|
+
warnImpl(message);
|
|
139
|
+
cache.warnedAt = now;
|
|
140
|
+
cache.warnedForVersion = latestVersion;
|
|
141
|
+
writeCache(cachePath, cache, { mkdirSyncImpl, writeFileSyncImpl });
|
|
142
|
+
return { status: "warned", latestVersion };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { status: "stale-suppressed", latestVersion };
|
|
146
|
+
}
|
package/lib/codegen/index.mjs
CHANGED
|
@@ -6557,49 +6557,57 @@ async function withRlsConnection(session, rls, reuseOuterConnection, fn) {
|
|
|
6557
6557
|
}
|
|
6558
6558
|
function wrapPreparedQuery(pq, session, rls, reuseOuterConnection) {
|
|
6559
6559
|
const origExecute = pq.execute.bind(pq);
|
|
6560
|
-
const origAll = pq.all.bind(pq);
|
|
6560
|
+
const origAll = typeof pq.all === "function" ? pq.all.bind(pq) : null;
|
|
6561
|
+
const swapClient = (client) => {
|
|
6562
|
+
const prevPq = pq.client;
|
|
6563
|
+
const prevSession = session.client;
|
|
6564
|
+
pq.client = client;
|
|
6565
|
+
session.client = client;
|
|
6566
|
+
return () => {
|
|
6567
|
+
pq.client = prevPq;
|
|
6568
|
+
session.client = prevSession;
|
|
6569
|
+
};
|
|
6570
|
+
};
|
|
6561
6571
|
pq.execute = async (placeholderValues) => {
|
|
6562
6572
|
const active = rlsExecClient.getStore();
|
|
6563
6573
|
if (active) {
|
|
6564
|
-
const
|
|
6565
|
-
pq.client = active;
|
|
6574
|
+
const restore = swapClient(active);
|
|
6566
6575
|
try {
|
|
6567
6576
|
return await origExecute(placeholderValues);
|
|
6568
6577
|
} finally {
|
|
6569
|
-
|
|
6578
|
+
restore();
|
|
6570
6579
|
}
|
|
6571
6580
|
}
|
|
6572
6581
|
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
6573
|
-
const
|
|
6574
|
-
pq.client = client;
|
|
6582
|
+
const restore = swapClient(client);
|
|
6575
6583
|
try {
|
|
6576
6584
|
return await origExecute(placeholderValues);
|
|
6577
6585
|
} finally {
|
|
6578
|
-
|
|
6586
|
+
restore();
|
|
6579
6587
|
}
|
|
6580
6588
|
});
|
|
6581
6589
|
};
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
}
|
|
6593
|
-
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
6594
|
-
const prev = pq.client;
|
|
6595
|
-
pq.client = client;
|
|
6596
|
-
try {
|
|
6597
|
-
return await origAll(placeholderValues);
|
|
6598
|
-
} finally {
|
|
6599
|
-
pq.client = prev;
|
|
6590
|
+
if (origAll) {
|
|
6591
|
+
pq.all = async (placeholderValues) => {
|
|
6592
|
+
const active = rlsExecClient.getStore();
|
|
6593
|
+
if (active) {
|
|
6594
|
+
const restore = swapClient(active);
|
|
6595
|
+
try {
|
|
6596
|
+
return await origAll(placeholderValues);
|
|
6597
|
+
} finally {
|
|
6598
|
+
restore();
|
|
6599
|
+
}
|
|
6600
6600
|
}
|
|
6601
|
-
|
|
6602
|
-
|
|
6601
|
+
return withRlsConnection(session, rls, reuseOuterConnection, async (client) => {
|
|
6602
|
+
const restore = swapClient(client);
|
|
6603
|
+
try {
|
|
6604
|
+
return await origAll(placeholderValues);
|
|
6605
|
+
} finally {
|
|
6606
|
+
restore();
|
|
6607
|
+
}
|
|
6608
|
+
});
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6603
6611
|
}
|
|
6604
6612
|
function wrapSession(session, rls, reuseOuterConnection) {
|
|
6605
6613
|
return new Proxy(session, {
|
package/lib/codegen-command.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "fs";
|
|
2
2
|
import { resolve, relative } from "path";
|
|
3
|
-
import { generateAuthSchema } from "./codegen/index.mjs";
|
|
4
3
|
|
|
5
4
|
export function resolveCodegenConfig(config = {}) {
|
|
6
5
|
const outDir =
|
|
@@ -85,6 +84,7 @@ export function createCodegenCommand(deps) {
|
|
|
85
84
|
if (typeof bundled.runCodegen !== "function") {
|
|
86
85
|
throw new Error("Invalid codegen bundle: runCodegen export not found");
|
|
87
86
|
}
|
|
87
|
+
const generateAuthSchema = deps.generateAuthSchemaImpl ?? bundled.generateAuthSchema;
|
|
88
88
|
const result = await bundled.runCodegen({
|
|
89
89
|
projectRoot: cwd,
|
|
90
90
|
functionsDir: config.functionsDir,
|
|
@@ -93,7 +93,7 @@ export function createCodegenCommand(deps) {
|
|
|
93
93
|
finalOutDir: publishDir,
|
|
94
94
|
authSchema: {
|
|
95
95
|
enabled: codegen.authSchema,
|
|
96
|
-
generate:
|
|
96
|
+
generate: generateAuthSchema,
|
|
97
97
|
},
|
|
98
98
|
});
|
|
99
99
|
if (result.authSchema?.changed) {
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import { pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
export const CRON_MANIFEST_RELATIVE_PATH = ".gencow/cron-jobs.json";
|
|
7
|
+
|
|
8
|
+
function resolveCronDefinitionsFile({ cwd, functionsDir, existsSyncImpl, resolvePathImpl }) {
|
|
9
|
+
const cronsTs = resolvePathImpl(cwd, functionsDir, "crons.ts");
|
|
10
|
+
const cronsJs = resolvePathImpl(cwd, functionsDir, "crons.js");
|
|
11
|
+
if (existsSyncImpl(cronsTs)) return cronsTs;
|
|
12
|
+
if (existsSyncImpl(cronsJs)) return cronsJs;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readNonEmptyString(value, field) {
|
|
17
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
18
|
+
throw new Error(`cron job ${field} must be a non-empty string`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeCronJobs(rawJobs) {
|
|
24
|
+
if (!Array.isArray(rawJobs)) throw new Error("crons.getJobs() must return an array");
|
|
25
|
+
|
|
26
|
+
const jobs = [];
|
|
27
|
+
const skipped = [];
|
|
28
|
+
rawJobs.forEach((job, index) => {
|
|
29
|
+
if (!job || typeof job !== "object" || Array.isArray(job)) {
|
|
30
|
+
throw new Error(`cron job ${index} must be an object`);
|
|
31
|
+
}
|
|
32
|
+
if (typeof job.action !== "string") {
|
|
33
|
+
skipped.push({
|
|
34
|
+
name: typeof job.name === "string" && job.name.trim() ? job.name : `jobs[${index}]`,
|
|
35
|
+
reason: "inline action is not supported in cloud cron manifest",
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
jobs.push({
|
|
40
|
+
name: readNonEmptyString(job.name, `${index}.name`),
|
|
41
|
+
pattern: readNonEmptyString(job.pattern, `${index}.pattern`),
|
|
42
|
+
action: readNonEmptyString(job.action, `${index}.action`),
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return { jobs, skipped, skippedInlineCount: skipped.length };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeFallbackCoreShim(coreTarget, mkdirSyncImpl, writeFileSyncImpl) {
|
|
50
|
+
mkdirSyncImpl(coreTarget, { recursive: true });
|
|
51
|
+
writeFileSyncImpl(
|
|
52
|
+
resolve(coreTarget, "package.json"),
|
|
53
|
+
JSON.stringify({ name: "@gencow/core", type: "module", main: "index.js" }, null, 2),
|
|
54
|
+
);
|
|
55
|
+
writeFileSyncImpl(
|
|
56
|
+
resolve(coreTarget, "index.js"),
|
|
57
|
+
[
|
|
58
|
+
"export function cronJobs() {",
|
|
59
|
+
" const jobs = [];",
|
|
60
|
+
" const builder = {",
|
|
61
|
+
" interval(name, options, action) {",
|
|
62
|
+
" if (options?.seconds) jobs.push({ name, pattern: `*/${options.seconds} * * * * *`, action });",
|
|
63
|
+
" else if (options?.minutes) jobs.push({ name, pattern: `*/${options.minutes} * * * *`, action });",
|
|
64
|
+
" else if (options?.hours) jobs.push({ name, pattern: `0 */${options.hours} * * *`, action });",
|
|
65
|
+
" else throw new Error(\"interval cron requires seconds, minutes, or hours\");",
|
|
66
|
+
" return builder;",
|
|
67
|
+
" },",
|
|
68
|
+
" daily(name, options, action) { jobs.push({ name, pattern: `${options.minute ?? 0} ${options.hour} * * *`, action }); return builder; },",
|
|
69
|
+
" weekly(name, options, action) { jobs.push({ name, pattern: `${options.minute ?? 0} ${options.hour} * * ${options.dayOfWeek}`, action }); return builder; },",
|
|
70
|
+
" cron(name, pattern, action) { jobs.push({ name, pattern, action }); return builder; },",
|
|
71
|
+
" getJobs() { return [...jobs]; },",
|
|
72
|
+
" };",
|
|
73
|
+
" return builder;",
|
|
74
|
+
"}",
|
|
75
|
+
"",
|
|
76
|
+
].join("\n"),
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function installCronCoreShim({ workDir, mkdirSyncImpl, resolvePathImpl, writeFileSyncImpl }) {
|
|
81
|
+
const coreTarget = resolvePathImpl(workDir, "node_modules", "@gencow", "core");
|
|
82
|
+
mkdirSyncImpl(dirname(coreTarget), { recursive: true });
|
|
83
|
+
writeFallbackCoreShim(coreTarget, mkdirSyncImpl, writeFileSyncImpl);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function writeCronManifestForProject({
|
|
87
|
+
cwd,
|
|
88
|
+
functionsDir = "gencow",
|
|
89
|
+
cpSyncImpl = cpSync,
|
|
90
|
+
existsSyncImpl = existsSync,
|
|
91
|
+
mkdirSyncImpl = mkdirSync,
|
|
92
|
+
readFileSyncImpl = readFileSync,
|
|
93
|
+
resolvePathImpl = resolve,
|
|
94
|
+
rmSyncImpl = rmSync,
|
|
95
|
+
writeFileSyncImpl = writeFileSync,
|
|
96
|
+
} = {}) {
|
|
97
|
+
if (!cwd) throw new Error("cwd is required to write cron manifest");
|
|
98
|
+
|
|
99
|
+
const cronsFile = resolveCronDefinitionsFile({ cwd, functionsDir, existsSyncImpl, resolvePathImpl });
|
|
100
|
+
const manifestPath = resolvePathImpl(cwd, CRON_MANIFEST_RELATIVE_PATH);
|
|
101
|
+
if (!cronsFile) {
|
|
102
|
+
rmSyncImpl(manifestPath, { force: true });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const workDir = resolvePathImpl(cwd, ".gencow", "cron-manifest-workdir");
|
|
107
|
+
const stagedFunctionsDir = resolvePathImpl(workDir, functionsDir);
|
|
108
|
+
rmSyncImpl(workDir, { recursive: true, force: true });
|
|
109
|
+
mkdirSyncImpl(dirname(stagedFunctionsDir), { recursive: true });
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
cpSyncImpl(resolvePathImpl(cwd, functionsDir), stagedFunctionsDir, { recursive: true });
|
|
113
|
+
installCronCoreShim({ workDir, mkdirSyncImpl, resolvePathImpl, writeFileSyncImpl });
|
|
114
|
+
|
|
115
|
+
const cronsFileName = cronsFile.endsWith(".js") ? "crons.js" : "crons.ts";
|
|
116
|
+
const stagedCronsFile = resolvePathImpl(stagedFunctionsDir, cronsFileName);
|
|
117
|
+
const importUrl = `${pathToFileURL(stagedCronsFile).href}?v=${Date.now()}-${Math.random()}`;
|
|
118
|
+
const module = await import(importUrl);
|
|
119
|
+
const builder = module.default ?? module.crons;
|
|
120
|
+
if (!builder || typeof builder.getJobs !== "function") {
|
|
121
|
+
throw new Error("crons.ts must export default cronJobs() builder");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { jobs, skipped, skippedInlineCount } = normalizeCronJobs(builder.getJobs());
|
|
125
|
+
const sourceHash = createHash("sha256").update(readFileSyncImpl(cronsFile)).digest("hex");
|
|
126
|
+
const manifest = {
|
|
127
|
+
version: 1,
|
|
128
|
+
generatedAt: new Date().toISOString(),
|
|
129
|
+
source: `${functionsDir}/${cronsFileName}`,
|
|
130
|
+
sourceHash: `sha256:${sourceHash}`,
|
|
131
|
+
skipped,
|
|
132
|
+
skippedInlineCount,
|
|
133
|
+
jobs,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
mkdirSyncImpl(dirname(manifestPath), { recursive: true });
|
|
137
|
+
writeFileSyncImpl(manifestPath, JSON.stringify(manifest, null, 2));
|
|
138
|
+
return { manifestPath, manifest };
|
|
139
|
+
} finally {
|
|
140
|
+
rmSyncImpl(workDir, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -10,7 +10,7 @@ export const DEPENDENCY_COMPAT_MATRIX = Object.freeze([
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
packageName: "drizzle-orm",
|
|
13
|
-
expectedRange: "workspace:* || ^1.0.0-beta || ^1.0.0",
|
|
13
|
+
expectedRange: "workspace:* || ^1.0.0-beta || ^1.0.0-rc || ^1.0.0",
|
|
14
14
|
installRange: "drizzle-orm@^1.0.0",
|
|
15
15
|
policy: "block",
|
|
16
16
|
},
|
|
@@ -145,10 +145,7 @@ function clausesCompatible(current, expected) {
|
|
|
145
145
|
|
|
146
146
|
if (expectedVersion.major === 0) {
|
|
147
147
|
if (currentVersion.major !== 0 || currentVersion.minor !== expectedVersion.minor) return false;
|
|
148
|
-
|
|
149
|
-
return compareVersions(currentVersion, expectedVersion) >= 0;
|
|
150
|
-
}
|
|
151
|
-
return true;
|
|
148
|
+
return compareVersions(currentVersion, expectedVersion) >= 0;
|
|
152
149
|
}
|
|
153
150
|
|
|
154
151
|
if (currentVersion.major !== expectedVersion.major) return false;
|