@spring-systems/server 0.8.7 → 0.8.10

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/dist/client.js CHANGED
@@ -1 +1 @@
1
- "use client";import{a as e,c as t}from"./chunk-UDRRRHFI.js";var _="0.8.7";export{_ as SPRING_SERVER_VERSION,t as createClientOnlyNextUIAdapter,e as createNextRouteAdapter};
1
+ "use client";import{a as e,c as t}from"./chunk-UDRRRHFI.js";var _="0.8.10";export{_ as SPRING_SERVER_VERSION,t as createClientOnlyNextUIAdapter,e as createNextRouteAdapter};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as I,b as p}from"./chunk-KEEIJ5EV.js";import{a as E,b as _,c as S,d as r,e as o,f as t}from"./chunk-VB7DEUGT.js";import"./chunk-M23YQYLU.js";import{a as O,b as T}from"./chunk-TVAJDSYB.js";import"./chunk-E4AX7MID.js";import{a as e,b as R,c as n}from"./chunk-FXUI75TW.js";import"server-only";var f="0.8.7";export{n as BASE_SECURITY_HEADERS,R as BASE_SECURITY_HEADER_VALUES,r as DELETE,E as GET,t as OPTIONS,o as PATCH,e as PERMISSIONS_POLICY_VALUE,_ as POST,S as PUT,T as RuntimeEnvScript,f as SPRING_SERVER_VERSION,I as proxy,p as proxyConfig,O as runtimeEnvGET};
1
+ import{a as I,b as p}from"./chunk-KEEIJ5EV.js";import{a as E,b as _,c as S,d as r,e as o,f as t}from"./chunk-VB7DEUGT.js";import"./chunk-M23YQYLU.js";import{a as O,b as T}from"./chunk-TVAJDSYB.js";import"./chunk-E4AX7MID.js";import{a as e,b as R,c as n}from"./chunk-FXUI75TW.js";import"server-only";var f="0.8.10";export{n as BASE_SECURITY_HEADERS,R as BASE_SECURITY_HEADER_VALUES,r as DELETE,E as GET,t as OPTIONS,o as PATCH,e as PERMISSIONS_POLICY_VALUE,_ as POST,S as PUT,T as RuntimeEnvScript,f as SPRING_SERVER_VERSION,I as proxy,p as proxyConfig,O as runtimeEnvGET};
@@ -0,0 +1,18 @@
1
+ interface SpringDevConfig {
2
+ /** True when packages are locally linked (not from .pnpm store). */
3
+ isLinked: boolean;
4
+ /** Turbopack root directory (LCA of project + packages). */
5
+ turboRoot: string;
6
+ /** Turbopack resolveAlias entries pointing to src/ TypeScript files. */
7
+ aliases: Record<string, string>;
8
+ /** Packages to transpile (only when linked). */
9
+ transpilePackages: string[];
10
+ }
11
+ /**
12
+ * Detects linked mode and builds Turbopack configuration for spring packages.
13
+ *
14
+ * Returns empty/no-op values when packages come from npm (safe to always call).
15
+ */
16
+ declare function getSpringDevConfig(): SpringDevConfig;
17
+
18
+ export { type SpringDevConfig, getSpringDevConfig };
@@ -0,0 +1 @@
1
+ import{existsSync as h,readdirSync as v,readFileSync as P,realpathSync as j}from"fs";import{join as p,relative as C,resolve as i,sep as $}from"path";var D=["core","api","ui","server"],x=process.platform==="win32";function L(n){return n.replaceAll("\\","/")}function A(n){try{return P(i(".env.local"),"utf8").match(new RegExp(`^${n}=(.+)$`,"m"))?.[1]?.trim().replace(/^["']|["']$/g,"")}catch{return}}function W(){try{return!j(i("node_modules/@spring-systems/ui")).includes(".pnpm")}catch{return!1}}function w(){let n=process.env.SPRING_PACKAGES_DIR||A("SPRING_PACKAGES_DIR");return n?i(n):i("..","spring-packages")}function G(n,r){let t=i(n).split($),s=i(r).split($),e=0;for(;e<t.length&&e<s.length;){let g=x?(t[e]??"").toLowerCase():t[e]??"",a=x?(s[e]??"").toLowerCase():s[e]??"";if(g!==a)break;e++}return t.slice(0,e).join($)||i("..")}function _(n,r){let t=r.replace(/^\.\/dist\//,"src/").replace(/\.js$/,"");for(let s of[".ts",".tsx"])if(h(p(n,t+s)))return t+s;return null}function E(n){let r={};for(let t of D){let s=p(n,"packages",t),e=L(C(i("."),s)),g;try{g=JSON.parse(P(p(s,"package.json"),"utf8"))}catch{continue}let a=g.exports;if(!(!a||typeof a!="object"))for(let[u,l]of Object.entries(a)){let o=typeof l=="string"?l:typeof l=="object"&&l?.import?l.import:null;if(!o)continue;if(u.includes("*")){if(o.endsWith(".css"))continue;let f=u.replace("./*","").replace("./","").replace("/*",""),k=o.replace(/^\.\/dist\//,"src/").replace(/\/\*\.js$/,""),y=p(s,k);if(!h(y))continue;let S;try{S=v(y)}catch{continue}for(let c of S){if(!c.endsWith(".ts")&&!c.endsWith(".tsx")||c.endsWith(".test.ts")||c.endsWith(".test.tsx")||c.endsWith(".d.ts"))continue;let R=c.replace(/\.tsx?$/,"");if(R==="index")continue;let b=`@spring-systems/${t}/${f}/${R}`;r[b]||(r[b]=`${e}/${k}/${c}`)}continue}let d=u==="."?`@spring-systems/${t}`:`@spring-systems/${t}/${u.slice(2)}`;if(o.endsWith(".css")){let f=o.replace(/^\.\/dist\//,"src/");h(p(s,f))?r[d]=`${e}/${f}`:r[d]=`${e}/${o.replace(/^\.\//,"")}`;continue}let m=_(s,o);r[d]=m?`${e}/${m}`:`${e}/${o.replace(/^\.\//,"")}`}}return r}function N(){if(!W())return{isLinked:!1,turboRoot:i(".."),aliases:{},transpilePackages:[]};let r=w(),t=E(r),s=G(i("."),r);return console.log(`[spring] linked mode (${Object.keys(t).length} aliases, root: ${s})`),{isLinked:!0,turboRoot:s,aliases:t,transpilePackages:D.map(e=>`@spring-systems/${e}`)}}export{N as getSpringDevConfig};
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@spring-systems/server",
3
- "version": "0.8.7",
3
+ "version": "0.8.10",
4
4
  "description": "Next.js server-only code for the Spring Systems framework (proxy, API routes, runtime env)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "spring-postinstall": "./scripts/spring-postinstall.mjs"
10
+ },
8
11
  "files": [
9
12
  "dist",
13
+ "scripts/spring-postinstall.mjs",
10
14
  "README.md",
11
15
  "LICENSE",
12
16
  "CHANGELOG.md"
@@ -83,6 +87,11 @@
83
87
  "types": "./dist/security-headers.d.ts",
84
88
  "import": "./dist/security-headers.js",
85
89
  "default": "./dist/security-headers.js"
90
+ },
91
+ "./spring-dev-config": {
92
+ "types": "./dist/spring-dev-config.d.ts",
93
+ "import": "./dist/spring-dev-config.js",
94
+ "default": "./dist/spring-dev-config.js"
86
95
  }
87
96
  },
88
97
  "scripts": {
@@ -108,6 +117,7 @@
108
117
  "devDependencies": {
109
118
  "@spring-systems/core": "workspace:^",
110
119
  "@spring-systems/ui": "workspace:^",
120
+ "@types/node": "^24.12.0",
111
121
  "@types/react": "^19.2.14",
112
122
  "@vitest/coverage-v8": "^4.0.18",
113
123
  "next": "^16.1.6",
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @spring-systems/server — post-install auto-linker
4
+ *
5
+ * Automatically switches between npm packages (CI / deploy) and local source
6
+ * linking (developer workstation) based on environment detection.
7
+ *
8
+ * How it works:
9
+ * 1. pnpm install resolves @spring-systems/* from npm (lockfile stays stable)
10
+ * 2. This script runs as postinstall and either:
11
+ * a) Replaces node_modules symlinks with links to local spring-packages
12
+ * checkout → live TypeScript + CSS editing with HMR
13
+ * b) Creates CSS src/ → dist/ forwarding stubs so globals.css @import
14
+ * paths (which always point to src/) work with npm packages
15
+ *
16
+ * Detection priority:
17
+ * 1. SPRING_LINK=false → force npm mode
18
+ * 2. Non-local environment (CI, staging, prod) → npm mode
19
+ * 3. SPRING_PACKAGES_DIR in .env.local → linked mode
20
+ * 4. ../spring-packages/packages/ exists → linked mode (convention)
21
+ * 5. None of the above → npm mode
22
+ *
23
+ * Cross-platform: macOS (dir symlink), Windows (junction — no admin required).
24
+ *
25
+ * Usage in consumer project package.json:
26
+ * "postinstall": "node scripts/link-spring.mjs"
27
+ */
28
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "fs";
29
+ import { join, resolve } from "path";
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // Constants
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const isWindows = process.platform === "win32";
36
+ const root = resolve(process.cwd());
37
+ const packages = ["core", "api", "ui", "server"];
38
+ const nmDir = resolve(root, "node_modules", "@spring-systems");
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Env detection helpers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /** Read a key from .env.local (Next.js loads it too late for config files). */
45
+ function readEnvLocal(key) {
46
+ try {
47
+ const content = readFileSync(resolve(root, ".env.local"), "utf8");
48
+ const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
49
+ return match ? match[1].trim().replace(/^["']|["']$/g, "") : undefined;
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * True when running outside a developer workstation.
57
+ * This is defense-in-depth — even if detection misses a platform,
58
+ * the existsSync(springDir) check prevents linking in environments
59
+ * where the local packages directory doesn't exist.
60
+ */
61
+ function isNonLocalEnvironment() {
62
+ return !!(
63
+ process.env.CI ||
64
+ (process.env.TARGET_ENV && process.env.TARGET_ENV !== "test") ||
65
+ process.env.VERCEL ||
66
+ process.env.RAILWAY ||
67
+ process.env.FLY_APP_NAME ||
68
+ process.env.NODE_ENV === "production"
69
+ );
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Resolve SPRING_PACKAGES_DIR
74
+ // ---------------------------------------------------------------------------
75
+
76
+ function resolveSpringDir() {
77
+ // 1. Env var (system or .env.local)
78
+ const dir = process.env.SPRING_PACKAGES_DIR || readEnvLocal("SPRING_PACKAGES_DIR");
79
+ if (dir) return resolve(dir, "packages");
80
+
81
+ // 2. Convention: sibling directory
82
+ return resolve(root, "..", "spring-packages", "packages");
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // CSS forwarding (npm mode)
87
+ // ---------------------------------------------------------------------------
88
+
89
+ /**
90
+ * In npm mode, ui package only has dist/ (src/ is not published).
91
+ * globals.css always imports from src/styles/, so we create forwarding stubs.
92
+ */
93
+ function createStyleForwards() {
94
+ const uiPkg = resolve(nmDir, "ui");
95
+ if (!existsSync(uiPkg)) return 0;
96
+
97
+ // Real source tree present → linked mode, don't overwrite
98
+ if (existsSync(join(uiPkg, "src", "shared"))) return 0;
99
+
100
+ const distStyles = join(uiPkg, "dist", "styles");
101
+ const srcStyles = join(uiPkg, "src", "styles");
102
+ if (!existsSync(distStyles)) return 0;
103
+
104
+ mkdirSync(srcStyles, { recursive: true });
105
+
106
+ let count = 0;
107
+ for (const file of readdirSync(distStyles)) {
108
+ if (!file.endsWith(".css")) continue;
109
+ writeFileSync(join(srcStyles, file), `@import "../../dist/styles/${file}";\n`);
110
+ count++;
111
+ }
112
+ return count;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Package linking (linked mode)
117
+ // ---------------------------------------------------------------------------
118
+
119
+ function linkPackages(springDir) {
120
+ let linked = 0;
121
+ const linkedNames = [];
122
+
123
+ for (const name of packages) {
124
+ const target = resolve(springDir, name);
125
+ const link = resolve(nmDir, name);
126
+ if (!existsSync(target)) continue;
127
+
128
+ try {
129
+ rmSync(link, { recursive: true, force: true });
130
+ symlinkSync(target, link, isWindows ? "junction" : "dir");
131
+ linked++;
132
+ linkedNames.push(name);
133
+ } catch (err) {
134
+ console.warn(` [spring] ⚠ Failed to link ${name}: ${err.message}`);
135
+ }
136
+ }
137
+ return { linked, linkedNames };
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Main
142
+ // ---------------------------------------------------------------------------
143
+
144
+ function main() {
145
+ // --- Explicit opt-out ---
146
+ if (process.env.SPRING_LINK === "false") {
147
+ const css = createStyleForwards();
148
+ console.info(`[spring] npm mode (SPRING_LINK=false) — ${css} CSS forward(s)`);
149
+ verify("npm");
150
+ return;
151
+ }
152
+
153
+ // --- Explicit opt-in (skip environment checks, e.g. pnpm spring:link) ---
154
+ const forceLink = process.env.SPRING_LINK === "true";
155
+
156
+ // --- Non-local environment (skipped when force-linking) ---
157
+ if (!forceLink && isNonLocalEnvironment()) {
158
+ const reason = process.env.CI
159
+ ? "CI"
160
+ : process.env.TARGET_ENV
161
+ ? `TARGET_ENV=${process.env.TARGET_ENV}`
162
+ : process.env.NODE_ENV === "production"
163
+ ? "NODE_ENV=production"
164
+ : "non-local env detected";
165
+ const css = createStyleForwards();
166
+ console.info(`[spring] npm mode (${reason}) — ${css} CSS forward(s)`);
167
+ verify("npm");
168
+ return;
169
+ }
170
+
171
+ // --- Resolve local packages path ---
172
+ const springDir = resolveSpringDir();
173
+
174
+ if (!existsSync(springDir)) {
175
+ const css = createStyleForwards();
176
+ console.info(`[spring] npm mode (no local packages at ${springDir}) — ${css} CSS forward(s)`);
177
+ verify("npm");
178
+ return;
179
+ }
180
+
181
+ // --- Linked mode ---
182
+ const { linked, linkedNames } = linkPackages(springDir);
183
+
184
+ if (linked > 0) {
185
+ console.info(`[spring] linked mode — ${linkedNames.join(", ")} → ${springDir}`);
186
+ verify("linked");
187
+ } else {
188
+ const css = createStyleForwards();
189
+ console.info(`[spring] npm mode (no linkable packages found) — ${css} CSS forward(s)`);
190
+ verify("npm");
191
+ }
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Post-run verification
196
+ // ---------------------------------------------------------------------------
197
+
198
+ function verify(mode) {
199
+ const uiPkg = resolve(nmDir, "ui");
200
+
201
+ if (mode === "linked") {
202
+ // Verify symlinks actually resolve
203
+ if (!existsSync(join(uiPkg, "src", "shared"))) {
204
+ console.warn("[spring] ⚠ Verification failed: ui/src/shared not found after linking");
205
+ }
206
+ }
207
+
208
+ // Verify CSS chain works (src/styles/ must have content in both modes)
209
+ const srcStyles = join(uiPkg, "src", "styles");
210
+ if (existsSync(srcStyles)) {
211
+ const cssFiles = readdirSync(srcStyles).filter((f) => f.endsWith(".css"));
212
+ if (cssFiles.length === 0) {
213
+ console.warn("[spring] ⚠ Verification failed: no CSS files in ui/src/styles/");
214
+ }
215
+ }
216
+ }
217
+
218
+ main();