gencow 0.1.165 → 0.1.167

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 CHANGED
@@ -11,7 +11,7 @@
11
11
  * gencow db:restore — restore DB from backup
12
12
  * gencow db:studio — open Drizzle Studio (visual DB browser)
13
13
  * gencow dashboard — open /_admin in browser
14
- * gencow backup — manage database backups (list/create/restore/delete/download)
14
+ * gencow backup — manage database backups (list/create/restore/restore-file/delete/download)
15
15
  * gencow templates — publish/list/download/clone marketplace templates
16
16
  * gencow help — show this help
17
17
  */
@@ -7,7 +7,7 @@ export const CREDS_PATH_LABEL = "~/.gencow/credentials.json";
7
7
 
8
8
  export function getBrowserOpenCommand(platform = process.platform) {
9
9
  if (platform === "darwin") return "open";
10
- if (platform === "win32") return "start";
10
+ if (platform === "win32") return 'cmd /c start ""';
11
11
  return "xdg-open";
12
12
  }
13
13
 
@@ -18,9 +18,14 @@ export function formatWhoamiExpiry(expiresAt, now = Date.now()) {
18
18
  return `${expiresDate.toLocaleString()} (${daysLeft} days left)`;
19
19
  }
20
20
 
21
- async function defaultOpenUrl(url, platform) {
22
- const { exec } = await import("child_process");
23
- exec(`${getBrowserOpenCommand(platform)} "${url}"`);
21
+ export async function openBrowserUrl(url, options = {}) {
22
+ const { openImpl } = options;
23
+ const openFn = openImpl ?? (await import("open")).default;
24
+ await openFn(url);
25
+ }
26
+
27
+ async function defaultOpenUrl(url) {
28
+ await openBrowserUrl(url);
24
29
  }
25
30
 
26
31
  function sleep(ms, setTimeoutImpl) {
@@ -160,6 +165,9 @@ export function createAuthCommands({
160
165
 
161
166
  const data = await response.json();
162
167
  logImpl(`\n${BOLD}${CYAN}Gencow CLI${RESET}\n`);
168
+ if (typeof data.email === "string" && data.email.trim()) {
169
+ infoImpl(`Email: ${data.email}`);
170
+ }
163
171
  infoImpl(`User ID: ${data.userId}`);
164
172
  infoImpl(`Platform: ${creds.platformUrl}`);
165
173
  infoImpl(`Token: ${creds.apiKey.slice(0, 10)}...${creds.apiKey.slice(-4)}`);
@@ -17,6 +17,18 @@ export function resolveBackupAppId(restArgs, gencowJson = null) {
17
17
  return appId;
18
18
  }
19
19
 
20
+ export function resolveBackupFilePath(restArgs) {
21
+ for (let i = 0; i < restArgs.length; i++) {
22
+ const arg = restArgs[i];
23
+ if (arg === "--app" || arg === "-a") {
24
+ i++;
25
+ continue;
26
+ }
27
+ if (!arg.startsWith("-")) return arg;
28
+ }
29
+ return null;
30
+ }
31
+
20
32
  export function formatBackupSize(bytes) {
21
33
  if (bytes < 1024) return `${bytes} B`;
22
34
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
@@ -39,16 +51,17 @@ function renderBackupHelp(logImpl = log, errorImpl = error, subCmd = null) {
39
51
  logImpl(` gencow backup list List all backups`);
40
52
  logImpl(` gencow backup create [note] Create a manual backup`);
41
53
  logImpl(` gencow backup restore <id> Restore from backup`);
54
+ logImpl(` gencow backup restore-file <path> Restore from a downloaded dump file`);
42
55
  logImpl(` gencow backup delete <id> Delete a backup`);
43
56
  logImpl(` gencow backup download <id> Download backup file (Pro+)\n`);
44
57
  }
45
58
 
46
- async function promptRestoreConfirm(backupId, { createInterfaceImpl }) {
59
+ async function promptRestoreConfirm(sourceLabel, { createInterfaceImpl }) {
47
60
  const createInterface = createInterfaceImpl ?? (await import("readline")).createInterface;
48
61
  const rl = createInterface({ input: process.stdin, output: process.stdout });
49
62
  return await new Promise((resolveAnswer) => {
50
63
  rl.question(
51
- `\n ${YELLOW}⚠ 현재 데이터베이스가 백업 #${backupId}로 교체됩니다. 계속하시겠습니까? (y/N): ${RESET}`,
64
+ `\n ${YELLOW}⚠ 현재 데이터베이스가 ${sourceLabel}로 교체됩니다. 계속하시겠습니까? (y/N): ${RESET}`,
52
65
  (answer) => {
53
66
  rl.close();
54
67
  const normalized = answer.trim().toLowerCase();
@@ -66,6 +79,7 @@ export function createBackupCommand({
66
79
  infoImpl = info,
67
80
  logImpl = log,
68
81
  platformFetchImpl = platformFetch,
82
+ readFileAsyncImpl,
69
83
  readFileSyncImpl = readFileSync,
70
84
  requireCredsImpl = requireCreds,
71
85
  resolvePathImpl = resolve,
@@ -155,7 +169,7 @@ export function createBackupCommand({
155
169
  return;
156
170
  }
157
171
 
158
- const confirmed = await promptRestoreConfirm(backupId, { createInterfaceImpl });
172
+ const confirmed = await promptRestoreConfirm(`백업 #${backupId}`, { createInterfaceImpl });
159
173
  if (!confirmed) {
160
174
  infoImpl("Restore cancelled.");
161
175
  return;
@@ -173,6 +187,46 @@ export function createBackupCommand({
173
187
  return;
174
188
  }
175
189
 
190
+ case "restore-file": {
191
+ const filePath = resolveBackupFilePath(restArgs);
192
+ if (!filePath) {
193
+ errorImpl("Specify backup file path: gencow backup restore-file <path>");
194
+ return;
195
+ }
196
+ if (!existsSyncImpl(filePath)) {
197
+ errorImpl(`Backup file not found: ${filePath}`);
198
+ return;
199
+ }
200
+
201
+ const confirmed = await promptRestoreConfirm(`파일 ${filePath}`, { createInterfaceImpl });
202
+ if (!confirmed) {
203
+ infoImpl("Restore cancelled.");
204
+ return;
205
+ }
206
+
207
+ logImpl(`\n${BOLD}Restoring from file ${filePath}...${RESET}\n`);
208
+ const readFileAsync = readFileAsyncImpl ?? (await import("fs/promises")).readFile;
209
+ const buffer = await readFileAsync(filePath);
210
+ const form = new FormData();
211
+ form.append("file", new Blob([buffer]), filePath.split(/[\\/]/).pop() || "backup.dump");
212
+ const response = await platformFetchImpl(
213
+ creds,
214
+ `/platform/apps/${appId}/backups/restore-file`,
215
+ {
216
+ method: "POST",
217
+ body: form,
218
+ },
219
+ );
220
+ if (!response.ok) {
221
+ errorImpl(
222
+ `Restore failed: ${(await response.json?.().catch(() => ({}))).error || response.statusText}`,
223
+ );
224
+ return;
225
+ }
226
+ successImpl("Database restored from file. App restarted.");
227
+ return;
228
+ }
229
+
176
230
  case "delete":
177
231
  case "rm": {
178
232
  const backupId = parseInt(restArgs[0], 10);
@@ -1,7 +1,7 @@
1
1
  import { execSync, spawn } from "child_process";
2
2
  import { appendFileSync, existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
3
3
  import { createRequire } from "module";
4
- import { basename, dirname, join, resolve } from "path";
4
+ import { basename, delimiter, dirname, join, resolve } from "path";
5
5
  import { fileURLToPath, pathToFileURL } from "url";
6
6
 
7
7
  import { resolveCodegenServerPublishDir } from "./codegen-command.mjs";
@@ -400,9 +400,20 @@ function ensureEsbuildConsistency(options = {}) {
400
400
  }
401
401
 
402
402
  export function buildDrizzleKitCommand(subcmd, options = {}) {
403
- const { cwd = process.cwd(), existsSyncImpl = existsSync, resolvePathImpl = resolve } = options;
403
+ const {
404
+ cwd = process.cwd(),
405
+ existsSyncImpl = existsSync,
406
+ platform = process.platform,
407
+ resolvePathImpl = resolve,
408
+ } = options;
404
409
  const localBin = resolvePathImpl(cwd, "node_modules/.bin/drizzle-kit");
405
- const cmd = existsSyncImpl(localBin) ? `"${localBin}" ${subcmd}` : `npx drizzle-kit ${subcmd}`;
410
+ const windowsBin = `${localBin}.cmd`;
411
+ const resolvedBin = platform === "win32" && existsSyncImpl(windowsBin)
412
+ ? windowsBin
413
+ : existsSyncImpl(localBin)
414
+ ? localBin
415
+ : null;
416
+ const cmd = resolvedBin ? `"${resolvedBin}" ${subcmd}` : `npx drizzle-kit ${subcmd}`;
406
417
  const env = ensureEsbuildConsistency({ ...options, cwd });
407
418
  return { cmd, env };
408
419
  }
@@ -509,6 +520,7 @@ export function buildEnv(config, options = {}) {
509
520
  findServerRootImpl = findServerRoot,
510
521
  processEnv = process.env,
511
522
  existsSyncImpl = existsSync,
523
+ pathDelimiter = delimiter,
512
524
  readFileSyncImpl = readFileSync,
513
525
  resolvePathImpl = resolve,
514
526
  } = options;
@@ -531,7 +543,7 @@ export function buildEnv(config, options = {}) {
531
543
  const existingNodePath = processEnv.NODE_PATH || "";
532
544
  const nodePath = [cwdNodeModules, cliNodeModules, serverNodeModules, monoNodeModules, existingNodePath]
533
545
  .filter(Boolean)
534
- .join(":");
546
+ .join(pathDelimiter);
535
547
 
536
548
  const drizzleSchemaPaths = resolveDrizzleSchemaPaths(config, {
537
549
  cwd,
@@ -8953,7 +8953,7 @@ function sortArtifacts(artifacts) {
8953
8953
  // src/codegen/infra/auth-schema-worker.ts
8954
8954
  import { spawnSync } from "node:child_process";
8955
8955
  import { unlinkSync, writeFileSync } from "node:fs";
8956
- import { resolve } from "node:path";
8956
+ import { delimiter, resolve } from "node:path";
8957
8957
  import { pathToFileURL } from "node:url";
8958
8958
  function buildGeneratorScript(authPath, emitRelations = true) {
8959
8959
  const authUrl = pathToFileURL(authPath).href;
@@ -9015,7 +9015,7 @@ function runAuthSchemaWorker({
9015
9015
  encoding: "utf8",
9016
9016
  env: {
9017
9017
  ...process.env,
9018
- NODE_PATH: [resolve(cwd, "node_modules"), process.env.NODE_PATH ?? ""].filter(Boolean).join(":")
9018
+ NODE_PATH: [resolve(cwd, "node_modules"), process.env.NODE_PATH ?? ""].filter(Boolean).join(delimiter)
9019
9019
  }
9020
9020
  });
9021
9021
  if (result.error) throw result.error;
@@ -11942,8 +11942,8 @@ function emoji() {
11942
11942
  }
11943
11943
  var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
11944
11944
  var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
11945
- var mac = (delimiter) => {
11946
- const escapedDelim = escapeRegex(delimiter ?? ":");
11945
+ var mac = (delimiter3) => {
11946
+ const escapedDelim = escapeRegex(delimiter3 ?? ":");
11947
11947
  return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
11948
11948
  };
11949
11949
  var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -25120,7 +25120,7 @@ function assertSourceEntrypoints(input) {
25120
25120
  }
25121
25121
 
25122
25122
  // src/codegen/frontend/table-introspector.ts
25123
- import { relative as relative3, resolve as resolve7 } from "node:path";
25123
+ import { delimiter as delimiter2, relative as relative3, resolve as resolve7 } from "node:path";
25124
25124
  function createTableIntrospector(deps) {
25125
25125
  return {
25126
25126
  extract(input) {
@@ -25141,7 +25141,7 @@ async function extractTables(input, deps) {
25141
25141
  args: [scriptPath],
25142
25142
  cwd: workDir,
25143
25143
  env: {
25144
- NODE_PATH: [resolve7(input.projectRoot, "node_modules"), process.env.NODE_PATH ?? ""].filter(Boolean).join(":")
25144
+ NODE_PATH: [resolve7(input.projectRoot, "node_modules"), process.env.NODE_PATH ?? ""].filter(Boolean).join(delimiter2)
25145
25145
  }
25146
25146
  });
25147
25147
  if (result.exitCode !== 0) {
@@ -284,19 +284,48 @@ function collectInternalProcedureNames(source) {
284
284
 
285
285
  function parseDefineApiCronJobsSource(source) {
286
286
  const stripped = stripComments(source);
287
-
288
287
  const internalNames = collectInternalProcedureNames(stripped);
288
+ const parseJob = ({ name, method, scheduleArgs, actionSource }) => {
289
+ if (!name) return null;
290
+ let pattern = null;
291
+ if (method === "interval") pattern = intervalOptionsToPattern(scheduleArgs);
292
+ if (method === "daily") {
293
+ const hour = parseNumericOption(scheduleArgs, "hour");
294
+ const minute = parseNumericOption(scheduleArgs, "minute") ?? 0;
295
+ if (hour !== undefined) pattern = `${minute} ${hour} * * *`;
296
+ }
297
+ if (method === "weekly") {
298
+ const dayOfWeek = parseNumericOption(scheduleArgs, "dayOfWeek");
299
+ const hour = parseNumericOption(scheduleArgs, "hour");
300
+ const minute = parseNumericOption(scheduleArgs, "minute") ?? 0;
301
+ if (dayOfWeek !== undefined && hour !== undefined) pattern = `${minute} ${hour} * * ${dayOfWeek}`;
302
+ }
303
+ if (method === "cron") pattern = parseStringLiteral(scheduleArgs);
304
+ if (!pattern) return null;
305
+
306
+ const procedureName = parseStringLiteral(actionSource) ?? internalNames.get(actionSource?.trim());
307
+ if (!procedureName) {
308
+ return { skipped: { name, reason: "inline or non-internal action is not supported in cloud cron manifest" } };
309
+ }
310
+ return { job: { name, pattern, procedureName } };
311
+ };
312
+
313
+ const entries = [];
314
+ let entryOrder = 0;
315
+ const addParsed = (index, parsed) => {
316
+ if (!parsed) return;
317
+ entries.push({ index, order: entryOrder, parsed });
318
+ entryOrder += 1;
319
+ };
320
+
289
321
  const builderNames = new Set();
290
322
  const builderRegex = /\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*cronJobs\s*\(\s*\)\s*;?/g;
291
323
  let builderMatch;
292
324
  while ((builderMatch = builderRegex.exec(stripped))) {
293
325
  builderNames.add(builderMatch[1]);
294
326
  }
295
- if (builderNames.size === 0) return { jobs: [], skipped: [], skippedInlineCount: 0 };
296
327
 
297
328
  const callRegex = /\b([A-Za-z_$][\w$]*)\.(interval|daily|weekly|cron)\s*\(([\s\S]*?)\)\s*;?/g;
298
- const jobs = [];
299
- const skipped = [];
300
329
  let match;
301
330
  while ((match = callRegex.exec(stripped))) {
302
331
  const builderName = match[1];
@@ -305,34 +334,54 @@ function parseDefineApiCronJobsSource(source) {
305
334
  const method = match[2];
306
335
  const args = splitTopLevelArgs(match[3] ?? "");
307
336
  const name = parseStringLiteral(args[0]);
308
- if (!name) continue;
337
+ addParsed(
338
+ match.index,
339
+ parseJob({ name, method, scheduleArgs: args[1], actionSource: args[2]?.trim() }),
340
+ );
341
+ }
309
342
 
310
- let pattern = null;
311
- if (method === "interval") pattern = intervalOptionsToPattern(args[1]);
312
- if (method === "daily") {
313
- const hour = parseNumericOption(args[1], "hour");
314
- const minute = parseNumericOption(args[1], "minute") ?? 0;
315
- if (hour !== undefined) pattern = `${minute} ${hour} * * *`;
316
- }
317
- if (method === "weekly") {
318
- const dayOfWeek = parseNumericOption(args[1], "dayOfWeek");
319
- const hour = parseNumericOption(args[1], "hour");
320
- const minute = parseNumericOption(args[1], "minute") ?? 0;
321
- if (dayOfWeek !== undefined && hour !== undefined) pattern = `${minute} ${hour} * * ${dayOfWeek}`;
343
+ const chainRegex = /\b(?:export\s+)?const\s+[A-Za-z_$][\w$]*\s*=\s*cronJobs\s*\(\s*\)([\s\S]*?);/g;
344
+ while ((match = chainRegex.exec(stripped))) {
345
+ const chainSource = match[1] ?? "";
346
+ const chainCallRegex = /\.(interval|daily|weekly|cron)\s*\(([\s\S]*?)\)/g;
347
+ let chainMatch;
348
+ while ((chainMatch = chainCallRegex.exec(chainSource))) {
349
+ const args = splitTopLevelArgs(chainMatch[2] ?? "");
350
+ addParsed(
351
+ match.index + chainMatch.index,
352
+ parseJob({
353
+ name: parseStringLiteral(args[0]),
354
+ method: chainMatch[1],
355
+ scheduleArgs: args[1],
356
+ actionSource: args[2]?.trim(),
357
+ }),
358
+ );
322
359
  }
323
- if (method === "cron") pattern = parseStringLiteral(args[1]);
324
- if (!pattern) continue;
360
+ }
325
361
 
326
- const actionSource = args[2]?.trim();
327
- const stringAction = parseStringLiteral(actionSource);
328
- const procedureName = stringAction ?? internalNames.get(actionSource);
329
- if (!procedureName) {
330
- skipped.push({ name, reason: "inline or non-internal action is not supported in cloud cron manifest" });
331
- continue;
332
- }
333
- jobs.push({ name, pattern, procedureName });
362
+ const declarativeRegex =
363
+ /\b(?:export\s+)?const\s+[A-Za-z_$][\w$]*\s*=\s*cron\s*\.\s*name\s*\(\s*(['"`])([^'"`]+)\1\s*\)\s*\.\s*(interval|daily|weekly|cron)\s*\(([\s\S]*?)\)\s*\.\s*handler\s*\(([\s\S]*?)\)\s*;?/g;
364
+ while ((match = declarativeRegex.exec(stripped))) {
365
+ addParsed(
366
+ match.index,
367
+ parseJob({
368
+ name: match[2],
369
+ method: match[3],
370
+ scheduleArgs: match[4],
371
+ actionSource: splitTopLevelArgs(match[5] ?? "")[0]?.trim(),
372
+ }),
373
+ );
334
374
  }
335
375
 
376
+ const jobs = [];
377
+ const skipped = [];
378
+ entries
379
+ .sort((a, b) => a.index - b.index || a.order - b.order)
380
+ .forEach(({ parsed }) => {
381
+ if (parsed.job) jobs.push(parsed.job);
382
+ if (parsed.skipped) skipped.push(parsed.skipped);
383
+ });
384
+
336
385
  return { jobs, skipped, skippedInlineCount: skipped.length };
337
386
  }
338
387
 
@@ -501,6 +550,50 @@ async function collectDefineApiCronJobsWithShimFallback(params) {
501
550
  }
502
551
  }
503
552
 
553
+ function resolveLocalImportFile({ importerFile, specifier, existsSyncImpl, resolvePathImpl }) {
554
+ if (!specifier.startsWith(".")) return null;
555
+ const base = resolvePathImpl(dirname(importerFile), specifier);
556
+ const candidates = [
557
+ base,
558
+ `${base}.ts`,
559
+ `${base}.js`,
560
+ resolvePathImpl(base, "index.ts"),
561
+ resolvePathImpl(base, "index.js"),
562
+ ];
563
+ return candidates.find((candidate) => existsSyncImpl(candidate)) ?? null;
564
+ }
565
+
566
+ function collectLocalImportSourceText({
567
+ source,
568
+ file,
569
+ existsSyncImpl,
570
+ readFileSyncImpl,
571
+ resolvePathImpl,
572
+ }) {
573
+ const imports = [];
574
+ const fromRegex = /\bimport\s+[^'"]*?\s+from\s+["']([^"']+)["']/g;
575
+ const sideEffectRegex = /\bimport\s+["']([^"']+)["']/g;
576
+ for (const regex of [fromRegex, sideEffectRegex]) {
577
+ let match;
578
+ while ((match = regex.exec(source))) imports.push(match[1]);
579
+ }
580
+
581
+ const seen = new Set();
582
+ const chunks = [];
583
+ for (const specifier of imports) {
584
+ const importedFile = resolveLocalImportFile({
585
+ importerFile: file,
586
+ specifier,
587
+ existsSyncImpl,
588
+ resolvePathImpl,
589
+ });
590
+ if (!importedFile || seen.has(importedFile)) continue;
591
+ seen.add(importedFile);
592
+ chunks.push(readFileSyncImpl(importedFile, "utf8"));
593
+ }
594
+ return chunks.join("\n");
595
+ }
596
+
504
597
  export async function writeCronManifestForProject({
505
598
  cwd,
506
599
  functionsDir = "gencow",
@@ -526,7 +619,14 @@ export async function writeCronManifestForProject({
526
619
 
527
620
  if (!cronsFile && apiFile) {
528
621
  const source = readFileSyncImpl(apiFile, "utf8");
529
- const staticParsed = parseDefineApiCronJobsSource(source);
622
+ const importedSource = collectLocalImportSourceText({
623
+ source,
624
+ file: apiFile,
625
+ existsSyncImpl,
626
+ readFileSyncImpl,
627
+ resolvePathImpl,
628
+ });
629
+ const staticParsed = parseDefineApiCronJobsSource(`${source}\n${importedSource}`);
530
630
  let parsed = staticParsed;
531
631
  if (shouldLoadDefineApiCrons(source)) {
532
632
  try {
@@ -1,8 +1,10 @@
1
1
  import { execSync } from "child_process";
2
2
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync } from "fs";
3
3
  import { dirname, resolve } from "path";
4
+ import { runDrizzleGenerateWithFallback } from "./drizzle-generate.mjs";
4
5
  import { BOLD, CYAN, DIM, GREEN, RED, RESET, error, info, log, success, warn } from "./output.mjs";
5
6
  import { platformFetch, requireCreds } from "./platform-client.mjs";
7
+ import { createTarGzipArchive } from "./tar-archive.mjs";
6
8
 
7
9
  export function resolveDbCloudTarget(args, gencowJson = null) {
8
10
  const isProd = args.includes("--prod");
@@ -139,6 +141,7 @@ export function createDbCommands({
139
141
  runInServer,
140
142
  statSyncImpl = statSync,
141
143
  successImpl = success,
144
+ tarCreateImpl,
142
145
  unlinkSyncImpl = unlinkSync,
143
146
  cpSyncImpl = cpSync,
144
147
  dirnameImpl = dirname,
@@ -444,11 +447,13 @@ export function createDbCommands({
444
447
 
445
448
  infoImpl("Generating schema migrations...");
446
449
  try {
447
- const dk = drizzleKitCmdImpl("generate");
448
- execSyncImpl(dk.cmd, {
450
+ runDrizzleGenerateWithFallback({
449
451
  cwd: cwdImpl(),
450
- env: { ...processEnv, ...dk.env },
452
+ drizzleKitCmdImpl,
453
+ env: processEnv,
454
+ execSyncImpl,
451
455
  stdio: "inherit",
456
+ warnImpl,
452
457
  });
453
458
  successImpl("Migrations generated");
454
459
  } catch (e) {
@@ -469,8 +474,11 @@ export function createDbCommands({
469
474
  mkdirSyncImpl(dirnameImpl(tmpBundle), { recursive: true });
470
475
 
471
476
  const tarSourceDir = functionsDir.startsWith("./") ? functionsDir.slice(2) : functionsDir;
472
- execSyncImpl(`COPYFILE_DISABLE=1 tar -czf "${tmpBundle}" -C "${cwdImpl()}" "${tarSourceDir}/"`, {
473
- stdio: ["ignore", "pipe", "pipe"],
477
+ await createTarGzipArchive({
478
+ cwd: cwdImpl(),
479
+ file: tmpBundle,
480
+ entries: [`${tarSourceDir}/`],
481
+ tarCreateImpl,
474
482
  });
475
483
 
476
484
  const bundleData = readFileSyncImpl(tmpBundle);
@@ -518,11 +526,13 @@ export function createDbCommands({
518
526
  if (isMonorepo()) {
519
527
  runInServer("pnpm db:generate", buildEnv(config));
520
528
  } else {
521
- const dk = drizzleKitCmdImpl("generate");
522
- execSyncImpl(dk.cmd, {
529
+ runDrizzleGenerateWithFallback({
523
530
  cwd: cwdImpl(),
524
- env: { ...buildEnv(config), ...dk.env },
531
+ drizzleKitCmdImpl,
532
+ env: buildEnv(config),
533
+ execSyncImpl,
525
534
  stdio: "inherit",
535
+ warnImpl,
526
536
  });
527
537
  }
528
538
  successImpl("Migration files generated in gencow/migrations/");
@@ -1,13 +1,15 @@
1
1
  import { execSync } from "child_process";
2
- import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "fs";
2
+ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "fs";
3
3
  import { dirname, resolve } from "path";
4
4
  import {
5
5
  fetchAppDiagnosticLogsViaRest,
6
6
  normalizeDiagnosticLogLines,
7
7
  } from "./app-diagnostics.mjs";
8
8
  import { CRON_MANIFEST_RELATIVE_PATH, writeCronManifestForProject } from "./cron-manifest.mjs";
9
+ import { getCommandErrorMessage, runDrizzleGenerateWithFallback } from "./drizzle-generate.mjs";
9
10
  import { CYAN, DIM, RED, RESET, YELLOW, error, info, log, success, warn } from "./output.mjs";
10
11
  import { platformFetch, rpcMutation } from "./platform-client.mjs";
12
+ import { createTarGzipArchive } from "./tar-archive.mjs";
11
13
 
12
14
  export function writeProjectMetadata({
13
15
  appId,
@@ -44,15 +46,18 @@ export function createDeployPackageRuntime({
44
46
  loadConfigImpl,
45
47
  logImpl = log,
46
48
  mkdirSyncImpl = mkdirSync,
49
+ cpSyncImpl = cpSync,
47
50
  platformFetchImpl = platformFetch,
48
51
  processRef = process,
49
52
  readFileSyncImpl = readFileSync,
50
53
  resolvePathImpl = resolve,
54
+ rmSyncImpl = rmSync,
51
55
  rpcMutationImpl = rpcMutation,
52
56
  setIntervalImpl = setInterval,
53
57
  setTimeoutImpl = setTimeout,
54
58
  statSyncImpl = statSync,
55
59
  successImpl = success,
60
+ tarCreateImpl,
56
61
  unlinkSyncImpl = unlinkSync,
57
62
  verifyAppReadyImpl,
58
63
  warnImpl = warn,
@@ -106,16 +111,18 @@ export function createDeployPackageRuntime({
106
111
  stdio: "inherit",
107
112
  });
108
113
  } else {
109
- const dk = drizzleKitCmdImpl("generate");
110
- execGen(dk.cmd, {
114
+ runDrizzleGenerateWithFallback({
111
115
  cwd,
112
- env: { ...(genEnv ?? processRef.env), ...dk.env },
116
+ drizzleKitCmdImpl,
117
+ env: genEnv ?? processRef.env,
118
+ execSyncImpl: execGen,
113
119
  stdio: "inherit",
120
+ warnImpl,
114
121
  });
115
122
  }
116
123
  successImpl("Schema -> migrations synced");
117
124
  } catch (caught) {
118
- const message = (caught?.stderr?.toString() || caught?.message || "").trim();
125
+ const message = getCommandErrorMessage(caught);
119
126
  if (
120
127
  message.includes("No schema changes") ||
121
128
  message.includes("nothing to migrate") ||
@@ -280,16 +287,26 @@ export function createDeployPackageRuntime({
280
287
  const src = resolvePathImpl(cwd, file);
281
288
  const dst = resolvePathImpl(tmpDir, file);
282
289
  if (file.endsWith("/")) {
283
- execSyncImpl(`cp -r "${src}" "${dst}"`, { cwd });
290
+ cpSyncImpl(src, dst, { recursive: true });
284
291
  } else if (existsSyncImpl(src)) {
285
292
  mkdirSyncImpl(dirname(dst), { recursive: true });
286
- execSyncImpl(`cp "${src}" "${dst}"`, { cwd });
293
+ cpSyncImpl(src, dst);
287
294
  }
288
295
  }
289
- execSyncImpl(`COPYFILE_DISABLE=1 tar -czf "${tmpBundle}" .`, { cwd: tmpDir });
290
- execSyncImpl(`rm -rf "${tmpDir}"`, { cwd });
296
+ await createTarGzipArchive({
297
+ cwd: tmpDir,
298
+ file: tmpBundle,
299
+ entries: ["."],
300
+ tarCreateImpl,
301
+ });
302
+ rmSyncImpl(tmpDir, { recursive: true, force: true });
291
303
  } else {
292
- execSyncImpl(`COPYFILE_DISABLE=1 tar -czf "${tmpBundle}" ${filesToPack.join(" ")}`, { cwd });
304
+ await createTarGzipArchive({
305
+ cwd,
306
+ file: tmpBundle,
307
+ entries: filesToPack,
308
+ tarCreateImpl,
309
+ });
293
310
  }
294
311
  } catch (caught) {
295
312
  errorImpl(`Packaging failed: ${caught.message}`);
@@ -1,4 +1,5 @@
1
1
  import { execSync } from "child_process";
2
+ import { runDrizzleGenerateWithFallback } from "./drizzle-generate.mjs";
2
3
 
3
4
  export function runDevCloudMigrationGenerate(config, deps) {
4
5
  const cwd = deps.cwdImpl();
@@ -18,11 +19,13 @@ export function runDevCloudMigrationGenerate(config, deps) {
18
19
  return "server";
19
20
  }
20
21
 
21
- const dk = deps.drizzleKitCmdImpl("generate");
22
- execSyncImpl(dk.cmd, {
22
+ runDrizzleGenerateWithFallback({
23
23
  cwd,
24
- env: { ...genEnv, ...dk.env },
24
+ drizzleKitCmdImpl: deps.drizzleKitCmdImpl,
25
+ env: genEnv,
26
+ execSyncImpl,
25
27
  stdio: "inherit",
28
+ warnImpl: deps.warnImpl,
26
29
  });
27
30
  return "local";
28
31
  }