@abloatai/cli 0.56.0 → 0.57.0

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.cjs +75 -13
  2. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -3970,7 +3970,7 @@ var init_observeCliError = __esm({
3970
3970
  import_errorObservation = require("@abloatai/transaction/errorObservation");
3971
3971
  import_errors5 = require("@abloatai/transaction/errors");
3972
3972
  dsn = process.env.ABLO_CLI_SENTRY_DSN ?? "https://1ac154bff10b06836e1ea9de9e0d92f0@o4510928209772544.ingest.de.sentry.io/4511660691423312" ?? "";
3973
- release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.56.0";
3973
+ release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.57.0";
3974
3974
  initialized = false;
3975
3975
  nativeProcessExit = process.exit.bind(process);
3976
3976
  exitBoundaryInstalled = false;
@@ -4297,7 +4297,7 @@ var init_src2 = __esm({
4297
4297
 
4298
4298
  // src/cliEnvironment.ts
4299
4299
  function cliVersion() {
4300
- return "0.56.0";
4300
+ return "0.57.0";
4301
4301
  }
4302
4302
  function cliOs() {
4303
4303
  const value = (0, import_node_os.platform)();
@@ -4372,7 +4372,9 @@ function trackCliSchemaPushAttempted() {
4372
4372
  }
4373
4373
  function flushProductAnalytics(options = {}) {
4374
4374
  if (flushInFlight) return flushInFlight;
4375
- flushInFlight = flushOnce(options).catch(() => void 0).finally(() => {
4375
+ flushInFlight = flushOnce(options).catch((error) => {
4376
+ void error;
4377
+ }).finally(() => {
4376
4378
  flushInFlight = null;
4377
4379
  });
4378
4380
  return flushInFlight;
@@ -4395,8 +4397,8 @@ function queueEvent(eventName, properties) {
4395
4397
  state.queue = [...state.queue, parsed].slice(-MAX_QUEUED_EVENTS);
4396
4398
  writeState(state);
4397
4399
  if (disclose) {
4398
- console.error(
4399
- "Ablo collects limited usage analytics. Run `ablo telemetry disable` to opt out or `ablo telemetry status` for details."
4400
+ process.stderr.write(
4401
+ "Ablo collects limited usage analytics. Run `ablo telemetry disable` to opt out or `ablo telemetry status` for details.\n"
4400
4402
  );
4401
4403
  }
4402
4404
  } catch {
@@ -4473,8 +4475,9 @@ function writeState(state) {
4473
4475
  function environmentBlocker() {
4474
4476
  if (process.env.ABLO_TELEMETRY_DISABLED === "1") return "ABLO_TELEMETRY_DISABLED";
4475
4477
  if (process.env.DO_NOT_TRACK === "1") return "DO_NOT_TRACK";
4476
- const ciVariables = ["CI", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI", "TF_BUILD"];
4477
- return ciVariables.find((name) => truthyEnvironmentValue(process.env[name])) ?? null;
4478
+ return TELEMETRY_BLOCKING_ENV.slice(2).find(
4479
+ (name) => truthyEnvironmentValue(process.env[name])
4480
+ ) ?? null;
4478
4481
  }
4479
4482
  function truthyEnvironmentValue(value) {
4480
4483
  return value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
@@ -4502,7 +4505,7 @@ function runTelemetryCommand(argv) {
4502
4505
  if (status2.blockedBy) console.log(`Environment override: ${status2.blockedBy}`);
4503
4506
  console.log(`Queued events: ${status2.queuedEvents}`);
4504
4507
  }
4505
- var import_node_crypto2, import_node_fs, import_node_path, TELEMETRY_FILE_VERSION, MAX_QUEUED_EVENTS, MAX_FLUSH_EVENTS, DEFAULT_FLUSH_TIMEOUT_MS, flushInFlight;
4508
+ var import_node_crypto2, import_node_fs, import_node_path, TELEMETRY_FILE_VERSION, MAX_QUEUED_EVENTS, MAX_FLUSH_EVENTS, DEFAULT_FLUSH_TIMEOUT_MS, flushInFlight, TELEMETRY_BLOCKING_ENV;
4506
4509
  var init_telemetry = __esm({
4507
4510
  "src/telemetry.ts"() {
4508
4511
  "use strict";
@@ -4519,6 +4522,16 @@ var init_telemetry = __esm({
4519
4522
  MAX_FLUSH_EVENTS = 20;
4520
4523
  DEFAULT_FLUSH_TIMEOUT_MS = 800;
4521
4524
  flushInFlight = null;
4525
+ TELEMETRY_BLOCKING_ENV = [
4526
+ "ABLO_TELEMETRY_DISABLED",
4527
+ "DO_NOT_TRACK",
4528
+ "CI",
4529
+ "GITHUB_ACTIONS",
4530
+ "GITLAB_CI",
4531
+ "BUILDKITE",
4532
+ "CIRCLECI",
4533
+ "TF_BUILD"
4534
+ ];
4522
4535
  }
4523
4536
  });
4524
4537
 
@@ -288767,6 +288780,9 @@ async function check(argv) {
288767
288780
  }
288768
288781
  const schema = await loadSchema(args.schemaPath, args.exportName);
288769
288782
  const schemaJson = JSON.parse((0, import_schema9.serializeSchema)(schema));
288783
+ const accessFindings = new Map(
288784
+ (0, import_schema9.auditSchemaAccessPolicies)(schemaJson).map((finding) => [finding.model, finding])
288785
+ );
288770
288786
  console.log(`
288771
288787
  ${brand("ablo")} ${import_picocolors25.default.dim("check")} ${import_picocolors25.default.dim(`schema "${args.appSchema}"`)}
288772
288788
  `);
@@ -288807,6 +288823,12 @@ async function check(argv) {
288807
288823
  }
288808
288824
  const problems = [];
288809
288825
  const warns = [];
288826
+ const accessFinding = accessFindings.get(key);
288827
+ if (accessFinding) {
288828
+ problems.push(
288829
+ `[${accessFinding.code}] ${accessFinding.message} Fix: ${accessFinding.fix}`
288830
+ );
288831
+ }
288810
288832
  if (!present.has("id")) problems.push('missing primary key "id"');
288811
288833
  const orgCol = (0, import_schema9.tenancyColumn)((0, import_schema9.resolveTenancy)(model));
288812
288834
  if (orgCol && !present.has(orgCol)) {
@@ -289751,8 +289773,21 @@ export async function POST(request: Request): Promise<Response> {
289751
289773
  );
289752
289774
  }
289753
289775
 
289776
+ const authorizedScope = await authorizeActiveWorkspace(user.id);
289777
+ if (!authorizedScope) {
289778
+ return Response.json(
289779
+ credentialEndpointErrorSchema.parse({
289780
+ error: { code: 'policy_denied', message: 'Workspace membership is stale or revoked' },
289781
+ }),
289782
+ { status: 403, headers: noStore },
289783
+ );
289784
+ }
289785
+
289754
289786
  const { token, expiresAt } = await sync.sessions.create({
289755
289787
  user: { id: user.id },
289788
+ // These ids came from the server-side membership lookup below. Never take
289789
+ // organization, workspace, team, or group ids from the request body.
289790
+ syncGroups: authorizedScope.syncGroups,
289756
289791
  can: { records: ['read', 'create', 'update'] },
289757
289792
  });
289758
289793
  return Response.json(
@@ -289776,6 +289811,27 @@ async function getCurrentUser(): Promise<{ id: string } | null> {
289776
289811
  const session = await auth.api.getSession({ headers: await headers() });
289777
289812
  return session?.user ? { id: session.user.id } : null;
289778
289813
  }
289814
+
289815
+ type AuthorizedWorkspace = {
289816
+ workspaceId: string;
289817
+ syncGroups: readonly [\`workspace:\${string}\`, ...\`\${string}:\${string}\`[]];
289818
+ };
289819
+
289820
+ async function authorizeActiveWorkspace(userId: string): Promise<AuthorizedWorkspace | null> {
289821
+ void userId;
289822
+ // REQUIRED: query your membership table here, immediately before minting.
289823
+ // Read the active workspace from the server-side session, verify this user has
289824
+ // an active membership, and derive every team/group id on the server. Return
289825
+ // null for a stale or revoked membership. This refusal keeps a newly generated
289826
+ // route from turning "signed in" into workspace authorization by accident.
289827
+ //
289828
+ // Example after your membership query:
289829
+ // return {
289830
+ // workspaceId: membership.workspaceId,
289831
+ // syncGroups: [\`workspace:\${membership.workspaceId}\`],
289832
+ // };
289833
+ return null;
289834
+ }
289779
289835
  `;
289780
289836
  }
289781
289837
 
@@ -289808,7 +289864,7 @@ export const schema = defineSchema({
289808
289864
  });
289809
289865
  `;
289810
289866
  }
289811
- function generateSyncConfig(auth) {
289867
+ function generateSyncConfig(auth, options = {}) {
289812
289868
  const authLine = auth === "apikey" ? "" : auth === "firebase" ? `
289813
289869
  auth: async () => {
289814
289870
  const { getAuth } = await import('firebase/auth');
@@ -289820,7 +289876,7 @@ function generateSyncConfig(auth) {
289820
289876
  // auth: async () => { const { data } = await supabase.auth.getSession(); return data.session?.access_token ?? ''; },` : auth === "betterauth" ? `
289821
289877
  // auth: async () => { const session = await authClient.getSession(); return session?.token ?? ''; },` : `
289822
289878
  // auth: () => 'your-jwt-token', // replace with your auth provider`;
289823
- return `import Ablo from '@abloatai/ablo';
289879
+ return `${options.serverOnly ? "import 'server-only';\n\n" : ""}import Ablo from '@abloatai/ablo';
289824
289880
  import { schema } from './schema';
289825
289881
 
289826
289882
  // SERVER-ONLY client \u2014 it holds your \`sk_\` key. Use it from server code: the
@@ -289982,7 +290038,10 @@ export async function POST(req: Request): Promise<Response> {
289982
290038
  const model: unknown = Reflect.get(prisma, event.model); // prisma.record, prisma.task, \u2026
289983
290039
  if (!isModelDelegate(model)) continue; // a model you don't mirror locally \u2014 skip it
289984
290040
  if (event.data === null) {
289985
- await model.delete({ where: { id: event.objectId } }).catch(() => {}); // already gone
290041
+ await model.delete({ where: { id: event.objectId } }).catch((error) => {
290042
+ // Idempotent replay: a missing local row is already the desired state.
290043
+ void error;
290044
+ });
289986
290045
  } else {
289987
290046
  await model.upsert({ where: { id: event.objectId }, create: event.data, update: event.data });
289988
290047
  }
@@ -290062,7 +290121,7 @@ async function main() {
290062
290121
  main().catch((err) => {
290063
290122
  // Ablo errors stringify to one clean line (code + message + docs link),
290064
290123
  // never a stack/object dump \u2014 see AbloError.toString().
290065
- console.error(String(err));
290124
+ process.stderr.write(String(err) + '\\n');
290066
290125
  process.exit(1);
290067
290126
  });
290068
290127
  `;
@@ -290413,7 +290472,10 @@ async function runInit(args = []) {
290413
290472
  }
290414
290473
  }
290415
290474
  files.push({ path: (0, import_node_path5.join)(abloDir, "schema.ts"), content: schemaSource, note: schemaNote });
290416
- files.push({ path: (0, import_node_path5.join)(abloDir, "index.ts"), content: generateSyncConfig(auth) });
290475
+ files.push({
290476
+ path: (0, import_node_path5.join)(abloDir, "index.ts"),
290477
+ content: generateSyncConfig(auth, { serverOnly: framework === "nextjs" })
290478
+ });
290417
290479
  files.push({ path: (0, import_node_path5.join)(abloDir, "register.ts"), content: generateRegister() });
290418
290480
  const orm = detectOrm(opts.orm);
290419
290481
  if (storage === "endpoint") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/cli",
3
- "version": "0.56.0",
3
+ "version": "0.57.0",
4
4
  "description": "The ablo command line: set up Ablo, connect your database, and push your schema from the terminal.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,10 +35,10 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@sentry/node": "^10.18.0",
38
- "@abloatai/transaction": "^0.56.0",
38
+ "@abloatai/transaction": "^0.57.0",
39
39
  "jiti": "^2.7.0",
40
40
  "zod": "^4.4.3",
41
- "@abloatai/humans": "^0.56.0"
41
+ "@abloatai/humans": "^0.57.0"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@ablo/product-analytics": "file:../product-analytics",