@revoengine/cli 1.0.3 → 1.0.5
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 +6 -1
- package/dist/src/client.d.ts +5 -0
- package/dist/src/client.js +104 -0
- package/dist/src/commands/component.js +130 -8
- package/dist/src/commands/project.js +13 -12
- package/dist/src/project.d.ts +14 -4
- package/dist/src/project.js +72 -23
- package/dist/src/ui.js +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,9 +54,10 @@ Initialize a project for ambient low-code editor globals:
|
|
|
54
54
|
```bash
|
|
55
55
|
revo project
|
|
56
56
|
revo project init ./app
|
|
57
|
+
revo project update ./app
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
This writes `.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.
|
|
60
61
|
|
|
61
62
|
## Common commands
|
|
62
63
|
|
|
@@ -116,6 +117,7 @@ Debug one local component in sandbox:
|
|
|
116
117
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc
|
|
117
118
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc -d '{"filters":{}}'
|
|
118
119
|
revo component debug 6dfb536a-1111-4222-8333-123456789abc --timeout 30 --memory 256
|
|
120
|
+
revo component debug 6dfb536a-1111-4222-8333-123456789abc --stream
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
Pulled components are stored as a tree:
|
|
@@ -144,6 +146,9 @@ Bulk sync behavior:
|
|
|
144
146
|
- Push treats backend `Not modified` responses as skipped instead of failing the whole run.
|
|
145
147
|
- Push treats backend `404` responses as skipped with `doesn't exist remotely`; restore the component in RevoEngine before pushing local changes to it.
|
|
146
148
|
- Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the authenticated sandbox `debug` endpoint.
|
|
149
|
+
- `revo component debug --stream` posts to `debug/stream`, writes live `api.log()` frames to stderr, and writes the final raw result payload to stdout.
|
|
150
|
+
- Debug responses are saved under `.revoengine/output/debug_<timestamp>.json`.
|
|
151
|
+
- 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.
|
|
147
152
|
- Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
|
|
148
153
|
- Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
|
|
149
154
|
|
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,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { ApiError, PermissionDeniedError } from "../client.js";
|
|
4
|
-
import { buildSandboxDebugUrl, extractSandboxEndpoint, resolveProjectWorkspace } from "../project.js";
|
|
4
|
+
import { buildSandboxDebugStreamUrl, buildSandboxDebugUrl, extractSandboxEndpoint, resolveProjectWorkspace } 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__';
|
|
@@ -794,7 +794,7 @@ function buildDebugElements(component) {
|
|
|
794
794
|
if (!componentName) {
|
|
795
795
|
throw new Error('Missing component name in local manifest.');
|
|
796
796
|
}
|
|
797
|
-
return (component.elements || []).map((element) => {
|
|
797
|
+
return [...(component.elements || [])].sort((left, right) => left.order - right.order).map((element) => {
|
|
798
798
|
const details = element.details || '';
|
|
799
799
|
return {
|
|
800
800
|
key: element.key,
|
|
@@ -809,6 +809,101 @@ function buildDebugElements(component) {
|
|
|
809
809
|
};
|
|
810
810
|
});
|
|
811
811
|
}
|
|
812
|
+
function isDebugLibraryType(type) {
|
|
813
|
+
return type === 'CODE_TS_LIB' || type === 'CODE_JS_LIB';
|
|
814
|
+
}
|
|
815
|
+
function readLibraryDebugCode(cwd, type) {
|
|
816
|
+
const fileName = type === 'CODE_TS_LIB' ? 'debug_code.ts' : 'debug_code.js';
|
|
817
|
+
const filePath = path.join(cwd, '.revoengine', fileName);
|
|
818
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
819
|
+
}
|
|
820
|
+
function buildDebugPayload(cwd, component, input) {
|
|
821
|
+
const type = normalizeComponentType(component);
|
|
822
|
+
const base = {
|
|
823
|
+
type,
|
|
824
|
+
inputs: input.inputs,
|
|
825
|
+
timeout: input.timeout,
|
|
826
|
+
memory: input.memory,
|
|
827
|
+
...(input.extraLibs?.length ? { extraLibs: input.extraLibs } : {}),
|
|
828
|
+
};
|
|
829
|
+
if (!isDebugLibraryType(type)) {
|
|
830
|
+
return {
|
|
831
|
+
...base,
|
|
832
|
+
elements: buildDebugElements(component),
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
return {
|
|
836
|
+
...base,
|
|
837
|
+
libName: component.name || '',
|
|
838
|
+
libElements: buildDebugElements(component),
|
|
839
|
+
elements: [
|
|
840
|
+
{
|
|
841
|
+
key: 'Code',
|
|
842
|
+
order: 0,
|
|
843
|
+
details: readLibraryDebugCode(cwd, type),
|
|
844
|
+
hidden: false,
|
|
845
|
+
},
|
|
846
|
+
],
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function collectDebugExtraLibs(cwd, currentComponentId) {
|
|
850
|
+
return getComponentManifestPaths(getWorkspaceRoot(cwd))
|
|
851
|
+
.map((manifestPath) => readWorkspaceComponentSafe(manifestPath))
|
|
852
|
+
.filter((component) => Boolean(component))
|
|
853
|
+
.filter((component) => {
|
|
854
|
+
const type = normalizeComponentType(component);
|
|
855
|
+
const componentId = component.componentId || component.id || '';
|
|
856
|
+
return isDebugLibraryType(type) && componentId !== currentComponentId && Boolean(component.name);
|
|
857
|
+
})
|
|
858
|
+
.map((component) => ({
|
|
859
|
+
name: component.name,
|
|
860
|
+
type: normalizeComponentType(component),
|
|
861
|
+
elements: buildDebugElements(component),
|
|
862
|
+
}));
|
|
863
|
+
}
|
|
864
|
+
function formatProcessLog(log) {
|
|
865
|
+
if (!log || typeof log !== 'object' || Array.isArray(log)) {
|
|
866
|
+
return String(log ?? '');
|
|
867
|
+
}
|
|
868
|
+
const record = log;
|
|
869
|
+
const parts = [
|
|
870
|
+
typeof record.time === 'string' ? record.time : '',
|
|
871
|
+
typeof record.type === 'string' ? record.type : 'INFO',
|
|
872
|
+
typeof record.context === 'string' && record.context ? `[${record.context}]` : '',
|
|
873
|
+
typeof record.details?.message === 'string' ? record.details.message : '',
|
|
874
|
+
].filter(Boolean);
|
|
875
|
+
const args = record.details?.args === undefined ? '' : ` ${JSON.stringify(record.details.args)}`;
|
|
876
|
+
return `${parts.join(' ')}${args}`;
|
|
877
|
+
}
|
|
878
|
+
function writeDebugOutputFile(cwd, response) {
|
|
879
|
+
if (response === undefined || response === null || response === '') {
|
|
880
|
+
return '';
|
|
881
|
+
}
|
|
882
|
+
const outputDir = path.join(cwd, '.revoengine', 'output');
|
|
883
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
884
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
885
|
+
let outputFile = path.join(outputDir, `debug_${timestamp}.json`);
|
|
886
|
+
let suffix = 1;
|
|
887
|
+
while (fs.existsSync(outputFile)) {
|
|
888
|
+
outputFile = path.join(outputDir, `debug_${timestamp}_${suffix}.json`);
|
|
889
|
+
suffix += 1;
|
|
890
|
+
}
|
|
891
|
+
fs.writeFileSync(outputFile, `${JSON.stringify(response, null, 2)}\n`);
|
|
892
|
+
return outputFile;
|
|
893
|
+
}
|
|
894
|
+
function extractResultPayload(response) {
|
|
895
|
+
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
896
|
+
return response;
|
|
897
|
+
}
|
|
898
|
+
const record = response;
|
|
899
|
+
if (Object.prototype.hasOwnProperty.call(record, 'result')) {
|
|
900
|
+
return record.result;
|
|
901
|
+
}
|
|
902
|
+
if (Object.prototype.hasOwnProperty.call(record, 'results')) {
|
|
903
|
+
return record.results;
|
|
904
|
+
}
|
|
905
|
+
return response;
|
|
906
|
+
}
|
|
812
907
|
async function debugSingleComponent(context, componentId) {
|
|
813
908
|
const manifests = findComponentManifestsById(context.cwd, componentId);
|
|
814
909
|
if (manifests.length === 0) {
|
|
@@ -819,23 +914,50 @@ async function debugSingleComponent(context, componentId) {
|
|
|
819
914
|
}
|
|
820
915
|
const manifestPath = manifests[0];
|
|
821
916
|
const component = readWorkspaceComponent(manifestPath);
|
|
822
|
-
const
|
|
917
|
+
const componentKey = component.componentId || component.id || componentId;
|
|
823
918
|
const inputs = parseDebugInputs(context.args);
|
|
824
919
|
const timeout = parseDebugNumber(context.args, ['timeout', 't'], 10, 600, 'Timeout');
|
|
825
920
|
const memory = parseDebugNumber(context.args, ['memory', 'm'], 128, 1024, 'Memory');
|
|
921
|
+
const stream = readBoolFlag(context.args, ['stream']);
|
|
922
|
+
const includeExtraLibs = !readBoolFlag(context.args, ['no-extra-libs']);
|
|
826
923
|
const profile = await context.client.me();
|
|
827
924
|
const sandboxEndpoint = extractSandboxEndpoint(profile);
|
|
828
925
|
if (!sandboxEndpoint) {
|
|
829
926
|
throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
|
|
830
927
|
}
|
|
831
|
-
const
|
|
832
|
-
elements: buildDebugElements(component),
|
|
833
|
-
type,
|
|
928
|
+
const payload = buildDebugPayload(context.cwd, component, {
|
|
834
929
|
inputs,
|
|
835
930
|
timeout,
|
|
836
931
|
memory,
|
|
932
|
+
extraLibs: includeExtraLibs ? collectDebugExtraLibs(context.cwd, componentKey) : [],
|
|
837
933
|
});
|
|
838
|
-
|
|
934
|
+
if (stream) {
|
|
935
|
+
let result;
|
|
936
|
+
let done = false;
|
|
937
|
+
for await (const event of context.client.debugComponentStream(buildSandboxDebugStreamUrl(sandboxEndpoint), payload)) {
|
|
938
|
+
if (event.event === 'log') {
|
|
939
|
+
process.stderr.write(`${formatProcessLog(event.data)}\n`);
|
|
940
|
+
}
|
|
941
|
+
else if (event.event === 'result') {
|
|
942
|
+
result = event.data;
|
|
943
|
+
}
|
|
944
|
+
else if (event.event === 'error') {
|
|
945
|
+
throw new Error(`Debug stream failed: ${JSON.stringify(event.data)}`);
|
|
946
|
+
}
|
|
947
|
+
else if (event.event === 'done') {
|
|
948
|
+
done = true;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (!done) {
|
|
952
|
+
throw new Error('Debug stream ended before the done event.');
|
|
953
|
+
}
|
|
954
|
+
writeDebugOutputFile(context.cwd, result);
|
|
955
|
+
context.print(extractResultPayload(result));
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), payload);
|
|
959
|
+
writeDebugOutputFile(context.cwd, response);
|
|
960
|
+
context.print(extractResultPayload(response));
|
|
839
961
|
}
|
|
840
962
|
export async function handleComponentCommand(context) {
|
|
841
963
|
const { args } = context;
|
|
@@ -855,7 +977,7 @@ export async function handleComponentCommand(context) {
|
|
|
855
977
|
if (subcommand === 'debug') {
|
|
856
978
|
const componentId = args._[2] || readFlag(args, ['id', 'i']) || '';
|
|
857
979
|
if (!componentId) {
|
|
858
|
-
throw new Error('Missing component ID. Usage: `revo component debug <componentId> [-d <json>] [--timeout <seconds>] [--memory <mb>]`.');
|
|
980
|
+
throw new Error('Missing component ID. Usage: `revo component debug <componentId> [-d <json>] [--timeout <seconds>] [--memory <mb>] [--stream]`.');
|
|
859
981
|
}
|
|
860
982
|
await debugSingleComponent(context, componentId);
|
|
861
983
|
return;
|
|
@@ -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
|
}
|
|
@@ -30,14 +34,11 @@ async function resolveProjectSyncInput(context) {
|
|
|
30
34
|
export async function handleProjectCommand(context) {
|
|
31
35
|
const { args, client, cwd, println } = context;
|
|
32
36
|
const invocation = resolveProjectInvocation(args);
|
|
33
|
-
if (invocation.action === 'update') {
|
|
34
|
-
throw new Error('`revo project update` is not implemented yet.');
|
|
35
|
-
}
|
|
36
37
|
if (invocation.extraArgs.length > 0) {
|
|
37
|
-
throw new Error(
|
|
38
|
+
throw new Error(`Project ${invocation.action} accepts at most one path argument.`);
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
+
const layout = resolveProjectTarget(cwd, invocation.targetArg);
|
|
40
41
|
const syncInput = await resolveProjectSyncInput(context);
|
|
41
|
-
const result = syncProjectFiles(
|
|
42
|
-
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');
|
|
43
44
|
}
|
package/dist/src/project.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ 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
|
-
export declare const REVO_VSCODE_CONFIG_FILE: string;
|
|
7
7
|
export declare const REVO_TYPES_GITIGNORE_ENTRY = ".revoengine/types/";
|
|
8
8
|
export declare const REVO_DEBUG_JS_FILE: string;
|
|
9
9
|
export declare const REVO_DEBUG_TS_FILE: string;
|
|
@@ -18,6 +18,7 @@ export type ProjectInvocation = {
|
|
|
18
18
|
};
|
|
19
19
|
export type EditorTypesBundle = {
|
|
20
20
|
code: string;
|
|
21
|
+
definitions?: unknown[];
|
|
21
22
|
endpoint?: string;
|
|
22
23
|
apiVersion?: string;
|
|
23
24
|
libVersion?: string;
|
|
@@ -37,6 +38,7 @@ export type RevoProjectMetadata = {
|
|
|
37
38
|
export type ProjectSyncInput = {
|
|
38
39
|
endpoint: string;
|
|
39
40
|
code: string;
|
|
41
|
+
definitions: unknown[];
|
|
40
42
|
apiVersion: string;
|
|
41
43
|
libVersion: string;
|
|
42
44
|
hash: string;
|
|
@@ -50,12 +52,18 @@ export type GitignorePatchResult = {
|
|
|
50
52
|
filePath: string;
|
|
51
53
|
action: 'created' | 'patched' | 'unchanged';
|
|
52
54
|
};
|
|
55
|
+
export type ProjectTargetLayout = {
|
|
56
|
+
projectRoot: string;
|
|
57
|
+
workspaceRoot: string;
|
|
58
|
+
workspaceDirectory: string;
|
|
59
|
+
};
|
|
53
60
|
export declare function resolveProjectInvocation(args: ParsedArgs): ProjectInvocation;
|
|
54
|
-
export declare function resolveProjectTarget(cwd: string, targetArg?: string):
|
|
61
|
+
export declare function resolveProjectTarget(cwd: string, targetArg?: string): ProjectTargetLayout;
|
|
55
62
|
export declare function extractEditorTypesBundle(payload: unknown): EditorTypesBundle;
|
|
56
63
|
export declare function buildEditorEndpoint(baseUrl: string): string;
|
|
57
64
|
export declare function buildEditorTypesUrl(endpoint: string): string;
|
|
58
65
|
export declare function buildSandboxDebugUrl(endpoint: string): string;
|
|
66
|
+
export declare function buildSandboxDebugStreamUrl(endpoint: string): string;
|
|
59
67
|
export declare function extractSandboxEndpoint(profile: unknown): string | null;
|
|
60
68
|
export declare function buildProjectMetadata(input: {
|
|
61
69
|
baseUrl: string;
|
|
@@ -81,18 +89,20 @@ export declare function buildProjectSyncState(input: {
|
|
|
81
89
|
}): {
|
|
82
90
|
endpoint: string;
|
|
83
91
|
code: string;
|
|
92
|
+
definitions: unknown[];
|
|
84
93
|
apiVersion: string;
|
|
85
94
|
libVersion: string;
|
|
86
95
|
hash: string;
|
|
87
96
|
lastSyncAt: string;
|
|
88
97
|
};
|
|
89
|
-
export declare function syncProjectFiles(
|
|
98
|
+
export declare function syncProjectFiles(layoutOrTargetDir: ProjectTargetLayout | string, input: ProjectSyncInput): {
|
|
90
99
|
projectDir: string;
|
|
91
100
|
typesFile: string;
|
|
101
|
+
definitionsFile: string;
|
|
92
102
|
metadataFile: string;
|
|
93
103
|
configResult: ProjectConfigResult;
|
|
94
104
|
gitignoreResult: GitignorePatchResult;
|
|
95
105
|
};
|
|
96
106
|
export declare function ensureProjectDebugScaffolding(rootDir: string): void;
|
|
97
|
-
export declare function ensureProjectConfig(rootDir: string): ProjectConfigResult;
|
|
107
|
+
export declare function ensureProjectConfig(rootDir: string, typesInclude?: string): ProjectConfigResult;
|
|
98
108
|
export declare function ensureProjectGitignore(rootDir: string): GitignorePatchResult;
|
package/dist/src/project.js
CHANGED
|
@@ -4,8 +4,8 @@ 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
|
-
export const REVO_VSCODE_CONFIG_FILE = path.join(REVO_PROJECT_DIR, 'vscode.json');
|
|
9
9
|
export const REVO_TYPES_GITIGNORE_ENTRY = '.revoengine/types/';
|
|
10
10
|
export const REVO_DEBUG_JS_FILE = path.join(REVO_PROJECT_DIR, 'debug_code.js');
|
|
11
11
|
export const REVO_DEBUG_TS_FILE = path.join(REVO_PROJECT_DIR, 'debug_code.ts');
|
|
@@ -209,8 +209,29 @@ export function resolveProjectInvocation(args) {
|
|
|
209
209
|
extraArgs: second ? [second, ...rest] : rest,
|
|
210
210
|
};
|
|
211
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
|
+
}
|
|
212
222
|
export function resolveProjectTarget(cwd, targetArg) {
|
|
213
|
-
|
|
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
|
+
};
|
|
214
235
|
}
|
|
215
236
|
export function extractEditorTypesBundle(payload) {
|
|
216
237
|
const root = isRecord(payload) && isRecord(payload.data) ? payload.data : payload;
|
|
@@ -224,6 +245,7 @@ export function extractEditorTypesBundle(payload) {
|
|
|
224
245
|
}
|
|
225
246
|
return {
|
|
226
247
|
code,
|
|
248
|
+
definitions: Array.isArray(root.definitions) ? root.definitions : [],
|
|
227
249
|
endpoint: readString(root.endpoint) || readString(meta?.endpoint),
|
|
228
250
|
apiVersion: readString(root.apiVersion) || readString(meta?.apiVersion),
|
|
229
251
|
libVersion: readString(root.libVersion) || readString(meta?.libVersion),
|
|
@@ -257,6 +279,15 @@ export function buildSandboxDebugUrl(endpoint) {
|
|
|
257
279
|
url.hash = '';
|
|
258
280
|
return url.toString();
|
|
259
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
|
+
}
|
|
260
291
|
export function extractSandboxEndpoint(profile) {
|
|
261
292
|
if (!profile || typeof profile !== 'object') {
|
|
262
293
|
return null;
|
|
@@ -337,14 +368,6 @@ export function resolveProjectRoot(startDir = process.cwd()) {
|
|
|
337
368
|
return metadataFile ? path.dirname(path.dirname(metadataFile)) : '';
|
|
338
369
|
}
|
|
339
370
|
function readWorkspaceDirectoryFromProjectConfig(projectRoot, metadata) {
|
|
340
|
-
const vscodeConfigPath = path.join(projectRoot, REVO_VSCODE_CONFIG_FILE);
|
|
341
|
-
if (fs.existsSync(vscodeConfigPath)) {
|
|
342
|
-
const config = parseConfigFile(vscodeConfigPath);
|
|
343
|
-
const workspaceDirectory = readString(config.workspaceDirectory);
|
|
344
|
-
if (workspaceDirectory) {
|
|
345
|
-
return workspaceDirectory;
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
371
|
return readString(metadata.workspace) || '';
|
|
349
372
|
}
|
|
350
373
|
function resolveProjectWorkspaceRoot(projectRoot, workspaceDirectory) {
|
|
@@ -377,20 +400,38 @@ export function buildProjectSyncState(input) {
|
|
|
377
400
|
return {
|
|
378
401
|
endpoint: metadata.endpoint,
|
|
379
402
|
code: input.bundle.code,
|
|
403
|
+
definitions: input.bundle.definitions ?? [],
|
|
380
404
|
apiVersion: metadata.apiVersion,
|
|
381
405
|
libVersion: metadata.libVersion,
|
|
382
406
|
hash: metadata.hash,
|
|
383
407
|
lastSyncAt: metadata.lastSyncAt,
|
|
384
408
|
};
|
|
385
409
|
}
|
|
386
|
-
|
|
387
|
-
const
|
|
388
|
-
|
|
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);
|
|
389
425
|
fs.mkdirSync(projectDir, { recursive: true });
|
|
390
426
|
fs.mkdirSync(typesDir, { recursive: true });
|
|
391
|
-
const typesFile = path.join(
|
|
392
|
-
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);
|
|
393
430
|
fs.writeFileSync(typesFile, input.code);
|
|
431
|
+
writeJsonFile(definitionsFile, {
|
|
432
|
+
schemaVersion: 1,
|
|
433
|
+
definitions: input.definitions,
|
|
434
|
+
});
|
|
394
435
|
writeJsonFile(metadataFile, {
|
|
395
436
|
schemaVersion: 1,
|
|
396
437
|
endpoint: input.endpoint,
|
|
@@ -399,13 +440,15 @@ export function syncProjectFiles(targetDir, input) {
|
|
|
399
440
|
libVersion: input.libVersion,
|
|
400
441
|
hash: input.hash,
|
|
401
442
|
lastSyncAt: input.lastSyncAt,
|
|
443
|
+
...(workspaceDirectory !== '.' ? { workspace: workspaceDirectory } : {}),
|
|
402
444
|
});
|
|
403
|
-
const configResult = ensureProjectConfig(
|
|
404
|
-
const gitignoreResult = ensureProjectGitignore(
|
|
405
|
-
ensureProjectDebugScaffolding(
|
|
445
|
+
const configResult = ensureProjectConfig(workspaceRoot, toPosixRelative(workspaceRoot, typesFile));
|
|
446
|
+
const gitignoreResult = ensureProjectGitignore(projectRoot);
|
|
447
|
+
ensureProjectDebugScaffolding(projectRoot);
|
|
406
448
|
return {
|
|
407
449
|
projectDir,
|
|
408
450
|
typesFile,
|
|
451
|
+
definitionsFile,
|
|
409
452
|
metadataFile,
|
|
410
453
|
configResult,
|
|
411
454
|
gitignoreResult,
|
|
@@ -431,7 +474,7 @@ export function ensureProjectDebugScaffolding(rootDir) {
|
|
|
431
474
|
fs.writeFileSync(filePath, contents);
|
|
432
475
|
}
|
|
433
476
|
}
|
|
434
|
-
export function ensureProjectConfig(rootDir) {
|
|
477
|
+
export function ensureProjectConfig(rootDir, typesInclude = REVO_TYPES_FILE) {
|
|
435
478
|
const tsconfigPath = path.join(rootDir, 'tsconfig.json');
|
|
436
479
|
const jsconfigPath = path.join(rootDir, 'jsconfig.json');
|
|
437
480
|
if (!fs.existsSync(tsconfigPath) && !fs.existsSync(jsconfigPath)) {
|
|
@@ -442,7 +485,10 @@ export function ensureProjectConfig(rootDir) {
|
|
|
442
485
|
noEmit: true,
|
|
443
486
|
skipLibCheck: true,
|
|
444
487
|
},
|
|
445
|
-
include:
|
|
488
|
+
include: [
|
|
489
|
+
...DEFAULT_PROJECT_INCLUDE.filter((entry) => entry !== REVO_TYPES_FILE),
|
|
490
|
+
typesInclude,
|
|
491
|
+
],
|
|
446
492
|
exclude: DEFAULT_PROJECT_EXCLUDE,
|
|
447
493
|
});
|
|
448
494
|
return {
|
|
@@ -465,13 +511,16 @@ export function ensureProjectConfig(rootDir) {
|
|
|
465
511
|
const files = ensureStringArray(parsed.files, 'files', filePath);
|
|
466
512
|
const include = ensureStringArray(parsed.include, 'include', filePath);
|
|
467
513
|
if (files) {
|
|
468
|
-
parsed.files = appendUnique(files,
|
|
514
|
+
parsed.files = appendUnique(files, typesInclude);
|
|
469
515
|
}
|
|
470
516
|
else if (include) {
|
|
471
|
-
parsed.include = appendUnique(include,
|
|
517
|
+
parsed.include = appendUnique(include, typesInclude);
|
|
472
518
|
}
|
|
473
519
|
else {
|
|
474
|
-
parsed.include =
|
|
520
|
+
parsed.include = [
|
|
521
|
+
...DEFAULT_PROJECT_INCLUDE.filter((entry) => entry !== REVO_TYPES_FILE),
|
|
522
|
+
typesInclude,
|
|
523
|
+
];
|
|
475
524
|
}
|
|
476
525
|
writeJsonFile(filePath, parsed);
|
|
477
526
|
return {
|
package/dist/src/ui.js
CHANGED
|
@@ -104,6 +104,7 @@ function renderCommandCatalog() {
|
|
|
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 update [path]', 'Refresh editor types and project metadata'),
|
|
107
108
|
commandRow('revo endpoints', 'List available API endpoints'),
|
|
108
109
|
'',
|
|
109
110
|
paintHeader('Components'),
|
|
@@ -111,7 +112,7 @@ function renderCommandCatalog() {
|
|
|
111
112
|
commandRow('revo component pull --all [--force] [--stale]', 'Pull every available component with confirmation'),
|
|
112
113
|
commandRow('revo component push <componentId...>', 'Push one or more local components'),
|
|
113
114
|
commandRow('revo component push --all [--force]', 'Push every local component.json with confirmation'),
|
|
114
|
-
commandRow('revo component debug <componentId> [BODY]', 'Debug one local component against sandbox'),
|
|
115
|
+
commandRow('revo component debug <componentId> [BODY] [--stream]', 'Debug one local component against sandbox'),
|
|
115
116
|
'',
|
|
116
117
|
paintHeader('Low-Level'),
|
|
117
118
|
commandRow('revo search <CODE|SIMPLE> <term>', 'Search code references or all platform content'),
|