@aigne/afs-json 1.11.0-beta.8 → 1.12.0-beta.10
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 +32 -0
- package/dist/index.cjs +213 -34
- package/dist/index.d.cts +26 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +26 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +214 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -9
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { basename, dirname, extname, isAbsolute, join } from "node:path";
|
|
5
|
-
import { AFSNotFoundError } from "@aigne/afs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { AFSConflictError, AFSNotFoundError, getPlatform, orderByRequested, orderEntries } from "@aigne/afs";
|
|
6
4
|
import { AFSBaseProvider, Delete, Explain, List, Meta, Read, Rename, Search, Stat, Write } from "@aigne/afs/provider";
|
|
7
5
|
import { camelize, optionalize, zodParse } from "@aigne/afs/utils/zod";
|
|
6
|
+
import { resolveLocalPath } from "@aigne/afs-provider-utils";
|
|
7
|
+
import { dump, load } from "js-yaml";
|
|
8
8
|
import { joinURL } from "ufo";
|
|
9
|
-
import { parse, stringify } from "yaml";
|
|
10
9
|
import { z } from "zod";
|
|
11
10
|
|
|
12
11
|
//#region src/index.ts
|
|
13
12
|
const LIST_MAX_LIMIT = 1e3;
|
|
13
|
+
const JSON_AS_FORMATS = [
|
|
14
|
+
"content",
|
|
15
|
+
"compact",
|
|
16
|
+
"tree"
|
|
17
|
+
];
|
|
14
18
|
/** Hidden key for storing AFS metadata (mirrors FS provider's .afs directory) */
|
|
15
19
|
const AFS_KEY = ".afs";
|
|
16
20
|
/** Subkey for storing metadata (mirrors FS provider's meta.yaml file) */
|
|
17
21
|
const META_KEY = "meta";
|
|
18
22
|
/** Subkey for storing child node metadata (mirrors FS provider's .nodes directory) */
|
|
19
23
|
const NODES_KEY = ".nodes";
|
|
24
|
+
function assertLocalPathWithinCwd(resolvedPath, cwd, rawPath) {
|
|
25
|
+
const { path } = getPlatform();
|
|
26
|
+
const root = path.resolve(cwd);
|
|
27
|
+
const candidate = path.resolve(resolvedPath);
|
|
28
|
+
const rootPrefix = root.endsWith("/") || root.endsWith("\\") ? root : `${root}/`;
|
|
29
|
+
const windowsRootPrefix = root.endsWith("\\") ? root : `${root}\\`;
|
|
30
|
+
if (candidate !== root && !candidate.startsWith(rootPrefix) && !candidate.startsWith(windowsRootPrefix)) throw new Error(`Local path escapes configured base directory: ${rawPath}`);
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
20
33
|
const afsJSONOptionsSchema = camelize(z.object({
|
|
21
34
|
name: optionalize(z.string()),
|
|
22
35
|
jsonPath: z.string().describe("The path to the JSON/YAML file to mount"),
|
|
@@ -37,7 +50,7 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
37
50
|
static manifest() {
|
|
38
51
|
return {
|
|
39
52
|
name: "json",
|
|
40
|
-
description: "
|
|
53
|
+
description: "JSON or YAML file — navigate and edit structured data as a tree.\n- Objects and arrays become directories, primitives become leaf nodes\n- Read/write individual values, search across structure\n- Path structure: `/{key}/{nested-key}` (arrays indexed by position)",
|
|
41
54
|
uriTemplate: "json://{localPath+}",
|
|
42
55
|
category: "structured-data",
|
|
43
56
|
schema: z.object({ localPath: z.string() }),
|
|
@@ -45,12 +58,56 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
45
58
|
"json",
|
|
46
59
|
"yaml",
|
|
47
60
|
"structured-data"
|
|
48
|
-
]
|
|
61
|
+
],
|
|
62
|
+
capabilityTags: [
|
|
63
|
+
"read-write",
|
|
64
|
+
"crud",
|
|
65
|
+
"search",
|
|
66
|
+
"auth:none",
|
|
67
|
+
"local"
|
|
68
|
+
],
|
|
69
|
+
security: {
|
|
70
|
+
riskLevel: "sandboxed",
|
|
71
|
+
resourceAccess: []
|
|
72
|
+
},
|
|
73
|
+
capabilities: { filesystem: {
|
|
74
|
+
read: true,
|
|
75
|
+
write: true
|
|
76
|
+
} }
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
static treeSchema() {
|
|
80
|
+
return {
|
|
81
|
+
operations: [
|
|
82
|
+
"list",
|
|
83
|
+
"read",
|
|
84
|
+
"write",
|
|
85
|
+
"delete",
|
|
86
|
+
"search",
|
|
87
|
+
"stat",
|
|
88
|
+
"explain"
|
|
89
|
+
],
|
|
90
|
+
tree: {
|
|
91
|
+
"/": { kind: "json:root" },
|
|
92
|
+
"/{key}": { kind: "json:value" }
|
|
93
|
+
},
|
|
94
|
+
auth: { type: "none" },
|
|
95
|
+
bestFor: [
|
|
96
|
+
"JSON/YAML navigation",
|
|
97
|
+
"structured data editing",
|
|
98
|
+
"config files"
|
|
99
|
+
],
|
|
100
|
+
notFor: ["binary files", "large datasets"]
|
|
49
101
|
};
|
|
50
102
|
}
|
|
51
103
|
static async load({ basePath, config } = {}) {
|
|
104
|
+
const raw = config;
|
|
105
|
+
const normalized = raw?.localPath && !raw.jsonPath ? {
|
|
106
|
+
...raw,
|
|
107
|
+
jsonPath: raw.localPath
|
|
108
|
+
} : raw;
|
|
52
109
|
return new AFSJSON({
|
|
53
|
-
...await AFSJSON.schema().parseAsync(
|
|
110
|
+
...await AFSJSON.schema().parseAsync(normalized),
|
|
54
111
|
cwd: basePath
|
|
55
112
|
});
|
|
56
113
|
}
|
|
@@ -67,22 +124,17 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
67
124
|
this.options = options;
|
|
68
125
|
if (options.localPath && !options.jsonPath) options.jsonPath = options.localPath;
|
|
69
126
|
zodParse(afsJSONOptionsSchema, options);
|
|
70
|
-
let jsonPath;
|
|
71
|
-
jsonPath = options.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!existsSync(jsonPath)) {
|
|
75
|
-
mkdirSync(dirname(jsonPath), { recursive: true });
|
|
76
|
-
writeFileSync(jsonPath, "{}", "utf8");
|
|
77
|
-
}
|
|
78
|
-
const ext = extname(jsonPath).toLowerCase();
|
|
127
|
+
let jsonPath = resolveLocalPath(options.jsonPath, { cwd: options.cwd });
|
|
128
|
+
if (options.cwd) jsonPath = assertLocalPathWithinCwd(jsonPath, options.cwd, options.jsonPath);
|
|
129
|
+
const platform = getPlatform();
|
|
130
|
+
const ext = platform.path.extname(jsonPath).toLowerCase();
|
|
79
131
|
this.fileFormat = ext === ".yaml" || ext === ".yml" ? "yaml" : "json";
|
|
80
132
|
const extensions = [
|
|
81
133
|
".json",
|
|
82
134
|
".yaml",
|
|
83
135
|
".yml"
|
|
84
136
|
];
|
|
85
|
-
let name = basename(jsonPath);
|
|
137
|
+
let name = platform.path.basename(jsonPath);
|
|
86
138
|
for (const e of extensions) if (name.endsWith(e)) {
|
|
87
139
|
name = name.slice(0, -e.length);
|
|
88
140
|
break;
|
|
@@ -93,6 +145,12 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
93
145
|
this.accessMode = options.accessMode ?? (options.agentSkills ? "readonly" : "readwrite");
|
|
94
146
|
this.resolvedJsonPath = jsonPath;
|
|
95
147
|
}
|
|
148
|
+
async supportedAs(path) {
|
|
149
|
+
await this.ensureLoaded();
|
|
150
|
+
const normalizedPath = this.normalizePath(path);
|
|
151
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
152
|
+
return this.getValueAtPath(this.jsonData, segments) === void 0 ? [] : [...JSON_AS_FORMATS];
|
|
153
|
+
}
|
|
96
154
|
/**
|
|
97
155
|
* Read metadata for a JSON node via /.meta or /path/.meta
|
|
98
156
|
* Returns stored metadata merged with computed type information
|
|
@@ -144,7 +202,17 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
144
202
|
if (!this.isDirectoryValue(value)) return { data: [] };
|
|
145
203
|
const entries = [];
|
|
146
204
|
const rootChildren = this.getChildren(value);
|
|
147
|
-
const
|
|
205
|
+
const rootChildrenToProcess = rootChildren.length > maxChildren ? rootChildren.slice(0, maxChildren) : rootChildren;
|
|
206
|
+
const orderBy = ctx.options?.orderBy;
|
|
207
|
+
if (maxDepth === 1 && orderByRequested(orderBy)) {
|
|
208
|
+
const { entries: ordered, applied } = orderEntries(rootChildrenToProcess.map((child) => this.valueToAFSEntry(normalizedPath === "/" ? `/${child.key}` : `${normalizedPath}/${child.key}`, child.value)), orderBy);
|
|
209
|
+
const data = ordered.slice(0, limit);
|
|
210
|
+
return applied ? { data } : {
|
|
211
|
+
data,
|
|
212
|
+
meta: { orderByIgnored: true }
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const queue = rootChildrenToProcess.map((child) => ({
|
|
148
216
|
path: normalizedPath === "/" ? `/${child.key}` : `${normalizedPath}/${child.key}`,
|
|
149
217
|
value: child.value,
|
|
150
218
|
depth: 1
|
|
@@ -169,14 +237,37 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
169
237
|
}
|
|
170
238
|
}
|
|
171
239
|
}
|
|
240
|
+
if (orderByRequested(orderBy)) return {
|
|
241
|
+
data: entries,
|
|
242
|
+
meta: { orderByIgnored: true }
|
|
243
|
+
};
|
|
172
244
|
return { data: entries };
|
|
173
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* AFSJSON handles `handleDepth` itself, so it doesn't get the base.ts
|
|
248
|
+
* orderBy fallback — it implements ordering natively in listHandler and
|
|
249
|
+
* declares the sub-capability here (consumed by both base.list's
|
|
250
|
+
* no-silent-drop check and the /.meta/.capabilities manifest).
|
|
251
|
+
*/
|
|
252
|
+
getOperationsDeclaration() {
|
|
253
|
+
const ops = super.getOperationsDeclaration();
|
|
254
|
+
if (typeof ops.list === "boolean" ? ops.list : ops.list?.supported === true) ops.list = {
|
|
255
|
+
supported: true,
|
|
256
|
+
features: { orderBy: true }
|
|
257
|
+
};
|
|
258
|
+
return ops;
|
|
259
|
+
}
|
|
174
260
|
async readHandler(ctx) {
|
|
175
261
|
await this.ensureLoaded();
|
|
176
262
|
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
177
263
|
const segments = this.getPathSegments(normalizedPath);
|
|
178
264
|
const value = this.getValueAtPath(this.jsonData, segments);
|
|
179
265
|
if (value === void 0) throw new AFSNotFoundError(normalizedPath);
|
|
266
|
+
const as = ctx.options?.as;
|
|
267
|
+
if (as) {
|
|
268
|
+
if (!this.isJsonAsFormat(as)) throw new AFSNotFoundError(joinURL(normalizedPath, ".as", as));
|
|
269
|
+
return this.buildJsonAsEntry(normalizedPath, value, as);
|
|
270
|
+
}
|
|
180
271
|
return this.valueToAFSEntry(normalizedPath, value);
|
|
181
272
|
}
|
|
182
273
|
/**
|
|
@@ -192,6 +283,15 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
192
283
|
await this.ensureLoaded();
|
|
193
284
|
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
194
285
|
const segments = this.getPathSegments(normalizedPath);
|
|
286
|
+
const ifMatch = ctx.options?.ifMatch;
|
|
287
|
+
if (ifMatch !== void 0) {
|
|
288
|
+
const currentValue = this.getValueAtPath(this.jsonData, segments);
|
|
289
|
+
const currentVersion = this.computeVersion(currentValue);
|
|
290
|
+
if (ifMatch !== currentVersion) throw new AFSConflictError(normalizedPath, {
|
|
291
|
+
cid: currentVersion,
|
|
292
|
+
mtime: this.fileStats.mtime ? this.fileStats.mtime.getTime() : null
|
|
293
|
+
});
|
|
294
|
+
}
|
|
195
295
|
if (payload.content !== void 0) this.setValueAtPath(this.jsonData, segments, payload.content);
|
|
196
296
|
if (payload.meta !== void 0 && typeof payload.meta === "object") {
|
|
197
297
|
const finalMeta = {
|
|
@@ -214,7 +314,8 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
214
314
|
updatedAt: this.fileStats.mtime,
|
|
215
315
|
meta: {
|
|
216
316
|
...storedMeta,
|
|
217
|
-
childrenCount: isDir ? children.length : void 0
|
|
317
|
+
childrenCount: isDir ? children.length : void 0,
|
|
318
|
+
version: this.computeVersion(newValue)
|
|
218
319
|
},
|
|
219
320
|
userId: payload.userId,
|
|
220
321
|
sessionId: payload.sessionId,
|
|
@@ -290,6 +391,7 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
290
391
|
const children = isDir ? this.getChildren(value) : [];
|
|
291
392
|
const meta = { ...this.loadMeta(normalizedPath) };
|
|
292
393
|
if (isDir) meta.childrenCount = children.length;
|
|
394
|
+
meta.version = this.computeVersion(value);
|
|
293
395
|
return { data: {
|
|
294
396
|
id: segments.length > 0 ? segments[segments.length - 1] : "/",
|
|
295
397
|
path: normalizedPath,
|
|
@@ -298,6 +400,16 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
298
400
|
meta
|
|
299
401
|
} };
|
|
300
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Compute an opaque version string for a JSON value at a given path.
|
|
405
|
+
* Used as the ifMatch token for optimistic-concurrency writes. SHA-256 over
|
|
406
|
+
* the canonical JSON serialization keeps the token stable across mounts of
|
|
407
|
+
* the same content. Clients treat this as opaque — see AFSWriteOptions.ifMatch.
|
|
408
|
+
*/
|
|
409
|
+
computeVersion(value) {
|
|
410
|
+
const canonical = value === void 0 ? " undefined" : JSON.stringify(value) ?? "null";
|
|
411
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
412
|
+
}
|
|
301
413
|
async readCapabilitiesHandler(_ctx) {
|
|
302
414
|
await this.ensureLoaded();
|
|
303
415
|
const operations = [
|
|
@@ -308,6 +420,11 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
308
420
|
"search"
|
|
309
421
|
];
|
|
310
422
|
if (this.accessMode === "readwrite") operations.push("write", "delete", "rename");
|
|
423
|
+
const baseOps = this.getOperationsDeclaration();
|
|
424
|
+
if (typeof baseOps.write === "boolean" ? baseOps.write : baseOps.write?.supported === true) baseOps.write = {
|
|
425
|
+
supported: true,
|
|
426
|
+
features: { ifMatch: true }
|
|
427
|
+
};
|
|
311
428
|
return {
|
|
312
429
|
id: "/.meta/.capabilities",
|
|
313
430
|
path: "/.meta/.capabilities",
|
|
@@ -317,7 +434,7 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
317
434
|
description: this.description || `JSON/YAML virtual filesystem (${this.fileFormat} format)`,
|
|
318
435
|
tools: [],
|
|
319
436
|
actions: [],
|
|
320
|
-
operations:
|
|
437
|
+
operations: baseOps
|
|
321
438
|
},
|
|
322
439
|
meta: {
|
|
323
440
|
kind: "afs:capabilities",
|
|
@@ -491,16 +608,20 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
491
608
|
*/
|
|
492
609
|
async ensureLoaded() {
|
|
493
610
|
if (this.jsonData !== null) return;
|
|
611
|
+
const platform = getPlatform();
|
|
494
612
|
try {
|
|
495
|
-
const stats = await stat(this.resolvedJsonPath);
|
|
613
|
+
const stats = await platform.fs.stat(this.resolvedJsonPath);
|
|
496
614
|
this.fileStats = {
|
|
497
|
-
birthtime: stats.birthtime,
|
|
498
|
-
mtime: stats.mtime
|
|
615
|
+
birthtime: stats.birthtime ? new Date(stats.birthtime) : void 0,
|
|
616
|
+
mtime: stats.mtime ? new Date(stats.mtime) : void 0
|
|
499
617
|
};
|
|
500
|
-
this.jsonData =
|
|
501
|
-
} catch
|
|
502
|
-
|
|
503
|
-
|
|
618
|
+
this.jsonData = load(await platform.fs.readTextFile(this.resolvedJsonPath));
|
|
619
|
+
} catch {
|
|
620
|
+
try {
|
|
621
|
+
await platform.fs.mkdir(platform.path.dirname(this.resolvedJsonPath), { recursive: true });
|
|
622
|
+
await platform.fs.writeFile(this.resolvedJsonPath, "{}");
|
|
623
|
+
} catch {}
|
|
624
|
+
this.jsonData = {};
|
|
504
625
|
}
|
|
505
626
|
}
|
|
506
627
|
/**
|
|
@@ -508,22 +629,34 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
508
629
|
*/
|
|
509
630
|
async saveToFile() {
|
|
510
631
|
let content;
|
|
511
|
-
if (this.fileFormat === "yaml") content =
|
|
632
|
+
if (this.fileFormat === "yaml") content = dump(this.jsonData);
|
|
512
633
|
else content = JSON.stringify(this.jsonData, null, 2);
|
|
513
|
-
|
|
514
|
-
|
|
634
|
+
const platform = getPlatform();
|
|
635
|
+
await platform.fs.writeFile(this.resolvedJsonPath, content);
|
|
636
|
+
const stats = await platform.fs.stat(this.resolvedJsonPath);
|
|
515
637
|
this.fileStats = {
|
|
516
|
-
birthtime: this.fileStats.birthtime
|
|
517
|
-
mtime: stats.mtime
|
|
638
|
+
birthtime: this.fileStats.birthtime,
|
|
639
|
+
mtime: stats.mtime ? new Date(stats.mtime) : void 0
|
|
518
640
|
};
|
|
519
641
|
}
|
|
520
642
|
/**
|
|
521
643
|
* Get path segments from normalized path
|
|
522
644
|
*/
|
|
645
|
+
/**
|
|
646
|
+
* Dangerous property names that must never be used as object keys.
|
|
647
|
+
* Prevents prototype pollution attacks (e.g., writing to /__proto__/polluted).
|
|
648
|
+
*/
|
|
649
|
+
static DANGEROUS_KEYS = new Set([
|
|
650
|
+
"__proto__",
|
|
651
|
+
"constructor",
|
|
652
|
+
"prototype"
|
|
653
|
+
]);
|
|
523
654
|
getPathSegments(path) {
|
|
524
655
|
const normalized = this.normalizePath(path);
|
|
525
656
|
if (normalized === "/") return [];
|
|
526
|
-
|
|
657
|
+
const segments = normalized.slice(1).split("/");
|
|
658
|
+
for (const segment of segments) if (AFSJSON.DANGEROUS_KEYS.has(segment)) throw new Error(`Path segment "${segment}" is not allowed (prototype pollution guard)`);
|
|
659
|
+
return segments;
|
|
527
660
|
}
|
|
528
661
|
/**
|
|
529
662
|
* Navigate to a value in the JSON structure using path segments
|
|
@@ -608,6 +741,9 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
608
741
|
if (typeof value === "object" && value !== null) return true;
|
|
609
742
|
return false;
|
|
610
743
|
}
|
|
744
|
+
isJsonAsFormat(value) {
|
|
745
|
+
return typeof value === "string" && JSON_AS_FORMATS.includes(value);
|
|
746
|
+
}
|
|
611
747
|
/**
|
|
612
748
|
* Get children of a directory value (filters out .afs meta key)
|
|
613
749
|
*/
|
|
@@ -622,6 +758,48 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
622
758
|
}));
|
|
623
759
|
return [];
|
|
624
760
|
}
|
|
761
|
+
compactJsonValue(value) {
|
|
762
|
+
if (!this.isDirectoryValue(value)) return value;
|
|
763
|
+
if (Array.isArray(value)) return {
|
|
764
|
+
type: "array",
|
|
765
|
+
length: value.length,
|
|
766
|
+
sample: value.slice(0, 3).map((item) => this.compactJsonValue(item))
|
|
767
|
+
};
|
|
768
|
+
const entries = Object.entries(value).filter(([key]) => !this.isMetaKey(key));
|
|
769
|
+
const preview = {};
|
|
770
|
+
for (const [key, child] of entries) {
|
|
771
|
+
if (Object.keys(preview).length >= 6) break;
|
|
772
|
+
if (!this.isDirectoryValue(child)) preview[key] = child;
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
type: "object",
|
|
776
|
+
keys: entries.map(([key]) => key).slice(0, 12),
|
|
777
|
+
...Object.keys(preview).length > 0 ? { preview } : {}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
stripMetaFromValue(value) {
|
|
781
|
+
if (!this.isDirectoryValue(value)) return value;
|
|
782
|
+
if (Array.isArray(value)) return value.map((item) => this.stripMetaFromValue(item));
|
|
783
|
+
const result = {};
|
|
784
|
+
for (const [key, child] of Object.entries(value)) if (!this.isMetaKey(key)) result[key] = this.stripMetaFromValue(child);
|
|
785
|
+
return result;
|
|
786
|
+
}
|
|
787
|
+
buildJsonAsEntry(path, value, as) {
|
|
788
|
+
let content;
|
|
789
|
+
if (as === "content") content = value;
|
|
790
|
+
else if (as === "tree") content = this.stripMetaFromValue(value);
|
|
791
|
+
else content = this.compactJsonValue(value);
|
|
792
|
+
return {
|
|
793
|
+
id: `${path}.as:${as}`,
|
|
794
|
+
path: joinURL(path, ".as", as),
|
|
795
|
+
content,
|
|
796
|
+
meta: {
|
|
797
|
+
kind: `json:${as}`,
|
|
798
|
+
representation: as
|
|
799
|
+
},
|
|
800
|
+
actions: []
|
|
801
|
+
};
|
|
802
|
+
}
|
|
625
803
|
/**
|
|
626
804
|
* Convert a JSON value to an AFSEntry
|
|
627
805
|
*/
|
|
@@ -633,7 +811,8 @@ var AFSJSON = class AFSJSON extends AFSBaseProvider {
|
|
|
633
811
|
content: isDir ? void 0 : value,
|
|
634
812
|
meta: {
|
|
635
813
|
kind,
|
|
636
|
-
childrenCount: isDir ? children.length : void 0
|
|
814
|
+
childrenCount: isDir ? children.length : void 0,
|
|
815
|
+
version: this.computeVersion(value)
|
|
637
816
|
},
|
|
638
817
|
createdAt: this.fileStats.birthtime,
|
|
639
818
|
updatedAt: this.fileStats.mtime
|