freezedts 0.12.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 +21 -0
- package/README.md +319 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +121 -0
- package/dist/cli.js.map +1 -0
- package/dist/excluded-dirs.d.ts +2 -0
- package/dist/excluded-dirs.d.ts.map +1 -0
- package/dist/excluded-dirs.js +2 -0
- package/dist/excluded-dirs.js.map +1 -0
- package/dist/generator/config.d.ts +18 -0
- package/dist/generator/config.d.ts.map +1 -0
- package/dist/generator/config.js +40 -0
- package/dist/generator/config.js.map +1 -0
- package/dist/generator/emitter.d.ts +3 -0
- package/dist/generator/emitter.d.ts.map +1 -0
- package/dist/generator/emitter.js +170 -0
- package/dist/generator/emitter.js.map +1 -0
- package/dist/generator/generator.d.ts +8 -0
- package/dist/generator/generator.d.ts.map +1 -0
- package/dist/generator/generator.js +188 -0
- package/dist/generator/generator.js.map +1 -0
- package/dist/generator/parser.d.ts +32 -0
- package/dist/generator/parser.d.ts.map +1 -0
- package/dist/generator/parser.js +154 -0
- package/dist/generator/parser.js.map +1 -0
- package/dist/generator/watcher.d.ts +11 -0
- package/dist/generator/watcher.d.ts.map +1 -0
- package/dist/generator/watcher.js +45 -0
- package/dist/generator/watcher.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/copy.d.ts +3 -0
- package/dist/runtime/copy.d.ts.map +1 -0
- package/dist/runtime/copy.js +43 -0
- package/dist/runtime/copy.js.map +1 -0
- package/dist/runtime/deepEqual.d.ts +2 -0
- package/dist/runtime/deepEqual.d.ts.map +1 -0
- package/dist/runtime/deepEqual.js +43 -0
- package/dist/runtime/deepEqual.js.map +1 -0
- package/dist/runtime/deepFreeze.d.ts +2 -0
- package/dist/runtime/deepFreeze.d.ts.map +1 -0
- package/dist/runtime/deepFreeze.js +35 -0
- package/dist/runtime/deepFreeze.js.map +1 -0
- package/dist/runtime/freezed.d.ts +16 -0
- package/dist/runtime/freezed.d.ts.map +1 -0
- package/dist/runtime/freezed.js +11 -0
- package/dist/runtime/freezed.js.map +1 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +6 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Gorman
|
|
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,319 @@
|
|
|
1
|
+
# freezedts
|
|
2
|
+
|
|
3
|
+
Immutable class generation for TypeScript — deep copying, value equality, and runtime immutability via decorators and code generation.
|
|
4
|
+
|
|
5
|
+
A TypeScript port of Dart's [freezed](https://github.com/rrousselGit/freezed) package by Remi Rousselet.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Motivation](#motivation)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Running the Generator](#running-the-generator)
|
|
12
|
+
- [Creating Classes](#creating-classes)
|
|
13
|
+
- [Basic Usage](#basic-usage)
|
|
14
|
+
- [Field Configuration](#field-configuration)
|
|
15
|
+
- [Nested Freezed Types](#nested-freezed-types)
|
|
16
|
+
- [Collections](#collections)
|
|
17
|
+
- [Deep Copy](#deep-copy)
|
|
18
|
+
- [Deep Equality](#deep-equality)
|
|
19
|
+
- [Configuration](#configuration)
|
|
20
|
+
- [Per-Class Configuration](#per-class-configuration)
|
|
21
|
+
- [Project-Wide Configuration](#project-wide-configuration)
|
|
22
|
+
- [Resolution Order](#resolution-order)
|
|
23
|
+
- [Building the Library](#building-the-library)
|
|
24
|
+
|
|
25
|
+
## Motivation
|
|
26
|
+
|
|
27
|
+
TypeScript has no native support for immutable classes with named parameters. Achieving truly immutable data classes requires significant boilerplate:
|
|
28
|
+
|
|
29
|
+
- `readonly` on every property
|
|
30
|
+
- `Object.freeze()` in every constructor
|
|
31
|
+
- Manual `copyWith` methods for creating modified copies
|
|
32
|
+
- Manual `equals` methods for structural comparison
|
|
33
|
+
- Recursive freezing of nested collections
|
|
34
|
+
|
|
35
|
+
freezedts eliminates this boilerplate. You write a class with a decorator, and the generator produces an abstract base class that handles immutability, deep copying, value equality, `toString()`, and collection freezing.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// You write this:
|
|
39
|
+
@freezed()
|
|
40
|
+
class Person extends $Person {
|
|
41
|
+
constructor(params: { firstName: string; lastName: string; age: number }) {
|
|
42
|
+
super(params);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
The generator produces `$Person` with:
|
|
47
|
+
- readonly properties
|
|
48
|
+
- `Object.freeze(this)` in the constructor
|
|
49
|
+
- `person.with({ age: 31 })` for copying
|
|
50
|
+
- `person.equals(other)` for deep structural comparison
|
|
51
|
+
- `person.toString()` → `"Person(firstName: John, lastName: Smith, age: 30)"`
|
|
52
|
+
- Recursive freezing of arrays, Maps, and Sets
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install freezedts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`freezedts` must be a runtime dependency. It has a small runtime footprint and
|
|
62
|
+
no transient runtime dependencies.
|
|
63
|
+
|
|
64
|
+
**Requirements:** TypeScript 6, ESM, TC39 stage 3 decorators.
|
|
65
|
+
|
|
66
|
+
## Running the Generator
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Generate .freezed.ts files for all source files in the current directory
|
|
70
|
+
npx freezedts
|
|
71
|
+
|
|
72
|
+
# Generate for a specific directory
|
|
73
|
+
npx freezedts src
|
|
74
|
+
|
|
75
|
+
# Watch mode — regenerate on file changes
|
|
76
|
+
npx freezedts --watch
|
|
77
|
+
npx freezedts -w src
|
|
78
|
+
|
|
79
|
+
# Use a custom config file
|
|
80
|
+
npx freezedts --config path/to/freezedts.config.yaml
|
|
81
|
+
npx freezedts -c custom.yaml -w src
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The generator scans for `.ts` files containing `@freezed()` classes and produces `.freezed.ts` files alongside them.
|
|
85
|
+
|
|
86
|
+
Only changed files are regenerated (mtime-based).
|
|
87
|
+
|
|
88
|
+
## Creating Classes
|
|
89
|
+
|
|
90
|
+
### Basic Usage
|
|
91
|
+
|
|
92
|
+
1. Create your source file (e.g., `person.ts`):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { freezed } from 'freezedts';
|
|
96
|
+
import { $Person } from './person.freezed.ts';
|
|
97
|
+
|
|
98
|
+
@freezed()
|
|
99
|
+
class Person extends $Person {
|
|
100
|
+
constructor(params: { firstName: string; lastName: string; age: number }) {
|
|
101
|
+
super(params);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
2. Run the generator:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx freezedts
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
3. The generated `person.freezed.ts` provides an abstract base class `$Person` with `readonly` properties, `Object.freeze(this)`, `with()`, `equals()`, and `toString()`.
|
|
113
|
+
|
|
114
|
+
Where a source file contains multiple `@freezed` classes, they will be generated to the same file.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { freezed } from 'freezedts';
|
|
118
|
+
import { $Person, $Address } from './person.freezed.ts';
|
|
119
|
+
|
|
120
|
+
@freezed()
|
|
121
|
+
class Person extends $Person {
|
|
122
|
+
...
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@freezed()
|
|
126
|
+
class Address extends $Address {
|
|
127
|
+
...
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
### Field Configuration
|
|
133
|
+
|
|
134
|
+
Configure defaults and validation in the `@freezed()` decorator's `fields` option:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
@freezed({
|
|
138
|
+
fields: {
|
|
139
|
+
port: {
|
|
140
|
+
default: 3000,
|
|
141
|
+
assert: (v: number) => v > 0 && v < 65536,
|
|
142
|
+
message: 'port out of range',
|
|
143
|
+
},
|
|
144
|
+
host: { default: 'localhost' },
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
class ServerConfig extends $ServerConfig {
|
|
148
|
+
constructor(params: { name: string; host?: string; port?: number }) {
|
|
149
|
+
super(params);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const config = new ServerConfig({ name: 'api' });
|
|
154
|
+
config.host; // 'localhost'
|
|
155
|
+
config.port; // 3000
|
|
156
|
+
|
|
157
|
+
new ServerConfig({ name: 'api', port: -1 }); // throws: "port out of range"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Field config options:
|
|
161
|
+
|
|
162
|
+
| Option | Type | Description |
|
|
163
|
+
|--------|------|-------------|
|
|
164
|
+
| `default` | `unknown` | Default value when the parameter is `undefined` |
|
|
165
|
+
| `assert` | `(value) => boolean` | Validation function run at construction time |
|
|
166
|
+
| `message` | <code>string | undefined</code> | An optional error message when the assertion fails. If omitted, a basic error message will be generated. |
|
|
167
|
+
|
|
168
|
+
### Collections
|
|
169
|
+
|
|
170
|
+
Arrays, Maps, and Sets are recursively frozen at construction time. Mutation attempts throw at runtime:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
@freezed()
|
|
174
|
+
class Team extends $Team {
|
|
175
|
+
constructor(params: { name: string; members: string[]; scores: number[] }) {
|
|
176
|
+
super(params);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const team = new Team({ name: 'Alpha', members: ['Alice', 'Bob'], scores: [10, 20] });
|
|
181
|
+
team.members.push('Charlie'); // throws TypeError
|
|
182
|
+
team.members[0] = 'Zara'; // throws TypeError
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Deep Copy
|
|
186
|
+
|
|
187
|
+
The `with()` method creates a new frozen instance with selective property overrides:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const alice = new Person({ firstName: 'Alice', lastName: 'Smith', age: 30 });
|
|
191
|
+
const bob = alice.with({ firstName: 'Bob' });
|
|
192
|
+
// bob → Person(firstName: Bob, lastName: Smith, age: 30)
|
|
193
|
+
// alice is unchanged
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
For nested `@freezed` types, `with` supports proxy-chained deep copies:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
@freezed()
|
|
200
|
+
class Assistant extends $Assistant {
|
|
201
|
+
constructor(params: { name: string }) { super(params); }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@freezed()
|
|
205
|
+
class Director extends $Director {
|
|
206
|
+
constructor(params: { name: string; assistant: Assistant }) { super(params); }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@freezed()
|
|
210
|
+
class Company extends $Company {
|
|
211
|
+
constructor(params: { name: string; director: Director }) { super(params); }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const co = new Company({
|
|
215
|
+
name: 'Acme',
|
|
216
|
+
director: new Director({
|
|
217
|
+
name: 'Jane',
|
|
218
|
+
assistant: new Assistant({ name: 'John' }),
|
|
219
|
+
}),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Shallow copy
|
|
223
|
+
co.with({ name: 'NewCo' });
|
|
224
|
+
|
|
225
|
+
// Deep copy — update a nested freezed property
|
|
226
|
+
co.with.director({ name: 'Larry' });
|
|
227
|
+
co.with.director.assistant({ name: 'Sue' });
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
## Deep Equality
|
|
232
|
+
|
|
233
|
+
The `equals()` method performs structural comparison:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
const a = new Person({ firstName: 'Alice', lastName: 'Smith', age: 30 });
|
|
237
|
+
const b = new Person({ firstName: 'Alice', lastName: 'Smith', age: 30 });
|
|
238
|
+
|
|
239
|
+
a === b; // false (different instances)
|
|
240
|
+
a.equals(b); // true (same structure)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Configure equality mode per class:
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
@freezed({ equality: 'deep' }) // default — recursive structural comparison
|
|
247
|
+
class DeepPerson extends $DeepPerson { ... }
|
|
248
|
+
|
|
249
|
+
@freezed({ equality: 'shallow' }) // === for primitives, .equals() for nested freezed types
|
|
250
|
+
class ShallowPerson extends $ShallowPerson { ... }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Configuration
|
|
254
|
+
|
|
255
|
+
### Per-Class Configuration
|
|
256
|
+
|
|
257
|
+
Disable generation of specific methods via the `@freezed()` decorator:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
@freezed({ copyWith: false }) // skip with() generation
|
|
261
|
+
@freezed({ equal: false }) // skip equals() generation
|
|
262
|
+
@freezed({ toString: false }) // skip toString() generation
|
|
263
|
+
@freezed({ copyWith: false, equal: false, toString: false }) // only immutability
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Project-Wide Configuration
|
|
267
|
+
|
|
268
|
+
Create a `freezedts.config.yaml` in your project root:
|
|
269
|
+
|
|
270
|
+
```yaml
|
|
271
|
+
freezed:
|
|
272
|
+
options:
|
|
273
|
+
# Format generated .freezed.ts files (can slow down generation)
|
|
274
|
+
format: true
|
|
275
|
+
|
|
276
|
+
# Disable with() generation for the entire project
|
|
277
|
+
copyWith: false
|
|
278
|
+
|
|
279
|
+
# Disable equals() generation for the entire project
|
|
280
|
+
equal: false
|
|
281
|
+
|
|
282
|
+
# Disable toString() generation for the entire project
|
|
283
|
+
toString: false
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
All options default to `true` (enabled) except `format` which defaults to `false`.
|
|
287
|
+
|
|
288
|
+
### Resolution Order
|
|
289
|
+
|
|
290
|
+
Per-class `@freezed()` options override project-wide `freezedts.config.yaml` defaults. If neither specifies a value, the built-in default applies (all features enabled).
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
per-class @freezed() → freezedts.config.yaml → built-in defaults
|
|
294
|
+
(highest priority) (lowest priority)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Building the Library
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Install dependencies
|
|
301
|
+
npm install
|
|
302
|
+
|
|
303
|
+
# Build TypeScript to dist/
|
|
304
|
+
npm run build
|
|
305
|
+
|
|
306
|
+
# Run tests
|
|
307
|
+
npm test
|
|
308
|
+
|
|
309
|
+
# Run the generator on this project's source files
|
|
310
|
+
npm run generate
|
|
311
|
+
|
|
312
|
+
# Watch mode for tests
|
|
313
|
+
npm run test:watch
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The project uses:
|
|
317
|
+
- **TypeScript 6** with ES2022 target and Node16 module resolution
|
|
318
|
+
- **bun:test** for testing
|
|
319
|
+
- **ts-morph** for AST parsing and code generation
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export declare function resolveSourceFiles(dir: string): string[];
|
|
3
|
+
export interface CliArgs {
|
|
4
|
+
watch: boolean;
|
|
5
|
+
dir: string;
|
|
6
|
+
config?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function filterChangedFiles(files: string[]): {
|
|
9
|
+
changed: string[];
|
|
10
|
+
skipped: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function parseArgs(argv: string[]): CliArgs;
|
|
13
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAUA,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBxD;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAkB1F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAkBjD"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { generate } from './generator/generator.js';
|
|
5
|
+
import { createWatcher } from './generator/watcher.js';
|
|
6
|
+
import { loadConfig } from './generator/config.js';
|
|
7
|
+
import { EXCLUDED_DIRS } from './excluded-dirs.js';
|
|
8
|
+
export function resolveSourceFiles(dir) {
|
|
9
|
+
const results = [];
|
|
10
|
+
function walk(currentDir) {
|
|
11
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (entry.isDirectory()) {
|
|
14
|
+
if (!EXCLUDED_DIRS.has(entry.name)) {
|
|
15
|
+
walk(path.join(currentDir, entry.name));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else if (entry.isFile() &&
|
|
19
|
+
entry.name.endsWith('.ts') &&
|
|
20
|
+
!entry.name.endsWith('.freezed.ts') &&
|
|
21
|
+
!entry.name.endsWith('.test.ts') &&
|
|
22
|
+
!entry.name.endsWith('.d.ts')) {
|
|
23
|
+
results.push(path.join(currentDir, entry.name));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
walk(dir);
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
export function filterChangedFiles(files) {
|
|
31
|
+
const changed = [];
|
|
32
|
+
let skipped = 0;
|
|
33
|
+
for (const file of files) {
|
|
34
|
+
const outputPath = file.replace(/\.ts$/, '.freezed.ts');
|
|
35
|
+
try {
|
|
36
|
+
const sourceMtime = fs.statSync(file).mtimeMs;
|
|
37
|
+
const outputMtime = fs.statSync(outputPath).mtimeMs;
|
|
38
|
+
if (outputMtime >= sourceMtime) {
|
|
39
|
+
skipped++;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Output file doesn't exist — needs generation
|
|
45
|
+
}
|
|
46
|
+
changed.push(file);
|
|
47
|
+
}
|
|
48
|
+
return { changed, skipped };
|
|
49
|
+
}
|
|
50
|
+
export function parseArgs(argv) {
|
|
51
|
+
const args = argv.slice(2);
|
|
52
|
+
let watch = false;
|
|
53
|
+
let dir = '.';
|
|
54
|
+
let config;
|
|
55
|
+
for (let i = 0; i < args.length; i++) {
|
|
56
|
+
const arg = args[i];
|
|
57
|
+
if (arg === '--watch' || arg === '-w') {
|
|
58
|
+
watch = true;
|
|
59
|
+
}
|
|
60
|
+
else if ((arg === '--config' || arg === '-c') && i + 1 < args.length) {
|
|
61
|
+
config = args[++i];
|
|
62
|
+
}
|
|
63
|
+
else if (!arg.startsWith('-')) {
|
|
64
|
+
dir = arg;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { watch, dir, config };
|
|
68
|
+
}
|
|
69
|
+
function main() {
|
|
70
|
+
const args = parseArgs(process.argv);
|
|
71
|
+
const resolvedDir = path.resolve(args.dir);
|
|
72
|
+
const configPath = args.config ?? path.join(resolvedDir, 'freezedts.config.yaml');
|
|
73
|
+
const config = loadConfig(configPath);
|
|
74
|
+
console.log(`freezedts: scanning ${resolvedDir}`);
|
|
75
|
+
const allFiles = resolveSourceFiles(resolvedDir);
|
|
76
|
+
console.log(`freezedts: found ${allFiles.length} source file(s)`);
|
|
77
|
+
const { changed, skipped } = filterChangedFiles(allFiles);
|
|
78
|
+
const result = generate(changed, config);
|
|
79
|
+
if (skipped > 0) {
|
|
80
|
+
console.log(`freezedts: generated ${result.filesWritten} .freezed.ts file(s), ${skipped} unchanged`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(`freezedts: generated ${result.filesWritten} .freezed.ts file(s)`);
|
|
84
|
+
}
|
|
85
|
+
if (result.warnings.length > 0) {
|
|
86
|
+
result.warnings.forEach((w) => console.warn(` warning: ${w}`));
|
|
87
|
+
}
|
|
88
|
+
if (result.errors.length > 0) {
|
|
89
|
+
console.error('freezedts: errors:');
|
|
90
|
+
result.errors.forEach((e) => console.error(` ${e}`));
|
|
91
|
+
if (!args.watch)
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (args.watch) {
|
|
95
|
+
console.log('freezedts: watching for changes... (press Ctrl+C to stop)');
|
|
96
|
+
createWatcher({
|
|
97
|
+
dir: resolvedDir,
|
|
98
|
+
onChange: (changedFiles) => {
|
|
99
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
100
|
+
const watchResult = generate(changedFiles, config);
|
|
101
|
+
if (watchResult.filesWritten > 0) {
|
|
102
|
+
for (const f of changedFiles) {
|
|
103
|
+
const rel = path.relative(resolvedDir, f);
|
|
104
|
+
console.log(`[${timestamp}] ${rel} → regenerated`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (watchResult.warnings.length > 0) {
|
|
108
|
+
watchResult.warnings.forEach((w) => console.warn(` warning: ${w}`));
|
|
109
|
+
}
|
|
110
|
+
if (watchResult.errors.length > 0) {
|
|
111
|
+
watchResult.errors.forEach((e) => console.error(` error: ${e}`));
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Only run when executed directly, not when imported
|
|
118
|
+
if (import.meta.main) {
|
|
119
|
+
main();
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excluded-dirs.d.ts","sourceRoot":"","sources":["../src/excluded-dirs.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,aAA4C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"excluded-dirs.js","sourceRoot":"","sources":["../src/excluded-dirs.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface ResolvedConfig {
|
|
2
|
+
format: boolean;
|
|
3
|
+
copyWith: boolean;
|
|
4
|
+
equal: boolean;
|
|
5
|
+
toString: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULTS: ResolvedConfig;
|
|
8
|
+
export declare function loadConfig(configPath: string): ResolvedConfig;
|
|
9
|
+
export declare function resolveClassOptions(cls: {
|
|
10
|
+
copyWith?: boolean;
|
|
11
|
+
equal?: boolean;
|
|
12
|
+
toString?: boolean;
|
|
13
|
+
}, config: ResolvedConfig): {
|
|
14
|
+
copyWith: boolean;
|
|
15
|
+
equal: boolean;
|
|
16
|
+
toString: boolean;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/generator/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAaD,eAAO,MAAM,QAAQ,EAAE,cAKtB,CAAC;AAEF,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAsB7D;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,EAChE,MAAM,EAAE,cAAc,GACrB;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAO1D"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
export const DEFAULTS = {
|
|
4
|
+
format: false,
|
|
5
|
+
copyWith: true,
|
|
6
|
+
equal: true,
|
|
7
|
+
toString: true,
|
|
8
|
+
};
|
|
9
|
+
export function loadConfig(configPath) {
|
|
10
|
+
let raw;
|
|
11
|
+
try {
|
|
12
|
+
raw = fs.readFileSync(configPath, 'utf-8');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { ...DEFAULTS };
|
|
16
|
+
}
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = parseYaml(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new Error(`freezedts: invalid YAML in config file: ${configPath}`);
|
|
23
|
+
}
|
|
24
|
+
const options = parsed?.freezed?.options;
|
|
25
|
+
return {
|
|
26
|
+
format: options?.format ?? DEFAULTS.format,
|
|
27
|
+
copyWith: options?.copyWith ?? DEFAULTS.copyWith,
|
|
28
|
+
equal: options?.equal ?? DEFAULTS.equal,
|
|
29
|
+
toString: options?.toString ?? DEFAULTS.toString,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function resolveClassOptions(cls, config) {
|
|
33
|
+
return {
|
|
34
|
+
copyWith: cls.copyWith ?? config.copyWith,
|
|
35
|
+
equal: cls.equal ?? config.equal,
|
|
36
|
+
// Cannot use ?? because Object.prototype.toString would always be truthy
|
|
37
|
+
toString: Object.hasOwn(cls, 'toString') ? cls.toString : config.toString,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/generator/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAoB1C,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,IAAI;IACd,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,IAAI;CACf,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,UAAkB;IAC3C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,MAAgC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,GAAG,CAA6B,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,2CAA2C,UAAU,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC;IAEzC,OAAO;QACL,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,QAAQ,CAAC,MAAM;QAC1C,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAChD,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,QAAQ,CAAC,KAAK;QACvC,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,QAAQ,CAAC,QAAQ;KACjD,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,GAAgE,EAChE,MAAsB;IAEtB,OAAO;QACL,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ;QACzC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK;QAChC,yEAAyE;QACzE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ;KAC3E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/generator/emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAItD,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,MAAM,CA0BrE"}
|