@archie/devtools 0.0.5 → 0.0.6

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/README.md CHANGED
@@ -1,22 +1,20 @@
1
1
  # @archie/devtools
2
2
 
3
- DevTools for Archie generated applications. Provides seamless communication between the generated app and the Archie editor.
3
+ DevTools for Archie generated applications. Route sync with the editor and optional element inspector script.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
8
  npm install @archie/devtools
9
+ # or
10
+ pnpm add @archie/devtools
9
11
  ```
10
12
 
11
13
  ## Features
12
14
 
13
- ### Route Synchronization
14
-
15
- Automatically syncs the current route from your app to the Archie editor, enabling real-time preview navigation.
16
-
17
- ### Vite Plugin (Optional)
18
-
19
- Provides build-time integration for future features like component tagging and HMR overrides.
15
+ - **Route synchronization** – Current route is sent to the Archie editor for live preview.
16
+ - **Inspector script (optional)** – Injects the element inspector at runtime (no URL, bundled).
17
+ - **Vite plugin (optional)** Build-time hook for future features.
20
18
 
21
19
  ## Usage
22
20
 
@@ -32,20 +30,46 @@ export default defineConfig({
32
30
  });
33
31
  ```
34
32
 
35
- ### 2. Initialize Route Listener (Required)
33
+ ### 2. Inspector script (optional)
34
+
35
+ Injects the inspector when the client bundle runs. Safe in SSR (no-op on server). One line, no `useEffect`:
36
+
37
+ ```typescript
38
+ // App root, e.g. layout.tsx or main.tsx
39
+ import "@archie/devtools/client/inject-inspector/auto";
40
+ ```
41
+
42
+ **¿Afecta que la función tenga opciones?** No. El auto-import llama `injectInspector()` sin argumentos. Por defecto se usan orígenes Archie + localhost en dev y el target desde el parent (iframe). Solo si quieres cambiar algo pasas opciones:
36
43
 
37
44
  ```typescript
38
- // src/main.tsx
45
+ import { injectInspector } from "@archie/devtools/client";
46
+
47
+ // Igual que el auto: sin argumentos, valores por defecto seguros
48
+ injectInspector();
49
+
50
+ // Opcional: incluir localhost explícitamente, o fijar orígenes/target
51
+ injectInspector({ dev: true });
52
+ injectInspector({ id: "my-inspector" });
53
+ injectInspector({ allowedOrigins: ["https://app.archie.com"], targetOrigin: "https://app.archie.com" });
54
+ ```
55
+
56
+ Paths:
57
+
58
+ - `@archie/devtools/client` – route listener + `injectInspector`
59
+ - `@archie/devtools/client/inject-inspector/auto` – side-effect only (import and it runs)
60
+
61
+ ### 3. Route listener (required for preview sync)
62
+
63
+ ```typescript
64
+ // e.g. src/main.tsx
39
65
  import { setupArchieRouteListener } from "@archie/devtools/client";
40
66
  import App, { router } from "./App";
41
67
 
42
- // Initialize route synchronization
43
68
  setupArchieRouteListener(router);
44
-
45
69
  createRoot(document.getElementById("root")!).render(<App />);
46
70
  ```
47
71
 
48
- ### 3. Export Router from App
72
+ ### 4. Export router from App
49
73
 
50
74
  ```typescript
51
75
  // src/App.tsx
@@ -81,6 +105,41 @@ interface ArchieRouteUpdateMessage {
81
105
  }
82
106
  ```
83
107
 
108
+ ## Host (editor) integration
109
+
110
+ If your editor receives postMessage from the inspector iframe:
111
+
112
+ 1. **Validate origin with an allowlist (recommended)**
113
+ In the **host**, allow the iframe’s origins (localhost for dev + your app URLs in production) so it works everywhere.
114
+ **Archie host origins:** `https://app.dev.archie-platform.com`, `https://app.staging.archie-platform.com`, `https://app.archie.com`. For local testing use `getAllowedOrigins(true)` (adds localhost). Example:
115
+ ```js
116
+ import { getAllowedOrigins } from "@archie/devtools/constants";
117
+ const ALLOWED_ORIGINS = getAllowedOrigins(true); // true = include localhost for local dev
118
+ window.addEventListener("message", (e) => {
119
+ if (!ALLOWED_ORIGINS.includes(e.origin)) return;
120
+ // handle e.data
121
+ });
122
+ ```
123
+ 2. **Inspector origins (iframe):** Only configured domains are allowed; **`"*"` is never used.** If you use `injectInspector()` (recommended), it sets Archie host origins + localhost in dev by default. You can override with `allowedOrigins` and `targetOrigin` (or set `window.__ARCHIE_INSPECTOR_ALLOWED_ORIGINS__` / `__ARCHIE_INSPECTOR_TARGET_ORIGIN__` before the script runs).
124
+
125
+ 4. **Globals (backward compatible)**
126
+ The inspector still sets `window._inspectorDebug`, `window._inspectorSelectedElement`, `window._inspectorMessageHandler`, and `window._inspectorQuerySelectorProtected`.
127
+ Prefer the namespace: `window.__ARCHIE_INSPECTOR__` with `debug`, `selectedElement`, `messageHandler` (same values, no DOM pollution on the namespace object).
128
+
129
+ 5. **Message types** (from iframe to parent)
130
+ `INSPECTOR_SCRIPT_LOADED`, `ELEMENT_SELECTED`, `STOP_INSPECTION`, `INSPECTION_CANCELLED`, `ELEMENT_POSITION_UPDATE`, `STYLE_CHANGE_APPLIED` / `STYLE_CHANGE_FAILED`, `TEXT_CHANGE_APPLIED` / `TEXT_CHANGE_FAILED`, `ELEMENT_REMOVED` / `ELEMENT_REMOVAL_FAILED`.
131
+ Payload shapes are unchanged; only the postMessage `targetOrigin` is configurable.
132
+
133
+ To **activate the inspector from the parent** (the site that contains the iframe): you don't call `runInspector()` from the parent. The app inside the iframe already loads the inspector; from the parent send `iframe.contentWindow.postMessage({ type: "START_INSPECTION" }, iframeOrigin)` to start inspection. See [docs/HOST_INTEGRATION.md](docs/HOST_INTEGRATION.md) for full flow, local testing, and all commands.
134
+
135
+ ## Linking locally
136
+
137
+ ```bash
138
+ # In the app repo
139
+ pnpm add file:../archie-devtools
140
+ # After changing the lib: run `pnpm run build` in archie-devtools
141
+ ```
142
+
84
143
  ## Requirements
85
144
 
86
145
  - React 18+
@@ -0,0 +1,21 @@
1
+ // src/constants/archieOrigins.ts
2
+ var ARCHIE_HOST_ORIGINS = [
3
+ "https://app.dev.archie-platform.com",
4
+ "https://app.staging.archie-platform.com",
5
+ "https://app.archie.com"
6
+ ];
7
+ var LOCAL_DEV_ORIGINS = [
8
+ "http://localhost:3000",
9
+ "http://localhost:3001"
10
+ ];
11
+ function getAllowedOrigins(includeLocal = false) {
12
+ const list = [...ARCHIE_HOST_ORIGINS];
13
+ if (includeLocal) list.push(...LOCAL_DEV_ORIGINS);
14
+ return list;
15
+ }
16
+
17
+ export {
18
+ ARCHIE_HOST_ORIGINS,
19
+ LOCAL_DEV_ORIGINS,
20
+ getAllowedOrigins
21
+ };
@@ -1,27 +1,4 @@
1
- // src/client.ts
2
- function setupArchieRouteListener(router) {
3
- const target = window.parent !== window ? window.parent : window.opener;
4
- if (!target) {
5
- return () => {
6
- };
7
- }
8
- broadcastRouteState(target, router.state, extractAllRoutes(router.routes));
9
- const unsubscribe = router.subscribe((state) => {
10
- const currentRoutes = extractAllRoutes(router.routes);
11
- broadcastRouteState(target, state, currentRoutes);
12
- });
13
- const handleMessage = (event) => {
14
- if (event.data?.type === "ARCHIE_QUERY_ROUTES") {
15
- const currentRoutes = extractAllRoutes(router.routes);
16
- broadcastRouteState(target, router.state, currentRoutes);
17
- }
18
- };
19
- window.addEventListener("message", handleMessage);
20
- return () => {
21
- unsubscribe();
22
- window.removeEventListener("message", handleMessage);
23
- };
24
- }
1
+ // src/client/route-listener/routeListener.ts
25
2
  function extractAllRoutes(routes, parentPath = "") {
26
3
  const result = [];
27
4
  for (const route of routes) {
@@ -60,6 +37,30 @@ function broadcastRouteState(target, state, allRoutes) {
60
37
  } catch {
61
38
  }
62
39
  }
40
+ function setupArchieRouteListener(router) {
41
+ const target = window.parent !== window ? window.parent : window.opener;
42
+ if (!target) {
43
+ return () => {
44
+ };
45
+ }
46
+ broadcastRouteState(target, router.state, extractAllRoutes(router.routes));
47
+ const unsubscribe = router.subscribe((state) => {
48
+ const currentRoutes = extractAllRoutes(router.routes);
49
+ broadcastRouteState(target, state, currentRoutes);
50
+ });
51
+ const handleMessage = (event) => {
52
+ if (event.data?.type === "ARCHIE_QUERY_ROUTES") {
53
+ const currentRoutes = extractAllRoutes(router.routes);
54
+ broadcastRouteState(target, router.state, currentRoutes);
55
+ }
56
+ };
57
+ window.addEventListener("message", handleMessage);
58
+ return () => {
59
+ unsubscribe();
60
+ window.removeEventListener("message", handleMessage);
61
+ };
62
+ }
63
+
63
64
  export {
64
65
  setupArchieRouteListener
65
66
  };
@@ -0,0 +1,74 @@
1
+ import {
2
+ getAllowedOrigins
3
+ } from "./chunk-6MYORGLK.mjs";
4
+
5
+ // src/client/inject-inspector/injectInspector.ts
6
+ var DEFAULT_SCRIPT_ID = "archie-inspector-script";
7
+ function isLocalDev(referrer) {
8
+ if (!referrer) return true;
9
+ try {
10
+ const u = new URL(referrer);
11
+ return u.hostname === "localhost" || u.hostname === "127.0.0.1";
12
+ } catch {
13
+ return true;
14
+ }
15
+ }
16
+ function injectInspector(opts = {}) {
17
+ if (typeof document === "undefined") return null;
18
+ const id = opts.id ?? DEFAULT_SCRIPT_ID;
19
+ const existing = document.getElementById(id);
20
+ if (existing) return Promise.resolve(existing);
21
+ const win = typeof window !== "undefined" ? window : null;
22
+ if (win) {
23
+ const referrer = typeof document.referrer === "string" ? document.referrer : "";
24
+ const dev = opts.dev ?? isLocalDev(referrer);
25
+ let allowed;
26
+ if (opts.allowedOrigins !== void 0 && opts.allowedOrigins !== "*") {
27
+ const v = opts.allowedOrigins;
28
+ allowed = typeof v === "string" ? [v] : Array.isArray(v) ? v : [];
29
+ } else {
30
+ allowed = getAllowedOrigins(dev);
31
+ }
32
+ if (allowed.length > 0) {
33
+ win["__ARCHIE_INSPECTOR_ALLOWED_ORIGINS__"] = allowed;
34
+ }
35
+ let target;
36
+ if (opts.targetOrigin !== void 0 && opts.targetOrigin !== "*") {
37
+ target = opts.targetOrigin || void 0;
38
+ } else if (referrer) {
39
+ try {
40
+ target = new URL(referrer).origin;
41
+ } catch {
42
+ target = void 0;
43
+ }
44
+ } else {
45
+ target = void 0;
46
+ }
47
+ if (target) {
48
+ win["__ARCHIE_INSPECTOR_TARGET_ORIGIN__"] = target;
49
+ }
50
+ }
51
+ return import("./inspector-LTHUYUGW.mjs").then((m) => {
52
+ try {
53
+ m.default();
54
+ } catch (err) {
55
+ if (typeof console !== "undefined" && console.error) {
56
+ console.error("[Archie DevTools] Inspector failed to run:", err);
57
+ }
58
+ throw err;
59
+ }
60
+ const script = document.createElement("script");
61
+ script.id = id;
62
+ document.head.appendChild(script);
63
+ return script;
64
+ }).catch((err) => {
65
+ if (typeof console !== "undefined" && console.error) {
66
+ console.error("[Archie DevTools] Failed to load inspector:", err);
67
+ }
68
+ throw err;
69
+ });
70
+ }
71
+
72
+ export {
73
+ injectInspector
74
+ };
@@ -0,0 +1,2 @@
1
+ export { ArchieRouteUpdateMessage, RouteInfo, setupArchieRouteListener } from './route-listener/routeListener.mjs';
2
+ import '@remix-run/router';
@@ -0,0 +1,2 @@
1
+ export { ArchieRouteUpdateMessage, RouteInfo, setupArchieRouteListener } from './route-listener/routeListener.js';
2
+ import '@remix-run/router';
@@ -17,35 +17,14 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/client.ts
20
+ // src/client/client.ts
21
21
  var client_exports = {};
22
22
  __export(client_exports, {
23
23
  setupArchieRouteListener: () => setupArchieRouteListener
24
24
  });
25
25
  module.exports = __toCommonJS(client_exports);
26
- function setupArchieRouteListener(router) {
27
- const target = window.parent !== window ? window.parent : window.opener;
28
- if (!target) {
29
- return () => {
30
- };
31
- }
32
- broadcastRouteState(target, router.state, extractAllRoutes(router.routes));
33
- const unsubscribe = router.subscribe((state) => {
34
- const currentRoutes = extractAllRoutes(router.routes);
35
- broadcastRouteState(target, state, currentRoutes);
36
- });
37
- const handleMessage = (event) => {
38
- if (event.data?.type === "ARCHIE_QUERY_ROUTES") {
39
- const currentRoutes = extractAllRoutes(router.routes);
40
- broadcastRouteState(target, router.state, currentRoutes);
41
- }
42
- };
43
- window.addEventListener("message", handleMessage);
44
- return () => {
45
- unsubscribe();
46
- window.removeEventListener("message", handleMessage);
47
- };
48
- }
26
+
27
+ // src/client/route-listener/routeListener.ts
49
28
  function extractAllRoutes(routes, parentPath = "") {
50
29
  const result = [];
51
30
  for (const route of routes) {
@@ -84,6 +63,29 @@ function broadcastRouteState(target, state, allRoutes) {
84
63
  } catch {
85
64
  }
86
65
  }
66
+ function setupArchieRouteListener(router) {
67
+ const target = window.parent !== window ? window.parent : window.opener;
68
+ if (!target) {
69
+ return () => {
70
+ };
71
+ }
72
+ broadcastRouteState(target, router.state, extractAllRoutes(router.routes));
73
+ const unsubscribe = router.subscribe((state) => {
74
+ const currentRoutes = extractAllRoutes(router.routes);
75
+ broadcastRouteState(target, state, currentRoutes);
76
+ });
77
+ const handleMessage = (event) => {
78
+ if (event.data?.type === "ARCHIE_QUERY_ROUTES") {
79
+ const currentRoutes = extractAllRoutes(router.routes);
80
+ broadcastRouteState(target, router.state, currentRoutes);
81
+ }
82
+ };
83
+ window.addEventListener("message", handleMessage);
84
+ return () => {
85
+ unsubscribe();
86
+ window.removeEventListener("message", handleMessage);
87
+ };
88
+ }
87
89
  // Annotate the CommonJS export names for ESM import in node:
88
90
  0 && (module.exports = {
89
91
  setupArchieRouteListener
@@ -0,0 +1,6 @@
1
+ import {
2
+ setupArchieRouteListener
3
+ } from "../chunk-EQV632XF.mjs";
4
+ export {
5
+ setupArchieRouteListener
6
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }