@base44/vite-plugin 0.1.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.
@@ -0,0 +1,13 @@
1
+ const { base44 } = require("./base44Client.cjs");
2
+
3
+ module.exports = new Proxy(
4
+ {},
5
+ {
6
+ get: (_, prop) => {
7
+ if (prop === "agentSDK" || prop === "default") {
8
+ return base44.agents;
9
+ }
10
+ return base44.agents[prop];
11
+ },
12
+ }
13
+ );
@@ -0,0 +1,6 @@
1
+ const { createClient } = require('@base44/sdk');
2
+
3
+ module.exports.base44 = createClient({
4
+ appId: process.env.VITE_BASE44_APP_ID,
5
+ serverUrl: process.env.VITE_BASE44_BACKEND_URL
6
+ });
@@ -0,0 +1,25 @@
1
+ const { base44 } = require('./base44Client.cjs');
2
+
3
+ module.exports = new Proxy({}, {
4
+ get: (_, entityName) => {
5
+ return new Proxy({}, {
6
+ get: (_, prop) => {
7
+ if (entityName === 'User') {
8
+ if (prop === 'me') {
9
+ return base44.auth.me;
10
+ }
11
+ if (prop === 'loginWithRedirect' || prop === 'login') {
12
+ return base44.auth.loginWithRedirect;
13
+ }
14
+ if (prop === 'logout') {
15
+ return base44.auth.logout;
16
+ }
17
+ if (prop === 'updateMyUserData') {
18
+ return base44.auth.updateMe;
19
+ }
20
+ }
21
+ return base44.entities[entityName][prop];
22
+ }
23
+ });
24
+ }
25
+ });
@@ -0,0 +1,9 @@
1
+ const { base44 } = require('./base44Client.cjs');
2
+
3
+ module.exports = new Proxy({}, {
4
+ get: (_, prop) => {
5
+ return (...args) => {
6
+ return base44.functions.invoke(prop, ...args);
7
+ }
8
+ }
9
+ });
@@ -0,0 +1,9 @@
1
+ const { base44 } = require('./base44Client.cjs');
2
+
3
+ module.exports = new Proxy({}, {
4
+ get: (_, prop) => {
5
+ return (...args) => {
6
+ return base44.integrations.Core[prop](...args);
7
+ }
8
+ }
9
+ });
@@ -0,0 +1,12 @@
1
+ declare const HTMLElement: {
2
+ new (): {};
3
+ };
4
+ export declare class ErrorOverlay extends HTMLElement {
5
+ static getOverlayHTML(): string;
6
+ close(): void;
7
+ static sendErrorToParent(error: Error, title: string, details: string | undefined, componentName: string | undefined): void;
8
+ constructor(error: Error);
9
+ }
10
+ export declare const errorOverlayCode: string;
11
+ export {};
12
+ //# sourceMappingURL=ErrorOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorOverlay.d.ts","sourceRoot":"","sources":["../src/ErrorOverlay.ts"],"names":[],"mappings":"AAGA,QAAA,MAAQ,WAAW;;CAA0B,CAAC;AAE9C,qBAAa,YAAa,SAAQ,WAAW;IAC3C,MAAM,CAAC,cAAc;IAMrB,KAAK;IAIL,MAAM,CAAC,iBAAiB,CACtB,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,aAAa,EAAE,MAAM,GAAG,SAAS;gBAqBvB,KAAK,EAAE,KAAK;CAuBzB;AAGD,eAAO,MAAM,gBAAgB,QAG5B,CAAC"}
@@ -0,0 +1,51 @@
1
+ /// <reference lib="dom" />
2
+ // Make HTMLElement available in non-browser environments
3
+ const { HTMLElement = class {
4
+ } } = globalThis;
5
+ export class ErrorOverlay extends HTMLElement {
6
+ static getOverlayHTML() {
7
+ return `
8
+ <div>
9
+ </div>
10
+ `;
11
+ }
12
+ close() {
13
+ this.parentNode?.removeChild(this);
14
+ }
15
+ static sendErrorToParent(error, title, details, componentName) {
16
+ // Send error to parent using framewire
17
+ if (globalThis.window?.parent) {
18
+ try {
19
+ globalThis.window.parent?.postMessage({
20
+ type: "app_error",
21
+ error: { title, details, componentName, originalError: error },
22
+ }, "*");
23
+ }
24
+ catch (error) {
25
+ console.warn("Failed to send error to iframe parent:", error?.message);
26
+ }
27
+ }
28
+ }
29
+ constructor(error) {
30
+ super();
31
+ const stack = error?.stack;
32
+ let componentName = stack?.match(/at\s+(\w+)\s+\(eval/)?.[1];
33
+ if (componentName === "eval") {
34
+ componentName = undefined;
35
+ }
36
+ const title = componentName
37
+ ? `in ${componentName}: ${error.message?.toString()}`
38
+ : error.message?.toString();
39
+ const details = error?.stack;
40
+ // Call editor frame with the error (via post message)
41
+ ErrorOverlay.sendErrorToParent(error, title, details, componentName);
42
+ // Create the overlay element using HTML template
43
+ const overlay = document.createElement("div");
44
+ overlay.innerHTML = ErrorOverlay.getOverlayHTML();
45
+ // Add to DOM
46
+ document.body.appendChild(overlay);
47
+ }
48
+ }
49
+ // vite/react-plugin transpiles classes with _SomeClass, so we need to replace all _ErrorOverlay with ErrorOverlay
50
+ export const errorOverlayCode = ErrorOverlay.toString().replaceAll("_ErrorOverlay", "ErrorOverlay");
51
+ //# sourceMappingURL=ErrorOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorOverlay.js","sourceRoot":"","sources":["../src/ErrorOverlay.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAE3B,yDAAyD;AACzD,MAAM,EAAE,WAAW,GAAG;CAAQ,EAAE,GAAG,UAAU,CAAC;AAE9C,MAAM,OAAO,YAAa,SAAQ,WAAW;IAC3C,MAAM,CAAC,cAAc;QACnB,OAAO;;;KAGN,CAAC;IACJ,CAAC;IACD,KAAK;QACF,IAAY,CAAC,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,iBAAiB,CACtB,KAAY,EACZ,KAAa,EACb,OAA2B,EAC3B,aAAiC;QAEjC,uCAAuC;QACvC,IAAI,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CACnC;oBACE,IAAI,EAAE,WAAW;oBACjB,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,EAAE;iBAC/D,EACD,GAAG,CACJ,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CACV,wCAAwC,EACvC,KAAe,EAAE,OAAO,CAC1B,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,YAAY,KAAY;QACtB,KAAK,EAAE,CAAC;QAER,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAC;QAC3B,IAAI,aAAa,GAAG,KAAK,EAAE,KAAK,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7D,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YAC7B,aAAa,GAAG,SAAS,CAAC;QAC5B,CAAC;QACD,MAAM,KAAK,GAAG,aAAa;YACzB,CAAC,CAAC,MAAM,aAAa,KAAK,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE;YACrD,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,EAAE,KAAK,CAAC;QAE7B,sDAAsD;QACtD,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC;QAErE,iDAAiD;QACjD,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC9C,OAAO,CAAC,SAAS,GAAG,YAAY,CAAC,cAAc,EAAE,CAAC;QAElD,aAAa;QACb,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;CACF;AAED,kHAAkH;AAClH,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,EAAE,CAAC,UAAU,CAChE,eAAe,EACf,cAAc,CACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function errorOverlayPlugin(): Plugin;
3
+ //# sourceMappingURL=error-overlay-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-overlay-plugin.d.ts","sourceRoot":"","sources":["../src/error-overlay-plugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,wBAAgB,kBAAkB,IAc3B,MAAM,CACZ"}
@@ -0,0 +1,15 @@
1
+ import { errorOverlayCode } from "./ErrorOverlay.js";
2
+ export function errorOverlayPlugin() {
3
+ return {
4
+ name: "error-overlay",
5
+ apply: (config) => config.mode === "development",
6
+ transform(code, id, opts = {}) {
7
+ if (opts?.ssr)
8
+ return;
9
+ if (!id.includes("vite/dist/client/client.mjs"))
10
+ return;
11
+ return code.replace("class ErrorOverlay", errorOverlayCode + "\nclass OldErrorOverlay");
12
+ },
13
+ };
14
+ }
15
+ //# sourceMappingURL=error-overlay-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-overlay-plugin.js","sourceRoot":"","sources":["../src/error-overlay-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,MAAM,UAAU,kBAAkB;IAChC,OAAO;QACL,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa;QAChD,SAAS,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE;YAC3B,IAAI,IAAI,EAAE,GAAG;gBAAE,OAAO;YAEtB,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;gBAAE,OAAO;YAExD,OAAO,IAAI,CAAC,OAAO,CACjB,oBAAoB,EACpB,gBAAgB,GAAG,yBAAyB,CAC7C,CAAC;QACJ,CAAC;KACQ,CAAC;AACd,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from "vite";
2
+ export default function vitePlugin(opts?: {
3
+ legacySDKImports?: boolean;
4
+ }): Plugin<any>[];
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,MAAM,CAAC;AAO/C,MAAM,CAAC,OAAO,UAAU,UAAU,CAChC,IAAI,GAAE;IACJ,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,iBAgIP"}
package/dist/index.js ADDED
@@ -0,0 +1,105 @@
1
+ import { loadEnv } from "vite";
2
+ import { errorOverlayPlugin } from "./error-overlay-plugin.js";
3
+ import { visualEditPlugin } from "./visual-edit-plugin.js";
4
+ const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
5
+ export default function vitePlugin(opts = {}) {
6
+ const { legacySDKImports = false } = opts;
7
+ return [
8
+ {
9
+ name: "base44",
10
+ config: ({ mode }) => {
11
+ const env = loadEnv(mode ?? "development", process.cwd(), "");
12
+ return {
13
+ resolve: {
14
+ alias: {
15
+ "@/": "/src/",
16
+ },
17
+ },
18
+ ...(isRunningInSandbox
19
+ ? {
20
+ server: {
21
+ host: "0.0.0.0", // Bind to all interfaces for container access
22
+ port: 5173,
23
+ strictPort: true,
24
+ // Allow all hosts - essential for Modal tunnel URLs
25
+ allowedHosts: true,
26
+ watch: {
27
+ // Enable polling for better file change detection in containers
28
+ usePolling: true,
29
+ interval: 100, // Check every 100ms for responsive HMR
30
+ },
31
+ hmr: {
32
+ protocol: "wss",
33
+ clientPort: 443,
34
+ },
35
+ },
36
+ build: {
37
+ rollupOptions: {
38
+ onwarn(warning, warn) {
39
+ // Treat import errors as fatal errors
40
+ if (warning.code === "UNRESOLVED_IMPORT" ||
41
+ warning.code === "MISSING_EXPORT") {
42
+ throw new Error(`Build failed: ${warning.message}`);
43
+ }
44
+ // Use default for other warnings
45
+ warn(warning);
46
+ },
47
+ },
48
+ },
49
+ }
50
+ : {}),
51
+ optimizeDeps: {
52
+ esbuildOptions: {
53
+ loader: {
54
+ ".js": "jsx",
55
+ },
56
+ ...(legacySDKImports
57
+ ? {
58
+ define: {
59
+ "process.env.VITE_BASE44_APP_ID": JSON.stringify(env.VITE_BASE44_APP_ID),
60
+ "process.env.VITE_BASE44_BACKEND_URL": JSON.stringify(env.VITE_BASE44_BACKEND_URL),
61
+ },
62
+ }
63
+ : {}),
64
+ },
65
+ },
66
+ };
67
+ },
68
+ resolveId(source, importer, options) {
69
+ if (legacySDKImports) {
70
+ if (source.includes("/entities")) {
71
+ return this.resolve("@base44/vite-plugin/compat/entities.cjs", importer, options);
72
+ }
73
+ if (source.includes("/functions")) {
74
+ return this.resolve("@base44/vite-plugin/compat/functions.cjs", importer, options);
75
+ }
76
+ if (source.includes("/integrations")) {
77
+ return this.resolve("@base44/vite-plugin/compat/integrations.cjs", importer, options);
78
+ }
79
+ if (source.includes("@/agents")) {
80
+ return this.resolve("@base44/vite-plugin/compat/agents.cjs", importer, options);
81
+ }
82
+ }
83
+ return null;
84
+ },
85
+ },
86
+ ...(isRunningInSandbox
87
+ ? [
88
+ {
89
+ name: "iframe-hmr",
90
+ configureServer(server) {
91
+ server.middlewares.use((req, res, next) => {
92
+ // Allow iframe embedding
93
+ res.setHeader("X-Frame-Options", "ALLOWALL");
94
+ res.setHeader("Content-Security-Policy", "frame-ancestors *;");
95
+ next();
96
+ });
97
+ },
98
+ },
99
+ errorOverlayPlugin(),
100
+ visualEditPlugin(),
101
+ ]
102
+ : []),
103
+ ];
104
+ }
105
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ export declare function visualEditPlugin(): Plugin;
3
+ //# sourceMappingURL=visual-edit-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-edit-plugin.d.ts","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AA+FnC,wBAAgB,gBAAgB,IAuKzB,MAAM,CACZ"}
@@ -0,0 +1,229 @@
1
+ import { parse } from "@babel/parser";
2
+ import { default as traverse } from "@babel/traverse";
3
+ import { default as generate } from "@babel/generator";
4
+ import * as t from "@babel/types";
5
+ // Helper function to check if JSX element contains dynamic content
6
+ function checkIfElementHasDynamicContent(jsxElement) {
7
+ let hasDynamicContent = false;
8
+ // Helper function to check if any node contains dynamic patterns
9
+ function checkNodeForDynamicContent(node) {
10
+ // JSX expressions like {variable}, {func()}, {obj.prop}
11
+ if (t.isJSXExpressionContainer(node)) {
12
+ const expression = node.expression;
13
+ // Skip empty expressions {}
14
+ if (t.isJSXEmptyExpression(expression)) {
15
+ return false;
16
+ }
17
+ // Any non-literal expression is considered dynamic
18
+ if (!t.isLiteral(expression)) {
19
+ return true;
20
+ }
21
+ }
22
+ // Template literals with expressions `Hello ${name}`
23
+ if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
24
+ return true;
25
+ }
26
+ // Member expressions like props.title, state.value
27
+ if (t.isMemberExpression(node)) {
28
+ return true;
29
+ }
30
+ // Function calls like getData(), format()
31
+ if (t.isCallExpression(node)) {
32
+ return true;
33
+ }
34
+ // Conditional expressions like condition ? "yes" : "no"
35
+ if (t.isConditionalExpression(node)) {
36
+ return true;
37
+ }
38
+ // Identifier references (could be props, state, variables)
39
+ if (t.isIdentifier(node)) {
40
+ // Common dynamic identifiers
41
+ const dynamicNames = [
42
+ "props",
43
+ "state",
44
+ "data",
45
+ "item",
46
+ "value",
47
+ "text",
48
+ "content",
49
+ ];
50
+ if (dynamicNames.some((name) => node.name.includes(name))) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ // Recursively traverse all child nodes
57
+ function traverseNode(node) {
58
+ if (checkNodeForDynamicContent(node)) {
59
+ hasDynamicContent = true;
60
+ return;
61
+ }
62
+ // Recursively check child nodes
63
+ Object.keys(node).forEach((key) => {
64
+ const value = node[key];
65
+ if (Array.isArray(value)) {
66
+ value.forEach((child) => {
67
+ if (child && typeof child === "object" && child.type) {
68
+ traverseNode(child);
69
+ }
70
+ });
71
+ }
72
+ else if (value && typeof value === "object" && value.type) {
73
+ traverseNode(value);
74
+ }
75
+ });
76
+ }
77
+ // Check all children of the JSX element
78
+ jsxElement.children.forEach((child) => {
79
+ if (hasDynamicContent)
80
+ return; // Early exit if already found dynamic content
81
+ traverseNode(child);
82
+ });
83
+ return hasDynamicContent;
84
+ }
85
+ export function visualEditPlugin() {
86
+ return {
87
+ name: "visual-edit-transform",
88
+ apply: (config) => config.mode === "development",
89
+ enforce: "pre",
90
+ order: "pre",
91
+ // Inject Tailwind CDN for visual editing capabilities
92
+ transformIndexHtml(html) {
93
+ // Inject the Tailwind CSS CDN script right before the closing </head> tag
94
+ const tailwindScript = ` <!-- Tailwind CSS CDN for visual editing -->\n <script src="https://cdn.tailwindcss.com"></script>\n `;
95
+ return html.replace("</head>", tailwindScript + "</head>");
96
+ },
97
+ transform(code, id) {
98
+ // Skip node_modules and visual-edit-agent itself
99
+ if (id.includes("node_modules") || id.includes("visual-edit-agent")) {
100
+ return null;
101
+ }
102
+ // Process JS/JSX/TS/TSX files
103
+ if (!id.match(/\.(jsx?|tsx?)$/)) {
104
+ return null;
105
+ }
106
+ // Extract filename from path, preserving pages/ or components/ structure
107
+ const pathParts = id.split("/");
108
+ let filename;
109
+ // Check if this is a pages or components file
110
+ if (id.includes("/pages/")) {
111
+ const pagesIndex = pathParts.findIndex((part) => part === "pages");
112
+ if (pagesIndex >= 0 && pagesIndex < pathParts.length - 1) {
113
+ // Get all parts from 'pages' to the file, preserving nested structure
114
+ const relevantParts = pathParts.slice(pagesIndex, pathParts.length);
115
+ const lastPart = relevantParts[relevantParts.length - 1];
116
+ // Remove file extension from the last part
117
+ relevantParts[relevantParts.length - 1] = lastPart.includes(".")
118
+ ? lastPart.split(".")[0]
119
+ : lastPart;
120
+ filename = relevantParts.join("/");
121
+ }
122
+ else {
123
+ filename = pathParts[pathParts.length - 1];
124
+ if (filename.includes(".")) {
125
+ filename = filename.split(".")[0];
126
+ }
127
+ }
128
+ }
129
+ else if (id.includes("/components/")) {
130
+ const componentsIndex = pathParts.findIndex((part) => part === "components");
131
+ if (componentsIndex >= 0 && componentsIndex < pathParts.length - 1) {
132
+ // Get all parts from 'components' to the file, preserving nested structure
133
+ const relevantParts = pathParts.slice(componentsIndex, pathParts.length);
134
+ const lastPart = relevantParts[relevantParts.length - 1];
135
+ // Remove file extension from the last part
136
+ relevantParts[relevantParts.length - 1] = lastPart.includes(".")
137
+ ? lastPart.split(".")[0]
138
+ : lastPart;
139
+ filename = relevantParts.join("/");
140
+ }
141
+ else {
142
+ filename = pathParts[pathParts.length - 1];
143
+ if (filename.includes(".")) {
144
+ filename = filename.split(".")[0];
145
+ }
146
+ }
147
+ }
148
+ else {
149
+ // For other files (like layout), just use the filename
150
+ filename = pathParts[pathParts.length - 1];
151
+ if (filename.includes(".")) {
152
+ filename = filename.split(".")[0];
153
+ }
154
+ }
155
+ try {
156
+ // Parse the code into an AST
157
+ const ast = parse(code, {
158
+ sourceType: "module",
159
+ plugins: [
160
+ "jsx",
161
+ "typescript",
162
+ "decorators-legacy",
163
+ "classProperties",
164
+ "objectRestSpread",
165
+ "functionBind",
166
+ "exportDefaultFrom",
167
+ "exportNamespaceFrom",
168
+ "dynamicImport",
169
+ "nullishCoalescingOperator",
170
+ "optionalChaining",
171
+ "asyncGenerators",
172
+ "bigInt",
173
+ "optionalCatchBinding",
174
+ "throwExpressions",
175
+ ],
176
+ });
177
+ // Traverse the AST and add source location and dynamic content attributes to JSX elements
178
+ let elementsProcessed = 0;
179
+ traverse.default(ast, {
180
+ JSXElement(path) {
181
+ const jsxElement = path.node;
182
+ const openingElement = jsxElement.openingElement;
183
+ // Skip fragments
184
+ if (t.isJSXFragment(jsxElement))
185
+ return;
186
+ // Skip if already has source location attribute
187
+ const hasSourceLocation = openingElement.attributes.some((attr) => t.isJSXAttribute(attr) &&
188
+ t.isJSXIdentifier(attr.name) &&
189
+ attr.name.name === "data-source-location");
190
+ if (hasSourceLocation)
191
+ return;
192
+ // Get line and column from AST node location
193
+ const { line, column } = openingElement.loc?.start || {
194
+ line: 1,
195
+ column: 0,
196
+ };
197
+ // Create the source location attribute
198
+ const sourceLocationAttr = t.jsxAttribute(t.jsxIdentifier("data-source-location"), t.stringLiteral(`${filename}:${line}:${column}`));
199
+ // Check if element has dynamic content
200
+ const isDynamic = checkIfElementHasDynamicContent(jsxElement);
201
+ // Create the dynamic content attribute
202
+ const dynamicContentAttr = t.jsxAttribute(t.jsxIdentifier("data-dynamic-content"), t.stringLiteral(isDynamic ? "true" : "false"));
203
+ // Add both attributes to the beginning of the attributes array
204
+ openingElement.attributes.unshift(sourceLocationAttr, dynamicContentAttr);
205
+ elementsProcessed++;
206
+ },
207
+ });
208
+ // Generate the code back from the AST
209
+ const result = generate.default(ast, {
210
+ compact: false,
211
+ concise: false,
212
+ retainLines: true,
213
+ });
214
+ return {
215
+ code: result.code,
216
+ map: null,
217
+ };
218
+ }
219
+ catch (error) {
220
+ console.error("Failed to add source location to JSX:", error);
221
+ return {
222
+ code: code, // Return original code on failure
223
+ map: null,
224
+ };
225
+ }
226
+ },
227
+ };
228
+ }
229
+ //# sourceMappingURL=visual-edit-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-edit-plugin.js","sourceRoot":"","sources":["../src/visual-edit-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAGlC,mEAAmE;AACnE,SAAS,+BAA+B,CAAC,UAAe;IACtD,IAAI,iBAAiB,GAAG,KAAK,CAAC;IAE9B,iEAAiE;IACjE,SAAS,0BAA0B,CAAC,IAAS;QAC3C,wDAAwD;QACxD,IAAI,CAAC,CAAC,wBAAwB,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;YAEnC,4BAA4B;YAC5B,IAAI,CAAC,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;gBACvC,OAAO,KAAK,CAAC;YACf,CAAC;YAED,mDAAmD;YACnD,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,qDAAqD;QACrD,IAAI,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,mDAAmD;QACnD,IAAI,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,0CAA0C;QAC1C,IAAI,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,wDAAwD;QACxD,IAAI,CAAC,CAAC,uBAAuB,CAAC,IAAI,CAAC,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,2DAA2D;QAC3D,IAAI,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,6BAA6B;YAC7B,MAAM,YAAY,GAAG;gBACnB,OAAO;gBACP,OAAO;gBACP,MAAM;gBACN,MAAM;gBACN,OAAO;gBACP,MAAM;gBACN,SAAS;aACV,CAAC;YACF,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC1D,OAAO,IAAI,CAAC;YACd,CAAC;QACH,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAED,uCAAuC;IACvC,SAAS,YAAY,CAAC,IAAS;QAC7B,IAAI,0BAA0B,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,iBAAiB,GAAG,IAAI,CAAC;YACzB,OAAO;QACT,CAAC;QAED,gCAAgC;QAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;YAChC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;YAExB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;oBACtB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;wBACrD,YAAY,CAAC,KAAK,CAAC,CAAC;oBACtB,CAAC;gBACH,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBAC5D,YAAY,CAAC,KAAK,CAAC,CAAC;YACtB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,wCAAwC;IACxC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAU,EAAE,EAAE;QACzC,IAAI,iBAAiB;YAAE,OAAO,CAAC,8CAA8C;QAC7E,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,aAAa;QAChD,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,KAAK;QACZ,sDAAsD;QACtD,kBAAkB,CAAC,IAAS;YAC1B,0EAA0E;YAC1E,MAAM,cAAc,GAAG,+GAA+G,CAAC;YACvI,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,CAAC,CAAC;QAC7D,CAAC;QACD,SAAS,CAAC,IAAS,EAAE,EAAO;YAC1B,iDAAiD;YACjD,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBACpE,OAAO,IAAI,CAAC;YACd,CAAC;YAED,8BAA8B;YAC9B,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBAChC,OAAO,IAAI,CAAC;YACd,CAAC;YAED,yEAAyE;YACzE,MAAM,SAAS,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,QAAQ,CAAC;YAEb,8CAA8C;YAC9C,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3B,MAAM,UAAU,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;gBACxE,IAAI,UAAU,IAAI,CAAC,IAAI,UAAU,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACzD,sEAAsE;oBACtE,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;oBACpE,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;oBACzD,2CAA2C;oBAC3C,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAC9D,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;wBACxB,CAAC,CAAC,QAAQ,CAAC;oBACb,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACrC,CAAC;qBAAM,CAAC;oBACN,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;oBAC3C,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;wBAC3B,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,IAAI,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBACvC,MAAM,eAAe,GAAG,SAAS,CAAC,SAAS,CACzC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,KAAK,YAAY,CACrC,CAAC;gBACF,IAAI,eAAe,IAAI,CAAC,IAAI,eAAe,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACnE,2EAA2E;oBAC3E,MAAM,aAAa,GAAG,SAAS,CAAC,KAAK,CACnC,eAAe,EACf,SAAS,CAAC,MAAM,CACjB,CAAC;oBACF,MAAM,QAAQ,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;oBACzD,2CAA2C;oBAC3C,aAAa,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;wBAC9D,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;wBACxB,CAAC,CAAC,QAAQ,CAAC;oBACb,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACrC,CAAC;qBAAM,CAAC;oBACN,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;oBAC3C,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;wBAC3B,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,uDAAuD;gBACvD,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC3C,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC3B,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YAED,IAAI,CAAC;gBACH,6BAA6B;gBAC7B,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,EAAE;oBACtB,UAAU,EAAE,QAAQ;oBACpB,OAAO,EAAE;wBACP,KAAK;wBACL,YAAY;wBACZ,mBAAmB;wBACnB,iBAAiB;wBACjB,kBAAkB;wBAClB,cAAc;wBACd,mBAAmB;wBACnB,qBAAqB;wBACrB,eAAe;wBACf,2BAA2B;wBAC3B,kBAAkB;wBAClB,iBAAiB;wBACjB,QAAQ;wBACR,sBAAsB;wBACtB,kBAAkB;qBACnB;iBACF,CAAC,CAAC;gBAEH,0FAA0F;gBAC1F,IAAI,iBAAiB,GAAG,CAAC,CAAC;gBAC1B,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE;oBACpB,UAAU,CAAC,IAAI;wBACb,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;wBAC7B,MAAM,cAAc,GAAG,UAAU,CAAC,cAAc,CAAC;wBAEjD,iBAAiB;wBACjB,IAAI,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC;4BAAE,OAAO;wBAExC,gDAAgD;wBAChD,MAAM,iBAAiB,GAAG,cAAc,CAAC,UAAU,CAAC,IAAI,CACtD,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC;4BACtB,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;4BAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,sBAAsB,CAC5C,CAAC;wBAEF,IAAI,iBAAiB;4BAAE,OAAO;wBAE9B,6CAA6C;wBAC7C,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,GAAG,EAAE,KAAK,IAAI;4BACpD,IAAI,EAAE,CAAC;4BACP,MAAM,EAAE,CAAC;yBACV,CAAC;wBAEF,uCAAuC;wBACvC,MAAM,kBAAkB,GAAG,CAAC,CAAC,YAAY,CACvC,CAAC,CAAC,aAAa,CAAC,sBAAsB,CAAC,EACvC,CAAC,CAAC,aAAa,CAAC,GAAG,QAAQ,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC,CACjD,CAAC;wBAEF,uCAAuC;wBACvC,MAAM,SAAS,GAAG,+BAA+B,CAAC,UAAU,CAAC,CAAC;wBAE9D,uCAAuC;wBACvC,MAAM,kBAAkB,GAAG,CAAC,CAAC,YAAY,CACvC,CAAC,CAAC,aAAa,CAAC,sBAAsB,CAAC,EACvC,CAAC,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAC9C,CAAC;wBAEF,+DAA+D;wBAC/D,cAAc,CAAC,UAAU,CAAC,OAAO,CAC/B,kBAAkB,EAClB,kBAAkB,CACnB,CAAC;wBACF,iBAAiB,EAAE,CAAC;oBACtB,CAAC;iBACF,CAAC,CAAC;gBAEH,sCAAsC;gBACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE;oBACnC,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,KAAK;oBACd,WAAW,EAAE,IAAI;iBAClB,CAAC,CAAC;gBAEH,OAAO;oBACL,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,GAAG,EAAE,IAAI;iBACV,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,KAAK,CAAC,CAAC;gBAC9D,OAAO;oBACL,IAAI,EAAE,IAAI,EAAE,kCAAkC;oBAC9C,GAAG,EAAE,IAAI;iBACV,CAAC;YACJ,CAAC;QACH,CAAC;KACQ,CAAC;AACd,CAAC"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@base44/vite-plugin",
3
+ "version": "0.1.0",
4
+ "description": "The Vite plugin for base44 based applications",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": "./dist/index.js",
9
+ "./compat/*": "./compat/*"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src",
14
+ "compat"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc"
18
+ },
19
+ "devDependencies": {
20
+ "@types/babel__generator": "^7.27.0",
21
+ "@types/babel__traverse": "^7.28.0",
22
+ "@types/node": "^24.6.2",
23
+ "typescript": "^5.9.3",
24
+ "vite": "^7.1.9"
25
+ },
26
+ "dependencies": {
27
+ "@babel/generator": "^7.28.5",
28
+ "@babel/parser": "^7.28.5",
29
+ "@babel/traverse": "^7.28.5",
30
+ "@babel/types": "^7.28.5"
31
+ }
32
+ }
@@ -0,0 +1,71 @@
1
+ /// <reference lib="dom" />
2
+
3
+ // Make HTMLElement available in non-browser environments
4
+ const { HTMLElement = class {} } = globalThis;
5
+
6
+ export class ErrorOverlay extends HTMLElement {
7
+ static getOverlayHTML() {
8
+ return `
9
+ <div>
10
+ </div>
11
+ `;
12
+ }
13
+ close() {
14
+ (this as any).parentNode?.removeChild(this);
15
+ }
16
+
17
+ static sendErrorToParent(
18
+ error: Error,
19
+ title: string,
20
+ details: string | undefined,
21
+ componentName: string | undefined
22
+ ) {
23
+ // Send error to parent using framewire
24
+ if (globalThis.window?.parent) {
25
+ try {
26
+ globalThis.window.parent?.postMessage(
27
+ {
28
+ type: "app_error",
29
+ error: { title, details, componentName, originalError: error },
30
+ },
31
+ "*"
32
+ );
33
+ } catch (error) {
34
+ console.warn(
35
+ "Failed to send error to iframe parent:",
36
+ (error as Error)?.message
37
+ );
38
+ }
39
+ }
40
+ }
41
+
42
+ constructor(error: Error) {
43
+ super();
44
+
45
+ const stack = error?.stack;
46
+ let componentName = stack?.match(/at\s+(\w+)\s+\(eval/)?.[1];
47
+ if (componentName === "eval") {
48
+ componentName = undefined;
49
+ }
50
+ const title = componentName
51
+ ? `in ${componentName}: ${error.message?.toString()}`
52
+ : error.message?.toString();
53
+ const details = error?.stack;
54
+
55
+ // Call editor frame with the error (via post message)
56
+ ErrorOverlay.sendErrorToParent(error, title, details, componentName);
57
+
58
+ // Create the overlay element using HTML template
59
+ const overlay = document.createElement("div");
60
+ overlay.innerHTML = ErrorOverlay.getOverlayHTML();
61
+
62
+ // Add to DOM
63
+ document.body.appendChild(overlay);
64
+ }
65
+ }
66
+
67
+ // vite/react-plugin transpiles classes with _SomeClass, so we need to replace all _ErrorOverlay with ErrorOverlay
68
+ export const errorOverlayCode = ErrorOverlay.toString().replaceAll(
69
+ "_ErrorOverlay",
70
+ "ErrorOverlay"
71
+ );
@@ -0,0 +1,19 @@
1
+ import { errorOverlayCode } from "./ErrorOverlay.js";
2
+ import type { Plugin } from "vite";
3
+
4
+ export function errorOverlayPlugin() {
5
+ return {
6
+ name: "error-overlay",
7
+ apply: (config) => config.mode === "development",
8
+ transform(code, id, opts = {}) {
9
+ if (opts?.ssr) return;
10
+
11
+ if (!id.includes("vite/dist/client/client.mjs")) return;
12
+
13
+ return code.replace(
14
+ "class ErrorOverlay",
15
+ errorOverlayCode + "\nclass OldErrorOverlay"
16
+ );
17
+ },
18
+ } as Plugin;
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,139 @@
1
+ import type { Plugin, UserConfig } from "vite";
2
+ import { loadEnv } from "vite";
3
+ import { errorOverlayPlugin } from "./error-overlay-plugin.js";
4
+ import { visualEditPlugin } from "./visual-edit-plugin.js";
5
+
6
+ const isRunningInSandbox = !!process.env.MODAL_SANDBOX_ID;
7
+
8
+ export default function vitePlugin(
9
+ opts: {
10
+ legacySDKImports?: boolean;
11
+ } = {}
12
+ ) {
13
+ const { legacySDKImports = false } = opts;
14
+
15
+ return [
16
+ {
17
+ name: "base44",
18
+ config: ({ mode }) => {
19
+ const env = loadEnv(mode ?? "development", process.cwd(), "");
20
+
21
+ return {
22
+ resolve: {
23
+ alias: {
24
+ "@/": "/src/",
25
+ },
26
+ },
27
+ ...(isRunningInSandbox
28
+ ? ({
29
+ server: {
30
+ host: "0.0.0.0", // Bind to all interfaces for container access
31
+ port: 5173,
32
+ strictPort: true,
33
+ // Allow all hosts - essential for Modal tunnel URLs
34
+ allowedHosts: true,
35
+ watch: {
36
+ // Enable polling for better file change detection in containers
37
+ usePolling: true,
38
+ interval: 100, // Check every 100ms for responsive HMR
39
+ },
40
+ hmr: {
41
+ protocol: "wss",
42
+ clientPort: 443,
43
+ },
44
+ },
45
+ build: {
46
+ rollupOptions: {
47
+ onwarn(warning, warn) {
48
+ // Treat import errors as fatal errors
49
+ if (
50
+ warning.code === "UNRESOLVED_IMPORT" ||
51
+ warning.code === "MISSING_EXPORT"
52
+ ) {
53
+ throw new Error(`Build failed: ${warning.message}`);
54
+ }
55
+ // Use default for other warnings
56
+ warn(warning);
57
+ },
58
+ },
59
+ },
60
+ } as Partial<UserConfig>)
61
+ : {}),
62
+ optimizeDeps: {
63
+ esbuildOptions: {
64
+ loader: {
65
+ ".js": "jsx",
66
+ },
67
+ ...(legacySDKImports
68
+ ? {
69
+ define: {
70
+ "process.env.VITE_BASE44_APP_ID": JSON.stringify(
71
+ env.VITE_BASE44_APP_ID
72
+ ),
73
+ "process.env.VITE_BASE44_BACKEND_URL": JSON.stringify(
74
+ env.VITE_BASE44_BACKEND_URL
75
+ ),
76
+ },
77
+ }
78
+ : {}),
79
+ },
80
+ },
81
+ };
82
+ },
83
+ resolveId(source, importer, options) {
84
+ if (legacySDKImports) {
85
+ if (source.includes("/entities")) {
86
+ return this.resolve(
87
+ "@base44/vite-plugin/compat/entities.cjs",
88
+ importer,
89
+ options
90
+ );
91
+ }
92
+
93
+ if (source.includes("/functions")) {
94
+ return this.resolve(
95
+ "@base44/vite-plugin/compat/functions.cjs",
96
+ importer,
97
+ options
98
+ );
99
+ }
100
+
101
+ if (source.includes("/integrations")) {
102
+ return this.resolve(
103
+ "@base44/vite-plugin/compat/integrations.cjs",
104
+ importer,
105
+ options
106
+ );
107
+ }
108
+
109
+ if (source.includes("@/agents")) {
110
+ return this.resolve(
111
+ "@base44/vite-plugin/compat/agents.cjs",
112
+ importer,
113
+ options
114
+ );
115
+ }
116
+ }
117
+
118
+ return null;
119
+ },
120
+ } as Plugin,
121
+ ...(isRunningInSandbox
122
+ ? [
123
+ {
124
+ name: "iframe-hmr",
125
+ configureServer(server) {
126
+ server.middlewares.use((req, res, next) => {
127
+ // Allow iframe embedding
128
+ res.setHeader("X-Frame-Options", "ALLOWALL");
129
+ res.setHeader("Content-Security-Policy", "frame-ancestors *;");
130
+ next();
131
+ });
132
+ },
133
+ } as Plugin,
134
+ errorOverlayPlugin(),
135
+ visualEditPlugin(),
136
+ ]
137
+ : []),
138
+ ];
139
+ }
@@ -0,0 +1,268 @@
1
+ import { parse } from "@babel/parser";
2
+ import { default as traverse } from "@babel/traverse";
3
+ import { default as generate } from "@babel/generator";
4
+ import * as t from "@babel/types";
5
+ import type { Plugin } from "vite";
6
+
7
+ // Helper function to check if JSX element contains dynamic content
8
+ function checkIfElementHasDynamicContent(jsxElement: any) {
9
+ let hasDynamicContent = false;
10
+
11
+ // Helper function to check if any node contains dynamic patterns
12
+ function checkNodeForDynamicContent(node: any) {
13
+ // JSX expressions like {variable}, {func()}, {obj.prop}
14
+ if (t.isJSXExpressionContainer(node)) {
15
+ const expression = node.expression;
16
+
17
+ // Skip empty expressions {}
18
+ if (t.isJSXEmptyExpression(expression)) {
19
+ return false;
20
+ }
21
+
22
+ // Any non-literal expression is considered dynamic
23
+ if (!t.isLiteral(expression)) {
24
+ return true;
25
+ }
26
+ }
27
+
28
+ // Template literals with expressions `Hello ${name}`
29
+ if (t.isTemplateLiteral(node) && node.expressions.length > 0) {
30
+ return true;
31
+ }
32
+
33
+ // Member expressions like props.title, state.value
34
+ if (t.isMemberExpression(node)) {
35
+ return true;
36
+ }
37
+
38
+ // Function calls like getData(), format()
39
+ if (t.isCallExpression(node)) {
40
+ return true;
41
+ }
42
+
43
+ // Conditional expressions like condition ? "yes" : "no"
44
+ if (t.isConditionalExpression(node)) {
45
+ return true;
46
+ }
47
+
48
+ // Identifier references (could be props, state, variables)
49
+ if (t.isIdentifier(node)) {
50
+ // Common dynamic identifiers
51
+ const dynamicNames = [
52
+ "props",
53
+ "state",
54
+ "data",
55
+ "item",
56
+ "value",
57
+ "text",
58
+ "content",
59
+ ];
60
+ if (dynamicNames.some((name) => node.name.includes(name))) {
61
+ return true;
62
+ }
63
+ }
64
+
65
+ return false;
66
+ }
67
+
68
+ // Recursively traverse all child nodes
69
+ function traverseNode(node: any) {
70
+ if (checkNodeForDynamicContent(node)) {
71
+ hasDynamicContent = true;
72
+ return;
73
+ }
74
+
75
+ // Recursively check child nodes
76
+ Object.keys(node).forEach((key) => {
77
+ const value = node[key];
78
+
79
+ if (Array.isArray(value)) {
80
+ value.forEach((child) => {
81
+ if (child && typeof child === "object" && child.type) {
82
+ traverseNode(child);
83
+ }
84
+ });
85
+ } else if (value && typeof value === "object" && value.type) {
86
+ traverseNode(value);
87
+ }
88
+ });
89
+ }
90
+
91
+ // Check all children of the JSX element
92
+ jsxElement.children.forEach((child: any) => {
93
+ if (hasDynamicContent) return; // Early exit if already found dynamic content
94
+ traverseNode(child);
95
+ });
96
+
97
+ return hasDynamicContent;
98
+ }
99
+
100
+ export function visualEditPlugin() {
101
+ return {
102
+ name: "visual-edit-transform",
103
+ apply: (config) => config.mode === "development",
104
+ enforce: "pre",
105
+ order: "pre",
106
+ // Inject Tailwind CDN for visual editing capabilities
107
+ transformIndexHtml(html: any) {
108
+ // Inject the Tailwind CSS CDN script right before the closing </head> tag
109
+ const tailwindScript = ` <!-- Tailwind CSS CDN for visual editing -->\n <script src="https://cdn.tailwindcss.com"></script>\n `;
110
+ return html.replace("</head>", tailwindScript + "</head>");
111
+ },
112
+ transform(code: any, id: any) {
113
+ // Skip node_modules and visual-edit-agent itself
114
+ if (id.includes("node_modules") || id.includes("visual-edit-agent")) {
115
+ return null;
116
+ }
117
+
118
+ // Process JS/JSX/TS/TSX files
119
+ if (!id.match(/\.(jsx?|tsx?)$/)) {
120
+ return null;
121
+ }
122
+
123
+ // Extract filename from path, preserving pages/ or components/ structure
124
+ const pathParts = id.split("/");
125
+ let filename;
126
+
127
+ // Check if this is a pages or components file
128
+ if (id.includes("/pages/")) {
129
+ const pagesIndex = pathParts.findIndex((part: any) => part === "pages");
130
+ if (pagesIndex >= 0 && pagesIndex < pathParts.length - 1) {
131
+ // Get all parts from 'pages' to the file, preserving nested structure
132
+ const relevantParts = pathParts.slice(pagesIndex, pathParts.length);
133
+ const lastPart = relevantParts[relevantParts.length - 1];
134
+ // Remove file extension from the last part
135
+ relevantParts[relevantParts.length - 1] = lastPart.includes(".")
136
+ ? lastPart.split(".")[0]
137
+ : lastPart;
138
+ filename = relevantParts.join("/");
139
+ } else {
140
+ filename = pathParts[pathParts.length - 1];
141
+ if (filename.includes(".")) {
142
+ filename = filename.split(".")[0];
143
+ }
144
+ }
145
+ } else if (id.includes("/components/")) {
146
+ const componentsIndex = pathParts.findIndex(
147
+ (part: any) => part === "components"
148
+ );
149
+ if (componentsIndex >= 0 && componentsIndex < pathParts.length - 1) {
150
+ // Get all parts from 'components' to the file, preserving nested structure
151
+ const relevantParts = pathParts.slice(
152
+ componentsIndex,
153
+ pathParts.length
154
+ );
155
+ const lastPart = relevantParts[relevantParts.length - 1];
156
+ // Remove file extension from the last part
157
+ relevantParts[relevantParts.length - 1] = lastPart.includes(".")
158
+ ? lastPart.split(".")[0]
159
+ : lastPart;
160
+ filename = relevantParts.join("/");
161
+ } else {
162
+ filename = pathParts[pathParts.length - 1];
163
+ if (filename.includes(".")) {
164
+ filename = filename.split(".")[0];
165
+ }
166
+ }
167
+ } else {
168
+ // For other files (like layout), just use the filename
169
+ filename = pathParts[pathParts.length - 1];
170
+ if (filename.includes(".")) {
171
+ filename = filename.split(".")[0];
172
+ }
173
+ }
174
+
175
+ try {
176
+ // Parse the code into an AST
177
+ const ast = parse(code, {
178
+ sourceType: "module",
179
+ plugins: [
180
+ "jsx",
181
+ "typescript",
182
+ "decorators-legacy",
183
+ "classProperties",
184
+ "objectRestSpread",
185
+ "functionBind",
186
+ "exportDefaultFrom",
187
+ "exportNamespaceFrom",
188
+ "dynamicImport",
189
+ "nullishCoalescingOperator",
190
+ "optionalChaining",
191
+ "asyncGenerators",
192
+ "bigInt",
193
+ "optionalCatchBinding",
194
+ "throwExpressions",
195
+ ],
196
+ });
197
+
198
+ // Traverse the AST and add source location and dynamic content attributes to JSX elements
199
+ let elementsProcessed = 0;
200
+ traverse.default(ast, {
201
+ JSXElement(path) {
202
+ const jsxElement = path.node;
203
+ const openingElement = jsxElement.openingElement;
204
+
205
+ // Skip fragments
206
+ if (t.isJSXFragment(jsxElement)) return;
207
+
208
+ // Skip if already has source location attribute
209
+ const hasSourceLocation = openingElement.attributes.some(
210
+ (attr) =>
211
+ t.isJSXAttribute(attr) &&
212
+ t.isJSXIdentifier(attr.name) &&
213
+ attr.name.name === "data-source-location"
214
+ );
215
+
216
+ if (hasSourceLocation) return;
217
+
218
+ // Get line and column from AST node location
219
+ const { line, column } = openingElement.loc?.start || {
220
+ line: 1,
221
+ column: 0,
222
+ };
223
+
224
+ // Create the source location attribute
225
+ const sourceLocationAttr = t.jsxAttribute(
226
+ t.jsxIdentifier("data-source-location"),
227
+ t.stringLiteral(`${filename}:${line}:${column}`)
228
+ );
229
+
230
+ // Check if element has dynamic content
231
+ const isDynamic = checkIfElementHasDynamicContent(jsxElement);
232
+
233
+ // Create the dynamic content attribute
234
+ const dynamicContentAttr = t.jsxAttribute(
235
+ t.jsxIdentifier("data-dynamic-content"),
236
+ t.stringLiteral(isDynamic ? "true" : "false")
237
+ );
238
+
239
+ // Add both attributes to the beginning of the attributes array
240
+ openingElement.attributes.unshift(
241
+ sourceLocationAttr,
242
+ dynamicContentAttr
243
+ );
244
+ elementsProcessed++;
245
+ },
246
+ });
247
+
248
+ // Generate the code back from the AST
249
+ const result = generate.default(ast, {
250
+ compact: false,
251
+ concise: false,
252
+ retainLines: true,
253
+ });
254
+
255
+ return {
256
+ code: result.code,
257
+ map: null,
258
+ };
259
+ } catch (error) {
260
+ console.error("Failed to add source location to JSX:", error);
261
+ return {
262
+ code: code, // Return original code on failure
263
+ map: null,
264
+ };
265
+ }
266
+ },
267
+ } as Plugin;
268
+ }