@rodyssey/cli 0.1.5 → 0.1.6
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 +20 -0
- package/dist/cli.js +460 -191
- package/package.json +14 -2
package/README.md
CHANGED
|
@@ -12,4 +12,24 @@ To run:
|
|
|
12
12
|
bun run index.ts
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
## Release
|
|
16
|
+
|
|
17
|
+
Release automation lives in `.github/workflows/release.yml` and uses Changesets.
|
|
18
|
+
|
|
19
|
+
For a change that should publish a new CLI version:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bun run changeset
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Choose `patch`, `minor`, or `major`, commit the generated `.changeset/*.md` file, and merge to `main`. The workflow opens or updates a release PR with the version bump and changelog. Merging that release PR publishes `@rodyssey/cli` to npm.
|
|
26
|
+
|
|
27
|
+
Configure npm trusted publishing for the package with:
|
|
28
|
+
|
|
29
|
+
- Organization/user: `airconcepts`
|
|
30
|
+
- Repository: `ro-cli`
|
|
31
|
+
- Workflow filename: `release.yml`
|
|
32
|
+
|
|
33
|
+
This repository is private, so npm provenance is disabled. If the repository becomes public later, change `publishConfig.provenance` and `NPM_CONFIG_PROVENANCE` back to `true`.
|
|
34
|
+
|
|
15
35
|
This project was created using `bun init` in bun v1.3.9. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/dist/cli.js
CHANGED
|
@@ -2068,6 +2068,47 @@ var {
|
|
|
2068
2068
|
Option,
|
|
2069
2069
|
Help
|
|
2070
2070
|
} = import__.default;
|
|
2071
|
+
// package.json
|
|
2072
|
+
var package_default = {
|
|
2073
|
+
name: "@rodyssey/cli",
|
|
2074
|
+
version: "0.1.6",
|
|
2075
|
+
description: "Scaffold new projects from airconcepts templates",
|
|
2076
|
+
repository: {
|
|
2077
|
+
type: "git",
|
|
2078
|
+
url: "git+https://github.com/airconcepts/ro-cli.git"
|
|
2079
|
+
},
|
|
2080
|
+
bin: {
|
|
2081
|
+
"@rodyssey/cli": "dist/cli.js"
|
|
2082
|
+
},
|
|
2083
|
+
files: [
|
|
2084
|
+
"dist"
|
|
2085
|
+
],
|
|
2086
|
+
type: "module",
|
|
2087
|
+
module: "index.ts",
|
|
2088
|
+
scripts: {
|
|
2089
|
+
build: "bun build src/cli.ts --outdir dist --target node",
|
|
2090
|
+
start: "bun dist/cli.js",
|
|
2091
|
+
changeset: "changeset",
|
|
2092
|
+
"version-packages": "changeset version",
|
|
2093
|
+
release: "bun run build && changeset publish",
|
|
2094
|
+
prepublishOnly: "bun run build"
|
|
2095
|
+
},
|
|
2096
|
+
publishConfig: {
|
|
2097
|
+
access: "public",
|
|
2098
|
+
provenance: false
|
|
2099
|
+
},
|
|
2100
|
+
dependencies: {
|
|
2101
|
+
commander: "^13.1.0",
|
|
2102
|
+
mime: "^4.1.0"
|
|
2103
|
+
},
|
|
2104
|
+
devDependencies: {
|
|
2105
|
+
"@changesets/cli": "^2.30.0",
|
|
2106
|
+
"@types/bun": "latest"
|
|
2107
|
+
},
|
|
2108
|
+
peerDependencies: {
|
|
2109
|
+
typescript: "^5"
|
|
2110
|
+
}
|
|
2111
|
+
};
|
|
2071
2112
|
|
|
2072
2113
|
// src/auth.ts
|
|
2073
2114
|
import { spawn } from "node:child_process";
|
|
@@ -2262,7 +2303,9 @@ ${JSON.stringify(payload, null, 2)}`);
|
|
|
2262
2303
|
token,
|
|
2263
2304
|
user: extractUser(payload)
|
|
2264
2305
|
};
|
|
2265
|
-
|
|
2306
|
+
if (options.persist !== false) {
|
|
2307
|
+
storeSession(session);
|
|
2308
|
+
}
|
|
2266
2309
|
return session;
|
|
2267
2310
|
}
|
|
2268
2311
|
async function me(options) {
|
|
@@ -2296,12 +2339,29 @@ ${JSON.stringify(payload, null, 2)}`);
|
|
|
2296
2339
|
|
|
2297
2340
|
// src/create.ts
|
|
2298
2341
|
import { execSync } from "node:child_process";
|
|
2299
|
-
import { existsSync as existsSync3,
|
|
2342
|
+
import { existsSync as existsSync3, rmSync } from "node:fs";
|
|
2300
2343
|
import path2 from "node:path";
|
|
2301
2344
|
|
|
2302
2345
|
// src/utils.ts
|
|
2303
2346
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
2304
2347
|
import path from "node:path";
|
|
2348
|
+
function upsertEnvValue(content, key, value) {
|
|
2349
|
+
const line = `${key}=${value}`;
|
|
2350
|
+
const pattern = new RegExp(`^${key}=.*$`, "m");
|
|
2351
|
+
if (pattern.test(content))
|
|
2352
|
+
return content.replace(pattern, line);
|
|
2353
|
+
return `${content}${content.endsWith(`
|
|
2354
|
+
`) || content.length === 0 ? "" : `
|
|
2355
|
+
`}${line}
|
|
2356
|
+
`;
|
|
2357
|
+
}
|
|
2358
|
+
function upsertEnvFile(filePath, entries) {
|
|
2359
|
+
let content = existsSync2(filePath) ? readFileSync2(filePath, "utf-8") : "";
|
|
2360
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
2361
|
+
content = upsertEnvValue(content, key, value);
|
|
2362
|
+
}
|
|
2363
|
+
writeFileSync2(filePath, content, "utf-8");
|
|
2364
|
+
}
|
|
2305
2365
|
function replaceInFile(filePath, search, replace) {
|
|
2306
2366
|
const content = readFileSync2(filePath, "utf-8");
|
|
2307
2367
|
const updated = content.replaceAll(search, replace);
|
|
@@ -2374,22 +2434,11 @@ function extractDeployToken(payload) {
|
|
|
2374
2434
|
const webapp = nestedObject(payload, "webapp");
|
|
2375
2435
|
return pickString(webapp?.deployToken, webapp?.deploymentToken);
|
|
2376
2436
|
}
|
|
2377
|
-
function upsertEnvValue(content, key, value) {
|
|
2378
|
-
const line = `${key}=${value}`;
|
|
2379
|
-
const pattern = new RegExp(`^${key}=.*$`, "m");
|
|
2380
|
-
if (pattern.test(content))
|
|
2381
|
-
return content.replace(pattern, line);
|
|
2382
|
-
return `${content}${content.endsWith(`
|
|
2383
|
-
`) || content.length === 0 ? "" : `
|
|
2384
|
-
`}${line}
|
|
2385
|
-
`;
|
|
2386
|
-
}
|
|
2387
2437
|
function writeProjectEnv(projectDir, provisioned) {
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
writeFileSync3(envFile, content, "utf-8");
|
|
2438
|
+
upsertEnvFile(path2.join(projectDir, ".env"), {
|
|
2439
|
+
WEBAPP_ID: provisioned.webappId,
|
|
2440
|
+
DEPLOY_TOKEN: provisioned.deployToken
|
|
2441
|
+
});
|
|
2393
2442
|
}
|
|
2394
2443
|
async function provisionWebapp(projectName, options) {
|
|
2395
2444
|
const cmsUrl = resolveCmsUrl(options.env, options.cmsUrl);
|
|
@@ -2481,7 +2530,7 @@ async function create(projectName, repoUrl, templateName, autoCreate) {
|
|
|
2481
2530
|
|
|
2482
2531
|
// src/deploy.ts
|
|
2483
2532
|
import { execSync as execSync2 } from "node:child_process";
|
|
2484
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2533
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2485
2534
|
import { join as join2 } from "node:path";
|
|
2486
2535
|
|
|
2487
2536
|
// node_modules/mime/dist/types/other.js
|
|
@@ -3693,7 +3742,7 @@ function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
|
3693
3742
|
return arrayOfFiles;
|
|
3694
3743
|
}
|
|
3695
3744
|
function fileToBlob(filePath) {
|
|
3696
|
-
const buffer =
|
|
3745
|
+
const buffer = readFileSync3(filePath);
|
|
3697
3746
|
return new Blob([buffer], { type: src_default.getType(filePath) || "application/octet-stream" });
|
|
3698
3747
|
}
|
|
3699
3748
|
async function deploy(env = "development", overrides = {}) {
|
|
@@ -3780,7 +3829,7 @@ ${errorText}`);
|
|
|
3780
3829
|
console.log(`\uD83D\uDCDC Step 3: Setting up ${scriptFiles.length} scripts (APIs & Crons)...`);
|
|
3781
3830
|
const scriptsPayload = { api: {}, cron: {}, cronConfig: null };
|
|
3782
3831
|
for (const f of scriptFiles) {
|
|
3783
|
-
const content =
|
|
3832
|
+
const content = readFileSync3(f, "utf-8");
|
|
3784
3833
|
const relativePath = f.substring(BUILD_DIR.length + 1).replace(/\\/g, "/");
|
|
3785
3834
|
if (relativePath === "cron-jobs/cron.config.json") {
|
|
3786
3835
|
scriptsPayload.cronConfig = JSON.parse(content);
|
|
@@ -3820,7 +3869,7 @@ ${errorText}`);
|
|
|
3820
3869
|
console.log(`✅ Created ${ZIP_FILE}
|
|
3821
3870
|
`);
|
|
3822
3871
|
console.log("☁️ Step 5: Deploying HTML zip to server...");
|
|
3823
|
-
const zipBuffer =
|
|
3872
|
+
const zipBuffer = readFileSync3(ZIP_FILE);
|
|
3824
3873
|
try {
|
|
3825
3874
|
const response = await fetch(DEPLOY_URL, {
|
|
3826
3875
|
method: "POST",
|
|
@@ -3853,20 +3902,387 @@ ${errorText}`);
|
|
|
3853
3902
|
✨ Deployment successful!`);
|
|
3854
3903
|
}
|
|
3855
3904
|
|
|
3905
|
+
// src/promote.ts
|
|
3906
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
3907
|
+
import { resolve as resolve2 } from "node:path";
|
|
3908
|
+
|
|
3909
|
+
// src/update-webapp-config.ts
|
|
3910
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
3911
|
+
import { resolve } from "node:path";
|
|
3912
|
+
var CONFIG_URLS = {
|
|
3913
|
+
local: "http://localhost:5176/api/webapps/config",
|
|
3914
|
+
development: "https://development-cms.rodyssey.ai/api/webapps/config",
|
|
3915
|
+
staging: "https://staging-cms.rodyssey.ai/api/webapps/config",
|
|
3916
|
+
production: "https://cms.rodyssey.ai/api/webapps/config"
|
|
3917
|
+
};
|
|
3918
|
+
function parseJsonOption(value, optionName) {
|
|
3919
|
+
const maybePath = resolve(process.cwd(), value);
|
|
3920
|
+
const raw = existsSync5(maybePath) ? readFileSync4(maybePath, "utf-8") : value;
|
|
3921
|
+
try {
|
|
3922
|
+
const parsed = JSON.parse(raw);
|
|
3923
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3924
|
+
throw new Error("value must be a JSON object");
|
|
3925
|
+
}
|
|
3926
|
+
return parsed;
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3929
|
+
throw new Error(`Invalid ${optionName}: ${message}`);
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
function coerceMaybeNull(value) {
|
|
3933
|
+
if (value === undefined)
|
|
3934
|
+
return;
|
|
3935
|
+
if (value === "null")
|
|
3936
|
+
return null;
|
|
3937
|
+
return value;
|
|
3938
|
+
}
|
|
3939
|
+
function resolveConfigUrl(options, required = true) {
|
|
3940
|
+
const configUrl = options.url || process.env.WEBAPP_CONFIG_URL || CONFIG_URLS[options.env];
|
|
3941
|
+
if (!configUrl) {
|
|
3942
|
+
if (!required)
|
|
3943
|
+
return;
|
|
3944
|
+
console.error("❌ Error: no webapp config endpoint configured.");
|
|
3945
|
+
console.error(`\uD83D\uDCA1 Use one of these environments: ${Object.keys(CONFIG_URLS).join(", ")}, pass --url, or set WEBAPP_CONFIG_URL in your .env file.`);
|
|
3946
|
+
process.exit(1);
|
|
3947
|
+
}
|
|
3948
|
+
const url = new URL(configUrl);
|
|
3949
|
+
if (!options.host && !options.port) {
|
|
3950
|
+
return url.toString().replace(/\/$/, "");
|
|
3951
|
+
}
|
|
3952
|
+
if (options.host)
|
|
3953
|
+
url.hostname = options.host;
|
|
3954
|
+
if (options.port)
|
|
3955
|
+
url.port = String(options.port);
|
|
3956
|
+
return url.toString().replace(/\/$/, "");
|
|
3957
|
+
}
|
|
3958
|
+
function buildDetailsPayload(options) {
|
|
3959
|
+
const payload = options.details ? parseJsonOption(options.details, "--details") : {};
|
|
3960
|
+
const title = coerceMaybeNull(options.title);
|
|
3961
|
+
const description = coerceMaybeNull(options.description);
|
|
3962
|
+
const coverImg = coerceMaybeNull(options.coverImg);
|
|
3963
|
+
if (title !== undefined)
|
|
3964
|
+
payload.title = title;
|
|
3965
|
+
if (description !== undefined)
|
|
3966
|
+
payload.description = description;
|
|
3967
|
+
if (coverImg !== undefined)
|
|
3968
|
+
payload.coverImg = coverImg;
|
|
3969
|
+
if (options.localization !== undefined) {
|
|
3970
|
+
payload.localization = options.localization === "null" ? null : parseJsonOption(options.localization, "--localization");
|
|
3971
|
+
}
|
|
3972
|
+
return payload;
|
|
3973
|
+
}
|
|
3974
|
+
function resolveWebappId(webappId) {
|
|
3975
|
+
const resolved = webappId || process.env.WEBAPP_ID;
|
|
3976
|
+
if (!resolved) {
|
|
3977
|
+
console.error("❌ Error: WEBAPP_ID is not set. Pass --webapp-id or set it in your .env file.");
|
|
3978
|
+
process.exit(1);
|
|
3979
|
+
}
|
|
3980
|
+
return resolved;
|
|
3981
|
+
}
|
|
3982
|
+
function ensureDeployToken(env) {
|
|
3983
|
+
if (process.env.DEPLOY_TOKEN)
|
|
3984
|
+
return;
|
|
3985
|
+
console.error("❌ Error: DEPLOY_TOKEN is not set in environment variables.");
|
|
3986
|
+
console.info(`\uD83D\uDCA1 Please check your .env or .env.${env} file.`);
|
|
3987
|
+
process.exit(1);
|
|
3988
|
+
}
|
|
3989
|
+
function getConfigUrl(options, required = true) {
|
|
3990
|
+
return resolveConfigUrl(options, required);
|
|
3991
|
+
}
|
|
3992
|
+
async function fetchWebappConfig(options) {
|
|
3993
|
+
loadEnv(options.env);
|
|
3994
|
+
const webappId = resolveWebappId(options.webappId);
|
|
3995
|
+
const CONFIG_URL = getConfigUrl(options);
|
|
3996
|
+
if (!CONFIG_URL) {
|
|
3997
|
+
throw new Error("No webapp config endpoint configured.");
|
|
3998
|
+
}
|
|
3999
|
+
ensureDeployToken(options.env);
|
|
4000
|
+
const url = new URL(CONFIG_URL);
|
|
4001
|
+
url.searchParams.set("webappId", webappId);
|
|
4002
|
+
const response = await fetch(url, {
|
|
4003
|
+
method: "GET",
|
|
4004
|
+
headers: {
|
|
4005
|
+
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
4006
|
+
Accept: "application/json"
|
|
4007
|
+
}
|
|
4008
|
+
});
|
|
4009
|
+
if (!response.ok) {
|
|
4010
|
+
const errorText = await response.text();
|
|
4011
|
+
throw new Error(`Config fetch failed: ${response.status} ${response.statusText}
|
|
4012
|
+
${errorText}`);
|
|
4013
|
+
}
|
|
4014
|
+
return await response.json();
|
|
4015
|
+
}
|
|
4016
|
+
async function getWebappConfig(options) {
|
|
4017
|
+
loadEnv(options.env);
|
|
4018
|
+
const webappId = resolveWebappId(options.webappId);
|
|
4019
|
+
const CONFIG_URL = getConfigUrl(options);
|
|
4020
|
+
if (!CONFIG_URL)
|
|
4021
|
+
return;
|
|
4022
|
+
ensureDeployToken(options.env);
|
|
4023
|
+
const url = new URL(CONFIG_URL);
|
|
4024
|
+
url.searchParams.set("webappId", webappId);
|
|
4025
|
+
console.log(`⚙️ Pulling webapp config for [${options.env}] environment...`);
|
|
4026
|
+
console.log(`\uD83D\uDCCD Config URL: ${url.toString()}`);
|
|
4027
|
+
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}
|
|
4028
|
+
`);
|
|
4029
|
+
const response = await fetch(url, {
|
|
4030
|
+
method: "GET",
|
|
4031
|
+
headers: {
|
|
4032
|
+
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
4033
|
+
Accept: "application/json"
|
|
4034
|
+
}
|
|
4035
|
+
});
|
|
4036
|
+
if (!response.ok) {
|
|
4037
|
+
const errorText = await response.text();
|
|
4038
|
+
throw new Error(`Config fetch failed: ${response.status} ${response.statusText}
|
|
4039
|
+
${errorText}`);
|
|
4040
|
+
}
|
|
4041
|
+
const config = await response.json();
|
|
4042
|
+
const output = JSON.stringify(config, null, 2);
|
|
4043
|
+
if (options.out) {
|
|
4044
|
+
writeFileSync3(options.out, `${output}
|
|
4045
|
+
`, "utf-8");
|
|
4046
|
+
console.log(`✅ Webapp config written to ${options.out}`);
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
console.log(output);
|
|
4050
|
+
}
|
|
4051
|
+
async function updateWebappConfig(options) {
|
|
4052
|
+
loadEnv(options.env);
|
|
4053
|
+
const webappId = resolveWebappId(options.webappId);
|
|
4054
|
+
const details = buildDetailsPayload(options);
|
|
4055
|
+
if (Object.keys(details).length === 0) {
|
|
4056
|
+
console.error("❌ Error: no detail fields provided. Use --title, --description, --cover-img, --localization, or --details.");
|
|
4057
|
+
process.exit(1);
|
|
4058
|
+
}
|
|
4059
|
+
const payload = { webappId, details };
|
|
4060
|
+
const CONFIG_URL = getConfigUrl(options, !options.dryRun);
|
|
4061
|
+
if (!options.dryRun)
|
|
4062
|
+
ensureDeployToken(options.env);
|
|
4063
|
+
console.log(`⚙️ Updating webapp config for [${options.env}] environment...`);
|
|
4064
|
+
if (CONFIG_URL) {
|
|
4065
|
+
console.log(`\uD83D\uDCCD Config URL: ${CONFIG_URL}`);
|
|
4066
|
+
}
|
|
4067
|
+
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}
|
|
4068
|
+
`);
|
|
4069
|
+
if (options.dryRun) {
|
|
4070
|
+
console.log("\uD83E\uDDEA Dry run payload:");
|
|
4071
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
4074
|
+
if (!CONFIG_URL)
|
|
4075
|
+
return;
|
|
4076
|
+
const response = await fetch(CONFIG_URL, {
|
|
4077
|
+
method: "PATCH",
|
|
4078
|
+
headers: {
|
|
4079
|
+
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
4080
|
+
"Content-Type": "application/json"
|
|
4081
|
+
},
|
|
4082
|
+
body: JSON.stringify(payload)
|
|
4083
|
+
});
|
|
4084
|
+
if (!response.ok) {
|
|
4085
|
+
const errorText = await response.text();
|
|
4086
|
+
throw new Error(`Config update failed: ${response.status} ${response.statusText}
|
|
4087
|
+
${errorText}`);
|
|
4088
|
+
}
|
|
4089
|
+
const result = await response.json().catch(() => {
|
|
4090
|
+
return;
|
|
4091
|
+
});
|
|
4092
|
+
console.log("✅ Webapp config updated");
|
|
4093
|
+
if (result !== undefined) {
|
|
4094
|
+
console.log(`
|
|
4095
|
+
\uD83D\uDCCB Update result:`, result);
|
|
4096
|
+
}
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
// src/promote.ts
|
|
4100
|
+
var DEFAULT_SOURCE_ENV = "development";
|
|
4101
|
+
var PROD_ENV = "production";
|
|
4102
|
+
var PROD_ENV_FILE = ".env.production";
|
|
4103
|
+
function isObject3(value) {
|
|
4104
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
4105
|
+
}
|
|
4106
|
+
function pickString2(...values) {
|
|
4107
|
+
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
4108
|
+
}
|
|
4109
|
+
function nestedObject2(value, key) {
|
|
4110
|
+
if (!isObject3(value))
|
|
4111
|
+
return;
|
|
4112
|
+
const nested = value[key];
|
|
4113
|
+
return isObject3(nested) ? nested : undefined;
|
|
4114
|
+
}
|
|
4115
|
+
function extractDeployToken2(payload) {
|
|
4116
|
+
const webapp = nestedObject2(payload, "webapp");
|
|
4117
|
+
return pickString2(webapp?.deployToken, webapp?.deploymentToken);
|
|
4118
|
+
}
|
|
4119
|
+
function unwrapSourceDetails(payload) {
|
|
4120
|
+
if (!isObject3(payload))
|
|
4121
|
+
return {};
|
|
4122
|
+
const inner = payload.details;
|
|
4123
|
+
if (isObject3(inner))
|
|
4124
|
+
return inner;
|
|
4125
|
+
return payload;
|
|
4126
|
+
}
|
|
4127
|
+
function parseDetailsOption(value) {
|
|
4128
|
+
const maybePath = resolve2(process.cwd(), value);
|
|
4129
|
+
const raw = existsSync6(maybePath) ? readFileSync5(maybePath, "utf-8") : value;
|
|
4130
|
+
try {
|
|
4131
|
+
const parsed = JSON.parse(raw);
|
|
4132
|
+
if (!isObject3(parsed)) {
|
|
4133
|
+
throw new Error("value must be a JSON object");
|
|
4134
|
+
}
|
|
4135
|
+
return parsed;
|
|
4136
|
+
} catch (error) {
|
|
4137
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4138
|
+
throw new Error(`Invalid --details: ${message}`);
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
async function prompt(question) {
|
|
4142
|
+
const readline = await import("node:readline");
|
|
4143
|
+
const rl = readline.createInterface({
|
|
4144
|
+
input: process.stdin,
|
|
4145
|
+
output: process.stdout
|
|
4146
|
+
});
|
|
4147
|
+
return new Promise((resolveAnswer) => {
|
|
4148
|
+
rl.question(question, (answer) => {
|
|
4149
|
+
rl.close();
|
|
4150
|
+
resolveAnswer(answer);
|
|
4151
|
+
});
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
function isAffirmative(answer) {
|
|
4155
|
+
const trimmed = answer.trim().toLowerCase();
|
|
4156
|
+
return trimmed === "" || trimmed === "y" || trimmed === "yes";
|
|
4157
|
+
}
|
|
4158
|
+
async function promote(options) {
|
|
4159
|
+
loadEnv();
|
|
4160
|
+
const webappId = process.env.WEBAPP_ID;
|
|
4161
|
+
if (!webappId) {
|
|
4162
|
+
console.error("❌ Error: WEBAPP_ID is not set in .env.");
|
|
4163
|
+
console.info("\uD83D\uDCA1 Run `ro app create --auto` first, or set WEBAPP_ID manually.");
|
|
4164
|
+
process.exit(1);
|
|
4165
|
+
}
|
|
4166
|
+
const prodEnvPath = resolve2(process.cwd(), PROD_ENV_FILE);
|
|
4167
|
+
if (existsSync6(prodEnvPath)) {
|
|
4168
|
+
const content = readFileSync5(prodEnvPath, "utf-8");
|
|
4169
|
+
if (/^WEBAPP_ID=.+/m.test(content)) {
|
|
4170
|
+
console.error(`❌ Error: ${PROD_ENV_FILE} already has WEBAPP_ID. The app appears to be promoted already.`);
|
|
4171
|
+
process.exit(1);
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
const sourceEnv = options.from || DEFAULT_SOURCE_ENV;
|
|
4175
|
+
let details;
|
|
4176
|
+
if (options.details) {
|
|
4177
|
+
details = parseDetailsOption(options.details);
|
|
4178
|
+
console.log("\uD83D\uDCE6 Using details from --details flag.");
|
|
4179
|
+
} else {
|
|
4180
|
+
console.log(`\uD83D\uDCE5 Fetching latest details from [${sourceEnv}]...`);
|
|
4181
|
+
const sourceResponse = await fetchWebappConfig({ env: sourceEnv, webappId });
|
|
4182
|
+
const sourceDetails = unwrapSourceDetails(sourceResponse);
|
|
4183
|
+
console.log(`
|
|
4184
|
+
\uD83D\uDCCB Source details:`);
|
|
4185
|
+
console.log(JSON.stringify(sourceDetails, null, 2));
|
|
4186
|
+
console.log();
|
|
4187
|
+
if (options.yes) {
|
|
4188
|
+
console.log("✓ --yes flag set, using server details.");
|
|
4189
|
+
details = sourceDetails;
|
|
4190
|
+
} else {
|
|
4191
|
+
const answer = await prompt("Pull these details from server? (Y/n): ");
|
|
4192
|
+
if (isAffirmative(answer)) {
|
|
4193
|
+
details = sourceDetails;
|
|
4194
|
+
} else {
|
|
4195
|
+
console.error("❌ Aborted. Re-run with --details <json|file> to provide custom details.");
|
|
4196
|
+
process.exit(1);
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
console.log(`
|
|
4201
|
+
\uD83D\uDD10 Logging in to ${PROD_ENV} CMS (ephemeral, not stored)...`);
|
|
4202
|
+
const session = await login({
|
|
4203
|
+
env: PROD_ENV,
|
|
4204
|
+
cmsUrl: options.cmsUrl,
|
|
4205
|
+
persist: false
|
|
4206
|
+
});
|
|
4207
|
+
console.log(`✅ Logged in to ${session.cmsUrl}`);
|
|
4208
|
+
const cmsUrl = resolveCmsUrl(PROD_ENV, options.cmsUrl);
|
|
4209
|
+
const promoteUrl = options.promoteUrl || `${cmsUrl}/api/cli/webapps/promote`;
|
|
4210
|
+
console.log(`
|
|
4211
|
+
\uD83D\uDE80 Promoting webapp to ${PROD_ENV}...`);
|
|
4212
|
+
console.log(`\uD83D\uDCCD Promote URL: ${promoteUrl}`);
|
|
4213
|
+
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}
|
|
4214
|
+
`);
|
|
4215
|
+
const response = await fetch(promoteUrl, {
|
|
4216
|
+
method: "POST",
|
|
4217
|
+
headers: {
|
|
4218
|
+
Accept: "application/json",
|
|
4219
|
+
Authorization: `Bearer ${session.token}`,
|
|
4220
|
+
"Content-Type": "application/json"
|
|
4221
|
+
},
|
|
4222
|
+
body: JSON.stringify({ webappId, details })
|
|
4223
|
+
});
|
|
4224
|
+
const payload = await readResponsePayload(response);
|
|
4225
|
+
if (response.status === 409) {
|
|
4226
|
+
console.error(`❌ Webapp ${webappId} is already promoted to ${PROD_ENV}.`);
|
|
4227
|
+
process.exit(1);
|
|
4228
|
+
}
|
|
4229
|
+
if (!response.ok) {
|
|
4230
|
+
throw new Error(`Promote failed: ${response.status} ${response.statusText}
|
|
4231
|
+
${JSON.stringify(payload, null, 2)}`);
|
|
4232
|
+
}
|
|
4233
|
+
const deployToken = extractDeployToken2(payload);
|
|
4234
|
+
if (!deployToken) {
|
|
4235
|
+
throw new Error(`Promote response did not include webapp.deployToken:
|
|
4236
|
+
${JSON.stringify(payload, null, 2)}`);
|
|
4237
|
+
}
|
|
4238
|
+
upsertEnvFile(prodEnvPath, {
|
|
4239
|
+
WEBAPP_ID: webappId,
|
|
4240
|
+
DEPLOY_TOKEN: deployToken
|
|
4241
|
+
});
|
|
4242
|
+
console.log(`✅ Wrote WEBAPP_ID and DEPLOY_TOKEN to ${PROD_ENV_FILE}`);
|
|
4243
|
+
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}`);
|
|
4244
|
+
console.log(`
|
|
4245
|
+
✨ Promotion complete! Deploy to production with:
|
|
4246
|
+
`);
|
|
4247
|
+
console.log(` ro app deploy -e production
|
|
4248
|
+
`);
|
|
4249
|
+
}
|
|
4250
|
+
|
|
3856
4251
|
// src/upgrade-template.ts
|
|
3857
4252
|
import { execSync as execSync3 } from "node:child_process";
|
|
3858
|
-
import { existsSync as
|
|
4253
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, copyFileSync, rmSync as rmSync2 } from "node:fs";
|
|
3859
4254
|
var TEMPLATES = {
|
|
3860
4255
|
webapp: {
|
|
3861
4256
|
name: "webapp (SPA)",
|
|
3862
4257
|
repo: "https://github.com/airconcepts/webapp-template.git",
|
|
3863
4258
|
remoteName: "template",
|
|
3864
|
-
checkoutFiles: [
|
|
4259
|
+
checkoutFiles: [
|
|
4260
|
+
"AGENTS.md",
|
|
4261
|
+
".agent/",
|
|
4262
|
+
"src/types/webapp.d.ts",
|
|
4263
|
+
"src/types/game-sdk.d.ts",
|
|
4264
|
+
"src/widgets/Widget.tsx",
|
|
4265
|
+
"src/widgets/data-source.ts",
|
|
4266
|
+
"src/widgets/index.ts",
|
|
4267
|
+
"src/widgets/manifest.ts",
|
|
4268
|
+
"src/widgets/registry.ts",
|
|
4269
|
+
"src/widgets/types.ts",
|
|
4270
|
+
"src/widgets/use-widget-data.ts",
|
|
4271
|
+
"src/widgets/dev/WidgetGrid.tsx",
|
|
4272
|
+
"src/widgets/templates/action.tsx",
|
|
4273
|
+
"src/widgets/templates/list.tsx",
|
|
4274
|
+
"src/widgets/templates/progress.tsx",
|
|
4275
|
+
"src/widgets/templates/stat.tsx",
|
|
4276
|
+
"vite-plugins/widgets-manifest.ts"
|
|
4277
|
+
],
|
|
3865
4278
|
newFiles: [
|
|
3866
4279
|
"src/exp-engine/cli.ts",
|
|
3867
4280
|
"src/exp-engine/evaluate.ts",
|
|
3868
4281
|
"src/exp-engine/README.md",
|
|
3869
|
-
"src/types/exp-engine.d.ts"
|
|
4282
|
+
"src/types/exp-engine.d.ts",
|
|
4283
|
+
"src/widgets/examples.tsx",
|
|
4284
|
+
"src/routes/widget.$id.tsx",
|
|
4285
|
+
"src/routes/dev/widgets-preview.tsx"
|
|
3870
4286
|
]
|
|
3871
4287
|
},
|
|
3872
4288
|
"webapp-fullstack": {
|
|
@@ -3890,12 +4306,12 @@ var CLI_SCRIPTS = {
|
|
|
3890
4306
|
"upgrade-template": "bunx @rodyssey/cli@latest app upgrade-template"
|
|
3891
4307
|
};
|
|
3892
4308
|
function detectTemplate() {
|
|
3893
|
-
if (
|
|
4309
|
+
if (existsSync7("app")) {
|
|
3894
4310
|
console.log(`\uD83D\uDD0D Detected fullstack template (found app/ directory)
|
|
3895
4311
|
`);
|
|
3896
4312
|
return TEMPLATES["webapp-fullstack"];
|
|
3897
4313
|
}
|
|
3898
|
-
if (
|
|
4314
|
+
if (existsSync7("src")) {
|
|
3899
4315
|
console.log(`\uD83D\uDD0D Detected SPA template (found src/ directory)
|
|
3900
4316
|
`);
|
|
3901
4317
|
return TEMPLATES["webapp"];
|
|
@@ -3906,11 +4322,11 @@ function detectTemplate() {
|
|
|
3906
4322
|
}
|
|
3907
4323
|
function updatePackageJsonScripts() {
|
|
3908
4324
|
const pkgPath = "package.json";
|
|
3909
|
-
if (!
|
|
4325
|
+
if (!existsSync7(pkgPath)) {
|
|
3910
4326
|
console.log("⚠️ No package.json found, skipping scripts update");
|
|
3911
4327
|
return;
|
|
3912
4328
|
}
|
|
3913
|
-
const pkg = JSON.parse(
|
|
4329
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
3914
4330
|
if (!pkg.scripts) {
|
|
3915
4331
|
pkg.scripts = {};
|
|
3916
4332
|
}
|
|
@@ -3948,7 +4364,7 @@ function updateCliSkill() {
|
|
|
3948
4364
|
}
|
|
3949
4365
|
execSync3(`git fetch ${cliRemote}`, { stdio: "inherit" });
|
|
3950
4366
|
execSync3(`git checkout ${cliRemote}/main -- skills/ro-cli/SKILL.md`, { stdio: "inherit" });
|
|
3951
|
-
if (
|
|
4367
|
+
if (existsSync7("skills/ro-cli/SKILL.md")) {
|
|
3952
4368
|
mkdirSync2(".agent/skills/ro-cli", { recursive: true });
|
|
3953
4369
|
copyFileSync("skills/ro-cli/SKILL.md", ".agent/skills/ro-cli/SKILL.md");
|
|
3954
4370
|
rmSync2("skills", { recursive: true, force: true });
|
|
@@ -3981,7 +4397,7 @@ async function upgradeTemplate() {
|
|
|
3981
4397
|
const checkoutList = template.checkoutFiles.join(" ");
|
|
3982
4398
|
execSync3(`git checkout ${template.remoteName}/main -- ${checkoutList}`, { stdio: "inherit" });
|
|
3983
4399
|
for (const file of template.newFiles) {
|
|
3984
|
-
if (!
|
|
4400
|
+
if (!existsSync7(file)) {
|
|
3985
4401
|
console.log(`\uD83D\uDCC2 Checking out ${file}...`);
|
|
3986
4402
|
try {
|
|
3987
4403
|
execSync3(`git checkout ${template.remoteName}/main -- ${file}`, { stdio: "inherit" });
|
|
@@ -4139,162 +4555,6 @@ async function updateGameSdk() {
|
|
|
4139
4555
|
}
|
|
4140
4556
|
}
|
|
4141
4557
|
|
|
4142
|
-
// src/update-webapp-config.ts
|
|
4143
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4144
|
-
import { resolve } from "node:path";
|
|
4145
|
-
var CONFIG_URLS = {
|
|
4146
|
-
local: "http://localhost:5176/api/webapps/config",
|
|
4147
|
-
development: "https://development-cms.rodyssey.ai/api/webapps/config",
|
|
4148
|
-
staging: "https://staging-cms.rodyssey.ai/api/webapps/config",
|
|
4149
|
-
production: "https://cms.rodyssey.ai/api/webapps/config"
|
|
4150
|
-
};
|
|
4151
|
-
function parseJsonOption(value, optionName) {
|
|
4152
|
-
const maybePath = resolve(process.cwd(), value);
|
|
4153
|
-
const raw = existsSync6(maybePath) ? readFileSync6(maybePath, "utf-8") : value;
|
|
4154
|
-
try {
|
|
4155
|
-
const parsed = JSON.parse(raw);
|
|
4156
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4157
|
-
throw new Error("value must be a JSON object");
|
|
4158
|
-
}
|
|
4159
|
-
return parsed;
|
|
4160
|
-
} catch (error) {
|
|
4161
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4162
|
-
throw new Error(`Invalid ${optionName}: ${message}`);
|
|
4163
|
-
}
|
|
4164
|
-
}
|
|
4165
|
-
function resolveConfigUrl(options, required = true) {
|
|
4166
|
-
const configUrl = options.url || process.env.WEBAPP_CONFIG_URL || CONFIG_URLS[options.env];
|
|
4167
|
-
if (!configUrl) {
|
|
4168
|
-
if (!required)
|
|
4169
|
-
return;
|
|
4170
|
-
console.error("❌ Error: no webapp config endpoint configured.");
|
|
4171
|
-
console.error(`\uD83D\uDCA1 Use one of these environments: ${Object.keys(CONFIG_URLS).join(", ")}, pass --url, or set WEBAPP_CONFIG_URL in your .env file.`);
|
|
4172
|
-
process.exit(1);
|
|
4173
|
-
}
|
|
4174
|
-
const url = new URL(configUrl);
|
|
4175
|
-
if (!options.host && !options.port) {
|
|
4176
|
-
return url.toString().replace(/\/$/, "");
|
|
4177
|
-
}
|
|
4178
|
-
if (options.host)
|
|
4179
|
-
url.hostname = options.host;
|
|
4180
|
-
if (options.port)
|
|
4181
|
-
url.port = String(options.port);
|
|
4182
|
-
return url.toString().replace(/\/$/, "");
|
|
4183
|
-
}
|
|
4184
|
-
function buildConfigPayload(options) {
|
|
4185
|
-
const payload = options.config ? parseJsonOption(options.config, "--config") : {};
|
|
4186
|
-
if (options.title !== undefined)
|
|
4187
|
-
payload.title = options.title;
|
|
4188
|
-
if (options.description !== undefined)
|
|
4189
|
-
payload.description = options.description;
|
|
4190
|
-
if (options.coverImg !== undefined)
|
|
4191
|
-
payload.coverImg = options.coverImg;
|
|
4192
|
-
if (options.localization !== undefined) {
|
|
4193
|
-
payload.localization = parseJsonOption(options.localization, "--localization");
|
|
4194
|
-
}
|
|
4195
|
-
return payload;
|
|
4196
|
-
}
|
|
4197
|
-
function resolveWebappId(webappId) {
|
|
4198
|
-
const resolved = webappId || process.env.WEBAPP_ID;
|
|
4199
|
-
if (!resolved) {
|
|
4200
|
-
console.error("❌ Error: WEBAPP_ID is not set. Pass --webapp-id or set it in your .env file.");
|
|
4201
|
-
process.exit(1);
|
|
4202
|
-
}
|
|
4203
|
-
return resolved;
|
|
4204
|
-
}
|
|
4205
|
-
function ensureDeployToken(env) {
|
|
4206
|
-
if (process.env.DEPLOY_TOKEN)
|
|
4207
|
-
return;
|
|
4208
|
-
console.error("❌ Error: DEPLOY_TOKEN is not set in environment variables.");
|
|
4209
|
-
console.info(`\uD83D\uDCA1 Please check your .env or .env.${env} file.`);
|
|
4210
|
-
process.exit(1);
|
|
4211
|
-
}
|
|
4212
|
-
function getConfigUrl(options, required = true) {
|
|
4213
|
-
return resolveConfigUrl(options, required);
|
|
4214
|
-
}
|
|
4215
|
-
async function getWebappConfig(options) {
|
|
4216
|
-
loadEnv(options.env);
|
|
4217
|
-
const webappId = resolveWebappId(options.webappId);
|
|
4218
|
-
const CONFIG_URL = getConfigUrl(options);
|
|
4219
|
-
if (!CONFIG_URL)
|
|
4220
|
-
return;
|
|
4221
|
-
ensureDeployToken(options.env);
|
|
4222
|
-
const url = new URL(CONFIG_URL);
|
|
4223
|
-
url.searchParams.set("webappId", webappId);
|
|
4224
|
-
console.log(`⚙️ Pulling webapp config for [${options.env}] environment...`);
|
|
4225
|
-
console.log(`\uD83D\uDCCD Config URL: ${url.toString()}`);
|
|
4226
|
-
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}
|
|
4227
|
-
`);
|
|
4228
|
-
const response = await fetch(url, {
|
|
4229
|
-
method: "GET",
|
|
4230
|
-
headers: {
|
|
4231
|
-
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
4232
|
-
Accept: "application/json"
|
|
4233
|
-
}
|
|
4234
|
-
});
|
|
4235
|
-
if (!response.ok) {
|
|
4236
|
-
const errorText = await response.text();
|
|
4237
|
-
throw new Error(`Config fetch failed: ${response.status} ${response.statusText}
|
|
4238
|
-
${errorText}`);
|
|
4239
|
-
}
|
|
4240
|
-
const config = await response.json();
|
|
4241
|
-
const output = JSON.stringify(config, null, 2);
|
|
4242
|
-
if (options.out) {
|
|
4243
|
-
writeFileSync5(options.out, `${output}
|
|
4244
|
-
`, "utf-8");
|
|
4245
|
-
console.log(`✅ Webapp config written to ${options.out}`);
|
|
4246
|
-
return;
|
|
4247
|
-
}
|
|
4248
|
-
console.log(output);
|
|
4249
|
-
}
|
|
4250
|
-
async function updateWebappConfig(options) {
|
|
4251
|
-
loadEnv(options.env);
|
|
4252
|
-
const webappId = resolveWebappId(options.webappId);
|
|
4253
|
-
const config = buildConfigPayload(options);
|
|
4254
|
-
if (Object.keys(config).length === 0) {
|
|
4255
|
-
console.error("❌ Error: no config fields provided. Use --title, --cover-img, --localization, or --config.");
|
|
4256
|
-
process.exit(1);
|
|
4257
|
-
}
|
|
4258
|
-
const payload = { webappId, config };
|
|
4259
|
-
const CONFIG_URL = getConfigUrl(options, !options.dryRun);
|
|
4260
|
-
if (!options.dryRun)
|
|
4261
|
-
ensureDeployToken(options.env);
|
|
4262
|
-
console.log(`⚙️ Updating webapp config for [${options.env}] environment...`);
|
|
4263
|
-
if (CONFIG_URL) {
|
|
4264
|
-
console.log(`\uD83D\uDCCD Config URL: ${CONFIG_URL}`);
|
|
4265
|
-
}
|
|
4266
|
-
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}
|
|
4267
|
-
`);
|
|
4268
|
-
if (options.dryRun) {
|
|
4269
|
-
console.log("\uD83E\uDDEA Dry run payload:");
|
|
4270
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
4271
|
-
return;
|
|
4272
|
-
}
|
|
4273
|
-
if (!CONFIG_URL)
|
|
4274
|
-
return;
|
|
4275
|
-
const response = await fetch(CONFIG_URL, {
|
|
4276
|
-
method: "PATCH",
|
|
4277
|
-
headers: {
|
|
4278
|
-
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
4279
|
-
"Content-Type": "application/json"
|
|
4280
|
-
},
|
|
4281
|
-
body: JSON.stringify(payload)
|
|
4282
|
-
});
|
|
4283
|
-
if (!response.ok) {
|
|
4284
|
-
const errorText = await response.text();
|
|
4285
|
-
throw new Error(`Config update failed: ${response.status} ${response.statusText}
|
|
4286
|
-
${errorText}`);
|
|
4287
|
-
}
|
|
4288
|
-
const result = await response.json().catch(() => {
|
|
4289
|
-
return;
|
|
4290
|
-
});
|
|
4291
|
-
console.log("✅ Webapp config updated");
|
|
4292
|
-
if (result !== undefined) {
|
|
4293
|
-
console.log(`
|
|
4294
|
-
\uD83D\uDCCB Update result:`, result);
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
|
|
4298
4558
|
// src/cli.ts
|
|
4299
4559
|
var TEMPLATES2 = {
|
|
4300
4560
|
webapp: {
|
|
@@ -4322,26 +4582,26 @@ Available templates:
|
|
|
4322
4582
|
input: process.stdin,
|
|
4323
4583
|
output: process.stdout
|
|
4324
4584
|
});
|
|
4325
|
-
return new Promise((
|
|
4585
|
+
return new Promise((resolve3) => {
|
|
4326
4586
|
rl.question(`Select a template (1-${entries.length}): `, (answer) => {
|
|
4327
4587
|
rl.close();
|
|
4328
4588
|
const index = parseInt(answer, 10) - 1;
|
|
4329
4589
|
if (index >= 0 && index < entries.length) {
|
|
4330
|
-
|
|
4590
|
+
resolve3(entries[index].name);
|
|
4331
4591
|
} else {
|
|
4332
4592
|
console.error("Invalid selection, defaulting to 'webapp'");
|
|
4333
|
-
|
|
4593
|
+
resolve3("webapp");
|
|
4334
4594
|
}
|
|
4335
4595
|
});
|
|
4336
4596
|
});
|
|
4337
4597
|
}
|
|
4338
|
-
program.name("@rodyssey/cli").description("Airconcepts CLI toolkit").version(
|
|
4598
|
+
program.name("@rodyssey/cli").description("Airconcepts CLI toolkit").version(package_default.version);
|
|
4339
4599
|
var app = program.command("app").description("Manage webapp projects");
|
|
4340
4600
|
function addConfigTargetOptions(command) {
|
|
4341
4601
|
return command.option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--url <url>", "Override the config endpoint URL").option("--host <host>", "Override the config endpoint host").option("--port <port>", "Override the config endpoint port", parseInt).option("--webapp-id <id>", "Webapp ID. Defaults to WEBAPP_ID from .env");
|
|
4342
4602
|
}
|
|
4343
4603
|
function addConfigSetOptions(command) {
|
|
4344
|
-
return addConfigTargetOptions(command).option("--title <title>", "Webapp title").option("--description <description>", "Webapp description").option("--cover-img <url>", "Cover image URL").option("--localization <json-or-file>", "Localization JSON object
|
|
4604
|
+
return addConfigTargetOptions(command).option("--title <title>", "Webapp title (pass the literal 'null' to clear)").option("--description <description>", "Webapp description (pass the literal 'null' to clear)").option("--cover-img <url>", "Cover image URL (pass the literal 'null' to clear)").option("--localization <json-or-file>", "Localization JSON object, path to a JSON file, or 'null' to clear").option("--details <json-or-file>", "Partial WebappDetails JSON or path to a JSON file. Sent as a delta — only include keys you want to change. Do NOT echo a full GET response here, or empty defaults like 'tags: []' will clobber real data.").option("--dry-run", "Print the request payload without sending it");
|
|
4345
4605
|
}
|
|
4346
4606
|
app.command("create").argument("<project-name>", "Name of the project to create").option("-t, --template <template>", "Template to use (webapp | webapp-fullstack)").option("--auto", "Create a CMS webapp and write WEBAPP_ID/DEPLOY_TOKEN to .env").option("-e, --env <environment>", "CMS environment for --auto (local | development | staging | production)", "development").option("--cms-url <url>", "CMS base URL for --auto. Defaults to the selected environment").option("--create-url <url>", "Full CMS create endpoint for --auto. Defaults to <cms-url>/api/cli/webapps/create").description("Create a new project from a template").action(async (projectName, options) => {
|
|
4347
4607
|
let templateName;
|
|
@@ -4362,6 +4622,15 @@ app.command("create").argument("<project-name>", "Name of the project to create"
|
|
|
4362
4622
|
createUrl: options.createUrl
|
|
4363
4623
|
});
|
|
4364
4624
|
});
|
|
4625
|
+
app.command("promote").description("Promote the current webapp to production (creates a prod record with the same WEBAPP_ID)").option("--details <json-or-file>", "Full WebappDetails JSON object or path to a JSON file. Skips the source-pull and confirmation prompts when provided.").option("-y, --yes", "Auto-accept pulling the latest details from development").option("--cms-url <url>", "Production CMS base URL. Defaults to the production environment").option("--promote-url <url>", "Full CMS promote endpoint. Defaults to <cms-url>/api/cli/webapps/promote").option("--from <env>", "Source environment to pull details from (testing override). Defaults to development", "development").action(async (options) => {
|
|
4626
|
+
await promote({
|
|
4627
|
+
details: options.details,
|
|
4628
|
+
yes: options.yes,
|
|
4629
|
+
cmsUrl: options.cmsUrl,
|
|
4630
|
+
promoteUrl: options.promoteUrl,
|
|
4631
|
+
from: options.from
|
|
4632
|
+
});
|
|
4633
|
+
});
|
|
4365
4634
|
app.command("update-game-sdk").description("Download and update the GameSDK library, types, and documentation").action(async () => {
|
|
4366
4635
|
await updateGameSdk();
|
|
4367
4636
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rodyssey/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Scaffold new projects from airconcepts templates",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/airconcepts/ro-cli.git"
|
|
8
|
+
},
|
|
5
9
|
"bin": {
|
|
6
10
|
"@rodyssey/cli": "dist/cli.js"
|
|
7
11
|
},
|
|
@@ -13,16 +17,24 @@
|
|
|
13
17
|
"scripts": {
|
|
14
18
|
"build": "bun build src/cli.ts --outdir dist --target node",
|
|
15
19
|
"start": "bun dist/cli.js",
|
|
20
|
+
"changeset": "changeset",
|
|
21
|
+
"version-packages": "changeset version",
|
|
22
|
+
"release": "bun run build && changeset publish",
|
|
16
23
|
"prepublishOnly": "bun run build"
|
|
17
24
|
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"provenance": false
|
|
28
|
+
},
|
|
18
29
|
"dependencies": {
|
|
19
30
|
"commander": "^13.1.0",
|
|
20
31
|
"mime": "^4.1.0"
|
|
21
32
|
},
|
|
22
33
|
"devDependencies": {
|
|
34
|
+
"@changesets/cli": "^2.30.0",
|
|
23
35
|
"@types/bun": "latest"
|
|
24
36
|
},
|
|
25
37
|
"peerDependencies": {
|
|
26
38
|
"typescript": "^5"
|
|
27
39
|
}
|
|
28
|
-
}
|
|
40
|
+
}
|