Skip to content

Commit f9efc77

Browse files
committed
debugger /3
1 parent becfa4e commit f9efc77

8 files changed

Lines changed: 143 additions & 170 deletions

File tree

.github/workflows/node.js.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ jobs:
2020
uses: actions/setup-node@v3
2121
with:
2222
node-version: ${{ matrix.node-version }}
23-
cache: "npm"
23+
cache: 'npm'
2424
- run: npm ci
2525
- run: ./node_modules/.bin/lerna -v
2626
- run: ./node_modules/.bin/lerna bootstrap
27-
- run: npm run build --if-present
27+
- run: cd packages/cma-client-browser && ../../generate/reExportEverything.ts cma-client-browser
2828
- run: npm test
2929
env:
3030
DATOCMS_ACCOUNT_EMAIL: ${{ secrets.DATOCMS_ACCOUNT_EMAIL }}

generate/reExportEverything.ts

Lines changed: 119 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,39 @@
1-
#!/usr/bin/env node --stack_size=800 -r ts-node/register
1+
#!/usr/bin/env node -r ts-node/register
22

33
import { readFileSync, writeFileSync } from 'node:fs';
44
import path from 'node:path';
5-
import { Project, type SourceFile } from 'ts-morph';
5+
import { Project, SyntaxKind, type Node } from 'ts-morph';
66

77
const startTime = Date.now();
88
const repoRoot = path.resolve(__dirname, '..');
99

10-
function logDebug(message: string) {
11-
const elapsed = ((Date.now() - startTime) / 1000).toFixed(3);
12-
console.log(`[${elapsed}s] ${message}`);
10+
function logDebug(msg: string) {
11+
const t = ((Date.now() - startTime) / 1000).toFixed(3);
12+
console.log(`[${t}s] ${msg}`);
1313
}
14-
15-
function prettyPath(filePath: string) {
16-
return path.relative(repoRoot, filePath);
14+
function pretty(p: string) {
15+
return path.relative(repoRoot, p);
1716
}
1817

19-
// Get target package from command line argument
2018
const targetPackage = process.argv[2];
2119
if (!targetPackage) {
22-
console.error('Usage: ./generate/reExportEverything.ts <package-name>');
23-
console.error('Example: ./generate/reExportEverything.ts cma-client-node');
20+
console.error('Usage: ./generate/reExportEverything.light.ts <package-name>');
2421
process.exit(1);
2522
}
2623

27-
logDebug(`Starting re-export generation for package "${targetPackage}"`);
24+
logDebug(`Lightweight re-export generation for "${targetPackage}"`);
2825

2926
const SRC_FILE = path.resolve(
3027
__dirname,
3128
`../packages/${targetPackage}/src/index.ts`,
3229
);
33-
const CMA_CLIENT_PATH = path.resolve(
34-
__dirname,
35-
'../packages/cma-client/dist/types/index.d.ts',
36-
);
37-
38-
logDebug(`Source file path resolved to ${prettyPath(SRC_FILE)}`);
39-
logDebug(`CMA client types source at ${prettyPath(CMA_CLIENT_PATH)}`);
30+
const TYPES_DIR = path.resolve(__dirname, '../packages/cma-client/dist/types');
31+
const ENTRY_D_TS = path.join(TYPES_DIR, 'index.d.ts');
4032

4133
const START_MARKER = '// <AUTO-GENERATED-EXPORTS>';
4234
const END_MARKER = '// </AUTO-GENERATED-EXPORTS>';
4335

36+
// Keep these out of the public surface:
4437
const blacklist = new Set([
4538
'buildClient',
4639
'Client',
@@ -54,177 +47,146 @@ const project = new Project({
5447
moduleResolution: 2, // Node
5548
target: 99, // ESNext
5649
},
50+
skipAddingFilesFromTsConfig: true,
5751
});
5852

59-
// Track collected exports
60-
const collected = {
61-
value: new Set<string>(),
62-
type: new Set<string>(),
63-
};
64-
65-
// Avoid infinite recursion
66-
const visitedFiles = new Set<string>();
53+
// Add all .d.ts under the types folder so re-exports resolve
54+
project.addSourceFilesAtPaths(path.join(TYPES_DIR, '**/*.d.ts'));
6755

68-
function collectExports(file: SourceFile) {
69-
const currentPath = file.getFilePath();
70-
if (visitedFiles.has(currentPath)) {
71-
logDebug(`Skipping already visited file ${prettyPath(currentPath)}`);
72-
return;
73-
}
74-
visitedFiles.add(currentPath);
75-
logDebug(`Collecting exports from ${prettyPath(currentPath)}`);
76-
77-
for (const stmt of file.getExportDeclarations()) {
78-
const moduleSpecifier = stmt.getModuleSpecifierSourceFile();
56+
const entry =
57+
project.getSourceFile(ENTRY_D_TS) ?? project.addSourceFileAtPath(ENTRY_D_TS);
58+
logDebug(`Loaded declaration entry: ${pretty(entry.getFilePath())}`);
7959

80-
// Handle named exports
81-
const namedExports = stmt.getNamedExports();
82-
for (const ne of namedExports) {
83-
// Use alias if present, otherwise use original name
84-
const name = ne.getAliasNode()?.getText() || ne.getName();
85-
if (blacklist.has(name)) continue;
60+
const exported = entry.getExportedDeclarations();
8661

87-
if (stmt.isTypeOnly()) collected.type.add(name);
88-
else collected.value.add(name);
89-
}
62+
// Gather explicit type-only names from the entry file
63+
const explicitTypeOnly = new Set<string>();
9064

91-
// Handle export * (wildcards) - recursively collect from the target file
92-
// Note: export * statements have 0 named exports and no namespace export identifier
93-
const isWildcard =
94-
stmt.getNamedExports().length === 0 && !stmt.getNamespaceExport();
95-
96-
if (isWildcard && moduleSpecifier) {
97-
// Case 1: moduleSpecifier is resolved by ts-morph
98-
if (moduleSpecifier) {
99-
logDebug(
100-
`Discovered wildcard export in ${prettyPath(
101-
currentPath,
102-
)} → walking ${prettyPath(moduleSpecifier.getFilePath())}`,
103-
);
104-
collectExports(moduleSpecifier);
105-
}
106-
} else if (isWildcard && stmt.getModuleSpecifier()) {
107-
// Case 2: relative imports that ts-morph didn't resolve
108-
const moduleString = stmt.getModuleSpecifier()?.getLiteralValue();
109-
if (moduleString) {
110-
// Try to resolve the module path relative to current file
111-
const currentDir = path.dirname(file.getFilePath());
112-
const resolvedPath = path.resolve(currentDir, `${moduleString}.d.ts`);
113-
114-
try {
115-
const targetFile = project.addSourceFileAtPath(resolvedPath);
116-
logDebug(
117-
`Attempting manual resolution for wildcard export in ${prettyPath(
118-
currentPath,
119-
)}${prettyPath(resolvedPath)}`,
120-
);
121-
collectExports(targetFile);
122-
} catch (e) {
123-
console.warn(
124-
`Could not resolve wildcard export: ${moduleString} from ${file.getFilePath()}`,
125-
);
126-
}
127-
}
128-
}
129-
130-
// Handle "export * as NS" → collect namespace as a value export
131-
const nsExport = stmt.getNamespaceExport();
132-
if (nsExport) {
133-
const nsName = nsExport.getName();
134-
if (nsName && !blacklist.has(nsName)) {
135-
collected.value.add(nsName);
136-
}
65+
for (const ed of entry.getExportDeclarations()) {
66+
// Whole declaration is type-only
67+
if (ed.isTypeOnly?.()) {
68+
for (const spec of ed.getNamedExports()) {
69+
const alias = spec.getAliasNode()?.getText();
70+
const name = alias ?? spec.getNameNode()?.getText() ?? spec.getName();
71+
if (name) explicitTypeOnly.add(name);
13772
}
13873
}
139-
140-
// Collect direct exports from functions, classes, variables
141-
for (const fn of file.getFunctions()) {
142-
if (fn.isExported() && !blacklist.has(fn.getName() || '')) {
143-
collected.value.add(fn.getName() || '');
74+
// Per-specifier `export { type Foo, Bar }`
75+
for (const spec of ed.getNamedExports()) {
76+
if (spec.isTypeOnly?.()) {
77+
const alias = spec.getAliasNode()?.getText();
78+
const name = alias ?? spec.getNameNode()?.getText() ?? spec.getName();
79+
if (name) explicitTypeOnly.add(name);
14480
}
14581
}
82+
}
14683

147-
for (const cls of file.getClasses()) {
148-
if (cls.isExported() && !blacklist.has(cls.getName() || '')) {
149-
collected.value.add(cls.getName() || '');
150-
}
84+
// Classifiers
85+
function isTypeOnlyDecl(n: Node): boolean {
86+
const k = n.getKind();
87+
if (
88+
k === SyntaxKind.InterfaceDeclaration ||
89+
k === SyntaxKind.TypeAliasDeclaration ||
90+
k === SyntaxKind.TypeParameter
91+
) {
92+
return true;
15193
}
152-
153-
for (const vs of file.getVariableStatements()) {
154-
if (vs.isExported()) {
155-
for (const decl of vs.getDeclarations()) {
156-
const name = decl.getName();
157-
if (!blacklist.has(name)) {
158-
collected.value.add(name);
159-
}
160-
}
161-
}
94+
// Ambient namespaces in d.ts
95+
if (
96+
k === SyntaxKind.ModuleDeclaration &&
97+
n.getSourceFile().isDeclarationFile()
98+
) {
99+
return true;
162100
}
101+
return false;
102+
}
163103

164-
// Also collect type-only exports directly declared in this file
165-
for (const ta of file.getTypeAliases()) {
166-
if (ta.isExported() && !blacklist.has(ta.getName())) {
167-
collected.type.add(ta.getName());
168-
}
169-
}
170-
for (const i of file.getInterfaces()) {
171-
if (i.isExported() && !blacklist.has(i.getName())) {
172-
collected.type.add(i.getName());
173-
}
104+
function isValueDecl(n: Node): boolean {
105+
const k = n.getKind();
106+
return (
107+
k === SyntaxKind.EnumDeclaration ||
108+
k === SyntaxKind.ClassDeclaration ||
109+
k === SyntaxKind.FunctionDeclaration ||
110+
k === SyntaxKind.VariableDeclaration
111+
);
112+
}
113+
114+
// Collect exports
115+
const valueExports = new Set<string>();
116+
const typeExports = new Set<string>();
117+
118+
for (const [name, decls] of exported) {
119+
if (!name || blacklist.has(name)) continue;
120+
121+
// Explicit type-only hint
122+
if (explicitTypeOnly.has(name)) {
123+
typeExports.add(name);
124+
continue;
174125
}
175-
for (const e of file.getEnums()) {
176-
if (e.isExported() && !blacklist.has(e.getName())) {
177-
collected.value.add(e.getName());
126+
127+
let hasValue = false;
128+
let hasType = false;
129+
130+
if (!decls || decls.length === 0) {
131+
// Safer default: treat as type
132+
hasType = true;
133+
} else {
134+
for (const d of decls) {
135+
if (isValueDecl(d)) hasValue = true;
136+
else if (isTypeOnlyDecl(d)) hasType = true;
137+
else {
138+
// Ambiguous nodes → default to type
139+
hasType = true;
140+
}
178141
}
179142
}
180143

181-
logDebug(
182-
`Finished ${prettyPath(currentPath)}${collected.value.size} value exports, ${collected.type.size} type exports accumulated`,
183-
);
144+
if (hasValue) valueExports.add(name);
145+
else if (hasType) typeExports.add(name);
184146
}
185147

186-
const cmaFile = project.addSourceFileAtPath(CMA_CLIENT_PATH);
187-
logDebug(`Loaded root CMA client declaration file ${prettyPath(cmaFile.getFilePath())}`);
188-
collectExports(cmaFile);
148+
// De-dupe: if something has a value, don’t also emit as type
149+
for (const n of valueExports) typeExports.delete(n);
150+
151+
const values = Array.from(valueExports).sort();
152+
const types = Array.from(typeExports).sort();
189153

190154
logDebug(
191-
`Export collection completed across ${visitedFiles.size} files → ${collected.value.size} values, ${collected.type.size} types`,
155+
`Collected ${values.length} value exports and ${types.length} type exports`,
192156
);
193157

194-
const valueExports = Array.from(collected.value).sort();
195-
const typeExports = Array.from(collected.type).sort();
196-
197-
const newExportBlock = `
198-
${START_MARKER}
199-
export {
200-
${valueExports.join(',\n ')},
201-
} from '@datocms/cma-client';
202-
203-
export type {
204-
${typeExports.join(',\n ')},
205-
} from '@datocms/cma-client';
206-
${END_MARKER}
207-
`.trim();
158+
const lines: string[] = [];
159+
lines.push(START_MARKER);
160+
if (values.length) {
161+
lines.push(
162+
`export {\n ${values.join(',\n ')}\n} from '@datocms/cma-client';`,
163+
);
164+
}
165+
if (types.length) {
166+
lines.push(
167+
`export type {\n ${types.join(',\n ')}\n} from '@datocms/cma-client';`,
168+
);
169+
}
170+
lines.push(END_MARKER);
171+
const newBlock = lines.join('\n');
208172

209-
// Read existing file
173+
// Read/patch target file
210174
let content = '';
211175
try {
212176
content = readFileSync(SRC_FILE, 'utf-8');
177+
logDebug(`Loaded target file: ${pretty(SRC_FILE)}`);
213178
} catch {
214-
console.warn(`File ${SRC_FILE} not found. Creating a new one.`);
179+
logDebug(`Target file ${pretty(SRC_FILE)} not found; will create.`);
215180
}
216181

217-
// Replace old export block if present, otherwise prepend
218182
if (content.includes(START_MARKER) && content.includes(END_MARKER)) {
219-
const regex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, 'm');
220-
content = content.replace(regex, newExportBlock);
221-
logDebug('Updated existing auto-generated export block');
183+
const re = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, 'm');
184+
content = content.replace(re, newBlock);
185+
logDebug('Replaced existing auto-generated export block.');
222186
} else {
223-
content = `${newExportBlock}\n\n${content}`;
224-
logDebug('Inserted new auto-generated export block at file top');
187+
content = `${newBlock}\n\n${content}`;
188+
logDebug('Inserted new auto-generated export block at top.');
225189
}
226190

227-
// Write updated file
228191
writeFileSync(SRC_FILE, content);
229-
logDebug(`Wrote updated exports to ${prettyPath(SRC_FILE)}`);
230-
logDebug('Re-export generation script completed');
192+
logDebug(`Wrote exports to ${pretty(SRC_FILE)}. Done.`);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"test-next": "ACCOUNT_API_BASE_URL=http://account-api.lvh.me:3001 SITE_API_BASE_URL=http://site-api.lvh.me:3001 PUSHER_APP_KEY=12a5ddac68784be0fc59 PUSHER_CLUSTER=eu npm run test",
1010
"generate": "./generate/index.ts && npm run format",
1111
"generate-next": "GENERATE_FROM_DEV=true ./generate/index.ts && npm run format",
12-
"build": "lerna run build --stream --concurrency 1",
12+
"build": "lerna run build",
1313
"prepublishOnly": "npm run generate && ./generate/setClientVersion.ts && npm run build && npm run test",
1414
"publish": "./bin/publish.sh",
1515
"publish-next": "GENERATE_FROM_DEV=true ACCOUNT_API_BASE_URL=http://account-api.lvh.me:3001 SITE_API_BASE_URL=http://site-api.lvh.me:3001 PUSHER_APP_KEY=12a5ddac68784be0fc59 PUSHER_CLUSTER=eu ./bin/publish.sh --dist-tag next"

packages/cma-client-browser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@datocms/rest-client-utils": "^5.1.13"
3333
},
3434
"scripts": {
35-
"build": "../../generate/reExportEverything.ts cma-client-browser && tsc && tsc --project ./tsconfig.esnext.json && ts-node ./esbuild.ts",
35+
"build": "../../generate/reExportEverything.ts cma-client-browser && ../../node_modules/.bin/biome check --write ./src/index.ts && tsc && tsc --project ./tsconfig.esnext.json && ts-node ./esbuild.ts",
3636
"prebuild": "rimraf dist"
3737
},
3838
"bugs": {

packages/cma-client-browser/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ export {
9292
visitNormalizedFieldValues,
9393
visitNormalizedFieldValuesAsync,
9494
} from '@datocms/cma-client';
95-
9695
export type {
9796
ApiTypes,
9897
BlockInNestedResponse,

0 commit comments

Comments
 (0)