shipfolio 1.0.7 → 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 +137 -58
- 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/dist/lib/spec/builder.js +2 -1
- package/dist/lib/spec/builder.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
|
}
|
|
@@ -664,6 +664,8 @@ var init_i18n = __esm({
|
|
|
664
664
|
deployTo: "Deploy to:",
|
|
665
665
|
projectNamePrompt: "Project name (used in URL):",
|
|
666
666
|
projectNameInvalid: "Lowercase letters, numbers, and hyphens only",
|
|
667
|
+
customDomainPrompt: "Custom domain (optional, e.g. portfolio.example.com):",
|
|
668
|
+
customDomainInvalid: "Enter a valid domain (e.g. portfolio.example.com)",
|
|
667
669
|
configComplete: "Configuration complete. Generating your site...",
|
|
668
670
|
// Draft
|
|
669
671
|
draftFound: "Found a saved draft from a previous session.",
|
|
@@ -758,6 +760,8 @@ var init_i18n = __esm({
|
|
|
758
760
|
deployTo: "\u90E8\u7F72\u5230:",
|
|
759
761
|
projectNamePrompt: "\u9879\u76EE\u540D\u79F0 (\u7528\u4E8E URL):",
|
|
760
762
|
projectNameInvalid: "\u4EC5\u9650\u5C0F\u5199\u5B57\u6BCD, \u6570\u5B57\u548C\u8FDE\u5B57\u7B26",
|
|
763
|
+
customDomainPrompt: "\u81EA\u5B9A\u4E49\u57DF\u540D (\u53EF\u9009, \u5982 portfolio.example.com):",
|
|
764
|
+
customDomainInvalid: "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u57DF\u540D (\u5982 portfolio.example.com)",
|
|
761
765
|
configComplete: "\u914D\u7F6E\u5B8C\u6210, \u6B63\u5728\u751F\u6210\u7F51\u7AD9...",
|
|
762
766
|
draftFound: "\u53D1\u73B0\u4E0A\u6B21\u672A\u5B8C\u6210\u7684\u914D\u7F6E\u8349\u7A3F.",
|
|
763
767
|
draftLoadPrompt: "\u662F\u5426\u52A0\u8F7D\u8349\u7A3F? (\u8DF3\u8FC7\u91CD\u590D\u586B\u5199)",
|
|
@@ -1181,6 +1185,7 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
1181
1185
|
});
|
|
1182
1186
|
handleCancel(deployPlatform);
|
|
1183
1187
|
let projectName = "";
|
|
1188
|
+
let customDomain = "";
|
|
1184
1189
|
if (deployPlatform !== "local") {
|
|
1185
1190
|
projectName = await p.text({
|
|
1186
1191
|
message: t().projectNamePrompt,
|
|
@@ -1188,6 +1193,12 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
1188
1193
|
validate: (v) => /^[a-z0-9-]+$/.test(v) ? void 0 : t().projectNameInvalid
|
|
1189
1194
|
});
|
|
1190
1195
|
handleCancel(projectName);
|
|
1196
|
+
customDomain = await p.text({
|
|
1197
|
+
message: t().customDomainPrompt,
|
|
1198
|
+
placeholder: "portfolio.example.com",
|
|
1199
|
+
defaultValue: ""
|
|
1200
|
+
});
|
|
1201
|
+
handleCancel(customDomain);
|
|
1191
1202
|
}
|
|
1192
1203
|
p.outro(t().configComplete);
|
|
1193
1204
|
return {
|
|
@@ -1198,7 +1209,8 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
1198
1209
|
engine,
|
|
1199
1210
|
deploy: {
|
|
1200
1211
|
platform: deployPlatform,
|
|
1201
|
-
projectName
|
|
1212
|
+
projectName,
|
|
1213
|
+
customDomain: customDomain || void 0
|
|
1202
1214
|
}
|
|
1203
1215
|
};
|
|
1204
1216
|
}
|
|
@@ -1225,7 +1237,8 @@ function buildSpec(interview) {
|
|
|
1225
1237
|
sections: interview.sections,
|
|
1226
1238
|
deploy: {
|
|
1227
1239
|
platform: interview.deploy.platform,
|
|
1228
|
-
projectName: interview.deploy.projectName
|
|
1240
|
+
projectName: interview.deploy.projectName,
|
|
1241
|
+
customDomain: interview.deploy.customDomain
|
|
1229
1242
|
}
|
|
1230
1243
|
};
|
|
1231
1244
|
}
|
|
@@ -1754,9 +1767,9 @@ async function generateSite(engine, prompt, outputDir) {
|
|
|
1754
1767
|
"The previous generation was incomplete. The following required files are missing:",
|
|
1755
1768
|
...validation.errors.map((e) => `- ${e}`),
|
|
1756
1769
|
"",
|
|
1757
|
-
"Please create ALL missing files
|
|
1758
|
-
"",
|
|
1759
|
-
|
|
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."
|
|
1760
1773
|
].join("\n");
|
|
1761
1774
|
await runEngine(engine, fixPrompt, outputDir);
|
|
1762
1775
|
validation = await validateGeneratedSite(outputDir);
|
|
@@ -1777,8 +1790,9 @@ async function buildSite(siteDir) {
|
|
|
1777
1790
|
spinner.succeed("Site built successfully");
|
|
1778
1791
|
} catch (error) {
|
|
1779
1792
|
spinner.fail("Build failed");
|
|
1780
|
-
|
|
1781
|
-
|
|
1793
|
+
const errorMsg = error.stderr || error.message || String(error);
|
|
1794
|
+
logger.error(errorMsg);
|
|
1795
|
+
return { success: false, error: errorMsg };
|
|
1782
1796
|
}
|
|
1783
1797
|
const validation = await validateBuildOutput(siteDir);
|
|
1784
1798
|
if (!validation.valid) {
|
|
@@ -1786,23 +1800,26 @@ async function buildSite(siteDir) {
|
|
|
1786
1800
|
for (const err of validation.errors) {
|
|
1787
1801
|
logger.error(` ${err}`);
|
|
1788
1802
|
}
|
|
1789
|
-
return false;
|
|
1803
|
+
return { success: false, error: validation.errors.join("\n") };
|
|
1790
1804
|
}
|
|
1791
|
-
return true;
|
|
1805
|
+
return { success: true };
|
|
1792
1806
|
}
|
|
1793
|
-
async function retryBuild(engine, siteDir, buildError
|
|
1807
|
+
async function retryBuild(engine, siteDir, buildError) {
|
|
1794
1808
|
logger.info("Retrying: feeding build error back to AI...");
|
|
1795
|
-
const fixPrompt =
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
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");
|
|
1801
1818
|
try {
|
|
1802
1819
|
await runEngine(engine, fixPrompt, siteDir);
|
|
1803
1820
|
return await buildSite(siteDir);
|
|
1804
1821
|
} catch {
|
|
1805
|
-
return false;
|
|
1822
|
+
return { success: false, error: "Retry generation failed" };
|
|
1806
1823
|
}
|
|
1807
1824
|
}
|
|
1808
1825
|
var init_orchestrator = __esm({
|
|
@@ -1880,7 +1897,26 @@ async function ensureProject(projectName) {
|
|
|
1880
1897
|
} catch {
|
|
1881
1898
|
}
|
|
1882
1899
|
}
|
|
1883
|
-
async function
|
|
1900
|
+
async function addCustomDomain(projectName, domain) {
|
|
1901
|
+
try {
|
|
1902
|
+
const whoami = await runWithOutput("npx", ["wrangler", "whoami"]);
|
|
1903
|
+
const accountMatch = whoami.match(/([0-9a-f]{32})/);
|
|
1904
|
+
if (!accountMatch) {
|
|
1905
|
+
logger.warn("Could not detect Cloudflare account ID for custom domain setup.");
|
|
1906
|
+
return false;
|
|
1907
|
+
}
|
|
1908
|
+
const accountId = accountMatch[1];
|
|
1909
|
+
logger.blank();
|
|
1910
|
+
logger.info(`To add custom domain "${domain}" to your Cloudflare Pages project:`);
|
|
1911
|
+
logger.plain(` 1. Go to https://dash.cloudflare.com/${accountId}/pages/${projectName}/custom-domains`);
|
|
1912
|
+
logger.plain(` 2. Click "Set up a custom domain" and enter: ${domain}`);
|
|
1913
|
+
logger.plain(` 3. Add the CNAME record as instructed`);
|
|
1914
|
+
return false;
|
|
1915
|
+
} catch {
|
|
1916
|
+
return false;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
async function deployToCloudflare(distDir, projectName, customDomain) {
|
|
1884
1920
|
const spinner = ora6("Deploying to Cloudflare Pages...").start();
|
|
1885
1921
|
try {
|
|
1886
1922
|
spinner.text = "Creating Cloudflare Pages project...";
|
|
@@ -1899,6 +1935,9 @@ async function deployToCloudflare(distDir, projectName) {
|
|
|
1899
1935
|
const url = urlMatch?.[0] || `https://${projectName}.pages.dev`;
|
|
1900
1936
|
spinner.succeed(`Deployed to Cloudflare Pages`);
|
|
1901
1937
|
logger.info(`URL: ${url}`);
|
|
1938
|
+
if (customDomain) {
|
|
1939
|
+
await addCustomDomain(projectName, customDomain);
|
|
1940
|
+
}
|
|
1902
1941
|
return url;
|
|
1903
1942
|
} catch (error) {
|
|
1904
1943
|
spinner.fail("Cloudflare Pages deployment failed");
|
|
@@ -1916,7 +1955,7 @@ var init_cloudflare = __esm({
|
|
|
1916
1955
|
|
|
1917
1956
|
// src/deployer/vercel.ts
|
|
1918
1957
|
import ora7 from "ora";
|
|
1919
|
-
async function deployToVercel(distDir) {
|
|
1958
|
+
async function deployToVercel(distDir, customDomain) {
|
|
1920
1959
|
const spinner = ora7("Deploying to Vercel...").start();
|
|
1921
1960
|
try {
|
|
1922
1961
|
const output = await runWithOutput("npx", [
|
|
@@ -1932,6 +1971,24 @@ async function deployToVercel(distDir) {
|
|
|
1932
1971
|
const url = urlMatch?.[0] || output.trim().split("\n").pop() || "";
|
|
1933
1972
|
spinner.succeed("Deployed to Vercel");
|
|
1934
1973
|
logger.info(`URL: ${url}`);
|
|
1974
|
+
if (customDomain) {
|
|
1975
|
+
spinner.start(`Adding custom domain: ${customDomain}...`);
|
|
1976
|
+
try {
|
|
1977
|
+
await runWithOutput("npx", [
|
|
1978
|
+
"vercel",
|
|
1979
|
+
"domains",
|
|
1980
|
+
"add",
|
|
1981
|
+
customDomain
|
|
1982
|
+
]);
|
|
1983
|
+
spinner.succeed(`Custom domain added: ${customDomain}`);
|
|
1984
|
+
logger.blank();
|
|
1985
|
+
logger.info("Add a CNAME record pointing to cname.vercel-dns.com in your DNS provider.");
|
|
1986
|
+
logger.info(`Your site will be available at: https://${customDomain}`);
|
|
1987
|
+
} catch (err) {
|
|
1988
|
+
spinner.warn(`Could not add domain automatically: ${err.message || err}`);
|
|
1989
|
+
logger.info(`Add it manually: npx vercel domains add ${customDomain}`);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1935
1992
|
return url;
|
|
1936
1993
|
} catch (error) {
|
|
1937
1994
|
spinner.fail("Vercel deployment failed");
|
|
@@ -2081,7 +2138,7 @@ var init_github = __esm({
|
|
|
2081
2138
|
|
|
2082
2139
|
// src/deployer/index.ts
|
|
2083
2140
|
import { join as join3 } from "path";
|
|
2084
|
-
async function deploy(siteDir, platform, projectName) {
|
|
2141
|
+
async function deploy(siteDir, platform, projectName, customDomain) {
|
|
2085
2142
|
if (platform === "local") {
|
|
2086
2143
|
logger.info(`Site built at ${join3(siteDir, "out/")}`);
|
|
2087
2144
|
logger.info("Run `npx serve ./out` in the site directory to preview.");
|
|
@@ -2091,9 +2148,9 @@ async function deploy(siteDir, platform, projectName) {
|
|
|
2091
2148
|
const distDir = join3(siteDir, "out");
|
|
2092
2149
|
let url;
|
|
2093
2150
|
if (platform === "cloudflare") {
|
|
2094
|
-
url = await deployToCloudflare(distDir, projectName);
|
|
2151
|
+
url = await deployToCloudflare(distDir, projectName, customDomain);
|
|
2095
2152
|
} else {
|
|
2096
|
-
url = await deployToVercel(distDir);
|
|
2153
|
+
url = await deployToVercel(distDir, customDomain);
|
|
2097
2154
|
}
|
|
2098
2155
|
await setupGitHubAutoDeploy(siteDir, projectName, platform);
|
|
2099
2156
|
return url;
|
|
@@ -2180,12 +2237,22 @@ async function startPreviewServer(siteDir, port = 3e3) {
|
|
|
2180
2237
|
cleanUrls: true
|
|
2181
2238
|
});
|
|
2182
2239
|
});
|
|
2183
|
-
return new Promise((resolve7
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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);
|
|
2189
2256
|
});
|
|
2190
2257
|
}
|
|
2191
2258
|
var init_pdf = __esm({
|
|
@@ -2200,7 +2267,7 @@ var init_pdf = __esm({
|
|
|
2200
2267
|
import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir3, unlink } from "fs/promises";
|
|
2201
2268
|
import { join as join5 } from "path";
|
|
2202
2269
|
import { homedir } from "os";
|
|
2203
|
-
async function saveDraft(interviewResult) {
|
|
2270
|
+
async function saveDraft(interviewResult, silent = false) {
|
|
2204
2271
|
try {
|
|
2205
2272
|
await mkdir3(DRAFT_DIR, { recursive: true });
|
|
2206
2273
|
const draft = {
|
|
@@ -2208,7 +2275,9 @@ async function saveDraft(interviewResult) {
|
|
|
2208
2275
|
interviewResult
|
|
2209
2276
|
};
|
|
2210
2277
|
await writeFile3(DRAFT_FILE, JSON.stringify(draft, null, 2), "utf-8");
|
|
2211
|
-
|
|
2278
|
+
if (!silent) {
|
|
2279
|
+
logger.info(t().draftSaved);
|
|
2280
|
+
}
|
|
2212
2281
|
} catch {
|
|
2213
2282
|
}
|
|
2214
2283
|
}
|
|
@@ -2220,10 +2289,12 @@ async function loadDraft() {
|
|
|
2220
2289
|
return null;
|
|
2221
2290
|
}
|
|
2222
2291
|
}
|
|
2223
|
-
async function clearDraft() {
|
|
2292
|
+
async function clearDraft(silent = false) {
|
|
2224
2293
|
try {
|
|
2225
2294
|
await unlink(DRAFT_FILE);
|
|
2226
|
-
|
|
2295
|
+
if (!silent) {
|
|
2296
|
+
logger.info(t().draftCleared);
|
|
2297
|
+
}
|
|
2227
2298
|
} catch {
|
|
2228
2299
|
}
|
|
2229
2300
|
}
|
|
@@ -2248,7 +2319,7 @@ import { resolve } from "path";
|
|
|
2248
2319
|
import { join as join6 } from "path";
|
|
2249
2320
|
import * as p3 from "@clack/prompts";
|
|
2250
2321
|
async function initCommand(options) {
|
|
2251
|
-
logger.header("shipfolio v1.0.
|
|
2322
|
+
logger.header("shipfolio v1.0.9");
|
|
2252
2323
|
logger.info("Detecting AI engines...");
|
|
2253
2324
|
const engines = await detectEngines();
|
|
2254
2325
|
const availableTypes = getAvailableEngineTypes(engines);
|
|
@@ -2299,16 +2370,16 @@ async function initCommand(options) {
|
|
|
2299
2370
|
} else {
|
|
2300
2371
|
interviewResult = await runInterview(scannedProjects, availableTypes);
|
|
2301
2372
|
}
|
|
2302
|
-
await saveDraft(interviewResult);
|
|
2373
|
+
await saveDraft(interviewResult, true);
|
|
2303
2374
|
const spec = buildSpec(interviewResult);
|
|
2304
2375
|
const prompt = await buildFreshPrompt(spec);
|
|
2305
2376
|
const outputDir = resolve(options.output || "./shipfolio-site");
|
|
2306
2377
|
await ensureDir(outputDir);
|
|
2307
2378
|
await generateSite(spec.engine, prompt, outputDir);
|
|
2308
|
-
let
|
|
2309
|
-
if (!
|
|
2310
|
-
|
|
2311
|
-
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) {
|
|
2312
2383
|
logger.error("Build failed after retry. Check the output directory for details.");
|
|
2313
2384
|
process.exit(1);
|
|
2314
2385
|
}
|
|
@@ -2344,7 +2415,8 @@ async function initCommand(options) {
|
|
|
2344
2415
|
const url = await deploy(
|
|
2345
2416
|
outputDir,
|
|
2346
2417
|
spec.deploy.platform,
|
|
2347
|
-
spec.deploy.projectName
|
|
2418
|
+
spec.deploy.projectName,
|
|
2419
|
+
spec.deploy.customDomain
|
|
2348
2420
|
);
|
|
2349
2421
|
if (url) {
|
|
2350
2422
|
spec.deploy.url = url;
|
|
@@ -2358,7 +2430,7 @@ async function initCommand(options) {
|
|
|
2358
2430
|
...spec,
|
|
2359
2431
|
sitePath: outputDir
|
|
2360
2432
|
});
|
|
2361
|
-
await clearDraft();
|
|
2433
|
+
await clearDraft(true);
|
|
2362
2434
|
logger.header("Done.");
|
|
2363
2435
|
if (spec.deploy.url) {
|
|
2364
2436
|
logger.info(`Site: ${spec.deploy.url}`);
|
|
@@ -2396,7 +2468,7 @@ import { Command } from "commander";
|
|
|
2396
2468
|
// src/commands/update.ts
|
|
2397
2469
|
init_esm_shims();
|
|
2398
2470
|
init_scanner();
|
|
2399
|
-
import { resolve as resolve2 } from "path";
|
|
2471
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
2400
2472
|
|
|
2401
2473
|
// src/spec/diff.ts
|
|
2402
2474
|
init_esm_shims();
|
|
@@ -2467,8 +2539,9 @@ async function updateCommand(options) {
|
|
|
2467
2539
|
return;
|
|
2468
2540
|
}
|
|
2469
2541
|
const config = await readJson(configPath);
|
|
2542
|
+
const generatedDate = config.generatedAt?.slice(0, 10) || "unknown";
|
|
2470
2543
|
logger.info(
|
|
2471
|
-
`Site: ${config.deploy.projectName || "local"} (generated ${
|
|
2544
|
+
`Site: ${config.deploy.projectName || "local"} (generated ${generatedDate})`
|
|
2472
2545
|
);
|
|
2473
2546
|
logger.info(`Theme: ${config.style.theme}`);
|
|
2474
2547
|
logger.info(`Projects: ${config.projects.length}`);
|
|
@@ -2478,11 +2551,7 @@ async function updateCommand(options) {
|
|
|
2478
2551
|
logger.error("No AI engine found. See `npx shipfolio` for install instructions.");
|
|
2479
2552
|
process.exit(1);
|
|
2480
2553
|
}
|
|
2481
|
-
const scanDirs = options.scan?.map((d) => resolve2(d)) || [...new Set(config.projects.map((p6) =>
|
|
2482
|
-
const parts = p6.localPath.split("/");
|
|
2483
|
-
parts.pop();
|
|
2484
|
-
return parts.join("/");
|
|
2485
|
-
}))];
|
|
2554
|
+
const scanDirs = options.scan?.map((d) => resolve2(d)) || [...new Set(config.projects.map((p6) => dirname2(p6.localPath)))];
|
|
2486
2555
|
const scannedProjects = await scanProjects(scanDirs);
|
|
2487
2556
|
const diff = computeDiff(config, scannedProjects);
|
|
2488
2557
|
logger.header("Changes detected:");
|
|
@@ -2520,10 +2589,13 @@ async function updateCommand(options) {
|
|
|
2520
2589
|
const engine = availableTypes.includes(config.engine) ? config.engine : availableTypes[0];
|
|
2521
2590
|
const prompt = await buildUpdatePrompt(config, diff);
|
|
2522
2591
|
await generateSite(engine, prompt, siteDir);
|
|
2523
|
-
|
|
2524
|
-
if (!
|
|
2525
|
-
|
|
2526
|
-
|
|
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
|
+
}
|
|
2527
2599
|
}
|
|
2528
2600
|
if (!options.noPreview) {
|
|
2529
2601
|
const server = await startPreviewServer(siteDir);
|
|
@@ -2545,7 +2617,12 @@ async function updateCommand(options) {
|
|
|
2545
2617
|
}
|
|
2546
2618
|
}
|
|
2547
2619
|
if (!options.noDeploy && config.deploy.platform !== "local") {
|
|
2548
|
-
|
|
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
|
+
}
|
|
2549
2626
|
}
|
|
2550
2627
|
const updatedConfig = {
|
|
2551
2628
|
...config,
|
|
@@ -2595,7 +2672,7 @@ async function specCommand(options) {
|
|
|
2595
2672
|
logger.header("shipfolio spec");
|
|
2596
2673
|
const engines = await detectEngines();
|
|
2597
2674
|
const availableTypes = getAvailableEngineTypes(engines);
|
|
2598
|
-
const engineTypes = availableTypes.length > 0 ? availableTypes : ["claude"];
|
|
2675
|
+
const engineTypes = availableTypes.length > 0 ? availableTypes : ["claude", "codex", "v0"];
|
|
2599
2676
|
const scanDirs = options.scan?.map((d) => resolve3(d)) || [
|
|
2600
2677
|
resolve3(process.cwd())
|
|
2601
2678
|
];
|
|
@@ -2636,10 +2713,12 @@ async function deployCommand(options) {
|
|
|
2636
2713
|
const configPath = join(siteDir, "shipfolio.config.json");
|
|
2637
2714
|
let platform;
|
|
2638
2715
|
let projectName;
|
|
2716
|
+
let customDomain;
|
|
2639
2717
|
if (await fileExists(configPath)) {
|
|
2640
2718
|
const config = await readJson(configPath);
|
|
2641
2719
|
platform = options.platform || config.deploy.platform;
|
|
2642
2720
|
projectName = config.deploy.projectName;
|
|
2721
|
+
customDomain = config.deploy.customDomain;
|
|
2643
2722
|
} else {
|
|
2644
2723
|
platform = options.platform || "cloudflare";
|
|
2645
2724
|
projectName = await p5.text({
|
|
@@ -2657,7 +2736,7 @@ async function deployCommand(options) {
|
|
|
2657
2736
|
logger.error("Build output not found. Run `npm run build` in the site directory first.");
|
|
2658
2737
|
process.exit(1);
|
|
2659
2738
|
}
|
|
2660
|
-
await deploy(siteDir, platform, projectName);
|
|
2739
|
+
await deploy(siteDir, platform, projectName, customDomain);
|
|
2661
2740
|
}
|
|
2662
2741
|
|
|
2663
2742
|
// src/commands/pdf.ts
|
|
@@ -2706,8 +2785,8 @@ initLocale();
|
|
|
2706
2785
|
var program = new Command();
|
|
2707
2786
|
program.name("shipfolio").description(
|
|
2708
2787
|
"Generate and deploy your personal portfolio site from local projects using AI"
|
|
2709
|
-
).version("1.0.
|
|
2710
|
-
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);
|
|
2711
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);
|
|
2712
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);
|
|
2713
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);
|