@ciqol/resenv 0.1.0-beta.1 → 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/CONTRIBUTING.md +41 -19
- package/README.md +36 -20
- package/SECURITY.md +20 -20
- package/bin/resenv.cjs +21 -23
- package/docs/build.md +29 -26
- package/docs/dependencies.md +65 -502
- package/docs/doppler.md +69 -60
- package/docs/maintainer-handoff.md +30 -102
- package/docs/release-security.md +64 -134
- package/docs/releasing.md +72 -137
- package/docs/security-exceptions.json +2 -135
- package/doppler/auth.cjs +4 -110
- package/doppler/http.cjs +3 -8
- package/doppler/system.cjs +26 -75
- package/package.json +2 -2
- package/types/src/doppler/auth.d.ts +2 -17
- package/types/src/doppler/http.d.ts +1 -3
- package/types/src/doppler/system.d.ts +3 -8
- package/docs/maturity.md +0 -61
- package/docs/public-readiness.md +0 -138
- package/docs/security-performance-review.md +0 -150
- package/docs/validation.md +0 -700
package/docs/releasing.md
CHANGED
|
@@ -1,143 +1,78 @@
|
|
|
1
1
|
# Releases
|
|
2
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
|
-
1.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
prerelease. It does not stage or publish the package again.
|
|
46
|
-
|
|
47
|
-
Pushes, PRs and dispatches with `publish: false` cannot stage a package. Release
|
|
48
|
-
jobs are serialized and not cancelled. Artifacts last seven days. A fresh
|
|
49
|
-
workflow run is not a substitute for rerunning the original release job when
|
|
50
|
-
recovering an already staged/published candidate.
|
|
51
|
-
|
|
52
|
-
## Required setup
|
|
53
|
-
|
|
54
|
-
The existing npm trusted publisher must match:
|
|
3
|
+
**Merge the PR to `main`; CI publishes the release.** There is no separate
|
|
4
|
+
approval button, npm staging queue or Finalize job. Manual workflow dispatch
|
|
5
|
+
runs checks only. The default branch requires a PR, current passing checks,
|
|
6
|
+
resolved review threads and squash merging, with no bypass actors. The sole
|
|
7
|
+
maintainer approves by merging; no second reviewer is required.
|
|
8
|
+
|
|
9
|
+
## Normal flow
|
|
10
|
+
|
|
11
|
+
CI audits dependencies, checks signatures, lint, types and coverage, builds
|
|
12
|
+
once, and tests the packed build across Linux Node 20/22/24 and macOS Node 24.
|
|
13
|
+
After a merge, the protected release job downloads that build and invokes
|
|
14
|
+
standard semantic-release. The official npm plugin prepares the release
|
|
15
|
+
version and archive; consumer checks run again on the final package. The
|
|
16
|
+
official npm and GitHub plugins publish to npm `latest` and create the GitHub
|
|
17
|
+
release. No source is rebuilt during release.
|
|
18
|
+
|
|
19
|
+
Conventional Commits determine releases: fixes and performance changes bump
|
|
20
|
+
patch, features bump minor, and breaking changes bump minor while the package
|
|
21
|
+
remains below 1.0. Docs and maintenance commits alone do not trigger a release.
|
|
22
|
+
Squash PR titles must follow that convention. `0.x.x` denotes initial
|
|
23
|
+
development, not a SemVer prerelease; published versions have no beta suffix.
|
|
24
|
+
A guard blocks 1.0 until that transition is reviewed explicitly.
|
|
25
|
+
|
|
26
|
+
## Initial 0.1.0 release
|
|
27
|
+
|
|
28
|
+
semantic-release starts at 1.0.0 when no regular release tag exists. To preserve
|
|
29
|
+
0.x numbering without a fake tag, the first enabled main release publishes the
|
|
30
|
+
tested `0.1.0` archive through npm OIDC and records its real tag and GitHub
|
|
31
|
+
release. Thereafter the normal engine uses that baseline. Existing beta tags
|
|
32
|
+
and npm versions remain historical; the `beta` channel is not updated.
|
|
33
|
+
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
- Default/release branch: `main`, protected by PRs and the actual CI/CodeQL checks.
|
|
37
|
+
- npm trusted publisher: `ciqol/resenv`, `ci.yml`, environment `release`, with
|
|
38
|
+
**direct publish** permission. Stage-only permission cannot run this flow.
|
|
39
|
+
- GitHub environment `release`: allow only `main`, with no required reviewers.
|
|
40
|
+
- `RELEASE_ENABLED=true` only after protection, the npm publisher and dependency
|
|
41
|
+
audit are verified. The maintainer manages the npm setting.
|
|
42
|
+
|
|
43
|
+
On npmjs.com, open **@ciqol/resenv → Settings → Trusted publishing** and
|
|
44
|
+
configure its GitHub Actions publisher with these exact values:
|
|
55
45
|
|
|
56
46
|
| Field | Value |
|
|
57
47
|
| --- | --- |
|
|
58
|
-
|
|
|
59
|
-
| Organization | `ciqol` |
|
|
48
|
+
| Organization or user | `ciqol` |
|
|
60
49
|
| Repository | `resenv` |
|
|
61
|
-
| Workflow filename | `ci.yml` |
|
|
62
|
-
| Environment | `release` |
|
|
63
|
-
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
to
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
##
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
These are maintainer review/approval commands; the archive is built and staged
|
|
93
|
-
by GitHub Actions. OIDC cannot perform the approval step. Wait for npm scanning
|
|
94
|
-
and public availability, then rerun the original release job to record the tag.
|
|
95
|
-
Verify the npm version, `beta` tag, archive integrity, provenance and GitHub
|
|
96
|
-
prerelease. Never infer success from a staging receipt alone.
|
|
97
|
-
|
|
98
|
-
For a hosted rehearsal, leave the staging input off. Local verification is:
|
|
99
|
-
|
|
100
|
-
```sh
|
|
101
|
-
npm ci
|
|
102
|
-
npm run audit
|
|
103
|
-
npm run release:check
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
None of these commands publishes. The planner can generate a non-publishing
|
|
107
|
-
plan with `node scripts/release-plan.mjs /absolute/path/to/candidate-directory`.
|
|
108
|
-
|
|
109
|
-
## Recovery and old versions
|
|
110
|
-
|
|
111
|
-
A failed stage operation creates no Git tag or GitHub release. Check npm before
|
|
112
|
-
retrying an ambiguous outcome: a staged version reserves its version number.
|
|
113
|
-
Do not attempt to stage it again while awaiting approval. Approve or reject the
|
|
114
|
-
existing stage through npm. Rejection requires a reviewed replacement candidate.
|
|
115
|
-
|
|
116
|
-
The original release job accepts an existing public version only if integrity
|
|
117
|
-
matches its candidate. A conflicting archive, changed beta head or conflicting
|
|
118
|
-
Git tag stops recovery. Keep the beta branch at the reviewed commit until the
|
|
119
|
-
release is recorded. If its artifact expires, review recovery separately.
|
|
120
|
-
|
|
121
|
-
The maintainer removed the accidental `1.0.0` publication. Retain the
|
|
122
|
-
`0.0.0-stage` placeholder until `0.1.0-beta.1` is publicly available and verified.
|
|
123
|
-
With explicit authorization, remove that exact placeholder version and check
|
|
124
|
-
remaining versions and tags. Never unpublish the whole package. npm's unpublish
|
|
125
|
-
policy still applies; deleted versions cannot be reused.
|
|
126
|
-
See [npm's policy](https://docs.npmjs.com/policies/unpublish/).
|
|
127
|
-
|
|
128
|
-
## Security and platform limits
|
|
129
|
-
|
|
130
|
-
The runtime package has no npm dependencies or advisory exceptions. The
|
|
131
|
-
release-tool tree has bounded, executable advisory exceptions expiring November
|
|
132
|
-
9, 2026; it is not vulnerability-free. The privileged job does not install that
|
|
133
|
-
tree. See [release security](release-security.md).
|
|
134
|
-
|
|
135
|
-
Doppler is the only built-in cloud provider. Fresh native browser login has not
|
|
136
|
-
been verified end to end, Linux credential storage has mock coverage, and
|
|
137
|
-
Windows native login/storage is not implemented. Explicit tokens and existing
|
|
138
|
-
CLI credentials remain supported. Commands require explicit opt-in. Dotenvx
|
|
139
|
-
ciphertext is left unchanged. See [Security](../SECURITY.md).
|
|
140
|
-
|
|
141
|
-
Sources: [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/),
|
|
142
|
-
[staged publishing](https://docs.npmjs.com/cli/v11/commands/npm-stage/),
|
|
143
|
-
[semantic-release branching](https://semantic-release.org/foundation/workflow-configuration/).
|
|
50
|
+
| Workflow filename | `ci.yml` (filename only) |
|
|
51
|
+
| Environment name | `release` |
|
|
52
|
+
| Allow npm publish | Enabled |
|
|
53
|
+
| Allow npm dist-tag | Not needed for this single-branch release flow |
|
|
54
|
+
|
|
55
|
+
Save the configuration and complete npm's confirmation if prompted. Staging is
|
|
56
|
+
always allowed; enabling direct publishing is the required change. The branch
|
|
57
|
+
restriction lives in GitHub's `release` environment, which already allows only
|
|
58
|
+
`main`. Keep account 2FA and the existing token restrictions; no npm token is
|
|
59
|
+
needed. See [npm's trusted-publishing guide](https://docs.npmjs.com/trusted-publishers/).
|
|
60
|
+
|
|
61
|
+
Never publish from a developer machine, configure a permanent npm publishing
|
|
62
|
+
token, or request local npm login. Local `npm run release:check` is read-only
|
|
63
|
+
with respect to publication. Only the trusted main push can pass the release
|
|
64
|
+
guard. A merge does not waive a failing audit or the security review expiry.
|
|
65
|
+
|
|
66
|
+
## Failures
|
|
67
|
+
|
|
68
|
+
Inspect the failed run, npm version and GitHub tag before retrying. The standard
|
|
69
|
+
engine creates its tag before calling publishers; a failed publication may
|
|
70
|
+
therefore leave a tag or partial release. Rerunning is not a general repair for
|
|
71
|
+
that state. Recover the intended version using its original source and archive
|
|
72
|
+
after maintainer review; do not retag different code or silently replace a
|
|
73
|
+
published version. The initial bootstrap creates its tag after npm succeeds;
|
|
74
|
+
if GitHub recording fails, recover that published baseline before another run.
|
|
75
|
+
|
|
76
|
+
No workflow automatically deletes tags, releases or npm versions. Existing
|
|
77
|
+
beta versions without matching GitHub releases need separate recovery from
|
|
78
|
+
their original evidence. See [release security](release-security.md).
|
|
@@ -1,139 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"expires": "2026-11-09T00:00:00.000Z",
|
|
3
3
|
"owner": "titan-ron",
|
|
4
|
-
"scope": "
|
|
5
|
-
"exceptions": [
|
|
6
|
-
{
|
|
7
|
-
"advisory": "https://github.com/advisories/GHSA-q2hr-2g5m-vwhr",
|
|
8
|
-
"package": "brace-expansion",
|
|
9
|
-
"versions": [
|
|
10
|
-
"5.0.9"
|
|
11
|
-
],
|
|
12
|
-
"nodes": [
|
|
13
|
-
"node_modules/npm/node_modules/brace-expansion"
|
|
14
|
-
],
|
|
15
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"advisory": "https://github.com/advisories/GHSA-qhr7-859c-m2p7",
|
|
19
|
-
"package": "brace-expansion",
|
|
20
|
-
"versions": [
|
|
21
|
-
"5.0.9"
|
|
22
|
-
],
|
|
23
|
-
"nodes": [
|
|
24
|
-
"node_modules/npm/node_modules/brace-expansion"
|
|
25
|
-
],
|
|
26
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"advisory": "https://github.com/advisories/GHSA-6j4f-fj2g-mc7p",
|
|
30
|
-
"package": "brace-expansion",
|
|
31
|
-
"versions": [
|
|
32
|
-
"5.0.9"
|
|
33
|
-
],
|
|
34
|
-
"nodes": [
|
|
35
|
-
"node_modules/npm/node_modules/brace-expansion"
|
|
36
|
-
],
|
|
37
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"advisory": "https://github.com/advisories/GHSA-vfj7-8cjw-p6xm",
|
|
41
|
-
"package": "braces",
|
|
42
|
-
"versions": [
|
|
43
|
-
"3.0.3"
|
|
44
|
-
],
|
|
45
|
-
"nodes": [
|
|
46
|
-
"node_modules/braces"
|
|
47
|
-
],
|
|
48
|
-
"reason": "The planner blocks every braces callable and only uses micromatch matching, which delegates to picomatch."
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
"advisory": "https://github.com/advisories/GHSA-ch52-4w7c-c8xp",
|
|
52
|
-
"package": "http-cache-semantics",
|
|
53
|
-
"versions": [
|
|
54
|
-
"4.2.0"
|
|
55
|
-
],
|
|
56
|
-
"nodes": [
|
|
57
|
-
"node_modules/npm/node_modules/http-cache-semantics"
|
|
58
|
-
],
|
|
59
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"advisory": "https://github.com/advisories/GHSA-rpw4-54j3-4h4q",
|
|
63
|
-
"package": "ip-address",
|
|
64
|
-
"versions": [
|
|
65
|
-
"10.5.0"
|
|
66
|
-
],
|
|
67
|
-
"nodes": [
|
|
68
|
-
"node_modules/npm/node_modules/ip-address"
|
|
69
|
-
],
|
|
70
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"advisory": "https://github.com/advisories/GHSA-2vr4-cq9g-pvrc",
|
|
74
|
-
"package": "ip-address",
|
|
75
|
-
"versions": [
|
|
76
|
-
"10.5.0"
|
|
77
|
-
],
|
|
78
|
-
"nodes": [
|
|
79
|
-
"node_modules/npm/node_modules/ip-address"
|
|
80
|
-
],
|
|
81
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"advisory": "https://github.com/advisories/GHSA-j6r3-76f7-8jcv",
|
|
85
|
-
"package": "ip-address",
|
|
86
|
-
"versions": [
|
|
87
|
-
"10.5.0"
|
|
88
|
-
],
|
|
89
|
-
"nodes": [
|
|
90
|
-
"node_modules/npm/node_modules/ip-address"
|
|
91
|
-
],
|
|
92
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
"advisory": "https://github.com/advisories/GHSA-h3mg-xc3c-68pw",
|
|
96
|
-
"package": "ip-address",
|
|
97
|
-
"versions": [
|
|
98
|
-
"10.5.0"
|
|
99
|
-
],
|
|
100
|
-
"nodes": [
|
|
101
|
-
"node_modules/npm/node_modules/ip-address"
|
|
102
|
-
],
|
|
103
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
"advisory": "https://github.com/advisories/GHSA-rj75-hqrm-r3gf",
|
|
107
|
-
"package": "postcss-selector-parser",
|
|
108
|
-
"versions": [
|
|
109
|
-
"7.1.4"
|
|
110
|
-
],
|
|
111
|
-
"nodes": [
|
|
112
|
-
"node_modules/npm/node_modules/postcss-selector-parser"
|
|
113
|
-
],
|
|
114
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
"advisory": "https://github.com/advisories/GHSA-3wwx-pv8p-q78v",
|
|
118
|
-
"package": "undici",
|
|
119
|
-
"versions": [
|
|
120
|
-
"6.28.0"
|
|
121
|
-
],
|
|
122
|
-
"nodes": [
|
|
123
|
-
"node_modules/npm/node_modules/undici"
|
|
124
|
-
],
|
|
125
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"advisory": "https://github.com/advisories/GHSA-rfgv-xxqx-mfg5",
|
|
129
|
-
"package": "undici",
|
|
130
|
-
"versions": [
|
|
131
|
-
"6.28.0"
|
|
132
|
-
],
|
|
133
|
-
"nodes": [
|
|
134
|
-
"node_modules/npm/node_modules/undici"
|
|
135
|
-
],
|
|
136
|
-
"reason": "Bundled inside npm for the unselected @semantic-release/npm plugin. The planner blocks loading that plugin, the GitHub plugin and bundled npm; the publisher does not install release dependencies."
|
|
137
|
-
}
|
|
138
|
-
]
|
|
4
|
+
"scope": "No advisory exceptions; the complete installed tree must pass npm audit at moderate severity.",
|
|
5
|
+
"exceptions": []
|
|
139
6
|
}
|
package/doppler/auth.cjs
CHANGED
|
@@ -1,125 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var promises = require('node:timers/promises');
|
|
4
|
-
var node_module = require('node:module');
|
|
5
3
|
var errors = require('../errors.cjs');
|
|
6
|
-
var values = require('../values.cjs');
|
|
7
4
|
var http = require('./http.cjs');
|
|
8
5
|
var system = require('./system.cjs');
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
if (!values.isRecord(manifest) || typeof manifest.version !== 'string')
|
|
12
|
-
throw new Error('Invalid package version');
|
|
13
|
-
const { version } = manifest;
|
|
14
|
-
function credential(env, system$1 = system.createSystem()) {
|
|
7
|
+
function credential(env, system$1) {
|
|
15
8
|
return Promise.resolve().then(() => {
|
|
16
|
-
// An
|
|
9
|
+
// An explicit identity is never replaced after validation or API rejection.
|
|
17
10
|
if (env.DOPPLER_TOKEN !== undefined)
|
|
18
11
|
return http.validateToken(env.DOPPLER_TOKEN);
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
saved = system$1.read();
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
if (!values.hasErrorCode(err, 'DOPPLER_KEYRING_UNAVAILABLE'))
|
|
25
|
-
throw err;
|
|
26
|
-
}
|
|
27
|
-
if (saved)
|
|
28
|
-
return saved;
|
|
29
|
-
const token = system$1.cliToken();
|
|
12
|
+
const token = (system$1 || system.createSystem()).cliToken();
|
|
30
13
|
if (token)
|
|
31
14
|
return token;
|
|
32
|
-
throw new errors.ResenvError('DOPPLER_AUTH_REQUIRED', 'Run
|
|
15
|
+
throw new errors.ResenvError('DOPPLER_AUTH_REQUIRED', 'Run doppler login from your application directory, or supply DOPPLER_TOKEN. Startup never opens a login prompt.');
|
|
33
16
|
});
|
|
34
17
|
}
|
|
35
|
-
async function login({ cli = false, noBrowser = false } = {}, { request = http.createClient(), system: system$1 = system.createSystem(), output = text => process.stderr.write(`${text}\n`), interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY), env = process.env, sleep = promises.setTimeout, now = Date.now } = {}) {
|
|
36
|
-
if (!interactive || env.CI)
|
|
37
|
-
throw new errors.ResenvError('DOPPLER_INTERACTIVE_REQUIRED', 'Login requires an interactive local terminal outside CI. Supply DOPPLER_TOKEN for unattended runs.');
|
|
38
|
-
if (env.DOPPLER_TOKEN !== undefined)
|
|
39
|
-
throw new errors.ResenvError('DOPPLER_TOKEN_OVERRIDE', 'Unset DOPPLER_TOKEN before logging in; it overrides saved credentials.');
|
|
40
|
-
const fallback = ({ storeUnavailable = false } = {}) => {
|
|
41
|
-
output('Using Doppler CLI login.');
|
|
42
|
-
system$1.cliLogin();
|
|
43
|
-
if (system$1.canStore && !storeUnavailable) {
|
|
44
|
-
// Keep the identity chosen by this login ahead of an older resenv token.
|
|
45
|
-
try {
|
|
46
|
-
system$1.write(http.validateToken(system$1.cliToken()));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
throw new errors.ResenvError('DOPPLER_KEYRING_UNAVAILABLE', 'CLI login completed, but resenv could not update its saved credential. Unlock the credential store and retry.');
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
output('Done: Doppler CLI login completed.');
|
|
53
|
-
return 'cli';
|
|
54
|
-
};
|
|
55
|
-
if (cli || !system$1.canStore)
|
|
56
|
-
return fallback();
|
|
57
|
-
// Check keyring access before creating an authorization session.
|
|
58
|
-
try {
|
|
59
|
-
system$1.read();
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
if (!values.hasErrorCode(err, 'DOPPLER_KEYRING_UNAVAILABLE'))
|
|
63
|
-
throw err;
|
|
64
|
-
return fallback({ storeUnavailable: true });
|
|
65
|
-
}
|
|
66
|
-
let challenge;
|
|
67
|
-
try {
|
|
68
|
-
// Observed in Doppler's official MCP client, not a public OAuth contract.
|
|
69
|
-
// Identify resenv honestly rather than impersonating Doppler's MCP client.
|
|
70
|
-
const query = new URLSearchParams({ hostname: 'resenv', version: `v${version}`, os: process.platform, arch: process.arch });
|
|
71
|
-
challenge = await request(`/v3/auth/cli/generate/2?${query.toString()}`);
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
if (values.hasErrorCode(err, 'DOPPLER_ENDPOINT_UNAVAILABLE'))
|
|
75
|
-
return fallback();
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
let url;
|
|
79
|
-
try {
|
|
80
|
-
url = new URL(typeof challenge?.auth_url === 'string' ? challenge.auth_url : '');
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
if (!(err instanceof TypeError))
|
|
84
|
-
throw err;
|
|
85
|
-
}
|
|
86
|
-
if (!challenge || !url || url.origin !== 'https://dashboard.doppler.com' || url.username || url.password || url.pathname !== '/workplace/auth/cli' ||
|
|
87
|
-
typeof challenge.code !== 'string' || !/^[A-Za-z0-9_-]{8,256}$/.test(challenge.code) ||
|
|
88
|
-
typeof challenge.polling_code !== 'string' || !/^[A-Za-z0-9_.-]{8,512}$/.test(challenge.polling_code)) {
|
|
89
|
-
throw new errors.ResenvError('DOPPLER_INVALID_RESPONSE', 'Doppler returned an invalid browser authorization challenge.');
|
|
90
|
-
}
|
|
91
|
-
// Display the human authorization code only in the explicit login command.
|
|
92
|
-
output(`Authorize resenv at ${url.href}`);
|
|
93
|
-
output(`Authorization code: ${challenge.code}`);
|
|
94
|
-
if (!noBrowser)
|
|
95
|
-
system$1.openBrowser(url.href);
|
|
96
|
-
const deadline = now() + 300000;
|
|
97
|
-
while (now() < deadline) {
|
|
98
|
-
const result = await request('/v3/auth/cli/authorize', { body: { code: challenge.polling_code }, pending: true });
|
|
99
|
-
if (result) {
|
|
100
|
-
const token = http.validateToken(result.token);
|
|
101
|
-
try {
|
|
102
|
-
system$1.write(token);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
// Do not leave a newly minted, unpersisted credential active if storage fails.
|
|
106
|
-
try {
|
|
107
|
-
await request('/v3/auth/revoke', { token, body: { token } });
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
if (!(err instanceof errors.ResenvError)) {
|
|
111
|
-
throw new errors.ResenvError('DOPPLER_KEYRING_UNAVAILABLE', 'Could not save or revoke the login credential. Check Doppler CLI Tokens before retrying.');
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
throw new errors.ResenvError('DOPPLER_KEYRING_UNAVAILABLE', 'Could not save the login credential. Revocation was attempted; check Doppler CLI Tokens, then try resenv login doppler --cli.');
|
|
115
|
-
}
|
|
116
|
-
output('Done: Doppler login saved in the OS credential store.');
|
|
117
|
-
return 'native';
|
|
118
|
-
}
|
|
119
|
-
await sleep(2000);
|
|
120
|
-
}
|
|
121
|
-
throw new errors.ResenvError('DOPPLER_LOGIN_TIMEOUT', 'Doppler login timed out. Run resenv login doppler to try again.');
|
|
122
|
-
}
|
|
123
18
|
|
|
124
19
|
exports.credential = credential;
|
|
125
|
-
exports.login = login;
|
package/doppler/http.cjs
CHANGED
|
@@ -6,24 +6,19 @@ var values = require('../values.cjs');
|
|
|
6
6
|
const API_ORIGIN = 'https://api.doppler.com';
|
|
7
7
|
// No environment-controlled endpoint or redirects: credentials stay at Doppler.
|
|
8
8
|
function createClient(fetchImpl = globalThis.fetch) {
|
|
9
|
-
return async function request(path, { token
|
|
9
|
+
return async function request(path, { token } = {}) {
|
|
10
10
|
let response;
|
|
11
11
|
try {
|
|
12
12
|
response = await fetchImpl(new URL(path, API_ORIGIN), {
|
|
13
|
-
method:
|
|
13
|
+
method: 'GET',
|
|
14
14
|
headers: {
|
|
15
15
|
'content-type': 'application/json',
|
|
16
16
|
'user-agent': 'ciqol-resenv',
|
|
17
17
|
...(token ? { authorization: `Bearer ${token}` } : {})
|
|
18
18
|
},
|
|
19
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
20
19
|
redirect: 'error',
|
|
21
20
|
signal: AbortSignal.timeout(10000)
|
|
22
21
|
});
|
|
23
|
-
if (pending && response.status === 409) {
|
|
24
|
-
await response.body?.cancel();
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
22
|
if (!response.ok) {
|
|
28
23
|
await response.body?.cancel();
|
|
29
24
|
const code = response.status === 401 ? 'DOPPLER_AUTH_REQUIRED'
|
|
@@ -68,7 +63,7 @@ function createClient(fetchImpl = globalThis.fetch) {
|
|
|
68
63
|
}
|
|
69
64
|
function validateToken(token) {
|
|
70
65
|
if (typeof token !== 'string' || !/^dp\.[a-z]+\.[A-Za-z0-9_.-]{10,512}$/.test(token)) {
|
|
71
|
-
throw new errors.ResenvError('DOPPLER_INVALID_TOKEN', 'The Doppler credential is malformed.
|
|
66
|
+
throw new errors.ResenvError('DOPPLER_INVALID_TOKEN', 'The Doppler credential is malformed. Check your Doppler CLI login or supply a valid DOPPLER_TOKEN.');
|
|
72
67
|
}
|
|
73
68
|
return token;
|
|
74
69
|
}
|