@onexapis/cli 1.1.10 → 1.1.12

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.js CHANGED
@@ -155,8 +155,37 @@ async function resolveNodeModulesFile(startDir, relativePath) {
155
155
  }
156
156
  return null;
157
157
  }
158
+ async function scanImportsFromPackage(sourceDir, packageName) {
159
+ const result = {};
160
+ const escapedPkg = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
161
+ const importRegex = new RegExp(
162
+ `(?:import|export)\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+["']${escapedPkg}(/[\\w./-]+)?["']`,
163
+ "g"
164
+ );
165
+ const sourceFiles = await glob.glob("**/*.{ts,tsx}", {
166
+ cwd: sourceDir,
167
+ ignore: ["node_modules/**", "dist/**"]
168
+ });
169
+ for (const file of sourceFiles) {
170
+ try {
171
+ const content = await fs7__default.default.readFile(path8__default.default.join(sourceDir, file), "utf-8");
172
+ for (const match of content.matchAll(importRegex)) {
173
+ const subpath = match[2] ? match[2].slice(1) : "";
174
+ if (!result[subpath]) result[subpath] = /* @__PURE__ */ new Set();
175
+ for (const name of match[1].split(",")) {
176
+ let original = name.trim().split(/\s+as\s+/)[0].trim();
177
+ if (original.startsWith("type ")) continue;
178
+ if (original) result[subpath].add(original);
179
+ }
180
+ }
181
+ } catch {
182
+ }
183
+ }
184
+ return result;
185
+ }
158
186
  function createCoreGlobalPlugin(themePath) {
159
187
  const exportsBySubpath = {};
188
+ let scanPromise = null;
160
189
  return {
161
190
  name: "core-global",
162
191
  setup(build2) {
@@ -165,6 +194,16 @@ function createCoreGlobalPlugin(themePath) {
165
194
  namespace: "core-global"
166
195
  }));
167
196
  build2.onLoad({ filter: /.*/, namespace: "core-global" }, async (args) => {
197
+ if (!scanPromise) {
198
+ scanPromise = scanImportsFromPackage(themePath, "@onexapis/core").then((scanned) => {
199
+ for (const [subpath2, names] of Object.entries(scanned)) {
200
+ const cacheKey2 = subpath2 || "__root__";
201
+ exportsBySubpath[cacheKey2] = [...names];
202
+ }
203
+ }).catch(() => {
204
+ });
205
+ }
206
+ await scanPromise;
168
207
  const match = args.path.match(/^@onexapis\/core(\/(.+))?$/);
169
208
  const subpath = match?.[2] || "";
170
209
  const moduleAccess = subpath ? `['${subpath}']` : "";
@@ -228,6 +267,160 @@ ${namedExportLines}
228
267
  }
229
268
  };
230
269
  }
270
+ function createThemeDepsStubPlugin(themePath) {
271
+ return {
272
+ name: "theme-deps-stub",
273
+ setup(build2) {
274
+ const tryResolveOrStub = (filter, namespace) => {
275
+ build2.onResolve({ filter }, async (args) => {
276
+ if (args.pluginData?.skipStub) return void 0;
277
+ try {
278
+ const result = await build2.resolve(args.path, {
279
+ kind: args.kind,
280
+ resolveDir: args.resolveDir || themePath,
281
+ importer: args.importer,
282
+ namespace: "file",
283
+ pluginData: { skipStub: true }
284
+ });
285
+ if (!result.errors.length) return result;
286
+ } catch {
287
+ }
288
+ return { path: args.path, namespace };
289
+ });
290
+ };
291
+ tryResolveOrStub(/^next\//, "next-stub");
292
+ build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
293
+ const stubs = {
294
+ "next/image": `
295
+ import React from 'react';
296
+ const Image = (props) => {
297
+ const { src, alt, width, height, fill, priority, ...rest } = props;
298
+ const imgSrc = typeof src === 'object' ? src.src : src;
299
+ return React.createElement('img', { src: imgSrc, alt, width: fill ? undefined : width, height: fill ? undefined : height, loading: priority ? 'eager' : 'lazy', ...rest });
300
+ };
301
+ export default Image;
302
+ `,
303
+ "next/link": `
304
+ import React from 'react';
305
+ const Link = ({ href, children, ...rest }) => React.createElement('a', { href, ...rest }, children);
306
+ export default Link;
307
+ `,
308
+ "next/navigation": `
309
+ 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(){} }; }
310
+ export function usePathname() { return window.location.pathname; }
311
+ export function useSearchParams() { return new URLSearchParams(window.location.search); }
312
+ export function useParams() { return {}; }
313
+ export function redirect(url) { window.location.href = url; }
314
+ export function notFound() { throw new Error('Not Found'); }
315
+ `,
316
+ "next/headers": `
317
+ export function cookies() { return { get(){}, getAll(){ return []; }, set(){}, delete(){}, has(){ return false; } }; }
318
+ export function headers() { return new Headers(); }
319
+ `
320
+ };
321
+ return {
322
+ contents: stubs[args.path] || "export default {};",
323
+ loader: "jsx",
324
+ resolveDir: themePath
325
+ };
326
+ });
327
+ const lucideImports = /* @__PURE__ */ new Set();
328
+ let lucideThemeScanned = false;
329
+ tryResolveOrStub(/^lucide-react/, "lucide-stub");
330
+ build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
331
+ if (!lucideThemeScanned) {
332
+ lucideThemeScanned = true;
333
+ try {
334
+ const scanned = await scanImportsFromPackage(themePath, "lucide-react");
335
+ for (const names of Object.values(scanned)) {
336
+ for (const name of names) lucideImports.add(name);
337
+ }
338
+ } catch {
339
+ }
340
+ }
341
+ const iconNames = [...lucideImports];
342
+ const exports$1 = iconNames.map((n) => `icon as ${n}`).join(", ");
343
+ return {
344
+ contents: `
345
+ const icon = (props) => null;
346
+ export { ${exports$1} };
347
+ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
348
+ `.trim(),
349
+ loader: "jsx"
350
+ };
351
+ });
352
+ tryResolveOrStub(/^framer-motion/, "motion-stub");
353
+ build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
354
+ contents: `
355
+ import React from 'react';
356
+ const handler = { get: (_, name) => {
357
+ if (name === '__esModule') return true;
358
+ return React.forwardRef((props, ref) => React.createElement(name, { ...props, ref }));
359
+ }};
360
+ export const motion = new Proxy({}, handler);
361
+ export const AnimatePresence = ({ children }) => children || null;
362
+ export function useInView() { return true; }
363
+ export default { motion, AnimatePresence, useInView };
364
+ `.trim(),
365
+ loader: "jsx",
366
+ resolveDir: themePath
367
+ }));
368
+ tryResolveOrStub(/^sonner$/, "sonner-stub");
369
+ build2.onLoad({ filter: /.*/, namespace: "sonner-stub" }, () => ({
370
+ contents: `
371
+ export const toast = new Proxy(() => {}, { get: () => () => {} });
372
+ export const Toaster = () => null;
373
+ export default { toast, Toaster };
374
+ `.trim(),
375
+ loader: "jsx"
376
+ }));
377
+ tryResolveOrStub(/^react-hook-form$/, "rhf-stub");
378
+ build2.onLoad({ filter: /.*/, namespace: "rhf-stub" }, () => ({
379
+ contents: `
380
+ export function useForm() {
381
+ return {
382
+ register: () => ({}),
383
+ handleSubmit: (fn) => (e) => { e?.preventDefault?.(); fn({}); },
384
+ formState: { errors: {}, isSubmitting: false, isValid: true },
385
+ watch: () => undefined,
386
+ setValue: () => {},
387
+ reset: () => {},
388
+ control: {},
389
+ };
390
+ }
391
+ export function useController() { return { field: {}, fieldState: {} }; }
392
+ export function useFormContext() { return useForm(); }
393
+ `.trim(),
394
+ loader: "js"
395
+ }));
396
+ tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
397
+ build2.onLoad({ filter: /.*/, namespace: "hookform-resolvers-stub" }, () => ({
398
+ contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
399
+ loader: "js"
400
+ }));
401
+ tryResolveOrStub(/^next-intl$/, "next-intl-stub");
402
+ build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
403
+ contents: `
404
+ export function useTranslations(ns) {
405
+ return (key) => ns ? ns + '.' + key : key;
406
+ }
407
+ export function useLocale() { return 'en'; }
408
+ export function useMessages() { return {}; }
409
+ `.trim(),
410
+ loader: "js"
411
+ }));
412
+ tryResolveOrStub(/^zod$/, "zod-stub");
413
+ build2.onLoad({ filter: /.*/, namespace: "zod-stub" }, () => ({
414
+ contents: `
415
+ 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 });
416
+ export const z = { string: schema, number: schema, boolean: schema, object: (s) => ({ ...schema(), shape: s }), array: schema, enum: schema, union: schema, literal: schema, infer: undefined };
417
+ export default z;
418
+ `.trim(),
419
+ loader: "js"
420
+ }));
421
+ }
422
+ };
423
+ }
231
424
  async function generateThemeData(themePath, outputDir, themeId) {
232
425
  const { createJiti } = await import('jiti');
233
426
  const jiti = createJiti((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href)));
@@ -462,7 +655,7 @@ async function compileStandaloneTheme(themePath, themeName) {
462
655
  banner: {
463
656
  js: '"use client";'
464
657
  },
465
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
658
+ plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
466
659
  external: [],
467
660
  alias: {
468
661
  events: "events/",
@@ -544,7 +737,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
544
737
  banner: {
545
738
  js: '"use client";'
546
739
  },
547
- plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath)],
740
+ plugins: [reactGlobalPlugin, createCoreGlobalPlugin(themePath), createThemeDepsStubPlugin(themePath)],
548
741
  external: [],
549
742
  alias: {
550
743
  events: "events/",
@@ -655,6 +848,8 @@ ${locations.join("\n")}`
655
848
  loader: "js"
656
849
  };
657
850
  });
851
+ const lucideIconNames = /* @__PURE__ */ new Set();
852
+ let lucideScanned = false;
658
853
  build2.onResolve({ filter: /^lucide-react/ }, async (args) => {
659
854
  if (args.pluginData?.skipStub) return void 0;
660
855
  try {
@@ -670,23 +865,93 @@ ${locations.join("\n")}`
670
865
  }
671
866
  return { path: args.path, namespace: "lucide-stub" };
672
867
  });
673
- build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, () => ({
674
- // Provide all icon names used by @onexapis/core as no-op SVG stub components
675
- contents: `
868
+ build2.onLoad({ filter: /.*/, namespace: "lucide-stub" }, async () => {
869
+ if (!lucideScanned) {
870
+ lucideScanned = true;
871
+ const coreSrcCandidates = [
872
+ path8__default.default.join(themePath, "node_modules", "@onexapis", "core", "src"),
873
+ path8__default.default.join(themePath, "..", "..", "packages", "core", "src"),
874
+ // monorepo sibling
875
+ path8__default.default.join(__dirname, "..", "..", "..", "..", "packages", "core", "src")
876
+ // from CLI src
877
+ ];
878
+ let coreSourceDir = null;
879
+ for (const candidate of coreSrcCandidates) {
880
+ try {
881
+ await fs7__default.default.access(candidate);
882
+ coreSourceDir = candidate;
883
+ break;
884
+ } catch {
885
+ }
886
+ }
887
+ if (coreSourceDir) {
888
+ try {
889
+ const scanned = await scanImportsFromPackage(coreSourceDir, "lucide-react");
890
+ for (const names of Object.values(scanned)) {
891
+ for (const name of names) lucideIconNames.add(name);
892
+ }
893
+ } catch {
894
+ }
895
+ }
896
+ try {
897
+ const scanned = await scanImportsFromPackage(themePath, "lucide-react");
898
+ for (const names of Object.values(scanned)) {
899
+ for (const name of names) lucideIconNames.add(name);
900
+ }
901
+ } catch {
902
+ }
903
+ }
904
+ if (lucideIconNames.size === 0) {
905
+ const fallbackIcons = [
906
+ "Check",
907
+ "ChevronDown",
908
+ "XCircle",
909
+ "AlertTriangle",
910
+ "CheckCircle",
911
+ "Info",
912
+ "X",
913
+ "XIcon",
914
+ "CircleIcon",
915
+ "Star",
916
+ "ShoppingCart",
917
+ "ChevronRight",
918
+ "ChevronLeft",
919
+ "ChevronUp",
920
+ "Search",
921
+ "Menu",
922
+ "Heart",
923
+ "User",
924
+ "Trash2",
925
+ "Plus",
926
+ "Minus",
927
+ "Eye",
928
+ "EyeOff",
929
+ "ArrowRight",
930
+ "ArrowLeft",
931
+ "ExternalLink",
932
+ "Mail",
933
+ "Phone",
934
+ "MapPin",
935
+ "Calendar",
936
+ "Clock",
937
+ "Facebook",
938
+ "Twitter",
939
+ "Instagram",
940
+ "Linkedin",
941
+ "Github"
942
+ ];
943
+ for (const icon of fallbackIcons) lucideIconNames.add(icon);
944
+ }
945
+ const iconExports = [...lucideIconNames].map((n) => `icon as ${n}`).join(", ");
946
+ return {
947
+ contents: `
676
948
  const icon = (props) => null;
677
- export { icon as Check, icon as ChevronDown, icon as XCircle, icon as AlertTriangle };
678
- export { icon as CheckCircle, icon as Info, icon as X, icon as XIcon };
679
- export { icon as CircleIcon, icon as Star, icon as ShoppingCart };
680
- export { icon as ChevronRight, icon as ChevronLeft, icon as ChevronUp };
681
- export { icon as Search, icon as Menu, icon as Heart, icon as User };
682
- export { icon as Trash2, icon as Plus, icon as Minus, icon as Eye, icon as EyeOff };
683
- export { icon as ArrowRight, icon as ArrowLeft, icon as ExternalLink, icon as Mail };
684
- export { icon as Phone, icon as MapPin, icon as Calendar, icon as Clock };
685
- export { icon as Facebook, icon as Twitter, icon as Instagram, icon as Linkedin, icon as Github };
949
+ export { ${iconExports} };
686
950
  export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true : icon });
687
- `.trim(),
688
- loader: "jsx"
689
- }));
951
+ `.trim(),
952
+ loader: "jsx"
953
+ };
954
+ });
690
955
  build2.onResolve({ filter: /^framer-motion/ }, async (args) => {
691
956
  if (args.pluginData?.skipStub) return void 0;
692
957
  try {