ship-em 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # shipem
1
+ # shipem 🐑
2
2
 
3
3
  **One command. Your app is live.**
4
4
 
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import chalk4 from "chalk";
9
9
  import axios2 from "axios";
10
10
  import FormData2 from "form-data";
11
11
  import { createReadStream } from "fs";
12
- import { rmSync, existsSync as existsSync6, appendFileSync, writeFileSync as writeFileSync4, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
12
+ import { rmSync, existsSync as existsSync6, appendFileSync, writeFileSync as writeFileSync5, readFileSync as readFileSync8, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
13
13
  import { join as join8 } from "path";
14
14
  import { tmpdir } from "os";
15
15
  import { create as tarCreate } from "tar";
@@ -68,7 +68,7 @@ var ui = {
68
68
  );
69
69
  console.log("");
70
70
  console.log(
71
- ` ${brand.gray("Your AI built it.")} ${brand.blue.bold("We'll ship it.")}`
71
+ ` \u{1F411} ${brand.gray("Your AI built it.")} ${brand.blue.bold("We'll ship it.")}`
72
72
  );
73
73
  console.log("");
74
74
  },
@@ -546,7 +546,7 @@ function scanProjectInternal(cwd) {
546
546
  return {
547
547
  framework: "nextjs",
548
548
  buildCommand: pkg.scripts?.build ?? "npm run build",
549
- outputDirectory: ".next",
549
+ outputDirectory: "out",
550
550
  installCommand: detectPackageManager(cwd),
551
551
  serverType,
552
552
  deployTarget: "cloudflare-pages",
@@ -794,9 +794,34 @@ function findWorkspacePackages(cwd) {
794
794
 
795
795
  // src/build/builder.ts
796
796
  import { execa } from "execa";
797
- import { existsSync as existsSync2 } from "fs";
797
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
798
798
  import { join as join2 } from "path";
799
799
  import chalk2 from "chalk";
800
+ function patchNextConfig(cwd) {
801
+ const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
802
+ for (const filename of candidates) {
803
+ const configPath = join2(cwd, filename);
804
+ if (existsSync2(configPath)) {
805
+ const content = readFileSync2(configPath, "utf-8");
806
+ if (content.includes("output")) return;
807
+ const patched = content.replace(
808
+ /const nextConfig[^=]*=\s*\{/,
809
+ (match) => match + "\n output: 'export',"
810
+ );
811
+ if (patched !== content) {
812
+ writeFileSync(configPath, patched, "utf-8");
813
+ ui.dim(" \u2139\uFE0F Added output: 'export' to " + filename + " for static deployment");
814
+ return;
815
+ }
816
+ }
817
+ }
818
+ writeFileSync(
819
+ join2(cwd, "next.config.js"),
820
+ "/** @type {import('next').NextConfig} */\nconst nextConfig = { output: 'export' };\nmodule.exports = nextConfig;\n",
821
+ "utf-8"
822
+ );
823
+ ui.dim(" \u2139\uFE0F Created next.config.js with output: 'export' for static deployment");
824
+ }
800
825
  async function buildProject(config, cwd = process.cwd()) {
801
826
  const start = Date.now();
802
827
  if (config.installCommand) {
@@ -804,8 +829,8 @@ async function buildProject(config, cwd = process.cwd()) {
804
829
  const outputLines = [];
805
830
  let lineCount = 0;
806
831
  try {
807
- const [installBin, ...installArgs] = config.installCommand.split(" ");
808
- const installProc = execa(installBin, installArgs, {
832
+ const installProc = execa(config.installCommand, {
833
+ shell: true,
809
834
  cwd,
810
835
  env: { ...process.env, CI: "true" },
811
836
  timeout: 5 * 60 * 1e3,
@@ -850,6 +875,9 @@ async function buildProject(config, cwd = process.cwd()) {
850
875
  }
851
876
  console.log("");
852
877
  }
878
+ if (config.framework === "nextjs") {
879
+ patchNextConfig(cwd);
880
+ }
853
881
  if (config.buildCommand) {
854
882
  const buildSpinner = ui.spinner("Building...");
855
883
  const outputLines = [];
@@ -865,8 +893,8 @@ async function buildProject(config, cwd = process.cwd()) {
865
893
  }
866
894
  }
867
895
  try {
868
- const [finalBin, ...finalArgs] = config.buildCommand.split(" ");
869
- const buildProc = execa(finalBin, finalArgs, {
896
+ const buildProc = execa(config.buildCommand, {
897
+ shell: true,
870
898
  cwd,
871
899
  env: buildEnv,
872
900
  timeout: 10 * 60 * 1e3,
@@ -944,7 +972,7 @@ function parseErrorMessage(raw, outputLines) {
944
972
 
945
973
  // src/config.ts
946
974
  import Conf from "conf";
947
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
975
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "fs";
948
976
  import { join as join3 } from "path";
949
977
  var SHIPEM_API_URL = process.env.SHIPEM_API_URL ?? "https://api.shipem.dev";
950
978
  var globalConf = new Conf({
@@ -985,7 +1013,7 @@ function readProjectConfig(cwd = process.cwd()) {
985
1013
  return {};
986
1014
  }
987
1015
  try {
988
- const raw = readFileSync2(configPath, "utf-8");
1016
+ const raw = readFileSync3(configPath, "utf-8");
989
1017
  const config = JSON.parse(raw);
990
1018
  warnIfConfigContainsSecrets(config, configPath);
991
1019
  return config;
@@ -995,7 +1023,7 @@ function readProjectConfig(cwd = process.cwd()) {
995
1023
  }
996
1024
  function writeProjectConfig(config, cwd = process.cwd()) {
997
1025
  const configPath = join3(cwd, SHIPIT_CONFIG_FILE);
998
- writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1026
+ writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
999
1027
  }
1000
1028
  var SECRET_KEY_PATTERN = /token|secret|key|password|credential|api_?key/i;
1001
1029
  function warnIfConfigContainsSecrets(config, configPath) {
@@ -1162,7 +1190,7 @@ async function loginCommand(opts = {}) {
1162
1190
  }
1163
1191
 
1164
1192
  // src/commands/fix.ts
1165
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync as readdirSync2 } from "fs";
1193
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3, readdirSync as readdirSync2 } from "fs";
1166
1194
  import { join as join4 } from "path";
1167
1195
  import { execa as execa2 } from "execa";
1168
1196
  function findMissingModules(errorOutput) {
@@ -1239,12 +1267,12 @@ function applyTsConfigFixes(cwd, fixes) {
1239
1267
  const tsconfigPath = join4(cwd, "tsconfig.json");
1240
1268
  if (!existsSync4(tsconfigPath)) return false;
1241
1269
  try {
1242
- const content = readFileSync3(tsconfigPath, "utf-8");
1270
+ const content = readFileSync4(tsconfigPath, "utf-8");
1243
1271
  const stripped = content.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
1244
1272
  const tsconfig = JSON.parse(stripped);
1245
1273
  if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
1246
1274
  Object.assign(tsconfig.compilerOptions, fixes);
1247
- writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
1275
+ writeFileSync3(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
1248
1276
  return true;
1249
1277
  } catch {
1250
1278
  return false;
@@ -1267,7 +1295,7 @@ function findMissingEnvVars(cwd) {
1267
1295
  scanDir(fullPath, depth + 1);
1268
1296
  } else if (/\.(ts|tsx|js|jsx|mjs|mts|vue|svelte|astro)$/.test(entry.name)) {
1269
1297
  try {
1270
- const content = readFileSync3(fullPath, "utf-8");
1298
+ const content = readFileSync4(fullPath, "utf-8");
1271
1299
  for (const pat of patterns) {
1272
1300
  pat.lastIndex = 0;
1273
1301
  let match;
@@ -1296,7 +1324,7 @@ function generateEnvExample(cwd, vars) {
1296
1324
  const envExamplePath = join4(cwd, ".env.example");
1297
1325
  if (existsSync4(envExamplePath)) return false;
1298
1326
  const content = vars.map((v) => `${v}=`).join("\n") + "\n";
1299
- writeFileSync2(envExamplePath, content, "utf-8");
1327
+ writeFileSync3(envExamplePath, content, "utf-8");
1300
1328
  return true;
1301
1329
  }
1302
1330
  function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
@@ -1305,7 +1333,7 @@ function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
1305
1333
  const nextConfigPath = existsSync4(join4(cwd, "next.config.mjs")) ? join4(cwd, "next.config.mjs") : existsSync4(join4(cwd, "next.config.js")) ? join4(cwd, "next.config.js") : null;
1306
1334
  if (nextConfigPath && errorOutput.includes("output")) {
1307
1335
  try {
1308
- const content = readFileSync3(nextConfigPath, "utf-8");
1336
+ const content = readFileSync4(nextConfigPath, "utf-8");
1309
1337
  if (!content.includes("output")) {
1310
1338
  fixes.push(`Add output: 'export' to ${nextConfigPath.split("/").pop()}`);
1311
1339
  }
@@ -1316,7 +1344,7 @@ function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
1316
1344
  if ((framework === "vite-react" || framework === "vite-vue" || framework === "vite-svelte") && errorOutput.includes("plugin")) {
1317
1345
  const pkgPath = join4(cwd, "package.json");
1318
1346
  try {
1319
- const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
1347
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
1320
1348
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1321
1349
  if (framework === "vite-react" && !deps["@vitejs/plugin-react"]) {
1322
1350
  fixes.push("Install @vitejs/plugin-react");
@@ -1488,11 +1516,11 @@ async function tryInlineFix(errorOutput, cwd, config) {
1488
1516
  // src/deploy/cloudflare.ts
1489
1517
  import axios from "axios";
1490
1518
  import { createHash } from "crypto";
1491
- import { readdirSync as readdirSync3, statSync as statSync2, readFileSync as readFileSync5 } from "fs";
1519
+ import { readdirSync as readdirSync3, statSync as statSync2, readFileSync as readFileSync6 } from "fs";
1492
1520
  import { join as join6, relative } from "path";
1493
1521
 
1494
1522
  // src/deploy/exclude.ts
1495
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1523
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1496
1524
  import { join as join5 } from "path";
1497
1525
  import chalk3 from "chalk";
1498
1526
  var DEFAULT_PATTERNS = [
@@ -1547,7 +1575,7 @@ function matchesIgnoreLine(relPath, line) {
1547
1575
  function loadIgnoreLines(filePath) {
1548
1576
  if (!existsSync5(filePath)) return [];
1549
1577
  try {
1550
- return readFileSync4(filePath, "utf-8").split("\n");
1578
+ return readFileSync5(filePath, "utf-8").split("\n");
1551
1579
  } catch {
1552
1580
  return [];
1553
1581
  }
@@ -1599,8 +1627,7 @@ var CloudflarePages = class {
1599
1627
  this.client = axios.create({
1600
1628
  baseURL: CF_API_BASE,
1601
1629
  headers: {
1602
- Authorization: `Bearer ${apiToken}`,
1603
- "Content-Type": "application/json"
1630
+ Authorization: `Bearer ${apiToken}`
1604
1631
  },
1605
1632
  timeout: CF_REQUEST_TIMEOUT_MS
1606
1633
  });
@@ -1681,7 +1708,7 @@ var CloudflarePages = class {
1681
1708
  let totalBytes = 0;
1682
1709
  const fileMap = /* @__PURE__ */ new Map();
1683
1710
  for (const filePath of filePaths) {
1684
- const content = readFileSync5(filePath);
1711
+ const content = readFileSync6(filePath);
1685
1712
  const hash = createHash("sha256").update(content).digest("hex");
1686
1713
  const urlPath = "/" + relative(fullOutputPath, filePath).replace(/\\/g, "/");
1687
1714
  fileMap.set(urlPath, { hash, content });
@@ -1696,14 +1723,20 @@ var CloudflarePages = class {
1696
1723
  for (const [urlPath, { hash }] of fileMap) {
1697
1724
  manifest[urlPath] = hash;
1698
1725
  }
1726
+ if (Object.keys(manifest).length === 0) {
1727
+ throw new DeployError(`Output directory '${outputDir}' contains no files to deploy. Check your build output.`);
1728
+ }
1699
1729
  const deploySpinner = ui.spinner("Creating deployment...");
1700
1730
  let jwt;
1701
1731
  let requiredFiles;
1702
1732
  let deployment;
1703
1733
  try {
1734
+ const deployFormData = new FormData();
1735
+ deployFormData.append("manifest", JSON.stringify(manifest));
1736
+ deployFormData.append("branch", "main");
1704
1737
  const res = await this.client.post(
1705
1738
  `/accounts/${this.accountId}/pages/projects/${projectName}/deployments`,
1706
- { files: manifest, branch: "main" },
1739
+ deployFormData,
1707
1740
  { timeout: CF_REQUEST_TIMEOUT_MS }
1708
1741
  );
1709
1742
  if (!res.data.success) {
@@ -1757,7 +1790,7 @@ var CloudflarePages = class {
1757
1790
  let totalBytes = 0;
1758
1791
  const fileMap = /* @__PURE__ */ new Map();
1759
1792
  for (const filePath of filePaths) {
1760
- const content = readFileSync5(filePath);
1793
+ const content = readFileSync6(filePath);
1761
1794
  const hash = createHash("sha256").update(content).digest("hex");
1762
1795
  const urlPath = "/" + relative(fullOutputPath, filePath).replace(/\\/g, "/");
1763
1796
  fileMap.set(urlPath, { hash, content });
@@ -1772,14 +1805,20 @@ var CloudflarePages = class {
1772
1805
  for (const [urlPath, { hash }] of fileMap) {
1773
1806
  manifest[urlPath] = hash;
1774
1807
  }
1808
+ if (Object.keys(manifest).length === 0) {
1809
+ throw new DeployError(`Output directory '${outputDir}' contains no files to deploy. Check your build output.`);
1810
+ }
1775
1811
  const deploySpinner = ui.spinner(`Creating preview deployment (branch: ${branch})...`);
1776
1812
  let jwt;
1777
1813
  let requiredFiles;
1778
1814
  let deployment;
1779
1815
  try {
1816
+ const deployFormData = new FormData();
1817
+ deployFormData.append("manifest", JSON.stringify(manifest));
1818
+ deployFormData.append("branch", branch);
1780
1819
  const res = await this.client.post(
1781
1820
  `/accounts/${this.accountId}/pages/projects/${projectName}/deployments`,
1782
- { files: manifest, branch },
1821
+ deployFormData,
1783
1822
  { timeout: CF_REQUEST_TIMEOUT_MS }
1784
1823
  );
1785
1824
  if (!res.data.success) {
@@ -1943,7 +1982,7 @@ function sanitizeProjectName(name) {
1943
1982
  }
1944
1983
 
1945
1984
  // src/deploy/badge.ts
1946
- import { readdirSync as readdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1985
+ import { readdirSync as readdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1947
1986
  import { join as join7 } from "path";
1948
1987
  var BADGE_HTML = `<!-- Shipped with Shipem -->
1949
1988
  <div id="shipem-badge" style="position:fixed;bottom:12px;right:12px;z-index:9999;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:12px;background:rgba(13,17,23,0.9);color:#94A3B8;padding:6px 12px;border-radius:20px;border:1px solid rgba(59,130,246,0.3);text-decoration:none;display:flex;align-items:center;gap:6px;backdrop-filter:blur(8px);transition:opacity 0.2s"><a href="https://shipem.dev" target="_blank" rel="noopener" style="color:#94A3B8;text-decoration:none;display:flex;align-items:center;gap:6px">Shipped with <span style="color:#3B82F6">\u26A1</span> Shipem</a></div>`;
@@ -1953,14 +1992,14 @@ function injectBadge(outputDir) {
1953
1992
  const files = readdirSync4(outputDir).filter((f) => f.endsWith(".html"));
1954
1993
  for (const file of files) {
1955
1994
  const filePath = join7(outputDir, file);
1956
- const content = readFileSync6(filePath, "utf-8");
1995
+ const content = readFileSync7(filePath, "utf-8");
1957
1996
  if (content.includes("shipem-badge")) continue;
1958
1997
  if (content.includes("</body>")) {
1959
- writeFileSync3(filePath, content.replace("</body>", `${BADGE_HTML}
1998
+ writeFileSync4(filePath, content.replace("</body>", `${BADGE_HTML}
1960
1999
  </body>`), "utf-8");
1961
2000
  injected++;
1962
2001
  } else if (content.includes("</html>")) {
1963
- writeFileSync3(filePath, content.replace("</html>", `${BADGE_HTML}
2002
+ writeFileSync4(filePath, content.replace("</html>", `${BADGE_HTML}
1964
2003
  </html>`), "utf-8");
1965
2004
  injected++;
1966
2005
  }
@@ -2294,6 +2333,10 @@ async function deployCommand(options) {
2294
2333
  }
2295
2334
  }
2296
2335
  phases.push({ name: "Scan", durationMs: Date.now() - phaseStart });
2336
+ if (projectConfig.framework === "nextjs") {
2337
+ patchNextConfigForStaticExport(cwd);
2338
+ patchNextLayoutFonts(cwd);
2339
+ }
2297
2340
  if (!options.skipBuild && !options.turbo) {
2298
2341
  phaseStart = Date.now();
2299
2342
  ui.section("Building...");
@@ -2549,13 +2592,13 @@ async function deployCommand(options) {
2549
2592
  }, cwd);
2550
2593
  const gitignorePath = join8(cwd, ".gitignore");
2551
2594
  if (existsSync6(gitignorePath)) {
2552
- const gitignoreContent = readFileSync7(gitignorePath, "utf-8");
2595
+ const gitignoreContent = readFileSync8(gitignorePath, "utf-8");
2553
2596
  const lines = gitignoreContent.split("\n").map((l) => l.trim());
2554
2597
  if (!lines.includes("shipem.json")) {
2555
2598
  appendFileSync(gitignorePath, "\n# Shipem config\nshipem.json\n");
2556
2599
  }
2557
2600
  } else {
2558
- writeFileSync4(gitignorePath, "# Shipem config\nshipem.json\n");
2601
+ writeFileSync5(gitignorePath, "# Shipem config\nshipem.json\n");
2559
2602
  }
2560
2603
  ui.deployBoxEnhanced(
2561
2604
  projectConfig.name,
@@ -2567,9 +2610,77 @@ async function deployCommand(options) {
2567
2610
  isAnonymous
2568
2611
  );
2569
2612
  }
2613
+ function patchNextConfigForStaticExport(cwd) {
2614
+ const tsConfig = join8(cwd, "next.config.ts");
2615
+ const jsConfig = join8(cwd, "next.config.js");
2616
+ const mjsConfig = join8(cwd, "next.config.mjs");
2617
+ for (const configPath of [tsConfig, jsConfig, mjsConfig]) {
2618
+ if (existsSync6(configPath)) {
2619
+ const content = readFileSync8(configPath, "utf-8");
2620
+ if (/output\s*:\s*['"]export['"]/.test(content)) {
2621
+ return;
2622
+ }
2623
+ }
2624
+ }
2625
+ if (existsSync6(tsConfig)) {
2626
+ const content = readFileSync8(tsConfig, "utf-8");
2627
+ const patched = content.replace(
2628
+ /({[\s\S]*?)(})\s*(?:satisfies|as)\s/,
2629
+ "$1 output: 'export',\n$2 satisfies "
2630
+ );
2631
+ if (patched === content) {
2632
+ const fallback = content.replace(/(\{)/, "$1\n output: 'export',");
2633
+ writeFileSync5(tsConfig, fallback);
2634
+ } else {
2635
+ writeFileSync5(tsConfig, patched);
2636
+ }
2637
+ console.log(` \u2139\uFE0F Added output: 'export' to next.config.ts for static deployment`);
2638
+ } else if (existsSync6(mjsConfig)) {
2639
+ const content = readFileSync8(mjsConfig, "utf-8");
2640
+ const patched = content.replace(/(\{)/, "$1\n output: 'export',");
2641
+ writeFileSync5(mjsConfig, patched);
2642
+ console.log(` \u2139\uFE0F Added output: 'export' to next.config.mjs for static deployment`);
2643
+ } else if (existsSync6(jsConfig)) {
2644
+ const content = readFileSync8(jsConfig, "utf-8");
2645
+ const patched = content.replace(/(\{)/, "$1\n output: 'export',");
2646
+ writeFileSync5(jsConfig, patched);
2647
+ console.log(` \u2139\uFE0F Added output: 'export' to next.config.js for static deployment`);
2648
+ } else {
2649
+ writeFileSync5(jsConfig, `/** @type {import('next').NextConfig} */
2650
+ module.exports = { output: 'export' };
2651
+ `);
2652
+ console.log(` \u2139\uFE0F Created next.config.js with output: 'export' for static deployment`);
2653
+ }
2654
+ }
2655
+ function patchNextLayoutFonts(cwd) {
2656
+ const layoutPaths = [
2657
+ join8(cwd, "app/layout.tsx"),
2658
+ join8(cwd, "app/layout.jsx"),
2659
+ join8(cwd, "src/app/layout.tsx"),
2660
+ join8(cwd, "src/app/layout.jsx")
2661
+ ];
2662
+ for (const layoutPath of layoutPaths) {
2663
+ if (!existsSync6(layoutPath)) continue;
2664
+ const content = readFileSync8(layoutPath, "utf-8");
2665
+ if (!/import.*next\/font/.test(content)) continue;
2666
+ let patched = content;
2667
+ patched = patched.replace(/import\s+\{[^}]*\}\s+from\s+['"]next\/font\/[^'"]+['"];?\n?/g, "");
2668
+ patched = patched.replace(/const\s+\w+\s*=\s*\w+\(\s*\{[^}]*\}\s*\)\s*;?\n?/g, "");
2669
+ patched = patched.replace(/\$\{\w+\.className\}/g, "");
2670
+ patched = patched.replace(/className=\{`([^`]*)`\}/g, (match, inner) => {
2671
+ const cleaned = inner.trim();
2672
+ return cleaned ? `className={\`${cleaned}\`}` : "";
2673
+ });
2674
+ patched = patched.replace(/className=\{\w+\.className\}/g, "");
2675
+ if (patched !== content) {
2676
+ writeFileSync5(layoutPath, patched);
2677
+ console.log(` \u2139\uFE0F Removed next/font imports from ${layoutPath.replace(cwd + "/", "")} for static export`);
2678
+ }
2679
+ }
2680
+ }
2570
2681
 
2571
2682
  // src/commands/env.ts
2572
- import { existsSync as existsSync7, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync5 } from "fs";
2683
+ import { existsSync as existsSync7, readFileSync as readFileSync9, readdirSync as readdirSync6, writeFileSync as writeFileSync6 } from "fs";
2573
2684
  import { join as join9 } from "path";
2574
2685
  var SERVICE_LINKS = {
2575
2686
  SUPABASE_URL: { name: "Supabase", url: "https://app.supabase.com/project/_/settings/api" },
@@ -2609,7 +2720,7 @@ function scanSourceForEnvVars(cwd) {
2609
2720
  scanDir(fullPath, depth + 1);
2610
2721
  } else if (/\.(ts|tsx|js|jsx|mjs|mts|vue|svelte|astro)$/.test(entry.name)) {
2611
2722
  try {
2612
- const content = readFileSync8(fullPath, "utf-8");
2723
+ const content = readFileSync9(fullPath, "utf-8");
2613
2724
  for (const pat of patterns) {
2614
2725
  pat.lastIndex = 0;
2615
2726
  let match;
@@ -2648,7 +2759,7 @@ function readEnvFile(cwd) {
2648
2759
  }
2649
2760
  function readFile2(path) {
2650
2761
  try {
2651
- return readFileSync8(path, "utf-8");
2762
+ return readFileSync9(path, "utf-8");
2652
2763
  } catch {
2653
2764
  return null;
2654
2765
  }
@@ -2713,7 +2824,7 @@ async function envCommand() {
2713
2824
  const comment = service ? ` # ${service.name}` : "";
2714
2825
  return `${v}=${comment}`;
2715
2826
  }).join("\n") + "\n";
2716
- writeFileSync5(envExamplePath, content, "utf-8");
2827
+ writeFileSync6(envExamplePath, content, "utf-8");
2717
2828
  ui.success("Generated .env.example from detected variables");
2718
2829
  console.log("");
2719
2830
  } else if (existsSync7(envExamplePath)) {
@@ -2734,7 +2845,7 @@ async function envCommand() {
2734
2845
  // src/commands/init.ts
2735
2846
  import inquirer2 from "inquirer";
2736
2847
  import { execa as execa3 } from "execa";
2737
- import { existsSync as existsSync8, writeFileSync as writeFileSync6, mkdirSync } from "fs";
2848
+ import { existsSync as existsSync8, writeFileSync as writeFileSync7, mkdirSync } from "fs";
2738
2849
  import { join as join10 } from "path";
2739
2850
  import chalk6 from "chalk";
2740
2851
 
@@ -2928,7 +3039,7 @@ async function initCommand(options = {}) {
2928
3039
  }
2929
3040
  const projectDir = join10(process.cwd(), projectName);
2930
3041
  if (existsSync8(projectDir)) {
2931
- writeFileSync6(
3042
+ writeFileSync7(
2932
3043
  join10(projectDir, "shipem.json"),
2933
3044
  JSON.stringify({ project: { name: projectName, framework: matched.value } }, null, 2) + "\n",
2934
3045
  "utf-8"
@@ -3009,7 +3120,7 @@ async function scaffoldFromTemplate(tpl, options) {
3009
3120
  }
3010
3121
  const projectDir = join10(process.cwd(), projectName);
3011
3122
  if (existsSync8(projectDir)) {
3012
- writeFileSync6(
3123
+ writeFileSync7(
3013
3124
  join10(projectDir, "shipem.json"),
3014
3125
  JSON.stringify({ project: { name: projectName, framework: tpl.framework } }, null, 2) + "\n",
3015
3126
  "utf-8"
@@ -3025,7 +3136,7 @@ function createMinimalProject(projectName, description) {
3025
3136
  const dir = join10(process.cwd(), projectName);
3026
3137
  if (!existsSync8(dir)) {
3027
3138
  mkdirSync(dir, { recursive: true });
3028
- writeFileSync6(join10(dir, "index.html"), `<!DOCTYPE html>
3139
+ writeFileSync7(join10(dir, "index.html"), `<!DOCTYPE html>
3029
3140
  <html lang="en">
3030
3141
  <head>
3031
3142
  <meta charset="UTF-8">
@@ -3051,7 +3162,7 @@ function createMinimalProject(projectName, description) {
3051
3162
  function createStaticPortfolio(projectName) {
3052
3163
  const dir = join10(process.cwd(), projectName);
3053
3164
  mkdirSync(dir, { recursive: true });
3054
- writeFileSync6(join10(dir, "index.html"), `<!DOCTYPE html>
3165
+ writeFileSync7(join10(dir, "index.html"), `<!DOCTYPE html>
3055
3166
  <html lang="en">
3056
3167
  <head>
3057
3168
  <meta charset="UTF-8">
@@ -3088,7 +3199,7 @@ function createStaticPortfolio(projectName) {
3088
3199
  </footer>
3089
3200
  </body>
3090
3201
  </html>`, "utf-8");
3091
- writeFileSync6(join10(dir, "style.css"), `* { margin: 0; padding: 0; box-sizing: border-box; }
3202
+ writeFileSync7(join10(dir, "style.css"), `* { margin: 0; padding: 0; box-sizing: border-box; }
3092
3203
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0a0a0a; color: #ededed; min-height: 100vh; }
3093
3204
  header { text-align: center; padding: 4rem 1rem 2rem; }
3094
3205
  header h1 { font-size: 2.5rem; margin-bottom: 0.5rem; }
@@ -3527,7 +3638,7 @@ async function watchCommand() {
3527
3638
  import chalk11 from "chalk";
3528
3639
 
3529
3640
  // src/memory/index.ts
3530
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync2, existsSync as existsSync9 } from "fs";
3641
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync2, existsSync as existsSync9 } from "fs";
3531
3642
  import { join as join12 } from "path";
3532
3643
  import { homedir } from "os";
3533
3644
  var SHIPEM_HOME = join12(homedir(), ".shipem");
@@ -3543,14 +3654,14 @@ function ensureDir() {
3543
3654
  function readJson2(path, fallback) {
3544
3655
  try {
3545
3656
  if (!existsSync9(path)) return fallback;
3546
- return JSON.parse(readFileSync9(path, "utf-8"));
3657
+ return JSON.parse(readFileSync10(path, "utf-8"));
3547
3658
  } catch {
3548
3659
  return fallback;
3549
3660
  }
3550
3661
  }
3551
3662
  function writeJson(path, data) {
3552
3663
  ensureDir();
3553
- writeFileSync7(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
3664
+ writeFileSync8(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
3554
3665
  }
3555
3666
  function getMemory() {
3556
3667
  return readJson2(MEMORY_PATH, {
@@ -3645,7 +3756,7 @@ async function configCommand(action, key, value) {
3645
3756
  // src/commands/monitor.ts
3646
3757
  import axios5 from "axios";
3647
3758
  import chalk12 from "chalk";
3648
- import { writeFileSync as writeFileSync8, readFileSync as readFileSync10, existsSync as existsSync10, unlinkSync } from "fs";
3759
+ import { writeFileSync as writeFileSync9, readFileSync as readFileSync11, existsSync as existsSync10, unlinkSync } from "fs";
3649
3760
  import { join as join13 } from "path";
3650
3761
  import { tmpdir as tmpdir2 } from "os";
3651
3762
 
@@ -3724,7 +3835,7 @@ async function monitorCommand(options = {}) {
3724
3835
  if (options.stop) {
3725
3836
  if (existsSync10(PID_FILE)) {
3726
3837
  try {
3727
- const pid = parseInt(readFileSync10(PID_FILE, "utf-8").trim(), 10);
3838
+ const pid = parseInt(readFileSync11(PID_FILE, "utf-8").trim(), 10);
3728
3839
  process.kill(pid, "SIGTERM");
3729
3840
  unlinkSync(PID_FILE);
3730
3841
  ui.success("Monitor daemon stopped.");
@@ -3798,11 +3909,11 @@ async function monitorCommand(options = {}) {
3798
3909
  });
3799
3910
  }
3800
3911
  function writePidFile() {
3801
- writeFileSync8(PID_FILE, String(process.pid), "utf-8");
3912
+ writeFileSync9(PID_FILE, String(process.pid), "utf-8");
3802
3913
  }
3803
3914
 
3804
3915
  // src/commands/hooks.ts
3805
- import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync2, chmodSync, mkdirSync as mkdirSync3 } from "fs";
3916
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync10, unlinkSync as unlinkSync2, chmodSync, mkdirSync as mkdirSync3 } from "fs";
3806
3917
  import { join as join14 } from "path";
3807
3918
  var HOOK_MARKER = "# shipem-auto-deploy";
3808
3919
  var HOOK_CONTENT = `#!/bin/sh
@@ -3816,7 +3927,7 @@ function getHookPath(cwd) {
3816
3927
  }
3817
3928
  function isShipemHook(path) {
3818
3929
  if (!existsSync11(path)) return false;
3819
- const content = readFileSync11(path, "utf-8");
3930
+ const content = readFileSync12(path, "utf-8");
3820
3931
  return content.includes(HOOK_MARKER);
3821
3932
  }
3822
3933
  async function hooksCommand(action) {
@@ -3856,7 +3967,7 @@ async function hooksCommand(action) {
3856
3967
  if (!existsSync11(hooksDir)) {
3857
3968
  mkdirSync3(hooksDir, { recursive: true });
3858
3969
  }
3859
- writeFileSync9(hookPath, HOOK_CONTENT, "utf-8");
3970
+ writeFileSync10(hookPath, HOOK_CONTENT, "utf-8");
3860
3971
  chmodSync(hookPath, 493);
3861
3972
  ui.success("Installed post-commit hook \u2192 auto-deploy on commit");
3862
3973
  ui.dim(`Remove with: shipem hooks remove`);
@@ -4009,7 +4120,7 @@ async function previewCommand(options = {}) {
4009
4120
  }
4010
4121
 
4011
4122
  // src/index.ts
4012
- import { readFileSync as readFileSync12 } from "fs";
4123
+ import { readFileSync as readFileSync13 } from "fs";
4013
4124
  import { fileURLToPath } from "url";
4014
4125
  import { dirname, join as join15 } from "path";
4015
4126
  var __filename2 = fileURLToPath(import.meta.url);
@@ -4017,7 +4128,7 @@ var __dirname2 = dirname(__filename2);
4017
4128
  var version = "0.1.0";
4018
4129
  try {
4019
4130
  const pkg = JSON.parse(
4020
- readFileSync12(join15(__dirname2, "../package.json"), "utf-8")
4131
+ readFileSync13(join15(__dirname2, "../package.json"), "utf-8")
4021
4132
  );
4022
4133
  version = pkg.version;
4023
4134
  } catch {
package/dist/lib.js CHANGED
@@ -137,7 +137,7 @@ function scanProjectInternal(cwd) {
137
137
  return {
138
138
  framework: "nextjs",
139
139
  buildCommand: pkg.scripts?.build ?? "npm run build",
140
- outputDirectory: ".next",
140
+ outputDirectory: "out",
141
141
  installCommand: detectPackageManager(cwd),
142
142
  serverType,
143
143
  deployTarget: "cloudflare-pages",
@@ -450,7 +450,7 @@ function warnIfConfigContainsSecrets(config, configPath) {
450
450
 
451
451
  // src/build/builder.ts
452
452
  import { execa } from "execa";
453
- import { existsSync as existsSync3 } from "fs";
453
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
454
454
  import { join as join3 } from "path";
455
455
  import chalk2 from "chalk";
456
456
 
@@ -508,7 +508,7 @@ var ui = {
508
508
  );
509
509
  console.log("");
510
510
  console.log(
511
- ` ${brand.gray("Your AI built it.")} ${brand.blue.bold("We'll ship it.")}`
511
+ ` \u{1F411} ${brand.gray("Your AI built it.")} ${brand.blue.bold("We'll ship it.")}`
512
512
  );
513
513
  console.log("");
514
514
  },
@@ -848,6 +848,31 @@ ${url}`);
848
848
  };
849
849
 
850
850
  // src/build/builder.ts
851
+ function patchNextConfig(cwd) {
852
+ const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
853
+ for (const filename of candidates) {
854
+ const configPath = join3(cwd, filename);
855
+ if (existsSync3(configPath)) {
856
+ const content = readFileSync3(configPath, "utf-8");
857
+ if (content.includes("output")) return;
858
+ const patched = content.replace(
859
+ /const nextConfig[^=]*=\s*\{/,
860
+ (match) => match + "\n output: 'export',"
861
+ );
862
+ if (patched !== content) {
863
+ writeFileSync2(configPath, patched, "utf-8");
864
+ ui.dim(" \u2139\uFE0F Added output: 'export' to " + filename + " for static deployment");
865
+ return;
866
+ }
867
+ }
868
+ }
869
+ writeFileSync2(
870
+ join3(cwd, "next.config.js"),
871
+ "/** @type {import('next').NextConfig} */\nconst nextConfig = { output: 'export' };\nmodule.exports = nextConfig;\n",
872
+ "utf-8"
873
+ );
874
+ ui.dim(" \u2139\uFE0F Created next.config.js with output: 'export' for static deployment");
875
+ }
851
876
  async function buildProject(config, cwd = process.cwd()) {
852
877
  const start = Date.now();
853
878
  if (config.installCommand) {
@@ -855,8 +880,8 @@ async function buildProject(config, cwd = process.cwd()) {
855
880
  const outputLines = [];
856
881
  let lineCount = 0;
857
882
  try {
858
- const [installBin, ...installArgs] = config.installCommand.split(" ");
859
- const installProc = execa(installBin, installArgs, {
883
+ const installProc = execa(config.installCommand, {
884
+ shell: true,
860
885
  cwd,
861
886
  env: { ...process.env, CI: "true" },
862
887
  timeout: 5 * 60 * 1e3,
@@ -901,6 +926,9 @@ async function buildProject(config, cwd = process.cwd()) {
901
926
  }
902
927
  console.log("");
903
928
  }
929
+ if (config.framework === "nextjs") {
930
+ patchNextConfig(cwd);
931
+ }
904
932
  if (config.buildCommand) {
905
933
  const buildSpinner = ui.spinner("Building...");
906
934
  const outputLines = [];
@@ -916,8 +944,8 @@ async function buildProject(config, cwd = process.cwd()) {
916
944
  }
917
945
  }
918
946
  try {
919
- const [finalBin, ...finalArgs] = config.buildCommand.split(" ");
920
- const buildProc = execa(finalBin, finalArgs, {
947
+ const buildProc = execa(config.buildCommand, {
948
+ shell: true,
921
949
  cwd,
922
950
  env: buildEnv,
923
951
  timeout: 10 * 60 * 1e3,
@@ -996,11 +1024,11 @@ function parseErrorMessage(raw, outputLines) {
996
1024
  // src/deploy/cloudflare.ts
997
1025
  import axios from "axios";
998
1026
  import { createHash } from "crypto";
999
- import { readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync4 } from "fs";
1027
+ import { readdirSync as readdirSync2, statSync as statSync2, readFileSync as readFileSync5 } from "fs";
1000
1028
  import { join as join5, relative } from "path";
1001
1029
 
1002
1030
  // src/deploy/exclude.ts
1003
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
1031
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1004
1032
  import { join as join4 } from "path";
1005
1033
  import chalk3 from "chalk";
1006
1034
  var DEFAULT_PATTERNS = [
@@ -1055,7 +1083,7 @@ function matchesIgnoreLine(relPath, line) {
1055
1083
  function loadIgnoreLines(filePath) {
1056
1084
  if (!existsSync4(filePath)) return [];
1057
1085
  try {
1058
- return readFileSync3(filePath, "utf-8").split("\n");
1086
+ return readFileSync4(filePath, "utf-8").split("\n");
1059
1087
  } catch {
1060
1088
  return [];
1061
1089
  }
@@ -1139,8 +1167,7 @@ var CloudflarePages = class {
1139
1167
  this.client = axios.create({
1140
1168
  baseURL: CF_API_BASE,
1141
1169
  headers: {
1142
- Authorization: `Bearer ${apiToken}`,
1143
- "Content-Type": "application/json"
1170
+ Authorization: `Bearer ${apiToken}`
1144
1171
  },
1145
1172
  timeout: CF_REQUEST_TIMEOUT_MS
1146
1173
  });
@@ -1221,7 +1248,7 @@ var CloudflarePages = class {
1221
1248
  let totalBytes = 0;
1222
1249
  const fileMap = /* @__PURE__ */ new Map();
1223
1250
  for (const filePath of filePaths) {
1224
- const content = readFileSync4(filePath);
1251
+ const content = readFileSync5(filePath);
1225
1252
  const hash = createHash("sha256").update(content).digest("hex");
1226
1253
  const urlPath = "/" + relative(fullOutputPath, filePath).replace(/\\/g, "/");
1227
1254
  fileMap.set(urlPath, { hash, content });
@@ -1236,14 +1263,20 @@ var CloudflarePages = class {
1236
1263
  for (const [urlPath, { hash }] of fileMap) {
1237
1264
  manifest[urlPath] = hash;
1238
1265
  }
1266
+ if (Object.keys(manifest).length === 0) {
1267
+ throw new DeployError(`Output directory '${outputDir}' contains no files to deploy. Check your build output.`);
1268
+ }
1239
1269
  const deploySpinner = ui.spinner("Creating deployment...");
1240
1270
  let jwt;
1241
1271
  let requiredFiles;
1242
1272
  let deployment;
1243
1273
  try {
1274
+ const deployFormData = new FormData();
1275
+ deployFormData.append("manifest", JSON.stringify(manifest));
1276
+ deployFormData.append("branch", "main");
1244
1277
  const res = await this.client.post(
1245
1278
  `/accounts/${this.accountId}/pages/projects/${projectName}/deployments`,
1246
- { files: manifest, branch: "main" },
1279
+ deployFormData,
1247
1280
  { timeout: CF_REQUEST_TIMEOUT_MS }
1248
1281
  );
1249
1282
  if (!res.data.success) {
@@ -1297,7 +1330,7 @@ var CloudflarePages = class {
1297
1330
  let totalBytes = 0;
1298
1331
  const fileMap = /* @__PURE__ */ new Map();
1299
1332
  for (const filePath of filePaths) {
1300
- const content = readFileSync4(filePath);
1333
+ const content = readFileSync5(filePath);
1301
1334
  const hash = createHash("sha256").update(content).digest("hex");
1302
1335
  const urlPath = "/" + relative(fullOutputPath, filePath).replace(/\\/g, "/");
1303
1336
  fileMap.set(urlPath, { hash, content });
@@ -1312,14 +1345,20 @@ var CloudflarePages = class {
1312
1345
  for (const [urlPath, { hash }] of fileMap) {
1313
1346
  manifest[urlPath] = hash;
1314
1347
  }
1348
+ if (Object.keys(manifest).length === 0) {
1349
+ throw new DeployError(`Output directory '${outputDir}' contains no files to deploy. Check your build output.`);
1350
+ }
1315
1351
  const deploySpinner = ui.spinner(`Creating preview deployment (branch: ${branch})...`);
1316
1352
  let jwt;
1317
1353
  let requiredFiles;
1318
1354
  let deployment;
1319
1355
  try {
1356
+ const deployFormData = new FormData();
1357
+ deployFormData.append("manifest", JSON.stringify(manifest));
1358
+ deployFormData.append("branch", branch);
1320
1359
  const res = await this.client.post(
1321
1360
  `/accounts/${this.accountId}/pages/projects/${projectName}/deployments`,
1322
- { files: manifest, branch },
1361
+ deployFormData,
1323
1362
  { timeout: CF_REQUEST_TIMEOUT_MS }
1324
1363
  );
1325
1364
  if (!res.data.success) {
@@ -1483,7 +1522,7 @@ function sanitizeProjectName(name) {
1483
1522
  }
1484
1523
 
1485
1524
  // src/commands/fix.ts
1486
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync2, readdirSync as readdirSync3 } from "fs";
1525
+ import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3, readdirSync as readdirSync3 } from "fs";
1487
1526
  import { join as join6 } from "path";
1488
1527
  import { execa as execa2 } from "execa";
1489
1528
  function findMissingModules(errorOutput) {
@@ -1560,12 +1599,12 @@ function applyTsConfigFixes(cwd, fixes) {
1560
1599
  const tsconfigPath = join6(cwd, "tsconfig.json");
1561
1600
  if (!existsSync5(tsconfigPath)) return false;
1562
1601
  try {
1563
- const content = readFileSync5(tsconfigPath, "utf-8");
1602
+ const content = readFileSync6(tsconfigPath, "utf-8");
1564
1603
  const stripped = content.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
1565
1604
  const tsconfig = JSON.parse(stripped);
1566
1605
  if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
1567
1606
  Object.assign(tsconfig.compilerOptions, fixes);
1568
- writeFileSync2(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
1607
+ writeFileSync3(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n", "utf-8");
1569
1608
  return true;
1570
1609
  } catch {
1571
1610
  return false;
@@ -1588,7 +1627,7 @@ function findMissingEnvVars(cwd) {
1588
1627
  scanDir(fullPath, depth + 1);
1589
1628
  } else if (/\.(ts|tsx|js|jsx|mjs|mts|vue|svelte|astro)$/.test(entry.name)) {
1590
1629
  try {
1591
- const content = readFileSync5(fullPath, "utf-8");
1630
+ const content = readFileSync6(fullPath, "utf-8");
1592
1631
  for (const pat of patterns) {
1593
1632
  pat.lastIndex = 0;
1594
1633
  let match;
@@ -1617,7 +1656,7 @@ function generateEnvExample(cwd, vars) {
1617
1656
  const envExamplePath = join6(cwd, ".env.example");
1618
1657
  if (existsSync5(envExamplePath)) return false;
1619
1658
  const content = vars.map((v) => `${v}=`).join("\n") + "\n";
1620
- writeFileSync2(envExamplePath, content, "utf-8");
1659
+ writeFileSync3(envExamplePath, content, "utf-8");
1621
1660
  return true;
1622
1661
  }
1623
1662
  function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
@@ -1626,7 +1665,7 @@ function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
1626
1665
  const nextConfigPath = existsSync5(join6(cwd, "next.config.mjs")) ? join6(cwd, "next.config.mjs") : existsSync5(join6(cwd, "next.config.js")) ? join6(cwd, "next.config.js") : null;
1627
1666
  if (nextConfigPath && errorOutput.includes("output")) {
1628
1667
  try {
1629
- const content = readFileSync5(nextConfigPath, "utf-8");
1668
+ const content = readFileSync6(nextConfigPath, "utf-8");
1630
1669
  if (!content.includes("output")) {
1631
1670
  fixes.push(`Add output: 'export' to ${nextConfigPath.split("/").pop()}`);
1632
1671
  }
@@ -1637,7 +1676,7 @@ function detectFrameworkConfigFixes(errorOutput, cwd, framework) {
1637
1676
  if ((framework === "vite-react" || framework === "vite-vue" || framework === "vite-svelte") && errorOutput.includes("plugin")) {
1638
1677
  const pkgPath = join6(cwd, "package.json");
1639
1678
  try {
1640
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1679
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1641
1680
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1642
1681
  if (framework === "vite-react" && !deps["@vitejs/plugin-react"]) {
1643
1682
  fixes.push("Install @vitejs/plugin-react");
@@ -1710,7 +1749,7 @@ async function runFixHeuristics(errorOutput, cwd, config) {
1710
1749
  }
1711
1750
 
1712
1751
  // src/commands/env.ts
1713
- import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4, writeFileSync as writeFileSync3 } from "fs";
1752
+ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, writeFileSync as writeFileSync4 } from "fs";
1714
1753
  import { join as join7 } from "path";
1715
1754
  function scanSourceForEnvVars(cwd) {
1716
1755
  const found = /* @__PURE__ */ new Set();
@@ -1730,7 +1769,7 @@ function scanSourceForEnvVars(cwd) {
1730
1769
  scanDir(fullPath, depth + 1);
1731
1770
  } else if (/\.(ts|tsx|js|jsx|mjs|mts|vue|svelte|astro)$/.test(entry.name)) {
1732
1771
  try {
1733
- const content = readFileSync6(fullPath, "utf-8");
1772
+ const content = readFileSync7(fullPath, "utf-8");
1734
1773
  for (const pat of patterns) {
1735
1774
  pat.lastIndex = 0;
1736
1775
  let match;
@@ -1769,7 +1808,7 @@ function readEnvFile(cwd) {
1769
1808
  }
1770
1809
  function readFile2(path) {
1771
1810
  try {
1772
- return readFileSync6(path, "utf-8");
1811
+ return readFileSync7(path, "utf-8");
1773
1812
  } catch {
1774
1813
  return null;
1775
1814
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship-em",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "One-command deployment for apps built by AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {