@postxl/generator 1.4.1 → 1.5.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.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GeneratorManager = void 0;
4
4
  const utils_1 = require("@postxl/utils");
5
5
  const branded_types_1 = require("./helpers/branded.types");
6
+ const generator_context_1 = require("./generator.context");
6
7
  /**
7
8
  * The GeneratorManager coordinates the generation process across multiple Generators.
8
9
  *
@@ -144,6 +145,10 @@ class GeneratorManager {
144
145
  // Not all generators extend the context and hence do not need to return it.
145
146
  // Therefore, if the generator does not return the context, we use the original context.
146
147
  context = (await generator.register(context)) ?? context;
148
+ // Register phases typically rebuild `context.models` as a fresh Map with
149
+ // extended per-model contexts. Re-sync `targetModels` so it points at
150
+ // the same (or narrowed) extended entries rather than a stale reference.
151
+ context = (0, generator_context_1.syncTargetModels)(context);
147
152
  }
148
153
  catch (error) {
149
154
  throw new Error(`Generator ${(0, utils_1.yellow)(generator.id)} failed during register phase: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
@@ -22,6 +22,18 @@ export type Context = {
22
22
  * Each generator can extend these contexts with its own content.
23
23
  */
24
24
  models: Map<Schema.ModelName, ModelContext>;
25
+ /**
26
+ * Subset of `models` that per-model generation should target. Same reference as
27
+ * `models` by default; only the `-m <names...>` CLI flag narrows it to a smaller
28
+ * set. Generators that iterate models to write one file per model should read
29
+ * `targetModels`; generators that iterate to contribute to an aggregate file,
30
+ * or that do cross-model lookups via `.get(name)`, should stay on `models`.
31
+ *
32
+ * The narrowing is sourced from `options.targetModelNames` and re-synced by
33
+ * the `GeneratorManager` after every register phase, so it survives generators
34
+ * that rebuild the `models` map.
35
+ */
36
+ targetModels: Map<Schema.ModelName, ModelContext>;
25
37
  /**
26
38
  * Contains the context for each schema enum.
27
39
  * Each generator can extend these contexts with its own content.
@@ -144,12 +156,21 @@ export type GeneratorOptions = {
144
156
  * Default: undefined (no filtering)
145
157
  */
146
158
  filePattern?: string;
159
+ /**
160
+ * Optional set of model names to narrow generation to. When set, per-model
161
+ * generators iterate only these models (via `context.targetModels`) and the
162
+ * VFS safety-net drops any per-model writes for models outside this set.
163
+ *
164
+ * Default: undefined (all models).
165
+ */
166
+ targetModelNames?: ReadonlySet<Schema.ModelName>;
147
167
  };
148
168
  export type ExtendContext<Context, ContextExtension> = Context & ContextExtension;
149
169
  export type ExtendModelContext<Context, ModelContextExtension> = Context extends {
150
170
  models: Map<infer K, infer V>;
151
- } ? Omit<Context, 'models'> & {
171
+ } ? Omit<Context, 'models' | 'targetModels'> & {
152
172
  models: Map<K, V & ModelContextExtension>;
173
+ targetModels: Map<K, V & ModelContextExtension>;
153
174
  } : never;
154
175
  export type ExtendEnumContext<Context, EnumContextExtension> = Context extends {
155
176
  enums: Map<infer K, infer V>;
@@ -165,6 +186,13 @@ export type InferEnumContext<Context> = Context extends {
165
186
  enums: Map<any, infer V>;
166
187
  } ? V : never;
167
188
  export declare function prepareBaseContext(schema: Schema.ProjectSchema, opts?: Partial<GeneratorOptions>): Context;
189
+ /**
190
+ * Given a fresh `models` Map and the requested narrowing on
191
+ * `ctx.options.targetModelNames` (if any), produce a `targetModels` Map that
192
+ * matches. Called by the GeneratorManager after every register phase to keep
193
+ * `targetModels` pointing at the latest extended contexts.
194
+ */
195
+ export declare function syncTargetModels<C extends Context>(ctx: C): C;
168
196
  /**
169
197
  * Helper function for tests to create a mock schema.
170
198
  */
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.prepareBaseContext = prepareBaseContext;
37
+ exports.syncTargetModels = syncTargetModels;
37
38
  exports.mockSchema = mockSchema;
38
39
  exports.mockSchemaWithDefaultModels = mockSchemaWithDefaultModels;
39
40
  exports.getBaseModelContext = getBaseModelContext;
@@ -56,14 +57,61 @@ function prepareBaseContext(schema, opts = defaultGeneratorOptions) {
56
57
  for (const enumSchema of schema.enums.values()) {
57
58
  enums.set(enumSchema.name, getBaseEnumContext(enumSchema));
58
59
  }
60
+ const targetModelNames = effectiveTargetModelNames(options.targetModelNames, models);
61
+ const targetModels = targetModelNames ? filterModelMapByNames(models, targetModelNames) : models;
62
+ const vfsOptions = {};
63
+ if (options.filePattern) {
64
+ vfsOptions.filePattern = options.filePattern;
65
+ }
66
+ if (targetModelNames) {
67
+ vfsOptions.targetModelNames = targetModelNames;
68
+ vfsOptions.allModelNames = new Set(models.keys());
69
+ }
59
70
  return {
60
71
  schema: schema,
61
- vfs: new vfs_class_1.VirtualFileSystem(options.filePattern ? { filePattern: options.filePattern } : undefined),
72
+ vfs: new vfs_class_1.VirtualFileSystem(Object.keys(vfsOptions).length > 0 ? vfsOptions : undefined),
62
73
  models,
74
+ targetModels,
63
75
  enums,
64
76
  options,
65
77
  };
66
78
  }
79
+ /**
80
+ * Given a fresh `models` Map and the requested narrowing on
81
+ * `ctx.options.targetModelNames` (if any), produce a `targetModels` Map that
82
+ * matches. Called by the GeneratorManager after every register phase to keep
83
+ * `targetModels` pointing at the latest extended contexts.
84
+ */
85
+ function syncTargetModels(ctx) {
86
+ const targetModelNames = effectiveTargetModelNames(ctx.options?.targetModelNames, ctx.models);
87
+ if (!targetModelNames) {
88
+ return ctx.targetModels === ctx.models ? ctx : { ...ctx, targetModels: ctx.models };
89
+ }
90
+ return { ...ctx, targetModels: filterModelMapByNames(ctx.models, targetModelNames) };
91
+ }
92
+ /**
93
+ * Returns the requested narrowing if it actually narrows the given `models`
94
+ * map; returns `undefined` when no narrowing was requested or the requested set
95
+ * already covers every current model (no real narrowing).
96
+ */
97
+ function effectiveTargetModelNames(requested, models) {
98
+ if (!requested) {
99
+ return undefined;
100
+ }
101
+ if (requested.size === models.size && [...requested].every((n) => models.has(n))) {
102
+ return undefined;
103
+ }
104
+ return requested;
105
+ }
106
+ function filterModelMapByNames(models, names) {
107
+ const out = new Map();
108
+ for (const [name, model] of models) {
109
+ if (names.has(name)) {
110
+ out.set(name, model);
111
+ }
112
+ }
113
+ return out;
114
+ }
67
115
  /**
68
116
  * Helper function for tests to create a mock schema.
69
117
  */
@@ -0,0 +1,26 @@
1
+ export type PathClassification = {
2
+ kind: 'per-model';
3
+ model: string;
4
+ } | {
5
+ kind: 'aggregate-or-unknown';
6
+ };
7
+ /**
8
+ * Classifies a VFS path as "per-model" (a file that belongs to exactly one
9
+ * model from `allModelNames`) or "aggregate-or-unknown" (everything else).
10
+ *
11
+ * Algorithm:
12
+ * 1. Take the basename, strip every extension segment.
13
+ * 2. Split the remaining stem on `.`, `-`, `_`, and camelCase boundaries.
14
+ * 3. For each token, case-insensitive match against `allModelNames`.
15
+ * 4. Exactly one distinct model matched → `per-model`.
16
+ * 5. Zero or multiple distinct matches → `aggregate-or-unknown`.
17
+ *
18
+ * The conservative multi-match treatment means cross-model files like
19
+ * `post-country-link.ts` keep falling through as "aggregate-or-unknown" — the
20
+ * safety-net in the VFS will then keep the file, which is the correct default.
21
+ *
22
+ * Used by the VFS safety-net: when a `targetModelNames` filter is active, the
23
+ * VFS drops writes classified as `per-model` for models not in the target set.
24
+ * Aggregate-or-unknown writes always pass through.
25
+ */
26
+ export declare function classifyPath(path: string, allModelNames: ReadonlySet<string>): PathClassification;
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.classifyPath = classifyPath;
37
+ const Path = __importStar(require("./path"));
38
+ /**
39
+ * Classifies a VFS path as "per-model" (a file that belongs to exactly one
40
+ * model from `allModelNames`) or "aggregate-or-unknown" (everything else).
41
+ *
42
+ * Algorithm:
43
+ * 1. Take the basename, strip every extension segment.
44
+ * 2. Split the remaining stem on `.`, `-`, `_`, and camelCase boundaries.
45
+ * 3. For each token, case-insensitive match against `allModelNames`.
46
+ * 4. Exactly one distinct model matched → `per-model`.
47
+ * 5. Zero or multiple distinct matches → `aggregate-or-unknown`.
48
+ *
49
+ * The conservative multi-match treatment means cross-model files like
50
+ * `post-country-link.ts` keep falling through as "aggregate-or-unknown" — the
51
+ * safety-net in the VFS will then keep the file, which is the correct default.
52
+ *
53
+ * Used by the VFS safety-net: when a `targetModelNames` filter is active, the
54
+ * VFS drops writes classified as `per-model` for models not in the target set.
55
+ * Aggregate-or-unknown writes always pass through.
56
+ */
57
+ function classifyPath(path, allModelNames) {
58
+ if (allModelNames.size === 0) {
59
+ return { kind: 'aggregate-or-unknown' };
60
+ }
61
+ const basename = Path.fileName(Path.normalize(path));
62
+ const dotIdx = basename.indexOf('.');
63
+ const stem = dotIdx === -1 ? basename : basename.slice(0, dotIdx);
64
+ if (stem.length === 0) {
65
+ return { kind: 'aggregate-or-unknown' };
66
+ }
67
+ const tokens = tokenize(stem);
68
+ if (tokens.length === 0) {
69
+ return { kind: 'aggregate-or-unknown' };
70
+ }
71
+ const lowerModelByName = new Map();
72
+ for (const name of allModelNames) {
73
+ lowerModelByName.set(name.toLowerCase(), name);
74
+ }
75
+ const matched = new Set();
76
+ for (const token of tokens) {
77
+ const hit = lowerModelByName.get(token.toLowerCase());
78
+ if (hit) {
79
+ matched.add(hit);
80
+ }
81
+ }
82
+ if (matched.size === 1) {
83
+ return { kind: 'per-model', model: matched.values().next().value };
84
+ }
85
+ return { kind: 'aggregate-or-unknown' };
86
+ }
87
+ function tokenize(stem) {
88
+ const out = [];
89
+ // Split on `.`, `-`, `_` first.
90
+ for (const chunk of stem.split(/[.\-_]+/)) {
91
+ if (chunk.length === 0) {
92
+ continue;
93
+ }
94
+ // Split remaining chunk on camelCase boundaries (lower→upper, and
95
+ // consecutive-uppercase→upper+lower transitions for acronym-capable names).
96
+ for (const part of chunk.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/)) {
97
+ if (part.length > 0) {
98
+ out.push(part);
99
+ }
100
+ }
101
+ }
102
+ return out;
103
+ }
@@ -1,3 +1,4 @@
1
+ export * from './classify-path';
1
2
  export * from './custom-blocks';
2
3
  export * from './jsdoc';
3
4
  export * from './lint';
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./classify-path"), exports);
17
18
  __exportStar(require("./custom-blocks"), exports);
18
19
  __exportStar(require("./jsdoc"), exports);
19
20
  __exportStar(require("./lint"), exports);
@@ -27,6 +27,8 @@ export type LockEntry = Checksum | EjectedSentinel;
27
27
  declare const zLockFile: z.ZodPipe<z.ZodRecord<z.core.$ZodBranded<z.ZodString, "PXL.PosixPath", "out">, z.ZodUnion<readonly [z.core.$ZodBranded<z.ZodString, "PXL.Checksum", "out">, z.ZodLiteral<"ejected">]>>, z.ZodTransform<Map<string & z.core.$brand<"PXL.PosixPath">, LockEntry>, Record<string & z.core.$brand<"PXL.PosixPath">, (string & z.core.$brand<"PXL.Checksum">) | "ejected">>>;
28
28
  type LockFile = z.infer<typeof zLockFile>;
29
29
  export declare function isEjected(entry: LockEntry | undefined): entry is EjectedSentinel;
30
- export declare function writeLockFile(lockFilePath: string, vfs: VirtualFileSystem): Promise<void>;
30
+ export declare function writeLockFile(lockFilePath: string, vfs: VirtualFileSystem, opts?: {
31
+ selectiveGeneration?: boolean;
32
+ }): Promise<void>;
31
33
  export declare function readLockFile(lockFilePath: string): Promise<LockFile | undefined>;
32
34
  export {};
@@ -65,7 +65,7 @@ const zLockFile = zod_1.z
65
65
  function isEjected(entry) {
66
66
  return entry === exports.EJECTED_SENTINEL;
67
67
  }
68
- async function writeLockFile(lockFilePath, vfs) {
68
+ async function writeLockFile(lockFilePath, vfs, opts = {}) {
69
69
  const newLockFileMap = await vfsToLockFile(vfs);
70
70
  const existingLockFile = await readLockFile(lockFilePath);
71
71
  if (existingLockFile) {
@@ -80,6 +80,13 @@ async function writeLockFile(lockFilePath, vfs) {
80
80
  // that don't match the current filter (they were out of scope for this run).
81
81
  if (vfs.filePattern && !vfs.matchesPattern(filePath) && !newLockFileMap.has(filePath)) {
82
82
  newLockFileMap.set(filePath, entry);
83
+ continue;
84
+ }
85
+ // Selective generation (`-m`): any lock entry not produced by this run is
86
+ // deliberately out of scope — preserve it as-is. Sync skips matching entries
87
+ // so nothing on disk is deleted. A subsequent full regeneration rewrites the lock cleanly.
88
+ if (opts.selectiveGeneration && !newLockFileMap.has(filePath)) {
89
+ newLockFileMap.set(filePath, entry);
83
90
  }
84
91
  }
85
92
  }
@@ -67,6 +67,14 @@ type SyncParams = {
67
67
  lockFilePath: string;
68
68
  diskFilePath: string;
69
69
  force: boolean;
70
+ /**
71
+ * When true, any lock-file entry that is not in the current VFS is considered
72
+ * out of scope for this run: sync does not touch it (no delete, no force
73
+ * overwrite) and the lock file preserves the entry as-is. A subsequent full
74
+ * regeneration reconciles the lock cleanly. Set this when the CLI narrows
75
+ * generation via `-m <names...>`.
76
+ */
77
+ selectiveGeneration?: boolean;
70
78
  };
71
79
  /**
72
80
  * Error returned when files with unresolved merge conflicts are detected
@@ -109,7 +117,7 @@ export type SyncResults = {
109
117
  * merge conflict markers. If found, the sync will abort immediately and return an error with the
110
118
  * list of files that need to be resolved before generation can continue.
111
119
  */
112
- export declare function sync({ vfs, lockFilePath, diskFilePath, force }: SyncParams): Promise<SyncResults>;
120
+ export declare function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, }: SyncParams): Promise<SyncResults>;
113
121
  type FileState = {
114
122
  state: 'empty';
115
123
  } | {
@@ -121,9 +121,9 @@ const Path = __importStar(require("./path"));
121
121
  * merge conflict markers. If found, the sync will abort immediately and return an error with the
122
122
  * list of files that need to be resolved before generation can continue.
123
123
  */
124
- async function sync({ vfs, lockFilePath, diskFilePath, force }) {
124
+ async function sync({ vfs, lockFilePath, diskFilePath, force, selectiveGeneration, }) {
125
125
  const diskPathNormalized = Path.normalize(diskFilePath);
126
- const files = await getFilesStates({ vfs, lockFilePath, diskFilePath });
126
+ const files = await getFilesStates({ vfs, lockFilePath, diskFilePath, selectiveGeneration: !!selectiveGeneration });
127
127
  // Check for unresolved merge conflicts before writing any files
128
128
  const filesWithConflicts = findFilesWithMergeConflicts(files);
129
129
  if (filesWithConflicts.length > 0 && !force) {
@@ -165,7 +165,7 @@ async function sync({ vfs, lockFilePath, diskFilePath, force }) {
165
165
  });
166
166
  tasks.push(task);
167
167
  }
168
- tasks.push(limit(() => (0, lockfile_1.writeLockFile)(lockFilePath, vfs)));
168
+ tasks.push(limit(() => (0, lockfile_1.writeLockFile)(lockFilePath, vfs, selectiveGeneration ? { selectiveGeneration: true } : {})));
169
169
  await Promise.all(tasks);
170
170
  return { success: true, ...result };
171
171
  }
@@ -262,7 +262,7 @@ function lockEntryToFileState(entry) {
262
262
  }
263
263
  return { state: 'hash', hash: entry };
264
264
  }
265
- async function getFilesStates({ vfs, lockFilePath, diskFilePath, }) {
265
+ async function getFilesStates({ vfs, lockFilePath, diskFilePath, selectiveGeneration, }) {
266
266
  const lockFile = await (0, lockfile_1.readLockFile)(lockFilePath);
267
267
  const files = new Map();
268
268
  const diskPathNormalized = Path.normalize(diskFilePath);
@@ -286,6 +286,11 @@ async function getFilesStates({ vfs, lockFilePath, diskFilePath, }) {
286
286
  if (!vfs.matchesPattern(filePath)) {
287
287
  continue;
288
288
  }
289
+ // Selective generation: any lock entry not in the VFS is out of scope for
290
+ // this run. Sync must not touch it (no delete, no force overwrite).
291
+ if (selectiveGeneration) {
292
+ continue;
293
+ }
289
294
  const lock = lockEntryToFileState(entry);
290
295
  const disk = await getDiskFileState(Path.join(diskPathNormalized, filePath));
291
296
  files.set(filePath, { virtual, lock, disk });
@@ -10,6 +10,17 @@ export type VFSOptions = {
10
10
  * When specified, only files matching this pattern will be stored in the VFS.
11
11
  */
12
12
  filePattern?: string;
13
+ /**
14
+ * Optional target model names for the selective-generation safety net.
15
+ * When set, writes classified as "per-model X" for X not in this set are dropped.
16
+ * Requires `allModelNames` to be set.
17
+ */
18
+ targetModelNames?: ReadonlySet<string>;
19
+ /**
20
+ * Full set of model names in the project. Required whenever `targetModelNames`
21
+ * is set; used by the path classifier to decide whether a write is per-model.
22
+ */
23
+ allModelNames?: ReadonlySet<string>;
13
24
  };
14
25
  /**
15
26
  * The virtual file system (VFS) represents a file system that can be manipulated in memory.
@@ -37,6 +37,7 @@ exports.VirtualFileSystem = void 0;
37
37
  const minimatch_1 = require("minimatch");
38
38
  const fs = __importStar(require("node:fs/promises"));
39
39
  const utils_1 = require("@postxl/utils");
40
+ const classify_path_1 = require("./classify-path");
40
41
  const fs_utils_1 = require("./fs-utils");
41
42
  const Path = __importStar(require("./path"));
42
43
  /**
@@ -67,6 +68,8 @@ const Path = __importStar(require("./path"));
67
68
  class VirtualFileSystem {
68
69
  #files = new Map();
69
70
  #filePattern;
71
+ #targetModelNames;
72
+ #allModelNames;
70
73
  /**
71
74
  * Constructs a new VirtualFileSystem.
72
75
  *
@@ -74,6 +77,16 @@ class VirtualFileSystem {
74
77
  */
75
78
  constructor(options) {
76
79
  this.#filePattern = options?.filePattern;
80
+ // Only activate the classifier when narrowing is actually in effect —
81
+ // absent targetModelNames, or a target set that already covers every model,
82
+ // means "keep everything" and the classifier can be skipped entirely.
83
+ if (options?.targetModelNames &&
84
+ options.allModelNames &&
85
+ options.targetModelNames.size > 0 &&
86
+ options.targetModelNames.size < options.allModelNames.size) {
87
+ this.#targetModelNames = options.targetModelNames;
88
+ this.#allModelNames = options.allModelNames;
89
+ }
77
90
  }
78
91
  /**
79
92
  * Returns all file names in the VFS.
@@ -108,16 +121,33 @@ class VirtualFileSystem {
108
121
  const normalizedPattern = this.#filePattern.startsWith('/') ? this.#filePattern.slice(1) : this.#filePattern;
109
122
  return (0, minimatch_1.minimatch)(normalizedPath, normalizedPattern, { dot: true, matchBase: false });
110
123
  }
124
+ /**
125
+ * Safety net for `-m` selective generation: if the write is classified as
126
+ * per-model for a model outside the target set, drop it. Returns true when
127
+ * the write should proceed, false when it should be skipped.
128
+ */
129
+ #matchesTargetModels(filePath) {
130
+ if (!this.#targetModelNames || !this.#allModelNames) {
131
+ return true;
132
+ }
133
+ const classification = (0, classify_path_1.classifyPath)(filePath, this.#allModelNames);
134
+ if (classification.kind === 'aggregate-or-unknown') {
135
+ return true;
136
+ }
137
+ return this.#targetModelNames.has(classification.model);
138
+ }
111
139
  /**
112
140
  * Writes the given content to the specified path.
113
141
  * If a file pattern is set, only files matching the pattern will be stored.
114
142
  */
115
143
  write(path, content) {
116
144
  const posixPath = Path.normalize(path);
117
- // Skip files that don't match the pattern
118
145
  if (!this.matchesPattern(posixPath)) {
119
146
  return;
120
147
  }
148
+ if (!this.#matchesTargetModels(posixPath)) {
149
+ return;
150
+ }
121
151
  this.#files.set(posixPath, content);
122
152
  }
123
153
  /**
@@ -150,10 +180,12 @@ class VirtualFileSystem {
150
180
  const basePath = Path.normalize(targetPath);
151
181
  for (const [filePath, content] of vfs.files) {
152
182
  const combinedPath = Path.join(basePath, filePath);
153
- // Skip files that don't match the pattern
154
183
  if (!this.matchesPattern(combinedPath)) {
155
184
  continue;
156
185
  }
186
+ if (!this.#matchesTargetModels(combinedPath)) {
187
+ continue;
188
+ }
157
189
  this.#files.set(combinedPath, content);
158
190
  }
159
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postxl/generator",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Core package that orchestrates the code generation of a PXL project",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "jszip": "3.10.1",
47
47
  "minimatch": "^10.2.2",
48
48
  "p-limit": "3.1.0",
49
- "@postxl/schema": "^1.10.1",
49
+ "@postxl/schema": "^1.11.0",
50
50
  "@postxl/utils": "^1.4.0"
51
51
  },
52
52
  "devDependencies": {