@tinacms/graphql 2.3.0 → 2.4.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.
|
@@ -4,6 +4,11 @@ import type { Bridge } from './index';
|
|
|
4
4
|
* The basic example here is for the filesystem, one is needed
|
|
5
5
|
* for GitHub has well.
|
|
6
6
|
*
|
|
7
|
+
* When `outputPath` differs from `rootPath` (multi-repo: generator +
|
|
8
|
+
* sibling content repo), paths under `tina/__generated__/` still resolve
|
|
9
|
+
* against `rootPath` — schema/graphql/lookup artifacts live only in the
|
|
10
|
+
* generator. Everything else (content files) resolves against `outputPath`.
|
|
11
|
+
*
|
|
7
12
|
* @security All public methods validate their `filepath` / `pattern`
|
|
8
13
|
* argument via `assertWithinBase` before performing any I/O. If you add a
|
|
9
14
|
* new method that accepts a path, you MUST validate it the same way.
|
|
@@ -12,6 +17,8 @@ export declare class FilesystemBridge implements Bridge {
|
|
|
12
17
|
rootPath: string;
|
|
13
18
|
outputPath: string;
|
|
14
19
|
constructor(rootPath: string, outputPath?: string);
|
|
20
|
+
private baseFor;
|
|
21
|
+
private assertGeneratedSubtree;
|
|
15
22
|
glob(pattern: string, extension: string): Promise<string[]>;
|
|
16
23
|
delete(filepath: string): Promise<void>;
|
|
17
24
|
get(filepath: string): Promise<string>;
|
package/dist/index.js
CHANGED
|
@@ -3035,7 +3035,7 @@ var validateField = async (field) => {
|
|
|
3035
3035
|
var package_default = {
|
|
3036
3036
|
name: "@tinacms/graphql",
|
|
3037
3037
|
type: "module",
|
|
3038
|
-
version: "2.
|
|
3038
|
+
version: "2.4.0",
|
|
3039
3039
|
main: "dist/index.js",
|
|
3040
3040
|
module: "./dist/index.js",
|
|
3041
3041
|
files: [
|
|
@@ -3779,6 +3779,9 @@ var getTemplateForFile = (templateInfo, data) => {
|
|
|
3779
3779
|
throw new Error(`Unable to determine template`);
|
|
3780
3780
|
};
|
|
3781
3781
|
var loadAndParseWithAliases = async (bridge, filepath, collection, templateInfo) => {
|
|
3782
|
+
if (filepath.endsWith(".gitkeep")) {
|
|
3783
|
+
return { _is_tina_folder_placeholder: true };
|
|
3784
|
+
}
|
|
3782
3785
|
const dataString = await bridge.get(normalizePath(filepath));
|
|
3783
3786
|
const data = parseFile(
|
|
3784
3787
|
dataString,
|
|
@@ -4637,17 +4640,18 @@ var resolveMediaCloudToRelative = (value, config = { useRelativeMedia: true }, s
|
|
|
4637
4640
|
return value;
|
|
4638
4641
|
}
|
|
4639
4642
|
if (hasTinaMediaConfig(schema) === true) {
|
|
4640
|
-
const assetsURL = `https://${config.assetsHost}/${config.clientId}`;
|
|
4641
4643
|
const cleanMediaRoot = cleanUpSlashes(schema.config.media.tina.mediaRoot);
|
|
4642
|
-
|
|
4644
|
+
const cloudUrl = cloudUrlPattern(config.clientId);
|
|
4645
|
+
if (typeof value === "string" && cloudUrl.test(value)) {
|
|
4643
4646
|
return `${cleanMediaRoot}${stripStagingPrefix(
|
|
4644
|
-
value.replace(
|
|
4647
|
+
value.replace(cloudUrl, "")
|
|
4645
4648
|
)}`;
|
|
4646
4649
|
}
|
|
4647
4650
|
if (Array.isArray(value)) {
|
|
4648
4651
|
return value.map((v) => {
|
|
4649
4652
|
if (!v || typeof v !== "string") return v;
|
|
4650
|
-
|
|
4653
|
+
if (!cloudUrl.test(v)) return v;
|
|
4654
|
+
const strippedURL = v.replace(cloudUrl, "");
|
|
4651
4655
|
return `${cleanMediaRoot}${stripStagingPrefix(strippedURL)}`;
|
|
4652
4656
|
});
|
|
4653
4657
|
}
|
|
@@ -4683,12 +4687,14 @@ var resolveMediaRelativeToCloud = (value, config = { useRelativeMedia: true }, s
|
|
|
4683
4687
|
return value;
|
|
4684
4688
|
}
|
|
4685
4689
|
};
|
|
4686
|
-
var stagingPrefix = (config) => config.branch && config.branch !== config.mediaBranch ? `/__staging/${
|
|
4687
|
-
var STAGING_SEGMENT = /^\/__staging
|
|
4690
|
+
var stagingPrefix = (config) => config.branch && config.branch !== config.mediaBranch ? `/__staging/${config.branch}/__file` : "";
|
|
4691
|
+
var STAGING_SEGMENT = /^\/__staging\/.+?\/__file(\/.*)$/;
|
|
4688
4692
|
var stripStagingPrefix = (path9) => {
|
|
4689
4693
|
const match = path9.match(STAGING_SEGMENT);
|
|
4690
4694
|
return match ? match[1] : path9;
|
|
4691
4695
|
};
|
|
4696
|
+
var escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4697
|
+
var cloudUrlPattern = (clientId) => new RegExp(`^https://[^/]+/${escapeRegExp(clientId)}`);
|
|
4692
4698
|
var cleanUpSlashes = (path9) => {
|
|
4693
4699
|
if (path9) {
|
|
4694
4700
|
return `/${path9.replace(/^\/+|\/+$/gm, "")}`;
|
|
@@ -5361,6 +5367,12 @@ var Resolver = class _Resolver {
|
|
|
5361
5367
|
if (newRealPath === realPath) {
|
|
5362
5368
|
return doc;
|
|
5363
5369
|
}
|
|
5370
|
+
const newPathAlreadyExists = await this.database.documentExists(newRealPath);
|
|
5371
|
+
if (newPathAlreadyExists) {
|
|
5372
|
+
throw new Error(
|
|
5373
|
+
`Unable to rename document, ${newRealPath} already exists`
|
|
5374
|
+
);
|
|
5375
|
+
}
|
|
5364
5376
|
await this.database.put(newRealPath, doc._rawData, collection.name);
|
|
5365
5377
|
await this.deleteDocument(realPath);
|
|
5366
5378
|
const collRefs = await this.findReferences(realPath, collection);
|
|
@@ -6915,7 +6927,10 @@ var Database = class {
|
|
|
6915
6927
|
if (!collection) {
|
|
6916
6928
|
throw new GraphQLError5(`Unable to find collection for ${filepath}.`);
|
|
6917
6929
|
}
|
|
6918
|
-
|
|
6930
|
+
const isFolderPlaceholder = filepath.endsWith(
|
|
6931
|
+
`.gitkeep.${collection.format || "md"}`
|
|
6932
|
+
);
|
|
6933
|
+
if (!isFolderPlaceholder && (collection.match?.exclude || collection.match?.include)) {
|
|
6919
6934
|
const matches = this.tinaSchema.getMatches({ collection });
|
|
6920
6935
|
const match = micromatch2.isMatch(filepath, matches);
|
|
6921
6936
|
if (!match) {
|
|
@@ -7839,8 +7854,9 @@ var _indexContent = async ({
|
|
|
7839
7854
|
]);
|
|
7840
7855
|
}
|
|
7841
7856
|
}
|
|
7857
|
+
let putOps = [];
|
|
7842
7858
|
if (!isGitKeep(filepath, collection)) {
|
|
7843
|
-
|
|
7859
|
+
putOps = [
|
|
7844
7860
|
...makeRefOpsForDocument(
|
|
7845
7861
|
normalizedPath,
|
|
7846
7862
|
collection?.name,
|
|
@@ -7865,18 +7881,21 @@ var _indexContent = async ({
|
|
|
7865
7881
|
aliasedData,
|
|
7866
7882
|
"put",
|
|
7867
7883
|
level
|
|
7868
|
-
)
|
|
7869
|
-
|
|
7870
|
-
type: "put",
|
|
7871
|
-
key: normalizedPath,
|
|
7872
|
-
value: aliasedData,
|
|
7873
|
-
sublevel: level.sublevel(
|
|
7874
|
-
CONTENT_ROOT_PREFIX,
|
|
7875
|
-
SUBLEVEL_OPTIONS
|
|
7876
|
-
)
|
|
7877
|
-
}
|
|
7878
|
-
]);
|
|
7884
|
+
)
|
|
7885
|
+
];
|
|
7879
7886
|
}
|
|
7887
|
+
await enqueueOps([
|
|
7888
|
+
...putOps,
|
|
7889
|
+
{
|
|
7890
|
+
type: "put",
|
|
7891
|
+
key: normalizedPath,
|
|
7892
|
+
value: aliasedData,
|
|
7893
|
+
sublevel: level.sublevel(
|
|
7894
|
+
CONTENT_ROOT_PREFIX,
|
|
7895
|
+
SUBLEVEL_OPTIONS
|
|
7896
|
+
)
|
|
7897
|
+
}
|
|
7898
|
+
]);
|
|
7880
7899
|
} catch (error) {
|
|
7881
7900
|
throw new TinaFetchError(`Unable to seed ${filepath}`, {
|
|
7882
7901
|
originalError: error,
|
|
@@ -8105,6 +8124,13 @@ function assertWithinBase(filepath, baseDir) {
|
|
|
8105
8124
|
}
|
|
8106
8125
|
return resolved;
|
|
8107
8126
|
}
|
|
8127
|
+
var GENERATED_PATH_PREFIXES = ["tina/__generated__/", ".tina/__generated__/"];
|
|
8128
|
+
function isGeneratedPath(filepath) {
|
|
8129
|
+
const normalized = filepath.replace(/\\/g, "/");
|
|
8130
|
+
return GENERATED_PATH_PREFIXES.some(
|
|
8131
|
+
(prefix) => normalized.startsWith(prefix)
|
|
8132
|
+
);
|
|
8133
|
+
}
|
|
8108
8134
|
var FilesystemBridge = class {
|
|
8109
8135
|
rootPath;
|
|
8110
8136
|
outputPath;
|
|
@@ -8112,6 +8138,30 @@ var FilesystemBridge = class {
|
|
|
8112
8138
|
this.rootPath = path7.resolve(rootPath);
|
|
8113
8139
|
this.outputPath = outputPath ? path7.resolve(outputPath) : this.rootPath;
|
|
8114
8140
|
}
|
|
8141
|
+
// Picks the base directory for a given path. The `assertWithinBase` check
|
|
8142
|
+
// in delete/get/put still runs against the chosen base, so a path like
|
|
8143
|
+
// `tina/__generated__/../../escape.txt` is still rejected — just rejected
|
|
8144
|
+
// against `rootPath` rather than `outputPath`.
|
|
8145
|
+
baseFor(filepath) {
|
|
8146
|
+
return isGeneratedPath(filepath) ? this.rootPath : this.outputPath;
|
|
8147
|
+
}
|
|
8148
|
+
// Defense-in-depth for generated-path routing: assertWithinBase already
|
|
8149
|
+
// rejects paths that escape rootPath, but a path like
|
|
8150
|
+
// `tina/__generated__/../../.env` would resolve to `<rootPath>/.env` —
|
|
8151
|
+
// technically inside rootPath but outside the generated subtree the
|
|
8152
|
+
// routing is supposed to grant access to. Verify the resolved path stays
|
|
8153
|
+
// inside the matching generated subdirectory.
|
|
8154
|
+
assertGeneratedSubtree(filepath, resolved) {
|
|
8155
|
+
if (!isGeneratedPath(filepath)) return;
|
|
8156
|
+
const normalized = filepath.replace(/\\/g, "/");
|
|
8157
|
+
const subdir = normalized.startsWith(".tina/__generated__/") ? ".tina/__generated__" : "tina/__generated__";
|
|
8158
|
+
const generatedRoot = path7.resolve(path7.join(this.rootPath, subdir));
|
|
8159
|
+
if (resolved !== generatedRoot && !resolved.startsWith(generatedRoot + path7.sep)) {
|
|
8160
|
+
throw new Error(
|
|
8161
|
+
`Path traversal detected: "${filepath}" routed via generated prefix but resolved outside ${generatedRoot}`
|
|
8162
|
+
);
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8115
8165
|
async glob(pattern, extension) {
|
|
8116
8166
|
const basePath = assertWithinBase(pattern, this.outputPath);
|
|
8117
8167
|
const items = await fg(
|
|
@@ -8127,16 +8177,19 @@ var FilesystemBridge = class {
|
|
|
8127
8177
|
);
|
|
8128
8178
|
}
|
|
8129
8179
|
async delete(filepath) {
|
|
8130
|
-
const resolved = assertWithinBase(filepath, this.
|
|
8180
|
+
const resolved = assertWithinBase(filepath, this.baseFor(filepath));
|
|
8181
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8131
8182
|
await fs2.remove(resolved);
|
|
8132
8183
|
}
|
|
8133
8184
|
async get(filepath) {
|
|
8134
|
-
const resolved = assertWithinBase(filepath, this.
|
|
8185
|
+
const resolved = assertWithinBase(filepath, this.baseFor(filepath));
|
|
8186
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8135
8187
|
return (await fs2.readFile(resolved)).toString();
|
|
8136
8188
|
}
|
|
8137
8189
|
async put(filepath, data, basePathOverride) {
|
|
8138
|
-
const basePath = basePathOverride || this.
|
|
8190
|
+
const basePath = basePathOverride || this.baseFor(filepath);
|
|
8139
8191
|
const resolved = assertWithinBase(filepath, basePath);
|
|
8192
|
+
this.assertGeneratedSubtree(filepath, resolved);
|
|
8140
8193
|
await fs2.outputFile(resolved, data);
|
|
8141
8194
|
}
|
|
8142
8195
|
};
|
package/dist/resolver/index.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ interface ResolverConfig {
|
|
|
11
11
|
isAudit: boolean;
|
|
12
12
|
}
|
|
13
13
|
export declare const createResolver: (args: ResolverConfig) => Resolver;
|
|
14
|
+
export declare const resolveFieldData: ({ namespace, ...field }: TinaField<true>, rawData: unknown, accumulator: {
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}, tinaSchema: TinaSchema, config?: GraphQLConfig, isAudit?: boolean) => Promise<{
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}>;
|
|
14
19
|
export declare const transformDocumentIntoPayload: (fullPath: string, rawData: {
|
|
15
20
|
_collection: any;
|
|
16
21
|
_template: any;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/graphql",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.0",
|
|
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": "2.1.4",
|
|
47
|
+
"@tinacms/schema-tools": "2.7.4"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"registry": "https://registry.npmjs.org"
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"vite": "^4.5.9",
|
|
73
73
|
"vitest": "^0.32.4",
|
|
74
74
|
"zod": "^3.24.2",
|
|
75
|
-
"@tinacms/
|
|
76
|
-
"@tinacms/
|
|
75
|
+
"@tinacms/scripts": "1.6.1",
|
|
76
|
+
"@tinacms/schema-tools": "2.7.4"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"types": "pnpm tsc",
|