reposets 0.4.1 → 0.4.2
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 +15 -14
- package/bin/reposets.d.ts +1 -0
- package/bin/reposets.js +25 -507
- package/cli/commands/credentials.js +74 -0
- package/cli/commands/doctor.js +154 -0
- package/cli/commands/init.js +147 -0
- package/cli/commands/list.js +49 -0
- package/cli/commands/sync.js +70 -0
- package/cli/commands/validate.js +53 -0
- package/errors.js +12 -0
- package/index.d.ts +1750 -1840
- package/index.js +19 -2
- package/lib/crypto.js +27 -0
- package/package.json +61 -73
- package/schemas/common.js +130 -0
- package/schemas/config.js +469 -0
- package/schemas/credentials.js +78 -0
- package/schemas/environment.js +70 -0
- package/schemas/ruleset.js +545 -0
- package/services/ConfigFiles.js +119 -0
- package/services/CredentialResolver.js +40 -0
- package/services/GitHubClient.js +875 -0
- package/services/OnePasswordClient.js +30 -0
- package/services/SyncEngine.js +580 -0
- package/services/SyncLogger.js +106 -0
- package/tsdoc-metadata.json +11 -11
- package/500.js +0 -3354
package/index.js
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { GitHubApiError, OnePasswordError, ResolveError, SyncError } from "./errors.js";
|
|
2
|
+
import { encryptSecret } from "./lib/crypto.js";
|
|
3
|
+
import { CleanupSchema, CleanupScopeSchema, SecretGroupSchema, VariableGroupSchema } from "./schemas/common.js";
|
|
4
|
+
import { BypassActorSchema, ResolvedRefSchema, RulesetSchema, buildRulesetPayload } from "./schemas/ruleset.js";
|
|
5
|
+
import { ConfigSchema, GroupSchema, LogLevelSchema } from "./schemas/config.js";
|
|
6
|
+
import { CredentialProfileSchema, CredentialsSchema, ResolveSectionSchema } from "./schemas/credentials.js";
|
|
7
|
+
import { CONFIG_FILENAME, CREDENTIALS_FILENAME, ConfigFilesLive, ReposetsConfigFile, ReposetsCredentialsFile, makeConfigFilesLive, validateConfigRefs } from "./services/ConfigFiles.js";
|
|
8
|
+
import { OnePasswordClient, OnePasswordClientLive, OnePasswordClientTest } from "./services/OnePasswordClient.js";
|
|
9
|
+
import { CredentialResolver, CredentialResolverLive } from "./services/CredentialResolver.js";
|
|
10
|
+
import { GitHubClient, GitHubClientLive, GitHubClientTest } from "./services/GitHubClient.js";
|
|
11
|
+
import { SyncLogger, SyncLoggerLive } from "./services/SyncLogger.js";
|
|
12
|
+
import { SyncEngine, SyncEngineLive } from "./services/SyncEngine.js";
|
|
13
|
+
import { AppDirs, ConfigError as XdgConfigError, ConfigFile } from "xdg-effect";
|
|
14
|
+
|
|
15
|
+
//#region src/index.ts
|
|
16
|
+
/* v8 ignore stop */
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AppDirs, BypassActorSchema, CONFIG_FILENAME, CREDENTIALS_FILENAME, CleanupSchema, CleanupScopeSchema, ConfigFile, ConfigFilesLive, ConfigSchema, CredentialProfileSchema, CredentialResolver, CredentialResolverLive, CredentialsSchema, GitHubApiError, GitHubClient, GitHubClientLive, GitHubClientTest, GroupSchema, LogLevelSchema, OnePasswordClient, OnePasswordClientLive, OnePasswordClientTest, OnePasswordError, ReposetsConfigFile, ReposetsCredentialsFile, ResolveError, ResolveSectionSchema, ResolvedRefSchema, RulesetSchema, SecretGroupSchema, SyncEngine, SyncEngineLive, SyncError, SyncLogger, SyncLoggerLive, VariableGroupSchema, XdgConfigError, buildRulesetPayload, encryptSecret, makeConfigFilesLive, validateConfigRefs };
|
package/lib/crypto.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { blake2b } from "blakejs";
|
|
2
|
+
import nacl from "tweetnacl";
|
|
3
|
+
|
|
4
|
+
//#region src/lib/crypto.ts
|
|
5
|
+
/**
|
|
6
|
+
* Encrypt a secret using libsodium's sealed box algorithm.
|
|
7
|
+
* Implementation based on tweetsodium using tweetnacl + blakejs.
|
|
8
|
+
*
|
|
9
|
+
* The sealed box format is: ephemeral_public_key (32 bytes) || ciphertext
|
|
10
|
+
*/
|
|
11
|
+
function encryptSecret(publicKey, secretValue) {
|
|
12
|
+
const messageBytes = Buffer.from(secretValue);
|
|
13
|
+
const publicKeyBytes = Buffer.from(publicKey, "base64");
|
|
14
|
+
const ephemeralKeyPair = nacl.box.keyPair();
|
|
15
|
+
const nonceInput = new Uint8Array(64);
|
|
16
|
+
nonceInput.set(ephemeralKeyPair.publicKey);
|
|
17
|
+
nonceInput.set(publicKeyBytes, 32);
|
|
18
|
+
const nonce = blake2b(nonceInput, void 0, 24);
|
|
19
|
+
const ciphertext = nacl.box(messageBytes, nonce, publicKeyBytes, ephemeralKeyPair.secretKey);
|
|
20
|
+
const sealed = new Uint8Array(ephemeralKeyPair.publicKey.length + ciphertext.length);
|
|
21
|
+
sealed.set(ephemeralKeyPair.publicKey);
|
|
22
|
+
sealed.set(ciphertext, ephemeralKeyPair.publicKey.length);
|
|
23
|
+
return Buffer.from(sealed).toString("base64");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { encryptSecret };
|
package/package.json
CHANGED
|
@@ -1,75 +1,63 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"!reposets.api.json",
|
|
64
|
-
"!tsconfig.json",
|
|
65
|
-
"!tsdoc.json",
|
|
66
|
-
"500.js",
|
|
67
|
-
"LICENSE",
|
|
68
|
-
"README.md",
|
|
69
|
-
"bin/reposets.js",
|
|
70
|
-
"index.d.ts",
|
|
71
|
-
"index.js",
|
|
72
|
-
"package.json",
|
|
73
|
-
"tsdoc-metadata.json"
|
|
74
|
-
]
|
|
2
|
+
"name": "reposets",
|
|
3
|
+
"version": "0.4.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "CLI tool to sync GitHub repo settings, secrets and rulesets across personal repositories",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"github",
|
|
8
|
+
"sync",
|
|
9
|
+
"cli",
|
|
10
|
+
"secrets",
|
|
11
|
+
"rulesets",
|
|
12
|
+
"variables",
|
|
13
|
+
"environments",
|
|
14
|
+
"toml",
|
|
15
|
+
"effect",
|
|
16
|
+
"xdg",
|
|
17
|
+
"repository",
|
|
18
|
+
"settings",
|
|
19
|
+
"automation",
|
|
20
|
+
"devops"
|
|
21
|
+
],
|
|
22
|
+
"homepage": "https://github.com/spencerbeggs/reposets#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/spencerbeggs/reposets/issues"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/spencerbeggs/reposets.git",
|
|
29
|
+
"directory": "package"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"author": {
|
|
33
|
+
"name": "C. Spencer Beggs",
|
|
34
|
+
"email": "spencer@beggs.codes",
|
|
35
|
+
"url": "https://spencerbeg.gs"
|
|
36
|
+
},
|
|
37
|
+
"type": "module",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./index.d.ts",
|
|
41
|
+
"import": "./index.js"
|
|
42
|
+
},
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"bin": {
|
|
46
|
+
"reposets": "bin/reposets.js"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@1password/sdk": "^0.4.0",
|
|
50
|
+
"@effect/cli": "^0.75.2",
|
|
51
|
+
"@effect/platform": "^0.96.1",
|
|
52
|
+
"@effect/platform-node": "^0.107.0",
|
|
53
|
+
"@octokit/rest": "^22.0.1",
|
|
54
|
+
"blakejs": "^1.2.1",
|
|
55
|
+
"effect": "^3.21.3",
|
|
56
|
+
"smol-toml": ">=1.6.1",
|
|
57
|
+
"tweetnacl": "^1.0.3",
|
|
58
|
+
"xdg-effect": "^1.0.3"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=24.11.0"
|
|
62
|
+
}
|
|
75
63
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Jsonifiable, taplo, tombi } from "xdg-effect";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/schemas/common.ts
|
|
5
|
+
const ResourceFileKind = Schema.Struct({ file: Schema.Record({
|
|
6
|
+
key: Schema.String,
|
|
7
|
+
value: Schema.String
|
|
8
|
+
}).annotations({
|
|
9
|
+
title: "File entries",
|
|
10
|
+
description: "Named entries with file path values, resolved relative to config directory",
|
|
11
|
+
jsonSchema: tombi({ additionalKeyLabel: "name" })
|
|
12
|
+
}) });
|
|
13
|
+
const ResourceValueKind = Schema.Struct({ value: Schema.Record({
|
|
14
|
+
key: Schema.String,
|
|
15
|
+
value: Schema.Union(Schema.String, Schema.Record({
|
|
16
|
+
key: Schema.String,
|
|
17
|
+
value: Jsonifiable
|
|
18
|
+
}))
|
|
19
|
+
}).annotations({
|
|
20
|
+
title: "Value entries",
|
|
21
|
+
description: "Named entries with inline values. Strings used as-is, objects JSON-stringified.",
|
|
22
|
+
jsonSchema: tombi({ additionalKeyLabel: "name" })
|
|
23
|
+
}) });
|
|
24
|
+
const ResourceResolvedKind = Schema.Struct({ resolved: Schema.Record({
|
|
25
|
+
key: Schema.String,
|
|
26
|
+
value: Schema.String
|
|
27
|
+
}).annotations({
|
|
28
|
+
title: "Resolved entries",
|
|
29
|
+
description: "Named entries mapped to credential labels. Values come from the active credential profile.",
|
|
30
|
+
jsonSchema: tombi({ additionalKeyLabel: "name" })
|
|
31
|
+
}) });
|
|
32
|
+
const SecretGroupSchema = Schema.Union(ResourceFileKind, ResourceValueKind, ResourceResolvedKind).annotations({
|
|
33
|
+
identifier: "SecretGroup",
|
|
34
|
+
title: "Secret group",
|
|
35
|
+
description: "A group of secrets. Must be exactly one kind: file, value, or resolved.",
|
|
36
|
+
jsonSchema: taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/secrets-and-variables.md" } })
|
|
37
|
+
});
|
|
38
|
+
const VariableGroupSchema = Schema.Union(ResourceFileKind, ResourceValueKind, ResourceResolvedKind).annotations({
|
|
39
|
+
identifier: "VariableGroup",
|
|
40
|
+
title: "Variable group",
|
|
41
|
+
description: "A group of variables. Must be exactly one kind: file, value, or resolved.",
|
|
42
|
+
jsonSchema: taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/secrets-and-variables.md" } })
|
|
43
|
+
});
|
|
44
|
+
const CleanupScopeSchema = Schema.Union(Schema.Boolean, Schema.Struct({ preserve: Schema.Array(Schema.String).annotations({
|
|
45
|
+
title: "Preserve list",
|
|
46
|
+
description: "Resource names that should never be deleted during cleanup",
|
|
47
|
+
examples: [["LEGACY_TOKEN", "DEPLOY_KEY"]]
|
|
48
|
+
}) })).annotations({
|
|
49
|
+
identifier: "CleanupScope",
|
|
50
|
+
title: "Cleanup scope",
|
|
51
|
+
description: "Controls cleanup for a single resource scope. false disables cleanup, true enables full cleanup, or specify names to preserve."
|
|
52
|
+
});
|
|
53
|
+
const CleanupSecretsSchema = Schema.Struct({
|
|
54
|
+
actions: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
55
|
+
title: "Clean up Actions secrets",
|
|
56
|
+
description: "Delete Actions secrets not declared in any referenced secret group",
|
|
57
|
+
default: false
|
|
58
|
+
}),
|
|
59
|
+
dependabot: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
60
|
+
title: "Clean up Dependabot secrets",
|
|
61
|
+
description: "Delete Dependabot secrets not declared in any referenced secret group",
|
|
62
|
+
default: false
|
|
63
|
+
}),
|
|
64
|
+
codespaces: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
65
|
+
title: "Clean up Codespaces secrets",
|
|
66
|
+
description: "Delete Codespaces secrets not declared in any referenced secret group",
|
|
67
|
+
default: false
|
|
68
|
+
}),
|
|
69
|
+
environments: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
70
|
+
title: "Clean up environment secrets",
|
|
71
|
+
description: "Delete environment secrets not declared in any referenced secret group",
|
|
72
|
+
default: false
|
|
73
|
+
})
|
|
74
|
+
}).annotations({
|
|
75
|
+
identifier: "CleanupSecrets",
|
|
76
|
+
title: "Secrets cleanup configuration",
|
|
77
|
+
description: "Controls deletion of secrets by scope (Actions, Dependabot, Codespaces, environments)."
|
|
78
|
+
});
|
|
79
|
+
const CleanupVariablesSchema = Schema.Struct({
|
|
80
|
+
actions: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
81
|
+
title: "Clean up Actions variables",
|
|
82
|
+
description: "Delete Actions variables not declared in any referenced variable group",
|
|
83
|
+
default: false
|
|
84
|
+
}),
|
|
85
|
+
environments: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
86
|
+
title: "Clean up environment variables",
|
|
87
|
+
description: "Delete environment variables not declared in any referenced variable group",
|
|
88
|
+
default: false
|
|
89
|
+
})
|
|
90
|
+
}).annotations({
|
|
91
|
+
identifier: "CleanupVariables",
|
|
92
|
+
title: "Variables cleanup configuration",
|
|
93
|
+
description: "Controls deletion of variables by scope (Actions, environments)."
|
|
94
|
+
});
|
|
95
|
+
const CleanupSchema = Schema.Struct({
|
|
96
|
+
secrets: Schema.optionalWith(CleanupSecretsSchema, { default: () => ({
|
|
97
|
+
actions: false,
|
|
98
|
+
dependabot: false,
|
|
99
|
+
codespaces: false,
|
|
100
|
+
environments: false
|
|
101
|
+
}) }).annotations({
|
|
102
|
+
title: "Secrets cleanup",
|
|
103
|
+
description: "Controls cleanup of secrets by scope"
|
|
104
|
+
}),
|
|
105
|
+
variables: Schema.optionalWith(CleanupVariablesSchema, { default: () => ({
|
|
106
|
+
actions: false,
|
|
107
|
+
environments: false
|
|
108
|
+
}) }).annotations({
|
|
109
|
+
title: "Variables cleanup",
|
|
110
|
+
description: "Controls cleanup of variables by scope"
|
|
111
|
+
}),
|
|
112
|
+
rulesets: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
113
|
+
title: "Clean up rulesets",
|
|
114
|
+
description: "Delete repository rulesets not declared in any referenced ruleset group",
|
|
115
|
+
default: false
|
|
116
|
+
}),
|
|
117
|
+
environments: Schema.optionalWith(CleanupScopeSchema, { default: () => false }).annotations({
|
|
118
|
+
title: "Clean up environments",
|
|
119
|
+
description: "Delete repository environments not declared in config",
|
|
120
|
+
default: false
|
|
121
|
+
})
|
|
122
|
+
}).annotations({
|
|
123
|
+
identifier: "Cleanup",
|
|
124
|
+
title: "Cleanup configuration",
|
|
125
|
+
description: "Controls deletion of resources not declared in config. All disabled by default.",
|
|
126
|
+
jsonSchema: taplo({ links: { key: "https://github.com/spencerbeggs/reposets/blob/main/docs/cleanup.md" } })
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { CleanupSchema, CleanupScopeSchema, SecretGroupSchema, VariableGroupSchema };
|