@guanjinke/artifact-client 1.11.0 → 1.12.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 +217 -217
- package/dist/cli/setup.js +2 -2
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli.js +0 -0
- package/dist/environment/builder.d.ts.map +1 -1
- package/dist/environment/builder.js +78 -2
- package/dist/environment/builder.js.map +1 -1
- package/dist/environment/catalog.d.ts +24 -0
- package/dist/environment/catalog.d.ts.map +1 -1
- package/dist/environment/catalog.js +23 -1
- package/dist/environment/catalog.js.map +1 -1
- package/dist/environment/deferred.d.ts +38 -0
- package/dist/environment/deferred.d.ts.map +1 -0
- package/dist/environment/deferred.js +133 -0
- package/dist/environment/deferred.js.map +1 -0
- package/dist/environment/package-resource.d.ts +95 -0
- package/dist/environment/package-resource.d.ts.map +1 -0
- package/dist/environment/package-resource.js +323 -0
- package/dist/environment/package-resource.js.map +1 -0
- package/dist/environment/plan.d.ts +7 -1
- package/dist/environment/plan.d.ts.map +1 -1
- package/dist/environment/plan.js +25 -5
- package/dist/environment/plan.js.map +1 -1
- package/dist/environment/resource.d.ts +36 -0
- package/dist/environment/resource.d.ts.map +1 -0
- package/dist/environment/resource.js +101 -0
- package/dist/environment/resource.js.map +1 -0
- package/dist/environment/types.d.ts +36 -8
- package/dist/environment/types.d.ts.map +1 -1
- package/dist/environment/types.js +5 -0
- package/dist/environment/types.js.map +1 -1
- package/dist/environment/update.d.ts.map +1 -1
- package/dist/environment/update.js +210 -15
- package/dist/environment/update.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ArtifactClient } from '../artifact-client';
|
|
2
|
-
export type EnvironmentModule = 'dashboard' | 'reporting' | 'data-monitoring' | 'analysis' | 'sheets' | 'documents' | 'collaboration';
|
|
2
|
+
export type EnvironmentModule = 'dashboard' | 'reporting' | 'data-monitoring' | 'analysis' | 'sheets' | 'documents' | 'collaboration' | 'patch';
|
|
3
3
|
export interface EnvironmentOptions {
|
|
4
4
|
root: string;
|
|
5
5
|
major: string;
|
|
@@ -24,9 +24,10 @@ export interface LayoutMapping {
|
|
|
24
24
|
* `resource` is a static tree deployed into the environment's web root for the
|
|
25
25
|
* Server to serve (the built-in `dashboard-resource` module's 3D scene and map
|
|
26
26
|
* assets). It carries no assembly, needs no `.runtimeconfig.json`, and adds
|
|
27
|
-
* nothing to `conf/Wyn.conf`.
|
|
27
|
+
* nothing to `conf/Wyn.conf`. `replace` overwrites one existing regular file
|
|
28
|
+
* instead of deploying a directory; the selectable `patch` module uses it.
|
|
28
29
|
*/
|
|
29
|
-
role: 'plugin' | 'worker' | 'server' | 'monitor' | 'service' | 'resource';
|
|
30
|
+
role: 'plugin' | 'worker' | 'server' | 'monitor' | 'service' | 'resource' | 'replace';
|
|
30
31
|
service?: string;
|
|
31
32
|
/**
|
|
32
33
|
* Edition this mapping belongs to. A mapping without one is deployed for every
|
|
@@ -40,21 +41,22 @@ export interface LayoutMapping {
|
|
|
40
41
|
interface EnvironmentArtifactBase {
|
|
41
42
|
module: string;
|
|
42
43
|
plugin: string;
|
|
43
|
-
version: string;
|
|
44
44
|
/**
|
|
45
45
|
* Remote object name of the artifact inside the version directory, or — for a
|
|
46
46
|
* directory-of-files module — the remote directory holding its payload files.
|
|
47
|
-
* Never a credential-bearing URL.
|
|
47
|
+
* Never a credential-bearing URL. A resolved deferred replacement records no
|
|
48
|
+
* path: the provider's path would expose a name the module hides.
|
|
48
49
|
*/
|
|
49
|
-
path
|
|
50
|
+
path?: string;
|
|
50
51
|
mappings: LayoutMapping[];
|
|
51
52
|
}
|
|
52
53
|
/**
|
|
53
54
|
* A module that publishes exactly one archive, downloaded and then extracted
|
|
54
55
|
* before its mappings are laid out. This is the shape every module but
|
|
55
|
-
* `builtin-samples` has.
|
|
56
|
+
* `builtin-samples` and `patch` has.
|
|
56
57
|
*/
|
|
57
58
|
export interface EnvironmentArtifact extends EnvironmentArtifactBase {
|
|
59
|
+
version: string;
|
|
58
60
|
/** File name of the single archive, relative to the version directory. */
|
|
59
61
|
name: string;
|
|
60
62
|
names?: undefined;
|
|
@@ -69,10 +71,36 @@ export interface EnvironmentArtifact extends EnvironmentArtifactBase {
|
|
|
69
71
|
* artifact can never describe this module and vice versa.
|
|
70
72
|
*/
|
|
71
73
|
export interface EnvironmentPayloadArtifact extends EnvironmentArtifactBase {
|
|
74
|
+
version: string;
|
|
72
75
|
names: string[];
|
|
73
76
|
name?: undefined;
|
|
74
77
|
}
|
|
75
|
-
|
|
78
|
+
/**
|
|
79
|
+
* A selectable module whose version is only known while an environment is
|
|
80
|
+
* assembled.
|
|
81
|
+
*
|
|
82
|
+
* `patch` replaces one file inside the Server package, and the version of that
|
|
83
|
+
* replacement is the version of the component the Server package itself depends
|
|
84
|
+
* on. Neither value exists at plan time: the plan carries this descriptor after
|
|
85
|
+
* all core modules, and the builder resolves it from the Server tree it has just
|
|
86
|
+
* laid out. The descriptor deliberately carries no mapping: the mapping names
|
|
87
|
+
* the file the module replaces, and the module's real identity must stay out of
|
|
88
|
+
* every public document.
|
|
89
|
+
*/
|
|
90
|
+
export interface EnvironmentDeferredArtifact {
|
|
91
|
+
module: string;
|
|
92
|
+
/** The module's public label, never the publishing plugin. */
|
|
93
|
+
plugin: string;
|
|
94
|
+
deferred: true;
|
|
95
|
+
path?: undefined;
|
|
96
|
+
version?: undefined;
|
|
97
|
+
name?: undefined;
|
|
98
|
+
names?: undefined;
|
|
99
|
+
mappings?: undefined;
|
|
100
|
+
}
|
|
101
|
+
export type PlannedArtifact = EnvironmentArtifact | EnvironmentPayloadArtifact | EnvironmentDeferredArtifact;
|
|
102
|
+
/** True for the plan descriptor whose version and payload resolve while assembling. */
|
|
103
|
+
export declare function isDeferredArtifact(artifact: PlannedArtifact): artifact is EnvironmentDeferredArtifact;
|
|
76
104
|
export type EnvironmentSchema = 'V91' | 'V100';
|
|
77
105
|
export interface EnvironmentPlan {
|
|
78
106
|
managed?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/environment/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/environment/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isDeferredArtifact = isDeferredArtifact;
|
|
4
|
+
/** True for the plan descriptor whose version and payload resolve while assembling. */
|
|
5
|
+
function isDeferredArtifact(artifact) {
|
|
6
|
+
return artifact.deferred === true;
|
|
7
|
+
}
|
|
3
8
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/environment/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/environment/types.ts"],"names":[],"mappings":";;AAwGA,gDAEC;AAHD,uFAAuF;AACvF,SAAgB,kBAAkB,CAAC,QAAyB;IAC1D,OAAQ,QAAmC,CAAC,QAAQ,KAAK,IAAI,CAAC;AAChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/environment/update.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/environment/update.ts"],"names":[],"mappings":"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"}
|
|
@@ -40,6 +40,7 @@ const fs = __importStar(require("fs/promises"));
|
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
42
42
|
const catalog_1 = require("./catalog");
|
|
43
|
+
const deferred_1 = require("./deferred");
|
|
43
44
|
const archive_1 = require("./archive");
|
|
44
45
|
const builder_1 = require("./builder");
|
|
45
46
|
const config_1 = require("./config");
|
|
@@ -93,7 +94,49 @@ const requiredPackageFiles = {
|
|
|
93
94
|
*/
|
|
94
95
|
const excludedCopyNames = (mapping) => mapping.role === 'server' ? [...serverOwnedFiles] : mapping.role === 'monitor' ? ['conf'] : [];
|
|
95
96
|
/**
|
|
96
|
-
*
|
|
97
|
+
* Download the payload of a deferred module.
|
|
98
|
+
*
|
|
99
|
+
* It is published as files, not as an archive: the provider keeps the relative
|
|
100
|
+
* sub-path and the file is deployed exactly as downloaded, so nothing is
|
|
101
|
+
* extracted. Exactly one object is downloaded for `patch`. The public label is
|
|
102
|
+
* the only name in progress output or in errors.
|
|
103
|
+
*/
|
|
104
|
+
async function stageReplacement(client, module, version, payloads, workspace, onProgress) {
|
|
105
|
+
const entry = catalog_1.catalog[module];
|
|
106
|
+
const label = (0, catalog_1.moduleLabel)(entry);
|
|
107
|
+
const download = path.join(workspace, 'download', label);
|
|
108
|
+
try {
|
|
109
|
+
await fs.mkdir(download, { recursive: true });
|
|
110
|
+
onProgress?.(`[${label}] ${label}/${version}: Downloading replacement`);
|
|
111
|
+
const files = [];
|
|
112
|
+
for (const payload of payloads) {
|
|
113
|
+
await client.downloadArtifact(entry.plugin, version, payload.name, download);
|
|
114
|
+
const source = path.join(download, ...payload.name.split('/'));
|
|
115
|
+
await (0, builder_1.regularFile)(source);
|
|
116
|
+
files.push({ name: payload.name, source, sha256: await (0, builder_1.hashFile)(source) });
|
|
117
|
+
}
|
|
118
|
+
onProgress?.(`[${label}] ${label}/${version}: Download complete; replacement ready`);
|
|
119
|
+
return { version, files };
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error instanceof Error && error.message.startsWith(`Module ${label} could not be applied:`))
|
|
123
|
+
throw error;
|
|
124
|
+
throw new Error(`Module update failed for ${label}/${version} (download or layout).`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** One environment-relative Server path inside a staged Server package. */
|
|
128
|
+
function stagedServerPath(staged, relative) {
|
|
129
|
+
const server = catalog_1.catalog.server.mappings.find(mapping => mapping.role === 'server');
|
|
130
|
+
const prefix = server ? `${server.destination}/` : '';
|
|
131
|
+
const inner = prefix && relative.startsWith(prefix) ? relative.slice(prefix.length) : relative;
|
|
132
|
+
return path.join(staged.expanded, ...(server?.source ? server.source.split('/') : []), ...inner.split('/'));
|
|
133
|
+
}
|
|
134
|
+
/** The version file inside a staged Server package, before it is laid out. */
|
|
135
|
+
function stagedServerFile(staged, module) {
|
|
136
|
+
const descriptor = catalog_1.catalog[module].deferred;
|
|
137
|
+
return stagedServerPath(staged, descriptor.versionFile);
|
|
138
|
+
}
|
|
139
|
+
/** The modules a selection may name, from the same table `create-env` and the web
|
|
97
140
|
* interface read.
|
|
98
141
|
*
|
|
99
142
|
* Reading it through the call keeps this module loadable on its own: the policy
|
|
@@ -185,6 +228,11 @@ function pushSpec(specs, seen, spec) {
|
|
|
185
228
|
}
|
|
186
229
|
else {
|
|
187
230
|
validateUpdatableModule(spec.module);
|
|
231
|
+
// A deferred module's version is always the one recorded by the deployed (or
|
|
232
|
+
// newly staged) Server package. Pinning a different one would either be
|
|
233
|
+
// ignored or produce a mismatched environment, so it is refused.
|
|
234
|
+
if (catalog_1.catalog[spec.module].deferred && spec.version !== undefined)
|
|
235
|
+
throw new Error(`Module ${spec.module} takes its version from the deployed Server package: a pinned version is not supported.`);
|
|
188
236
|
if (seen.has(allModules))
|
|
189
237
|
throw new Error('Module spec all cannot be combined with individual modules.');
|
|
190
238
|
const previous = seen.get(spec.module);
|
|
@@ -560,14 +608,17 @@ function environmentSchema(manifest, configuration) {
|
|
|
560
608
|
* that policy. Returns the warning text, or undefined when the package is
|
|
561
609
|
* complete.
|
|
562
610
|
*/
|
|
563
|
-
async function verifyStagedPackage(module, staged, schema) {
|
|
611
|
+
async function verifyStagedPackage(module, staged, schema, required = requiredPackageFiles[module] ?? []) {
|
|
564
612
|
const label = `${staged.plugin}/${staged.version}/${staged.name}`;
|
|
565
|
-
for (const name of
|
|
613
|
+
for (const name of required) {
|
|
566
614
|
try {
|
|
567
615
|
await (0, builder_1.regularFile)(path.join(staged.expanded, name));
|
|
568
616
|
}
|
|
617
|
+
// The Server's required file is the one the selectable patch replaces, so the
|
|
618
|
+
// message stays generic: its name must not be user-visible. Every other
|
|
619
|
+
// module's required file name is public and useful.
|
|
569
620
|
catch {
|
|
570
|
-
throw new Error(`Module update failed for ${label}: the package is missing ${name}.`);
|
|
621
|
+
throw new Error(`Module update failed for ${label}: the package is missing ${module === 'server' ? 'a required runtime file' : name}.`);
|
|
571
622
|
}
|
|
572
623
|
}
|
|
573
624
|
const relative = `xsd/${schema}/Wyn.conf.xsd`;
|
|
@@ -643,11 +694,17 @@ function mappingForAssembly(module, assembly) {
|
|
|
643
694
|
* explicitly, for a build whose file name matches no assembly at all.
|
|
644
695
|
*/
|
|
645
696
|
async function resolveLocalSource(entry, module, assembly, input) {
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
//
|
|
649
|
-
//
|
|
650
|
-
|
|
697
|
+
// A deferred module is a published replacement for one file the Server package
|
|
698
|
+
// owns; a developer build has no mapping the configuration could point at, so
|
|
699
|
+
// a local source is refused on its own terms before any path is looked at.
|
|
700
|
+
// The message names the module only.
|
|
701
|
+
if (catalog_1.catalog[module].deferred)
|
|
702
|
+
throw new Error(`Module ${module} is refreshed from its published artifact only and has no local source.`);
|
|
703
|
+
// Any built-in trailing module is never a developer-build target either: it
|
|
704
|
+
// ships a static asset tree no local DLL could stand in for. Both are refused
|
|
705
|
+
// as explicit specs before this point (one internal, one create-only); this
|
|
706
|
+
// guard is the safety net for a trailing module a user could select, whose
|
|
707
|
+
// remote specs would keep working.
|
|
651
708
|
if (plan_1.trailingModules.includes(module))
|
|
652
709
|
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\`).`);
|
|
653
710
|
const mappings = localMappings(module);
|
|
@@ -817,6 +874,21 @@ class EnvironmentUpdater {
|
|
|
817
874
|
onProgress?.(`${module} is refreshed with the Server package: its trees deploy inside the Server directory`);
|
|
818
875
|
}
|
|
819
876
|
}
|
|
877
|
+
// A Server refresh restores the unpatched file the Server package ships, so
|
|
878
|
+
// an installed deferred module is re-applied in the same call. Its version is
|
|
879
|
+
// resolved from the *new* package below, never from the old tree. Without a
|
|
880
|
+
// Server refresh it is only applied when the caller names it explicitly.
|
|
881
|
+
const autoReplaced = [];
|
|
882
|
+
if (remote.some(spec => spec.module === 'server')) {
|
|
883
|
+
for (const module of Object.keys(catalog_1.catalog)) {
|
|
884
|
+
if (!catalog_1.catalog[module].deferred || !installed.has(module) || parsed.some(spec => spec.module === module))
|
|
885
|
+
continue;
|
|
886
|
+
parsed.push({ module });
|
|
887
|
+
remote.push({ module });
|
|
888
|
+
autoReplaced.push(module);
|
|
889
|
+
onProgress?.(`${module} is re-applied with the Server package: the new package replaces the file it patches`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
820
892
|
// Smart documents cannot run without the collaboration service, which
|
|
821
893
|
// create-env deploys and declares in the Server configuration alongside
|
|
822
894
|
// documents. Installing documents into an environment with no collaboration
|
|
@@ -847,15 +919,57 @@ class EnvironmentUpdater {
|
|
|
847
919
|
try {
|
|
848
920
|
const resolved = new Map();
|
|
849
921
|
const staged = new Map();
|
|
922
|
+
const replacements = new Map();
|
|
850
923
|
if (remote.length) {
|
|
851
924
|
const client = clientFactory();
|
|
852
|
-
|
|
925
|
+
const direct = remote.filter(spec => !catalog_1.catalog[spec.module].deferred);
|
|
926
|
+
for (const spec of direct) {
|
|
853
927
|
onProgress?.(`Resolving ${catalog_1.catalog[spec.module].plugin}`);
|
|
854
928
|
resolved.set(spec.module, await resolveRemoteArtifact(client, entry, spec));
|
|
855
929
|
}
|
|
856
930
|
if (!dryRun)
|
|
857
|
-
for (const spec of
|
|
931
|
+
for (const spec of direct)
|
|
858
932
|
staged.set(spec.module, await stageRemoteArtifact(client, resolved.get(spec.module), editionOf(entry), workspace, onProgress));
|
|
933
|
+
// A deferred module resolves after the direct artifacts are staged, so a
|
|
934
|
+
// Server refresh contributes the *new* package's version. Without a
|
|
935
|
+
// Server refresh the already deployed tree is the authority. A dry-run
|
|
936
|
+
// that refreshes the Server cannot know that version without downloading
|
|
937
|
+
// the package, so it reports the deferred resolution and stops there.
|
|
938
|
+
for (const spec of remote.filter(candidate => catalog_1.catalog[candidate.module].deferred)) {
|
|
939
|
+
const module = spec.module;
|
|
940
|
+
const source = catalog_1.catalog[module];
|
|
941
|
+
const label = (0, catalog_1.moduleLabel)(source);
|
|
942
|
+
const server = staged.get('server');
|
|
943
|
+
let version;
|
|
944
|
+
if (server) {
|
|
945
|
+
version = await (0, deferred_1.deferredVersionAt)(source, stagedServerFile(server, module));
|
|
946
|
+
// The replacement must overwrite an existing regular file in the new
|
|
947
|
+
// package; checking the staged tree catches a package that no longer
|
|
948
|
+
// ships it before anything has been written.
|
|
949
|
+
for (const mapping of (0, deferred_1.replacementMappings)(source)) {
|
|
950
|
+
const file = stagedServerPath(server, mapping.destination);
|
|
951
|
+
const stat = await lstatOrUndefined(file);
|
|
952
|
+
if (!stat)
|
|
953
|
+
throw new Error(`Module ${label} could not be applied: the Server package holds no file to replace.`);
|
|
954
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
955
|
+
throw new Error(`Module ${label} could not be applied: the Server package holds no regular file to replace.`);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
else if (remote.some(candidate => candidate.module === 'server')) {
|
|
959
|
+
onProgress?.(`${label}: version resolves from the refreshed Server package at execution time`);
|
|
960
|
+
replacements.set(module, { version: '' });
|
|
961
|
+
continue;
|
|
962
|
+
}
|
|
963
|
+
else
|
|
964
|
+
version = await (0, deferred_1.deferredVersion)(source, entry.root);
|
|
965
|
+
const payloads = await (0, deferred_1.deferredPayload)(source, version, client);
|
|
966
|
+
if (dryRun) {
|
|
967
|
+
onProgress?.(`${label}/${version}: resolved from the deployed Server package; nothing downloaded`);
|
|
968
|
+
replacements.set(module, { version });
|
|
969
|
+
continue;
|
|
970
|
+
}
|
|
971
|
+
replacements.set(module, await stageReplacement(client, module, version, payloads, workspace, onProgress));
|
|
972
|
+
}
|
|
859
973
|
}
|
|
860
974
|
const configured = await fs.readFile(path.join(entry.root, 'conf', 'Wyn.conf'), 'utf8');
|
|
861
975
|
// A Server or Monitor package is only usable when it still serves the
|
|
@@ -867,11 +981,16 @@ class EnvironmentUpdater {
|
|
|
867
981
|
if (remote.some(spec => spec.module === 'server' || spec.module === 'monitoring') && !schema) {
|
|
868
982
|
throw new Error(`Module update for ${remote.filter(spec => spec.module === 'server' || spec.module === 'monitoring').map(spec => spec.module).join(' and ')} needs the configuration schema of ${entry.name}, which neither its manifest nor its conf/Wyn.conf records. Recreate the environment with create-env.`);
|
|
869
983
|
}
|
|
984
|
+
// A planned deferred module validates the exact Server file it replaces,
|
|
985
|
+
// with a message that names only the module; the Server's own required-file
|
|
986
|
+
// check would otherwise report the same missing file by its internal name.
|
|
987
|
+
const deferredPlanned = remote.some(spec => catalog_1.catalog[spec.module].deferred);
|
|
870
988
|
for (const spec of remote) {
|
|
871
989
|
const stagedArtifact = staged.get(spec.module);
|
|
872
990
|
if (!stagedArtifact || !schema || !(spec.module in requiredPackageFiles))
|
|
873
991
|
continue;
|
|
874
|
-
const
|
|
992
|
+
const required = spec.module === 'server' && deferredPlanned ? [] : requiredPackageFiles[spec.module];
|
|
993
|
+
const warning = await verifyStagedPackage(spec.module, stagedArtifact, schema, required);
|
|
875
994
|
if (warning)
|
|
876
995
|
warnings.push(warning);
|
|
877
996
|
}
|
|
@@ -879,9 +998,11 @@ class EnvironmentUpdater {
|
|
|
879
998
|
// A module may carry several local specs: plan it once, for all its targets.
|
|
880
999
|
// The built-in trailing modules are planned last, in the same order
|
|
881
1000
|
// `create-env` assembles: their trees live inside other modules'
|
|
882
|
-
// directories, so a Server replacement must not wipe them again.
|
|
883
|
-
|
|
884
|
-
|
|
1001
|
+
// directories, so a Server replacement must not wipe them again. A deferred
|
|
1002
|
+
// module runs after `server` even when the caller wrote it first: the Server
|
|
1003
|
+
// directory must hold the new package before it is patched.
|
|
1004
|
+
const rank = (module) => plan_1.trailingModules.includes(module) ? 2 : catalog_1.catalog[module].deferred ? 1 : 0;
|
|
1005
|
+
const planOrder = [...new Set(parsed.map(spec => spec.module))].sort((left, right) => rank(left) - rank(right));
|
|
885
1006
|
for (const module of planOrder) {
|
|
886
1007
|
const artifact = artifacts.find(candidate => candidate.module === module);
|
|
887
1008
|
const previous = moduleSources[module] ? { local: moduleSources[module].path } : { version: artifact?.version };
|
|
@@ -911,6 +1032,37 @@ class EnvironmentUpdater {
|
|
|
911
1032
|
});
|
|
912
1033
|
continue;
|
|
913
1034
|
}
|
|
1035
|
+
// A deferred module replaces one existing regular file and contributes
|
|
1036
|
+
// no directory, plugin, worker, service or runtime requirement. Its
|
|
1037
|
+
// version and payload were resolved above; nothing about the provider is
|
|
1038
|
+
// recorded, and the target is checked before any write.
|
|
1039
|
+
if (catalog_1.catalog[module].deferred) {
|
|
1040
|
+
const replacement = replacements.get(module);
|
|
1041
|
+
const label = (0, catalog_1.moduleLabel)(catalog_1.catalog[module]);
|
|
1042
|
+
const version = replacement?.version || undefined;
|
|
1043
|
+
const files = [];
|
|
1044
|
+
if (replacement?.files?.length) {
|
|
1045
|
+
const mappings = (0, deferred_1.replacementMappings)(catalog_1.catalog[module]);
|
|
1046
|
+
for (const file of replacement.files) {
|
|
1047
|
+
const matches = mappings.filter(mapping => mapping.source === file.name);
|
|
1048
|
+
if (matches.length !== 1)
|
|
1049
|
+
throw new Error(`Module ${label} could not be applied: the replacement target is not configured.`);
|
|
1050
|
+
const destination = installationDirectory(entry.root, matches[0].destination);
|
|
1051
|
+
const stat = await lstatOrUndefined(destination);
|
|
1052
|
+
if (!stat)
|
|
1053
|
+
throw new Error(`Module ${label} could not be applied: the Server tree holds no file to replace.`);
|
|
1054
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
1055
|
+
throw new Error(`Module ${label} could not be applied: the Server tree holds no regular file to replace.`);
|
|
1056
|
+
files.push({ source: file.source, destination, sha256: file.sha256 });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
plans.push({
|
|
1060
|
+
module, previous, next: version ? { version } : {},
|
|
1061
|
+
...(files.length ? { replacement: { version: version, files } } : {}),
|
|
1062
|
+
entries: [], copies: [], removals: [], preserved: [], copiedDirectories: [], removedDirectories: [],
|
|
1063
|
+
});
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
914
1066
|
const remoteArtifact = resolved.get(module);
|
|
915
1067
|
const stagedArtifact = staged.get(module);
|
|
916
1068
|
const copies = [];
|
|
@@ -1029,6 +1181,28 @@ class EnvironmentUpdater {
|
|
|
1029
1181
|
}
|
|
1030
1182
|
delete moduleSources[plan.module];
|
|
1031
1183
|
}
|
|
1184
|
+
if (plan.replacement) {
|
|
1185
|
+
// The record keeps the public label, the resolved version and the
|
|
1186
|
+
// payload's SHA256. No remote path and no mapping are written: both
|
|
1187
|
+
// would expose a name the module hides.
|
|
1188
|
+
const label = (0, catalog_1.moduleLabel)(catalog_1.catalog[plan.module]);
|
|
1189
|
+
const replacement = { module: plan.module, plugin: label, version: plan.replacement.version, name: label };
|
|
1190
|
+
const index = nextArtifacts.findIndex(candidate => candidate.module === plan.module);
|
|
1191
|
+
if (index < 0)
|
|
1192
|
+
nextArtifacts.push(replacement);
|
|
1193
|
+
else
|
|
1194
|
+
nextArtifacts[index] = { ...nextArtifacts[index], ...replacement };
|
|
1195
|
+
for (const file of plan.replacement.files) {
|
|
1196
|
+
const download = { plugin: label, version: plan.replacement.version, name: label, sha256: file.sha256 };
|
|
1197
|
+
const downloadIndex = nextDownloads.findIndex(candidate => candidate.plugin === label);
|
|
1198
|
+
if (downloadIndex < 0)
|
|
1199
|
+
nextDownloads.push(download);
|
|
1200
|
+
else
|
|
1201
|
+
nextDownloads[downloadIndex] = download;
|
|
1202
|
+
}
|
|
1203
|
+
requirements = requirements.filter(line => !line.endsWith(` (${plan.module})`));
|
|
1204
|
+
delete moduleSources[plan.module];
|
|
1205
|
+
}
|
|
1032
1206
|
if (plan.local)
|
|
1033
1207
|
moduleSources[plan.module] = manifestSource(plan.module, plan.local, moduleSources[plan.module]);
|
|
1034
1208
|
// First installing a module records it the way `create-env` would: a
|
|
@@ -1108,6 +1282,8 @@ class EnvironmentUpdater {
|
|
|
1108
1282
|
}
|
|
1109
1283
|
if (autoRefreshed.length)
|
|
1110
1284
|
warnings.push(`${autoRefreshed.join(', ')} was refreshed with the Server package: its trees live inside the Server directory and would otherwise be lost.`);
|
|
1285
|
+
if (autoReplaced.length)
|
|
1286
|
+
warnings.push(`${autoReplaced.join(', ')} was re-applied with the Server package: the new package ships the file it replaces, so it was applied again.`);
|
|
1111
1287
|
if (removedCreateOnly.length)
|
|
1112
1288
|
warnings.push(`${removedCreateOnly.join(', ')} was removed with the Server package: it is deployed only when an environment is created.`);
|
|
1113
1289
|
if (!dryRun) {
|
|
@@ -1132,6 +1308,25 @@ class EnvironmentUpdater {
|
|
|
1132
1308
|
await (0, builder_1.copyTree)(copy.source, copy.destination, copy.exclude);
|
|
1133
1309
|
for (const file of plan.preserved)
|
|
1134
1310
|
await fs.copyFile(file.source, path.join(file.directory, file.name));
|
|
1311
|
+
// The replacement is applied after the directory it lives in has been
|
|
1312
|
+
// copied, so a Server refresh cannot overwrite it again. The target is
|
|
1313
|
+
// re-checked here and every error names the module only.
|
|
1314
|
+
if (plan.replacement) {
|
|
1315
|
+
const label = (0, catalog_1.moduleLabel)(catalog_1.catalog[plan.module]);
|
|
1316
|
+
try {
|
|
1317
|
+
for (const file of plan.replacement.files) {
|
|
1318
|
+
const stat = await fs.lstat(file.destination).catch(() => undefined);
|
|
1319
|
+
if (!stat)
|
|
1320
|
+
throw new Error('missing');
|
|
1321
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
1322
|
+
throw new Error('not a regular file');
|
|
1323
|
+
await fs.copyFile(file.source, file.destination);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
catch {
|
|
1327
|
+
throw new Error(`Module ${label} could not be applied: the Server tree holds no regular file to replace.`);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1135
1330
|
if (plan.configurationAssets) {
|
|
1136
1331
|
onProgress?.(`Refreshing Monitor configuration assets in ${path.relative(entry.root, plan.configurationAssets.destination)}`);
|
|
1137
1332
|
await mergeConfigurationAssets(plan.configurationAssets.source, plan.configurationAssets.destination);
|