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 +72 -55
- package/dist/cli.js.map +1 -1
- package/dist/lib/scanner/index.js +2 -2
- package/dist/lib/scanner/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
1771
|
-
"",
|
|
1772
|
-
|
|
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
|
-
|
|
1794
|
-
|
|
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
|
|
1807
|
+
async function retryBuild(engine, siteDir, buildError) {
|
|
1807
1808
|
logger.info("Retrying: feeding build error back to AI...");
|
|
1808
|
-
const fixPrompt =
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
2368
|
-
if (!
|
|
2369
|
-
|
|
2370
|
-
if (!
|
|
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 ${
|
|
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
|
-
|
|
2584
|
-
if (!
|
|
2585
|
-
|
|
2586
|
-
|
|
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
|
-
|
|
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.
|
|
2772
|
-
program.command("init", { isDefault: true }).description("Generate a new portfolio site").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-
|
|
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);
|