@stackable-labs/cli-app-extension 1.10.2 → 1.11.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.
Files changed (3) hide show
  1. package/README.md +39 -2
  2. package/dist/index.js +35 -89
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -25,8 +25,8 @@ When run without all required flags, the CLI guides you through a step-by-step p
25
25
  | 1 | **App** | Select the App you are building an Extension for (fetched live from the API) |
26
26
  | 2 | **Name** | Display name for your Extension (e.g. `My Commerce Extension`) |
27
27
  | 3 | **Targets** | Multiselect from the Surface targets/slots exposed by the selected app |
28
- | 4 | **Extension Port** | Dev server port for the Extension (default: `5173`) |
29
- | 5 | **Preview Port** | Dev server port for the Preview host (default: `5174`) |
28
+ | 4 | **Extension Port** | Dev server port for the Extension (default: `6543`) |
29
+ | 5 | **Preview Port** | Dev server port for the Preview host (default: `6544`) |
30
30
  | 6 | **Directory** | Output directory path (default: kebab-case of name) |
31
31
  | 7 | **Confirm** | Review all selections before scaffolding |
32
32
 
@@ -76,6 +76,43 @@ my-extension/
76
76
  └── package.json
77
77
  ```
78
78
 
79
+ ## `dev` Command
80
+
81
+ Start local dev servers with a public Cloudflare tunnel for testing:
82
+
83
+ ```bash
84
+ pnpm preview
85
+ ```
86
+
87
+ ```
88
+ Usage: stackable-app-extension dev [options]
89
+
90
+ Options:
91
+ --dir <path> Project root (default: cwd)
92
+ --extension-port <port> Override Extension port
93
+ --preview-port <port> Override Preview port
94
+ --no-tunnel Skip tunnel, just run vite dev
95
+ -h, --help Display help
96
+ ```
97
+
98
+ The `dev` command:
99
+ 1. Reads `.env.stackable` for cached App/Extension context (prompts if missing)
100
+ 2. Starts Cloudflare tunnels for both extension and preview servers
101
+ 3. Starts Vite dev servers with hot-reload
102
+ 4. Displays a **Host App Query Param** you can append to your host app URL to test against the real deployed host
103
+
104
+ ### Host App Override
105
+
106
+ The dashboard displays a query param like:
107
+
108
+ ```
109
+ ?_stackable_dev=ext-123%3Ahttps%3A%2F%2Fabc.trycloudflare.com
110
+ ```
111
+
112
+ Append this to your deployed host app URL to override the extension's `bundleUrl` in your browser session only. The `@stackable-labs/embeddables` SDK detects this param and loads from your tunnel instead of the production bundle. No DB changes, no shared state — each developer gets isolated overrides.
113
+
114
+ > **Note:** The `dev` command never updates the `bundleUrl` in the database. It is purely local.
115
+
79
116
  ## Development Workflow
80
117
 
81
118
  ```bash
package/dist/index.js CHANGED
@@ -287,7 +287,7 @@ import { Box as Box6, Text as Text6, useFocus as useFocus2, useFocusManager as u
287
287
  import TextInput3 from "ink-text-input";
288
288
  import { useState as useState3 } from "react";
289
289
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
290
- var DEFAULT_EXTENSION_PORT = 5173;
290
+ var DEFAULT_EXTENSION_PORT = 6543;
291
291
  var DEFAULT_PREVIEW_PORT = DEFAULT_EXTENSION_PORT + 1;
292
292
  var FieldRow = ({ label, value, onChange, onSubmit, onConfirm, placeholder, autoFocus, isFirst, isLast }) => {
293
293
  const { isFocused } = useFocus2({ autoFocus });
@@ -622,7 +622,7 @@ var UpdateSettingsPrompt = ({
622
622
  label: "Bundle URL",
623
623
  value: bundleUrlValue,
624
624
  onChange: setBundleUrlValue,
625
- placeholder: "http://localhost:5173",
625
+ placeholder: "http://localhost:6543",
626
626
  onSubmitAll: handleSubmitAll
627
627
  }
628
628
  ),
@@ -1066,15 +1066,14 @@ var readDevContext = async (projectRoot) => {
1066
1066
  } catch {
1067
1067
  }
1068
1068
  const devLocalEnv = await readEnvFile(join(projectRoot, ".env.development.local"));
1069
- const extensionPort = parseInt(devLocalEnv.VITE_EXTENSION_PORT || env.VITE_EXTENSION_PORT || "5173", 10);
1070
- const previewPort = parseInt(devLocalEnv.VITE_PREVIEW_PORT || env.VITE_PREVIEW_PORT || "5174", 10);
1069
+ const extensionPort = parseInt(devLocalEnv.VITE_EXTENSION_PORT || env.VITE_EXTENSION_PORT || "6543", 10);
1070
+ const previewPort = parseInt(devLocalEnv.VITE_PREVIEW_PORT || env.VITE_PREVIEW_PORT || "6544", 10);
1071
1071
  return {
1072
1072
  projectRoot,
1073
1073
  extensionName,
1074
1074
  appId: stackableEnv.APP_ID || null,
1075
1075
  extensionId: stackableEnv.EXTENSION_ID || null,
1076
1076
  appName: stackableEnv.APP_NAME || null,
1077
- originalBundleUrl: stackableEnv.ORIGINAL_BUNDLE_URL || null,
1078
1077
  extensionPort,
1079
1078
  previewPort
1080
1079
  };
@@ -1103,9 +1102,6 @@ var writeDevContext = async (projectRoot, ctx) => {
1103
1102
  if (ctx.extensionName && ctx.extensionName !== "Unknown Extension") {
1104
1103
  env.EXTENSION_NAME = ctx.extensionName;
1105
1104
  }
1106
- if (ctx.originalBundleUrl) {
1107
- env.ORIGINAL_BUNDLE_URL = ctx.originalBundleUrl;
1108
- }
1109
1105
  await writeEnvFile(join(projectRoot, ".env.stackable"), env);
1110
1106
  };
1111
1107
 
@@ -1334,7 +1330,7 @@ const extensions: ExtensionRegistryEntry[] = [
1334
1330
  {
1335
1331
  id: manifest.name.toLowerCase().replace(/\\s+/g, '-'),
1336
1332
  manifest,
1337
- bundleUrl: import.meta.env.VITE_EXTENSION_BUNDLE_URL || \`http://localhost:\${import.meta.env.VITE_EXTENSION_PORT || '5173'}\`,
1333
+ bundleUrl: import.meta.env.VITE_EXTENSION_BUNDLE_URL || \`http://localhost:\${import.meta.env.VITE_EXTENSION_PORT || '6543'}\`,
1338
1334
  enabled: true,
1339
1335
  },
1340
1336
  ]
@@ -1517,10 +1513,10 @@ var App = ({ command, initialName, initialExtensionId, options }) => {
1517
1513
  );
1518
1514
  const [selectedApp, setSelectedApp] = useState8(null);
1519
1515
  const [extensionPort, setExtensionPort] = useState8(
1520
- options?.extensionPort ? parseInt(options.extensionPort, 10) : 5173
1516
+ options?.extensionPort ? parseInt(options.extensionPort, 10) : 6543
1521
1517
  );
1522
1518
  const [previewPort, setPreviewPort] = useState8(
1523
- options?.previewPort ? parseInt(options.previewPort, 10) : 5174
1519
+ options?.previewPort ? parseInt(options.previewPort, 10) : 6544
1524
1520
  );
1525
1521
  const [outputDir, setOutputDir] = useState8("");
1526
1522
  const [progressSteps, setProgressSteps] = useState8(PROGRESS_STEPS[command]);
@@ -1929,7 +1925,6 @@ var DevDashboard = ({
1929
1925
  previewTunnelUrl,
1930
1926
  previewPort,
1931
1927
  extensionPort,
1932
- bundleUrlUpdated,
1933
1928
  onQuit
1934
1929
  }) => {
1935
1930
  useEffect5(() => {
@@ -1952,13 +1947,10 @@ var DevDashboard = ({
1952
1947
  StepShell,
1953
1948
  {
1954
1949
  title: "dev",
1955
- hint: "Live development with Tunnel",
1956
- footer: /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", gap: 1, children: [
1957
- bundleUrlUpdated && /* @__PURE__ */ jsx15(Text15, { color: "green", children: "bundleUrl updated \u2713" }),
1958
- /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Press q or Ctrl-C to quit" })
1959
- ] }),
1950
+ hint: `Live development ${tunnelUrl ? "with" : "w/o"} Tunnel`,
1951
+ footer: /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", gap: 1, children: /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Press q or Ctrl-C to quit" }) }),
1960
1952
  children: [
1961
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", gap: 1, children: [
1953
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
1962
1954
  /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1963
1955
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Extension:" }),
1964
1956
  /* @__PURE__ */ jsxs15(Text15, { children: [
@@ -1971,9 +1963,13 @@ var DevDashboard = ({
1971
1963
  /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1972
1964
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "App:" }),
1973
1965
  /* @__PURE__ */ jsx15(Text15, { children: `${appName ? `${appName} ` : ""}(${appId})` })
1966
+ ] }),
1967
+ /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1968
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Bundle URL:" }),
1969
+ /* @__PURE__ */ jsx15(Text15, { children: tunnelUrl || `http://localhost:${extensionPort}` })
1974
1970
  ] })
1975
1971
  ] }),
1976
- /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", gap: 1, children: [
1972
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
1977
1973
  previewTunnelUrl && /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1978
1974
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Tunnel Dev - Preview:" }),
1979
1975
  /* @__PURE__ */ jsx15(Text15, { children: previewTunnelUrl })
@@ -1984,7 +1980,9 @@ var DevDashboard = ({
1984
1980
  "http://localhost:",
1985
1981
  previewPort
1986
1982
  ] })
1987
- ] }),
1983
+ ] })
1984
+ ] }),
1985
+ /* @__PURE__ */ jsxs15(Box15, { flexDirection: "column", children: [
1988
1986
  tunnelUrl && /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1989
1987
  /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Tunnel Dev - Extension:" }),
1990
1988
  /* @__PURE__ */ jsx15(Text15, { children: tunnelUrl })
@@ -1995,15 +1993,15 @@ var DevDashboard = ({
1995
1993
  "http://localhost:",
1996
1994
  extensionPort
1997
1995
  ] })
1998
- ] }),
1999
- /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
2000
- /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Bundle URL:" }),
2001
- /* @__PURE__ */ jsxs15(Text15, { children: [
2002
- tunnelUrl || `http://localhost:${extensionPort}`,
2003
- bundleUrlUpdated ? " (updated \u2713)" : ""
2004
- ] })
2005
1996
  ] })
2006
- ] })
1997
+ ] }),
1998
+ /* @__PURE__ */ jsx15(Box15, { flexDirection: "column", children: tunnelUrl && /* @__PURE__ */ jsxs15(Box15, { gap: 2, children: [
1999
+ /* @__PURE__ */ jsx15(Text15, { dimColor: true, children: "Host Dev - Query Param:" }),
2000
+ /* @__PURE__ */ jsxs15(Text15, { children: [
2001
+ "?_stackable_dev=",
2002
+ encodeURIComponent(`${extensionId}:${tunnelUrl}`)
2003
+ ] })
2004
+ ] }) })
2007
2005
  ]
2008
2006
  }
2009
2007
  )
@@ -2012,28 +2010,17 @@ var DevDashboard = ({
2012
2010
 
2013
2011
  // src/components/DevApp.tsx
2014
2012
  import { jsx as jsx16 } from "react/jsx-runtime";
2015
- var isEphemeralUrl = (url) => {
2016
- try {
2017
- const { hostname } = new URL(url);
2018
- return hostname.endsWith(".ngrok.io") || hostname.endsWith(".ngrok-free.app") || hostname.endsWith(".trycloudflare.com");
2019
- } catch {
2020
- return true;
2021
- }
2022
- };
2023
2013
  var DevApp = ({ options = {} }) => {
2024
2014
  const [state, setState] = useState10("setup");
2025
2015
  const [devContext, setDevContext] = useState10(null);
2026
2016
  const [resolvedContext, setResolvedContext] = useState10(null);
2027
2017
  const [tunnelUrl, setTunnelUrl] = useState10(null);
2028
2018
  const [previewTunnelUrl, setPreviewTunnelUrl] = useState10(null);
2029
- const [bundleUrlUpdated, setBundleUrlUpdated] = useState10(false);
2030
2019
  const [tunnelHandle, setTunnelHandle] = useState10(null);
2031
2020
  const [previewTunnelHandle, setPreviewTunnelHandle] = useState10(null);
2032
2021
  const [devServerHandle, setDevServerHandle] = useState10(null);
2033
2022
  const shuttingDown = useRef(false);
2034
2023
  const useTunnel = options.tunnel !== false;
2035
- const useUpdate = options.update !== false;
2036
- const useRestore = options.restore !== false;
2037
2024
  useEffect6(() => {
2038
2025
  const projectRoot = options.dir || process.cwd();
2039
2026
  console.log(`[dev] Reading context from ${projectRoot}`);
@@ -2055,32 +2042,6 @@ var DevApp = ({ options = {} }) => {
2055
2042
  const extensionPort = options.extensionPort ? parseInt(options.extensionPort, 10) : devContext.extensionPort;
2056
2043
  const previewPort = options.previewPort ? parseInt(options.previewPort, 10) : devContext.previewPort;
2057
2044
  await patchViteAllowedHosts(devContext.projectRoot);
2058
- let cachedOriginalUrl = devContext.originalBundleUrl;
2059
- if (useUpdate) {
2060
- try {
2061
- console.log("[dev] Fetching current bundleUrl from API...");
2062
- const extensions = await fetchExtensions(resolved.appId);
2063
- const currentExtension = extensions[resolved.extensionId];
2064
- if (currentExtension?.bundleUrl) {
2065
- console.log(`[dev] Current bundleUrl: ${currentExtension.bundleUrl}`);
2066
- if (!isEphemeralUrl(currentExtension.bundleUrl)) {
2067
- console.log("[dev] Caching stable bundleUrl to .env.stackable");
2068
- cachedOriginalUrl = currentExtension.bundleUrl;
2069
- await writeDevContext(devContext.projectRoot, {
2070
- ...devContext,
2071
- appId: resolved.appId,
2072
- extensionId: resolved.extensionId,
2073
- appName: resolved.appName || null,
2074
- originalBundleUrl: cachedOriginalUrl
2075
- });
2076
- } else {
2077
- console.log("[dev] Current bundleUrl is ephemeral, keeping previous cached value");
2078
- }
2079
- }
2080
- } catch (err) {
2081
- console.warn("[dev] Failed to fetch current bundleUrl:", err);
2082
- }
2083
- }
2084
2045
  if (useTunnel) {
2085
2046
  try {
2086
2047
  console.log(`[dev] Starting extension tunnel on port ${extensionPort}...`);
@@ -2095,14 +2056,6 @@ var DevApp = ({ options = {} }) => {
2095
2056
  setPreviewTunnelUrl(previewTunnelResult.url);
2096
2057
  console.log(`[dev] Writing VITE_EXTENSION_BUNDLE_URL=${tunnelResult.url} to .env`);
2097
2058
  await patchProjectEnv(devContext.projectRoot, "VITE_EXTENSION_BUNDLE_URL", tunnelResult.url);
2098
- if (useUpdate) {
2099
- console.log(`[dev] Updating bundleUrl to ${tunnelResult.url}`);
2100
- await updateExtension(resolved.appId, resolved.extensionId, {
2101
- bundleUrl: tunnelResult.url
2102
- });
2103
- console.log("[dev] bundleUrl updated successfully");
2104
- setBundleUrlUpdated(true);
2105
- }
2106
2059
  } catch (err) {
2107
2060
  console.error("[dev] Failed to start tunnel:", err);
2108
2061
  }
@@ -2114,7 +2067,7 @@ var DevApp = ({ options = {} }) => {
2114
2067
  setDevServerHandle(serverHandle);
2115
2068
  console.log("[dev] Ready");
2116
2069
  setState("running");
2117
- }, [devContext, options.extensionPort, options.previewPort, useTunnel, useUpdate]);
2070
+ }, [devContext, options.extensionPort, options.previewPort, useTunnel]);
2118
2071
  useEffect6(() => {
2119
2072
  if (state === "setup" && devContext && devContext.appId && devContext.extensionId) {
2120
2073
  handleSetupReady({
@@ -2130,15 +2083,6 @@ var DevApp = ({ options = {} }) => {
2130
2083
  setState("stopping");
2131
2084
  console.log("[dev] Shutting down...");
2132
2085
  try {
2133
- if (useRestore && devContext.originalBundleUrl) {
2134
- console.log(`[dev] Restoring bundleUrl to ${devContext.originalBundleUrl}`);
2135
- await updateExtension(resolvedContext.appId, resolvedContext.extensionId, {
2136
- bundleUrl: devContext.originalBundleUrl
2137
- });
2138
- console.log("[dev] bundleUrl restored");
2139
- } else if (useRestore && !devContext.originalBundleUrl) {
2140
- console.warn("[dev] No cached original bundleUrl to restore");
2141
- }
2142
2086
  console.log("[dev] Removing VITE_EXTENSION_BUNDLE_URL from .env");
2143
2087
  await removeProjectEnvKey(devContext.projectRoot, "VITE_EXTENSION_BUNDLE_URL");
2144
2088
  if (tunnelHandle) {
@@ -2183,7 +2127,6 @@ var DevApp = ({ options = {} }) => {
2183
2127
  previewPort: options.previewPort ? parseInt(options.previewPort, 10) : devContext.previewPort,
2184
2128
  tunnelUrl,
2185
2129
  previewTunnelUrl,
2186
- bundleUrlUpdated,
2187
2130
  onQuit: handleQuit
2188
2131
  }
2189
2132
  );
@@ -2218,8 +2161,11 @@ var checkForUpdate = (currentVersion) => {
2218
2161
  res.on("end", () => {
2219
2162
  try {
2220
2163
  const { version: latest } = JSON.parse(data);
2221
- if (latest && latest !== currentVersion && isNewer(latest, currentVersion)) {
2222
- const warning = "\n\x1B[33m\u26A0 Update available: " + currentVersion + " \u2192 " + latest + "\x1B[0m\n Run \x1B[36mnpx @stackable-labs/cli-app-extension@latest\x1B[0m to update\n";
2164
+ if (latest && latest !== currentVersion && currentVersion !== "0.0.0" && isNewer(latest, currentVersion)) {
2165
+ const warning = `
2166
+ \x1B[33m\u26A0 Update available: ${currentVersion} \u2192 ${latest}\x1B[0m
2167
+ Run \x1B[36mnpx @stackable-labs/cli-app-extension@latest\x1B[0m to update
2168
+ `;
2223
2169
  console.error(warning);
2224
2170
  }
2225
2171
  } catch {
@@ -2244,16 +2190,16 @@ var require2 = createRequire(import.meta.url);
2244
2190
  var { version } = require2("../package.json");
2245
2191
  checkForUpdate(version);
2246
2192
  program.name("stackable-app-extension").description("Stackable Labs - App Extension developer CLI").version(version);
2247
- program.command("create" /* CREATE */).description("Create a new Extension project").argument("[name]", "Extension project name").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((name, options) => {
2193
+ program.command("create" /* CREATE */).description("Create a new Extension project").argument("[name]", "Extension project name").option("--extension-port <port>", "Extension dev server port (default: 6543)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((name, options) => {
2248
2194
  render(/* @__PURE__ */ jsx17(App, { command: "create" /* CREATE */, initialName: name, options }));
2249
2195
  });
2250
- program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 5173)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((options) => {
2196
+ program.command("scaffold" /* SCAFFOLD */).description("Scaffold a local project from an existing Extension").option("--extension-port <port>", "Extension dev server port (default: 6543)").option("--preview-port <port>", "Preview dev server port").option("--skip-install", "Skip package manager install").option("--skip-git", "Skip git initialization").action((options) => {
2251
2197
  render(/* @__PURE__ */ jsx17(App, { command: "scaffold" /* SCAFFOLD */, options }));
2252
2198
  });
2253
2199
  program.command("update" /* UPDATE */).description("Update an existing Extension").argument("[extensionId]", "Extension ID to update").option("--app-id <id>", "Skip App selection").option("--name <name>", "New Extension name").option("--targets <targets>", "Comma-separated target slots (validated against app)").option("--bundle-url <url>", "New bundle URL").option("--enabled <bool>", "Enable/disable Extension").option("--set-version <version>", "Explicit version (skips auto-compute)").action((extensionId, options) => {
2254
2200
  render(/* @__PURE__ */ jsx17(App, { command: "update" /* UPDATE */, initialExtensionId: extensionId, options }));
2255
2201
  });
2256
- program.command("dev" /* DEV */).description("Start dev servers with a public tunnel").option("--dir <path>", "Project root (default: cwd)").option("--extension-port <port>", "Override Extension port").option("--preview-port <port>", "Override Preview port").option("--no-tunnel", "Skip tunnel, just run vite dev").option("--no-update", "Skip bundleUrl update on server").option("--no-restore", "On exit, keep tunnel URL as bundleUrl").action((options) => {
2202
+ program.command("dev" /* DEV */).description("Start dev servers with a public tunnel").option("--dir <path>", "Project root (default: cwd)").option("--extension-port <port>", "Override Extension port").option("--preview-port <port>", "Override Preview port").option("--no-tunnel", "Skip tunnel, just run vite dev").action((options) => {
2257
2203
  render(/* @__PURE__ */ jsx17(DevApp, { options }), { exitOnCtrlC: false });
2258
2204
  });
2259
2205
  program.parse(process.argv.filter((arg) => arg !== "--"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stackable-labs/cli-app-extension",
3
- "version": "1.10.2",
3
+ "version": "1.11.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "bin": {