@revoengine/cli 1.0.0 → 1.0.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 +23 -12
- package/dist/src/cli.js +5 -1
- package/dist/src/client.d.ts +0 -2
- package/dist/src/client.js +1 -12
- package/dist/src/commands/auth.js +6 -32
- package/dist/src/commands/component.js +1 -1
- package/dist/src/commands/project.js +2 -39
- package/dist/src/config.d.ts +14 -14
- package/dist/src/config.js +58 -58
- package/dist/src/project.d.ts +1 -8
- package/dist/src/project.js +18 -20
- package/dist/src/runtime-view.js +1 -7
- package/dist/src/ui.js +2 -5
- package/dist/src/update-notifier.d.ts +16 -0
- package/dist/src/update-notifier.js +142 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ Log in and save your default credentials:
|
|
|
27
27
|
revo auth login
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
`revo auth login` opens an interactive terminal prompt for your API key
|
|
30
|
+
`revo auth login` opens an interactive terminal prompt for your API key. If you are already logged in, the CLI warns and asks you to `revo auth logout` first.
|
|
31
31
|
|
|
32
32
|
Check the active session:
|
|
33
33
|
|
|
@@ -52,10 +52,9 @@ Initialize a project for ambient low-code editor globals:
|
|
|
52
52
|
```bash
|
|
53
53
|
revo project
|
|
54
54
|
revo project init ./app
|
|
55
|
-
revo project switch a931e927-738d-42a4-b486-5fc19bfb3dfb
|
|
56
55
|
```
|
|
57
56
|
|
|
58
|
-
This writes `.revoengine/types/revo.editor.d.ts`, `.revoengine/revo.json`, patches `tsconfig.json` or `jsconfig.json`, and updates `.gitignore` so the generated type bundle stays local by default.
|
|
57
|
+
This writes `.revoengine/types/revo.editor.d.ts`, `.revoengine/revo.json`, patches `tsconfig.json` or `jsconfig.json`, and updates `.gitignore` so the generated type bundle stays local by default.
|
|
59
58
|
|
|
60
59
|
## Common commands
|
|
61
60
|
|
|
@@ -140,7 +139,7 @@ Bulk sync behavior:
|
|
|
140
139
|
- Pull skips with `changed` when local files differ from the remote contract.
|
|
141
140
|
- Pull skips with `stale version` when the local version is older than the remote version, unless `--stale` or `--force` is passed.
|
|
142
141
|
- Push treats backend `Not modified` responses as skipped instead of failing the whole run.
|
|
143
|
-
- Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the
|
|
142
|
+
- Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the authenticated sandbox `debug` endpoint.
|
|
144
143
|
- Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
|
|
145
144
|
- Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
|
|
146
145
|
|
|
@@ -148,8 +147,8 @@ Bulk sync behavior:
|
|
|
148
147
|
|
|
149
148
|
The CLI reads configuration in this order:
|
|
150
149
|
|
|
151
|
-
1. CLI flags such as `--url
|
|
152
|
-
2. Environment variables such as `REVO_URL
|
|
150
|
+
1. CLI flags such as `--url` and `--token`
|
|
151
|
+
2. Environment variables such as `REVO_URL` and `REVO_TOKEN`
|
|
153
152
|
3. Stored config in the OS config directory
|
|
154
153
|
|
|
155
154
|
This means environment variables override the locally stored config, which is the intended setup for CI and non-interactive automation.
|
|
@@ -158,7 +157,7 @@ Use `--url` for one-off local or dedicated API targets:
|
|
|
158
157
|
|
|
159
158
|
```bash
|
|
160
159
|
revo --url https://api.test.revong.com -i
|
|
161
|
-
revo --url https://api.test.revong.com auth login
|
|
160
|
+
revo --url https://api.test.revong.com auth login
|
|
162
161
|
revo --url https://api.revoengine.com endpoints
|
|
163
162
|
```
|
|
164
163
|
|
|
@@ -167,13 +166,11 @@ Use environment variables for CI/CD or shell profiles:
|
|
|
167
166
|
```bash
|
|
168
167
|
# TEST
|
|
169
168
|
export REVO_URL="https://api.test.revong.com"
|
|
170
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
171
169
|
export REVO_TOKEN="your-test-token"
|
|
172
170
|
revo endpoints
|
|
173
171
|
|
|
174
172
|
# PROD
|
|
175
173
|
export REVO_URL="https://api.revoengine.com"
|
|
176
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
177
174
|
export REVO_TOKEN="your-prod-token"
|
|
178
175
|
revo endpoints
|
|
179
176
|
```
|
|
@@ -181,17 +178,15 @@ revo endpoints
|
|
|
181
178
|
Supported environment variables:
|
|
182
179
|
|
|
183
180
|
- `REVO_URL` or `REVO_BASE_URL`
|
|
184
|
-
- `REVO_INSTANCE`
|
|
185
181
|
- `REVO_TOKEN` or `REVO_API_KEY`
|
|
182
|
+
- `REVO_NO_UPDATE_NOTIFIER`
|
|
186
183
|
- `REVOENGINE_URL` or `REVOENGINE_BASE_URL`
|
|
187
|
-
- `REVOENGINE_INSTANCE`
|
|
188
184
|
- `REVOENGINE_TOKEN` or `REVOENGINE_API_KEY`
|
|
189
185
|
|
|
190
186
|
Example for CI:
|
|
191
187
|
|
|
192
188
|
```bash
|
|
193
189
|
export REVO_URL="https://api.revoengine.com"
|
|
194
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
195
190
|
export REVO_TOKEN="your-ci-token"
|
|
196
191
|
revo endpoints
|
|
197
192
|
```
|
|
@@ -201,6 +196,22 @@ Stored config lives in:
|
|
|
201
196
|
- macOS and Linux: `~/.config/revoengine`
|
|
202
197
|
- Windows: `%APPDATA%/revoengine`
|
|
203
198
|
|
|
199
|
+
## Update notifications
|
|
200
|
+
|
|
201
|
+
When running in an interactive terminal, the CLI checks the npm registry at most once every 24 hours and prints a colored notice on stderr when a newer `@revoengine/cli` version is available.
|
|
202
|
+
|
|
203
|
+
Disable the check for one run:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
revo --no-update-notifier endpoints
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Disable it through the environment:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
export REVO_NO_UPDATE_NOTIFIER=1
|
|
213
|
+
```
|
|
214
|
+
|
|
204
215
|
## Local development
|
|
205
216
|
|
|
206
217
|
```bash
|
package/dist/src/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { resolveRuntimeConfig } from "./config.js";
|
|
|
5
5
|
import { handleAuthCommand, handleComponentCommand, handleEndpointsCommand, handleInfoCommand, handleProjectCommand, handleRequestCommand, handleSearchCommand, } from "./commands/index.js";
|
|
6
6
|
import { buildRuntimeViewModel } from "./runtime-view.js";
|
|
7
7
|
import { renderHelp, renderOverview } from "./ui.js";
|
|
8
|
+
import { maybeNotifyUpdateAvailable } from "./update-notifier.js";
|
|
8
9
|
import { readPackageVersion } from "./utils.js";
|
|
9
10
|
const packageVersion = readPackageVersion();
|
|
10
11
|
const ANSI = {
|
|
@@ -137,7 +138,6 @@ function printError(error) {
|
|
|
137
138
|
function resolveClient(args) {
|
|
138
139
|
const runtime = resolveRuntimeConfig({
|
|
139
140
|
baseUrl: typeof args.url === 'string' ? args.url : typeof args.baseUrl === 'string' ? args.baseUrl : undefined,
|
|
140
|
-
instance: typeof args.instance === 'string' ? args.instance : typeof args.i === 'string' ? args.i : undefined,
|
|
141
141
|
token: typeof args.token === 'string' ? args.token : typeof args.t === 'string' ? args.t : undefined,
|
|
142
142
|
});
|
|
143
143
|
return new RevoClient(runtime);
|
|
@@ -159,6 +159,10 @@ function printHelp() {
|
|
|
159
159
|
}
|
|
160
160
|
export async function main(argv = process.argv.slice(2)) {
|
|
161
161
|
const args = parseArgs(argv);
|
|
162
|
+
await maybeNotifyUpdateAvailable({
|
|
163
|
+
args,
|
|
164
|
+
currentVersion: packageVersion,
|
|
165
|
+
});
|
|
162
166
|
const command = args._[0];
|
|
163
167
|
const noPositionals = args._.length === 0;
|
|
164
168
|
if (command === 'version' || ((args.version !== undefined || args.V !== undefined) && noPositionals)) {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -11,7 +11,6 @@ export type ComponentListRequest = {
|
|
|
11
11
|
};
|
|
12
12
|
export type ClientOptions = {
|
|
13
13
|
baseUrl?: string;
|
|
14
|
-
instance?: string;
|
|
15
14
|
token?: string;
|
|
16
15
|
fetch?: typeof fetch;
|
|
17
16
|
};
|
|
@@ -37,7 +36,6 @@ declare function buildUrl(baseUrl: string, requestPath: string, query?: Record<s
|
|
|
37
36
|
declare function normalizeToken(token?: string): string;
|
|
38
37
|
export declare class RevoClient {
|
|
39
38
|
baseUrl: string;
|
|
40
|
-
instance: string;
|
|
41
39
|
token: string;
|
|
42
40
|
fetchImpl: typeof fetch | undefined;
|
|
43
41
|
constructor(options?: ClientOptions);
|
package/dist/src/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL,
|
|
1
|
+
import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL, readAuthValidationState, resolveRuntimeConfig, saveAuthValidationState, } from "./config.js";
|
|
2
2
|
import { createSpinner } from "./spinner.js";
|
|
3
3
|
export class ApiError extends Error {
|
|
4
4
|
status;
|
|
@@ -127,13 +127,11 @@ async function readResponseData(response) {
|
|
|
127
127
|
}
|
|
128
128
|
export class RevoClient {
|
|
129
129
|
baseUrl;
|
|
130
|
-
instance;
|
|
131
130
|
token;
|
|
132
131
|
fetchImpl;
|
|
133
132
|
constructor(options = {}) {
|
|
134
133
|
const config = resolveRuntimeConfig(options);
|
|
135
134
|
this.baseUrl = config.baseUrl;
|
|
136
|
-
this.instance = config.instance;
|
|
137
135
|
this.token = config.token;
|
|
138
136
|
this.fetchImpl = options.fetch || globalThis.fetch;
|
|
139
137
|
}
|
|
@@ -143,7 +141,6 @@ export class RevoClient {
|
|
|
143
141
|
get authValidationKey() {
|
|
144
142
|
return buildAuthValidationKey({
|
|
145
143
|
baseUrl: this.baseUrl,
|
|
146
|
-
instance: this.instance,
|
|
147
144
|
token: this.token,
|
|
148
145
|
});
|
|
149
146
|
}
|
|
@@ -154,12 +151,6 @@ export class RevoClient {
|
|
|
154
151
|
if (!this.token) {
|
|
155
152
|
throw new Error('Missing API token. Run `revo auth login` or set REVO_TOKEN / REVO_API_KEY.');
|
|
156
153
|
}
|
|
157
|
-
if (!this.instance) {
|
|
158
|
-
throw new Error('Missing instance UUID. Run `revo auth login` or set REVO_INSTANCE.');
|
|
159
|
-
}
|
|
160
|
-
if (!isUuid(this.instance)) {
|
|
161
|
-
throw new Error(`Invalid instance UUID: ${this.instance}`);
|
|
162
|
-
}
|
|
163
154
|
}
|
|
164
155
|
async validateSession(options = {}) {
|
|
165
156
|
this.assertReady();
|
|
@@ -215,8 +206,6 @@ export class RevoClient {
|
|
|
215
206
|
const headers = new Headers(options.headers || {});
|
|
216
207
|
headers.set('Authorization', this.authHeader);
|
|
217
208
|
headers.set('x-api-key', this.token);
|
|
218
|
-
headers.set('instance', this.instance);
|
|
219
|
-
headers.set('x-api-instance', this.instance);
|
|
220
209
|
let body = options.body;
|
|
221
210
|
if (body !== undefined && body !== null && method.toUpperCase() !== 'GET') {
|
|
222
211
|
headers.set('Content-Type', 'application/json');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { clearAuthValidationState, clearStoredConfig, getConfigDir,
|
|
2
|
+
import { clearAuthValidationState, clearStoredConfig, getConfigDir, loadStoredConfig, resolveRuntimeConfig, saveStoredConfig, } from "../config.js";
|
|
3
3
|
import { readLegacyImportFromFile } from "../legacy.js";
|
|
4
4
|
import { promptText } from "../prompt.js";
|
|
5
5
|
import { buildRuntimeViewModel } from "../runtime-view.js";
|
|
@@ -8,28 +8,19 @@ import { readBoolFlag, readFlag } from "../utils.js";
|
|
|
8
8
|
function getConfigInput(args) {
|
|
9
9
|
return resolveRuntimeConfig({
|
|
10
10
|
baseUrl: readFlag(args, ['url', 'baseUrl']) || '',
|
|
11
|
-
instance: readFlag(args, ['instance', 'i']) || '',
|
|
12
11
|
token: readFlag(args, ['token', 't']) || '',
|
|
13
12
|
});
|
|
14
13
|
}
|
|
15
14
|
async function promptMissingConfig(runtime) {
|
|
16
15
|
let token = runtime.token;
|
|
17
|
-
let instance = runtime.instance;
|
|
18
16
|
if (!token) {
|
|
19
17
|
token = (await promptText('Paste API key (input is visible)')).trim();
|
|
20
18
|
}
|
|
21
|
-
if (!instance) {
|
|
22
|
-
instance = (await promptText('Paste instance ID')).trim();
|
|
23
|
-
}
|
|
24
19
|
if (!token) {
|
|
25
20
|
throw new Error('API key is required.');
|
|
26
21
|
}
|
|
27
|
-
if (!instance || !isUuid(instance)) {
|
|
28
|
-
throw new Error('Instance ID must be a UUID.');
|
|
29
|
-
}
|
|
30
22
|
return {
|
|
31
23
|
token,
|
|
32
|
-
instance,
|
|
33
24
|
baseUrl: runtime.baseUrl,
|
|
34
25
|
};
|
|
35
26
|
}
|
|
@@ -44,7 +35,6 @@ export async function handleAuthCommand(context) {
|
|
|
44
35
|
if (subcommand === 'get') {
|
|
45
36
|
const stored = loadStoredConfig();
|
|
46
37
|
print({
|
|
47
|
-
instance: stored.instance || null,
|
|
48
38
|
baseUrl: stored.baseUrl,
|
|
49
39
|
tokenConfigured: Boolean(stored.token),
|
|
50
40
|
configDir: getConfigDir(),
|
|
@@ -52,21 +42,7 @@ export async function handleAuthCommand(context) {
|
|
|
52
42
|
return;
|
|
53
43
|
}
|
|
54
44
|
if (subcommand === 'set') {
|
|
55
|
-
|
|
56
|
-
if (!instance) {
|
|
57
|
-
throw new Error('Missing instance UUID.');
|
|
58
|
-
}
|
|
59
|
-
if (!isUuid(instance)) {
|
|
60
|
-
throw new Error('Instance must be a UUID.');
|
|
61
|
-
}
|
|
62
|
-
const stored = loadStoredConfig();
|
|
63
|
-
saveStoredConfig({
|
|
64
|
-
baseUrl: stored.baseUrl,
|
|
65
|
-
token: stored.token,
|
|
66
|
-
instance,
|
|
67
|
-
});
|
|
68
|
-
println(`Saved default instance ${instance}.`);
|
|
69
|
-
return;
|
|
45
|
+
throw new Error('`revo auth set` was removed because API-key credentials are no longer instance-scoped.');
|
|
70
46
|
}
|
|
71
47
|
if (subcommand === 'status') {
|
|
72
48
|
const view = await buildRuntimeViewModel(context, { forceValidation: true });
|
|
@@ -86,9 +62,8 @@ export async function handleAuthCommand(context) {
|
|
|
86
62
|
println(`Imported legacy config from ${filePath}.`);
|
|
87
63
|
if (!offline) {
|
|
88
64
|
client.token = imported.token;
|
|
89
|
-
client.instance = imported.instance;
|
|
90
65
|
client.baseUrl = imported.baseUrl;
|
|
91
|
-
if (imported.token
|
|
66
|
+
if (imported.token) {
|
|
92
67
|
try {
|
|
93
68
|
await client.validateSession({ force: true });
|
|
94
69
|
}
|
|
@@ -105,14 +80,13 @@ export async function handleAuthCommand(context) {
|
|
|
105
80
|
throw new Error('Unknown auth command.');
|
|
106
81
|
}
|
|
107
82
|
const stored = loadStoredConfig();
|
|
108
|
-
if (stored.token
|
|
109
|
-
println(
|
|
83
|
+
if (stored.token) {
|
|
84
|
+
println('Warning: already logged in. Run `revo auth logout` before logging in again.');
|
|
110
85
|
return;
|
|
111
86
|
}
|
|
112
87
|
const runtime = getConfigInput(args);
|
|
113
|
-
const next = runtime.token
|
|
88
|
+
const next = runtime.token ? runtime : await promptMissingConfig(runtime);
|
|
114
89
|
client.token = next.token;
|
|
115
|
-
client.instance = next.instance;
|
|
116
90
|
client.baseUrl = next.baseUrl;
|
|
117
91
|
try {
|
|
118
92
|
await client.validateSession({ force: true });
|
|
@@ -837,7 +837,7 @@ async function debugSingleComponent(context, componentId) {
|
|
|
837
837
|
const profile = await context.client.me();
|
|
838
838
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
839
839
|
if (!sandboxEndpoint) {
|
|
840
|
-
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run
|
|
840
|
+
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
841
841
|
}
|
|
842
842
|
const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), {
|
|
843
843
|
elements: buildDebugElements(component),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import { buildProjectSyncState, buildEditorTypesUrl, extractSandboxEndpoint, extractEditorTypesBundle, findProjectMetadataFile, resolveProjectInvocation, resolveProjectRoot, resolveProjectTarget, syncProjectFiles, } from "../project.js";
|
|
2
|
+
import { buildProjectSyncState, buildEditorTypesUrl, extractSandboxEndpoint, extractEditorTypesBundle, resolveProjectInvocation, resolveProjectTarget, syncProjectFiles, } from "../project.js";
|
|
4
3
|
function printSyncSummary(println, targetDir, result, prefix) {
|
|
5
4
|
println(`${prefix} ${targetDir}`);
|
|
6
5
|
println(`Wrote ${path.relative(targetDir, result.typesFile)}`);
|
|
@@ -20,12 +19,11 @@ async function resolveProjectSyncInput(context) {
|
|
|
20
19
|
const profile = await client.me();
|
|
21
20
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
22
21
|
if (!sandboxEndpoint) {
|
|
23
|
-
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so editor types cannot be initialized
|
|
22
|
+
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so editor types cannot be initialized.');
|
|
24
23
|
}
|
|
25
24
|
const bundle = extractEditorTypesBundle(await client.getEditorTypes(buildEditorTypesUrl(sandboxEndpoint)));
|
|
26
25
|
return buildProjectSyncState({
|
|
27
26
|
fallbackEndpoint: sandboxEndpoint,
|
|
28
|
-
fallbackInstanceId: client.instance,
|
|
29
27
|
bundle,
|
|
30
28
|
});
|
|
31
29
|
}
|
|
@@ -35,41 +33,6 @@ export async function handleProjectCommand(context) {
|
|
|
35
33
|
if (invocation.action === 'update') {
|
|
36
34
|
throw new Error('`revo project update` is not implemented yet.');
|
|
37
35
|
}
|
|
38
|
-
if (invocation.action === 'switch') {
|
|
39
|
-
if (invocation.extraArgs.length > 0) {
|
|
40
|
-
throw new Error('Project switch accepts exactly one instance ID.');
|
|
41
|
-
}
|
|
42
|
-
const instanceId = invocation.targetArg || '';
|
|
43
|
-
if (!instanceId) {
|
|
44
|
-
throw new Error('Missing instance UUID. Usage: `revo project switch <instanceId>`.');
|
|
45
|
-
}
|
|
46
|
-
if (!isUuid(instanceId)) {
|
|
47
|
-
throw new Error('Instance ID must be a UUID.');
|
|
48
|
-
}
|
|
49
|
-
const metadataFile = findProjectMetadataFile(cwd);
|
|
50
|
-
if (!metadataFile) {
|
|
51
|
-
throw new Error('Current directory is not inside an initialized Revo project.');
|
|
52
|
-
}
|
|
53
|
-
const projectRoot = resolveProjectRoot(cwd);
|
|
54
|
-
if (!projectRoot) {
|
|
55
|
-
throw new Error('Unable to resolve the current Revo project root.');
|
|
56
|
-
}
|
|
57
|
-
const storedConfigs = loadStoredConfigIndex();
|
|
58
|
-
const selected = storedConfigs.instances[instanceId];
|
|
59
|
-
if (!selected) {
|
|
60
|
-
throw new Error(`No stored credentials found for instance ${instanceId}. Run \`revo auth login --instance ${instanceId}\` first.`);
|
|
61
|
-
}
|
|
62
|
-
if (!selected.token) {
|
|
63
|
-
throw new Error(`Stored credentials for instance ${instanceId} are incomplete. Run \`revo auth login --instance ${instanceId}\` again.`);
|
|
64
|
-
}
|
|
65
|
-
client.baseUrl = selected.baseUrl;
|
|
66
|
-
client.instance = instanceId;
|
|
67
|
-
client.token = selected.token;
|
|
68
|
-
const syncInput = await resolveProjectSyncInput(context);
|
|
69
|
-
const result = syncProjectFiles(projectRoot, syncInput);
|
|
70
|
-
printSyncSummary(println, projectRoot, result, `Switched Revo project to ${instanceId} in`);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
36
|
if (invocation.extraArgs.length > 0) {
|
|
74
37
|
throw new Error('Project init accepts at most one path argument.');
|
|
75
38
|
}
|
package/dist/src/config.d.ts
CHANGED
|
@@ -2,22 +2,14 @@ export declare const APP_NAME = "revoengine";
|
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://api.revoengine.com";
|
|
3
3
|
export type RuntimeConfig = {
|
|
4
4
|
baseUrl: string;
|
|
5
|
-
instance: string;
|
|
6
5
|
token: string;
|
|
7
6
|
};
|
|
8
7
|
export type RuntimeConfigOptions = {
|
|
9
8
|
baseUrl?: string;
|
|
10
|
-
instance?: string;
|
|
11
9
|
token?: string;
|
|
12
10
|
};
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
baseUrl: string;
|
|
16
|
-
token: string;
|
|
17
|
-
};
|
|
18
|
-
type StoredConfigFile = {
|
|
19
|
-
defaultInstance: string;
|
|
20
|
-
instances: Record<string, StoredInstanceConfig>;
|
|
11
|
+
type LegacyRuntimeConfig = RuntimeConfig & {
|
|
12
|
+
instance?: string;
|
|
21
13
|
};
|
|
22
14
|
export type AuthValidationState = {
|
|
23
15
|
key: string;
|
|
@@ -35,11 +27,20 @@ export declare function getConfigPaths(): {
|
|
|
35
27
|
};
|
|
36
28
|
export declare function readProjectInstanceId(startDir?: string): string;
|
|
37
29
|
export declare function isUuid(value: unknown): value is string;
|
|
38
|
-
export declare function saveStoredConfig(nextConfig: Partial<
|
|
30
|
+
export declare function saveStoredConfig(nextConfig: Partial<LegacyRuntimeConfig>): void;
|
|
39
31
|
export declare function clearStoredConfig(): void;
|
|
40
|
-
export declare function loadStoredConfigIndex():
|
|
32
|
+
export declare function loadStoredConfigIndex(): {
|
|
33
|
+
defaultInstance: string;
|
|
34
|
+
instances: {
|
|
35
|
+
default: {
|
|
36
|
+
baseUrl: string;
|
|
37
|
+
token: string;
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
default?: undefined;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
41
43
|
export declare function loadStoredConfig(options?: {
|
|
42
|
-
instance?: string;
|
|
43
44
|
allowLegacy?: boolean;
|
|
44
45
|
}): RuntimeConfig;
|
|
45
46
|
export declare function buildAuthValidationKey(runtime: RuntimeConfigOptions): string;
|
|
@@ -48,7 +49,6 @@ export declare function saveAuthValidationState(state: AuthValidationState): voi
|
|
|
48
49
|
export declare function clearAuthValidationState(key?: string): void;
|
|
49
50
|
export declare function resolveRuntimeConfig(options?: RuntimeConfigOptions): {
|
|
50
51
|
baseUrl: string;
|
|
51
|
-
instance: string;
|
|
52
52
|
token: string;
|
|
53
53
|
};
|
|
54
54
|
export {};
|
package/dist/src/config.js
CHANGED
|
@@ -73,20 +73,10 @@ export function saveStoredConfig(nextConfig) {
|
|
|
73
73
|
const { dir, configFile, credentialsFile } = getConfigPaths();
|
|
74
74
|
ensureDirectory(dir);
|
|
75
75
|
const current = readStoredConfigFile();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
removeFileIfExists(credentialsFile);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const currentEntry = current.instances[instance];
|
|
84
|
-
current.instances[instance] = {
|
|
85
|
-
baseUrl: nextConfig.baseUrl || currentEntry?.baseUrl || fallback?.baseUrl || DEFAULT_BASE_URL,
|
|
86
|
-
token: nextConfig.token || currentEntry?.token || fallback?.token || '',
|
|
87
|
-
};
|
|
88
|
-
current.defaultInstance = instance;
|
|
89
|
-
writeJsonFile(configFile, current);
|
|
76
|
+
writeJsonFile(configFile, {
|
|
77
|
+
baseUrl: nextConfig.baseUrl || current.baseUrl || DEFAULT_BASE_URL,
|
|
78
|
+
token: nextConfig.token || current.token || '',
|
|
79
|
+
});
|
|
90
80
|
removeFileIfExists(credentialsFile);
|
|
91
81
|
}
|
|
92
82
|
export function clearStoredConfig() {
|
|
@@ -131,8 +121,8 @@ function migrateStoredConfigBestEffort(configFile, credentialsFile, nextConfig)
|
|
|
131
121
|
}
|
|
132
122
|
function emptyStoredConfigFile() {
|
|
133
123
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
124
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
125
|
+
token: '',
|
|
136
126
|
};
|
|
137
127
|
}
|
|
138
128
|
function isRecord(value) {
|
|
@@ -151,6 +141,24 @@ function normalizeInstanceConfig(value) {
|
|
|
151
141
|
token: typeof value.token === 'string' ? value.token : '',
|
|
152
142
|
};
|
|
153
143
|
}
|
|
144
|
+
function parseFlatConfig(raw, credentials) {
|
|
145
|
+
if (!isRecord(raw) || isRecord(raw.instances)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const token = typeof raw.token === 'string'
|
|
149
|
+
? raw.token
|
|
150
|
+
: isRecord(credentials) && typeof credentials.token === 'string'
|
|
151
|
+
? credentials.token
|
|
152
|
+
: '';
|
|
153
|
+
return {
|
|
154
|
+
baseUrl: typeof raw.baseUrl === 'string'
|
|
155
|
+
? raw.baseUrl
|
|
156
|
+
: typeof raw.url === 'string'
|
|
157
|
+
? raw.url
|
|
158
|
+
: DEFAULT_BASE_URL,
|
|
159
|
+
token,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
154
162
|
function parseMappedConfig(raw) {
|
|
155
163
|
if (!isRecord(raw) || !isRecord(raw.instances)) {
|
|
156
164
|
return null;
|
|
@@ -169,10 +177,13 @@ function parseMappedConfig(raw) {
|
|
|
169
177
|
const defaultInstance = isUuid(configuredDefault) && instances[configuredDefault]
|
|
170
178
|
? configuredDefault
|
|
171
179
|
: Object.keys(instances)[0] || '';
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
180
|
+
const selected = defaultInstance ? instances[defaultInstance] : undefined;
|
|
181
|
+
return selected
|
|
182
|
+
? {
|
|
183
|
+
baseUrl: selected.baseUrl,
|
|
184
|
+
token: selected.token,
|
|
185
|
+
}
|
|
186
|
+
: emptyStoredConfigFile();
|
|
176
187
|
}
|
|
177
188
|
function parseLegacyConfig(config, credentials) {
|
|
178
189
|
const baseUrl = isRecord(config) && typeof config.baseUrl === 'string'
|
|
@@ -180,58 +191,56 @@ function parseLegacyConfig(config, credentials) {
|
|
|
180
191
|
: isRecord(config) && typeof config.url === 'string'
|
|
181
192
|
? config.url
|
|
182
193
|
: DEFAULT_BASE_URL;
|
|
183
|
-
const instance = isRecord(config) && typeof config.instance === 'string' ? config.instance : '';
|
|
184
194
|
const token = isRecord(credentials) && typeof credentials.token === 'string' ? credentials.token : '';
|
|
185
|
-
if (!instance) {
|
|
186
|
-
return emptyStoredConfigFile();
|
|
187
|
-
}
|
|
188
195
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
[instance]: {
|
|
192
|
-
baseUrl,
|
|
193
|
-
token,
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
+
baseUrl,
|
|
197
|
+
token,
|
|
196
198
|
};
|
|
197
199
|
}
|
|
198
200
|
function readStoredConfigFile() {
|
|
199
201
|
const { configFile, credentialsFile } = getConfigPaths();
|
|
200
202
|
const config = fs.existsSync(configFile) ? readJsonFile(configFile) : {};
|
|
201
203
|
const credentials = fs.existsSync(credentialsFile) ? readJsonFile(credentialsFile) : {};
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
204
|
+
const flat = parseFlatConfig(config, credentials);
|
|
205
|
+
if (flat) {
|
|
204
206
|
if (fs.existsSync(credentialsFile)) {
|
|
205
|
-
migrateStoredConfigBestEffort(configFile, credentialsFile,
|
|
207
|
+
migrateStoredConfigBestEffort(configFile, credentialsFile, flat);
|
|
206
208
|
}
|
|
209
|
+
return flat;
|
|
210
|
+
}
|
|
211
|
+
const mapped = parseMappedConfig(config);
|
|
212
|
+
if (mapped) {
|
|
213
|
+
migrateStoredConfigBestEffort(configFile, credentialsFile, mapped);
|
|
207
214
|
return mapped;
|
|
208
215
|
}
|
|
209
216
|
const legacy = parseLegacyConfig(config, credentials);
|
|
210
|
-
if (legacy.
|
|
217
|
+
if (legacy.token || legacy.baseUrl !== DEFAULT_BASE_URL) {
|
|
211
218
|
migrateStoredConfigBestEffort(configFile, credentialsFile, legacy);
|
|
212
219
|
}
|
|
213
220
|
return legacy;
|
|
214
221
|
}
|
|
215
222
|
export function loadStoredConfigIndex() {
|
|
216
|
-
return readStoredConfigFile();
|
|
217
|
-
}
|
|
218
|
-
function readStoredConfig(options = {}) {
|
|
219
223
|
const stored = readStoredConfigFile();
|
|
220
|
-
const selectedInstance = options.instance && stored.instances[options.instance]
|
|
221
|
-
? options.instance
|
|
222
|
-
: stored.defaultInstance;
|
|
223
|
-
const selected = selectedInstance ? stored.instances[selectedInstance] : undefined;
|
|
224
224
|
return {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
defaultInstance: '',
|
|
226
|
+
instances: stored.token
|
|
227
|
+
? {
|
|
228
|
+
default: {
|
|
229
|
+
baseUrl: stored.baseUrl,
|
|
230
|
+
token: stored.token,
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
: {},
|
|
228
234
|
};
|
|
229
235
|
}
|
|
236
|
+
function readStoredConfig() {
|
|
237
|
+
return readStoredConfigFile();
|
|
238
|
+
}
|
|
230
239
|
function mergeWithLegacy(stored, options = {}) {
|
|
231
240
|
if (options.allowLegacy === false) {
|
|
232
241
|
return stored;
|
|
233
242
|
}
|
|
234
|
-
if (
|
|
243
|
+
if (stored.token || !process.cwd()) {
|
|
235
244
|
return stored;
|
|
236
245
|
}
|
|
237
246
|
const legacy = readLegacyImportFromProject(process.cwd());
|
|
@@ -240,22 +249,20 @@ function mergeWithLegacy(stored, options = {}) {
|
|
|
240
249
|
}
|
|
241
250
|
const merged = {
|
|
242
251
|
baseUrl: stored.baseUrl || legacy.baseUrl || DEFAULT_BASE_URL,
|
|
243
|
-
instance: stored.instance || options.instance || legacy.instance || '',
|
|
244
252
|
token: stored.token || legacy.token || '',
|
|
245
253
|
};
|
|
246
|
-
if (merged.token || merged.
|
|
254
|
+
if (merged.token || merged.baseUrl !== DEFAULT_BASE_URL) {
|
|
247
255
|
saveStoredConfig(merged);
|
|
248
256
|
}
|
|
249
257
|
return merged;
|
|
250
258
|
}
|
|
251
259
|
export function loadStoredConfig(options = {}) {
|
|
252
|
-
return mergeWithLegacy(readStoredConfig(
|
|
260
|
+
return mergeWithLegacy(readStoredConfig(), options);
|
|
253
261
|
}
|
|
254
262
|
export function buildAuthValidationKey(runtime) {
|
|
255
263
|
return createHash('sha256')
|
|
256
264
|
.update(JSON.stringify({
|
|
257
265
|
baseUrl: runtime.baseUrl || DEFAULT_BASE_URL,
|
|
258
|
-
instance: runtime.instance || '',
|
|
259
266
|
token: runtime.token || '',
|
|
260
267
|
}))
|
|
261
268
|
.digest('hex');
|
|
@@ -339,18 +346,11 @@ export function clearAuthValidationState(key) {
|
|
|
339
346
|
export function resolveRuntimeConfig(options = {}) {
|
|
340
347
|
const env = {
|
|
341
348
|
baseUrl: resolveEnvValue(['REVO_URL', 'REVO_BASE_URL', 'REVOENGINE_URL', 'REVOENGINE_BASE_URL']),
|
|
342
|
-
instance: resolveEnvValue(['REVO_INSTANCE', 'REVOENGINE_INSTANCE']),
|
|
343
349
|
token: resolveEnvValue(['REVO_TOKEN', 'REVO_API_KEY', 'REVOENGINE_TOKEN', 'REVOENGINE_API_KEY']),
|
|
344
350
|
};
|
|
345
|
-
const
|
|
346
|
-
const requestedInstance = options.instance || env.instance || projectInstance || undefined;
|
|
347
|
-
const stored = loadStoredConfig({
|
|
348
|
-
instance: requestedInstance,
|
|
349
|
-
allowLegacy: !requestedInstance,
|
|
350
|
-
});
|
|
351
|
+
const stored = loadStoredConfig();
|
|
351
352
|
return {
|
|
352
353
|
baseUrl: options.baseUrl || env.baseUrl || stored.baseUrl || DEFAULT_BASE_URL,
|
|
353
|
-
instance: options.instance || env.instance || stored.instance || '',
|
|
354
354
|
token: options.token || env.token || stored.token || '',
|
|
355
355
|
};
|
|
356
356
|
}
|
package/dist/src/project.d.ts
CHANGED
|
@@ -11,14 +11,13 @@ export declare const REVO_DEBUG_CONFIG_FILE: string;
|
|
|
11
11
|
export declare const REVO_PLAYGROUND_JS_FILE: string;
|
|
12
12
|
export declare const REVO_PLAYGROUND_TS_FILE: string;
|
|
13
13
|
export type ProjectInvocation = {
|
|
14
|
-
action: 'init' | '
|
|
14
|
+
action: 'init' | 'update';
|
|
15
15
|
targetArg?: string;
|
|
16
16
|
extraArgs: string[];
|
|
17
17
|
};
|
|
18
18
|
export type EditorTypesBundle = {
|
|
19
19
|
code: string;
|
|
20
20
|
endpoint?: string;
|
|
21
|
-
instanceId?: string;
|
|
22
21
|
apiVersion?: string;
|
|
23
22
|
libVersion?: string;
|
|
24
23
|
hash?: string;
|
|
@@ -27,7 +26,6 @@ export type EditorTypesBundle = {
|
|
|
27
26
|
export type RevoProjectMetadata = {
|
|
28
27
|
schemaVersion: 1;
|
|
29
28
|
endpoint: string;
|
|
30
|
-
instanceId: string;
|
|
31
29
|
authProfile: string;
|
|
32
30
|
apiVersion: string;
|
|
33
31
|
libVersion: string;
|
|
@@ -36,7 +34,6 @@ export type RevoProjectMetadata = {
|
|
|
36
34
|
};
|
|
37
35
|
export type ProjectSyncInput = {
|
|
38
36
|
endpoint: string;
|
|
39
|
-
instanceId: string;
|
|
40
37
|
code: string;
|
|
41
38
|
apiVersion: string;
|
|
42
39
|
libVersion: string;
|
|
@@ -60,13 +57,11 @@ export declare function buildSandboxDebugUrl(endpoint: string): string;
|
|
|
60
57
|
export declare function extractSandboxEndpoint(profile: unknown): string | null;
|
|
61
58
|
export declare function buildProjectMetadata(input: {
|
|
62
59
|
baseUrl: string;
|
|
63
|
-
instance: string;
|
|
64
60
|
bundle: EditorTypesBundle;
|
|
65
61
|
now?: Date;
|
|
66
62
|
}): {
|
|
67
63
|
schemaVersion: 1;
|
|
68
64
|
endpoint: string;
|
|
69
|
-
instanceId: string;
|
|
70
65
|
authProfile: string;
|
|
71
66
|
apiVersion: string;
|
|
72
67
|
libVersion: string;
|
|
@@ -78,12 +73,10 @@ export declare function readProjectMetadata(startDir?: string): RevoProjectMetad
|
|
|
78
73
|
export declare function resolveProjectRoot(startDir?: string): string;
|
|
79
74
|
export declare function buildProjectSyncState(input: {
|
|
80
75
|
fallbackEndpoint: string;
|
|
81
|
-
fallbackInstanceId: string;
|
|
82
76
|
bundle: EditorTypesBundle;
|
|
83
77
|
now?: Date;
|
|
84
78
|
}): {
|
|
85
79
|
endpoint: string;
|
|
86
|
-
instanceId: string;
|
|
87
80
|
code: string;
|
|
88
81
|
apiVersion: string;
|
|
89
82
|
libVersion: string;
|
package/dist/src/project.js
CHANGED
|
@@ -192,13 +192,16 @@ export function resolveProjectInvocation(args) {
|
|
|
192
192
|
extraArgs: [],
|
|
193
193
|
};
|
|
194
194
|
}
|
|
195
|
-
if (first === 'init' || first === '
|
|
195
|
+
if (first === 'init' || first === 'update') {
|
|
196
196
|
return {
|
|
197
197
|
action: first,
|
|
198
198
|
targetArg: second,
|
|
199
199
|
extraArgs: rest,
|
|
200
200
|
};
|
|
201
201
|
}
|
|
202
|
+
if (first === 'switch') {
|
|
203
|
+
throw new Error('`revo project switch` was removed because credentials are no longer instance-scoped.');
|
|
204
|
+
}
|
|
202
205
|
return {
|
|
203
206
|
action: 'init',
|
|
204
207
|
targetArg: first,
|
|
@@ -221,7 +224,6 @@ export function extractEditorTypesBundle(payload) {
|
|
|
221
224
|
return {
|
|
222
225
|
code,
|
|
223
226
|
endpoint: readString(root.endpoint) || readString(meta?.endpoint),
|
|
224
|
-
instanceId: readString(root.instanceId) || readString(root.instance) || readString(meta?.instanceId) || readString(meta?.instance),
|
|
225
227
|
apiVersion: readString(root.apiVersion) || readString(meta?.apiVersion),
|
|
226
228
|
libVersion: readString(root.libVersion) || readString(meta?.libVersion),
|
|
227
229
|
hash: readString(root.hash) || readString(meta?.hash),
|
|
@@ -265,27 +267,27 @@ export function extractSandboxEndpoint(profile) {
|
|
|
265
267
|
roots.push(nestedProfile);
|
|
266
268
|
}
|
|
267
269
|
for (const root of roots) {
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const endpoints
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
270
|
+
const endpointContainers = [
|
|
271
|
+
root.endpoints,
|
|
272
|
+
isRecord(root.instance) ? root.instance.endpoints : null,
|
|
273
|
+
];
|
|
274
|
+
for (const endpoints of endpointContainers) {
|
|
275
|
+
if (!isRecord(endpoints)) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const sandbox = endpoints.sandbox;
|
|
279
|
+
if (typeof sandbox === 'string' && sandbox) {
|
|
280
|
+
return normalizeEndpoint(sandbox);
|
|
281
|
+
}
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
return null;
|
|
282
285
|
}
|
|
283
286
|
export function buildProjectMetadata(input) {
|
|
284
|
-
const { baseUrl, bundle,
|
|
287
|
+
const { baseUrl, bundle, now = new Date() } = input;
|
|
285
288
|
return {
|
|
286
289
|
schemaVersion: 1,
|
|
287
290
|
endpoint: bundle.endpoint || buildEditorEndpoint(baseUrl),
|
|
288
|
-
instanceId: bundle.instanceId || instance,
|
|
289
291
|
authProfile: 'default',
|
|
290
292
|
apiVersion: getRequiredMetadataValue(bundle, 'apiVersion'),
|
|
291
293
|
libVersion: getRequiredMetadataValue(bundle, 'libVersion'),
|
|
@@ -319,8 +321,7 @@ export function readProjectMetadata(startDir = process.cwd()) {
|
|
|
319
321
|
if (!isRecord(raw)) {
|
|
320
322
|
return null;
|
|
321
323
|
}
|
|
322
|
-
if (typeof raw.
|
|
323
|
-
|| typeof raw.endpoint !== 'string'
|
|
324
|
+
if (typeof raw.endpoint !== 'string'
|
|
324
325
|
|| typeof raw.authProfile !== 'string'
|
|
325
326
|
|| typeof raw.apiVersion !== 'string'
|
|
326
327
|
|| typeof raw.libVersion !== 'string'
|
|
@@ -337,13 +338,11 @@ export function resolveProjectRoot(startDir = process.cwd()) {
|
|
|
337
338
|
export function buildProjectSyncState(input) {
|
|
338
339
|
const metadata = buildProjectMetadata({
|
|
339
340
|
baseUrl: input.fallbackEndpoint,
|
|
340
|
-
instance: input.fallbackInstanceId,
|
|
341
341
|
bundle: input.bundle,
|
|
342
342
|
now: input.now,
|
|
343
343
|
});
|
|
344
344
|
return {
|
|
345
345
|
endpoint: metadata.endpoint,
|
|
346
|
-
instanceId: metadata.instanceId,
|
|
347
346
|
code: input.bundle.code,
|
|
348
347
|
apiVersion: metadata.apiVersion,
|
|
349
348
|
libVersion: metadata.libVersion,
|
|
@@ -362,7 +361,6 @@ export function syncProjectFiles(targetDir, input) {
|
|
|
362
361
|
writeJsonFile(metadataFile, {
|
|
363
362
|
schemaVersion: 1,
|
|
364
363
|
endpoint: input.endpoint,
|
|
365
|
-
instanceId: input.instanceId,
|
|
366
364
|
authProfile: 'default',
|
|
367
365
|
apiVersion: input.apiVersion,
|
|
368
366
|
libVersion: input.libVersion,
|
package/dist/src/runtime-view.js
CHANGED
|
@@ -7,11 +7,6 @@ function resolveArgsRuntime(context) {
|
|
|
7
7
|
: typeof context.args.baseUrl === 'string'
|
|
8
8
|
? context.args.baseUrl
|
|
9
9
|
: undefined,
|
|
10
|
-
instance: typeof context.args.instance === 'string'
|
|
11
|
-
? context.args.instance
|
|
12
|
-
: typeof context.args.i === 'string'
|
|
13
|
-
? context.args.i
|
|
14
|
-
: undefined,
|
|
15
10
|
token: typeof context.args.token === 'string'
|
|
16
11
|
? context.args.token
|
|
17
12
|
: typeof context.args.t === 'string'
|
|
@@ -49,9 +44,8 @@ export async function buildRuntimeViewModel(context, options = {}) {
|
|
|
49
44
|
validation: null,
|
|
50
45
|
email: null,
|
|
51
46
|
};
|
|
52
|
-
if (runtime.token
|
|
47
|
+
if (runtime.token) {
|
|
53
48
|
context.client.baseUrl = runtime.baseUrl;
|
|
54
|
-
context.client.instance = runtime.instance;
|
|
55
49
|
context.client.token = runtime.token;
|
|
56
50
|
try {
|
|
57
51
|
const profile = await context.client.me({ force: options.forceValidation });
|
package/dist/src/ui.js
CHANGED
|
@@ -95,16 +95,15 @@ function renderCommandCatalog() {
|
|
|
95
95
|
'',
|
|
96
96
|
paintHeader('Global Options'),
|
|
97
97
|
commandRow('--url <url>', 'Override API base URL for TEST, prod, local, or dedicated tenants'),
|
|
98
|
-
commandRow('--instance <uuid>', 'Override instance ID'),
|
|
99
98
|
commandRow('--token <apiKey>', 'Override API key for non-interactive runs'),
|
|
99
|
+
commandRow('--no-update-notifier', 'Disable npm version update checks for this run'),
|
|
100
100
|
'',
|
|
101
101
|
paintHeader('Core'),
|
|
102
102
|
commandRow('revo -i', 'Show runtime info, config path, and auth state'),
|
|
103
|
-
commandRow('revo auth login', 'Paste API key
|
|
103
|
+
commandRow('revo auth login', 'Paste API key in the terminal'),
|
|
104
104
|
commandRow('revo auth status', 'Show the current authentication status'),
|
|
105
105
|
commandRow('revo auth logout', 'Remove stored credentials'),
|
|
106
106
|
commandRow('revo project [path]', 'Initialize ambient editor types for JS and TS'),
|
|
107
|
-
commandRow('revo project switch <instanceId>', 'Switch the current project to another stored instance'),
|
|
108
107
|
commandRow('revo endpoints', 'List available API endpoints'),
|
|
109
108
|
'',
|
|
110
109
|
paintHeader('Components'),
|
|
@@ -153,7 +152,6 @@ export function renderRuntimeInfo(input) {
|
|
|
153
152
|
row('Config', configDir),
|
|
154
153
|
row('Base URL', runtime.baseUrl),
|
|
155
154
|
row('Status', formatStatusValue(auth)),
|
|
156
|
-
row('Instance', runtime.instance || 'missing'),
|
|
157
155
|
...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
|
|
158
156
|
'',
|
|
159
157
|
paintHeader('Tips'),
|
|
@@ -168,7 +166,6 @@ export function renderAuthStatus(input) {
|
|
|
168
166
|
row('Status', formatStatusValue(auth)),
|
|
169
167
|
row('Config', configDir),
|
|
170
168
|
row('Base URL', runtime.baseUrl),
|
|
171
|
-
row('Instance', runtime.instance || 'missing'),
|
|
172
169
|
row('API key', runtime.token ? 'configured' : 'missing'),
|
|
173
170
|
...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
|
|
174
171
|
].join('\n');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ParsedArgs } from './types.ts';
|
|
2
|
+
type UpdateNotifierOptions = {
|
|
3
|
+
args?: ParsedArgs;
|
|
4
|
+
currentVersion: string;
|
|
5
|
+
fetch?: typeof fetch;
|
|
6
|
+
now?: number;
|
|
7
|
+
stream?: NodeJS.WriteStream;
|
|
8
|
+
};
|
|
9
|
+
export declare function compareSemver(left: string, right: string): 0 | 1 | -1;
|
|
10
|
+
export declare function renderUpdateNotice(input: {
|
|
11
|
+
currentVersion: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
stream?: NodeJS.WriteStream;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function maybeNotifyUpdateAvailable(options: UpdateNotifierOptions): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getConfigDir } from "./config.js";
|
|
4
|
+
import { CLI_NAME, CLI_PACKAGE } from "./ui.js";
|
|
5
|
+
import { readJsonFile, writeJsonFile } from "./utils.js";
|
|
6
|
+
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
+
const UPDATE_CHECK_TIMEOUT_MS = 1_000;
|
|
8
|
+
const UPDATE_STATE_FILE = 'update-state.json';
|
|
9
|
+
const NPM_PACKAGE_URL = `https://registry.npmjs.org/${encodeURIComponent(CLI_PACKAGE)}/latest`;
|
|
10
|
+
function isTruthyEnv(value) {
|
|
11
|
+
return value === '1' || value === 'true' || value === 'yes';
|
|
12
|
+
}
|
|
13
|
+
function updateNotifierDisabled(args) {
|
|
14
|
+
return Boolean(args?.['no-update-notifier'])
|
|
15
|
+
|| isTruthyEnv(process.env.REVO_NO_UPDATE_NOTIFIER)
|
|
16
|
+
|| isTruthyEnv(process.env.NO_UPDATE_NOTIFIER)
|
|
17
|
+
|| isTruthyEnv(process.env.CI);
|
|
18
|
+
}
|
|
19
|
+
function getUpdateStatePath() {
|
|
20
|
+
return path.join(getConfigDir(), UPDATE_STATE_FILE);
|
|
21
|
+
}
|
|
22
|
+
function readUpdateState() {
|
|
23
|
+
const filePath = getUpdateStatePath();
|
|
24
|
+
if (!fs.existsSync(filePath)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = readJsonFile(filePath);
|
|
29
|
+
if (raw
|
|
30
|
+
&& typeof raw === 'object'
|
|
31
|
+
&& !Array.isArray(raw)
|
|
32
|
+
&& typeof raw.checkedAt === 'number'
|
|
33
|
+
&& typeof raw.latestVersion === 'string') {
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function saveUpdateState(state) {
|
|
43
|
+
try {
|
|
44
|
+
writeJsonFile(getUpdateStatePath(), state);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Update checks are advisory and must never make a CLI command fail.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function parseVersion(version) {
|
|
51
|
+
const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
|
|
52
|
+
if (!match) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
56
|
+
}
|
|
57
|
+
export function compareSemver(left, right) {
|
|
58
|
+
const parsedLeft = parseVersion(left);
|
|
59
|
+
const parsedRight = parseVersion(right);
|
|
60
|
+
if (!parsedLeft || !parsedRight) {
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
for (let index = 0; index < parsedLeft.length; index += 1) {
|
|
64
|
+
if (parsedLeft[index] > parsedRight[index]) {
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
if (parsedLeft[index] < parsedRight[index]) {
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
async function fetchLatestVersion(fetchImpl) {
|
|
74
|
+
const response = await fetchImpl(NPM_PACKAGE_URL, {
|
|
75
|
+
headers: {
|
|
76
|
+
accept: 'application/json',
|
|
77
|
+
},
|
|
78
|
+
signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS),
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
return '';
|
|
82
|
+
}
|
|
83
|
+
const data = await response.json();
|
|
84
|
+
return data && typeof data === 'object' && typeof data.version === 'string'
|
|
85
|
+
? data.version
|
|
86
|
+
: '';
|
|
87
|
+
}
|
|
88
|
+
function supportsColor(stream) {
|
|
89
|
+
return Boolean(stream.isTTY || process.env.FORCE_COLOR);
|
|
90
|
+
}
|
|
91
|
+
function color(stream, text, code) {
|
|
92
|
+
return supportsColor(stream) ? `${code}${text}\u001b[0m` : text;
|
|
93
|
+
}
|
|
94
|
+
export function renderUpdateNotice(input) {
|
|
95
|
+
const stream = input.stream || process.stderr;
|
|
96
|
+
const yellow = '\u001b[33m';
|
|
97
|
+
const cyan = '\u001b[36m';
|
|
98
|
+
const bold = '\u001b[1m';
|
|
99
|
+
return [
|
|
100
|
+
color(stream, `Update available ${input.currentVersion} -> ${input.latestVersion}`, bold + yellow),
|
|
101
|
+
`Run ${color(stream, `npm install -g ${CLI_PACKAGE}`, cyan)} to update ${CLI_NAME}.`,
|
|
102
|
+
].join('\n');
|
|
103
|
+
}
|
|
104
|
+
export async function maybeNotifyUpdateAvailable(options) {
|
|
105
|
+
const stream = options.stream || process.stderr;
|
|
106
|
+
const fetchImpl = options.fetch || globalThis.fetch;
|
|
107
|
+
const now = options.now || Date.now();
|
|
108
|
+
if (updateNotifierDisabled(options.args) || !stream.isTTY || !fetchImpl) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const cached = readUpdateState();
|
|
112
|
+
if (cached && now - cached.checkedAt < UPDATE_CHECK_TTL_MS) {
|
|
113
|
+
if (compareSemver(cached.latestVersion, options.currentVersion) > 0) {
|
|
114
|
+
stream.write(`${renderUpdateNotice({
|
|
115
|
+
currentVersion: options.currentVersion,
|
|
116
|
+
latestVersion: cached.latestVersion,
|
|
117
|
+
stream,
|
|
118
|
+
})}\n`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const latestVersion = await fetchLatestVersion(fetchImpl);
|
|
124
|
+
if (!latestVersion) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
saveUpdateState({
|
|
128
|
+
checkedAt: now,
|
|
129
|
+
latestVersion,
|
|
130
|
+
});
|
|
131
|
+
if (compareSemver(latestVersion, options.currentVersion) > 0) {
|
|
132
|
+
stream.write(`${renderUpdateNotice({
|
|
133
|
+
currentVersion: options.currentVersion,
|
|
134
|
+
latestVersion,
|
|
135
|
+
stream,
|
|
136
|
+
})}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// Network, registry, and cache failures must not affect the requested command.
|
|
141
|
+
}
|
|
142
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revoengine/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "CLI package for the RevoEngine Platform API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc -p tsconfig.build.json",
|
|
13
|
-
"check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/ui.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
|
|
13
|
+
"check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/ui.ts'); await import('./src/update-notifier.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
|
|
14
14
|
"dev": "node ./scripts/dev.ts",
|
|
15
15
|
"prepack": "npm run build",
|
|
16
16
|
"test": "node --test ./test/*.test.ts"
|