appfunnel 0.7.0 → 0.9.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 CHANGED
@@ -21,9 +21,7 @@ function formatError(err) {
21
21
  return lines.join("\n");
22
22
  }
23
23
  function formatWarning(code, message, hint) {
24
- const lines = [
25
- `${pc.yellow("WARNING")} ${pc.dim(`[${code}]`)}: ${message}`
26
- ];
24
+ const lines = [`${pc.yellow("WARNING")} ${pc.dim(`[${code}]`)}: ${message}`];
27
25
  if (hint) {
28
26
  lines.push(` ${pc.dim("Hint:")} ${hint}`);
29
27
  }
@@ -330,10 +328,10 @@ function formatStoreName(store) {
330
328
  const test = store.isTestMode ? pc4.yellow(" (test)") : "";
331
329
  return `${store.name || store.type}${test}`;
332
330
  }
333
- async function initCommand() {
331
+ async function initCommand(nameArg) {
334
332
  const creds = requireAuth();
335
333
  const apiOpts = { token: creds.token };
336
- const name = await input({
334
+ const name = nameArg?.trim() || await input({
337
335
  message: "Funnel name",
338
336
  validate: (value) => {
339
337
  if (!value.trim()) return "Name is required";
@@ -344,6 +342,12 @@ async function initCommand() {
344
342
  return true;
345
343
  }
346
344
  });
345
+ if (nameArg) {
346
+ if (!/^[a-z0-9-]+$/.test(name))
347
+ throw new Error("Name must be lowercase letters, numbers, and hyphens only");
348
+ if (existsSync(join2(process.cwd(), name)))
349
+ throw new Error(`Directory '${name}' already exists`);
350
+ }
347
351
  const dir = join2(process.cwd(), name.trim());
348
352
  const projectId = await promptForProject(creds.token);
349
353
  const projects = await fetchProjects(creds.token);
@@ -476,7 +480,7 @@ ${itemsStr},
476
480
  }
477
481
  writeFileSync2(configPath, config);
478
482
  }
479
- const sdkVersion = `^${"0.7.0"}`;
483
+ const sdkVersion = `^${"0.9.0"}`;
480
484
  writeFileSync2(
481
485
  join2(dir, "package.json"),
482
486
  JSON.stringify(
@@ -722,7 +726,7 @@ var init_config = __esm({
722
726
  import { readFileSync as readFileSync4 } from "fs";
723
727
  import { join as join4 } from "path";
724
728
  function checkVersionCompatibility(cwd) {
725
- const cliVersion = "0.7.0";
729
+ const cliVersion = "0.9.0";
726
730
  const sdkVersion = getSdkVersion(cwd);
727
731
  const [cliMajor, cliMinor] = cliVersion.split(".").map(Number);
728
732
  const [sdkMajor, sdkMinor] = sdkVersion.split(".").map(Number);
@@ -879,7 +883,7 @@ var init_pages = __esm({
879
883
  import { join as join6 } from "path";
880
884
  import { existsSync as existsSync4 } from "fs";
881
885
  function generateEntrySource(options) {
882
- const { config, pages, pagesDir, funnelTsxPath, isDev } = options;
886
+ const { config, pages, pagesDir, funnelTsxPath, isDev, translations } = options;
883
887
  const pageKeys = Object.keys(pages);
884
888
  const mergedPages = {};
885
889
  const mergedRoutes = {};
@@ -937,6 +941,8 @@ ${pageImports}
937
941
 
938
942
  ${priceDataCode}
939
943
 
944
+ const translations = ${translations ? JSON.stringify(translations) : "undefined"}
945
+
940
946
  const config = ${JSON.stringify(fullConfig, null, 2)}
941
947
 
942
948
  const keyToSlug = ${JSON.stringify(
@@ -945,7 +951,7 @@ const keyToSlug = ${JSON.stringify(
945
951
  const slugToKey = ${JSON.stringify(slugMap)}
946
952
 
947
953
  const DEV_CAMPAIGN_SLUG = 'campaign'
948
- const DEFAULT_INITIAL = '${config.initialPageKey || pageKeys[0] || "index"}'
954
+ const DEFAULT_INITIAL = '${config.initialPageKey}'
949
955
 
950
956
  /**
951
957
  * Parse the URL to extract basePath, campaignSlug, and initial page.
@@ -1093,6 +1099,7 @@ function App() {
1093
1099
  campaignSlug={campaignSlug}
1094
1100
  priceData={priceData}
1095
1101
  sessionData={sessionData}
1102
+ translations={translations}
1096
1103
  >
1097
1104
  <FunnelWrapper>
1098
1105
  <PageRenderer />
@@ -1142,7 +1149,24 @@ var init_html = __esm({
1142
1149
 
1143
1150
  // src/vite/plugin.ts
1144
1151
  import { resolve as resolve2, join as join7 } from "path";
1145
- import { existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync6 } from "fs";
1152
+ import { existsSync as existsSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
1153
+ function loadTranslations(cwd) {
1154
+ const localesDir = join7(cwd, "locales");
1155
+ if (!existsSync5(localesDir)) return void 0;
1156
+ const translations = {};
1157
+ let hasAny = false;
1158
+ for (const file of readdirSync3(localesDir)) {
1159
+ if (!file.endsWith(".json")) continue;
1160
+ const locale = file.replace(/\.json$/, "");
1161
+ try {
1162
+ const content = readFileSync6(join7(localesDir, file), "utf-8");
1163
+ translations[locale] = JSON.parse(content);
1164
+ hasAny = true;
1165
+ } catch {
1166
+ }
1167
+ }
1168
+ return hasAny ? translations : void 0;
1169
+ }
1146
1170
  function appfunnelPlugin(options) {
1147
1171
  const { cwd, config, isDev } = options;
1148
1172
  let pages = options.pages;
@@ -1150,6 +1174,7 @@ function appfunnelPlugin(options) {
1150
1174
  const funnelTsxPath = resolve2(cwd, "src", "funnel.tsx");
1151
1175
  const appfunnelDir = join7(cwd, APPFUNNEL_DIR);
1152
1176
  const htmlPath = join7(appfunnelDir, "index.html");
1177
+ const localesDir = join7(cwd, "locales");
1153
1178
  function getEntrySource() {
1154
1179
  return generateEntrySource({
1155
1180
  config,
@@ -1157,7 +1182,8 @@ function appfunnelPlugin(options) {
1157
1182
  pagesDir,
1158
1183
  funnelTsxPath,
1159
1184
  isDev,
1160
- priceData: options.priceData
1185
+ priceData: options.priceData,
1186
+ translations: loadTranslations(cwd)
1161
1187
  });
1162
1188
  }
1163
1189
  return {
@@ -1171,7 +1197,8 @@ function appfunnelPlugin(options) {
1171
1197
  resolve: {
1172
1198
  alias: {
1173
1199
  "@": resolve2(cwd, "src")
1174
- }
1200
+ },
1201
+ dedupe: ["react", "react-dom"]
1175
1202
  },
1176
1203
  esbuild: {
1177
1204
  jsx: "automatic"
@@ -1233,6 +1260,18 @@ function appfunnelPlugin(options) {
1233
1260
  }
1234
1261
  });
1235
1262
  }
1263
+ if (existsSync5(localesDir)) {
1264
+ watcher.add(localesDir);
1265
+ watcher.on("change", (file) => {
1266
+ if (file.startsWith(localesDir) && file.endsWith(".json")) {
1267
+ const mod = devServer.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ENTRY_ID);
1268
+ if (mod) {
1269
+ devServer.moduleGraph.invalidateModule(mod);
1270
+ }
1271
+ devServer.ws.send({ type: "full-reload" });
1272
+ }
1273
+ });
1274
+ }
1236
1275
  return () => {
1237
1276
  devServer.middlewares.use(async (req, res, next) => {
1238
1277
  const url = req.url?.split("?")[0] || "";
@@ -1411,7 +1450,7 @@ __export(build_exports, {
1411
1450
  });
1412
1451
  import { resolve as resolve3, join as join9 } from "path";
1413
1452
  import { randomUUID as randomUUID2 } from "crypto";
1414
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, statSync, readdirSync as readdirSync3 } from "fs";
1453
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, statSync, readdirSync as readdirSync4 } from "fs";
1415
1454
  import pc7 from "picocolors";
1416
1455
  async function buildCommand() {
1417
1456
  const cwd = process.cwd();
@@ -1427,6 +1466,14 @@ async function buildCommand() {
1427
1466
  "Run 'appfunnel dev' first to select a project, or add projectId manually."
1428
1467
  );
1429
1468
  }
1469
+ if (!config.initialPageKey) {
1470
+ s.stop();
1471
+ throw new CLIError(
1472
+ "MISSING_INITIAL_PAGE_KEY",
1473
+ "Missing initialPageKey in appfunnel.config.ts.",
1474
+ "Set initialPageKey to the page key (filename without .tsx) of your first page."
1475
+ );
1476
+ }
1430
1477
  const pageKeys = scanPages(cwd);
1431
1478
  const pages = await extractPageDefinitions(cwd, pageKeys);
1432
1479
  s.stop();
@@ -1622,7 +1669,7 @@ function validateConditionVariables(condition, pageKey, allVariables) {
1622
1669
  function collectAssets(outDir) {
1623
1670
  const assets = [];
1624
1671
  function walk(dir, prefix = "") {
1625
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1672
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
1626
1673
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
1627
1674
  const fullPath = join9(dir, entry.name);
1628
1675
  if (entry.isDirectory()) {
@@ -1642,7 +1689,7 @@ function formatSize(bytes) {
1642
1689
  }
1643
1690
  function getSdkVersion2(cwd) {
1644
1691
  try {
1645
- const pkg = JSON.parse(readFileSync8(join9(cwd, "node_modules", "@appfunnel", "sdk", "package.json"), "utf-8"));
1692
+ const pkg = JSON.parse(readFileSync8(join9(cwd, "node_modules", "@appfunnel-dev", "sdk", "package.json"), "utf-8"));
1646
1693
  return pkg.version;
1647
1694
  } catch {
1648
1695
  return "0.0.0";
@@ -1818,10 +1865,10 @@ init_errors();
1818
1865
  import { Command } from "commander";
1819
1866
  import pc9 from "picocolors";
1820
1867
  var program = new Command();
1821
- program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.7.0");
1822
- program.command("init").description("Create a new AppFunnel project").action(async () => {
1868
+ program.name("appfunnel").description("Build and publish headless AppFunnel projects").version("0.9.0");
1869
+ program.command("init").argument("[name]", "Project directory name").description("Create a new AppFunnel project").action(async (name) => {
1823
1870
  const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1824
- await initCommand2();
1871
+ await initCommand2(name);
1825
1872
  });
1826
1873
  program.command("login").description("Authenticate with AppFunnel").action(async () => {
1827
1874
  const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), login_exports));