@vibecodemax/cli 0.1.6 → 0.1.7
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/dist/cli.js +100 -86
- package/dist/storageS3.js +1 -73
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { checkS3Context, setupS3Storage
|
|
5
|
+
import { checkS3Context, setupS3Storage } from "./storageS3.js";
|
|
6
6
|
const SETUP_STATE_PATH = path.join(".vibecodemax", "setup-state.json");
|
|
7
|
+
const SETUP_CONFIG_PATH = path.join(".vibecodemax", "setup-config.json");
|
|
7
8
|
const MANAGEMENT_API_BASE = "https://api.supabase.com";
|
|
8
9
|
const DEFAULT_LOCALHOST_URL = "http://localhost:3000";
|
|
9
10
|
const STORAGE_PUBLIC_BUCKET = "public-assets";
|
|
10
11
|
const STORAGE_PRIVATE_BUCKET = "private-uploads";
|
|
11
12
|
const STORAGE_REQUIRED_FILE_SIZE_LIMIT = 10 * 1024 * 1024;
|
|
12
|
-
const STORAGE_HEALTHCHECK_PREFIX = "_vibecodemax/healthcheck/default";
|
|
13
13
|
const STORAGE_MIME_TYPES_BY_CATEGORY = {
|
|
14
14
|
images: ["image/jpeg", "image/png", "image/webp", "image/gif"],
|
|
15
15
|
documents: [
|
|
@@ -23,16 +23,6 @@ const STORAGE_MIME_TYPES_BY_CATEGORY = {
|
|
|
23
23
|
video: ["video/mp4", "video/webm", "video/quicktime"],
|
|
24
24
|
};
|
|
25
25
|
const STORAGE_DEFAULT_MIME_CATEGORIES = ["images"];
|
|
26
|
-
const STORAGE_REQUIRED_POLICY_NAMES = [
|
|
27
|
-
"public_assets_select_own",
|
|
28
|
-
"public_assets_insert_own",
|
|
29
|
-
"public_assets_update_own",
|
|
30
|
-
"public_assets_delete_own",
|
|
31
|
-
"private_uploads_select_own",
|
|
32
|
-
"private_uploads_insert_own",
|
|
33
|
-
"private_uploads_update_own",
|
|
34
|
-
"private_uploads_delete_own",
|
|
35
|
-
];
|
|
36
26
|
function printJson(value) {
|
|
37
27
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
38
28
|
}
|
|
@@ -114,6 +104,18 @@ function loadLocalEnv(cwd = process.cwd()) {
|
|
|
114
104
|
},
|
|
115
105
|
};
|
|
116
106
|
}
|
|
107
|
+
function readSetupConfig(cwd = process.cwd()) {
|
|
108
|
+
const raw = readFileIfExists(path.join(cwd, SETUP_CONFIG_PATH)).trim();
|
|
109
|
+
if (!raw)
|
|
110
|
+
return {};
|
|
111
|
+
try {
|
|
112
|
+
const parsed = JSON.parse(raw);
|
|
113
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
117
119
|
function isNonEmptyString(value) {
|
|
118
120
|
return typeof value === "string" && value.trim().length > 0;
|
|
119
121
|
}
|
|
@@ -656,6 +658,44 @@ function runLinkedSupabaseCommand(command, cwd, failureCode, fallbackMessage) {
|
|
|
656
658
|
}
|
|
657
659
|
return typeof result.stdout === "string" ? result.stdout.trim() : "";
|
|
658
660
|
}
|
|
661
|
+
function isMigrationOrderingConflict(message) {
|
|
662
|
+
return /Found local migration files to be inserted before the last migration on remote database\./i.test(message);
|
|
663
|
+
}
|
|
664
|
+
function runStorageMigrationPush(commandBase, cwd) {
|
|
665
|
+
const firstResult = spawnSync(process.env.SHELL || "/bin/zsh", ["-lc", commandBase], {
|
|
666
|
+
cwd,
|
|
667
|
+
env: process.env,
|
|
668
|
+
encoding: "utf8",
|
|
669
|
+
});
|
|
670
|
+
if (firstResult.status === 0) {
|
|
671
|
+
return {
|
|
672
|
+
stdout: typeof firstResult.stdout === "string" ? firstResult.stdout.trim() : "",
|
|
673
|
+
includeAll: false,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const firstMessage = [firstResult.stderr, firstResult.stdout]
|
|
677
|
+
.map((value) => (typeof value === "string" ? value.trim() : ""))
|
|
678
|
+
.find(Boolean) || "Failed to apply storage migrations to the linked Supabase project.";
|
|
679
|
+
if (!isMigrationOrderingConflict(firstMessage)) {
|
|
680
|
+
fail("STORAGE_POLICY_MIGRATION_APPLY_FAILED", firstMessage);
|
|
681
|
+
}
|
|
682
|
+
const retryCommand = `${commandBase} --include-all`;
|
|
683
|
+
const retryResult = spawnSync(process.env.SHELL || "/bin/zsh", ["-lc", retryCommand], {
|
|
684
|
+
cwd,
|
|
685
|
+
env: process.env,
|
|
686
|
+
encoding: "utf8",
|
|
687
|
+
});
|
|
688
|
+
if (retryResult.status !== 0) {
|
|
689
|
+
const retryMessage = [retryResult.stderr, retryResult.stdout]
|
|
690
|
+
.map((value) => (typeof value === "string" ? value.trim() : ""))
|
|
691
|
+
.find(Boolean) || "Failed to apply storage migrations to the linked Supabase project.";
|
|
692
|
+
fail("STORAGE_POLICY_MIGRATION_APPLY_FAILED", retryMessage);
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
stdout: typeof retryResult.stdout === "string" ? retryResult.stdout.trim() : "",
|
|
696
|
+
includeAll: true,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
659
699
|
function normalizeStorageMimeCategories(rawValue) {
|
|
660
700
|
const requested = rawValue
|
|
661
701
|
.split(",")
|
|
@@ -664,11 +704,18 @@ function normalizeStorageMimeCategories(rawValue) {
|
|
|
664
704
|
const selected = requested.filter((value) => Object.prototype.hasOwnProperty.call(STORAGE_MIME_TYPES_BY_CATEGORY, value));
|
|
665
705
|
return selected.length > 0 ? [...new Set(selected)] : [...STORAGE_DEFAULT_MIME_CATEGORIES];
|
|
666
706
|
}
|
|
667
|
-
function resolveStorageMimeCategories(
|
|
668
|
-
const
|
|
669
|
-
|
|
707
|
+
function resolveStorageMimeCategories(cwd) {
|
|
708
|
+
const setupConfig = readSetupConfig(cwd);
|
|
709
|
+
const storageConfig = setupConfig.storage && typeof setupConfig.storage === "object"
|
|
710
|
+
? setupConfig.storage
|
|
711
|
+
: {};
|
|
712
|
+
const rawCategories = Array.isArray(storageConfig.mimeCategories)
|
|
713
|
+
? storageConfig.mimeCategories.filter((value) => isNonEmptyString(value))
|
|
714
|
+
: [];
|
|
715
|
+
if (rawCategories.length === 0) {
|
|
670
716
|
return [...STORAGE_DEFAULT_MIME_CATEGORIES];
|
|
671
|
-
|
|
717
|
+
}
|
|
718
|
+
return normalizeStorageMimeCategories(rawCategories.join(","));
|
|
672
719
|
}
|
|
673
720
|
function expandStorageMimeCategories(categories) {
|
|
674
721
|
const mimeTypes = [];
|
|
@@ -694,28 +741,47 @@ function parseStorageMigrationFilename(filename) {
|
|
|
694
741
|
return null;
|
|
695
742
|
}
|
|
696
743
|
function discoverStorageMigrationFiles(cwd) {
|
|
697
|
-
const
|
|
698
|
-
|
|
744
|
+
const storageMigrationsRoot = path.join(cwd, "supabase", "storage-migrations");
|
|
745
|
+
const activeMigrationsRoot = path.join(cwd, "supabase", "migrations");
|
|
746
|
+
if (!fs.existsSync(activeMigrationsRoot)) {
|
|
699
747
|
fail("MISSING_STORAGE_MIGRATIONS", "supabase/migrations is missing. Run bootstrap.base first so the local Supabase project is initialized.");
|
|
700
748
|
}
|
|
701
|
-
|
|
749
|
+
if (!fs.existsSync(storageMigrationsRoot)) {
|
|
750
|
+
fail("MISSING_STORAGE_MIGRATIONS", "supabase/storage-migrations is missing. Regenerate the project so storage-owned migrations are available locally.");
|
|
751
|
+
}
|
|
752
|
+
const policyFiles = fs.readdirSync(storageMigrationsRoot)
|
|
702
753
|
.map((filename) => parseStorageMigrationFilename(filename))
|
|
703
|
-
.filter((entry) => entry !== null && entry.scope
|
|
754
|
+
.filter((entry) => entry !== null && /(^|_)storage_policies$/.test(entry.scope))
|
|
704
755
|
.sort((a, b) => a.filename.localeCompare(b.filename));
|
|
705
|
-
if (selected.length === 0) {
|
|
706
|
-
fail("MISSING_STORAGE_MIGRATIONS", "No storage migration files were found. Add a migration matching YYYYMMDDHHMMSS_storage_*.sql in supabase/migrations.");
|
|
707
|
-
}
|
|
708
|
-
const policyFiles = selected
|
|
709
|
-
.filter((entry) => /(^|_)storage_policies\.sql$/.test(entry.filename))
|
|
710
|
-
.map((entry) => entry.filename);
|
|
711
756
|
if (policyFiles.length === 0) {
|
|
712
|
-
fail("MISSING_STORAGE_POLICY_MIGRATION", "No storage policy migration file was found.
|
|
757
|
+
fail("MISSING_STORAGE_POLICY_MIGRATION", "No storage policy migration file was found in supabase/storage-migrations. This directory is for storage policy SQL only. Buckets are created by storage bootstrap through the Supabase Storage API. Regenerate the project or add a file ending in _storage_policies.sql.");
|
|
713
758
|
}
|
|
714
759
|
return {
|
|
715
|
-
files:
|
|
716
|
-
policyFiles,
|
|
760
|
+
files: policyFiles.map((entry) => entry.filename),
|
|
761
|
+
policyFiles: policyFiles.map((entry) => entry.filename),
|
|
717
762
|
};
|
|
718
763
|
}
|
|
764
|
+
function materializeStorageMigrationFiles(cwd, files) {
|
|
765
|
+
const storageMigrationsRoot = path.join(cwd, "supabase", "storage-migrations");
|
|
766
|
+
const activeMigrationsRoot = path.join(cwd, "supabase", "migrations");
|
|
767
|
+
const preparedFiles = [];
|
|
768
|
+
for (const filename of files) {
|
|
769
|
+
const sourcePath = path.join(storageMigrationsRoot, filename);
|
|
770
|
+
const targetPath = path.join(activeMigrationsRoot, filename);
|
|
771
|
+
const sourceSql = fs.readFileSync(sourcePath, "utf8");
|
|
772
|
+
if (fs.existsSync(targetPath)) {
|
|
773
|
+
const existingSql = fs.readFileSync(targetPath, "utf8");
|
|
774
|
+
if (existingSql !== sourceSql) {
|
|
775
|
+
fail("STORAGE_MIGRATION_CONFLICT", `Active migration ${filename} does not match supabase/storage-migrations/${filename}.`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
780
|
+
}
|
|
781
|
+
preparedFiles.push(filename);
|
|
782
|
+
}
|
|
783
|
+
return preparedFiles;
|
|
784
|
+
}
|
|
719
785
|
async function storageRequest(params) {
|
|
720
786
|
const response = await fetch(`${params.supabaseUrl}${params.endpoint}`, {
|
|
721
787
|
method: params.method,
|
|
@@ -842,9 +908,6 @@ async function ensureStorageBucket(supabaseUrl, serviceRoleKey, bucketId, isPubl
|
|
|
842
908
|
verified: true,
|
|
843
909
|
};
|
|
844
910
|
}
|
|
845
|
-
function buildStorageHealthcheckObjectPath(runId) {
|
|
846
|
-
return `${STORAGE_HEALTHCHECK_PREFIX}/${runId}/upload.txt`;
|
|
847
|
-
}
|
|
848
911
|
async function checkSupabaseContext() {
|
|
849
912
|
const { values, envLocalPath } = loadLocalEnv();
|
|
850
913
|
const missingKeys = [];
|
|
@@ -880,10 +943,11 @@ async function setupSupabaseStorage(flags) {
|
|
|
880
943
|
const serviceRoleKey = requireServiceRoleKey(values);
|
|
881
944
|
const dependencyManager = detectDependencyManager(cwd, flags);
|
|
882
945
|
const supabaseRunner = getSupabaseRunner(dependencyManager);
|
|
883
|
-
const mimeCategories = resolveStorageMimeCategories(
|
|
946
|
+
const mimeCategories = resolveStorageMimeCategories(cwd);
|
|
884
947
|
const allowedMimeTypes = expandStorageMimeCategories(mimeCategories);
|
|
885
948
|
const migrations = discoverStorageMigrationFiles(cwd);
|
|
886
|
-
|
|
949
|
+
const preparedMigrationFiles = materializeStorageMigrationFiles(cwd, migrations.files);
|
|
950
|
+
const migrationPush = runStorageMigrationPush(`${supabaseRunner} db push --linked`, cwd);
|
|
887
951
|
const publicBucket = await ensureStorageBucket(supabaseUrl, serviceRoleKey, STORAGE_PUBLIC_BUCKET, true, allowedMimeTypes);
|
|
888
952
|
const privateBucket = await ensureStorageBucket(supabaseUrl, serviceRoleKey, STORAGE_PRIVATE_BUCKET, false, allowedMimeTypes);
|
|
889
953
|
mergeEnvFile(envLocalPath, {
|
|
@@ -902,57 +966,13 @@ async function setupSupabaseStorage(flags) {
|
|
|
902
966
|
fileSizeLimit: STORAGE_REQUIRED_FILE_SIZE_LIMIT,
|
|
903
967
|
migrationFiles: migrations.files,
|
|
904
968
|
policyFiles: migrations.policyFiles,
|
|
969
|
+
preparedMigrationFiles,
|
|
905
970
|
policyMigrationsDiscovered: true,
|
|
906
971
|
policyMigrationsApplied: true,
|
|
907
|
-
|
|
908
|
-
policyVerificationMethod: "linked_db_push",
|
|
972
|
+
migrationPushIncludeAll: migrationPush.includeAll,
|
|
909
973
|
envWritten: ["SUPABASE_PUBLIC_BUCKET", "SUPABASE_PRIVATE_BUCKET"],
|
|
910
974
|
});
|
|
911
975
|
}
|
|
912
|
-
async function smokeTestSupabase() {
|
|
913
|
-
const { values } = loadLocalEnv();
|
|
914
|
-
const supabaseUrl = requireSupabaseUrl(values);
|
|
915
|
-
const serviceRoleKey = requireServiceRoleKey(values);
|
|
916
|
-
const runId = `run_${Date.now()}`;
|
|
917
|
-
const objectPath = buildStorageHealthcheckObjectPath(runId);
|
|
918
|
-
const prefix = `${STORAGE_HEALTHCHECK_PREFIX}/${runId}/`;
|
|
919
|
-
await storageRequest({
|
|
920
|
-
supabaseUrl,
|
|
921
|
-
serviceRoleKey,
|
|
922
|
-
method: "POST",
|
|
923
|
-
endpoint: `/storage/v1/object/${STORAGE_PUBLIC_BUCKET}/${objectPath}`,
|
|
924
|
-
rawBody: "healthcheck probe",
|
|
925
|
-
contentType: "text/plain",
|
|
926
|
-
});
|
|
927
|
-
const listResult = await storageRequest({
|
|
928
|
-
supabaseUrl,
|
|
929
|
-
serviceRoleKey,
|
|
930
|
-
method: "POST",
|
|
931
|
-
endpoint: `/storage/v1/object/list/${STORAGE_PUBLIC_BUCKET}`,
|
|
932
|
-
body: { prefix },
|
|
933
|
-
contentType: "application/json",
|
|
934
|
-
});
|
|
935
|
-
await storageRequest({
|
|
936
|
-
supabaseUrl,
|
|
937
|
-
serviceRoleKey,
|
|
938
|
-
method: "DELETE",
|
|
939
|
-
endpoint: `/storage/v1/object/${STORAGE_PUBLIC_BUCKET}`,
|
|
940
|
-
body: { prefixes: [objectPath] },
|
|
941
|
-
contentType: "application/json",
|
|
942
|
-
});
|
|
943
|
-
printJson({
|
|
944
|
-
ok: true,
|
|
945
|
-
command: "storage smoke-test-supabase",
|
|
946
|
-
runId,
|
|
947
|
-
bucketId: STORAGE_PUBLIC_BUCKET,
|
|
948
|
-
objectPath,
|
|
949
|
-
prefix,
|
|
950
|
-
upload: true,
|
|
951
|
-
list: true,
|
|
952
|
-
delete: true,
|
|
953
|
-
listedItems: Array.isArray(listResult) ? listResult.length : 0,
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
976
|
async function main() {
|
|
957
977
|
const { command, subcommand, flags } = parseArgs(process.argv.slice(2));
|
|
958
978
|
if (!command || command === "--help" || command === "help") {
|
|
@@ -963,10 +983,8 @@ async function main() {
|
|
|
963
983
|
"admin ensure-admin",
|
|
964
984
|
"storage check-supabase-context",
|
|
965
985
|
"storage setup-supabase",
|
|
966
|
-
"storage smoke-test-supabase",
|
|
967
986
|
"storage check-s3-context",
|
|
968
987
|
"storage setup-s3",
|
|
969
|
-
"storage smoke-test-s3",
|
|
970
988
|
"configure-site-redirects",
|
|
971
989
|
"configure-email-password",
|
|
972
990
|
"enable-google-provider",
|
|
@@ -989,14 +1007,10 @@ async function main() {
|
|
|
989
1007
|
return checkSupabaseContext();
|
|
990
1008
|
if (command === "storage" && subcommand === "setup-supabase")
|
|
991
1009
|
return setupSupabaseStorage(flags);
|
|
992
|
-
if (command === "storage" && subcommand === "smoke-test-supabase")
|
|
993
|
-
return smokeTestSupabase();
|
|
994
1010
|
if (command === "storage" && subcommand === "check-s3-context")
|
|
995
1011
|
return checkS3Context(flags);
|
|
996
1012
|
if (command === "storage" && subcommand === "setup-s3")
|
|
997
1013
|
return setupS3Storage(flags);
|
|
998
|
-
if (command === "storage" && subcommand === "smoke-test-s3")
|
|
999
|
-
return smokeTestS3(flags);
|
|
1000
1014
|
if (command === "configure-site-redirects")
|
|
1001
1015
|
return configureSiteRedirects(flags);
|
|
1002
1016
|
if (command === "configure-email-password")
|
package/dist/storageS3.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as crypto from "node:crypto";
|
|
4
|
-
import { S3Client, HeadBucketCommand, CreateBucketCommand, PutPublicAccessBlockCommand, PutBucketEncryptionCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketCorsCommand, GetBucketLocationCommand, GetPublicAccessBlockCommand, GetBucketEncryptionCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketCorsCommand,
|
|
4
|
+
import { S3Client, HeadBucketCommand, CreateBucketCommand, PutPublicAccessBlockCommand, PutBucketEncryptionCommand, PutBucketOwnershipControlsCommand, PutBucketPolicyCommand, PutBucketCorsCommand, GetBucketLocationCommand, GetPublicAccessBlockCommand, GetBucketEncryptionCommand, GetBucketOwnershipControlsCommand, GetBucketPolicyCommand, GetBucketCorsCommand, } from "@aws-sdk/client-s3";
|
|
5
5
|
import { STSClient, GetCallerIdentityCommand } from "@aws-sdk/client-sts";
|
|
6
6
|
const SETUP_CONFIG_PATH = path.join(".vibecodemax", "setup-config.json");
|
|
7
7
|
const DEFAULT_BUCKETS = {
|
|
@@ -10,7 +10,6 @@ const DEFAULT_BUCKETS = {
|
|
|
10
10
|
};
|
|
11
11
|
const DEFAULT_PROJECT_SLUG = "vibecodemax";
|
|
12
12
|
const DEFAULT_REGION = "us-east-1";
|
|
13
|
-
const HEALTHCHECK_PREFIX = "_vibecodemax/healthcheck/default";
|
|
14
13
|
const PUBLIC_ACCESS_BLOCK_PRIVATE = {
|
|
15
14
|
BlockPublicAcls: true,
|
|
16
15
|
IgnorePublicAcls: true,
|
|
@@ -482,55 +481,6 @@ async function verifyTwoBuckets(region, credentials, buckets) {
|
|
|
482
481
|
});
|
|
483
482
|
}
|
|
484
483
|
}
|
|
485
|
-
function buildObjectUrl(bucket, region, key) {
|
|
486
|
-
const encoded = key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
|
|
487
|
-
if (region === "us-east-1")
|
|
488
|
-
return `https://${bucket}.s3.amazonaws.com/${encoded}`;
|
|
489
|
-
return `https://${bucket}.s3.${region}.amazonaws.com/${encoded}`;
|
|
490
|
-
}
|
|
491
|
-
async function smokeTestBuckets(region, credentials, buckets) {
|
|
492
|
-
const { s3Client } = createAwsClients(region, credentials);
|
|
493
|
-
const runId = `run_${Date.now()}`;
|
|
494
|
-
const prefix = `${HEALTHCHECK_PREFIX}/${runId}`;
|
|
495
|
-
const publicKey = `${prefix}/public-probe.txt`;
|
|
496
|
-
const privateKey = `${prefix}/private-probe.txt`;
|
|
497
|
-
await s3Client.send(new PutObjectCommand({ Bucket: buckets.public, Key: publicKey, Body: "public healthcheck probe", ContentType: "text/plain" }));
|
|
498
|
-
await s3Client.send(new PutObjectCommand({ Bucket: buckets.private, Key: privateKey, Body: "private healthcheck probe", ContentType: "text/plain" }));
|
|
499
|
-
const publicList = await s3Client.send(new ListObjectsV2Command({ Bucket: buckets.public, Prefix: prefix }));
|
|
500
|
-
const privateList = await s3Client.send(new ListObjectsV2Command({ Bucket: buckets.private, Prefix: prefix }));
|
|
501
|
-
const listPassed = Number(publicList.KeyCount || 0) > 0 && Number(privateList.KeyCount || 0) > 0;
|
|
502
|
-
if (!listPassed) {
|
|
503
|
-
fail("AWS_SMOKE_TEST_FAILED", "AWS S3 smoke-test list operation did not return the uploaded healthcheck objects.", 1, { prefix });
|
|
504
|
-
}
|
|
505
|
-
const publicUrl = buildObjectUrl(buckets.public, region, publicKey);
|
|
506
|
-
const privateUrl = buildObjectUrl(buckets.private, region, privateKey);
|
|
507
|
-
const publicReadResponse = await fetch(publicUrl);
|
|
508
|
-
const privateReadResponse = await fetch(privateUrl);
|
|
509
|
-
const publicRead = publicReadResponse.ok;
|
|
510
|
-
const privateReadBlocked = !privateReadResponse.ok;
|
|
511
|
-
if (!publicRead || !privateReadBlocked) {
|
|
512
|
-
fail("AWS_SMOKE_TEST_FAILED", "AWS S3 smoke-test public/private read checks failed.", 1, {
|
|
513
|
-
publicReadStatus: publicReadResponse.status,
|
|
514
|
-
privateReadStatus: privateReadResponse.status,
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
await s3Client.send(new GetObjectCommand({ Bucket: buckets.private, Key: privateKey }));
|
|
518
|
-
await s3Client.send(new DeleteObjectCommand({ Bucket: buckets.public, Key: publicKey }));
|
|
519
|
-
await s3Client.send(new DeleteObjectCommand({ Bucket: buckets.private, Key: privateKey }));
|
|
520
|
-
return {
|
|
521
|
-
ok: true,
|
|
522
|
-
command: "storage smoke-test-s3",
|
|
523
|
-
runId,
|
|
524
|
-
prefix,
|
|
525
|
-
buckets,
|
|
526
|
-
upload: true,
|
|
527
|
-
list: true,
|
|
528
|
-
publicRead: true,
|
|
529
|
-
privateRead: true,
|
|
530
|
-
delete: true,
|
|
531
|
-
publicObjectUrl: publicUrl,
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
484
|
export async function checkS3Context(flags) {
|
|
535
485
|
const context = readAwsContext(flags);
|
|
536
486
|
if (context.missingKeys.length > 0) {
|
|
@@ -603,28 +553,6 @@ export async function setupS3Storage(flags) {
|
|
|
603
553
|
envWritten: ["AWS_REGION", "AWS_S3_PUBLIC_BUCKET", "AWS_S3_PRIVATE_BUCKET"],
|
|
604
554
|
});
|
|
605
555
|
}
|
|
606
|
-
export async function smokeTestS3(flags) {
|
|
607
|
-
const context = readAwsContext(flags);
|
|
608
|
-
const publicBucket = context.localEnv.values.AWS_S3_PUBLIC_BUCKET || "";
|
|
609
|
-
const privateBucket = context.localEnv.values.AWS_S3_PRIVATE_BUCKET || "";
|
|
610
|
-
if (context.missingKeys.length > 0) {
|
|
611
|
-
fail("MISSING_ENV", `Missing required AWS values: ${context.missingKeys.join(", ")}. Add them to .env.bootstrap.local.`);
|
|
612
|
-
}
|
|
613
|
-
if (!isNonEmptyString(publicBucket) || !isNonEmptyString(privateBucket)) {
|
|
614
|
-
fail("MISSING_ENV", "AWS_S3_PUBLIC_BUCKET or AWS_S3_PRIVATE_BUCKET is missing. Run storage setup first so .env.local is populated.");
|
|
615
|
-
}
|
|
616
|
-
const credentials = {
|
|
617
|
-
accessKeyId: context.accessKeyId,
|
|
618
|
-
secretAccessKey: context.secretAccessKey,
|
|
619
|
-
...(context.sessionToken ? { sessionToken: context.sessionToken } : {}),
|
|
620
|
-
};
|
|
621
|
-
await validateCredentials(context.region, credentials);
|
|
622
|
-
const result = await smokeTestBuckets(context.region, credentials, {
|
|
623
|
-
public: publicBucket.trim(),
|
|
624
|
-
private: privateBucket.trim(),
|
|
625
|
-
});
|
|
626
|
-
printJson(result);
|
|
627
|
-
}
|
|
628
556
|
export function __testOnlyDeterministicBuckets(projectSlug, accountId) {
|
|
629
557
|
return deterministicBuckets({ projectSlug, accountId });
|
|
630
558
|
}
|