@superblocksteam/sdk 1.14.2 → 2.0.3-next.46
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/.mocharc.json +7 -0
- package/.prettierrc +18 -0
- package/dist/cli-replacement/dev.d.mts +19 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -0
- package/dist/cli-replacement/dev.mjs +122 -0
- package/dist/cli-replacement/dev.mjs.map +1 -0
- package/dist/cli-replacement/init.d.ts +14 -0
- package/dist/cli-replacement/init.d.ts.map +1 -0
- package/dist/cli-replacement/init.js +26 -0
- package/dist/cli-replacement/init.js.map +1 -0
- package/dist/client.d.ts +31 -17
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +137 -155
- package/dist/client.js.map +1 -0
- package/dist/dbfs/client.d.ts +6 -0
- package/dist/dbfs/client.d.ts.map +1 -0
- package/dist/dbfs/client.js +117 -0
- package/dist/dbfs/client.js.map +1 -0
- package/dist/dbfs/local.d.ts +15 -0
- package/dist/dbfs/local.d.ts.map +1 -0
- package/dist/dbfs/local.js +126 -0
- package/dist/dbfs/local.js.map +1 -0
- package/dist/dev-utils/custom-build.d.mts +4 -0
- package/dist/dev-utils/custom-build.d.mts.map +1 -0
- package/dist/dev-utils/custom-build.mjs +99 -0
- package/dist/dev-utils/custom-build.mjs.map +1 -0
- package/dist/dev-utils/custom-config.d.mts +2 -0
- package/dist/dev-utils/custom-config.d.mts.map +1 -0
- package/dist/dev-utils/custom-config.mjs +57 -0
- package/dist/dev-utils/custom-config.mjs.map +1 -0
- package/dist/dev-utils/dev-logger.d.mts +8 -0
- package/dist/dev-utils/dev-logger.d.mts.map +1 -0
- package/dist/dev-utils/dev-logger.mjs +25 -0
- package/dist/dev-utils/dev-logger.mjs.map +1 -0
- package/dist/dev-utils/dev-server.d.mts +18 -0
- package/dist/dev-utils/dev-server.d.mts.map +1 -0
- package/dist/dev-utils/dev-server.mjs +265 -0
- package/dist/dev-utils/dev-server.mjs.map +1 -0
- package/dist/dev-utils/dev-tracer.d.ts +3 -0
- package/dist/dev-utils/dev-tracer.d.ts.map +1 -0
- package/dist/dev-utils/dev-tracer.js +28 -0
- package/dist/dev-utils/dev-tracer.js.map +1 -0
- package/dist/dev-utils/vite-plugin-dd-rum.d.mts +10 -0
- package/dist/dev-utils/vite-plugin-dd-rum.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-dd-rum.mjs +34 -0
- package/dist/dev-utils/vite-plugin-dd-rum.mjs.map +1 -0
- package/dist/dev-utils/vite-plugin-react-transform.d.mts +7 -0
- package/dist/dev-utils/vite-plugin-react-transform.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-react-transform.mjs +110 -0
- package/dist/dev-utils/vite-plugin-react-transform.mjs.map +1 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.d.mts +34 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.d.mts.map +1 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.mjs +720 -0
- package/dist/dev-utils/vite-plugin-sb-cdn.mjs.map +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -9
- package/dist/errors.js.map +1 -0
- package/dist/flag.d.ts +2 -2
- package/dist/flag.d.ts.map +1 -0
- package/dist/flag.js +5 -9
- package/dist/flag.js.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -20
- package/dist/index.js.map +1 -0
- package/dist/sdk.d.ts +42 -18
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +47 -43
- package/dist/sdk.js.map +1 -0
- package/dist/socket/handlers.d.ts +3 -128
- package/dist/socket/handlers.d.ts.map +1 -0
- package/dist/socket/handlers.js +7 -9
- package/dist/socket/handlers.js.map +1 -0
- package/dist/socket/index.d.ts +4 -3
- package/dist/socket/index.d.ts.map +1 -0
- package/dist/socket/index.js +12 -21
- package/dist/socket/index.js.map +1 -0
- package/dist/socket/signing.d.ts +3 -1
- package/dist/socket/signing.d.ts.map +1 -0
- package/dist/socket/signing.js +8 -17
- package/dist/socket/signing.js.map +1 -0
- package/dist/socket/socket.d.ts +3 -2
- package/dist/socket/socket.d.ts.map +1 -0
- package/dist/socket/socket.js +9 -19
- package/dist/socket/socket.js.map +1 -0
- package/dist/types/common.d.ts +2 -103
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +8 -24
- package/dist/types/common.js.map +1 -0
- package/dist/types/index.d.ts +5 -4
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -20
- package/dist/types/index.js.map +1 -0
- package/dist/types/plugin.d.ts +1 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +3 -5
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/signing.d.ts +2 -1
- package/dist/types/signing.d.ts.map +1 -0
- package/dist/types/signing.js +2 -2
- package/dist/types/signing.js.map +1 -0
- package/dist/types/socket.d.ts +1 -0
- package/dist/types/socket.d.ts.map +1 -0
- package/dist/types/socket.js +2 -5
- package/dist/types/socket.js.map +1 -0
- package/dist/utils.d.ts +3 -1
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +14 -23
- package/dist/utils.js.map +1 -0
- package/dist/version-control.d.mts +59 -0
- package/dist/version-control.d.mts.map +1 -0
- package/dist/version-control.mjs +899 -0
- package/dist/version-control.mjs.map +1 -0
- package/eslint.config.js +85 -0
- package/package.json +72 -32
- package/src/cli-replacement/dev.mts +182 -0
- package/src/cli-replacement/init.ts +47 -0
- package/src/client.ts +114 -38
- package/src/dbfs/client.ts +162 -0
- package/src/dbfs/local.ts +163 -0
- package/src/dev-utils/custom-build.mts +113 -0
- package/src/dev-utils/custom-config.mts +66 -0
- package/src/dev-utils/dev-logger.mts +39 -0
- package/src/dev-utils/dev-server.mts +342 -0
- package/src/dev-utils/dev-tracer.ts +31 -0
- package/src/dev-utils/vite-plugin-dd-rum.mts +47 -0
- package/src/dev-utils/vite-plugin-react-transform.mts +130 -0
- package/src/dev-utils/vite-plugin-sb-cdn.mts +988 -0
- package/src/flag.ts +2 -3
- package/src/index.ts +119 -4
- package/src/sdk.ts +91 -17
- package/src/socket/handlers.ts +9 -147
- package/src/socket/index.ts +6 -9
- package/src/socket/signing.ts +7 -8
- package/src/socket/socket.ts +8 -9
- package/src/types/common.ts +2 -119
- package/src/types/index.ts +4 -4
- package/src/types/signing.ts +1 -1
- package/src/types/socket.ts +1 -1
- package/src/utils.ts +5 -6
- package/src/version-control.mts +1351 -0
- package/test/dev-utils/fixture/index.html +12 -0
- package/test/dev-utils/fixture/main.jsx +22 -0
- package/test/dev-utils/fixture/package-lock.json +25 -0
- package/test/dev-utils/fixture/package.json +9 -0
- package/test/dev-utils/vite-plugin-sb-cdn.test.mts +74 -0
- package/test/tsconfig.json +9 -0
- package/test/version-control.test.mts +1412 -0
- package/tsconfig.json +15 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/.eslintrc.json +0 -55
package/src/client.ts
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
+
import { Bucketeer, FileDescriptor } from "@superblocksteam/bucketeer-sdk";
|
|
3
|
+
import { ExportViewMode } from "@superblocksteam/shared";
|
|
2
4
|
import {
|
|
3
5
|
COMPONENT_EVENT_HEADER,
|
|
4
6
|
ComponentEvent,
|
|
5
7
|
ForbiddenError,
|
|
6
8
|
getBucketeerUrlFromSuperblocksUrl,
|
|
7
9
|
getContentType,
|
|
8
|
-
LocalGitRepoState,
|
|
9
10
|
NotFoundError,
|
|
10
|
-
SuperblocksResourceType,
|
|
11
11
|
BadRequestError,
|
|
12
12
|
unreachable,
|
|
13
|
-
ValidateGitSetupRequestBody,
|
|
14
13
|
} from "@superblocksteam/util";
|
|
15
|
-
import axios, { AxiosError
|
|
14
|
+
import axios, { AxiosError } from "axios";
|
|
16
15
|
import FormData from "form-data";
|
|
17
|
-
import { isEqual, isEmpty } from "lodash";
|
|
16
|
+
import { isEqual, isEmpty } from "lodash-es";
|
|
18
17
|
import {
|
|
19
18
|
BranchNotCheckedOutError,
|
|
20
19
|
CommitAlreadyExistsError,
|
|
21
20
|
ValidateGitSetupError,
|
|
22
|
-
} from "./errors";
|
|
23
|
-
import { signingEnabled } from "./flag";
|
|
24
|
-
import { connectToISocketRPCServer
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
} from "./errors.js";
|
|
22
|
+
import { signingEnabled } from "./flag.js";
|
|
23
|
+
import { connectToISocketRPCServer } from "./socket/index.js";
|
|
24
|
+
import { AgentType } from "./types/index.js";
|
|
25
|
+
import { getAgentUrl } from "./utils.js";
|
|
26
|
+
import type { StdISocketRPCClient } from "./socket/index.js";
|
|
27
|
+
import type {
|
|
28
28
|
ApiWithPb,
|
|
29
29
|
Page,
|
|
30
30
|
RemoteCommitDto,
|
|
31
|
-
UserMeDto,
|
|
32
|
-
ViewMode,
|
|
33
31
|
DeploymentDto,
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
UserMeDto,
|
|
33
|
+
} from "./types/index.js";
|
|
34
|
+
import type {
|
|
35
|
+
LocalGitRepoState,
|
|
36
|
+
SuperblocksResourceType,
|
|
37
|
+
ValidateGitSetupRequestBody,
|
|
38
|
+
} from "@superblocksteam/util";
|
|
39
|
+
import type { AxiosRequestConfig } from "axios";
|
|
36
40
|
|
|
37
41
|
const BASE_BUCKETEER_URL = "api";
|
|
38
42
|
const BASE_SERVER_PUBLIC_API_URL_V1 = "api/v1/public";
|
|
@@ -63,23 +67,31 @@ export interface MultiPageApplicationWrapper {
|
|
|
63
67
|
apis: Record<string, any>[];
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
export interface MultiPageApplicationWrapperWithComponents {
|
|
71
|
+
type: "multi-page";
|
|
72
|
+
application: Record<string, any>;
|
|
73
|
+
pages: Page[];
|
|
74
|
+
apis: Record<string, any>[];
|
|
75
|
+
componentFiles: any;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CodeModeApplicationWrapper {
|
|
79
|
+
type: "code-mode";
|
|
80
|
+
application: Record<string, any>;
|
|
81
|
+
}
|
|
82
|
+
|
|
66
83
|
export type PushMultiPageApplicationWithCommitConfig =
|
|
67
84
|
MultiPageApplicationWrapper & {
|
|
68
|
-
commitId
|
|
69
|
-
commitMessage
|
|
85
|
+
commitId: string;
|
|
86
|
+
commitMessage: string;
|
|
70
87
|
gitState: LocalGitRepoState;
|
|
71
88
|
skipCommit: boolean;
|
|
72
89
|
};
|
|
73
90
|
|
|
74
|
-
export interface ApiWrapper {
|
|
75
|
-
apiPb: Api;
|
|
76
|
-
name?: string; //@deprecated this attribute is used for getting a name for backends(jobs and workflows) that were never migrated to the new API version.
|
|
77
|
-
}
|
|
78
|
-
|
|
79
91
|
export type PushApiWithCommitConfig = {
|
|
80
92
|
apiPb: Record<string, any>;
|
|
81
|
-
commitId
|
|
82
|
-
commitMessage
|
|
93
|
+
commitId: string;
|
|
94
|
+
commitMessage: string;
|
|
83
95
|
gitState: LocalGitRepoState;
|
|
84
96
|
skipCommit: boolean;
|
|
85
97
|
};
|
|
@@ -136,12 +148,12 @@ export async function fetchApplication({
|
|
|
136
148
|
branch?: string;
|
|
137
149
|
token: string;
|
|
138
150
|
superblocksBaseUrl: string;
|
|
139
|
-
viewMode:
|
|
151
|
+
viewMode: ExportViewMode;
|
|
140
152
|
commitId?: string;
|
|
141
153
|
skipSigningVerification?: boolean;
|
|
142
154
|
injectedHeaders: Record<string, string>;
|
|
143
155
|
}): Promise<MultiPageApplicationWrapper | undefined> {
|
|
144
|
-
if (commitId && viewMode !==
|
|
156
|
+
if (commitId && viewMode !== ExportViewMode.EXPORT_COMMIT) {
|
|
145
157
|
throw new Error(
|
|
146
158
|
`If commitId ${commitId} is provided, viewMode cannot be ${viewMode}`,
|
|
147
159
|
);
|
|
@@ -177,7 +189,7 @@ export async function fetchApplication({
|
|
|
177
189
|
branchName: branch,
|
|
178
190
|
commitId,
|
|
179
191
|
});
|
|
180
|
-
return resp.data;
|
|
192
|
+
return resp.data as unknown as MultiPageApplicationWrapper;
|
|
181
193
|
} finally {
|
|
182
194
|
socket.close();
|
|
183
195
|
}
|
|
@@ -263,14 +275,13 @@ export async function fetchApplicationWithComponents({
|
|
|
263
275
|
branch: string;
|
|
264
276
|
token: string;
|
|
265
277
|
superblocksBaseUrl: string;
|
|
266
|
-
viewMode:
|
|
278
|
+
viewMode: ExportViewMode;
|
|
267
279
|
commitId?: string;
|
|
268
280
|
skipSigningVerification?: boolean;
|
|
269
281
|
injectedHeaders: Record<string, string>;
|
|
270
282
|
}): Promise<
|
|
271
|
-
|
|
|
272
|
-
|
|
273
|
-
})
|
|
283
|
+
| MultiPageApplicationWrapperWithComponents
|
|
284
|
+
| CodeModeApplicationWrapper
|
|
274
285
|
| undefined
|
|
275
286
|
> {
|
|
276
287
|
const applicationWrapper = await fetchApplication({
|
|
@@ -289,9 +300,48 @@ export async function fetchApplicationWithComponents({
|
|
|
289
300
|
return;
|
|
290
301
|
}
|
|
291
302
|
|
|
303
|
+
if (applicationWrapper.application.devEnvEnabled) {
|
|
304
|
+
return {
|
|
305
|
+
type: "code-mode",
|
|
306
|
+
...applicationWrapper,
|
|
307
|
+
} satisfies CodeModeApplicationWrapper;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return await fetchApplicationWithComponentsFromBucketeer({
|
|
311
|
+
applicationWrapper,
|
|
312
|
+
superblocksBaseUrl,
|
|
313
|
+
applicationId,
|
|
314
|
+
branch,
|
|
315
|
+
commitId,
|
|
316
|
+
viewMode,
|
|
317
|
+
token,
|
|
318
|
+
injectedHeaders,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function fetchApplicationWithComponentsFromBucketeer({
|
|
323
|
+
applicationWrapper,
|
|
324
|
+
superblocksBaseUrl,
|
|
325
|
+
applicationId,
|
|
326
|
+
branch,
|
|
327
|
+
commitId,
|
|
328
|
+
viewMode,
|
|
329
|
+
token,
|
|
330
|
+
injectedHeaders,
|
|
331
|
+
}: {
|
|
332
|
+
applicationWrapper: MultiPageApplicationWrapper;
|
|
333
|
+
superblocksBaseUrl: string;
|
|
334
|
+
applicationId: string;
|
|
335
|
+
branch: string;
|
|
336
|
+
commitId?: string;
|
|
337
|
+
viewMode: ExportViewMode;
|
|
338
|
+
token: string;
|
|
339
|
+
injectedHeaders: Record<string, string>;
|
|
340
|
+
}): Promise<MultiPageApplicationWrapperWithComponents> {
|
|
292
341
|
// if there are no custom components, just return here without trying to initialize them using bucketeer
|
|
293
342
|
if (isEmpty(applicationWrapper.application?.settings?.registeredComponents)) {
|
|
294
343
|
return {
|
|
344
|
+
type: "multi-page",
|
|
295
345
|
...applicationWrapper,
|
|
296
346
|
componentFiles: null,
|
|
297
347
|
};
|
|
@@ -318,9 +368,7 @@ export async function fetchApplicationWithComponents({
|
|
|
318
368
|
},
|
|
319
369
|
};
|
|
320
370
|
const bucketeerApp = (await axios(config))
|
|
321
|
-
.data as
|
|
322
|
-
componentFiles: any;
|
|
323
|
-
};
|
|
371
|
+
.data as MultiPageApplicationWrapperWithComponents;
|
|
324
372
|
|
|
325
373
|
if (
|
|
326
374
|
!isEqual(
|
|
@@ -383,7 +431,7 @@ export async function fetchApi(
|
|
|
383
431
|
apiId: string,
|
|
384
432
|
token: string,
|
|
385
433
|
superblocksBaseUrl: string,
|
|
386
|
-
viewMode:
|
|
434
|
+
viewMode: ExportViewMode,
|
|
387
435
|
branch?: string,
|
|
388
436
|
commitId?: string,
|
|
389
437
|
skipSigningVerification = false,
|
|
@@ -716,7 +764,7 @@ You can reduce your component bundle size by uploading static assets to a separa
|
|
|
716
764
|
branchName: branch ?? undefined,
|
|
717
765
|
srcFiles: srcFiles.map((file) => file.filename),
|
|
718
766
|
buildFiles: buildFiles.map((file) => file.filename),
|
|
719
|
-
registeredComponents: componentConfigs,
|
|
767
|
+
registeredComponents: componentConfigs as Record<string, never>,
|
|
720
768
|
cliVersion,
|
|
721
769
|
componentBaseUrl: uploadResponse.data.componentBaseUrl,
|
|
722
770
|
signingRequired: !isEmpty(initialSocket),
|
|
@@ -858,7 +906,7 @@ export async function pushApplication({
|
|
|
858
906
|
if (resp.responseMeta.status !== 200) {
|
|
859
907
|
// Get the raw error message from the server and throw it. The outer try-catch block will wrap it in a nicer error message
|
|
860
908
|
const message: string =
|
|
861
|
-
|
|
909
|
+
resp?.responseMeta?.message ?? JSON.stringify(resp?.data);
|
|
862
910
|
throw new Error(message);
|
|
863
911
|
}
|
|
864
912
|
return resp.data;
|
|
@@ -968,7 +1016,7 @@ export async function pushApi({
|
|
|
968
1016
|
if (resp.responseMeta.status !== 200) {
|
|
969
1017
|
// Get the raw error message from the server and throw it. The outer try-catch block will wrap it in a nicer error message
|
|
970
1018
|
const message: string =
|
|
971
|
-
|
|
1019
|
+
resp?.responseMeta?.message ?? JSON.stringify(resp?.data);
|
|
972
1020
|
throw new Error(message);
|
|
973
1021
|
}
|
|
974
1022
|
return resp.data;
|
|
@@ -1215,3 +1263,31 @@ async function deployResource(
|
|
|
1215
1263
|
throw new Error(`${e.message}`);
|
|
1216
1264
|
}
|
|
1217
1265
|
}
|
|
1266
|
+
|
|
1267
|
+
export async function uploadApplication({
|
|
1268
|
+
files,
|
|
1269
|
+
scopedJwt,
|
|
1270
|
+
url,
|
|
1271
|
+
cliVersion,
|
|
1272
|
+
}: {
|
|
1273
|
+
files: string[];
|
|
1274
|
+
scopedJwt: string;
|
|
1275
|
+
url: string;
|
|
1276
|
+
cliVersion: string;
|
|
1277
|
+
}) {
|
|
1278
|
+
const fds = filesToFileDescriptors(files);
|
|
1279
|
+
const bucketeer = new Bucketeer({
|
|
1280
|
+
token: scopedJwt,
|
|
1281
|
+
baseUrl: url,
|
|
1282
|
+
cliVersion,
|
|
1283
|
+
maxTotalFileSizeMB: SUPERBLOCKS_MAX_FILE_SIZE_MB,
|
|
1284
|
+
});
|
|
1285
|
+
await bucketeer.uploadApplication(fds);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function filesToFileDescriptors(files: string[]) {
|
|
1289
|
+
const fds = files.map((file) => {
|
|
1290
|
+
return new FileDescriptor(file, fs.createReadStream(file));
|
|
1291
|
+
});
|
|
1292
|
+
return fds;
|
|
1293
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { unreachable } from "@superblocksteam/util";
|
|
2
|
+
import { connectToISocketRPCServer } from "../socket/index.js";
|
|
3
|
+
import { doDownloadDirectoryToLocal, doUploadLocalDirectory } from "./local.js";
|
|
4
|
+
|
|
5
|
+
// TODO(code-mode): this is re-implemented in the vite-plugin-file-sync package
|
|
6
|
+
// because SDK depends on vite-plugin-file-sync, but can't be circular
|
|
7
|
+
export async function getApplicationDirectoryHash(
|
|
8
|
+
token: string,
|
|
9
|
+
superblocksBaseUrl: string,
|
|
10
|
+
applicationId: string,
|
|
11
|
+
branch: string | undefined,
|
|
12
|
+
) {
|
|
13
|
+
const rpcClient = await connectToISocketRPCServer({
|
|
14
|
+
token,
|
|
15
|
+
superblocksBaseUrl,
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
const response =
|
|
19
|
+
await rpcClient.call.v3.application.liveEditDirectoryContents.get({
|
|
20
|
+
applicationId,
|
|
21
|
+
branchName: branch,
|
|
22
|
+
});
|
|
23
|
+
const liveEditHash = response.data.hash;
|
|
24
|
+
if (!liveEditHash) {
|
|
25
|
+
throw new Error("No live edit hash found");
|
|
26
|
+
}
|
|
27
|
+
return liveEditHash;
|
|
28
|
+
} finally {
|
|
29
|
+
rpcClient.close();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function downloadApplicationDirectory(
|
|
34
|
+
token: string,
|
|
35
|
+
superblocksBaseUrl: string,
|
|
36
|
+
applicationId: string,
|
|
37
|
+
branch: string | undefined,
|
|
38
|
+
localDirectoryPath: string,
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const rpcClient = await connectToISocketRPCServer({
|
|
41
|
+
token,
|
|
42
|
+
superblocksBaseUrl,
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
const liveEditHash = await getApplicationDirectoryHash(
|
|
46
|
+
token,
|
|
47
|
+
superblocksBaseUrl,
|
|
48
|
+
applicationId,
|
|
49
|
+
branch,
|
|
50
|
+
);
|
|
51
|
+
await doDownloadDirectoryToLocal(
|
|
52
|
+
rpcClient,
|
|
53
|
+
liveEditHash,
|
|
54
|
+
localDirectoryPath,
|
|
55
|
+
);
|
|
56
|
+
} finally {
|
|
57
|
+
rpcClient.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function uploadLocalApplication(
|
|
62
|
+
token: string,
|
|
63
|
+
superblocksBaseUrl: string,
|
|
64
|
+
applicationId: string,
|
|
65
|
+
branch: string | undefined,
|
|
66
|
+
localDirectoryPath: string,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const rpcClient = await connectToISocketRPCServer({
|
|
69
|
+
token,
|
|
70
|
+
superblocksBaseUrl,
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const directoryHash = await doUploadLocalDirectory(
|
|
74
|
+
rpcClient,
|
|
75
|
+
localDirectoryPath,
|
|
76
|
+
);
|
|
77
|
+
console.log(`New application directory hash: ${directoryHash}`);
|
|
78
|
+
await rpcClient.call.v3.application.liveEditDirectoryContents.set({
|
|
79
|
+
applicationId,
|
|
80
|
+
branchName: branch,
|
|
81
|
+
hash: directoryHash,
|
|
82
|
+
});
|
|
83
|
+
} finally {
|
|
84
|
+
rpcClient.close();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function printDirectoryEntries(
|
|
89
|
+
token: string,
|
|
90
|
+
superblocksBaseUrl: string,
|
|
91
|
+
directoryHash: string,
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
const rpcClient = await connectToISocketRPCServer({
|
|
94
|
+
token,
|
|
95
|
+
superblocksBaseUrl,
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const directoryContentsResponse =
|
|
99
|
+
await rpcClient.call.v1.dbfs.directoryContents.get({
|
|
100
|
+
hash: directoryHash,
|
|
101
|
+
});
|
|
102
|
+
for (const entry of directoryContentsResponse.data.contents) {
|
|
103
|
+
let executable: boolean;
|
|
104
|
+
let hash: string;
|
|
105
|
+
let target: string | undefined;
|
|
106
|
+
switch (entry.type) {
|
|
107
|
+
case "-": {
|
|
108
|
+
executable = entry.executable;
|
|
109
|
+
hash = entry.hash;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "l": {
|
|
113
|
+
executable = false;
|
|
114
|
+
target = entry.target;
|
|
115
|
+
hash = " ".repeat(Math.ceil(256 / 24) * 4);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "d": {
|
|
119
|
+
executable = false;
|
|
120
|
+
hash = entry.hash;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
default: {
|
|
124
|
+
unreachable(entry);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log(
|
|
128
|
+
`${entry.type}${executable ? "x" : " "} ${hash} ${entry.name}${
|
|
129
|
+
target ? ` -> ${target}` : ""
|
|
130
|
+
}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
rpcClient.close();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function printFileContents(
|
|
139
|
+
token: string,
|
|
140
|
+
superblocksBaseUrl: string,
|
|
141
|
+
fileHash: string,
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const rpcClient = await connectToISocketRPCServer({
|
|
144
|
+
token,
|
|
145
|
+
superblocksBaseUrl,
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
const fileContentsResponse = await rpcClient.call.v1.dbfs.fileContents.get({
|
|
149
|
+
hash: fileHash,
|
|
150
|
+
});
|
|
151
|
+
// The file can contain binary data, do not interpret it as text
|
|
152
|
+
// Note: `Buffer.from` is a Node.js specific API
|
|
153
|
+
const fileContents = Buffer.from(
|
|
154
|
+
fileContentsResponse.data.contents,
|
|
155
|
+
"base64",
|
|
156
|
+
);
|
|
157
|
+
// Note: `process.stdout.write` is a Node.js specific API
|
|
158
|
+
process.stdout.write(fileContents);
|
|
159
|
+
} finally {
|
|
160
|
+
rpcClient.close();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import * as fsp from "node:fs/promises";
|
|
2
|
+
import {
|
|
3
|
+
hashDirectoryContents,
|
|
4
|
+
hashFileContents,
|
|
5
|
+
type DirectoryEntry,
|
|
6
|
+
} from "@superblocksteam/shared";
|
|
7
|
+
import { unreachable } from "@superblocksteam/util";
|
|
8
|
+
import { listLocalDirectory } from "@superblocksteam/vite-plugin-file-sync/list-dir";
|
|
9
|
+
import type { StdISocketRPCClient } from "../socket/index.js";
|
|
10
|
+
|
|
11
|
+
export async function doDownloadDirectoryToLocal(
|
|
12
|
+
rpcClient: StdISocketRPCClient,
|
|
13
|
+
directoryHash: string,
|
|
14
|
+
localDirectoryPath: string,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
// ensure that the top-level local directory exists
|
|
17
|
+
await fsp.mkdir(localDirectoryPath, { recursive: true });
|
|
18
|
+
await doDownloadDirectoryToLocalRec(
|
|
19
|
+
rpcClient,
|
|
20
|
+
directoryHash,
|
|
21
|
+
localDirectoryPath,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function doDownloadDirectoryToLocalRec(
|
|
26
|
+
rpcClient: StdISocketRPCClient,
|
|
27
|
+
directoryHash: string,
|
|
28
|
+
localDirectoryPath: string,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
const directoryContentsResponse =
|
|
31
|
+
await rpcClient.call.v1.dbfs.directoryContents.get({ hash: directoryHash });
|
|
32
|
+
if (!directoryContentsResponse.data) {
|
|
33
|
+
throw new Error("Failed to get directory contents");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const entry of directoryContentsResponse.data.contents) {
|
|
37
|
+
const localFilePath = `${localDirectoryPath}/${entry.name}`;
|
|
38
|
+
switch (entry.type) {
|
|
39
|
+
case "-": {
|
|
40
|
+
const fileContentsResponse =
|
|
41
|
+
await rpcClient.call.v1.dbfs.fileContents.get({ hash: entry.hash });
|
|
42
|
+
const fileContents = Buffer.from(
|
|
43
|
+
fileContentsResponse.data.contents,
|
|
44
|
+
"base64",
|
|
45
|
+
);
|
|
46
|
+
await fsp.writeFile(localFilePath, fileContents);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "l": {
|
|
50
|
+
await fsp.symlink(entry.target, localFilePath);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "d": {
|
|
54
|
+
await fsp.mkdir(localFilePath, {
|
|
55
|
+
recursive: true /* do not reject if exists */,
|
|
56
|
+
});
|
|
57
|
+
await doDownloadDirectoryToLocalRec(
|
|
58
|
+
rpcClient,
|
|
59
|
+
entry.hash,
|
|
60
|
+
localFilePath,
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
unreachable(entry);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Computes the hash of a local directory, including the hashes of all its contents recursively.
|
|
73
|
+
*
|
|
74
|
+
* @param localDirectoryPath - The path to the local directory to hash.
|
|
75
|
+
* @returns A promise that resolves to the hash of the directory and its contents.
|
|
76
|
+
*/
|
|
77
|
+
export async function hashLocalDirectory(localDirectoryPath: string): Promise<{
|
|
78
|
+
hash: string;
|
|
79
|
+
contents: DirectoryEntry[];
|
|
80
|
+
}> {
|
|
81
|
+
const directoryContents: any[] = [];
|
|
82
|
+
const localDirListing = listLocalDirectory(localDirectoryPath);
|
|
83
|
+
for await (const localDirEntry of localDirListing) {
|
|
84
|
+
let entry: any;
|
|
85
|
+
switch (localDirEntry.type) {
|
|
86
|
+
case "-": {
|
|
87
|
+
const hash = await hashFileContents(localDirEntry.contents);
|
|
88
|
+
entry = {
|
|
89
|
+
type: "-",
|
|
90
|
+
executable: localDirEntry.executable,
|
|
91
|
+
name: localDirEntry.name,
|
|
92
|
+
hash,
|
|
93
|
+
};
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "d": {
|
|
97
|
+
const { hash, contents: subdirectoryContents } =
|
|
98
|
+
await hashLocalDirectory(localDirEntry.localPath);
|
|
99
|
+
entry = {
|
|
100
|
+
type: "d",
|
|
101
|
+
name: localDirEntry.name,
|
|
102
|
+
hash,
|
|
103
|
+
contents: subdirectoryContents,
|
|
104
|
+
};
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "l": {
|
|
108
|
+
entry = {
|
|
109
|
+
type: "l",
|
|
110
|
+
target: localDirEntry.target,
|
|
111
|
+
name: localDirEntry.name,
|
|
112
|
+
};
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
unreachable(localDirEntry);
|
|
117
|
+
}
|
|
118
|
+
directoryContents.push(entry);
|
|
119
|
+
}
|
|
120
|
+
const hash = await hashDirectoryContents(directoryContents);
|
|
121
|
+
return { contents: directoryContents, hash };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function doUploadLocalDirectory(
|
|
125
|
+
rpcClient: StdISocketRPCClient,
|
|
126
|
+
localDirectoryPath: string,
|
|
127
|
+
): Promise<string> {
|
|
128
|
+
const directoryContents: DirectoryEntry[] = [];
|
|
129
|
+
const localDirListing = listLocalDirectory(localDirectoryPath);
|
|
130
|
+
for await (const localDirEntry of localDirListing) {
|
|
131
|
+
let entry: DirectoryEntry;
|
|
132
|
+
if (localDirEntry.type === "-") {
|
|
133
|
+
const putFileResponse = await rpcClient.call.v1.dbfs.fileContents.put({
|
|
134
|
+
contents: localDirEntry.contents.toString("base64"),
|
|
135
|
+
});
|
|
136
|
+
entry = {
|
|
137
|
+
type: "-",
|
|
138
|
+
hash: putFileResponse.data.hash,
|
|
139
|
+
executable: localDirEntry.executable,
|
|
140
|
+
name: localDirEntry.name,
|
|
141
|
+
};
|
|
142
|
+
} else if (localDirEntry.type === "d") {
|
|
143
|
+
const childHash = await doUploadLocalDirectory(
|
|
144
|
+
rpcClient,
|
|
145
|
+
localDirEntry.localPath,
|
|
146
|
+
);
|
|
147
|
+
entry = { type: "d", name: localDirEntry.name, hash: childHash };
|
|
148
|
+
} else if (localDirEntry.type === "l") {
|
|
149
|
+
entry = {
|
|
150
|
+
type: "l",
|
|
151
|
+
target: localDirEntry.target,
|
|
152
|
+
name: localDirEntry.name,
|
|
153
|
+
};
|
|
154
|
+
} else {
|
|
155
|
+
unreachable(localDirEntry);
|
|
156
|
+
}
|
|
157
|
+
directoryContents.push(entry);
|
|
158
|
+
}
|
|
159
|
+
const putDirResponse = await rpcClient.call.v1.dbfs.directoryContents.put({
|
|
160
|
+
contents: directoryContents,
|
|
161
|
+
});
|
|
162
|
+
return putDirResponse.data.hash;
|
|
163
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import watcher, { type FSWatcher } from "chokidar";
|
|
3
|
+
import { red } from "colorette";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import semver from "semver";
|
|
6
|
+
import { build } from "vite";
|
|
7
|
+
import { getLegacyComponentsConfig } from "./custom-config.mjs";
|
|
8
|
+
import type { Plugin, UserConfig, ViteDevServer } from "vite";
|
|
9
|
+
|
|
10
|
+
// we only need to build if not on React 18
|
|
11
|
+
export async function isCustomComponentsEnabled() {
|
|
12
|
+
try {
|
|
13
|
+
const packageJson = await fs.readJson(
|
|
14
|
+
path.join(process.cwd(), "custom", "package.json"),
|
|
15
|
+
);
|
|
16
|
+
const reactVersion =
|
|
17
|
+
packageJson.dependencies?.["react"] ||
|
|
18
|
+
packageJson.devDependencies?.["react"];
|
|
19
|
+
if (!reactVersion) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return semver.lt(semver.coerce(reactVersion)!, "18.0.0");
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function buildCustomComponents(config: UserConfig) {
|
|
29
|
+
try {
|
|
30
|
+
console.log("Building custom components...");
|
|
31
|
+
await build(config);
|
|
32
|
+
console.log("Custom components build complete");
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(red("Custom components build failed:"), "\n", error);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let __watcher: FSWatcher | undefined;
|
|
41
|
+
|
|
42
|
+
async function watchCustomComponents(folder: string) {
|
|
43
|
+
if (__watcher) {
|
|
44
|
+
return __watcher;
|
|
45
|
+
}
|
|
46
|
+
__watcher = watcher.watch(folder, {
|
|
47
|
+
ignored: [/dist/, /node_modules/],
|
|
48
|
+
persistent: true,
|
|
49
|
+
ignoreInitial: true,
|
|
50
|
+
});
|
|
51
|
+
return __watcher;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function buildCustomComponentsProject() {
|
|
55
|
+
const customComponentsFolder = path.join(process.cwd(), "custom");
|
|
56
|
+
const config = getLegacyComponentsConfig(customComponentsFolder);
|
|
57
|
+
await buildCustomComponents(config);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// on file changes, we want to rebuild the custom components library
|
|
61
|
+
// and let our vite server process that the file as changed, which triggers HMR
|
|
62
|
+
// Related reading: https://vite.dev/changes/hotupdate-hook.html
|
|
63
|
+
// TODO: can we run a vite dev server instead of a vite build in order to use hotUpdate to get changes?
|
|
64
|
+
async function watchForChanges(server: ViteDevServer) {
|
|
65
|
+
const customComponentsFolder = path.join(process.cwd(), "custom");
|
|
66
|
+
const watcher = await watchCustomComponents(customComponentsFolder);
|
|
67
|
+
|
|
68
|
+
const handleChange = async (filePath: string) => {
|
|
69
|
+
// We need to rebuild the configuration to pick up on any file system changes
|
|
70
|
+
const config = getLegacyComponentsConfig(customComponentsFolder);
|
|
71
|
+
|
|
72
|
+
console.log(`Custom component file changed: ${filePath}`);
|
|
73
|
+
const url = path.relative(customComponentsFolder, filePath);
|
|
74
|
+
await buildCustomComponents(config);
|
|
75
|
+
|
|
76
|
+
const module = await server.moduleGraph.getModuleByUrl(
|
|
77
|
+
`/custom/dist/${url}`.replace(/\.tsx?$/, ".js"),
|
|
78
|
+
);
|
|
79
|
+
if (module) {
|
|
80
|
+
server.reloadModule(module);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
watcher.on("change", handleChange);
|
|
85
|
+
watcher.on("add", handleChange);
|
|
86
|
+
watcher.on("unlink", handleChange);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const customComponentsPlugin = (): Plugin => {
|
|
90
|
+
return {
|
|
91
|
+
enforce: "pre",
|
|
92
|
+
name: "custom-components",
|
|
93
|
+
// vite is calling this twice during the initial dev server start due to how remix loads
|
|
94
|
+
// configs via resolveConfig
|
|
95
|
+
async buildStart() {
|
|
96
|
+
const enabled = await isCustomComponentsEnabled();
|
|
97
|
+
if (!enabled) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await buildCustomComponentsProject();
|
|
101
|
+
},
|
|
102
|
+
async configureServer(server) {
|
|
103
|
+
const enabled = await isCustomComponentsEnabled();
|
|
104
|
+
if (!enabled) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await watchForChanges(server);
|
|
108
|
+
},
|
|
109
|
+
buildEnd() {
|
|
110
|
+
__watcher?.close();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
};
|