blodemd 0.0.10 → 0.0.11
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/dist/cli.mjs +370 -286
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -2
- package/packages/@repo/models/dist/docs-config.d.ts +3 -0
- package/packages/@repo/models/dist/docs-config.d.ts.map +1 -1
- package/packages/@repo/models/dist/docs-config.js +6 -0
- package/packages/@repo/models/src/docs-config.ts +6 -0
- package/packages/@repo/previewing/dist/index.d.ts +1 -0
- package/packages/@repo/previewing/dist/index.d.ts.map +1 -1
- package/packages/@repo/previewing/dist/index.js +5 -2
- package/packages/@repo/previewing/src/index.ts +8 -2
- package/packages/@repo/validation/src/blodemd-docs-schema.json +7 -1
package/dist/cli.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
21
|
//#region src/constants.ts
|
|
22
22
|
const CLI_NAME = "blodemd";
|
|
23
|
+
const BLODE_PROJECT_ENV = "BLODEMD_PROJECT";
|
|
23
24
|
const OAUTH_CLIENT_ID = "6b5f9860-fe96-4a83-b1ad-266260523c91";
|
|
24
25
|
const DEFAULT_OAUTH_CALLBACK_PORT = 8787;
|
|
25
26
|
const DEFAULT_OAUTH_CALLBACK_PATH = "/auth/callback";
|
|
@@ -831,10 +832,48 @@ const findExistingPaths = async (root, relativePaths) => {
|
|
|
831
832
|
}))).filter((relativePath) => relativePath !== null).toSorted((left, right) => left.localeCompare(right));
|
|
832
833
|
};
|
|
833
834
|
//#endregion
|
|
835
|
+
//#region src/project-config.ts
|
|
836
|
+
const LEGACY_PROJECT_NAME_FALLBACK_WARNING = "docs.json.slug is recommended. Falling back to docs.json.name as the deployment slug is deprecated.";
|
|
837
|
+
const validateProjectSlug = (value) => {
|
|
838
|
+
const trimmed = value?.trim();
|
|
839
|
+
if (!trimmed) return "Project slug is required.";
|
|
840
|
+
const normalized = slugify(trimmed);
|
|
841
|
+
if (!normalized) return "Use at least one letter or number.";
|
|
842
|
+
if (normalized !== trimmed) return `Use lowercase letters, numbers, and hyphens. Try "${normalized}".`;
|
|
843
|
+
};
|
|
844
|
+
const deriveDisplayNameFromProjectSlug = (projectSlug) => projectSlug.split("-").filter(Boolean).map((segment) => segment[0]?.toUpperCase() + segment.slice(1)).join(" ");
|
|
845
|
+
const resolveProjectTarget = (options) => {
|
|
846
|
+
if (options.cliProject) return {
|
|
847
|
+
project: options.cliProject,
|
|
848
|
+
usedLegacyNameFallback: false
|
|
849
|
+
};
|
|
850
|
+
if (options.envProject) return {
|
|
851
|
+
project: options.envProject,
|
|
852
|
+
usedLegacyNameFallback: false
|
|
853
|
+
};
|
|
854
|
+
if (options.config.slug) return {
|
|
855
|
+
project: options.config.slug,
|
|
856
|
+
usedLegacyNameFallback: false
|
|
857
|
+
};
|
|
858
|
+
if (options.config.name) return {
|
|
859
|
+
project: options.config.name,
|
|
860
|
+
usedLegacyNameFallback: true
|
|
861
|
+
};
|
|
862
|
+
return {
|
|
863
|
+
project: void 0,
|
|
864
|
+
usedLegacyNameFallback: false
|
|
865
|
+
};
|
|
866
|
+
};
|
|
867
|
+
const getProjectSlugError = (project) => {
|
|
868
|
+
if (!project) return;
|
|
869
|
+
return validateProjectSlug(project);
|
|
870
|
+
};
|
|
871
|
+
//#endregion
|
|
834
872
|
//#region src/scaffold.ts
|
|
835
873
|
const SCAFFOLD_TEMPLATES = ["minimal", "starter"];
|
|
836
874
|
const DEFAULT_SCAFFOLD_DIRECTORY = "docs";
|
|
837
875
|
const stringifyJson = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
876
|
+
const escapeXmlText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
838
877
|
const isScaffoldTemplate = (value) => SCAFFOLD_TEMPLATES.includes(value);
|
|
839
878
|
const normalizeProjectSlug = (value) => slugify(value) || "my-project";
|
|
840
879
|
const resolveScaffoldDirectory = (directory) => directory?.trim() || "docs";
|
|
@@ -843,22 +882,16 @@ const deriveDefaultProjectSlug = (directory, cwd) => {
|
|
|
843
882
|
if (resolvedDirectory === "." || resolvedDirectory === "docs") return normalizeProjectSlug(path.basename(cwd));
|
|
844
883
|
return normalizeProjectSlug(path.basename(path.resolve(cwd, resolvedDirectory)));
|
|
845
884
|
};
|
|
846
|
-
const
|
|
847
|
-
const trimmed = value?.trim();
|
|
848
|
-
if (!trimmed) return "Project slug is required.";
|
|
849
|
-
const normalized = slugify(trimmed);
|
|
850
|
-
if (!normalized) return "Use at least one letter or number.";
|
|
851
|
-
if (normalized !== trimmed) return `Use lowercase letters, numbers, and hyphens. Try "${normalized}".`;
|
|
852
|
-
};
|
|
853
|
-
const createMinimalDocsJson = (projectSlug) => ({
|
|
885
|
+
const createMinimalDocsJson = (projectSlug, displayName) => ({
|
|
854
886
|
$schema: "https://blode.md/docs.json",
|
|
855
|
-
name:
|
|
887
|
+
name: displayName,
|
|
856
888
|
navigation: { groups: [{
|
|
857
889
|
group: "Getting Started",
|
|
858
890
|
pages: ["index"]
|
|
859
|
-
}] }
|
|
891
|
+
}] },
|
|
892
|
+
slug: projectSlug
|
|
860
893
|
});
|
|
861
|
-
const createStarterDocsJson = (projectSlug) => ({
|
|
894
|
+
const createStarterDocsJson = (projectSlug, displayName) => ({
|
|
862
895
|
$schema: "https://blode.md/docs.json",
|
|
863
896
|
appearance: { default: "system" },
|
|
864
897
|
contextual: { options: [
|
|
@@ -870,12 +903,12 @@ const createStarterDocsJson = (projectSlug) => ({
|
|
|
870
903
|
description: "Ship documentation from your terminal.",
|
|
871
904
|
favicon: "/favicon.svg",
|
|
872
905
|
logo: {
|
|
873
|
-
alt: `${
|
|
906
|
+
alt: `${displayName} logo`,
|
|
874
907
|
dark: "/logo/dark.svg",
|
|
875
908
|
light: "/logo/light.svg"
|
|
876
909
|
},
|
|
877
910
|
metadata: { timestamp: true },
|
|
878
|
-
name:
|
|
911
|
+
name: displayName,
|
|
879
912
|
navigation: { groups: [{
|
|
880
913
|
group: "Getting Started",
|
|
881
914
|
pages: [
|
|
@@ -883,7 +916,8 @@ const createStarterDocsJson = (projectSlug) => ({
|
|
|
883
916
|
"quickstart",
|
|
884
917
|
"development"
|
|
885
918
|
]
|
|
886
|
-
}] }
|
|
919
|
+
}] },
|
|
920
|
+
slug: projectSlug
|
|
887
921
|
});
|
|
888
922
|
const claudeInstructions = [
|
|
889
923
|
"> **First-time setup**: Customize this file for your project. Prompt the user to update terminology, style preferences, and content boundaries before drafting large amounts of docs.",
|
|
@@ -927,270 +961,276 @@ const claudeInstructions = [
|
|
|
927
961
|
"- Run `blodemd validate` before publishing.",
|
|
928
962
|
""
|
|
929
963
|
].join("\n");
|
|
930
|
-
const createMinimalFiles = (projectSlug) => [{
|
|
931
|
-
content: stringifyJson(createMinimalDocsJson(projectSlug)),
|
|
964
|
+
const createMinimalFiles = (projectSlug, displayName) => [{
|
|
965
|
+
content: stringifyJson(createMinimalDocsJson(projectSlug, displayName)),
|
|
932
966
|
path: "docs.json"
|
|
933
967
|
}, {
|
|
934
968
|
content: "---\ntitle: Welcome\n---\n\nStart writing your docs here.\n",
|
|
935
969
|
path: "index.mdx"
|
|
936
970
|
}];
|
|
937
|
-
const createStarterFiles = (projectSlug) =>
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
"
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
971
|
+
const createStarterFiles = (projectSlug, displayName) => {
|
|
972
|
+
const escapedDisplayName = escapeXmlText(displayName);
|
|
973
|
+
return [
|
|
974
|
+
{
|
|
975
|
+
content: stringifyJson(createStarterDocsJson(projectSlug, displayName)),
|
|
976
|
+
path: "docs.json"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
content: [
|
|
980
|
+
"---",
|
|
981
|
+
"title: Welcome",
|
|
982
|
+
"description: Start here.",
|
|
983
|
+
"---",
|
|
984
|
+
"",
|
|
985
|
+
"# Welcome",
|
|
986
|
+
"",
|
|
987
|
+
"This starter gives you branded assets, repo helper files, and a small docs structure you can rewrite quickly.",
|
|
988
|
+
"",
|
|
989
|
+
"",
|
|
990
|
+
"",
|
|
991
|
+
"## What is included",
|
|
992
|
+
"",
|
|
993
|
+
"- A starter `docs.json` with branding, contextual actions, and navigation.",
|
|
994
|
+
"- Placeholder brand assets in `/logo` and `/images`.",
|
|
995
|
+
"- Repo helper files like `.gitignore`, `README.md`, `AGENTS.md`, and `CLAUDE.md`.",
|
|
996
|
+
"",
|
|
997
|
+
"## Next steps",
|
|
998
|
+
"",
|
|
999
|
+
"- Confirm `slug` in `docs.json` matches your deployment target.",
|
|
1000
|
+
"- Update `name` in `docs.json` to match the visible product or docs brand.",
|
|
1001
|
+
"- Set `description` in `docs.json` to explain your product.",
|
|
1002
|
+
"- Replace the files in `/logo` and `/images` with your own brand assets.",
|
|
1003
|
+
"- Rewrite `CLAUDE.md` with your terminology and writing standards.",
|
|
1004
|
+
"- Update this page, then preview locally with `blodemd dev`.",
|
|
1005
|
+
"",
|
|
1006
|
+
"## Included pages",
|
|
1007
|
+
"",
|
|
1008
|
+
"- [Quickstart](quickstart)",
|
|
1009
|
+
"- [Development](development)",
|
|
1010
|
+
""
|
|
1011
|
+
].join("\n"),
|
|
1012
|
+
path: "index.mdx"
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
content: [
|
|
1016
|
+
"---",
|
|
1017
|
+
"title: Quickstart",
|
|
1018
|
+
"description: Get your docs running fast.",
|
|
1019
|
+
"---",
|
|
1020
|
+
"",
|
|
1021
|
+
"# Quickstart",
|
|
1022
|
+
"",
|
|
1023
|
+
"",
|
|
1024
|
+
"",
|
|
1025
|
+
"1. Confirm `slug` in `docs.json` matches your deployment target.",
|
|
1026
|
+
"2. Update `name` in `docs.json` to match your visible docs brand.",
|
|
1027
|
+
"3. Update the `description` field to match your product.",
|
|
1028
|
+
"4. Replace the assets in `/logo` and `/images`.",
|
|
1029
|
+
"5. Run `blodemd dev` to preview locally.",
|
|
1030
|
+
"6. Run `blodemd push` when you are ready to publish.",
|
|
1031
|
+
""
|
|
1032
|
+
].join("\n"),
|
|
1033
|
+
path: "quickstart.mdx"
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
content: [
|
|
1037
|
+
"---",
|
|
1038
|
+
"title: Development",
|
|
1039
|
+
"description: Work on your docs locally.",
|
|
1040
|
+
"---",
|
|
1041
|
+
"",
|
|
1042
|
+
"# Development",
|
|
1043
|
+
"",
|
|
1044
|
+
"",
|
|
1045
|
+
"",
|
|
1046
|
+
"Preview locally with:",
|
|
1047
|
+
"",
|
|
1048
|
+
"```bash",
|
|
1049
|
+
"blodemd dev",
|
|
1050
|
+
"```",
|
|
1051
|
+
"",
|
|
1052
|
+
"Validate your configuration with:",
|
|
1053
|
+
"",
|
|
1054
|
+
"```bash",
|
|
1055
|
+
"blodemd validate",
|
|
1056
|
+
"```",
|
|
1057
|
+
"",
|
|
1058
|
+
"Keep `CLAUDE.md` current as your product terminology and writing rules evolve.",
|
|
1059
|
+
""
|
|
1060
|
+
].join("\n"),
|
|
1061
|
+
path: "development.mdx"
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
content: [
|
|
1065
|
+
"# Documentation starter",
|
|
1066
|
+
"",
|
|
1067
|
+
"This directory was scaffolded with `blodemd new --template starter`.",
|
|
1068
|
+
"",
|
|
1069
|
+
"## What is included",
|
|
1070
|
+
"",
|
|
1071
|
+
"- `docs.json` with branding, contextual actions, and starter navigation",
|
|
1072
|
+
"- `index.mdx`, `quickstart.mdx`, and `development.mdx`",
|
|
1073
|
+
"- Placeholder brand assets in `/logo` and `/images`",
|
|
1074
|
+
"- Repo helper files: `.gitignore`, `README.md`, `AGENTS.md`, and `CLAUDE.md`",
|
|
1075
|
+
"",
|
|
1076
|
+
"## Commands",
|
|
1077
|
+
"",
|
|
1078
|
+
"```bash",
|
|
1079
|
+
"blodemd dev",
|
|
1080
|
+
"blodemd validate",
|
|
1081
|
+
"blodemd push",
|
|
1082
|
+
"```",
|
|
1083
|
+
"",
|
|
1084
|
+
"## Customize",
|
|
1085
|
+
"",
|
|
1086
|
+
"- Confirm `slug` in `docs.json` and set the display `name` and description.",
|
|
1087
|
+
"- Replace the assets in `/logo` and `/images`.",
|
|
1088
|
+
"- Rewrite `CLAUDE.md` with project-specific terminology and writing rules.",
|
|
1089
|
+
"- Rewrite the starter pages to match your product.",
|
|
1090
|
+
"- Add a `LICENSE` file deliberately if this repo will be public.",
|
|
1091
|
+
""
|
|
1092
|
+
].join("\n"),
|
|
1093
|
+
path: "README.md"
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
fallbackContent: claudeInstructions,
|
|
1097
|
+
path: "AGENTS.md",
|
|
1098
|
+
target: "CLAUDE.md",
|
|
1099
|
+
type: "symlink"
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
content: claudeInstructions,
|
|
1103
|
+
path: "CLAUDE.md"
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
content: [
|
|
1107
|
+
"# dependencies",
|
|
1108
|
+
"node_modules/",
|
|
1109
|
+
"",
|
|
1110
|
+
"# local env files",
|
|
1111
|
+
".env*",
|
|
1112
|
+
"!.env.example",
|
|
1113
|
+
"",
|
|
1114
|
+
"# build and cache",
|
|
1115
|
+
".next/",
|
|
1116
|
+
".turbo/",
|
|
1117
|
+
"coverage/",
|
|
1118
|
+
"dist/",
|
|
1119
|
+
".vercel/",
|
|
1120
|
+
"*.tsbuildinfo",
|
|
1121
|
+
"",
|
|
1122
|
+
"# logs",
|
|
1123
|
+
"*.log",
|
|
1124
|
+
"",
|
|
1125
|
+
"# misc",
|
|
1126
|
+
".DS_Store",
|
|
1127
|
+
""
|
|
1128
|
+
].join("\n"),
|
|
1129
|
+
path: ".gitignore"
|
|
1130
|
+
},
|
|
1131
|
+
{
|
|
1132
|
+
content: [
|
|
1133
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 64 64\" fill=\"none\">",
|
|
1134
|
+
" <rect width=\"64\" height=\"64\" rx=\"16\" fill=\"#0D9373\"/>",
|
|
1135
|
+
" <path d=\"M20 18h14c8.837 0 16 7.163 16 16s-7.163 16-16 16H20V18Z\" fill=\"#CFF6EE\"/>",
|
|
1136
|
+
" <path d=\"M28 26h6c5.523 0 10 4.477 10 10s-4.477 10-10 10h-6V26Z\" fill=\"#0C3A33\"/>",
|
|
1137
|
+
"</svg>",
|
|
1138
|
+
""
|
|
1139
|
+
].join("\n"),
|
|
1140
|
+
path: "favicon.svg"
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
content: [
|
|
1144
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 240 64\" fill=\"none\">",
|
|
1145
|
+
" <rect width=\"64\" height=\"64\" rx=\"16\" fill=\"#0C3A33\"/>",
|
|
1146
|
+
" <path d=\"M20 18h14c8.837 0 16 7.163 16 16s-7.163 16-16 16H20V18Z\" fill=\"#CFF6EE\"/>",
|
|
1147
|
+
` <text x="84" y="41" fill="#111827" font-family="Arial, sans-serif" font-size="28" font-weight="700">${escapedDisplayName}</text>`,
|
|
1148
|
+
"</svg>",
|
|
1149
|
+
""
|
|
1150
|
+
].join("\n"),
|
|
1151
|
+
path: "logo/light.svg"
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
content: [
|
|
1155
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 240 64\" fill=\"none\">",
|
|
1156
|
+
" <rect width=\"64\" height=\"64\" rx=\"16\" fill=\"#CFF6EE\"/>",
|
|
1157
|
+
" <path d=\"M20 18h14c8.837 0 16 7.163 16 16s-7.163 16-16 16H20V18Z\" fill=\"#0C3A33\"/>",
|
|
1158
|
+
` <text x="84" y="41" fill="#F9FAFB" font-family="Arial, sans-serif" font-size="28" font-weight="700">${escapedDisplayName}</text>`,
|
|
1159
|
+
"</svg>",
|
|
1160
|
+
""
|
|
1161
|
+
].join("\n"),
|
|
1162
|
+
path: "logo/dark.svg"
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
content: [
|
|
1166
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 960 520\" fill=\"none\">",
|
|
1167
|
+
" <rect width=\"960\" height=\"520\" rx=\"32\" fill=\"#F4FBF8\"/>",
|
|
1168
|
+
" <rect x=\"48\" y=\"48\" width=\"260\" height=\"424\" rx=\"24\" fill=\"#E1F4EE\"/>",
|
|
1169
|
+
" <rect x=\"96\" y=\"120\" width=\"164\" height=\"20\" rx=\"10\" fill=\"#0D9373\" opacity=\".25\"/>",
|
|
1170
|
+
" <rect x=\"96\" y=\"164\" width=\"132\" height=\"16\" rx=\"8\" fill=\"#0D9373\" opacity=\".18\"/>",
|
|
1171
|
+
" <rect x=\"96\" y=\"204\" width=\"152\" height=\"16\" rx=\"8\" fill=\"#0D9373\" opacity=\".18\"/>",
|
|
1172
|
+
" <rect x=\"356\" y=\"80\" width=\"556\" height=\"104\" rx=\"24\" fill=\"#0D9373\"/>",
|
|
1173
|
+
" <rect x=\"388\" y=\"116\" width=\"220\" height=\"18\" rx=\"9\" fill=\"#CFF6EE\"/>",
|
|
1174
|
+
" <rect x=\"388\" y=\"148\" width=\"156\" height=\"14\" rx=\"7\" fill=\"#CFF6EE\" opacity=\".7\"/>",
|
|
1175
|
+
" <rect x=\"356\" y=\"216\" width=\"268\" height=\"256\" rx=\"24\" fill=\"#FFFFFF\"/>",
|
|
1176
|
+
" <rect x=\"388\" y=\"260\" width=\"168\" height=\"16\" rx=\"8\" fill=\"#0C3A33\" opacity=\".18\"/>",
|
|
1177
|
+
" <rect x=\"388\" y=\"292\" width=\"196\" height=\"16\" rx=\"8\" fill=\"#0C3A33\" opacity=\".12\"/>",
|
|
1178
|
+
" <rect x=\"656\" y=\"216\" width=\"256\" height=\"256\" rx=\"24\" fill=\"#0C3A33\"/>",
|
|
1179
|
+
" <rect x=\"692\" y=\"260\" width=\"128\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".85\"/>",
|
|
1180
|
+
" <rect x=\"692\" y=\"292\" width=\"152\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".45\"/>",
|
|
1181
|
+
" <circle cx=\"804\" cy=\"380\" r=\"52\" fill=\"#0D9373\"/>",
|
|
1182
|
+
"</svg>",
|
|
1183
|
+
""
|
|
1184
|
+
].join("\n"),
|
|
1185
|
+
path: "images/hero-light.svg"
|
|
1186
|
+
},
|
|
1187
|
+
{
|
|
1188
|
+
content: [
|
|
1189
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 960 520\" fill=\"none\">",
|
|
1190
|
+
" <rect width=\"960\" height=\"520\" rx=\"32\" fill=\"#071715\"/>",
|
|
1191
|
+
" <rect x=\"48\" y=\"48\" width=\"260\" height=\"424\" rx=\"24\" fill=\"#0F2E28\"/>",
|
|
1192
|
+
" <rect x=\"96\" y=\"120\" width=\"164\" height=\"20\" rx=\"10\" fill=\"#CFF6EE\" opacity=\".18\"/>",
|
|
1193
|
+
" <rect x=\"96\" y=\"164\" width=\"132\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".14\"/>",
|
|
1194
|
+
" <rect x=\"96\" y=\"204\" width=\"152\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".14\"/>",
|
|
1195
|
+
" <rect x=\"356\" y=\"80\" width=\"556\" height=\"104\" rx=\"24\" fill=\"#0D9373\"/>",
|
|
1196
|
+
" <rect x=\"388\" y=\"116\" width=\"220\" height=\"18\" rx=\"9\" fill=\"#E8FFF9\"/>",
|
|
1197
|
+
" <rect x=\"388\" y=\"148\" width=\"156\" height=\"14\" rx=\"7\" fill=\"#E8FFF9\" opacity=\".6\"/>",
|
|
1198
|
+
" <rect x=\"356\" y=\"216\" width=\"268\" height=\"256\" rx=\"24\" fill=\"#0C3A33\"/>",
|
|
1199
|
+
" <rect x=\"388\" y=\"260\" width=\"168\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".22\"/>",
|
|
1200
|
+
" <rect x=\"388\" y=\"292\" width=\"196\" height=\"16\" rx=\"8\" fill=\"#CFF6EE\" opacity=\".16\"/>",
|
|
1201
|
+
" <rect x=\"656\" y=\"216\" width=\"256\" height=\"256\" rx=\"24\" fill=\"#E9FFF8\"/>",
|
|
1202
|
+
" <rect x=\"692\" y=\"260\" width=\"128\" height=\"16\" rx=\"8\" fill=\"#0C3A33\" opacity=\".24\"/>",
|
|
1203
|
+
" <rect x=\"692\" y=\"292\" width=\"152\" height=\"16\" rx=\"8\" fill=\"#0C3A33\" opacity=\".12\"/>",
|
|
1204
|
+
" <circle cx=\"804\" cy=\"380\" r=\"52\" fill=\"#0D9373\"/>",
|
|
1205
|
+
"</svg>",
|
|
1206
|
+
""
|
|
1207
|
+
].join("\n"),
|
|
1208
|
+
path: "images/hero-dark.svg"
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
content: [
|
|
1212
|
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 960 520\" fill=\"none\">",
|
|
1213
|
+
" <rect width=\"960\" height=\"520\" rx=\"32\" fill=\"#F8FCFA\"/>",
|
|
1214
|
+
" <rect x=\"60\" y=\"76\" width=\"840\" height=\"368\" rx=\"28\" fill=\"#FFFFFF\" stroke=\"#D7ECE6\" stroke-width=\"4\"/>",
|
|
1215
|
+
" <rect x=\"108\" y=\"124\" width=\"96\" height=\"96\" rx=\"24\" fill=\"#0D9373\"/>",
|
|
1216
|
+
" <path d=\"M136 172l18 18 38-48\" stroke=\"#CFF6EE\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"18\"/>",
|
|
1217
|
+
" <rect x=\"244\" y=\"132\" width=\"280\" height=\"24\" rx=\"12\" fill=\"#0C3A33\"/>",
|
|
1218
|
+
" <rect x=\"244\" y=\"176\" width=\"416\" height=\"18\" rx=\"9\" fill=\"#0C3A33\" opacity=\".16\"/>",
|
|
1219
|
+
" <rect x=\"244\" y=\"214\" width=\"340\" height=\"18\" rx=\"9\" fill=\"#0C3A33\" opacity=\".12\"/>",
|
|
1220
|
+
" <rect x=\"108\" y=\"280\" width=\"744\" height=\"22\" rx=\"11\" fill=\"#0D9373\" opacity=\".12\"/>",
|
|
1221
|
+
" <rect x=\"108\" y=\"326\" width=\"520\" height=\"22\" rx=\"11\" fill=\"#0D9373\" opacity=\".12\"/>",
|
|
1222
|
+
" <rect x=\"108\" y=\"372\" width=\"612\" height=\"22\" rx=\"11\" fill=\"#0D9373\" opacity=\".12\"/>",
|
|
1223
|
+
"</svg>",
|
|
1224
|
+
""
|
|
1225
|
+
].join("\n"),
|
|
1226
|
+
path: "images/checks-passed.svg"
|
|
1227
|
+
}
|
|
1228
|
+
];
|
|
1229
|
+
};
|
|
1191
1230
|
const getScaffoldFiles = (template, options) => {
|
|
1192
1231
|
const projectSlug = options?.projectSlug ?? "my-project";
|
|
1193
|
-
|
|
1232
|
+
const displayName = options?.displayName ?? deriveDisplayNameFromProjectSlug(projectSlug);
|
|
1233
|
+
return template === "starter" ? createStarterFiles(projectSlug, displayName) : createMinimalFiles(projectSlug, displayName);
|
|
1194
1234
|
};
|
|
1195
1235
|
//#endregion
|
|
1196
1236
|
//#region src/new-flow.ts
|
|
@@ -1497,6 +1537,18 @@ const promptForProjectSlug = async (initialValue) => {
|
|
|
1497
1537
|
if (isCancel(projectSlug)) return;
|
|
1498
1538
|
return projectSlug.trim();
|
|
1499
1539
|
};
|
|
1540
|
+
const promptForDisplayName = async (initialValue) => {
|
|
1541
|
+
const displayName = await text({
|
|
1542
|
+
initialValue,
|
|
1543
|
+
message: "Display name",
|
|
1544
|
+
placeholder: initialValue,
|
|
1545
|
+
validate: (value) => {
|
|
1546
|
+
if (!value?.trim()) return "Display name is required.";
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
if (isCancel(displayName)) return;
|
|
1550
|
+
return displayName.trim();
|
|
1551
|
+
};
|
|
1500
1552
|
const resolveRequestedDirectory = async (directory, shouldPrompt) => {
|
|
1501
1553
|
let currentDirectoryEntries = [];
|
|
1502
1554
|
if (!directory && shouldPrompt) currentDirectoryEntries = await fs.readdir(process.cwd());
|
|
@@ -1527,14 +1579,20 @@ const confirmScaffoldTarget = async (root, template, shouldPrompt, options) => {
|
|
|
1527
1579
|
const shouldContinue = await confirm({ message: `Scaffold into the non-empty directory ${root}? Existing files will be left untouched.` });
|
|
1528
1580
|
return !isCancel(shouldContinue) && shouldContinue;
|
|
1529
1581
|
};
|
|
1530
|
-
const resolveProjectSlug = async (
|
|
1582
|
+
const resolveProjectSlug = async (providedSlug, directory, shouldPrompt) => {
|
|
1531
1583
|
const defaultProjectSlug = deriveDefaultProjectSlug(directory, process.cwd());
|
|
1532
|
-
if (
|
|
1584
|
+
if (providedSlug) return providedSlug;
|
|
1533
1585
|
if (!shouldPrompt) return defaultProjectSlug;
|
|
1534
1586
|
return await promptForProjectSlug(defaultProjectSlug);
|
|
1535
1587
|
};
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1588
|
+
const resolveDisplayName = async (providedDisplayName, projectSlug, shouldPrompt) => {
|
|
1589
|
+
const defaultDisplayName = deriveDisplayNameFromProjectSlug(projectSlug);
|
|
1590
|
+
if (providedDisplayName?.trim()) return providedDisplayName.trim();
|
|
1591
|
+
if (!shouldPrompt) return defaultDisplayName;
|
|
1592
|
+
return await promptForDisplayName(defaultDisplayName);
|
|
1593
|
+
};
|
|
1594
|
+
const writeScaffoldFiles = async (root, template, options) => {
|
|
1595
|
+
for (const file of getScaffoldFiles(template, options)) {
|
|
1538
1596
|
const filePath = path.join(root, file.path);
|
|
1539
1597
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
1540
1598
|
if (file.type === "symlink") {
|
|
@@ -1552,7 +1610,11 @@ const fetchUserEmail = async (apiUrl, token) => {
|
|
|
1552
1610
|
}
|
|
1553
1611
|
};
|
|
1554
1612
|
const resolvePushConfig = async (config, options) => {
|
|
1555
|
-
const
|
|
1613
|
+
const { project, usedLegacyNameFallback } = resolveProjectTarget({
|
|
1614
|
+
cliProject: options.project,
|
|
1615
|
+
config,
|
|
1616
|
+
envProject: process.env[BLODE_PROJECT_ENV]
|
|
1617
|
+
});
|
|
1556
1618
|
const apiUrl = options.apiUrl ?? process.env["BLODEMD_API_URL"] ?? "https://api.blode.md";
|
|
1557
1619
|
const authToken = (await resolveAuthToken(options.apiKey))?.token;
|
|
1558
1620
|
const branch = options.branch ?? process.env["BLODEMD_BRANCH"] ?? process.env.GITHUB_REF_NAME ?? readGitValue([
|
|
@@ -1565,23 +1627,30 @@ const resolvePushConfig = async (config, options) => {
|
|
|
1565
1627
|
"-1",
|
|
1566
1628
|
"--pretty=%s"
|
|
1567
1629
|
]);
|
|
1568
|
-
if (!project) throw new Error("Missing project slug. Set \"
|
|
1630
|
+
if (!project) throw new Error("Missing project slug. Set \"slug\" in docs.json, pass --project, or set BLODEMD_PROJECT.");
|
|
1631
|
+
const projectSlugError = getProjectSlugError(project);
|
|
1632
|
+
if (projectSlugError) {
|
|
1633
|
+
if (usedLegacyNameFallback) throw new Error(`docs.json.name is not a valid deployment slug. Add "slug" to docs.json, pass --project, or set BLODEMD_PROJECT. ${projectSlugError}`);
|
|
1634
|
+
throw new Error(`Invalid project slug "${project}". ${projectSlugError}`);
|
|
1635
|
+
}
|
|
1569
1636
|
if (!authToken) throw new Error("Missing credentials. Run \"blodemd login\", pass --api-key, or set BLODEMD_API_KEY.");
|
|
1570
1637
|
return {
|
|
1571
1638
|
apiUrl,
|
|
1572
1639
|
authToken,
|
|
1573
1640
|
branch,
|
|
1574
1641
|
commitMessage,
|
|
1575
|
-
project
|
|
1642
|
+
project,
|
|
1643
|
+
projectDisplayName: config.name?.trim() || project,
|
|
1644
|
+
usedLegacyNameFallback
|
|
1576
1645
|
};
|
|
1577
1646
|
};
|
|
1578
|
-
const autoCreateProject = async (project, apiUrl, headers) => {
|
|
1647
|
+
const autoCreateProject = async (project, projectDisplayName, apiUrl, headers) => {
|
|
1579
1648
|
if (!(await readAuthFile())?.session) throw new Error(`Project "${project}" not found. Create it at blode.md or login with "blodemd login" to auto-create.`);
|
|
1580
1649
|
const shouldCreate = await confirm({ message: `Project "${project}" doesn't exist. Create it?` });
|
|
1581
1650
|
if (isCancel(shouldCreate) || !shouldCreate) return false;
|
|
1582
1651
|
const createResult = await requestJson(new URL("/projects", apiUrl).toString(), {
|
|
1583
1652
|
body: JSON.stringify({
|
|
1584
|
-
name:
|
|
1653
|
+
name: projectDisplayName,
|
|
1585
1654
|
slug: project
|
|
1586
1655
|
}),
|
|
1587
1656
|
headers,
|
|
@@ -1594,6 +1663,7 @@ const autoCreateProject = async (project, apiUrl, headers) => {
|
|
|
1594
1663
|
const scaffoldDocsSite = async (directory, options) => {
|
|
1595
1664
|
intro(chalk.bold("blodemd new"));
|
|
1596
1665
|
if (options?.deprecatedCommand) log.warn(`"${options.deprecatedCommand}" is deprecated. Use ${chalk.cyan("blodemd new")} instead.`);
|
|
1666
|
+
if (options?.name && !options.slug) log.warn(`"${chalk.cyan("--name")}" is deprecated. Use ${chalk.cyan("--slug")} instead.`);
|
|
1597
1667
|
try {
|
|
1598
1668
|
const template = options?.template ?? "minimal";
|
|
1599
1669
|
const shouldPrompt = isInteractiveTerminal() && !options?.yes;
|
|
@@ -1608,15 +1678,24 @@ const scaffoldDocsSite = async (directory, options) => {
|
|
|
1608
1678
|
log.warn("Cancelled");
|
|
1609
1679
|
return;
|
|
1610
1680
|
}
|
|
1611
|
-
const projectSlug = await resolveProjectSlug(options?.name, resolvedDirectory, shouldPrompt);
|
|
1681
|
+
const projectSlug = await resolveProjectSlug(options?.slug ?? options?.name, resolvedDirectory, shouldPrompt);
|
|
1612
1682
|
if (!projectSlug) {
|
|
1613
1683
|
log.warn("Cancelled");
|
|
1614
1684
|
return;
|
|
1615
1685
|
}
|
|
1686
|
+
const displayName = await resolveDisplayName(options?.displayName, projectSlug, shouldPrompt);
|
|
1687
|
+
if (!displayName) {
|
|
1688
|
+
log.warn("Cancelled");
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1616
1691
|
await fs.mkdir(root, { recursive: true });
|
|
1617
|
-
await writeScaffoldFiles(root, template,
|
|
1692
|
+
await writeScaffoldFiles(root, template, {
|
|
1693
|
+
displayName,
|
|
1694
|
+
projectSlug
|
|
1695
|
+
});
|
|
1618
1696
|
log.success(`Docs scaffolded in ${chalk.cyan(root)}`);
|
|
1619
1697
|
if (template === "starter") log.info("Starter template includes brand assets and helper files.");
|
|
1698
|
+
log.info(`Display name: ${chalk.cyan(displayName)}`);
|
|
1620
1699
|
log.info(`Project slug: ${chalk.cyan(projectSlug)}`);
|
|
1621
1700
|
log.info("Done");
|
|
1622
1701
|
} catch (error) {
|
|
@@ -1757,17 +1836,21 @@ program.command("whoami").description("Show current authentication").action(asyn
|
|
|
1757
1836
|
reportCommandError("Whoami failed", error);
|
|
1758
1837
|
}
|
|
1759
1838
|
});
|
|
1760
|
-
program.command("new").description("Create a new blode.md documentation site").argument("[directory]", "target directory").option("--
|
|
1839
|
+
program.command("new").description("Create a new blode.md documentation site").argument("[directory]", "target directory").option("--slug <slug>", "project slug for docs.json", parseProjectSlug).option("--name <slug>", "deprecated alias for --slug", parseProjectSlug).option("--display-name <name>", "display name for docs.json").option("-t, --template <template>", `scaffold template (${SCAFFOLD_TEMPLATES.join(", ")})`, parseScaffoldTemplate, "minimal").option("-y, --yes", "accept defaults without prompting").action(async (directory, options) => {
|
|
1761
1840
|
await scaffoldDocsSite(directory, {
|
|
1841
|
+
displayName: options.displayName,
|
|
1762
1842
|
name: options.name,
|
|
1843
|
+
slug: options.slug ?? options.name,
|
|
1763
1844
|
template: options.template,
|
|
1764
1845
|
yes: options.yes
|
|
1765
1846
|
});
|
|
1766
1847
|
});
|
|
1767
|
-
program.command("init", { hidden: true }).argument("[directory]", "target directory").option("--
|
|
1848
|
+
program.command("init", { hidden: true }).argument("[directory]", "target directory").option("--slug <slug>", "project slug for docs.json", parseProjectSlug).option("--name <slug>", "deprecated alias for --slug", parseProjectSlug).option("--display-name <name>", "display name for docs.json").option("-t, --template <template>", `scaffold template (${SCAFFOLD_TEMPLATES.join(", ")})`, parseScaffoldTemplate, "minimal").option("-y, --yes", "accept defaults without prompting").action(async (directory, options) => {
|
|
1768
1849
|
await scaffoldDocsSite(directory, {
|
|
1769
1850
|
deprecatedCommand: "blodemd init",
|
|
1851
|
+
displayName: options.displayName,
|
|
1770
1852
|
name: options.name,
|
|
1853
|
+
slug: options.slug ?? options.name,
|
|
1771
1854
|
template: options.template,
|
|
1772
1855
|
yes: options.yes
|
|
1773
1856
|
});
|
|
@@ -1792,7 +1875,8 @@ program.command("push").description("Deploy docs").argument("[dir]", "docs direc
|
|
|
1792
1875
|
const { config, warnings } = await loadValidatedSiteConfig(root);
|
|
1793
1876
|
s.stop("Configuration valid");
|
|
1794
1877
|
for (const warning of warnings) log.warn(warning);
|
|
1795
|
-
const { project, apiUrl, authToken, branch, commitMessage } = await resolvePushConfig(config, options);
|
|
1878
|
+
const { project, projectDisplayName, apiUrl, authToken, branch, commitMessage, usedLegacyNameFallback } = await resolvePushConfig(config, options);
|
|
1879
|
+
if (usedLegacyNameFallback) log.warn(LEGACY_PROJECT_NAME_FALLBACK_WARNING);
|
|
1796
1880
|
s.start("Collecting files");
|
|
1797
1881
|
const files = await collectFiles(root);
|
|
1798
1882
|
if (files.length === 0) throw new Error("No files found to deploy.");
|
|
@@ -1817,7 +1901,7 @@ program.command("push").description("Deploy docs").argument("[dir]", "docs direc
|
|
|
1817
1901
|
} catch (error) {
|
|
1818
1902
|
if (!(error instanceof Error ? error.message : "").includes("404")) throw error;
|
|
1819
1903
|
s.stop("Project not found");
|
|
1820
|
-
if (!await autoCreateProject(project, apiUrl, headers)) {
|
|
1904
|
+
if (!await autoCreateProject(project, projectDisplayName, apiUrl, headers)) {
|
|
1821
1905
|
log.info("Cancelled");
|
|
1822
1906
|
return;
|
|
1823
1907
|
}
|