appfunnel 0.12.0 → 0.14.0
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/index.js +216 -116
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/templates/default/appfunnel.config.ts +0 -17
- package/templates/default/gitignore +0 -3
- package/templates/default/locales/en.json +0 -3
- package/templates/default/src/app.css +0 -1
- package/templates/default/src/components/ConsentDrawer.tsx +0 -70
- package/templates/default/src/components/Header.tsx +0 -37
- package/templates/default/src/components/paywall/PaymentCheckoutDialog.tsx +0 -76
- package/templates/default/src/funnel.tsx +0 -9
- package/templates/default/src/pages/birthday.tsx +0 -66
- package/templates/default/src/pages/download.tsx +0 -67
- package/templates/default/src/pages/email.tsx +0 -95
- package/templates/default/src/pages/intro.tsx +0 -109
- package/templates/default/src/pages/multi-select.tsx +0 -79
- package/templates/default/src/pages/name.tsx +0 -48
- package/templates/default/src/pages/paywall.tsx +0 -191
- package/templates/default/src/pages/single-select.tsx +0 -61
- package/templates/default/src/pages/upsell.tsx +0 -158
- package/templates/default/template.json +0 -10
- package/templates/default/tsconfig.json +0 -16
package/dist/index.js
CHANGED
|
@@ -231,49 +231,21 @@ async function fetchStorePrices(projectId, storeId, options) {
|
|
|
231
231
|
const data = await response.json();
|
|
232
232
|
return data.data || [];
|
|
233
233
|
}
|
|
234
|
-
async function
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
const response = await apiFetch(`/project/${projectId}/headless/publish`, {
|
|
252
|
-
...options,
|
|
253
|
-
method: "POST",
|
|
254
|
-
body: formData
|
|
255
|
-
});
|
|
256
|
-
return await response.json();
|
|
257
|
-
} catch (err) {
|
|
258
|
-
if (err instanceof CLIError && err.code === "API_ERROR") {
|
|
259
|
-
if (err.statusCode === 413) {
|
|
260
|
-
throw new CLIError(
|
|
261
|
-
"BUNDLE_TOO_LARGE",
|
|
262
|
-
err.message,
|
|
263
|
-
"Reduce page bundle sizes. Check for large dependencies."
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
if (err.statusCode === 409) {
|
|
267
|
-
throw new CLIError(
|
|
268
|
-
"FUNNEL_NOT_HEADLESS",
|
|
269
|
-
err.message,
|
|
270
|
-
"Remove funnelId from config to create a new headless funnel."
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
throw new CLIError("PUBLISH_FAILED", err.message);
|
|
274
|
-
}
|
|
275
|
-
throw err;
|
|
276
|
-
}
|
|
234
|
+
async function publishInit(projectId, body, options) {
|
|
235
|
+
const response = await apiFetch(`/project/${projectId}/headless/publish-init`, {
|
|
236
|
+
...options,
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: JSON.stringify(body)
|
|
239
|
+
});
|
|
240
|
+
return await response.json();
|
|
241
|
+
}
|
|
242
|
+
async function publishFinalize(projectId, body, options) {
|
|
243
|
+
const response = await apiFetch(`/project/${projectId}/headless/publish-finalize`, {
|
|
244
|
+
...options,
|
|
245
|
+
method: "POST",
|
|
246
|
+
body: JSON.stringify(body)
|
|
247
|
+
});
|
|
248
|
+
return await response.json();
|
|
277
249
|
}
|
|
278
250
|
var DEFAULT_API_BASE2;
|
|
279
251
|
var init_api = __esm({
|
|
@@ -289,25 +261,47 @@ var init_exports = {};
|
|
|
289
261
|
__export(init_exports, {
|
|
290
262
|
initCommand: () => initCommand
|
|
291
263
|
});
|
|
292
|
-
import {
|
|
293
|
-
import { join as join2
|
|
294
|
-
import {
|
|
264
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
265
|
+
import { join as join2 } from "path";
|
|
266
|
+
import { Readable } from "stream";
|
|
267
|
+
import { extract } from "tar";
|
|
295
268
|
import pc4 from "picocolors";
|
|
296
269
|
import select2 from "@inquirer/select";
|
|
297
270
|
import input from "@inquirer/input";
|
|
298
|
-
function
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
271
|
+
async function fetchTemplateIndex() {
|
|
272
|
+
const url = `https://raw.githubusercontent.com/${TEMPLATES_REPO}/main/index.json`;
|
|
273
|
+
const res = await fetch(url);
|
|
274
|
+
if (!res.ok) throw new Error(`Failed to fetch template index: ${res.status}`);
|
|
275
|
+
const index = await res.json();
|
|
276
|
+
const templates = await Promise.all(
|
|
277
|
+
index.map(async (entry) => {
|
|
278
|
+
const configUrl = `https://raw.githubusercontent.com/${TEMPLATES_REPO}/main/${entry.dir}/template.json`;
|
|
279
|
+
const configRes = await fetch(configUrl);
|
|
280
|
+
const config = configRes.ok ? await configRes.json() : { name: entry.name, description: entry.description, products: [] };
|
|
281
|
+
return { dir: entry.dir, config };
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
return templates;
|
|
285
|
+
}
|
|
286
|
+
async function downloadTemplate(templateDir, destDir) {
|
|
287
|
+
const tarballUrl = `https://api.github.com/repos/${TEMPLATES_REPO}/tarball/main`;
|
|
288
|
+
const res = await fetch(tarballUrl, {
|
|
289
|
+
headers: { Accept: "application/vnd.github+json" }
|
|
290
|
+
});
|
|
291
|
+
if (!res.ok) throw new Error(`Failed to download template: ${res.status}`);
|
|
292
|
+
mkdirSync2(destDir, { recursive: true });
|
|
293
|
+
const nodeStream = Readable.fromWeb(res.body);
|
|
294
|
+
await new Promise((resolve5, reject) => {
|
|
295
|
+
nodeStream.pipe(
|
|
296
|
+
extract({
|
|
297
|
+
cwd: destDir,
|
|
298
|
+
strip: 2,
|
|
299
|
+
filter: (path) => {
|
|
300
|
+
const parts = path.split("/");
|
|
301
|
+
return parts.length > 2 && parts[1] === templateDir;
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
).on("finish", resolve5).on("error", reject);
|
|
311
305
|
});
|
|
312
306
|
}
|
|
313
307
|
function formatInterval(interval, count) {
|
|
@@ -352,7 +346,9 @@ async function initCommand(nameArg) {
|
|
|
352
346
|
const projectId = await promptForProject(creds.token);
|
|
353
347
|
const projects = await fetchProjects(creds.token);
|
|
354
348
|
const project = projects.find((p) => p.id === projectId);
|
|
355
|
-
const
|
|
349
|
+
const templateSpinner = spinner("Fetching templates...");
|
|
350
|
+
const templates = await fetchTemplateIndex();
|
|
351
|
+
templateSpinner.stop();
|
|
356
352
|
const selectedDir = await select2({
|
|
357
353
|
message: "Choose a template",
|
|
358
354
|
choices: templates.map((t) => ({
|
|
@@ -362,7 +358,6 @@ async function initCommand(nameArg) {
|
|
|
362
358
|
});
|
|
363
359
|
const chosen = templates.find((t) => t.dir === selectedDir);
|
|
364
360
|
const templateConfig = chosen.config;
|
|
365
|
-
const templateDir = join2(getTemplatesDir(), chosen.dir);
|
|
366
361
|
const productBindings = [];
|
|
367
362
|
if (templateConfig.products.length > 0) {
|
|
368
363
|
const storesSpinner = spinner("Fetching stores...");
|
|
@@ -444,7 +439,7 @@ async function initCommand(nameArg) {
|
|
|
444
439
|
}
|
|
445
440
|
}
|
|
446
441
|
const s = spinner(`Creating ${name}...`);
|
|
447
|
-
|
|
442
|
+
await downloadTemplate(selectedDir, dir);
|
|
448
443
|
const templateJsonPath = join2(dir, "template.json");
|
|
449
444
|
if (existsSync(templateJsonPath)) {
|
|
450
445
|
const { unlinkSync } = await import("fs");
|
|
@@ -455,6 +450,12 @@ async function initCommand(nameArg) {
|
|
|
455
450
|
const { renameSync } = await import("fs");
|
|
456
451
|
renameSync(gitignoreSrc, join2(dir, ".gitignore"));
|
|
457
452
|
}
|
|
453
|
+
const readmePath = join2(dir, "README.md");
|
|
454
|
+
if (existsSync(readmePath)) {
|
|
455
|
+
let readme = readFileSync2(readmePath, "utf-8");
|
|
456
|
+
readme = readme.replace("__NAME__", name);
|
|
457
|
+
writeFileSync2(readmePath, readme);
|
|
458
|
+
}
|
|
458
459
|
const configPath = join2(dir, "appfunnel.config.ts");
|
|
459
460
|
if (existsSync(configPath)) {
|
|
460
461
|
let config = readFileSync2(configPath, "utf-8");
|
|
@@ -485,7 +486,7 @@ ${itemsStr},
|
|
|
485
486
|
}
|
|
486
487
|
writeFileSync2(configPath, config);
|
|
487
488
|
}
|
|
488
|
-
const sdkVersion = `^${"0.
|
|
489
|
+
const sdkVersion = `^${"0.14.0"}`;
|
|
489
490
|
writeFileSync2(
|
|
490
491
|
join2(dir, "package.json"),
|
|
491
492
|
JSON.stringify(
|
|
@@ -528,7 +529,7 @@ ${itemsStr},
|
|
|
528
529
|
console.log(` ${pc4.dim("appfunnel dev")}`);
|
|
529
530
|
console.log();
|
|
530
531
|
}
|
|
531
|
-
var
|
|
532
|
+
var TEMPLATES_REPO, INTERVAL_LABELS;
|
|
532
533
|
var init_init = __esm({
|
|
533
534
|
"src/commands/init.ts"() {
|
|
534
535
|
"use strict";
|
|
@@ -536,7 +537,7 @@ var init_init = __esm({
|
|
|
536
537
|
init_auth();
|
|
537
538
|
init_projects();
|
|
538
539
|
init_api();
|
|
539
|
-
|
|
540
|
+
TEMPLATES_REPO = "appfunnel/templates";
|
|
540
541
|
INTERVAL_LABELS = {
|
|
541
542
|
day: { 1: "day", 7: "week", 14: "2 weeks", 30: "month" },
|
|
542
543
|
week: { 1: "week", 2: "2 weeks", 4: "month", 12: "quarter", 52: "year" },
|
|
@@ -731,7 +732,7 @@ var init_config = __esm({
|
|
|
731
732
|
import { readFileSync as readFileSync4 } from "fs";
|
|
732
733
|
import { join as join4 } from "path";
|
|
733
734
|
function checkVersionCompatibility(cwd) {
|
|
734
|
-
const cliVersion = "0.
|
|
735
|
+
const cliVersion = "0.14.0";
|
|
735
736
|
const sdkVersion = getSdkVersion(cwd);
|
|
736
737
|
const [cliMajor, cliMinor] = cliVersion.split(".").map(Number);
|
|
737
738
|
const [sdkMajor, sdkMinor] = sdkVersion.split(".").map(Number);
|
|
@@ -770,7 +771,7 @@ var init_version = __esm({
|
|
|
770
771
|
});
|
|
771
772
|
|
|
772
773
|
// src/extract/pages.ts
|
|
773
|
-
import { readdirSync
|
|
774
|
+
import { readdirSync, readFileSync as readFileSync5, existsSync as existsSync3 } from "fs";
|
|
774
775
|
import { join as join5, basename } from "path";
|
|
775
776
|
function scanPages(cwd) {
|
|
776
777
|
const pagesDir = resolvePagesDir(cwd);
|
|
@@ -781,7 +782,7 @@ function scanPages(cwd) {
|
|
|
781
782
|
"Create src/pages/ and add at least one .tsx page file."
|
|
782
783
|
);
|
|
783
784
|
}
|
|
784
|
-
const files =
|
|
785
|
+
const files = readdirSync(pagesDir).filter((f) => f.endsWith(".tsx") && !f.startsWith("_")).map((f) => basename(f, ".tsx")).sort();
|
|
785
786
|
if (files.length === 0) {
|
|
786
787
|
throw new CLIError(
|
|
787
788
|
"NO_PAGES",
|
|
@@ -1154,13 +1155,13 @@ var init_html = __esm({
|
|
|
1154
1155
|
|
|
1155
1156
|
// src/vite/plugin.ts
|
|
1156
1157
|
import { resolve as resolve2, join as join7 } from "path";
|
|
1157
|
-
import { existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as
|
|
1158
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
|
|
1158
1159
|
function loadTranslations(cwd) {
|
|
1159
1160
|
const localesDir = join7(cwd, "locales");
|
|
1160
1161
|
if (!existsSync5(localesDir)) return void 0;
|
|
1161
1162
|
const translations = {};
|
|
1162
1163
|
let hasAny = false;
|
|
1163
|
-
for (const file of
|
|
1164
|
+
for (const file of readdirSync2(localesDir)) {
|
|
1164
1165
|
if (!file.endsWith(".json")) continue;
|
|
1165
1166
|
const locale = file.replace(/\.json$/, "");
|
|
1166
1167
|
try {
|
|
@@ -1194,7 +1195,7 @@ function appfunnelPlugin(options) {
|
|
|
1194
1195
|
return {
|
|
1195
1196
|
name: "appfunnel",
|
|
1196
1197
|
config() {
|
|
1197
|
-
|
|
1198
|
+
mkdirSync3(appfunnelDir, { recursive: true });
|
|
1198
1199
|
writeFileSync3(htmlPath, generateHtml(config.name || "AppFunnel"));
|
|
1199
1200
|
return {
|
|
1200
1201
|
// Don't let Vite auto-serve index.html — we handle it ourselves
|
|
@@ -1835,7 +1836,7 @@ __export(build_exports, {
|
|
|
1835
1836
|
});
|
|
1836
1837
|
import { resolve as resolve3, join as join9 } from "path";
|
|
1837
1838
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1838
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, statSync, readdirSync as
|
|
1839
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, statSync, readdirSync as readdirSync3 } from "fs";
|
|
1839
1840
|
import pc8 from "picocolors";
|
|
1840
1841
|
async function buildCommand() {
|
|
1841
1842
|
const cwd = process.cwd();
|
|
@@ -1950,6 +1951,7 @@ async function buildCommand() {
|
|
|
1950
1951
|
responses: config.responses || {},
|
|
1951
1952
|
queryParams: { ...BUILTIN_QUERY_PARAMS, ...config.queryParams },
|
|
1952
1953
|
data: config.data || {},
|
|
1954
|
+
user: BUILTIN_USER_VARS,
|
|
1953
1955
|
products: config.products || {},
|
|
1954
1956
|
defaultLocale: config.defaultLocale,
|
|
1955
1957
|
assets,
|
|
@@ -1972,7 +1974,7 @@ async function buildCommand() {
|
|
|
1972
1974
|
if (totalSize > MAX_TOTAL_SIZE) {
|
|
1973
1975
|
console.log(formatWarning(
|
|
1974
1976
|
"BUNDLE_TOO_LARGE",
|
|
1975
|
-
`Total bundle size (${formatSize(totalSize)}) exceeds the
|
|
1977
|
+
`Total bundle size (${formatSize(totalSize)}) exceeds the 10MB limit.`,
|
|
1976
1978
|
"This will be rejected on publish. Reduce dependencies or code-split."
|
|
1977
1979
|
));
|
|
1978
1980
|
console.log();
|
|
@@ -2055,7 +2057,7 @@ function validateConditionVariables(condition, pageKey, allVariables) {
|
|
|
2055
2057
|
function collectAssets(outDir) {
|
|
2056
2058
|
const assets = [];
|
|
2057
2059
|
function walk(dir, prefix = "") {
|
|
2058
|
-
for (const entry of
|
|
2060
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
2059
2061
|
const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
2060
2062
|
const fullPath = join9(dir, entry.name);
|
|
2061
2063
|
if (entry.isDirectory()) {
|
|
@@ -2081,7 +2083,7 @@ function getSdkVersion2(cwd) {
|
|
|
2081
2083
|
return "0.0.0";
|
|
2082
2084
|
}
|
|
2083
2085
|
}
|
|
2084
|
-
var MAX_TOTAL_SIZE, MAX_PAGE_SIZE, BUILTIN_QUERY_PARAMS;
|
|
2086
|
+
var MAX_TOTAL_SIZE, MAX_PAGE_SIZE, BUILTIN_QUERY_PARAMS, BUILTIN_USER_VARS;
|
|
2085
2087
|
var init_build = __esm({
|
|
2086
2088
|
"src/commands/build.ts"() {
|
|
2087
2089
|
"use strict";
|
|
@@ -2094,7 +2096,7 @@ var init_build = __esm({
|
|
|
2094
2096
|
init_html();
|
|
2095
2097
|
init_errors();
|
|
2096
2098
|
init_errors();
|
|
2097
|
-
MAX_TOTAL_SIZE =
|
|
2099
|
+
MAX_TOTAL_SIZE = 10 * 1024 * 1024;
|
|
2098
2100
|
MAX_PAGE_SIZE = 500 * 1024;
|
|
2099
2101
|
BUILTIN_QUERY_PARAMS = {
|
|
2100
2102
|
utm_source: { type: "string" },
|
|
@@ -2103,6 +2105,14 @@ var init_build = __esm({
|
|
|
2103
2105
|
utm_content: { type: "string" },
|
|
2104
2106
|
utm_term: { type: "string" }
|
|
2105
2107
|
};
|
|
2108
|
+
BUILTIN_USER_VARS = {
|
|
2109
|
+
email: { type: "string" },
|
|
2110
|
+
name: { type: "string" },
|
|
2111
|
+
dateOfBirth: { type: "string" },
|
|
2112
|
+
gender: { type: "string" },
|
|
2113
|
+
stripeCustomerId: { type: "string" },
|
|
2114
|
+
paddleCustomerId: { type: "string" }
|
|
2115
|
+
};
|
|
2106
2116
|
}
|
|
2107
2117
|
});
|
|
2108
2118
|
|
|
@@ -2135,10 +2145,22 @@ __export(publish_exports, {
|
|
|
2135
2145
|
});
|
|
2136
2146
|
import { resolve as resolve4, join as join11 } from "path";
|
|
2137
2147
|
import { readFileSync as readFileSync10, existsSync as existsSync6 } from "fs";
|
|
2148
|
+
import { execSync } from "child_process";
|
|
2138
2149
|
import pc9 from "picocolors";
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2150
|
+
import select3 from "@inquirer/select";
|
|
2151
|
+
function tryGit(command, cwd) {
|
|
2152
|
+
try {
|
|
2153
|
+
return execSync(command, { cwd, stdio: ["pipe", "pipe", "ignore"] }).toString().trim() || void 0;
|
|
2154
|
+
} catch {
|
|
2155
|
+
return void 0;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
function getGitMetadata(cwd) {
|
|
2159
|
+
const commitSha = tryGit("git rev-parse HEAD", cwd);
|
|
2160
|
+
if (!commitSha) return {};
|
|
2161
|
+
const branch = tryGit("git rev-parse --abbrev-ref HEAD", cwd);
|
|
2162
|
+
const message = tryGit("git log -1 --pretty=%s", cwd);
|
|
2163
|
+
return { commitSha, branch, message };
|
|
2142
2164
|
}
|
|
2143
2165
|
function formatSize2(bytes) {
|
|
2144
2166
|
if (bytes < 1024) return `${bytes}B`;
|
|
@@ -2170,7 +2192,7 @@ async function publishCommand(options) {
|
|
|
2170
2192
|
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
2171
2193
|
const assets = manifest.assets || [];
|
|
2172
2194
|
const s = spinner("Preparing assets...");
|
|
2173
|
-
const
|
|
2195
|
+
const assetBuffers = [];
|
|
2174
2196
|
let totalBytes = 0;
|
|
2175
2197
|
for (let i = 0; i < assets.length; i++) {
|
|
2176
2198
|
const asset = assets[i];
|
|
@@ -2183,45 +2205,133 @@ async function publishCommand(options) {
|
|
|
2183
2205
|
"Run 'appfunnel build' to regenerate."
|
|
2184
2206
|
);
|
|
2185
2207
|
}
|
|
2186
|
-
const
|
|
2187
|
-
totalBytes +=
|
|
2188
|
-
|
|
2189
|
-
path: asset.path,
|
|
2190
|
-
content,
|
|
2191
|
-
contentType: getMimeType(asset.path)
|
|
2192
|
-
});
|
|
2208
|
+
const body = readFileSync10(fullPath);
|
|
2209
|
+
totalBytes += body.length;
|
|
2210
|
+
assetBuffers.push({ path: asset.path, body, size: body.length });
|
|
2193
2211
|
s.text = `Preparing assets... ${i + 1}/${assets.length} ${pc9.dim(`(${formatSize2(totalBytes)})`)}`;
|
|
2194
2212
|
}
|
|
2195
|
-
s.
|
|
2196
|
-
const
|
|
2213
|
+
s.stop();
|
|
2214
|
+
const MAX_TOTAL_SIZE2 = 10 * 1024 * 1024;
|
|
2215
|
+
if (totalBytes > MAX_TOTAL_SIZE2) {
|
|
2216
|
+
throw new CLIError(
|
|
2217
|
+
"BUNDLE_TOO_LARGE",
|
|
2218
|
+
`Total bundle size (${formatSize2(totalBytes)}) exceeds the ${formatSize2(MAX_TOTAL_SIZE2)} limit.`,
|
|
2219
|
+
"Reduce page bundle sizes. Check for large dependencies."
|
|
2220
|
+
);
|
|
2221
|
+
}
|
|
2222
|
+
info(`${assets.length} assets prepared ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
2223
|
+
let promote = options?.promote ?? false;
|
|
2224
|
+
if (!promote) {
|
|
2225
|
+
promote = await select3({
|
|
2226
|
+
message: "Publish as the new active version?",
|
|
2227
|
+
choices: [
|
|
2228
|
+
{ name: "Yes", value: true },
|
|
2229
|
+
{ name: "No", value: false }
|
|
2230
|
+
]
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
const git = getGitMetadata(cwd);
|
|
2234
|
+
const initSpinner = spinner("Initializing publish...");
|
|
2235
|
+
let initResult;
|
|
2236
|
+
try {
|
|
2237
|
+
initResult = await publishInit(
|
|
2238
|
+
projectId,
|
|
2239
|
+
{
|
|
2240
|
+
manifest,
|
|
2241
|
+
funnelId: config.funnelId || void 0,
|
|
2242
|
+
promote,
|
|
2243
|
+
commitSha: git.commitSha,
|
|
2244
|
+
branch: git.branch,
|
|
2245
|
+
message: git.message,
|
|
2246
|
+
triggeredBy: creds.email || void 0
|
|
2247
|
+
},
|
|
2248
|
+
{ token: creds.token }
|
|
2249
|
+
);
|
|
2250
|
+
} catch (err) {
|
|
2251
|
+
initSpinner.stop();
|
|
2252
|
+
if (err instanceof CLIError && err.code === "API_ERROR") {
|
|
2253
|
+
if (err.statusCode === 413) {
|
|
2254
|
+
throw new CLIError(
|
|
2255
|
+
"BUNDLE_TOO_LARGE",
|
|
2256
|
+
err.message,
|
|
2257
|
+
"Reduce page bundle sizes. Check for large dependencies."
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
if (err.statusCode === 409) {
|
|
2261
|
+
throw new CLIError(
|
|
2262
|
+
"FUNNEL_NOT_HEADLESS",
|
|
2263
|
+
err.message,
|
|
2264
|
+
"Remove funnelId from config to create a new headless funnel."
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
throw new CLIError("PUBLISH_FAILED", err.message);
|
|
2268
|
+
}
|
|
2269
|
+
throw err;
|
|
2270
|
+
}
|
|
2271
|
+
initSpinner.stop();
|
|
2272
|
+
info(`Build ${pc9.dim(initResult.buildId)} initialized`);
|
|
2273
|
+
const uploadSpinner = spinner(`Uploading 0/${assets.length} assets...`);
|
|
2274
|
+
const uploadInfoMap = new Map(initResult.assets.map((a) => [a.path, a]));
|
|
2275
|
+
let uploaded = 0;
|
|
2276
|
+
let uploadedBytes = 0;
|
|
2277
|
+
const uploadAsset = async (asset) => {
|
|
2278
|
+
const info2 = uploadInfoMap.get(asset.path);
|
|
2279
|
+
if (!info2) {
|
|
2280
|
+
throw new CLIError("PUBLISH_FAILED", `No upload URL for asset: ${asset.path}`);
|
|
2281
|
+
}
|
|
2282
|
+
const response = await fetch(info2.uploadUrl, {
|
|
2283
|
+
method: "PUT",
|
|
2284
|
+
body: new Uint8Array(asset.body),
|
|
2285
|
+
headers: {
|
|
2286
|
+
"Content-Type": info2.contentType
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2289
|
+
if (!response.ok) {
|
|
2290
|
+
throw new CLIError(
|
|
2291
|
+
"PUBLISH_FAILED",
|
|
2292
|
+
`Failed to upload ${asset.path}: ${response.status} ${response.statusText}`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
uploaded++;
|
|
2296
|
+
uploadedBytes += asset.size;
|
|
2297
|
+
uploadSpinner.text = `Uploading ${uploaded}/${assets.length} assets... ${pc9.dim(`(${formatSize2(uploadedBytes)}/${formatSize2(totalBytes)})`)}`;
|
|
2298
|
+
};
|
|
2299
|
+
const queue = [...assetBuffers];
|
|
2300
|
+
const workers = [];
|
|
2301
|
+
for (let i = 0; i < Math.min(UPLOAD_CONCURRENCY, queue.length); i++) {
|
|
2302
|
+
workers.push((async () => {
|
|
2303
|
+
while (queue.length > 0) {
|
|
2304
|
+
const asset = queue.shift();
|
|
2305
|
+
await uploadAsset(asset);
|
|
2306
|
+
}
|
|
2307
|
+
})());
|
|
2308
|
+
}
|
|
2309
|
+
await Promise.all(workers);
|
|
2310
|
+
uploadSpinner.stop();
|
|
2311
|
+
info(`${assets.length} assets uploaded ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
2312
|
+
const finalizeSpinner = spinner("Finalizing publish...");
|
|
2313
|
+
const result = await publishFinalize(
|
|
2197
2314
|
projectId,
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
assetPayloads,
|
|
2201
|
-
{ token: creds.token },
|
|
2202
|
-
options?.promote
|
|
2315
|
+
{ buildId: initResult.buildId, funnelId: initResult.funnelId },
|
|
2316
|
+
{ token: creds.token }
|
|
2203
2317
|
);
|
|
2204
|
-
|
|
2205
|
-
if (
|
|
2206
|
-
patchConfigFunnelId(cwd,
|
|
2318
|
+
finalizeSpinner.stop();
|
|
2319
|
+
if (initResult.created && initResult.funnelId) {
|
|
2320
|
+
patchConfigFunnelId(cwd, initResult.funnelId);
|
|
2207
2321
|
info(`Funnel created \u2014 funnelId added to appfunnel.config.ts`);
|
|
2208
2322
|
}
|
|
2209
2323
|
console.log();
|
|
2210
2324
|
success(result.activated ? "Published and activated" : "Published successfully");
|
|
2211
2325
|
console.log();
|
|
2212
2326
|
console.log(` ${pc9.dim("Build ID:")} ${result.buildId}`);
|
|
2213
|
-
if (
|
|
2214
|
-
console.log(` ${pc9.dim("Funnel:")} ${
|
|
2327
|
+
if (initResult.funnelId && !config.funnelId) {
|
|
2328
|
+
console.log(` ${pc9.dim("Funnel:")} ${initResult.funnelId}`);
|
|
2215
2329
|
}
|
|
2216
2330
|
console.log(` ${pc9.dim("Dashboard:")} ${pc9.cyan(result.dashboardUrl)}`);
|
|
2217
2331
|
console.log(` ${pc9.dim("Assets:")} ${assets.length} files ${pc9.dim(`(${formatSize2(totalBytes)})`)}`);
|
|
2218
|
-
if (!result.activated) {
|
|
2219
|
-
console.log();
|
|
2220
|
-
console.log(` ${pc9.dim("Tip:")} Use ${pc9.cyan("--promote")} to activate immediately, or promote from the dashboard.`);
|
|
2221
|
-
}
|
|
2222
2332
|
console.log();
|
|
2223
2333
|
}
|
|
2224
|
-
var
|
|
2334
|
+
var UPLOAD_CONCURRENCY;
|
|
2225
2335
|
var init_publish = __esm({
|
|
2226
2336
|
"src/commands/publish.ts"() {
|
|
2227
2337
|
"use strict";
|
|
@@ -2232,17 +2342,7 @@ var init_publish = __esm({
|
|
|
2232
2342
|
init_api();
|
|
2233
2343
|
init_errors();
|
|
2234
2344
|
init_config_patch();
|
|
2235
|
-
|
|
2236
|
-
".js": "application/javascript",
|
|
2237
|
-
".css": "text/css",
|
|
2238
|
-
".html": "text/html",
|
|
2239
|
-
".json": "application/json",
|
|
2240
|
-
".svg": "image/svg+xml",
|
|
2241
|
-
".png": "image/png",
|
|
2242
|
-
".jpg": "image/jpeg",
|
|
2243
|
-
".woff2": "font/woff2",
|
|
2244
|
-
".woff": "font/woff"
|
|
2245
|
-
};
|
|
2345
|
+
UPLOAD_CONCURRENCY = 10;
|
|
2246
2346
|
}
|
|
2247
2347
|
});
|
|
2248
2348
|
|
|
@@ -2251,7 +2351,7 @@ init_errors();
|
|
|
2251
2351
|
import { Command } from "commander";
|
|
2252
2352
|
import pc10 from "picocolors";
|
|
2253
2353
|
var program = new Command();
|
|
2254
|
-
program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.
|
|
2354
|
+
program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.14.0");
|
|
2255
2355
|
program.command("init").argument("[name]", "Project directory name").description("Create a new AppFunnel project").action(async (name) => {
|
|
2256
2356
|
const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
2257
2357
|
await initCommand2(name);
|