sandstream-kit 1.4.3 → 1.6.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/README.md +32 -0
- package/dist/check-security.d.ts +6 -0
- package/dist/check-security.js +148 -9
- package/dist/check-security.js.map +1 -1
- package/dist/cli.js +180 -9
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js.map +1 -1
- package/dist/context-lock.d.ts +8 -0
- package/dist/context-lock.js +10 -0
- package/dist/context-lock.js.map +1 -1
- package/dist/secret-backends.d.ts +3 -0
- package/dist/secret-backends.js +35 -11
- package/dist/secret-backends.js.map +1 -1
- package/dist/service-registry.d.ts +54 -0
- package/dist/service-registry.js +248 -0
- package/dist/service-registry.js.map +1 -0
- package/dist/stack-detector.js +176 -55
- package/dist/stack-detector.js.map +1 -1
- package/dist/toml-generator.d.ts +5 -0
- package/dist/toml-generator.js +100 -105
- package/dist/toml-generator.js.map +1 -1
- package/dist/vault-meta.d.ts +44 -0
- package/dist/vault-meta.js +35 -0
- package/dist/vault-meta.js.map +1 -0
- package/package.json +2 -1
package/dist/toml-generator.js
CHANGED
|
@@ -1,81 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
login: "stripe login",
|
|
4
|
-
check: "stripe config --list",
|
|
5
|
-
secrets: ["STRIPE_SECRET_KEY", "STRIPE_PUBLISHABLE_KEY", "STRIPE_WEBHOOK_SECRET"],
|
|
6
|
-
tool: "stripe",
|
|
7
|
-
},
|
|
8
|
-
supabase: {
|
|
9
|
-
login: "supabase login",
|
|
10
|
-
check: "supabase projects list",
|
|
11
|
-
secrets: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY"],
|
|
12
|
-
tool: "supabase",
|
|
13
|
-
},
|
|
14
|
-
vercel: {
|
|
15
|
-
login: "vercel login",
|
|
16
|
-
check: "vercel whoami",
|
|
17
|
-
secrets: [],
|
|
18
|
-
tool: "vercel",
|
|
19
|
-
},
|
|
20
|
-
expo: {
|
|
21
|
-
login: "eas login",
|
|
22
|
-
check: "eas whoami",
|
|
23
|
-
secrets: ["EXPO_TOKEN"],
|
|
24
|
-
tool: "eas-cli",
|
|
25
|
-
},
|
|
26
|
-
resend: {
|
|
27
|
-
login: "# resend — no CLI login; set RESEND_API_KEY in env",
|
|
28
|
-
check: "# resend — check RESEND_API_KEY is set",
|
|
29
|
-
secrets: ["RESEND_API_KEY", "RESEND_FROM_EMAIL"],
|
|
30
|
-
},
|
|
31
|
-
clerk: {
|
|
32
|
-
login: "# clerk — no CLI login; get keys from https://dashboard.clerk.com",
|
|
33
|
-
check: "# clerk — check CLERK_SECRET_KEY is set",
|
|
34
|
-
secrets: ["CLERK_SECRET_KEY", "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY"],
|
|
35
|
-
},
|
|
36
|
-
liveblocks: {
|
|
37
|
-
login: "# liveblocks — no CLI login; get keys from https://liveblocks.io/dashboard",
|
|
38
|
-
check: "# liveblocks — check LIVEBLOCKS_SECRET_KEY is set",
|
|
39
|
-
secrets: ["LIVEBLOCKS_SECRET_KEY", "NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY"],
|
|
40
|
-
},
|
|
41
|
-
trigger: {
|
|
42
|
-
login: "# trigger — no CLI login; get key from https://cloud.trigger.dev",
|
|
43
|
-
check: "# trigger — check TRIGGER_SECRET_KEY is set",
|
|
44
|
-
secrets: ["TRIGGER_SECRET_KEY"],
|
|
45
|
-
},
|
|
46
|
-
inngest: {
|
|
47
|
-
login: "# inngest — no CLI login; get keys from https://app.inngest.com",
|
|
48
|
-
check: "# inngest — check INNGEST_EVENT_KEY is set",
|
|
49
|
-
secrets: ["INNGEST_EVENT_KEY", "INNGEST_SIGNING_KEY"],
|
|
50
|
-
},
|
|
51
|
-
sentry: {
|
|
52
|
-
login: "# sentry — no CLI login; get DSN from https://sentry.io",
|
|
53
|
-
check: "# sentry — check SENTRY_DSN is set",
|
|
54
|
-
secrets: ["SENTRY_DSN", "SENTRY_ORG", "SENTRY_PROJECT", "SENTRY_AUTH_TOKEN"],
|
|
55
|
-
},
|
|
56
|
-
netlify: {
|
|
57
|
-
login: "netlify login",
|
|
58
|
-
check: "netlify status",
|
|
59
|
-
secrets: ["NETLIFY_AUTH_TOKEN", "NETLIFY_SITE_ID"],
|
|
60
|
-
tool: "netlify",
|
|
61
|
-
},
|
|
62
|
-
"cloudflare-pages": {
|
|
63
|
-
login: "wrangler login",
|
|
64
|
-
check: "wrangler whoami",
|
|
65
|
-
secrets: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
|
|
66
|
-
tool: "wrangler",
|
|
67
|
-
},
|
|
68
|
-
typeorm: {
|
|
69
|
-
login: "# typeorm — no CLI login; configure DATABASE_URL",
|
|
70
|
-
check: "# typeorm — check DATABASE_URL is set",
|
|
71
|
-
secrets: ["DATABASE_URL"],
|
|
72
|
-
},
|
|
73
|
-
mongoose: {
|
|
74
|
-
login: "# mongoose — no CLI login; configure MONGODB_URI",
|
|
75
|
-
check: "# mongoose — check MONGODB_URI is set",
|
|
76
|
-
secrets: ["MONGODB_URI"],
|
|
77
|
-
},
|
|
78
|
-
};
|
|
1
|
+
import { VAULT_META } from "./vault-meta.js";
|
|
2
|
+
import { SERVICE_BY_ID } from "./service-registry.js";
|
|
79
3
|
const FRAMEWORK_SETUP = {
|
|
80
4
|
nextjs: { install: "pnpm install", dev: "pnpm dev", verify: "pnpm build" },
|
|
81
5
|
remix: { install: "pnpm install", dev: "pnpm dev", verify: "pnpm build" },
|
|
@@ -92,6 +16,11 @@ const FRAMEWORK_SETUP = {
|
|
|
92
16
|
fiber: { install: "go mod download", dev: "go run .", verify: "go build ./..." },
|
|
93
17
|
laravel: { install: "composer install", migrate: "php artisan migrate", verify: "php artisan test" },
|
|
94
18
|
symfony: { install: "composer install", verify: "php bin/console lint:all" },
|
|
19
|
+
// native mobile
|
|
20
|
+
"react-native": { install: "pnpm install", dev: "pnpm start", verify: "pnpm tsc --noEmit" },
|
|
21
|
+
flutter: { install: "flutter pub get", dev: "flutter run", verify: "flutter analyze" },
|
|
22
|
+
ios: { install: "pod install", verify: "swift build" },
|
|
23
|
+
android: { install: "./gradlew dependencies", verify: "./gradlew build" },
|
|
95
24
|
};
|
|
96
25
|
/**
|
|
97
26
|
* Security scanners kit installs by default, keyed by mise tool ref → version.
|
|
@@ -103,6 +32,10 @@ const FRAMEWORK_SETUP = {
|
|
|
103
32
|
export const DEFAULT_SECURITY_SCANNERS = {
|
|
104
33
|
semgrep: "latest",
|
|
105
34
|
"npm:@socketsecurity/cli": "latest",
|
|
35
|
+
// trufflehog (single Go binary via aqua) → deep secret scan on by default;
|
|
36
|
+
// `kit check` resolves the `trufflehog` bin mise-first and uses it instead of
|
|
37
|
+
// the basic regex fallback.
|
|
38
|
+
"aqua:trufflesecurity/trufflehog": "latest",
|
|
106
39
|
};
|
|
107
40
|
function lines(...parts) {
|
|
108
41
|
return parts.filter(Boolean).join("\n");
|
|
@@ -120,24 +53,46 @@ function toolsSection(tools) {
|
|
|
120
53
|
.join("\n");
|
|
121
54
|
return `[tools]\n${entries}\n`;
|
|
122
55
|
}
|
|
123
|
-
function servicesSection(services
|
|
56
|
+
function servicesSection(services) {
|
|
124
57
|
const sections = [];
|
|
125
58
|
for (const svc of services) {
|
|
126
|
-
const
|
|
127
|
-
|
|
59
|
+
const def = SERVICE_BY_ID[svc];
|
|
60
|
+
// Only services with a login/check get a [services.X] block. ORM-only
|
|
61
|
+
// entries (prisma/drizzle) declare just deps+migrate, so they're skipped here.
|
|
62
|
+
if (!def?.login || !def.check)
|
|
128
63
|
continue;
|
|
129
|
-
//
|
|
130
|
-
sections.push(`[services.${svc}]\nlogin = "${
|
|
64
|
+
// Tools are merged into [tools] by generateToml; here we only emit login/check.
|
|
65
|
+
sections.push(`[services.${svc}]\nlogin = "${def.login}"\ncheck = "${def.check}"`);
|
|
131
66
|
}
|
|
132
67
|
return sections.join("\n\n");
|
|
133
68
|
}
|
|
134
|
-
|
|
69
|
+
/** Extract env keys from a `.env.example`/`.env.template` file body —
|
|
70
|
+
* `^KEY=` lines (KEY = upper/underscore/digit). Comments + blanks ignored. */
|
|
71
|
+
export function parseEnvTemplateKeys(content) {
|
|
72
|
+
const keys = [];
|
|
73
|
+
for (const line of content.split("\n")) {
|
|
74
|
+
const m = line.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
75
|
+
if (m)
|
|
76
|
+
keys.push(m[1]);
|
|
77
|
+
}
|
|
78
|
+
return keys;
|
|
79
|
+
}
|
|
80
|
+
function secretsSection(services, store = "1password", extraKeys = []) {
|
|
135
81
|
const allKeys = [];
|
|
82
|
+
const seen = new Set();
|
|
83
|
+
const add = (k) => {
|
|
84
|
+
if (!seen.has(k)) {
|
|
85
|
+
seen.add(k);
|
|
86
|
+
allKeys.push(k);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
136
89
|
for (const svc of services) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
allKeys.push(...tmpl.secrets);
|
|
90
|
+
const def = SERVICE_BY_ID[svc];
|
|
91
|
+
def?.secrets?.forEach(add);
|
|
140
92
|
}
|
|
93
|
+
// Keys from an existing .env.example the project already documents (deduped
|
|
94
|
+
// against service-template keys) — so a project's real secret contract is kept.
|
|
95
|
+
extraKeys.forEach(add);
|
|
141
96
|
if (allKeys.length === 0)
|
|
142
97
|
return "";
|
|
143
98
|
const keyLines = allKeys.map((k) => {
|
|
@@ -172,7 +127,16 @@ function secretsSection(services, store = "1password") {
|
|
|
172
127
|
}
|
|
173
128
|
return `${k} = { ${src} }`;
|
|
174
129
|
});
|
|
175
|
-
|
|
130
|
+
// For Infisical, scaffold the project binding. `environment` is the one piece
|
|
131
|
+
// we can know before login; `project_id` needs a logged-in session, so it's
|
|
132
|
+
// left commented with a pointer to `infisical init` (writes .infisical.json).
|
|
133
|
+
// Without this block the backend silently defaults to env="dev" with no
|
|
134
|
+
// project, which is rarely what the user means.
|
|
135
|
+
const bindingBlock = store === "infisical"
|
|
136
|
+
? `\n[secrets.infisical]\nenvironment = "dev"\n` +
|
|
137
|
+
`# project_id = "..." # run \`infisical login && infisical init\` to bind this repo`
|
|
138
|
+
: "";
|
|
139
|
+
return lines(`[secrets]`, `store = "${store}"`, `template = ".env.template"`, ``, `[secrets.keys]`, keyLines.join("\n"), bindingBlock || undefined);
|
|
176
140
|
}
|
|
177
141
|
function setupSection(stack) {
|
|
178
142
|
const frameworkSetup = stack.framework ? FRAMEWORK_SETUP[stack.framework] : null;
|
|
@@ -192,19 +156,25 @@ function setupSection(stack) {
|
|
|
192
156
|
installCmd = "cargo fetch";
|
|
193
157
|
else if (stack.language === "php")
|
|
194
158
|
installCmd = "composer install";
|
|
159
|
+
else if (stack.language === "dart")
|
|
160
|
+
installCmd = frameworkSetup?.install ?? "dart pub get";
|
|
161
|
+
else if (stack.language === "swift")
|
|
162
|
+
installCmd = frameworkSetup?.install ?? "swift build";
|
|
163
|
+
else if (stack.language === "kotlin")
|
|
164
|
+
installCmd = frameworkSetup?.install ?? "./gradlew build";
|
|
195
165
|
else
|
|
196
166
|
installCmd = frameworkSetup?.install ?? "npm install";
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const hasDrizzle = stack.services.includes("drizzle");
|
|
167
|
+
// First detected service that declares a migrate command wins. Registry order
|
|
168
|
+
// puts supabase before prisma before drizzle, preserving the old precedence.
|
|
200
169
|
let migrateCmd = null;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
170
|
+
for (const svc of stack.services) {
|
|
171
|
+
const m = SERVICE_BY_ID[svc]?.migrate;
|
|
172
|
+
if (m) {
|
|
173
|
+
migrateCmd = m;
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!migrateCmd && frameworkSetup?.migrate)
|
|
208
178
|
migrateCmd = frameworkSetup.migrate;
|
|
209
179
|
const verifyCmd = frameworkSetup?.verify ?? null;
|
|
210
180
|
const parts = [`[setup]`, `install = "${installCmd}"`];
|
|
@@ -221,25 +191,50 @@ export function generateToml(stack, options = {}) {
|
|
|
221
191
|
// Merge service tools into tools map
|
|
222
192
|
const tools = { ...stack.tools };
|
|
223
193
|
for (const svc of stack.services) {
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
tools[
|
|
194
|
+
const def = SERVICE_BY_ID[svc];
|
|
195
|
+
if (def?.tool && !tools[def.tool]) {
|
|
196
|
+
tools[def.tool] = "latest";
|
|
227
197
|
}
|
|
228
198
|
}
|
|
229
|
-
//
|
|
199
|
+
// Universal security scanners — mise-provisioned and on by default, so
|
|
230
200
|
// `kit check` runs them out of the box (kit orchestrates scanners; it
|
|
231
201
|
// shouldn't just warn they're missing). semgrep = SAST (your code);
|
|
232
|
-
// socket =
|
|
202
|
+
// socket = deps; trufflehog = secrets. Remove from [tools] to opt out.
|
|
233
203
|
for (const [tool, ref] of Object.entries(DEFAULT_SECURITY_SCANNERS)) {
|
|
234
204
|
if (!tools[tool])
|
|
235
205
|
tools[tool] = ref;
|
|
236
206
|
}
|
|
207
|
+
// Conditional scanners — only where they apply, to avoid noise/redundancy:
|
|
208
|
+
// - trivy: container/IaC, only when a Dockerfile is present (caller-detected).
|
|
209
|
+
// - pip-audit: Python dep CVEs.
|
|
210
|
+
// - osv-scanner: dep CVEs for ecosystems kit has no dedicated scanner for
|
|
211
|
+
// (go/rust/php/…). Skipped for node (npm audit) and python (pip-audit) to
|
|
212
|
+
// avoid duplicating their coverage.
|
|
213
|
+
if (options.hasDockerfile && !tools["aqua:aquasecurity/trivy"]) {
|
|
214
|
+
tools["aqua:aquasecurity/trivy"] = "latest";
|
|
215
|
+
}
|
|
216
|
+
if (stack.language === "python" && !tools["pipx:pip-audit"]) {
|
|
217
|
+
tools["pipx:pip-audit"] = "latest";
|
|
218
|
+
}
|
|
219
|
+
const hasEcosystemScanner = ["typescript", "javascript", "python"].includes(stack.language);
|
|
220
|
+
if (!hasEcosystemScanner && !tools["aqua:google/osv-scanner"]) {
|
|
221
|
+
tools["aqua:google/osv-scanner"] = "latest";
|
|
222
|
+
}
|
|
223
|
+
// Provision the chosen vault's CLI so `kit setup` installs it like any other
|
|
224
|
+
// tool. Choosing a vault used to record `store = "..."` and nothing else,
|
|
225
|
+
// leaving the CLI absent and `kit secrets` failing key-by-key — fix that by
|
|
226
|
+
// wiring the CLI in here. Cloud secret managers (no `miseTool`) ship their CLI
|
|
227
|
+
// through the cloud env, so they're guided at login but not provisioned.
|
|
228
|
+
const vaultTool = options.secretsStore && VAULT_META[options.secretsStore]?.miseTool;
|
|
229
|
+
if (vaultTool && !tools[vaultTool]) {
|
|
230
|
+
tools[vaultTool] = "latest";
|
|
231
|
+
}
|
|
237
232
|
const header = lines(`# .kit.toml — generated by kit init`, stack.framework
|
|
238
233
|
? `# Detected: ${stack.language} / ${stack.framework}${stack.services.length ? ` + ${stack.services.join(", ")}` : ""}`
|
|
239
234
|
: `# Detected: ${stack.language}${stack.services.length ? ` + ${stack.services.join(", ")}` : ""}`, `# Includes default security scanners (semgrep, socket) installed via mise.`, ``);
|
|
240
235
|
const toolsSec = toolsSection(tools);
|
|
241
|
-
const servicesSec = servicesSection(stack.services
|
|
242
|
-
const secretsSec = secretsSection(stack.services, options.secretsStore ?? "1password");
|
|
236
|
+
const servicesSec = servicesSection(stack.services);
|
|
237
|
+
const secretsSec = secretsSection(stack.services, options.secretsStore ?? "1password", options.extraSecretKeys ?? []);
|
|
243
238
|
const setupSec = setupSection(stack);
|
|
244
239
|
const parts = [header, toolsSec, servicesSec, secretsSec, setupSec].filter((s) => s.trim().length > 0);
|
|
245
240
|
return parts.join("\n") + "\n";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toml-generator.js","sourceRoot":"","sources":["../src/toml-generator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"toml-generator.js","sourceRoot":"","sources":["../src/toml-generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { SecretsStore } from "./toml-generator.js";
|
|
2
|
+
/**
|
|
3
|
+
* Single source of truth for every secret backend kit can wire up.
|
|
4
|
+
*
|
|
5
|
+
* The point of this file is to close a silent dead-end: choosing a vault at
|
|
6
|
+
* `kit init` used to record `store = "<vault>"` and nothing else — the CLI was
|
|
7
|
+
* never installed, no login was guided, and `kit secrets` just failed key by
|
|
8
|
+
* key with "CLI not available". Three consumers now read from here so the choice
|
|
9
|
+
* is actually provisioned end-to-end:
|
|
10
|
+
*
|
|
11
|
+
* - `toml-generator` adds {@link VaultMeta.miseTool} to `[tools]` so
|
|
12
|
+
* `kit setup` installs the vault CLI like any other tool.
|
|
13
|
+
* - `cli` (secrets / setup) uses {@link VaultMeta.loginCmd} / {@link initCmd}
|
|
14
|
+
* to print a LOUD, actionable "chosen but not authenticated yet" hint instead
|
|
15
|
+
* of a wall of silent per-key failures.
|
|
16
|
+
* - the secret backends resolve that same CLI mise-first (see `execCli`), so
|
|
17
|
+
* the binary kit just installed is actually found.
|
|
18
|
+
*
|
|
19
|
+
* `miseTool` is set only for vaults whose CLI mise can install. Cloud secret
|
|
20
|
+
* managers (aws/gcp/azure) ship their CLI through the cloud environment / IAM,
|
|
21
|
+
* not mise — kit guides their login but does not try to provision the binary.
|
|
22
|
+
*/
|
|
23
|
+
export interface VaultMeta {
|
|
24
|
+
label: string;
|
|
25
|
+
/** mise registry key kit adds to `[tools]` so `kit setup` installs the CLI.
|
|
26
|
+
* Absent ⇒ kit does not provision this backend's CLI (cloud-managed). */
|
|
27
|
+
miseTool?: string;
|
|
28
|
+
/** How the user authenticates. This stays the user's own account action —
|
|
29
|
+
* kit guides it, never runs it. */
|
|
30
|
+
loginCmd?: string;
|
|
31
|
+
/** Optional repo-binding step run after login (e.g. `infisical init` writes
|
|
32
|
+
* `.infisical.json`, binding this checkout to a project/environment). */
|
|
33
|
+
initCmd?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const VAULT_META: Record<Exclude<SecretsStore, "env">, VaultMeta>;
|
|
36
|
+
/** Vault metadata for a configured `store`, or null for `env` / unknown. */
|
|
37
|
+
export declare function vaultMeta(store: string | undefined): VaultMeta | null;
|
|
38
|
+
/**
|
|
39
|
+
* Detect a secret backend a brownfield repo is already bound to, from its
|
|
40
|
+
* marker files, so `kit init` can pre-select the right store instead of
|
|
41
|
+
* defaulting to 1Password (and hardcoding the wrong one in `--yes` runs).
|
|
42
|
+
* Returns null when nothing recognizable is present.
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectSecretStore(fileExists: (relPath: string) => Promise<boolean>): Promise<SecretsStore | null>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const VAULT_META = {
|
|
2
|
+
"1password": { label: "1Password", miseTool: "1password", loginCmd: "op signin" },
|
|
3
|
+
infisical: {
|
|
4
|
+
label: "Infisical",
|
|
5
|
+
miseTool: "infisical",
|
|
6
|
+
loginCmd: "infisical login",
|
|
7
|
+
initCmd: "infisical init",
|
|
8
|
+
},
|
|
9
|
+
doppler: { label: "Doppler", miseTool: "doppler", loginCmd: "doppler login", initCmd: "doppler setup" },
|
|
10
|
+
bitwarden: { label: "Bitwarden", miseTool: "bitwarden", loginCmd: "bw login && bw unlock" },
|
|
11
|
+
vault: { label: "HashiCorp Vault", miseTool: "vault", loginCmd: "vault login" },
|
|
12
|
+
"aws-sm": { label: "AWS Secrets Manager", loginCmd: "aws configure (or assume an IAM role)" },
|
|
13
|
+
"gcp-sm": { label: "GCP Secret Manager", loginCmd: "gcloud auth login" },
|
|
14
|
+
"azure-kv": { label: "Azure Key Vault", loginCmd: "az login" },
|
|
15
|
+
};
|
|
16
|
+
/** Vault metadata for a configured `store`, or null for `env` / unknown. */
|
|
17
|
+
export function vaultMeta(store) {
|
|
18
|
+
if (!store || store === "env")
|
|
19
|
+
return null;
|
|
20
|
+
return VAULT_META[store] ?? null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect a secret backend a brownfield repo is already bound to, from its
|
|
24
|
+
* marker files, so `kit init` can pre-select the right store instead of
|
|
25
|
+
* defaulting to 1Password (and hardcoding the wrong one in `--yes` runs).
|
|
26
|
+
* Returns null when nothing recognizable is present.
|
|
27
|
+
*/
|
|
28
|
+
export async function detectSecretStore(fileExists) {
|
|
29
|
+
if (await fileExists(".infisical.json"))
|
|
30
|
+
return "infisical";
|
|
31
|
+
if ((await fileExists("doppler.yaml")) || (await fileExists(".doppler.yaml")))
|
|
32
|
+
return "doppler";
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=vault-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-meta.js","sourceRoot":"","sources":["../src/vault-meta.ts"],"names":[],"mappings":"AAoCA,MAAM,CAAC,MAAM,UAAU,GAAoD;IACzE,WAAW,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE;IACjF,SAAS,EAAE;QACT,KAAK,EAAE,WAAW;QAClB,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,iBAAiB;QAC3B,OAAO,EAAE,gBAAgB;KAC1B;IACD,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe,EAAE;IACvG,SAAS,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,uBAAuB,EAAE;IAC3F,KAAK,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE;IAC/E,QAAQ,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,QAAQ,EAAE,wCAAwC,EAAE;IAC9F,QAAQ,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,mBAAmB,EAAE;IACxE,UAAU,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAE;CAC/D,CAAC;AAEF,4EAA4E;AAC5E,MAAM,UAAU,SAAS,CAAC,KAAyB;IACjD,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAQ,UAAwC,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,UAAiD;IAEjD,IAAI,MAAM,UAAU,CAAC,iBAAiB,CAAC;QAAE,OAAO,WAAW,CAAC;IAC5D,IAAI,CAAC,MAAM,UAAU,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,UAAU,CAAC,eAAe,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAChG,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sandstream-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "developer kit. zero LLM, local-first, multi-vault. one command from git clone to working dev environment.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"funding": "https://buymeacoffee.com/sandstream",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"workspaces": [
|
|
8
9
|
"packages/*"
|