@revoengine/cli 1.0.0 → 1.0.2
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 +27 -12
- package/dist/src/cli.js +5 -0
- package/dist/src/client.d.ts +1 -1
- package/dist/src/client.js +32 -13
- package/dist/src/commands/auth.js +10 -32
- package/dist/src/commands/component.js +34 -46
- package/dist/src/commands/project.js +2 -39
- package/dist/src/config.d.ts +14 -11
- package/dist/src/config.js +65 -54
- package/dist/src/project.d.ts +1 -8
- package/dist/src/project.js +18 -20
- package/dist/src/runtime-view.js +2 -2
- 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,9 @@ 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
|
+
|
|
32
|
+
The CLI validates the API key against `/api/v1/me` and infers the RevoEngine instance from the authenticated profile for tenant-scoped operations such as component push.
|
|
31
33
|
|
|
32
34
|
Check the active session:
|
|
33
35
|
|
|
@@ -52,10 +54,9 @@ Initialize a project for ambient low-code editor globals:
|
|
|
52
54
|
```bash
|
|
53
55
|
revo project
|
|
54
56
|
revo project init ./app
|
|
55
|
-
revo project switch a931e927-738d-42a4-b486-5fc19bfb3dfb
|
|
56
57
|
```
|
|
57
58
|
|
|
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.
|
|
59
|
+
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
60
|
|
|
60
61
|
## Common commands
|
|
61
62
|
|
|
@@ -135,12 +136,14 @@ Components with `category: null` are stored under `Components/__no_category__/..
|
|
|
135
136
|
Bulk sync behavior:
|
|
136
137
|
|
|
137
138
|
- `revo component pull --all` and `revo component push --all` require terminal confirmation unless `--force` is passed.
|
|
139
|
+
- `revo component pull --all` requests only active remote components where `deletedAt` is empty.
|
|
138
140
|
- Pull compares the full local workspace contract before overwriting anything.
|
|
139
141
|
- Pull skips with `no changes` when the local workspace already matches the remote component.
|
|
140
142
|
- Pull skips with `changed` when local files differ from the remote contract.
|
|
141
143
|
- Pull skips with `stale version` when the local version is older than the remote version, unless `--stale` or `--force` is passed.
|
|
142
144
|
- Push treats backend `Not modified` responses as skipped instead of failing the whole run.
|
|
143
|
-
-
|
|
145
|
+
- Push treats backend `404` responses as skipped with `doesn't exist remotely`; restore the component in RevoEngine before pushing local changes to it.
|
|
146
|
+
- Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the authenticated sandbox `debug` endpoint.
|
|
144
147
|
- Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
|
|
145
148
|
- Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
|
|
146
149
|
|
|
@@ -148,8 +151,8 @@ Bulk sync behavior:
|
|
|
148
151
|
|
|
149
152
|
The CLI reads configuration in this order:
|
|
150
153
|
|
|
151
|
-
1. CLI flags such as `--url
|
|
152
|
-
2. Environment variables such as `REVO_URL
|
|
154
|
+
1. CLI flags such as `--url` and `--token`
|
|
155
|
+
2. Environment variables such as `REVO_URL` and `REVO_TOKEN`
|
|
153
156
|
3. Stored config in the OS config directory
|
|
154
157
|
|
|
155
158
|
This means environment variables override the locally stored config, which is the intended setup for CI and non-interactive automation.
|
|
@@ -158,7 +161,7 @@ Use `--url` for one-off local or dedicated API targets:
|
|
|
158
161
|
|
|
159
162
|
```bash
|
|
160
163
|
revo --url https://api.test.revong.com -i
|
|
161
|
-
revo --url https://api.test.revong.com auth login
|
|
164
|
+
revo --url https://api.test.revong.com auth login
|
|
162
165
|
revo --url https://api.revoengine.com endpoints
|
|
163
166
|
```
|
|
164
167
|
|
|
@@ -167,13 +170,11 @@ Use environment variables for CI/CD or shell profiles:
|
|
|
167
170
|
```bash
|
|
168
171
|
# TEST
|
|
169
172
|
export REVO_URL="https://api.test.revong.com"
|
|
170
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
171
173
|
export REVO_TOKEN="your-test-token"
|
|
172
174
|
revo endpoints
|
|
173
175
|
|
|
174
176
|
# PROD
|
|
175
177
|
export REVO_URL="https://api.revoengine.com"
|
|
176
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
177
178
|
export REVO_TOKEN="your-prod-token"
|
|
178
179
|
revo endpoints
|
|
179
180
|
```
|
|
@@ -181,17 +182,15 @@ revo endpoints
|
|
|
181
182
|
Supported environment variables:
|
|
182
183
|
|
|
183
184
|
- `REVO_URL` or `REVO_BASE_URL`
|
|
184
|
-
- `REVO_INSTANCE`
|
|
185
185
|
- `REVO_TOKEN` or `REVO_API_KEY`
|
|
186
|
+
- `REVO_NO_UPDATE_NOTIFIER`
|
|
186
187
|
- `REVOENGINE_URL` or `REVOENGINE_BASE_URL`
|
|
187
|
-
- `REVOENGINE_INSTANCE`
|
|
188
188
|
- `REVOENGINE_TOKEN` or `REVOENGINE_API_KEY`
|
|
189
189
|
|
|
190
190
|
Example for CI:
|
|
191
191
|
|
|
192
192
|
```bash
|
|
193
193
|
export REVO_URL="https://api.revoengine.com"
|
|
194
|
-
export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
|
|
195
194
|
export REVO_TOKEN="your-ci-token"
|
|
196
195
|
revo endpoints
|
|
197
196
|
```
|
|
@@ -201,6 +200,22 @@ Stored config lives in:
|
|
|
201
200
|
- macOS and Linux: `~/.config/revoengine`
|
|
202
201
|
- Windows: `%APPDATA%/revoengine`
|
|
203
202
|
|
|
203
|
+
## Update notifications
|
|
204
|
+
|
|
205
|
+
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.
|
|
206
|
+
|
|
207
|
+
Disable the check for one run:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
revo --no-update-notifier endpoints
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Disable it through the environment:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
export REVO_NO_UPDATE_NOTIFIER=1
|
|
217
|
+
```
|
|
218
|
+
|
|
204
219
|
## Local development
|
|
205
220
|
|
|
206
221
|
```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 = {
|
|
@@ -159,6 +160,10 @@ function printHelp() {
|
|
|
159
160
|
}
|
|
160
161
|
export async function main(argv = process.argv.slice(2)) {
|
|
161
162
|
const args = parseArgs(argv);
|
|
163
|
+
await maybeNotifyUpdateAvailable({
|
|
164
|
+
args,
|
|
165
|
+
currentVersion: packageVersion,
|
|
166
|
+
});
|
|
162
167
|
const command = args._[0];
|
|
163
168
|
const noPositionals = args._.length === 0;
|
|
164
169
|
if (command === 'version' || ((args.version !== undefined || args.V !== undefined) && noPositionals)) {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare class PermissionDeniedError extends ApiError {
|
|
|
35
35
|
}
|
|
36
36
|
declare function buildUrl(baseUrl: string, requestPath: string, query?: Record<string, unknown>): URL;
|
|
37
37
|
declare function normalizeToken(token?: string): string;
|
|
38
|
+
export declare function extractProfileInstanceId(profile: unknown): string;
|
|
38
39
|
export declare class RevoClient {
|
|
39
40
|
baseUrl: string;
|
|
40
41
|
instance: string;
|
|
@@ -66,7 +67,6 @@ export declare class RevoClient {
|
|
|
66
67
|
search(params: Record<string, unknown>): Promise<unknown>;
|
|
67
68
|
listComponents(options?: ComponentListRequest): Promise<unknown>;
|
|
68
69
|
getComponent(componentId: string): Promise<unknown>;
|
|
69
|
-
createComponent(body: Record<string, unknown>): Promise<ApiResponse<unknown>>;
|
|
70
70
|
saveComponentElements(componentId: string, body: unknown): Promise<ApiResponse<unknown>>;
|
|
71
71
|
}
|
|
72
72
|
export { buildUrl, normalizeToken };
|
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;
|
|
@@ -125,6 +125,30 @@ async function readResponseData(response) {
|
|
|
125
125
|
}
|
|
126
126
|
return text;
|
|
127
127
|
}
|
|
128
|
+
function readProfileInstanceId(profile) {
|
|
129
|
+
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
const record = profile;
|
|
133
|
+
if (typeof record.id === 'string') {
|
|
134
|
+
return record.id;
|
|
135
|
+
}
|
|
136
|
+
const instance = record.instance;
|
|
137
|
+
if (instance && typeof instance === 'object' && !Array.isArray(instance)) {
|
|
138
|
+
const instanceId = instance.id;
|
|
139
|
+
if (typeof instanceId === 'string') {
|
|
140
|
+
return instanceId;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const data = record.data;
|
|
144
|
+
if (data && typeof data === 'object' && !Array.isArray(data)) {
|
|
145
|
+
return readProfileInstanceId(data);
|
|
146
|
+
}
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
export function extractProfileInstanceId(profile) {
|
|
150
|
+
return readProfileInstanceId(profile);
|
|
151
|
+
}
|
|
128
152
|
export class RevoClient {
|
|
129
153
|
baseUrl;
|
|
130
154
|
instance;
|
|
@@ -133,7 +157,7 @@ export class RevoClient {
|
|
|
133
157
|
constructor(options = {}) {
|
|
134
158
|
const config = resolveRuntimeConfig(options);
|
|
135
159
|
this.baseUrl = config.baseUrl;
|
|
136
|
-
this.instance = config.instance;
|
|
160
|
+
this.instance = config.instance || '';
|
|
137
161
|
this.token = config.token;
|
|
138
162
|
this.fetchImpl = options.fetch || globalThis.fetch;
|
|
139
163
|
}
|
|
@@ -154,12 +178,6 @@ export class RevoClient {
|
|
|
154
178
|
if (!this.token) {
|
|
155
179
|
throw new Error('Missing API token. Run `revo auth login` or set REVO_TOKEN / REVO_API_KEY.');
|
|
156
180
|
}
|
|
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
181
|
}
|
|
164
182
|
async validateSession(options = {}) {
|
|
165
183
|
this.assertReady();
|
|
@@ -173,6 +191,7 @@ export class RevoClient {
|
|
|
173
191
|
throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', null);
|
|
174
192
|
}
|
|
175
193
|
if (cached.profile !== undefined) {
|
|
194
|
+
this.instance = this.instance || extractProfileInstanceId(cached.profile);
|
|
176
195
|
return {
|
|
177
196
|
authenticated: true,
|
|
178
197
|
fromCache: true,
|
|
@@ -182,6 +201,7 @@ export class RevoClient {
|
|
|
182
201
|
}
|
|
183
202
|
try {
|
|
184
203
|
const profile = await this.requestData('GET', '/api/v1/me', { authGuard: false });
|
|
204
|
+
this.instance = this.instance || extractProfileInstanceId(profile);
|
|
185
205
|
saveAuthValidationState({
|
|
186
206
|
key: this.authValidationKey,
|
|
187
207
|
status: 'authenticated',
|
|
@@ -215,8 +235,10 @@ export class RevoClient {
|
|
|
215
235
|
const headers = new Headers(options.headers || {});
|
|
216
236
|
headers.set('Authorization', this.authHeader);
|
|
217
237
|
headers.set('x-api-key', this.token);
|
|
218
|
-
|
|
219
|
-
|
|
238
|
+
if (this.instance) {
|
|
239
|
+
headers.set('instance', this.instance);
|
|
240
|
+
headers.set('x-api-instance', this.instance);
|
|
241
|
+
}
|
|
220
242
|
let body = options.body;
|
|
221
243
|
if (body !== undefined && body !== null && method.toUpperCase() !== 'GET') {
|
|
222
244
|
headers.set('Content-Type', 'application/json');
|
|
@@ -305,9 +327,6 @@ export class RevoClient {
|
|
|
305
327
|
async getComponent(componentId) {
|
|
306
328
|
return this.requestData('GET', `/api/v1/component/${componentId}`);
|
|
307
329
|
}
|
|
308
|
-
async createComponent(body) {
|
|
309
|
-
return this.request('POST', '/api/v1/component', { body });
|
|
310
|
-
}
|
|
311
330
|
async saveComponentElements(componentId, body) {
|
|
312
331
|
return this.request('POST', `/api/v1/component/${componentId}/save`, { body });
|
|
313
332
|
}
|
|
@@ -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 });
|
|
@@ -88,7 +64,7 @@ export async function handleAuthCommand(context) {
|
|
|
88
64
|
client.token = imported.token;
|
|
89
65
|
client.instance = imported.instance;
|
|
90
66
|
client.baseUrl = imported.baseUrl;
|
|
91
|
-
if (imported.token
|
|
67
|
+
if (imported.token) {
|
|
92
68
|
try {
|
|
93
69
|
await client.validateSession({ force: true });
|
|
94
70
|
}
|
|
@@ -105,14 +81,13 @@ export async function handleAuthCommand(context) {
|
|
|
105
81
|
throw new Error('Unknown auth command.');
|
|
106
82
|
}
|
|
107
83
|
const stored = loadStoredConfig();
|
|
108
|
-
if (stored.token
|
|
109
|
-
println(
|
|
84
|
+
if (stored.token) {
|
|
85
|
+
println('Warning: already logged in. Run `revo auth logout` before logging in again.');
|
|
110
86
|
return;
|
|
111
87
|
}
|
|
112
88
|
const runtime = getConfigInput(args);
|
|
113
|
-
const next = runtime.token
|
|
89
|
+
const next = runtime.token ? runtime : await promptMissingConfig(runtime);
|
|
114
90
|
client.token = next.token;
|
|
115
|
-
client.instance = next.instance;
|
|
116
91
|
client.baseUrl = next.baseUrl;
|
|
117
92
|
try {
|
|
118
93
|
await client.validateSession({ force: true });
|
|
@@ -125,7 +100,10 @@ export async function handleAuthCommand(context) {
|
|
|
125
100
|
error(`Saved credentials, but validation failed: ${String(validationError)}`);
|
|
126
101
|
return;
|
|
127
102
|
}
|
|
128
|
-
saveStoredConfig(
|
|
103
|
+
saveStoredConfig({
|
|
104
|
+
...next,
|
|
105
|
+
instance: client.instance || undefined,
|
|
106
|
+
});
|
|
129
107
|
clearAuthValidationState();
|
|
130
108
|
println(`Logged in and saved credentials to ${getConfigDir()}.`);
|
|
131
109
|
}
|
|
@@ -5,7 +5,11 @@ import { buildSandboxDebugUrl, extractSandboxEndpoint } from "../project.js";
|
|
|
5
5
|
import { isInteractiveTerminal, promptConfirm } from "../prompt.js";
|
|
6
6
|
import { deepClone, readBoolFlag, readFlag, readValues, sanitizeSegment, writeJsonFile } from "../utils.js";
|
|
7
7
|
const NULL_CATEGORY_FOLDER = '__no_category__';
|
|
8
|
-
const COMPONENT_LIST_PAGE_SIZE =
|
|
8
|
+
const COMPONENT_LIST_PAGE_SIZE = 200;
|
|
9
|
+
const ACTIVE_COMPONENT_LIST_FILTER = {
|
|
10
|
+
'filter[and][0][field]': 'deletedAt',
|
|
11
|
+
'filter[and][0][op]': 'isNull',
|
|
12
|
+
};
|
|
9
13
|
const ANSI = {
|
|
10
14
|
reset: '\u001b[0m',
|
|
11
15
|
bold: '\u001b[1m',
|
|
@@ -202,23 +206,6 @@ function getComponentFolder(component) {
|
|
|
202
206
|
const componentId = sanitizeSegment(component.componentId || component.id || 'unknown');
|
|
203
207
|
return path.join(categoryFolder, `${name}-${componentId}`);
|
|
204
208
|
}
|
|
205
|
-
function getCategoryFromManifestPath(manifestPath) {
|
|
206
|
-
const componentDir = path.dirname(manifestPath);
|
|
207
|
-
const categoryDir = path.basename(path.dirname(componentDir));
|
|
208
|
-
if (categoryDir === NULL_CATEGORY_FOLDER) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
return categoryDir;
|
|
212
|
-
}
|
|
213
|
-
function normalizeCategoryForApi(manifestPath, category) {
|
|
214
|
-
if (category === NULL_CATEGORY_FOLDER) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
if (category != null) {
|
|
218
|
-
return category;
|
|
219
|
-
}
|
|
220
|
-
return getCategoryFromManifestPath(manifestPath);
|
|
221
|
-
}
|
|
222
209
|
function getDetailsExtension(component) {
|
|
223
210
|
return componentTypeToExtension(normalizeComponentType(component));
|
|
224
211
|
}
|
|
@@ -351,6 +338,27 @@ function resolveNextComponentListRequest(value) {
|
|
|
351
338
|
}
|
|
352
339
|
return Object.keys(query).length > 0 ? { query } : null;
|
|
353
340
|
}
|
|
341
|
+
function buildActiveComponentListQuery(skip, take = COMPONENT_LIST_PAGE_SIZE) {
|
|
342
|
+
return {
|
|
343
|
+
take,
|
|
344
|
+
skip,
|
|
345
|
+
count: true,
|
|
346
|
+
...ACTIVE_COMPONENT_LIST_FILTER,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function withActiveComponentListFilter(request) {
|
|
350
|
+
if (!request.query) {
|
|
351
|
+
return request;
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
...request,
|
|
355
|
+
query: {
|
|
356
|
+
...request.query,
|
|
357
|
+
count: request.query.count ?? true,
|
|
358
|
+
...ACTIVE_COMPONENT_LIST_FILTER,
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
354
362
|
function unwrapComponentListPage(value) {
|
|
355
363
|
const items = unwrapList(value);
|
|
356
364
|
if (!isRecord(value)) {
|
|
@@ -571,10 +579,7 @@ async function pullAllComponents(context, options) {
|
|
|
571
579
|
const components = [];
|
|
572
580
|
const seenRequests = new Set();
|
|
573
581
|
let nextRequest = {
|
|
574
|
-
query:
|
|
575
|
-
take: COMPONENT_LIST_PAGE_SIZE,
|
|
576
|
-
skip: 0,
|
|
577
|
-
},
|
|
582
|
+
query: buildActiveComponentListQuery(0),
|
|
578
583
|
};
|
|
579
584
|
let discoveredTotal = null;
|
|
580
585
|
while (nextRequest) {
|
|
@@ -604,17 +609,14 @@ async function pullAllComponents(context, options) {
|
|
|
604
609
|
continue;
|
|
605
610
|
}
|
|
606
611
|
if (page.nextRequest) {
|
|
607
|
-
nextRequest = page.nextRequest;
|
|
612
|
+
nextRequest = withActiveComponentListFilter(page.nextRequest);
|
|
608
613
|
continue;
|
|
609
614
|
}
|
|
610
615
|
if (nextRequest.query
|
|
611
616
|
&& typeof nextRequest.query.take === 'number'
|
|
612
617
|
&& page.items.length === nextRequest.query.take) {
|
|
613
618
|
nextRequest = {
|
|
614
|
-
query:
|
|
615
|
-
take: nextRequest.query.take,
|
|
616
|
-
skip: components.length,
|
|
617
|
-
},
|
|
619
|
+
query: buildActiveComponentListQuery(components.length, nextRequest.query.take),
|
|
618
620
|
};
|
|
619
621
|
continue;
|
|
620
622
|
}
|
|
@@ -655,15 +657,6 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
655
657
|
if (!component.name) {
|
|
656
658
|
throw new Error(`Missing component name in ${manifestPath}.`);
|
|
657
659
|
}
|
|
658
|
-
const payload = {
|
|
659
|
-
componentId,
|
|
660
|
-
name: component.name,
|
|
661
|
-
category: normalizeCategoryForApi(manifestPath, component.category),
|
|
662
|
-
desc: component.desc,
|
|
663
|
-
type: normalizeComponentType(component),
|
|
664
|
-
active: component.active ?? true,
|
|
665
|
-
async: component.async ?? false,
|
|
666
|
-
};
|
|
667
660
|
const elements = (component.elements || []).map((element) => ({
|
|
668
661
|
key: element.key,
|
|
669
662
|
desc: element.desc,
|
|
@@ -717,21 +710,16 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
717
710
|
throw new Error('Access denied (403). You are authenticated, but you do not have permission to push components.');
|
|
718
711
|
}
|
|
719
712
|
if (error instanceof ApiError && error.status === 404) {
|
|
720
|
-
try {
|
|
721
|
-
await client.createComponent(payload);
|
|
722
|
-
await client.saveComponentElements(componentId, elements);
|
|
723
|
-
}
|
|
724
|
-
catch (createError) {
|
|
725
|
-
rethrowComponentAccessError(createError, 'push');
|
|
726
|
-
}
|
|
727
713
|
const result = {
|
|
728
|
-
status: '
|
|
714
|
+
status: 'skipped',
|
|
729
715
|
targetPath,
|
|
716
|
+
reason: "doesn't exist remotely",
|
|
730
717
|
};
|
|
731
718
|
printSyncStatus(println, {
|
|
732
|
-
status: '
|
|
719
|
+
status: 'Skipped',
|
|
733
720
|
direction: 'push',
|
|
734
721
|
targetPath,
|
|
722
|
+
reason: result.reason,
|
|
735
723
|
});
|
|
736
724
|
return result;
|
|
737
725
|
}
|
|
@@ -837,7 +825,7 @@ async function debugSingleComponent(context, componentId) {
|
|
|
837
825
|
const profile = await context.client.me();
|
|
838
826
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
839
827
|
if (!sandboxEndpoint) {
|
|
840
|
-
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run
|
|
828
|
+
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
841
829
|
}
|
|
842
830
|
const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), {
|
|
843
831
|
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
|
@@ -10,14 +10,8 @@ export type RuntimeConfigOptions = {
|
|
|
10
10
|
instance?: string;
|
|
11
11
|
token?: string;
|
|
12
12
|
};
|
|
13
|
-
type StoredConfig = RuntimeConfig
|
|
14
|
-
|
|
15
|
-
baseUrl: string;
|
|
16
|
-
token: string;
|
|
17
|
-
};
|
|
18
|
-
type StoredConfigFile = {
|
|
19
|
-
defaultInstance: string;
|
|
20
|
-
instances: Record<string, StoredInstanceConfig>;
|
|
13
|
+
type StoredConfig = Omit<RuntimeConfig, 'instance'> & {
|
|
14
|
+
instance?: string;
|
|
21
15
|
};
|
|
22
16
|
export type AuthValidationState = {
|
|
23
17
|
key: string;
|
|
@@ -37,11 +31,20 @@ export declare function readProjectInstanceId(startDir?: string): string;
|
|
|
37
31
|
export declare function isUuid(value: unknown): value is string;
|
|
38
32
|
export declare function saveStoredConfig(nextConfig: Partial<StoredConfig>): void;
|
|
39
33
|
export declare function clearStoredConfig(): void;
|
|
40
|
-
export declare function loadStoredConfigIndex():
|
|
34
|
+
export declare function loadStoredConfigIndex(): {
|
|
35
|
+
defaultInstance: string;
|
|
36
|
+
instances: {
|
|
37
|
+
default: {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
token: string;
|
|
40
|
+
};
|
|
41
|
+
} | {
|
|
42
|
+
default?: undefined;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
41
45
|
export declare function loadStoredConfig(options?: {
|
|
42
|
-
instance?: string;
|
|
43
46
|
allowLegacy?: boolean;
|
|
44
|
-
}):
|
|
47
|
+
}): StoredConfig;
|
|
45
48
|
export declare function buildAuthValidationKey(runtime: RuntimeConfigOptions): string;
|
|
46
49
|
export declare function readAuthValidationState(key?: string): AuthValidationState;
|
|
47
50
|
export declare function saveAuthValidationState(state: AuthValidationState): void;
|
package/dist/src/config.js
CHANGED
|
@@ -73,20 +73,13 @@ export function saveStoredConfig(nextConfig) {
|
|
|
73
73
|
const { dir, configFile, credentialsFile } = getConfigPaths();
|
|
74
74
|
ensureDirectory(dir);
|
|
75
75
|
const current = readStoredConfigFile();
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 || '',
|
|
76
|
+
const instance = nextConfig.instance || current.instance || '';
|
|
77
|
+
const config = {
|
|
78
|
+
baseUrl: nextConfig.baseUrl || current.baseUrl || DEFAULT_BASE_URL,
|
|
79
|
+
token: nextConfig.token || current.token || '',
|
|
80
|
+
...(instance ? { instance } : {}),
|
|
87
81
|
};
|
|
88
|
-
|
|
89
|
-
writeJsonFile(configFile, current);
|
|
82
|
+
writeJsonFile(configFile, config);
|
|
90
83
|
removeFileIfExists(credentialsFile);
|
|
91
84
|
}
|
|
92
85
|
export function clearStoredConfig() {
|
|
@@ -131,8 +124,8 @@ function migrateStoredConfigBestEffort(configFile, credentialsFile, nextConfig)
|
|
|
131
124
|
}
|
|
132
125
|
function emptyStoredConfigFile() {
|
|
133
126
|
return {
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
128
|
+
token: '',
|
|
136
129
|
};
|
|
137
130
|
}
|
|
138
131
|
function isRecord(value) {
|
|
@@ -151,6 +144,25 @@ function normalizeInstanceConfig(value) {
|
|
|
151
144
|
token: typeof value.token === 'string' ? value.token : '',
|
|
152
145
|
};
|
|
153
146
|
}
|
|
147
|
+
function parseFlatConfig(raw, credentials) {
|
|
148
|
+
if (!isRecord(raw) || isRecord(raw.instances)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const token = typeof raw.token === 'string'
|
|
152
|
+
? raw.token
|
|
153
|
+
: isRecord(credentials) && typeof credentials.token === 'string'
|
|
154
|
+
? credentials.token
|
|
155
|
+
: '';
|
|
156
|
+
return {
|
|
157
|
+
baseUrl: typeof raw.baseUrl === 'string'
|
|
158
|
+
? raw.baseUrl
|
|
159
|
+
: typeof raw.url === 'string'
|
|
160
|
+
? raw.url
|
|
161
|
+
: DEFAULT_BASE_URL,
|
|
162
|
+
...(typeof raw.instance === 'string' && isUuid(raw.instance) ? { instance: raw.instance } : {}),
|
|
163
|
+
token,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
154
166
|
function parseMappedConfig(raw) {
|
|
155
167
|
if (!isRecord(raw) || !isRecord(raw.instances)) {
|
|
156
168
|
return null;
|
|
@@ -169,10 +181,14 @@ function parseMappedConfig(raw) {
|
|
|
169
181
|
const defaultInstance = isUuid(configuredDefault) && instances[configuredDefault]
|
|
170
182
|
? configuredDefault
|
|
171
183
|
: Object.keys(instances)[0] || '';
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
184
|
+
const selected = defaultInstance ? instances[defaultInstance] : undefined;
|
|
185
|
+
return selected
|
|
186
|
+
? {
|
|
187
|
+
baseUrl: selected.baseUrl,
|
|
188
|
+
instance: defaultInstance,
|
|
189
|
+
token: selected.token,
|
|
190
|
+
}
|
|
191
|
+
: emptyStoredConfigFile();
|
|
176
192
|
}
|
|
177
193
|
function parseLegacyConfig(config, credentials) {
|
|
178
194
|
const baseUrl = isRecord(config) && typeof config.baseUrl === 'string'
|
|
@@ -180,58 +196,59 @@ function parseLegacyConfig(config, credentials) {
|
|
|
180
196
|
: isRecord(config) && typeof config.url === 'string'
|
|
181
197
|
? config.url
|
|
182
198
|
: DEFAULT_BASE_URL;
|
|
183
|
-
const instance = isRecord(config) && typeof config.instance === 'string' ? config.instance : '';
|
|
184
199
|
const token = isRecord(credentials) && typeof credentials.token === 'string' ? credentials.token : '';
|
|
185
|
-
if (!instance) {
|
|
186
|
-
return emptyStoredConfigFile();
|
|
187
|
-
}
|
|
188
200
|
return {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
},
|
|
195
|
-
},
|
|
201
|
+
baseUrl,
|
|
202
|
+
...(isRecord(config) && typeof config.instance === 'string' && isUuid(config.instance)
|
|
203
|
+
? { instance: config.instance }
|
|
204
|
+
: {}),
|
|
205
|
+
token,
|
|
196
206
|
};
|
|
197
207
|
}
|
|
198
208
|
function readStoredConfigFile() {
|
|
199
209
|
const { configFile, credentialsFile } = getConfigPaths();
|
|
200
210
|
const config = fs.existsSync(configFile) ? readJsonFile(configFile) : {};
|
|
201
211
|
const credentials = fs.existsSync(credentialsFile) ? readJsonFile(credentialsFile) : {};
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
212
|
+
const flat = parseFlatConfig(config, credentials);
|
|
213
|
+
if (flat) {
|
|
204
214
|
if (fs.existsSync(credentialsFile)) {
|
|
205
|
-
migrateStoredConfigBestEffort(configFile, credentialsFile,
|
|
215
|
+
migrateStoredConfigBestEffort(configFile, credentialsFile, flat);
|
|
206
216
|
}
|
|
217
|
+
return flat;
|
|
218
|
+
}
|
|
219
|
+
const mapped = parseMappedConfig(config);
|
|
220
|
+
if (mapped) {
|
|
221
|
+
migrateStoredConfigBestEffort(configFile, credentialsFile, mapped);
|
|
207
222
|
return mapped;
|
|
208
223
|
}
|
|
209
224
|
const legacy = parseLegacyConfig(config, credentials);
|
|
210
|
-
if (legacy.
|
|
225
|
+
if (legacy.token || legacy.baseUrl !== DEFAULT_BASE_URL) {
|
|
211
226
|
migrateStoredConfigBestEffort(configFile, credentialsFile, legacy);
|
|
212
227
|
}
|
|
213
228
|
return legacy;
|
|
214
229
|
}
|
|
215
230
|
export function loadStoredConfigIndex() {
|
|
216
|
-
return readStoredConfigFile();
|
|
217
|
-
}
|
|
218
|
-
function readStoredConfig(options = {}) {
|
|
219
231
|
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
232
|
return {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
233
|
+
defaultInstance: '',
|
|
234
|
+
instances: stored.token
|
|
235
|
+
? {
|
|
236
|
+
default: {
|
|
237
|
+
baseUrl: stored.baseUrl,
|
|
238
|
+
token: stored.token,
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
: {},
|
|
228
242
|
};
|
|
229
243
|
}
|
|
244
|
+
function readStoredConfig() {
|
|
245
|
+
return readStoredConfigFile();
|
|
246
|
+
}
|
|
230
247
|
function mergeWithLegacy(stored, options = {}) {
|
|
231
248
|
if (options.allowLegacy === false) {
|
|
232
249
|
return stored;
|
|
233
250
|
}
|
|
234
|
-
if (
|
|
251
|
+
if (stored.token || !process.cwd()) {
|
|
235
252
|
return stored;
|
|
236
253
|
}
|
|
237
254
|
const legacy = readLegacyImportFromProject(process.cwd());
|
|
@@ -240,16 +257,15 @@ function mergeWithLegacy(stored, options = {}) {
|
|
|
240
257
|
}
|
|
241
258
|
const merged = {
|
|
242
259
|
baseUrl: stored.baseUrl || legacy.baseUrl || DEFAULT_BASE_URL,
|
|
243
|
-
instance: stored.instance || options.instance || legacy.instance || '',
|
|
244
260
|
token: stored.token || legacy.token || '',
|
|
245
261
|
};
|
|
246
|
-
if (merged.token || merged.
|
|
262
|
+
if (merged.token || merged.baseUrl !== DEFAULT_BASE_URL) {
|
|
247
263
|
saveStoredConfig(merged);
|
|
248
264
|
}
|
|
249
265
|
return merged;
|
|
250
266
|
}
|
|
251
267
|
export function loadStoredConfig(options = {}) {
|
|
252
|
-
return mergeWithLegacy(readStoredConfig(
|
|
268
|
+
return mergeWithLegacy(readStoredConfig(), options);
|
|
253
269
|
}
|
|
254
270
|
export function buildAuthValidationKey(runtime) {
|
|
255
271
|
return createHash('sha256')
|
|
@@ -342,12 +358,7 @@ export function resolveRuntimeConfig(options = {}) {
|
|
|
342
358
|
instance: resolveEnvValue(['REVO_INSTANCE', 'REVOENGINE_INSTANCE']),
|
|
343
359
|
token: resolveEnvValue(['REVO_TOKEN', 'REVO_API_KEY', 'REVOENGINE_TOKEN', 'REVOENGINE_API_KEY']),
|
|
344
360
|
};
|
|
345
|
-
const
|
|
346
|
-
const requestedInstance = options.instance || env.instance || projectInstance || undefined;
|
|
347
|
-
const stored = loadStoredConfig({
|
|
348
|
-
instance: requestedInstance,
|
|
349
|
-
allowLegacy: !requestedInstance,
|
|
350
|
-
});
|
|
361
|
+
const stored = loadStoredConfig();
|
|
351
362
|
return {
|
|
352
363
|
baseUrl: options.baseUrl || env.baseUrl || stored.baseUrl || DEFAULT_BASE_URL,
|
|
353
364
|
instance: options.instance || env.instance || stored.instance || '',
|
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
|
@@ -49,9 +49,9 @@ export async function buildRuntimeViewModel(context, options = {}) {
|
|
|
49
49
|
validation: null,
|
|
50
50
|
email: null,
|
|
51
51
|
};
|
|
52
|
-
if (runtime.token
|
|
52
|
+
if (runtime.token) {
|
|
53
53
|
context.client.baseUrl = runtime.baseUrl;
|
|
54
|
-
context.client.instance = runtime.instance;
|
|
54
|
+
context.client.instance = runtime.instance || '';
|
|
55
55
|
context.client.token = runtime.token;
|
|
56
56
|
try {
|
|
57
57
|
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.2",
|
|
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"
|