@jointhedots/gear 1.1.13 → 1.1.14

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.
@@ -66,7 +66,8 @@ export class BuildTarget {
66
66
  const start = Date.now();
67
67
  await task.execute();
68
68
  const elapsed = (Date.now() - start) / 1000;
69
- this.log.info(`⏱ ${name} completed in ${elapsed.toFixed(2)}s`);
69
+ if (elapsed > 1.0)
70
+ this.log.info(`⏱ ${name} completed in ${elapsed.toFixed(2)}s`);
70
71
  }
71
72
  async build() {
72
73
  const buildStartTime = Date.now();
@@ -9,7 +9,7 @@ import { PackageRootDir, resolve_normalized_suffixed_path } from "../../utils/fi
9
9
  import { Library } from "../../model/workspace.js";
10
10
  import { computeNameHashID } from "../../utils/normalized-name.js";
11
11
  import { BuildTask } from "./task.js";
12
- const VirtualOutDir = Path.normalize('X:/');
12
+ const VirtualOutDir = process.platform === 'win32' ? 'X:\\' : '/_/';
13
13
  function getEsbuildLogLevel(mode) {
14
14
  switch (mode) {
15
15
  case "verbose":
@@ -1,7 +1,8 @@
1
- import * as Glob from 'glob';
1
+ import Fs from 'fs';
2
2
  import Os from 'os';
3
3
  import Path from 'path';
4
4
  import Ts from 'typescript';
5
+ import { execFileSync } from 'child_process';
5
6
  import { BuildTask } from "./task.js";
6
7
  import { BuildTarget } from "../build-target.js";
7
8
  import { Library } from "../../model/workspace.js";
@@ -33,15 +34,6 @@ function getError(diagnostics) {
33
34
  error.name = 'EmitterError';
34
35
  return error;
35
36
  }
36
- function getFilenames(baseDir, files) {
37
- return files.map(function (filename) {
38
- const resolvedFilename = Path.resolve(filename);
39
- if (resolvedFilename.indexOf(baseDir) === 0) {
40
- return resolvedFilename;
41
- }
42
- return Path.resolve(baseDir, filename);
43
- });
44
- }
45
37
  function processTree(sourceFile, replacer) {
46
38
  let code = '';
47
39
  let cursorPosition = 0;
@@ -67,19 +59,6 @@ function processTree(sourceFile, replacer) {
67
59
  code += sourceFile.text.slice(cursorPosition);
68
60
  return code;
69
61
  }
70
- function getTSConfig(baseDir, tsconfig) {
71
- const { config, error } = Ts.parseConfigFileTextToJson("tsconfig.json", tsconfig);
72
- if (error)
73
- throw getError([error]);
74
- const configParsed = Ts.parseJsonConfigFileContent(config, Ts.sys, baseDir);
75
- if (configParsed.errors && configParsed.errors.length) {
76
- throw getError(configParsed.errors);
77
- }
78
- return [
79
- configParsed.fileNames,
80
- configParsed.options
81
- ];
82
- }
83
62
  function isNodeKindImportType(value) {
84
63
  return value && value.kind === Ts.SyntaxKind.ImportType;
85
64
  }
@@ -97,40 +76,68 @@ function isNodeKindExportDeclaration(value) {
97
76
  }
98
77
  class TypescriptProject {
99
78
  baseDir;
100
- files;
101
79
  compilerOptions;
102
- host;
103
- program;
104
- filenames;
80
+ dtsDir;
105
81
  constructor(baseDir, tsconfig, outDir) {
106
82
  this.baseDir = Path.resolve(baseDir);
107
- const [files, compilerOptions] = getTSConfig(baseDir, tsconfig);
108
- compilerOptions.declaration = true;
109
- compilerOptions.emitDeclarationOnly = true;
110
- compilerOptions.noEmit = false;
111
- compilerOptions.target = compilerOptions.target || Ts.ScriptTarget.Latest;
112
- compilerOptions.moduleResolution = compilerOptions.moduleResolution || Ts.ModuleResolutionKind.Bundler;
83
+ const { config, error } = Ts.parseConfigFileTextToJson("tsconfig.json", tsconfig);
84
+ if (error)
85
+ throw getError([error]);
86
+ const configParsed = Ts.parseJsonConfigFileContent(config, Ts.sys, baseDir);
87
+ if (configParsed.errors?.length)
88
+ throw getError(configParsed.errors);
89
+ const compilerOptions = configParsed.options;
113
90
  compilerOptions.outDir = compilerOptions.outDir || outDir;
114
- this.files = files;
115
91
  this.compilerOptions = compilerOptions;
116
- this.filenames = getFilenames(this.baseDir, files);
117
- this.host = Ts.createCompilerHost(compilerOptions);
118
- this.program = Ts.createProgram(this.filenames, compilerOptions, this.host);
92
+ // Create a temp directory for tsgo to emit .d.ts into
93
+ this.dtsDir = Fs.mkdtempSync(Path.join(Os.tmpdir(), 'gear-dts-'));
119
94
  }
120
- getSourceFiles() {
121
- return this.program.getSourceFiles();
122
- }
123
- emit(sourceFile, writeFile) {
124
- return this.program.emit(sourceFile, writeFile);
125
- }
126
- getSemanticDiagnostics(sourceFile) {
127
- return this.program.getSemanticDiagnostics(sourceFile);
95
+ /** Run tsgo to emit .d.ts files into dtsDir. Returns stderr output. */
96
+ emitWithTsgo() {
97
+ const tsgoPath = Path.resolve('node_modules/.bin/tsgo');
98
+ const args = [
99
+ '-p', Path.join(this.baseDir, 'tsconfig.json'),
100
+ '--declaration',
101
+ '--emitDeclarationOnly',
102
+ '--skipLibCheck',
103
+ '--outDir', this.dtsDir,
104
+ ];
105
+ try {
106
+ execFileSync(tsgoPath, args, {
107
+ cwd: this.baseDir,
108
+ stdio: ['pipe', 'pipe', 'pipe'],
109
+ timeout: 120_000,
110
+ shell: true,
111
+ });
112
+ return '';
113
+ }
114
+ catch (e) {
115
+ // tsgo may exit non-zero on type errors but still emit .d.ts files
116
+ return e.stderr?.toString() || e.message || '';
117
+ }
128
118
  }
129
- getSyntacticDiagnostics(sourceFile) {
130
- return this.program.getSyntacticDiagnostics(sourceFile);
119
+ /** Recursively collect all .d.ts files emitted by tsgo */
120
+ getEmittedDtsFiles() {
121
+ const results = [];
122
+ function walk(dir) {
123
+ for (const entry of Fs.readdirSync(dir, { withFileTypes: true })) {
124
+ const full = Path.join(dir, entry.name);
125
+ if (entry.isDirectory())
126
+ walk(full);
127
+ else if (isDtsFilename(entry.name))
128
+ results.push(full);
129
+ }
130
+ }
131
+ if (Fs.existsSync(this.dtsDir))
132
+ walk(this.dtsDir);
133
+ return results;
131
134
  }
132
- getDeclarationDiagnostics(sourceFile) {
133
- return this.program.getDeclarationDiagnostics(sourceFile);
135
+ /** Clean up temp directory */
136
+ cleanup() {
137
+ try {
138
+ Fs.rmSync(this.dtsDir, { recursive: true, force: true });
139
+ }
140
+ catch { }
134
141
  }
135
142
  }
136
143
  export function createTypescriptProject(baseDir, tsconfig, outDir) {
@@ -138,18 +145,14 @@ export function createTypescriptProject(baseDir, tsconfig, outDir) {
138
145
  }
139
146
  export function generateTypescriptDefinition(options) {
140
147
  const project = options.project;
141
- const diagnostics = [];
142
148
  const baseDir = project.baseDir;
143
- const outDir = project.compilerOptions.outDir;
144
- const excludesMap = {};
145
- options.exclude = options.exclude || ['node_modules/**/*'];
146
- options.exclude.forEach(function (filename) {
147
- Glob.sync(filename, { cwd: baseDir }).forEach(function (globFileName) {
148
- excludesMap[normalizeFileName(Path.resolve(baseDir, globFileName))] = true;
149
- });
150
- });
149
+ const dtsDir = project.dtsDir;
150
+ function isExcludedPath(fileName) {
151
+ return fileName.includes('/node_modules/') || fileName.includes('\\node_modules\\');
152
+ }
151
153
  // Compute baseUrl prefix to strip from module paths
152
154
  const normalizedBaseDir = normalizeFileName(Path.resolve(baseDir)) + "/";
155
+ const normalizedDtsDir = normalizeFileName(Path.resolve(dtsDir)) + "/";
153
156
  let baseUrlPrefix = '';
154
157
  if (project.compilerOptions.baseUrl) {
155
158
  const absoluteBaseUrl = Path.resolve(baseDir, project.compilerOptions.baseUrl);
@@ -165,138 +168,97 @@ export function generateTypescriptDefinition(options) {
165
168
  }
166
169
  return modulePath;
167
170
  }
168
- let outputContent = '';
171
+ const outputParts = [];
169
172
  if (options.externs) {
170
173
  options.externs.forEach(function (path) {
171
- outputContent += `/// <reference path="${path}" />` + eol;
174
+ outputParts.push(`/// <reference path="${path}" />` + eol);
172
175
  });
173
176
  }
174
177
  if (options.types) {
175
178
  options.types.forEach(function (type) {
176
- outputContent += `/// <reference types="${type}" />` + eol;
179
+ outputParts.push(`/// <reference types="${type}" />` + eol);
177
180
  });
178
181
  }
179
- // Filter source files
180
- const sourcesMap = {};
182
+ // Map source-relative paths to internal: module IDs for ALL emitted files
181
183
  const internalsMap = {};
182
- project.getSourceFiles().some(function (sourceFile) {
183
- const { fileName } = sourceFile;
184
- if (fileName.indexOf(normalizedBaseDir) !== 0)
185
- return;
186
- if (excludesMap[fileName])
187
- return;
188
- const shortName = fileName.slice(normalizedBaseDir.length);
189
- const shortNameNoExt = shortName.slice(0, -Path.extname(fileName).length);
190
- const strippedShortName = stripBaseUrlPrefix(shortName);
191
- const strippedShortNameNoExt = stripBaseUrlPrefix(shortNameNoExt);
192
- const moduleId = `${options.prefix}/${strippedShortNameNoExt}`;
193
- internalsMap[shortName] = moduleId;
194
- internalsMap[shortNameNoExt] = moduleId;
195
- internalsMap[strippedShortName] = moduleId;
196
- internalsMap[strippedShortNameNoExt] = moduleId;
197
- sourcesMap[fileName] = true;
198
- });
199
- // Build reverse map from internal paths to export names
200
- // e.g., "src/Inputs" -> "./Inputs" means internal path "src/Inputs" exports as "prefix/Inputs"
201
- // Store as [internalPrefix, exportName] pairs for prefix matching
184
+ const dtsFiles = project.getEmittedDtsFiles();
185
+ for (const dtsPath of dtsFiles) {
186
+ const normalizedDts = normalizeFileName(Path.resolve(dtsPath));
187
+ if (!normalizedDts.startsWith(normalizedDtsDir))
188
+ continue;
189
+ const relativeDts = normalizedDts.slice(normalizedDtsDir.length);
190
+ const relativeSource = relativeDts.replace(/\.d\.ts$/, '');
191
+ const strippedSource = stripBaseUrlPrefix(relativeSource);
192
+ const moduleId = `${options.prefix}:${strippedSource}`;
193
+ internalsMap[relativeSource] = moduleId;
194
+ internalsMap[strippedSource] = moduleId;
195
+ if (strippedSource.endsWith('/index')) {
196
+ internalsMap[strippedSource.slice(0, -6)] = moduleId;
197
+ }
198
+ }
199
+ // Build public export map: maps export entry ID to internal module ID
200
+ const publicReexports = [];
202
201
  if (options.exports) {
203
202
  for (const entry of Object.values(options.exports)) {
204
203
  const fileName = entry.source;
205
- if (fileName.indexOf(normalizedBaseDir) !== 0)
204
+ if (isExcludedPath(fileName))
206
205
  continue;
207
- if (excludesMap[fileName])
206
+ const normalizedSource = normalizeFileName(Path.resolve(fileName));
207
+ if (!normalizedSource.startsWith(normalizedBaseDir))
208
208
  continue;
209
- const shortName = fileName.slice(normalizedBaseDir.length);
210
- const shortNameNoExt = shortName.slice(0, -Path.extname(fileName).length);
211
- const strippedShortName = stripBaseUrlPrefix(shortName);
209
+ const shortName = normalizedSource.slice(normalizedBaseDir.length);
210
+ const shortNameNoExt = shortName.replace(/\.(ts|tsx|js|jsx)$/, '');
212
211
  const strippedShortNameNoExt = stripBaseUrlPrefix(shortNameNoExt);
213
- const moduleId = entry.id;
214
- internalsMap[shortName] = moduleId;
215
- internalsMap[shortNameNoExt] = moduleId;
216
- internalsMap[strippedShortName] = moduleId;
217
- internalsMap[strippedShortNameNoExt] = moduleId;
218
- sourcesMap[fileName] = true;
212
+ const internalId = internalsMap[shortNameNoExt] ||
213
+ internalsMap[strippedShortNameNoExt] ||
214
+ internalsMap[shortName];
215
+ if (internalId) {
216
+ publicReexports.push({ id: entry.id, internalId });
217
+ }
219
218
  }
220
219
  }
221
- // Unified module ID normalization: strip extensions, baseUrl prefix, and remap to exports
220
+ // Unified module ID normalization
222
221
  function normalizeModuleId(moduleId) {
223
- // Strip .ts, .tsx, .js, .jsx, .d.ts extensions
224
222
  moduleId = moduleId.replace(/\.(d\.ts|ts|tsx|js|jsx)$/, '');
225
- // Strip baseUrl prefix
226
223
  moduleId = stripBaseUrlPrefix(moduleId);
227
- // Apply resolved prefix
228
224
  const remapped = internalsMap[moduleId] ||
229
225
  internalsMap[moduleId + "/index"] ||
230
226
  internalsMap[moduleId + "/index.ts"];
231
227
  return remapped || moduleId;
232
228
  }
233
- // Generate source files
234
- project.getSourceFiles().some(function (sourceFile) {
235
- if (!sourcesMap[sourceFile.fileName])
236
- return;
237
- // Source file is already a declaration file so should does not need to be pre-processed by the emitter
238
- if (isDtsFilename(sourceFile.fileName)) {
239
- writeDeclaration(sourceFile, sourceFile.fileName);
240
- return;
241
- }
242
- const emitOutput = project.emit(sourceFile, (filename, data) => writeFile(filename, data, sourceFile.fileName));
243
- if (emitOutput.emitSkipped || emitOutput.diagnostics.length > 0) {
244
- diagnostics.push(...emitOutput.diagnostics);
245
- diagnostics.push(...project.getSemanticDiagnostics(sourceFile));
246
- diagnostics.push(...project.getSyntacticDiagnostics(sourceFile));
247
- diagnostics.push(...project.getDeclarationDiagnostics(sourceFile));
248
- }
249
- });
250
229
  function isExternalModule(moduleId) {
251
- if (!internalsMap[moduleId]) {
252
- return true;
253
- }
254
- return false;
230
+ return !internalsMap[moduleId];
255
231
  }
256
- function writeFile(filename, data, sourceFilePath) {
257
- if (isDtsFilename(filename)) {
258
- const declFile = Ts.createSourceFile(filename, data, project.compilerOptions.target, true);
259
- writeDeclaration(declFile, sourceFilePath);
260
- }
232
+ // Process each emitted .d.ts file as internal: module
233
+ for (const dtsPath of dtsFiles) {
234
+ const normalizedDts = normalizeFileName(Path.resolve(dtsPath));
235
+ if (!normalizedDts.startsWith(normalizedDtsDir))
236
+ continue;
237
+ const relativeDts = normalizedDts.slice(normalizedDtsDir.length);
238
+ const relativeSource = relativeDts.replace(/\.d\.ts$/, '');
239
+ const data = Fs.readFileSync(dtsPath, 'utf-8');
240
+ const declFile = Ts.createSourceFile(dtsPath, data, Ts.ScriptTarget.Latest, true);
241
+ writeDeclaration(declFile, relativeSource);
242
+ }
243
+ // Emit public re-export modules
244
+ for (const { id, internalId } of publicReexports) {
245
+ outputParts.push(`declare module '${id}' {${eol}`);
246
+ outputParts.push(`${indent}export * from '${internalId}';${eol}`);
247
+ outputParts.push(`}${eol}${eol}`);
261
248
  }
262
- function writeDeclaration(declarationFile, sourceFilePath) {
263
- // Compute rawSourceModuleId based on the source file path that produced the declaration
264
- const resolvedSourcePath = Path.resolve(sourceFilePath);
265
- const sourceExt = Path.extname(resolvedSourcePath);
266
- const rawSourceModuleId = normalizeFileName(resolvedSourcePath.slice(baseDir.length + 1, -sourceExt.length));
267
- // Normalize and remap the module ID
249
+ function writeDeclaration(declarationFile, rawSourceModuleId) {
268
250
  const moduleDeclId = normalizeModuleId(rawSourceModuleId);
269
- outputContent += `declare module '${moduleDeclId}' {${eol}${indent}`;
251
+ outputParts.push(`declare module '${moduleDeclId}' {${eol}${indent}`);
270
252
  function resolveModuleImport(moduleId) {
271
- // Resolve module id
272
253
  let resolved;
273
254
  if (moduleId.charAt(0) === '.') {
274
255
  resolved = normalizeFileName(Path.join(Path.dirname(rawSourceModuleId), moduleId));
275
256
  }
276
257
  else {
277
- // Try to resolve using TypeScript's module resolution (handles tsconfig paths)
278
- const resolveResult = Ts.resolveModuleName(moduleId, declarationFile.fileName, project.compilerOptions, project.host);
279
- if (resolveResult.resolvedModule) {
280
- const resolvedFileName = normalizeFileName(resolveResult.resolvedModule.resolvedFileName);
281
- if (excludesMap[resolvedFileName])
282
- return moduleId;
283
- // Check if resolved file is within our project (internal module)
284
- if (resolvedFileName.startsWith(normalizedBaseDir)) {
285
- // Convert absolute path to relative module id
286
- resolved = resolvedFileName.slice(normalizedBaseDir.length);
287
- }
288
- else {
289
- // External module - return as-is
290
- return moduleId;
291
- }
292
- }
293
- else {
294
- resolved = moduleId;
295
- if (isExternalModule(resolved))
296
- return resolved;
297
- }
258
+ resolved = moduleId;
259
+ if (isExternalModule(resolved))
260
+ return resolved;
298
261
  }
299
- // Normalize: strip extensions, baseUrl prefix, and remap to exports
300
262
  return normalizeModuleId(resolved);
301
263
  }
302
264
  const content = processTree(declarationFile, function (node) {
@@ -322,38 +284,13 @@ export function generateTypescriptDefinition(options) {
322
284
  const chunks = content.replaceAll("\r", "").split("\n").map(c => c.trimEnd());
323
285
  while (chunks.length > 0 && chunks[chunks.length - 1] === "")
324
286
  chunks.pop();
325
- outputContent += chunks.join(eol + indent) + eol;
326
- outputContent += '}' + eol + eol;
287
+ outputParts.push(chunks.join(eol + indent) + eol);
288
+ outputParts.push('}' + eol + eol);
327
289
  }
328
290
  return {
329
- dts: outputContent,
330
- diagnostics,
291
+ dts: outputParts.join(''),
331
292
  };
332
293
  }
333
- export function logDiagnostics(log, diagnostics) {
334
- if (diagnostics && diagnostics.length > 0) {
335
- for (const diagnostic of diagnostics) {
336
- const text = typeof diagnostic.messageText === 'string'
337
- ? diagnostic.messageText
338
- : diagnostic.messageText.messageText;
339
- const message = {
340
- id: `TS${diagnostic.code}`,
341
- text,
342
- detail: diagnostic,
343
- };
344
- if (diagnostic.file && diagnostic.start !== undefined) {
345
- const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
346
- message.location = {
347
- file: diagnostic.file.fileName,
348
- line: line + 1,
349
- column: character,
350
- length: diagnostic.length ?? 0,
351
- };
352
- }
353
- log.put('error', message);
354
- }
355
- }
356
- }
357
294
  export class TypescriptDefinitionTask extends BuildTask {
358
295
  library;
359
296
  constructor(target, library) {
@@ -367,14 +304,18 @@ export class TypescriptDefinitionTask extends BuildTask {
367
304
  const configText = file.read.text(Path.join(lib.path, "./tsconfig.json"))
368
305
  || file.read.text("./tsconfig.json");
369
306
  const project = createTypescriptProject(lib.path, configText, storage.getBaseDirFS());
370
- const { dts, diagnostics } = generateTypescriptDefinition({
307
+ const stderr = project.emitWithTsgo();
308
+ if (stderr) {
309
+ this.log.warn(stderr);
310
+ }
311
+ const { dts } = generateTypescriptDefinition({
371
312
  project,
372
313
  prefix: lib.name,
373
314
  exclude: ["node_modules/**/*"],
374
315
  exports: create_export_map(lib, lib.bundle),
375
316
  });
376
317
  file.write.text(storage.getBaseDirFS() + "/types.d.ts", dts);
377
- logDiagnostics(this.log, diagnostics);
318
+ project.cleanup();
378
319
  }
379
320
  catch (e) {
380
321
  this.log.error("no 'type.d.ts' will be generated for the package:" + e.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jointhedots/gear",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "jointhedots-gear": "esm/cli.js"
@@ -33,19 +33,17 @@
33
33
  "@aws-sdk/client-s3": "^3.968.0",
34
34
  "@jspm/core": "^2.1.0",
35
35
  "@tailwindcss/postcss": "~4.1.3",
36
- "@types/semver": "~7.7.1",
36
+ "@typescript/native-preview": "^7.0.0-dev.20260310.1",
37
37
  "dotenv": "~17.2.3",
38
38
  "esbuild": "~0.27.2",
39
39
  "esbuild-sass-plugin": "~3.6.0",
40
40
  "express": "~5.2.1",
41
- "glob": "~13.0.0",
42
41
  "mime": "~4.1.0",
43
42
  "node-watch": "~0.7.4",
44
43
  "postcss": "~8.5.6",
45
44
  "sass": "~1.97.2",
46
45
  "semver": "~7.7.3",
47
46
  "sharp": "~0.34.5",
48
- "source-map-support": "~0.5.21",
49
47
  "toml": "^3.0.0",
50
48
  "typescript": "~5.9.3",
51
49
  "yaml": "^2.8.2",
@@ -55,6 +53,7 @@
55
53
  "@polycuber/script.cli": "~1.0.5",
56
54
  "@types/chrome": "~0.1.33",
57
55
  "@types/node": "~22.7.4",
56
+ "@types/semver": "~7.7.1",
58
57
  "@types/web-app-manifest": "~1.0.9",
59
58
  "@types/yargs": "~17.0.35"
60
59
  },