@superblocksteam/sdk 2.0.141 → 2.0.142-next.0

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 (31) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/dev-utils/dev-server.optimize-deps.test.mjs +242 -2
  3. package/dist/dev-utils/dev-server.optimize-deps.test.mjs.map +1 -1
  4. package/dist/dev-utils/optimize-deps-config.d.mts +14 -0
  5. package/dist/dev-utils/optimize-deps-config.d.mts.map +1 -1
  6. package/dist/dev-utils/optimize-deps-config.mjs +104 -8
  7. package/dist/dev-utils/optimize-deps-config.mjs.map +1 -1
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/telemetry/startup-context.d.ts +17 -0
  13. package/dist/telemetry/startup-context.d.ts.map +1 -0
  14. package/dist/telemetry/startup-context.js +43 -0
  15. package/dist/telemetry/startup-context.js.map +1 -0
  16. package/dist/telemetry/startup-context.test.d.ts +2 -0
  17. package/dist/telemetry/startup-context.test.d.ts.map +1 -0
  18. package/dist/telemetry/startup-context.test.js +70 -0
  19. package/dist/telemetry/startup-context.test.js.map +1 -0
  20. package/dist/vite-plugin-optimize-lucide-imports.d.ts +20 -0
  21. package/dist/vite-plugin-optimize-lucide-imports.d.ts.map +1 -1
  22. package/dist/vite-plugin-optimize-lucide-imports.js +19 -4
  23. package/dist/vite-plugin-optimize-lucide-imports.js.map +1 -1
  24. package/package.json +6 -6
  25. package/src/dev-utils/dev-server.optimize-deps.test.mts +334 -2
  26. package/src/dev-utils/optimize-deps-config.mts +106 -8
  27. package/src/index.ts +7 -0
  28. package/src/telemetry/startup-context.test.ts +102 -0
  29. package/src/telemetry/startup-context.ts +77 -0
  30. package/src/vite-plugin-optimize-lucide-imports.ts +21 -4
  31. package/tsconfig.tsbuildinfo +1 -1
@@ -1,5 +1,31 @@
1
1
  import type { UserConfig } from "vite";
2
2
 
3
+ /**
4
+ * Deep-path prefix that `vite-plugin-optimize-lucide-imports` rewrites every
5
+ * `import { X } from "lucide-react"` into (`lucide-react/dist/esm/icons/x.js`).
6
+ * Shared between the rewrite plugin and the optimizeDeps include glob below so
7
+ * the pre-bundled paths can never drift from the paths the plugin emits.
8
+ */
9
+ export const LUCIDE_ICON_DEEP_PATH_PREFIX = "lucide-react/dist/esm/icons/";
10
+
11
+ /**
12
+ * Selection rule for the include list below: every dependency declared by BOTH
13
+ * live-edit templates is force-included, EXCEPT the packages listed here. Each
14
+ * exclusion needs a reason; the contract is enforced in both directions by
15
+ * `dev-server.optimize-deps.test.mts`.
16
+ */
17
+ export const NOT_PRE_OPTIMIZED_TEMPLATE_DEPS: readonly string[] = [
18
+ // The hot-swapped Superblocks runtime: generated apps receive it through the
19
+ // SDK plugin pipeline, and a prebundled duplicate splits React context/hook
20
+ // state between the two copies. Only its `/jsx-dev-runtime` subpath (which
21
+ // the dep scan structurally cannot discover) is force-included.
22
+ "@superblocksteam/library",
23
+ // Node-side Vite build plugin, never imported by browser code. Bundling it
24
+ // drags in lightningcss/oxide native binaries and crashes the optimizer —
25
+ // the same reason `*.config.*` files are excluded from scan entries above.
26
+ "@tailwindcss/vite",
27
+ ];
28
+
3
29
  /**
4
30
  * Shared `optimizeDeps` config for both the pre-warm and activation Vite
5
31
  * servers (see `dev-server.mts`).
@@ -59,14 +85,86 @@ export const OPTIMIZE_DEPS_CONFIG: UserConfig["optimizeDeps"] = {
59
85
  // EE warm-pool box: without this, a freshly-generated app still reloads once
60
86
  // on first render for the JSX runtime — APPS-4576).
61
87
  //
62
- // The scan's esbuild uses the DEFAULT (react) JSX runtime, so it emits
63
- // `react/jsx-runtime` imports and pre-bundles those — but never
64
- // `@superblocksteam/library/jsx-dev-runtime`, even though every generated app
65
- // sets `jsxImportSource: "@superblocksteam/library"` and pulls that runtime in
66
- // at serve time. Force-including it pre-bundles it up front. An unresolvable
67
- // `include` entry is a no-op (Vite warns and skips), so an app that somehow
68
- // lacks the library never fails cold-start over this.
69
- include: ["@superblocksteam/library/jsx-dev-runtime"],
88
+ // Two kinds of entries live here:
89
+ //
90
+ // 1. `@superblocksteam/library/jsx-dev-runtime`: the scan's esbuild uses the
91
+ // DEFAULT (react) JSX runtime, so it emits `react/jsx-runtime` imports and
92
+ // pre-bundles those but never the library runtime, even though every
93
+ // generated app sets `jsxImportSource: "@superblocksteam/library"` and
94
+ // pulls that runtime in at serve time. The scan can never see it.
95
+ //
96
+ // 2. The common live-edit template deps: the `entries` scan only pre-bundles
97
+ // deps that *existing* app source imports, but a warm-pool box pre-warms
98
+ // against the seeded template, and Clark's generated code routinely
99
+ // imports template-declared deps the seed never touches (an unused Radix
100
+ // primitive, `recharts` for the first dashboard). Each such first-time
101
+ // import is a runtime discovery — a mid-generation re-optimize, worst
102
+ // case the "optimized dependencies changed" reload. Force-including every
103
+ // dep the templates declare (minus NOT_PRE_OPTIMIZED_TEMPLATE_DEPS above)
104
+ // closes that gap.
105
+ //
106
+ // The list is deliberately STATIC (not derived from the app's package.json):
107
+ // `include` IS part of Vite's dep-cache config hash, so pre-warm and
108
+ // activation must see the exact same value or activation discards the
109
+ // pre-warmed cache and re-optimizes — the very reload this config removes.
110
+ // The two-way template contract (include ⊆ shared template deps, and every
111
+ // shared template dep is included or explicitly excluded above) is pinned in
112
+ // `dev-server.optimize-deps.test.mts`. An unresolvable `include` entry is a
113
+ // no-op (Vite warns and skips), so apps lacking one of these deps (e.g.
114
+ // older 2.0 apps) never fail cold-start over this.
115
+ //
116
+ // Keep sorted.
117
+ include: [
118
+ "@radix-ui/react-accordion",
119
+ "@radix-ui/react-aspect-ratio",
120
+ "@radix-ui/react-avatar",
121
+ "@radix-ui/react-checkbox",
122
+ "@radix-ui/react-dialog",
123
+ "@radix-ui/react-dropdown-menu",
124
+ "@radix-ui/react-hover-card",
125
+ "@radix-ui/react-label",
126
+ "@radix-ui/react-navigation-menu",
127
+ "@radix-ui/react-popover",
128
+ "@radix-ui/react-progress",
129
+ "@radix-ui/react-scroll-area",
130
+ "@radix-ui/react-select",
131
+ "@radix-ui/react-separator",
132
+ "@radix-ui/react-slider",
133
+ "@radix-ui/react-slot",
134
+ "@radix-ui/react-switch",
135
+ "@radix-ui/react-tabs",
136
+ "@radix-ui/react-toggle",
137
+ "@radix-ui/react-toggle-group",
138
+ "@radix-ui/react-tooltip",
139
+ "@superblocksteam/library/jsx-dev-runtime",
140
+ "class-variance-authority",
141
+ "clsx",
142
+ "date-fns",
143
+ "lucide-react",
144
+ // The icon deep paths, NOT just the bare package: the lucide import
145
+ // optimizer plugin rewrites every named lucide import into a per-icon deep
146
+ // path, so the dep optimizer never sees the bare specifier at serve time —
147
+ // a bare include alone leaves every first-time icon import as a runtime
148
+ // discovery. Vite expands this glob (`expandGlobIds`) against the installed
149
+ // package: ~1,850 icons, measured at ~+1.4s cold-start optimize on the real
150
+ // template — inside the warm-standby budget, and it covers any icon Clark
151
+ // picks instead of a top-N guess. On apps without lucide-react the glob
152
+ // matches nothing and is a no-op. Expanded entries are keyed with the `.js`
153
+ // extension, which is why the rewrite plugin emits `.js` ids (see
154
+ // `lucideIconModuleId`) — Vite matches optimized deps by exact id.
155
+ `${LUCIDE_ICON_DEEP_PATH_PREFIX}*`,
156
+ "react",
157
+ "react-day-picker",
158
+ "react-dom",
159
+ "react-dom/client",
160
+ "react-dropzone",
161
+ "react-router",
162
+ "react-syntax-highlighter",
163
+ "react/jsx-runtime",
164
+ "recharts",
165
+ "sonner",
166
+ "tailwind-merge",
167
+ ],
70
168
  // `@superblocksteam/sdk-api` is a VIRTUAL module the `sb-sdk-api-entry-point`
71
169
  // plugin serves (not a real package), imported by generated app code. Left to
72
170
  // the optimizer it gets discovered as a "new dependency" on first import and
package/src/index.ts CHANGED
@@ -133,3 +133,10 @@ export {
133
133
  getTracer,
134
134
  isCloudPrem,
135
135
  } from "./telemetry/index.js";
136
+
137
+ export {
138
+ applyStartupTraceCarrier,
139
+ startupTraceCarrierFromHeaders,
140
+ type StartupTraceCarrier,
141
+ withStartupTraceContext,
142
+ } from "./telemetry/startup-context.js";
@@ -0,0 +1,102 @@
1
+ import { ROOT_CONTEXT, trace } from "@opentelemetry/api";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ getTestTelemetryInstance,
6
+ getTestTracer,
7
+ initTestTelemetry,
8
+ resetTestTelemetry,
9
+ } from "@superblocksteam/telemetry/testing";
10
+
11
+ import {
12
+ applyStartupTraceCarrier,
13
+ extractStartupTraceContext,
14
+ startupTraceCarrierFromHeaders,
15
+ type StartupTraceCarrier,
16
+ withStartupTraceContext,
17
+ } from "./startup-context.js";
18
+
19
+ const TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736";
20
+ const TRACEPARENT = `00-${TRACE_ID}-00f067aa0ba902b7-01`;
21
+
22
+ describe("sdk-dev-server startup trace context", () => {
23
+ beforeAll(() => {
24
+ initTestTelemetry();
25
+ });
26
+
27
+ afterAll(async () => {
28
+ await resetTestTelemetry();
29
+ });
30
+
31
+ it("extracts the SABS parent from cold-start environment values", () => {
32
+ const parentContext = extractStartupTraceContext({
33
+ TRACEPARENT,
34
+ TRACESTATE: "vendor=value",
35
+ });
36
+
37
+ expect(trace.getSpanContext(parentContext)).toMatchObject({
38
+ isRemote: true,
39
+ traceId: TRACE_ID,
40
+ traceFlags: 1,
41
+ traceState: expect.objectContaining({
42
+ serialize: expect.any(Function),
43
+ }),
44
+ });
45
+ expect(trace.getSpanContext(parentContext)?.traceState?.serialize()).toBe(
46
+ "vendor=value",
47
+ );
48
+ });
49
+
50
+ it("uses the root context when startup propagation is absent", () => {
51
+ expect(extractStartupTraceContext({})).toBe(ROOT_CONTEXT);
52
+ });
53
+
54
+ it("stores warm activation headers for the startup path without stale tracestate", () => {
55
+ const env: NodeJS.ProcessEnv = {
56
+ TRACEPARENT: "stale",
57
+ TRACESTATE: "stale=value",
58
+ };
59
+ const carrier: StartupTraceCarrier = { traceparent: TRACEPARENT };
60
+
61
+ applyStartupTraceCarrier(carrier, env);
62
+
63
+ expect(env.TRACEPARENT).toBe(TRACEPARENT);
64
+ expect(env.TRACESTATE).toBeUndefined();
65
+ });
66
+
67
+ it("captures inbound warm activation trace headers", () => {
68
+ expect(
69
+ startupTraceCarrierFromHeaders({
70
+ traceparent: TRACEPARENT,
71
+ tracestate: "vendor=value",
72
+ }),
73
+ ).toEqual({
74
+ traceparent: TRACEPARENT,
75
+ tracestate: "vendor=value",
76
+ });
77
+ });
78
+
79
+ it("parents the dev-server startup span to the propagated SABS context", async () => {
80
+ const environment = {
81
+ TRACEPARENT,
82
+ TRACESTATE: "vendor=value",
83
+ };
84
+
85
+ await withStartupTraceContext(environment, async () => {
86
+ await getTestTracer().startActiveSpan(
87
+ "devServerStartup",
88
+ async (span) => {
89
+ span.end();
90
+ },
91
+ );
92
+ });
93
+
94
+ const [startupSpan] = getTestTelemetryInstance()
95
+ .spanExporter.getSpans()
96
+ .filter((span) => span.name === "devServerStartup");
97
+ expect(startupSpan.spanContext().traceId).toBe(TRACE_ID);
98
+ expect(startupSpan.parentSpanContext?.spanId).toBe("00f067aa0ba902b7");
99
+ expect(environment.TRACEPARENT).toBeUndefined();
100
+ expect(environment.TRACESTATE).toBeUndefined();
101
+ });
102
+ });
@@ -0,0 +1,77 @@
1
+ import {
2
+ type Context,
3
+ ROOT_CONTEXT,
4
+ context,
5
+ propagation,
6
+ trace,
7
+ } from "@opentelemetry/api";
8
+
9
+ export type StartupTraceCarrier = {
10
+ traceparent?: string;
11
+ tracestate?: string;
12
+ };
13
+
14
+ type StartupTraceEnvironment = {
15
+ [key: string]: string | undefined;
16
+ TRACEPARENT?: string;
17
+ TRACESTATE?: string;
18
+ };
19
+
20
+ export function startupTraceCarrierFromHeaders(
21
+ headers: Record<string, string | string[] | undefined>,
22
+ ): StartupTraceCarrier {
23
+ const traceparent = headers.traceparent;
24
+ const tracestate = headers.tracestate;
25
+ return {
26
+ traceparent: typeof traceparent === "string" ? traceparent : undefined,
27
+ tracestate: typeof tracestate === "string" ? tracestate : undefined,
28
+ };
29
+ }
30
+
31
+ export function extractStartupTraceContext(
32
+ environment: StartupTraceEnvironment = process.env,
33
+ ): Context {
34
+ if (!environment.TRACEPARENT) {
35
+ return ROOT_CONTEXT;
36
+ }
37
+
38
+ const extracted = propagation.extract(ROOT_CONTEXT, {
39
+ traceparent: environment.TRACEPARENT,
40
+ ...(environment.TRACESTATE ? { tracestate: environment.TRACESTATE } : {}),
41
+ });
42
+
43
+ return trace.getSpanContext(extracted)?.isRemote ? extracted : ROOT_CONTEXT;
44
+ }
45
+
46
+ export function applyStartupTraceCarrier(
47
+ carrier: StartupTraceCarrier,
48
+ environment: NodeJS.ProcessEnv = process.env,
49
+ ): void {
50
+ if (carrier.traceparent) {
51
+ environment.TRACEPARENT = carrier.traceparent;
52
+ } else {
53
+ delete environment.TRACEPARENT;
54
+ }
55
+
56
+ if (carrier.tracestate) {
57
+ environment.TRACESTATE = carrier.tracestate;
58
+ } else {
59
+ delete environment.TRACESTATE;
60
+ }
61
+ }
62
+
63
+ export function consumeStartupTraceContext(
64
+ environment: StartupTraceEnvironment,
65
+ ): Context {
66
+ const parentContext = extractStartupTraceContext(environment);
67
+ delete environment.TRACEPARENT;
68
+ delete environment.TRACESTATE;
69
+ return parentContext;
70
+ }
71
+
72
+ export function withStartupTraceContext<T>(
73
+ environment: StartupTraceEnvironment,
74
+ callback: () => T,
75
+ ): T {
76
+ return context.with(consumeStartupTraceContext(environment), callback);
77
+ }
@@ -1,5 +1,7 @@
1
1
  import type { TransformResult, Plugin } from "vite";
2
2
 
3
+ import { LUCIDE_ICON_DEEP_PATH_PREFIX } from "./dev-utils/optimize-deps-config.mjs";
4
+
3
5
  // Pre-compile regex for better performance
4
6
  const LUCIDE_IMPORT_PATTERN =
5
7
  /([ \t]*)import\s+\{\s*([^}]+)\s*\}\s+from\s+['"]lucide-react['"]/g;
@@ -48,9 +50,11 @@ function isValidInput(code: string, id: string): boolean {
48
50
  }
49
51
 
50
52
  /**
51
- * Transforms lucide-react imports from destructured to individual imports
53
+ * Transforms lucide-react imports from destructured to individual imports.
54
+ * Exported so tests can pin the emitted import specifiers against the ids the
55
+ * dep optimizer registers.
52
56
  */
53
- function transformLucideImports(sourceCode: string): {
57
+ export function transformLucideImports(sourceCode: string): {
54
58
  transformedCode: string;
55
59
  hasChanges: boolean;
56
60
  } {
@@ -80,6 +84,20 @@ function transformLucideImports(sourceCode: string): {
80
84
  return { transformedCode, hasChanges };
81
85
  }
82
86
 
87
+ /**
88
+ * The module id this plugin emits for a named lucide-react import. The `.js`
89
+ * extension is load-bearing: the dep optimizer's include glob registers each
90
+ * icon under its on-disk id (`…/icons/check.js`), and Vite's
91
+ * `tryOptimizedResolve` matches optimized deps by EXACT id — an extensionless
92
+ * import misses the optimized entry and is re-registered as a newly discovered
93
+ * dependency, re-running the optimizer (the reload this plugin + config
94
+ * combination exists to prevent). Exported so tests can assert the emitted ids
95
+ * and the optimized ids stay identical.
96
+ */
97
+ export function lucideIconModuleId(importName: string): string {
98
+ return `${LUCIDE_ICON_DEEP_PATH_PREFIX}${convertToKebabCase(importName)}.js`;
99
+ }
100
+
83
101
  /**
84
102
  * Converts a comma-separated list of imports to individual import statements
85
103
  */
@@ -93,9 +111,8 @@ function convertToIndividualImports(
93
111
  .map((name) => name.trim())
94
112
  .filter(Boolean)
95
113
  .map((name) => {
96
- const kebabCasePath = convertToKebabCase(name);
97
114
  const semicolon = withSemicolon ? ";" : "";
98
- return `${indentation}import ${name} from "lucide-react/dist/esm/icons/${kebabCasePath}"${semicolon}`;
115
+ return `${indentation}import ${name} from "${lucideIconModuleId(name)}"${semicolon}`;
99
116
  })
100
117
  .join("\n");
101
118
  }