@onexapis/cli 1.1.16 → 1.1.18
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 +117 -51
- package/bin/onexthm.js +4 -0
- package/dist/cli.js +750 -328
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +747 -325
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +162 -299
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +162 -299
- package/dist/index.mjs.map +1 -1
- package/dist/preview/preview-app.tsx +175 -53
- package/package.json +14 -12
- package/templates/default/.env.example +1 -1
- package/templates/default/.mcp.json +8 -0
- package/templates/default/CLAUDE.md +941 -0
- package/templates/default/bundle-entry.ts +18 -0
- package/templates/default/index.ts +26 -0
- package/templates/default/package.json +37 -0
- package/templates/default/pages/about.ts +66 -0
- package/templates/default/pages/home.ts +93 -0
- package/templates/default/pages/showcase.ts +146 -0
- package/templates/default/sections/about/about-default.tsx +237 -0
- package/templates/default/sections/about/about.schema.ts +259 -0
- package/templates/default/sections/about/index.ts +15 -0
- package/templates/default/sections/cta/cta-default.tsx +180 -0
- package/templates/default/sections/cta/cta.schema.ts +210 -0
- package/templates/default/sections/cta/index.ts +11 -0
- package/templates/default/sections/features/features-default.tsx +154 -0
- package/templates/default/sections/features/features.schema.ts +330 -0
- package/templates/default/sections/features/index.ts +11 -0
- package/templates/default/sections/gallery/gallery-default.tsx +134 -0
- package/templates/default/sections/gallery/gallery.schema.ts +397 -0
- package/templates/default/sections/gallery/index.ts +11 -0
- package/templates/default/sections/hero/hero-default.tsx +212 -0
- package/templates/default/sections/hero/hero.schema.ts +273 -0
- package/templates/default/sections/hero/index.ts +15 -0
- package/templates/default/sections/stats/index.ts +11 -0
- package/templates/default/sections/stats/stats-default.tsx +103 -0
- package/templates/default/sections/stats/stats.schema.ts +266 -0
- package/templates/default/sections/testimonials/index.ts +11 -0
- package/templates/default/sections/testimonials/testimonials-default.tsx +130 -0
- package/templates/default/sections/testimonials/testimonials.schema.ts +371 -0
- package/templates/default/sections-registry.ts +32 -0
- package/templates/default/theme.config.ts +107 -0
- package/templates/default/theme.layout.ts +21 -0
- package/templates/default/tsconfig.json +16 -7
- package/templates/default/README.md.ejs +0 -129
- package/templates/default/esbuild.config.js +0 -81
- package/templates/default/package.json.ejs +0 -31
- package/templates/default/src/config.ts.ejs +0 -98
- package/templates/default/src/index.ts.ejs +0 -11
- package/templates/default/src/layout.ts +0 -23
- package/templates/default/src/manifest.ts.ejs +0 -47
- package/templates/default/src/pages/home.ts.ejs +0 -37
- package/templates/default/src/sections/footer/footer-default.tsx +0 -28
- package/templates/default/src/sections/footer/footer.schema.ts +0 -45
- package/templates/default/src/sections/footer/index.ts +0 -2
- package/templates/default/src/sections/header/header-default.tsx +0 -61
- package/templates/default/src/sections/header/header.schema.ts +0 -46
- package/templates/default/src/sections/header/index.ts +0 -2
- package/templates/default/src/sections/hero/hero-default.tsx +0 -52
- package/templates/default/src/sections/hero/hero.schema.ts +0 -52
- package/templates/default/src/sections/hero/index.ts +0 -2
package/dist/cli.mjs
CHANGED
|
@@ -4,20 +4,20 @@ import ora from 'ora';
|
|
|
4
4
|
import * as esbuild from 'esbuild';
|
|
5
5
|
import path8 from 'path';
|
|
6
6
|
import fs7 from 'fs/promises';
|
|
7
|
-
import
|
|
7
|
+
import crypto2 from 'crypto';
|
|
8
8
|
import { glob } from 'glob';
|
|
9
|
-
import
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
import os3 from 'os';
|
|
10
11
|
import dotenv from 'dotenv';
|
|
11
12
|
import fs from 'fs-extra';
|
|
12
13
|
import ejs from 'ejs';
|
|
13
14
|
import { execSync, spawn } from 'child_process';
|
|
14
15
|
import { Command } from 'commander';
|
|
15
|
-
import { createRequire } from 'module';
|
|
16
16
|
import fs2 from 'fs';
|
|
17
17
|
import inquirer from 'inquirer';
|
|
18
18
|
import archiver from 'archiver';
|
|
19
19
|
import FormData from 'form-data';
|
|
20
|
-
import
|
|
20
|
+
import fetch2 from 'node-fetch';
|
|
21
21
|
import { PutObjectCommand, GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
22
22
|
import AdmZip from 'adm-zip';
|
|
23
23
|
import chokidar from 'chokidar';
|
|
@@ -96,7 +96,7 @@ __export(compile_theme_exports, {
|
|
|
96
96
|
compilePreviewRuntime: () => compilePreviewRuntime,
|
|
97
97
|
compileStandaloneTheme: () => compileStandaloneTheme,
|
|
98
98
|
compileStandaloneThemeDev: () => compileStandaloneThemeDev,
|
|
99
|
-
generateManifest: () =>
|
|
99
|
+
generateManifest: () => generateManifest
|
|
100
100
|
});
|
|
101
101
|
async function resolveNodeModulesFile(startDir, relativePath) {
|
|
102
102
|
let dir = startDir;
|
|
@@ -243,6 +243,12 @@ function createThemeDepsStubPlugin(themePath) {
|
|
|
243
243
|
if (!result.errors.length) return result;
|
|
244
244
|
} catch {
|
|
245
245
|
}
|
|
246
|
+
try {
|
|
247
|
+
const req = createRequire(import.meta.url || __filename);
|
|
248
|
+
const resolved = req.resolve(args.path);
|
|
249
|
+
if (resolved) return { path: resolved, namespace: "file" };
|
|
250
|
+
} catch {
|
|
251
|
+
}
|
|
246
252
|
return { path: args.path, namespace };
|
|
247
253
|
});
|
|
248
254
|
};
|
|
@@ -251,11 +257,19 @@ function createThemeDepsStubPlugin(themePath) {
|
|
|
251
257
|
const stubs = {
|
|
252
258
|
"next/image": `
|
|
253
259
|
import React from 'react';
|
|
254
|
-
const Image = (props) => {
|
|
255
|
-
const { src, alt, width, height, fill, priority, ...rest } = props;
|
|
260
|
+
const Image = React.forwardRef((props, ref) => {
|
|
261
|
+
const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
|
|
256
262
|
const imgSrc = typeof src === 'object' ? src.src : src;
|
|
257
|
-
|
|
258
|
-
};
|
|
263
|
+
const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
|
|
264
|
+
const mergedStyle = { ...fillStyle, ...style };
|
|
265
|
+
return React.createElement('img', {
|
|
266
|
+
ref, src: imgSrc, alt,
|
|
267
|
+
width: fill ? undefined : width, height: fill ? undefined : height,
|
|
268
|
+
loading: priority ? 'eager' : 'lazy',
|
|
269
|
+
style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
|
|
270
|
+
className, onLoad, onError, ...rest,
|
|
271
|
+
});
|
|
272
|
+
});
|
|
259
273
|
export default Image;
|
|
260
274
|
`,
|
|
261
275
|
"next/link": `
|
|
@@ -289,7 +303,10 @@ export function headers() { return new Headers(); }
|
|
|
289
303
|
if (!lucideThemeScanned) {
|
|
290
304
|
lucideThemeScanned = true;
|
|
291
305
|
try {
|
|
292
|
-
const scanned = await scanImportsFromPackage(
|
|
306
|
+
const scanned = await scanImportsFromPackage(
|
|
307
|
+
themePath,
|
|
308
|
+
"lucide-react"
|
|
309
|
+
);
|
|
293
310
|
for (const names of Object.values(scanned)) {
|
|
294
311
|
for (const name of names) lucideImports.add(name);
|
|
295
312
|
}
|
|
@@ -352,10 +369,13 @@ export function useFormContext() { return useForm(); }
|
|
|
352
369
|
loader: "js"
|
|
353
370
|
}));
|
|
354
371
|
tryResolveOrStub(/^@hookform\/resolvers/, "hookform-resolvers-stub");
|
|
355
|
-
build2.onLoad(
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
372
|
+
build2.onLoad(
|
|
373
|
+
{ filter: /.*/, namespace: "hookform-resolvers-stub" },
|
|
374
|
+
() => ({
|
|
375
|
+
contents: `export function zodResolver() { return () => ({ values: {}, errors: {} }); }`,
|
|
376
|
+
loader: "js"
|
|
377
|
+
})
|
|
378
|
+
);
|
|
359
379
|
tryResolveOrStub(/^next-intl$/, "next-intl-stub");
|
|
360
380
|
build2.onLoad({ filter: /.*/, namespace: "next-intl-stub" }, () => ({
|
|
361
381
|
contents: `
|
|
@@ -464,7 +484,7 @@ async function contentHashEntry(outputDir) {
|
|
|
464
484
|
logger.warning("No entry file found in output, skipping content hash");
|
|
465
485
|
return;
|
|
466
486
|
}
|
|
467
|
-
const hash2 =
|
|
487
|
+
const hash2 = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
468
488
|
const hashedName2 = `bundle-entry-${hash2}.js`;
|
|
469
489
|
const indexMapPath = path8.join(outputDir, "index.js.map");
|
|
470
490
|
const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
|
|
@@ -482,7 +502,7 @@ async function contentHashEntry(outputDir) {
|
|
|
482
502
|
logger.info(`Entry hashed: ${hashedName2}`);
|
|
483
503
|
return;
|
|
484
504
|
}
|
|
485
|
-
const hash =
|
|
505
|
+
const hash = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
486
506
|
const hashedName = `bundle-entry-${hash}.js`;
|
|
487
507
|
const hashedMapName = `bundle-entry-${hash}.js.map`;
|
|
488
508
|
entryContent = entryContent.replace(
|
|
@@ -520,7 +540,7 @@ async function extractDataRequirements(themePath) {
|
|
|
520
540
|
}
|
|
521
541
|
return requirements;
|
|
522
542
|
}
|
|
523
|
-
async function
|
|
543
|
+
async function generateManifest(themeName, themePath, outputDir) {
|
|
524
544
|
let version2 = "1.0.0";
|
|
525
545
|
let themeId = themeName;
|
|
526
546
|
try {
|
|
@@ -613,7 +633,11 @@ async function compileStandaloneTheme(themePath, themeName) {
|
|
|
613
633
|
banner: {
|
|
614
634
|
js: '"use client";'
|
|
615
635
|
},
|
|
616
|
-
plugins: [
|
|
636
|
+
plugins: [
|
|
637
|
+
reactGlobalPlugin,
|
|
638
|
+
createCoreGlobalPlugin(themePath),
|
|
639
|
+
createThemeDepsStubPlugin(themePath)
|
|
640
|
+
],
|
|
617
641
|
external: [],
|
|
618
642
|
alias: {
|
|
619
643
|
events: "events/",
|
|
@@ -651,7 +675,7 @@ async function compileStandaloneTheme(themePath, themeName) {
|
|
|
651
675
|
} catch {
|
|
652
676
|
}
|
|
653
677
|
await contentHashEntry(outputDir);
|
|
654
|
-
await
|
|
678
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
655
679
|
await generateThemeData(themePath, outputDir, themeName);
|
|
656
680
|
if (result.metafile) {
|
|
657
681
|
const outputs = result.metafile.outputs;
|
|
@@ -695,7 +719,11 @@ async function compileStandaloneThemeDev(themePath, themeName) {
|
|
|
695
719
|
banner: {
|
|
696
720
|
js: '"use client";'
|
|
697
721
|
},
|
|
698
|
-
plugins: [
|
|
722
|
+
plugins: [
|
|
723
|
+
reactGlobalPlugin,
|
|
724
|
+
createCoreGlobalPlugin(themePath),
|
|
725
|
+
createThemeDepsStubPlugin(themePath)
|
|
726
|
+
],
|
|
699
727
|
external: [],
|
|
700
728
|
alias: {
|
|
701
729
|
events: "events/",
|
|
@@ -728,7 +756,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
|
|
|
728
756
|
};
|
|
729
757
|
const context2 = await esbuild.context(buildOptions);
|
|
730
758
|
await context2.rebuild();
|
|
731
|
-
await
|
|
759
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
732
760
|
await generateThemeData(themePath, outputDir, themeName);
|
|
733
761
|
return { context: context2, outputDir };
|
|
734
762
|
}
|
|
@@ -830,7 +858,16 @@ ${locations.join("\n")}`
|
|
|
830
858
|
path8.join(themePath, "node_modules", "@onexapis", "core", "src"),
|
|
831
859
|
path8.join(themePath, "..", "..", "packages", "core", "src"),
|
|
832
860
|
// monorepo sibling
|
|
833
|
-
path8.join(
|
|
861
|
+
path8.join(
|
|
862
|
+
__dirname,
|
|
863
|
+
"..",
|
|
864
|
+
"..",
|
|
865
|
+
"..",
|
|
866
|
+
"..",
|
|
867
|
+
"packages",
|
|
868
|
+
"core",
|
|
869
|
+
"src"
|
|
870
|
+
)
|
|
834
871
|
// from CLI src
|
|
835
872
|
];
|
|
836
873
|
let coreSourceDir = null;
|
|
@@ -844,7 +881,10 @@ ${locations.join("\n")}`
|
|
|
844
881
|
}
|
|
845
882
|
if (coreSourceDir) {
|
|
846
883
|
try {
|
|
847
|
-
const scanned = await scanImportsFromPackage(
|
|
884
|
+
const scanned = await scanImportsFromPackage(
|
|
885
|
+
coreSourceDir,
|
|
886
|
+
"lucide-react"
|
|
887
|
+
);
|
|
848
888
|
for (const names of Object.values(scanned)) {
|
|
849
889
|
for (const name of names) lucideIconNames.add(name);
|
|
850
890
|
}
|
|
@@ -865,7 +905,10 @@ ${locations.join("\n")}`
|
|
|
865
905
|
const mjsFiles = await glob("*.mjs", { cwd: candidate });
|
|
866
906
|
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']lucide-react["']/g;
|
|
867
907
|
for (const file of mjsFiles) {
|
|
868
|
-
const content = await fs7.readFile(
|
|
908
|
+
const content = await fs7.readFile(
|
|
909
|
+
path8.join(candidate, file),
|
|
910
|
+
"utf-8"
|
|
911
|
+
);
|
|
869
912
|
for (const match of content.matchAll(importRegex)) {
|
|
870
913
|
for (const name of match[1].split(",")) {
|
|
871
914
|
const original = name.trim().split(/\s+as\s+/)[0].trim();
|
|
@@ -881,7 +924,10 @@ ${locations.join("\n")}`
|
|
|
881
924
|
}
|
|
882
925
|
}
|
|
883
926
|
try {
|
|
884
|
-
const scanned = await scanImportsFromPackage(
|
|
927
|
+
const scanned = await scanImportsFromPackage(
|
|
928
|
+
themePath,
|
|
929
|
+
"lucide-react"
|
|
930
|
+
);
|
|
885
931
|
for (const names of Object.values(scanned)) {
|
|
886
932
|
for (const name of names) lucideIconNames.add(name);
|
|
887
933
|
}
|
|
@@ -911,14 +957,39 @@ export default new Proxy({}, { get: (_, name) => name === '__esModule' ? true :
|
|
|
911
957
|
if (!result.errors.length) return result;
|
|
912
958
|
} catch {
|
|
913
959
|
}
|
|
960
|
+
try {
|
|
961
|
+
const req = createRequire(import.meta.url || __filename);
|
|
962
|
+
const cjsPath = req.resolve("framer-motion");
|
|
963
|
+
const pkgDir = cjsPath.replace(/[/\\]dist[/\\].*$/, "");
|
|
964
|
+
const esmEntry = path8.join(pkgDir, "dist", "es", "index.mjs");
|
|
965
|
+
const { existsSync } = await import('fs');
|
|
966
|
+
if (existsSync(esmEntry)) {
|
|
967
|
+
return { path: esmEntry, namespace: "file" };
|
|
968
|
+
}
|
|
969
|
+
return { path: cjsPath, namespace: "file" };
|
|
970
|
+
} catch {
|
|
971
|
+
}
|
|
914
972
|
return { path: args.path, namespace: "motion-stub" };
|
|
915
973
|
});
|
|
916
974
|
build2.onLoad({ filter: /.*/, namespace: "motion-stub" }, () => ({
|
|
917
975
|
contents: `
|
|
918
|
-
|
|
919
|
-
const
|
|
976
|
+
import React from 'react';
|
|
977
|
+
const MotionComponent = React.forwardRef((props, ref) => {
|
|
978
|
+
const { initial, animate, exit, variants, transition, whileInView, whileHover, whileTap, viewport, onAnimationComplete, layout, layoutId, style, className, ...rest } = props;
|
|
979
|
+
return React.createElement(rest.as || 'div', { ref, style, className, ...rest }, rest.children);
|
|
980
|
+
});
|
|
981
|
+
const handler = { get: (_, name) => {
|
|
982
|
+
if (name === '__esModule') return true;
|
|
983
|
+
if (name === 'create') return () => new Proxy({}, handler);
|
|
984
|
+
return MotionComponent;
|
|
985
|
+
}};
|
|
920
986
|
export const motion = new Proxy({}, handler);
|
|
921
|
-
export const AnimatePresence =
|
|
987
|
+
export const AnimatePresence = (props) => props.children || null;
|
|
988
|
+
export const useInView = () => true;
|
|
989
|
+
export const useAnimation = () => ({ start: () => {}, stop: () => {}, set: () => {} });
|
|
990
|
+
export const useMotionValue = (v) => ({ get: () => v, set: () => {}, onChange: () => () => {} });
|
|
991
|
+
export const useTransform = (v) => v;
|
|
992
|
+
export const useScroll = () => ({ scrollY: { get: () => 0, onChange: () => () => {} }, scrollYProgress: { get: () => 0, onChange: () => () => {} } });
|
|
922
993
|
export default { motion, AnimatePresence };
|
|
923
994
|
`.trim(),
|
|
924
995
|
loader: "jsx"
|
|
@@ -941,12 +1012,26 @@ export default { motion, AnimatePresence };
|
|
|
941
1012
|
build2.onLoad({ filter: /.*/, namespace: "next-stub" }, (args) => {
|
|
942
1013
|
const stubs = {
|
|
943
1014
|
"next/image": `
|
|
944
|
-
const Image = (props) => {
|
|
945
|
-
const { src, alt, width, height, fill, priority, ...rest } = props;
|
|
946
|
-
const imgSrc = typeof src === 'object' ? src.src : src;
|
|
947
|
-
return React.createElement('img', { src: imgSrc, alt, width: fill ? undefined : width, height: fill ? undefined : height, loading: priority ? 'eager' : 'lazy', ...rest });
|
|
948
|
-
};
|
|
949
1015
|
import React from 'react';
|
|
1016
|
+
const Image = React.forwardRef((props, ref) => {
|
|
1017
|
+
const { src, alt, width, height, fill, priority, sizes, quality, placeholder, blurDataURL, onLoad, onError, style, className, ...rest } = props;
|
|
1018
|
+
const imgSrc = typeof src === 'object' ? src.src : src;
|
|
1019
|
+
const fillStyle = fill ? { position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: style?.objectFit || 'cover', display: 'block' } : {};
|
|
1020
|
+
const mergedStyle = { ...fillStyle, ...style };
|
|
1021
|
+
return React.createElement('img', {
|
|
1022
|
+
ref,
|
|
1023
|
+
src: imgSrc,
|
|
1024
|
+
alt,
|
|
1025
|
+
width: fill ? undefined : width,
|
|
1026
|
+
height: fill ? undefined : height,
|
|
1027
|
+
loading: priority ? 'eager' : 'lazy',
|
|
1028
|
+
style: Object.keys(mergedStyle).length > 0 ? mergedStyle : undefined,
|
|
1029
|
+
className,
|
|
1030
|
+
onLoad,
|
|
1031
|
+
onError,
|
|
1032
|
+
...rest,
|
|
1033
|
+
});
|
|
1034
|
+
});
|
|
950
1035
|
export default Image;
|
|
951
1036
|
`,
|
|
952
1037
|
"next/link": `
|
|
@@ -1407,45 +1492,20 @@ async function initCommand(projectName, options = {}) {
|
|
|
1407
1492
|
try {
|
|
1408
1493
|
fs2.mkdirSync(projectPath, { recursive: true });
|
|
1409
1494
|
await copyTemplate(template, projectPath, data);
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
const layoutContent = generateThemeLayout(data);
|
|
1417
|
-
await writeFile(path8.join(srcPath, "layout.ts"), layoutContent);
|
|
1418
|
-
const indexContent = generateThemeIndex(data);
|
|
1419
|
-
await writeFile(path8.join(srcPath, "index.ts"), indexContent);
|
|
1420
|
-
const sectionsPath = path8.join(srcPath, "sections");
|
|
1421
|
-
fs2.mkdirSync(sectionsPath, { recursive: true });
|
|
1422
|
-
await writeFile(
|
|
1423
|
-
path8.join(sectionsPath, "README.md"),
|
|
1424
|
-
`# ${displayName} Sections
|
|
1425
|
-
|
|
1426
|
-
Add your theme-specific sections here.
|
|
1427
|
-
`
|
|
1495
|
+
await renameThemeInFiles(
|
|
1496
|
+
projectPath,
|
|
1497
|
+
name,
|
|
1498
|
+
displayName,
|
|
1499
|
+
description,
|
|
1500
|
+
author
|
|
1428
1501
|
);
|
|
1429
|
-
const blocksPath = path8.join(srcPath, "blocks");
|
|
1430
|
-
fs2.mkdirSync(blocksPath, { recursive: true });
|
|
1431
|
-
await writeFile(
|
|
1432
|
-
path8.join(blocksPath, "README.md"),
|
|
1433
|
-
`# ${displayName} Blocks
|
|
1434
|
-
|
|
1435
|
-
Add your theme-specific blocks here.
|
|
1436
|
-
`
|
|
1437
|
-
);
|
|
1438
|
-
const pagesPath = path8.join(srcPath, "pages");
|
|
1439
|
-
fs2.mkdirSync(pagesPath, { recursive: true });
|
|
1440
|
-
const homePageContent = generateHomePage(data);
|
|
1441
|
-
await writeFile(path8.join(pagesPath, "home.ts"), homePageContent);
|
|
1442
1502
|
logger.stopSpinner(true, "Project structure created!");
|
|
1443
1503
|
if (options.git) {
|
|
1444
1504
|
logger.startSpinner("Initializing git repository...");
|
|
1445
1505
|
try {
|
|
1446
1506
|
execSync("git init", { cwd: projectPath, stdio: "ignore" });
|
|
1447
1507
|
execSync("git add .", { cwd: projectPath, stdio: "ignore" });
|
|
1448
|
-
execSync('git commit -m "Initial commit from
|
|
1508
|
+
execSync('git commit -m "Initial commit from onexthm init"', {
|
|
1449
1509
|
cwd: projectPath,
|
|
1450
1510
|
stdio: "ignore"
|
|
1451
1511
|
});
|
|
@@ -1482,14 +1542,14 @@ Add your theme-specific blocks here.
|
|
|
1482
1542
|
logger.log(` npm run dev # Start development mode`);
|
|
1483
1543
|
logger.newLine();
|
|
1484
1544
|
logger.section("Theme structure:");
|
|
1485
|
-
logger.log("
|
|
1545
|
+
logger.log(" bundle-entry.ts - Theme manifest and exports");
|
|
1486
1546
|
logger.log(
|
|
1487
|
-
"
|
|
1547
|
+
" theme.config.ts - Design tokens (colors, typography, etc.)"
|
|
1488
1548
|
);
|
|
1489
|
-
logger.log("
|
|
1490
|
-
logger.log("
|
|
1491
|
-
logger.log("
|
|
1492
|
-
logger.log("
|
|
1549
|
+
logger.log(" theme.layout.ts - Header and footer configuration");
|
|
1550
|
+
logger.log(" sections/ - Custom sections for your theme");
|
|
1551
|
+
logger.log(" pages/ - Page configurations");
|
|
1552
|
+
logger.log(" CLAUDE.md - AI assistant context");
|
|
1493
1553
|
logger.newLine();
|
|
1494
1554
|
logger.success(`Happy theming! \u{1F3A8}`);
|
|
1495
1555
|
} catch (error) {
|
|
@@ -1503,231 +1563,33 @@ Add your theme-specific blocks here.
|
|
|
1503
1563
|
process.exit(1);
|
|
1504
1564
|
}
|
|
1505
1565
|
}
|
|
1506
|
-
function
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
// Example: productCard: () => import("./blocks/product-card").then((m) => m.productCardDefinition),
|
|
1534
|
-
},
|
|
1535
|
-
|
|
1536
|
-
// Default pages
|
|
1537
|
-
pages: {
|
|
1538
|
-
home: () => import("./pages/home").then((m) => m.homePageConfig),
|
|
1539
|
-
},
|
|
1540
|
-
|
|
1541
|
-
// Supported page types
|
|
1542
|
-
supportedPageTypes: ["home", "about", "contact", "custom"],
|
|
1543
|
-
|
|
1544
|
-
// Preview image (optional)
|
|
1545
|
-
preview: undefined,
|
|
1546
|
-
|
|
1547
|
-
// Tags for categorization (optional)
|
|
1548
|
-
tags: ["custom"],
|
|
1549
|
-
};
|
|
1550
|
-
|
|
1551
|
-
export default manifest;
|
|
1552
|
-
`;
|
|
1553
|
-
}
|
|
1554
|
-
function generateThemeConfig(data) {
|
|
1555
|
-
return `import type { ThemeConfig } from "@onexapis/core";
|
|
1556
|
-
|
|
1557
|
-
/**
|
|
1558
|
-
* ${data.displayName} Theme Configuration
|
|
1559
|
-
* Design tokens: colors, typography, spacing, etc.
|
|
1560
|
-
*/
|
|
1561
|
-
export const themeConfig: ThemeConfig = {
|
|
1562
|
-
// Color palette
|
|
1563
|
-
colors: {
|
|
1564
|
-
primary: {
|
|
1565
|
-
50: "#eff6ff",
|
|
1566
|
-
100: "#dbeafe",
|
|
1567
|
-
200: "#bfdbfe",
|
|
1568
|
-
300: "#93c5fd",
|
|
1569
|
-
400: "#60a5fa",
|
|
1570
|
-
500: "#3b82f6",
|
|
1571
|
-
600: "#2563eb",
|
|
1572
|
-
700: "#1d4ed8",
|
|
1573
|
-
800: "#1e40af",
|
|
1574
|
-
900: "#1e3a8a",
|
|
1575
|
-
},
|
|
1576
|
-
secondary: {
|
|
1577
|
-
50: "#f8fafc",
|
|
1578
|
-
100: "#f1f5f9",
|
|
1579
|
-
200: "#e2e8f0",
|
|
1580
|
-
300: "#cbd5e1",
|
|
1581
|
-
400: "#94a3b8",
|
|
1582
|
-
500: "#64748b",
|
|
1583
|
-
600: "#475569",
|
|
1584
|
-
700: "#334155",
|
|
1585
|
-
800: "#1e293b",
|
|
1586
|
-
900: "#0f172a",
|
|
1587
|
-
},
|
|
1588
|
-
accent: {
|
|
1589
|
-
50: "#fdf4ff",
|
|
1590
|
-
100: "#fae8ff",
|
|
1591
|
-
200: "#f5d0fe",
|
|
1592
|
-
300: "#f0abfc",
|
|
1593
|
-
400: "#e879f9",
|
|
1594
|
-
500: "#d946ef",
|
|
1595
|
-
600: "#c026d3",
|
|
1596
|
-
700: "#a21caf",
|
|
1597
|
-
800: "#86198f",
|
|
1598
|
-
900: "#701a75",
|
|
1599
|
-
},
|
|
1600
|
-
},
|
|
1601
|
-
|
|
1602
|
-
// Typography
|
|
1603
|
-
typography: {
|
|
1604
|
-
fontFamily: {
|
|
1605
|
-
sans: ["Inter", "system-ui", "sans-serif"],
|
|
1606
|
-
serif: ["Georgia", "serif"],
|
|
1607
|
-
mono: ["Monaco", "monospace"],
|
|
1608
|
-
},
|
|
1609
|
-
fontSize: {
|
|
1610
|
-
xs: "0.75rem",
|
|
1611
|
-
sm: "0.875rem",
|
|
1612
|
-
base: "1rem",
|
|
1613
|
-
lg: "1.125rem",
|
|
1614
|
-
xl: "1.25rem",
|
|
1615
|
-
"2xl": "1.5rem",
|
|
1616
|
-
"3xl": "1.875rem",
|
|
1617
|
-
"4xl": "2.25rem",
|
|
1618
|
-
"5xl": "3rem",
|
|
1619
|
-
},
|
|
1620
|
-
},
|
|
1621
|
-
|
|
1622
|
-
// Spacing
|
|
1623
|
-
spacing: {
|
|
1624
|
-
xs: "0.5rem",
|
|
1625
|
-
sm: "1rem",
|
|
1626
|
-
md: "1.5rem",
|
|
1627
|
-
lg: "2rem",
|
|
1628
|
-
xl: "3rem",
|
|
1629
|
-
"2xl": "4rem",
|
|
1630
|
-
"3xl": "6rem",
|
|
1631
|
-
"4xl": "8rem",
|
|
1632
|
-
},
|
|
1633
|
-
|
|
1634
|
-
// Border radius
|
|
1635
|
-
borderRadius: {
|
|
1636
|
-
none: "0",
|
|
1637
|
-
sm: "0.125rem",
|
|
1638
|
-
md: "0.375rem",
|
|
1639
|
-
lg: "0.5rem",
|
|
1640
|
-
xl: "0.75rem",
|
|
1641
|
-
full: "9999px",
|
|
1642
|
-
},
|
|
1643
|
-
|
|
1644
|
-
// Breakpoints
|
|
1645
|
-
breakpoints: {
|
|
1646
|
-
sm: "640px",
|
|
1647
|
-
md: "768px",
|
|
1648
|
-
lg: "1024px",
|
|
1649
|
-
xl: "1280px",
|
|
1650
|
-
"2xl": "1536px",
|
|
1651
|
-
},
|
|
1652
|
-
};
|
|
1653
|
-
`;
|
|
1654
|
-
}
|
|
1655
|
-
function generateThemeLayout(data) {
|
|
1656
|
-
return `import type { ThemeLayoutConfig } from "@onexapis/core";
|
|
1657
|
-
|
|
1658
|
-
/**
|
|
1659
|
-
* ${data.themeName} Theme Layout
|
|
1660
|
-
* Define header and footer sections
|
|
1661
|
-
*/
|
|
1662
|
-
export const themeLayout: ThemeLayoutConfig = {
|
|
1663
|
-
// Header section configuration
|
|
1664
|
-
header: undefined,
|
|
1665
|
-
// Example:
|
|
1666
|
-
// header: {
|
|
1667
|
-
// type: "header",
|
|
1668
|
-
// template: "default",
|
|
1669
|
-
// enabled: true,
|
|
1670
|
-
// settings: {},
|
|
1671
|
-
// },
|
|
1672
|
-
|
|
1673
|
-
// Footer section configuration
|
|
1674
|
-
footer: undefined,
|
|
1675
|
-
// Example:
|
|
1676
|
-
// footer: {
|
|
1677
|
-
// type: "footer",
|
|
1678
|
-
// template: "default",
|
|
1679
|
-
// enabled: true,
|
|
1680
|
-
// settings: {},
|
|
1681
|
-
// },
|
|
1682
|
-
};
|
|
1683
|
-
`;
|
|
1684
|
-
}
|
|
1685
|
-
function generateThemeIndex(data) {
|
|
1686
|
-
return `/**
|
|
1687
|
-
* ${data.themeNamePascal} Theme
|
|
1688
|
-
*/
|
|
1689
|
-
|
|
1690
|
-
export { manifest as ${data.themeNamePascal}Manifest } from "./manifest";
|
|
1691
|
-
export { themeConfig as ${data.themeNamePascal}Config } from "./config";
|
|
1692
|
-
export { themeLayout as ${data.themeNamePascal}Layout } from "./layout";
|
|
1693
|
-
`;
|
|
1694
|
-
}
|
|
1695
|
-
function generateHomePage(data) {
|
|
1696
|
-
return `import type { PageConfig } from "@onexapis/core";
|
|
1697
|
-
|
|
1698
|
-
/**
|
|
1699
|
-
* Home Page Configuration
|
|
1700
|
-
*/
|
|
1701
|
-
export const homePageConfig: PageConfig = {
|
|
1702
|
-
type: "home",
|
|
1703
|
-
title: "${data.displayName}",
|
|
1704
|
-
description: "Welcome to ${data.displayName}",
|
|
1705
|
-
|
|
1706
|
-
// SEO metadata
|
|
1707
|
-
seo: {
|
|
1708
|
-
title: "${data.displayName} - Home",
|
|
1709
|
-
description: "Welcome to ${data.displayName}",
|
|
1710
|
-
keywords: [],
|
|
1711
|
-
ogImage: undefined,
|
|
1712
|
-
},
|
|
1713
|
-
|
|
1714
|
-
// Page sections
|
|
1715
|
-
sections: [
|
|
1716
|
-
// Add your sections here
|
|
1717
|
-
// Example:
|
|
1718
|
-
// {
|
|
1719
|
-
// id: "hero-1",
|
|
1720
|
-
// type: "hero",
|
|
1721
|
-
// template: "default",
|
|
1722
|
-
// order: 0,
|
|
1723
|
-
// enabled: true,
|
|
1724
|
-
// settings: {},
|
|
1725
|
-
// components: [],
|
|
1726
|
-
// blocks: [],
|
|
1727
|
-
// },
|
|
1728
|
-
],
|
|
1729
|
-
};
|
|
1730
|
-
`;
|
|
1566
|
+
async function renameThemeInFiles(projectPath, themeName, displayName, description, author) {
|
|
1567
|
+
const configPath = path8.join(projectPath, "theme.config.ts");
|
|
1568
|
+
if (fs2.existsSync(configPath)) {
|
|
1569
|
+
let content = fs2.readFileSync(configPath, "utf-8");
|
|
1570
|
+
content = content.replace(
|
|
1571
|
+
/name: "My Simple Theme"/,
|
|
1572
|
+
`name: "${displayName}"`
|
|
1573
|
+
);
|
|
1574
|
+
content = content.replace(
|
|
1575
|
+
/description: ".*?"/,
|
|
1576
|
+
`description: "${description}"`
|
|
1577
|
+
);
|
|
1578
|
+
fs2.writeFileSync(configPath, content, "utf-8");
|
|
1579
|
+
}
|
|
1580
|
+
const pkgPath = path8.join(projectPath, "package.json");
|
|
1581
|
+
if (fs2.existsSync(pkgPath)) {
|
|
1582
|
+
let content = fs2.readFileSync(pkgPath, "utf-8");
|
|
1583
|
+
content = content.replace(
|
|
1584
|
+
/@onex-themes\/my-simple/g,
|
|
1585
|
+
`@onex-themes/${themeName}`
|
|
1586
|
+
);
|
|
1587
|
+
content = content.replace(
|
|
1588
|
+
/"description": ".*?"/,
|
|
1589
|
+
`"description": "${description}"`
|
|
1590
|
+
);
|
|
1591
|
+
fs2.writeFileSync(pkgPath, content, "utf-8");
|
|
1592
|
+
}
|
|
1731
1593
|
}
|
|
1732
1594
|
|
|
1733
1595
|
// src/commands/create-section.ts
|
|
@@ -2795,7 +2657,7 @@ async function buildCommand(options) {
|
|
|
2795
2657
|
logger.stopSpinner(true, "Lint passed");
|
|
2796
2658
|
const pkgJson = fs.readJsonSync(packageJsonPath);
|
|
2797
2659
|
const buildScript = pkgJson.scripts?.build || "";
|
|
2798
|
-
const isRecursive = buildScript.includes("
|
|
2660
|
+
const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex-cli build");
|
|
2799
2661
|
logger.startSpinner(
|
|
2800
2662
|
options.watch ? "Building (watch mode)..." : "Building..."
|
|
2801
2663
|
);
|
|
@@ -2941,7 +2803,7 @@ async function packageCommand(options) {
|
|
|
2941
2803
|
logger.newLine();
|
|
2942
2804
|
logger.section("Next steps:");
|
|
2943
2805
|
logger.log(
|
|
2944
|
-
`
|
|
2806
|
+
` onexthm deploy --package ${path8.relative(process.cwd(), outputPath)}`
|
|
2945
2807
|
);
|
|
2946
2808
|
} catch (error) {
|
|
2947
2809
|
logger.stopSpinner(false, "Failed to create package");
|
|
@@ -3003,7 +2865,7 @@ async function deployCommand(options) {
|
|
|
3003
2865
|
} else if (options.theme) {
|
|
3004
2866
|
const distDir = path8.join(process.cwd(), "dist");
|
|
3005
2867
|
if (!fs.existsSync(distDir)) {
|
|
3006
|
-
logger.error("No dist/ directory found. Run '
|
|
2868
|
+
logger.error("No dist/ directory found. Run 'onexthm package' first.");
|
|
3007
2869
|
process.exit(1);
|
|
3008
2870
|
}
|
|
3009
2871
|
const files = fs.readdirSync(distDir);
|
|
@@ -3012,7 +2874,7 @@ async function deployCommand(options) {
|
|
|
3012
2874
|
);
|
|
3013
2875
|
if (packageFiles.length === 0) {
|
|
3014
2876
|
logger.error(`No package found for theme "${options.theme}".`);
|
|
3015
|
-
logger.info("Run:
|
|
2877
|
+
logger.info("Run: onexthm package --theme " + options.theme);
|
|
3016
2878
|
process.exit(1);
|
|
3017
2879
|
}
|
|
3018
2880
|
packageFiles.sort().reverse();
|
|
@@ -3020,8 +2882,8 @@ async function deployCommand(options) {
|
|
|
3020
2882
|
} else {
|
|
3021
2883
|
logger.error("Either --package or --theme must be specified.");
|
|
3022
2884
|
logger.info("Examples:");
|
|
3023
|
-
logger.log("
|
|
3024
|
-
logger.log("
|
|
2885
|
+
logger.log(" onexthm deploy --package dist/tinan-1.0.0.zip");
|
|
2886
|
+
logger.log(" onexthm deploy --theme tinan");
|
|
3025
2887
|
process.exit(1);
|
|
3026
2888
|
}
|
|
3027
2889
|
if (!fs.existsSync(packagePath)) {
|
|
@@ -3037,7 +2899,7 @@ async function deployCommand(options) {
|
|
|
3037
2899
|
logger.log(`Path: ${path8.relative(process.cwd(), packagePath)}`);
|
|
3038
2900
|
logger.newLine();
|
|
3039
2901
|
const apiUrl = options.apiUrl || process.env.ONEX_API_URL || "http://localhost:3001";
|
|
3040
|
-
const uploadEndpoint = `${apiUrl}/api/themes/upload`;
|
|
2902
|
+
const uploadEndpoint = `${apiUrl}/website-api/themes/upload`;
|
|
3041
2903
|
logger.section("Uploading to API Server");
|
|
3042
2904
|
logger.info(`Endpoint: ${uploadEndpoint}`);
|
|
3043
2905
|
logger.newLine();
|
|
@@ -3054,7 +2916,7 @@ async function deployCommand(options) {
|
|
|
3054
2916
|
if (options.environment) {
|
|
3055
2917
|
formData.append("environment", options.environment);
|
|
3056
2918
|
}
|
|
3057
|
-
const response = await
|
|
2919
|
+
const response = await fetch2(uploadEndpoint, {
|
|
3058
2920
|
method: "POST",
|
|
3059
2921
|
body: formData,
|
|
3060
2922
|
headers: formData.getHeaders()
|
|
@@ -3135,7 +2997,7 @@ function getBucketName(env) {
|
|
|
3135
2997
|
return process.env.BUCKET_NAME;
|
|
3136
2998
|
}
|
|
3137
2999
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3138
|
-
return environment === "production" ? "
|
|
3000
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3139
3001
|
}
|
|
3140
3002
|
async function findCompiledThemeDir(themeId, version2) {
|
|
3141
3003
|
const searchPaths = [path8.resolve(process.cwd(), "dist")];
|
|
@@ -3242,7 +3104,7 @@ async function uploadCommand(options) {
|
|
|
3242
3104
|
if (!compiledDir) {
|
|
3243
3105
|
spinner.fail(
|
|
3244
3106
|
chalk4.red(
|
|
3245
|
-
`Compiled theme not found for ${themeId}@${version2}. Run '
|
|
3107
|
+
`Compiled theme not found for ${themeId}@${version2}. Run 'onexthm build' first.`
|
|
3246
3108
|
)
|
|
3247
3109
|
);
|
|
3248
3110
|
logger.info(chalk4.gray(`Expected location:
|
|
@@ -3251,7 +3113,7 @@ async function uploadCommand(options) {
|
|
|
3251
3113
|
}
|
|
3252
3114
|
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
3253
3115
|
spinner.start("Creating bundle.zip...");
|
|
3254
|
-
const tmpDir =
|
|
3116
|
+
const tmpDir = os3.tmpdir();
|
|
3255
3117
|
const bundleZipPath = path8.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
|
|
3256
3118
|
await createZipFromDir(compiledDir, bundleZipPath);
|
|
3257
3119
|
const bundleZipBuffer = await fs.readFile(bundleZipPath);
|
|
@@ -3404,7 +3266,7 @@ function getBucketName2(env) {
|
|
|
3404
3266
|
return process.env.BUCKET_NAME;
|
|
3405
3267
|
}
|
|
3406
3268
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3407
|
-
return environment === "production" ? "
|
|
3269
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3408
3270
|
}
|
|
3409
3271
|
async function streamToString(stream) {
|
|
3410
3272
|
const chunks = [];
|
|
@@ -3474,7 +3336,7 @@ function showDownloadFailureHelp(themeId, bucket) {
|
|
|
3474
3336
|
console.log(chalk4.white("1. Compile and upload the theme:"));
|
|
3475
3337
|
console.log(chalk4.gray(` cd themes/${themeId}`));
|
|
3476
3338
|
console.log(chalk4.gray(" pnpm build"));
|
|
3477
|
-
console.log(chalk4.gray("
|
|
3339
|
+
console.log(chalk4.gray(" onexthm upload"));
|
|
3478
3340
|
console.log();
|
|
3479
3341
|
console.log(chalk4.white("2. Verify AWS credentials are set:"));
|
|
3480
3342
|
console.log(
|
|
@@ -3605,7 +3467,7 @@ function getBucketName3(env) {
|
|
|
3605
3467
|
return process.env.BUCKET_NAME;
|
|
3606
3468
|
}
|
|
3607
3469
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3608
|
-
return environment === "production" ? "
|
|
3470
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3609
3471
|
}
|
|
3610
3472
|
async function streamToString2(stream) {
|
|
3611
3473
|
const chunks = [];
|
|
@@ -3649,8 +3511,8 @@ function runInstall(cwd) {
|
|
|
3649
3511
|
});
|
|
3650
3512
|
}
|
|
3651
3513
|
async function promptThemeName(originalName) {
|
|
3652
|
-
const { default:
|
|
3653
|
-
const { themeName } = await
|
|
3514
|
+
const { default: inquirer7 } = await import('inquirer');
|
|
3515
|
+
const { themeName } = await inquirer7.prompt([
|
|
3654
3516
|
{
|
|
3655
3517
|
type: "input",
|
|
3656
3518
|
name: "themeName",
|
|
@@ -3776,7 +3638,7 @@ async function cloneCommand(themeName, options) {
|
|
|
3776
3638
|
chalk4.yellow("The theme source may not have been uploaded yet.")
|
|
3777
3639
|
);
|
|
3778
3640
|
console.log(
|
|
3779
|
-
chalk4.gray(`Upload source with:
|
|
3641
|
+
chalk4.gray(`Upload source with: onexthm upload --theme ${themeName}`)
|
|
3780
3642
|
);
|
|
3781
3643
|
console.log();
|
|
3782
3644
|
process.exit(1);
|
|
@@ -3803,7 +3665,7 @@ async function cloneCommand(themeName, options) {
|
|
|
3803
3665
|
[
|
|
3804
3666
|
"# API Configuration (enables real data in preview)",
|
|
3805
3667
|
"# Get your Company ID from the OneX dashboard",
|
|
3806
|
-
"NEXT_PUBLIC_API_URL=https://
|
|
3668
|
+
"NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
|
|
3807
3669
|
"NEXT_PUBLIC_COMPANY_ID=",
|
|
3808
3670
|
""
|
|
3809
3671
|
].join("\n")
|
|
@@ -3845,7 +3707,7 @@ async function cloneCommand(themeName, options) {
|
|
|
3845
3707
|
if (options.install === false) {
|
|
3846
3708
|
console.log(chalk4.gray(" pnpm install"));
|
|
3847
3709
|
}
|
|
3848
|
-
console.log(chalk4.gray("
|
|
3710
|
+
console.log(chalk4.gray(" onexthm build"));
|
|
3849
3711
|
console.log();
|
|
3850
3712
|
} catch (error) {
|
|
3851
3713
|
spinner.fail(chalk4.red(`Clone failed: ${error.message}`));
|
|
@@ -3873,6 +3735,7 @@ var MIME_TYPES = {
|
|
|
3873
3735
|
};
|
|
3874
3736
|
function createDevServer(options) {
|
|
3875
3737
|
const clients = /* @__PURE__ */ new Set();
|
|
3738
|
+
const themeDataPath = path8.join(options.distDir, "theme-data.json");
|
|
3876
3739
|
const server = http.createServer((req, res) => {
|
|
3877
3740
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
3878
3741
|
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
@@ -3886,7 +3749,9 @@ function createDevServer(options) {
|
|
|
3886
3749
|
const pathname = url.pathname;
|
|
3887
3750
|
if (pathname === "/" || pathname === "/index.html") {
|
|
3888
3751
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3889
|
-
res.end(
|
|
3752
|
+
res.end(
|
|
3753
|
+
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
3754
|
+
);
|
|
3890
3755
|
return;
|
|
3891
3756
|
}
|
|
3892
3757
|
if (pathname === "/preview-runtime.js") {
|
|
@@ -3905,6 +3770,37 @@ function createDevServer(options) {
|
|
|
3905
3770
|
serveFile(res, assetPath);
|
|
3906
3771
|
return;
|
|
3907
3772
|
}
|
|
3773
|
+
if (pathname.startsWith("/themes/")) {
|
|
3774
|
+
const match = pathname.match(/^\/themes\/[^/]+\/assets\/(.+)/);
|
|
3775
|
+
if (match) {
|
|
3776
|
+
const assetPath = path8.join(options.themePath, "assets", match[1]);
|
|
3777
|
+
if (!assetPath.startsWith(path8.join(options.themePath, "assets"))) {
|
|
3778
|
+
res.writeHead(403);
|
|
3779
|
+
res.end("Forbidden");
|
|
3780
|
+
return;
|
|
3781
|
+
}
|
|
3782
|
+
serveFile(res, assetPath);
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
if (pathname.startsWith("/assets/")) {
|
|
3787
|
+
const subpath = pathname.replace(/^\/assets\//, "");
|
|
3788
|
+
const segments = subpath.split("/");
|
|
3789
|
+
let assetPath;
|
|
3790
|
+
if (segments[0] === options.themeName || segments[0] === options.themeName.replace(/^my-/, "")) {
|
|
3791
|
+
assetPath = path8.join(
|
|
3792
|
+
options.themePath,
|
|
3793
|
+
"assets",
|
|
3794
|
+
segments.slice(1).join("/")
|
|
3795
|
+
);
|
|
3796
|
+
} else {
|
|
3797
|
+
assetPath = path8.join(options.themePath, "assets", subpath);
|
|
3798
|
+
}
|
|
3799
|
+
if (assetPath.startsWith(path8.join(options.themePath, "assets")) && fs2.existsSync(assetPath)) {
|
|
3800
|
+
serveFile(res, assetPath);
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3908
3804
|
const filePath = path8.join(options.distDir, pathname);
|
|
3909
3805
|
if (!filePath.startsWith(options.distDir)) {
|
|
3910
3806
|
res.writeHead(403);
|
|
@@ -3915,7 +3811,9 @@ function createDevServer(options) {
|
|
|
3915
3811
|
serveFile(res, filePath);
|
|
3916
3812
|
} else {
|
|
3917
3813
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
3918
|
-
res.end(
|
|
3814
|
+
res.end(
|
|
3815
|
+
generatePreviewHTML(options.themeName, options.port, themeDataPath)
|
|
3816
|
+
);
|
|
3919
3817
|
}
|
|
3920
3818
|
});
|
|
3921
3819
|
const wss = new WebSocketServer({ server });
|
|
@@ -3956,17 +3854,82 @@ function serveFile(res, filePath) {
|
|
|
3956
3854
|
res.end("Internal Server Error");
|
|
3957
3855
|
}
|
|
3958
3856
|
}
|
|
3959
|
-
function generatePreviewHTML(themeName, port) {
|
|
3857
|
+
function generatePreviewHTML(themeName, port, themeDataPath) {
|
|
3858
|
+
let fontLinks = "";
|
|
3859
|
+
let fontVarsCSS = "";
|
|
3860
|
+
if (themeDataPath) {
|
|
3861
|
+
try {
|
|
3862
|
+
const themeData = JSON.parse(fs2.readFileSync(themeDataPath, "utf-8"));
|
|
3863
|
+
const typography = (themeData?.themeConfig || themeData?.theme?.config)?.typography?.fontFamily;
|
|
3864
|
+
if (typography) {
|
|
3865
|
+
const fontFamilies = /* @__PURE__ */ new Set();
|
|
3866
|
+
for (const value of Object.values(typography)) {
|
|
3867
|
+
const primary = value.split(",")[0].trim();
|
|
3868
|
+
if (primary && ![
|
|
3869
|
+
"serif",
|
|
3870
|
+
"sans-serif",
|
|
3871
|
+
"monospace",
|
|
3872
|
+
"system-ui",
|
|
3873
|
+
"Georgia",
|
|
3874
|
+
"Inter",
|
|
3875
|
+
"Consolas"
|
|
3876
|
+
].includes(primary)) {
|
|
3877
|
+
fontFamilies.add(primary);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (fontFamilies.size > 0) {
|
|
3881
|
+
const families = Array.from(fontFamilies).map(
|
|
3882
|
+
(f) => `family=${f.replace(/\s+/g, "+")}:wght@300;400;500;600;700;800;900`
|
|
3883
|
+
).join("&");
|
|
3884
|
+
fontLinks = `<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
3885
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
3886
|
+
<link href="https://fonts.googleapis.com/css2?${families}&display=swap" rel="stylesheet">`;
|
|
3887
|
+
}
|
|
3888
|
+
const heading = typography.heading || typography.body || "system-ui";
|
|
3889
|
+
const body = typography.body || "system-ui";
|
|
3890
|
+
fontVarsCSS = `
|
|
3891
|
+
:root {
|
|
3892
|
+
--font-heading: ${heading};
|
|
3893
|
+
--font-body: ${body};
|
|
3894
|
+
}
|
|
3895
|
+
body { font-family: var(--font-body); }
|
|
3896
|
+
h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }`;
|
|
3897
|
+
}
|
|
3898
|
+
} catch {
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3960
3901
|
return `<!DOCTYPE html>
|
|
3961
3902
|
<html lang="en">
|
|
3962
3903
|
<head>
|
|
3963
3904
|
<meta charset="UTF-8">
|
|
3964
3905
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3965
3906
|
<title>OneX Dev \u2014 ${themeName}</title>
|
|
3907
|
+
${fontLinks}
|
|
3966
3908
|
<!-- Tailwind CSS Play CDN \u2014 JIT compilation in browser for dev preview -->
|
|
3967
3909
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
3910
|
+
<script>
|
|
3911
|
+
tailwind.config = {
|
|
3912
|
+
theme: {
|
|
3913
|
+
extend: {
|
|
3914
|
+
aspectRatio: {
|
|
3915
|
+
'2/1': '2 / 1',
|
|
3916
|
+
'3/2': '3 / 2',
|
|
3917
|
+
'4/3': '4 / 3',
|
|
3918
|
+
'3/4': '3 / 4',
|
|
3919
|
+
'5/4': '5 / 4',
|
|
3920
|
+
'16/9': '16 / 9',
|
|
3921
|
+
'21/9': '21 / 9',
|
|
3922
|
+
},
|
|
3923
|
+
fontFamily: {
|
|
3924
|
+
playfair: ['Playfair Display', 'Georgia', 'serif'],
|
|
3925
|
+
roboto: ['Roboto', 'system-ui', 'sans-serif'],
|
|
3926
|
+
},
|
|
3927
|
+
},
|
|
3928
|
+
},
|
|
3929
|
+
}
|
|
3930
|
+
</script>
|
|
3968
3931
|
<style>
|
|
3969
|
-
#onex-preview-root { margin-top: 0; }
|
|
3932
|
+
#onex-preview-root { margin-top: 0; }${fontVarsCSS}
|
|
3970
3933
|
</style>
|
|
3971
3934
|
</head>
|
|
3972
3935
|
<body>
|
|
@@ -4050,7 +4013,7 @@ async function devCommand(options) {
|
|
|
4050
4013
|
logger.info(`File changed: ${filePath}`);
|
|
4051
4014
|
try {
|
|
4052
4015
|
await context2.rebuild();
|
|
4053
|
-
await
|
|
4016
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
4054
4017
|
server.broadcast({ type: "reload", timestamp: Date.now() });
|
|
4055
4018
|
logger.success("Rebuilt successfully");
|
|
4056
4019
|
} catch (error) {
|
|
@@ -4085,7 +4048,7 @@ async function devCommand(options) {
|
|
|
4085
4048
|
|
|
4086
4049
|
// src/commands/config.ts
|
|
4087
4050
|
init_logger();
|
|
4088
|
-
var CONFIG_DIR = path8.join(
|
|
4051
|
+
var CONFIG_DIR = path8.join(os3.homedir(), ".onexthm");
|
|
4089
4052
|
var CONFIG_FILE = path8.join(CONFIG_DIR, ".env");
|
|
4090
4053
|
var CONFIG_ENTRIES = [
|
|
4091
4054
|
{
|
|
@@ -4115,7 +4078,7 @@ var CONFIG_ENTRIES = [
|
|
|
4115
4078
|
key: "NEXT_PUBLIC_API_URL",
|
|
4116
4079
|
label: "API URL",
|
|
4117
4080
|
required: false,
|
|
4118
|
-
defaultValue: "https://
|
|
4081
|
+
defaultValue: "https://platform-dev.onexeos.com"
|
|
4119
4082
|
},
|
|
4120
4083
|
{
|
|
4121
4084
|
key: "NEXT_PUBLIC_COMPANY_ID",
|
|
@@ -4139,7 +4102,7 @@ function parseEnvFile(content) {
|
|
|
4139
4102
|
function serializeEnv(values) {
|
|
4140
4103
|
const lines = [
|
|
4141
4104
|
"# OneX CLI Configuration",
|
|
4142
|
-
"# Generated by:
|
|
4105
|
+
"# Generated by: onexthm config",
|
|
4143
4106
|
""
|
|
4144
4107
|
];
|
|
4145
4108
|
lines.push("# AWS / S3 Configuration");
|
|
@@ -4226,6 +4189,461 @@ async function configCommand() {
|
|
|
4226
4189
|
);
|
|
4227
4190
|
}
|
|
4228
4191
|
|
|
4192
|
+
// src/commands/login.ts
|
|
4193
|
+
init_logger();
|
|
4194
|
+
var AUTH_DIR = path8.join(os3.homedir(), ".onexthm");
|
|
4195
|
+
var AUTH_FILE = path8.join(AUTH_DIR, "auth.json");
|
|
4196
|
+
function getApiUrl() {
|
|
4197
|
+
return process.env.NEXT_PUBLIC_API_URL || process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
|
|
4198
|
+
}
|
|
4199
|
+
async function saveAuthTokens(tokens) {
|
|
4200
|
+
await fs.ensureDir(AUTH_DIR);
|
|
4201
|
+
const key = getMachineKey();
|
|
4202
|
+
const data = JSON.stringify(tokens);
|
|
4203
|
+
const encrypted = encrypt(data, key);
|
|
4204
|
+
await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
|
|
4205
|
+
}
|
|
4206
|
+
function loadAuthTokens() {
|
|
4207
|
+
try {
|
|
4208
|
+
if (!fs.existsSync(AUTH_FILE)) return null;
|
|
4209
|
+
const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
|
|
4210
|
+
const key = getMachineKey();
|
|
4211
|
+
const data = decrypt(encrypted, key);
|
|
4212
|
+
return JSON.parse(data);
|
|
4213
|
+
} catch {
|
|
4214
|
+
return null;
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
async function clearAuthTokens() {
|
|
4218
|
+
try {
|
|
4219
|
+
await fs.remove(AUTH_FILE);
|
|
4220
|
+
} catch {
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
function isTokenExpired(tokens) {
|
|
4224
|
+
return Date.now() / 1e3 > tokens.expiresAt - 60;
|
|
4225
|
+
}
|
|
4226
|
+
async function getValidTokens() {
|
|
4227
|
+
const tokens = loadAuthTokens();
|
|
4228
|
+
if (!tokens) return null;
|
|
4229
|
+
if (!isTokenExpired(tokens)) return tokens;
|
|
4230
|
+
try {
|
|
4231
|
+
const apiUrl = getApiUrl();
|
|
4232
|
+
const response = await fetch(`${apiUrl}/auth/refresh`, {
|
|
4233
|
+
method: "POST",
|
|
4234
|
+
headers: { "Content-Type": "application/json" },
|
|
4235
|
+
body: JSON.stringify({ refresh_token: tokens.refreshToken })
|
|
4236
|
+
});
|
|
4237
|
+
if (!response.ok) {
|
|
4238
|
+
await clearAuthTokens();
|
|
4239
|
+
return null;
|
|
4240
|
+
}
|
|
4241
|
+
const data = await response.json();
|
|
4242
|
+
const body = data.statusCode ? data.body : data;
|
|
4243
|
+
const refreshed = {
|
|
4244
|
+
...tokens,
|
|
4245
|
+
accessToken: body.AccessToken || tokens.accessToken,
|
|
4246
|
+
idToken: body.IdToken || tokens.idToken,
|
|
4247
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
|
|
4248
|
+
};
|
|
4249
|
+
await saveAuthTokens(refreshed);
|
|
4250
|
+
return refreshed;
|
|
4251
|
+
} catch {
|
|
4252
|
+
await clearAuthTokens();
|
|
4253
|
+
return null;
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
async function authenticatedFetch(url, init) {
|
|
4257
|
+
const tokens = await getValidTokens();
|
|
4258
|
+
if (!tokens) {
|
|
4259
|
+
throw new Error("Not logged in. Run: onexthm login");
|
|
4260
|
+
}
|
|
4261
|
+
const headers = new Headers(init?.headers);
|
|
4262
|
+
headers.set("Authorization", `Bearer ${tokens.idToken}`);
|
|
4263
|
+
headers.set("Content-Type", "application/json");
|
|
4264
|
+
return fetch(url, { ...init, headers });
|
|
4265
|
+
}
|
|
4266
|
+
function getMachineKey() {
|
|
4267
|
+
let seed;
|
|
4268
|
+
if (process.platform === "darwin") {
|
|
4269
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4270
|
+
} else if (process.platform === "linux") {
|
|
4271
|
+
try {
|
|
4272
|
+
seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
|
|
4273
|
+
} catch {
|
|
4274
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4275
|
+
}
|
|
4276
|
+
} else {
|
|
4277
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4278
|
+
}
|
|
4279
|
+
return crypto2.createHash("sha256").update(seed).digest();
|
|
4280
|
+
}
|
|
4281
|
+
function encrypt(text, key) {
|
|
4282
|
+
const iv = crypto2.randomBytes(16);
|
|
4283
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
4284
|
+
let encrypted = cipher.update(text, "utf-8", "hex");
|
|
4285
|
+
encrypted += cipher.final("hex");
|
|
4286
|
+
const tag = cipher.getAuthTag();
|
|
4287
|
+
return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
|
|
4288
|
+
}
|
|
4289
|
+
function decrypt(text, key) {
|
|
4290
|
+
const [ivHex, tagHex, encrypted] = text.split(":");
|
|
4291
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
4292
|
+
const tag = Buffer.from(tagHex, "hex");
|
|
4293
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
4294
|
+
decipher.setAuthTag(tag);
|
|
4295
|
+
let decrypted = decipher.update(encrypted, "hex", "utf-8");
|
|
4296
|
+
decrypted += decipher.final("utf-8");
|
|
4297
|
+
return decrypted;
|
|
4298
|
+
}
|
|
4299
|
+
function parseJwtClaims(idToken) {
|
|
4300
|
+
try {
|
|
4301
|
+
const payload = idToken.split(".")[1];
|
|
4302
|
+
const decoded = Buffer.from(payload, "base64url").toString("utf-8");
|
|
4303
|
+
return JSON.parse(decoded);
|
|
4304
|
+
} catch {
|
|
4305
|
+
return {};
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
|
|
4309
|
+
// src/commands/login.ts
|
|
4310
|
+
async function loginCommand() {
|
|
4311
|
+
logger.header("OneX Theme Developer Login");
|
|
4312
|
+
const existing = loadAuthTokens();
|
|
4313
|
+
if (existing) {
|
|
4314
|
+
logger.info(`Already logged in as: ${existing.user.email}`);
|
|
4315
|
+
const { relogin } = await inquirer.prompt([
|
|
4316
|
+
{
|
|
4317
|
+
type: "confirm",
|
|
4318
|
+
name: "relogin",
|
|
4319
|
+
message: "Re-login with different account?",
|
|
4320
|
+
default: false
|
|
4321
|
+
}
|
|
4322
|
+
]);
|
|
4323
|
+
if (!relogin) return;
|
|
4324
|
+
}
|
|
4325
|
+
const { email, password } = await inquirer.prompt([
|
|
4326
|
+
{
|
|
4327
|
+
type: "input",
|
|
4328
|
+
name: "email",
|
|
4329
|
+
message: "Email:",
|
|
4330
|
+
validate: (input) => input.includes("@") ? true : "Enter a valid email"
|
|
4331
|
+
},
|
|
4332
|
+
{
|
|
4333
|
+
type: "password",
|
|
4334
|
+
name: "password",
|
|
4335
|
+
message: "Password:",
|
|
4336
|
+
validate: (input) => input.length >= 6 ? true : "Password too short"
|
|
4337
|
+
}
|
|
4338
|
+
]);
|
|
4339
|
+
logger.startSpinner("Logging in...");
|
|
4340
|
+
try {
|
|
4341
|
+
const apiUrl = getApiUrl();
|
|
4342
|
+
const response = await fetch(`${apiUrl}/auth/login`, {
|
|
4343
|
+
method: "POST",
|
|
4344
|
+
headers: { "Content-Type": "application/json" },
|
|
4345
|
+
body: JSON.stringify({ username: email, password })
|
|
4346
|
+
});
|
|
4347
|
+
const raw = await response.json();
|
|
4348
|
+
const data = raw.statusCode ? raw.body : raw;
|
|
4349
|
+
if (!response.ok || data.error) {
|
|
4350
|
+
logger.stopSpinner(false, "Login failed");
|
|
4351
|
+
logger.error(data.message || data.error || "Invalid credentials");
|
|
4352
|
+
process.exit(1);
|
|
4353
|
+
}
|
|
4354
|
+
const idToken = data.IdToken;
|
|
4355
|
+
const accessToken = data.AccessToken;
|
|
4356
|
+
const refreshToken = data.RefreshToken;
|
|
4357
|
+
const expiresIn = data.ExpiresIn || 3600;
|
|
4358
|
+
if (!idToken) {
|
|
4359
|
+
logger.stopSpinner(false, "Login failed");
|
|
4360
|
+
logger.error("No token received from server");
|
|
4361
|
+
process.exit(1);
|
|
4362
|
+
}
|
|
4363
|
+
const claims = parseJwtClaims(idToken);
|
|
4364
|
+
const tokens = {
|
|
4365
|
+
accessToken,
|
|
4366
|
+
idToken,
|
|
4367
|
+
refreshToken,
|
|
4368
|
+
expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
|
|
4369
|
+
user: {
|
|
4370
|
+
email: claims.email || email,
|
|
4371
|
+
name: claims.name,
|
|
4372
|
+
companyId: claims["custom:company_id"],
|
|
4373
|
+
userId: claims.sub
|
|
4374
|
+
}
|
|
4375
|
+
};
|
|
4376
|
+
await saveAuthTokens(tokens);
|
|
4377
|
+
logger.stopSpinner(true, "Logged in!");
|
|
4378
|
+
logger.newLine();
|
|
4379
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4380
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4381
|
+
if (tokens.user.companyId)
|
|
4382
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4383
|
+
logger.newLine();
|
|
4384
|
+
logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
|
|
4385
|
+
} catch (error) {
|
|
4386
|
+
logger.stopSpinner(false, "Login failed");
|
|
4387
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4388
|
+
process.exit(1);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
// src/commands/logout.ts
|
|
4393
|
+
init_logger();
|
|
4394
|
+
async function logoutCommand() {
|
|
4395
|
+
const tokens = loadAuthTokens();
|
|
4396
|
+
if (!tokens) {
|
|
4397
|
+
logger.info("Not logged in.");
|
|
4398
|
+
return;
|
|
4399
|
+
}
|
|
4400
|
+
await clearAuthTokens();
|
|
4401
|
+
logger.success(`Logged out (was: ${tokens.user.email})`);
|
|
4402
|
+
}
|
|
4403
|
+
|
|
4404
|
+
// src/commands/whoami.ts
|
|
4405
|
+
init_logger();
|
|
4406
|
+
async function whoamiCommand() {
|
|
4407
|
+
const tokens = loadAuthTokens();
|
|
4408
|
+
if (!tokens) {
|
|
4409
|
+
logger.error("Not logged in. Run: onexthm login");
|
|
4410
|
+
process.exit(1);
|
|
4411
|
+
}
|
|
4412
|
+
const expired = isTokenExpired(tokens);
|
|
4413
|
+
logger.header("OneX Theme Developer");
|
|
4414
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4415
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4416
|
+
if (tokens.user.companyId)
|
|
4417
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4418
|
+
logger.info(
|
|
4419
|
+
` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
|
|
4423
|
+
// src/commands/publish.ts
|
|
4424
|
+
init_logger();
|
|
4425
|
+
async function publishCommand(options) {
|
|
4426
|
+
logger.header("OneX Theme Publish");
|
|
4427
|
+
const tokens = await getValidTokens();
|
|
4428
|
+
if (!tokens) {
|
|
4429
|
+
logger.error("Not logged in. Run: onexthm login");
|
|
4430
|
+
process.exit(1);
|
|
4431
|
+
}
|
|
4432
|
+
logger.info(`Logged in as: ${tokens.user.email}`);
|
|
4433
|
+
let themePath;
|
|
4434
|
+
if (options.theme) {
|
|
4435
|
+
themePath = path8.resolve(options.theme);
|
|
4436
|
+
} else {
|
|
4437
|
+
const isThemeDir = [
|
|
4438
|
+
"theme.config.ts",
|
|
4439
|
+
"bundle-entry.ts",
|
|
4440
|
+
"manifest.ts"
|
|
4441
|
+
].some((f) => fs.existsSync(path8.join(process.cwd(), f)));
|
|
4442
|
+
if (isThemeDir) {
|
|
4443
|
+
themePath = process.cwd();
|
|
4444
|
+
} else {
|
|
4445
|
+
logger.error(
|
|
4446
|
+
"Not in a theme directory. Run from theme root or use --theme flag."
|
|
4447
|
+
);
|
|
4448
|
+
process.exit(1);
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
const pkgPath = path8.join(themePath, "package.json");
|
|
4452
|
+
if (!fs.existsSync(pkgPath)) {
|
|
4453
|
+
logger.error("No package.json found in theme directory");
|
|
4454
|
+
process.exit(1);
|
|
4455
|
+
}
|
|
4456
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
4457
|
+
const themeId = pkg.name?.replace("@onex-themes/", "") || path8.basename(themePath);
|
|
4458
|
+
const version2 = pkg.version || "1.0.0";
|
|
4459
|
+
logger.newLine();
|
|
4460
|
+
logger.info(`Theme: ${themeId}`);
|
|
4461
|
+
logger.info(`Version: ${version2}`);
|
|
4462
|
+
logger.newLine();
|
|
4463
|
+
const apiUrl = getApiUrl();
|
|
4464
|
+
logger.startSpinner("Registering theme...");
|
|
4465
|
+
try {
|
|
4466
|
+
const regResponse = await authenticatedFetch(
|
|
4467
|
+
`${apiUrl}/website-api/themes/register`,
|
|
4468
|
+
{
|
|
4469
|
+
method: "POST",
|
|
4470
|
+
body: JSON.stringify({
|
|
4471
|
+
themeId,
|
|
4472
|
+
name: pkg.displayName || themeId,
|
|
4473
|
+
description: pkg.description || "",
|
|
4474
|
+
email: tokens.user.email
|
|
4475
|
+
})
|
|
4476
|
+
}
|
|
4477
|
+
);
|
|
4478
|
+
const regData = await regResponse.json();
|
|
4479
|
+
const regBody = regData.statusCode ? regData.body : regData;
|
|
4480
|
+
if (!regResponse.ok && regBody.error && !regBody.error.includes("already registered")) {
|
|
4481
|
+
logger.stopSpinner(false, "Registration failed");
|
|
4482
|
+
logger.error(regBody.error);
|
|
4483
|
+
process.exit(1);
|
|
4484
|
+
}
|
|
4485
|
+
logger.stopSpinner(true, regBody.message || "Theme registered");
|
|
4486
|
+
} catch (error) {
|
|
4487
|
+
logger.stopSpinner(false, "Registration failed");
|
|
4488
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4489
|
+
process.exit(1);
|
|
4490
|
+
}
|
|
4491
|
+
logger.startSpinner("Building theme...");
|
|
4492
|
+
try {
|
|
4493
|
+
const { execSync: execSync3 } = await import('child_process');
|
|
4494
|
+
execSync3(
|
|
4495
|
+
"npx tsup bundle-entry.ts --format esm --target es2020 --outDir dist",
|
|
4496
|
+
{
|
|
4497
|
+
cwd: themePath,
|
|
4498
|
+
stdio: "ignore"
|
|
4499
|
+
}
|
|
4500
|
+
);
|
|
4501
|
+
logger.stopSpinner(true, "Theme compiled");
|
|
4502
|
+
} catch {
|
|
4503
|
+
logger.stopSpinner(false, "Build failed");
|
|
4504
|
+
logger.error("Run 'onexthm build' to see build errors");
|
|
4505
|
+
process.exit(1);
|
|
4506
|
+
}
|
|
4507
|
+
logger.startSpinner("Getting upload URL...");
|
|
4508
|
+
let bundleUploadUrl;
|
|
4509
|
+
let sourceUploadUrl;
|
|
4510
|
+
try {
|
|
4511
|
+
const pubResponse = await authenticatedFetch(
|
|
4512
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
|
|
4513
|
+
{
|
|
4514
|
+
method: "POST",
|
|
4515
|
+
body: JSON.stringify({ version: version2 })
|
|
4516
|
+
}
|
|
4517
|
+
);
|
|
4518
|
+
const pubData = await pubResponse.json();
|
|
4519
|
+
const pubBody = pubData.statusCode ? pubData.body : pubData;
|
|
4520
|
+
if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
|
|
4521
|
+
logger.stopSpinner(false, "Failed to get upload URL");
|
|
4522
|
+
logger.error(pubBody.error || "Server error");
|
|
4523
|
+
process.exit(1);
|
|
4524
|
+
}
|
|
4525
|
+
bundleUploadUrl = pubBody.bundleUploadUrl;
|
|
4526
|
+
sourceUploadUrl = pubBody.sourceUploadUrl;
|
|
4527
|
+
logger.stopSpinner(true, "Upload URL obtained");
|
|
4528
|
+
} catch (error) {
|
|
4529
|
+
logger.stopSpinner(false, "Failed");
|
|
4530
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4531
|
+
process.exit(1);
|
|
4532
|
+
}
|
|
4533
|
+
logger.startSpinner("Uploading bundle...");
|
|
4534
|
+
try {
|
|
4535
|
+
const archiver3 = await import('archiver');
|
|
4536
|
+
const { createWriteStream } = await import('fs');
|
|
4537
|
+
const distDir = path8.join(themePath, "dist");
|
|
4538
|
+
if (!fs.existsSync(distDir)) {
|
|
4539
|
+
logger.stopSpinner(false, "No dist/ directory");
|
|
4540
|
+
logger.error("Build the theme first: onexthm build");
|
|
4541
|
+
process.exit(1);
|
|
4542
|
+
}
|
|
4543
|
+
const bundleZipPath = path8.join(themePath, "dist", "bundle.zip");
|
|
4544
|
+
await createZip(distDir, bundleZipPath, ["bundle.zip"]);
|
|
4545
|
+
const bundleBuffer = fs.readFileSync(bundleZipPath);
|
|
4546
|
+
const bundleRes = await fetch(bundleUploadUrl, {
|
|
4547
|
+
method: "PUT",
|
|
4548
|
+
headers: { "Content-Type": "application/zip" },
|
|
4549
|
+
body: bundleBuffer
|
|
4550
|
+
});
|
|
4551
|
+
if (!bundleRes.ok) {
|
|
4552
|
+
throw new Error(`Upload failed: ${bundleRes.status}`);
|
|
4553
|
+
}
|
|
4554
|
+
const sizeMB = (bundleBuffer.length / 1024 / 1024).toFixed(2);
|
|
4555
|
+
logger.stopSpinner(true, `Bundle uploaded (${sizeMB} MB)`);
|
|
4556
|
+
} catch (error) {
|
|
4557
|
+
logger.stopSpinner(false, "Upload failed");
|
|
4558
|
+
logger.error(error instanceof Error ? error.message : "Upload error");
|
|
4559
|
+
process.exit(1);
|
|
4560
|
+
}
|
|
4561
|
+
logger.startSpinner("Uploading source...");
|
|
4562
|
+
try {
|
|
4563
|
+
const sourceZipPath = path8.join(themePath, "dist", "source.zip");
|
|
4564
|
+
await createZip(themePath, sourceZipPath, [
|
|
4565
|
+
"node_modules",
|
|
4566
|
+
"dist",
|
|
4567
|
+
".git",
|
|
4568
|
+
".env",
|
|
4569
|
+
".env.local"
|
|
4570
|
+
]);
|
|
4571
|
+
const sourceBuffer = fs.readFileSync(sourceZipPath);
|
|
4572
|
+
const sourceRes = await fetch(sourceUploadUrl, {
|
|
4573
|
+
method: "PUT",
|
|
4574
|
+
headers: { "Content-Type": "application/zip" },
|
|
4575
|
+
body: sourceBuffer
|
|
4576
|
+
});
|
|
4577
|
+
if (!sourceRes.ok) {
|
|
4578
|
+
throw new Error(`Source upload failed: ${sourceRes.status}`);
|
|
4579
|
+
}
|
|
4580
|
+
const sizeMB = (sourceBuffer.length / 1024 / 1024).toFixed(2);
|
|
4581
|
+
logger.stopSpinner(true, `Source uploaded (${sizeMB} MB)`);
|
|
4582
|
+
} catch (error) {
|
|
4583
|
+
logger.stopSpinner(false, "Source upload failed");
|
|
4584
|
+
logger.info("Source upload skipped (bundle was uploaded successfully)");
|
|
4585
|
+
}
|
|
4586
|
+
logger.startSpinner("Scanning and publishing...");
|
|
4587
|
+
try {
|
|
4588
|
+
const confirmResponse = await authenticatedFetch(
|
|
4589
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
|
|
4590
|
+
{ method: "POST" }
|
|
4591
|
+
);
|
|
4592
|
+
const confirmData = await confirmResponse.json();
|
|
4593
|
+
const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
|
|
4594
|
+
if (!confirmResponse.ok || !confirmBody.success) {
|
|
4595
|
+
logger.stopSpinner(false, "Publishing failed");
|
|
4596
|
+
if (confirmBody.violations) {
|
|
4597
|
+
logger.error("Theme rejected \u2014 security violations found:");
|
|
4598
|
+
for (const v of confirmBody.violations) {
|
|
4599
|
+
logger.log(` \u274C ${v.file}: ${v.violation}`);
|
|
4600
|
+
}
|
|
4601
|
+
} else {
|
|
4602
|
+
logger.error(confirmBody.error || "Unknown error");
|
|
4603
|
+
}
|
|
4604
|
+
if (confirmBody.warnings?.length) {
|
|
4605
|
+
logger.newLine();
|
|
4606
|
+
logger.info("Warnings:");
|
|
4607
|
+
for (const w of confirmBody.warnings) {
|
|
4608
|
+
logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
process.exit(1);
|
|
4612
|
+
}
|
|
4613
|
+
logger.stopSpinner(true, confirmBody.message || "Published!");
|
|
4614
|
+
if (confirmBody.warnings?.length) {
|
|
4615
|
+
logger.newLine();
|
|
4616
|
+
logger.info("Warnings (non-blocking):");
|
|
4617
|
+
for (const w of confirmBody.warnings) {
|
|
4618
|
+
logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
} catch (error) {
|
|
4622
|
+
logger.stopSpinner(false, "Publishing failed");
|
|
4623
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4624
|
+
process.exit(1);
|
|
4625
|
+
}
|
|
4626
|
+
logger.newLine();
|
|
4627
|
+
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
4628
|
+
}
|
|
4629
|
+
async function createZip(sourceDir, outputPath, exclude) {
|
|
4630
|
+
const archiver3 = (await import('archiver')).default;
|
|
4631
|
+
const { createWriteStream } = await import('fs');
|
|
4632
|
+
return new Promise((resolve, reject) => {
|
|
4633
|
+
const output = createWriteStream(outputPath);
|
|
4634
|
+
const archive = archiver3("zip", { zlib: { level: 9 } });
|
|
4635
|
+
output.on("close", resolve);
|
|
4636
|
+
archive.on("error", reject);
|
|
4637
|
+
archive.pipe(output);
|
|
4638
|
+
archive.glob("**/*", {
|
|
4639
|
+
cwd: sourceDir,
|
|
4640
|
+
ignore: exclude.map((e) => `${e}/**`),
|
|
4641
|
+
dot: false
|
|
4642
|
+
});
|
|
4643
|
+
archive.finalize();
|
|
4644
|
+
});
|
|
4645
|
+
}
|
|
4646
|
+
|
|
4229
4647
|
// src/cli.ts
|
|
4230
4648
|
try {
|
|
4231
4649
|
const projectRoot = getProjectRoot();
|
|
@@ -4237,13 +4655,13 @@ try {
|
|
|
4237
4655
|
} catch {
|
|
4238
4656
|
}
|
|
4239
4657
|
dotenv.config({
|
|
4240
|
-
path: path8.join(
|
|
4658
|
+
path: path8.join(os3.homedir(), ".onexthm", ".env"),
|
|
4241
4659
|
quiet: true
|
|
4242
4660
|
});
|
|
4243
4661
|
var require2 = createRequire(import.meta.url);
|
|
4244
4662
|
var { version } = require2("../package.json");
|
|
4245
4663
|
var program = new Command();
|
|
4246
|
-
program.name("
|
|
4664
|
+
program.name("onexthm").description("CLI tool for OneX theme development").version(version);
|
|
4247
4665
|
program.command("init").description("Create a new OneX theme project").argument("[project-name]", "Name of the project").option(
|
|
4248
4666
|
"-t, --template <template>",
|
|
4249
4667
|
"Template to use (default, minimal)",
|
|
@@ -4294,6 +4712,10 @@ program.command("clone").description("Clone theme source code from S3").argument
|
|
|
4294
4712
|
"staging"
|
|
4295
4713
|
).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
4296
4714
|
program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
|
|
4715
|
+
program.command("login").description("Login to OneX platform").action(loginCommand);
|
|
4716
|
+
program.command("logout").description("Logout from OneX platform").action(logoutCommand);
|
|
4717
|
+
program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
|
|
4718
|
+
program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").action(publishCommand);
|
|
4297
4719
|
program.configureOutput({
|
|
4298
4720
|
writeErr: (str) => process.stderr.write(chalk4.red(str))
|
|
4299
4721
|
});
|