@baybreezy/docd 0.3.1 → 0.3.3

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.
@@ -46,7 +46,7 @@
46
46
  import { normalizeClass } from "vue";
47
47
  import type { HtmlHTMLAttributes } from "vue";
48
48
 
49
- import type { NuxtLinkProps } from "#app/components";
49
+ import type { NuxtLinkProps } from "#app";
50
50
 
51
51
  /** Exported button styles that can be used by other components. */
52
52
  export const buttonStyles = tv({
@@ -146,6 +146,12 @@
146
146
  loadingIcon: "line-md:loading-loop",
147
147
  iconPlacement: "left",
148
148
  loading: false,
149
+ noRel: undefined,
150
+ prefetch: undefined,
151
+ noPrefetch: undefined,
152
+ replace: undefined,
153
+ external: undefined,
154
+ custom: undefined,
149
155
  });
150
156
 
151
157
  const elementType = computed(() => {
@@ -2,13 +2,14 @@
2
2
  <NuxtLink
3
3
  data-slot="prose-a"
4
4
  v-bind="rest"
5
- :class="proseAStyles({ class: normalizeClass(_class) || undefined })"
5
+ :class="proseAStyles({ class: normalizeClass(props.class) || undefined })"
6
6
  >
7
7
  <slot />
8
8
  </NuxtLink>
9
9
  </template>
10
10
 
11
11
  <script lang="ts">
12
+ import { reactiveOmit } from "@vueuse/core";
12
13
  import { normalizeClass } from "vue";
13
14
  import type { HTMLAttributes } from "vue";
14
15
 
@@ -27,5 +28,20 @@
27
28
  </script>
28
29
 
29
30
  <script setup lang="ts">
30
- const { class: _class, ...rest } = defineProps<ProseAProps>();
31
+ const props = withDefaults(
32
+ defineProps<
33
+ ProseAProps & {
34
+ custom?: true | undefined;
35
+ }
36
+ >(),
37
+ {
38
+ noRel: undefined,
39
+ prefetch: undefined,
40
+ noPrefetch: undefined,
41
+ replace: undefined,
42
+ external: undefined,
43
+ custom: undefined,
44
+ }
45
+ );
46
+ const rest = reactiveOmit(props, "class");
31
47
  </script>
@@ -69,7 +69,14 @@
69
69
  </script>
70
70
 
71
71
  <script lang="ts" setup>
72
- const props = withDefaults(defineProps<ProseCardProps>(), {});
72
+ const props = withDefaults(defineProps<ProseCardProps>(), {
73
+ noRel: undefined,
74
+ prefetch: undefined,
75
+ noPrefetch: undefined,
76
+ replace: undefined,
77
+ external: undefined,
78
+ custom: undefined,
79
+ });
73
80
 
74
81
  defineSlots<{
75
82
  /**
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <MDC v-if="md" unwrap="p" :value="md" class="not-first:mt-5" />
2
+ <MDC
3
+ v-if="md"
4
+ :key="mdCacheKey"
5
+ :cache-key="mdCacheKey"
6
+ unwrap="p"
7
+ :value="md"
8
+ class="not-first:mt-5"
9
+ />
3
10
  </template>
4
11
 
5
12
  <script setup lang="ts">
@@ -39,6 +46,7 @@
39
46
  );
40
47
 
41
48
  const { packageManagers } = usePm();
49
+ const route = useRoute();
42
50
 
43
51
  const md = computed(() => {
44
52
  const codeBlocks = packageManagers
@@ -57,4 +65,6 @@
57
65
 
58
66
  return `::prose-code-group${attributes ? `{${attributes}}` : ""}\n${codeBlocks}\n::`;
59
67
  });
68
+
69
+ const mdCacheKey = computed(() => `prose-pm-install:${route.path}:${props.name ?? ""}`);
60
70
  </script>
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <MDC v-if="md" unwrap="p" :value="md" class="not-first:mt-5" />
2
+ <MDC
3
+ v-if="md"
4
+ :key="mdCacheKey"
5
+ :cache-key="mdCacheKey"
6
+ unwrap="p"
7
+ :value="md"
8
+ class="not-first:mt-5"
9
+ />
3
10
  </template>
4
11
 
5
12
  <script setup lang="ts">
@@ -33,6 +40,7 @@
33
40
  );
34
41
 
35
42
  const { packageManagers } = usePm();
43
+ const route = useRoute();
36
44
 
37
45
  const md = computed(() => {
38
46
  const codeBlocks = packageManagers
@@ -48,4 +56,6 @@
48
56
 
49
57
  return `::prose-code-group${attributes ? `{${attributes}}` : ""}\n${codeBlocks}\n::`;
50
58
  });
59
+
60
+ const mdCacheKey = computed(() => `prose-pm-run:${route.path}:${props.script}`);
51
61
  </script>
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <MDC unwrap="p" :value="md" class="not-first:mt-5" v-if="md" />
2
+ <MDC
3
+ v-if="md"
4
+ :key="mdCacheKey"
5
+ :cache-key="mdCacheKey"
6
+ unwrap="p"
7
+ :value="md"
8
+ class="not-first:mt-5"
9
+ />
3
10
  </template>
4
11
 
5
12
  <script setup lang="ts">
@@ -33,6 +40,7 @@
33
40
  );
34
41
 
35
42
  const { packageManagers } = usePm();
43
+ const route = useRoute();
36
44
 
37
45
  const md = computed(() => {
38
46
  const codeBlocks = packageManagers
@@ -53,4 +61,6 @@
53
61
 
54
62
  return `::prose-code-group${attributes ? `{${attributes}}` : ""}\n${codeBlocks}\n::`;
55
63
  });
64
+
65
+ const mdCacheKey = computed(() => `prose-pm-x:${route.path}:${props.command}`);
56
66
  </script>
package/modules/config.ts CHANGED
@@ -30,6 +30,7 @@ export default defineNuxtModule({
30
30
  "vaul-vue",
31
31
  "@vue/devtools-core",
32
32
  "@vue/devtools-kit",
33
+ "nuxt-studio > slugify",
33
34
  ],
34
35
  });
35
36
 
@@ -15,6 +15,7 @@ import {
15
15
  readGeneratedProseComponentMeta,
16
16
  resolveProseComponentPaths,
17
17
  } from "../utils/generate-prose-component-meta";
18
+ import { rewritePackageManagerMarkdown } from "../utils/package-manager-markdown";
18
19
 
19
20
  const log = logger.withTag("Docd");
20
21
 
@@ -74,6 +75,12 @@ export default defineNuxtModule({
74
75
 
75
76
  await regenerate();
76
77
 
78
+ nuxt.hook("content:file:beforeParse", (event) => {
79
+ if (typeof event.file.body === "string" && event.file.extension === ".md") {
80
+ event.file.body = rewritePackageManagerMarkdown(event.file.body);
81
+ }
82
+ });
83
+
77
84
  nuxt.hook("content:file:afterParse", async (event) => {
78
85
  const context = {
79
86
  rootDir: nuxt.options.rootDir,
package/nuxt.config.ts CHANGED
@@ -67,6 +67,7 @@ export default defineNuxtConfig({
67
67
  markdown: {
68
68
  toc: { depth: 4, searchDepth: 4 },
69
69
  highlight: { langs },
70
+ __docdPackageManagerTransform: 2,
70
71
  },
71
72
  },
72
73
  experimental: { sqliteConnector: "native" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baybreezy/docd",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "A Nuxt layer for building documentation sites with UI Thing.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,36 +33,36 @@
33
33
  "@baybreezy/file-extension-icon": "^0.0.5",
34
34
  "@iconify/utils": "^3.1.4",
35
35
  "@morev/vue-transitions": "^3.0.5",
36
- "@nuxt/content": "3.15.0",
36
+ "@nuxt/content": "3.15.2",
37
37
  "@nuxt/fonts": "^0.14.0",
38
- "@nuxt/icon": "^2.3.1",
39
- "@nuxt/image": "2.0.0",
40
- "@nuxt/kit": "^4.5.0",
38
+ "@nuxt/icon": "^2.5.1",
39
+ "@nuxt/image": "2.1.0",
40
+ "@nuxt/kit": "^4.5.2",
41
41
  "@nuxtjs/color-mode": "^4.0.1",
42
- "@nuxtjs/mcp-toolkit": "^0.18.0",
43
- "@nuxtjs/mdc": "^0.22.2",
44
- "@nuxtjs/robots": "^6.1.2",
42
+ "@nuxtjs/mcp-toolkit": "^0.19.0",
43
+ "@nuxtjs/mdc": "^0.23.1",
44
+ "@nuxtjs/robots": "^6.2.0",
45
45
  "@tailwindcss/forms": "^0.5.11",
46
46
  "@tailwindcss/typography": "^0.5.20",
47
47
  "@tailwindcss/vite": "^4.3.3",
48
- "@takumi-rs/core": "^2.3.0",
49
- "@vueuse/core": "^14.3.0",
50
- "@vueuse/nuxt": "^14.3.0",
51
- "better-sqlite3": "^12.11.1",
48
+ "@takumi-rs/core": "^2.12.0",
49
+ "@vueuse/core": "^14.4.0",
50
+ "@vueuse/nuxt": "^14.4.0",
51
+ "better-sqlite3": "^13.0.3",
52
52
  "defu": "^6.1.7",
53
53
  "git-url-parse": "^16.1.0",
54
54
  "lodash-es": "^4.18.1",
55
- "mermaid": "^11.16.0",
56
- "motion-v": "^2.3.0",
55
+ "mermaid": "^11.17.1",
56
+ "motion-v": "^2.4.0",
57
57
  "nuxt-component-meta": "^0.18.0",
58
- "nuxt-gtag": "4.1.0",
58
+ "nuxt-gtag": "5.0.0",
59
59
  "nuxt-llms": "^0.2.0",
60
- "nuxt-og-image": "^6.7.2",
60
+ "nuxt-og-image": "^6.7.8",
61
61
  "pkg-types": "^2.3.1",
62
- "reka-ui": "^2.10.1",
63
- "shiki": "^4.3.1",
62
+ "reka-ui": "^2.10.3",
63
+ "shiki": "^4.4.3",
64
64
  "tailwind-merge": "^3.6.0",
65
- "tailwind-variants": "^3.2.2",
65
+ "tailwind-variants": "^3.3.1",
66
66
  "tailwindcss": "^4.3.3",
67
67
  "tw-animate-css": "^1.4.0",
68
68
  "ufo": "^1.6.4",
@@ -73,12 +73,12 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@types/lodash-es": "^4.17.12",
76
- "nuxt": "^4.5.0",
77
- "typescript": "^6.0.3",
76
+ "nuxt": "^4.5.2",
77
+ "typescript": "6.0.3",
78
78
  "vue": "latest",
79
79
  "vue-router": "^5.2.0"
80
80
  },
81
- "packageManager": "bun@1.3.14",
81
+ "packageManager": "bun@1.4.0",
82
82
  "trustedDependencies": [
83
83
  "@morev/vue-transitions",
84
84
  "@parcel/watcher",
@@ -0,0 +1,226 @@
1
+ type PackageManagerTag = "prose-pm-install" | "prose-pm-run" | "prose-pm-x";
2
+
3
+ type PackageManagerProps = Record<string, string | boolean>;
4
+
5
+ const PACKAGE_MANAGERS = [
6
+ {
7
+ name: "npm",
8
+ command: "npm ",
9
+ install: "i ",
10
+ installEmpty: "install",
11
+ run: "run ",
12
+ x: "npx ",
13
+ saveDev: "-D ",
14
+ icon: "material-icon-theme:npm",
15
+ },
16
+ {
17
+ name: "pnpm",
18
+ command: "pnpm ",
19
+ install: "i ",
20
+ installEmpty: "install",
21
+ run: "run ",
22
+ x: "pnpm dlx ",
23
+ saveDev: "-D ",
24
+ icon: "material-icon-theme:pnpm",
25
+ },
26
+ {
27
+ name: "bun",
28
+ command: "bun ",
29
+ install: "add ",
30
+ installEmpty: "install",
31
+ run: "run ",
32
+ x: "bun x ",
33
+ saveDev: "-d ",
34
+ icon: "material-icon-theme:bun",
35
+ },
36
+ {
37
+ name: "yarn",
38
+ command: "yarn ",
39
+ install: "add ",
40
+ installEmpty: "install",
41
+ run: "run ",
42
+ x: "yarn dlx ",
43
+ saveDev: "-D ",
44
+ icon: "material-icon-theme:yarn",
45
+ },
46
+ ] as const;
47
+
48
+ const PACKAGE_MANAGER_LINE_RE = /^(\s*)(:{1,})(prose-pm-(?:install|run|x))(?:\{([^}]*)\})?\s*$/;
49
+ const FENCE_RE = /^\s*(`{3,}|~{3,})/;
50
+ const ATTRIBUTE_RE = /(?::?[\w-]+)(?:=(?:"[^"]*"|'[^']*'|[^\s}]+))?/g;
51
+
52
+ function escapeMdcAttribute(value: string) {
53
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
54
+ }
55
+
56
+ function normalizePropName(name: string) {
57
+ return name.replace(/^:/, "").replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());
58
+ }
59
+
60
+ function parseProps(source = "") {
61
+ const props: PackageManagerProps = {};
62
+ const attributes = source.match(ATTRIBUTE_RE) || [];
63
+
64
+ for (const attribute of attributes) {
65
+ const [rawName, ...rawValueParts] = attribute.split("=");
66
+ const name = normalizePropName(rawName);
67
+ const rawValue = rawValueParts.join("=");
68
+
69
+ if (!rawValue) {
70
+ props[name] = true;
71
+ continue;
72
+ }
73
+
74
+ props[name] = rawValue.replace(/^["']|["']$/g, "");
75
+ }
76
+
77
+ return props;
78
+ }
79
+
80
+ function stringProp(props: PackageManagerProps, name: string) {
81
+ const value = props[name];
82
+ return typeof value === "string" ? value : undefined;
83
+ }
84
+
85
+ function booleanProp(props: PackageManagerProps, name: string) {
86
+ const value = props[name];
87
+
88
+ if (typeof value === "boolean") {
89
+ return value;
90
+ }
91
+
92
+ return value === "" || value === "true";
93
+ }
94
+
95
+ function buildCodeFence(label: string, icon: string, command: string) {
96
+ return `\`\`\`bash icon="${icon}" noFormat hideHeader [${label}]\n${command}\n\`\`\``;
97
+ }
98
+
99
+ function buildCodeGroupSource(tag: PackageManagerTag, props: PackageManagerProps) {
100
+ const inStack = booleanProp(props, "inStack");
101
+ const noSync = booleanProp(props, "noSync");
102
+ const sync = stringProp(props, "sync") || "_pm";
103
+
104
+ const codeBlocks = PACKAGE_MANAGERS.map((pm) => {
105
+ if (tag === "prose-pm-install") {
106
+ const name = stringProp(props, "name");
107
+ const saveDev = booleanProp(props, "saveDev");
108
+ const command = name
109
+ ? `${pm.command}${pm.install}${saveDev ? pm.saveDev : ""}${name}`
110
+ : `${pm.command}${pm.installEmpty}`;
111
+
112
+ return buildCodeFence(pm.name, pm.icon, command);
113
+ }
114
+
115
+ if (tag === "prose-pm-run") {
116
+ const script = stringProp(props, "script");
117
+
118
+ if (!script) {
119
+ return undefined;
120
+ }
121
+
122
+ return buildCodeFence(pm.name, pm.icon, `${pm.command}${pm.run}${script}`);
123
+ }
124
+
125
+ const command = stringProp(props, "command");
126
+
127
+ if (!command) {
128
+ return undefined;
129
+ }
130
+
131
+ return buildCodeFence(pm.name, pm.icon, `${pm.x}${command}`);
132
+ }).filter((block): block is string => Boolean(block));
133
+
134
+ if (!codeBlocks.length) {
135
+ return;
136
+ }
137
+
138
+ const attributes = [
139
+ inStack && "in-stack",
140
+ !noSync && `sync="${escapeMdcAttribute(sync)}"`,
141
+ tag === "prose-pm-x" && "noFormat",
142
+ tag === "prose-pm-x" && "hideHeaderMeta",
143
+ ]
144
+ .filter(Boolean)
145
+ .join(" ");
146
+
147
+ return `::prose-code-group${attributes ? `{${attributes}}` : ""}\n${codeBlocks.join("\n\n")}\n::`;
148
+ }
149
+
150
+ function indentBlock(block: string, indent: string) {
151
+ return block
152
+ .split("\n")
153
+ .map((line) => (line ? `${indent}${line}` : line))
154
+ .join("\n");
155
+ }
156
+
157
+ function nextNonEmptyLineIndex(lines: string[], start: number) {
158
+ for (let index = start; index < lines.length; index += 1) {
159
+ if (lines[index].trim()) {
160
+ return index;
161
+ }
162
+ }
163
+ }
164
+
165
+ export function rewritePackageManagerMarkdown(markdown: string) {
166
+ const lines = markdown.split("\n");
167
+ const output: string[] = [];
168
+ const skipLines = new Set<number>();
169
+ let activeFence: string | undefined;
170
+ let changed = false;
171
+
172
+ for (let index = 0; index < lines.length; index += 1) {
173
+ const line = lines[index];
174
+
175
+ if (skipLines.has(index)) {
176
+ changed = true;
177
+ continue;
178
+ }
179
+
180
+ const fence = line.match(FENCE_RE)?.[1];
181
+ if (fence && (!activeFence || fence[0] === activeFence[0])) {
182
+ activeFence = activeFence ? undefined : fence;
183
+ output.push(line);
184
+ continue;
185
+ }
186
+
187
+ if (activeFence) {
188
+ output.push(line);
189
+ continue;
190
+ }
191
+
192
+ const match = line.match(PACKAGE_MANAGER_LINE_RE);
193
+
194
+ if (!match) {
195
+ output.push(line);
196
+ continue;
197
+ }
198
+
199
+ const [, indent, marker, tag, propsSource] = match as [
200
+ string,
201
+ string,
202
+ string,
203
+ PackageManagerTag,
204
+ string | undefined,
205
+ ];
206
+ const source = buildCodeGroupSource(tag, parseProps(propsSource));
207
+
208
+ if (!source) {
209
+ output.push(line);
210
+ continue;
211
+ }
212
+
213
+ output.push(indentBlock(source, indent));
214
+ changed = true;
215
+
216
+ if (marker.length > 1) {
217
+ const closeIndex = nextNonEmptyLineIndex(lines, index + 1);
218
+
219
+ if (closeIndex !== undefined && lines[closeIndex].trim() === marker) {
220
+ skipLines.add(closeIndex);
221
+ }
222
+ }
223
+ }
224
+
225
+ return changed ? output.join("\n") : markdown;
226
+ }