rwsdk 1.0.0-alpha.3 → 1.0.0-alpha.5

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.
Files changed (72) hide show
  1. package/dist/lib/getShortName.mjs +6 -1
  2. package/dist/lib/getShortName.test.mjs +25 -0
  3. package/dist/lib/hasPkgScript.d.mts +4 -1
  4. package/dist/lib/hasPkgScript.mjs +9 -6
  5. package/dist/lib/hasPkgScript.test.d.mts +1 -0
  6. package/dist/lib/hasPkgScript.test.mjs +33 -0
  7. package/dist/lib/jsonUtils.mjs +3 -0
  8. package/dist/lib/jsonUtils.test.d.mts +1 -0
  9. package/dist/lib/jsonUtils.test.mjs +90 -0
  10. package/dist/lib/normalizeModulePath.d.mts +5 -0
  11. package/dist/lib/normalizeModulePath.mjs +1 -1
  12. package/dist/lib/normalizeModulePath.test.d.mts +1 -0
  13. package/dist/lib/{normalizeModulePath.test.js → normalizeModulePath.test.mjs} +20 -1
  14. package/dist/runtime/lib/memoizeOnId.test.d.ts +1 -0
  15. package/dist/runtime/lib/memoizeOnId.test.js +49 -0
  16. package/dist/runtime/lib/realtime/protocol.test.d.ts +1 -0
  17. package/dist/runtime/lib/realtime/protocol.test.js +107 -0
  18. package/dist/runtime/lib/realtime/shared.test.d.ts +1 -0
  19. package/dist/runtime/lib/realtime/shared.test.js +18 -0
  20. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.d.ts +1 -0
  21. package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +66 -0
  22. package/dist/runtime/lib/router.test.js +1 -1
  23. package/dist/runtime/lib/turnstile/verifyTurnstileToken.d.ts +2 -1
  24. package/dist/runtime/lib/turnstile/verifyTurnstileToken.js +6 -6
  25. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.d.ts +1 -0
  26. package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +49 -0
  27. package/dist/vite/checkIsUsingPrisma.d.mts +4 -0
  28. package/dist/vite/checkIsUsingPrisma.mjs +2 -2
  29. package/dist/vite/checkIsUsingPrisma.test.d.mts +1 -0
  30. package/dist/vite/checkIsUsingPrisma.test.mjs +30 -0
  31. package/dist/vite/configPlugin.mjs +22 -0
  32. package/dist/vite/createDirectiveLookupPlugin.d.mts +9 -0
  33. package/dist/vite/createDirectiveLookupPlugin.mjs +33 -29
  34. package/dist/vite/createDirectiveLookupPlugin.test.d.mts +1 -0
  35. package/dist/vite/createDirectiveLookupPlugin.test.mjs +40 -0
  36. package/dist/vite/directiveModulesDevPlugin.d.mts +2 -0
  37. package/dist/vite/directiveModulesDevPlugin.mjs +3 -3
  38. package/dist/vite/directiveModulesDevPlugin.test.d.mts +1 -0
  39. package/dist/vite/directiveModulesDevPlugin.test.mjs +59 -0
  40. package/dist/vite/directivesPlugin.d.mts +1 -0
  41. package/dist/vite/directivesPlugin.mjs +1 -1
  42. package/dist/vite/directivesPlugin.test.d.mts +1 -0
  43. package/dist/vite/directivesPlugin.test.mjs +24 -0
  44. package/dist/vite/ensureAliasArray.test.d.mts +1 -0
  45. package/dist/vite/ensureAliasArray.test.mjs +71 -0
  46. package/dist/vite/findSpecifiers.mjs +2 -1
  47. package/dist/vite/findSpecifiers.test.d.mts +1 -0
  48. package/dist/vite/findSpecifiers.test.mjs +202 -0
  49. package/dist/vite/findSsrSpecifiers.test.d.mts +1 -0
  50. package/dist/vite/findSsrSpecifiers.test.mjs +99 -0
  51. package/dist/vite/hasDirective.test.d.mts +1 -0
  52. package/dist/vite/hasDirective.test.mjs +109 -0
  53. package/dist/vite/isJsFile.test.d.mts +1 -0
  54. package/dist/vite/isJsFile.test.mjs +38 -0
  55. package/dist/vite/linkerPlugin.d.mts +8 -0
  56. package/dist/vite/linkerPlugin.mjs +30 -22
  57. package/dist/vite/linkerPlugin.test.d.mts +1 -0
  58. package/dist/vite/linkerPlugin.test.mjs +41 -0
  59. package/dist/vite/miniflareHMRPlugin.d.mts +5 -0
  60. package/dist/vite/miniflareHMRPlugin.mjs +2 -2
  61. package/dist/vite/miniflareHMRPlugin.test.d.mts +1 -0
  62. package/dist/vite/miniflareHMRPlugin.test.mjs +42 -0
  63. package/dist/vite/redwoodPlugin.d.mts +7 -0
  64. package/dist/vite/redwoodPlugin.mjs +7 -3
  65. package/dist/vite/redwoodPlugin.test.d.mts +1 -0
  66. package/dist/vite/redwoodPlugin.test.mjs +34 -0
  67. package/dist/vite/runDirectivesScan.d.mts +19 -0
  68. package/dist/vite/runDirectivesScan.mjs +52 -44
  69. package/dist/vite/runDirectivesScan.test.d.mts +1 -0
  70. package/dist/vite/runDirectivesScan.test.mjs +73 -0
  71. package/package.json +1 -1
  72. /package/dist/lib/{normalizeModulePath.test.d.ts → getShortName.test.d.mts} +0 -0
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isUsingPrisma } from "./checkIsUsingPrisma.mjs";
3
+ describe("isUsingPrisma", () => {
4
+ it("should return true if prisma client is resolved", () => {
5
+ const resolver = () => "/path/to/prisma/client";
6
+ const result = isUsingPrisma({
7
+ projectRootDir: "/test/project",
8
+ resolver: resolver,
9
+ });
10
+ expect(result).toBe(true);
11
+ });
12
+ it("should return false if prisma client is not resolved", () => {
13
+ const resolver = () => false;
14
+ const result = isUsingPrisma({
15
+ projectRootDir: "/test/project",
16
+ resolver: resolver,
17
+ });
18
+ expect(result).toBe(false);
19
+ });
20
+ it("should return false if resolver throws", () => {
21
+ const resolver = () => {
22
+ throw new Error("Module not found");
23
+ };
24
+ const result = isUsingPrisma({
25
+ projectRootDir: "/test/project",
26
+ resolver: resolver,
27
+ });
28
+ expect(result).toBe(false);
29
+ });
30
+ });
@@ -136,8 +136,30 @@ export const configPlugin = ({ silent, projectRootDir, workerEntryPathname, clie
136
136
  outDir: path.dirname(INTERMEDIATE_SSR_BRIDGE_PATH),
137
137
  rollupOptions: {
138
138
  output: {
139
+ // context(justinvdm, 15 Sep 2025): The SSR bundle is a
140
+ // pre-compiled artifact. When the linker pass bundles it into
141
+ // the intermediate worker bundle (another pre-compiled
142
+ // artifact), Rollup merges their top-level scopes. Since both
143
+ // may have identical minified identifiers (e.g., `l0`), this
144
+ // causes a redeclaration error. To solve this, we wrap the SSR
145
+ // bundle in an exporting IIFE. This creates a scope boundary,
146
+ // preventing symbol collisions while producing a valid,
147
+ // tree-shakeable ES module. The inline plugin below removes the
148
+ // original `export` statement from the bundle to prevent syntax
149
+ // errors.
139
150
  inlineDynamicImports: true,
151
+ banner: `export const { renderRscThenableToHtmlStream, ssrWebpackRequire, ssrGetModuleExport } = (function() {`,
152
+ footer: `return { renderRscThenableToHtmlStream, ssrWebpackRequire, ssrGetModuleExport };\n})();`,
140
153
  },
154
+ plugins: [
155
+ {
156
+ name: "rwsdk:ssr-bridge-exports",
157
+ renderChunk(code) {
158
+ // Remove the original export statement as it's now handled by the banner/footer
159
+ return code.replace(/export\s*{[^}]+};?/, "");
160
+ },
161
+ },
162
+ ],
141
163
  },
142
164
  },
143
165
  },
@@ -1,4 +1,13 @@
1
1
  import { Plugin } from "vite";
2
+ export declare function generateLookupMap({ files, isDev, kind, exportName, }: {
3
+ files: Set<string>;
4
+ isDev: boolean;
5
+ kind: "client" | "server";
6
+ exportName: string;
7
+ }): {
8
+ code: string;
9
+ map: import("magic-string").SourceMap;
10
+ };
2
11
  interface DirectiveLookupConfig {
3
12
  kind: "client" | "server";
4
13
  directive: "use client" | "use server";
@@ -2,6 +2,33 @@ import MagicString from "magic-string";
2
2
  import path from "path";
3
3
  import debug from "debug";
4
4
  import { VENDOR_CLIENT_BARREL_EXPORT_PATH, VENDOR_SERVER_BARREL_EXPORT_PATH, } from "../lib/constants.mjs";
5
+ export function generateLookupMap({ files, isDev, kind, exportName, }) {
6
+ const s = new MagicString(`
7
+ export const ${exportName} = {
8
+ ${Array.from(files)
9
+ .map((file) => {
10
+ if (file.includes("node_modules") && isDev) {
11
+ const barrelPath = kind === "client"
12
+ ? VENDOR_CLIENT_BARREL_EXPORT_PATH
13
+ : VENDOR_SERVER_BARREL_EXPORT_PATH;
14
+ return `
15
+ "${file}": () => import("${barrelPath}").then(m => m.default["${file}"]),
16
+ `;
17
+ }
18
+ else {
19
+ return `
20
+ "${file}": () => import("${file}"),
21
+ `;
22
+ }
23
+ })
24
+ .join("")}
25
+ };
26
+ `);
27
+ return {
28
+ code: s.toString(),
29
+ map: s.generateMap(),
30
+ };
31
+ }
5
32
  export const createDirectiveLookupPlugin = async ({ projectRootDir, files, config, }) => {
6
33
  const debugNamespace = `rwsdk:vite:${config.pluginName}`;
7
34
  const log = debug(debugNamespace);
@@ -121,35 +148,12 @@ export const createDirectiveLookupPlugin = async ({ projectRootDir, files, confi
121
148
  log("Loading %s module with %d files", config.virtualModuleName, files.size);
122
149
  const environment = this.environment?.name || "client";
123
150
  log("Current environment: %s, isDev: %s", environment, isDev);
124
- const s = new MagicString(`
125
- export const ${config.exportName} = {
126
- ${Array.from(files)
127
- .map((file) => {
128
- if (file.includes("node_modules") && isDev) {
129
- const barrelPath = config.kind === "client"
130
- ? VENDOR_CLIENT_BARREL_EXPORT_PATH
131
- : VENDOR_SERVER_BARREL_EXPORT_PATH;
132
- return `
133
- "${file}": () => import("${barrelPath}").then(m => m.default["${file}"]),
134
- `;
135
- }
136
- else {
137
- return `
138
- "${file}": () => import("${file}"),
139
- `;
140
- }
141
- })
142
- .join("")}
143
- };
144
- `);
145
- const code = s.toString();
146
- const map = s.generateMap();
147
- log("Generated virtual module code length: %d", code.length);
148
- process.env.VERBOSE && log("Generated virtual module code: %s", code);
149
- return {
150
- code,
151
- map,
152
- };
151
+ return generateLookupMap({
152
+ files,
153
+ isDev,
154
+ kind: config.kind,
155
+ exportName: config.exportName,
156
+ });
153
157
  }
154
158
  },
155
159
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateLookupMap } from "./createDirectiveLookupPlugin.mjs";
3
+ import { VENDOR_CLIENT_BARREL_EXPORT_PATH, VENDOR_SERVER_BARREL_EXPORT_PATH, } from "../lib/constants.mjs";
4
+ describe("generateLookupMap", () => {
5
+ const files = new Set([
6
+ "src/app.js",
7
+ "node_modules/lib-a/index.js",
8
+ "src/component.tsx",
9
+ ]);
10
+ it("should generate correct map for client in dev", () => {
11
+ const result = generateLookupMap({
12
+ files,
13
+ isDev: true,
14
+ kind: "client",
15
+ exportName: "clientLookup",
16
+ });
17
+ expect(result.code).toContain("export const clientLookup = {");
18
+ expect(result.code).toContain(`"src/app.js": () => import("src/app.js")`);
19
+ expect(result.code).toContain(`"node_modules/lib-a/index.js": () => import("${VENDOR_CLIENT_BARREL_EXPORT_PATH}").then(m => m.default["node_modules/lib-a/index.js"])`);
20
+ });
21
+ it("should generate correct map for server in dev", () => {
22
+ const result = generateLookupMap({
23
+ files,
24
+ isDev: true,
25
+ kind: "server",
26
+ exportName: "serverLookup",
27
+ });
28
+ expect(result.code).toContain("export const serverLookup = {");
29
+ expect(result.code).toContain(`"node_modules/lib-a/index.js": () => import("${VENDOR_SERVER_BARREL_EXPORT_PATH}").then(m => m.default["node_modules/lib-a/index.js"])`);
30
+ });
31
+ it("should generate correct map for prod (isDev: false)", () => {
32
+ const result = generateLookupMap({
33
+ files,
34
+ isDev: false,
35
+ kind: "client",
36
+ exportName: "clientLookup",
37
+ });
38
+ expect(result.code).toContain(`"node_modules/lib-a/index.js": () => import("node_modules/lib-a/index.js")`);
39
+ });
40
+ });
@@ -1,4 +1,6 @@
1
1
  import { Plugin } from "vite";
2
+ export declare const generateVendorBarrelContent: (files: Set<string>, projectRootDir: string) => string;
3
+ export declare const generateAppBarrelContent: (files: Set<string>, projectRootDir: string) => string;
2
4
  export declare const directiveModulesDevPlugin: ({ clientFiles, serverFiles, projectRootDir, }: {
3
5
  clientFiles: Set<string>;
4
6
  serverFiles: Set<string>;
@@ -4,7 +4,7 @@ import { writeFileSync, mkdirSync, mkdtempSync } from "node:fs";
4
4
  import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
5
5
  import { VENDOR_CLIENT_BARREL_PATH, VENDOR_SERVER_BARREL_PATH, VENDOR_CLIENT_BARREL_EXPORT_PATH, VENDOR_SERVER_BARREL_EXPORT_PATH, } from "../lib/constants.mjs";
6
6
  import { runDirectivesScan } from "./runDirectivesScan.mjs";
7
- const generateVendorBarrelContent = (files, projectRootDir) => {
7
+ export const generateVendorBarrelContent = (files, projectRootDir) => {
8
8
  const imports = [...files]
9
9
  .filter((file) => file.includes("node_modules"))
10
10
  .map((file, i) => `import * as M${i} from '${normalizeModulePath(file, projectRootDir, {
@@ -19,7 +19,7 @@ const generateVendorBarrelContent = (files, projectRootDir) => {
19
19
  "\n};";
20
20
  return `${imports}\n\n${exports}`;
21
21
  };
22
- const generateAppBarrelContent = (files, projectRootDir, barrelFilePath) => {
22
+ export const generateAppBarrelContent = (files, projectRootDir) => {
23
23
  return [...files]
24
24
  .filter((file) => !file.includes("node_modules"))
25
25
  .map((file) => {
@@ -129,7 +129,7 @@ export const directiveModulesDevPlugin = ({ clientFiles, serverFiles, projectRoo
129
129
  build.onLoad({ filter: /.*/, namespace: "rwsdk-app-barrel-ns" }, (args) => {
130
130
  const isServerBarrel = args.path.includes("app-server-barrel");
131
131
  const files = isServerBarrel ? serverFiles : clientFiles;
132
- const content = generateAppBarrelContent(files, projectRootDir, args.path);
132
+ const content = generateAppBarrelContent(files, projectRootDir);
133
133
  return {
134
134
  contents: content,
135
135
  loader: "js",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { generateVendorBarrelContent, generateAppBarrelContent, } from "./directiveModulesDevPlugin.mjs";
3
+ describe("directiveModulesDevPlugin helpers", () => {
4
+ const projectRootDir = "/Users/test/project";
5
+ describe("generateVendorBarrelContent", () => {
6
+ it("should generate correct content for vendor files", () => {
7
+ const files = new Set([
8
+ "node_modules/lib-a/index.js",
9
+ "src/app.js",
10
+ "node_modules/lib-b/component.tsx",
11
+ ]);
12
+ const content = generateVendorBarrelContent(files, projectRootDir);
13
+ const expected = `import * as M0 from '${projectRootDir}/node_modules/lib-a/index.js';
14
+ import * as M1 from '${projectRootDir}/node_modules/lib-b/component.tsx';
15
+
16
+ export default {
17
+ '/node_modules/lib-a/index.js': M0,
18
+ '/node_modules/lib-b/component.tsx': M1,
19
+ };`;
20
+ expect(content).toEqual(expected);
21
+ });
22
+ it("should return empty content if no vendor files", () => {
23
+ const files = new Set(["src/app.js", "src/component.tsx"]);
24
+ const content = generateVendorBarrelContent(files, projectRootDir);
25
+ expect(content).toEqual("\n\nexport default {\n\n};");
26
+ });
27
+ it("should handle an empty file set", () => {
28
+ const files = new Set();
29
+ const content = generateVendorBarrelContent(files, projectRootDir);
30
+ expect(content).toEqual("\n\nexport default {\n\n};");
31
+ });
32
+ });
33
+ describe("generateAppBarrelContent", () => {
34
+ it("should generate correct content for app files", () => {
35
+ const files = new Set([
36
+ "src/app.js",
37
+ "node_modules/lib-a/index.js",
38
+ "src/component.tsx",
39
+ ]);
40
+ const content = generateAppBarrelContent(files, projectRootDir);
41
+ const expected = `import "${projectRootDir}/src/app.js";
42
+ import "${projectRootDir}/src/component.tsx";`;
43
+ expect(content).toEqual(expected);
44
+ });
45
+ it("should return empty content if no app files", () => {
46
+ const files = new Set([
47
+ "node_modules/lib-a/index.js",
48
+ "node_modules/lib-b/component.tsx",
49
+ ]);
50
+ const content = generateAppBarrelContent(files, projectRootDir);
51
+ expect(content).toEqual("");
52
+ });
53
+ it("should handle an empty file set", () => {
54
+ const files = new Set();
55
+ const content = generateAppBarrelContent(files, projectRootDir);
56
+ expect(content).toEqual("");
57
+ });
58
+ });
59
+ });
@@ -1,4 +1,5 @@
1
1
  import { Plugin } from "vite";
2
+ export declare const getLoader: (filePath: string) => "jsx" | "js" | "ts" | "tsx";
2
3
  export declare const directivesPlugin: ({ projectRootDir, clientFiles, serverFiles, }: {
3
4
  projectRootDir: string;
4
5
  clientFiles: Set<string>;
@@ -5,7 +5,7 @@ import { transformClientComponents } from "./transformClientComponents.mjs";
5
5
  import { transformServerFunctions } from "./transformServerFunctions.mjs";
6
6
  import { normalizeModulePath } from "../lib/normalizeModulePath.mjs";
7
7
  const log = debug("rwsdk:vite:rsc-directives-plugin");
8
- const getLoader = (filePath) => {
8
+ export const getLoader = (filePath) => {
9
9
  const ext = path.extname(filePath).slice(1);
10
10
  switch (ext) {
11
11
  case "mjs":
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { getLoader } from "./directivesPlugin.mjs";
3
+ describe("getLoader", () => {
4
+ const testCases = [
5
+ { path: "file.js", expected: "js" },
6
+ { path: "file.mjs", expected: "js" },
7
+ { path: "file.cjs", expected: "js" },
8
+ { path: "file.ts", expected: "ts" },
9
+ { path: "file.mts", expected: "ts" },
10
+ { path: "file.cts", expected: "ts" },
11
+ { path: "file.jsx", expected: "jsx" },
12
+ { path: "file.tsx", expected: "tsx" },
13
+ { path: "/path/to/component.ts", expected: "ts" },
14
+ { path: "../relative/path.jsx", expected: "jsx" },
15
+ { path: "file.css", expected: "js" }, // default case
16
+ { path: "file.json", expected: "js" }, // default case
17
+ { path: "file", expected: "js" }, // no extension
18
+ ];
19
+ testCases.forEach(({ path, expected }) => {
20
+ it(`should return "${expected}" for "${path}"`, () => {
21
+ expect(getLoader(path)).toBe(expected);
22
+ });
23
+ });
24
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ensureAliasArray } from "./ensureAliasArray.mjs";
3
+ describe("ensureAliasArray", () => {
4
+ it("should create resolve and alias array if resolve is undefined", () => {
5
+ const config = {};
6
+ const result = ensureAliasArray(config);
7
+ expect(result).toEqual([]);
8
+ expect(config.resolve?.alias).toEqual([]);
9
+ expect(result).toBe(config.resolve?.alias);
10
+ });
11
+ it("should create alias array if alias is undefined", () => {
12
+ const config = { resolve: {} };
13
+ const result = ensureAliasArray(config);
14
+ expect(result).toEqual([]);
15
+ expect(config.resolve?.alias).toEqual([]);
16
+ expect(result).toBe(config.resolve?.alias);
17
+ });
18
+ it("should convert an alias object to an array", () => {
19
+ const config = {
20
+ resolve: {
21
+ alias: {
22
+ find: "/replacement",
23
+ another: "/another-replacement",
24
+ },
25
+ },
26
+ };
27
+ const result = ensureAliasArray(config);
28
+ const expected = [
29
+ { find: "find", replacement: "/replacement" },
30
+ { find: "another", replacement: "/another-replacement" },
31
+ ];
32
+ expect(result).toEqual(expected);
33
+ expect(config.resolve?.alias).toEqual(expected);
34
+ expect(result).toBe(config.resolve?.alias);
35
+ });
36
+ it("should return a clone of an existing alias array", () => {
37
+ const originalAlias = [{ find: "find", replacement: "/replacement" }];
38
+ const config = {
39
+ resolve: {
40
+ alias: originalAlias,
41
+ },
42
+ };
43
+ const result = ensureAliasArray(config);
44
+ expect(result).toEqual(originalAlias);
45
+ expect(result).not.toBe(originalAlias);
46
+ expect(config.resolve?.alias).toEqual(originalAlias);
47
+ expect(config.resolve?.alias).not.toBe(originalAlias);
48
+ });
49
+ it("should handle an empty alias object", () => {
50
+ const config = {
51
+ resolve: {
52
+ alias: {},
53
+ },
54
+ };
55
+ const result = ensureAliasArray(config);
56
+ expect(result).toEqual([]);
57
+ expect(config.resolve?.alias).toEqual([]);
58
+ });
59
+ it("should handle an empty alias array", () => {
60
+ const config = {
61
+ resolve: {
62
+ alias: [],
63
+ },
64
+ };
65
+ const originalAlias = config.resolve?.alias;
66
+ const result = ensureAliasArray(config);
67
+ expect(result).toEqual([]);
68
+ expect(config.resolve?.alias).toEqual([]);
69
+ expect(result).not.toBe(originalAlias);
70
+ });
71
+ });
@@ -178,7 +178,8 @@ export function findExports(id, code, log) {
178
178
  });
179
179
  logger("Found default export: %s", name);
180
180
  }
181
- else if (matchText.includes("export {")) {
181
+ else if (matchText.includes("export {") &&
182
+ !match.getMatch("MODULE")) {
182
183
  // Local export declaration
183
184
  const exportListMatch = matchText.match(/export\s*\{\s*([^}]+)\s*\}/);
184
185
  if (exportListMatch) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,202 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { findImportSpecifiers, findExports } from "./findSpecifiers.mjs";
3
+ function dedupeImports(imports) {
4
+ const seen = new Set();
5
+ return imports.filter((imp) => {
6
+ const key = `${imp.s}:${imp.e}`;
7
+ if (seen.has(key)) {
8
+ return false;
9
+ }
10
+ seen.add(key);
11
+ return true;
12
+ });
13
+ }
14
+ describe("findSpecifiers", () => {
15
+ describe("findImportSpecifiers", () => {
16
+ it("should find various import types", () => {
17
+ const code = `
18
+ import { a } from "module-a";
19
+ import b from 'module-b';
20
+ import * as c from "module-c";
21
+ import "module-d";
22
+ const e = import('module-e');
23
+ const f = require("module-f");
24
+ `;
25
+ const results = findImportSpecifiers("test.ts", code, []);
26
+ const specifiers = dedupeImports(results).map((r) => r.raw);
27
+ expect(specifiers).toEqual([
28
+ "module-a",
29
+ "module-c",
30
+ "module-b",
31
+ "module-d",
32
+ "module-e",
33
+ "module-f",
34
+ ]);
35
+ });
36
+ it("should find re-exports", () => {
37
+ const code = `
38
+ export { a } from "module-a";
39
+ export * from 'module-b';
40
+ `;
41
+ const results = findImportSpecifiers("test.ts", code, []);
42
+ const specifiers = dedupeImports(results).map((r) => r.raw);
43
+ expect(specifiers).toEqual(["module-a", "module-b"]);
44
+ });
45
+ it("should ignore specified patterns", () => {
46
+ const code = `
47
+ import { a } from "module-a";
48
+ import b from 'module-b';
49
+ import c from "ignored-module";
50
+ `;
51
+ const results = findImportSpecifiers("test.ts", code, [/ignored/]);
52
+ const specifiers = dedupeImports(results).map((r) => r.raw);
53
+ expect(specifiers).toEqual(["module-a", "module-b"]);
54
+ });
55
+ it("should ignore virtual modules", () => {
56
+ const code = `import a from "virtual:my-virtual-module";`;
57
+ const results = findImportSpecifiers("test.ts", code, []);
58
+ expect(results).toHaveLength(0);
59
+ });
60
+ it("should ignore __rwsdknossr modules", () => {
61
+ const code = `import a from "some-module?__rwsdknossr";`;
62
+ const results = findImportSpecifiers("test.ts", code, []);
63
+ expect(results).toHaveLength(0);
64
+ });
65
+ it("should handle tsx files", () => {
66
+ const code = `
67
+ import React from 'react';
68
+ const App = () => <div>Hello</div>;
69
+ export default App;
70
+ `;
71
+ const results = findImportSpecifiers("test.tsx", code, []);
72
+ const specifiers = dedupeImports(results).map((r) => r.raw);
73
+ expect(specifiers).toEqual(["react"]);
74
+ });
75
+ it("should return correct positions", () => {
76
+ const code = `import { a } from "module-a";`;
77
+ const results = findImportSpecifiers("test.ts", code, []);
78
+ const deduped = dedupeImports(results);
79
+ expect(deduped).toHaveLength(1);
80
+ expect(deduped[0].s).toBe(19);
81
+ expect(deduped[0].e).toBe(27);
82
+ expect(code.substring(deduped[0].s, deduped[0].e)).toBe("module-a");
83
+ });
84
+ });
85
+ describe("findExports", () => {
86
+ it("should find named exports (const, let, function)", () => {
87
+ const code = `
88
+ export const a = 1;
89
+ export let b = 2;
90
+ export function c() {}
91
+ export async function d() {}
92
+ `;
93
+ const results = findExports("test.ts", code);
94
+ expect(results).toEqual([
95
+ { name: "a", isDefault: false },
96
+ { name: "b", isDefault: false },
97
+ { name: "c", isDefault: false },
98
+ { name: "d", isDefault: false },
99
+ ]);
100
+ });
101
+ it("should find default exports", () => {
102
+ const code = `
103
+ export default function myFunc() {}
104
+ const b = 1;
105
+ export default b;
106
+ `;
107
+ const results = findExports("test.ts", code);
108
+ expect(results).toEqual([
109
+ { name: "myFunc", isDefault: true },
110
+ { name: "default", isDefault: true },
111
+ ]);
112
+ });
113
+ it("should find export declarations", () => {
114
+ const code = `
115
+ const a = 1;
116
+ const b = 2;
117
+ export { a, b as c };
118
+ `;
119
+ const results = findExports("test.ts", code);
120
+ expect(results).toEqual([
121
+ {
122
+ name: "a",
123
+ isDefault: false,
124
+ alias: undefined,
125
+ originalName: "a",
126
+ },
127
+ {
128
+ name: "c",
129
+ isDefault: false,
130
+ alias: "c",
131
+ originalName: "b",
132
+ },
133
+ ]);
134
+ });
135
+ it("should find re-exports", () => {
136
+ const code = `
137
+ export { a, b as c } from 'module-a';
138
+ export { default as d } from 'module-b';
139
+ `;
140
+ const results = findExports("test.ts", code);
141
+ expect(results).toEqual([
142
+ {
143
+ name: "a",
144
+ isDefault: false,
145
+ alias: undefined,
146
+ originalName: "a",
147
+ isReExport: true,
148
+ moduleSpecifier: "module-a",
149
+ },
150
+ {
151
+ name: "c",
152
+ isDefault: false,
153
+ alias: "c",
154
+ originalName: "b",
155
+ isReExport: true,
156
+ moduleSpecifier: "module-a",
157
+ },
158
+ {
159
+ name: "d",
160
+ isDefault: true,
161
+ alias: "d",
162
+ originalName: "default",
163
+ isReExport: true,
164
+ moduleSpecifier: "module-b",
165
+ },
166
+ {
167
+ alias: undefined,
168
+ isDefault: false,
169
+ name: "a",
170
+ originalName: "a",
171
+ },
172
+ {
173
+ alias: "c",
174
+ isDefault: false,
175
+ name: "c",
176
+ originalName: "b",
177
+ },
178
+ {
179
+ alias: "d",
180
+ isDefault: true,
181
+ name: "d",
182
+ originalName: "default",
183
+ },
184
+ ]);
185
+ });
186
+ it("should handle mixed exports", () => {
187
+ const code = `
188
+ export const a = 1;
189
+ export default function b() {}
190
+ const c = 3;
191
+ export { c };
192
+ `;
193
+ const results = findExports("test.tsx", code);
194
+ expect(results).toEqual([
195
+ { name: "a", isDefault: false },
196
+ { name: "b", isDefault: true },
197
+ { isDefault: true, name: "default" },
198
+ { name: "c", isDefault: false, originalName: "c", alias: undefined },
199
+ ]);
200
+ });
201
+ });
202
+ });
@@ -0,0 +1 @@
1
+ export {};