shipfolio 1.0.8 → 1.0.9

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/dist/cli.js CHANGED
@@ -378,10 +378,10 @@ function extractFirstParagraph(readme) {
378
378
  continue;
379
379
  }
380
380
  if (collecting && trimmed === "" && paragraphLines.length > 0) break;
381
- if (collecting && trimmed !== "") {
381
+ if (collecting && trimmed !== "" && !trimmed.startsWith("![") && !trimmed.startsWith("[![")) {
382
382
  paragraphLines.push(trimmed);
383
383
  }
384
- if (!collecting && trimmed !== "" && !trimmed.startsWith("[")) {
384
+ if (!collecting && trimmed !== "" && !trimmed.startsWith("[") && !trimmed.startsWith("!") && !trimmed.startsWith("[![")) {
385
385
  paragraphLines.push(trimmed);
386
386
  collecting = true;
387
387
  }
@@ -1767,9 +1767,9 @@ async function generateSite(engine, prompt, outputDir) {
1767
1767
  "The previous generation was incomplete. The following required files are missing:",
1768
1768
  ...validation.errors.map((e) => `- ${e}`),
1769
1769
  "",
1770
- "Please create ALL missing files. Here is the original specification:",
1771
- "",
1772
- prompt
1770
+ "Please create ALL missing files in the current working directory.",
1771
+ "This is a Next.js 15 + Tailwind CSS + shadcn/ui static site (output: 'export').",
1772
+ "npm install && npm run build must succeed."
1773
1773
  ].join("\n");
1774
1774
  await runEngine(engine, fixPrompt, outputDir);
1775
1775
  validation = await validateGeneratedSite(outputDir);
@@ -1790,8 +1790,9 @@ async function buildSite(siteDir) {
1790
1790
  spinner.succeed("Site built successfully");
1791
1791
  } catch (error) {
1792
1792
  spinner.fail("Build failed");
1793
- logger.error(error.stderr || error.message || String(error));
1794
- return false;
1793
+ const errorMsg = error.stderr || error.message || String(error);
1794
+ logger.error(errorMsg);
1795
+ return { success: false, error: errorMsg };
1795
1796
  }
1796
1797
  const validation = await validateBuildOutput(siteDir);
1797
1798
  if (!validation.valid) {
@@ -1799,23 +1800,26 @@ async function buildSite(siteDir) {
1799
1800
  for (const err of validation.errors) {
1800
1801
  logger.error(` ${err}`);
1801
1802
  }
1802
- return false;
1803
+ return { success: false, error: validation.errors.join("\n") };
1803
1804
  }
1804
- return true;
1805
+ return { success: true };
1805
1806
  }
1806
- async function retryBuild(engine, siteDir, buildError, originalPrompt) {
1807
+ async function retryBuild(engine, siteDir, buildError) {
1807
1808
  logger.info("Retrying: feeding build error back to AI...");
1808
- const fixPrompt = `The site generation produced build errors. Fix the following errors without changing the overall design or structure:
1809
-
1810
- ${buildError}
1811
-
1812
- Original prompt for context:
1813
- ${originalPrompt.slice(0, 2e3)}`;
1809
+ const fixPrompt = [
1810
+ "The site build failed with the following errors. Fix them without changing the design or structure.",
1811
+ "",
1812
+ "Build errors:",
1813
+ buildError.slice(0, 4e3),
1814
+ "",
1815
+ "Fix the errors in the existing files in the current working directory.",
1816
+ "Do not recreate files that already work. Only fix what is broken."
1817
+ ].join("\n");
1814
1818
  try {
1815
1819
  await runEngine(engine, fixPrompt, siteDir);
1816
1820
  return await buildSite(siteDir);
1817
1821
  } catch {
1818
- return false;
1822
+ return { success: false, error: "Retry generation failed" };
1819
1823
  }
1820
1824
  }
1821
1825
  var init_orchestrator = __esm({
@@ -1902,12 +1906,6 @@ async function addCustomDomain(projectName, domain) {
1902
1906
  return false;
1903
1907
  }
1904
1908
  const accountId = accountMatch[1];
1905
- await runWithOutput("npx", [
1906
- "wrangler",
1907
- "pages",
1908
- "project",
1909
- "list"
1910
- ]);
1911
1909
  logger.blank();
1912
1910
  logger.info(`To add custom domain "${domain}" to your Cloudflare Pages project:`);
1913
1911
  logger.plain(` 1. Go to https://dash.cloudflare.com/${accountId}/pages/${projectName}/custom-domains`);
@@ -2239,12 +2237,22 @@ async function startPreviewServer(siteDir, port = 3e3) {
2239
2237
  cleanUrls: true
2240
2238
  });
2241
2239
  });
2242
- return new Promise((resolve7, reject) => {
2243
- server.listen(port, () => {
2244
- logger.info(`Preview: http://localhost:${port}`);
2245
- resolve7(server);
2246
- });
2247
- server.on("error", reject);
2240
+ return new Promise((resolve7) => {
2241
+ const tryListen = (p6) => {
2242
+ server.once("error", (err) => {
2243
+ if (err.code === "EADDRINUSE") {
2244
+ tryListen(0);
2245
+ } else {
2246
+ tryListen(0);
2247
+ }
2248
+ });
2249
+ server.listen(p6, () => {
2250
+ const actualPort = server.address()?.port || p6;
2251
+ logger.info(`Preview: http://localhost:${actualPort}`);
2252
+ resolve7(server);
2253
+ });
2254
+ };
2255
+ tryListen(port);
2248
2256
  });
2249
2257
  }
2250
2258
  var init_pdf = __esm({
@@ -2259,7 +2267,7 @@ var init_pdf = __esm({
2259
2267
  import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir3, unlink } from "fs/promises";
2260
2268
  import { join as join5 } from "path";
2261
2269
  import { homedir } from "os";
2262
- async function saveDraft(interviewResult) {
2270
+ async function saveDraft(interviewResult, silent = false) {
2263
2271
  try {
2264
2272
  await mkdir3(DRAFT_DIR, { recursive: true });
2265
2273
  const draft = {
@@ -2267,7 +2275,9 @@ async function saveDraft(interviewResult) {
2267
2275
  interviewResult
2268
2276
  };
2269
2277
  await writeFile3(DRAFT_FILE, JSON.stringify(draft, null, 2), "utf-8");
2270
- logger.info(t().draftSaved);
2278
+ if (!silent) {
2279
+ logger.info(t().draftSaved);
2280
+ }
2271
2281
  } catch {
2272
2282
  }
2273
2283
  }
@@ -2279,10 +2289,12 @@ async function loadDraft() {
2279
2289
  return null;
2280
2290
  }
2281
2291
  }
2282
- async function clearDraft() {
2292
+ async function clearDraft(silent = false) {
2283
2293
  try {
2284
2294
  await unlink(DRAFT_FILE);
2285
- logger.info(t().draftCleared);
2295
+ if (!silent) {
2296
+ logger.info(t().draftCleared);
2297
+ }
2286
2298
  } catch {
2287
2299
  }
2288
2300
  }
@@ -2307,7 +2319,7 @@ import { resolve } from "path";
2307
2319
  import { join as join6 } from "path";
2308
2320
  import * as p3 from "@clack/prompts";
2309
2321
  async function initCommand(options) {
2310
- logger.header("shipfolio v1.0.8");
2322
+ logger.header("shipfolio v1.0.9");
2311
2323
  logger.info("Detecting AI engines...");
2312
2324
  const engines = await detectEngines();
2313
2325
  const availableTypes = getAvailableEngineTypes(engines);
@@ -2358,16 +2370,16 @@ async function initCommand(options) {
2358
2370
  } else {
2359
2371
  interviewResult = await runInterview(scannedProjects, availableTypes);
2360
2372
  }
2361
- await saveDraft(interviewResult);
2373
+ await saveDraft(interviewResult, true);
2362
2374
  const spec = buildSpec(interviewResult);
2363
2375
  const prompt = await buildFreshPrompt(spec);
2364
2376
  const outputDir = resolve(options.output || "./shipfolio-site");
2365
2377
  await ensureDir(outputDir);
2366
2378
  await generateSite(spec.engine, prompt, outputDir);
2367
- let buildSuccess = await buildSite(outputDir);
2368
- if (!buildSuccess) {
2369
- buildSuccess = await retryBuild(spec.engine, outputDir, "Build failed. Check missing files and fix errors.", prompt);
2370
- if (!buildSuccess) {
2379
+ let buildResult = await buildSite(outputDir);
2380
+ if (!buildResult.success) {
2381
+ buildResult = await retryBuild(spec.engine, outputDir, buildResult.error || "Unknown build error");
2382
+ if (!buildResult.success) {
2371
2383
  logger.error("Build failed after retry. Check the output directory for details.");
2372
2384
  process.exit(1);
2373
2385
  }
@@ -2418,7 +2430,7 @@ async function initCommand(options) {
2418
2430
  ...spec,
2419
2431
  sitePath: outputDir
2420
2432
  });
2421
- await clearDraft();
2433
+ await clearDraft(true);
2422
2434
  logger.header("Done.");
2423
2435
  if (spec.deploy.url) {
2424
2436
  logger.info(`Site: ${spec.deploy.url}`);
@@ -2456,7 +2468,7 @@ import { Command } from "commander";
2456
2468
  // src/commands/update.ts
2457
2469
  init_esm_shims();
2458
2470
  init_scanner();
2459
- import { resolve as resolve2 } from "path";
2471
+ import { resolve as resolve2, dirname as dirname2 } from "path";
2460
2472
 
2461
2473
  // src/spec/diff.ts
2462
2474
  init_esm_shims();
@@ -2527,8 +2539,9 @@ async function updateCommand(options) {
2527
2539
  return;
2528
2540
  }
2529
2541
  const config = await readJson(configPath);
2542
+ const generatedDate = config.generatedAt?.slice(0, 10) || "unknown";
2530
2543
  logger.info(
2531
- `Site: ${config.deploy.projectName || "local"} (generated ${config.generatedAt.slice(0, 10)})`
2544
+ `Site: ${config.deploy.projectName || "local"} (generated ${generatedDate})`
2532
2545
  );
2533
2546
  logger.info(`Theme: ${config.style.theme}`);
2534
2547
  logger.info(`Projects: ${config.projects.length}`);
@@ -2538,11 +2551,7 @@ async function updateCommand(options) {
2538
2551
  logger.error("No AI engine found. See `npx shipfolio` for install instructions.");
2539
2552
  process.exit(1);
2540
2553
  }
2541
- const scanDirs = options.scan?.map((d) => resolve2(d)) || [...new Set(config.projects.map((p6) => {
2542
- const parts = p6.localPath.split("/");
2543
- parts.pop();
2544
- return parts.join("/");
2545
- }))];
2554
+ const scanDirs = options.scan?.map((d) => resolve2(d)) || [...new Set(config.projects.map((p6) => dirname2(p6.localPath)))];
2546
2555
  const scannedProjects = await scanProjects(scanDirs);
2547
2556
  const diff = computeDiff(config, scannedProjects);
2548
2557
  logger.header("Changes detected:");
@@ -2580,10 +2589,13 @@ async function updateCommand(options) {
2580
2589
  const engine = availableTypes.includes(config.engine) ? config.engine : availableTypes[0];
2581
2590
  const prompt = await buildUpdatePrompt(config, diff);
2582
2591
  await generateSite(engine, prompt, siteDir);
2583
- const buildSuccess = await buildSite(siteDir);
2584
- if (!buildSuccess) {
2585
- logger.error("Build failed. Check the site directory.");
2586
- process.exit(1);
2592
+ let buildResult = await buildSite(siteDir);
2593
+ if (!buildResult.success) {
2594
+ buildResult = await retryBuild(engine, siteDir, buildResult.error || "Unknown build error");
2595
+ if (!buildResult.success) {
2596
+ logger.error("Build failed after retry. Check the site directory.");
2597
+ process.exit(1);
2598
+ }
2587
2599
  }
2588
2600
  if (!options.noPreview) {
2589
2601
  const server = await startPreviewServer(siteDir);
@@ -2605,7 +2617,12 @@ async function updateCommand(options) {
2605
2617
  }
2606
2618
  }
2607
2619
  if (!options.noDeploy && config.deploy.platform !== "local") {
2608
- await deploy(siteDir, config.deploy.platform, config.deploy.projectName);
2620
+ try {
2621
+ await deploy(siteDir, config.deploy.platform, config.deploy.projectName, config.deploy.customDomain);
2622
+ } catch (err) {
2623
+ logger.error(`Deployment failed: ${err.message || err}`);
2624
+ logger.info("You can retry later with: npx shipfolio deploy --site " + siteDir);
2625
+ }
2609
2626
  }
2610
2627
  const updatedConfig = {
2611
2628
  ...config,
@@ -2655,7 +2672,7 @@ async function specCommand(options) {
2655
2672
  logger.header("shipfolio spec");
2656
2673
  const engines = await detectEngines();
2657
2674
  const availableTypes = getAvailableEngineTypes(engines);
2658
- const engineTypes = availableTypes.length > 0 ? availableTypes : ["claude"];
2675
+ const engineTypes = availableTypes.length > 0 ? availableTypes : ["claude", "codex", "v0"];
2659
2676
  const scanDirs = options.scan?.map((d) => resolve3(d)) || [
2660
2677
  resolve3(process.cwd())
2661
2678
  ];
@@ -2768,8 +2785,8 @@ initLocale();
2768
2785
  var program = new Command();
2769
2786
  program.name("shipfolio").description(
2770
2787
  "Generate and deploy your personal portfolio site from local projects using AI"
2771
- ).version("1.0.8");
2772
- program.command("init", { isDefault: true }).description("Generate a new portfolio site").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-e, --engine <engine>", "AI engine: claude | codex | v0").option("-d, --deploy <platform>", "Deploy target: cloudflare | vercel | local").option("--style <theme>", "Theme: dark-minimal | light-clean | monochrome").option("--accent <hex>", "Accent color (hex)").option("--auto", "Skip prompts, use defaults").option("-o, --output <dir>", "Output directory", "./shipfolio-site").option("--no-pdf", "Skip PDF export").option("--no-preview", "Skip local preview").option("-v, --verbose", "Verbose output").action(initCommand);
2788
+ ).version("1.0.9");
2789
+ program.command("init", { isDefault: true }).description("Generate a new portfolio site").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-o, --output <dir>", "Output directory", "./shipfolio-site").option("--no-pdf", "Skip PDF export").option("--no-preview", "Skip local preview").action(initCommand);
2773
2790
  program.command("update").description("Update an existing portfolio site").requiredOption("--site <path>", "Path to existing site directory").option("-s, --scan <dirs...>", "Directories to scan for projects").option("--no-pdf", "Skip PDF export").option("--no-preview", "Skip preview").option("--no-deploy", "Skip deployment").action(updateCommand);
2774
2791
  program.command("spec").description("Generate spec and prompt files only").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-o, --output <dir>", "Output directory for spec files", ".").action(specCommand);
2775
2792
  program.command("deploy").description("Deploy an existing built site").requiredOption("--site <path>", "Path to site directory").option("-p, --platform <platform>", "Deploy platform: cloudflare | vercel").action(deployCommand);