@swissjs/swite 0.4.1 → 0.4.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.
Files changed (36) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/__tests__/import-rewriter-bug.test.ts +122 -122
  3. package/__tests__/security-r001-r002.test.ts +190 -190
  4. package/dist/cli.js +0 -0
  5. package/dist/dev-engine/hmr/hmr-client-template.js +111 -111
  6. package/dist/dev-engine/middleware/middleware-setup.js +4 -3
  7. package/docs/architecture/build-pipeline.md +97 -97
  8. package/docs/architecture/dev-server.md +87 -87
  9. package/docs/architecture/hmr.md +78 -78
  10. package/docs/architecture/import-rewriting.md +101 -101
  11. package/docs/architecture/index.md +16 -16
  12. package/docs/architecture/python-integration.md +93 -93
  13. package/docs/architecture/resolution.md +92 -92
  14. package/docs/cli/build.md +78 -78
  15. package/docs/cli/dev.md +90 -90
  16. package/docs/cli/index.md +15 -15
  17. package/docs/cli/start.md +45 -45
  18. package/docs/development/contributing.md +74 -74
  19. package/docs/development/index.md +12 -12
  20. package/docs/development/internals.md +101 -101
  21. package/docs/guide/configuration.md +89 -89
  22. package/docs/guide/index.md +13 -13
  23. package/docs/guide/project-structure.md +75 -75
  24. package/docs/guide/quickstart.md +113 -113
  25. package/docs/index.md +16 -16
  26. package/package.json +10 -9
  27. package/src/config/env.ts +98 -98
  28. package/src/dev-engine/handlers/ui-handler.ts +30 -30
  29. package/src/dev-engine/handlers/uix-handler.ts +21 -21
  30. package/src/dev-engine/hmr/hmr-client-template.ts +122 -122
  31. package/src/dev-engine/middleware/middleware-setup.ts +354 -354
  32. package/src/dev-engine/middleware/static-files.ts +813 -813
  33. package/src/resolution/cdn/cdn-fallback.ts +40 -40
  34. package/src/resolution/path/path-fixup.ts +27 -27
  35. package/src/resolution/rewriting/import-rewriter.ts +237 -237
  36. package/src/resolution/symlink-registry.ts +114 -114
@@ -1,114 +1,114 @@
1
- /*
2
- * Symlink Registry - CG-03 root cause fix
3
- *
4
- * fs.realpath() throughout Swite's handler chain resolves symlinks to absolute
5
- * filesystem paths (e.g. /mnt/c/.../swiss-lib/packages/core/src/index.ts).
6
- * These leak into toUrl() and hit the startsWith("/") early-return before the
7
- * proper node_modules/swiss-lib handling logic is reached.
8
- *
9
- * At server startup we scan node_modules directories for symlinks and build a
10
- * map: realpath → /node_modules/<pkg-name>
11
- * toUrl() consults this registry FIRST and short-circuits back to the correct
12
- * browser URL.
13
- */
14
-
15
- import { promises as fs } from "node:fs";
16
- import path from "node:path";
17
-
18
- // realpath (normalized, forward slashes) → browser URL prefix
19
- const registry = new Map<string, string>();
20
-
21
- export async function buildSymlinkRegistry(
22
- nodeModulesDirs: string[]
23
- ): Promise<void> {
24
- registry.clear();
25
- for (const dir of nodeModulesDirs) {
26
- await scanNodeModulesDir(dir);
27
- }
28
- console.log(
29
- `[SWITE] Symlink registry built: ${registry.size} entries from ${nodeModulesDirs.length} node_modules dirs`
30
- );
31
- }
32
-
33
- async function scanNodeModulesDir(nodeModulesDir: string): Promise<void> {
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- let dirents: any[];
36
- try {
37
- dirents = await fs.readdir(nodeModulesDir, {
38
- withFileTypes: true,
39
- encoding: "utf8",
40
- });
41
- } catch {
42
- return; // dir doesn't exist — skip silently
43
- }
44
-
45
- for (const dirent of dirents) {
46
- if (dirent.name.startsWith(".")) continue;
47
-
48
- if (dirent.name.startsWith("@")) {
49
- // Scoped scope directory — scan one level deeper
50
- const scopeDir = path.join(nodeModulesDir, dirent.name);
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- let scopedDirents: any[];
53
- try {
54
- scopedDirents = await fs.readdir(scopeDir, {
55
- withFileTypes: true,
56
- encoding: "utf8",
57
- });
58
- } catch {
59
- continue;
60
- }
61
- for (const scoped of scopedDirents) {
62
- if (scoped.isSymbolicLink()) {
63
- await registerSymlink(
64
- path.join(scopeDir, scoped.name),
65
- `${dirent.name}/${scoped.name}`
66
- );
67
- }
68
- }
69
- } else if (dirent.isSymbolicLink()) {
70
- await registerSymlink(
71
- path.join(nodeModulesDir, dirent.name),
72
- dirent.name
73
- );
74
- }
75
- }
76
- }
77
-
78
- async function registerSymlink(
79
- symlinkPath: string,
80
- pkgName: string
81
- ): Promise<void> {
82
- try {
83
- const realPath = await fs.realpath(symlinkPath);
84
- const key = realPath.replace(/\\/g, "/");
85
- const value = `/node_modules/${pkgName}`;
86
- registry.set(key, value);
87
- console.log(`[SWITE] Registry: ${pkgName}: ${key} → ${value}`);
88
- } catch {
89
- // broken symlink — ignore
90
- }
91
- }
92
-
93
- /**
94
- * Look up an absolute filesystem path in the symlink registry.
95
- *
96
- * Returns the browser URL if absolutePath is, or is within, a package whose
97
- * realpath was registered at startup. Returns null if not found.
98
- *
99
- * Example:
100
- * /mnt/c/.../swiss-lib/packages/core/src/index.ts
101
- * → /node_modules/@swissjs/core/src/index.ts
102
- */
103
- export function lookupInSymlinkRegistry(absolutePath: string): string | null {
104
- const normalized = absolutePath.replace(/\\/g, "/");
105
- for (const [realPkgPath, browserPrefix] of registry) {
106
- if (normalized === realPkgPath) {
107
- return browserPrefix;
108
- }
109
- if (normalized.startsWith(realPkgPath + "/")) {
110
- return browserPrefix + normalized.slice(realPkgPath.length);
111
- }
112
- }
113
- return null;
114
- }
1
+ /*
2
+ * Symlink Registry - CG-03 root cause fix
3
+ *
4
+ * fs.realpath() throughout Swite's handler chain resolves symlinks to absolute
5
+ * filesystem paths (e.g. /mnt/c/.../swiss-lib/packages/core/src/index.ts).
6
+ * These leak into toUrl() and hit the startsWith("/") early-return before the
7
+ * proper node_modules/swiss-lib handling logic is reached.
8
+ *
9
+ * At server startup we scan node_modules directories for symlinks and build a
10
+ * map: realpath → /node_modules/<pkg-name>
11
+ * toUrl() consults this registry FIRST and short-circuits back to the correct
12
+ * browser URL.
13
+ */
14
+
15
+ import { promises as fs } from "node:fs";
16
+ import path from "node:path";
17
+
18
+ // realpath (normalized, forward slashes) → browser URL prefix
19
+ const registry = new Map<string, string>();
20
+
21
+ export async function buildSymlinkRegistry(
22
+ nodeModulesDirs: string[]
23
+ ): Promise<void> {
24
+ registry.clear();
25
+ for (const dir of nodeModulesDirs) {
26
+ await scanNodeModulesDir(dir);
27
+ }
28
+ console.log(
29
+ `[SWITE] Symlink registry built: ${registry.size} entries from ${nodeModulesDirs.length} node_modules dirs`
30
+ );
31
+ }
32
+
33
+ async function scanNodeModulesDir(nodeModulesDir: string): Promise<void> {
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ let dirents: any[];
36
+ try {
37
+ dirents = await fs.readdir(nodeModulesDir, {
38
+ withFileTypes: true,
39
+ encoding: "utf8",
40
+ });
41
+ } catch {
42
+ return; // dir doesn't exist — skip silently
43
+ }
44
+
45
+ for (const dirent of dirents) {
46
+ if (dirent.name.startsWith(".")) continue;
47
+
48
+ if (dirent.name.startsWith("@")) {
49
+ // Scoped scope directory — scan one level deeper
50
+ const scopeDir = path.join(nodeModulesDir, dirent.name);
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ let scopedDirents: any[];
53
+ try {
54
+ scopedDirents = await fs.readdir(scopeDir, {
55
+ withFileTypes: true,
56
+ encoding: "utf8",
57
+ });
58
+ } catch {
59
+ continue;
60
+ }
61
+ for (const scoped of scopedDirents) {
62
+ if (scoped.isSymbolicLink()) {
63
+ await registerSymlink(
64
+ path.join(scopeDir, scoped.name),
65
+ `${dirent.name}/${scoped.name}`
66
+ );
67
+ }
68
+ }
69
+ } else if (dirent.isSymbolicLink()) {
70
+ await registerSymlink(
71
+ path.join(nodeModulesDir, dirent.name),
72
+ dirent.name
73
+ );
74
+ }
75
+ }
76
+ }
77
+
78
+ async function registerSymlink(
79
+ symlinkPath: string,
80
+ pkgName: string
81
+ ): Promise<void> {
82
+ try {
83
+ const realPath = await fs.realpath(symlinkPath);
84
+ const key = realPath.replace(/\\/g, "/");
85
+ const value = `/node_modules/${pkgName}`;
86
+ registry.set(key, value);
87
+ console.log(`[SWITE] Registry: ${pkgName}: ${key} → ${value}`);
88
+ } catch {
89
+ // broken symlink — ignore
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Look up an absolute filesystem path in the symlink registry.
95
+ *
96
+ * Returns the browser URL if absolutePath is, or is within, a package whose
97
+ * realpath was registered at startup. Returns null if not found.
98
+ *
99
+ * Example:
100
+ * /mnt/c/.../swiss-lib/packages/core/src/index.ts
101
+ * → /node_modules/@swissjs/core/src/index.ts
102
+ */
103
+ export function lookupInSymlinkRegistry(absolutePath: string): string | null {
104
+ const normalized = absolutePath.replace(/\\/g, "/");
105
+ for (const [realPkgPath, browserPrefix] of registry) {
106
+ if (normalized === realPkgPath) {
107
+ return browserPrefix;
108
+ }
109
+ if (normalized.startsWith(realPkgPath + "/")) {
110
+ return browserPrefix + normalized.slice(realPkgPath.length);
111
+ }
112
+ }
113
+ return null;
114
+ }