carrick 0.3.75 → 0.3.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carrick",
3
- "version": "0.3.75",
3
+ "version": "0.3.77",
4
4
  "description": "The API contract index for a TypeScript workspace: what the other services do with the routes and calls in the file you are editing, in your editor and in your agent's context",
5
5
  "keywords": [
6
6
  "typescript",
@@ -57,11 +57,11 @@
57
57
  "zod": "^3.23.0"
58
58
  },
59
59
  "optionalDependencies": {
60
- "@carrick-tools/cli-darwin-arm64": "0.3.75",
61
- "@carrick-tools/cli-darwin-x64": "0.3.75",
62
- "@carrick-tools/cli-linux-arm64": "0.3.75",
63
- "@carrick-tools/cli-linux-x64": "0.3.75",
64
- "@carrick-tools/cli-win32-x64": "0.3.75"
60
+ "@carrick-tools/cli-darwin-arm64": "0.3.77",
61
+ "@carrick-tools/cli-darwin-x64": "0.3.77",
62
+ "@carrick-tools/cli-linux-arm64": "0.3.77",
63
+ "@carrick-tools/cli-linux-x64": "0.3.77",
64
+ "@carrick-tools/cli-win32-x64": "0.3.77"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "^24.13.3",
@@ -19,6 +19,7 @@ export declare class DenoProject {
19
19
  readonly pinned: Record<string, string>;
20
20
  private readonly modules;
21
21
  private readonly localPaths;
22
+ private readonly specifiersByPath;
22
23
  private readonly edges;
23
24
  private readonly redirects;
24
25
  private readonly npmPackages;
@@ -27,6 +28,16 @@ export declare class DenoProject {
27
28
  private targetPath;
28
29
  resolve(spec: string, from: string, options: ts.CompilerOptions, host?: ts.ModuleResolutionHost): ts.ResolvedModule | undefined;
29
30
  private npmOwner;
31
+ /** Which registry package a resolved file belongs to (carrick#1260).
32
+ *
33
+ * The inverse of resolution, and the only answer available for a Deno
34
+ * project: nothing it resolves sits under `node_modules`, so reading a
35
+ * package name off the path cannot work. An npm dependency is owned by the
36
+ * graph entry whose cache directory contains the file; a JSR one is named by
37
+ * its own specifier, since its cached copy is content-addressed and carries
38
+ * no identity. A file the workspace itself owns belongs to no package and
39
+ * answers nothing, exactly as a workspace-declared type does under Node. */
40
+ packageOf(file: string): string | undefined;
30
41
  private isNpmFile;
31
42
  /** Let TypeScript interpret real package exports/types using Deno's exact
32
43
  * dependency graph. Only the resolution host sees virtual node_modules;
@@ -12,6 +12,22 @@ function readConfig(file) {
12
12
  throw new Error(ts.flattenDiagnosticMessageText(parsed.error.messageText, '\n'));
13
13
  return parsed.config;
14
14
  }
15
+ /** The package a JSR module specifier names, in the form the registry uses
16
+ * (`@scope/name`). Both the specifier Deno records after resolution
17
+ * (`https://jsr.io/@scope/name/1.2.3/mod.ts`) and the one an import map writes
18
+ * (`jsr:@scope/name@1`) name it in their first two segments. Anything else —
19
+ * a `file:` module, an `https://` module from a host that is not a package
20
+ * registry — names no package. */
21
+ function jsrPackageName(specifier) {
22
+ const tail = /^jsr:\/{0,2}(.*)$/.exec(specifier)?.[1]
23
+ ?? /^https:\/\/jsr\.io\/(.*)$/.exec(specifier)?.[1];
24
+ if (!tail)
25
+ return undefined;
26
+ const [scope, name] = tail.split('/');
27
+ if (!scope?.startsWith('@') || !name)
28
+ return undefined;
29
+ return `${scope}/${name.split('@')[0]}`;
30
+ }
15
31
  /** Explicit TS configs keep their existing behaviour, including mixed repos. */
16
32
  export function findDenoConfig(repoRoot, explicit) {
17
33
  if (explicit && !/^deno\.jsonc?$/.test(path.basename(explicit)))
@@ -78,6 +94,7 @@ export class DenoProject {
78
94
  pinned = {};
79
95
  modules = new Map();
80
96
  localPaths = new Map();
97
+ specifiersByPath = new Map();
81
98
  edges = new Map();
82
99
  redirects;
83
100
  npmPackages;
@@ -168,6 +185,7 @@ export class DenoProject {
168
185
  fs.copyFileSync(module.local, local);
169
186
  }
170
187
  this.localPaths.set(module.specifier, path.resolve(local));
188
+ this.specifiersByPath.set(path.resolve(local), module.specifier);
171
189
  }
172
190
  for (const module of graph.modules) {
173
191
  const local = this.localPaths.get(module.specifier);
@@ -236,8 +254,26 @@ export class DenoProject {
236
254
  return this.resolveNpm(spec, from, options) ?? ts.resolveModuleName(spec, from, options, host).resolvedModule;
237
255
  }
238
256
  npmOwner(file) {
257
+ const target = path.normalize(file);
239
258
  return Object.values(this.npmPackages).find(pkg => pkg.localPath &&
240
- (file === pkg.localPath || file.startsWith(pkg.localPath + path.sep)));
259
+ (target === pkg.localPath || target.startsWith(pkg.localPath + path.sep)));
260
+ }
261
+ /** Which registry package a resolved file belongs to (carrick#1260).
262
+ *
263
+ * The inverse of resolution, and the only answer available for a Deno
264
+ * project: nothing it resolves sits under `node_modules`, so reading a
265
+ * package name off the path cannot work. An npm dependency is owned by the
266
+ * graph entry whose cache directory contains the file; a JSR one is named by
267
+ * its own specifier, since its cached copy is content-addressed and carries
268
+ * no identity. A file the workspace itself owns belongs to no package and
269
+ * answers nothing, exactly as a workspace-declared type does under Node. */
270
+ packageOf(file) {
271
+ const resolved = path.resolve(file);
272
+ const owner = this.npmOwner(resolved);
273
+ if (owner)
274
+ return owner.name;
275
+ const specifier = this.specifiersByPath.get(resolved);
276
+ return specifier ? jsrPackageName(specifier) : undefined;
241
277
  }
242
278
  isNpmFile(file) {
243
279
  return !!this.npmOwner(file) || file.split(path.sep).includes('node_modules');
@@ -37,12 +37,22 @@ function projectComponents() {
37
37
  throw new Error('Sidecar not initialized. Call init first.');
38
38
  }
39
39
  if (!components) {
40
- const project = projectLoader.getProject();
41
- const repoRoot = projectLoader.getRepoRoot();
40
+ // Bound here rather than read from the module slot inside the components:
41
+ // a re-init drops `components` and points the slot at another service, and
42
+ // nothing built over this project may follow it there.
43
+ const loader = projectLoader;
44
+ const project = loader.getProject();
45
+ const repoRoot = loader.getRepoRoot();
42
46
  components = {
43
47
  typeBundler: new TypeBundler({ project, repoRoot }),
44
48
  surfaceEmitter: new SurfaceEmitter({ project, repoRoot }),
45
- typeInferrer: new TypeInferrer({ project }),
49
+ // The module graph, where the project resolved through one, is the only
50
+ // thing that can name the package a file belongs to: a Deno service
51
+ // resolves nothing under `node_modules` (carrick#1260).
52
+ typeInferrer: new TypeInferrer({
53
+ project,
54
+ packageOf: (filePath) => loader.packageOf(filePath),
55
+ }),
46
56
  definitionResolver: new DefinitionResolver({ project }),
47
57
  };
48
58
  }
@@ -54,6 +54,13 @@ export declare class ProjectLoader {
54
54
  private project;
55
55
  /** Set by `load()`; builds the ts-morph project on first `getProject()`. */
56
56
  private buildProject;
57
+ /**
58
+ * The Deno graph this project resolved through, when it did (carrick#1260).
59
+ * It is the only thing that can name the package a resolved file belongs to,
60
+ * because a Deno project resolves nothing under `node_modules`. Set by the
61
+ * build, so it is there from the first `getProject()` onwards.
62
+ */
63
+ private denoProject;
57
64
  private readonly repoRoot;
58
65
  private readonly tsconfigPath;
59
66
  private readonly tsconfigSnapshot;
@@ -82,6 +89,12 @@ export declare class ProjectLoader {
82
89
  * @throws Error if load() has not succeeded, or if the build fails
83
90
  */
84
91
  getProject(): Project;
92
+ /**
93
+ * The registry package a resolved file belongs to, when the module graph
94
+ * names one (carrick#1260). `undefined` for a project that did not resolve
95
+ * through Deno, and for a file the workspace itself owns.
96
+ */
97
+ packageOf(filePath: string): string | undefined;
85
98
  /**
86
99
  * Check if the project has been successfully initialized
87
100
  */
@@ -108,6 +108,13 @@ export class ProjectLoader {
108
108
  project = null;
109
109
  /** Set by `load()`; builds the ts-morph project on first `getProject()`. */
110
110
  buildProject = null;
111
+ /**
112
+ * The Deno graph this project resolved through, when it did (carrick#1260).
113
+ * It is the only thing that can name the package a resolved file belongs to,
114
+ * because a Deno project resolves nothing under `node_modules`. Set by the
115
+ * build, so it is there from the first `getProject()` onwards.
116
+ */
117
+ denoProject;
111
118
  repoRoot;
112
119
  tsconfigPath;
113
120
  tsconfigSnapshot;
@@ -175,6 +182,7 @@ export class ProjectLoader {
175
182
  this.log(`Project will load with Deno config: ${denoConfig.configPath}`);
176
183
  this.buildProject = () => {
177
184
  const deno = new DenoProject(denoConfig, this.repoRoot);
185
+ this.denoProject = deno;
178
186
  const project = new Project({
179
187
  compilerOptions: deno.parsed.options,
180
188
  skipAddingFilesFromTsConfig: true,
@@ -317,6 +325,14 @@ export class ProjectLoader {
317
325
  }
318
326
  return this.project;
319
327
  }
328
+ /**
329
+ * The registry package a resolved file belongs to, when the module graph
330
+ * names one (carrick#1260). `undefined` for a project that did not resolve
331
+ * through Deno, and for a file the workspace itself owns.
332
+ */
333
+ packageOf(filePath) {
334
+ return this.denoProject?.packageOf(filePath);
335
+ }
320
336
  /**
321
337
  * Check if the project has been successfully initialized
322
338
  */
@@ -62,6 +62,13 @@ export declare function isExternalOrigin(filePath: string): boolean;
62
62
  export interface TypeInferrerOptions {
63
63
  /** The ts-morph Project instance */
64
64
  project: Project;
65
+ /**
66
+ * The registry package a resolved file belongs to, when the module graph
67
+ * that built the project names one (carrick#1260). Supplied for a project
68
+ * whose resolution went through a graph rather than through `node_modules`;
69
+ * absent otherwise, and the path itself is then the only thing to read.
70
+ */
71
+ packageOf?: (filePath: string) => string | undefined;
65
72
  }
66
73
  /**
67
74
  * TypeInferrer - Extracts types from source code, both explicit and inferred
@@ -72,6 +79,7 @@ export interface TypeInferrerOptions {
72
79
  */
73
80
  export declare class TypeInferrer {
74
81
  private readonly project;
82
+ private readonly packageOf;
75
83
  constructor(options: TypeInferrerOptions);
76
84
  /**
77
85
  * Infer types for the given requests
@@ -727,14 +735,25 @@ export declare class TypeInferrer {
727
735
  */
728
736
  private inferReceiverType;
729
737
  /**
730
- * The npm package name that declares a type, read off its declaration's file
731
- * path. `undefined` when the type has no declaration to read (a top type, a
732
- * primitive, an anonymous object literal) or when its declaration is not
733
- * under a `node_modules` tree — a type the workspace itself declares.
734
- *
735
- * The LAST `node_modules` segment wins, which is what a nested or
736
- * content-addressed store (`node_modules/.store/pkg@1.0.0/node_modules/pkg`)
737
- * requires. Scoped names keep both segments.
738
+ * The npm package name that declares a type. `undefined` when the type has
739
+ * no declaration to read (a top type, a primitive, an anonymous object
740
+ * literal) or when nothing owns its declaration but the workspace itself.
741
+ *
742
+ * The module graph is asked first, where there is one, because it is the
743
+ * only thing that can answer for a project whose resolution does not go
744
+ * through `node_modules`. Deno resolves an npm dependency's types straight
745
+ * out of its own cache
746
+ * (`$DENO_DIR/npm/<registry-host>/<name>/<version>/…`), a path with no
747
+ * `node_modules` segment anywhere in it, so the scan below found no package
748
+ * for ANY dependency-declared type on such a repo and every receiver the
749
+ * compiler had typed correctly went unclassified (carrick#1260). It runs
750
+ * first rather than second because a mixed checkout has both, and the graph
751
+ * is what actually resolved the module.
752
+ *
753
+ * The path scan is the Node answer: the LAST `node_modules` segment wins,
754
+ * which is what a nested or content-addressed store
755
+ * (`node_modules/.store/pkg@1.0.0/node_modules/pkg`) requires. Scoped names
756
+ * keep both segments.
738
757
  */
739
758
  private declaringPackageOf;
740
759
  /**
@@ -249,8 +249,10 @@ function typeText(type, enclosingNode) {
249
249
  */
250
250
  export class TypeInferrer {
251
251
  project;
252
+ packageOf;
252
253
  constructor(options) {
253
254
  this.project = options.project;
255
+ this.packageOf = options.packageOf;
254
256
  }
255
257
  /**
256
258
  * Infer types for the given requests
@@ -2942,14 +2944,25 @@ export class TypeInferrer {
2942
2944
  };
2943
2945
  }
2944
2946
  /**
2945
- * The npm package name that declares a type, read off its declaration's file
2946
- * path. `undefined` when the type has no declaration to read (a top type, a
2947
- * primitive, an anonymous object literal) or when its declaration is not
2948
- * under a `node_modules` tree — a type the workspace itself declares.
2947
+ * The npm package name that declares a type. `undefined` when the type has
2948
+ * no declaration to read (a top type, a primitive, an anonymous object
2949
+ * literal) or when nothing owns its declaration but the workspace itself.
2949
2950
  *
2950
- * The LAST `node_modules` segment wins, which is what a nested or
2951
- * content-addressed store (`node_modules/.store/pkg@1.0.0/node_modules/pkg`)
2952
- * requires. Scoped names keep both segments.
2951
+ * The module graph is asked first, where there is one, because it is the
2952
+ * only thing that can answer for a project whose resolution does not go
2953
+ * through `node_modules`. Deno resolves an npm dependency's types straight
2954
+ * out of its own cache
2955
+ * (`$DENO_DIR/npm/<registry-host>/<name>/<version>/…`), a path with no
2956
+ * `node_modules` segment anywhere in it, so the scan below found no package
2957
+ * for ANY dependency-declared type on such a repo and every receiver the
2958
+ * compiler had typed correctly went unclassified (carrick#1260). It runs
2959
+ * first rather than second because a mixed checkout has both, and the graph
2960
+ * is what actually resolved the module.
2961
+ *
2962
+ * The path scan is the Node answer: the LAST `node_modules` segment wins,
2963
+ * which is what a nested or content-addressed store
2964
+ * (`node_modules/.store/pkg@1.0.0/node_modules/pkg`) requires. Scoped names
2965
+ * keep both segments.
2953
2966
  */
2954
2967
  declaringPackageOf(type) {
2955
2968
  const symbol = type.getSymbol() ?? type.getAliasSymbol();
@@ -2958,6 +2971,10 @@ export class TypeInferrer {
2958
2971
  return undefined;
2959
2972
  }
2960
2973
  const filePath = declaration.getSourceFile().getFilePath().replace(/\\/g, '/');
2974
+ const named = this.packageOf?.(filePath);
2975
+ if (named) {
2976
+ return named;
2977
+ }
2961
2978
  const marker = '/node_modules/';
2962
2979
  const index = filePath.lastIndexOf(marker);
2963
2980
  if (index < 0) {