@revoengine/cli 1.0.4 → 1.0.6
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 +22 -12
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +104 -0
- package/dist/src/commands/component.js +415 -33
- package/dist/src/commands/project.js +12 -8
- package/dist/src/component-lock.d.ts +40 -0
- package/dist/src/component-lock.js +107 -0
- package/dist/src/project.d.ts +14 -3
- package/dist/src/project.js +72 -14
- package/dist/src/ui.js +5 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ revo project init ./app
|
|
|
57
57
|
revo project update ./app
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
This writes or refreshes `.revoengine/types/revo.editor.d.ts`, `.revoengine/revo.json`, patches `tsconfig.json` or `jsconfig.json`, and updates `.gitignore` so the generated
|
|
60
|
+
This writes or refreshes root `.revoengine/types/revo.editor.d.ts`, `.revoengine/types/revo.editor.definitions.json`, and `.revoengine/revo.json`, patches the workspace `tsconfig.json` or `jsconfig.json`, and updates root `.gitignore` so the generated editor bundle stays local by default. Pass a path such as `./backend` when the RevoEngine workspace is nested; the path is saved as `.revoengine/revo.json.workspace` while root `.revoengine/` remains the single state directory.
|
|
61
61
|
|
|
62
62
|
## Common commands
|
|
63
63
|
|
|
@@ -90,17 +90,16 @@ Pull every available component:
|
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
92
|
revo component pull --all
|
|
93
|
+
revo component pull --all --yes
|
|
93
94
|
revo component pull --all --force
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
Preview sync status before changing local or remote state:
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
revo component
|
|
100
|
-
revo component
|
|
101
|
-
revo component
|
|
102
|
-
revo component pull --all --stale
|
|
103
|
-
revo component pull --all --force
|
|
100
|
+
revo component plan --all
|
|
101
|
+
revo component plan --all --json
|
|
102
|
+
revo component plan --all --strict
|
|
104
103
|
```
|
|
105
104
|
|
|
106
105
|
Push one or more local components:
|
|
@@ -108,6 +107,7 @@ Push one or more local components:
|
|
|
108
107
|
```bash
|
|
109
108
|
revo component push 6dfb536a-1111-4222-8333-123456789abc
|
|
110
109
|
revo component push --all
|
|
110
|
+
revo component push --all --yes
|
|
111
111
|
revo component push --all --force
|
|
112
112
|
```
|
|
113
113
|
|
|
@@ -117,6 +117,7 @@ Debug one local component in sandbox:
|
|
|
117
117
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc
|
|
118
118
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc -d '{"filters":{}}'
|
|
119
119
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc --timeout 30 --memory 256
|
|
120
|
+
revo component debug 6dfb536a-1111-4222-8333-123456789abc --stream
|
|
120
121
|
```
|
|
121
122
|
|
|
122
123
|
Pulled components are stored as a tree:
|
|
@@ -134,17 +135,26 @@ Components/
|
|
|
134
135
|
Each component folder keeps its `component.json` manifest alongside an `elements/` directory with the source payload for every element.
|
|
135
136
|
Components with `category: null` are stored under `Components/__no_category__/...` while the manifest keeps `"category": null`.
|
|
136
137
|
|
|
138
|
+
Remote sync state is stored separately in `.revoengine/revo.lock.json`. The lock file records the last remote component hash/version that this workspace was pulled from or successfully pushed to. It is a baseline, not the deployed source of truth; the server remains authoritative for deployed state and local component files remain the desired source.
|
|
139
|
+
|
|
137
140
|
Bulk sync behavior:
|
|
138
141
|
|
|
139
|
-
- `revo component pull --all` and `revo component push --all` require terminal confirmation unless `--force` is passed.
|
|
142
|
+
- `revo component pull --all` and `revo component push --all` require terminal confirmation unless `--yes` or `--force` is passed.
|
|
143
|
+
- `--yes` only confirms the bulk operation. `--force` is the destructive escape hatch that bypasses lock safety.
|
|
140
144
|
- `revo component pull --all` requests only active remote components where `deletedAt` is empty.
|
|
141
|
-
- Pull compares the
|
|
142
|
-
- Pull
|
|
143
|
-
- Pull
|
|
144
|
-
- Pull
|
|
145
|
+
- Pull compares local files, `.revoengine/revo.lock.json`, and the current remote component before overwriting anything.
|
|
146
|
+
- Pull fast-forwards clean local files when the remote moved and the local copy still matches the lock.
|
|
147
|
+
- Pull rejects conflicts when both local files and the remote changed since the lock baseline.
|
|
148
|
+
- Pull writes stable source manifests and keeps remote version churn out of `Components/**/component.json`.
|
|
149
|
+
- `revo component plan --all --strict` exits with an error when conflicts or missing lock entries are present.
|
|
150
|
+
- Push checks the current remote component against the lock before saving local changes.
|
|
151
|
+
- Push rejects stale remote state instead of trusting the version inside local component JSON.
|
|
145
152
|
- Push treats backend `Not modified` responses as skipped instead of failing the whole run.
|
|
146
153
|
- Push treats backend `404` responses as skipped with `doesn't exist remotely`; restore the component in RevoEngine before pushing local changes to it.
|
|
147
154
|
- Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the authenticated sandbox `debug` endpoint.
|
|
155
|
+
- `revo component debug --stream` posts to `debug/stream`, writes live `api.log()` frames to stderr, and writes the final raw result payload to stdout.
|
|
156
|
+
- Debug responses are saved under `.revoengine/output/debug_<timestamp>.json`.
|
|
157
|
+
- Debug includes local `CODE_TS_LIB` and `CODE_JS_LIB` components as temporary `extraLibs` overrides by default. Use `--no-extra-libs` when you want backend-saved libraries only.
|
|
148
158
|
- Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
|
|
149
159
|
- Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
|
|
150
160
|
|
package/dist/src/client.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export type ComponentListRequest = {
|
|
|
9
9
|
path?: string;
|
|
10
10
|
query?: Record<string, unknown>;
|
|
11
11
|
};
|
|
12
|
+
export type DebugStreamEvent = {
|
|
13
|
+
event: string;
|
|
14
|
+
data: unknown;
|
|
15
|
+
};
|
|
12
16
|
export type ClientOptions = {
|
|
13
17
|
baseUrl?: string;
|
|
14
18
|
instance?: string;
|
|
@@ -64,6 +68,7 @@ export declare class RevoClient {
|
|
|
64
68
|
listEndpoints(): Promise<unknown>;
|
|
65
69
|
getEditorTypes(requestPath?: string): Promise<unknown>;
|
|
66
70
|
debugComponent(requestPath: string, body: unknown): Promise<unknown>;
|
|
71
|
+
debugComponentStream(requestPath: string, body: unknown): AsyncGenerator<DebugStreamEvent>;
|
|
67
72
|
search(params: Record<string, unknown>): Promise<unknown>;
|
|
68
73
|
listComponents(options?: ComponentListRequest): Promise<unknown>;
|
|
69
74
|
getComponent(componentId: string): Promise<unknown>;
|
package/dist/src/client.js
CHANGED
|
@@ -125,6 +125,73 @@ async function readResponseData(response) {
|
|
|
125
125
|
}
|
|
126
126
|
return text;
|
|
127
127
|
}
|
|
128
|
+
function parseSseFrame(frame) {
|
|
129
|
+
let event = 'message';
|
|
130
|
+
const dataLines = [];
|
|
131
|
+
for (const line of frame.split(/\r?\n/g)) {
|
|
132
|
+
if (!line || line.startsWith(':')) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const separatorIndex = line.indexOf(':');
|
|
136
|
+
const field = separatorIndex >= 0 ? line.slice(0, separatorIndex) : line;
|
|
137
|
+
const rawValue = separatorIndex >= 0 ? line.slice(separatorIndex + 1) : '';
|
|
138
|
+
const value = rawValue.startsWith(' ') ? rawValue.slice(1) : rawValue;
|
|
139
|
+
if (field === 'event') {
|
|
140
|
+
event = value || event;
|
|
141
|
+
}
|
|
142
|
+
else if (field === 'data') {
|
|
143
|
+
dataLines.push(value);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (dataLines.length === 0) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const rawData = dataLines.join('\n');
|
|
150
|
+
let data = rawData;
|
|
151
|
+
try {
|
|
152
|
+
data = JSON.parse(rawData);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Keep non-JSON SSE payloads readable instead of dropping them.
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
event,
|
|
159
|
+
data,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function* readSseEvents(response) {
|
|
163
|
+
if (!response.body) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const reader = response.body.getReader();
|
|
167
|
+
const decoder = new TextDecoder();
|
|
168
|
+
let buffer = '';
|
|
169
|
+
while (true) {
|
|
170
|
+
const { value, done } = await reader.read();
|
|
171
|
+
if (done) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
buffer += decoder.decode(value, { stream: true });
|
|
175
|
+
let separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
176
|
+
while (separatorMatch?.index !== undefined) {
|
|
177
|
+
const frame = buffer.slice(0, separatorMatch.index);
|
|
178
|
+
buffer = buffer.slice(separatorMatch.index + separatorMatch[0].length);
|
|
179
|
+
const event = parseSseFrame(frame);
|
|
180
|
+
if (event) {
|
|
181
|
+
yield event;
|
|
182
|
+
}
|
|
183
|
+
separatorMatch = buffer.match(/\r?\n\r?\n/);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
buffer += decoder.decode();
|
|
187
|
+
const trailing = buffer.trim();
|
|
188
|
+
if (trailing) {
|
|
189
|
+
const event = parseSseFrame(trailing);
|
|
190
|
+
if (event) {
|
|
191
|
+
yield event;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
128
195
|
function readProfileInstanceId(profile) {
|
|
129
196
|
if (!profile || typeof profile !== 'object' || Array.isArray(profile)) {
|
|
130
197
|
return '';
|
|
@@ -316,6 +383,43 @@ export class RevoClient {
|
|
|
316
383
|
spinnerLabel: 'Debugging component',
|
|
317
384
|
});
|
|
318
385
|
}
|
|
386
|
+
async *debugComponentStream(requestPath, body) {
|
|
387
|
+
this.assertReady();
|
|
388
|
+
await this.validateSession();
|
|
389
|
+
const url = buildUrl(this.baseUrl, requestPath);
|
|
390
|
+
const headers = new Headers();
|
|
391
|
+
headers.set('Accept', 'text/event-stream');
|
|
392
|
+
headers.set('Content-Type', 'application/json');
|
|
393
|
+
headers.set('Authorization', this.authHeader);
|
|
394
|
+
headers.set('x-api-key', this.token);
|
|
395
|
+
if (this.instance) {
|
|
396
|
+
headers.set('instance', this.instance);
|
|
397
|
+
headers.set('x-api-instance', this.instance);
|
|
398
|
+
}
|
|
399
|
+
const response = await this.fetchImpl(url, {
|
|
400
|
+
method: 'POST',
|
|
401
|
+
headers,
|
|
402
|
+
body: JSON.stringify(body),
|
|
403
|
+
});
|
|
404
|
+
if (!response.ok) {
|
|
405
|
+
const data = await readResponseData(response);
|
|
406
|
+
if (response.status === 401) {
|
|
407
|
+
saveAuthValidationState({
|
|
408
|
+
key: this.authValidationKey,
|
|
409
|
+
status: 'not_authenticated',
|
|
410
|
+
checkedAt: Date.now(),
|
|
411
|
+
});
|
|
412
|
+
throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data);
|
|
413
|
+
}
|
|
414
|
+
if (response.status === 403) {
|
|
415
|
+
throw new PermissionDeniedError(requestPath, `Access denied for ${requestPath}.`, data);
|
|
416
|
+
}
|
|
417
|
+
throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data);
|
|
418
|
+
}
|
|
419
|
+
for await (const event of readSseEvents(response)) {
|
|
420
|
+
yield event;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
319
423
|
async search(params) {
|
|
320
424
|
return this.requestData('GET', '/api/v1/search', { query: params });
|
|
321
425
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ApiError, PermissionDeniedError } from "../client.js";
|
|
4
|
-
import {
|
|
4
|
+
import { hashComponentSource, hashStable, readComponentLock, sanitizeComponentSource, upsertComponentLockEntry, } from "../component-lock.js";
|
|
5
|
+
import { buildSandboxDebugStreamUrl, buildSandboxDebugUrl, extractSandboxEndpoint, resolveProjectRoot, resolveProjectWorkspace, } from "../project.js";
|
|
5
6
|
import { isInteractiveTerminal, promptConfirm } from "../prompt.js";
|
|
6
7
|
import { deepClone, readBoolFlag, readFlag, readValues, sanitizeSegment, writeJsonFile } from "../utils.js";
|
|
7
8
|
const NULL_CATEGORY_FOLDER = '__no_category__';
|
|
@@ -195,6 +196,9 @@ function getWorkspaceRoot(cwd) {
|
|
|
195
196
|
const projectWorkspace = resolveProjectWorkspace(cwd);
|
|
196
197
|
return path.join(projectWorkspace || cwd, 'Components');
|
|
197
198
|
}
|
|
199
|
+
function getProjectRoot(cwd) {
|
|
200
|
+
return resolveProjectRoot(cwd) || cwd;
|
|
201
|
+
}
|
|
198
202
|
function getCategoryFolder(component) {
|
|
199
203
|
if (component.category == null || component.category === '') {
|
|
200
204
|
return NULL_CATEGORY_FOLDER;
|
|
@@ -211,7 +215,7 @@ function getDetailsExtension(component) {
|
|
|
211
215
|
return componentTypeToExtension(normalizeComponentType(component));
|
|
212
216
|
}
|
|
213
217
|
function stripDetails(component) {
|
|
214
|
-
const clone = deepClone(component);
|
|
218
|
+
const clone = sanitizeComponentSource(deepClone(component));
|
|
215
219
|
clone.elements = (clone.elements || []).map((element) => {
|
|
216
220
|
const next = deepClone(element);
|
|
217
221
|
delete next.details;
|
|
@@ -406,28 +410,47 @@ function normalizeElementContract(element) {
|
|
|
406
410
|
};
|
|
407
411
|
}
|
|
408
412
|
function normalizeComponentContract(component) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
componentId,
|
|
412
|
-
name: component.name || '',
|
|
413
|
-
category: normalizeValue(component.category),
|
|
414
|
-
desc: normalizeValue(component.desc),
|
|
415
|
-
active: component.active ?? true,
|
|
413
|
+
return hashStable({
|
|
414
|
+
...sanitizeComponentSource(component),
|
|
416
415
|
type: normalizeComponentType(component),
|
|
417
416
|
compiler: compilerFromType(component.type, component.compiler),
|
|
418
|
-
version: typeof component.version === 'number' ? component.version : null,
|
|
419
417
|
elements: [...(component.elements || [])]
|
|
420
418
|
.map((element) => normalizeElementContract(element))
|
|
421
419
|
.sort((left, right) => left.order - right.order || left.key.localeCompare(right.key)),
|
|
422
|
-
};
|
|
420
|
+
});
|
|
423
421
|
}
|
|
424
422
|
function isSameComponentContract(left, right) {
|
|
425
|
-
return
|
|
423
|
+
return normalizeComponentContract(left) === normalizeComponentContract(right);
|
|
424
|
+
}
|
|
425
|
+
function componentVersion(component) {
|
|
426
|
+
return typeof component.version === 'number' ? component.version : null;
|
|
427
|
+
}
|
|
428
|
+
function componentIdOf(component) {
|
|
429
|
+
return component.componentId || component.id || '';
|
|
430
|
+
}
|
|
431
|
+
function componentTargetPath(cwd, component) {
|
|
432
|
+
return path.relative(cwd, path.join(getWorkspaceRoot(cwd), getComponentFolder(component)));
|
|
433
|
+
}
|
|
434
|
+
function upsertLockFromRemote(cwd, remote, targetPath) {
|
|
435
|
+
const componentId = componentIdOf(remote);
|
|
436
|
+
if (!componentId) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const remoteHash = hashComponentSource(remote);
|
|
440
|
+
upsertComponentLockEntry(getProjectRoot(cwd), {
|
|
441
|
+
componentId,
|
|
442
|
+
componentName: remote.name || '',
|
|
443
|
+
category: normalizeValue(remote.category),
|
|
444
|
+
path: targetPath || componentTargetPath(cwd, remote),
|
|
445
|
+
remoteVersion: componentVersion(remote),
|
|
446
|
+
remoteHash,
|
|
447
|
+
sourceHash: remoteHash,
|
|
448
|
+
pulledAt: new Date().toISOString(),
|
|
449
|
+
});
|
|
426
450
|
}
|
|
427
451
|
function buildPullDecision(cwd, remote) {
|
|
428
|
-
const
|
|
429
|
-
const
|
|
430
|
-
const componentId = remote.componentId || remote.id || '';
|
|
452
|
+
const remoteTargetPath = componentTargetPath(cwd, remote);
|
|
453
|
+
const componentId = componentIdOf(remote);
|
|
431
454
|
const localInfo = componentId ? findLocalManifestInfo(cwd, componentId) : null;
|
|
432
455
|
if (!localInfo?.manifestPath) {
|
|
433
456
|
return {
|
|
@@ -443,13 +466,42 @@ function buildPullDecision(cwd, remote) {
|
|
|
443
466
|
reason: 'changed',
|
|
444
467
|
};
|
|
445
468
|
}
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
|
|
469
|
+
const lockEntry = readComponentLock(getProjectRoot(cwd)).components[componentId];
|
|
470
|
+
const localHash = hashComponentSource(localComponent);
|
|
471
|
+
const remoteHash = hashComponentSource(remote);
|
|
472
|
+
if (!lockEntry) {
|
|
473
|
+
if (localHash === remoteHash) {
|
|
474
|
+
return {
|
|
475
|
+
kind: 'skip',
|
|
476
|
+
targetPath: localInfo.targetPath || remoteTargetPath,
|
|
477
|
+
reason: 'no changes',
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return {
|
|
481
|
+
kind: 'skip',
|
|
482
|
+
targetPath: localInfo.targetPath || remoteTargetPath,
|
|
483
|
+
reason: 'missing lock',
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (localHash === lockEntry.remoteHash && remoteHash !== lockEntry.remoteHash) {
|
|
487
|
+
return {
|
|
488
|
+
kind: 'pull',
|
|
489
|
+
targetPath: localInfo.targetPath || remoteTargetPath,
|
|
490
|
+
reason: 'remote changed',
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (localHash !== lockEntry.remoteHash && remoteHash === lockEntry.remoteHash) {
|
|
494
|
+
return {
|
|
495
|
+
kind: 'skip',
|
|
496
|
+
targetPath: localInfo.targetPath || remoteTargetPath,
|
|
497
|
+
reason: 'local changes',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (localHash !== lockEntry.remoteHash && remoteHash !== lockEntry.remoteHash && localHash !== remoteHash) {
|
|
449
501
|
return {
|
|
450
502
|
kind: 'skip',
|
|
451
503
|
targetPath: localInfo.targetPath || remoteTargetPath,
|
|
452
|
-
reason: '
|
|
504
|
+
reason: 'conflict',
|
|
453
505
|
};
|
|
454
506
|
}
|
|
455
507
|
if (isSameComponentContract(localComponent, remote)) {
|
|
@@ -534,6 +586,12 @@ async function pullSingleComponent(context, componentId, mode = { force: false,
|
|
|
534
586
|
}
|
|
535
587
|
}
|
|
536
588
|
else {
|
|
589
|
+
if (decision.reason === 'conflict' || decision.reason === 'missing lock') {
|
|
590
|
+
throw new Error(`Cannot pull ${componentId}: ${decision.reason} at ${decision.targetPath}. Re-run with --force only if you want to overwrite local files.`);
|
|
591
|
+
}
|
|
592
|
+
if (decision.reason === 'no changes') {
|
|
593
|
+
upsertLockFromRemote(cwd, component, decision.targetPath);
|
|
594
|
+
}
|
|
537
595
|
const result = {
|
|
538
596
|
status: 'skipped',
|
|
539
597
|
targetPath: decision.targetPath,
|
|
@@ -560,6 +618,7 @@ async function pullSingleComponent(context, componentId, mode = { force: false,
|
|
|
560
618
|
fs.writeFileSync(filePath, content);
|
|
561
619
|
}
|
|
562
620
|
writeJsonFile(path.join(folder, 'component.json'), stripDetails(component));
|
|
621
|
+
upsertLockFromRemote(cwd, component, targetPath);
|
|
563
622
|
const result = {
|
|
564
623
|
status: 'pulled',
|
|
565
624
|
targetPath,
|
|
@@ -575,7 +634,7 @@ async function pullSingleComponent(context, componentId, mode = { force: false,
|
|
|
575
634
|
});
|
|
576
635
|
return result;
|
|
577
636
|
}
|
|
578
|
-
async function
|
|
637
|
+
async function fetchAllComponentSummaries(context) {
|
|
579
638
|
const { client, cwd } = context;
|
|
580
639
|
const components = [];
|
|
581
640
|
const seenRequests = new Set();
|
|
@@ -623,11 +682,18 @@ async function pullAllComponents(context, options) {
|
|
|
623
682
|
}
|
|
624
683
|
nextRequest = null;
|
|
625
684
|
}
|
|
685
|
+
if (components.length === 0) {
|
|
686
|
+
return { components, discoveredTotal };
|
|
687
|
+
}
|
|
688
|
+
return { components, discoveredTotal };
|
|
689
|
+
}
|
|
690
|
+
async function pullAllComponents(context, options) {
|
|
691
|
+
const { components, discoveredTotal } = await fetchAllComponentSummaries(context);
|
|
626
692
|
if (components.length === 0) {
|
|
627
693
|
context.println('No components found.');
|
|
628
694
|
return [];
|
|
629
695
|
}
|
|
630
|
-
await confirmBulkAction(context, 'pull', discoveredTotal ?? (components.length >= COMPONENT_LIST_PAGE_SIZE ? `at least ${components.length} components` : components.length), options.force);
|
|
696
|
+
await confirmBulkAction(context, 'pull', discoveredTotal ?? (components.length >= COMPONENT_LIST_PAGE_SIZE ? `at least ${components.length} components` : components.length), options.force || options.yes);
|
|
631
697
|
const startedAt = Date.now();
|
|
632
698
|
const results = [];
|
|
633
699
|
for (const item of components) {
|
|
@@ -666,6 +732,52 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
666
732
|
order: element.order,
|
|
667
733
|
}));
|
|
668
734
|
try {
|
|
735
|
+
if (!readBoolFlag(context.args, ['force', 'f'])) {
|
|
736
|
+
let remote = null;
|
|
737
|
+
try {
|
|
738
|
+
remote = unwrapComponent(await client.getComponent(componentId));
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
742
|
+
const result = {
|
|
743
|
+
status: 'skipped',
|
|
744
|
+
targetPath,
|
|
745
|
+
reason: "doesn't exist remotely",
|
|
746
|
+
};
|
|
747
|
+
printSyncStatus(println, {
|
|
748
|
+
status: 'Skipped',
|
|
749
|
+
direction: 'push',
|
|
750
|
+
targetPath,
|
|
751
|
+
reason: result.reason,
|
|
752
|
+
});
|
|
753
|
+
return result;
|
|
754
|
+
}
|
|
755
|
+
rethrowComponentAccessError(error, 'push');
|
|
756
|
+
}
|
|
757
|
+
const lockEntry = readComponentLock(getProjectRoot(context.cwd)).components[componentId];
|
|
758
|
+
if (!lockEntry) {
|
|
759
|
+
throw new Error(`Cannot push ${targetPath}: missing lock entry. Run \`revo component pull ${componentId} --force\` to establish a remote baseline.`);
|
|
760
|
+
}
|
|
761
|
+
const remoteHash = hashComponentSource(remote);
|
|
762
|
+
if (remoteHash !== lockEntry.remoteHash) {
|
|
763
|
+
throw new Error(`Cannot push ${targetPath}: remote changed since the last lock baseline. Run \`revo component plan --all\` and pull/reconcile first.`);
|
|
764
|
+
}
|
|
765
|
+
const localHash = hashComponentSource(component);
|
|
766
|
+
if (localHash === lockEntry.remoteHash) {
|
|
767
|
+
const result = {
|
|
768
|
+
status: 'skipped',
|
|
769
|
+
targetPath,
|
|
770
|
+
reason: 'no local changes',
|
|
771
|
+
};
|
|
772
|
+
printSyncStatus(println, {
|
|
773
|
+
status: 'Skipped',
|
|
774
|
+
direction: 'push',
|
|
775
|
+
targetPath,
|
|
776
|
+
reason: result.reason,
|
|
777
|
+
});
|
|
778
|
+
return result;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
669
781
|
const response = await client.saveComponentElements(componentId, elements);
|
|
670
782
|
if (response.status === 304) {
|
|
671
783
|
const result = {
|
|
@@ -685,6 +797,13 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
685
797
|
status: 'deployed',
|
|
686
798
|
targetPath,
|
|
687
799
|
};
|
|
800
|
+
try {
|
|
801
|
+
const remote = unwrapComponent(await client.getComponent(componentId));
|
|
802
|
+
upsertLockFromRemote(context.cwd, remote, targetPath);
|
|
803
|
+
}
|
|
804
|
+
catch {
|
|
805
|
+
upsertLockFromRemote(context.cwd, component, targetPath);
|
|
806
|
+
}
|
|
688
807
|
printSyncStatus(println, {
|
|
689
808
|
status: 'Deployed',
|
|
690
809
|
direction: 'push',
|
|
@@ -705,6 +824,7 @@ async function pushSingleComponent(context, manifestPath) {
|
|
|
705
824
|
targetPath,
|
|
706
825
|
reason: result.reason,
|
|
707
826
|
});
|
|
827
|
+
upsertLockFromRemote(context.cwd, component, targetPath);
|
|
708
828
|
return result;
|
|
709
829
|
}
|
|
710
830
|
if (error instanceof PermissionDeniedError) {
|
|
@@ -735,7 +855,7 @@ async function pushAllComponents(context, options) {
|
|
|
735
855
|
println(`No component.json files found in ${path.relative(cwd, root) || 'Components'}.`);
|
|
736
856
|
return [];
|
|
737
857
|
}
|
|
738
|
-
await confirmBulkAction(context, 'push', manifests.length, options.force);
|
|
858
|
+
await confirmBulkAction(context, 'push', manifests.length, options.force || options.yes);
|
|
739
859
|
const startedAt = Date.now();
|
|
740
860
|
const results = [];
|
|
741
861
|
for (const manifestPath of manifests) {
|
|
@@ -747,6 +867,124 @@ async function pushAllComponents(context, options) {
|
|
|
747
867
|
printSummary(context.println, 'Deployed', results, Date.now() - startedAt);
|
|
748
868
|
return results;
|
|
749
869
|
}
|
|
870
|
+
async function buildComponentPlan(context) {
|
|
871
|
+
const projectRoot = getProjectRoot(context.cwd);
|
|
872
|
+
const lock = readComponentLock(projectRoot);
|
|
873
|
+
const root = getWorkspaceRoot(context.cwd);
|
|
874
|
+
const localManifests = getComponentManifestPaths(root);
|
|
875
|
+
const localById = new Map();
|
|
876
|
+
for (const manifestPath of localManifests) {
|
|
877
|
+
const component = readWorkspaceComponentSafe(manifestPath);
|
|
878
|
+
const componentId = component ? componentIdOf(component) : '';
|
|
879
|
+
if (component && componentId) {
|
|
880
|
+
localById.set(componentId, {
|
|
881
|
+
manifestPath,
|
|
882
|
+
component,
|
|
883
|
+
hash: hashComponentSource(component),
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const { components } = await fetchAllComponentSummaries(context);
|
|
888
|
+
const remoteIds = components.map((item) => String(item.componentId || item.id || '')).filter(Boolean);
|
|
889
|
+
const remoteById = new Map();
|
|
890
|
+
for (const componentId of remoteIds) {
|
|
891
|
+
const remote = unwrapComponent(await context.client.getComponent(componentId));
|
|
892
|
+
remoteById.set(componentId, {
|
|
893
|
+
component: remote,
|
|
894
|
+
hash: hashComponentSource(remote),
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
const allIds = [...new Set([
|
|
898
|
+
...localById.keys(),
|
|
899
|
+
...remoteById.keys(),
|
|
900
|
+
...Object.keys(lock.components),
|
|
901
|
+
])].sort();
|
|
902
|
+
const items = allIds.map((componentId) => {
|
|
903
|
+
const local = localById.get(componentId) || null;
|
|
904
|
+
const remote = remoteById.get(componentId) || null;
|
|
905
|
+
const lockEntry = lock.components[componentId] || null;
|
|
906
|
+
const component = local?.component || remote?.component;
|
|
907
|
+
const componentName = component?.name || lockEntry?.componentName || componentId;
|
|
908
|
+
const category = normalizeValue(component?.category, lockEntry?.category ?? null);
|
|
909
|
+
const targetPath = local
|
|
910
|
+
? componentWorkspacePath(context.cwd, local.manifestPath)
|
|
911
|
+
: remote
|
|
912
|
+
? componentTargetPath(context.cwd, remote.component)
|
|
913
|
+
: lockEntry?.path || componentId;
|
|
914
|
+
const localHash = local?.hash || null;
|
|
915
|
+
const remoteHash = remote?.hash || null;
|
|
916
|
+
const lockHash = lockEntry?.remoteHash || null;
|
|
917
|
+
let status = 'clean';
|
|
918
|
+
let reason = 'local, lock, and remote match';
|
|
919
|
+
if (!local && remote) {
|
|
920
|
+
status = 'safe-update';
|
|
921
|
+
reason = 'remote component is missing locally';
|
|
922
|
+
}
|
|
923
|
+
else if (local && !remote) {
|
|
924
|
+
status = 'create';
|
|
925
|
+
reason = 'local component does not exist remotely';
|
|
926
|
+
}
|
|
927
|
+
else if (!lockEntry) {
|
|
928
|
+
status = localHash === remoteHash ? 'clean' : 'missing-lock';
|
|
929
|
+
reason = localHash === remoteHash ? 'local matches remote; lock can be adopted' : 'no lock baseline exists';
|
|
930
|
+
}
|
|
931
|
+
else if (localHash === lockHash && remoteHash === lockHash) {
|
|
932
|
+
status = 'clean';
|
|
933
|
+
}
|
|
934
|
+
else if (localHash !== lockHash && remoteHash === lockHash) {
|
|
935
|
+
status = 'local-changed';
|
|
936
|
+
reason = 'local source changed and remote still matches lock';
|
|
937
|
+
}
|
|
938
|
+
else if (localHash === lockHash && remoteHash !== lockHash) {
|
|
939
|
+
status = 'remote-changed';
|
|
940
|
+
reason = 'remote changed and local is clean';
|
|
941
|
+
}
|
|
942
|
+
else if (localHash === remoteHash) {
|
|
943
|
+
status = 'clean';
|
|
944
|
+
reason = 'local matches remote; lock is stale';
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
status = 'conflict';
|
|
948
|
+
reason = 'local and remote changed since lock';
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
componentId,
|
|
952
|
+
componentName,
|
|
953
|
+
category,
|
|
954
|
+
path: targetPath,
|
|
955
|
+
status,
|
|
956
|
+
localHash,
|
|
957
|
+
lockHash,
|
|
958
|
+
remoteHash,
|
|
959
|
+
remoteVersion: remote ? componentVersion(remote.component) : lockEntry?.remoteVersion ?? null,
|
|
960
|
+
reason,
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
return {
|
|
964
|
+
schemaVersion: 1,
|
|
965
|
+
generatedAt: new Date().toISOString(),
|
|
966
|
+
workspaceRoot: path.relative(context.cwd, root) || 'Components',
|
|
967
|
+
counts: items.reduce((counts, item) => {
|
|
968
|
+
counts[item.status] += 1;
|
|
969
|
+
return counts;
|
|
970
|
+
}, {
|
|
971
|
+
clean: 0,
|
|
972
|
+
'local-changed': 0,
|
|
973
|
+
'remote-changed': 0,
|
|
974
|
+
'safe-update': 0,
|
|
975
|
+
conflict: 0,
|
|
976
|
+
create: 0,
|
|
977
|
+
'missing-lock': 0,
|
|
978
|
+
}),
|
|
979
|
+
items,
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
function printPlan(context, plan) {
|
|
983
|
+
for (const item of plan.items) {
|
|
984
|
+
context.println(`${item.status.padEnd(14)} ${item.path} ${item.reason ? `(${item.reason})` : ''}`.trimEnd());
|
|
985
|
+
}
|
|
986
|
+
context.println(`Plan: ${plan.items.length} components, ${plan.counts.conflict} conflicts, ${plan.counts['local-changed']} local changes, ${plan.counts['remote-changed']} remote changes.`);
|
|
987
|
+
}
|
|
750
988
|
function parseTargets(args, names) {
|
|
751
989
|
const values = readValues(args, names);
|
|
752
990
|
if (values.length > 0) {
|
|
@@ -794,7 +1032,7 @@ function buildDebugElements(component) {
|
|
|
794
1032
|
if (!componentName) {
|
|
795
1033
|
throw new Error('Missing component name in local manifest.');
|
|
796
1034
|
}
|
|
797
|
-
return (component.elements || []).map((element) => {
|
|
1035
|
+
return [...(component.elements || [])].sort((left, right) => left.order - right.order).map((element) => {
|
|
798
1036
|
const details = element.details || '';
|
|
799
1037
|
return {
|
|
800
1038
|
key: element.key,
|
|
@@ -809,6 +1047,101 @@ function buildDebugElements(component) {
|
|
|
809
1047
|
};
|
|
810
1048
|
});
|
|
811
1049
|
}
|
|
1050
|
+
function isDebugLibraryType(type) {
|
|
1051
|
+
return type === 'CODE_TS_LIB' || type === 'CODE_JS_LIB';
|
|
1052
|
+
}
|
|
1053
|
+
function readLibraryDebugCode(cwd, type) {
|
|
1054
|
+
const fileName = type === 'CODE_TS_LIB' ? 'debug_code.ts' : 'debug_code.js';
|
|
1055
|
+
const filePath = path.join(cwd, '.revoengine', fileName);
|
|
1056
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
1057
|
+
}
|
|
1058
|
+
function buildDebugPayload(cwd, component, input) {
|
|
1059
|
+
const type = normalizeComponentType(component);
|
|
1060
|
+
const base = {
|
|
1061
|
+
type,
|
|
1062
|
+
inputs: input.inputs,
|
|
1063
|
+
timeout: input.timeout,
|
|
1064
|
+
memory: input.memory,
|
|
1065
|
+
...(input.extraLibs?.length ? { extraLibs: input.extraLibs } : {}),
|
|
1066
|
+
};
|
|
1067
|
+
if (!isDebugLibraryType(type)) {
|
|
1068
|
+
return {
|
|
1069
|
+
...base,
|
|
1070
|
+
elements: buildDebugElements(component),
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
...base,
|
|
1075
|
+
libName: component.name || '',
|
|
1076
|
+
libElements: buildDebugElements(component),
|
|
1077
|
+
elements: [
|
|
1078
|
+
{
|
|
1079
|
+
key: 'Code',
|
|
1080
|
+
order: 0,
|
|
1081
|
+
details: readLibraryDebugCode(cwd, type),
|
|
1082
|
+
hidden: false,
|
|
1083
|
+
},
|
|
1084
|
+
],
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
function collectDebugExtraLibs(cwd, currentComponentId) {
|
|
1088
|
+
return getComponentManifestPaths(getWorkspaceRoot(cwd))
|
|
1089
|
+
.map((manifestPath) => readWorkspaceComponentSafe(manifestPath))
|
|
1090
|
+
.filter((component) => Boolean(component))
|
|
1091
|
+
.filter((component) => {
|
|
1092
|
+
const type = normalizeComponentType(component);
|
|
1093
|
+
const componentId = component.componentId || component.id || '';
|
|
1094
|
+
return isDebugLibraryType(type) && componentId !== currentComponentId && Boolean(component.name);
|
|
1095
|
+
})
|
|
1096
|
+
.map((component) => ({
|
|
1097
|
+
name: component.name,
|
|
1098
|
+
type: normalizeComponentType(component),
|
|
1099
|
+
elements: buildDebugElements(component),
|
|
1100
|
+
}));
|
|
1101
|
+
}
|
|
1102
|
+
function formatProcessLog(log) {
|
|
1103
|
+
if (!log || typeof log !== 'object' || Array.isArray(log)) {
|
|
1104
|
+
return String(log ?? '');
|
|
1105
|
+
}
|
|
1106
|
+
const record = log;
|
|
1107
|
+
const parts = [
|
|
1108
|
+
typeof record.time === 'string' ? record.time : '',
|
|
1109
|
+
typeof record.type === 'string' ? record.type : 'INFO',
|
|
1110
|
+
typeof record.context === 'string' && record.context ? `[${record.context}]` : '',
|
|
1111
|
+
typeof record.details?.message === 'string' ? record.details.message : '',
|
|
1112
|
+
].filter(Boolean);
|
|
1113
|
+
const args = record.details?.args === undefined ? '' : ` ${JSON.stringify(record.details.args)}`;
|
|
1114
|
+
return `${parts.join(' ')}${args}`;
|
|
1115
|
+
}
|
|
1116
|
+
function writeDebugOutputFile(cwd, response) {
|
|
1117
|
+
if (response === undefined || response === null || response === '') {
|
|
1118
|
+
return '';
|
|
1119
|
+
}
|
|
1120
|
+
const outputDir = path.join(cwd, '.revoengine', 'output');
|
|
1121
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
1122
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
1123
|
+
let outputFile = path.join(outputDir, `debug_${timestamp}.json`);
|
|
1124
|
+
let suffix = 1;
|
|
1125
|
+
while (fs.existsSync(outputFile)) {
|
|
1126
|
+
outputFile = path.join(outputDir, `debug_${timestamp}_${suffix}.json`);
|
|
1127
|
+
suffix += 1;
|
|
1128
|
+
}
|
|
1129
|
+
fs.writeFileSync(outputFile, `${JSON.stringify(response, null, 2)}\n`);
|
|
1130
|
+
return outputFile;
|
|
1131
|
+
}
|
|
1132
|
+
function extractResultPayload(response) {
|
|
1133
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
1134
|
+
return response;
|
|
1135
|
+
}
|
|
1136
|
+
const record = response;
|
|
1137
|
+
if (Object.prototype.hasOwnProperty.call(record, 'result')) {
|
|
1138
|
+
return record.result;
|
|
1139
|
+
}
|
|
1140
|
+
if (Object.prototype.hasOwnProperty.call(record, 'results')) {
|
|
1141
|
+
return record.results;
|
|
1142
|
+
}
|
|
1143
|
+
return response;
|
|
1144
|
+
}
|
|
812
1145
|
async function debugSingleComponent(context, componentId) {
|
|
813
1146
|
const manifests = findComponentManifestsById(context.cwd, componentId);
|
|
814
1147
|
if (manifests.length === 0) {
|
|
@@ -819,23 +1152,50 @@ async function debugSingleComponent(context, componentId) {
|
|
|
819
1152
|
}
|
|
820
1153
|
const manifestPath = manifests[0];
|
|
821
1154
|
const component = readWorkspaceComponent(manifestPath);
|
|
822
|
-
const
|
|
1155
|
+
const componentKey = component.componentId || component.id || componentId;
|
|
823
1156
|
const inputs = parseDebugInputs(context.args);
|
|
824
1157
|
const timeout = parseDebugNumber(context.args, ['timeout', 't'], 10, 600, 'Timeout');
|
|
825
1158
|
const memory = parseDebugNumber(context.args, ['memory', 'm'], 128, 1024, 'Memory');
|
|
1159
|
+
const stream = readBoolFlag(context.args, ['stream']);
|
|
1160
|
+
const includeExtraLibs = !readBoolFlag(context.args, ['no-extra-libs']);
|
|
826
1161
|
const profile = await context.client.me();
|
|
827
1162
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
828
1163
|
if (!sandboxEndpoint) {
|
|
829
1164
|
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
830
1165
|
}
|
|
831
|
-
const
|
|
832
|
-
elements: buildDebugElements(component),
|
|
833
|
-
type,
|
|
1166
|
+
const payload = buildDebugPayload(context.cwd, component, {
|
|
834
1167
|
inputs,
|
|
835
1168
|
timeout,
|
|
836
1169
|
memory,
|
|
1170
|
+
extraLibs: includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [],
|
|
837
1171
|
});
|
|
838
|
-
|
|
1172
|
+
if (stream) {
|
|
1173
|
+
let result;
|
|
1174
|
+
let done = false;
|
|
1175
|
+
for await (const event of context.client.debugComponentStream(buildSandboxDebugStreamUrl(sandboxEndpoint), payload)) {
|
|
1176
|
+
if (event.event === 'log') {
|
|
1177
|
+
process.stderr.write(`${formatProcessLog(event.data)}\n`);
|
|
1178
|
+
}
|
|
1179
|
+
else if (event.event === 'result') {
|
|
1180
|
+
result = event.data;
|
|
1181
|
+
}
|
|
1182
|
+
else if (event.event === 'error') {
|
|
1183
|
+
throw new Error(`Debug stream failed: ${JSON.stringify(event.data)}`);
|
|
1184
|
+
}
|
|
1185
|
+
else if (event.event === 'done') {
|
|
1186
|
+
done = true;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (!done) {
|
|
1190
|
+
throw new Error('Debug stream ended before the done event.');
|
|
1191
|
+
}
|
|
1192
|
+
writeDebugOutputFile(context.cwd, result);
|
|
1193
|
+
context.print(extractResultPayload(result));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), payload);
|
|
1197
|
+
writeDebugOutputFile(context.cwd, response);
|
|
1198
|
+
context.print(extractResultPayload(response));
|
|
839
1199
|
}
|
|
840
1200
|
export async function handleComponentCommand(context) {
|
|
841
1201
|
const { args } = context;
|
|
@@ -843,19 +1203,41 @@ export async function handleComponentCommand(context) {
|
|
|
843
1203
|
const targets = parseTargets(args, ['id', 'i']);
|
|
844
1204
|
const all = readFlag(args, ['all', 'a']) === 'true' || args.all === true || args.a === true;
|
|
845
1205
|
const force = readBoolFlag(args, ['force', 'f']);
|
|
1206
|
+
const yes = readBoolFlag(args, ['yes', 'y']);
|
|
846
1207
|
const stale = readBoolFlag(args, ['stale', 's']);
|
|
1208
|
+
const json = readBoolFlag(args, ['json']);
|
|
1209
|
+
const strict = readBoolFlag(args, ['strict']);
|
|
847
1210
|
if (subcommand === 'pull-all') {
|
|
848
|
-
await pullAllComponents(context, { force, stale });
|
|
1211
|
+
await pullAllComponents(context, { force, stale, yes });
|
|
849
1212
|
return;
|
|
850
1213
|
}
|
|
851
1214
|
if (subcommand === 'push-all') {
|
|
852
|
-
await pushAllComponents(context, { force });
|
|
1215
|
+
await pushAllComponents(context, { force, yes });
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
if (subcommand === 'plan') {
|
|
1219
|
+
const plan = await buildComponentPlan(context);
|
|
1220
|
+
if (json) {
|
|
1221
|
+
context.print(plan);
|
|
1222
|
+
}
|
|
1223
|
+
else {
|
|
1224
|
+
printPlan(context, plan);
|
|
1225
|
+
}
|
|
1226
|
+
if (strict) {
|
|
1227
|
+
const blocking = plan.items.filter((item) => (item.status === 'conflict'
|
|
1228
|
+
|| item.status === 'missing-lock'
|
|
1229
|
+
|| item.status === 'remote-changed'
|
|
1230
|
+
|| item.status === 'safe-update'));
|
|
1231
|
+
if (blocking.length > 0) {
|
|
1232
|
+
throw new Error(`Component plan is not safe: ${blocking.length} blocking item(s).`);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
853
1235
|
return;
|
|
854
1236
|
}
|
|
855
1237
|
if (subcommand === 'debug') {
|
|
856
1238
|
const componentId = args._[2] || readFlag(args, ['id', 'i']) || '';
|
|
857
1239
|
if (!componentId) {
|
|
858
|
-
throw new Error('Missing component ID. Usage: `revo component debug <componentId> [-d <json>] [--timeout <seconds>] [--memory <mb>]`.');
|
|
1240
|
+
throw new Error('Missing component ID. Usage: `revo component debug <componentId> [-d <json>] [--timeout <seconds>] [--memory <mb>] [--stream]`.');
|
|
859
1241
|
}
|
|
860
1242
|
await debugSingleComponent(context, componentId);
|
|
861
1243
|
return;
|
|
@@ -865,7 +1247,7 @@ export async function handleComponentCommand(context) {
|
|
|
865
1247
|
throw new Error('Provide --id <componentId> or --all.');
|
|
866
1248
|
}
|
|
867
1249
|
if (all) {
|
|
868
|
-
await pullAllComponents(context, { force, stale });
|
|
1250
|
+
await pullAllComponents(context, { force, stale, yes });
|
|
869
1251
|
return;
|
|
870
1252
|
}
|
|
871
1253
|
for (const componentId of targets) {
|
|
@@ -878,7 +1260,7 @@ export async function handleComponentCommand(context) {
|
|
|
878
1260
|
throw new Error('Provide --id <componentId> or --all.');
|
|
879
1261
|
}
|
|
880
1262
|
if (all) {
|
|
881
|
-
await pushAllComponents(context, { force });
|
|
1263
|
+
await pushAllComponents(context, { force, yes });
|
|
882
1264
|
return;
|
|
883
1265
|
}
|
|
884
1266
|
const manifests = targets.flatMap((componentId) => findComponentManifestsById(context.cwd, componentId));
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { buildProjectSyncState, buildEditorTypesUrl, extractSandboxEndpoint, extractEditorTypesBundle, resolveProjectInvocation, resolveProjectTarget, syncProjectFiles, } from "../project.js";
|
|
3
|
-
function printSyncSummary(println,
|
|
4
|
-
|
|
5
|
-
println(
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
function printSyncSummary(println, projectRoot, workspaceRoot, result, prefix) {
|
|
4
|
+
const workspaceDirectory = path.relative(projectRoot, workspaceRoot) || '.';
|
|
5
|
+
println(`${prefix} ${projectRoot}`);
|
|
6
|
+
if (workspaceDirectory !== '.') {
|
|
7
|
+
println(`Workspace ${workspaceDirectory}`);
|
|
8
|
+
}
|
|
9
|
+
println(`Wrote ${path.relative(projectRoot, result.typesFile)}`);
|
|
10
|
+
println(`Wrote ${path.relative(projectRoot, result.metadataFile)}`);
|
|
11
|
+
println(`${result.configResult.action === 'created' ? 'Created' : 'Patched'} ${path.relative(projectRoot, result.configResult.filePath)}`);
|
|
8
12
|
if (result.gitignoreResult.action === 'created') {
|
|
9
13
|
println('Created .gitignore');
|
|
10
14
|
}
|
|
@@ -33,8 +37,8 @@ export async function handleProjectCommand(context) {
|
|
|
33
37
|
if (invocation.extraArgs.length > 0) {
|
|
34
38
|
throw new Error(`Project ${invocation.action} accepts at most one path argument.`);
|
|
35
39
|
}
|
|
36
|
-
const
|
|
40
|
+
const layout = resolveProjectTarget(cwd, invocation.targetArg);
|
|
37
41
|
const syncInput = await resolveProjectSyncInput(context);
|
|
38
|
-
const result = syncProjectFiles(
|
|
39
|
-
printSyncSummary(println,
|
|
42
|
+
const result = syncProjectFiles(layout, syncInput);
|
|
43
|
+
printSyncSummary(println, layout.projectRoot, layout.workspaceRoot, result, invocation.action === 'update' ? 'Updated Revo project in' : 'Initialized Revo project in');
|
|
40
44
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ComponentRecord } from './types.ts';
|
|
2
|
+
export declare const REVO_LOCK_FILE: string;
|
|
3
|
+
export type ComponentLockEntry = {
|
|
4
|
+
componentId: string;
|
|
5
|
+
componentName: string;
|
|
6
|
+
category: string | null;
|
|
7
|
+
path: string;
|
|
8
|
+
remoteVersion: number | null;
|
|
9
|
+
remoteHash: string;
|
|
10
|
+
sourceHash: string;
|
|
11
|
+
pulledAt: string;
|
|
12
|
+
};
|
|
13
|
+
export type RevoLockFile = {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
components: Record<string, ComponentLockEntry>;
|
|
16
|
+
};
|
|
17
|
+
export declare function hashStable(value: unknown): string;
|
|
18
|
+
export declare function sanitizeComponentSource<T extends ComponentRecord>(component: T): T;
|
|
19
|
+
export declare function normalizeComponentSource(component: ComponentRecord): {
|
|
20
|
+
componentId: string;
|
|
21
|
+
name: string;
|
|
22
|
+
category: string | null;
|
|
23
|
+
desc: string | null;
|
|
24
|
+
active: boolean;
|
|
25
|
+
type: string;
|
|
26
|
+
compiler: string;
|
|
27
|
+
async: boolean;
|
|
28
|
+
elements: {
|
|
29
|
+
key: string;
|
|
30
|
+
desc: string | null;
|
|
31
|
+
hidden: boolean;
|
|
32
|
+
order: number;
|
|
33
|
+
details: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
export declare function hashComponentSource(component: ComponentRecord): string;
|
|
37
|
+
export declare function getLockPath(projectRoot: string): string;
|
|
38
|
+
export declare function readComponentLock(projectRoot: string): RevoLockFile;
|
|
39
|
+
export declare function writeComponentLock(projectRoot: string, lock: RevoLockFile): void;
|
|
40
|
+
export declare function upsertComponentLockEntry(projectRoot: string, entry: ComponentLockEntry): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { REVO_PROJECT_DIR } from "./project.js";
|
|
5
|
+
import { readJsonFile, writeJsonFile } from "./utils.js";
|
|
6
|
+
export const REVO_LOCK_FILE = path.join(REVO_PROJECT_DIR, 'revo.lock.json');
|
|
7
|
+
const VOLATILE_COMPONENT_FIELDS = new Set([
|
|
8
|
+
'version',
|
|
9
|
+
'revision',
|
|
10
|
+
'deployedAt',
|
|
11
|
+
'lastDeployedAt',
|
|
12
|
+
'lastSyncAt',
|
|
13
|
+
'lastUpdateAt',
|
|
14
|
+
'lastUpdateBy',
|
|
15
|
+
'createdAt',
|
|
16
|
+
'createdBy',
|
|
17
|
+
'updatedAt',
|
|
18
|
+
'updatedBy',
|
|
19
|
+
'deletedAt',
|
|
20
|
+
'deletedBy',
|
|
21
|
+
'hash',
|
|
22
|
+
'remoteHash',
|
|
23
|
+
]);
|
|
24
|
+
function normalizeValue(value, fallback = null) {
|
|
25
|
+
return value ?? fallback;
|
|
26
|
+
}
|
|
27
|
+
function stableObject(value) {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
return value.map((item) => stableObject(item));
|
|
30
|
+
}
|
|
31
|
+
if (!value || typeof value !== 'object') {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
const output = {};
|
|
35
|
+
for (const key of Object.keys(value).sort()) {
|
|
36
|
+
output[key] = stableObject(value[key]);
|
|
37
|
+
}
|
|
38
|
+
return output;
|
|
39
|
+
}
|
|
40
|
+
export function hashStable(value) {
|
|
41
|
+
return crypto
|
|
42
|
+
.createHash('sha256')
|
|
43
|
+
.update(JSON.stringify(stableObject(value)))
|
|
44
|
+
.digest('hex');
|
|
45
|
+
}
|
|
46
|
+
export function sanitizeComponentSource(component) {
|
|
47
|
+
const clone = JSON.parse(JSON.stringify(component));
|
|
48
|
+
for (const field of VOLATILE_COMPONENT_FIELDS) {
|
|
49
|
+
delete clone[field];
|
|
50
|
+
}
|
|
51
|
+
return clone;
|
|
52
|
+
}
|
|
53
|
+
export function normalizeComponentSource(component) {
|
|
54
|
+
const source = sanitizeComponentSource(component);
|
|
55
|
+
const componentId = source.componentId || source.id || '';
|
|
56
|
+
return {
|
|
57
|
+
componentId,
|
|
58
|
+
name: source.name || '',
|
|
59
|
+
category: normalizeValue(source.category),
|
|
60
|
+
desc: normalizeValue(source.desc),
|
|
61
|
+
active: source.active ?? true,
|
|
62
|
+
type: source.type || '',
|
|
63
|
+
compiler: source.compiler || '',
|
|
64
|
+
async: Boolean(source.async),
|
|
65
|
+
elements: [...(source.elements || [])]
|
|
66
|
+
.map((element) => ({
|
|
67
|
+
key: element.key,
|
|
68
|
+
desc: normalizeValue(element.desc),
|
|
69
|
+
hidden: Boolean(element.hidden),
|
|
70
|
+
order: element.order,
|
|
71
|
+
details: element.details ?? '',
|
|
72
|
+
}))
|
|
73
|
+
.sort((left, right) => left.order - right.order || left.key.localeCompare(right.key)),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function hashComponentSource(component) {
|
|
77
|
+
return hashStable(normalizeComponentSource(component));
|
|
78
|
+
}
|
|
79
|
+
export function getLockPath(projectRoot) {
|
|
80
|
+
return path.join(projectRoot, REVO_LOCK_FILE);
|
|
81
|
+
}
|
|
82
|
+
export function readComponentLock(projectRoot) {
|
|
83
|
+
const lockPath = getLockPath(projectRoot);
|
|
84
|
+
if (!fs.existsSync(lockPath)) {
|
|
85
|
+
return {
|
|
86
|
+
schemaVersion: 1,
|
|
87
|
+
components: {},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const raw = readJsonFile(lockPath);
|
|
91
|
+
return {
|
|
92
|
+
schemaVersion: 1,
|
|
93
|
+
components: raw.components && typeof raw.components === 'object' ? raw.components : {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function writeComponentLock(projectRoot, lock) {
|
|
97
|
+
const sortedComponents = Object.fromEntries(Object.entries(lock.components).sort(([left], [right]) => left.localeCompare(right)));
|
|
98
|
+
writeJsonFile(getLockPath(projectRoot), {
|
|
99
|
+
schemaVersion: 1,
|
|
100
|
+
components: sortedComponents,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function upsertComponentLockEntry(projectRoot, entry) {
|
|
104
|
+
const lock = readComponentLock(projectRoot);
|
|
105
|
+
lock.components[entry.componentId] = entry;
|
|
106
|
+
writeComponentLock(projectRoot, lock);
|
|
107
|
+
}
|
package/dist/src/project.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ParsedArgs } from './types.ts';
|
|
|
2
2
|
export declare const REVO_PROJECT_DIR = ".revoengine";
|
|
3
3
|
export declare const REVO_TYPES_DIR: string;
|
|
4
4
|
export declare const REVO_TYPES_FILE: string;
|
|
5
|
+
export declare const REVO_TYPES_DEFINITIONS_FILE: string;
|
|
5
6
|
export declare const REVO_METADATA_FILE: string;
|
|
6
7
|
export declare const REVO_TYPES_GITIGNORE_ENTRY = ".revoengine/types/";
|
|
7
8
|
export declare const REVO_DEBUG_JS_FILE: string;
|
|
@@ -17,6 +18,7 @@ export type ProjectInvocation = {
|
|
|
17
18
|
};
|
|
18
19
|
export type EditorTypesBundle = {
|
|
19
20
|
code: string;
|
|
21
|
+
definitions?: unknown[];
|
|
20
22
|
endpoint?: string;
|
|
21
23
|
apiVersion?: string;
|
|
22
24
|
libVersion?: string;
|
|
@@ -36,6 +38,7 @@ export type RevoProjectMetadata = {
|
|
|
36
38
|
export type ProjectSyncInput = {
|
|
37
39
|
endpoint: string;
|
|
38
40
|
code: string;
|
|
41
|
+
definitions: unknown[];
|
|
39
42
|
apiVersion: string;
|
|
40
43
|
libVersion: string;
|
|
41
44
|
hash: string;
|
|
@@ -49,12 +52,18 @@ export type GitignorePatchResult = {
|
|
|
49
52
|
filePath: string;
|
|
50
53
|
action: 'created' | 'patched' | 'unchanged';
|
|
51
54
|
};
|
|
55
|
+
export type ProjectTargetLayout = {
|
|
56
|
+
projectRoot: string;
|
|
57
|
+
workspaceRoot: string;
|
|
58
|
+
workspaceDirectory: string;
|
|
59
|
+
};
|
|
52
60
|
export declare function resolveProjectInvocation(args: ParsedArgs): ProjectInvocation;
|
|
53
|
-
export declare function resolveProjectTarget(cwd: string, targetArg?: string):
|
|
61
|
+
export declare function resolveProjectTarget(cwd: string, targetArg?: string): ProjectTargetLayout;
|
|
54
62
|
export declare function extractEditorTypesBundle(payload: unknown): EditorTypesBundle;
|
|
55
63
|
export declare function buildEditorEndpoint(baseUrl: string): string;
|
|
56
64
|
export declare function buildEditorTypesUrl(endpoint: string): string;
|
|
57
65
|
export declare function buildSandboxDebugUrl(endpoint: string): string;
|
|
66
|
+
export declare function buildSandboxDebugStreamUrl(endpoint: string): string;
|
|
58
67
|
export declare function extractSandboxEndpoint(profile: unknown): string | null;
|
|
59
68
|
export declare function buildProjectMetadata(input: {
|
|
60
69
|
baseUrl: string;
|
|
@@ -80,18 +89,20 @@ export declare function buildProjectSyncState(input: {
|
|
|
80
89
|
}): {
|
|
81
90
|
endpoint: string;
|
|
82
91
|
code: string;
|
|
92
|
+
definitions: unknown[];
|
|
83
93
|
apiVersion: string;
|
|
84
94
|
libVersion: string;
|
|
85
95
|
hash: string;
|
|
86
96
|
lastSyncAt: string;
|
|
87
97
|
};
|
|
88
|
-
export declare function syncProjectFiles(
|
|
98
|
+
export declare function syncProjectFiles(layoutOrTargetDir: ProjectTargetLayout | string, input: ProjectSyncInput): {
|
|
89
99
|
projectDir: string;
|
|
90
100
|
typesFile: string;
|
|
101
|
+
definitionsFile: string;
|
|
91
102
|
metadataFile: string;
|
|
92
103
|
configResult: ProjectConfigResult;
|
|
93
104
|
gitignoreResult: GitignorePatchResult;
|
|
94
105
|
};
|
|
95
106
|
export declare function ensureProjectDebugScaffolding(rootDir: string): void;
|
|
96
|
-
export declare function ensureProjectConfig(rootDir: string): ProjectConfigResult;
|
|
107
|
+
export declare function ensureProjectConfig(rootDir: string, typesInclude?: string): ProjectConfigResult;
|
|
97
108
|
export declare function ensureProjectGitignore(rootDir: string): GitignorePatchResult;
|
package/dist/src/project.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readJsonFile } from "./utils.js";
|
|
|
4
4
|
export const REVO_PROJECT_DIR = '.revoengine';
|
|
5
5
|
export const REVO_TYPES_DIR = path.join(REVO_PROJECT_DIR, 'types');
|
|
6
6
|
export const REVO_TYPES_FILE = path.join(REVO_TYPES_DIR, 'revo.editor.d.ts');
|
|
7
|
+
export const REVO_TYPES_DEFINITIONS_FILE = path.join(REVO_TYPES_DIR, 'revo.editor.definitions.json');
|
|
7
8
|
export const REVO_METADATA_FILE = path.join(REVO_PROJECT_DIR, 'revo.json');
|
|
8
9
|
export const REVO_TYPES_GITIGNORE_ENTRY = '.revoengine/types/';
|
|
9
10
|
export const REVO_DEBUG_JS_FILE = path.join(REVO_PROJECT_DIR, 'debug_code.js');
|
|
@@ -208,8 +209,29 @@ export function resolveProjectInvocation(args) {
|
|
|
208
209
|
extraArgs: second ? [second, ...rest] : rest,
|
|
209
210
|
};
|
|
210
211
|
}
|
|
212
|
+
function normalizeWorkspaceDirectory(projectRoot, workspaceRoot) {
|
|
213
|
+
const relative = path.relative(projectRoot, workspaceRoot).split(path.sep).join('/');
|
|
214
|
+
if (relative === '') {
|
|
215
|
+
return '.';
|
|
216
|
+
}
|
|
217
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
218
|
+
throw new Error('Project workspace directory must stay inside the Revo project root.');
|
|
219
|
+
}
|
|
220
|
+
return relative;
|
|
221
|
+
}
|
|
211
222
|
export function resolveProjectTarget(cwd, targetArg) {
|
|
212
|
-
|
|
223
|
+
const existingProjectRoot = resolveProjectRoot(cwd);
|
|
224
|
+
const projectRoot = existingProjectRoot || path.resolve(cwd);
|
|
225
|
+
const existingMetadata = existingProjectRoot ? readProjectMetadata(projectRoot) : null;
|
|
226
|
+
const workspaceRoot = targetArg
|
|
227
|
+
? path.resolve(cwd, targetArg)
|
|
228
|
+
: resolveProjectWorkspaceRoot(projectRoot, existingMetadata?.workspace || '.');
|
|
229
|
+
const workspaceDirectory = normalizeWorkspaceDirectory(projectRoot, workspaceRoot);
|
|
230
|
+
return {
|
|
231
|
+
projectRoot,
|
|
232
|
+
workspaceRoot,
|
|
233
|
+
workspaceDirectory,
|
|
234
|
+
};
|
|
213
235
|
}
|
|
214
236
|
export function extractEditorTypesBundle(payload) {
|
|
215
237
|
const root = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
|
|
@@ -223,6 +245,7 @@ export function extractEditorTypesBundle(payload) {
|
|
|
223
245
|
}
|
|
224
246
|
return {
|
|
225
247
|
code,
|
|
248
|
+
definitions: Array.isArray(root.definitions) ? root.definitions : [],
|
|
226
249
|
endpoint: readString(root.endpoint) || readString(meta?.endpoint),
|
|
227
250
|
apiVersion: readString(root.apiVersion) || readString(meta?.apiVersion),
|
|
228
251
|
libVersion: readString(root.libVersion) || readString(meta?.libVersion),
|
|
@@ -256,6 +279,15 @@ export function buildSandboxDebugUrl(endpoint) {
|
|
|
256
279
|
url.hash = '';
|
|
257
280
|
return url.toString();
|
|
258
281
|
}
|
|
282
|
+
export function buildSandboxDebugStreamUrl(endpoint) {
|
|
283
|
+
const url = new URL(endpoint);
|
|
284
|
+
const basePath = normalizeEndpoint(url.pathname);
|
|
285
|
+
const versionedPath = /\/v\d+$/i.test(basePath) ? basePath : `${basePath || ''}/v1`;
|
|
286
|
+
url.pathname = `${versionedPath}/debug/stream`.replace(/\/{2,}/g, '/');
|
|
287
|
+
url.search = '';
|
|
288
|
+
url.hash = '';
|
|
289
|
+
return url.toString();
|
|
290
|
+
}
|
|
259
291
|
export function extractSandboxEndpoint(profile) {
|
|
260
292
|
if (!profile || typeof profile !== 'object') {
|
|
261
293
|
return null;
|
|
@@ -368,20 +400,38 @@ export function buildProjectSyncState(input) {
|
|
|
368
400
|
return {
|
|
369
401
|
endpoint: metadata.endpoint,
|
|
370
402
|
code: input.bundle.code,
|
|
403
|
+
definitions: input.bundle.definitions ?? [],
|
|
371
404
|
apiVersion: metadata.apiVersion,
|
|
372
405
|
libVersion: metadata.libVersion,
|
|
373
406
|
hash: metadata.hash,
|
|
374
407
|
lastSyncAt: metadata.lastSyncAt,
|
|
375
408
|
};
|
|
376
409
|
}
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
410
|
+
function toPosixRelative(fromDir, toPath) {
|
|
411
|
+
const relative = path.relative(fromDir, toPath).split(path.sep).join('/');
|
|
412
|
+
return relative || path.basename(toPath);
|
|
413
|
+
}
|
|
414
|
+
export function syncProjectFiles(layoutOrTargetDir, input) {
|
|
415
|
+
const layout = typeof layoutOrTargetDir === 'string'
|
|
416
|
+
? {
|
|
417
|
+
projectRoot: layoutOrTargetDir,
|
|
418
|
+
workspaceRoot: layoutOrTargetDir,
|
|
419
|
+
workspaceDirectory: '.',
|
|
420
|
+
}
|
|
421
|
+
: layoutOrTargetDir;
|
|
422
|
+
const { projectRoot, workspaceRoot, workspaceDirectory } = layout;
|
|
423
|
+
const projectDir = path.join(projectRoot, REVO_PROJECT_DIR);
|
|
424
|
+
const typesDir = path.join(projectRoot, REVO_TYPES_DIR);
|
|
380
425
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
381
426
|
fs.mkdirSync(typesDir, { recursive: true });
|
|
382
|
-
const typesFile = path.join(
|
|
383
|
-
const
|
|
427
|
+
const typesFile = path.join(projectRoot, REVO_TYPES_FILE);
|
|
428
|
+
const definitionsFile = path.join(projectRoot, REVO_TYPES_DEFINITIONS_FILE);
|
|
429
|
+
const metadataFile = path.join(projectRoot, REVO_METADATA_FILE);
|
|
384
430
|
fs.writeFileSync(typesFile, input.code);
|
|
431
|
+
writeJsonFile(definitionsFile, {
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
definitions: input.definitions,
|
|
434
|
+
});
|
|
385
435
|
writeJsonFile(metadataFile, {
|
|
386
436
|
schemaVersion: 1,
|
|
387
437
|
endpoint: input.endpoint,
|
|
@@ -390,13 +440,15 @@ export function syncProjectFiles(targetDir, input) {
|
|
|
390
440
|
libVersion: input.libVersion,
|
|
391
441
|
hash: input.hash,
|
|
392
442
|
lastSyncAt: input.lastSyncAt,
|
|
443
|
+
...(workspaceDirectory !== '.' ? { workspace: workspaceDirectory } : {}),
|
|
393
444
|
});
|
|
394
|
-
const configResult = ensureProjectConfig(
|
|
395
|
-
const gitignoreResult = ensureProjectGitignore(
|
|
396
|
-
ensureProjectDebugScaffolding(
|
|
445
|
+
const configResult = ensureProjectConfig(workspaceRoot, toPosixRelative(workspaceRoot, typesFile));
|
|
446
|
+
const gitignoreResult = ensureProjectGitignore(projectRoot);
|
|
447
|
+
ensureProjectDebugScaffolding(projectRoot);
|
|
397
448
|
return {
|
|
398
449
|
projectDir,
|
|
399
450
|
typesFile,
|
|
451
|
+
definitionsFile,
|
|
400
452
|
metadataFile,
|
|
401
453
|
configResult,
|
|
402
454
|
gitignoreResult,
|
|
@@ -422,7 +474,7 @@ export function ensureProjectDebugScaffolding(rootDir) {
|
|
|
422
474
|
fs.writeFileSync(filePath, contents);
|
|
423
475
|
}
|
|
424
476
|
}
|
|
425
|
-
export function ensureProjectConfig(rootDir) {
|
|
477
|
+
export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
|
|
426
478
|
const tsconfigPath = path.join(rootDir, 'tsconfig.json');
|
|
427
479
|
const jsconfigPath = path.join(rootDir, 'jsconfig.json');
|
|
428
480
|
if (!fs.existsSync(tsconfigPath) && !fs.existsSync(jsconfigPath)) {
|
|
@@ -433,7 +485,10 @@ export function ensureProjectConfig(rootDir) {
|
|
|
433
485
|
noEmit: true,
|
|
434
486
|
skipLibCheck: true,
|
|
435
487
|
},
|
|
436
|
-
include:
|
|
488
|
+
include: [
|
|
489
|
+
...DEFAULT_PROJECT_INCLUDE.filter((entry) => entry !== REVO_TYPES_FILE),
|
|
490
|
+
typesInclude,
|
|
491
|
+
],
|
|
437
492
|
exclude: DEFAULT_PROJECT_EXCLUDE,
|
|
438
493
|
});
|
|
439
494
|
return {
|
|
@@ -456,13 +511,16 @@ export function ensureProjectConfig(rootDir) {
|
|
|
456
511
|
const files = ensureStringArray(parsed.files, 'files', filePath);
|
|
457
512
|
const include = ensureStringArray(parsed.include, 'include', filePath);
|
|
458
513
|
if (files) {
|
|
459
|
-
parsed.files = appendUnique(files,
|
|
514
|
+
parsed.files = appendUnique(files, typesInclude);
|
|
460
515
|
}
|
|
461
516
|
else if (include) {
|
|
462
|
-
parsed.include = appendUnique(include,
|
|
517
|
+
parsed.include = appendUnique(include, typesInclude);
|
|
463
518
|
}
|
|
464
519
|
else {
|
|
465
|
-
parsed.include =
|
|
520
|
+
parsed.include = [
|
|
521
|
+
...DEFAULT_PROJECT_INCLUDE.filter((entry) => entry !== REVO_TYPES_FILE),
|
|
522
|
+
typesInclude,
|
|
523
|
+
];
|
|
466
524
|
}
|
|
467
525
|
writeJsonFile(filePath, parsed);
|
|
468
526
|
return {
|
package/dist/src/ui.js
CHANGED
|
@@ -108,11 +108,12 @@ function renderCommandCatalog() {
|
|
|
108
108
|
commandRow('revo endpoints', 'List available API endpoints'),
|
|
109
109
|
'',
|
|
110
110
|
paintHeader('Components'),
|
|
111
|
-
commandRow('revo component
|
|
112
|
-
commandRow('revo component pull
|
|
111
|
+
commandRow('revo component plan --all [--json] [--strict]', 'Preview local/remote sync status'),
|
|
112
|
+
commandRow('revo component pull <componentId...> [--force]', 'Pull one or more components safely'),
|
|
113
|
+
commandRow('revo component pull --all [--yes] [--force]', 'Pull every available component safely'),
|
|
113
114
|
commandRow('revo component push <componentId...>', 'Push one or more local components'),
|
|
114
|
-
commandRow('revo component push --all [--force]', 'Push every local component
|
|
115
|
-
commandRow('revo component debug <componentId> [BODY]', 'Debug one local component against sandbox'),
|
|
115
|
+
commandRow('revo component push --all [--yes] [--force]', 'Push every local component with lock checks'),
|
|
116
|
+
commandRow('revo component debug <componentId> [BODY] [--stream]', 'Debug one local component against sandbox'),
|
|
116
117
|
'',
|
|
117
118
|
paintHeader('Low-Level'),
|
|
118
119
|
commandRow('revo search <CODE|SIMPLE> <term>', 'Search code references or all platform content'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revoengine/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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/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')\"",
|
|
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/component-lock.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"
|