@ttsc/unplugin 0.30.0 → 0.30.2

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.
@@ -21,6 +21,7 @@ import {
21
21
  findNearestProjectTsconfig,
22
22
  isIgnoredProjectDirectory,
23
23
  } from "./projectDiscovery";
24
+ import { matchesProjectRootFile } from "./projectRootFiles";
24
25
  import {
25
26
  CONFIG_DIR_TEMPLATE_LIST_OPTIONS,
26
27
  CONFIG_DIR_TEMPLATE_SCALAR_OPTIONS,
@@ -2624,6 +2625,29 @@ function matchesCachedSource(
2624
2625
  cached.sourceHashes?.[identity] ??
2625
2626
  cached.inputHashes[currentKey] ??
2626
2627
  cached.externalInputHashes?.[identity];
2628
+ if (
2629
+ expected === undefined &&
2630
+ cached.result.type === "success" &&
2631
+ !matchesProjectRootFile(file, cached.membershipPolicy, false)
2632
+ ) {
2633
+ const state = envelopeDerivation(cached);
2634
+ const outputs = (state.outputIndex ??= createEnvelopeKeyIndex(
2635
+ state,
2636
+ cached.projectRoot,
2637
+ cached.result.typescript,
2638
+ ));
2639
+ if (!outputs.has(identity)) {
2640
+ // Root discovery deliberately never hashed this unrelated module. Its
2641
+ // bytes cannot affect an output the compiler did not produce, but the
2642
+ // whole program must still be current before we reuse that absence: a
2643
+ // changed config or importer can bring this file into the next program.
2644
+ refreshFilesystemClockReference(
2645
+ TRANSFORM_CLOCK_REFERENCE_DIRECTORIES.get(cached),
2646
+ resultFilesystem(cached.result),
2647
+ );
2648
+ return matchesCompleteInputSnapshot(cached, currentKey, source);
2649
+ }
2650
+ }
2627
2651
  if (expected !== hashText(source)) {
2628
2652
  return false;
2629
2653
  }
@@ -4442,7 +4466,10 @@ function walkProjectInputs(
4442
4466
  continue;
4443
4467
  }
4444
4468
  const file = path.join(current, entry.name);
4445
- if (entry.isDirectory() && isExcludedProjectDirectory(file, policy)) {
4469
+ if (
4470
+ (entry.isDirectory() && isExcludedProjectDirectory(file, policy)) ||
4471
+ !matchesProjectRootFile(file, policy, entry.isDirectory())
4472
+ ) {
4446
4473
  continue;
4447
4474
  }
4448
4475
  const possible = isPossibleProgramEntry(entry, policy);
@@ -4851,30 +4878,34 @@ function reportsProgramMembership(
4851
4878
  policy: ITtscProjectMembershipPolicy,
4852
4879
  filesystem: TtscTransformFilesystemOperations,
4853
4880
  ): boolean {
4854
- if (isPossibleProgramFileName(filename, policy)) {
4855
- // A name the program could admit. It still says nothing if it lies inside a
4856
- // directory the walk never descends into, because the digest cannot see
4857
- // there either and the tracker must not be the one side that reacts.
4858
- return !insideExcludedProjectDirectory(location, policy, true);
4881
+ if (
4882
+ !matchesProjectRootFile(location, policy, false) &&
4883
+ !matchesProjectRootFile(location, policy, true)
4884
+ ) {
4885
+ return false;
4859
4886
  }
4860
- let directory: boolean;
4861
4887
  try {
4862
- directory = filesystem.lstat(location).isDirectory();
4888
+ if (filesystem.lstat(location).isDirectory()) {
4889
+ return (
4890
+ matchesProjectRootFile(location, policy, true) &&
4891
+ !insideExcludedProjectDirectory(location, policy, false)
4892
+ );
4893
+ }
4863
4894
  } catch {
4864
- // Gone again, or unreadable. Its name could not have been a program input,
4865
- // and a directory removed under this one reports its own contents leaving
4866
- // through the watch that was opened on it.
4867
- return false;
4895
+ // Deleted file names still need classification below.
4868
4896
  }
4869
- if (!directory) {
4870
- return false;
4897
+ if (isPossibleProgramFileName(filename, policy)) {
4898
+ // A name the program could admit. It still says nothing if it lies inside a
4899
+ // directory the walk never descends into, because the digest cannot see
4900
+ // there either and the tracker must not be the one side that reacts.
4901
+ return (
4902
+ matchesProjectRootFile(location, policy, false) &&
4903
+ !insideExcludedProjectDirectory(location, policy, true)
4904
+ );
4871
4905
  }
4872
- // A directory counts, because it can hold sources and the tracker is not
4873
- // watching it yet, unless the configuration says the program does not contain
4874
- // it. Emptying and recreating an `outDir`, which is what `emptyOutDir` and
4875
- // `output.clean` do on every build, would otherwise void the generation once
4876
- // per build on every host that has no build boundary.
4877
- return !insideExcludedProjectDirectory(location, policy, false);
4906
+ // Removed directories report their source removals through their own watch.
4907
+ // A non-source file name cannot introduce program membership.
4908
+ return false;
4878
4909
  }
4879
4910
 
4880
4911
  /** Record enough exact mutation evidence without retaining an event stream. */
@@ -5356,7 +5387,10 @@ export function isProjectWalkPath(
5356
5387
  // a graph input the compiler really read in neither snapshot: absent from
5357
5388
  // `inputHashes` because the walk skipped it, and absent from the out-of-walk
5358
5389
  // snapshot because this predicate claimed the walk covered it.
5359
- if (!isPossibleProgramFileName(path.basename(file), policy)) {
5390
+ if (
5391
+ !isPossibleProgramFileName(path.basename(file), policy) ||
5392
+ !matchesProjectRootFile(file, policy, false)
5393
+ ) {
5360
5394
  return false;
5361
5395
  }
5362
5396
  let current = resolvedRoot;
@@ -116,6 +116,16 @@ export function readTsconfigSourceSnapshot(
116
116
  * dropped from the walk entirely (samchon/ttsc#1307).
117
117
  */
118
118
  export interface ITtscProjectMembershipPolicy {
119
+ /** Absolute root-file specifications; absent means conservative discovery. */
120
+ rootFileSpecs?: Readonly<{
121
+ files: readonly string[];
122
+ include: readonly string[];
123
+ /**
124
+ * The requested root and its regular/native realpath spellings, without
125
+ * following child links. Native realpath expands Windows short names.
126
+ */
127
+ root?: Readonly<{ path: string; realpath: string; nativepath?: string }>;
128
+ }>;
119
129
  /**
120
130
  * Absolute directory exclusions separated by the configuration entry that
121
131
  * contributed them.
@@ -210,6 +220,48 @@ export function readProjectMembershipPolicy(
210
220
  // when it has gone stale. `findDeclaredValue` walks `extends` for each option
211
221
  // independently, and each walk records what it read.
212
222
  const sources = new Set<string>();
223
+ const fileSpec = (key: "files" | "include") =>
224
+ findDeclaredValue(
225
+ resolved,
226
+ (parsed) =>
227
+ Object.prototype.hasOwnProperty.call(parsed, key)
228
+ ? { value: (parsed as Record<string, unknown>)[key] }
229
+ : undefined,
230
+ new Set(),
231
+ sources,
232
+ );
233
+ const files = fileSpec("files");
234
+ const include = fileSpec("include");
235
+ const resolveSpecs = (
236
+ declared: ReturnType<typeof fileSpec>,
237
+ ): string[] | undefined => {
238
+ if (declared === null || declared.value.value == null) return undefined;
239
+ const value = declared.value.value;
240
+ if (
241
+ !Array.isArray(value) ||
242
+ !value.every((entry) => typeof entry === "string")
243
+ )
244
+ return undefined;
245
+ return value.map((entry) =>
246
+ absolutizePathsTarget(declared.baseDir, entry, path.dirname(resolved)),
247
+ );
248
+ };
249
+ const explicitFiles = resolveSpecs(files);
250
+ const includes = resolveSpecs(include);
251
+ const root = {
252
+ path: path.dirname(resolved),
253
+ realpath: resolveRealPath(path.dirname(resolved)),
254
+ nativepath: resolveNativeRootPath(path.dirname(resolved)),
255
+ };
256
+ // Missing/invalid specs keep the wide fallback. A files-only project has no
257
+ // implicit include, whereas include and files together form a union.
258
+ const rootFileSpecs =
259
+ includes !== undefined
260
+ ? { files: explicitFiles ?? [], include: includes, root }
261
+ : explicitFiles !== undefined &&
262
+ (include === null || include.value.value == null)
263
+ ? { files: explicitFiles, include: [], root }
264
+ : undefined;
213
265
  const flag = (key: string): boolean =>
214
266
  findDeclaredValue(
215
267
  resolved,
@@ -302,6 +354,7 @@ export function readProjectMembershipPolicy(
302
354
  }
303
355
  }
304
356
  return {
357
+ rootFileSpecs,
305
358
  directoryExclusionOrigins,
306
359
  excludedDirectories: flattenDirectoryExclusionOrigins(
307
360
  directoryExclusionOrigins,
@@ -367,6 +420,7 @@ export function mergeMembershipPolicyOverlay(
367
420
  }
368
421
  }
369
422
  return {
423
+ rootFileSpecs: policy.rootFileSpecs,
370
424
  directoryExclusionOrigins,
371
425
  excludedDirectories: flattenDirectoryExclusionOrigins(
372
426
  directoryExclusionOrigins,
@@ -866,10 +920,16 @@ function isRelativeSpecifier(specifier: string): boolean {
866
920
  );
867
921
  }
868
922
 
869
- /**
870
- * Resolve symlinks on `location`, returning the original path when
871
- * `realpathSync` fails (e.g. when the file does not exist).
872
- */
923
+ /** Native watchers expand Windows short names that regular realpath retains. */
924
+ function resolveNativeRootPath(location: string): string {
925
+ try {
926
+ return fs.realpathSync.native(location);
927
+ } catch {
928
+ return resolveRealPath(location);
929
+ }
930
+ }
931
+
932
+ /** Resolve symlinks, retaining the original spelling when the path is missing. */
873
933
  function resolveRealPath(location: string): string {
874
934
  try {
875
935
  return fs.realpathSync(location);