Skip to content

Commit 370fdf3

Browse files
authored
fix: exp post build script to strip Table_Internal (TanStack#6326)
* fix: post build script to strip Table_Internal * harden script
1 parent 86ff246 commit 370fdf3

4 files changed

Lines changed: 197 additions & 4 deletions

File tree

packages/table-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"test:lib:dev": "pnpm test:lib --watch",
7171
"test:types": "tsc && tsc -p tests/tsconfig.declaration-emit.json",
7272
"test:build": "publint --strict",
73-
"build": "tsdown"
73+
"build": "tsdown && node ../../scripts/rewrite-table-core-dts.mjs"
7474
},
7575
"dependencies": {
7676
"@tanstack/store": "^0.11.0"

packages/table-core/src/core/headers/coreHeadersFeature.types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { CellData, RowData } from '../../types/type-utils'
22
import type { TableFeatures } from '../../types/TableFeatures'
3-
import type { Table } from '../../types/Table'
3+
import type { Table, Table_Internal } from '../../types/Table'
44
import type { Header } from '../../types/Header'
55
import type { HeaderGroup } from '../../types/HeaderGroup'
66
import type { Column } from '../../types/Column'
@@ -96,7 +96,7 @@ export interface Header_CoreProperties<
9696
/**
9797
* Reference to the parent table instance.
9898
*/
99-
table: Table<TFeatures, TData>
99+
table: Table_Internal<TFeatures, TData>
100100
}
101101

102102
export interface Header_Header<

packages/table-core/src/types/Column.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Table_Internal } from './Table'
12
import type { Column_RowSorting } from '../features/row-sorting/rowSortingFeature.types'
23
import type { Column_ColumnFaceting } from '../features/column-faceting/columnFacetingFeature.types'
34
import type { Column_ColumnFiltering } from '../features/column-filtering/columnFilteringFeature.types'
@@ -46,6 +47,7 @@ export interface Column_Internal<
4647
in out TFeatures extends TableFeatures,
4748
in out TData extends RowData,
4849
TValue = unknown,
49-
> extends Omit<Column_Core<TFeatures, TData, TValue>, 'columnDef'> {
50+
> extends Omit<Column_Core<TFeatures, TData, TValue>, 'columnDef' | 'table'> {
5051
columnDef: ColumnDefBase_All<TFeatures, TData, TValue>
52+
table: Table_Internal<TFeatures, TData>
5153
}

scripts/rewrite-table-core-dts.mjs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
import { fileURLToPath } from 'node:url'
4+
5+
const packageRoot = fileURLToPath(
6+
new URL('../packages/table-core/', import.meta.url),
7+
)
8+
const distDir = join(packageRoot, 'dist')
9+
const forbiddenTypeNames = ['Table_Internal', 'Column_Internal']
10+
11+
function walkDeclarationFiles(dir) {
12+
const files = []
13+
14+
for (const entry of readdirSync(dir)) {
15+
const path = join(dir, entry)
16+
const stat = statSync(path)
17+
18+
if (stat.isDirectory()) {
19+
files.push(...walkDeclarationFiles(path))
20+
continue
21+
}
22+
23+
if (path.endsWith('.d.ts') || path.endsWith('.d.cts')) {
24+
files.push(path)
25+
}
26+
}
27+
28+
return files
29+
}
30+
31+
function removeExportedInterface(source, interfaceName) {
32+
let start = source.indexOf(`export interface ${interfaceName}`)
33+
34+
if (start === -1) {
35+
start = source.indexOf(`interface ${interfaceName}`)
36+
}
37+
38+
if (start === -1) {
39+
return source
40+
}
41+
42+
const bodyStart = source.indexOf('{', start)
43+
44+
if (bodyStart === -1) {
45+
throw new Error(`Could not find body for ${interfaceName}`)
46+
}
47+
48+
let depth = 0
49+
50+
for (let i = bodyStart; i < source.length; i++) {
51+
const char = source[i]
52+
53+
if (char === '{') {
54+
depth++
55+
} else if (char === '}') {
56+
depth--
57+
58+
if (depth === 0) {
59+
let end = i + 1
60+
61+
while (source[end] === '\n' || source[end] === '\r') {
62+
end++
63+
}
64+
65+
return source.slice(0, start) + source.slice(end)
66+
}
67+
}
68+
}
69+
70+
throw new Error(`Could not find end of ${interfaceName}`)
71+
}
72+
73+
function removeTypeAlias(source, typeName) {
74+
const aliasPattern = new RegExp(
75+
String.raw`\b(?:export\s+)?type\s+${typeName}\b`,
76+
)
77+
const match = aliasPattern.exec(source)
78+
79+
if (!match) {
80+
return source
81+
}
82+
83+
const start = match.index
84+
const end = source.indexOf(';', start)
85+
86+
if (end === -1) {
87+
throw new Error(`Could not find end of ${typeName}`)
88+
}
89+
90+
return source.slice(0, start) + source.slice(end + 1)
91+
}
92+
93+
function getSpecifierName(specifier) {
94+
return specifier
95+
.replace(/^type\s+/, '')
96+
.split(/\s+as\s+/)[0]
97+
?.trim()
98+
}
99+
100+
function removeNamedSpecifiers(source, names) {
101+
return source
102+
.replace(
103+
/\b(import|export)(\s+type)?\s+\{([^}]+)\}\s+from\s+([^;\n]+);/g,
104+
(statement, kind, typeKeyword = '', specifiers, fromClause) => {
105+
const nextSpecifiers = specifiers
106+
.split(',')
107+
.map((specifier) => specifier.trim())
108+
.filter(Boolean)
109+
.filter((specifier) => {
110+
const importedName = getSpecifierName(specifier)
111+
return importedName && !names.includes(importedName)
112+
})
113+
114+
if (!nextSpecifiers.length) {
115+
return ''
116+
}
117+
118+
return `${kind}${typeKeyword} { ${nextSpecifiers.join(
119+
', ',
120+
)} } from ${fromClause};`
121+
},
122+
)
123+
.replace(
124+
/\bexport(\s+type)?\s+\{([^}]+)\};/g,
125+
(statement, typeKeyword = '', specifiers) => {
126+
const nextSpecifiers = specifiers
127+
.split(',')
128+
.map((specifier) => specifier.trim())
129+
.filter(Boolean)
130+
.filter((specifier) => {
131+
const exportedName = getSpecifierName(specifier)
132+
return exportedName && !names.includes(exportedName)
133+
})
134+
135+
if (!nextSpecifiers.length) {
136+
return ''
137+
}
138+
139+
return `export${typeKeyword} { ${nextSpecifiers.join(', ')} };`
140+
},
141+
)
142+
}
143+
144+
function rewriteDeclaration(source) {
145+
let next = source
146+
147+
for (const typeName of forbiddenTypeNames) {
148+
next = removeExportedInterface(next, typeName)
149+
}
150+
151+
next = removeTypeAlias(next, 'Table_InternalBroadenedKeys')
152+
next = removeNamedSpecifiers(next, forbiddenTypeNames)
153+
154+
next = next.replaceAll('Table_Internal<', 'Table<')
155+
next = next.replaceAll('Column_Internal<', 'Column<')
156+
next = next.replaceAll('Table_Internal', 'Table')
157+
next = next.replaceAll('Column_Internal', 'Column')
158+
159+
return next
160+
}
161+
162+
const files = walkDeclarationFiles(distDir)
163+
164+
for (const file of files) {
165+
const source = readFileSync(file, 'utf8')
166+
const next = rewriteDeclaration(source)
167+
168+
if (next !== source) {
169+
writeFileSync(file, next)
170+
}
171+
}
172+
173+
const leaks = []
174+
175+
for (const file of files) {
176+
const source = readFileSync(file, 'utf8')
177+
178+
for (const typeName of forbiddenTypeNames) {
179+
if (source.includes(typeName)) {
180+
leaks.push(`${file}: ${typeName}`)
181+
}
182+
}
183+
}
184+
185+
if (leaks.length) {
186+
throw new Error(
187+
`Internal table-core types leaked into emitted declarations:\n${leaks.join(
188+
'\n',
189+
)}`,
190+
)
191+
}

0 commit comments

Comments
 (0)