pointer-feedback 0.1.3 → 0.1.4

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 (2) hide show
  1. package/dist/cli.js +117 -17
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ var init_build_constants = __esm({
15
15
  "src/build-constants.ts"() {
16
16
  "use strict";
17
17
  BUILD_DEFAULT_SERVER = true ? "https://api.pointer.moamen.work" : "https://api.pointer.moamen.work";
18
- BUILD_CLI_VERSION = true ? "0.1.3" : "0.0.0-dev";
18
+ BUILD_CLI_VERSION = true ? "0.1.4" : "0.0.0-dev";
19
19
  }
20
20
  });
21
21
 
@@ -1244,6 +1244,8 @@ async function injectStatic(cwd2, htmlPath, cfg) {
1244
1244
  const pinnedProps = cfg.pin ? `
1245
1245
  s.integrity = '${cfg.pin.integrity}';
1246
1246
  s.crossOrigin = 'anonymous';` : "";
1247
+ const pinnedAttrs = cfg.pin ? ` integrity="${cfg.pin.integrity}" crossorigin="anonymous"` : "";
1248
+ const multiEnv = (cfg.environments?.length ?? 0) > 1 || Object.keys(cfg.envMap ?? {}).length > 0;
1247
1249
  const block = cfg.envGuarded ? `<!-- pointer-feedback:start -->
1248
1250
  <script>
1249
1251
  if (
@@ -1254,19 +1256,61 @@ async function injectStatic(cwd2, htmlPath, cfg) {
1254
1256
  s.src = '%VITE_POINTER_SERVER%/pointer.js${pinnedSrc}';${pinnedProps}
1255
1257
  s.defer = true;
1256
1258
  document.head.appendChild(s);
1257
- var el = document.createElement('pointer-feedback');
1258
- el.setAttribute('project', '%VITE_POINTER_PROJECT%');
1259
- el.setAttribute('server', '%VITE_POINTER_SERVER%');
1260
- el.setAttribute('environment', '%VITE_POINTER_ENV%');
1261
- el.setAttribute('source-attr', 'data-component-source');
1262
- document.body.appendChild(el);
1259
+ // Deferred until the body exists. Injected just above </body> this is already true, but the
1260
+ // block gets copied into other files by hand, and inside <head> document.body is null \u2014
1261
+ // "Cannot read properties of null (reading 'appendChild')", and nothing mounts.
1262
+ var mount = function () {
1263
+ if (document.querySelector('pointer-feedback')) return;
1264
+ var el = document.createElement('pointer-feedback');
1265
+ el.setAttribute('project', '%VITE_POINTER_PROJECT%');
1266
+ el.setAttribute('server', '%VITE_POINTER_SERVER%');
1267
+ el.setAttribute('environment', '%VITE_POINTER_ENV%');
1268
+ el.setAttribute('source-attr', 'data-component-source');
1269
+ document.body.appendChild(el);
1270
+ };
1271
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
1272
+ else mount();
1263
1273
  }
1264
1274
  </script>
1265
- <!-- pointer-feedback:end -->` : cfg.pin ? `<!-- pointer-feedback:start -->
1266
- <script src="${cfg.server}/pointer.js?v=${cfg.pin.version}" integrity="${cfg.pin.integrity}" crossorigin="anonymous" defer></script>
1267
- <pointer-feedback project="${cfg.key}" server="${cfg.server}" environment="${cfg.environment}" source-attr="data-component-source"></pointer-feedback>
1268
- <!-- pointer-feedback:end -->` : `<!-- pointer-feedback:start -->
1269
- <script src="${cfg.server}/pointer.js" defer></script>
1275
+ <!-- pointer-feedback:end -->` : multiEnv ? (
1276
+ // One file, several environments.
1277
+ //
1278
+ // The single-environment form writes environment="local" into the markup, which is right
1279
+ // until the same index.html is built for staging and production too — then every comment
1280
+ // from every deployment is tagged `local` and nothing can tell them apart. This form
1281
+ // resolves the environment from the page's own origin at runtime, using the URLs already
1282
+ // registered against the project, so one committed file is correct everywhere.
1283
+ `<!-- pointer-feedback:start -->
1284
+ <script${pinnedAttrs} src="${cfg.server}/pointer.js${pinnedSrc}" defer></script>
1285
+ <script>
1286
+ (function () {
1287
+ var ORIGINS = ${JSON.stringify(cfg.envMap ?? {})};
1288
+ var FALLBACK = '${cfg.environment}';
1289
+ function pointerEnv() {
1290
+ if (ORIGINS[location.origin]) return ORIGINS[location.origin];
1291
+ // A dev server's port changes more often than anyone updates a URL list, so localhost is
1292
+ // recognised by host rather than by exact origin.
1293
+ if (/^(localhost|127\\.0\\.0\\.1|\\[::1\\])$/.test(location.hostname)) return 'local';
1294
+ return FALLBACK;
1295
+ }
1296
+ function mount() {
1297
+ if (document.querySelector('pointer-feedback')) return;
1298
+ var el = document.createElement('pointer-feedback');
1299
+ el.setAttribute('project', '${cfg.key}');
1300
+ el.setAttribute('server', '${cfg.server}');
1301
+ el.setAttribute('environment', pointerEnv());
1302
+ el.setAttribute('source-attr', 'data-component-source');
1303
+ document.body.appendChild(el);
1304
+ }
1305
+ // document.body is null while the parser is still in <head>. Waiting for DOMContentLoaded
1306
+ // makes the snippet work wherever it is pasted, instead of only just above </body>.
1307
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount);
1308
+ else mount();
1309
+ })();
1310
+ </script>
1311
+ <!-- pointer-feedback:end -->`
1312
+ ) : `<!-- pointer-feedback:start -->
1313
+ <script${pinnedAttrs} src="${cfg.server}/pointer.js${pinnedSrc}" defer></script>
1270
1314
  <pointer-feedback project="${cfg.key}" server="${cfg.server}" environment="${cfg.environment}" source-attr="data-component-source"></pointer-feedback>
1271
1315
  <!-- pointer-feedback:end -->`;
1272
1316
  const re = /<!-- pointer-feedback:start -->[\s\S]*?<!-- pointer-feedback:end -->/;
@@ -2580,9 +2624,63 @@ and the pointer-init skill uses them to mount the widget for you afterwards.
2580
2624
  }
2581
2625
  }
2582
2626
  }
2583
- let env = options["environment"] || "local";
2627
+ const ALL_ENVS = ["local", "staging", "production"];
2628
+ let envs = String(options["environment"] ?? "").split(",").map((e) => e.trim()).filter(Boolean);
2629
+ const badEnv = envs.find((e) => !ALL_ENVS.includes(e));
2630
+ if (badEnv) {
2631
+ console.error(`Unknown environment "${badEnv}". Valid values: ${ALL_ENVS.join(", ")}.`);
2632
+ process.exit(2);
2633
+ }
2584
2634
  if (!isYes && !options["environment"]) {
2585
- env = await select("Environment", ["local", "staging", "production"], env);
2635
+ envs = await multiSelect("Which environments does this codebase run in?", ALL_ENVS, ["local"]);
2636
+ }
2637
+ if (envs.length === 0)
2638
+ envs = ["local"];
2639
+ const env = ALL_ENVS.filter((e) => envs.includes(e))[0] ?? "local";
2640
+ const projectRow = await api(server, "/api/admin/projects", { token }).then((rows) => rows.find((p) => p.key === finalProjectKey)).catch(() => null);
2641
+ if (projectRow?.id) {
2642
+ const activation = {};
2643
+ if (envs.includes("local") && !projectRow.isActiveLocal)
2644
+ activation["isActiveLocal"] = true;
2645
+ if (envs.includes("staging") && !projectRow.isActiveStaging)
2646
+ activation["isActiveStaging"] = true;
2647
+ if (envs.includes("production") && !projectRow.isActiveProduction)
2648
+ activation["isActiveProduction"] = true;
2649
+ if (Object.keys(activation).length) {
2650
+ try {
2651
+ await api(server, `/api/admin/projects/${projectRow.id}`, {
2652
+ method: "PATCH",
2653
+ body: activation,
2654
+ token
2655
+ });
2656
+ } catch (err) {
2657
+ if (!isJson) {
2658
+ console.error(
2659
+ `Note: could not activate ${Object.keys(activation).length} environment(s) for this project (${err?.message ?? err}). An admin can switch them on in the dashboard.`
2660
+ );
2661
+ }
2662
+ }
2663
+ }
2664
+ }
2665
+ const envMap = {};
2666
+ if (projectRow?.id && envs.length > 1) {
2667
+ try {
2668
+ const [urls, environments] = await Promise.all([
2669
+ api(server, `/api/admin/projects/${projectRow.id}/app-urls`, { token }),
2670
+ api(server, "/api/admin/environments", { token })
2671
+ ]);
2672
+ const nameById = new Map((environments ?? []).map((e) => [e.id, String(e.name ?? "").toLowerCase()]));
2673
+ for (const row of urls ?? []) {
2674
+ const name = nameById.get(row.appEnvironmentId);
2675
+ if (!name || !row.url || !envs.includes(name))
2676
+ continue;
2677
+ try {
2678
+ envMap[new URL(row.url).origin] = name;
2679
+ } catch {
2680
+ }
2681
+ }
2682
+ } catch {
2683
+ }
2586
2684
  }
2587
2685
  let appUrl = options["app-url"];
2588
2686
  let noAppUrl = options["no-app-url"];
@@ -2652,7 +2750,9 @@ and the pointer-init skill uses them to mount the widget for you afterwards.
2652
2750
  server,
2653
2751
  key: finalProjectKey,
2654
2752
  environment: env,
2655
- pin
2753
+ pin,
2754
+ envMap,
2755
+ environments: envs
2656
2756
  });
2657
2757
  filesMod = [htmlPath];
2658
2758
  injected = true;
@@ -2664,7 +2764,7 @@ and the pointer-init skill uses them to mount the widget for you afterwards.
2664
2764
  if (!isJson)
2665
2765
  console.log(`Injected widget into ${filesMod.join(", ")}`);
2666
2766
  } else if (appInfo.kind === "static") {
2667
- const htmlPath = await injectStatic(cwd2, options["html"], { server, key: finalProjectKey, environment: env, pin });
2767
+ const htmlPath = await injectStatic(cwd2, options["html"], { server, key: finalProjectKey, environment: env, pin, envMap, environments: envs });
2668
2768
  filesMod = [htmlPath];
2669
2769
  injected = true;
2670
2770
  if (!isJson)
@@ -2715,7 +2815,7 @@ and the pointer-init skill uses them to mount the widget for you afterwards.
2715
2815
  const mergedStack = mergeStack(stackMeta, serverStackResponse?.data ?? serverStackResponse, noDesign ? null : designBlock);
2716
2816
  await writeStackFile(cwd2, mergedStack);
2717
2817
  const injectedHtml = injected ? filesMod.find((f) => f.toLowerCase().endsWith(".html"))?.replace(`${cwd2}/`, "") : void 0;
2718
- await writeConfig(cwd2, { server, project: finalProjectKey, environment: env, aiTool: tool, skillsDir: options["skills-dir"], cliVersion: BUILD_CLI_VERSION, htmlPath: injectedHtml });
2818
+ await writeConfig(cwd2, { server, project: finalProjectKey, environment: env, aiTool: tool, skillsDir: options["skills-dir"], cliVersion: BUILD_CLI_VERSION, htmlPath: injectedHtml, environments: envs.length > 1 ? envs : void 0 });
2719
2819
  filesMod.push(".pointer/config.json");
2720
2820
  filesMod.push(".pointer/credentials.env");
2721
2821
  filesMod.push(".gitignore");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pointer-feedback",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Click-to-comment feedback for your web app: install the widget, then turn pending comments into AI apply prompts.",
5
5
  "type": "module",
6
6
  "license": "MIT",