@xdelivered/emberflow 0.4.0 → 0.5.0

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.
Files changed (37) hide show
  1. package/dist/server/index.js +23 -2
  2. package/dist/server/nodesPayload.d.ts +14 -1
  3. package/dist/server/nodesPayload.js +15 -2
  4. package/dist/server/projectMode.js +5 -2
  5. package/dist/server/sourceNav.d.ts +77 -0
  6. package/dist/server/sourceNav.js +503 -0
  7. package/dist/src/engine/registry.d.ts +27 -2
  8. package/dist/src/engine/registry.js +84 -2
  9. package/dist/src/nodes/index.d.ts +8 -2
  10. package/dist/src/nodes/index.js +7 -3
  11. package/dist/src/nodes/login.d.ts +3 -1
  12. package/dist/src/nodes/login.js +2 -2
  13. package/package.json +2 -2
  14. package/server/index.ts +24 -2
  15. package/server/nodesPayload.test.ts +38 -0
  16. package/server/nodesPayload.ts +28 -3
  17. package/server/projectMode.ts +5 -2
  18. package/server/sourceNav.test.ts +382 -0
  19. package/server/sourceNav.ts +644 -0
  20. package/server/sourceNavRoute.test.ts +163 -0
  21. package/src/components/Inspector.tsx +9 -26
  22. package/src/components/SourceNavigator.test.tsx +226 -0
  23. package/src/components/SourceNavigator.tsx +520 -0
  24. package/src/engine/registry.sourceRef.test.ts +112 -0
  25. package/src/engine/registry.ts +103 -2
  26. package/src/nodes/index.ts +10 -3
  27. package/src/nodes/login.ts +5 -2
  28. package/src/store/builderStore.ts +2 -2
  29. package/src/store/nodeMeta.ts +10 -2
  30. package/src/store/sourceNavClient.ts +48 -0
  31. package/studio-dist/assets/{index-CGwEx82J.js → index-BbzppDpt.js} +25 -25
  32. package/studio-dist/assets/{index-CAIDjNhv.css → index-CLf6blcA.css} +1 -1
  33. package/studio-dist/index.html +2 -2
  34. package/templates/skills/emberflow-basics/SKILL.md +29 -1
  35. package/templates/skills/emberflow-model-process/SKILL.md +11 -1
  36. package/templates/skills/emberflow-new-workflow/SKILL.md +8 -1
  37. package/templates/skills/emberflow-review-workflow/SKILL.md +28 -1
@@ -16,6 +16,7 @@ import { loadInfrastructure } from './infrastructure.js';
16
16
  import { configPathFor, loadProjectConfig } from './projectConfig.js';
17
17
  import { buildApiStore, buildRegistries, requireProjectWhenExplicit } from './projectMode.js';
18
18
  import { nodesPayload } from './nodesPayload.js';
19
+ import { getSourceFile } from './sourceNav.js';
19
20
  import { openBrowser } from './openBrowser.js';
20
21
  import { AgentRunManager, AgentStartError } from './agents/runManager.js';
21
22
  import { isGitRepo } from './agents/gitScope.js';
@@ -127,7 +128,7 @@ if (!project) {
127
128
  apiStore.save(flow, flow.id.includes('/') ? flow.id : `default/${flow.id}`);
128
129
  }
129
130
  }
130
- const agentRuns = new AgentRunManager(project ? project.root : projectDir, apiStore.dir, apiStore.pathOf.bind(apiStore), () => nodesPayload(validationRegistry).nodes.map(({ type, label, description }) => ({ type, label, description })), project?.language ?? 'typescript',
131
+ const agentRuns = new AgentRunManager(project ? project.root : projectDir, apiStore.dir, apiStore.pathOf.bind(apiStore), () => nodesPayload(validationRegistry, project ? project.root : process.cwd()).nodes.map(({ type, label, description }) => ({ type, label, description })), project?.language ?? 'typescript',
131
132
  // Fresh per run: re-read emberflow/infrastructure.json so a scout that ran
132
133
  // earlier this session primes later prompts. Malformed → null (agent guesses).
133
134
  () => loadInfrastructure(project ? project.root : projectDir),
@@ -212,7 +213,27 @@ api.post('/serving', (req, res) => {
212
213
  res.status(204).end();
213
214
  });
214
215
  api.get('/nodes', (_req, res) => {
215
- res.json(nodesPayload(validationRegistry));
216
+ res.json(nodesPayload(validationRegistry, project ? project.root : process.cwd()));
217
+ });
218
+ // Whole-file source view + symbol table for the studio's source navigator.
219
+ // Works in project mode AND no-project mode (root = cwd) — path guards in
220
+ // sourceNav (isPathWithin, node_modules, secret basenames) bound what is
221
+ // servable either way. 400s stay generic: the requested path is never echoed.
222
+ api.get('/source-file', (req, res) => {
223
+ void (async () => {
224
+ const path = req.query.path;
225
+ if (typeof path !== 'string' || path.length === 0) {
226
+ res.status(400).json({ error: 'Query param path is required' });
227
+ return;
228
+ }
229
+ const root = project ? project.root : process.cwd();
230
+ const result = await getSourceFile(root, path);
231
+ if (!result.ok) {
232
+ res.status(result.status).json({ error: result.error });
233
+ return;
234
+ }
235
+ res.json(result.payload);
236
+ })();
216
237
  });
217
238
  // Validate a flow against the runner's LIVE registry (built-ins + project
218
239
  // nodes) so the CLI `validate` command doesn't false-reject ops that use
@@ -5,10 +5,23 @@ import type { NodeDefinition, NodeRegistry } from '../src/engine/index.js';
5
5
  * implementation's toString() so the Inspector can show a node's code without
6
6
  * the browser bundling the implementation — the key to consumer nodes
7
7
  * appearing in the palette without a per-project browser build.
8
+ *
9
+ * `sourceRef` (repo-relative file + line of the register() call) is present
10
+ * only when registration provenance was captured AND the file lives inside
11
+ * the project root — the studio uses it to open the real source via
12
+ * GET /source-file. A captured ref OUTSIDE the root (Emberflow's own
13
+ * built-ins registered from the package) is flagged `builtin: true` instead:
14
+ * those files are never served (node_modules exposure rule), so the
15
+ * Inspector keeps the toString() view for them.
8
16
  */
9
17
  export interface NodeMetaPayload {
10
18
  nodes: Array<NodeDefinition & {
11
19
  source?: string;
20
+ sourceRef?: {
21
+ file: string;
22
+ line?: number;
23
+ };
24
+ builtin?: boolean;
12
25
  }>;
13
26
  }
14
- export declare function nodesPayload(registry: NodeRegistry): NodeMetaPayload;
27
+ export declare function nodesPayload(registry: NodeRegistry, projectRoot?: string): NodeMetaPayload;
@@ -1,4 +1,6 @@
1
- export function nodesPayload(registry) {
1
+ import { isAbsolute, relative, resolve } from 'node:path';
2
+ export function nodesPayload(registry, projectRoot) {
3
+ const root = resolve(projectRoot ?? process.cwd());
2
4
  return {
3
5
  nodes: registry.list().map((definition) => {
4
6
  let source;
@@ -8,7 +10,18 @@ export function nodesPayload(registry) {
8
10
  catch {
9
11
  source = undefined;
10
12
  }
11
- return { ...definition, source };
13
+ const ref = registry.getSourceRef(definition.type);
14
+ if (!ref)
15
+ return { ...definition, source };
16
+ const rel = relative(root, resolve(root, ref.file));
17
+ const inside = rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
18
+ if (!inside)
19
+ return { ...definition, source, builtin: true };
20
+ return {
21
+ ...definition,
22
+ source,
23
+ sourceRef: ref.line !== undefined ? { file: rel, line: ref.line } : { file: rel },
24
+ };
12
25
  }),
13
26
  };
14
27
  }
@@ -34,8 +34,11 @@ export function buildApiStore(project) {
34
34
  return new ApiStore(apisDir);
35
35
  }
36
36
  export function buildRegistries(project) {
37
- const validation = createDefaultRegistry();
38
- const execution = createDefaultRegistry();
37
+ // Server-side registries capture registration provenance (file:line of each
38
+ // register() call) so the studio can navigate to a node's real source.
39
+ // Browser registries never enable this — see createDefaultRegistry.
40
+ const validation = createDefaultRegistry(undefined, { captureSourceRefs: true });
41
+ const execution = createDefaultRegistry(undefined, { captureSourceRefs: true });
39
42
  if (project?.registerNodes) {
40
43
  project.registerNodes(validation);
41
44
  project.registerNodes(execution);
@@ -0,0 +1,77 @@
1
+ import type * as TS from 'typescript';
2
+ /**
3
+ * Source navigation for the studio's node-implementation view: serve a whole
4
+ * project file plus a flat symbol table (top-level declarations, imports with
5
+ * resolutions, re-exports) so the client can render identifiers as links and
6
+ * navigate file-by-file. One flat parse per file, mtime-cached; recursion
7
+ * happens client-side by fetching the next file — except re-export chains,
8
+ * which the server follows (bounded, cycle-safe) so an import through an
9
+ * index module resolves straight to the declaring file.
10
+ *
11
+ * TypeScript is loaded lazily on first use (`await import('typescript')`);
12
+ * when that fails the file is still served with `resolver: 'unavailable'`
13
+ * and empty symbols, and the studio shows unresolved-with-reason.
14
+ */
15
+ export type Resolution = {
16
+ kind: 'project';
17
+ path: string;
18
+ line?: number;
19
+ } | {
20
+ kind: 'external';
21
+ package: string;
22
+ } | {
23
+ kind: 'builtin';
24
+ } | {
25
+ kind: 'unresolved';
26
+ reason: string;
27
+ };
28
+ export interface SourceDeclaration {
29
+ name: string;
30
+ kind: 'fn' | 'const' | 'class' | 'other';
31
+ line: number;
32
+ endLine: number;
33
+ exported: boolean;
34
+ }
35
+ export interface SourceImport {
36
+ /** Imported name in the source module ('default', '*' for namespace, 'import()' for dynamic). */
37
+ name: string;
38
+ /** Local binding name in the served file ('' when there is none). */
39
+ local: string;
40
+ /** The specifier as written. */
41
+ from: string;
42
+ resolution: Resolution;
43
+ }
44
+ export interface SourceReexport {
45
+ name: string;
46
+ from: string;
47
+ resolution: Resolution;
48
+ }
49
+ export interface SourceFilePayload {
50
+ path: string;
51
+ content: string;
52
+ language: 'ts' | 'js';
53
+ /** Present (as 'unavailable') only when the typescript module could not be loaded. */
54
+ resolver?: 'unavailable';
55
+ symbols: {
56
+ declarations: SourceDeclaration[];
57
+ imports: SourceImport[];
58
+ reexports: SourceReexport[];
59
+ };
60
+ }
61
+ export type SourceFileResult = {
62
+ ok: true;
63
+ payload: SourceFilePayload;
64
+ } | {
65
+ ok: false;
66
+ status: 400 | 404;
67
+ error: string;
68
+ };
69
+ export interface SourceNavOptions {
70
+ /** Test seam: overrides the lazy `import('typescript')` for this call only. */
71
+ tsLoader?: () => Promise<TsModule>;
72
+ }
73
+ type TsModule = typeof TS;
74
+ /** Clears all sourceNav caches (parse, tsconfig, ts module) — tests only. */
75
+ export declare function resetSourceNavCaches(): void;
76
+ export declare function getSourceFile(projectRoot: string, relPath: string, opts?: SourceNavOptions): Promise<SourceFileResult>;
77
+ export {};