This is an automated email from the ASF dual-hosted git repository.

Harbs pushed a commit to branch codegraph
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git


The following commit(s) were added to refs/heads/codegraph by this push:
     new 15511ad2b feat(codegraph): Implement Code Graph Exporter with JSON 
Schema validation
15511ad2b is described below

commit 15511ad2bbd6b15b0f57d545f50e8b0cd9ab8c67
Author: Harbs <[email protected]>
AuthorDate: Fri Jul 31 16:54:08 2026 +0300

    feat(codegraph): Implement Code Graph Exporter with JSON Schema validation
    
    - Added CODEGRAPH implementation plan with detailed schema compatibility 
policy.
    - Introduced `codegraph.jar` in the build process and created corresponding 
scripts for Unix and Windows.
    - Integrated CODEGRAPH tool into RoyaleToolGroup for seamless usage.
    - Developed CodeGraphExporter and CodeGraphWriter classes to handle graph 
generation and output.
    - Created JSON Schema for code graph validation and added tests to ensure 
compliance.
    - Enhanced TestCODEGRAPH to validate tool registration and output 
correctness.
    - Added Maven plugin for compiling code graphs with configurable output 
options.
    - Implemented configuration files for JS and SWF code graph compilation.
---
 CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md          |  31 ++--
 compiler-jx/build.xml                              |  16 +++
 compiler-jx/pom.xml                                |  12 ++
 compiler-jx/src/assembly/scripts/codegraph         |  62 ++++++++
 compiler-jx/src/assembly/scripts/codegraph.bat     |  24 ++++
 .../apache/royale/compiler/clients/CODEGRAPH.java  |  19 ++-
 .../royale/compiler/clients/RoyaleToolGroup.java   |   1 +
 .../internal/codegen/graph/CodeGraphExporter.java  |  21 ++-
 .../internal/codegen/graph/CodeGraphWriter.java    |   3 +
 .../codegen/graph/codegraph-1.0.schema.json        | 158 +++++++++++++++++++++
 .../royale/compiler/clients/TestCODEGRAPH.java     |  45 +++++-
 .../codegen/graph/TestCodeGraphWriter.java         |  77 ++++++++++
 .../test/resources/codegraph/InvalidCodeGraph.as   |   2 +-
 .../apache/royale/maven/CompileCodeGraphMojo.java  | 156 ++++++++++++++++++++
 .../config/compile-codegraph-js-config.xml         |  90 ++++++++++++
 .../config/compile-codegraph-swf-config.xml        |  91 ++++++++++++
 16 files changed, 786 insertions(+), 22 deletions(-)

diff --git a/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md 
b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
index 7239db7db..ca1aa1f46 100644
--- a/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
+++ b/CODEGRAPH_EXPORTER_IMPLEMENTATION_PLAN.md
@@ -292,19 +292,28 @@ Passing the existing suite is necessary but not 
sufficient: the new client must
 Implemented and covered by focused compiler-backed tests:
 
 - Public classes, interfaces, implicit interface members, package definitions, 
and directly declared public members.
-- Expanded directory-valued include sources and unreferenced included source 
files.
+- Expanded directory-valued include sources and unreferenced included source 
files, with or without a positional target file.
 - Stable IDs, deterministic JSON, source-root-relative provenance, module 
identity, and external library origins.
 - Resolved and explicitly unresolved signature references, with compiler 
diagnostics retained for unresolved types.
 - Base, interface, override, and implementation relationships.
 - Visibility, declaration modifiers, parameter defaults, and variable/constant 
initial values.
 - Parsed ASDoc, `@private` exclusion, structured metadata, metadata ASDoc, and 
type-bearing metadata references.
 - JS/SWF conditional selection, compiler error gating, two-run determinism, 
and an exact golden document.
+- A packaged Draft 2020-12 JSON Schema with validation of unit-built and 
compiler-backed graph documents.
 
-Deferred extensions remain effective inherited-member views, `@copy` 
resolution, JSON Schema/versioning policy, and aggregate build-tool integration.
+`@copy` targets remain structured, opaque tag values, matching Royale's 
existing parser and emitter semantics. Effective inherited-member views remain 
derivable from explicit base/interface edges. Aggregate build-tool integration 
remains deferred.
+
+### Schema Compatibility Policy
+
+- `schemaVersion` uses `major.minor` numbering and identifies the graph 
contract, independently of compiler releases.
+- Additive optional fields and new enum values require a minor version. 
Removing, renaming, changing the meaning of a field, or making an optional 
field required requires a major version.
+- Each supported contract has a packaged, immutable schema named 
`codegraph-<major>.<minor>.schema.json` beside the graph model classes.
+- Writers emit one exact schema version. Consumers must reject unsupported 
major versions and may accept newer minor versions only when they tolerate 
unknown optional fields and enum values.
+- The schema, model version constant, writer output, and canonical golden 
document must change together and pass schema validation.
 
 ## Suggested Pull Request Sequence
 
-### PR 1: Semantic exporter MVP
+### Slice 1: Semantic exporter MVP
 
 - Graph model and deterministic JSON writer.
 - Compiler-backed collection of public types and directly declared members.
@@ -312,22 +321,22 @@ Deferred extensions remain effective inherited-member 
views, `@copy` resolution,
 - JS/SWF fixture tests. Implemented with opposite compiler define sets that 
verify target labels and active-member selection.
 - CLI entry point in `compiler-jx`.
 
-### PR 2: Completeness
+### Slice 2: Completeness
 
 - Package-level definitions. Implemented with focused 
function/variable/constant coverage and a compiler-backed reachable 
package-function fixture.
 - External and unresolved symbol records. Implemented with explicit unresolved 
markers, origins, and compiler-problem coverage.
 - Inheritance/override edges. Implemented for base types, interfaces, 
overridden methods, and interface implementations.
 - Effective inherited public member view if clients require it.
-- `@copy` resolution.
-- JSON Schema and schema compatibility policy.
+- `@copy` is preserved as an opaque structured tag, matching Royale parser and 
emitter behavior.
+- JSON Schema and schema compatibility policy. Implemented with a packaged 
versioned schema and validator-backed tests.
 
-### PR 3: Build-tool integration
+### Slice 3: Build-tool integration
 
-- Add a `compile-codegraph` goal to `royale-maven-plugin`, modeled after 
`CompileASDocMojo`.
-- Add Ant/tool registration and SDK launcher scripts.
-- Ensure both target configurations select the same dependency classifiers as 
`CompileASDocMojo`.
+- `compile-codegraph` goal in `royale-maven-plugin`. Implemented with 
dedicated graph configs and separate JS/SWF outputs.
+- Ant/tool registration and SDK launcher scripts. Implemented with a 
`codegraph.jar` entry point and Unix/Windows launchers.
+- Target-specific dependency classifiers. Implemented to match 
`CompileASDocMojo` without invoking the ASDoc compiler.
 
-### PR 4: `royale-asjs` integration
+### Slice 4: `royale-asjs` integration
 
 This work happens in the sibling repository:
 
diff --git a/compiler-jx/build.xml b/compiler-jx/build.xml
index a1fd8149e..c80a397e2 100644
--- a/compiler-jx/build.xml
+++ b/compiler-jx/build.xml
@@ -55,6 +55,7 @@
        <property name="compc.jar" value="${lib}/compc.jar" />
        <property name="jsc.jar" value="${lib}/jsc.jar" />
     <property name="asdoc.jar" value="${lib}/asdoc.jar" />
+    <property name="codegraph.jar" value="${lib}/codegraph.jar" />
 
     <!-- env.BUILD_NUMBER is set by Jenkins CI -->
     <condition property="build.number" value="nightly-${env.BUILD_NUMBER}">
@@ -166,6 +167,20 @@
             </manifest>
         </jar>
 
+        <echo message="Building ${codegraph.jar}" />
+        <jar file="${codegraph.jar}" basedir="${classes.dir}">
+            <include name="META-INF/LICENSE"/>
+            <include name="META-INF/NOTICE"/>
+            <manifest>
+                <attribute name="Sealed" value="${manifest.sealed}"/>
+                <attribute name="Implementation-Title" 
value="${manifest.Implementation-Title}"/>
+                <attribute name="Implementation-Version" 
value="${manifest.Implementation-Version}.${build.number}"/>
+                <attribute name="Implementation-Vendor" 
value="${manifest.Implementation-Vendor}"/>
+                <attribute name="Main-Class" 
value="org.apache.royale.compiler.clients.CODEGRAPH" />
+                <attribute name="Class-Path" value="jsc.jar" />
+            </manifest>
+        </jar>
+
         <chmod dir="${basedir}/src/assembly" excludes="**/*.bat" perm="+x" />
        </target>
 
@@ -196,6 +211,7 @@
                 <include name="mxmlc.jar" />
                 <include name="jsc.jar" />
                 <include name="asdoc.jar" />
+                <include name="codegraph.jar" />
             </fileset>
                </delete>
     </target>
diff --git a/compiler-jx/pom.xml b/compiler-jx/pom.xml
index f084a58f4..fab8a5656 100644
--- a/compiler-jx/pom.xml
+++ b/compiler-jx/pom.xml
@@ -234,6 +234,18 @@
       <version>4.13.2</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.networknt</groupId>
+      <artifactId>json-schema-validator</artifactId>
+      <version>1.0.87</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-nop</artifactId>
+      <version>2.0.7</version>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.royale.compiler</groupId>
       <artifactId>compiler-externc</artifactId>
diff --git a/compiler-jx/src/assembly/scripts/codegraph 
b/compiler-jx/src/assembly/scripts/codegraph
new file mode 100755
index 000000000..e3db7b9c7
--- /dev/null
+++ b/compiler-jx/src/assembly/scripts/codegraph
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+################################################################################
+##
+##  Licensed to the Apache Software Foundation (ASF) under one or more
+##  contributor license agreements.  See the NOTICE file distributed with
+##  this work for additional information regarding copyright ownership.
+##  The ASF licenses this file to You under the Apache License, Version 2.0
+##  (the "License"); you may not use this file except in compliance with
+##  the License.  You may obtain a copy of the License at
+##
+##      http://www.apache.org/licenses/LICENSE-2.0
+##
+##  Unless required by applicable law or agreed to in writing, software
+##  distributed under the License is distributed on an "AS IS" BASIS,
+##  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+##  See the License for the specific language governing permissions and
+##  limitations under the License.
+##
+################################################################################
+
+SCRIPT_HOME=`dirname "$0"`
+
+if [ "x${ROYALE_COMPILER_HOME}" = "x" ]
+then
+    ROYALE_COMPILER_HOME=${SCRIPT_HOME}/../../compiler
+fi
+
+echo Using Royale codebase: $ROYALE_COMPILER_HOME
+
+if [ "x${ROYALE_HOME}" = "x" ]
+then
+    ROYALE_HOME=${ROYALE_COMPILER_HOME}/generated/dist/sdk
+fi
+echo Using Royale SDK: $ROYALE_HOME
+
+case `uname` in
+        CYGWIN*)
+            OS="Windows"
+        ;;
+        *)
+            OS=Unix
+esac
+
+D32=''
+
+if [ $OS = "Windows" ]; then
+    ROYALE_COMPILER_HOME=`cygpath -m $ROYALE_COMPILER_HOME`
+    ROYALE_HOME=`cygpath -m $ROYALE_HOME`
+elif [ $OS = "Unix" ]; then
+    check64="`java -version 2>&1 | grep -i 64-Bit`"
+    isOSX="`uname | grep -i Darwin`"
+    javaVersion="`java -version 2>&1 | awk -F '[ ".]+' 'NR==1 {print $3 "." 
$4}'`"
+
+    if [ "$isOSX" != "" -a "$HOSTTYPE" = "x86_64" -a "$check64" != "" -a 
"$javaVersion" = "1.6" ]; then
+        D32='-d32'
+    fi
+fi
+
+VMARGS="-Xmx384m -Dsun.io.useCanonCaches=false "
+
+java $VMARGS $D32 $SETUP_SH_VMARGS -Droyalecompiler="$ROYALE_COMPILER_HOME" 
-Droyalelib="$ROYALE_HOME/frameworks" -jar "$SCRIPT_HOME/../lib/codegraph.jar" 
+royalelib="$ROYALE_HOME/frameworks" "$@"
\ No newline at end of file
diff --git a/compiler-jx/src/assembly/scripts/codegraph.bat 
b/compiler-jx/src/assembly/scripts/codegraph.bat
new file mode 100644
index 000000000..cb080d54f
--- /dev/null
+++ b/compiler-jx/src/assembly/scripts/codegraph.bat
@@ -0,0 +1,24 @@
+@echo off
+
+rem
+rem Licensed to the Apache Software Foundation (ASF) under one or more
+rem contributor license agreements.  See the NOTICE file distributed with
+rem this work for additional information regarding copyright ownership.
+rem The ASF licenses this file to You under the Apache License, Version 2.0
+rem (the "License"); you may not use this file except in compliance with
+rem the License.  You may obtain a copy of the License at
+rem
+rem     http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+rem
+
+if "x%ROYALE_COMPILER_HOME%"=="x"  (set ROYALE_COMPILER_HOME=%~dp0..) else 
echo Using Royale codebase: %ROYALE_COMPILER_HOME%
+
+if "x%ROYALE_HOME%"=="x" (set ROYALE_HOME=%~dp0..) else echo Using Royale SDK: 
%ROYALE_HOME%
+
+@java -Dsun.io.useCanonCaches=false -Xms32m -Xmx512m 
-Droyalecompiler="%ROYALE_COMPILER_HOME%" 
-Droyalelib="%ROYALE_HOME%\frameworks" -jar 
"%ROYALE_COMPILER_HOME%\lib\codegraph.jar" %*
\ No newline at end of file
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
index 3ccf2e40b..ace87b2c8 100644
--- 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/CODEGRAPH.java
@@ -106,7 +106,10 @@ public class CODEGRAPH extends MXMLJSCRoyale
                 
definitions.addAll(compilationUnit.getFileScopeRequest().get().getExternallyVisibleDefinitions());
             }
 
-            String module = FilenameUtils.getBaseName(config.getTargetFile());
+            String targetFile = config.getTargetFile();
+            String module = targetFile == null
+                    ? FilenameUtils.getBaseName(getGraphOutputFile().getName())
+                    : FilenameUtils.getBaseName(targetFile);
             CodeGraphModel model = new 
CodeGraphExporter(project).export(definitions, getGraphTarget(), module);
             syntaxTrees.clear();
             File outputFile = getGraphOutputFile();
@@ -161,7 +164,8 @@ public class CODEGRAPH extends MXMLJSCRoyale
         File sourceFile = new 
File(compilationUnit.getAbsoluteFilename()).getAbsoluteFile();
         if (project.isFileOnSourcePath(sourceFile))
             return true;
-        if (sourceFile.equals(new 
File(config.getTargetFile()).getAbsoluteFile()))
+        if (config.getTargetFile() != null
+            && sourceFile.equals(new 
File(config.getTargetFile()).getAbsoluteFile()))
             return true;
         for (File includeSource : targetSettings.getIncludeSources())
         {
@@ -189,9 +193,12 @@ public class CODEGRAPH extends MXMLJSCRoyale
             Collection<File> includeSources = new ArrayList<File>();
             for (String includeSource : config.getIncludeSources())
                 includeSources.add(new File(includeSource));
-            File targetFile = new File(config.getTargetFile());
-            if (!includeSources.contains(targetFile))
-                includeSources.add(targetFile);
+            if (config.getTargetFile() != null)
+            {
+                File targetFile = new File(config.getTargetFile());
+                if (!includeSources.contains(targetFile))
+                    includeSources.add(targetFile);
+            }
             projectConfigurator.setIncludeSources(includeSources);
             targetSettings = 
projectConfigurator.getTargetSettings(getTargetType());
         }
@@ -221,6 +228,8 @@ public class CODEGRAPH extends MXMLJSCRoyale
     {
         if (config.getOutput() != null)
             return new File(config.getOutput());
+        if (config.getTargetFile() == null)
+            return new File("codegraph.json");
         return new File(FilenameUtils.removeExtension(config.getTargetFile()) 
+ ".codegraph.json");
     }
 
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
index d2bf90f56..5aae7caeb 100644
--- 
a/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/clients/RoyaleToolGroup.java
@@ -31,6 +31,7 @@ public class RoyaleToolGroup extends AbstractFlexToolGroup {
         addFlexTool(new COMPJSC());
         addFlexTool(new MXMLJSC());
         addFlexTool(new ASDOCJSC(new MXMLRoyaleASDocDITABackend()));
+        addFlexTool(new CODEGRAPH());
         addFlexTool(new EXTERNC());
         addFlexTool(new PLAYERGLOBALC());
     }
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
index 4b89c380d..74a405a84 100644
--- 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphExporter.java
@@ -112,8 +112,8 @@ public final class CodeGraphExporter
             String baseClassName = 
classDefinition.getBaseClassAsDisplayString();
             if (baseClass != null || (baseClassName != null && 
!baseClassName.isEmpty()))
                 symbol.setBaseType(createReference(baseClass, baseClassName));
-            for (IInterfaceDefinition interfaceDefinition : 
classDefinition.resolveImplementedInterfaces(project))
-                symbol.addInterface(createReference(interfaceDefinition));
+            addInterfaces(symbol, 
classDefinition.resolveImplementedInterfaces(project),
+                    
classDefinition.getImplementedInterfacesAsDisplayStrings());
             IFunctionDefinition constructor = classDefinition.getConstructor();
             if (constructor != null && !constructor.isImplicit())
                 symbol.addMember(exportFunction(constructor, definition));
@@ -121,8 +121,8 @@ public final class CodeGraphExporter
         else
         {
             IInterfaceDefinition interfaceDefinition = 
(IInterfaceDefinition)definition;
-            for (IInterfaceDefinition extendedInterface : 
interfaceDefinition.resolveExtendedInterfaces(project))
-                symbol.addInterface(createReference(extendedInterface));
+            addInterfaces(symbol, 
interfaceDefinition.resolveExtendedInterfaces(project),
+                    
interfaceDefinition.getExtendedInterfacesAsDisplayStrings());
         }
 
         for (IDefinition memberDefinition : 
definition.getContainedScope().getAllLocalDefinitions())
@@ -141,6 +141,19 @@ public final class CodeGraphExporter
         return symbol;
     }
 
+    private void addInterfaces(CodeGraphSymbol symbol, IInterfaceDefinition[] 
definitions, String[] displayNames)
+    {
+        int count = Math.max(definitions.length, displayNames.length);
+        for (int i = 0; i < count; i++)
+        {
+            IInterfaceDefinition definition = i < definitions.length ? 
definitions[i] : null;
+            String displayName = i < displayNames.length ? displayNames[i] : 
null;
+            CodeGraphReference reference = createReference(definition, 
displayName);
+            if (reference != null)
+                symbol.addInterface(reference);
+        }
+    }
+
     private CodeGraphSymbol exportFunction(IFunctionDefinition definition, 
ITypeDefinition declaringType)
     {
         String kind;
diff --git 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
index 0dcbda427..c52ee156c 100644
--- 
a/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
+++ 
b/compiler-jx/src/main/java/org/apache/royale/compiler/internal/codegen/graph/CodeGraphWriter.java
@@ -376,6 +376,9 @@ public final class CodeGraphWriter
         writer.write(": ");
         if (value == null)
             writer.write("null");
+        else if ((value instanceof Double && !Double.isFinite((Double)value))
+                || (value instanceof Float && !Float.isFinite((Float)value)))
+            writeString(writer, value.toString());
         else if (value instanceof Number || value instanceof Boolean)
             writer.write(value.toString());
         else
diff --git 
a/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
 
b/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
new file mode 100644
index 000000000..1b1b9dba7
--- /dev/null
+++ 
b/compiler-jx/src/main/resources/org/apache/royale/compiler/internal/codegen/graph/codegraph-1.0.schema.json
@@ -0,0 +1,158 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema";,
+  "$id": "https://royale.apache.org/schemas/codegraph-1.0.schema.json";,
+  "title": "Apache Royale Code Graph",
+  "description": "A deterministic graph of public ActionScript API 
definitions.",
+  "type": "object",
+  "required": ["schemaVersion", "target", "module", "symbols", 
"externalSymbols"],
+  "additionalProperties": false,
+  "properties": {
+    "schemaVersion": { "const": "1.0" },
+    "target": { "enum": ["js", "swf"] },
+    "module": { "type": ["string", "null"] },
+    "symbols": {
+      "type": "array",
+      "items": { "$ref": "#/$defs/symbol" }
+    },
+    "externalSymbols": {
+      "type": "array",
+      "items": { "$ref": "#/$defs/symbol" }
+    }
+  },
+  "$defs": {
+    "value": {
+      "type": ["string", "number", "boolean", "null"]
+    },
+    "reference": {
+      "type": "object",
+      "required": ["id", "qualifiedName", "external", "unresolved"],
+      "additionalProperties": false,
+      "properties": {
+        "id": { "type": "string" },
+        "qualifiedName": { "type": "string" },
+        "external": { "type": "boolean" },
+        "unresolved": { "type": "boolean" }
+      }
+    },
+    "modifiers": {
+      "type": "object",
+      "required": ["static", "final", "dynamic", "override", "abstract", 
"native"],
+      "additionalProperties": false,
+      "properties": {
+        "static": { "type": "boolean" },
+        "final": { "type": "boolean" },
+        "dynamic": { "type": "boolean" },
+        "override": { "type": "boolean" },
+        "abstract": { "type": "boolean" },
+        "native": { "type": "boolean" }
+      }
+    },
+    "asdocTag": {
+      "type": "object",
+      "required": ["name", "description"],
+      "additionalProperties": false,
+      "properties": {
+        "name": { "type": "string" },
+        "description": { "type": ["string", "null"] }
+      }
+    },
+    "asdoc": {
+      "type": "object",
+      "required": ["description", "tags"],
+      "additionalProperties": false,
+      "properties": {
+        "description": { "type": ["string", "null"] },
+        "tags": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/asdocTag" }
+        }
+      }
+    },
+    "metadataAttribute": {
+      "type": "object",
+      "required": ["key", "value"],
+      "additionalProperties": false,
+      "properties": {
+        "key": { "type": ["string", "null"] },
+        "value": { "type": ["string", "null"] }
+      }
+    },
+    "metadata": {
+      "type": "object",
+      "required": ["name", "attributes"],
+      "additionalProperties": false,
+      "properties": {
+        "name": { "type": "string" },
+        "attributes": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/metadataAttribute" }
+        },
+        "references": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/reference" }
+        },
+        "asdoc": { "$ref": "#/$defs/asdoc" }
+      }
+    },
+    "parameter": {
+      "type": "object",
+      "required": ["name", "type", "optional", "rest", "defaultValue"],
+      "additionalProperties": false,
+      "properties": {
+        "name": { "type": "string" },
+        "type": {
+          "oneOf": [
+            { "$ref": "#/$defs/reference" },
+            { "type": "null" }
+          ]
+        },
+        "optional": { "type": "boolean" },
+        "rest": { "type": "boolean" },
+        "defaultValue": { "$ref": "#/$defs/value" }
+      }
+    },
+    "symbol": {
+      "type": "object",
+      "required": ["id", "qualifiedName", "baseName", "package", "kind"],
+      "additionalProperties": false,
+      "properties": {
+        "id": { "type": "string" },
+        "qualifiedName": { "type": "string" },
+        "baseName": { "type": "string" },
+        "package": { "type": "string" },
+        "kind": {
+          "enum": ["class", "interface", "constructor", "method", "getter", 
"setter", "field", "constant", "function", "variable"]
+        },
+        "external": { "type": "boolean" },
+        "source": { "type": "string" },
+        "origin": { "type": "string" },
+        "visibility": { "type": "string" },
+        "modifiers": { "$ref": "#/$defs/modifiers" },
+        "initialValue": { "$ref": "#/$defs/value" },
+        "declaringType": { "$ref": "#/$defs/reference" },
+        "type": { "$ref": "#/$defs/reference" },
+        "returnType": { "$ref": "#/$defs/reference" },
+        "baseType": { "$ref": "#/$defs/reference" },
+        "overrides": { "$ref": "#/$defs/reference" },
+        "implements": { "$ref": "#/$defs/reference" },
+        "asdoc": { "$ref": "#/$defs/asdoc" },
+        "interfaces": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/reference" }
+        },
+        "parameters": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/parameter" }
+        },
+        "metadata": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/metadata" }
+        },
+        "members": {
+          "type": "array",
+          "items": { "$ref": "#/$defs/symbol" }
+        }
+      }
+    }
+  }
+}
diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
index f669d83eb..16ab2b22c 100644
--- 
a/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/clients/TestCODEGRAPH.java
@@ -28,6 +28,9 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.flex.tools.FlexTool;
+import org.apache.flex.tools.FlexToolGroup;
+import org.apache.flex.tools.FlexToolRegistry;
 import org.apache.commons.io.FileUtils;
 import org.apache.royale.compiler.problems.ICompilerProblem;
 import org.apache.royale.compiler.problems.UnknownTypeProblem;
@@ -56,6 +59,16 @@ public class TestCODEGRAPH
         FileUtils.deleteQuietly(outputFile);
     }
 
+    @Test
+    public void testRegisteredAsRoyaleFlexTool()
+    {
+        FlexToolRegistry registry = new FlexToolRegistry();
+        FlexToolGroup toolGroup = registry.getToolGroup("Royale");
+        FlexTool tool = toolGroup.getFlexTool("codegraph");
+
+        assertTrue(tool instanceof CODEGRAPH);
+    }
+
     @Test
     public void testCompilerErrorsPreventOutputByDefault()
     {
@@ -75,6 +88,7 @@ public class TestCODEGRAPH
         assertTrue(problems.toString(), 
containsProblem(UnknownTypeProblem.class));
         String output = FileUtils.readFileToString(outputFile, "UTF-8");
         assertTrue(output, output.contains("\"qualifiedName\": 
\"MissingType\""));
+        assertTrue(output, output.contains("\"qualifiedName\": 
\"MissingInterface\""));
         assertTrue(output, output.contains("\"unresolved\": true"));
     }
 
@@ -136,6 +150,34 @@ public class TestCODEGRAPH
         assertTrue(output, 
output.contains("as3://codegraph/conditional/IncludedOnly"));
     }
 
+    @Test
+    public void testIncludeSourcesDirectoryDoesNotRequireTargetFile() throws 
IOException
+    {
+        File sourceDirectory = testAdapter.getUnitTestBaseDir();
+        File includeDirectory = new File(sourceDirectory, 
"codegraph/conditional");
+        outputFile = new File(testAdapter.getTempDir(), 
"codegraph/IncludedDirectory.json");
+
+        int exitCode = compile(null, sourceDirectory, includeDirectory, false, 
false);
+        assertEquals(problems.toString(), 0, exitCode);
+        String output = FileUtils.readFileToString(outputFile, "UTF-8");
+        assertTrue(output, output.contains("\"module\": 
\"IncludedDirectory\""));
+        assertTrue(output, 
output.contains("as3://codegraph/conditional/ConditionalGraph"));
+        assertTrue(output, 
output.contains("as3://codegraph/conditional/IncludedOnly"));
+    }
+
+    @Test
+    public void testIncludeSourcesDirectoryDoesNotRequireSourcePath() throws 
IOException
+    {
+        File includeDirectory = new File(testAdapter.getUnitTestBaseDir(), 
"codegraph/conditional");
+        outputFile = new File(testAdapter.getTempDir(), 
"codegraph/IncludedDirectory.json");
+
+        int exitCode = compile(null, null, includeDirectory, false, false);
+        assertEquals(problems.toString(), 0, exitCode);
+        String output = FileUtils.readFileToString(outputFile, "UTF-8");
+        assertTrue(output, 
output.contains("as3://codegraph/conditional/ConditionalGraph"));
+        assertTrue(output, 
output.contains("as3://codegraph/conditional/IncludedOnly"));
+    }
+
     private int compile(boolean createTargetWithErrors)
     {
         File sourceFile = new File(testAdapter.getUnitTestBaseDir(), 
"codegraph/InvalidCodeGraph.as");
@@ -179,7 +221,8 @@ public class TestCODEGRAPH
             arguments.add("-define=COMPILE::JS," + !swf);
             arguments.add("-define+=COMPILE::SWF," + swf);
         }
-        arguments.add(sourceFile.getPath());
+        if (sourceFile != null)
+            arguments.add(sourceFile.getPath());
 
         problems = new ArrayList<ICompilerProblem>();
         return new CODEGRAPH().mainNoExit(arguments.toArray(new 
String[arguments.size()]), problems, false);
diff --git 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
index 4ee729549..9d10c9f49 100644
--- 
a/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
+++ 
b/compiler-jx/src/test/java/org/apache/royale/compiler/internal/codegen/graph/TestCodeGraphWriter.java
@@ -23,8 +23,16 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.StringWriter;
+import java.util.Set;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SpecVersion;
+import com.networknt.schema.ValidationMessage;
 import org.junit.Test;
 
 public class TestCodeGraphWriter
@@ -42,6 +50,8 @@ public class TestCodeGraphWriter
         StringWriter output = new StringWriter();
         new CodeGraphWriter().write(model, output);
 
+        assertConformsToSchema(output.toString());
+
         assertEquals("{\n"
                 + "  \"schemaVersion\": \"1.0\",\n"
                 + "  \"target\": \"js\",\n"
@@ -96,6 +106,7 @@ public class TestCodeGraphWriter
         writer.write(model, secondOutput);
 
         assertEquals(firstOutput.toString(), secondOutput.toString());
+        assertConformsToSchema(firstOutput.toString());
         assertTrue(firstOutput.toString().contains("\"baseType\": {"));
         assertTrue(firstOutput.toString().contains("\"members\": ["));
         assertTrue(firstOutput.toString().contains("\"parameters\": ["));
@@ -106,4 +117,70 @@ public class TestCodeGraphWriter
         assertTrue(firstOutput.toString().contains("\"key\": null"));
         assertTrue(firstOutput.toString().contains("\"external\": true"));
     }
+
+    @Test
+    public void testGoldenDocumentConformsToSchema() throws IOException
+    {
+        InputStream documentInput = 
getClass().getResourceAsStream("/codegraph/golden/GraphRoot.json");
+        assertTrue("Golden code graph resource is missing", documentInput != 
null);
+        try
+        {
+            assertConformsToSchema(new ObjectMapper().readTree(documentInput));
+        }
+        finally
+        {
+            documentInput.close();
+        }
+    }
+
+    @Test
+    public void testNonFiniteValuesAreValidJSONStrings() throws IOException
+    {
+        CodeGraphModel model = new CodeGraphModel("js", "non-finite-values");
+        CodeGraphSymbol symbol = new CodeGraphSymbol("as3://example/Values", 
"example.Values", "Values",
+                "example", "class");
+        CodeGraphSymbol nan = new CodeGraphSymbol("as3://example/Values#nan", 
"example.Values.nan", "nan",
+                "example", "constant");
+        nan.setInitialValue(Double.NaN);
+        CodeGraphSymbol positiveInfinity = new 
CodeGraphSymbol("as3://example/Values#positiveInfinity",
+                "example.Values.positiveInfinity", "positiveInfinity", 
"example", "constant");
+        positiveInfinity.setInitialValue(Double.POSITIVE_INFINITY);
+        CodeGraphSymbol negativeInfinity = new 
CodeGraphSymbol("as3://example/Values#negativeInfinity",
+                "example.Values.negativeInfinity", "negativeInfinity", 
"example", "constant");
+        negativeInfinity.setInitialValue(Double.NEGATIVE_INFINITY);
+        symbol.addMember(nan);
+        symbol.addMember(positiveInfinity);
+        symbol.addMember(negativeInfinity);
+        model.addSymbol(symbol);
+
+        StringWriter output = new StringWriter();
+        new CodeGraphWriter().write(model, output);
+
+        assertConformsToSchema(output.toString());
+        assertTrue(output.toString().contains("\"initialValue\": \"NaN\""));
+        assertTrue(output.toString().contains("\"initialValue\": 
\"Infinity\""));
+        assertTrue(output.toString().contains("\"initialValue\": 
\"-Infinity\""));
+    }
+
+    private void assertConformsToSchema(String document) throws IOException
+    {
+        assertConformsToSchema(new ObjectMapper().readTree(document));
+    }
+
+    private void assertConformsToSchema(JsonNode document) throws IOException
+    {
+        InputStream schemaInput = 
getClass().getResourceAsStream("codegraph-1.0.schema.json");
+        assertTrue("Code graph schema resource is missing", schemaInput != 
null);
+        try
+        {
+            JsonSchemaFactory factory = 
JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V202012);
+            JsonSchema schema = factory.getSchema(schemaInput);
+            Set<ValidationMessage> errors = schema.validate(document);
+            assertTrue(errors.toString(), errors.isEmpty());
+        }
+        finally
+        {
+            schemaInput.close();
+        }
+    }
 }
\ No newline at end of file
diff --git a/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as 
b/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
index f7ad08b7e..9d577256a 100644
--- a/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
+++ b/compiler-jx/src/test/resources/codegraph/InvalidCodeGraph.as
@@ -1,6 +1,6 @@
 package codegraph
 {
-    public class InvalidCodeGraph
+    public class InvalidCodeGraph implements MissingInterface
     {
         public var unresolved:MissingType;
 
diff --git 
a/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
 
b/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
new file mode 100644
index 000000000..bddb112ac
--- /dev/null
+++ 
b/royale-maven-plugin/src/main/java/org/apache/royale/maven/CompileCodeGraphMojo.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.royale.maven;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Goal which exports the public API graph for the project.
+ */
+@Mojo(name="compile-codegraph", defaultPhase = LifecyclePhase.SITE)
+public class CompileCodeGraphMojo
+    extends BaseMojo
+{
+
+    @Parameter(defaultValue = "codegraph")
+    private String codeGraphDirectoryName;
+
+    @Parameter(defaultValue = "${project.artifactId}.json")
+    private String outputFileName;
+
+    @Parameter(defaultValue = "false")
+    private boolean skipCodeGraph;
+
+    @Parameter(defaultValue = "false")
+    private boolean skipAS;
+
+    private ThreadLocal<Type> type = new ThreadLocal<Type>();
+
+    @Override
+    protected String getToolGroupName() {
+        return "Royale";
+    }
+
+    @Override
+    protected String getFlexTool() {
+        return "codegraph";
+    }
+
+    @Override
+    protected String getConfigFileName() throws MojoExecutionException {
+        if(type.get() == null) {
+            throw new MojoExecutionException("type not set");
+        }
+        switch (type.get()) {
+            case SWF:
+                return "compile-codegraph-swf-config.xml";
+            case JS:
+                return "compile-codegraph-js-config.xml";
+        }
+        return null;
+    }
+
+    @Override
+    protected File getOutput() throws MojoExecutionException {
+        if(type.get() == null) {
+            throw new MojoExecutionException("type not set");
+        }
+        return new File(new File(new File(outputDirectory, 
codeGraphDirectoryName),
+                type.get() == Type.SWF ? "swf" : "js"), outputFileName);
+    }
+
+    @Override
+    protected boolean skip() {
+        return skipCodeGraph;
+    }
+
+    @Override
+    public void execute() throws MojoExecutionException {
+        try {
+            if (!skipAS)
+            {
+                getLog().info("Generating SWF code graph");
+                type.set(Type.SWF);
+                super.execute();
+                getLog().info("Finished");
+            }
+
+            getLog().info("Generating JS code graph");
+            type.set(Type.JS);
+            super.execute();
+            getLog().info("Finished");
+        } finally {
+            type.remove();
+        }
+    }
+
+    @Override
+    protected List<Define> getDefines() throws MojoExecutionException {
+        List<Define> defines = super.getDefines();
+        if(type.get() == null) {
+            throw new MojoExecutionException("type not set");
+        }
+        switch (type.get()) {
+            case SWF:
+                defines.add(new Define("COMPILE::JS", "false"));
+                defines.add(new Define("COMPILE::SWF", "true"));
+                break;
+            case JS:
+                defines.add(new Define("COMPILE::JS", "true"));
+                defines.add(new Define("COMPILE::SWF", "false"));
+                break;
+        }
+        return defines;
+    }
+
+    @Override
+    protected boolean includeLibrary(Artifact library) {
+        String scope = library.getScope();
+        if ("test".equalsIgnoreCase(scope)) {
+            return false;
+        }
+        switch (type.get()) {
+            case SWF: {
+                String classifier = library.getClassifier();
+                return "swf".equalsIgnoreCase(classifier) ||
+                    ((classifier == null) && 
"runtime".equalsIgnoreCase(scope));
+            }
+            case JS: {
+                String classifier = library.getClassifier();
+                return "typedefs".equalsIgnoreCase(classifier) ||
+                    "js".equalsIgnoreCase(classifier);
+            }
+        }
+        return false;
+    }
+
+    private enum Type {
+        SWF,
+        JS
+    }
+
+}
\ No newline at end of file
diff --git 
a/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml 
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml
new file mode 100644
index 000000000..1cd47231f
--- /dev/null
+++ 
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-js-config.xml
@@ -0,0 +1,90 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<royale-config>
+
+  <compiler>
+    <debug>$debug</debug>
+
+    <library-path>
+#foreach($artifact in $libraries)      
<path-element>$artifact.file</path-element>
+#end
+    </library-path>
+
+    <external-library-path>
+#foreach($artifact in $externalLibraries)      
<path-element>$artifact.file</path-element>
+#end
+    </external-library-path>
+
+    <source-path>
+#foreach($sourcePath in $sourcePaths)      
<path-element>$sourcePath</path-element>
+#end
+    </source-path>
+
+    <namespaces>
+#foreach($namespace in $namespaces)      <namespace>
+        <uri>$namespace.uri</uri>
+        <manifest>$namespace.manifest</manifest>
+      </namespace>
+#end
+#if($languageNamespace)
+      <namespace>
+        <uri>$languageNamespace.uri</uri>
+        <manifest>$languageNamespace.manifest</manifest>
+      </namespace>
+#end
+    </namespaces>
+
+    <keep-as3-metadata>
+#foreach($metadata in $keepAs3Metadata)      <name>$metadata</name>
+#end
+    </keep-as3-metadata>
+
+    
<allow-subclass-overrides>$allowSubclassOverrides</allow-subclass-overrides>
+    <show-deprecation-warnings>false</show-deprecation-warnings>
+
+#foreach($define in $defines)    <define>
+      <name>$define.name</name>
+      <value>$define.value</value>
+    </define>
+#end
+  </compiler>
+
+  <include-sources>
+#foreach($sourcePath in $sourcePaths)    
<path-element>$sourcePath</path-element>
+#end
+  </include-sources>
+
+#if($includeClasses)
+  <include-classes>
+#foreach($includeClass in $includeClasses)    <class>$includeClass</class>
+#end
+  </include-classes>
+#end
+
+#if($namespaceUris)
+  <include-namespaces>
+#foreach($namespaceUri in $namespaceUris)    <uri>$namespaceUri</uri>
+#end
+  </include-namespaces>
+#end
+
+  <keep-asdoc>true</keep-asdoc>
+  <output>${output}</output>
+
+</royale-config>
\ No newline at end of file
diff --git 
a/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
 
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
new file mode 100644
index 000000000..ad5610444
--- /dev/null
+++ 
b/royale-maven-plugin/src/main/resources/config/compile-codegraph-swf-config.xml
@@ -0,0 +1,91 @@
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+-->
+<royale-config>
+
+  <compiler>
+    <debug>$debug</debug>
+
+    <library-path>
+#foreach($artifact in $libraries)      
<path-element>$artifact.file</path-element>
+#end
+    </library-path>
+
+    <external-library-path>
+#foreach($artifact in $externalLibraries)      
<path-element>$artifact.file</path-element>
+#end
+    </external-library-path>
+
+    <source-path>
+#foreach($sourcePath in $sourcePaths)      
<path-element>$sourcePath</path-element>
+#end
+    </source-path>
+
+    <namespaces>
+#foreach($namespace in $namespaces)      <namespace>
+        <uri>$namespace.uri</uri>
+        <manifest>$namespace.manifest</manifest>
+      </namespace>
+#end
+#if($languageNamespace)
+      <namespace>
+        <uri>$languageNamespace.uri</uri>
+        <manifest>$languageNamespace.manifest</manifest>
+      </namespace>
+#end
+    </namespaces>
+
+    <keep-as3-metadata>
+#foreach($metadata in $keepAs3Metadata)      <name>$metadata</name>
+#end
+    </keep-as3-metadata>
+
+    
<allow-subclass-overrides>$allowSubclassOverrides</allow-subclass-overrides>
+    <show-deprecation-warnings>false</show-deprecation-warnings>
+
+#foreach($define in $defines)    <define>
+      <name>$define.name</name>
+      <value>$define.value</value>
+    </define>
+#end
+  </compiler>
+
+  <include-sources>
+#foreach($sourcePath in $sourcePaths)    
<path-element>$sourcePath</path-element>
+#end
+  </include-sources>
+
+#if($includeClasses)
+  <include-classes>
+#foreach($includeClass in $includeClasses)    <class>$includeClass</class>
+#end
+  </include-classes>
+#end
+
+#if($namespaceUris)
+  <include-namespaces>
+#foreach($namespaceUri in $namespaceUris)    <uri>$namespaceUri</uri>
+#end
+  </include-namespaces>
+#end
+
+  <target-player>${targetPlayer}</target-player>
+  <keep-asdoc>true</keep-asdoc>
+  <output>${output}</output>
+
+</royale-config>
\ No newline at end of file

Reply via email to