@rodyssey/cli 0.6.0 → 0.7.0
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 +144 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2071,7 +2071,7 @@ var {
|
|
|
2071
2071
|
// package.json
|
|
2072
2072
|
var package_default = {
|
|
2073
2073
|
name: "@rodyssey/cli",
|
|
2074
|
-
version: "0.
|
|
2074
|
+
version: "0.7.0",
|
|
2075
2075
|
description: "Scaffold new projects from airconcepts templates",
|
|
2076
2076
|
repository: {
|
|
2077
2077
|
type: "git",
|
|
@@ -2857,6 +2857,15 @@ ${sections.join(`
|
|
|
2857
2857
|
|
|
2858
2858
|
// src/config-file.ts
|
|
2859
2859
|
var DEFAULT_CONFIG_FILE = "webapp.config.json";
|
|
2860
|
+
var READ_ONLY_FIELDS = new Set(["appType"]);
|
|
2861
|
+
function stripReadOnlyFields(details) {
|
|
2862
|
+
const out = {};
|
|
2863
|
+
for (const [key, value] of Object.entries(details)) {
|
|
2864
|
+
if (!READ_ONLY_FIELDS.has(key))
|
|
2865
|
+
out[key] = value;
|
|
2866
|
+
}
|
|
2867
|
+
return out;
|
|
2868
|
+
}
|
|
2860
2869
|
function isClearSignalEmpty(value) {
|
|
2861
2870
|
if (value === null)
|
|
2862
2871
|
return true;
|
|
@@ -2930,7 +2939,7 @@ async function pullWebappConfig(options) {
|
|
|
2930
2939
|
host: options.host,
|
|
2931
2940
|
port: options.port
|
|
2932
2941
|
});
|
|
2933
|
-
const details = unwrapDetails(raw);
|
|
2942
|
+
const details = stripReadOnlyFields(unwrapDetails(raw));
|
|
2934
2943
|
const cleaned = sanitizeDetails(details);
|
|
2935
2944
|
const stripped = Object.keys(details).length - Object.keys(cleaned).length;
|
|
2936
2945
|
const fileName = options.out || DEFAULT_CONFIG_FILE;
|
|
@@ -2942,7 +2951,7 @@ async function pullWebappConfig(options) {
|
|
|
2942
2951
|
async function pushWebappConfig(options) {
|
|
2943
2952
|
const fileName = options.file || DEFAULT_CONFIG_FILE;
|
|
2944
2953
|
const filePath = resolve2(process.cwd(), fileName);
|
|
2945
|
-
const fileDetails = readConfigFile(filePath);
|
|
2954
|
+
const fileDetails = stripReadOnlyFields(readConfigFile(filePath));
|
|
2946
2955
|
const raw = await fetchWebappConfig({
|
|
2947
2956
|
env: options.env,
|
|
2948
2957
|
webappId: options.webappId,
|
|
@@ -2998,7 +3007,7 @@ async function checkConfigDriftOnDeploy(options) {
|
|
|
2998
3007
|
}
|
|
2999
3008
|
let fileDetails;
|
|
3000
3009
|
try {
|
|
3001
|
-
fileDetails = readConfigFile(filePath);
|
|
3010
|
+
fileDetails = stripReadOnlyFields(readConfigFile(filePath));
|
|
3002
3011
|
} catch (error) {
|
|
3003
3012
|
const message = error instanceof Error ? error.message : String(error);
|
|
3004
3013
|
console.warn(`
|
|
@@ -5305,6 +5314,106 @@ async function removeFromGroup(groupId, options) {
|
|
|
5305
5314
|
// src/promote.ts
|
|
5306
5315
|
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
|
|
5307
5316
|
import { resolve as resolve6 } from "node:path";
|
|
5317
|
+
|
|
5318
|
+
// src/release.ts
|
|
5319
|
+
var WEBAPP_TYPES = [
|
|
5320
|
+
"RO_APP",
|
|
5321
|
+
"RO_APP_REMOTE",
|
|
5322
|
+
"VIBED_APP",
|
|
5323
|
+
"STORY_GAME",
|
|
5324
|
+
"UTILITY",
|
|
5325
|
+
"APP_MISSION",
|
|
5326
|
+
"ADMIN_TOOL",
|
|
5327
|
+
"CHAT_APP",
|
|
5328
|
+
"1TO1_CHAT_APP",
|
|
5329
|
+
"STORY_APP",
|
|
5330
|
+
"HIDDEN_APP",
|
|
5331
|
+
"CMS_APP",
|
|
5332
|
+
"TEMP_APP"
|
|
5333
|
+
];
|
|
5334
|
+
var RELEASE_URLS = {
|
|
5335
|
+
local: "http://localhost:5176/api/cli/webapps/release",
|
|
5336
|
+
development: "https://development-cms.rodyssey.ai/api/cli/webapps/release",
|
|
5337
|
+
staging: "https://staging-cms.rodyssey.ai/api/cli/webapps/release",
|
|
5338
|
+
production: "https://cms.rodyssey.ai/api/cli/webapps/release"
|
|
5339
|
+
};
|
|
5340
|
+
function parseWebappType(raw) {
|
|
5341
|
+
if (WEBAPP_TYPES.includes(raw)) {
|
|
5342
|
+
return raw;
|
|
5343
|
+
}
|
|
5344
|
+
throw new Error(`--type must be one of: ${WEBAPP_TYPES.join(", ")}`);
|
|
5345
|
+
}
|
|
5346
|
+
function resolveWebappId3(webappId) {
|
|
5347
|
+
const resolved = webappId || process.env.WEBAPP_ID;
|
|
5348
|
+
if (!resolved) {
|
|
5349
|
+
throw new Error("WEBAPP_ID is not set. Pass --webapp-id or set it in your .env file.");
|
|
5350
|
+
}
|
|
5351
|
+
return resolved;
|
|
5352
|
+
}
|
|
5353
|
+
function ensureDeployToken3(env) {
|
|
5354
|
+
if (process.env.DEPLOY_TOKEN)
|
|
5355
|
+
return process.env.DEPLOY_TOKEN;
|
|
5356
|
+
throw new Error(`DEPLOY_TOKEN is not set. Please check your .env or .env.${env} file.`);
|
|
5357
|
+
}
|
|
5358
|
+
function resolveReleaseUrl(env, url) {
|
|
5359
|
+
const resolved = url || RELEASE_URLS[env];
|
|
5360
|
+
if (!resolved) {
|
|
5361
|
+
throw new Error(`Unknown environment: "${env}". Use one of: ${Object.keys(RELEASE_URLS).join(", ")}, or pass --url.`);
|
|
5362
|
+
}
|
|
5363
|
+
return resolved;
|
|
5364
|
+
}
|
|
5365
|
+
async function releaseWebapp(options) {
|
|
5366
|
+
loadEnv(options.env);
|
|
5367
|
+
const appType = parseWebappType(options.type ?? "RO_APP");
|
|
5368
|
+
const webappId = resolveWebappId3(options.webappId);
|
|
5369
|
+
console.log(`\uD83D\uDE80 Release webapp ${webappId} as [${appType}] on [${options.env}]`);
|
|
5370
|
+
if (options.dryRun) {
|
|
5371
|
+
console.log(dim(`
|
|
5372
|
+
↷ Dry run — no request sent.`));
|
|
5373
|
+
return;
|
|
5374
|
+
}
|
|
5375
|
+
const tty = !!process.stdin.isTTY && !!process.stdout.isTTY;
|
|
5376
|
+
if (!options.yes) {
|
|
5377
|
+
if (!tty) {
|
|
5378
|
+
throw new Error("Refusing to release in non-interactive mode. Pass --yes to confirm or --dry-run to preview.");
|
|
5379
|
+
}
|
|
5380
|
+
const answer = await prompt(`
|
|
5381
|
+
Proceed with release on [${options.env}]? (y/N): `);
|
|
5382
|
+
if (!isExplicitYes(answer)) {
|
|
5383
|
+
console.log("✋ Aborted.");
|
|
5384
|
+
return;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
const token = ensureDeployToken3(options.env);
|
|
5388
|
+
const url = resolveReleaseUrl(options.env, options.url);
|
|
5389
|
+
const response = await fetch(url, {
|
|
5390
|
+
method: "PATCH",
|
|
5391
|
+
headers: {
|
|
5392
|
+
Accept: "application/json",
|
|
5393
|
+
"Content-Type": "application/json",
|
|
5394
|
+
Authorization: `Bearer ${token}`
|
|
5395
|
+
},
|
|
5396
|
+
body: JSON.stringify({ webappId, appType })
|
|
5397
|
+
});
|
|
5398
|
+
let payload;
|
|
5399
|
+
try {
|
|
5400
|
+
payload = await response.json();
|
|
5401
|
+
} catch {
|
|
5402
|
+
throw new Error(`${response.status} ${response.statusText} (no JSON body)`);
|
|
5403
|
+
}
|
|
5404
|
+
if (!response.ok) {
|
|
5405
|
+
throw new Error(`PATCH ${url} failed: ${response.status} ${response.statusText}
|
|
5406
|
+
${pretty(payload)}`);
|
|
5407
|
+
}
|
|
5408
|
+
if (options.json) {
|
|
5409
|
+
console.log(pretty(payload));
|
|
5410
|
+
return;
|
|
5411
|
+
}
|
|
5412
|
+
console.log(`
|
|
5413
|
+
✅ Released as ${appType}.`);
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// src/promote.ts
|
|
5308
5417
|
var DEFAULT_SOURCE_ENV = "development";
|
|
5309
5418
|
var PROD_ENV3 = "production";
|
|
5310
5419
|
var PROD_ENV_FILE = ".env.production";
|
|
@@ -5414,6 +5523,27 @@ Deploy to production now? (y/N): `);
|
|
|
5414
5523
|
process.env.DEPLOY_TOKEN = deployToken;
|
|
5415
5524
|
await deploy(PROD_ENV3, { skipConfigCheck: true });
|
|
5416
5525
|
}
|
|
5526
|
+
async function maybeReleaseWebapp(options, webappId, deployToken) {
|
|
5527
|
+
const releaseType = options.releaseType ?? "RO_APP";
|
|
5528
|
+
const releaseRequested = options.release === true || options.releaseType !== undefined;
|
|
5529
|
+
if (!releaseRequested) {
|
|
5530
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
5531
|
+
return;
|
|
5532
|
+
const answer = await prompt2(`
|
|
5533
|
+
Release as [${releaseType}] on ${PROD_ENV3}? (y/N): `);
|
|
5534
|
+
if (!isExplicitYes2(answer)) {
|
|
5535
|
+
console.log(`
|
|
5536
|
+
↷ Skipping release. Run later with:
|
|
5537
|
+
|
|
5538
|
+
ro app release --type ${releaseType} -e production
|
|
5539
|
+
`);
|
|
5540
|
+
return;
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
process.env.WEBAPP_ID = webappId;
|
|
5544
|
+
process.env.DEPLOY_TOKEN = deployToken;
|
|
5545
|
+
await releaseWebapp({ env: PROD_ENV3, type: releaseType, yes: true });
|
|
5546
|
+
}
|
|
5417
5547
|
async function promote(options) {
|
|
5418
5548
|
assertDeployOptions(options);
|
|
5419
5549
|
loadEnv();
|
|
@@ -5505,6 +5635,7 @@ ${JSON.stringify(payload, null, 2)}`);
|
|
|
5505
5635
|
console.log(`✅ Wrote WEBAPP_ID and DEPLOY_TOKEN to ${PROD_ENV_FILE}`);
|
|
5506
5636
|
console.log(`\uD83D\uDCCD Webapp ID: ${webappId}`);
|
|
5507
5637
|
await maybeDeployProduction(options, webappId, deployToken);
|
|
5638
|
+
await maybeReleaseWebapp(options, webappId, deployToken);
|
|
5508
5639
|
}
|
|
5509
5640
|
|
|
5510
5641
|
// src/upgrade-template.ts
|
|
@@ -6037,7 +6168,7 @@ function splitBatches(entries, sizeOf) {
|
|
|
6037
6168
|
batches.push(current);
|
|
6038
6169
|
return batches;
|
|
6039
6170
|
}
|
|
6040
|
-
function
|
|
6171
|
+
function ensureDeployToken4(env) {
|
|
6041
6172
|
if (process.env.DEPLOY_TOKEN)
|
|
6042
6173
|
return process.env.DEPLOY_TOKEN;
|
|
6043
6174
|
throw new Error(`DEPLOY_TOKEN is not set. Please check your .env or .env.${env} file.`);
|
|
@@ -6059,7 +6190,7 @@ async function pushAssets(inputs, options) {
|
|
|
6059
6190
|
↷ Dry run — no request sent.`));
|
|
6060
6191
|
return;
|
|
6061
6192
|
}
|
|
6062
|
-
const token =
|
|
6193
|
+
const token = ensureDeployToken4(options.env);
|
|
6063
6194
|
const uploaded = [];
|
|
6064
6195
|
for (const [index, batch] of batches.entries()) {
|
|
6065
6196
|
const formData = new FormData;
|
|
@@ -6220,7 +6351,7 @@ app.command("create").argument("<project-name>", "Name of the project to create"
|
|
|
6220
6351
|
createUrl: options.createUrl
|
|
6221
6352
|
});
|
|
6222
6353
|
});
|
|
6223
|
-
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").option("--deploy", "Deploy to production immediately after promotion").option("--skip-deploy", "Do not ask to deploy after promotion").action(async (options) => {
|
|
6354
|
+
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").option("--deploy", "Deploy to production immediately after promotion").option("--skip-deploy", "Do not ask to deploy after promotion").option("--release", "Set the app type on production after promotion (default type: RO_APP)").option("--release-type <appType>", `App type to set on release (implies --release). One of: ${WEBAPP_TYPES.join(", ")}`).action(async (options) => {
|
|
6224
6355
|
await promote({
|
|
6225
6356
|
details: options.details,
|
|
6226
6357
|
yes: options.yes,
|
|
@@ -6228,7 +6359,9 @@ app.command("promote").description("Promote the current webapp to production (cr
|
|
|
6228
6359
|
promoteUrl: options.promoteUrl,
|
|
6229
6360
|
from: options.from,
|
|
6230
6361
|
deploy: options.deploy,
|
|
6231
|
-
skipDeploy: options.skipDeploy
|
|
6362
|
+
skipDeploy: options.skipDeploy,
|
|
6363
|
+
release: options.release,
|
|
6364
|
+
releaseType: options.releaseType
|
|
6232
6365
|
});
|
|
6233
6366
|
});
|
|
6234
6367
|
app.command("update-game-sdk").description("Download and update the GameSDK library, types, and documentation").action(async () => {
|
|
@@ -6253,6 +6386,9 @@ addConfigTargetOptions(config.command("pull").description("Pull the CMS webapp c
|
|
|
6253
6386
|
addConfigTargetOptions(config.command("push").description("Push webapp.config.json back to the CMS (previews a diff and confirms)").option("--file <path>", "Config file to push (default: webapp.config.json)").option("--dry-run", "Preview the delta without sending").option("-y, --yes", "Skip the confirmation prompt")).action(async (options) => {
|
|
6254
6387
|
await pushWebappConfig(options);
|
|
6255
6388
|
});
|
|
6389
|
+
app.command("release").description("Set the webapp app type (e.g. promote a TEMP_APP to RO_APP)").option("--type <appType>", "App type to set (default: RO_APP)").option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--webapp-id <id>", "Override WEBAPP_ID from .env").option("--url <url>", "Override the release endpoint URL").option("--dry-run", "Preview the release without sending").option("-y, --yes", "Skip confirmation prompt").option("--json", "Print the raw JSON result").action(async (options) => {
|
|
6390
|
+
await releaseWebapp(options);
|
|
6391
|
+
});
|
|
6256
6392
|
var assets = app.command("assets").description("Manage webapp assets (R2-hosted files)");
|
|
6257
6393
|
assets.command("push").description("Upload or overwrite webapp assets and print their public URLs").argument("<paths...>", "Files and/or directories to upload").option("--dest <remote-dir>", "Remote directory prefix (e.g. images)").option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--url <url>", "Override the assets endpoint URL").option("--host <host>", "Override the assets endpoint host").option("--port <port>", "Override the assets endpoint port", parseInt).option("--dry-run", "Preview the local→remote mapping without uploading").option("--json", "Print the raw JSON result").action(async (paths, options) => {
|
|
6258
6394
|
await pushAssets(paths, options);
|