@thallylabs/migrate 0.2.25 → 0.2.26
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 +36 -23
- package/dist/index.d.ts +50 -7
- package/dist/index.js +198 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -889,27 +889,104 @@ function normalizeAssetPath(value) {
|
|
|
889
889
|
function objectValue(value) {
|
|
890
890
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
891
891
|
}
|
|
892
|
+
var OPEN_NAVIGATION_ACCESS = Object.freeze({ hasPublicFalse: false, hasPublicTrue: false });
|
|
892
893
|
var PUBLIC_FALSE_STRINGS = /* @__PURE__ */ new Set(["false", "no", "off", "0"]);
|
|
893
|
-
|
|
894
|
-
|
|
894
|
+
var PUBLIC_TRUE_STRINGS = /* @__PURE__ */ new Set(["true"]);
|
|
895
|
+
function parsePublic(value) {
|
|
896
|
+
if (value === void 0 || value === null) return { isMalformed: false };
|
|
897
|
+
if (value === true) return { isPublic: true, isMalformed: false };
|
|
898
|
+
if (value === false || value === 0) return { isPublic: false, isMalformed: false };
|
|
899
|
+
if (typeof value === "string") {
|
|
900
|
+
const normalized = value.trim().toLowerCase();
|
|
901
|
+
if (PUBLIC_TRUE_STRINGS.has(normalized)) return { isPublic: true, isMalformed: false };
|
|
902
|
+
if (PUBLIC_FALSE_STRINGS.has(normalized)) return { isPublic: false, isMalformed: false };
|
|
903
|
+
}
|
|
904
|
+
return { isMalformed: true };
|
|
905
|
+
}
|
|
906
|
+
function parseGroups(value) {
|
|
907
|
+
if (value === void 0 || value === null || value === "" || value === false) return { groups: [], isMalformed: false };
|
|
908
|
+
if (typeof value === "string") {
|
|
909
|
+
const groups = value.split(",").map((group) => group.trim()).filter(Boolean);
|
|
910
|
+
return { groups: [...new Set(groups)], isMalformed: groups.length === 0 };
|
|
911
|
+
}
|
|
912
|
+
if (Array.isArray(value)) {
|
|
913
|
+
if (value.some((group) => typeof group !== "string")) return { groups: [], isMalformed: true };
|
|
914
|
+
const groups = value.map((group) => group.trim()).filter(Boolean);
|
|
915
|
+
return { groups: [...new Set(groups)], isMalformed: value.length > 0 && groups.length === 0 };
|
|
916
|
+
}
|
|
917
|
+
return { groups: [], isMalformed: true };
|
|
918
|
+
}
|
|
919
|
+
function accessFrom(groupsValue, publicValue, where) {
|
|
920
|
+
const groups = parseGroups(groupsValue);
|
|
921
|
+
const visibility = parsePublic(publicValue);
|
|
922
|
+
const problem = groups.isMalformed ? `${where} \`groups\` is not a list of group names` : visibility.isMalformed ? `${where} \`public\` is neither true nor false` : void 0;
|
|
923
|
+
return {
|
|
924
|
+
...groups.groups.length > 0 ? { groups: groups.groups } : {},
|
|
925
|
+
...visibility.isPublic !== void 0 ? { isPublic: visibility.isPublic } : {},
|
|
926
|
+
...problem ? { problem } : {}
|
|
927
|
+
};
|
|
895
928
|
}
|
|
896
|
-
function
|
|
897
|
-
return
|
|
929
|
+
function frontmatterAccess(data) {
|
|
930
|
+
return accessFrom(data.groups, data.public, "frontmatter");
|
|
898
931
|
}
|
|
899
|
-
function
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
932
|
+
function isRestrictedAccess(access) {
|
|
933
|
+
return Boolean(access.problem) || (access.groups?.length ?? 0) > 0 || access.isPublic === false;
|
|
934
|
+
}
|
|
935
|
+
var NAVIGATION_CONTAINER_KEYS = ["group", "pages", "tab", "groups", "page", "root", "menu", "href"];
|
|
936
|
+
function navigationAccess(node) {
|
|
937
|
+
const isContainer = (entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) && NAVIGATION_CONTAINER_KEYS.some((key) => key in entry);
|
|
938
|
+
const groups = Array.isArray(node.groups) && node.groups.length > 0 && node.groups.every(isContainer) ? void 0 : node.groups;
|
|
939
|
+
const access = accessFrom(groups, node.public, "its navigation container's");
|
|
940
|
+
return Object.keys(access).length > 0 ? access : void 0;
|
|
905
941
|
}
|
|
906
942
|
function navigationGateReason(node) {
|
|
907
|
-
const
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
return
|
|
943
|
+
const access = navigationAccess(node);
|
|
944
|
+
if (!access || !isRestrictedAccess(access)) return void 0;
|
|
945
|
+
return access.problem ?? (access.groups ? "its navigation container sets `groups`" : "its navigation container sets `public: false`");
|
|
946
|
+
}
|
|
947
|
+
function intersect(left, right) {
|
|
948
|
+
if (!left) return right;
|
|
949
|
+
if (!right) return left;
|
|
950
|
+
return left.filter((group) => right.includes(group));
|
|
951
|
+
}
|
|
952
|
+
function descendNavigationAccess(outer, container) {
|
|
953
|
+
if (!container) return outer;
|
|
954
|
+
const groups = intersect(outer.groups, container.groups);
|
|
955
|
+
const problem = outer.problem ?? container.problem ?? (groups && groups.length === 0 ? "nested navigation containers set `groups` that share no group" : void 0);
|
|
956
|
+
return {
|
|
957
|
+
...groups ? { groups } : {},
|
|
958
|
+
hasPublicFalse: outer.hasPublicFalse || container.isPublic === false,
|
|
959
|
+
hasPublicTrue: outer.hasPublicTrue || container.isPublic === true,
|
|
960
|
+
...problem ? { problem } : {}
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
function mergeNavigationAppearances(appearances) {
|
|
964
|
+
if (appearances.length === 0) return OPEN_NAVIGATION_ACCESS;
|
|
965
|
+
let groups;
|
|
966
|
+
for (const appearance of appearances) groups = intersect(groups, appearance.groups);
|
|
967
|
+
const problem = appearances.find((appearance) => appearance.problem)?.problem ?? (groups && groups.length === 0 ? "it is listed under navigation containers whose `groups` share no group" : void 0);
|
|
968
|
+
return {
|
|
969
|
+
...groups ? { groups } : {},
|
|
970
|
+
hasPublicFalse: appearances.some((appearance) => appearance.hasPublicFalse),
|
|
971
|
+
hasPublicTrue: appearances.every((appearance) => appearance.hasPublicTrue),
|
|
972
|
+
...problem ? { problem } : {}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
function hasNavigationRules(access) {
|
|
976
|
+
return isNavigationRestricted(access) || access.hasPublicTrue;
|
|
977
|
+
}
|
|
978
|
+
function isNavigationRestricted(access) {
|
|
979
|
+
return Boolean(access.problem) || (access.groups?.length ?? 0) > 0 || access.hasPublicFalse;
|
|
980
|
+
}
|
|
981
|
+
function resolvePageAccess(own2, navigation) {
|
|
982
|
+
const problem = own2.problem ?? navigation.problem;
|
|
983
|
+
if (problem) return { problem };
|
|
984
|
+
const groups = intersect(own2.groups, navigation.groups);
|
|
985
|
+
if (groups && groups.length === 0) {
|
|
986
|
+
return { problem: "its frontmatter `groups` and its navigation container's `groups` share no group" };
|
|
987
|
+
}
|
|
988
|
+
const isPublic = own2.isPublic === false || navigation.hasPublicFalse ? false : own2.isPublic === true || navigation.hasPublicTrue && !groups ? true : void 0;
|
|
989
|
+
return { access: { ...groups ? { groups } : {}, ...isPublic !== void 0 ? { isPublic } : {} } };
|
|
913
990
|
}
|
|
914
991
|
var CONFIG_BASENAME = /(?:^|[.-])(?:config|rc)\.[cm]?js$|^(?:tailwind|postcss|next|eslint|prettier|babel|webpack|vite|vitest|jest|rollup|gulpfile|gruntfile|stylelint|commitlint|lint-staged|svgo|metro)[.-]/i;
|
|
915
992
|
var BUILD_OUTPUT_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "coverage"]);
|
|
@@ -4223,22 +4300,31 @@ function collectGatedSourceRefs(node, gate, out, limits, depth = 0) {
|
|
|
4223
4300
|
if (reason && typeof object.sourceRef === "string") out.set(object, reason);
|
|
4224
4301
|
for (const value of Object.values(object)) collectGatedSourceRefs(value, reason, out, limits, depth + 1);
|
|
4225
4302
|
}
|
|
4226
|
-
function
|
|
4303
|
+
function collectReferenceAccess(node, access, pathPrefix, out, depth = 0) {
|
|
4227
4304
|
if (depth > SWEEP_DEPTH_LIMIT) return;
|
|
4228
4305
|
if (Array.isArray(node)) {
|
|
4229
|
-
for (const item of node)
|
|
4306
|
+
for (const item of node) collectReferenceAccess(item, access, pathPrefix, out, depth + 1);
|
|
4230
4307
|
return;
|
|
4231
4308
|
}
|
|
4232
4309
|
const object = objectValue2(node);
|
|
4233
4310
|
if (!object) return;
|
|
4234
|
-
const
|
|
4235
|
-
if (
|
|
4236
|
-
const refs = [object.page, object.root, ...Array.isArray(object.pages) ? object.pages : []];
|
|
4311
|
+
const nested = descendNavigationAccess(access, navigationAccess(object));
|
|
4312
|
+
if (hasNavigationRules(nested)) {
|
|
4313
|
+
const refs = [object.page, object.root, object.href, ...Array.isArray(object.pages) ? object.pages : []];
|
|
4237
4314
|
for (const ref of refs) {
|
|
4238
|
-
if (typeof ref === "string" && normalizePageRef(ref, pathPrefix)) out.push({ ref,
|
|
4315
|
+
if (typeof ref === "string" && normalizePageRef(ref, pathPrefix)) out.push({ ref, access: nested });
|
|
4239
4316
|
}
|
|
4240
4317
|
}
|
|
4241
|
-
for (const value of Object.values(object))
|
|
4318
|
+
for (const value of Object.values(object)) collectReferenceAccess(value, nested, pathPrefix, out, depth + 1);
|
|
4319
|
+
}
|
|
4320
|
+
function withContainerAccess(object, context, walk2) {
|
|
4321
|
+
const outer = context.access;
|
|
4322
|
+
context.access = descendNavigationAccess(outer, navigationAccess(object));
|
|
4323
|
+
try {
|
|
4324
|
+
return walk2();
|
|
4325
|
+
} finally {
|
|
4326
|
+
context.access = outer;
|
|
4327
|
+
}
|
|
4242
4328
|
}
|
|
4243
4329
|
function warnOnce(context, key, message) {
|
|
4244
4330
|
if (context.warningKeys.has(key)) return;
|
|
@@ -4257,11 +4343,12 @@ function containerPresentation(value) {
|
|
|
4257
4343
|
};
|
|
4258
4344
|
}
|
|
4259
4345
|
function registerReference(value, context) {
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
if (normalizePageRef(value, context.pathPrefix)) context.
|
|
4346
|
+
if (context.limits.tooDeep) {
|
|
4347
|
+
const problem = "its navigation is too deeply nested to check access rules";
|
|
4348
|
+
if (normalizePageRef(value, context.pathPrefix)) context.appearances.push({ ref: value, access: { ...context.access, problem } });
|
|
4263
4349
|
return null;
|
|
4264
4350
|
}
|
|
4351
|
+
if (normalizePageRef(value, context.pathPrefix)) context.appearances.push({ ref: value, access: context.access });
|
|
4265
4352
|
const localizedValue = context.locale ? mintlifyLocalizedReference(value, context.locale, context.defaultPageIds) : value;
|
|
4266
4353
|
const navigationId = normalizePageRef(localizedValue, context.pathPrefix);
|
|
4267
4354
|
if (!navigationId) return null;
|
|
@@ -4287,14 +4374,7 @@ function convertPage(value, context) {
|
|
|
4287
4374
|
}
|
|
4288
4375
|
const object = objectValue2(value);
|
|
4289
4376
|
if (!object) return null;
|
|
4290
|
-
|
|
4291
|
-
const gate = navigationGateReason(object);
|
|
4292
|
-
if (gate && !outerGate) context.gateReason = gate;
|
|
4293
|
-
try {
|
|
4294
|
-
return convertPageObject(object, context);
|
|
4295
|
-
} finally {
|
|
4296
|
-
context.gateReason = outerGate;
|
|
4297
|
-
}
|
|
4377
|
+
return withContainerAccess(object, context, () => convertPageObject(object, context));
|
|
4298
4378
|
}
|
|
4299
4379
|
function gatedSourceRef(repo, context) {
|
|
4300
4380
|
if (!context.gatedSourceRefs.includes(repo)) context.gatedSourceRefs.push(repo);
|
|
@@ -4311,7 +4391,6 @@ function convertPageObject(object, context) {
|
|
|
4311
4391
|
if (href && !object.pages && !object.groups) {
|
|
4312
4392
|
const page = registerReference(href, context);
|
|
4313
4393
|
if (page) return page;
|
|
4314
|
-
if (context.gateReason) return null;
|
|
4315
4394
|
warnOnce(
|
|
4316
4395
|
context,
|
|
4317
4396
|
"external-page-link",
|
|
@@ -4320,7 +4399,7 @@ function convertPageObject(object, context) {
|
|
|
4320
4399
|
return null;
|
|
4321
4400
|
}
|
|
4322
4401
|
if (typeof object.sourceRef === "string") {
|
|
4323
|
-
if (context.
|
|
4402
|
+
if (isNavigationRestricted(context.access) || context.gatedNodes.has(object) || context.limits.tooDeep) return gatedSourceRef(object.sourceRef, context);
|
|
4324
4403
|
const imported = context.resolveSourceRef?.(object.sourceRef);
|
|
4325
4404
|
if (imported) return { group: object.sourceRef.split("/").pop() ?? object.sourceRef, pages: imported };
|
|
4326
4405
|
const where = context.groupLabel ? `the "${context.groupLabel}" navigation group` : "the navigation";
|
|
@@ -4344,7 +4423,7 @@ function convertPageObject(object, context) {
|
|
|
4344
4423
|
for (const page of pages) {
|
|
4345
4424
|
const pageObject = objectValue2(page);
|
|
4346
4425
|
const sourceRef = pageObject?.sourceRef;
|
|
4347
|
-
const imported = typeof sourceRef === "string" && !context.
|
|
4426
|
+
const imported = typeof sourceRef === "string" && !isNavigationRestricted(context.access) && !navigationGateReason(pageObject) && !context.gatedNodes.has(pageObject) && !context.limits.tooDeep ? context.resolveSourceRef?.(sourceRef) : null;
|
|
4348
4427
|
if (imported) {
|
|
4349
4428
|
children.push(...imported);
|
|
4350
4429
|
continue;
|
|
@@ -4462,15 +4541,7 @@ function convertContainerToTabs(containerValue, context, fallbackTab, trace, dep
|
|
|
4462
4541
|
...object.hidden === true ? { hidden: true } : {}
|
|
4463
4542
|
}];
|
|
4464
4543
|
}
|
|
4465
|
-
const
|
|
4466
|
-
const gate = navigationGateReason(object);
|
|
4467
|
-
if (gate && !outerGate) context.gateReason = gate;
|
|
4468
|
-
let nested;
|
|
4469
|
-
try {
|
|
4470
|
-
nested = convertContainerToTabs(object, context, tab, trace, depth + 1);
|
|
4471
|
-
} finally {
|
|
4472
|
-
context.gateReason = outerGate;
|
|
4473
|
-
}
|
|
4544
|
+
const nested = withContainerAccess(object, context, () => convertContainerToTabs(object, context, tab, trace, depth + 1));
|
|
4474
4545
|
if (nested.length > 0) {
|
|
4475
4546
|
if (nested.length === 1) {
|
|
4476
4547
|
return [{
|
|
@@ -4844,10 +4915,11 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
4844
4915
|
const references = [];
|
|
4845
4916
|
const seenReferences = /* @__PURE__ */ new Set();
|
|
4846
4917
|
const warningKeys = /* @__PURE__ */ new Set();
|
|
4847
|
-
const
|
|
4918
|
+
const appearances = [];
|
|
4848
4919
|
const gatedSourceRefs = [];
|
|
4849
4920
|
const gatedNodes = /* @__PURE__ */ new Map();
|
|
4850
4921
|
const navigation = objectValue2(config.navigation) ?? config;
|
|
4922
|
+
const rootAccess = descendNavigationAccess(OPEN_NAVIGATION_ACCESS, navigationAccess(navigation));
|
|
4851
4923
|
const limits = { tooDeep: false };
|
|
4852
4924
|
collectGatedSourceRefs(navigation, void 0, gatedNodes, limits);
|
|
4853
4925
|
if (limits.tooDeep) {
|
|
@@ -4896,7 +4968,8 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
4896
4968
|
seenReferences,
|
|
4897
4969
|
warnings,
|
|
4898
4970
|
warningKeys,
|
|
4899
|
-
|
|
4971
|
+
appearances,
|
|
4972
|
+
access: descendNavigationAccess(rootAccess, navigationAccess(language)),
|
|
4900
4973
|
gatedSourceRefs,
|
|
4901
4974
|
gatedNodes,
|
|
4902
4975
|
limits
|
|
@@ -4916,7 +4989,7 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
4916
4989
|
}
|
|
4917
4990
|
if (Object.keys(localizedNavigation).length > 0) i18n.navigation = localizedNavigation;
|
|
4918
4991
|
} else {
|
|
4919
|
-
const context = { references, seenReferences, warnings, warningKeys,
|
|
4992
|
+
const context = { references, seenReferences, warnings, warningKeys, appearances, access: rootAccess, gatedSourceRefs, gatedNodes, limits, pathPrefix: options.pathPrefix, resolveSourceRef: options.resolveSourceRef };
|
|
4920
4993
|
tabs = convertContainerToTabs(navigation, context, "Documentation", projectionTrace);
|
|
4921
4994
|
if (tabs.length === 0 && Array.isArray(config.navigation)) {
|
|
4922
4995
|
const children = convertNavigationValues(config.navigation, context);
|
|
@@ -4996,7 +5069,7 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
4996
5069
|
...typeof redirect.permanent === "boolean" ? { permanent: redirect.permanent } : {}
|
|
4997
5070
|
}];
|
|
4998
5071
|
}) : [];
|
|
4999
|
-
|
|
5072
|
+
collectReferenceAccess(navigation, OPEN_NAVIGATION_ACCESS, options.pathPrefix, appearances);
|
|
5000
5073
|
return {
|
|
5001
5074
|
docsConfig: {
|
|
5002
5075
|
tabs,
|
|
@@ -5011,7 +5084,7 @@ function projectMintlifyNavigation(config, options = {}) {
|
|
|
5011
5084
|
...redirects.length > 0 ? { redirects } : {}
|
|
5012
5085
|
},
|
|
5013
5086
|
pageReferences: references,
|
|
5014
|
-
|
|
5087
|
+
referenceAccess: appearances,
|
|
5015
5088
|
gatedSourceRefs,
|
|
5016
5089
|
warnings
|
|
5017
5090
|
};
|
|
@@ -8567,7 +8640,7 @@ function flattenTabs(config) {
|
|
|
8567
8640
|
});
|
|
8568
8641
|
}
|
|
8569
8642
|
function withheldNotSaved(message) {
|
|
8570
|
-
return message.replace(/ and saved under migration-quarantine\/ \(local only[^)]*\)/g, " (not saved)").replace(/ and saved under migration-quarantine\/assets\//g, " (not saved)").replace(/, so it was NOT published and was not saved under migration-quarantine\/;/g, ", so it was NOT published and is not saved;").replace(/, so it was not copied to migration-quarantine\/;/g, ", so it is not saved;").replace(/The original is saved at migration-quarantine\/[^;]*;/g, "It is withheld from the site (not saved);").replace(/; review migration-quarantine\/assets\/ and copy any that published pages need\./g, "; they are withheld from the site (not saved).").replace(/Copy any that published pages need from migration-quarantine\/assets\/ into public\/ by hand\./g, "They are withheld from the site (not saved); recover any that published pages need from the referenced repository.").replace(/migration-quarantine\/\S*/g, "withheld from the site (not saved)");
|
|
8643
|
+
return message.replace(/ and saved under migration-quarantine\/ \(local only[^)]*\)/g, " (not saved)").replace(/ and saved under migration-quarantine\/assets\//g, " (not saved)").replace(/; they are saved under migration-quarantine\/assets\/\. Copy them into public\/ manually if they may be public, or/g, "; they are withheld from the site (not saved). Recover any that may be public from the referenced repository, or").replace(/, so it was NOT published and was not saved under migration-quarantine\/;/g, ", so it was NOT published and is not saved;").replace(/, so it was not copied to migration-quarantine\/;/g, ", so it is not saved;").replace(/The original is saved at migration-quarantine\/[^;]*;/g, "It is withheld from the site (not saved);").replace(/; review migration-quarantine\/assets\/ and copy any that published pages need\./g, "; they are withheld from the site (not saved).").replace(/Copy any that published pages need from migration-quarantine\/assets\/ into public\/ by hand\./g, "They are withheld from the site (not saved); recover any that published pages need from the referenced repository.").replace(/migration-quarantine\/\S*/g, "withheld from the site (not saved)");
|
|
8571
8644
|
}
|
|
8572
8645
|
function empty(mapping, message) {
|
|
8573
8646
|
return { ...mapping, pages: [], assets: [], navigation: [], warnings: [{ code: "skipped-file", message: `sourceRef ${mapping.repo} was not imported: ${message}` }] };
|
|
@@ -9949,6 +10022,9 @@ function readFrontmatterHead(path) {
|
|
|
9949
10022
|
closeSync(fd);
|
|
9950
10023
|
}
|
|
9951
10024
|
}
|
|
10025
|
+
function ownRestrictionReason(access) {
|
|
10026
|
+
return access.problem ?? (access.groups?.length ? "frontmatter `groups`" : "frontmatter `public: false`");
|
|
10027
|
+
}
|
|
9952
10028
|
function canonicalPath(path) {
|
|
9953
10029
|
try {
|
|
9954
10030
|
return realpathSync3.native(path);
|
|
@@ -9956,11 +10032,15 @@ function canonicalPath(path) {
|
|
|
9956
10032
|
return path;
|
|
9957
10033
|
}
|
|
9958
10034
|
}
|
|
9959
|
-
function
|
|
10035
|
+
function pageHeadAccess(raw) {
|
|
9960
10036
|
const parsed = parseFrontmatter(raw);
|
|
9961
|
-
const unreadable = parsed.error && /^(groups|public)\s*:/m.test(raw.slice(0, raw.length - parsed.content.length))
|
|
10037
|
+
const unreadable = parsed.error && /^(groups|public)\s*:/m.test(raw.slice(0, raw.length - parsed.content.length));
|
|
9962
10038
|
const openapi = typeof parsed.data.openapi === "string" ? parsed.data.openapi.trim() : void 0;
|
|
9963
|
-
|
|
10039
|
+
const access = unreadable ? { problem: "frontmatter could not be parsed and declares `groups` or `public`" } : frontmatterAccess(parsed.data);
|
|
10040
|
+
return { access, openapi };
|
|
10041
|
+
}
|
|
10042
|
+
function describeRestriction(access) {
|
|
10043
|
+
return access.groups?.length ? `restricted to groups ${access.groups.join(", ")}` : "`public: false`";
|
|
9964
10044
|
}
|
|
9965
10045
|
function withoutFrontmatter(value) {
|
|
9966
10046
|
return value.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
@@ -10818,7 +10898,7 @@ function migrateRepository(options) {
|
|
|
10818
10898
|
const referenceOrder = /* @__PURE__ */ new Map();
|
|
10819
10899
|
let docusaurusSidebars = null;
|
|
10820
10900
|
let mintlifyConfig = null;
|
|
10821
|
-
const
|
|
10901
|
+
const mintlifyNavigationAccess = /* @__PURE__ */ new Map();
|
|
10822
10902
|
let fernRawConfig = null;
|
|
10823
10903
|
let fernBasePath = "";
|
|
10824
10904
|
let fernVersionPath = "";
|
|
@@ -10859,9 +10939,11 @@ function migrateRepository(options) {
|
|
|
10859
10939
|
docsConfig = projected.docsConfig;
|
|
10860
10940
|
warnings.push(...projected.warnings);
|
|
10861
10941
|
for (const repo of projected.gatedSourceRefs) gatedSourceRepos.add(repo.toLowerCase());
|
|
10862
|
-
for (const
|
|
10863
|
-
const
|
|
10864
|
-
|
|
10942
|
+
for (const appearance of projected.referenceAccess) {
|
|
10943
|
+
const accessKey = normalizedReferenceKey(appearance.ref).toLowerCase();
|
|
10944
|
+
const known = mintlifyNavigationAccess.get(accessKey);
|
|
10945
|
+
if (known) known.push(appearance.access);
|
|
10946
|
+
else mintlifyNavigationAccess.set(accessKey, [appearance.access]);
|
|
10865
10947
|
}
|
|
10866
10948
|
for (const [index, reference] of projected.pageReferences.entries()) {
|
|
10867
10949
|
const key = normalizedReferenceKey(reference.ref);
|
|
@@ -10985,6 +11067,7 @@ function migrateRepository(options) {
|
|
|
10985
11067
|
const docusaurusDocCardRoutes = /* @__PURE__ */ new Set();
|
|
10986
11068
|
const seenPageIds = /* @__PURE__ */ new Set();
|
|
10987
11069
|
const quarantinedFiles = [];
|
|
11070
|
+
const restrictedPages = /* @__PURE__ */ new Set();
|
|
10988
11071
|
const withheldAssetPaths = /* @__PURE__ */ new Set();
|
|
10989
11072
|
const publishedExact = /* @__PURE__ */ new Set();
|
|
10990
11073
|
const publishedLoose = /* @__PURE__ */ new Set();
|
|
@@ -10998,19 +11081,21 @@ function migrateRepository(options) {
|
|
|
10998
11081
|
const withheldPaths = /* @__PURE__ */ new Set();
|
|
10999
11082
|
const isDocFile = (file) => [".md", ".mdx"].includes(extname6(file.relativePath).toLowerCase());
|
|
11000
11083
|
const isGateCandidate = (file) => isDocFile(file) && !file.relativePath.split("/").some((segment) => SNIPPET_DIRECTORIES.has(segment.toLowerCase()));
|
|
11084
|
+
const navigationAccessFor = (relativePath) => mergeNavigationAppearances(mintlifyNavigationAccess.get(normalizedReferenceKey(relativePath).toLowerCase()) ?? []);
|
|
11001
11085
|
const classifyPageGate = (file) => {
|
|
11002
|
-
let head
|
|
11003
|
-
let unreadableGate;
|
|
11086
|
+
let head;
|
|
11004
11087
|
try {
|
|
11005
11088
|
const size = lstatSync6(file.absolutePath).size;
|
|
11006
11089
|
const raw = readFrontmatterHead(file.absolutePath) ?? (size > MAX_PAGE_BYTES ? void 0 : readFileSync6(file.absolutePath, "utf8"));
|
|
11007
11090
|
if (raw === void 0) throw new Error("frontmatter is not terminated within the bounded read");
|
|
11008
|
-
head =
|
|
11091
|
+
head = pageHeadAccess(raw);
|
|
11009
11092
|
} catch {
|
|
11010
|
-
|
|
11093
|
+
head = { access: { problem: "frontmatter could not be read" } };
|
|
11011
11094
|
}
|
|
11012
|
-
const
|
|
11013
|
-
return { reason,
|
|
11095
|
+
const verdict = resolvePageAccess(head.access, navigationAccessFor(file.relativePath));
|
|
11096
|
+
if (verdict.problem) return { reason: verdict.problem, quarantineReason: verdict.problem, openapi: head.openapi };
|
|
11097
|
+
const access = verdict.access ?? {};
|
|
11098
|
+
return { ...isRestrictedAccess(access) ? { reason: describeRestriction(access) } : {}, access, openapi: head.openapi };
|
|
11014
11099
|
};
|
|
11015
11100
|
const keptFiles = new Set(files);
|
|
11016
11101
|
const droppedGatedFiles = [];
|
|
@@ -11026,7 +11111,7 @@ function migrateRepository(options) {
|
|
|
11026
11111
|
}
|
|
11027
11112
|
if (!isGateCandidate(file)) continue;
|
|
11028
11113
|
const verdict = classifyPageGate(file);
|
|
11029
|
-
gateByPath.set(file.absolutePath,
|
|
11114
|
+
gateByPath.set(file.absolutePath, verdict);
|
|
11030
11115
|
if (verdict.openapi) (verdict.reason ? withheldSpecRefs : publishedSpecRefs).push(verdict.openapi);
|
|
11031
11116
|
if (verdict.reason) withheldPaths.add(file.absolutePath);
|
|
11032
11117
|
}
|
|
@@ -11074,10 +11159,12 @@ function migrateRepository(options) {
|
|
|
11074
11159
|
} catch {
|
|
11075
11160
|
return { kind: "unreadable", reason: "could not be read" };
|
|
11076
11161
|
}
|
|
11077
|
-
const
|
|
11078
|
-
if (
|
|
11079
|
-
|
|
11080
|
-
|
|
11162
|
+
const own2 = head === void 0 ? void 0 : pageHeadAccess(head).access;
|
|
11163
|
+
if (own2 && isRestrictedAccess(own2)) return { kind: inSnippetDirectory ? "snippet-gated" : "gated", reason: ownRestrictionReason(own2) };
|
|
11164
|
+
if (!inSnippetDirectory) {
|
|
11165
|
+
const navigation = navigationAccessFor(shown);
|
|
11166
|
+
if (isNavigationRestricted(navigation)) return { kind: "gated", reason: navigation.problem ?? "its navigation container restricts it" };
|
|
11167
|
+
}
|
|
11081
11168
|
if (head !== void 0 && parseFrontmatter(head).error) return { kind: "unreadable", reason: "frontmatter could not be parsed" };
|
|
11082
11169
|
if (lstatSync6(candidate).size > MAX_PAGE_BYTES) return { kind: "oversized", reason: "over 2 MB" };
|
|
11083
11170
|
return head === void 0 ? { kind: "unreadable", reason: "frontmatter not terminated in the bounded read" } : void 0;
|
|
@@ -11192,10 +11279,11 @@ function migrateRepository(options) {
|
|
|
11192
11279
|
continue;
|
|
11193
11280
|
}
|
|
11194
11281
|
const key = normalizedReferenceKey(file.relativePath);
|
|
11282
|
+
let pageGate;
|
|
11195
11283
|
if (platform === "mintlify") {
|
|
11196
|
-
|
|
11197
|
-
if (
|
|
11198
|
-
const gateReason =
|
|
11284
|
+
pageGate = gateByPath.get(file.absolutePath);
|
|
11285
|
+
if (pageGate?.access?.isPublic === true) sawPublicTrue = true;
|
|
11286
|
+
const gateReason = pageGate?.quarantineReason;
|
|
11199
11287
|
if (gateReason) {
|
|
11200
11288
|
skipped++;
|
|
11201
11289
|
if (file.relativePath.split("/").some((segment) => segment === "..")) {
|
|
@@ -11222,12 +11310,24 @@ function migrateRepository(options) {
|
|
|
11222
11310
|
}
|
|
11223
11311
|
warnings.push({
|
|
11224
11312
|
code: "gated-page",
|
|
11225
|
-
message: `Access-restricted on the source site (${gateReason}), so it was NOT published.
|
|
11313
|
+
message: `Access-restricted on the source site, but its access rules could not be migrated safely (${gateReason}), so it was NOT published. Fix the rules in the original, saved at ${QUARANTINE_DIRECTORY}/${file.relativePath}, and move it into src/content/; until then links from other pages to it will break.`,
|
|
11226
11314
|
source: file.relativePath
|
|
11227
11315
|
});
|
|
11228
11316
|
continue;
|
|
11229
11317
|
}
|
|
11318
|
+
if (pageGate?.reason && mintlifyProjectRoot) {
|
|
11319
|
+
try {
|
|
11320
|
+
rewriteRepositoryAssetLinks(
|
|
11321
|
+
inlineMdxSnippets(readFileSync6(file.absolutePath, "utf8"), file.absolutePath, repositoryDir, [], 0, mintlifyProjectRoot, snippetAliases, inlineGate),
|
|
11322
|
+
file.absolutePath,
|
|
11323
|
+
mintlifyProjectRoot,
|
|
11324
|
+
withholdAsset
|
|
11325
|
+
);
|
|
11326
|
+
} catch {
|
|
11327
|
+
}
|
|
11328
|
+
}
|
|
11230
11329
|
}
|
|
11330
|
+
const isRestrictedPage = Boolean(pageGate?.reason);
|
|
11231
11331
|
const referenced = exactReferenceMap.get(exactReferenceKey(file.relativePath)) ?? referenceMap.get(key);
|
|
11232
11332
|
let locale = referenced?.locale;
|
|
11233
11333
|
let navigationId = referenced?.navigationId ?? pageIdFromReference(file.relativePath, platform === "mintlify");
|
|
@@ -11257,7 +11357,7 @@ function migrateRepository(options) {
|
|
|
11257
11357
|
snippetAliases,
|
|
11258
11358
|
inlineGate
|
|
11259
11359
|
);
|
|
11260
|
-
const publishedText = trackPublishedRefs ? raw : void 0;
|
|
11360
|
+
const publishedText = trackPublishedRefs && !isRestrictedPage ? raw : void 0;
|
|
11261
11361
|
const pageAssetReferences = [];
|
|
11262
11362
|
if (platform === "fern" || platform === "mintlify" || platform === "docusaurus") {
|
|
11263
11363
|
raw = normalizeExplicitHeadingIds(
|
|
@@ -11350,7 +11450,8 @@ function migrateRepository(options) {
|
|
|
11350
11450
|
if (platform === "fern" && fernHiddenIds.has(navigationId)) page.hidden = true;
|
|
11351
11451
|
if (platform === "mintlify" && mintlifyProjectRoot) {
|
|
11352
11452
|
page.body = rewriteRepositoryAssetLinks(page.body, file.absolutePath, mintlifyProjectRoot, (assetPath, onDiskSpelling) => {
|
|
11353
|
-
|
|
11453
|
+
if (isRestrictedPage) withholdAsset(assetPath, onDiskSpelling);
|
|
11454
|
+
else pageAssetReferences.push(publicAssetKey(onDiskSpelling ?? assetPath));
|
|
11354
11455
|
}, (spelled, used) => {
|
|
11355
11456
|
warnings.push({ code: "unsupported-config", message: `"${spelled}" was not found beside this page; the file "${used}" in the site root was used instead and will be published.`, source: file.relativePath });
|
|
11356
11457
|
});
|
|
@@ -11437,7 +11538,10 @@ function migrateRepository(options) {
|
|
|
11437
11538
|
continue;
|
|
11438
11539
|
}
|
|
11439
11540
|
seenPageIds.add(page.id);
|
|
11541
|
+
if (pageGate?.access?.groups) page.groups = pageGate.access.groups;
|
|
11542
|
+
if (pageGate?.access?.isPublic !== void 0) page.public = pageGate.access.isPublic;
|
|
11440
11543
|
pages.push(page);
|
|
11544
|
+
if (isRestrictedPage) restrictedPages.add(page);
|
|
11441
11545
|
for (const assetPath of pageAssetReferences) addAssetReference(assetPath, file.relativePath);
|
|
11442
11546
|
if (publishedText !== void 0) addPathReferences(publishedText, posix4.dirname(file.relativePath).replace(/^\.$/, ""), publishedExact, publishedLoose);
|
|
11443
11547
|
if (hasDocusaurusDocCardList) {
|
|
@@ -11533,7 +11637,9 @@ function migrateRepository(options) {
|
|
|
11533
11637
|
try {
|
|
11534
11638
|
rootNavbarButtons = navbarLinkButtons(css);
|
|
11535
11639
|
rootStyleProjection = projectAuthoredStyles(css, [
|
|
11536
|
-
|
|
11640
|
+
// Restricted pages' markup must not keep a rule (and the image its
|
|
11641
|
+
// url() names) public; rules only they use are dropped.
|
|
11642
|
+
...pages.filter((page) => !restrictedPages.has(page)).map((page) => page.body),
|
|
11537
11643
|
...(componentMigrator?.files() ?? []).map((file) => typeof file.content === "string" ? file.content : ""),
|
|
11538
11644
|
// Site-wide scripts build markup of their own (a CTA card, say) that the stylesheet is meant for.
|
|
11539
11645
|
...files.filter((file) => isMintlifyServedScriptOrStyle(file.relativePath) && extname6(file.relativePath).toLowerCase() === ".js" && lstatSync6(file.absolutePath).size <= MAX_PAGE_BYTES).map((file) => readFileSync6(file.absolutePath, "utf8"))
|
|
@@ -11866,7 +11972,7 @@ function migrateRepository(options) {
|
|
|
11866
11972
|
warnings.push({ code: "gated-page", message: sharedSpecMessage(path, excluded), source: path });
|
|
11867
11973
|
};
|
|
11868
11974
|
const keptSpecRefs = [...publishedSpecRefs, ...navigationOperationRefs(mintlifyConfig)];
|
|
11869
|
-
const allSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs, pages);
|
|
11975
|
+
const allSpecs = resolveMintlifyApiSpecs(mintlifyConfig, files, warnings, remoteApiSpecs, pages.filter((page) => !restrictedPages.has(page)));
|
|
11870
11976
|
for (const remote of remoteApiSpecs) {
|
|
11871
11977
|
const { withheld, kept } = withheldOperationKeys({ sourcePath: remote.url, filename: remote.url }, withheldSpecRefs, keptSpecRefs);
|
|
11872
11978
|
if (withheld.length > 0) {
|
|
@@ -12360,8 +12466,15 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
12360
12466
|
});
|
|
12361
12467
|
if (platform === "mintlify") {
|
|
12362
12468
|
docsConfig = { ...docsConfig, markdown: { enabled: true } };
|
|
12469
|
+
if (restrictedPages.size > 0) {
|
|
12470
|
+
const apiNote = [...restrictedPages].some((page) => page.openapi) ? " API operations documented only on restricted pages were withheld from the published OpenAPI spec (specs are not access-controlled), so those pages do not render their endpoint until you restore the operation." : "";
|
|
12471
|
+
warnings.push({
|
|
12472
|
+
code: "gated-page",
|
|
12473
|
+
message: `${restrictedPages.size} page(s) are restricted with \`groups\`/\`public: false\`. Thally hides them from everyone until you configure reader authentication in docs.json \`auth\` (see /guides/authenticated-docs).${apiNote}`
|
|
12474
|
+
});
|
|
12475
|
+
}
|
|
12363
12476
|
if (quarantinedPageCount > 0 || withheldAssetCount > 0 || unreferencedAssetCount > 0) {
|
|
12364
|
-
const assetNote = withheldAssetCount > 0 ? `${withheldAssetCount} file(s) used only by access-restricted pages were
|
|
12477
|
+
const assetNote = withheldAssetCount > 0 ? `${withheldAssetCount} file(s) used only by access-restricted pages were not copied to public/, because public/ is not access-controlled; they are saved under ${QUARANTINE_DIRECTORY}/assets/. Copy them into public/ manually if they may be public, or the restricted pages will show them broken. ` : "";
|
|
12365
12478
|
const unreferencedReason = [
|
|
12366
12479
|
...hasWithheldContent ? ["this site has access-restricted content"] : [],
|
|
12367
12480
|
...pagesNotClassified ? ["pages were dropped by the file limit (or the file scan stopped at its cap) and could not all be checked for access restrictions"] : []
|
|
@@ -12369,12 +12482,12 @@ ${reasons.map((reason) => `- ${reason}`).join("\n")}` : "No importable Markdown
|
|
|
12369
12482
|
const unreferencedNote = unreferencedAssetCount > 0 ? `${unreferencedAssetCount} unreferenced asset(s) were kept out of public/ because ${unreferencedReason}; review ${QUARANTINE_DIRECTORY}/assets/ and copy any that published pages need. ` : "";
|
|
12370
12483
|
warnings.push({
|
|
12371
12484
|
code: "gated-page",
|
|
12372
|
-
message: (quarantinedPageCount > 0 ? `${quarantinedPageCount} access-restricted page(s) were withheld from the published site and saved under ${QUARANTINE_DIRECTORY}/ (local only: git-ignored, never served or deployed). ` : "") + assetNote + unreferencedNote + "Assets that published pages also use are still copied to public/. Review them before deciding how to publish or protect that content."
|
|
12485
|
+
message: (quarantinedPageCount > 0 ? `${quarantinedPageCount} access-restricted page(s) whose access rules could not be migrated safely were withheld from the published site and saved under ${QUARANTINE_DIRECTORY}/ (local only: git-ignored, never served or deployed). ` : "") + assetNote + unreferencedNote + "Assets that published pages also use are still copied to public/. Review them before deciding how to publish or protect that content."
|
|
12373
12486
|
});
|
|
12374
12487
|
}
|
|
12375
12488
|
warnings.push({
|
|
12376
12489
|
code: "gated-page",
|
|
12377
|
-
message: (sawPublicTrue ? "Some pages set `public: true`, which means the source site used Mintlify authentication and every page WITHOUT it was private. " : "") + "Access control set in the Mintlify dashboard is not visible in the repository. Check the source site's dashboard access settings before publishing: Thally
|
|
12490
|
+
message: (sawPublicTrue ? "Some pages set `public: true`, which means the source site used Mintlify authentication and every page WITHOUT it was private. " : "") + "Access control set in the Mintlify dashboard is not visible in the repository. Check the source site's dashboard access settings before publishing: until docs.json `auth` is configured, Thally publishes every imported page that has no `groups` or `public: false` to everyone. Confirm nothing here was meant to stay private before deploying."
|
|
12378
12491
|
});
|
|
12379
12492
|
}
|
|
12380
12493
|
if (fernRawConfig?.logo && !logoLight && !logo.light) warnings.push({
|
|
@@ -12438,6 +12551,10 @@ function renderPage(bundle, page) {
|
|
|
12438
12551
|
page.mode ? `mode: ${yamlString(page.mode)}` : null,
|
|
12439
12552
|
page.hidden ? "hidden: true" : null,
|
|
12440
12553
|
page.noindex ? "noindex: true" : null,
|
|
12554
|
+
// Group names must match identity-provider claims exactly, so they are
|
|
12555
|
+
// quoted as-is rather than whitespace-collapsed like display strings.
|
|
12556
|
+
page.groups?.length ? `groups: [${page.groups.map((group) => JSON.stringify(group)).join(", ")}]` : null,
|
|
12557
|
+
page.public !== void 0 ? `public: ${page.public ? "true" : "false"}` : null,
|
|
12441
12558
|
...Object.entries(page.meta ?? {}).map(([field, value]) => `${field}: ${yamlString(value)}`),
|
|
12442
12559
|
page.openapi ? `openapi: ${yamlString(page.openapi)}` : null,
|
|
12443
12560
|
page.api ? `api: ${yamlString(page.api)}` : null,
|