@@ -4,6 +4,7 @@ import { APPLIED_STRATEGY, Package } from '../../../utils/package';
4
4
import path from 'path' ;
5
5
import fs from 'fs' ;
6
6
import { Glob } from 'bun' ;
7
+ import { getFile } from '../../../utils/json-file' ;
7
8
8
9
const PROJECT_ROOT = process . cwd ( ) ;
9
10
const TARBALL_DIR = path . join ( PROJECT_ROOT , 'tmp/tarballs' ) ;
@@ -137,6 +138,133 @@ async function makeTypesPrivate(pkg: Package) {
137
138
} ) ;
138
139
}
139
140
141
+ // convert each file to a module
142
+ // and write it back to the file system
143
+ // e.g.
144
+ // ```
145
+ // declare module '@ember-data/model' {
146
+ // export default class Model {}
147
+ // }
148
+ // ```
149
+ //
150
+ // instead of
151
+ // ```
152
+ // export default class Model {}
153
+ // ```
154
+ //
155
+ // additionally, rewrite each relative import
156
+ // to an absolute import
157
+ // e.g. if the types for @ember -data/model contain a file with
158
+ // the following import statement in the types directory
159
+ //
160
+ // ```
161
+ // import attr from './attr';
162
+ // ```
163
+ //
164
+ // then it becomes
165
+ //
166
+ // ```
167
+ // import attr from '@ember-data/model/attr';
168
+ // ```
169
+ async function convertFileToModule ( fileData : string , relativePath : string , pkgName : string ) : Promise < string > {
170
+ const lines = fileData . split ( '\n' ) ;
171
+ const maybeModuleName = pkgName + '/' + relativePath . replace ( / \. d \. t s $ / , '' ) ;
172
+ const moduleDir = pkgName + '/' + path . dirname ( relativePath ) ;
173
+ const moduleName =
174
+ maybeModuleName . endsWith ( '/index' ) && ! maybeModuleName . endsWith ( '/-private/index' )
175
+ ? maybeModuleName . slice ( 0 , - 6 )
176
+ : maybeModuleName ;
177
+
178
+ for ( let i = 0 ; i < lines . length ; i ++ ) {
179
+ const line = lines [ i ] ;
180
+ if ( line . startsWith ( 'import ' ) ) {
181
+ if ( ! line . includes ( `'` ) ) {
182
+ throw new Error ( `Unhandled import in ${ relativePath } ` ) ;
183
+ }
184
+ if ( line . includes ( `'.` ) ) {
185
+ const importPath = line . match ( / ' ( [ ^ ' ] + ) ' / ) ! [ 1 ] ;
186
+ const newImportPath = path . join ( moduleDir , importPath ) ;
187
+ lines [ i ] = line . replace ( importPath , newImportPath ) ;
188
+ }
189
+ }
190
+
191
+ // fix re-exports
192
+ else if ( line . startsWith ( 'export {' ) ) {
193
+ if ( ! line . includes ( '}' ) ) {
194
+ throw new Error ( `Unhandled re-export in ${ relativePath } ` ) ;
195
+ }
196
+ if ( line . includes ( `'.` ) ) {
197
+ const importPath = line . match ( / ' ( [ ^ ' ] + ) ' / ) ! [ 1 ] ;
198
+ const newImportPath = path . join ( moduleDir , importPath ) ;
199
+ lines [ i ] = line . replace ( importPath , newImportPath ) ;
200
+ }
201
+ }
202
+
203
+ // fix * re-exports
204
+ else if ( line . startsWith ( 'export * from' ) ) {
205
+ if ( ! line . includes ( `'` ) ) {
206
+ throw new Error ( `Unhandled re-export in ${ relativePath } ` ) ;
207
+ }
208
+ if ( line . includes ( `'.` ) ) {
209
+ const importPath = line . match ( / ' ( [ ^ ' ] + ) ' / ) ! [ 1 ] ;
210
+ const newImportPath = path . join ( moduleDir , importPath ) ;
211
+ lines [ i ] = line . replace ( importPath , newImportPath ) ;
212
+ }
213
+ }
214
+
215
+ // insert 2 spaces at the beginning of each line
216
+ // to account for module wrapper
217
+ lines [ i ] = ' ' + lines [ i ] ;
218
+ }
219
+
220
+ lines . unshift ( `declare module '${ moduleName } ' {` ) ;
221
+ const srcMapLine = lines . at ( - 1 ) ! ;
222
+ if ( ! srcMapLine . startsWith ( '//# sourceMappingURL=' ) ) {
223
+ lines . push ( '}' ) ;
224
+ } else {
225
+ lines . splice ( - 1 , 0 , '}' ) ;
226
+ }
227
+
228
+ const updatedFileData = lines . join ( '\n' ) ;
229
+
230
+ return updatedFileData ;
231
+ }
232
+
233
+ async function convertTypesToModules ( pkg : Package , subdir : 'unstable-preview-types' | 'preview-types' | 'types' ) {
234
+ const typesDir = path . join ( path . dirname ( pkg . filePath ) , subdir ) ;
235
+ const glob = new Glob ( '**/*.d.ts' ) ;
236
+
237
+ // we will insert a reference to each file in the index.d.ts
238
+ // so that all modules are available to consumers
239
+ // as soon as the tsconfig sources the types directory
240
+ const references = new Set < string > ( ) ;
241
+
242
+ // convert each file to a module
243
+ for await ( const filePath of glob . scan ( typesDir ) ) {
244
+ const fullPath = path . join ( typesDir , filePath ) ;
245
+ const file = Bun . file ( fullPath ) ;
246
+ const fileData = await file . text ( ) ;
247
+ const updatedFileData = await convertFileToModule ( fileData , filePath , pkg . pkgData . name ) ;
248
+
249
+ if ( filePath !== 'index.d.ts' ) {
250
+ references . add ( `/// <reference path="./${ filePath } " />` ) ;
251
+ }
252
+
253
+ await Bun . write ( file , updatedFileData ) ;
254
+ }
255
+
256
+ // write the references into the index.d.ts
257
+ const indexFile = Bun . file ( path . join ( typesDir , 'index.d.ts' ) ) ;
258
+ const exists = await indexFile . exists ( ) ;
259
+ if ( ! exists ) {
260
+ await Bun . write ( indexFile , Array . from ( references ) . join ( '\n' ) ) ;
261
+ } else {
262
+ const fileData = await indexFile . text ( ) ;
263
+ const updatedFileData = Array . from ( references ) . join ( '\n' ) + '\n' + fileData ;
264
+ await Bun . write ( indexFile , updatedFileData ) ;
265
+ }
266
+ }
267
+
140
268
async function makeTypesAlpha ( pkg : Package ) {
141
269
scrubTypesFromExports ( pkg ) ;
142
270
@@ -158,6 +286,8 @@ async function makeTypesAlpha(pkg: Package) {
158
286
) ;
159
287
}
160
288
289
+ await convertTypesToModules ( pkg , 'unstable-preview-types' ) ;
290
+
161
291
// TODO we should probably scan our dist/addon directories for ts/.d.ts files and throw if found.
162
292
}
163
293
0 commit comments