Skip to content

Commit b5d2fe0

Browse files
committed
Fix CI
1 parent 8f18538 commit b5d2fe0

9 files changed

Lines changed: 156 additions & 137 deletions

File tree

.github/workflows/node.js.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ jobs:
1212

1313
strategy:
1414
matrix:
15-
node-version: [18.x, 20.x]
15+
node-version: [20.x, 22.x]
1616

1717
steps:
1818
- uses: actions/checkout@v3
1919
- name: Use Node.js ${{ matrix.node-version }}
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

generate/index.ts

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

3-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
43
import Handlebars from 'handlebars';
4+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
55
import rimraf from 'rimraf';
66
import extractInfoFromSchema, {
77
type ResourceInfo,
8-
type SchemaInfo,
8+
type SchemaInfo
99
} from './extractInfoFromSchema';
1010
import toSafeName from './toSafeName';
1111

generate/reExportEverything.ts

Lines changed: 127 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
1-
#!/usr/bin/env node --stack_size=800 -r ts-node/register
1+
#!/usr/bin/env -S 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';
6+
7+
const startTime = Date.now();
8+
const repoRoot = path.resolve(__dirname, '..');
9+
10+
function logDebug(msg: string) {
11+
const t = ((Date.now() - startTime) / 1000).toFixed(3);
12+
console.log(`[${t}s] ${msg}`);
13+
}
14+
function pretty(p: string) {
15+
return path.relative(repoRoot, p);
16+
}
617

7-
// Get target package from command line argument
818
const targetPackage = process.argv[2];
919
if (!targetPackage) {
10-
console.error('Usage: ./generate/reExportEverything.ts <package-name>');
11-
console.error('Example: ./generate/reExportEverything.ts cma-client-node');
20+
console.error('Usage: ./generate/reExportEverything.light.ts <package-name>');
1221
process.exit(1);
1322
}
1423

24+
logDebug(`Lightweight re-export generation for "${targetPackage}"`);
25+
1526
const SRC_FILE = path.resolve(
1627
__dirname,
1728
`../packages/${targetPackage}/src/index.ts`,
1829
);
19-
const CMA_CLIENT_PATH = path.resolve(
20-
__dirname,
21-
'../packages/cma-client/dist/types/index.d.ts',
22-
);
30+
const TYPES_DIR = path.resolve(__dirname, '../packages/cma-client/dist/types');
31+
const ENTRY_D_TS = path.join(TYPES_DIR, 'index.d.ts');
2332

2433
const START_MARKER = '// <AUTO-GENERATED-EXPORTS>';
2534
const END_MARKER = '// </AUTO-GENERATED-EXPORTS>';
2635

36+
// Keep these out of the public surface:
2737
const blacklist = new Set([
2838
'buildClient',
2939
'Client',
@@ -37,148 +47,146 @@ const project = new Project({
3747
moduleResolution: 2, // Node
3848
target: 99, // ESNext
3949
},
50+
skipAddingFilesFromTsConfig: true,
4051
});
4152

42-
// Track collected exports
43-
const collected = {
44-
value: new Set<string>(),
45-
type: new Set<string>(),
46-
};
47-
48-
// Avoid infinite recursion
49-
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'));
5055

51-
function collectExports(file: SourceFile) {
52-
if (visitedFiles.has(file.getFilePath())) return;
53-
visitedFiles.add(file.getFilePath());
56+
const entry =
57+
project.getSourceFile(ENTRY_D_TS) ?? project.addSourceFileAtPath(ENTRY_D_TS);
58+
logDebug(`Loaded declaration entry: ${pretty(entry.getFilePath())}`);
5459

55-
for (const stmt of file.getExportDeclarations()) {
56-
const moduleSpecifier = stmt.getModuleSpecifierSourceFile();
60+
const exported = entry.getExportedDeclarations();
5761

58-
// Handle named exports
59-
const namedExports = stmt.getNamedExports();
60-
for (const ne of namedExports) {
61-
// Use alias if present, otherwise use original name
62-
const name = ne.getAliasNode()?.getText() || ne.getName();
63-
if (blacklist.has(name)) continue;
62+
// Gather explicit type-only names from the entry file
63+
const explicitTypeOnly = new Set<string>();
6464

65-
if (stmt.isTypeOnly()) collected.type.add(name);
66-
else collected.value.add(name);
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);
6772
}
68-
69-
// Handle export * (wildcards) - recursively collect from the target file
70-
// Note: export * statements have 0 named exports and no namespace export identifier
71-
const isWildcard =
72-
stmt.getNamedExports().length === 0 && !stmt.getNamespaceExport();
73-
74-
if (isWildcard && moduleSpecifier) {
75-
// Case 1: moduleSpecifier is resolved by ts-morph
76-
collectExports(moduleSpecifier);
77-
} else if (isWildcard && stmt.getModuleSpecifier()) {
78-
// Case 2: relative imports that ts-morph didn't resolve
79-
const moduleString = stmt.getModuleSpecifier()?.getLiteralValue();
80-
if (moduleString) {
81-
// Try to resolve the module path relative to current file
82-
const currentDir = path.dirname(file.getFilePath());
83-
const resolvedPath = path.resolve(currentDir, `${moduleString}.d.ts`);
84-
85-
try {
86-
const targetFile = project.addSourceFileAtPath(resolvedPath);
87-
collectExports(targetFile);
88-
} catch (e) {
89-
console.warn(
90-
`Could not resolve wildcard export: ${moduleString} from ${file.getFilePath()}`,
91-
);
92-
}
93-
}
94-
}
95-
96-
// Handle "export * as NS" → collect namespace as a value export
97-
const nsExport = stmt.getNamespaceExport();
98-
if (nsExport) {
99-
const nsName = nsExport.getName();
100-
if (nsName && !blacklist.has(nsName)) {
101-
collected.value.add(nsName);
102-
}
73+
}
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);
10380
}
10481
}
82+
}
10583

106-
// Collect direct exports from functions, classes, variables
107-
for (const fn of file.getFunctions()) {
108-
if (fn.isExported() && !blacklist.has(fn.getName() || '')) {
109-
collected.value.add(fn.getName() || '');
110-
}
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;
11193
}
94+
// Ambient namespaces in d.ts
95+
if (
96+
k === SyntaxKind.ModuleDeclaration &&
97+
n.getSourceFile().isDeclarationFile()
98+
) {
99+
return true;
100+
}
101+
return false;
102+
}
112103

113-
for (const cls of file.getClasses()) {
114-
if (cls.isExported() && !blacklist.has(cls.getName() || '')) {
115-
collected.value.add(cls.getName() || '');
116-
}
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;
117125
}
118126

119-
for (const vs of file.getVariableStatements()) {
120-
if (vs.isExported()) {
121-
for (const decl of vs.getDeclarations()) {
122-
const name = decl.getName();
123-
if (!blacklist.has(name)) {
124-
collected.value.add(name);
125-
}
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;
126140
}
127141
}
128142
}
129143

130-
// Also collect type-only exports directly declared in this file
131-
for (const ta of file.getTypeAliases()) {
132-
if (ta.isExported() && !blacklist.has(ta.getName())) {
133-
collected.type.add(ta.getName());
134-
}
135-
}
136-
for (const i of file.getInterfaces()) {
137-
if (i.isExported() && !blacklist.has(i.getName())) {
138-
collected.type.add(i.getName());
139-
}
140-
}
141-
for (const e of file.getEnums()) {
142-
if (e.isExported() && !blacklist.has(e.getName())) {
143-
collected.value.add(e.getName());
144-
}
145-
}
144+
if (hasValue) valueExports.add(name);
145+
else if (hasType) typeExports.add(name);
146146
}
147147

148-
const cmaFile = project.addSourceFileAtPath(CMA_CLIENT_PATH);
149-
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);
150150

151-
const valueExports = Array.from(collected.value).sort();
152-
const typeExports = Array.from(collected.type).sort();
151+
const values = Array.from(valueExports).sort();
152+
const types = Array.from(typeExports).sort();
153153

154-
const newExportBlock = `
155-
${START_MARKER}
156-
export {
157-
${valueExports.join(',\n ')},
158-
} from '@datocms/cma-client';
154+
logDebug(
155+
`Collected ${values.length} value exports and ${types.length} type exports`,
156+
);
159157

160-
export type {
161-
${typeExports.join(',\n ')},
162-
} from '@datocms/cma-client';
163-
${END_MARKER}
164-
`.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');
165172

166-
// Read existing file
173+
// Read/patch target file
167174
let content = '';
168175
try {
169176
content = readFileSync(SRC_FILE, 'utf-8');
177+
logDebug(`Loaded target file: ${pretty(SRC_FILE)}`);
170178
} catch {
171-
console.warn(`File ${SRC_FILE} not found. Creating a new one.`);
179+
logDebug(`Target file ${pretty(SRC_FILE)} not found; will create.`);
172180
}
173181

174-
// Replace old export block if present, otherwise prepend
175182
if (content.includes(START_MARKER) && content.includes(END_MARKER)) {
176-
const regex = new RegExp(`${START_MARKER}[\\s\\S]*?${END_MARKER}`, 'm');
177-
content = content.replace(regex, newExportBlock);
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.');
178186
} else {
179-
content = `${newExportBlock}\n\n${content}`;
187+
content = `${newBlock}\n\n${content}`;
188+
logDebug('Inserted new auto-generated export block at top.');
180189
}
181190

182-
// Write updated file
183191
writeFileSync(SRC_FILE, content);
184-
console.log(`Updated exports (including types) in ${SRC_FILE}`);
192+
logDebug(`Wrote exports to ${pretty(SRC_FILE)}. Done.`);

generate/setClientVersion.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
#!/usr/bin/env node -r ts-node/register
1+
#!/usr/bin/env -S node -r ts-node/register
22

33
import { readFileSync, writeFileSync } from 'node:fs';
44

55
for (const dir of ['cma-client', 'dashboard-client']) {
6-
const version: string = JSON.parse(
6+
const version: string = (JSON.parse(
77
readFileSync(`./packages/${dir}/package.json`, 'utf8'),
8-
).version;
8+
) as any).version;
99

1010
const sourceFilePath = `./packages/${dir}/src/generated/Client.ts`;
1111

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)