@guanjinke/artifact-client 1.8.0 → 1.10.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.
- package/README.md +2 -7
- package/dist/cli/environment.d.ts +1 -10
- package/dist/cli/environment.d.ts.map +1 -1
- package/dist/cli/environment.js +0 -24
- package/dist/cli/environment.js.map +1 -1
- package/dist/cli/setup.js +5 -8
- package/dist/cli/setup.js.map +1 -1
- package/dist/environment/builder.d.ts.map +1 -1
- package/dist/environment/builder.js +83 -23
- package/dist/environment/builder.js.map +1 -1
- package/dist/environment/catalog.d.ts +36 -3
- package/dist/environment/catalog.d.ts.map +1 -1
- package/dist/environment/catalog.js +50 -0
- package/dist/environment/catalog.js.map +1 -1
- package/dist/environment/plan.d.ts +31 -5
- package/dist/environment/plan.d.ts.map +1 -1
- package/dist/environment/plan.js +104 -16
- package/dist/environment/plan.js.map +1 -1
- package/dist/environment/types.d.ts +49 -12
- package/dist/environment/types.d.ts.map +1 -1
- package/dist/environment/update.d.ts +6 -26
- package/dist/environment/update.d.ts.map +1 -1
- package/dist/environment/update.js +120 -74
- package/dist/environment/update.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +2 -3
- package/resources/3DScene/capeHill.hdr +0 -0
- package/resources/3DScene/defaultGround.jpg +0 -0
- package/resources/3DScene/environmentSpecular.env +0 -0
- package/resources/3DScene/flare.png +0 -0
- package/resources/3DScene/geometry1.png +0 -0
- package/resources/3DScene/geometry10.png +0 -0
- package/resources/3DScene/geometry11.png +0 -0
- package/resources/3DScene/geometry12.png +0 -0
- package/resources/3DScene/geometry13.png +0 -0
- package/resources/3DScene/geometry2.png +0 -0
- package/resources/3DScene/geometry3.png +0 -0
- package/resources/3DScene/geometry4.png +0 -0
- package/resources/3DScene/geometry5.png +0 -0
- package/resources/3DScene/geometry6.png +0 -0
- package/resources/3DScene/geometry7.png +0 -0
- package/resources/3DScene/geometry8.png +0 -0
- package/resources/3DScene/geometry9.png +0 -0
- package/resources/3DScene/lilienstein.hdr +0 -0
- package/resources/3DScene/sky.hdr +0 -0
- package/resources/3DScene/studio.env +0 -0
- package/resources/3DScene/wastelandCloudsPureSky.hdr +0 -0
|
@@ -48,7 +48,6 @@ const registry_1 = require("./registry");
|
|
|
48
48
|
const validate_1 = require("./validate");
|
|
49
49
|
const process_1 = require("./process");
|
|
50
50
|
const posix_process_1 = require("./posix-process");
|
|
51
|
-
const package_resource_1 = require("./package-resource");
|
|
52
51
|
const atomic_1 = require("../utils/atomic");
|
|
53
52
|
const local_path_1 = require("../utils/local-path");
|
|
54
53
|
/** Same parser options as the creation path, so a rewrite round-trips identically. */
|
|
@@ -57,8 +56,13 @@ const builderOptions = { ignoreAttributes: false, format: true, suppressEmptyNod
|
|
|
57
56
|
const windowsPath = (value, platform) => (0, local_path_1.nativeSeparators)(value, platform);
|
|
58
57
|
/** The separator the recorded entries use on the target platform. */
|
|
59
58
|
const separatorOf = (platform) => (platform === 'win32' ? '\\' : '/');
|
|
60
|
-
/**
|
|
61
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Every catalog module an update may refresh. The create-only modules are
|
|
61
|
+
* excluded: they seed data the product imports once, when the Server starts for
|
|
62
|
+
* the first time, so an update never refreshes, preserves or re-deploys them —
|
|
63
|
+
* and the web update picker reads this same list.
|
|
64
|
+
*/
|
|
65
|
+
exports.updatableModules = Object.keys(catalog_1.catalog).filter(module => !catalog_1.catalog[module].createOnly).sort();
|
|
62
66
|
/**
|
|
63
67
|
* Server-root settings the environment owns.
|
|
64
68
|
*
|
|
@@ -95,10 +99,21 @@ const excludedCopyNames = (mapping) => mapping.role === 'server' ? [...serverOwn
|
|
|
95
99
|
*/
|
|
96
100
|
const selectableModules = () => new Set([...new Set(Object.values(plan_1.optionalModulesByEdition).flat())]);
|
|
97
101
|
const installationDirectory = (root, destination) => path.join(root, ...destination.split('/'));
|
|
102
|
+
/**
|
|
103
|
+
* The edition an environment deploys. The registry records a free string and
|
|
104
|
+
* only `en` is special everywhere else in this module, so anything else counts
|
|
105
|
+
* as `cn`, exactly like the edition gate below does.
|
|
106
|
+
*/
|
|
107
|
+
const editionOf = (entry) => entry.edition === 'en' ? 'en' : 'cn';
|
|
98
108
|
function validateUpdatableModule(module) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
// `hasOwnProperty` keeps prototype names (`constructor`) out of the catalog.
|
|
110
|
+
if (!Object.prototype.hasOwnProperty.call(catalog_1.catalog, module))
|
|
111
|
+
throw new Error(`Unsupported module "${module}": supported modules are ${exports.updatableModules.join(', ')}.`);
|
|
112
|
+
// A create-only module is refused before anything else happens — before the
|
|
113
|
+
// registry, the manifest or the configuration is read — because no update can
|
|
114
|
+
// produce it: it is deployed only while the environment is created.
|
|
115
|
+
if (catalog_1.catalog[module].createOnly)
|
|
116
|
+
throw new Error(`Module ${module} is create-only: it is deployed only when the environment is created and is never refreshed, preserved or re-deployed by update-env. Recreate the environment with create-env to get it again.`);
|
|
102
117
|
}
|
|
103
118
|
/** `all` is the only allowed pseudo-module: every configured module from remote. */
|
|
104
119
|
const allModules = 'all';
|
|
@@ -382,10 +397,9 @@ async function readManifest(root) {
|
|
|
382
397
|
}
|
|
383
398
|
if (manifest.selectedModules !== undefined && (!Array.isArray(manifest.selectedModules) || manifest.selectedModules.some(module => typeof module !== 'string')))
|
|
384
399
|
throw new Error('Environment manifest is unreadable.');
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
throw new Error('Environment manifest is unreadable.');
|
|
400
|
+
// A stale `resources` key written by an older version is ignored here and
|
|
401
|
+
// dropped when the manifest is rewritten: the key belongs to the removed
|
|
402
|
+
// packaged-resource feature, not to the environment's configuration.
|
|
389
403
|
if (manifest.moduleSources !== undefined && (!manifest.moduleSources || typeof manifest.moduleSources !== 'object' || Array.isArray(manifest.moduleSources)))
|
|
390
404
|
throw new Error('Environment manifest is unreadable.');
|
|
391
405
|
for (const source of Object.values(manifest.moduleSources ?? {})) {
|
|
@@ -444,7 +458,7 @@ async function resolveRemoteArtifact(client, entry, spec) {
|
|
|
444
458
|
catch {
|
|
445
459
|
throw new Error(`Unable to resolve artifacts for ${source.plugin}/${spec.version ?? entry.major}.`);
|
|
446
460
|
}
|
|
447
|
-
const name =
|
|
461
|
+
const name = (0, catalog_1.archiveName)(source, version, entry.edition);
|
|
448
462
|
const candidates = files.filter(file => file.name === name);
|
|
449
463
|
if (candidates.length !== 1)
|
|
450
464
|
throw new Error(`${source.plugin}/${version}: expected exactly one ${name}, found ${candidates.length}.`);
|
|
@@ -454,7 +468,7 @@ async function resolveRemoteArtifact(client, entry, spec) {
|
|
|
454
468
|
return { module, plugin: source.plugin, version, name, path: remotePath, mappings: source.mappings.map(mapping => ({ ...mapping, assemblies: [...mapping.assemblies] })) };
|
|
455
469
|
}
|
|
456
470
|
/** Download, hash, extract and verify one resolved artifact inside the staging workspace. */
|
|
457
|
-
async function stageRemoteArtifact(client, artifact, workspace, onProgress) {
|
|
471
|
+
async function stageRemoteArtifact(client, artifact, edition, workspace, onProgress) {
|
|
458
472
|
const label = `[${artifact.module}] ${artifact.plugin}/${artifact.version}/${artifact.name}`;
|
|
459
473
|
const download = path.join(workspace, 'download', artifact.module);
|
|
460
474
|
const expanded = path.join(workspace, `expanded-${artifact.module}`);
|
|
@@ -469,10 +483,16 @@ async function stageRemoteArtifact(client, artifact, workspace, onProgress) {
|
|
|
469
483
|
onProgress?.(`${label}: Extracting and validating ZIP`);
|
|
470
484
|
await (0, archive_1.extractArchive)(archive, expanded);
|
|
471
485
|
const assemblies = new Map();
|
|
472
|
-
|
|
486
|
+
// A mapping of the other edition is never required: its source directory
|
|
487
|
+
// does not even have to be in the archive.
|
|
488
|
+
for (const mapping of (0, catalog_1.editionMappings)(artifact.mappings, edition)) {
|
|
473
489
|
const source = path.join(expanded, mapping.source);
|
|
474
490
|
if (!(await fs.lstat(source)).isDirectory())
|
|
475
491
|
throw new Error('Required source directory missing.');
|
|
492
|
+
// A `resource` mapping is a static tree with no assembly to verify and no
|
|
493
|
+
// runtimeconfig to read; the Server only serves it.
|
|
494
|
+
if (mapping.role === 'resource')
|
|
495
|
+
continue;
|
|
476
496
|
if (mapping.role === 'service' && mapping.assemblies.length === 0)
|
|
477
497
|
continue;
|
|
478
498
|
const present = [];
|
|
@@ -616,6 +636,12 @@ function mappingForAssembly(module, assembly) {
|
|
|
616
636
|
* explicitly, for a build whose file name matches no assembly at all.
|
|
617
637
|
*/
|
|
618
638
|
async function resolveLocalSource(entry, module, assembly, input) {
|
|
639
|
+
// The built-in resource module is never a developer-build target: it ships a
|
|
640
|
+
// static asset tree that no local DLL could stand in for, so a local source is
|
|
641
|
+
// refused on its own terms before any path is even looked at. Remote specs
|
|
642
|
+
// (latest, pinned and `all`) keep working.
|
|
643
|
+
if (plan_1.trailingModules.includes(module))
|
|
644
|
+
throw new Error(`Module ${module} is built in and has no local source: it ships the 3D scene and map assets and is always deployed from its published artifact. Refresh it from the cloud (\`${module}\`, \`${module}:<version>\` or \`all\`).`);
|
|
619
645
|
const mappings = localMappings(module);
|
|
620
646
|
// The Server, Monitor and collaboration deployments are whole services, not a
|
|
621
647
|
// plugin the Server loads: a developer build has no place to point the
|
|
@@ -705,14 +731,12 @@ class EnvironmentUpdater {
|
|
|
705
731
|
registry;
|
|
706
732
|
processes;
|
|
707
733
|
platform;
|
|
708
|
-
bundledResources;
|
|
709
734
|
onProgress;
|
|
710
735
|
constructor(dependencies = {}) {
|
|
711
736
|
this.dependencies = dependencies;
|
|
712
737
|
this.registry = dependencies.registry ?? new registry_1.EnvironmentRegistry();
|
|
713
738
|
this.processes = dependencies.processes ?? (process.platform === 'win32' ? new process_1.WindowsProcessBackend() : new posix_process_1.PosixProcessBackend());
|
|
714
739
|
this.platform = dependencies.platform ?? process.platform;
|
|
715
|
-
this.bundledResources = dependencies.bundledResourcesDirectory ?? (0, package_resource_1.bundledResourcesDirectory)();
|
|
716
740
|
this.onProgress = dependencies.onProgress;
|
|
717
741
|
}
|
|
718
742
|
async update(name, specs, clientFactory, options = {}) {
|
|
@@ -720,9 +744,8 @@ class EnvironmentUpdater {
|
|
|
720
744
|
const onProgress = options.onProgress ?? this.onProgress;
|
|
721
745
|
onProgress?.('Validating module specs');
|
|
722
746
|
let parsed = normalizeModuleSpecs(specs);
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
// them without touching a single module.
|
|
747
|
+
if (!parsed.length)
|
|
748
|
+
throw new Error('At least one module spec is required: pass `all` or one or more module names (module, module:<version> or module=<local path>).');
|
|
726
749
|
const entry = await this.registry.get(name);
|
|
727
750
|
onProgress?.(`Validating environment ${entry.name} at ${entry.root}`);
|
|
728
751
|
await (0, validate_1.validateEnvironmentRoot)(entry, this.registry.directory);
|
|
@@ -766,6 +789,23 @@ class EnvironmentUpdater {
|
|
|
766
789
|
localOverrides.set(spec.module, targets);
|
|
767
790
|
}
|
|
768
791
|
const remote = parsed.filter(spec => spec.localPath === undefined);
|
|
792
|
+
// The built-in resource module deploys its trees *inside* the Server
|
|
793
|
+
// directory, and a Server refresh replaces that whole directory. An
|
|
794
|
+
// environment that has it therefore re-deploys it from its own artifact
|
|
795
|
+
// whenever the Server is refreshed in the same call: a remote-only module has
|
|
796
|
+
// no local copy to restore the trees from. A create-only trailing module is
|
|
797
|
+
// never re-deployed: the Server refresh simply drops what it had seeded.
|
|
798
|
+
const autoRefreshed = [];
|
|
799
|
+
if (remote.some(spec => spec.module === 'server')) {
|
|
800
|
+
for (const module of plan_1.trailingModules) {
|
|
801
|
+
if (catalog_1.catalog[module].createOnly || !installed.has(module) || parsed.some(spec => spec.module === module))
|
|
802
|
+
continue;
|
|
803
|
+
parsed.push({ module });
|
|
804
|
+
remote.push({ module });
|
|
805
|
+
autoRefreshed.push(module);
|
|
806
|
+
onProgress?.(`${module} is refreshed with the Server package: its trees deploy inside the Server directory`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
769
809
|
// Smart documents cannot run without the collaboration service, which
|
|
770
810
|
// create-env deploys and declares in the Server configuration alongside
|
|
771
811
|
// documents. Installing documents into an environment with no collaboration
|
|
@@ -779,26 +819,6 @@ class EnvironmentUpdater {
|
|
|
779
819
|
throw new Error(`Cannot update modules from a remote source while ${entry.name} is ${state}: deployed files may be in use. Run \`afm env stop ${entry.name}\` first.`);
|
|
780
820
|
if (!remote.length && state !== 'ready' && state !== 'stopped' && state !== 'running')
|
|
781
821
|
throw new Error(`Cannot update modules while ${entry.name} is ${state}: the runtime identity must be verified before its files are changed. Run \`afm env stop ${entry.name}\` first.`);
|
|
782
|
-
// The packaged 3D scene assets belong to every environment, not only to one
|
|
783
|
-
// that create-env assembled: the plan below finds the files an environment is
|
|
784
|
-
// still missing (or that differ) and the apply writes them with the rest of
|
|
785
|
-
// the update. Planning touches nothing and runs before any download or write,
|
|
786
|
-
// so an incomplete installation fails loudly with the path it expected and
|
|
787
|
-
// leaves the environment exactly as it was.
|
|
788
|
-
//
|
|
789
|
-
// The assets live at `Server/wwwroot/3DScene`, so refreshing the server
|
|
790
|
-
// module removes the whole tree they are in. Plan every packaged file as
|
|
791
|
-
// missing in that case: comparing against the content that is about to be
|
|
792
|
-
// deleted would report every file identical and lay nothing out at all.
|
|
793
|
-
const refreshesServer = remote.some(spec => spec.module === 'server');
|
|
794
|
-
const resourceSource = (0, package_resource_1.bundledResourceDirectory)(package_resource_1.sceneResourceName, this.bundledResources);
|
|
795
|
-
const resourceDestination = path.join(entry.root, ...package_resource_1.sceneResourceDestination.split('/'));
|
|
796
|
-
onProgress?.(`Checking bundled 3D scene resources in ${package_resource_1.sceneResourceDestination}`);
|
|
797
|
-
const resourcePlan = await (0, package_resource_1.planBundledResource)(resourceSource, resourceDestination, refreshesServer);
|
|
798
|
-
const resourceWrites = resourcePlan.added + resourcePlan.updated;
|
|
799
|
-
const resource = { name: package_resource_1.sceneResourceName, source: resourceSource, destination: package_resource_1.sceneResourceDestination, files: resourcePlan.files.length, added: resourcePlan.added, updated: resourcePlan.updated };
|
|
800
|
-
const resourceSummary = `${resourcePlan.files.length - resourceWrites} identical, ${resourcePlan.added} missing, ${resourcePlan.updated} different`;
|
|
801
|
-
onProgress?.(`Bundled 3D scene resources in ${package_resource_1.sceneResourceDestination}: ${dryRun ? 'dry run; ' : ''}${resourceSummary}`);
|
|
802
822
|
const downloads = [...(manifest.downloads ?? [])];
|
|
803
823
|
const selectedModules = stringListOrUndefined(manifest.selectedModules);
|
|
804
824
|
const autoAddedModules = stringListOrUndefined(manifest.autoAddedModules);
|
|
@@ -811,7 +831,7 @@ class EnvironmentUpdater {
|
|
|
811
831
|
// runtimeconfig files, so an update replaces the entries of the modules it
|
|
812
832
|
// refreshes and leaves every other module's alone.
|
|
813
833
|
let requirements = Array.isArray(manifest.requirements) ? manifest.requirements.filter((line) => typeof line === 'string') : [];
|
|
814
|
-
const workspace = !dryRun &&
|
|
834
|
+
const workspace = !dryRun && remote.length > 0 ? await fs.mkdtemp(path.join(path.dirname(entry.root), '.afm-env-')) : undefined;
|
|
815
835
|
const warnings = [];
|
|
816
836
|
try {
|
|
817
837
|
const resolved = new Map();
|
|
@@ -824,7 +844,7 @@ class EnvironmentUpdater {
|
|
|
824
844
|
}
|
|
825
845
|
if (!dryRun)
|
|
826
846
|
for (const spec of remote)
|
|
827
|
-
staged.set(spec.module, await stageRemoteArtifact(client, resolved.get(spec.module), workspace, onProgress));
|
|
847
|
+
staged.set(spec.module, await stageRemoteArtifact(client, resolved.get(spec.module), editionOf(entry), workspace, onProgress));
|
|
828
848
|
}
|
|
829
849
|
const configured = await fs.readFile(path.join(entry.root, 'conf', 'Wyn.conf'), 'utf8');
|
|
830
850
|
// A Server or Monitor package is only usable when it still serves the
|
|
@@ -846,7 +866,12 @@ class EnvironmentUpdater {
|
|
|
846
866
|
}
|
|
847
867
|
const plans = [];
|
|
848
868
|
// A module may carry several local specs: plan it once, for all its targets.
|
|
849
|
-
|
|
869
|
+
// The built-in trailing modules are planned last, in the same order
|
|
870
|
+
// `create-env` assembles: their trees live inside other modules'
|
|
871
|
+
// directories, so a Server replacement must not wipe them again.
|
|
872
|
+
const planOrder = [...new Set(parsed.map(spec => spec.module))]
|
|
873
|
+
.sort((left, right) => Number(plan_1.trailingModules.includes(left)) - Number(plan_1.trailingModules.includes(right)));
|
|
874
|
+
for (const module of planOrder) {
|
|
850
875
|
const artifact = artifacts.find(candidate => candidate.module === module);
|
|
851
876
|
const previous = moduleSources[module] ? { local: moduleSources[module].path } : { version: artifact?.version };
|
|
852
877
|
const override = localOverrides.get(module);
|
|
@@ -885,8 +910,12 @@ class EnvironmentUpdater {
|
|
|
885
910
|
const entries = [];
|
|
886
911
|
let configurationAssets;
|
|
887
912
|
let configurationLink;
|
|
888
|
-
|
|
913
|
+
// Only the mappings this environment's edition deploys: the other
|
|
914
|
+
// edition's directory is never removed, copied or referenced.
|
|
915
|
+
for (const mapping of (0, catalog_1.editionMappings)(remoteArtifact.mappings, editionOf(entry))) {
|
|
889
916
|
const destination = installationDirectory(entry.root, mapping.destination);
|
|
917
|
+
// A `resource` directory the module already deployed is replaced, never
|
|
918
|
+
// merged, and it contributes no plugin, worker, service or runtime entry.
|
|
890
919
|
if (await exists(destination)) {
|
|
891
920
|
removals.push(destination);
|
|
892
921
|
removedDirectories.push(mapping.destination);
|
|
@@ -903,6 +932,8 @@ class EnvironmentUpdater {
|
|
|
903
932
|
if (stagedArtifact)
|
|
904
933
|
copies.push({ source: path.join(stagedArtifact.expanded, mapping.source), destination, exclude: excludedCopyNames(mapping) });
|
|
905
934
|
copiedDirectories.push(mapping.destination);
|
|
935
|
+
if (mapping.role === 'resource')
|
|
936
|
+
continue;
|
|
906
937
|
// A native out-of-process service has no assembly and is declared as an
|
|
907
938
|
// empty node: the Server loads it from that entry and supplies the rest.
|
|
908
939
|
if (mapping.role === 'service') {
|
|
@@ -975,9 +1006,10 @@ class EnvironmentUpdater {
|
|
|
975
1006
|
nextDownloads[downloadIndex] = download;
|
|
976
1007
|
// The requirements the old artifact needed are gone with it: re-read
|
|
977
1008
|
// the ones the new package actually declares, per deployed mapping.
|
|
1009
|
+
// A resource mapping is a static tree: it declares none.
|
|
978
1010
|
requirements = requirements.filter(line => !line.endsWith(` (${plan.module})`));
|
|
979
|
-
for (const mapping of plan.artifact.mappings) {
|
|
980
|
-
if (mapping.role === 'service')
|
|
1011
|
+
for (const mapping of (0, catalog_1.editionMappings)(plan.artifact.mappings, editionOf(entry))) {
|
|
1012
|
+
if (mapping.role === 'service' || mapping.role === 'resource')
|
|
981
1013
|
continue;
|
|
982
1014
|
for (const framework of await (0, builder_1.runtimeRequirements)(path.join(plan.staged.expanded, mapping.source))) {
|
|
983
1015
|
requirements.push(`${framework.name} ${framework.version} (${plan.module})`);
|
|
@@ -1010,6 +1042,32 @@ class EnvironmentUpdater {
|
|
|
1010
1042
|
autoAddedModules.length = 0;
|
|
1011
1043
|
autoAddedModules.push(...unique);
|
|
1012
1044
|
}
|
|
1045
|
+
// A create-only module deploys its payload inside the Server directory and is
|
|
1046
|
+
// never refreshed, so a Server refresh replaces the tree it owns: the module
|
|
1047
|
+
// is gone from the environment, and its record goes with it — the manifest
|
|
1048
|
+
// and the registry would otherwise keep claiming a deployment that is not
|
|
1049
|
+
// there any more.
|
|
1050
|
+
const removedCreateOnly = [];
|
|
1051
|
+
if (plans.some(plan => plan.module === 'server')) {
|
|
1052
|
+
for (const module of Object.keys(catalog_1.catalog)) {
|
|
1053
|
+
if (catalog_1.catalog[module].createOnly !== true || !installed.has(module) || plans.some(plan => plan.module === module))
|
|
1054
|
+
continue;
|
|
1055
|
+
const plugin = catalog_1.catalog[module].plugin;
|
|
1056
|
+
for (let index = nextArtifacts.length - 1; index >= 0; index -= 1)
|
|
1057
|
+
if (nextArtifacts[index].module === module)
|
|
1058
|
+
nextArtifacts.splice(index, 1);
|
|
1059
|
+
for (let index = nextDownloads.length - 1; index >= 0; index -= 1)
|
|
1060
|
+
if (nextDownloads[index].plugin === plugin)
|
|
1061
|
+
nextDownloads.splice(index, 1);
|
|
1062
|
+
for (let index = requirements.length - 1; index >= 0; index -= 1)
|
|
1063
|
+
if (requirements[index].endsWith(` (${module})`))
|
|
1064
|
+
requirements.splice(index, 1);
|
|
1065
|
+
if (autoAddedModules)
|
|
1066
|
+
autoAddedModules.splice(0, autoAddedModules.length, ...autoAddedModules.filter(name => name !== module));
|
|
1067
|
+
delete moduleSources[module];
|
|
1068
|
+
removedCreateOnly.push(module);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1013
1071
|
const nextManifest = { ...manifest, artifacts: nextArtifacts, downloads: nextDownloads, requirements: [...new Set(requirements)] };
|
|
1014
1072
|
if (autoAddedModules)
|
|
1015
1073
|
nextManifest.autoAddedModules = autoAddedModules;
|
|
@@ -1019,19 +1077,10 @@ class EnvironmentUpdater {
|
|
|
1019
1077
|
delete nextManifest.moduleSources;
|
|
1020
1078
|
if (selectedModules)
|
|
1021
1079
|
nextManifest.selectedModules = selectedModules;
|
|
1022
|
-
//
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const resources = [...(manifest.resources ?? [])];
|
|
1027
|
-
const recorded = resources.findIndex(candidate => candidate.name === package_resource_1.sceneResourceName);
|
|
1028
|
-
const record = { name: package_resource_1.sceneResourceName, source: resource.source, destination: resource.destination, files: resource.files };
|
|
1029
|
-
if (recorded < 0)
|
|
1030
|
-
resources.push(record);
|
|
1031
|
-
else
|
|
1032
|
-
resources[recorded] = { ...resources[recorded], ...record };
|
|
1033
|
-
nextManifest.resources = resources;
|
|
1034
|
-
const modules = [...new Set([...entry.modules, ...plans.filter(plan => !installed.has(plan.module) && selectable.has(plan.module)).map(plan => plan.module)])].sort();
|
|
1080
|
+
// A stale `resources` key from an older manifest is dropped here: the
|
|
1081
|
+
// packaged-resource feature is gone and nothing records it any more.
|
|
1082
|
+
delete nextManifest.resources;
|
|
1083
|
+
const modules = [...new Set([...entry.modules, ...plans.filter(plan => !installed.has(plan.module) && selectable.has(plan.module)).map(plan => plan.module)])].filter(module => !removedCreateOnly.includes(module)).sort();
|
|
1035
1084
|
const localModules = Object.keys(moduleSources).sort();
|
|
1036
1085
|
if (state === 'running')
|
|
1037
1086
|
warnings.push(`${entry.name} is running; restart it to load the updated modules.`);
|
|
@@ -1046,6 +1095,10 @@ class EnvironmentUpdater {
|
|
|
1046
1095
|
if (parsed.some(spec => spec.module === 'documents') && !parsed.some(spec => spec.module === 'collaboration')) {
|
|
1047
1096
|
warnings.push('documents was refreshed without collaboration; the smart documents plugin may expect the deployed collaboration service version, so refresh collaboration too.');
|
|
1048
1097
|
}
|
|
1098
|
+
if (autoRefreshed.length)
|
|
1099
|
+
warnings.push(`${autoRefreshed.join(', ')} was refreshed with the Server package: its trees live inside the Server directory and would otherwise be lost.`);
|
|
1100
|
+
if (removedCreateOnly.length)
|
|
1101
|
+
warnings.push(`${removedCreateOnly.join(', ')} was removed with the Server package: it is deployed only when an environment is created.`);
|
|
1049
1102
|
if (!dryRun) {
|
|
1050
1103
|
for (const plan of plans) {
|
|
1051
1104
|
// Take the environment-owned settings aside first, and drop the Monitor
|
|
@@ -1075,20 +1128,13 @@ class EnvironmentUpdater {
|
|
|
1075
1128
|
if (plan.configurationLink)
|
|
1076
1129
|
await fs.symlink(path.join(entry.root, 'conf'), plan.configurationLink, 'junction');
|
|
1077
1130
|
}
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
if (applied.added.length !== resourcePlan.added || applied.updated.length !== resourcePlan.updated)
|
|
1086
|
-
throw new Error('Bundled resource layout did not match its plan.');
|
|
1087
|
-
}
|
|
1088
|
-
// A resource-only update (no module spec) leaves the Server configuration
|
|
1089
|
-
// exactly as it is: the assets need no plugin, worker or service entry.
|
|
1090
|
-
onProgress?.(plans.length ? 'Writing configuration, manifest and registry record' : 'Writing manifest and registry record');
|
|
1091
|
-
if (plans.length)
|
|
1131
|
+
// Only a plan that really contributes something to the configuration
|
|
1132
|
+
// rewrites it: the built-in dashboard-resource module adds no plugin,
|
|
1133
|
+
// worker or service entry, so refreshing it alone leaves `conf/Wyn.conf`
|
|
1134
|
+
// exactly as it is instead of round-tripping it through the XML builder.
|
|
1135
|
+
const configurationChanged = plans.some(plan => plan.entries.length > 0 || plan.configurationAssets !== undefined || plan.configurationLink !== undefined);
|
|
1136
|
+
onProgress?.(configurationChanged ? 'Writing configuration, manifest and registry record' : 'Writing manifest and registry record');
|
|
1137
|
+
if (configurationChanged)
|
|
1092
1138
|
await writeAtomic(path.join(entry.root, 'conf', 'Wyn.conf'), configuration);
|
|
1093
1139
|
await writeJsonAtomic(path.join(entry.root, 'environment.lock.json'), nextManifest);
|
|
1094
1140
|
await this.registry.update(entries => {
|
|
@@ -1104,10 +1150,10 @@ class EnvironmentUpdater {
|
|
|
1104
1150
|
delete entries[index].localModules;
|
|
1105
1151
|
});
|
|
1106
1152
|
}
|
|
1107
|
-
const restartRequired = !dryRun &&
|
|
1153
|
+
const restartRequired = !dryRun && changes.length > 0;
|
|
1108
1154
|
if (restartRequired)
|
|
1109
|
-
onProgress?.(`${entry.name}:
|
|
1110
|
-
return { name: entry.name, root: entry.root, dryRun, changes, skipped, restartRequired, warnings, localTargets: plans.flatMap(plan => plan.local ?? [])
|
|
1155
|
+
onProgress?.(`${entry.name}: modules updated; restart the environment to load the change (no service was started).`);
|
|
1156
|
+
return { name: entry.name, root: entry.root, dryRun, changes, skipped, restartRequired, warnings, localTargets: plans.flatMap(plan => plan.local ?? []) };
|
|
1111
1157
|
}
|
|
1112
1158
|
finally {
|
|
1113
1159
|
if (workspace)
|