@numbered/docs-to-context 0.1.5 → 0.3.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.
- package/README.md +29 -12
- package/docs/liquid/README.mdx +59 -0
- package/docs/liquid/a11y-alt-text.mdx +16 -0
- package/docs/liquid/a11y-semantic-html.mdx +19 -0
- package/docs/liquid/alpine-cleanup.mdx +18 -0
- package/docs/liquid/alpine-debounce.mdx +12 -0
- package/docs/liquid/alpine-defer-heavy.mdx +13 -0
- package/docs/liquid/alpine-separate-scopes.mdx +19 -0
- package/docs/liquid/alpine-static-data.mdx +16 -0
- package/docs/liquid/css-critical-inline.mdx +16 -0
- package/docs/liquid/css-prefer-tailwind.mdx +30 -0
- package/docs/liquid/liquid-assign-over-capture.mdx +17 -0
- package/docs/liquid/liquid-avoid-nested-loops.mdx +21 -0
- package/docs/liquid/liquid-break-continue.mdx +23 -0
- package/docs/liquid/liquid-cache-assigns.mdx +16 -0
- package/docs/liquid/liquid-elsif-chain.mdx +25 -0
- package/docs/liquid/liquid-filter-early.mdx +18 -0
- package/docs/liquid/liquid-limit-loops.mdx +17 -0
- package/docs/liquid/liquid-map-join.mdx +16 -0
- package/docs/liquid/liquid-render-over-include.mdx +13 -0
- package/docs/liquid/liquid-snippet-data.mdx +17 -0
- package/docs/liquid/liquid-whitespace-control.mdx +17 -0
- package/docs/liquid/loading-defer-scripts.mdx +13 -0
- package/docs/liquid/loading-lazy-images.mdx +31 -0
- package/docs/liquid/loading-preload-critical.mdx +24 -0
- package/docs/liquid/loading-responsive-images.mdx +23 -0
- package/docs/liquid/schema-blocks-over-settings.mdx +35 -0
- package/docs/liquid/schema-section-settings.mdx +36 -0
- package/docs/react/README.mdx +99 -0
- package/docs/react/advanced-handler-refs.mdx +15 -0
- package/docs/react/advanced-use-latest.mdx +21 -0
- package/docs/react/async-defer-await.mdx +19 -0
- package/docs/react/async-promise-all.mdx +17 -0
- package/docs/react/async-start-early.mdx +25 -0
- package/docs/react/async-suspense-boundaries.mdx +35 -0
- package/docs/react/bundle-barrel-imports.mdx +25 -0
- package/docs/react/bundle-conditional-loading.mdx +21 -0
- package/docs/react/bundle-defer-third-party.mdx +15 -0
- package/docs/react/bundle-dynamic-imports.mdx +15 -0
- package/docs/react/bundle-preload-intent.mdx +24 -0
- package/docs/react/client-event-listeners.mdx +45 -0
- package/docs/react/client-swr-dedup.mdx +40 -0
- package/docs/react/effect-derive-state.mdx +12 -0
- package/docs/react/effect-event-handlers.mdx +16 -0
- package/docs/react/effect-key-reset.mdx +11 -0
- package/docs/react/effect-no-chains.mdx +22 -0
- package/docs/react/effect-notify-parents.mdx +18 -0
- package/docs/react/effect-use-memo.mdx +17 -0
- package/docs/react/js-batch-css.mdx +23 -0
- package/docs/react/js-cache-property.mdx +17 -0
- package/docs/react/js-cache-storage.mdx +29 -0
- package/docs/react/js-combine-iterations.mdx +20 -0
- package/docs/react/js-early-return.mdx +24 -0
- package/docs/react/js-hoist-regexp.mdx +21 -0
- package/docs/react/js-index-maps.mdx +14 -0
- package/docs/react/js-length-check.mdx +12 -0
- package/docs/react/js-loop-min-max.mdx +14 -0
- package/docs/react/js-set-lookups.mdx +12 -0
- package/docs/react/js-tosorted.mdx +15 -0
- package/docs/react/render-activity.mdx +17 -0
- package/docs/react/render-conditional.mdx +11 -0
- package/docs/react/render-content-visibility.mdx +12 -0
- package/docs/react/render-cx-clsx.mdx +12 -0
- package/docs/react/render-hoist-jsx.mdx +16 -0
- package/docs/react/render-hydration-flicker.mdx +25 -0
- package/docs/react/render-svg-precision.mdx +17 -0
- package/docs/react/render-svg-wrapper.mdx +19 -0
- package/docs/react/rerender-defer-reads.mdx +24 -0
- package/docs/react/rerender-derived-state.mdx +18 -0
- package/docs/react/rerender-inline-objects.mdx +18 -0
- package/docs/react/rerender-isolate-state.mdx +36 -0
- package/docs/react/rerender-lazy-init.mdx +19 -0
- package/docs/react/rerender-memo-extract.mdx +26 -0
- package/docs/react/rerender-narrow-deps.mdx +26 -0
- package/docs/react/rerender-transitions.mdx +29 -0
- package/docs/react/rerender-use-client-down.mdx +34 -0
- package/docs/react/server-lru-cache.mdx +24 -0
- package/docs/react/server-parallel-fetching.mdx +24 -0
- package/docs/react/server-react-cache.mdx +16 -0
- package/docs/react/server-rsc-serialization.mdx +17 -0
- package/package.json +2 -1
- package/scripts/extract.ts +2 -2
- package/scripts/generate.ts +9 -2
- package/scripts/inject.ts +86 -23
package/scripts/extract.ts
CHANGED
|
@@ -199,7 +199,7 @@ function generateNextjsMdx(comp: NextjsComponent): string {
|
|
|
199
199
|
|
|
200
200
|
function scanNextjs(projectRoot: string, dirs?: string[], outputDir?: string): NextjsComponent[] {
|
|
201
201
|
const root = resolve(projectRoot)
|
|
202
|
-
const outDir = outputDir ?? join(root, '
|
|
202
|
+
const outDir = outputDir ?? join(root, '.context', 'components')
|
|
203
203
|
const scanDirs = dirs ?? ['packages/ui/components']
|
|
204
204
|
|
|
205
205
|
mkdirSync(outDir, { recursive: true })
|
|
@@ -340,7 +340,7 @@ function generateShopifyMdx(snippet: ShopifySnippet): string {
|
|
|
340
340
|
|
|
341
341
|
function scanShopify(projectRoot: string, outputDir?: string): ShopifySnippet[] {
|
|
342
342
|
const root = resolve(projectRoot)
|
|
343
|
-
const outDir = outputDir ?? join(root, '
|
|
343
|
+
const outDir = outputDir ?? join(root, '.context', 'components')
|
|
344
344
|
const snippetsDir = join(root, 'snippets')
|
|
345
345
|
|
|
346
346
|
if (!isDir(snippetsDir)) {
|
package/scripts/generate.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { resolve } from 'node:path'
|
|
14
|
-
import { extract } from './extract'
|
|
14
|
+
import { detectPlatform, extract } from './extract'
|
|
15
15
|
import { inject } from './inject'
|
|
16
16
|
import { log } from './log'
|
|
17
17
|
|
|
@@ -42,6 +42,13 @@ for (let i = 0; i < args.length; i++) {
|
|
|
42
42
|
|
|
43
43
|
projectRoot = resolve(projectRoot)
|
|
44
44
|
|
|
45
|
+
// ── Detect platform early ──────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
if (!platform) {
|
|
48
|
+
const detected = detectPlatform(projectRoot)
|
|
49
|
+
if (detected) platform = detected
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
// ── Run ────────────────────────────────────────────────────────────────
|
|
46
53
|
|
|
47
54
|
log.banner()
|
|
@@ -50,6 +57,6 @@ log.section('Extract')
|
|
|
50
57
|
extract({ projectRoot, platform, dirs, outputDir })
|
|
51
58
|
|
|
52
59
|
log.section('Inject')
|
|
53
|
-
inject({ projectRoot, docsDir: outputDir })
|
|
60
|
+
inject({ projectRoot, docsDir: outputDir, platform })
|
|
54
61
|
|
|
55
62
|
log.done()
|
package/scripts/inject.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inject project docs INDEX into CLAUDE.md between marker comments.
|
|
3
3
|
*
|
|
4
|
-
* Reads the INDEX file from
|
|
4
|
+
* Reads the INDEX file from .context/components/ and injects a compact reference
|
|
5
5
|
* between <!-- PROJECT_DOCS_START --> and <!-- PROJECT_DOCS_END --> markers.
|
|
6
|
-
* Also discovers core docs (design system, grid, architecture) and
|
|
6
|
+
* Also discovers core docs (design system, grid, architecture) and copies
|
|
7
|
+
* bundled best-practice docs for the detected platform.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
10
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
10
11
|
import { join, relative, resolve } from 'node:path'
|
|
11
12
|
import { isDir, isFile, walkDir } from './fs'
|
|
12
13
|
import { log } from './log'
|
|
@@ -21,6 +22,7 @@ const MARKER_PATTERN = new RegExp(
|
|
|
21
22
|
+ MARKER_END.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'),
|
|
22
23
|
)
|
|
23
24
|
const CORE_DOCS_ROOT = 'docs'
|
|
25
|
+
const CONTEXT_ROOT = '.context'
|
|
24
26
|
const REGENERATE_CMD = 'bunx @numbered/docs-to-context'
|
|
25
27
|
|
|
26
28
|
// ── Types ──────────────────────────────────────────────────────────────
|
|
@@ -28,6 +30,7 @@ const REGENERATE_CMD = 'bunx @numbered/docs-to-context'
|
|
|
28
30
|
interface CoreDocs {
|
|
29
31
|
frontend: string[]
|
|
30
32
|
entities: string[]
|
|
33
|
+
bestPractices: string[]
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
@@ -45,11 +48,57 @@ function groupByDir(paths: string[]): [string, string[]][] {
|
|
|
45
48
|
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b))
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
// ── Platform → doc folder mapping ─────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
const PLATFORM_DOC_FOLDER: Record<string, string> = {
|
|
54
|
+
nextjs: 'react',
|
|
55
|
+
shopify: 'liquid',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Best practices copy ───────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
function copyBestPractices(projectRoot: string, platform: string): string[] {
|
|
61
|
+
const folder = PLATFORM_DOC_FOLDER[platform]
|
|
62
|
+
if (!folder) return []
|
|
63
|
+
|
|
64
|
+
const bundledDir = resolve(import.meta.dir, '..', 'docs', folder)
|
|
65
|
+
if (!isDir(bundledDir)) return []
|
|
66
|
+
|
|
67
|
+
const destDir = join(projectRoot, CONTEXT_ROOT, 'best-practices')
|
|
68
|
+
mkdirSync(destDir, { recursive: true })
|
|
69
|
+
|
|
70
|
+
const files = walkDir(bundledDir, ['.md', '.mdx'])
|
|
71
|
+
const copied: string[] = []
|
|
72
|
+
|
|
73
|
+
// Ensure README is first so the index is always listed before patterns
|
|
74
|
+
files.sort((a, b) => {
|
|
75
|
+
const aName = a.slice(a.lastIndexOf('/') + 1)
|
|
76
|
+
const bName = b.slice(b.lastIndexOf('/') + 1)
|
|
77
|
+
const aReadme = aName.toLowerCase().startsWith('readme')
|
|
78
|
+
const bReadme = bName.toLowerCase().startsWith('readme')
|
|
79
|
+
if (aReadme && !bReadme) return -1
|
|
80
|
+
if (!aReadme && bReadme) return 1
|
|
81
|
+
return aName.localeCompare(bName)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
for (const src of files) {
|
|
85
|
+
const name = src.slice(src.lastIndexOf('/') + 1)
|
|
86
|
+
copyFileSync(src, join(destDir, name))
|
|
87
|
+
copied.push(`best-practices/${name}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (copied.length) {
|
|
91
|
+
log.success(`Copied ${copied.length} best practice doc(s)`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return copied
|
|
95
|
+
}
|
|
96
|
+
|
|
48
97
|
// ── Core docs discovery ────────────────────────────────────────────────
|
|
49
98
|
|
|
50
|
-
function discoverCoreDocs(projectRoot: string): CoreDocs {
|
|
99
|
+
function discoverCoreDocs(projectRoot: string, bestPractices: string[]): CoreDocs {
|
|
51
100
|
const docsDir = join(projectRoot, CORE_DOCS_ROOT)
|
|
52
|
-
const result: CoreDocs = { frontend: [], entities: [] }
|
|
101
|
+
const result: CoreDocs = { frontend: [], entities: [], bestPractices }
|
|
53
102
|
|
|
54
103
|
if (!isDir(docsDir)) return result
|
|
55
104
|
|
|
@@ -69,16 +118,17 @@ function discoverCoreDocs(projectRoot: string): CoreDocs {
|
|
|
69
118
|
|
|
70
119
|
// ── Block builder ──────────────────────────────────────────────────────
|
|
71
120
|
|
|
72
|
-
function buildComponentLines(
|
|
121
|
+
function buildComponentLines(names: string[]): string[] {
|
|
122
|
+
const root = `./${CONTEXT_ROOT}/components`
|
|
73
123
|
return [
|
|
74
|
-
`[Component Index]|root:
|
|
124
|
+
`[Component Index]|root: ${root}`,
|
|
75
125
|
'|IMPORTANT: Read component MDX before using any component',
|
|
76
126
|
`|components:{${names.join(',')}}`,
|
|
77
|
-
`|If
|
|
127
|
+
`|If ${root} is missing, run: ${REGENERATE_CMD}`,
|
|
78
128
|
]
|
|
79
129
|
}
|
|
80
130
|
|
|
81
|
-
function buildIndexBlock(indexPath: string,
|
|
131
|
+
function buildIndexBlock(indexPath: string, coreDocs: CoreDocs | null): string | null {
|
|
82
132
|
const indexContent = readFileSync(indexPath, 'utf-8').trim()
|
|
83
133
|
|
|
84
134
|
const names: string[] = []
|
|
@@ -91,20 +141,29 @@ function buildIndexBlock(indexPath: string, docsDirRel: string, coreDocs: CoreDo
|
|
|
91
141
|
if (names.length === 0) return null
|
|
92
142
|
|
|
93
143
|
const lines: string[] = [MARKER_START, '## Project Docs']
|
|
94
|
-
const
|
|
144
|
+
const docsRoot = `./${CORE_DOCS_ROOT}`
|
|
95
145
|
|
|
96
146
|
// Frontend
|
|
97
147
|
if (coreDocs?.frontend.length) {
|
|
98
|
-
lines.push(`|[Frontend]|root: ${
|
|
148
|
+
lines.push(`|[Frontend]|root: ${docsRoot}`)
|
|
99
149
|
lines.push(`|frontend:{${coreDocs.frontend.join(',')}}`)
|
|
100
150
|
}
|
|
101
151
|
|
|
152
|
+
// Best Practices
|
|
153
|
+
if (coreDocs?.bestPractices.length) {
|
|
154
|
+
const contextRoot = `./${CONTEXT_ROOT}`
|
|
155
|
+
const fileNames = coreDocs.bestPractices.map((p) => p.slice(p.lastIndexOf('/') + 1))
|
|
156
|
+
lines.push(`|[Best Practices]|root: ${contextRoot}`)
|
|
157
|
+
lines.push(`|best-practices:{${fileNames.join(',')}}`)
|
|
158
|
+
lines.push('|IMPORTANT: Read best-practices/README.mdx first — it indexes all patterns by category and impact')
|
|
159
|
+
}
|
|
160
|
+
|
|
102
161
|
// Components (always present)
|
|
103
|
-
lines.push(...buildComponentLines(
|
|
162
|
+
lines.push(...buildComponentLines(names))
|
|
104
163
|
|
|
105
164
|
// Entities (grouped by directory)
|
|
106
165
|
if (coreDocs?.entities.length) {
|
|
107
|
-
lines.push(`|[Entities]|root: ${
|
|
166
|
+
lines.push(`|[Entities]|root: ${docsRoot}`)
|
|
108
167
|
for (const [dirPath, files] of groupByDir(coreDocs.entities)) {
|
|
109
168
|
lines.push(`|${dirPath}:{${files.join(',')}}`)
|
|
110
169
|
}
|
|
@@ -140,18 +199,18 @@ function injectIntoClaudeMd(claudeMdPath: string, block: string) {
|
|
|
140
199
|
|
|
141
200
|
// ── .gitignore ─────────────────────────────────────────────────────────
|
|
142
201
|
|
|
143
|
-
function ensureGitignore(projectRoot: string
|
|
202
|
+
function ensureGitignore(projectRoot: string) {
|
|
144
203
|
const gitignorePath = join(projectRoot, '.gitignore')
|
|
145
|
-
const entry =
|
|
204
|
+
const entry = `${CONTEXT_ROOT}/`
|
|
146
205
|
|
|
147
206
|
if (existsSync(gitignorePath)) {
|
|
148
207
|
const content = readFileSync(gitignorePath, 'utf-8')
|
|
149
|
-
if (content.includes(entry) || content.includes(
|
|
208
|
+
if (content.includes(entry) || content.includes(CONTEXT_ROOT)) return
|
|
150
209
|
|
|
151
210
|
const padded = content.endsWith('\n') ? content : content + '\n'
|
|
152
|
-
writeFileSync(gitignorePath, padded + `\n# Generated
|
|
211
|
+
writeFileSync(gitignorePath, padded + `\n# Generated project context\n${entry}\n`, 'utf-8')
|
|
153
212
|
} else {
|
|
154
|
-
writeFileSync(gitignorePath, `# Generated
|
|
213
|
+
writeFileSync(gitignorePath, `# Generated project context\n${entry}\n`, 'utf-8')
|
|
155
214
|
}
|
|
156
215
|
|
|
157
216
|
log.info(`Added ${entry} to .gitignore`)
|
|
@@ -164,11 +223,12 @@ export interface InjectOptions {
|
|
|
164
223
|
indexPath?: string
|
|
165
224
|
claudeMdPath?: string
|
|
166
225
|
docsDir?: string
|
|
226
|
+
platform?: 'nextjs' | 'shopify'
|
|
167
227
|
}
|
|
168
228
|
|
|
169
229
|
export function inject(opts: InjectOptions) {
|
|
170
230
|
const root = resolve(opts.projectRoot)
|
|
171
|
-
const docsDir = opts.docsDir ??
|
|
231
|
+
const docsDir = opts.docsDir ?? `${CONTEXT_ROOT}/components`
|
|
172
232
|
const indexPath = opts.indexPath ?? join(root, docsDir, 'INDEX')
|
|
173
233
|
const claudeMdPath = opts.claudeMdPath ?? join(root, 'CLAUDE.md')
|
|
174
234
|
|
|
@@ -178,16 +238,19 @@ export function inject(opts: InjectOptions) {
|
|
|
178
238
|
process.exit(1)
|
|
179
239
|
}
|
|
180
240
|
|
|
181
|
-
|
|
182
|
-
const
|
|
241
|
+
// Copy best practices if platform is known
|
|
242
|
+
const bestPractices = opts.platform ? copyBestPractices(root, opts.platform) : []
|
|
243
|
+
|
|
244
|
+
const coreDocs = discoverCoreDocs(root, bestPractices)
|
|
245
|
+
const total = coreDocs.frontend.length + coreDocs.entities.length + coreDocs.bestPractices.length
|
|
183
246
|
if (total) log.info(`Found ${total} core doc(s)`)
|
|
184
247
|
|
|
185
|
-
const block = buildIndexBlock(indexPath,
|
|
248
|
+
const block = buildIndexBlock(indexPath, total ? coreDocs : null)
|
|
186
249
|
if (!block) {
|
|
187
250
|
log.error('INDEX file is empty, nothing to inject.')
|
|
188
251
|
process.exit(1)
|
|
189
252
|
}
|
|
190
253
|
|
|
191
254
|
injectIntoClaudeMd(claudeMdPath, block)
|
|
192
|
-
ensureGitignore(root
|
|
255
|
+
ensureGitignore(root)
|
|
193
256
|
}
|