feat(fls): keep a .fls's own applications through the editor model

The archive's entries besides the configuration — the project's own Java
applications under projekt/anwendungen (.java, .class,
EigeneAnwendungen.txt) — now go through the editor model:
openProjectArchive reads a whole .fls into the model and keeps them in its
archiveEntries, saveProjectArchive writes a whole .fls from the model with
them unchanged. They cannot run here, but must not be lost.

Checked on every corpus file: each entry comes back byte for byte (7 files
carry applications of their own); a probe that drops them is caught.
Installing such applications on a host is the next step; Java loads those
only through its full loading path, which the oracle is to use first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 09:29:21 +00:00
co-authored by Claude Opus 5.5
parent 8ab8850b20
commit 75c3da2bd1
5 changed files with 49 additions and 15 deletions
+21 -2
View File
@@ -97,10 +97,12 @@ import {
type TopologyNode,
type VirtualFileContent,
} from '@mdg-filius/core';
import { readFlsArchive } from './archive.js';
import { dnsRecordsOf, javaHostsFileContent, readDnsHosts } from './dnsHosts.js';
import { decodeJavaBeans } from './javabeans.js';
import { emailClientConfiguration, emailServerConfiguration } from './importEmail.js';
import type { FlsApplicationClass, FlsFileTree, FlsFirewall } from './systemSoftware.js';
import type { FlsDocumentationItem, FlsInterface, FlsNode, FlsNodeClass, FlsTopology } from './topology.js';
import { readTopology, type FlsDocumentationItem, type FlsInterface, type FlsNode, type FlsNodeClass, type FlsTopology } from './topology.js';
export interface ImportedProject {
versionLine: string | null;
@@ -132,6 +134,12 @@ export interface ImportedProject {
simpleClientConfigurations: Map<Id, SimpleClientConfiguration>;
/** In render order: rectangles below text, then by order. */
documentationItems: DocumentationItem[];
/**
* The archive's entries besides the configuration — the project's own Java
* applications (projekt/anwendungen) — carried unchanged to the next save:
* they cannot run here, but must not be lost.
*/
archiveEntries: ReadonlyMap<string, Uint8Array>;
/** The topology's warnings, then this step's. */
warnings: string[];
}
@@ -150,7 +158,11 @@ export const NODE_KIND_OF_CLASS: Readonly<Record<FlsNodeClass, NodeKind>> = {
* (Swift uses random UUIDs; tests pass a counter). Documentation ids are
* derived from the item, as in Swift.
*/
export function importProject(topology: FlsTopology, newId: () => Id): ImportedProject {
export function importProject(
topology: FlsTopology,
newId: () => Id,
archiveEntries: ReadonlyMap<string, Uint8Array> = new Map(),
): ImportedProject {
const warnings = [...topology.warnings];
const deviceConfigurations = new Map<Id, DeviceConfiguration>();
const interfaceConfigurations = new Map<InterfaceKey, InterfaceConfiguration>();
@@ -236,10 +248,17 @@ export function importProject(topology: FlsTopology, newId: () => Id): ImportedP
interfaceConfigurations,
...system,
documentationItems: inDeterministicRenderOrder(documentationItems),
archiveEntries,
warnings,
};
}
/** A whole .fls: its archive, Java's view of its configuration, the editor model with the archive's other entries. */
export function openProjectArchive(archive: Uint8Array, newId: () => Id): ImportedProject {
const contents = readFlsArchive(archive);
return importProject(readTopology(decodeJavaBeans(contents.configurationXml)), newId, contents.supplementalEntries);
}
function hostLabelPolicy(node: FlsNode): HostLabelPolicy {
if (node.useIpAsName && node.useMacAsName) return 'ipAndMAC';
if (node.useIpAsName) return 'ipAddress';