@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.
Files changed (2) hide show
  1. package/dist/cli.js +144 -8
  2. 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.6.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 ensureDeployToken3(env) {
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 = ensureDeployToken3(options.env);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rodyssey/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Scaffold new projects from airconcepts templates",
5
5
  "repository": {
6
6
  "type": "git",