@cipherstash/stack 0.8.0 → 0.10.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.
- package/CHANGELOG.md +12 -0
- package/README.md +25 -3
- package/dist/bin/stash.js +438 -216
- package/dist/bin/stash.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/stash.js
CHANGED
|
@@ -500,7 +500,7 @@ var require_picocolors = __commonJS({
|
|
|
500
500
|
// src/bin/stash.ts
|
|
501
501
|
var import_dotenv2 = __toESM(require_main(), 1);
|
|
502
502
|
import { readFileSync } from "node:fs";
|
|
503
|
-
import { dirname, join } from "node:path";
|
|
503
|
+
import { dirname as dirname2, join } from "node:path";
|
|
504
504
|
import { fileURLToPath } from "node:url";
|
|
505
505
|
|
|
506
506
|
// ../../node_modules/.pnpm/@clack+prompts@0.10.1/node_modules/@clack/prompts/dist/index.mjs
|
|
@@ -855,6 +855,41 @@ var dD = class extends x {
|
|
|
855
855
|
};
|
|
856
856
|
var A;
|
|
857
857
|
A = /* @__PURE__ */ new WeakMap();
|
|
858
|
+
var kD = Object.defineProperty;
|
|
859
|
+
var $D = (e2, u2, t) => u2 in e2 ? kD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
860
|
+
var H = (e2, u2, t) => ($D(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
861
|
+
var SD = class extends x {
|
|
862
|
+
constructor(u2) {
|
|
863
|
+
super(u2, false), H(this, "options"), H(this, "cursor", 0), this.options = u2.options, this.value = [...u2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: t }) => t === u2.cursorAt), 0), this.on("key", (t) => {
|
|
864
|
+
t === "a" && this.toggleAll();
|
|
865
|
+
}), this.on("cursor", (t) => {
|
|
866
|
+
switch (t) {
|
|
867
|
+
case "left":
|
|
868
|
+
case "up":
|
|
869
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
870
|
+
break;
|
|
871
|
+
case "down":
|
|
872
|
+
case "right":
|
|
873
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
874
|
+
break;
|
|
875
|
+
case "space":
|
|
876
|
+
this.toggleValue();
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
get _value() {
|
|
882
|
+
return this.options[this.cursor].value;
|
|
883
|
+
}
|
|
884
|
+
toggleAll() {
|
|
885
|
+
const u2 = this.value.length === this.options.length;
|
|
886
|
+
this.value = u2 ? [] : this.options.map((t) => t.value);
|
|
887
|
+
}
|
|
888
|
+
toggleValue() {
|
|
889
|
+
const u2 = this.value.includes(this._value);
|
|
890
|
+
this.value = u2 ? this.value.filter((t) => t !== this._value) : [...this.value, this._value];
|
|
891
|
+
}
|
|
892
|
+
};
|
|
858
893
|
var OD = Object.defineProperty;
|
|
859
894
|
var PD = (e2, u2, t) => u2 in e2 ? OD(e2, u2, { enumerable: true, configurable: true, writable: true, value: t }) : e2[u2] = t;
|
|
860
895
|
var J = (e2, u2, t) => (PD(e2, typeof u2 != "symbol" ? u2 + "" : u2, t), t);
|
|
@@ -1023,6 +1058,46 @@ ${import_picocolors2.default.cyan(d2)}
|
|
|
1023
1058
|
}
|
|
1024
1059
|
} }).prompt();
|
|
1025
1060
|
};
|
|
1061
|
+
var fe = (t) => {
|
|
1062
|
+
const n = (r, i) => {
|
|
1063
|
+
const s = r.label ?? String(r.value);
|
|
1064
|
+
return i === "active" ? `${import_picocolors2.default.cyan(A2)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "selected" ? `${import_picocolors2.default.green(T)} ${import_picocolors2.default.dim(s)} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : i === "active-selected" ? `${import_picocolors2.default.green(T)} ${s} ${r.hint ? import_picocolors2.default.dim(`(${r.hint})`) : ""}` : i === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(F)} ${import_picocolors2.default.dim(s)}`;
|
|
1065
|
+
};
|
|
1066
|
+
return new SD({ options: t.options, initialValues: t.initialValues, required: t.required ?? true, cursorAt: t.cursorAt, validate(r) {
|
|
1067
|
+
if (this.required && r.length === 0) return `Please select at least one option.
|
|
1068
|
+
${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
|
|
1069
|
+
}, render() {
|
|
1070
|
+
const r = `${import_picocolors2.default.gray(o)}
|
|
1071
|
+
${b2(this.state)} ${t.message}
|
|
1072
|
+
`, i = (s, c) => {
|
|
1073
|
+
const a = this.value.includes(s.value);
|
|
1074
|
+
return c && a ? n(s, "active-selected") : a ? n(s, "selected") : n(s, c ? "active" : "inactive");
|
|
1075
|
+
};
|
|
1076
|
+
switch (this.state) {
|
|
1077
|
+
case "submit":
|
|
1078
|
+
return `${r}${import_picocolors2.default.gray(o)} ${this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
|
|
1079
|
+
case "cancel": {
|
|
1080
|
+
const s = this.options.filter(({ value: c }) => this.value.includes(c)).map((c) => n(c, "cancelled")).join(import_picocolors2.default.dim(", "));
|
|
1081
|
+
return `${r}${import_picocolors2.default.gray(o)} ${s.trim() ? `${s}
|
|
1082
|
+
${import_picocolors2.default.gray(o)}` : ""}`;
|
|
1083
|
+
}
|
|
1084
|
+
case "error": {
|
|
1085
|
+
const s = this.error.split(`
|
|
1086
|
+
`).map((c, a) => a === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(c)}` : ` ${c}`).join(`
|
|
1087
|
+
`);
|
|
1088
|
+
return `${r + import_picocolors2.default.yellow(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
1089
|
+
${import_picocolors2.default.yellow(o)} `)}
|
|
1090
|
+
${s}
|
|
1091
|
+
`;
|
|
1092
|
+
}
|
|
1093
|
+
default:
|
|
1094
|
+
return `${r}${import_picocolors2.default.cyan(o)} ${G2({ options: this.options, cursor: this.cursor, maxItems: t.maxItems, style: i }).join(`
|
|
1095
|
+
${import_picocolors2.default.cyan(o)} `)}
|
|
1096
|
+
${import_picocolors2.default.cyan(d2)}
|
|
1097
|
+
`;
|
|
1098
|
+
}
|
|
1099
|
+
} }).prompt();
|
|
1100
|
+
};
|
|
1026
1101
|
var Me = (t = "", n = "") => {
|
|
1027
1102
|
const r = `
|
|
1028
1103
|
${t}
|
|
@@ -1097,7 +1172,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
1097
1172
|
}, R2 = (m2) => m2.replace(/\.+$/, ""), O2 = (m2) => {
|
|
1098
1173
|
const h2 = (performance.now() - m2) / 1e3, w2 = Math.floor(h2 / 60), I2 = Math.floor(h2 % 60);
|
|
1099
1174
|
return w2 > 0 ? `[${w2}m ${I2}s]` : `[${I2}s]`;
|
|
1100
|
-
},
|
|
1175
|
+
}, H2 = (m2 = "") => {
|
|
1101
1176
|
a = true, s = fD(), l2 = R2(m2), g2 = performance.now(), process.stdout.write(`${import_picocolors2.default.gray(o)}
|
|
1102
1177
|
`);
|
|
1103
1178
|
let h2 = 0, w2 = 0;
|
|
@@ -1120,7 +1195,7 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
|
|
|
1120
1195
|
`) : process.stdout.write(`${w2} ${l2}
|
|
1121
1196
|
`), E(), s();
|
|
1122
1197
|
};
|
|
1123
|
-
return { start:
|
|
1198
|
+
return { start: H2, stop: N2, message: (m2 = "") => {
|
|
1124
1199
|
l2 = R2(m2 ?? l2);
|
|
1125
1200
|
} };
|
|
1126
1201
|
};
|
|
@@ -1131,23 +1206,20 @@ function createBaseProvider() {
|
|
|
1131
1206
|
name: "base",
|
|
1132
1207
|
introMessage: "Setting up CipherStash for your project...",
|
|
1133
1208
|
connectionOptions: [
|
|
1134
|
-
{ value: "drizzle", label: "Drizzle ORM"
|
|
1209
|
+
{ value: "drizzle", label: "Drizzle ORM" },
|
|
1210
|
+
{ value: "supabase-js", label: "Supabase JS Client" },
|
|
1135
1211
|
{ value: "prisma", label: "Prisma" },
|
|
1136
1212
|
{ value: "raw-sql", label: "Raw SQL / pg" }
|
|
1137
1213
|
],
|
|
1138
1214
|
getNextSteps(state) {
|
|
1139
1215
|
const steps = [
|
|
1140
|
-
"
|
|
1216
|
+
"Create a CipherStash account and get your credentials:\n https://dashboard.cipherstash.com/sign-up\n Then set: CS_WORKSPACE_CRN, CS_CLIENT_ID, CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY",
|
|
1217
|
+
"Set up your database: npx stash-forge setup"
|
|
1141
1218
|
];
|
|
1142
|
-
if (state.
|
|
1143
|
-
steps.push(
|
|
1144
|
-
} else if (state.connectionMethod === "prisma") {
|
|
1145
|
-
steps.push("Set up Prisma with @cipherstash/stack");
|
|
1219
|
+
if (state.clientFilePath) {
|
|
1220
|
+
steps.push(`Edit your encryption schema: ${state.clientFilePath}`);
|
|
1146
1221
|
}
|
|
1147
|
-
steps.push(
|
|
1148
|
-
"Define your encrypted schema",
|
|
1149
|
-
"Read the docs: https://docs.cipherstash.com"
|
|
1150
|
-
);
|
|
1222
|
+
steps.push("Read the docs: https://cipherstash.com/docs");
|
|
1151
1223
|
return steps;
|
|
1152
1224
|
}
|
|
1153
1225
|
};
|
|
@@ -1170,20 +1242,14 @@ function createSupabaseProvider() {
|
|
|
1170
1242
|
],
|
|
1171
1243
|
getNextSteps(state) {
|
|
1172
1244
|
const steps = [
|
|
1173
|
-
"
|
|
1245
|
+
"Create a CipherStash account and get your credentials:\n https://dashboard.cipherstash.com/sign-up\n Then set: CS_WORKSPACE_CRN, CS_CLIENT_ID, CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY",
|
|
1246
|
+
"Set up your database: npx stash-forge setup"
|
|
1174
1247
|
];
|
|
1175
|
-
if (state.
|
|
1176
|
-
steps.push(
|
|
1177
|
-
} else if (state.connectionMethod === "drizzle") {
|
|
1178
|
-
steps.push("Import encryptedType from @cipherstash/stack/drizzle");
|
|
1179
|
-
} else if (state.connectionMethod === "prisma") {
|
|
1180
|
-
steps.push("Set up Prisma with @cipherstash/stack");
|
|
1248
|
+
if (state.clientFilePath) {
|
|
1249
|
+
steps.push(`Edit your encryption schema: ${state.clientFilePath}`);
|
|
1181
1250
|
}
|
|
1182
1251
|
steps.push(
|
|
1183
|
-
"
|
|
1184
|
-
"Supabase guides: https://cipherstash.com/docs/supabase/encrypt-user-data",
|
|
1185
|
-
"Multi-tenant encryption: https://docs.cipherstash.com/docs/multi-tenant",
|
|
1186
|
-
"Migrating existing data: https://docs.cipherstash.com/docs/migration",
|
|
1252
|
+
"Supabase guides: https://cipherstash.com/docs/stack/encryption/supabase",
|
|
1187
1253
|
"Need help? #supabase in Discord or support@cipherstash.com"
|
|
1188
1254
|
);
|
|
1189
1255
|
return steps;
|
|
@@ -1191,46 +1257,9 @@ function createSupabaseProvider() {
|
|
|
1191
1257
|
};
|
|
1192
1258
|
}
|
|
1193
1259
|
|
|
1194
|
-
// src/bin/commands/init/
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
async function startDeviceCodeAuth() {
|
|
1199
|
-
await delay(500);
|
|
1200
|
-
return {
|
|
1201
|
-
verificationUrl: "https://cipherstash.com/activate",
|
|
1202
|
-
userCode: "ABCD-1234",
|
|
1203
|
-
deviceCode: "device_code_placeholder",
|
|
1204
|
-
expiresIn: 900,
|
|
1205
|
-
interval: 5
|
|
1206
|
-
};
|
|
1207
|
-
}
|
|
1208
|
-
async function pollForToken(deviceCode) {
|
|
1209
|
-
void deviceCode;
|
|
1210
|
-
await delay(2e3);
|
|
1211
|
-
return {
|
|
1212
|
-
accessToken: "stub_access_token",
|
|
1213
|
-
refreshToken: "stub_refresh_token",
|
|
1214
|
-
expiresIn: 3600
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
async function fetchWorkspaces(accessToken) {
|
|
1218
|
-
void accessToken;
|
|
1219
|
-
await delay(300);
|
|
1220
|
-
return [
|
|
1221
|
-
{ id: "ws_1", name: "My First Workspace" },
|
|
1222
|
-
{ id: "ws_2", name: "Production" }
|
|
1223
|
-
];
|
|
1224
|
-
}
|
|
1225
|
-
async function createWorkspace(accessToken, name) {
|
|
1226
|
-
void accessToken;
|
|
1227
|
-
await delay(500);
|
|
1228
|
-
return { id: `ws_${Date.now()}`, name };
|
|
1229
|
-
}
|
|
1230
|
-
async function installEqlExtension(databaseUrl) {
|
|
1231
|
-
void databaseUrl;
|
|
1232
|
-
await delay(1500);
|
|
1233
|
-
}
|
|
1260
|
+
// src/bin/commands/init/steps/build-schema.ts
|
|
1261
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
|
|
1262
|
+
import { dirname, resolve as resolve2 } from "node:path";
|
|
1234
1263
|
|
|
1235
1264
|
// src/bin/commands/init/types.ts
|
|
1236
1265
|
var CancelledError = class extends Error {
|
|
@@ -1239,105 +1268,362 @@ var CancelledError = class extends Error {
|
|
|
1239
1268
|
this.name = "CancelledError";
|
|
1240
1269
|
}
|
|
1241
1270
|
};
|
|
1271
|
+
function toIntegration(method) {
|
|
1272
|
+
switch (method) {
|
|
1273
|
+
case "drizzle":
|
|
1274
|
+
return "drizzle";
|
|
1275
|
+
case "supabase-js":
|
|
1276
|
+
return "supabase";
|
|
1277
|
+
case "prisma":
|
|
1278
|
+
case "raw-sql":
|
|
1279
|
+
return "postgresql";
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1242
1282
|
|
|
1243
|
-
// src/bin/commands/init/
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1283
|
+
// src/bin/commands/init/utils.ts
|
|
1284
|
+
import { existsSync } from "node:fs";
|
|
1285
|
+
import { resolve } from "node:path";
|
|
1286
|
+
function isPackageInstalled(packageName) {
|
|
1287
|
+
const modulePath = resolve(process.cwd(), "node_modules", packageName);
|
|
1288
|
+
return existsSync(modulePath);
|
|
1289
|
+
}
|
|
1290
|
+
function detectPackageManager() {
|
|
1291
|
+
const cwd = process.cwd();
|
|
1292
|
+
if (existsSync(resolve(cwd, "bun.lockb")) || existsSync(resolve(cwd, "bun.lock")))
|
|
1293
|
+
return "bun";
|
|
1294
|
+
if (existsSync(resolve(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1295
|
+
if (existsSync(resolve(cwd, "yarn.lock"))) return "yarn";
|
|
1296
|
+
return "npm";
|
|
1297
|
+
}
|
|
1298
|
+
function prodInstallCommand(pm, packageName) {
|
|
1299
|
+
switch (pm) {
|
|
1300
|
+
case "bun":
|
|
1301
|
+
return `bun add ${packageName}`;
|
|
1302
|
+
case "pnpm":
|
|
1303
|
+
return `pnpm add ${packageName}`;
|
|
1304
|
+
case "yarn":
|
|
1305
|
+
return `yarn add ${packageName}`;
|
|
1306
|
+
case "npm":
|
|
1307
|
+
return `npm install ${packageName}`;
|
|
1261
1308
|
}
|
|
1262
|
-
}
|
|
1309
|
+
}
|
|
1310
|
+
function devInstallCommand(pm, packageName) {
|
|
1311
|
+
switch (pm) {
|
|
1312
|
+
case "bun":
|
|
1313
|
+
return `bun add -D ${packageName}`;
|
|
1314
|
+
case "pnpm":
|
|
1315
|
+
return `pnpm add -D ${packageName}`;
|
|
1316
|
+
case "yarn":
|
|
1317
|
+
return `yarn add -D ${packageName}`;
|
|
1318
|
+
case "npm":
|
|
1319
|
+
return `npm install -D ${packageName}`;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function toCamelCase(str) {
|
|
1323
|
+
return str.replace(/_([a-z])/g, (_3, c) => c.toUpperCase());
|
|
1324
|
+
}
|
|
1325
|
+
function drizzleTsType(dataType) {
|
|
1326
|
+
switch (dataType) {
|
|
1327
|
+
case "number":
|
|
1328
|
+
return "number";
|
|
1329
|
+
case "boolean":
|
|
1330
|
+
return "boolean";
|
|
1331
|
+
case "date":
|
|
1332
|
+
return "Date";
|
|
1333
|
+
case "json":
|
|
1334
|
+
return "Record<string, unknown>";
|
|
1335
|
+
default:
|
|
1336
|
+
return "string";
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function generateDrizzleFromSchema(schema) {
|
|
1340
|
+
const varName = `${toCamelCase(schema.tableName)}Table`;
|
|
1341
|
+
const schemaVarName = `${toCamelCase(schema.tableName)}Schema`;
|
|
1342
|
+
const columnDefs = schema.columns.map((col) => {
|
|
1343
|
+
const opts = [];
|
|
1344
|
+
if (col.dataType !== "string") {
|
|
1345
|
+
opts.push(`dataType: '${col.dataType}'`);
|
|
1346
|
+
}
|
|
1347
|
+
if (col.searchOps.includes("equality")) {
|
|
1348
|
+
opts.push("equality: true");
|
|
1349
|
+
}
|
|
1350
|
+
if (col.searchOps.includes("orderAndRange")) {
|
|
1351
|
+
opts.push("orderAndRange: true");
|
|
1352
|
+
}
|
|
1353
|
+
if (col.searchOps.includes("freeTextSearch")) {
|
|
1354
|
+
opts.push("freeTextSearch: true");
|
|
1355
|
+
}
|
|
1356
|
+
const tsType = drizzleTsType(col.dataType);
|
|
1357
|
+
const optsStr = opts.length > 0 ? `, {
|
|
1358
|
+
${opts.join(",\n ")},
|
|
1359
|
+
}` : "";
|
|
1360
|
+
return ` ${col.name}: encryptedType<${tsType}>('${col.name}'${optsStr}),`;
|
|
1361
|
+
});
|
|
1362
|
+
return `import { pgTable, integer, timestamp } from 'drizzle-orm/pg-core'
|
|
1363
|
+
import { encryptedType, extractEncryptionSchema } from '@cipherstash/stack/drizzle'
|
|
1364
|
+
import { Encryption } from '@cipherstash/stack'
|
|
1365
|
+
|
|
1366
|
+
export const ${varName} = pgTable('${schema.tableName}', {
|
|
1367
|
+
id: integer('id').primaryKey().generatedAlwaysAsIdentity(),
|
|
1368
|
+
${columnDefs.join("\n")}
|
|
1369
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
1370
|
+
})
|
|
1263
1371
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1372
|
+
const ${schemaVarName} = extractEncryptionSchema(${varName})
|
|
1373
|
+
|
|
1374
|
+
export const encryptionClient = await Encryption({
|
|
1375
|
+
schemas: [${schemaVarName}],
|
|
1376
|
+
})
|
|
1377
|
+
`;
|
|
1267
1378
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1379
|
+
function generateSchemaFromDef(schema) {
|
|
1380
|
+
const varName = `${toCamelCase(schema.tableName)}Table`;
|
|
1381
|
+
const columnDefs = schema.columns.map((col) => {
|
|
1382
|
+
const parts = [` ${col.name}: encryptedColumn('${col.name}')`];
|
|
1383
|
+
if (col.dataType !== "string") {
|
|
1384
|
+
parts.push(`.dataType('${col.dataType}')`);
|
|
1385
|
+
}
|
|
1386
|
+
for (const op of col.searchOps) {
|
|
1387
|
+
parts.push(`.${op}()`);
|
|
1388
|
+
}
|
|
1389
|
+
return `${parts.join("\n ")},`;
|
|
1390
|
+
});
|
|
1391
|
+
return `import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema'
|
|
1392
|
+
import { Encryption } from '@cipherstash/stack'
|
|
1393
|
+
|
|
1394
|
+
export const ${varName} = encryptedTable('${schema.tableName}', {
|
|
1395
|
+
${columnDefs.join("\n")}
|
|
1396
|
+
})
|
|
1397
|
+
|
|
1398
|
+
export const encryptionClient = await Encryption({
|
|
1399
|
+
schemas: [${varName}],
|
|
1400
|
+
})
|
|
1401
|
+
`;
|
|
1402
|
+
}
|
|
1403
|
+
function generateClientFromSchema(integration, schema) {
|
|
1404
|
+
switch (integration) {
|
|
1405
|
+
case "drizzle":
|
|
1406
|
+
return generateDrizzleFromSchema(schema);
|
|
1407
|
+
case "supabase":
|
|
1408
|
+
case "postgresql":
|
|
1409
|
+
return generateSchemaFromDef(schema);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function generatePlaceholderClient(integration) {
|
|
1413
|
+
const placeholder = {
|
|
1414
|
+
tableName: "users",
|
|
1415
|
+
columns: [
|
|
1416
|
+
{
|
|
1417
|
+
name: "email",
|
|
1418
|
+
dataType: "string",
|
|
1419
|
+
searchOps: ["equality", "freeTextSearch"]
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
name: "name",
|
|
1423
|
+
dataType: "string",
|
|
1424
|
+
searchOps: ["equality", "freeTextSearch"]
|
|
1425
|
+
}
|
|
1426
|
+
]
|
|
1427
|
+
};
|
|
1428
|
+
return generateClientFromSchema(integration, placeholder);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// src/bin/commands/init/steps/build-schema.ts
|
|
1432
|
+
var DEFAULT_CLIENT_PATH = "./src/encryption/index.ts";
|
|
1433
|
+
async function addColumn(index) {
|
|
1434
|
+
const name = await he({
|
|
1435
|
+
message: `Column ${index} name:`,
|
|
1436
|
+
placeholder: index === 1 ? "email" : "name",
|
|
1437
|
+
validate(value) {
|
|
1438
|
+
if (!value || value.trim().length === 0) {
|
|
1439
|
+
return "Column name is required.";
|
|
1440
|
+
}
|
|
1441
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(value)) {
|
|
1442
|
+
return "Column name must be a valid identifier.";
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
if (pD(name)) return void 0;
|
|
1447
|
+
const dataType = await ve({
|
|
1448
|
+
message: `Data type for "${name}":`,
|
|
1449
|
+
options: [
|
|
1450
|
+
{ value: "string", label: "string", hint: "text, email, name, etc." },
|
|
1451
|
+
{ value: "number", label: "number", hint: "integer or decimal" },
|
|
1452
|
+
{ value: "boolean", label: "boolean" },
|
|
1453
|
+
{ value: "date", label: "date", hint: "Date object" },
|
|
1454
|
+
{ value: "json", label: "json", hint: "structured JSON data" }
|
|
1455
|
+
]
|
|
1456
|
+
});
|
|
1457
|
+
if (pD(dataType)) return void 0;
|
|
1458
|
+
const searchOptions = [
|
|
1459
|
+
{ value: "equality", label: "Exact match", hint: "eq, neq, in" },
|
|
1460
|
+
{
|
|
1461
|
+
value: "orderAndRange",
|
|
1462
|
+
label: "Order and range",
|
|
1463
|
+
hint: "gt, gte, lt, lte, between, sorting"
|
|
1464
|
+
}
|
|
1465
|
+
];
|
|
1466
|
+
if (dataType === "string") {
|
|
1467
|
+
searchOptions.push({
|
|
1468
|
+
value: "freeTextSearch",
|
|
1469
|
+
label: "Free-text search",
|
|
1470
|
+
hint: "like, ilike, substring matching"
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
const searchOps = await fe({
|
|
1474
|
+
message: `Search operations for "${name}":`,
|
|
1475
|
+
options: searchOptions,
|
|
1476
|
+
required: false
|
|
1477
|
+
});
|
|
1478
|
+
if (pD(searchOps)) return void 0;
|
|
1479
|
+
return { name, dataType, searchOps };
|
|
1480
|
+
}
|
|
1481
|
+
async function buildSchema() {
|
|
1482
|
+
const tableName = await he({
|
|
1483
|
+
message: "What is the name of your table?",
|
|
1484
|
+
placeholder: "users",
|
|
1485
|
+
validate(value) {
|
|
1486
|
+
if (!value || value.trim().length === 0) {
|
|
1487
|
+
return "Table name is required.";
|
|
1488
|
+
}
|
|
1489
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(value)) {
|
|
1490
|
+
return "Table name must be a valid identifier (letters, numbers, underscores).";
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
if (pD(tableName)) return void 0;
|
|
1495
|
+
const columns = [];
|
|
1496
|
+
M2.info("Add encrypted columns to your table. You can add more later.");
|
|
1497
|
+
while (true) {
|
|
1498
|
+
const column = await addColumn(columns.length + 1);
|
|
1499
|
+
if (!column) return void 0;
|
|
1500
|
+
columns.push(column);
|
|
1501
|
+
const addMore = await ye({
|
|
1502
|
+
message: "Add another encrypted column?",
|
|
1503
|
+
initialValue: false
|
|
1504
|
+
});
|
|
1505
|
+
if (pD(addMore)) return void 0;
|
|
1506
|
+
if (!addMore) break;
|
|
1507
|
+
}
|
|
1508
|
+
M2.success(
|
|
1509
|
+
`Schema defined: ${tableName} with ${columns.length} encrypted column${columns.length !== 1 ? "s" : ""}`
|
|
1510
|
+
);
|
|
1511
|
+
return { tableName, columns };
|
|
1512
|
+
}
|
|
1513
|
+
var buildSchemaStep = {
|
|
1514
|
+
id: "build-schema",
|
|
1515
|
+
name: "Build encryption schema",
|
|
1271
1516
|
async run(state, _provider) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
${maskUrl(envUrl)}`);
|
|
1276
|
-
return { ...state, databaseUrl: envUrl };
|
|
1517
|
+
if (!state.connectionMethod) {
|
|
1518
|
+
M2.warn("Skipping schema generation (no connection method selected)");
|
|
1519
|
+
return { ...state, schemaGenerated: false };
|
|
1277
1520
|
}
|
|
1278
|
-
|
|
1279
|
-
const
|
|
1280
|
-
message: "
|
|
1521
|
+
const integration = toIntegration(state.connectionMethod);
|
|
1522
|
+
const clientFilePath = await he({
|
|
1523
|
+
message: "Where should we create your encryption client?",
|
|
1524
|
+
placeholder: DEFAULT_CLIENT_PATH,
|
|
1525
|
+
defaultValue: DEFAULT_CLIENT_PATH
|
|
1526
|
+
});
|
|
1527
|
+
if (pD(clientFilePath)) throw new CancelledError();
|
|
1528
|
+
const resolvedPath = resolve2(process.cwd(), clientFilePath);
|
|
1529
|
+
if (existsSync2(resolvedPath)) {
|
|
1530
|
+
const action = await ve({
|
|
1531
|
+
message: `${clientFilePath} already exists. What would you like to do?`,
|
|
1532
|
+
options: [
|
|
1533
|
+
{
|
|
1534
|
+
value: "keep",
|
|
1535
|
+
label: "Keep existing file",
|
|
1536
|
+
hint: "skip code generation"
|
|
1537
|
+
},
|
|
1538
|
+
{ value: "overwrite", label: "Overwrite with new schema" }
|
|
1539
|
+
]
|
|
1540
|
+
});
|
|
1541
|
+
if (pD(action)) throw new CancelledError();
|
|
1542
|
+
if (action === "keep") {
|
|
1543
|
+
M2.info("Keeping existing encryption client file.");
|
|
1544
|
+
return { ...state, clientFilePath, schemaGenerated: false };
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
const schemaChoice = await ve({
|
|
1548
|
+
message: "How would you like to set up your encryption schema?",
|
|
1281
1549
|
options: [
|
|
1282
|
-
{
|
|
1283
|
-
|
|
1550
|
+
{
|
|
1551
|
+
value: "build",
|
|
1552
|
+
label: "Build schema now",
|
|
1553
|
+
hint: "interactive wizard"
|
|
1554
|
+
},
|
|
1555
|
+
{
|
|
1556
|
+
value: "placeholder",
|
|
1557
|
+
label: "Use placeholder schema",
|
|
1558
|
+
hint: "edit later"
|
|
1559
|
+
}
|
|
1284
1560
|
]
|
|
1285
1561
|
});
|
|
1286
|
-
if (pD(
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
return "Must be a PostgreSQL connection string";
|
|
1295
|
-
}
|
|
1296
|
-
});
|
|
1297
|
-
if (pD(url)) throw new CancelledError();
|
|
1298
|
-
return { ...state, databaseUrl: url };
|
|
1562
|
+
if (pD(schemaChoice)) throw new CancelledError();
|
|
1563
|
+
let fileContents;
|
|
1564
|
+
if (schemaChoice === "build") {
|
|
1565
|
+
const schema = await buildSchema();
|
|
1566
|
+
if (!schema) throw new CancelledError();
|
|
1567
|
+
fileContents = generateClientFromSchema(integration, schema);
|
|
1568
|
+
} else {
|
|
1569
|
+
fileContents = generatePlaceholderClient(integration);
|
|
1299
1570
|
}
|
|
1300
|
-
|
|
1571
|
+
const dir = dirname(resolvedPath);
|
|
1572
|
+
if (!existsSync2(dir)) {
|
|
1573
|
+
mkdirSync(dir, { recursive: true });
|
|
1574
|
+
}
|
|
1575
|
+
writeFileSync(resolvedPath, fileContents, "utf-8");
|
|
1576
|
+
M2.success(`Encryption client written to ${clientFilePath}`);
|
|
1577
|
+
return { ...state, clientFilePath, schemaGenerated: true };
|
|
1301
1578
|
}
|
|
1302
1579
|
};
|
|
1303
1580
|
|
|
1304
|
-
// src/bin/commands/init/steps/install-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1581
|
+
// src/bin/commands/init/steps/install-forge.ts
|
|
1582
|
+
import { execSync } from "node:child_process";
|
|
1583
|
+
var STACK_PACKAGE = "@cipherstash/stack";
|
|
1584
|
+
var FORGE_PACKAGE = "@cipherstash/stack-forge";
|
|
1585
|
+
async function installIfNeeded(packageName, buildCommand, depLabel) {
|
|
1586
|
+
if (isPackageInstalled(packageName)) {
|
|
1587
|
+
M2.success(`${packageName} is already installed.`);
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
const pm = detectPackageManager();
|
|
1591
|
+
const cmd = buildCommand(pm, packageName);
|
|
1592
|
+
const install = await ye({
|
|
1593
|
+
message: `Install ${packageName} as a ${depLabel} dependency? (${cmd})`
|
|
1594
|
+
});
|
|
1595
|
+
if (pD(install)) throw new CancelledError();
|
|
1596
|
+
if (!install) {
|
|
1597
|
+
M2.info(`Skipping ${packageName} installation.`);
|
|
1598
|
+
Me(
|
|
1599
|
+
`You can install it manually later:
|
|
1600
|
+
${cmd}`,
|
|
1601
|
+
"Manual Installation"
|
|
1602
|
+
);
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
const s = Y2();
|
|
1606
|
+
s.start(`Installing ${packageName}...`);
|
|
1607
|
+
try {
|
|
1608
|
+
execSync(cmd, { cwd: process.cwd(), stdio: "pipe" });
|
|
1609
|
+
s.stop(`${packageName} installed successfully`);
|
|
1610
|
+
return true;
|
|
1611
|
+
} catch (err) {
|
|
1612
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1613
|
+
s.stop(`${packageName} installation failed`);
|
|
1614
|
+
M2.error(message);
|
|
1615
|
+
Me(`You can install it manually:
|
|
1616
|
+
${cmd}`, "Manual Installation");
|
|
1617
|
+
return false;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
var installForgeStep = {
|
|
1621
|
+
id: "install-forge",
|
|
1622
|
+
name: "Install stack dependencies",
|
|
1308
1623
|
async run(state, _provider) {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
const install = await ye({
|
|
1314
|
-
message: "Install the EQL encryption extension in your database?"
|
|
1315
|
-
});
|
|
1316
|
-
if (pD(install)) throw new CancelledError();
|
|
1317
|
-
if (!install) {
|
|
1318
|
-
M2.info("Skipping EQL extension installation");
|
|
1319
|
-
Me(
|
|
1320
|
-
"You can install it manually later:\n CREATE EXTENSION IF NOT EXISTS eql_v2;\n\nOr re-run this command:\n npx @cipherstash/stack init",
|
|
1321
|
-
"Manual Installation"
|
|
1322
|
-
);
|
|
1323
|
-
return { ...state, eqlInstalled: false };
|
|
1324
|
-
}
|
|
1325
|
-
const s = Y2();
|
|
1326
|
-
s.start("Installing EQL extension...");
|
|
1327
|
-
try {
|
|
1328
|
-
await installEqlExtension(state.databaseUrl);
|
|
1329
|
-
s.stop("EQL extension installed successfully");
|
|
1330
|
-
return { ...state, eqlInstalled: true };
|
|
1331
|
-
} catch (err) {
|
|
1332
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1333
|
-
s.stop("EQL extension installation failed");
|
|
1334
|
-
M2.error(message);
|
|
1335
|
-
Me(
|
|
1336
|
-
"You can install it manually:\n CREATE EXTENSION IF NOT EXISTS eql_v2;\n\nOr install via Database.dev:\n https://database.dev/cipherstash/eql",
|
|
1337
|
-
"Manual Installation"
|
|
1338
|
-
);
|
|
1339
|
-
return { ...state, eqlInstalled: false };
|
|
1340
|
-
}
|
|
1624
|
+
const stackInstalled = await installIfNeeded(STACK_PACKAGE, prodInstallCommand, "production");
|
|
1625
|
+
const forgeInstalled = await installIfNeeded(FORGE_PACKAGE, devInstallCommand, "dev");
|
|
1626
|
+
return { ...state, forgeInstalled, stackInstalled };
|
|
1341
1627
|
}
|
|
1342
1628
|
};
|
|
1343
1629
|
|
|
@@ -1367,75 +1653,14 @@ var selectConnectionStep = {
|
|
|
1367
1653
|
}
|
|
1368
1654
|
};
|
|
1369
1655
|
|
|
1370
|
-
// src/bin/commands/init/steps/select-region.ts
|
|
1371
|
-
var REGIONS = [
|
|
1372
|
-
{ value: "ap-southeast-2", label: "Asia Pacific (Sydney)" },
|
|
1373
|
-
{ value: "us-east-1", label: "US East (N. Virginia)" },
|
|
1374
|
-
{ value: "us-west-2", label: "US West (Oregon)" },
|
|
1375
|
-
{ value: "eu-west-1", label: "Europe (Ireland)" }
|
|
1376
|
-
];
|
|
1377
|
-
var selectRegionStep = {
|
|
1378
|
-
id: "select-region",
|
|
1379
|
-
name: "Select region",
|
|
1380
|
-
async run(state, _provider) {
|
|
1381
|
-
const region = await ve({
|
|
1382
|
-
message: "Where should we create your workspace?",
|
|
1383
|
-
options: REGIONS
|
|
1384
|
-
});
|
|
1385
|
-
if (pD(region)) throw new CancelledError();
|
|
1386
|
-
return { ...state, region };
|
|
1387
|
-
}
|
|
1388
|
-
};
|
|
1389
|
-
|
|
1390
|
-
// src/bin/commands/init/steps/select-workspace.ts
|
|
1391
|
-
var selectWorkspaceStep = {
|
|
1392
|
-
id: "select-workspace",
|
|
1393
|
-
name: "Select workspace",
|
|
1394
|
-
async run(state, _provider) {
|
|
1395
|
-
const s = Y2();
|
|
1396
|
-
s.start("Loading workspaces...");
|
|
1397
|
-
const workspaces = await fetchWorkspaces(state.accessToken);
|
|
1398
|
-
s.stop("Workspaces loaded");
|
|
1399
|
-
const options = [
|
|
1400
|
-
...workspaces.map((ws2) => ({ value: ws2.id, label: ws2.name })),
|
|
1401
|
-
{ value: "__create__", label: "Create new workspace" }
|
|
1402
|
-
];
|
|
1403
|
-
const selected = await ve({
|
|
1404
|
-
message: "Select a workspace",
|
|
1405
|
-
options
|
|
1406
|
-
});
|
|
1407
|
-
if (pD(selected)) throw new CancelledError();
|
|
1408
|
-
if (selected === "__create__") {
|
|
1409
|
-
const name = await he({
|
|
1410
|
-
message: "What should we call your new workspace?",
|
|
1411
|
-
placeholder: "my-project",
|
|
1412
|
-
validate: (val) => {
|
|
1413
|
-
if (!val.trim()) return "Workspace name is required";
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
if (pD(name)) throw new CancelledError();
|
|
1417
|
-
const s2 = Y2();
|
|
1418
|
-
s2.start("Creating workspace...");
|
|
1419
|
-
const ws2 = await createWorkspace(state.accessToken, name);
|
|
1420
|
-
s2.stop(`Workspace created: ${ws2.name}`);
|
|
1421
|
-
return { ...state, workspaceId: ws2.id, workspaceName: ws2.name };
|
|
1422
|
-
}
|
|
1423
|
-
const ws = workspaces.find((w2) => w2.id === selected);
|
|
1424
|
-
return { ...state, workspaceId: ws.id, workspaceName: ws.name };
|
|
1425
|
-
}
|
|
1426
|
-
};
|
|
1427
|
-
|
|
1428
1656
|
// src/bin/commands/init/index.ts
|
|
1429
1657
|
var PROVIDER_MAP = {
|
|
1430
1658
|
supabase: createSupabaseProvider
|
|
1431
1659
|
};
|
|
1432
1660
|
var STEPS = [
|
|
1433
|
-
authenticateStep,
|
|
1434
|
-
selectWorkspaceStep,
|
|
1435
|
-
selectRegionStep,
|
|
1436
1661
|
selectConnectionStep,
|
|
1437
|
-
|
|
1438
|
-
|
|
1662
|
+
buildSchemaStep,
|
|
1663
|
+
installForgeStep,
|
|
1439
1664
|
nextStepsStep
|
|
1440
1665
|
];
|
|
1441
1666
|
function resolveProvider(flags) {
|
|
@@ -1449,9 +1674,6 @@ function resolveProvider(flags) {
|
|
|
1449
1674
|
async function initCommand(flags) {
|
|
1450
1675
|
const provider = resolveProvider(flags);
|
|
1451
1676
|
Ie("CipherStash Stack Setup");
|
|
1452
|
-
M2.warn(
|
|
1453
|
-
"This command is a prototype and a sneak peek at what's next. It doesn't perform any actual setup yet."
|
|
1454
|
-
);
|
|
1455
1677
|
M2.info(provider.introMessage);
|
|
1456
1678
|
let state = {};
|
|
1457
1679
|
try {
|
|
@@ -4966,7 +5188,7 @@ async function secretsCommand(args) {
|
|
|
4966
5188
|
|
|
4967
5189
|
// src/bin/stash.ts
|
|
4968
5190
|
(0, import_dotenv2.config)();
|
|
4969
|
-
var __dirname =
|
|
5191
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
4970
5192
|
var pkg = JSON.parse(
|
|
4971
5193
|
readFileSync(join(__dirname, "../../package.json"), "utf-8")
|
|
4972
5194
|
);
|