taist 0.1.9 → 0.1.10
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.
- package/README.md +82 -1
- package/lib/transform.js +43 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Taist - AI Test Runner
|
|
2
2
|
## Token-Optimized Testing Framework for AI-Assisted Development
|
|
3
3
|
|
|
4
|
-
Version: 0.1.
|
|
4
|
+
Version: 0.1.9 | January 2025 | [Technical Specification](./SPEC.md)
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -135,6 +135,7 @@ tracer.instrument(MyClass, 'MyClass');
|
|
|
135
135
|
| Method | Use Case | Setup |
|
|
136
136
|
|--------|----------|-------|
|
|
137
137
|
| **ESM Loader** | Node.js apps, automatic tracing | `node --import taist/module-patcher app.js` |
|
|
138
|
+
| **Build-Time** | Bundled apps (Directus, Vite) | `taist/vite-plugin` in build config |
|
|
138
139
|
| **Import-based** | Express apps, selective tracing | `import 'taist/instrument'` |
|
|
139
140
|
| **Programmatic** | Full control, multiple tracers | `new ServiceTracer()` |
|
|
140
141
|
|
|
@@ -321,6 +322,86 @@ This is particularly useful for:
|
|
|
321
322
|
- Classes are wrapped so new instances are automatically instrumented
|
|
322
323
|
- Non-function exports are passed through unchanged
|
|
323
324
|
|
|
325
|
+
### Build-Time Instrumentation (Bundled Apps)
|
|
326
|
+
|
|
327
|
+
For applications that bundle their code (like Directus extensions, Vite apps, etc.), runtime instrumentation can't see inside the bundle. Use the **Rollup/Vite plugin** to instrument during build instead.
|
|
328
|
+
|
|
329
|
+
**The Problem:**
|
|
330
|
+
```
|
|
331
|
+
src/order.js ─┐
|
|
332
|
+
src/user.js ─┼─► Bundler ─► dist/bundle.js (one file)
|
|
333
|
+
src/utils.js ─┘
|
|
334
|
+
↑
|
|
335
|
+
ESM loader only sees this one module
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**The Solution:** Instrument source files BEFORE bundling:
|
|
339
|
+
```
|
|
340
|
+
src/order.js ─► instrument ─┐
|
|
341
|
+
src/user.js ─► instrument ─┼─► Bundler ─► dist/bundle.js
|
|
342
|
+
src/utils.js ─► instrument ─┘ (instrumented!)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
#### Vite Configuration
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
// vite.config.js
|
|
349
|
+
import { defineConfig } from 'vite';
|
|
350
|
+
import taistPlugin from 'taist/vite-plugin';
|
|
351
|
+
|
|
352
|
+
export default defineConfig({
|
|
353
|
+
plugins: [
|
|
354
|
+
taistPlugin({
|
|
355
|
+
include: ['src/**/*.js', 'src/**/*.ts'],
|
|
356
|
+
exclude: ['**/*.test.js']
|
|
357
|
+
})
|
|
358
|
+
],
|
|
359
|
+
build: {
|
|
360
|
+
rollupOptions: {
|
|
361
|
+
// Keep taist as external - it's a runtime dependency
|
|
362
|
+
external: ['taist/lib/trace-reporter.js', 'taist/lib/trace-context.js']
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Rollup Configuration
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
// rollup.config.js
|
|
372
|
+
import taistPlugin from 'taist/rollup-plugin';
|
|
373
|
+
|
|
374
|
+
export default {
|
|
375
|
+
input: 'src/index.js',
|
|
376
|
+
output: { file: 'dist/bundle.js', format: 'es' },
|
|
377
|
+
plugins: [
|
|
378
|
+
taistPlugin({
|
|
379
|
+
include: ['src/**/*.js']
|
|
380
|
+
})
|
|
381
|
+
],
|
|
382
|
+
external: ['taist/lib/trace-reporter.js', 'taist/lib/trace-context.js']
|
|
383
|
+
};
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### Plugin Options
|
|
387
|
+
|
|
388
|
+
| Option | Type | Default | Description |
|
|
389
|
+
|--------|------|---------|-------------|
|
|
390
|
+
| `include` | `string[]` | `['src/**/*.js']` | Glob patterns for files to instrument |
|
|
391
|
+
| `exclude` | `string[]` | `['**/node_modules/**']` | Glob patterns to skip |
|
|
392
|
+
| `enabled` | `boolean` | `true` | Enable/disable (respects `TAIST_ENABLED` env) |
|
|
393
|
+
|
|
394
|
+
**When to use:**
|
|
395
|
+
- Directus extensions
|
|
396
|
+
- Vite/Rollup bundled applications
|
|
397
|
+
- Any code that gets bundled before deployment
|
|
398
|
+
- When ESM loader can't intercept your modules
|
|
399
|
+
|
|
400
|
+
**Runtime requirements:**
|
|
401
|
+
- `taist` must be installed as a dependency of the host application
|
|
402
|
+
- Set `TAIST_ENABLED=true` and `TAIST_COLLECTOR_SOCKET` at runtime
|
|
403
|
+
- The built code will send traces to the collector when executed
|
|
404
|
+
|
|
324
405
|
---
|
|
325
406
|
|
|
326
407
|
## Test Integration
|
package/lib/transform.js
CHANGED
|
@@ -242,10 +242,18 @@ const __taist_module = "${moduleName}";
|
|
|
242
242
|
|
|
243
243
|
let transformed = shebang + injection + sourceWithoutShebang;
|
|
244
244
|
|
|
245
|
-
//
|
|
246
|
-
|
|
245
|
+
// Separate classes from functions - classes need different handling to preserve hoisting
|
|
246
|
+
const classExports = exports.filter(e => e.type === "class");
|
|
247
|
+
const functionExports = exports.filter(e => e.type !== "class");
|
|
248
|
+
|
|
249
|
+
// For CLASSES: Keep original export, instrument in-place (preserves hoisting)
|
|
250
|
+
// This avoids TDZ issues with circular dependencies
|
|
251
|
+
// We'll add __taist_instrumentClass() calls at the end instead of re-exporting
|
|
252
|
+
|
|
253
|
+
// For FUNCTIONS: Rename and re-export (original behavior)
|
|
254
|
+
for (const exp of functionExports) {
|
|
247
255
|
if (exp.declaration === "inline") {
|
|
248
|
-
// Inline exports: export function/
|
|
256
|
+
// Inline exports: export function/const Name
|
|
249
257
|
if (exp.type === "function") {
|
|
250
258
|
// Rename: export function foo -> function __taist_orig_foo
|
|
251
259
|
transformed = transformed.replace(
|
|
@@ -261,23 +269,10 @@ const __taist_module = "${moduleName}";
|
|
|
261
269
|
new RegExp(`export\\s+const\\s+${exp.name}\\s*=`, "g"),
|
|
262
270
|
`const __taist_orig_${exp.name} =`
|
|
263
271
|
);
|
|
264
|
-
} else if (exp.type === "class") {
|
|
265
|
-
// Rename: export class Foo -> class __taist_orig_Foo
|
|
266
|
-
transformed = transformed.replace(
|
|
267
|
-
new RegExp(`export\\s+class\\s+${exp.name}\\b`, "g"),
|
|
268
|
-
`class __taist_orig_${exp.name}`
|
|
269
|
-
);
|
|
270
272
|
}
|
|
271
273
|
} else if (exp.declaration === "named") {
|
|
272
|
-
// Named exports:
|
|
273
|
-
|
|
274
|
-
if (exp.type === "class") {
|
|
275
|
-
// Rename: class Foo -> class __taist_orig_Foo (handles extends)
|
|
276
|
-
transformed = transformed.replace(
|
|
277
|
-
new RegExp(`\\bclass\\s+${exp.name}\\s*(extends\\s+\\w+\\s*)?([{<])`, "g"),
|
|
278
|
-
(match, ext, brace) => `class __taist_orig_${exp.name} ${ext || ''}${brace}`
|
|
279
|
-
);
|
|
280
|
-
} else if (exp.type === "function") {
|
|
274
|
+
// Named exports: function foo {...} then export { foo }
|
|
275
|
+
if (exp.type === "function") {
|
|
281
276
|
// Rename: function foo -> function __taist_orig_foo
|
|
282
277
|
transformed = transformed.replace(
|
|
283
278
|
new RegExp(`\\bfunction\\s+${exp.name}\\s*\\(`, "g"),
|
|
@@ -292,10 +287,10 @@ const __taist_module = "${moduleName}";
|
|
|
292
287
|
}
|
|
293
288
|
}
|
|
294
289
|
|
|
295
|
-
// Remove named export statements
|
|
290
|
+
// Remove named export statements for FUNCTIONS we're replacing (not classes)
|
|
296
291
|
// Match: export { Name1, Name2 } and remove names we're wrapping
|
|
297
|
-
const
|
|
298
|
-
if (
|
|
292
|
+
const namedFunctionExportNames = functionExports.filter(e => e.declaration === "named").map(e => e.name);
|
|
293
|
+
if (namedFunctionExportNames.length > 0) {
|
|
299
294
|
transformed = transformed.replace(
|
|
300
295
|
/export\s*\{([^}]+)\}/g,
|
|
301
296
|
(match, names) => {
|
|
@@ -303,7 +298,7 @@ const __taist_module = "${moduleName}";
|
|
|
303
298
|
.map(n => n.trim())
|
|
304
299
|
.filter(n => {
|
|
305
300
|
const name = n.split(/\s+as\s+/)[0].trim();
|
|
306
|
-
return !
|
|
301
|
+
return !namedFunctionExportNames.includes(name);
|
|
307
302
|
});
|
|
308
303
|
if (remaining.length === 0) {
|
|
309
304
|
return '// export moved to end';
|
|
@@ -313,11 +308,12 @@ const __taist_module = "${moduleName}";
|
|
|
313
308
|
);
|
|
314
309
|
}
|
|
315
310
|
|
|
316
|
-
// Track default exports that need to be moved to after re-exports
|
|
311
|
+
// Track default exports for FUNCTIONS that need to be moved to after re-exports
|
|
317
312
|
const defaultExports = [];
|
|
318
313
|
|
|
319
|
-
// Remove `export default Name;` from original location
|
|
320
|
-
|
|
314
|
+
// Remove `export default Name;` from original location for FUNCTIONS only
|
|
315
|
+
// (Classes keep their default export in place)
|
|
316
|
+
for (const exp of functionExports) {
|
|
321
317
|
// Match: export default Name;
|
|
322
318
|
if (transformed.match(new RegExp(`export\\s+default\\s+${exp.name}\\s*;`))) {
|
|
323
319
|
transformed = transformed.replace(
|
|
@@ -336,22 +332,34 @@ const __taist_module = "${moduleName}";
|
|
|
336
332
|
}
|
|
337
333
|
}
|
|
338
334
|
|
|
339
|
-
// Add wrapped re-exports at the end
|
|
335
|
+
// Add wrapped re-exports for FUNCTIONS at the end
|
|
340
336
|
// Only add module prefix if it differs from export name to avoid "Calculator.Calculator"
|
|
341
|
-
const
|
|
337
|
+
const functionReexports = functionExports
|
|
342
338
|
.map((exp) => {
|
|
343
339
|
const nameExpr = `(__taist_module === "${exp.name}" ? "${exp.name}" : __taist_module + ".${exp.name}")`;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return `export const ${exp.name} = __taist_instrumentClass(__taist_orig_${exp.name}, ${nameExpr});`;
|
|
347
|
-
} else {
|
|
348
|
-
// Functions use wrapMethod()
|
|
349
|
-
return `export const ${exp.name} = __taist_wrap(__taist_orig_${exp.name}, ${nameExpr});`;
|
|
350
|
-
}
|
|
340
|
+
// Functions use wrapMethod()
|
|
341
|
+
return `export const ${exp.name} = __taist_wrap(__taist_orig_${exp.name}, ${nameExpr});`;
|
|
351
342
|
})
|
|
352
343
|
.join("\n");
|
|
353
344
|
|
|
354
|
-
|
|
345
|
+
// Add in-place instrumentation for CLASSES (preserves hoisting/TDZ)
|
|
346
|
+
// __taist_instrumentClass mutates the prototype in-place
|
|
347
|
+
const classInstrumentations = classExports
|
|
348
|
+
.map((exp) => {
|
|
349
|
+
const nameExpr = `(__taist_module === "${exp.name}" ? "${exp.name}" : __taist_module + ".${exp.name}")`;
|
|
350
|
+
return `__taist_instrumentClass(${exp.name}, ${nameExpr});`;
|
|
351
|
+
})
|
|
352
|
+
.join("\n");
|
|
353
|
+
|
|
354
|
+
transformed += `\n\n// --- TAIST INSTRUMENTATION ---\n`;
|
|
355
|
+
|
|
356
|
+
if (functionReexports) {
|
|
357
|
+
transformed += `// Wrapped function exports\n${functionReexports}\n`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (classInstrumentations) {
|
|
361
|
+
transformed += `// In-place class instrumentation (preserves hoisting)\n${classInstrumentations}\n`;
|
|
362
|
+
}
|
|
355
363
|
|
|
356
364
|
// Add default exports at the end (after the wrapped versions are defined)
|
|
357
365
|
for (const name of defaultExports) {
|