@apexkit/cli 1.0.3 → 1.0.5

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/LICENSE CHANGED
File without changes
package/README.md CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apexkit/cli",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "The official CLI and developer sync engine for ApexKit",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -138,10 +138,163 @@ const TRIGGER_DEFINITIONS = [
138
138
  { id: "after_ai_run", name: "after_ai_run", desc: "Runs after AI Prompt execution." },
139
139
  { id: "on_vectorization_start", name: "on_vectorization_start", desc: "Fires when revectorization job begins." },
140
140
  { id: "cron", name: "cron (Scheduled Job)", desc: "Executed periodically according to Cron expression settings." },
141
- { id: "graphql", name: "graphql (Custom Resolver)", desc: "Dynamic Query/Mutation field resolver for GraphQL schema." }
141
+ { id: "graphql", name: "graphql (Custom Resolver)", desc: "Dynamic Query/Mutation field resolver for GraphQL schema." },
142
+ { id: "before_realtime_connection_established", name: "before_realtime_connection_established", desc: "Runs before an incoming SSE or WebSocket connection is accepted. Can block connections." },
143
+ { id: "before_realtime_data_pushed", name: "before_realtime_data_pushed", desc: "Intercepts and mutates real-time event payloads right before sending to a client." },
144
+ { id: "after_realtime_connection_desconnected", name: "after_realtime_connection_desconnected", desc: "Runs after an SSE or WebSocket client disconnects or connection is dropped." },
142
145
  ];
143
146
 
144
- // --- 4. ACCURATE WORKSPACE TYPES & CONFIG GENERATOR ---
147
+ // --- 4. AUTOMATED REMOTE TYPE RESOLUTION & SCANNER ---
148
+ function scanWorkspaceForRemoteUrls() {
149
+ const urls = new Set();
150
+ const dirs = ["./webhooks", "./modules", "./templates"];
151
+ const urlRegex =
152
+ /(?:import|export)\s+(?:[\s\S]*?from\s+)?["'](https?:\/\/[^"']+)["']|import\s*\(\s*["'](https?:\/\/[^"']+)["']\s*\)|@jsxImportSource\s+(https?:\/\/[^\s*]+)/g;
153
+
154
+ dirs.forEach((dir) => {
155
+ if (!fs.existsSync(dir)) return;
156
+ const files = fs.readdirSync(dir, { recursive: true });
157
+ files.forEach((file) => {
158
+ const fullPath = path.join(dir, file);
159
+ if (fs.statSync(fullPath).isFile()) {
160
+ const ext = path.extname(fullPath);
161
+ if ([".js", ".ts", ".jsx", ".tsx", ".html"].includes(ext)) {
162
+ try {
163
+ const content = fs.readFileSync(fullPath, "utf-8");
164
+ let match;
165
+ while ((match = urlRegex.exec(content)) !== null) {
166
+ const url = match[1] || match[2] || match[3];
167
+ if (url) urls.add(url.trim());
168
+ }
169
+ } catch (e) {}
170
+ }
171
+ }
172
+ });
173
+ });
174
+
175
+ return Array.from(urls);
176
+ }
177
+
178
+ async function resolveRemoteTypeUrl(importUrl) {
179
+ try {
180
+ const res = await fetch(importUrl, {
181
+ method: "GET",
182
+ headers: { "User-Agent": "ApexKit-CLI/1.0" },
183
+ redirect: "follow",
184
+ });
185
+
186
+ const dtsHeader = res.headers.get("x-typescript-types");
187
+ if (dtsHeader) {
188
+ return dtsHeader.startsWith("http") ? dtsHeader : new URL(dtsHeader, importUrl).href;
189
+ }
190
+
191
+ if (importUrl.endsWith(".d.ts")) {
192
+ return importUrl;
193
+ }
194
+
195
+ if (importUrl.includes("esm.sh")) {
196
+ const dtsParamUrl = importUrl.includes("?") ? `${importUrl}&dts` : `${importUrl}?dts`;
197
+ const paramRes = await fetch(dtsParamUrl, { method: "HEAD", redirect: "follow" });
198
+ const paramHeader = paramRes.headers.get("x-typescript-types");
199
+ if (paramHeader) {
200
+ return paramHeader.startsWith("http") ? paramHeader : new URL(paramHeader, importUrl).href;
201
+ }
202
+ }
203
+ } catch (e) {}
204
+ return null;
205
+ }
206
+
207
+ /**
208
+ * Recursively fetches a .d.ts file and all relative imports/exports inside it
209
+ */
210
+ async function downloadDtsTree(currentDtsUrl, baseDir, relativeSubpath = "index.d.ts", visited = new Set()) {
211
+ if (visited.has(currentDtsUrl)) return;
212
+ visited.add(currentDtsUrl);
213
+
214
+ const localFilePath = path.join(baseDir, relativeSubpath);
215
+ fs.mkdirSync(path.dirname(localFilePath), { recursive: true });
216
+
217
+ try {
218
+ const res = await fetch(currentDtsUrl, {
219
+ headers: { "User-Agent": "ApexKit-CLI/1.0" },
220
+ redirect: "follow",
221
+ });
222
+ if (!res.ok) return;
223
+
224
+ let content = await res.text();
225
+
226
+ // Regex to locate relative imports or exports like:
227
+ // import { Hono } from './hono.d.ts';
228
+ // export * from './types.d.ts';
229
+ const relImportRegex = /(?:import|export)\s+[\s\S]*?from\s+["'](\.[^"']+)["']/g;
230
+ const relMatches = [];
231
+ let match;
232
+ while ((match = relImportRegex.exec(content)) !== null) {
233
+ relMatches.push(match[1]);
234
+ }
235
+
236
+ // Write file locally
237
+ fs.writeFileSync(localFilePath, content);
238
+
239
+ // Recursively pull all dependencies relative to current URL
240
+ for (const relPath of relMatches) {
241
+ // Normalize extension: ensure it resolves to .d.ts on the CDN
242
+ let targetRel = relPath;
243
+ if (!targetRel.endsWith(".d.ts") && !targetRel.endsWith(".ts")) {
244
+ targetRel = `${targetRel}.d.ts`;
245
+ }
246
+
247
+ const nextUrl = new URL(targetRel, currentDtsUrl).href;
248
+ const nextLocalSubpath = path.normalize(
249
+ path.join(path.dirname(relativeSubpath), targetRel.replace(/^\.\//, ""))
250
+ );
251
+
252
+ await downloadDtsTree(nextUrl, baseDir, nextLocalSubpath, visited);
253
+ }
254
+ } catch (err) {
255
+ console.warn(` ⚠️ Failed to recursively pull: ${currentDtsUrl} -> ${err.message}`);
256
+ }
257
+ }
258
+
259
+ async function pullRemoteModuleTypes(urls) {
260
+ const typesDir = path.join(".apexkit", "types");
261
+ fs.mkdirSync(typesDir, { recursive: true });
262
+
263
+ const resolvedPaths = {};
264
+
265
+ for (const rawUrl of urls) {
266
+ try {
267
+ // Use directory container for each package to support relative child imports cleanly
268
+ const urlFolder = rawUrl.replace(/[^a-zA-Z0-9_.-]/g, "_");
269
+ const packageDir = path.join(typesDir, urlFolder);
270
+ const rootDtsFile = path.join(packageDir, "index.d.ts");
271
+
272
+ if (fs.existsSync(rootDtsFile) && fs.statSync(rootDtsFile).size > 0) {
273
+ resolvedPaths[rawUrl] = `./${rootDtsFile.replace(/\\/g, "/")}`;
274
+ continue;
275
+ }
276
+
277
+ console.log(` 🌐 [Types]: Resolving remote type tree for ${rawUrl}...`);
278
+ const dtsUrl = await resolveRemoteTypeUrl(rawUrl);
279
+
280
+ if (dtsUrl) {
281
+ fs.mkdirSync(packageDir, { recursive: true });
282
+ await downloadDtsTree(dtsUrl, packageDir, "index.d.ts");
283
+
284
+ if (fs.existsSync(rootDtsFile)) {
285
+ resolvedPaths[rawUrl] = `./${rootDtsFile.replace(/\\/g, "/")}`;
286
+ console.log(` 📦 [Types]: Successfully cached dependency tree in ${packageDir}`);
287
+ }
288
+ }
289
+ } catch (err) {
290
+ console.warn(` ⚠️ Could not pull remote types for ${rawUrl}: ${err.message}`);
291
+ }
292
+ }
293
+
294
+ return resolvedPaths;
295
+ }
296
+
297
+ // --- 5. ACCURATE WORKSPACE TYPES & CONFIG GENERATOR ---
145
298
  async function generateWorkspaceFiles() {
146
299
  console.log(" ⏳ Fetching collection schemas from DB...");
147
300
  let collectionsData = [];
@@ -332,6 +485,13 @@ export interface FileMetadata {
332
485
  visibility?: ScriptVisibility;
333
486
  }
334
487
 
488
+ export type ApexKitDb = typeof $db;
489
+ export type ApexKitHttp = typeof $http;
490
+ export type ApexKitFiles = typeof $files;
491
+ export type ApexKitFs = typeof $fs;
492
+ export type ApexKitCache = typeof $cache;
493
+ export type ApexKitEnv = typeof $env;
494
+
335
495
  export interface AuthContext {
336
496
  id: number | string;
337
497
  email: string;
@@ -369,6 +529,19 @@ export interface VoidHookEvent {
369
529
  }
370
530
 
371
531
  declare global {
532
+ namespace JSX {
533
+ interface Element extends Promise<string>, String {
534
+ [key: string]: any;
535
+ toString(): string;
536
+ }
537
+ interface IntrinsicElements {
538
+ [elemName: string]: any;
539
+ }
540
+ interface ElementChildrenAttribute {
541
+ children: {};
542
+ }
543
+ }
544
+
372
545
  const __fileMetadata__: FileMetadata;
373
546
 
374
547
  /** ApexKit Database Client */
@@ -597,50 +770,122 @@ declare global {
597
770
  init?: { method?: string; headers?: any; body?: any; redirect?: "follow" | "manual" | "error" }
598
771
  ): Promise<Response>;
599
772
  }
773
+
774
+ // --- RUNTIME MODULE DEFINITIONS ---
775
+ declare module "react/jsx-runtime" {
776
+ export const jsx: any;
777
+ export const jsxs: any;
778
+ export const Fragment: any;
779
+ }
780
+
781
+ declare module "react/jsx-dev-runtime" {
782
+ export const jsxDEV: any;
783
+ export const Fragment: any;
784
+ }
785
+
786
+ declare module "hono/jsx/jsx-runtime" {
787
+ export const jsx: any;
788
+ export const jsxs: any;
789
+ export const Fragment: any;
790
+ }
791
+
792
+ declare module "hono/jsx" {
793
+ export const jsx: any;
794
+ export const jsxs: any;
795
+ export const Fragment: any;
796
+ export type FC<T = any> = (props: T) => any;
797
+ }
798
+
799
+ // --- HONO CONTEXT OVERLOADS ---
800
+ declare module "hono" {
801
+ interface Context {
802
+ html(html: string | Promise<string> | JSX.Element | any, status?: any, headers?: any): Response | Promise<Response>;
803
+ }
804
+ }
805
+
806
+ // --- REMOTE URL MODULE DECLARATIONS & FALLBACKS ---
807
+ declare module "https://*" {
808
+ const content: any;
809
+ export default content;
810
+ export * from "https://*";
811
+ }
812
+
813
+ declare module "http://*" {
814
+ const content: any;
815
+ export default content;
816
+ export * from "http://*";
817
+ }
818
+
600
819
  export {};
601
820
  `;
602
821
  fs.writeFileSync("apexkit.d.ts", types.trim());
603
- console.log(" 📄 Generated apexkit.d.ts with Accurate Scaled & Relational Typings");
822
+ console.log(" 📄 Generated apexkit.d.ts with Accurate Scaled Typings & Universal JSX");
823
+
824
+ // Dynamically scan all files and auto-pull remote module types
825
+ const remoteUrls = scanWorkspaceForRemoteUrls();
826
+ const resolvedRemoteTypes = await pullRemoteModuleTypes(remoteUrls);
827
+
828
+ const dynamicPaths = {
829
+ "@/custom/*": ["./modules/custom/*"],
830
+ "@/esm/*": ["./modules/esm/*"],
831
+ };
832
+
833
+ // Map each pulled remote URL to its local cached definition
834
+ for (const [remoteUrl, localPath] of Object.entries(resolvedRemoteTypes)) {
835
+ dynamicPaths[remoteUrl] = [localPath];
836
+ dynamicPaths[`${remoteUrl}/*`] = [`${localPath}/*`];
837
+ }
604
838
 
605
839
  const jsconfig = {
606
840
  compilerOptions: {
607
841
  target: "ES2022",
608
842
  module: "ESNext",
609
843
  ignoreDeprecations: "6.0",
610
- moduleResolution: "node",
844
+ moduleResolution: "bundler",
845
+ allowImportingTsExtensions: true,
846
+ jsx: "preserve",
611
847
  baseUrl: ".",
612
- paths: {
613
- "@/custom/*": ["./modules/custom/*"],
614
- "@/esm/*": ["./modules/esm/*"],
615
- },
848
+ paths: dynamicPaths,
616
849
  allowJs: true,
617
850
  checkJs: true,
618
851
  },
619
- include: ["**/*.js", "**/*.ts", "**/*.html", "apexkit.d.ts"],
852
+ include: [
853
+ "**/*.js",
854
+ "**/*.ts",
855
+ "**/*.jsx",
856
+ "**/*.tsx",
857
+ "**/*.html",
858
+ "apexkit.d.ts",
859
+ ".apexkit/types/**/*.d.ts",
860
+ ],
620
861
  };
621
862
  fs.writeFileSync("jsconfig.json", JSON.stringify(jsconfig, null, 2));
622
- console.log(" 📄 Generated jsconfig.json for module resolution");
863
+ console.log(" 📄 Generated jsconfig.json with Auto-Mapped Remote Types & JSX Support");
623
864
  }
624
865
 
625
866
  // --- 5. INITIAL CODE GENERATOR ---
626
867
  function getBoilerplateCode(fileType, cleanName, ext, triggerType, targetCollection) {
868
+ const metadataPath =
869
+ fileType === "template"
870
+ ? "./templates/"
871
+ : fileType.includes("module")
872
+ ? "./modules/custom/"
873
+ : "./webhooks/";
874
+
875
+ const typeImportPath = metadataPath.includes("modules/") ? "../../apexkit" : "../apexkit";
876
+
627
877
  const metadata = {
628
878
  name: cleanName,
629
879
  extension: ext,
630
880
  target_collection: targetCollection,
631
881
  type: fileType,
632
- path:
633
- fileType === "template"
634
- ? "./templates/"
635
- : fileType.includes("module")
636
- ? "./modules/custom/"
637
- : "./webhooks/",
882
+ path: metadataPath,
638
883
  trigger_type: triggerType,
639
884
  active: true,
640
885
  visibility: "private",
641
886
  };
642
887
 
643
- const header = `/** @type {import("../apexkit").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
888
+ const header = `/** @type {import("${typeImportPath}").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
644
889
  metadata,
645
890
  null,
646
891
  2
@@ -666,10 +911,11 @@ export function ${cleanName.replace(/-/g, "_")}(data = {}) {
666
911
  __fileMetadata__ = ${JSON.stringify(metadata, null, 2)}
667
912
  -->
668
913
  <script server>
669
- /// <reference path="../apexkit.d.ts" />
914
+ /** @type {import("../apexkit").ApexKitDb} */
915
+ const db = $db;
670
916
 
671
917
  export default async function (context) {
672
- const posts = await $db.records.list(\`${targetCollection || "posts"}\`, { limit: 10 }).catch(() => ({ items: [] }));
918
+ const posts = await db.records.list(\`${targetCollection || "posts"}\`, { limit: 10 }).catch(() => ({ items: [] }));
673
919
  return {
674
920
  title: \`${cleanName.replace(/-/g, " ").toUpperCase()}\`,
675
921
  items: posts.items
@@ -756,6 +1002,62 @@ export default async function (event) {
756
1002
  data
757
1003
  });
758
1004
  }
1005
+ `;
1006
+
1007
+ case "before_realtime_connection_established":
1008
+ return `${header}/**
1009
+ * Hook: before_realtime_connection_established
1010
+ * Intercepts incoming Realtime connections before upgrading/accepting.
1011
+ *
1012
+ * @param {{ trigger: string, data: { ip: string, protocol: 'sse' | 'websocket', headers: Record<string, string> }, auth: import("${typeImportPath}").AuthContext | null }} event
1013
+ */
1014
+ export default async function (event) {
1015
+ const { ip, protocol, headers } = event.data;
1016
+
1017
+ // Example: Block suspicious user-agents or IPs (throwing an error drops the connection)
1018
+ // if (headers["user-agent"]?.includes("BadBot")) {
1019
+ // throw new Error("Connection denied");
1020
+ // }
1021
+
1022
+ return true;
1023
+ }
1024
+ `;
1025
+
1026
+ case "before_realtime_data_pushed":
1027
+ return `${header}/**
1028
+ * Hook: before_realtime_data_pushed
1029
+ * Intercepts event data just before broadcasting to the connected client.
1030
+ *
1031
+ * @param {{ trigger: string, data: any, auth: import("${typeImportPath}").AuthContext | null }} event
1032
+ * @returns {Promise<any>} Return modified payload, or throw an error to send an empty object {}
1033
+ */
1034
+ export default async function (event) {
1035
+ const eventPayload = event.data;
1036
+
1037
+ // Example: Redact sensitive information before the client receives it
1038
+ // (NOTE: $realtime.send is blocked inside this hook to avoid infinite loops)
1039
+ // if (eventPayload.data && eventPayload.data.internal_secret) {
1040
+ // delete eventPayload.data.internal_secret;
1041
+ // }
1042
+
1043
+ return eventPayload;
1044
+ }
1045
+ `;
1046
+
1047
+ case "after_realtime_connection_desconnected":
1048
+ return `${header}/**
1049
+ * Hook: after_realtime_connection_desconnected
1050
+ * Fires asynchronously when a client stream/socket disconnects or drops.
1051
+ *
1052
+ * @param {{ trigger: string, data: { ip: string, protocol: 'sse' | 'websocket', headers: Record<string, string> }, auth: import("${typeImportPath}").AuthContext | null }} event
1053
+ */
1054
+ export default async function (event) {
1055
+ const { ip, protocol } = event.data;
1056
+
1057
+ console.log(\`Client disconnected (\${protocol}) from \${ip}\`);
1058
+
1059
+ return true;
1060
+ }
759
1061
  `;
760
1062
 
761
1063
  default:
@@ -802,10 +1104,12 @@ async function runInitFile() {
802
1104
  }
803
1105
 
804
1106
  const cleanName = rawName.replace(/[^a-zA-Z0-9_-]/g, "");
805
- const ext =
1107
+ const allowedScriptExts = ["js", "ts", "jsx", "tsx"];
1108
+ const rawExt =
806
1109
  fileType === "template"
807
1110
  ? "html"
808
- : ((await rl.question("Extension (js/ts) [js]: ")).trim() || "js");
1111
+ : ((await rl.question("Extension (js/ts/jsx/tsx) [js]: ")).trim().toLowerCase() || "js");
1112
+ const ext = fileType === "template" ? "html" : (allowedScriptExts.includes(rawExt) ? rawExt : "js");
809
1113
 
810
1114
  let triggerType = "manual";
811
1115
  let targetCollection = null;
@@ -816,7 +1120,7 @@ async function runInitFile() {
816
1120
  console.log(` ${String(i + 1).padStart(2, " ")}) ${t.name}`);
817
1121
  });
818
1122
 
819
- const trigChoice = parseInt((await rl.question("\nChoice (1-42) [1]: ")).trim() || "1", 10);
1123
+ const trigChoice = parseInt((await rl.question("\nChoice (1-45) [1]: ")).trim() || "1", 10);
820
1124
  const selectedTrig = TRIGGER_DEFINITIONS[trigChoice - 1] || TRIGGER_DEFINITIONS[0];
821
1125
  triggerType = selectedTrig.id;
822
1126
 
@@ -918,11 +1222,16 @@ async function runPull() {
918
1222
  const payload = await scriptRes.json();
919
1223
  const scripts = Array.isArray(payload) ? payload : payload.local || [];
920
1224
 
1225
+ const allowedScriptExts = ["js", "ts", "jsx", "tsx"];
1226
+
921
1227
  scripts.forEach((script) => {
1228
+ const remoteExt = script.metadata?.extension?.toLowerCase();
1229
+ const extension = allowedScriptExts.includes(remoteExt) ? remoteExt : "js";
1230
+
922
1231
  const meta = {
923
1232
  id: script.id,
924
1233
  name: script.name,
925
- extension: "js",
1234
+ extension: extension,
926
1235
  target_collection: script.target_collection || null,
927
1236
  type: script.metadata?.type || "webhook",
928
1237
  path: script.metadata?.path || "./webhooks/",
@@ -934,7 +1243,9 @@ async function runPull() {
934
1243
  const targetDir = meta.path.replace(/^\.\//, "");
935
1244
  fs.mkdirSync(targetDir, { recursive: true });
936
1245
 
937
- const formattedContent = `/** @type {import("../apexkit").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
1246
+ const typeImportPath = meta.path.includes("modules/") ? "../../apexkit" : "../apexkit";
1247
+
1248
+ const formattedContent = `/** @type {import("${typeImportPath}").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
938
1249
  meta,
939
1250
  null,
940
1251
  2
@@ -996,9 +1307,11 @@ async function runPull() {
996
1307
  console.warn(` ⚠️ AI Actions pull warning: ${e.message}`);
997
1308
  }
998
1309
 
1310
+ // Ensure .apexkit/types is ready and regenerate workspace definitions + remote types
1311
+ fs.mkdirSync(path.join(".apexkit", "types"), { recursive: true });
999
1312
  await generateWorkspaceFiles();
1000
1313
 
1001
- console.log(`\n✨ Pull complete! Hydrated ${pulledCount} items to local workspace.`);
1314
+ console.log(`\n✨ Pull complete! Hydrated ${pulledCount} items and resolved remote types.`);
1002
1315
  process.exit(0);
1003
1316
  }
1004
1317
 
@@ -1039,7 +1352,7 @@ function inferFileMetadata(filePath, content) {
1039
1352
  return content;
1040
1353
  }
1041
1354
 
1042
- // 3. Scripts / Webhooks / Modules -> JS Export metadata
1355
+ // 3. Scripts / Webhooks / Modules -> JS/TS/JSX/TSX Export metadata
1043
1356
  let deducedType = "webhook";
1044
1357
  let deducedPath = "./webhooks/";
1045
1358
  let deducedTrigger = "manual";
@@ -1052,7 +1365,7 @@ function inferFileMetadata(filePath, content) {
1052
1365
  deducedPath = "./modules/esm/";
1053
1366
  }
1054
1367
 
1055
- const jsMetaRe = /(?:export\s+const\s+__fileMetadata__\s*=\s*)(\{[\s\S]*?\});?/;
1368
+ const jsMetaRe = /(?:\/\*\*[\s\S]*?\*\/\s*)?export\s+const\s+__fileMetadata__\s*=\s*(\{[\s\S]*?\});?/;
1056
1369
  const match = content.match(jsMetaRe);
1057
1370
 
1058
1371
  let metaObj = {};
@@ -1062,9 +1375,14 @@ function inferFileMetadata(filePath, content) {
1062
1375
  } catch (e) {}
1063
1376
  }
1064
1377
 
1378
+ // Prioritize real file extension if js|ts|jsx|tsx, otherwise force to js
1379
+ const allowedScriptExts = ["js", "ts", "jsx", "tsx"];
1380
+ const fileExt = ext.toLowerCase();
1381
+ const prioritizedExt = allowedScriptExts.includes(fileExt) ? fileExt : "js";
1382
+
1065
1383
  const finalMeta = {
1066
1384
  name: metaObj.name || baseName,
1067
- extension: metaObj.extension || ext || "js",
1385
+ extension: prioritizedExt,
1068
1386
  target_collection: metaObj.target_collection || null,
1069
1387
  type: metaObj.type || deducedType,
1070
1388
  path: metaObj.path || deducedPath,
@@ -1073,12 +1391,20 @@ function inferFileMetadata(filePath, content) {
1073
1391
  visibility: metaObj.visibility || "private",
1074
1392
  };
1075
1393
 
1394
+ const typeImportPath = finalMeta.path.includes("modules/") ? "../../apexkit" : "../apexkit";
1395
+ const formattedMetadata = `/** @type {import("${typeImportPath}").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
1396
+ finalMeta,
1397
+ null,
1398
+ 2
1399
+ )};`;
1400
+
1076
1401
  if (!match) {
1077
- return `/** @type {import("../apexkit").FileMetadata} */\nexport const __fileMetadata__ = ${JSON.stringify(
1078
- finalMeta,
1079
- null,
1080
- 2
1081
- )};\n\n${content}`;
1402
+ return `${formattedMetadata}\n\n${content}`;
1403
+ }
1404
+
1405
+ // If metadata extension differed or needs updating, replace with prioritized definition
1406
+ if (metaObj.extension !== prioritizedExt || content.includes('import("../apexkit")') && typeImportPath.includes("../..")) {
1407
+ return content.replace(jsMetaRe, formattedMetadata);
1082
1408
  }
1083
1409
 
1084
1410
  return content;
@@ -1393,6 +1719,8 @@ function startWatcher() {
1393
1719
  if (
1394
1720
  filename.endsWith(".js") ||
1395
1721
  filename.endsWith(".ts") ||
1722
+ filename.endsWith(".jsx") ||
1723
+ filename.endsWith(".tsx") ||
1396
1724
  filename.endsWith(".html") ||
1397
1725
  filename.endsWith(".json")
1398
1726
  ) {