@vibecodemax/cli 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +75 -48
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from "node:fs";
3
- import * as os from "node:os";
4
3
  import * as path from "node:path";
5
4
  import { spawnSync } from "node:child_process";
6
5
  import { checkS3Context, setupS3Storage, smokeTestS3 } from "./storageS3.js";
@@ -41,6 +40,19 @@ function fail(code, message, exitCode = 1, extra = {}) {
41
40
  printJson({ ok: false, code, message, ...extra });
42
41
  process.exit(exitCode);
43
42
  }
43
+ /** Extract a human-readable error message from a Supabase/provider JSON response. */
44
+ function extractErrorMessage(json) {
45
+ if (!json || typeof json !== "object")
46
+ return null;
47
+ const obj = json;
48
+ if (isNonEmptyString(obj.error))
49
+ return obj.error;
50
+ if (isNonEmptyString(obj.message))
51
+ return obj.message;
52
+ if (isNonEmptyString(obj.msg))
53
+ return obj.msg;
54
+ return null;
55
+ }
44
56
  function parseArgs(argv) {
45
57
  const [command, ...rest] = argv;
46
58
  let subcommand;
@@ -492,31 +504,44 @@ async function createAdminUser(supabaseUrl, serviceRoleKey, email, temporaryPass
492
504
  }
493
505
  return userId;
494
506
  }
495
- async function promoteAdminProfile(supabaseUrl, serviceRoleKey, userId, email) {
507
+ async function findAdminMembership(supabaseUrl, serviceRoleKey, userId) {
508
+ const result = await supabaseAdminRequest({
509
+ supabaseUrl,
510
+ serviceRoleKey,
511
+ method: "GET",
512
+ endpoint: `/rest/v1/admin_users?user_id=eq.${userId}&select=user_id,role`,
513
+ });
514
+ const rows = Array.isArray(result) ? result : [];
515
+ const row = rows.find((candidate) => {
516
+ if (!candidate || typeof candidate !== "object")
517
+ return false;
518
+ return candidate.user_id === userId;
519
+ });
520
+ return {
521
+ exists: Boolean(row),
522
+ role: typeof row?.role === "string" ? row.role : null,
523
+ };
524
+ }
525
+ async function promoteAdminUser(supabaseUrl, serviceRoleKey, userId) {
526
+ const existing = await findAdminMembership(supabaseUrl, serviceRoleKey, userId);
527
+ if (existing.exists)
528
+ return;
496
529
  await supabaseAdminRequest({
497
530
  supabaseUrl,
498
531
  serviceRoleKey,
499
532
  method: "POST",
500
- endpoint: "/rest/v1/profiles?on_conflict=id",
533
+ endpoint: "/rest/v1/admin_users",
501
534
  body: [
502
535
  {
503
- id: userId,
504
- email,
505
- name: "Admin User",
506
- role: "admin",
536
+ user_id: userId,
537
+ role: "super",
507
538
  },
508
539
  ],
509
540
  });
510
541
  }
511
- async function verifyAdminProfile(supabaseUrl, serviceRoleKey, userId) {
512
- const result = await supabaseAdminRequest({
513
- supabaseUrl,
514
- serviceRoleKey,
515
- method: "GET",
516
- endpoint: `/rest/v1/profiles?id=eq.${userId}&select=id,role`,
517
- });
518
- const rows = Array.isArray(result) ? result : [];
519
- return rows.some((row) => row && typeof row === "object" && row.role === "admin");
542
+ async function verifyAdminUser(supabaseUrl, serviceRoleKey, userId) {
543
+ const membership = await findAdminMembership(supabaseUrl, serviceRoleKey, userId);
544
+ return membership.exists;
520
545
  }
521
546
  async function ensureAdmin(flags) {
522
547
  const { values } = loadLocalEnv();
@@ -538,8 +563,8 @@ async function ensureAdmin(flags) {
538
563
  if (!userId) {
539
564
  fail("INVALID_RESPONSE", "Resolved admin user ID is missing after ensure-admin.");
540
565
  }
541
- await promoteAdminProfile(supabaseUrl, serviceRoleKey, userId, email);
542
- const verified = await verifyAdminProfile(supabaseUrl, serviceRoleKey, userId);
566
+ await promoteAdminUser(supabaseUrl, serviceRoleKey, userId);
567
+ const verified = await verifyAdminUser(supabaseUrl, serviceRoleKey, userId);
543
568
  if (!verified) {
544
569
  fail("ADMIN_VERIFY_FAILED", "The admin role could not be verified after promotion.");
545
570
  }
@@ -553,7 +578,7 @@ async function ensureAdmin(flags) {
553
578
  promoted: true,
554
579
  verified: true,
555
580
  defaultName: "Admin User",
556
- applied: ["auth_user", "admin_profile"],
581
+ applied: ["auth_user", "admin_users"],
557
582
  };
558
583
  if (temporaryPassword) {
559
584
  result.temporaryPassword = temporaryPassword;
@@ -617,6 +642,20 @@ function runShellCommand(command, cwd) {
617
642
  }
618
643
  return typeof result.stdout === "string" ? result.stdout.trim() : "";
619
644
  }
645
+ function runLinkedSupabaseCommand(command, cwd, failureCode, fallbackMessage) {
646
+ const result = spawnSync(process.env.SHELL || "/bin/zsh", ["-lc", command], {
647
+ cwd,
648
+ env: process.env,
649
+ encoding: "utf8",
650
+ });
651
+ if (result.status !== 0) {
652
+ const message = [result.stderr, result.stdout]
653
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
654
+ .find(Boolean) || fallbackMessage;
655
+ fail(failureCode, message);
656
+ }
657
+ return typeof result.stdout === "string" ? result.stdout.trim() : "";
658
+ }
620
659
  function normalizeStorageMimeCategories(rawValue) {
621
660
  const requested = rawValue
622
661
  .split(",")
@@ -695,13 +734,7 @@ async function storageRequest(params) {
695
734
  json = null;
696
735
  }
697
736
  if (!response.ok) {
698
- const message = isNonEmptyString(json?.error)
699
- ? json.error
700
- : isNonEmptyString(json?.message)
701
- ? json.message
702
- : isNonEmptyString(json?.msg)
703
- ? json.msg
704
- : `Supabase returned ${response.status}`;
737
+ const message = extractErrorMessage(json) || `Supabase returned ${response.status}`;
705
738
  fail("SUPABASE_STORAGE_ERROR", message, 1, { status: response.status });
706
739
  }
707
740
  return json;
@@ -751,14 +784,14 @@ async function readStorageBucket(supabaseUrl, serviceRoleKey, bucketId) {
751
784
  }
752
785
  if (response.status === 404)
753
786
  return null;
787
+ // Supabase may return 400 (not 404) for non-existent buckets in some API versions
788
+ if (response.status === 400) {
789
+ const extracted = extractErrorMessage(json);
790
+ if (extracted && /bucket\s*not\s*found/i.test(extracted))
791
+ return null;
792
+ }
754
793
  if (!response.ok) {
755
- const message = isNonEmptyString(json?.error)
756
- ? json.error
757
- : isNonEmptyString(json?.message)
758
- ? json.message
759
- : isNonEmptyString(json?.msg)
760
- ? json.msg
761
- : `Supabase returned ${response.status}`;
794
+ const message = extractErrorMessage(json) || `Supabase returned ${response.status}`;
762
795
  fail("SUPABASE_STORAGE_ERROR", message, 1, { status: response.status });
763
796
  }
764
797
  return json && typeof json === "object" ? json : {};
@@ -809,13 +842,6 @@ async function ensureStorageBucket(supabaseUrl, serviceRoleKey, bucketId, isPubl
809
842
  verified: true,
810
843
  };
811
844
  }
812
- function verifyRequiredStoragePolicies(dumpContent) {
813
- const normalized = dumpContent.toLowerCase();
814
- const missing = STORAGE_REQUIRED_POLICY_NAMES.filter((policyName) => !normalized.includes(`create policy \"${policyName}\"`));
815
- if (missing.length > 0) {
816
- fail("STORAGE_POLICY_VERIFY_FAILED", `Missing required storage policies after local Supabase CLI apply: ${missing.join(", ")}.`);
817
- }
818
- }
819
845
  function buildStorageHealthcheckObjectPath(runId) {
820
846
  return `${STORAGE_HEALTHCHECK_PREFIX}/${runId}/upload.txt`;
821
847
  }
@@ -856,20 +882,14 @@ async function setupSupabaseStorage(flags) {
856
882
  const supabaseRunner = getSupabaseRunner(dependencyManager);
857
883
  const mimeCategories = resolveStorageMimeCategories(flags);
858
884
  const allowedMimeTypes = expandStorageMimeCategories(mimeCategories);
885
+ const migrations = discoverStorageMigrationFiles(cwd);
886
+ runLinkedSupabaseCommand(`${supabaseRunner} db push --linked`, cwd, "STORAGE_POLICY_MIGRATION_APPLY_FAILED", "Failed to apply storage migrations to the linked Supabase project.");
859
887
  const publicBucket = await ensureStorageBucket(supabaseUrl, serviceRoleKey, STORAGE_PUBLIC_BUCKET, true, allowedMimeTypes);
860
888
  const privateBucket = await ensureStorageBucket(supabaseUrl, serviceRoleKey, STORAGE_PRIVATE_BUCKET, false, allowedMimeTypes);
861
889
  mergeEnvFile(envLocalPath, {
862
890
  SUPABASE_PUBLIC_BUCKET: STORAGE_PUBLIC_BUCKET,
863
891
  SUPABASE_PRIVATE_BUCKET: STORAGE_PRIVATE_BUCKET,
864
892
  });
865
- const migrations = discoverStorageMigrationFiles(cwd);
866
- runShellCommand(`${supabaseRunner} db push --linked`, cwd);
867
- const dumpDir = fs.mkdtempSync(path.join(os.tmpdir(), "vibecodemax-storage-dump-"));
868
- const dumpPath = path.join(dumpDir, "storage.sql");
869
- runShellCommand(`${supabaseRunner} db dump --linked --schema storage -f ${shellQuote(dumpPath)} >/dev/null`, cwd);
870
- const dumpContent = readFileIfExists(dumpPath);
871
- verifyRequiredStoragePolicies(dumpContent);
872
- fs.rmSync(dumpDir, { recursive: true, force: true });
873
893
  printJson({
874
894
  ok: true,
875
895
  command: "storage setup-supabase",
@@ -885,6 +905,7 @@ async function setupSupabaseStorage(flags) {
885
905
  policyMigrationsDiscovered: true,
886
906
  policyMigrationsApplied: true,
887
907
  policiesVerified: true,
908
+ policyVerificationMethod: "linked_db_push",
888
909
  envWritten: ["SUPABASE_PUBLIC_BUCKET", "SUPABASE_PRIVATE_BUCKET"],
889
910
  });
890
911
  }
@@ -954,6 +975,12 @@ async function main() {
954
975
  });
955
976
  return;
956
977
  }
978
+ // Handle --help for any resolved command before dispatching (avoids live API calls)
979
+ if (flags.help || flags.h) {
980
+ const resolvedCommand = subcommand ? `${command} ${subcommand}` : command;
981
+ printJson({ ok: true, command: resolvedCommand, help: true, message: `Usage: npx @vibecodemax/cli ${resolvedCommand} [options]` });
982
+ return;
983
+ }
957
984
  if (command === "read-setup-state")
958
985
  return readSetupState();
959
986
  if (command === "admin" && subcommand === "ensure-admin")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecodemax/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "VibeCodeMax CLI — local provider setup for bootstrap and project configuration",
5
5
  "type": "module",
6
6
  "bin": {