@revoengine/cli 1.0.4 → 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 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 type bundle stays local by default. Pass a path such as `./backend` when the RevoEngine workspace is nested.
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
 
@@ -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:
@@ -145,6 +146,9 @@ Bulk sync behavior:
145
146
  - Push treats backend `Not modified` responses as skipped instead of failing the whole run.
146
147
  - Push treats backend `404` responses as skipped with `doesn't exist remotely`; restore the component in RevoEngine before pushing local changes to it.
147
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.
148
152
  - Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
149
153
  - Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
150
154
 
@@ -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>;
@@ -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 type = normalizeComponentType(component);
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 response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), {
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
- context.print(response);
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, targetDir, result, prefix) {
4
- println(`${prefix} ${targetDir}`);
5
- println(`Wrote ${path.relative(targetDir, result.typesFile)}`);
6
- println(`Wrote ${path.relative(targetDir, result.metadataFile)}`);
7
- println(`${result.configResult.action === 'created' ? 'Created' : 'Patched'} ${path.basename(result.configResult.filePath)}`);
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 targetDir = resolveProjectTarget(cwd, invocation.targetArg);
40
+ const layout = resolveProjectTarget(cwd, invocation.targetArg);
37
41
  const syncInput = await resolveProjectSyncInput(context);
38
- const result = syncProjectFiles(targetDir, syncInput);
39
- printSyncSummary(println, targetDir, result, invocation.action === 'update' ? 'Updated Revo project in' : 'Initialized Revo project in');
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
  }
@@ -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): 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(targetDir: string, input: ProjectSyncInput): {
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;
@@ -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
- return path.resolve(cwd, targetArg || '.');
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
- export function syncProjectFiles(targetDir, input) {
378
- const projectDir = path.join(targetDir, REVO_PROJECT_DIR);
379
- const typesDir = path.join(targetDir, REVO_TYPES_DIR);
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(targetDir, REVO_TYPES_FILE);
383
- const metadataFile = path.join(targetDir, REVO_METADATA_FILE);
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(targetDir);
395
- const gitignoreResult = ensureProjectGitignore(targetDir);
396
- ensureProjectDebugScaffolding(targetDir);
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: DEFAULT_PROJECT_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, REVO_TYPES_FILE);
514
+ parsed.files = appendUnique(files, typesInclude);
460
515
  }
461
516
  else if (include) {
462
- parsed.include = appendUnique(include, REVO_TYPES_FILE);
517
+ parsed.include = appendUnique(include, typesInclude);
463
518
  }
464
519
  else {
465
- parsed.include = DEFAULT_PROJECT_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
@@ -112,7 +112,7 @@ function renderCommandCatalog() {
112
112
  commandRow('revo component pull --all [--force] [--stale]', 'Pull every available component with confirmation'),
113
113
  commandRow('revo component push <componentId...>', 'Push one or more local components'),
114
114
  commandRow('revo component push --all [--force]', 'Push every local component.json with confirmation'),
115
- 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'),
116
116
  '',
117
117
  paintHeader('Low-Level'),
118
118
  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.4",
3
+ "version": "1.0.5",
4
4
  "description": "CLI package for the RevoEngine Platform API",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",