@superblocksteam/sdk 2.0.6-next.6 → 2.0.6-next.60

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 (41) hide show
  1. package/dist/application-build.mjs +6 -2
  2. package/dist/application-build.mjs.map +1 -1
  3. package/dist/cli-replacement/automatic-upgrades.d.ts +5 -2
  4. package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
  5. package/dist/cli-replacement/automatic-upgrades.js +199 -93
  6. package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
  7. package/dist/cli-replacement/dev.d.mts +1 -2
  8. package/dist/cli-replacement/dev.d.mts.map +1 -1
  9. package/dist/cli-replacement/dev.mjs +53 -29
  10. package/dist/cli-replacement/dev.mjs.map +1 -1
  11. package/dist/dev-utils/dev-logger.d.mts +17 -7
  12. package/dist/dev-utils/dev-logger.d.mts.map +1 -1
  13. package/dist/dev-utils/dev-logger.mjs +50 -9
  14. package/dist/dev-utils/dev-logger.mjs.map +1 -1
  15. package/dist/dev-utils/dev-server.d.mts.map +1 -1
  16. package/dist/dev-utils/dev-server.mjs +8 -12
  17. package/dist/dev-utils/dev-server.mjs.map +1 -1
  18. package/dist/dev-utils/vite-plugin-build-manifest-stub.d.mts +10 -0
  19. package/dist/dev-utils/vite-plugin-build-manifest-stub.d.mts.map +1 -0
  20. package/dist/dev-utils/vite-plugin-build-manifest-stub.mjs +27 -0
  21. package/dist/dev-utils/vite-plugin-build-manifest-stub.mjs.map +1 -0
  22. package/dist/dev-utils/vite-plugin-sb-cdn.d.mts.map +1 -1
  23. package/dist/dev-utils/vite-plugin-sb-cdn.mjs +13 -3
  24. package/dist/dev-utils/vite-plugin-sb-cdn.mjs.map +1 -1
  25. package/dist/vite-plugin-generate-build-manifest.d.mts +21 -0
  26. package/dist/vite-plugin-generate-build-manifest.d.mts.map +1 -0
  27. package/dist/vite-plugin-generate-build-manifest.mjs +131 -0
  28. package/dist/vite-plugin-generate-build-manifest.mjs.map +1 -0
  29. package/dist/vite-plugin-inject-sb-ids-transform.mjs +1 -1
  30. package/dist/vite-plugin-inject-sb-ids-transform.mjs.map +1 -1
  31. package/package.json +6 -4
  32. package/src/application-build.mts +6 -3
  33. package/src/cli-replacement/automatic-upgrades.ts +253 -113
  34. package/src/cli-replacement/dev.mts +67 -42
  35. package/src/dev-utils/dev-logger.mts +94 -20
  36. package/src/dev-utils/dev-server.mts +10 -12
  37. package/src/dev-utils/vite-plugin-build-manifest-stub.mts +30 -0
  38. package/src/dev-utils/vite-plugin-sb-cdn.mts +14 -3
  39. package/src/vite-plugin-generate-build-manifest.mts +193 -0
  40. package/src/vite-plugin-inject-sb-ids-transform.mts +1 -1
  41. package/tsconfig.tsbuildinfo +1 -1
@@ -1,39 +1,113 @@
1
1
  import { createLogger, format, transports } from "winston";
2
+ import type winston from "winston";
2
3
 
3
- export type DevLogger = {
4
- debug: (message: string, ...meta: any[]) => void;
5
- info: (message: string, ...meta: any[]) => void;
6
- warn: (message: string, ...meta: any[]) => void;
7
- error: (message: string, ...meta: any[]) => void;
8
- };
4
+ const activeTransports: winston.transport[] = [];
5
+
6
+ if (process.env.SUPERBLOCKS_IS_CSB === "true") {
7
+ activeTransports.push(
8
+ new transports.File({
9
+ format: format.json(),
10
+ filename: `/tmp/dev-server.log`,
11
+ level: "info",
12
+ }),
13
+ );
14
+ }
15
+
16
+ activeTransports.push(
17
+ new transports.Console({
18
+ format: format.combine(
19
+ format.colorize(),
20
+ format.timestamp({
21
+ format: "HH:mm:ss.SSS",
22
+ }),
23
+ format.printf((props) => {
24
+ const { message, timestamp, error, level } = props;
25
+ const base =
26
+ process.env.SUPERBLOCKS_IS_CSB === "true"
27
+ ? `${timestamp} ${message}`
28
+ : `${message}`;
29
+ if (level === "error") {
30
+ return `${base} ${(error as ErrorMeta["error"]).message} ${(error as ErrorMeta["error"]).stack}`;
31
+ }
32
+ return base;
33
+ }),
34
+ ),
35
+ }),
36
+ );
9
37
 
10
38
  const winstonLogger = createLogger({
11
- level: "info",
39
+ level: "debug",
12
40
  exitOnError: false,
13
41
  format: format.json(),
14
- transports: [
15
- new transports.File({ filename: `/tmp/dev-server.log`, level: "info" }),
16
- new transports.Console({
17
- format: format.combine(
18
- format.printf(({ message }) => `${message}`),
19
- format.colorize({ message: true }),
20
- ),
21
- level: "debug",
22
- }),
23
- ],
42
+ transports: activeTransports,
43
+ });
44
+
45
+ interface ErrorMeta {
46
+ error: {
47
+ kind: string;
48
+ message: string;
49
+ stack?: string;
50
+ };
51
+ }
52
+
53
+ export interface DevLogger {
54
+ log: (...messages: string[]) => void;
55
+ debug: (...messages: string[]) => void;
56
+ info: (...messages: string[]) => void;
57
+ warn: (...messages: string[]) => void;
58
+ error: (message: string, meta?: ErrorMeta) => void;
59
+ }
60
+ const logger: DevLogger = Object.freeze({
61
+ debug: (...messages: string[]) =>
62
+ winstonLogger.debug(
63
+ messages.length > 0 ? messages.join(" ") : (messages[0] as string),
64
+ ),
65
+ log: (...messages: string[]) =>
66
+ winstonLogger.info(
67
+ messages.length > 0 ? messages.join(" ") : (messages[0] as string),
68
+ ),
69
+ info: (...messages: string[]) =>
70
+ winstonLogger.info(
71
+ messages.length > 0 ? messages.join(" ") : (messages[0] as string),
72
+ ),
73
+ warn: (...messages: string[]) =>
74
+ winstonLogger.warn(
75
+ messages.length > 0 ? messages.join(" ") : (messages[0] as string),
76
+ ),
77
+ error: (message: string, meta?: ErrorMeta) =>
78
+ winstonLogger.error(message, meta),
24
79
  });
25
80
 
26
81
  export function getLogger(
27
- loggerOverride?: (message: string) => void,
82
+ loggerOverride?: (...messages: string[]) => void,
28
83
  ): DevLogger {
29
84
  if (!loggerOverride) {
30
- return winstonLogger;
85
+ return logger;
31
86
  }
32
87
 
33
88
  return {
34
89
  debug: loggerOverride,
35
90
  info: loggerOverride,
36
91
  warn: loggerOverride,
37
- error: loggerOverride,
92
+ log: loggerOverride,
93
+ error: loggerOverride as any,
94
+ };
95
+ }
96
+
97
+ export function getErrorMeta(error: unknown): ErrorMeta {
98
+ if (error instanceof Error) {
99
+ return {
100
+ error: {
101
+ kind: error.name,
102
+ message: error.message,
103
+ stack: error.stack,
104
+ },
105
+ };
106
+ }
107
+ return {
108
+ error: {
109
+ kind: "Unknown Error",
110
+ message: JSON.stringify(error),
111
+ },
38
112
  };
39
113
  }
@@ -18,7 +18,8 @@ import {
18
18
  customComponentsPlugin,
19
19
  isCustomComponentsEnabled,
20
20
  } from "./custom-build.mjs";
21
- import { getLogger } from "./dev-logger.mjs";
21
+ import { getErrorMeta, getLogger } from "./dev-logger.mjs";
22
+ import { buildManifestStubPlugin } from "./vite-plugin-build-manifest-stub.mjs";
22
23
  import { ddRumPlugin } from "./vite-plugin-dd-rum.mjs";
23
24
  import { superblocksCdnPlugin } from "./vite-plugin-sb-cdn.mjs";
24
25
  import type { AiService } from "@superblocksteam/vite-plugin-file-sync/ai-service";
@@ -187,13 +188,6 @@ export async function createDevServer({
187
188
  }, viteReject);
188
189
  });
189
190
 
190
- // TODO(code-mode): remove this soon
191
- app.get("/_sb_disconnect", async (_req, res) => {
192
- console.log("GET /_sb_disconnect");
193
- // TODO(code-mode): should this include any validation checks, such as getting a token?
194
- await gracefulShutdown({ logger, serverInitiated: false });
195
- res.send("ok");
196
- });
197
191
  app.post("/_sb_disconnect", async (req, res) => {
198
192
  const { initiatedByEmail, switchingTo } = req.body;
199
193
  // TODO(code-mode): should this include any validation checks, such as getting a token?
@@ -206,7 +200,7 @@ export async function createDevServer({
206
200
  });
207
201
  res.send("ok");
208
202
  } catch (e) {
209
- console.error("Error disconnecting from dev server", e);
203
+ logger.error("Error disconnecting from dev server", getErrorMeta(e));
210
204
  res.status(500).send("Error disconnecting from dev server");
211
205
  }
212
206
  });
@@ -260,7 +254,7 @@ async function startVite({
260
254
  }) {
261
255
  const viteLogger = createLogger();
262
256
  const logger = getLogger(loggerOverride);
263
- viteLogger.info = logger.info;
257
+ viteLogger.info = (msg: string) => logger.info(msg);
264
258
  viteLogger.warn = (msg: string) => {
265
259
  logger.warn(yellow(msg));
266
260
  };
@@ -276,6 +270,8 @@ async function startVite({
276
270
  const isCustomBuildEnabled = await isCustomComponentsEnabled();
277
271
  const customFolder = path.join(root, "custom");
278
272
 
273
+ const draftsFolder = path.join(root, ".superblocks");
274
+
279
275
  const cdnUrl = process.env.SUPERBLOCKS_CDN_URL ?? "http://localhost:4040/cdn";
280
276
 
281
277
  const env = loadEnv(mode, root, "");
@@ -313,7 +309,7 @@ async function startVite({
313
309
  server: {
314
310
  middlewareMode: true,
315
311
  watch: {
316
- ignored: [`${customFolder}/**/*`],
312
+ ignored: [`${customFolder}/**/*`, `${draftsFolder}/**/*`],
317
313
  },
318
314
  hmr: hmrOptions,
319
315
  cors: {
@@ -342,11 +338,13 @@ async function startVite({
342
338
  aiService,
343
339
  httpServer,
344
340
  tracer,
345
- logger,
346
341
  },
347
342
  { isCustomBuildEnabled },
348
343
  ) as Plugin,
349
344
 
345
+ // Add a virtual "stub" module for the build manifest
346
+ buildManifestStubPlugin(),
347
+
350
348
  // for now, only use the CDN locally
351
349
  superblocksCdnPlugin({
352
350
  imports: {
@@ -0,0 +1,30 @@
1
+ import type { Plugin } from "vite";
2
+
3
+ const BUILD_MANIFEST_MODULE_PATH = "./user-facing/build-manifest.js";
4
+ const VIRTUAL_MODULE_ID = "\0virtual:build-manifest.js";
5
+
6
+ /**
7
+ * This plugin is used to stub the build manifest module. It creates a virtual module
8
+ * for the build manifest module and returns an empty object.
9
+ *
10
+ * This is meant to be used to avoid errors when the build manifest module is not found.
11
+ * This plugin only runs in dev server mode.
12
+ */
13
+ export const buildManifestStubPlugin = (): Plugin => {
14
+ return {
15
+ name: "build-manifest-stub",
16
+ enforce: "pre",
17
+ apply: "serve",
18
+
19
+ resolveId(source) {
20
+ if (source === BUILD_MANIFEST_MODULE_PATH) {
21
+ return VIRTUAL_MODULE_ID;
22
+ }
23
+ },
24
+ load(id) {
25
+ if (id === VIRTUAL_MODULE_ID) {
26
+ return "export default {}";
27
+ }
28
+ },
29
+ };
30
+ };
@@ -224,8 +224,8 @@ async function analyzeModuleGraph(
224
224
  { content: string; binary: ArrayBuffer }
225
225
  > = new Map(),
226
226
  ): Promise<AnalysisResult> {
227
- // Avoid circular dependencies
228
- if (visited.has(url)) {
227
+ // Avoid circular dependencies and skip the shared library's build manifest reference
228
+ if (visited.has(url) || url.endsWith("/user-facing/build-manifest.js")) {
229
229
  return {
230
230
  dependencies: new Set(),
231
231
  content: "",
@@ -474,6 +474,13 @@ const wellKnownPackages = new Map<string, string>([
474
474
  ["react-dom", "https://esm.sh/react-dom@18.2.0"],
475
475
  ["react/jsx-runtime", "https://esm.sh/react@18.2.0/jsx-runtime"],
476
476
  ["react/jsx-dev-runtime", "https://esm.sh/react@18.2.0/jsx-dev-runtime"],
477
+ ["./user-facing/build-manifest.js", "/assets/build-manifest.js"],
478
+ ["/assets/user-facing/build-manifest.js", "/assets/build-manifest.js"],
479
+ ]);
480
+
481
+ const packagesToExcludeFromCdnRedirect = new Set<string>([
482
+ "./user-facing/build-manifest.js",
483
+ "/assets/user-facing/build-manifest.js",
477
484
  ]);
478
485
 
479
486
  const react18CdnUrl = "https://esm.sh/react@18.2.0";
@@ -773,8 +780,12 @@ export async function superblocksCdnPlugin(
773
780
  // Externalize modules specified in the import map
774
781
  // The cdn: prefix ensures they're properly resolved to the CDN URL
775
782
  if (imports.includes(id)) {
783
+ const shouldRedirectToCdn = !packagesToExcludeFromCdnRedirect.has(id);
776
784
  debug(`Externalizing module for CDN resolution: ${id}`);
777
- return { id: `cdn:${id}`, external: true };
785
+ return {
786
+ id: `${shouldRedirectToCdn ? "cdn:" : ""}${id}`,
787
+ external: true,
788
+ };
778
789
  }
779
790
 
780
791
  return null;
@@ -0,0 +1,193 @@
1
+ import path from "node:path";
2
+ import { parse } from "@babel/parser";
3
+ import { getClientApiId } from "@superblocksteam/library-shared";
4
+ import { resolveLanguageSpecificStepContentFromBlocks } from "@superblocksteam/util";
5
+ import { getPageName } from "@superblocksteam/vite-plugin-file-sync";
6
+ import {
7
+ extractIdentifierPathsFromApi,
8
+ extractApiDependencies,
9
+ } from "@superblocksteam/vite-plugin-file-sync/binding-extraction";
10
+ import {
11
+ getScope,
12
+ extractImportsFromAst,
13
+ } from "@superblocksteam/vite-plugin-file-sync/parsing";
14
+ import { yellow, red } from "colorette";
15
+ import fg from "fast-glob";
16
+ import fs from "fs-extra";
17
+ import { createLogger } from "vite";
18
+ import yaml from "yaml";
19
+ import { getLogger } from "./dev-utils/dev-logger.mjs";
20
+ import type { ParseResult } from "@babel/parser";
21
+ import type { File } from "@babel/types";
22
+ import type {
23
+ DeleteMeLibraryApi,
24
+ StaticScope,
25
+ } from "@superblocksteam/library-shared/types";
26
+ import type { Plugin } from "vite";
27
+
28
+ export const scopeFileBaseName = "scope.ts";
29
+ export const apiFileBaseName = "api.yaml";
30
+
31
+ type ApiDependency = {
32
+ apiName: string;
33
+ params: string[];
34
+ dependencies: string[];
35
+ };
36
+
37
+ /**
38
+ * Vite plugin that generates a build manifest for the application.
39
+ *
40
+ * This plugin will:
41
+ * 1. Read all api.yaml and api.yml files in the application
42
+ * 2. Read all scope.ts files in the application
43
+ * 3. Generate a build manifest for the application
44
+ * 4. Write the build manifest to a file in the build output directory
45
+ *
46
+ * The build manifest is a JSON object with the following properties:
47
+ * - apis: a map of all apis in the application keyed by their file path
48
+ * - apiDependencies: an array where each item represents an api and its dependencies
49
+ *
50
+ * @param root - The root directory of the application
51
+ * @returns A Vite plugin that generates a build manifest for the application
52
+ */
53
+ export function generateBuildManifestPlugin(root: string) {
54
+ const viteLogger = createLogger();
55
+ const logger = getLogger();
56
+ viteLogger.info = (msg: string) => logger.info(msg);
57
+ viteLogger.warn = (msg: string) => {
58
+ logger.warn(yellow(msg));
59
+ };
60
+ viteLogger.warnOnce = (msg: string) => {
61
+ logger.warn(yellow(msg));
62
+ };
63
+ viteLogger.error = (msg: string) => {
64
+ logger.error(red(msg));
65
+ };
66
+
67
+ viteLogger.clearScreen = () => {};
68
+
69
+ const scopesByPage: Record<string, StaticScope | null> = {};
70
+ const apiFiles: Record<string, DeleteMeLibraryApi> = {};
71
+ let buildManifest: {
72
+ apis: Record<string, { api: DeleteMeLibraryApi; scopeId: string }>;
73
+ apiDependencies: ApiDependency[];
74
+ } = { apis: {}, apiDependencies: [] };
75
+
76
+ return {
77
+ name: "sb-generate-build-manifest",
78
+ apply: "build",
79
+ enforce: "pre",
80
+
81
+ async buildStart() {
82
+ const apiDocuments = await fg(["**/api.yaml", "**/api.yml"], {
83
+ cwd: root,
84
+ });
85
+
86
+ for (const apiFilePath of apiDocuments) {
87
+ const absoluteApiFilePath = path.join(root, apiFilePath);
88
+
89
+ const document = await fs.readFile(absoluteApiFilePath, "utf-8");
90
+ const apiPb = yaml.parse(document);
91
+
92
+ await resolveLanguageSpecificStepContentFromBlocks(
93
+ path.dirname(absoluteApiFilePath),
94
+ apiPb.blocks ?? [],
95
+ {},
96
+ );
97
+
98
+ const pageName = getPageName(apiFilePath);
99
+ apiPb.metadata.id = getClientApiId(apiPb.metadata.name, pageName);
100
+ apiFiles[apiFilePath] = { apiPb, pageName };
101
+ }
102
+ },
103
+
104
+ transform(code: string, id: string) {
105
+ if (id.endsWith(scopeFileBaseName)) {
106
+ const ast = parse(code, {
107
+ sourceType: "module",
108
+ sourceFilename: id,
109
+ plugins: [["typescript", {}]],
110
+ });
111
+
112
+ const scopeFileMeta: {
113
+ code: string;
114
+ ast: ParseResult<File>;
115
+ imports: Record<string, boolean>;
116
+ } = {
117
+ code,
118
+ ast,
119
+ imports: extractImportsFromAst(ast),
120
+ };
121
+ const scope = getScope(id, scopeFileMeta);
122
+
123
+ scopesByPage[getPageName(id)] = scope;
124
+ }
125
+
126
+ return code;
127
+ },
128
+
129
+ async generateBundle() {
130
+ const allApiNames = getAllUniqueApiNames(apiFiles);
131
+
132
+ const allApis = Object.entries(apiFiles).reduce(
133
+ (acc, [id, api]) => {
134
+ acc[id] = {
135
+ api,
136
+ scopeId: scopesByPage[api.pageName]?.scopeId ?? "",
137
+ };
138
+ return acc;
139
+ },
140
+ {} as Record<string, { api: DeleteMeLibraryApi; scopeId: string }>,
141
+ );
142
+
143
+ const deps = await getApiDependencies(allApiNames, apiFiles);
144
+
145
+ buildManifest = {
146
+ apis: allApis,
147
+ apiDependencies: deps,
148
+ };
149
+
150
+ this.emitFile({
151
+ type: "prebuilt-chunk",
152
+ fileName: "assets/build-manifest.js",
153
+ code: `export default ${JSON.stringify(buildManifest)}`,
154
+ exports: ["default"],
155
+ });
156
+ },
157
+ } as Plugin;
158
+ }
159
+
160
+ function getAllUniqueApiNames(
161
+ apiFiles: Record<string, DeleteMeLibraryApi>,
162
+ ): string[] {
163
+ const allApiNamesSet = new Set<string>(
164
+ Object.values(apiFiles).map((api) => api.apiPb.metadata.name),
165
+ );
166
+ return Array.from(allApiNamesSet);
167
+ }
168
+
169
+ async function getApiDependencies(
170
+ apiNames: string[],
171
+ apiFiles: Record<string, DeleteMeLibraryApi>,
172
+ ): Promise<ApiDependency[]> {
173
+ const deps: ApiDependency[] = [];
174
+
175
+ // TODO: This logic is a copy of the extractApiParamsAndDependencies function in the
176
+ // fileSyncVitePlugin (https://github.com/superblocksteam/superblocks/blob/474c0b11d79a1d6988864fb20d5ec26f92931fde/packages/vite-plugin-file-sync/src/file-sync-vite-plugin.ts#L261-L280).
177
+ //
178
+ // This logic should be refactored so that it can be shared between the two plugins.
179
+ await Promise.all([
180
+ ...Object.values(apiFiles).map(async (api) => {
181
+ const bindings = await extractIdentifierPathsFromApi(api);
182
+ const allIdentifiers = bindings.flatMap((binding) => binding.bindings);
183
+ const apiDependencies = extractApiDependencies(apiNames, bindings);
184
+ deps.push({
185
+ apiName: api.apiPb.metadata.name,
186
+ params: allIdentifiers,
187
+ dependencies: apiDependencies,
188
+ });
189
+ }),
190
+ ]);
191
+
192
+ return deps;
193
+ }
@@ -29,7 +29,7 @@ const routesFileBaseName = "routes.json";
29
29
  export async function injectSuperblocksIdsPlugin(root: string) {
30
30
  const viteLogger = createLogger();
31
31
  const logger = getLogger();
32
- viteLogger.info = logger.info;
32
+ viteLogger.info = (msg: string) => logger.info(msg);
33
33
  viteLogger.warn = (msg: string) => {
34
34
  logger.warn(yellow(msg));
35
35
  };