@onexapis/cli 1.1.64 → 1.1.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +267 -105
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +267 -105
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +10 -17
- package/dist/index.d.ts +10 -17
- package/dist/index.js +67 -39
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/default/.env.example +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1764,21 +1764,43 @@ function getValidCategories() {
|
|
|
1764
1764
|
];
|
|
1765
1765
|
}
|
|
1766
1766
|
var AUTH_DIR = path9.join(os.homedir(), ".onexthm");
|
|
1767
|
-
var
|
|
1768
|
-
|
|
1769
|
-
|
|
1767
|
+
var ENV_URLS = {
|
|
1768
|
+
dev: "https://platform-dev.onexeos.com",
|
|
1769
|
+
prod: "https://platform-staging.onexeos.com"
|
|
1770
|
+
};
|
|
1771
|
+
function getAuthFile(env = "dev") {
|
|
1772
|
+
const newFile = path9.join(AUTH_DIR, `auth-${env}.json`);
|
|
1773
|
+
if (env === "dev") {
|
|
1774
|
+
const legacyFile = path9.join(AUTH_DIR, "auth.json");
|
|
1775
|
+
if (fs.existsSync(legacyFile) && !fs.existsSync(newFile)) {
|
|
1776
|
+
try {
|
|
1777
|
+
fs.moveSync(legacyFile, newFile);
|
|
1778
|
+
} catch {
|
|
1779
|
+
try {
|
|
1780
|
+
fs.copySync(legacyFile, newFile);
|
|
1781
|
+
fs.removeSync(legacyFile);
|
|
1782
|
+
} catch {
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return newFile;
|
|
1788
|
+
}
|
|
1789
|
+
function getApiUrl(env = "dev") {
|
|
1790
|
+
return process.env.ONEXTHM_API_URL || ENV_URLS[env];
|
|
1770
1791
|
}
|
|
1771
|
-
async function saveAuthTokens(tokens) {
|
|
1792
|
+
async function saveAuthTokens(tokens, env = "dev") {
|
|
1772
1793
|
await fs.ensureDir(AUTH_DIR);
|
|
1773
1794
|
const key = getMachineKey();
|
|
1774
1795
|
const data = JSON.stringify(tokens);
|
|
1775
1796
|
const encrypted = encrypt(data, key);
|
|
1776
|
-
await fs.writeFile(
|
|
1797
|
+
await fs.writeFile(getAuthFile(env), encrypted, "utf-8");
|
|
1777
1798
|
}
|
|
1778
|
-
function loadAuthTokens() {
|
|
1799
|
+
function loadAuthTokens(env = "dev") {
|
|
1779
1800
|
try {
|
|
1780
|
-
|
|
1781
|
-
|
|
1801
|
+
const file = getAuthFile(env);
|
|
1802
|
+
if (!fs.existsSync(file)) return null;
|
|
1803
|
+
const encrypted = fs.readFileSync(file, "utf-8");
|
|
1782
1804
|
const key = getMachineKey();
|
|
1783
1805
|
const data = decrypt(encrypted, key);
|
|
1784
1806
|
return JSON.parse(data);
|
|
@@ -1786,34 +1808,34 @@ function loadAuthTokens() {
|
|
|
1786
1808
|
return null;
|
|
1787
1809
|
}
|
|
1788
1810
|
}
|
|
1789
|
-
async function clearAuthTokens() {
|
|
1811
|
+
async function clearAuthTokens(env = "dev") {
|
|
1790
1812
|
try {
|
|
1791
|
-
await fs.remove(
|
|
1813
|
+
await fs.remove(getAuthFile(env));
|
|
1792
1814
|
} catch {
|
|
1793
1815
|
}
|
|
1794
1816
|
}
|
|
1795
1817
|
function isTokenExpired(tokens) {
|
|
1796
1818
|
return Date.now() / 1e3 > tokens.expiresAt - 300;
|
|
1797
1819
|
}
|
|
1798
|
-
async function getValidTokens() {
|
|
1799
|
-
const tokens = loadAuthTokens();
|
|
1820
|
+
async function getValidTokens(env = "dev") {
|
|
1821
|
+
const tokens = loadAuthTokens(env);
|
|
1800
1822
|
if (!tokens) return null;
|
|
1801
1823
|
if (!isTokenExpired(tokens)) return tokens;
|
|
1802
1824
|
try {
|
|
1803
|
-
const apiUrl = getApiUrl();
|
|
1825
|
+
const apiUrl = getApiUrl(env);
|
|
1804
1826
|
const response = await fetch(`${apiUrl}/auth/refresh`, {
|
|
1805
1827
|
method: "POST",
|
|
1806
1828
|
headers: { "Content-Type": "application/json" },
|
|
1807
1829
|
body: JSON.stringify({ refresh_token: tokens.refreshToken })
|
|
1808
1830
|
});
|
|
1809
1831
|
if (!response.ok) {
|
|
1810
|
-
await clearAuthTokens();
|
|
1832
|
+
await clearAuthTokens(env);
|
|
1811
1833
|
return null;
|
|
1812
1834
|
}
|
|
1813
1835
|
const data = await response.json();
|
|
1814
1836
|
const body = data.statusCode ? data.body : data;
|
|
1815
1837
|
if (!body.IdToken) {
|
|
1816
|
-
await clearAuthTokens();
|
|
1838
|
+
await clearAuthTokens(env);
|
|
1817
1839
|
return null;
|
|
1818
1840
|
}
|
|
1819
1841
|
const refreshed = {
|
|
@@ -1822,17 +1844,19 @@ async function getValidTokens() {
|
|
|
1822
1844
|
idToken: body.IdToken,
|
|
1823
1845
|
expiresAt: Math.floor(Date.now() / 1e3) + (body.ExpiresIn || 3600)
|
|
1824
1846
|
};
|
|
1825
|
-
await saveAuthTokens(refreshed);
|
|
1847
|
+
await saveAuthTokens(refreshed, env);
|
|
1826
1848
|
return refreshed;
|
|
1827
1849
|
} catch {
|
|
1828
|
-
await clearAuthTokens();
|
|
1850
|
+
await clearAuthTokens(env);
|
|
1829
1851
|
return null;
|
|
1830
1852
|
}
|
|
1831
1853
|
}
|
|
1832
|
-
async function authenticatedFetch(url, init) {
|
|
1833
|
-
const tokens = await getValidTokens();
|
|
1854
|
+
async function authenticatedFetch(url, init, env = "dev") {
|
|
1855
|
+
const tokens = await getValidTokens(env);
|
|
1834
1856
|
if (!tokens) {
|
|
1835
|
-
throw new Error(
|
|
1857
|
+
throw new Error(
|
|
1858
|
+
`Not logged in to ${env} environment. Run: onexthm login --env ${env}`
|
|
1859
|
+
);
|
|
1836
1860
|
}
|
|
1837
1861
|
const headers = new Headers(init?.headers);
|
|
1838
1862
|
headers.set("Authorization", `Bearer ${tokens.idToken}`);
|
|
@@ -1916,7 +1940,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
1916
1940
|
}
|
|
1917
1941
|
if (!options.yes) {
|
|
1918
1942
|
try {
|
|
1919
|
-
const apiUrl = getApiUrl();
|
|
1943
|
+
const apiUrl = getApiUrl(options.env ?? "dev");
|
|
1920
1944
|
const controller = new AbortController();
|
|
1921
1945
|
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1922
1946
|
const response = await fetch(
|
|
@@ -3865,9 +3889,7 @@ function showDownloadFailureHelp(themeId, apiUrl) {
|
|
|
3865
3889
|
console.log();
|
|
3866
3890
|
console.log(chalk4.white("2. Check API URL configuration:"));
|
|
3867
3891
|
console.log(chalk4.gray(` Current API URL: ${apiUrl}`));
|
|
3868
|
-
console.log(
|
|
3869
|
-
chalk4.gray(" Override with NEXT_PUBLIC_API_URL or ONEXTHM_API_URL")
|
|
3870
|
-
);
|
|
3892
|
+
console.log(chalk4.gray(" Override with ONEXTHM_API_URL env var if needed"));
|
|
3871
3893
|
console.log();
|
|
3872
3894
|
console.log(chalk4.white("3. Pin a specific version (CI/production):"));
|
|
3873
3895
|
console.log(
|
|
@@ -3877,15 +3899,17 @@ function showDownloadFailureHelp(themeId, apiUrl) {
|
|
|
3877
3899
|
}
|
|
3878
3900
|
async function downloadCommand(options) {
|
|
3879
3901
|
logger.header("Download Theme");
|
|
3902
|
+
const env = options.env ?? "dev";
|
|
3903
|
+
const apiUrl = getApiUrl(env);
|
|
3904
|
+
logger.info(`Environment: ${env} (${apiUrl})`);
|
|
3880
3905
|
const spinner = ora("Initializing download...").start();
|
|
3881
|
-
if (options.bucket
|
|
3906
|
+
if (options.bucket) {
|
|
3882
3907
|
spinner.stop();
|
|
3883
3908
|
logger.warning(
|
|
3884
|
-
"--bucket
|
|
3909
|
+
"--bucket is deprecated and ignored. Themes are now served via HTTP from the website-api Lambda."
|
|
3885
3910
|
);
|
|
3886
3911
|
spinner.start();
|
|
3887
3912
|
}
|
|
3888
|
-
const apiUrl = getApiUrl();
|
|
3889
3913
|
try {
|
|
3890
3914
|
const themeId = options.themeId || process.env.NEXT_PUBLIC_THEME_ID || process.env.THEME_ID;
|
|
3891
3915
|
const requestedVersion = options.version || process.env.THEME_VERSION || "latest";
|
|
@@ -3942,6 +3966,7 @@ async function downloadCommand(options) {
|
|
|
3942
3966
|
console.log(
|
|
3943
3967
|
chalk4.cyan(" Theme: ") + chalk4.white(`${themeId}@${resolvedVersion}`)
|
|
3944
3968
|
);
|
|
3969
|
+
console.log(chalk4.cyan(" Env: ") + chalk4.white(env));
|
|
3945
3970
|
console.log(chalk4.cyan(" Source: ") + chalk4.white(apiUrl));
|
|
3946
3971
|
console.log(chalk4.cyan(" Output: ") + chalk4.white(outputDir));
|
|
3947
3972
|
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
@@ -3984,11 +4009,9 @@ async function resolveLatestVersion2(apiUrl, themeId) {
|
|
|
3984
4009
|
}
|
|
3985
4010
|
return latest;
|
|
3986
4011
|
}
|
|
3987
|
-
async function fetchSourceZip(apiUrl, themeId, version2) {
|
|
4012
|
+
async function fetchSourceZip(apiUrl, themeId, version2, env) {
|
|
3988
4013
|
const url = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/source?version=${encodeURIComponent(version2)}`;
|
|
3989
|
-
const response = await authenticatedFetch(url, {
|
|
3990
|
-
method: "GET"
|
|
3991
|
-
});
|
|
4014
|
+
const response = await authenticatedFetch(url, { method: "GET" }, env);
|
|
3992
4015
|
if (!response.ok) {
|
|
3993
4016
|
if (response.status === 404) {
|
|
3994
4017
|
throw new Error(
|
|
@@ -3997,7 +4020,7 @@ async function fetchSourceZip(apiUrl, themeId, version2) {
|
|
|
3997
4020
|
}
|
|
3998
4021
|
if (response.status === 401 || response.status === 403) {
|
|
3999
4022
|
throw new Error(
|
|
4000
|
-
`Not authorized to download source for "${themeId}". Run \`onexthm login\` first.`
|
|
4023
|
+
`Not authorized to download source for "${themeId}". Run \`onexthm login --env ${env}\` first.`
|
|
4001
4024
|
);
|
|
4002
4025
|
}
|
|
4003
4026
|
throw new Error(
|
|
@@ -4116,14 +4139,19 @@ async function renameTheme(themeDir, oldName, newName) {
|
|
|
4116
4139
|
}
|
|
4117
4140
|
async function cloneCommand(themeName, options) {
|
|
4118
4141
|
logger.header("Clone Theme Source");
|
|
4119
|
-
|
|
4142
|
+
const env = options.env ?? "dev";
|
|
4143
|
+
const apiUrl = getApiUrl(env);
|
|
4144
|
+
logger.info(`Environment: ${env} (${apiUrl})`);
|
|
4145
|
+
if (options.bucket) {
|
|
4120
4146
|
logger.warning(
|
|
4121
|
-
"--bucket
|
|
4147
|
+
"--bucket is deprecated and ignored. Source is now fetched via HTTP from the website-api Lambda."
|
|
4122
4148
|
);
|
|
4123
4149
|
}
|
|
4124
|
-
const tokens = await getValidTokens();
|
|
4150
|
+
const tokens = await getValidTokens(env);
|
|
4125
4151
|
if (!tokens) {
|
|
4126
|
-
logger.error(
|
|
4152
|
+
logger.error(
|
|
4153
|
+
`Not logged in to ${env} environment. Run: onexthm login --env ${env}`
|
|
4154
|
+
);
|
|
4127
4155
|
process.exit(1);
|
|
4128
4156
|
}
|
|
4129
4157
|
let newName = options.name;
|
|
@@ -4132,7 +4160,6 @@ async function cloneCommand(themeName, options) {
|
|
|
4132
4160
|
}
|
|
4133
4161
|
const spinner = ora("Initializing clone...").start();
|
|
4134
4162
|
try {
|
|
4135
|
-
const apiUrl = getApiUrl();
|
|
4136
4163
|
const outputDir = options.output || path9.resolve(process.cwd(), newName);
|
|
4137
4164
|
if (await fs.pathExists(outputDir)) {
|
|
4138
4165
|
spinner.fail(chalk4.red(`Directory already exists: ${outputDir}`));
|
|
@@ -4152,7 +4179,7 @@ async function cloneCommand(themeName, options) {
|
|
|
4152
4179
|
spinner.start(`Downloading source.zip for ${themeName}@${version2}...`);
|
|
4153
4180
|
let zipBuffer;
|
|
4154
4181
|
try {
|
|
4155
|
-
zipBuffer = await fetchSourceZip(apiUrl, themeName, version2);
|
|
4182
|
+
zipBuffer = await fetchSourceZip(apiUrl, themeName, version2, env);
|
|
4156
4183
|
} catch (error) {
|
|
4157
4184
|
spinner.fail(chalk4.red(error.message));
|
|
4158
4185
|
console.log();
|
|
@@ -4186,7 +4213,7 @@ async function cloneCommand(themeName, options) {
|
|
|
4186
4213
|
[
|
|
4187
4214
|
"# API Configuration (enables real data in preview)",
|
|
4188
4215
|
"# Get your Company ID from the OneX dashboard",
|
|
4189
|
-
|
|
4216
|
+
`NEXT_PUBLIC_API_URL=${apiUrl}`,
|
|
4190
4217
|
"NEXT_PUBLIC_COMPANY_ID=",
|
|
4191
4218
|
""
|
|
4192
4219
|
].join("\n")
|
|
@@ -4239,6 +4266,7 @@ async function cloneCommand(themeName, options) {
|
|
|
4239
4266
|
console.log(
|
|
4240
4267
|
chalk4.cyan(" Source: ") + chalk4.gray(`${themeName}@${version2}`)
|
|
4241
4268
|
);
|
|
4269
|
+
console.log(chalk4.cyan(" Env: ") + chalk4.white(env));
|
|
4242
4270
|
console.log(chalk4.cyan(" Theme: ") + chalk4.white(newName));
|
|
4243
4271
|
console.log(chalk4.cyan(" Location: ") + chalk4.white(outputDir));
|
|
4244
4272
|
console.log(chalk4.cyan(" Files: ") + chalk4.white(entries.length));
|
|
@@ -4515,9 +4543,13 @@ async function configCommand() {
|
|
|
4515
4543
|
|
|
4516
4544
|
// src/commands/login.ts
|
|
4517
4545
|
init_logger();
|
|
4518
|
-
async function loginCommand() {
|
|
4546
|
+
async function loginCommand(options = {}) {
|
|
4547
|
+
const env = options.env ?? "dev";
|
|
4548
|
+
const apiUrl = getApiUrl(env);
|
|
4519
4549
|
logger.header("OneX Theme Developer Login");
|
|
4520
|
-
|
|
4550
|
+
logger.info(`Environment: ${env} (${apiUrl})`);
|
|
4551
|
+
logger.newLine();
|
|
4552
|
+
const existing = loadAuthTokens(env);
|
|
4521
4553
|
if (existing) {
|
|
4522
4554
|
logger.info(`Already logged in as: ${existing.user.email}`);
|
|
4523
4555
|
const { relogin } = await inquirer.prompt([
|
|
@@ -4546,7 +4578,6 @@ async function loginCommand() {
|
|
|
4546
4578
|
]);
|
|
4547
4579
|
logger.startSpinner("Logging in...");
|
|
4548
4580
|
try {
|
|
4549
|
-
const apiUrl = getApiUrl();
|
|
4550
4581
|
const response = await fetch(`${apiUrl}/auth/login`, {
|
|
4551
4582
|
method: "POST",
|
|
4552
4583
|
headers: { "Content-Type": "application/json" },
|
|
@@ -4581,15 +4612,18 @@ async function loginCommand() {
|
|
|
4581
4612
|
userId: claims.sub
|
|
4582
4613
|
}
|
|
4583
4614
|
};
|
|
4584
|
-
await saveAuthTokens(tokens);
|
|
4615
|
+
await saveAuthTokens(tokens, env);
|
|
4585
4616
|
logger.stopSpinner(true, "Logged in!");
|
|
4586
4617
|
logger.newLine();
|
|
4587
|
-
logger.info(`
|
|
4588
|
-
|
|
4618
|
+
logger.info(` Environment: ${env}`);
|
|
4619
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4620
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4589
4621
|
if (tokens.user.companyId)
|
|
4590
|
-
logger.info(` Company:
|
|
4622
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4591
4623
|
logger.newLine();
|
|
4592
|
-
logger.success(
|
|
4624
|
+
logger.success(
|
|
4625
|
+
`Token stored securely in ~/.onexthm/auth-${env}.json (encrypted)`
|
|
4626
|
+
);
|
|
4593
4627
|
} catch (error) {
|
|
4594
4628
|
logger.stopSpinner(false, "Login failed");
|
|
4595
4629
|
logger.error(error instanceof Error ? error.message : "Connection failed");
|
|
@@ -4599,32 +4633,37 @@ async function loginCommand() {
|
|
|
4599
4633
|
|
|
4600
4634
|
// src/commands/logout.ts
|
|
4601
4635
|
init_logger();
|
|
4602
|
-
async function logoutCommand() {
|
|
4603
|
-
const
|
|
4636
|
+
async function logoutCommand(options = {}) {
|
|
4637
|
+
const env = options.env ?? "dev";
|
|
4638
|
+
const tokens = loadAuthTokens(env);
|
|
4604
4639
|
if (!tokens) {
|
|
4605
|
-
logger.info(
|
|
4640
|
+
logger.info(`Not logged in to ${env} environment.`);
|
|
4606
4641
|
return;
|
|
4607
4642
|
}
|
|
4608
|
-
await clearAuthTokens();
|
|
4609
|
-
logger.success(`Logged out (was: ${tokens.user.email})`);
|
|
4643
|
+
await clearAuthTokens(env);
|
|
4644
|
+
logger.success(`Logged out of ${env} (was: ${tokens.user.email})`);
|
|
4610
4645
|
}
|
|
4611
4646
|
|
|
4612
4647
|
// src/commands/whoami.ts
|
|
4613
4648
|
init_logger();
|
|
4614
|
-
async function whoamiCommand() {
|
|
4615
|
-
const
|
|
4649
|
+
async function whoamiCommand(options = {}) {
|
|
4650
|
+
const env = options.env ?? "dev";
|
|
4651
|
+
const tokens = loadAuthTokens(env);
|
|
4616
4652
|
if (!tokens) {
|
|
4617
|
-
logger.error(
|
|
4653
|
+
logger.error(
|
|
4654
|
+
`Not logged in to ${env} environment. Run: onexthm login --env ${env}`
|
|
4655
|
+
);
|
|
4618
4656
|
process.exit(1);
|
|
4619
4657
|
}
|
|
4620
4658
|
const expired = isTokenExpired(tokens);
|
|
4621
4659
|
logger.header("OneX Theme Developer");
|
|
4622
|
-
logger.info(`
|
|
4623
|
-
|
|
4660
|
+
logger.info(` Environment: ${env} (${getApiUrl(env)})`);
|
|
4661
|
+
logger.info(` Email: ${tokens.user.email}`);
|
|
4662
|
+
if (tokens.user.name) logger.info(` Name: ${tokens.user.name}`);
|
|
4624
4663
|
if (tokens.user.companyId)
|
|
4625
|
-
logger.info(` Company:
|
|
4664
|
+
logger.info(` Company: ${tokens.user.companyId}`);
|
|
4626
4665
|
logger.info(
|
|
4627
|
-
` Status:
|
|
4666
|
+
` Status: ${expired ? "\u26A0 Token expired (will auto-refresh)" : "\u2713 Active"}`
|
|
4628
4667
|
);
|
|
4629
4668
|
}
|
|
4630
4669
|
|
|
@@ -4719,10 +4758,15 @@ function buildAssetMap(entries) {
|
|
|
4719
4758
|
|
|
4720
4759
|
// src/commands/publish.ts
|
|
4721
4760
|
async function publishCommand(options) {
|
|
4761
|
+
const env = options.env ?? "dev";
|
|
4722
4762
|
logger.header("OneX Theme Publish");
|
|
4723
|
-
|
|
4763
|
+
logger.info(`Environment: ${env} (${getApiUrl(env)})`);
|
|
4764
|
+
logger.newLine();
|
|
4765
|
+
const tokens = await getValidTokens(env);
|
|
4724
4766
|
if (!tokens) {
|
|
4725
|
-
logger.error(
|
|
4767
|
+
logger.error(
|
|
4768
|
+
`Not logged in to ${env} environment. Run: onexthm login --env ${env}`
|
|
4769
|
+
);
|
|
4726
4770
|
process.exit(1);
|
|
4727
4771
|
}
|
|
4728
4772
|
logger.info(`Logged in as: ${tokens.user.email}`);
|
|
@@ -4773,7 +4817,7 @@ async function publishCommand(options) {
|
|
|
4773
4817
|
logger.info(`Theme: ${themeId}`);
|
|
4774
4818
|
logger.info(`Version: ${version2}`);
|
|
4775
4819
|
logger.newLine();
|
|
4776
|
-
const apiUrl = getApiUrl();
|
|
4820
|
+
const apiUrl = getApiUrl(env);
|
|
4777
4821
|
logger.startSpinner("Registering theme...");
|
|
4778
4822
|
try {
|
|
4779
4823
|
const regResponse = await authenticatedFetch(
|
|
@@ -4790,7 +4834,8 @@ async function publishCommand(options) {
|
|
|
4790
4834
|
tags: pkg.keywords || [],
|
|
4791
4835
|
thumbnail_url: pkg.onex?.thumbnail || ""
|
|
4792
4836
|
})
|
|
4793
|
-
}
|
|
4837
|
+
},
|
|
4838
|
+
env
|
|
4794
4839
|
);
|
|
4795
4840
|
const regData = await regResponse.json();
|
|
4796
4841
|
const regBody = regData.statusCode ? regData.body : regData;
|
|
@@ -4812,7 +4857,8 @@ async function publishCommand(options) {
|
|
|
4812
4857
|
try {
|
|
4813
4858
|
const checkResponse = await authenticatedFetch(
|
|
4814
4859
|
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/exists`,
|
|
4815
|
-
{ method: "GET" }
|
|
4860
|
+
{ method: "GET" },
|
|
4861
|
+
env
|
|
4816
4862
|
);
|
|
4817
4863
|
const checkData = await checkResponse.json();
|
|
4818
4864
|
const checkBody = checkData.statusCode ? checkData.body : checkData;
|
|
@@ -4877,7 +4923,7 @@ Or use the --bump flag:
|
|
|
4877
4923
|
logger.startSpinner(`Uploading ${videoAssets.length} video(s)...`);
|
|
4878
4924
|
try {
|
|
4879
4925
|
for (const video of videoAssets) {
|
|
4880
|
-
const url = await uploadVideoMultipart(apiUrl, themeId, video);
|
|
4926
|
+
const url = await uploadVideoMultipart(apiUrl, themeId, video, env);
|
|
4881
4927
|
videoUrls[video.originalPath] = url;
|
|
4882
4928
|
}
|
|
4883
4929
|
logger.stopSpinner(true, `Uploaded ${videoAssets.length} video(s)`);
|
|
@@ -4919,7 +4965,8 @@ Or use the --bump flag:
|
|
|
4919
4965
|
content_type: a.contentType
|
|
4920
4966
|
}))
|
|
4921
4967
|
})
|
|
4922
|
-
}
|
|
4968
|
+
},
|
|
4969
|
+
env
|
|
4923
4970
|
);
|
|
4924
4971
|
const pubData = await pubResponse.json();
|
|
4925
4972
|
const pubBody = pubData.statusCode ? pubData.body : pubData;
|
|
@@ -4945,6 +4992,12 @@ Or use the --bump flag:
|
|
|
4945
4992
|
}
|
|
4946
4993
|
if (assetUploads.length > 0) {
|
|
4947
4994
|
logger.startSpinner(`Uploading ${assetUploads.length} asset(s) to S3...`);
|
|
4995
|
+
if (assetUploads[0]) {
|
|
4996
|
+
logger.log(
|
|
4997
|
+
` [debug] sample presigned PUT URL: ${assetUploads[0].upload_url}`
|
|
4998
|
+
);
|
|
4999
|
+
logger.log(` [debug] sample s3_key: ${assetUploads[0].s3_key}`);
|
|
5000
|
+
}
|
|
4948
5001
|
const CONCURRENCY = 8;
|
|
4949
5002
|
const byHashedPath = new Map(regularAssets.map((a) => [a.hashedPath, a]));
|
|
4950
5003
|
const queue = [...assetUploads];
|
|
@@ -4971,6 +5024,13 @@ Or use the --bump flag:
|
|
|
4971
5024
|
body: buf
|
|
4972
5025
|
});
|
|
4973
5026
|
if (!res.ok) {
|
|
5027
|
+
if (failed === 0) {
|
|
5028
|
+
const errBody = await res.text().catch(() => "(unreadable)");
|
|
5029
|
+
logger.log(` [debug] PUT ${item.upload_url}`);
|
|
5030
|
+
logger.log(
|
|
5031
|
+
` [debug] response ${res.status} ${res.statusText}: ${errBody.slice(0, 500)}`
|
|
5032
|
+
);
|
|
5033
|
+
}
|
|
4974
5034
|
throw new Error(`HTTP ${res.status}`);
|
|
4975
5035
|
}
|
|
4976
5036
|
uploaded++;
|
|
@@ -5055,7 +5115,8 @@ Or use the --bump flag:
|
|
|
5055
5115
|
try {
|
|
5056
5116
|
const confirmResponse = await authenticatedFetch(
|
|
5057
5117
|
`${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}/versions/${encodeURIComponent(version2)}/confirm`,
|
|
5058
|
-
{ method: "POST" }
|
|
5118
|
+
{ method: "POST" },
|
|
5119
|
+
env
|
|
5059
5120
|
);
|
|
5060
5121
|
const confirmData = await confirmResponse.json();
|
|
5061
5122
|
const confirmBody = confirmData.statusCode ? confirmData.body : confirmData;
|
|
@@ -5101,9 +5162,9 @@ Or use the --bump flag:
|
|
|
5101
5162
|
}
|
|
5102
5163
|
logger.newLine();
|
|
5103
5164
|
logger.success(`\u2713 Theme "${themeId}" v${version2} published!`);
|
|
5104
|
-
await uploadThumbnail(apiUrl, themeId, themePath, distDir);
|
|
5165
|
+
await uploadThumbnail(apiUrl, themeId, themePath, distDir, env);
|
|
5105
5166
|
}
|
|
5106
|
-
async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
|
|
5167
|
+
async function uploadThumbnail(apiUrl, themeId, themePath, distDir, env = "dev") {
|
|
5107
5168
|
const THUMBNAIL_CANDIDATES = [
|
|
5108
5169
|
{ file: "thumbnail.png", mime: "image/png" },
|
|
5109
5170
|
{ file: "thumbnail.jpg", mime: "image/jpeg" },
|
|
@@ -5138,9 +5199,19 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
|
|
|
5138
5199
|
}
|
|
5139
5200
|
}
|
|
5140
5201
|
logger.startSpinner("Uploading thumbnail...");
|
|
5202
|
+
const imageUploadUrl = `${apiUrl}/media/images/upload`;
|
|
5203
|
+
const imageRequestBody = {
|
|
5204
|
+
prefix: `themes/${themeId}`,
|
|
5205
|
+
image: imageBase64 ? `${imageBase64.slice(0, 60)}... [${Math.round(imageBase64.length * 3 / 4 / 1024)} KB]` : null,
|
|
5206
|
+
name: "thumbnail.png"
|
|
5207
|
+
};
|
|
5208
|
+
logger.log(` \u2192 POST ${imageUploadUrl}`);
|
|
5209
|
+
logger.log(
|
|
5210
|
+
` \u2192 body: ${JSON.stringify({ ...imageRequestBody, image: imageBase64 ? `<base64 ${mimeType} truncated>` : null })}`
|
|
5211
|
+
);
|
|
5141
5212
|
try {
|
|
5142
5213
|
const uploadRes = await authenticatedFetch(
|
|
5143
|
-
|
|
5214
|
+
imageUploadUrl,
|
|
5144
5215
|
{
|
|
5145
5216
|
method: "POST",
|
|
5146
5217
|
body: JSON.stringify({
|
|
@@ -5148,30 +5219,56 @@ async function uploadThumbnail(apiUrl, themeId, themePath, distDir) {
|
|
|
5148
5219
|
image: imageBase64,
|
|
5149
5220
|
name: "thumbnail.png"
|
|
5150
5221
|
})
|
|
5151
|
-
}
|
|
5222
|
+
},
|
|
5223
|
+
env
|
|
5152
5224
|
);
|
|
5153
|
-
|
|
5225
|
+
logger.log(` \u2190 HTTP ${uploadRes.status} ${uploadRes.statusText}`);
|
|
5226
|
+
const uploadRawText = await uploadRes.text();
|
|
5227
|
+
logger.log(` \u2190 raw response: ${uploadRawText.slice(0, 500)}`);
|
|
5228
|
+
let uploadData;
|
|
5229
|
+
try {
|
|
5230
|
+
uploadData = JSON.parse(uploadRawText);
|
|
5231
|
+
} catch {
|
|
5232
|
+
throw new Error(
|
|
5233
|
+
`Image upload returned non-JSON (HTTP ${uploadRes.status}): ${uploadRawText.slice(0, 200)}`
|
|
5234
|
+
);
|
|
5235
|
+
}
|
|
5154
5236
|
const uploadBody = uploadData.statusCode ? uploadData.body : uploadData;
|
|
5155
5237
|
if (!uploadRes.ok || !uploadBody.url) {
|
|
5156
|
-
throw new Error(
|
|
5238
|
+
throw new Error(
|
|
5239
|
+
`Image upload failed \u2014 HTTP ${uploadRes.status}: ${uploadBody.error || uploadBody.message || JSON.stringify(uploadBody).slice(0, 300)}`
|
|
5240
|
+
);
|
|
5157
5241
|
}
|
|
5242
|
+
const patchUrl = `${apiUrl}/website-api/themes/${encodeURIComponent(themeId)}`;
|
|
5243
|
+
logger.log(` \u2192 PATCH ${patchUrl}`);
|
|
5158
5244
|
const patchRes = await authenticatedFetch(
|
|
5159
|
-
|
|
5245
|
+
patchUrl,
|
|
5160
5246
|
{
|
|
5161
5247
|
method: "PATCH",
|
|
5162
5248
|
body: JSON.stringify({ thumbnail_url: uploadBody.url })
|
|
5163
|
-
}
|
|
5249
|
+
},
|
|
5250
|
+
env
|
|
5164
5251
|
);
|
|
5252
|
+
logger.log(` \u2190 HTTP ${patchRes.status} ${patchRes.statusText}`);
|
|
5165
5253
|
if (!patchRes.ok) {
|
|
5166
|
-
const
|
|
5167
|
-
|
|
5168
|
-
|
|
5254
|
+
const patchRawText = await patchRes.text();
|
|
5255
|
+
logger.log(` \u2190 raw response: ${patchRawText.slice(0, 500)}`);
|
|
5256
|
+
let patchBody = {};
|
|
5257
|
+
try {
|
|
5258
|
+
patchBody = JSON.parse(patchRawText);
|
|
5259
|
+
} catch {
|
|
5260
|
+
}
|
|
5261
|
+
patchBody = patchBody.statusCode ? patchBody.body : patchBody;
|
|
5262
|
+
throw new Error(
|
|
5263
|
+
`Thumbnail patch failed \u2014 HTTP ${patchRes.status}: ${patchBody.error || patchBody.message || patchRawText.slice(0, 200)}`
|
|
5264
|
+
);
|
|
5169
5265
|
}
|
|
5170
5266
|
logger.stopSpinner(true, "Thumbnail set");
|
|
5171
5267
|
} catch (err) {
|
|
5172
|
-
logger.stopSpinner(false, "Thumbnail upload
|
|
5268
|
+
logger.stopSpinner(false, "Thumbnail upload failed");
|
|
5269
|
+
logger.error(err instanceof Error ? err.message : String(err));
|
|
5173
5270
|
logger.info(
|
|
5174
|
-
|
|
5271
|
+
"Theme published successfully. Thumbnail can be updated later."
|
|
5175
5272
|
);
|
|
5176
5273
|
}
|
|
5177
5274
|
}
|
|
@@ -5235,25 +5332,40 @@ async function findFreePort(start) {
|
|
|
5235
5332
|
srv.on("error", () => resolve(findFreePort(start + 1)));
|
|
5236
5333
|
});
|
|
5237
5334
|
}
|
|
5238
|
-
async function uploadVideoMultipart(apiUrl, themeId, video) {
|
|
5335
|
+
async function uploadVideoMultipart(apiUrl, themeId, video, env = "dev") {
|
|
5239
5336
|
const fileName = path9.basename(video.originalPath);
|
|
5337
|
+
const videoInitUrl = `${apiUrl}/media/videos/multipart/init`;
|
|
5338
|
+
const videoInitBody = {
|
|
5339
|
+
file_name: fileName,
|
|
5340
|
+
content_type: video.contentType,
|
|
5341
|
+
file_size: video.size,
|
|
5342
|
+
prefix: `themes/${themeId}/assets`
|
|
5343
|
+
};
|
|
5344
|
+
logger.log(` \u2192 POST ${videoInitUrl}`);
|
|
5345
|
+
logger.log(` \u2192 body: ${JSON.stringify(videoInitBody)}`);
|
|
5240
5346
|
const initRes = await authenticatedFetch(
|
|
5241
|
-
|
|
5347
|
+
videoInitUrl,
|
|
5242
5348
|
{
|
|
5243
5349
|
method: "POST",
|
|
5244
|
-
body: JSON.stringify(
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
file_size: video.size,
|
|
5248
|
-
prefix: `themes/${themeId}/assets`
|
|
5249
|
-
})
|
|
5250
|
-
}
|
|
5350
|
+
body: JSON.stringify(videoInitBody)
|
|
5351
|
+
},
|
|
5352
|
+
env
|
|
5251
5353
|
);
|
|
5252
|
-
|
|
5354
|
+
logger.log(` \u2190 HTTP ${initRes.status} ${initRes.statusText}`);
|
|
5355
|
+
const initRawText = await initRes.text();
|
|
5356
|
+
logger.log(` \u2190 raw response: ${initRawText.slice(0, 500)}`);
|
|
5357
|
+
let initData;
|
|
5358
|
+
try {
|
|
5359
|
+
initData = JSON.parse(initRawText);
|
|
5360
|
+
} catch {
|
|
5361
|
+
throw new Error(
|
|
5362
|
+
`Video init returned non-JSON (HTTP ${initRes.status}): ${initRawText.slice(0, 200)}`
|
|
5363
|
+
);
|
|
5364
|
+
}
|
|
5253
5365
|
const initBody = initData.statusCode ? initData.body : initData;
|
|
5254
5366
|
if (!initRes.ok || !initBody.upload_id) {
|
|
5255
5367
|
throw new Error(
|
|
5256
|
-
`Init multipart failed for ${fileName}: ${initBody.error ||
|
|
5368
|
+
`Init multipart failed for ${fileName} \u2014 HTTP ${initRes.status}: ${initBody.error || initBody.message || JSON.stringify(initBody).slice(0, 300)}`
|
|
5257
5369
|
);
|
|
5258
5370
|
}
|
|
5259
5371
|
const { upload_id, file_key, chunk_size, chunk_urls } = initBody;
|
|
@@ -5288,25 +5400,47 @@ async function uploadVideoMultipart(apiUrl, themeId, video) {
|
|
|
5288
5400
|
)
|
|
5289
5401
|
);
|
|
5290
5402
|
parts.sort((a, b) => a.part_number - b.part_number);
|
|
5403
|
+
const videoCompleteUrl = `${apiUrl}/media/videos/multipart/complete`;
|
|
5404
|
+
logger.log(` \u2192 POST ${videoCompleteUrl}`);
|
|
5405
|
+
logger.log(
|
|
5406
|
+
` \u2192 body: ${JSON.stringify({ upload_id, file_key, parts: `[${parts.length} parts]` })}`
|
|
5407
|
+
);
|
|
5291
5408
|
const completeRes = await authenticatedFetch(
|
|
5292
|
-
|
|
5409
|
+
videoCompleteUrl,
|
|
5293
5410
|
{
|
|
5294
5411
|
method: "POST",
|
|
5295
5412
|
body: JSON.stringify({ upload_id, file_key, parts })
|
|
5296
|
-
}
|
|
5413
|
+
},
|
|
5414
|
+
env
|
|
5297
5415
|
);
|
|
5298
|
-
|
|
5416
|
+
logger.log(` \u2190 HTTP ${completeRes.status} ${completeRes.statusText}`);
|
|
5417
|
+
const completeRawText = await completeRes.text();
|
|
5418
|
+
logger.log(` \u2190 raw response: ${completeRawText.slice(0, 500)}`);
|
|
5419
|
+
let completeData;
|
|
5420
|
+
try {
|
|
5421
|
+
completeData = JSON.parse(completeRawText);
|
|
5422
|
+
} catch {
|
|
5423
|
+
throw new Error(
|
|
5424
|
+
`Video complete returned non-JSON (HTTP ${completeRes.status}): ${completeRawText.slice(0, 200)}`
|
|
5425
|
+
);
|
|
5426
|
+
}
|
|
5299
5427
|
const completeBody = completeData.statusCode ? completeData.body : completeData;
|
|
5300
5428
|
if (!completeRes.ok || !completeBody.url) {
|
|
5301
5429
|
try {
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5430
|
+
const abortUrl = `${apiUrl}/media/videos/multipart/abort`;
|
|
5431
|
+
logger.log(` \u2192 POST ${abortUrl} (cleanup)`);
|
|
5432
|
+
await authenticatedFetch(
|
|
5433
|
+
abortUrl,
|
|
5434
|
+
{
|
|
5435
|
+
method: "POST",
|
|
5436
|
+
body: JSON.stringify({ upload_id, file_key })
|
|
5437
|
+
},
|
|
5438
|
+
env
|
|
5439
|
+
);
|
|
5306
5440
|
} catch {
|
|
5307
5441
|
}
|
|
5308
5442
|
throw new Error(
|
|
5309
|
-
`Complete multipart failed for ${fileName}: ${completeBody.error ||
|
|
5443
|
+
`Complete multipart failed for ${fileName} \u2014 HTTP ${completeRes.status}: ${completeBody.error || completeBody.message || JSON.stringify(completeBody).slice(0, 300)}`
|
|
5310
5444
|
);
|
|
5311
5445
|
}
|
|
5312
5446
|
return completeBody.url;
|
|
@@ -5569,7 +5703,11 @@ program.command("init").description("Create a new OneX theme project").argument(
|
|
|
5569
5703
|
"-t, --template <template>",
|
|
5570
5704
|
"Template to use (default, minimal)",
|
|
5571
5705
|
"default"
|
|
5572
|
-
).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").
|
|
5706
|
+
).option("--no-install", "Skip installing dependencies").option("--git", "Initialize git repository").option("-y, --yes", "Skip prompts and use defaults").option(
|
|
5707
|
+
"--env <env>",
|
|
5708
|
+
"Target environment: dev or prod (default: dev)",
|
|
5709
|
+
"dev"
|
|
5710
|
+
).action(initCommand);
|
|
5573
5711
|
program.command("create:section").alias("cs").description("Create a new section").argument("<name>", "Name of the section (e.g., hero, features)").option("-t, --theme <theme>", "Theme to create section in").option(
|
|
5574
5712
|
"-c, --category <category>",
|
|
5575
5713
|
"Section category (headers, content, footers)"
|
|
@@ -5596,16 +5734,36 @@ program.command("download").description("Download a published theme via the webs
|
|
|
5596
5734
|
"-v, --version <version>",
|
|
5597
5735
|
"Theme version (default: latest)",
|
|
5598
5736
|
"latest"
|
|
5599
|
-
).option(
|
|
5737
|
+
).option(
|
|
5738
|
+
"--env <env>",
|
|
5739
|
+
"Target environment: dev or prod (default: dev)",
|
|
5740
|
+
"dev"
|
|
5741
|
+
).option("-b, --bucket <name>", "[deprecated] ignored").option("-o, --output <dir>", "Output directory", "./active-theme").action(downloadCommand);
|
|
5600
5742
|
program.command("clone").description("Clone theme source code via the website-api").argument("<theme-name>", "Theme to clone").option(
|
|
5601
5743
|
"-v, --version <version>",
|
|
5602
5744
|
"Theme version (default: latest)",
|
|
5603
5745
|
"latest"
|
|
5604
|
-
).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
|
|
5746
|
+
).option("-n, --name <name>", "New theme name (skips interactive prompt)").option("-o, --output <dir>", "Output directory").option(
|
|
5747
|
+
"--env <env>",
|
|
5748
|
+
"Target environment: dev or prod (default: dev)",
|
|
5749
|
+
"dev"
|
|
5750
|
+
).option("-b, --bucket <name>", "[deprecated] ignored").option("--no-install", "Skip running pnpm install after clone").action(cloneCommand);
|
|
5605
5751
|
program.command("config").description("Configure OneX CLI credentials (AWS, API keys)").action(configCommand);
|
|
5606
|
-
program.command("login").description("Login to OneX platform").
|
|
5607
|
-
|
|
5608
|
-
|
|
5752
|
+
program.command("login").description("Login to OneX platform").option(
|
|
5753
|
+
"--env <env>",
|
|
5754
|
+
"Target environment: dev or prod (default: dev)",
|
|
5755
|
+
"dev"
|
|
5756
|
+
).action(loginCommand);
|
|
5757
|
+
program.command("logout").description("Logout from OneX platform").option(
|
|
5758
|
+
"--env <env>",
|
|
5759
|
+
"Target environment: dev or prod (default: dev)",
|
|
5760
|
+
"dev"
|
|
5761
|
+
).action(logoutCommand);
|
|
5762
|
+
program.command("whoami").description("Show current logged-in developer").option(
|
|
5763
|
+
"--env <env>",
|
|
5764
|
+
"Target environment: dev or prod (default: dev)",
|
|
5765
|
+
"dev"
|
|
5766
|
+
).action(whoamiCommand);
|
|
5609
5767
|
var mcpCmd = program.command("mcp").description("Manage MCP server registration and AI-context files");
|
|
5610
5768
|
mcpCmd.command("setup").description(
|
|
5611
5769
|
"Install .mcp.json + CLAUDE.md + AGENTS.md + .cursorrules into the current theme"
|
|
@@ -5617,6 +5775,10 @@ mcpCmd.command("doctor").description("Diagnose MCP setup in the current theme di
|
|
|
5617
5775
|
program.command("publish").description("Build, scan, and publish theme to marketplace (requires login)").option("-t, --theme <path>", "Theme directory path").option(
|
|
5618
5776
|
"--bump <type>",
|
|
5619
5777
|
"Auto-bump version before publish (patch|minor|major)"
|
|
5778
|
+
).option(
|
|
5779
|
+
"--env <env>",
|
|
5780
|
+
"Target environment: dev or prod (default: dev)",
|
|
5781
|
+
"dev"
|
|
5620
5782
|
).action(publishCommand);
|
|
5621
5783
|
program.configureOutput({
|
|
5622
5784
|
writeErr: (str) => process.stderr.write(chalk4.red(str))
|