@nextclaw/app-runtime 0.10.0 → 0.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 +19 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +6 -1
- package/dist/package.js +1 -1
- package/dist/services/app-home.service.d.ts +4 -0
- package/dist/services/app-home.service.d.ts.map +1 -1
- package/dist/services/app-home.service.js +15 -1
- package/dist/services/app-home.service.js.map +1 -1
- package/dist/services/app-install-source.service.js +14 -1
- package/dist/services/app-install-source.service.js.map +1 -1
- package/dist/services/app-installation-filesystem.service.js +68 -0
- package/dist/services/app-installation-filesystem.service.js.map +1 -0
- package/dist/services/app-installation-integrity.service.d.ts +20 -0
- package/dist/services/app-installation-integrity.service.d.ts.map +1 -0
- package/dist/services/app-installation-integrity.service.js +84 -0
- package/dist/services/app-installation-integrity.service.js.map +1 -0
- package/dist/services/app-installation-lifecycle.service.js +110 -0
- package/dist/services/app-installation-lifecycle.service.js.map +1 -0
- package/dist/services/app-installation.service.d.ts +21 -11
- package/dist/services/app-installation.service.d.ts.map +1 -1
- package/dist/services/app-installation.service.js +262 -280
- package/dist/services/app-installation.service.js.map +1 -1
- package/dist/services/app-instance-inventory.service.d.ts +31 -0
- package/dist/services/app-instance-inventory.service.d.ts.map +1 -0
- package/dist/services/app-instance-inventory.service.js +136 -0
- package/dist/services/app-instance-inventory.service.js.map +1 -0
- package/dist/services/app-instance-storage.service.d.ts +55 -0
- package/dist/services/app-instance-storage.service.d.ts.map +1 -0
- package/dist/services/app-instance-storage.service.js +269 -0
- package/dist/services/app-instance-storage.service.js.map +1 -0
- package/dist/services/app-manifest.service.d.ts +5 -1
- package/dist/services/app-manifest.service.d.ts.map +1 -1
- package/dist/services/app-manifest.service.js +59 -3
- package/dist/services/app-manifest.service.js.map +1 -1
- package/dist/services/app-publish.service.js +1 -1
- package/dist/services/app-publish.service.js.map +1 -1
- package/dist/services/app-registry.service.d.ts +13 -3
- package/dist/services/app-registry.service.d.ts.map +1 -1
- package/dist/services/app-registry.service.js +143 -13
- package/dist/services/app-registry.service.js.map +1 -1
- package/dist/services/file-lock.service.d.ts +20 -0
- package/dist/services/file-lock.service.d.ts.map +1 -0
- package/dist/services/file-lock.service.js +156 -0
- package/dist/services/file-lock.service.js.map +1 -0
- package/dist/types/app-installation.types.d.ts +7 -0
- package/dist/types/app-installation.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.d.ts +22 -1
- package/dist/types/app-manifest.types.d.ts.map +1 -1
- package/dist/types/app-manifest.types.js.map +1 -1
- package/dist/types/app-registry.types.d.ts +7 -1
- package/dist/types/app-registry.types.d.ts.map +1 -1
- package/dist/types/app-storage.types.d.ts +64 -0
- package/dist/types/app-storage.types.d.ts.map +1 -0
- package/dist/types/app-storage.types.js +7 -0
- package/dist/types/app-storage.types.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { AppHomeService } from "./app-home.service.js";
|
|
2
|
+
import { FileLockService } from "./file-lock.service.js";
|
|
3
|
+
import { DEFAULT_APP_INSTANCE_ID } from "../types/app-storage.types.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { access, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
//#region src/services/app-instance-storage.service.ts
|
|
8
|
+
const STORAGE_METADATA_FILE = "metadata.json";
|
|
9
|
+
const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
10
|
+
var AppInstanceStorageService = class {
|
|
11
|
+
fileLockService = new FileLockService();
|
|
12
|
+
constructor(appHomeService = new AppHomeService()) {
|
|
13
|
+
this.appHomeService = appHomeService;
|
|
14
|
+
}
|
|
15
|
+
materializeDefaultInstance = async (params) => {
|
|
16
|
+
return await this.materialize({
|
|
17
|
+
...params,
|
|
18
|
+
instanceId: DEFAULT_APP_INSTANCE_ID,
|
|
19
|
+
instanceDirectory: this.appHomeService.getAppInstanceDirectory(params.appId, DEFAULT_APP_INSTANCE_ID)
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
materialize = async (params) => {
|
|
23
|
+
this.assertSafeId(params.appId, "appId");
|
|
24
|
+
this.assertSafeId(params.instanceId, "instanceId");
|
|
25
|
+
const instanceDirectory = path.resolve(params.instanceDirectory);
|
|
26
|
+
return await this.fileLockService.withLock(`${instanceDirectory}.lock`, async () => await this.materializeUnlocked({
|
|
27
|
+
...params,
|
|
28
|
+
instanceDirectory
|
|
29
|
+
}));
|
|
30
|
+
};
|
|
31
|
+
materializeUnlocked = async (params) => {
|
|
32
|
+
const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;
|
|
33
|
+
const existingInstance = await this.resolveExistingInstance({
|
|
34
|
+
appId,
|
|
35
|
+
dataSchemaVersion,
|
|
36
|
+
instanceDirectory,
|
|
37
|
+
instanceId,
|
|
38
|
+
publisherId
|
|
39
|
+
});
|
|
40
|
+
if (existingInstance) return existingInstance;
|
|
41
|
+
return await this.materializeNewInstance(params);
|
|
42
|
+
};
|
|
43
|
+
materializeNewInstance = async (params) => {
|
|
44
|
+
const { appId, dataSchemaVersion, instanceDirectory, instanceId, legacyDataDirectory: legacyDataPath, publisherId } = params;
|
|
45
|
+
await mkdir(path.dirname(instanceDirectory), { recursive: true });
|
|
46
|
+
const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;
|
|
47
|
+
const legacyDataDirectory = legacyDataPath ? path.resolve(legacyDataPath) : void 0;
|
|
48
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49
|
+
let movedLegacyData = false;
|
|
50
|
+
try {
|
|
51
|
+
const stagingStorage = this.buildContext(stagingDirectory, instanceId);
|
|
52
|
+
await mkdir(stagingDirectory, { recursive: false });
|
|
53
|
+
if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {
|
|
54
|
+
await rename(legacyDataDirectory, stagingStorage.dataDirectory);
|
|
55
|
+
movedLegacyData = true;
|
|
56
|
+
}
|
|
57
|
+
await this.ensureStorageDirectories(stagingStorage);
|
|
58
|
+
const metadata = {
|
|
59
|
+
schemaVersion: 1,
|
|
60
|
+
appId,
|
|
61
|
+
instanceId,
|
|
62
|
+
publisherId,
|
|
63
|
+
layoutVersion: 1,
|
|
64
|
+
createdAt,
|
|
65
|
+
...movedLegacyData ? {
|
|
66
|
+
migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67
|
+
legacyDataDirectory
|
|
68
|
+
} : {}
|
|
69
|
+
};
|
|
70
|
+
await writeFile(path.join(stagingDirectory, STORAGE_METADATA_FILE), `${JSON.stringify(metadata, null, 2)}\n`, {
|
|
71
|
+
encoding: "utf8",
|
|
72
|
+
mode: 384,
|
|
73
|
+
flag: "wx"
|
|
74
|
+
});
|
|
75
|
+
await rename(stagingDirectory, instanceDirectory);
|
|
76
|
+
return this.toInstanceRecord(metadata, this.buildContext(instanceDirectory, instanceId), dataSchemaVersion);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (movedLegacyData && legacyDataDirectory && !await this.pathExists(legacyDataDirectory)) {
|
|
79
|
+
const stagedDataDirectory = path.join(stagingDirectory, "data");
|
|
80
|
+
if (await this.pathExists(stagedDataDirectory)) await rename(stagedDataDirectory, legacyDataDirectory);
|
|
81
|
+
}
|
|
82
|
+
await rm(stagingDirectory, {
|
|
83
|
+
recursive: true,
|
|
84
|
+
force: true
|
|
85
|
+
});
|
|
86
|
+
if (await this.pathExists(instanceDirectory)) {
|
|
87
|
+
const racedMetadata = await this.readMetadata(instanceDirectory);
|
|
88
|
+
if (racedMetadata) {
|
|
89
|
+
this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);
|
|
90
|
+
const storage = this.buildContext(instanceDirectory, instanceId);
|
|
91
|
+
await this.ensureStorageDirectories(storage);
|
|
92
|
+
return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
buildLegacyDefaultInstance = (params) => {
|
|
99
|
+
const { appId, createdAt, dataDirectory } = params;
|
|
100
|
+
const instanceDirectory = this.appHomeService.getAppInstanceDirectory(appId, DEFAULT_APP_INSTANCE_ID);
|
|
101
|
+
return {
|
|
102
|
+
id: DEFAULT_APP_INSTANCE_ID,
|
|
103
|
+
publisherId: void 0,
|
|
104
|
+
storage: {
|
|
105
|
+
...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),
|
|
106
|
+
layout: "legacy",
|
|
107
|
+
dataDirectory: path.resolve(dataDirectory)
|
|
108
|
+
},
|
|
109
|
+
dataSchemaVersion: 1,
|
|
110
|
+
createdAt,
|
|
111
|
+
legacyDataDirectory: path.resolve(dataDirectory)
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
measureUsage = async (storage) => {
|
|
115
|
+
const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] = await Promise.all([
|
|
116
|
+
this.measureDirectory(storage.dataDirectory),
|
|
117
|
+
this.measureDirectory(storage.configDirectory),
|
|
118
|
+
this.measureDirectory(storage.stateDirectory),
|
|
119
|
+
this.measureDirectory(storage.cacheDirectory),
|
|
120
|
+
this.measureDirectory(storage.temporaryDirectory),
|
|
121
|
+
this.measureDirectory(storage.logsDirectory)
|
|
122
|
+
]);
|
|
123
|
+
return {
|
|
124
|
+
dataBytes,
|
|
125
|
+
configBytes,
|
|
126
|
+
stateBytes,
|
|
127
|
+
cacheBytes,
|
|
128
|
+
temporaryBytes,
|
|
129
|
+
logsBytes,
|
|
130
|
+
totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
inspect = async (params) => {
|
|
134
|
+
const { appId, instanceId, instanceDirectory: instancePath } = params;
|
|
135
|
+
this.assertSafeId(appId, "appId");
|
|
136
|
+
this.assertSafeId(instanceId, "instanceId");
|
|
137
|
+
const instanceDirectory = path.resolve(instancePath);
|
|
138
|
+
const metadata = await this.readMetadata(instanceDirectory);
|
|
139
|
+
if (!metadata) throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);
|
|
140
|
+
this.assertMetadataCoordinates(metadata, appId, instanceId);
|
|
141
|
+
return this.toInstanceRecord(metadata, this.buildContext(instanceDirectory, instanceId));
|
|
142
|
+
};
|
|
143
|
+
rollbackNewInstance = async (params) => {
|
|
144
|
+
const { instance, legacyDataDirectory: legacyDataPath } = params;
|
|
145
|
+
const instanceDirectory = instance.storage.instanceDirectory;
|
|
146
|
+
if (!await this.readMetadata(instanceDirectory)) return;
|
|
147
|
+
const legacyDataDirectory = legacyDataPath ? path.resolve(legacyDataPath) : void 0;
|
|
148
|
+
if (legacyDataDirectory && !await this.pathExists(legacyDataDirectory) && await this.pathExists(instance.storage.dataDirectory)) {
|
|
149
|
+
await mkdir(path.dirname(legacyDataDirectory), { recursive: true });
|
|
150
|
+
await rename(instance.storage.dataDirectory, legacyDataDirectory);
|
|
151
|
+
}
|
|
152
|
+
await rm(instanceDirectory, {
|
|
153
|
+
recursive: true,
|
|
154
|
+
force: true
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
resolveExistingInstance = async (params) => {
|
|
158
|
+
const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;
|
|
159
|
+
let metadata = await this.readMetadata(instanceDirectory);
|
|
160
|
+
if (!metadata) return;
|
|
161
|
+
this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);
|
|
162
|
+
if (publisherId && !metadata.publisherId) metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);
|
|
163
|
+
const storage = this.buildContext(instanceDirectory, instanceId);
|
|
164
|
+
await this.ensureStorageDirectories(storage);
|
|
165
|
+
return this.toInstanceRecord(metadata, storage, dataSchemaVersion);
|
|
166
|
+
};
|
|
167
|
+
buildContext = (instanceDirectory, instanceId) => ({
|
|
168
|
+
layout: "instance-v1",
|
|
169
|
+
layoutVersion: 1,
|
|
170
|
+
instanceId,
|
|
171
|
+
instanceDirectory,
|
|
172
|
+
dataDirectory: path.join(instanceDirectory, "data"),
|
|
173
|
+
configDirectory: path.join(instanceDirectory, "config"),
|
|
174
|
+
stateDirectory: path.join(instanceDirectory, "state"),
|
|
175
|
+
cacheDirectory: path.join(instanceDirectory, "cache"),
|
|
176
|
+
temporaryDirectory: path.join(instanceDirectory, "tmp"),
|
|
177
|
+
logsDirectory: path.join(instanceDirectory, "logs")
|
|
178
|
+
});
|
|
179
|
+
ensureStorageDirectories = async (storage) => {
|
|
180
|
+
await Promise.all([
|
|
181
|
+
mkdir(storage.dataDirectory, { recursive: true }),
|
|
182
|
+
mkdir(storage.configDirectory, { recursive: true }),
|
|
183
|
+
mkdir(storage.stateDirectory, { recursive: true }),
|
|
184
|
+
mkdir(storage.cacheDirectory, { recursive: true }),
|
|
185
|
+
mkdir(storage.temporaryDirectory, { recursive: true }),
|
|
186
|
+
mkdir(storage.logsDirectory, { recursive: true })
|
|
187
|
+
]);
|
|
188
|
+
};
|
|
189
|
+
readMetadata = async (instanceDirectory) => {
|
|
190
|
+
try {
|
|
191
|
+
const raw = JSON.parse(await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), "utf8"));
|
|
192
|
+
if (raw.schemaVersion !== 1 || raw.layoutVersion !== 1 || typeof raw.appId !== "string" || typeof raw.instanceId !== "string" || raw.publisherId !== void 0 && typeof raw.publisherId !== "string" || typeof raw.createdAt !== "string") throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);
|
|
193
|
+
return raw;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (this.isMissingFileError(error)) return;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
toInstanceRecord = (metadata, storage, dataSchemaVersion = 1) => ({
|
|
200
|
+
id: metadata.instanceId,
|
|
201
|
+
publisherId: metadata.publisherId,
|
|
202
|
+
storage,
|
|
203
|
+
dataSchemaVersion,
|
|
204
|
+
createdAt: metadata.createdAt,
|
|
205
|
+
migratedAt: metadata.migratedAt,
|
|
206
|
+
legacyDataDirectory: metadata.legacyDataDirectory
|
|
207
|
+
});
|
|
208
|
+
assertMetadataIdentity = (metadata, appId, instanceId, publisherId) => {
|
|
209
|
+
this.assertMetadataCoordinates(metadata, appId, instanceId);
|
|
210
|
+
if (metadata.publisherId && metadata.publisherId !== publisherId) throw new Error(`App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? "未验证本地来源"} 接管。`);
|
|
211
|
+
};
|
|
212
|
+
assertMetadataCoordinates = (metadata, appId, instanceId) => {
|
|
213
|
+
if (metadata.appId !== appId || metadata.instanceId !== instanceId) throw new Error(`App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`);
|
|
214
|
+
};
|
|
215
|
+
bindPublisher = async (instanceDirectory, metadata, publisherId) => {
|
|
216
|
+
const nextMetadata = {
|
|
217
|
+
...metadata,
|
|
218
|
+
publisherId
|
|
219
|
+
};
|
|
220
|
+
const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);
|
|
221
|
+
const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;
|
|
222
|
+
try {
|
|
223
|
+
await writeFile(temporaryPath, `${JSON.stringify(nextMetadata, null, 2)}\n`, {
|
|
224
|
+
encoding: "utf8",
|
|
225
|
+
mode: 384,
|
|
226
|
+
flag: "wx"
|
|
227
|
+
});
|
|
228
|
+
await rename(temporaryPath, metadataPath);
|
|
229
|
+
return nextMetadata;
|
|
230
|
+
} finally {
|
|
231
|
+
await rm(temporaryPath, { force: true });
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
assertSafeId = (value, field) => {
|
|
235
|
+
if (!SAFE_ID_PATTERN.test(value)) throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);
|
|
236
|
+
};
|
|
237
|
+
measureDirectory = async (directory) => {
|
|
238
|
+
try {
|
|
239
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
240
|
+
let bytes = 0;
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
const entryPath = path.join(directory, entry.name);
|
|
243
|
+
if (entry.isSymbolicLink()) continue;
|
|
244
|
+
if (entry.isDirectory()) {
|
|
245
|
+
bytes += await this.measureDirectory(entryPath);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (entry.isFile()) bytes += (await stat(entryPath)).size;
|
|
249
|
+
}
|
|
250
|
+
return bytes;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (this.isMissingFileError(error)) return 0;
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
pathExists = async (targetPath) => {
|
|
257
|
+
try {
|
|
258
|
+
await access(targetPath);
|
|
259
|
+
return true;
|
|
260
|
+
} catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
isMissingFileError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
265
|
+
};
|
|
266
|
+
//#endregion
|
|
267
|
+
export { AppInstanceStorageService };
|
|
268
|
+
|
|
269
|
+
//# sourceMappingURL=app-instance-storage.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-instance-storage.service.js","names":[],"sources":["../../src/services/app-instance-storage.service.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { access, mkdir, readdir, readFile, rename, rm, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AppHomeService } from \"#app-runtime/services/app-home.service.js\";\nimport { FileLockService } from \"#app-runtime/services/file-lock.service.js\";\nimport {\n APP_STORAGE_LAYOUT_VERSION,\n DEFAULT_APP_INSTANCE_ID,\n type AppInstanceMetadata,\n type AppInstanceRecord,\n type AppStorageContext,\n type AppStorageUsage,\n} from \"#app-runtime/types/app-storage.types.js\";\n\nconst STORAGE_METADATA_FILE = \"metadata.json\";\nconst SAFE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\n\nexport class AppInstanceStorageService {\n private readonly fileLockService = new FileLockService();\n\n constructor(private readonly appHomeService: AppHomeService = new AppHomeService()) {}\n\n materializeDefaultInstance = async (params: {\n appId: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n return await this.materialize({\n ...params,\n instanceId: DEFAULT_APP_INSTANCE_ID,\n instanceDirectory: this.appHomeService.getAppInstanceDirectory(\n params.appId,\n DEFAULT_APP_INSTANCE_ID,\n ),\n });\n };\n\n materialize = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n this.assertSafeId(params.appId, \"appId\");\n this.assertSafeId(params.instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(params.instanceDirectory);\n return await this.fileLockService.withLock(\n `${instanceDirectory}.lock`,\n async () => await this.materializeUnlocked({ ...params, instanceDirectory }),\n );\n };\n\n private materializeUnlocked = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n } = params;\n const existingInstance = await this.resolveExistingInstance({\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n publisherId,\n });\n if (existingInstance) {\n return existingInstance;\n }\n\n return await this.materializeNewInstance(params);\n };\n\n private materializeNewInstance = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n publisherId?: string;\n legacyDataDirectory?: string;\n dataSchemaVersion?: number;\n }): Promise<AppInstanceRecord> => {\n const {\n appId,\n dataSchemaVersion,\n instanceDirectory,\n instanceId,\n legacyDataDirectory: legacyDataPath,\n publisherId,\n } = params;\n\n await mkdir(path.dirname(instanceDirectory), { recursive: true });\n const stagingDirectory = `${instanceDirectory}.migrating-${randomUUID()}`;\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n const createdAt = new Date().toISOString();\n let movedLegacyData = false;\n try {\n const stagingStorage = this.buildContext(stagingDirectory, instanceId);\n await mkdir(stagingDirectory, { recursive: false });\n if (legacyDataDirectory && await this.pathExists(legacyDataDirectory)) {\n await rename(legacyDataDirectory, stagingStorage.dataDirectory);\n movedLegacyData = true;\n }\n await this.ensureStorageDirectories(stagingStorage);\n const metadata: AppInstanceMetadata = {\n schemaVersion: 1,\n appId,\n instanceId,\n publisherId,\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n createdAt,\n ...(movedLegacyData\n ? { migratedAt: new Date().toISOString(), legacyDataDirectory }\n : {}),\n };\n await writeFile(\n path.join(stagingDirectory, STORAGE_METADATA_FILE),\n `${JSON.stringify(metadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(stagingDirectory, instanceDirectory);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n dataSchemaVersion,\n );\n } catch (error) {\n if (\n movedLegacyData &&\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory)\n ) {\n const stagedDataDirectory = path.join(stagingDirectory, \"data\");\n if (await this.pathExists(stagedDataDirectory)) {\n await rename(stagedDataDirectory, legacyDataDirectory);\n }\n }\n await rm(stagingDirectory, { recursive: true, force: true });\n if (await this.pathExists(instanceDirectory)) {\n const racedMetadata = await this.readMetadata(instanceDirectory);\n if (racedMetadata) {\n this.assertMetadataIdentity(racedMetadata, appId, instanceId, publisherId);\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(racedMetadata, storage, dataSchemaVersion);\n }\n }\n throw error;\n }\n };\n\n buildLegacyDefaultInstance = (params: {\n appId: string;\n dataDirectory: string;\n createdAt: string;\n }): AppInstanceRecord => {\n const { appId, createdAt, dataDirectory } = params;\n const instanceDirectory = this.appHomeService.getAppInstanceDirectory(\n appId,\n DEFAULT_APP_INSTANCE_ID,\n );\n return {\n id: DEFAULT_APP_INSTANCE_ID,\n publisherId: undefined,\n storage: {\n ...this.buildContext(instanceDirectory, DEFAULT_APP_INSTANCE_ID),\n layout: \"legacy\",\n dataDirectory: path.resolve(dataDirectory),\n },\n dataSchemaVersion: 1,\n createdAt,\n legacyDataDirectory: path.resolve(dataDirectory),\n };\n };\n\n measureUsage = async (storage: AppStorageContext): Promise<AppStorageUsage> => {\n const [dataBytes, configBytes, stateBytes, cacheBytes, temporaryBytes, logsBytes] =\n await Promise.all([\n this.measureDirectory(storage.dataDirectory),\n this.measureDirectory(storage.configDirectory),\n this.measureDirectory(storage.stateDirectory),\n this.measureDirectory(storage.cacheDirectory),\n this.measureDirectory(storage.temporaryDirectory),\n this.measureDirectory(storage.logsDirectory),\n ]);\n return {\n dataBytes,\n configBytes,\n stateBytes,\n cacheBytes,\n temporaryBytes,\n logsBytes,\n totalBytes: dataBytes + configBytes + stateBytes + cacheBytes + temporaryBytes + logsBytes,\n };\n };\n\n inspect = async (params: {\n appId: string;\n instanceId: string;\n instanceDirectory: string;\n }): Promise<AppInstanceRecord> => {\n const { appId, instanceId, instanceDirectory: instancePath } = params;\n this.assertSafeId(appId, \"appId\");\n this.assertSafeId(instanceId, \"instanceId\");\n const instanceDirectory = path.resolve(instancePath);\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n throw new Error(`App Instance metadata 不存在:${instanceDirectory}`);\n }\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n return this.toInstanceRecord(\n metadata,\n this.buildContext(instanceDirectory, instanceId),\n );\n };\n\n rollbackNewInstance = async (params: {\n instance: AppInstanceRecord;\n legacyDataDirectory?: string;\n }): Promise<void> => {\n const { instance, legacyDataDirectory: legacyDataPath } = params;\n const instanceDirectory = instance.storage.instanceDirectory;\n const metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return;\n }\n const legacyDataDirectory = legacyDataPath\n ? path.resolve(legacyDataPath)\n : undefined;\n if (\n legacyDataDirectory &&\n !await this.pathExists(legacyDataDirectory) &&\n await this.pathExists(instance.storage.dataDirectory)\n ) {\n await mkdir(path.dirname(legacyDataDirectory), { recursive: true });\n await rename(instance.storage.dataDirectory, legacyDataDirectory);\n }\n await rm(instanceDirectory, { recursive: true, force: true });\n };\n\n private resolveExistingInstance = async (params: {\n appId: string;\n dataSchemaVersion?: number;\n instanceDirectory: string;\n instanceId: string;\n publisherId?: string;\n }): Promise<AppInstanceRecord | undefined> => {\n const { appId, dataSchemaVersion, instanceDirectory, instanceId, publisherId } = params;\n let metadata = await this.readMetadata(instanceDirectory);\n if (!metadata) {\n return undefined;\n }\n this.assertMetadataIdentity(metadata, appId, instanceId, publisherId);\n if (publisherId && !metadata.publisherId) {\n metadata = await this.bindPublisher(instanceDirectory, metadata, publisherId);\n }\n const storage = this.buildContext(instanceDirectory, instanceId);\n await this.ensureStorageDirectories(storage);\n return this.toInstanceRecord(metadata, storage, dataSchemaVersion);\n };\n\n private buildContext = (\n instanceDirectory: string,\n instanceId: string,\n ): AppStorageContext => ({\n layout: \"instance-v1\",\n layoutVersion: APP_STORAGE_LAYOUT_VERSION,\n instanceId,\n instanceDirectory,\n dataDirectory: path.join(instanceDirectory, \"data\"),\n configDirectory: path.join(instanceDirectory, \"config\"),\n stateDirectory: path.join(instanceDirectory, \"state\"),\n cacheDirectory: path.join(instanceDirectory, \"cache\"),\n temporaryDirectory: path.join(instanceDirectory, \"tmp\"),\n logsDirectory: path.join(instanceDirectory, \"logs\"),\n });\n\n private ensureStorageDirectories = async (storage: AppStorageContext): Promise<void> => {\n await Promise.all([\n mkdir(storage.dataDirectory, { recursive: true }),\n mkdir(storage.configDirectory, { recursive: true }),\n mkdir(storage.stateDirectory, { recursive: true }),\n mkdir(storage.cacheDirectory, { recursive: true }),\n mkdir(storage.temporaryDirectory, { recursive: true }),\n mkdir(storage.logsDirectory, { recursive: true }),\n ]);\n };\n\n private readMetadata = async (\n instanceDirectory: string,\n ): Promise<AppInstanceMetadata | undefined> => {\n try {\n const raw = JSON.parse(\n await readFile(path.join(instanceDirectory, STORAGE_METADATA_FILE), \"utf8\"),\n ) as Partial<AppInstanceMetadata>;\n if (\n raw.schemaVersion !== 1 ||\n raw.layoutVersion !== APP_STORAGE_LAYOUT_VERSION ||\n typeof raw.appId !== \"string\" ||\n typeof raw.instanceId !== \"string\" ||\n (raw.publisherId !== undefined && typeof raw.publisherId !== \"string\") ||\n typeof raw.createdAt !== \"string\"\n ) {\n throw new Error(`无效的 App Instance metadata:${instanceDirectory}`);\n }\n return raw as AppInstanceMetadata;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return undefined;\n }\n throw error;\n }\n };\n\n private toInstanceRecord = (\n metadata: AppInstanceMetadata,\n storage: AppStorageContext,\n dataSchemaVersion = 1,\n ): AppInstanceRecord => ({\n id: metadata.instanceId,\n publisherId: metadata.publisherId,\n storage,\n dataSchemaVersion,\n createdAt: metadata.createdAt,\n migratedAt: metadata.migratedAt,\n legacyDataDirectory: metadata.legacyDataDirectory,\n });\n\n private assertMetadataIdentity = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n publisherId?: string,\n ): void => {\n this.assertMetadataCoordinates(metadata, appId, instanceId);\n if (metadata.publisherId && metadata.publisherId !== publisherId) {\n throw new Error(\n `App Instance ${appId}/${instanceId} 已绑定发布者 ${metadata.publisherId},拒绝由 ${publisherId ?? \"未验证本地来源\"} 接管。`,\n );\n }\n };\n\n private assertMetadataCoordinates = (\n metadata: AppInstanceMetadata,\n appId: string,\n instanceId: string,\n ): void => {\n if (metadata.appId !== appId || metadata.instanceId !== instanceId) {\n throw new Error(\n `App Instance identity 不匹配:期望 ${appId}/${instanceId},实际 ${metadata.appId}/${metadata.instanceId}`,\n );\n }\n };\n\n private bindPublisher = async (\n instanceDirectory: string,\n metadata: AppInstanceMetadata,\n publisherId: string,\n ): Promise<AppInstanceMetadata> => {\n const nextMetadata = { ...metadata, publisherId };\n const metadataPath = path.join(instanceDirectory, STORAGE_METADATA_FILE);\n const temporaryPath = `${metadataPath}.${randomUUID()}.tmp`;\n try {\n await writeFile(\n temporaryPath,\n `${JSON.stringify(nextMetadata, null, 2)}\\n`,\n { encoding: \"utf8\", mode: 0o600, flag: \"wx\" },\n );\n await rename(temporaryPath, metadataPath);\n return nextMetadata;\n } finally {\n await rm(temporaryPath, { force: true });\n }\n };\n\n private assertSafeId = (value: string, field: string): void => {\n if (!SAFE_ID_PATTERN.test(value)) {\n throw new Error(`${field} 不是安全的 App Instance 标识:${value}`);\n }\n };\n\n private measureDirectory = async (directory: string): Promise<number> => {\n try {\n const entries = await readdir(directory, { withFileTypes: true });\n let bytes = 0;\n for (const entry of entries) {\n const entryPath = path.join(directory, entry.name);\n if (entry.isSymbolicLink()) {\n continue;\n }\n if (entry.isDirectory()) {\n bytes += await this.measureDirectory(entryPath);\n continue;\n }\n if (entry.isFile()) {\n bytes += (await stat(entryPath)).size;\n }\n }\n return bytes;\n } catch (error) {\n if (this.isMissingFileError(error)) {\n return 0;\n }\n throw error;\n }\n };\n\n private pathExists = async (targetPath: string): Promise<boolean> => {\n try {\n await access(targetPath);\n return true;\n } catch {\n return false;\n }\n };\n\n private isMissingFileError = (error: unknown): boolean =>\n typeof error === \"object\" && error !== null &&\n \"code\" in error && (error as { code?: unknown }).code === \"ENOENT\";\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppManifestBundle, AppManifestSummary } from "../types/app-manifest.types.js";
|
|
1
|
+
import { AppComponentManifest, AppManifestBundle, AppManifestSummary, AppPlatformSecuritySummary } from "../types/app-manifest.types.js";
|
|
2
2
|
|
|
3
3
|
//#region src/services/app-manifest.service.d.ts
|
|
4
4
|
declare class AppManifestService {
|
|
@@ -7,12 +7,16 @@ declare class AppManifestService {
|
|
|
7
7
|
private loadStandaloneBundle;
|
|
8
8
|
private loadComponentBundle;
|
|
9
9
|
private parseManifest;
|
|
10
|
+
resolvePlatformSecurity: (manifest: AppComponentManifest) => AppPlatformSecuritySummary;
|
|
10
11
|
private parseCommonManifest;
|
|
11
12
|
private parseMain;
|
|
12
13
|
private parseUi;
|
|
13
14
|
private parseComponents;
|
|
14
15
|
private parseEngines;
|
|
15
16
|
private parsePresentation;
|
|
17
|
+
private parseRuntime;
|
|
18
|
+
private parseAppStorage;
|
|
19
|
+
private assertRuntimeMatchesComponents;
|
|
16
20
|
private readComponentId;
|
|
17
21
|
private parsePermissions;
|
|
18
22
|
private parseDocumentAccess;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-manifest.service.d.ts","names":[],"sources":["../../src/services/app-manifest.service.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"app-manifest.service.d.ts","names":[],"sources":["../../src/services/app-manifest.service.ts"],"mappings":";;;cAoBa,kBAAA;EACX,IAAA,GAAc,YAAA,aAAuB,OAAA,CAAQ,iBAAA;EAU7C,SAAA,GAAa,MAAA,EAAQ,iBAAA,KAAoB,kBAAA;EAAA,QAqCjC,oBAAA;EAAA,QA8BA,mBAAA;EAAA,QA8DA,aAAA;EA+BR,uBAAA,GACE,QAAA,EAAU,oBAAA,KACT,0BAAA;EAAA,QAgCK,mBAAA;EAAA,QAcA,SAAA;EAAA,QAoBA,OAAA;EAAA,QAKA,eAAA;EAAA,QA+BA,YAAA;EAAA,QAUA,iBAAA;EAAA,QAeA,YAAA;EAAA,QAcA,eAAA;EAAA,QAkBA,8BAAA;EAAA,QAgBA,eAAA;EAAA,QA0BA,gBAAA;EAAA,QAaA,mBAAA;EAAA,QAwBA,gBAAA;EAAA,QAUA,YAAA;EAAA,QAQA,iBAAA;EAAA,QAeA,kBAAA;EAAA,QAaA,uBAAA;EAAA,QAcA,aAAA;EAAA,QAUA,qBAAA;EAAA,QAWA,YAAA;EAAA,QAOA,kBAAA;EAAA,QAOA,kBAAA;EAAA,QAIA,UAAA;EAAA,QAOA,WAAA;AAAA"}
|
|
@@ -23,7 +23,8 @@ var AppManifestService = class {
|
|
|
23
23
|
manifestPath: componentBundle.manifestPath,
|
|
24
24
|
iconPath: componentBundle.iconPath,
|
|
25
25
|
primaryPanelId: componentBundle.primaryPanelId,
|
|
26
|
-
components: componentBundle.components
|
|
26
|
+
components: componentBundle.components,
|
|
27
|
+
security: this.resolvePlatformSecurity(componentBundle.manifest)
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
const standaloneBundle = bundle;
|
|
@@ -102,12 +103,39 @@ var AppManifestService = class {
|
|
|
102
103
|
ui: this.parseUi(candidate.ui),
|
|
103
104
|
permissions: this.parsePermissions(candidate.permissions)
|
|
104
105
|
};
|
|
106
|
+
const components = this.parseComponents(candidate.components);
|
|
107
|
+
const runtime = this.parseRuntime(candidate.runtime);
|
|
108
|
+
this.assertRuntimeMatchesComponents(runtime, components);
|
|
105
109
|
return {
|
|
106
110
|
schemaVersion: 2,
|
|
107
111
|
...common,
|
|
108
112
|
engines: this.parseEngines(candidate.engines),
|
|
109
113
|
presentation: this.parsePresentation(candidate.presentation),
|
|
110
|
-
|
|
114
|
+
runtime,
|
|
115
|
+
storage: this.parseAppStorage(candidate.storage),
|
|
116
|
+
permissions: this.parsePermissions(candidate.permissions),
|
|
117
|
+
components
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
resolvePlatformSecurity = (manifest) => {
|
|
121
|
+
const hasServiceComponents = manifest.components.some((component) => component.kind === "service");
|
|
122
|
+
const runtimeProfile = manifest.runtime?.profile ?? (hasServiceComponents ? "native-process" : "panel-only");
|
|
123
|
+
const isolation = runtimeProfile === "panel-only" ? "sandboxed" : runtimeProfile === "wasi" ? "host-mediated" : "full-user";
|
|
124
|
+
const declaredPermissions = manifest.permissions ?? {};
|
|
125
|
+
const permissions = runtimeProfile === "native-process" ? {
|
|
126
|
+
...declaredPermissions,
|
|
127
|
+
storage: declaredPermissions.storage ?? true,
|
|
128
|
+
capabilities: {
|
|
129
|
+
...declaredPermissions.capabilities,
|
|
130
|
+
nativeProcess: true
|
|
131
|
+
}
|
|
132
|
+
} : declaredPermissions;
|
|
133
|
+
return {
|
|
134
|
+
runtimeProfile,
|
|
135
|
+
isolation,
|
|
136
|
+
hasServiceComponents,
|
|
137
|
+
inferred: manifest.runtime === void 0,
|
|
138
|
+
permissions
|
|
111
139
|
};
|
|
112
140
|
};
|
|
113
141
|
parseCommonManifest = (candidate) => {
|
|
@@ -168,6 +196,31 @@ var AppManifestService = class {
|
|
|
168
196
|
const candidate = this.assertObject(rawPresentation, "presentation");
|
|
169
197
|
return { primaryPanel: this.readOptionalString(candidate.primaryPanel, "presentation.primaryPanel") };
|
|
170
198
|
};
|
|
199
|
+
parseRuntime = (rawRuntime) => {
|
|
200
|
+
if (rawRuntime === void 0) return;
|
|
201
|
+
const candidate = this.assertObject(rawRuntime, "runtime");
|
|
202
|
+
const profile = this.readRequiredString(candidate.profile, "runtime.profile");
|
|
203
|
+
if (profile !== "panel-only" && profile !== "wasi" && profile !== "native-process") throw new Error("runtime.profile 只支持 panel-only、wasi 或 native-process。");
|
|
204
|
+
return { profile };
|
|
205
|
+
};
|
|
206
|
+
parseAppStorage = (rawStorage) => {
|
|
207
|
+
if (rawStorage === void 0) return;
|
|
208
|
+
const candidate = this.assertObject(rawStorage, "storage");
|
|
209
|
+
const scope = this.readRequiredString(candidate.scope, "storage.scope");
|
|
210
|
+
if (scope !== "global") throw new Error("当前 storage.scope 只支持 global。");
|
|
211
|
+
const schemaVersion = this.readNumber(candidate.schemaVersion, "storage.schemaVersion");
|
|
212
|
+
if (!Number.isSafeInteger(schemaVersion) || schemaVersion < 1) throw new Error("storage.schemaVersion 必须是正整数。");
|
|
213
|
+
return {
|
|
214
|
+
scope,
|
|
215
|
+
schemaVersion
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
assertRuntimeMatchesComponents = (runtime, components) => {
|
|
219
|
+
if (!runtime) return;
|
|
220
|
+
const hasService = components.some((component) => component.kind === "service");
|
|
221
|
+
if (runtime.profile === "panel-only" && hasService) throw new Error("runtime.profile=panel-only 不能包含 Service component。");
|
|
222
|
+
if (runtime.profile !== "panel-only" && !hasService) throw new Error(`${runtime.profile} runtime 必须包含 Service component。`);
|
|
223
|
+
};
|
|
171
224
|
readComponentId = async (component, componentDirectory) => {
|
|
172
225
|
const manifestFile = component.kind === "panel" ? "panel-app.json" : "service-app.json";
|
|
173
226
|
const raw = JSON.parse(await readFile(path.join(componentDirectory, manifestFile), "utf-8"));
|
|
@@ -217,7 +270,10 @@ var AppManifestService = class {
|
|
|
217
270
|
parseCapabilities = (rawCapabilities) => {
|
|
218
271
|
if (rawCapabilities === void 0) return;
|
|
219
272
|
const candidate = this.assertObject(rawCapabilities, "permissions.capabilities");
|
|
220
|
-
return {
|
|
273
|
+
return {
|
|
274
|
+
hostBridge: candidate.hostBridge === void 0 ? void 0 : this.readBoolean(candidate.hostBridge, "permissions.capabilities.hostBridge"),
|
|
275
|
+
nativeProcess: candidate.nativeProcess === void 0 ? void 0 : this.readBoolean(candidate.nativeProcess, "permissions.capabilities.nativeProcess")
|
|
276
|
+
};
|
|
221
277
|
};
|
|
222
278
|
assertResolvedFile = async (appDirectory, relativePath, fieldName) => {
|
|
223
279
|
const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-manifest.service.js","names":[],"sources":["../../src/services/app-manifest.service.ts"],"sourcesContent":["import { access, lstat, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppComponentManifest,\n AppComponentManifestBundle,\n AppComponentReference,\n AppDocumentAccessScope,\n AppManifest,\n AppManifestBundle,\n AppManifestSummary,\n AppPermissions,\n AppResolvedComponent,\n AppStandaloneManifest,\n AppStandaloneManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\n\nconst PACKAGE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppManifestService {\n load = async (appDirectory: string): Promise<AppManifestBundle> => {\n const normalizedDirectory = path.resolve(appDirectory);\n const manifestPath = path.join(normalizedDirectory, \"manifest.json\");\n const rawManifest = JSON.parse(await readFile(manifestPath, \"utf-8\")) as unknown;\n const manifest = this.parseManifest(rawManifest);\n return manifest.schemaVersion === 1\n ? await this.loadStandaloneBundle(normalizedDirectory, manifestPath, manifest)\n : await this.loadComponentBundle(normalizedDirectory, manifestPath, manifest);\n };\n\n summarize = (bundle: AppManifestBundle): AppManifestSummary => {\n if (bundle.manifest.schemaVersion === 2) {\n const componentBundle = bundle as AppComponentManifestBundle;\n return {\n schemaVersion: 2,\n id: componentBundle.manifest.id,\n name: componentBundle.manifest.name,\n version: componentBundle.manifest.version,\n description: componentBundle.manifest.description,\n manifestPath: componentBundle.manifestPath,\n iconPath: componentBundle.iconPath,\n primaryPanelId: componentBundle.primaryPanelId,\n components: componentBundle.components,\n };\n }\n const standaloneBundle = bundle as AppStandaloneManifestBundle;\n const permissions = standaloneBundle.manifest.permissions ?? {};\n return {\n schemaVersion: 1,\n id: standaloneBundle.manifest.id,\n name: standaloneBundle.manifest.name,\n version: standaloneBundle.manifest.version,\n description: standaloneBundle.manifest.description,\n mainKind: standaloneBundle.manifest.main.kind,\n action:\n standaloneBundle.manifest.main.kind === \"wasm\"\n ? standaloneBundle.manifest.main.action\n : undefined,\n manifestPath: standaloneBundle.manifestPath,\n mainEntryPath: standaloneBundle.mainEntryPath,\n uiEntryPath: standaloneBundle.uiEntryPath,\n iconPath: standaloneBundle.iconPath,\n permissions,\n };\n };\n\n private loadStandaloneBundle = async (\n appDirectory: string,\n manifestPath: string,\n manifest: AppStandaloneManifest,\n ): Promise<AppStandaloneManifestBundle> => {\n const mainEntryPath = await this.assertResolvedFile(\n appDirectory,\n manifest.main.entry,\n \"main.entry\",\n );\n const uiEntryPath = await this.assertResolvedFile(\n appDirectory,\n manifest.ui.entry,\n \"ui.entry\",\n );\n const iconPath = manifest.icon\n ? await this.assertResolvedFile(appDirectory, manifest.icon, \"icon\")\n : undefined;\n return {\n appDirectory,\n manifestPath,\n manifest,\n mainEntryPath,\n uiEntryPath,\n uiDirectoryPath: path.dirname(uiEntryPath),\n assetsDirectoryPath: path.join(appDirectory, \"assets\"),\n iconPath,\n };\n };\n\n private loadComponentBundle = async (\n appDirectory: string,\n manifestPath: string,\n manifest: AppComponentManifest,\n ): Promise<AppComponentManifestBundle> => {\n const components: AppResolvedComponent[] = [];\n for (const [index, component] of manifest.components.entries()) {\n const componentDirectory = await this.assertResolvedDirectory(\n appDirectory,\n component.path,\n `components[${index}].path`,\n );\n const componentId = await this.readComponentId(component, componentDirectory);\n const expectedPrefix = `${manifest.id.replace(/[^a-z0-9]+/g, \"-\")}-`;\n if (!componentId.startsWith(expectedPrefix)) {\n throw new Error(\n `组件 ${componentId} 必须使用包前缀 ${expectedPrefix}。`,\n );\n }\n components.push({\n ...component,\n id: componentId,\n componentDirectory,\n manifestPath: path.join(\n componentDirectory,\n component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n ),\n });\n }\n const duplicateId = components.find(\n (component, index) => components.findIndex((entry) => entry.id === component.id) !== index,\n );\n if (duplicateId) {\n throw new Error(`components 包含重复组件 id:${duplicateId.id}`);\n }\n const panelIds = components\n .filter((component) => component.kind === \"panel\")\n .map((component) => component.id);\n const declaredPrimaryPanel = manifest.presentation?.primaryPanel;\n if (declaredPrimaryPanel && !panelIds.includes(declaredPrimaryPanel)) {\n throw new Error(\"presentation.primaryPanel 必须引用真实 Panel component id。\");\n }\n if (panelIds.length > 1 && !declaredPrimaryPanel) {\n throw new Error(\"包含多个 Panel 时必须声明 presentation.primaryPanel。\");\n }\n if (panelIds.length === 0 && declaredPrimaryPanel) {\n throw new Error(\"不含 Panel 的包不能声明 presentation.primaryPanel。\");\n }\n const iconPath = manifest.icon\n ? await this.assertResolvedFile(appDirectory, manifest.icon, \"icon\")\n : undefined;\n return {\n appDirectory,\n manifestPath,\n manifest,\n components,\n assetsDirectoryPath: path.join(appDirectory, \"assets\"),\n iconPath,\n primaryPanelId: declaredPrimaryPanel ?? panelIds[0],\n };\n };\n\n private parseManifest = (rawManifest: unknown): AppManifest => {\n const candidate = this.assertObject(rawManifest, \"manifest.json\");\n const schemaVersion = this.readNumber(candidate.schemaVersion, \"schemaVersion\");\n if (schemaVersion !== 1 && schemaVersion !== 2) {\n throw new Error(\"当前只支持 schemaVersion = 1 或 2。\");\n }\n const common = this.parseCommonManifest(candidate);\n if (schemaVersion === 1) {\n return {\n schemaVersion: 1,\n ...common,\n main: this.parseMain(candidate.main),\n ui: this.parseUi(candidate.ui),\n permissions: this.parsePermissions(candidate.permissions),\n };\n }\n return {\n schemaVersion: 2,\n ...common,\n engines: this.parseEngines(candidate.engines),\n presentation: this.parsePresentation(candidate.presentation),\n components: this.parseComponents(candidate.components),\n };\n };\n\n private parseCommonManifest = (candidate: Record<string, unknown>) => {\n const id = this.readRequiredString(candidate.id, \"id\");\n if (!PACKAGE_ID_PATTERN.test(id)) {\n throw new Error(\"id 必须由小写字母、数字、点或连字符组成。\");\n }\n return {\n id,\n name: this.readRequiredString(candidate.name, \"name\"),\n version: this.readRequiredString(candidate.version, \"version\"),\n description: this.readOptionalString(candidate.description, \"description\"),\n icon: this.readOptionalString(candidate.icon, \"icon\"),\n };\n };\n\n private parseMain = (rawMain: unknown): AppStandaloneManifest[\"main\"] => {\n const candidate = this.assertObject(rawMain, \"main\");\n const kind = this.readRequiredString(candidate.kind, \"main.kind\");\n if (kind === \"wasm\") {\n return {\n kind,\n entry: this.readRequiredString(candidate.entry, \"main.entry\"),\n export: this.readRequiredString(candidate.export, \"main.export\"),\n action: this.readRequiredString(candidate.action, \"main.action\"),\n };\n }\n if (kind === \"wasi-http-component\") {\n return {\n kind,\n entry: this.readRequiredString(candidate.entry, \"main.entry\"),\n };\n }\n throw new Error(\"当前 main.kind 只支持 wasm 或 wasi-http-component。\");\n };\n\n private parseUi = (rawUi: unknown): AppStandaloneManifest[\"ui\"] => {\n const candidate = this.assertObject(rawUi, \"ui\");\n return { entry: this.readRequiredString(candidate.entry, \"ui.entry\") };\n };\n\n private parseComponents = (rawComponents: unknown): AppComponentReference[] => {\n if (!Array.isArray(rawComponents) || rawComponents.length === 0) {\n throw new Error(\"components 必须是非空数组。\");\n }\n const components = rawComponents.map((rawComponent, index) => {\n const candidate = this.assertObject(rawComponent, `components[${index}]`);\n const kind = this.readRequiredString(candidate.kind, `components[${index}].kind`);\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`components[${index}].kind 只支持 panel 或 service。`);\n }\n const normalizedKind: AppComponentReference[\"kind\"] = kind;\n return {\n kind: normalizedKind,\n path: this.normalizeRelativePath(\n this.readRequiredString(candidate.path, `components[${index}].path`),\n `components[${index}].path`,\n ),\n };\n });\n for (let leftIndex = 0; leftIndex < components.length; leftIndex += 1) {\n for (let rightIndex = leftIndex + 1; rightIndex < components.length; rightIndex += 1) {\n const left = components[leftIndex]?.path;\n const right = components[rightIndex]?.path;\n if (left && right && (left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`))) {\n throw new Error(`components path 不能重复或重叠:${left} / ${right}`);\n }\n }\n }\n return components;\n };\n\n private parseEngines = (\n rawEngines: unknown,\n ): AppComponentManifest[\"engines\"] => {\n if (rawEngines === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawEngines, \"engines\");\n return { nextclaw: this.readOptionalString(candidate.nextclaw, \"engines.nextclaw\") };\n };\n\n private parsePresentation = (\n rawPresentation: unknown,\n ): AppComponentManifest[\"presentation\"] => {\n if (rawPresentation === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawPresentation, \"presentation\");\n return {\n primaryPanel: this.readOptionalString(\n candidate.primaryPanel,\n \"presentation.primaryPanel\",\n ),\n };\n };\n\n private readComponentId = async (\n component: AppComponentReference,\n componentDirectory: string,\n ): Promise<string> => {\n const manifestFile = component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\";\n const raw = JSON.parse(\n await readFile(path.join(componentDirectory, manifestFile), \"utf-8\"),\n ) as unknown;\n const manifest = this.assertObject(raw, `${component.path}/${manifestFile}`);\n const id = this.readRequiredString(manifest.id, `${component.path}/${manifestFile}.id`);\n if (!COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`组件 id 必须是 kebab-case:${id}`);\n }\n const directoryName = path.basename(componentDirectory);\n const expectedId = component.kind === \"panel\"\n ? directoryName.replace(/\\.panel$/, \"\")\n : directoryName;\n if (component.kind === \"panel\" && !directoryName.endsWith(\".panel\")) {\n throw new Error(`Panel component 目录必须以 .panel 结尾:${component.path}`);\n }\n if (id !== expectedId) {\n throw new Error(`组件 id 必须与目录名一致:期望 ${expectedId},实际 ${id}`);\n }\n return id;\n };\n\n private parsePermissions = (rawPermissions: unknown): AppPermissions | undefined => {\n if (rawPermissions === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawPermissions, \"permissions\");\n return {\n documentAccess: this.parseDocumentAccess(candidate.documentAccess),\n allowedDomains: this.parseStringArray(candidate.allowedDomains, \"permissions.allowedDomains\"),\n storage: this.parseStorage(candidate.storage),\n capabilities: this.parseCapabilities(candidate.capabilities),\n };\n };\n\n private parseDocumentAccess = (rawDocumentAccess: unknown): AppDocumentAccessScope[] | undefined => {\n if (rawDocumentAccess === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawDocumentAccess)) {\n throw new Error(\"permissions.documentAccess 必须是数组。\");\n }\n return rawDocumentAccess.map((scope, index) => {\n const candidate = this.assertObject(scope, `permissions.documentAccess[${index}]`);\n const mode = this.readRequiredString(candidate.mode, `permissions.documentAccess[${index}].mode`);\n if (mode !== \"read\" && mode !== \"read-write\") {\n throw new Error(`permissions.documentAccess[${index}].mode 只支持 read 或 read-write。`);\n }\n return {\n id: this.readRequiredString(candidate.id, `permissions.documentAccess[${index}].id`),\n mode,\n description: this.readOptionalString(\n candidate.description,\n `permissions.documentAccess[${index}].description`,\n ),\n };\n });\n };\n\n private parseStringArray = (rawValue: unknown, fieldName: string): string[] | undefined => {\n if (rawValue === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawValue)) {\n throw new Error(`${fieldName} 必须是字符串数组。`);\n }\n return rawValue.map((item, index) => this.readRequiredString(item, `${fieldName}[${index}]`));\n };\n\n private parseStorage = (rawStorage: unknown): AppPermissions[\"storage\"] | undefined => {\n if (rawStorage === undefined || typeof rawStorage === \"boolean\") {\n return rawStorage;\n }\n const candidate = this.assertObject(rawStorage, \"permissions.storage\");\n return { namespace: this.readOptionalString(candidate.namespace, \"permissions.storage.namespace\") };\n };\n\n private parseCapabilities = (rawCapabilities: unknown): AppPermissions[\"capabilities\"] | undefined => {\n if (rawCapabilities === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawCapabilities, \"permissions.capabilities\");\n return {\n hostBridge: candidate.hostBridge === undefined\n ? undefined\n : this.readBoolean(candidate.hostBridge, \"permissions.capabilities.hostBridge\"),\n };\n };\n\n private assertResolvedFile = async (\n appDirectory: string,\n relativePath: string,\n fieldName: string,\n ): Promise<string> => {\n const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);\n const stats = await lstat(resolvedPath);\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new Error(`${fieldName} 必须指向包内普通文件。`);\n }\n return resolvedPath;\n };\n\n private assertResolvedDirectory = async (\n appDirectory: string,\n relativePath: string,\n fieldName: string,\n ): Promise<string> => {\n const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);\n const stats = await lstat(resolvedPath);\n if (!stats.isDirectory() || stats.isSymbolicLink()) {\n throw new Error(`${fieldName} 必须指向包内普通目录。`);\n }\n await access(resolvedPath);\n return resolvedPath;\n };\n\n private resolveInside = (appDirectory: string, relativePath: string, fieldName: string): string => {\n const normalizedPath = this.normalizeRelativePath(relativePath, fieldName);\n const resolvedPath = path.resolve(appDirectory, normalizedPath);\n const relative = path.relative(appDirectory, resolvedPath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`${fieldName} 不能指向应用目录之外。`);\n }\n return resolvedPath;\n };\n\n private normalizeRelativePath = (relativePath: string, fieldName: string): string => {\n if (path.isAbsolute(relativePath) || relativePath.includes(\"\\0\")) {\n throw new Error(`${fieldName} 必须使用包内相对路径。`);\n }\n const segments = relativePath.replace(/\\\\/g, \"/\").split(\"/\");\n if (segments.some((segment) => !segment || segment === \".\" || segment === \"..\")) {\n throw new Error(`${fieldName} 包含非法路径片段。`);\n }\n return segments.join(\"/\");\n };\n\n private assertObject = (value: unknown, fieldName: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private readRequiredString = (value: unknown, fieldName: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${fieldName} 必须是非空字符串。`);\n }\n return value.trim();\n };\n\n private readOptionalString = (value: unknown, fieldName: string): string | undefined => {\n return value === undefined ? undefined : this.readRequiredString(value, fieldName);\n };\n\n private readNumber = (value: unknown, fieldName: string): number => {\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n throw new Error(`${fieldName} 必须是数字。`);\n }\n return value;\n };\n\n private readBoolean = (value: unknown, fieldName: string): boolean => {\n if (typeof value !== \"boolean\") {\n throw new Error(`${fieldName} 必须是布尔值。`);\n }\n return value;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app-manifest.service.js","names":[],"sources":["../../src/services/app-manifest.service.ts"],"sourcesContent":["import { access, lstat, readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type {\n AppComponentManifest,\n AppComponentManifestBundle,\n AppComponentReference,\n AppDocumentAccessScope,\n AppManifest,\n AppManifestBundle,\n AppManifestSummary,\n AppPermissions,\n AppPlatformSecuritySummary,\n AppResolvedComponent,\n AppStandaloneManifest,\n AppStandaloneManifestBundle,\n} from \"#app-runtime/types/app-manifest.types.js\";\n\nconst PACKAGE_ID_PATTERN = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;\nconst COMPONENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class AppManifestService {\n load = async (appDirectory: string): Promise<AppManifestBundle> => {\n const normalizedDirectory = path.resolve(appDirectory);\n const manifestPath = path.join(normalizedDirectory, \"manifest.json\");\n const rawManifest = JSON.parse(await readFile(manifestPath, \"utf-8\")) as unknown;\n const manifest = this.parseManifest(rawManifest);\n return manifest.schemaVersion === 1\n ? await this.loadStandaloneBundle(normalizedDirectory, manifestPath, manifest)\n : await this.loadComponentBundle(normalizedDirectory, manifestPath, manifest);\n };\n\n summarize = (bundle: AppManifestBundle): AppManifestSummary => {\n if (bundle.manifest.schemaVersion === 2) {\n const componentBundle = bundle as AppComponentManifestBundle;\n return {\n schemaVersion: 2,\n id: componentBundle.manifest.id,\n name: componentBundle.manifest.name,\n version: componentBundle.manifest.version,\n description: componentBundle.manifest.description,\n manifestPath: componentBundle.manifestPath,\n iconPath: componentBundle.iconPath,\n primaryPanelId: componentBundle.primaryPanelId,\n components: componentBundle.components,\n security: this.resolvePlatformSecurity(componentBundle.manifest),\n };\n }\n const standaloneBundle = bundle as AppStandaloneManifestBundle;\n const permissions = standaloneBundle.manifest.permissions ?? {};\n return {\n schemaVersion: 1,\n id: standaloneBundle.manifest.id,\n name: standaloneBundle.manifest.name,\n version: standaloneBundle.manifest.version,\n description: standaloneBundle.manifest.description,\n mainKind: standaloneBundle.manifest.main.kind,\n action:\n standaloneBundle.manifest.main.kind === \"wasm\"\n ? standaloneBundle.manifest.main.action\n : undefined,\n manifestPath: standaloneBundle.manifestPath,\n mainEntryPath: standaloneBundle.mainEntryPath,\n uiEntryPath: standaloneBundle.uiEntryPath,\n iconPath: standaloneBundle.iconPath,\n permissions,\n };\n };\n\n private loadStandaloneBundle = async (\n appDirectory: string,\n manifestPath: string,\n manifest: AppStandaloneManifest,\n ): Promise<AppStandaloneManifestBundle> => {\n const mainEntryPath = await this.assertResolvedFile(\n appDirectory,\n manifest.main.entry,\n \"main.entry\",\n );\n const uiEntryPath = await this.assertResolvedFile(\n appDirectory,\n manifest.ui.entry,\n \"ui.entry\",\n );\n const iconPath = manifest.icon\n ? await this.assertResolvedFile(appDirectory, manifest.icon, \"icon\")\n : undefined;\n return {\n appDirectory,\n manifestPath,\n manifest,\n mainEntryPath,\n uiEntryPath,\n uiDirectoryPath: path.dirname(uiEntryPath),\n assetsDirectoryPath: path.join(appDirectory, \"assets\"),\n iconPath,\n };\n };\n\n private loadComponentBundle = async (\n appDirectory: string,\n manifestPath: string,\n manifest: AppComponentManifest,\n ): Promise<AppComponentManifestBundle> => {\n const components: AppResolvedComponent[] = [];\n for (const [index, component] of manifest.components.entries()) {\n const componentDirectory = await this.assertResolvedDirectory(\n appDirectory,\n component.path,\n `components[${index}].path`,\n );\n const componentId = await this.readComponentId(component, componentDirectory);\n const expectedPrefix = `${manifest.id.replace(/[^a-z0-9]+/g, \"-\")}-`;\n if (!componentId.startsWith(expectedPrefix)) {\n throw new Error(\n `组件 ${componentId} 必须使用包前缀 ${expectedPrefix}。`,\n );\n }\n components.push({\n ...component,\n id: componentId,\n componentDirectory,\n manifestPath: path.join(\n componentDirectory,\n component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\",\n ),\n });\n }\n const duplicateId = components.find(\n (component, index) => components.findIndex((entry) => entry.id === component.id) !== index,\n );\n if (duplicateId) {\n throw new Error(`components 包含重复组件 id:${duplicateId.id}`);\n }\n const panelIds = components\n .filter((component) => component.kind === \"panel\")\n .map((component) => component.id);\n const declaredPrimaryPanel = manifest.presentation?.primaryPanel;\n if (declaredPrimaryPanel && !panelIds.includes(declaredPrimaryPanel)) {\n throw new Error(\"presentation.primaryPanel 必须引用真实 Panel component id。\");\n }\n if (panelIds.length > 1 && !declaredPrimaryPanel) {\n throw new Error(\"包含多个 Panel 时必须声明 presentation.primaryPanel。\");\n }\n if (panelIds.length === 0 && declaredPrimaryPanel) {\n throw new Error(\"不含 Panel 的包不能声明 presentation.primaryPanel。\");\n }\n const iconPath = manifest.icon\n ? await this.assertResolvedFile(appDirectory, manifest.icon, \"icon\")\n : undefined;\n return {\n appDirectory,\n manifestPath,\n manifest,\n components,\n assetsDirectoryPath: path.join(appDirectory, \"assets\"),\n iconPath,\n primaryPanelId: declaredPrimaryPanel ?? panelIds[0],\n };\n };\n\n private parseManifest = (rawManifest: unknown): AppManifest => {\n const candidate = this.assertObject(rawManifest, \"manifest.json\");\n const schemaVersion = this.readNumber(candidate.schemaVersion, \"schemaVersion\");\n if (schemaVersion !== 1 && schemaVersion !== 2) {\n throw new Error(\"当前只支持 schemaVersion = 1 或 2。\");\n }\n const common = this.parseCommonManifest(candidate);\n if (schemaVersion === 1) {\n return {\n schemaVersion: 1,\n ...common,\n main: this.parseMain(candidate.main),\n ui: this.parseUi(candidate.ui),\n permissions: this.parsePermissions(candidate.permissions),\n };\n }\n const components = this.parseComponents(candidate.components);\n const runtime = this.parseRuntime(candidate.runtime);\n this.assertRuntimeMatchesComponents(runtime, components);\n return {\n schemaVersion: 2,\n ...common,\n engines: this.parseEngines(candidate.engines),\n presentation: this.parsePresentation(candidate.presentation),\n runtime,\n storage: this.parseAppStorage(candidate.storage),\n permissions: this.parsePermissions(candidate.permissions),\n components,\n };\n };\n\n resolvePlatformSecurity = (\n manifest: AppComponentManifest,\n ): AppPlatformSecuritySummary => {\n const hasServiceComponents = manifest.components.some(\n (component) => component.kind === \"service\",\n );\n const runtimeProfile = manifest.runtime?.profile ?? (\n hasServiceComponents ? \"native-process\" : \"panel-only\"\n );\n const isolation = runtimeProfile === \"panel-only\"\n ? \"sandboxed\"\n : runtimeProfile === \"wasi\"\n ? \"host-mediated\"\n : \"full-user\";\n const declaredPermissions = manifest.permissions ?? {};\n const permissions: AppPermissions = runtimeProfile === \"native-process\"\n ? {\n ...declaredPermissions,\n storage: declaredPermissions.storage ?? true,\n capabilities: {\n ...declaredPermissions.capabilities,\n nativeProcess: true,\n },\n }\n : declaredPermissions;\n return {\n runtimeProfile,\n isolation,\n hasServiceComponents,\n inferred: manifest.runtime === undefined,\n permissions,\n };\n };\n\n private parseCommonManifest = (candidate: Record<string, unknown>) => {\n const id = this.readRequiredString(candidate.id, \"id\");\n if (!PACKAGE_ID_PATTERN.test(id)) {\n throw new Error(\"id 必须由小写字母、数字、点或连字符组成。\");\n }\n return {\n id,\n name: this.readRequiredString(candidate.name, \"name\"),\n version: this.readRequiredString(candidate.version, \"version\"),\n description: this.readOptionalString(candidate.description, \"description\"),\n icon: this.readOptionalString(candidate.icon, \"icon\"),\n };\n };\n\n private parseMain = (rawMain: unknown): AppStandaloneManifest[\"main\"] => {\n const candidate = this.assertObject(rawMain, \"main\");\n const kind = this.readRequiredString(candidate.kind, \"main.kind\");\n if (kind === \"wasm\") {\n return {\n kind,\n entry: this.readRequiredString(candidate.entry, \"main.entry\"),\n export: this.readRequiredString(candidate.export, \"main.export\"),\n action: this.readRequiredString(candidate.action, \"main.action\"),\n };\n }\n if (kind === \"wasi-http-component\") {\n return {\n kind,\n entry: this.readRequiredString(candidate.entry, \"main.entry\"),\n };\n }\n throw new Error(\"当前 main.kind 只支持 wasm 或 wasi-http-component。\");\n };\n\n private parseUi = (rawUi: unknown): AppStandaloneManifest[\"ui\"] => {\n const candidate = this.assertObject(rawUi, \"ui\");\n return { entry: this.readRequiredString(candidate.entry, \"ui.entry\") };\n };\n\n private parseComponents = (rawComponents: unknown): AppComponentReference[] => {\n if (!Array.isArray(rawComponents) || rawComponents.length === 0) {\n throw new Error(\"components 必须是非空数组。\");\n }\n const components = rawComponents.map((rawComponent, index) => {\n const candidate = this.assertObject(rawComponent, `components[${index}]`);\n const kind = this.readRequiredString(candidate.kind, `components[${index}].kind`);\n if (kind !== \"panel\" && kind !== \"service\") {\n throw new Error(`components[${index}].kind 只支持 panel 或 service。`);\n }\n const normalizedKind: AppComponentReference[\"kind\"] = kind;\n return {\n kind: normalizedKind,\n path: this.normalizeRelativePath(\n this.readRequiredString(candidate.path, `components[${index}].path`),\n `components[${index}].path`,\n ),\n };\n });\n for (let leftIndex = 0; leftIndex < components.length; leftIndex += 1) {\n for (let rightIndex = leftIndex + 1; rightIndex < components.length; rightIndex += 1) {\n const left = components[leftIndex]?.path;\n const right = components[rightIndex]?.path;\n if (left && right && (left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`))) {\n throw new Error(`components path 不能重复或重叠:${left} / ${right}`);\n }\n }\n }\n return components;\n };\n\n private parseEngines = (\n rawEngines: unknown,\n ): AppComponentManifest[\"engines\"] => {\n if (rawEngines === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawEngines, \"engines\");\n return { nextclaw: this.readOptionalString(candidate.nextclaw, \"engines.nextclaw\") };\n };\n\n private parsePresentation = (\n rawPresentation: unknown,\n ): AppComponentManifest[\"presentation\"] => {\n if (rawPresentation === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawPresentation, \"presentation\");\n return {\n primaryPanel: this.readOptionalString(\n candidate.primaryPanel,\n \"presentation.primaryPanel\",\n ),\n };\n };\n\n private parseRuntime = (\n rawRuntime: unknown,\n ): AppComponentManifest[\"runtime\"] => {\n if (rawRuntime === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawRuntime, \"runtime\");\n const profile = this.readRequiredString(candidate.profile, \"runtime.profile\");\n if (profile !== \"panel-only\" && profile !== \"wasi\" && profile !== \"native-process\") {\n throw new Error(\"runtime.profile 只支持 panel-only、wasi 或 native-process。\");\n }\n return { profile };\n };\n\n private parseAppStorage = (\n rawStorage: unknown,\n ): AppComponentManifest[\"storage\"] => {\n if (rawStorage === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawStorage, \"storage\");\n const scope = this.readRequiredString(candidate.scope, \"storage.scope\");\n if (scope !== \"global\") {\n throw new Error(\"当前 storage.scope 只支持 global。\");\n }\n const schemaVersion = this.readNumber(candidate.schemaVersion, \"storage.schemaVersion\");\n if (!Number.isSafeInteger(schemaVersion) || schemaVersion < 1) {\n throw new Error(\"storage.schemaVersion 必须是正整数。\");\n }\n return { scope, schemaVersion };\n };\n\n private assertRuntimeMatchesComponents = (\n runtime: AppComponentManifest[\"runtime\"],\n components: AppComponentReference[],\n ): void => {\n if (!runtime) {\n return;\n }\n const hasService = components.some((component) => component.kind === \"service\");\n if (runtime.profile === \"panel-only\" && hasService) {\n throw new Error(\"runtime.profile=panel-only 不能包含 Service component。\");\n }\n if (runtime.profile !== \"panel-only\" && !hasService) {\n throw new Error(`${runtime.profile} runtime 必须包含 Service component。`);\n }\n };\n\n private readComponentId = async (\n component: AppComponentReference,\n componentDirectory: string,\n ): Promise<string> => {\n const manifestFile = component.kind === \"panel\" ? \"panel-app.json\" : \"service-app.json\";\n const raw = JSON.parse(\n await readFile(path.join(componentDirectory, manifestFile), \"utf-8\"),\n ) as unknown;\n const manifest = this.assertObject(raw, `${component.path}/${manifestFile}`);\n const id = this.readRequiredString(manifest.id, `${component.path}/${manifestFile}.id`);\n if (!COMPONENT_ID_PATTERN.test(id)) {\n throw new Error(`组件 id 必须是 kebab-case:${id}`);\n }\n const directoryName = path.basename(componentDirectory);\n const expectedId = component.kind === \"panel\"\n ? directoryName.replace(/\\.panel$/, \"\")\n : directoryName;\n if (component.kind === \"panel\" && !directoryName.endsWith(\".panel\")) {\n throw new Error(`Panel component 目录必须以 .panel 结尾:${component.path}`);\n }\n if (id !== expectedId) {\n throw new Error(`组件 id 必须与目录名一致:期望 ${expectedId},实际 ${id}`);\n }\n return id;\n };\n\n private parsePermissions = (rawPermissions: unknown): AppPermissions | undefined => {\n if (rawPermissions === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawPermissions, \"permissions\");\n return {\n documentAccess: this.parseDocumentAccess(candidate.documentAccess),\n allowedDomains: this.parseStringArray(candidate.allowedDomains, \"permissions.allowedDomains\"),\n storage: this.parseStorage(candidate.storage),\n capabilities: this.parseCapabilities(candidate.capabilities),\n };\n };\n\n private parseDocumentAccess = (rawDocumentAccess: unknown): AppDocumentAccessScope[] | undefined => {\n if (rawDocumentAccess === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawDocumentAccess)) {\n throw new Error(\"permissions.documentAccess 必须是数组。\");\n }\n return rawDocumentAccess.map((scope, index) => {\n const candidate = this.assertObject(scope, `permissions.documentAccess[${index}]`);\n const mode = this.readRequiredString(candidate.mode, `permissions.documentAccess[${index}].mode`);\n if (mode !== \"read\" && mode !== \"read-write\") {\n throw new Error(`permissions.documentAccess[${index}].mode 只支持 read 或 read-write。`);\n }\n return {\n id: this.readRequiredString(candidate.id, `permissions.documentAccess[${index}].id`),\n mode,\n description: this.readOptionalString(\n candidate.description,\n `permissions.documentAccess[${index}].description`,\n ),\n };\n });\n };\n\n private parseStringArray = (rawValue: unknown, fieldName: string): string[] | undefined => {\n if (rawValue === undefined) {\n return undefined;\n }\n if (!Array.isArray(rawValue)) {\n throw new Error(`${fieldName} 必须是字符串数组。`);\n }\n return rawValue.map((item, index) => this.readRequiredString(item, `${fieldName}[${index}]`));\n };\n\n private parseStorage = (rawStorage: unknown): AppPermissions[\"storage\"] | undefined => {\n if (rawStorage === undefined || typeof rawStorage === \"boolean\") {\n return rawStorage;\n }\n const candidate = this.assertObject(rawStorage, \"permissions.storage\");\n return { namespace: this.readOptionalString(candidate.namespace, \"permissions.storage.namespace\") };\n };\n\n private parseCapabilities = (rawCapabilities: unknown): AppPermissions[\"capabilities\"] | undefined => {\n if (rawCapabilities === undefined) {\n return undefined;\n }\n const candidate = this.assertObject(rawCapabilities, \"permissions.capabilities\");\n return {\n hostBridge: candidate.hostBridge === undefined\n ? undefined\n : this.readBoolean(candidate.hostBridge, \"permissions.capabilities.hostBridge\"),\n nativeProcess: candidate.nativeProcess === undefined\n ? undefined\n : this.readBoolean(candidate.nativeProcess, \"permissions.capabilities.nativeProcess\"),\n };\n };\n\n private assertResolvedFile = async (\n appDirectory: string,\n relativePath: string,\n fieldName: string,\n ): Promise<string> => {\n const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);\n const stats = await lstat(resolvedPath);\n if (!stats.isFile() || stats.isSymbolicLink()) {\n throw new Error(`${fieldName} 必须指向包内普通文件。`);\n }\n return resolvedPath;\n };\n\n private assertResolvedDirectory = async (\n appDirectory: string,\n relativePath: string,\n fieldName: string,\n ): Promise<string> => {\n const resolvedPath = this.resolveInside(appDirectory, relativePath, fieldName);\n const stats = await lstat(resolvedPath);\n if (!stats.isDirectory() || stats.isSymbolicLink()) {\n throw new Error(`${fieldName} 必须指向包内普通目录。`);\n }\n await access(resolvedPath);\n return resolvedPath;\n };\n\n private resolveInside = (appDirectory: string, relativePath: string, fieldName: string): string => {\n const normalizedPath = this.normalizeRelativePath(relativePath, fieldName);\n const resolvedPath = path.resolve(appDirectory, normalizedPath);\n const relative = path.relative(appDirectory, resolvedPath);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n throw new Error(`${fieldName} 不能指向应用目录之外。`);\n }\n return resolvedPath;\n };\n\n private normalizeRelativePath = (relativePath: string, fieldName: string): string => {\n if (path.isAbsolute(relativePath) || relativePath.includes(\"\\0\")) {\n throw new Error(`${fieldName} 必须使用包内相对路径。`);\n }\n const segments = relativePath.replace(/\\\\/g, \"/\").split(\"/\");\n if (segments.some((segment) => !segment || segment === \".\" || segment === \"..\")) {\n throw new Error(`${fieldName} 包含非法路径片段。`);\n }\n return segments.join(\"/\");\n };\n\n private assertObject = (value: unknown, fieldName: string): Record<string, unknown> => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(`${fieldName} 必须是对象。`);\n }\n return value as Record<string, unknown>;\n };\n\n private readRequiredString = (value: unknown, fieldName: string): string => {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${fieldName} 必须是非空字符串。`);\n }\n return value.trim();\n };\n\n private readOptionalString = (value: unknown, fieldName: string): string | undefined => {\n return value === undefined ? undefined : this.readRequiredString(value, fieldName);\n };\n\n private readNumber = (value: unknown, fieldName: string): number => {\n if (typeof value !== \"number\" || Number.isNaN(value)) {\n throw new Error(`${fieldName} 必须是数字。`);\n }\n return value;\n };\n\n private readBoolean = (value: unknown, fieldName: string): boolean => {\n if (typeof value !== \"boolean\") {\n throw new Error(`${fieldName} 必须是布尔值。`);\n }\n return value;\n };\n}\n"],"mappings":"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"}
|
|
@@ -62,7 +62,7 @@ var AppPublishService = class {
|
|
|
62
62
|
visuals: metadata.visuals,
|
|
63
63
|
distributionMode,
|
|
64
64
|
manifest: manifestBundle.manifest,
|
|
65
|
-
permissions: manifestBundle.manifest.schemaVersion === 1 ? manifestBundle.manifest.permissions ?? {} :
|
|
65
|
+
permissions: manifestBundle.manifest.schemaVersion === 1 ? manifestBundle.manifest.permissions ?? {} : this.manifestService.resolvePlatformSecurity(manifestBundle.manifest).permissions,
|
|
66
66
|
bundleBase64: bundleBytes.toString("base64"),
|
|
67
67
|
bundleSha256,
|
|
68
68
|
files: publishFiles.map((file) => ({
|