@jskit-ai/jskit-cli 0.2.134 → 0.2.136
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/package.json +4 -4
- package/src/server/cliRuntime/mutations/fileMutations.js +6 -2
- package/src/server/commandHandlers/appCommandCatalog.js +5 -4
- package/src/server/commandHandlers/appCommands/shared.js +117 -1
- package/src/server/commandHandlers/appCommands/updatePackages.js +582 -64
- package/src/server/commandHandlers/health.js +23 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.136",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.131",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.121",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.120",
|
|
26
26
|
"@vue/compiler-sfc": "^3.5.29",
|
|
27
27
|
"ts-morph": "^28.0.0",
|
|
28
28
|
"yaml": "^2.8.3"
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
loadMutationWhenConfigContext,
|
|
21
21
|
resolveAppRelativePathWithinRoot
|
|
22
22
|
} from "../ioAndMigrations.js";
|
|
23
|
+
import { normalizeRelativePosixPath } from "../localPackageSupport.js";
|
|
23
24
|
import {
|
|
24
25
|
interpolateFileMutationRecord,
|
|
25
26
|
renderTemplateFile,
|
|
@@ -39,7 +40,7 @@ async function prepareFileMutations(
|
|
|
39
40
|
const existingManagedFilesByPath = new Map();
|
|
40
41
|
for (const managedFileValue of ensureArray(existingManagedFiles)) {
|
|
41
42
|
const managedFile = ensureObject(managedFileValue);
|
|
42
|
-
const managedPath =
|
|
43
|
+
const managedPath = normalizeRelativePosixPath(managedFile.path);
|
|
43
44
|
if (!managedPath) {
|
|
44
45
|
continue;
|
|
45
46
|
}
|
|
@@ -205,7 +206,7 @@ async function applyFileMutations(
|
|
|
205
206
|
const existingManagedFilesByPath = new Map();
|
|
206
207
|
for (const managedFileValue of ensureArray(existingManagedFiles)) {
|
|
207
208
|
const managedFile = ensureObject(managedFileValue);
|
|
208
|
-
const managedPath =
|
|
209
|
+
const managedPath = normalizeRelativePosixPath(managedFile.path);
|
|
209
210
|
if (!managedPath) {
|
|
210
211
|
continue;
|
|
211
212
|
}
|
|
@@ -260,6 +261,7 @@ async function applyFileMutations(
|
|
|
260
261
|
managedFiles.push({
|
|
261
262
|
...existingManaged,
|
|
262
263
|
path: relativeTargetPath,
|
|
264
|
+
ownership: mutation.ownership,
|
|
263
265
|
preserveOnRemove: mutation.preserveOnRemove,
|
|
264
266
|
reason: mutation.reason || String(existingManaged.reason || ""),
|
|
265
267
|
category: mutation.category || String(existingManaged.category || ""),
|
|
@@ -271,6 +273,7 @@ async function applyFileMutations(
|
|
|
271
273
|
if (mutation.ownership === "app" && previous.exists && hashBuffer(previous.buffer) === renderedSourceHash) {
|
|
272
274
|
managedFiles.push({
|
|
273
275
|
path: relativeTargetPath,
|
|
276
|
+
ownership: mutation.ownership,
|
|
274
277
|
hash: renderedSourceHash,
|
|
275
278
|
hadPrevious: true,
|
|
276
279
|
previousContentBase64: previous.buffer.toString("base64"),
|
|
@@ -289,6 +292,7 @@ async function applyFileMutations(
|
|
|
289
292
|
|
|
290
293
|
managedFiles.push({
|
|
291
294
|
path: relativeTargetPath,
|
|
295
|
+
ownership: mutation.ownership,
|
|
292
296
|
hash: renderedSourceHash,
|
|
293
297
|
hadPrevious: previous.exists,
|
|
294
298
|
previousContentBase64: previous.exists ? previous.buffer.toString("base64") : "",
|
|
@@ -75,7 +75,7 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
75
75
|
}),
|
|
76
76
|
"update-packages": Object.freeze({
|
|
77
77
|
name: "update-packages",
|
|
78
|
-
summary: "Update
|
|
78
|
+
summary: "Update @jskit-ai dependencies across the app and its npm workspaces.",
|
|
79
79
|
usage: "jskit app update-packages [--registry <url>] [--dry-run]",
|
|
80
80
|
options: Object.freeze([
|
|
81
81
|
Object.freeze({
|
|
@@ -88,9 +88,10 @@ const APP_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
88
88
|
})
|
|
89
89
|
]),
|
|
90
90
|
defaults: Object.freeze([
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
91
|
+
"Root runtime, development, optional, and peer dependencies are installed at their exact latest registry versions.",
|
|
92
|
+
"JSKIT ranges in npm workspace manifests and package descriptors are aligned with the latest major release, then workspace resolutions are refreshed in the lockfile.",
|
|
93
|
+
"Installed packages whose descriptor versions changed are reapplied with their saved options before managed migrations and the composed CI workflow are refreshed.",
|
|
94
|
+
"App-owned files retain local edits through the normal package-update ownership rules."
|
|
94
95
|
])
|
|
95
96
|
}),
|
|
96
97
|
"sync-ci": Object.freeze({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { access, mkdir, readFile, readdir, rm, symlink } from "node:fs/promises";
|
|
4
4
|
|
|
@@ -80,6 +80,94 @@ function runExternalCommand(
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
function runExternalCommandAsync(
|
|
84
|
+
command,
|
|
85
|
+
args = [],
|
|
86
|
+
{
|
|
87
|
+
cwd = "",
|
|
88
|
+
env = {},
|
|
89
|
+
stdout,
|
|
90
|
+
stderr,
|
|
91
|
+
quiet = false,
|
|
92
|
+
createCliError
|
|
93
|
+
} = {}
|
|
94
|
+
) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
let capturedStdout = "";
|
|
97
|
+
let capturedStderr = "";
|
|
98
|
+
let settled = false;
|
|
99
|
+
let child;
|
|
100
|
+
|
|
101
|
+
const finish = (result) => {
|
|
102
|
+
if (settled) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
settled = true;
|
|
106
|
+
try {
|
|
107
|
+
resolve(ensureCommandSucceeded(result, command, {
|
|
108
|
+
createCliError,
|
|
109
|
+
cwd,
|
|
110
|
+
stdout,
|
|
111
|
+
stderr,
|
|
112
|
+
quiet: true
|
|
113
|
+
}));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
reject(error);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
child = spawn(command, Array.isArray(args) ? args : [], {
|
|
121
|
+
cwd: cwd || process.cwd(),
|
|
122
|
+
env: {
|
|
123
|
+
...process.env,
|
|
124
|
+
...env
|
|
125
|
+
},
|
|
126
|
+
shell: process.platform === "win32"
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
finish({
|
|
130
|
+
error,
|
|
131
|
+
status: null,
|
|
132
|
+
stdout: capturedStdout,
|
|
133
|
+
stderr: capturedStderr
|
|
134
|
+
});
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
child.stdout?.setEncoding("utf8");
|
|
139
|
+
child.stderr?.setEncoding("utf8");
|
|
140
|
+
child.stdout?.on("data", (chunk) => {
|
|
141
|
+
capturedStdout += chunk;
|
|
142
|
+
if (!quiet) {
|
|
143
|
+
stdout?.write(chunk);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
child.stderr?.on("data", (chunk) => {
|
|
147
|
+
capturedStderr += chunk;
|
|
148
|
+
if (!quiet) {
|
|
149
|
+
stderr?.write(chunk);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
child.once("error", (error) => {
|
|
153
|
+
finish({
|
|
154
|
+
error,
|
|
155
|
+
status: null,
|
|
156
|
+
stdout: capturedStdout,
|
|
157
|
+
stderr: capturedStderr
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
child.once("close", (status, signal) => {
|
|
161
|
+
finish({
|
|
162
|
+
error: signal ? new Error(`${command} terminated by signal ${signal}.`) : null,
|
|
163
|
+
status,
|
|
164
|
+
stdout: capturedStdout,
|
|
165
|
+
stderr: capturedStderr
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
83
171
|
function runExternalShellCommand(
|
|
84
172
|
commandText,
|
|
85
173
|
{
|
|
@@ -155,6 +243,32 @@ async function runLocalJskit(
|
|
|
155
243
|
});
|
|
156
244
|
}
|
|
157
245
|
|
|
246
|
+
async function runLocalJskitAsync(
|
|
247
|
+
appRoot,
|
|
248
|
+
args = [],
|
|
249
|
+
{
|
|
250
|
+
stdout,
|
|
251
|
+
stderr,
|
|
252
|
+
createCliError,
|
|
253
|
+
quiet = false
|
|
254
|
+
} = {}
|
|
255
|
+
) {
|
|
256
|
+
const localJskitBin = resolveLocalJskitBin(appRoot);
|
|
257
|
+
if (!(await fileExists(localJskitBin))) {
|
|
258
|
+
throw createCliError(`Local jskit binary not found at ${path.relative(appRoot, localJskitBin)}. Run npm install first.`, {
|
|
259
|
+
exitCode: 1
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return runExternalCommandAsync(localJskitBin, args, {
|
|
264
|
+
cwd: appRoot,
|
|
265
|
+
stdout,
|
|
266
|
+
stderr,
|
|
267
|
+
quiet,
|
|
268
|
+
createCliError
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
158
272
|
async function resolveLocalRepoRoot({ appRoot = "", explicitRepoRoot = "" } = {}) {
|
|
159
273
|
const explicit = normalizeText(explicitRepoRoot);
|
|
160
274
|
if (explicit) {
|
|
@@ -295,10 +409,12 @@ export {
|
|
|
295
409
|
normalizeText,
|
|
296
410
|
isTruthyFlag,
|
|
297
411
|
runExternalCommand,
|
|
412
|
+
runExternalCommandAsync,
|
|
298
413
|
runExternalShellCommand,
|
|
299
414
|
formatUtcReleaseTimestamp,
|
|
300
415
|
resolveLocalJskitBin,
|
|
301
416
|
runLocalJskit,
|
|
417
|
+
runLocalJskitAsync,
|
|
302
418
|
resolveLocalRepoRoot,
|
|
303
419
|
discoverLocalPackageMap,
|
|
304
420
|
linkPackageBinEntries,
|
|
@@ -1,20 +1,70 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
runExternalCommandAsync,
|
|
6
|
+
runLocalJskitAsync
|
|
7
|
+
} from "./shared.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureObject,
|
|
10
|
+
sortStrings
|
|
11
|
+
} from "../../shared/collectionUtils.js";
|
|
12
|
+
|
|
13
|
+
const DEPENDENCY_SECTIONS = Object.freeze([
|
|
14
|
+
Object.freeze({
|
|
15
|
+
installArgs: Object.freeze(["--save-exact"]),
|
|
16
|
+
label: "runtime",
|
|
17
|
+
name: "dependencies"
|
|
18
|
+
}),
|
|
19
|
+
Object.freeze({
|
|
20
|
+
installArgs: Object.freeze(["--save-dev", "--save-exact"]),
|
|
21
|
+
label: "development",
|
|
22
|
+
name: "devDependencies"
|
|
23
|
+
}),
|
|
24
|
+
Object.freeze({
|
|
25
|
+
installArgs: Object.freeze(["--save-optional", "--save-exact"]),
|
|
26
|
+
label: "optional",
|
|
27
|
+
name: "optionalDependencies"
|
|
28
|
+
}),
|
|
29
|
+
Object.freeze({
|
|
30
|
+
installArgs: Object.freeze(["--save-peer", "--save-exact"]),
|
|
31
|
+
label: "peer",
|
|
32
|
+
name: "peerDependencies"
|
|
33
|
+
})
|
|
34
|
+
]);
|
|
35
|
+
const JSKIT_PACKAGE_PATTERN = /^@jskit-ai\/[a-z0-9._-]+$/iu;
|
|
36
|
+
const PROGRESS_INTERVAL_MS = 5_000;
|
|
2
37
|
|
|
3
38
|
function collectJskitPackageNames(packageMap = {}) {
|
|
4
|
-
return
|
|
5
|
-
.
|
|
6
|
-
|
|
39
|
+
return sortStrings(
|
|
40
|
+
Object.keys(packageMap && typeof packageMap === "object" ? packageMap : {})
|
|
41
|
+
.filter((name) => JSKIT_PACKAGE_PATTERN.test(String(name || "")))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function collectManifestJskitPackageNames(packageJson = {}) {
|
|
46
|
+
const packageNames = new Set();
|
|
47
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
48
|
+
for (const packageName of collectJskitPackageNames(packageJson?.[section.name])) {
|
|
49
|
+
packageNames.add(packageName);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return sortStrings([...packageNames]);
|
|
7
53
|
}
|
|
8
54
|
|
|
9
|
-
function
|
|
55
|
+
function resolveExactVersion(packageName = "", rawVersion = "", createCliError) {
|
|
10
56
|
const normalizedVersion = String(rawVersion || "").trim();
|
|
11
|
-
|
|
12
|
-
if (!match) {
|
|
57
|
+
if (!/^\d+\.\d+\.\d+(?:[.+-][0-9A-Za-z.-]+)?$/u.test(normalizedVersion)) {
|
|
13
58
|
throw createCliError(`Invalid latest version for ${packageName}: ${normalizedVersion || "<empty>"}.`, {
|
|
14
59
|
exitCode: 1
|
|
15
60
|
});
|
|
16
61
|
}
|
|
17
|
-
return
|
|
62
|
+
return normalizedVersion;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveMajorRange(packageName = "", version = "", createCliError) {
|
|
66
|
+
const exactVersion = resolveExactVersion(packageName, version, createCliError);
|
|
67
|
+
return `${exactVersion.slice(0, exactVersion.indexOf("."))}.x`;
|
|
18
68
|
}
|
|
19
69
|
|
|
20
70
|
function resolveRegistryArgs(registryUrl = "") {
|
|
@@ -25,86 +75,554 @@ function resolveRegistryArgs(registryUrl = "") {
|
|
|
25
75
|
return ["--registry", normalizedRegistryUrl];
|
|
26
76
|
}
|
|
27
77
|
|
|
28
|
-
function resolveInstallSpecs(packageNames = [],
|
|
29
|
-
return packageNames.map((packageName) => `${packageName}@${
|
|
78
|
+
function resolveInstallSpecs(packageNames = [], latestVersions = new Map()) {
|
|
79
|
+
return packageNames.map((packageName) => `${packageName}@${latestVersions.get(packageName)}`);
|
|
30
80
|
}
|
|
31
81
|
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
82
|
+
function collectChangedInstalledPackageIds(lock = {}, latestVersions = new Map()) {
|
|
83
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
84
|
+
return sortStrings(
|
|
85
|
+
[...latestVersions.entries()]
|
|
86
|
+
.filter(([packageId, targetVersion]) => {
|
|
87
|
+
if (!Object.prototype.hasOwnProperty.call(installedPackages, packageId)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const installedPackage = ensureObject(installedPackages[packageId]);
|
|
91
|
+
return String(installedPackage.version || "").trim() !== targetVersion;
|
|
92
|
+
})
|
|
93
|
+
.map(([packageId]) => packageId)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
38
96
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
97
|
+
async function reapplyChangedInstalledPackages({
|
|
98
|
+
appRoot,
|
|
99
|
+
createCliError,
|
|
100
|
+
dryRun,
|
|
101
|
+
latestVersions,
|
|
102
|
+
loadLockFile,
|
|
103
|
+
stderr,
|
|
104
|
+
stdout
|
|
105
|
+
}) {
|
|
106
|
+
const { lock } = await loadLockFile(appRoot);
|
|
107
|
+
const packageIds = collectChangedInstalledPackageIds(lock, latestVersions);
|
|
108
|
+
if (packageIds.length < 1) {
|
|
109
|
+
stdout?.write("[jskit:update] managed package state is already current.\n");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
46
112
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return
|
|
113
|
+
stdout?.write(`[jskit:update] managed packages requiring reapply: ${packageIds.join(", ")}.\n`);
|
|
114
|
+
if (dryRun) {
|
|
115
|
+
return;
|
|
50
116
|
}
|
|
51
117
|
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
118
|
+
for (const [index, packageId] of packageIds.entries()) {
|
|
119
|
+
const { lock: currentLock } = await loadLockFile(appRoot);
|
|
120
|
+
const currentVersion = String(
|
|
121
|
+
ensureObject(ensureObject(currentLock.installedPackages)[packageId]).version || ""
|
|
122
|
+
).trim();
|
|
123
|
+
if (currentVersion === latestVersions.get(packageId)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
stdout?.write(
|
|
127
|
+
`[jskit:update] reapplying managed package ${index + 1}/${packageIds.length}: ${packageId}.\n`
|
|
128
|
+
);
|
|
129
|
+
await runLocalJskitAsync(appRoot, ["update", "package", packageId], {
|
|
55
130
|
stdout,
|
|
56
131
|
stderr,
|
|
57
|
-
quiet: true,
|
|
58
132
|
createCliError
|
|
59
133
|
});
|
|
60
|
-
|
|
61
|
-
};
|
|
134
|
+
}
|
|
62
135
|
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
|
|
136
|
+
const { lock: updatedLock } = await loadLockFile(appRoot);
|
|
137
|
+
const remainingPackageIds = collectChangedInstalledPackageIds(updatedLock, latestVersions);
|
|
138
|
+
if (remainingPackageIds.length > 0) {
|
|
139
|
+
throw createCliError(
|
|
140
|
+
`Managed package reapply finished with stale lock versions: ${remainingPackageIds.join(", ")}.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
stdout?.write("[jskit:update] managed package state is current.\n");
|
|
144
|
+
}
|
|
66
145
|
|
|
67
|
-
|
|
68
|
-
|
|
146
|
+
function formatElapsedTime(elapsedMilliseconds = 0) {
|
|
147
|
+
const elapsedSeconds = Math.max(0, Math.floor(Number(elapsedMilliseconds) / 1000));
|
|
148
|
+
if (elapsedSeconds < 1) {
|
|
149
|
+
return "under 1s";
|
|
150
|
+
}
|
|
151
|
+
if (elapsedSeconds < 60) {
|
|
152
|
+
return `${elapsedSeconds}s`;
|
|
69
153
|
}
|
|
70
154
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
155
|
+
const minutes = Math.floor(elapsedSeconds / 60);
|
|
156
|
+
const seconds = elapsedSeconds % 60;
|
|
157
|
+
return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function runWithProgress(task, {
|
|
161
|
+
activity,
|
|
162
|
+
progressIntervalMs = PROGRESS_INTERVAL_MS,
|
|
163
|
+
stdout,
|
|
164
|
+
step
|
|
165
|
+
} = {}) {
|
|
166
|
+
const normalizedActivity = String(activity || "running update").trim();
|
|
167
|
+
const normalizedStep = String(step || "Update").trim();
|
|
168
|
+
const startedAt = Date.now();
|
|
169
|
+
stdout?.write(`[jskit:update] ${normalizedStep}: ${normalizedActivity}.\n`);
|
|
170
|
+
|
|
171
|
+
const progressTimer = setInterval(() => {
|
|
172
|
+
stdout?.write(
|
|
173
|
+
`[jskit:update] ${normalizedStep} is still running (${formatElapsedTime(Date.now() - startedAt)} elapsed): ${normalizedActivity}.\n`
|
|
174
|
+
);
|
|
175
|
+
}, progressIntervalMs);
|
|
176
|
+
progressTimer.unref();
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const result = await task();
|
|
180
|
+
stdout?.write(
|
|
181
|
+
`[jskit:update] ${normalizedStep} complete in ${formatElapsedTime(Date.now() - startedAt)}.\n`
|
|
182
|
+
);
|
|
183
|
+
return result;
|
|
184
|
+
} finally {
|
|
185
|
+
clearInterval(progressTimer);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function hasNpmWorkspaces(packageJson = {}) {
|
|
190
|
+
const workspaces = Array.isArray(packageJson?.workspaces)
|
|
191
|
+
? packageJson.workspaces
|
|
192
|
+
: packageJson?.workspaces?.packages;
|
|
193
|
+
return Array.isArray(workspaces) && workspaces.some((workspace) => String(workspace || "").trim());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function readJson(filePath) {
|
|
197
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function readOptionalFile(filePath) {
|
|
201
|
+
try {
|
|
202
|
+
return await readFile(filePath, "utf8");
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error?.code === "ENOENT") {
|
|
205
|
+
return "";
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function resolveWorkspaceDirectories({
|
|
212
|
+
appRoot,
|
|
213
|
+
createCliError,
|
|
214
|
+
packageJson,
|
|
215
|
+
stderr,
|
|
216
|
+
stdout
|
|
217
|
+
}) {
|
|
218
|
+
if (!hasNpmWorkspaces(packageJson)) {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const result = await runExternalCommandAsync("npm", ["query", ".workspace", "--json"], {
|
|
223
|
+
cwd: appRoot,
|
|
224
|
+
stdout,
|
|
225
|
+
stderr,
|
|
226
|
+
quiet: true,
|
|
227
|
+
createCliError
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
let entries;
|
|
231
|
+
try {
|
|
232
|
+
entries = JSON.parse(String(result.stdout || "[]"));
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw createCliError(`npm returned invalid workspace metadata: ${error instanceof Error ? error.message : String(error)}.`, {
|
|
235
|
+
exitCode: 1
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (!Array.isArray(entries)) {
|
|
239
|
+
throw createCliError("npm returned invalid workspace metadata: expected a JSON array.", {
|
|
240
|
+
exitCode: 1
|
|
78
241
|
});
|
|
79
242
|
}
|
|
80
243
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
244
|
+
const normalizedAppRoot = path.resolve(appRoot);
|
|
245
|
+
const workspaceDirectories = new Set();
|
|
246
|
+
for (const entry of entries) {
|
|
247
|
+
const location = String(entry?.location || "").trim();
|
|
248
|
+
if (!location) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const workspaceDirectory = path.resolve(normalizedAppRoot, location);
|
|
252
|
+
if (
|
|
253
|
+
workspaceDirectory === normalizedAppRoot ||
|
|
254
|
+
!workspaceDirectory.startsWith(`${normalizedAppRoot}${path.sep}`)
|
|
255
|
+
) {
|
|
256
|
+
throw createCliError(`npm returned a workspace outside the app root: ${location}.`, {
|
|
257
|
+
exitCode: 1
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
workspaceDirectories.add(workspaceDirectory);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return [...workspaceDirectories].sort((left, right) => left.localeCompare(right));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function descriptorJskitPackageNames(source = "") {
|
|
267
|
+
const packageNames = new Set();
|
|
268
|
+
const patterns = [
|
|
269
|
+
/(["'])(@jskit-ai\/[a-z0-9._-]+)\1\s*:\s*(["'])[^"']+\3/giu,
|
|
270
|
+
/(["'])(@jskit-ai\/[a-z0-9._-]+)\1\s*:\s*\{[^{}]*?(?:["'](?:version|value)["']|version|value)\s*:\s*(["'])[^"']+\3/giu
|
|
271
|
+
];
|
|
272
|
+
for (const pattern of patterns) {
|
|
273
|
+
for (const match of String(source || "").matchAll(pattern)) {
|
|
274
|
+
packageNames.add(match[2]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return [...packageNames].sort((left, right) => left.localeCompare(right));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function loadWorkspacePackages(workspaceDirectories = []) {
|
|
281
|
+
const workspacePackages = [];
|
|
282
|
+
for (const directory of workspaceDirectories) {
|
|
283
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
284
|
+
const descriptorPath = path.join(directory, "package.descriptor.mjs");
|
|
285
|
+
workspacePackages.push({
|
|
286
|
+
descriptorPath,
|
|
287
|
+
descriptorSource: await readOptionalFile(descriptorPath),
|
|
288
|
+
directory,
|
|
289
|
+
packageJson: await readJson(packageJsonPath),
|
|
290
|
+
packageJsonPath
|
|
88
291
|
});
|
|
89
292
|
}
|
|
293
|
+
return workspacePackages;
|
|
294
|
+
}
|
|
90
295
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
296
|
+
async function resolveLatestVersions(packageNames = [], latestVersions = new Map(), {
|
|
297
|
+
appRoot,
|
|
298
|
+
createCliError,
|
|
299
|
+
registryArgs,
|
|
300
|
+
stderr,
|
|
301
|
+
stdout
|
|
302
|
+
}) {
|
|
303
|
+
const unresolvedPackageNames = packageNames
|
|
304
|
+
.filter((packageName) => !latestVersions.has(packageName))
|
|
305
|
+
.sort((left, right) => left.localeCompare(right));
|
|
306
|
+
|
|
307
|
+
for (const [index, packageName] of unresolvedPackageNames.entries()) {
|
|
308
|
+
stdout?.write(
|
|
309
|
+
`[jskit:update] resolving latest package ${index + 1}/${unresolvedPackageNames.length}: ${packageName}.\n`
|
|
310
|
+
);
|
|
311
|
+
const result = await runExternalCommandAsync(
|
|
312
|
+
"npm",
|
|
313
|
+
["view", ...registryArgs, packageName, "version"],
|
|
314
|
+
{
|
|
315
|
+
cwd: appRoot,
|
|
316
|
+
stdout,
|
|
317
|
+
stderr,
|
|
318
|
+
quiet: true,
|
|
319
|
+
createCliError
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
const version = resolveExactVersion(packageName, result.stdout, createCliError);
|
|
323
|
+
latestVersions.set(packageName, version);
|
|
324
|
+
stdout?.write(`[jskit:update] resolved ${packageName}@${version}.\n`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return latestVersions;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function updateWorkspaceManifest(packageJson = {}, latestVersions = new Map(), createCliError) {
|
|
331
|
+
const updates = [];
|
|
332
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
333
|
+
const packageMap = packageJson?.[section.name];
|
|
334
|
+
for (const packageName of collectJskitPackageNames(packageMap)) {
|
|
335
|
+
const targetRange = resolveMajorRange(packageName, latestVersions.get(packageName), createCliError);
|
|
336
|
+
if (packageMap[packageName] === targetRange) {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
packageMap[packageName] = targetRange;
|
|
340
|
+
updates.push(`${packageName}@${targetRange}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return updates;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function updateWorkspaceDescriptor(source = "", latestVersions = new Map(), createCliError) {
|
|
347
|
+
const updates = [];
|
|
348
|
+
const replaceVersion = (match, keyQuote, packageName, separator, valueQuote, currentVersion) => {
|
|
349
|
+
const targetRange = resolveMajorRange(packageName, latestVersions.get(packageName), createCliError);
|
|
350
|
+
if (currentVersion === targetRange) {
|
|
351
|
+
return match;
|
|
352
|
+
}
|
|
353
|
+
updates.push(`${packageName}@${targetRange}`);
|
|
354
|
+
return `${keyQuote}${packageName}${keyQuote}${separator}${valueQuote}${targetRange}${valueQuote}`;
|
|
355
|
+
};
|
|
356
|
+
let nextSource = String(source || "").replace(
|
|
357
|
+
/(["'])(@jskit-ai\/[a-z0-9._-]+)\1(\s*:\s*)(["'])([^"']+)\4/giu,
|
|
358
|
+
replaceVersion
|
|
359
|
+
);
|
|
360
|
+
nextSource = nextSource.replace(
|
|
361
|
+
/(["'])(@jskit-ai\/[a-z0-9._-]+)\1(\s*:\s*\{[^{}]*?(?:["'](?:version|value)["']|version|value)\s*:\s*)(["'])([^"']+)\4/giu,
|
|
362
|
+
replaceVersion
|
|
363
|
+
);
|
|
364
|
+
return {
|
|
365
|
+
nextSource,
|
|
366
|
+
updates
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function synchronizeWorkspacePackageSpecs({
|
|
371
|
+
appRoot,
|
|
372
|
+
createCliError,
|
|
373
|
+
dryRun,
|
|
374
|
+
latestVersions,
|
|
375
|
+
packageJson,
|
|
376
|
+
registryArgs,
|
|
377
|
+
stderr,
|
|
378
|
+
stdout
|
|
379
|
+
}) {
|
|
380
|
+
const workspaceDirectories = await resolveWorkspaceDirectories({
|
|
381
|
+
appRoot,
|
|
382
|
+
createCliError,
|
|
383
|
+
packageJson,
|
|
384
|
+
stderr,
|
|
385
|
+
stdout
|
|
386
|
+
});
|
|
387
|
+
const workspacePackages = await loadWorkspacePackages(workspaceDirectories);
|
|
388
|
+
const workspaceJskitPackages = new Set();
|
|
389
|
+
for (const workspacePackage of workspacePackages) {
|
|
390
|
+
for (const packageName of collectManifestJskitPackageNames(workspacePackage.packageJson)) {
|
|
391
|
+
workspaceJskitPackages.add(packageName);
|
|
392
|
+
}
|
|
393
|
+
for (const packageName of descriptorJskitPackageNames(workspacePackage.descriptorSource)) {
|
|
394
|
+
workspaceJskitPackages.add(packageName);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const packageNames = [...workspaceJskitPackages].sort((left, right) => left.localeCompare(right));
|
|
399
|
+
await resolveLatestVersions(packageNames, latestVersions, {
|
|
400
|
+
appRoot,
|
|
401
|
+
createCliError,
|
|
402
|
+
registryArgs,
|
|
403
|
+
stderr,
|
|
404
|
+
stdout
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const changedFiles = [];
|
|
408
|
+
for (const workspacePackage of workspacePackages) {
|
|
409
|
+
const manifestUpdates = updateWorkspaceManifest(
|
|
410
|
+
workspacePackage.packageJson,
|
|
411
|
+
latestVersions,
|
|
96
412
|
createCliError
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
413
|
+
);
|
|
414
|
+
if (manifestUpdates.length > 0) {
|
|
415
|
+
const relativePath = path.relative(appRoot, workspacePackage.packageJsonPath).replaceAll(path.sep, "/");
|
|
416
|
+
changedFiles.push(relativePath);
|
|
417
|
+
stdout?.write(`[jskit:update] workspace manifest ${relativePath} -> ${manifestUpdates.join(", ")}\n`);
|
|
418
|
+
if (!dryRun) {
|
|
419
|
+
await writeFile(
|
|
420
|
+
workspacePackage.packageJsonPath,
|
|
421
|
+
`${JSON.stringify(workspacePackage.packageJson, null, 2)}\n`,
|
|
422
|
+
"utf8"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!workspacePackage.descriptorSource) {
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const descriptorResult = updateWorkspaceDescriptor(
|
|
431
|
+
workspacePackage.descriptorSource,
|
|
432
|
+
latestVersions,
|
|
102
433
|
createCliError
|
|
103
|
-
|
|
434
|
+
);
|
|
435
|
+
if (descriptorResult.updates.length < 1) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const relativePath = path.relative(appRoot, workspacePackage.descriptorPath).replaceAll(path.sep, "/");
|
|
439
|
+
changedFiles.push(relativePath);
|
|
440
|
+
stdout?.write(`[jskit:update] workspace descriptor ${relativePath} -> ${descriptorResult.updates.join(", ")}\n`);
|
|
441
|
+
if (!dryRun) {
|
|
442
|
+
await writeFile(workspacePackage.descriptorPath, descriptorResult.nextSource, "utf8");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
changedFiles,
|
|
448
|
+
packageNames
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function updateRootPackages({
|
|
453
|
+
appRoot,
|
|
454
|
+
createCliError,
|
|
455
|
+
dryRun,
|
|
456
|
+
latestVersions,
|
|
457
|
+
packageJson,
|
|
458
|
+
registryArgs,
|
|
459
|
+
stderr,
|
|
460
|
+
stdout
|
|
461
|
+
}) {
|
|
462
|
+
const rootPackageNames = collectManifestJskitPackageNames(packageJson);
|
|
463
|
+
if (rootPackageNames.length < 1) {
|
|
464
|
+
stdout?.write("[jskit:update] no root @jskit-ai packages found.\n");
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
await resolveLatestVersions(rootPackageNames, latestVersions, {
|
|
469
|
+
appRoot,
|
|
470
|
+
createCliError,
|
|
471
|
+
registryArgs,
|
|
472
|
+
stderr,
|
|
473
|
+
stdout
|
|
474
|
+
});
|
|
475
|
+
const dryRunArgs = dryRun ? ["--dry-run"] : [];
|
|
476
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
477
|
+
const packageNames = collectJskitPackageNames(packageJson?.[section.name]);
|
|
478
|
+
if (packageNames.length < 1) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const installSpecs = resolveInstallSpecs(packageNames, latestVersions);
|
|
482
|
+
stdout?.write(`[jskit:update] updating ${section.label} packages: ${installSpecs.join(" ")}\n`);
|
|
483
|
+
await runExternalCommandAsync(
|
|
484
|
+
"npm",
|
|
485
|
+
["install", ...section.installArgs, ...registryArgs, ...dryRunArgs, ...installSpecs],
|
|
486
|
+
{
|
|
487
|
+
cwd: appRoot,
|
|
488
|
+
stdout,
|
|
489
|
+
stderr,
|
|
490
|
+
createCliError
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
return true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function runAppUpdatePackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout, stderr }) {
|
|
498
|
+
const {
|
|
499
|
+
createCliError,
|
|
500
|
+
loadAppPackageJson,
|
|
501
|
+
loadLockFile,
|
|
502
|
+
assertAppManagedCiWorkflowUnmodified
|
|
503
|
+
} = ctx;
|
|
504
|
+
|
|
505
|
+
await assertAppManagedCiWorkflowUnmodified({ appRoot });
|
|
506
|
+
const { packageJson } = await loadAppPackageJson(appRoot);
|
|
507
|
+
const registryUrl = String(options?.inlineOptions?.registry || "").trim();
|
|
508
|
+
const registryArgs = resolveRegistryArgs(registryUrl);
|
|
509
|
+
const dryRun = options?.dryRun === true;
|
|
510
|
+
const latestVersions = new Map();
|
|
511
|
+
|
|
512
|
+
if (dryRun) {
|
|
513
|
+
stdout?.write("[jskit:update] dry-run mode enabled.\n");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
await runWithProgress(
|
|
517
|
+
async () => {
|
|
518
|
+
const updatedRootPackages = await updateRootPackages({
|
|
519
|
+
appRoot,
|
|
520
|
+
createCliError,
|
|
521
|
+
dryRun,
|
|
522
|
+
latestVersions,
|
|
523
|
+
packageJson,
|
|
524
|
+
registryArgs,
|
|
525
|
+
stderr,
|
|
526
|
+
stdout
|
|
527
|
+
});
|
|
528
|
+
if (!updatedRootPackages) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
await reapplyChangedInstalledPackages({
|
|
532
|
+
appRoot,
|
|
533
|
+
createCliError,
|
|
534
|
+
dryRun,
|
|
535
|
+
latestVersions,
|
|
536
|
+
loadLockFile,
|
|
537
|
+
stderr,
|
|
538
|
+
stdout
|
|
539
|
+
});
|
|
540
|
+
if (dryRun) {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
stdout?.write("[jskit:update] generating managed migrations for changed packages.\n");
|
|
544
|
+
await runLocalJskitAsync(appRoot, ["migrations", "changed"], {
|
|
545
|
+
stdout,
|
|
546
|
+
stderr,
|
|
547
|
+
createCliError
|
|
548
|
+
});
|
|
549
|
+
stdout?.write("[jskit:update] synchronizing the managed CI workflow.\n");
|
|
550
|
+
await runLocalJskitAsync(appRoot, ["app", "sync-ci"], {
|
|
551
|
+
stdout,
|
|
552
|
+
stderr,
|
|
553
|
+
createCliError
|
|
554
|
+
});
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
activity: dryRun
|
|
558
|
+
? "checking root JSKIT package updates"
|
|
559
|
+
: "updating root JSKIT packages and managed artifacts",
|
|
560
|
+
stdout,
|
|
561
|
+
step: "Step 1/3"
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
let workspaceResult = {
|
|
566
|
+
changedFiles: [],
|
|
567
|
+
packageNames: []
|
|
568
|
+
};
|
|
569
|
+
await runWithProgress(
|
|
570
|
+
async () => {
|
|
571
|
+
workspaceResult = await synchronizeWorkspacePackageSpecs({
|
|
572
|
+
appRoot,
|
|
573
|
+
createCliError,
|
|
574
|
+
dryRun,
|
|
575
|
+
latestVersions,
|
|
576
|
+
packageJson,
|
|
577
|
+
registryArgs,
|
|
578
|
+
stderr,
|
|
579
|
+
stdout
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
activity: "aligning JSKIT ranges in workspace manifests and descriptors",
|
|
584
|
+
stdout,
|
|
585
|
+
step: "Step 2/3"
|
|
586
|
+
}
|
|
587
|
+
);
|
|
588
|
+
stdout?.write(
|
|
589
|
+
`[jskit:update] Step 2/3 summary: ${workspaceResult.changedFiles.length} workspace files ${dryRun ? "would change" : "changed"}.\n`
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
if (dryRun) {
|
|
593
|
+
stdout?.write(
|
|
594
|
+
`[jskit:update] Step 3/3 skipped in dry-run mode: ${workspaceResult.packageNames.length} workspace JSKIT packages would be refreshed.\n`
|
|
595
|
+
);
|
|
596
|
+
} else if (workspaceResult.packageNames.length > 0) {
|
|
597
|
+
await runWithProgress(
|
|
598
|
+
() => runExternalCommandAsync(
|
|
599
|
+
"npm",
|
|
600
|
+
["update", ...registryArgs, "--workspaces", ...workspaceResult.packageNames],
|
|
601
|
+
{
|
|
602
|
+
cwd: appRoot,
|
|
603
|
+
stdout,
|
|
604
|
+
stderr,
|
|
605
|
+
createCliError
|
|
606
|
+
}
|
|
607
|
+
),
|
|
608
|
+
{
|
|
609
|
+
activity: `refreshing ${workspaceResult.packageNames.length} workspace JSKIT packages and updating the lockfile`,
|
|
610
|
+
stdout,
|
|
611
|
+
step: "Step 3/3"
|
|
612
|
+
}
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
stdout?.write("[jskit:update] Step 3/3 skipped: no workspace JSKIT packages were found.\n");
|
|
104
616
|
}
|
|
105
617
|
|
|
106
|
-
stdout
|
|
618
|
+
stdout?.write("[jskit:update] done.\n");
|
|
107
619
|
return 0;
|
|
108
620
|
}
|
|
109
621
|
|
|
110
|
-
export {
|
|
622
|
+
export {
|
|
623
|
+
collectChangedInstalledPackageIds,
|
|
624
|
+
formatElapsedTime,
|
|
625
|
+
reapplyChangedInstalledPackages,
|
|
626
|
+
runAppUpdatePackagesCommand,
|
|
627
|
+
runWithProgress
|
|
628
|
+
};
|
|
@@ -30,6 +30,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
30
30
|
inspectPackageOfferings,
|
|
31
31
|
fileExists,
|
|
32
32
|
normalizeRelativePath,
|
|
33
|
+
normalizeRelativePosixPath,
|
|
33
34
|
path
|
|
34
35
|
} = ctx;
|
|
35
36
|
|
|
@@ -1321,7 +1322,12 @@ function createHealthCommands(ctx = {}) {
|
|
|
1321
1322
|
}
|
|
1322
1323
|
}
|
|
1323
1324
|
|
|
1324
|
-
async function collectMainPackageFeatureLaneWarnings({
|
|
1325
|
+
async function collectMainPackageFeatureLaneWarnings({
|
|
1326
|
+
appRoot,
|
|
1327
|
+
appLocalRegistry,
|
|
1328
|
+
managedAppOwnedFilePaths,
|
|
1329
|
+
warnings
|
|
1330
|
+
}) {
|
|
1325
1331
|
const mainPackageEntry = [...appLocalRegistry.values()].find((packageEntry) => {
|
|
1326
1332
|
const packageId = String(packageEntry?.packageId || "").trim();
|
|
1327
1333
|
const relativeDir = String(packageEntry?.relativeDir || "").trim();
|
|
@@ -1343,7 +1349,8 @@ function createHealthCommands(ctx = {}) {
|
|
|
1343
1349
|
const extraDomainFiles = relativeServerFiles.filter(
|
|
1344
1350
|
(relativePath) =>
|
|
1345
1351
|
!MAIN_SERVER_BASELINE_RELATIVE_PATHS.has(relativePath) &&
|
|
1346
|
-
MAIN_SERVER_DOMAIN_FILE_PATTERN.test(relativePath)
|
|
1352
|
+
MAIN_SERVER_DOMAIN_FILE_PATTERN.test(relativePath) &&
|
|
1353
|
+
!managedAppOwnedFilePaths.has(normalizeRelativePath(appRoot, path.join(rootDir, relativePath)))
|
|
1347
1354
|
);
|
|
1348
1355
|
if (extraDomainFiles.length > 0) {
|
|
1349
1356
|
reasons.push(`extra server domain files: ${extraDomainFiles.join(", ")}`);
|
|
@@ -1438,7 +1445,13 @@ function createHealthCommands(ctx = {}) {
|
|
|
1438
1445
|
}
|
|
1439
1446
|
}
|
|
1440
1447
|
|
|
1441
|
-
async function collectFeatureLaneDoctorIssues({
|
|
1448
|
+
async function collectFeatureLaneDoctorIssues({
|
|
1449
|
+
appRoot,
|
|
1450
|
+
appLocalRegistry,
|
|
1451
|
+
managedAppOwnedFilePaths,
|
|
1452
|
+
issues,
|
|
1453
|
+
warnings
|
|
1454
|
+
}) {
|
|
1442
1455
|
const packageEntries = sortStrings([...appLocalRegistry.keys()])
|
|
1443
1456
|
.map((packageId) => appLocalRegistry.get(packageId))
|
|
1444
1457
|
.filter(Boolean);
|
|
@@ -1454,6 +1467,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
1454
1467
|
await collectMainPackageFeatureLaneWarnings({
|
|
1455
1468
|
appRoot,
|
|
1456
1469
|
appLocalRegistry,
|
|
1470
|
+
managedAppOwnedFilePaths,
|
|
1457
1471
|
warnings
|
|
1458
1472
|
});
|
|
1459
1473
|
await collectHandmadeFeatureLaneWarnings({
|
|
@@ -2125,6 +2139,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
2125
2139
|
const issues = [];
|
|
2126
2140
|
const warnings = [];
|
|
2127
2141
|
const installed = ensureObject(lock.installedPackages);
|
|
2142
|
+
const managedAppOwnedFilePaths = new Set();
|
|
2128
2143
|
await hydratePackageRegistryFromInstalledNodeModules({
|
|
2129
2144
|
appRoot,
|
|
2130
2145
|
packageRegistry: combinedPackageRegistry,
|
|
@@ -2148,7 +2163,10 @@ function createHealthCommands(ctx = {}) {
|
|
|
2148
2163
|
const managed = ensureObject(lockEntry.managed);
|
|
2149
2164
|
for (const fileChange of ensureArray(managed.files)) {
|
|
2150
2165
|
const changeRecord = ensureObject(fileChange);
|
|
2151
|
-
const relativePath =
|
|
2166
|
+
const relativePath = normalizeRelativePosixPath(changeRecord.path);
|
|
2167
|
+
if (changeRecord.ownership === "app" && relativePath) {
|
|
2168
|
+
managedAppOwnedFilePaths.add(relativePath);
|
|
2169
|
+
}
|
|
2152
2170
|
const absolutePath = path.join(appRoot, relativePath);
|
|
2153
2171
|
if (!(await fileExists(absolutePath))) {
|
|
2154
2172
|
issues.push(`${packageId}: managed file missing: ${relativePath}`);
|
|
@@ -2172,6 +2190,7 @@ function createHealthCommands(ctx = {}) {
|
|
|
2172
2190
|
await collectFeatureLaneDoctorIssues({
|
|
2173
2191
|
appRoot,
|
|
2174
2192
|
appLocalRegistry,
|
|
2193
|
+
managedAppOwnedFilePaths,
|
|
2175
2194
|
issues,
|
|
2176
2195
|
warnings
|
|
2177
2196
|
});
|