@superblocksteam/sdk 2.0.3-next.103 → 2.0.3-next.105

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.
@@ -0,0 +1,315 @@
1
+ import * as child_process from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { isNativeError } from "node:util/types";
4
+ import { isEqual, pickBy } from "lodash-es";
5
+ import { resolveCommand, type DetectResult } from "package-manager-detector";
6
+ import { detect } from "package-manager-detector/detect";
7
+ import gt from "semver/functions/gt.js";
8
+ import valid from "semver/functions/valid.js";
9
+ import { getLogger } from "../dev-utils/dev-logger.mjs";
10
+ import type { ResponseMeta } from "../socket/handlers.js";
11
+ import type { ApplicationConfig } from "../types/common.js";
12
+ import type { LockService } from "@superblocksteam/vite-plugin-file-sync/lock-service";
13
+
14
+ const exec = promisify(child_process.exec);
15
+ const logger = getLogger();
16
+
17
+ interface Versions {
18
+ [pkg: string]: string;
19
+ }
20
+
21
+ interface CheckVersionsErrorResponse {
22
+ responseMeta: ResponseMeta;
23
+ }
24
+
25
+ interface CheckVersionsSuccessResponse {
26
+ data: Versions;
27
+ responseMeta: ResponseMeta;
28
+ }
29
+
30
+ async function getRemoteCliLibraryVersions(
31
+ config: ApplicationConfig,
32
+ ): Promise<Versions | undefined> {
33
+ const { token, superblocksBaseUrl, id } = config;
34
+
35
+ try {
36
+ const response = await fetch(
37
+ new URL(
38
+ `api/v3/applications/${id}/cli-library-versions`,
39
+ superblocksBaseUrl,
40
+ ),
41
+ {
42
+ method: "GET",
43
+ headers: {
44
+ Authorization: `Bearer ${token}`,
45
+ },
46
+ },
47
+ );
48
+
49
+ if (response.ok) {
50
+ const data = (await response.json()) as CheckVersionsSuccessResponse;
51
+ return data.data;
52
+ } else {
53
+ const error = (await response.json()) as CheckVersionsErrorResponse;
54
+ console.log(
55
+ `Could not get latest CLI version: ${error.responseMeta.message}`,
56
+ );
57
+ }
58
+ } catch (error) {
59
+ if (isNativeError(error)) {
60
+ logger.error(error.message);
61
+ } else {
62
+ logger.error(`${error}`);
63
+ }
64
+ }
65
+ }
66
+
67
+ async function installPackageVersions(pm: DetectResult, versions: Versions) {
68
+ const installCommand = resolveCommand(
69
+ pm.agent,
70
+ "install",
71
+ Object.entries(versions).map(
72
+ ([pkg, version]) => `@superblocksteam/${pkg}@${version}`,
73
+ ),
74
+ );
75
+
76
+ if (!installCommand) {
77
+ console.error(
78
+ "We could not determine how to upgrade your Superblocks packages.",
79
+ );
80
+ return;
81
+ }
82
+
83
+ const { command, args } = installCommand;
84
+
85
+ // upgrade packages
86
+ return await exec(`${command} ${args.join(" ")}`);
87
+ }
88
+
89
+ async function findSuperblocksExecutable(): Promise<string | undefined> {
90
+ try {
91
+ // Use cross-platform approach to find superblocks executable
92
+ const command = process.platform === "win32" ? "where" : "which";
93
+ const { stdout } = await exec(`${command} superblocks`);
94
+ return stdout.trim();
95
+ } catch {
96
+ return undefined;
97
+ }
98
+ }
99
+
100
+ async function getLocalCliLibraryVersions(
101
+ pm: DetectResult,
102
+ ): Promise<Partial<Versions>> {
103
+ return {
104
+ cli: await getLocalCliVersion(),
105
+ library: await getLocalPackageVersion(pm, "library"),
106
+ };
107
+ }
108
+
109
+ async function getLocalCliVersion(): Promise<string | undefined> {
110
+ try {
111
+ const superblocksPath = await findSuperblocksExecutable();
112
+ if (!superblocksPath) {
113
+ return undefined;
114
+ }
115
+
116
+ const { stdout } = await exec(`${superblocksPath} version --json`);
117
+ const json = JSON.parse(stdout) as Record<string, string>;
118
+ // Extract version from string like "@superblocksteam/cli/2.0.0-next.1"
119
+ return json.cliVersion?.replace("@superblocksteam/cli/", "");
120
+ } catch (error) {
121
+ if (isNativeError(error)) {
122
+ logger.error(`Error getting CLI version: ${error.message}`);
123
+ }
124
+ return undefined;
125
+ }
126
+ }
127
+
128
+ async function getLocalPackageVersion(
129
+ pm: DetectResult,
130
+ pkg: string,
131
+ global?: boolean,
132
+ ) {
133
+ try {
134
+ // Configure commands and parsing logic based on package manager type
135
+ switch (pm.agent) {
136
+ case "npm": {
137
+ const { stdout } = await exec(
138
+ `npm list ${global ? "--global" : ""} @superblocksteam/${pkg} --json`,
139
+ {
140
+ cwd: process.cwd(),
141
+ },
142
+ );
143
+ const parsed = JSON.parse(stdout);
144
+ return (
145
+ parsed.dependencies?.[`@superblocksteam/${pkg}`]?.version ??
146
+ parsed.devDependencies?.[`@superblocksteam/${pkg}`]?.version
147
+ );
148
+ }
149
+ case "pnpm": {
150
+ const { stdout } = await exec(
151
+ `pnpm list ${global ? "--global" : ""} @superblocksteam/${pkg} --json`,
152
+ { cwd: process.cwd() },
153
+ );
154
+ const parsed = JSON.parse(stdout);
155
+ // Check the structure based on actual output
156
+ return (
157
+ parsed[0]?.dependencies?.[`@superblocksteam/${pkg}`]?.version ??
158
+ parsed[0]?.devDependencies?.[`@superblocksteam/${pkg}`]?.version
159
+ );
160
+ }
161
+ default:
162
+ console.error(
163
+ `${pm.agent} is currently not supported. Your application will not be upgraded automatically.`,
164
+ );
165
+ return;
166
+ }
167
+ } catch (e) {
168
+ console.error(
169
+ "We couldn't resolve your current Superblocks package versions. Your application will not be upgraded automatically.",
170
+ );
171
+ console.error(e);
172
+ return;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Check if the CLI is updatable via the oclif update command
178
+ * This indicates it was installed through a prebuilt installer (and updates the CLI)
179
+ */
180
+ async function tryCliUpdateCommand(version: string): Promise<boolean> {
181
+ try {
182
+ const superblocksPath = await findSuperblocksExecutable();
183
+ if (!superblocksPath) {
184
+ return false;
185
+ }
186
+
187
+ // Check if the CLI is installed in a way that allows direct updates
188
+ // We'll try running the update command with --version to see if it shows an updatable message
189
+ const { stdout } = await exec(
190
+ `${superblocksPath} update --version=${version}`,
191
+ );
192
+
193
+ // If it mentions "not updatable" in either stdout or stderr, it's not a prebuilt installer
194
+ return !stdout.includes("not updatable");
195
+ } catch (error) {
196
+ if (isNativeError(error)) {
197
+ logger.error(`Error checking updatability: ${error.message}`);
198
+ }
199
+ return false;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Restart the CLI
205
+ */
206
+ async function restartCli() {
207
+ // Get the args that were used to start the current process
208
+ const args = process.argv.slice(1);
209
+
210
+ // Create a new process with the same arguments
211
+ const child = child_process.spawn(process.execPath, [...args], {
212
+ detached: true,
213
+ stdio: "inherit",
214
+ env: process.env,
215
+ });
216
+
217
+ // Disconnect the child from the parent
218
+ child.unref();
219
+
220
+ // Exit the current process after the next tick to ensure any logs were written to stdout
221
+ process.nextTick(() => process.exit(0));
222
+ }
223
+
224
+ export async function checkVersionsAndUpgrade(
225
+ lockService: LockService,
226
+ config?: ApplicationConfig,
227
+ ) {
228
+ const pm = await detect({
229
+ strategies: [
230
+ "packageManager-field",
231
+ "lockfile",
232
+ "install-metadata",
233
+ "devEngines-field",
234
+ ],
235
+ });
236
+
237
+ if (!pm) return;
238
+ if (!config?.id) return;
239
+
240
+ const localVersions = await getLocalCliLibraryVersions(pm);
241
+
242
+ if (
243
+ // don't attempt upgrades in local development
244
+ Object.values(localVersions).some(
245
+ (x) => !x || !valid(x) || x.startsWith("file:") || x.startsWith("link:"),
246
+ )
247
+ ) {
248
+ return;
249
+ }
250
+
251
+ const serverVersions = await getRemoteCliLibraryVersions(config);
252
+
253
+ // don't attempt upgrades if
254
+ if (
255
+ // server versions could not be fetched, or
256
+ !serverVersions ||
257
+ // we have a different # of libraries in server/local
258
+ Object.keys(localVersions).length !== Object.keys(serverVersions).length
259
+ ) {
260
+ return;
261
+ }
262
+
263
+ const isUpToDate = isEqual(localVersions, serverVersions);
264
+
265
+ if (isUpToDate) return;
266
+
267
+ // Determine which packages need to be updated
268
+ const packagesToUpdate = pickBy(localVersions, (_, key) => {
269
+ try {
270
+ return gt(serverVersions[key], (localVersions as Versions)[key]);
271
+ } catch {
272
+ return false;
273
+ }
274
+ }) as Versions;
275
+
276
+ if (Object.keys(packagesToUpdate).length === 0) {
277
+ return;
278
+ }
279
+
280
+ let cliUpdated = false;
281
+ let libraryUpdated = false;
282
+
283
+ // Handle CLI update separately if it needs updating
284
+ if (packagesToUpdate.cli) {
285
+ // Try to update using Oclif's builtin update command
286
+ const isUpdated = await tryCliUpdateCommand(serverVersions.cli);
287
+
288
+ if (!isUpdated) {
289
+ // try to update using package manager (npm/pnpm/yarn)
290
+ await installPackageVersions(pm, { cli: serverVersions.cli });
291
+ cliUpdated = true;
292
+ }
293
+ }
294
+
295
+ // Always use package manager for library updates
296
+ if (packagesToUpdate.library) {
297
+ await installPackageVersions(pm, { library: serverVersions.library });
298
+ libraryUpdated = true;
299
+ }
300
+
301
+ if (cliUpdated && libraryUpdated) {
302
+ logger.info(
303
+ "@superblocksteam/cli and @superblocksteam/library have been updated.",
304
+ );
305
+ } else if (cliUpdated) {
306
+ logger.info("@superblocksteam/cli has been updated.");
307
+ } else if (libraryUpdated) {
308
+ logger.info("@superblocksteam/library has been updated.");
309
+ }
310
+
311
+ logger.info("Restarting the CLI…");
312
+
313
+ await lockService.releaseLock();
314
+ await restartCli();
315
+ }
@@ -1,4 +1,5 @@
1
1
  import "../dev-utils/dev-tracer.js";
2
+
2
3
  import * as fsp from "node:fs/promises";
3
4
  import { maskUnixSignals } from "@superblocksteam/util";
4
5
  import { AiService } from "@superblocksteam/vite-plugin-file-sync/ai-service";
@@ -9,19 +10,15 @@ import {
9
10
  import { OperationQueue } from "@superblocksteam/vite-plugin-file-sync/operation-queue";
10
11
  import { SyncService } from "@superblocksteam/vite-plugin-file-sync/sync-service";
11
12
  import { green } from "colorette";
13
+
12
14
  import { getLogger } from "../dev-utils/dev-logger.mjs";
13
15
  import { createDevServer } from "../dev-utils/dev-server.mjs";
14
16
  import { SuperblocksSdk } from "../sdk.js";
17
+ import { checkVersionsAndUpgrade } from "./automatic-upgrades.js";
15
18
  import type { DevLogger } from "../dev-utils/dev-logger.mjs";
19
+ import type { ApplicationConfig } from "../types/common.js";
16
20
  import type { DraftInterface } from "@superblocksteam/vite-plugin-file-sync/draft-interface";
17
21
 
18
- export interface ApplicationConfig {
19
- superblocksBaseUrl: string;
20
- token: string;
21
- id: string;
22
- branchName: string;
23
- }
24
-
25
22
  const passErrorToVSCode = (message: string, logger: DevLogger) => {
26
23
  if (message) {
27
24
  // Prefixing with `clierr:` will make the VS code extension capture this message and show it to the user.
@@ -105,6 +102,7 @@ export async function dev(options: {
105
102
  if (lockService) {
106
103
  try {
107
104
  await lockService!.acquireLock();
105
+ await checkVersionsAndUpgrade(lockService, applicationConfig);
108
106
  } catch (error) {
109
107
  passErrorToVSCode(
110
108
  (error as { context: { message: string } }).context.message,
package/src/index.ts CHANGED
@@ -53,6 +53,7 @@ export {
53
53
  type ApiWithPb,
54
54
  type AppToSign,
55
55
  type AppToVerify,
56
+ type ApplicationConfig,
56
57
  type ApplicationSettingsHashes,
57
58
  type ApplicationSignatureTree,
58
59
  type ApplicationSignatureTreeSigned,
@@ -113,7 +114,7 @@ export {
113
114
 
114
115
  export { createDevServer } from "./dev-utils/dev-server.mjs";
115
116
 
116
- export { dev, type ApplicationConfig } from "./cli-replacement/dev.mjs";
117
+ export { dev } from "./cli-replacement/dev.mjs";
117
118
  export { fetchAndWriteApplication } from "./cli-replacement/init.js";
118
119
 
119
120
  export { ExportViewMode } from "@superblocksteam/shared";
@@ -14,6 +14,13 @@ export interface FlagBootstrap {
14
14
  "superblocks.git.split.large.step.lines"?: number;
15
15
  }
16
16
 
17
+ export interface ApplicationConfig {
18
+ superblocksBaseUrl: string;
19
+ token: string;
20
+ id: string;
21
+ branchName: string;
22
+ }
23
+
17
24
  export type User = {
18
25
  id: string;
19
26
  email: string;