@onexapis/cli 1.1.17 → 1.1.19
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 +82 -16
- package/dist/cli.js +621 -294
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +618 -291
- package/dist/cli.mjs.map +1 -1
- package/dist/index.js +73 -278
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -278
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- 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 +34 -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,10 +4,10 @@ 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
9
|
import { createRequire } from 'module';
|
|
10
|
-
import
|
|
10
|
+
import os3 from 'os';
|
|
11
11
|
import dotenv from 'dotenv';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import ejs from 'ejs';
|
|
@@ -17,7 +17,7 @@ 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;
|
|
@@ -484,7 +484,7 @@ async function contentHashEntry(outputDir) {
|
|
|
484
484
|
logger.warning("No entry file found in output, skipping content hash");
|
|
485
485
|
return;
|
|
486
486
|
}
|
|
487
|
-
const hash2 =
|
|
487
|
+
const hash2 = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
488
488
|
const hashedName2 = `bundle-entry-${hash2}.js`;
|
|
489
489
|
const indexMapPath = path8.join(outputDir, "index.js.map");
|
|
490
490
|
const hashedMapName2 = `bundle-entry-${hash2}.js.map`;
|
|
@@ -502,7 +502,7 @@ async function contentHashEntry(outputDir) {
|
|
|
502
502
|
logger.info(`Entry hashed: ${hashedName2}`);
|
|
503
503
|
return;
|
|
504
504
|
}
|
|
505
|
-
const hash =
|
|
505
|
+
const hash = crypto2.createHash("sha256").update(entryContent).digest("hex").slice(0, 8);
|
|
506
506
|
const hashedName = `bundle-entry-${hash}.js`;
|
|
507
507
|
const hashedMapName = `bundle-entry-${hash}.js.map`;
|
|
508
508
|
entryContent = entryContent.replace(
|
|
@@ -540,7 +540,7 @@ async function extractDataRequirements(themePath) {
|
|
|
540
540
|
}
|
|
541
541
|
return requirements;
|
|
542
542
|
}
|
|
543
|
-
async function
|
|
543
|
+
async function generateManifest(themeName, themePath, outputDir) {
|
|
544
544
|
let version2 = "1.0.0";
|
|
545
545
|
let themeId = themeName;
|
|
546
546
|
try {
|
|
@@ -675,7 +675,7 @@ async function compileStandaloneTheme(themePath, themeName) {
|
|
|
675
675
|
} catch {
|
|
676
676
|
}
|
|
677
677
|
await contentHashEntry(outputDir);
|
|
678
|
-
await
|
|
678
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
679
679
|
await generateThemeData(themePath, outputDir, themeName);
|
|
680
680
|
if (result.metafile) {
|
|
681
681
|
const outputs = result.metafile.outputs;
|
|
@@ -756,7 +756,7 @@ async function compileStandaloneThemeDev(themePath, themeName) {
|
|
|
756
756
|
};
|
|
757
757
|
const context2 = await esbuild.context(buildOptions);
|
|
758
758
|
await context2.rebuild();
|
|
759
|
-
await
|
|
759
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
760
760
|
await generateThemeData(themePath, outputDir, themeName);
|
|
761
761
|
return { context: context2, outputDir };
|
|
762
762
|
}
|
|
@@ -1492,38 +1492,13 @@ async function initCommand(projectName, options = {}) {
|
|
|
1492
1492
|
try {
|
|
1493
1493
|
fs2.mkdirSync(projectPath, { recursive: true });
|
|
1494
1494
|
await copyTemplate(template, projectPath, data);
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
const layoutContent = generateThemeLayout(data);
|
|
1502
|
-
await writeFile(path8.join(srcPath, "layout.ts"), layoutContent);
|
|
1503
|
-
const indexContent = generateThemeIndex(data);
|
|
1504
|
-
await writeFile(path8.join(srcPath, "index.ts"), indexContent);
|
|
1505
|
-
const sectionsPath = path8.join(srcPath, "sections");
|
|
1506
|
-
fs2.mkdirSync(sectionsPath, { recursive: true });
|
|
1507
|
-
await writeFile(
|
|
1508
|
-
path8.join(sectionsPath, "README.md"),
|
|
1509
|
-
`# ${displayName} Sections
|
|
1510
|
-
|
|
1511
|
-
Add your theme-specific sections here.
|
|
1512
|
-
`
|
|
1513
|
-
);
|
|
1514
|
-
const blocksPath = path8.join(srcPath, "blocks");
|
|
1515
|
-
fs2.mkdirSync(blocksPath, { recursive: true });
|
|
1516
|
-
await writeFile(
|
|
1517
|
-
path8.join(blocksPath, "README.md"),
|
|
1518
|
-
`# ${displayName} Blocks
|
|
1519
|
-
|
|
1520
|
-
Add your theme-specific blocks here.
|
|
1521
|
-
`
|
|
1495
|
+
await renameThemeInFiles(
|
|
1496
|
+
projectPath,
|
|
1497
|
+
name,
|
|
1498
|
+
displayName,
|
|
1499
|
+
description,
|
|
1500
|
+
author
|
|
1522
1501
|
);
|
|
1523
|
-
const pagesPath = path8.join(srcPath, "pages");
|
|
1524
|
-
fs2.mkdirSync(pagesPath, { recursive: true });
|
|
1525
|
-
const homePageContent = generateHomePage(data);
|
|
1526
|
-
await writeFile(path8.join(pagesPath, "home.ts"), homePageContent);
|
|
1527
1502
|
logger.stopSpinner(true, "Project structure created!");
|
|
1528
1503
|
if (options.git) {
|
|
1529
1504
|
logger.startSpinner("Initializing git repository...");
|
|
@@ -1567,14 +1542,14 @@ Add your theme-specific blocks here.
|
|
|
1567
1542
|
logger.log(` npm run dev # Start development mode`);
|
|
1568
1543
|
logger.newLine();
|
|
1569
1544
|
logger.section("Theme structure:");
|
|
1570
|
-
logger.log("
|
|
1545
|
+
logger.log(" bundle-entry.ts - Theme manifest and exports");
|
|
1571
1546
|
logger.log(
|
|
1572
|
-
"
|
|
1547
|
+
" theme.config.ts - Design tokens (colors, typography, etc.)"
|
|
1573
1548
|
);
|
|
1574
|
-
logger.log("
|
|
1575
|
-
logger.log("
|
|
1576
|
-
logger.log("
|
|
1577
|
-
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");
|
|
1578
1553
|
logger.newLine();
|
|
1579
1554
|
logger.success(`Happy theming! \u{1F3A8}`);
|
|
1580
1555
|
} catch (error) {
|
|
@@ -1588,231 +1563,33 @@ Add your theme-specific blocks here.
|
|
|
1588
1563
|
process.exit(1);
|
|
1589
1564
|
}
|
|
1590
1565
|
}
|
|
1591
|
-
function
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
// Example: productCard: () => import("./blocks/product-card").then((m) => m.productCardDefinition),
|
|
1619
|
-
},
|
|
1620
|
-
|
|
1621
|
-
// Default pages
|
|
1622
|
-
pages: {
|
|
1623
|
-
home: () => import("./pages/home").then((m) => m.homePageConfig),
|
|
1624
|
-
},
|
|
1625
|
-
|
|
1626
|
-
// Supported page types
|
|
1627
|
-
supportedPageTypes: ["home", "about", "contact", "custom"],
|
|
1628
|
-
|
|
1629
|
-
// Preview image (optional)
|
|
1630
|
-
preview: undefined,
|
|
1631
|
-
|
|
1632
|
-
// Tags for categorization (optional)
|
|
1633
|
-
tags: ["custom"],
|
|
1634
|
-
};
|
|
1635
|
-
|
|
1636
|
-
export default manifest;
|
|
1637
|
-
`;
|
|
1638
|
-
}
|
|
1639
|
-
function generateThemeConfig(data) {
|
|
1640
|
-
return `import type { ThemeConfig } from "@onexapis/core";
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* ${data.displayName} Theme Configuration
|
|
1644
|
-
* Design tokens: colors, typography, spacing, etc.
|
|
1645
|
-
*/
|
|
1646
|
-
export const themeConfig: ThemeConfig = {
|
|
1647
|
-
// Color palette
|
|
1648
|
-
colors: {
|
|
1649
|
-
primary: {
|
|
1650
|
-
50: "#eff6ff",
|
|
1651
|
-
100: "#dbeafe",
|
|
1652
|
-
200: "#bfdbfe",
|
|
1653
|
-
300: "#93c5fd",
|
|
1654
|
-
400: "#60a5fa",
|
|
1655
|
-
500: "#3b82f6",
|
|
1656
|
-
600: "#2563eb",
|
|
1657
|
-
700: "#1d4ed8",
|
|
1658
|
-
800: "#1e40af",
|
|
1659
|
-
900: "#1e3a8a",
|
|
1660
|
-
},
|
|
1661
|
-
secondary: {
|
|
1662
|
-
50: "#f8fafc",
|
|
1663
|
-
100: "#f1f5f9",
|
|
1664
|
-
200: "#e2e8f0",
|
|
1665
|
-
300: "#cbd5e1",
|
|
1666
|
-
400: "#94a3b8",
|
|
1667
|
-
500: "#64748b",
|
|
1668
|
-
600: "#475569",
|
|
1669
|
-
700: "#334155",
|
|
1670
|
-
800: "#1e293b",
|
|
1671
|
-
900: "#0f172a",
|
|
1672
|
-
},
|
|
1673
|
-
accent: {
|
|
1674
|
-
50: "#fdf4ff",
|
|
1675
|
-
100: "#fae8ff",
|
|
1676
|
-
200: "#f5d0fe",
|
|
1677
|
-
300: "#f0abfc",
|
|
1678
|
-
400: "#e879f9",
|
|
1679
|
-
500: "#d946ef",
|
|
1680
|
-
600: "#c026d3",
|
|
1681
|
-
700: "#a21caf",
|
|
1682
|
-
800: "#86198f",
|
|
1683
|
-
900: "#701a75",
|
|
1684
|
-
},
|
|
1685
|
-
},
|
|
1686
|
-
|
|
1687
|
-
// Typography
|
|
1688
|
-
typography: {
|
|
1689
|
-
fontFamily: {
|
|
1690
|
-
sans: ["Inter", "system-ui", "sans-serif"],
|
|
1691
|
-
serif: ["Georgia", "serif"],
|
|
1692
|
-
mono: ["Monaco", "monospace"],
|
|
1693
|
-
},
|
|
1694
|
-
fontSize: {
|
|
1695
|
-
xs: "0.75rem",
|
|
1696
|
-
sm: "0.875rem",
|
|
1697
|
-
base: "1rem",
|
|
1698
|
-
lg: "1.125rem",
|
|
1699
|
-
xl: "1.25rem",
|
|
1700
|
-
"2xl": "1.5rem",
|
|
1701
|
-
"3xl": "1.875rem",
|
|
1702
|
-
"4xl": "2.25rem",
|
|
1703
|
-
"5xl": "3rem",
|
|
1704
|
-
},
|
|
1705
|
-
},
|
|
1706
|
-
|
|
1707
|
-
// Spacing
|
|
1708
|
-
spacing: {
|
|
1709
|
-
xs: "0.5rem",
|
|
1710
|
-
sm: "1rem",
|
|
1711
|
-
md: "1.5rem",
|
|
1712
|
-
lg: "2rem",
|
|
1713
|
-
xl: "3rem",
|
|
1714
|
-
"2xl": "4rem",
|
|
1715
|
-
"3xl": "6rem",
|
|
1716
|
-
"4xl": "8rem",
|
|
1717
|
-
},
|
|
1718
|
-
|
|
1719
|
-
// Border radius
|
|
1720
|
-
borderRadius: {
|
|
1721
|
-
none: "0",
|
|
1722
|
-
sm: "0.125rem",
|
|
1723
|
-
md: "0.375rem",
|
|
1724
|
-
lg: "0.5rem",
|
|
1725
|
-
xl: "0.75rem",
|
|
1726
|
-
full: "9999px",
|
|
1727
|
-
},
|
|
1728
|
-
|
|
1729
|
-
// Breakpoints
|
|
1730
|
-
breakpoints: {
|
|
1731
|
-
sm: "640px",
|
|
1732
|
-
md: "768px",
|
|
1733
|
-
lg: "1024px",
|
|
1734
|
-
xl: "1280px",
|
|
1735
|
-
"2xl": "1536px",
|
|
1736
|
-
},
|
|
1737
|
-
};
|
|
1738
|
-
`;
|
|
1739
|
-
}
|
|
1740
|
-
function generateThemeLayout(data) {
|
|
1741
|
-
return `import type { ThemeLayoutConfig } from "@onexapis/core";
|
|
1742
|
-
|
|
1743
|
-
/**
|
|
1744
|
-
* ${data.themeName} Theme Layout
|
|
1745
|
-
* Define header and footer sections
|
|
1746
|
-
*/
|
|
1747
|
-
export const themeLayout: ThemeLayoutConfig = {
|
|
1748
|
-
// Header section configuration
|
|
1749
|
-
header: undefined,
|
|
1750
|
-
// Example:
|
|
1751
|
-
// header: {
|
|
1752
|
-
// type: "header",
|
|
1753
|
-
// template: "default",
|
|
1754
|
-
// enabled: true,
|
|
1755
|
-
// settings: {},
|
|
1756
|
-
// },
|
|
1757
|
-
|
|
1758
|
-
// Footer section configuration
|
|
1759
|
-
footer: undefined,
|
|
1760
|
-
// Example:
|
|
1761
|
-
// footer: {
|
|
1762
|
-
// type: "footer",
|
|
1763
|
-
// template: "default",
|
|
1764
|
-
// enabled: true,
|
|
1765
|
-
// settings: {},
|
|
1766
|
-
// },
|
|
1767
|
-
};
|
|
1768
|
-
`;
|
|
1769
|
-
}
|
|
1770
|
-
function generateThemeIndex(data) {
|
|
1771
|
-
return `/**
|
|
1772
|
-
* ${data.themeNamePascal} Theme
|
|
1773
|
-
*/
|
|
1774
|
-
|
|
1775
|
-
export { manifest as ${data.themeNamePascal}Manifest } from "./manifest";
|
|
1776
|
-
export { themeConfig as ${data.themeNamePascal}Config } from "./config";
|
|
1777
|
-
export { themeLayout as ${data.themeNamePascal}Layout } from "./layout";
|
|
1778
|
-
`;
|
|
1779
|
-
}
|
|
1780
|
-
function generateHomePage(data) {
|
|
1781
|
-
return `import type { PageConfig } from "@onexapis/core";
|
|
1782
|
-
|
|
1783
|
-
/**
|
|
1784
|
-
* Home Page Configuration
|
|
1785
|
-
*/
|
|
1786
|
-
export const homePageConfig: PageConfig = {
|
|
1787
|
-
type: "home",
|
|
1788
|
-
title: "${data.displayName}",
|
|
1789
|
-
description: "Welcome to ${data.displayName}",
|
|
1790
|
-
|
|
1791
|
-
// SEO metadata
|
|
1792
|
-
seo: {
|
|
1793
|
-
title: "${data.displayName} - Home",
|
|
1794
|
-
description: "Welcome to ${data.displayName}",
|
|
1795
|
-
keywords: [],
|
|
1796
|
-
ogImage: undefined,
|
|
1797
|
-
},
|
|
1798
|
-
|
|
1799
|
-
// Page sections
|
|
1800
|
-
sections: [
|
|
1801
|
-
// Add your sections here
|
|
1802
|
-
// Example:
|
|
1803
|
-
// {
|
|
1804
|
-
// id: "hero-1",
|
|
1805
|
-
// type: "hero",
|
|
1806
|
-
// template: "default",
|
|
1807
|
-
// order: 0,
|
|
1808
|
-
// enabled: true,
|
|
1809
|
-
// settings: {},
|
|
1810
|
-
// components: [],
|
|
1811
|
-
// blocks: [],
|
|
1812
|
-
// },
|
|
1813
|
-
],
|
|
1814
|
-
};
|
|
1815
|
-
`;
|
|
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
|
+
}
|
|
1816
1593
|
}
|
|
1817
1594
|
|
|
1818
1595
|
// src/commands/create-section.ts
|
|
@@ -2768,6 +2545,79 @@ async function validateCommand(options) {
|
|
|
2768
2545
|
}
|
|
2769
2546
|
}
|
|
2770
2547
|
}
|
|
2548
|
+
if (fs.existsSync(sectionsDir)) {
|
|
2549
|
+
const sections = fs.readdirSync(sectionsDir).filter(
|
|
2550
|
+
(name) => fs.statSync(path8.join(sectionsDir, name)).isDirectory()
|
|
2551
|
+
);
|
|
2552
|
+
for (const sectionName of sections) {
|
|
2553
|
+
const sectionPath = path8.join(sectionsDir, sectionName);
|
|
2554
|
+
const tsxFiles = fs.readdirSync(sectionPath).filter((f) => f.endsWith(".tsx") && !f.endsWith(".schema.ts"));
|
|
2555
|
+
for (const tsxFile of tsxFiles) {
|
|
2556
|
+
const filePath = path8.join(sectionPath, tsxFile);
|
|
2557
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
2558
|
+
const relPath = `sections/${sectionName}/${tsxFile}`;
|
|
2559
|
+
if (!content.includes('"use client"') && !content.includes("'use client'")) {
|
|
2560
|
+
issues.push({
|
|
2561
|
+
type: "error",
|
|
2562
|
+
file: relPath,
|
|
2563
|
+
message: 'Missing "use client" directive at top of file'
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
if (!content.includes("ComponentRenderer") && !content.includes("BlockRenderer")) {
|
|
2567
|
+
issues.push({
|
|
2568
|
+
type: "warning",
|
|
2569
|
+
file: relPath,
|
|
2570
|
+
message: "No ComponentRenderer or BlockRenderer found \u2014 sections should use core renderers for content"
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
if (!content.includes("data-section-id")) {
|
|
2574
|
+
issues.push({
|
|
2575
|
+
type: "error",
|
|
2576
|
+
file: relPath,
|
|
2577
|
+
message: "Missing data-section-id attribute \u2014 editor cannot select this section"
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
if (/\beval\s*\(/.test(content)) {
|
|
2581
|
+
issues.push({
|
|
2582
|
+
type: "error",
|
|
2583
|
+
file: relPath,
|
|
2584
|
+
message: "eval() detected \u2014 arbitrary code execution risk"
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
if (content.includes("document.cookie")) {
|
|
2588
|
+
issues.push({
|
|
2589
|
+
type: "error",
|
|
2590
|
+
file: relPath,
|
|
2591
|
+
message: "document.cookie access \u2014 session hijacking risk"
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
if (/\brequire\s*\(/.test(content)) {
|
|
2595
|
+
issues.push({
|
|
2596
|
+
type: "warning",
|
|
2597
|
+
file: relPath,
|
|
2598
|
+
message: "require() detected \u2014 themes should use ES module imports"
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
const registryPath = path8.join(themePath, "sections-registry.ts");
|
|
2605
|
+
const bundleEntryPath = path8.join(themePath, "bundle-entry.ts");
|
|
2606
|
+
const registryContent = fs.existsSync(registryPath) ? fs.readFileSync(registryPath, "utf-8") : fs.existsSync(bundleEntryPath) ? fs.readFileSync(bundleEntryPath, "utf-8") : "";
|
|
2607
|
+
if (fs.existsSync(sectionsDir) && registryContent) {
|
|
2608
|
+
const sections = fs.readdirSync(sectionsDir).filter(
|
|
2609
|
+
(name) => fs.statSync(path8.join(sectionsDir, name)).isDirectory()
|
|
2610
|
+
);
|
|
2611
|
+
for (const sectionName of sections) {
|
|
2612
|
+
if (!registryContent.includes(`sections/${sectionName}`) && !registryContent.includes(`"${sectionName}"`)) {
|
|
2613
|
+
issues.push({
|
|
2614
|
+
type: "warning",
|
|
2615
|
+
file: `sections/${sectionName}/`,
|
|
2616
|
+
message: "Section not found in sections-registry.ts or bundle-entry.ts \u2014 may not be included in build"
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2771
2621
|
logger.stopSpinner(true, "Validation complete");
|
|
2772
2622
|
const errors = issues.filter((i) => i.type === "error");
|
|
2773
2623
|
const warnings = issues.filter((i) => i.type === "warning");
|
|
@@ -2880,7 +2730,7 @@ async function buildCommand(options) {
|
|
|
2880
2730
|
logger.stopSpinner(true, "Lint passed");
|
|
2881
2731
|
const pkgJson = fs.readJsonSync(packageJsonPath);
|
|
2882
2732
|
const buildScript = pkgJson.scripts?.build || "";
|
|
2883
|
-
const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex-cli build");
|
|
2733
|
+
const isRecursive = buildScript.includes("onexthm build") || buildScript.includes("onex build") || buildScript.includes("onex-cli build");
|
|
2884
2734
|
logger.startSpinner(
|
|
2885
2735
|
options.watch ? "Building (watch mode)..." : "Building..."
|
|
2886
2736
|
);
|
|
@@ -2914,18 +2764,36 @@ function runCommand(command, args, cwd) {
|
|
|
2914
2764
|
return new Promise((resolve) => {
|
|
2915
2765
|
const proc = spawn(command, args, {
|
|
2916
2766
|
cwd,
|
|
2917
|
-
stdio: "pipe",
|
|
2767
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2918
2768
|
shell: true
|
|
2919
2769
|
});
|
|
2920
|
-
let
|
|
2770
|
+
let stdout = "";
|
|
2771
|
+
let stderr = "";
|
|
2772
|
+
proc.stdout.on("data", (data) => {
|
|
2773
|
+
stdout += data.toString();
|
|
2774
|
+
});
|
|
2921
2775
|
proc.stderr.on("data", (data) => {
|
|
2922
|
-
|
|
2923
|
-
if (message.includes("error") || message.includes("Error") || message.includes("ERROR")) {
|
|
2924
|
-
hasError = true;
|
|
2925
|
-
}
|
|
2776
|
+
stderr += data.toString();
|
|
2926
2777
|
});
|
|
2927
2778
|
proc.on("close", (code) => {
|
|
2928
|
-
|
|
2779
|
+
if (code !== 0) {
|
|
2780
|
+
const output = (stderr + stdout).trim();
|
|
2781
|
+
if (output) {
|
|
2782
|
+
logger.newLine();
|
|
2783
|
+
for (const line of output.split("\n")) {
|
|
2784
|
+
const t = line.trim();
|
|
2785
|
+
if (!t) continue;
|
|
2786
|
+
if (t.includes("error") || t.includes("Error") || t.includes("TS")) {
|
|
2787
|
+
logger.log(` \u274C ${t}`);
|
|
2788
|
+
} else if (t.includes("warning") || t.includes("Warning")) {
|
|
2789
|
+
logger.log(` \u26A0\uFE0F ${t}`);
|
|
2790
|
+
} else {
|
|
2791
|
+
logger.log(` ${t}`);
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
resolve(code === 0);
|
|
2929
2797
|
});
|
|
2930
2798
|
proc.on("error", () => {
|
|
2931
2799
|
resolve(false);
|
|
@@ -3139,7 +3007,7 @@ async function deployCommand(options) {
|
|
|
3139
3007
|
if (options.environment) {
|
|
3140
3008
|
formData.append("environment", options.environment);
|
|
3141
3009
|
}
|
|
3142
|
-
const response = await
|
|
3010
|
+
const response = await fetch2(uploadEndpoint, {
|
|
3143
3011
|
method: "POST",
|
|
3144
3012
|
body: formData,
|
|
3145
3013
|
headers: formData.getHeaders()
|
|
@@ -3220,7 +3088,7 @@ function getBucketName(env) {
|
|
|
3220
3088
|
return process.env.BUCKET_NAME;
|
|
3221
3089
|
}
|
|
3222
3090
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3223
|
-
return environment === "production" ? "
|
|
3091
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3224
3092
|
}
|
|
3225
3093
|
async function findCompiledThemeDir(themeId, version2) {
|
|
3226
3094
|
const searchPaths = [path8.resolve(process.cwd(), "dist")];
|
|
@@ -3336,7 +3204,7 @@ async function uploadCommand(options) {
|
|
|
3336
3204
|
}
|
|
3337
3205
|
spinner.succeed(`Found compiled theme at: ${compiledDir}`);
|
|
3338
3206
|
spinner.start("Creating bundle.zip...");
|
|
3339
|
-
const tmpDir =
|
|
3207
|
+
const tmpDir = os3.tmpdir();
|
|
3340
3208
|
const bundleZipPath = path8.join(tmpDir, `${themeId}-${version2}-bundle.zip`);
|
|
3341
3209
|
await createZipFromDir(compiledDir, bundleZipPath);
|
|
3342
3210
|
const bundleZipBuffer = await fs.readFile(bundleZipPath);
|
|
@@ -3489,7 +3357,7 @@ function getBucketName2(env) {
|
|
|
3489
3357
|
return process.env.BUCKET_NAME;
|
|
3490
3358
|
}
|
|
3491
3359
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3492
|
-
return environment === "production" ? "
|
|
3360
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3493
3361
|
}
|
|
3494
3362
|
async function streamToString(stream) {
|
|
3495
3363
|
const chunks = [];
|
|
@@ -3690,7 +3558,7 @@ function getBucketName3(env) {
|
|
|
3690
3558
|
return process.env.BUCKET_NAME;
|
|
3691
3559
|
}
|
|
3692
3560
|
const environment = env || process.env.ENVIRONMENT || "staging";
|
|
3693
|
-
return environment === "production" ? "
|
|
3561
|
+
return environment === "production" ? "theme-s3-bucket" : "theme-s3-bucket";
|
|
3694
3562
|
}
|
|
3695
3563
|
async function streamToString2(stream) {
|
|
3696
3564
|
const chunks = [];
|
|
@@ -3734,8 +3602,8 @@ function runInstall(cwd) {
|
|
|
3734
3602
|
});
|
|
3735
3603
|
}
|
|
3736
3604
|
async function promptThemeName(originalName) {
|
|
3737
|
-
const { default:
|
|
3738
|
-
const { themeName } = await
|
|
3605
|
+
const { default: inquirer7 } = await import('inquirer');
|
|
3606
|
+
const { themeName } = await inquirer7.prompt([
|
|
3739
3607
|
{
|
|
3740
3608
|
type: "input",
|
|
3741
3609
|
name: "themeName",
|
|
@@ -3888,7 +3756,7 @@ async function cloneCommand(themeName, options) {
|
|
|
3888
3756
|
[
|
|
3889
3757
|
"# API Configuration (enables real data in preview)",
|
|
3890
3758
|
"# Get your Company ID from the OneX dashboard",
|
|
3891
|
-
"NEXT_PUBLIC_API_URL=https://
|
|
3759
|
+
"NEXT_PUBLIC_API_URL=https://platform-dev.onexeos.com",
|
|
3892
3760
|
"NEXT_PUBLIC_COMPANY_ID=",
|
|
3893
3761
|
""
|
|
3894
3762
|
].join("\n")
|
|
@@ -4236,7 +4104,7 @@ async function devCommand(options) {
|
|
|
4236
4104
|
logger.info(`File changed: ${filePath}`);
|
|
4237
4105
|
try {
|
|
4238
4106
|
await context2.rebuild();
|
|
4239
|
-
await
|
|
4107
|
+
await generateManifest(themeName, themePath, outputDir);
|
|
4240
4108
|
server.broadcast({ type: "reload", timestamp: Date.now() });
|
|
4241
4109
|
logger.success("Rebuilt successfully");
|
|
4242
4110
|
} catch (error) {
|
|
@@ -4271,7 +4139,7 @@ async function devCommand(options) {
|
|
|
4271
4139
|
|
|
4272
4140
|
// src/commands/config.ts
|
|
4273
4141
|
init_logger();
|
|
4274
|
-
var CONFIG_DIR = path8.join(
|
|
4142
|
+
var CONFIG_DIR = path8.join(os3.homedir(), ".onexthm");
|
|
4275
4143
|
var CONFIG_FILE = path8.join(CONFIG_DIR, ".env");
|
|
4276
4144
|
var CONFIG_ENTRIES = [
|
|
4277
4145
|
{
|
|
@@ -4301,7 +4169,7 @@ var CONFIG_ENTRIES = [
|
|
|
4301
4169
|
key: "NEXT_PUBLIC_API_URL",
|
|
4302
4170
|
label: "API URL",
|
|
4303
4171
|
required: false,
|
|
4304
|
-
defaultValue: "https://
|
|
4172
|
+
defaultValue: "https://platform-dev.onexeos.com"
|
|
4305
4173
|
},
|
|
4306
4174
|
{
|
|
4307
4175
|
key: "NEXT_PUBLIC_COMPANY_ID",
|
|
@@ -4412,6 +4280,461 @@ async function configCommand() {
|
|
|
4412
4280
|
);
|
|
4413
4281
|
}
|
|
4414
4282
|
|
|
4283
|
+
// src/commands/login.ts
|
|
4284
|
+
init_logger();
|
|
4285
|
+
var AUTH_DIR = path8.join(os3.homedir(), ".onexthm");
|
|
4286
|
+
var AUTH_FILE = path8.join(AUTH_DIR, "auth.json");
|
|
4287
|
+
function getApiUrl() {
|
|
4288
|
+
return process.env.NEXT_PUBLIC_API_URL || process.env.ONEXTHM_API_URL || "https://platform-dev.onexeos.com";
|
|
4289
|
+
}
|
|
4290
|
+
async function saveAuthTokens(tokens) {
|
|
4291
|
+
await fs.ensureDir(AUTH_DIR);
|
|
4292
|
+
const key = getMachineKey();
|
|
4293
|
+
const data = JSON.stringify(tokens);
|
|
4294
|
+
const encrypted = encrypt(data, key);
|
|
4295
|
+
await fs.writeFile(AUTH_FILE, encrypted, "utf-8");
|
|
4296
|
+
}
|
|
4297
|
+
function loadAuthTokens() {
|
|
4298
|
+
try {
|
|
4299
|
+
if (!fs.existsSync(AUTH_FILE)) return null;
|
|
4300
|
+
const encrypted = fs.readFileSync(AUTH_FILE, "utf-8");
|
|
4301
|
+
const key = getMachineKey();
|
|
4302
|
+
const data = decrypt(encrypted, key);
|
|
4303
|
+
return JSON.parse(data);
|
|
4304
|
+
} catch {
|
|
4305
|
+
return null;
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
async function clearAuthTokens() {
|
|
4309
|
+
try {
|
|
4310
|
+
await fs.remove(AUTH_FILE);
|
|
4311
|
+
} catch {
|
|
4312
|
+
}
|
|
4313
|
+
}
|
|
4314
|
+
function isTokenExpired(tokens) {
|
|
4315
|
+
return Date.now() / 1e3 > tokens.expiresAt - 60;
|
|
4316
|
+
}
|
|
4317
|
+
async function getValidTokens() {
|
|
4318
|
+
const tokens = loadAuthTokens();
|
|
4319
|
+
if (!tokens) return null;
|
|
4320
|
+
if (!isTokenExpired(tokens)) return tokens;
|
|
4321
|
+
try {
|
|
4322
|
+
const apiUrl = getApiUrl();
|
|
4323
|
+
const response = await fetch(`${apiUrl}/auth/refresh`, {
|
|
4324
|
+
method: "POST",
|
|
4325
|
+
headers: { "Content-Type": "application/json" },
|
|
4326
|
+
body: JSON.stringify({ refresh_token: tokens.refreshToken })
|
|
4327
|
+
});
|
|
4328
|
+
if (!response.ok) {
|
|
4329
|
+
await clearAuthTokens();
|
|
4330
|
+
return null;
|
|
4331
|
+
}
|
|
4332
|
+
const data = await response.json();
|
|
4333
|
+
const body = data.statusCode ? data.body : data;
|
|
4334
|
+
const refreshed = {
|
|
4335
|
+
...tokens,
|
|
4336
|
+
accessToken: body.AccessToken || tokens.accessToken,
|
|
4337
|
+
idToken: body.IdToken || tokens.idToken,
|
|
4338
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
|
|
4339
|
+
};
|
|
4340
|
+
await saveAuthTokens(refreshed);
|
|
4341
|
+
return refreshed;
|
|
4342
|
+
} catch {
|
|
4343
|
+
await clearAuthTokens();
|
|
4344
|
+
return null;
|
|
4345
|
+
}
|
|
4346
|
+
}
|
|
4347
|
+
async function authenticatedFetch(url, init) {
|
|
4348
|
+
const tokens = await getValidTokens();
|
|
4349
|
+
if (!tokens) {
|
|
4350
|
+
throw new Error("Not logged in. Run: onexthm login");
|
|
4351
|
+
}
|
|
4352
|
+
const headers = new Headers(init?.headers);
|
|
4353
|
+
headers.set("Authorization", `Bearer ${tokens.idToken}`);
|
|
4354
|
+
headers.set("Content-Type", "application/json");
|
|
4355
|
+
return fetch(url, { ...init, headers });
|
|
4356
|
+
}
|
|
4357
|
+
function getMachineKey() {
|
|
4358
|
+
let seed;
|
|
4359
|
+
if (process.platform === "darwin") {
|
|
4360
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4361
|
+
} else if (process.platform === "linux") {
|
|
4362
|
+
try {
|
|
4363
|
+
seed = `onexthm:${fs.readFileSync("/etc/machine-id", "utf-8").trim()}`;
|
|
4364
|
+
} catch {
|
|
4365
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4366
|
+
}
|
|
4367
|
+
} else {
|
|
4368
|
+
seed = `onexthm:${os3.hostname()}:${os3.userInfo().username}`;
|
|
4369
|
+
}
|
|
4370
|
+
return crypto2.createHash("sha256").update(seed).digest();
|
|
4371
|
+
}
|
|
4372
|
+
function encrypt(text, key) {
|
|
4373
|
+
const iv = crypto2.randomBytes(16);
|
|
4374
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, iv);
|
|
4375
|
+
let encrypted = cipher.update(text, "utf-8", "hex");
|
|
4376
|
+
encrypted += cipher.final("hex");
|
|
4377
|
+
const tag = cipher.getAuthTag();
|
|
4378
|
+
return `${iv.toString("hex")}:${tag.toString("hex")}:${encrypted}`;
|
|
4379
|
+
}
|
|
4380
|
+
function decrypt(text, key) {
|
|
4381
|
+
const [ivHex, tagHex, encrypted] = text.split(":");
|
|
4382
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
4383
|
+
const tag = Buffer.from(tagHex, "hex");
|
|
4384
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, iv);
|
|
4385
|
+
decipher.setAuthTag(tag);
|
|
4386
|
+
let decrypted = decipher.update(encrypted, "hex", "utf-8");
|
|
4387
|
+
decrypted += decipher.final("utf-8");
|
|
4388
|
+
return decrypted;
|
|
4389
|
+
}
|
|
4390
|
+
function parseJwtClaims(idToken) {
|
|
4391
|
+
try {
|
|
4392
|
+
const payload = idToken.split(".")[1];
|
|
4393
|
+
const decoded = Buffer.from(payload, "base64url").toString("utf-8");
|
|
4394
|
+
return JSON.parse(decoded);
|
|
4395
|
+
} catch {
|
|
4396
|
+
return {};
|
|
4397
|
+
}
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
// src/commands/login.ts
|
|
4401
|
+
async function loginCommand() {
|
|
4402
|
+
logger.header("OneX Theme Developer Login");
|
|
4403
|
+
const existing = loadAuthTokens();
|
|
4404
|
+
if (existing) {
|
|
4405
|
+
logger.info(`Already logged in as: ${existing.user.email}`);
|
|
4406
|
+
const { relogin } = await inquirer.prompt([
|
|
4407
|
+
{
|
|
4408
|
+
type: "confirm",
|
|
4409
|
+
name: "relogin",
|
|
4410
|
+
message: "Re-login with different account?",
|
|
4411
|
+
default: false
|
|
4412
|
+
}
|
|
4413
|
+
]);
|
|
4414
|
+
if (!relogin) return;
|
|
4415
|
+
}
|
|
4416
|
+
const { email, password } = await inquirer.prompt([
|
|
4417
|
+
{
|
|
4418
|
+
type: "input",
|
|
4419
|
+
name: "email",
|
|
4420
|
+
message: "Email:",
|
|
4421
|
+
validate: (input) => input.includes("@") ? true : "Enter a valid email"
|
|
4422
|
+
},
|
|
4423
|
+
{
|
|
4424
|
+
type: "password",
|
|
4425
|
+
name: "password",
|
|
4426
|
+
message: "Password:",
|
|
4427
|
+
validate: (input) => input.length >= 6 ? true : "Password too short"
|
|
4428
|
+
}
|
|
4429
|
+
]);
|
|
4430
|
+
logger.startSpinner("Logging in...");
|
|
4431
|
+
try {
|
|
4432
|
+
const apiUrl = getApiUrl();
|
|
4433
|
+
const response = await fetch(`${apiUrl}/auth/login`, {
|
|
4434
|
+
method: "POST",
|
|
4435
|
+
headers: { "Content-Type": "application/json" },
|
|
4436
|
+
body: JSON.stringify({ username: email, password })
|
|
4437
|
+
});
|
|
4438
|
+
const raw = await response.json();
|
|
4439
|
+
const data = raw.statusCode ? raw.body : raw;
|
|
4440
|
+
if (!response.ok || data.error) {
|
|
4441
|
+
logger.stopSpinner(false, "Login failed");
|
|
4442
|
+
logger.error(data.message || data.error || "Invalid credentials");
|
|
4443
|
+
process.exit(1);
|
|
4444
|
+
}
|
|
4445
|
+
const idToken = data.IdToken;
|
|
4446
|
+
const accessToken = data.AccessToken;
|
|
4447
|
+
const refreshToken = data.RefreshToken;
|
|
4448
|
+
const expiresIn = data.ExpiresIn || 3600;
|
|
4449
|
+
if (!idToken) {
|
|
4450
|
+
logger.stopSpinner(false, "Login failed");
|
|
4451
|
+
logger.error("No token received from server");
|
|
4452
|
+
process.exit(1);
|
|
4453
|
+
}
|
|
4454
|
+
const claims = parseJwtClaims(idToken);
|
|
4455
|
+
const tokens = {
|
|
4456
|
+
accessToken,
|
|
4457
|
+
idToken,
|
|
4458
|
+
refreshToken,
|
|
4459
|
+
expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
|
|
4460
|
+
user: {
|
|
4461
|
+
email: claims.email || email,
|
|
4462
|
+
name: claims.name,
|
|
4463
|
+
companyId: claims["custom:company_id"],
|
|
4464
|
+
userId: claims.sub
|
|
4465
|
+
}
|
|
4466
|
+
};
|
|
4467
|
+
await saveAuthTokens(tokens);
|
|
4468
|
+
logger.stopSpinner(true, "Logged in!");
|
|
4469
|
+
logger.newLine();
|
|
4470
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4471
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4472
|
+
if (tokens.user.companyId)
|
|
4473
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4474
|
+
logger.newLine();
|
|
4475
|
+
logger.success("Token stored securely in ~/.onexthm/auth.json (encrypted)");
|
|
4476
|
+
} catch (error) {
|
|
4477
|
+
logger.stopSpinner(false, "Login failed");
|
|
4478
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4479
|
+
process.exit(1);
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
// src/commands/logout.ts
|
|
4484
|
+
init_logger();
|
|
4485
|
+
async function logoutCommand() {
|
|
4486
|
+
const tokens = loadAuthTokens();
|
|
4487
|
+
if (!tokens) {
|
|
4488
|
+
logger.info("Not logged in.");
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
await clearAuthTokens();
|
|
4492
|
+
logger.success(`Logged out (was: ${tokens.user.email})`);
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4495
|
+
// src/commands/whoami.ts
|
|
4496
|
+
init_logger();
|
|
4497
|
+
async function whoamiCommand() {
|
|
4498
|
+
const tokens = loadAuthTokens();
|
|
4499
|
+
if (!tokens) {
|
|
4500
|
+
logger.error("Not logged in. Run: onexthm login");
|
|
4501
|
+
process.exit(1);
|
|
4502
|
+
}
|
|
4503
|
+
const expired = isTokenExpired(tokens);
|
|
4504
|
+
logger.header("OneX Theme Developer");
|
|
4505
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4506
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4507
|
+
if (tokens.user.companyId)
|
|
4508
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4509
|
+
logger.info(
|
|
4510
|
+
` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
|
|
4514
|
+
// src/commands/publish.ts
|
|
4515
|
+
init_logger();
|
|
4516
|
+
async function publishCommand(options) {
|
|
4517
|
+
logger.header("OneX Theme Publish");
|
|
4518
|
+
const tokens = await getValidTokens();
|
|
4519
|
+
if (!tokens) {
|
|
4520
|
+
logger.error("Not logged in. Run: onexthm login");
|
|
4521
|
+
process.exit(1);
|
|
4522
|
+
}
|
|
4523
|
+
logger.info(`Logged in as: ${tokens.user.email}`);
|
|
4524
|
+
let themePath;
|
|
4525
|
+
if (options.theme) {
|
|
4526
|
+
themePath = path8.resolve(options.theme);
|
|
4527
|
+
} else {
|
|
4528
|
+
const isThemeDir = [
|
|
4529
|
+
"theme.config.ts",
|
|
4530
|
+
"bundle-entry.ts",
|
|
4531
|
+
"manifest.ts"
|
|
4532
|
+
].some((f) => fs.existsSync(path8.join(process.cwd(), f)));
|
|
4533
|
+
if (isThemeDir) {
|
|
4534
|
+
themePath = process.cwd();
|
|
4535
|
+
} else {
|
|
4536
|
+
logger.error(
|
|
4537
|
+
"Not in a theme directory. Run from theme root or use --theme flag."
|
|
4538
|
+
);
|
|
4539
|
+
process.exit(1);
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
const pkgPath = path8.join(themePath, "package.json");
|
|
4543
|
+
if (!fs.existsSync(pkgPath)) {
|
|
4544
|
+
logger.error("No package.json found in theme directory");
|
|
4545
|
+
process.exit(1);
|
|
4546
|
+
}
|
|
4547
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
4548
|
+
const themeId = pkg.name?.replace("@onex-themes/", "") || path8.basename(themePath);
|
|
4549
|
+
const version2 = pkg.version || "1.0.0";
|
|
4550
|
+
logger.newLine();
|
|
4551
|
+
logger.info(`Theme: ${themeId}`);
|
|
4552
|
+
logger.info(`Version: ${version2}`);
|
|
4553
|
+
logger.newLine();
|
|
4554
|
+
const apiUrl = getApiUrl();
|
|
4555
|
+
logger.startSpinner("Registering theme...");
|
|
4556
|
+
try {
|
|
4557
|
+
const regResponse = await authenticatedFetch(
|
|
4558
|
+
`${apiUrl}/website-api/themes/register`,
|
|
4559
|
+
{
|
|
4560
|
+
method: "POST",
|
|
4561
|
+
body: JSON.stringify({
|
|
4562
|
+
themeId,
|
|
4563
|
+
name: pkg.displayName || themeId,
|
|
4564
|
+
description: pkg.description || "",
|
|
4565
|
+
email: tokens.user.email
|
|
4566
|
+
})
|
|
4567
|
+
}
|
|
4568
|
+
);
|
|
4569
|
+
const regData = await regResponse.json();
|
|
4570
|
+
const regBody = regData.statusCode ? regData.body : regData;
|
|
4571
|
+
if (!regResponse.ok && regBody.error && !regBody.error.includes("already registered")) {
|
|
4572
|
+
logger.stopSpinner(false, "Registration failed");
|
|
4573
|
+
logger.error(regBody.error);
|
|
4574
|
+
process.exit(1);
|
|
4575
|
+
}
|
|
4576
|
+
logger.stopSpinner(true, regBody.message || "Theme registered");
|
|
4577
|
+
} catch (error) {
|
|
4578
|
+
logger.stopSpinner(false, "Registration failed");
|
|
4579
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4580
|
+
process.exit(1);
|
|
4581
|
+
}
|
|
4582
|
+
logger.startSpinner("Building theme...");
|
|
4583
|
+
try {
|
|
4584
|
+
const { execSync: execSync3 } = await import('child_process');
|
|
4585
|
+
execSync3(
|
|
4586
|
+
"npx tsup bundle-entry.ts --format esm --target es2020 --outDir dist",
|
|
4587
|
+
{
|
|
4588
|
+
cwd: themePath,
|
|
4589
|
+
stdio: "ignore"
|
|
4590
|
+
}
|
|
4591
|
+
);
|
|
4592
|
+
logger.stopSpinner(true, "Theme compiled");
|
|
4593
|
+
} catch {
|
|
4594
|
+
logger.stopSpinner(false, "Build failed");
|
|
4595
|
+
logger.error("Run 'onexthm build' to see build errors");
|
|
4596
|
+
process.exit(1);
|
|
4597
|
+
}
|
|
4598
|
+
logger.startSpinner("Getting upload URL...");
|
|
4599
|
+
let bundleUploadUrl;
|
|
4600
|
+
let sourceUploadUrl;
|
|
4601
|
+
try {
|
|
4602
|
+
const pubResponse = await authenticatedFetch(
|
|
4603
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions`,
|
|
4604
|
+
{
|
|
4605
|
+
method: "POST",
|
|
4606
|
+
body: JSON.stringify({ version: version2 })
|
|
4607
|
+
}
|
|
4608
|
+
);
|
|
4609
|
+
const pubData = await pubResponse.json();
|
|
4610
|
+
const pubBody = pubData.statusCode ? pubData.body : pubData;
|
|
4611
|
+
if (!pubResponse.ok || !pubBody.bundleUploadUrl) {
|
|
4612
|
+
logger.stopSpinner(false, "Failed to get upload URL");
|
|
4613
|
+
logger.error(pubBody.error || "Server error");
|
|
4614
|
+
process.exit(1);
|
|
4615
|
+
}
|
|
4616
|
+
bundleUploadUrl = pubBody.bundleUploadUrl;
|
|
4617
|
+
sourceUploadUrl = pubBody.sourceUploadUrl;
|
|
4618
|
+
logger.stopSpinner(true, "Upload URL obtained");
|
|
4619
|
+
} catch (error) {
|
|
4620
|
+
logger.stopSpinner(false, "Failed");
|
|
4621
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4622
|
+
process.exit(1);
|
|
4623
|
+
}
|
|
4624
|
+
logger.startSpinner("Uploading bundle...");
|
|
4625
|
+
try {
|
|
4626
|
+
const archiver3 = await import('archiver');
|
|
4627
|
+
const { createWriteStream } = await import('fs');
|
|
4628
|
+
const distDir = path8.join(themePath, "dist");
|
|
4629
|
+
if (!fs.existsSync(distDir)) {
|
|
4630
|
+
logger.stopSpinner(false, "No dist/ directory");
|
|
4631
|
+
logger.error("Build the theme first: onexthm build");
|
|
4632
|
+
process.exit(1);
|
|
4633
|
+
}
|
|
4634
|
+
const bundleZipPath = path8.join(themePath, "dist", "bundle.zip");
|
|
4635
|
+
await createZip(distDir, bundleZipPath, ["bundle.zip"]);
|
|
4636
|
+
const bundleBuffer = fs.readFileSync(bundleZipPath);
|
|
4637
|
+
const bundleRes = await fetch(bundleUploadUrl, {
|
|
4638
|
+
method: "PUT",
|
|
4639
|
+
headers: { "Content-Type": "application/zip" },
|
|
4640
|
+
body: bundleBuffer
|
|
4641
|
+
});
|
|
4642
|
+
if (!bundleRes.ok) {
|
|
4643
|
+
throw new Error(`Upload failed: ${bundleRes.status}`);
|
|
4644
|
+
}
|
|
4645
|
+
const sizeMB = (bundleBuffer.length / 1024 / 1024).toFixed(2);
|
|
4646
|
+
logger.stopSpinner(true, `Bundle uploaded (${sizeMB} MB)`);
|
|
4647
|
+
} catch (error) {
|
|
4648
|
+
logger.stopSpinner(false, "Upload failed");
|
|
4649
|
+
logger.error(error instanceof Error ? error.message : "Upload error");
|
|
4650
|
+
process.exit(1);
|
|
4651
|
+
}
|
|
4652
|
+
logger.startSpinner("Uploading source...");
|
|
4653
|
+
try {
|
|
4654
|
+
const sourceZipPath = path8.join(themePath, "dist", "source.zip");
|
|
4655
|
+
await createZip(themePath, sourceZipPath, [
|
|
4656
|
+
"node_modules",
|
|
4657
|
+
"dist",
|
|
4658
|
+
".git",
|
|
4659
|
+
".env",
|
|
4660
|
+
".env.local"
|
|
4661
|
+
]);
|
|
4662
|
+
const sourceBuffer = fs.readFileSync(sourceZipPath);
|
|
4663
|
+
const sourceRes = await fetch(sourceUploadUrl, {
|
|
4664
|
+
method: "PUT",
|
|
4665
|
+
headers: { "Content-Type": "application/zip" },
|
|
4666
|
+
body: sourceBuffer
|
|
4667
|
+
});
|
|
4668
|
+
if (!sourceRes.ok) {
|
|
4669
|
+
throw new Error(`Source upload failed: ${sourceRes.status}`);
|
|
4670
|
+
}
|
|
4671
|
+
const sizeMB = (sourceBuffer.length / 1024 / 1024).toFixed(2);
|
|
4672
|
+
logger.stopSpinner(true, `Source uploaded (${sizeMB} MB)`);
|
|
4673
|
+
} catch (error) {
|
|
4674
|
+
logger.stopSpinner(false, "Source upload failed");
|
|
4675
|
+
logger.info("Source upload skipped (bundle was uploaded successfully)");
|
|
4676
|
+
}
|
|
4677
|
+
logger.startSpinner("Scanning and publishing...");
|
|
4678
|
+
try {
|
|
4679
|
+
const confirmResponse = await authenticatedFetch(
|
|
4680
|
+
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
|
|
4681
|
+
{ method: "POST" }
|
|
4682
|
+
);
|
|
4683
|
+
const confirmData = await confirmResponse.json();
|
|
4684
|
+
const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
|
|
4685
|
+
if (!confirmResponse.ok || !confirmBody.success) {
|
|
4686
|
+
logger.stopSpinner(false, "Publishing failed");
|
|
4687
|
+
if (confirmBody.violations) {
|
|
4688
|
+
logger.error("Theme rejected \u2014 security violations found:");
|
|
4689
|
+
for (const v of confirmBody.violations) {
|
|
4690
|
+
logger.log(` \u274C ${v.file}: ${v.violation}`);
|
|
4691
|
+
}
|
|
4692
|
+
} else {
|
|
4693
|
+
logger.error(confirmBody.error || "Unknown error");
|
|
4694
|
+
}
|
|
4695
|
+
if (confirmBody.warnings?.length) {
|
|
4696
|
+
logger.newLine();
|
|
4697
|
+
logger.info("Warnings:");
|
|
4698
|
+
for (const w of confirmBody.warnings) {
|
|
4699
|
+
logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
process.exit(1);
|
|
4703
|
+
}
|
|
4704
|
+
logger.stopSpinner(true, confirmBody.message || "Published!");
|
|
4705
|
+
if (confirmBody.warnings?.length) {
|
|
4706
|
+
logger.newLine();
|
|
4707
|
+
logger.info("Warnings (non-blocking):");
|
|
4708
|
+
for (const w of confirmBody.warnings) {
|
|
4709
|
+
logger.log(` \u26A0\uFE0F ${w.file}: ${w.warning}`);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
} catch (error) {
|
|
4713
|
+
logger.stopSpinner(false, "Publishing failed");
|
|
4714
|
+
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
4715
|
+
process.exit(1);
|
|
4716
|
+
}
|
|
4717
|
+
logger.newLine();
|
|
4718
|
+
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
4719
|
+
}
|
|
4720
|
+
async function createZip(sourceDir, outputPath, exclude) {
|
|
4721
|
+
const archiver3 = (await import('archiver')).default;
|
|
4722
|
+
const { createWriteStream } = await import('fs');
|
|
4723
|
+
return new Promise((resolve, reject) => {
|
|
4724
|
+
const output = createWriteStream(outputPath);
|
|
4725
|
+
const archive = archiver3("zip", { zlib: { level: 9 } });
|
|
4726
|
+
output.on("close", resolve);
|
|
4727
|
+
archive.on("error", reject);
|
|
4728
|
+
archive.pipe(output);
|
|
4729
|
+
archive.glob("**/*", {
|
|
4730
|
+
cwd: sourceDir,
|
|
4731
|
+
ignore: exclude.map((e) => `${e}/**`),
|
|
4732
|
+
dot: false
|
|
4733
|
+
});
|
|
4734
|
+
archive.finalize();
|
|
4735
|
+
});
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4415
4738
|
// src/cli.ts
|
|
4416
4739
|
try {
|
|
4417
4740
|
const projectRoot = getProjectRoot();
|
|
@@ -4423,7 +4746,7 @@ try {
|
|
|
4423
4746
|
} catch {
|
|
4424
4747
|
}
|
|
4425
4748
|
dotenv.config({
|
|
4426
|
-
path: path8.join(
|
|
4749
|
+
path: path8.join(os3.homedir(), ".onexthm", ".env"),
|
|
4427
4750
|
quiet: true
|
|
4428
4751
|
});
|
|
4429
4752
|
var require2 = createRequire(import.meta.url);
|
|
@@ -4480,6 +4803,10 @@ program.command("clone").description("Clone theme source code from S3").argument
|
|
|
4480
4803
|
"staging"
|
|
4481
4804
|
).option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
4482
4805
|
program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
|
|
4806
|
+
program.command("login").description("Login to OneX platform").action(loginCommand);
|
|
4807
|
+
program.command("logout").description("Logout from OneX platform").action(logoutCommand);
|
|
4808
|
+
program.command("whoami").description("Show current logged-in developer").action(whoamiCommand);
|
|
4809
|
+
program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").action(publishCommand);
|
|
4483
4810
|
program.configureOutput({
|
|
4484
4811
|
writeErr: (str) => process.stderr.write(chalk4.red(str))
|
|
4485
4812
|
});
|