@rodyssey/cli 0.3.1 → 0.4.1
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 +8 -1
- package/dist/cli.js +285 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,7 +102,10 @@ no identity block stored)`. Re-running `ro auth login -e <env>` refreshes them.
|
|
|
102
102
|
|
|
103
103
|
`app upgrade-template` backfills additive template files that are missing from
|
|
104
104
|
older projects. This includes the Dynamic Worker MCP sample endpoint and the
|
|
105
|
-
shared `mcp/` helper folder; existing local MCP files are left untouched.
|
|
105
|
+
shared `mcp/` helper folder; existing local MCP files are left untouched. It
|
|
106
|
+
also force-overwrites a whitelist of CLI/template-owned `package.json` scripts
|
|
107
|
+
such as `deploy`, `deploy:staging`, `deploy:production`, and
|
|
108
|
+
`sync-widget-manifest`.
|
|
106
109
|
|
|
107
110
|
## Deployment Output
|
|
108
111
|
|
|
@@ -118,6 +121,10 @@ SPA deploys register built `dist/api/*` and `dist/cron-jobs/*` scripts after the
|
|
|
118
121
|
HTML zip has deployed. Fullstack deploys register the same Dynamic Worker
|
|
119
122
|
scripts after `wrangler deploy` and widget manifest sync.
|
|
120
123
|
|
|
124
|
+
`ro app sync-widget-manifest` can also be run directly from a fullstack project
|
|
125
|
+
after `bun run build`. It reads `build/client/widgets.manifest.json` by default,
|
|
126
|
+
then PATCHes the CMS webapp config with `details.widgetManifest`.
|
|
127
|
+
|
|
121
128
|
## Release
|
|
122
129
|
|
|
123
130
|
Release automation lives in `.github/workflows/release.yml` and uses Changesets.
|
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.4.1",
|
|
2075
2075
|
description: "Scaffold new projects from airconcepts templates",
|
|
2076
2076
|
repository: {
|
|
2077
2077
|
type: "git",
|
|
@@ -2671,7 +2671,7 @@ async function create(projectName, repoUrl, templateName, autoCreate) {
|
|
|
2671
2671
|
|
|
2672
2672
|
// src/deploy.ts
|
|
2673
2673
|
import { execSync as execSync2 } from "node:child_process";
|
|
2674
|
-
import { existsSync as
|
|
2674
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
2675
2675
|
import { join as join2 } from "node:path";
|
|
2676
2676
|
|
|
2677
2677
|
// node_modules/mime/dist/types/other.js
|
|
@@ -3857,6 +3857,98 @@ var Mime_default = Mime;
|
|
|
3857
3857
|
// node_modules/mime/dist/src/index.js
|
|
3858
3858
|
var src_default = new Mime_default(standard_default, other_default)._freeze();
|
|
3859
3859
|
|
|
3860
|
+
// src/sync-widget-manifest.ts
|
|
3861
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
3862
|
+
import { resolve } from "node:path";
|
|
3863
|
+
var CONFIG_URLS = {
|
|
3864
|
+
local: "http://localhost:5176/api/webapps/config",
|
|
3865
|
+
development: "https://development-cms.rodyssey.ai/api/webapps/config",
|
|
3866
|
+
staging: "https://staging-cms.rodyssey.ai/api/webapps/config",
|
|
3867
|
+
production: "https://cms.rodyssey.ai/api/webapps/config"
|
|
3868
|
+
};
|
|
3869
|
+
function resolveWidgetConfigUrl(options) {
|
|
3870
|
+
const rawUrl = options.url || process.env.WEBAPP_CONFIG_URL || CONFIG_URLS[options.env];
|
|
3871
|
+
if (!rawUrl) {
|
|
3872
|
+
throw new Error(`Unknown environment "${options.env}". Use one of: ${Object.keys(CONFIG_URLS).join(", ")}, pass --url, or set WEBAPP_CONFIG_URL.`);
|
|
3873
|
+
}
|
|
3874
|
+
const url = new URL(rawUrl);
|
|
3875
|
+
if (options.host)
|
|
3876
|
+
url.hostname = options.host;
|
|
3877
|
+
if (options.port)
|
|
3878
|
+
url.port = String(options.port);
|
|
3879
|
+
return url.toString().replace(/\/$/, "");
|
|
3880
|
+
}
|
|
3881
|
+
function resolveManifestPath(manifest) {
|
|
3882
|
+
if (manifest) {
|
|
3883
|
+
const manifestPath = resolve(process.cwd(), manifest);
|
|
3884
|
+
if (!existsSync4(manifestPath)) {
|
|
3885
|
+
throw new Error(`Widget manifest not found: ${manifestPath}`);
|
|
3886
|
+
}
|
|
3887
|
+
return manifestPath;
|
|
3888
|
+
}
|
|
3889
|
+
const candidates = [
|
|
3890
|
+
resolve(process.cwd(), "build/client/widgets.manifest.json"),
|
|
3891
|
+
resolve(process.cwd(), "dist/widgets.manifest.json")
|
|
3892
|
+
];
|
|
3893
|
+
const found = candidates.find((candidate) => existsSync4(candidate));
|
|
3894
|
+
return found;
|
|
3895
|
+
}
|
|
3896
|
+
function readManifest(path3) {
|
|
3897
|
+
const parsed = JSON.parse(readFileSync3(path3, "utf-8"));
|
|
3898
|
+
if (!Array.isArray(parsed)) {
|
|
3899
|
+
throw new Error(`Widget manifest must be a JSON array: ${path3}`);
|
|
3900
|
+
}
|
|
3901
|
+
return parsed;
|
|
3902
|
+
}
|
|
3903
|
+
function resolveWebappId(webappId) {
|
|
3904
|
+
const resolved = webappId || process.env.WEBAPP_ID;
|
|
3905
|
+
if (!resolved) {
|
|
3906
|
+
throw new Error("WEBAPP_ID is not set. Add it to .env or pass --webapp-id.");
|
|
3907
|
+
}
|
|
3908
|
+
return resolved;
|
|
3909
|
+
}
|
|
3910
|
+
function ensureDeployToken(env) {
|
|
3911
|
+
if (process.env.DEPLOY_TOKEN)
|
|
3912
|
+
return;
|
|
3913
|
+
throw new Error(`DEPLOY_TOKEN is not set. Please check your .env or .env.${env} file.`);
|
|
3914
|
+
}
|
|
3915
|
+
async function syncWidgetManifest(options) {
|
|
3916
|
+
loadEnv(options.env);
|
|
3917
|
+
const manifestPath = resolveManifestPath(options.manifest);
|
|
3918
|
+
if (!manifestPath) {
|
|
3919
|
+
console.log("No widget manifest found; skipping widget manifest sync.");
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3922
|
+
const webappId = resolveWebappId(options.webappId);
|
|
3923
|
+
if (!options.dryRun)
|
|
3924
|
+
ensureDeployToken(options.env);
|
|
3925
|
+
const manifest = readManifest(manifestPath);
|
|
3926
|
+
const payload = { webappId, details: { widgetManifest: manifest } };
|
|
3927
|
+
const configUrl = resolveWidgetConfigUrl(options);
|
|
3928
|
+
console.log(`Syncing ${manifest.length} widget manifest entr${manifest.length === 1 ? "y" : "ies"}`);
|
|
3929
|
+
console.log(`Manifest: ${manifestPath}`);
|
|
3930
|
+
console.log(`Config URL: ${configUrl}`);
|
|
3931
|
+
console.log(`Webapp ID: ${webappId}`);
|
|
3932
|
+
if (options.dryRun) {
|
|
3933
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
const response = await fetch(configUrl, {
|
|
3937
|
+
method: "PATCH",
|
|
3938
|
+
headers: {
|
|
3939
|
+
Authorization: `Bearer ${process.env.DEPLOY_TOKEN}`,
|
|
3940
|
+
"Content-Type": "application/json"
|
|
3941
|
+
},
|
|
3942
|
+
body: JSON.stringify(payload)
|
|
3943
|
+
});
|
|
3944
|
+
if (!response.ok) {
|
|
3945
|
+
const errorText = await response.text();
|
|
3946
|
+
throw new Error(`Widget manifest sync failed: ${response.status} ${response.statusText}
|
|
3947
|
+
${errorText}`);
|
|
3948
|
+
}
|
|
3949
|
+
console.log("Widget manifest synced");
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3860
3952
|
// src/deploy.ts
|
|
3861
3953
|
var DEPLOY_URLS = {
|
|
3862
3954
|
local: "http://localhost:5176/api/webapps/deploy",
|
|
@@ -3888,7 +3980,7 @@ function pickNumber(value) {
|
|
|
3888
3980
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
3889
3981
|
}
|
|
3890
3982
|
function isFullstackProject() {
|
|
3891
|
-
return
|
|
3983
|
+
return existsSync5("app") && existsSync5("workers/app.ts") && existsSync5("wrangler.jsonc");
|
|
3892
3984
|
}
|
|
3893
3985
|
function resolveDeployUrl(env, overrides) {
|
|
3894
3986
|
let deployUrl = DEPLOY_URLS[env];
|
|
@@ -3908,6 +4000,9 @@ function resolveDeployUrl(env, overrides) {
|
|
|
3908
4000
|
function resolveScriptsSetupUrl(deployUrl) {
|
|
3909
4001
|
return deployUrl.replace("/webapps/deploy", "/webapps/scripts-setup");
|
|
3910
4002
|
}
|
|
4003
|
+
function resolveConfigUrlFromDeployUrl(deployUrl) {
|
|
4004
|
+
return deployUrl.replace("/webapps/deploy", "/webapps/config");
|
|
4005
|
+
}
|
|
3911
4006
|
function resolveWebClientBaseUrl(env) {
|
|
3912
4007
|
return WEB_CLIENT_URLS[env];
|
|
3913
4008
|
}
|
|
@@ -3961,7 +4056,7 @@ function collectScripts(scriptFiles) {
|
|
|
3961
4056
|
const payload = { api: {}, cron: {}, cronConfig: null };
|
|
3962
4057
|
const summary = { apiEndpoints: [], cronJobs: [], mcpEndpoints: [] };
|
|
3963
4058
|
for (const filePath of scriptFiles) {
|
|
3964
|
-
const content =
|
|
4059
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
3965
4060
|
const relativePath = normalizeBuildRelativePath(filePath);
|
|
3966
4061
|
if (relativePath === "cron-jobs/cron.config.json") {
|
|
3967
4062
|
payload.cronConfig = readCronConfig(filePath, content);
|
|
@@ -4077,7 +4172,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4077
4172
|
return { summary, result };
|
|
4078
4173
|
}
|
|
4079
4174
|
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
4080
|
-
if (!
|
|
4175
|
+
if (!existsSync5(dirPath))
|
|
4081
4176
|
return arrayOfFiles;
|
|
4082
4177
|
const files = readdirSync(dirPath);
|
|
4083
4178
|
files.forEach(function(f) {
|
|
@@ -4091,7 +4186,7 @@ function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
|
4091
4186
|
return arrayOfFiles;
|
|
4092
4187
|
}
|
|
4093
4188
|
function fileToBlob(filePath) {
|
|
4094
|
-
const buffer =
|
|
4189
|
+
const buffer = readFileSync4(filePath);
|
|
4095
4190
|
return new Blob([buffer], { type: src_default.getType(filePath) || "application/octet-stream" });
|
|
4096
4191
|
}
|
|
4097
4192
|
async function deployFullstack(env, overrides) {
|
|
@@ -4125,13 +4220,10 @@ async function deployFullstack(env, overrides) {
|
|
|
4125
4220
|
console.log(`✅ Worker deployed
|
|
4126
4221
|
`);
|
|
4127
4222
|
console.log("\uD83D\uDCCB Step 3: Syncing widget manifest to CMS config...");
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
...overrides.port ? ["--port", shellQuote(String(overrides.port))] : []
|
|
4133
|
-
];
|
|
4134
|
-
execSync2(`bun run sync-widget-manifest -- ${syncArgs.join(" ")}`, { stdio: "inherit", env: childEnv });
|
|
4223
|
+
await syncWidgetManifest({
|
|
4224
|
+
env,
|
|
4225
|
+
url: resolveConfigUrlFromDeployUrl(deployUrl)
|
|
4226
|
+
});
|
|
4135
4227
|
console.log();
|
|
4136
4228
|
const scriptFiles = getAllFiles(BUILD_DIR).filter(isScriptFile);
|
|
4137
4229
|
const scriptsSync = await syncScripts(deployUrl, scriptFiles, "Step 4");
|
|
@@ -4218,7 +4310,7 @@ ${errorText}`);
|
|
|
4218
4310
|
console.log(`✅ Created ${ZIP_FILE}
|
|
4219
4311
|
`);
|
|
4220
4312
|
console.log("☁️ Step 4: Deploying HTML zip to server...");
|
|
4221
|
-
const zipBuffer =
|
|
4313
|
+
const zipBuffer = readFileSync4(ZIP_FILE);
|
|
4222
4314
|
try {
|
|
4223
4315
|
const response = await fetch(DEPLOY_URL, {
|
|
4224
4316
|
method: "POST",
|
|
@@ -4248,7 +4340,7 @@ ${errorText}`);
|
|
|
4248
4340
|
console.error("❌ Deploy failed:", error);
|
|
4249
4341
|
throw error;
|
|
4250
4342
|
} finally {
|
|
4251
|
-
if (
|
|
4343
|
+
if (existsSync5(ZIP_FILE)) {
|
|
4252
4344
|
unlinkSync(ZIP_FILE);
|
|
4253
4345
|
console.log(`
|
|
4254
4346
|
\uD83E\uDDF9 Cleaned up ${ZIP_FILE}`);
|
|
@@ -4267,8 +4359,8 @@ async function deploy(env = "development", overrides = {}) {
|
|
|
4267
4359
|
}
|
|
4268
4360
|
|
|
4269
4361
|
// src/global-config.ts
|
|
4270
|
-
import { existsSync as
|
|
4271
|
-
import { resolve } from "node:path";
|
|
4362
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4363
|
+
import { resolve as resolve2 } from "node:path";
|
|
4272
4364
|
var PROD_ENV = "production";
|
|
4273
4365
|
function isPlainObject(value) {
|
|
4274
4366
|
return !!value && typeof value === "object" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
|
|
@@ -4289,8 +4381,8 @@ function applyMergePatch(target, patch) {
|
|
|
4289
4381
|
function parseFlag(name, value) {
|
|
4290
4382
|
if (value === "null")
|
|
4291
4383
|
return null;
|
|
4292
|
-
const candidatePath =
|
|
4293
|
-
const raw =
|
|
4384
|
+
const candidatePath = resolve2(process.cwd(), value);
|
|
4385
|
+
const raw = existsSync6(candidatePath) ? readFileSync5(candidatePath, "utf-8") : value;
|
|
4294
4386
|
try {
|
|
4295
4387
|
return JSON.parse(raw);
|
|
4296
4388
|
} catch (error) {
|
|
@@ -4485,7 +4577,7 @@ ${pretty(payload)}`);
|
|
|
4485
4577
|
}
|
|
4486
4578
|
const text = pretty(payload);
|
|
4487
4579
|
if (options.out) {
|
|
4488
|
-
const outPath =
|
|
4580
|
+
const outPath = resolve2(process.cwd(), options.out);
|
|
4489
4581
|
writeFileSync3(outPath, `${text}
|
|
4490
4582
|
`, "utf-8");
|
|
4491
4583
|
console.log(`✅ Wrote global config to ${outPath}`);
|
|
@@ -4581,21 +4673,21 @@ async function patchGlobalConfig(options) {
|
|
|
4581
4673
|
}
|
|
4582
4674
|
|
|
4583
4675
|
// src/promote.ts
|
|
4584
|
-
import { existsSync as
|
|
4585
|
-
import { resolve as
|
|
4676
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
4677
|
+
import { resolve as resolve4 } from "node:path";
|
|
4586
4678
|
|
|
4587
4679
|
// src/update-webapp-config.ts
|
|
4588
|
-
import { existsSync as
|
|
4589
|
-
import { resolve as
|
|
4590
|
-
var
|
|
4680
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4681
|
+
import { resolve as resolve3 } from "node:path";
|
|
4682
|
+
var CONFIG_URLS2 = {
|
|
4591
4683
|
local: "http://localhost:5176/api/webapps/config",
|
|
4592
4684
|
development: "https://development-cms.rodyssey.ai/api/webapps/config",
|
|
4593
4685
|
staging: "https://staging-cms.rodyssey.ai/api/webapps/config",
|
|
4594
4686
|
production: "https://cms.rodyssey.ai/api/webapps/config"
|
|
4595
4687
|
};
|
|
4596
4688
|
function parseJsonOption(value, optionName) {
|
|
4597
|
-
const maybePath =
|
|
4598
|
-
const raw =
|
|
4689
|
+
const maybePath = resolve3(process.cwd(), value);
|
|
4690
|
+
const raw = existsSync7(maybePath) ? readFileSync6(maybePath, "utf-8") : value;
|
|
4599
4691
|
try {
|
|
4600
4692
|
const parsed = JSON.parse(raw);
|
|
4601
4693
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -4615,12 +4707,12 @@ function coerceMaybeNull(value) {
|
|
|
4615
4707
|
return value;
|
|
4616
4708
|
}
|
|
4617
4709
|
function resolveConfigUrl(options, required = true) {
|
|
4618
|
-
const configUrl = options.url || process.env.WEBAPP_CONFIG_URL ||
|
|
4710
|
+
const configUrl = options.url || process.env.WEBAPP_CONFIG_URL || CONFIG_URLS2[options.env];
|
|
4619
4711
|
if (!configUrl) {
|
|
4620
4712
|
if (!required)
|
|
4621
4713
|
return;
|
|
4622
4714
|
console.error("❌ Error: no webapp config endpoint configured.");
|
|
4623
|
-
console.error(`\uD83D\uDCA1 Use one of these environments: ${Object.keys(
|
|
4715
|
+
console.error(`\uD83D\uDCA1 Use one of these environments: ${Object.keys(CONFIG_URLS2).join(", ")}, pass --url, or set WEBAPP_CONFIG_URL in your .env file.`);
|
|
4624
4716
|
process.exit(1);
|
|
4625
4717
|
}
|
|
4626
4718
|
const url = new URL(configUrl);
|
|
@@ -4649,7 +4741,7 @@ function buildDetailsPayload(options) {
|
|
|
4649
4741
|
}
|
|
4650
4742
|
return payload;
|
|
4651
4743
|
}
|
|
4652
|
-
function
|
|
4744
|
+
function resolveWebappId2(webappId) {
|
|
4653
4745
|
const resolved = webappId || process.env.WEBAPP_ID;
|
|
4654
4746
|
if (!resolved) {
|
|
4655
4747
|
console.error("❌ Error: WEBAPP_ID is not set. Pass --webapp-id or set it in your .env file.");
|
|
@@ -4657,7 +4749,7 @@ function resolveWebappId(webappId) {
|
|
|
4657
4749
|
}
|
|
4658
4750
|
return resolved;
|
|
4659
4751
|
}
|
|
4660
|
-
function
|
|
4752
|
+
function ensureDeployToken2(env) {
|
|
4661
4753
|
if (process.env.DEPLOY_TOKEN)
|
|
4662
4754
|
return;
|
|
4663
4755
|
console.error("❌ Error: DEPLOY_TOKEN is not set in environment variables.");
|
|
@@ -4669,12 +4761,12 @@ function getConfigUrl(options, required = true) {
|
|
|
4669
4761
|
}
|
|
4670
4762
|
async function fetchWebappConfig(options) {
|
|
4671
4763
|
loadEnv(options.env);
|
|
4672
|
-
const webappId =
|
|
4764
|
+
const webappId = resolveWebappId2(options.webappId);
|
|
4673
4765
|
const CONFIG_URL = getConfigUrl(options);
|
|
4674
4766
|
if (!CONFIG_URL) {
|
|
4675
4767
|
throw new Error("No webapp config endpoint configured.");
|
|
4676
4768
|
}
|
|
4677
|
-
|
|
4769
|
+
ensureDeployToken2(options.env);
|
|
4678
4770
|
const url = new URL(CONFIG_URL);
|
|
4679
4771
|
url.searchParams.set("webappId", webappId);
|
|
4680
4772
|
const response = await fetch(url, {
|
|
@@ -4693,7 +4785,7 @@ ${errorText}`);
|
|
|
4693
4785
|
}
|
|
4694
4786
|
async function getWebappConfig(options) {
|
|
4695
4787
|
loadEnv(options.env);
|
|
4696
|
-
const webappId =
|
|
4788
|
+
const webappId = resolveWebappId2(options.webappId);
|
|
4697
4789
|
const CONFIG_URL = getConfigUrl(options);
|
|
4698
4790
|
if (!CONFIG_URL)
|
|
4699
4791
|
return;
|
|
@@ -4715,7 +4807,7 @@ async function getWebappConfig(options) {
|
|
|
4715
4807
|
}
|
|
4716
4808
|
async function updateWebappConfig(options) {
|
|
4717
4809
|
loadEnv(options.env);
|
|
4718
|
-
const webappId =
|
|
4810
|
+
const webappId = resolveWebappId2(options.webappId);
|
|
4719
4811
|
const details = buildDetailsPayload(options);
|
|
4720
4812
|
if (Object.keys(details).length === 0) {
|
|
4721
4813
|
console.error("❌ Error: no detail fields provided. Use --title, --description, --cover-img, --localization, or --details.");
|
|
@@ -4724,7 +4816,7 @@ async function updateWebappConfig(options) {
|
|
|
4724
4816
|
const payload = { webappId, details };
|
|
4725
4817
|
const CONFIG_URL = getConfigUrl(options, !options.dryRun);
|
|
4726
4818
|
if (!options.dryRun)
|
|
4727
|
-
|
|
4819
|
+
ensureDeployToken2(options.env);
|
|
4728
4820
|
console.log(`⚙️ Updating webapp config for [${options.env}] environment...`);
|
|
4729
4821
|
if (CONFIG_URL) {
|
|
4730
4822
|
console.log(`\uD83D\uDCCD Config URL: ${CONFIG_URL}`);
|
|
@@ -4790,8 +4882,8 @@ function unwrapSourceDetails(payload) {
|
|
|
4790
4882
|
return payload;
|
|
4791
4883
|
}
|
|
4792
4884
|
function parseDetailsOption(value) {
|
|
4793
|
-
const maybePath =
|
|
4794
|
-
const raw =
|
|
4885
|
+
const maybePath = resolve4(process.cwd(), value);
|
|
4886
|
+
const raw = existsSync8(maybePath) ? readFileSync7(maybePath, "utf-8") : value;
|
|
4795
4887
|
try {
|
|
4796
4888
|
const parsed = JSON.parse(raw);
|
|
4797
4889
|
if (!isObject3(parsed)) {
|
|
@@ -4880,9 +4972,9 @@ async function promote(options) {
|
|
|
4880
4972
|
console.info("\uD83D\uDCA1 Run `ro app create --auto` first, or set WEBAPP_ID manually.");
|
|
4881
4973
|
process.exit(1);
|
|
4882
4974
|
}
|
|
4883
|
-
const prodEnvPath =
|
|
4884
|
-
if (
|
|
4885
|
-
const content =
|
|
4975
|
+
const prodEnvPath = resolve4(process.cwd(), PROD_ENV_FILE);
|
|
4976
|
+
if (existsSync8(prodEnvPath)) {
|
|
4977
|
+
const content = readFileSync7(prodEnvPath, "utf-8");
|
|
4886
4978
|
if (/^WEBAPP_ID=.+/m.test(content)) {
|
|
4887
4979
|
console.error(`❌ Error: ${PROD_ENV_FILE} already has WEBAPP_ID. The app appears to be promoted already.`);
|
|
4888
4980
|
process.exit(1);
|
|
@@ -4966,10 +5058,11 @@ ${JSON.stringify(payload, null, 2)}`);
|
|
|
4966
5058
|
|
|
4967
5059
|
// src/upgrade-template.ts
|
|
4968
5060
|
import { execSync as execSync3 } from "node:child_process";
|
|
4969
|
-
import { existsSync as
|
|
5061
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2, copyFileSync, rmSync as rmSync2 } from "node:fs";
|
|
4970
5062
|
import path3 from "node:path";
|
|
4971
5063
|
var TEMPLATES = {
|
|
4972
5064
|
webapp: {
|
|
5065
|
+
key: "webapp",
|
|
4973
5066
|
name: "webapp (SPA)",
|
|
4974
5067
|
repo: "https://github.com/airconcepts/webapp-template.git",
|
|
4975
5068
|
remoteName: "template",
|
|
@@ -5005,6 +5098,7 @@ var TEMPLATES = {
|
|
|
5005
5098
|
]
|
|
5006
5099
|
},
|
|
5007
5100
|
"webapp-fullstack": {
|
|
5101
|
+
key: "webapp-fullstack",
|
|
5008
5102
|
name: "webapp (Fullstack)",
|
|
5009
5103
|
repo: "https://github.com/airconcepts/webapp-template-fullstack.git",
|
|
5010
5104
|
remoteName: "template",
|
|
@@ -5019,20 +5113,31 @@ var TEMPLATES = {
|
|
|
5019
5113
|
]
|
|
5020
5114
|
}
|
|
5021
5115
|
};
|
|
5022
|
-
var
|
|
5116
|
+
var CLI_SCRIPT_DEFAULTS = {
|
|
5023
5117
|
"link-game-sdk": "bunx @rodyssey/cli@latest app update-game-sdk",
|
|
5024
5118
|
deploy: "bunx @rodyssey/cli@latest app deploy",
|
|
5119
|
+
"sync-widget-manifest": "bunx @rodyssey/cli@latest app sync-widget-manifest",
|
|
5025
5120
|
"get-webapp-config": "bunx @rodyssey/cli@latest app config get",
|
|
5026
5121
|
"update-webapp-config": "bunx @rodyssey/cli@latest app config set",
|
|
5027
5122
|
"upgrade-template": "bunx @rodyssey/cli@latest app upgrade-template"
|
|
5028
5123
|
};
|
|
5124
|
+
var FORCE_OVERWRITE_SCRIPT_NAMES = [
|
|
5125
|
+
"link-game-sdk",
|
|
5126
|
+
"deploy",
|
|
5127
|
+
"sync-widget-manifest",
|
|
5128
|
+
"deploy:staging",
|
|
5129
|
+
"deploy:production",
|
|
5130
|
+
"get-webapp-config",
|
|
5131
|
+
"update-webapp-config",
|
|
5132
|
+
"upgrade-template"
|
|
5133
|
+
];
|
|
5029
5134
|
function detectTemplate() {
|
|
5030
|
-
if (
|
|
5135
|
+
if (existsSync9("app")) {
|
|
5031
5136
|
console.log(`\uD83D\uDD0D Detected fullstack template (found app/ directory)
|
|
5032
5137
|
`);
|
|
5033
5138
|
return TEMPLATES["webapp-fullstack"];
|
|
5034
5139
|
}
|
|
5035
|
-
if (
|
|
5140
|
+
if (existsSync9("src")) {
|
|
5036
5141
|
console.log(`\uD83D\uDD0D Detected SPA template (found src/ directory)
|
|
5037
5142
|
`);
|
|
5038
5143
|
return TEMPLATES["webapp"];
|
|
@@ -5041,26 +5146,64 @@ function detectTemplate() {
|
|
|
5041
5146
|
`);
|
|
5042
5147
|
return TEMPLATES["webapp"];
|
|
5043
5148
|
}
|
|
5044
|
-
function
|
|
5149
|
+
function normalizeScripts(value) {
|
|
5150
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
5151
|
+
return {};
|
|
5152
|
+
}
|
|
5153
|
+
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
|
|
5154
|
+
}
|
|
5155
|
+
function readTemplatePackageScripts(template) {
|
|
5156
|
+
try {
|
|
5157
|
+
const rawPackageJson = execSync3(`git show ${template.remoteName}/main:package.json`, {
|
|
5158
|
+
encoding: "utf-8"
|
|
5159
|
+
});
|
|
5160
|
+
const packageJson = JSON.parse(rawPackageJson);
|
|
5161
|
+
return normalizeScripts(packageJson.scripts);
|
|
5162
|
+
} catch (error) {
|
|
5163
|
+
console.log(` ⚠️ Failed to read package.json scripts from template remote: ${error instanceof Error ? error.message : error}`);
|
|
5164
|
+
console.log(" ℹ️ Falling back to built-in CLI script defaults.");
|
|
5165
|
+
return {};
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
function getForcedScriptUpdates(templateScripts) {
|
|
5169
|
+
const normalizedTemplateScripts = normalizeScripts(templateScripts);
|
|
5170
|
+
const updates = {};
|
|
5171
|
+
for (const name of FORCE_OVERWRITE_SCRIPT_NAMES) {
|
|
5172
|
+
const command = normalizedTemplateScripts[name] ?? CLI_SCRIPT_DEFAULTS[name];
|
|
5173
|
+
if (command) {
|
|
5174
|
+
updates[name] = command;
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
return updates;
|
|
5178
|
+
}
|
|
5179
|
+
function applyPackageJsonScriptUpdates(packageJson, scriptUpdates) {
|
|
5180
|
+
if (!packageJson.scripts) {
|
|
5181
|
+
packageJson.scripts = {};
|
|
5182
|
+
}
|
|
5183
|
+
const changes = [];
|
|
5184
|
+
for (const [name, cmd] of Object.entries(scriptUpdates)) {
|
|
5185
|
+
if (packageJson.scripts[name] !== cmd) {
|
|
5186
|
+
const action = packageJson.scripts[name] ? "Updated" : "Added";
|
|
5187
|
+
packageJson.scripts[name] = cmd;
|
|
5188
|
+
changes.push({ name, action });
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
return { updated: changes.length > 0, changes };
|
|
5192
|
+
}
|
|
5193
|
+
function updatePackageJsonScripts(template) {
|
|
5045
5194
|
const pkgPath = "package.json";
|
|
5046
|
-
if (!
|
|
5195
|
+
if (!existsSync9(pkgPath)) {
|
|
5047
5196
|
console.log("⚠️ No package.json found, skipping scripts update");
|
|
5048
5197
|
return;
|
|
5049
5198
|
}
|
|
5050
|
-
const pkg = JSON.parse(
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
if (pkg.scripts[name] !== cmd) {
|
|
5057
|
-
const action = pkg.scripts[name] ? "Updated" : "Added";
|
|
5058
|
-
pkg.scripts[name] = cmd;
|
|
5059
|
-
console.log(` \uD83D\uDCDD ${action} script: "${name}"`);
|
|
5060
|
-
updated = true;
|
|
5061
|
-
}
|
|
5199
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
5200
|
+
const templateScripts = readTemplatePackageScripts(template);
|
|
5201
|
+
const scriptUpdates = getForcedScriptUpdates(templateScripts);
|
|
5202
|
+
const result = applyPackageJsonScriptUpdates(pkg, scriptUpdates);
|
|
5203
|
+
for (const change of result.changes) {
|
|
5204
|
+
console.log(` \uD83D\uDCDD ${change.action} script: "${change.name}"`);
|
|
5062
5205
|
}
|
|
5063
|
-
if (updated) {
|
|
5206
|
+
if (result.updated) {
|
|
5064
5207
|
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
5065
5208
|
`, "utf-8");
|
|
5066
5209
|
console.log(`✅ package.json scripts updated
|
|
@@ -5070,6 +5213,73 @@ function updatePackageJsonScripts() {
|
|
|
5070
5213
|
`);
|
|
5071
5214
|
}
|
|
5072
5215
|
}
|
|
5216
|
+
var SERVER_SCRIPTS_STEP = "vite build --mode server-scripts";
|
|
5217
|
+
var CANONICAL_BUILD_SCRIPT = "tsc -b && vite build && vite build --mode server-scripts && vite build --mode report";
|
|
5218
|
+
var VITE_CONFIG_CANDIDATES = ["vite.config.ts", "vite.config.js", "vite.config.mjs"];
|
|
5219
|
+
var TEMPLATE_VITE_CONFIG_URL = "https://github.com/airconcepts/webapp-template/blob/main/vite.config.ts";
|
|
5220
|
+
function normalizeSegment(segment) {
|
|
5221
|
+
return segment.trim().replace(/\s+/g, " ");
|
|
5222
|
+
}
|
|
5223
|
+
function ensureServerScriptsStep(buildScript) {
|
|
5224
|
+
const segments = buildScript.split("&&").map(normalizeSegment).filter(Boolean);
|
|
5225
|
+
if (segments.includes(SERVER_SCRIPTS_STEP)) {
|
|
5226
|
+
return { status: "present" };
|
|
5227
|
+
}
|
|
5228
|
+
const bareBuildIndex = segments.indexOf("vite build");
|
|
5229
|
+
if (bareBuildIndex === -1) {
|
|
5230
|
+
return { status: "unsafe" };
|
|
5231
|
+
}
|
|
5232
|
+
const next = [...segments];
|
|
5233
|
+
next.splice(bareBuildIndex + 1, 0, SERVER_SCRIPTS_STEP);
|
|
5234
|
+
return { status: "inserted", script: next.join(" && ") };
|
|
5235
|
+
}
|
|
5236
|
+
function viteHandlesServerScripts(viteConfigSource) {
|
|
5237
|
+
return viteConfigSource.includes("server-scripts");
|
|
5238
|
+
}
|
|
5239
|
+
function ensureBuildScript() {
|
|
5240
|
+
const pkgPath = "package.json";
|
|
5241
|
+
if (!existsSync9(pkgPath)) {
|
|
5242
|
+
console.log(" ⚠️ No package.json found, skipping build-script check");
|
|
5243
|
+
return;
|
|
5244
|
+
}
|
|
5245
|
+
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
5246
|
+
const buildScript = pkg.scripts?.build;
|
|
5247
|
+
if (typeof buildScript !== "string" || buildScript.trim() === "") {
|
|
5248
|
+
console.log(` ⚠️ No "build" script found. Add one that runs the server-scripts pass, e.g.:
|
|
5249
|
+
` + ` "build": "${CANONICAL_BUILD_SCRIPT}"`);
|
|
5250
|
+
return;
|
|
5251
|
+
}
|
|
5252
|
+
const result = ensureServerScriptsStep(buildScript);
|
|
5253
|
+
if (result.status === "present") {
|
|
5254
|
+
console.log(" ✅ build script already runs the server-scripts pass");
|
|
5255
|
+
return;
|
|
5256
|
+
}
|
|
5257
|
+
if (result.status === "unsafe") {
|
|
5258
|
+
console.log(` ⚠️ Could not auto-update the build script. Ensure it runs ` + `"vite build --mode server-scripts" after "vite build".
|
|
5259
|
+
` + ` Current: ${buildScript}`);
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
pkg.scripts.build = result.script;
|
|
5263
|
+
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
5264
|
+
`, "utf-8");
|
|
5265
|
+
console.log(` \uD83D\uDCDD Added "vite build --mode server-scripts" to the build script`);
|
|
5266
|
+
}
|
|
5267
|
+
function verifyViteServerScriptsMode() {
|
|
5268
|
+
const configPath2 = VITE_CONFIG_CANDIDATES.find((p) => existsSync9(p));
|
|
5269
|
+
if (!configPath2) {
|
|
5270
|
+
console.log(" ⚠️ No vite.config found. Server scripts in src/api and src/cron-jobs won't be compiled.");
|
|
5271
|
+
return;
|
|
5272
|
+
}
|
|
5273
|
+
const source = readFileSync8(configPath2, "utf-8");
|
|
5274
|
+
if (viteHandlesServerScripts(source)) {
|
|
5275
|
+
console.log(` ✅ ${configPath2} handles the server-scripts build mode`);
|
|
5276
|
+
return;
|
|
5277
|
+
}
|
|
5278
|
+
console.log(` ⚠️ ${configPath2} does not handle the "server-scripts" build mode.
|
|
5279
|
+
` + ` src/api and src/cron-jobs won't compile into dist/, so "app deploy" will
|
|
5280
|
+
` + ` report "No scripts found to sync." Add the server-scripts branch from the template:
|
|
5281
|
+
` + ` ${TEMPLATE_VITE_CONFIG_URL}`);
|
|
5282
|
+
}
|
|
5073
5283
|
function updateCliSkill() {
|
|
5074
5284
|
const cliRemote = "ro-cli";
|
|
5075
5285
|
const cliRepo = "https://github.com/airconcepts/ro-cli.git";
|
|
@@ -5085,7 +5295,7 @@ function updateCliSkill() {
|
|
|
5085
5295
|
}
|
|
5086
5296
|
execSync3(`git fetch ${cliRemote}`, { stdio: "inherit" });
|
|
5087
5297
|
execSync3(`git checkout ${cliRemote}/main -- skills/ro-cli/SKILL.md`, { stdio: "inherit" });
|
|
5088
|
-
if (
|
|
5298
|
+
if (existsSync9("skills/ro-cli/SKILL.md")) {
|
|
5089
5299
|
mkdirSync2(".agent/skills/ro-cli", { recursive: true });
|
|
5090
5300
|
copyFileSync("skills/ro-cli/SKILL.md", ".agent/skills/ro-cli/SKILL.md");
|
|
5091
5301
|
rmSync2("skills", { recursive: true, force: true });
|
|
@@ -5118,7 +5328,7 @@ async function upgradeTemplate() {
|
|
|
5118
5328
|
const checkoutList = template.checkoutFiles.join(" ");
|
|
5119
5329
|
execSync3(`git checkout ${template.remoteName}/main -- ${checkoutList}`, { stdio: "inherit" });
|
|
5120
5330
|
for (const file of template.newFiles) {
|
|
5121
|
-
if (!
|
|
5331
|
+
if (!existsSync9(file)) {
|
|
5122
5332
|
console.log(`\uD83D\uDCC2 Checking out ${file}...`);
|
|
5123
5333
|
try {
|
|
5124
5334
|
mkdirSync2(path3.dirname(file), { recursive: true });
|
|
@@ -5134,7 +5344,13 @@ async function upgradeTemplate() {
|
|
|
5134
5344
|
\uD83D\uDD27 Updating CLI skill documentation...`);
|
|
5135
5345
|
updateCliSkill();
|
|
5136
5346
|
console.log("\uD83D\uDCE6 Updating package.json scripts...");
|
|
5137
|
-
updatePackageJsonScripts();
|
|
5347
|
+
updatePackageJsonScripts(template);
|
|
5348
|
+
if (template.key === "webapp") {
|
|
5349
|
+
console.log("\uD83E\uDDE9 Verifying server-scripts build chain...");
|
|
5350
|
+
ensureBuildScript();
|
|
5351
|
+
verifyViteServerScriptsMode();
|
|
5352
|
+
console.log();
|
|
5353
|
+
}
|
|
5138
5354
|
console.log("✅ Template upgrade complete! Please check git status for changes.");
|
|
5139
5355
|
} catch (error) {
|
|
5140
5356
|
console.error("❌ Upgrade failed:", error);
|
|
@@ -5317,15 +5533,15 @@ Available templates:
|
|
|
5317
5533
|
input: process.stdin,
|
|
5318
5534
|
output: process.stdout
|
|
5319
5535
|
});
|
|
5320
|
-
return new Promise((
|
|
5536
|
+
return new Promise((resolve5) => {
|
|
5321
5537
|
rl.question(`Select a template (1-${entries.length}): `, (answer) => {
|
|
5322
5538
|
rl.close();
|
|
5323
5539
|
const index = parseInt(answer, 10) - 1;
|
|
5324
5540
|
if (index >= 0 && index < entries.length) {
|
|
5325
|
-
|
|
5541
|
+
resolve5(entries[index].name);
|
|
5326
5542
|
} else {
|
|
5327
5543
|
console.error("Invalid selection, defaulting to 'webapp'");
|
|
5328
|
-
|
|
5544
|
+
resolve5("webapp");
|
|
5329
5545
|
}
|
|
5330
5546
|
});
|
|
5331
5547
|
});
|
|
@@ -5406,6 +5622,9 @@ app.command("update-game-sdk").description("Download and update the GameSDK libr
|
|
|
5406
5622
|
app.command("deploy").description("Build and deploy the webapp to the server").option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--host <host>", "Override the deploy host").option("--port <port>", "Override the deploy port", parseInt).action(async (options) => {
|
|
5407
5623
|
await deploy(options.env, { host: options.host, port: options.port });
|
|
5408
5624
|
});
|
|
5625
|
+
app.command("sync-widget-manifest").description("Sync the built widget manifest to the CMS webapp config").option("-e, --env <environment>", "Target environment (local | development | staging | production)", "development").option("--manifest <path>", "Path to widgets.manifest.json. Defaults to build/client/widgets.manifest.json or dist/widgets.manifest.json").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").option("--dry-run", "Print the request payload without sending it").action(async (options) => {
|
|
5626
|
+
await syncWidgetManifest(options);
|
|
5627
|
+
});
|
|
5409
5628
|
var config = app.command("config").description("Manage webapp metadata config");
|
|
5410
5629
|
addConfigTargetOptions(config.command("get").description("Pull the current webapp metadata config from the CMS").option("--out <file>", "Write the config JSON to a file")).action(async (options) => {
|
|
5411
5630
|
await getWebappConfig(options);
|