@tinacms/graphql 0.0.0-04a60ca-20260217063130 → 0.0.0-059f480-20260324232913
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.
|
@@ -3,6 +3,10 @@ import type { Bridge } from './index';
|
|
|
3
3
|
* This is the bridge from whatever datasource we need for I/O.
|
|
4
4
|
* The basic example here is for the filesystem, one is needed
|
|
5
5
|
* for GitHub has well.
|
|
6
|
+
*
|
|
7
|
+
* @security All public methods validate their `filepath` / `pattern`
|
|
8
|
+
* argument via `assertWithinBase` before performing any I/O. If you add a
|
|
9
|
+
* new method that accepts a path, you MUST validate it the same way.
|
|
6
10
|
*/
|
|
7
11
|
export declare class FilesystemBridge implements Bridge {
|
|
8
12
|
rootPath: string;
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* I/O abstraction layer for reading/writing content files.
|
|
3
|
+
*
|
|
4
|
+
* @security **Path traversal (CWE-22):** All `filepath` and `pattern`
|
|
5
|
+
* parameters may originate from user input (e.g. GraphQL mutations or media
|
|
6
|
+
* API requests). Implementations MUST validate that resolved paths stay
|
|
7
|
+
* within their root/output directory before performing any filesystem
|
|
8
|
+
* operation. See `FilesystemBridge.assertWithinBase` and
|
|
9
|
+
* `IsomorphicBridge.assertWithinBase` for reference implementations.
|
|
10
|
+
*
|
|
11
|
+
* The recommended validation pattern is:
|
|
12
|
+
* ```ts
|
|
13
|
+
* const resolved = path.resolve(path.join(baseDir, filepath));
|
|
14
|
+
* if (!resolved.startsWith(path.resolve(baseDir) + path.sep)) {
|
|
15
|
+
* throw new Error('Path traversal detected');
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* If you are adding a new Bridge implementation, add path traversal
|
|
20
|
+
* validation to every method that accepts a filepath from the caller.
|
|
21
|
+
*/
|
|
1
22
|
export interface Bridge {
|
|
2
23
|
rootPath: string;
|
|
24
|
+
/**
|
|
25
|
+
* @param pattern - Glob pattern prefix (untrusted — validate before use).
|
|
26
|
+
* @param extension - File extension to match.
|
|
27
|
+
*/
|
|
3
28
|
glob(pattern: string, extension: string): Promise<string[]>;
|
|
29
|
+
/** @param filepath - Relative path to delete (untrusted — validate before use). */
|
|
4
30
|
delete(filepath: string): Promise<void>;
|
|
31
|
+
/** @param filepath - Relative path to read (untrusted — validate before use). */
|
|
5
32
|
get(filepath: string): Promise<string>;
|
|
33
|
+
/** @param filepath - Relative path to write (untrusted — validate before use). */
|
|
6
34
|
put(filepath: string, data: string): Promise<void>;
|
|
7
35
|
/**
|
|
8
36
|
* Optionally, the bridge can perform
|
|
@@ -18,6 +18,11 @@ export type IsomorphicGitBridgeOptions = {
|
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* Bridge backed by isomorphic-git
|
|
21
|
+
*
|
|
22
|
+
* @security All public methods (glob, get, put, delete) validate their
|
|
23
|
+
* `filepath` / `pattern` argument via `assertWithinBase` before performing
|
|
24
|
+
* any git operations. If you add a new method that accepts a path, you
|
|
25
|
+
* MUST validate it the same way.
|
|
21
26
|
*/
|
|
22
27
|
export declare class IsomorphicBridge implements Bridge {
|
|
23
28
|
rootPath: string;
|
package/dist/database/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface DatabaseArgs {
|
|
|
19
19
|
indexStatusCallback?: IndexStatusCallback;
|
|
20
20
|
version?: boolean;
|
|
21
21
|
namespace?: string;
|
|
22
|
+
levelBatchSize?: number;
|
|
22
23
|
}
|
|
23
24
|
export interface GitProvider {
|
|
24
25
|
onPut: (key: string, value: string) => Promise<void>;
|
|
@@ -69,6 +70,7 @@ export declare class Database {
|
|
|
69
70
|
indexStatusCallback: IndexStatusCallback | undefined;
|
|
70
71
|
private readonly onPut;
|
|
71
72
|
private readonly onDelete;
|
|
73
|
+
private readonly levelBatchSize;
|
|
72
74
|
private tinaSchema;
|
|
73
75
|
private contentNamespace;
|
|
74
76
|
private collectionIndexDefinitions;
|
package/dist/index.js
CHANGED
|
@@ -45,8 +45,8 @@ var btoa = (string2) => {
|
|
|
45
45
|
var lastItem = (arr) => {
|
|
46
46
|
return arr[arr.length - 1];
|
|
47
47
|
};
|
|
48
|
-
var get = (obj,
|
|
49
|
-
const travel = (regexp) => String.prototype.split.call(
|
|
48
|
+
var get = (obj, path9, defaultValue = void 0) => {
|
|
49
|
+
const travel = (regexp) => String.prototype.split.call(path9, regexp).filter(Boolean).reduce(
|
|
50
50
|
(res, key) => res !== null && res !== void 0 ? res[key] : res,
|
|
51
51
|
obj
|
|
52
52
|
);
|
|
@@ -3026,7 +3026,7 @@ var validateField = async (field) => {
|
|
|
3026
3026
|
var package_default = {
|
|
3027
3027
|
name: "@tinacms/graphql",
|
|
3028
3028
|
type: "module",
|
|
3029
|
-
version: "2.
|
|
3029
|
+
version: "2.2.0",
|
|
3030
3030
|
main: "dist/index.js",
|
|
3031
3031
|
module: "./dist/index.js",
|
|
3032
3032
|
files: [
|
|
@@ -3053,7 +3053,7 @@ var package_default = {
|
|
|
3053
3053
|
types: "pnpm tsc",
|
|
3054
3054
|
build: "tinacms-scripts build",
|
|
3055
3055
|
docs: "pnpm typedoc",
|
|
3056
|
-
test: "vitest run",
|
|
3056
|
+
test: "vitest run --coverage.enabled",
|
|
3057
3057
|
"test-watch": "vitest"
|
|
3058
3058
|
},
|
|
3059
3059
|
dependencies: {
|
|
@@ -3099,6 +3099,7 @@ var package_default = {
|
|
|
3099
3099
|
"@types/node": "^22.13.1",
|
|
3100
3100
|
"@types/normalize-path": "catalog:",
|
|
3101
3101
|
"@types/ws": "catalog:",
|
|
3102
|
+
"@vitest/coverage-v8": "0.32.4",
|
|
3102
3103
|
"jest-file-snapshot": "^0.5.0",
|
|
3103
3104
|
"memory-level": "catalog:",
|
|
3104
3105
|
typescript: "^5.7.3",
|
|
@@ -3662,17 +3663,17 @@ var scanAllContent = async (tinaSchema, bridge, callback) => {
|
|
|
3662
3663
|
const documentPaths = await bridge.glob(normalPath, format);
|
|
3663
3664
|
const matches = tinaSchema.getMatches({ collection });
|
|
3664
3665
|
const filteredPaths = matches.length > 0 ? micromatch(documentPaths, matches) : documentPaths;
|
|
3665
|
-
filteredPaths.forEach((
|
|
3666
|
-
if (filesSeen.has(
|
|
3667
|
-
filesSeen.get(
|
|
3668
|
-
duplicateFiles.add(
|
|
3666
|
+
filteredPaths.forEach((path9) => {
|
|
3667
|
+
if (filesSeen.has(path9)) {
|
|
3668
|
+
filesSeen.get(path9).push(collection.name);
|
|
3669
|
+
duplicateFiles.add(path9);
|
|
3669
3670
|
} else {
|
|
3670
|
-
filesSeen.set(
|
|
3671
|
+
filesSeen.set(path9, [collection.name]);
|
|
3671
3672
|
}
|
|
3672
3673
|
});
|
|
3673
|
-
duplicateFiles.forEach((
|
|
3674
|
+
duplicateFiles.forEach((path9) => {
|
|
3674
3675
|
warnings.push(
|
|
3675
|
-
`"${
|
|
3676
|
+
`"${path9}" Found in multiple collections: ${filesSeen.get(path9).map((collection2) => `"${collection2}"`).join(
|
|
3676
3677
|
", "
|
|
3677
3678
|
)}. This can cause unexpected behavior. We recommend updating the \`match\` property of those collections so that each file is in only one collection.
|
|
3678
3679
|
This will be an error in the future. See https://tina.io/docs/errors/file-in-mutpliple-collections/
|
|
@@ -4235,9 +4236,9 @@ var makeFilterSuffixes = (filterChain, index) => {
|
|
|
4235
4236
|
}
|
|
4236
4237
|
};
|
|
4237
4238
|
var FOLDER_ROOT = "~";
|
|
4238
|
-
var stripCollectionFromPath = (collectionPath,
|
|
4239
|
+
var stripCollectionFromPath = (collectionPath, path9) => {
|
|
4239
4240
|
const collectionPathParts = collectionPath.split("/");
|
|
4240
|
-
const pathParts =
|
|
4241
|
+
const pathParts = path9.split("/");
|
|
4241
4242
|
const strippedPathParts = pathParts.slice(collectionPathParts.length);
|
|
4242
4243
|
return strippedPathParts.join("/");
|
|
4243
4244
|
};
|
|
@@ -4293,13 +4294,13 @@ var makeFolderOpsForCollection = (folderTree, collection, indexDefinitions, opTy
|
|
|
4293
4294
|
SUBLEVEL_OPTIONS
|
|
4294
4295
|
);
|
|
4295
4296
|
let folderSortingIdx = 0;
|
|
4296
|
-
for (const
|
|
4297
|
+
for (const path9 of Array.from(folder).sort()) {
|
|
4297
4298
|
for (const [sort] of Object.entries(indexDefinitions)) {
|
|
4298
4299
|
const indexSublevel = folderCollectionSublevel.sublevel(
|
|
4299
4300
|
sort,
|
|
4300
4301
|
SUBLEVEL_OPTIONS
|
|
4301
4302
|
);
|
|
4302
|
-
const subFolderKey = sha.hex(
|
|
4303
|
+
const subFolderKey = sha.hex(path9);
|
|
4303
4304
|
if (sort === DEFAULT_COLLECTION_SORT_KEY) {
|
|
4304
4305
|
result.push({
|
|
4305
4306
|
type: opType,
|
|
@@ -4381,8 +4382,8 @@ var makeRefOpsForDocument = (filepath, collection, references, data, opType, lev
|
|
|
4381
4382
|
SUBLEVEL_OPTIONS
|
|
4382
4383
|
);
|
|
4383
4384
|
const references2 = {};
|
|
4384
|
-
for (const
|
|
4385
|
-
const ref = JSONPath({ path:
|
|
4385
|
+
for (const path9 of referencePaths) {
|
|
4386
|
+
const ref = JSONPath({ path: path9, json: data });
|
|
4386
4387
|
if (!ref) {
|
|
4387
4388
|
continue;
|
|
4388
4389
|
}
|
|
@@ -4392,24 +4393,24 @@ var makeRefOpsForDocument = (filepath, collection, references, data, opType, lev
|
|
|
4392
4393
|
continue;
|
|
4393
4394
|
}
|
|
4394
4395
|
if (references2[r]) {
|
|
4395
|
-
references2[r].push(
|
|
4396
|
+
references2[r].push(path9);
|
|
4396
4397
|
} else {
|
|
4397
|
-
references2[r] = [
|
|
4398
|
+
references2[r] = [path9];
|
|
4398
4399
|
}
|
|
4399
4400
|
}
|
|
4400
4401
|
} else {
|
|
4401
4402
|
if (references2[ref]) {
|
|
4402
|
-
references2[ref].push(
|
|
4403
|
+
references2[ref].push(path9);
|
|
4403
4404
|
} else {
|
|
4404
|
-
references2[ref] = [
|
|
4405
|
+
references2[ref] = [path9];
|
|
4405
4406
|
}
|
|
4406
4407
|
}
|
|
4407
4408
|
}
|
|
4408
4409
|
for (const ref of Object.keys(references2)) {
|
|
4409
|
-
for (const
|
|
4410
|
+
for (const path9 of references2[ref]) {
|
|
4410
4411
|
result.push({
|
|
4411
4412
|
type: opType,
|
|
4412
|
-
key: `${ref}${INDEX_KEY_FIELD_SEPARATOR}${
|
|
4413
|
+
key: `${ref}${INDEX_KEY_FIELD_SEPARATOR}${path9}${INDEX_KEY_FIELD_SEPARATOR}${filepath}`,
|
|
4413
4414
|
sublevel: refSublevel,
|
|
4414
4415
|
value: opType === "put" ? {} : void 0
|
|
4415
4416
|
});
|
|
@@ -4676,9 +4677,9 @@ var resolveMediaRelativeToCloud = (value, config = { useRelativeMedia: true }, s
|
|
|
4676
4677
|
return value;
|
|
4677
4678
|
}
|
|
4678
4679
|
};
|
|
4679
|
-
var cleanUpSlashes = (
|
|
4680
|
-
if (
|
|
4681
|
-
return `/${
|
|
4680
|
+
var cleanUpSlashes = (path9) => {
|
|
4681
|
+
if (path9) {
|
|
4682
|
+
return `/${path9.replace(/^\/+|\/+$/gm, "")}`;
|
|
4682
4683
|
}
|
|
4683
4684
|
return "";
|
|
4684
4685
|
};
|
|
@@ -4888,17 +4889,17 @@ var transformDocumentIntoPayload = async (fullPath, rawData, tinaSchema, config,
|
|
|
4888
4889
|
throw e;
|
|
4889
4890
|
}
|
|
4890
4891
|
};
|
|
4891
|
-
var updateObjectWithJsonPath = (obj,
|
|
4892
|
+
var updateObjectWithJsonPath = (obj, path9, oldValue, newValue) => {
|
|
4892
4893
|
let updated = false;
|
|
4893
|
-
if (!
|
|
4894
|
-
if (
|
|
4895
|
-
obj[
|
|
4894
|
+
if (!path9.includes(".") && !path9.includes("[")) {
|
|
4895
|
+
if (path9 in obj && obj[path9] === oldValue) {
|
|
4896
|
+
obj[path9] = newValue;
|
|
4896
4897
|
updated = true;
|
|
4897
4898
|
}
|
|
4898
4899
|
return { object: obj, updated };
|
|
4899
4900
|
}
|
|
4900
|
-
const parentPath =
|
|
4901
|
-
const keyToUpdate =
|
|
4901
|
+
const parentPath = path9.replace(/\.[^.\[\]]+$/, "");
|
|
4902
|
+
const keyToUpdate = path9.match(/[^.\[\]]+$/)[0];
|
|
4902
4903
|
const parents = JSONPath2({
|
|
4903
4904
|
path: parentPath,
|
|
4904
4905
|
json: obj,
|
|
@@ -5322,10 +5323,10 @@ var Resolver = class {
|
|
|
5322
5323
|
)) {
|
|
5323
5324
|
let docWithRef = await this.getRaw(pathToDocWithRef);
|
|
5324
5325
|
let hasUpdate = false;
|
|
5325
|
-
for (const
|
|
5326
|
+
for (const path9 of referencePaths) {
|
|
5326
5327
|
const { object: object2, updated } = updateObjectWithJsonPath(
|
|
5327
5328
|
docWithRef,
|
|
5328
|
-
|
|
5329
|
+
path9,
|
|
5329
5330
|
realPath,
|
|
5330
5331
|
newRealPath
|
|
5331
5332
|
);
|
|
@@ -5390,10 +5391,10 @@ var Resolver = class {
|
|
|
5390
5391
|
)) {
|
|
5391
5392
|
let refDoc = await this.getRaw(pathToDocWithRef);
|
|
5392
5393
|
let hasUpdate = false;
|
|
5393
|
-
for (const
|
|
5394
|
+
for (const path9 of referencePaths) {
|
|
5394
5395
|
const { object: object2, updated } = updateObjectWithJsonPath(
|
|
5395
5396
|
refDoc,
|
|
5396
|
-
|
|
5397
|
+
path9,
|
|
5397
5398
|
realPath,
|
|
5398
5399
|
null
|
|
5399
5400
|
);
|
|
@@ -5660,7 +5661,7 @@ var Resolver = class {
|
|
|
5660
5661
|
first: -1
|
|
5661
5662
|
},
|
|
5662
5663
|
collection: referencedCollection,
|
|
5663
|
-
hydrator: (
|
|
5664
|
+
hydrator: (path9) => path9
|
|
5664
5665
|
// just return the path
|
|
5665
5666
|
}
|
|
5666
5667
|
);
|
|
@@ -6101,8 +6102,8 @@ async function handleUpdatePassword({
|
|
|
6101
6102
|
// src/error.ts
|
|
6102
6103
|
import { GraphQLError as GraphQLError3 } from "graphql";
|
|
6103
6104
|
var NotFoundError = class extends GraphQLError3 {
|
|
6104
|
-
constructor(message, nodes, source, positions,
|
|
6105
|
-
super(message, nodes, source, positions,
|
|
6105
|
+
constructor(message, nodes, source, positions, path9, originalError, extensions) {
|
|
6106
|
+
super(message, nodes, source, positions, path9, originalError, extensions);
|
|
6106
6107
|
this.name = "NotFoundError";
|
|
6107
6108
|
}
|
|
6108
6109
|
};
|
|
@@ -6598,6 +6599,7 @@ var createDatabaseInternal = (config) => {
|
|
|
6598
6599
|
});
|
|
6599
6600
|
};
|
|
6600
6601
|
var SYSTEM_FILES = ["_schema", "_graphql", "_lookup"];
|
|
6602
|
+
var DEFAULT_LEVEL_BATCH_SIZE = 25;
|
|
6601
6603
|
var defaultStatusCallback = () => Promise.resolve();
|
|
6602
6604
|
var defaultOnPut = () => Promise.resolve();
|
|
6603
6605
|
var defaultOnDelete = () => Promise.resolve();
|
|
@@ -6610,6 +6612,7 @@ var Database = class {
|
|
|
6610
6612
|
this.indexStatusCallback = config.indexStatusCallback || defaultStatusCallback;
|
|
6611
6613
|
this.onPut = config.onPut || defaultOnPut;
|
|
6612
6614
|
this.onDelete = config.onDelete || defaultOnDelete;
|
|
6615
|
+
this.levelBatchSize = config.levelBatchSize ?? DEFAULT_LEVEL_BATCH_SIZE;
|
|
6613
6616
|
this.contentNamespace = config.namespace;
|
|
6614
6617
|
}
|
|
6615
6618
|
bridge;
|
|
@@ -6620,6 +6623,7 @@ var Database = class {
|
|
|
6620
6623
|
indexStatusCallback;
|
|
6621
6624
|
onPut;
|
|
6622
6625
|
onDelete;
|
|
6626
|
+
levelBatchSize;
|
|
6623
6627
|
tinaSchema;
|
|
6624
6628
|
contentNamespace;
|
|
6625
6629
|
collectionIndexDefinitions;
|
|
@@ -7364,21 +7368,21 @@ var Database = class {
|
|
|
7364
7368
|
edges,
|
|
7365
7369
|
async ({
|
|
7366
7370
|
cursor,
|
|
7367
|
-
path:
|
|
7371
|
+
path: path9,
|
|
7368
7372
|
value
|
|
7369
7373
|
}) => {
|
|
7370
7374
|
try {
|
|
7371
|
-
const node = await hydrator(
|
|
7375
|
+
const node = await hydrator(path9, value);
|
|
7372
7376
|
return {
|
|
7373
7377
|
node,
|
|
7374
7378
|
cursor: btoa(cursor)
|
|
7375
7379
|
};
|
|
7376
7380
|
} catch (error) {
|
|
7377
7381
|
console.log(error);
|
|
7378
|
-
if (error instanceof Error && (!
|
|
7382
|
+
if (error instanceof Error && (!path9.includes(".tina/__generated__/_graphql.json") || !path9.includes("tina/__generated__/_graphql.json"))) {
|
|
7379
7383
|
throw new TinaQueryError({
|
|
7380
7384
|
originalError: error,
|
|
7381
|
-
file:
|
|
7385
|
+
file: path9,
|
|
7382
7386
|
collection: collection.name
|
|
7383
7387
|
});
|
|
7384
7388
|
}
|
|
@@ -7484,8 +7488,10 @@ var Database = class {
|
|
|
7484
7488
|
const operations = [];
|
|
7485
7489
|
const enqueueOps = async (ops) => {
|
|
7486
7490
|
operations.push(...ops);
|
|
7487
|
-
while (operations.length >=
|
|
7488
|
-
await this.contentLevel.batch(
|
|
7491
|
+
while (operations.length >= this.levelBatchSize) {
|
|
7492
|
+
await this.contentLevel.batch(
|
|
7493
|
+
operations.splice(0, this.levelBatchSize)
|
|
7494
|
+
);
|
|
7489
7495
|
}
|
|
7490
7496
|
};
|
|
7491
7497
|
const tinaSchema = await this.getSchema(this.contentLevel);
|
|
@@ -7504,7 +7510,7 @@ var Database = class {
|
|
|
7504
7510
|
}
|
|
7505
7511
|
});
|
|
7506
7512
|
while (operations.length) {
|
|
7507
|
-
await this.contentLevel.batch(operations.splice(0,
|
|
7513
|
+
await this.contentLevel.batch(operations.splice(0, this.levelBatchSize));
|
|
7508
7514
|
}
|
|
7509
7515
|
};
|
|
7510
7516
|
indexContentByPaths = async (documentPaths) => {
|
|
@@ -7512,8 +7518,10 @@ var Database = class {
|
|
|
7512
7518
|
const operations = [];
|
|
7513
7519
|
const enqueueOps = async (ops) => {
|
|
7514
7520
|
operations.push(...ops);
|
|
7515
|
-
while (operations.length >=
|
|
7516
|
-
await this.contentLevel.batch(
|
|
7521
|
+
while (operations.length >= this.levelBatchSize) {
|
|
7522
|
+
await this.contentLevel.batch(
|
|
7523
|
+
operations.splice(0, this.levelBatchSize)
|
|
7524
|
+
);
|
|
7517
7525
|
}
|
|
7518
7526
|
};
|
|
7519
7527
|
const tinaSchema = await this.getSchema(this.contentLevel);
|
|
@@ -7536,7 +7544,7 @@ var Database = class {
|
|
|
7536
7544
|
);
|
|
7537
7545
|
});
|
|
7538
7546
|
while (operations.length) {
|
|
7539
|
-
await this.contentLevel.batch(operations.splice(0,
|
|
7547
|
+
await this.contentLevel.batch(operations.splice(0, this.levelBatchSize));
|
|
7540
7548
|
}
|
|
7541
7549
|
};
|
|
7542
7550
|
delete = async (filepath) => {
|
|
@@ -7620,8 +7628,8 @@ var Database = class {
|
|
|
7620
7628
|
const operations = [];
|
|
7621
7629
|
const enqueueOps = async (ops) => {
|
|
7622
7630
|
operations.push(...ops);
|
|
7623
|
-
while (operations.length >=
|
|
7624
|
-
const batchOps = operations.splice(0,
|
|
7631
|
+
while (operations.length >= this.levelBatchSize) {
|
|
7632
|
+
const batchOps = operations.splice(0, this.levelBatchSize);
|
|
7625
7633
|
await level.batch(batchOps);
|
|
7626
7634
|
}
|
|
7627
7635
|
};
|
|
@@ -7661,13 +7669,13 @@ var Database = class {
|
|
|
7661
7669
|
}
|
|
7662
7670
|
);
|
|
7663
7671
|
while (operations.length) {
|
|
7664
|
-
await level.batch(operations.splice(0,
|
|
7672
|
+
await level.batch(operations.splice(0, this.levelBatchSize));
|
|
7665
7673
|
}
|
|
7666
7674
|
return { warnings };
|
|
7667
7675
|
};
|
|
7668
7676
|
};
|
|
7669
|
-
var hashPasswordVisitor = async (node,
|
|
7670
|
-
const passwordValuePath = [...
|
|
7677
|
+
var hashPasswordVisitor = async (node, path9) => {
|
|
7678
|
+
const passwordValuePath = [...path9, "value"];
|
|
7671
7679
|
const plaintextPassword = get(node, passwordValuePath);
|
|
7672
7680
|
if (plaintextPassword) {
|
|
7673
7681
|
set2(
|
|
@@ -7677,10 +7685,10 @@ var hashPasswordVisitor = async (node, path8) => {
|
|
|
7677
7685
|
);
|
|
7678
7686
|
}
|
|
7679
7687
|
};
|
|
7680
|
-
var visitNodes = async (node,
|
|
7681
|
-
const [currentLevel, ...remainingLevels] =
|
|
7688
|
+
var visitNodes = async (node, path9, callback) => {
|
|
7689
|
+
const [currentLevel, ...remainingLevels] = path9;
|
|
7682
7690
|
if (!remainingLevels?.length) {
|
|
7683
|
-
return callback(node,
|
|
7691
|
+
return callback(node, path9);
|
|
7684
7692
|
}
|
|
7685
7693
|
if (Array.isArray(node[currentLevel])) {
|
|
7686
7694
|
for (const item of node[currentLevel]) {
|
|
@@ -8009,10 +8017,20 @@ var shaExists = async ({
|
|
|
8009
8017
|
}) => git.readCommit({ fs: fs4, dir, oid: sha3 }).then(() => true).catch(() => false);
|
|
8010
8018
|
|
|
8011
8019
|
// src/database/bridge/filesystem.ts
|
|
8012
|
-
import fs2 from "fs-extra";
|
|
8013
|
-
import fg from "fast-glob";
|
|
8014
8020
|
import path7 from "path";
|
|
8021
|
+
import fg from "fast-glob";
|
|
8022
|
+
import fs2 from "fs-extra";
|
|
8015
8023
|
import normalize from "normalize-path";
|
|
8024
|
+
function assertWithinBase(filepath, baseDir) {
|
|
8025
|
+
const resolvedBase = path7.resolve(baseDir);
|
|
8026
|
+
const resolved = path7.resolve(path7.join(baseDir, filepath));
|
|
8027
|
+
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + path7.sep)) {
|
|
8028
|
+
throw new Error(
|
|
8029
|
+
`Path traversal detected: "${filepath}" escapes the base directory`
|
|
8030
|
+
);
|
|
8031
|
+
}
|
|
8032
|
+
return resolved;
|
|
8033
|
+
}
|
|
8016
8034
|
var FilesystemBridge = class {
|
|
8017
8035
|
rootPath;
|
|
8018
8036
|
outputPath;
|
|
@@ -8021,7 +8039,7 @@ var FilesystemBridge = class {
|
|
|
8021
8039
|
this.outputPath = outputPath ? path7.resolve(outputPath) : this.rootPath;
|
|
8022
8040
|
}
|
|
8023
8041
|
async glob(pattern, extension) {
|
|
8024
|
-
const basePath =
|
|
8042
|
+
const basePath = assertWithinBase(pattern, this.outputPath);
|
|
8025
8043
|
const items = await fg(
|
|
8026
8044
|
path7.join(basePath, "**", `/*.${extension}`).replace(/\\/g, "/"),
|
|
8027
8045
|
{
|
|
@@ -8035,14 +8053,17 @@ var FilesystemBridge = class {
|
|
|
8035
8053
|
);
|
|
8036
8054
|
}
|
|
8037
8055
|
async delete(filepath) {
|
|
8038
|
-
|
|
8056
|
+
const resolved = assertWithinBase(filepath, this.outputPath);
|
|
8057
|
+
await fs2.remove(resolved);
|
|
8039
8058
|
}
|
|
8040
8059
|
async get(filepath) {
|
|
8041
|
-
|
|
8060
|
+
const resolved = assertWithinBase(filepath, this.outputPath);
|
|
8061
|
+
return (await fs2.readFile(resolved)).toString();
|
|
8042
8062
|
}
|
|
8043
8063
|
async put(filepath, data, basePathOverride) {
|
|
8044
8064
|
const basePath = basePathOverride || this.outputPath;
|
|
8045
|
-
|
|
8065
|
+
const resolved = assertWithinBase(filepath, basePath);
|
|
8066
|
+
await fs2.outputFile(resolved, data);
|
|
8046
8067
|
}
|
|
8047
8068
|
};
|
|
8048
8069
|
var AuditFileSystemBridge = class extends FilesystemBridge {
|
|
@@ -8062,12 +8083,21 @@ var AuditFileSystemBridge = class extends FilesystemBridge {
|
|
|
8062
8083
|
};
|
|
8063
8084
|
|
|
8064
8085
|
// src/database/bridge/isomorphic.ts
|
|
8065
|
-
import
|
|
8086
|
+
import path8, { dirname } from "path";
|
|
8066
8087
|
import fs3 from "fs-extra";
|
|
8067
8088
|
import globParent from "glob-parent";
|
|
8068
|
-
import normalize2 from "normalize-path";
|
|
8069
8089
|
import { GraphQLError as GraphQLError6 } from "graphql";
|
|
8070
|
-
import
|
|
8090
|
+
import git2 from "isomorphic-git";
|
|
8091
|
+
import normalize2 from "normalize-path";
|
|
8092
|
+
function assertWithinBase2(filepath, relativePath) {
|
|
8093
|
+
const qualified = relativePath ? `${relativePath}/${filepath}` : filepath;
|
|
8094
|
+
const normalized = path8.normalize(qualified);
|
|
8095
|
+
if (normalized.startsWith("..") || normalized.startsWith("/") || path8.isAbsolute(normalized) || relativePath && normalized !== relativePath && !normalized.startsWith(relativePath + "/")) {
|
|
8096
|
+
throw new Error(
|
|
8097
|
+
`Path traversal detected: "${filepath}" escapes the content root`
|
|
8098
|
+
);
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8071
8101
|
var flat = typeof Array.prototype.flat === "undefined" ? (entries) => entries.reduce((acc, x) => acc.concat(x), []) : (entries) => entries.flat();
|
|
8072
8102
|
var toUint8Array = (buf) => {
|
|
8073
8103
|
const ab = new ArrayBuffer(buf.length);
|
|
@@ -8146,7 +8176,7 @@ var IsomorphicBridge = class {
|
|
|
8146
8176
|
async listEntries({
|
|
8147
8177
|
pattern,
|
|
8148
8178
|
entry,
|
|
8149
|
-
path:
|
|
8179
|
+
path: path9,
|
|
8150
8180
|
results
|
|
8151
8181
|
}) {
|
|
8152
8182
|
const treeResult = await git2.readTree({
|
|
@@ -8156,7 +8186,7 @@ var IsomorphicBridge = class {
|
|
|
8156
8186
|
});
|
|
8157
8187
|
const children = [];
|
|
8158
8188
|
for (const childEntry of treeResult.tree) {
|
|
8159
|
-
const childPath =
|
|
8189
|
+
const childPath = path9 ? `${path9}/${childEntry.path}` : childEntry.path;
|
|
8160
8190
|
if (childEntry.type === "tree") {
|
|
8161
8191
|
children.push(childEntry);
|
|
8162
8192
|
} else {
|
|
@@ -8166,7 +8196,7 @@ var IsomorphicBridge = class {
|
|
|
8166
8196
|
}
|
|
8167
8197
|
}
|
|
8168
8198
|
for (const childEntry of children) {
|
|
8169
|
-
const childPath =
|
|
8199
|
+
const childPath = path9 ? `${path9}/${childEntry.path}` : childEntry.path;
|
|
8170
8200
|
await this.listEntries({
|
|
8171
8201
|
pattern,
|
|
8172
8202
|
entry: childEntry,
|
|
@@ -8184,17 +8214,17 @@ var IsomorphicBridge = class {
|
|
|
8184
8214
|
* @param ref - ref to resolve path entries for
|
|
8185
8215
|
* @private
|
|
8186
8216
|
*/
|
|
8187
|
-
async resolvePathEntries(
|
|
8188
|
-
let pathParts =
|
|
8217
|
+
async resolvePathEntries(path9, ref) {
|
|
8218
|
+
let pathParts = path9.split("/");
|
|
8189
8219
|
const result = await git2.walk({
|
|
8190
8220
|
...this.isomorphicConfig,
|
|
8191
8221
|
map: async (filepath, [head]) => {
|
|
8192
8222
|
if (head._fullpath === ".") {
|
|
8193
8223
|
return head;
|
|
8194
8224
|
}
|
|
8195
|
-
if (
|
|
8196
|
-
if (dirname(
|
|
8197
|
-
if (
|
|
8225
|
+
if (path9.startsWith(filepath)) {
|
|
8226
|
+
if (dirname(path9) === dirname(filepath)) {
|
|
8227
|
+
if (path9 === filepath) {
|
|
8198
8228
|
return head;
|
|
8199
8229
|
}
|
|
8200
8230
|
} else {
|
|
@@ -8225,7 +8255,7 @@ var IsomorphicBridge = class {
|
|
|
8225
8255
|
* @param pathParts - parent path parts
|
|
8226
8256
|
* @private
|
|
8227
8257
|
*/
|
|
8228
|
-
async updateTreeHierarchy(existingOid, updatedOid,
|
|
8258
|
+
async updateTreeHierarchy(existingOid, updatedOid, path9, type, pathEntries, pathParts) {
|
|
8229
8259
|
const lastIdx = pathEntries.length - 1;
|
|
8230
8260
|
const parentEntry = pathEntries[lastIdx];
|
|
8231
8261
|
const parentPath = pathParts[lastIdx];
|
|
@@ -8240,7 +8270,7 @@ var IsomorphicBridge = class {
|
|
|
8240
8270
|
cache: this.cache
|
|
8241
8271
|
});
|
|
8242
8272
|
tree = existingOid ? treeResult.tree.map((entry) => {
|
|
8243
|
-
if (entry.path ===
|
|
8273
|
+
if (entry.path === path9) {
|
|
8244
8274
|
entry.oid = updatedOid;
|
|
8245
8275
|
}
|
|
8246
8276
|
return entry;
|
|
@@ -8249,7 +8279,7 @@ var IsomorphicBridge = class {
|
|
|
8249
8279
|
{
|
|
8250
8280
|
oid: updatedOid,
|
|
8251
8281
|
type,
|
|
8252
|
-
path:
|
|
8282
|
+
path: path9,
|
|
8253
8283
|
mode
|
|
8254
8284
|
}
|
|
8255
8285
|
];
|
|
@@ -8258,7 +8288,7 @@ var IsomorphicBridge = class {
|
|
|
8258
8288
|
{
|
|
8259
8289
|
oid: updatedOid,
|
|
8260
8290
|
type,
|
|
8261
|
-
path:
|
|
8291
|
+
path: path9,
|
|
8262
8292
|
mode
|
|
8263
8293
|
}
|
|
8264
8294
|
];
|
|
@@ -8334,6 +8364,7 @@ var IsomorphicBridge = class {
|
|
|
8334
8364
|
return ref;
|
|
8335
8365
|
}
|
|
8336
8366
|
async glob(pattern, extension) {
|
|
8367
|
+
assertWithinBase2(pattern, this.relativePath);
|
|
8337
8368
|
const ref = await this.getRef();
|
|
8338
8369
|
const parent = globParent(this.qualifyPath(pattern));
|
|
8339
8370
|
const { pathParts, pathEntries } = await this.resolvePathEntries(
|
|
@@ -8367,9 +8398,10 @@ var IsomorphicBridge = class {
|
|
|
8367
8398
|
path: parentPath,
|
|
8368
8399
|
results
|
|
8369
8400
|
});
|
|
8370
|
-
return results.map((
|
|
8401
|
+
return results.map((path9) => this.unqualifyPath(path9)).filter((path9) => path9.endsWith(extension));
|
|
8371
8402
|
}
|
|
8372
8403
|
async delete(filepath) {
|
|
8404
|
+
assertWithinBase2(filepath, this.relativePath);
|
|
8373
8405
|
const ref = await this.getRef();
|
|
8374
8406
|
const { pathParts, pathEntries } = await this.resolvePathEntries(
|
|
8375
8407
|
this.qualifyPath(filepath),
|
|
@@ -8441,6 +8473,7 @@ var IsomorphicBridge = class {
|
|
|
8441
8473
|
return this.relativePath ? filepath.slice(this.relativePath.length + 1) : filepath;
|
|
8442
8474
|
}
|
|
8443
8475
|
async get(filepath) {
|
|
8476
|
+
assertWithinBase2(filepath, this.relativePath);
|
|
8444
8477
|
const ref = await this.getRef();
|
|
8445
8478
|
const oid = await git2.resolveRef({
|
|
8446
8479
|
...this.isomorphicConfig,
|
|
@@ -8455,6 +8488,7 @@ var IsomorphicBridge = class {
|
|
|
8455
8488
|
return Buffer.from(blob).toString("utf8");
|
|
8456
8489
|
}
|
|
8457
8490
|
async put(filepath, data) {
|
|
8491
|
+
assertWithinBase2(filepath, this.relativePath);
|
|
8458
8492
|
const ref = await this.getRef();
|
|
8459
8493
|
const { pathParts, pathEntries } = await this.resolvePathEntries(
|
|
8460
8494
|
this.qualifyPath(filepath),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/graphql",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-059f480-20260324232913",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
7
7
|
"files": [
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"normalize-path": "^3.0.0",
|
|
44
44
|
"readable-stream": "^4.7.0",
|
|
45
45
|
"yup": "^1.6.1",
|
|
46
|
-
"@tinacms/
|
|
47
|
-
"@tinacms/
|
|
46
|
+
"@tinacms/mdx": "0.0.0-059f480-20260324232913",
|
|
47
|
+
"@tinacms/schema-tools": "2.7.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"registry": "https://registry.npmjs.org"
|
|
@@ -65,20 +65,21 @@
|
|
|
65
65
|
"@types/node": "^22.13.1",
|
|
66
66
|
"@types/normalize-path": "^3.0.2",
|
|
67
67
|
"@types/ws": "^7.4.7",
|
|
68
|
+
"@vitest/coverage-v8": "0.32.4",
|
|
68
69
|
"jest-file-snapshot": "^0.5.0",
|
|
69
70
|
"memory-level": "^1.0.0",
|
|
70
71
|
"typescript": "^5.7.3",
|
|
71
72
|
"vite": "^4.5.9",
|
|
72
73
|
"vitest": "^0.32.4",
|
|
73
74
|
"zod": "^3.24.2",
|
|
74
|
-
"@tinacms/schema-tools": "
|
|
75
|
-
"@tinacms/scripts": "0.0.0-
|
|
75
|
+
"@tinacms/schema-tools": "2.7.0",
|
|
76
|
+
"@tinacms/scripts": "0.0.0-059f480-20260324232913"
|
|
76
77
|
},
|
|
77
78
|
"scripts": {
|
|
78
79
|
"types": "pnpm tsc",
|
|
79
80
|
"build": "tinacms-scripts build",
|
|
80
81
|
"docs": "pnpm typedoc",
|
|
81
|
-
"test": "vitest run",
|
|
82
|
+
"test": "vitest run --coverage.enabled",
|
|
82
83
|
"test-watch": "vitest"
|
|
83
84
|
}
|
|
84
85
|
}
|