@onexapis/cli 1.1.10 → 1.1.11

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/dist/cli.mjs CHANGED
@@ -113,8 +113,37 @@ async function resolveNodeModulesFile(startDir, relativePath) {
113
113
  }
114
114
  return null;
115
115
  }
116
+ async function scanImportsFromPackage(sourceDir, packageName) {
117
+ const result = {};
118
+ const escapedPkg = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
119
+ const importRegex = new RegExp(
120
+ `(?:import|export)\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+["']${escapedPkg}(/[\\w./-]+)?["']`,
121
+ "g"
122
+ );
123
+ const sourceFiles = await glob("**/*.{ts,tsx}", {
124
+ cwd: sourceDir,
125
+ ignore: ["node_modules/**", "dist/**"]
126
+ });
127
+ for (const file of sourceFiles) {
128
+ try {
129
+ const content = await fs7.readFile(path8.join(sourceDir, file), "utf-8");
130
+ for (const match of content.matchAll(importRegex)) {
131
+ const subpath = match[2] ? match[2].slice(1) : "";
132
+ if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
133
+ for (const name of match[1].split(",")) {
134
+ let original = name.trim().split(/\s+as\s+/)[0].trim();
135
+ if (original.startsWith("type ")) continue;
136
+ if (original) result[subpath].add(original);
137
+ }
138
+ }
139
+ } catch {
140
+ }
141
+ }
142
+ return result;
143
+ }
116
144
  function createCoreGlobalPlugin(themePath) {
117
145
  const exportsBySubpath = {};
146
+ let scanPromise = null;
118
147
  return {
119
148
  name: "core-global",
120
149
  setup(build2) {
@@ -123,6 +152,16 @@ function createCoreGlobalPlugin(themePath) {
123
152
  namespace: "core-global"
124
153
  }));
125
154
  build2.onLoad({ filter: /.*/, namespace: "core-global" }, async (args) => {
155
+ if (!scanPromise) {
156
+ scanPromise = scanImportsFromPackage(themePath, "@onexapis/core").then((scanned) => {
157
+ for (const [subpath2, names] of Object.entries(scanned)) {
158
+ const cacheKey2 = subpath2 || "__root__";
159
+ exportsBySubpath[cacheKey2] = [...names];
160
+ }
161
+ }).catch(() => {
162
+ });
163
+ }
164
+ await scanPromise;
126
165
  const match = args.path.match(/^@onexapis\/core(\/(.+))?$/);
127
166
  const subpath = match?.[2] || "";
128
167
  const moduleAccess = subpath ? `['${subpath}']` : "";
@@ -186,6 +225,160 @@ ${namedExportLines}
186
225
  }
187
226
  };
188
227
  }
228
+ function createThemeDepsStubPlugin(themePath) {
229
+ return {
230
+ name: "theme-deps-stub",
231
+ setup(build2) {
232
+ const tryResolveOrStub = (filter, namespace) => {
233
+ build2.onResolve({ filter }, async (args) => {
234
+ if (args.pluginData?.skipStub) return void 0;
235
+ try {
236
+ const result = await build2.resolve(args.path, {
237
+ kind: args.kind,
238
+ resolveDir: args.resolveDir || themePath,
239
+ importer: args.importer,
240
+ namespace: "file",
241
+ pluginData: { skipStub: true }
242
+ });
243
+ if (!result.errors.length) return result;
244
+ } catch {
245
+ }
246
+ return { path: args.path, namespace };
247
+ });
248
+ };
249
+ tryResolveOrStub(/^next\//, "next-stub");
250
+ build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
251
+ const stubs = {
252
+ "next/image": `
253
+ import React from 'react';
254
+ const Image = (props) => {
255
+ const { src, alt, width, height, fill, priority, ...rest } = props;
256
+ const imgSrc = typeof src === 'object' ? src.src : src;
257
+ return React.createElement('img', { src: imgSrc, alt, width: fill ? undefined : width, height: fill ? undefined : height, loading: priority ? 'eager' : 'lazy', ...rest });
258
+ };
259
+ export default Image;
260
+ `,
261
+ "next/link": `
262
+ import React from 'react';
263
+ const Link = ({ href, children, ...rest }) => React.createElement('a', { href, ...rest }, children);
264
+ export default Link;
265
+ `,
266
+ "next/navigation": `
267
+ export function useRouter() { return { push(u){window.location.href=u}, replace(u){window.location.href=u}, back(){window.history.back()}, forward(){window.history.forward()}, refresh(){window.location.reload()}, prefetch(){} }; }
268
+ export function usePathname() { return window.location.pathname; }
269
+ export function useSearchParams() { return new URLSearchParams(window.location.search); }
270
+ export function useParams() { return {}; }
271
+ export function redirect(url) { window.location.href = url; }
272
+ export function notFound() { throw new Error('Not Found'); }
273
+ `,
274
+ "next/headers": `
275
+ export function cookies() { return { get(){}, getAll(){ return []; }, set(){}, delete(){}, has(){ return false; } }; }
276
+ export function headers() { return new Headers(); }
277
+ `
278
+ };
279
+ return {
280
+ contents: stubs[args.path] || "export default {};",
281
+ loader: "jsx",
282
+ resolveDir: themePath
283
+ };
284
+ });
285
+ const lucideImports = /* @__PURE__ */ new Set();
286
+ let lucideThemeScanned = false;
287
+ tryResolveOrStub(/^lucide-react/, "lucide-stub");
288
+ build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
289
+ if (!lucideThemeScanned) {
290
+ lucideThemeScanned = true;
291
+ try {
292
+ const scanned = await scanImportsFromPackage(themePath, "lucide-react");
293
+ for (const names of Object.values(scanned)) {
294
+ for (const name of names) lucideImports.add(name);
295
+ }
296
+ } catch {
297
+ }
298
+ }
299
+ const iconNames = [...lucideImports];
300
+ const exports$1 = iconNames.map((n) => `icon as ${n}`).join(", ");
301
+ return {
302
+ contents: `
303
+ const icon = (props) => null;
304
+ export { ${exports$1} };
305
+ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
306
+ `.trim(),
307
+ loader: "jsx"
308
+ };
309
+ });
310
+ tryResolveOrStub(/^framer-motion/, "motion-stub");
311
+ build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
312
+ contents: `
313
+ import React from 'react';
314
+ const handler = { get: (_, name) => {
315
+ if (name === '__esModule') return true;
316
+ return React.forwardRef((props, ref) => React.createElement(name, { ...props, ref }));
317
+ }};
318
+ export const motion = new Proxy({}, handler);
319
+ export const AnimatePresence = ({ children }) => children || null;
320
+ export function useInView() { return true; }
321
+ export default { motion, AnimatePresence, useInView };
322
+ `.trim(),
323
+ loader: "jsx",
324
+ resolveDir: themePath
325
+ }));
326
+ tryResolveOrStub(/^sonner$/, "sonner-stub");
327
+ build2.onLoad({ filter: /.*/, namespace: "sonner-stub" }, () => ({
328
+ contents: `
329
+ export const toast = new Proxy(() => {}, { get: () => () => {} });
330
+ export const Toaster = () => null;
331
+ export default { toast, Toaster };
332
+ `.trim(),
333
+ loader: "jsx"
334
+ }));
335
+ tryResolveOrStub(/^react-hook-form$/, "rhf-stub");
336
+ build2.onLoad({ filter: /.*/, namespace: "rhf-stub" }, () => ({
337
+ contents: `
338
+ export function useForm() {
339
+ return {
340
+ register: () => ({}),
341
+ handleSubmit: (fn) => (e) => { e?.preventDefault?.(); fn({}); },
342
+ formState: { errors: {}, isSubmitting: false, isValid: true },
343
+ watch: () => undefined,
344
+ setValue: () => {},
345
+ reset: () => {},
346
+ control: {},
347
+ };
348
+ }
349
+ export function useController() { return { field: {}, fieldState: {} }; }
350
+ export function useFormContext() { return useForm(); }
351
+ `.trim(),
352
+ loader: "js"
353
+ }));
354
+ tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
355
+ build2.onLoad({ filter: /.*/, namespace: "hookform-resolvers-stub" }, () => ({
356
+ contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
357
+ loader: "js"
358
+ }));
359
+ tryResolveOrStub(/^next-intl$/, "next-intl-stub");
360
+ build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
361
+ contents: `
362
+ export function useTranslations(ns) {
363
+ return (key) => ns ? ns + '.' + key : key;
364
+ }
365
+ export function useLocale() { return 'en'; }
366
+ export function useMessages() { return {}; }
367
+ `.trim(),
368
+ loader: "js"
369
+ }));
370
+ tryResolveOrStub(/^zod$/, "zod-stub");
371
+ build2.onLoad({ filter: /.*/, namespace: "zod-stub" }, () => ({
372
+ contents: `
373
+ const schema = () => ({ parse: (v) => v, safeParse: (v) => ({ success: true, data: v }), optional: schema, min: schema, max: schema, email: schema, url: schema, regex: schema, refine: schema, transform: schema });
374
+ export const z = { string: schema, number: schema, boolean: schema, object: (s) => ({ ...schema(), shape: s }), array: schema, enum: schema, union: schema, literal: schema, infer: undefined };
375
+ export default z;
376
+ `.trim(),
377
+ loader: "js"
378
+ }));
379
+ }
380
+ };
381
+ }
189
382
  async function generateThemeData(themePath, outputDir, themeId) {
190
383
  const { createJiti } = await import('jiti');
191
384
  const jiti = createJiti(import.meta.url);
@@ -420,7 +613,7 @@ async function compileStandaloneTheme(themePath, themeName) {
420
613
  banner: {
421
614
  js: '"use client";'
422
615
  },
423
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
616
+ plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
424
617
  external: [],
425
618
  alias: {
426
619
  events: "events/",
@@ -502,7 +695,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
502
695
  banner: {
503
696
  js: '"use client";'
504
697
  },
505
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
698
+ plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
506
699
  external: [],
507
700
  alias: {
508
701
  events: "events/",
@@ -613,6 +806,8 @@ ${locations.join("\n")}`
613
806
  loader: "js"
614
807
  };
615
808
  });
809
+ const lucideIconNames = /* @__PURE__ */ new Set();
810
+ let lucideScanned = false;
616
811
  build2.onResolve({ filter: /^lucide-react/ }, async (args) => {
617
812
  if (args.pluginData?.skipStub) return void 0;
618
813
  try {
@@ -628,23 +823,93 @@ ${locations.join("\n")}`
628
823
  }
629
824
  return { path: args.path, namespace: "lucide-stub" };
630
825
  });
631
- build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, () => ({
632
- // Provide all icon names used by @onexapis/core as no-op SVG stub components
633
- contents: `
826
+ build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
827
+ if (!lucideScanned) {
828
+ lucideScanned = true;
829
+ const coreSrcCandidates = [
830
+ path8.join(themePath, "node_modules", "@onexapis", "core", "src"),
831
+ path8.join(themePath, "..", "..", "packages", "core", "src"),
832
+ // monorepo sibling
833
+ path8.join(__dirname, "..", "..", "..", "..", "packages", "core", "src")
834
+ // from CLI src
835
+ ];
836
+ let coreSourceDir = null;
837
+ for (const candidate of coreSrcCandidates) {
838
+ try {
839
+ await fs7.access(candidate);
840
+ coreSourceDir = candidate;
841
+ break;
842
+ } catch {
843
+ }
844
+ }
845
+ if (coreSourceDir) {
846
+ try {
847
+ const scanned = await scanImportsFromPackage(coreSourceDir, "lucide-react");
848
+ for (const names of Object.values(scanned)) {
849
+ for (const name of names) lucideIconNames.add(name);
850
+ }
851
+ } catch {
852
+ }
853
+ }
854
+ try {
855
+ const scanned = await scanImportsFromPackage(themePath, "lucide-react");
856
+ for (const names of Object.values(scanned)) {
857
+ for (const name of names) lucideIconNames.add(name);
858
+ }
859
+ } catch {
860
+ }
861
+ }
862
+ if (lucideIconNames.size === 0) {
863
+ const fallbackIcons = [
864
+ "Check",
865
+ "ChevronDown",
866
+ "XCircle",
867
+ "AlertTriangle",
868
+ "CheckCircle",
869
+ "Info",
870
+ "X",
871
+ "XIcon",
872
+ "CircleIcon",
873
+ "Star",
874
+ "ShoppingCart",
875
+ "ChevronRight",
876
+ "ChevronLeft",
877
+ "ChevronUp",
878
+ "Search",
879
+ "Menu",
880
+ "Heart",
881
+ "User",
882
+ "Trash2",
883
+ "Plus",
884
+ "Minus",
885
+ "Eye",
886
+ "EyeOff",
887
+ "ArrowRight",
888
+ "ArrowLeft",
889
+ "ExternalLink",
890
+ "Mail",
891
+ "Phone",
892
+ "MapPin",
893
+ "Calendar",
894
+ "Clock",
895
+ "Facebook",
896
+ "Twitter",
897
+ "Instagram",
898
+ "Linkedin",
899
+ "Github"
900
+ ];
901
+ for (const icon of fallbackIcons) lucideIconNames.add(icon);
902
+ }
903
+ const iconExports = [...lucideIconNames].map((n) => `icon as ${n}`).join(", ");
904
+ return {
905
+ contents: `
634
906
  const icon = (props) => null;
635
- export { icon as Check, icon as ChevronDown, icon as XCircle, icon as AlertTriangle };
636
- export { icon as CheckCircle, icon as Info, icon as X, icon as XIcon };
637
- export { icon as CircleIcon, icon as Star, icon as ShoppingCart };
638
- export { icon as ChevronRight, icon as ChevronLeft, icon as ChevronUp };
639
- export { icon as Search, icon as Menu, icon as Heart, icon as User };
640
- export { icon as Trash2, icon as Plus, icon as Minus, icon as Eye, icon as EyeOff };
641
- export { icon as ArrowRight, icon as ArrowLeft, icon as ExternalLink, icon as Mail };
642
- export { icon as Phone, icon as MapPin, icon as Calendar, icon as Clock };
643
- export { icon as Facebook, icon as Twitter, icon as Instagram, icon as Linkedin, icon as Github };
907
+ export { ${iconExports} };
644
908
  export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
645
- `.trim(),
646
- loader: "jsx"
647
- }));
909
+ `.trim(),
910
+ loader: "jsx"
911
+ };
912
+ });
648
913
  build2.onResolve({ filter: /^framer-motion/ }, async (args) => {
649
914
  if (args.pluginData?.skipStub) return void 0;
650
915
  try {