@di-framework/tsc 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DI Framework Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # @di-framework/tsc
2
+
3
+ [`ttsc`](https://ttsc.dev) transform plugin that injects **runtime parameter checks** from your TypeScript types.
4
+
5
+ Source stays plain TypeScript — no `assert()`, schemas, or decorators. On emit, function bodies get `typeof` / shape guards synthesized from parameter types.
6
+
7
+ > MVP: function declarations with required identifier params; primitives + plain object/interface props. See [Limitations](#limitations).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm i -D ttsc typescript @di-framework/tsc
13
+ ```
14
+
15
+ Consumers supply `ttsc` and TypeScript 7+; this package is the transform only.
16
+
17
+ First build compiles the Go sidecar (cached afterward). Needs a Go toolchain (`go` 1.26+ recommended; `ttsc` can pin via `TTSC_GO_BINARY`).
18
+
19
+ ## Setup
20
+
21
+ `tsconfig.json`:
22
+
23
+ ```json
24
+ {
25
+ "compilerOptions": {
26
+ "strict": true,
27
+ "plugins": [{ "transform": "@di-framework/tsc" }]
28
+ }
29
+ }
30
+ ```
31
+
32
+ The package also declares `ttsc.plugin` for auto-discovery when listed in `devDependencies`. Explicit `plugins[]` is recommended so wiring is obvious.
33
+
34
+ Build with `ttsc`, not stock `tsc`:
35
+
36
+ ```bash
37
+ npx ttsc --emit
38
+ ```
39
+
40
+ ## Example
41
+
42
+ ```ts
43
+ interface User {
44
+ id: number;
45
+ name: string;
46
+ }
47
+
48
+ function greet(user: User): string {
49
+ return `hello ${user.name}`;
50
+ }
51
+ ```
52
+
53
+ Emitted JS (simplified):
54
+
55
+ ```js
56
+ function greet(user) {
57
+ if (typeof user !== "object" || user === null)
58
+ throw new TypeError("Expected user to be an object");
59
+ if (typeof user.id !== "number")
60
+ throw new TypeError("Expected user.id to be a number");
61
+ if (typeof user.name !== "string")
62
+ throw new TypeError("Expected user.name to be a string");
63
+ return `hello ${user.name}`;
64
+ }
65
+ ```
66
+
67
+ ## How it works
68
+
69
+ 1. `ttsc` loads the program (parse + typecheck).
70
+ 2. This plugin walks `FunctionDeclaration` nodes.
71
+ 3. For each required parameter, it reads the type (syntax keywords, else checker).
72
+ 4. It synthesizes `if` / `throw` AST nodes with typescript-go `NodeFactory` and prepends them to the body.
73
+ 5. `EmitAllRaw` prints JavaScript from the mutated AST.
74
+
75
+ ## Limitations
76
+
77
+ Skipped today:
78
+
79
+ - methods, arrow functions, function expressions
80
+ - optional / default / rest / destructured parameters
81
+ - unions, arrays, tuples, classes, branded types, typia-style tags
82
+
83
+ ## Monorepo
84
+
85
+ Workspace package at `packages/di-framework-tsc`. It is listed in the CLI build/publish allowlists; `build` is a no-op (plugin.cjs + Go sidecar, not the TS5 `tsc` graph). Peers `ttsc` and TypeScript 7+ stay out of root/`@di-framework/*` core packages.
86
+
87
+ Isolated smoke fixture (installs its own ttsc + TS7):
88
+
89
+ ```bash
90
+ cd packages/di-framework-tsc/fixture
91
+ bun install
92
+ bun run smoke # ttsc --emit + check injected guards
93
+ # or: bun run smoke:dtsc
94
+ ```
95
+
96
+ ## Publish (maintainers)
97
+
98
+ ```bash
99
+ cd packages/di-framework-tsc
100
+ bun run pack:check # npm pack --dry-run
101
+ npm login # once; scoped package needs org access
102
+ npm publish # publishConfig.access=public
103
+ ```
104
+
105
+ Smoke-test a packed tarball in another project:
106
+
107
+ ```bash
108
+ npm pack
109
+ cd /path/to/other-project
110
+ npm i -D ttsc typescript /path/to/di-framework-tsc-0.1.0.tgz
111
+ # add plugins: [{ "transform": "@di-framework/tsc" }]
112
+ npx ttsc --emit
113
+ ```
114
+
115
+ ## License
116
+
117
+ MIT
package/bin/dtsc.cjs ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ function findTtscJs(startDir) {
9
+ let dir = path.resolve(startDir);
10
+ for (;;) {
11
+ const candidate = path.join(dir, 'node_modules', 'ttsc', 'lib', 'launcher', 'ttsc.js');
12
+ if (fs.existsSync(candidate)) return candidate;
13
+ const parent = path.dirname(dir);
14
+ if (parent === dir) return null;
15
+ dir = parent;
16
+ }
17
+ }
18
+
19
+ const args = process.argv.slice(2);
20
+ const ttscJs = findTtscJs(process.cwd());
21
+
22
+ let result;
23
+ if (ttscJs) {
24
+ result = spawnSync(process.execPath, [ttscJs, ...args], {
25
+ stdio: 'inherit',
26
+ env: process.env,
27
+ });
28
+ } else {
29
+ result = spawnSync('ttsc', args, {
30
+ stdio: 'inherit',
31
+ env: process.env,
32
+ shell: process.platform === 'win32',
33
+ });
34
+ if (result.error && result.error.code === 'ENOENT') {
35
+ console.error(
36
+ 'dtsc: could not find ttsc. Install it in this project (`npm i -D ttsc`) or on PATH.',
37
+ );
38
+ process.exit(1);
39
+ }
40
+ }
41
+
42
+ if (result.signal) process.kill(process.pid, result.signal);
43
+ process.exit(result.status ?? 1);
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@di-framework/tsc",
3
+ "version": "0.1.0",
4
+ "description": "ttsc transform that injects runtime parameter checks from TypeScript types (assertAll)",
5
+ "author": "@di-framework Contributors",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "type": "commonjs",
9
+ "main": "plugin.cjs",
10
+ "exports": {
11
+ ".": "./plugin.cjs"
12
+ },
13
+ "bin": {
14
+ "dtsc": "bin/dtsc.cjs"
15
+ },
16
+ "ttsc": {
17
+ "plugin": {
18
+ "transform": "@di-framework/tsc"
19
+ }
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "plugin.cjs",
24
+ "plugin",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "keywords": [
32
+ "ttsc",
33
+ "ttsc-plugin",
34
+ "tsgo",
35
+ "typescript",
36
+ "runtime-checks",
37
+ "assertAll"
38
+ ],
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/di-framework/di-framework",
42
+ "directory": "packages/di-framework-tsc"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/di-framework/di-framework/issues"
46
+ },
47
+ "homepage": "https://github.com/di-framework/di-framework#readme",
48
+ "peerDependencies": {
49
+ "ttsc": ">=0.25.0",
50
+ "typescript": ">=7.0.0"
51
+ },
52
+ "peerDependenciesMeta": {
53
+ "ttsc": {
54
+ "optional": false
55
+ },
56
+ "typescript": {
57
+ "optional": false
58
+ }
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "scripts": {
64
+ "build": "bun -e \"console.log('@di-framework/tsc: plugin package (plugin.cjs + Go sidecar), no TS5 compile step')\"",
65
+ "pack:check": "npm pack --dry-run",
66
+ "test": "bun test",
67
+ "smoke": "bun run --cwd fixture smoke"
68
+ }
69
+ }
package/plugin/go.mod ADDED
@@ -0,0 +1,10 @@
1
+ module github.com/di-framework/tsc
2
+
3
+ go 1.26
4
+
5
+ require (
6
+ github.com/samchon/ttsc/packages/ttsc v0.0.0
7
+ github.com/microsoft/typescript-go/shim/ast v0.0.0
8
+ github.com/microsoft/typescript-go/shim/checker v0.0.0
9
+ github.com/microsoft/typescript-go/shim/printer v0.0.0
10
+ )
package/plugin/main.go ADDED
@@ -0,0 +1,457 @@
1
+ // @di-framework/tsc injects runtime typeof/shape checks into function
2
+ // bodies from parameter types (assertAll). Transparent: no source markers.
3
+ // Checks are synthesized as AST nodes (NodeFactory), not text edits.
4
+ package main
5
+
6
+ import (
7
+ "encoding/json"
8
+ "flag"
9
+ "fmt"
10
+ "os"
11
+ "path/filepath"
12
+ "strings"
13
+
14
+ shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ shimchecker "github.com/microsoft/typescript-go/shim/checker"
16
+ shimprinter "github.com/microsoft/typescript-go/shim/printer"
17
+
18
+ "github.com/samchon/ttsc/packages/ttsc/driver"
19
+ )
20
+
21
+ type options struct {
22
+ cwd string
23
+ emit bool
24
+ noEmit bool
25
+ outDir string
26
+ tsconfig string
27
+ }
28
+
29
+ type transformResult struct {
30
+ Diagnostics []any `json:"diagnostics,omitempty"`
31
+ TypeScript map[string]string `json:"typescript"`
32
+ }
33
+
34
+ func main() {
35
+ os.Exit(run(os.Args[1:]))
36
+ }
37
+
38
+ func run(args []string) int {
39
+ if len(args) == 0 {
40
+ fmt.Fprintln(os.Stderr, "@di-framework/tsc: command required")
41
+ return 2
42
+ }
43
+ switch args[0] {
44
+ case "version", "-v", "--version":
45
+ fmt.Fprintln(os.Stdout, "@di-framework/tsc 0.1.0")
46
+ return 0
47
+ case "check":
48
+ return 0
49
+ case "transform":
50
+ return runTransform(args[1:])
51
+ case "build":
52
+ return runBuild(args[1:])
53
+ default:
54
+ fmt.Fprintf(os.Stderr, "@di-framework/tsc: unknown command %q\n", args[0])
55
+ return 2
56
+ }
57
+ }
58
+
59
+ func runTransform(args []string) int {
60
+ opts, ok := parseOptions("transform", args)
61
+ if !ok {
62
+ return 2
63
+ }
64
+ prog, ok := loadProgram(opts)
65
+ if !ok {
66
+ return 2
67
+ }
68
+ defer prog.Close()
69
+
70
+ injectProgram(prog)
71
+
72
+ // Host treats typescript[path] as opaque text — print the mutated AST.
73
+ printer := shimprinter.NewPrinter(
74
+ shimprinter.PrinterOptions{},
75
+ shimprinter.PrintHandlers{},
76
+ nil,
77
+ )
78
+ out := transformResult{TypeScript: map[string]string{}}
79
+ for _, file := range prog.SourceFiles() {
80
+ if file == nil || file.IsDeclarationFile {
81
+ continue
82
+ }
83
+ out.TypeScript[outputKey(opts.cwd, file.FileName())] = shimprinter.EmitSourceFile(printer, file)
84
+ }
85
+ data, err := json.Marshal(out)
86
+ if err != nil {
87
+ fmt.Fprintf(os.Stderr, "@di-framework/tsc: marshal failed: %v\n", err)
88
+ return 3
89
+ }
90
+ fmt.Fprintln(os.Stdout, string(data))
91
+ return 0
92
+ }
93
+
94
+ func runBuild(args []string) int {
95
+ opts, ok := parseOptions("build", args)
96
+ if !ok {
97
+ return 2
98
+ }
99
+ prog, ok := loadProgram(opts)
100
+ if !ok {
101
+ return 2
102
+ }
103
+ defer prog.Close()
104
+
105
+ injectProgram(prog)
106
+ if opts.noEmit {
107
+ return 0
108
+ }
109
+ _, emitDiags, err := prog.EmitAllRaw(nil)
110
+ if err != nil {
111
+ fmt.Fprintf(os.Stderr, "@di-framework/tsc: emit failed: %v\n", err)
112
+ return 3
113
+ }
114
+ for _, d := range emitDiags {
115
+ fmt.Fprintln(os.Stderr, d.String())
116
+ }
117
+ if len(emitDiags) > 0 {
118
+ return 2
119
+ }
120
+ return 0
121
+ }
122
+
123
+ func loadProgram(opts options) (*driver.Program, bool) {
124
+ prog, parseDiags, err := driver.LoadProgram(opts.cwd, opts.tsconfig, driver.LoadProgramOptions{
125
+ ForceEmit: opts.emit,
126
+ ForceNoEmit: opts.noEmit,
127
+ OutDir: opts.outDir,
128
+ })
129
+ if err != nil {
130
+ fmt.Fprintf(os.Stderr, "@di-framework/tsc: %v\n", err)
131
+ return nil, false
132
+ }
133
+ if len(parseDiags) > 0 {
134
+ driver.WritePrettyDiagnostics(os.Stderr, parseDiags, opts.cwd)
135
+ prog.Close()
136
+ return nil, false
137
+ }
138
+ if diags := prog.Diagnostics(); len(diags) > 0 {
139
+ driver.WritePrettyDiagnostics(os.Stderr, diags, opts.cwd)
140
+ prog.Close()
141
+ return nil, false
142
+ }
143
+ return prog, true
144
+ }
145
+
146
+ func injectProgram(prog *driver.Program) {
147
+ factory := shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
148
+ for _, file := range prog.SourceFiles() {
149
+ if file == nil || file.IsDeclarationFile {
150
+ continue
151
+ }
152
+ injectFile(factory, file, prog.Checker)
153
+ }
154
+ }
155
+
156
+ func injectFile(factory *shimast.NodeFactory, file *shimast.SourceFile, checker *shimchecker.Checker) {
157
+ var walk func(node *shimast.Node)
158
+ walk = func(node *shimast.Node) {
159
+ if node == nil {
160
+ return
161
+ }
162
+ if node.Kind == shimast.KindFunctionDeclaration {
163
+ injectFunction(factory, checker, node)
164
+ }
165
+ node.ForEachChild(func(child *shimast.Node) bool {
166
+ walk(child)
167
+ return false
168
+ })
169
+ }
170
+ if file.Statements != nil {
171
+ for _, stmt := range file.Statements.Nodes {
172
+ walk(stmt)
173
+ }
174
+ }
175
+ }
176
+
177
+ func injectFunction(factory *shimast.NodeFactory, checker *shimchecker.Checker, node *shimast.Node) {
178
+ fn := node.AsFunctionDeclaration()
179
+ if fn == nil || fn.Body == nil || fn.Parameters == nil {
180
+ return
181
+ }
182
+ block := fn.Body.AsBlock()
183
+ if block == nil || block.Statements == nil {
184
+ return
185
+ }
186
+
187
+ var checks []*shimast.Node
188
+ for _, param := range fn.Parameters.Nodes {
189
+ if param == nil || param.Kind != shimast.KindParameter {
190
+ continue
191
+ }
192
+ p := param.AsParameterDeclaration()
193
+ if p == nil || p.Name() == nil || p.Name().Kind != shimast.KindIdentifier {
194
+ continue
195
+ }
196
+ if p.DotDotDotToken != nil || p.QuestionToken != nil || p.Initializer != nil {
197
+ continue
198
+ }
199
+ name := p.Name().Text()
200
+ checks = append(checks, checksForParam(factory, checker, param, name)...)
201
+ }
202
+ if len(checks) == 0 {
203
+ return
204
+ }
205
+
206
+ // Structural prepend — printer walks Statements.Nodes directly.
207
+ out := make([]*shimast.Node, 0, len(checks)+len(block.Statements.Nodes))
208
+ out = append(out, checks...)
209
+ out = append(out, block.Statements.Nodes...)
210
+ block.Statements.Nodes = out
211
+ // Emit walks parents; factory nodes start detached.
212
+ shimast.SetParentInChildren(fn.Body)
213
+ }
214
+
215
+ func checksForParam(
216
+ factory *shimast.NodeFactory,
217
+ checker *shimchecker.Checker,
218
+ param *shimast.Node,
219
+ path string,
220
+ ) []*shimast.Node {
221
+ p := param.AsParameterDeclaration()
222
+ if p != nil && p.Type != nil {
223
+ if stmts := stmtsFromTypeNode(factory, path, p.Type); len(stmts) > 0 {
224
+ return stmts
225
+ }
226
+ }
227
+ if checker == nil {
228
+ return nil
229
+ }
230
+ t := checker.GetTypeAtLocation(param)
231
+ if t == nil {
232
+ return nil
233
+ }
234
+ return stmtsFromCheckerType(factory, checker, path, t)
235
+ }
236
+
237
+ func stmtsFromTypeNode(
238
+ factory *shimast.NodeFactory,
239
+ path string,
240
+ typeNode *shimast.Node,
241
+ ) []*shimast.Node {
242
+ if typeNode == nil {
243
+ return nil
244
+ }
245
+ switch typeNode.Kind {
246
+ case shimast.KindStringKeyword:
247
+ return []*shimast.Node{typeofCheck(factory, path, "string")}
248
+ case shimast.KindNumberKeyword:
249
+ return []*shimast.Node{typeofCheck(factory, path, "number")}
250
+ case shimast.KindBooleanKeyword:
251
+ return []*shimast.Node{typeofCheck(factory, path, "boolean")}
252
+ case shimast.KindBigIntKeyword:
253
+ return []*shimast.Node{typeofCheck(factory, path, "bigint")}
254
+ default:
255
+ return nil
256
+ }
257
+ }
258
+
259
+ func stmtsFromCheckerType(
260
+ factory *shimast.NodeFactory,
261
+ checker *shimchecker.Checker,
262
+ path string,
263
+ t *shimchecker.Type,
264
+ ) []*shimast.Node {
265
+ flags := t.Flags()
266
+ switch {
267
+ case flags&shimchecker.TypeFlagsString != 0:
268
+ return []*shimast.Node{typeofCheck(factory, path, "string")}
269
+ case flags&shimchecker.TypeFlagsNumber != 0:
270
+ return []*shimast.Node{typeofCheck(factory, path, "number")}
271
+ case flags&shimchecker.TypeFlagsBoolean != 0:
272
+ return []*shimast.Node{typeofCheck(factory, path, "boolean")}
273
+ case flags&shimchecker.TypeFlagsBigInt != 0:
274
+ return []*shimast.Node{typeofCheck(factory, path, "bigint")}
275
+ case flags&shimchecker.TypeFlagsObject != 0:
276
+ props := shimchecker.Checker_getApparentProperties(checker, t)
277
+ if len(props) == 0 {
278
+ return nil
279
+ }
280
+ out := []*shimast.Node{objectCheck(factory, path)}
281
+ for _, sym := range props {
282
+ if sym == nil {
283
+ continue
284
+ }
285
+ propName := sym.Name
286
+ if propName == "" || strings.HasPrefix(propName, "__") {
287
+ continue
288
+ }
289
+ if sym.Flags&shimast.SymbolFlagsOptional != 0 {
290
+ continue
291
+ }
292
+ propType := shimchecker.Checker_getTypeOfPropertyOfType(checker, t, propName)
293
+ if propType == nil {
294
+ continue
295
+ }
296
+ out = append(out, stmtsFromCheckerType(
297
+ factory,
298
+ checker,
299
+ path+"."+propName,
300
+ propType,
301
+ )...)
302
+ }
303
+ return out
304
+ default:
305
+ return nil
306
+ }
307
+ }
308
+
309
+ // pathExpr builds a fresh expression tree for a dotted path (e.g. "user.id").
310
+ // Each call allocates new nodes — AST nodes are not shareable across parents.
311
+ func pathExpr(factory *shimast.NodeFactory, path string) *shimast.Expression {
312
+ parts := strings.Split(path, ".")
313
+ var expr *shimast.Expression = factory.NewIdentifier(parts[0])
314
+ for _, part := range parts[1:] {
315
+ expr = factory.NewPropertyAccessExpression(
316
+ expr,
317
+ nil,
318
+ factory.NewIdentifier(part),
319
+ 0,
320
+ )
321
+ }
322
+ return expr
323
+ }
324
+
325
+ func typeofCheck(factory *shimast.NodeFactory, path, kind string) *shimast.Node {
326
+ cond := binary(
327
+ factory,
328
+ factory.NewTypeOfExpression(pathExpr(factory, path)),
329
+ shimast.KindExclamationEqualsEqualsToken,
330
+ factory.NewStringLiteral(kind, shimast.TokenFlagsNone),
331
+ )
332
+ return throwIf(factory, cond, "Expected "+path+" to be a "+kind)
333
+ }
334
+
335
+ func objectCheck(factory *shimast.NodeFactory, path string) *shimast.Node {
336
+ notObject := binary(
337
+ factory,
338
+ factory.NewTypeOfExpression(pathExpr(factory, path)),
339
+ shimast.KindExclamationEqualsEqualsToken,
340
+ factory.NewStringLiteral("object", shimast.TokenFlagsNone),
341
+ )
342
+ isNull := binary(
343
+ factory,
344
+ pathExpr(factory, path),
345
+ shimast.KindEqualsEqualsEqualsToken,
346
+ factory.NewToken(shimast.KindNullKeyword),
347
+ )
348
+ cond := binary(factory, notObject, shimast.KindBarBarToken, isNull)
349
+ return throwIf(factory, cond, "Expected "+path+" to be an object")
350
+ }
351
+
352
+ func throwIf(factory *shimast.NodeFactory, condition *shimast.Expression, message string) *shimast.Node {
353
+ msg := factory.NewStringLiteral(message, shimast.TokenFlagsNone)
354
+ ctor := factory.NewIdentifier("TypeError")
355
+ args := factory.NewNodeList([]*shimast.Node{msg})
356
+ newExpr := factory.NewNewExpression(ctor, nil, args)
357
+ throwStmt := factory.NewThrowStatement(newExpr)
358
+ return factory.NewIfStatement(condition, throwStmt, nil)
359
+ }
360
+
361
+ func binary(
362
+ factory *shimast.NodeFactory,
363
+ left *shimast.Expression,
364
+ op shimast.Kind,
365
+ right *shimast.Expression,
366
+ ) *shimast.Expression {
367
+ return factory.NewBinaryExpression(
368
+ nil,
369
+ left,
370
+ nil,
371
+ factory.NewToken(op),
372
+ right,
373
+ )
374
+ }
375
+
376
+ func parseOptions(command string, args []string) (options, bool) {
377
+ fs := flag.NewFlagSet(command, flag.ContinueOnError)
378
+ fs.SetOutput(os.Stderr)
379
+ cwd := fs.String("cwd", "", "project directory")
380
+ emit := fs.Bool("emit", false, "force emit")
381
+ noEmit := fs.Bool("noEmit", false, "force no emit")
382
+ outDir := fs.String("outDir", "", "emit directory override")
383
+ tsconfig := fs.String("tsconfig", "tsconfig.json", "project tsconfig")
384
+ _ = fs.String("plugins-json", "", "ttsc plugin metadata")
385
+ _ = fs.Bool("quiet", true, "suppress summary")
386
+ _ = fs.Bool("verbose", false, "print summary")
387
+ if err := fs.Parse(filterHostArgs(args, fs)); err != nil {
388
+ return options{}, false
389
+ }
390
+ root := *cwd
391
+ if root == "" {
392
+ var err error
393
+ root, err = os.Getwd()
394
+ if err != nil {
395
+ fmt.Fprintln(os.Stderr, err)
396
+ return options{}, false
397
+ }
398
+ }
399
+ if !filepath.IsAbs(root) {
400
+ abs, err := filepath.Abs(root)
401
+ if err != nil {
402
+ fmt.Fprintln(os.Stderr, err)
403
+ return options{}, false
404
+ }
405
+ root = abs
406
+ }
407
+ cfg := *tsconfig
408
+ if !filepath.IsAbs(cfg) {
409
+ cfg = filepath.Join(root, cfg)
410
+ }
411
+ return options{
412
+ cwd: filepath.Clean(root),
413
+ emit: *emit,
414
+ noEmit: *noEmit,
415
+ outDir: *outDir,
416
+ tsconfig: cfg,
417
+ }, true
418
+ }
419
+
420
+ var valueFlags = map[string]bool{
421
+ "--cwd": true,
422
+ "--tsconfig": true,
423
+ "--outDir": true,
424
+ "--plugins-json": true,
425
+ }
426
+
427
+ func filterHostArgs(args []string, fs *flag.FlagSet) []string {
428
+ out := make([]string, 0, len(args))
429
+ for i := 0; i < len(args); i++ {
430
+ a := args[i]
431
+ if !strings.HasPrefix(a, "-") {
432
+ out = append(out, a)
433
+ continue
434
+ }
435
+ name, _, hasEq := strings.Cut(a, "=")
436
+ if fs.Lookup(strings.TrimLeft(name, "-")) != nil {
437
+ out = append(out, a)
438
+ if !hasEq && valueFlags[name] && i+1 < len(args) {
439
+ out = append(out, args[i+1])
440
+ i++
441
+ }
442
+ continue
443
+ }
444
+ if !hasEq && valueFlags[name] && i+1 < len(args) {
445
+ i++
446
+ }
447
+ }
448
+ return out
449
+ }
450
+
451
+ func outputKey(cwd, fileName string) string {
452
+ rel, err := filepath.Rel(cwd, fileName)
453
+ if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
454
+ return filepath.ToSlash(fileName)
455
+ }
456
+ return filepath.ToSlash(rel)
457
+ }
package/plugin.cjs ADDED
@@ -0,0 +1,9 @@
1
+ const path = require('node:path');
2
+
3
+ module.exports = function createDiFrameworkTscPlugin(context) {
4
+ return {
5
+ name: '@di-framework/tsc',
6
+ source: path.resolve(context.dirname, 'plugin'),
7
+ stage: 'transform',
8
+ };
9
+ };