merge: wave 2 executor (01-02) into main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a5b8fcf9d3
commit
aa1a78d7d9
1081 files changed
+445341
No files matched your search
@@ -0,0 +1,157 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { DatabaseConfig } from "../config/schema.js";
|
||||
import {
|
||||
resolveDefaultBackupDir,
|
||||
resolveDefaultEmbeddedPostgresDir,
|
||||
resolvePaperclipInstanceId,
|
||||
} from "../config/home.js";
|
||||
|
||||
export async function promptDatabase(current?: DatabaseConfig): Promise<DatabaseConfig> {
|
||||
const instanceId = resolvePaperclipInstanceId();
|
||||
const defaultEmbeddedDir = resolveDefaultEmbeddedPostgresDir(instanceId);
|
||||
const defaultBackupDir = resolveDefaultBackupDir(instanceId);
|
||||
const base: DatabaseConfig = current ?? {
|
||||
mode: "embedded-postgres",
|
||||
embeddedPostgresDataDir: defaultEmbeddedDir,
|
||||
embeddedPostgresPort: 54329,
|
||||
backup: {
|
||||
enabled: true,
|
||||
intervalMinutes: 60,
|
||||
retentionDays: 30,
|
||||
dir: defaultBackupDir,
|
||||
},
|
||||
};
|
||||
|
||||
const mode = await p.select({
|
||||
message: "Database mode",
|
||||
options: [
|
||||
{ value: "embedded-postgres" as const, label: "Embedded PostgreSQL (managed locally)", hint: "recommended" },
|
||||
{ value: "postgres" as const, label: "PostgreSQL (external server)" },
|
||||
],
|
||||
initialValue: base.mode,
|
||||
});
|
||||
|
||||
if (p.isCancel(mode)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let connectionString: string | undefined = base.connectionString;
|
||||
let embeddedPostgresDataDir = base.embeddedPostgresDataDir || defaultEmbeddedDir;
|
||||
let embeddedPostgresPort = base.embeddedPostgresPort || 54329;
|
||||
|
||||
if (mode === "postgres") {
|
||||
const value = await p.text({
|
||||
message: "PostgreSQL connection string",
|
||||
defaultValue: base.connectionString ?? "",
|
||||
placeholder: "postgres://user:pass@localhost:5432/paperclip",
|
||||
validate: (val) => {
|
||||
if (!val) return "Connection string is required for PostgreSQL mode";
|
||||
if (!val.startsWith("postgres")) return "Must be a postgres:// or postgresql:// URL";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(value)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
connectionString = value;
|
||||
} else {
|
||||
const dataDir = await p.text({
|
||||
message: "Embedded PostgreSQL data directory",
|
||||
defaultValue: base.embeddedPostgresDataDir || defaultEmbeddedDir,
|
||||
placeholder: defaultEmbeddedDir,
|
||||
});
|
||||
|
||||
if (p.isCancel(dataDir)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
embeddedPostgresDataDir = dataDir || defaultEmbeddedDir;
|
||||
|
||||
const portValue = await p.text({
|
||||
message: "Embedded PostgreSQL port",
|
||||
defaultValue: String(base.embeddedPostgresPort || 54329),
|
||||
placeholder: "54329",
|
||||
validate: (val) => {
|
||||
const n = Number(val);
|
||||
if (!Number.isInteger(n) || n < 1 || n > 65535) return "Port must be an integer between 1 and 65535";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(portValue)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
embeddedPostgresPort = Number(portValue || "54329");
|
||||
connectionString = undefined;
|
||||
}
|
||||
|
||||
const backupEnabled = await p.confirm({
|
||||
message: "Enable automatic database backups?",
|
||||
initialValue: base.backup.enabled,
|
||||
});
|
||||
if (p.isCancel(backupEnabled)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const backupDirInput = await p.text({
|
||||
message: "Backup directory",
|
||||
defaultValue: base.backup.dir || defaultBackupDir,
|
||||
placeholder: defaultBackupDir,
|
||||
validate: (val) => (!val || val.trim().length === 0 ? "Backup directory is required" : undefined),
|
||||
});
|
||||
if (p.isCancel(backupDirInput)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const backupIntervalInput = await p.text({
|
||||
message: "Backup interval (minutes)",
|
||||
defaultValue: String(base.backup.intervalMinutes || 60),
|
||||
placeholder: "60",
|
||||
validate: (val) => {
|
||||
const n = Number(val);
|
||||
if (!Number.isInteger(n) || n < 1) return "Interval must be a positive integer";
|
||||
if (n > 10080) return "Interval must be 10080 minutes (7 days) or less";
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
if (p.isCancel(backupIntervalInput)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const backupRetentionInput = await p.text({
|
||||
message: "Backup retention (days)",
|
||||
defaultValue: String(base.backup.retentionDays || 30),
|
||||
placeholder: "30",
|
||||
validate: (val) => {
|
||||
const n = Number(val);
|
||||
if (!Number.isInteger(n) || n < 1) return "Retention must be a positive integer";
|
||||
if (n > 3650) return "Retention must be 3650 days or less";
|
||||
return undefined;
|
||||
},
|
||||
});
|
||||
if (p.isCancel(backupRetentionInput)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return {
|
||||
mode,
|
||||
connectionString,
|
||||
embeddedPostgresDataDir,
|
||||
embeddedPostgresPort,
|
||||
backup: {
|
||||
enabled: backupEnabled,
|
||||
intervalMinutes: Number(backupIntervalInput || "60"),
|
||||
retentionDays: Number(backupRetentionInput || "30"),
|
||||
dir: backupDirInput || defaultBackupDir,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { LlmConfig } from "../config/schema.js";
|
||||
|
||||
export async function promptLlm(): Promise<LlmConfig | undefined> {
|
||||
const configureLlm = await p.confirm({
|
||||
message: "Configure an LLM provider now?",
|
||||
initialValue: false,
|
||||
});
|
||||
|
||||
if (p.isCancel(configureLlm)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!configureLlm) return undefined;
|
||||
|
||||
const provider = await p.select({
|
||||
message: "LLM provider",
|
||||
options: [
|
||||
{ value: "claude" as const, label: "Claude (Anthropic)" },
|
||||
{ value: "openai" as const, label: "OpenAI" },
|
||||
],
|
||||
});
|
||||
|
||||
if (p.isCancel(provider)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const apiKey = await p.password({
|
||||
message: `${provider === "claude" ? "Anthropic" : "OpenAI"} API key`,
|
||||
validate: (val) => {
|
||||
if (!val) return "API key is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(apiKey)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return { provider, apiKey };
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { LoggingConfig } from "../config/schema.js";
|
||||
import { resolveDefaultLogsDir, resolvePaperclipInstanceId } from "../config/home.js";
|
||||
|
||||
export async function promptLogging(): Promise<LoggingConfig> {
|
||||
const defaultLogDir = resolveDefaultLogsDir(resolvePaperclipInstanceId());
|
||||
const mode = await p.select({
|
||||
message: "Logging mode",
|
||||
options: [
|
||||
{ value: "file" as const, label: "File-based logging", hint: "recommended" },
|
||||
{ value: "cloud" as const, label: "Cloud logging", hint: "coming soon" },
|
||||
],
|
||||
});
|
||||
|
||||
if (p.isCancel(mode)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (mode === "file") {
|
||||
const logDir = await p.text({
|
||||
message: "Log directory",
|
||||
defaultValue: defaultLogDir,
|
||||
placeholder: defaultLogDir,
|
||||
});
|
||||
|
||||
if (p.isCancel(logDir)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return { mode: "file", logDir: logDir || defaultLogDir };
|
||||
}
|
||||
|
||||
p.note("Cloud logging is coming soon. Using file-based logging for now.");
|
||||
return { mode: "file", logDir: defaultLogDir };
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { SecretProvider } from "@paperclipai/shared";
|
||||
import type { SecretsConfig } from "../config/schema.js";
|
||||
import { resolveDefaultSecretsKeyFilePath, resolvePaperclipInstanceId } from "../config/home.js";
|
||||
|
||||
function defaultKeyFilePath(): string {
|
||||
return resolveDefaultSecretsKeyFilePath(resolvePaperclipInstanceId());
|
||||
}
|
||||
|
||||
export function defaultSecretsConfig(): SecretsConfig {
|
||||
const keyFilePath = defaultKeyFilePath();
|
||||
return {
|
||||
provider: "local_encrypted",
|
||||
strictMode: false,
|
||||
localEncrypted: {
|
||||
keyFilePath,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function promptSecrets(current?: SecretsConfig): Promise<SecretsConfig> {
|
||||
const base = current ?? defaultSecretsConfig();
|
||||
|
||||
const provider = await p.select({
|
||||
message: "Secrets provider",
|
||||
options: [
|
||||
{
|
||||
value: "local_encrypted" as const,
|
||||
label: "Local encrypted (recommended)",
|
||||
hint: "best for single-developer installs",
|
||||
},
|
||||
{
|
||||
value: "aws_secrets_manager" as const,
|
||||
label: "AWS Secrets Manager",
|
||||
hint: "requires external adapter integration",
|
||||
},
|
||||
{
|
||||
value: "gcp_secret_manager" as const,
|
||||
label: "GCP Secret Manager",
|
||||
hint: "requires external adapter integration",
|
||||
},
|
||||
{
|
||||
value: "vault" as const,
|
||||
label: "HashiCorp Vault",
|
||||
hint: "requires external adapter integration",
|
||||
},
|
||||
],
|
||||
initialValue: base.provider,
|
||||
});
|
||||
|
||||
if (p.isCancel(provider)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const strictMode = await p.confirm({
|
||||
message: "Require secret refs for sensitive env vars?",
|
||||
initialValue: base.strictMode,
|
||||
});
|
||||
|
||||
if (p.isCancel(strictMode)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const fallbackDefault = defaultKeyFilePath();
|
||||
let keyFilePath = base.localEncrypted.keyFilePath || fallbackDefault;
|
||||
if (provider === "local_encrypted") {
|
||||
const keyPath = await p.text({
|
||||
message: "Local encrypted key file path",
|
||||
defaultValue: keyFilePath,
|
||||
placeholder: fallbackDefault,
|
||||
validate: (value) => {
|
||||
if (!value || value.trim().length === 0) return "Key file path is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(keyPath)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
keyFilePath = keyPath.trim();
|
||||
}
|
||||
|
||||
if (provider !== "local_encrypted") {
|
||||
p.note(
|
||||
`${provider} is not fully wired in this build yet. Keep local_encrypted unless you are actively implementing that adapter.`,
|
||||
"Heads up",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
provider: provider as SecretProvider,
|
||||
strictMode,
|
||||
localEncrypted: {
|
||||
keyFilePath,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { AuthConfig, ServerConfig } from "../config/schema.js";
|
||||
import { parseHostnameCsv } from "../config/hostnames.js";
|
||||
|
||||
export async function promptServer(opts?: {
|
||||
currentServer?: Partial<ServerConfig>;
|
||||
currentAuth?: Partial<AuthConfig>;
|
||||
}): Promise<{ server: ServerConfig; auth: AuthConfig }> {
|
||||
const currentServer = opts?.currentServer;
|
||||
const currentAuth = opts?.currentAuth;
|
||||
|
||||
const deploymentModeSelection = await p.select({
|
||||
message: "Deployment mode",
|
||||
options: [
|
||||
{
|
||||
value: "local_trusted",
|
||||
label: "Local trusted",
|
||||
hint: "Easiest for local setup (no login, localhost-only)",
|
||||
},
|
||||
{
|
||||
value: "authenticated",
|
||||
label: "Authenticated",
|
||||
hint: "Login required; use for private network or public hosting",
|
||||
},
|
||||
],
|
||||
initialValue: currentServer?.deploymentMode ?? "local_trusted",
|
||||
});
|
||||
|
||||
if (p.isCancel(deploymentModeSelection)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
const deploymentMode = deploymentModeSelection as ServerConfig["deploymentMode"];
|
||||
|
||||
let exposure: ServerConfig["exposure"] = "private";
|
||||
if (deploymentMode === "authenticated") {
|
||||
const exposureSelection = await p.select({
|
||||
message: "Exposure profile",
|
||||
options: [
|
||||
{
|
||||
value: "private",
|
||||
label: "Private network",
|
||||
hint: "Private access (for example Tailscale), lower setup friction",
|
||||
},
|
||||
{
|
||||
value: "public",
|
||||
label: "Public internet",
|
||||
hint: "Internet-facing deployment with stricter requirements",
|
||||
},
|
||||
],
|
||||
initialValue: currentServer?.exposure ?? "private",
|
||||
});
|
||||
if (p.isCancel(exposureSelection)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
exposure = exposureSelection as ServerConfig["exposure"];
|
||||
}
|
||||
|
||||
const hostDefault = deploymentMode === "local_trusted" ? "127.0.0.1" : "0.0.0.0";
|
||||
const hostStr = await p.text({
|
||||
message: "Bind host",
|
||||
defaultValue: currentServer?.host ?? hostDefault,
|
||||
placeholder: hostDefault,
|
||||
validate: (val) => {
|
||||
if (!val.trim()) return "Host is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(hostStr)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const portStr = await p.text({
|
||||
message: "Server port",
|
||||
defaultValue: String(currentServer?.port ?? 3100),
|
||||
placeholder: "3100",
|
||||
validate: (val) => {
|
||||
const n = Number(val);
|
||||
if (isNaN(n) || n < 1 || n > 65535 || !Number.isInteger(n)) {
|
||||
return "Must be an integer between 1 and 65535";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(portStr)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let allowedHostnames: string[] = [];
|
||||
if (deploymentMode === "authenticated" && exposure === "private") {
|
||||
const allowedHostnamesInput = await p.text({
|
||||
message: "Allowed hostnames (comma-separated, optional)",
|
||||
defaultValue: (currentServer?.allowedHostnames ?? []).join(", "),
|
||||
placeholder: "dotta-macbook-pro, your-host.tailnet.ts.net",
|
||||
validate: (val) => {
|
||||
try {
|
||||
parseHostnameCsv(val);
|
||||
return;
|
||||
} catch (err) {
|
||||
return err instanceof Error ? err.message : "Invalid hostname list";
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(allowedHostnamesInput)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
allowedHostnames = parseHostnameCsv(allowedHostnamesInput);
|
||||
}
|
||||
|
||||
const port = Number(portStr) || 3100;
|
||||
let auth: AuthConfig = { baseUrlMode: "auto", disableSignUp: false };
|
||||
if (deploymentMode === "authenticated" && exposure === "public") {
|
||||
const urlInput = await p.text({
|
||||
message: "Public base URL",
|
||||
defaultValue: currentAuth?.publicBaseUrl ?? "",
|
||||
placeholder: "https://paperclip.example.com",
|
||||
validate: (val) => {
|
||||
const candidate = val.trim();
|
||||
if (!candidate) return "Public base URL is required for public exposure";
|
||||
try {
|
||||
const url = new URL(candidate);
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
||||
return "URL must start with http:// or https://";
|
||||
}
|
||||
return;
|
||||
} catch {
|
||||
return "Enter a valid URL";
|
||||
}
|
||||
},
|
||||
});
|
||||
if (p.isCancel(urlInput)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
auth = {
|
||||
baseUrlMode: "explicit",
|
||||
disableSignUp: false,
|
||||
publicBaseUrl: urlInput.trim().replace(/\/+$/, ""),
|
||||
};
|
||||
} else if (currentAuth?.baseUrlMode === "explicit" && currentAuth.publicBaseUrl) {
|
||||
auth = {
|
||||
baseUrlMode: "explicit",
|
||||
disableSignUp: false,
|
||||
publicBaseUrl: currentAuth.publicBaseUrl,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
server: {
|
||||
deploymentMode,
|
||||
exposure,
|
||||
host: hostStr.trim(),
|
||||
port,
|
||||
allowedHostnames,
|
||||
serveUi: currentServer?.serveUi ?? true,
|
||||
},
|
||||
auth,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import * as p from "@clack/prompts";
|
||||
import type { StorageConfig } from "../config/schema.js";
|
||||
import { resolveDefaultStorageDir, resolvePaperclipInstanceId } from "../config/home.js";
|
||||
|
||||
function defaultStorageBaseDir(): string {
|
||||
return resolveDefaultStorageDir(resolvePaperclipInstanceId());
|
||||
}
|
||||
|
||||
export function defaultStorageConfig(): StorageConfig {
|
||||
return {
|
||||
provider: "local_disk",
|
||||
localDisk: {
|
||||
baseDir: defaultStorageBaseDir(),
|
||||
},
|
||||
s3: {
|
||||
bucket: "paperclip",
|
||||
region: "us-east-1",
|
||||
endpoint: undefined,
|
||||
prefix: "",
|
||||
forcePathStyle: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function promptStorage(current?: StorageConfig): Promise<StorageConfig> {
|
||||
const base = current ?? defaultStorageConfig();
|
||||
|
||||
const provider = await p.select({
|
||||
message: "Storage provider",
|
||||
options: [
|
||||
{
|
||||
value: "local_disk" as const,
|
||||
label: "Local disk (recommended)",
|
||||
hint: "best for single-user local deployments",
|
||||
},
|
||||
{
|
||||
value: "s3" as const,
|
||||
label: "S3 compatible",
|
||||
hint: "for cloud/object storage backends",
|
||||
},
|
||||
],
|
||||
initialValue: base.provider,
|
||||
});
|
||||
|
||||
if (p.isCancel(provider)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (provider === "local_disk") {
|
||||
const baseDir = await p.text({
|
||||
message: "Local storage base directory",
|
||||
defaultValue: base.localDisk.baseDir || defaultStorageBaseDir(),
|
||||
placeholder: defaultStorageBaseDir(),
|
||||
validate: (value) => {
|
||||
if (!value || value.trim().length === 0) return "Storage base directory is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(baseDir)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return {
|
||||
provider: "local_disk",
|
||||
localDisk: {
|
||||
baseDir: baseDir.trim(),
|
||||
},
|
||||
s3: base.s3,
|
||||
};
|
||||
}
|
||||
|
||||
const bucket = await p.text({
|
||||
message: "S3 bucket",
|
||||
defaultValue: base.s3.bucket || "paperclip",
|
||||
placeholder: "paperclip",
|
||||
validate: (value) => {
|
||||
if (!value || value.trim().length === 0) return "Bucket is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(bucket)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const region = await p.text({
|
||||
message: "S3 region",
|
||||
defaultValue: base.s3.region || "us-east-1",
|
||||
placeholder: "us-east-1",
|
||||
validate: (value) => {
|
||||
if (!value || value.trim().length === 0) return "Region is required";
|
||||
},
|
||||
});
|
||||
|
||||
if (p.isCancel(region)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const endpoint = await p.text({
|
||||
message: "S3 endpoint (optional for compatible backends)",
|
||||
defaultValue: base.s3.endpoint ?? "",
|
||||
placeholder: "https://s3.amazonaws.com",
|
||||
});
|
||||
|
||||
if (p.isCancel(endpoint)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const prefix = await p.text({
|
||||
message: "Object key prefix (optional)",
|
||||
defaultValue: base.s3.prefix ?? "",
|
||||
placeholder: "paperclip/",
|
||||
});
|
||||
|
||||
if (p.isCancel(prefix)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const forcePathStyle = await p.confirm({
|
||||
message: "Use S3 path-style URLs?",
|
||||
initialValue: base.s3.forcePathStyle ?? false,
|
||||
});
|
||||
|
||||
if (p.isCancel(forcePathStyle)) {
|
||||
p.cancel("Setup cancelled.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
return {
|
||||
provider: "s3",
|
||||
localDisk: base.localDisk,
|
||||
s3: {
|
||||
bucket: bucket.trim(),
|
||||
region: region.trim(),
|
||||
endpoint: endpoint.trim() || undefined,
|
||||
prefix: prefix.trim(),
|
||||
forcePathStyle,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user