Line

You are reading the unreleased docs (next). See latest (v0.9)

ui/server/src/pagesEditor

pagesEditor.mjs's implementation is pure logic (JSX tree edits, Palette insertion, enforcement checks) with no Cockpit-server dependency beyond a path-containment helper, so it now lives in packages/engine where const...

Cockpit server, next. Source: ui/server/src/pagesEditor.mjs.

Classes#

PagesEditorError#

Defined in: packages/engine/pagesEditor.mjs:40

An HTTP-facing error from one of the Pages Editor's own operations (the scope guard, the content-hash guard, a missing node/file, an enforcement rejection, ...): carries the response status to send, an optional violations list and an optional machine-readable code (e.g. HASH_REQUIRED, CHANGED_ON_DISK) callers branch on.

Extends#

  • Error

Constructors#

Constructor

new PagesEditorError(message, options?): PagesEditorError

Defined in: packages/engine/pagesEditor.mjs:47

Parameters
message

string

Human-readable error message.

options?

status defaults to 400; violations is the enforcement violation list when this rejects a save; code is a short machine-readable reason (e.g. HASH_REQUIRED, CHANGED_ON_DISK) for client branching.

code?

string

status?

number = 400

violations?

any[]

Returns

PagesEditorError

Overrides

Error.constructor

Properties#

code

code: string

Defined in: packages/engine/pagesEditor.mjs:51

status

status: number

Defined in: packages/engine/pagesEditor.mjs:49

violations

violations: any[]

Defined in: packages/engine/pagesEditor.mjs:50

Functions#

addChildInSnippet()#

addChildInSnippet(snippetSource, parentId): { ok: true; snippet: string; } | { error: string; ok: false; }

Defined in: packages/engine/pagesEditor.mjs:566

Ticket F.3 — append a new (fixed, deliberately minimal — <div />) child right before a node's closing tag, reusing the start/end offsets parsePageTree already computed (no second AST walk). Only supported when the node already has an opening/closing tag pair or is a fragment (<Tag>...</Tag> / <>...</>) — a self-closing element (<Tag />) is rejected rather than the tool guessing how to split /> into an open/close pair on the caller's behalf. Never throws.

Parameters#

snippetSource

string

The snippet's own current source text.

parentId

string

Id of the node to append a new child into.

Returns#

{ ok: true; snippet: string; } | { error: string; ok: false; }

The snippet with a new <div /> child appended, or why it couldn't be added.


applyAutoMap()#

applyAutoMap(source, nodeId, propNames): string

Defined in: packages/engine/pagesEditor.mjs:774

's write side: apply a batch of accepted auto-map suggestions to one node, wiring each propName as the shorthand {propName} identifier attribute, one patchNode splice at a time (the content hash is re-taken after each patch so the next splice targets the just-patched text).

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the node to add the props to.

propNames

string[]

The in-scope names to wire as same-named {name} attributes.

Returns#

string

The patched full page source with every prop applied.


applyWrapConfirm()#

applyWrapConfirm(root, absPath, source, nodeId, name?): object

Defined in: packages/engine/pagesEditor.mjs:1000

-- the real write, only ever reached from "Approve": re-derives the same flagged hit from nodeId (never trusting a client-sent range) and calls the real, non-dry-run extractExpression(). Touches multiple files (the page, a new Expression, and optionally one or more new Components) -- the caller (the server route) commits/records all of them as one save.

Parameters#

root

string

Project root.

absPath

string

Absolute path of the page/component file being wrapped.

source

string

That file's full current source text.

nodeId

string

Id of the selected node the flagged hit is re-derived from.

name?

string

Caller-supplied Expression name override.

Returns#

object

The written files, as extractExpression's own non-dry-run result reports them.

component

component: object

component.file

file: string

component.name

name: string

components

components: object[]

expression

expression: object

expression.file

file: string

expression.name

name: string

page

page: object

page.file

file: string

Throws#

When nothing is flagged (PAGE-008) at the selection.

Throws#

Any usage error extractExpression itself throws.


assertContentHash()#

assertContentHash(source, contentHash, options?): void

Defined in: packages/engine/pagesEditor.mjs:179

— the one hash guard every Pages-editor write that lands on disk goes through, the same contract componentSave (componentsApi.mjs) already enforces for the Components screen: a hash that's missing, empty or not a string is a distinct 400 HASH_REQUIRED (the client never fetched, or forgot to resend, the hash it loaded the file against — a client bug, never routine) from a present-but-wrong hash's 409 CHANGED_ON_DISK (someone else's edit landed on disk first — routine, "reload and try again"). Never optional: a save endpoint that skips this on a falsy hash is exactly 's bug (a client that omits the hash silently wins over whatever is on disk).

Parameters#

source

string

Current on-disk file content to hash and compare against.

contentHash

string

The hash the caller loaded the file against (from a prior read).

options?

mismatchMessage overrides the 409 message used when a present hash doesn't match the current content.

mismatchMessage?

string = 'The file changed on disk since this was loaded — reload the tree and try again.'

Returns#

void

Throws#

400 HASH_REQUIRED when contentHash is missing/empty; 409 CHANGED_ON_DISK when it doesn't match source's current hash.


buildAttributeSnippet()#

buildAttributeSnippet(source, nodeId, propName, kind, value, index?): string

Defined in: packages/engine/pagesEditor.mjs:385

Update a single JSXAttribute's value on a node by textually replacing just that attribute (or appending a new one if it doesn't exist yet) inside the node's opening tag, then delegating to the same splice-based patchNode used by so both go through one mechanism (and one enforcement gate).

follow-up to — a kind === 'spread' edit is a different shape ({...expr}, no attribute name, no =) and has no name to look it up by, so it takes its own branch keyed on index (its position in the opening tag's attribute list, from the prop record's index field) instead of going through the name lookup below. Only edits an existing spread — this doesn't support inserting a brand new one.

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the node whose opening tag is edited.

propName

string

The attribute's name, or null for a kind === 'spread' edit.

kind

"string" | "number" | "boolean" | "spread" | "identifier" | "expression"

The prop's value kind, as recorded on the node's props entries; 'spread' takes the index branch below, every other kind is passed straight through to setAttributeText.

value

string | number | boolean

The new attribute value (rendered by setAttributeText/setSpreadText).

index?

number

Position of the spread among the tag's attributes (only for kind === 'spread').

Returns#

string

The node's whole new opening-tag-through-node text, ready for patchNode to splice in.

Throws#

409 when nodeId/the targeted spread no longer exists, or the node is a fragment.


buildComponentUsageJsx()#

buildComponentUsageJsx(name, described): string

Defined in: packages/engine/pagesEditor.mjs:828

A self-closing JSX usage for a Component entry, its required props stubbed from described (describeComponent's real react-docgen result) -- optional props are left out entirely rather than guessed. described may be null/failed (a component that couldn't be documented): the usage then has no props at all, same as any component with none.

Parameters#

name

string

The component's exported name.

described

any

describeComponent's result for the file it's exported from, or null.

Returns#

string

A self-closing JSX usage, e.g. <Foo bar="" /> or <Foo />.


buildPaletteInsertion()#

buildPaletteInsertion(root, pageAbsPath, source, entry, describe?): Promise<{ ok: true; source: string; } | { error: string; ok: false; }>

Defined in: packages/engine/pagesEditor.mjs:880

(Slice 2 of 's design) -- insert one Palette entry's real usage into a page: the import (skipped if already present; rejected if the name is already imported from somewhere ELSE) plus either a self-closing JSX usage (Component, appended as the last child of the page's own root JSX element/fragment) or a hook-call statement (Provider, inserted as the first statement of that root's enclosing component function). No selection UI needed (block-palette.md section 4's Slice 2 note): unlike "Wrap with..." (Slice 3, ), a brand-new usage doesn't need to target existing JSX -- there is exactly one sensible place to add it. entry must be one of buildPalette's own providers/components entries (the caller re-derives it from a fresh buildPalette call rather than trusting whatever the client sent, the same "never resolve a client-named path directly" discipline componentsApi.mjs's pick() uses) plus a kind.

Parameters#

root

string

Project root.

pageAbsPath

string

Absolute path of the page file to insert into.

source

string

The page file's full current source text.

entry

The Palette entry to insert (one of buildPalette's own components/providers entries).

feature?

string

kind

"provider" | "component"

name

string

path?

string

via?

string

describe?

Function = describeComponent

describeComponent, injectable for tests.

Returns#

Promise<{ ok: true; source: string; } | { error: string; ok: false; }>

The page source with the usage inserted, or why it was rejected.


buildProviderUsageStatement()#

buildProviderUsageStatement(name): string

Defined in: packages/engine/pagesEditor.mjs:843

A Provider hook-call statement for a Provider entry. Assigns the WHOLE result to a local variable rather than destructuring named fields: useProvider()'s return shape (Value) isn't statically known here (no type-checker run for this), so guessing field names would risk inserting code that doesn't compile -- calling the hook and letting the developer destructure by hand is the honest "real, working reference" this slice promises, not a fabricated shape.

Parameters#

name

string

The Provider hook's exported name (e.g. useCartProvider).

Returns#

string

A statement calling it, e.g. const cart = useCartProvider();.


buildWrapSuggestion()#

buildWrapSuggestion(root, absPath, source, nodeId, palette, name?, options?): object

Defined in: packages/engine/pagesEditor.mjs:958

(Slice 3 of 's design, docs/design/block-palette.md section 3) -- everything the Palette tab's "Wrap with..." flow needs for one JSX selection: which flagged PAGE-008 conditional/loop (if any) encloses it, every Expression in scope annotated with its structural fit (packages/engine/palette.mjs's buildWrapSuggestions), the name that would be used (derived, or name once given), and -- only when includeFiles is true -- a real dry-run preview of exactly what construct refactor extract-expression (packages/core/extractExpression.mjs, ) would write. dryRun: true throughout: nothing here ever touches disk. includeFiles defaults to false so the automatic "you selected something" suggestion (fired the moment a selection lands, per design section 3 step 1-2) never itself renders a diff -- only the explicit "Wrap with..." click (design section 3 step 4) asks for one, keeping "Suggest" and "Confirm" two distinct, separately-observable steps exactly as the design's own step numbering has them.

Parameters#

root

string

Project root.

absPath

string

Absolute path of the page/component file the selection is in.

source

string

That file's full current source text.

nodeId

string

Id of the selected node.

palette

A fresh buildPalette(...) result for this feature (only its expressions entries are used here).

expressions

any[]

name?

string

Caller-supplied Expression name override.

options?

includeFiles also computes the real dry-run diff preview.

includeFiles?

boolean = false

Returns#

object

files

files: object[]

hit

hit: any

name

name: string

nameError

nameError: string

nameRequired

nameRequired: boolean

ok

ok: true

suggestions

suggestions: any[]


checkEnforcement()#

checkEnforcement(root, relPath, patchedSource): object

Defined in: packages/engine/pagesEditor.mjs:1023

— run the existing PAGE-* / COMPONENT-* architecture rules and the separation-of-concerns rules against a prospective file, scoped to just that one file, and surface any error-severity violation before it lands (the caller writes patched to disk only if this returns ok: true, and reverts otherwise — see server route below).

Parameters#

root

string

Project root.

relPath

string

The file's path relative to root (as classified by the layer graph).

patchedSource

string

The prospective new content to check, written to disk temporarily (and restored in a finally, whether this returns or throws).

Returns#

object

Every architecture/SoC violation attributed to this file, errors filtered to error-severity ones, and whether any exist.

errors

errors: any[]

ok

ok: boolean

violations

violations: any[]


collectComponentScopeNames()#

collectComponentScopeNames(ast): string[]

Defined in: packages/ast/jsxScope.mjs:52

In-scope names of a page: every function's destructured/plain parameter names plus every const [value, setValue] = useState(...) binding, in source order, de-duplicated.

Parameters#

ast

any

A parsed Program.

Returns#

string[]

The in-scope names, in source order, de-duplicated.


featuresRootOf()#

featuresRootOf(root): string

Defined in: packages/engine/pagesEditor.mjs:61

The configured features-root directory name for a project (e.g. "features"), from construct.config's features.root, defaulting to "features" when unset.

Parameters#

root

string

Project root.

Returns#

string

The features-root directory name, relative to root.


findUnmappedProps()#

findUnmappedProps(source, nodeId, root?, pageAbsPath?): object

Defined in: packages/engine/pagesEditor.mjs:675

— for a selected custom-component node, find scope names (parent page's own props/state) that are NOT currently passed down to it as a same-named attribute, and propose the shorthand {name} wiring for each. follow-up: when the child component's own declared props can be resolved across files (see resolveDeclaredPropNames above), candidates are further filtered to names the child actually declares — e.g. a state setter like setCount is no longer offered for a child that doesn't destructure a same-named prop. root/pageAbsPath are optional so in-memory single-string callers (existing tests) keep working; omit them to keep the old, fully permissive behavior.

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the selected custom-component node.

root?

string

Project root, so the child's declared props can be resolved across files.

pageAbsPath?

string

Absolute path of source's own file (paired with root).

Returns#

object

Unmapped in-scope names to offer, and whether they were filtered to the child's own declared props.

candidates

candidates: string[]

childPropsResolved

childPropsResolved: boolean

nodeId

nodeId: string

Throws#

409 when nodeId doesn't exist in source.


getNodeProps()#

getNodeProps(source, nodeId): object

Defined in: packages/engine/pagesEditor.mjs:354

read-only helper: same shape as a tree node's props, for a single node, plus the enclosing page component's own in-scope prop/state names (used by 's auto-mapper).

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the node to read.

Returns#

object

The node's tag/props plus the enclosing component's in-scope prop/state names.

nodeId

nodeId: string

props

props: any[]

scopeNames

scopeNames: string[]

tag

tag: string

Throws#

409 when nodeId doesn't exist in source.


getNodeSnippet()#

getNodeSnippet(source, nodeId): object

Defined in: packages/engine/pagesEditor.mjs:282

The exact source snippet for one node plus its parent chain's tag names for breadcrumb display.

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the node to read (from a prior parsePageTree/serializeTree call).

Returns#

object

The node's own source text and source's content hash.

contentHash

contentHash: string

nodeId

nodeId: string

snippet

snippet: string

Throws#

409 when nodeId doesn't exist in source.


getScopeLinks(source, nodeId, root?, pageAbsPath?): any

Defined in: packages/engine/pagesEditor.mjs:745

Scope/binding link graph for one element: thin glue over the core buildScopeLinks block -- the path-scoped cross-file lookup of the child component's source (bare/package imports and anything outside root resolve to null, leaving childProps unknown) plus, independently of which element is selected, every reachable Provider hook's own source so the 'provider' scope kind is populated for real Cockpit pages.

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the selected node.

root?

string

Project root, so child/provider sources can be resolved across files.

pageAbsPath?

string

Absolute path of source's own file (paired with root).

Returns#

any

The scope/binding link graph (see buildScopeLinks).

Throws#

409 when nodeId doesn't exist in source.


hashOf()#

hashOf(source): string

Defined in: packages/engine/pagesEditor.mjs:241

The sha256 content hash of a file's source text, used as the Pages Editor's optimistic-concurrency token: the client resends the hash it last loaded, and every mutating endpoint rejects a save whose hash no longer matches the current on-disk content.

Parameters#

source

string

File content to hash.

Returns#

string

The hex-encoded sha256 digest.


listAllPages()#

listAllPages(root): object[]

Defined in: packages/engine/pagesEditor.mjs:104

Every page of the project, [{ feature, file }]: what the Browser lists on the Pages screen. Only names the client may later send back to the per-feature routes, each of which re-checks it with resolvePageFile.

Parameters#

root

string

Project root.

Returns#

object[]

Every page, across every feature.


listFeatures()#

listFeatures(root): string[]

Defined in: packages/engine/pagesEditor.mjs:70

Every feature directory under the configured features root.

Parameters#

root

string

Project root.

Returns#

string[]

Feature directory names, sorted; [] when the features root doesn't exist.


listPages()#

listPages(root, feature): string[]

Defined in: packages/engine/pagesEditor.mjs:88

Every file under features//pages/**, relative to that pages/ folder (e.g. "Home.jsx", "billing/Detail.tsx").

Parameters#

root

string

Project root.

feature

string

Feature directory name.

Returns#

string[]

Page file paths relative to that feature's pages/ folder, sorted; [] when the feature has no pages/ folder.


moveNodeInSnippet()#

moveNodeInSnippet(snippetSource, nodeId, direction): { ok: true; snippet: string; } | { error: string; ok: false; }

Defined in: packages/engine/pagesEditor.mjs:528

Ticket F.3 — move a node one position up/down among its own siblings, expressed as swapping its text with the adjacent sibling's (whatever sits between the two — other text/elements — stays exactly where it is; only the two elements' own text spans trade places). Rejects a root (no siblings to move among) or a node already at the first/last position for the requested direction. Never throws.

Parameters#

snippetSource

string

The snippet's own current source text.

nodeId

string

Id of the node to move.

direction

"up" | "down"

Which way to move it among its siblings.

Returns#

{ ok: true; snippet: string; } | { error: string; ok: false; }

The snippet with the node moved, or why the move was rejected.


parsePageTree()#

parsePageTree(source): object

Defined in: packages/engine/pagesEditor.mjs:204

Parse a page file's JSX into a navigable, serializable element tree. Node ids are assigned in source (document) order during one traversal — n0, n1, ... — which is what every later save/props/auto-map lookup uses to re-find "the same" node on a fresh re-parse. Ids are only valid for the exact source text they were computed from, which is why every mutating endpoint re-parses fresh and includes contentHash (sha256 of the file's current text) so a stale client can't silently patch the wrong node after the file changed underneath it.

Parameters#

source

string

Page file's full JSX/TSX source text.

Returns#

object

The navigable node tree.

ast

ast: any

byId

byId: Map<string, any>

roots

roots: any[]


parseSnippetToTree()#

parseSnippetToTree(snippetSource): object

Defined in: packages/engine/pagesEditor.mjs:264

Ticket F.1 — parse a bare snippet's own text (not a whole page file) into the same node tree shape serializeTree produces, so the visual composer can derive its graph live from exactly the text currently sitting in the snippet editor, with zero persisted metadata: no .flyde-style side file, no cached JSON layout, just a fresh parse of whatever text is passed in. Reuses parsePageTree/serializeTree completely unchanged — a standalone JSX snippet (e.g. <div><Foo/></div>) is already valid module source on its own (it parses as an ExpressionStatement wrapping the JSXElement/JSXFragment), so no wrapping hack is needed. Parse failures are expected transiently while the user is mid-edit (an unclosed tag, etc.) — reported as {roots: [], error} rather than thrown, so the canvas can just keep showing its last-good graph instead of crashing on every keystroke.

Parameters#

snippetSource

string

The snippet's own current source text (not a whole page file).

Returns#

object

The parsed tree (same shape as serializeTree) plus a null error, or {roots: [], error} when it doesn't currently parse or is blank.

contentHash?

optional contentHash?: string

error

error: string

roots

roots: any[]


patchNode()#

patchNode(source, nodeId, newSnippet, expectedHash): string

Defined in: packages/engine/pagesEditor.mjs:339

Patch one node's JSX back into the file. We re-parse the current disk content, require the caller's contentHash to match it, then delegate the actual splice to spliceNodeText.

Parameters#

source

string

Current disk content of the page file.

nodeId

string

Id of the node to replace.

newSnippet

string

Replacement JSX text for that node (one element or fragment).

expectedHash

string

The content hash the caller loaded source against.

Returns#

string

The patched full page source.

Throws#

400/409 from the hash guard, 409 if nodeId no longer exists, or 400 if newSnippet isn't valid JSX / leaves the file unparseable.


removeAttributeSnippet()#

removeAttributeSnippet(source, nodeId, propName): string

Defined in: packages/engine/pagesEditor.mjs:418

Ticket F.2 — remove one named attribute from a node's opening tag, returning that node's whole replacement text (same "whole node's new text" contract buildAttributeSnippet above already uses, so patchNode splices it back the same way). Also consumes one immediately-preceding whitespace run so the removal doesn't leave a double space behind in the tag. Only for a named (non-spread) attribute that currently exists.

Parameters#

source

string

Page file's full source text.

nodeId

string

Id of the node whose opening tag is edited.

propName

string

Name of the attribute to remove.

Returns#

string

The node's whole new text with that attribute removed.

Throws#

409 when nodeId doesn't exist or the node is a fragment; a plain error when propName isn't currently on the node.


removeNodeInSnippet()#

removeNodeInSnippet(snippetSource, nodeId): { ok: true; snippet: string; } | { error: string; ok: false; }

Defined in: packages/engine/pagesEditor.mjs:497

Ticket F.3 — remove a node (and its whole subtree) from the snippet, expressed as a plain text-range deletion plus a small whitespace cleanup (one run of leading indentation, one trailing newline) so it doesn't leave a blank line behind. Rejects removing the snippet's own single root — a snippet is always exactly one root element/fragment (the same invariant patchNode's own save-time validation enforces), so there is nothing left to save if the root itself were removed. Never throws — {ok:false, error} on any rejection.

Parameters#

snippetSource

string

The snippet's own current source text.

nodeId

string

Id of the node (and subtree) to remove.

Returns#

{ ok: true; snippet: string; } | { error: string; ok: false; }

The snippet with that node removed, or why the removal was rejected.


resolveDeclaredPropNames()#

resolveDeclaredPropNames(root, pageAbsPath, pageAst, tagName): any

Defined in: packages/engine/pagesEditor.mjs:640

follow-up to — resolve the child component actually rendered by JSX tag tagName (via the page file's own import of it) and return its declared prop names, so findUnmappedProps can filter candidates to ones the child can actually use instead of every in-scope name. Returns null when the child can't be resolved (bare/package import, dynamic tag, no matching export) or its param shape isn't recognized — callers treat null as "unknown, don't filter" (today's permissive behavior). The AST work (import lookup, finding the component function, reading its declared props) is src/ast's; this function only does the file resolution, scoped to root.

Parameters#

root

string

Project root.

pageAbsPath

string

Absolute path of the page file tagName is rendered in.

pageAst

any

The page's already-parsed AST (from parsePageTree).

tagName

string

The JSX tag name to resolve (the rendered custom component).

Returns#

any

{closed, names, types} (see declaredPropNames), or null when the child can't be resolved or its prop shape isn't recognized.


resolvePageFile()#

resolvePageFile(root, feature, file): object

Defined in: packages/engine/pagesEditor.mjs:118

Resolve + validate a (feature, file) pair to an absolute path strictly inside that feature's pages/ folder. Throws PagesEditorError on any attempt to escape it — a .. segment, an absolute file path, a symlink-free resolution that lands outside pages/, or a feature/file that simply doesn't exist.

Parameters#

root

string

Project root.

feature

string

Feature directory name.

file

string

Page file path relative to that feature's pages/ folder, as sent by the client.

Returns#

object

The resolved absolute path and its path relative to root.

absPath

absPath: string

relPath

relPath: string


rewireWireInSnippet()#

rewireWireInSnippet(snippetSource, wire): { ok: true; snippet: string; } | { error: string; ok: false; }

Defined in: packages/engine/pagesEditor.mjs:448

Ticket F.2 — the visual composer's wire-rewrite: moves an existing prop from one child to a different sibling under the same parent, expressed as an unambiguous source-text edit (remove the attribute from the old child, add the identical attribute — same kind and value — to the new one), reusing removeAttributeSnippet/ buildAttributeSnippet/patchNode unchanged rather than a second splicing mechanism. Operates purely on the snippet's own text (never a file) so the result can be handed straight to the existing save-back-to-source + diff-preview flow. Never throws — an invalid drag (stale ids, a cross-parent target, a name collision on the target) comes back as {ok: false, error} so the canvas can show a clear inline message and leave the snippet untouched, per 's "reject rather than write broken code" instruction.

Parameters#

snippetSource

string

The snippet's own current source text.

wire

The common parent, the prop being moved, and the sibling ids it moves from/to.

fromChildId

string

parentId

string

propName

string

toChildId

string

Returns#

{ ok: true; snippet: string; } | { error: string; ok: false; }

The rewired snippet text, or why the rewire was rejected.


serializeTree()#

serializeTree(source): object

Defined in: packages/engine/pagesEditor.mjs:216

Lightweight JSON-safe projection of parsePageTree's node records (drops start/end/internal fields the frontend doesn't need, but keeps a content hash so the client can detect the tree going stale).

Parameters#

source

string

Page file's full JSX/TSX source text.

Returns#

object

The stripped-down tree the client renders, plus source's content hash.

contentHash

contentHash: string

roots

roots: any[]