sync-cf-secrets 0.1.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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/copy.d.ts +9 -0
- package/dist/commands/copy.js +84 -0
- package/dist/commands/copy.js.map +1 -0
- package/dist/commands/diff.d.ts +7 -0
- package/dist/commands/diff.js +63 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +128 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +5 -0
- package/dist/commands/list.js +21 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/pull.d.ts +8 -0
- package/dist/commands/pull.js +42 -0
- package/dist/commands/pull.js.map +1 -0
- package/dist/commands/push.d.ts +8 -0
- package/dist/commands/push.js +68 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +136 -0
- package/dist/config.js.map +1 -0
- package/dist/providers/bitwarden.d.ts +10 -0
- package/dist/providers/bitwarden.js +98 -0
- package/dist/providers/bitwarden.js.map +1 -0
- package/dist/providers/index.d.ts +14 -0
- package/dist/providers/index.js +47 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/onepassword.d.ts +15 -0
- package/dist/providers/onepassword.js +84 -0
- package/dist/providers/onepassword.js.map +1 -0
- package/dist/providers/types.d.ts +25 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +29 -0
- package/dist/utils.js.map +1 -0
- package/dist/wrangler.d.ts +19 -0
- package/dist/wrangler.js +87 -0
- package/dist/wrangler.js.map +1 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jason
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# sync-cf-secrets
|
|
2
|
+
|
|
3
|
+
Sync secrets from your password manager to Cloudflare Workers. Supports 1Password and Bitwarden with a pluggable provider architecture.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Managing Cloudflare Workers secrets is tedious — tokens from third parties are often single-use, so they end up in your password manager, then need to be manually pushed via `wrangler secret put` for each environment. This tool automates that.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g sync-cf-secrets
|
|
13
|
+
# or as a devDependency
|
|
14
|
+
npm install -D sync-cf-secrets
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Requires Node.js 18.3+ and [wrangler](https://developers.cloudflare.com/workers/wrangler/) installed.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Bootstrap: create password manager items for all environments
|
|
23
|
+
sync-cf-secrets init
|
|
24
|
+
|
|
25
|
+
# 2. Copy local secrets to staging (most values are the same)
|
|
26
|
+
sync-cf-secrets copy local staging
|
|
27
|
+
|
|
28
|
+
# 3. Edit staging item in your password manager — change any values that differ
|
|
29
|
+
|
|
30
|
+
# 4. Push secrets to Cloudflare
|
|
31
|
+
sync-cf-secrets push staging
|
|
32
|
+
|
|
33
|
+
# 5. Generate .dev.vars for local dev from password manager
|
|
34
|
+
sync-cf-secrets pull local
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
### `init`
|
|
40
|
+
|
|
41
|
+
Create password manager items for all environments discovered from your wrangler config. Reads field names and values from `.dev.vars`:
|
|
42
|
+
|
|
43
|
+
- **local** item gets the actual values from `.dev.vars`
|
|
44
|
+
- **staging**, **production**, etc. get the same fields with `CHANGE_ME` placeholders
|
|
45
|
+
|
|
46
|
+
Any variables already defined as `vars` in your wrangler config are automatically excluded — they're non-secret config and don't belong in the password manager.
|
|
47
|
+
|
|
48
|
+
If items already exist, you'll be prompted before they're replaced.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
sync-cf-secrets init
|
|
52
|
+
sync-cf-secrets init --dry-run
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `copy <from> <to>`
|
|
56
|
+
|
|
57
|
+
Copy secrets from one environment's password manager item to another. Useful when environments share most values (e.g. local and staging both use test/sandbox API keys).
|
|
58
|
+
|
|
59
|
+
Variables defined as `vars` in the target environment's wrangler config are excluded.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
sync-cf-secrets copy local staging
|
|
63
|
+
sync-cf-secrets copy staging production
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `push <env>`
|
|
67
|
+
|
|
68
|
+
Push secrets from your password manager to a Cloudflare Workers environment.
|
|
69
|
+
|
|
70
|
+
Variables already defined as `vars` in your wrangler config are automatically skipped.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
sync-cf-secrets push staging
|
|
74
|
+
sync-cf-secrets push production --dry-run # preview without pushing
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `pull <env>`
|
|
78
|
+
|
|
79
|
+
Generate a `.dev.vars` file from your password manager.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sync-cf-secrets pull local
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `list <env>`
|
|
86
|
+
|
|
87
|
+
List secret names deployed to a Cloudflare Workers environment.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
sync-cf-secrets list staging
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `diff <env>`
|
|
94
|
+
|
|
95
|
+
Compare secrets in your password manager vs what's deployed to Cloudflare.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
sync-cf-secrets diff production --verbose
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Options
|
|
102
|
+
|
|
103
|
+
| Flag | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `--provider <name>` | Password manager: `1password`, `bitwarden` (auto-detected by default) |
|
|
106
|
+
| `--vault <name>` | Override vault name |
|
|
107
|
+
| `--dry-run` | Show what would happen without doing it |
|
|
108
|
+
| `--verbose` | Show more detail |
|
|
109
|
+
| `--help` | Show help |
|
|
110
|
+
|
|
111
|
+
## Wrangler Vars vs Secrets
|
|
112
|
+
|
|
113
|
+
The tool automatically reads your wrangler config (`wrangler.toml` / `wrangler.jsonc`) and excludes any names defined as `vars`. These are non-secret environment config (like `DEPLOY_ENV` or `AUTH_URL`) that Cloudflare manages as plaintext bindings — pushing them as secrets would cause a "Binding name already in use" error.
|
|
114
|
+
|
|
115
|
+
This filtering is per-environment, so a variable that's a `var` in staging but not in production is handled correctly.
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
Create a `.sync-cf-secrets.json` in your project root (all fields optional):
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"provider": "1password",
|
|
124
|
+
"vault": "Engineering",
|
|
125
|
+
"prefix": "myproject",
|
|
126
|
+
"wranglerConfig": "apps/web/wrangler.jsonc",
|
|
127
|
+
"devVarsPath": "apps/web/.dev.vars",
|
|
128
|
+
"environments": {
|
|
129
|
+
"local": { "item": "myproject local" },
|
|
130
|
+
"staging": { "item": "myproject staging" },
|
|
131
|
+
"production": { "item": "myproject production" }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Defaults
|
|
137
|
+
|
|
138
|
+
- **provider** — auto-detected from available CLIs (`op` or `bw`)
|
|
139
|
+
- **vault** — project name from `package.json`
|
|
140
|
+
- **prefix** — same as vault
|
|
141
|
+
- **wranglerConfig** — auto-searches for `wrangler.toml`, `wrangler.jsonc`, or `wrangler.json` (including `apps/web/`)
|
|
142
|
+
- **devVarsPath** — `.dev.vars` next to your wrangler config
|
|
143
|
+
- **environments** — auto-discovered from your wrangler config's `env` block, plus `local`
|
|
144
|
+
|
|
145
|
+
## Password Manager Setup
|
|
146
|
+
|
|
147
|
+
### 1Password
|
|
148
|
+
|
|
149
|
+
The easiest way to get started is `sync-cf-secrets init`, which creates Secure Note items with the right fields. You can also create them manually — one Secure Note per environment with custom fields where the **label** is the env var name and the **value** is the secret.
|
|
150
|
+
|
|
151
|
+
Requires the [1Password CLI](https://developer.1password.com/docs/cli/get-started/) (`op`). Auth is handled via the 1Password desktop app (biometric/Touch ID).
|
|
152
|
+
|
|
153
|
+
### Bitwarden
|
|
154
|
+
|
|
155
|
+
Same concept — one Secure Note per environment with custom fields for each secret.
|
|
156
|
+
|
|
157
|
+
Requires the [Bitwarden CLI](https://bitwarden.com/help/cli/) (`bw`).
|
|
158
|
+
|
|
159
|
+
### Adding a Provider
|
|
160
|
+
|
|
161
|
+
Providers implement the `SecretProvider` interface:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
interface SecretProvider {
|
|
165
|
+
name: string;
|
|
166
|
+
cli: string;
|
|
167
|
+
validate(): Promise<void>;
|
|
168
|
+
exists(opts: { vault: string; item: string }): Promise<boolean>;
|
|
169
|
+
fetch(opts: { vault: string; item: string }): Promise<Map<string, string>>;
|
|
170
|
+
save(opts: { vault: string; item: string; secrets: Map<string, string> }): Promise<void>;
|
|
171
|
+
listFields(opts: { vault: string; item: string }): Promise<string[]>;
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Security
|
|
176
|
+
|
|
177
|
+
- Secret values are piped to wrangler via stdin — never exposed in CLI arguments or process listings
|
|
178
|
+
- No intermediate temp files
|
|
179
|
+
- Requires password manager authentication (biometrics/master password)
|
|
180
|
+
- Generated `.dev.vars` files include a "do not commit" warning
|
|
181
|
+
- Duplicate items are detected and cleaned up by unique ID
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
import { copy } from "./commands/copy.js";
|
|
5
|
+
import { diff } from "./commands/diff.js";
|
|
6
|
+
import { init } from "./commands/init.js";
|
|
7
|
+
import { list } from "./commands/list.js";
|
|
8
|
+
import { pull } from "./commands/pull.js";
|
|
9
|
+
import { push } from "./commands/push.js";
|
|
10
|
+
import { resolveProvider } from "./providers/index.js";
|
|
11
|
+
import { error } from "./utils.js";
|
|
12
|
+
const USAGE = `
|
|
13
|
+
sync-cf-secrets — Sync secrets from your password manager to Cloudflare Workers
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
sync-cf-secrets push <env> Push secrets to Cloudflare
|
|
17
|
+
sync-cf-secrets pull <env> Write .dev.vars from password manager
|
|
18
|
+
sync-cf-secrets init Create items for all environments from .dev.vars
|
|
19
|
+
sync-cf-secrets copy <from> <to> Copy secrets between environments
|
|
20
|
+
sync-cf-secrets list <env> List deployed Cloudflare secrets
|
|
21
|
+
sync-cf-secrets diff <env> Compare password manager vs Cloudflare
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--provider <name> Password manager: 1password, bitwarden (auto-detected)
|
|
25
|
+
--vault <name> Override vault name
|
|
26
|
+
--dry-run Show what would happen without doing it
|
|
27
|
+
--verbose Show more detail
|
|
28
|
+
--help Show this help
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
sync-cf-secrets push staging --dry-run
|
|
32
|
+
sync-cf-secrets pull local
|
|
33
|
+
sync-cf-secrets init
|
|
34
|
+
sync-cf-secrets copy local staging
|
|
35
|
+
sync-cf-secrets diff production --verbose
|
|
36
|
+
`.trim();
|
|
37
|
+
async function main() {
|
|
38
|
+
const { values, positionals } = parseArgs({
|
|
39
|
+
allowPositionals: true,
|
|
40
|
+
options: {
|
|
41
|
+
provider: { type: "string" },
|
|
42
|
+
vault: { type: "string" },
|
|
43
|
+
"dry-run": { type: "boolean", default: false },
|
|
44
|
+
verbose: { type: "boolean", default: false },
|
|
45
|
+
help: { type: "boolean", short: "h", default: false },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (values.help || positionals.length === 0) {
|
|
49
|
+
console.log(USAGE);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
const [command, env, env2] = positionals;
|
|
53
|
+
if (!env && !["help", "init"].includes(command)) {
|
|
54
|
+
const usage = command === "copy"
|
|
55
|
+
? "sync-cf-secrets copy <from> <to>"
|
|
56
|
+
: `sync-cf-secrets ${command} <env>`;
|
|
57
|
+
error(`Missing argument(s).\n\nUsage: ${usage}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const config = loadConfig({
|
|
61
|
+
provider: values.provider,
|
|
62
|
+
vault: values.vault,
|
|
63
|
+
});
|
|
64
|
+
// Commands that need a provider
|
|
65
|
+
if (["push", "pull", "init", "copy", "diff"].includes(command)) {
|
|
66
|
+
const provider = resolveProvider(config.provider);
|
|
67
|
+
await provider.validate();
|
|
68
|
+
switch (command) {
|
|
69
|
+
case "push":
|
|
70
|
+
return push(provider, config, {
|
|
71
|
+
env: env,
|
|
72
|
+
dryRun: values["dry-run"],
|
|
73
|
+
verbose: values.verbose,
|
|
74
|
+
});
|
|
75
|
+
case "pull":
|
|
76
|
+
return pull(provider, config, {
|
|
77
|
+
env: env,
|
|
78
|
+
dryRun: values["dry-run"],
|
|
79
|
+
verbose: values.verbose,
|
|
80
|
+
});
|
|
81
|
+
case "init":
|
|
82
|
+
return init(provider, config, {
|
|
83
|
+
dryRun: values["dry-run"],
|
|
84
|
+
verbose: values.verbose,
|
|
85
|
+
});
|
|
86
|
+
case "copy":
|
|
87
|
+
if (!env2) {
|
|
88
|
+
error("Missing target environment.\n\nUsage: sync-cf-secrets copy <from> <to>");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
return copy(provider, config, {
|
|
92
|
+
from: env,
|
|
93
|
+
to: env2,
|
|
94
|
+
dryRun: values["dry-run"],
|
|
95
|
+
verbose: values.verbose,
|
|
96
|
+
});
|
|
97
|
+
case "diff":
|
|
98
|
+
return diff(provider, config, {
|
|
99
|
+
env: env,
|
|
100
|
+
verbose: values.verbose,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Commands that don't need a provider
|
|
105
|
+
if (command === "list") {
|
|
106
|
+
return list(config, { env: env });
|
|
107
|
+
}
|
|
108
|
+
error(`Unknown command "${command}".\n`);
|
|
109
|
+
console.log(USAGE);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
main().catch((err) => {
|
|
113
|
+
error(err.message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
import type { SecretProvider } from "../providers/types.js";
|
|
3
|
+
export interface CopyOptions {
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
dryRun: boolean;
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function copy(provider: SecretProvider, config: Config, opts: CopyOptions): Promise<void>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { error, log, success, warn } from "../utils.js";
|
|
3
|
+
import { getWranglerVars } from "../wrangler.js";
|
|
4
|
+
function confirm(question) {
|
|
5
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
rl.question(`${question} [y/N] `, (answer) => {
|
|
8
|
+
rl.close();
|
|
9
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function copy(provider, config, opts) {
|
|
14
|
+
const fromEnv = config.environments[opts.from];
|
|
15
|
+
const toEnv = config.environments[opts.to];
|
|
16
|
+
if (!fromEnv) {
|
|
17
|
+
throw new Error(`Unknown source environment "${opts.from}". ` +
|
|
18
|
+
`Available: ${Object.keys(config.environments).join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
if (!toEnv) {
|
|
21
|
+
throw new Error(`Unknown target environment "${opts.to}". ` +
|
|
22
|
+
`Available: ${Object.keys(config.environments).join(", ")}`);
|
|
23
|
+
}
|
|
24
|
+
log(`Fetching secrets from "${fromEnv.item}"...`);
|
|
25
|
+
const secrets = await provider.fetch({
|
|
26
|
+
vault: config.vault,
|
|
27
|
+
item: fromEnv.item,
|
|
28
|
+
});
|
|
29
|
+
if (secrets.size === 0) {
|
|
30
|
+
warn(`No secrets found in "${fromEnv.item}".`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Filter out vars defined in wrangler config for the target environment
|
|
34
|
+
const wranglerVars = getWranglerVars(opts.to, config.wranglerConfig);
|
|
35
|
+
const skipped = [];
|
|
36
|
+
for (const name of secrets.keys()) {
|
|
37
|
+
if (wranglerVars.has(name)) {
|
|
38
|
+
skipped.push(name);
|
|
39
|
+
secrets.delete(name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (skipped.length > 0) {
|
|
43
|
+
log(`Skipping ${skipped.length} wrangler var(s): ${skipped.join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
log(`${secrets.size} secret(s) to copy.\n`);
|
|
46
|
+
if (opts.dryRun) {
|
|
47
|
+
log(`Dry run — would copy ${secrets.size} field(s) from "${fromEnv.item}" to "${toEnv.item}":`);
|
|
48
|
+
for (const name of secrets.keys()) {
|
|
49
|
+
log(` ${name}`);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
// Check if target exists
|
|
54
|
+
if (await provider.exists({ vault: config.vault, item: toEnv.item })) {
|
|
55
|
+
warn(`"${toEnv.item}" already exists in ${provider.name}.`);
|
|
56
|
+
const confirmed = await confirm("Delete and replace it?");
|
|
57
|
+
if (!confirmed) {
|
|
58
|
+
log("Aborted.");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
log("");
|
|
62
|
+
}
|
|
63
|
+
log(`Copying to "${toEnv.item}"...`);
|
|
64
|
+
try {
|
|
65
|
+
await provider.save({
|
|
66
|
+
vault: config.vault,
|
|
67
|
+
item: toEnv.item,
|
|
68
|
+
secrets,
|
|
69
|
+
});
|
|
70
|
+
success(`Copied ${secrets.size} field(s) from "${fromEnv.item}" to "${toEnv.item}".`);
|
|
71
|
+
log(` → Open ${provider.name} and update any values that differ for ${opts.to}.`);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
75
|
+
const stderr = err.stderr;
|
|
76
|
+
error(`Failed to create "${toEnv.item}": ${stderr}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
80
|
+
error(`Failed to create "${toEnv.item}": ${msg}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.js","sourceRoot":"","sources":["../../src/commands/copy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
import type { SecretProvider } from "../providers/types.js";
|
|
3
|
+
export interface DiffOptions {
|
|
4
|
+
env: string;
|
|
5
|
+
verbose: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function diff(provider: SecretProvider, config: Config, opts: DiffOptions): Promise<void>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { error, log, success, warn } from "../utils.js";
|
|
2
|
+
import { listSecrets, validateWrangler } from "../wrangler.js";
|
|
3
|
+
export async function diff(provider, config, opts) {
|
|
4
|
+
const envConfig = config.environments[opts.env];
|
|
5
|
+
if (!envConfig) {
|
|
6
|
+
throw new Error(`Unknown environment "${opts.env}". ` +
|
|
7
|
+
`Available: ${Object.keys(config.environments).join(", ")}`);
|
|
8
|
+
}
|
|
9
|
+
validateWrangler();
|
|
10
|
+
log(`Comparing ${provider.name} item "${envConfig.item}" vs Cloudflare ${opts.env}...\n`);
|
|
11
|
+
// Fetch field names from provider
|
|
12
|
+
const providerFields = new Set(await provider.listFields({
|
|
13
|
+
vault: config.vault,
|
|
14
|
+
item: envConfig.item,
|
|
15
|
+
}));
|
|
16
|
+
// Fetch deployed secret names from Cloudflare
|
|
17
|
+
const cfSecrets = new Set(listSecrets(opts.env, config.wranglerConfig));
|
|
18
|
+
const missingFromCf = [];
|
|
19
|
+
const extraInCf = [];
|
|
20
|
+
const matching = [];
|
|
21
|
+
for (const name of providerFields) {
|
|
22
|
+
if (cfSecrets.has(name)) {
|
|
23
|
+
matching.push(name);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
missingFromCf.push(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const name of cfSecrets) {
|
|
30
|
+
if (!providerFields.has(name)) {
|
|
31
|
+
extraInCf.push(name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (missingFromCf.length > 0) {
|
|
35
|
+
warn(`Missing from Cloudflare (in ${provider.name} but not deployed):`);
|
|
36
|
+
for (const name of missingFromCf.sort()) {
|
|
37
|
+
log(` + ${name}`);
|
|
38
|
+
}
|
|
39
|
+
log("");
|
|
40
|
+
}
|
|
41
|
+
if (extraInCf.length > 0) {
|
|
42
|
+
warn(`Extra in Cloudflare (deployed but not in ${provider.name}):`);
|
|
43
|
+
for (const name of extraInCf.sort()) {
|
|
44
|
+
log(` - ${name}`);
|
|
45
|
+
}
|
|
46
|
+
log("");
|
|
47
|
+
}
|
|
48
|
+
if (matching.length > 0 && opts.verbose) {
|
|
49
|
+
log("Matching:");
|
|
50
|
+
for (const name of matching.sort()) {
|
|
51
|
+
log(` = ${name}`);
|
|
52
|
+
}
|
|
53
|
+
log("");
|
|
54
|
+
}
|
|
55
|
+
if (missingFromCf.length === 0 && extraInCf.length === 0) {
|
|
56
|
+
success(`All ${matching.length} secret(s) are in sync.`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
error(`${missingFromCf.length} missing, ${extraInCf.length} extra, ${matching.length} matching.`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Config } from "../config.js";
|
|
2
|
+
import type { SecretProvider } from "../providers/types.js";
|
|
3
|
+
export interface InitOptions {
|
|
4
|
+
dryRun: boolean;
|
|
5
|
+
verbose: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function init(provider: SecretProvider, config: Config, opts: InitOptions): Promise<void>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { error, log, success, warn } from "../utils.js";
|
|
4
|
+
import { getWranglerVars } from "../wrangler.js";
|
|
5
|
+
/**
|
|
6
|
+
* Parse a .dev.vars file into a Map of key-value pairs.
|
|
7
|
+
*/
|
|
8
|
+
function parseDevVars(path) {
|
|
9
|
+
const content = readFileSync(path, "utf-8");
|
|
10
|
+
const secrets = new Map();
|
|
11
|
+
for (const line of content.split("\n")) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
14
|
+
continue;
|
|
15
|
+
const eqIndex = trimmed.indexOf("=");
|
|
16
|
+
if (eqIndex === -1)
|
|
17
|
+
continue;
|
|
18
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
19
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
20
|
+
if (key) {
|
|
21
|
+
secrets.set(key, value);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return secrets;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Prompt the user for yes/no confirmation.
|
|
28
|
+
*/
|
|
29
|
+
function confirm(question) {
|
|
30
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
rl.question(`${question} [y/N] `, (answer) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export async function init(provider, config, opts) {
|
|
39
|
+
if (!existsSync(config.devVarsPath)) {
|
|
40
|
+
throw new Error(`File not found: ${config.devVarsPath}`);
|
|
41
|
+
}
|
|
42
|
+
const devVars = parseDevVars(config.devVarsPath);
|
|
43
|
+
if (devVars.size === 0) {
|
|
44
|
+
warn("No secrets found in .dev.vars file.");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
log(`Parsed ${devVars.size} field(s) from ${config.devVarsPath}\n`);
|
|
48
|
+
// Check which items already exist
|
|
49
|
+
const envNames = Object.keys(config.environments);
|
|
50
|
+
const existing = [];
|
|
51
|
+
for (const env of envNames) {
|
|
52
|
+
const envConfig = config.environments[env];
|
|
53
|
+
if (await provider.exists({ vault: config.vault, item: envConfig.item })) {
|
|
54
|
+
existing.push(envConfig.item);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (existing.length > 0 && !opts.dryRun) {
|
|
58
|
+
warn(`These items already exist in ${provider.name}:`);
|
|
59
|
+
for (const name of existing) {
|
|
60
|
+
log(` • ${name}`);
|
|
61
|
+
}
|
|
62
|
+
log("");
|
|
63
|
+
const confirmed = await confirm("Delete and recreate them? Existing values will be lost.");
|
|
64
|
+
if (!confirmed) {
|
|
65
|
+
log("Aborted.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
log("");
|
|
69
|
+
}
|
|
70
|
+
for (const env of envNames) {
|
|
71
|
+
const envConfig = config.environments[env];
|
|
72
|
+
const isLocal = env === "local";
|
|
73
|
+
// Filter out vars defined in wrangler config for this environment
|
|
74
|
+
const wranglerVars = getWranglerVars(env, config.wranglerConfig);
|
|
75
|
+
const secrets = new Map();
|
|
76
|
+
const skipped = [];
|
|
77
|
+
for (const [name, value] of devVars) {
|
|
78
|
+
if (wranglerVars.has(name)) {
|
|
79
|
+
skipped.push(name);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
secrets.set(name, isLocal ? value : "CHANGE_ME");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (skipped.length > 0) {
|
|
86
|
+
log(` Skipping wrangler var(s): ${skipped.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
if (opts.dryRun) {
|
|
89
|
+
const label = isLocal ? "with values from .dev.vars" : "with CHANGE_ME placeholders";
|
|
90
|
+
const action = existing.includes(envConfig.item) ? "Replace" : "Create";
|
|
91
|
+
log(`Dry run — would ${action.toLowerCase()} "${envConfig.item}" ${label}`);
|
|
92
|
+
if (opts.verbose) {
|
|
93
|
+
for (const name of secrets.keys()) {
|
|
94
|
+
log(` ${name}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const label = isLocal ? "with values" : "with placeholders";
|
|
100
|
+
log(`Creating "${envConfig.item}" ${label}...`);
|
|
101
|
+
try {
|
|
102
|
+
await provider.save({
|
|
103
|
+
vault: config.vault,
|
|
104
|
+
item: envConfig.item,
|
|
105
|
+
secrets,
|
|
106
|
+
});
|
|
107
|
+
if (isLocal) {
|
|
108
|
+
success(`Created "${envConfig.item}" with ${secrets.size} field(s).`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
success(`Created "${envConfig.item}" with ${secrets.size} placeholder field(s).`);
|
|
112
|
+
log(` → Open ${provider.name} and replace CHANGE_ME values with real secrets.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
117
|
+
const stderr = err.stderr;
|
|
118
|
+
error(`Failed to create "${envConfig.item}": ${stderr}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
122
|
+
error(`Failed to create "${envConfig.item}": ${msg}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
log("");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|