| 1 | // @effect-diagnostics nodeBuiltinImport:off |
| 2 | import * as NodeChildProcess from "node:child_process"; |
| 3 | import * as NodeFSP from "node:fs/promises"; |
| 4 | import * as NodeFS from "node:fs"; |
| 5 | import * as NodeModule from "node:module"; |
| 6 | import * as NodeOS from "node:os"; |
| 7 | import * as NodePath from "node:path"; |
| 8 | import * as NodeURL from "node:url"; |
| 9 | import { assert, it } from "@effect/vitest"; |
| 10 | import * as Schema from "effect/Schema"; |
| 11 | import { build } from "vite-plus/pack"; |
| 12 | |
| 13 | import serverPackage from "../../apps/server/package.json" with { type: "json" }; |
| 14 | import { |
| 15 | isExternalCliDependency, |
| 16 | selectCliRuntimeExternalDependencies, |
| 17 | shouldBundleCliDependency, |
| 18 | } from "./cli-external-packages.ts"; |
| 19 | import { findEsmImportsOfExternalPackages } from "./cli-executable-imports.ts"; |
| 20 | |
| 21 | const decodeManifest = Schema.decodeUnknownSync( |
| 22 | Schema.fromJsonString( |
| 23 | Schema.Struct({ |
| 24 | name: Schema.optionalKey(Schema.String), |
| 25 | dependencies: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)), |
| 26 | peerDependencies: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)), |
| 27 | }), |
| 28 | ), |
| 29 | ); |
| 30 | |
| 31 | const repoRoot = NodeURL.fileURLToPath(new URL("../..", import.meta.url)); |
| 32 | |
| 33 | // Copy the installed production JS dependency graph, with no pnpm symlinks back |
| 34 | // into the checkout. Optional platform executables are covered by desktop staging |
| 35 | // tests; this probe never creates a local agent or contacts Cursor. |
| 36 | async function stagePackage(name: string, from: string, destination: string): Promise<void> { |
| 37 | const require = NodeModule.createRequire(from); |
| 38 | let source = NodePath.dirname(require.resolve(name)); |
| 39 | while (!NodeFS.existsSync(NodePath.join(source, "package.json"))) { |
| 40 | const parent = NodePath.dirname(source); |
| 41 | if (parent === source) throw new Error(`Cannot locate ${name}`); |
| 42 | source = parent; |
| 43 | } |
| 44 | let manifest = decodeManifest( |
| 45 | await NodeFSP.readFile(NodePath.join(source, "package.json"), "utf8"), |
| 46 | ); |
| 47 | while (manifest.name !== name) { |
| 48 | source = NodePath.dirname(source); |
| 49 | if (source === NodePath.dirname(source)) throw new Error(`Cannot locate ${name}`); |
| 50 | if (NodeFS.existsSync(NodePath.join(source, "package.json"))) { |
| 51 | manifest = decodeManifest( |
| 52 | await NodeFSP.readFile(NodePath.join(source, "package.json"), "utf8"), |
| 53 | ); |
| 54 | } |
| 55 | } |
| 56 | const target = NodePath.join(destination, "node_modules", name); |
| 57 | await NodeFSP.mkdir(NodePath.dirname(target), { recursive: true }); |
| 58 | await NodeFSP.cp(source, target, { |
| 59 | recursive: true, |
| 60 | filter: (entry) => NodePath.basename(entry) !== "node_modules", |
| 61 | }); |
| 62 | const dependencies = { ...manifest.dependencies, ...manifest.peerDependencies }; |
| 63 | for (const dependency of Object.keys(dependencies)) { |
| 64 | await stagePackage(dependency, NodePath.join(source, "package.json"), target); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | it("loads packaged Cursor catalog chunks without credentials or checkout dependencies", async () => { |
| 69 | const scratch = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-cursor-package-")); |
| 70 | try { |
| 71 | // Missing staged dependencies must not resolve from a developer's /tmp tree. |
| 72 | for (let parent = NodePath.dirname(scratch); ; parent = NodePath.dirname(parent)) { |
| 73 | assert.isFalse(NodeFS.existsSync(NodePath.join(parent, "node_modules"))); |
| 74 | if (parent === NodePath.dirname(parent)) break; |
| 75 | } |
| 76 | const entry = NodePath.join(scratch, "probe.mjs"); |
| 77 | const output = NodePath.join(scratch, "package"); |
| 78 | await NodeFSP.writeFile( |
| 79 | entry, |
| 80 | ` |
| 81 | import assert from 'node:assert/strict'; |
| 82 | import { Cursor } from ${JSON.stringify(NodePath.join(repoRoot, "packages/provider-cursor/src/server/sdk.ts"))}; |
| 83 | for (const [operation, request] of [ |
| 84 | ['Cursor.models.list', () => Cursor.models.list({ apiKey: '' })], |
| 85 | ['Cursor.me', () => Cursor.me({ apiKey: '' })], |
| 86 | ]) { |
| 87 | await assert.rejects(request, error => { |
| 88 | assert.equal(error.name, 'ConfigurationError'); |
| 89 | assert.equal(error.operation, operation); |
| 90 | assert.match(error.message, /empty apiKey explicitly/); |
| 91 | return true; |
| 92 | }); |
| 93 | } |
| 94 | console.log('Cursor catalog chunks loaded; empty keys rejected locally'); |
| 95 | `, |
| 96 | ); |
| 97 | await build({ |
| 98 | config: false, |
| 99 | entry: [entry], |
| 100 | outDir: output, |
| 101 | platform: "node", |
| 102 | format: "esm", |
| 103 | dts: false, |
| 104 | logLevel: "error", |
| 105 | deps: { |
| 106 | alwaysBundle: shouldBundleCliDependency, |
| 107 | neverBundle: isExternalCliDependency, |
| 108 | onlyBundle: false, |
| 109 | }, |
| 110 | }); |
| 111 | const roots = selectCliRuntimeExternalDependencies({ |
| 112 | "@cursor/sdk": serverPackage.dependencies["@cursor/sdk"], |
| 113 | }); |
| 114 | for (const name of Object.keys(roots)) { |
| 115 | await stagePackage(name, NodePath.join(repoRoot, "apps/server/package.json"), output); |
| 116 | } |
| 117 | const probe = NodePath.join(output, "probe.mjs"); |
| 118 | assert.deepEqual(findEsmImportsOfExternalPackages(await NodeFSP.readFile(probe, "utf8")), []); |
| 119 | const stdout = NodeChildProcess.execFileSync( |
| 120 | process.execPath, |
| 121 | ["--no-global-search-paths", probe], |
| 122 | { |
| 123 | cwd: output, |
| 124 | env: { |
| 125 | HOME: scratch, |
| 126 | USERPROFILE: scratch, |
| 127 | PATH: "", |
| 128 | SystemRoot: process.env.SystemRoot ?? "", |
| 129 | }, |
| 130 | encoding: "utf8", |
| 131 | timeout: 30_000, |
| 132 | }, |
| 133 | ); |
| 134 | assert.include(stdout, "Cursor catalog chunks loaded; empty keys rejected locally"); |
| 135 | } finally { |
| 136 | await NodeFSP.rm(scratch, { recursive: true, force: true }); |
| 137 | } |
| 138 | }, 60_000); |