@superblocksteam/sdk 2.0.119-next.0 → 2.0.119
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/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/dev-s3-restore.test.mjs +0 -116
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +0 -1
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
- package/dist/cli-replacement/dev.d.mts +0 -6
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +6 -77
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/client.d.ts +6 -67
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -94
- package/dist/client.js.map +1 -1
- package/dist/dev-utils/dev-server-persist.test.mjs +17 -117
- package/dist/dev-utils/dev-server-persist.test.mjs.map +1 -1
- package/dist/dev-utils/dev-server.d.mts +1 -19
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +28 -296
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/flag.d.ts +1 -1
- package/dist/flag.d.ts.map +1 -1
- package/dist/flag.js +3 -3
- package/dist/flag.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +1 -8
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +1 -24
- package/dist/sdk.js.map +1 -1
- package/dist/sdk.test.js +1 -102
- package/dist/sdk.test.js.map +1 -1
- package/dist/telemetry/index.d.ts +1 -2
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/index.js +1 -8
- package/dist/telemetry/index.js.map +1 -1
- package/dist/telemetry/logging.d.ts +2 -1
- package/dist/telemetry/logging.d.ts.map +1 -1
- package/dist/telemetry/logging.js +1 -1
- package/dist/telemetry/logging.js.map +1 -1
- package/dist/types/common.d.ts +1 -1
- package/dist/types/common.d.ts.map +1 -1
- package/dist/version-control.d.mts.map +1 -1
- package/dist/version-control.mjs +19 -14
- package/dist/version-control.mjs.map +1 -1
- package/eslint.config.js +0 -6
- package/package.json +8 -8
- package/src/cli-replacement/dev-s3-restore.test.mts +0 -156
- package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +0 -1
- package/src/cli-replacement/dev.mts +14 -104
- package/src/client.ts +6 -189
- package/src/dev-utils/dev-server-persist.test.mts +19 -170
- package/src/dev-utils/dev-server.mts +32 -366
- package/src/flag.ts +4 -4
- package/src/index.ts +1 -19
- package/src/sdk.test.ts +6 -145
- package/src/sdk.ts +0 -36
- package/src/telemetry/index.ts +1 -9
- package/src/telemetry/logging.ts +2 -2
- package/src/types/common.ts +1 -1
- package/src/version-control.mts +30 -11
- package/test/version-control.test.mts +2 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/turbo.json +0 -1
- package/dist/dev-utils/dedupe-async.d.ts +0 -16
- package/dist/dev-utils/dedupe-async.d.ts.map +0 -1
- package/dist/dev-utils/dedupe-async.js +0 -27
- package/dist/dev-utils/dedupe-async.js.map +0 -1
- package/dist/dev-utils/dedupe-async.test.d.ts +0 -2
- package/dist/dev-utils/dedupe-async.test.d.ts.map +0 -1
- package/dist/dev-utils/dedupe-async.test.js +0 -120
- package/dist/dev-utils/dedupe-async.test.js.map +0 -1
- package/dist/dev-utils/dev-server-metrics.d.mts +0 -95
- package/dist/dev-utils/dev-server-metrics.d.mts.map +0 -1
- package/dist/dev-utils/dev-server-metrics.mjs +0 -193
- package/dist/dev-utils/dev-server-metrics.mjs.map +0 -1
- package/dist/telemetry/memory-metrics.d.ts +0 -3
- package/dist/telemetry/memory-metrics.d.ts.map +0 -1
- package/dist/telemetry/memory-metrics.js +0 -59
- package/dist/telemetry/memory-metrics.js.map +0 -1
- package/src/dev-utils/dedupe-async.test.ts +0 -151
- package/src/dev-utils/dedupe-async.ts +0 -45
- package/src/dev-utils/dev-server-metrics.mts +0 -252
- package/src/telemetry/memory-metrics.ts +0 -90
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
AiServiceFeatureFlags,
|
|
27
27
|
SnapshotManager,
|
|
28
28
|
isSdkApiTemplate,
|
|
29
|
-
stripResolvedFromLockfile,
|
|
30
29
|
} from "@superblocksteam/vite-plugin-file-sync/ai-service";
|
|
31
30
|
import type { DraftInterface } from "@superblocksteam/vite-plugin-file-sync/draft-interface";
|
|
32
31
|
import { createGitService } from "@superblocksteam/vite-plugin-file-sync/git-service";
|
|
@@ -296,7 +295,7 @@ async function installPackages(cwd: string, logger: Logger) {
|
|
|
296
295
|
logger.info("Package installation completed successfully");
|
|
297
296
|
logger.info(stdout);
|
|
298
297
|
} catch (error) {
|
|
299
|
-
logger.error(
|
|
298
|
+
logger.error(`Error during package installation: ${error}`);
|
|
300
299
|
throw error;
|
|
301
300
|
}
|
|
302
301
|
}
|
|
@@ -352,13 +351,6 @@ export async function dev(options: {
|
|
|
352
351
|
/** Pre-existing HTTP server from warm standby mode (avoids port gap on transition). */
|
|
353
352
|
existingServer?: HttpServer;
|
|
354
353
|
|
|
355
|
-
/**
|
|
356
|
-
* Wall-clock timestamp (Date.now()) when the warm-standby /_sb_activate
|
|
357
|
-
* POST was accepted. Forwarded to createDevServer so the warm-activation
|
|
358
|
-
* handoff histogram can be recorded.
|
|
359
|
-
*/
|
|
360
|
-
warmActivationStart?: number;
|
|
361
|
-
|
|
362
354
|
/** Force package installation when the caller has detected dependency drift. */
|
|
363
355
|
forcePackageInstall?: boolean;
|
|
364
356
|
|
|
@@ -391,26 +383,8 @@ export async function dev(options: {
|
|
|
391
383
|
let snapshotManager: SnapshotManager | undefined;
|
|
392
384
|
let gitUserName: string | undefined;
|
|
393
385
|
let gitUserEmail: string | undefined;
|
|
394
|
-
// In-flight install handle. We launch the install while the upload/restart
|
|
395
|
-
// decisions are still being evaluated and join at every gate that depends
|
|
396
|
-
// on post-install state. See the install block and `joinPackageInstall`.
|
|
397
|
-
let packageInstallPromise: Promise<void> | undefined;
|
|
398
386
|
const tracer = getTracer();
|
|
399
387
|
const logger = getLogger(options.logger);
|
|
400
|
-
// Joins the in-flight install at any step that depends on a settled
|
|
401
|
-
// node_modules / lockfile state. Clears the handle so later joins are
|
|
402
|
-
// no-ops. The rejection (if any) propagates so the caller's step can
|
|
403
|
-
// abort cleanly. Defined at outer scope so the post-sync `createDevServer`
|
|
404
|
-
// gate and the abort handler can call it too.
|
|
405
|
-
const joinPackageInstall = async (reason: string): Promise<void> => {
|
|
406
|
-
if (!packageInstallPromise) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
logger.info(`Waiting for background package install (${reason})…`);
|
|
410
|
-
const promise = packageInstallPromise;
|
|
411
|
-
packageInstallPromise = undefined;
|
|
412
|
-
await promise;
|
|
413
|
-
};
|
|
414
388
|
const skipAutoUpgrade = autoUpgradeMode === DevServerAutoUpgradeMode.SKIP;
|
|
415
389
|
const skipCliUpgrade =
|
|
416
390
|
skipAutoUpgrade ||
|
|
@@ -830,16 +804,6 @@ export async function dev(options: {
|
|
|
830
804
|
logger.info("[dev-startup] Skipping download, already in sync");
|
|
831
805
|
}
|
|
832
806
|
|
|
833
|
-
// Unconditional lockfile sanitation: strip cross-registry
|
|
834
|
-
// `resolved` URLs from any lockfile on disk regardless of whether
|
|
835
|
-
// install runs next. The lockfile here is whatever survived the
|
|
836
|
-
// DBFS path (downloadFirst overwrite, prior boot, brownfield
|
|
837
|
-
// import); npm honors `resolved` verbatim and would bypass the
|
|
838
|
-
// active registry. `integrity` is preserved, so genuine
|
|
839
|
-
// cross-registry tarball drift surfaces as EINTEGRITY. No-op when
|
|
840
|
-
// there's no lockfile or no `resolved` entries.
|
|
841
|
-
await stripResolvedFromLockfile(cwd);
|
|
842
|
-
|
|
843
807
|
let hasCliUpdated = false;
|
|
844
808
|
let upgradePromises: Promise<void>[] = [];
|
|
845
809
|
const forceUpgrade =
|
|
@@ -953,42 +917,17 @@ export async function dev(options: {
|
|
|
953
917
|
upgradePromises.length > 0 ||
|
|
954
918
|
forcePackageInstall
|
|
955
919
|
) {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
// Upgrade global CLI and local packages in parallel - improves performance
|
|
968
|
-
await Promise.all([
|
|
969
|
-
...upgradePromises,
|
|
970
|
-
installPackages(cwd, logger),
|
|
971
|
-
]);
|
|
972
|
-
} finally {
|
|
973
|
-
span.end();
|
|
974
|
-
}
|
|
975
|
-
},
|
|
976
|
-
);
|
|
977
|
-
// Backstop: assigning `.catch` to a separate (discarded) promise
|
|
978
|
-
// keeps `packageInstallPromise` itself rejecting, so the joins
|
|
979
|
-
// can still observe and abort. Without this, an install that
|
|
980
|
-
// fails before any join fires becomes an unhandled rejection.
|
|
981
|
-
const installApplicationId = applicationConfig.id;
|
|
982
|
-
const installUpgradeCount = upgradePromises.length;
|
|
983
|
-
packageInstallPromise.catch((err) => {
|
|
984
|
-
logger.error(
|
|
985
|
-
// errorId is encoded into the message body because the
|
|
986
|
-
// logger.error contract limits structured attributes to
|
|
987
|
-
// `{ error: { kind, message, stack } }`. The id stays
|
|
988
|
-
// grep-able for Datadog/Sentry alert rules.
|
|
989
|
-
`Background package install failed [errorId=DEV_SERVER_BG_INSTALL_FAILED applicationId=${installApplicationId} cwd=${cwd} upgradePromiseCount=${installUpgradeCount}]`,
|
|
990
|
-
getErrorMeta(err),
|
|
991
|
-
);
|
|
920
|
+
logger.info("Installing packages…");
|
|
921
|
+
await tracer.startActiveSpan("installPackages", async (span) => {
|
|
922
|
+
try {
|
|
923
|
+
// Upgrade global CLI and local packages in parallel - improves performance
|
|
924
|
+
await Promise.all([
|
|
925
|
+
...upgradePromises,
|
|
926
|
+
installPackages(cwd, logger),
|
|
927
|
+
]);
|
|
928
|
+
} finally {
|
|
929
|
+
span.end();
|
|
930
|
+
}
|
|
992
931
|
});
|
|
993
932
|
} else {
|
|
994
933
|
logger.info(
|
|
@@ -999,9 +938,6 @@ export async function dev(options: {
|
|
|
999
938
|
const shouldUploadPackageState =
|
|
1000
939
|
hasPackageChanged || forcePackageInstall;
|
|
1001
940
|
if (shouldUploadPackageState || uploadFirst) {
|
|
1002
|
-
// Upload serializes the post-install lockfile + node_modules
|
|
1003
|
-
// tree to DBFS, so it must observe a quiesced install.
|
|
1004
|
-
await joinPackageInstall("before upload");
|
|
1005
941
|
logger.info(
|
|
1006
942
|
`Uploading local files to branch '${activeDbfsBranchName}' on server before starting`,
|
|
1007
943
|
);
|
|
@@ -1016,9 +952,6 @@ export async function dev(options: {
|
|
|
1016
952
|
}
|
|
1017
953
|
|
|
1018
954
|
if (hasCliUpdated) {
|
|
1019
|
-
// Exiting mid-install would leave a half-written lockfile that
|
|
1020
|
-
// the next boot would have to recover from.
|
|
1021
|
-
await joinPackageInstall("before CLI restart");
|
|
1022
955
|
try {
|
|
1023
956
|
logger.info("Releasing lock before restarting the dev server");
|
|
1024
957
|
await aiService?.removeIntegrationCache();
|
|
@@ -1034,9 +967,9 @@ export async function dev(options: {
|
|
|
1034
967
|
}
|
|
1035
968
|
});
|
|
1036
969
|
} catch (error: any) {
|
|
970
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1037
971
|
logger.error(
|
|
1038
|
-
|
|
1039
|
-
getErrorMeta(error),
|
|
972
|
+
`[dev-server] Startup failed during sync/lock/setup (exiting with code 1): ${msg}`,
|
|
1040
973
|
);
|
|
1041
974
|
try {
|
|
1042
975
|
await aiService?.removeIntegrationCache();
|
|
@@ -1050,15 +983,6 @@ export async function dev(options: {
|
|
|
1050
983
|
logger.info("Skipping directory sync");
|
|
1051
984
|
}
|
|
1052
985
|
|
|
1053
|
-
// Drain the install before Vite starts. Vite's createServer eagerly
|
|
1054
|
-
// scans `node_modules` for `optimizeDeps` pre-bundling; if that scan
|
|
1055
|
-
// observes a mid-install state (npm moves per-package directories via
|
|
1056
|
-
// rename, so a reader can race the rename), the resulting `.vite/deps`
|
|
1057
|
-
// cache embeds partial state that survives across reloads. Awaiting
|
|
1058
|
-
// here costs at most the install's remaining wall time — the upload
|
|
1059
|
-
// and CLI-restart joins above usually drain it first.
|
|
1060
|
-
await joinPackageInstall("before Vite startup");
|
|
1061
|
-
|
|
1062
986
|
const activateRuntimeGitService = async (): Promise<
|
|
1063
987
|
GitService | undefined
|
|
1064
988
|
> => {
|
|
@@ -1174,7 +1098,6 @@ export async function dev(options: {
|
|
|
1174
1098
|
sdk,
|
|
1175
1099
|
superblocksBaseUrl: tokenConfig.superblocksBaseUrl,
|
|
1176
1100
|
existingServer: options.existingServer,
|
|
1177
|
-
warmActivationStart: options.warmActivationStart,
|
|
1178
1101
|
// TODO: Remove this cast — build the options object to match
|
|
1179
1102
|
// CreateDevServerOptions directly so new required fields cause a
|
|
1180
1103
|
// compile error instead of silently passing undefined.
|
|
@@ -1194,19 +1117,6 @@ export async function dev(options: {
|
|
|
1194
1117
|
logger.warn(`Error stopping auth hot-reload server: ${error}`);
|
|
1195
1118
|
});
|
|
1196
1119
|
}
|
|
1197
|
-
// Drain any straggler install before tear-down. By this point the
|
|
1198
|
-
// pre-Vite join has already run on the success path, so usually
|
|
1199
|
-
// `packageInstallPromise` is undefined and this is a no-op; if abort
|
|
1200
|
-
// races createDevServer (or fires during the inner sync block on
|
|
1201
|
-
// some error path), draining here keeps the spawned npm child from
|
|
1202
|
-
// being SIGKILL'd mid-rename. Errors only get logged — we are tearing
|
|
1203
|
-
// down anyway.
|
|
1204
|
-
joinPackageInstall("during abort").catch((error: unknown) => {
|
|
1205
|
-
logger.warn(
|
|
1206
|
-
"Error draining background install during abort",
|
|
1207
|
-
getErrorMeta(error),
|
|
1208
|
-
);
|
|
1209
|
-
});
|
|
1210
1120
|
// Clean up AI service cache (must happen before app switches directories)
|
|
1211
1121
|
aiService?.removeIntegrationCache().catch((error: any) => {
|
|
1212
1122
|
logger.warn(`Error removing integration cache: ${error}`);
|
package/src/client.ts
CHANGED
|
@@ -2796,71 +2796,21 @@ export interface UsageRecordRow {
|
|
|
2796
2796
|
source: string;
|
|
2797
2797
|
}
|
|
2798
2798
|
|
|
2799
|
-
export interface UserCreditLimit {
|
|
2800
|
-
amountLimitCents: number | null;
|
|
2801
|
-
creditLimit: number | null;
|
|
2802
|
-
id: string;
|
|
2803
|
-
orgId: string;
|
|
2804
|
-
updatedAt: string;
|
|
2805
|
-
updatedBy: string | null;
|
|
2806
|
-
userId: string | null;
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
export type OrgCreditLimit = Omit<UserCreditLimit, "userId">;
|
|
2810
|
-
|
|
2811
|
-
export interface BillingUsageLimitsResponse {
|
|
2812
|
-
limits: UserCreditLimit[];
|
|
2813
|
-
orgLimit: OrgCreditLimit | null;
|
|
2814
|
-
}
|
|
2815
|
-
|
|
2816
|
-
type BillingUsageLimitValue =
|
|
2817
|
-
| {
|
|
2818
|
-
amountLimitCents: number;
|
|
2819
|
-
creditLimit?: never;
|
|
2820
|
-
}
|
|
2821
|
-
| {
|
|
2822
|
-
amountLimitCents?: never;
|
|
2823
|
-
creditLimit: number;
|
|
2824
|
-
};
|
|
2825
|
-
|
|
2826
|
-
export type UpdateUserBillingUsageLimitRequest = BillingUsageLimitValue & {
|
|
2827
|
-
userId: string | null;
|
|
2828
|
-
};
|
|
2829
|
-
|
|
2830
|
-
export type UpdateOrgBillingUsageLimitRequest = BillingUsageLimitValue;
|
|
2831
|
-
|
|
2832
2799
|
export interface PlanSummary {
|
|
2833
2800
|
billingInterval: "annual" | "monthly";
|
|
2834
|
-
builderSeatsAssigned?: number;
|
|
2835
|
-
builderSeatsTotal?: number;
|
|
2836
|
-
contractEnd?: string | null;
|
|
2837
|
-
contractStart?: string | null;
|
|
2838
|
-
contractType?: string | null;
|
|
2839
2801
|
currentPlanCredits: number;
|
|
2840
2802
|
currentUsageCredits: number;
|
|
2841
2803
|
cycleStart: string | null;
|
|
2842
2804
|
cycleEnd: string | null;
|
|
2843
2805
|
creditsPerSeat: number | null;
|
|
2844
|
-
deployedAppsAdditional: number;
|
|
2845
|
-
deployedAppPriceAnnual: number | null;
|
|
2846
|
-
deployedAppPriceMonthly: number | null;
|
|
2847
|
-
deployedAppsIncluded: number;
|
|
2848
|
-
deployedAppsLimit: number;
|
|
2849
|
-
deployedAppsUsed: number;
|
|
2850
|
-
deployedAppCostCents?: number | null;
|
|
2851
|
-
dollarCommitAmountCents?: number | null;
|
|
2852
|
-
dollarCommitUsedCents?: number | null;
|
|
2853
|
-
overageAppsEnabled?: boolean;
|
|
2854
|
-
overageCreditsEnabled?: boolean;
|
|
2855
|
-
overageSeatsEnabled?: boolean;
|
|
2856
2806
|
pricePerSeatAnnual: number | null;
|
|
2857
2807
|
pricePerSeatMonthly: number | null;
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2808
|
+
deployedAppsUsed: number;
|
|
2809
|
+
deployedAppsLimit: number;
|
|
2810
|
+
deployedAppsIncluded: number;
|
|
2811
|
+
deployedAppsAdditional: number;
|
|
2812
|
+
deployedAppPriceMonthly: number | null;
|
|
2813
|
+
deployedAppPriceAnnual: number | null;
|
|
2864
2814
|
}
|
|
2865
2815
|
|
|
2866
2816
|
export async function fetchBillingUsageDaily({
|
|
@@ -2995,139 +2945,6 @@ export async function fetchBillingPlanSummary({
|
|
|
2995
2945
|
}
|
|
2996
2946
|
}
|
|
2997
2947
|
|
|
2998
|
-
export async function fetchBillingUsageLimits({
|
|
2999
|
-
cliVersion,
|
|
3000
|
-
token,
|
|
3001
|
-
superblocksBaseUrl,
|
|
3002
|
-
}: {
|
|
3003
|
-
cliVersion: string;
|
|
3004
|
-
token: string;
|
|
3005
|
-
superblocksBaseUrl: string;
|
|
3006
|
-
}): Promise<BillingUsageLimitsResponse> {
|
|
3007
|
-
try {
|
|
3008
|
-
const url = new URL(
|
|
3009
|
-
`${BASE_SERVER_API_URL_V1}/billing/user-credit-limits`,
|
|
3010
|
-
superblocksBaseUrl,
|
|
3011
|
-
);
|
|
3012
|
-
|
|
3013
|
-
const config: AxiosRequestConfig = {
|
|
3014
|
-
method: "get",
|
|
3015
|
-
url: url.toString(),
|
|
3016
|
-
headers: {
|
|
3017
|
-
Authorization: "Bearer " + token,
|
|
3018
|
-
[CLI_VERSION_HEADER]: cliVersion,
|
|
3019
|
-
},
|
|
3020
|
-
};
|
|
3021
|
-
const response = await axios(config);
|
|
3022
|
-
return response.data.data as BillingUsageLimitsResponse;
|
|
3023
|
-
} catch (e: any) {
|
|
3024
|
-
let message: string;
|
|
3025
|
-
if (e instanceof AxiosError) {
|
|
3026
|
-
message =
|
|
3027
|
-
(e.response?.data?.responseMeta?.message as string) ??
|
|
3028
|
-
JSON.stringify(e.response?.data) ??
|
|
3029
|
-
e.response?.statusText ??
|
|
3030
|
-
e?.message;
|
|
3031
|
-
} else {
|
|
3032
|
-
message = `${e?.message ? e?.message : e}`;
|
|
3033
|
-
}
|
|
3034
|
-
throw new Error(`Could not fetch billing usage limits: ${message}`);
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3037
|
-
|
|
3038
|
-
export async function updateUserBillingUsageLimit({
|
|
3039
|
-
amountLimitCents,
|
|
3040
|
-
cliVersion,
|
|
3041
|
-
creditLimit,
|
|
3042
|
-
token,
|
|
3043
|
-
superblocksBaseUrl,
|
|
3044
|
-
userId,
|
|
3045
|
-
}: UpdateUserBillingUsageLimitRequest & {
|
|
3046
|
-
cliVersion: string;
|
|
3047
|
-
token: string;
|
|
3048
|
-
superblocksBaseUrl: string;
|
|
3049
|
-
}): Promise<{ success: boolean }> {
|
|
3050
|
-
try {
|
|
3051
|
-
const url = new URL(
|
|
3052
|
-
`${BASE_SERVER_API_URL_V1}/billing/user-credit-limits`,
|
|
3053
|
-
superblocksBaseUrl,
|
|
3054
|
-
);
|
|
3055
|
-
|
|
3056
|
-
const config: AxiosRequestConfig = {
|
|
3057
|
-
method: "put",
|
|
3058
|
-
url: url.toString(),
|
|
3059
|
-
headers: {
|
|
3060
|
-
Authorization: "Bearer " + token,
|
|
3061
|
-
[CLI_VERSION_HEADER]: cliVersion,
|
|
3062
|
-
},
|
|
3063
|
-
data: {
|
|
3064
|
-
userId,
|
|
3065
|
-
...(amountLimitCents !== undefined
|
|
3066
|
-
? { amountLimitCents }
|
|
3067
|
-
: { creditLimit }),
|
|
3068
|
-
},
|
|
3069
|
-
};
|
|
3070
|
-
const response = await axios(config);
|
|
3071
|
-
return response.data.data as { success: boolean };
|
|
3072
|
-
} catch (e: any) {
|
|
3073
|
-
let message: string;
|
|
3074
|
-
if (e instanceof AxiosError) {
|
|
3075
|
-
message =
|
|
3076
|
-
(e.response?.data?.responseMeta?.message as string) ??
|
|
3077
|
-
JSON.stringify(e.response?.data) ??
|
|
3078
|
-
e.response?.statusText ??
|
|
3079
|
-
e?.message;
|
|
3080
|
-
} else {
|
|
3081
|
-
message = `${e?.message ? e?.message : e}`;
|
|
3082
|
-
}
|
|
3083
|
-
throw new Error(`Could not update user billing usage limit: ${message}`);
|
|
3084
|
-
}
|
|
3085
|
-
}
|
|
3086
|
-
|
|
3087
|
-
export async function updateOrgBillingUsageLimit({
|
|
3088
|
-
amountLimitCents,
|
|
3089
|
-
cliVersion,
|
|
3090
|
-
creditLimit,
|
|
3091
|
-
token,
|
|
3092
|
-
superblocksBaseUrl,
|
|
3093
|
-
}: UpdateOrgBillingUsageLimitRequest & {
|
|
3094
|
-
cliVersion: string;
|
|
3095
|
-
token: string;
|
|
3096
|
-
superblocksBaseUrl: string;
|
|
3097
|
-
}): Promise<{ success: boolean }> {
|
|
3098
|
-
try {
|
|
3099
|
-
const url = new URL(
|
|
3100
|
-
`${BASE_SERVER_API_URL_V1}/billing/user-credit-limits/org`,
|
|
3101
|
-
superblocksBaseUrl,
|
|
3102
|
-
);
|
|
3103
|
-
|
|
3104
|
-
const config: AxiosRequestConfig = {
|
|
3105
|
-
method: "put",
|
|
3106
|
-
url: url.toString(),
|
|
3107
|
-
headers: {
|
|
3108
|
-
Authorization: "Bearer " + token,
|
|
3109
|
-
[CLI_VERSION_HEADER]: cliVersion,
|
|
3110
|
-
},
|
|
3111
|
-
data:
|
|
3112
|
-
amountLimitCents !== undefined ? { amountLimitCents } : { creditLimit },
|
|
3113
|
-
};
|
|
3114
|
-
const response = await axios(config);
|
|
3115
|
-
return response.data.data as { success: boolean };
|
|
3116
|
-
} catch (e: any) {
|
|
3117
|
-
let message: string;
|
|
3118
|
-
if (e instanceof AxiosError) {
|
|
3119
|
-
message =
|
|
3120
|
-
(e.response?.data?.responseMeta?.message as string) ??
|
|
3121
|
-
JSON.stringify(e.response?.data) ??
|
|
3122
|
-
e.response?.statusText ??
|
|
3123
|
-
e?.message;
|
|
3124
|
-
} else {
|
|
3125
|
-
message = `${e?.message ? e?.message : e}`;
|
|
3126
|
-
}
|
|
3127
|
-
throw new Error(`Could not update org billing usage limit: ${message}`);
|
|
3128
|
-
}
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
2948
|
// ---------------------------------------------------------------------------
|
|
3132
2949
|
// Knowledge (Facts)
|
|
3133
2950
|
// ---------------------------------------------------------------------------
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import type * as NodeChildProcess from "node:child_process";
|
|
2
2
|
import { EventEmitter } from "node:events";
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
3
|
import { PassThrough } from "node:stream";
|
|
7
4
|
|
|
8
|
-
import {
|
|
5
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
6
|
|
|
10
7
|
const { spawnMock } = vi.hoisted(() => ({
|
|
11
8
|
spawnMock: vi.fn(),
|
|
@@ -25,14 +22,6 @@ vi.mock("node:child_process", async () => {
|
|
|
25
22
|
};
|
|
26
23
|
});
|
|
27
24
|
|
|
28
|
-
// Use a static import so the slow `dev-server.mjs` module graph (vite, react
|
|
29
|
-
// plugin, workspace deps) loads during file evaluation rather than inside each
|
|
30
|
-
// test, where the cold-import cost easily exceeds the default 5s test timeout.
|
|
31
|
-
import {
|
|
32
|
-
createWorkspacePersistArchive,
|
|
33
|
-
getSuperblocksTarExcludes,
|
|
34
|
-
} from "./dev-server.mjs";
|
|
35
|
-
|
|
36
25
|
function createMockProcess() {
|
|
37
26
|
return Object.assign(new EventEmitter(), {
|
|
38
27
|
stdin: new PassThrough(),
|
|
@@ -41,53 +30,8 @@ function createMockProcess() {
|
|
|
41
30
|
});
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
describe("getSuperblocksTarExcludes", () => {
|
|
45
|
-
let tempRoot: string;
|
|
46
|
-
|
|
47
|
-
beforeAll(() => {
|
|
48
|
-
tempRoot = mkdtempSync(join(tmpdir(), "persist-test-"));
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
afterAll(() => {
|
|
52
|
-
rmSync(tempRoot, { recursive: true, force: true });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("excludes .superblocks entirely when the directory does not exist", () => {
|
|
56
|
-
const result = getSuperblocksTarExcludes(tempRoot);
|
|
57
|
-
expect(result).toEqual(["--exclude", ".superblocks"]);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it("excludes subdirs not in the safelist and keeps context/drafts", () => {
|
|
61
|
-
const sb = join(tempRoot, ".superblocks");
|
|
62
|
-
mkdirSync(sb);
|
|
63
|
-
mkdirSync(join(sb, "context"));
|
|
64
|
-
mkdirSync(join(sb, "drafts"));
|
|
65
|
-
mkdirSync(join(sb, "recordings"));
|
|
66
|
-
mkdirSync(join(sb, "scratch"));
|
|
67
|
-
mkdirSync(join(sb, "system-skills"));
|
|
68
|
-
|
|
69
|
-
const result = getSuperblocksTarExcludes(tempRoot);
|
|
70
|
-
|
|
71
|
-
// Safelisted dirs must NOT appear
|
|
72
|
-
expect(result).not.toContain(".superblocks/context");
|
|
73
|
-
expect(result).not.toContain(".superblocks/drafts");
|
|
74
|
-
|
|
75
|
-
// Non-safelisted dirs must be excluded
|
|
76
|
-
expect(result).toContain(".superblocks/recordings");
|
|
77
|
-
expect(result).toContain(".superblocks/scratch");
|
|
78
|
-
expect(result).toContain(".superblocks/system-skills");
|
|
79
|
-
|
|
80
|
-
// Each excluded dir has a preceding --exclude flag
|
|
81
|
-
for (const name of ["recordings", "scratch", "system-skills"]) {
|
|
82
|
-
const idx = result.indexOf(`.superblocks/${name}`);
|
|
83
|
-
expect(idx).toBeGreaterThan(0);
|
|
84
|
-
expect(result[idx - 1]).toBe("--exclude");
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
33
|
describe("createWorkspacePersistArchive", () => {
|
|
90
|
-
it("
|
|
34
|
+
it("excludes node_modules from the tar archive", async () => {
|
|
91
35
|
const tarProc = createMockProcess();
|
|
92
36
|
const zstdProc = createMockProcess();
|
|
93
37
|
spawnMock.mockImplementation((command: string) => {
|
|
@@ -103,17 +47,27 @@ describe("createWorkspacePersistArchive", () => {
|
|
|
103
47
|
return zstdProc;
|
|
104
48
|
});
|
|
105
49
|
|
|
50
|
+
const { createWorkspacePersistArchive } = await import("./dev-server.mjs");
|
|
51
|
+
|
|
106
52
|
await expect(createWorkspacePersistArchive("/workspace")).resolves.toEqual(
|
|
107
53
|
Buffer.from("archive"),
|
|
108
54
|
);
|
|
109
55
|
|
|
110
56
|
const tarCall = spawnMock.mock.calls.find(([command]) => command === "tar");
|
|
111
57
|
expect(tarCall).toBeDefined();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
58
|
+
expect(tarCall?.[1]).toEqual([
|
|
59
|
+
"cf",
|
|
60
|
+
"-",
|
|
61
|
+
"--exclude",
|
|
62
|
+
".git",
|
|
63
|
+
"--exclude",
|
|
64
|
+
"node_modules",
|
|
65
|
+
"--exclude",
|
|
66
|
+
".superblocks",
|
|
67
|
+
"-C",
|
|
68
|
+
"/workspace",
|
|
69
|
+
".",
|
|
70
|
+
]);
|
|
117
71
|
});
|
|
118
72
|
|
|
119
73
|
it("rejects if tar fails after zstd closes successfully", async () => {
|
|
@@ -133,115 +87,10 @@ describe("createWorkspacePersistArchive", () => {
|
|
|
133
87
|
return zstdProc;
|
|
134
88
|
});
|
|
135
89
|
|
|
90
|
+
const { createWorkspacePersistArchive } = await import("./dev-server.mjs");
|
|
91
|
+
|
|
136
92
|
await expect(
|
|
137
93
|
createWorkspacePersistArchive("/workspace"),
|
|
138
94
|
).rejects.toThrowError(/^tar archival failed \(code 2\): tar failed$/);
|
|
139
95
|
});
|
|
140
96
|
});
|
|
141
|
-
|
|
142
|
-
describe("createWorkspacePersistUploadHeaders", () => {
|
|
143
|
-
it("forwards presigned upload headers and adds the archive content length", async () => {
|
|
144
|
-
const { createWorkspacePersistUploadHeaders } =
|
|
145
|
-
await import("./dev-server.mjs");
|
|
146
|
-
|
|
147
|
-
expect(
|
|
148
|
-
createWorkspacePersistUploadHeaders(123, {
|
|
149
|
-
"Content-Type": "application/custom-zstd",
|
|
150
|
-
"x-amz-server-side-encryption": "AES256",
|
|
151
|
-
}),
|
|
152
|
-
).toEqual({
|
|
153
|
-
"content-length": "123",
|
|
154
|
-
"content-type": "application/custom-zstd",
|
|
155
|
-
"x-amz-server-side-encryption": "AES256",
|
|
156
|
-
});
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("normalizes presigned upload header names case-insensitively", async () => {
|
|
160
|
-
const { createWorkspacePersistUploadHeaders } =
|
|
161
|
-
await import("./dev-server.mjs");
|
|
162
|
-
|
|
163
|
-
expect(
|
|
164
|
-
createWorkspacePersistUploadHeaders(789, {
|
|
165
|
-
"content-type": "application/custom-zstd",
|
|
166
|
-
"content-length": "1",
|
|
167
|
-
"x-amz-server-side-encryption": "AES256",
|
|
168
|
-
}),
|
|
169
|
-
).toEqual({
|
|
170
|
-
"content-length": "789",
|
|
171
|
-
"content-type": "application/custom-zstd",
|
|
172
|
-
"x-amz-server-side-encryption": "AES256",
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("keeps the legacy content headers when SABS sends only a URL", async () => {
|
|
177
|
-
const { createWorkspacePersistUploadHeaders } =
|
|
178
|
-
await import("./dev-server.mjs");
|
|
179
|
-
|
|
180
|
-
expect(createWorkspacePersistUploadHeaders(456)).toEqual({
|
|
181
|
-
"content-length": "456",
|
|
182
|
-
"content-type": "application/zstd",
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe("isWorkspacePersistUploadHeaders", () => {
|
|
188
|
-
it("accepts undefined (legacy SABS payloads with no uploadHeaders)", async () => {
|
|
189
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
190
|
-
await import("./dev-server.mjs");
|
|
191
|
-
|
|
192
|
-
expect(isWorkspacePersistUploadHeaders(undefined)).toBe(true);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
it("accepts a valid string map", async () => {
|
|
196
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
197
|
-
await import("./dev-server.mjs");
|
|
198
|
-
|
|
199
|
-
expect(
|
|
200
|
-
isWorkspacePersistUploadHeaders({
|
|
201
|
-
"content-type": "application/zstd",
|
|
202
|
-
"x-amz-server-side-encryption": "AES256",
|
|
203
|
-
}),
|
|
204
|
-
).toBe(true);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it("rejects null, arrays, and non-string values", async () => {
|
|
208
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
209
|
-
await import("./dev-server.mjs");
|
|
210
|
-
|
|
211
|
-
expect(isWorkspacePersistUploadHeaders(null)).toBe(false);
|
|
212
|
-
expect(isWorkspacePersistUploadHeaders([])).toBe(false);
|
|
213
|
-
expect(isWorkspacePersistUploadHeaders({ "x-foo": 1 })).toBe(false);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("rejects case-insensitive duplicate keys", async () => {
|
|
217
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
218
|
-
await import("./dev-server.mjs");
|
|
219
|
-
|
|
220
|
-
expect(
|
|
221
|
-
isWorkspacePersistUploadHeaders({
|
|
222
|
-
"Content-Type": "application/zstd",
|
|
223
|
-
"content-type": "application/custom-zstd",
|
|
224
|
-
}),
|
|
225
|
-
).toBe(false);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("rejects header names with illegal characters", async () => {
|
|
229
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
230
|
-
await import("./dev-server.mjs");
|
|
231
|
-
|
|
232
|
-
// Spaces are not allowed in HTTP token names.
|
|
233
|
-
expect(isWorkspacePersistUploadHeaders({ "bad header": "value" })).toBe(
|
|
234
|
-
false,
|
|
235
|
-
);
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
it("rejects header values with illegal characters", async () => {
|
|
239
|
-
const { isWorkspacePersistUploadHeaders } =
|
|
240
|
-
await import("./dev-server.mjs");
|
|
241
|
-
|
|
242
|
-
// CR/LF in header values is not allowed (header injection guard).
|
|
243
|
-
expect(isWorkspacePersistUploadHeaders({ "x-foo": "bad\r\nvalue" })).toBe(
|
|
244
|
-
false,
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
});
|